STM32 上部署 MAVLink 协议教程

1. 文档目的

这份文档面向当前工程 /home/af/sg-px4/stm32--px4/stm32_px4,用于说明:

  1. 什么是 MAVLink,为什么在 STM32 上常用它和 PX4 通信。
  2. 当前工程里是如何在 STM32F103 上部署 MAVLink v1 的。
  3. 如果后续你要扩展更多 MAVLink 消息,应该从哪里改、怎么改。
  4. 在 STM32 上使用 MAVLink 时常见的配置项、宏定义、消息结构、CRC 规则和调试方法。

这份文档既讲协议原理,也讲当前项目中的实际落地方式。


MAVLink 是无人机领域非常常见的一种轻量级通信协议,广泛用于:

  1. 飞控和地面站之间通信。
  2. 飞控和外设之间通信。
  3. 飞控和伴随计算机之间通信。
  4. 模块之间传递标准化消息,例如姿态、位置、通道、心跳、命令等。

它的特点是:

  1. 二进制协议,数据紧凑,适合串口和无线链路。
  2. 标准化消息很多,例如 HEARTBEATRC_CHANNELSATTITUDECOMMAND_LONG
  3. 支持不同版本,常见是 MAVLink v1MAVLink v2
  4. 每条消息都有固定 msgid、固定或规定好的负载结构、CRC extra。

对于 STM32 这种资源较小的 MCU 来说,MAVLink 很适合做"标准化串口协议"。


当前工程里,STM32 与 PX4 的主要需求是:

  1. PX4 通过串口向 STM32 发送控制量。
  2. STM32 解析控制量后驱动本地执行机构。
  3. STM32 向 PX4 回发链路在线状态。

在这个场景下,MAVLink v1 已经足够,原因如下:

  1. 需要的消息不多。

    当前核心只需要:
    HEARTBEAT
    RC_CHANNELS

  2. v1 帧格式更简单。

    对裸机 MCU 手写解析器更友好。

  3. STM32F103 资源有限。
    v1 的状态机更容易维护和调试。

  4. 当前目标是先把链路稳定打通。

    等以后需要更多标准消息或者签名,再考虑上 v2


在当前工程里:

  1. PX4 是串口发送端,发送 MAVLink RC_CHANNELS 消息给 STM32。
  2. STM32 是串口接收端,解析 RC_CHANNELS 并取出通道值。
  3. STM32 同时也会周期性发送 MAVLink HEARTBEAT 给 PX4,表示自己在线。

也就是说,这里 STM32 并没有实现一个完整的 MAVLink 通用协议栈,而是实现了:

  1. MAVLink v1 帧接收器
  2. RC_CHANNELS 解码器
  3. HEARTBEAT 解码器
  4. HEARTBEAT 打包发送器

这是一个"最小可用"的 MAVLink 子集实现。


5. 当前工程中的引脚和模块分工

5.1 串口通信引脚

当前与 PX4 通信使用:

  1. PA2:USART2_TX
  2. PA3:USART2_RX

对应文件:

  1. Core/Src/usart.c
  2. App/px4_link.c
  3. App/px4_link.h

5.2 其他功能引脚

当前工程里还有这些控制链路:

  1. PA7:PPM 解析
  2. PB10/PB11:SBUS 解析
  3. PA11/PA12:CAN 控制
  4. PA9/PB15:涡轮/推杆控制

而 MAVLink 只负责 PA2/PA3 这一条链路。


6.1 核心文件

当前 MAVLink 相关实现主要在:

  1. App/px4_link.c
  2. App/px4_link.h
  3. Core/Src/usart.c
  4. Core/Src/main.c

6.2 各文件职责

App/px4_link.c

负责:

  1. 定义 MAVLink 相关宏。
  2. 接收串口字节并按 MAVLink v1 状态机组帧。
  3. 校验 CRC。
  4. 解码 HEARTBEAT
  5. 解码 RC_CHANNELS
  6. 发送 HEARTBEAT
App/px4_link.h

