基于stm32和富斯遥控器的SBUS波形分析和通讯实现

  • 简介
  • 软件环境和硬件搭建
    • 软件环境
    • 硬件搭建
  • SBUS协议
    • SBUS协议
    • SBUS波形分析
  • 程序部分
    • 程序流程
    • 核心程序
  • 总结

简介

最近一个小项目用到了富斯的遥控器(使用的SBUS协议),目的是实现通过遥控器的各个通道对小车进行简单控制(移动、灯光、不同工作模式等),一点小经验和大家分享下。SBUS网上的资料很多,本篇更偏向于新人对SBUS的快速理解和直接应用,对一些不太常用的细则不再进行介绍。
因为是第一次使用SBUS协议,根据个人习惯在学习通讯协议时喜欢对照着实际波形理解,如果有朋友对硬件有简单了解,建议接触新的通讯协议时也用示波器配合实际波形来学习,能发现很多细节。当然这个不是必须的,仅是个人建议而已,实际波形我也会贴出供感兴趣的朋友参考。
其他细节如有疏漏还请各位指出,共同进步。

软件环境和硬件搭建

软件环境

编译软件:KEIL MDK
库:STM32标准库
单片机I/O使用:PC11(串口USART4 RX端,TX端不接即可)
单片机外设使用:USART4(接收遥控数据)、TIM3(定时验证数据正确性)

硬件搭建

发射装置:富斯遥控器FS-I6S
接收装置:接收机IA10B
MCU控制板:STM32F407电路板
外接电路:简单的三极管反向电路(必须)

发射装置和接收装置之间只要是SBUS通讯方式,不同型号理论来说影响不大,程序可以通用。
因为只需要用到单片机的串口(为了验证数据的正确性笔者多用了个定时器TIM3),所以只是实现通讯的话电路要求比较简单,只要能正常工作并带有串口外设的单片机板即可,比如某宝上卖的STM32F103最小系统板。
由于SBUS逻辑电平和常用的串口通讯极性刚好相反,所以需要搭建一个简单的三极管反向电路,电路参考下图。

遥控器需要配置为SBUS输出模式:

接收机接线如下:
绿线为信号线-----接三极管反向电路的输入端(Single)
黄线为电源+线-----接5V电源
蓝线为电源地线 -----接电源GND
总体连接如下:

SBUS协议

SBUS协议

SBUS协议其实就是串口通讯(USART)的应用层协议,它的本质还是USART通讯。可以粗暴理解为一帧SBUS数据是由连续发送或接收25个字节(即25次)的串口数据构成,第一个字节固定为0x0F,最后一个字节固定为0x00,中间23个字节和起来构成了所需数据。所以使用它在程序上还是使用串口,只不过在串口配置上必须按照以下参数配置:
串口波特率为100000,数据位为8位2个停止位,校验,无硬件控流
Sbus的编码方式为每11位为一个数据,除去第一个字节和第25个字节,需要把中间23个字节的常规8位数据合在一起,并按每11位为一组的格式进行解析处理。具体解析方法网上教程较多,不再赘述。如果不想了解具体解析方法,可直接引用下文的解析函数得出解析后的结果即可。

SBUS波形分析

位长度:
SBUS的波特率固定为100K,所以每传输一位的时间为:1/100K=10us,
随机用示波器抓取了一位,实测结果略微有误差为11.7us,在接受范围内。

字节长度:
SBUS一帧由25次串口接收或发送构成(25个字节),每次串口发送有12位组成:1个起始位+8个数据位+1个偶校验位+2个停止位。下图为截取一帧SBUS前几个数据字节波形。由于发送顺序遵循LSB(低位优先)原则,所以需要注意每个字节高位和低位的波形和实际结果颠倒的。如波形第一个字节为0xF0,实际数据为0x0F。

帧长度
SBUS一帧由25个字节构成,每个字节12位,每位长度10us,总长度=10us12位25个字节=3000us(纠正:图中3000us单位错打成了3000ms)。
帧间隔
SBUS两帧间间隔约4.68ms,如果要求不能漏掉任何一帧,则需要注意其他程序处理时间必须在4.68ms内,不能影响一下帧的接收。

