【嵌入式开发学习】第14天:SD 卡文件系统与数据日志(本地存储实战)

核心目标:掌握 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. 工程配置步骤
  1. 新建工程,启用必要外设:

    • SPI1:Mode=“Full-Duplex Master”(全双工主机),Prescaler=“2”(72MHz/2=36MHz,低于 SD 卡最大支持速率);
    • GPIO:PA4(CS)设为 “GPIO_Output”(推挽输出);
    • I2C1(AHT10)、USART1(调试串口);
    • RTC(实时时钟,用于日志时间戳,需配置 LSE 时钟)。
  2. 启用 FATFS 文件系统:

    • 左侧 “Middleware→FATFS”,选择 “SD Card”(存储介质),“SPI”(通信接口);
    • 配置 FATFS 参数:Code Page=“936”(支持中文路径 / 文件名),Volume Label=“STM32_LOG”(卷标名)。
  3. 生成代码:CubeIDE 自动生成 SPI 初始化、FATFS 底层接口(如disk_initializedisk_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 分钟)

  1. 硬件测试

    • 将格式化后的 SD 卡插入模块,下载程序到 STM32;
    • 串口助手观察输出:“SD 卡挂载成功”“读取配置:...”“2025-11-06 10:00:00, 温度 = 25.5℃, ...”。
  2. 数据验证

    • 断电后取出 SD 卡,用电脑读取:
      • 根目录生成config.txt(内容为阈值配置);
      • log.txt中按时间戳记录温湿度数据(与串口输出一致)。
  3. 异常处理测试

    • 拔掉 SD 卡,程序应输出 “SD 卡挂载失败”(不崩溃,继续运行);
    • 满卡时,f_write返回FR_DISK_FULL,可在代码中捕获并提示 “存储空间不足”。

六、第十四天必掌握的 3 个核心点

  1. FATFS 文件系统基础:理解f_mount(挂载)、f_open(打开)、f_write(写入)的调用流程,能实现基本文件操作;
  2. SPI 通信配置:掌握 STM32 SPI 主机模式配置(时钟分频、数据格式),理解 CS 片选信号的作用;
  3. 数据持久化应用:能将传感器数据写入日志文件,从配置文件读取参数,解决 “断电数据丢失” 问题。

总结

本地存储是嵌入式设备的 “记忆系统”,今天实现的 SD 卡日志功能可直接用于环境监测仪、工业记录仪等产品。进阶方向包括:

  • 日志轮转(如每天生成一个日志文件,避免单文件过大);
  • 加密存储(保护配置文件中的敏感信息,如 WiFi 密码);
  • 结合 USB 功能(将 STM32 模拟为 U 盘,方便直接读取 SD 卡数据)。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值