前言

  1.BootLoader程序,升级简要流程图

  

  2.其实主要的就是把程序文件写入环形队列,然后环形队列取出来数据写入Flash

  3.用户程序,简要流程图

    

下面的读一下,有个印象就可以:

  说白了就是BootLoader里面通过http远程下载完程序以后, 设置更新状态是 0x01  然后重启

  重启以后还是会先执行BootLoader,然后BootLoader判断更新状态是 0x01 那么就设置更新状态是 0xFF

  然后就会执行 用户程序,用户程序判断更新状态是 0xFF 就切换下版本号.设置升级状态为0,升级完成

  假设咱更新的用户程序有问题,那么就会执行用户程序失败,然后导致重启执行了BootLoader

  BootLoader一判断还是0xFF,就说明没有正确执行用户程序,就设置更新状态是 0xEE,同时设置下切换执行文件

  然后执行另一份没有问题的用户程序.

关于乒乓升级

1.简要

  每次更新的时候 用户程序1和用户程序2来回的切换写入运行

  BootLoader程序主要做的工作就是如果上次运行的用户程序1

  则获取第二份用户程序,然后把程序文件写入用户程序2地址

  如果上次运行的用户程序2

  则获取第一份用户程序,然后把程序文件写入用户程序1地址

2.关于源码中的两份用户程序

首先,两份用户程序除了设置的中断偏移不一样以外其他完全一样!

不同的偏移值使其能运行在不同的Flash地址上

STM32F10xTemplate 设置的偏移值只能运行在 ↓

STM32F10xTemplate - 副本  设置的偏移值只能运行在 ↓

3.升级切换文件流程

首先,咱们需要每次要升级的时候需要把运行在不同地址,

功能完全一样的两份用户程序的bin文件放到云端,

(编译 STM32F10xTemplate 产生的bin文件

和编译 STM32F10xTemplate - 副本 产生的bin文件)

以供单片机下载.

假设当前单片机是在用户程序1 地址加载运行的程序

那么单片机下次升级就需要下载 能在用户程序2 地址运行的程序文件

然后写到 用户程序2 地址,然后单片机就加载用户程序2 地址上的程序运行

下次再升级就是下载 能在用户程序1 地址运行的程序文件

这里说一下为什么需要同时把两份文件放上去.

大家可能会想,假设我单片机一开始是在用户程序1 地址加载运行的程序

那么我单片机升级肯定是下载 能在用户程序2 地址运行的程序文件

我直接把  能在用户程序2 地址运行的一个程序文件放到云端不就可以了

我单片机如果接着再升级,肯定是下载 能在用户程序1 地址运行的程序文件

我直接把  能在用户程序1 地址运行的一个程序文件放到云端不就可以了

干嘛非要把 能在两个地址运行的执行功能一样的程序放上去呢??

我只问一句:

大家在看到软件更新的时候,大家是否真的去执行更新???

是不是有时候都过去好几个版本了才选择升级??

大白话就是:

假设有两个用户买了咱的产品

假设一开始都是在用户程序1 地址加载运行的程序

然后第一次升级的时候,其中一个用户升级了

另一个用户没有执行升级.

假设咱又换了下版本,需要再升级!

现在的情况就变为:

第一个用户再次升级的时候,需要下载能在用户程序1 地址运行的程序文件

第二个用户由于第一次没有升级,

再次更新的时候需要下载能在用户程序2 地址运行的程序文件

所以......

大家一定是要把在两个地址运行的执行功能一样的程序放上去!

BootLoader程序说明

  首先需要明确:

  总共把Flash分为了下面四部分

  

  每次更新的时候 用户程序1和用户程序2来回的切换写入运行

  BootLoader程序主要做的工作就是如果上次运行的用户程序1

  则获取第二份用户程序,然后把程序文件写入用户程序2地址

  如果上次运行的用户程序2

  则获取第一份用户程序,然后把程序文件写入用户程序1地址

main函数初始化里面

  提醒:为了便于叙述,升级程序都写在了main里面,后面章节为了便于移植使用,全部封装成了单个函数!

  一,初始化变量

    

  二,获取存储的云端版本,获取当前设备版本

    获取更新状态:如果是0x01 设置更新状态为:0xFF

    如果更新状态是0xFF  设置更新状态为:0xEE  同时切换执行程序

    

  三,获取当前应该运行哪一份用户程序,然后设置运行和更新的Flash起始地址

    

