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 时间戳与同步
日志没有时间戳,等于白记。你事后分析时,根本不知道哪个事件先发生。
我建议:
- 每次开机时,从GPS或飞控获取UTC时间。
- 每条日志记录一个相对时间戳(比如系统启动后的毫秒数)。
- 在文件头里记录绝对时间基准。
// 日志记录格式示例
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卡写满后飞机直接失控。嗯,那故事挺刺激的。