SFUD | 一款串行 Flash 通用驅動庫

嵌入式開源項目精選專欄

本專欄由Mculover666創建,主要內容爲尋找嵌入式領域內的優質開源項目,一是幫助開發者使用開源項目實現更多的功能,二是通過這些開源項目,學習大佬的代碼及背後的實現思想,提升自己的代碼水平,和其它專欄相比,本專欄的優勢在於:

不會單純的介紹分享項目,還會包含作者親自實踐的過程分享,甚至還會有對它背後的設計思想解讀

目前本專欄包含的開源項目有:

如果您自己編寫或者發現的開源項目不錯,歡迎留言或者私信投稿到本專欄,分享獲得雙倍的快樂!

1. SFUD

本期給大家帶來的開源項目是 SFUD,一款串行 Flash 通用驅動庫,作者armink,目前收穫 407 個 star,遵循 MIT 開源許可協議。

SFUD全稱Serial Flash Universal Driver,是一款開源的串行 SPI Flash 通用驅動庫,由於現有市面的串行 Flash 種類居多,各個 Flash 的規格及命令存在差異, SFUD 就是爲了解決這些 Flash 的差異現狀而設計

SFUD的特點在於:

  • 支持 SPI/QSPI 接口
  • 面向對象設計(同時支持多個 Flash 對象)
  • 可靈活裁剪、擴展性強
  • 支持 4 字節地址

項目地址:https://github.com/armink/SFUD

2. 移植SFUD

2.1. 移植思路

在移植過程中主要參考兩個資料:項目的readme文檔和demo工程。

對於這些開源項目,其實移植起來也就兩步:

  • ① 添加源碼到裸機工程中;
  • ② 實現需要的接口即可;

2.2. 準備裸機工程

本文中我使用的是小熊派IoT開發套件,主控芯片爲STM32L431RCT6:

板載Flash型號爲W25Q64JV,大小64Mbit,與STM32的QSPI接口相連:

移植之前需要準備一份裸機工程,我使用STM32CubeMX生成,需要初始化以下配置:

  • 配置SPI Flash通信接口(SPI或QSPI)
  • 配置一個串口用於打印信息
  • printf重定向

具體過程請參考:

使用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』。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章