STM32+ESP8266(ESP-12F)实现在线OTA升级(腾讯云物联网)

Posted Fog.

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STM32+ESP8266(ESP-12F)实现在线OTA升级(腾讯云物联网)相关的知识,希望对你有一定的参考价值。

STM32+ESP8266(ESP-12F)实现在线OTA升级(腾讯云物联网)

目录

一、硬件及软件准备

1、完整工程源码

下载地址:https://download.csdn.net/download/qq_44062900/85060833

2、硬件:STM32单片机、ESP8266(ESP-12F)

注:ESP8266刷的固件是标准的AT指令固件,不同版本AT固件的AT指令略有不同,请注意!

二、实现效果

  1. 通过开机时按下S2按键进入BootLoader
  2. 按下S3按键上报固件版本以获取升级的固件
  3. 按下S4按键开始下载OTA固件
  4. 按下S5按键开始校验固件、写入到APP区并运行

1、串口打印效果图

2、云端效果图

三、FLASH分区

本文使用的STM32型号为STM32F103C8T6,FLASH大小为64KB。FLASH分区如下所示:

分区名称分区起始地址分区大小
BootLoader 程序0x80000000x3C00(15KB)
APP版本信息0x8003C000x400(1KB)
APP程序0x80040000x6000(24KB)
OTA分区0x800A0000x6000(24KB)

注意这里的BootLoader程序与官方的BootLoader程序不同。官方的BootLoader程序在出厂时已经固化到ROM中,不能修改。这里的BootLoader程序是用户自行编写的BootLoader,用于OTA升级操作,会占用FLASH大小。

  1. BootLoader分区
    用于实现OTA升级操作,与普通编写的业务程序无本质区别。这里的BootLoader大小根据编写的BootLoader程序大小而定。如果BootLoader程序比较小,可以适当减小该分区的大小,以保证APP程序分区有足够的空间。
  2. APP版本信息分区
    这里用了1KB大小来存储APP版本信息,在刷写BootLoader之后,这个分区是没有任何版本信息的,所以在第一次运行BootLoader时会判断APP版本信息是否被写入过,如果没有被写入过则会自动写入“1.0”的APP版本到该分区中。在升级成功后会将新版本的APP版本信息写入到这个分区。记录APP版本信息主要是用在获取OTA固件时需要上传到云平台,云平台根据当前的APP版本才能知道推送哪个版本升级固件。为什么要1KB的大小?因为STM32F103C8T6的扇区大小是1KB,在写入FLASH时要先擦除FLASH才能写入,而擦除FLASH只能按扇区大小擦除。所以这里最小只能分配1KB。不同容量的MCU的扇区大小不同,可以查看手册。
  3. APP程序分区
    APP分区是用户实现的业务逻辑程序,就是想要系统实现什么功能,把APP程序写入到这个分区就可以了,但这个写入操作一般不是用户自己手动烧写,是由BootLoader程序来写入。当然,这里可以通过将BootLoader和APP程序合并后一起写入,但是有点麻烦,这里不采用这种方式。这里的APP程序是通过云平台上传后,然后进入BootLoader后下载并写入。这个分区是运行的主分区,如果没有手动进入BootLoader程序,则会自动跳转到这个分区。
  4. OTA分区
    这个分区用于存放下载的APP程序,本质上与要运行的APP程序无任何差别,在升级完成后会将该分区的数据擦除。为什么要OTA分区?不能直接将下载的APP程序写入APP区吗?当然可以直接将下载的APP程序写入APP区,但是这需要在数据传输过程中APP程序下载完整,如果有一个字节出现错误,则会导致校验失败,APP程序自然就崩溃了。这种情况是很常见的,比如说在升级过程中突然断电。这种情况下不能回滚到之前的版本,这种情况是致命的,当然你可以尝试重新下载并写入,直到升级成功。为了保险起见,OTA分区的作用就来了,首先将下载的OTA固件放入这个分区,固件校验通过后再将APP程序从OTA分区搬运到APP分区中。即使传输过程出现错误导致固件不完整,用户也可以执行之前的APP程序。

四、OTA升级流程

腾讯云官方关于固件升级文档介绍:https://cloud.tencent.com/document/product/1081/39359
有关如何在控制台创建项目、产品及设备请参考此文:https://blog.csdn.net/qq_44062900/article/details/118067766

简单来说就是以下步骤:

  1. 首先连接MQTT服务器,并且订阅“$ota/update/$productID/$deviceName”主题,其中“$productID”和“$deviceName”分别为产品ID和设备名,如"$ota/update/ZD0FYH1ONP/test"。
  2. 上报当前APP版本,这个很重要,云端是根据APP版本来推送OTA固件的。发布的主题为"$ota/report/$productID/$deviceName",上报的协议格式如下:

"type": "report_version",
"report":
    "version": "1.0"


