摘要: 告别繁琐的串口线!RTT提供了一种高速、非侵入式的调试信息传输方式,极大提升嵌入式开发调试效率。本教程将手把手教你从零开始使用RTT。

一、 RTT 是什么?为什么是调试利器?

  1. 基本概念:
    • SEGGER 公司推出的嵌入式调试技术。
    • 核心思想:利用调试器(如J-Link)与目标芯片之间已有的调试通道(如SWD/JTAG)传输数据
    • 无需占用额外的硬件串口引脚。
  2. 核心优势 (为什么用它?):
    • 极高速率: 远超传统UART串口(可达MB/s级别)。
    • 零硬件成本: 复用调试接口,不占宝贵引脚资源。
    • 非侵入式: 对目标系统运行影响极小,尤其适合实时系统调试。
    • 双向通信: 不仅可输出(目标->主机),也可输入(主机->目标)。
    • 多通道: 支持多个独立的逻辑通道(如:LOG、ERROR、USER INPUT、PROFILING)。
    • 与调试器无缝集成: 与J-Link、Ozone等工具深度整合。
  3. 典型应用场景:
    • 高速日志输出(替代printf)。
    • 实时系统状态监控。
    • 性能分析(Profiling)数据上传。
    • 接收调试命令/控制指令。
    • 在资源受限或没有空闲串口的设备上调试。

二、 准备工作

  1. 硬件要求:
    • 支持RTT的目标微控制器(绝大多数现代ARM Cortex-M/M+/R/A内核都支持)。
    • SEGGER J-Link 调试器(这是使用RTT的核心硬件)。
    • 目标开发板。
    • PC(Windows/Linux/macOS)。
  2. 软件要求:
    • J-Link Software Pack: 必须安装(包含驱动和工具)。
      下载:千万别选错了!!!
      经过测试,V828安装后没有RTT源码!!!
      建议下载V864
      在这里插入图片描述

      安装:一路默认下一步

      在这里插入图片描述

    • RTT 实现源码SEGGER_RTT.cSEGGER_RTT.h(从SEGGER官网下载或J-Link包中获取)。
      在这里插入图片描述
      在这里插入图片描述

    • 终端软件(选其一):
      在这里插入图片描述

      • J-Link RTT Viewer (SEGGER官方,推荐)
      • J-Link RTT Client (命令行工具)
      • Telnet客户端(如PuTTY, SecureCRT)
    • IDE/编译器: 如Keil MDK, IAR EWARM, GCC等。

二、SEGGER RTT V864 核心文件结构解析

SEGGER_RTT_V864/
│
├── RTT/                            # RTT核心实现文件目录
│   ├── SEGGER_RTT.c                # RTT主要实现:初始化、读写函数等
│   ├── SEGGER_RTT.h                # RTT主要头文件:API声明、数据结构、宏定义
│   ├── SEGGER_RTT_ASM_ARMv7M.S     # ARMv7-M架构的汇编优化实现(如无锁读写)
│   └── SEGGER_RTT_Printf.c         # 简单的printf实现,用于通过RTT输出格式化字符串
│
├── Syscalls/                       # 系统调用重定向目录(用于不同工具链)
│   ├── SEGGER_RTT_Syscalls_GCC.c   # 针对GCC工具链的系统调用,重定向printf到RTT
│   ├── SEGGER_RTT_Syscalls_KEIL.c  # 针对Keil工具链的系统调用
│   ├── SEGGER_RTT_Syscalls_IAR.c   # 针对IAR工具链的系统调用
│   └── ...                         # 可能还有其他工具链的系统调用文件
│
├── Config/
│   └── SEGGER_RTT_Conf.h           # RTT配置文件:缓冲区大小、模式等可调整参数
│
└── Examples/                       # 示例程序目录
    ├── Main_RTT_InputEchoApp.c     # 回显输入示例:在通道0上回显输入
    ├── Main_RTT_MenuApp.c          # 菜单示例:演示RTT双向功能
    ├── Main_RTT_PrintfTest.c       # 测试RTT的printf实现
    └── Main_RTT_SpeedTestApp.c     # 性能测试示例(需要embOS)

