mavlink协议详解_MAVLink通讯协议在STM32上移植,并自定义协议
mavlink全称是(Micro Air Vehicle Message Marshalling Library),从名字可以看出,mavlink是主要面向飞控的一种开源通信协议。因此它默认定义了很多适用于飞控的信息格式,比如heartbeat(心跳信号,每隔一两秒主从通信一次,以验证通信是否正常)。
首先要说明的是,mavlink作为一个非常可靠(至少两字节校验)、支持类型丰富(message ID、component ID等)的通信协议,每次通信时,除了payload以外,还要占用至少8个字节的冗余信息,具体的这八个字节都是什么,可以参考别人的详细介绍。因此在使用mavlink之前需要考虑,在硬件资源非常有限的情况下,是否有必要牺牲效率来换取可靠性。
先放一些参考文章
MAVLink除了能够支持ardupilot等无人机通信协议外,最大的特点是可以定制通信协议。前面两篇文章主要在讲MAVLink的主要结构,后面三篇出自同一个人,完整再现了一个如何从自动生成代码并移植到STM32上的过程,本文参考其甚多,但是正如前面所言,这里面没有对如何定制通信协议进行讨论,并且也没有对整个MAVLink的结构有介绍,在移植的过程中总是报错。
定制通信协议
MAVLink的通信协议是根据xml文件自动生成的。
image.png
从官网下载MAVLink的源码后,可以得知定义通信协议的xml文件位于message_definitions/v1.0/下面,其中参考文章3、4和5就利用的common.xml进行自动生成的。
image.png
test.xml是其中最简单的一种协议,test.xml的代码如下所示:
3
Test all field types
char
string
uint8_t
uint16_t
uint32_t
uint64_t
int8_t
int16_t
int32_t
int64_t
float
double
uint8_t_array
uint16_t_array
uint32_t_array
uint64_t_array
int8_t_array
int16_t_array
int32_t_array
int64_t_array
float_array
double_array
里面的定义比较清晰,参考前面1、2文章,相信大多数人是很容易看懂是什么意思的,此处不再赘述。
我们定义我们发送的数据叫pressure,里面只包含一个double型的变量,名叫PP(此处也可以定义更多变量),其定义xml如下:
3
Test all field types
double
message id为0的情况在无人机通信协议中一般代指heartbeat,这里我们直接忽略,就命其为pressure。可以理解为pressure就类似结构体的名字,PP就是里面的成员变量的名字,类型是double。
生成mavlink通信协议的文件
参考文章3,可以用Python根据xml文件自动生成mavlink通信所需的文件。
在mavlink文件夹内执行
python -m mavgenerate
弹出下图所示 MAVink Generator
image.png
XML选择message_definitions/v1.0/下已经定义好的文件Out随便选择一个空文件夹
点击Generate即可在out文件夹内生成所需要的通讯文件,全部都是.h文件,其中带有一个pressure文件夹,这个文件夹的名字和你XML的名字是一样的
image.png
image.png
pressure文件夹内的文件是针对pressure这一种message专门生成的,pressure外面文件夹内的文件是较为通用的文件,但是每个协议xml不同,生成的内容也不一样。
修改文件避免报错
在移植到keil5中,需要修改的主要以下几处,否则会报大量的错误。
mavlink_types.h,
image.png
mavlink_types.h
image.png
checksum.h
image.png
mavlink_conversions.h
image.png
image.png
mavlink_helpers.h
image.png
至此,在keil5中编译mavlink.h开头的文件都不会有错了,使用时直接包含mavlink.h即可。
在我们使用中,pressure外面文件夹内的文件定义了上层的通信接口,每次生成都是一样的(比如在pressure内再添加一个成员变量时),pressure文件夹内的文件是根据xml文件来的,如果再添加一条attitude信息,则会根据attitude的定义,生成一个对应的文件夹,因此修改好外面这几个错误,可以直接拷贝使用,不用每次换个协议就重新修改使用。
6 warning: #191-D: type qualifier is meaningless on cast type
MDK中问题:warning : type qualifier is meaningless on cast type return 的解决
在MDK编译代码时,有时会出现这样的警告,
..\MAVLINK\fish_type\./mavlink_msg_pressure_collected_full.h(317): warning: #191-D: type qualifier is meaningless on cast type
image.png
解决办法:
image.png
--gnu 则根据实际情况添加或者不添加
这里吐槽一下mavlink,它生成函数只有定义,没有声明,keil无法跳转到函数定义,非常不方便。
打包信息并发送
MAVLink的关于pressure的函数都位于mavlink_msg_pressure.h中,我们最需要关心两个问题
1、如何发送我采集到的pressure数据?
2、如何接收并解析出上位机发送给我的数据?
对于问题1,mavlink分两步走:
1)mavlink_msg_pressure_pack、mavlink_msg_pressure_pack_chan、mavlink_msg_pressure_encode、mavlink_msg_pressure_encode_chan,这四个函数都在mavlink_msg_pressure中定义,是用来打包所需要发送的信息的,打包好的信息里面已经带有校验码和顺序等一系列信息,因此无需再考虑添加校验位的问题
2)打包好的信息并不是一个数组,而是mavlink_message_t类型的,此类型名字不带pressure,说明这是一个比较上层的结构。我们可以利用mavlink_msg_to_send_buffer函数将mavlink_message_t类型的信息转成char 数组的形式,并返回数组长度,有了此数组可以调用对应单片机的发送模块(如串口)进行发送
3)注:mavlink还提供了上层代码和下层代码之间进行互相匹配的设置,默认是没有开启的。这一段代码在mavlink_msg_pressure.h中,即#define MAVLINK_USE_CONVENIENCE_FUNCTIONS后可以使用mavlink_msg_pressure_send、mavlink_msg_pressure_send_struct、mavlink_msg_pressure_send_buf等函数直接调用串口的发送程序进行发送。这四个函数的仅是接口略有不同,调用的核心函数都是一样的。函数的调用过程为发送函数 >> _mav_finalize_message_chan_send >> _mavlink_send_uart >> comm_send_ch,因此只需要定义好comm_send_ch即可使用上层函数通过串口发送数据。
发送信息的大致流程代码为:
mavlink_message_t message_buf;
// preesure_buffer的大小为8+sizeof(double)
uint8_t preesure_buffer[MAVLINK_NUM_NON_PAYLOAD_BYTES + MAVLINK_MSG_ID_pressure_LEN];
double PP= 123.5678;
int length = 0;
// system_id、component_id随便设置,不影响发送,接收方自己能对号入座即可
mavlink_msg_pressure_pack(14, 15, &message_buf, PP);
length = mavlink_msg_to_send_buffer(preesure_buffer, &message_buf);
// serial_send(preesure_buffer, length);
// serial_write_buf(preesure_buffer, length); //配合后面的mavlink_usart_fifo.c使用
接收信息并解析
首先我们需要认识到,单片机接收数据是按照字节进行接收的,每一个字节都会触发接收中断,但是单片机事先是无法得知这一帧数据是多少个字节的,即使知道字节数,万一出现丢失数据的情况,真实数据也无从得知。此处就体现出标准通信协议的优势了,我们不仅不需要考虑丢失数据校验的问题,还能够按照字节处理数据,做到及时解析出正确数据和及时发现传输错误的数据。接收数据的关键函数在mavlink_helper.h中
MAVLink在接收信息时,也需要两步走:
1)在不间断的接收过程中,指示出何时接收到完整的一帧数据,并返回。mavlink_parse_char即可以不断接收一个字节的数据,并在接收到完整一条数据时返回1,否则返回0,并返回一个mavlink_message_t类型的数据。
2)在接收到完整的一帧数据时,可以用mavlink_msg_pressure_get_PP从mavlink_message_t类型中得到PP数据,也可用mavlink_msg_pressure_decode对mavlink_message_t进行解析得到一个mavlink_pressure_t的数据**。
接收信息的处理大致流程为:
mavlink_message_t msg;
mavlink_status_t status;
mavlink_channel_t chan;
void USART3_IRQHandler(void)
{
uint8_t c;
if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)//数据接收终端
{
c = USART_ReceiveData(USART3);
if(mavlink_parse_char(chan, c, &msg, &status))
{
double pp = mavlink_msg_pressure_get_PP(&msg);
printf("Received message with ID %d, sequence: %d from component %d of system %d, pp = %.3f\n", \
msg.msgid, msg.seq, msg.compid, msg.sysid, pp);
}
}
}
串口FIFO
具体到STM32,其作为一款嵌入式芯片,实时性是它优先考虑的。
一般来说串口是高速设备,因此发生中断时处理串口任务应时间应尽量短,同时,在发送时,如果有大量的数据要发送,会一直占用串口资源,也会阻碍后续任务运行。因此考虑为串口设备增加FIFO缓存,以减轻高速设备和低速任务之间速度不匹配的问题。
代码来自于文章5,这里仅作备份。
mavlink_usart_fifo.h
// mavlink_usart_fifo.h
#ifndef _USART_FIFO_H_//×÷Õߣººã¾ÃÁ¦ÐÐ qq:624668529
#define _USART_FIFO_H_
#include "stdint.h"
#define true 1
#define false 0
#define UART_TX_BUFFER_SIZE 120
#define UART_RX_BUFFER_SIZE 120
typedef struct _fifo
{
uint8_t *buf;
uint16_t length;
uint16_t head;
uint16_t tail;
} fifo_t;
uint8_t fifo_read_ch(fifo_t *fifo, uint8_t *ch);
uint8_t fifo_write_ch(fifo_t *fifo, uint8_t ch);
uint16_t fifo_free(fifo_t *fifo);
uint16_t fifo_used(fifo_t *fifo);
void fifo_init(fifo_t *fifo, uint8_t *buf, uint16_t length);
uint8_t serial_write_buf(uint8_t *buf, uint16_t length);
uint8_t serial_read_ch(void);
uint16_t serial_free(void);
uint16_t serial_available(void);
#endif /*_USART_FIFO_H_*/
mavlink_usart_fifo.c
//mavlink_usart_fifo.c
#include "mavlink_usart_fifo.h"
#include "stm32f4xx.h"
#include "mavlink.h"
mavlink_message_t msg;
mavlink_status_t status;
extern mavlink_channel_t chan;
fifo_t uart_rx_fifo, uart_tx_fifo;
uint8_t uart_tx_buf[UART_TX_BUFFER_SIZE], uart_rx_buf[UART_RX_BUFFER_SIZE];
/** @brief 读FIFO
* @param fifo 待读缓冲区
* *ch 读到的数据
* @return
* 正确读取,1; 无数据,0
*/
uint8_t fifo_read_ch(fifo_t* fifo, uint8_t* ch)
{
if(fifo->tail == fifo->head) return false;
*ch = fifo->buf[fifo->tail];
if(++fifo->tail >= fifo->length) fifo->tail = 0;
return true;
}
/** @brief 写一字节数据到FIFO
* @param fifo 待写入缓冲区
* ch 待写入的数据
* @return
* 正确,1; 缓冲区满,0
*/
uint8_t fifo_write_ch(fifo_t* fifo, uint8_t ch)
{
uint16_t h = fifo->head;
if(++h >= fifo->length) h = 0;
if(h == fifo->tail) return false;
fifo->buf[fifo->head] = ch;
fifo->head = h;
return true;
}
/** @brief 返回缓冲区剩余字节长度
* @param fifo
* @return
* 剩余空间
*
* @note 剩余字节长度大于等于2时,才可写入数据
*/
uint16_t fifo_free(fifo_t* fifo)
{
uint16_t free;
if(fifo->head >= fifo->tail) free = fifo->tail + (fifo->length - fifo->head);
else free = fifo->tail - fifo->head;
return free;
}
uint16_t fifo_used(fifo_t* fifo)
{
uint16_t used;
if(fifo->head >= fifo->tail) used = fifo->head - fifo->tail;
else used = fifo->head + (fifo->length - fifo->tail);
return used;
}
/** @brief 初始化缓冲区
* @param *fifo
* *buf
* length
*/
void fifo_init(fifo_t* fifo, uint8_t* buf, uint16_t length)
{
uint16_t i;
fifo->buf = buf;
fifo->length = length;
fifo->head = 0;
fifo->tail = 0;
for(i=0; ibuf[i] = 0;
}
/** @brief 写数据到串口,启动发射
*
* @note 数据写入发射缓冲区后,启动发射中断,在中断程序,数据自动发出
*/
uint8_t serial_write_buf(uint8_t* buf, uint16_t length) {
uint16_t i;
if(length == 0) return false;
for(i = 0; length > 0; length--, i++) {
fifo_write_ch(&uart_tx_fifo, buf[i]);
}
USART_ITConfig(USART2, USART_IT_TXE, ENABLE);
return true;
}
/** @brief 自串口读数据
* @return 一字节数据
*/
uint8_t serial_read_ch(void){
uint8_t ch;
fifo_read_ch(&uart_rx_fifo, &ch);
return ch;
}
/** @breif 检测发射缓冲区剩余字节长度
* @return 剩余字节长度
*/
uint16_t serial_free(void){
return fifo_free(&uart_tx_fifo);
}
uint16_t serial_available(void){
uint16_t used=0;
used = fifo_used(&uart_rx_fifo);
//printf("%d\n", used);
return used;
}
// 数据发送
void USART2_IRQHandler(void)
{
uint8_t c;
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)//数据接收终端
{
USART_ITConfig(USART2, USART_IT_RXNE, DISABLE);
}
if(USART_GetITStatus(USART2, USART_IT_TXE) != RESET)//数据发送中断
{
if(fifo_read_ch(&uart_tx_fifo, &c))
USART_SendData(USART2, c);
else
USART_SendData(USART2, 0x55);
if (fifo_used(&uart_tx_fifo) == 0) // Check if all data is transmitted . if yes disable transmitter UDRE interrupt
{
// Disable the EVAL_COM1 Transmit interrupt
USART_ITConfig(USART2, USART_IT_TXE, DISABLE);
}
}
}
//数据接收
void USART3_IRQHandler(void)
{
uint8_t c;
if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)//数据接收终端
{
c = USART_ReceiveData(USART3);
//fifo_write_ch(&uart_rx_fifo, c);
if(mavlink_parse_char(chan, c, &msg, &status))
{
double pp = mavlink_msg_pressure_get_PP(&msg);
printf("Received message with ID %d, sequence: %d from component %d of system %d, pp = %.3f\n", \
msg.msgid, msg.seq, msg.compid, msg.sysid, pp);
}
}
}
mavlink协议详解_MAVLink通讯协议在STM32上移植,并自定义协议相关推荐
- mavlink协议详解_MAVLink通讯协议全文.pdf
您所在位置:网站首页 > 海量文档  > 计算机 > 网络信息安全 MAVLink通讯协议全文.pdf104页 本文档 ...
- Telnet协议详解及使用C# 用Socket 编程来实现Telnet协议 - Atpking - 博客园
Telnet协议详解及使用C# 用Socket 编程来实现Telnet协议 - Atpking - 博客园 Telnet协议详解及使用C# 用Socket 编程来实现Telnet协议 - Atpkin ...
- mavlink协议详解_MAVLink学习之路05_ MAVLink应用编程接口分析
描述 MAVLink应用编程主要内容 2.1 发送和接收说明 利用MAVLink通信协议进行编程,主要实现的功能就是: 1.发送端 将需要发送的数据(如:SysState, BatVol),添加MAV ...
- PPI协议详解 ppi通讯协议 ppi通信协议 vb与ppi协议通讯
转自:http://blog.csdn.net/vbvcde/article/details/7660497 我们提供 PPI协议的官方文档,协议更新时间为2005年,下面是我们根据文档解析的PPI读 ...
- Telnet协议详解及使用C# 用Socket 编程来实现Telnet协议
同步发行到atpking.com...... 这因为有个任务涉及到使用telnet 来连接远端的路由器,获取信息,之后进行处理. 所以需要写一个自动telnet登录到远端,之后获取信息进行处理的程序. ...
- HTTP协议详解(文档)
目录 引言............................................................................................... ...
- USB协议详解第0讲(系列博文介绍)
目录 1.课程目标 2.简单介绍 3.课程大纲 1.课程目标 USB协议详解旨在为大家通俗理解USB通讯协议,我会带着大家一步一步理解USB通讯中的各种概念及通讯方式,并且会借助于USB Protoc ...
- TCP/IP协议详解、TCP三次握手
TCP/IP协议详解:TCP/IP协议详解_王佳斌-CSDN博客_tcp/ip协议认识HTTP协议它是互联网协议(Internet Protocol Suite),一个网络通信模型,是互联网的一个基本 ...
- mavlink协议详解_无人机通信协议mavlink资料汇总
[实例简介] 无人机通信协议mavlink资料汇总 [实例截图] [核心代码] f83d116b-73ea-4f18-8c1e-cae95b88b8c0 └── MAVLINK协议资料 ├── ASP ...
最新文章
- 从PHP5到PHP7自我封装MongoDB以及平滑升级
- java基础之XML
- python读取usb扫码枪数据_vue扫码枪input接收数据
- 巧用「打印」功能实现PDF单页提取
- 【VS2010学习笔记】【编程实例】 (在Visual Studio中使用C++创建和使用DLL)
- Atitit eclipse新特性总结3.1---4.4 4.5
- centos7 端口3306无法连接问题
- 网站盗取html文件工具,一键获取仿站精灵
- Unity WebGL 修改鼠标指针
- 计算机桌面设置上时间表,如何在电脑桌面设置显示星期
- iText PDF操作(查找关键字、插入图片)
- 万字讲解WiFi为何物
- vue 修改标题栏_在Vue中如何实现动态修改页面title
- 按国家归类的海淘网站大全
- ffmpeg命令录制windows音视频
- Arduino常用的附加开发版管理器网址
- RN8215芯片 32768Hz晶体停振案例分析
- webrtc服务器janus的一点看法
- SpringBoot整合Redis配置MyBatis二级缓存
- ESA SNAP工具包Java接口的使用
热门文章
- Spring Cloud Discovery——Consul Discovery
- java爬虫知识盲区整理
- Studio One6中文语言版DAW数字音频音乐创作软件
- 谷歌浏览器手势操作_[经验分享] 2步操作永久提升谷歌Chrome浏览器默认下载速度...
- 解决vi中文乱码问题
- CASE工具 ——软件产业危机的解决方案
- [篇四章一]_在 VMWare 16 上安装 Windows 98 SE 操作系统
- vivo手机怎么恢复信息_vivo手机短信删除了怎么恢复-互盾安卓恢复大师
- 网上银行转账是怎么测的,设计一下测试用例。
- JavaScript实战之简单的抽签器