1. SBUS信号简介
最近在搞一个项目的通信和控制,用到了SBUS,记录一下心得。
SBUS全称serial-bus,是一种串口通信协议,广泛应用于航模遥控器(接收机)中。只用一根信号线就能传输多达16通道的数据,比多路PWM捕获高效且省资源。
- 串口配置:
100k波特率,8位数据位,2位停止位,偶校验(EVEN),无控流,25个字节。 - 协议格式:(8字节)
[startbyte] [data1][data2]…[data22][flags][endbyte]
startbyte=0x0f;
endbyte=0x00;
data1…data22: LSB(低位在前),对应16个通道(ch1-ch16),每个通道11bit(22 × 8=16 × 11);
flag位标志遥控器的通讯状态,我使用的乐迪AT9S在遥控器通上的时候是0x00,断开的时候是0xC0,可以通过查询flag位来采取失控保护。 - 数据范围
航模遥控器输出的PWM值是1000~2000,中值为1500,sbus输出的会不一样,例如乐迪AT9S的范围为300 ~ 1700,中值1000,这个我估计跟遥控器厂家有关。 - sbus的负逻辑
这个地方一定要万分注意,必须加硬件反相器,因为SBUS的信号是采用的负逻辑,也就是电平相反,不要试图在软件里面取反,因为软件里面只能操作数据位(记得串口配置里面的数据位8么),你是操作不了停止位、校验位啥的!!
如果是自己画板子也很简单,如图所示
5. 数据读取
一般的串口调试助手可能没有100K波特率的选项,推荐一个串口调试助手MicroLab,可以自定义串口波特率,还有其他好功能自己探索叭。
2. STM32F7解析SBUS信号例程
清楚了通信协议,解析就很简单了。我使用的是正点原子的阿波罗F7开发板,其他的板子是一样的。
(1) 串口配置
首先一些变量声明,串口uart.c里用到的
#define USART_REC_LEN 100 //定义最大接收字节数 200
#define RXBUFFERSIZE 1 //缓存大小
u8 USART1_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.
u16 USART1_RX_STA = 0; //接收状态标记
u8 aRxBuffer1[RXBUFFERSIZE]; //HAL库使用的串口接收缓冲
UART_HandleTypeDef UART1_Handler; //UART句柄
串口初始化函数
void uart1_init(u32 bound)
{
//UART 初始化设置
UART1_Handler.Instance = USART1; //USART1
UART1_Handler.Init.BaudRate = bound; //波特率
UART1_Handler.Init.WordLength = UART_WORDLENGTH_9B; //字长为8位数据格式
UART1_Handler.Init.StopBits = UART_STOPBITS_1; //一个停止位
UART1_Handler.Init.Parity = UART_PARITY_EVEN; //无奇偶校验位
UART1_Handler.Init.HwFlowCtl = UART_HWCONTROL_NONE; //无硬件流控
UART1_Handler.Init.Mode = UART_MODE_TX_RX; //收发模式
HAL_UART_Init(&UART1_Handler); //HAL_UART_Init()会使能UART1
HAL_UART_Receive_IT(&UART1_Handler, (u8 *)aRxBuffer1, RXBUFFERSIZE); //该函数会开启接收中断:标志位UART_IT_RXNE,并且设置接收缓冲以及接收缓冲接收最大数据量
}
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
//GPIO端口设置
GPIO_InitTypeDef GPIO_Initure;
if (huart->Instance == USART1) //如果是串口1,进行串口1 MSP初始化
{
__HAL_RCC_GPIOA_CLK_ENABLE(); //使能GPIOA时钟
__HAL_RCC_USART1_CLK_ENABLE(); //使能USART1时钟
GPIO_Initure.Pin = GPIO_PIN_9; //PA9
GPIO_Initure.Mode = GPIO_MODE_AF_PP; //复用推挽输出
GPIO_Initure.Pull = GPIO_PULLUP; //上拉
GPIO_Initure.Speed = GPIO_SPEED_FAST; //高速
GPIO_Initure.Alternate = GPIO_AF7_USART1; //复用为USART1
HAL_GPIO_Init(GPIOA, &GPIO_Initure); //初始化PA9
GPIO_Initure.Pin = GPIO_PIN_10; //PA10
HAL_GPIO_Init(GPIOA, &GPIO_Initure); //初始化PA10
#if EN_USART1_RX
HAL_NVIC_EnableIRQ(USART1_IRQn); //使能USART1中断通道
HAL_NVIC_SetPriority(USART1_IRQn, 3, 2); //抢占优先级3,子优先级3
#endif
}
}
这里有个诡异的地方就是stm32要设置成9个数据位,一个停止位,我一开始按照8个数据位、两个停止位读出来的数据是错的,后来改了之后才正常了。是不是和stm32内部的串口配置有关,哪位大神弄明白了告诉我哈。
(2) 串口中断接收
串口中断函数,在中断函数里面接收数据,进行SBUS信号解析。
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
int i;
while (huart->Instance == USART1) //如果是串口1
{
USART1_RX_BUF[USART1_RX_STA] = aRxBuffer1[0];
if (USART1_RX_STA == 0 && USART1_RX_BUF[USART1_RX_STA] != 0x0F) break; //帧头不对,丢掉
USART1_RX_STA++;
if (USART1_RX_STA > USART_REC_LEN) USART1_RX_STA = 0; ///接收数据错误,重新开始接收
if (USART1_RX_BUF[0] == 0x0F && USART1_RX_BUF[24] == 0x00 && USART1_RX_STA == 25) //接受完一帧数据
{
update_sbus(USART1_RX_BUF);
for (i = 0; i<25; i++) //清空缓存区
USART1_RX_BUF[i] = 0;
USART1_RX_STA = 0;
}
break;
}
}
(3) 信号解析
上面中断函数里面有一个update_sbus函数,原型为u8 update_sbus(u8 *buf)
,解析subs信号全靠它了!!新建一个sbus.c文件,输入如下代码
#include "sbus.h"
SBUS_CH_Struct SBUS_CH;
//将sbus信号转化为通道值
u8 update_sbus(u8 *buf)
{
int i;
if (buf[23] == 0)
{
SBUS_CH.ConnectState = 1;
SBUS_CH.CH1 = ((int16_t)buf[ 1] >> 0 | ((int16_t)buf[ 2] << 8 )) & 0x07FF;
SBUS_CH.CH2 = ((int16_t)buf[ 2] >> 3 | ((int16_t)buf[ 3] << 5 )) & 0x07FF;
SBUS_CH.CH3 = ((int16_t)buf[ 3] >> 6 | ((int16_t)buf[ 4] << 2 ) | (int16_t)buf[ 5] << 10 ) & 0x07FF;
SBUS_CH.CH4 = ((int16_t)buf[ 5] >> 1 | ((int16_t)buf[ 6] << 7 )) & 0x07FF;
SBUS_CH.CH5 = ((int16_t)buf[ 6] >> 4 | ((int16_t)buf[ 7] << 4 )) & 0x07FF;
SBUS_CH.CH6 = ((int16_t)buf[ 7] >> 7 | ((int16_t)buf[ 8] << 1 ) | (int16_t)buf[9] << 9 ) & 0x07FF;
SBUS_CH.CH7 = ((int16_t)buf[ 9] >> 2 | ((int16_t)buf[10] << 6 )) & 0x07FF;
SBUS_CH.CH8 = ((int16_t)buf[10] >> 5 | ((int16_t)buf[11] << 3 )) & 0x07FF;
SBUS_CH.CH9 = ((int16_t)buf[12] << 0 | ((int16_t)buf[13] << 8 )) & 0x07FF;
SBUS_CH.CH10 = ((int16_t)buf[13] >> 3 | ((int16_t)buf[14] << 5 )) & 0x07FF;
SBUS_CH.CH11 = ((int16_t)buf[14] >> 6 | ((int16_t)buf[15] << 2 ) | (int16_t)buf[16] << 10 ) & 0x07FF;
SBUS_CH.CH12 = ((int16_t)buf[16] >> 1 | ((int16_t)buf[17] << 7 )) & 0x07FF;
SBUS_CH.CH13 = ((int16_t)buf[17] >> 4 | ((int16_t)buf[18] << 4 )) & 0x07FF;
SBUS_CH.CH14 = ((int16_t)buf[18] >> 7 | ((int16_t)buf[19] << 1 ) | (int16_t)buf[20] << 9 ) & 0x07FF;
SBUS_CH.CH15 = ((int16_t)buf[20] >> 2 | ((int16_t)buf[21] << 6 )) & 0x07FF;
SBUS_CH.CH16 = ((int16_t)buf[21] >> 5 | ((int16_t)buf[22] << 3 )) & 0x07FF;
return 1;
}
else
{
SBUS_CH.ConnectState = 0;
return 0;
}
}
u16 sbus_to_pwm(u16 sbus_value)
{
float pwm;
pwm = (float)SBUS_TARGET_MIN + (float)(sbus_value - SBUS_RANGE_MIN) * SBUS_SCALE_FACTOR;
// 1000 300 1000/1400
if (pwm > 2000) pwm = 2000;
if (pwm < 1000) pwm = 1000;
return (u16)pwm;
}
上面定义了一个SBUS_CH_Struct 结构体类型的变量SBUS_CH,该结构体在sbus.h中定义
typedef struct
{
uint16_t CH1;//通道1数值
uint16_t CH2;//通道2数值
uint16_t CH3;//通道3数值
uint16_t CH4;//通道4数值
uint16_t CH5;//通道5数值
uint16_t CH6;//通道6数值
uint16_t CH7;//通道7数值
uint16_t CH8;//通道8数值
uint16_t CH9;//通道9数值
uint16_t CH10;//通道10数值
uint16_t CH11;//通道11数值
uint16_t CH12;//通道12数值
uint16_t CH13;//通道13数值
uint16_t CH14;//通道14数值
uint16_t CH15;//通道15数值
uint16_t CH16;//通道16数值
uint8_t ConnectState;//遥控器与接收器连接状态 0=未连接,1=正常连接
}SBUS_CH_Struct;
u16 sbus_to_pwm(u16 sbus_value)
很好理解了,就是把sbus的值转化为标准的1000-2000的pwm值,因为我用的遥控器sbus值是300-1700,大家用的时候具体数值到时候可以通过串口直接读出来看看。
这样就读出了16个通道的数据啦。同时通过读取ConnectState位判断遥控器的状态,在主函数中采取失控保护。
上面这段解析数据的代码是国际通用的,可以用在任何使用sbus协议的场合,可以很方便的移植到arduino、51、树莓派上面。
最后main函数里面就很简单了,只注意初始化串口设置为100K波特率。
void main()
{
/* 省略 */
uart1_init(100000);
/* 省略 */
}
本文代码已上传至CSDN,独乐乐不如众乐乐,提供免费下载,点击下载,欢迎交流讨论。别忘了点个赞哦!