背景
????????在实际项目开发过程中,常常会涉及到一些参数的存储,这些参数占用存储空间不大,但需要具备可读可写的能力,而且这些参数旺旺扮演着重要的角色。例如,一个温度控制器系统,如果需要存储默认开机恒温参数,假设没有其它安全逻辑进行判断,那么这个恒温参数是万万不能出差错的。如果默认设置为40°C,而由于写入错误或flash块异常,读出参数值为400℃,这个错误是致命的!那么在实际项目开发时,是有必要做一些措施来降低风险。目前针对开源的存储方案如?armink?的FlashDB、easyflash功能可靠,可移植性强,但是针对不同的项目或许并不想占用那么多资源(尽管是一个轻量级嵌入式产品的数据存储方案),也能够对实现的代码知根知底(类似FlashDB开源项目读懂代码需花费一定时间,快速上手出现问题不好定位),那么希望在小型项目,资源较少的MCU中实现存储方案是可靠、简单的。如有兴趣可往下继续阅读探讨。
实现方法
? ? ? ? 针对存储方案第一个要求:可靠。这里的理解是数据的可靠性,即我读出的数据是我写入的数据,针对其它存储可靠性本文无法实现。为了保证读出即写入,那么首先想到的是做校验,使用校验,存储的数据中带一个校验,在读出数据时将这个校验与读出的有效数据校验进行对比,以此确定数据的有效性。那么每个参数前面都带了一个校验吗?通常我的做法是直接将要存储的数据放置在一个结构中,然后在结构体的头部定义一个校验,这个校验是针对多个参数,读出时也是多个参数的一个校验比对。现在增加校验可以让数据可靠性得到提高,但是还想到另外一个问题,如果这个扇区出现异常了该怎么办?在加入校验的基础上我们加入备份区,通过1~2个的备份区,当一个扇区出现异常时我们去获取备份区的数据,即使扇区失效我们也能得救,增强鲁棒性。
? ? ? ? 那么针对第二个要求:简单。看到实现的代码仅两百多行,可读性是非常强的,在stm32和GD32上已实现功能,其它MCU应该类似。
代码实现(stm32)
bsp_flash.c文件
#include "bsp_flash.h"
flash_appinfo_t flash_app_info;
/**
* @description: crc32_chksum_get, CRC32 get chksum
* @param const uint32_t* buff: Expected check data
* const uint16_t length: Expected check data len
* uint32_t* result: check result
* @return success: 0 | fail: 1
*/
static int crc32_chksum_get(const uint32_t *buff, const uint16_t length, uint32_t *result)
{
uint32_t chksum = 0xFFFFFFFF;
if (buff == NULL || result == NULL)
{
return -1;
}
for (int i = 0; i < length; i++)
{
chksum += buff[i];
}
*result = chksum;
return 0;
}
/**
* @description: flash_read_word
* @param : uint16_t read_addr: EEPROM addr(first addr: ) [input]
* uint8_t *buffer: Temporary storage cache [output]
* uint16_t length: len (unit: BYTE) [input]
* @return: success: 0 | fail: 1
*/
static int flash_read_word(uint32_t read_addr, uint32_t *buffer, uint16_t length)
{
if (buffer == NULL || ((read_addr % 4) != 0))
{
return -1;
}
if ((read_addr) < FLASH_STORE_BASE_ADDR || ((read_addr + length * 4) >= (FLASH_STORE_BASE_ADDR + STM_SECTOR_SIZE * STM32_FLASH_SIZE)))
{
return -1;
}
HAL_FLASH_Unlock();
__disable_irq();
for (int i = 0; i < length; i++)
{
buffer[i] = *(__IO uint32_t *)(read_addr);
read_addr += 4;
}
__enable_irq();
HAL_FLASH_Lock();
return 0;
}
/**
* @description: flash_write_word
* @param : uint16_t write_addr: EEPROM addr(first addr: ) [input]
* uint8_t *buffer: Temporary storage cache [output]
* uint16_t length: len (unit: BYTE) [input]
* @return: success: 0 | fail: 1
*/
static int flash_write_word(uint32_t write_addr, const uint32_t *buffer, uint16_t length)
{
uint16_t i = 0;
uint32_t *tempBuff = NULL;
HAL_StatusTypeDef hal_status = HAL_ERROR;
if (buffer == NULL || ((write_addr % 4) != 0))
{
return -1;
}
if ((write_addr) < FLASH_STORE_BASE_ADDR || ((write_addr + length * 4) >= (FLASH_STORE_BASE_ADDR + STM_SECTOR_SIZE * STM32_FLASH_SIZE)))
{
return -1;
}
tempBuff = (uint32_t *)buffer;
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_WRPERR); //clear flash->SR WPRERR flag
HAL_FLASH_Unlock();
__disable_irq();
for (i = 0; i < length; i++)
{
hal_status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, write_addr + 4 * i, *(tempBuff + i));
if (hal_status != HAL_OK)
{
__enable_irq();
return -1;
}
}
__enable_irq();
HAL_FLASH_Lock();
return 0;
}
/**
* @description: flash_Write_bytes_check
* @param :
* @return: success: 0 | fail: 1
*/
static int flashex_write_check(flash_appinfo_t *flash_info)
{
uint8_t head_len = 1;
uint16_t wr_len = sizeof(flash_appinfo_t) / 4 - head_len;
flash_appinfo_t fw_info, fr_info;
uint32_t *fw_pinfo = (uint32_t *)flash_info, *fr_pinfo = (uint32_t *)(&fr_info); // pflash_info:
if (flash_info == NULL)
{
return -1;
}
if (flash_info->addr < FLASH_STORE_BASE_ADDR || (flash_info->addr >= (FLASH_STORE_BASE_ADDR + STM_SECTOR_SIZE * STM32_FLASH_SIZE)))
{
return -1;
}
// 获取校验和, 并保存在结构体中
if (crc32_chksum_get(&fw_pinfo[head_len + 1], wr_len - 1, &flash_info->crc32) != 0)
{
return -1;
}
// 开始写入...
for (int i = 0; i < BACKUP_BLOCK_NUMS; i++)
{
// 擦除块扇区
FLASH_EraseInitTypeDef EraseInitStruct;
uint32_t SECTORError = 0;
EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;
EraseInitStruct.PageAddress = (flash_info->addr - (flash_info->addr % STM_SECTOR_SIZE)) + i * STM_SECTOR_SIZE;
EraseInitStruct.NbPages = 1;
HAL_FLASH_Unlock(); //解锁
HAL_FLASHEx_Erase(&EraseInitStruct, &SECTORError);
HAL_FLASH_Lock();
// 写入内容: 校验和 + 数据
if (flash_write_word(flash_info->addr + i * STM_SECTOR_SIZE, &fw_pinfo[head_len], wr_len) != 0)
{
return -1;
}
// 读取地址 数据并进行校验,读取内容: 校验和 + 数据
if (flash_read_word(flash_info->addr + i * STM_SECTOR_SIZE, &fr_pinfo[head_len], wr_len) != 0)
{
return -1;
}
// 校验
if (crc32_chksum_get(&fr_pinfo[head_len + 1], wr_len - 1, &fr_info.crc32) != 0) //计算读出的校验和
{
return -1;
}
if (fr_info.crc32 == flash_info->crc32) //与原有校验和比较
{
for (int i = 0; i < wr_len; i++) //与原有数据进行对比
{
if (fr_pinfo[i + head_len] != fw_pinfo[i + head_len])
{
return -1;
}
}
}
}
return 0;
}
/**
* @description: flash_Write_bytes_check
* @param :
* @return: success: 0 | fail: 1
*/
static int flashex_read_check(flash_appinfo_t *flash_info)
{
uint8_t head_len = 1;
uint16_t wr_len = sizeof(flash_appinfo_t) / 4 - head_len;
flash_appinfo_t fr_info = {0};
uint32_t *fr_pinfo = (uint32_t *)(&fr_info); // pflash_info:
if (flash_info == NULL)
{
return -1;
}
if (flash_info->addr < FLASH_STORE_BASE_ADDR || (flash_info->addr >= (FLASH_STORE_BASE_ADDR + STM_SECTOR_SIZE * STM32_FLASH_SIZE)))
{
return -1;
}
// 读取 flash 数据
for (int i = 0; i < BACKUP_BLOCK_NUMS; i++)
{
// 读取数据: 校验和 + 数据
if (flash_read_word(flash_info->addr + i * STM_SECTOR_SIZE, &fr_pinfo[head_len], wr_len) != 0)
{
continue;
}
//校验数据
uint32_t chksum = 0;
if (crc32_chksum_get(&fr_pinfo[head_len + 1], wr_len - 1, &chksum) != 0)
{
continue;
}
//读取出来的校验和 与 外部计算校验数据做比较
if (chksum == fr_info.crc32)
{
for(int i = 0; i < wr_len; i++)
{
((uint32_t *)flash_info)[head_len + i] = fr_pinfo[head_len + i];
}
return 0;
}
}
return -1;
}
/**
* @description: flash_app_store_init, init app store area data but not write flash
* @param flash_appinfo_t *flash_info
* @return 0 | -1
*/
int32_t flash_app_store_init(flash_appinfo_t *flash_info)
{
flash_info->addr = FLASH_STORE_BASE_ADDR;
/* 参数初始化 */
// 默认值初始化...放置代码
//如 flash_info->count = 999;
return 0;
}
/**
* @description: flash_app_store_read, read app store area data
* @param flash_appinfo_t *flash_info
* @return 0 | -1
*/
int32_t flash_app_store_read(flash_appinfo_t *flash_info)
{
return flashex_read_check(flash_info);
}
/**
* @description: flash_app_store_write ,write app store area data
* @param flash_appinfo_t *flash_info
* @return 0 | -1
*/
int32_t flash_app_store_write(flash_appinfo_t *flash_info)
{
return flashex_write_check(flash_info);
}
bap_flash.h文件
#ifndef __BSP_FLASH_H
#define __BSP_FLASH_H
#include "main.h"
/* 确定自己的芯片,仅供参考 */
#define STM32_FLASH_SIZE (256)
#define STM_SECTOR_SIZE (2048)
/* 备份区数量和存储起始地址,按扇区为单位 */
#define BACKUP_BLOCK_NUMS (3)
#define FLASH_STORE_BASE_ADDR (0x0807E800)
#pragma pack(4)
typedef struct
{
uint32_t addr;
uint32_t crc32;
/*
... 定义自己的参数
*/
}flash_appinfo_t;
#pragma pack()
extern flash_appinfo_t flash_app_info;
int32_t flash_app_store_init(flash_appinfo_t *flash_info);
int32_t flash_app_store_read(flash_appinfo_t *flash_info);
int32_t flash_app_store_write(flash_appinfo_t *flash_info);
#endif
app.c文件
void board_init(void)
{
flash_app_info.addr = FLASH_STORE_BASE_ADDR ;
if(flash_app_store_read(&flash_app_info) != 0)
{
PRINTD(" init flash info. \r\n");
flash_app_store_init(&flash_app_info);
flash_app_store_write(&flash_app_info);
}
/* 读写测试,在应用时使用这两个函数即可*/
flash_app_store_write(&flash_app_info);
flash_app_store_read(&flash_app_info);
}
注意?
- 代码对可移植性还是存在限制,但是看懂代码后用到自己的MCU上,是可以很快实现的,上文实现是stm32的code,如需要GD32的code可私聊
- 在使用上有部分局限性:扇区大小需一致
- 代码虽已在工程中使用,但使用时不能保证绝对可靠,需自行测试
- 本文使用的flash为MCU的内部flash
版权声明:本文为CSDN博主「Hi,Mr.Wang」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/qq_34672688/article/details/118635537
分享不易,点个赞再走吧???
|