STM32+ESP8266(ESP-12F)实现在线OTA升级(腾讯云物联网)
一、硬件及软件准备
1、完整工程源码
下载地址:https://download.csdn.net/download/qq_44062900/85060833
2、硬件:STM32单片机、ESP8266(ESP-12F)
注:ESP8266刷的固件是标准的AT指令固件,不同版本AT固件的AT指令略有不同,请注意!
二、实现效果
- 通过开机时按下S2按键进入BootLoader
- 按下S3按键上报固件版本以获取升级的固件
- 按下S4按键开始下载OTA固件
- 按下S5按键开始校验固件、写入到APP区并运行
1、串口打印效果图
2、云端效果图
三、FLASH分区
本文使用的STM32型号为STM32F103C8T6,FLASH大小为64KB。FLASH分区如下所示:
分区名称 | 分区起始地址 | 分区大小 |
---|
BootLoader 程序 | 0x8000000 | 0x3C00(15KB) | APP版本信息 | 0x8003C00 | 0x400(1KB) | APP程序 | 0x8004000 | 0x6000(24KB) | OTA分区 | 0x800A000 | 0x6000(24KB) |
注意这里的BootLoader程序与官方的BootLoader程序不同。官方的BootLoader程序在出厂时已经固化到ROM中,不能修改。这里的BootLoader程序是用户自行编写的BootLoader,用于OTA升级操作,会占用FLASH大小。
- BootLoader分区
用于实现OTA升级操作,与普通编写的业务程序无本质区别。这里的BootLoader大小根据编写的BootLoader程序大小而定。如果BootLoader程序比较小,可以适当减小该分区的大小,以保证APP程序分区有足够的空间。 - APP版本信息分区
这里用了1KB大小来存储APP版本信息,在刷写BootLoader之后,这个分区是没有任何版本信息的,所以在第一次运行BootLoader时会判断APP版本信息是否被写入过,如果没有被写入过则会自动写入“1.0”的APP版本到该分区中。在升级成功后会将新版本的APP版本信息写入到这个分区。记录APP版本信息主要是用在获取OTA固件时需要上传到云平台,云平台根据当前的APP版本才能知道推送哪个版本升级固件。为什么要1KB的大小?因为STM32F103C8T6的扇区大小是1KB,在写入FLASH时要先擦除FLASH才能写入,而擦除FLASH只能按扇区大小擦除。所以这里最小只能分配1KB。不同容量的MCU的扇区大小不同,可以查看手册。 - APP程序分区
APP分区是用户实现的业务逻辑程序,就是想要系统实现什么功能,把APP程序写入到这个分区就可以了,但这个写入操作一般不是用户自己手动烧写,是由BootLoader程序来写入。当然,这里可以通过将BootLoader和APP程序合并后一起写入,但是有点麻烦,这里不采用这种方式。这里的APP程序是通过云平台上传后,然后进入BootLoader后下载并写入。这个分区是运行的主分区,如果没有手动进入BootLoader程序,则会自动跳转到这个分区。 - 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
简单来说就是以下步骤:
- 首先连接MQTT服务器,并且订阅“$ota/update/${productID}/${deviceName}”主题,其中“${productID}”和“${deviceName}”分别为产品ID和设备名,如"$ota/update/ZD0FYH1ONP/test"。
- 上报当前APP版本,这个很重要,云端是根据APP版本来推送OTA固件的。发布的主题为"$ota/report/${productID}/${deviceName}",上报的协议格式如下:
{
"type": "report_version",
"report":{
"version": "1.0"
}
}
- 在控制台上传OTA升级固件,选择待升级的版本号,创建OTA升级任务。
- 云端自动下发OTA升级消息到设备,消息格式如下:
{
"file_size": 708482,
"md5sum": "36eb5951179db14a631463a37a9322a2",
"type": "update_firmware",
"url": "https://ota-1255858890.cos.ap-guangzhou.myqcloud.com",
"version": "1.1"
}
- 设备根据根下发的URL 下载固件,本文使用的是HTTP协议进行固件分包下载。
- 下载完成升级固件后,校验固件MD5值。如果校验通过,则将下载的固件写入到APP分区。
- 校验APP分区MD5值,向云端上传当前升级后的APP版本号以通知云端升级成功,运行APP程序。
五、OTA固件上传
注意:请确保已经在控制台中创建了项目、产品及设备。在上传固件前,请在设备端进行一次上传APP版本号操作,否则后面会找不到要升级的版本号。已上传版本号可以在设备详情页看到上传的版本号,如下图所示:
- 在进入项目后,依次点击"固件升级"、“添加固件”。
-
在弹出的对话框中填入固件信息,并上传固件,这里上传的固件格式为.bin,后面介绍如何在keil5中生成。 -
上传固件后,点击"固件升级"开始创建升级任务。 -
选择待升级的APP版本号,如果没有出现待升级的版本号,说明你在上传固件前设备未上报版本号,所以会找不到,解决方法是在上传固件前在设备端上传一次版本号即可。 -
在保存后,云端会向设备端发送一条OTA升级消息,如果没有收到OTA升级消息,请检查是否成功订阅了"$ota/update/${productID}/${deviceName}"主题。在确定订阅成功后,手动再向云端上传一次版本号即可收到OTA升级消息。
六、BootLoader程序配置
修改BootLoader起始地址和大小。
七、应用程序配置
-
设置APP分区FLASH起始地址及大小 这里根据划分的FLASH分区填写APP程序起始地址及大小。 -
设置中断向量表偏移 在main函数中设置中断向量表地址,中断向量表的地址为APP的起始地址。 -
生成.bin二进制文件 设置好后,点击重新编译即可生成二进制文件。 生成的二进制文件在工程目录中的"Objects"文件夹下。
八、核心代码
- 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"
void MQTT_OTA_Init(void)
{
ESP_UnvarnishedMode_Init(WIFI_SSID,WIFI_PWD,SERVER_IP,SERVER_PORT);
if(MQTT_Connect(CLIENT_ID,USER_NAME,PASSWORD,KEEPALIVE_TIME))
{
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))
{
printf("OTA Topic Subscribe Failure\r\n");
}
else
{
printf("OTA Topic Subscribe success\r\n");
}
}
}
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);
}
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\""))
{
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\""))
{
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://"))
{
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;
}
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)
{
if(!ESP_UnvarnishedMode_Init(WIFI_SSID,WIFI_PWD,ota_info->host,80))
{
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);
USARTx_Send_String(USART3,ota_info->http_request);
while(usart3.flag==RX_BUFF_EMPTY)
{
Delay_Ms(100);
if(++time_cnt>=30) return HTTP_REQUEST_TIMEOUT;
}
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;
}
}
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;
}
}
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);
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();
}
else printf("The updated firmware was not obtained\r\n");
}
void Update_FirmwareToApp(ota_info_t *ota_info,char *check_md5)
{
if(strcmp(ota_info->md5sum,check_md5)==0)
{
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);
printf("APP MD5:%s\r\n",check_md5);
if((strcmp(ota_info->md5sum,check_md5)==0)&&(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000))
{
printf("APP MD5 OK\r\n");
ota_info->file_size=0;
iap_erase_all_bkp_sector();
memcpy(ota_info->current_version,ota_info->ota_version,sizeof(ota_info->ota_version));
USART_ITConfig(USART3,USART_IT_RXNE, DISABLE);
Get_OTA_Firmware(ota_info);
Write_CurrentFirmwareVersion(ota_info->ota_version);
printf("Jump Run APP...\r\n");
iap_load_app(FLASH_APP1_ADDR);
}
else printf("Update APP Failure\r\n");
}
else printf("OTA Firmware MD5 Error\r\n");
}
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);
}
}
void Write_CurrentFirmwareVersion(char *current_version)
{
iap_write_flash(FIRMWARE_VERSION_STORE_ADDR,(uint8_t*)current_version,FIRMWARE_VERSION_SIZE);
}
- ota.h
#ifndef _OTA_H_
#define _OTA_H_
#include "sys.h"
#define BOOTLOADER_START_ADDR 0x8000000
#define BOOTLOADER_SIZE 0x3C00
#define FIRMWARE_VERSION_STORE_ADDR (BOOTLOADER_START_ADDR+BOOTLOADER_SIZE)
#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];
char url[512];
char host[256];
char http_request[512];
char current_version[FIRMWARE_VERSION_SIZE];
char ota_version[FIRMWARE_VERSION_SIZE];
}ota_info_t;
uint8_t Get_OTA_Info(char *rev_buff,ota_info_t *ota_info);
void MQTT_OTA_Init(void);
void Get_OTA_Firmware(ota_info_t *ota_info);
void Start_OTA_Update(ota_info_t *ota_info,char *check_md5);
void Update_FirmwareToApp(ota_info_t *ota_info,char *check_md5);
void Read_CurrentFirmwareVersion(char *current_version);
void Write_CurrentFirmwareVersion(char *current_version);
#endif
- main.c
#include "stm32f10x.h"
#include "delay.h"
#include "usart.h"
#include "key.h"
#include "iap.h"
#include "ota.h"
int main(void)
{
ota_info_t ota_info;
uint8_t key_val;
char check_md5[100];
Delay_Init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
KEY_Init();
if(Get_Key_Value(KEY_SINGLE_MODE)==KEY_S2_PRESS)
{
Usarts_Init();
printf("Run BootLoader...\r\n");
MQTT_OTA_Init();
Read_CurrentFirmwareVersion(ota_info.current_version);
if(strlen(ota_info.current_version)==0)
{
printf("The APP version was not written,Writting...\r\n");
memcpy(ota_info.current_version,"1.0",4);
Write_CurrentFirmwareVersion(ota_info.current_version);
}
printf("\r\n****************************\r\nCurrent Firmware Version:%s\r\nS3-->Get OTA Firmware\r\nS4-->Download OTA Firmware\r\nS5-->Update APP\r\n****************************\r\n\r\n",ota_info.current_version);
}
else
{
iap_load_app(FLASH_APP_ADDR);
}
while(1)
{
if(usart3.flag==RX_BUFF_NO_EMPTY)
{
for(uint16_t i=0;i<=usart3.cnt;i++)
{
if(usart3.rx_buff[i]=='\0') usart3.rx_buff[i]='0';
}
if(strstr((char*)usart3.rx_buff,"report_version_rsp")==NULL)
{
if(Get_OTA_Info((char*)usart3.rx_buff,&ota_info))
{
printf("\r\n****************************\r\n OTA Firmware Infomation \r\nfile_size:%d\r\nmd5sum:%s\r\nurl:%s\r\nhost:%s\r\nversion:%s\r\n****************************\r\n\r\n",
ota_info.file_size,ota_info.md5sum,ota_info.url,ota_info.host,ota_info.ota_version);
}
else
{
printf("None update firmware\r\n");
}
}
else printf("Upload firmware version success\r\n");
usart3.flag=RX_BUFF_EMPTY;
usart3.cnt=0;
memset(usart3.rx_buff,0,sizeof(usart3.rx_buff));
}
key_val=Get_Key_Value(KEY_SINGLE_MODE);
if(key_val==KEY_S3_PRESS)
{
Get_OTA_Firmware(&ota_info);
}
else if(key_val==KEY_S4_PRESS)
{
Start_OTA_Update(&ota_info,check_md5);
}
else if(key_val==KEY_S5_PRESS)
{
Update_FirmwareToApp(&ota_info,check_md5);
}
}
}
九、代码调试
- 有关如何在控制台创建项目、产品及设备请参考此文:STM32+ESP8266(ESP-12F)物联网温度计-腾讯云物联网
- 有关使用wireshark抓包工具调试代码请移步:STM32+ESP8266(ESP-12F)物联网温度计-移植paho MQTT协议连接阿里云
十、说明
说明:所发布工程只完成了基本的OTA升级部分,可能会存在一些bug,如有发现可以评论或私信告诉我,其他应用功能还需要自己根据实际需要进行更改。 如教程中存在错误,欢迎在评论区指正!
|