2. 日志文件结构解析:二进制日志与文本日志的区别、数据帧结构、时间戳解析
好,咱们进入第二个实战环节。日志文件,说白了就是无人机飞控系统留给我们的「黑匣子」。你拿到手的日志,可能是 .bin 文件,也可能是 .log 或 .txt 文件。这两种格式,差别可不小。
2.1 二进制日志 vs 文本日志:一个省空间,一个省脑子
我个人习惯,外场测试时优先保存二进制日志。为什么?因为飞控的存储空间太金贵了。二进制日志,说白了就是一堆 0 和 1 的紧凑排列,一个浮点数只占 4 个字节。文本日志呢?同样一个数字 "3.14159",存成字符串要 7 个字节,加上逗号、换行符,轻松翻倍。
我遇到过一位同事,为了调试方便,把日志全设成了文本格式。结果飞了 20 分钟,存储卡就满了,关键数据没录下来。嗯,这是个教训。
| 对比项 | 二进制日志 | 文本日志 |
|---|---|---|
| 存储效率 | 极高,压缩比可达 5:1 | 低,大量冗余字符 |
| 可读性 | 需要专用解析工具 | 记事本直接打开 |
| 解析速度 | 快,直接内存映射 | 慢,需要字符串转换 |
| 典型场景 | 长时间飞行、高速采样 | 调试阶段、小规模测试 |
2.2 数据帧结构:日志里的「乐高积木」
不管二进制还是文本,日志数据都不是乱堆的。它们遵循固定的帧结构。你想想看,飞控每秒要记录几百个传感器数据,如果没有统一格式,解析时根本不知道哪段是加速度计,哪段是 GPS。
一个典型的数据帧,长这样:
| 帧头 (2字节) | 消息ID (1字节) | 时间戳 (4字节) | 数据负载 (N字节) | 校验和 (1字节) |
| 0xFE 0xEF | 0x01 | 0x12345678 | acc_x, acc_y... | 0xAB |
帧头是固定的,比如 0xFE 0xEF,用来做同步。解析器一读到这个,就知道「新的一帧来了」。消息 ID 告诉你是哪种数据——0x01 是 IMU,0x02 是 GPS,0x03 是电机状态。时间戳是重中之重,我们马上细说。
我曾经在解析一个老版本日志时,发现帧头被改成了 0xEF 0xFE。结果整个解析器全乱套了。从那以后,我养成了一个习惯:先看帧头,再谈解析。
2.3 时间戳解析:把「时间」对齐了再说
时间戳,是日志分析里最容易出问题的地方。为什么?因为飞控里有好几个「时钟」在同时跑。
- 系统时钟:飞控上电后开始计数的微秒数,精度高,但会溢出。
- GPS 时钟:UTC 时间,精确到纳秒,但信号不好时会跳变。
- 传感器时钟:每个传感器有自己的时间基准,比如 IMU 是 1kHz 的固定间隔。
我遇到过最头疼的一次:GPS 时间戳突然跳了 1 秒,导致整个航迹回放时位置和姿态对不上。排查了半天,才发现是 GPS 卫星切换时的时间校正。
解析时间戳时,我一般这么做:
// 伪代码示例:统一时间基准
uint64_t system_time_us = get_system_time(); // 飞控上电后的微秒数
uint64_t gps_time_us = gps_tow_to_us(gps_week, gps_tow); // GPS 周内秒转微秒
// 关键:找到两个时钟的偏移量
int64_t time_offset = system_time_us - gps_time_us;
// 之后所有传感器数据,都统一到系统时间基准
sensor.timestamp_sys = sensor.timestamp_raw + time_offset;
2.4 实战:手撕一段二进制日志
光说不练假把式。咱们拿一段真实的二进制日志来拆解。假设你拿到的是 PX4 的 .ulog 文件,用 hexdump 看前 64 个字节:
00000000 55 4c 6f 67 01 00 00 00 00 00 00 00 00 00 00 00 |ULog............|
00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
看到没?前 4 个字节是魔数 "ULog",标识这是 PX4 的二进制日志格式。后面跟着版本号、时间戳偏移等信息。解析时,程序就是靠这个魔数来判断文件类型的。
再往下翻,你会看到各种消息块:
// 消息类型定义
#define MSG_TYPE_ADD_LOGGED 0x02 // 添加一个日志变量
#define MSG_TYPE_DATA 0x03 // 实际数据
#define MSG_TYPE_INFO 0x04 // 元信息(参数、版本等)
每个消息块,都遵循我们刚才说的帧结构。解析器按顺序读,读到 MSG_TYPE_DATA 时,就知道后面跟着的是传感器数值。
2.5 总结:日志结构是故障定位的「地图」
二进制和文本日志,各有各的用武之地。数据帧结构,是解析的骨架。时间戳,是串联所有数据的灵魂。这三者搞明白了,你手里的日志就不再是一堆乱码,而是一张清晰的飞行状态地图。
下一章,咱们聊聊怎么从这张地图里,找到故障的蛛丝马迹。