stlink 升级固件以后失败_物联网设备的固件远程升级方案以及软件代码

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

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

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

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

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

1)设备的远程访问

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

2)固件的分包以及传送

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

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

4)固件的有效性检验

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

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

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

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

5)bootloader程序

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

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

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

远程固件升级系统架构

78603c35b3c9fc4642eb37ebda424899.png

远程固件升级系统架构

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

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

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

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

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

一些设计要点

处理器的存储空间安排:

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

4KByte的空间用于bootloader程序。

52KByte用于存储用户数据;

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

固件的生成与分包

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

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

e52fd26a72e3bcfd959141d9df52d6c6.png

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);
5040b085828e9f311649a71802dd94d4.png
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值