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