程序部分

程序流程

程序执行流程:上电-----配置外设(USART4、TIM3,默认使能都为关闭状态,TIM3定时3ms)-----等待PC11出现持续一段时间的高电平后使能USART4,等待接收第一个字节(等待的持续高电平即为两帧间的高电平间隔部分,确保能从第一个字节接收)-----当串口收到数据后使能TIM3-----当TIM3时间到后关闭TIM3和USART4判断串口是否是刚好收到25个字节-----是则执行解析函数,不是则为接收错误-----重新等待持续的高电平。

Created with Raphaël 2.2.0上电初始化USAER4、TIM3读取PC11电平是否出现持续的高电平?使能USART4,等待接收第一个字节收到第一个字节则使能TIM3TIM3时间满后(3ms)判断是否完整收到25个字节对收到的数据进行解析yesnoyesno

核心程序

程序是基于STM32F407的,如果是103可能在系统头文件名上报错和USART配置时会有点小差别。
USART4配置及其中断函数:
一定要注意因为有一个偶校验位,数据长度要写为9:
USART_InitStructure.USART_WordLength = USART_WordLength_9b。
中断内的函数功能为:进中断开TIM3定时器,把收到的串口数据进行保存。

#include "sys.h"
#include "usart.h"      u8 rec_buff[30]={0};
u8 rec_cnt=0;
extern u32 WaitRec_cnt;void Uart4_Init(u32 bound){//GPIO端口设置  PC10 PC11GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC,ENABLE); //使能GPIOC时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART4,ENABLE); //使能USART1时钟//串口4对应引脚复用映射GPIO_PinAFConfig(GPIOC,GPIO_PinSource10,GPIO_AF_UART4); //GPIOC10复用为USART4GPIO_PinAFConfig(GPIOC,GPIO_PinSource11,GPIO_AF_UART4); //GPIOA11复用为USART4//USART1端口配置 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11; //GPIOc10与GPIOc11GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;    //速度50MHzGPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉GPIO_Init(GPIOC,&GPIO_InitStructure); //初始化C10 C11//Usart1 NVIC 配置NVIC_InitStructure.NVIC_IRQChannel = UART4_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;      //子优先级3NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;         //IRQ通道使能NVIC_Init(&NVIC_InitStructure);    //根据指定的参数初始化VIC寄存器//USART 初始化设置USART_InitStructure.USART_BaudRate = bound;//串口波特率USART_InitStructure.USART_WordLength = USART_WordLength_9b;//字长为8位数据格式USART_InitStructure.USART_StopBits = USART_StopBits_2;//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(UART4, &USART_InitStructure); //初始化串口1USART_ITConfig(UART4, USART_IT_RXNE, ENABLE);//开启串口接受中断USART_Cmd(UART4, DISABLE);                    //使能串口1 }
void UART4_IRQHandler(void)
{if(USART_GetITStatus(UART4, USART_IT_RXNE) != RESET){rec_buff[rec_cnt]=UART4->DR;rec_cnt++;WaitRec_cnt=0;TIM_Cmd(TIM3, ENABLE);    }USART_ClearITPendingBit(UART4, USART_IT_RXNE);
}

TIM3配置及其中断函数:
TIM3时间在实际应用时是3ms进定时器中断,理论上3ms能刚好把一帧SBUS(25个字节)接收完毕。因为是已经接收到第一个串口数据后才开的定时器,后续只会有24个字节的时间,所以实际上定时器3ms时间还留有一个字节的时间裕量。
TIM3的中断函数功能:即为判断串口是否正确接收了一帧SBUS(25个字节)数据,是则进行数据解析函数SbusDataParsing(u8 buf[]) ;,不是则错误位RecErr_Flag+1。

#include "tim.h"
#include "usart.h"
#include "sbus.h"
u8 RecErr_Flag;
extern u8 rec_cnt;
u32 WaitRec_cnt;
extern u8 rec_buff[30];
void TIM3_Init(u16 arr,u16 psc)
{TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能TIM_DeInit(TIM3);//定时器TIM3初始化TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值  TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_timTIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位TIM_ClearITPendingBit(TIM3, TIM_IT_Update);TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); //使能指定的TIM3中断,允许更新中断//中断优先级NVIC设置NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;  //TIM3中断NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;  //先占优先级0级NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;  //从优先级3级NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能NVIC_Init(&NVIC_InitStructure);  //初始化NVIC寄存器TIM_Cmd(TIM3, DISABLE);  //使能TIMx
}
void TIM3_IRQHandler(void)   //TIM3中断
{if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)  //检查TIM3更新中断发生与否{USART_Cmd(UART4, DISABLE);   TIM_Cmd(TIM3, DISABLE);TIM3->CNT=0;if(rec_cnt==25){SbusDataParsing(rec_buff);    //SBUS解析}else    RecErr_Flag++;rec_cnt=0;WaitRec_cnt=0;TIM_ClearITPendingBit(TIM3, TIM_IT_Update);  //清除TIMx更新中断标志 }
}