// type:消息类型
// version:上报的版本号
  1. 在控制台上传OTA升级固件,选择待升级的版本号,创建OTA升级任务。
  2. 云端自动下发OTA升级消息到设备,消息格式如下:

"file_size": 708482,
"md5sum": "36eb5951179db14a631463a37a9322a2",
"type": "update_firmware",
"url": "https://ota-1255858890.cos.ap-guangzhou.myqcloud.com",
"version": "1.1"

// type:消息类型为update_firmware
// version:升级版本
// url:下载固件的url
// md5asum:固件的MD5值
// file_size:固件大小,单位为字节
  1. 设备根据根下发的URL 下载固件,本文使用的是HTTP协议进行固件分包下载。
  2. 下载完成升级固件后,校验固件MD5值。如果校验通过,则将下载的固件写入到APP分区。
  3. 校验APP分区MD5值,向云端上传当前升级后的APP版本号以通知云端升级成功,运行APP程序。

五、OTA固件上传

注意:请确保已经在控制台中创建了项目、产品及设备。在上传固件前,请在设备端进行一次上传APP版本号操作,否则后面会找不到要升级的版本号。已上传版本号可以在设备详情页看到上传的版本号,如下图所示:

  1. 在进入项目后,依次点击"固件升级"、“添加固件”。

  1. 在弹出的对话框中填入固件信息,并上传固件,这里上传的固件格式为.bin,后面介绍如何在keil5中生成。

  2. 上传固件后,点击"固件升级"开始创建升级任务。

  3. 选择待升级的APP版本号,如果没有出现待升级的版本号,说明你在上传固件前设备未上报版本号,所以会找不到,解决方法是在上传固件前在设备端上传一次版本号即可。

  4. 在保存后,云端会向设备端发送一条OTA升级消息,如果没有收到OTA升级消息,请检查是否成功订阅了"$ota/update/$productID/$deviceName"主题。在确定订阅成功后,手动再向云端上传一次版本号即可收到OTA升级消息。

六、BootLoader程序配置

修改BootLoader起始地址和大小。

七、应用程序配置

  1. 设置APP分区FLASH起始地址及大小
    这里根据划分的FLASH分区填写APP程序起始地址及大小。

  2. 设置中断向量表偏移
    在main函数中设置中断向量表地址,中断向量表的地址为APP的起始地址。

  3. 生成.bin二进制文件
    设置好后,点击重新编译即可生成二进制文件。 生成的二进制文件在工程目录中的"Objects"文件夹下。

八、核心代码

  1. ota.c
#include "ota.h"
#include <string.h>
#include <stdlib.h>
#include "usart.h"
#include "esp8266.h"
#include "iap.h"
#include "delay.h"
#include "tencent_mqtt.h"
#include "md5.h"


/*
函数功能:初始化OTA,连接MQTT服务器
参数:无
返回值:无
*/
void MQTT_OTA_Init(void)

	ESP_UnvarnishedMode_Init(WIFI_SSID,WIFI_PWD,SERVER_IP,SERVER_PORT);//连接TCP服务器并进入透传模式
	if(MQTT_Connect(CLIENT_ID,USER_NAME,PASSWORD,KEEPALIVE_TIME))//登录MQTT服务器
    
        printf("MQTT Server Connect Failure\\r\\n");
    
	else 
	
		printf("MQTT Server Connect success\\r\\n");	
		if(MQTT_Subscribe_Topic(OTA_SUB_TOPIC,REQ_QOS_0,1))//订阅OTA下行消息
		
			printf("OTA Topic Subscribe Failure\\r\\n");
		
		else
		
			printf("OTA Topic Subscribe success\\r\\n");
					
	



/*
函数功能:上报固件版本以获取OTA固件
参数:无
返回值:无
*/
void Get_OTA_Firmware(ota_info_t *ota_info)

    char buff[100];
	usart3.flag=0;
	usart3.cnt=0;
	memset((char *)usart3.rx_buff,0,USART_RX_MAXLEN);//清空接收缓冲区
    snprintf(buff,sizeof(buff),OTA_VERSION_UPLOAD_TEMPLATE,ota_info->current_version);
	MQTT_Publish(OTA_PUB_TOPIC,(uint8_t*)buff);//上报固件版本


