牟曉東
在計算機編程中,“多線程”屬于一種并發的執行機制,它的引入也并非為了提高運行效率,其主要作用是追求“小時間片的輪詢”式同步以完成多項任務。以開源硬件編程中的“雙控”紅綠燈為例,十字路口的紅綠燈在正常情況下會每隔一段均勻的時間進行顏色的平均切換,當遇到“緊急情況”需要某個方向(水平或豎直)立刻切換為綠燈通行狀態時,不必等到另外方向的綠色通行時段結束就馬上為緊急通道讓路,處理完緊急情況后再自動切換至“自動”模式,實現對紅綠燈的“雙控”。此時,比較好的選擇就是使用多線程編程的方式來實現。
實驗器材包括樹莓派和古德微擴展板各一塊,搖桿模塊一個,ADS1115模數轉換器一個,紅色和綠色LED燈各四支,330Ω電阻四個,小型面包板一個,杜邦線若干。
首先,將擴展板正確安裝于樹莓派上;接著,將模數轉換器按照標注插入擴展板的Up引腳列(或反向插入Down引腳列),再使用杜邦線將搖桿模塊的+5V和GND端分別連接至擴展板的+5V和GND接地端,搖桿模塊的VRX和VRY端分別連接至擴展板的A0和A1模擬數據輸入端口(保持其按鈕的SW端是懸空不用狀態);然后,在面包板上模擬十字路口的紅綠燈——為了實現兩種顏色的LED燈在同時亮起時有大致一樣的亮度,需要為每支紅色LED燈先串聯一個330Ω電阻(綠色LED燈直接插入面包板即可),要注意處于同一方向上的兩支同色LED燈是并聯的,這樣可以使用一個信號來同時驅動其發光或熄滅;四組同色的LED燈連接好之后,分別再通過杜邦線連接至擴展板的5號、6號、12號和16號引腳(如圖1)。

