樂萬德,任 靜,劉舟洲,初建杰
(1.西安航空學院計算機學院,陜西西安 710077;2.西北工業大學工業設計與人機工效工信部重點實驗室,陜西 西安 710072)
Arduino因便捷靈活、開源等特性,成為了一款廣泛使用的電子原型平臺[1]。Arduino 不僅在高校創新創業及實驗教學中發揮著越來越重要的作用[2-3],也逐漸走向產品及工程應用[4-6]。隨著人工智能及機器人工程的發展,基于Arduino 的智能小車成為了研究熱點[7-11]。
串口通信在Arduino 系統中具有十分重要的地位,Arduino 程序燒錄最常用的方式是通過USB 從電腦上傳到Arduino 板,使用的就是串口協議;Arduino板與紅外、藍牙模塊、WiFi 模塊等無線通信模塊之間也是通過串口通信;基于此的衍生應用,如遠程控制終端通過無線模塊對Arduino 設備遙控指令的下發也是通過串口通信實現的。所以研究串口通信對于遙控指令系統具有十分重要的意義。
典型的串口通信控制指令使用控制端設備,通過無線模塊如紅外、藍牙、WiFi 等發送字符或字符串命令到Arduino 對應的無線通信模塊,對應無線通信模塊將字符或者字符串送到Arduino 串口輸入緩沖,Arduino 對字符或者字符串進行解析,并按照Arduino 程序定義,對不同的字符或者字符串執行不同的動作,從而實現對Arduino 設備的遠程控制。一種Arduino 遙控連接示意圖如圖1 所示。

圖1 Arduino遙控連接示意圖
分析文獻發現,通過解析字符或者字符串來實現串口通信控制算法和流程是一種常見方式[11-12]。以文獻[11]為例,其定義的串口通信協議格式為:幀頭+命令碼+操作碼。如向Arduino 下發不同的控制指令:0x55AA05、0x55AA06、0x55AA07、0x55AA08、0x55AA09,分別表示小車行進過程中的1 至5 個速度檔位。其中0x55 為幀頭,0xAA 為遙控指令,0x05~09 分別代表不同的速度。
這種串口通信控制協議對于簡單的控制是可行的,其不足也十分明顯:
1)程序可讀性差,單條控制指令只是抽象的字符串,如0x55AA05,其含義不清楚。面對多條控制指令,開發及測試人員都很難顧名思義,出現問題不容易排查;
2)程序結構化程度低,各條指令扁平排布,即便相關性很強的指令之間也是如此,比如0x05~0x09代表小車的5 個檔位。顯然在復雜程序中,通過一個根節點來統領,組織成樹狀結構更優。
3)多條指令的組合缺乏靈活性。比如原始方案a 指令對應的動作和b 指令對應的動作是獨立的,如果需要發一條指令同時執行原來a 指令和b 指令對應的動作,原始方案中需要定義一個新的字符串,并把原先對應a 指令和b 指令的動作代碼合并。
嵌入式系統對于外界事件的響應常用兩種方式分別是輪詢與中斷。輪詢方式采用循環結構[13],不斷地主動查詢某事件是否發生,一旦某事件發生,則采取相應的行動;中斷方式則是被動地等待事件發生,一旦事件發生,則中斷觸發并調用中斷處理函數對事件進行處理,文中遙控系統采用這種方式。
Arduino 系統從Arduino1.0 版本后,新增了serialEvent()函數,用于響應串口緩沖事件,即串口緩沖中收到字符串,則會調用該函數[14]。
需要注意的是,serialEvent()并非真正意義上的事件響應函數,因此無法做到嚴格意義上的實時響應。Arduino 系統在每次調用loop()函數后,檢查串口緩沖是否有數據,確定是否調用serialEvent()函數。因此稱其為偽中斷,使用serialEvent()可改善程序結構,使程序脈絡更為清晰。基于serialEvent 偽中斷的控制程序框架如圖2 所示。

圖2 基于serialEvent的程序框架
JSON(JavaScript Object Notation,JS 對象簡譜)是一種輕量級的數據交換格式。它源于JavaScript,但其應用卻更廣泛。它基于ECMAScript 的一個子集,作為一種數據格式,完全獨立于編程語言,并采用文本格式來存儲和表示數據[15]。
JSON 只包含6 個構造字符,用以表達一個序列化的對象或數組,分別是:{}表示的對象,[]表示數組,逗號用于分隔對象成員,冒號用于分隔鍵值對。值可以是對象、數組、數字、字符串或者3 個字面值(false、null、true)中的一個。JSON 具有簡潔和清晰的層次結構,不僅易于計算機生成和解析,也易于自然人讀寫。與XML 一樣,JSON 是一種理想的數據交換語言。但比起XML 規范的標簽形式,JSON 表達方式要比XML 少很多結構上的字符[16],因此更適合用于Arduino 這類資源受限的嵌入式設備及其串口控制。
最基本的JSON 對象是一個"key/value"(鍵值)對集合。一個對象以一對大括號括起來,每個"key"與"value"之間以冒號隔開;多個"key/value"對之間用逗號分隔。格式如下:

JSON 對象也可以具有層次結構。鍵值也可以是多個JSON 鍵值對構成的JSON 對象集合或數組。
Arduino 板通過對其數字接口和模擬接口寫入值,控制該接口相連的外設。Arduino 控制指令常用函數如下:

