STM32通用FLASH管理軟件包——SFUD/FAL

本次介紹的兩個軟件包SFUD/FAL都與FLASH有關,並且都可以獨立使用或者結合在一起使用,兩個軟件包都對操作系統無依賴,可以使用裸機移植,也很方便移植到各種系統。
這兩個軟件包的作者都是armink,armink的開源倉庫地址:https://github.com/armink,更多好玩的軟件,請到作者倉庫查詢。
以下將結合rtthread系統,分別對這兩個軟件包做下演示。

1.SFUD

SFUD 是一款開源的串行 SPI Flash 通用驅動庫。由於現有市面的串行 Flash 種類居多,各個 Flash 的規格及命令存在差異, SFUD 就是爲了解決這些 Flash 的差異現狀而設計,讓我們的產品能夠支持不同品牌及規格的 Flash,提高了涉及到 Flash 功能的軟件的可重用性及可擴展性,同時也可以規避 Flash 缺貨或停產給產品所帶來的風險

1.1 主要特點:

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

1.2 資源佔用:

  • 標準佔用:RAM:0.2KB ROM:5.5KB
  • 最小佔用:RAM:0.1KB ROM:3.6KB

1.3 設計原理:

  • 什麼是 SFDP :它是 JEDEC (固態技術協會)制定的串行 Flash 功能的參數表標準,最新版 V1.6B (點擊這裏查看)。該標準規定了,每個 Flash 中會存在一個參數表,該表中會存放 Flash 容量、寫粒度、擦除命令、地址模式等 Flash 規格參數。目前,除了部分廠家舊款 Flash 型號會不支持該標準,其他絕大多數新出廠的 Flash 均已支持 SFDP 標準。所以該庫在初始化時會優先讀取 SFDP 表參數。
  • 不支持 SFDP 怎麼辦 :如果該 Flash 不支持 SFDP 標準,SFUD 會查詢配置文件 ( /sfud/inc/sfud_flash_def.h ) 中提供的 Flash 參數信息表 中是否支持該款 Flash。如果不支持,則可以在配置文件中添加該款 Flash 的參數信息(添加方法詳細見 2.5 添加庫目前不支持的 Flash)。獲取到了 Flash 的規格參數後,就可以實現對 Flash 的全部操作。

1.4 如何移植:

項目地址:https://github.com/armink/SFUD
在移植過程中一定要參考兩個資料:項目的readme文檔和demo工程。

對於使用rtthread完整版來說,作者已經把SFUD製作成了rtthread的內置組件了,對於使用者只需要勾選就可以了:
在這裏插入圖片描述
勾選後,就已經移植完成了,有點太簡單了!
在這裏插入圖片描述
對於rtthread完整版的來說移植太簡單了,不利於切換到其他平臺,所以本次移植教程以rtthread nano爲例,裸機移植可以參考作者的demo工程

  1. 通過cubmx打開SPI
    在這裏插入圖片描述
  2. 下載SFUD項目源碼,並添加到工程目錄中;
    在這裏插入圖片描述
    在這裏插入圖片描述
  3. 完善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) {
    sfud_err result = SFUD_SUCCESS;
    spi_user_data_t spi_dev = (spi_user_data_t) spi->user_data;
    /**
     * add your spi write and read code
     */
    RT_ASSERT(spi);
	
	HAL_GPIO_WritePin(spi_dev->cs_gpiox, spi_dev->cs_gpio_pin, GPIO_PIN_RESET);
	if(write_size && read_size)
	{
		if(HAL_SPI_Transmit(spi_dev->spix, (uint8_t *)write_buf, write_size, 1000)!=HAL_OK)	
		{
			result = SFUD_ERR_WRITE;
		}	
	   /* For simplicity reasons, this example is just waiting till the end of the
	   transfer, but application may perform other tasks while transfer operation
	   is ongoing. */
        while (HAL_SPI_GetState(spi_dev->spix) != HAL_SPI_STATE_READY);
		if(HAL_SPI_Receive(spi_dev->spix, (uint8_t *)read_buf, read_size, 1000)!=HAL_OK)
		{
			result = SFUD_ERR_READ;
		}
	}else if(write_size)
	{
		if(HAL_SPI_Transmit(spi_dev->spix, (uint8_t *)write_buf, write_size, 1000)!=HAL_OK)	
		{
			result = SFUD_ERR_WRITE;
		}
	}else
	{
		if(HAL_SPI_Receive(spi_dev->spix, (uint8_t *)read_buf, read_size, 1000)!=HAL_OK)
		{
			result = SFUD_ERR_READ;
		}
	}
	   /* For simplicity reasons, this example is just waiting till the end of the
    transfer, but application may perform other tasks while transfer operation
    is ongoing. */
	while (HAL_SPI_GetState(spi_dev->spix) != HAL_SPI_STATE_READY);
	HAL_GPIO_WritePin(spi_dev->cs_gpiox, spi_dev->cs_gpio_pin, GPIO_PIN_SET);
    return result;
}

如果使用的是QSPI通信方式,還需要實現快速讀取數據的接口:

#ifdef SFUD_USING_QSPI
/**
 * read flash data by QSPI
 */
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_err result = SFUD_SUCCESS;

    /**
     * add your qspi read flash data code
     */
    RT_ASSERT(spi);
    RT_ASSERT(sfud_dev);
    RT_ASSERT(rtt_dev);

			
    return result;
}
#endif /* SFUD_USING_QSPI */

本次演示使用的是SPI,所以沒有定義SFUD_USING_QSPI這個宏。
② SPI設備對象初始化接口:

static spi_user_data user_spi = { .spix = &hspi2, .cs_gpiox = BSP_DATAFALSH_CS_GPIOX, .cs_gpio_pin = BSP_DATAFALSH_CS_GPIO_PIN };
sfud_err sfud_spi_port_init(sfud_flash *flash) {
    sfud_err result = SFUD_SUCCESS;

    /**
     * add your port spi bus and device object initialize code like this:
     * 1. rcc initialize
     * 2. gpio initialize
     * 3. spi device initialize
     * 4. flash->spi and flash->retry item initialize
     *    flash->spi.wr = spi_write_read; //Required
     *    flash->spi.qspi_read = qspi_read; //Required when QSPI mode enable
     *    flash->spi.lock = spi_lock;
     *    flash->spi.unlock = spi_unlock;
     *    flash->spi.user_data = &spix;
     *    flash->retry.delay = null;
     *    flash->retry.times = 10000; //Required
     */
	rt_mutex_init(&lock, "sfud_lock", RT_IPC_FLAG_FIFO);	
	MX_SPI_Init();
	#if defined(SOC_SERIES_STM32L4) || defined(SOC_SERIES_STM32F0) \
        || defined(SOC_SERIES_STM32F7) || defined(SOC_SERIES_STM32G0)
	
    SET_BIT(hspi2.Instance->CR2, SPI_RXFIFO_THRESHOLD_HF);
	#endif
	switch (flash->index) {
	case SFUD_W25QXX_DEVICE_INDEX: {

			/* 同步 Flash 移植所需的接口及數據 */
			flash->spi.wr = spi_write_read;
			flash->spi.lock = spi_lock;
			flash->spi.unlock = spi_unlock;
			flash->spi.user_data = &user_spi;
			/* about 100 microsecond delay */
			flash->retry.delay = retry_delay_100us;
			/* adout 60 seconds timeout */
			flash->retry.times = 60 * 10000;

			break;
		}
    }
    return result;
}

③ 其他接口移植:

static struct rt_mutex                 lock;
static char log_buf[256];

static void spi_lock(const sfud_spi *spi) {
    sfud_flash *sfud_dev = (sfud_flash *) (spi->user_data);
    struct spi_flash_device *rtt_dev = (struct spi_flash_device *) (sfud_dev->user_data);

    RT_ASSERT(spi);
    RT_ASSERT(sfud_dev);
    RT_ASSERT(rtt_dev);

    rt_mutex_take(&lock, RT_WAITING_FOREVER);
}

