Tiny Flash Database for MCU.
在單片機日常開發中,總會需要存儲一些信息,這時就需要使用單片機FLASH存儲的方案,目前單片機存儲的方案有很多,比如:EASYFLASH、FLASHDB、OSAL_NV等等方案,他們程序都非常大,在存儲不多的變量時不值得。而且很少有考慮到flash寫入出錯的情況。
在實際產品中,嵌入式產品flash寫入可能會受各種因素影響(電池供電、意外斷電、氣溫等)從而並不是很穩定,一旦出現錯誤,會導致產品一系列問題。
不同於其他很多的KV型數據庫,TinyFlashDB每一個需要存儲的變量都會分配一個單獨的單片機flash扇區,變量長度不可變。
所以TinyFlashDB僅適用於存儲幾個關鍵性變量(例如:IAP跳轉標誌、系統斷電時間等等),不適合大規模數據存儲(大規模數據存儲可使用EASYFLASH等)。
TinyFlashDB在設計時就考慮了寫入錯誤的影響,追求力所能及的安全保障、資源佔用方面盡可能的縮小(不到1kb代碼佔用)、盡可能的通用性(可以移植到51等8位機,無法逆序寫入的stm32L4系列,某些flash加密的單片機和其他普通32位機上)。
const tfdb_index_t test_index = {
. end_byte = 0x00 ,
. flash_addr = 0x4000 ,
. flash_size = 256 ,
. value_length = 2 ,
}; /* c99写法,如果编译器不支持,可自行改为c89写法 */
tfdb_addr_t addr = 0 ; /*addr cache*/
uint8_t test_buf [ TFDB_ALIGNED_RW_BUFFER_SIZE ( 2 , 1 )]; /*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:%xn" , addr );
}
addr = 0 ; /* reset addr cache, to see tfdb_get. */
result = tfdb_get ( & test_index , test_buf , & addr , & test_value );
if ( result == TFDB_NO_ERR )
{
printf ( "get ok, addr:%x, value:%xn" , addr , test_value );
}
} 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 ;結構體功能:在TinyFlashDB中,API的操作都需要指定的參數index,該index結構體中存儲了flash的地址,flash的大小,存儲的變量的長度,結束標誌位。 在讀取flash扇區時會去校驗此信息。
TFDB_Err_Code tfdb_get ( const tfdb_index_t * index , uint8_t * rw_buffer , tfdb_addr_t * addr_cache , void * value_to );函數功能:從index指向的扇區中獲取一個index中指定變量長度的變量,flash頭部數據校驗出錯不會重新初始化flash。
參數index :tfdb操作的index指針。
參數rw_buffer :寫入和讀取的緩存,所有flash的操作最後都會將整理後的數據拷貝到該buffer中,再調用tfdb_port_write或者tfdb_port_read進行讀取寫入。當芯片對於寫入的數據區緩存有特殊要求(例如4字節對齊,256字節對齊等),可以通過該參數將符合要求的變量指針傳遞給函數使用。至少為4字節長度。
參數addr_cache :可以是NULL ,或者是地址緩存變量的指針,當addr_cache不為NULL ,並且也不為0時,則認為addr_cache已經初始化成功,不再校驗flash頭部,直接從該addr_cache的地址讀取數據。
參數value_to :要存儲數據內容的地址。
返回值: 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 );函數功能:在index指向的扇區中寫入一個index中指定變量長度的變量,flash頭部數據校驗出錯重新初始化flash。
參數index :tfdb操作的index指針。
參數rw_buffer :寫入和讀取的緩存,所有flash的操作最後都會將整理後的數據拷貝到該buffer中,再調用tfdb_port_write或者tfdb_port_read進行讀取寫入。當芯片對於寫入的數據區緩存有特殊要求(例如4字節對齊,256字節對齊等),可以通過該參數將符合要求的變量指針傳遞給函數使用。至少為4字節長度。
參數addr_cache :可以是NULL ,或者是地址緩存變量的指針,當addr_cache不為NULL ,並且也不為0時,則認為addr_cache已經初始化成功,不再校驗flash頭部,直接從該addr_cache的地址讀取數據。
參數value_from :要存儲的數據內容。
返回值: TFDB_NO_ERR成功,其他失敗。
tfdb dual api是基於tfdb_set和tfdb_get封裝而成的。 tfdb dual會調用tfdb_set和tfdb_get ,並且在數據前部添加兩個字節的seq,所以在tfdb dual中,最長支持的存儲變量長度為253字節。
同時,tfdb dual api需要提供兩個緩衝區,並且需要是增加兩字節變量長度再重新計算的aligned_value_size 。
typedef struct _my_test_params_struct
{
uint32_t aa [ 2 ];
uint8_t bb [ 16 ];
} my_test_params_t ;
my_test_params_t my_test_params = {
1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , 15 , 16 , 17 , 18
};
tfdb_dual_index_t my_test_tfdb_dual = {
. indexes [ 0 ] = {
. end_byte = 0x00 ,
. flash_addr = 0x08077000 ,
. flash_size = 256 ,
. value_length = TFDB_DUAL_VALUE_LENGTH ( sizeof ( my_test_params_t )),
},
. indexes [ 1 ] = {
. end_byte = 0x00 ,
. flash_addr = 0x08077100 ,
. flash_size = 256 ,
. value_length = TFDB_DUAL_VALUE_LENGTH ( sizeof ( my_test_params_t )),
},
};
tfdb_dual_cache_t my_test_tfdb_dual_cache = { 0 };
void my_test_tfdb_dual_func ()
{
uint32_t rw_buffer [ TFDB_DUAL_ALIGNED_RW_BUFFER_SIZE ( TFDB_DUAL_VALUE_LENGTH ( sizeof ( my_test_params_t )), 4 )];
uint32_t rw_buffer_bak [ TFDB_DUAL_ALIGNED_RW_BUFFER_SIZE ( TFDB_DUAL_VALUE_LENGTH ( sizeof ( my_test_params_t )), 4 )];
TFDB_Err_Code err ;
for ( uint8_t i = 0 ; i < 36 ; i ++ )
{
err = tfdb_dual_get ( & my_test_tfdb_dual , ( uint8_t * ) rw_buffer , ( uint8_t * ) rw_buffer_bak , & my_test_tfdb_dual_cache , & my_test_params );
if ( err == TFDB_NO_ERR )
{
printf ( "read okncache seq1:0x%04x, seq2:0x%04xnaddr1:0x%08x, addr2:0x%08xn" , my_test_tfdb_dual_cache . seq [ 0 ], my_test_tfdb_dual_cache . seq [ 1 ], my_test_tfdb_dual_cache . addr_cache [ 0 ], my_test_tfdb_dual_cache . addr_cache [ 1 ]);
}
else
{
printf ( "read err:%dn" , err );
}
my_test_params . aa [ 0 ] ++ ;
my_test_params . aa [ 1 ] ++ ;
for ( uint8_t i = 0 ; i < 16 ; i ++ )
{
my_test_params . bb [ i ] ++ ;
}
memset ( & my_test_tfdb_dual_cache , 0 , sizeof ( my_test_tfdb_dual_cache )); /* 测试无地址缓存写入 */
err = tfdb_dual_set ( & my_test_tfdb_dual , ( uint8_t * ) rw_buffer , ( uint8_t * ) rw_buffer_bak , & my_test_tfdb_dual_cache , & my_test_params );
if ( err == TFDB_NO_ERR )
{
printf ( "write okncache seq1:0x%04x, seq2:0x%04xnaddr1:0x%08x, addr2:0x%08xn" , my_test_tfdb_dual_cache . seq [ 0 ], my_test_tfdb_dual_cache . seq [ 1 ], my_test_tfdb_dual_cache . addr_cache [ 0 ], my_test_tfdb_dual_cache . addr_cache [ 1 ]);
}
else
{
printf ( "write err:%dn" , err );
}
memset ( & my_test_tfdb_dual_cache , 0 , sizeof ( my_test_tfdb_dual_cache )); /* 测试无地址缓存读取 */
}
} typedef struct _tfdb_dual_index_struct
{
tfdb_index_t indexes [ 2 ];
} tfdb_dual_index_t ;
typedef struct _tfdb_dual_cache_struct
{
tfdb_addr_t addr_cache [ 2 ];
uint16_t seq [ 2 ];
} tfdb_dual_cache_t ;結構體功能:在TinyFlashDB dual中,API的操作都需要指定的參數index ,該index結構體中存儲了兩個tfdb_index_t 。
TFDB_Err_Code tfdb_dual_get ( const tfdb_dual_index_t * index , uint8_t * rw_buffer , uint8_t * rw_buffer_bak , tfdb_dual_cache_t * cache , void * value_to );函數功能:從index指向的扇區中獲取一個index中指定變量長度的變量,flash頭部數據校驗出錯不會重新初始化flash。
參數index :tfdb操作的index指針。
參數rw_buffer :寫入和讀取的緩存,所有flash的操作最後都會將整理後的數據拷貝到該buffer中,再調用tfdb_port_write或者tfdb_port_read進行讀取寫入。當芯片對於寫入的數據區緩存有特殊要求(例如4字節對齊,256字節對齊等),可以通過該參數將符合要求的變量指針傳遞給函數使用。至少為4字節長度。
參數rw_buffer_bak :寫入和讀取的緩存,所有flash的操作最後都會將整理後的數據拷貝到該buffer中,再調用tfdb_port_write或者tfdb_port_read進行讀取寫入。當芯片對於寫入的數據區緩存有特殊要求(例如4字節對齊,256字節對齊等),可以通過該參數將符合要求的變量指針傳遞給函數使用。至少為4字節長度。
參數cache :不可以是NULL ,必須是tfdb_dual_cache_t定義的緩存的指針,當cache中數據合法時,則認為cache已經初始化成功,直接從該cache的flash塊和地址讀取數據。
參數value_to :要存儲數據內容的地址。
返回值: TFDB_NO_ERR成功,其他失敗。
TFDB_Err_Code tfdb_dual_set ( const tfdb_dual_index_t * index , uint8_t * rw_buffer , uint8_t * rw_buffer_bak , tfdb_dual_cache_t * cache , void * value_from );函數功能:在index指向的扇區中寫入一個index中指定變量長度的變量,flash頭部數據校驗出錯重新初始化flash。
參數index :tfdb操作的index指針。
參數rw_buffer :寫入和讀取的緩存,所有flash的操作最後都會將整理後的數據拷貝到該buffer中,再調用tfdb_port_write或者tfdb_port_read進行讀取寫入。當芯片對於寫入的數據區緩存有特殊要求(例如4字節對齊,256字節對齊等),可以通過該參數將符合要求的變量指針傳遞給函數使用。至少為4字節長度。
參數rw_buffer_bak :寫入和讀取的緩存,所有flash的操作最後都會將整理後的數據拷貝到該buffer中,再調用tfdb_port_write或者tfdb_port_read進行讀取寫入。當芯片對於寫入的數據區緩存有特殊要求(例如4字節對齊,256字節對齊等),可以通過該參數將符合要求的變量指針傳遞給函數使用。至少為4字節長度。
參數cache :不可以是NULL ,必須是tfdb_dual_cache_t定義的緩存的指針,當cache中數據合法時,則認為cache已經初始化成功,直接從該cache的flash塊和地址讀取數據。
參數value_from :要存儲的數據內容。
返回值: TFDB_NO_ERR成功,其他失敗。
觀察上方代碼,可以發現TinyFlashDB的操作都需要tfdb_index_t定義的index參數。
Flash初始化後頭部信息為4字節,所以只支持1、2、4、8字節操作的flash:
頭部初始化時會讀取頭部,所以函數中rw_buffer指向的數據第一要求至少為4字節,如果最小寫入單位是8字節,則為第一要求最少為8字節。
| 第一字節 | 第二字節 | 第三字節 | 第四字節和其他對齊字節 |
|---|---|---|---|
| flash_size高8位字節 | flash_size低8位字節 | value_length | end_byte |
數據存儲時,會根據flash支持的字節操作進行對齊,所以函數中rw_buffer指向的數據第二要求至少為下面函數中計算得出的aligned_value_size個字節:
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個字節 | 第value_length+1字節 | 第value_length+2字節 | 其他對齊字節 |
|---|---|---|---|
| value_from數據內容 | value_from的和校驗 | end_byte | end_byte |
每次寫入後都會再讀取出來進行校驗,如果校驗不通過,就會繼續在下一個地址繼續嘗試寫入。直到達到最大寫入次數(TFDB_WRITE_MAX_RETRY)或者頭部校驗錯誤。
讀取數據時也會計算和校驗,不通過的話繼續讀取,直到返回校驗通過的最新數據,或者讀取失敗。
數據前部兩字節seq只有3種合法值,0x00ff->0x0ff0->0xff00。
如此循環往復,通過讀取兩個block中最新變量的seq來判斷哪個flash扇區中存儲的是最新值。
當最新值存儲在第一扇區時,下次寫入則會在第二扇區寫入,反之亦然。
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 ); /* 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.
* if it's over 1 byte, please be care of little endian or big endian. */
#define TFDB_VALUE_AFTER_ERASE 0xff
/* The size of TFDB_VALUE_AFTER_ERASE, only support 1 / 2 / 4.
* This value must not bigger than TFDB_WRITE_UNIT_BYTES. */
#define TFDB_VALUE_AFTER_ERASE_SIZE 1
/* 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 ;在去除DEBUG打印信息後,資源佔用如下:
keil -o2編譯優化選項
Code ( inc . data ) RO Data RW Data ZI Data Debug Object Name
154 0 0 0 0 2621 tfdb_port . o
682 0 0 0 0 4595 tinyflashdb . ogcc -os編譯優化選項
. text . tfdb_port_read
0x00000000000039b4 0x1a ./ Drivers / TFDB / tfdb_port . o
0x00000000000039b4 tfdb_port_read
. text . tfdb_port_erase
0x00000000000039ce 0x46 ./ Drivers / TFDB / tfdb_port . o
0x00000000000039ce tfdb_port_erase
. text . tfdb_port_write
0x0000000000003a14 0x5c ./ Drivers / TFDB / tfdb_port . o
0x0000000000003a14 tfdb_port_write
. text . tfdb_check
0x0000000000003a70 0x56 ./ Drivers / TFDB / tinyflashdb . o
0x0000000000003a70 tfdb_check
. text . tfdb_init
0x0000000000003ac6 0x56 ./ Drivers / TFDB / tinyflashdb . o
0x0000000000003ac6 tfdb_init
. text . tfdb_set
0x0000000000003b1c 0x186 ./ Drivers / TFDB / tinyflashdb . o
0x0000000000003b1c tfdb_set
. text . tfdb_get
0x0000000000003ca2 0x11c ./ Drivers / TFDB / tinyflashdb . o
0x0000000000003ca2 tfdb_get 裸機移植例程,RT-Thread可以參考使用:
STM32F429IGT6
CH583
QQ交流群:562090553