Skip to main content

在 Qt 中实现烧录 MCU 的功能

·1262 words·3 mins
Kydin
Author
Kydin
自由のために戦え
Table of Contents

笔者最近在开发一个项目的生产软件,用来测试产品的功能,产品在出厂的最后一步需要烧写 MCU(芯源)和 FPGA(高云)的固件,因此需要给测试人员讲解 keil 和高云的软件怎么烧写固件,增加了学习成本。因此萌生出在生产软件中集成烧写 MCU 和 FPGA 固件的想法。

MCU 芯片使用的是芯源的 CW32F030,研究了一下有什么可行的方案,最后选定了 QProcess + pyOCD 的方案。

开发环境
#

  • Qt6.9
  • MSVC 2019
  • Windows 11 24H2
  • pyOCD 0.40.0

什么是 pyOCD
#

Python based tool and API for debugging, programming, and exploring Arm Cortex microcontrollers.

官网的介绍可以得知,pyOCD 是一个基于 Python 的工具和 API,用于调试、编程和探索 Arm Cortex 微控制器,我们可以通过它来实现烧写 CW32。

由于 CW32 并不在 pyOCD 的官方支持型号中,我们可以将厂家提供的 pack 包(也就是安装到 keil 中的那个,不同的型号都可以在厂家的官网中下载到)作为参数传入 pyOCD,以此来实现固件的烧写操作,这也是选择 pyOCD 的原因之一,以后可以很方便的增加新的芯片烧写支持。

在命令行中运行 pyOCD

用到的指令有:

.\pyocd.exe list // 列出当前所有已连接的下载器
.\pyocd.exe json // 以json形式输出所有已连接的下载器
.\pyocd.exe flash --erase chip --target CW32F030F8 --pack WHXY.CW32F030_DFP.1.0.4.pack 固件路径 // 烧写固件

其中 --erase chip 代表擦除芯片 --target CW32F030F8 代表指定要烧写的芯片型号,你应该根据实际情况填写 --pack WHXY.CW32F030_DFP.1.0.4.pack 代表指定要烧写芯片的开发包,一般是厂家提供(如果你的芯片是 pyOCD 官方支持的,比如 stm32,就不需要这个配置) 至于固件,pyOCD 原生支持 .axf、.hex 和 .bin 格式的固件。

在 Qt 中实现调用 pyOCD
#

在 Qt 中,调用另一个可执行文件,我们可以通过 QProcess,可以转入我们想要的参数,并在回调函数中获取可执行文件的输出。

获取所有连接的下载器
#

由于 pyOCD 支持输出 json 格式,我们就可以很方便地解析出所有连接的下载器,我使用的是nlohmann/json 这个 json 库。

核心代码:

void FirmwareUpdateInterface::ScanProbe(QString pyocd_path)
{
    process_->start(pyocd_path, QStringList() << "json");
}

void FirmwareUpdateInterface::ParseProbeList(const QString& raw_output)
{
	QStringList probes;
	json j = json::parse(raw_output.toStdString(), nullptr, false);

    /*
    {
    "pyocd_version": "0.40.0",
    "version": {
        "major": 1,
        "minor": 1
    },
    "status": 0,
    "boards": [
        {
            "unique_id": "B8D4ECDC00E1",
            "info": "embedfire CMSIS-DAP",
            "board_vendor": null,
            "board_name": "Generic",
            "target": "cortex_m",
            "vendor_name": "embedfire",
            "product_name": "CMSIS-DAP"
        }
    ]
    }
    */
    QString pyocd_version = QString::fromStdString(j.value("pyocd_version", "unknown"));

    if (j.contains("boards") && j["boards"].is_array())
    {
        for (const auto &board : j["boards"])
        {
            ProbeInfo probe;
            probe.id = QString::fromStdString(board.value("unique_id", "unknown"));
            probe.info = QString::fromStdString(board.value("info", "unknown"));
            probe.target = QString::fromStdString(board.value("product_name", "unknown"));
            probe_list_.append(probe);
        }
    }
}

烧写固件
#

烧写固件的指令就是上面说的那个指令,不过有一点要注意的是,pyOCD 烧写过程中,每一次的输出并不是完整的一行,而是一个个字符,因此在解析成进度条时要特殊处理一下,我的方案是:因为 pyOCD 完成烧写一共会输出 50 个=号,因此只需要记录下已经输出的=的个数,就可以算出烧写完成了百分之多少。

核心代码:

void FirmwareUpdateInterface::StartFirmwareUpdate(FirmwareUpdateParam param)
{
    emit DebugMessage("Starting Firmware Update");
    QStringList arguments;
    arguments << "flash";
    if (param.is_erase_chip)
    {
        arguments << "--erase" << "chip";
    }
    if (!param.target_chip.isEmpty())
    {
        arguments << "--target" << param.target_chip;
    }
    if (!param.pack_path.isEmpty())
    {
        arguments << "--pack" << param.pack_path;
    }
    if (!param.firmware_path.isEmpty())
    {
        arguments << param.firmware_path;
    }
    else
    {
        emit DebugMessage("Firmware path is empty!");
        return;
    }

    qDebug() << "QProcess run command: [" << param.pyocd_path << " " << arguments << "]";
    action_ = InProgress;
    process_->setProcessChannelMode(QProcess::MergedChannels); // 设置进程通道模式
    process_->start(param.pyocd_path, arguments);
}

void FirmwareUpdateInterface::ParseFirmwareUpdateInfo(const QString& output)
{
    // pyOCD 通常输出类似 "[===    ] 45%" 的进度信息
    // 方法:匹配进度条格式 "[=======     ]"
    // 进度条通常有固定宽度,50 个字符
    if (output.contains("=")) {
        percentage_ += output.count('=');
        int percentage = (percentage_ * 100) / 50;
        emit ProgressUpdated(percentage);
    }
}

运行效果
#

我尝试烧写两个不同的固件,它们之间的差异是版本号不同。烧写完成后通过读写寄存器,证实了我们的烧写确实是成功了的。

运行效果

结语
#

pyOCD 在烧写固件方面确实比 keil 方便不少,集成在生产软件中后,通过傻瓜式的页面操作,降低了测试同事的学习成本,也能够减少工作量,算是很有收获的一次功能更新。

本文的完整代码和编译好的可执行程序可以在 GitHub 中找到:QPyocd