S-BUS协议及其衍生协议
S.BUS协议是一个串行通信协议,是由FUTABA提出的舵机控制总线。
S-BUS使用RS232C串口的硬件协议作为自己的硬件运行基础。使用TTL电平,即3.3V。使用负逻辑,即低电平为”1“,高电平为”0“。波特率为100000(100k),不兼容115200。
对于串口相关知识,欲知RS232,RS485等内容,详见 电控 | UART串口通信 。
硬件电路
Robomaster的C板和A板都有D-BUS电路,根据 无人机 | A3 PRO 外设进阶 可以知道,D-BUS和S-BUS实际上可以理解为是相同的。(注:好像发现了不同,详见下文)

这其实是一个非常简单的三极管电路,具备一个取反功能。S-BUS信号从基极(B)输入,从集电极(C)输出。基极输入‘0’,集电极上拉输出‘1’;基极输入’1‘,三极管被导通,则输出被下拉为’0‘,实现取反。
UART3_RX即为串口的输出端。
关于三极管相关知识,详见 电控 | 基础理论详解——三极管 。
通信协议
S-BUS拥有很简洁的协议帧,一帧包括25byte的数据:
首部1byte + 数据22byte + 标志位1byte + 结束符1byte
上面即为协议帧的格式。具体协议规则如下:
协议解析
通道解析表
将数据解析为通道的方法如下:
16通道 |
解析方法 |
通道0 |
byte[1] >> 0 | byte[2] << 8 & 0x7ff |
通道1 |
byte[2] >> 3 | byte[3] << 5 & 0x7ff |
通道2 |
byte[3] >> 6 | byte[4] << 2 | byte[5] << 10 & 0x7ff |
通道3 |
byte[5] >> 1 | byte[6] << 7 & 0x7ff |
通道4 |
byte[6] >> 4 | byte[7] << 4 & 0x7ff |
通道5 |
byte[7] >> 7 | byte[8] << 1 | byte[9] << 9 & 0x7ff |
通道6 |
byte[9] >> 2 | byte[10] << 6 & 0x7ff |
通道7 |
byte[10] >> 5 | byte[11] << 3 & 0x7ff |
通道8 |
byte[12] >> 0 | byte[13] << 8 & 0x7ff |
通道9 |
byte[13] >> 3 | byte[14] << 5 & 0x7ff |
通道10 |
byte[14] >> 6 | byte[15] << 2 | byte[16] << 10 & 0x7ff |
通道11 |
byte[16] >> 1 | byte[17] << 7 & 0x7ff |
通道12 |
byte[17] >> 4 | byte[18] << 4 & 0x7ff |
通道13 |
byte[18] >> 7 | byte[19] << 1 | byte[20] << 9 & 0x7ff |
通道14 |
byte[20] >> 2 | byte[21] << 6 & 0x7ff |
通道15 |
byte[21] >> 5 | byte[22] << 3 & 0x7ff |
解析逻辑
由上文可知,每个通道数据为11字节。但是我们在发送的时候,只能16字节16字节的发送。为了节约空间,提高传输效率,减少时延,在发送和接收的时候必须要把空出来的5位数补齐。
因此,在接收到信号之后,进行解码,就要把补齐后首尾相连的内容分离出来。这就叫做数据的解析。
0x07FF
十六进制的0x07FF是十进制中的2047,其二进制表示为11位全为1的掩码。通过&
来操作,以确保每个通道值不会超过11位。
byte[1] >> 0 | byte[2] << 8
假设,这些数据都是8位的(通常而言,都是8位的)
byte[1]的最低8位直接使用,无需移动。
byte[2]的8位左移8位
将两者相或,再与上0x07FF,就是我们要的Ch[0]的数据。
具体,我们可以举个例子。
让我们做一个假设:
1 2 3 4
| buf[1] = 0101 0110 buf[2] = 1010 1001 buf[3] = 0111 1010 buf[4] = 1011 1110
|
现在,我们来对上面的代码进行运算。
buf[1] >> 0
:buf[1] = 000 0101 0110
buf[2] << 8
:buf[2] = 101 0100 1000
buf[1] | buf[2]
:Ch[0] = 001 0101 0110
此时,Ch[0]已经解析完成。
让我们继续:
buf[2] >> 3
:buf[2] = 000 0001 0101
buf[3] << 5
:buf[3] = 111 0100 0000
buf[2] | buf[3]
:Ch[1] = 111 0101 0101
通过对比Ch[1]和Ch[0]两个数据,我们将他们写在一起:
111 0101 0101 001 0101 0110
再将他们每八位一划分:
11 1010 | 1010 1001 | 0101 0110
可以发现,这就是buf[3]一部分 + buf[2] + buf[1]
其解码的数理逻辑,如上所言。
解析代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| void Sbus_Data_Count(uint8_t *buf) { CH[ 0] = ((int16_t)buf[ 1] >> 0 | ((int16_t)buf[ 2] << 8 )) & 0x07FF; CH[ 1] = ((int16_t)buf[ 2] >> 3 | ((int16_t)buf[ 3] << 5 )) & 0x07FF; CH[ 2] = ((int16_t)buf[ 3] >> 6 | ((int16_t)buf[ 4] << 2 ) | (int16_t)buf[ 5] << 10 ) & 0x07FF; CH[ 3] = ((int16_t)buf[ 5] >> 1 | ((int16_t)buf[ 6] << 7 )) & 0x07FF; CH[ 4] = ((int16_t)buf[ 6] >> 4 | ((int16_t)buf[ 7] << 4 )) & 0x07FF; CH[ 5] = ((int16_t)buf[ 7] >> 7 | ((int16_t)buf[ 8] << 1 ) | (int16_t)buf[ 9] << 9 ) & 0x07FF; CH[ 6] = ((int16_t)buf[ 9] >> 2 | ((int16_t)buf[10] << 6 )) & 0x07FF; CH[ 7] = ((int16_t)buf[10] >> 5 | ((int16_t)buf[11] << 3 )) & 0x07FF;
CH[ 8] = ((int16_t)buf[12] << 0 | ((int16_t)buf[13] << 8 )) & 0x07FF; CH[ 9] = ((int16_t)buf[13] >> 3 | ((int16_t)buf[14] << 5 )) & 0x07FF; CH[10] = ((int16_t)buf[14] >> 6 | ((int16_t)buf[15] << 2 ) | (int16_t)buf[16] << 10 ) & 0x07FF; CH[11] = ((int16_t)buf[16] >> 1 | ((int16_t)buf[17] << 7 )) & 0x07FF; CH[12] = ((int16_t)buf[17] >> 4 | ((int16_t)buf[18] << 4 )) & 0x07FF; CH[13] = ((int16_t)buf[18] >> 7 | ((int16_t)buf[19] << 1 ) | (int16_t)buf[20] << 9 ) & 0x07FF; CH[14] = ((int16_t)buf[20] >> 2 | ((int16_t)buf[21] << 6 )) & 0x07FF; CH[15] = ((int16_t)buf[21] >> 5 | ((int16_t)buf[22] << 3 )) & 0x07FF; }
|
完整代码(基于HAL库)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #ifndef SBUS_H #define SBUS_H
#include "sys.h"
#define USART_BUF_SIZE 8 #define SBUS_DATA_SIZE 25
#define SBUS_PIN GPIO_PIN_2 | GPIO_PIN_3 #define SBUS_GPIO GPIOA #define SBUS_ENCLK() __HAL_RCC_GPIOA_CLK_ENABLE(); \ __HAL_RCC_USART2_CLK_ENABLE();
struct SBUS_t{ uint8_t head; uint16_t ch[16]; uint8_t flag; uint8_t end; };
void SBUS_Init(void); void SbusParseTask(void *arg);
#endif
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
| #include "sbus.h" #include "delay.h"
uint8_t usart_buf[USART_BUF_SIZE]; uint8_t sbus_rx_head = 0; uint8_t sbus_rx_sta = 0; uint8_t sbus_rx_index; uint8_t sbus_rx_buf[SBUS_DATA_SIZE];
struct SBUS_t sbus;
UART_HandleTypeDef UART2_Handler;
void SBUS_Init(void) { GPIO_InitTypeDef GPIO_Initure;
SBUS_ENCLK();
UART2_Handler.Instance = USART2; UART2_Handler.Init.BaudRate = 100000; UART2_Handler.Init.WordLength = UART_WORDLENGTH_8B; UART2_Handler.Init.StopBits = UART_STOPBITS_2; UART2_Handler.Init.Parity = UART_PARITY_EVEN; UART2_Handler.Init.HwFlowCtl = UART_HWCONTROL_NONE; UART2_Handler.Init.Mode = UART_MODE_TX_RX;
GPIO_Initure.Pin = SBUS_PIN; GPIO_Initure.Mode = GPIO_MODE_AF_PP; GPIO_Initure.Pull = GPIO_PULLUP; GPIO_Initure.Speed = GPIO_SPEED_HIGH; GPIO_Initure.Alternate = GPIO_AF7_USART2; HAL_GPIO_Init(GPIOA, &GPIO_Initure);
HAL_NVIC_EnableIRQ(USART2_IRQn); HAL_NVIC_SetPriority(USART1_IRQn, 3, 4);
HAL_UART_Init(&UART2_Handler); HAL_UART_Receive_IT(&UART2_Handler, (uint8_t *)usart_buf, USART_BUF_SIZE); }
void USART2_IRQHandler(void) { uint8_t chr; if ((__HAL_UART_GET_FLAG(&UART2_Handler, UART_FLAG_RXNE) != RESET)) {
HAL_UART_Receive(&UART2_Handler, &chr, 1, 1000);
if (sbus_rx_sta == 0) { if ((chr == 0x0F) || sbus_rx_head) { sbus_rx_head = 1; if (sbus_rx_index < SBUS_DATA_SIZE) { sbus_rx_buf[sbus_rx_index] = chr; sbus_rx_index ++; } else { sbus_rx_sta = 1; sbus_rx_head = 0; sbus_rx_index = 0; } } } } HAL_UART_IRQHandler(&UART2_Handler); }
void SbusParseTask(void *arg) { while (1) { if(sbus_rx_sta==1) {
NVIC_DisableIRQ(USART2_IRQn);
sbus.head = sbus_rx_buf[0]; sbus.flag = sbus_rx_buf[23]; sbus.end = sbus_rx_buf[24];
sbus.ch[0] =((sbus_rx_buf[2]<<8) + (sbus_rx_buf[1])) & 0x07ff; sbus.ch[1] =((sbus_rx_buf[3]<<5) + (sbus_rx_buf[2]>>3)) & 0x07ff; sbus.ch[2] =((sbus_rx_buf[5]<<10) + (sbus_rx_buf[4]<<2) + (sbus_rx_buf[3]>>6)) & 0x07ff; sbus.ch[3] =((sbus_rx_buf[6]<<7) + (sbus_rx_buf[5]>>1)) & 0x07ff; sbus.ch[4] =((sbus_rx_buf[7]<<4) + (sbus_rx_buf[6]>>4)) & 0x07ff; sbus.ch[5] =((sbus_rx_buf[9]<<9) + (sbus_rx_buf[8]<<1) + (sbus_rx_buf[7]>>7)) & 0x07ff; sbus.ch[6] =((sbus_rx_buf[10]<<6) + (sbus_rx_buf[9]>>2)) & 0x07ff; sbus.ch[7] =((sbus_rx_buf[11]<<3) + (sbus_rx_buf[10]>>5)) & 0x07ff; sbus.ch[8] =((sbus_rx_buf[13]<<8) + sbus_rx_buf[12]) & 0x07ff; sbus.ch[9] =((sbus_rx_buf[14]<<5) + (sbus_rx_buf[13]>>3)) & 0x07ff; sbus.ch[10]=((sbus_rx_buf[16]<<10) + (sbus_rx_buf[15]<<2) + (sbus_rx_buf[14]>>6)) & 0x07ff; sbus.ch[11]=((sbus_rx_buf[17]<<7) + (sbus_rx_buf[16]>>1)) & 0x07ff; sbus.ch[12]=((sbus_rx_buf[18]<<4) + (sbus_rx_buf[17]>>4)) & 0x07ff; sbus.ch[13]=((sbus_rx_buf[20]<<9) + (sbus_rx_buf[19]<<1) + (sbus_rx_buf[18]>>7)) & 0x07ff; sbus.ch[14]=((sbus_rx_buf[21]<<6) + (sbus_rx_buf[20]>>2)) & 0x07ff; sbus.ch[15]=((sbus_rx_buf[22]<<3) + (sbus_rx_buf[21]>>5)) & 0x07ff;
printf("======================================\r\n"); printf("正常: head=0x0F, flag=0x00, end=0x00\r\n\r\n"); printf("head: %d\r\n", sbus.head); printf(" %d, %d, %d, %d\r\n", sbus.ch[0], sbus.ch[1], sbus.ch[2], sbus.ch[3]); printf(" %d, %d, %d, %d\r\n", sbus.ch[4], sbus.ch[5], sbus.ch[6], sbus.ch[7]); printf(" %d, %d, %d, %d\r\n", sbus.ch[8], sbus.ch[9], sbus.ch[10], sbus.ch[11]); printf(" %d, %d, %d, %d\r\n", sbus.ch[12], sbus.ch[13], sbus.ch[14], sbus.ch[15]); printf("flag: %d\r\n", sbus.flag); printf("end: %d\r\n", sbus.end); printf("======================================\r\n\r\n");
delay_ms(500);
NVIC_EnableIRQ(USART2_IRQn); sbus_rx_sta = 0; } else { delay_ms(500); } } }
|
D-BUS协议
前面说道,D-BUS协议和S-BUS协议是一样的。但是,我始终对此存有怀疑。当我打开了Robomaster官方C板教程,寻找D-BUS解析相关代码的时候,就发现了一些问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
static void sbus_to_rc(volatile const uint8_t *sbus_buf, RC_ctrl_t *rc_ctrl) { if (sbus_buf == NULL || rc_ctrl == NULL) { return; }
rc_ctrl->rc.ch[0] = (sbus_buf[0] | (sbus_buf[1] << 8)) & 0x07ff; rc_ctrl->rc.ch[1] = ((sbus_buf[1] >> 3) | (sbus_buf[2] << 5)) & 0x07ff; rc_ctrl->rc.ch[2] = ((sbus_buf[2] >> 6) | (sbus_buf[3] << 2) | (sbus_buf[4] << 10)) &0x07ff; rc_ctrl->rc.ch[3] = ((sbus_buf[4] >> 1) | (sbus_buf[5] << 7)) & 0x07ff; rc_ctrl->rc.s[0] = ((sbus_buf[5] >> 4) & 0x0003); rc_ctrl->rc.s[1] = ((sbus_buf[5] >> 4) & 0x000C) >> 2; rc_ctrl->mouse.x = sbus_buf[6] | (sbus_buf[7] << 8); rc_ctrl->mouse.y = sbus_buf[8] | (sbus_buf[9] << 8); rc_ctrl->mouse.z = sbus_buf[10] | (sbus_buf[11] << 8); rc_ctrl->mouse.press_l = sbus_buf[12]; rc_ctrl->mouse.press_r = sbus_buf[13]; rc_ctrl->key.v = sbus_buf[14] | (sbus_buf[15] << 8); rc_ctrl->rc.ch[4] = sbus_buf[16] | (sbus_buf[17] << 8);
rc_ctrl->rc.ch[0] -= RC_CH_VALUE_OFFSET; rc_ctrl->rc.ch[1] -= RC_CH_VALUE_OFFSET; rc_ctrl->rc.ch[2] -= RC_CH_VALUE_OFFSET; rc_ctrl->rc.ch[3] -= RC_CH_VALUE_OFFSET; rc_ctrl->rc.ch[4] -= RC_CH_VALUE_OFFSET; }
|
这是一段Robomaster官方代码,遥控器DMA通信例程中的D-BUS数据解析函数。我们可以很容易地发现,它居然把buf[1]拉高了8位!
这是什么意思呢?难道协议帧的帧头部分,需要被拉高吗?协议帧的帧头前面说不是固定的0x0f吗?
继续往下看,发现还有蹊跷的地方。所有的buffer,到了第17就结束了。而正常的S-BUS是到了第22个。很显然,代码还有残缺的地方。
从解析的逻辑上看,拉高与拉低的位数相加起来确实都是8,而且在按位与的时候,仍然是0x07ff,说明保留的也还是11位,和S-BUS的解析逻辑是一样的。
因此我们可以断定,D-BUS是S-BUS的残缺版。没有头也没有尾,只接收18个字节数据。
W-BUS 协议
W-BUS和S-BUS几乎没有什么区别,唯一的区别在于数据尾部。结束符被去掉,第25个字节变成了一个不断变化的字节,剩下的所有都是兼容S-BUS的。