[STM32F1] 读写flash的注意事项

[复制链接]
 楼主| jackcat 发表于 2024-7-18 03:06 | 显示全部楼层 |阅读模式
解锁前确认状态: 在执行FLASH解锁操作之前,确实应该确认FLASH是否已被锁定。直接对一个未锁定的FLASH执行解锁指令可能会导致硬件错误(HardFault),因为解锁指令通常是设计用来移除写保护的,对一个已解锁的FLASH再次执行解锁操作是没有意义的,并且在某些情况下可能导致不可预料的行为。

擦除前关闭数据缓存: 这一点至关重要。在执行FLASH擦除操作前,必须确保数据缓存(DCEN位)是关闭状态。如果不关闭,会导致擦除不成功或出现其它未知错误

擦除后执行缓存复位: 擦除操作完成之后,执行数据缓存复位(DCRST位)是一个好的实践。这样做可以确保缓存中任何可能残留的、与FLASH当前状态不符的数据被清除,防止后续读取操作出现混乱。特别是对于连续的写入-擦除-读取序列,这一步骤能够保证读取到的数据是最新的。(遇到的真实情况是:未复位擦除后去访问flash数据,最开始访问的数据可能是擦除前的数据,而不是此刻真实的flash中的数据)

  1. /*
  2. * @Author: duanhuisheng
  3. * @Date: 2021-11-03 21:36:14
  4. * @LastEditTime: 2022-05-24 18:22:23
  5. * @LastEditors: duanhuisheng
  6. * @Description:
  7. * stm32f407用于参数平衡擦写读写flash的注意事项: 解锁前一定要确定是锁住的,或者执行解锁指令会产生 hardfauat
  8. * 错误中断  导致程序跑飞 ,另外在擦除扇区前一定要关闭数据缓存
  9. * 或者擦除失败,擦除完成后要执行一下缓存复位,不然擦除后去访问flash数据,最开始访问的数据可能是擦除前的数据,而不是此刻真实的flash中的数据
  10. *
  11. */
  12. #include <MString.h>

  13. static void delay(volatile int32_t length)
  14. {
  15.     while (length)
  16.         length--;
  17. }

  18. // FLASH起始地址
  19. #define STM32_FLASH_BASE 0x08000000  // STM32 FLASH的起始地址
  20. // FLASH解锁键值
  21. #define FLASH_KEY1 0X45670123
  22. #define FLASH_KEY2 0XCDEF89AB

  23. // FLASH 扇区的起始地址
  24. #define ADDR_FLASH_SECTOR_0 ((u32)0x08000000)   // 扇区0起始地址, 16 Kbytes
  25. #define ADDR_FLASH_SECTOR_1 ((u32)0x08004000)   // 扇区1起始地址, 16 Kbytes
  26. #define ADDR_FLASH_SECTOR_2 ((u32)0x08008000)   // 扇区2起始地址, 16 Kbytes
  27. #define ADDR_FLASH_SECTOR_3 ((u32)0x0800C000)   // 扇区3起始地址, 16 Kbytes
  28. #define ADDR_FLASH_SECTOR_4 ((u32)0x08010000)   // 扇区4起始地址, 64 Kbytes
  29. #define ADDR_FLASH_SECTOR_5 ((u32)0x08020000)   // 扇区5起始地址, 128 Kbytes
  30. #define ADDR_FLASH_SECTOR_6 ((u32)0x08040000)   // 扇区6起始地址, 128 Kbytes
  31. #define ADDR_FLASH_SECTOR_7 ((u32)0x08060000)   // 扇区7起始地址, 128 Kbytes
  32. #define ADDR_FLASH_SECTOR_8 ((u32)0x08080000)   // 扇区8起始地址, 128 Kbytes
  33. #define ADDR_FLASH_SECTOR_9 ((u32)0x080A0000)   // 扇区9起始地址, 128 Kbytes
  34. #define ADDR_FLASH_SECTOR_10 ((u32)0x080C0000)  // 扇区10起始地址,128 Kbytes
  35. #define ADDR_FLASH_SECTOR_11 ((u32)0x080E0000)  // 扇区11起始地址,128 Kbytes

  36. // 解锁STM32的FLASH
  37. static void STMFLASH_Unlock(void)
  38. {
  39.     FLASH->KEYR = FLASH_KEY1;  // 写入解锁序列.
  40.     FLASH->KEYR = FLASH_KEY2;
  41. }
  42. // flash上锁
  43. static void STMFLASH_Lock(void)
  44. {
  45.     FLASH->CR |= (u32)1 << 31;  // 上锁
  46. }
  47. // 得到FLASH状态
  48. // 返回值:
  49. //  0,操作完成
  50. //  1,忙
  51. //  2,操作异常
  52. static uint8_t STMFLASH_GetStatus(void)
  53. {
  54.     u32 res = 0;
  55.     res     = FLASH->SR;
  56.     if (res & (1 << 16))
  57.         return 1;  // 忙
  58.     else if (res & (1 << 4))
  59.         return 2;  // 操作异常
  60.     else if (res & (1 << 5))
  61.         return 2;  // 操作异常
  62.     else if (res & (1 << 6))
  63.         return 2;  // 操作异常
  64.     else if (res & (1 << 7))
  65.         return 2;  // 操作异常
  66.     return 0;      // 没有任何状态/操作完成.
  67. }
  68. // 等待操作完成
  69. //  time:要延时的长短(单位:10us)
  70. // 返回值:
  71. //  0,完成
  72. //  2,操作异常
  73. //  0XFF,超时
  74. static uint8_t STMFLASH_WaitDone(u32 time)
  75. {
  76.     uint8_t res;
  77.     do
  78.     {
  79.         res = STMFLASH_GetStatus();
  80.         if (res != 1)
  81.             break;  // 非忙,无需等待了,直接退出.
  82.         // delay_us(10);
  83.         time--;
  84.     } while (time);
  85.     if (time == 0)
  86.         res = 0xff;  // TIMEOUT
  87.     return res;
  88. }

  89. // 在FLASH指定地址写一个字
  90. //  faddr:指定地址(此地址必须为4的倍数!!)
  91. //  dat:要写入的数据
  92. // 返回值:0,写入成功
  93. //     其他,写入失败
  94. static uint8_t STMFLASH_WriteWord(u32 faddr, u32 dat)
  95. {
  96.     uint8_t res;
  97.     FLASH->SR |= 0xc0;
  98.     res = STMFLASH_WaitDone(0XFFFFFf);
  99.     if (res == 0)  // OK
  100.     {
  101.         FLASH->CR &= ~(3 << 8);  // 清除PSIZE原来的设置
  102.         FLASH->CR |= 2 << 8;
  103.         FLASH->CR |= 1 << 0;  // 编程使能
  104.         while ((FLASH->CR & 0x01) != 0x01)
  105.         {
  106.             FLASH->CR |= 1 << 0;
  107.         }
  108.         *(vu32 *)faddr = dat;                          // 写入数据
  109.         res            = STMFLASH_WaitDone(0XFFFFFf);  // 等待操作完成,一个字编程,最多100us.
  110.         if (res != 1)                                  // 操作成功
  111.         {
  112.             FLASH->CR &= ~(1 << 0);  // 清除PG位.
  113.         }
  114.     }
  115.     return res;
  116. }

  117. // 擦除扇区
  118. //  sectoraddr:扇区地址,范围是:0~11.
  119. //  0~3,16K扇区;4,64K扇区;5~11,128K扇区.
  120. // 返回值:执行情况
  121. static uint8_t STMFLASH_EraseSector(u32 sectoraddr)
  122. {
  123.     uint8_t res = 0;
  124.     res         = STMFLASH_WaitDone(5000000000);  // 等待上次操作结束,最大5s
  125.     if (res == 0)
  126.     {
  127.         FLASH->CR &= ~(3 << 8);               // 清除PSIZE原来的设置
  128.         FLASH->CR |= 2 << 8;                  // 设置为32bit宽,确保VCC=2.7~3.6V之间!!
  129.         FLASH->CR &= ~(0X1F << 3);            // 清除原来的设置
  130.         FLASH->CR |= sectoraddr << 3;         // 设置要擦除的扇区
  131.         FLASH->CR |= 1 << 1;                  // 扇区擦除
  132.         FLASH->CR |= 1 << 16;                 // 开始擦除
  133.         res = STMFLASH_WaitDone(5000000000);  // 等待操作结束,最大5s
  134.         if (res != 1)                         // 非忙
  135.         {
  136.             FLASH->CR &= ~(1 << 1);  // 清除扇区擦除标志.
  137.         }
  138.     }
  139.     return res;
  140. }

  141. void CHAL_Flash_init(void)
  142. {
  143.     STMFLASH_Lock();  //
  144.     /* 解锁前必须要确保是已经上锁的  或者执行解锁指令会导致程序进入 hard_fault */
  145.     STMFLASH_Unlock();    // 解锁
  146.     FLASH->CR |= 2 << 8;  // 设置为32bit宽,确保VCC=2.7~3.6V之间!!
  147. }

  148. uint32_t CHAL_Flash_Rd(void *addr, void *buffer, uint32_t size)
  149. {
  150.     uint32_t len = size;
  151.     uint8_t *sou = (uint8_t *)addr;
  152.     uint8_t *des = (uint8_t *)buffer;
  153.     // if(FLASH->CR&0x80000000)
  154.     // {
  155.     //   STMFLASH_Unlock();         // 解锁
  156.     // }

  157.     while (len--)
  158.     {
  159.         *des++ = *sou++;
  160.     }
  161.     return size;
  162. }

  163. uint8_t CHAL_Flash_EraseSector(uint8_t *addr, uint32_t sct)
  164. {
  165.     int i   = 0;
  166.     int ret = 1;
  167.     __disable_irq();
  168.     if (FLASH->CR & 0x80000000)
  169.     {
  170.         STMFLASH_Unlock();  // 解锁
  171.     }

  172.     // delay(1000);
  173.     FLASH->CR &= ~(3 << 8);  // 清除PSIZE原来的设置
  174.     FLASH->CR |= 2 << 8;     // 设置为32bit宽,确保VCC=2.7~3.6V之间!!

  175.     FLASH->ACR &= ~(1 << 10);  // FLASH擦除期间,必须禁止数据缓存!!!搞了我两晚上才发现这个问题!
  176.     if ((uint32_t)(addr) < ADDR_FLASH_SECTOR_3)
  177.     {
  178.         STMFLASH_EraseSector(2);
  179.     }
  180.     else
  181.     {
  182.         STMFLASH_EraseSector(3);
  183.     }
  184.     FLASH->ACR |= 1 << 12;  // 数据缓存复位
  185.     delay(10);
  186.     FLASH->ACR &= ~(1 << 12);  // 一定要执行缓存复位  有可能导致 读flash的数据可能是擦除前缓存的数据
  187.     FLASH->ACR |= 1 << 10;  // FLASH擦除结束,开启数据fetch
  188.     delay(10);

  189.     //  delay(1000);
  190.     STMFLASH_Lock();  // 上锁
  191.     __enable_irq();   // 开启总中
  192.     return ret;
  193. }

  194. uint32_t CHAL_Flash_Wt(void *addr, const void *buffer, uint32_t size)
  195. {
  196.     int i = 0, len = size / 4;
  197.     if (FLASH->CR & 0x80000000)
  198.     {
  199.         STMFLASH_Unlock();  // 解锁
  200.     }
  201.     __disable_irq();
  202.     /* Program data. */
  203.     FLASH->ACR &= ~(1 << 10);
  204.     for (i = 0; i < len; i++)
  205.     {
  206.         STMFLASH_WriteWord((uint32_t)addr + i * 4, ((uint32_t *)buffer)[i]);
  207.     }
  208.     FLASH->ACR |= 1 << 10;  // FLASH擦除结束,开启数据fetch
  209.     STMFLASH_Lock();        // 上
  210.                             //    delay(1000);
  211.     __enable_irq();         // 开启总中
  212.     return size;
  213. }


