[应用相关] 如何快速读写MCU内部flash?

[复制链接]
648|4
 楼主| 中国龙芯CDX 发表于 2023-12-28 10:52 | 显示全部楼层 |阅读模式
一、 读写步骤
内部flash我们参照HAL库或者标准库,直接调用ST公司给我们封装好的库进行编程就可以了,这里我用的是标准库,有兴趣的小伙伴可以去看看HAL库。

  1. /*------------ Functions used for all STM32F10x devices -----*/
  2. void FLASH_SetLatency(uint32_t FLASH_Latency);
  3. void FLASH_HalfCycleAccessCmd(uint32_t FLASH_HalfCycleAccess);
  4. void FLASH_PrefetchBufferCmd(uint32_t FLASH_PrefetchBuffer);
  5. void FLASH_Unlock(void);
  6. void FLASH_Lock(void);
  7. FLASH_Status FLASH_ErasePage(uint32_t Page_Address);
  8. FLASH_Status FLASH_EraseAllPages(void);
  9. FLASH_Status FLASH_EraseOptionBytes(void);
  10. FLASH_Status FLASH_ProgramWord(uint32_t Address, uint32_t Data);
  11. FLASH_Status FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data);
  12. FLASH_Status FLASH_ProgramOptionByteData(uint32_t Address, uint8_t Data);
  13. FLASH_Status FLASH_EnableWriteProtection(uint32_t FLASH_Pages);
  14. FLASH_Status FLASH_ReadOutProtection(FunctionalState NewState);
  15. FLASH_Status FLASH_UserOptionByteConfig(uint16_t OB_IWDG, uint16_t OB_STOP, uint16_t OB_STDBY);
  16. uint32_t FLASH_GetUserOptionByte(void);
  17. uint32_t FLASH_GetWriteProtectionOptionByte(void);

  18. FlagStatus FLASH_GetReadOutProtectionStatus(void);
  19. FlagStatus FLASH_GetPrefetchBufferStatus(void);
  20. void FLASH_ITConfig(uint32_t FLASH_IT, FunctionalState NewState);
  21. FlagStatus FLASH_GetFlagStatus(uint32_t FLASH_FLAG);
  22. void FLASH_ClearFlag(uint32_t FLASH_FLAG);
  23. FLASH_Status FLASH_GetStatus(void);
  24. FLASH_Status FLASH_WaitForLastOperation(uint32_t Timeout);
  25. /*------------ New function used for all STM32F10x devices -----*/
  26. void FLASH_UnlockBank1(void);
  27. void FLASH_LockBank1(void);
  28. FLASH_Status FLASH_EraseAllBank1Pages(void);
  29. FLASH_Status FLASH_GetBank1Status(void);
  30. FLASH_Status FLASH_WaitForLastBank1Operation(uint32_t Timeout);



在这里就不一个一个的详细说了,我们说一下常用的就行

1. 解锁
void FLASH_Unlock(void);

2. 上锁
void FLASH_Lock(void);

3. 页擦除
FLASH_Status FLASH_ErasePage(uint32_t Page_Address);

4. 半字写入
FLASH_Status FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data);

上面这4个函数就是我们最常用的。

下面说一下数据写入的步骤:

第一步:解锁。

第二步:判断写入的数据是否被擦除过,也就是判断写入的地址内存放的是不是0xFFFF 这里要重点说一下,为什么要判断是不是0xFFFF而不是判断是不是0xFF呢?因为我们每次写入数据都要写入半字,也就是两个字节的数据才行,而且写入的地址只能是2的整数倍,不能是奇数。这里大家注意一下。

第三步:写入数据 STM32F103C8T6只能按照半字的方式进行数据写入,写入前的数据必须是0XFFFF,因为FLASH数据写入,只能写0,不能写1,这也就是为什么我们要先确保写入前的数据是被擦除了的原因。

第四步:上锁。

第五步:验证写入是否正确。

其实第五步可以省略。
 楼主| 中国龙芯CDX 发表于 2023-12-28 10:54 | 显示全部楼层
本帖最后由 中国龙芯CDX 于 2023-12-28 11:00 编辑

我们看看官方给的写入过程:

1.png


