电子工程师技术服务社区
公告
登录
|
注册
首页
技术问答
厂商活动
正点原子
板卡试用
资源库
下载
文章
社区首页
文章
树莓派基础实验39:解析无线电接收机PWM、SBUS信号
分 享
扫描二维码分享
树莓派基础实验39:解析无线电接收机PWM、SBUS信号
树莓派
无线电接收机
PWM、SBUS
张国平
关注
发布时间: 2020-09-25
丨
阅读: 3982
## 一、介绍 虽然如今或者将来,5G网络的建设带来人工智能和工业自动化的全面升级,生产活动中劳动力的需求大大减少,大量的劳动力将向内容生产行业和服务行业转移。教育、医疗、娱乐、公共管理等诸多领域,乃至整个社会都将迎来巨大变革。可参阅我的一篇读书笔记[5G社会:万物互联新时代](https://www.jianshu.com/p/9b7a626414ee)。 但是,使用传统无线电通信设备通信仍然是非常重要的通信方式,比如无线电台、对讲机,航模、车模、船模遥控等等。与手机移动网络、WIFI连接相比,无线电连接有它独特的优势。 在[树莓派基础实验38:逻辑分析仪分析PWM、UART信号](https://www.icxbk.com/article/detail?aid=1734)中使用逻辑分析仪,对树莓派的PWM信号和UART信号进行分析,从中可以详细了解逻辑分析仪分析的使用方法及PWM信号和UART信号。 本实验中将使用逻辑分析仪、树莓派,对航模无线电接收机输出的PWM信号、SBUS信号进行采集分析,以便树莓派能够接收无线电控制信号,进而可以开发基于无线电控制的树莓派航模飞行控制系统、或者智能小车的无人驾驶系统。 ## 二、组件 ★Raspberry Pi 3 B+全套*1 ★睿思凯Frsky X8R 接收机*1 ★电平反向器模块*1 ★睿思凯Frsky Taranis X9D PLUS SE2019遥控器*1 ★国产梦源DSLogic Plus逻辑分析仪*1 ★面包板*1(可选) ★40P软排线*1 ★跳线若干 ## 三、实验原理 ####(一)航模无线电遥控系统 本实验中使用的遥控系统可以自行选择其它品牌的产品,如国产的天地飞还不错。 航模的遥控器就是像电视机遥控器、空调遥控器一样可以不用接触到被控设备,而通过一个手持器件,使用无线电与被控设备进行通信,从而达到对设备的控制。 遥控器想到达到与无人机通信的功能需要有两部分配合完成。即:发射器(遥控器)与接收机。遥控器上的控制杆转为无线电波发送给接收机,而接收机通过接收无线电波,读取遥控器上控制杆的读数,并转为数字信号发送到航模的控制器中。 ![发射器与接收机](https://cf02.ickimg.com/bbsimages/202009/65301d17ef8cd53465437e0d4ce7665c.jpg) 目前用于无人机遥控器主流的无线电频率是2.4G,这样的无线电波的波长更长,可以通信的距离较远,普通2.4G遥控器与接收机的通信距离在空旷的地方大概在1km以内。2.4GHz无线技术如今已经成为了无线产品的主流传输技术。所谓的2.4GHz所指的是一个工作频段2400M-2483M范围,这个频段是全世界免申请使用。 常见的Wifi、蓝牙、ZigBee都是使用的2.4G频率段,只不过他们采用的协议不同,导致其传输速率不同,所以运用的范围就不同。同样是采用2.4G频率作为载波,但不同的通讯协议衍生出的通讯方式会有着天壤之别;仅仅在传输数据量上,就有着从1M每秒到100M每秒的差别。 关于遥控器与无人机的通信协议也有很多种,常见的数据协议如下: **1.pwm:**需要在接收机上接上全部pwm输出通道,每一个通道就要接一组线,解析程序需要根据每一个通道的pwm高电平时长(即占空比)计算通道数值。 **2.ppm:**按固定周期发送所有通道pwm脉宽的数据格式,一组接线,一个周期内发送所有通道的pwm值,解析程序需要自行区分每一个通道的pwm时长。PPM的频率通常是50Hz,周期长度20ms,每一个周期中可以存放最多10路PWM信号,每一路PWM的周期为2ms。 **3.sbus:**每11个bit位表示一个通道数值的协议,串口通信,但是sbus的接收机通常是反向电平,连接到航模时需要接电平反向器,大部分支持sbus的飞行控制板已经集成了反向器。 **4.xbus:**常规通信协议,支持18个通道,数据包较大,串口通信有两种模式,可以在遥控器的配置选项中配置。接收机无需做特殊配置。 睿思凯Frsky Taranis X9D PLUS SE2019遥控器: ![睿思凯Frsky Taranis X9D PLUS SE2019遥控器](https://cf02.ickimg.com/bbsimages/202009/c95ea4f5d89a2f47138a7ce797af3eb1.jpg) 睿思凯Frsky X8R 接收机: ![睿思凯Frsky X8R 接收机](https://cf02.ickimg.com/bbsimages/202009/6a87aaef686d745d62559caffb5c6e5a.jpg) 然后,就是电调通过接收接收机输出的这些信号,来将输入的电源转为不同的电压,并输出到电机,从而达到使电机产生不同的转速的目的。有刷电调可以改变电流方向,从而可以改变电机转动方向。而无刷电调却不能改变电机的转动方向,但是可以将直流电转为三相交流电,从而输出到无刷电机上。 所谓电调就是电压调节器,也可以通俗的说成是电机调节器,这里不做过多讲解。 ####(二)接收机的PWM信号 PWM英文全称为(Pulse-width modulation)。也称占空比信号,它表示高电平时长占整个信号周期的比例。 ![](https://cf02.ickimg.com/bbsimages/202009/93aaed39ba1254f48150a2c8d3e48646.jpg) PWM信号的频率是通常是没有规定的,可以是50hz、100hz、200hz或500hz等等。控制频率越高,其周期越短,控制间隔也就越短,电调和电机响应速度也就越快。反之,控制频率越低,其周期就越长,控制间隔就越长,电调和电机的响应速度就越慢。早期电调响应PWM信号的频率是50hz,但随着科技的发展和对控制流畅度的要求,现在多数电调都支持500hz以上的PWM信号,并且电调内部自带滤波器,可以很好的响应并控制电机的转动。 传统的遥控器接收机是采用多路PWM的方式进行输出的,遥控器中有多少个通道,接收机中就有多少路PWM输出,睿思凯Frsky X8R接收机的1-8个PWM输出通道,都是以PWM的形式输出的,这就需要飞控能够采集并解析这些PWM信号,并为飞控所用。 那么,睿思凯Frsky X8R接收机的PWM信号到底是怎样的呢?我们使用逻辑分析仪看看吧,连接好遥控器、接收机、连接逻辑分析仪。 接收机连接逻辑分析仪: ![接收机连接逻辑分析仪](https://cf02.ickimg.com/bbsimages/202009/8da9bb0fee53723c3ee17f28f864509e.jpg) 这里我只采集了1、3、5号通道的PWM信号。1号通道是右手油门摇杆左右晃动,会自动回中;3号通道是右手油门摇杆油门控制,由低到高表示油门由小到大,不会回中;5号通道是SA开关,有上中下3个档位。 首先来看1号通道,当摇杆往左摇到底时,占空比约为5.5%,高电平时长为0.99ms,信号周期为18ms。 1号通道摇杆往左摇到底时: ![1号通道摇杆往左摇到底时](https://cf02.ickimg.com/bbsimages/202009/9bd2b10036ae24cf0f2d64c2540a3bda.jpg) 1号通道,当摇杆往右摇到底时,占空比约为11.2%,高电平时长为2.01ms,信号周期为18ms。 1号通道摇杆往右摇到底时: ![1号通道摇杆往右摇到底时](https://cf02.ickimg.com/bbsimages/202009/78491c328106a4a86adea62cc347b9f7.jpg) 再看3号通道,当摇杆往下摇到底时,油门为0,占空比约为5.5%,高电平时长为0.99ms,信号周期为18ms。 3号通道油门为0,摇杆往下摇到底时: ![3号通道油门为0,摇杆往下摇到底时](https://cf02.ickimg.com/bbsimages/202009/ea7c85b3ceb87013160bebf228ea370f.jpg) 3号通道,当摇杆往上摇到底时,油门为最大,占空比约为11.2%,高电平时长为2.01ms,信号周期为18ms。 3号通道油门为100,摇杆往上摇到最大时: ![3号通道油门为0,摇杆往上摇到最大时:](https://cf02.ickimg.com/bbsimages/202009/080dcf94ee7e61439f7d03834b1f4721.jpg) 那当他们居中时呢?占空比约为8.3%,高电平时长为1.50ms,信号周期还是为18ms。 摇杆居中时: ![摇杆居中时](https://cf02.ickimg.com/bbsimages/202009/a0b487bb90c335b50817a5d5b0041307.jpg) **5号通道为开关,下中上三档,与1/3通道的高中低三档时的数值一样,占空比依次约为11.2%、8.3%、5.5%,高电平时长依次约为2ms、1.5ms、1ms,信号周期一直是稳定的18ms。** 开关为下档时: ![开关为下档时](https://cf02.ickimg.com/bbsimages/202009/819c0a21bc429f21b0d535f4af7fd9ee.jpg) 开关为中档时: ![开关为中档时](https://cf02.ickimg.com/bbsimages/202009/103c6bef5d281eb8e9746efc822138ec.jpg) 开关为上档时: ![开关为上档时](https://cf02.ickimg.com/bbsimages/202009/a087d18e4a01f00dcc98ed0632dbd44f.jpg) 在采集接收机PWM信号时发现,当接收机刚通电时,接收机不输出PWM信号,当遥控器连接成功接收机后,接收机就立马输出遥控器的即时状态信号,所以请注意,连接之前请注意将油门调至0,否则如果电调没有保护机制,螺旋桨会立马飞起来。 无线电波在传输过程中可能受到干扰或是数据丢失等等问题,当接收机无法接收到发射器的数据时,通常会进入保护状态,也就是仍旧向无人机发送控制信号,此时的信号就是接收机收到遥控器发射器最后一次的有效数据。这样因为信号丢失而发送的保护数数据通常叫做failsafe数据。 如果遥控器没有设置failsafe mode,X8R接收机默认HOLD模式,即保持断联之前的信号一直输出;可以在遥控器上设置No pulses模式,指断联后接收机不输出信号;可以在遥控器上设置Custom模式,定制断联后接收机要输出的控制信号,比如降低油门到比较低的程度,以便飞机自动降落。 树莓派输出PWM信号很简单,但是如果我们需要使用树莓派来读取接收机输出的PWM信号值怎么办呢? 我们以第一个通道的PWM为例,讲述树莓派对其处理的具体方法: (1)检测引脚由低点平变为高电平的时刻,并记录当前时间t0,表示高电平开始; (2)检测引脚由高电平变为低点平的时刻,并记录当前时间t1,表示高电平结束; (3)继续检测引脚由低点平变为高电平的时刻,并记录当前时间t2,表示一个PWM周期结束; (4)计算高电平时长 = t1 - t0; (5)计算整个PWM周期 = t2 - t0; (6)计算PWM占空比 = 高电平时长 / PWM周期 每一个遥控器通道都需要一个PWM采集器进行采集,但是对于树莓派来说不可能使用多个定时器来采集多个通道的PWM,这对于树莓派的资源来说十分浪费,因此我优先采用的就是SBUS编码,可以在一个管脚中传输多路控制信号。 ####(三)SBUS信号 #####1.介绍 S.BUS是FUTABA提出的舵机控制总线,全称Serial Bus,别名S-BUS或SBUS,也称 Futaba S.BUS。 S-BUS其实是一种串口通信协议,采用100000的波特率,数据位点8bits,停止位点2bits,偶效验,即8E2的串口通信。但是S-BUS采用的是反向电平传输,也就是说,在S-BUS的发送端高低电平是反向的,协议中的所有高电平都被转换成低电平,协议中的所有低电平都被转换成高电平。所以在S-BUS的接收端需要增加一个高低电平反向器来进行电平反转。 ![高低电平反向器](https://cf02.ickimg.com/bbsimages/202009/89b401b82c3bcf48421c2a3e26e08e40.jpg) 实际上,有的飞控板上已经集成了反向器,所以对于使用这种飞控的用户来说,可以忽略掉S-BUS的反向机制,但是对于其它没有集成S-BUS反向器的硬件平台上,就需要使用者增加一个反向器来处理数据,否则将无法读取协议数据。 另外,100000的波特率并不是标准的波特率,这在一些只支持标准波特率的系统上无法实现,我们可以通过对设备节点的配置实现波特率的设定。 **通信接口:**USART(TTL) **通信参数:**1个起始位+8个数据位+偶校验位+2个停止位,无控流,25个字节,波特率=100000bit/s,电平逻辑反转。 **X6R的SBUS通信速率:**每6ms间隔发送数据,每数据帧时长为3ms。 **数据帧格式:** 需要注意的是S-BUS中用11bits来表示一个遥控器通道的数值,22个字节就可以表示16通道(8 × 22 = 11 ×16)。11个bit可以表示的数值范围为0~2047。 每帧25个字节,排列如下: [start byte] [data1] [data2] [data3] ... [data22] [flag] [end byte] ![SBUS帧格式](https://cf02.ickimg.com/bbsimages/202009/6a6d7bf026e05e7ec0efddd0cdd0a002.jpg) 简单来说就是,**通道1数据在前,通道16数据最后;每通道的数据,低位在前面的字节中,高位在后面的字节中;每8bit数据中,低位是上一通道的数据,高位是下一通道的数据。** start byte = 0x0F CH1 = [data2]的低3位 + [data1]的8位 (678 + 12345678 = 678,12345678) CH2 = [data3]的低6位 + [data2]的高5位 (345678 + 12345 = 345678,12345 ) CH3 = [data5]的低1位 + [data4]的8位 + [data3]的高2位 (8 + 12345678 + 12 = 8,12345678,12) ... ... flag(由高位到低位:N/A N/A N/A N/A 故障保护激活位 帧丢失位 数字通道CH18 数字通道CH17 ) end byte = 0x00 #####2.未做电平反向时的SBUS信号 ![未做电平反向时的SBUS信号](https://cf02.ickimg.com/bbsimages/202009/e9ba414c314608807b8e726cd81e7cdc.jpg) 可以看出字节数不对,只解析出23字节,起始字节不是正确的0x0F,而是0xF8,还有红色的PE(Frame error)帧错误,即是乱码。 未做电平反向时的起始字节: ![未做电平反向时的起始字节](https://cf02.ickimg.com/bbsimages/202009/6c99423a20f315198912dcc43860a08a.png) 未做电平反向时的结束字节: ![未做电平反向时的结束字节](https://cf02.ickimg.com/bbsimages/202009/8dd19c2d1c18b5d50886a42af6c94c11.jpg) #####3.电平反向后的SBUS信号 电平反向后的SBUS信号: ![电平反向后的SBUS信号](https://cf02.ickimg.com/bbsimages/202009/44190dde9d7c3496d2e51d4ef58f9ca3.jpg) 可以看出一帧数据为25字节,起始字节是正确的0x0F,结束字节为0x00。 再详细分析起始字节,要搞清楚每个字节的含义,先弄清UART的数据通信的字节格式: ![字节格式](https://cf02.ickimg.com/bbsimages/202009/40ba090efcf0dd107d2b79c50b920477.jpg) 其中各位的意义如下: 起始位:先发出一个逻辑”0”信号,表示传输字符的开始。 数据位:可以是5~8位逻辑”0”或”1”。如ASCII码(7位),扩展BCD码(8位),小端传输。 校验位:数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验) 停止位:它是一个字符数据的结束标志。可以是1位、1.5位、2位的高电平。 空闲位:处于逻辑“1”状态,表示当前线路上没有资料传送。 传输方向:即数据是从高位(MSB)开始传输还是从低位(LSB)开始传输,X8R是从低位开始传输的。 电平反向后的起始字节: ![电平反向后的起始字节](https://cf02.ickimg.com/bbsimages/202009/f18c968c48735da2e8f9da14ce62c6af.jpg) **波特率:**上图中可以看出每位的时长是10us,意思就是每秒传输100000比特位数(bit),即波特率为100000。 **起始位:**先发出一个逻辑”0”的信号,即低电平,表示传输数据的开始。 **数据位:**SBUS信号明显为8位。 **校验位:**数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以此来校验数据传送的正确性。SBUS为偶校验,起始字节数据位中已有4个“1”,所以偶校验位为0。 **停止位:**它是一帧数据的结束标志。可以是1bit、1.5bit、2bit的空闲电平。SBUS信号是2位停止位,即2位高电平。 **空闲位:**没有数据传输时线路上的电平状态。为逻辑1。 **传输方向:**uart传输数据的顺序就是:刚开始传输一个起始位,接着传输数据位,接着传输校验位(可不需要此位),最后传输停止位。这样一帧的数据就传输完了。所以上图中Bits显示的11110000,是从左到右是由低到高位显示的,其值实际上是B00001111=0x0F。 **帧间隔:**即传送数据的帧与帧之间的间隔大小,这里的间隔为6ms,每帧的周期可以以位为计量也可以用时间,(起始1位+数据8位+校验1位+中止2位=12位) x 25字节=300位,每位时长为10us x 300位=3000us=3ms。 帧与帧之间的间隔为6ms: ![帧与帧之间的间隔为6ms](https://cf02.ickimg.com/bbsimages/202009/f4a23e409490012e3b7c94549f6252b9.jpg) 每帧数据时长为2.990ms,有10us的误差,应该是3ms: ![每帧数据时长为3ms](https://cf02.ickimg.com/bbsimages/202009/e8582777c73d2f82afa45a194718b0da.jpg) #####4. 关闭遥控器后接收机的反应 为模拟接收机与遥控器失联后的状态,关闭遥控器的过程中,用逻辑分析仪分析了第24个字节的变化情况,在断开连接的前900ms内,帧丢失位由0变为1,即第24个字节值为0x04。 帧丢失位由0变为1: ![帧丢失位由0变为1](https://cf02.ickimg.com/bbsimages/202009/3481b808211b5e6ebb05173ae41bd814.jpg) 之后,故障保护激活位由0变为1,帧丢失位仍为1,即第24个字节值为0x0C,此时如果设置了failsafe数据,接收机就按照failsafe数据输出信号。 故障保护激活位也变为1: ![故障保护激活位也变为1](https://cf02.ickimg.com/bbsimages/202009/980dc87783ed146ee837be91fa833136.jpg) 比如我设置的为No pulses(无脉冲),所有的通道值变为0。 无脉冲: ![无脉冲](https://cf02.ickimg.com/bbsimages/202009/72f30dc1d92b7fbff9d7811fa49fbb76.jpg) ## 四、实验步骤 ####(一) 树莓派解析接收机PWM信号 1) 连接线路。将接收机的1/3/5通道分别连接到树莓派面包板上的G17、G18、G19上,接收机的电源+、-接5V和GND。 | 树莓派(name) | T型转接板(BCM) | 接收机 | |:-:|:-:|:-:| |GPIO.0|G17|Channel 1(SIG)| |GPIO.1|G18|Channel 3(SIG)| |GPIO.24|G19|Channel 5(SIG)| |5V|5V|+| |GND|GND|-| 连线很简单,电路图就没画了,接收机上端接出的两个黑色细长薄片是天线。 树莓派解析接收机PWM信号接线图: ![树莓派解析接收机PWM信号接线图](https://cf02.ickimg.com/bbsimages/202009/1ab5dc157480f0d8ced0987e8c6beb6b.jpg) 2) 编写树莓派解析PWM信号的程序。为了不至于结果刷新太快,为了便于观察,我设置了每次采集信号0.5秒的延迟,在实际信号使用过程中,显然是不用的。 ```python #!/usr/bin/env python import RPi.GPIO as GPIO import time channel_1 = 17 #接收机1通道连接树莓派G17针脚 channel_3 = 18 #接收机3通道连接树莓派G18针脚 channel_5 = 19 #接收机5通道连接树莓派G19针脚 def setup(): GPIO.setmode(GPIO.BCM) GPIO.setup(channel_1, GPIO.IN) GPIO.setup(channel_3, GPIO.IN) GPIO.setup(channel_5, GPIO.IN) def duty_cycle_collect(pin): #等待低电平结束,然后记录时间 while GPIO.input(pin) == 0: #捕捉信号端输出上升沿 pass time1 = time.time() #等待高电平结束,然后记录时间 while GPIO.input(pin) == 1: #捕捉信号端输出下降沿 pass time2 = time.time() #等待低电平结束,然后记录时间 while GPIO.input(pin) == 0: #捕捉信号端输出上升沿 pass time3 = time.time() period = time3 - time1 high_time = time2 - time1 low_time = time3 - time2 duty_cycle = high_time * 100 / period #print period return duty_cycle def loop(): while True: #调用占空比采集函数duty_cycle_collect()获得各通道的信号占空比 duty_cycle_channel_1 = duty_cycle_collect(channel_1) print 'duty_cycle_channel_1 =',duty_cycle_channel_1 duty_cycle_channel_3 = duty_cycle_collect(channel_3) print 'duty_cycle_channel_3 =',duty_cycle_channel_3 duty_cycle_channel_5 = duty_cycle_collect(channel_5) print 'duty_cycle_channel_5 =',duty_cycle_channel_5 print '' time.sl
eep(0.5) #为了便于观察结果设置了延迟 def destroy(): GPIO.cleanup() if __name__ == "__main__": setup() try: loop() except KeyboardInterrupt: destroy() ``` 3) 测试成功获取接收机PWM信号的占空比。1/5通道的遥控均在中位,所以占空比约为8.3%,与逻辑分析仪的结果一致;3通道是油门,由大到小滑动时,得到的占空比结果11.2%降至5.5%,与逻辑分析仪的结果一致。但是少数测量结果会有偏差,极少数情况偏差较大。 pwm_analyse: ![pwm_analyse](https://cf02.ickimg.com/bbsimages/202009/880fcd8e2946175bf6fdb9c1ab50425c.jpg) 4) 当遥控器与接收机失联时,我定制了failsafe数据,油门降低。3号通道的占空比在开始失联的时候有抖动,约3秒钟后稳定在设置的6.3%左右。 失联的时候有抖动: ![pwm_analyse_mistake](https://cf02.ickimg.com/bbsimages/202009/25b11cb5901962ee512b18a1e9d382f5.jpg) ####(二) 分析接收机SBUS信号 1) 连接电路。与[树莓派基础实验36:通用串口通信实验](https://www.icxbk.com/article/detail?aid=1697)一样设置树莓派的串口为通用串口,恢复硬件串口(/dev/ttyAMA0)与GPIO 14/15的映射关系,使得我们能够通过GPIO使用高性能的硬件串口来连接我们的SBUS信号输入。 | T型转接板(BCM) | 接收机 | 电平反向模块 |DSlogic逻辑分析仪 | |:-:|:-:|:-:|:-:| |-|SBUS|A6|-| |-|-|B6|Channel 1(SIG)| |3.3V|-|3.3V|-| |5V|5V|-|-| |GND|GND|GND|Channel 0(GND)| 电平反相模块很便宜,某宝5元一个能买到6路的电平反相器。注意反向后的高电平是几伏,反相器的VCC就接几伏的电源,树莓派GPIO接收3.3V高电平,不能接收5V高电平,所以这里电平反向模块的VCC只能接3.3V电源。 电平反相器: ![电平反相器](https://cf02.ickimg.com/bbsimages/202009/8a1fa28ebe4673e604ba4cf1c3154849.jpg) 解析SBUS信号接线图: ![解析SBUS信号接线图](https://cf02.ickimg.com/bbsimages/202009/5069d720f336f760092c1bd33fdde6c9.jpg) 2) 这里树莓派要使用pyserial模块编程接收SBUS信号,有关基础可以参考[树莓派基础实验37:pyserial模块通信实验](https://www.icxbk.com/article/detail?aid=1726)。下面的程序我做了详细注释,如果有更快更好的代码,请留言。 ```python #!/usr/bin/env python #-*- coding: utf-8 -*- import array #array模块是python中实现的一种高效的数组存储类型 import serial #serial模块封装了对串行端口的访问 import codecs #Python中专门用作编码转换的模块 import time class SBUSReceiver(): def __init__(self, _uart_port='/dev/ttyAMA0'): #初始化树莓派串口参数 self.ser = serial.Serial( port=_uart_port, #树莓派的硬件串口/dev/ttyAMA0 baudrate = 100000, #波特率为100k parity=serial.PARITY_EVEN, #偶校验 stopbits=serial.STOPBITS_TWO,#2个停止位 bytesize=serial.EIGHTBITS, #8个数据位 timeout = 0, ) # 常数 self.START_BYTE = b'\x0f' #起始字节为0x0f self.END_BYTE = b'\x00' #结束字节为0x00 self.SBUS_FRAME_LEN = 25 #SBUS帧有25个字节 self.SBUS_NUM_CHAN = 18 #18个通道 self.OUT_OF_SYNC_THD = 10 self.SBUS_NUM_CHANNELS = 18 #18个通道 self.SBUS_SIGNAL_OK = 0 #信号正常为0 self.SBUS_SIGNAL_LOST = 1 #信号丢失为1 self.SBUS_SIGNAL_FAILSAFE = 2 #输出failsafe信号时为2 # 堆栈变量初始化 self.isReady = True self.lastFrameTime = 0 self.sbusBuff = bytearray(1) # 用于同步的单个字节 #bytearray(n) 方法返回一个长度为n的初始化数组; self.sbusFrame = bytearray(25) # 单个SBUS数据帧,25个字节 self.sbusChannels = array.array('H', [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) # 接收到的各频道值 #array.array(typecode,[initializer]) --typecode:元素类型代码;initializer:初始化器,若数组为空,则省略初始化器 self.failSafeStatus = self.SBUS_SIGNAL_FAILSAFE def get_rx_channels(self): """ 用于读取最后的SBUS通道值 返回:由18个无符号短元素组成的数组,包含16个标准通道值+ 2个数字(ch17和18) """ return self.sbusChannels def get_rx_channel(self, num_ch): """ 用于读取最后的SBUS某一特定通道的值 num_ch: 要读取的某个通道的通道序号 返回:某一通道的值 """ return self.sbusChannels[num_ch] def get_failsafe_status(self): """ 用于获取最后的FAILSAFE状态 返回: FAILSAFE状态值 """ return self.failSafeStatus def decode_frame(self): """ 对每帧数据进行解码,每个通道的值在两个或三个不同的字节之间,要读取出来很麻烦 不过futaba已经发布了下面的解码代码 """ def toInt(_from): #encode() 方法以指定的编码格式编码字符串。 #int() 函数用于将一个字符串或数字转换为整型。 return int(codecs.encode(_from, 'hex'), 16) #CH1 = [data2]的低3位 + [data1]的8位(678+12345678 = 678,12345678) self.sbusChannels[0] = ((toInt(self.sbusFrame[1]) |toInt(self.sbusFrame[2])<<8) & 0x07FF); #CH2 = [data3]的低6位 + [data2]的高5位(345678+12345 = 345678,12345 ) self.sbusChannels[1] = ((toInt(self.sbusFrame[2])>>3 |toInt(self.sbusFrame[3])<<5) & 0x07FF); #CH3 = [data5]的低1位 + [data4]的8位 + [data3]的高2位(8+12345678+12 = 8,12345678,12) self.sbusChannels[2] = ((toInt(self.sbusFrame[3])>>6 |toInt(self.sbusFrame[4])<<2 |toInt(self.sbusFrame[5])<<10) & 0x07FF); self.sbusChannels[3] = ((toInt(self.sbusFrame[5])>>1 |toInt(self.sbusFrame[6])<<7) & 0x07FF); self.sbusChannels[4] = ((toInt(self.sbusFrame[6])>>4 |toInt(self.sbusFrame[7])<<4) & 0x07FF); self.sbusChannels[5] = ((toInt(self.sbusFrame[7])>>7 |toInt(self.sbusFrame[8])<<1 |toInt(self.sbusFrame[9])<<9) & 0x07FF); self.sbusChannels[6] = ((toInt(self.sbusFrame[9])>>2 |toInt(self.sbusFrame[10])<<6) & 0x07FF); self.sbusChannels[7] = ((toInt(self.sbusFrame[10])>>5 |toInt(self.sbusFrame[11])<<3) & 0x07FF); self.sbusChannels[8] = ((toInt(self.sbusFrame[12]) |toInt(self.sbusFrame[13])<<8) & 0x07FF); self.sbusChannels[9] = ((toInt(self.sbusFrame[13])>>3 |toInt(self.sbusFrame[14])<<5) & 0x07FF); self.sbusChannels[10] = ((toInt(self.sbusFrame[14])>>6 |toInt(self.sbusFrame[15])<<2|toInt(self.sbusFrame[16])<<10) & 0x07FF); self.sbusChannels[11] = ((toInt(self.sbusFrame[16])>>1 |toInt(self.sbusFrame[17])<<7) & 0x07FF); self.sbusChannels[12] = ((toInt(self.sbusFrame[17])>>4 |toInt(self.sbusFrame[18])<<4) & 0x07FF); self.sbusChannels[13] = ((toInt(self.sbusFrame[18])>>7 |toInt(self.sbusFrame[19])<<1|toInt(self.sbusFrame[20])<<9) & 0x07FF); self.sbusChannels[14] = ((toInt(self.sbusFrame[20])>>2 |toInt(self.sbusFrame[21])<<6) & 0x07FF); self.sbusChannels[15] = ((toInt(self.sbusFrame[21])>>5 |toInt(self.sbusFrame[22])<<3) & 0x07FF); #17频道,第24字节的最低一位 if toInt(self.sbusFrame[23]) & 0x0001 : self.sbusChannels[16] = 2047 else: self.sbusChannels[16] = 0 #18频道,第24字节的低第二位,所以要右移一位 if (toInt(self.sbusFrame[23]) >> 1) & 0x0001 : self.sbusChannels[17] = 2047 else: self.sbusChannels[17] = 0 #帧丢失位为1时,第24字节的低第三位,与0x04进行与运算 self.failSafeStatus = self.SBUS_SIGNAL_OK if toInt(self.sbusFrame[23]) & (1 << 2): self.failSafeStatus = self.SBUS_SIGNAL_LOST #故障保护激活位为1时,第24字节的低第四位,与0x08进行与运算 if toInt(self.sbusFrame[23]) & (1 << 3): self.failSafeStatus = self.SBUS_SIGNAL_FAILSAFE def update(self): """ 我们需要至少2帧大小,以确保找到一个完整的帧 所以我们取出所有的缓存(清空它),读取全部数据,直到捕获新的数据 首先找到END BYTE并向后查找SBUS_FRAME_LEN,看看它是否是START BYTE """ #我们是否有足够的数据在缓冲区和有没有线程在后台? if self.ser.inWaiting() >= self.SBUS_FRAME_LEN*2 and self.isReady: #inWaiting()返回接收缓存中的字节数 self.isReady = False #表明有线程在运行,isReady = False # 读取所有临时帧数据 tempFrame = self.ser.read(self.ser.inWaiting()) # 在缓冲区帧的每个字符中,我们寻找结束字节 for end in range(0, self.SBUS_FRAME_LEN): #寻找结束字节,从后向前查找 if tempFrame[len(tempFrame)-1-end] == self.END_BYTE : #从最后的命中点减去SBUS_FRAME_LEN寻找起始字节 if tempFrame[len(tempFrame)-end-self.SBUS_FRAME_LEN] == self.START_BYTE : # 如果相等,则帧数据正确,数据以8E2包到达,因此它已经被校验过 # 从临时帧数据中取出刚验证正确的一段正确帧数据 lastUpdate = tempFrame[len(tempFrame)-end-self.SBUS_FRAME_LEN:len(tempFrame)-1-end] if not self.sbusFrame == lastUpdate: #相等即表示没有操作,不用再次解码 self.sbusFrame = lastUpdate self.decode_frame() #调用解码函数 self.lastFrameTime = time.time() # 跟踪最近的更新时间 self.isReady = True break if __name__ == '__main__': sbus = SBUSReceiver('/dev/ttyAMA0') while True: time.sl
eep(0.005) # X8R的SBUS信号是间隔6ms发送一次,一次持续发送3ms; # 不要调用sbus.update()太快,如果sbus.ser.inWaiting()>50,且增长很多,可以调用sbus.update()快点,即time.sleep()延迟短点; # 如果sbus.ser.inWaiting()<50,可以调用sbus.update()慢点,即time.sleep()延迟长点; sbus.update() #在您的代码中,您可以调用sbus.get_rx_channels()来获取所有数据,或者调用sbus.get_rx_channels()[n]来获取第n个通道的值; #或get_rx_channel(self, num_ch)来获得第num_ch个通道的值; print sbus.get_failsafe_status(), sbus.get_rx_channels(), str(sbus.ser.inWaiting()).zfill(4) , (time.time()-sbus.lastFrameTime) #str() 函数将对象转化为适于人阅读的形式,将指定的值转换为字符串。 #zfill() 方法返回指定长度的字符串,原字符串右对齐,前面填充0。 #(time.time()-sbus.lastFrameTime)用于展示得到最近这次数据的延迟 ``` 3) 运行程序后,依次打印出了failsafe状态值、所有通道的10进制数组、读取缓存中的字节数、当次数据更新的延迟时间。控制遥控器摇杆晃动,能够及时得到该通道的数值变化。 实验结果: ![实验结果](https://cf02.ickimg.com/bbsimages/202009/bc32380c7115df3e3bac1c72f25c3a6a.gif) 4) 从实验数据中可以看出,三个档位的通道的下档值为172,中间档位时值为992,上档位时值为1811;2个档位的下档值为172,上档值为1811,摇杆在中间位置时值为992,向其它方向摇动时,数值向172或1811变化。 5) 使用上面的数值,通过函数转换,就可以输出相应通道的PWM控制信号,或者其它开关控制信号了!为什么不直接使用PWM输出呢?因为这样可以通过无线电远距离控制树莓派了,再通过树莓派编程,控制其他设备,比如树莓派无人机或者树莓派智能小车,特别是在没有移动网络信号的时候。 遥控器的数字通道17/18没有搞明白怎么用,所以这里没有能够测试,有知道的同学可以留言。 ![](https://cf02.ickimg.com/bbsimages/202009/59cdaeb48c9b0334482e13ad86802ed3.jpg)
原创作品,未经权利人授权禁止转载。详情见
转载须知
。
举报文章
点赞
(
1
)
张国平
关注
评论
(0)
登录后可评论,请
登录
或
注册
相关文章推荐
MK-米客方德推出工业级存储卡
Beetle ESP32 C3 蓝牙数据收发
Beetle ESP32 C3 wifi联网获取实时天气信息
开箱测评Beetle ESP32-C3 (RISC-V芯片)模块
正点原子数控电源DP100测评
DP100试用评测-----开箱+初体验
Beetle ESP32 C3环境搭建
【花雕体验】16 使用Beetle ESP32 C3控制8X32位WS2812硬屏之二
X
你的打赏是对原创作者最大的认可
请选择打赏IC币的数量,一经提交无法退回 !
100IC币
500IC币
1000IC币
自定义
IC币
确定
X
提交成功 ! 谢谢您的支持
返回
我要举报该内容理由
×
广告及垃圾信息
抄袭或未经授权
其它举报理由
请输入您举报的理由(50字以内)
取消
提交