利用Qt制作IAP上位机

最近项目没那么紧,因此利用QT进行IAP上位机的制作,IAP其实主要就是将bin文件或者hex文件的数据通过串口发给下位机的updataInfo区域即可,发送完成后,下位机进行系统复位,通过bootloader将刚才发送的数据搬运到正常工作的Info区域,同时擦除刚才的updataInfo即可,这样系统就可以进行升级,IAP主要的优势就是不进行拆机就可以升级,你想想如果一百台机器让你拆机进行升级,多痛苦啊!其实固件更新的原理也是一样,只不过这个更新文件(特制bin文件)是通过WIFI,将更新数据打包分帧进行下发,写入到updataInfo,好了扯远了,知道了原理,那编写的上位机其实就是将bin文件或者hex文件读取回来,然后打包发送,如果,你不管下位机是否接收到数据,你可以酷酷发,虽然也可以成功,但是遇到意外,就要拆机重新烧程序了,因此,保守的方法还是应答式,上位机发送一帧数据,下位机就回复上位机是否接受到,如果下位机接收到了回复正确的信息了,那我们再继续发送,循环往复直到最后一帧数据。

IAP协议格式
帧头指令1指令2数据长度数据域校验和
PC请求0xF10xAA0x010xFF(1025)
下位机应答0xF20xAA0x010x01帧号

对于一些简单的串口设置的代码就不写了,主要提供一下一些重要的相关代码;

首先打开文件的同时获取下位机的版本号;

void MainWindow::on_OpenFileBt_clicked()
{
    fileName = QFileDialog::getOpenFileName(this,"Open File",QDir::currentPath(), "Hex文件 (*.hex);;Binary文件 (*.bin);;所有文件(*.*)");
    ui->filePathLineEdit->setText (fileName);
    if(fileName.isEmpty())
    {
        QMessageBox::information(this,"Error Message", "Please Select a Text File");
        return;
    }
    QFileInfo *pcsfileInfo = new QFileInfo(fileName);
    binSize =  static_cast<int>(pcsfileInfo->size());
    delete pcsfileInfo;


    ui->SendEdit->clear();
    ui->SendEdit->setText("F1 88 00 01 00 89");//获取下位机的版本号
    SendInformationFunction();

}

然后就可以点发送按钮了,这里面的函数较多,且听我一一道来,在说明之前,先说一下我们命名文件版本格式:项目+名称+版本号(版本号格式0.0.0.0);eg:HC2024_Project_1.0.0.0.hex

void MainWindow::on_SendHexFileBt_clicked()
{
    //获取此次IAP的版本号
    QFileInfo fileInfo(fileName);
    qDebug()<<"fileName:"<<fileName;
    QString IAPVersion = fileInfo.fileName();
    QString BinVersion;
    QString BinVersionNum;
    QRegularExpression re("([A-Za-z0-9_]+)_(\\d+\\.\\d+\\.\\d+\\.\\d+)");
    QRegularExpressionMatch IAPmatch = re.match(IAPVersion);
    if (IAPmatch.hasMatch()) {
        BinVersion = IAPmatch.captured(0); // Whole match  不含checksum的名称
        BinVersionNum = IAPmatch.captured(2); // Capturing group 2 - version number
        qDebug() << "BinVersion: " << BinVersion;
        qDebug() << "BinversionNum: " << BinVersionNum;
    }

    //获取MCU版本号

    qDebug() << "MCUVersion: " << MCUVersion;
    QRegularExpressionMatch MCUmatch = re.match(MCUVersion);
    QString MCUVersionNum = MCUmatch.captured(2);
    qDebug() << "MCUVersionNum: " << MCUVersionNum;

    //比较两个版本是否一样
    bool CanUpgrade = canUpgrade(MCUVersionNum,BinVersionNum);

    if(CanUpgrade == true){

        UpgrateStatus = 1;
        ui->statusBar->showMessage(tr("正在更新"));
        qDebug() << "CanSendUpdata" ;
        emit CanSendUpdata();

    }else {
       QMessageBox::information(this,"提示","请使用合适、规范的文件名及Hex文件进行升级");
       return;
    }

}

判断完版本号(此函数自己写,每个人的版本号编码不一样),如果需要更新,就可以发送更新内容了,所以emit CanSendUpdata();连接一下槽函数SendUpDataFunction();

void MainWindow::SendUpDataFunction(void){
    qDebug() << "SendUpDataFunction" ;
    QFile* file = new QFile;
    fileData.clear();
    file->setFileName(fileName);
    //获取是什么类型文件bin还是hex
    QFileInfo fileInfo(fileName);
    QString DocumentType = fileInfo.suffix();
    QTextStream in(file);

    if(file->open(QIODevice::ReadOnly) == true){
        qDebug() << "File opened successfully";

        if(DocumentType.toLower() == "hex"){

            while (!in.atEnd()) {
                QString line = in.readLine();
                // 假设hex文件的每一行格式为:HHAAAATTHHHH
                // 其中HH为地址,TT为类型,HHH为数据
                QString DeleteLine = line.mid(7, 2);
                if(DeleteLine == "00"){
                    QString data = line.mid(9, line.length() - 11); // 去除地址后的数据
                    fileData.append(QByteArray::fromHex(data.toLatin1())); // 将hex字符串转换为字节数组
                }else{}

            }

        }else if((DocumentType.toLower() == "bin")){
            fileData = file->readAll();
        }else {}

//        QFile outFile("C:\\Users\\hanqi.zhang\\Desktop\\outputFile.bin");
//        if(outFile.open(QIODevice::WriteOnly)) {
//            outFile.write(fileData);
//            outFile.close();

//        }
        qDebug() << "fileDataSize: "<<fileData.size();

    }
    file->close();


    if(fileData.size()%1024 == 0)
    {
        m_totalSize = fileData.size()/1024;
    }
    else {
        m_totalSize = fileData.size()/1024+1;
    }
    m_bytesSent = 0;

    sendNextChunk();

}

