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

前言

  这一节实现的功能是使用MQTT通信控制模块去升级

  其实和这一节实现的功能一样  https://www.cnblogs.com/yangfengwu/p/11854595.html

  这一节还是着重讲解一下如何移植升级升级程序到自己的项目

  我做的单片机远程升级封装文件的目的是希望大家直接移植到自己的项目使用!

准备一个已经实现了TCP的工程,拷贝升级处理文件

  1.准备的工程

    

  2.把BootLoader需要用到的文件拷贝到自己的工程

    

    

  拷贝到自己的项目里面

    

整理下工程

  1.自行添加到工程,还有设置头文件位置

    

  2.注:

    可能自己的项目中已经有了上面的一些文件,建议大家把自己以前使用的替换掉!!

    注意: 为使得升级稳定可靠     stmflash文件   必须使用我提供的!!

在自己工程的定时器里面添加以下信息

  

    if(IAPStructValue.PutDataFlage && IAPStructValue.UpdateFlage)IAPStructValue.DownloadTimeout++;
    else IAPStructValue.DownloadTimeout=0;

    IAPStructValue.MainTimeout++;

在自己工程的主函数添加如下信息

  

  

#include "IAP.h"

IAP();

IAPLoadAPPProgram();
IAPDownloadTimeoutFunction();
IAPMainTimeoutFunction();
IAPWriteData();

大家把当前的程序下载到单片机,然后看一下串口1的打印信息

  

    

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

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

  大家可以在下面这个文件根据自己的芯片进行设置

  

  所选芯片Flash大小:这个根据自己的芯片设置

  BootLoader程序大小: BootLoader程序产生的bin文件大小

              假设自己的BootLoader程序的bin文件大小是 15K

            则可以设置上面的值 为16,18,20等

              假设自己的BootLoader程序的bin文件大小是 20K

            则可以设置上面的值 为22,24,26等

  存储用户数据所用Flash大小: 这个根据自己需要的设置,

            但是必须设置,因为咱升级的时候也需要记录数据

            可以是2,4,6,8等等等等

  设置好以后系统便会根据大家的设置打印出来APP用户程序的信息

  当前Flash存储分配如下图

    

    

  BootLoader程序占用 16KB

  两份用户程序各占23KB,升级的时候,就是两块区域来回的倒腾.(名词:乒乓升级)

  乒乓升级的好处是,如果运行新更新的程序失败,可以切换到原先的程序运行

  第一份APP用户程序从0x08004000开始存储

  第二份APP用户程序从0x08009C00开始存储

  剩余的2KB用来存储其它信息

  大家调整完以后编译一下自己的APP用户程序,看下自己的APP用户程序大小

  超了就重新调整,要么换芯片.....

获取云端当前升级的版本

  程序升级是先获取新程序的版本,如果和本地的不一致,则再获取程序文件

  

  这是我Web服务器里面存放的文件

  路径:

    Web服务器根目录的 hardware文件夹->STM32_MQTT_AT8266_SUM文件夹

  STM32_MQTT_AT8266_SUM:

    这个代表着改设备的型号,这节咱控制升级是先用APP通过MQTT询问下设备的型号,和当前版本

     APP获取型号和版本以后,访问对应的updatainfo.txt文件,如果版本不一致则提示用户

      

  updatainfo.txt文件:

    记录当前程序的版本号,程序文件的校验值,升级提示信息

    

  {"version":"1.0.456"}   标准JSON格式  版本号最长20个字符

  加上校验: {"version":"1.0.456","SumBin1":XX,"SumBin2":XX}

  注:校验不是强制的,如果有该参数,则程序就去判断校验. 无,则不判断校验

  SumBin1: 第一份用户程序的校验值

  SumBin2: 第二份用户程序的校验值

  后面制作完用户程序,根据计算的bin文件校验值填写

  我的模块配置成了串口TCP透传,

  串口发送的数据,网络模块直接发给服务器

  服务器返回的数据直接通过串口发给单片机

  所以串口发送的http协议,http协议便转发给了Web服务器

  我在BootLoader里面定时发送协议询问程序版本文件

  

        //不是处于升级状态                 配置模块连接了Web服务器
        if(!IAPStructValue.PutDataFlage && ConfigModuleNoBlockFlage)
        {
            if(GetVersionInfoCnt > 3000)//3S //每隔3S 访问一次程序版本
            {
                GetVersionInfoCnt=0;
                //获取程序版本
                printf("GET %s HTTP/1.1\r\nHost: %s\r\n\r\n","/hardware/STM32_MQTT_AT8266_SUM/updatainfo.txt","47.92.31.46");
            }
        }

