STM32 SPI-SD卡极简文件读写|FatFs小型数据日志存储、断电保存、原理深度解析
一、前言
前面专栏已经完成:软件IIC、软件SPI、AT24C02、W25Q64、片内Flash、SHT45、RS485-Modbus全套驱动。
但在实际项目中,出现明显存储瓶颈:
- AT24C02:仅256字节,只能存参数,存不了日志;
- W25Q64:需要自己做分区、环形缓存、日志解析,无文件系统,极难维护;
- 片内Flash:寿命低、容量极小,不能频繁写日志。
最优解决方案:SPI模式SD卡 + FatFs文件系统
优势:
- 容量极大(GB级),适合长时间日志采集。
- 自带文件系统,直接读写 .txt/.csv,电脑直接打开查看。
- 使用软件SPI,不占用硬件外设、无引脚冲突。
- 支持断电续写、时间戳日志、批量存储。
二、SD卡 SPI 模式底层深度解析(必懂原理)
2.1 SD卡两种工作模式区别
- SDIO模式:高速4线传输,速度快,但引脚固定、占用资源多、容易干扰系统
- SPI模式:低速单线、协议简单、兼容性极强、任意GPIO可模拟,工控首选
本项目采用 软件SPI模拟SD卡通信,彻底解决硬件资源冲突。
2.2 SD卡读写核心机制
- SD卡最小读写单位:扇区(512字节)。
- 无论写1字节还是100字节,底层必须一次写满512字节。
- FatFs文件系统负责:扇区映射、文件寻址、空闲块管理、断点续写。
2.3 为什么日志存储必须用文件系统?
裸Flash存储:地址手动管理、容易覆盖、无法续写、无法分类、电脑无法识别。
SD+FatFs:直接创建文件、追加数据、自动管理扇区、断电保存、电脑直接读取日志,是工控日志唯一正规方案。
三、项目硬件架构
主控:STM32F103C8T6 标准库。
通信方式:复用专栏通用 软件SPI。
引脚:SCK/MOSI/MISO 任意GPIO、SD_CS 独立片选。
业务流程:
SHT45温湿度采集 → 数据格式化 → SD卡TXT日志追加保存 → 485上传上位机。
四、极简 FatFs 工程架构
网上大部分FatFs工程极其臃肿、配置复杂、自带大量无用代码。
本文极致精简,仅保留日志存储必要功能:
- 只开启 FA_READ / FA_WRITE / FA_OPEN_ALWAYS 基础功能。
- 关闭长文件名、多级缓存、分区管理、冗余功能。
- 软件SPI底层对接,完全适配自研SPI驱动。
五、核心精简代码
5.1 sd_log.h
#ifndef __SD_LOG_H
#define __SD_LOG_H
#include “stm32f10x.h”
#include “ff.h”
#include “diskio.h”
//日志文件名称
#define LOG_FILE_NAME “data_log.txt”
//初始化SD卡+文件系统
uint8_t SD_Log_Init(void);
//追加一行日志
uint8_t SD_Log_AddLine(char *str);
#endif
5.2 sd_log.c 核心业务层(极简、无冗余)
#include “sd_log.h”
FATFS fs;
FIL file;
FRESULT res;
/**
-
@brief SD卡与文件系统初始化
*/
uint8_t SD_Log_Init(void)
{
//挂载文件系统
res = f_mount(&fs,“”,1);
if(res != FR_OK) return 1;//打开文件,不存在则创建,存在直接续写
res = f_open(&file,LOG_FILE_NAME,FA_OPEN_ALWAYS|FA_READ|FA_WRITE);
if(res != FR_OK) return 2;//移动文件指针到末尾,准备续写日志
f_lseek(&file,f_size(&file));
f_close(&file);return 0;
}
/**
-
@brief 追加一行日志数据
-
@param str 字符串日志
*/
uint8_t SD_Log_AddLine(char *str)
{
UINT bw;
res = f_open(&file,LOG_FILE_NAME,FA_OPEN_ALWAYS|FA_WRITE);
if(res != FR_OK) return 1;//指针移到末尾
f_lseek(&file,f_size(&file));//写入字符串+换行
f_printf(&file,“%s\r\n”,str,&bw);f_close(&file);
return 0;
}
5.3 diskio.c 底层对接软件SPI(关键适配)
完全替换硬件SPI,使用自研软件SPI驱动,无冲突、稳定适配
//磁盘读写底层全部适配软件SPI
#define SD_SPI_RW_BYTE(dat) Soft_SPI_RW_Byte(&hspi1,dat)
#define SD_CS_L() GPIO_ResetBits(GPIOA,GPIO_Pin_2)
#define SD_CS_H() GPIO_SetBits(GPIOA,GPIO_Pin_2)
5.4 main.c 日志存储实战(温湿度自动保存)
#include “stm32f10x.h”
#include “soft_spi.h”
#include “sd_log.h”
#include “sht45.h”
char log_buf[64];
float temp,hum;
int main(void)
{
//外设初始化
Soft_SPI_Init(&hspi1);
SD_Log_Init();
SHT45_Reset(&iic_dev1);
while(1)
{
//读取温湿度
if(SHT45_ReadData(&iic_dev1,&temp,&hum) == 0)
{
//格式化日志字符串
sprintf(log_buf,"Temp:%.2fC, Hum:%.2f%%",temp,hum);
//SD卡追加存储日志
SD_Log_AddLine(log_buf);
}
delay_ms(1000);
}
}
六、核心原理深度解析
6.1 FA_OPEN_ALWAYS 续写机制
- 文件不存在 → 自动创建新文件。
- 文件已存在 → 直接打开保留原有数据。
配合 f_lseek(file, size) 将指针移到末尾,实现断电开机继续续写,永不覆盖历史日志。
6.2 为什么小型日志优先SD卡而不是W25Q64?
- SD卡自带文件系统,无需手动管理扇区。
- 日志可直接电脑打开分析,无需上位机解析。
- 容量巨大,可存储数月甚至数年采集数据。
- FatFs成熟稳定,无丢包、无错乱。
6.3 软件SPI驱动SD卡优势
- 无硬件SPI寄存器BUG、无卡死
- 引脚任意配置,布线自由
- 可与OLED、Flash共用总线,分时复用无冲突
七、项目踩坑独家总结(全网稀缺经验)
坑1:每次开机日志从头覆盖
原因:没有 f_lseek 移动指针到文件末尾;本文代码已自动处理。
坑2:SD卡偶尔写入失败
解决:软件SPI微调延时,SD卡对时序容错较低,必须匹配模式0。
坑3:日志乱码
原因:未换行、缓冲区未清空、多次写入叠加;使用 f_printf 自带格式化最稳定。
坑4:和SPI-OLED同时使用花屏/写失败
解决:严格分时片选,同一总线互斥访问。
八、适用项目场景
- 温湿度、压力、电压长期采集日志。
- 设备运行状态、报警记录存储。
- 需要电脑直接读取数据的小型工控设备。
- 需要长期保存、不能丢数据的采集终端。
更多推荐

所有评论(0)