本次介紹的兩個軟體包SFUD/FAL都與FLASH有關,并且都可以獨立使用或者結合在一起使用,兩個軟體包都對作業系統無依賴,可以使用裸機移植,也很友善移植到各種系統。
這兩個軟體包的作者都是armink,armink的開源倉庫位址:https://github.com/armink,更多好玩的軟體,請到作者倉庫查詢。
這兩個軟體包也是OTA的底層,如果對OTA感興趣的也可以參考此文章。
以下将結合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工程
- 通過cubmx打開SPI
- 下載下傳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) {
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:
- 定義片内 flash 裝置可以參考
。fal_flash_stm32f2_port.c
- 定義片外 spi flash 裝置可以參考
。fal_flash_sfud_port.c
- FAL SFUD(W25Q64 Flash)移植拷貝FAL項目samples\porting的fal_flash_sfud_port.c到工程中。因為這個工程是在SFUD的基礎上移植的,是以可以直接使用sfud的API接口:
- 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對分區進行讀寫測試:
此示範項目的工程代碼:https://gitee.com/Aladdin-Wang/RT-FOTA-STM32L431