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的代码如下所示:

<?xml version="1.0"?>
<mavlink>
  <version>3</version>
  <messages>
    <message id="0" name="TEST_TYPES">
      <description>Test all field types</description>
      <field type="char" name="c">char</field>
      <field type="char[10]" name="s">string</field>
      <field type="uint8_t" name="u8">uint8_t</field>
      <field type="uint16_t" name="u16">uint16_t</field>
      <field print_format="0x%08x" type="uint32_t" name="u32">uint32_t</field>
      <field type="uint64_t" name="u64">uint64_t</field>
      <field type="int8_t" name="s8">int8_t</field>
      <field type="int16_t" name="s16">int16_t</field>
      <field type="int32_t" name="s32">int32_t</field>
      <field type="int64_t" name="s64">int64_t</field>
      <field type="float" name="f">float</field>
      <field type="double" name="d">double</field>
      <field type="uint8_t[3]" name="u8_array">uint8_t_array</field>
      <field type="uint16_t[3]" name="u16_array">uint16_t_array</field>
      <field type="uint32_t[3]" name="u32_array">uint32_t_array</field>
      <field type="uint64_t[3]" name="u64_array">uint64_t_array</field>
      <field type="int8_t[3]" name="s8_array">int8_t_array</field>
      <field type="int16_t[3]" name="s16_array">int16_t_array</field>
      <field type="int32_t[3]" name="s32_array">int32_t_array</field>
      <field type="int64_t[3]" name="s64_array">int64_t_array</field>
      <field type="float[3]" name="f_array">float_array</field>
      <field type="double[3]" name="d_array">double_array</field>
    </message>
  </messages>
</mavlink>

里面的定义比较清晰,参考前面1、2文章,相信大多数人是很容易看懂是什么意思的,此处不再赘述。
我们定义我们发送的数据叫pressure,里面只包含一个double型的变量,名叫PP(此处也可以定义更多变量),其定义xml如下:

<?xml version="1.0"?>
<mavlink>
  <version>3</version>
  <messages>
    <message id="0" name="pressure">
      <description>Test all field types</description>
      <field type="double" name="PP">double</field>
    </message>
  </messages>
</mavlink>

message id为0的情况在无人机通信协议中一般代指heartbeat,这里我们直接忽略,就命其为pressure。可以理解为pressure就类似结构体的名字,PP就是里面的成员变量的名字,类型是double。

生成mavlink通信协议的文件

参考文章3,可以用Python根据xml文件自动生成mavlink通信所需的文件。

    1. 在mavlink文件夹内执行
python -m mavgenerate

弹出下图所示 MAVink Generator

image.png
    1. XML选择message_definitions/v1.0/下已经定义好的文件Out随便选择一个空文件夹
    1. 点击Generate即可在out文件夹内生成所需要的通讯文件,全部都是.h文件,其中带有一个pressure文件夹,这个文件夹的名字和你XML的名字是一样的
image.png
image.png

pressure文件夹内的文件是针对pressure这一种message专门生成的,pressure外面文件夹内的文件是较为通用的文件,但是每个协议xml不同,生成的内容也不一样。

修改文件避免报错

在移植到keil5中,需要修改的主要以下几处,否则会报大量的错误。

    1. mavlink_types.h,
image.png
    1. mavlink_types.h
image.png
    1. checksum.h
image.png
    1. mavlink_conversions.h
image.png
image.png
    1. mavlink_helpers.h

image.png

至此,在keil5中编译mavlink.h开头的文件都不会有错了,使用时直接包含mavlink.h即可。
在我们使用中,pressure外面文件夹内的文件定义了上层的通信接口,每次生成都是一样的(比如在pressure内再添加一个成员变量时),pressure文件夹内的文件是根据xml文件来的,如果再添加一条attitude信息,则会根据attitude的定义,生成一个对应的文件夹,因此修改好外面这几个错误,可以直接拷贝使用,不用每次换个协议就重新修改使用。

..\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; i<length; i++) fifo->buf[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);
        }
  }     
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容

  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,800评论 6 13
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,497评论 18 139
  • http://qgroundcontrol.org/mavlink/start mavlink协议介绍https...
    Theshy阅读 34,485评论 6 37
  • 销售是一门人与人之间相互沟通的说服艺术,销售人员能否在与顾客一来一往的交流沟通中,打开顾客的沟通阀门,让顾客愿意并...
    原中劲法120阅读 643评论 0 0
  • 我与你的距离 文/深沉等笑 我与你的距离 是星星与月亮的距离 虽然好似很近 却是相隔万里 我与你的距离 是地球与太...
    深沉等笑阅读 446评论 2 1