基于KEIL5的SEGGER-RTT移植

SEGGER-RTT 移植指南

一、简介

SEGGER RTT (Real-Time Transfer) 是一种通过 J-Link 调试器进行实时数据传输的技术,无需额外的 UART/USB 接口即可实现日志输出和调试。

特点:

  • 无需额外硬件接口
  • 高速数据传输(比 UART 快得多)
  • 双向通信(输出日志 + 接收命令)
  • 极低的 CPU 占用

适用芯片: 所有支持 J-Link 调试的 ARM 芯片


二、文件准备

2.1 获取 RTT 源码

从 SEGGER 官网或 SDK 中复制以下文件:

RTT/
├── SEGGER_RTT.c              # 核心实现(必须)
├── SEGGER_RTT.h              # 头文件(必须)
├── SEGGER_RTT_printf.c       # printf 功能(推荐)
└── SEGGER_RTT_Conf.h         # 配置文件(必须)

⚠️ 注意:不需要 SEGGER_RTT_Syscalls_KEIL.c(与 Keil 库冲突)

2.2 创建应用层封装(可选但推荐)

创建 rtt_log.hrtt_log.c 提供更易用的接口。


三、移植步骤

步骤 1:复制文件到项目目录

BSP/
└── RTT/
    ├── SEGGER_RTT.c
    ├── SEGGER_RTT.h
    ├── SEGGER_RTT_printf.c
    ├── SEGGER_RTT_Conf.h
    ├── rtt_log.h          # 应用层封装
    └── rtt_log.c          # 应用层封装

步骤 2:配置 Keil 工程

2.1 添加文件到工程

  1. 在 Keil 中右键项目 → Manage Project Items
  2. 新建 Group: BSP/RTT
  3. 添加以下 C 文件:
    • BSP/RTT/SEGGER_RTT.c
    • BSP/RTT/SEGGER_RTT_printf.c
    • BSP/RTT/rtt_log.c

2.2 添加头文件路径

Options for Target → C/C++ → Include Paths
添加: BSP/RTT

步骤 3:修改配置文件(SEGGER_RTT_Conf.h)

关键配置项:

// 上行缓冲区数量(Target → Host)
#define SEGGER_RTT_MAX_NUM_UP_BUFFERS     3

// 下行缓冲区数量(Host → Target)
#define SEGGER_RTT_MAX_NUM_DOWN_BUFFERS   3

// 上行缓冲区大小(输出缓冲区)
#define BUFFER_SIZE_UP                    1024

// 下行缓冲区大小(输入缓冲区)
#define BUFFER_SIZE_DOWN                  16

// 默认工作模式
#define SEGGER_RTT_MODE_DEFAULT           SEGGER_RTT_MODE_NO_BLOCK_SKIP

模式说明:

模式 说明
SEGGER_RTT_MODE_NO_BLOCK_SKIP 缓冲区满时跳过数据(默认,推荐)
SEGGER_RTT_MODE_NO_BLOCK_TRIM 缓冲区满时截断数据
SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL 缓冲区满时阻塞等待

步骤 4:初始化代码

main.c 中添加:

#include "rtt_log.h"

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    
    // 初始化 RTT
    rtt_init();
    RTT_LOG_INFO("System Started!");
    
    // 你的代码...
    while (1) {
        // 测试输出
        RTT_LOG_DEBUG("Loop running...");
        HAL_Delay(1000);
    }
}

四、应用层接口代码

4.1 rtt_log.h(应用层头文件)

//-----------------------------------------------------------------------------
//* @file		rtt_log.h
//* @author  	WangLiang
//* @version 	V1.0
//* @brief   	SEGGER RTT 日志输出接口
//* @date    	2026-4-7
//-----------------------------------------------------------------------------
#ifndef __RTT_LOG_H
#define __RTT_LOG_H

#include "SEGGER_RTT.h"
#include <stdarg.h>

// 日志级别定义
#define RTT_LOG_LEVEL_NONE  0   // 不输出
#define RTT_LOG_LEVEL_ERROR 1   // 仅错误
#define RTT_LOG_LEVEL_WARN  2   // 错误+警告
#define RTT_LOG_LEVEL_INFO  3   // 错误+警告+信息
#define RTT_LOG_LEVEL_DEBUG 4   // 全部输出

// 默认日志级别
#ifndef RTT_LOG_LEVEL
    #define RTT_LOG_LEVEL RTT_LOG_LEVEL_DEBUG
#endif

// RTT 通道定义
#define RTT_LOG_CHANNEL     0   // 默认日志通道
#define RTT_TERM_CHANNEL    0   // 终端通道