负责:

  1. 暴露 px4_link_frame_t 结构体。
  2. 暴露初始化、接收回调、取帧、发心跳接口。
Core/Src/usart.c

负责:

  1. 初始化 USART2
  2. HAL_UART_RxCpltCallback() 中把收到的字节转发到 PX4_Link_RxCpltCallback()
Core/Src/main.c

负责:

  1. 初始化 PX4_Link_Init()
  2. 在主循环中读取 PX4_Link_GetFrame()
  3. 把解码出来的通道值放入控制优先级系统。
  4. 周期性发送 PX4_Link_SendHeartbeat()

当前工程使用的是 MAVLink v1,帧格式如下:

text 复制代码
STX | LEN | SEQ | SYSID | COMPID | MSGID | PAYLOAD | CRC_L | CRC_H

各字段含义:

  1. STX

    帧头。
    MAVLink v1 固定是 0xFE

  2. LEN

    当前消息负载长度,不包含头部和 CRC。

  3. SEQ

    包序号。

    每发一帧自增一次,主要用于统计丢包。

  4. SYSID

    系统 ID。

    表示消息来自哪个系统。

  5. COMPID

    组件 ID。

    表示系统中的哪个模块发出的消息。

  6. MSGID

    消息 ID。

    决定负载该如何解释。

  7. PAYLOAD

    实际消息负载。

    不同消息的负载结构不同。

  8. CRC_LCRC_H

    X25 CRC 校验结果低字节和高字节。

    还要额外累加一个 CRC extra


8.1 HEARTBEAT

作用

HEARTBEAT 用来表示节点在线。

当前工程中:

  1. PX4 发 HEARTBEAT 给 STM32,STM32 记录对端还活着。
  2. 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

含义:

  1. msgid = 0
  2. 负载长度 9
  3. CRC extra = 50

HEARTBEAT 负载字段

负载长度为 9 字节,字段含义如下:

  1. custom_mode

    4 字节

    自定义模式字段

  2. type

    1 字节

    节点类型,例如飞控、地面站、机载控制器等

  3. autopilot

    1 字节

    飞控类型

  4. base_mode

    1 字节

    系统基础模式位

  5. system_status

    1 字节

    系统状态

  6. mavlink_version

    1 字节

    协议版本号,通常固定填 3

当前 STM32 发心跳时使用的是:

  1. type = 18

    表示 MAV_TYPE_ONBOARD_CONTROLLER

  2. autopilot = 8

    表示 MAV_AUTOPILOT_INVALID

  3. base_mode = 0

  4. system_status = 3

    表示 MAV_STATE_STANDBY

  5. 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

含义:

  1. msgid = 65
  2. 负载长度 42
  3. CRC extra = 118

RC_CHANNELS 负载布局

当前工程按如下方式解析:

  1. 0..3
    time_boot_ms

  2. 4..39
    chan1_rawchan18_raw

    每个通道 2 字节,小端格式

  3. 40
    chancount

  4. 41
    rssi

STM32 解析时把:

  1. time_boot_ms 转成 timestamp_us
  2. chan1_raw ~ chan18_raw 放进 px4_link_frame_t.channels[]
  3. 0xFFFF 通道值视为"该通道无效",转换为 0

MAVLink 的 CRC 不只是对负载做普通校验,它还要求:

  1. 对负载字节做 X25 CRC
  2. 最后再额外累加一个 CRC extra

CRC extra 的意义是:

  1. 防止不同消息负载长度相同但字段定义不同,仍然误判为合法。
  2. 让消息格式变化时能更容易发现版本不匹配。

在当前工程里:

  1. HEARTBEATCRC extra = 50
  2. RC_CHANNELSCRC extra = 118

对应实现函数在:

  1. Mavlink_CrcAccumulate()
  2. Mavlink_CalculateCrc()
  3. Mavlink_GetCrcExtra()

当前工程的接收流程如下:

第一步:初始化串口

Core/Src/usart.c 中配置 USART2

  1. 波特率 115200
  2. 8N1
  3. 中断接收

第二步:启动链路接收

main() 中调用:

c 复制代码
PX4_Link_Init();

