智宇物联 专注于提供高稳定、高速率的三网物联网卡

物联网设备的固件远程升级方案以及软件代码

  • 作者:佚名
  • 发表时间:2022年10月13日上午2:30
  • 来源:未知

作为通用的物联网设备,由于用户需求各不相同,不少用户有一些个性化的定制要求;

设备所对接的传感器协议也多种多样,比如Modbus读写参数的数据类型,某些物理量需要特殊的数据类型;

甚至可能还存在意想不到的bug。

因此,远程升级的功能对于设备来说必不可少。

远程固件升级需要解决以下问题:

1)设备的远程访问

当设备被安装于局域网内部时,位于远程的固件升级软件工具无法穿透路由器访问设备。

2)固件的分包以及传送

由于设备的处理器资源有限,无法移植开源的http、FTP等协议栈,无法通过http、FTP等协议从服务器上下载固件,而需要自己实现代码,采用TCP协议进行固件包的发送;

而且对于几十k甚至上百k的固件,需要将固件拆成几百个字节的数据包,逐一发给设备;

4)固件的有效性检验

固件在传输过程中,难免会出现错误。

比如WiFi模块,或者是ethernet模块将数据通过uart转发给处理器时,如果有干扰、数据可能被破坏;

或者是处理器太忙,来不及接收数据,导致固件包丢失数据;

如果不对固件进行有效性检验,将被破坏的固件升级进控制器,会导致设备变砖而无法使用;

5)bootloader程序

bootloader程序需要下载固件的有效性检验,程序的擦除、固件数据从备份区到程序区的搬移。

6)处理器的固件升级软件实现

软件需要实现数据包接收,固件有效性验证、存储,数据应答等。

远程固件升级系统架构

物联网设备的固件远程升级方案以及软件代码

远程固件升级系统架构

设备作为TCP客户端连接到云服务器上的TCP服务端,定时发送心跳,维护连接,从而实现TCP的长链接。

在PC电脑上开发远程升级工具,作为TCP客户端与云服务器上的TCP服务器建立连接;

当需要远程升级时,通过PC工具向云服务器发送消息,所发消息中包括了远程设备的设备编号,以及PC工具的设备编号;

服务器收到消息之后,根据消息中的目标设备编号,从其维护的长链接中找到与该编号对应的链接,通过该链接向设备转发该消息;

设备收到消息之后,对消息中的固件包进行有效性验证,如果有效,则写入到固件暂存区,并回复成功,否则回复失败。

一些设计要点

处理器的存储空间安排:

以STM32F103RCT6为例,该处理器有256KByte的FLASH空间;

4KByte的空间用于bootloader程序。

52KByte用于存储用户数据;

剩余的FLASH空间一半作为程序存储区,一半作为固件暂存区,程序必须小于100KByte。

固件的生成与分包

在Keil中,将程序的memory的起始地址设置为0x8001000,大小设置为0x19000。

同时,设置运行fromelf.exe,使得编译程序时自动生成用于固件升级的bin文件。

物联网设备的固件远程升级方案以及软件代码

Keil设置

通过delphi将生成出来的bin文件读入,并采用下述代码进行发包,加上协议头以及CRC32的校验值。

pkgs := stream.Size div perpage;
  rem := stream.Size mod perpage;
  addr := 0;
  if(rem > 0) then
  begin
    pkgs := pkgs + 1;
  end;
  strcrc := '';
  for i:= 0 to (pkgs - 1) do
  begin
       curlen := perpage;
       if((i + 1) * perpage > bytecount) then
       begin
          curlen := bytecount - (i * perpage);
       end;
       payload :=  inttohex(i* perpage, 8)+inttohex(curlen, 8);      //
       stream.Position := i * perpage;
       k := 0;
       tmpstr := '';
       for j:= 0 to (curlen - 1) do
       begin
        stream.Read(val, 1);
        if((k and 1) = 0)  then
        begin
          tmpstr := inttohex(val, 2);
        end
        else
        begin
          tmpstr := inttohex(val, 2) + tmpstr;
        end;
        inc(k);
        if((k and 1) = 0)  then
        begin
          payload := payload + tmpstr;
        end;
       end;
       tempcrc :=  crc(payload);
       payload := tempcrc +payload;
       payload := inttohex((2 + 4 + 4 + perpage) * i, 8) + payload;
       payload := '01'+payload;
       strcrc := strcrc + tempcrc;
       payload := payload + crc(payload);;
       str := header+'&msgid='+inttostr(msgid)+'&length='+inttostr(1 + 2 + 4 + 4 + 4 + 2+ curlen)+'&cmd='+payload;
       inc(msgid);
       strcommands.Add(str);
  end;

固件的有效性验证以及升级的可靠性保证

整个固件包根据处理器的资源拆分为500个byte一个数据包;

每一个数据包都计算CRC32的数值并加入数据包中;

所以CRC32的数值再计算一遍CRC32数值并放入开始升级的命令之中;

控制器收到固件之后,重新计算500个byte的CRC32的计算值并与收到的CRC32值进行比对,只有两者相等时才存入暂存区;

当收到所有数据包时,从暂存区中按包读出固件,计算CRC32值与同时存储的CRC32值比对,同时计算所有CRC32数值的CRC32数值,与开始升级的命令中所携带的数值比对。

