概述
MCU OTA可以对MCU程序进行无线远程升级。原本MCU程序软件版本号是01,想升级到02,但是设备已经量产了不可能再去一个一个设备重新烧录新的程序,这时候就需要用到MCU OTA.本文以MCU OTA教程(3.1)的基础上移植到STM32G071RBT6芯片。下载源文档及源参考代码。
FLASH分区
STM32G071RBT6芯片Flash 空间划分出 4 个区域:Bootloader、FLAG、APP 分区、APPBAK 分区
Bootloader:存储 Bootloader 固件,MCU 上电后首先运行该固件。
FLAG:存储有关升级的相关标志位,Bootloader 和 APP 分区都需要操作该区域。
升级标志位(2B)
固件大小(4B)
MD5加密数据(16B)
APP 分区:存储用户程序固件。
APPBAK 分区:临时存储云端下发的新固件,升级固件的一个过渡存储区。
BOOTLOADER分区部分
Bootloader程序流程
Bootloader 的主要职能是在有升级任务的时候将 APPBAK 分区里面的固件拷贝到 APP 区域。当然,这期间需要做很多的工作,比如升级失败的容错等等。具体的流程可以参考图示。需要注意的是,在校验 MD5 正确后开始搬运固件数据期间,MCU 出现故障(包括突然断电),MCU 应发生复位操作(FLAG 区域数据未破坏),复位后重新开始执行 Bootloader,从而避免 MCU 刷成板砖。
Bootloader编译设置
APP分区部分
固件接收流程
做好 BOOTLOADER 工作后,我们开始写 APP 分区的代码。APP 分区固件的编写要注意硬件版本号和软件版本号,软件版号作为升级迭代很重要的标志。
App编译设置
FLASH驱动编写
Flash.c
#include "flash.h" #include <stdio.h> #include <string.h> volatile uint32_t flashWriteOffset = SYS_APP_BAK_SAVE_ADDR_BASE; volatile uint32_t flashReadOffset = SYS_APP_BAK_SAVE_ADDR_BASE; /* MCU OTA */ void flash_erase_page(uint8_t flashPage , uint32_t addr_base) { HAL_FLASH_Unlock(); FLASH_EraseInitTypeDef f; f.TypeErase = FLASH_TYPEERASE_PAGES; f.Page = flashPage + (addr_base - SYS_Bootloader_SAVE_ADDR_BASE)/FLASH_PAGE_SIZE; f.NbPages = 1; uint32_t PageError = 0; HAL_FLASHEx_Erase(&f, &PageError); HAL_FLASH_Lock(); } void flash_erase(uint32_t size , uint32_t addr_base) { uint32_t flashPageSum; uint32_t i; /*如果小于2048做处理*/ if(size < FLASH_PAGE_SIZE) size = FLASH_PAGE_SIZE; // /* 计算需要擦写的Flash页 */ if((size % FLASH_PAGE_SIZE) == 0) { flashPageSum = size / FLASH_PAGE_SIZE; //小于一页擦除一页 } else { flashPageSum = (size / FLASH_PAGE_SIZE) + 1; //大于一页擦除n+1页 } for(i = 0;i<flashPageSum;i++) { flash_erase_page(i,addr_base); //基址累加擦除flash } } void writeFlash(uint64_t * buf_to_save , uint16_t len , uint32_t wFlashAddr) { uint16_t count=0; if(wFlashAddr >= 0x08020000) { #ifdef DEBUG printf("Waring:Flash Write Addr Error\r\n"); #endif flashWriteOffset = SYS_APP_BAK_SAVE_ADDR_BASE; return; } HAL_FLASH_Unlock(); while(count < len) { HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD,(wFlashAddr + count*8),buf_to_save[count]); //вflashһٶַ֘дɫѫؖè16λé count ++; } HAL_FLASH_Lock(); } void readFlash(uint64_t * buf_to_get,uint16_t len , uint32_t readFlashAddr) { uint16_t count=0; while(count<len) { buf_to_get[count]=*(uint64_t *)(readFlashAddr + count*8); count++; } } /*写Flash,控制写长度,Flash地址偏移*/ void wFlashData(uint8_t * buf_to_save , uint16_t len , uint32_t wFlashAddr) { uint8_t WriteFlashTempBuf[PIECE_MAX_LEN];//写Flash临时缓冲区 uint16_t WriteFlashTempLen = 0;//写Flash长度 uint8_t rem; memset(WriteFlashTempBuf,0xEE,sizeof(WriteFlashTempBuf));//写Flash临时缓冲区首先全部填充0xEE memcpy(WriteFlashTempBuf,buf_to_save,len);//临时缓冲区 WriteFlashTempLen = len; if(len%8 != 0) { rem = len%8; WriteFlashTempLen = len +8 - rem; } writeFlash((uint64_t *)&WriteFlashTempBuf , WriteFlashTempLen/8 , wFlashAddr); } void rFlashData(uint8_t * buf_to_get , uint16_t len , uint32_t rFlashAddr) { uint8_t ReadFlashTempBuf[PIECE_MAX_LEN];//读Flash临时缓冲区 uint16_t ReadFlashTempLen = 0;//读Flash长度 uint8_t rem; if(len%8 == 0) { ReadFlashTempLen = len; readFlash((uint64_t *)&ReadFlashTempBuf,ReadFlashTempLen/8 , rFlashAddr); memcpy(buf_to_get,ReadFlashTempBuf,len); } else { rem = len%8; ReadFlashTempLen = len + 8 - rem; readFlash((uint64_t *)&ReadFlashTempBuf,ReadFlashTempLen/8 , rFlashAddr); memcpy(buf_to_get,ReadFlashTempBuf,len); } }
跳转到APP代码:
typedef void (*iapfun)(void); iapfun jump2app; uint16_t iapbuf[1024]; #define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) #define MEM_ADDR(addr) *((volatile unsigned long *)(addr)) #define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum)) //设置栈顶指针 __asm void MSR_MSP(uint32_t addr) { MSR MSP, r0 //set Main Stack value BX r14 } void iap_load_app(uint32_t appxaddr) { if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000) { #ifdef DEBUG printf("Stack Success!\r\n"); #endif jump2app=(iapfun)*(vu32*)(appxaddr+4); MSR_MSP(*(vu32*)appxaddr); jump2app(); } else { #ifdef DEBUG printf("Stack Failed!\r\n"); #endif } }
Flash.h
#ifndef _FLASH_ #define _FLASH_ #include <stm32g0xx.h> #define DEBUG #define PROTOCOL_DEBUG typedef uint32_t u32; typedef uint16_t u16; typedef uint8_t u8; typedef __IO uint32_t vu32; typedef __IO uint16_t vu16; typedef __IO uint8_t vu8; /* BootLoader Flash首地址 */ #define SYS_Bootloader_SAVE_ADDR_BASE 0x08000000//Bootloader首地址//支持Bootloader大小14KB /* 升级参数存储 */ #define UPDATE_PARAM_SAVE_ADDR_BASE 0x08003800 #define UPDATE_PARAM_MAX_SIZE (2*1024)//支持参数大小2KB /* APP Flash首地址 */ #define SYS_APP_SAVE_ADDR_BASE 0x08004000 #define APP_DATA_MAX_SIZE (56*1024)//支持APP大小56KB /* APP BAK Flash首地址 */ #define SYS_APP_BAK_SAVE_ADDR_BASE 0x08012000 #define APP_BAK_DATA_MAX_SIZE (56*1024)//支持APP_BAK大小56KB /* 升级参数 */ #define PIECE_MAX 256 #define SSL_MAX_LEN 16 typedef struct { uint16_t rom_statue; uint32_t rom_size; uint8_t ssl_data[SSL_MAX_LEN]; }update_param_def; #define PIECE_MAX_LEN 256 void save_param_to_flash(uint16_t * buf_to_save,uint16_t len ); void read_param_from_flash(uint16_t * buf_to_get,uint16_t len); void set_flash_flag_to_updata(uint16_t crc_code); void rFlashData(uint8_t * buf_to_get , uint16_t len , uint32_t rFlashAddr); void wFlashData(uint8_t * buf_to_save , uint16_t len , uint32_t wFlashAddr); void iap_load_app(uint32_t appxaddr); void flash_erase(uint32_t size , uint32_t addr_base); #endif
BOOTLOADER分区OTA功能代码移植
通过CubMX新建工程
配置GPIO和UART4
配置时钟
生成代码
代码移植
添加Flash.c/h和gagent_md5.c/h到工程中
新建app.c
#include "app.h" #include "../Src/md5/gagent_md5.h" #include "flash.h" #include "usart.h" /*Global Variable*/ /** * @brief Main program. * @param None * @retval None */ update_param_def update_param; uint8_t md5_calc[SSL_MAX_LEN]; MD5_CTX ctx; void mcu_restart() { //__set_FAULTMASK(1); NVIC_SystemReset(); } int8_t ROM_MD5_Check(uint32_t sys_size , uint32_t FlashAddr , uint8_t *ssl) { uint8_t update_data_tmp[PIECE_MAX]; uint32_t load_loop = 0; uint32_t remaind_data_len = sys_size; uint32_t valid_data_len = 0; GAgent_MD5Init(&ctx); if(0 == sys_size%PIECE_MAX) { load_loop = sys_size / PIECE_MAX; } else { load_loop = sys_size / PIECE_MAX + 1; } #ifdef DEBUG printf("Check New Sys ...loop = %d\r\n",load_loop); #endif for(uint32_t i = 0;i<load_loop;i++) { if(remaind_data_len > PIECE_MAX) { valid_data_len = PIECE_MAX; } else { valid_data_len = remaind_data_len; } memset(update_data_tmp,0,PIECE_MAX); rFlashData(update_data_tmp, valid_data_len, FlashAddr + i*PIECE_MAX); GAgent_MD5Update(&ctx, update_data_tmp, valid_data_len); remaind_data_len = remaind_data_len - valid_data_len; #ifdef DEBUG printf("*"); #endif } #ifdef DEBUG printf("\r\n"); #endif GAgent_MD5Final(&ctx, md5_calc); #ifdef DEBUG printf("MD5 Calculate Success \r\n "); #endif if(memcmp(ssl, md5_calc, SSL_MAX_LEN) != 0) { #ifdef DEBUG printf("Md5_Cacl Check Faild ,MCU OTA Faild\r\n "); #endif #ifdef PROTOCOL_DEBUG printf("MD5: "); for(uint16_t i=0; i<SSL_MAX_LEN; i++) { printf("x ", md5_calc[i]); } printf("\r\n"); #endif return -1; } else { #ifdef DEBUG printf("MD5 Check Success ,MCU OTA Success\r\n "); #endif return 0; } } uint8_t update_new_system(uint32_t sys_size) { uint8_t update_data_tmp[PIECE_MAX]; uint32_t load_loop = 0; uint32_t remaind_data_len = sys_size; uint32_t valid_data_len = 0; if(0 == sys_size%PIECE_MAX) { load_loop = sys_size / PIECE_MAX; } else { load_loop = sys_size / PIECE_MAX + 1; } #ifdef DEBUG printf("Copy New Sys ...loop = %d\r\n",load_loop); #endif flash_erase(update_param.rom_size , SYS_APP_SAVE_ADDR_BASE); #ifdef DEBUG printf("Copy New Sys\r\n"); #endif for(uint32_t i = 0;i<load_loop;i++) { if(remaind_data_len > PIECE_MAX) { valid_data_len = PIECE_MAX; } else { valid_data_len = remaind_data_len; } memset(update_data_tmp,0,PIECE_MAX); rFlashData(update_data_tmp, valid_data_len, SYS_APP_BAK_SAVE_ADDR_BASE + i*PIECE_MAX); wFlashData(update_data_tmp , valid_data_len , SYS_APP_SAVE_ADDR_BASE + i*PIECE_MAX); remaind_data_len = remaind_data_len - valid_data_len; #ifdef DEBUG printf("."); #endif } #ifdef DEBUG printf("\r\n"); printf("Copy Success , Wait to Check... \r\n"); #endif if(0 == ROM_MD5_Check(update_param.rom_size , SYS_APP_SAVE_ADDR_BASE , update_param.ssl_data)) { #ifdef DEBUG printf("New ROM Check Success , Wait to Load New Systerm \r\n"); #endif flash_erase(sizeof(update_param_def), UPDATE_PARAM_SAVE_ADDR_BASE); mcu_restart(); } else { #ifdef DEBUG printf("New ROM Check Faild , Update Faild , MCU Try To Update Again ,MCU Restart... \r\n"); #endif mcu_restart(); } return 0; } void APP_Process(void) { memset((uint8_t *)&update_param, 0 , sizeof(update_param_def)); rFlashData((uint8_t *)&update_param, sizeof(update_param_def), UPDATE_PARAM_SAVE_ADDR_BASE); if(0xEEEE == update_param.rom_statue) { #ifdef DEBUG printf("Update Task ,Sys Will Load New Sys..Wait For A Moment \r\n"); printf("Update Size [%d] \r\n",update_param.rom_size); #endif if(0 == ROM_MD5_Check(update_param.rom_size , SYS_APP_BAK_SAVE_ADDR_BASE , update_param.ssl_data)) { update_new_system(update_param.rom_size); } else { #ifdef DEBUG printf("Check Faild , Go to Old Systerm\r\n"); #endif flash_erase(sizeof(update_param_def), UPDATE_PARAM_SAVE_ADDR_BASE); if(((*(vu32*)(SYS_APP_SAVE_ADDR_BASE + 4)) & 0xFF000000) == 0x08000000) { #ifdef DEBUG printf("Sys Will Load APP.....\r\n"); #endif iap_load_app(SYS_APP_SAVE_ADDR_BASE); } else { #ifdef DEBUG printf("Start APP Failed!\r\n"); #endif } } } else { #ifdef DEBUG printf("No Update Task , Go To APP ....X\r\n",update_param.rom_statue); #endif if(((*(vu32*)(SYS_APP_SAVE_ADDR_BASE + 4)) & 0xFF000000) == 0x08000000) { #ifdef DEBUG printf("Sys Will Load APP.....\r\n"); #endif iap_load_app(SYS_APP_SAVE_ADDR_BASE); } else { #ifdef DEBUG printf("Start APP Failed!\r\n"); #endif } } }
更改主代码main.c
APP分区OTA功能代码移植
代码移植
添加Flash.c/h和gagent_md5.c/h到工程中
中断向量偏移地址修改:
app.c

