本帖最后由 sunsili 于 2023-8-24 22:19 编辑
单片机写flash意外断电处理
1 写flash意外断电
在写flash时突然断电可能会造成数据丢失,为了避免这种情况发生,我们可以加一层数据保护,在上电时检查数据是否正确,如果不正确则使用备份的数据
2 内部flash
还是以STM32F103ZET6为例可在ST官网下载文档:PM0075
(STM32F10xxx Flash memory microcontrollers)FLASH的最小擦除单位是扇区,扇区大小为2K
3 实现数据恢复
3.1 实现原理
-在保存数据时,对当前数据进行CRC校验,把校验结果一起写入FLASH,同时再拷贝一份作为备份数据
-在上电加载参数时,对当前数据进行CRC校验,对比校验结果是否正确,如果不正确则使用备份数据,正确则不处理
3.1.1 测试数据
假设需要存储的数据是这样的:- typedef struct
- {
- uint32_t times_clean;
- uint32_t times_error;
- uint8_t name[8];
- uint32_t crc32;
- }test_data_t;
复制代码
利用影子变量,每隔一定时间来检查参数是否发生变化,如果变化了就把最新的数据写入FLASH - if (0 != rt_memcmp(&test_data,&test_data_shadow,sizeof(test_data_t)))
- {
- uint32_t get_crc = crc32_customized(&test_data_shadow,sizeof(test_data_t)-4);
- test_data_shadow.crc32 = get_crc;
- stm32_flash_erase(CONFIG_ADDRESS_TEST_DATA,sizeof(test_data_t)*2);
- stm32_flash_write(CONFIG_ADDRESS_TEST_DATA,&test_data_shadow,sizeof(test_data_t));
- stm32_flash_write(CONFIG_ADDRESS_TEST_DATA+sizeof(test_data_t),&test_data_shadow,sizeof(test_data_t));
- rt_memcpy(&test_data,&test_data_shadow,sizeof(test_data_t));
- }
复制代码
此时FLASH中的数据应该是这个样子的:
3.2 实现代码3.2.1 需要被存储的数据相关定义- #define CONFIG_ADDRESS_TEST_DATA 0x0807F800
- #define CONFIG_HEAT_PARAMETER_DEFAULT \
- { \
- .times_clean = 0, \
- .times_error = 0, \
- .name = "test", \
- };
- test_data_t test_data = CONFIG_HEAT_PARAMETER_DEFAULT;
- test_data_t test_data_shadow = CONFIG_HEAT_PARAMETER_DEFAULT;
- test_data_t test_data_bak = CONFIG_HEAT_PARAMETER_DEFAULT;
复制代码
3.2.2 CRC32校验API,与STM32的硬件CRC结果相同 - #define CONFIG_CRC32_POLY 0x04C11DB7
- #define CONFIG_CRC32_INIT_VALUE 0xFFFFFFFF
- #define CONFIG_CRC32_OUT_XOR 0x00000000
- uint32_t crc32_stm32_hardware(uint8_t *source,uint32_t length)
- {
- uint32_t crc_value = CONFIG_CRC32_INIT_VALUE;
- for (int i =0; i < length; i++)
- {
- for (int j = 0; j < 8; j++)
- {
- uint8_t get_bit_value = ((source >> (7 - j) & 1) == 1);
- uint8_t get_value = ((crc_value >> 31 & 1) == 1);
- crc_value <<= 1;
- if (get_value ^ get_bit_value)
- {
- crc_value ^= CONFIG_CRC32_POLY;
- }
- }
- }
- crc_value &= 0xFFFFFFFF;
- return (crc_value ^= CONFIG_CRC32_OUT_XOR);
- }
复制代码
3.2.3 上电加载参数,检查数据是否出错,出错则使用备份数据 - void g_check_data(void)
- {
- stm32_flash_read(CONFIG_ADDRESS_TEST_DATA,&test_data_shadow,sizeof(test_data_t));
- stm32_flash_read(CONFIG_ADDRESS_TEST_DATA+sizeof(test_data_t),&test_data_bak,sizeof(test_data_t));
- uint32_t crc_value_cal = crc32_stm32_hardware(&test_data_shadow,sizeof(test_data_t)-4);
- rt_kprintf("crc_value_cal[%x], crc_old[%x]\r\n",crc_value_cal,test_data_shadow.crc32);
-
- if (crc_value_cal != test_data_shadow.crc32)
- {
- rt_kprintf("test data is invalid\r\n");
-
- rt_memcpy(&test_data_shadow,&test_data_bak,sizeof(test_data_t)-4);
-
- uint32_t crc_value_bak = crc32_stm32_hardware(&test_data_bak,sizeof(test_data_t)-4);
-
- test_data_shadow.crc32 = crc_value_bak;
-
- rt_memcpy(&test_data_shadow,&test_data_bak,sizeof(test_data_t)-4);
- }
-
- rt_memcpy(&test_data,&test_data_shadow,sizeof(test_data_t));
- }
复制代码
3.2.4 完整的测试代码 - int main(void)
- {
- uint32_t get_crc_first = crc32_stm32_hardware(&test_data_shadow,sizeof(test_data_t)-4);
-
- test_data_shadow.crc32 = get_crc_first;
- test_data.crc32 = get_crc_first;
-
- g_check_data();
-
- while (1)
- {
- if (0 != rt_memcmp(&test_data,&test_data_shadow,sizeof(test_data_t)))
- {
- uint32_t get_crc = crc32_stm32_hardware(&test_data_shadow,sizeof(test_data_t)-4);
-
- test_data_shadow.crc32 = get_crc;
-
- rt_base_t level;
-
- level = rt_hw_interrupt_disable();
-
- stm32_flash_erase(CONFIG_ADDRESS_TEST_DATA,sizeof(test_data_t)*2);
-
- stm32_flash_write(CONFIG_ADDRESS_TEST_DATA,&test_data_shadow,sizeof(test_data_t));
-
- stm32_flash_write(CONFIG_ADDRESS_TEST_DATA+sizeof(test_data_t),&test_data_shadow,sizeof(test_data_t));
-
- rt_hw_interrupt_enable(level);
-
- rt_memcpy(&test_data,&test_data_shadow,sizeof(test_data_t));
- }
-
- rt_thread_mdelay(1000);
- }
- }
- int cmd_flash_protect_test(int argc, char **argv)
- {
- if (2 == argc)
- {
- uint32_t get_type = atoi(argv[1]);
- if (0 == get_type)
- {
- g_check_data();
- }
- else if (1 == get_type)
- {
- test_data_shadow.times_clean++;
- }
- }
-
- return 0;
- }
- MSH_CMD_EXPORT_ALIAS(cmd_flash_protect_test,flash_protect,flash_protect [val]);
复制代码
4 测试效果 - \ | /
- - RT - Thread Operating System
- / | \ 4.1.1 build Jul 1 2023 21:37:26
- 2006 - 2022 Copyright by RT-Thread team
- crc_value_cal[95663ff9], crc_old[95663ff9]
- msh />flash_protect 1
- msh />need write flash crc[ba0600aa]
- old_data: times_clean:[6] times_error:[0] name[test] crc:[95663ff9] old_data end
- new_data: times_clean:[7] times_error:[0] name[test] crc:[ba0600aa] new_data end
- msh />flash_protect 0
- crc_value_cal[ba0600aa], crc_old[ba0600aa]
复制代码
5 总结
这个方法不适合存储的数据超过一个扇区大小,还需要根据实际情况来调整写入和加载参数的方式
我们虽不能保证自己的软件完全没有BUG,但可以先写一份软件测试用例,将需要测试的每一个功能列成TODOLIST,再按照这个清单去自测,这样就能在自测试发现并及时修正错误,反复测试多次后,我们再把软件提交给测试可能会更好一些,工作中遇到困难是让我们进步的,是提醒我们该优化自己的工作方法了。
|