4、消息定义文件(.msg):.msg文件语法、基本类型、数组、嵌套消息
好,咱们今天聊聊 .msg 文件。这是 uORB 通信的基石,说白了就是定义数据结构的模板。我在 PX4 里折腾了这么多年,发现很多新手上来就写代码,结果消息格式没定义好,后面调试起来那叫一个痛苦。所以这节课,咱们把 .msg 文件彻底讲透。
4.1 .msg 文件长什么样?
先看个最简单的例子。你随便找个 PX4 源码目录,比如 msg/ 文件夹下,打开 vehicle_attitude.msg,大概长这样:
# 这是注释,用 # 号开头
uint64 timestamp # 时间戳,单位微秒
float32[4] q # 四元数 [w, x, y, z]
float32[3] gyro # 陀螺仪数据
float32 roll # 横滚角
float32 pitch # 俯仰角
float32 yaw # 偏航角
嗯,结构很简单。每行一个字段,格式是:类型 变量名。变量名后面可以加注释,用 # 隔开。我个人习惯在注释里写清楚单位,比如 # 单位:弧度,这样别人看代码时不用猜。
4.2 基本类型有哪些?
uORB 支持的基本类型,说白了就是 C/C++ 里那些常见类型。我整理了一张表,你直接看:
| 类型 | C 对应类型 | 说明 |
|---|---|---|
| bool | uint8_t | 布尔值,0 或 1 |
| int8 / uint8 | int8_t / uint8_t | 8 位有/无符号整数 |
| int16 / uint16 | int16_t / uint16_t | 16 位有/无符号整数 |
| int32 / uint32 | int32_t / uint32_t | 32 位有/无符号整数 |
| int64 / uint64 | int64_t / uint64_t | 64 位有/无符号整数 |
| float32 | float | 单精度浮点数 |
| float64 | double | 双精度浮点数 |
| char | char | 单个字符 |
这里有个坑,我提醒一下:没有 string 类型。你想传字符串?只能用 char[] 数组。我曾经在项目里想传个设备名称,直接写了 string device_name,结果编译报错。后来改成 char[32] device_name 才搞定。
4.3 数组怎么用?
数组有两种写法:固定长度和可变长度。
固定长度数组:
float32[4] quaternion # 四元数,固定4个元素
int32[3] position # 位置坐标 [x, y, z]
uint8[6] mac_addr # MAC 地址
可变长度数组(PX4 里叫 varlen array):
uint8[] data # 可变长度数据,最大长度由系统限制
float32[] samples # 采样数据
我个人建议:能用固定长度就别用可变长度。为什么?可变长度数组在内存分配上更复杂,容易出性能问题。我记得有一次调试一个传感器驱动,用了可变长度数组,结果在高速发布时内存碎片化严重,飞控直接卡死。换成固定长度后,问题就解决了。
ORB_MESSAGE_MAX_BYTES 限制(默认 8KB)。如果你传的数据超过这个值,消息会被截断,不会报错。这坑我踩过,调试了半天才发现数据不全。
4.4 嵌套消息
有时候一个消息里需要包含另一个消息。比如你想定义一个 gps_data.msg,里面包含位置、速度、时间等信息。你可以先定义子消息,再在父消息里引用。
举个例子,先定义 gps_position.msg:
float64 latitude # 纬度
float64 longitude # 经度
float32 altitude # 海拔
再定义 gps_data.msg:
uint64 timestamp
gps_position position # 嵌套引用
float32 speed # 速度
uint8 satellites # 卫星数量
注意:嵌套消息的引用方式就是直接写子消息的类型名。编译时,uORB 会自动把子消息展开成扁平结构。说白了,最终生成的 C 结构体里,gps_data 会包含 gps_position 的所有字段。
4.5 实战:定义一个自定义消息
咱们动手写一个。假设你要定义一个 my_sensor.msg,包含加速度计、陀螺仪和温度数据:
# my_sensor.msg
# 自定义传感器数据
uint64 timestamp # 时间戳,微秒
# 加速度计数据
float32[3] accel # [x, y, z],单位 m/s^2
# 陀螺仪数据
float32[3] gyro # [x, y, z],单位 rad/s
# 温度
float32 temperature # 单位 摄氏度
# 状态标志
uint8 flags # bit0: 数据有效, bit1: 校准完成
写完之后,把这个文件放到 msg/ 目录下。然后在 CMakeLists.txt 里加上 my_sensor.msg,重新编译。PX4 会自动生成对应的 C 结构体和发布/订阅接口。
uint8 flags 字段,用来存各种状态位。比如 bit0 表示数据是否有效,bit1 表示是否校准过。这样不用每个状态都单独定义一个 bool,省空间又方便。
4.6 消息定义的注意事项
- 字段顺序很重要:uORB 消息的内存布局是扁平的,字段顺序决定了结构体里的偏移量。如果你改了字段顺序,所有订阅者都得重新编译。所以,一旦发布,就别改顺序。
- 对齐问题:uORB 不会自动做内存对齐。比如你写
uint8 a; float32 b;,在 C 结构体里b可能没对齐到 4 字节边界,导致性能下降。我建议把大类型(float32、int32)放在前面,小类型(uint8、bool)放在后面。 - 命名规范:变量名用蛇形命名法(snake_case),比如
accel_x、gyro_y。不要用驼峰,因为 PX4 的代码生成器默认转成蛇形,不一致会出问题。
4.7 知识体系图
下面这张图帮你理清 .msg 文件的核心逻辑:
嗯,这张图把咱们讲的内容串起来了。基本类型是砖块,数组是组合方式,嵌套消息是模块化设计。你写 .msg 文件时,就按这个思路来:先选基本类型,再决定用不用数组,最后考虑要不要嵌套。
好了,关于 .msg 文件的语法就讲到这里。记住:消息定义是通信的第一步,定义好了后面省一半的功夫。下次你写自定义消息时,多花 5 分钟把类型和注释写清楚,后面调试时你会感谢自己的。
公众号:蓝海资料掘金营,微信deep3321