目录
1 前言
在自己学习SBUS协议的过程中到处翻资料,看了很多也走了许多弯路,所以把这些记录下来供大家学习。
2 简介
SUBS协议,全称Serial Bus,即串行通信总线。本质上是一种串口通信协议,采用100K的波特率,8位数据位,2位停止位,偶效验的模式
SBUS协议通常使用在用于接收飞控遥控器的接收器上,如果这样使用,需要在硬件电路中加入反相器,进行硬件电路取反,软件取反是没用的
本实验中我们采用两块stm32f1开发板进行发送与接收,所以并不需要取反电路。
3 协议规则
3.1协议格式
一帧数据发送25个字节
总共发送16个通道的数据,每个通道用11位bit表示,即 11bit*16/8 = 22byte
Byte0 | Byte1–Byte22 | Byte23 | Byte24 |
---|---|---|---|
0x0F | Data | 0x00 | 0x00 |
起始字节 | 数据字节 | 标志位 | 结束字节 |
标志位:1字节,高四位从高到低依次表示:
bit7:CH17数字通道
bit6:CH16数字通道
bit5:帧丢失(Frame lost)
bit4:安全保护(Failsafe):失控保护激活位(0x10)判断飞机是否失控
bit3~bit0:低四位不用
3.2 协议发送
- 高速模式:每隔7ms一帧数据,因为两帧的间隔只有超过3ms,才会被接受;而根据波特率计算一下,发送25字节需要的时间+3~4ms=7ms
- 普通模式:每隔14ms一帧数据;
4 数据编码
4.1 串口发送初始化配置
注意:在stm32中使用校验位,数据字长选择USART_WordLength_9b
void USART1_SBUS_init(u32 bound)
{
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA时钟
//USART1_TX GPIOA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9
//USART1_RX GPIOA.10初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 4 ;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
//USART 初始化设置
USART_InitStructure.USART_BaudRate = bound;//串口波特率
USART_InitStructure.USART_WordLength = USART_WordLength_9b;//字长为9位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_2;//两个停止位
USART_InitStructure.USART_Parity = USART_Parity_Even;//偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
USART_Init(USART1, &USART_InitStructure); //初始化串口1
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断
USART_Cmd(USART1, ENABLE); //使能串口1
}
4.2 协议编码
数组SBUS_Data[25]为我们串口发送的数组,数组ChValue[16]为我们具体要发送的每个通道值,最大为11bit,所以赋值在0-2047之间
SBUS_Data[0] = 0x0F;
// 16 ChValue of 11 bit data
SBUS_Data[1] = (unsigned char) ((ChValue[0] & 0x07FF));
SBUS_Data[2] = (unsigned char) ((ChValue[0] & 0x07FF)>>8 | (ChValue[1] & 0x07FF)<<3);
SBUS_Data[3] = (unsigned char) ((ChValue[1] & 0x07FF)>>5 | (ChValue[2] & 0x07FF)<<6);
SBUS_Data[4] = (unsigned char) ((ChValue[2] & 0x07FF)>>2);
SBUS_Data[5] = (unsigned char) ((ChValue[2] & 0x07FF)>>10 | (ChValue[3] & 0x07FF)<<1);
SBUS_Data[6] = (unsigned char) ((ChValue[3] & 0x07FF)>>7 | (ChValue[4] & 0x07FF)<<4);
SBUS_Data[7] = (unsigned char) ((ChValue[4] & 0x07FF)>>4 | (ChValue[5] & 0x07FF)<<7);
SBUS_Data[8] = (unsigned char) ((ChValue[5] & 0x07FF)>>1);
SBUS_Data[9] = (unsigned char) ((ChValue[5] & 0x07FF)>>9 | (ChValue[6] & 0x07FF)<<2);
SBUS_Data[10] = (unsigned char) ((ChValue[6] & 0x07FF)>>6 | (ChValue[7] & 0x07FF)<<5);
SBUS_Data[11] = (unsigned char) ((ChValue[7] & 0x07FF)>>3);
SBUS_Data[12] = (unsigned char) ((ChValue[8] & 0x07FF));
SBUS_Data[13] = (unsigned char) ((ChValue[8] & 0x07FF)>>8 | (ChValue[9] & 0x07FF)<<3);
SBUS_Data[14] = (unsigned char) ((ChValue[9] & 0x07FF)>>5 | (ChValue[10] & 0x07FF)<<6);
SBUS_Data[15] = (unsigned char) ((ChValue[10] & 0x07FF)>>2);
SBUS_Data[16] = (unsigned char) ((ChValue[10] & 0x07FF)>>10 | (ChValue[11] & 0x07FF)<<1);
SBUS_Data[17] = (unsigned char) ((ChValue[11] & 0x07FF)>>7 | (ChValue[12] & 0x07FF)<<4);
SBUS_Data[18] = (unsigned char) ((ChValue[12] & 0x07FF)>>4 | (ChValue[13] & 0x07FF)<<7);
SBUS_Data[19] = (unsigned char) ((ChValue[13] & 0x07FF)>>1);
SBUS_Data[20] = (unsigned char) ((ChValue[13] & 0x07FF)>>9 | (ChValue[14] & 0x07FF)<<2);
SBUS_Data[21] = (unsigned char) ((ChValue[14] & 0x07FF)>>6 | (ChValue[15] & 0x07FF)<<5);
SBUS_Data[22] = (unsigned char) ((ChValue[15] & 0x07FF)>>3);
// flags
SBUS_Data[23] = 0x00;
// footer
SBUS_Data[24] = 0X00;
5 数据解析
5.1串口接收初始化配置
在数据接受阶段我们采用DMA来读取串口缓冲区的值,使用空闲中断
void usart2_SBUS_init(uint32_t BaudRate)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //DMA1时钟
USART_InitStructure.USART_BaudRate = BaudRate;
USART_InitStructure.USART_WordLength = USART_WordLength_9b;
USART_InitStructure.USART_StopBits = USART_StopBits_2;
USART_InitStructure.USART_Parity = USART_Parity_Even;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_Init(USART2,&USART_InitStructure);
//USART2_TX GPIOA2
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure);
//USART2_RX GPIOA3
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//USART2 NVIC
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
USART_Init(USART2, &USART_InitStructure);
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
USART_ITConfig(USART2, USART_IT_IDLE, ENABLE);
USART_Cmd(USART2,ENABLE);
//串口2的DMA接收配置
DMA_DeInit(DMA1_Channel6);//设置DMA通道x为缺省值
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART2->DR;//外设地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)Sbus_receive;//内存地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//外设作为数据传输的来源
DMA_InitStructure.DMA_BufferSize = 25;//缓存大小
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//内存地址递增
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设地址不变
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设数据宽度8位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//内存数据宽度8位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//正常工作模式,只运行一次
DMA_InitStructure.DMA_Priority = DMA_Priority_High;//高优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//不是内存到内存传输
DMA_Init(DMA1_Channel6,&DMA_InitStructure);
DMA_Cmd(DMA1_Channel6, ENABLE);//启动DMA1的第六通道 uart2->rx
USART_DMACmd(USART2, USART_DMAReq_Rx, ENABLE);//使能串口DMA接受请求
}
5.2 串口中断函数配置
注意,空闲中断需要读取SR和DR寄存器才能清除
单次DMA读取完后,需要重新设置和使能
void USART2_IRQHandler(void)
{
//IDLE中断:可接受不定长度的字节,一帧数据都接受完后才触发IDLE中断
uint8_t temp;
if(USART_GetITStatus(USART2,USART_IT_IDLE)!=RESET)
{
//清除IDLE中断标志,先读SR寄存器,再读DR寄存器
temp = USART2->SR;
temp = USART2->DR;
//关闭DMA接收
DMA_Cmd(DMA1_Channel6,DISABLE);
DMA_ClearFlag(DMA1_FLAG_TC6);
//重新设置缓存字节
DMA_SetCurrDataCounter(DMA1_Channel6,25);
//使能DMA接收
DMA_Cmd(DMA1_Channel6,ENABLE);
}
}
5.3 协议解码
数组Sbus_receive[25]是DMA搬运的数据存储区,数组buffer[25]为mencpy过来的数据,数组channels[16]为我们解码还原的每个通道具体值
memcpy(buffer,Sbus_receive,sizeof(Sbus_receive));
channels[0] = ((buffer[1] |buffer[2]<<8) & 0x07FF);
channels[1] = ((buffer[2]>>3 |buffer[3]<<5) & 0x07FF);
channels[2] = ((buffer[3]>>6 |buffer[4]<<2 |buffer[5]<<10) & 0x07FF);
channels[3] = ((buffer[5]>>1 |buffer[6]<<7) & 0x07FF);
channels[4] = ((buffer[6]>>4 |buffer[7]<<4) & 0x07FF);
channels[5] = ((buffer[7]>>7 |buffer[8]<<1 |buffer[9]<<9) & 0x07FF);
channels[6] = ((buffer[9]>>2 |buffer[10]<<6) & 0x07FF);
channels[7] = ((buffer[10]>>5|buffer[11]<<3) & 0x07FF);
channels[8] = ((buffer[12] |buffer[13]<<8) & 0x07FF);
channels[9] = ((buffer[13]>>3|buffer[14]<<5) & 0x07FF);
channels[10] = ((buffer[14]>>6|buffer[15]<<2|buffer[16]<<10) & 0x07FF);
channels[11] = ((buffer[16]>>1|buffer[17]<<7) & 0x07FF);
channels[12] = ((buffer[17]>>4|buffer[18]<<4) & 0x07FF);
channels[13] = ((buffer[18]>>7|buffer[19]<<1|buffer[20]<<9) & 0x07FF);
channels[14] = ((buffer[20]>>2|buffer[21]<<6) & 0x07FF);
channels[15] = ((buffer[21]>>5|buffer[22]<<3) & 0x07FF);
6 结束语
这就是我在整个SBUS协议学习中遇到的问题和感悟,如果能帮助到的你的话,我很荣幸,如果还有其他问题,欢迎私信,共同学习,共同进步。