这个函数会:

  1. 清空解析器状态机
  2. 清空上一帧缓存
  3. 启动 HAL_UART_Receive_IT()

第三步:USART2 收到字节

HAL_UART_RxCpltCallback() 中调用:

c 复制代码
PX4_Link_RxCpltCallback(huart);

这个函数负责:

  1. 按状态机依次收 STX
  2. LEN
  3. SEQ/SYSID/COMPID/MSGID
  4. 收负载
  5. 收 CRC
  6. 校验成功后按 msgid 分发消息

第四步:主循环读取最新数据

main() 主循环里调用:

c 复制代码
PX4_Link_GetFrame(&g_px4_frame);

如果有新帧,就把通道值取出来,进入控制优先级选择逻辑。


当前工程里 STM32 只主动发一种消息:

  1. HEARTBEAT

发送步骤

  1. 准备负载
  2. 调用 PX4_Link_BuildMessage()
  3. 计算 CRC
  4. HAL_UART_Transmit() 发出

调用位置

main() 主循环里每隔 50ms 调一次:

c 复制代码
PX4_Link_SendHeartbeat();

当前发送频率

50ms 一次,也就是约 20Hz

这对心跳来说偏高,但在调试阶段没有问题。

如果后续想更贴近常规 MAVLink 用法,可以降到:

  1. 1Hz
  2. 2Hz
  3. 5Hz

12. 当前工程里的系统 ID 和组件 ID

当前在 STM32 侧定义:

c 复制代码
#define MAVLINK_STM32_SYS_ID 200U
#define MAVLINK_STM32_COMP_ID 190U

作用

  1. SYS_ID

    区分是哪一个系统

  2. COMP_ID

    区分系统里的哪个模块

为什么这么配

  1. PX4 常见系统 ID 是 1
  2. STM32 作为外部控制板,单独给一个较大的系统 ID,便于抓包区分

是否可以修改

可以。

但修改后需要注意:

  1. 如果 PX4 端有做基于 sysid/compid 的过滤,必须同步改
  2. 日志和抓包分析时要知道新的 ID

13. 当前控制链路优先级

虽然本文件重点是 MAVLink,但当前工程不是只有 MAVLink 一路输入。

当前优先级是:

  1. PX4 MAVLink
  2. SBUS
  3. PPM

对应逻辑在 Core/Src/main.cselect_active_channels()

超时策略是:

  1. PX4:200ms
  2. SBUS:100ms
  3. PPM:100ms

如果都超时,则把当前生效通道清零。


14. 当前工程的控制数据是怎么用的

从 PX4 收到 RC_CHANNELS 后,流程如下:

  1. 存入 g_px4_channels[]
  2. select_active_channels() 选择当前生效源
  3. g_active_channels[] 成为最终控制输入
  4. 控制输出分发:

当前分发方式:

  1. g_active_channels[1]

    控制 PA9/PB15

    对应 TurboControl_SetCommandUs()

  2. g_active_channels[0]

    控制 CAN

    先通过 command_to_rpm() 换算,再进入 CAN_Motor_SetTargetRpm()


如果以后你想在 STM32 上支持更多 MAVLink 消息,建议按下面流程来。

15.1 第一步:确定消息

例如你要支持:

  1. MANUAL_CONTROL
  2. COMMAND_LONG
  3. ATTITUDE
  4. STATUSTEXT

先确定:

  1. 消息 ID
  2. 负载长度
  3. CRC extra
  4. 每个字段的字节布局

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 ...

例如:

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 第六步:在主循环中消费结果

根据你新增的结构体,把数据接到执行机构控制逻辑里。


如果以后要把当前实现做得更规范,建议分成三层:

第一层:传输层

只负责:

  1. 串口初始化
  2. 中断接收
  3. 发送字节流

只负责:

  1. 组帧
  2. 解帧
  3. CRC
  4. seq/sysid/compid/msgid 管理

第三层:业务层

只负责:

  1. 收到 RC_CHANNELS 后如何控制执行机构
  2. 收到 COMMAND_LONG 后如何响应
  3. 心跳如何上报

