手把手教你如何实现自动固件更新
——嵌入式篇
常席正 魏文龙
我们在上期 “手把手教你如何实现自动固件更新——服务器篇”那篇文章中介绍了通过云服
务器更新固件的方法,并着重介绍了服务器端的前期准备以及软件设计。这次小熊和大家分享下
嵌入式端的软件设计。相对于服务器端的软件设计,嵌入式软件设计需要更为严谨,因为固件升
级出错的后果会非常严重,因为这个功能一般使用在批量的设备上,而固件是控制系统的核心软
件,因此固件更新出错的话,会造成设备大面积瘫痪。所以严重性不言而喻。
本期我们来介绍一下客户端的具体实现过程,如图 1 所示,根据我们的自动固件更新协议,在
与更新服务器建立连接后,服务器会要求客户端进行一系列验证,嵌入式设备在通过验证后,更
新服务器会告知此嵌入式设备的最新固件信息,嵌入式设备根据这些信息下载并更新固件。
图 1 自动固件更新协议
“下载并更新固件”几个字囊括了我们所要执行的所有步骤。我们将按照以下步骤分别介绍。
1.下载准备--对 Flash 进行分区
为了实现‘下载并更新固件’我们先要做一些准备工作,我们把 MCU 的 Flash 分为三个区
分别为 BOOT 区,APP 区和 Backup 区,如图 2 所示
图 2 内存空间分配
区域
地址范围
大小
·作用
BOOT 区
0x08000000 ~ 0x08007FFF
32 KB 系统引导
APP 区
0x08008000 ~ 0x08023FFF
112 KB 主程序
Backup 区 0x08024000 ~ 0x0803FFFF
112 KB 存放更新的程序
了解了空间分配之后,我们再来看一下我们这个演示中各部分的主要功能:
1. BOOT 区:
清空 APP 区,为新 APP 写入做准备;
把暂存在 Backup 区的新版本程序拷贝到 APP 区;
2. APP 区
APP 区是应用程序运行区域,实现正常的网络连接,并更新固件。
配置网络参数;
在线固件升级;
每次上电都会从 Boot 区引导,若判断上层 APP 区载入程序是否成功,成功则直接从
Boot 区跳转到 APP 区,正常运行主程序。
3. Backup 区
从服务器接收并备份需要更新的新 App,也就是固件存储区域。
备注:由于Backup区的大小为112K,所以意味着APP的大小最大为 112K;
2. 程序流程设计
我们完成了对 Flash 的分区规划后,就要设计我们程序的流程,图 3 是程序执行的流程图。
图 3 嵌入式设备固件更新流程图
每次启动嵌入式设备,均从首地址开始执行程序:
启动进入 BOOT 区,若 BOOT 检测 APP 区的不为空,则跳转到 APP 区的首地址执行主程序;
APP 内的代码主要实现:
配置网络参数:配置 IP 地址,MAC 地址,建立网络连接。
远程更新固件:客户端向服务器发送固件版本查询报文。条件符合设定则进入步骤
当 APP 将新版本的固件下载完成后,进入步骤
跳转到 BOOT 区,执行更新操作;
BOOT 将 APP 区擦除,并将新 APP 从 Backup 区写入到 APP 区,写入完毕后擦除 Backup 区
的固件;
重启,重新执行程序。
我们将程序主要分为两个部分,分别为 BOOT 程序和 APP 程序。APP 程序即是我们的固件下载
程序对应流程图的步,BOOT 程序既是固件更新程序对应流程图的步:
3. APP 程序设计(固件验证与下载)
APP 代码程序初始化网络配置参数,实现嵌入式设备与服务器的正常连接,下载固件到
Backup 区,图 4 描述的为嵌入式设备与服务器的通信过程。
图 4 服务器-嵌入式设备通信过程示意图
固件服务器-嵌入式设备的通信过程大致分为三步:
1. 连接:嵌入式设备分配 socket 并连接到服务器。
2. 通信:连接建立后。服务器在接收到来自嵌入式设备的请求后发送应答。
3. 关闭:请求/应答完成后关闭连接。
我们 APP 区的函数主要做的就是下载固件,在程序里我们是通过 w5500_version()和
w5500_update()两个函数来实现的,w5500_version()用来验证当前的版本号与服务器上的版本
号是否相同,如果当前版本号小于服务器上的版本号就进行更新。
01 void w5500_version(void)
02 {
03 uint8 recv_buffer[2048];
04 uint8 version[10];
05 switch (getSn_SR(W5500_UPDATE)) {
06 case SOCK_ESTABLISHED:
07 if (getSn_IR(W5500_UPDATE) & Sn_IR_CON) {
08 setSn_IR(W5500_UPDATE, Sn_IR_CON);
09 }
10 send(W5500_UPDATE,(const uint8 *)postH,sizeof(postH));//发送
验证
11 Delay_ms(5000);
12 if ((len = getSn_RX_RSR(W5500_UPDATE)) > 0) {
13 len = recv(W5500_UPDATE, (uint8*)recv_buffer, len); //接
收数据
14 if (strstr((char*)recv_buffer,"\"error\"")) { //报文内包含
error,就结束函数
15 printf("upload error\r\n");
16 return;
17 }
18 printf("%s\r\n",recv_buffer);//打印服务器响应报文
19
mid((char*)recv_buffer,"\"version\":",",",(char*)version);//可以获取路
径
20 /*********读取版本号************/
21 if (strncmp(ver_num,version,7)<0) {
22 update_flag=1;
23
mid((char*)recv_buffer,"\"http://W5500.com/fw_update/upload/","\",",(
char*)bin_name);//可以获取路径
24 snprintf(post_msg,sizeof(post_msg),
25 "POST /fw_update/upload/%s HTTP/1.1\r\n"\
26 "Host:w5500.com\r\n"\
"Accept:image/gif,image/x-xbitmap,image/jpeg,image/pjpeg,*/*\r\n"\
27 "Pragma:no-cache\r\n"\
28 "Accept-Encoding: gzip,deflate\r\n"\
29 "Connection:keep-alive\r\n"\
30 "\r\n",bin_name);
31 printf("The version is %s\r\n",ver_num);
32 } else {
33 printf("The version is %s\r\n",version);
34 printf("The version is no need to update\r\n");
35 return;
36 }
37 }
38 close(W5500_UPDATE);
39 break;
40 case SOCK_CLOSE_WAIT:
41 break;
42 case SOCK_CLOSED:
43 socket(W5500_UPDATE,Sn_MR_TCP,30000,Sn_MR_ND);
44 break;
45 case SOCK_INIT:
46 connect(W5500_UPDATE, server_ip ,server_port);
47 break;
48 }
49 }
上述函数中 W5500 先与固件服务器建立 TCP Socket 连接,然后通过 send 函数发送固件
查询报文“postH”,该报文主要功能是把 W5500 的 MAC 地址发送给服务器。
01 char postH[]= {
02 "POST /fw_update/2.php HTTP1.1\r\n"\
03 "Host:w5500.com\r\n"\
04
"Accept:image/gif,image/x-xbitmap,image/jpeg,image/pjpeg,*/*\r\n"\
05 "User-Agent: Mozilla/4.0 (compatible;MSTE 5.5;Windows 98)\r\n"\
06 "Content-Length:21\r\n"\
07 "Content-Type:application/x-www-form-urlencoded\r\n"\
08 "Cache-Control:no-Cache\r\n"\
09 "Connection:close\r\n"\
10 "\r\n"\
11 "mac=00:08:DC:11:12:13"\
12 };
服务器端会根据传送的 MAC 地址检查设备是否注册以及是否有相关类型的固件。如果验证
不通过固件服务器就会向嵌入式设备发送 error 信息。如果验证通过服务器会向嵌入式设备回复
如图 5 所示的报文,报文中包含最新的版本号、下载路径、固件大小、文件 Hash 校验值等 4
个关键信息:{“version”:”X.X.X”,”path”:”…”,”size”:”XX”,”hash”:”…”},客户
端收到这 4 个关键信息后,先提取版本号与当前版本号比较如果比当前版本号新则置位更新标
志位并且拼接下载固件的报文。
图 5 查询报文
通过检查固件更新标志位,当发现此标志位置位后就运行 W5500_update()函数进行固件的
下载,首先我们要为嵌入式设备分配一个 Socket W5500_UPDATE,这个 Socket 初始状态为
SOCK_CLOSED,我们通过调用函数 socket(W5500_UPDATE, Sn_MR_TCP,30000,Sn_MR_ND),
打 开 Socket , Socket 状 态 改 变 为 SOCK_INIT, 打 开 Socket 后 调 用
connect(W5500_UPDATE,server_ip ,server_port)连接服务器,server_ip 和 server_port 分别为
服务器的 IP 地址和端口号,连接建立之后 Socket 状态变为 SOCK_ESTABLISHED,在此状态下
我们调用函数 Firmware_download()进行固件的下载,具体代码如下。
01 void Firmware_download(void)