static void spi_unlock(const sfud_spi *spi) {
    sfud_flash *sfud_dev = (sfud_flash *) (spi->user_data);
    struct spi_flash_device *rtt_dev = (struct spi_flash_device *) (sfud_dev->user_data);

    RT_ASSERT(spi);
    RT_ASSERT(sfud_dev);
    RT_ASSERT(rtt_dev);

    rt_mutex_release(&lock);
}
static void retry_delay_100us(void) {
    /* 100 microsecond delay */
    rt_thread_delay((RT_TICK_PER_SECOND * 1 + 9999) / 10000);
}
void sfud_log_debug(const char *file, const long line, const char *format, ...) {
    va_list args;

    /* args point to the first variable parameter */
    va_start(args, format);
    rt_kprintf("[SFUD](%s:%ld) ", file, line);
    /* must use vprintf to print */
    rt_vsnprintf(log_buf, sizeof(log_buf), format, args);
    rt_kprintf("%s\n", log_buf);
    va_end(args);
}

/**
 * This function is print routine info.
 *
 * @param format output format
 * @param ... args
 */
void sfud_log_info(const char *format, ...) {
    va_list args;

    /* args point to the first variable parameter */
    va_start(args, format);
    rt_kprintf("[SFUD]");
    /* must use vprintf to print */
    rt_vsnprintf(log_buf, sizeof(log_buf), format, args);
    rt_kprintf("%s\n", log_buf);
    va_end(args);
}

1.5 如何使用:

先說明下本庫主要使用的一個結構體 sfud_flash 。SFUD中最重要的就是Flash設備對象,一切操作都是對這個Flash設備對象進行的,每個Flash設備對象獨立,所以SFUD也支持系統中存在多個Flash設備對象。

Flash設備對象管理着Flash存儲器的所有信息,原型在sfud_def.h中,定義如下:

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;

1.5.1 配置SFUD:

SFUD的核心功能配置文件在sfud_cfg.h,修改說明如下:
在這裏插入圖片描述
修改完了之後,還需要去修改剛剛複製替換的sfud_port.c文件,與剛剛填寫的配置信息相對應:
在這裏插入圖片描述
至此,SFUD移植、配置完成,接下來介紹如何使用API接口!

1.5.2 API 說明:

  • 初始化 SFUD 庫:
    將會調用 sfud_device_init ,初始化 Flash 設備表中的全部設備。如果只有一個 Flash 也可以只使用 sfud_device_init 進行單一初始化。
sfud_err sfud_init(void)
  • 初始化指定的 Flash 設備
sfud_err sfud_device_init(sfud_flash *flash)
參數 描述
flash 待初始化的 Flash 設備
  • 獲取 Flash 設備對象
    在 SFUD 配置文件中會定義 Flash 設備表,負責存放所有將要使用的 Flash 設備對象,所以 SFUD 支持多個 Flash 設備同時驅動。本方法通過 Flash 設備位於設備表中索引值來返回 Flash 設備對象,超出設備表範圍返回 NULL 。
sfud_flash *sfud_get_device(size_t index)
參數 描述
index Flash 設備位於 FLash 設備表中的索引值
  • 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);
sfud_err sfud_erase_write(const sfud_flash *flash, uint32_t addr, size_t size, const uint8_t *data)

1.6 應用示例:

在這裏插入圖片描述
通過調用sfud_init()對sfud初始化後,可以使用rtthread的sfud命令行對flash進行性能測試:
在這裏插入圖片描述

2.FAL

FAL (Flash Abstraction Layer) Flash 抽象層,是對 Flash 及基於 Flash 的分區進行管理、操作的抽象層,對上層統一了 Flash 及 分區操作的 API ,FAL 框架圖如下:
在這裏插入圖片描述
從上圖可以看出FAL抽象層位於SFUD框架的上層,可以將多個Flash硬件(包括片內Flash和片外Flash)統一進行管理,並向上層比如OTA層提供對底層多個Flash硬件的統一訪問接口,方便上層應用對底層硬件的訪問操作。我上篇文章介紹的FOTA就是在FAL層之上做的,理解了這篇文章,FOTA的底層就理解了。參考文章:STM32通用Bootloader——FOTA

2.1 主要特點:

  • 支持靜態可配置的分區表,並可關聯多個 Flash 設備;
  • 分區表支持 自動裝載 。避免在多固件項目,分區表被多次定義的問題;
  • 代碼精簡,對操作系統 無依賴 ,可運行於裸機平臺,比如對資源有一定要求的 Bootloader;
  • 統一的操作接口。保證了文件系統、OTA、NVM(例如:EasyFlash) 等對 Flash 有一定依賴的組件,底層 Flash 驅動的可重用性;
  • 自帶基於 Finsh/MSH 的測試命令,可以通過 Shell 按字節尋址的方式操作(讀寫擦) Flash 或分區,方便開發者進行調試、測試;

