1. 日志基础入门:PX4日志系统概述、ULog文件格式解析、日志存储与获取方法

1.1 为什么日志是飞控调试的“黑匣子”

做飞控调试这几年,我越来越觉得日志就是飞行器的“黑匣子”。飞机在天上飞,你在地面站看数据,中间隔着无线链路,很多细节根本传不下来。飞机突然抖一下、高度掉一截、或者干脆炸机了——你问飞手发生了什么,他大概率说“不知道,突然就翻了”。

这时候,日志就是唯一的真相。它记录了每个传感器读数、每个控制指令、每个状态切换。说白了,没有日志,你就是在盲调。

我个人习惯是:每次试飞,不管飞得多顺利,一定把日志存下来。为什么?因为“顺利”不代表没问题。有时候问题藏得很深,只有把几次飞行的日志放在一起对比,才能发现规律。

核心观点:日志不是出问题才看的东西,它是你理解飞控行为的唯一可靠途径。养成每次飞行都保存日志的习惯,比学会任何高级分析技巧都重要。

1.2 PX4日志系统概述

PX4的日志系统,说白了就是一套“谁、在什么时候、记录了什么东西”的机制。它不像某些商业飞控那样只记录几个关键参数,而是把几乎所有内部数据都暴露出来——只要你愿意,连每个CPU时钟周期的占用率都能看到。

整个日志系统分为三层:

  • 数据源层:传感器(IMU、磁力计、气压计、GPS)、状态估计器(EKF2/3)、控制器(姿态、位置、速度)、执行器输出、RC输入等。
  • 记录层:SD logger模块负责把数据写入SD卡。它有一个订阅机制,任何模块想记录数据,只需要向logger注册一个主题即可。
  • 存储层:默认写入SD卡上的/log目录,按日期和飞行次数自动命名。

我记得有一次,一个学员问我:“为什么我的日志里没有某个传感器的数据?”我让他检查一下那个传感器模块是否在启动时注册了日志主题。结果发现,他用的固件版本里那个模块的日志功能被注释掉了。嗯,这种坑,不查源码根本想不到。

小提示:PX4的日志系统默认是“全量记录”模式,即所有注册的主题都会被记录。如果你觉得日志文件太大,可以在启动脚本中通过SDLOG_MODE参数调整记录级别,但我不建议新手这么做——宁可多记,不要漏记。

1.3 ULog文件格式解析

ULog是PX4自创的二进制日志格式。你想想看,飞控上的MCU资源有限,如果用文本格式记录数据,写入速度慢、占用空间大,飞着飞着SD卡就写满了。ULog就是为了解决这个问题而生的。

一个ULog文件的结构,我习惯把它想象成一本书:

章节 内容 说明
文件头 魔数、时间戳、版本号 固定64字节,用于校验文件完整性
定义段 消息类型、格式定义、参数列表 相当于书的目录,告诉解析器后面数据长什么样
数据段 实际传感器/状态数据 按时间顺序排列,每条数据带时间戳
附加段 日志信息、硬件信息 可选,记录固件版本、飞控型号等

这里有个关键点:定义段只出现一次,在文件开头。解析器必须先读完定义段,才能正确解析后面的数据段。我曾经遇到过有人手动修改了ULog文件,把定义段删了,结果整个文件就废了——解析器根本不知道那些二进制数字代表什么。

来看一个实际的ULog文件头解析示例:

// ULog文件头(64字节)
// 偏移 0: 魔数 'ULog' (3字节)
// 偏移 3: 版本号 (1字节) 0x01
// 偏移 4: 时间戳 (8字节) uint64_t,微秒
// 偏移 12: 文件大小 (8字节) uint64_t
// 偏移 20: 标志位 (8字节) uint64_t
// 偏移 28-63: 保留

// 用Python读取文件头
import struct

with open('flight.ulg', 'rb') as f:
    header = f.read(64)
    magic = header[0:3]
    version = header[3]
    timestamp_us = struct.unpack('<Q', header[4:12])[0]
    file_size = struct.unpack('<Q', header[12:20])[0]
    
    print(f'魔数: {magic}')
    print(f'版本: {version}')
    print(f'时间戳: {timestamp_us} us')
    print(f'文件大小: {file_size} bytes')