当前工程已经基本是这个结构,只是实现还偏轻量。


17. 常见问题

先检查:

  1. PA2/PA3 接线是否正确
  2. 波特率是否一致
  3. PX4 端是否真的在该串口发 MAVLink v1
  4. PX4 端发的是不是 RC_CHANNELS
  5. msgid、长度、CRC extra 是否一致

调试建议

优先抓原始串口字节,确认首字节是否为:

text 复制代码
0xFE

如果不是,说明对端发的不是 MAVLink v1


17.2 为什么能收到数据但 CRC 校验失败

常见原因:

  1. CRC extra 写错
  2. 消息长度写错
  3. 对端发的是 MAVLink v2
  4. 串口线路噪声导致字节错误
  5. 解析字段顺序写错

17.3 为什么通道值不对

检查:

  1. 负载偏移是否正确
  2. 是否按小端读取
  3. 是否把 chan1_raw 起始地址算错
  4. 0xFFFF 是否被错误当作正常值使用

17.4 为什么要保留 SBUS/PPM

因为当前工程不是单一路输入。

如果 PX4 串口异常:

  1. SBUS 仍可接管
  2. PPM 仍可接管

这样系统鲁棒性更高。


18. 当前工程中的关键宏和定义说明

下面把当前最重要的 MAVLink 定义集中解释一遍。

c 复制代码
#define MAVLINK_V1_STX 0xFEU

表示:

  1. 当前使用 MAVLink v1
  2. 每一帧消息都以 0xFE 开头
c 复制代码
#define MAVLINK_MAX_PAYLOAD_LEN 255U

表示:

  1. MAVLink 单帧最大负载长度为 255 字节
  2. 当前接收缓冲区按这个上限开辟
c 复制代码
#define MAVLINK_MSG_ID_HEARTBEAT 0U

表示:

  1. HEARTBEAT 消息的消息号是 0
c 复制代码
#define MAVLINK_MSG_ID_HEARTBEAT_LEN 9U

表示:

  1. HEARTBEAT 负载长度固定为 9 字节
c 复制代码
#define MAVLINK_MSG_ID_HEARTBEAT_CRC 50U

表示:

  1. HEARTBEAT 的 CRC extra 是 50
c 复制代码
#define MAVLINK_MSG_ID_RC_CHANNELS 65U

表示:

  1. RC_CHANNELS 的消息号是 65
c 复制代码
#define MAVLINK_MSG_ID_RC_CHANNELS_LEN 42U

表示:

  1. RC_CHANNELS 负载长度固定为 42 字节
c 复制代码
#define MAVLINK_MSG_ID_RC_CHANNELS_CRC 118U

表示:

  1. RC_CHANNELS 的 CRC extra 是 118
c 复制代码
#define MAVLINK_STM32_SYS_ID 200U

表示:

  1. STM32 节点在 MAVLink 网络里的系统 ID
c 复制代码
#define MAVLINK_STM32_COMP_ID 190U

表示:

  1. STM32 节点在当前系统中的组件 ID

当前工程使用的是"手写最小 MAVLink 子集"。

如果以后想换成官方/生成版 MAVLink C 库,步骤一般如下:

  1. 准备 mavlink 头文件目录

    通常是:
    mavlink/common/mavlink.h

  2. CMakeLists.txt 中加入 include path

  3. 在串口发送函数里对接:
    mavlink_msg_xxx_pack()
    mavlink_msg_to_send_buffer()

  4. 在接收中断里调用:
    mavlink_parse_char()

  5. 收到完整消息后按 msg.msgid 分发

这种方式的优点:

  1. 更标准
  2. 更容易扩展
  3. 消息定义不容易抄错

缺点:

  1. 占用代码空间更多
  2. 头文件体积大
  3. 对当前这种只需要少量消息的工程来说会显得偏重

20. 开发要求与扩展规范

这一节不是协议介绍,而是后续开发时必须遵守的工程要求。

也就是说,如果以后有人继续在这个工程上开发 MAVLink 功能,建议按这里的规则做。

