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