Skip to main content

在 Qt 中实现烧录高云 FPGA 的功能

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

前情提要
#

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

FPGA 芯片使用的是高云的 GW1N-4D,在高云官方提供的烧录软件中,提供了一个 cli 的可执行文件,于是我们就可以使用 QProcess 来调用这个软件,实现集成烧写 FPGA 固件的功能。

用到的指令有:

.\programmer_cli.exe --scan-cables // 列出当前所有已连接的下载器
.\programmer_cli.exe --scan // 列出当前所有已连接的设备
.\programmer_cli.exe --operation_index 6 --device GW1N-4D --fsFile 固件路径 // 烧写固件

其中 --operation_index 6 代表embFlash Erase,Program,Verify,你可以根据实际情况填写 --device GW1N-4D 代表指定要烧写的芯片型号,你应该根据实际情况填写 --fsFile 固件路径 代表需要烧写的固件,应该使用.fs 结尾的文件

还有更多的参数可以查阅高云提供的《SUG502-1.6_Gowin_Programmer 用户指南.pdf》

开发环境
#

  • Qt6.9
  • MSVC 2019
  • Windows 11 24H2
  • Gowin_Programmer Version 1.9.9 (64-bit) build(31129)

在 Qt 中实现调用 Gowin_Programmer
#

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

获取所有连接的下载器
#

由于 Gowin_Programmer 不支持输出 json 格式,我直接使用字符串解析来获取它的返回值。

核心代码:

void FpgaUpdateInterface::ScanProbe(QString gowin_cli_path)
{
    emit DebugMessage("Scanning probes...");
    action_ = Fpga_Proces_Action_ScanProbes;
    process_->start(gowin_cli_path, QStringList() << "--scan-cables");
}

void FpgaUpdateInterface::ParseProbeList(const QString& raw_output)
{
	qDebug() << "Raw probe scan output:" << raw_output;
    /*
    PS C:> .\programmer_cli.exe --scan-cables
    Cable found:  Gowin USB Cable(FT2CH)/0/786/null (USB location:786)
    Cost 0.05 second(s)
    */
    for (const QString& line : raw_output.split("\r\n")) {
        if (line.contains("Cable found:")) {
            QString probe = line.mid(QString("Cable found:").length() + 1).trimmed();
            probe_list_.append(probe);
        }
	}
}

获取所有连接的设备
#

同样也是使用字符串解析来获取它的返回值。

核心代码:

vvoid FpgaUpdateInterface::ScanDevice(QString gowin_cli_path)
{
    emit DebugMessage("Scanning device...");
    action_ = Fpga_Proces_Action_ScanDevice;
    process_->start(gowin_cli_path, QStringList() << "--scan");
}

void FpgaUpdateInterface::ParseDeviceList(const QString& raw_output)
{
    qDebug() << "Raw device scan output:" << raw_output;

    /*
    失败
    PS C:> .\programmer_cli.exe --scan
    Scanning!
    Target Cable: Gowin USB Cable(FT2CH)/0/0/null@2.5MHz

    Error: No Gowin devices found!
    Cost 0.54 second(s)

    成功
    PS C:> .\programmer_cli.exe --scan
    Scanning!
    Target Cable: Gowin USB Cable(FT2CH)/0/0/null@2.5MHz
    Device Info:
        Family: GW1NRF
        Name: GW1N-4D GW1NR-4D GW1N-4B GW1NR-4B GW1NRF-4B  (One of them)
        ID: 0x1100381B
    1 device(s) found!
    Cost 0.54 second(s)
    */
    for (const QString& line : raw_output.split('\n')) {
        if (line.contains("ID:")) {
            QString id = line.mid(QString("ID:").length() + 1).trimmed();
            device_list_.append(id);
        }
    }
}

烧写固件
#

烧写固件的指令就是上面说的那个指令,不过有一点要注意的是,烧写过程中会有两个进度条,先是烧写固件,随后进行校验,并且 Gowin_Programmer 每次输出都是完整的一行,因此可以直接使用正则表达式来解析输出,这点还是比较方便的。

核心代码:


void FpgaUpdateInterface::StartFirmwareUpdate(FirmwareUpdateParam param)
{
    emit DebugMessage("Starting Firmware Update");
    QStringList arguments;
    arguments << "--operation_index" << QString::number(param.operation_index);
    if (!param.target_chip.isEmpty())
    {
        arguments << "--device" << param.target_chip;
    }
    if (!param.firmware_path.isEmpty())
    {
        arguments << "--fsFile" << param.firmware_path;
    }
    else
    {
        emit DebugMessage("Firmware path is empty!");
        return;
    }

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

void FpgaUpdateInterface::ParseFirmwareUpdateInfo(const QString& output)
{
    qDebug() << "Firmware update output:" << output;
    // 升级阶段
    // gowin/programmer_cli.exe 通常输出类似 "\rProgramming...: [#                        ] 4%                 " 的进度信息
    // 校验阶段
    // gowin/programmer_cli.exe 通常输出类似 "\rVerifying...: [#                        ] 4%                 " 的进度信息

    QRegularExpression progressRegex(R"(\r(Programming|Verifying)\s*\.{0,3}:\s*\[(#+)\s*\]\s*(\d+)%\s*)");
    QRegularExpressionMatch match = progressRegex.match(output);

    if (match.hasMatch()) {
        QString stage = match.captured(1);  // Programming 或 Verifying
        int percentage = match.captured(3).toInt();  // 百分比

        // 根据阶段处理进度
        if (stage == "Programming") {
             emit ProgrammingUpdated(percentage);
        }
        else if (stage == "Verifying") {
             emit VerifyingUpdated(percentage);
        }

    }
}

运行效果
#

烧写 FPGA 运行效果

结语
#

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

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