Ameba Arduino: [RTL8195] 透過網路 OTA 上傳程式碼到 Ameba

材料準備

  • Ameba x 1

OTA

OTA(Over-The-Air)一般而言指的是透過網路達到更新,Arduino IDE本身也提供OTA的功能,它的概念如下圖:
1(i) Arduino IDE透過mDNS詢問區域網路裡提供Arduino IDE OTA服務的裝置
(ii) Ameba回應Ameba有這個服務。這意謂著Ameba正在執行含有mDNS服務的程式碼,並且開啟特定的TCP port等待連結。
(iii) 使用者在Arduino IDE上開發程式,完成之後選network port
(iv) 點選上傳程式碼。此時Arduino IDE會透過TCP將OTA image送至Ameba端,Ameba再將這份image放至特定的位置裡,並設定好開機選項,使得下次開機時使用這份image。
整個流程裡,包含了三個部份:mDNS, TCP, 與OTA image的處理。mDNS的技術細節請參考mDNS的範例。TCP socket programming只用於傳image,並且被包含在OTA API裡面,所以這裡不會討論這部份。至於如何處理OTA image,需要了解Ameba Flash memory如何編排,以及開機流程,請參考下節。

Ameba的Flash memory layout

Ameba RTL8195A模組的flash memory大小為2MB, 即flash的位置範圍為0x00000000~00200000, 但是Ameba RTL8710模組的flash大小為1MB,所以Ameba的Flash memory layout以泛用性考量上會以1MB大小做為考量。
以下圖為例,Ameba的程式主要佔據3個區塊:
- Boot Image:即Bootloader,當Ameba開機後,會將Boot Image放置到Memory執行。它的工作主要是做一些初始化,其中一項工作就是決定當Bootloader執行完之後,要從哪裡執行,它會參考位於System data的OTA Address以及Recovery Pin,如果決定是Default Image 2,則它會將這塊Image放置到memory,並接著執行之。

- Default Image 2:這部份主要是使用者開發的程式碼,從0x0000B000開始,它的前16 bytes為Image檔頭,其中0x0000B008~0x0000B00F為Signature,這個Signature主要是用來辨識這個Image是否為正常的Image,或只是無意義的資料。Signature有兩種合法的值,用於區別這份Image是新的或舊的。

- OTA Image:這部份也是使用者開發的程式碼,預設從0x00080000開始,這個位置可以更改。OTA Image主要用於更新程式碼。它與Default Image 2的差異是擺放在Flash的位置不同,以及Signature不同。
除了程式之外,也有一些資料區塊:

- System Data:這個區塊裡放了系統相關的資訊,從0x00009000開始。其中與OTA相關的資料有2個:
1. OTA address:從0x00009000開始的4個bytes,它描述了OTA Image的位置,如果OTA address裡面是不合法的值(即0xFFFFFFFF),即使Flash memory裡面有合法的OTA image,也會因此找不到而認為沒有。
2. Recovery Pin:從0x00009008開始的4個bytes,它的用途是,當Default Image 2與OTA Image都是正常的Image時,其中Signature會顯示出其中一塊Image是新的,另一塊則是舊的,此時Recovery Pin會決定要執行哪一塊Image。如果Recovery Pin是不合法的值(即0xFFFFFFFF),則預設會執行新的Image
System Data的資料在使用DAP透過USB上傳程式碼時,也會一併刪除,意謂著OTA address也會被抹除,Ameba在這種情況下會認為沒有OTA image。

- Calibration Data:這個區塊放置週邊校正的參數,正常情況下請勿刪除這裡的資料。
2

Ameba的開機流程

