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

news/2024/12/4 20:30:52/

基于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个字节构成,其波特率为100K,所以可在这里插入图片描述
帧长度
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,等待接收第一个字节 收到第一个字节则使能TIM3 TIM3时间满后(3ms)判断是否完整收到25个字节 对收到的数据进行解析 yes no yes no

核心程序

程序是基于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
最后附一张刚买的一个遥控器的初始值调试结果的截图:在这里插入图片描述


http://www.ppmy.cn/news/232579.html

相关文章

Redis6详细教程--介绍和安装

目录 NoSQL介绍&#xff1a;常见的NoSQL&#xff1a;行式数据库&#xff1a;列式数据库&#xff1a;图数据库&#xff1a;1.Redis简介:2.Redis安装&#xff1a;3.Redis启动&#xff1a;4.Redis关闭&#xff1a;Redis配置连接密码&#xff1a; NoSQL介绍&#xff1a; NOSQL解决c…

px4无人机常识介绍(固件,px4等)

专业名词解释 aircraft:任何可以飞或者可以携带物品还是搭载旅客的飞行器统称为飞机(航空器). uav:无人驾驶飞机 vehicle:飞行器 airplane/plane/aero-plane :有机翼和一个或多个引擎的飞行器统称为飞机 Drone:无人航空器,典型的有四旋翼,六旋翼,飞机模型,固定翼,垂起,直升…

Redis6简单安装

安装redis6 依赖安装 yum -y install centos-release-scl scl-utils-build yum -y install -y devtoolset-8-toolchain scl enable devtoolset-8 bashgcc --version下载redis-6.2.1.tar.gz放/opt目录 tar -zxvf redis-6.0.10.tar.gz cd redis-6.0.10编译安装 make &&am…

Redis6.2.6下载和安装

简介 Redis 是一种开源&#xff08;BSD 许可&#xff09;、内存中数据结构存储&#xff0c;用作数据库、缓存和消息代理。Redis 提供了数据结构&#xff0c;例如字符串、散列、列表、集合、带有范围查询的排序集合、位图、超级日志、地理空间索引和流。Redis 内置复制、Lua 脚…

低成本FPV制作记录(空心杯+2.5寸FPV)

前言&#xff1a;入FPV的坑&#xff0c;买的五寸太大&#xff0c;飞起来就是个移动的血滴子&#xff0c;有点害怕&#xff0c;所以想做个低成本、有保护圈的FPV练手。 空心杯 材料&#xff1a; 7.4V 2S 8520空心杯电机 https://item.taobao.com/item.htm?spma1z09.2.0.0.5…

SCOUT 的ROS PACKAGE 介绍和使用

​松灵课堂又开课啦&#xff01;&#xff01;&#xff01;通过上节课内容&#xff08;如何通过开源SDK控制SCOUT&#xff1f;&#xff09;&#xff0c;我们知道通过开源SDK&#xff0c;客户可以快速通过指令的形式与小车底盘进行控制&#xff0c;省去客户自主适配协议的过程&am…

【空心杯四旋翼TinyLeaf】介绍

写在最前&#xff1a; 时间过得很快&#xff0c;转眼我就要毕业了&#xff0c;本科四年中&#xff0c;实验室的学长学弟都给我非常多的帮助&#xff0c;我也在很多开源项目中学习到很多。作为开源精神的受益者&#xff0c;我觉得也应该把自己的工作分享给大家。 我一直对飞行器…

Prometheus V2仿真开发套件全新上市,实现一键启动仿真

产品介绍&#xff1a; Prometheus仿真开发套件是阿木实验室为Prometheus仿真以及matlab仿真专门打造的一款小型X86计算主机&#xff0c;预装仿真所需环境及全部资源&#xff0c;搭载Prometheus地⾯站和Prometheus仿真启动器&#xff0c;⽀持到⼿即用&#xff0c;能够帮助开发者…