基于OTA升级的上位机刷写软件

准备工具:KD6630开发板、网线、PC端、软件升级包。TCP/UDS协议、QT、Wireshark抓包工具。

主要功能为给网关控制器的MCU端做升级更新。本质为HEX、asc文件的传输。

附加功能:能将路由表excel文件转换为hex文件。代码如下:

int client::readExcelAndGenerateHex(const QString &excelFilePath)
{
    qDebug() << "Attempting to open file at path: " << excelFilePath;
    QAxObject *excel = new QAxObject("Excel.Application", this);
    if (!excel) {
        qDebug() << "Failed to create Excel object";
        return -1;
    }
    QAxObject *workbooks = excel->querySubObject("Workbooks");
    if (!workbooks) {
        qDebug() << "Failed to query Workbooks sub-object";
        return -1;
    }
    QAxObject *workbook = workbooks->querySubObject("Open(const QString&)", excelFilePath);
    if (!workbook) {
        qDebug() << "Failed to open workbook";
        return-1;
    }
    QAxObject *worksheet = workbook->querySubObject("Worksheets(int)", 1);
    if (!worksheet) {
        qDebug() << "Failed to query Worksheets sub-object";
        return -1;
    }
    QAxObject *usedRange = worksheet->querySubObject("UsedRange");
    if (!usedRange) {
        qDebug() << "Failed to query UsedRange sub-object";
        return -1;
    }
    QAxObject *lastCell = worksheet->querySubObject("Cells(int,int)", worksheet->querySubObject("Rows")->property("Count").toInt(), 1)
            ->querySubObject("End(int)", -4162);  // xlUp constant
    int rowCount = lastCell->property("Row").toInt();
    qDebug() << "Total number of rows:" << rowCount;

    QAxObject *rows = usedRange->querySubObject("Rows");
    if (!rows) {
        qDebug() << "Failed to query Rows sub-object";
        return -1;
    }
    QAxObject *columns = usedRange->querySubObject("Columns");
    if (!columns) {
        qDebug() << "Failed to query Columns sub-object";
        return -1;
    }
    int columnCount = columns->property("Count").toInt();
    qDebug()<<"columnCount:"<<columnCount;
    QString valid,num;
    QStringList canswts;
    QStringList canbauds;
    for(int col = 1; col <= columnCount; ++col) {
        QString header = worksheet->querySubObject("Cells(int,int)", 1, col)->dynamicCall("Value2()").toString();
        // 检查列是否为空
        if (header.isEmpty()) {
            continue; // 如果列为空,跳过该列
        }
        qDebug() << "Column" << col << ":" << header;
    }
    for(int col=1;col<11;col++)
    {
        QString header=worksheet->querySubObject("Cells(int,int)",1,col)->dynamicCall("Value2()").toString();
        if(header=="VALID")
        {
            valid=worksheet->querySubObject("Cells(int,int)",2,col)->dynamicCall("Value2()").toString();
        }
        else if(header.startsWith("CAN")&&header.endsWith("SWT"))
        {
            qDebug()<<"ENtering CANSWT";

            QString canswt=worksheet->querySubObject("Cells(int,int)",2,col)->dynamicCall("Value2()").toString();
            canswts.append(canswt);
        }
        else if(header=="NUM")
        {
            qDebug()<<"ENtering NUM";
            QString num_str=worksheet->querySubObject("Cells(int,int)",2,col)->dynamicCall("Value2()").toString();
            int true_count=rowCount-5;
            int num_int=num_str.toInt();
            qDebug()<<"num_int:"<<num_int;
            qDebug()<<"true_count:"<<true_count;
            if(num_int>true_count)
            {
                num="0002";
                qDebug()<<"num::"<<num;
            }
            else {
                QMessageBox::critical(this, "路由关系", "路由关系数量错误\n");
                return -1;
            }

        }
        else if(!header.isEmpty()&& header!= "NUM"&&header!="VALID"&&!header.startsWith("CAN")&&!header.endsWith("SWT") )
        {
            qDebug()<<"第一行有问题:"<<header;
            QMessageBox::critical(this, "格式错误", "输入excel码格式错误\n");
            return -1;
        }
        QString headersecond=worksheet->querySubObject("Cells(int,int)",3,col)->dynamicCall("Value2()").toString();
        if(headersecond.startsWith("CAN")&&headersecond.endsWith("BAUD"))
        {
            qDebug()<<"ENtering CANBAUD";
            QString canbaud=worksheet->querySubObject("Cells(int,int)",4,col)->dynamicCall("Value2()").toString();
            canbauds.append(canbaud);
        }
        else if(headersecond=="VERSION")
        {
            QString ver_ascii=worksheet->querySubObject("Cells(int,int)",4,col)->dynamicCall("Value2()").toString();
            qDebug()<<"ver_ascii:"<<ver_ascii;
            for(QChar assic:ver_ascii)
            {
                int hex_assic=assic.toLatin1();
                version.append(QString::number(hex_assic, 16).toUpper().rightJustified(2, '0'));
            }
            qDebug()<<"version:"<<version;
        }
        else if (!headersecond.isEmpty() && headersecond!="VERSION" && !headersecond.startsWith("CAN") && !headersecond.endsWith("BAUD") )
        {
            qDebug()<<"第二行有问题:"<<headersecond;
            QMessageBox::critical(this, "格式错误", "输入excel码格式错误\n");
            return -1;
        }

    }
    if(valid=="ON")
    {
        QString headerhex="AAA5A5A5";
        QString CANS;
        for(auto &canswt:canswts)
        {
            // qDebug()<<"canswt:"<<canswt;
            if(canswt=="ON")
            {
                CANS+="1";
            }
            else if(canswt=="OFF")
            {
                CANS+="0";
            }
            else if(canswt!="ON"&&canswt!="OFF")
            {
                QMessageBox::critical(this, "格式错误", "输入excel码格式错误\n");
                return -1;
            }
        }
        quint8 binvalue=0;
        for(int i=0;i<8;i++)
        {
            qDebug()<<"CAN["<<i<<"]:"<<CANS[i];
            if(CANS[i]=='1')
            {
                binvalue|=(1<<i);
            }
        }
        qDebug()<<"binvalue:"<<binvalue;
        QString hexValue = QString("%1").arg(binvalue, 2, 16, QChar('0'));
        for(auto &canbaud:canbauds)
        {
            qDebug()<<"canbaud:"<<canbaud;
            if(canbaud!="250K"&&canbaud!="500K"&&canbaud!="1000K")
            {
                QMessageBox::critical(this, "格式错误", "输入excel码格式错误\n");
                return -1;
            }
            if(canbaud=="250K")
            {
                headerhex=headerhex+"01";
                qDebug()<<"headerhex:"<<headerhex;
            }
            else if(canbaud=="500K")
            {
                headerhex=headerhex+"02";
                qDebug()<<"headerhex:"<<headerhex;
            }
            else if(canbaud=="1000K")
            {
                headerhex=headerhex+"03";
                qDebug()<<"headerhex:"<<headerhex;
            }
        }
        qDebug()<<"num:"<<num;
        qint8 numF=32-version.size();
        qDebug()<<"numF:"<<numF;
        qDebug()<<"version:"<<version;
        qDebug()<<"version.size:"<<version.size();
        QString HeaderVer=headerhex+hexValue+num+"FF"+version;
        qDebug()<<"headerhex:"<<headerhex;
        qDebug()<<"hexValue:"<<hexValue;
        qDebug()<<"num:"<<num;
        qDebug()<<"HeaderVer:"<<HeaderVer;
        Headerstr=HeaderVer.append(QString(numF,'F'));
        //Headerstr=headerhex+hexValue+num+"FF"+version+"FFFFFFFFFF";
        qDebug()<<"num2:"<<num;
        qDebug()<<"Headerstr:"<<Headerstr;
        qDebug()<<"headerstr.size:"<<Headerstr.size();
        string_hex+=":02000004103CAE";
        string_hex+="\n";
        //string_hex+=":20000000"+Headerstr;
        QString line=":20000000"+Headerstr;
        qDebug()<<"line:"<<line;
        int linesize=line.size();
        qDebug()<<"linesize:"<<linesize;
        string_hex += line;
        //qDebug()<<"string_hex:"<<string_hex;
        string_hex+="\n";
        string_hex+=":20002000";
        int start_address=0x0040;
        int current_address=0x0040;
        for(int i=6;i<rowCount+1;i++)
        {

            if(prossrow(worksheet,i)!=0)
            {
                qDebug()<<"列表读取失败";
                return -1;
            }
            else{
                if(i>=7&&i%2==1)
                {

                    qDebug()<<"current_address:"<<QString::number(current_address, 16).toUpper().rightJustified(4, '0');
                    // string_hex+=":20"+QString::number(current_address, 16).toUpper().rightJustified(4, '0')+"00";
                    //string_hex+="AC";
                    string_hex+="\n";
                    QString line=":20"+QString::number(current_address, 16).toUpper().rightJustified(4, '0')+"00";
                    string_hex+=line;
                }else if(i>=7&&i%2==0) {
                    current_address=start_address+(i-6)*0x0010;
                    continue;
                }

            }


        }
        //qDebug()<<"string_hex:"<<string_hex;
        QString lastline=string_hex.split("\n").last();
        qDebug()<<"lastline:"<<lastline;
        int lastline_length=lastline.length();
        for(int i=lastline_length;i<linesize;i++)
        {
            string_hex+="0";
        }
        string_hex+="\n";
        string_hex+=":00000001FF";
        workbook->dynamicCall("Close()");
        excel->dynamicCall("Quit()");
        delete columns;
        delete rows;
        delete usedRange;
        delete worksheet;
        delete workbook;
        delete workbooks;
        excel->dynamicCall("ReleaseDispatch()");
        delete excel;
    }
    else {
        QMessageBox::critical(this, "VALID", "路由信息无效\n");
        return -1;
    }
    return 0;
}

