Creating a JLINK Programming Tool with QT

Overview

  • The author has always had an idea to create a powerful desktop tool that integrates various tools needed for development, such as: serial port assistant, network assistant, download tools, etc. However, this raises several questions:
  1. Question 1: How to present it on the desktop is also a very important issue — desktop floating window.
  2. Question 2: The name of the tool — RTOOL (Rice Tool)
  3. Question 3: Should the entire tool be open-sourced — it is divided into two versions: open-source version and company project version (the V1.0 version has been released).
Creating a JLINK Programming Tool with QT
  • This article introduces the JLINK programming tool of RTOOL. Why integrate the JLINK programming tool into RTOOL? The reasons are:
  1. For MCUs, after using GCC to build our program, without the support of an IDE, we need to use tools like JFLASH for programming, which involves quite a few steps.
Creating a JLINK Programming Tool with QT
  1. It facilitates us to perform operations on the firmware, such as encrypting the firmware and manipulating the chip’s RAM and flash at will.
  • Reference: https://www.amobbs.com/thread-5690311-1-1.html?_dsign=48d76ae6

Principle Explanation

  • When we use the JFlash programming tool, it actually operates by calling the interfaces provided by the JLinkARM.dll dynamic library. We can analyze the JLinkARM.dll using Dependency Walker to obtain all function symbols in the DLL.
Creating a JLINK Programming Tool with QT
  • QT provides the QLibrary class to dynamically load DLLs, so combined with the obtained function symbols, we can define a series of function pointers pointing to the corresponding symbols.
Creating a JLINK Programming Tool with QT

Development Process

  1. UI design can actually be very simple, and the goal is to simplify the operation process of JFlash:
Creating a JLINK Programming Tool with QT
  1. Define a series of function pointers to interface with the dynamic library JLinkARM.dll, header file 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
  1. Load the DLL using QT’s QLibrary class, then point the function pointers to the corresponding function symbols:
  • Define variables corresponding to the function pointer types defined in the header file 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;
  • Obtain the corresponding function pointers from the dynamic library (JLinkARM.dll)
void RJLinkView::jlinkLibLoadHandle(void)
{
    jlinkLib = new QLibrary("JLinkARM.dll");
    if(jlinkLib->load())
    {
        rjlinkOpenFuncPtr = (rjlinkOpenFunc)jlinkLib->resolve("JLINKARM_Open");                             // Open device
        rjlinkCloseFuncPtr = (rjlinkCloseFunc)jlinkLib->resolve("JLINKARM_Close");                          // Close device
        rjlinkIsOpenFuncPtr = (rjlinkIsOpenFunc)jlinkLib->resolve("JLINKARM_IsOpen");                       // Check if device is open
        rjlinkTIFSelectFuncPtr = (rjlinkTIFSelectFunc)jlinkLib->resolve("JLINKARM_TIF_Select");             // Select device
        rjlinkSetSpeedFuncPtr = (rjlinkSetSpeedFunc)jlinkLib->resolve("JLINKARM_SetSpeed");                 // Set programming speed
        rjlinkGetSpeedFuncPtr = (rjlinkGetSpeedFunc)jlinkLib->resolve("JLINKARM_GetSpeed");                 // Get programming speed
        rjlinkResetFuncPtr = (rjlinkResetFunc)jlinkLib->resolve("JLINKARM_Reset");                          // Reset device
        rjlinkHaltFuncPtr = (rjlinkHaltFunc)jlinkLib->resolve("JLINKARM_Halt");                             // Interrupt program execution
        rjlinkReadMemFuncPtr = (rjlinkReadMemFunc)jlinkLib->resolve("JLINKARM_ReadMem");                    // Read memory
        rjlinkWriteMemFuncPtr = (rjlinkWriteMemFunc)jlinkLib->resolve("JLINKARM_WriteMem");                 // Write memory
        rjlinkEraseChipFuncPtr = (rjlinkEraseChipFunc)jlinkLib->resolve("JLINK_EraseChip");                 // Erase chip
        rjlinkExecCommandFuncPtr = (rjlinkExecCommandFunc)jlinkLib->resolve("JLINKARM_ExecCommand");        // Execute command
        rjlinkGetDLLVersionFuncPtr = (rjlinkGetDLLVersionFunc)jlinkLib->resolve("JLINKARM_GetDLLVersion");  // Get DLL version
        rjlinkGetSNFuncPtr = (rjlinkGetSNFunc)jlinkLib->resolve("JLINKARM_GetSN");                          // Get SN
        rjlinkGetIdFuncPtr = (rjlinkGetIdFunc)jlinkLib->resolve("JLINKARM_GetId");                          // Get ID
        rjlinkConnectFuncPtr = (rjlinkConnectFunc)jlinkLib->resolve("JLINKARM_Connect");                    // Connect device
        rjlinkIsConnectedFuncPtr = (rjlinkIsConnectedFunc)jlinkLib->resolve("JLINKARM_IsConnected");        // Check if device is connected
    }
}
  • The above function pointers are not user-friendly for access operations, so we encapsulate them through class methods to avoid null pointer access:
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;
}
  • Set the display format for the download information form to facilitate previewing our operation steps, so a display format with a timestamp is specified:
void RJLinkView::infoShowHandle(QString info)
{
    QString timestamp = QDateTime::currentDateTime().toString("[hh:mm:ss.zzz]==> ");
    ui->burnInfoTextBrowser->append(timestamp + info);
}
  • Connect to the chip device, the following takes STM32F407IG as an example, the download method uses SWD, and the download speed is set to 4000kHz:
