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