嵌入式系统与单片机|技术阅读
登录|注册

您现在的位置是:嵌入式系统与单片机 > 技术阅读 > QT编写一个JLINK烧录工具

QT编写一个JLINK烧录工具

概述

  • 作者一直有一个想法,就是写一个功能强大的桌面小工具,里面集成各种平时开发要用的工具。例如:串口助手,网络助手,下载工具等。那么如何也带来几个问题:
  • 问题1:那么如何呈现在桌面上也是一个非常重要的问题 -- 桌面悬浮窗。
  • 问题2:工具的名字 -- RTOOL(米饭工具)
  • 问题3:是否贡献整个工具 -- 分为两个版本:开源版本和公司项目版本(已经发布了V1.0版本)。
    • 本篇文章介绍RTOOL的JLINK烧录小工具,那为什么要在RTOOL中集成JLINK的烧录工具呢?原因:
  • 像MCU,我们如果使用GCC构建我们的程序后,没有IDE的支撑,就需要使用JFLASH这样的工具进行烧录,这个操作流程还是挺多步骤的。
  • 方便我们对固件进行动手术,如对固件进行加密处理,对芯片ram,flash进行随心所欲的操作。
    • 参考:https://www.amobbs.com/thread-5690311-1-1.html?_dsign=48d76ae6

    原理说明

    • 我们在使用JFlash烧录工具时,实际JFlash是通过调用JLinkARM.dll动态库提供的接口进行操作的。那么我们可以通过Dependency walker对JLinkARM.dll进行分析。获取到dll库中所有函数符号。
    • QT提供了QLibrary类可以动态加载dll,所以结合获取的函数符号,我们可以定义一些列函数指针指向对应的符号。

    开发流程

  • UI设计,实际可以很简单,目的也是简化JFlash的操作流程:
  • 定义对接动态库JLinkARM.dll的一系列函数指针,头文件RJlinkARM.h:
  • #ifndef RJLINKARMH
    #define RJLINKARMH

    //JLINK TIF
    #define JLINKARM_TIF_JTAG                0
    #define JLINKARM_TIF_SWD                 1
    #define JLINKARM_TIF_DBM3                2
    #define JLINKARM_TIF_FINE                3
    #define JLINKARM_TIF_2wire_JTAG_PIC32  4

    //RESET TYPE
    #define JLINKARM_RESET_TYPE_NORMAL       0
    #define JLINKARM_RESET_TYPE_CORE         1
    #define JLINKARM_RESET_TYPE_PIN          2

    typedef bool  (*rjlinkOpenFunc)(void);
    typedef void  (*rjlinkCloseFunc)(void);
    typedef bool  (*rjlinkIsOpenFunc)(void);
    typedef unsigned int (*rjlinkTIFSelectFunc)(int);
    typedef void  (*rjlinkSetSpeedFunc)(int);
    typedef unsigned int (*rjlinkGetSpeedFunc)(void);
    typedef void  (*rjlinkResetFunc)(void);
    typedef int   (*rjlinkHaltFunc)(void);
    typedef void  (*rjlinkGoFunc)(void);

    typedef int   (*rjlinkReadMemFunc)(unsigned int addr, int len, void *buf);
    typedef int   (*rjlinkWriteMemFunc)(unsigned int addr, int len, void *buf);
    typedef int   (*rjlinkWriteU8Func)(unsigned int addr, unsigned char data);
    typedef int   (*rjlinkWriteU16Func)(unsigned int addr, unsigned short data);
    typedef int   (*rjlinkWriteU32Func)(unsigned int addr, unsigned int data);

    typedef int   (*rjlinkEraseChipFunc)(void);
    typedef int   (*rjlinkDownloadFileFunc)(const char *file, unsigned int addr);
    typedef void  (*rjlinkBeginDownloadFunc)(int index);
    typedef void  (*rjlinkEndDownloadFunc)(void);
    typedef bool  (*rjlinkExecCommandFunc)(const char* cmd, int a, int b);

    typedef unsigned int (*rjlinkReadRegFunc)(int index);
    typedef int   (*rjlinkWriteRegFunc)(int index, unsigned int data);

    typedef void  (*rjlinkSetLogFileFunc)(char *file);
    typedef unsigned int (*rjlinkGetDLLVersionFunc)(void);
    typedef unsigned int (*rjlinkGetHardwareVersionFunc)(void);
    typedef unsigned int (*rjlinkGetFirmwareStringFunc)(char *buff, int count);
    typedef unsigned int (*rjlinkGetSNFunc)(void);
    typedef unsigned int (*rjlinkGetIdFunc)(void);
    typedef bool  (*rjlinkConnectFunc)(void);
    typedef bool  (*rjlinkIsConnectedFunc)(void);

    #endif // RJLINKARMH
  • 通过QT提供了QLibrary类加载dll,然后函数指针指向对应的函数符号:
    • 通过头文件RJlinkARM.h定义的函数指针类型定义对应的变量:
    private:
        rjlinkOpenFunc rjlinkOpenFuncPtr = NULL;
        rjlinkCloseFunc rjlinkCloseFuncPtr = NULL;
        rjlinkIsOpenFunc rjlinkIsOpenFuncPtr = NULL;
        rjlinkTIFSelectFunc rjlinkTIFSelectFuncPtr = NULL;
        rjlinkSetSpeedFunc rjlinkSetSpeedFuncPtr = NULL;
        rjlinkGetSpeedFunc rjlinkGetSpeedFuncPtr = NULL;
        rjlinkResetFunc rjlinkResetFuncPtr = NULL;
        rjlinkHaltFunc rjlinkHaltFuncPtr = NULL;
        rjlinkGoFunc rjlinkGoFuncPtr = NULL;
        rjlinkReadMemFunc rjlinkReadMemFuncPtr = NULL;
        rjlinkWriteMemFunc rjlinkWriteMemFuncPtr = NULL;
        rjlinkWriteU8Func rjlinkWriteU8FuncPtr = NULL;
        rjlinkWriteU16Func rjlinkWriteU16FuncPtr = NULL;
        rjlinkWriteU32Func rjlinkWriteU32FuncPtr = NULL;
        rjlinkEraseChipFunc rjlinkEraseChipFuncPtr = NULL;
        rjlinkDownloadFileFunc rjlinkDownloadFileFuncPtr = NULL;
        rjlinkBeginDownloadFunc rjlinkBeginDownloadFuncPtr = NULL;
        rjlinkEndDownloadFunc rjlinkEndDownloadFuncPtr = NULL;
        rjlinkExecCommandFunc rjlinkExecCommandFuncPtr = NULL;
        rjlinkReadRegFunc rjlinkReadRegFuncPtr = NULL;
        rjlinkWriteRegFunc rjlinkWriteRegFuncPtr = NULL;
        rjlinkSetLogFileFunc rjlinkSetLogFileFuncPtr = NULL;
        rjlinkGetDLLVersionFunc rjlinkGetDLLVersionFuncPtr = NULL;
        rjlinkGetHardwareVersionFunc rjlinkGetHardwareVersionFuncPtr = NULL;
        rjlinkGetFirmwareStringFunc rjlinkGetFirmwareStringFuncPtr = NULL;
        rjlinkGetSNFunc rjlinkGetSNFuncPtr = NULL;
        rjlinkGetIdFunc rjlinkGetIdFuncPtr = NULL;
        rjlinkConnectFunc rjlinkConnectFuncPtr = NULL;
        rjlinkIsConnectedFunc rjlinkIsConnectedFuncPtr = NULL;
    • 通过动态库(JLinkARM.dll)获取对应的函数指针
    void RJLinkView::jlinkLibLoadHandle(void)
    {
        jlinkLib = new QLibrary("JLinkARM.dll");
        if(jlinkLib->load())
        {
            rjlinkOpenFuncPtr = (rjlinkOpenFunc)jlinkLib->resolve("JLINKARM_Open");                             // 打开设备
            rjlinkCloseFuncPtr = (rjlinkCloseFunc)jlinkLib->resolve("JLINKARM_Close");                          // 关闭设备
            rjlinkIsOpenFuncPtr = (rjlinkIsOpenFunc)jlinkLib->resolve("JLINKARM_IsOpen");                       // 判断设备是否打开
            rjlinkTIFSelectFuncPtr = (rjlinkTIFSelectFunc)jlinkLib->resolve("JLINKARM_TIF_Select");             // 选择设备
            rjlinkSetSpeedFuncPtr = (rjlinkSetSpeedFunc)jlinkLib->resolve("JLINKARM_SetSpeed");                 // 设置烧录速度
            rjlinkGetSpeedFuncPtr = (rjlinkGetSpeedFunc)jlinkLib->resolve("JLINKARM_GetSpeed");                 // 获取烧录速度
            rjlinkResetFuncPtr = (rjlinkResetFunc)jlinkLib->resolve("JLINKARM_Reset");                          // 复位设备
            rjlinkHaltFuncPtr = (rjlinkHaltFunc)jlinkLib->resolve("JLINKARM_Halt");                             // 中断程序执行
            rjlinkReadMemFuncPtr = (rjlinkReadMemFunc)jlinkLib->resolve("JLINKARM_ReadMem");                    // 读取内存
            rjlinkWriteMemFuncPtr = (rjlinkWriteMemFunc)jlinkLib->resolve("JLINKARM_WriteMem");                 // 写入内存
            rjlinkEraseChipFuncPtr = (rjlinkEraseChipFunc)jlinkLib->resolve("JLINK_EraseChip");                 // 擦除芯片
            rjlinkExecCommandFuncPtr = (rjlinkExecCommandFunc)jlinkLib->resolve("JLINKARM_ExecCommand");        // 执行命令
            rjlinkGetDLLVersionFuncPtr = (rjlinkGetDLLVersionFunc)jlinkLib->resolve("JLINKARM_GetDLLVersion");  // 获取DLL版本号
            rjlinkGetSNFuncPtr = (rjlinkGetSNFunc)jlinkLib->resolve("JLINKARM_GetSN");                          // 获取sn号
            rjlinkGetIdFuncPtr = (rjlinkGetIdFunc)jlinkLib->resolve("JLINKARM_GetId");                          // 获取ID
            rjlinkConnectFuncPtr = (rjlinkConnectFunc)jlinkLib->resolve("JLINKARM_Connect");                    // 连接设备
            rjlinkIsConnectedFuncPtr = (rjlinkIsConnectedFunc)jlinkLib->resolve("JLINKARM_IsConnected");        // 判断是否连接设备
        }
    }
    • 上述的函数指针,其实对访问操作一点也不友好,所以通过类方法重新对其封装,也避免的空指针的访问:
    bool RJLinkView::jlinkOpen(void)
    {
        if(rjlinkOpenFuncPtr){
            return rjlinkOpenFuncPtr();
        }
        return false;
    }
    void RJLinkView::jlinkClose(void)
    {
        if(rjlinkCloseFuncPtr){
            rjlinkCloseFuncPtr();
        }
    }
    bool RJLinkView::jlinkIsOpen(void)
    {
        if(rjlinkIsOpenFuncPtr){
            return rjlinkIsOpenFuncPtr();
        }
        return false;
    }
    unsigned int RJLinkView::jlinkTIFSelectFunc(int type)
    {
        if(rjlinkTIFSelectFuncPtr){
            return rjlinkTIFSelectFuncPtr(type);
        }
        return false;
    }
    void RJLinkView::jlinkSetSpeedFunc(unsigned int speed)
    {
        if(rjlinkSetSpeedFuncPtr){
            rjlinkSetSpeedFuncPtr(speed);
        }
    }
    unsigned int RJLinkView::jlinkGetSpeedFunc(void)
    {
        if(rjlinkGetSpeedFuncPtr){
            return rjlinkGetSpeedFuncPtr();
        }
        return 0;
    }
    void RJLinkView::jlinkResetFunc(void)
    {
        if(rjlinkResetFuncPtr){
            rjlinkResetFuncPtr();
        }
    }
    int RJLinkView::jlinkHaltFunc(void)
    {
        if(rjlinkHaltFuncPtr){
            return rjlinkHaltFuncPtr();
        }
        return 0;
    }
    int RJLinkView::jlinkReadMemFunc(unsigned int addr, int len, void *buf)
    {
        if(rjlinkReadMemFuncPtr){
            return rjlinkReadMemFuncPtr(addr, len, buf);
        }
        return 0;
    }
    int RJLinkView::jlinkWriteMemFunc(unsigned int addr, int len, void *buf)
    {
        if(rjlinkWriteMemFuncPtr){
            return rjlinkWriteMemFuncPtr(addr, len, buf);
        }
        return 0;
    }
    int RJLinkView::jlinkEraseChipFunc(void)
    {
        if(rjlinkEraseChipFuncPtr){
            return rjlinkEraseChipFuncPtr();
        }
        return 0;
    }
    bool RJLinkView::jlinkExecCommandFunc(const char *cmd, int a, int b)
    {
        if(rjlinkExecCommandFuncPtr){
            return rjlinkExecCommandFuncPtr(cmd, a, b);
        }
        return false;
    }
    unsigned int RJLinkView::jlinkGetDLLVersionFunc(void)
    {
        if(rjlinkGetDLLVersionFuncPtr){
            return rjlinkGetDLLVersionFuncPtr();
        }
        return 0;
    }
    unsigned int RJLinkView::jlinkGetSNFunc(void)
    {
        if(rjlinkGetSNFuncPtr){
            return rjlinkGetSNFuncPtr();
        }
        return 0;
    }
    unsigned int RJLinkView::jlinkGetIdFunc(void)
    {
        if(rjlinkGetIdFuncPtr){
            return rjlinkGetIdFuncPtr();
        }
        return 0;
    }
    bool RJLinkView::jlinkConnectFunc(void)
    {
        if(rjlinkConnectFuncPtr){
            return rjlinkConnectFuncPtr();
        }
        return false;
    }
    bool RJLinkView::jlinkIsConnectedFunc(void)
    {
        if(rjlinkIsConnectedFuncPtr){
            return rjlinkIsConnectedFuncPtr();
        }
        return false;
    }
    • 下载信息窗体的显示格式设置,为了方便预览我们的操作步骤,所以规定一个显示格式,增加时间戳的显示:
    void RJLinkView::infoShowHandle(QString info)
    {
        QString timestamp = QDateTime::currentDateTime().toString("[hh:mm:ss.zzz]==> ");
        ui->burnInfoTextBrowser->append(timestamp + info);
    }
    • 连接芯片设备,以下是按照STM32F407IG为例,下载方式采用SWD,下载速度未4000kHz:
    bool RJLinkView::jlinkConnectHandle(void)
    {
        if(jlinkIsOpen())
        {
            infoShowHandle(tr("设备连接成功"));
            return true;
        }
        jlinkOpen();
        if(jlinkIsOpen())
        {
            jlinkExecCommandFunc("device = STM32F407IG"00);
            jlinkTIFSelectFunc(JLINKARM_TIF_SWD);
            jlinkSetSpeedFunc(4000);
            jlinkConnectFunc();
            if(jlinkIsConnectedFunc()){
                infoShowHandle(tr("设备连接成功"));
                return true;
            }else
            {
                infoShowHandle(tr("连接设备失败! 请检查设备连接..."));
            }
        }
        else
        {
            infoShowHandle(tr("连接设备失败! 请检查烧录器连接..."));
        }
        return false;
    }
    • 断开芯片设备:
    void RJLinkView::jlinkdisconnectHandle(void)
    {
        jlinkClose();
        if(!jlinkIsOpen())
        {
            infoShowHandle(tr("断开设备成功!"));
        }
        else {
            infoShowHandle(tr("断开设备失败..."));
        }
    }
    • 获取CPU ID,原理其实很简单,通过读取对应UUID寄存器,就可以获取过去到芯片的UUID
    QString RJLinkView::jlinkGetCpuIdHandle(void)
    {
        unsigned char cpuId[12]={0};
        char cpuIdTemp[128]={0};
        jlinkReadMemFunc(0x1FFF7A1012, cpuId);
        sprintf(cpuIdTemp, "%02X%02X%02X%02X-%02X%02X%02X%02X-%02X%02X%02X%02X",
                cpuId[3], cpuId[2], cpuId[1], cpuId[0],
                cpuId[7], cpuId[6], cpuId[5], cpuId[4],
                cpuId[11], cpuId[10], cpuId[9], cpuId[8]);
        return QString(cpuIdTemp);
    }
    • 选择固件按钮实现,这里指定了文件后缀为.bin格式:
    void RJLinkView::on_fwFilePathSelectPushButton_clicked()
    {
        QString fwFileName = QFileDialog::getOpenFileName(this"Firmware file", QCoreApplication::applicationDirPath(), "fw file(*.bin)");
        ui->fwFilePathLineEdit->setText(fwFileName);
    }
    • 清除信息显示窗体按钮实现:
    void RJLinkView::on_cleanInfoPushButton_clicked()
    {
        ui->burnInfoTextBrowser->clear();
    }
    • 获取芯片ID按钮实现,需要先连接上设备,然后调用jlinkGetCpuIdHandle方案获取ID之后,断开连接:
    void RJLinkView::on_getCpuIdPushButton_clicked()
    {
        if(jlinkConnectHandle())
        {
            infoShowHandle(tr("获取CPU ID中,请稍后..."));
            infoShowHandle(tr("获取CPUID成功: ") + jlinkGetCpuIdHandle());
            jlinkdisconnectHandle();
        }
    }
    • 同理,擦除flash也是要先连接上设备,然后通过调用jlinkEraseChipFunc方法进行擦除之后,断开连接:
    void RJLinkView::on_clearFlashPushButton_clicked()
    {
        if(jlinkConnectHandle())
        {
            infoShowHandle(tr("擦除flash中,请稍后..."));
            jlinkEraseChipFunc();
            infoShowHandle(tr("擦除flash成功!"));
            jlinkdisconnectHandle();
        }
    }
    • 一键烧录按钮原理,获取固件内容存储到一个缓冲区中,然后启动一个定时器,然后将固件内容搬运到对应的Flash区域:
    void RJLinkView::on_burnPushButton_clicked()
    {
        bool burnAddrIsOk = false;
        QFile burnFile;

        burnFileSize = 0;
        burnFileContent.clear();

        if(ui->fwFilePathLineEdit->text() == tr(""))
        {
            infoShowHandle(tr("请选择要烧录的固件!"));
            return;
        }

        if(jlinkConnectHandle())
        {
            burnAddr = ui->burnAddrLineEdit->text().trimmed().toInt(&burnAddrIsOk, 16);
            if(!burnAddrIsOk)
            {
                infoShowHandle(tr("烧录起始地址格式有误,请正确输入地址..."));
                jlinkdisconnectHandle();
                return;
            }
            burnFile.setFileName(ui->fwFilePathLineEdit->text());
            burnFile.open(QIODevice::ReadOnly);
            if(burnFile.isOpen())
            {
                burnFileContent = burnFile.readAll();
                burnFileSize = burnFileContent.size();
                burnFile.close();

                infoShowHandle(tr("开始烧录固件, 请稍后..."));

                burnFileTimer->start(RJLINK_BURN_TIME_INTERVAL);
            }
            else
            {
                infoShowHandle(tr("烧录固件打开失败,请检查文件是否存在..."));
                jlinkdisconnectHandle();
            }
        }
    }
    • 定时器处理函数,每次烧录从缓冲区提取1k数据进行烧录:
    void RJLinkView::burnFileTimerHandle(void)
    {
        int burnPercent = 0;
        if(burnFileTimer)
        {
            burnFileTimer->stop();

            if(burnFileContent.isEmpty())
            {
                ui->burnProgressBar->setValue(100);
                jlinkdisconnectHandle();
                infoShowHandle(tr("烧录完成!"));
                return;
            }
            else
            {
                if(burnFileContent.size() > RJLINK_BURN_CONTENT_SIZE)       // 每次搬运1K数据
                {
                    jlinkWriteMemFunc(burnAddr, RJLINK_BURN_CONTENT_SIZE, burnFileContent.data());
                    burnAddr += RJLINK_BURN_CONTENT_SIZE;
                    burnFileContent.remove(0, RJLINK_BURN_CONTENT_SIZE);
                }
                else
                {
                    jlinkWriteMemFunc(burnAddr, burnFileContent.size(), burnFileContent.data());
                    burnAddr += burnFileContent.size();
                    burnFileContent.clear();
                }

                burnPercent = (burnFileSize - burnFileContent.size()) * 100 / burnFileSize;
                ui->burnProgressBar->setValue(burnPercent);
                burnFileTimer->start(RJLINK_BURN_TIME_INTERVAL);
            }
        }
    }

    演示实例

  • 获取CPU ID演示,点击"获取CPU ID"按钮,在显示窗体便可以看到对应的ID:
  • 擦除flash演示,点击"擦除flash"按钮,会调用SEGGER应用,然后进行flash擦除:
  • 烧录程序,点击"一键烧录"按钮,同样会调用SEGGER应用,然后进行烧录: