一、介紹
??閱讀本篇文章前建議先參考前期文章:
??樹莓派基礎實驗34:L298N模塊驅動直流電機實驗,學習了單個電機的簡單驅動。
??樹莓派綜合項目2:智能小車(一)四輪驅動,實現了代碼輸入對四個電機的簡單控制。
??樹莓派綜合項目2:智能小車(二)tkinter圖形界面控制,實現了本地圖形界面控制小車的前進后退、轉向和原地轉圈。
??樹莓派綜合項目2:智能小車(三)無線電遙控,實現了無線電遙控設備控制小車的前進后退、轉向和原地轉圈。
??樹莓派綜合項目2:智能小車(四)超聲波避障,實現了超聲波傳感器實時感知小車前方障礙物的距離。
??樹莓派綜合項目2:智能小車(五)紅外避障,實現了紅外光電傳感器探測前方是否存在障礙物。
??本實驗中將使用HJ-IR1紅外循跡模塊。循跡模塊的紅外發(fā)射二極管不斷發(fā)射紅外線,放射出的紅外線被物體反射后,被紅外接收器接收,并輸出信號給樹莓派處理,再對電機驅動模塊進行控制,實現通過對黑線和小車位置的判斷,控制小車沿黑線行進。
??這樣的循跡小車又稱為簡單的循跡機器人,比如餐廳的機器人服務員、農場的投食機器人、瓜果采摘機器人等等。
??其它基礎內容可以參考文集:樹莓派基礎實驗。
二、組件
★Raspberry Pi 3 B+全套*1
★睿思凱Frsky XM+ 迷你接收機*1
★電平反向器模塊*1
★睿思凱Frsky Taranis X9D PLUS SE2019遙控器*1
★L298N擴展板模塊*1
★智能小車底板模塊*1
★減速電機和車輪*4
★HC-SR04超聲波模塊*1
★HJ-IR2紅外光電傳感器*2
★HJ-IR1紅外循跡模塊*2
★跳線若干
三、實驗原理


常見有三種循跡方法:
紅外對管循跡法:利用黑白色對紅外線的吸收作用不同;
攝像頭循跡法:利用攝像頭讀取賽道信息,分為模擬和數字;
激光對管循跡法:和紅外循跡法原理相似,但是檢測距離遠;
本實驗中使用的是第一種,紅外對管循跡法,白色地面反射的紅外信號強度大于黑色地面的反射。


??將兩路探頭布置在智能車前方,探頭朝下,調節(jié)可調電阻,讓白色地面和黑色地面會產生不同的信號。若沒有被任何一個探頭檢測到障礙物,小車直行;左邊探頭檢測到障礙物時小車向右轉,右邊探頭檢測到障礙物時小車向左轉。

??關于紅外傳感器的基礎知識請參見樹莓派基礎實驗28:紅外避障傳感器實驗。
??實際試驗過程中發(fā)現,小車在彎曲的黑線上循跡時,容易沖出黑線,一方面可能是速度過快,另一方面可能是循環(huán)間隙時間太長。速度過快降低速度就好,循跡間隙時間太長可能是由于檢測循跡模塊信號的頻率太慢,當檢測到偏離黑線時,小車已經沖出黑線了。
??這可能是由于檢測循跡模塊信號的循環(huán)語句里包含了其它傳感器的檢測,或者是為了檢測其它傳感器,間隔一定時間才運行檢測循跡模塊信號的循環(huán)語句。所以我使用多線程編程技術,將檢測循跡模塊信號的函數創(chuàng)建為一個單獨的線程,不與其它傳感器或者其它循環(huán)相干擾,或者并行運行,使得循跡控制非常靈敏。
四、實驗步驟
??第1步: 連接電路。這里對黑線循跡外的連線方法不在累述,請參考樹莓派綜合項目2:智能小車(四)超聲波避障。
| 樹莓派(name) | 樹莓派(BOARD) | 循跡模塊 |
|---|---|---|
| GPIO.23 | 33 | 左側循跡模塊輸出 |
| GPIO.24 | 35 | 右側循跡模塊輸出 |
| 5V | 5V | 兩個模塊的VCC |
| GND | GND | 兩個模塊的GND |


