嵌入式開源項目精選專欄
本專欄由Mculover666創建,主要內容爲尋找嵌入式領域內的優質開源項目,一是幫助開發者使用開源項目實現更多的功能,二是通過這些開源項目,學習大佬的代碼及背後的實現思想,提升自己的代碼水平,和其它專欄相比,本專欄的優勢在於:
不會單純的介紹分享項目,還會包含作者親自實踐的過程分享,甚至還會有對它背後的設計思想解讀。
目前本專欄包含的開源項目有:
- cJSON | 一個輕量級C語言JSON解析器
- paho | 支持10種語言編寫mqtt客戶端,總有一款適合你!
- MultiButton | 一個小巧簡單易用的事件驅動型按鍵驅動模塊
- letter-shell | 一個功能強大的嵌入式shell
- EasyLogger | 一款輕量級且高性能的日誌庫
如果您自己編寫或者發現的開源項目不錯,歡迎留言或者私信投稿到本專欄,分享獲得雙倍的快樂!
1. SFUD
本期給大家帶來的開源項目是 SFUD,一款串行 Flash 通用驅動庫,作者armink,目前收穫 407 個 star,遵循 MIT 開源許可協議。
SFUD全稱Serial Flash Universal Driver,是一款開源的串行 SPI Flash 通用驅動庫,由於現有市面的串行 Flash 種類居多,各個 Flash 的規格及命令存在差異, SFUD 就是爲了解決這些 Flash 的差異現狀而設計。
SFUD的特點在於:
- 支持 SPI/QSPI 接口
- 面向對象設計(同時支持多個 Flash 對象)
- 可靈活裁剪、擴展性強
- 支持 4 字節地址
2. 移植SFUD
2.1. 移植思路
在移植過程中主要參考兩個資料:項目的readme文檔和demo工程。
對於這些開源項目,其實移植起來也就兩步:
- ① 添加源碼到裸機工程中;
- ② 實現需要的接口即可;
2.2. 準備裸機工程
本文中我使用的是小熊派IoT開發套件,主控芯片爲STM32L431RCT6:
板載Flash型號爲W25Q64JV
,大小64Mbit,與STM32的QSPI接口相連:
移植之前需要準備一份裸機工程,我使用STM32CubeMX生成,需要初始化以下配置:
- 配置SPI Flash通信接口(SPI或QSPI)
- 配置一個串口用於打印信息
- printf重定向
具體過程請參考:
- STM32CubeMX_06 | 使用USART發送和接收數據(查詢模式)
- STM32CubeMX_09 | 重定向printf函數到串口輸出的多種方法
- STM32CubeMX_18 | 使用硬件QSPI讀寫SPI Flash(W25Q64)
使用CubeMX配置好SPI或QSPI通信即可,不用編寫W25Q64驅動。
2.3. 添加SFUD到工程中
① 複製源碼到工程中:
② 在keil中添加 SFUD 組件的源碼文件:
src\sfud.c
:SFUD核心功能源碼;src\sfud_sfdp.c
:讀取並分析SFDP功能源碼;port\sfud_port.c
:SFUD移植接口;
③ 將sfud/inc
頭文件路徑添加到keil中:
2.4. 實現SFUD移植接口
SFUD的移植接口都已經寫好了,在sfud_port.c
文件中,只需要在函數體中添加代碼即可。
① 底層SPI/QSPI讀寫接口:
/**
* SPI write data then read data
*/
static sfud_err spi_write_read(const sfud_spi *spi, const uint8_t *write_buf, size_t write_size, uint8_t *read_buf, size_t read_size);
② 如果使用的是QSPI通信方式,還需要實現快速讀取數據的接口:
/**
* QSPI fast read data
*/
static sfud_err qspi_read(const struct __sfud_spi *spi, uint32_t addr, sfud_qspi_read_cmd_format *qspi_read_cmd_format, uint8_t *read_buf, size_t read_size);
③ SFUD底層使用的SPI/QSPI接口和SPI設備對象初始化接口:
sfud_err sfud_spi_port_init(sfud_flash *flash);
關於SFUD底層所抽象出來的SPI設備對象,在接下來的設計思想解讀章節中會詳細講述。
本文中所使用的裸機工程是基於HAL庫的,在SFUD源碼的Demo中也有一份HAL庫的工程,因爲基於HAL庫的移植接口實現都是一樣的,所以我直接將Demo中的sfud_port.c
文件複製過來替換:
複製過來之後,如果使用的不是STM32L4系列的芯片,則需要修改sfud_port.c
中包含的頭文件:
2.5. 配置SFUD
SFUD的核心功能配置文件在sfud_cfg.h
,修改說明如下:
修改完了之後,還需要去修改剛剛複製替換的sfud_port.c
文件,與剛剛填寫的配置信息相對應:
至此,SFUD移植、配置完成,接下來就可以愉快的使用了!
3. 使用SFUD
使用時包含頭文件:
#include <sfud.h>
3.1. 初始化SFUD
初始化SFUD的API如下,該函數會初始化 Flash 設備表中的全部設備:
sfud_err sfud_init(void);
在QSPI模式下,SFUD 對於 QSPI 模式的支持僅限於快速讀命令,通過該函數可以配置 Flash 所使用的 QSPI 總線的實際支持的數據線最大寬度,例如:1 線(默認值,即傳統的 SPI 模式)、2 線、4 線:
sfud_err sfud_qspi_fast_read_enable(sfud_flash *flash, uint8_t data_line_width);
所以,在main函數中編寫如下初始化函數:
/* USER CODE BEGIN 2 */
/* SFUD初始化 */
if(sfud_init() != SFUD_SUCCESS)
{
printf("SFUD init fail.\r\n");
}
/* 使能QSPI快讀 */
sfud_qspi_fast_read_enable(sfud_get_device(SFUD_W25Q64_DEVICE_INDEX), 1);
/* USER CODE END 2 */
編譯、下載之後,可以在串口終端中看到SFUD打印的日誌:
SFUD初始化Flash設備成功後進行接下來的讀寫測試。
3.2. Flash擦除/讀寫操作
① 讀取Flash數據:
sfud_err sfud_read(const sfud_flash *flash, uint32_t addr, size_t size, uint8_t *data);
② 擦除 Flash 數據:
sfud_err sfud_erase(const sfud_flash *flash, uint32_t addr, size_t size);
③ 往Flash寫數據:
sfud_err sfud_write(const sfud_flash *flash, uint32_t addr, size_t size, const uint8_t *data);
接下來使用作者編寫的demo測試。
首先在main.c
開頭編寫代碼,開闢一塊緩衝區用於存放測試數據:
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* SFUD讀寫Flash數據測試的緩衝區 */
#define SFUD_DEMO_TEST_BUFFER_SIZE 1024
static uint8_t sfud_demo_test_buf[SFUD_DEMO_TEST_BUFFER_SIZE];
/* SFUD讀寫Flash數據測試函數 */
void sfud_demo(uint32_t addr, size_t size, uint8_t *data);
/* USER CODE END 0 */
然後在main.c
最後添加測試函數:
/* USER CODE BEGIN 4 */
/**
* SFUD demo for the first flash device test.
*
* @param addr flash start address
* @param size test flash size
* @param size test flash data buffer
*/
void sfud_demo(uint32_t addr, size_t size, uint8_t *data)
{
sfud_err result = SFUD_SUCCESS;
extern sfud_flash *sfud_dev;
const sfud_flash *flash = sfud_get_device(SFUD_W25Q64_DEVICE_INDEX);
size_t i;
/* prepare write data */
for (i = 0; i < size; i++)
{
data[i] = i;
}
/* erase test */
result = sfud_erase(flash, addr, size);
if (result == SFUD_SUCCESS)
{
printf("Erase the %s flash data finish. Start from 0x%08X, size is %zu.\r\n", flash->name, addr, size);
}
else
{
printf("Erase the %s flash data failed.\r\n", flash->name);
return;
}
/* write test */
result = sfud_write(flash, addr, size, data);
if (result == SFUD_SUCCESS)
{
printf("Write the %s flash data finish. Start from 0x%08X, size is %zu.\r\n", flash->name, addr, size);
}
else
{
printf("Write the %s flash data failed.\r\n", flash->name);
return;
}
/* read test */
result = sfud_read(flash, addr, size, data);
if (result == SFUD_SUCCESS)
{
printf("Read the %s flash data success. Start from 0x%08X, size is %zu. The data is:\r\n", flash->name, addr, size);
printf("Offset (h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\r\n");
for (i = 0; i < size; i++)
{
if (i % 16 == 0)
{
printf("[%08X] ", addr + i);
}
printf("%02X ", data[i]);
if (((i + 1) % 16 == 0) || i == size - 1)
{
printf("\r\n");
}
}
printf("\r\n");
}
else
{
printf("Read the %s flash data failed.\r\n", flash->name);
}
/* data check */
for (i = 0; i < size; i++)
{
if (data[i] != i % 256)
{
printf("Read and check write data has an error. Write the %s flash data failed.\r\n", flash->name);
break;
}
}
if (i == size)
{
printf("The %s flash test is success.\r\n", flash->name);
}
}
/* USER CODE END 4 */
在main函數中,SFUD初始化代碼之後,調用該函數進行Flash測試:
/* 測試Flash讀寫 */
sfud_demo(0, sizeof(sfud_demo_test_buf), sfud_demo_test_buf);
編譯、下載,在串口終端中查看結果:
3.3. 移植前後內存佔用情況
SFUD中獲取Flash信息有兩種方式:
- 使用SFDP 參數方式:開關宏
SFUD_USING_SFDP
; - 使用庫自帶的 Flash 參數信息表:開關宏
SFUD_USING_FLASH_INFO_TABLE
;
本文中兩種方式都開啓,所以移植之後較大,實際使用中可以視情況關閉這兩個功能。
SFDP功能關閉後,只會查詢該庫在 /sfud/inc/sfud_flash_def.h
中提供的 Flash 信息表,代碼量會降低,但是軟件適配性也隨之降低。
查表功能關閉後,該庫只驅動支持 SFDP 規範的 Flash,也會適當的降低部分代碼量。
一般情況下上述二者必須要選擇一個,在實際使用時視情況而定,但是也可以兩者都不開啓,直接指定好具體的某款 Flash 參數。
4. SFUD設計思想解讀
4.1. Flash設備對象
SFUD中最重要的就是Flash設備對象,一切操作都是對這個Flash設備對象進行的,每個Flash設備對象獨立,所以SFUD也支持系統中存在多個Flash設備對象。
Flash設備對象管理着Flash存儲器的所有信息,原型在sfud_def.h
中,定義如下:
/**
* serial flash device
*/
typedef struct {
char *name; /**< serial flash name */
size_t index; /**< index of flash device information table @see flash_table */
sfud_flash_chip chip; /**< flash chip information */
sfud_spi spi; /**< SPI device */
bool init_ok; /**< initialize OK flag */
bool addr_in_4_byte; /**< flash is in 4-Byte addressing */
struct {
void (*delay)(void); /**< every retry's delay */
size_t times; /**< default times for error retry */
} retry;
void *user_data; /**< some user data */
#ifdef SFUD_USING_QSPI
sfud_qspi_read_cmd_format read_cmd_format; /**< fast read cmd format */
#endif
#ifdef SFUD_USING_SFDP
sfud_sfdp sfdp; /**< serial flash discoverable parameters by JEDEC standard */
#endif
} sfud_flash, *sfud_flash_t;
其中Flash設備的通信接口信息由 sfud_spi 對象管理,包括SPI讀寫數據函數,加鎖解鎖函數定義如下:
/**
* SPI device
*/
typedef struct __sfud_spi {
/* SPI device name */
char *name;
/* SPI bus write read data function */
sfud_err (*wr)(const struct __sfud_spi *spi, const uint8_t *write_buf, size_t write_size, uint8_t *read_buf,
size_t read_size);
#ifdef SFUD_USING_QSPI
/* QSPI fast read function */
sfud_err (*qspi_read)(const struct __sfud_spi *spi, uint32_t addr, sfud_qspi_read_cmd_format *qspi_read_cmd_format,
uint8_t *read_buf, size_t read_size);
#endif
/* lock SPI bus */
void (*lock)(const struct __sfud_spi *spi);
/* unlock SPI bus */
void (*unlock)(const struct __sfud_spi *spi);
/* some user data */
void *user_data;
} sfud_spi, *sfud_spi_t;
4.2. JESD216 SFDP標準
SFDP全稱 Serial Flash Discoverable Parameter,它是 JEDEC (固態技術協會)制定的串行 Flash 功能的參數表標準。
該標準規定了,每個 Flash 中會存在一個參數表,該表中會存放 Flash 容量、寫粒度、擦除命令、地址模式等 Flash 規格參數。目前,除了部分廠家舊款 Flash 型號會不支持該標準,其他絕大多數新出廠的 Flash 均已支持 SFDP 標準。
所以 SFUD 在初始化時會優先讀取 SFDP 表參數,以達到SFUD在支持SFDP標準的Flash上全部適用的效果,更加通用。
那麼SFDP標準的內容是什麼呢?SFDP標準強制規範必須要有:
- SFDP標題頭
- 1st參數頭
- JEDEC Flash基本參數表格
SFDP標題頭一般爲“S”“F”“U”“D”,如果能讀取出這四個字符,則認爲該款Flash支持SFDP標準,比如在sfud_sfdp
源碼中校驗代碼如下:
/* check SFDP header */
if (!(header[0] == 'S' &&
header[1] == 'F' &&
header[2] == 'D' &&
header[3] == 'P')) {
SFUD_DEBUG("Error: Check SFDP signature error. It's must be 50444653h('S' 'F' 'D' 'P').");
return false;
}
接下來是一些預留空內容,屬於廠商可選內容,Flash廠商可以在這些空白內容中添加自己的廠商ID識別號、SFDP版本號、參數長度以及存放參數表格的地址指針,比如讀取W25Q64的結果中顯示:
接下來的 JEDEC Flash基本參數表格裏面規範和定義了該器件的一些最基本的讀取方式、指令內容、扇區大小和芯片容量等信息:
4.3. 添加庫目前不支持的 Flash
如果你使用的Flash型號比較老或者不支持SFDP,SFUD庫當然考慮到了這一點,所以提供了Flash設備參數表,在sfdu_flash_def.h
文件的 SFUD_FLASH_CHIP_TABLE 就能看到當前所有支持的 Flash:
如果你使用的Flash型號既不支持SFDP,也不在此Flash設備參數表中,那麼就需要手動添加到該設備參數表中纔可以正常使用。
具體的添加方式請參考SFUD項目的README文檔中2.5節,講述的非常詳細。
5. 項目工程源碼獲取和問題交流
目前我將SFUD源碼、我移植到小熊派STM32L431RCT6開發板的工程源碼上傳到了QQ羣裏(包含好幾份HAL庫,QQ相對速度快點),可以在QQ羣裏下載,有問題也可以在羣裏交流,當然也歡迎大家分享出來自己移植的工程到QQ羣裏:
放上QQ羣二維碼:
接收更多精彩文章及資源推送,歡迎訂閱我的微信公衆號:『mculover666』。