※已刊登在“无线电”07月刊上手把手教你如何实现自动固件更新 —— 嵌入式篇 作者:常席正,魏文龙 我们在上期 “手把手教你如何实现自动固件更新——服务器篇”那篇文章中介绍了通过云服务器更新固件的方法,并着重介绍了服务器端的前期准备以及软件设计。这次小熊和大家分享下嵌入式端的软件设计。相对于服务器端的软件设计,嵌入式软件设计需要更为严谨,因为固件升级出错的后果会非常严重,因为这个功能一般使用在批量的设备上,而固件是控制系统的核心软件,因此固件更新出错的话,会造成设备大面积瘫痪。所以严重性不言而喻。 本期我们来介绍一下客户端的具体实现过程,如图1所示,根据我们的自动固件更新协议,在与更新服务器建立连接后,服务器会要求客户端进行一系列验证,嵌入式设备在通过验证后,更新服务器会告知此嵌入式设备的最新固件信息,嵌入式设备根据这些信息下载并更新固件 图1 自动固件更新协议 “下载并更新固件”几个字囊括了我们所要执行的所有步骤。我们将按照以下步骤分别介绍。 1.下载准备--对Flash进行分区 为了实现‘下载并更新固件’我们先要做一些准备工作,我们把MCU的Flash分为三个区分别为BOOT区,APP区和Backup区,如图2所示 图2 内存空间分配 了解了空间分配之后,我们再来看一下我们这个演示中各部分的主要功能: BOOT区: 清空APP区,为新APP写入做准备; 把暂存在Backup区的新版本程序拷贝到APP区; APP区 APP区是应用程序运行区域,实现正常的网络连接,并更新固件。 配置网络参数; 在线固件升级; 每次上电都会从Boot区引导,若判断上层APP区载入程序是否成功,成功则直接从Boot区跳转到APP区,正常运行主程序。 Backup区 从服务器接收并备份需要更新的新应用程序,也就是固件存储区域。 备注:由于备区的大小为112K,所以意味着APP的大小最大为112K ; 程序流程设计 我们完成了对闪存的分区规划后,就要设计我们程序的流程,图3是程序执行的流程图。 图3嵌入式设备固件更新流程图 每次启动嵌入式设备,均从首地址开始执行程序: (1)启动进入BOOT区,若BOOT检测APP区的不为空,则跳转到APP区的首地址执行主程序; (2) APP内的代码主要实现: 配置网络参数:配置IP地址,MAC地址,建立网络连接。 远程更新固件:客户端向服务器发送固件版本查询报文条件符合设定则进入步骤(3) (3)当APP将新版本的固件下载完成后,进入步骤(4) (4)跳转到BOOT区,执行更新操作; (5) BOOT将APP区擦除,并将新APP从备份区写入到APP区,写入完毕后擦除备份区的固件; (6)重启,重新执行程序。 我们将程序主要分为两个部分,分别为BOOT程序和APP程序.APP程序即是我们的固件下载程序对应流程图的(1)(2)(3)步,BOOT程序既是固件更新程序对应流程图的(4)(5)(6)步: APP 程序设计(固件验证与下载) APP代码程序初始化网络配置参数,实现嵌入式设备与服务器的正常连接,下载固件到备区,图4描述的为嵌入式设备与服务器的通信过程。 图4服务器-嵌入式设备通信过程示意图 固件服务器-嵌入式设备的通信过程大致分为三步: 连接:嵌入式设备分配socket并连接到服务器。 通信:连接建立后。服务器在接收到来自嵌入式设备的请求后发送应答。 关闭:请求/应答完成后关闭连接。 我们APP区的函数主要做的就是下载固件,在程序里我们是通过w5500_version()和w5500_update()两个函数来实现的,w5500_version()用来验证当前的版本号与服务器上的版本号是否相同,如果当前版本号小于服务器上的版本号就进行更新。 [sourcecode language="c"]void w5500_version(void) { uint8 recv_buffer[2048]; uint8 version[10]; switch (getSn_SR(W5500_UPDATE)) { case SOCK_ESTABLISHED: if (getSn_IR(W5500_UPDATE) & Sn_IR_CON) { setSn_IR(W5500_UPDATE, Sn_IR_CON); } send(W5500_UPDATE,(const uint8 *)postH,sizeof(postH));//发送验证 Delay_ms(5000); if ((len = getSn_RX_RSR(W5500_UPDATE)) > 0) { len = recv(W5500_UPDATE, (uint8*)recv_buffer, len); //接收数据 if (strstr((char*)recv_buffer,"\"error\"")) { //报文内包含error,就结束函数 printf("upload error\r\n"); return; } printf("%s\r\n",recv_buffer);//打印服务器响应报文 mid((char*)recv_buffer,"\"version\":",",",(char*)version);//可以获取路径 /*********读取版本号************/ if (strncmp(ver_num,version,7)<0) { update_flag=1; mid((char*)recv_buffer,"\"http://W5500.com/fw_update/upload/","\",",(char*)bin_name);//可以获取路径 snprintf(post_msg,sizeof(post_msg), "POST /fw_update/upload/%s HTTP/1.1\r\n"\ "Host:w5500.com\r\n"\ "Accept:image/gif,image/x-xbitmap,image/jpeg,image/pjpeg,*/*\r\n"\ "Pragma:no-cache\r\n"\ "Accept-Encoding: gzip,deflate\r\n"\ "Connection:keep-alive\r\n"\ "\r\n",bin_name); printf("The version is %s\r\n",ver_num); } else { printf("The version is %s\r\n",version); printf("The version is…