??第2步: motor_4w.py,moving_control.py,sbus_receiver_pi.py,ultrasonic.py,infrared.py文件的編碼這里不再累述,參考樹莓派綜合項目2:智能小車(四)超聲波避障。
??第3步:編寫循跡模塊,文件名為tracking.py,與樹莓派基礎實驗33:TCRT5000紅外循跡傳感器實驗中的Python程序原理基本相同。Tracking()類中的方法tracking_detect()可以返回兩個值,當檢測到紅外反射時值為0,當沒有檢測到紅外反射時值為1。
tracking.py:
#!/usr/bin/env python3
#-*- coding: utf-8 -*-
#本模塊只含Tracking()一個類,用于塊測出是否有障礙物
#有障礙物時返回值0,無障礙物時返回值1
import RPi.GPIO as GPIO
import time
class Tracking():
trackingPinLeft = 33 #左側模塊的輸出連接樹莓派33號針腳
trackingPinRight = 35
def setup(self):
GPIO.setmode(GPIO.BOARD)
GPIO.setup(Tracking.trackingPinLeft, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(Tracking.trackingPinRight, GPIO.IN, pull_up_down=GPIO.PUD_UP)
def tracking_detect(self):
tracking_left_value = 1
tracking_right_value = 1
if (0 == GPIO.input(Tracking.trackingPinLeft)): # 當檢測到紅外反射,即白色地面時,輸出低電平信號
tracking_left_value = 0
if (0 == GPIO.input(Tracking.trackingPinRight)): #當檢測到紅外反射時,即白色地面時,輸出低電平信號
tracking_right_value = 0
return tracking_left_value,tracking_right_value
def destroy(self):
GPIO.cleanup()
if __name__ == "__main__":
try:
tracking = Tracking()
tracking.setup()
while True:
tracking_left_value,tracking_right_value = tracking.tracking_detect()
print('tracking_left_value=%d tracking_right_value=%d'%(tracking_left_value,tracking_right_value) )
#time.sleep(0.1)
except KeyboardInterrupt:
tracking.destroy()
??第4步: 編寫樹莓派智能小車的主程序,文件名為smartcar.py,將這5個Python文件放入一個文件夾后,只運行本文件就可以了。
??主程序中加入了tracking_control()循跡控制函數,實現了沿黑線自動行進。引入了python多線程模塊threading,啟動線程后,tracking_control()函數中的while True:語句會一直運行,但是只有當全局變量exitFlag_tracking為0時,循跡控制才會實際執(zhí)行。即當SA開關打到下檔位置時,exitFlag_tracking為0,不為下檔時,exitFlag_tracking = 1,循跡控制暫停,由超聲波等傳感器和遙控器控制小車行進。通過SA開關可以雙向隨時切換控制。
smartcar.py:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# 本模塊為樹莓派小車的主程序,
from motor_4w import Motor_4w
from sbus_receiver_pi import SBUSReceiver
from moving_control import MovingControl
from ultrasonic import Ultrasonic
from infrared import Infrared
from tracking import Tracking
import time
import threading
sbus_receiver = SBUSReceiver('/dev/ttyAMA0') # 初始化SBUS信號接收實例
smp_car = Motor_4w() # 初始化電機控制實例
smp_car.setGPIO() # 初始化引腳
ENA_pwm = smp_car.pwm(smp_car.ENA) # 初始化使能信號PWM,A為左邊車輪
ENB_pwm = smp_car.pwm(smp_car.ENB) # 初始化使能信號PWM,B為右邊車輪
smartcar = MovingControl(smp_car,ENA_pwm,ENB_pwm) # 初始化車輛運動控制實例
ultr = Ultrasonic() # 初始化超聲波實例
ultr.setup() # 初始化超聲波引腳
infrared = Infrared() # 初始化紅外避障實例
infrared.setup() # 初始化紅外避障引腳
tracking = Tracking()
tracking.setup()
def ultra_control():
"""超聲波傳感器控制"""
# 如果測距小于10cm時,先停車再倒車0.3秒
smartcar.brake()
smartcar.reverse()
time.sleep(0.3)
def infra_control(infra_left_value,infra_right_value):
"""紅外避障傳感器控制"""
if infra_left_value == 0: # 值為0時,表示左側檢測到障礙物
# smartcar.acc_value=80
smartcar.accelerator(1, 0.5) # 控制小車向右偏轉
time.sleep(0.5) # 向右偏轉行進0.5秒
if infra_right_value == 0:
# smartcar.acc_value=80
smartcar.accelerator(0.5, 1)
time.sleep(0.5)
print('infra_left_value=%d infra_right_value=%d'%(infra_left_value,infra_right_value) )
exitFlag_tracking = 1 # 是否退出循跡while循環(huán)的標志,0為不退出
def tracking_control():
"""紅外循跡傳感器,控制小車沿黑線自動行進"""
while True:
time.sleep(0.5) # 為節(jié)省計算資源,不讓循環(huán)過于太快
while not exitFlag_tracking:
smartcar.acc_value = 40
tracking_left_value,tracking_right_value = tracking.tracking_detect()
if tracking_left_value == 0 and tracking_right_value == 0: # 值都為0時,表示都檢測到紅外反射,直行
smartcar.accelerator() # 讓油門左右一致
smartcar.forward() # 控制小車直線前進
if tracking_left_value == 1 and tracking_right_value == 0: # 左側檢測不到反射信號,即遇到黑線,車向左偏轉
smartcar.accelerator(0, 1)
smartcar.forward()
if tracking_right_value == 1 and tracking_left_value == 0: # 右側檢測不到反射信號,即遇到黑線,車向右偏轉
smartcar.accelerator(1, 0)
smartcar.forward()
if tracking_left_value == 1 and tracking_right_value == 1: # 值都為1時,表示都沒有檢測到紅外反射,停車
smartcar.brake() # 控制小車停車
def sbus_control():
"""無線電控制"""
aileron_value = sbus_receiver.get_rx_channel(0) # 1通道為副翼通道,這里控制車原地轉向
elevator_value = sbus_receiver.get_rx_channel(1) # 2通道為升降舵通道,這里控制車前進后退
rudder_value = sbus_receiver.get_rx_channel(3) # 4通道為方向舵通道,這里控制車左右偏移行進
acc_value_sbus = 172 # 3通道,即油門的值,最低時為172
if sbus_receiver.get_rx_channel(2):
acc_value_sbus = sbus_receiver.get_rx_channel(2) # 3通道為油門通道,這里控制車速度
# 將172~1811的油門通道值轉換為0~100的占空比信號,
smartcar.acc_value = int(100*(acc_value_sbus-172)/(1811-172))
print('original.acc_value=', smartcar.acc_value) # 檢測SBUS信號的油門值
smartcar.accelerator()
print('disposed.acc_value=', smartcar.acc_value) # 檢測超聲波函數處理后的油門值
if 970 < rudder_value < 1100: # 沒有左右偏移時,中間值為992,但遙控器微調時會有上下浮動
pass # 沒有左右偏移時
elif rudder_value >= 1100: # 向右偏移行進時
rate_right = (1811.0 - rudder_value)/(1811-1100)
# 最大值一般為1811,這里使用浮點類型,所以一定要使用1811.0
smartcar.accelerator(1, rate_right)
elif rudder_value <= 970: # 向左偏移行進時
rate_left = (rudder_value - 172.0)/(970-172)
# 最小值為172,這里使用浮點類型,所以一定要使用172.0
smartcar.accelerator(rate_left, 1)
print('elevator_value=%d,aileron_value=%d,rudder_value=%d'%(elevator_value,aileron_value,rudder_value))#測試數據用
if aileron_value >= 1100: # 原地左轉彎
smartcar.leftTurn()
smartcar.accelerator()
elif aileron_value <= 970: # 原地右轉彎
smartcar.rightTurn()
smartcar.accelerator()
else:
smartcar.brake() # 停車,這里用了else,后面不需要再停車了
if elevator_value >= 1100: # 前進
smartcar.forward()
elif elevator_value <= 970: # 后退
smartcar.reverse()
# 循環(huán)最后,這里不能再用停車了
try:
# 創(chuàng)建循跡控制的線程,讓tracking_control()函數始終處于運行狀態(tài)
myThread_tracking = threading.Thread(target=tracking_control)
myThread_tracking.start() # 啟動線程
while True:
sbus_receiver.update() # 獲取一個完整的幀數據
tracking_value = sbus_receiver.get_rx_channel(4) # 5通道為是否循跡的開關,打開后則按循跡自動行進
# print('tracking_value=',tracking_value)
if tracking_value > 1800: # 當SA開關打到下檔位置時,get_rx_channel(4)的值為1811
exitFlag_tracking = 0 # 讓循跡函數線程中的while循環(huán)開始,則由循跡函數線程控制小車行進
else:
exitFlag_tracking = 1 # 讓循跡函數線程中的while循環(huán)結束,開始由遙控器和其它傳感器控制小車行進
dis = ultr.distance()
# print('distance= %.1f cm\n' % dis)
if dis < 10:
ultra_control() # 超聲波避障
infra_left_value,infra_right_value = infrared.infra_detect()
if infra_left_value == 0 or infra_right_value == 0:
infra_control(infra_left_value,infra_right_value)
sbus_control() # 遙控器控制小車行進
except KeyboardInterrupt:
smp_car.destroy(ENA_pwm,ENB_pwm)
finally:
smp_car.destroy(ENA_pwm,ENB_pwm)

這個項目的代碼90%是我原創(chuàng)瞎寫的,有需要參考的同學可以下載:
樹莓派智能小車項目python源代碼下載
這個小項目就到這里截止了,本來想再融入舵機,攝像頭,圖像識別,網絡編程控制等等,不過上一次實驗就不小心把L298N擴展板燒了,雖然經過我不專業(yè)的維修后可以繼續(xù)使用,但車身太小,空間有限,決定重新選一款小車項目套件,也看看別的專家是怎么編寫代碼的,謝謝大家的支持點贊!