tpgf 发表于 2024-7-19 08:26 | 显示全部楼层
请问楼主如何确认FLASH是否已被锁定呢
paotangsan 发表于 2024-7-19 09:43 | 显示全部楼层
如果flash已经是解锁状态 此时我还进行解锁操作 会有什么后果呢
renzheshengui 发表于 2024-7-19 20:00 | 显示全部楼层
在读写flash的时候如果没有关闭中断使能 会有什么后果呢
keaibukelian 发表于 2024-7-19 21:39 | 显示全部楼层
为什么数据缓存不关闭就会导致擦除不成功呢
heimaojingzhang 发表于 2024-7-19 22:45 | 显示全部楼层
如果是在保护状态下进行写操作 会有什么错误码返回吗
guanjiaer 发表于 2024-7-19 23:51 | 显示全部楼层
是否也可以不用判断 无论如何就先进行解锁操作?
4c1l 发表于 2024-7-27 11:15 | 显示全部楼层
在执行FLASH擦除操作之前,需要关闭数据缓存(DCEN位)。
工程师犹饿死 发表于 2024-7-30 18:10 | 显示全部楼层
通过检查相关寄存器的状态位来确认。执行解锁指令对一个未锁定的FLASH可能会导致不可预料的行为或硬件错误
裤脚口感好 发表于 2024-7-31 23:57 | 显示全部楼层
开启的数据缓存可能会导致擦除操作不成功或出现数据不一致的情况
ulystronglll 发表于 2024-8-12 22:21 | 显示全部楼层
在执行FLASH解锁操作之前,确认FLASH是否已被锁定是非常重要的。
kmzuaz 发表于 2024-8-13 03:05 | 显示全部楼层
因为解锁指令的设计初衷是移除写保护,如果FLASH已经处于解锁状态,再次执行解锁操作可能会干扰正常的硬件操作。
zerorobert 发表于 2024-8-13 05:57 | 显示全部楼层
在执行Flash操作时,应该实现适当的错误处理逻辑来检测并响应任何可能的错误条件,比如写入保护错误、编程错误或擦除错误。
maudlu 发表于 2024-8-13 13:16 | 显示全部楼层
STM32的固件库提供了函数来检查FLASH的锁定状态,例如FLASH_OB_GetLockStatus()。这些函数封装了直接访问寄存器的复杂性,提供了更易用的接口。
robincotton 发表于 2024-8-13 17:59 | 显示全部楼层
在STM32中,FLASH解锁操作涉及以下几个步骤:

写保护:默认情况下,STM32的FLASH是写保护的,这意味着不能对FLASH进行编程或擦除操作。
解锁:要解除写保护,需要按照一定的顺序写入特定的密钥到FLASH的解锁寄存器(FLASH_KEYR)。
编程/擦除:解锁之后,可以对FLASH进行编程或擦除操作。
自动锁定:在编程或擦除操作完成后,FLASH会自动恢复到写保护状态。
1988020566 发表于 2024-8-13 19:43 | 显示全部楼层
在确认FLASH锁定后,可以使用解锁指令(如对FLASH_KEYR寄存器写入特定的解锁键值)。这个步骤会解除对FLASH的写保护,允许程序或固件更新操作。
pixhw 发表于 2024-8-13 21:30 | 显示全部楼层
在执行FLASH操作时,应设计错误处理机制,包括处理解锁失败、编程错误等情况,保证系统稳定和数据安全。
pl202 发表于 2024-8-14 22:56 | 显示全部楼层
在尝试解锁Flash之前,应该先检查Flash的当前状态。如果Flash已经是解锁状态,再次执行解锁操作可能会导致不可预测的结果,包括硬件错误(HardFault)。
beacherblack 发表于 2024-8-15 11:41 | 显示全部楼层
如果Flash是锁定状态,需要按照正确的解锁序列来解锁Flash。对于大多数STM32系列,这个序列通常涉及向特定的寄存器写入特定的值。例如,对于STM32F系列的微控制器,通常需要连续两次写入特定的密钥值到FLASH_KEYR寄存器。
uytyu 发表于 2024-8-15 15:04 | 显示全部楼层
在STM32中,可以通过读取特定的寄存器(通常是FLASH_CR寄存器中的LOCK位)来判断FLASH是否已被锁定。如果LOCK位显示FLASH已锁定,那么执行解锁操作是合理的。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

27

主题

1809

帖子

0

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