bool RJLinkView::jlinkConnectHandle(void)
{
    if(jlinkIsOpen())
    {
        infoShowHandle(tr("Device connected successfully"));
        return true;
    }
    jlinkOpen();
    if(jlinkIsOpen())
    {
        jlinkExecCommandFunc("device = STM32F407IG", 0, 0);
        jlinkTIFSelectFunc(JLINKARM_TIF_SWD);
        jlinkSetSpeedFunc(4000);
        jlinkConnectFunc();
        if(jlinkIsConnectedFunc()){
            infoShowHandle(tr("Device connected successfully"));
            return true;
        }else
        {
            infoShowHandle(tr("Failed to connect to device! Please check the device connection..."));
        }
    }
    else
    {
        infoShowHandle(tr("Failed to connect to device! Please check the programmer connection..."));
    }
    return false;
}
  • Disconnect the chip device:
void RJLinkView::jlinkdisconnectHandle(void)
{
    jlinkClose();
    if(!jlinkIsOpen())
    {
        infoShowHandle(tr("Device disconnected successfully!"));
    }
    else {
        infoShowHandle(tr("Failed to disconnect device..."));
    }
}
  • Obtain the CPU ID, the principle is quite simple, by reading the corresponding UUID register, we can get the UUID of the chip:
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);
}
  • Implement the firmware selection button, specifying the file suffix as .bin format:
void RJLinkView::on_fwFilePathSelectPushButton_clicked()
{
    QString fwFileName = QFileDialog::getOpenFileName(this, "Firmware file", QCoreApplication::applicationDirPath(), "fw file(*.bin)");
    ui->fwFilePathLineEdit->setText(fwFileName);
}
  • Implement the clear information display form button:
void RJLinkView::on_cleanInfoPushButton_clicked()
{
    ui->burnInfoTextBrowser->clear();
}
  • Implement the button to get the chip ID, which requires first connecting to the device, then calling the jlinkGetCpuIdHandle method to obtain the ID and then disconnecting:
void RJLinkView::on_getCpuIdPushButton_clicked()
{
    if(jlinkConnectHandle())
    {
        infoShowHandle(tr("Getting CPU ID, please wait..."));
        infoShowHandle(tr("Successfully obtained CPUID: ") + jlinkGetCpuIdHandle());
        jlinkdisconnectHandle();
    }
}
  • Similarly, erasing flash also requires first connecting to the device, then calling the jlinkEraseChipFunc method to perform the erase, and then disconnecting:
void RJLinkView::on_clearFlashPushButton_clicked()
{
    if(jlinkConnectHandle())
    {
        infoShowHandle(tr("Erasing flash, please wait..."));
        jlinkEraseChipFunc();
        infoShowHandle(tr("Flash erased successfully!"));
        jlinkdisconnectHandle();
    }
}
  • The principle of the one-click programming button is to store the firmware content into a buffer, then start a timer, and transfer the firmware content to the corresponding Flash area:
void RJLinkView::on_burnPushButton_clicked()
{
    bool burnAddrIsOk = false;
    QFile burnFile;

    burnFileSize = 0;
    burnFileContent.clear();

    if(ui->fwFilePathLineEdit->text() == tr("")
    {
        infoShowHandle(tr("Please select the firmware to program!"));
        return;
    }

    if(jlinkConnectHandle())
    {
        burnAddr = ui->burnAddrLineEdit->text().trimmed().toInt(&burnAddrIsOk, 16);
        if(!burnAddrIsOk)
        {
            infoShowHandle(tr("The format of the programming starting address is incorrect, please enter the address correctly..."));
            jlinkdisconnectHandle();
            return;
        }
        burnFile.setFileName(ui->fwFilePathLineEdit->text());
        burnFile.open(QIODevice::ReadOnly);
        if(burnFile.isOpen())
        {
            burnFileContent = burnFile.readAll();
            burnFileSize = burnFileContent.size();
            burnFile.close();

            infoShowHandle(tr("Starting to program firmware, please wait..."));

            burnFileTimer->start(RJLINK_BURN_TIME_INTERVAL);
        }
        else
        {
            infoShowHandle(tr("Failed to open firmware for programming, please check if the file exists..."));
            jlinkdisconnectHandle();
        }
    }
}
  • The timer processing function extracts 1k of data from the buffer for programming each time:
void RJLinkView::burnFileTimerHandle(void)
{
    int burnPercent = 0;
    if(burnFileTimer)
    {
        burnFileTimer->stop();

        if(burnFileContent.isEmpty())
        {
            ui->burnProgressBar->setValue(100);
            jlinkdisconnectHandle();
            infoShowHandle(tr("Programming complete!"));
            return;
        }
        else
        {
            if(burnFileContent.size() > RJLINK_BURN_CONTENT_SIZE)       // Move 1K data each time
            {
                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);
        }
    }
}

Demo Example

  1. Demo for obtaining CPU ID, click the “Get CPU ID” button, and the corresponding ID can be seen in the display form:
Creating a JLINK Programming Tool with QT
  1. Demo for erasing flash, click the “Erase Flash” button, which will call the SEGGER application to perform the flash erase:
Creating a JLINK Programming Tool with QT
  1. Programming demo, click the “One-Click Programming” button, which will also call the SEGGER application to perform the programming:

Creating a JLINK Programming Tool with QTCreating a JLINK Programming Tool with QT

Creating a JLINK Programming Tool with QT

Leave a Comment