文件详细说明

1. RTT/SEGGER_RTT.h

  • 主要头文件:包含RTT的所有API声明、数据结构和宏定义。
  • 关键内容
    • SEGGER_RTT_CB:RTT控制块结构,管理所有通道。
    • SEGGER_RTT_BUFFER:缓冲区描述结构,用于上行和下行缓冲区。
    • API函数声明:如SEGGER_RTT_WriteSEGGER_RTT_Read等。
    • 终端控制序列的宏(如颜色、光标控制)。
    • 版本信息(V864)。

2. RTT/SEGGER_RTT.c

  • 核心实现文件:包含RTT的主要功能实现。
  • 关键内容
    • 全局RTT控制块_SEGGER_RTT
    • API函数实现:初始化、读写、配置缓冲区等。
    • 可重入锁机制(默认使用关中断方式,用户可配置)。

3. RTT/SEGGER_RTT_ASM_ARMv7M.S

  • 汇编优化实现:针对ARMv7-M架构(Cortex-M3/M4/M7等)的高性能无锁读写函数。
  • 关键函数
    • SEGGER_RTT_WriteSkipNoLock:无锁写入并跳过已满缓冲区。
    • SEGGER_RTT_WriteNoLock:无锁写入。
    • SEGGER_RTT_ReadNoLock:无锁读取。

4. RTT/SEGGER_RTT_Printf.c

  • 格式化输出支持:提供简单的SEGGER_RTT_printf函数,支持格式化字符串输出。
  • 注意:默认可能不支持浮点数,需要额外配置。

5. Syscalls/ 目录

  • 系统调用重定向:将标准库的输入输出函数(如printfgetchar)重定向到RTT。
  • 针对不同编译器
    • GCC:SEGGER_RTT_Syscalls_GCC.c
    • Keil:SEGGER_RTT_Syscalls_KEIL.c
    • IAR:SEGGER_RTT_Syscalls_IAR.c
  • 功能:重定义_write_read等底层系统调用函数。

6. Config/SEGGER_RTT_Conf.h

  • 配置文件:用户可根据需要调整RTT的各项参数。
  • 关键配置
    • BUFFER_SIZE_UP:上行缓冲区大小(默认1KB)。
    • BUFFER_SIZE_DOWN:下行缓冲区大小(默认16字节)。
    • SEGGER_RTT_MODE_DEFAULT:默认模式(阻塞或非阻塞)。
    • 终端数量、打印缓冲区大小、锁机制配置等。

7. Examples/ 目录

  • 示例程序:演示RTT的各种用法。
    • Main_RTT_InputEchoApp.c:回显输入,演示双向通信。
    • Main_RTT_MenuApp.c:通过RTT实现简单的菜单交互。
    • Main_RTT_PrintfTest.c:测试RTT的printf功能。
    • Main_RTT_SpeedTestApp.c:测试RTT的性能(需要embOS支持)。

使用注意事项

  1. 版本兼容性:V864版本可能引入了新特性或修复,建议查看官方ReleaseNotes。
  2. 浮点数打印:默认的SEGGER_RTT_printf可能不支持浮点数,如需支持,需修改SEGGER_RTT_Printf.c或使用标准库的printf重定向。
  3. 系统调用:根据使用的工具链选择正确的系统调用文件,并正确链接到工程中。
  4. 性能优化:在高速数据传输时,建议使用汇编优化版本(ARMv7M)并合理设置缓冲区大小。
  5. 多任务环境:如果是在RTOS中使用,确保正确配置锁机制(如使用信号量)以避免竞态条件。

通过以上结构,SEGGER RTT提供了一个高效、灵活的双向通信机制,适用于各种嵌入式调试和输出场景。

三、 在目标工程中集成RTT

  1. 获取RTT源码:

    • 从SEGGER官网下载最新J-Link软件包或独立RTT包。
    • 找到SEGGER_RTT.cSEGGER_RTT.h文件。
      在这里插入图片描述
  2. 添加文件到工程:
    在这里插入图片描述

    • SEGGER_RTT.cSEGGER_RTT_Printf.c 加入工程的源文件列表。
      在这里插入图片描述

    • SEGGER_RTT.hSEGGER_RTT_Conf.h所在路径添加到头文件搜索路径。
      在这里插入图片描述

  3. 配置RTT缓冲区 (关键步骤!):

    • 打开SEGGER_RTT_Conf.h(通常与.c/.h一起提供,或需从示例复制)。
    • 配置上行缓冲区(目标->主机):
      #define BUFFER_SIZE_UP 1024 // 根据需求调整大小,建议至少512字节
      
    • 配置下行缓冲区(主机->目标):
      #define BUFFER_SIZE_DOWN 16  // 通常用于命令输入,不需要太大
      
      在这里插入图片描述
    • SEGGER_RTT_PRINTF_BUFFER_SIZE :SEGGER_RTT_printf 函数使用的内部缓冲区大小。此值必小于 BUFFER_SIZE_UP 。
    • SEGGER_RTT_MODE_DEFAULT :默认模式。可选:SEGGER_RTT_MODE_NO_BLOCK_SKIP (非阻塞,缓冲区满时丢弃新数据) SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL (阻塞,缓冲区满时等待直到有空闲)
      注意:阻塞模式在不连接J-Link且缓冲区满时可能导致程序卡住。
    • 可选配置:通道数量、是否使用printf重定向等。
  4. 初始化RTT (可选但推荐):

    • main()函数早期(初始化外设后)调用:
      SEGGER_RTT_Init();
      
  5. 重定向printf到RTT (可选但极实用):

    • 修改SEGGER_RTT_Conf.h启用:
      #define SEGGER_RTT_PRINTF_ENABLE 1
      
    • 实现_writefputc(根据编译器):
      // 例如在GCC/Newlib中:
      int _write(int file, char *ptr, int len) {
          (void) file; // 避免未使用参数警告
          SEGGER_RTT_Write(0, ptr, len); // 写入通道0
          return len;
      }
      
    • 之后即可在代码中直接使用printf("Hello RTT! Count = %d\n", count);,输出将自动通过RTT发送。
  6. SEGGER_RTT_printf 函数默认不支持浮点数格式:
    要让SEGGER_RTT_printf函数支持浮点数打印,主要有两种方法:一种是通过sprintf进行中转(无需修改源码,推荐使用),另一种是直接修改SEGGER RTT的源码(一劳永逸,但涉及源码改动)。我将为你详细说明这两种方法的操作步骤和注意事项。

    • 方法一:使用sprintf中转(安全便捷)

      这是最安全、兼容性最好的方法,无需修改SEGGER RTT源码。

      实现步骤:

      1. 包含头文件:在你的源文件中包含必要的头文件。

        #include <stdio.h>
        #include "SEGGER_RTT.h"
        
      2. 转换并打印:使用sprintfsnprintf先将浮点数格式化为字符串,然后用SEGGER_RTT_WriteStringSEGGER_RTT_printf输出。

        float sensor_value = 102.1;
        char buffer[64]; // 确保缓冲区足够大
        
        // 将浮点数格式化为字符串
        sprintf(buffer, "Sensor value: %.2f\n", sensor_value); 
        // 通过RTT输出字符串
        SEGGER_RTT_WriteString(0, buffer);
        
        // 或者使用snprintf防止缓冲区溢出(更安全)
        snprintf(buffer, sizeof(buffer), "Sensor value: %.2f\n", sensor_value);
        SEGGER_RTT_printf(0, "%s", buffer);
        

      封装函数(可选):

      你可以封装一个自己的RTT打印函数,简化调用:

      #include <stdarg.h>
      #include <stdio.h>
      
      void My_RTT_printf(const char *fmt, ...) {
          char buffer[128];
          va_list args;
          
          va_start(args, fmt);
          int len = vsnprintf(buffer, sizeof(buffer), fmt, args); // 使用vsnprintf处理变参
          va_end(args);
          
          if (len > 0) {
              SEGGER_RTT_Write(0, buffer, len); // 将格式化后的字符串写入RTT
          }
      }
      // 使用示例
      float temp = 26.5;
      int humidity = 70;
      My_RTT_printf("Temperature: %.1f°C, Humidity: %d%%\n", temp, humidity);
      

      注意事项:

      • 字节对齐:在某些嵌入式系统(如使用了RTOS)中,如果sprintf转换浮点数得到"0.000000",可能需要检查堆栈的字节对齐,例如将任务堆栈设置为8字节对齐可能解决此问题。
      • 缓冲区大小:务必确保字符数组缓冲区足够大,以防止溢出,使用snprintfsprintf更安全。
      • 代码体积:使用sprintf等标准库函数可能会增加代码体积,如果资源紧张,需要留意。
    • 方法二:修改RTT源码(直接高效)

      此方法直接修改SEGGER_RTT_printf.c文件,让SEGGER_RTT_printf直接支持%f格式符。

      操作步骤:

      1. 定位函数:打开SEGGER_RTT_printf.c文件,找到SEGGER_RTT_vprintf函数。

      2. 添加处理分支:在该函数内部的switch (c)语句中,找到处理格式说明符(如%d, %s)的代码块,添加对%f的处理。下面是一个参考实现,主要步骤是分别处理整数和小数部分

        case 'f':
        case 'F':
          {
            float f_val = (float)va_arg(*pParamList, double); // 提取浮点参数
            int int_part = (int)f_val; // 提取整数部分
            float frac_part = f_val - int_part; // 提取小数部分
            if (f_val < 0) { // 处理负数
              _StoreChar(&BufferDesc, '-');
              int_part = -int_part;
              frac_part = -frac_part;
            }
            // 打印整数部分
            _PrintInt(&BufferDesc, int_part, 10, 0, 0, 0);
            _StoreChar(&BufferDesc, '.'); // 打印小数点
            // 打印小数部分(例如转换为整数打印指定位数)
            int frac_int = (int)(frac_part * 1000000); // 例如转换为6位整数
            _PrintUnsigned(&BufferDesc, (unsigned)frac_int, 10, 6, 6, FORMAT_FLAG_PAD_ZERO); // 打印6位小数,不足补零
          }
          break;
        

        注意:以上为示例性代码,实际实现可能需要根据你的精度、四舍五入等需求进行调整。网络上也有更完善的开源实现可供参考。
        在这里插入图片描述

      3. 辅助函数(如有需要):某些实现可能需要一个简单的幂运算辅助函数(_pow)来处理不同的小数位数。

        static unsigned int _pow(unsigned int base, unsigned int exponent) {
          unsigned int result = 1;
          for (unsigned int i = 0; i < exponent; ++i) {
            result *= base;
          }
          return result;
        }
        

