天天看點

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檔案自動生成的。

mavlink協定詳解_MAVLink通訊協定在STM32上移植,并自定義協定

image.png

從官網下載下傳MAVLink的源碼後,可以得知定義通信協定的xml檔案位于message_definitions/v1.0/下面,其中參考文章3、4和5就利用的common.xml進行自動生成的。

mavlink協定詳解_MAVLink通訊協定在STM32上移植,并自定義協定

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

mavlink協定詳解_MAVLink通訊協定在STM32上移植,并自定義協定

image.png

XML選擇message_definitions/v1.0/下已經定義好的檔案Out随便選擇一個空檔案夾

點選Generate即可在out檔案夾内生成所需要的通訊檔案,全部都是.h檔案,其中帶有一個pressure檔案夾,這個檔案夾的名字和你XML的名字是一樣的

mavlink協定詳解_MAVLink通訊協定在STM32上移植,并自定義協定

image.png

mavlink協定詳解_MAVLink通訊協定在STM32上移植,并自定義協定

image.png

pressure檔案夾内的檔案是針對pressure這一種message專門生成的,pressure外面檔案夾内的檔案是較為通用的檔案,但是每個協定xml不同,生成的内容也不一樣。

修改檔案避免報錯

在移植到keil5中,需要修改的主要以下幾處,否則會報大量的錯誤。

mavlink_types.h,

mavlink協定詳解_MAVLink通訊協定在STM32上移植,并自定義協定

image.png

mavlink_types.h

mavlink協定詳解_MAVLink通訊協定在STM32上移植,并自定義協定

image.png

checksum.h

mavlink協定詳解_MAVLink通訊協定在STM32上移植,并自定義協定

image.png

mavlink_conversions.h

mavlink協定詳解_MAVLink通訊協定在STM32上移植,并自定義協定

image.png

mavlink協定詳解_MAVLink通訊協定在STM32上移植,并自定義協定

image.png

mavlink_helpers.h

mavlink協定詳解_MAVLink通訊協定在STM32上移植,并自定義協定

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

mavlink協定詳解_MAVLink通訊協定在STM32上移植,并自定義協定

image.png

解決辦法:

mavlink協定詳解_MAVLink通訊協定在STM32上移植,并自定義協定

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

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];

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;

}

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;

}

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;

}

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;

}

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;

}

uint8_t serial_read_ch(void){

uint8_t ch;

fifo_read_ch(&uart_rx_fifo, &ch);

return ch;

}

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);

}

}

}