发送数据的函数,根据自己的协议自行修改

void MainWindow::sendNextChunk() {

    if (m_bytesSent < m_totalSize) {
        qDebug()<<"m_totalSize"<< m_totalSize;
        s_buffer = fileData.mid(m_bytesSent*1024,1024);
        s_buffer.resize(1024);//目的是给最后一帧会补零
//        m_buffer.prepend("\x01");
        s_buffer.prepend(static_cast<char>(m_bytesSent+1));
        qDebug()<<"帧号"<<QByteArray::number(m_bytesSent + 1);
        s_buffer.prepend('\xFF');
        s_buffer.prepend('\x01');
        s_buffer.prepend('\xAA');
        s_buffer.prepend('\xF1');

        m_checksum = 0;
        qDebug()<<"s_buffer.size():"<<s_buffer.size();
        for (int j = 1; j < s_buffer.size(); j++) {
            m_checksum += s_buffer.at(j);
        }
        // 将总和添加到QByteArray的末尾
        s_buffer.append(m_checksum);
        serial->write(s_buffer);
        s_buffer.fill(0);//清空buf,防止最后一帧数据不对


        qDebug()<<"开始发送数据";
    } else {
        emit IAPfinish();

    }
}

此时发完等待下位机回应

   connect(serial,&QSerialPort::readyRead,             //接收数据的连接   这边用定时器的原因是防止接收的数据断断续续
            this,[=](){

        qDebug()<<"timer start";
        timer->start(100);//100ms
        Receivetext.append(serial->readAll()); // 从串口读取数据

    });

后面就是关定时器,分析Receivetext中的数据了,如果是你想要的数据,就继续发下一帧,用sendNextChunk()函数;如果不是则报错即可。这里需要说一下,为什么要开100ms去接收数据,因为本人在采集数据时会发现数据会断断续续的进来,如果简单一点就用我这种方法,如果想用高级一点的,就用队列buf,实时处理数据即可;用timer的方法简单粗暴,节约时间,哈哈哈,另外一种方法可以请读者自行思考。还有下位机的相关代码程序这边直接用我的项目进行举例,如何将数据写入flash中的app2区域。

// 返回 1成功,0失败
uint8_t UpData_IAP_Write(uint16_t Kilo, const uint8_t *pBuf, uint16_t Length)
{
    uint8_t ret = 1;
    uint32_t addr;


    rt_base_t level = rt_hw_interrupt_disable();
    rt_enter_critical();
    Flash__Unlock();


     识别 Kilo
    if (0 == Kilo)                      // 不应该出现0
    {
        ret = 0;
    }
    else if (1 == Kilo)                 // 第1K
    {
        if (0 != ret)
        {
            if (0 != memcmp(pBuf, PROJECR_NAME, 6))  // 项目错误
            {
                //ret = 0;
            }
        }
        
        //
        if (0 != ret)
        {
             Get Info
            if (strlen((char *)pBuf) < 16)
            {
                strcpy(Update_Info.Project_String, (char *)pBuf);
            }

             Length
            Update_Info.Length  = *(pBuf + 0x20) * 0x1000000
                                + *(pBuf + 0x21) * 0x10000
                                + *(pBuf + 0x22) * 0x100
                                + *(pBuf + 0x23);

             Checksum
            Update_Info.Checksum    = *(pBuf + 0x24) * 0x1000000
                                    + *(pBuf + 0x25) * 0x10000
                                    + *(pBuf + 0x26) * 0x100
                                    + *(pBuf + 0x27);

             Kilos
            if (Update_Info.Length % 1024 == 0)
            {
                Update_Info.Kilos = Update_Info.Length / 1024;
            }
            else
            {
                Update_Info.Kilos = Update_Info.Length / 1024 + 1;
            }


			 Pages
            if (Update_Info.Kilos % 8 == 0)
            {
                Update_Info.Pages = Update_Info.Kilos / 8;
            }
            else
            {
                Update_Info.Pages = Update_Info.Kilos/ 8 + 1;
            }


             擦 App1 全部
            for (uint32_t i=0; i<Update_Info.Pages; i++)
            {
                addr = FLASH_APP1_ADDR_START + (FLASH_PAGE_SIZE * i);
                Flash__ErasePage(addr);
            }
        }
        
    }
    else if (Kilo <= Update_Info.Kilos)   // 范围合适
    {

    }
    else                                // 超范围
    {
        ret = 0;
    }


     写入1K数据
    if (0 != ret)
    {
        addr = FLASH_APP1_ADDR_START + (1024 * (Kilo - 1));
        Flash__WriteArray(addr, pBuf, 1024);

        if (Kilo == Update_Info.Kilos)   // 最后一页
        {
            // 做标记
            Flash__ErasePage(FLASH_MARK_ADDR);
            Flash__WriteWord(FLASH_MARK_ADDR, FLASH_MARK_WORD);
        }
    }


    Flash__Lock();
    rt_exit_critical();
    rt_hw_interrupt_enable(level);



    if (Kilo == Update_Info.Kilos)   // 最后一页
    {
        // 重启前喂狗
        #ifdef WDG_EN
            WDG_Feed();
        #endif

        // 重启
        NVIC_SystemReset();
    }



    return(ret);
}

我是一个小白,上面的内容肯定有不周到的地方,如有问题请邮箱zhanghanqi@njfu.edu.cn联系

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值