/*
函数功能:从接收buff中获取ota信息
参数:char *rev_buff,ota_info_t *ota_info
返回值:无
*/
uint8_t Get_OTA_Info(char *rev_buff,ota_info_t *ota_info)

	char *data_pt_start=NULL;
	char *data_pt_end=NULL;
	char buff[100];
    uint8_t get_state=0;
	if(strstr(rev_buff,"\\"file_size\\""))//获取固件大小
	
		data_pt_start=strstr(rev_buff,"\\"file_size\\"");
		data_pt_end=strstr(data_pt_start,",");
		snprintf(buff,data_pt_end-data_pt_start-11,"%s",data_pt_start+12);
		ota_info->file_size=atoi(buff);
        get_state=1;
	
	if(strstr(rev_buff,"\\"md5sum\\""))//获取MD5值
	
		data_pt_start=strstr(rev_buff,"\\"md5sum\\"");
		data_pt_end=strstr(data_pt_start,",");
		snprintf(ota_info->md5sum,data_pt_end-data_pt_start-10,"%s",data_pt_start+10);
        get_state=1;
	
	if(strstr(rev_buff,"\\"url\\""))//获取下载URL
	
		data_pt_start=strstr(rev_buff,"\\"url\\"");
		data_pt_end=strstr(data_pt_start,",");
		snprintf(ota_info->url,data_pt_end-data_pt_start-7,"%s",data_pt_start+7);
        get_state=1;
	
	if(strstr(ota_info->url,"https://"))//获取OTA主机域名
	
		data_pt_start=strstr(rev_buff,"https://");
		data_pt_end=strstr(data_pt_start+8,"/");
		snprintf(ota_info->host,data_pt_end-data_pt_start-7,"%s",data_pt_start+8);
		get_state=1;
	

	if(strstr(rev_buff,"\\"version\\""))//获取固件版本
	
		data_pt_start=strstr(rev_buff,"\\"version\\"");
		data_pt_end=strstr(data_pt_start,"");
		snprintf(ota_info->ota_version,data_pt_end-data_pt_start-11,"%s",data_pt_start+11);
	
    return get_state;


/*
函数功能:开始下载OTA固件
参数:ota_info_t *ota_info
返回值:u8
*/
static u8 DownLoad_OTA_Firmware(ota_info_t *ota_info)

	int i;
	char *data_pt_start=NULL;
	u8 time_cnt=0;
	u8 data_packet_num=0;
	if(ota_info->file_size<=APP1_FLASH_SIZE)//判断固件是否超过FLASH大小
	
		if(!ESP_UnvarnishedMode_Init(WIFI_SSID,WIFI_PWD,ota_info->host,80))//连接OTA固件下载服务器
		
			//计算分包下载次数
			if(ota_info->file_size%1024==0) data_packet_num=(ota_info->file_size/1024)-1;
            else  data_packet_num=ota_info->file_size/1024;
			for(i=0;i<=data_packet_num;i++)//分包下载
			
					usart3.flag=RX_BUFF_EMPTY;//接收标志位清零
					usart3.cnt=0;
					memset(usart3.rx_buff,0,sizeof(usart3.rx_buff));//清空接收缓冲区
					snprintf(ota_info->http_request,sizeof(ota_info->http_request),
                        "GET http://%s HTTP/1.1\\r\\nHost:%s\\r\\nRange:bytes=%d-%d\\r\\n\\r\\n",
                    ota_info->url+8,ota_info->host,i*1024,(i+1)*1024-1);//序列化HTTP请求报文
					USARTx_Send_String(USART3,ota_info->http_request);//发起HTTP下载固件请求
					while(usart3.flag==RX_BUFF_EMPTY)//等待接收数据完成
					
						Delay_Ms(100);
						if(++time_cnt>=30) return HTTP_REQUEST_TIMEOUT;//HTTP请求超时
					
					time_cnt=0;
					if(strstr((char*)usart3.rx_buff,"\\r\\n\\r\\n"))//寻找数据包头部
					
						data_pt_start=strstr((char*)usart3.rx_buff,"\\r\\n\\r\\n");
                        iap_write_flash(FLASH_OTA_ADDR+i*1024,(u8 *)data_pt_start+4,1024);
						printf("总大小:%d Bytes,写入第%d包,%d-%d Bytes已写入\\r\\n",ota_info->file_size,i+1,i*1024,(i+1)*1024-1);
					
					else
                    
                        printf("未找到头部数据\\r\\n");
                        return GET_OTA_DATA_PACKET_FAIL;//解析OTA数据包失败
                    
			
            printf("固件下载成功\\r\\n");
			return FIRMWARE_DOWNLOAD_SUCC;//固件下载成功
		
		else 
        
            printf("进入透传模式失败\\r\\n");
            return SERVER_CONNECT_FAIL;//服务器连接失败
        
	
	else
	
		printf("OTA固件大小超出范围\\r\\n");
		return OTA_PACKET_SIZE_OVERFLOW;
	
	


