1. 项目背景与硬件选型
大家好,今天我想分享一个实战项目:如何在STM32F103ZET6上结合SFUD和FATFS,为W25Q32 SPI Flash移植文件系统。如果你正在做嵌入式开发,尤其是需要存储数据到外部Flash的场景,这个方案会非常实用。STM32F103ZET6是一款经典的Cortex-M3内核MCU,资源丰富(512KB Flash、64KB RAM),适合中等复杂度的应用。W25Q32是Winbond的SPI Flash,容量4MB(32Mbit),通过SPI接口通信,常用于存储日志、配置文件或媒体数据。
为什么选SFUD和FATFS?SFUD(Serial Flash Universal Driver)是一个开源通用闪存驱动库,它自动识别Flash参数(如容量、擦除粒度),省去为不同芯片写驱动的麻烦。FATFS是轻量级文件系统,支持FAT12/16/32格式,代码量小且易移植。两者结合,能快速实现可靠的文件存储。我曾在产品中用过这套方案,实测稳定,维护成本低。
硬件连接很简单:STM32F103ZET6的SPI1(PA5/PA6/PA7)接W25Q32的SPI引脚,另加一个片选引脚(如PC13)。注意SPI时钟别太高,初期建议用1-2MHz调试。电源和地线要稳定,避免读写错误。
2. 环境搭建与工程配置
首先,用STM32CubeMX创建基础工程。选择STM32F103ZET6芯片,配置SPI1为全双工主模式,时钟分频设256(初始低速更稳),数据大小8位,CPOL=0/CPHA=0(模式0)。片选引脚PC13设为GPIO_Output。接着使能FATFS:在Middleware中选择FATFS,模式选“User-defined”,这样我们可以自定义底层驱动。最后生成MDK-ARM工程。
SFUD的源码从GitHub获取(https://github.com/armink/SFUD),把sfud目录复制到工程文件夹,包含inc(头文件)、port(移植层)、src(核心源码)。在Keil中添加port和src下的.c文件,并包含inc路径。关键移植文件是sfud_port.c,这里实现硬件相关的SPI读写和初始化。
配置FATFS时,注意ffconf.h中的参数:_USE_LFN设为2(支持长文件名,栈缓冲),_CODE_PAGE为936(中文支持),_MAX_SS改为4096(匹配W25Q32扇区大小)。这些设置影响功能性和资源占用,根据需求调整。
3. SFUD移植与芯片识别
SFUD的核心是统一API(如sfud_read、sfud_erase)和动态参数检测。在sfud_port.c中,先实现spi_write_read函数,处理SPI数据传输。我用HAL库的HAL_SPI_Transmit和HAL_SPI_Receive,注意片选控制:
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_Start(); // 拉低片选
if (write_size > 0) {
if (HAL_SPI_Transmit(&hspi1, (uint8_t*)write_buf, write_size, 1000) != HAL_OK)
result = SFUD_ERR_TIMEOUT;
}
if (read_size > 0) {
if (HAL_SPI_Receive(&hspi1, read_buf, read_size, 1000) != HAL_OK)
result = SFUD_ERR_TIMEOUT;
}
SPI_Stop(); // 拉高片选
return result;
}
接着实现sfud_spi_port_init,设置Flash对象的spi和retry成员:
sfud_err sfud_spi_port_init(sfud_flash* flash) {
flash->spi.wr = spi_write_read;
flash->retry.delay = delay; // 简单软件延时函数
flash->retry.times = 10000;
return SFUD_SUCCESS;
}
在main函数初始化SFUD后,调用sfud_init(),它会自动读取W25Q32的ID(如EF16h),识别容量和擦除参数。通过串口打印日志,确认芯片识别成功。如果失败,检查SPI时序和片选信号。SFUD支持SFDP协议,但W25Q32需依赖预设参数表,好在库已内置常见型号。
4. FATFS底层驱动适配
FATFS移植的关键在user_diskio.c,实现磁盘操作接口。首先修改初始化函数,但这里简单返回状态即可:
DSTATUS USER_initialize(BYTE pdrv) {
return RES_OK;
}
DSTATUS USER_status(BYTE pdrv) {
return RES_OK;
}
重点在读写函数。W25Q32的扇区大小为4KB,所以LBA地址要转换:
DRESULT USER_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count) {
const sfud_flash* flash = sfud_get_device(0);
uint32_t addr = sector * 4096;
sfud_read(flash, addr, count * 4096, buff);
return RES_OK;
}
DRESULT USER_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT count) {
const sfud_flash* flash = sfud_get_device(0);
uint32_t addr = sector * 4096;
sfud_erase_write(flash, addr, count * 4096, buff); // 擦除后写入
return RES_OK;
}
ioctl函数必须实现,提供设备信息:
DRESULT USER_ioctl(BYTE pdrv, BYTE cmd, void* buff) {
switch (cmd) {
case CTRL_SYNC: break; // 同步操作,无额外处理
case GET_SECTOR_COUNT: *(DWORD*)buff = 1024; break; // 总扇区数:4MB/4KB=1024
case GET_SECTOR_SIZE: *(WORD*)buff = 4096; break; // 扇区大小4KB
case GET_BLOCK_SIZE: *(DWORD*)buff = 1; break; // 擦除块大小(扇区数)
default: return RES_PARERR;
}
return RES_OK;
}
确保ffconf.h中_MAX_SS设为4096,匹配扇区大小。首次使用需格式化,调用f_mkfs,创建FAT表和根目录。
5. 文件系统测试与优化
挂载文件系统后,进行读写测试。在main函数中,先初始化SFUD和FATFS:
sfud_init();
FATFS fs;
f_mount(&fs, "0:", 1); // 挂载到驱动器0
f_mkfs("0:", 1, 4096); // 格式化,可选但首次必需
然后创建文件并写入数据:
FIL file;
UINT bw;
f_open(&file, "0:/test.txt", FA_CREATE_ALWAYS | FA_WRITE);
f_write(&file, "Hello FATFS!", 12, &bw);
f_close(&file);
读取验证内容:
char buffer[20];
f_open(&file, "0:/test.txt", FA_READ);
f_read(&file, buffer, sizeof(buffer), &bw);
printf("Read: %s\n", buffer);
f_close(&file);
如果遇到问题,常见坑点包括:SPI时钟太快(导致数据错误)、片选信号不稳定、扇区大小不匹配。建议添加错误处理,检查FATFS返回值(如FR_OK、FR_DISK_ERR)。优化方面,可启用FATFS的缓冲(_FS_TINY设为1)减少RAM占用,或调整SFUD的重试参数提高可靠性。
6. 多设备管理与扩展应用
SFUD支持多Flash设备管理,只需在sfud_port_init中区分pdrv,并初始化多个SPI实例。例如,添加W25Q64作为第二存储:
// 在sfud_port.c中扩展
if (pdrv == 0) {
flash->spi.user_data = &hspi1; // 第一个SPI
} else if (pdrv == 1) {
flash->spi.user_data = &hspi2; // 第二个SPI
}
FATFS中通过驱动器号区分(如"0:"、"1:")。这对备份存储或日志轮换很有用。此外,可结合RTOS(如FreeRTOS),在任务中安全操作文件系统,注意互斥访问共享资源。
扩展功能:支持长文件名需在ffconf.h中设置_USE_LFN和_LFN_UNICODE,并添加cc936.c处理编码。如需实时时间戳,实现get_fattime函数,集成RTC模块。
7. 调试技巧与性能考量
调试时,先用SFUD测试底层读写:写入模式数据(如0xAA55),再读取验证。逻辑分析仪抓SPI波形,确认时序正确。FATFS层,关注f_open和f_write的返回值,常见错误码包括FR_NOT_READY(驱动未就绪)和FR_INVALID_PARAMETER(路径错误)。
性能方面,W25Q32页编程时间约0.3ms,扇区擦除约50ms。批量写入时,合理缓冲数据减少擦除次数。SFUD的sfud_erase_write内部优化了擦除-写入流程,但频繁小文件操作仍可能磨损Flash。建议日志系统采用追加写入,定期归档。
资源消耗:SFUD约占用5KB ROM、0.2KB RAM,FATFS约6-10KB ROM(取决于配置)。STM32F103ZET6的64KB RAM足够,但注意栈大小,尤其启用长文件名时。
8. 实际应用案例
我在智能家居项目中用这套方案存储设备配置和事件日志。例如,每小时将传感器数据写入CSV文件:
void log_data(float temp, float humi) {
FIL log_file;
char line[50];
sprintf(line, "%lu,%.2f,%.2f\n", HAL_GetTick(), temp, humi);
f_open(&log_file, "0:/log.csv", FA_OPEN_APPEND | FA_WRITE);
f_write(&log_file, line, strlen(line), NULL);
f_close(&log_file);
}
通过USB或串口导出文件,方便数据分析。另外,固件升级时,将新固件存到Flash,校验后跳转执行。

1832

被折叠的 条评论
为什么被折叠?