硬件连接:

首先保证电脑和板端在同一网段能ping通,上位机界面如下图所示。

点击选择含有指定flash、路由表、app等hex文件的目录。

asc文件hex的验签文件,先转成bin文件再加签生成asc文件。函数如下:

bool client::processFile(const QString &hexFilePath, const QString &binFilePath, const QString &ascFilePath)
{
    if(!hexToBin(hexFilePath,binFilePath))
    {
        qDebug()<<hexFilePath<<"转bin文件失败";
        QMessageBox::critical(this, "文件转换失败", hexFilePath+"文件转换bin文件失败\n");
        return false;
    }
    QByteArray sign;
    if(!DigestAndSign(binFilePath, "SHA256", sign))
    {
        qDebug()<<"验签文件失败"<<binFilePath;
        QMessageBox::critical(this, "文件验签失败", binFilePath+"文件验签失败\n");
        return false;
    }
    QFile binFile(binFilePath); // 生产的bin文件
    if (binFile.open(QIODevice::WriteOnly))
    {
        qint64 writtenBytes = binFile.write(sign); // 写入签名数据
        if (writtenBytes == -1)
        {
            qDebug() << "Failed to write to bin file.";
        }
        else
        {
            qDebug() << "Successfully wrote" << writtenBytes << "bytes to bin file.";
        }
        binFile.close(); // 关闭文件
    }
    else
    {
        qDebug() << "Failed to open bin file for writing.";
        return false;
    }
    binToAsc(binFilePath,ascFilePath);
    if (QFile::exists(binFilePath)) {
        if (!QFile::remove(binFilePath)) {
            qDebug() << "Failed to delete bin file after processing.";
        } else {
            qDebug() << "Bin file deleted after processing.";
        }
    }
    return true;
}