/*
函数功能:开始OTA更新
参数:ota_info_t *ota_info,char *check_md5
返回值:无
*/
void Start_OTA_Update(ota_info_t *ota_info,char *check_md5)

	if(ota_info->file_size!=0)
	
		printf("Firmware Size:%d Bytes,Downloading...\\r\\n",ota_info->file_size);
		if(DownLoad_OTA_Firmware(ota_info)==FIRMWARE_DOWNLOAD_SUCC)//开始下载固件
		
			Compute_string_md5((u8*)FLASH_OTA_ADDR,ota_info->file_size, check_md5);//计算APP2 MD5值
			printf("OTA Firmware MD5:%s\\r\\nFirmware Download Success\\r\\n",check_md5);
		
		else printf("Firmware Download Failure\\r\\n");
        printf("Login MQTT Server...\\r\\n");
        MQTT_OTA_Init();//重新登录MQTT服务器
	
    else printf("The updated firmware was not obtained\\r\\n");//未获取到更新固件
	


/*
函数功能:将下载的固件更新到APP1中
参数:ota_info_t *ota_info,char *check_md5
返回值:无
*/
void Update_FirmwareToApp(ota_info_t *ota_info,char *check_md5)

	if(strcmp(ota_info->md5sum,check_md5)==0)//比较MD5值
	
		printf("OTA Firmware MD5 OK\\r\\nUpdate APP...\\r\\n");
        iap_write_flash(FLASH_APP1_ADDR,(u8*)FLASH_OTA_ADDR,ota_info->file_size);
		Compute_string_md5((u8*)FLASH_APP1_ADDR,ota_info->file_size, check_md5);//计算APP MD5值
		printf("APP MD5:%s\\r\\n",check_md5);
		if((strcmp(ota_info->md5sum,check_md5)==0)&&(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000))//判断是否为0X08XXXXXX.
		
			printf("APP MD5 OK\\r\\n");
			ota_info->file_size=0;//清除固件
            iap_erase_all_bkp_sector();//擦除下载的OTA固件
            memcpy(ota_info->current_version,ota_info->ota_version,sizeof(ota_info->ota_version));
            USART_ITConfig(USART3,USART_IT_RXNE, DISABLE);//关闭USART3接收缓冲区非空中断
            Get_OTA_Firmware(ota_info);//上报固件版本,通知服务器OTA升级完成
            Write_CurrentFirmwareVersion(ota_info->ota_version);//写入APP版本
            printf("Jump Run APP...\\r\\n");
			iap_load_app(FLASH_APP1_ADDR);//执行FLASH APP代码
            
		
		else printf("Update APP Failure\\r\\n");
	
	else printf("OTA Firmware MD5 Error\\r\\n");


/*
函数功能:读取当前版本信息
参数:char *current_version
返回值:无
*/
void Read_CurrentFirmwareVersion(char *current_version)

    uint8_t *version_pt=(uint8_t*)FIRMWARE_VERSION_STORE_ADDR;
    for(uint8_t i=0;i<FIRMWARE_VERSION_SIZE;i++)
    
        if(*(version_pt+i)==0xFF) continue;
        *(current_version+i)=*(version_pt+i);
    


/*
函数功能:写入当前版本信息
参数:char *current_version
返回值:无
*/
void Write_CurrentFirmwareVersion(char *current_version)

    iap_write_flash(FIRMWARE_VERSION_STORE_ADDR,(uint8_t*)current_version,FIRMWARE_VERSION_SIZE);


  1. ota.h
#ifndef _OTA_H_
#define _OTA_H_

#include "sys.h"

#define BOOTLOADER_START_ADDR   0x8000000  //BootLoader起始地址
#define BOOTLOADER_SIZE         0x3C00  //BootLoader大小
#define FIRMWARE_VERSION_STORE_ADDR  (BOOTLOADER_START_ADDR+BOOTLOADER_SIZE)   //存储固件信息的FLASH地址
#define FIRMWARE_VERSION_SIZE   16 //存储固件信息的字节数
enum

	HTTP_REQUEST_TIMEOUT,
	GET_OTA_DATA_PACKET_FAIL,
	SERVER_CONNECT_FAIL,
	FIRMWARE_DOWNLOAD_SUCC,
	OTA_PACKET_SIZE_OVERFLOW
;


typedef struct ota_info

	u32 file_size;//文件大小
	char md5sum[100];//服务器下发的md5
	char url[512];//下载URL
	char host[256];//OTA服务器域名
	char http_request[512];//HTTP请求报文
    char

以上是关于STM32+ESP8266(ESP-12F)实现在线OTA升级(腾讯云物联网)的主要内容,如果未能解决你的问题,请参考以下文章

STM32+ESP8266(ESP-12F)实现在线OTA升级(腾讯云物联网)

STM32+ESP8266(ESP-12F)物联网温度计-腾讯云物联网

STM32+ESP8266(ESP-12F)物联网温度计-腾讯云物联网

STM32+ESP8266(ESP-12F)物联网温度计-腾讯云物联网

STM32+ESP8266(ESP-12F)物联网温度计-腾讯云物联网

STM32+ESP8266移植paho MQTT协议连接阿里云物联网平台