在线升级流程:stm32程序的第一条指令,存储地址的基地址0x8000000开始执行。IAP程序升级的执行是在BootLoader引导文件执行后,进行加载、跳转APP程序。所以每次上电后进入BootLoader判断是否升级,如果升级则将外部FLASH中的bin文件复制写入到ROM中,再接跳转,如果不升级则直接跳转app程序。 BootLoader和app程序的FLASH大小需要根据自己的程序情况自由的分配大小就可以了。 一、在线获取BIN文件 获取bin文件方式:设备端利用wifi或者gprs通讯模组,使用MQTT协议(基于TCP/IP),通过消息队列的方式接受服务端的升级命令。当接收到升级指令时,设备端会新增一个TCP/IP通道,专门用来传输bin文件数据,并将接收的bin文件存入外部flash中,同时将升级标志位置1。升级标志位存储在外部flash中。 二、IAP到APP应用程序的跳转 STM32的内部闪存(FLASH)地址起始于0x08000000,一般情况下,程序文件就从此地址开始写入。 STM32F103ZET6的内部闪存(FLASH)地址为0x8000000-0x0807ffff,一共512KB空间。将512KB的空间分成两块,一块存放BootLoader的程序,用来判断升级跳转APP,根据实际代码量来合理分配空间。本例程BootLoader分配了0x8000000-0x0800ffff的地址,64KB大小的空间。用户程序APP地址为0x8010000-0x807ffff。 IAP(BootLoader)程序运行先判断外部flash的升级标志位是否有置1,若为1,将外部flash存储的bin文件复制到内部ROM中,同时将升级标志位清0。然后判断写入ROM的文件是否为正确的应用程序,再完成跳转。 三、IAP程序代码 1.读取升级标志位和bin文件的大小 // 读升级标志位 W25QXX_Read(ADDR_UPDATE_TAG,tagBuff1,1); printf(' ADDR_UPDATE_TAG is 0x%04X \r\n',tagBuff1[0]); delay_ms(100); // 读bin文件长度 W25QXX_Read(ADDR_UPDATE_length,lengthBuff,3); for(i=0;i<3;i++) { len=(u32)lengthBuff[0]<<16; len|=(u32)lengthBuff[1]<<8; len|=(u32)lengthBuff[2]<<0; } printf(' len is %d \r\n',len); 2.判断是否升级。如果升级则开始将外部flash的程序复制到内部ROM中 // 如果升级标志位等于1开始转移程序 if(tagBuff1[0]==0x31) { u16 filelen=0; u16 filepacketnum=0; u16 remain = 0; filepacketnum=len/2048; if(len%2048) filepacketnum++; for(i=0; i<filepacketnum; i++) { if(len / 2048) { remain= 2048; } else { remain= len % 2048; } printf('read flash to rBuff1 \r\n'); W25QXX_Read(ADDR_CODE+i*2048,rBuff1,remain); //判断是否为flash应用程序,如果是就执行更新,如果不是不更新 if(i==0) { printf('Theaddress of 20001000 is %08x\r\n',(*(vu32*)0X20001000)); if(((*(vu32*)(0X20001000+4))&0xFF000000)==0x08000000) { printf('UpdateFirmware...\r\n'); } else { printf('Non-flashapplications!\r\n'); tagBuff1[0]=0x30; W25QXX_Write(ADDR_UPDATE_TAG,tagBuff1,1); break; } } //如果是应用程序,将数据写入STMflash iap_write_appbin(FLASH_APP1_ADDR+i*2048,rBuff1,remain); filelen -= 2048; } 3.升级到内部ROM完成,将标志位清除 if(i == filepacketnum) { printf('-----Updatecompleted-------!\r\n'); tagBuff1[0]=0x30; W25QXX_WAKEUP(); W25QXX_Erase_Sector(ADDR_UPDATE_TAG); W25QXX_Write(ADDR_UPDATE_TAG,tagBuff1,1); //测试写入标志位是否成功 W25QXX_Read(ADDR_UPDATE_TAG,tagBuff2,1); printf(' ADDR_UPDATE_TAGis 0x%04X \r\n',tagBuff2[0]); delay_ms(200); } 4.升级完成后判断写入的bin文件是否正确,若正确则跳转到FLASH_APP1_ADDR delay_ms(200); printf('Judgingwhether the application is correct!\r\n'); if(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000) { printf('Firmware packagecorrect!\r\n'); iap_load_app(FLASH_APP1_ADDR); }else { printf('Firmware packageerror\r\n'); } 5.跳转之前,需要将使用过的外设关闭,如中断定时器串口等 voidiap_load_app(u32 appxaddr) { if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000)//检查栈顶地址是否合法 { jump2app=(iapfun)*(vu32*)(appxaddr+4); MSR_MSP(*(vu32*)appxaddr); __disable_irq(); TIM_DeInit(TIM3); USART_DeInit(USART1); SPI_I2S_DeInit(SPI1); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF,DISABLE); GPIO_DeInit(GPIOF); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG,DISABLE); GPIO_DeInit(GPIOG); jump2app(); } } 注意: (1) __disable_irq(); TIM_DeInit(TIM3); USART_DeInit(USART1); SPI_I2S_DeInit(SPI1); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF,DISABLE); GPIO_DeInit(GPIOF); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG,DISABLE); GPIO_DeInit(GPIOG); 跳转前,使用过的定时器、中断、串口、GPIO都需要关闭,否则跳转会失败。只关闭了部分,可能能跳转,但是会有一定概率的产生死机,多次复位才能正常运行。 (2) u8 rBuff1[2048]__attribute__((at(0X20001000))); if(((*(vu32*)(0X20001000+4))&0xFF000000)==0x08000000) 定义了数组分配的内存地址是0X20001000起,用来从外部flash向stmflash转移bin文件。 而bin文件的第4-7的4个字节的值是app程序复位中断向量的值。固定格式是08xxxxxxxx。 (3) #defineFLASH_APP1_ADDR 0x08010000 if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000)//检查栈顶地址是否合法 即取0x80010000开始到0x8010003的4个字节的值,因为我们的应用程序APP中设置把中断向量表放置在0x08010000开始的位置; 而中断向量表里第一个放的就是栈顶地址的值。这句话即通过判断栈顶地址值是否正确来判断是否应用程序已经下载了,因为应用程序的启动文件刚开始就去初始化化栈空间,如果栈顶值对了,说明应用程序已经下载了启动文件,初始化也执行了。 四、APP程序代码 1.通过JSON解析MQTT消息队列下发的升级命令 CLR_CMD_UPDATE_BIN,获取bin文件包的信息。 case CLR_CMD_UPDATE_BIN: updateBinVersion = cJSON_GetObjectItem(pRoot,'version')->valuestring; updateBinFileName = cJSON_GetObjectItem(pRoot,'fileName')->valuestring; countPackage = atoi(cJSON_GetObjectItem(pRoot,'countPackage')->valuestring); pJsonNode = cJSON_GetObjectItem(pRoot, 'totalLength');//实际文件大小 totalLength = atoi(pJsonNode->valuestring); updateBinPort = cJSON_GetObjectItem(pRoot, 'fileServerPort')->valuestring; pJsonNode = cJSON_GetObjectItem(pRoot, 'fileServerIp'); if (pJsonNode != NULL) { updateBinIp = pJsonNode->valuestring; rt_kprintf('version:%s\n', updateBinVersion); rt_kprintf('IP:%s\n', updateBinIp); rt_kprintf('Port:%s\n', updateBinPort); rt_kprintf('updateBinFilenName:%s\n', updateBinFileName); rt_kprintf('countPackage:%d\n', countPackage); rt_kprintf('totalLength:%d\n', totalLength); param_buf.ip = updateBinIp; param_buf.port = updateBinPort; param_buf.fileName = updateBinFileName; param_buf.countPackage = countPackage; param_buf.totalLength = totalLength; 2. 关闭原有的TCP通道,删除GPRS任务(本次使用的是GPRS模组),创建新的TCP任务新建TCP连接(此TCP在本例中专用于升级BIN)。 close_tcp(); /********* delete mqtt thread ************/ rt_thread_delay(50); rt_err_t rt_err; rt_err = rt_thread_delete(gprs_thread); #ifdef KT_PRINTF_DEBUG if (rt_err != RT_EOK) { rt_kprintf('\r\n mqtt task delete failed!\r\n'); } else { rt_kprintf('\r\n mqtt task delete successed!\r\n'); /********* 创建 tcp 任务 ************/ tcp_thread =rt_thread_create('tcp', tcp_thread_entry, /* tcp_thread_entry */ RT_NULL, /* 入口参数是RT_NULL */ 2048, /* 分配空间大小 */ 1, /* 任务优先级 */ 20); /* 时间片 */ if (tcp_thread != RT_NULL) rt_thread_startup(tcp_thread); else rt_kprintf('\r\n createtcp_thread failed!!\r\n'); } #endif } break; 3. 接收bin文件,存入外部flash中,接收完将升级标志位置1 //擦除bin存储地址 for(int i = 0; i < countPackage; i++) { int offset = i * 0x1000; W25Q128FV_Erase_Sector((Addr_UPDATE_BIN + offset) / 4096); } //接收数据包 for(int j = 1; j <= countPackage; j++) { TCP_DATA_TRANSFORM(j, updateBinFileName); rt_thread_delay(50); } //将升级标志1存入flash中 uint8_t *tag = '1'; W25Q128FV_Erase_Sector(ADDR_UPDATE_TAG / 4096); SPI_FLASH_PageWrite(tag, ADDR_UPDATE_TAG, 1); uint8_t rBuff[1]; SPI_FLASH_Read(rBuff, ADDR_UPDATE_TAG, 1); rt_kprintf(' ADDR_UPDATE_TAG is %s \r\n', rBuff); //将bin包大小存入flash中 intptl = param_buf.totalLength; uint8_t d[3]; d[0]= ptl >> 16 & 0xff; d[1] = ptl >> 8 & 0xff; d[2] = ptl & 0xff; SPI_FLASH_PageWrite( d, ADDR_UPDATE_TAG + 1, 3); //断开tcp连接,关闭看门狗电路让单片机断电复位复位 disconnectToServer(); rt_kprintf('^^^^^^^^^^^^^^^^^^^^^^^^^^^tcp_thread is died!^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n'); rt_thread_delete(watchdog_thread); 4.接收单个bin数据分包 TCP数据单包交互方法 [0],[1] => 当前包序号 [2],[3] => 当前包长度 [4].... => 包内容 末尾4位是crc校验位 1、组需要发送的数据包 2、通过串口发送数据包 3、接受服务器返回的数据包 4、对比从串口获取的数据包实际长度和 从服务器数据包内定义的长度, 不相等则重发 int返回值一次读取的数据长度 int TCP_DATA_TRANSFORM(int packageIndex,char *fileName) { buildSendP ackage((char*)content, packageIndex, fileName); rt_kprintf(' \r\n content is %s \r\n', content); transport_gprs_sendPacketBuffer(content, strlen((char *)content)); intpIndex, pLength; wait_for_tcp_data(CRT_HEAD_LENGTH, 10); char *buf = buf_uart2.buf; memcpy(tcpReceiveBuf, buf + 11, buf_uart2.index); //包序号 pIndex = (tcpReceiveBuf[0] & 0xff) << 8 | tcpReceiveBuf[1]& 0xff; //包长度 pLength = (tcpReceiveBuf[2] & 0xff) << 8 | tcpReceiveBuf[3]& 0xff; rt_kprintf(' \r\n server saypIndex is _____%d____ pLength is ____%d____ \r\n', pIndex,pLength); unsigned char *reBuf = tcpReceiveBuf; uint32_t deCodeCRC = my_crc32(reBuf, pLength - 4); char crcStr[4] = {0}; unsigned char crcArray[4] = {tcpReceiveBuf[pLength - 4],tcpReceiveBuf[pLength - 3], tcpReceiveBuf[pLength - 2], tcpReceiveBuf[pLength -1]}; byteArrayToHexStr(crcArray, crcStr); uint32_t serverCRC = htoi(crcStr); rt_kprintf(' \r\n deCodeCRC is %d , serverCRC is %d \r\n', deCodeCRC,serverCRC); if(serverCRC > 0 && deCodeCRC > 0 && serverCRC ==deCodeCRC) { // for (int i = 4; i < pLength; i++) // { // rt_kprintf('%02x',tcpReceiveBuf[i]); // } // rt_kprintf('\r\n'); int pOffset = 0X400 * (packageIndex - 1); SPI_FLASH_PageWrite(reBuf + 4, Addr_UPDATE_BIN + pOffset, 1024); ////////////////////////////// uint8_t binBuff2[256]; SPI_FLASH_Read(binBuff2, Addr_UPDATE_BIN + pOffset, 256); rt_kprintf(' \r\n binBuff2 is \r\n'); for (size_t i = 0; i < 256; i++) { rt_kprintf('%02x', binBuff2[i]); } rt_kprintf('\r\n'); return pLength - 8; } else { return TCP_DATA_TRANSFORM(packageIndex, fileName); } } 五、APP程序配置 中断向量偏移设置 修改代码存放的地址空间
六、服务器侧,程序代码 1.立即执行固件更新 1、查询数据库bin 2、获取所有设备sn列表 3、通知设备开始更新了,内容包括:文件名,大小,版本号 */ @RequestMapping(value = '/update') @ResponseBody public Object update(LongdeviceBinId) { DeviceBinbin = deviceBinService.selectById(deviceBinId); if(bin == null) throw newGunsException(BizExceptionEnum.UPDATE_BIN_NOT_EXISTED); if(MinaConfig.fileCacheInit(bin)) { deviceBinService.setBinActive(deviceBinId); }else throw newGunsException(BizExceptionEnum.UPDATE_BIN_FAILD); List<byte[]>cacheList = CacheKit.get(Cache.BIN_GROUP, CacheKey.BIN_NAME); StringfilenName = bin.getFileName(); Stringversion = filenName.substring(filenName.indexOf('_') + 1,filenName.lastIndexOf('.')); StringcountPackage = String.valueOf(cacheList.size()); StringtotalLength = String.valueOf(bin.getFileData().length); StringfileServerIp = env.getProperty('spring.tcp.host'); StringfileServerPort = env.getProperty('spring.tcp.port'); JSONObjectobj = new JSONObject(); obj.put('method','updateBin'); obj.put('fileName',filenName); obj.put('version',version); obj.put('countPackage',countPackage); obj.put('totalLength',totalLength); obj.put('fileServerIp',fileServerIp); obj.put('fileServerPort',fileServerPort); logger.info('更新固件下发命令:{}',obj.toJSONString()); List<Device>list= deviceService.selectList(newEntityWrapper<Device>().eq('is_onLine', 1)); Message<String>message; Device device; for(int i = 0,size=list.size() ; i < size; i++) { device = list.get(i); if (device != null &&StringUtils.isNotBlank(device.getSnNum())) { message= MessageBuilder.withPayload(obj.toJSONString()) .setHeader(MqttHeaders.TOPIC,MqttConst.getCourrentIssueCmd(device.getSnNum())).build(); mqtt.handleMessage(message); if(i%10== 0) { try {Thread.sleep(1000);} catch(InterruptedException e) {e.printStackTrace();} } } } returnSUCCESS_TIP; } 2.分片bin数据包,存入数据缓存 2 + 2 + data + 4 分包序号 +发送数据包总长度 + 数据包 + crc校验码 * *@param bin */ public static booleanfileCacheInit(DeviceBin bin) { intfileSizeSlice = FILE_SLICE_SIZE; // 分片大小等于1k if(bin != null) { byte[] bytes = bin.getFileData(); int bytesSize = bytes.length; int totalDataPackage = (int)Math.ceil((float) bytesSize / fileSizeSlice); logger.info('需要发送的总包数:' + totalDataPackage); int currentFileSizeSlice, offsize,floorDataPackage, j, sliceIndex, packageLength, crcLength = 4; floorDataPackage = bytesSize /fileSizeSlice; List<byte[]> bufferArray = new ArrayList<>(128); for (int i = 1; i <= totalDataPackage;i++) { currentFileSizeSlice= fileSizeSlice;// 当前文件分包大小 if(i > floorDataPackage) { currentFileSizeSlice = bytesSize %fileSizeSlice; } byte[]preSendData = new byte[2 + 2 + currentFileSizeSlice]; // 低位在前,高位在后 // 分包序号 sliceIndex= i; j =0; preSendData[j++]= (byte) ((sliceIndex >> 8) & 0xff); preSendData[j++]= (byte) (sliceIndex & 0xff); // 分包长度 packageLength= preSendData.length + crcLength; preSendData[j++]= (byte) ((packageLength >> 8) & 0xff); preSendData[j++]= (byte) (packageLength & 0xff); offsize= fileSizeSlice * (i - 1);// 分包偏移量 System.arraycopy(bytes,offsize, preSendData, j, currentFileSizeSlice); //crc32校验 CRC32crc32 = new CRC32(); crc32.update(preSendData); ByteBufferbuffer = ByteBuffer.allocate(8); buffer.putLong(crc32.getValue()); logger.info('packageindex {},crc32 code is {}', i, crc32.getValue()); byte[]sendData = new byte[packageLength]; System.arraycopy(preSendData, 0, sendData, 0, preSendData.length); System.arraycopy(buffer.array(),4, sendData, preSendData.length, crcLength); bufferArray.add(sendData); } CacheKit.put(Cache.BIN_GROUP,CacheKey.BIN_NAME, bufferArray); logger.info('bin file cached success!'); return true; } returnfalse; } 3.往设备端发送bin的分包 public void messageReceived(IoSessionsession, Object message) throws Exception { StringstrMsg = message.toString(); logger.info('服务端接收到的数据为: {}',strMsg); if(strMsg.trim().equalsIgnoreCase(EXIT)) { session.closeOnFlush(); return; } JSONObjectobj = JSON.parseObject(strMsg); //String fileName = obj.getString('fileName'); intpackageIndex = obj.getIntValue('packageIndex'); List<byte[]>cacheList = CacheKit.get(Cache.BIN_GROUP, CacheKey.BIN_NAME); if(packageIndex > cacheList.size()) { return; } byte[]buf = cacheList.get(packageIndex - 1); IoBufferio = IoBuffer.wrap(buf, 0, buf.length); session.write(io); } 作者:冯美文、应晓川 |
|