Arduino探索漫遊車3—程式設計

指令和指令包結構

既然我們使用LoRa來傳輸和接收資料,就必須記住該技術的設計用途:小容量資料的遠距離傳輸。這意味著我們要儘量精簡我們的指令,因為每一個位元組都會被計數。我們可以只發送一些字串,例如“馬達左側50”來將左側的馬達設定為50%的轉速,但是這其實非常浪費空間。這個字串佔用了14個位元組的資料容量,但是僅僅傳輸了三個區塊:該指令的子系統目標(“馬達”)中哪些馬達會被影響(“左側”)以及它們將會被設定的轉速所占最大轉速的百分比(“50”)。可以肯定地說,我們可以做得更好。這種類型指令的優點是非常易於閱讀,但其實,再重申一下,只要系統運行正常,在指令執行這一塊是不需要人為干預的。

所以,我決定不使用那些長的字串,而是研發一種簡單的指令系統,在每個指令包中使用的指令最長僅為4個位元組。指令包中的第一個位元組叫做首位址,將會被進一步分為兩半:高四位元包含來自控制系統的指令分類,低四位元用來作為機器人的響應。0表示成功,非零值是具有已知含義的錯誤代碼。

第1個位元組 指令描述
0x00 停止所有馬達
0x10 設定所有馬達的轉速和轉向
0x20 設定所有左側馬達的轉速和轉向
0x30 設定所有右側馬達的轉速和轉向
0x40 設定相機傾斜角度
0x50 設定相機平移距離
0x60 使用相機拍攝圖片
0x70 開始JPEG 傳輸
0x80 強制啟動新感測器測量
0x90 獲取最新感測器資料
0xA0 重新發送最終指令包
0xB0 設定LoRa調製穩壓器配置
0xC0 獲取當前所有存在的錯誤資訊
0xD0 未被使用
0xE0 未被使用
0xF0 未被使用

指令包的下一部分內容是有效負載—這也是在有關頭檔中指令的所有附加資訊存儲的地方。這部分比首位址更簡單一些,因為它的內容取決於指令類型:有些指令不需要任何附加資訊,比如說0x00(停止兩側馬達)。所以這些指令的有效負載為空。然而,一些命令要求額外資料不能超過三個位元組,比如指令0xD0(LoRa配置),要求每個主要設定,如頻寬、擴頻因數、以及編碼率(有關LoRa細節請參考 LoRaLib課程 或者 GitHub wiki)只占一個位元組。下表描述了所有指令包的結構。

第1個位元組 第2個位元組 第3個位元組 第4個位元組
0x00
0x10 左側PWM控制轉速 右側PWM控制轉速 轉向
0x20 左側PWM控制轉速 轉向
0x30 右側PWM控制轉速 轉向
0x40 傾斜角度
0x50 平移距離
0x60
0x70 圖片編碼
0x80 感測器 ID(s)
0x90 感測器 ID(s)
0xA0
0xB0 頻寬 擴頻因數 編碼率
0xC0
0xD0
0xE0
0xF0

當然,這些只是由控制系統發送然後機器人來接收的指令包。我們也希望機器人能夠作出響應。對於某些指令,這種響應可能非常簡單,僅僅是為了讓控制系統知道指令是否被成功執行。如果您仔細看上文中的表格,會注意到所有的指令都只用了第一個指令位元組的高四位元,這樣剩下的低四位可以用於響應。這種方法有兩個顯著的優勢:首先,始終可以追蹤到每個響應所屬的指令,因為指令位元總是作為響應值的一部分返回;其次,對於每一個指令,都有足夠的空間用於16種不同的響應。

讓我們用一個例子來進行說明。假設我們想用相機拍攝一張圖片,那麼我們會發送指令0x60。那麼機器人有16種不同的方式來響應。如果響應是0x60,這意味指令已經被成功執行。所有其他類型的響應,0x61到0x6F,都意味著出現了錯誤。這樣我們不僅知道指令執行失敗了,還會知道為什麼失敗並且進行修正。當然,這些響應不一定總是只有一個位元組的長度,有些指令會要求機器發回一些附加資訊,比如感測器資料。下表顯示的是所有針對不同指令的響應包。

指令 第1個位元組 第2個位元組 第3個位元組 第4~240位元組
0x00 0x00
0x10 0x10
0x20 0x20
0x30 0x30
0x40 0x40
0x50 0x50
0x60 0x6_
0x70 0x7_ 圖像資料 圖像資料 圖像資料
0x80 0x8_ 發生錯誤的感測器 感測器資料 感測器資料
0x90 0x9_ 發生錯誤的感測器 感測器資料 感測器資料
0xA0 0xA0
0xB0 0xB0
0xC0 0xC_ 錯誤標誌 錯誤標誌
0xD0 0xD0
0xE0 0xE0
0xF0 0xF0