配网

  对于一个新的Wi-Fi模块而言,没有连接路由器的状态下,无法实现远程http访问

  所以在BootLoader里面有个配网程序

  一,BootLoader里面配网完成以后会初始化版本号,然后设置升级标志

    

配网重启以后,便能连接上Web服务器

  

  

  

  连接TCP服务器(Web服务器)是用的自动重连的透传模式

然后定时http询问程序版本

  定时询问云端的版本

  

  

  就是访问的云端的下面文件

  

询问到版本信息以后处理

  一,处理获取的版本号

  因为设置的是串口TCP透传,所以获取的数据直接在串口里面获取

  

  二,获取升级文件的校验和

  校验和累加方式是每一个字节进行累加,取低八位

  

  三,如果获取的版本号和校验和都是可以的

    1.擦除对应的Flash地址

    2.把云端版本存储起来

    3.发送获取相应的程序文件http指令

    4.然后设置 IAPStructValue.PutDataFlage = 1;  (允许把数据写入环形队列)

    提示:发送完获取文件指令以后,Web服务器便会下发程序文件了

    

到串口中断里面看下接收程序文件

  

  if(IAPStructValue.PutDataFlage && (IAPStructValue.PutDataFlage^IAPStructValue.Overflow))

  IAPStructValue.PutDataFlage: (允许把程序写入环形队列)

  IAPStructValue.Overflow:如果溢出过,就不再往里面写了

  PutData(&rb_tIAP,NULL,&Res,1) : 把接收的数据写入环形队列

  提示:主循环只要判断环形队列里面有数据,就把数据写入Flash

  提示:主循环只要判断环形队列里面有数据,就把数据写入Flash

  提示:主循环只要判断环形队列里面有数据,就把数据写入Flash

  具体里面的判断后面会说明!

  

  提醒:由于http会返回数据头,需要去掉数据头

  

//解析http数据-------------------------------Start
//HTTP/1.1 200 OK
if(!HttpHeadOK && IAPStructValue.PutDataFlage)
{
if(Res=='H' && HttpHeadCnt==)HttpHeadCnt++;
else if(Res=='T' && HttpHeadCnt==)HttpHeadCnt++;
else if(Res=='T' && HttpHeadCnt==)HttpHeadCnt++;
else if(Res=='P' && HttpHeadCnt==)HttpHeadCnt++;
else if(Res=='/' && HttpHeadCnt==)HttpHeadCnt++;
else if(Res=='' && HttpHeadCnt==)HttpHeadCnt++;
else if(Res=='.' && HttpHeadCnt==)HttpHeadCnt++;
else if(Res=='' && HttpHeadCnt==)HttpHeadCnt++;
else if(Res==' ' && HttpHeadCnt==)HttpHeadCnt++;
else if(Res=='' && HttpHeadCnt==)HttpHeadCnt++;
else if(Res=='' && HttpHeadCnt==)HttpHeadCnt++;
else if(Res=='' && HttpHeadCnt==)HttpHeadCnt++;
else if(Res==' ' && HttpHeadCnt==)HttpHeadCnt++;
else if(Res=='O' && HttpHeadCnt==)HttpHeadCnt++;
else if(Res=='K' && HttpHeadCnt==){HttpHeadOK = ;HttpHeadCnt=;HttpDataLength=;}
else
{
HttpHeadCnt=;
}
} #ifdef UserContentLength
//Content-Length: XXXXXXXX
if(HttpHeadOK && !HttpDataLengthOK)//获取http发过来的数据个数
{
if(Res=='-' && HttpHeadCnt==) HttpHeadCnt++;
else if(Res=='L' && HttpHeadCnt==)HttpHeadCnt++;
else if(Res=='e' && HttpHeadCnt==)HttpHeadCnt++;
else if(Res=='n' && HttpHeadCnt==)HttpHeadCnt++;
else if(Res=='g' && HttpHeadCnt==)HttpHeadCnt++;
else if(Res=='t' && HttpHeadCnt==)HttpHeadCnt++;
else if(Res=='h' && HttpHeadCnt==)HttpHeadCnt++;
else if(Res==':' && HttpHeadCnt==)HttpHeadCnt++;
else if(Res==' ' && HttpHeadCnt==)HttpHeadCnt++;
else if(HttpHeadCnt>= && HttpHeadCnt<= )//最大99999999个字节. 16:99999999 17:999999999 18:9999999999
{
if(Res!=0x0D)
{
HttpDataLength = HttpDataLength* + Res - '';
HttpHeadCnt++;
}
else
{
HttpDataLengthOK = ;
HttpHeadCnt = ;
}
}
else
{
HttpHeadCnt = ;
}
} if(HttpHeadOK && HttpDataLengthOK && HttpDataLength && !HttpHeadEndOK)
#else
if(HttpHeadOK && !HttpHeadEndOK)
#endif
{//0D 0A 0D 0A
if(Res==0x0D && HttpHeadCnt==)HttpHeadCnt++;
else if(Res==0x0A && HttpHeadCnt==)HttpHeadCnt++;
else if(Res==0x0D && HttpHeadCnt==)HttpHeadCnt++;
else if(Res==0x0A && HttpHeadCnt==){HttpHeadEndOK = ;}
else HttpHeadCnt = ;
} if(HttpHeadEndOK == )//http数据的head已经过去,后面的是真实数据
{
HttpHeadEndOK=;
HttpHeadCnt = ;
HttpDataLengthOK=; HttpDataStartFlage=; Usart1IdleTime = ;//GPRS判断空闲时间需要3S左右,因为GPRS有延迟
}
//解析http数据-------------------------------end

把程序文件写入Flash

  只要环形队列里面有数据,就提取数据写入Flash

  

  1: 不用多说

  2:http服务器的头信息会返回程序文件的大小,程序中获取了这个数据

  对比下写入的和http实际返回的数据个数是不是一致

  

  获取程序文件大小的程序如下:

  

  不过鉴于有的http服务器不会返回数据大小,所以

  如果用户 #define UserContentLength 才会进行判断

  3:计算校验和

  4:把数据写入Flash

判断接收完数据

  

  如果判断 IAPStructValue.ReadDataEndFlage == 1

  才认为是接收完了数据

  由于我是串口接收数据,所以只要是判断串口出现了空闲就说明接收完了数据

  

接收完了更新程序以后检验下整个过程

            if(IAPStructValue.ReadDataEndFlage)//接收完更新程序
{
IAPStructValue.PutDataFlage = ;//停止向环形队列写入数据
IAPStructValue.UpdateFlage = ; //更新标志清零
IAPStructValue.ReadDataEndFlage = ;//清零接收完更新程序标志 #ifdef UserContentLength //自己的Web服务器返回 Length: XXXXXXXX (本次的数据长度)
//写入的数据个数和http实际返回的数据个数不相等
if( (IAPStructValue.UpdateAddressCnt - IAPStructValue.UpdateAddress) != HttpDataLength)
{
IAPSetUpdateStatus(UpdateStatus_MissingData);//数据错误(数据丢失)
}
else if(IAPStructValue.FlashWriteErrFlage == )//Flash写错误
#else
if(IAPStructValue.FlashWriteErrFlage == )//Flash写错误
#endif
{
IAPSetUpdateStatus(UpdateStatus_FlashWriteErr);//Flash写错误
}
else if(!IAPStructValue.Overflow)//没有溢出过
{
if(IAPCheckRamFlashAddress(IAPStructValue.UpdateAddress))//检测某些位置的Flash的高位地址是不是0x08 //RAM的高位地址是不是0x20
{
if(IAPStructValue.SumBin != -)//获取了云端的校验和
{
if(IAPStructValue.SumBin == IAPStructValue.Sum)//校验和正确
{
IAPSetUpdateChangeProgram();//切换运行程序地址
IAPSetUpdateStatus(UpdateStatus_WriteAppOk);//写入0x01标志
}
else
{
IAPSetUpdateStatus(UpdateStatus_SumCheckErr);//数据和校验错误
}
}
else
{
IAPSetUpdateChangeProgram();//切换运行程序地址
IAPSetUpdateStatus(UpdateStatus_WriteAppOk);//写入0x01标志
}
}
else
{
IAPSetUpdateStatus(UpdateStatus_DataAddressError);//数据错误
}
}
else//数据溢出
{
IAPSetUpdateStatus(UpdateStatus_DataOverflow);//数据溢出
}
delay_ms();
Reset_MCU();//重启
}

  1. 如果用户使用了对比http返回的数据个数,则对比数据个数

    

  2.判断写Flash有没有错误

    

  3.判断写环形队列的时候有没有溢出过

    

  4.检测数据一开头的那几个地址是不是0x08 和 0x20

    

  5.如果有校验和,则对比下程序的校验和

    否则就不对比,然后写入更新标志是0x01,切换程序运行的地址 ,  最后重启

    

重启以后,加载用户程序

  

  