只有所有CRC32的数值都相同的情况下,应用程序才将升级程序的标志位写入到FLASH中,并重启处理器进入bootloader程序。

bootloader程序从FLASH中读取到升级程序的标志,则从暂存区中按包读取数据,进行同样的CRC32的验证过程,确保无误的情况下,将暂存区中的固件搬移到程序区。

全部程序搬移完之后,再逐个字节比较暂存区以及程序区的内容。

比对时,再检验CRC32是否正确。

只有CRC32数值正确并且与程序区的数据都相等的情况下,才清空升级程序的标志,完成升级过程。

升级程序的步骤及代码

步骤1:PC工具发送清空暂存区的命令,将暂存区的内存都擦写成0xff。

步骤2:PC工具发送写固件数据包的命令,处理器收到之后,进行有效性验证,并写入暂存区,重复该过程,完成整个固件的发送。

步骤3:PC工具发送开始升级的命令,处理器收到之后,再进行一次有效性验证,并重启,进入bootloader程序。

步骤4:bootloader程序进行有效性验证之后,将暂存区的固件搬移至程序区,完成升级;

代码如下:

U32 data, value, dataB;
U8 res = FALSE;
U8 flag;
U16 pointer;
U16 len;

U8 *ins = lins + AP_ID_HEX_BYTE;

if(fnCRC16_Check(lins, llen)){
  len = 0;
  if(llen >= AP_ID_HEX_BYTE){
    len = llen - AP_ID_HEX_BYTE;
  }
  if(inscode == FM_OPERATECODE_START){
    if(fmups.m_uchState == FM_STATE_IDLE){
      if(len == (1 + 4 + FM_STARTCODE_LEN + 2 )){

        if(fnFM_IsStartStopValid(&ins[1 + 4])){
          data = (U32)ins[1] << 24;	
          data |= (U32)ins[2] << 16;
          data |= (U32)ins[3] << 8;
          data |= (U32)ins[4];
          if(data < FLASH_ROM_SIZE_FIRMWARE){
            fmups.m_uchState = FM_STATE_INIT;
            fmups.m_ulLen = data;
            fmups.m_uiTimer = FM_STATE_TIME;
            res = TRUE;
          }
        }
      }
    }
  }
  else if(inscode == FM_OPERATECODE_DOWNLOAD){

    if(fmups.m_uchState == FM_STATE_DOWNLOAD){

      if((len > (1 + 4 + 2 + 4 + 4 + 2))
         && (len <= (1 + 4 + 2 + 4 + 4 + 2 + FM_DOWNLOAD_EVERYMSG))){

        data = (U32)ins[1] << 24;	
        data |= (U32)ins[2] << 16;
        data |= (U32)ins[3] << 8;
        data |= (U32)ins[4];

        value = (U32)ins[7] << 24;	
        value |= (U32)ins[8] << 16;
        value |= (U32)ins[9] << 8;
        value |= (U32)ins[10];

        dataB = (U32)ins[11] << 24;	
        dataB |= (U32)ins[12] << 16;
        dataB |= (U32)ins[13] << 8;
        dataB |= (U32)ins[14];

        flag = TRUE;
        if(value != fmups.m_uchPointer){
          flag = FALSE;
          if((value + dataB) == fmups.m_uchPointer){
            res = TRUE;
          }
        }
        if(data >= (FLASH_ROM_SIZE_FIRMWARE - (FM_DOWNLOAD_EVERYMSG + 4+ 4 + 2))){
          flag = FALSE;
        }
        if(dataB > FM_DOWNLOAD_EVERYMSG){
          flag = FALSE;
        }
        if((fmups.m_uchPointer + dataB) > fmups.m_ulLen){
          flag = FALSE;
        }
        if(flag){
          if(value == fmups.m_uchPointer){
            res = fnFL_WriteBytesAndCheck(data + FLASH_ROM_ADDR_FIRMWARE, (2 + 4 + 4 + dataB), &ins[5]);
            if(res){
              fmups.m_uchPointer += dataB;
            }
          }else{
            res = TRUE;
          }
        }
      }
    }
  }
  else if(inscode == FM_OPERATECODE_STOP){
    if(len == (1 + 2 + 2 + FM_STARTCODE_LEN)){
      if(fnFM_IsStartStopValid(&ins[3])){
        if(fmups.m_uchState == FM_STATE_COMPLETE){
          if(fnFM_Check(ins[1], ins[2])){
            res = fnFM_ProCon(ins[1], ins[2]);
            if(res){
              fmups.m_uchReStartTimer = 10;
            }
          }

        }
      }
    }
  }
  else if(inscode == FM_OPERATECODE_RESET){
    if(len == (1 + 2 + FM_STARTCODE_LEN)){
      if(fnFM_IsStartStopValid(&ins[1])){
        res = TRUE;
        fmups.m_uchState = FM_STATE_IDLE;
        fmups.m_uiTimer = 0;
      }
    }
  }
}
ack[0] = inscode | 0x80;
ack[1] = res;
ack[2] = 0 ;
pointer = 3;
return(pointer);
物联网设备的固件远程升级方案以及软件代码

最新资讯
最热资讯