FATFS文件操作详解:如何像操作记事本一样在SD卡里‘续写’数据?
2026/5/8 11:23:48 网站建设 项目流程

FATFS文件操作实战:用记事本思维玩转SD卡数据追加

想象一下,你正在用Windows记事本记录实验数据——每次打开文件,光标自动停在最后一行,输入新内容后保存关闭。这种"续写"操作在嵌入式系统中同样常见,比如温湿度记录仪每小时追加一行数据到CSV文件。本文将用这种生活化类比,带你轻松掌握FATFS文件指针的奥秘。

1. 理解文件指针:嵌入式系统的"记事本光标"

在PC上编辑文本时,闪烁的光标决定了输入位置。FATFS中的文件指针扮演着相同角色,只是你看不见它。每次f_write()写入数据时,都会从指针当前位置开始,就像在记事本的光标处打字。

关键区别在于:

  • PC记事本:图形界面直观显示光标位置
  • FATFS文件系统:需要通过API控制不可见的指针
// 基础文件操作三件套 FIL file; // 文件对象 FRESULT res; // 操作结果 UINT bytes_written; // 成功写入的字节数

当调用f_open()打开文件时,指针默认位于文件开头(相当于记事本刚打开时光标在第一行首)。要实现"续写",就需要将指针移动到文件末尾,主要有两种方式:

2. 追加数据的两把钥匙:FA_OPEN_APPEND vs f_lseek

2.1 一键到位的FA_OPEN_APPEND

就像用记事本的"追加模式"打开文件,这个标志让指针自动定位到末尾:

res = f_open(&file, "0:/data.csv", FA_WRITE | FA_OPEN_APPEND); if (res != FR_OK) { printf("文件打开失败: %d\n", res); return; }

适用场景

  • 简单的周期性数据记录(如每小时记录一次温湿度)
  • 不需要在文件中来回跳转的纯追加操作

注意:某些旧版FATFS可能不支持FA_OPEN_APPEND,需检查版本说明

2.2 精准控制的f_lseek

如同在记事本中按Ctrl+End跳转到文件尾,f_lseek()可以精确控制指针位置:

res = f_open(&file, "0:/data.csv", FA_WRITE); if (res == FR_OK) { // 获取文件总大小并跳转到末尾 res = f_lseek(&file, f_size(&file)); if (res != FR_OK) { printf("指针定位失败: %d\n", res); } }

优势对比

特性FA_OPEN_APPENDf_lseek
代码简洁度★★★★★★★★☆☆
灵活性只能追加可定位到任意位置
兼容性依赖FATFS版本通用性强
性能一次操作完成需要额外调用f_size

3. 温湿度记录仪实战案例

假设我们需要开发一个每30分钟记录DHT11传感器数据的设备,数据存储为CSV格式。完整流程如下:

3.1 初始化文件与标题行

首次运行时创建新文件并写入列标题:

void init_log_file() { FRESULT res; FIL file; // 挂载文件系统 res = f_mount(&fatfs, "0:", 1); if (res != FR_OK) return; // 创建或清空文件 res = f_open(&file, "0:/sensor_log.csv", FA_CREATE_ALWAYS | FA_WRITE); if (res == FR_OK) { const char *header = "时间,温度(℃),湿度(%)\n"; f_write(&file, header, strlen(header), &bytes_written); f_close(&file); } f_mount(0, "0:", 0); // 卸载 }

3.2 周期性追加数据记录

每次传感器采集后追加新数据:

void append_sensor_data(float temp, float humi) { char buffer[64]; FIL file; time_t now = time(NULL); // 格式化数据行 snprintf(buffer, sizeof(buffer), "%ld,%.1f,%.1f\n", now, temp, humi); // 追加模式打开 FRESULT res = f_open(&file, "0:/sensor_log.csv", FA_WRITE | FA_OPEN_APPEND); if (res == FR_OK) { f_write(&file, buffer, strlen(buffer), &bytes_written); f_close(&file); } }

3.3 错误处理增强版

实际项目中需要更健壮的错误处理:

void safe_append(const char* filename, const char* data) { FIL file; FRESULT res; for (int retry = 0; retry < 3; retry++) { res = f_open(&file, filename, FA_WRITE | FA_OPEN_APPEND); if (res == FR_OK) break; delay_ms(100); // 等待后重试 } if (res != FR_OK) { log_error("文件打开失败"); return; } if (f_write(&file, data, strlen(data), &bytes_written) != FR_OK) { log_error("写入失败"); } if (f_close(&file) != FR_OK) { log_error("关闭失败"); } }

4. 高级技巧与性能优化

4.1 减少文件开关次数

频繁打开关闭文件会影响Flash寿命,对于高频记录场景可以考虑:

// 保持文件长时间打开 void logging_task() { FIL file; f_open(&file, "0:/data.csv", FA_WRITE | FA_OPEN_APPEND); while(1) { // ...采集数据... f_write(&file, data, strlen(data), NULL); f_sync(&file); // 确保数据写入物理介质 delay_ms(1800000); // 等待30分钟 } f_close(&file); }

4.2 文件大小限制管理

长期运行可能使文件过大,需要定期归档:

void check_file_rotation() { if (f_size(&file) > 1024 * 1024) { // 超过1MB f_close(&file); char newname[32]; sprintf(newname, "0:/data_%lu.csv", time(NULL)); f_rename("0:/data.csv", newname); f_open(&file, "0:/data.csv", FA_CREATE_ALWAYS | FA_WRITE); } }

4.3 内存优化技巧

对于资源受限的MCU,可以复用缓冲区:

void efficient_logging() { static char buf[128]; // 静态缓冲区 FIL file; // 格式化数据到固定缓冲区 format_sensor_data(buf, sizeof(buf)); f_open(&file, "0:/data.csv", FA_WRITE | FA_OPEN_APPEND); f_write(&file, buf, strlen(buf), NULL); f_close(&file); }

5. 常见问题排查指南

遇到文件追加不正常时,可以按以下步骤检查:

  1. 确认挂载成功

    if (f_mount(&fatfs, "0:", 1) != FR_OK) { printf("存储设备挂载失败\n"); }
  2. 检查写权限

    • 确保调用f_open()时包含FA_WRITE标志
    • 确认存储介质不是只读的
  3. 验证指针位置

    FSIZE_t pos = f_tell(&file); // 获取当前指针位置 printf("当前指针位置: %lu\n", pos);
  4. 确保及时关闭文件

    • 未关闭的文件可能导致数据丢失
    • 使用f_sync()强制写入物理介质
  5. 检查存储空间

    DWORD free_clusters; FATFS* fs; f_getfree("0:", &free_clusters, &fs); printf("剩余空间: %lu KB\n", free_clusters * fs->csize / 2);

在STM32项目中使用时,曾遇到一个典型问题:连续运行几天后追加操作变慢。最终发现是忘记定期调用f_sync(),导致文件系统需要处理大量缓存数据。添加定期同步后,性能恢复稳定。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询