專欄推薦 | 開源項(xiàng)目解讀
嵌入式開源項(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)勢(shì)在于:
不會(huì)單純的介紹分享項(xiàng)目,還會(huì)包含作者親自實(shí)踐的過程分享,甚至還會(huì)有對(duì)它背后的設(shè)計(jì)思想解讀。
目前本專欄包含的開源項(xiàng)目有:
-
第1期 | MultiButton,一個(gè)小巧簡(jiǎn)單易用的事件驅(qū)動(dòng)型按鍵驅(qū)動(dòng)模塊 -
第2期 | letter-shell,一個(gè)功能強(qiáng)大的嵌入式shell -
第3期 | EasyLogger,一款輕量級(jí)且高性能的日志庫(kù)
如果您自己編寫或者發(fā)現(xiàn)的開源項(xiàng)目不錯(cuò),歡迎留言或者私信投稿到本專欄,分享獲得雙倍的快樂!
1. SFUD
本期給大家?guī)淼拈_源項(xiàng)目是 SFUD,一款串行 Flash 通用驅(qū)動(dòng)庫(kù),作者armink,目前收獲 407 個(gè) star,遵循 MIT 開源許可協(xié)議。
SFUD全稱Serial Flash Universal Driver,是一款開源的串行 SPI Flash 通用驅(qū)動(dòng)庫(kù),由于現(xiàn)有市面的串行 Flash 種類居多,各個(gè) Flash 的規(guī)格及命令存在差異, SFUD 就是為了解決這些 Flash 的差異現(xiàn)狀而設(shè)計(jì)。
SFUD的特點(diǎn)在于:
-
支持 SPI/QSPI 接口 -
面向?qū)ο笤O(shè)計(jì)(同時(shí)支持多個(gè) Flash 對(duì)象) -
可靈活裁剪、擴(kuò)展性強(qiáng) -
支持 4 字節(jié)地址
項(xiàng)目地址:https://github.com/armink/SFUD
2. 移植SFUD
2.1. 移植思路
在移植過程中主要參考兩個(gè)資料:項(xiàng)目的readme文檔和demo工程。
對(duì)于這些開源項(xiàng)目,其實(shí)移植起來也就兩步:
-
① 添加源碼到裸機(jī)工程中; -
② 實(shí)現(xiàn)需要的接口即可;
2.2. 準(zhǔn)備裸機(jī)工程
本文中我使用的是小熊派IoT開發(fā)套件,主控芯片為STM32L431RCT6:板載Flash型號(hào)為W25Q64JV
,大小64Mbit,與STM32的QSPI接口相連:
移植之前需要準(zhǔn)備一份裸機(jī)工程,我使用STM32CubeMX生成,需要初始化以下配置:
-
配置SPI Flash通信接口(SPI或QSPI) -
配置一個(gè)串口用于打印信息 -
printf重定向
具體過程請(qǐng)參考:
-
STM32CubeMX_06 | 使用USART發(fā)送和接收數(shù)據(jù)(查詢模式) -
STM32CubeMX_09 | 重定向printf函數(shù)到串口輸出的多種方法 -
STM32CubeMX_18 | 使用硬件QSPI讀寫SPI Flash(W25Q64)
使用CubeMX配置好SPI或QSPI通信即可,不用編寫W25Q64驅(qū)動(dòng)。
2.3. 添加SFUD到工程中
① 復(fù)制源碼到工程中:② 在keil中添加 SFUD 組件的源碼文件:
-
src\sfud.c
:SFUD核心功能源碼; -
src\sfud_sfdp.c
:讀取并分析SFDP功能源碼; -
port\sfud_port.c
:SFUD移植接口;
③ 將sfud/inc
頭文件路徑添加到keil中:
2.4. 實(shí)現(xiàn)SFUD移植接口
SFUD的移植接口都已經(jīng)寫好了,在sfud_port.c
文件中,只需要在函數(shù)體中添加代碼即可。
① 底層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通信方式,還需要實(shí)現(xiàn)快速讀取數(shù)據(jù)的接口:
/**
* 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設(shè)備對(duì)象初始化接口:
sfud_err sfud_spi_port_init(sfud_flash *flash);
關(guān)于SFUD底層所抽象出來的SPI設(shè)備對(duì)象,在接下來的設(shè)計(jì)思想解讀章節(jié)中會(huì)詳細(xì)講述。
本文中所使用的裸機(jī)工程是基于HAL庫(kù)的,在SFUD源碼的Demo中也有一份HAL庫(kù)的工程,因?yàn)?strong style="color: black;">基于HAL庫(kù)的移植接口實(shí)現(xiàn)都是一樣的,所以我直接將Demo中的sfud_port.c
文件復(fù)制過來替換:復(fù)制過來之后,如果使用的不是STM32L4系列的芯片,則需要修改sfud_port.c
中包含的頭文件:
2.5. 配置SFUD
SFUD的核心功能配置文件在sfud_cfg.h
,修改說明如下:修改完了之后,還需要去修改剛剛復(fù)制替換的sfud_port.c
文件,與剛剛填寫的配置信息相對(duì)應(yīng):至此,SFUD移植、配置完成,接下來就可以愉快的使用了!
3. 使用SFUD
使用時(shí)包含頭文件:
#include <sfud.h>
3.1. 初始化SFUD
初始化SFUD的API如下,該函數(shù)會(huì)初始化 Flash 設(shè)備表中的全部設(shè)備:
sfud_err sfud_init(void);
在QSPI模式下,SFUD 對(duì)于 QSPI 模式的支持僅限于快速讀命令,通過該函數(shù)可以配置 Flash 所使用的 QSPI 總線的實(shí)際支持的數(shù)據(jù)線最大寬度,例如:1 線(默認(rèn)值,即傳統(tǒng)的 SPI 模式)、2 線、4 線:
sfud_err sfud_qspi_fast_read_enable(sfud_flash *flash, uint8_t data_line_width);
所以,在main函數(shù)中編寫如下初始化函數(shù):
/* 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設(shè)備成功后進(jìn)行接下來的讀寫測(cè)試。
3.2. Flash擦除/讀寫操作
① 讀取Flash數(shù)據(jù):
sfud_err sfud_read(const sfud_flash *flash, uint32_t addr, size_t size, uint8_t *data);
② 擦除 Flash 數(shù)據(jù):
sfud_err sfud_erase(const sfud_flash *flash, uint32_t addr, size_t size);
③ 往Flash寫數(shù)據(jù):
sfud_err sfud_write(const sfud_flash *flash, uint32_t addr, size_t size, const uint8_t *data);
接下來使用作者編寫的demo測(cè)試。
首先在main.c
開頭編寫代碼,開辟一塊緩沖區(qū)用于存放測(cè)試數(shù)據(jù):
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* SFUD讀寫Flash數(shù)據(jù)測(cè)試的緩沖區(qū) */
#define SFUD_DEMO_TEST_BUFFER_SIZE 1024
static uint8_t sfud_demo_test_buf[SFUD_DEMO_TEST_BUFFER_SIZE];
/* SFUD讀寫Flash數(shù)據(jù)測(cè)試函數(shù) */
void sfud_demo(uint32_t addr, size_t size, uint8_t *data);
/* USER CODE END 0 */
然后在main.c
最后添加測(cè)試函數(shù):
/* 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函數(shù)中,SFUD初始化代碼之后,調(diào)用該函數(shù)進(jìn)行Flash測(cè)試:
/* 測(cè)試Flash讀寫 */
sfud_demo(0, sizeof(sfud_demo_test_buf), sfud_demo_test_buf);
編譯、下載,在串口終端中查看結(jié)果:
3.3. 移植前后內(nèi)存占用情況
SFUD中獲取Flash信息有兩種方式:
-
使用SFDP 參數(shù)方式:開關(guān)宏 SFUD_USING_SFDP
; -
使用庫(kù)自帶的 Flash 參數(shù)信息表:開關(guān)宏 SFUD_USING_FLASH_INFO_TABLE
;
本文中兩種方式都開啟,所以移植之后較大,實(shí)際使用中可以視情況關(guān)閉這兩個(gè)功能。
SFDP功能關(guān)閉后,只會(huì)查詢?cè)搸?kù)在 /sfud/inc/sfud_flash_def.h
中提供的 Flash 信息表,代碼量會(huì)降低,但是軟件適配性也隨之降低。
查表功能關(guān)閉后,該庫(kù)只驅(qū)動(dòng)支持 SFDP 規(guī)范的 Flash,也會(huì)適當(dāng)?shù)慕档筒糠执a量。
一般情況下上述二者必須要選擇一個(gè),在實(shí)際使用時(shí)視情況而定,但是也可以兩者都不開啟,直接指定好具體的某款 Flash 參數(shù)。
4. SFUD設(shè)計(jì)思想解讀
4.1. Flash設(shè)備對(duì)象
SFUD中最重要的就是Flash設(shè)備對(duì)象,一切操作都是對(duì)這個(gè)Flash設(shè)備對(duì)象進(jìn)行的,每個(gè)Flash設(shè)備對(duì)象獨(dú)立,所以SFUD也支持系統(tǒng)中存在多個(gè)Flash設(shè)備對(duì)象。
Flash設(shè)備對(duì)象管理著Flash存儲(chǔ)器的所有信息,原型在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設(shè)備的通信接口信息由 sfud_spi 對(duì)象管理,包括SPI讀寫數(shù)據(jù)函數(shù),加鎖解鎖函數(shù)定義如下:
/**
* 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標(biāo)準(zhǔn)
SFDP全稱 Serial Flash Discoverable Parameter,它是 JEDEC (固態(tài)技術(shù)協(xié)會(huì))制定的串行 Flash 功能的參數(shù)表標(biāo)準(zhǔn)。
該標(biāo)準(zhǔn)規(guī)定了,每個(gè) Flash 中會(huì)存在一個(gè)參數(shù)表,該表中會(huì)存放 Flash 容量、寫粒度、擦除命令、地址模式等 Flash 規(guī)格參數(shù)。目前,除了部分廠家舊款 Flash 型號(hào)會(huì)不支持該標(biāo)準(zhǔn),其他絕大多數(shù)新出廠的 Flash 均已支持 SFDP 標(biāo)準(zhǔn)。
所以 SFUD 在初始化時(shí)會(huì)優(yōu)先讀取 SFDP 表參數(shù),以達(dá)到SFUD在支持SFDP標(biāo)準(zhǔn)的Flash上全部適用的效果,更加通用。
那么SFDP標(biāo)準(zhǔn)的內(nèi)容是什么呢?SFDP標(biāo)準(zhǔn)強(qiáng)制規(guī)范必須要有:
-
SFDP標(biāo)題頭 -
1st參數(shù)頭 -
JEDEC Flash基本參數(shù)表格
SFDP標(biāo)題頭一般為“S”“F”“U”“D”,如果能讀取出這四個(gè)字符,則認(rèn)為該款Flash支持SFDP標(biāo)準(zhǔn),比如在sfud_sfdp
源碼中校驗(yàn)代碼如下:
/* 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;
}
接下來是一些預(yù)留空內(nèi)容,屬于廠商可選內(nèi)容,F(xiàn)lash廠商可以在這些空白內(nèi)容中添加自己的廠商ID識(shí)別號(hào)、SFDP版本號(hào)、參數(shù)長(zhǎng)度以及存放參數(shù)表格的地址指針,比如讀取W25Q64的結(jié)果中顯示:接下來的 JEDEC Flash基本參數(shù)表格里面規(guī)范和定義了該器件的一些最基本的讀取方式、指令內(nèi)容、扇區(qū)大小和芯片容量等信息:
4.3. 添加庫(kù)目前不支持的 Flash
如果你使用的Flash型號(hào)比較老或者不支持SFDP,SFUD庫(kù)當(dāng)然考慮到了這一點(diǎn),所以提供了Flash設(shè)備參數(shù)表,在sfdu_flash_def.h
文件的 SFUD_FLASH_CHIP_TABLE 就能看到當(dāng)前所有支持的 Flash:如果你使用的Flash型號(hào)既不支持SFDP,也不在此Flash設(shè)備參數(shù)表中,那么就需要手動(dòng)添加到該設(shè)備參數(shù)表中才可以正常使用。
具體的添加方式請(qǐng)參考SFUD項(xiàng)目的README文檔中2.5節(jié),講述的非常詳細(xì)。
5. 項(xiàng)目工程源碼獲取和問題交流
目前我將SFUD源碼、我移植到小熊派STM32L431RCT6開發(fā)板的工程源碼上傳到了QQ群里(包含好幾份HAL庫(kù),QQ相對(duì)速度快點(diǎn)),可以在QQ群里下載,有問題也可以在群里交流,當(dāng)然也歡迎大家分享出來自己移植的工程到QQ群里:放上QQ群二維碼:
接收更多精彩文章及資源推送,歡迎訂閱我的微信公眾號(hào):『mculover666』。
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問題,請(qǐng)聯(lián)系我們,謝謝!