概述
- 作者一直有一个想法,就是写一个功能强大的桌面小工具,里面集成各种平时开发要用的工具。例如:串口助手,网络助手,下载工具等。那么如何也带来几个问题:
- 问题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 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", 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应用,然后进行烧录:
作者:Rice 嵌入式开发技术分享
文章来源:Rice 嵌入式开发技术分享
推荐阅读
更多嵌入式开发干货请关注 Rice 嵌入式开发技术分享 专栏。欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。