• 自制J-Flash烧录工具——Qt调用jlinkARM.dll方式


    背景介绍

    想必玩过STM32、GD32的同学都用过下面这个烧录工具吧,它就是J-Flash。通过它再配合我们购买的jlink、jlink-ob等烧录器,便能够非常方便的实现对cortext-M系列的单片机进行程序烧录。
    在这里插入图片描述
    但是在产品的生产和应用过程中,我们通常都会有一些定制化需求,比如读取MCU的芯片ID,根据芯片ID计算出秘钥,烧录程序的同时将秘钥烧录进Flash的某个区域,从而实现软件加密等功能。

    原理分析

    经过网上大佬们的分析得到一个内部消息,J-Flash烧录工具的大部分接口都封装在安装目录下的JLinkARM.dll动态库中。

    知道这个消息后,我们可以使用depends工具对dll库的进行分析, 获取到dll库中所有函数符号,然后在网上查找这些函数的参数类型和个数,然后定义一个函数指针,将指针指向该函数在dll中的地址,从而实现调用。

    调用方法

    理论上,只要是windows的程序都可以调用dll。
    通过Visual Studio( C#)调用JLinkARM.dll,实现程序下载已经有大佬写过文章了。
    在此我就以Qt( C++)为例,调用JLinkARM.dll,实现程序下载功能和芯片ID读取功能。调用方式是采用QLibrary类显式调用的方式。

    示例程序

    下面展示部分示例程序供大家参考,包括函数指针的定义、使用方法、设备连接、读取、烧录的步骤等。

    宏定义及函数指针类型定义
    //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 (*JLINKARM_Open_Func_Ptr)(void);       // 定义导出函数类型
    typedef void (*JLINKARM_Close_Func_Ptr)(void);
    typedef DWORD (*JLINKARM_TIF_Select_Func_Ptr)(int);
    typedef void (*JLINKARM_SetSpeed_Func_Ptr)(int);
    typedef void (*JLINKARM_Reset_Func_Ptr)(void);
    typedef void  (*JLINKARM_Go_Func_Ptr)(void);
    typedef BOOL (*JLINKARM_IsOpen_Func_Ptr)(void);
    
    typedef void  (*JLINKARM_SetLogFile_Func_Ptr)(char *file);
    typedef DWORD (*JLINKARM_GetDLLVersion_Func_Ptr)(void);
    typedef DWORD (*JLINKARM_GetHardwareVersion_Func_Ptr)(void);
    typedef DWORD (*JLINKARM_GetFirmwareString_Func_Ptr)(char *buff, int count);
    typedef DWORD (*JLINKARM_GetSN_Func_Ptr)(void);
    
    typedef BOOL  (*JLINKARM_ExecCommand_Func_Ptr)(char* cmd, int a, int b);
    typedef DWORD (*JLINKARM_TIF_Select_Func_Ptr)(int type);
    typedef void  (*JLINKARM_SetSpeed_Func_Ptr)(int speed);
    typedef DWORD (*JLINKARM_GetSpeed_Func_Ptr)(void);
    typedef DWORD (*JLINKARM_GetId_Func_Ptr)(void);
    typedef DWORD (*JLINKARM_GetDeviceFamily_Func_Ptr)(void);
    
    typedef BOOL  (*JLINKARM_Open_Func_Ptr)(void);
    typedef void  (*JLINKARM_Close_Func_Ptr)(void);
    typedef BOOL  (*JLINKARM_IsOpen_Func_Ptr)(void);
    typedef BOOL  (*JLINKARM_Connect_Func_Ptr)(void);
    typedef BOOL  (*JLINKARM_IsConnected_Func_Ptr)(void);
    typedef int   (*JLINKARM_Halt_Func_Ptr)(void);
    typedef BOOL  (*JLINKARM_IsHalted_Func_Ptr)(void);
    typedef void  (*JLINKARM_SetResetType_Func_Ptr)(int type);
    typedef void  (*JLINKARM_Reset_Func_Ptr)(void);
    typedef void  (*JLINKARM_Go_Func_Ptr)(void);
    typedef void  (*JLINKARM_GoIntDis_Func_Ptr)(void);
    typedef DWORD (*JLINKARM_ReadReg_Func_Ptr)(int index);
    typedef int   (*JLINKARM_WriteReg_Func_Ptr)(int index, DWORD data);
    
    typedef int   (*JLINKARM_ReadMem_Func_Ptr)(DWORD addr, int len, void *buf);
    typedef int   (*JLINKARM_WriteMem_Func_Ptr)(DWORD addr, int len, void *buf);
    typedef int   (*JLINKARM_WriteU8_Func_Ptr)(DWORD addr, BYTE data);
    typedef int   (*JLINKARM_WriteU16_Func_Ptr)(DWORD addr, WORD data);
    typedef int   (*JLINKARM_WriteU32_Func_Ptr)(DWORD addr, DWORD data);
    
    typedef int   (*JLINK_EraseChip_Func_Ptr)(void);
    typedef int   (*JLINKARM_DownloadFile_Func_Ptr)(LPCSTR file, DWORD addr);
    typedef void  (*JLINKARM_BeginDownload_Func_Ptr)(int index);
    typedef void  (*JLINKARM_EndDownload_Func_Ptr)(void);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    函数指针定义
    JLINKARM_GetDLLVersion_Func_Ptr JLINKARM_GetDLLVersion_Entry = NULL;   //获取DLL版本
    JLINKARM_Open_Func_Ptr JLINKARM_Open_Entry = NULL;               //打开设备
    JLINKARM_IsOpen_Func_Ptr JLINKARM_IsOpen_Entry = NULL;           //是否已经打开
    JLINKARM_Close_Func_Ptr JLINKARM_Close_Entry = NULL;             //关闭设备
    JLINKARM_TIF_Select_Func_Ptr JLINKARM_TIF_Select_Entry = NULL;   //选择设备
    JLINKARM_SetSpeed_Func_Ptr JLINKARM_SetSpeed_Entry = NULL;       //设置JLINK接口速度 0为自动调整
    JLINKARM_Reset_Func_Ptr JLINKARM_Reset_Entry = NULL;             //复位系统
    JLINKARM_Halt_Func_Ptr JLINKARM_Halt_Entry = NULL;               //中断程序执行,进入停止状态
    JLINKARM_Go_Func_Ptr JLINKARM_Go_Entry = NULL;                   //执行程序
    JLINKARM_WriteMem_Func_Ptr JLINKARM_WriteMem_Entry = NULL;       //写内存
    JLINKARM_ReadMem_Func_Ptr JLINKARM_ReadMem_Entry = NULL;         //读内存
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    函数入口加载
    //构造函数初始化时,new QLibaray时传入使用的dll文件
    QLibrary jlink_lib = new QLibrary("JLinkARM.dll");
    
    void Widget::load_library_function()
    {
        if(jlink_lib->load()){
            qDebug()<<"加载JLinkARM.dll成功, 开始解析函数";
            JLINKARM_Open_Entry = (JLINKARM_Open_Func_Ptr)jlink_lib->resolve("JLINKARM_Open");
            JLINKARM_IsOpen_Entry = (JLINKARM_IsOpen_Func_Ptr)jlink_lib->resolve("JLINKARM_IsOpen");
            JLINKARM_Close_Entry = (JLINKARM_Close_Func_Ptr)jlink_lib->resolve("JLINKARM_Close");
            JLINKARM_ExecCommand_Entry = (JLINKARM_ExecCommand_Func_Ptr)jlink_lib->resolve("JLINKARM_ExecCommand");
            JLINKARM_GetDLLVersion_Entry = (JLINKARM_GetDLLVersion_Func_Ptr)jlink_lib->resolve("JLINKARM_GetDLLVersion");
            JLINKARM_TIF_Select_Entry = (JLINKARM_TIF_Select_Func_Ptr)jlink_lib->resolve("JLINKARM_TIF_Select");
            JLINKARM_SetSpeed_Entry = (JLINKARM_SetSpeed_Func_Ptr)jlink_lib->resolve("JLINKARM_SetSpeed");
            JLINKARM_GetSpeed_Entry = (JLINKARM_GetSpeed_Func_Ptr)jlink_lib->resolve("JLINKARM_GetSpeed");
            JLINKARM_Connect_Entry = (JLINKARM_Connect_Func_Ptr)jlink_lib->resolve("JLINKARM_Connect");
            JLINKARM_IsConnected_Entry = (JLINKARM_IsConnected_Func_Ptr)jlink_lib->resolve("JLINKARM_IsConnected");
            JLINKARM_GetId_Entry = (JLINKARM_GetId_Func_Ptr)jlink_lib->resolve("JLINKARM_GetId");
            JLINKARM_GetSN_Entry = (JLINKARM_GetSN_Func_Ptr)jlink_lib->resolve("JLINKARM_GetSN");
            JLINKARM_Reset_Entry = (JLINKARM_Reset_Func_Ptr)jlink_lib->resolve("JLINKARM_Reset");
            JLINKARM_Halt_Entry = (JLINKARM_Halt_Func_Ptr)jlink_lib->resolve("JLINKARM_Halt");
            JLINKARM_WriteMem_Entry = (JLINKARM_WriteMem_Func_Ptr)jlink_lib->resolve("JLINKARM_WriteMem");
            JLINKARM_ReadMem_Entry = (JLINKARM_ReadMem_Func_Ptr)jlink_lib->resolve("JLINKARM_ReadMem");
            JLINK_EraseChip_Entry = (JLINK_EraseChip_Func_Ptr)jlink_lib->resolve("JLINK_EraseChip");
            qDebug()<<"解析函数完成";
        }
        else
        {
            qDebug()<<"加载JLinkARM.dll失败!!";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    连接设备

    所有操作前必需确保设备已经连接,如下为设备连接的示例程序。

    bool Widget::connect_device()
    {
        if(JLINKARM_IsOpen())
        {
            qDebug()<<"JLINKARM was Opened!";
            return true;
        }
        qDebug()<<"Try Open JLINKARM...";
        JLINKARM_Open();
        if(JLINKARM_IsOpen())
        {
            qDebug()<<"JLINKARM Open success!";
            JLINKARM_ExecCommand("device = STM32F407IG", 0, 0);
            JLINKARM_TIF_Select(JLINKARM_TIF_SWD);
            JLINKARM_SetSpeed(4000); //设置下载速度
            JLINKARM_Connect();
            if(JLINKARM_IsConnected()){
                return true;
            }else
            {
                print_log("连接设备失败! 请检查设备连接..");
            }
        }
        else {
            qDebug()<<"JLINKARM Open fail!";
            print_log("连接设备失败! 请检查烧录器连接..");
        }
        return false;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    读取芯片ID

    连接设备后我们可以通过JLINKARM_ReadMem接口,读取芯片ID,也就是读取CPUID寄存器地址的值。

    QString Widget::get_cpu_id()
    {
        unsigned char cpuid[12]={0};
        char cpu_id_tmp[128]={0};
        JLINKARM_ReadMem(0x1FFF7A10, 12, cpuid);
        JLINKARM_ReadMem(0x1FFF7A10, 12, cpuid); //多读一次解决读取错误的问题
        sprintf(cpu_id_tmp, "%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]);
        for(int i=0; i<sizeof(cpuid); i++)
            qDebug("cpuid[%d]=%02X", i, cpuid[i]);
        qDebug("cpuid=%s",cpu_id_tmp);
        return QString(cpu_id_tmp);
    }
    
    void Widget::on_pushButton_cpuid_clicked()
    {
        static int cpuid_reading_flag = 0;
        if(cpuid_reading_flag)
        {
            print_log(QString("获取CPUID中,请稍后..."));
            return;
        }
        if( connect_device()){
    //        qDebug("JLink Info:");
    //        qDebug("SN = %08u", JLINKARM_GetSN());
    //        qDebug("ID = %08X", JLINKARM_GetId());
    //        qDebug("VER = %u", JLINKARM_GetDLLVersion());
    //        qDebug("Speed = %u", JLINKARM_GetSpeed());
            print_log(QString("获取CPUID中,请稍后..."));
            cpu_id_str = get_cpu_id();
            print_log(QString("获取CPUID成功: ")+cpu_id_str);
            disconnect_device();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    烧录程序

    烧录程序考虑到会持续一段时间,可能会导致界面假死。所以使用了一个QTimer定时器来实现程序烧录过程,每次定时结束时烧录1KB数据,同时更新烧录进度条,直到烧录结束。但是在实际使用过程中,感觉并没有真正将程序烧录至MCU中去,只是传递至jlinkARM.dll接口的内存中去了。而在断开连接时,会自动触发jlinkARM.dll中烧录功能,会弹出一个JFLASH的小烧录窗口进行真正的烧录。

    //构造函数初始化时,连接定时器超时信号与槽函数
    connect(timer_burn, SIGNAL(timeout()),this, SLOT(on_timer_burn_timeout()));
    
    void Widget::on_pushButton_burn_clicked()
    {
        if(burnning_flag)
        {
            print_log("正在加速烧录中,请稍后...");
            return;
        }
        burn_bin_data.clear();
        total_file_size = 0;
        if(target_bin_path.isEmpty())
        {
            print_log("请选择要烧录的固件!");
            return;
        }
    
        if( connect_device() ){ //连接设备
    
            cpu_id_str = get_cpu_id();// 获取CPUID
    
            bool ok = false;        //检查起始地址信息
            write_start_addr = ui->lineEdit_start_addr->text().trimmed().toInt(&ok, 16);
            if(!ok)
            {
                print_log("烧录起始地址格式有误!");
                disconnect_device();
                return;
            }
    
            QFile burn_file;
            burn_file.setFileName(target_bin_path);
            burn_file.open(QIODevice::ReadOnly); //打开文件
            if(burn_file.isOpen())
            {
                burn_bin_data = burn_file.readAll();    //将要烧录的数据读取到内存中
                burn_file.close();  //关闭文件
                if(burn_bin_data.size() > 1024*1024)
                {
                    print_log("文件大小不允许超过1MB!");
                    burn_bin_data.clear();
                    disconnect_device();
                    return;
                }
                print_log("开始烧录固件, 请稍后...");
                burnning_flag = 1;  //正在烧录
                timer_burn->start(BURN_DELAY);
            }
            else
            {
                print_log("打开固件失败, 请检查文件是否存在!");
                disconnect_device();
            }
        }
    }
    
    void Widget::on_timer_burn_timeout()
    {
        if(timer_burn)
        {
            timer_burn->stop();
            //烧写固件
            if(burn_bin_data.isEmpty())  //烧录完成
            {
                ui->progressBar->setValue(100);
                burnning_flag = 0;
                disconnect_device();
                print_log("烧录完成!");
                return;
            }
            else  //烧录的数据非空
            {
                if(burn_bin_data.size() > BURN_STEP_SIZE)   //大小超过1K
                {
                    int ret = JLINKARM_WriteMem(write_start_addr, BURN_STEP_SIZE, burn_bin_data.data());
    //                qDebug()<<"JLINKARM_WriteMem ret = "<<ret;
                    write_start_addr += BURN_STEP_SIZE; //烧写地址递增
                    burn_bin_data.remove(0, BURN_STEP_SIZE);
                }
                else    //大小不到1K
                {
                    int ret = JLINKARM_WriteMem(write_start_addr, burn_bin_data.size(), burn_bin_data.data());
    //                qDebug()<<"JLINKARM_WriteMem ret = "<<ret;
                    write_start_addr += burn_bin_data.size(); //烧写地址递增
                    burn_bin_data.clear();
                }
    
                unsigned int percent = 1.0*(total_file_size - burn_bin_data.size())/total_file_size*100;
    //            qDebug()<<"Burn progress: "<<percent;
                ui->progressBar->setValue(percent);
                timer_burn->start(BURN_DELAY);//5ms后继续烧录
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96

    学习参考传送门

    在开发过程中,踩了很多坑,在此整理几个有借鉴意义的文章。
    https://www.amobbs.com/thread-5670237-1-1.html
    https://www.amobbs.com/thread-5718918-1-1.html
    https://blog.csdn.net/qq446252221/article/details/89878996

    免责声明

    segger官网有配套的SDK,包含完整的头文件、库文件及PDF文档,有条件的同学建议支持正版。
    https://www.segger.com/products/debug-probes/j-link/technology/j-link-sdk
    在这里插入图片描述
    本文章仅供学习参考,不可用于商业用途,如侵权请联系删除。

  • 相关阅读:
    Astyle Linux风格配置
    零数科技受邀参加中国投资峰会
    阿里直呼真省钱,全网首发IntelliJ IDEA应用实战手册竟遭哄抢
    鸿蒙开发实战-手写一个Openharmony投屏工具
    laravel composer require require-dev和APP_ENV的使用场景
    2023.9.20 简单了解 HTTP协议 及 Fiddle 安装使用
    天地图key申请-加载地图空白问题
    智能电表远程抄表在电力系统中的运用分析
    数据挖掘(1)概述
    2024清理mac苹果电脑内存免费工具CleanMyMac X4.15
  • 原文地址:https://blog.csdn.net/fangye945a/article/details/125549658