生成验签文件后点击升级按钮,即可烧录6个文件,烧录函数如下,这里用到QT的信号和槽机制。

void client::on_pushButton_clicked()
{
    qDebug() << "button start";
    ui->pushButton->setEnabled(false);
    qDebug() << "button after";

    //timeValid=true;
    if (timeValid) {
        qDebug() << "Entering on_pushButton_clicked";
        flash_file = mcu_package + "/NEVC_E3110_None_FLS_IAR_Project.hex";
        flash_signfile = mcu_package + "/NEVC_E3110_None_FLS_IAR_Project.asc";
        app_file = mcu_package + "/NEVC_E3110_IAR_8p50_Project.hex";
        app_signfile = mcu_package + "/NEVC_E3110_IAR_8p50_Project.asc";
        routetable_file = mcu_package + "/CAN2CAN.hex";
        routetable_signfile = mcu_package + "/CAN2CAN.asc";
        mcu_json_file = mcu_package + "/mcu.json";
        public_key_file = mcu_package + "/public.txt";

        ui->listWidget_2->clear();
        ui->listWidget_2->addItem("正在连接...");
        QCoreApplication::processEvents();
        if (worker) {
            worker->deleteLater();
        }
        worker = new Worker(flash_file, flash_signfile, app_file, app_signfile, routetable_file, routetable_signfile, public_key_file);
        worker->moveToThread(workerThread);
        connect(workerThread, &QThread::started, worker, &Worker::process);
        connect(worker, &Worker::connectionResult, this, &client::onConnectionResult);
        connect(worker, &Worker::updateResult, this, &client::onUpdateResult);
        connect(worker, &Worker::fristFile, this, &client::onfrisetFile);
        connect(worker, &Worker::restartResult, this, &client::onRestartResult);
        connect(worker, &Worker::finished, this, &client::on_fininshed);
        qDebug() << "emit statechanged";
        QMetaObject::invokeMethod(worker, "process", Qt::QueuedConnection);
        qDebug() << "进入后台线程";
    } else {
        qDebug() << "NO find valid!";
        QMessageBox::critical(nullptr, "错误", "请检查网络连接!");
        qApp->quit();
    }
}