// ANSI 颜色定义
#define RTT_COLOR_RED       RTT_CTRL_TEXT_RED
#define RTT_COLOR_GREEN     RTT_CTRL_TEXT_GREEN
#define RTT_COLOR_YELLOW    RTT_CTRL_TEXT_YELLOW
#define RTT_COLOR_BLUE      RTT_CTRL_TEXT_BLUE
#define RTT_COLOR_CYAN      RTT_CTRL_TEXT_CYAN
#define RTT_COLOR_WHITE     RTT_CTRL_TEXT_WHITE
#define RTT_COLOR_RESET     RTT_CTRL_RESET

// 简洁颜色宏
#define RTT_C_RED           RTT_CTRL_TEXT_RED
#define RTT_C_GREEN         RTT_CTRL_TEXT_GREEN
#define RTT_C_YELLOW        RTT_CTRL_TEXT_YELLOW
#define RTT_C_BLUE          RTT_CTRL_TEXT_BLUE
#define RTT_C_CYAN          RTT_CTRL_TEXT_CYAN
#define RTT_C_WHITE         RTT_CTRL_TEXT_WHITE
#define RTT_C_RESET         RTT_CTRL_RESET

//-----------------------------------------------------------------------------
// 基础函数声明
//-----------------------------------------------------------------------------

/**
 * @brief 初始化RTT(系统自动调用,用户一般不需要手动调用)
 */
void rtt_init(void);

/**
 * @brief 输出字符串(带换行)
 * @param str 要输出的字符串
 */
void rtt_println(const char *str);

/**
 * @brief 输出字符串(不带换行)
 * @param str 要输出的字符串
 */
void rtt_print(const char *str);

/**
 * @brief 格式化输出(类似printf)
 * @param fmt 格式字符串
 * @param ... 可变参数
 * @return 写入的字节数
 */
int rtt_printf(const char *fmt, ...);

/**
 * @brief 格式化输出(带换行)
 * @param fmt 格式字符串
 * @param ... 可变参数
 */
void rtt_printfln(const char *fmt, ...);

/**
 * @brief 清空RTT缓冲区
 */
void rtt_clear(void);

/**
 * @brief 等待输入按键
 * @return 按键字符
 */
int rtt_getkey(void);

/**
 * @brief 检查是否有输入数据
 * @return 非0表示有数据
 */
int rtt_haskey(void);

/**
 * @brief 读取输入数据
 * @param buf 接收缓冲区
 * @param len 缓冲区大小
 * @return 实际读取的字节数
 */
unsigned rtt_read(char *buf, unsigned len);

//-----------------------------------------------------------------------------
// 日志级别输出宏
//-----------------------------------------------------------------------------

#if (RTT_LOG_LEVEL >= RTT_LOG_LEVEL_ERROR)
    /**
     * @brief 错误日志输出(红色)
     */
    #define RTT_LOG_ERROR(...)   SEGGER_RTT_printf(0, RTT_C_RED "[ERR] " RTT_C_RESET __VA_ARGS__); SEGGER_RTT_WriteString(0, "\r\n")
#else
    #define RTT_LOG_ERROR(...)
#endif

#if (RTT_LOG_LEVEL >= RTT_LOG_LEVEL_WARN)
    /**
     * @brief 警告日志输出(黄色)
     */
    #define RTT_LOG_WARN(...)    SEGGER_RTT_printf(0, RTT_C_YELLOW "[WRN] " RTT_C_RESET __VA_ARGS__); SEGGER_RTT_WriteString(0, "\r\n")
#else
    #define RTT_LOG_WARN(...)
#endif

#if (RTT_LOG_LEVEL >= RTT_LOG_LEVEL_INFO)
    /**
     * @brief 信息日志输出(绿色)
     */
    #define RTT_LOG_INFO(...)    SEGGER_RTT_printf(0, RTT_C_GREEN "[INF] " RTT_C_RESET __VA_ARGS__); SEGGER_RTT_WriteString(0, "\r\n")
#else
    #define RTT_LOG_INFO(...)
#endif

#if (RTT_LOG_LEVEL >= RTT_LOG_LEVEL_DEBUG)
    /**
     * @brief 调试日志输出(白色)
     */
    #define RTT_LOG_DEBUG(...)   SEGGER_RTT_printf(0, RTT_C_WHITE "[DBG] " RTT_C_RESET __VA_ARGS__); SEGGER_RTT_WriteString(0, "\r\n")
    /**
     * @brief 原始输出(不带前缀和换行)
     */
    #define RTT_LOG_RAW(...)     SEGGER_RTT_printf(0, __VA_ARGS__)
#else
    #define RTT_LOG_DEBUG(...)
    #define RTT_LOG_RAW(...)
#endif

//-----------------------------------------------------------------------------
// 便捷宏
//-----------------------------------------------------------------------------

/**
 * @brief 简化的日志输出宏
 * @param ... 格式字符串和参数
 */
#define LOG_PRINT(...)       rtt_printfln(__VA_ARGS__)
#define LOG_PRINT_RAW(...)   rtt_printf(__VA_ARGS__)

