TinyFlashDB:一種超輕量的可糾錯的通用單片機Flash存儲方案
擊上方“芯片之家”,選擇“置頂/星標(biāo)公眾號”
干貨福利,第一時間送達!
摘要:在單片機日常開發(fā)中,總會需要存儲一些信息,這時就需要使用單片機FLASH存儲的方案,目前單片機存儲的方案有很多如:EASYFLASH、FLASHDB、OSAL_NV等等方案,他們程序都非常大,在存儲不多的變量時不值得。而且現(xiàn)有方案的代碼中很少有考慮到flash寫入出錯的情況。
在實際產(chǎn)品中,嵌入式產(chǎn)品flash寫入可能會受各種因素影響(電池供電、意外斷電、氣溫等)從而并不是很穩(wěn)定,一旦出現(xiàn)錯誤,會導(dǎo)致產(chǎn)品一系列問題。
一、TinyFlashDB設(shè)計理念
不同于其他很多的KV型數(shù)據(jù)庫,TinyFlashDB每一個需要存儲的變量都會分配一個單獨的單片機flash扇區(qū),變量長度不可變。
TinyFlashDB在設(shè)計時就考慮了寫入錯誤的影響,追求力所能及的安全保障、資源占用方面盡可能的縮小(不到1kb代碼占用)、盡可能的通用性(可以移植到51等8位機,無法逆序?qū)懭氲膕tm32L4系列,某些flash加密的單片機和其他普通32位機上)。
二、TinyFlashDB使用示例
const tfdb_index_t test_index = {
.end_byte = 0x00,
.flash_addr = 0x4000,
.flash_size = 256,
.value_length = 2,
};
tfdb_addr_t addr = 0; /*addr cache*/
uint8_t test_buf[4]; /*aligned_value_size*/
uint16_t test_value;
void main()
{
TFDB_Err_Code result;
result = tfdb_set(&test_index, test_buf, &addr, &test_value);
if(result == TFDB_NO_ERR)
{
printf("set ok, addr:%x\n", addr);
}
result = tfdb_get(&test_index, test_buf, &addr, &test_value);
if(result == TFDB_NO_ERR)
{
printf("get ok, addr:%x, value:%x\n", addr, test_value);
}
}
三、TinyFlashDB API介紹
typedef struct _tfdb_index_struct{
tfdb_addr_t flash_addr;/* the start address of the flash block */
uint16_t flash_size;/* the size of the flash block */
uint8_t value_length;/* the length of value that saved in this flash block */
uint8_t end_byte; /* must different to TFDB_VALUE_AFTER_ERASE */
/* 0x00 is recommended for end_byte, because almost all flash is 0xff after erase. */
}tfdb_index_t;
結(jié)構(gòu)體功能:在TinyFlashDB中,API的操作都需要指定的參數(shù)index,該index結(jié)構(gòu)體中存儲了flash的地址,flash的大小,存儲的變量的長度,結(jié)束標(biāo)志位。在讀取flash扇區(qū)時會去校驗此信息。
TFDB_Err_Code tfdb_get(const tfdb_index_t *index, uint8_t *rw_buffer, tfdb_addr_t *addr_cache, void* value_to);
函數(shù)功能:從index指向的扇區(qū)中獲取一個index中指定變量長度的變量,flash頭部數(shù)據(jù)校驗出錯不會重新初始化flash。
參數(shù) index:tfdb操作的index指針。
參數(shù) rw_buffer:寫入和讀取的緩存,所有flash的操作最后都會將整理后的數(shù)據(jù)拷貝到該buffer中,再調(diào)用tfdb_port_write或者tfdb_port_read進行寫入。當(dāng)芯片對于寫入的數(shù)據(jù)區(qū)緩存有特殊要求(例如4字節(jié)對齊,256字節(jié)對齊等),可以通過該參數(shù)將符合要求的變量指針傳遞給函數(shù)使用。至少為4字節(jié)長度。
參數(shù) addr_cache:可以是NULL,或者是地址緩存變量的指針,當(dāng)addr_cache不為NULL,并且也不為0時,則認為addr_cache已經(jīng)初始化成功,不再校驗flash頭部,直接從該addr_cache的地址讀取數(shù)據(jù)。
參數(shù) value_to:要存儲數(shù)據(jù)內(nèi)容的地址。
返回值:TFDB_NO_ERR成功,其他失敗。
TFDB_Err_Code tfdb_set(const tfdb_index_t *index, uint8_t *rw_buffer, tfdb_addr_t *addr_cache, void* value_from);
函數(shù)功能:在index指向的扇區(qū)中寫入一個index中指定變量長度的變量,flash頭部數(shù)據(jù)校驗出錯重新初始化flash。
參數(shù) index:tfdb操作的index指針。
參數(shù) rw_buffer:寫入和讀取的緩存,所有flash的操作最后都會將整理后的數(shù)據(jù)拷貝到該buffer中,再調(diào)用tfdb_port_write或者tfdb_port_read進行寫入。當(dāng)芯片對于寫入的數(shù)據(jù)區(qū)緩存有特殊要求(例如4字節(jié)對齊,256字節(jié)對齊等),可以通過該參數(shù)將符合要求的變量指針傳遞給函數(shù)使用。至少為4字節(jié)長度。
參數(shù) addr_cache:可以是NULL,或者是地址緩存變量的指針,當(dāng)addr_cache不為NULL,并且也不為0時,則認為addr_cache已經(jīng)初始化成功,不再校驗flash頭部,直接從該addr_cache的地址讀取數(shù)據(jù)。
參數(shù) value_from:要存儲的數(shù)據(jù)內(nèi)容。
返回值:TFDB_NO_ERR成功,其他失敗。
四、TinyFlashDB設(shè)計原理
觀察上方代碼,可以發(fā)現(xiàn)TinyFlashDB的操作都需要tfdb_index_t定義的index參數(shù)。
Flash初始化后頭部信息為4字節(jié),所以只支持1、2、4、8字節(jié)操作的flash:頭部初始化時會讀取頭部,所以函數(shù)中rw_buffer指向的數(shù)據(jù)第一要求至少為4字節(jié),如果最小寫入單位是8字節(jié),則為第一要求最少為8字節(jié)。
| 第一字節(jié) | 第二字節(jié) | 第三字節(jié) | 第四字節(jié)和其他對齊字節(jié) |
|---|---|---|---|
| flash_size高8位字節(jié) | flash_size低8位字節(jié) | value_length | end_byte |
數(shù)據(jù)存儲時,會根據(jù)flash支持的字節(jié)操作進行對齊,所以函數(shù)中rw_buffer指向的數(shù)據(jù)第二要求至少為下面函數(shù)中計算得出的aligned_value_size個字節(jié):
aligned_value_size = index->value_length + 2;/* data + verify + end_byte */
#if (TFDB_WRITE_UNIT_BYTES==2)
/* aligned with TFDB_WRITE_UNIT_BYTES */
aligned_value_size = ((aligned_value_size + 1) & 0xfe);
#elif (TFDB_WRITE_UNIT_BYTES==4)
/* aligned with TFDB_WRITE_UNIT_BYTES */
aligned_value_size = ((aligned_value_size + 3) & 0xfc);
#elif (TFDB_WRITE_UNIT_BYTES==8)
/* aligned with TFDB_WRITE_UNIT_BYTES */
aligned_value_size = ((aligned_value_size + 7) & 0xf8);
#endif
| 前value_length個字節(jié) | 第value_length+1字節(jié) | 第value_length+2字節(jié) | 其他對齊字節(jié) |
|---|---|---|---|
| value_from數(shù)據(jù)內(nèi)容 | value_from的和校驗 | end_byte | end_byte |
每次寫入后都會再讀取出來進行校驗,如果校驗不通過,就會繼續(xù)在下一個地址寫入。指導(dǎo)達到最大寫入次數(shù)(TFDB_WRITE_MAX_RETRY)或者頭部校驗錯誤。
讀取數(shù)據(jù)時也會計算和校驗,不通過的話繼續(xù)讀取,直到返回校驗通過的最新數(shù)據(jù),或者讀取失敗。
五、TinyFlashDB移植和配置
移植使用只需要在tfdb_port.c中,編寫完成三個接口函數(shù),也要在tfdb_port.h中添加相應(yīng)的頭文件和根據(jù)不同芯片修改宏定義
TFDB_Err_Code tfdb_port_read(tfdb_addr_t addr, uint8_t *buf, size_t size);
TFDB_Err_Code tfdb_port_erase(tfdb_addr_t addr, size_t size);
TFDB_Err_Code tfdb_port_write(tfdb_addr_t addr, const uint8_t *buf, size_t size);
所有的配置項都在tfdb_port.h中
/* use string.h or self functions */
#define TFDB_USE_STRING_H 1
#if TFDB_USE_STRING_H
#include "string.h"
#define tfdb_memcpy memcpy
#define tfdb_memcmp memcmp
#define TFDB_MEMCMP_SAME 0
#else
#define tfdb_memcpy
#define tfdb_memcmp
#define TFDB_MEMCMP_SAME
#endif
#define TFDB_DEBUG printf
/* The data value in flash after erased, most are 0xff, some flash maybe different. */
#define TFDB_VALUE_AFTER_ERASE 0xff
/* the flash write granularity, unit: byte
* only support 1(stm32f4)/ 2(CH559)/ 4(stm32f1)/ 8(stm32L4) */
#define TFDB_WRITE_UNIT_BYTES 8 /* @note you must define it for a value */
/* @note the max retry times when flash is error ,set 0 will disable retry count */
#define TFDB_WRITE_MAX_RETRY 32
/* must not use pointer type. Please use uint32_t, uint16_t or uint8_t. */
typedef uint32_t tfdb_addr_t;
六、移植到STM32單片機

項目地址:https://github.com/smartmx/TFDB/tree/main
使用一下命令克隆Demo,裸機移植例程
git clone -b raw https://github.com/smartmx/TFDB.git
本文來源:果果小師弟

嵌入式軟件版本怎么樣命名才比較專業(yè)?

深度長文:STM32是如何軟硬件結(jié)合,編譯后怎么樣一步步運行起來的

電子漫畫系列套圖更新11張,共計83張,附高清無碼下載鏈接

又踩廠商的雷了!1000多套板子要批量返工,說多了都是淚
覺得好看,請點這里↓↓↓