2.2 如何移植:

項目地址:https://github.com/RT-Thread-packages/fal

同樣在移植過程中一定要參考兩個資料:項目的readme文檔和samples的移植說明。

對於使用rtthread完整版來說,對於使用者只需要勾選就可以了:
在這裏插入圖片描述
在這裏插入圖片描述
對於rtthread完整版的來說移植很簡單,所以本次移植教程還是以rtthread nano爲例,在上個移植完SFUD工程的基礎上,繼續移植FAL。

2.2.1 下載FAL項目源碼,並添加到工程目錄中;

在這裏插入圖片描述

2.2.2 定義 flash 設備

在定義 Flash 設備表前,需要先定義 Flash 設備。可以是片內 flash, 也可以是片外基於 SFUD 的 spi flash:

  1. FAL SFUD(W25Q64 Flash)移植
    拷貝FAL項目samples\porting的fal_flash_sfud_port.c到工程中。
    因爲這個工程是在SFUD的基礎上移植的,所以可以直接使用sfud的API接口:
    在這裏插入圖片描述
  2. FAL MCU Flash移植
    STM32片內Flash驅動,RT-Thread已經在libraries\HAL_Drivers \drv_flash\目錄下提供了,可以根據芯片自行拷貝到工程:
    在這裏插入圖片描述
    本次演示項目使用的是STM32L431單片機,所以拷貝drv_flash_l4.c到工程中:
    在這裏插入圖片描述
    RT-Thread提供的內部flash驅動通過宏#define PKG_USING_FAL向FAL提供的fal_flash_dev設備對象onchip_flash,包含了STM32L431片內Flash的參數及其訪問接口函數:

在這裏插入圖片描述
在board.h中或者rtconfig.h中定義flash的參數:

#define STM32_FLASH_START_ADRESS     ((uint32_t)0x08000000)
#define STM32_FLASH_SIZE             (256 * 1024)
#define STM32_FLASH_END_ADDRESS      ((uint32_t)(STM32_FLASH_START_ADRESS + STM32_FLASH_SIZE))

#define STM32_SRAM1_START              (0x20000000)      
#define STM32_SRAM1_END                (STM32_SRAM1_START + 64 * 1024)   // 結束地址 = 0x20000000(基址) + 64K(RAM大小)

2.2.3 定義 flash 設備表

Flash 設備表定義在 fal_cfg.h 頭文件中,定義分區表前需 新建 fal_cfg.h 文件 ,請將該文件統一放在對應 BSP 或工程目錄的 port 文件夾下,並將該頭文件路徑加入到工程。fal_cfg.h 可以參考 示例文件 fal/samples/porting/fal_cfg.h 完成。

設備表示例:

extern struct fal_flash_dev nor_flash0;
extern const struct fal_flash_dev stm32_onchip_flash;
/* flash device table */
#define FAL_FLASH_DEV_TABLE                                          \
{  																	\
                                                                  \
    &stm32_onchip_flash,                                           \
    &nor_flash0,                                                     \
}

Flash 設備表中,有兩個 Flash 對象,一個爲 STM32F2 的片內 Flash ,一個爲片外的 Nor Flash。

2.2.4 定義 flash 分區表

分區表也定義在 fal_cfg.h 頭文件中。Flash 分區基於 Flash 設備,每個 Flash 設備又可以有 N 個分區,這些分區的集合就是分區表。在配置分區表前,務必保證已定義好 Flash 設備設備表。fal_cfg.h 可以參考 示例文件 fal/samples/porting/fal_cfg.h 完成。

分區表示例:

#define FAL_PART_TABLE                                                               \
{  																						\
    {FAL_PART_MAGIC_WROD, "app",    "onchip_flash", 64*1024,       192*1024, 0}, \
	{FAL_PART_MAGIC_WROD, "ef", FAL_USING_NOR_FLASH_DEV_NAME, 0 , 1024 * 1024, 0}, \
	{FAL_PART_MAGIC_WROD, "download", FAL_USING_NOR_FLASH_DEV_NAME, 1024 * 1024 , 512 * 1024, 0}, \
	{FAL_PART_MAGIC_WROD, "factory", FAL_USING_NOR_FLASH_DEV_NAME, (1024 + 512) * 1024 , 512 * 1024, 0}, \
}

