4. 软件架构设计:嵌入式实时操作系统、文件系统选择、日志记录策略

好,咱们进入第四章。这一章,说白了就是黑匣子的“大脑”和“记忆”怎么搭。我见过不少团队,硬件堆得挺猛,结果软件架构一塌糊涂,数据要么丢了,要么时间戳对不上。嗯,这章咱们就把这些坑一个个填上。

4.1 嵌入式实时操作系统(RTOS)的选择

为什么非得用RTOS?你想想看,无人机在天上飞,传感器数据、控制指令、日志写入,哪个都不能等。裸机跑轮询?我年轻时干过这事,结果有一次日志写入卡住了,飞控循环被拖慢,飞机直接晃了一下。从那以后,我再也不敢在关键系统上用裸机了。

RTOS的核心价值就两条:确定性优先级管理。黑匣子任务里,日志写入可以稍微等一等,但IMU数据采集必须准时。RTOS能保证高优先级任务先执行。

我个人推荐的选择:

  • FreeRTOS:轻量、开源、生态好。适合大多数Cortex-M系列MCU。我在Pixhawk的衍生项目里用过,稳定。
  • RT-Thread:国产之光,组件丰富,有文件系统、网络协议栈一键集成。适合功能复杂的黑匣子。
  • Zephyr:如果你要做Linux和RTOS的混合架构,这个值得看看。但学习曲线陡一点。

我的经验:别贪多。RTOS的任务栈大小一定要算清楚。我曾经因为栈开小了,任务跑着跑着就崩了,查了三天才发现是栈溢出。建议每个任务至少给512字节的余量。

4.2 文件系统的选择

黑匣子数据要存到SD卡或Flash里,总不能裸写扇区吧?那太原始了。文件系统就是给数据一个“家”。但选哪个,有讲究。

我遇到过最头疼的事:飞机坠了,SD卡拔出来,电脑读不出来。为什么?文件系统损坏了。所以,可靠性是第一位的。

文件系统 适用场景 优点 缺点
FAT32 通用SD卡 兼容性好,电脑直接读 单文件最大4GB,掉电易损坏
LittleFS SPI Flash 掉电安全,磨损均衡 速度稍慢,不适合大容量
SPIFFS 小容量Flash 实现简单 性能一般,已逐渐被LittleFS取代
ext4 高性能Linux平台 稳定、日志式 资源开销大

注意:FAT32虽然方便,但掉电保护很差。如果你用FAT32,一定要配合“先写临时文件,再重命名”的策略。我曾经在项目里直接写FAT32,结果一次异常断电,整个目录表都乱了。后来改用LittleFS,再没出过这问题。

4.3 日志记录策略

文件系统选好了,接下来就是怎么记。日志策略设计不好,要么丢数据,要么把Flash写废了。

4.3.1 环形缓冲区 + 批量写入

这是我最常用的模式。为什么?因为实时写入太慢了。每次写一个字节都调用一次文件系统,性能直接崩掉。

做法很简单:

  • 在RAM里开一个环形缓冲区,比如64KB。
  • 传感器数据先往缓冲区里塞。
  • 缓冲区满了,或者定时到了(比如100ms),一次性写入文件系统。
// 伪代码示例
#define BUFFER_SIZE 65536
uint8_t log_buffer[BUFFER_SIZE];
uint32_t write_index = 0;

void log_data(uint8_t *data, uint32_t len) {
    // 检查剩余空间
    if ((write_index + len) > BUFFER_SIZE) {
        flush_buffer_to_sd(); // 满了就刷盘
    }
    memcpy(&log_buffer[write_index], data, len);
    write_index += len;
}

void flush_buffer_to_sd() {
    // 批量写入,减少文件系统调用次数
    sd_write(log_buffer, write_index);
    write_index = 0;
}

避坑指南:缓冲区大小要跟你的写入频率匹配。我见过有人设了1KB的缓冲区,结果每几毫秒就刷一次盘,SD卡寿命直线下降。建议至少16KB起步。

4.3.2 分级日志

不是所有数据都值得存。黑匣子空间有限,得学会“挑食”。

  • 关键数据(IMU、GPS、控制指令):必须存,掉电也不能丢。写入频率高,用环形缓冲区保障。
  • 状态数据(电池电压、温度):可以降频存,比如1秒一次。
  • 调试数据(内部变量、堆栈使用):只在调试模式下开启,正式飞行关掉。

4.3.3 时间戳与同步

日志没有时间戳,等于白记。你事后分析时,根本不知道哪个事件先发生。

我建议:

  1. 每次开机时,从GPS或飞控获取UTC时间。
  2. 每条日志记录一个相对时间戳(比如系统启动后的毫秒数)。
  3. 在文件头里记录绝对时间基准。
// 日志记录格式示例
typedef struct {
    uint32_t timestamp_ms;  // 相对时间
    uint8_t  log_level;     // 0:关键, 1:状态, 2:调试
    uint8_t  data_type;     // 0:IMU, 1:GPS, ...
    uint8_t  data[64];      // 实际数据
} log_entry_t;

注意:时间戳的精度很重要。我遇到过用RTC做时间戳的,结果RTC晶振不准,一天能差好几秒。后来改用MCU的定时器计数器,精度到了微秒级。记住,黑匣子分析时,毫秒级的偏差都可能让你误判事故原因。

4.4 总结一下我的建议

嗯,这一章内容不少。我最后给你捋一捋:

  • RTOS选FreeRTOS或RT-Thread,别裸奔。
  • 文件系统,SD卡用FAT32+掉电保护,Flash用LittleFS。
  • 日志策略,环形缓冲区+批量写入是王道。
  • 时间戳一定要准,用硬件定时器,别用RTC。

下一章咱们聊聊数据记录的具体格式和压缩策略。到时候我会分享一个我踩过的坑——日志文件太大,导致SD卡写满后飞机直接失控。嗯,那故事挺刺激的。