20.1 总体开发目标

当前工程中的 MAVLink 模块,目标不是做"完整飞控协议栈",而是做"稳定、可维护、可扩展的串口控制链路"。

因此开发时应优先保证:

  1. 串口链路稳定。
  2. 消息解析准确。
  3. 消息扩展简单。
  4. 出错后容易定位。
  5. 不影响 PPM、SBUS、CAN、PWM 这些已有功能。

20.2 开发时的基本原则

后续开发建议遵守以下原则:

  1. 先保证兼容当前 MAVLink v1 实现,再考虑新增消息。
  2. 除非明确要升级,否则不要中途把现有链路切成 MAVLink v2
  3. 一次只增加少量消息,不要同时引入太多消息和太多控制逻辑。
  4. 协议层和业务层必须分开,不要在解析函数里直接写复杂控制逻辑。
  5. 所有新增宏、消息 ID、长度、CRC extra 都必须写中文注释。
  6. 所有新增消息都必须说明:
    用来干什么
    谁发送
    谁接收
    负载结构是什么
    如何影响主控制逻辑

20.3 当前工程建议支持的数据传输类型

当前工程最适合支持以下几类数据:

  1. 控制类数据

    例如:
    RC_CHANNELS
    MANUAL_CONTROL
    COMMAND_LONG

  2. 状态类数据

    例如:
    HEARTBEAT
    STATUSTEXT
    SYS_STATUS

  3. 轻量级遥测类数据

    例如:

    电机转速

    当前有效输入源

    机构状态

    故障标志

  4. 参数型数据

    例如:

    中位值

    限幅

    超时阈值

    方向反转标志

20.4 当前工程不建议直接支持的数据

以下数据不建议在当前 STM32F103 工程里直接上:

  1. 大带宽图像数据
  2. 大量高频姿态/位置全量遥测
  3. 长报文文件传输
  4. 复杂日志流
  5. 高负载的 MAVLink FTP、参数全量同步等功能

原因是:

  1. STM32F103 资源有限
  2. 当前链路主要是控制链路,不是通用数传链路
  3. 串口带宽和中断负载都要控制

20.5 当前工程建议支持的消息方向

建议把消息方向固定清楚,避免以后越写越乱。

PX4 -> STM32

适合发送:

  1. RC_CHANNELS

    把遥控通道值发给 STM32

  2. MANUAL_CONTROL

    如果以后不想用 PWM 风格通道,可以直接发摇杆量

  3. COMMAND_LONG

    用于控制 STM32 执行某些离散命令

  4. PARAM_SET / 自定义参数消息

    用于调整 STM32 运行参数

STM32 -> PX4

适合发送:

  1. HEARTBEAT

    表示 STM32 在线

  2. STATUSTEXT

    用于打印故障和状态信息

  3. NAMED_VALUE_FLOAT

    用于调试输出浮点数据

  4. 自定义状态消息或标准状态消息

    例如:

    当前生效通道

    当前电机目标转速

    当前输入源

    故障标志

20.6 新增消息时的标准开发流程

以后加新消息,建议严格按下面步骤做。

步骤 1:确定用途

先回答清楚:

  1. 这个消息是谁发给谁的?
  2. 这条消息是控制还是状态?
  3. 更新频率是多少?
  4. 丢一帧是否允许?
  5. 超时之后应该怎么处理?
步骤 2:确定消息定义

必须明确:

  1. msgid
  2. 负载长度
  3. CRC extra
  4. 每个字段的偏移
  5. 每个字段的数据类型
  6. 小端还是大端

在 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 ...

每个宏都必须带中文注释。

如果不注册,CRC 校验会失败。

步骤 5:写消息解析函数

例如:

c 复制代码
static void PX4_Link_HandleManualControl(void)
{
    ...
}

要求:

  1. 只负责解析,不直接做复杂业务决策。
  2. 解析结果放到结构体或缓存中。
  3. 必须检查长度是否匹配。
步骤 6:在分发函数中接入

例如:

c 复制代码
case MAVLINK_MSG_ID_MANUAL_CONTROL:
    PX4_Link_HandleManualControl();
    break;
步骤 7:在 main.c 中消费数据

要求:

  1. 解析层只给出结果
  2. main.c 决定是否把它作为最高优先级控制源
  3. 控制优先级和超时逻辑必须集中管理
步骤 8:补中文注释和文档

每新增一条消息,至少要补:

  1. 宏定义注释
  2. 解析函数注释
  3. 结构体字段注释
  4. 这份教程中的对应章节

20.7 数据类型开发要求

在 STM32 上手写 MAVLink 解析时,数据类型必须非常明确。

建议遵循以下规则:

  1. uint8_t

    用于单字节枚举、标志位、小范围计数

  2. uint16_t

    用于通道值、PWM 值、短整数状态量

  3. uint32_t

    用于 time_boot_ms、计数器、位标志

  4. int16_t

    用于摇杆量、带正负号的小范围控制量

  5. float

    只在业务层使用

    协议层如非必要尽量不要直接解析复杂浮点业务逻辑

  6. uint64_t

    用于时间戳扩展值

20.8 字节序要求

当前工程默认所有 MAVLink 字段都按小端解析。

开发时必须遵守:

  1. 16 位字段统一使用小端读取函数
  2. 32 位字段统一使用小端读取函数
  3. 不允许到处手写乱七八糟的移位解析
  4. 若新增 64 位字段,也必须单独写统一的读取函数

20.9 命名规范

MAVLink 相关代码建议遵守下面命名规则:

  1. 宏定义

    使用全大写

    例如:
    MAVLINK_MSG_ID_RC_CHANNELS

  2. 协议内部静态函数

    统一前缀:
    Mavlink_PX4_Link_

  3. 对外接口函数

    统一前缀:
    PX4_Link_

  4. 解析缓存变量

    使用 s_ 前缀

  5. 全局业务缓存

    使用 g_ 前缀

20.10 串口配置开发要求

后续如果要改串口参数,建议遵守:

  1. PX4 和 STM32 两端波特率必须完全一致
  2. USART2 默认保持 115200 8N1
  3. 如果要升到更高波特率,例如 230400460800
    必须重新做链路稳定性测试
  4. 波特率变更后要检查:
    丢包率
    CRC 错误率
    中断负载

20.11 频率和带宽开发要求

不同消息建议使用不同频率:

  1. HEARTBEAT

    建议 1Hz ~ 5Hz

  2. RC_CHANNELS

    建议 20Hz ~ 50Hz

  3. 状态调试类消息

    建议 1Hz ~ 10Hz

  4. 高优先级控制类消息

    必须先评估串口带宽和主循环负载

不要把所有消息都高频发送,否则会导致:

  1. 串口拥塞
  2. 中断负担变重
  3. 解析抖动
  4. 控制延迟变大

20.12 超时与容错要求

所有控制类消息都必须配套超时策略。

例如:

  1. RC_CHANNELS 超过 200ms 没更新

    则视为 PX4 控制链路失效

  2. HEARTBEAT 长时间没收到

    可视为 PX4 不在线

  3. 如果新增 COMMAND_LONG

    也要考虑命令是否允许重复执行、是否需要 ACK

20.13 向后兼容要求

如果以后修改消息解析或控制逻辑,建议遵守:

  1. 不要轻易修改现有 msgid
  2. 不要轻易修改现有字段顺序
  3. 不要在没有同步 PX4 端的情况下改 CRC extra
  4. 如果确实要改协议,必须同时更新:
    STM32 代码
    PX4 代码
    文档

20.14 调试要求

新增消息后,至少做以下验证:

  1. 能否稳定收到完整帧
  2. CRC 是否正确
  3. 字段解析是否正确
  4. 超时后行为是否正确
  5. 主循环是否还能稳定运行
  6. 不会影响 CAN、PPM、SBUS、PWM 的既有功能

建议新增以下调试手段:

  1. 帧计数
  2. CRC 错误计数
  3. 未知消息 ID 计数
  4. 超时计数
  5. 当前输入源状态