情况1:正常运行了用户程序

  打开用户程序

  正常运行用户程序便会执行

  

  先看一下处理更新

  IAPSetUpdateStatus(UpdateStatus_None);//清除升级状态(设置升级状态为0)

  然后判断如果是0x01,说明是运行的新程序

  然后切换下程序版本

  再看一下 GetUpdateInfo();

  

  为了便于一眼看出更新的状态

  便把更新状态,设置了对应的字符串

  

  现在就完成了升级了

情况2:运行用户程序失败

  

  提示:大家的这个处理更新程序,最好是让程序运行一段时间,

  感觉程序运行没有问题以后再调用

  如果用户程序运行失败,则执行不到处理更新程序,然后看门狗超时重启

  接着运行 BootLoader程序

  

  BootLoader 判断还是0xFF,说明没有运行起来新更新的用户程序

  设置更新状态是 0xEE

  切换运行程序的地址(运行另一套用户程序)

  

关于BootLoader程序里面的两个定时器

一,下载超时定时器

  1.1下载程序的时候该定时器开始累加

    

  

    

  1.2.调用

    

  1.3.该定时器在写入Flash的时候清零

    

二,整体运行超时定时器

  1.该定时器只要执行BootLoader程序,就开始运行,除非是Wi-Fi配网的时候才会清零,是强制的.

    

其它: Flash调整(保持所有程序的stmflash文件一样)

一,Flash调整(可根据自己使用的f103系列的芯片进行设置),兼容f103全系列

  1.1 可在stmflash.h 调整Flash存储位置

    

  1.2 一般,用户只需要修改

    

  1.3 可在串口打印查看两份程序文件的配置信息 

    user1ROMStart: 0x8004000   用户程序1 Flash存储的开始地址
    user1ROMSize : 0x5c00        用户程序1 程序大小

    user2ROMStart: 0x8009c00   用户程序2 Flash存储的开始地址
    user2ROMSize : 0x5c00         用户程序2 程序大小

    

    当前Flash存储分配如下图

    

  1.4 用户程序1配置

    

  1.5 用户程序2配置

    

结语

  有什么不明白的可在下面留言,我将根据情况对文章再做更改.

