更新
0924,一些潜在的bug解决方案
前言
这周一直想做一个IAP固件升级的上位机,然后把升级流程全都搞懂。
我用的单片机型号是STM32F103VET6,FLASH容量是512K,FLASH单页是2K。
有纰漏请指出,转载请说明。
学习交流请发邮件 1280253714@qq.com
IAP原理
IAP的原理我就不多赘述了,这里贴上几位大佬的文章
STM32CubeIDE IAP原理讲解,及UART双APP交替升级IAP实现-CSDN博客
STM32 IAP升级固件 + 上位机 例程 | 码农家园
IAR环境下STM32+IAP方案的实现
之前做过IAP,也讲解了一些存在的问题,参考之前我写的博客
单片机IAP升级的一些问题与经验_iap更新_TianYaKe-天涯客的博客-CSDN博客
0923初版
Qt读取二进制文件
读取二进制文件,将内容放在binRawData里
void MainWindow::readFw()
{
QFileDialog dlg(this);
QString fileName = dlg.getOpenFileName(this, tr("Open"), "./", tr("Bin File(*.bin)"));
if( fileName == "" )
{
return;
}
QFile file(fileName);
QFileInfo fileInfo(fileName);
fwFileLen = fileInfo.size();
fwPackNum = fwFileLen/fwPackLength + 1;
if(file.open(QIODevice::ReadOnly))
{
binRawData = file.readAll();
ui->lineEdit_fwUpdateFile->setText(fileName);
ui->textEdit_fwUpdateFile->append(binRawData.toHex());
file.close();
ui->pushButton_startFwUpdate->setEnabled(true);
ui->pushButton_stopFwUpdate->setEnabled(false);
}
else
{
QMessageBox::warning(this, tr("Error"), tr("Fail to open file!"));
}
}
将binRawData拆包,并调用串口发送
connect(fwUpdateTimer,&QTimer::timeout,[=](){
if(fwUpdateState == 1)
{
QByteArray fwSendBuff = binRawData.mid(fwPackIndex*fwPackLength+1,fwPackLength);
fwPackIndex++;
serialPort->write(fwSendBuff);
if(fwPackIndex>fwPackNum)
{
fwUpdateTimer->stop();
fwUpdateState = 3;
}
}
});
加上固件传输的协议
发送开始指令,发送固件包大小
void MainWindow::startFwUpdate()
{
ui->pushButton_startFwUpdate->setEnabled(false);
ui->pushButton_stopFwUpdate->setEnabled(true);
fwUpdateState = fwStart;
QByteArray startCmd;
uchar startCmd1 = 0xAB;
uchar startCmd2 = 0xf0;
startCmd = loadTxMsg(startCmd1, startCmd2, &startCmd);
serialPort->write(startCmd);
delay_ms(1000);
uchar cmd1 = 0xAB;
uchar cmd2 = 0xf1;
uchar uData[2];
uint16_t u16FwPackNum = fwPackNum;
*(uint16_t *)&uData[0] = *(uint16_t *)&u16FwPackNum;
QByteArray txFwData;
txFwData.append(uData[0]);
txFwData.append(uData[1]);
txFwData = loadTxMsg(cmd1, cmd2, &txFwData);
serialPort->write(txFwData);
fwUpdateTimer->start(100);
}
通过定时器逐帧传输,传输结束后发送结束信号
connect(fwUpdateTimer,&QTimer::timeout,[=](){
if(fwUpdateState == fwStart)
{
QByteArray fwSendBuff = binRawData.mid(fwPackIndex*fwPackLength,fwPackLength);
fwSendBuff.insert(0,fwSendBuff.length());
QByteArray fwSendProtocolBuff = loadFwPackData(&fwSendBuff);
serialPort->write(fwSendProtocolBuff);
fwPackIndex++;
QString fwDataString = ByteArrayToHexString(fwSendProtocolBuff).toLatin1();
ui->textEdit_fwInfo->clear();
ui->textEdit_fwInfo->setWordWrapMode(QTextOption::WordWrap);
ui->textEdit_fwInfo->insertPlainText(QString("["));
ui->textEdit_fwInfo->insertPlainText(QString::number(fwPackIndex));
ui->textEdit_fwInfo->insertPlainText(QString("] "));
ui->textEdit_fwInfo->insertPlainText(fwDataString);
if(fwPackIndex>=fwPackNum)
{
fwUpdateState = fwComplete;
fwUpdateTimer->stop();
QByteArray stopCmd;
uchar stopCmd1 = 0xAB;
uchar stopCmd2 = 0xf3;
stopCmd = loadTxMsg(stopCmd1, stopCmd2, &stopCmd);
serialPort->write(stopCmd);
}
}
});
STM32代码部分
iap.h
#ifndef __IAP_H
#define __IAP_H
#include "includes.h"
#define __APP_START_ADDR 0x08010000U
#define __APP_SIZE 0x10000U
typedef enum
{
IAP_START,
IAP_TRANFER,
IAP_COMPLETE,
} IAP_Status;
typedef struct
{
u8 u8Length; // 当前接受到数据帧的帧长
u8 u8Data[64]; // 当前接受到的数据
} RcvFrame_S;
typedef struct
{
IAP_Status state; // ipa升级当前状态
RcvFrame_S stRcvFrame; // 接受到的数据
u16 u16FwFrameNum; // 固件数据帧总量
u16 u16FwFrameIndex; // 固件数据帧偏移
u32 u32WriteAddrIndex; // 写地址偏移
} IAP_S;
extern IAP_S stIap;
void IapRcvDataProc(u8 *MsgData);
typedef void (*Application)(void);
void JumpToApplication(void);
#endif //__IAP_H
iap.c
#include "includes.h"
IAP_S stIap;
void IapRcvDataProc(u8 *MsgData)
{
u8 cmd = MsgData[3];
u8 i = 0;
switch(cmd)
{
case 0xF1:
EraseFwSpace(__APP_START_ADDR,__APP_SIZE/__FLASH_PAGE_SIZE);
memcpy(&stIap.u16FwFrameNum, &MsgData[4], 2);
break;
case 0xF2:
stIap.u16FwFrameIndex++;
stIap.stRcvFrame.u8Length = MsgData[6];
memcpy(&stIap.stRcvFrame.u8Data, &MsgData[7], stIap.stRcvFrame.u8Length);
for(i = 0; i < stIap.stRcvFrame.u8Length; i += 4) //一次写入是4个字节
{
FlashWriteWord(__APP_START_ADDR+stIap.u32WriteAddrIndex, *(u32 *)&stIap.stRcvFrame.u8Data[i]);
stIap.u32WriteAddrIndex += 4; //写入的地址加4
}
break;
case 0xF3:
JumpToApplication();
}
}
void JumpToApplication(void)
{
Application application;
__set_FAULTMASK (1);
application = (Application)(*(__IO u32*)(__APP_START_ADDR+4));
__set_MSP(*(__IO u32*)(__APP_START_ADDR));
SCB->VTOR = __APP_START_ADDR;
application();
}
视频演示
IAP固件升级(Qt上位机)最初版0923_哔哩哔哩_bilibili
IAP固件升级(Qt上位机)最初版0923
0924更新
之前的演示,终于能把新固件下载到单片机,并在单片机上面跑新固件。
可是有很多问题待解决
1.上位机怎么知道单片机接收到了正确的数据?
2.如果单片机接收的固件大于APP空间,该怎么处理?
3.升级过程,如果数据传输的通路断掉了(单片机挂了,上位机崩溃了,连接断开了),该怎么处理?
5.如果待传输的新固件与当前的固件是同一个版本,还有必要传输吗?
6.之前的演示只能跑新固件,但是没有预留升级的接口,如何在APP写一份接口,支持可重复升级?
7.上位机怎么知道单片机固件升级结束?
待我一一道来
1.上位机怎么知道单片机接收到了正确的数据?
这个问题可难可简,最常见的是加校验,之前已经做了串口数据帧的校验,基本上是不会出错了。但如果要保证数据传输的准确性,还需要加一种校验,可以是CRC32、奇偶校验。如果还要进一步保证整个固件的准确性,需要再加一个文件校验可以是 MD5、SHA1等等。
这部分的工作由网上各位大神来做,我这里只提一嘴。
[CRC校验]手算与直观演示_哔哩哔哩_bilibili
MD5为何不再安全_哔哩哔哩_bilibili
2.如果单片机接收的固件大于APP空间,该怎么处理?
这种要看情况,如果只是用户给APP分配过小,可以考虑增大APP空间;如果是有两个APP代码,可以考虑空间合并,只跑一份APP。
3.升级过程,如果数据传输的通路断掉了(单片机挂了,上位机崩溃了,连接断开了),该怎么处理?
参考我之前写的博客
单片机IAP升级的一些问题与经验_iap更新_TianYaKe-天涯客的博客-CSDN博客
5.待传输的新固件与当前的固件是同一个版本,还有必要传输吗?
首先这个问题很容易回答,没必要,但这不是关键。
这里有几个问题,
怎么知道新老固件的版本号?
版本号如何比对?
如何在传输过程中附带版本号信息?
我的解决办法是,在固件传输前,也就是上位机给单片机发开始传输的起始信号时,附带上版本号信息。然后单片机接受到后,跟存放在用户FLASH空间的版本号进行比对。
6.如何在APP写一份接口,支持可重复升级?
最简单的办法是,APP只做固件版本号判断和跳转到IAP的升级接口。
7.上位机怎么知道单片机固件升级结束?
最保险的办法是,在IAP跳转到APP后,由APP发送升级结束的信号。
不然可能出现IAP全部接收到固件,但是出现跳转失败的情况。