20.15 建议支持的数据传输内容总表

下面给出一个建议表,说明当前工程后续适合支持什么数据。

适合优先支持
  1. RC_CHANNELS
  2. HEARTBEAT
  3. MANUAL_CONTROL
  4. COMMAND_LONG
  5. STATUSTEXT
  6. NAMED_VALUE_FLOAT
适合按需支持
  1. 自定义电机状态消息
  2. 自定义执行机构状态消息
  3. 自定义错误码消息
  4. 参数下发消息
不建议在当前板级大量使用
  1. 大量高频姿态数据
  2. 参数全量同步
  3. FTP/文件类功能
  4. 大型遥测流

21. 当前工程的建议后续改进

如果后续要继续完善,我建议按下面顺序做:

  1. 先把 PX4 侧 stm32_control.cpp 改成标准 MAVLink v1 RC_CHANNELS 发送

  2. 在 STM32 侧增加串口调试统计

    例如:

    收到多少帧

    CRC 错误多少帧

    丢弃多少帧

  3. 增加 PX4_Link_IsAlive() 接口

    根据 HEARTBEAT 判断 PX4 链路是否在线

  4. 如有需要,再增加:
    COMMAND_LONG
    MANUAL_CONTROL
    STATUSTEXT

  5. 最后再考虑是否升级到 MAVLink v2


这一节讲的是 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

这个文件目前做的事情是:

  1. input_rcmanual_control_setpoint 中取控制量
  2. 打包成自定义二进制帧
  3. 从串口发给 STM32

如果 STM32 已经改成收 MAVLink v1,那 PX4 侧也必须同步改成发 MAVLink v1

22.2 PX4 端建议发送什么消息

对于当前工程,PX4 端建议至少发送:

  1. HEARTBEAT
  2. RC_CHANNELS

如果以后需要更直接的摇杆量,也可以加:

  1. MANUAL_CONTROL

22.3 PX4 端最推荐的最小方案

最小可用方案是:

  1. 保留 stm32_control.cpp 模块
  2. 仍然由它自己管理串口
  3. 不依赖 PX4 主 mavlink 模块做转发
  4. 直接在这个模块里用 MAVLink 头文件打包消息

优点:

  1. 改动集中
  2. 不影响 PX4 原有数传实例
  3. 更容易和 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 的桥接环境。

PX4 端改成 MAVLink 发送后,流程建议如下:

  1. 取出 RC 数据
  2. 构造 mavlink_rc_channels_t
  3. 调用打包函数
  4. 转成字节流
  5. 从串口写出

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 端改造步骤建议

建议按这个顺序改:

  1. 保留原串口打开逻辑
  2. 删掉自定义 0xAA/0xFF 封包代码
  3. 增加 MAVLink 头文件
  4. 增加 send_rc_channels_mavlink()
  5. 可选增加 send_heartbeat_mavlink()
  6. Run() 中改成发 MAVLink 消息
  7. 串口抓包确认 STM32 收到 0xFE 帧头

22.9 PX4 端改造后的验证方法

改完 PX4 端后,建议验证:

  1. 串口首字节是否为 0xFE
  2. STM32 是否能解析 RC_CHANNELS
  3. STM32 是否把通道值正确接入控制逻辑
  4. STM32 发回的 HEARTBEAT 是否能被 PX4 端识别或至少抓到字节流

这一节给一个"通用模板",以后你要继续扩展时,可以直接照着改。

下面以新增 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

注意:

  1. msgid
  2. len
  3. CRC 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 第九步:补文档

新增消息后,必须同步更新这份文档中的:

  1. 支持的消息列表
  2. 消息 ID / 长度 / CRC extra
  3. 负载结构
  4. 业务使用方式

23.10 第十步:做回归测试

建议至少验证:

  1. 新消息能收
  2. 旧消息不受影响
  3. CRC 正常
  4. 控制逻辑没有被错误覆盖
  5. 超时逻辑仍然正确

这一节不是完整的 MAVLink 全消息表,而是面向当前 STM32 工程最常见、最值得关注的消息速查表。

