J-LINK RTT应用

RTT(Real Time Transfer) 是SEGGER公司推出的一种嵌入式调试技术,允许在目标芯片运行时双向通信。

相较于串口,速度更快,无需占用额外引脚,配置简单,对CPU占用率极低。优势非常明显!

1. RTT的工作方式

它通过Jlink连接,使用一种内存共享机制,将MCU内部的数据实时“搬运”到PC上1

就像你在MCU的RAM里放了个“邮箱”,PC随时来收信,MCU照常干活,互不打扰。

收发数据过程:
MCU发送数据 ➔ 只需把数据 memcpy 拷贝到 UpBuffer 的空闲区域;
PC接收数据 ➔ 通过 J-Link 读取 UpBuffer 的数据;
PC发送指令 ➔ 把数据写入 DownBuffer;
MCU读取指令 ➔ 从 DownBuffer 中 memcpy 出来。
由于只是内存拷贝,整个收发过程极快,微秒级完成,不会打断 MCU 正常工作。

2. 快速开始

  1. 从 SEGGER J-Link 安装目录 Samples/RTT 复制以下文件到工程中,并添加头文件路径。
    • 包括四个文件:SEGGER_RTT.c、SEGGER_RTT.h、SEGGER_RTT_Conf.c、SEGGER_RTT_Conf.h
  2. 把他们包含到你的工程中(略)
  3. 调用SEGGER_RTT.h接口
    • 初始化:SEGGER_RTT_Init();
    • 日志输出:SEGGER_RTT_WriteString();
  4. 如果使用J-Link,打开RTT Viewer / RTT Logger;
  5. 连接File -> Connect,配置
    • 通常是USB连接方式
    • 选择你的芯片类型
    • SWD/JTAG、4000kHzs(SWD速度好像可以快点)
    • 使用”Auto Detection”或手动设置地址
    • 连接

配置完成,会显示已连接(但并不一定能连上🤬)。

LOG: Connecting to J-Link via USB...
LOG: Device "HC32F460XE" selected.
LOG: Found SW-DP with ID 0x2BA01477
LOG: DPIDR: 0x2BA01477
LOG: CoreSight SoC-400 or earlier
LOG: Scanning AP map to find all available APs
LOG: AP[1]: Stopped AP scan as end of AP map has been reached
LOG: AP[0]: AHB-AP (IDR: 0x24770011)
LOG: Iterating through AP map to find AHB-AP to use
LOG: AP[0]: Core found
LOG: AP[0]: AHB-AP ROM base: 0xE00FF000
LOG: CPUID register: 0x410FC241. Implementer code: 0x41 (ARM)
LOG: Found Cortex-M4 r0p1, Little endian.
LOG: FPUnit: 6 code (BP) slots and 2 literal slots
LOG: CoreSight components:
LOG: ROMTbl[0] @ E00FF000
LOG: [0][0]: E000E000 CID B105E00D PID 000BB00C SCS-M7
LOG: [0][1]: E0001000 CID B105E00D PID 003BB002 DWT
LOG: [0][2]: E0002000 CID B105E00D PID 002BB003 FPB
LOG: [0][3]: E0000000 CID B105E00D PID 003BB001 ITM
LOG: [0][4]: E0040000 CID B105900D PID 000BB9A1 TPIU
LOG: RTT Viewer connected.

3. 参考代码

我的日志系统:

#include "cmn_log.h"
#include "SEGGER_RTT.h"
#include <stdarg.h>
#include <string.h>
#include <stdio.h>

static CmnLogLevel_T s_stCurrentLevel = LOG_SHOW_LEVEL;

// 日志级别字符串
static const char *s_cLevelStr[] = {
    "",
    "[ERROR]",
    "[WARN] ",
    "[DEBUG] ",
    "[INFO] "
};

/**
 *  @brief 获取文件名(去除路径)
 *  @note 输入:`"E:/MyWorkspace/Project/source/main.c"`
 *        输出:"main.c"
 */
static const char* CmnLogGetFileName(const char *pcfile)
{
    const char *p = strrchr(pcfile, '/');
    if (p) {
        return p + 1;
    }
    p = strrchr(pcfile, '\\');
    if (p) {
        return p + 1;
    }
    return pcfile;
}

void CmnLogInit()
{
    SEGGER_RTT_Init();
    LOG_ERROR("_SEGGER_RTT: 0x%8X", (uint32_t)&_SEGGER_RTT);
    // 0x1FFF8000 0x10000000
    // 地址搜索直接全部,256M RAM
}

void CmnSetLogLevel(CmnLogLevel_T stlevel)
{
    s_stCurrentLevel = stlevel;
}

