写flash意外断电的处理方法

电子说

1.2w人已加入

描述

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[i] > > (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]rn",crc_value_cal,test_data_shadow.crc32);

    if  (crc_value_cal != test_data_shadow.crc32)
    {
        rt_kprintf("test data is invalidrn");

        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,再按照这个清单去自测,这样就能在自测试发现并及时修正错误,反复测试多次后,我们再把软件提交给测试可能会更好一些,工作中遇到困难是让我们进步的,是提醒我们该优化自己的工作方法了。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分