SFUD(Serial Flash Universal Driver)是一款开源的串行 SPI Flash 通用驱动库。它通过提供一套跨平台、统一且易用的 API,将开发者从适配不同厂商、不同型号 SPI Flash 芯片的繁琐工作中解放出来,极大地简化了底层存储驱动的开发。
应用场景
SFUD 的通用性使其能够广泛应用于多种嵌入式及物联网项目中。
嵌入式系统开发
- IoT 智能网关:用于存储设备配置参数、采集的用户数据以及固件镜像,为远程 OTA(空中下载)升级提供可靠的存储基础。
- 工业 PLC:存储控制程序、关键的工艺参数和运行日志,其高可靠性设计确保了工业场景下数据的安全与完整。
- 智能手表:存储用户的健康监测数据、运动记录以及个人系统配置,在有限的资源下实现数据的有效管理。
固件更新系统
- 远程 OTA 升级:通过网络接收新固件包,经安全校验后写入 Flash 的特定分区,并可支持断点续传功能。
- 双分区固件管理:实现 A/B 分区切换机制,确保在主分区升级失败时,系统能自动回滚至备份分区,保障设备可用性。
- 固件完整性校验:在写入前后,使用 MD5 或 SHA 等哈希算法验证固件包的完整性,防止因传输错误导致刷入损坏的固件。
数据存储应用
- 配置参数管理:采用键值对或结构体等组织形式存储设备参数,并可通过版本号进行管理,便于后期维护与兼容。
- 事件日志记录:实现循环日志缓冲区,当存储空间写满时自动覆盖最早的数据,确保最新的关键事件总是被保留。
- 用户数据持久化:可靠地保存用户的个性化设置和配置,即使在设备断电重启后,这些数据也能保持不变。
快速上手
核心 API 概览
SFUD 提供了一套简洁的核心 API 用于 Flash 的基本操作。
| API 函数 |
功能描述 |
参数说明 |
返回值 |
sfud_init() |
初始化 SFUD 库,探测并初始化 SPI Flash 设备 |
无 |
sfud_err:操作结果,SFUD_SUCCESS 表示成功 |
sfud_get_device() |
获取 Flash 设备对象 |
index:设备索引(从 0 开始) |
const sfud_flash*:Flash 设备指针,NULL 表示失败 |
sfud_erase() |
擦除 Flash 指定区域(库自动选择最佳擦除命令) |
flash:Flash 设备指针<br>addr:起始地址(必须对齐到擦除块边界)<br>size:擦除大小(必须是擦除块大小的整数倍) |
sfud_err:操作结果 |
sfud_write() |
向 Flash 写入数据(自动处理页边界和写前擦除检查) |
flash:Flash 设备指针<br>addr:起始地址<br>size:写入大小<br>data:要写入的数据缓冲区 |
sfud_err:操作结果 |
sfud_read() |
从 Flash 读取数据 |
flash:Flash 设备指针<br>addr:起始地址<br>size:读取大小<br>data:用于存储读取数据的缓冲区 |
sfud_err:操作结果 |
sfud_chip_erase() |
擦除整个 Flash 芯片(耗时较长) |
flash:Flash 设备指针 |
sfud_err:操作结果 |
sfud_is_flash_busy() |
检查 Flash 是否处于忙状态(例如正在擦除或写入) |
flash:Flash 设备指针 |
bool:true 表示忙,false 表示空闲 |
sfud_get_error_code_string() |
获取错误码对应的可读字符串描述 |
error_code:错误码 |
const char*:错误描述字符串 |
API 使用示例
以下代码片段展示了 SFUD 核心 API 的基本调用流程。
初始化和获取设备
// 初始化 SFUD 库
if (sfud_init() != SFUD_SUCCESS) {
printf("SFUD init failed\r\n");
return -1;
}
// 获取第一个 Flash 设备
const sfud_flash *flash = sfud_get_device(0);
if (flash == NULL) {
printf("Get flash device failed\r\n");
return -1;
}
擦除操作
// 擦除 4KB 扇区
sfud_err result = sfud_erase(flash, 0x00000000, 4096);
if (result != SFUD_SUCCESS) {
printf("Erase failed: %s\r\n", sfud_get_error_code_string(result));
return -1;
}
// 擦除整个芯片(耗时较长)
result = sfud_chip_erase(flash);
if (result != SFUD_SUCCESS) {
printf("Chip erase failed: %s\r\n", sfud_get_error_code_string(result));
return -1;
}
读写操作
// 写入数据
uint8_t write_buf[16] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10};
result = sfud_write(flash, 0x00000000, sizeof(write_buf), write_buf);
if (result != SFUD_SUCCESS) {
printf("Write failed: %s\r\n", sfud_get_error_code_string(result));
return -1;
}
// 读取数据
uint8_t read_buf[16];
result = sfud_read(flash, 0x00000000, sizeof(read_buf), read_buf);
if (result != SFUD_SUCCESS) {
printf("Read failed: %s\r\n", sfud_get_error_code_string(result));
return -1;
}
集成实现
集成步骤
将 SFUD 集成到你的项目中通常包含以下几个步骤。
1. 获取源码
从官方仓库克隆源代码:
# 从 GitHub 克隆源码
git clone https://github.com/armink/SFUD.git
2. 添加文件到项目
- 将
sfud.c、sfud.h 核心文件添加到项目的源代码目录。
- 将
sfud_flash_def.h(包含预定义的芯片支持列表)添加到项目中。
- 根据你的目标平台(如 RT-Thread、Arduino、裸机等),从
port 目录下选择或参考对应的平台适配文件。
3. 实现硬件适配函数
SFUD 需要通过你实现的底层 spi_write_read 函数与硬件 SPI 交互。以下是一个基于 STM32 HAL 库的示例:
/**
* SPI 接口读写函数
* @param spi SPI 设备结构体
* @param write_buf 写入缓冲区
* @param write_size 写入大小
* @param read_buf 读取缓冲区
* @param read_size 读取大小
* @return 操作结果
*/
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)
{
// 片选使能
HAL_GPIO_WritePin(SPI_FLASH_CS_GPIO_Port, SPI_FLASH_CS_Pin, GPIO_PIN_RESET);
// 写入数据
if (write_size > 0) {
HAL_SPI_Transmit(&hspi1, (uint8_t *)write_buf, write_size, 1000);
}
// 读取数据
if (read_size > 0) {
HAL_SPI_Receive(&hspi1, read_buf, read_size, 1000);
}
// 片选禁用
HAL_GPIO_WritePin(SPI_FLASH_CS_GPIO_Port, SPI_FLASH_CS_Pin, GPIO_PIN_SET);
return SFUD_SUCCESS;
}
此函数是驱动与硬件连接的桥梁,任何 SPI 通信问题都可能首先在这里出现,是 嵌入式系统开发 中硬件调试的关键环节之一。
4. 初始化和使用 SFUD
完成硬件适配后,便可在应用层调用 SFUD 进行完整的读写测试。
#include "sfud.h"
int main(void) {
sfud_err result = SFUD_SUCCESS;
const sfud_flash *flash = NULL; // 注意:标准API返回的是const指针
// 1. 初始化SFUD库
result = sfud_init();
if (result != SFUD_SUCCESS) {
printf("SFUD init failed: %d\r\n", result);
return -1;
}
// 2. 获取Flash设备对象(索引号根据你在sfud_cfg.h中的配置)
flash = sfud_get_device(0); // 获取设备表中第一个Flash设备
if (flash == NULL) {
printf("Get SFUD flash device failed!\r\n");
return -1;
}
// 3. 打印Flash信息
printf("Flash info:\r\n");
printf(" Name: %s\r\n", flash->name);
printf(" Size: %ld bytes\r\n", flash->chip.size);
printf(" Sector size: %ld bytes\r\n", flash->chip.sector_size);
printf(" Page size: %ld bytes\r\n", flash->chip.page_size);
// 4. 擦除第一个扇区(必须确保地址0是扇区起始地址,且size是扇区大小的整数倍)
result = sfud_erase(flash, 0, flash->chip.sector_size); // 使用芯片自身的扇区大小更安全
if (result != SFUD_SUCCESS) {
printf("Erase failed: %d\r\n", result);
return -1;
}
printf("Erase sector success\r\n");
// 5. 写入数据(确保目标区域已擦除)
uint8_t write_data[16] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10};
result = sfud_write(flash, 0, sizeof(write_data), write_data);
if (result != SFUD_SUCCESS) {
printf("Write failed: %d\r\n", result);
return -1;
}
printf("Write data success\r\n");
// 6. 读取数据并验证
uint8_t read_data[16];
result = sfud_read(flash, 0, sizeof(read_data), read_data);
if (result != SFUD_SUCCESS) {
printf("Read failed: %d\r\n", result);
return -1;
}
bool data_match = true;
for (int i = 0; i < sizeof(write_data); i++) {
if (read_data[i] != write_data[i]) {
data_match = false;
break;
}
}
if (data_match) {
printf("Data verify success\r\n");
} else {
printf("Data verify failed\r\n");
// 注意:写入后立即读取不一致,通常是因为写入前未正确擦除
return -1;
}
printf("SFUD test completed successfully!\r\n");
return 0;
}
常见问题处理
在集成和使用过程中,你可能会遇到以下典型问题。
| 问题 |
可能原因 |
解决方案 |
| 初始化失败 |
SPI 硬件连接错误、时钟配置不正确、片选信号异常。 |
检查硬件连线,使用逻辑分析仪抓取 SPI 波形,确认时序和 片选 信号符合芯片手册要求。 |
| 芯片识别失败 |
使用的 Flash 芯片型号不在 SFUD 默认支持列表中。 |
首先检查 sfud_flash_def.h 确认是否支持。若不支持,可在配置文件中通过 SFUD_FLASH_DEVICE_TABLE 宏手动添加芯片参数。 |
| 写入失败 |
目标存储区域在写入前未被擦除(Flash 特性要求写前必须擦)。 |
确保在执行 sfud_write() 前,已对目标地址范围调用 sfud_erase()。SFUD 的写函数内部有检查,但显式擦除是良好实践。 |
| 读取数据错误 |
地址计算超出芯片范围,或发生了位翻转(概率较低)。 |
核对读写地址是否在芯片容量内。对于关键数据,可考虑增加 ECC 校验或使用文件系统。 |
| 性能不佳 |
SPI 时钟频率设置过低。 |
在硬件和芯片允许的范围内,尽可能提高 SPI 主时钟频率,这能显著提升读写速度。 |
参考资料
[1] 嵌入式存储开发高效应用, 微信公众号:mp.weixin.qq.com/s/yLymgb9SOAO9mRMdyiJ7qQ
版权声明:本文由 云栈社区 整理发布,版权归原作者所有。