#endif /* __RTT_LOG_H */

4.2 rtt_log.c(应用层实现)

//-----------------------------------------------------------------------------
//* @file		rtt_log.c
//* @author  	WangLiang
//* @version 	V1.0
//* @brief   	SEGGER RTT 日志输出接口实现
//* @date    	2026-4-7
//-----------------------------------------------------------------------------
#include "rtt_log.h"
#include <string.h>
#include <stdio.h>

//-----------------------------------------------------------------------------
// 内部变量
//-----------------------------------------------------------------------------
static uint8_t s_rtt_initialized = 0;

//-----------------------------------------------------------------------------
// 函数实现
//-----------------------------------------------------------------------------

/**
 * @brief 初始化RTT
 */
void rtt_init(void)
{
    if (!s_rtt_initialized) {
        SEGGER_RTT_Init();
        s_rtt_initialized = 1;
    }
}

/**
 * @brief 输出字符串(带换行)
 */
void rtt_println(const char *str)
{
    if (!s_rtt_initialized) {
        rtt_init();
    }
    SEGGER_RTT_WriteString(RTT_LOG_CHANNEL, str);
    SEGGER_RTT_WriteString(RTT_LOG_CHANNEL, "\r\n");
}

/**
 * @brief 输出字符串(不带换行)
 */
void rtt_print(const char *str)
{
    if (!s_rtt_initialized) {
        rtt_init();
    }
    SEGGER_RTT_WriteString(RTT_LOG_CHANNEL, str);
}

/**
 * @brief 格式化输出(类似printf)
 */
int rtt_printf(const char *fmt, ...)
{
    va_list args;
    int r;
    
    if (!s_rtt_initialized) {
        rtt_init();
    }
    
    va_start(args, fmt);
    r = SEGGER_RTT_vprintf(RTT_LOG_CHANNEL, fmt, &args);
    va_end(args);
    
    return r;
}

/**
 * @brief 格式化输出(带换行)
 */
void rtt_printfln(const char *fmt, ...)
{
    va_list args;
    
    if (!s_rtt_initialized) {
        rtt_init();
    }
    
    va_start(args, fmt);
    SEGGER_RTT_vprintf(RTT_LOG_CHANNEL, fmt, &args);
    va_end(args);
    
    SEGGER_RTT_WriteString(RTT_LOG_CHANNEL, "\r\n");
}

/**
 * @brief 清空RTT缓冲区
 */
void rtt_clear(void)
{
    SEGGER_RTT_WriteString(RTT_LOG_CHANNEL, RTT_CTRL_CLEAR);
}

/**
 * @brief 等待输入按键
 */
int rtt_getkey(void)
{
    if (!s_rtt_initialized) {
        rtt_init();
    }
    return SEGGER_RTT_WaitKey();
}

/**
 * @brief 检查是否有输入数据
 */
int rtt_haskey(void)
{
    if (!s_rtt_initialized) {
        rtt_init();
    }
    return SEGGER_RTT_HasKey();
}

/**
 * @brief 读取输入数据
 */
unsigned rtt_read(char *buf, unsigned len)
{
    if (!s_rtt_initialized) {
        rtt_init();
    }
    return SEGGER_RTT_Read(RTT_TERM_CHANNEL, buf, len);
}

4.3 接口使用说明

// 基础输出函数
void rtt_init(void);                          // 初始化 RTT
void rtt_println(const char *str);            // 输出字符串(带换行)
void rtt_print(const char *str);              // 输出字符串(不带换行)
int  rtt_printf(const char *fmt, ...);        // 格式化输出
void rtt_printfln(const char *fmt, ...);      // 格式化输出(带换行)
void rtt_clear(void);                         // 清空屏幕

// 输入函数
int      rtt_getkey(void);                    // 等待并获取一个按键
int      rtt_haskey(void);                    // 检查是否有输入(非阻塞)
unsigned rtt_read(char *buf, unsigned len);   // 读取输入数据

// 日志级别宏(带颜色)
RTT_LOG_ERROR("错误信息: %d", err_code);      // 红色
RTT_LOG_WARN("警告信息");                      // 黄色
RTT_LOG_INFO("系统启动完成");                  // 绿色
RTT_LOG_DEBUG("变量 x=%d", x);                // 白色
RTT_LOG_RAW("原始输出,无前缀");               // 无前缀

五、查看输出

打开方式:

  • 开始菜单 → SEGGER → J-Link RTT Viewer
  • 或找到 JLinkRTTViewer.exe

配置参数:

Connection:        USB
Target Device:     STM32F103C8    // 根据实际芯片修改
Target Interface:  SWD            // 或 JTAG
Speed:             4000 kHz
RTT Control Block: Auto Detection // 自动检测地址

点击 OK 即可看到输出。