处理信息

  1.按照上面的指令,便获取到了

    

  2.现在把信息丢给一个函数处理  IAPVersionDispose

    

    

进入处理版本号函数,添加获取程序文件程序

  该程序处理版本号以后,如果版本不一致,则发送请求相应的程序文件

/**
* @brief  处理从服务器获取的版本号,并获取另一份用户程序
* @warn
* @param  data 传入从云端获取的版本号信息
* @param  None
* @param  None
* @retval None
* @example
**/
void IAPVersionDispose(char *data)
{
    if(!IAPStructValue.PutDataFlage)//升级状态下不再进入判断
    {
        if(strstr(data,"version"))//接收到版本//{"version":"1.02.56"}
        {
            IAPStructValue.Str = StrBetwString(data,"version\":\"","\"");//提取版本号

            if(IAPStructValue.Str != NULL && strlen(IAPStructValue.Str)<=20)//版本号没有问题,设置的版本号最长20位
            {
                memset(IAPStructValue.VersionServer,0,sizeof(IAPStructValue.VersionServer));
                memcpy(IAPStructValue.VersionServer,IAPStructValue.Str,strlen(IAPStructValue.Str));//获取当前云端版本

                if(memcmp(IAPStructValue.VersionServer,IAPStructValue.VersionDevice,20)==0)//云端版本和当前版本一致
                {
                    IAPSetUpdateStatus(UpdateStatus_VersionAlike);//版本号和服务器上面的一致
                    IAPResetMCU();//重启
                }
                else
                {
                    cStringRestore();
                    if(IAPStructValue.RunProgram == 1)//运行的第一份程序
                    {
                        IAPStructValue.Str = StrBetwString(data,"SumBin2\":",",");//提取第二份bin文件的数据和
                    }
                    else//运行的第二份程序
                    {
                        IAPStructValue.Str = StrBetwString(data,"SumBin1\":",",");//提取第一份bin文件的数据和
                    }
                    if(IAPStructValue.Str != NULL)//有数据
                    {
                        IAPStructValue.Len = strlen(IAPStructValue.Str);//获取字符串长度
                        if(IAPStructValue.Len == 1 && IAPStructValue.Str[0]>=‘0‘&& IAPStructValue.Str[0]<=‘9‘)
                        {//1位
                            IAPStructValue.SumBin = IAPStructValue.Str[0]-‘0‘;
                        }
                        else if(IAPStructValue.Len==2 && IAPStructValue.Str[0]>=‘0‘&& IAPStructValue.Str[0]<=‘9‘ && IAPStructValue.Str[1]>=‘0‘&& IAPStructValue.Str[1]<=‘9‘)
                        {//2位
                            IAPStructValue.SumBin = (IAPStructValue.Str[0]-‘0‘)*10+(IAPStructValue.Str[1]-‘0‘);
                        }
                        else if(IAPStructValue.Len==3&&IAPStructValue.Str[0]>=‘0‘&& IAPStructValue.Str[0]<=‘9‘&& IAPStructValue.Str[1]>=‘0‘&& IAPStructValue.Str[1]<=‘9‘&& IAPStructValue.Str[2]>=‘0‘&& IAPStructValue.Str[2]<=‘9‘)
                        {//3位
                            IAPStructValue.SumBin = (IAPStructValue.Str[0]-‘0‘)*100+(IAPStructValue.Str[1]-‘0‘)*10+(IAPStructValue.Str[2]-‘0‘);
                        }
                        else
                        {
                            IAPStructValue.Len = 4;
                        }
                        if(IAPStructValue.Len>3 || IAPStructValue.SumBin < 0 || IAPStructValue.SumBin >255)
                        {
                            IAPSetUpdateStatus(UpdateStatus_SumBinRangeErr);//校验和范围错误
                            IAPResetMCU();//重启
                        }
                    }

                    if(FlashErasePage(IAPStructValue.UpdateAddress,FLASH_USER_SIZE)!=4)//擦除接收用户程序Flash地址
                    {
                        IAPSetUpdateStatus(UpdateStatus_FlashEraseErr);//Flash 擦除失败
                        IAPResetMCU();//重启
                    }
                    IAPSetUpdateVersionServer(IAPStructValue.VersionServer);//存储云端版本

                    if(IAPStructValue.RunProgram == 1)//运行的第一份程序
                    {
                        //发送请求第二份程序文件指令
                    }
                    else//运行的第二份程序
                    {
                        //发送请求第一份程序文件指令
                    }

                    IAPStructValue.PutDataFlage = 1;//可以向环形队列写入数据
                }
                cStringRestore();
            }
            else
            {
                IAPSetUpdateStatus(UpdateStatus_VersionLenErr);//版本号长度错误
                IAPResetMCU();//重启
            }
        }
    }
}

  这个地方根据自己的更改

    

