生活随笔
收集整理的這篇文章主要介紹了
树莓派综合项目2:智能小车(三)无线电遥控
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
一、介紹
??閱讀本篇文章前建議先參考前期文章:
??樹莓派基礎實驗34:L298N模塊驅動直流電機實驗
??樹莓派綜合項目2:智能小車(一)四輪驅動
??樹莓派綜合項目2:智能小車(二)tkinter圖形界面控制
??《智能小車(一)四輪驅動》中,實現了代碼輸入對四個電機的簡單控制?!吨悄苄≤?#xff08;二)tkinter圖形界面控制》中,實現了本地圖形界面控制小車的前進后退、轉向和原地轉圈。
??本實驗中將使用無線電遙控設備控制小車的前進后退、轉向和原地轉圈。使用傳統無線電通信設備通信仍然是非常重要的通信方式,比如無線電臺、對講機,航模、車模、船模遙控等等。與手機移動網絡、WIFI連接相比,無線電連接有它獨特的優勢,遙控距離遠,實時性好,操作靈活,不受網絡信號限制。
??其它基礎內容可以參考文集:樹莓派基礎實驗。
二、組件
★Raspberry Pi 3 B+全套*1
★睿思凱Frsky XM+ 迷你接收機*1
★電平反向器模塊*1
★睿思凱Frsky Taranis X9D PLUS SE2019遙控器*1
★L298N擴展板模塊*1
★智能小車底板模塊*1
★減速電機和車輪*4
★跳線若干
三、實驗原理
本實驗中使用的遙控系統可以自行選擇其它品牌的產品,如國產的天地飛還不錯。
這里的遙控器就是像電視機遙控器、空調遙控器一樣可以不用接觸到被控設備,而通過一個手持器件,使用無線電與被控設備進行通信,從而達到對設備的控制。
遙控器想到達到與小車通信的功能需要有兩部分配合完成。即:發射器(遙控器)與接收機。遙控器上的控制桿轉為無線電波發送給接收機,而接收機通過接收無線電波,讀取遙控器上控制桿的讀數,并轉為數字信號發送到樹莓派中。
接收機輸出兩種信號,一種是模擬信號PWM,一種是SBUS數字信號。
由于每一路遙控器通道都需要一個PWM采集器進行采集,但是對于樹莓派來說不可能使用多個定時器來采集多個通道的PWM,這對于樹莓派的GPIO端口資源來說十分浪費,因此我采用的就是SBUS信號,可以在一個管腳中傳輸多路控制信號。
S-BUS其實是一種串口通信協議,采用100000的波特率,數據位點8bits,停止位點2bits,偶效驗,即8E2的串口通信。使用樹莓派的串口GPIO(TXD/RXD)中的RXD端口接收接收機的SBUS輸出信號,解析出每路通道的控制信號,進而控制小車行進。
但是S-BUS采用的是反向電平傳輸,也就是說,在S-BUS的發送端高低電平是反向的,協議中的所有高電平都被轉換成低電平,協議中的所有低電平都被轉換成高電平。所以在S-BUS的接收端需要增加一個高低電平反向器來進行電平反轉。
關于解析無線電接收機PWM、SBUS信號的更詳細內容,請參考樹莓派基礎實驗39:解析無線電接收機PWM、SBUS信號。
四、實驗步驟
??第1步: 連接電路。在樹莓派綜合項目2:智能小車(一)四輪驅動中的接線基礎上,接入電平反向器、無線電接收機。
樹莓派(name)樹莓派(BOARD)L298N小車擴展板
| 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 | 電池組供電負極 |
關于這里樹莓派GND、L298N小車擴展板的電池組供電負極相連,是特殊情況下的情況,經測試發現:
如果樹莓派用的是充電頭供電,而L298N擴展板用的是電池組供電,這兩個負極必須相連,否則馬達不動。
如果樹莓派用的是L298N擴展板接出來的5V供電,即兩者同一個電源,則這里不用連接。
L298N小車擴展板電池組樹莓派電壓表頭馬達
| 電池+(-) | 電池+(-) | | | |
| 5V供電 | | 電源接口 | | |
| +(-) | | | +(-) | |
| T1(L后) | | | | +(-) |
| T2(L前) | | | | +(-) |
| T3(R前) | | | | +(-) |
| T4(R后) | | | | +(-) |
樹莓派(name)樹莓派(BOARD)電平反向器無線電接收機
| | A6 | SBUS_OUT |
| RXD | 10 | B6 | |
| 3.3V | 1 | VCC | |
| 0V | 9 | GND | |
| 5V | 2 | | VCC |
| 0V | 14 | | GND |
這里也要注意,由于樹莓派的GPIO只能接收3.3V的最高輸入,所以電平反相器的電源也只能使用3.3V,若反向后接收的信號需要是5V,則電平反相器的電源就使用5V。
這里我將18650電池組換成了航模使用的格氏ACE鋰電池(3S/11.1V/2200MAH/40C),電壓更高,能給樹莓派提供更穩定的電源,動力性也更好,效果非常不錯。
??第2步: 編寫電機的驅動程序,文件名為motor_4w.py。與樹莓派綜合項目2:智能小車(一)四輪驅動中的程序完全相同。
??該車的行進控制與履帶車的行進控制類似:
前進和后退很簡單,左右兩邊的方向都朝前或朝后,速度一致;
原地順時針旋轉時,左邊輪子前進,右邊輪子后退,速度一致;
原地逆時針旋轉時,左邊輪子后退,右邊輪子前進,速度一致;
偏左前進時,左右兩邊的方向都朝前,左輪速度比右輪速度慢一點;
偏右前進時,左右兩邊的方向都朝前,左輪速度比右輪速度快一點;
偏左后退時,左右兩邊的方向都朝后,左輪速度比右輪速度慢一點;
偏右后退時,左右兩邊的方向都朝后,左輪速度比右輪速度快一點;
motor_4w.py:
import RPi
.GPIO
as GPIO
class SMPcar():'''控制小車四輪動作的類'''ENA
= 11 IN1
= 13 IN2
= 15 ENB
= 12 IN3
= 16 IN4
= 18 GPIO
.setwarnings
(False) 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(脈寬調制),返回PWM對象'''EN_pwm
= GPIO
.PWM
(pwm
, 500)EN_pwm
.start
(0)return EN_pwm
def changespeed(self
,pwm
,speed
):'''通過改變占空比改變馬達轉速'''pwm
.ChangeDutyCycle
(speed
)def clockwise(self
,in1_pin
,in2_pin
):'''馬達順時針轉的信號'''GPIO
.output
(in1_pin
, 1)GPIO
.output
(in2_pin
, 0)def counter_clockwise(self
,in1_pin
,in2_pin
):'''馬達逆時針轉的信號'''GPIO
.output
(in1_pin
, 0)GPIO
.output
(in2_pin
, 1)def stop_car(self
,in1_pin
,in2_pin
):'''馬達制動的信號'''GPIO
.output
(in1_pin
, 0)GPIO
.output
(in2_pin
, 0)def destroy(self
,A
,B
):'''結束程序時清空GPIO狀態'''A
.stop
()B
.stop
()GPIO
.cleanup
() if __name__
== '__main__': try:smpcar
= SMPcar
() smpcar
.setGPIO
() ENA_pwm
=smpcar
.pwm
(smpcar
.ENA
) ENB_pwm
=smpcar
.pwm
(smpcar
.ENB
) while True:'''通過輸入的命令改變馬達轉動'''cmd
= raw_input("Command, E.g. ff30ff30 :")direction
= cmd
[0] A_direction
= cmd
[0:2] B_direction
= cmd
[4:6] A_speed
= cmd
[2:4] B_speed
= cmd
[6:8] print (A_direction
,B_direction
,A_speed
,B_speed
) if A_direction
== "ff": smpcar
.clockwise
(smpcar
.IN1
,smpcar
.IN2
)if A_direction
== "00": smpcar
.counter_clockwise
(smpcar
.IN1
,smpcar
.IN2
)if B_direction
== "ff": smpcar
.clockwise
(smpcar
.IN3
,smpcar
.IN4
)if B_direction
== "00": smpcar
.counter_clockwise
(smpcar
.IN3
,smpcar
.IN4
)if direction
== "b": smpcar
.stop_car
(smpcar
.IN1
,smpcar
.IN2
)smpcar
.stop_car
(smpcar
.IN3
,smpcar
.IN4
)continue smpcar
.changespeed
(ENA_pwm
,int(A_speed
))smpcar
.changespeed
(ENB_pwm
,int(B_speed
))except KeyboardInterrupt
: smpcar
.destroy
(ENA_pwm
,ENB_pwm
)finally:smpcar
.destroy
(ENA_pwm
,ENB_pwm
)
??第3步: 小車行進控制模塊,文件名為moving_control.py,設計小車的移動行為,方向控制分為:原地左轉彎、原地右轉彎、直線前進、直線后退、剎車,而向前左偏移開進或右偏移開進等,由左右兩邊不同的速度(油門)來控制。
moving_control.py:
class MovingControl():def __init__(self
, smpcar
,pwm1
,pwm2
):self
.smpcar
=smpcarself
.ENA_pwm
=pwm1self
.ENB_pwm
=pwm2self
.rudder_value
= 0self
.acc_value
= 0def accelerator(self
,rate_left
=1,rate_right
=1):'''速度(油門)控制'''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
):'''原地左轉彎'''self
.smpcar
.counter_clockwise
(self
.smpcar
.IN1
,self
.smpcar
.IN2
) self
.smpcar
.clockwise
(self
.smpcar
.IN3
,self
.smpcar
.IN4
) def rightTurn(self
):'''原地右轉彎'''self
.smpcar
.clockwise
(self
.smpcar
.IN1
,self
.smpcar
.IN2
)self
.smpcar
.counter_clockwise
(self
.smpcar
.IN3
,self
.smpcar
.IN4
)def forward(self
):'''直線前進'''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信號接收解析模塊,文件名為sbus_receiver_pi.py,與樹莓派基礎實驗39:解析無線電接收機PWM、SBUS信號中的Python2程序有所不同,下面的程序在Python3中運行,并標注了兩者的不同之處。
sbus_receiver_pi.py:
import serial
import time
class SBUSReceiver():def __init__(self
, _uart_port
='/dev/ttyAMA0'):self
.ser
= serial
.Serial
(port
=_uart_port
, baudrate
= 100000, parity
=serial
.PARITY_EVEN
, stopbits
=serial
.STOPBITS_TWO
,bytesize
=serial
.EIGHTBITS
, timeout
= 0,)self
.START_BYTE
= 0x0f self
.END_BYTE
= 0x00 self
.SBUS_FRAME_LEN
= 25 self
.SBUS_NUM_CHAN
= 18 self
.OUT_OF_SYNC_THD
= 10self
.SBUS_NUM_CHANNELS
= 18 self
.SBUS_SIGNAL_OK
= 0 self
.SBUS_SIGNAL_LOST
= 1 self
.SBUS_SIGNAL_FAILSAFE
= 2 self
.isReady
= Trueself
.lastFrameTime
= 0self
.sbusBuff
= bytearray(1) '''這里Python2與Python3存儲數據的編碼格式會不同'''self
.sbusFrame
= bytearray(25) 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個無符號短元素組成的數組,包含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
):return _from self
.sbusChannels
[0] = ((toInt
(self
.sbusFrame
[1]) |toInt
(self
.sbusFrame
[2])<<8) & 0x07FF);self
.sbusChannels
[1] = ((toInt
(self
.sbusFrame
[2])>>3 |toInt
(self
.sbusFrame
[3])<<5) & 0x07FF);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);if toInt
(self
.sbusFrame
[23]) & 0x0001 :self
.sbusChannels
[16] = 2047else:self
.sbusChannels
[16] = 0if (toInt
(self
.sbusFrame
[23]) >> 1) & 0x0001 :self
.sbusChannels
[17] = 2047else:self
.sbusChannels
[17] = 0self
.failSafeStatus
= self
.SBUS_SIGNAL_OK
if toInt
(self
.sbusFrame
[23]) & (1 << 2):self
.failSafeStatus
= self
.SBUS_SIGNAL_LOST
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
: self
.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
:if tempFrame
[len(tempFrame
)-end
-self
.SBUS_FRAME_LEN
] == self
.START_BYTE
:lastUpdate
= tempFrame
[len(tempFrame
)-end
-self
.SBUS_FRAME_LEN
:len(tempFrame
)-1-end
]if not self
.sbusFrame
== lastUpdate
: self
.sbusFrame
= lastUpdateself
.decode_frame
() self
.lastFrameTime
= time
.time
() self
.isReady
= Truebreakif __name__
== '__main__':sbus
= SBUSReceiver
('/dev/ttyAMA0')while True:time
.sleep
(0.005)sbus
.update
()print(sbus
.get_failsafe_status
(), sbus
.get_rx_channels
(), str(sbus
.ser
.inWaiting
()).zfill
(4) , (time
.time
()-sbus
.lastFrameTime
))
??第5步: 編寫樹莓派智能小車的主程序,文件名為smartcar.py,將這4個Python文件放入一個文件夾后,只運行本文件就可以了。
smartcar.py:
from motor_4w
import SMPcar
from sbus_receiver_pi
import SBUSReceiver
from moving_control
import MovingControl
import time
try:sbus_receiver
= SBUSReceiver
('/dev/ttyAMA0') smp_car
= SMPcar
() smp_car
.setGPIO
() ENA_pwm
= smp_car
.pwm
(smp_car
.ENA
) ENB_pwm
= smp_car
.pwm
(smp_car
.ENB
) smartcar
= MovingControl
(smp_car
,ENA_pwm
,ENB_pwm
) acc_value_sbus
= 172 while True:time
.sleep
(0.005)sbus_receiver
.update
() aileron_value
= sbus_receiver
.get_rx_channel
(0) elevator_value
= sbus_receiver
.get_rx_channel
(1)smartcar
.rudder_value
= sbus_receiver
.get_rx_channel
(3)if sbus_receiver
.get_rx_channel
(2):acc_value_sbus
= sbus_receiver
.get_rx_channel
(2) smartcar
.acc_value
= int(100*(acc_value_sbus
-172)/(1811-172))if 970 < smartcar
.rudder_value
< 1100: smartcar
.accelerator
()elif smartcar
.rudder_value
>=1100: rate_right
= (1811.0 - smartcar
.rudder_value
)/(1811-1100) smartcar
.accelerator
(1,rate_right
)elif smartcar
.rudder_value
<=970: rate_left
= (smartcar
.rudder_value
- 172.0)/(970-172) smartcar
.accelerator
(rate_left
,1)print(elevator_value
,smartcar
.acc_value
,aileron_value
,smartcar
.rudder_value
)if aileron_value
>= 1100: smartcar
.leftTurn
()elif aileron_value
<= 970: smartcar
.rightTurn
()else : smartcar
.brake
() if elevator_value
>=1100: smartcar
.forward
()elif elevator_value
<=970: smartcar
.reverse
()
except KeyboardInterrupt
: smp_car
.destroy
(ENA_pwm
,ENB_pwm
)
finally:smp_car
.destroy
(ENA_pwm
,ENB_pwm
)
操控演示視頻:
https://www.bilibili.com/video/BV1Q5411L75a/
最近太忙,這篇文章拖了很久才完成,當中克服了一個個不懂的技術難點和BUG,不過終于實現了遙控小車的想法,成功將無線電遙控和樹莓派結合起來。
辛苦是值得的,學到不少東西,后面將陸續把各種傳感器加入進來,實現智能化。
這個項目的代碼90%是我原創瞎寫的,有需要參考的同學可以下載:
樹莓派智能小車項目python源代碼下載
總結
以上是生活随笔為你收集整理的树莓派综合项目2:智能小车(三)无线电遥控的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。