当前位置:网站首页>自制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
本文章仅供学习参考,不可用于商业用途,如侵权请联系删除。
边栏推荐
- HDR image reconstruction from a single exposure using deep CNNs阅读札记
- Data processing skills (7): MATLAB reads the data in the text file TXT with mixed digital strings
- Unity3d Learning Notes 6 - GPU instantiation (1)
- HDU 2008 digital statistics
- GPS从入门到放弃(十三)、接收机自主完好性监测(RAIM)
- GPS from getting started to giving up (16), satellite clock error and satellite ephemeris error
- Classic sql50 questions
- 【sdx62】WCN685X将bdwlan.bin和bdwlan.txt相互转化操作方法
- 中国1,4-环己烷二甲醇(CHDM)行业调研与投资决策报告(2022版)
- GPS從入門到放弃(十三)、接收機自主完好性監測(RAIM)
猜你喜欢
PVL EDI project case
ZABBIX proxy server and ZABBIX SNMP monitoring
数据处理技巧(7):MATLAB 读取数字字符串混杂的文本文件txt中的数据
Oracle-控制文件及日志文件的管理
GPS從入門到放弃(十三)、接收機自主完好性監測(RAIM)
C # réalise la liaison des données du rapport Crystal et l'impression du Code à barres 4
(十八)LCD1602实验
2020 Bioinformatics | GraphDTA: predicting drug target binding affinity with graph neural networks
GPS from getting started to giving up (XIII), receiver autonomous integrity monitoring (RAIM)
[sciter]: encapsulate the notification bar component based on sciter
随机推荐
[linear algebra] determinant of order 1.3 n
GPS from getting started to giving up (12), Doppler constant speed
[leetcode daily clock in] 1020 Number of enclaves
Assembly and interface technology experiment 5-8259 interrupt experiment
RESNET rs: Google takes the lead in tuning RESNET, and its performance comprehensively surpasses efficientnet series | 2021 arXiv
0 basic learning C language - interrupt
labelimg的安装与使用
Unity3D学习笔记6——GPU实例化(1)
Xiaoman network model & http1-http2 & browser cache
GPS from getting started to giving up (XIII), receiver autonomous integrity monitoring (RAIM)
[MySQL] online DDL details
Maximum product of three numbers in question 628 of Li Kou
CCNA-思科网络 EIGRP协议
2022-07-05 使用tpcc对stonedb进行子查询测试
2022-07-05 stonedb的子查询处理解析耗时分析
2020 Bioinformatics | GraphDTA: predicting drug target binding affinity with graph neural networks
【10点公开课】:视频质量评价基础与实践
Embedded common computing artifact excel, welcome to recommend skills to keep the document constantly updated and provide convenience for others
BarcodeX(ActiveX打印控件) v5.3.0.80 免费版使用
微信红包封面小程序源码-后台独立版-带测评积分功能源码