用戶需要修改的分區參數包括:分區名稱、關聯的 Flash 設備名、偏移地址(相對 Flash 設備內部)、大小,需要注意以下幾點:

  • 分區名保證 不能重複
  • 關聯的 Flash 設備 務必已經在 Flash 設備表中定義好 ,並且 名稱一致 ,否則會出現無法找到 Flash 設備的錯誤;
  • 分區的起始地址和大小 不能超過 Flash 設備的地址範圍 ,否則會導致包初始化錯誤;

至此,FAL移植完成,接下來介紹如何使用API接口!

2.3 如何使用:

API 說明:

查找 Flash 設備

const struct fal_flash_dev *fal_flash_device_find(const char *name)
參數 描述
name Flash 設備名稱
return 如果查找成功,將返回 Flash 設備對象,查找失敗返回 NULL

查找 Flash 分區

const struct fal_partition *fal_partition_find(const char *name)
參數 描述
name Flash 分區名稱
return 如果查找成功,將返回 Flash 分區對象,查找失敗返回 NULL

獲取分區表

const struct fal_partition *fal_get_partition_table(size_t *len)
參數 描述
len 分區表的長度
return 分區表

臨時設置分區表

FAL 初始化時會自動裝載默認分區表。使用該設置將臨時修改分區表,重啓後會 丟失 該設置

void fal_set_partition_table_temp(struct fal_partition *table, size_t len)
參數 描述
table 分區表
len 分區表的長度

從分區讀取數據

int fal_partition_read(const struct fal_partition *part, uint32_t addr, uint8_t *buf, size_t size)
參數 描述
part 分區對象
addr 相對分區的偏移地址
buf 存放待讀取數據的緩衝區
size 待讀取數據的大小
return 返回實際讀取的數據大小

往分區寫入數據

int fal_partition_write(const struct fal_partition *part, uint32_t addr, const uint8_t *buf, size_t size)
參數 描述
part 分區對象
addr 相對分區的偏移地址
buf 存放待寫入數據的緩衝區
size 待寫入數據的大小
return 返回實際寫入的數據大小

擦除分區數據

int fal_partition_erase(const struct fal_partition *part, uint32_t addr, size_t size)
參數 描述
part 分區對象
addr 相對分區的偏移地址
size 擦除區域的大小
return 返回實際擦除的區域大小

擦除整個分區數據

int fal_partition_erase_all(const struct fal_partition *part)
參數 描述
part 分區對象
return 返回實際擦除的區域大小

打印分區表

void fal_show_part_table(void)

創建塊設備

該函數可以根據指定的分區名稱,創建對應的塊設備,以便於在指定的分區上掛載文件系統

struct rt_device *fal_blk_device_create(const char *parition_name)
參數 描述
parition_name 分區名稱
return 創建成功,則返回對應的塊設備,失敗返回空

創建 MTD Nor Flash 設備

該函數可以根據指定的分區名稱,創建對應的 MTD Nor Flash 設備,以便於在指定的分區上掛載文件系統

struct rt_device *fal_mtd_nor_device_create(const char *parition_name)
參數 描述
parition_name 分區名稱
return 創建成功,則返回對應的 MTD Nor Flash 設備,失敗返回空

創建字符設備

該函數可以根據指定的分區名稱,創建對應的字符設備,以便於通過 deivice 接口或 devfs 接口操作分區,開啓了 POSIX 後,還可以通過 oepn/read/write 函數操作分區。

struct rt_device *fal_char_device_create(const char *parition_name)
參數 描述
parition_name 分區名稱
return 創建成功,則返回對應的字符設備,失敗返回空

2.4 應用示例:

通過調用fal_init()對fal初始化後,可以使用rtthread的fal命令行進行性能測試:
在這裏插入圖片描述
FAL對分區進行讀寫測試:
在這裏插入圖片描述
聯繫作者:
歡迎關注本人公衆號:

在這裏插入圖片描述

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