gizwits_protocol.h
gizwits_protocol.c
尾部添加以下代码:
/** * @brief Pro_W2D_UpdateCmdHandle * Handle OTA Ask , Transform MD5 Char2Hex * @param[in] : * @param[out] : * @return 0,Update Ask Handle Success , Send Ready Success * -1,Input Param Illegal * -2,Update Ask Handle Success , Send Ready Faild * */ int8_t Pro_W2D_UpdateCmdHandle(uint8_t *inData,uint32_t dataLen) { uint8_t k = 0; int8_t ret = 0; uint8_t fileMD5value[FILE_MD5_MAX_LEN]; uint16_t fileMD5len; //MD5 Length if(NULL == inData) { return -1; } romUpdate.updateFileSize = ((uint32_t)(inData[0]<<24))|((uint32_t)(inData[1]<<16))|((uint32_t)(inData[2]<<8))|((uint32_t)(inData[3])); //judge flash size if(romUpdate.updateFileSize > APP_BAK_DATA_MAX_SIZE) { GIZWITS_LOG("UpdateFileSize Error ,Update Failed ,fileSize = %d \n",romUpdate.updateFileSize); return -1; } else { GIZWITS_LOG("UpdateFileSize Legal ,size = %d \n",romUpdate.updateFileSize); romUpdate.update_param.rom_size = romUpdate.updateFileSize; flash_erase(APP_BAK_DATA_MAX_SIZE,SYS_APP_BAK_SAVE_ADDR_BASE); GIZWITS_LOG("flash erase finished!!\n"); } fileMD5len = inData[4]*256 + inData[5]; GIZWITS_LOG("FileMD5len = %d ", fileMD5len); memcpy(fileMD5value,&inData[6],fileMD5len); #ifdef PROTOCOL_DEBUG GIZWITS_LOG("MD5: "); for(uint16_t i=0; i<32; i++) { GIZWITS_LOG("x ", fileMD5value[i]); } GIZWITS_LOG("\r\n"); #endif for(uint16_t j = 0; j<SSL_MAX_LEN; j++) { romUpdate.update_param.ssl_data[j] = char2hex(fileMD5value[k],fileMD5value[k+1]); k += 2; } #ifdef PROTOCOL_DEBUG GIZWITS_LOG("MD5_Hex: "); for(uint16_t i=0; i<SSL_MAX_LEN; i++) { GIZWITS_LOG("x ", romUpdate.update_param.ssl_data[i]); } GIZWITS_LOG("\r\n"); #endif GIZWITS_LOG("GAgent_MD5Init \n"); GAgent_MD5Init(&romUpdate.ctx); //send ready ret = Pro_D2W_UpdateReady(fileMD5value,fileMD5len); if(0 != ret) { GIZWITS_LOG("Pro_D2W_UpdateReady Error ,Error Code = %d \n",ret); return -2; } return 0; } /** * @brief Pro_D2W_UpdateReady * MCU Send Update Ready * @param[in] md5Data: Input md5 char data * @param[in] md5Len : Input md5 length * @param[out] : * @return 0,Update Ask Handle Success , Send Ready Success * -1,Input Param Illegal * -2,Uart Send Faild * */ int8_t Pro_D2W_UpdateReady(uint8_t *md5Data , uint16_t md5Len) { int8_t ret = 0; uint8_t txBuf[100]; memset(txBuf,0,100); uint8_t *pTxBuf = txBuf; if(NULL == md5Data) { return -1; } uint16_t dataLen = sizeof(protocolCommon_t) + 2 + md5Len + 2 - 4 ; *pTxBuf ++= 0xFF; *pTxBuf ++= 0xFF; *pTxBuf ++= (uint8_t)(dataLen>>8); *pTxBuf ++= (uint8_t)(dataLen); *pTxBuf ++= CMD_BIGDATA_READY; *pTxBuf ++= gizwitsProtocol.sn++; *pTxBuf ++= 0x00;//flag txBuf[7] |= UPDATE_IS_HEX_FORMAT<<0;//TERRY WARNING pTxBuf += 1; *pTxBuf ++= (uint8_t)(md5Len>>8);//len *pTxBuf ++= (uint8_t)(md5Len); memcpy(&txBuf[8 + 2],md5Data,md5Len); pTxBuf += md5Len; *pTxBuf ++= (uint8_t)(PIECE_MAX_LEN>>8);//len *pTxBuf ++= (uint8_t)(PIECE_MAX_LEN); *pTxBuf ++= gizProtocolSum(txBuf , (dataLen+4)); ret = uartWrite(txBuf , (dataLen+4)); if(ret < 0) { GIZWITS_LOG("ERROR: uart write error %d \n", ret); return -2; } GIZWITS_LOG("MCU Ready To Update ROM \n"); return 0; } /** * @brief Pro_W2D_UpdateDataHandle * update Piece Handle , Judge Last Piece * @param[in] indata : Piece Data * @param[in] dataLen : Piece Length * @param[in] formatType : Piece Data Format * @param[out] * @return 0,Handle Success * -1,Input Param Illegal * -2,Last Piece , MD5 Check Faild * */ int8_t Pro_W2D_UpdateDataHandle(uint8_t *inData , uint32_t dataLen , otaDataType formatType) { uint16_t piecenum = 0; uint16_t piececount = 0; uint32_t tempWFlashAddr = 0; updataPieceData_TypeDef pieceData; uint8_t md5_calc[SSL_MAX_LEN];//MD5 Calculate Fact if(NULL == inData) { return -1; } memcpy((uint8_t *)&pieceData, inData, dataLen); piecenum = exchangeBytes(pieceData.piecenum); piececount = exchangeBytes(pieceData.piececount); GIZWITS_LOG("******piecenum = %d , piececount = %d, pieceSize = %d******** \r\n",piecenum,piececount,dataLen - 4); tempWFlashAddr = SYS_APP_BAK_SAVE_ADDR_BASE + (piecenum-1) * PIECE_MAX_LEN; wFlashData((uint8_t *)pieceData.piececontent , dataLen - 4, tempWFlashAddr); GAgent_MD5Update(&romUpdate.ctx, (uint8_t *)pieceData.piececontent, dataLen - 4); /*updata package data ,ack*/ if(piecenum == piececount) { memset(md5_calc,0,SSL_MAX_LEN); GAgent_MD5Final(&romUpdate.ctx, md5_calc); GIZWITS_LOG("MD5 Calculate Success , Will Check The MD5 ..\n "); if(0 != memcmp(romUpdate.update_param.ssl_data, md5_calc, SSL_MAX_LEN)) { GIZWITS_LOG("Md5_Cacl Check Faild ,MCU OTA Faild\r\n "); #ifdef PROTOCOL_DEBUG GIZWITS_LOG("Calc MD5: "); for(uint16_t i=0; i<SSL_MAX_LEN; i++) { GIZWITS_LOG("x ", md5_calc[i]); } GIZWITS_LOG("\r\n"); #endif #ifdef PROTOCOL_DEBUG GIZWITS_LOG("SSL MD5: "); for(uint16_t i=0; i<SSL_MAX_LEN; i++) { GIZWITS_LOG("x ", romUpdate.update_param.ssl_data[i]); } GIZWITS_LOG("\r\n"); #endif memset((uint8_t *)&romUpdate.update_param,0,sizeof(updateParamSave_t)); return -2; } else { GIZWITS_LOG("MD5 Check Success ,Storage ROM Success , Write Update Flag\n "); flash_erase(sizeof(updateParamSave_t) , UPDATE_PARAM_SAVE_ADDR_BASE); romUpdate.update_param.rom_statue = 0xEEEE; wFlashData((uint8_t *)&romUpdate.update_param, sizeof(updateParamSave_t), UPDATE_PARAM_SAVE_ADDR_BASE); //romUpdate.update_param.rom_statue = 0x1234; //printf("\n\romUpdate.update_param.rom_statue = X \r\n\n",romUpdate.update_param.rom_statue); //memset((uint8_t *)&romUpdate, 0 , sizeof(romUpdate)); //rFlashData((uint8_t *)&romUpdate, sizeof(romUpdate), UPDATE_PARAM_SAVE_ADDR_BASE); //printf("\n\romUpdate.update_param.rom_statue = X \r\n\n",romUpdate.update_param.rom_statue); GIZWITS_LOG("System Will Restart... \n"); /****************************MCU RESTART****************************/ mcuRestart(); /******************************************************************************/ //last package , updata ok //MD5 checkout :Failed clear updata,Success , write flash ,begin updata } } return 0; } /** * @brief Pro_D2W_UpdateSuspend * Data Receiver * @param[in] : Void * @param[out] : * @return : Void * */ void Pro_D2W_UpdateSuspend() { int32_t ret = 0; protocolCommon_t protocolCommon; memset(&protocolCommon, 0, sizeof(protocolCommon_t)); gizProtocolHeadInit((protocolHead_t *)&protocolCommon); protocolCommon.head.len = exchangeBytes(sizeof(protocolCommon_t)-4); protocolCommon.head.cmd = CMD_D_STOP_BIGDATA_SEND; protocolCommon.head.sn = gizwitsProtocol.sn++; protocolCommon.sum = gizProtocolSum((uint8_t *)&protocolCommon, sizeof(protocolCommon_t)); ret = uartWrite((uint8_t *)&protocolCommon,sizeof(protocolCommon_t)); if(ret < 0) { GIZWITS_LOG("ERROR: uart write error %d \n", ret); } gizProtocolWaitAck((uint8_t *)&protocolCommon,sizeof(protocolCommon_t)); } /** * @brief Pro_D2W_Ask_Module_Reboot * Ask Module Reboot * @param[in] : Void * @param[out] : * @return : Void * */ void Pro_D2W_Ask_Module_Reboot() { int32_t ret = 0; protocolCommon_t protocolCommon; memset(&protocolCommon, 0, sizeof(protocolCommon_t)); gizProtocolHeadInit((protocolHead_t *)&protocolCommon); protocolCommon.head.len = exchangeBytes(sizeof(protocolCommon_t)-4); protocolCommon.head.cmd = CMD_REBOOT_MODULE; protocolCommon.head.sn = gizwitsProtocol.sn++; protocolCommon.sum = gizProtocolSum((uint8_t *)&protocolCommon, sizeof(protocolCommon_t)); ret = uartWrite((uint8_t *)&protocolCommon,sizeof(protocolCommon_t)); if(ret < 0) { GIZWITS_LOG("ERROR: uart write error %d \n", ret); } gizProtocolWaitAck((uint8_t *)&protocolCommon,sizeof(protocolCommon_t)); }
更改软件版本号,更改后的版本号要比原先版本号大
gizwits_product.h
MCU OTA验证
一次用stlink烧录boot和app代码后,mcu日志如图
准备OTA,先让设备连上机智云
OTA成功