好了,其实是一样的。下面我就和大家来分享一下(百分之九十九参考的正点原子的例程)。

  1. //不检查的写入
  2. //WriteAddr:起始地址
  3. //pBuffer:数据指针
  4. //NumToWrite:半字(16位)数   
  5. void STMFLASH_Write_NoCheck(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)   
  6. {                                          
  7.         u16 i;
  8.         for(i=0;i<NumToWrite;i++)
  9.         {
  10.                 FLASH_ProgramHalfWord(WriteAddr,pBuffer);
  11.             WriteAddr+=2;//地址增加2.
  12.         }  
  13. }

  14. //从指定地址开始写入指定长度的数据
  15. //WriteAddr:起始地址(此地址必须为2的倍数!!)
  16. //pBuffer:数据指针
  17. //NumToWrite:半字(16位)数(就是要写入的16位数据的个数.)
  18. u16 STMFLASH_BUF[STM_SECTOR_SIZE/2];//最多是2K字节
  19. void STMFLASH_Write(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)        
  20. {
  21.         u32 secpos;           //扇区地址
  22.         u16 secoff;           //扇区内偏移地址(16位字计算)
  23.         u16 secremain; //扇区内剩余地址(16位字计算)           
  24.          u16 i;   
  25.         u32 offaddr;   //去掉0X08000000后的地址
  26.         if(WriteAddr<STM32_FLASH_BASE||(WriteAddr>=(STM32_FLASH_BASE+1024*STM32_FLASH_SIZE)))return;//非法地址
  27.         FLASH_Unlock();                                                //解锁
  28.         offaddr=WriteAddr-STM32_FLASH_BASE;                //实际偏移地址.
  29.         secpos=offaddr/STM_SECTOR_SIZE;                        //扇区地址  0~127 for STM32F103RBT6
  30.         secoff=(offaddr%STM_SECTOR_SIZE)/2;                //在扇区内的偏移(2个字节为基本单位.)
  31.         secremain=STM_SECTOR_SIZE/2-secoff;                //扇区剩余空间大小   
  32.         if(NumToWrite<=secremain)
  33.         {
  34.           secremain=NumToWrite;//不大于该扇区范围
  35.         }
  36.         while(1)
  37.         {        
  38.                 STMFLASH_Read(((secpos*STM_SECTOR_SIZE)+STM32_FLASH_BASE),STMFLASH_BUF,STM_SECTOR_SIZE/2);//读出整个扇区的内容
  39.                 for(i=0;i<secremain;i++)//校验数据
  40. //                for(i=0;i<(STM_SECTOR_SIZE/2);i++)//校验数据
  41.                 {
  42.                         if(STMFLASH_BUF[secoff+i]!=0XFFFF)break;//需要擦除  
  43. //        if(STMFLASH_BUF!=0XFFFF)break;//需要擦除                        
  44.                 }
  45.                 FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);
  46.                 if(i<secremain)//需要擦除
  47. //                if(i<(STM_SECTOR_SIZE/2))//需要擦除
  48.                 {
  49.                         FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);
  50.                         FLASH_ErasePage(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE);//擦除这个扇区
  51.                         for(i=0;i<secremain;i++)//复制
  52.                         {
  53.                                 STMFLASH_BUF[i+secoff]=pBuffer;         
  54.                         }
  55.                         STMFLASH_Write_NoCheck(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//写入整个扇区  
  56.                 }else STMFLASH_Write_NoCheck(WriteAddr,pBuffer,secremain);//写已经擦除了的,直接写入扇区剩余区间.                                    
  57.                 if(NumToWrite==secremain)break;//写入结束了
  58.                 else//写入未结束
  59.                 {
  60.                                 secpos++;                                //扇区地址增1
  61.                                 secoff=0;                                //偏移位置为0         
  62.                                   pBuffer+=secremain;          //指针偏移
  63.                                 WriteAddr+=(secremain*2);        //写地址偏移           
  64.                                  NumToWrite-=secremain;        //字节(16位)数递减
  65.                                 if(NumToWrite>(STM_SECTOR_SIZE/2))
  66.                                 {
  67.                                   secremain=STM_SECTOR_SIZE/2;//下一个扇区还是写不完
  68.                                 }
  69.                                 else
  70.                                 {
  71.                                   secremain=NumToWrite;//下一个扇区可以写完了
  72.                                 }
  73.                 }         
  74.         }        
  75.         FLASH_Lock();//上锁
  76. }

最终我们调用STMFLASH_Write()函数进行数据的写入,是不是有没看懂的小伙伴,我给大家解释一下写入的过程吧。
 楼主| 中国龙芯CDX 发表于 2023-12-28 10:56 | 显示全部楼层

这个STMFLASH_Write()函数,是说给定一个写入的地址、数据和写入的个数,然后按照给定的地址开始写数据,注意红色字体。

写数据是怎么做的呢?

首先是整理一下写入的页地址和需要写入多少页,每一页写入的话起始地址是什么
然后开始一页一页的写,当遇到跨页写入的时候,把第二页的地址写进去,写的个数继续写入就行。

