樂萬德,任 靜,劉舟洲,初建杰
(1.西安航空學(xué)院計算機學(xué)院,陜西西安 710077;2.西北工業(yè)大學(xué)工業(yè)設(shè)計與人機工效工信部重點實驗室,陜西 西安 710072)
Arduino因便捷靈活、開源等特性,成為了一款廣泛使用的電子原型平臺[1]。Arduino 不僅在高校創(chuàng)新創(chuàng)業(yè)及實驗教學(xué)中發(fā)揮著越來越重要的作用[2-3],也逐漸走向產(chǎn)品及工程應(yīng)用[4-6]。隨著人工智能及機器人工程的發(fā)展,基于Arduino 的智能小車成為了研究熱點[7-11]。
串口通信在Arduino 系統(tǒng)中具有十分重要的地位,Arduino 程序燒錄最常用的方式是通過USB 從電腦上傳到Arduino 板,使用的就是串口協(xié)議;Arduino板與紅外、藍(lán)牙模塊、WiFi 模塊等無線通信模塊之間也是通過串口通信;基于此的衍生應(yīng)用,如遠(yuǎn)程控制終端通過無線模塊對Arduino 設(shè)備遙控指令的下發(fā)也是通過串口通信實現(xiàn)的。所以研究串口通信對于遙控指令系統(tǒng)具有十分重要的意義。
典型的串口通信控制指令使用控制端設(shè)備,通過無線模塊如紅外、藍(lán)牙、WiFi 等發(fā)送字符或字符串命令到Arduino 對應(yīng)的無線通信模塊,對應(yīng)無線通信模塊將字符或者字符串送到Arduino 串口輸入緩沖,Arduino 對字符或者字符串進(jìn)行解析,并按照Arduino 程序定義,對不同的字符或者字符串執(zhí)行不同的動作,從而實現(xiàn)對Arduino 設(shè)備的遠(yuǎn)程控制。一種Arduino 遙控連接示意圖如圖1 所示。
圖1 Arduino遙控連接示意圖
分析文獻(xiàn)發(fā)現(xiàn),通過解析字符或者字符串來實現(xiàn)串口通信控制算法和流程是一種常見方式[11-12]。以文獻(xiàn)[11]為例,其定義的串口通信協(xié)議格式為:幀頭+命令碼+操作碼。如向Arduino 下發(fā)不同的控制指令:0x55AA05、0x55AA06、0x55AA07、0x55AA08、0x55AA09,分別表示小車行進(jìn)過程中的1 至5 個速度檔位。其中0x55 為幀頭,0xAA 為遙控指令,0x05~09 分別代表不同的速度。
這種串口通信控制協(xié)議對于簡單的控制是可行的,其不足也十分明顯:
1)程序可讀性差,單條控制指令只是抽象的字符串,如0x55AA05,其含義不清楚。面對多條控制指令,開發(fā)及測試人員都很難顧名思義,出現(xiàn)問題不容易排查;
2)程序結(jié)構(gòu)化程度低,各條指令扁平排布,即便相關(guān)性很強的指令之間也是如此,比如0x05~0x09代表小車的5 個檔位。顯然在復(fù)雜程序中,通過一個根節(jié)點來統(tǒng)領(lǐng),組織成樹狀結(jié)構(gòu)更優(yōu)。
3)多條指令的組合缺乏靈活性。比如原始方案a 指令對應(yīng)的動作和b 指令對應(yīng)的動作是獨立的,如果需要發(fā)一條指令同時執(zhí)行原來a 指令和b 指令對應(yīng)的動作,原始方案中需要定義一個新的字符串,并把原先對應(yīng)a 指令和b 指令的動作代碼合并。
嵌入式系統(tǒng)對于外界事件的響應(yīng)常用兩種方式分別是輪詢與中斷。輪詢方式采用循環(huán)結(jié)構(gòu)[13],不斷地主動查詢某事件是否發(fā)生,一旦某事件發(fā)生,則采取相應(yīng)的行動;中斷方式則是被動地等待事件發(fā)生,一旦事件發(fā)生,則中斷觸發(fā)并調(diào)用中斷處理函數(shù)對事件進(jìn)行處理,文中遙控系統(tǒng)采用這種方式。
Arduino 系統(tǒng)從Arduino1.0 版本后,新增了serialEvent()函數(shù),用于響應(yīng)串口緩沖事件,即串口緩沖中收到字符串,則會調(diào)用該函數(shù)[14]。
需要注意的是,serialEvent()并非真正意義上的事件響應(yīng)函數(shù),因此無法做到嚴(yán)格意義上的實時響應(yīng)。Arduino 系統(tǒng)在每次調(diào)用loop()函數(shù)后,檢查串口緩沖是否有數(shù)據(jù),確定是否調(diào)用serialEvent()函數(shù)。因此稱其為偽中斷,使用serialEvent()可改善程序結(jié)構(gòu),使程序脈絡(luò)更為清晰?;趕erialEvent 偽中斷的控制程序框架如圖2 所示。
圖2 基于serialEvent的程序框架
JSON(JavaScript Object Notation,JS 對象簡譜)是一種輕量級的數(shù)據(jù)交換格式。它源于JavaScript,但其應(yīng)用卻更廣泛。它基于ECMAScript 的一個子集,作為一種數(shù)據(jù)格式,完全獨立于編程語言,并采用文本格式來存儲和表示數(shù)據(jù)[15]。
JSON 只包含6 個構(gòu)造字符,用以表達(dá)一個序列化的對象或數(shù)組,分別是:{}表示的對象,[]表示數(shù)組,逗號用于分隔對象成員,冒號用于分隔鍵值對。值可以是對象、數(shù)組、數(shù)字、字符串或者3 個字面值(false、null、true)中的一個。JSON 具有簡潔和清晰的層次結(jié)構(gòu),不僅易于計算機生成和解析,也易于自然人讀寫。與XML 一樣,JSON 是一種理想的數(shù)據(jù)交換語言。但比起XML 規(guī)范的標(biāo)簽形式,JSON 表達(dá)方式要比XML 少很多結(jié)構(gòu)上的字符[16],因此更適合用于Arduino 這類資源受限的嵌入式設(shè)備及其串口控制。
最基本的JSON 對象是一個"key/value"(鍵值)對集合。一個對象以一對大括號括起來,每個"key"與"value"之間以冒號隔開;多個"key/value"對之間用逗號分隔。格式如下:
JSON 對象也可以具有層次結(jié)構(gòu)。鍵值也可以是多個JSON 鍵值對構(gòu)成的JSON 對象集合或數(shù)組。
Arduino 板通過對其數(shù)字接口和模擬接口寫入值,控制該接口相連的外設(shè)。Arduino 控制指令常用函數(shù)如下:
Arduino 數(shù)字端口為雙向IO,因此在控制數(shù)字IO時,需首先用pinMode 函數(shù)把IO 口設(shè)置為OUTPUT模式。對數(shù)字端口的控制采用digitalWrite 函數(shù),數(shù)字IO 端口的值valBool 為HIGH 或LOW。對模擬輸出的控制采用analogWrite 函數(shù),Arduino 在特定的數(shù)字IO 口上通過PWM(Pulse Width Modulation,脈寬調(diào)制)實現(xiàn)模擬輸出,對外接設(shè)備進(jìn)行模擬輸出控制。以Arduino UNO 為例,其PWM 模擬輸出端口為IO3、IO5、IO6、IO9、IO10、IO11。PWM 模擬輸出的值域范圍為0~255。
2.4.1 ArduinoJSON主要類及方法
ArduinoJSON V6 版本[17]的主要類及方法如圖3所示。
圖3 ArduinoJSON主要元素示意圖
ArduinoJSON主要分為三大類:
1)JsonDocument,是整個JSON 庫的入口,它負(fù)責(zé)高效管理內(nèi)存以及調(diào)用JSON 解析器;V6 版本的JSON 操作都在JsonDocument 上面進(jìn)行。
2)JsonObject,存儲key-value 鍵值對的集合,每一個key 是一個字符串,每一個value 是一個JsonVariant。最簡單的JSON 對象可以用根節(jié)點為JsonObject,其內(nèi)僅僅包含key-value 鍵值對的集合,沒有嵌套JsonObject 或者JsonArray。以鍵為參數(shù)的[]操作符即可獲取對應(yīng)鍵的JSON 值。
3)JsonArray,是JSON 對象構(gòu)成的數(shù)組。
JSON 的操作方法主要與解析、構(gòu)造相關(guān),其中deserializeJson()函數(shù)用于解析JSON 輸入并將結(jié)果放入JsonDocument 中。
2.4.2 ArduinoJSON類轉(zhuǎn)換
JsonDocument::as<T>()函數(shù)用于獲取JSON 文檔頂節(jié)點,并把它轉(zhuǎn)成T類型,T類型包含上述JsonArray、JsonObject。
此方法只會返回JsonDocument 頂節(jié)點的引用。如果頂節(jié)點的類型和強制轉(zhuǎn)換的T 類型不匹配,此方法將會返回空引用。比如,如果JsonDocument 是一個JsonArray,當(dāng)調(diào)用JsonDocument::as(),會返回空J(rèn)sonObject。
遙控設(shè)備對Arduino 的控制是通過串口通信,將相關(guān)的控制指令傳遞給Arduino。Arduino 收到相關(guān)指令后,調(diào)用Arduino 相關(guān)數(shù)字和模擬接口函數(shù)對具體的端口進(jìn)行控制。Arduino 函數(shù)中涉及到的端口及值可以是Arduino 中的預(yù)設(shè)值、計算值,可以通過串口從遙控終端傳遞到Arduino 設(shè)備[18]。
基于JSON的Arduino串口控制指令流程圖如圖4所示,程序框架如前所述采用偽中斷方式的串口事件方式,每次loop 執(zhí)行完后,都會去檢查串口輸入緩沖里是否有字符串,一旦此前遙控終端通過紅外、WiFi 或者藍(lán)牙等無線通信方式將字符串及JSON 格式消息送到Arduino 串口緩沖,則執(zhí)行偽中斷serialEvent 函數(shù)的程序。
圖4 基于JSON的Arduino串口控制指令流程圖
該文算法中Arduino 從串口中讀取JSON 格式控制指令,因此JSON 的數(shù)據(jù)源是串口。算法首先定義一個JsonDocument 對象,使用函數(shù)deserializeJson 解析從串口接收數(shù)據(jù)并放入JsonDocument 對象中,再通過JsonDocument 對象的模板方法as<T>,獲取根節(jié)點,并把它轉(zhuǎn)成T 類型[19]。這里的模板參數(shù)T 為JsonObject、JsonArray或者JsonVariant,根據(jù)JSON對象的實際情況來決定。上述解碼過程的主要程序如下:
圖5 為實驗硬件,在某型基于Arduino 的遙控車基礎(chǔ)上進(jìn)行改裝而成。在原車基礎(chǔ)上擴展了4 個led燈模擬汽車的前后燈和蜂鳴器buz2,并通過面包板進(jìn)行線路擴展。
圖5 遙控指令控制系統(tǒng)實驗硬件
原遙控控制程序為簡單的讀取字符并采用級聯(lián)if-else if-else 結(jié)構(gòu)進(jìn)行控制。下面是原車控制程序片段:
以run()函數(shù)為例,原車中決定小車運行速度的PWM 的值在代碼里是寫死的,意味著從遙控終端不能控制行車速度。
采用基于JSON 的遙控指令系統(tǒng),設(shè)計控制指令JSON 結(jié)構(gòu)樹及協(xié)議如下:
JSON根節(jié)點采用JsonObject,其內(nèi)部第一層為motor對象、led嵌套對象和buzzer 數(shù)組。motor對象包括兩個元素,分別是左輪的速度和右輪的速度,兩個速度值不再是寫死在Arduino 程序里,而是由遙控終端通過JSON 指令來傳遞的,增加了控制的靈活性。led 內(nèi)嵌對象和buzzer 數(shù)組是在原車程序基礎(chǔ)上針對擴充的部件增加的JSON 控制指令。led 對象內(nèi)嵌兩個對象,分別是front 和back,front 前燈一般同時亮滅,因此只用一個鍵值對表達(dá)。而back 表示后燈,作為轉(zhuǎn)向指示時左右燈不同時亮滅,因而back 子對象又分別包含left 和right 兩個元素。buzzer 數(shù)組則演示了JSON 數(shù)組的用法,可以通過索引直接訪問數(shù)組元素。值得說明的是,JSON 對象是集合,通過key字符串訪問元素,與元素位置無關(guān);JSON 數(shù)組則通過index 訪問元素,與元素位置有關(guān)。
針對上述JSON 控制指令設(shè)計的Arduino 解析核心代碼片段如下:
對比原車run()函數(shù),motor_left、motor_right 作為自變量參數(shù)傳入run()函數(shù),決定了小車運行速度,motor_left、motor_right 值相同且為正,小車前進(jìn)并直行,為負(fù)則后退,兩值有差異則轉(zhuǎn)彎。同理,解析出來的led 參數(shù)和buzzer 參數(shù)控制led 燈和buzzer,不僅解決了參數(shù)的靈活傳遞,也使程序結(jié)構(gòu)富有層次性,功能擴展也更方便。
文中設(shè)計了一種Arduino 遙控指令控制系統(tǒng),系統(tǒng)采用Arduino 串口事件偽中斷機制作為控制程序框架,基于JSON 格式化數(shù)據(jù)在遙控終端與Arduino之間傳遞控制指令。該控制系統(tǒng)解決了扁平結(jié)構(gòu)的字符或者字符串控制算法中參數(shù)傳遞不靈活、程序結(jié)構(gòu)可讀性不佳、可擴展性可維護性差的問題。以智能小車為實驗對象,驗證了該控制系統(tǒng)的可行性和有效性。