命令行运行:

JLinkRTTClient.exe -device STM32F103C8 -if SWD -speed 4000

5.3 IDE 集成

Keil MDK

Keil 原生不支持 RTT 协议,需要使用外部 RTT Viewer。

IAR Embedded Workbench

支持 RTT 集成,可在 Terminal I/O 窗口查看。

SEGGER Embedded Studio

原生支持 RTT。


六、常见问题

Q1: 编译报错 "Symbol _sys_xxx multiply defined"

原因: 同时包含了 SEGGER_RTT_Syscalls_KEIL.c 和 Keil 库
解决: 删除 SEGGER_RTT_Syscalls_KEIL.c 文件

Q2: RTT Viewer 检测不到控制块

原因: 芯片型号选择错误或程序未运行
解决:

  1. 确认 Target Device 选择正确
  2. 确认程序已下载并在运行状态
  3. 手动指定地址:在 Keil Debug 状态查看 _SEGGER_RTT 符号地址,填入 RTT Viewer

Q3: 输出乱码或不完整

原因: 缓冲区太小或模式设置不当
解决:

  1. 增大 BUFFER_SIZE_UP
  2. 将模式改为 SEGGER_RTT_MODE_NO_BLOCK_TRIM

Q4: 在中断中使用 RTT 输出异常

原因: RTT 在中断中可能被重入
解决:

  • 中断中只使用 SEGGER_RTT_WriteString()SEGGER_RTT_PutChar()
  • 避免在中断中使用 printf 类格式化函数

七、高级配置

7.1 自定义缓冲区大小

// 在包含 SEGGER_RTT.h 之前定义
#define BUFFER_SIZE_UP    2048   // 增大输出缓冲区
#define BUFFER_SIZE_DOWN  64     // 增大输入缓冲区

#include "SEGGER_RTT.h"

7.2 使用多个通道

// 通道 0: 终端输出
// 通道 1: 数据日志
// 通道 2: 调试信息

SEGGER_RTT_WriteString(1, "Channel 1 message\r\n");
SEGGER_RTT_printf(2, "Debug: %d\r\n", value);

7.3 禁用颜色输出

// 在 rtt_log.h 中定义
#define RTT_LOG_COLOR_DISABLE

八、性能优化

8.1 缓冲区大小优化

应用场景 推荐大小
少量日志 512 字节
中等日志 1024 字节
大量日志 2048+ 字节
高速数据 4096+ 字节

8.2 输出模式选择

  • 调试阶段: 使用 SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL 确保不丢数据
  • 发布阶段: 使用 SEGGER_RTT_MODE_NO_BLOCK_SKIP 避免阻塞

8.3 减少 CPU 占用

  • 使用 SEGGER_RTT_WriteString() 代替格式化输出
  • 避免频繁的小数据量输出(尽量批量输出)

九、示例代码

9.1 基础日志输出

#include "rtt_log.h"

void test_log(void)
{
    RTT_LOG_INFO("系统启动");
    RTT_LOG_WARN("温度偏高: %d°C", 45);
    RTT_LOG_ERROR("通信超时!");
    RTT_LOG_DEBUG("变量值: x=%d, y=%d", 10, 20);
}

9.2 交互式命令

#include "rtt_log.h"
#include <string.h>

void rtt_shell(void)
{
    char buf[32];
    unsigned len;
    
    rtt_println("RTT Shell Ready. Type 'help' for commands.");
    
    while (1) {
        if (rtt_haskey()) {
            len = rtt_read(buf, sizeof(buf) - 1);
            buf[len] = '\0';
            
            if (strcmp(buf, "help") == 0) {
                rtt_println("Commands: help, status, reset");
            }
            else if (strcmp(buf, "status") == 0) {
                rtt_printfln("Status: OK, Tick: %lu", HAL_GetTick());
            }
            // ...
        }
        HAL_Delay(10);
    }
}

9.3 高速数据输出

// 使用二进制模式传输高速数据
void send_data_fast(uint8_t *data, uint32_t len)
{
    // 使用通道 1 传输数据,通道 0 保留给日志
    SEGGER_RTT_Write(1, data, len);
}

十、参考资源


附录:完整文件清单

移植完成后,你的项目应该包含以下文件:

BSP/
└── RTT/
    ├── SEGGER_RTT.c              # SEGGER 官方源码
    ├── SEGGER_RTT.h              # SEGGER 官方源码
    ├── SEGGER_RTT_printf.c       # SEGGER 官方源码
    ├── SEGGER_RTT_Conf.h         # SEGGER 官方配置(可修改)
    ├── rtt_log.h                 # 应用层封装头文件(自建)
    └── rtt_log.c                 # 应用层封装实现(自建)

快速检查清单

posted @ 2026-04-07 14:48  tanyee  阅读(39)  评论(0)    收藏  举报