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版,请参考底下的网址更新:https://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)

程式码说明(nonblock的范例)

在前面的范例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成功则会重新启动,又回到一开始的地方。