EasyFlash | 让 Flash 成为小型 KV 数据库
嵌入式開源項目精選專欄
本專欄由Mculover666創建,主要內容為尋找嵌入式領域內的優質開源項目,一是幫助開發者使用開源項目實現更多的功能,二是通過這些開源項目,學習大佬的代碼及背后的實現思想,提升自己的代碼水平,和其它專欄相比,本專欄的優勢在于:
不會單純的介紹分享項目,還會包含作者親自實踐的過程分享,甚至還會有對它背后的設計思想解讀。
目前本專欄包含的開源項目有:
- cJSON | 一個輕量級C語言JSON解析器
- paho | 支持10種語言編寫mqtt客戶端,總有一款適合你!
- MultiButton | 一個小巧簡單易用的事件驅動型按鍵驅動模塊
- letter-shell | 一個功能強大的嵌入式shell
- EasyLogger | 一款輕量級且高性能的日志庫
- SFUD | 一款串行 Flash 通用驅動庫
如果您自己編寫或者發現的開源項目不錯,歡迎留言或者私信投稿到本專欄,分享獲得雙倍的快樂!
1. EasyFlash
本期給大家帶來的開源項目是 EasyFlash,可以讓 Flash 成為小型 KV 數據庫(Key-Value),作者armink,目前收獲 975 個 star,遵循 MIT 開源許可協議。
EasyFlash是一款開源的輕量級嵌入式Flash存儲器庫,非常適合智能家居、可穿戴、工控、醫療、物聯網等需要斷電存儲功能的產品,資源占用極低,并且支持各種 MCU 片上存儲器。
目前 EasyFlash 支持以下功能:
- ENV:快速保存產品參數,支持 寫平衡(磨損平衡) 及掉電保護功能;
- IAP:在線升級;
- LOG:無需文件系統,日志可直接存儲在Flash上;
項目地址:https://github.com/armink/EasyFlash
2. 移植EasyFlash
2.1. 移植思路
在移植過程中主要參考兩個資料:項目的readme文檔和demo工程。
對于這些開源項目,其實移植起來也就兩步:
- ① 添加源碼到裸機工程中;
- ② 實現需要的接口即可(擦、寫、讀、打印);
2.2. 準備裸機工程
本文中我使用的是小熊派IoT開發套件,主控芯片為STM32L431RCT6:
板載Flash型號為W25Q64JV,大小64Mbit,與STM32的QSPI接口相連:
移植之前需要準備一份裸機工程,我使用STM32CubeMX生成,需要初始化以下配置:
- 配置一個串口用于打印信息
- printf重定向
- 配置SPI Flash通信接口(SPI或QSPI)
- 移植SFUD開源庫(方便操作Flash)
SFUD移植過程請參考上一期文章:
- SFUD | 一款串行 Flash 通用驅動庫
2.3. 添加EasyFlash到工程中
② 在keil中添加 SFUD 組件的源碼文件:
- src\easyflash.c:(必選)包含EasyFlash初始化方法;
- src\ef_utils.c:(必選)EasyFlash常用小工具;
- port\ef_port.c:(必選)EasyFlash移植接口;
- src\ef_env.c:Env(常規模式)相關操作接口及實現源碼;
其它兩個IAP和LOG相關的源碼暫且不用添加。
③ 將easyflash/inc頭文件路徑添加到keil中:
2.4. 實現EasyFlash移植接口
EasyFlash的移植接口都已經寫好了,在ef_port.c文件中,只需要在函數體中添加代碼即可。
① 默認環境變量集合
產品上需要的默認環境變量集中定義在這里,當 flash 第一次初始化時會將默認的環境變量寫入,采用 void * 類型,所以支持任意類型:
/* default environment variables set for user */ static const ef_env default_env_set[] = {{"wifi_ssid","FAST_88A6", 0}, //字符串大小設置為0,會自動檢測{"wifi_passwd","12345678", 0}, };② EasyFlash初始化接口
在該接口中會傳遞默認環境變量,初始化EasyFlash移植所需的資源(比如SFUD庫的初始化):
EfErrCode ef_port_init(ef_env const **default_env, size_t *default_env_size) {EfErrCode result = EF_NO_ERR;*default_env = default_env_set;*default_env_size = sizeof(default_env_set) / sizeof(default_env_set[0]);//SFUD庫初始化sfud_init();return result; }③ 讀取Flash接口
使用SFUD開源庫提供的API實現該接口:
EfErrCode ef_port_read(uint32_t addr, uint32_t *buf, size_t size) {EfErrCode result = EF_NO_ERR;//獲取SFUD Flash設備對象const sfud_flash *flash = sfud_get_device_table() + SFUD_W25Q64_DEVICE_INDEX;//使用SFUD開源庫提供的API實現Flash讀取sfud_read(flash, addr, size, (uint8_t *)buf);return result; }④ 擦除Flash接口
使用SFUD開源庫提供的API實現該接口:
EfErrCode ef_port_erase(uint32_t addr, size_t size) {EfErrCode result = EF_NO_ERR;sfud_err sfud_result = SFUD_SUCCESS;//獲取SFUD Flash設備對象const sfud_flash *flash = sfud_get_device_table() + SFUD_W25Q64_DEVICE_INDEX;/* make sure the start address is a multiple of FLASH_ERASE_MIN_SIZE */EF_ASSERT(addr % EF_ERASE_MIN_SIZE == 0);//使用SFUD提供的API實現Flash擦除sfud_result = sfud_erase(flash, addr, size);if(sfud_result != SFUD_SUCCESS) {result = EF_ERASE_ERR;}return result; }⑤ 寫入Flash接口
使用SFUD開源庫提供的API實現該接口:
EfErrCode ef_port_write(uint32_t addr, const uint32_t *buf, size_t size) {EfErrCode result = EF_NO_ERR;sfud_err sfud_result = SFUD_SUCCESS;//獲取SFUD Flash設備對象const sfud_flash *flash = sfud_get_device_table() + SFUD_W25Q64_DEVICE_INDEX;//使用SFUD開源庫提供的API實現sfud_result = sfud_write(flash, addr, size, (const uint8_t *)buf);if(sfud_result != SFUD_SUCCESS) {result = EF_WRITE_ERR;}return result; }⑥ 對環境變量緩沖區加鎖/解鎖
裸機時可以使用關閉/打開全局中斷來上鎖/解鎖:
/*** lock the ENV ram cache*/ void ef_port_env_lock(void) {//關閉全局中斷__disable_irq(); }/*** unlock the ENV ram cache*/ void ef_port_env_unlock(void) {//打開全局中斷__enable_irq();}⑦ EasyFlash打印數據和日志接口
在該文件最頂部開辟一塊打印數據緩沖區:
//easyflash打印數據緩沖區 static char log_buf[128];然后實現輸出無固定格式的打印信息接口:
void ef_print(const char *format, ...) {va_list args;/* args point to the first variable parameter */va_start(args, format);/* 實現數據輸出 */vsprintf(log_buf, format, args);printf("%s", log_buf);va_end(args); }然后使用該函數去實現調試信息日志打印接口:
void ef_log_debug(const char *file, const long line, const char *format, ...) {#ifdef PRINT_DEBUGva_list args;/* args point to the first variable parameter */va_start(args, format);/* You can add your code under here. */ef_print("[Flash](%s:%ld) ", file, line);/* must use vprintf to print */vsprintf(log_buf, format, args);ef_print("%s", log_buf);printf("\r");va_end(args);#endif}最后實現普通日志信息打印接口:
void ef_log_info(const char *format, ...) {va_list args;/* args point to the first variable parameter */va_start(args, format);/* You can add your code under here. */ef_print("[Flash]");/* must use vprintf to print */vsprintf(log_buf, format, args);ef_print("%s", log_buf);printf("\r");va_end(args); }2.5. 配置EasyFlash
EasyFlash的核心功能配置文件在ef_cfg.h,修改說明如下。
① 環境變量功能相關
② Flash擦除粒度和寫入粒度
③ 備份區相關
④ 調試日志是否開啟
至此,EasyFlash移植、配置完成,接下來就可以愉快的使用了!
3. 使用EasyFlash
使用時包含頭文件:
#include <easyflash.h>3.1. 初始化EasyFlash
EfErrCode easyflash_init(void);該 API 會初始化的EasyFlash的各個組件,初始化后才可以使用別的API,第一次初始化的時候,會自動調用 ef_env_set_default 將定義的默認環境變量保存到Flash。
在main函數中初始化EasyFlash:
/* USER CODE BEGIN 2 */ //初始化EasyFlash ret = easyflash_init(); if(ret != EF_NO_ERR) {printf("EasyFlash init fail, EfErrCode = %d.r\n", ret); }/* USER CODE END 2 */3.2. 環境變量操作API
在 V4.0 以后,環境變量在 EasyFlash 底層都是按照二進制數據格式進行存儲,即 blob 格式 ,這樣上層支持傳入任意類型。
① 獲取 blob 類型環境變量
size_t ef_get_env_blob(const char *key, void *value_buf, size_t buf_len, size_t *save_value_len);其中參數的意義如下:
- key:環境變量名稱
- value_buf:存放環境變量的緩沖區
- buf_len:該緩沖區的大小
- save_value_len:返回該環境變量實際存儲在 flash 中的大小
- 返回值:成功存放至緩沖區中的數據長度
② 設置 blob 類型環境變量
使用該API可以對環境變量完成如下操作:
- 增加 :當環境變量表中不存在該名稱的環境變量時,則會執行新增操作;
- 修改 :入參中的環境變量名稱在當前環境變量表中存在,則把該環境變量值修改為入參中的值;
- 刪除:當入參中的value為NULL時,則會刪除入參名對應的環境變量。
其中參數的意義如下:
- key:環境變量名稱
- value_buf:環境變量值緩沖區
- buf_len:緩沖區長度,即值的長度
3.3. 測試讀取默認環境變量
在main.c中編寫一個EasyFlash測試函數:
void test_env(void) {char wifi_ssid[20] = {0};char wifi_passwd[20] = {0};size_t len = 0;/* 讀取默認環境變量值 *///環境變量長度未知,先獲取 Flash 上存儲的實際長度 */ef_get_env_blob("wifi_ssid", NULL, 0, &len);//獲取環境變量ef_get_env_blob("wifi_ssid", wifi_ssid, len, NULL);//打印獲取的環境變量值printf("wifi_ssid env is:%s\r\n", wifi_ssid);//環境變量長度未知,先獲取 Flash 上存儲的實際長度 */ef_get_env_blob("wifi_passwd", NULL, 0, &len);//獲取環境變量ef_get_env_blob("wifi_passwd", wifi_passwd, len, NULL);//打印獲取的環境變量值printf("wifi_passwd env is:%s\r\n", wifi_passwd);/* 將環境變量值改變 */ef_set_env_blob("wifi_ssid", "SSID_TEST", 9);ef_set_env_blob("wifi_passwd", "66666666", 8);}在main函數的初始化代碼之后調用該函數,編譯下載之后,在串口終端中可以看到讀取結果:
此時環境變量已經被修改,直接復位開發板,可以看到讀取出的新值:
3.4. Easyflash和letter-shell的結合
EasyFlash在測試階段需要不斷的設置環境變量、讀取環境變量、開發板重新上電,這個特點剛好可以應用letter-shell,直接將兩個常用函數導出為命令,在串口命令行測試。
letter-shell的移植過程請參考第2期:
- letter-shell | 一個功能強大的嵌入式shell
移植之后將讀取環境變量的API封裝,導出到命令列表中:
/* USER CODE BEGIN 4 */ int getenv(const char *key) {size_t len = 0;char buf[20] = {0};//獲取長度ef_get_env_blob(key, NULL, 0, &len);if(len == 0){//環境變量不存在printf("evn %s is not exist\r\n", key);return -1;}else if(len < 20){//環境變量值超長printf("buf size is not enough, len is %d\r\n", len);return -1;}else{//獲取環境變量值ef_get_env_blob(key, buf, len, NULL);printf("read env %s, value is:%s\r\n", key, buf);return 0;} }SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC), getenv, getenv, getenv); /* USER CODE END 4 */編譯下載之后,在串口終端中查看串口輸出:
然后進行讀取環境變量測試:
測試成功,同理,修改環境變量的API也可以進行封裝,導出到命令列表中進行測試。
4. 設計思想解讀
對于EasyFlash的全新版本設計,作者armink在倉庫中寫了一份文檔,解鈴還須系鈴人,筆者的技術水平有限,直接放上作者的文檔鏈接,歡迎感興趣的讀者閱讀。
- EasyFlash V4.0 ENV 功能設計與實現
5. 項目工程源碼獲取和問題交流
目前我將EasyFlash源碼、我移植到小熊派STM32L431RCT6開發板的工程源碼上傳到了QQ群里(包含好幾份HAL庫,QQ相對速度快點),可以在QQ群里下載,有問題也可以在群里交流,當然也歡迎大家分享出來自己移植的工程到QQ群里:
放上QQ群二維碼:
接收更多精彩文章及資源推送,歡迎訂閱我的微信公眾號:『mculover666』。
總結
以上是生活随笔為你收集整理的EasyFlash | 让 Flash 成为小型 KV 数据库的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 太可爱啦!程序员把电脑病毒当宠物养
- 下一篇: Three.js杂记(十一)—— 精灵与