物联网设备的固件远程升级方案以及软件代码
- 作者:佚名
- 发表时间: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);
- 物联网卡内部结构(物联网(NB-IoT)是什么) 2024-12-12 08:00:05
- 物联网卡几天激活好使(物联网卡要怎么激活) 2024-12-12 18:00:10
- 物联网卡分配原则是(网关是什么) 2024-12-13 18:00:08
- 物联网卡到哪去办理套餐(物联网卡怎么开通) 2024-12-14 00:00:09
- 物联网卡加工视频(物联网卡能看视频电影连续剧吗) 2024-12-18 12:00:14
- 物联网卡包年可以退吗(网上卖的流量卡可不可靠) 2024-12-20 04:00:05
- 物联网卡厚度问题有哪些(物联网卡常见的4大问题,最后一点很重要) 2024-12-21 12:00:08
- 物联网卡发货现场视频(物联网卡能看视频电影连续剧吗) 2024-12-22 04:00:04
- 物联网卡可以个人实名吗(物联卡实名认证有影响吗) 2024-12-23 00:00:08
- 物联网卡可以做什么(网上的零月租流量卡是物联卡吗 使用物联卡有什么风险) 2024-12-23 00:00:12
- 物联网卡分离(物联网卡卡机分离后无服务) 2024-12-13 16:00:06
- 物联网卡分颜色(物联网卡到底是什么卡) 2024-12-13 20:00:08
- 物联网卡到哪儿投诉商家(移动物联网卡怎么投诉) 2024-12-14 00:00:05
- 物联网卡刷视频怎么用(物联网卡能看视频电影连续剧吗) 2024-12-14 08:00:04
- 物联网卡办哪个好点实惠(物联网卡那种套餐比较合适,物联网卡怎么办理) 2024-12-14 12:00:09
- 物联网卡办理入口官网(物联网卡怎么开通) 2024-12-14 16:00:05
- 物联网卡单独断网3(手机老是断网) 2024-12-20 16:00:05
- 物联网卡去哪批发(在哪能买到正规的物联网卡啊) 2024-12-21 16:00:05
- 物联网卡发快递怎么包装(物联卡怎么查询号码) 2024-12-22 00:00:09
- 物联网卡只能频段用吗(物联网卡可以买来用在手机吗) 2024-12-22 20:00:04