- 嵌入式
- 物联网
- 机器人
- 自动驾驶
- 智能硬件
【免费下载链接】PX4-Autopilot
PX4 Autopilot Software
MavlinkTunnel 是 PX4-Autopilot 中的一条 uORB 消息,用于在 MAVLink 链路与板载外设(如 ESC、I/O 协处理器)之间承载MAV_TUNNEL数据包,实现串口数据透传(UART passthrough)。本文基于 docs/en/msg_docs/MavlinkTunnel.md 与对应源码 msg/MavlinkTunnel.msg,完整讲解其字段定义、payload 类型注册规则、主题别名机制,并结合 mavlink_receiver.cpp 与 voxl_esc.cpp 等实现,还原一条隧道数据从地面站发出到外设 UART 落地的完整调用链。读完本文,你可以准确理解 MavlinkTunnel 的消息布局、如何自定义 payload 类型,以及如何把隧道数据路由到指定的外设串口。
消息概述:用于 MAVLink 数据隧道的 uORB 载体
MavlinkTunnel 对应 MAVLink 协议中的MAV_TUNNEL(消息 ID 110)报文。它的核心定位是把一段"透明"的字节载荷从 MAVLink 链路搬运到飞行控制器内部,再通过 uORB 分发给订阅该消息的驱动,最终写入某个外设的串口。典型应用包括:
- ESC UART 透传:地面站直接与电调(ESC)的串口固件通信,用于调试、参数配置或固件升级;
- I/O 协处理器 UART 透传:与板载 I/O 芯片(如 VOXL2 IO)的串口交互;
- 通用隧道:任意的自定义协议数据包,通过
mavlink_tunnel主题广播,由用户程序订阅消费。
在 PX4 的 uORB 体系中,MavlinkTunnel 通过TOPICS声明定义了三个主题别名(详见 msg/MavlinkTunnel.msg):
# TOPICS mavlink_tunnel esc_serial_passthru io_serial_passthru这三个主题共用同一套消息类型mavlink_tunnel_s,但按载荷类型(payload_type)分流到不同的订阅者,是理解整个隧道机制的关键。
字段定义与字节布局
消息定义源文件位于 msg/MavlinkTunnel.msg,生成的 C++ 结构体为mavlink_tunnel_s(对应头文件 uORB/topics/mavlink_tunnel.h)。各字段含义如下:
| 字段 | 类型 | 说明 |
|---|---|---|
timestamp | uint64 | 系统启动以来的时间(微秒),由发布端(MAVLink 接收模块)以hrt_absolute_time()填充 |
payload_type | uint16 | 标识 payload 内容的编码,0 表示未知(默认)。小于 32768 视为"已注册"类型,需加入MAV_TUNNEL_PAYLOAD_TYPE枚举;大于 32767 视为本地实验类型,不应提交到广泛分发的代码库 |
target_system | uint8 | 目标系统 ID,0 表示广播(但不推荐) |
target_component | uint8 | 目标组件 ID,0 表示广播(但不推荐) |
payload_length | uint8 | payload 中实际携带数据的字节数 |
payload | uint8[128] | 数据本体,固定 128 字节缓冲区 |
需要注意的关键细节:
payload_length是uint8_t(最大 255),而payload缓冲区固定为 128 字节。接收端会对长度做校验:若payload_length > sizeof(payload),整条消息会被直接丢弃(见下文"接收处理"一节),而不是截断转发——因为截断后的帧会破坏外设的串口协议。- 结构体内各字段顺序与 .msg 源文件一一对应,发布与订阅双方通过 uORB 代码生成器保持布局一致。
MAV_TUNNEL_PAYLOAD_TYPE 常量:payload 类型注册规则
MavlinkTunnel 在消息定义中内嵌了MAV_TUNNEL_PAYLOAD_TYPE枚举常量,用于约定 payload 内容的"编码规范":
uint8 MAV_TUNNEL_PAYLOAD_TYPE_UNKNOWN = 0 # Encoding of payload unknown uint8 MAV_TUNNEL_PAYLOAD_TYPE_STORM32_RESERVED0 = 200 # Registered for STorM32 gimbal controller uint8 MAV_TUNNEL_PAYLOAD_TYPE_STORM32_RESERVED1 = 201 # Registered for STorM32 gimbal controller uint8 MAV_TUNNEL_PAYLOAD_TYPE_STORM32_RESERVED2 = 202 # Registered for STorM32 gimbal controller uint8 MAV_TUNNEL_PAYLOAD_TYPE_STORM32_RESERVED3 = 203 # Registered for STorM32 gimbal controller uint8 MAV_TUNNEL_PAYLOAD_TYPE_STORM32_RESERVED4 = 204 # Registered for STorM32 gimbal controller uint8 MAV_TUNNEL_PAYLOAD_TYPE_STORM32_RESERVED5 = 205 # Registered for STorM32 gimbal controller uint8 MAV_TUNNEL_PAYLOAD_TYPE_STORM32_RESERVED6 = 206 # Registered for STorM32 gimbal controller uint8 MAV_TUNNEL_PAYLOAD_TYPE_STORM32_RESERVED7 = 207 # Registered for STorM32 gimbal controller uint8 MAV_TUNNEL_PAYLOAD_TYPE_STORM32_RESERVED8 = 208 # Registered for STorM32 gimbal controller uint8 MAV_TUNNEL_PAYLOAD_TYPE_STORM32_RESERVED9 = 209 # Registered for STorM32 gimbal controller常量值一览:
| 常量名 | 值 | 用途 |
|---|---|---|
MAV_TUNNEL_PAYLOAD_TYPE_UNKNOWN | 0 | payload 编码未知(默认值) |
MAV_TUNNEL_PAYLOAD_TYPE_STORM32_RESERVED0 | 200 | 为 STorM32 云台控制器保留 |
MAV_TUNNEL_PAYLOAD_TYPE_STORM32_RESERVED1 | 201 | 为 STorM32 云台控制器保留 |
MAV_TUNNEL_PAYLOAD_TYPE_STORM32_RESERVED2 | 202 | 为 STorM32 云台控制器保留 |
MAV_TUNNEL_PAYLOAD_TYPE_STORM32_RESERVED3 | 203 | 为 STorM32 云台控制器保留 |
MAV_TUNNEL_PAYLOAD_TYPE_STORM32_RESERVED4 | 204 | 为 STorM32 云台控制器保留 |
MAV_TUNNEL_PAYLOAD_TYPE_STORM32_RESERVED5 | 205 | 为 STorM32 云台控制器保留 |
MAV_TUNNEL_PAYLOAD_TYPE_STORM32_RESERVED6 | 206 | 为 STorM32 云台控制器保留 |
MAV_TUNNEL_PAYLOAD_TYPE_STORM32_RESERVED7 | 207 | 为 STorM32 云台控制器保留 |
MAV_TUNNEL_PAYLOAD_TYPE_STORM32_RESERVED8 | 208 | 为 STorM32 云台控制器保留 |
MAV_TUNNEL_PAYLOAD_TYPE_STORM32_RESERVED9 | 209 | 为 STorM32 云台控制器保留 |
类型注册规则的实践含义:
- < 32768(注册区):需要把新类型添加到
MAV_TUNNEL_PAYLOAD_TYPE枚举中,并提交到 PX4 主仓库,供所有厂商共用; - ≥ 32768(实验区):留给软件开发者做本地实验,不应提交到广泛分发的代码库,避免枚举空间被未协调的类型号污染。
从源码实际使用看,PX4 内部还使用了几类未写入本消息枚举、而是由 MAVLink 库定义的类型:MAV_TUNNEL_PAYLOAD_TYPE_MODALAI_ESC_UART_PASSTHRU与MAV_TUNNEL_PAYLOAD_TYPE_MODALAI_IO_UART_PASSTHRU(见 mavlink_receiver.cpp),它们分别触发esc_serial_passthru与io_serial_passthru主题的转发,是当前仓库中"别名主题真正被使用"的证据。
数据通路一:MAVLink 接收端如何解析并分流
隧道数据的入口是 MAVLink 接收模块。在 mavlink_receiver.cpp 的handle_message_tunnel()中,处理流程如下:
- 解码:调用
mavlink_msg_tunnel_decode()将原始 MAVLink 报文解码为mavlink_tunnel_t; - 目标过滤:调用
evaluate_target_ok()检查target_system/target_component是否指向本机。注释明确指出:payload 会被转发到真实设备,若目标指向同一链路上的另一架飞行器,绝不能写入本机的总线(mavlink_receiver.cpp); - 长度校验:若
payload_length > sizeof(payload)(即超过 128 字节),直接丢弃消息。源码注释解释:payload 缓冲区固定 128 字节而payload_length是uint8_t,发送方可能宣称携带超过报文实际容量的数据;此时宁可丢弃也不做截断,因为透传消费者会把载荷原样转发到 UART,截断帧会破坏设备协议(mavlink_receiver.cpp); - 填充 uORB 消息:将时间戳、类型、目标、长度与载荷拷贝到
mavlink_tunnel_s tunnel{},并通过static_assert在编译期保证tunnel.payload与 MAVLink 结构体 payload 尺寸一致(mavlink_receiver.cpp); - 按类型分流发布(mavlink_receiver.cpp):
switch (mavlink_tunnel.payload_type) { case MAV_TUNNEL_PAYLOAD_TYPE_MODALAI_ESC_UART_PASSTHRU: _esc_serial_passthru_pub.publish(tunnel); break; case MAV_TUNNEL_PAYLOAD_TYPE_MODALAI_IO_UART_PASSTHRU: _io_serial_passthru_pub.publish(tunnel); break; default: _mavlink_tunnel_pub.publish(tunnel); break; }对应的三个 uORB 发布器声明在 mavlink_receiver.h:
uORB::Publication<mavlink_tunnel_s> _mavlink_tunnel_pub{ORB_ID(mavlink_tunnel)}; uORB::Publication<mavlink_tunnel_s> _esc_serial_passthru_pub{ORB_ID(esc_serial_passthru)}; uORB::Publication<mavlink_tunnel_s> _io_serial_passthru_pub{ORB_ID(io_serial_passthru)};也就是说:同一份解码后的数据,根据payload_type决定进入哪个主题;未知类型一律走通用的mavlink_tunnel主题。消息处理入口在MavlinkReceiver::handle_message()的case MAVLINK_MSG_ID_TUNNEL:处注册(mavlink_receiver.cpp)。
数据通路二:驱动侧订阅并写入外设 UART
隧道数据的出口是订阅对应主题的驱动。仓库中两处真实消费端:
VOXL ESC 电调透传(voxl_esc.cpp):
uint8_t num_writes = 0; // Don't do these faster than 20Hz if (hrt_elapsed_time(&_last_uart_passthru) > 50_ms) { _last_uart_passthru = hrt_absolute_time(); // Don't do more than a few writes each check while (_esc_serial_passthru_sub.updated() && (num_writes < 4)) { mavlink_tunnel_s uart_passthru{}; _esc_serial_passthru_sub.copy(&uart_passthru); if (_uart_port.write(uart_passthru.payload, uart_passthru.payload_length) != uart_passthru.payload_length) { PX4_ERR("Failed to send mavlink tunnel data to esc"); return false; } num_writes++; } }实现要点:
- 订阅
esc_serial_passthru主题,每次周期最多处理 4 条消息(num_writes < 4),避免突发数据阻塞控制回路; - 透传频率被限制在 20 Hz(
hrt_elapsed_time(...) > 50_ms),防止隧道数据淹没串口; - 写入串口时只发送
payload_length指定的字节数,而不是整个 128 字节缓冲区,保证外设只收到有效数据; - 写入长度不符时打印
PX4_ERR("Failed to send mavlink tunnel data to esc")并返回失败。
VOXL2 I/O 协处理器透传(voxl2_io.cpp)采用完全相同的模式:订阅io_serial_passthru主题,将payload与payload_length写入 I/O 串口。这两个驱动文件分别位于 src/drivers/actuators/voxl_esc/ 与 src/drivers/voxl2_io/,可以对照阅读确认调用细节。
日志记录与可观测性
MavlinkTunnel 主题还被纳入了 PX4 的日志系统。在 logged_topics.cpp 中,LoggedTopics::add_mavlink_tunnel()通过add_topic("mavlink_tunnel")将通用隧道主题注册为可记录的 topic,随后在日志主题清单构建时被加入(logged_topics.cpp)。这意味着默认日志配置下,mavlink_tunnel的收发记录会被写入 ulog,便于事后分析隧道流量;而esc_serial_passthru/io_serial_passthru是否记录则取决于具体板卡的日志配置。
自定义 payload 类型的接入步骤
如果你要在自己的项目里复用这套隧道机制,从上述源码可以总结出完整接入路径:
- 注册类型号:若使用小于 32768 的类型号,在 msg/MavlinkTunnel.msg 的枚举区追加常量并提交回 PX4 主仓库;仅做本地实验则选用 32768 以上的号段;
- 发送端:地面站/上位机发送
MAV_TUNNEL报文,填写payload_type、target_system、target_component、payload_length与payload; - 接收端:在 mavlink_receiver.cpp 的
switch中新增case,将消息发布到你自定义的主题(或复用mavlink_tunnel通用主题); - 消费端:驱动中订阅对应主题,参照 voxl_esc.cpp 的模式,按
payload_length将payload写入目标串口,并注意节流(20 Hz)与限流(每周期最多若干条)以保护外设链路。
小结
MavlinkTunnel 是 PX4 中连接 MAVLink 网络与板载串口设备的"透明管道":MAV_TUNNEL_PAYLOAD_TYPE枚举负责约定载荷编码规则,TOPICS别名把同一消息类型按载荷类型分流到mavlink_tunnel、esc_serial_passthru、io_serial_passthru三个主题,接收端(mavlink_receiver.cpp)负责解码、目标过滤、长度校验与分流发布,消费端(voxl_esc.cpp 与 voxl2_io.cpp)负责把载荷写入外设 UART。理解这条通路,即可安全地扩展自己的串口透传场景。
- 嵌入式
- 物联网
- 机器人
- 自动驾驶
- 智能硬件
【免费下载链接】PX4-Autopilot
PX4 Autopilot Software
相关推荐
PX4-Autopilot 中的 DebugValue UORB 消息:定义、发布、MAVLink 透传与飞行日志调试实战
PX4 Autopilot 中的 DebugValue UORB 消息:定义、发布、MAVLink 透传与飞行日志调试实战 DebugValue 是 PX4 飞
嵌入式物联网机器人自动驾驶智能硬件PX4 DebugArray UORB 消息详解:从字段定义到 Mavlink 调试链路实战
PX4 DebugArray UORB 消息详解:从字段定义到 Mavlink 调试链路实战 PX4 的 debug_array 是一类专用于批量浮点调试数据的
嵌入式物联网机器人自动驾驶智能硬件5款免费开源网络拓扑工具:从手画到自动更新拓扑
5款免费开源网络拓扑工具:从手画到自动更新拓扑 网络变更前夜,你要向客户讲清故障路径,翻遍共享盘只找到半年前的旧拓扑图——这种窘境很常见。开源项目 Awesom
嵌入式物联网机器人自动驾驶智能硬件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考