void CmnLogPrint(CmnLogLevel_T stLevel, const char *pcFile, uint32_t u32Line, const char *pcFormat, ...)
{
    if (stLevel > s_stCurrentLevel) {
        return;
    }

    char cBuffer[128];
    uint8_t u8Len = 0;

    // 构建日志前缀:级别 + 文件名 + 行号
    const char *cFilename = CmnLogGetFileName(pcFile);
    u8Len = snprintf(cBuffer, sizeof(cBuffer), "%s %s:%d ",
                    s_cLevelStr[stLevel], cFilename, u32Line);

    if (u8Len > 0 && u8Len < (int)sizeof(cBuffer)) {
        // 添加用户消息
        va_list args;
        va_start(args, pcFormat);
        vsnprintf(cBuffer + u8Len, sizeof(cBuffer) - u8Len, pcFormat, args);
        va_end(args);

        // 确保以换行符结束
        size_t total_len = strlen(cBuffer);
        if (total_len > 0 && cBuffer[total_len - 1] != '\n') {
            if (total_len < sizeof(cBuffer) - 2) {
                cBuffer[total_len] = '\n';
                cBuffer[total_len + 1] = '\0';
            }
        }

        // 输出日志
        SEGGER_RTT_WriteString(0, cBuffer);
    }
}
#ifndef __CMN_LOG_H__
#define __CMN_LOG_H__

#ifdef __cplusplus
extern "C" {
#endif

#include <stdint.h>

typedef enum{
    CMN_LOG_LEVEL_NONE = 0,
    CMN_LOG_LEVEL_ERROR = 1,
    CMN_LOG_LEVEL_WARN = 2,
    CMN_LOG_LEVEL_DEBUG = 3,
    CMN_LOG_LEVEL_INFO = 4,
} CmnLogLevel_T;

#define LOG_SHOW_LEVEL   CMN_LOG_LEVEL_DEBUG
#define LOG_ENABLE       1  // 发布版本,将LOG_ENABLE改为0

#if LOG_ENABLE
#define LOG_ERROR(...)   CmnLogPrint(CMN_LOG_LEVEL_ERROR, __FILE__, __LINE__, __VA_ARGS__)
#define LOG_WARN(...)    CmnLogPrint(CMN_LOG_LEVEL_WARN,  __FILE__, __LINE__, __VA_ARGS__)
#define LOG_DEBUG(...)   CmnLogPrint(CMN_LOG_LEVEL_DEBUG, __FILE__, __LINE__, __VA_ARGS__)
#define LOG_INFO(...)    CmnLogPrint(CMN_LOG_LEVEL_INFO,  __FILE__, __LINE__, __VA_ARGS__)
#else
#define LOG_ERROR(...)   // 空宏,编译时完全移除
#define LOG_WARN(...)    // 空宏,编译时完全移除
#define LOG_DEBUG(...)   // 空宏,编译时完全移除
#define LOG_INFO(...)    // 空宏,编译时完全移除
#endif

void CmnLogInit();
void CmnSetLogLevel(CmnLogLevel_T stLevel);
void CmnLogPrint(CmnLogLevel_T stLevel, const char *cpFile, uint32_t u32Line, const char *cpFormat, ...);

#ifdef __cplusplus
}
#endif

#endif /* __CMN_LOG_H__ */

常见问题

问题现象:

LOG: RTT Viewer connected.
WARNING: Sent 0 of 1 byte.
WARNING: Sent 0 of 1 byte.
...
LOG: Terminal 1 added.

先检查代码,配置应用流程,没有发现问题。

命名勾选”Auto Detection”,还是无法找到,可能自动搜索范围有限。

尝试手动输入地址,解决。

解决办法:检查RTT控制块地址,使用搜索范围0x1FFF8000 0x10000000

  • 0x1FFF8000 RAM起始地址
  • 0x10000000 整个RAM大小

如何找到确切地址?查看map文件中的_SEGGER_RTT或在代码中输出:&_SEGGER_RTT。

真正成功:能看到输出日志,无警告信息。

虚假连接:显示”Connected”但有”Sent 0 of X byte”警告。

_SEGGER_RTT地址变化

可以在.ld连接脚本中,固定地址。

    .rtt_section 0x20000000 : {
        _SEGGER_RTT = .;
        *(.bss._SEGGER_RTT)
        *(.bss._SEGGER_RTT*)
        . = ALIGN(4);
    } >RAM

输出乱码或丢失

确保SEGGER_RTT_Conf.h中缓冲区大小合理:

#define BUFFER_SIZE_UP (1024)  // 上行缓冲区
#define BUFFER_SIZE_DOWN (16)  // 下行缓冲区

参考资料

  1. Aladdin-Wang. (2025). printf的终极调试大法 .https://microboot.readthedocs.io/zh-cn/latest/tools/microlink/RTT_printf/ 

results matching ""

    No results matching ""