注意:ULog文件是二进制格式,不要用文本编辑器打开。如果你非要看原始内容,用hexdump或者xxd命令。我曾经见过有人用记事本打开.ulg文件,然后说“全是乱码”——那当然,二进制文件不是给你人眼看的。

1.4 日志存储与获取方法

日志存在哪里?怎么拿出来?这个问题看似简单,但我在项目中被问过无数次。

1.4.1 存储位置与命名规则

默认情况下,PX4把日志写在SD卡的/log目录下。每次上电算一次“会话”,每次解锁飞行算一次“飞行”。目录结构是这样的:

/log/
├── 2024-01-15/
│   ├── flight_001.ulg   # 第一次飞行
│   ├── flight_002.ulg   # 第二次飞行
│   └── flight_003.ulg
├── 2024-01-16/
│   ├── flight_001.ulg
│   └── flight_002.ulg
└── 2024-01-17/
    └── flight_001.ulg

文件名中的数字是自动递增的。如果你飞了10次,第10次就是flight_010.ulg。这个命名规则很直观,但有个坑:如果你换了SD卡,计数器会重置。所以我的习惯是每次飞完,立刻把日志拷贝到电脑上,按“日期_飞机编号_飞行任务”重命名。

1.4.2 获取日志的三种方式

  1. 直接拔SD卡:最原始也最可靠。飞机落地断电,拔卡插电脑。适合所有场景,尤其是炸机后——只要SD卡没坏。
  2. 通过MAVLink下载:用QGroundControl地面站,在“分析工具”里选择“下载日志”。适合不想拔卡的情况,但速度慢,而且如果无线链路不稳定,容易中断。
  3. 通过USB连接:飞控通过USB连电脑,挂载为U盘。适合Pixhawk等有USB接口的硬件。速度比MAVLink快,但需要驱动支持。

我的建议:日常调试用QGC下载就行,方便。但如果是重要试飞或者炸机调查,一定拔SD卡。为什么?因为炸机后飞控可能还在运行,但无线链路已经断了,MAVLink下载根本连不上。只有SD卡里的数据是完整的。

1.4.3 日志文件大小与存储策略

一个典型的10分钟飞行,日志文件大约50-100MB。SD卡建议用32GB以上的Class 10卡。我见过有人用2GB的老卡,飞了3次就满了,然后日志被覆盖——嗯,这种教训一次就够了。

PX4提供了几个参数来控制日志行为:

参数名 默认值 作用
SDLOG_MODE 1 0=禁用日志,1=始终记录,2=仅记录异常
SDLOG_DIR /log 日志存储目录
SDLOG_ALGORITHM 0 0=全量记录,1=按主题优先级丢弃
SDLOG_TOPICS 空 指定要记录的主题列表(逗号分隔)

我个人习惯把SDLOG_MODE设为1,永远记录。SD卡便宜,数据无价。你想想看,一次炸机损失几千块,一张SD卡才几十块,这个账很好算。

1.5 本章知识体系总览

下面这张图是我自己总结的日志系统知识框架,帮你把本章内容串起来:

PX4日志系统知识体系 PX4日志系统 数据源层 记录层 存储层 IMU GPS 磁力计 气压计 SD logger模块 主题订阅机制 SD卡 /log目录 ULog文件格式 ULog文件内部结构 文件头 定义段 数据段 附加段 拔SD卡 MAVLink下载 USB连接 日志是飞控的 “黑匣子”

这张图把本章的核心内容都串起来了。从上到下看:PX4日志系统分为三层,数据源层产生数据,记录层负责写入,存储层保存为ULog文件。ULog文件内部有四个段,其中定义段是关键——没有它,数据段就是一堆乱码。最后,获取日志有三种方式,各有优劣。

嗯,到这里,第一章的基础内容就差不多了。记住一句话:日志是你和飞控之间最诚实的对话。它不会骗你,只会告诉你它看到了什么、做了什么。学会读懂这些对话,你就能真正理解飞控的行为。