• (39)STM32——FLASH闪存


    目录

    学习目标

    成果展示 

    介绍

    组成

    主存储器

    系统存储器

    OTP 区域

    选项字节

    读取

     编程

    寄存器

    步骤

    擦除

    扇区擦除

    批量擦除

    寄存器

    代码 

    总结 


    学习目标

            本节我们要来介绍一下关于FLASH闪存的知识,也是有关存储部分的知识,和之前学的EEPROM类似。FLASH结合了ROM和RAM的长处,不仅具备电子可擦除可编程(EEPROM)的性能,还可以快速读取数据(NVRAM的优势),使数据不会因为断电而丢失。

    成果展示 

    介绍

            不同型号的 STM32F40xx/41xx,其 FLASH 容量也有所不同,最小的只有 128K 字节,最大的则达到了 1024K 字节。探索者 STM32F4 开发板选择的 STM32F407ZGT6 的 FLASH 容量为 1024K 字节,STM32F40xx/41xx 的闪存模块组织如图所示:

    组成

             STM32F4 的闪存模块由:主存储器、系统存储器、OPT 区域和选项字节等 4 部分组成,接下来我们就来详细介绍一下各个部分。

    主存储器

            该部分用来存放代码和数据常数(如 const 类型的数据)。分为 12 个扇区,前 4 个扇区为 16KB 大小,然后扇区 4 是 64KB 大小,扇区 5~11 是128K 大小,不同容量的 STM32F4, 拥有的扇区数不一样,比如STM32F407ZGT6,则拥有全部 12 个扇区。从上图可以看出主存储器的起始地址就是 0X08000000, B0、B1 都接 GND 的时候,就是从 0X08000000 开始 运行代码的。

    系统存储器

            主要用来存放 STM32F4 的 bootloader 代码,此代码是出厂的时候就固化在 STM32F4 里面了,专门来给主存储器下载代码的。当 B0 接 V3.3,B1 接 GND 的时候,从该存储器启动(即进入串口下载模式)。

    OTP 区域

            即一次性可编程区域,共 528 字节,被分成两个部分,前面 512 字节(32 字节为 1 块,分成 16 块),可以用来存储一些用户数据(永远不可以擦除), 后面 16 字节,用于锁定对应块。

    选项字节

            用于配置读保护、BOR 级别、软件/硬件看门狗以及器件处于待机或停止模式下的复位。

            闪存存储器接口寄存器,该部分用于控制闪存读写等,是整个闪存模块的控制机构。

            在执行闪存写操作时,任何对闪存的读操作都会锁住总线,在写操作完成后读操作才能正确地进行;既在进行写或擦除操作时,不能进行代码或数据的读取操作。

    读取

            STM23F4 的 FLASH 读取是很简单的。例如,我们要从地址 addr,读取一个字(字节为 8位,半字为 16 位,字为 32 位),可以通过如下的语句读取:

    data=*(vu32*)addr;

            将 addr 强制转换为 vu32 指针,然后取该指针所指向的地址的值,即得到了 addr 地址的值。 类似的,将上面的 vu32 改为 vu16,即可读取指定地址的一个半字。相对 FLASH 读取来说, STM32F4 FLASH 的写就复杂一点了,下面我们介绍 STM32F4 闪存的编程和擦除。

            为了准确读取 Flash 数据, 必须根据 CPU 时钟 (HCLK) 频率和器件电源电压在 Flash 存取控制寄存器 (FLASH_ACR) 中正确地设置等待周期数 (LATENCY)。当电源电压低于 2.1V 时,必须关闭预取缓冲器。Flash 等待周期与 CPU 时钟频率之间的对应关系,如表所示:

            供电电压,我们一般是 3.3V,所以,在我们设置 168Mhz 频率作为 CPU 时钟之前,必须先设置 LATENCY 为 5,否则 FLASH 读写可能出错,导致死机。 

     编程

    • 执行任何 Flash 编程操作(擦除或编程)时,CPU 时钟频率 (HCLK)不能低于 1 MHz。如果在 Flash 操作期间发生器件复位,无法保证 Flash 中的内容。
    • 在对 STM32F4 的 Flash 执行写入或擦除操作期间,任何读取 Flash 的尝试都会导致总线阻塞。只有在完成编程操作后,才能正确处理读操作。这意味着,写/擦除操作进行期间不能从 Flash 中执行代码或数据获取操作。

    寄存器

    • FLASH 访问控制寄存器(FLASH_ACR)
    • FLASH 秘钥寄存器(FLASH_KEYR)
    • FLASH 选项秘钥寄存器(FLASH_OPTKEYR)
    • FLASH 状态寄存器(FLASH_SR)
    • FLASH 控制寄存器(FLASH_CR)
    • FLASH 选项控制寄存器(FLASH_OPTCR)

            STM32F4 复位后,FLASH 编程操作是被保护的,不能写入 FLASH_CR 寄存器;通过写入特定的序列(0X45670123 和 0XCDEF89AB)到 FLASH_KEYR 寄存器才可解除写保护,只有在写保护被解除后,我们才能操作相关寄存器。

    1. 写 0X45670123 到 FLASH_KEYR
    2. 写 0XCDEF89AB 到 FLASH_KEYR

            通过这两个步骤,即可解锁 FLASH_CR,如果写入错误,那么 FLASH_CR 将被锁定,直到下次复位后才可以再次解锁。

            STM32F4 闪存的编程位数可以通过 FLASH_CR 的 PSIZE 字段配置,PSIZE 的设置必须和电源电压匹配,由于我们开发板用的电压是 3.3V,所以 PSIZE 必须设置为 10,即 32 位并行位数。擦除或 者编程,都必须以 32 位为基础进行,见表:

            STM32F4 的 FLASH 在编程的时候,也必须要求其写入地址的 FLASH 是被擦除了的(也就是其值必须是 0XFFFFFFFF),否则无法写入,而且擦除是按片区来擦除的。

    步骤

    • 检查 FLASH_SR 中的 BSY 位,确保当前未执行任何 FLASH 操作。
    • 将 FLASH_CR 寄存器中的 PG 位置 1,激活 FLASH 编程。
    • 针对所需存储器地址(主存储器块或 OTP 区域内)执行数据写入操作:
      • 并行位数为 x8 时按字节写入(PSIZE=00) 
      • 并行位数为 x16 时按半字写入(PSIZE=01)
      • 并行位数为 x32 时按字写入(PSIZE=02)(我们是这个)
      • 并行位数为 x64 时按双字写入(PSIZE=03)
    • 等待 BSY 位清零,完成一次编程。

            按以上四步操作,就可以完成一次 FLASH 编程。不过有几点要注意:

    1. 编程前,要确保 要写如地址的 FLASH 已经擦除。
    2. 要先解锁(否则不能操作 FLASH_CR)。
    3. 编程操作对 OPT 区域也有效,方法一模一样。

    擦除

            我们在 STM32F4 的 FLASH 编程的时候,首先需要判断缩写地址是否被擦除了,所以,我们来介绍一下擦除操作,擦除主要分为扇区擦除和整片擦除,我们简单的来介绍一下扇区擦除。

    扇区擦除

    1. 检查 FLASH_CR 的 LOCK 是否解锁,如果没有则先解锁
    2. 检查 FLASH_SR 寄存器中的 BSY 位,确保当前未执行任何 FLASH 操作
    3. 在 FLASH_CR 寄存器中,将 SER 位置 1,并从主存储块的 12 个扇区中选择要擦除的 扇区 (SNB)
    4. 将 FLASH_CR 寄存器中的 STRT 位置 1,触发擦除操作
    5. 等待 BSY 位清零

    批量擦除

    1. 检查 FLASH_SR 寄存器中的 BSY 位,确保当前未执行任何 FLASH 操作
    2. 在 FLASH_CR 寄存器中,将 MER 位置 1
    3. 在 FLASH_CR 寄存器中,将 STRT 位置 1,触发擦除操作
    4. 等待 BSY 位清零

    寄存器

            寄存器还是不详细介绍了,感兴趣的同学可以去看开发手册。

    代码 

    1. // stmflash.c
    2. #include "stmflash.h"
    3. #include "delay.h"
    4. #include "usart.h"
    5. //读取指定地址的半字(16位数据)
    6. //faddr:读地址
    7. //返回值:对应数据.
    8. u32 STMFLASH_ReadWord(u32 faddr)
    9. {
    10. return *(vu32*)faddr;
    11. }
    12. //获取某个地址所在的flash扇区
    13. //addr:flash地址
    14. //返回值:0~11,即addr所在的扇区
    15. uint16_t STMFLASH_GetFlashSector(u32 addr)
    16. {
    17. if(addrreturn FLASH_Sector_0;
    18. else if(addrreturn FLASH_Sector_1;
    19. else if(addrreturn FLASH_Sector_2;
    20. else if(addrreturn FLASH_Sector_3;
    21. else if(addrreturn FLASH_Sector_4;
    22. else if(addrreturn FLASH_Sector_5;
    23. else if(addrreturn FLASH_Sector_6;
    24. else if(addrreturn FLASH_Sector_7;
    25. else if(addrreturn FLASH_Sector_8;
    26. else if(addrreturn FLASH_Sector_9;
    27. else if(addrreturn FLASH_Sector_10;
    28. return FLASH_Sector_11;
    29. }
    30. //从指定地址开始写入指定长度的数据
    31. //特别注意:因为STM32F4的扇区实在太大,没办法本地保存扇区数据,所以本函数
    32. // 写地址如果非0XFF,那么会先擦除整个扇区且不保存扇区数据.所以
    33. // 写非0XFF的地址,将导致整个扇区数据丢失.建议写之前确保扇区里
    34. // 没有重要数据,最好是整个扇区先擦除了,然后慢慢往后写.
    35. //该函数对OTP区域也有效!可以用来写OTP区!
    36. //OTP区域地址范围:0X1FFF7800~0X1FFF7A0F
    37. //WriteAddr:起始地址(此地址必须为4的倍数!!)
    38. //pBuffer:数据指针
    39. //NumToWrite:字(32位)数(就是要写入的32位数据的个数.)
    40. void STMFLASH_Write(u32 WriteAddr,u32 *pBuffer,u32 NumToWrite)
    41. {
    42. FLASH_Status status = FLASH_COMPLETE;
    43. u32 addrx=0;
    44. u32 endaddr=0;
    45. if(WriteAddr4)return; //非法地址,对于有效性进行判断
    46. FLASH_Unlock(); //解锁
    47. FLASH_DataCacheCmd(DISABLE);//FLASH擦除期间,必须禁止数据缓存
    48. addrx=WriteAddr; //写入的起始地址
    49. endaddr=WriteAddr+NumToWrite*4; //写入的结束地址
    50. if(addrx<0X1FFF0000) //只有主存储区,才需要执行擦除操作!!
    51. {
    52. while(addrx//扫清一切障碍.(对非FFFFFFFF的地方,先擦除)
    53. {
    54. if(STMFLASH_ReadWord(addrx)!=0XFFFFFFFF)//有非0XFFFFFFFF的地方,要擦除这个扇区
    55. {
    56. status=FLASH_EraseSector(STMFLASH_GetFlashSector(addrx),VoltageRange_3);//VCC=2.7~3.6V之间!!
    57. if(status!=FLASH_COMPLETE)break; //发生错误了
    58. }else addrx+=4;
    59. }
    60. }
    61. if(status==FLASH_COMPLETE)
    62. {
    63. while(WriteAddr//写数据
    64. {
    65. if(FLASH_ProgramWord(WriteAddr,*pBuffer)!=FLASH_COMPLETE)//写入数据
    66. {
    67. break; //写入异常
    68. }
    69. WriteAddr+=4;
    70. pBuffer++;
    71. }
    72. }
    73. FLASH_DataCacheCmd(ENABLE); //FLASH擦除结束,开启数据缓存
    74. FLASH_Lock();//上锁
    75. }
    76. //从指定地址开始读出指定长度的数据
    77. //ReadAddr:起始地址
    78. //pBuffer:数据指针
    79. //NumToRead:字(4位)数
    80. void STMFLASH_Read(u32 ReadAddr,u32 *pBuffer,u32 NumToRead)
    81. {
    82. u32 i;
    83. for(i=0;i
    84. {
    85. pBuffer[i]=STMFLASH_ReadWord(ReadAddr);//读取4个字节.
    86. ReadAddr+=4;//偏移4个字节.
    87. }
    88. }

    1. //main.c
    2. #include "sys.h"
    3. #include "delay.h"
    4. #include "usart.h"
    5. #include "led.h"
    6. #include "stmflash.h"
    7. #include "key.h"
    8. //要写入到STM32 FLASH的字符串数组
    9. const u8 TEXT_Buffer[]={"STM32 FLASH TEST"};
    10. #define TEXT_LENTH sizeof(TEXT_Buffer) //数组长度
    11. #define SIZE TEXT_LENTH/4+((TEXT_LENTH%4)?1:0)
    12. #define FLASH_SAVE_ADDR 0X0800C004 //设置FLASH 保存地址(必须为偶数,且所在扇区,要大于本代码所占用到的扇区.
    13. //否则,写操作的时候,可能会导致擦除整个扇区,从而引起部分程序丢失.引起死机.
    14. int main(void)
    15. {
    16. u8 key=0;
    17. u16 i=0;
    18. u8 datatemp[SIZE];
    19. NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
    20. delay_init(168); //初始化延时函数
    21. uart_init(115200); //初始化串口波特率为115200
    22. LED_Init(); //初始化LED
    23. KEY_Init(); //按键初始化 ;
    24. printf ("KEY1:Write KEY0:Read");
    25. printf ("\r\n\r\n");
    26. while(1)
    27. {
    28. key=KEY_Scan(0);
    29. if(key==KEY1_PRES) //KEY1按下,写入STM32 FLASH
    30. {
    31. printf ("Start Write FLASH....");
    32. printf ("\r\n\r\n");
    33. STMFLASH_Write(FLASH_SAVE_ADDR,(u32*)TEXT_Buffer,SIZE);
    34. printf ("FLASH Write Finished!");//提示传送完成
    35. printf ("\r\n\r\n");
    36. }
    37. if(key==KEY0_PRES) //KEY0按下,读取字符串并显示
    38. {
    39. printf ("Start Read FLASH.... ");
    40. printf ("\r\n\r\n");
    41. STMFLASH_Read(FLASH_SAVE_ADDR,(u32*)datatemp,SIZE);
    42. printf ("The Data Readed Is: %s ",datatemp);//提示传送完成
    43. printf ("\r\n\r\n");
    44. }
    45. i++;
    46. delay_ms(10);
    47. if(i==20)
    48. {
    49. LED0=!LED0;//提示系统正在运行
    50. i=0;
    51. }
    52. }
    53. }

    总结 

            这节FLASH的内容,难度不是特别大,但是感觉还是慢有用的,加深了之前学到的有关存储的知识 。

  • 相关阅读:
    2.5 C#视觉程序开发实例1----CamManager实现模拟相机采集图片(Form_Vision部分代码)
    php 命名空间
    176. 装满的油箱 图 - 拆点
    RocketMQ部署
    设计模式-迭代器模式-笔记
    【React】手把手学习React - 元素渲染
    CFdiv2-Beautiful Mirrors-(期望)
    封装window10-21H1踩的坑,无法分析或处理pass[specialize]应答文件
    LSTM的预测算法 - 股票预测 天气预测 房价预测 计算机竞赛
    【面试经验包】面试被吊打经验总结
  • 原文地址:https://blog.csdn.net/weixin_66578482/article/details/126779054