字段说明:

  1. 消息名

    MAVLink 标准消息名

  2. MsgID

    消息 ID

  3. Len

    负载长度

  4. CRC Extra

    该消息对应的 CRC extra

  5. 方向建议

    更适合 PX4 -> STM32 还是 STM32 -> PX4

  6. 推荐程度

    是否适合当前工程优先支持

注意:

下面表格中的数值用于当前文档开发参考。

实际编码前,仍建议再以 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 当前工程优先建议使用的消息

如果后续继续开发,优先级建议如下:

第一优先级
  1. HEARTBEAT
  2. RC_CHANNELS
  3. COMMAND_LONG
  4. COMMAND_ACK
第二优先级
  1. MANUAL_CONTROL
  2. STATUSTEXT
  3. NAMED_VALUE_FLOAT
  4. PARAM_SET
第三优先级
  1. SYS_STATUS
  2. PING
  3. TIMESYNC

24.2 当前工程最不建议优先做的消息

以下消息虽然常见,但不建议在当前 STM32F103 控制链路里优先做:

  1. MISSION_*
  2. GPS_*
  3. LOCAL_POSITION_*
  4. GLOBAL_POSITION_*
  5. 大量高频 IMU/姿态遥测

原因是:

  1. 当前工程核心是控制链路,不是完整飞控节点
  2. 板子资源有限
  3. 串口带宽应该优先给控制类消息

24.3 使用这张速查表时的注意事项

开发时建议这样用:

  1. 先在这张表里找目标消息
  2. 记下 MsgID / Len / CRC Extra
  3. 再去官方定义或 PX4 MAVLink 生成头文件中二次确认
  4. 最后再写入 App/px4_link.c

不要只凭记忆手写 CRC extra,因为这是最容易出错的地方之一。


25. 总结

当前工程已经完成了一套可运行的 STM32 上 MAVLink 部署方案,特点是:

  1. 使用 USART2 (PA2/PA3) 与 PX4 通信
  2. 使用 MAVLink v1
  3. 接收 RC_CHANNELS
  4. 接收 HEARTBEAT
  5. 发送 HEARTBEAT
  6. RC_CHANNELS 接入本地控制优先级系统

这套方案的优点是:

  1. 实现简单
  2. 资源占用可控
  3. 易于在 STM32F103 上调试
  4. 已经适合当前工程需求

如果后续你要,我可以继续再写两份配套文档:

  1. PX4 端如何发送 MAVLink RC_CHANNELS 给 STM32
  2. STM32 上如何扩展更多 MAVLink 消息的开发模板
相关推荐
进击的小头2 小时前
第5篇:嵌入式处理器内核全解析:TI DSP各个系列核心差异与选型指南
单片机·嵌入式硬件
广药门徒2 小时前
PADS 复用模块的使用
嵌入式硬件
HIZYUAN2 小时前
AG32 MCU可以替代STM32+CPLD吗 (二)
stm32·单片机·嵌入式硬件·fpga开发·agm ag32·国产mcu+fpga·低成本soc
古译汉书11 小时前
【IoT死磕系列】Day 9:架构一台“自动驾驶物流车”,看8种协议如何协同作战
网络·arm开发·单片机·物联网·tcp/ip·架构·自动驾驶
FreakStudio13 小时前
小作坊 GitHub 协作闭环:fork-sync-dev-pr-merge 实战指南
python·单片机·嵌入式·面向对象·电子diy
cmpxr_18 小时前
【单片机】位域非原子写的风险
单片机·嵌入式硬件
FPGA-ADDA18 小时前
第二篇:RFSoC芯片架构详解——处理系统(PS)与可编程逻辑(PL)
嵌入式硬件·fpga开发·信号处理·fpga·47dr
恒森宇电子有限公司19 小时前
南麟LN1151 超低静态功耗 CMOS 低压差线性稳压器 多种封装形式
单片机·嵌入式硬件
九鼎创展科技21 小时前
国产高性能 MCU 开发板新标杆:PICO2 主板深度解析
单片机·嵌入式硬件