最后,給樹莓派連接數據線,通電啟動操作系統。
通過瀏覽器訪問古德微網站(http://www.gdwrobot.cn/robot_system/#/home/carcontrol),登錄自己的賬號后進入圖形化編程界面。
為了實現水平和豎直兩個方向各自“亮綠燈”的通行狀態,同時也對應另一方向“亮紅燈”的禁行狀態,需要先編寫“水平方向通行”和“豎直方向通行”兩個函數——前者實現5號和12號小燈“亮”、6號和16號小燈“滅”,后者實現5號和12號小燈“滅”、6號和16號小燈“亮”,同時也加入調試信息的顯示輸出(比如“水平方向通行”)和等待5秒的亮燈(滅燈)延時。接著,再來建立一個名為“自動紅綠燈”的函數,將“水平方向通行”和“豎直方向通行”兩個子函數添加進來即可——不區分前后次序(如圖2)。

從左側“線程”處添加子線程,對應調用的線程函數是“自動紅綠燈”(注意名稱要正確對應);然后再建立一個“重復當‘真’”的循環結構,執行的動作即為手動操控搖桿時對“自動紅綠燈”進行中斷,執行某方向紅綠燈的臨時通行——建立名為“搖桿X軸”和“搖桿Y軸”的兩個變量,分別賦值為從模擬端口A0和A1進行數據的讀取,并且通過兩個輸出調試信息模塊進行數據的提示輸出;建立一個“如果…執行…否則如果…執行…”雙分支選擇結構,分別對應調用“水平方向通行”和“豎直方向通行”函數,各自的判斷條件是搖桿對應方向是否有撥動的動作。經過測試后發現,搖桿在水平方向上向左和向右撥動到極限時,變量“搖桿X軸”的值大約分別是22和32767(中間狀態的數據值是18774左右),因此構建“‘搖桿X軸<=22’或‘搖桿X軸>=32767’”作為判斷搖桿是否發生了左右撥動的條件,然后就會調用“水平方向通行”函數,控制水平方向的兩處綠色LED燈發光,同時豎直方向的兩處紅色LED燈也發光,并且持續5秒鐘。同理,第二個條件判斷對應搖桿在豎直方向是否有撥動的動作;最后,添加一個等待0.01秒的等待模塊(如圖3)。

也就是說,主程序其實就是兩個子線程在并行,一個(子線程)負責紅綠燈每隔5秒鐘就進行一次紅燈和綠燈不同方向的切換;另一個(循環結構)負責每隔0.01秒鐘就對搖桿進行一次檢測,若有某個方向的撥動動作發生時,則迅速將自動紅綠燈切換為該方向綠燈通行、對應方向紅燈禁行的狀態,持續5秒鐘后若沒有檢測到搖桿有撥動動作,則恢復為之前的自動紅綠燈切換狀態。
程序編寫完畢后保存為“多線程‘雙控’紅綠燈”,然后點擊“連接設備”與樹莓派進行連接,最后點擊“運行”進行程序的測試,實現了預期的多線程“雙控”紅綠燈效果(如圖4)。

運行Windows的“遠程桌面連接”,輸入對應的IP地址后登錄進入樹莓派操作系統,通過菜單命令打開IDE開始進行Python代碼編程。
導入RPi.GPIO模塊:“import RPi.GPIO as GPIO”,導入time模塊:“import time”;為了對模數轉換器進行數據讀取,還需要導入模塊:“import Adafruit_ADS1x15”;為了進行線程方面的操作,再導入threading模塊:“import threading”。
接著,通過“GPIO.setwarnings(False)”將錯誤警告提示信息關閉,并且通過“GPIO.setmode(GPIO.BCM)”將工作模式設置為BCM模式;然后將5號、6號、12號和16號引腳均設置為OUT輸出狀態:“GPIO.setup(5,GPIO.OUT)”、“GPIO.setup(6,GPIO.OUT)”、“GPIO.setup(12,GPIO.OUT)”、“GPIO.setup(16,GPIO.OUT)”;最后,通過“adc = Adafruit_ADS1x15.ADS1115()”來生成模數轉換器的具體實例對象(如圖5)。

先來編寫Horizontal_Go()和Vertical_Go()兩個子函數,分別對應實現水平方向通行和豎直方向通行的亮燈與滅燈功能。其中,主要是將LED所連接的引腳端口號設置為HIGH高電平(亮燈)和LOW低電平(滅燈),比如“GPIO.output(5,GPIO.HIGH)”、“GPIO.output(6,GPIO.LOW)”等等,后面的“time.sleep(5)”作用是控制亮燈和滅燈進行5秒鐘的延時。
再來編寫Auto_RedGreen()函數,對應圖形化編程中的“自動紅綠燈”函數,直接建立一個“while True”循環結構,對Horizontal_Go()和Vertical_Go()兩個子函數進行順序調用即可。
最后再編寫搖桿My_Rocker()函數,實現對搖桿是否有左右或上下撥動動作的判斷及做出某方向的通行響應。仍然也是在“while True”循環結構中,先建立My_X和My_Y兩個變量,分別為其賦值為“adc.read_adc(0,gain=1)”和“adc.read_adc(0, gain=1)”,對應從模擬端口A0和A1進行數據的讀取(其中的參數gain是增益);接著使用“if…elif…”雙分支選擇結構分別對My_X和My_Y兩個變量的數據進行大小判斷,條件成立的話則分別執行Horizontal_Go()和Vertical_Go()兩個子函數的亮燈和滅燈動作;最后,再添加延時0.01秒的等待命令:“time.sleep(0.01)”(如圖6)。

在主程序中建立t1和t2兩個線程,其值分別為“threading.Thread(target=Auto_RedGreen)”和“threading.Thread(target=My_Rocker)”,通過target參數來控制調用自動紅綠燈Auto_RedGreen()函數和搖桿My_Rocker ()函數;接著,啟動兩個子線程:“t1.start()”、“t2.start()”,執行對應的線程代碼;最后,添加“while 1:pass”代碼,將程序保存為“多線程’雙控’紅綠燈.py”(如圖7)。

點擊Run按鈕運行程序,與圖形化編程所實現的多線程“雙控”紅綠燈效果完全一致(如圖8)。