Arduino 數字端口為雙向IO,因此在控制數字IO時,需首先用pinMode 函數把IO 口設置為OUTPUT模式。對數字端口的控制采用digitalWrite 函數,數字IO 端口的值valBool 為HIGH 或LOW。對模擬輸出的控制采用analogWrite 函數,Arduino 在特定的數字IO 口上通過PWM(Pulse Width Modulation,脈寬調制)實現模擬輸出,對外接設備進行模擬輸出控制。以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 庫的入口,它負責高效管理內存以及調用JSON 解析器;V6 版本的JSON 操作都在JsonDocument 上面進行。
2)JsonObject,存儲key-value 鍵值對的集合,每一個key 是一個字符串,每一個value 是一個JsonVariant。最簡單的JSON 對象可以用根節點為JsonObject,其內僅僅包含key-value 鍵值對的集合,沒有嵌套JsonObject 或者JsonArray。以鍵為參數的[]操作符即可獲取對應鍵的JSON 值。
3)JsonArray,是JSON 對象構成的數組。
JSON 的操作方法主要與解析、構造相關,其中deserializeJson()函數用于解析JSON 輸入并將結果放入JsonDocument 中。
2.4.2 ArduinoJSON類轉換
JsonDocument::as<T>()函數用于獲取JSON 文檔頂節點,并把它轉成T類型,T類型包含上述JsonArray、JsonObject。
此方法只會返回JsonDocument 頂節點的引用。如果頂節點的類型和強制轉換的T 類型不匹配,此方法將會返回空引用。比如,如果JsonDocument 是一個JsonArray,當調用JsonDocument::as(),會返回空JsonObject。
遙控設備對Arduino 的控制是通過串口通信,將相關的控制指令傳遞給Arduino。Arduino 收到相關指令后,調用Arduino 相關數字和模擬接口函數對具體的端口進行控制。Arduino 函數中涉及到的端口及值可以是Arduino 中的預設值、計算值,可以通過串口從遙控終端傳遞到Arduino 設備[18]。
基于JSON的Arduino串口控制指令流程圖如圖4所示,程序框架如前所述采用偽中斷方式的串口事件方式,每次loop 執行完后,都會去檢查串口輸入緩沖里是否有字符串,一旦此前遙控終端通過紅外、WiFi 或者藍牙等無線通信方式將字符串及JSON 格式消息送到Arduino 串口緩沖,則執行偽中斷serialEvent 函數的程序。

圖4 基于JSON的Arduino串口控制指令流程圖
該文算法中Arduino 從串口中讀取JSON 格式控制指令,因此JSON 的數據源是串口。算法首先定義一個JsonDocument 對象,使用函數deserializeJson 解析從串口接收數據并放入JsonDocument 對象中,再通過JsonDocument 對象的模板方法as<T>,獲取根節點,并把它轉成T 類型[19]。這里的模板參數T 為JsonObject、JsonArray或者JsonVariant,根據JSON對象的實際情況來決定。上述解碼過程的主要程序如下:

圖5 為實驗硬件,在某型基于Arduino 的遙控車基礎上進行改裝而成。在原車基礎上擴展了4 個led燈模擬汽車的前后燈和蜂鳴器buz2,并通過面包板進行線路擴展。

圖5 遙控指令控制系統實驗硬件
原遙控控制程序為簡單的讀取字符并采用級聯if-else if-else 結構進行控制。下面是原車控制程序片段:


以run()函數為例,原車中決定小車運行速度的PWM 的值在代碼里是寫死的,意味著從遙控終端不能控制行車速度。

采用基于JSON 的遙控指令系統,設計控制指令JSON 結構樹及協議如下:


JSON根節點采用JsonObject,其內部第一層為motor對象、led嵌套對象和buzzer 數組。motor對象包括兩個元素,分別是左輪的速度和右輪的速度,兩個速度值不再是寫死在Arduino 程序里,而是由遙控終端通過JSON 指令來傳遞的,增加了控制的靈活性。led 內嵌對象和buzzer 數組是在原車程序基礎上針對擴充的部件增加的JSON 控制指令。led 對象內嵌兩個對象,分別是front 和back,front 前燈一般同時亮滅,因此只用一個鍵值對表達。而back 表示后燈,作為轉向指示時左右燈不同時亮滅,因而back 子對象又分別包含left 和right 兩個元素。buzzer 數組則演示了JSON 數組的用法,可以通過索引直接訪問數組元素。值得說明的是,JSON 對象是集合,通過key字符串訪問元素,與元素位置無關;JSON 數組則通過index 訪問元素,與元素位置有關。
針對上述JSON 控制指令設計的Arduino 解析核心代碼片段如下:


對比原車run()函數,motor_left、motor_right 作為自變量參數傳入run()函數,決定了小車運行速度,motor_left、motor_right 值相同且為正,小車前進并直行,為負則后退,兩值有差異則轉彎。同理,解析出來的led 參數和buzzer 參數控制led 燈和buzzer,不僅解決了參數的靈活傳遞,也使程序結構富有層次性,功能擴展也更方便。
文中設計了一種Arduino 遙控指令控制系統,系統采用Arduino 串口事件偽中斷機制作為控制程序框架,基于JSON 格式化數據在遙控終端與Arduino之間傳遞控制指令。該控制系統解決了扁平結構的字符或者字符串控制算法中參數傳遞不靈活、程序結構可讀性不佳、可擴展性可維護性差的問題。以智能小車為實驗對象,驗證了該控制系統的可行性和有效性。