一、介绍
虽然如今或者将来,5G网络的建设带来人工智能和工业自动化的全面升级,生产活动中劳动力的需求大大减少,大量的劳动力将向内容生产行业和服务行业转移。教育、医疗、娱乐、公共管理等诸多领域,乃至整个社会都将迎来巨大变革。可参阅我的一篇读书笔记5G社会:万物互联新时代。
但是,使用传统无线电通信设备通信仍然是非常重要的通信方式,比如无线电台、对讲机,航模、车模、船模遥控等等。与手机移动网络、WIFI连接相比,无线电连接有它独特的优势。
在树莓派基础实验38:逻辑分析仪分析PWM、UART信号中使用逻辑分析仪,对树莓派的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
★跳线若干
三、实验原理
(一)航模无线电遥控系统
本实验中使用的遥控系统可以自行选择其它品牌的产品,如国产的天地飞还不错。
航模的遥控器就是像电视机遥控器、空调遥控器一样可以不用接触到被控设备,而通过一个手持器件,使用无线电与被控设备进行通信,从而达到对设备的控制。
遥控器想到达到与无人机通信的功能需要有两部分配合完成。即:发射器(遥控器)与接收机。遥控器上的控制杆转为无线电波发送给接收机,而接收机通过接收无线电波,读取遥控器上控制杆的读数,并转为数字信号发送到航模的控制器中。
目前用于无人机遥控器主流的无线电频率是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个通道,数据包较大,串口通信有两种模式,可以在遥控器的配置选项中配置。接收机无需做特殊配置。
然后,就是电调通过接收接收机输出的这些信号,来将输入的电源转为不同的电压,并输出到电机,从而达到使电机产生不同的转速的目的。有刷电调可以改变电流方向,从而可以改变电机转动方向。而无刷电调却不能改变电机的转动方向,但是可以将直流电转为三相交流电,从而输出到无刷电机上。
所谓电调就是电压调节器,也可以通俗的说成是电机调节器,这里不做过多讲解。
(二)接收机的PWM信号
PWM英文全称为(Pulse-width modulation)。也称占空比信号,它表示高电平时长占整个信号周期的比例。
PWM信号的频率是通常是没有规定的,可以是50hz、100hz、200hz或500hz等等。控制频率越高,其周期越短,控制间隔也就越短,电调和电机响应速度也就越快。反之,控制频率越低,其周期就越长,控制间隔就越长,电调和电机的响应速度就越慢。早期电调响应PWM信号的频率是50hz,但随着科技的发展和对控制流畅度的要求,现在多数电调都支持500hz以上的PWM信号,并且电调内部自带滤波器,可以很好的响应并控制电机的转动。
传统的遥控器接收机是采用多路PWM的方式进行输出的,遥控器中有多少个通道,接收机中就有多少路PWM输出,睿思凯Frsky X8R接收机的1-8个PWM输出通道,都是以PWM的形式输出的,这就需要飞控能够采集并解析这些PWM信号,并为飞控所用。
那么,睿思凯Frsky X8R接收机的PWM信号到底是怎样的呢?我们使用逻辑分析仪看看吧,连接好遥控器、接收机、连接逻辑分析仪。
这里我只采集了1、3、5号通道的PWM信号。1号通道是右手油门摇杆左右晃动,会自动回中;3号通道是右手油门摇杆油门控制,由低到高表示油门由小到大,不会回中;5号通道是SA开关,有上中下3个档位。
首先来看1号通道,当摇杆往左摇到底时,占空比约为5.5%,高电平时长为0.99ms,信号周期为18ms。
1号通道,当摇杆往右摇到底时,占空比约为11.2%,高电平时长为2.01ms,信号周期为18ms。
再看3号通道,当摇杆往下摇到底时,油门为0,占空比约为5.5%,高电平时长为0.99ms,信号周期为18ms。
3号通道,当摇杆往上摇到底时,油门为最大,占空比约为11.2%,高电平时长为2.01ms,信号周期为18ms。
那当他们居中时呢?占空比约为8.3%,高电平时长为1.50ms,信号周期还是为18ms。
5号通道为开关,上中下三档,与1/3通道的高中低三档时的数值一样,占空比依次约为11.2%、8.3%、5.5%,高电平时长依次约为2ms、1.5ms、1ms,信号周期一直是稳定的18ms。
在采集接收机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的接收端需要增加一个高低电平反向器来进行电平反转。
实际上,有的飞控板上已经集成了反向器,所以对于使用这种飞控的用户来说,可以忽略掉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]
简单来说就是,通道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信号
可以看出字节数不对,只解析出23字节,起始字节不是正确的0x0F,而是0xF8,还有红色的PE(Frame error)帧错误,即是乱码。
3.电平反向后的SBUS信号
可以看出一帧数据为25字节,起始字节是正确的0x0F,结束字节为0x00。
再详细分析起始字节,要搞清楚每个字节的含义,先弄清UART的数据通信的字节格式:
其中各位的意义如下:
起始位:先发出一个逻辑”0”信号,表示传输字符的开始。
数据位:可以是5~8位逻辑”0”或”1”。如ASCII码(7位),扩展BCD码(8位),小端传输。
校验位:数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验)
停止位:它是一个字符数据的结束标志。可以是1位、1.5位、2位的高电平。
空闲位:处于逻辑“1”状态,表示当前线路上没有资料传送。
传输方向:即数据是从高位(MSB)开始传输还是从低位(LSB)开始传输,X8R是从低位开始传输的。
波特率:上图中可以看出每位的时长是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。
每帧数据时长为2.990ms,有10us的误差,应该是3ms:
4. 关闭遥控器后接收机的反应
为模拟接收机与遥控器失联后的状态,关闭遥控器的过程中,用逻辑分析仪分析了第24个字节的变化情况,在断开连接的前900ms内,帧丢失位由0变为1,即第24个字节值为0x04。
之后,故障保护激活位由0变为1,帧丢失位仍为1,即第24个字节值为0x0C,此时如果设置了failsafe数据,接收机就按照failsafe数据输出信号。
比如我设置的为No pulses(无脉冲),所有的通道值变为0。
四、实验步骤
(一) 树莓派解析接收机PWM信号
- 连接线路。将接收机的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信号的程序。为了不至于结果刷新太快,为了便于观察,我设置了每次采集信号0.5秒的延迟,在实际信号使用过程中,显然是不用的。
#!/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.sleep(0.5) #为了便于观察结果设置了延迟
def destroy():
GPIO.cleanup()
if __name__ == "__main__":
setup()
try:
loop()
except KeyboardInterrupt:
destroy()
-
测试成功获取接收机PWM信号的占空比。1/5通道的遥控均在中位,所以占空比约为8.3%,与逻辑分析仪的结果一致;3通道是油门,由大到小滑动时,得到的占空比结果11.2%降至5.5%,与逻辑分析仪的结果一致。但是少数测量结果会有偏差,极少数情况偏差较大。
- 当遥控器与接收机失联时,我定制了failsafe数据,油门降低。3号通道的占空比在开始失联的时候有抖动,约3秒钟后稳定在设置的6.3%左右。
(二) 分析接收机SBUS信号
- 连接电路。与树莓派基础实验36:通用串口通信实验一样设置树莓派的串口为通用串口,恢复硬件串口(/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电源。
- 这里树莓派要使用pyserial模块编程接收SBUS信号,有关基础可以参考树莓派基础实验37:pyserial模块通信实验。下面的程序只能在Python2.7的环境下运行,Python3上会出现问题。如果有更快更好的代码,请留言。
#!/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.sleep(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)用于展示得到最近这次数据的延迟
-
运行程序后,依次打印出了failsafe状态值、所有通道的10进制数组、读取缓存中的字节数、当次数据更新的延迟时间。控制遥控器摇杆晃动,能够及时得到该通道的数值变化。
从实验数据中可以看出,三个档位的通道的下档值为172,中间档位时值为992,上档位时值为1811;2个档位的下档值为172,上档值为1811,摇杆在中间位置时值为992,向其它方向摇动时,数值向172或1811变化。
使用上面的数值,通过函数转换,就可以输出相应通道的PWM控制信号,或者其它开关控制信号了!为什么不直接使用PWM输出呢?因为这样可以通过无线电远距离控制树莓派了,再通过树莓派编程,控制其他设备,比如树莓派无人机或者树莓派智能小车,特别是在没有移动网络信号的时候。
遥控器的数字通道17/18没有搞明白怎么用,所以这里没有能够测试,有知道的同学可以留言。
我的博客即将同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=3pjcxw02g6o04