跳到主要内容

潘多拉 RT-Thread HTTP 协议固件升级

实验概述

本实验基于 HTTP 客户端实现 RT-Thread HTTP OTA 固件下载器,通过 HTTP 协议从 HTTP 服务器下载升级固件到设备,在潘多拉 IoT 开发板上完成 OTA 升级。HTTP 客户端代码参考 WebClient 软件包 。

虽然 RT-Thread 提供了一些工具简化固件升级的开发工作,但这部分内容对初学者来说仍然具有挑战性。因此,在开始实验之前,建议先阅读《RT-Thread OTA 固件升级》了解一下 OTA 固件升级的相关知识。

特别提示:实验中用到的 bootloader 程序以 bin 文件的形式提供,并且只适用于该 STM32L4 设备平台,文件位于 /bin/bootloader.bin

硬件说明

本实验使用到潘多拉 IoT Board 的硬件资源如下:

  • UART1(Tx:PA9,Rx:PA10)
  • 片内 FLASH(512 KBytes)
  • 片外 Nor Flash(16 MBytes)
  • AP6181 WiFi 模块

分区说明

在本实验中,bootloader 程序和 app 程序存放在 STM32L4 MCU 的内部 Flash 中,download 下载区域存放在外部扩展的 Nor Flash 中。Flash 分区表如下:

分区名称存储位置分区大小起始地址结束地址说明
bootloader片内 Flash64K0x080000000x0800FFFFbootloader 程序存储区
app片内 Flash448K0x080100000x0807FFFFapp 应用程序存储区
easyflashNor Flash512K0x000000000x0007FFFFeasyflash 存储区
downloadNor Flash1M0x000800000x0017FFFFdownload 下载存储区
wifi_imageNor Flash512K0x001800000x001FFFFFWiFi 固件存储区
fontNor Flash7M0x002000000x008FFFFF字库分区
filesystemNor Flash7M0x009000000x00FFFFFF文件系统分区

分区表定义在 bootloader 程序中,如果需要修改分区表,则需要修改 bootloader 程序。

注意:目前暂不支持用户自定义 bootloader,如果有商用需求,请联系 RT-Thread 获取支持。

示例代码

参考《潘多拉 IoT Board 开发环境》创建工程,在 applications/main.c 中输入如下代码。

applications/main.c
#include <rtthread.h>
#include "wifi_config.h"
#include "board.h"

#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>

#define APP_VERSION "2.0.0"

/* 将中断向量表起始地址重新设置为 app 分区的起始地址 */
static int ota_app_vtor_reconfig(void)
{
#define NVIC_VTOR_MASK 0x3FFFFF80
#define RT_APP_PART_ADDR 0x08010000
/* 根据应用设置向量表 */
SCB->VTOR = RT_APP_PART_ADDR & NVIC_VTOR_MASK;

return 0;
}
INIT_BOARD_EXPORT(ota_app_vtor_reconfig);

int main(void)
{
wlan_autoconnect_init();

LOG_D("The current version of APP firmware is %s", APP_VERSION);

return 0;
}

除了 main.c 文件,需要 ota_http.c 文件,它实现了基于 HTTP 的 OTA 固件下载功能。另外还依赖 fal 和 webclient 软件包。ota_http.c 仅有三个 API 接口,介绍如下:

print_progress 函数

static void print_progress(size_t cur_size , size_t total_size);

该函数用于打印文件的下载进度。

http_ota_fw_download 函数

static int http_ota_fw_download(const char* uri);

该函数基于 webclient API 实现了从指定的 URL 下载文件的功能,并将下载的文件存储到 download 分区。

URL 格式示例:http://192.168.1.10:80/rt-thread.rbl。非 80 端口需要用户指定。如果使用了 TLS 加密连接,请使用 https://192.168.1.10:80/rt-thread.rbl

http_ota 函数

void http_ota(uint8_t argc, char **argv);
MSH_CMD_EXPORT(http_ota , OTA by http client: http_ota [url]);

HTTP OTA 入口函数,使用 MSH_CMD_EXPORT 函数将其导出为 http_ota 命令。http_ota 命令需要传入固件下载地址,例如:

http_ota http://192.168.1.10:80/rt-thread.rbl

完整代码:23_iot_ota_http

编译运行

编译工程

$ scons
...
LINK rtthread-stm32l4xx.elf
arm-none-eabi-objcopy -O binary rtthread-stm32l4xx.elf rt-thread.bin
arm-none-eabi-size rtthread-stm32l4xx.elf
text data bss dec hex filename
357752 2460 43680 403892 629b4 rtthread-stm32l4xx.elf
scons: done building targets.