四、 连接调试器与使用RTT Viewer

  1. 硬件连接:

    • 将J-Link通过USB连接到PC。
    • 将J-Link的SWD/JTAG接口连接到目标板。
    • 给目标板上电。
  2. 启动J-Link RTT Viewer:

    • 在PC上运行J-Link RTT Viewer
      在这里插入图片描述
  3. 配置连接:

    • Target Device: 选择你的目标芯片型号(如STM32F407ZG)。
      在这里插入图片描述

    • Target Interface & Speed: 选择SWD和合适的速率(通常默认即可)。

    • Target Device Core: 选择核心(如Cortex-M4)。

    • Specify Target Device…: 如果列表没有,手动输入芯片型号。
      在这里插入图片描述

    • 点击OK

  4. 建立连接并查看输出:

    • 连接成功后,Viewer会自动检测并连接到目标板上的RTT控制块。
    • 主窗口会显示目标程序通过SEGGER_RTT_Write()或重定向printf发送到通道0的数据。
    • 如果目标程序在运行,你应该能看到日志输出开始滚动。
  5. RTT Viewer 核心功能详解:

    • 多标签页(Tabs): 每个标签页可连接到一个不同的RTT通道(通道0是默认的LOG通道)。
    • 通道选择: 顶部下拉菜单可选择要查看或输入的通道。
    • 输入区(Send): 在下方输入框输入文本,点击Send或按Enter,可将数据发送到目标板的下行缓冲区(通常是通道0)。目标程序需调用SEGGER_RTT_Read()SEGGER_RTT_HasKey()来读取。
    • 控制按钮:
      • Connect/Disconnect: 手动连接/断开。
      • Settings: 配置字体、颜色、时间戳、自动滚动等。
      • Clear Window: 清空当前视图。
    • 时间戳: 可配置显示每条消息的精确主机端时间戳。
    • 查找: 支持在输出日志中搜索文本。