Ameba的開機流程如下圖,說明如下:
3
我們分成幾種開機的情境討論:
(i) 未使用OTA,並使用DAP透過USB上傳程式碼:
這種是一般接USB線使用開發版上傳程式碼的情況。Bootloader會先檢查Default Image 2的signature,接著檢查OTA address,但會因為OTA address並未設定,因此認為沒有OTA image,因此選擇Default Image 2來執行
(ii) 已燒錄OTA image,已正確設定OTA addres,但未設定recovery pin:
這種情境通常發生在Ameba已透過OTA更新程式,此時Default Image 2的signature會被設定成舊的,而OTA image的signature會被設定成新的。
Bootloader在檢查完Default Image 2之後,在檢查OTA 時會發現flash memory裡面有合法的OTA image,接著檢查recovery pin時因為沒有設定recovery pin,因此會選擇新的Image執行,也就是執行OTA image。
(iii) 已燒錄OTA image,已正確設定OTA address,並且設定recovery pin:
這種情況一樣是Ameba已透過OTA更新程式,一樣Default image 2的signature是舊的,而OTA image的signature是新的。
Bootloader在檢查完Default Image 2之後,在檢查OTA 時會發現flash memory裡面有合法的OTA image,接著檢查recovery pin,雖然我們已設定recovery pin,但如果沒有將它接到HIGH (3.3V)的訊號,它一樣會執行新的Image,也就是OTA image。但如果使用者想要回復到初始狀態,它可以將recovery pin接到HIGH的訊號,這樣就會執行舊的Image,也就是Default Image 2

使用範例與測試

使用OTA需要更新DAP firmware至0.7版之後(不包含0.7版),預設出廠是0.7版,請參考底下的網址更新:http://www.amebaiot.com/change-dap-firmware/我們打開範例 “File” -> “Examples” -> “AmebaOTA” -> “ota_basic”
範例裡會使用到網路,所以我們設定欲連上的ssid與密碼
4接著有一些參數可以設定

  • MY_VERSION_NUMBER:在第一版裡,我們需要設定OTA address以及recovery pin,因為這一次透過USB上傳程式碼就是第一版,所以我們不需改它
  • OTA_PORT:Arduino IDE會經由mDNS找到Ameba,並且Ameba會告訴Arduino IDE讓它知道我們在TCP port 5000接收OTA image
  • RECOVERY_PIN:設定可以回復到初版的pin,這裡使用pin 18

5

改完之後我們要確認使用USB上傳程式碼,我們點選Tools -> Ports, 檢查是否使用Serial Port:
6

這邊要注意的地方是,不管是Serial port或是network port ,Arduino IDE在上傳code與顯示log使用的是同一個port,為了避免之後使用OTA時,無法從log uart看到log,我們改用其它Serial port terminal (Ex. Tera term or putty)來看log,(原本使用Serial Monitor)。
然後點選上傳,上傳完成之後,按下Reset,可以看到底下的Log。這份log有一些可以注意的地方:
1. 在 “===== Enter Image 1====” 與 “Enter Image 2 ====” 中間有個log “Flash Image 2:Addr 0xb000”, 代表這次是選擇位於0xb000的Default Image 2開機
2. “Enter Image 2 ====”之後有段log “This is version 1”, 這段log是我們在sketch裡面自己加的log,可以看到這是第一版
3. 連上AP之後,Ameba取得的IP是 “192.168.1.238”,它會啟動mDNS,並且等待client連進來
7

接著我們將我們的程式碼進版,將MY_VERSION_NUMBER改成2
8

接著在 “Tools”-> “Port”裡面會出現 “Network ports”的列表,其中一個是 “MyAmeba at 192.168.1.238 (Ameba RTL8195A)” ,其中 MyAmeba是我們為Ameba在啟用mDNS時設定的裝置名稱, “192.168.1.238”是Ameba連上AP之後取得的IP,代表Arduino IDE已經從網路取得Ameba的IP,後面的 Ameba RTL8195A是裝置的型號。
9

如果你的Arduino IDE沒有找到Ameba的network port,請確認
- 你的電腦與Ameba是否在同個區域網路裡?
- 重開Arduino IDE試試看, Arduino IDE會重新找尋mDNS服務
- 在Serial Monitor的log裡Ameba是否成功連上AP並且成功啟用mDNS

接著我們點選上傳程式碼,這次的程式碼就不會從USB上傳,而是經由網路TCP傳送,你會看到底下的log,會看到有client連進來,它的IP是 “192.168.1.226”,這個IP應該與你的電腦的IP是同一個。接著它會讀OTA的資訊並嘗試下載OTA
10

