rt-thread 解析sbus协议源码分享

一、sbus协议介绍
SBUS协议(Serial Bus)是一种专为遥控模型(如无人机、航模)设计的串行通信协议,由Futaba公司开发。它主要用于从接收器向伺服器、飞控或其他设备传输多通道控制数据,具有高效、低延迟的特点。下面我将逐步解释其核心内容,确保解释清晰可靠。
1、协议概述
- SBUS是一种单向异步串行协议,采用负逻辑(即高电平表示逻辑0,低电平表示逻辑1),这有助于减少噪声干扰。
- 工作波特率固定为 100 kbps 100\,\text{kbps} 100kbps(千比特每秒),数据帧结构紧凑,支持最多16个通道。
- 应用场景:常见于RC(遥控)系统中,用于传输舵机位置、油门等控制信号,实现实时响应。
2、工作原理
SBUS基于标准的UART(通用异步收发器)通信,但逻辑反转:
- 数据发送:接收器将通道数据打包成帧,通过单线传输到伺服器。
- 时序特性:每帧数据以固定间隔发送(通常 5 ms 5\,\text{ms} 5ms到 10 ms 10\,\text{ms} 10ms),确保低延迟。最小单位时间由波特率决定,计算式为:
T bit = 1 100000 s = 10 μ s T_{\text{bit}} = \frac{1}{100000} \,\text{s} = 10\,\mu\text{s} Tbit=1000001s=10μs
其中 T bit T_{\text{bit}} Tbit是每个比特的持续时间。 - 同步机制:使用起始位和停止位进行帧同步,数据格式包括8个数据位、偶校验位和2个停止位。
3、数据格式
每帧SBUS数据固定为25字节,结构如下:
- 起始字节 :固定为 0 x 0 F 0\text{x}0\text{F} 0x0F(十六进制),标识帧开始。
- 通道数据 :22字节主体,包含16个通道的值。每个通道用11位表示,值范围为 0 0 0到 2047 2047 2047(因为 2 11 = 2048 2^{11} = 2048 211=2048)。
- 例如,通道1的值存储在字节1到字节2的部分比特中,计算式为:
Value ch1 = Byte 1 + ( Byte 2 ≪ 8 ) (移位操作) \text{Value}_{\text{ch1}} = \text{Byte}_1 + (\text{Byte}_2 \ll 8) \quad \text{(移位操作)} Valuech1=Byte1+(Byte2≪8)(移位操作)
这里 ≪ \ll ≪表示左移运算符。
- 例如,通道1的值存储在字节1到字节2的部分比特中,计算式为:
- 标志字节:1字节,用于错误检测或特殊标志(如信号丢失标志)。
- 结束字节 :固定为 0 x 00 0\text{x}00 0x00,标识帧结束。
数据解析示例:
- 假设接收数据帧:起始字节后,字节1为 0 x 7 F 0\text{x}7\text{F} 0x7F,字节2为 0 x 03 0\text{x}03 0x03,则通道1值计算为:
Value ch1 = 127 + ( 3 × 256 ) = 895 \text{Value}_{\text{ch1}} = 127 + (3 \times 256) = 895 Valuech1=127+(3×256)=895
值 895 895 895对应舵机的中位位置。
4、协议特点
- 优点 :
- 高分辨率:11位通道值提供 2048 2048 2048级精度,比传统PWM(脉宽调制)更精细。
- 低延迟:帧间隔短,典型延迟低于 10 ms 10\,\text{ms} 10ms。
- 多设备支持:单总线可连接多个伺服器,减少布线复杂度。
- 缺点 :
- 单向通信:不支持设备回传数据。
- 兼容性问题:需硬件支持负逻辑,不兼容标准TTL电平。
5、应用实例
SBUS广泛应用于无人机飞控、航模遥控系统中:
- 在飞控中,SBUS接收器解码数据后,输出控制信号到电机和舵机。
- 典型设置:遥控器发送指令,接收器通过SBUS线传输到飞控板,飞控板解析数据后执行动作。
二、源码分享
cpp
/*************************************************
* @copyright:
* @author:Xupeng
* @date:2022-11-03
* @description:
**************************************************/
#include "sbus.h"
#define DBG_TAG "sbus"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#define SBUS_UART_NAME "uart3" /* 串口设备名称 */
/* 用于接收消息的信号量 */
static rt_device_t sbusSerial;
static rt_uint8_t recBuf[50];
static rt_uint8_t recCnt = 0;
rt_uint16_t sbusVal[SBUS_CH_MAX] = {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000};
rt_uint8_t sbusIsOk = 0;
/*************************************************
* @function:static rt_err_t uart_input(rt_device_t dev, rt_size_t size)
* @description: 接收数据
* @calls:
* @input:
* @return:
* @others:
*************************************************/
static rt_err_t sbus_input(rt_device_t dev, rt_size_t size)
{
uint8_t readLen;
readLen = rt_device_read(sbusSerial, 0, &recBuf[recCnt], size);
if(recBuf[0] == 0x0f)
{
recCnt += readLen;
}
if(recCnt >= 25)
{
if(recBuf[0] != 0x0f || recBuf[23] !=0)
{
recCnt = 0;
recBuf[0] = 0x00;
}
else
{
sbusVal[0] = ((rt_uint16_t)recBuf[ 1] >> 0 | ((rt_uint16_t)recBuf[ 2] << 8 )) & 0x07FF;
sbusVal[1] = ((rt_uint16_t)recBuf[ 2] >> 3 | ((rt_uint16_t)recBuf[ 3] << 5 )) & 0x07FF;
sbusVal[2] = ((rt_uint16_t)recBuf[ 3] >> 6 | ((rt_uint16_t)recBuf[ 4] << 2 ) | (rt_uint16_t)recBuf[ 5] << 10 ) & 0x07FF;
sbusVal[3] = ((rt_uint16_t)recBuf[ 5] >> 1 | ((rt_uint16_t)recBuf[ 6] << 7 )) & 0x07FF;
sbusVal[4] = ((rt_uint16_t)recBuf[ 6] >> 4 | ((rt_uint16_t)recBuf[ 7] << 4 )) & 0x07FF;
sbusVal[5] = ((rt_uint16_t)recBuf[ 7] >> 7 | ((rt_uint16_t)recBuf[ 8] << 1 ) | (rt_uint16_t)recBuf[9] << 9 ) & 0x07FF;
sbusVal[6] = ((rt_uint16_t)recBuf[ 9] >> 2 | ((rt_uint16_t)recBuf[10] << 6 )) & 0x07FF;
sbusVal[7] = ((rt_uint16_t)recBuf[10] >> 5 | ((rt_uint16_t)recBuf[11] << 3 )) & 0x07FF;
sbusVal[8] = ((rt_uint16_t)recBuf[12] << 0 | ((rt_uint16_t)recBuf[13] << 8 )) & 0x07FF;
sbusVal[9] = ((rt_uint16_t)recBuf[13] >> 3 | ((rt_uint16_t)recBuf[14] << 5 )) & 0x07FF;
sbusVal[10] = ((rt_uint16_t)recBuf[14] >> 6 | ((rt_uint16_t)recBuf[15] << 2 ) | (rt_uint16_t)recBuf[16] << 10 ) & 0x07FF;
sbusVal[11] = ((rt_uint16_t)recBuf[16] >> 1 | ((rt_uint16_t)recBuf[17] << 7 )) & 0x07FF;
sbusVal[12] = ((rt_uint16_t)recBuf[17] >> 4 | ((rt_uint16_t)recBuf[18] << 4 )) & 0x07FF;
sbusVal[13] = ((rt_uint16_t)recBuf[18] >> 7 | ((rt_uint16_t)recBuf[19] << 1 ) | (rt_uint16_t)recBuf[20] << 9 ) & 0x07FF;
sbusVal[14] = ((rt_uint16_t)recBuf[20] >> 2 | ((rt_uint16_t)recBuf[21] << 6 )) & 0x07FF;
sbusVal[15] = ((rt_uint16_t)recBuf[21] >> 5 | ((rt_uint16_t)recBuf[22] << 3 )) & 0x07FF;
sbusIsOk ++;
recCnt = 0;
recBuf[0] = 0x00;
}
}
return RT_EOK;
}
/*************************************************
* @function:void sbus_init()
* @description: 初始化SBUS
* @calls:
* @input:
* @return:
* @others:
*************************************************/
int sbus_init()
{
struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT; /* 初始化配置参数 */
rt_err_t res;
sbusSerial = rt_device_find(SBUS_UART_NAME);
if (!sbusSerial)
{
LOG_E("find %s failed!", SBUS_UART_NAME);
return RT_ERROR;
}
config.baud_rate = BAUD_RATE_100000; //修改波特率为 100000
config.data_bits = DATA_BITS_9; //数据位 9
config.stop_bits = STOP_BITS_1; //停止位 1
config.bufsz = 128; //修改缓冲区 buff size 为 128
config.parity = PARITY_EVEN; //偶校验位
res = rt_device_control(sbusSerial, RT_DEVICE_CTRL_CONFIG, &config);
if(res != RT_EOK)
{
LOG_E("commit control err!");
return RT_ERROR;
}
res = rt_device_open(sbusSerial, RT_DEVICE_OFLAG_RDONLY|RT_DEVICE_FLAG_INT_RX);
if(res != RT_EOK)
{
LOG_E("commit open err!");
return RT_ERROR;
}
res = rt_device_set_rx_indicate(sbusSerial, sbus_input);
if(res != RT_EOK)
{
LOG_E("commit set indicate err!");
return RT_ERROR;
}
return RT_EOK;
}
INIT_PREV_EXPORT(sbus_init);
/*************************************************
* @function:static void show_sbus()
* @description: 打印输出sbus信号
* @calls:
* @input:
* @return:
* @others:
*************************************************/
static void show_sbus(uint8_t argc, char *argv[])
{
uint8_t count = 0;
if(argc > 2)
{
rt_kprintf("please input correct para(0-255)!\r\n");
return;
}
else if(argc == 2)
count = atoi(argv[1]);
else
count = 20;
while(count-- > 0)
{
for(uint8_t i=0;i<SBUS_CH_MAX/2;i++)
{
rt_kprintf("ch%d:%4d ",i+1,sbusVal[i]);
}
rt_kprintf("\r\n");
rt_thread_mdelay(500);
}
}
MSH_CMD_EXPORT_ALIAS(show_sbus,sbus,show sbus value);
