本文为《无人机飞控固件开发教程》系列视频的辅助资料,已经在“网易云课堂”上发布:视频链接。无人机调试,飞控硬件定制、固件修改,日志分析,请QQ联系:3500985284
本文参考链接:https://mavlink.io/zh/
需求来源:
1、增稳云台里的STM32单片机需要通过串口接收飞控传来的云台俯仰、横滚控制指令和相机拍照控制指令;

2、自制的有害气体采集器需要接收飞控传来的飞机当前的位置信息。

Mavlink协议的优势(为什么不自定义协议)
1、通用性强,绝大部分飞控和地面站都是使用Mavlink协议,兼容性好;相比于自定义协议,在不同的飞控和地面站之间切换时代价更小;
2、避免了重复造轮子,降低了总体工作量,方便快速实现功能;
3、基于XML文件定义消息,可读性强,然后使用Python自动生成代码,并且可以生成常见的大部分语言的代码(C、C++、Java、Python、C#等),这极大地降低了飞控与地面站联调的工作量(如APM飞控使用的C++,而MissionPlanner地面站使用的是C#),也就是说跨平台性非常好;
4、支持多主机和多节点(System ID、Component ID)。
Mavlink的帧结构
# 以下为Mavlink2的帧结构:
uint8_t magic; // 固定帧头,Mavlink2为0xFD
uint8_t len; // 帧数据长度
uint8_t incompat_flags; // 不兼容标识
uint8_t compat_flags; // 兼容标识
uint8_t seq; // 帧序号
uint8_t sysid; // 系统ID,飞行器、地面站等各自算一个系统。
// 此值为0表示广播;飞控的此值一般为1;地面站的此值一般为255
uint8_t compid; // 组件ID,此值为0表示广播
uint8_t msgid 0:7; // 消息ID,bit0 ~ bit7
uint8_t msgid 8:15; // 消息ID,bit8 ~ bit15
uint8_t msgid 16:23; // 消息ID,bit0 ~ bit7
uint8_t payload[max 255]; // 帧数据,最多255个字节
uint16_t checksum; // CRC-16/MCRF4XX

安装Mavlink生成器
1、安装Python3
Microsoft Store中搜索Python3,安装大于Python3.7的版本(是否含Python3.7不确定)。
2、获取Mavlink代码
# 无需切换分支
# 下面的“--recursive”已经包含了更新子模块的功能
git clone https://github.com/mavlink/mavlink.git --recursive
我下载好放到了百度网盘上:
链接:https://pan.baidu.com/s/1iCCTlAM7B-VMutP9TDFwsg
提取码:nfcy
3、安装依赖项
# 在mavlink文件夹,右键,“在终端中打开”
python3 -m pip install -r pymavlink/requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
# 注意,有黄色warning不用管
生成Mavlink头文件

使用Mavlink的一大优势就是:在大部分情况下,你只需要大致了解Mavlink底层帧结构、只需要关注所需传输信息的数据类型、组合结构即可。所有的解帧、组帧函数都会自动生成,只需要直接调用就行,非常方便。
# 在mavlink文件夹,右键,“在终端中打开”
python3 -m mavgenerate
# XML选择mavlink/message_definitions/v1.0/ardupilotmega.xml
# Out选择一个自己建立的文件夹
# Language选择C
# Protocol选择2.0,即使用mavlink2

注意,Mavlink2已经在2017年开始取代Mavlink1,并且Mavlink2的解析函数是兼容Mavlink1的,因此我们这里选用Mavlink2。
认识Mavlink的XML文件
参考链接:https://mavlink.io/zh/guide/xml_schema.html
认识生成的头文件
将Mavlink头文件集成到STM32工程中
示例工程链接:https://gitee.com/junzixing/stm32-mavlink (master分支)
1、硬件介绍

演示电路板原理图在STM32工程目录的Doc文件夹下:

2、飞控端设置
- SERIAL2_PROTOCOL:2,飞控串口2的通信协议,mavlink2
- SERIAL2_BAUD:460,飞控串口2的波特率,460指的是460800
- SERIAL2_OPTIONS:0,飞控串口2的额外选型,设置为默认值
3、将生成的代码复制到STM32工程中
4、在Keil中添加include路径

5、在代码中包含“.h”文件
#include <ardupilotmega/mavlink.h>

6、Mavlink解帧示例(详见视频教程:链接)
7、Mavlink组帧发送示例(详见视频教程:链接)
8、消除编译时的warning

原理:32位的int的最大值为2^31(去掉一个符号位)- 1 = 2147483647,而mavlink生成的代码里最大值已经超过了这个值。
解决方法:
MAV_SYS_STATUS_EXTENSION_USED=2147483646,
MAV_SYS_STATUS_SENSOR_ENUM_END=2147483647,
HIL_SENSOR_UPDATED_RESET=2147483646,
HIL_SENSOR_UPDATED_FLAGS_ENUM_END=2147483647,
9、mavlink中“数据流”(data stream)的概念
配置SERIAL2(参数名中简称SR2)的各个数据流的频率的参数:


进阶:添加自定义Mavlink帧
此例程飞控源代码下载链接:
链接: https://pan.baidu.com/s/1xuTV4CnW0K2_jv6MKyK4Qw
提取码: nfcy
注意,此压缩包里的代码已经切换好分支并且更新好子模块,可以直接编译。
1、修改飞控端XML文件
飞控中的xml文件位置:modules/mavlink/message_defines/v1.0/
视频中的示例中我们修改的是modules/mavlink/message_defines/v1.0/cubepilot.xml,添加的内容如下:
<message id="50006" name="NFCY_TEST_MAVLINK">
<description>test mavlink.</description>
<field type="uint8_t" name="test1">Test field 1.</field>
<field type="uint16_t" name="test2">Test field 2.</field>
<field type="uint32_t" name="test3">Test field 3.</field>
</message>
2、编译飞控固件,产生对应的头文件
修改xml文件后直接编译即可,不需要重新制定编译目标;
飞控代码编译后mavlink头文件位置:build/飞控名/libraries/GCS_MAVLink/include/v2.0/
注意,xml文件夹中的v1.0和头文件的v2.0含义不同(应该是历史遗留问题,这里我们生成的的确是mavlink2)。
3、在飞控中添加自定义mavlink的发送程序
在ArduCopter/GCS_Mavlink.h中,添加发送函数的声明(注意,对于固定翼,就是在ArduPlane/GCS_Mavlink.h中,也就是说每种载具里都有其独有的GCS_Mavlink,然后在libraries中有各种载具共有的GCS_Mavlink)
void send_nfcy_test_mavlink() const;
在ArduCopter/GCS_Mavlink.cpp中添加函数定义:
void GCS_MAVLINK_Copter::send_nfcy_test_mavlink() const
{
mavlink_msg_nfcy_test_mavlink_send( # 发送函数的名字都为“mavlink_msg_消息名_send”
chan, 1, 2, 3);
}
在libraries/GCS_MAVLink/ap_message.h中添加自定义的ID:
MSG_NFCY_TEST, // 注意,这个必须放在MSG_LAST前面
在libraries/GCS_MAVLink/GCS_Common.cpp中添加两种ID的对应关系:
// 前面一个为Mavlink自动生成的ID,后面一个为上一步添加的ID
// 前面的ID格式都是“MAVLINK_MSG_ID_消息名”
{ MAVLINK_MSG_ID_NFCY_TEST_MAVLINK, MSG_NFCY_TEST},
在ArduCopter/GCS_Mavlink.cpp中添加:
// bool GCS_MAVLINK_Copter::try_send_message(enum ap_message id)函数中:
case MSG_NFCY_TEST:
send_nfcy_test_mavlink();
break;
在ArduCopter/Copter.cpp中添加:
// void Copter::three_hz_loop()函数中添加:
gcs().send_message(MSG_NFCY_TEST);
为什么这么麻烦?
因为飞控实际上是将打算发送的帧的ID放入了一个队列中,然后逐个发送出去,避免了等待上一个帧发送完成的阻塞。

4、在飞控中添加解析自定义mavlink的程序
请按照视频教程中的步骤操作:链接
5、为STM32工程重新生成Mavlink头文件(先修改XML文件)
请按照视频教程中的步骤操作:链接
6、在STM32中添加自定义mavlink的发送程序
请按照视频教程中的步骤操作:链接。注意,stm32的例程切换到“add_msg”分支。
7、在STM32中添加自定义mavlink的接收处理程序
请按照视频教程中的步骤操作:链接。注意,stm32的例程切换到“add_msg”分支。
疑难解答
后续大家遇到的问题的解决方法会更新在此处。