单片机写flash意外断电处理
本帖最后由 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;
uint32_t crc32;
}test_data_t;
利用影子变量,每隔一定时间来检查参数是否发生变化,如果变化了就把最新的数据写入FLASHif(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);
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 );
4 测试效果\ | /
- RT - Thread Operating System
/ | \ 4.1.1 build Jul1 2023 21:37:26
2006 - 2022 Copyright by RT-Thread team
crc_value_cal, crc_old
msh />flash_protect 1
msh />need write flash crc
old_data: times_clean: times_error: name crc: old_data end
new_data: times_clean: times_error: name crc: new_data end
msh />flash_protect 0
crc_value_cal, crc_old
5 总结
这个方法不适合存储的数据超过一个扇区大小,还需要根据实际情况来调整写入和加载参数的方式
我们虽不能保证自己的软件完全没有BUG,但可以先写一份软件测试用例,将需要测试的每一个功能列成TODOLIST,再按照这个清单去自测,这样就能在自测试发现并及时修正错误,反复测试多次后,我们再把软件提交给测试可能会更好一些,工作中遇到困难是让我们进步的,是提醒我们该优化自己的工作方法了。
页:
[1]