将 bin 文件上传到 STM32(注意地址是 0x8010000)

st-flash write rt-thread.bin 0x8010000

另外还要将 bootloader 上传到地址 0x8000000

st-flash write bootloader.bin 0x8000000

打开串口终端,输出如下内容

[SFUD]Find a Winbond W25Q128 flash chip. Size is 16777216 bytes.
[SFUD]norflash0 flash device is initialize success.

RT-Thread Bootloader Starting...
[D/FAL] (fal_flash_init:61) Flash device | onchip_flash | addr: 0x08000000 | len: 0x00080000 | blk_size: 0x00000800 |initialized.
[D/FAL] (fal_flash_init:61) Flash device | nor_flash | addr: 0x00000000 | len: 0x01000000 | blk_size: 0x00001000 |initialized.
[I/FAL] ==================== FAL partition table ====================
[I/FAL] | name | flash_dev | offset | length |
[I/FAL] -------------------------------------------------------------
[I/FAL] | bootloader | onchip_flash | 0x00000000 | 0x00010000 |
[I/FAL] | app | onchip_flash | 0x00010000 | 0x00070000 |
[I/FAL] | easyflash | nor_flash | 0x00000000 | 0x00080000 |
[I/FAL] | download | nor_flash | 0x00080000 | 0x00100000 |
[I/FAL] | wifi_image | nor_flash | 0x00180000 | 0x00080000 |
[I/FAL] | font | nor_flash | 0x00200000 | 0x00700000 |
[I/FAL] | filesystem | nor_flash | 0x00900000 | 0x00700000 |
[I/FAL] =============================================================
[I/FAL] RT-Thread Flash Abstraction Layer (V0.2.0) initialize success.
[I/OTA] RT-Thread OTA package(V0.2.1) initialize success.
[I/OTA] Verify 'bootloader' partition(fw ver: 1.3, timestamp: 1545134551) success.
Find user application success.
The Bootloader will go to user application now.

\ | /
- RT - Thread Operating System
/ | \ 4.0.1 build Jan 8 2022
2006 - 2019 Copyright by rt-thread team
lwIP-2.0.2 initialized!
[I/sal.skt] Socket Abstraction Layer initialize success.
[SFUD] Find a Winbond flash chip. Size is 16777216 bytes.
[SFUD] w25q128 flash device is initialize success.
msh />[I/FAL] RT-Thread Flash Abstraction Layer (V0.2.0) initialize success.
[I/OTA] RT-Thread OTA package(V0.1.3) initialize success.
[I/OTA] Verify 'wifi_image' partition(fw ver: 1.0, timestamp: 1529386280) success.
[I/WLAN.dev] wlan init success
[I/WLAN.lwip] eth device init ok name:w0
[Flash] EasyFlash V3.2.1 is initialize success.
[Flash] You can get the latest version on https://github.com/armink/EasyFlash .
[D/main] The current version of APP firmware is 1.0.0
msh >

从以上日志里可以看到,前半部分是 bootloader 固件打印的日志,后半部分是 app 固件打印的日志,输出了 app 固件的版本号,并成功进入了 RT-Thread MSH 命令行。

如果看到串口打印的日志上对 download 分区校验失败,这是因为设备初次烧录固件,位于片外 Flash 的 download 分区内没有数据,所以会校验失败。这属于正常现象,下次使用 OTA 升级后就不会再出现该提示现象。

[E/OTA] (get_fw_hdr:149) Get firmware header occur CRC32(calc.crc: 7b93c5c8 != hdr.info_crc32: ffffffff) error on 'download' partition!
[E/OTA] (get_fw_hdr:149) Get firmware header occur CRC32(calc.crc: 7b93c5c8 != hdr.info_crc32: ffffffff) error on 'download' partition!
[E/OTA] (rt_ota_check_upgrade:464) Get OTA download partition firmware header failed!

现在 APP 固件的版本是 1.0.0,接下来我们就来尝试升级固件。

固件升级

HTTP 固件升级流程如下所示:

  1. 在存放升级固件的目录启动一个 HTTP 服务器;
  2. 在 MSH 中使用 http_ota 命令下载固件到 download 分区;
  3. bootloader 对 OTA 升级固件进行校验、解密和搬运(搬运到 app 分区);
  4. 程序从 bootloader 跳转到 app 分区执行新的固件。

打包 rbl 升级固件

首先,我们基于本实验示例程序来制作用于 HTTP 升级演示所用到的 app 固件。将 main.c 中的 APP_VERSION 版本号修改为“2.0.0”。

