电子说
概述
作者一直有一个想法,就是写一个功能强大的桌面小工具,里面集成各种平时开发要用的工具。例如:串口助手,网络助手,下载工具等。那么如何也带来几个问题:
问题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("[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应用,然后进行烧录:
审核编辑:刘清
全部0条评论
快来发表一下你的评论吧 !