当前位置:网站首页>自制J-Flash烧录工具——Qt调用jlinkARM.dll方式
自制J-Flash烧录工具——Qt调用jlinkARM.dll方式
2022-07-06 14:29:00 【fangye945a】
背景介绍
想必玩过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);
函数指针定义
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; //读内存
函数入口加载
//构造函数初始化时,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失败!!";
}
}
连接设备
所有操作前必需确保设备已经连接,如下为设备连接的示例程序。
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;
}
读取芯片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();
}
}
烧录程序
烧录程序考虑到会持续一段时间,可能会导致界面假死。所以使用了一个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后继续烧录
}
}
}
学习参考传送门
在开发过程中,踩了很多坑,在此整理几个有借鉴意义的文章。
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
本文章仅供学习参考,不可用于商业用途,如侵权请联系删除。
边栏推荐
猜你喜欢

Embedded common computing artifact excel, welcome to recommend skills to keep the document constantly updated and provide convenience for others

数据处理技巧(7):MATLAB 读取数字字符串混杂的文本文件txt中的数据

嵌入式常用计算神器EXCEL,欢迎各位推荐技巧,以保持文档持续更新,为其他人提供便利
Learn the principle of database kernel from Oracle log parsing

About the professional ethics of programmers, let's talk about it from the way of craftsmanship and neatness

Adjustable DC power supply based on LM317

二叉(搜索)树的最近公共祖先 ●●

HDR image reconstruction from a single exposure using deep CNNs阅读札记

labelimg的安装与使用

Oracle control file and log file management
随机推荐
Management background --1 Create classification
lora同步字设置
Maximum product of three numbers in question 628 of Li Kou
Unity3D学习笔记6——GPU实例化(1)
GPS from getting started to giving up (XVIII), multipath effect
Oracle性能分析3:TKPROF简介
GNN, please deepen your network layer~
Seata聚合 AT、TCC、SAGA 、 XA事务模式打造一站式的分布式事务解决方案
[sciter]: encapsulate the notification bar component based on sciter
将MySQL的表数据纯净方式导出
MariaDb数据库管理系统的学习(一)安装示意图
GNN,请你的网络层数再深一点~
Shell product written examination related
Oracle-控制文件及日志文件的管理
[MySQL] online DDL details
GPS from getting started to giving up (16), satellite clock error and satellite ephemeris error
Search element topic (DFS)
Unity3d Learning Notes 6 - GPU instantiation (1)
GPS從入門到放弃(十三)、接收機自主完好性監測(RAIM)
anaconda安装第三方包