1. 问题描述 客户反馈在使用STM32H503RB进行flash编程时,在flash擦写之前读取当前扇区Sector时,意外触发hardfault。 2. 问题复现 使用NUCLO-H503RB 和STM32CubeH5 提供的例程“FLASH_EraseProgram”,并没有复现问题,要求客户提供出现问题的代码。对比客户代码和例程,发现在客户代码中,客户为了更高的代码执行效率,使能了ICACHE :
- if (HAL_ICACHE_Enable() != HAL_OK)
- {
- Error_Handler();
- }
在例程中,可以看到会在flash的编程之前,关掉了ICACHE ,在完成flash的所有操作之后,才再次使能ICACHE。
- /* Disable instruction cache prior to internal cacheable memory update */
- if (HAL_ICACHE_Disable() != HAL_OK)
- {
- Error_Handler();
- }
-
-
- 操作flash
-
-
- /* Re-enable instruction cache */
- if (HAL_ICACHE_Enable() != HAL_OK)
- {
- Error_Handler();
- }
按照客户的设置,去掉关掉ICACHE的部分,问题复现。
3. 问题分析
从手册来看, STM32H503RB是dual-bank,支持read-while-write,意思是说它支持在一个bank中读取代码,而在另外一个bank中擦 /写flash。手册中也没有提到,在擦/写flash时一定要关掉ICACHE。 使用能复现问题的代码,进行单步调试发现,进入hardfault的时候,应用代码并不是在调用擦除flash 或者编程flash的时候,而是在查找要编程的flash地址所在的扇区时。
- FirstSector = GetSector(FLASH_USER_START_ADDR);
-
- 函数原型:
- /**
- * [url=/u/brief]@brief[/url] Gets the sector of a given address
- * @param Addr: Address of the FLASH Memory
- * @retval The sector of a given address
- */
- static uint32_t GetSector(uint32_t Address)
- {
- uint32_t sector = 0;
-
- if((Address >= FLASH_BASE) && (Address < FLASH_BASE + FLASH_BANK_SIZE))
- {
- sector = (Address & ~FLASH_BASE) / FLASH_SECTOR_SIZE;
- }
- else if ((Address >= FLASH_BASE + FLASH_BANK_SIZE) && (Address < FLASH_BASE + FLASH_SIZE))
- {
- sector = ((Address & ~FLASH_BASE) - FLASH_BANK_SIZE) / FLASH_SECTOR_SIZE;
- }
- else
- {
- sector = 0xFFFFFFFF; /* Address out of range */
- }
-
- return sector;
- }
表面上看只是仅仅读取一下宏定义的值,并将它们进行一些简单运算或者判断,它不应该产生hardfault。为什么会进入hardfault呢? 再进一步查看这些宏定义,如下:
1. #define FLASH_BASE FLASH_BASE_NS
2. #define FLASH_BASE_NS (0x08000000UL) /*!< FLASH (up to 128 KB) non-secure base address */
3. #define FLASH_BANK_SIZE (FLASH_SIZE >> 1U)
4. #define FLASH_SIZE ((((*((uint16_t *)FLASHSIZE_BASE)) == 0xFFFFU)) ? FLASH_SIZE_DEFAULT : \ ((((*((uint16_t *)FLASHSIZE_BASE)) == 0x0000U)) ? FLASH_SIZE_DEFAULT : \ (((uint32_t)(*((uint16_t *)FLASHSIZE_BASE)) & (0xFFFFU)) << 10U)))
5. #define FLASH_SECTOR_SIZE 0x2000U
6. #define FLASH_SIZE_DEFAULT (0x20000U)
7. #define FLASHSIZE_BASE (0x08FFF80CUL) /*!< Flash size data register base address */
从这些宏中来看,似乎也没有什么特别的地方,理论上也不应该产生hardfault,但是如果指定一个扇区(sector)进行擦除和编程flash,它的确不会产生hardfault,只有通过地址在查找某个扇区(sector)之后,在进行擦除或编程,才会进入hardfault。
从上面的结果来看,问题应该还是出在了对这些宏定义的地址读取出问题,再次分析这些宏,可以看到除了“FLASH_BASE_NS ”和“FLASHSIZE_BASE”,其它都是一些常量值,应该不会导致这个问题,我们尝试在代码中去逐个读取两个地址的值,当在读取“FLASHSIZE_BASE”时,产生了hardfault。那为什么读取“FLASHSIZE_BASE”寄存器会跟ICACHE有关呢? 我们查看参考相关flash和ICACHE的相关章节进行查找,发现在flash 章节中有如下的描述:
所以,对于FLASHSIZE_BASE(0x08FFF80CUL)它是 属于RO区,在读取时,是不能使能Cache的。 同时我们也注意到这个RO区域还包括一些其它的宏定义,大家需要注意,调用的时候记得关掉CACHE。 #define PACKAGE_BASE #define UID_BASE #define FLASHSIZE_BASE (0x08FFF80EUL) /*!< Package data register base address (0x08FFF800UL) /*!< Unique device ID register base address */ */ (0x08FFF80CUL) /*!< Flash size data register base address */ 当然,除了直接关闭Cache,我们也可以通过设置MPU,将这部分地址区域设置为Non-cacheable 的属性,这样即使开启了Cache,在读取RO区域时也是未经过cache,以Non-cacheable 的方式直接读取的。
4. 总结 在ST的MCU带cache的型号中,在遇到一些异常情况(比如,内存不刷新,两次运行效果不一致,hardfault等等),我们可以先关掉相关Cache,看是否可以恢复正常,然后再一步一步查找问题。
|