把程序文件写入Flash

  发送完上面的 printf("GET %s HTTP/1.1\r\nHost: %s\r\n\r\n","/hardware/STM32_MQTT_AT8266_SUM/Progect2.bin","47.92.31.46");

  或者 printf("GET %s HTTP/1.1\r\nHost: %s\r\n\r\n","/hardware/STM32_MQTT_AT8266_SUM/Progect.bin","47.92.31.46");

  服务器便会返回程序文件数据了

  咱只需要:

  

  由于是串口返回的数据,所以咱该程序放到串口中断里面

  

  

  有人会问,这样就把数据写入Flash??

  我怎么没有看到写入的地方???

  嘿嘿,我封装好了,大家如果只是用的话,不需要关心怎么做到的

  如果想了解详细的步骤,就从升级篇的第一节开始看

  其实写入Flash是调用的这个函数

  

  

/**
* @brief  把接收到的程序文件写入Flash
* @warn
* @param
* @param  None
* @param  None
* @retval None
* @example
**/
void IAPWriteData(void)
{
    if(rbCanRead(&rb_tIAP)>1)//接收到更新程序就开始写入
    {
        IAPResetDownloadTimeoutFunction();//重置程序下载超时

        rbRead(&rb_tIAP, &IAPStructValue.ReadDat, 2);//读取两个数据
        IAPStructValue.ReadDate = (u16)IAPStructValue.ReadDat[1]<<8;
        IAPStructValue.ReadDate = IAPStructValue.ReadDate|IAPStructValue.ReadDat[0];//拼接数据

        if(IAPStructValue.UpdateAddressCnt< FLASH_DATA_ADDR)//不能超过 数据区
        {
            #ifdef UserContentLength   //自己的Web服务器返回 Length: XXXXXXXX (本次的数据长度)
            //写入的数据个数不能超出,http实际返回的数据个数
            if( (IAPStructValue.UpdateAddressCnt - IAPStructValue.UpdateAddress) <= HttpDataLength)
            #endif
            {
                if(!IAPStructValue.FlashWriteErrFlage)//写Flash没有错误
                {
                    //计算数据累加和
                    IAPStructValue.Sum = IAPStructValue.Sum + IAPStructValue.ReadDat[0] + IAPStructValue.ReadDat[1];

                    if(WriteFlashHalfWord(IAPStructValue.UpdateAddressCnt,IAPStructValue.ReadDate) != 0)//写Flash
                    {
                        IAPStructValue.FlashWriteErrFlage = 1;//写Flash错误
                    }
                }
                IAPStructValue.UpdateAddressCnt+=2;//地址增加
            }
        }
    }
    else//环形队列里面没有数据了.并不证明接收完了数据,可能写入环形队列慢,读的快
    {
        if(IAPStructValue.ReadDataEndFlage)//接收完更新程序
        {
            IAPStructValue.PutDataFlage = 0;//停止向环形队列写入数据
            IAPStructValue.UpdateFlage = 0; //更新标志清零
            IAPStructValue.ReadDataEndFlage = 0;//清零接收完更新程序标志

            #ifdef UserContentLength   //自己的Web服务器返回 Length: XXXXXXXX (本次的数据长度)
            //写入的数据个数和http实际返回的数据个数不相等
            if( (IAPStructValue.UpdateAddressCnt - IAPStructValue.UpdateAddress) != HttpDataLength)
            {
                IAPSetUpdateStatus(UpdateStatus_MissingData);//数据错误
            }
            else if(IAPStructValue.FlashWriteErrFlage == 1)//Flash写错误
            #else
            if(IAPStructValue.FlashWriteErrFlage == 1)//Flash写错误
            #endif
            {
                IAPSetUpdateStatus(UpdateStatus_FlashWriteErr);//Flash写错误
            }
            else if(!IAPStructValue.Overflow)//没有溢出过
            {
                if(IAPCheckRamFlashAddress(IAPStructValue.UpdateAddress))//检测某些位置的Flash的高位地址是不是0x08        //RAM的高位地址是不是0x20
                {
                    if(IAPStructValue.SumBin != -1)//获取了云端的校验和
                    {
                        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);//数据溢出
            }
            IAPResetMCU();//重启
        }
    }
}

注:大家如果想详细了解就从第一节开始看

大约花费一天时间(针对开发人员),如果是初学者.....看自己的学习能力了...

但是,

  大家注意一点...无论用什么过来的数据,你必须保证传入环形队列的数据只是程序数据

  http返回的数据是有数据头的

  咱需要去掉数据头,

  

void USART1_IRQHandler(void)//串口1中断服务程序
{
    u8 Res;
    if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
    {
        Res =USART_ReceiveData(USART1);    //读取接收到的数据

        Usart1ReadBuff[Usart1ReadCnt] = Res;    //接收的数据存入数组
        Usart1ReadCnt++;
        if(Usart1ReadCnt > Usart1ReadLen -10)//防止数组溢出
        {
            Usart1ReadCnt = 0;
        }
        Usart1IdleCnt = 0;

        if(HttpDataStartFlage)
        {
            //可以往环形队列里面写数据,同时没有溢出
            if(IAPStructValue.PutDataFlage && (IAPStructValue.PutDataFlage^IAPStructValue.Overflow))
            {
                if(PutData(&rb_tIAP,NULL,&Res,1) == -1)
                {
                    IAPStructValue.Overflow = 1;//环形队列溢出
                }
            }
        }

                //解析http数据-------------------------------Start
        //HTTP/1.1 200 OK
        if(!HttpHeadOK && IAPStructValue.PutDataFlage)
        {
            if(Res==‘H‘ && HttpHeadCnt==0)HttpHeadCnt++;
            else if(Res==‘T‘ && HttpHeadCnt==1)HttpHeadCnt++;
            else if(Res==‘T‘ && HttpHeadCnt==2)HttpHeadCnt++;
            else if(Res==‘P‘ && HttpHeadCnt==3)HttpHeadCnt++;
            else if(Res==‘/‘ && HttpHeadCnt==4)HttpHeadCnt++;
            else if(Res==‘1‘ && HttpHeadCnt==5)HttpHeadCnt++;
            else if(Res==‘.‘ && HttpHeadCnt==6)HttpHeadCnt++;
            else if(Res==‘1‘ && HttpHeadCnt==7)HttpHeadCnt++;
            else if(Res==‘ ‘ && HttpHeadCnt==8)HttpHeadCnt++;
            else if(Res==‘2‘ && HttpHeadCnt==9)HttpHeadCnt++;
            else if(Res==‘0‘ && HttpHeadCnt==10)HttpHeadCnt++;
            else if(Res==‘0‘ && HttpHeadCnt==11)HttpHeadCnt++;
            else if(Res==‘ ‘ && HttpHeadCnt==12)HttpHeadCnt++;
            else if(Res==‘O‘ && HttpHeadCnt==13)HttpHeadCnt++;
            else if(Res==‘K‘ && HttpHeadCnt==14){HttpHeadOK = 1;HttpHeadCnt=0;HttpDataLength=0;}
            else
            {
                HttpHeadCnt=0;
            }
        }

        #ifdef UserContentLength
        //Content-Length: XXXXXXXX
        if(HttpHeadOK && !HttpDataLengthOK)//获取http发过来的数据个数
        {
            if(Res==‘-‘ && HttpHeadCnt==0)     HttpHeadCnt++;
            else if(Res==‘L‘ && HttpHeadCnt==1)HttpHeadCnt++;
            else if(Res==‘e‘ && HttpHeadCnt==2)HttpHeadCnt++;
            else if(Res==‘n‘ && HttpHeadCnt==3)HttpHeadCnt++;
            else if(Res==‘g‘ && HttpHeadCnt==4)HttpHeadCnt++;
            else if(Res==‘t‘ && HttpHeadCnt==5)HttpHeadCnt++;
            else if(Res==‘h‘ && HttpHeadCnt==6)HttpHeadCnt++;
            else if(Res==‘:‘ && HttpHeadCnt==7)HttpHeadCnt++;
            else if(Res==‘ ‘ && HttpHeadCnt==8)HttpHeadCnt++;
            else if(HttpHeadCnt>=9 && HttpHeadCnt<=16 )//最大99999999个字节. 16:99999999  17:999999999 18:9999999999
            {
                if(Res!=0x0D)
                {
                    HttpDataLength = HttpDataLength*10 + Res - ‘0‘;
                    HttpHeadCnt++;
                }
                else
                {
                    HttpDataLengthOK = 1;
                    HttpHeadCnt = 0;
                }
            }
            else
            {
                HttpHeadCnt = 0;
            }
        }

        if(HttpHeadOK && HttpDataLengthOK && HttpDataLength && !HttpHeadEndOK)
        #else
        if(HttpHeadOK && !HttpHeadEndOK)
        #endif
        {//0D 0A 0D 0A
            if(Res==0x0D && HttpHeadCnt==0)HttpHeadCnt++;
            else if(Res==0x0A && HttpHeadCnt==1)HttpHeadCnt++;
            else if(Res==0x0D && HttpHeadCnt==2)HttpHeadCnt++;
            else if(Res==0x0A && HttpHeadCnt==3){HttpHeadEndOK = 1;}
            else HttpHeadCnt = 0;
        }

        if(HttpHeadEndOK == 1)//http数据的head已经过去,后面的是真实数据
        {
            HttpHeadEndOK=0;
            HttpHeadCnt = 0;
            HttpDataLengthOK=0;

            HttpDataStartFlage=1;
        }
        //解析http数据-------------------------------end

    }
} 

现在传入环形队列的数据只是咱的程序文件数据了

最后

  上面在传输着程序文件,大家需要告诉我数据接收完了

  大家需要在确认数据接收完的地方写上

  

    if(IAPStructValue.PutDataFlage)//写入环形队列的标志位置位了
    {
        IAPStructValue.ReadDataEndFlage=1;//接收完了程序
    }

由于我是单片机串口接收数据

只要是判定串口等了一段时间都没有接收到数据,就说明接收完数据了

现在 BootLoader 已经做好了

然后说一下另一个细节

  

原文地址:https://www.cnblogs.com/yangfengwu/p/12065522.html

时间: 2024-10-10 16:25:07

ESA2GJK1DH1K升级篇: STM32远程乒乓升级,基于(Wi-Fi模块AT指令TCP透传方式),MQTT通信控制升级(含有数据校验)的相关文章

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

实现功能概要 前面的版本都是,定时访问云端的程序版本,如果版本不一致,然后下载最新的升级文件,实现升级. 这一节,在用户程序里面加入MQTT通信,执行用户程序的时候,通过接收MQTT的升级命令实现升级 注意:BootLoader程序还是用以前的程序. 一开始设计这个升级篇的时候,我就规定好了大的框架 BootLoader只是负责升级,其它一概不管 用户程序只是写入升级标志,然后重启,所有的升级步骤全部交给BootLoader! 协议: 一,MQTT上位机通过MQTT发送获取设备信息指令 {"da

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

前言 这一节和上一节是搭配的 给大家鱼,也必须给鱼竿! 我期望自己封装的代码,无论过了多少年都有应用的价值! 这节说明一下制作APP用户程序的过程 咱是用MQTT通信控制模块实现升级,所以首先自己的程序先实现MQTT哈. 协议 注:所有的实现MQTT的软件,统称为MQTT上位机 一,MQTT上位机通过MQTT发送获取设备信息指令 {"data":"updata","cmd":"DeviceInfo"} //设备接收到回复 {&

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

前言 上节实现远程更新是更新的STM32F103C8T6的单片机 GPRS网络(Air202/SIM800)升级STM32: 测试STM32远程乒乓升级,基于(GPRS模块AT指令TCP透传方式),定时访问升级 这节将告诉大家如何移植到其它型号的单片机. 这一节以 STM32F103RET6 (512KB Flash 64KB RAM) 为例 我使用我的这块板子 大家测试的时候可以按照下面的方式接到自己的GPRS模块(Air202 / SIM800) 单片机串口1 接到GPRS的AT指令配置串口

WIFI网络(ESP8266)升级STM32:程序固定Flash地址写入,运行 ( AT指令版;TCP透传方式;不支持MQTT )

前言 这一节为下面两节的结合 实现功能概要 BootLoader程序: 如果Flash没有存储更新标志和用户没有在操作更新,就尝试加载用户程序.    (一直在主循环中判断)           如果有更新标志,控制WIFI以TCP方式连接Web服务器,获取当前程序版本,如果版本不一致,则获取程序文件.实现升级 如果用户按下配网按钮,配网以后,置位更新标志,重启! APP用户程序: 每隔10S,控制WIFI以TCP方式连接Web服务器,获取当前程序版本,如果版本不一致,置位更新标志,重启! 总结

ESA2GJK1DH1K基础篇: STM32+Wi-Fi(AT指令版)实现MQTT源码讲解

前言 该程序需要的基础知识:  https://www.cnblogs.com/yangfengwu/category/1566194.html   所有源码开源,请自行学习 打开第一节的源码 为了方便修改,我用数组存储了些参数 然后看链接MQTT部分 说个地方 然后看 判断连接状态 接着看订阅 判断订阅是否成功 发送一条上线消息 连接MQTT部分就结束了,然后就到了主循环了 一,配网 二,处理接收的数据 三,每隔一段时间采集发送温湿度数据 看下我的心跳包处理 说下我的处理思路 首先,如果到了发

基于hc-05蓝牙模块控制的步进电机指定旋转角度

一.蓝牙模块 蓝牙选用[HC-05主从机一体蓝牙模块 无线蓝牙串口透传 无线模块 电子模块],淘宝链接地址为:https://detail.tmall.com/item.htm?id=41281471872&spm=a1z09.2.0.0.u7iKss&_u=m10qactucc9f 二.手机端APP 手机端蓝牙串口调试助手,如图一所示: 图一 用手机数据线连接手机,下载上图所示[蓝牙串口调试助手].下载完毕后,打开手机APP,如图二所示: 图二                      

ESA2GJK1DH1K升级篇: 阿里云物联网平台 OTA: 关于阿里云物联网平台 OTA 的升级流程

前言 鉴于有些用户直接想使用现成的物联网平台实现 OTA 远程升级 我就写一写这系列的文章 注意:首先大家必须把我自建服务器是如何实现的看明白! 我看了下阿里云提供的,实际上流程和咱自建实现的差别不大 https://help.aliyun.com/document_detail/85700.html?spm=a2c4g.11186623.6.699.6292740d5hzKl6 首先控制升级获取当前程序的版本号等走的还是MQTT 设备端首先通过MQTT获取云端的版本信息,还有程序固件的http

3-STM32物联网开发WIFI(ESP8266)+GPRS(Air202)系统方案升级篇(项目功能演示--远程升级WIFI模块程序)

2-STM32物联网开发WIFI(ESP8266)+GPRS(Air202)系统方案升级篇(视频总揽) 为了节省时间,基础篇的基础内容不再叙述 , 原文地址:https://www.cnblogs.com/yangfengwu/p/10360618.html

【转载】运维职业向!我是怎么入得运维行业?运维工程师入门必备技能以及打怪升级篇

前言:转载 陈浩一个从事安全运维向的前辈文章.写的很好.人非常nice,遇到了问题,qq上很快就回复了我. 大道三千 入门最难,凡事入了行,也就什么都好说了,好的自然不断努力奋斗修行,不好的自然很快就被淘汰.恭谨勤勉,时不我待~ ---------------------------------------------------------------------------------------------------------------------------------------