本文共 10137 字,大约阅读时间需要 33 分钟。
这篇还写了在STM32上怎么收发数据,调用哪些函数,这个不错。
摘自:
0.1382017.09.12 15:25:40字数 2,016阅读 5,218
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。
参考文章3,可以用Python根据xml文件自动生成mavlink通信所需的文件。
python -m mavgenerate
弹出下图所示 MAVink Generator
image.png
image.png
image.png
pressure文件夹内的文件是针对pressure这一种message专门生成的,pressure外面文件夹内的文件是较为通用的文件,但是每个协议xml不同,生成的内容也不一样。
在移植到keil5中,需要修改的主要以下几处,否则会报大量的错误。
image.png
image.png
image.png
image.png
image.png
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中,我们最需要关心两个问题
发送信息的大致流程代码为:
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在接收信息时,也需要两步走:接收信息的处理大致流程为:
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); } } }
具体到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 120typedef 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); } } }
转载地址:http://ealni.baihongyu.com/