核心目标:掌握 STM32 通过 SPI 接口驱动 SD 卡,基于 FATFS 文件系统实现数据的本地存储(创建文件、写入日志、读取配置),解决 “设备离线时数据丢失” 问题 —— 这是嵌入式设备(如记录仪、监测终端)的必备能力,可实现传感器数据本地备份、设备配置持久化、故障日志记录等关键功能。
一、嵌入式文件系统:为什么需要 “本地存储”?(20 分钟)
1. 核心痛点:数据不能只依赖 “实时传输”
- 物联网设备可能因网络中断(如 WiFi 信号差)导致数据丢失;
- 工业设备需要记录故障日志(如温度超标时间点),方便后期排查;
- 设备配置参数(如 WiFi 密码、Modbus 地址)需断电后保留,不能存在 RAM 中(断电丢失)。
2. 解决方案:SD 卡 + FATFS 文件系统
- SD 卡:低成本、大容量(从 MB 到 GB 级)、易插拔,支持 SPI 接口(STM32 通用);
- FATFS:开源的通用文件系统库,支持 FAT12/FAT16/FAT32 格式,可直接在 STM32 上移植,无需深入理解文件系统底层原理。
3. 核心应用场景
- 传感器日志:按时间戳记录温湿度、电压等数据(如 “2025-11-06 10:00:00, 25.5℃, 45.2% RH”);
- 配置文件:存储 WiFi 账号密码、设备阈值等(掉电不丢失,下次启动自动读取);
- 固件备份:存储 OTA 升级的固件包(防止传输中断导致升级失败)。
二、硬件准备与 SPI 通信原理(20 分钟)
1. 硬件清单
- STM32F103C8T6 开发板、AHT10 温湿度传感器(复用);
- SD 卡模块(如 W25Q 系列或标准 SD 卡转接板,支持 SPI 模式);
- microSD 卡(已格式化为 FAT32 格式,容量≤32GB);
- 杜邦线、面包板。
2. SPI 通信:STM32 与 SD 卡的 “高速接口”
SPI 是一种高速同步串行通信协议,采用 “主从模式”,STM32 作为主机,SD 卡作为从机,通过 4 根线通信:
- SCLK(时钟线):STM32 输出时钟信号(控制通信速率,如 18MHz);
- MOSI(主机输出,从机输入):STM32 向 SD 卡发送数据;
- MISO(主机输入,从机输出):SD 卡向 STM32 返回数据;
- CS(片选线):STM32 通过高低电平控制 SD 卡是否工作(低电平选中)。
3. 接线方式(SPI 模式)
| 设备 | 引脚连接(STM32 ↔ SD 卡模块) | 说明 |
|---|---|---|
| STM32 SPI1_SCK(PA5) | → SD 卡 SCLK | 时钟线 |
| STM32 SPI1_MOSI(PA7) | → SD 卡 MOSI | 主机发送数据 |
| STM32 SPI1_MISO(PA6) | → SD 卡 MISO | 主机接收数据 |
| STM32 PA4 | → SD 卡 CS | 片选控制(低电平有效) |
| SD 卡模块 VCC | → 3.3V | 严禁接 5V,避免烧毁 SD 卡 |
| SD 卡模块 GND | → STM32 GND | 共地保证电平一致 |
三、CubeIDE 配置与 FATFS 移植(30 分钟)
1. 工程配置步骤
-
新建工程,启用必要外设:
- SPI1:Mode=“Full-Duplex Master”(全双工主机),Prescaler=“2”(72MHz/2=36MHz,低于 SD 卡最大支持速率);
- GPIO:PA4(CS)设为 “GPIO_Output”(推挽输出);
- I2C1(AHT10)、USART1(调试串口);
- RTC(实时时钟,用于日志时间戳,需配置 LSE 时钟)。
-
启用 FATFS 文件系统:
- 左侧 “Middleware→FATFS”,选择 “SD Card”(存储介质),“SPI”(通信接口);
- 配置 FATFS 参数:Code Page=“936”(支持中文路径 / 文件名),Volume Label=“STM32_LOG”(卷标名)。
-
生成代码:CubeIDE 自动生成 SPI 初始化、FATFS 底层接口(如
disk_initialize、disk_read)代码。
四、核心代码实现:SD 卡日志系统(60 分钟)
目标:实现 “温湿度数据按时间戳写入 SD 卡日志文件”,同时支持读取配置文件中的报警阈值(如温度上限 30℃)。
1. 基础文件操作函数(基于 FATFS 库)
FATFS 提供了标准化的文件操作 API,核心函数如下:
f_mount:挂载文件系统(初始化 SD 卡);f_open:打开 / 创建文件(如 “log.txt”);f_write:向文件写入数据;f_read:从文件读取数据;f_close:关闭文件;f_lseek:移动文件指针(如追加写入时移到文件末尾)。
2. 完整代码实现
c
/* USER CODE BEGIN 0 */
#include <string.h>
#include <stdio.h>
#include "ff.h" // FATFS库头文件
#include "rtc.h" // RTC实时时钟
// 全局变量
FATFS fs; // 文件系统对象
FIL log_file; // 日志文件对象
FIL config_file; // 配置文件对象
FRESULT fr; // 文件操作结果
UINT bw, br; // 写入/读取字节数
// 温湿度数据(复用)
float temp = 0.0, humi = 0.0;
uint8_t AHT10_ReadData(I2C_HandleTypeDef *hi2c) { ... } // 更新temp和humi
// 实时时钟时间结构体(用于日志时间戳)
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef sDate = {0};
// SD卡初始化(挂载文件系统)
uint8_t SD_Init() {
// 片选SD卡(CS低电平)
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
// 挂载文件系统("0:"表示第一个存储设备)
fr = f_mount(&fs, "0:", 1); // 1=立即挂载
if (fr != FR_OK) {
HAL_UART_Transmit(&huart1, (uint8_t*)"SD卡挂载失败!\r\n", 16, 100);
return 1;
}
HAL_UART_Transmit(&huart1, (uint8_t*)"SD卡挂载成功!\r\n", 16, 100);
return 0;
}
// 从配置文件读取报警阈值(config.txt格式:temp_threshold=30.0\nhumi_threshold=60.0)
void Read_Config(float *temp_th, float *humi_th) {
char config_buf[100] = {0};
// 打开配置文件(若不存在则创建)
fr = f_open(&config_file, "0:config.txt", FA_READ | FA_OPEN_EXISTING);
if (fr == FR_NO_FILE) {
// 文件不存在,创建并写入默认值
fr = f_open(&config_file, "0:config.txt", FA_WRITE | FA_CREATE_ALWAYS);
sprintf(config_buf, "temp_threshold=30.0\nhumi_threshold=60.0\r\n");
f_write(&config_file, config_buf, strlen(config_buf), &bw);
f_close(&config_file);
*temp_th = 30.0;
*humi_th = 60.0;
return;
}
// 读取文件内容并解析
f_read(&config_file, config_buf, sizeof(config_buf)-1, &br);
f_close(&config_file);
// 解析温度阈值(sscanf从字符串提取数值)
sscanf(config_buf, "temp_threshold=%f", temp_th);
// 解析湿度阈值(跳过第一行,从第二行提取)
char *humi_str = strstr(config_buf, "humi_threshold=");
if (humi_str) sscanf(humi_str, "humi_threshold=%f", humi_th);
}
// 向日志文件写入温湿度数据(带时间戳)
void Write_Log(float t, float h) {
char log_buf[100] = {0};
// 获取当前时间(RTC)
HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
// 格式化日志内容(时间戳+数据)
sprintf(log_buf, "%04d-%02d-%02d %02d:%02d:%02d, 温度=%.1f℃, 湿度=%.1f%%RH\r\n",
2000 + sDate.Year, sDate.Month, sDate.Date,
sTime.Hours, sTime.Minutes, sTime.Seconds,
t, h);
// 打开日志文件(追加模式)
fr = f_open(&log_file, "0:log.txt", FA_WRITE | FA_OPEN_APPEND | FA_CREATE_ALWAYS);
if (fr != FR_OK) {
HAL_UART_Transmit(&huart1, (uint8_t*)"日志文件打开失败!\r\n", 18, 100);
return;
}
// 写入日志并关闭文件
f_write(&log_file, log_buf, strlen(log_buf), &bw);
f_close(&log_file);
// 调试:串口输出日志内容
HAL_UART_Transmit(&huart1, (uint8_t*)log_buf, strlen(log_buf), 100);
}
/* USER CODE END 0 */
3. 主循环逻辑(数据采集 + 日志写入)
c
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_SPI1_Init();
MX_USART1_UART_Init();
MX_I2C1_Init();
MX_RTC_Init(); // 初始化实时时钟(需提前配置时间)
// 1. 初始化SD卡
SD_Init();
// 2. 读取配置文件中的报警阈值
float temp_threshold, humi_threshold;
Read_Config(&temp_threshold, &humi_threshold);
char info_buf[50];
sprintf(info_buf, "读取配置:温度阈值=%.1f℃,湿度阈值=%.1f%%RH\r\n", temp_threshold, humi_threshold);
HAL_UART_Transmit(&huart1, (uint8_t*)info_buf, strlen(info_buf), 100);
// 3. 循环采集并写入日志(每5秒一次)
while (1) {
AHT10_ReadData(&hi2c1); // 采集温湿度
Write_Log(temp, humi); // 写入SD卡日志
HAL_Delay(5000); // 5秒间隔
}
}
五、测试与验证(30 分钟)
-
硬件测试:
- 将格式化后的 SD 卡插入模块,下载程序到 STM32;
- 串口助手观察输出:“SD 卡挂载成功”“读取配置:...”“2025-11-06 10:00:00, 温度 = 25.5℃, ...”。
-
数据验证:
- 断电后取出 SD 卡,用电脑读取:
- 根目录生成
config.txt(内容为阈值配置); log.txt中按时间戳记录温湿度数据(与串口输出一致)。
- 根目录生成
- 断电后取出 SD 卡,用电脑读取:
-
异常处理测试:
- 拔掉 SD 卡,程序应输出 “SD 卡挂载失败”(不崩溃,继续运行);
- 满卡时,
f_write返回FR_DISK_FULL,可在代码中捕获并提示 “存储空间不足”。
六、第十四天必掌握的 3 个核心点
- FATFS 文件系统基础:理解
f_mount(挂载)、f_open(打开)、f_write(写入)的调用流程,能实现基本文件操作; - SPI 通信配置:掌握 STM32 SPI 主机模式配置(时钟分频、数据格式),理解 CS 片选信号的作用;
- 数据持久化应用:能将传感器数据写入日志文件,从配置文件读取参数,解决 “断电数据丢失” 问题。
总结
本地存储是嵌入式设备的 “记忆系统”,今天实现的 SD 卡日志功能可直接用于环境监测仪、工业记录仪等产品。进阶方向包括:
- 日志轮转(如每天生成一个日志文件,避免单文件过大);
- 加密存储(保护配置文件中的敏感信息,如 WiFi 密码);
- 结合 USB 功能(将 STM32 模拟为 U 盘,方便直接读取 SD 卡数据)。


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



