在線升級流程:stm32程序的第一條指令,存儲地址的基地址0x8000000開始執(zhí)行。IAP程序升級的執(zhí)行是在BootLoader引導文件執(zhí)行后,進行加載、跳轉APP程序。所以每次上電后進入BootLoader判斷是否升級,如果升級則將外部FLASH中的bin文件復制寫入到ROM中,再接跳轉,如果不升級則直接跳轉app程序。 BootLoader和app程序的FLASH大小需要根據(jù)自己的程序情況自由的分配大小就可以了。 一、在線獲取BIN文件 獲取bin文件方式:設備端利用wifi或者gprs通訊模組,使用MQTT協(xié)議(基于TCP/IP),通過消息隊列的方式接受服務端的升級命令。當接收到升級指令時,設備端會新增一個TCP/IP通道,專門用來傳輸bin文件數(shù)據(jù),并將接收的bin文件存入外部flash中,同時將升級標志位置1。升級標志位存儲在外部flash中。 二、IAP到APP應用程序的跳轉 STM32的內部閃存(FLASH)地址起始于0x08000000,一般情況下,程序文件就從此地址開始寫入。 STM32F103ZET6的內部閃存(FLASH)地址為0x8000000-0x0807ffff,一共512KB空間。將512KB的空間分成兩塊,一塊存放BootLoader的程序,用來判斷升級跳轉APP,根據(jù)實際代碼量來合理分配空間。本例程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應用程序,如果是就執(zhí)行更新,如果不是不更新 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; } } //如果是應用程序,將數(shù)據(jù)寫入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)//檢查棧頂?shù)刂肥欠窈戏?/p> { 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都需要關閉,否則跳轉會失敗。只關閉了部分,可能能跳轉,但是會有一定概率的產(chǎn)生死機,多次復位才能正常運行。 (2) u8 rBuff1[2048]__attribute__((at(0X20001000))); if(((*(vu32*)(0X20001000+4))&0xFF000000)==0x08000000) 定義了數(shù)組分配的內存地址是0X20001000起,用來從外部flash向stmflash轉移bin文件。 而bin文件的第4-7的4個字節(jié)的值是app程序復位中斷向量的值。固定格式是08xxxxxxxx。 (3) #defineFLASH_APP1_ADDR 0x08010000 if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000)//檢查棧頂?shù)刂肥欠窈戏?/p> 即取0x80010000開始到0x8010003的4個字節(jié)的值,因為我們的應用程序APP中設置把中斷向量表放置在0x08010000開始的位置; 而中斷向量表里第一個放的就是棧頂?shù)刂返闹怠_@句話即通過判斷棧頂?shù)刂分凳欠裾_來判斷是否應用程序已經(jīng)下載了,因為應用程序的啟動文件剛開始就去初始化化??臻g,如果棧頂值對了,說明應用程序已經(jīng)下載了啟動文件,初始化也執(zhí)行了。 四、APP程序代碼 1.通過JSON解析MQTT消息隊列下發(fā)的升級命令 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模組),創(chuàng)建新的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'); /********* 創(chuàng)建 tcp 任務 ************/ tcp_thread =rt_thread_create('tcp', tcp_thread_entry, /* tcp_thread_entry */ RT_NULL, /* 入口參數(shù)是RT_NULL */ 2048, /* 分配空間大小 */ 1, /* 任務優(yōu)先級 */ 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); } //接收數(shù)據(jù)包 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數(shù)據(jù)分包 TCP數(shù)據(jù)單包交互方法 [0],[1] => 當前包序號 [2],[3] => 當前包長度 [4].... => 包內容 末尾4位是crc校驗位 1、組需要發(fā)送的數(shù)據(jù)包 2、通過串口發(fā)送數(shù)據(jù)包 3、接受服務器返回的數(shù)據(jù)包 4、對比從串口獲取的數(shù)據(jù)包實際長度和 從服務器數(shù)據(jù)包內定義的長度, 不相等則重發(fā) int返回值一次讀取的數(shù)據(jù)長度 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.立即執(zhí)行固件更新 1、查詢數(shù)據(jù)庫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('更新固件下發(fā)命令:{}',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數(shù)據(jù)包,存入數(shù)據(jù)緩存 2 + 2 + data + 4 分包序號 +發(fā)送數(shù)據(jù)包總長度 + 數(shù)據(jù)包 + 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('需要發(fā)送的總包數(shù):' + 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.往設備端發(fā)送bin的分包 public void messageReceived(IoSessionsession, Object message) throws Exception { StringstrMsg = message.toString(); logger.info('服務端接收到的數(shù)據(jù)為: {}',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); } 作者:馮美文、應曉川 |
|