牟曉東
在計算機編程中,“多線程”屬于一種并發(fā)的執(zhí)行機制,它的引入也并非為了提高運行效率,其主要作用是追求“小時間片的輪詢”式同步以完成多項任務(wù)。以開源硬件編程中的“雙控”紅綠燈為例,十字路口的紅綠燈在正常情況下會每隔一段均勻的時間進行顏色的平均切換,當遇到“緊急情況”需要某個方向(水平或豎直)立刻切換為綠燈通行狀態(tài)時,不必等到另外方向的綠色通行時段結(jié)束就馬上為緊急通道讓路,處理完緊急情況后再自動切換至“自動”模式,實現(xiàn)對紅綠燈的“雙控”。此時,比較好的選擇就是使用多線程編程的方式來實現(xiàn)。
實驗器材包括樹莓派和古德微擴展板各一塊,搖桿模塊一個,ADS1115模數(shù)轉(zhuǎn)換器一個,紅色和綠色LED燈各四支,330Ω電阻四個,小型面包板一個,杜邦線若干。
首先,將擴展板正確安裝于樹莓派上;接著,將模數(shù)轉(zhuǎn)換器按照標注插入擴展板的Up引腳列(或反向插入Down引腳列),再使用杜邦線將搖桿模塊的+5V和GND端分別連接至擴展板的+5V和GND接地端,搖桿模塊的VRX和VRY端分別連接至擴展板的A0和A1模擬數(shù)據(jù)輸入端口(保持其按鈕的SW端是懸空不用狀態(tài));然后,在面包板上模擬十字路口的紅綠燈——為了實現(xiàn)兩種顏色的LED燈在同時亮起時有大致一樣的亮度,需要為每支紅色LED燈先串聯(lián)一個330Ω電阻(綠色LED燈直接插入面包板即可),要注意處于同一方向上的兩支同色LED燈是并聯(lián)的,這樣可以使用一個信號來同時驅(qū)動其發(fā)光或熄滅;四組同色的LED燈連接好之后,分別再通過杜邦線連接至擴展板的5號、6號、12號和16號引腳(如圖1)。
最后,給樹莓派連接數(shù)據(jù)線,通電啟動操作系統(tǒng)。
通過瀏覽器訪問古德微網(wǎng)站(http://www.gdwrobot.cn/robot_system/#/home/carcontrol),登錄自己的賬號后進入圖形化編程界面。
為了實現(xiàn)水平和豎直兩個方向各自“亮綠燈”的通行狀態(tài),同時也對應(yīng)另一方向“亮紅燈”的禁行狀態(tài),需要先編寫“水平方向通行”和“豎直方向通行”兩個函數(shù)——前者實現(xiàn)5號和12號小燈“亮”、6號和16號小燈“滅”,后者實現(xiàn)5號和12號小燈“滅”、6號和16號小燈“亮”,同時也加入調(diào)試信息的顯示輸出(比如“水平方向通行”)和等待5秒的亮燈(滅燈)延時。接著,再來建立一個名為“自動紅綠燈”的函數(shù),將“水平方向通行”和“豎直方向通行”兩個子函數(shù)添加進來即可——不區(qū)分前后次序(如圖2)。
從左側(cè)“線程”處添加子線程,對應(yīng)調(diào)用的線程函數(shù)是“自動紅綠燈”(注意名稱要正確對應(yīng));然后再建立一個“重復(fù)當‘真’”的循環(huán)結(jié)構(gòu),執(zhí)行的動作即為手動操控搖桿時對“自動紅綠燈”進行中斷,執(zhí)行某方向紅綠燈的臨時通行——建立名為“搖桿X軸”和“搖桿Y軸”的兩個變量,分別賦值為從模擬端口A0和A1進行數(shù)據(jù)的讀取,并且通過兩個輸出調(diào)試信息模塊進行數(shù)據(jù)的提示輸出;建立一個“如果…執(zhí)行…否則如果…執(zhí)行…”雙分支選擇結(jié)構(gòu),分別對應(yīng)調(diào)用“水平方向通行”和“豎直方向通行”函數(shù),各自的判斷條件是搖桿對應(yīng)方向是否有撥動的動作。經(jīng)過測試后發(fā)現(xiàn),搖桿在水平方向上向左和向右撥動到極限時,變量“搖桿X軸”的值大約分別是22和32767(中間狀態(tài)的數(shù)據(jù)值是18774左右),因此構(gòu)建“‘搖桿X軸<=22’或‘搖桿X軸>=32767’”作為判斷搖桿是否發(fā)生了左右撥動的條件,然后就會調(diào)用“水平方向通行”函數(shù),控制水平方向的兩處綠色LED燈發(fā)光,同時豎直方向的兩處紅色LED燈也發(fā)光,并且持續(xù)5秒鐘。同理,第二個條件判斷對應(yīng)搖桿在豎直方向是否有撥動的動作;最后,添加一個等待0.01秒的等待模塊(如圖3)。
也就是說,主程序其實就是兩個子線程在并行,一個(子線程)負責紅綠燈每隔5秒鐘就進行一次紅燈和綠燈不同方向的切換;另一個(循環(huán)結(jié)構(gòu))負責每隔0.01秒鐘就對搖桿進行一次檢測,若有某個方向的撥動動作發(fā)生時,則迅速將自動紅綠燈切換為該方向綠燈通行、對應(yīng)方向紅燈禁行的狀態(tài),持續(xù)5秒鐘后若沒有檢測到搖桿有撥動動作,則恢復(fù)為之前的自動紅綠燈切換狀態(tài)。
程序編寫完畢后保存為“多線程‘雙控’紅綠燈”,然后點擊“連接設(shè)備”與樹莓派進行連接,最后點擊“運行”進行程序的測試,實現(xiàn)了預(yù)期的多線程“雙控”紅綠燈效果(如圖4)。
運行Windows的“遠程桌面連接”,輸入對應(yīng)的IP地址后登錄進入樹莓派操作系統(tǒng),通過菜單命令打開IDE開始進行Python代碼編程。
導(dǎo)入RPi.GPIO模塊:“import RPi.GPIO as GPIO”,導(dǎo)入time模塊:“import time”;為了對模數(shù)轉(zhuǎn)換器進行數(shù)據(jù)讀取,還需要導(dǎo)入模塊:“import Adafruit_ADS1x15”;為了進行線程方面的操作,再導(dǎo)入threading模塊:“import threading”。
接著,通過“GPIO.setwarnings(False)”將錯誤警告提示信息關(guān)閉,并且通過“GPIO.setmode(GPIO.BCM)”將工作模式設(shè)置為BCM模式;然后將5號、6號、12號和16號引腳均設(shè)置為OUT輸出狀態(tài):“GPIO.setup(5,GPIO.OUT)”、“GPIO.setup(6,GPIO.OUT)”、“GPIO.setup(12,GPIO.OUT)”、“GPIO.setup(16,GPIO.OUT)”;最后,通過“adc = Adafruit_ADS1x15.ADS1115()”來生成模數(shù)轉(zhuǎn)換器的具體實例對象(如圖5)。
先來編寫Horizontal_Go()和Vertical_Go()兩個子函數(shù),分別對應(yīng)實現(xiàn)水平方向通行和豎直方向通行的亮燈與滅燈功能。其中,主要是將LED所連接的引腳端口號設(shè)置為HIGH高電平(亮燈)和LOW低電平(滅燈),比如“GPIO.output(5,GPIO.HIGH)”、“GPIO.output(6,GPIO.LOW)”等等,后面的“time.sleep(5)”作用是控制亮燈和滅燈進行5秒鐘的延時。
再來編寫Auto_RedGreen()函數(shù),對應(yīng)圖形化編程中的“自動紅綠燈”函數(shù),直接建立一個“while True”循環(huán)結(jié)構(gòu),對Horizontal_Go()和Vertical_Go()兩個子函數(shù)進行順序調(diào)用即可。
最后再編寫搖桿My_Rocker()函數(shù),實現(xiàn)對搖桿是否有左右或上下?lián)軇觿幼鞯呐袛嗉白龀瞿撤较虻耐ㄐ许憫?yīng)。仍然也是在“while True”循環(huán)結(jié)構(gòu)中,先建立My_X和My_Y兩個變量,分別為其賦值為“adc.read_adc(0,gain=1)”和“adc.read_adc(0, gain=1)”,對應(yīng)從模擬端口A0和A1進行數(shù)據(jù)的讀取(其中的參數(shù)gain是增益);接著使用“if…elif…”雙分支選擇結(jié)構(gòu)分別對My_X和My_Y兩個變量的數(shù)據(jù)進行大小判斷,條件成立的話則分別執(zhí)行Horizontal_Go()和Vertical_Go()兩個子函數(shù)的亮燈和滅燈動作;最后,再添加延時0.01秒的等待命令:“time.sleep(0.01)”(如圖6)。
在主程序中建立t1和t2兩個線程,其值分別為“threading.Thread(target=Auto_RedGreen)”和“threading.Thread(target=My_Rocker)”,通過target參數(shù)來控制調(diào)用自動紅綠燈Auto_RedGreen()函數(shù)和搖桿My_Rocker ()函數(shù);接著,啟動兩個子線程:“t1.start()”、“t2.start()”,執(zhí)行對應(yīng)的線程代碼;最后,添加“while 1:pass”代碼,將程序保存為“多線程’雙控’紅綠燈.py”(如圖7)。
點擊Run按鈕運行程序,與圖形化編程所實現(xiàn)的多線程“雙控”紅綠燈效果完全一致(如圖8)。