五、 在代码中使用RTT API

  1. 基本输出:
    #include "SEGGER_RTT.h"
    ...
    SEGGER_RTT_Write(0, "Hello, World!\n", 14); // 向通道0写入字符串
    SEGGER_RTT_printf(0, "Temperature: %.2f C, Voltage: %d mV\n", temp, voltage); // 格式化输出到通道0
    
  2. 使用不同通道:
    #define CHANNEL_ERROR 1
    #define CHANNEL_PROFILING 2
    ...
    SEGGER_RTT_WriteString(CHANNEL_ERROR, "FATAL ERROR: Sensor timeout!\n");
    SEGGER_RTT_printf(CHANNEL_PROFILING, "TaskA Exec Time: %u us\n", exec_time);
    
    • 在RTT Viewer中,切换到对应通道标签页即可查看。
  3. 输入处理 (接收主机命令):
    char cmdBuffer[32];
    int numBytesRead;
    ...
    // 检查下行缓冲区是否有数据
    if (SEGGER_RTT_HasKey(0)) {
        // 从通道0的下行缓冲区读取数据
        numBytesRead = SEGGER_RTT_Read(0, cmdBuffer, sizeof(cmdBuffer) - 1);
        if (numBytesRead > 0) {
            cmdBuffer[numBytesRead] = '\0'; // 确保字符串结束
            processCommand(cmdBuffer); // 处理接收到的命令
        }
    }
    
  4. 其他实用API:
    • SEGGER_RTT_GetKey(): 从下行缓冲区读取一个字符。
    • SEGGER_RTT_WaitKey(): 阻塞等待并读取一个字符。
    • SEGGER_RTT_HasData(): 检查上行缓冲区是否有待发送数据(通常由RTT内部管理)。
    • SEGGER_RTT_AllocDownBuffer() / SEGGER_RTT_AllocUpBuffer(): 运行时动态分配缓冲区(高级用法)。

六、 使用Telnet连接RTT (替代RTT Viewer)

  1. 启动J-Link RTT Server:
    • 运行J-Link RTT Server
    • 配置目标芯片、接口等(同RTT Viewer)。
    • Connection to Client部分选择Telnet,设置端口号(默认为19021)。
    • 点击Start启动服务器。
  2. 使用Telnet客户端连接:
    • 打开PuTTY、SecureCRT或其他Telnet客户端。
    • 连接类型选择Telnet
    • 主机名:localhost127.0.0.1
    • 端口:19021(或你在RTT Server中设置的端口)。
    • 连接。
  3. 使用:
    • 连接成功后,即可看到目标程序输出到通道0的数据。
    • 在Telnet窗口中直接输入并按回车,即可发送数据到目标板下行缓冲区(通道0)。
    • 优点: 轻量、可编写脚本自动化测试。缺点: 功能不如RTT Viewer丰富(如多通道切换、格式化稍弱)。

