套件介紹
Robomaster中遙控機器人的手段是固定的,只能使用大疆提供的DR16&DT7套件進行操控數據的發送和接受。這個套件的手冊可以在Robomaster的官網上下載到,里面有詳細的說明,以及官方給的解碼demo。
https://www.robomaster.com/zh-CN/products/components/detail/122
操控數據可以通過DT7遙控器直接發送,也可以將遙控器通過數據線連接到電腦上,然后打開Robomaster客戶端操作界面,遙控器會將客戶端內的鍵鼠操作發送給接收器。
使用DT7&DR16套件前需要保證接收機和遙控器之間已經成功配對。
DR16上的LED指示燈一般有三種狀態:紅燈常亮,綠燈閃爍,綠燈常亮,對應的狀態如下:
指示燈狀態 | 對應狀態 |
---|---|
紅燈常亮 | 未檢測到遙控器 |
綠燈閃爍 | 檢測到遙控器,但未配對 |
綠燈常亮 | 已與遙控器配對 |
配對方法為打開需要配對的遙控器和接收機,長按對頻按鍵10s左右,然后松開。
套件參數如下,通信距離足足可以達到1km,實際使用中也確實很穩,DJI還是牛逼的。但是需要注意的是DT7遙控器很不耐摔,天線也容易斷,切記要保護好。
DBUS協議
接收機與接收機之間采用DBUS協議進行通信,這個DBUS協議和常用的SBUS協議應該是一樣的東西,不知道為什么要改個名。
信號電平為TTL電平,但是和UART是反相的,需要過一個反相器再輸入到單片機的串口上進行接收。這個反相器可以自己搭,一個三極管加兩個電阻就可以了。
接收時對照着參數表進行串口的參數設置。
當然也可以直接連接Robomaster的開發板上專門留給DR16接收機的接口,一般是串口1,直接將接收機上引出的線對應連接即可,接口內置了反相器。DBUS與UART之間電平標准為反相關系,所以不能隨意連接到其他串口。
數據幀解析
下面開始說重頭戲——遙控器發送給接收機的數據幀。在實際使用時,我們需要將接收到的數據流進行解碼,將接收到的數據流“翻譯”成對應的遙控器/鍵盤/鼠標的數值。DBUS數據以18個字節為一幀.
可以將接收機通過反相器,接給USB轉TTL模塊,然后接到PC上,開串口助手調成16進制顯示看一下。
一個典型的數據幀長下面這樣
00 04 20 00 01 78 00 00 00 00 00 00 00 00 00 00 00 00
將其對應到官方給的手冊上,很容易就可以理解數據幀的含義。
以通道0的數值為例,將上面的數據幀的前兩個字節寫成二進制的形式,得到
0000 0000 0000 0010
按照規則,將第二個字節的后3位作為高八位,第一個字節整體作為低8位拼接起來,即可得到
010 0000 0000
這個二進制數轉化成十進制后,得到的值恰好就是2的10次方,即1024。
通過同樣的方式可以分離-拼接出所有的數據值,實際上我們的遙控器解碼函數所作的也正是這個工作。
DMA接收&&解碼示例
這里以官方開源代碼為例來講解遙控器接收+解碼的過程。
https://github.com/RoboMaster/RoboRTS-Firmware
在dbus.c下面,我們可以看到官方的解碼函數,其完成的工作包括——數據的分離和拼接;為了防止遙控器數據的零漂,設置了一個正負5的死區;數據溢出的處理
static void get_dr16_data(rc_device_t rc_dev, uint8_t *buff)
{
memcpy(&(rc_dev->last_rc_info), &rc_dev->rc_info, sizeof(struct rc_info));
rc_info_t rc = &rc_dev->rc_info;
//satori:這里完成的是數據的分離和拼接,減去1024是為了讓數據的中間值變為0
rc->ch1 = (buff[0] | buff[1] << 8) & 0x07FF;
rc->ch1 -= 1024;
rc->ch2 = (buff[1] >> 3 | buff[2] << 5) & 0x07FF;
rc->ch2 -= 1024;
rc->ch3 = (buff[2] >> 6 | buff[3] << 2 | buff[4] << 10) & 0x07FF;
rc->ch3 -= 1024;
rc->ch4 = (buff[4] >> 1 | buff[5] << 7) & 0x07FF;
rc->ch4 -= 1024;
//satori:防止數據零漂,設置正負5的死區
/* prevent remote control zero deviation */
if(rc->ch1 <= 5 && rc->ch1 >= -5)
rc->ch1 = 0;
if(rc->ch2 <= 5 && rc->ch2 >= -5)
rc->ch2 = 0;
if(rc->ch3 <= 5 && rc->ch3 >= -5)
rc->ch3 = 0;
if(rc->ch4 <= 5 && rc->ch4 >= -5)
rc->ch4 = 0;
rc->sw1 = ((buff[5] >> 4) & 0x000C) >> 2;
rc->sw2 = (buff[5] >> 4) & 0x0003;
//satori:防止數據溢出
if ((abs(rc->ch1) > 660) || \
(abs(rc->ch2) > 660) || \
(abs(rc->ch3) > 660) || \
(abs(rc->ch4) > 660))
{
memset(rc, 0, sizeof(struct rc_info));
return ;
}
rc->mouse.x = buff[6] | (buff[7] << 8); // x axis
rc->mouse.y = buff[8] | (buff[9] << 8);
rc->mouse.z = buff[10] | (buff[11] << 8);
rc->mouse.l = buff[12];
rc->mouse.r = buff[13];
rc->kb.key_code = buff[14] | buff[15] << 8; // key borad code
rc->wheel = (buff[16] | buff[17] << 8) - 1024;
}
找到對應的數據結構rc_info,其內容和手冊上的內容完全一一對應,可以看到官方代碼為了方便后續代碼的編寫,在鍵盤的數據處使用了聯合體。
struct rc_info
{
/* rocker channel information */
int16_t ch1;
int16_t ch2;
int16_t ch3;
int16_t ch4;
/* left and right lever information */
uint8_t sw1;
uint8_t sw2;
/* mouse movement and button information */
struct
{
int16_t x;
int16_t y;
int16_t z;
uint8_t l;
uint8_t r;
} mouse;
/* keyboard key information */
union {
uint16_t key_code;
struct
{
uint16_t W : 1;
uint16_t S : 1;
uint16_t A : 1;
uint16_t D : 1;
uint16_t SHIFT : 1;
uint16_t CTRL : 1;
uint16_t Q : 1;
uint16_t E : 1;
uint16_t R : 1;
uint16_t F : 1;
uint16_t G : 1;
uint16_t Z : 1;
uint16_t X : 1;
uint16_t C : 1;
uint16_t V : 1;
uint16_t B : 1;
} bit;
} kb;
int16_t wheel;
};
關於遙控器數據的接收,我們可以直接去尋找解碼函數在何處被調用,按照
get_dr16_data -> rc_device_date_update -> dr16_rx_data_by_uart -> dr16_rx_callback -> dr16_uart_rx_data_handle -> USART1_IRQHandler
可以發現其是在串口1的接收中斷中被調用的,DMA初始化函數如下
void dr16_uart_init(void)
{
UART_Receive_DMA_No_IT(&huart1, dr16_uart_rx_buff, DR16_RX_BUFFER_SIZE);
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
}
接收處理函數如下
拋除一些HAL庫為了驅動DMA進行的一些寄存器操作,其實我們可以看到整個DMA接收邏輯實際上還是很簡單的,即通過dr16_uart_rx_buff這個數組存儲DMA獲取的數據,每次完成接收之后對接收數據的長度進行判斷,如果確認了是18個字節則判定為合法數據,傳入dr16_rx_callback進行下一步的處理。
雖然官方的代碼為了各種目的進行了很多層的封裝,但是實際上在dma接收處理函數中進行數據合法性的判斷之后,已經可以直接將數據送入解碼函數了。
uint32_t dr16_uart_rx_data_handle(UART_HandleTypeDef *huart)
{
if (__HAL_UART_GET_FLAG(huart, UART_FLAG_IDLE))
{
/* clear idle it flag avoid idle interrupt all the time */
__HAL_UART_CLEAR_IDLEFLAG(huart);
/* clear DMA transfer complete flag */
__HAL_DMA_DISABLE(huart->hdmarx);
/* handle dbus data dbus_buf from DMA */
if ((DR16_RX_BUFFER_SIZE - huart->hdmarx->Instance->NDTR) == DR16_DATA_LEN)
{
if (dr16_rx_callback != NULL)
{
dr16_rx_callback(dr16_uart_rx_buff, DR16_DATA_LEN);
}
if (dr16_forword_callback != NULL)
{
dr16_forword_callback(dr16_uart_rx_buff, DR16_DATA_LEN);
}
}
/* restart dma transmission */
__HAL_DMA_SET_COUNTER(huart->hdmarx, DR16_RX_BUFFER_SIZE);
__HAL_DMA_ENABLE(huart->hdmarx);
}
return 0;
}
結語
那么本講就到此為之了,實際上DR16&DT7遙控器套件接收是一個很容易的工作,需要注意的是由於機器人對於任務執行的實時性需求,一般不會采用耗時長,占用資源多的串口中斷的方式進行接收,而是使用DMA方式進行接收。