ESA2GJK1DH1K升级篇: STM32远程乒乓升级,升级流程源码详细说明的更多相关文章

  1. ESA2GJK1DH1K升级篇: STM32远程乒乓升级,基于(Wi-Fi模块AT指令TCP透传方式),MQTT通信控制升级

    实现功能概要 前面的版本都是,定时访问云端的程序版本,如果版本不一致,然后下载最新的升级文件,实现升级. 这一节,在用户程序里面加入MQTT通信,执行用户程序的时候,通过接收MQTT的升级命令实现升级 ...

  2. ESA2GJK1DH1K升级篇: STM32远程乒乓升级,基于GPRS模块(Air202,SIM800)AT指令TCP透传方式,MQTT通信控制升级

    实现功能概要 这节和上一节的功能一样(只不过上节是利用Wi-Fi模块,这节是利用GPRS模块) 用户程序里面加入MQTT通信,执行用户程序的时候, 通过接收MQTT的升级命令实现升级. 凡是可以实现M ...

  3. ESA2GJK1DH1K升级篇: STM32远程乒乓升级,基于GPRS模块AT指令TCP透传方式,定时访问升级(含有数据校验)

    实现功能概要 单片机定时使用http访问云端的程序版本,如果版本不一致, 然后通过http下载最新的升级文件,实现远程升级STM32程序. 兼容Air202 ,SIM800 测试准备工作(默认访问我的 ...

  4. ESA2GJK1DH1K升级篇: STM32远程乒乓升级,基于WIFI模块AT指令TCP透传方式,定时访问升级(含有数据校验)

    实现功能概要 定时使用http访问云端的程序版本,如果版本不一致,然后通过http下载最新的升级文件,实现升级. 测试准备工作(默认访问我的服务器,改为自己的服务器,请看后面说明) 一,下载BootL ...

  5. ESA2GJK1DH1K升级篇: STM32远程乒乓升级,基于(GPRS模块AT指令TCP透传方式),定时访问升级(兼容Air202,SIM800)

    实现功能概要 单片机定时使用http访问云端的程序版本, 如果版本不一致,然后通过http下载最新的升级文件,实现远程升级STM32. 兼容Air202,SIM800 测试准备工作(默认访问我的服务器 ...

  6. ESA2GJK1DH1K升级篇: STM32远程乒乓升级,基于Wi-Fi模块AT指令TCP透传方式,MQTT通信控制升级(含有数据校验)-APP用户程序制作过程

    前言 这一节和上一节是搭配的 给大家鱼,也必须给鱼竿! 我期望自己封装的代码,无论过了多少年都有应用的价值! 这节说明一下制作APP用户程序的过程 咱是用MQTT通信控制模块实现升级,所以首先自己的程 ...

  7. ESA2GJK1DH1K升级篇: STM32远程乒乓升级,基于(WIFI模块AT指令TCP透传方式),定时访问升级

    前言 学习此代码所需: 实现功能概要 定时使用http访问云端的程序版本,如果版本不一致,然后通过http下载最新的升级文件,实现升级. 测试准备工作(默认访问我的服务器,改为自己的服务器,请看后面说 ...

  8. ESA2GJK1DH1K升级篇: STM32远程乒乓升级,基于Wi-Fi模块(ESP8266)AT指令TCP透传方式,MQTT通信控制升级(加入数据校验)

    前言 这节演示下,上两节写的利用MQTT来控制STM32控制的程序 测试准备工作(默认访问我的服务器,改为自己的服务器,请看后面说明) 一,下载BootLoader程序(请自行下载) 首先BootLo ...

  9. ESA2GJK1DH1K升级篇: 移植远程更新程序到STM32F103RET6型号的单片机,基于(GPRS模块AT指令TCP透传方式)

    前言 上节实现远程更新是更新的STM32F103C8T6的单片机 GPRS网络(Air202/SIM800)升级STM32: 测试STM32远程乒乓升级,基于(GPRS模块AT指令TCP透传方式),定 ...

随机推荐

  1. day06——小数据池、深浅拷贝、集合

    day06 小数据池 小数据池--缓存机制(驻留机制),只是一种规格,不会实际的开辟一个空间 == 判断两边内容是否相等 ***** # a = 10 # b = 10 # print(a == b) ...

  2. Web应急:搜索引擎劫持

    当你直接打开网址访问网站,是正常的,可是当你在搜索引擎结果页中打开网站时,会跳转到一些其他网站,比如博彩,虚假广告,淘宝搜索页面等.是的,你可能了遇到搜索引擎劫持. 现象描述 从搜索引擎来的流量自动跳 ...

  3. 【LOJ#6485】LJJ 学二项式定理(单位根反演)

    [LOJ#6485]LJJ 学二项式定理(单位根反演) 题面 LOJ 题解 显然对于\(a0,a1,a2,a3\)分开算答案. 这里以\(a0\)为例 \[\begin{aligned} Ans&am ...

  4. 【BZOJ3328】PYXFIB(单位根反演,矩阵快速幂)

    [BZOJ3328]PYXFIB(单位根反演,矩阵快速幂) 题面 BZOJ 题解 首先要求的式子是:\(\displaystyle \sum_{i=0}^n [k|i]{n\choose i}f_i\ ...

  5. DNS:从零搭建公司内网DNS服务器

    写在前面的话 网上关于 DNS 的文章其实一搜索一大把,但是看别人的文档一般都会有个问题,乱,不讲究,全是 ctrl c + ctrl v,我个人是看不下去的.头皮发麻.所以决定自己来写写这方面的东西 ...

  6. EF Core中的DB First与Code First

    前言: 大家都习惯在程序中生成对应的model来对数据库进行操作,所以如何快速的生成数据库表的对应model,是基础之一.总结了一下在我的认知中大概是这个结构: Db first方式: 先创建好对应的 ...

  7. HighChat 动态绑定数据记录

    最近刚开始做图形操作,纠结了一上午,highchat 动态绑定数据这块一直不知道怎么绑定,后来多次尝试,发现 1.x轴的数据是个数组格式,我从后台传到前台的时候,js中用数组进行处理数据,然后赋值到c ...

  8. vue接入腾讯防水墙代码

    vue接入腾讯防水墙代码 开始创建代码: 登陆调用方法代码

  9. 配置Java,jdk环境变量

    注意:所有的都是配系统变量 变量名:JAVA_HOME 变量值:D:\Program Files\Java\jdk1.8.0_202(以自己的为准)变量名:Path 变量值:%JAVA_HOME%\b ...

  10. http头字段

    HTTP头字段总结 本节摘自https://www.cnblogs.com/skynet/archive/2010/12/11/1903347.html. 1. Accept:告诉WEB服务器自己接受 ...