一把兔、介紹
??閱讀本篇文章前建議先參考前期文章:
??樹莓派基礎(chǔ)實(shí)驗(yàn)34:L298N模塊驅(qū)動(dòng)直流電機(jī)實(shí)驗(yàn)
??樹莓派綜合項(xiàng)目2:智能小車(一)四輪驅(qū)動(dòng)
??樹莓派綜合項(xiàng)目2:智能小車(二)tkinter圖形界面控制
??《智能小車(一)四輪驅(qū)動(dòng)》中,實(shí)現(xiàn)了代碼輸入對(duì)四個(gè)電機(jī)的簡(jiǎn)單控制蚀同∠韬幔《智能小車(二)tkinter圖形界面控制》中读跷,實(shí)現(xiàn)了本地圖形界面控制小車的前進(jìn)后退、轉(zhuǎn)向和原地轉(zhuǎn)圈禾唁。
??本實(shí)驗(yàn)中將使用無(wú)線電遙控設(shè)備控制小車的前進(jìn)后退效览、轉(zhuǎn)向和原地轉(zhuǎn)圈无切。使用傳統(tǒng)無(wú)線電通信設(shè)備通信仍然是非常重要的通信方式,比如無(wú)線電臺(tái)丐枉、對(duì)講機(jī)哆键,航模、車模瘦锹、船模遙控等等籍嘹。與手機(jī)移動(dòng)網(wǎng)絡(luò)、WIFI連接相比弯院,無(wú)線電連接有它獨(dú)特的優(yōu)勢(shì)辱士,遙控距離遠(yuǎn),實(shí)時(shí)性好听绳,操作靈活颂碘,不受網(wǎng)絡(luò)信號(hào)限制。
??其它基礎(chǔ)內(nèi)容可以參考文集:樹莓派基礎(chǔ)實(shí)驗(yàn)辫红。
二凭涂、組件
★Raspberry Pi 3 B+全套*1
★睿思凱Frsky XM+ 迷你接收機(jī)*1
★電平反向器模塊*1
★睿思凱Frsky Taranis X9D PLUS SE2019遙控器*1
★L(fēng)298N擴(kuò)展板模塊*1
★智能小車底板模塊*1
★減速電機(jī)和車輪*4
★跳線若干
三、實(shí)驗(yàn)原理
本實(shí)驗(yàn)中使用的遙控系統(tǒng)可以自行選擇其它品牌的產(chǎn)品贴妻,如國(guó)產(chǎn)的天地飛還不錯(cuò)。
這里的遙控器就是像電視機(jī)遙控器蝙斜、空調(diào)遙控器一樣可以不用接觸到被控設(shè)備名惩,而通過(guò)一個(gè)手持器件,使用無(wú)線電與被控設(shè)備進(jìn)行通信孕荠,從而達(dá)到對(duì)設(shè)備的控制娩鹉。
遙控器想到達(dá)到與小車通信的功能需要有兩部分配合完成。即:發(fā)射器(遙控器)與接收機(jī)稚伍。遙控器上的控制桿轉(zhuǎn)為無(wú)線電波發(fā)送給接收機(jī)弯予,而接收機(jī)通過(guò)接收無(wú)線電波,讀取遙控器上控制桿的讀數(shù)个曙,并轉(zhuǎn)為數(shù)字信號(hào)發(fā)送到樹莓派中锈嫩。
接收機(jī)輸出兩種信號(hào),一種是模擬信號(hào)PWM垦搬,一種是SBUS數(shù)字信號(hào)呼寸。
由于每一路遙控器通道都需要一個(gè)PWM采集器進(jìn)行采集,但是對(duì)于樹莓派來(lái)說(shuō)不可能使用多個(gè)定時(shí)器來(lái)采集多個(gè)通道的PWM猴贰,這對(duì)于樹莓派的GPIO端口資源來(lái)說(shuō)十分浪費(fèi)对雪,因此我采用的就是SBUS信號(hào),可以在一個(gè)管腳中傳輸多路控制信號(hào)米绕。
S-BUS其實(shí)是一種串口通信協(xié)議瑟捣,采用100000的波特率馋艺,數(shù)據(jù)位點(diǎn)8bits,停止位點(diǎn)2bits迈套,偶效驗(yàn)捐祠,即8E2的串口通信。使用樹莓派的串口GPIO(TXD/RXD)中的RXD端口接收接收機(jī)的SBUS輸出信號(hào)交汤,解析出每路通道的控制信號(hào)雏赦,進(jìn)而控制小車行進(jìn)。
但是S-BUS采用的是反向電平傳輸芙扎,也就是說(shuō)星岗,在S-BUS的發(fā)送端高低電平是反向的,協(xié)議中的所有高電平都被轉(zhuǎn)換成低電平戒洼,協(xié)議中的所有低電平都被轉(zhuǎn)換成高電平俏橘。所以在S-BUS的接收端需要增加一個(gè)高低電平反向器來(lái)進(jìn)行電平反轉(zhuǎn)。
關(guān)于解析無(wú)線電接收機(jī)PWM圈浇、SBUS信號(hào)的更詳細(xì)內(nèi)容寥掐,請(qǐng)參考樹莓派基礎(chǔ)實(shí)驗(yàn)39:解析無(wú)線電接收機(jī)PWM、SBUS信號(hào)磷蜀。
四召耘、實(shí)驗(yàn)步驟
??第1步: 連接電路。在樹莓派綜合項(xiàng)目2:智能小車(一)四輪驅(qū)動(dòng)中的接線基礎(chǔ)上褐隆,接入電平反向器污它、無(wú)線電接收機(jī)。
樹莓派(name) | 樹莓派(BOARD) | L298N小車擴(kuò)展板 |
---|---|---|
GPIO.0 | 11 | ENA |
GPIO.2 | 13 | IN1 |
GPIO.3 | 15 | IN2 |
GPIO.1 | 12 | ENB |
GPIO.4 | 16 | IN3 |
GPIO.5 | 18 | IN4 |
GND | GND | 電池組供電負(fù)極 |
關(guān)于這里樹莓派GND庶弃、L298N小車擴(kuò)展板的電池組供電負(fù)極相連衫贬,是特殊情況下的情況,經(jīng)測(cè)試發(fā)現(xiàn):
如果樹莓派用的是充電頭供電歇攻,而L298N擴(kuò)展板用的是電池組供電固惯,這兩個(gè)負(fù)極必須相連,否則馬達(dá)不動(dòng)缴守。
如果樹莓派用的是L298N擴(kuò)展板接出來(lái)的5V供電葬毫,即兩者同一個(gè)電源,則這里不用連接斧散。
L298N小車擴(kuò)展板 | 電池組 | 樹莓派 | 電壓表頭 | 馬達(dá) |
---|---|---|---|---|
電池+(-) | 電池+(-) | |||
5V供電 | 電源接口 | |||
+(-) | +(-) | |||
T1(L后) | +(-) | |||
T2(L前) | +(-) | |||
T3(R前) | +(-) | |||
T4(R后) | +(-) |
樹莓派(name) | 樹莓派(BOARD) | 電平反向器 | 無(wú)線電接收機(jī) |
---|---|---|---|
A6 | SBUS_OUT | ||
RXD | 10 | B6 | |
3.3V | 1 | VCC | |
0V | 9 | GND | |
5V | 2 | VCC | |
0V | 14 | GND |
這里也要注意供常,由于樹莓派的GPIO只能接收3.3V的最高輸入,所以電平反相器的電源也只能使用3.3V鸡捐,若反向后接收的信號(hào)需要是5V栈暇,則電平反相器的電源就使用5V。
這里我將18650電池組換成了航模使用的格氏ACE鋰電池(3S/11.1V/2200MAH/40C)箍镜,電壓更高源祈,能給樹莓派提供更穩(wěn)定的電源煎源,動(dòng)力性也更好,效果非常不錯(cuò)香缺。
??第2步: 編寫電機(jī)的驅(qū)動(dòng)程序手销,文件名為motor_4w.py。與樹莓派綜合項(xiàng)目2:智能小車(一)四輪驅(qū)動(dòng)中的程序完全相同图张。
??該車的行進(jìn)控制與履帶車的行進(jìn)控制類似:
前進(jìn)和后退很簡(jiǎn)單锋拖,左右兩邊的方向都朝前或朝后,速度一致祸轮;
原地順時(shí)針旋轉(zhuǎn)時(shí)兽埃,左邊輪子前進(jìn),右邊輪子后退适袜,速度一致柄错;
原地逆時(shí)針旋轉(zhuǎn)時(shí),左邊輪子后退苦酱,右邊輪子前進(jìn)售貌,速度一致;
偏左前進(jìn)時(shí)疫萤,左右兩邊的方向都朝前颂跨,左輪速度比右輪速度慢一點(diǎn);
偏右前進(jìn)時(shí)扯饶,左右兩邊的方向都朝前毫捣,左輪速度比右輪速度快一點(diǎn);
偏左后退時(shí)帝际,左右兩邊的方向都朝后,左輪速度比右輪速度慢一點(diǎn)饶辙;
偏右后退時(shí)蹲诀,左右兩邊的方向都朝后,左輪速度比右輪速度快一點(diǎn)弃揽;
motor_4w.py:
#!/usr/bin/env python
#-*- coding: utf-8 -*-
#本模塊只含SMPcar()一個(gè)類脯爪,用于樹莓派對(duì)電機(jī)信號(hào)的控制
#通過(guò)GPIO輸出信號(hào),直接對(duì)某個(gè)電機(jī)的轉(zhuǎn)動(dòng)方向矿微、速度進(jìn)行控制
import RPi.GPIO as GPIO
class SMPcar():
'''控制小車四輪動(dòng)作的類'''
ENA = 11 #使能信號(hào)A痕慢,左邊兩輪
IN1 = 13 #信號(hào)輸入1
IN2 = 15 #信號(hào)輸入2
ENB = 12 #使能信號(hào)B,右邊兩輪
IN3 = 16 #信號(hào)輸入3
IN4 = 18 #信號(hào)輸入4
GPIO.setwarnings(False) #關(guān)閉警告
def setGPIO(self):
'''初始化引腳'''
GPIO.setmode(GPIO.BOARD)
GPIO.setup(SMPcar.ENA, GPIO.OUT)
GPIO.setup(SMPcar.IN1, GPIO.OUT)
GPIO.setup(SMPcar.IN2, GPIO.OUT)
GPIO.setup(SMPcar.ENB, GPIO.OUT)
GPIO.setup(SMPcar.IN3, GPIO.OUT)
GPIO.setup(SMPcar.IN4, GPIO.OUT)
def pwm(self,pwm):
'''初始化PWM(脈寬調(diào)制)涌矢,返回PWM對(duì)象'''
EN_pwm = GPIO.PWM(pwm, 500)
EN_pwm.start(0)
return EN_pwm
def changespeed(self,pwm,speed):
'''通過(guò)改變占空比改變馬達(dá)轉(zhuǎn)速'''
pwm.ChangeDutyCycle(speed)
def clockwise(self,in1_pin,in2_pin):
'''馬達(dá)順時(shí)針轉(zhuǎn)的信號(hào)'''
GPIO.output(in1_pin, 1)
GPIO.output(in2_pin, 0)
def counter_clockwise(self,in1_pin,in2_pin):
'''馬達(dá)逆時(shí)針轉(zhuǎn)的信號(hào)'''
GPIO.output(in1_pin, 0)
GPIO.output(in2_pin, 1)
def stop_car(self,in1_pin,in2_pin):
'''馬達(dá)制動(dòng)的信號(hào)'''
GPIO.output(in1_pin, 0)
GPIO.output(in2_pin, 0)
#使能信號(hào)為低電平掖举,或者高電平(占空比設(shè)為100,IN1和IN2都為0或1時(shí))馬達(dá)制動(dòng)
def destroy(self,A,B):
'''結(jié)束程序時(shí)清空GPIO狀態(tài)'''
A.stop()
B.stop()
GPIO.cleanup() # Release resource
if __name__ == '__main__': # 本模塊單獨(dú)測(cè)試時(shí)運(yùn)行使用
try:
smpcar = SMPcar() #創(chuàng)建樹莓派小車對(duì)象
smpcar.setGPIO() #初始化引腳
ENA_pwm=smpcar.pwm(smpcar.ENA) #初始化使能信號(hào)PWM娜庇,A為左邊車輪
ENB_pwm=smpcar.pwm(smpcar.ENB) #初始化使能信號(hào)PWM塔次,B為右邊車輪
while True:
'''通過(guò)輸入的命令改變馬達(dá)轉(zhuǎn)動(dòng)'''
cmd = raw_input("Command, E.g. ff30ff30 :")
direction = cmd[0] #只輸入字母b時(shí)方篮,小車剎車
A_direction = cmd[0:2] #字符串0/1兩位為控制A(左邊車輪)方向信號(hào)
B_direction = cmd[4:6] #4/5位為控制B(右邊車輪)方向信號(hào)
A_speed = cmd[2:4] #字符串2/3兩位為控制A(左邊車輪)速度信號(hào)
B_speed = cmd[6:8] #字符串6/7兩位為控制B(右邊車輪)速度信號(hào)
print (A_direction,B_direction,A_speed,B_speed) #測(cè)試用
if A_direction == "ff": #控制A(左邊車輪)順時(shí)針信號(hào)
smpcar.clockwise(smpcar.IN1,smpcar.IN2)
if A_direction == "00": #控制A(左邊車輪)逆時(shí)針信號(hào)
smpcar.counter_clockwise(smpcar.IN1,smpcar.IN2)
if B_direction == "ff": #控制B(右邊車輪)順時(shí)針信號(hào)
smpcar.clockwise(smpcar.IN3,smpcar.IN4)
if B_direction == "00": #控制B(右邊車輪)逆時(shí)針信號(hào)
smpcar.counter_clockwise(smpcar.IN3,smpcar.IN4)
if direction == "b": #小車剎車,IN1和IN2都為0励负,馬達(dá)制動(dòng)
smpcar.stop_car(smpcar.IN1,smpcar.IN2)
smpcar.stop_car(smpcar.IN3,smpcar.IN4)
continue #跳出本次循環(huán)
# 通過(guò)輸入的兩位數(shù)字設(shè)置占空比藕溅,改變馬達(dá)轉(zhuǎn)速
smpcar.changespeed(ENA_pwm,int(A_speed))
smpcar.changespeed(ENB_pwm,int(B_speed))
except KeyboardInterrupt: # When 'Ctrl+C' is pressed, the child program destroy() will be executed.
smpcar.destroy(ENA_pwm,ENB_pwm)
finally:
smpcar.destroy(ENA_pwm,ENB_pwm)
??第3步: 小車行進(jìn)控制模塊,文件名為moving_control.py继榆,設(shè)計(jì)小車的移動(dòng)行為巾表,方向控制分為:原地左轉(zhuǎn)彎、原地右轉(zhuǎn)彎略吨、直線前進(jìn)集币、直線后退、剎車晋南,而向前左偏移開進(jìn)或右偏移開進(jìn)等惠猿,由左右兩邊不同的速度(油門)來(lái)控制。
moving_control.py:
#-*- coding: utf-8 -*-
#本模塊只含MovingControl()一個(gè)類负间,針對(duì)遙控器的控制方式偶妖,設(shè)計(jì)小車的移動(dòng)行為
#方向控制分為:原地左轉(zhuǎn)彎、原地右轉(zhuǎn)彎政溃、直線前進(jìn)趾访、直線后退、剎車
#而向前左偏移開進(jìn)或右偏移開進(jìn)等董虱,由左右兩邊不同的速度(油門)來(lái)控制
class MovingControl():
def __init__(self, smpcar,pwm1,pwm2):
self.smpcar=smpcar
self.ENA_pwm=pwm1
self.ENB_pwm=pwm2
self.rudder_value = 0
self.acc_value = 0
def accelerator(self,rate_left=1,rate_right=1):
'''速度(油門)控制'''
#rate_left為向左偏移行進(jìn)時(shí)扼鞋,降低左邊輪的速度
#rate_right為向右偏移行進(jìn)時(shí),降低右邊輪的速度
self.smpcar.changespeed(self.ENA_pwm,self.acc_value * rate_left)
self.smpcar.changespeed(self.ENB_pwm,self.acc_value * rate_right)
def leftTurn(self):
'''原地左轉(zhuǎn)彎'''
self.smpcar.counter_clockwise(self.smpcar.IN1,self.smpcar.IN2) #左邊車輪后退
self.smpcar.clockwise(self.smpcar.IN3,self.smpcar.IN4) #右邊車輪前進(jìn)
def rightTurn(self):
'''原地右轉(zhuǎn)彎'''
self.smpcar.clockwise(self.smpcar.IN1,self.smpcar.IN2)
self.smpcar.counter_clockwise(self.smpcar.IN3,self.smpcar.IN4)
def forward(self):
'''直線前進(jìn)'''
self.smpcar.clockwise(self.smpcar.IN1,self.smpcar.IN2)
self.smpcar.clockwise(self.smpcar.IN3,self.smpcar.IN4)
def reverse(self):
'''直線后退'''
self.smpcar.counter_clockwise(self.smpcar.IN1,self.smpcar.IN2)
self.smpcar.counter_clockwise(self.smpcar.IN3,self.smpcar.IN4)
def brake(self):
'''剎車'''
self.smpcar.stop_car(self.smpcar.IN1,self.smpcar.IN2)
self.smpcar.stop_car(self.smpcar.IN3,self.smpcar.IN4)
??第4步: 編寫SBUS信號(hào)接收解析模塊愤诱,文件名為sbus_receiver_pi.py云头,與樹莓派基礎(chǔ)實(shí)驗(yàn)39:解析無(wú)線電接收機(jī)PWM、SBUS信號(hào)中的Python2程序有所不同淫半,下面的程序在Python3中運(yùn)行溃槐,并標(biāo)注了兩者的不同之處。
sbus_receiver_pi.py:
#!/usr/bin/env python
#-*- coding: utf-8 -*-
#本模塊只含SBUSReceiver()一個(gè)類科吭,用于獲取和解析遙控器接收機(jī)的SBUS輸出信號(hào)
#并能返回每個(gè)通道的數(shù)值昏滴,遙控器的failsafe信號(hào)狀態(tài),每次獲得數(shù)據(jù)幀的長(zhǎng)度和這次數(shù)據(jù)的延遲時(shí)間
#import array #python2運(yùn)行時(shí)需要对人,array模塊是python中實(shí)現(xiàn)的一種高效的數(shù)組存儲(chǔ)類型
import serial #serial模塊封裝了對(duì)串行端口的訪問(wèn)
#import binascii #python2運(yùn)行時(shí)需要谣殊,binascii模塊包含很多在二進(jìn)制和ASCII編碼的二進(jìn)制表示轉(zhuǎn)換的方法
#import codecs #python2運(yùn)行時(shí)需要,Python中專門用作編碼轉(zhuǎn)換的模塊
import time
class SBUSReceiver():
def __init__(self, _uart_port='/dev/ttyAMA0'):
#初始化樹莓派串口參數(shù)
self.ser = serial.Serial(
port=_uart_port, #樹莓派的硬件串口/dev/ttyAMA0
baudrate = 100000, #波特率為100k
parity=serial.PARITY_EVEN, #偶校驗(yàn)
stopbits=serial.STOPBITS_TWO,#2個(gè)停止位
bytesize=serial.EIGHTBITS, #8個(gè)數(shù)據(jù)位
timeout = 0,
)
# 常數(shù)
#這里注意:Python2 與Python3 的編碼方式是不同的
#self.START_BYTE = b'\x0f' #python2運(yùn)行時(shí)用這句牺弄,起始字節(jié)為0x0f
#self.END_BYTE = b'\x00' #python2運(yùn)行時(shí)用這句姻几,結(jié)束字節(jié)為0x00
self.START_BYTE = 0x0f #python3運(yùn)行時(shí)用這句,起始字節(jié)為0x0f
self.END_BYTE = 0x00 #python3運(yùn)行時(shí)用這句,結(jié)束字節(jié)為0x00
self.SBUS_FRAME_LEN = 25 #SBUS幀有25個(gè)字節(jié)
self.SBUS_NUM_CHAN = 18 #18個(gè)通道
self.OUT_OF_SYNC_THD = 10
self.SBUS_NUM_CHANNELS = 18 #18個(gè)通道
self.SBUS_SIGNAL_OK = 0 #信號(hào)正常為0
self.SBUS_SIGNAL_LOST = 1 #信號(hào)丟失為1
self.SBUS_SIGNAL_FAILSAFE = 2 #輸出failsafe信號(hào)時(shí)為2
# 堆棧變量初始化
self.isReady = True
self.lastFrameTime = 0
self.sbusBuff = bytearray(1) # 用于同步的單個(gè)字節(jié)
#bytearray(n) 方法返回一個(gè)長(zhǎng)度為n的初始化數(shù)組鲜棠;
'''這里Python2與Python3存儲(chǔ)數(shù)據(jù)的編碼格式會(huì)不同'''
self.sbusFrame = bytearray(25) # 單個(gè)SBUS數(shù)據(jù)幀肌厨,25個(gè)字節(jié)
# 接收到的各頻道值,前面的Python2中使用了數(shù)組
#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:初始化器柑爸,若數(shù)組為空,則省略初始化器
# 接收到的各頻道值盒音,Python3中這里也可以使用列表
self.sbusChannels = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
self.failSafeStatus = self.SBUS_SIGNAL_FAILSAFE
def get_rx_channels(self):
"""
用于讀取最后的SBUS通道值
返回:由18個(gè)無(wú)符號(hào)短元素組成的數(shù)組表鳍,包含16個(gè)標(biāo)準(zhǔn)通道值+ 2個(gè)數(shù)字(ch17和18)
"""
return self.sbusChannels
def get_rx_channel(self, num_ch):
"""
用于讀取最后的SBUS某一特定通道的值
num_ch: 要讀取的某個(gè)通道的通道序號(hào)
返回:某一通道的值
"""
return self.sbusChannels[num_ch]
def get_failsafe_status(self):
"""
用于獲取最后的FAILSAFE狀態(tài)
返回: FAILSAFE狀態(tài)值
"""
return self.failSafeStatus
def decode_frame(self):
"""
對(duì)每幀數(shù)據(jù)進(jìn)行解碼,每個(gè)通道的值在兩個(gè)或三個(gè)不同的字節(jié)之間祥诽,要讀取出來(lái)很麻煩
不過(guò)futaba已經(jīng)發(fā)布了下面的解碼代碼
"""
def toInt(_from):
#encode() 方法以指定的編碼格式編碼字符串譬圣。
#int() 函數(shù)用于將一個(gè)字符串或數(shù)字轉(zhuǎn)換為整型。
#return int(codecs.encode(_from, 'hex'), 16) #Python2中要使用這句雄坪,轉(zhuǎn)換編碼格式
return _from #Python3中要使用這句厘熟,即不用轉(zhuǎn)換編碼格式
#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字節(jié)的最低一位
if toInt(self.sbusFrame[23]) & 0x0001 :
self.sbusChannels[16] = 2047
else:
self.sbusChannels[16] = 0
#18頻道维哈,第24字節(jié)的低第二位绳姨,所以要右移一位
if (toInt(self.sbusFrame[23]) >> 1) & 0x0001 :
self.sbusChannels[17] = 2047
else:
self.sbusChannels[17] = 0
#幀丟失位為1時(shí),第24字節(jié)的低第三位阔挠,與0x04進(jìn)行與運(yùn)算
self.failSafeStatus = self.SBUS_SIGNAL_OK
if toInt(self.sbusFrame[23]) & (1 << 2):
self.failSafeStatus = self.SBUS_SIGNAL_LOST
#故障保護(hù)激活位為1時(shí)飘庄,第24字節(jié)的低第四位,與0x08進(jìn)行與運(yùn)算
if toInt(self.sbusFrame[23]) & (1 << 3):
self.failSafeStatus = self.SBUS_SIGNAL_FAILSAFE
def update(self):
"""
我們需要至少2幀大小购撼,以確保找到一個(gè)完整的幀
所以我們?nèi)〕鏊械木彺妫ㄇ蹇账┕蛳鳎x取全部數(shù)據(jù),直到捕獲新的數(shù)據(jù)
首先找到END BYTE并向后查找SBUS_FRAME_LEN迂求,看看它是否是START BYTE
"""
#我們是否有足夠的數(shù)據(jù)在緩沖區(qū)和有沒有線程在后臺(tái)?
if self.ser.inWaiting() >= self.SBUS_FRAME_LEN*2 and self.isReady: #inWaiting()返回接收緩存中的字節(jié)數(shù)
self.isReady = False #表明有線程在運(yùn)行碾盐,isReady = False
# 讀取所有臨時(shí)幀數(shù)據(jù)
tempFrame = self.ser.read(self.ser.inWaiting())
# 在緩沖區(qū)幀的每個(gè)字符中,我們尋找結(jié)束字節(jié)
for end in range(0, self.SBUS_FRAME_LEN):
#尋找結(jié)束字節(jié)揩局,從后向前查找
if tempFrame[len(tempFrame)-1-end] == self.END_BYTE :
#從最后的命中點(diǎn)減去SBUS_FRAME_LEN尋找起始字節(jié)
if tempFrame[len(tempFrame)-end-self.SBUS_FRAME_LEN] == self.START_BYTE :
# 如果相等廓旬,則幀數(shù)據(jù)正確,數(shù)據(jù)以8E2包到達(dá)谐腰,因此它已經(jīng)被校驗(yàn)過(guò)
# 從臨時(shí)幀數(shù)據(jù)中取出剛驗(yàn)證正確的一段正確幀數(shù)據(jù)
lastUpdate = tempFrame[len(tempFrame)-end-self.SBUS_FRAME_LEN:len(tempFrame)-1-end]
if not self.sbusFrame == lastUpdate: #相等即表示沒有操作,不用再次解碼
self.sbusFrame = lastUpdate
self.decode_frame() #調(diào)用解碼函數(shù)
self.lastFrameTime = time.time() # 跟蹤最近的更新時(shí)間
self.isReady = True
break
if __name__ == '__main__':
sbus = SBUSReceiver('/dev/ttyAMA0')
while True:
time.sleep(0.005)
# X8R的SBUS信號(hào)是間隔6ms發(fā)送一次涩盾,一次持續(xù)發(fā)送3ms十气;
# 不要調(diào)用sbus.update()太快,如果sbus.ser.inWaiting()>50,且增長(zhǎng)很多春霍,可以調(diào)用sbus.update()快點(diǎn)砸西,即time.sleep()延遲短點(diǎn);
# 如果sbus.ser.inWaiting()<50,可以調(diào)用sbus.update()慢點(diǎn),即time.sleep()延遲長(zhǎng)點(diǎn)芹枷;
sbus.update()
#在您的代碼中衅疙,您可以調(diào)用sbus.get_rx_channels()來(lái)獲取所有數(shù)據(jù),或者調(diào)用sbus.get_rx_channels()[n]來(lái)獲取第n個(gè)通道的值鸳慈;
#或get_rx_channel(self, num_ch)來(lái)獲得第num_ch個(gè)通道的值饱溢;
print(sbus.get_failsafe_status(), sbus.get_rx_channels(), str(sbus.ser.inWaiting()).zfill(4) , (time.time()-sbus.lastFrameTime))
#str() 函數(shù)將對(duì)象轉(zhuǎn)化為適于人閱讀的形式,將指定的值轉(zhuǎn)換為字符串走芋。
#zfill() 方法返回指定長(zhǎng)度的字符串绩郎,原字符串右對(duì)齊,前面填充0翁逞。
#(time.time()-sbus.lastFrameTime)用于展示得到最近這次數(shù)據(jù)的延遲
??第5步: 編寫樹莓派智能小車的主程序肋杖,文件名為smartcar.py,將這4個(gè)Python文件放入一個(gè)文件夾后挖函,只運(yùn)行本文件就可以了状植。
smartcar.py:
#!/usr/bin/env python
#-*- coding: utf-8 -*-
#本模塊為樹莓派小車的主程序
from motor_4w import SMPcar
from sbus_receiver_pi import SBUSReceiver
from moving_control import MovingControl
import time
try:
sbus_receiver = SBUSReceiver('/dev/ttyAMA0') #初始化實(shí)例
smp_car = SMPcar() #初始化實(shí)例
smp_car.setGPIO() #初始化引腳
ENA_pwm = smp_car.pwm(smp_car.ENA) #初始化使能信號(hào)PWM,A為左邊車輪
ENB_pwm = smp_car.pwm(smp_car.ENB) #初始化使能信號(hào)PWM怨喘,B為右邊車輪
smartcar = MovingControl(smp_car,ENA_pwm,ENB_pwm) ##初始化實(shí)例
acc_value_sbus = 172 #3通道津畸,即油門的值,最低時(shí)為172
while True:
time.sleep(0.005)
sbus_receiver.update() #獲取一個(gè)完整的幀數(shù)據(jù)
aileron_value = sbus_receiver.get_rx_channel(0) #1通道為副翼通道哲思,這里控制車原地轉(zhuǎn)向
elevator_value = sbus_receiver.get_rx_channel(1)#2通道為升降舵通道奴潘,這里控制車前進(jìn)后退
smartcar.rudder_value = sbus_receiver.get_rx_channel(3)#4通道為方向舵通道,這里控制車左右偏移行進(jìn)
if sbus_receiver.get_rx_channel(2):
acc_value_sbus = sbus_receiver.get_rx_channel(2) #3通道為油門通道涮帘,這里控制車速度
#將172~1811的油門通道值轉(zhuǎn)換為0~100的占空比信號(hào)系吩,
smartcar.acc_value = int(100*(acc_value_sbus-172)/(1811-172))
if 970 < smartcar.rudder_value < 1100: #沒有左右偏移時(shí),中間值為992靠益,但遙控器微調(diào)時(shí)會(huì)有上下浮動(dòng)
smartcar.accelerator()
elif smartcar.rudder_value >=1100: #向右偏移行進(jìn)時(shí)
rate_right = (1811.0 - smartcar.rudder_value)/(1811-1100)
#最大值一般為1811丧肴,這里使用浮點(diǎn)類型,所以一定要使用1811.0
smartcar.accelerator(1,rate_right)
elif smartcar.rudder_value <=970: #向左偏移行進(jìn)時(shí)
rate_left = (smartcar.rudder_value - 172.0)/(970-172)
#最小值為172胧后,這里使用浮點(diǎn)類型芋浮,所以一定要使用172.0
smartcar.accelerator(rate_left,1)
print(elevator_value,smartcar.acc_value,aileron_value,smartcar.rudder_value)#測(cè)試數(shù)據(jù)用
if aileron_value >= 1100: #原地左轉(zhuǎn)彎
smartcar.leftTurn()
elif aileron_value <= 970: #原地右轉(zhuǎn)彎
smartcar.rightTurn()
else :
smartcar.brake() #停車
if elevator_value >=1100: #前進(jìn)
smartcar.forward()
elif elevator_value <=970: #后退
smartcar.reverse()
#循環(huán)最后,這里不能再用停車了
except KeyboardInterrupt:
smp_car.destroy(ENA_pwm,ENB_pwm)
finally:
smp_car.destroy(ENA_pwm,ENB_pwm)
操控演示視頻:
https://www.bilibili.com/video/BV1Q5411L75a/
最近太忙壳快,這篇文章拖了很久才完成纸巷,當(dāng)中克服了一個(gè)個(gè)不懂的技術(shù)難點(diǎn)和BUG,不過(guò)終于實(shí)現(xiàn)了遙控小車的想法眶痰,成功將無(wú)線電遙控和樹莓派結(jié)合起來(lái)瘤旨。
辛苦是值得的,學(xué)到不少東西竖伯,后面將陸續(xù)把各種傳感器加入進(jìn)來(lái)存哲,實(shí)現(xiàn)智能化因宇。
這個(gè)項(xiàng)目的代碼90%是我原創(chuàng)瞎寫的,有需要參考的同學(xué)可以下載:
樹莓派智能小車項(xiàng)目python源代碼下載