STM32 上部署 MAVLink 协议教程
1. 文档目的
这份文档面向当前工程 /home/af/sg-px4/stm32--px4/stm32_px4,用于说明:
- 什么是 MAVLink,为什么在 STM32 上常用它和 PX4 通信。
- 当前工程里是如何在
STM32F103上部署MAVLink v1的。 - 如果后续你要扩展更多 MAVLink 消息,应该从哪里改、怎么改。
- 在 STM32 上使用 MAVLink 时常见的配置项、宏定义、消息结构、CRC 规则和调试方法。
这份文档既讲协议原理,也讲当前项目中的实际落地方式。
2. 什么是 MAVLink
MAVLink 是无人机领域非常常见的一种轻量级通信协议,广泛用于:
- 飞控和地面站之间通信。
- 飞控和外设之间通信。
- 飞控和伴随计算机之间通信。
- 模块之间传递标准化消息,例如姿态、位置、通道、心跳、命令等。
它的特点是:
- 二进制协议,数据紧凑,适合串口和无线链路。
- 标准化消息很多,例如
HEARTBEAT、RC_CHANNELS、ATTITUDE、COMMAND_LONG。 - 支持不同版本,常见是
MAVLink v1和MAVLink v2。 - 每条消息都有固定
msgid、固定或规定好的负载结构、CRC extra。
对于 STM32 这种资源较小的 MCU 来说,MAVLink 很适合做"标准化串口协议"。
3. 为什么当前工程选用 MAVLink v1
当前工程里,STM32 与 PX4 的主要需求是:
- PX4 通过串口向 STM32 发送控制量。
- STM32 解析控制量后驱动本地执行机构。
- STM32 向 PX4 回发链路在线状态。
在这个场景下,MAVLink v1 已经足够,原因如下:
-
需要的消息不多。
当前核心只需要:
HEARTBEAT
RC_CHANNELS -
v1帧格式更简单。对裸机 MCU 手写解析器更友好。
-
STM32F103资源有限。
v1的状态机更容易维护和调试。 -
当前目标是先把链路稳定打通。
等以后需要更多标准消息或者签名,再考虑上
v2。
4. 当前工程中的 MAVLink 通信角色
在当前工程里:
PX4是串口发送端,发送MAVLink RC_CHANNELS消息给 STM32。STM32是串口接收端,解析RC_CHANNELS并取出通道值。STM32同时也会周期性发送MAVLink HEARTBEAT给 PX4,表示自己在线。
也就是说,这里 STM32 并没有实现一个完整的 MAVLink 通用协议栈,而是实现了:
MAVLink v1帧接收器RC_CHANNELS解码器HEARTBEAT解码器HEARTBEAT打包发送器
这是一个"最小可用"的 MAVLink 子集实现。
5. 当前工程中的引脚和模块分工
5.1 串口通信引脚
当前与 PX4 通信使用:
PA2:USART2_TXPA3:USART2_RX
对应文件:
Core/Src/usart.cApp/px4_link.cApp/px4_link.h
5.2 其他功能引脚
当前工程里还有这些控制链路:
PA7:PPM 解析PB10/PB11:SBUS 解析PA11/PA12:CAN 控制PA9/PB15:涡轮/推杆控制
而 MAVLink 只负责 PA2/PA3 这一条链路。
6. 当前工程中的 MAVLink 相关文件
6.1 核心文件
当前 MAVLink 相关实现主要在:
6.2 各文件职责
App/px4_link.c
负责:
- 定义 MAVLink 相关宏。
- 接收串口字节并按 MAVLink v1 状态机组帧。
- 校验 CRC。
- 解码
HEARTBEAT。 - 解码
RC_CHANNELS。 - 发送
HEARTBEAT。
App/px4_link.h
负责:
- 暴露
px4_link_frame_t结构体。 - 暴露初始化、接收回调、取帧、发心跳接口。
Core/Src/usart.c
负责:
- 初始化
USART2。 - 在
HAL_UART_RxCpltCallback()中把收到的字节转发到PX4_Link_RxCpltCallback()。
Core/Src/main.c
负责:
- 初始化
PX4_Link_Init()。 - 在主循环中读取
PX4_Link_GetFrame()。 - 把解码出来的通道值放入控制优先级系统。
- 周期性发送
PX4_Link_SendHeartbeat()。
7. MAVLink v1 帧格式
当前工程使用的是 MAVLink v1,帧格式如下:
text
STX | LEN | SEQ | SYSID | COMPID | MSGID | PAYLOAD | CRC_L | CRC_H
各字段含义:
-
STX帧头。
MAVLink v1固定是0xFE。 -
LEN当前消息负载长度,不包含头部和 CRC。
-
SEQ包序号。
每发一帧自增一次,主要用于统计丢包。
-
SYSID系统 ID。
表示消息来自哪个系统。
-
COMPID组件 ID。
表示系统中的哪个模块发出的消息。
-
MSGID消息 ID。
决定负载该如何解释。
-
PAYLOAD实际消息负载。
不同消息的负载结构不同。
-
CRC_L、CRC_HX25 CRC 校验结果低字节和高字节。
还要额外累加一个
CRC extra。
8. 当前工程使用到的 MAVLink 消息
8.1 HEARTBEAT
作用
HEARTBEAT 用来表示节点在线。
当前工程中:
- PX4 发
HEARTBEAT给 STM32,STM32 记录对端还活着。 - STM32 也发
HEARTBEAT给 PX4,表示自己在线。
当前配置
在 App/px4_link.c 中定义:
c
#define MAVLINK_MSG_ID_HEARTBEAT 0U
#define MAVLINK_MSG_ID_HEARTBEAT_LEN 9U
#define MAVLINK_MSG_ID_HEARTBEAT_CRC 50U
含义:
msgid = 0- 负载长度
9 CRC extra = 50
HEARTBEAT 负载字段
负载长度为 9 字节,字段含义如下:
-
custom_mode4 字节
自定义模式字段
-
type1 字节
节点类型,例如飞控、地面站、机载控制器等
-
autopilot1 字节
飞控类型
-
base_mode1 字节
系统基础模式位
-
system_status1 字节
系统状态
-
mavlink_version1 字节
协议版本号,通常固定填
3
当前 STM32 发心跳时使用的是:
-
type = 18表示
MAV_TYPE_ONBOARD_CONTROLLER -
autopilot = 8表示
MAV_AUTOPILOT_INVALID -
base_mode = 0 -
system_status = 3表示
MAV_STATE_STANDBY -
mavlink_version = 3
8.2 RC_CHANNELS
作用
RC_CHANNELS 用来传输遥控通道值。
当前工程里,PX4 通过它把通道值发给 STM32。
当前配置
在 App/px4_link.c 中定义:
c
#define MAVLINK_MSG_ID_RC_CHANNELS 65U
#define MAVLINK_MSG_ID_RC_CHANNELS_LEN 42U
#define MAVLINK_MSG_ID_RC_CHANNELS_CRC 118U
含义:
msgid = 65- 负载长度
42 CRC extra = 118
RC_CHANNELS 负载布局
当前工程按如下方式解析:
-
0..3
time_boot_ms -
4..39
chan1_raw到chan18_raw每个通道 2 字节,小端格式
-
40
chancount -
41
rssi
STM32 解析时把:
time_boot_ms转成timestamp_uschan1_raw ~ chan18_raw放进px4_link_frame_t.channels[]0xFFFF通道值视为"该通道无效",转换为0
9. CRC 是什么,为什么 MAVLink 需要 CRC extra
MAVLink 的 CRC 不只是对负载做普通校验,它还要求:
- 对负载字节做
X25 CRC - 最后再额外累加一个
CRC extra
CRC extra 的意义是:
- 防止不同消息负载长度相同但字段定义不同,仍然误判为合法。
- 让消息格式变化时能更容易发现版本不匹配。
在当前工程里:
HEARTBEAT的CRC extra = 50RC_CHANNELS的CRC extra = 118
对应实现函数在:
Mavlink_CrcAccumulate()Mavlink_CalculateCrc()Mavlink_GetCrcExtra()
10. STM32 上的 MAVLink 接收流程
当前工程的接收流程如下:
第一步:初始化串口
在 Core/Src/usart.c 中配置 USART2:
- 波特率
115200 8N1- 中断接收
第二步:启动链路接收
在 main() 中调用:
c
PX4_Link_Init();
这个函数会:
- 清空解析器状态机
- 清空上一帧缓存
- 启动
HAL_UART_Receive_IT()
第三步:USART2 收到字节
在 HAL_UART_RxCpltCallback() 中调用:
c
PX4_Link_RxCpltCallback(huart);
这个函数负责:
- 按状态机依次收
STX - 收
LEN - 收
SEQ/SYSID/COMPID/MSGID - 收负载
- 收 CRC
- 校验成功后按
msgid分发消息
第四步:主循环读取最新数据
在 main() 主循环里调用:
c
PX4_Link_GetFrame(&g_px4_frame);
如果有新帧,就把通道值取出来,进入控制优先级选择逻辑。
11. STM32 上的 MAVLink 发送流程
当前工程里 STM32 只主动发一种消息:
HEARTBEAT
发送步骤
- 准备负载
- 调用
PX4_Link_BuildMessage() - 计算 CRC
- 用
HAL_UART_Transmit()发出
调用位置
在 main() 主循环里每隔 50ms 调一次:
c
PX4_Link_SendHeartbeat();
当前发送频率
50ms 一次,也就是约 20Hz
这对心跳来说偏高,但在调试阶段没有问题。
如果后续想更贴近常规 MAVLink 用法,可以降到:
1Hz2Hz5Hz
12. 当前工程里的系统 ID 和组件 ID
当前在 STM32 侧定义:
c
#define MAVLINK_STM32_SYS_ID 200U
#define MAVLINK_STM32_COMP_ID 190U
作用
-
SYS_ID区分是哪一个系统
-
COMP_ID区分系统里的哪个模块
为什么这么配
PX4常见系统 ID 是1- STM32 作为外部控制板,单独给一个较大的系统 ID,便于抓包区分
是否可以修改
可以。
但修改后需要注意:
- 如果 PX4 端有做基于
sysid/compid的过滤,必须同步改 - 日志和抓包分析时要知道新的 ID
13. 当前控制链路优先级
虽然本文件重点是 MAVLink,但当前工程不是只有 MAVLink 一路输入。
当前优先级是:
PX4 MAVLinkSBUSPPM
对应逻辑在 Core/Src/main.c 的 select_active_channels()。
超时策略是:
- PX4:
200ms - SBUS:
100ms - PPM:
100ms
如果都超时,则把当前生效通道清零。
14. 当前工程的控制数据是怎么用的
从 PX4 收到 RC_CHANNELS 后,流程如下:
- 存入
g_px4_channels[] select_active_channels()选择当前生效源g_active_channels[]成为最终控制输入- 控制输出分发:
当前分发方式:
-
g_active_channels[1]控制
PA9/PB15对应
TurboControl_SetCommandUs() -
g_active_channels[0]控制
CAN先通过
command_to_rpm()换算,再进入CAN_Motor_SetTargetRpm()
15. 如果要增加更多 MAVLink 消息,应该怎么做
如果以后你想在 STM32 上支持更多 MAVLink 消息,建议按下面流程来。
15.1 第一步:确定消息
例如你要支持:
MANUAL_CONTROLCOMMAND_LONGATTITUDESTATUSTEXT
先确定:
- 消息 ID
- 负载长度
- CRC extra
- 每个字段的字节布局
15.2 第二步:在 px4_link.c 添加宏
例如:
c
#define MAVLINK_MSG_ID_MANUAL_CONTROL 69U
#define MAVLINK_MSG_ID_MANUAL_CONTROL_LEN ...
#define MAVLINK_MSG_ID_MANUAL_CONTROL_CRC ...
15.3 第三步:在 Mavlink_GetCrcExtra() 中注册
例如:
c
case MAVLINK_MSG_ID_MANUAL_CONTROL:
return MAVLINK_MSG_ID_MANUAL_CONTROL_CRC;
15.4 第四步:写对应的解析函数
例如:
c
static void PX4_Link_HandleManualControl(void)
{
...
}
15.5 第五步:在分发函数里接入
例如:
c
case MAVLINK_MSG_ID_MANUAL_CONTROL:
PX4_Link_HandleManualControl();
break;
15.6 第六步:在主循环中消费结果
根据你新增的结构体,把数据接到执行机构控制逻辑里。
16. STM32 上部署 MAVLink 的推荐做法
如果以后要把当前实现做得更规范,建议分成三层:
第一层:传输层
只负责:
- 串口初始化
- 中断接收
- 发送字节流
第二层:MAVLink 帧层
只负责:
- 组帧
- 解帧
- CRC
- seq/sysid/compid/msgid 管理
第三层:业务层
只负责:
- 收到
RC_CHANNELS后如何控制执行机构 - 收到
COMMAND_LONG后如何响应 - 心跳如何上报
当前工程已经基本是这个结构,只是实现还偏轻量。
17. 常见问题
17.1 为什么收不到 PX4 的 MAVLink 数据
先检查:
PA2/PA3接线是否正确- 波特率是否一致
- PX4 端是否真的在该串口发
MAVLink v1 - PX4 端发的是不是
RC_CHANNELS msgid、长度、CRC extra 是否一致
调试建议
优先抓原始串口字节,确认首字节是否为:
text
0xFE
如果不是,说明对端发的不是 MAVLink v1。
17.2 为什么能收到数据但 CRC 校验失败
常见原因:
CRC extra写错- 消息长度写错
- 对端发的是
MAVLink v2 - 串口线路噪声导致字节错误
- 解析字段顺序写错
17.3 为什么通道值不对
检查:
- 负载偏移是否正确
- 是否按小端读取
- 是否把
chan1_raw起始地址算错 0xFFFF是否被错误当作正常值使用
17.4 为什么要保留 SBUS/PPM
因为当前工程不是单一路输入。
如果 PX4 串口异常:
- SBUS 仍可接管
- PPM 仍可接管
这样系统鲁棒性更高。
18. 当前工程中的关键宏和定义说明
下面把当前最重要的 MAVLink 定义集中解释一遍。
MAVLINK_V1_STX
c
#define MAVLINK_V1_STX 0xFEU
表示:
- 当前使用
MAVLink v1 - 每一帧消息都以
0xFE开头
MAVLINK_MAX_PAYLOAD_LEN
c
#define MAVLINK_MAX_PAYLOAD_LEN 255U
表示:
- MAVLink 单帧最大负载长度为 255 字节
- 当前接收缓冲区按这个上限开辟
MAVLINK_MSG_ID_HEARTBEAT
c
#define MAVLINK_MSG_ID_HEARTBEAT 0U
表示:
HEARTBEAT消息的消息号是 0
MAVLINK_MSG_ID_HEARTBEAT_LEN
c
#define MAVLINK_MSG_ID_HEARTBEAT_LEN 9U
表示:
HEARTBEAT负载长度固定为 9 字节
MAVLINK_MSG_ID_HEARTBEAT_CRC
c
#define MAVLINK_MSG_ID_HEARTBEAT_CRC 50U
表示:
HEARTBEAT的 CRC extra 是 50
MAVLINK_MSG_ID_RC_CHANNELS
c
#define MAVLINK_MSG_ID_RC_CHANNELS 65U
表示:
RC_CHANNELS的消息号是 65
MAVLINK_MSG_ID_RC_CHANNELS_LEN
c
#define MAVLINK_MSG_ID_RC_CHANNELS_LEN 42U
表示:
RC_CHANNELS负载长度固定为 42 字节
MAVLINK_MSG_ID_RC_CHANNELS_CRC
c
#define MAVLINK_MSG_ID_RC_CHANNELS_CRC 118U
表示:
RC_CHANNELS的 CRC extra 是 118
MAVLINK_STM32_SYS_ID
c
#define MAVLINK_STM32_SYS_ID 200U
表示:
- STM32 节点在 MAVLink 网络里的系统 ID
MAVLINK_STM32_COMP_ID
c
#define MAVLINK_STM32_COMP_ID 190U
表示:
- STM32 节点在当前系统中的组件 ID
19. 如果要接入官方 MAVLink C 库,应该怎么做
当前工程使用的是"手写最小 MAVLink 子集"。
如果以后想换成官方/生成版 MAVLink C 库,步骤一般如下:
-
准备
mavlink头文件目录通常是:
mavlink/common/mavlink.h -
在
CMakeLists.txt中加入 include path -
在串口发送函数里对接:
mavlink_msg_xxx_pack()
mavlink_msg_to_send_buffer() -
在接收中断里调用:
mavlink_parse_char() -
收到完整消息后按
msg.msgid分发
这种方式的优点:
- 更标准
- 更容易扩展
- 消息定义不容易抄错
缺点:
- 占用代码空间更多
- 头文件体积大
- 对当前这种只需要少量消息的工程来说会显得偏重
20. 开发要求与扩展规范
这一节不是协议介绍,而是后续开发时必须遵守的工程要求。
也就是说,如果以后有人继续在这个工程上开发 MAVLink 功能,建议按这里的规则做。
20.1 总体开发目标
当前工程中的 MAVLink 模块,目标不是做"完整飞控协议栈",而是做"稳定、可维护、可扩展的串口控制链路"。
因此开发时应优先保证:
- 串口链路稳定。
- 消息解析准确。
- 消息扩展简单。
- 出错后容易定位。
- 不影响 PPM、SBUS、CAN、PWM 这些已有功能。
20.2 开发时的基本原则
后续开发建议遵守以下原则:
- 先保证兼容当前
MAVLink v1实现,再考虑新增消息。 - 除非明确要升级,否则不要中途把现有链路切成
MAVLink v2。 - 一次只增加少量消息,不要同时引入太多消息和太多控制逻辑。
- 协议层和业务层必须分开,不要在解析函数里直接写复杂控制逻辑。
- 所有新增宏、消息 ID、长度、CRC extra 都必须写中文注释。
- 所有新增消息都必须说明:
用来干什么
谁发送
谁接收
负载结构是什么
如何影响主控制逻辑
20.3 当前工程建议支持的数据传输类型
当前工程最适合支持以下几类数据:
-
控制类数据
例如:
RC_CHANNELS
MANUAL_CONTROL
COMMAND_LONG -
状态类数据
例如:
HEARTBEAT
STATUSTEXT
SYS_STATUS -
轻量级遥测类数据
例如:
电机转速
当前有效输入源
机构状态
故障标志
-
参数型数据
例如:
中位值
限幅
超时阈值
方向反转标志
20.4 当前工程不建议直接支持的数据
以下数据不建议在当前 STM32F103 工程里直接上:
- 大带宽图像数据
- 大量高频姿态/位置全量遥测
- 长报文文件传输
- 复杂日志流
- 高负载的 MAVLink FTP、参数全量同步等功能
原因是:
STM32F103资源有限- 当前链路主要是控制链路,不是通用数传链路
- 串口带宽和中断负载都要控制
20.5 当前工程建议支持的消息方向
建议把消息方向固定清楚,避免以后越写越乱。
PX4 -> STM32
适合发送:
-
RC_CHANNELS把遥控通道值发给 STM32
-
MANUAL_CONTROL如果以后不想用 PWM 风格通道,可以直接发摇杆量
-
COMMAND_LONG用于控制 STM32 执行某些离散命令
-
PARAM_SET/ 自定义参数消息用于调整 STM32 运行参数
STM32 -> PX4
适合发送:
-
HEARTBEAT表示 STM32 在线
-
STATUSTEXT用于打印故障和状态信息
-
NAMED_VALUE_FLOAT用于调试输出浮点数据
-
自定义状态消息或标准状态消息
例如:
当前生效通道
当前电机目标转速
当前输入源
故障标志
20.6 新增消息时的标准开发流程
以后加新消息,建议严格按下面步骤做。
步骤 1:确定用途
先回答清楚:
- 这个消息是谁发给谁的?
- 这条消息是控制还是状态?
- 更新频率是多少?
- 丢一帧是否允许?
- 超时之后应该怎么处理?
步骤 2:确定消息定义
必须明确:
msgid- 负载长度
CRC extra- 每个字段的偏移
- 每个字段的数据类型
- 小端还是大端
在 MAVLink 中通常使用小端。
步骤 3:在 App/px4_link.c 中添加协议常量
例如新增 MANUAL_CONTROL 时,要补:
c
#define MAVLINK_MSG_ID_MANUAL_CONTROL ...
#define MAVLINK_MSG_ID_MANUAL_CONTROL_LEN ...
#define MAVLINK_MSG_ID_MANUAL_CONTROL_CRC ...
每个宏都必须带中文注释。
步骤 4:在 Mavlink_GetCrcExtra() 中注册
如果不注册,CRC 校验会失败。
步骤 5:写消息解析函数
例如:
c
static void PX4_Link_HandleManualControl(void)
{
...
}
要求:
- 只负责解析,不直接做复杂业务决策。
- 解析结果放到结构体或缓存中。
- 必须检查长度是否匹配。
步骤 6:在分发函数中接入
例如:
c
case MAVLINK_MSG_ID_MANUAL_CONTROL:
PX4_Link_HandleManualControl();
break;
步骤 7:在 main.c 中消费数据
要求:
- 解析层只给出结果
main.c决定是否把它作为最高优先级控制源- 控制优先级和超时逻辑必须集中管理
步骤 8:补中文注释和文档
每新增一条消息,至少要补:
- 宏定义注释
- 解析函数注释
- 结构体字段注释
- 这份教程中的对应章节
20.7 数据类型开发要求
在 STM32 上手写 MAVLink 解析时,数据类型必须非常明确。
建议遵循以下规则:
-
uint8_t用于单字节枚举、标志位、小范围计数
-
uint16_t用于通道值、PWM 值、短整数状态量
-
uint32_t用于
time_boot_ms、计数器、位标志 -
int16_t用于摇杆量、带正负号的小范围控制量
-
float只在业务层使用
协议层如非必要尽量不要直接解析复杂浮点业务逻辑
-
uint64_t用于时间戳扩展值
20.8 字节序要求
当前工程默认所有 MAVLink 字段都按小端解析。
开发时必须遵守:
- 16 位字段统一使用小端读取函数
- 32 位字段统一使用小端读取函数
- 不允许到处手写乱七八糟的移位解析
- 若新增 64 位字段,也必须单独写统一的读取函数
20.9 命名规范
MAVLink 相关代码建议遵守下面命名规则:
-
宏定义
使用全大写
例如:
MAVLINK_MSG_ID_RC_CHANNELS -
协议内部静态函数
统一前缀:
Mavlink_或PX4_Link_ -
对外接口函数
统一前缀:
PX4_Link_ -
解析缓存变量
使用
s_前缀 -
全局业务缓存
使用
g_前缀
20.10 串口配置开发要求
后续如果要改串口参数,建议遵守:
- PX4 和 STM32 两端波特率必须完全一致
USART2默认保持115200 8N1- 如果要升到更高波特率,例如
230400或460800
必须重新做链路稳定性测试 - 波特率变更后要检查:
丢包率
CRC 错误率
中断负载
20.11 频率和带宽开发要求
不同消息建议使用不同频率:
-
HEARTBEAT建议
1Hz ~ 5Hz -
RC_CHANNELS建议
20Hz ~ 50Hz -
状态调试类消息
建议
1Hz ~ 10Hz -
高优先级控制类消息
必须先评估串口带宽和主循环负载
不要把所有消息都高频发送,否则会导致:
- 串口拥塞
- 中断负担变重
- 解析抖动
- 控制延迟变大
20.12 超时与容错要求
所有控制类消息都必须配套超时策略。
例如:
-
RC_CHANNELS超过200ms没更新则视为 PX4 控制链路失效
-
HEARTBEAT长时间没收到可视为 PX4 不在线
-
如果新增
COMMAND_LONG也要考虑命令是否允许重复执行、是否需要 ACK
20.13 向后兼容要求
如果以后修改消息解析或控制逻辑,建议遵守:
- 不要轻易修改现有
msgid - 不要轻易修改现有字段顺序
- 不要在没有同步 PX4 端的情况下改
CRC extra - 如果确实要改协议,必须同时更新:
STM32 代码
PX4 代码
文档
20.14 调试要求
新增消息后,至少做以下验证:
- 能否稳定收到完整帧
- CRC 是否正确
- 字段解析是否正确
- 超时后行为是否正确
- 主循环是否还能稳定运行
- 不会影响 CAN、PPM、SBUS、PWM 的既有功能
建议新增以下调试手段:
- 帧计数
- CRC 错误计数
- 未知消息 ID 计数
- 超时计数
- 当前输入源状态
20.15 建议支持的数据传输内容总表
下面给出一个建议表,说明当前工程后续适合支持什么数据。
适合优先支持
RC_CHANNELSHEARTBEATMANUAL_CONTROLCOMMAND_LONGSTATUSTEXTNAMED_VALUE_FLOAT
适合按需支持
- 自定义电机状态消息
- 自定义执行机构状态消息
- 自定义错误码消息
- 参数下发消息
不建议在当前板级大量使用
- 大量高频姿态数据
- 参数全量同步
- FTP/文件类功能
- 大型遥测流
21. 当前工程的建议后续改进
如果后续要继续完善,我建议按下面顺序做:
-
先把 PX4 侧
stm32_control.cpp改成标准MAVLink v1 RC_CHANNELS发送 -
在 STM32 侧增加串口调试统计
例如:
收到多少帧
CRC 错误多少帧
丢弃多少帧
-
增加
PX4_Link_IsAlive()接口根据
HEARTBEAT判断 PX4 链路是否在线 -
如有需要,再增加:
COMMAND_LONG
MANUAL_CONTROL
STATUSTEXT -
最后再考虑是否升级到
MAVLink v2
22. PX4 端 stm32_control.cpp 改成 MAVLink v1 发送的详细开发教程
这一节讲的是 PX4 端如何改。
目标是把你现在 PX4 侧的自定义串口协议:
text
0xAA + len + payload + checksum + 0xFF
替换成标准 MAVLink v1 消息发送。
22.1 当前 PX4 端的现状
你当前给出的文件是:
/home/af/sg-px4/PX4-Autopilot/src/modules/sg/stm32_control.cpp
这个文件目前做的事情是:
- 从
input_rc或manual_control_setpoint中取控制量 - 打包成自定义二进制帧
- 从串口发给 STM32
如果 STM32 已经改成收 MAVLink v1,那 PX4 侧也必须同步改成发 MAVLink v1。
22.2 PX4 端建议发送什么消息
对于当前工程,PX4 端建议至少发送:
HEARTBEATRC_CHANNELS
如果以后需要更直接的摇杆量,也可以加:
MANUAL_CONTROL
22.3 PX4 端最推荐的最小方案
最小可用方案是:
- 保留
stm32_control.cpp模块 - 仍然由它自己管理串口
- 不依赖 PX4 主
mavlink模块做转发 - 直接在这个模块里用 MAVLink 头文件打包消息
优点:
- 改动集中
- 不影响 PX4 原有数传实例
- 更容易和 STM32 这条专用链路解耦
22.4 PX4 端需要包含什么头文件
在 PX4 端,建议包含:
cpp
#include <modules/mavlink/mavlink_bridge_header.h>
或者可直接使用:
cpp
#include <mavlink.h>
前提是该模块的编译 include path 已经带上 MAVLink 头文件目录。
如果是 PX4 工程内模块,优先建议用:
cpp
#include <modules/mavlink/mavlink_bridge_header.h>
因为它已经处理了 PX4 的桥接环境。
22.5 PX4 端发送 MAVLink 的基本流程
PX4 端改成 MAVLink 发送后,流程建议如下:
- 取出 RC 数据
- 构造
mavlink_rc_channels_t - 调用打包函数
- 转成字节流
- 从串口写出
22.6 RC_CHANNELS 发送模板
下面是 PX4 端发送 RC_CHANNELS 的推荐模板逻辑:
cpp
#include <modules/mavlink/mavlink_bridge_header.h>
void Stm32Control::send_rc_channels_mavlink(const input_rc_s &rc)
{
if (_serial_fd < 0) {
return;
}
mavlink_message_t msg{};
mavlink_rc_channels_t rc_msg{};
uint8_t buffer[MAVLINK_MAX_PACKET_LEN];
rc_msg.time_boot_ms = rc.timestamp / 1000;
rc_msg.chancount = rc.channel_count;
rc_msg.chan1_raw = (rc.channel_count > 0) ? rc.values[0] : UINT16_MAX;
rc_msg.chan2_raw = (rc.channel_count > 1) ? rc.values[1] : UINT16_MAX;
rc_msg.chan3_raw = (rc.channel_count > 2) ? rc.values[2] : UINT16_MAX;
rc_msg.chan4_raw = (rc.channel_count > 3) ? rc.values[3] : UINT16_MAX;
rc_msg.chan5_raw = (rc.channel_count > 4) ? rc.values[4] : UINT16_MAX;
rc_msg.chan6_raw = (rc.channel_count > 5) ? rc.values[5] : UINT16_MAX;
rc_msg.chan7_raw = (rc.channel_count > 6) ? rc.values[6] : UINT16_MAX;
rc_msg.chan8_raw = (rc.channel_count > 7) ? rc.values[7] : UINT16_MAX;
rc_msg.chan9_raw = (rc.channel_count > 8) ? rc.values[8] : UINT16_MAX;
rc_msg.chan10_raw = (rc.channel_count > 9) ? rc.values[9] : UINT16_MAX;
rc_msg.chan11_raw = (rc.channel_count > 10) ? rc.values[10] : UINT16_MAX;
rc_msg.chan12_raw = (rc.channel_count > 11) ? rc.values[11] : UINT16_MAX;
rc_msg.chan13_raw = (rc.channel_count > 12) ? rc.values[12] : UINT16_MAX;
rc_msg.chan14_raw = (rc.channel_count > 13) ? rc.values[13] : UINT16_MAX;
rc_msg.chan15_raw = (rc.channel_count > 14) ? rc.values[14] : UINT16_MAX;
rc_msg.chan16_raw = (rc.channel_count > 15) ? rc.values[15] : UINT16_MAX;
rc_msg.chan17_raw = (rc.channel_count > 16) ? rc.values[16] : UINT16_MAX;
rc_msg.chan18_raw = (rc.channel_count > 17) ? rc.values[17] : UINT16_MAX;
rc_msg.rssi = rc.rssi;
mavlink_msg_rc_channels_encode(1, MAV_COMP_ID_AUTOPILOT1, &msg, &rc_msg);
const uint16_t len = mavlink_msg_to_send_buffer(buffer, &msg);
px4_write(_serial_fd, buffer, len);
}
22.7 PX4 端发送 HEARTBEAT 模板
如果希望 PX4 端也主动发 HEARTBEAT 给 STM32,可以参考:
cpp
void Stm32Control::send_heartbeat_mavlink()
{
if (_serial_fd < 0) {
return;
}
mavlink_message_t msg{};
uint8_t buffer[MAVLINK_MAX_PACKET_LEN];
mavlink_msg_heartbeat_pack(
1,
MAV_COMP_ID_AUTOPILOT1,
&msg,
MAV_TYPE_QUADROTOR,
MAV_AUTOPILOT_PX4,
0,
0,
MAV_STATE_ACTIVE
);
const uint16_t len = mavlink_msg_to_send_buffer(buffer, &msg);
px4_write(_serial_fd, buffer, len);
}
22.8 PX4 端改造步骤建议
建议按这个顺序改:
- 保留原串口打开逻辑
- 删掉自定义
0xAA/0xFF封包代码 - 增加 MAVLink 头文件
- 增加
send_rc_channels_mavlink() - 可选增加
send_heartbeat_mavlink() - 在
Run()中改成发 MAVLink 消息 - 串口抓包确认 STM32 收到
0xFE帧头
22.9 PX4 端改造后的验证方法
改完 PX4 端后,建议验证:
- 串口首字节是否为
0xFE - STM32 是否能解析
RC_CHANNELS - STM32 是否把通道值正确接入控制逻辑
- STM32 发回的
HEARTBEAT是否能被 PX4 端识别或至少抓到字节流
23. 新增一条 MAVLink 消息时的代码模板示例
这一节给一个"通用模板",以后你要继续扩展时,可以直接照着改。
下面以新增 MANUAL_CONTROL 消息为例说明。
23.1 第一步:加宏定义
在 App/px4_link.c 顶部加:
c
/* MANUAL_CONTROL 的消息 ID。 */
#define MAVLINK_MSG_ID_MANUAL_CONTROL 69U
/* MANUAL_CONTROL 的负载长度。 */
#define MAVLINK_MSG_ID_MANUAL_CONTROL_LEN 11U
/* MANUAL_CONTROL 的 CRC extra。 */
#define MAVLINK_MSG_ID_MANUAL_CONTROL_CRC 243U
注意:
msgidlenCRC extra
必须与 MAVLink 官方定义一致。
23.2 第二步:在 CRC extra 查询函数中注册
在 Mavlink_GetCrcExtra() 中加:
c
case MAVLINK_MSG_ID_MANUAL_CONTROL:
return MAVLINK_MSG_ID_MANUAL_CONTROL_CRC;
23.3 第三步:设计业务结构体
如果当前 px4_link_frame_t 不够用,建议新增一个结构体:
c
typedef struct
{
int16_t x;
int16_t y;
int16_t z;
int16_t r;
uint16_t buttons;
uint8_t target;
} px4_manual_control_frame_t;
建议放在 App/px4_link.h 中,并写中文注释。
23.4 第四步:添加静态缓存
在 App/px4_link.c 中增加:
c
static volatile px4_manual_control_frame_t s_manual_frame;
static volatile uint8_t s_manual_frame_ready;
23.5 第五步:写解析函数
模板如下:
c
/*
* 解析 MANUAL_CONTROL 消息。
* 只负责把负载字段拆出来,不直接写业务控制逻辑。
*/
static void PX4_Link_HandleManualControl(void)
{
px4_manual_control_frame_t frame;
if (s_rx_len != MAVLINK_MSG_ID_MANUAL_CONTROL_LEN) {
return;
}
frame.x = (int16_t)Mavlink_GetU16(s_rx_payload, 0);
frame.y = (int16_t)Mavlink_GetU16(s_rx_payload, 2);
frame.z = (int16_t)Mavlink_GetU16(s_rx_payload, 4);
frame.r = (int16_t)Mavlink_GetU16(s_rx_payload, 6);
frame.buttons = Mavlink_GetU16(s_rx_payload, 8);
frame.target = s_rx_payload[10];
s_manual_frame = frame;
s_manual_frame_ready = 1U;
}
23.6 第六步:加到消息分发函数中
在 PX4_Link_HandleMessage() 中加:
c
case MAVLINK_MSG_ID_MANUAL_CONTROL:
PX4_Link_HandleManualControl();
break;
23.7 第七步:增加读取接口
在 App/px4_link.h 中加:
c
uint8_t PX4_Link_GetManualControl(px4_manual_control_frame_t *frame);
在 App/px4_link.c 中实现:
c
uint8_t PX4_Link_GetManualControl(px4_manual_control_frame_t *frame)
{
uint8_t ready;
if (frame == NULL) {
return 0U;
}
__disable_irq();
ready = s_manual_frame_ready;
if (ready != 0U) {
*frame = s_manual_frame;
s_manual_frame_ready = 0U;
}
__enable_irq();
return ready;
}
23.8 第八步:在 main.c 中接入业务逻辑
例如:
c
px4_manual_control_frame_t manual_frame;
if (PX4_Link_GetManualControl(&manual_frame) != 0U) {
/* 在这里决定是否把 manual_frame 转换成执行机构控制量 */
}
23.9 第九步:补文档
新增消息后,必须同步更新这份文档中的:
- 支持的消息列表
- 消息 ID / 长度 / CRC extra
- 负载结构
- 业务使用方式
23.10 第十步:做回归测试
建议至少验证:
- 新消息能收
- 旧消息不受影响
- CRC 正常
- 控制逻辑没有被错误覆盖
- 超时逻辑仍然正确
24. 常用 MAVLink 消息 ID / 长度 / CRC extra 速查表
这一节不是完整的 MAVLink 全消息表,而是面向当前 STM32 工程最常见、最值得关注的消息速查表。
字段说明:
-
消息名MAVLink 标准消息名
-
MsgID消息 ID
-
Len负载长度
-
CRC Extra该消息对应的 CRC extra
-
方向建议更适合
PX4 -> STM32还是STM32 -> PX4 -
推荐程度是否适合当前工程优先支持
注意:
下面表格中的数值用于当前文档开发参考。
实际编码前,仍建议再以 MAVLink 官方定义或 PX4 当前使用的生成头文件为最终准。
| 消息名 | MsgID | Len | CRC Extra | 方向建议 | 推荐程度 | 说明 |
|---|---|---|---|---|---|---|
HEARTBEAT |
0 | 9 | 50 | 双向 | 强烈推荐 | 所有节点在线状态基础消息 |
SYS_STATUS |
1 | 31 | 124 | STM32 -> PX4 | 按需 | 可用于回传系统状态、电压、负载等 |
SYSTEM_TIME |
2 | 12 | 137 | PX4 -> STM32 | 按需 | 如果后续要做时间同步可考虑 |
PING |
4 | 14 | 237 | 双向 | 按需 | 用于测延迟、测链路 |
CHANGE_OPERATOR_CONTROL |
5 | 28 | 217 | 不推荐 | 不建议 | 当前工程基本用不到 |
CHANGE_OPERATOR_CONTROL_ACK |
6 | 3 | 104 | 不推荐 | 不建议 | 当前工程基本用不到 |
PARAM_REQUEST_READ |
20 | 20 | 214 | PX4 -> STM32 | 按需 | 若以后支持参数读取可使用 |
PARAM_REQUEST_LIST |
21 | 2 | 159 | PX4 -> STM32 | 按需 | 若以后支持参数枚举可使用 |
PARAM_VALUE |
22 | 25 | 220 | STM32 -> PX4 | 按需 | 用于回传参数值 |
PARAM_SET |
23 | 23 | 168 | PX4 -> STM32 | 推荐 | 后续做参数下发时很有用 |
GPS_RAW_INT |
24 | 30 | 24 | 不建议 | 不建议 | 当前控制链路不适合大量发这类遥测 |
SCALED_IMU |
26 | 22 | 170 | 不建议 | 不建议 | 当前 STM32 不是 IMU 主节点 |
ATTITUDE |
30 | 28 | 39 | STM32 -> PX4 | 按需 | 如果 STM32 后续产生姿态信息可考虑 |
LOCAL_POSITION_NED |
32 | 28 | 185 | 不建议 | 不建议 | 当前工程不是定位主模块 |
GLOBAL_POSITION_INT |
33 | 28 | 104 | 不建议 | 不建议 | 当前工程不建议承担这类遥测主链路 |
RC_CHANNELS_SCALED |
34 | 22 | 237 | PX4 -> STM32 | 一般 | 若要发标准化摇杆量可考虑 |
RC_CHANNELS_RAW |
35 | 22 | 244 | PX4 -> STM32 | 一般 | 老式通道消息,当前更建议用 RC_CHANNELS |
SERVO_OUTPUT_RAW |
36 | 21 | 222 | PX4 -> STM32 | 按需 | 若以后 STM32 要接管部分舵机输出可参考 |
MISSION_REQUEST_PARTIAL_LIST |
37 | 6 | 212 | 不推荐 | 不建议 | 当前工程不用任务协议 |
MISSION_WRITE_PARTIAL_LIST |
38 | 6 | 9 | 不推荐 | 不建议 | 当前工程不用任务协议 |
MISSION_ITEM |
39 | 37 | 254 | 不推荐 | 不建议 | 当前工程不用任务协议 |
MISSION_REQUEST |
40 | 4 | 230 | 不推荐 | 不建议 | 当前工程不用任务协议 |
MISSION_SET_CURRENT |
41 | 4 | 28 | 不推荐 | 不建议 | 当前工程不用任务协议 |
MISSION_CURRENT |
42 | 2 | 28 | 不推荐 | 不建议 | 当前工程不用任务协议 |
MISSION_REQUEST_LIST |
43 | 2 | 132 | 不推荐 | 不建议 | 当前工程不用任务协议 |
MISSION_COUNT |
44 | 4 | 221 | 不推荐 | 不建议 | 当前工程不用任务协议 |
MISSION_CLEAR_ALL |
45 | 2 | 232 | 不推荐 | 不建议 | 当前工程不用任务协议 |
MISSION_ITEM_REACHED |
46 | 2 | 11 | 不推荐 | 不建议 | 当前工程不用任务协议 |
MISSION_ACK |
47 | 3 | 153 | 不推荐 | 不建议 | 当前工程不用任务协议 |
SET_GPS_GLOBAL_ORIGIN |
48 | 13 | 41 | 不建议 | 不建议 | 当前工程不用 |
GPS_GLOBAL_ORIGIN |
49 | 12 | 39 | 不建议 | 不建议 | 当前工程不用 |
ATTITUDE_QUATERNION |
31 | 32 | 246 | 按需 | 一般 | 如果以后需要更完整姿态可考虑 |
MANUAL_CONTROL |
69 | 11 | 243 | PX4 -> STM32 | 推荐 | 如果以后想直接传摇杆量,非常适合 |
RC_CHANNELS |
65 | 42 | 118 | PX4 -> STM32 | 强烈推荐 | 当前工程主控制消息 |
REQUEST_DATA_STREAM |
66 | 6 | 148 | PX4 -> STM32 | 一般 | 若以后需要请求类机制可参考 |
DATA_STREAM |
67 | 4 | 21 | STM32 -> PX4 | 一般 | 当前工程一般不必自己实现 |
STATUSTEXT |
253 | 51 | 83 | STM32 -> PX4 | 推荐 | 用于调试提示、错误提示很合适 |
NAMED_VALUE_FLOAT |
251 | 18 | 170 | STM32 -> PX4 | 推荐 | 用于输出调试变量非常方便 |
DEBUG |
254 | 9 | 46 | STM32 -> PX4 | 一般 | 简单调试值可用 |
DEBUG_VECT |
250 | 30 | 49 | STM32 -> PX4 | 一般 | 三轴调试数据可用 |
COMMAND_LONG |
76 | 33 | 152 | PX4 -> STM32 | 强烈推荐 | 适合做离散命令控制 |
COMMAND_ACK |
77 | 3 | 143 | STM32 -> PX4 | 强烈推荐 | 若支持 COMMAND_LONG,最好配 ACK |
COMMAND_INT |
75 | 35 | 158 | 按需 | 一般 | 当前工程通常不如 COMMAND_LONG 简单 |
AUTOPILOT_VERSION |
148 | 60 | 178 | STM32 -> PX4 | 按需 | 如果以后要把 STM32 做成更独立节点可考虑 |
TIMESYNC |
111 | 16 | 34 | 双向 | 按需 | 若以后需要更严格时间同步可考虑 |
24.1 当前工程优先建议使用的消息
如果后续继续开发,优先级建议如下:
第一优先级
HEARTBEATRC_CHANNELSCOMMAND_LONGCOMMAND_ACK
第二优先级
MANUAL_CONTROLSTATUSTEXTNAMED_VALUE_FLOATPARAM_SET
第三优先级
SYS_STATUSPINGTIMESYNC
24.2 当前工程最不建议优先做的消息
以下消息虽然常见,但不建议在当前 STM32F103 控制链路里优先做:
MISSION_*GPS_*LOCAL_POSITION_*GLOBAL_POSITION_*- 大量高频 IMU/姿态遥测
原因是:
- 当前工程核心是控制链路,不是完整飞控节点
- 板子资源有限
- 串口带宽应该优先给控制类消息
24.3 使用这张速查表时的注意事项
开发时建议这样用:
- 先在这张表里找目标消息
- 记下
MsgID / Len / CRC Extra - 再去官方定义或 PX4 MAVLink 生成头文件中二次确认
- 最后再写入
App/px4_link.c
不要只凭记忆手写 CRC extra,因为这是最容易出错的地方之一。
25. 总结
当前工程已经完成了一套可运行的 STM32 上 MAVLink 部署方案,特点是:
- 使用
USART2 (PA2/PA3)与 PX4 通信 - 使用
MAVLink v1 - 接收
RC_CHANNELS - 接收
HEARTBEAT - 发送
HEARTBEAT - 将
RC_CHANNELS接入本地控制优先级系统
这套方案的优点是:
- 实现简单
- 资源占用可控
- 易于在
STM32F103上调试 - 已经适合当前工程需求
如果后续你要,我可以继续再写两份配套文档:
PX4 端如何发送 MAVLink RC_CHANNELS 给 STM32STM32 上如何扩展更多 MAVLink 消息的开发模板