还有一个地方很重要,就是我修改了库函数:

  1. /**
  2.   * [url=home.php?mod=space&uid=247401]@brief[/url]  Programs a half word at a specified address.
  3.   * [url=home.php?mod=space&uid=536309]@NOTE[/url]   This function can be used for all STM32F10x devices.
  4.   * [color=Red]@param[/color]  Address: specifies the address to be programmed.
  5.   * [color=Red]@param[/color]  Data: specifies the data to be programmed.
  6.   * [color=Red]@retval[/color] FLASH Status: The returned value can be: FLASH_ERROR_PG,
  7.   *         FLASH_ERROR_WRP, FLASH_COMPLETE or FLASH_TIMEOUT.
  8.   */
  9. FLASH_Status FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data)
  10. {
  11.         FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);
  12.   FLASH_Status status = FLASH_COMPLETE;
  13.   /* Check the parameters */
  14.   assert_param(IS_FLASH_ADDRESS(Address));
  15. #ifdef STM32F10X_XL
  16.   /* Wait for last operation to be completed */
  17.   status = FLASH_WaitForLastOperation(ProgramTimeout);
  18.   if(Address < FLASH_BANK1_END_ADDRESS)
  19.   {
  20.     if(status == FLASH_COMPLETE)
  21.     {
  22.       /* if the previous operation is completed, proceed to program the new data */
  23.       FLASH->CR |= CR_PG_Set;
  24.       *(__IO uint16_t*)Address = Data;
  25.       /* Wait for last operation to be completed */
  26.       status = FLASH_WaitForLastBank1Operation(ProgramTimeout);
  27.       /* Disable the PG Bit */
  28.       FLASH->CR &= CR_PG_Reset;
  29.     }
  30.   }
  31.   else
  32.   {
  33.     if(status == FLASH_COMPLETE)
  34.     {
  35.       /* if the previous operation is completed, proceed to program the new data */
  36.       FLASH->CR2 |= CR_PG_Set;
  37.       *(__IO uint16_t*)Address = Data;
  38.       /* Wait for last operation to be completed */
  39.       status = FLASH_WaitForLastBank2Operation(ProgramTimeout);
  40.       /* Disable the PG Bit */
  41.       FLASH->CR2 &= CR_PG_Reset;
  42.     }
  43.   }
  44. #else
  45.   /* Wait for last operation to be completed */
  46.   status = FLASH_WaitForLastOperation(ProgramTimeout);
  47.   if(status == FLASH_COMPLETE)
  48.   {
  49.     /* if the previous operation is completed, proceed to program the new data */
  50.     FLASH->CR |= CR_PG_Set;
  51.     *(__IO uint16_t*)Address = Data;
  52.     /* Wait for last operation to be completed */
  53.     status = FLASH_WaitForLastOperation(ProgramTimeout);
  54.     /* Disable the PG Bit */
  55.     FLASH->CR &= CR_PG_Reset;
  56.   }
  57. #endif  /* STM32F10X_XL */
  58.   /* Return the Program Status */
  59.   return status;
  60. }


大家能看出来吗?就是红色字体部分,增加了一个每次写入前清除所有异常状态。
为什么添加这个呢?
 楼主| 中国龙芯CDX 发表于 2023-12-28 11:01 | 显示全部楼层

因为,如果你写入的数据的地址没有擦除,你就写入的话会导致异常状态的发生,而这个异常状态时要手动清除的,如果你没有清除这个异常状态,而继续写入数据的话,那么你后面写入任何数据都会报错,均写不进去,所以我在这里增加了一个异常状态清除,如果前面写入的数据报错了,不会影响我接下来的数据写入。

这里大家就清除为什么了吧。

写数据会了,那么再说一下读数据,其实这里读数据要比外部flash读取容易的多,我们直接读取地址,返回的就是地址存放的数据,是不是很简单。

看下面的函数:

  1. //读取指定地址的半字(16位数据)
  2. //faddr:读地址(此地址必须为2的倍数!!)
  3. //返回值:对应数据.
  4. u16 STMFLASH_ReadHalfWord(u32 faddr)
  5. {
  6.         return *(vu16*)faddr;
  7. }
  8. //从指定地址开始读出指定长度的数据
  9. //ReadAddr:起始地址
  10. //pBuffer:数据指针
  11. //NumToWrite:半字(16位)数
  12. void STMFLASH_Read(u32 ReadAddr,u16 *pBuffer,u16 NumToRead)           
  13. {
  14.         u16 i;
  15.         for(i=0;i<NumToRead;i++)
  16.         {
  17.                 pBuffer=STMFLASH_ReadHalfWord(ReadAddr);//读取2个字节.
  18.                 ReadAddr+=2;//偏移2个字节.        
  19.         }
  20. }

有没有很开心,读写数据就是这么简单就完成了。

以后如果我们想开发BootLoader、把剩余的flash利用起来,就都很简单了。我会把用到的数据手册当成附件挂到下面,大家可以自行下载
AdaMaYun 发表于 2023-12-28 17:50 | 显示全部楼层
如果写入的数据的地址没有擦除,会导致异常状态的发生,这个必须手动清除错误嘛?
您需要登录后才可以回帖 登录 | 注册

本版积分规则

339

主题

2677

帖子

4

粉丝
快速回复 在线客服 返回列表 返回顶部