一、前言
前面专栏已经完成:软件IIC、软件SPI、AT24C02、W25Q64、片内Flash、SHT45、RS485-Modbus全套驱动。
但在实际项目中,出现明显存储瓶颈:

  • AT24C02:仅256字节,只能存参数,存不了日志;
  • W25Q64:需要自己做分区、环形缓存、日志解析,无文件系统,极难维护;
  • 片内Flash:寿命低、容量极小,不能频繁写日志。
    最优解决方案:SPI模式SD卡 + FatFs文件系统
    优势:
  1. 容量极大(GB级),适合长时间日志采集。
  2. 自带文件系统,直接读写 .txt/.csv,电脑直接打开查看。
  3. 使用软件SPI,不占用硬件外设、无引脚冲突。
  4. 支持断电续写、时间戳日志、批量存储。

二、SD卡 SPI 模式底层深度解析(必懂原理)
2.1 SD卡两种工作模式区别

  • SDIO模式:高速4线传输,速度快,但引脚固定、占用资源多、容易干扰系统
  • SPI模式:低速单线、协议简单、兼容性极强、任意GPIO可模拟,工控首选
    本项目采用 软件SPI模拟SD卡通信,彻底解决硬件资源冲突。

2.2 SD卡读写核心机制

  1. SD卡最小读写单位:扇区(512字节)。
  2. 无论写1字节还是100字节,底层必须一次写满512字节。
  3. 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?

  1. SD卡自带文件系统,无需手动管理扇区。
  2. 日志可直接电脑打开分析,无需上位机解析。
  3. 容量巨大,可存储数月甚至数年采集数据。
  4. FatFs成熟稳定,无丢包、无错乱。

6.3 软件SPI驱动SD卡优势

  • 无硬件SPI寄存器BUG、无卡死
  • 引脚任意配置,布线自由
  • 可与OLED、Flash共用总线,分时复用无冲突

七、项目踩坑独家总结(全网稀缺经验)
坑1:每次开机日志从头覆盖
原因:没有 f_lseek 移动指针到文件末尾;本文代码已自动处理。
坑2:SD卡偶尔写入失败
解决:软件SPI微调延时,SD卡对时序容错较低,必须匹配模式0。
坑3:日志乱码
原因:未换行、缓冲区未清空、多次写入叠加;使用 f_printf 自带格式化最稳定。
坑4:和SPI-OLED同时使用花屏/写失败
解决:严格分时片选,同一总线互斥访问。


八、适用项目场景

  • 温湿度、压力、电压长期采集日志。
  • 设备运行状态、报警记录存储。
  • 需要电脑直接读取数据的小型工控设备。
  • 需要长期保存、不能丢数据的采集终端。
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