QT编写一个JLINK烧录工具

概述
作者一直有一个想法,就是写一个功能强大的桌面小工具,里面集成各种平时开发要用的工具。例如:串口助手,网络助手,下载工具等。那么如何也带来几个问题:
问题1:那么如何呈现在桌面上也是一个非常重要的问题 -- 桌面悬浮窗。
问题2:工具的名字 -- rtool(米饭工具)
问题3:是否贡献整个工具 -- 分为两个版本:开源版本和公司项目版本(已经发布了v1.0版本)。
本篇文章介绍rtool的jlink烧录小工具,那为什么要在rtool中集成jlink的烧录工具呢?原因:
像mcu,我们如果使用gcc构建我们的程序后,没有ide的支撑,就需要使用jflash这样的工具进行烧录,这个操作流程还是挺多步骤的。
方便我们对固件进行动手术,如对固件进行加密处理,对芯片ram,flash进行随心所欲的操作。
原理说明
我们在使用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          2typedef 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([hhss.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, 0, 0);        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(0x1fff7a10, 12, 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应用,然后进行烧录:


目标检测YOLO系列算法的发展过程
后疫情时代智慧医院建设更应强调人、财、物的精细化管理
电感元件的作用_电感元件的特性是什么
三星未获得代工骁龙865芯片的机会,采用台积电的7n“N7P”工艺
电气工程与智能电网迫切需要攻克这些核心技术
QT编写一个JLINK烧录工具
航顺主流级32位MCU系列的MCU-HK32C0家族介绍
电力系统用单相逆变电源的研制
嵌入式技术的应用与就业方向
新起点,新征程,WAYON维安DCDC炼成之路
鸿利智汇Mini LED显示屏获CDIA年度最佳显示模组组件产品奖
VC供应瓶颈将成为LFP电池市场进一步增长的障碍
英特尔描绘融入AI的未来折叠屏PC
TSMC OIP创新平台-EDA认证工具
8吋晶圆产能吃紧,已确定会涨价
华为提出Sorted LLaMA:SoFT代替SFT,训练多合一大语言模型
什么是串扰?PCB走线串扰详解
重磅!成立仅一年的新公司凭什么私有化上市公司先进半导体?
高效节能台式机首次纳入“节能补贴”范围
福特将增加电动汽车投资?新投资额是此前的1倍以上