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. 快速开始
- 从 SEGGER J-Link 安装目录 Samples/RTT 复制以下文件到工程中,并添加头文件路径。
- 包括四个文件:SEGGER_RTT.c、SEGGER_RTT.h、SEGGER_RTT_Conf.c、SEGGER_RTT_Conf.h
- 把他们包含到你的工程中(略)
- 调用SEGGER_RTT.h接口
- 初始化:SEGGER_RTT_Init();
- 日志输出:SEGGER_RTT_WriteString();
- 如果使用J-Link,打开RTT Viewer / RTT Logger;
- 连接
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__ */
常见问题
在使用J-Link RTT Viewer连接HC32F460XE芯片时,虽然显示”Connected”,但始终看不到日志输出,出现”Sent 0 of 1 byte”警告。
问题现象:
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) // 下行缓冲区
参考资料
-
Aladdin-Wang. (2025). printf的终极调试大法 .https://microboot.readthedocs.io/zh-cn/latest/tools/microlink/RTT_printf/ ↩