解析函数:
SbusDataParsing(u8 buf[])为解析函数,如果TIM3判断正确接收了25个字节的数据,则把串口接收到的25个数据放入buf[]数组内,执行完的数组结果ch[]就是我们需要的最终结果。


#include "sbus.h"u16 ch[16]={0};
void SbusDataParsing(u8 buf[])  //S-BUS解析
{ch[0] = ((u16)buf[ 1] >> 0 | ((int16_t)buf[ 2] << 8 )) & 0x07FF;ch[1] = ((u16)buf[ 2] >> 3 | ((int16_t)buf[ 3] << 5 )) & 0x07FF;ch[2] = ((u16)buf[ 3] >> 6 | ((int16_t)buf[ 4] << 2 )  | (int16_t)buf[ 5] << 10 ) & 0x07FF;ch[3] = ((u16)buf[ 5] >> 1 | ((int16_t)buf[ 6] << 7 )) & 0x07FF;ch[4] = ((u16)buf[ 6] >> 4 | ((int16_t)buf[ 7] << 4 )) & 0x07FF;ch[5] = ((u16)buf[ 7] >> 7 | ((int16_t)buf[ 8] << 1 )  | (int16_t)buf[9] <<  9 ) & 0x07FF;ch[6] = ((u16)buf[ 9] >> 2 | ((int16_t)buf[10] << 6 )) & 0x07FF;ch[7] = ((u16)buf[10] >> 5 | ((int16_t)buf[11] << 3 )) & 0x07FF;ch[8] = ((u16)buf[12] << 0 | ((int16_t)buf[13] << 8 )) & 0x07FF;ch[9] = ((u16)buf[13] >> 3 | ((int16_t)buf[14] << 5 )) & 0x07FF;ch[10] = ((u16)buf[14] >> 6 | ((int16_t)buf[15] << 2 )  | (int16_t)buf[16] << 10 ) & 0x07FF;ch[11] = ((u16)buf[16] >> 1 | ((int16_t)buf[17] << 7 )) & 0x07FF;ch[12] = ((u16)buf[17] >> 4 | ((int16_t)buf[18] << 4 )) & 0x07FF;ch[13] = ((u16)buf[18] >> 7 | ((int16_t)buf[19] << 1 )  | (int16_t)buf[20] <<  9 ) & 0x07FF;ch[14] = ((u16)buf[20] >> 2 | ((int16_t)buf[21] << 6 )) & 0x07FF;ch[15] = ((u16)buf[21] >> 5 | ((int16_t)buf[22] << 3 )) & 0x07FF;
}

主函数:
主函数的主要功能:上电配置串口USART4和定时器TIM3,然后while循环检查串口USART4的RX引脚PC11是否出现连续的高电平。每次while循环一次检测是高则WaitRec_cnt+1,是低则清0,直到出现一段连续的高电平就表明进入了两帧SBUS中间的帧间隔中,再开启串口确保能从第一个字节开始接收。实际的WaitRec_cnt时间不用特别精确但需要大家进行调试,不同单片机主频不同,执行while的时间也不同。STM32F407主频168M,WaitRec_cnt执行到3000时大概700多us。同时需要自行考虑持续多久开启串口中断比较好,不要影响到其他程序的运行。