七、 常见问题与解决方案 (保姆级排错)

  1. RTT Viewer 连接不上 / 无输出:
    • 检查硬件: J-Link灯是否正常?USB线是否可靠?SWD线是否连接正确且牢固?目标板是否供电?芯片是否正常运行?
    • 检查软件:
      • J-Link驱动是否安装且版本较新?
      • 目标芯片型号在RTT Viewer中选择是否正确?
      • 目标程序是否成功编译并烧录?确认SEGGER_RTT.c已包含在工程中并参与了编译链接。
      • RTT缓冲区是否配置成功? 检查SEGGER_RTT_Conf.h中的BUFFER_SIZE_UP是否足够大(至少512)。
      • 目标程序是否调用了SEGGER_RTT_Init()或使用了输出函数?尝试在main开头加一句SEGGER_RTT_WriteString(0, "RTT Init OK!\n");
      • 目标代码是否在运行?检查是否卡在启动代码或HardFault。
    • 尝试复位目标: 在RTT Viewer中点击Reset按钮或手动复位开发板。
    • 尝试降低SWD速率: 在RTT Viewer连接配置中降低速度。
  2. 输出乱码:
    • 最常见原因:目标系统时钟配置错误! RTT依赖于目标芯片的时钟(通常是系统时钟SystemCoreClock)。确保SystemCoreClock变量在代码中被正确设置且反映了实际运行频率。检查system_<device>.c初始化代码。
    • 检查RTT Viewer或Telnet客户端的字符编码设置(通常设为UTF-8或ANSI)。
  3. 输出延迟或丢失数据:
    • 增大上行缓冲区大小(BUFFER_SIZE_UP),尤其是在高频率输出日志时。
    • 检查目标程序是否在长时间关闭全局中断(会影响RTT传输)。
    • 降低日志输出频率或优化日志内容。
  4. 无法接收输入:
    • 检查RTT Viewer的输入框是否选择了正确的通道(通常是0)。
    • 检查目标程序是否在正确调用SEGGER_RTT_Read()SEGGER_RTT_HasKey()来读取下行缓冲区。
    • 检查SEGGER_RTT_Conf.h中的BUFFER_SIZE_DOWN是否大于0。
    • 确认下行缓冲区配置正确且API使用无误。

八、 高级技巧与最佳实践

  1. 性能优化:
    • 避免在中断服务程序(ISR)中频繁调用RTT_printf(格式化耗时),改用RTT_WriteString输出简单信息或先缓存数据在主循环输出。
    • 根据实际需求调整缓冲区大小,平衡内存占用与性能。
  2. 多通道管理:
    • 为不同类型信息定义清晰通道(LOG, DEBUG, ERROR, COMMAND, PROFILE)。
    • 在RTT Viewer中用不同标签页分别监控关键通道。
  3. 与SystemView集成:
    • RTT是SEGGER SystemView性能分析工具的数据传输基础。学习使用SystemView进行更深入的RTOS或裸机任务调度分析。
  4. 自定义终端控制:
    • 利用ANSI Escape序列在RTT输出中实现彩色文本、清屏、光标定位等(需终端支持)。
  5. 版本管理:
    • SEGGER_RTT.cSEGGER_RTT.h(以及你的SEGGER_RTT_Conf.h)纳入代码版本管理。

九、 总结

  • RTT是嵌入式调试效率的革命性提升工具。
  • 掌握集成、配置、连接和基本API使用,即可替代传统串口调试。
  • 善用多通道、输入输出和工具链集成,能应对复杂调试场景。
  • 遇到问题优先检查硬件连接、驱动版本、目标时钟配置和缓冲区设置。

附录:


Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