#define APP_VERSION "2.0.0"

重新编译固件

$ scons
...
LINK rtthread-stm32l4xx.elf
arm-none-eabi-objcopy -O binary rtthread-stm32l4xx.elf rt-thread.bin
arm-none-eabi-size rtthread-stm32l4xx.elf
text data bss dec hex filename
357752 2460 43680 403892 629b4 rtthread-stm32l4xx.elf
scons: done building targets.

由于编译出来的应用程序 rt-thread.bin 属于原始 app 固件,并不能直接用于 RT-Thread OTA 的升级固件,需要用户使用 RT-Thread OTA 固件打包器将其打包生成 .rbl 后缀名的固件,然后才能进行 OTA 升级。

使用 /tools/ota_packager 目录下的 OTA 打包工具制作 OTA 升级固件( .rbl 后缀名的文件)。注意,RT-Thread OTA 固件打包器只有 Windows 版本,因此你需要在 Windows 系统上使用它(也可以在 Linux 上使用 Wine 运行,参考 Ubuntu 安装 Wine 软件)。

用户可以根据需要,选择是否对固件进行加密和压缩,提供多种压缩算法和加密算法支持,基本操作步骤如下:

  • 选择待打包的固件(rt-thread.bin)
  • 选择生成固件的位置
  • 选择压缩算法(不压缩则留空)
  • 选择加密算法(不加密则留空)
  • 配置加密密钥(不加密则留空)
  • 配置加密 IV (不加密则留空)
  • 填写固件名称(对应分区名称,这里为 app)
  • 填写固件版本(填写 main.c 中的版本号 2.0.0)
  • 开始打包

通过以上步骤制作完成的 rt-thread.rbl 文件即可用于后续的升级文件。

注意事项:

  • 加密密钥加密 IV 必须与 bootloader 程序中的一致,否则无法正确加解密固件默认提供的 bootloader.bin 支持加密压缩,使用的加密密钥为 0123456789ABCDEF0123456789ABCDEF,使用的加密 IV 为 0123456789ABCDEF
  • 固件打包过程中有 固件名称 的填写,这里注意需要填入 Flash 分区表中对应分区的名称,不能有误。如果要升级 app 程序,则填写 app;如果升级 WiFi 固件,则填写 wifi_image 。
  • 固件版本号填写 main.c 中定义的版本号 2.0.0。

HTTP 升级固件

要完成固件升级,我们还需要一个 HTTP 服务器,可以使用 Python 在 rt-thread.rbl 固件所在目录启动一个简单的 HTTP 服务器。

# Python 3.x
python -m http.server
# Python 2.x
python -m SimpleHTTPServer

默认情况下,这将在本地 Web 服务器上的端口 8000 上运行目录的内容。也可以指定端口,例如指定 7800 端口:

# Python 3.x
python -m http.server 7800
# Python 2.x
python -m SimpleHTTPServer 7800

这样你就可以在 MSH 中使用本地 Web 服务器的 URL 地址下载固件:

http_ota http://192.168.1.10:8000/rt-thread.rbl

为了方便测试,你也可以使用我们服务器上的资源链接:

http_ota http://static.getiot.tech/rt-thread.rbl

输入命令后,会擦除 download 分区,下载升级固件,显示下载进度条:

msh />http_ota http://static.getiot.tech/rt-thread.rbl  
[I/http_ota] Start erase flash (download) partition!
[I/http_ota] Erase flash (download) partition success!
[I/http_ota] Download: [====================================================================================================] 100%

HTTP OTA 下载固件完成后,会自动重启,并在串口终端打印如下 log:

[I/http_ota] Download firmware to flash success.
[I/http_ota] System now will restart...

设备重启后,bootloader 会对升级固件进行合法性和完整性校验,验证成功后将升级固件从 download 分区搬运到目标分区(这里是 app 分区)。

升级成功后设备状态如下所示:

[SFUD]Find a Winbond W25Q128 flash chip. Size is 16777216 bytes.
[SFUD]norflash0 flash device is initialize success.