下載OTA成功之後,它會馬上重開機,會有底下的log
- 在 “===== Enter Image 1====” 與 “Enter Image 2 ====” 中間有個log “Flash Image 2:Addr 0x80000”, 代表這次是選擇位於0x80000的OTA Image開機
- “Enter Image 2 ====”之後有段log “This is version 2”, 這段log是我們在sketch裡面自己加的log,代表已經進到第2版
11

如果發現之後下載的OTA不是你想要的,或者想要回復到初版,可以將我們在sketch裡第一版設定的Recover pin(即pin 18)接到HIGH(3.3V),然後按下Reset
12

此時你應該會發現它又選擇位於0xb000的Default Image 2來執行。
要特別注意一點,如果之後不將Recover Pin接到HIGH,並此時按Reset,它還是會執行OTA image,Recover pin只用於開機時決定要執行哪一塊image,並非抹除特定的image。

所以整個包含OTA的開發流程大致如下:
13

程式碼說明

程式一開始一樣先連上WiFi
接著有一段code只有在第一版才會執行

#if MY_VERSION_NUMBER == 1
  OTA.setOtaAddress(DEFAULT_OTA_ADDRESS);
  OTA.setRecoverPin(RECOVER_PIN);
#endif

setOtaAddress會將OTA address寫到flash memory的system data裡面,目前default值是0x00080000,

OTA.setOtaAddress(DEFAULT_OTA_ADDRESS);

接著setRecoverPin會設定recover pin,這裡的RECOVERY_PIN是自己定義的值

OTA.setRecoverPin(RECOVER_PIN);

然後是每一版都需要執行的部份,是啟用mDNS的服務,這裡的OTA API已經將Arduino IDE使用的格式包裝起來,使用者只需要設定裝置的名稱,以及要開放OTA所使用的TCP port即可。這個API裡面的mDNS實作內容與AmebaMDNS的範例是一樣的。

OTA.beginArduinoMdnsService("MyAmeba", OTA_PORT);

最後是啟用OTA,程式碼會在這裡等待client連進捱,當OTA完成之後會重新啟動Ameba。或者是當它失敗時才會往下執行。

OTA.beginLocal(OTA_PORT)

程式碼說明(non block的範例)

在前面的範例ota_basic裡面,可以看到整份程式碼其實只為了做OTA,而沒有做其它事,在一般的使用情境會是,我們啟用OTA,並且做其它想做的事(Ex. 點LED燈、操作Servo……),這種需求可以參考另個範例 “File” -> “Examples” -> “AmebaOTA” -> “ota_non_block”
這個範例裡將上述的流程包裝成兩個Thread,程式碼的流程如下圖。
一開始的main thread會啟用wifi_service_thread,它負責連上WiFi,連上AP之後啟用另一個ota_thread,它負責OTA的部份。此時原本的main thread可以做自己想做的事。:
14底下描述程式碼的一些設定:
在main thread的setup()一開始我們呼叫os_thread_create API,第一個參數是Thread的function pointer,第二個參數是要帶進wifi_service_thread的參數,這裡我們沒有要帶參數,第三個參數是thread的優先權,因為這個thread我們有適當地交出執行權,所以我們將它的優先權設定成最高,第四個參數是thread使用的記憶體,如果要在這個thread裡面加更多功能就要考慮將這裡的記憶體加大。
os_thread_create(wifi_service_thread, NULL, OS_PRIORITY_REALTIME, 2048);
呼叫完之後,OS會在main thread與wifi_service_thread排程並執行
Main thread接著的loop()裡面只有每秒印一次log
wifi_service_thread則是檢查WiFi連線狀況,如果未連線則嘗試連上AP。連上AP之後,如果是第一次連上AP,則新增ota_thread。這裡只差在第一個參數是ota_thread。os_thread_create的回傳值是thread id,我們將它記下來避免重復新增ota_thread
ota_thread_id = os_thread_create(ota_thread, NULL, OS_PRIORITY_REALTIME, 2048);
在這之後,OS會在main thread, wifi_service_thread, 與ota_thread排程並執行
wifi_service_thread之後就是不斷檢查WiFi的情況,如果斷線就嘗試連線
ota_thread則是執行OTA的工作,如果OTA失敗就重試,如果OTA成功則會重新啟動,又回到一開始的地方。