树莓派基础实验39:解析无线电接收机PWM、SBUS信号 您所在的位置:网站首页 pwm与ppm信号 树莓派基础实验39:解析无线电接收机PWM、SBUS信号

树莓派基础实验39:解析无线电接收机PWM、SBUS信号

2024-05-21 04:40| 来源: 网络整理| 查看: 265

一、介绍

  虽然如今或者将来,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个通道,数据包较大,串口通信有两种模式,可以在遥控器的配置选项中配置。接收机无需做特殊配置。

睿思凯Frsky Taranis X9D PLUS SE2019遥控器

睿思凯Frsky X8R 接收机

然后,就是电调通过接收接收机输出的这些信号,来将输入的电源转为不同的电压,并输出到电机,从而达到使电机产生不同的转速的目的。有刷电调可以改变电流方向,从而可以改变电机转动方向。而无刷电调却不能改变电机的转动方向,但是可以将直流电转为三相交流电,从而输出到无刷电机上。

所谓电调就是电压调节器,也可以通俗的说成是电机调节器,这里不做过多讲解。

(二)接收机的PWM信号

PWM英文全称为(Pulse-width modulation)。也称占空比信号,它表示高电平时长占整个信号周期的比例。

PWM周期

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号通道摇杆往左摇到底时

1号通道,当摇杆往右摇到底时,占空比约为11.2%,高电平时长为2.01ms,信号周期为18ms。

1号通道摇杆往右摇到底时

再看3号通道,当摇杆往下摇到底时,油门为0,占空比约为5.5%,高电平时长为0.99ms,信号周期为18ms。

3号通道油门为0,摇杆往下摇到底时

3号通道,当摇杆往上摇到底时,油门为最大,占空比约为11.2%,高电平时长为2.01ms,信号周期为18ms。

pwm_acc_100.jpg

那当他们居中时呢?占空比约为8.3%,高电平时长为1.50ms,信号周期还是为18ms。

pwm_center.jpg

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 data2 ... data22 end byte

SBUS帧格式

简单来说就是,通道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信号

可以看出字节数不对,只解析出23字节,起始字节不是正确的0x0F,而是0xF8,还有红色的PE(Frame error)帧错误,即是乱码。

未做电平反向时的起始字节

未做电平反向时的结束字节

3.电平反向后的SBUS信号

电平反向后的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。

帧与帧之间的间隔为6ms

每帧数据时长为2.990ms,有10us的误差,应该是3ms:

每帧数据时长为3ms

4. 关闭遥控器后接收机的反应

为模拟接收机与遥控器失联后的状态,关闭遥控器的过程中,用逻辑分析仪分析了第24个字节的变化情况,在断开连接的前900ms内,帧丢失位由0变为1,即第24个字节值为0x04。

帧丢失位由0变为1

之后,故障保护激活位由0变为1,帧丢失位仍为1,即第24个字节值为0x0C,此时如果设置了failsafe数据,接收机就按照failsafe数据输出信号。

故障保护激活位也变为1

比如我设置的为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信号接线图

编写树莓派解析PWM信号的程序。为了不至于结果刷新太快,为了便于观察,我设置了每次采集信号0.5秒的延迟,在实际信号使用过程中,显然是不用的。代码语言:javascript复制#!/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%,与逻辑分析仪的结果一致。但是少数测量结果会有偏差,极少数情况偏差较大。

pwm_analyse

当遥控器与接收机失联时,我定制了failsafe数据,油门降低。3号通道的占空比在开始失联的时候有抖动,约3秒钟后稳定在设置的6.3%左右。

pwm_analyse_mistake

(二) 分析接收机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电源。

电平反相器

解析SBUS信号接线图

这里树莓派要使用pyserial模块编程接收SBUS信号,有关基础可以参考树莓派基础实验37:pyserial模块通信实验。下面的程序我做了详细注释,如果有更快更好的代码,请留言。代码语言:javascript复制#!/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])3 |toInt(self.sbusFrame[3])6 |toInt(self.sbusFrame[4])


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有