#include "stm32f4xx.h"
#include "usart.h"
#include "tim.h"
extern u32 WaitRec_cnt;int main(void)
{   Uart4_Init(100000);  //遥控器TIM3_Init(29,8399);while(1){  if(GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_11)==1)  //等待一段高电平,确保从第一个字节开始。{WaitRec_cnt++;}else WaitRec_cnt=0;if(WaitRec_cnt>3000)           //3000时大概为几百us,持续几百us都为高则开启串口{USART_Cmd(UART4, ENABLE);}}
}

其他.h文件
sbus.h

#ifndef __SBUS_H
#define __SBUS_H
#include "sys.h"
#define RightRocker_Horizontal ch[0]    //left:242    center:1033    right:1804
#define RightRocker_Vertical ch[1]       //up:1807     center:1024      down:240
#define LeftRocker_Vertical ch[2]        //up:1805     center:1024      down:240
#define LeftRocker_Horizontal ch[3]     //left:240   center:1025    right:1807
#define SWA ch[4]                                            //up:240                                     down: 1807
#define SWB ch[5]                                            //up:240        center:1024        down:   1807
#define SWC ch[6]                                            //up:240        center:1024    down:   1807
#define SWD ch[7]                                            //up:240                                     down: 1807
#define VAA ch[8]                                            //left:240      center:1024    right:1807
#define VAB ch[9]                                            //left:1807     center:1024    right:240
void SbusDataParsing(u8 buf[]);
#endif

usart.h

#ifndef __UART_H
#define __UART_H
#include "stdio.h"
#include "sys.h" void Uart4_Init(u32 bound);
void Uart1_Init(u32 bound);
#endif  

tim.h

#ifndef __TIMER_H
#define __TIMER_H
#include "sys.h"void TIM3_Init(u16 arr,u16 psc);#endif

总结

至此,整个SBUS的通讯已经完成,通讯的最终结果存放在CH[]数组里以方便调用。遥控器上不同的摇杆和拨动开关对应不同的CH[]通道,sbus.h里也有进行宏定义以便大家进行遥控的按钮和CH[]通道的对应:

#define RightRocker_Horizontal ch[0]    //left:242    center:1033    right:1804

实际就是遥控器右边摇杆水平拨动时对应的是ch[0]中的值的变化。不拨动时ch[0]值是1033,右摇杆拨到最左边时ch[0]是242,最右边时ch[0]是1804.不同的遥控器中间值和最大最小值会有小范围的偏差,一般不会超过几十。其他摇杆和按键的对应关系请自行体会。也可以去B站看实际控制遥控器对应的CH[]变化,不过是16进制的看着不是很方便:
https://www.bilibili.com/video/BV1Kv411k7fQ
最后附一张刚买的一个遥控器的初始值调试结果的截图:

基于stm32和富斯遥控器的SBUS波形分析和通讯实现相关推荐

  1. STM32与Futaba遥控器进行S.Bus的通讯程序

    Futaba(日本双叶电子工业,戏称"扶他爸")的遥控器用航模中使用较为广泛的遥控器.S.Bus则是Futaba公司提出的舵机控制总线.本篇博文主要以T6K为对象,介绍STM32与 ...

  2. 基于stm32的自定义HID设备开发与上位机通讯实现

    现在主流的安卓手机数据连接线,Mini-usb.Micro-usb,Type-c,产品追随主流,非联网设备,摒弃ST-LINK.JLINK,直接用usb数据传输升级.主要实现与HID设备的通信即人机交 ...

  3. 基于STM32的万能红外遥控器

    本博客介绍一种基于STM32的可学习和存储已有红外发射设备的万能红外遥控器的设计思路. 一.首先需要了解设计一款这功能的遥控器需要什么硬件设备支持. 1.3.3V,5V电源模块,用作给系统模块供电和单 ...

  4. 富斯遥控器/接收机的PWM/PPM/iBUS/SBUS通道设置

    富斯遥控器FS-i6X拥有10通道输出,富斯接收机FS-iA10B拥有10通道输入,两者都有PWM/PPM/iBUS/SBUS协议,但是PPM和iBUS协议最高只支持8通道,而SBUS协议可以支持10 ...

  5. 基于stm32的无人机控制系统设计

    基于stm32的无人机控制系统设计 **==整篇文章有两万字左右,字数太多了,实在是懒得全部放在这上面来,太废时间了.需要完整论文可主页联系==** 第一章 前言 1.1项目背景和意义 1.2国内外发 ...

  6. [转载]基于Stm32,LD3320的非特定语音识别USB HID Keyboar

    基于Stm32,LD3320的非特定语音识别USB HID Keyboard实现 ---用声音跟机器沟通 鉴于手头拥有一块ST官方的stm32f407VG discover板子以及一块ICRoute公 ...

  7. 基于STM32四轴飞控制作笔记

    基于STM32四轴飞控制作笔记 前言 大四选了个四轴飞控作为毕业设计的题目,近来没事,把之前的制作做个总结开源出来. 硬件设计 1.遥控MCU模块和飞控的MCU模块都采用stm32f103c8t6单片 ...

  8. 基于stm32的两轮自平衡小车4(软件调试篇)

    本篇是软件调试篇,接上一篇硬件篇:基于stm32的两轮自平衡小车3(硬件篇),本篇内容是对硬件部分的软件实现,具体模块详见目录.这里先上效果:转B站 目录 定时器PWM驱动程序 定时器编码器模式驱动程 ...

  9. 最简单DIY基于STM32单片机的蓝牙智能小车设计方案

    STM32库函数开发系列文章目录 第一篇:STM32F103ZET6单片机双串口互发程序设计与实现 第二篇:最简单DIY基于STM32单片机的蓝牙智能小车设计方案 文章目录 STM32库函数开发系列文 ...

  10. 基于STM32的智能小车方案设计

    基于STM32的智能小车设计 前言 一.什么是STM32智能小车? 二.模块汇总 1.主控板(STM32F103ZE) 2.底板 3.电机 4.避障模块(超声波传感器,红外传感器) 5.寻迹模块(3路 ...

最新文章

  1. Android中使用ViewStub提高布局性能
  2. @AutoWired和@Resource注解异同分析
  3. Delegate(QLabel和QComboBox)
  4. RDC如何打造支撑百万用户的分布式代码托管平台
  5. 重庆考区非计算机专业二级等级考试vf上机题怎么建立程序文件?,计算机等级考试一级上机试题...
  6. pytorch3d在linux下安装
  7. cesium 页面截图_Cesium开发入门篇 | 02Cesium开发环境搭建及第一个示例
  8. python数据结构3-链表
  9. 剖析云计算和大数据落地后x86服务器的业务痛点
  10. 1. 架构到底是指什么? 容易混淆的概念
  11. 阿里巴巴连接池mysql_阿里巴巴连接池(Druid)
  12. OpenGL入门学习[二] 绘制简单的几何图形
  13. 罗克韦尔PLC程序,水处理自动化最高程序
  14. 老男孩数据库学习记录
  15. 千变万化的ViewPager指示器-MagicIndicator
  16. uniapp h5页面打开app,没有下载则跳转下载
  17. JavaIO流之文件路径
  18. 内网IP端口扫描统计+服务识别简单思路
  19. 初期草根站长适合做什么样的网站好
  20. 库存管理系统(java)

热门文章

  1. 鸿蒙系统官网电脑版,华为鸿蒙系统官方pc版下载-华为鸿蒙系统官方电脑pc版 -优盘手机站...
  2. 实用的手机app商城购物网站模板源码
  3. 欧姆龙PLC程序 欧姆龙NX系列PLC程序,ST语言和梯形图配合使用,数据处理使用ST语言,逻辑用梯形图
  4. 该如何实现EDIUS中的多机位编辑
  5. Ubuntu中安装微信(wechat)
  6. halcon修改程序框字体大小
  7. 全面的软件测试-软件测试图解
  8. Javaweb - JSP章节 - MVC和三层架构案例总练习(下) - “回显数据”-“修改数据”功能实现
  9. 工程项目成本费用明细表_项目成本费用明细表
  10. PHP 汉字转拼音Class类