您可以注意到某些指令(主要是關於馬達和伺服器的那些指令)只能返回0(成功)。這是因為當前版本中的板載電子裝置無法判斷這些指令是否被成功執行。為了達到驗證的目的,我們需要增加新的裝置來檢查馬達是否在運轉,或者伺服器是否在正確的位置。這應該不太困難,讓我們暫時忽略它,來看看那些可能會有報錯響應的指令:

  • 0x60 (用相機拍攝照片)
    這項指令有多種報錯響應。如果沒有相機,那麼機器人會返回0x61。如果有相機並且能夠正常工作,但是無法拍攝照片,那麼會返回0x62。最後,如果成功拍攝了照片,但是不能存儲,那麼返回值為0x63。
  • 0x70 (開始JPEG 傳輸)
    這同樣是一個有多種返回值的指令。如果您能夠回想起我之前的文章Arduino上的JPEG解碼,就知道第一個資料包裡是有關圖片的資訊,而非真實像素值。我們需要對這兩種類型進行區分,所以說如果資料包僅包含圖片資訊,響應值為0x70,後跟類似寬度、高度和單片機(MCU)計數的資訊。如果資料包包含原始像素值,機器人的響應值為0x71,後跟238位元組的像素資料。
    現在我們介紹報錯代碼:如果響應值是0x72,這意味著無法找到SD卡。如果是0x73,就表示SD卡沒問題,但是無法找到請求的圖片。
  • 0x80 (強制啟動新感測器測量)
    該指令只有一種失敗模式—所請求的感測器無法進行測量。這種情況下,響應值為0x81,後跟有包含暗示感測器故障的一個位元組。在這個位元組中,位元的索引對應下列感測器的順序(從MSB到LSB):左VNH5019,右VNH5019, BD1020HFV, ML8511A, BM1383GLV, KX022-1020, RPR-0521RS和BM1422GMV。比如,如果響應的第二個位元組是0x03 (0b0000 00011),表示感測器RPR-0521RS 和 BM1422GMV故障。
  • 0x90 (獲取最新感測器資料)
    該指令與0x80非常相似—只有當某些感測器故障時才會顯示報錯。如果那種情況發生,將會返回0x91,後跟有錯誤標誌的相同位元組。之後,將會有來自感測器的最多46位元組的資料。如果沒有感測器發生故障,那麼返回的第二個位元組將會是簡單的0x00,後跟有包含所有46位元組的資料。
  • 0xB0 (設定LoRa調製穩壓器配置)
    這是目前應用的指令中最後一個會報錯的指令,雖然這項指令本不應該會執行失敗。該指令將設定新的LoRa調製穩壓器配置,LoRaLib檢驗所提供配置的有效性。如果有一個或者更多的配置值無效,那麼響應位元組中低四位元之一將會被設定為相應值。所以如果提供的頻寬是無效的,那麼機器人將返回0xB1 (0b1011 0001)。如果提供的所有配置值都不正確,那麼會返回0xB7 (0b1011 0111)。這項指令本不應該會失敗,因為用戶只能從已知設定值中進行選擇。但是,在執行該指令時最好還是再檢查一遍。LoRa模組是最關鍵的子系統,沒有了它,就無法對機器人進行控制。由於LoRa調製的屬性,兩個模組上的設定必須相同,否則,接收器就無法對傳輸內容進行解調。

在解釋完所有內容後,讓我們回到最開始的內容。我們想要把左側馬達的轉速設定為50%。我們已經知道了指令首位址為0x20。所以根據表格內容,我們可以填寫剩下的位元組,從控制器發送的指令包如下所示:

0x20 0x7F 0x00 0x00

第一個位元組顯然是首位址—設定左側馬達轉速。下一個位元組是轉速。馬達驅動器透過PWM調製來改變馬達轉速,所以數位0xF7(十進位為127)對應50%占空比或50%轉速。第三個位元組是轉向—0x00表示正向,0x01表示逆向。最後一個位元組僅用來占位元—添加該位元組來使指令包始終為四位元組長。一旦指令被接收並被機器人成功執行,會返回以下指令包:

0x20

對於機器人上所運行的Arduino程式,其指令系統的實現都可以在我的GitHub中查看。通常我在這上面發佈實際代碼。不幸的是,代碼檔太大無法放在本文中。所以如果您對於代碼實現的細節有興趣的話,請查看實際代碼中的注釋。現在,我們來繼續看一些有趣的東西吧,那就是實現遠端操控的應用程式!