通过wireshark可抓取文件的传输的交互报文:

其中还涉及协议中的加解密,发送逻辑。想起来再写

### UDS协议下的汽车ECU刷写软件及其使用 对于希望通过UDS协议进行汽车电子控制单元(ECU)刷写的开发者而言,存在多种可用的解决方案。项目不仅实现了UDS协议的基本功能,还提供了一套完整的ECU升级刷写流程。这意味着开发者能够利用此项目快速建立上位机与ECU间的通信链路,从而实现固件更新和刷写操作[^1]。 #### 软件选择 市场上有多个支持UDS标准并适用于不同应用场景的专业级工具可供选用: - **Vector CANape**: 提供全面的功能集来配置、校准及诊断基于总线系统的设备。 - **ETAS Inca**: 支持复杂测量任务的同时也具备强大的编程能力。 - **PEAK PCAN Diag**: 集成了丰富的诊断特性,适合入门级用户尝试简单的调试工作。 以上提到的产品通常都内置了详细的帮助文档和支持论坛,方便初次使用者获取必要的指导信息。 #### 安全认证过程 值得注意的是,在实际应用过程中,出于安全性考虑,必须先完成相应的身份验证才能继续后续的操作。具体来说就是在调用服务27 (Security Access Service) 后获得授权许可,以此确保只有经过合法授权的人才可以更改内部程序或参数设置[^3]。 #### 刷写前准备事项 在正式开始刷写之前还需要做一系列准备工作,包括但不限于确认目标硬件型号兼容性、安装最新的驱动程序版本以及备份现有系统状态等重要环节。此外还需注意检查是否有足够的存储空间可用于保存新镜像文件,并且保持稳定的电源供应以防意外断电造成损害。 #### 实际案例分析 以某品牌车型为例,当需要对其动力管理系统内的某个特定模块实施在线空中(OTA) 更新时,则可以借助上述任意一款或多款组合起来共同协作完成整个作业流程。例如通过云端推送方式发送待部署的新版固件至指定位置等待接收端激活指令后自动启动刷新进程直至结束返回最终结果报告给管理员审核确认无误即可宣告此次维护活动圆满落幕[^4]。 ```python def perform_ecu_flash(ecu_id, firmware_path): """ Perform ECU flash using provided ID and path to new firmware. :param ecu_id: Identifier of the target ECU unit :type ecu_id: str :param firmware_path: Path where updated firmware is stored locally :type firmware_path: str """ try: # Establish secure connection with vehicle's network interface establish_secure_connection() # Request entry into programming session mode enter_programming_session_mode(ecu_id) # Authenticate access rights before proceeding further authenticate_access_rights() # Transfer binary data from local storage device onto remote node transfer_firmware_to_remote_node(firmware_path) # Execute flashing procedure on selected component(s) execute_flashing_procedure() # Verify integrity post-operation completion verify_post_operation_integrity() print("Flashing completed successfully.") except Exception as e: handle_exception(e) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值