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

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字节,用于错误检测或特殊标志(如信号丢失标志)。
  • 结束字节 :固定为 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);
相关推荐
xzl042 天前
LVGL显示移植:lv_port_disp.c 详情介绍
rt-thread·lvgl·ra6m3-hmi-board
xzl048 天前
PL111 RGB LCD时序配置详解
rt-thread·pl111
xzl049 天前
LVGL 启动流程全解析:RT-Thread 下的界面渲染链路
rt-thread·lvgl
xzl049 天前
LVGL Coffee UI 接入实战:问题解决全记录
ui·rt-thread·lvgl
神一样的老师15 天前
【兆易创新GD32VW553开发板试用】 BSP 从 GitHub 下载与编译指南
单片机·github·rt-thread
xzl0415 天前
【Menuconfig】RT-Thread配置菜单第一级
rt-thread
xzl0416 天前
瑞萨 FSP 和 STM32 HAL 库的启动流程核心差异
stm32·单片机·嵌入式硬件·rt-thread
xzl0416 天前
RT-Thread 5.2.2内核模块
开发语言·rt-thread
fox08153 个月前
RTThread-Studio中,使用5.2.0版本默认配置生成工程,进行编译报警告的部分解决方法。
mcu·rt-thread·rtthread-studio