RT-Thread Bootloader Starting...
[D/FAL] (fal_flash_init:61) Flash device | onchip_flash | addr: 0x08000000 | len: 0x00080000 | blk_size: 0x00000800 |initialized.
[D/FAL] (fal_flash_init:61) Flash device | nor_flash | addr: 0x00000000 | len: 0x01000000 | blk_size: 0x00001000 |initialized.
[I/FAL] ==================== FAL partition table ====================
[I/FAL] | name | flash_dev | offset | length |
[I/FAL] -------------------------------------------------------------
[I/FAL] | bootloader | onchip_flash | 0x00000000 | 0x00010000 |
[I/FAL] | app | onchip_flash | 0x00010000 | 0x00070000 |
[I/FAL] | easyflash | nor_flash | 0x00000000 | 0x00080000 |
[I/FAL] | download | nor_flash | 0x00080000 | 0x00100000 |
[I/FAL] | wifi_image | nor_flash | 0x00180000 | 0x00080000 |
[I/FAL] | font | nor_flash | 0x00200000 | 0x00700000 |
[I/FAL] | filesystem | nor_flash | 0x00900000 | 0x00700000 |
[I/FAL] =============================================================
[I/FAL] RT-Thread Flash Abstraction Layer (V0.2.0) initialize success.
[I/OTA] RT-Thread OTA package(V0.2.1) initialize success.
[I/OTA] Verify 'bootloader' partition(fw ver: 1.3, timestamp: 1545134551) success.
[I/OTA] Verify 'download' partition(fw ver: 2.0.0, timestamp: 1641624017) success.
[I/OTA] OTA firmware(app) upgrade(2.0.0->2.0.0) startup.
[I/OTA] The partition 'app' is erasing.
[I/OTA] The partition 'app' erase success.
[I/OTA] OTA Write: [====================================================================================================] 100%
[I/OTA] Verify 'app' partition(fw ver: 2.0.0, timestamp: 1641624017) success.
Find user application success.
The Bootloader will go to user application now.

\ | /
- RT - Thread Operating System
/ | \ 4.0.1 build Jan 8 2022
2006 - 2019 Copyright by rt-thread team
lwIP-2.0.2 initialized!
[I/sal.skt] Socket Abstraction Layer initialize success.
[SFUD] Find a Winbond flash chip. Size is 16777216 bytes.
[SFUD] w25q128 flash device is initialize success.
msh />[I/FAL] RT-Thread Flash Abstraction Layer (V0.2.0) initialize success.
[I/OTA] RT-Thread OTA package(V0.1.3) initialize success.
[I/OTA] Verify 'wifi_image' partition(fw ver: 1.0, timestamp: 1529386280) success.
[I/WLAN.dev] wlan init success
[I/WLAN.lwip] eth device init ok name:w0
[Flash] EasyFlash V3.2.1 is initialize success.
[Flash] You can get the latest version on https://github.com/armink/EasyFlash .
[D/main] The current version of APP firmware is 2.0.0

设备升级完成后会自动运行新的固件,可以看到,app 固件已经从 1.0.0 版本升级到了 2.0.0 版本。2.0.0 版本的固件同样是支持 HTTP OTA 下载功能的,因此可以一直使用 HTTP 进行 OTA 升级。用户如何需要增加自己的业务代码,可以基于该例程进行修改。

思考总结

通过本次实验,我们学习了如何通过 RT-Thread OTA 工具在潘多拉 IoT Board 上通过 HTTP 网络协议完成固件升级。

在使用 RT-Thread OTA 工具时,有以下注意事项:

  • 必须使用 .rbl 格式的升级固件。
  • 打包 OTA 升级固件时,分区名字必须与分区表中的名字相同(升级 app 固件对应 app 分区)。
  • 搭建本地 HTTP 服务器时如果出现异常,请检查本地网络配置和防火墙设置等。
  • app 固件必须从 0x08010000 地址开始链接,否则应用 bootloader 会跳转到 app 失败。

app 固件存储在 app 分区内,起始地址为 0x08010000,如果用户需要升级其他 app 程序,请确保编译器从 0x08010000 地址链接 app 固件。

OTA 的关键在于中断向量的重新设置,使用 bootloader 的时候,app 固件从 0x08010000 地址开始链接,因此需要将中断向量重新设置到 0x08010000 地址,程序如下所示:

/* 将中断向量表起始地址重新设置为 app 分区的起始地址 */
static int ota_app_vtor_reconfig(void)
{
#define NVIC_VTOR_MASK 0x3FFFFF80
#define RT_APP_PART_ADDR 0x08010000
/* 根据应用设置向量表 */
SCB->VTOR = RT_APP_PART_ADDR & NVIC_VTOR_MASK;

return 0;
}
INIT_BOARD_EXPORT(ota_app_vtor_reconfig);

通过 HTTP 进行 OTA 固件升级的流程和 Ymodem 类似,不同点在于固件下载所用的通道和协议不同,Bootloader 启动流程是一样的。