找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

3290

积分

0

好友

452

主题
发表于 16 小时前 | 查看: 0| 回复: 0

实时传输(RTT)是SEGGER基于基本的存储读写实现的目标和主机之间的双向通讯接口。该规范独立于目标架构,只要支持“后台内存访问”,也就是说当目标在运行时,可以读写内存,那么就可以使用该方式。一个通道对应一个TCP/IP连接,可以给某个通道开启多个TCP/IP服务。

当前的OpenOCD版本只支持单目标设备,不支持channel buffer flags,并且TARGET为RISCV时还需要一些修改才能支持。

其中目标到主机为up数据流。
主机到目标为down数据流。

基本命令

先介绍下基本的RTT相关的配置命令。在TARGET为RISCV时,目前的版本默认除了rtt server外的命令都不支持,需要修改源码,后面会介绍。

配置RTT

rtt setup address size [ID]

ID默认为字符串 "SEGGER RTT"
设置之后OpenOCD就后台从address地址处的size大小范围内搜寻ID对应的控制块。

启动RTT

rtt start

如果控制块的位置未知则会搜寻。

停止RTT

rtt stop

上行查询间隔设置与打印

rtt polling_interval [interval]

设置查询上行数据的间隔为interval单位为ms,不指定interval则为打印当前值。

查看通道

rtt channels

打印所有通道和其属性。

获取通道信息

rtt channellist

rtt channels 一样,只是 rtt channellist 的执行结果返回一个tcl的list,可以放在[]中,脚本中其他地方使用。

启动服务

rtt server start port channel [message]

给指定通道启动一个TCP服务端,当指定message不为空时,就会将内容发送给连接到该服务端的客户端。

停止服务

rtt server stop port

停止指定通道对应的TCP服务。

OpenOCD中为RISCV添加RTT支持

在RISCV平台默认输入 rtt 看到只支持以下两个命令。

> rtt
rtt
  rtt server
    rtt server start <port> <channel> [message]
    rtt server stop <port>
>

这是在 src\server\rtt_server.c 中实现的,只有 startstop 俩个命令。

static const struct command_registration rtt_server_subcommand_handlers[] = {
    {
        .name = "start",
        .handler = handle_rtt_start_command,
        .mode = COMMAND_ANY,
        .help = "Start a RTT server",
        .usage = "<port> <channel> [message]"
    },
    {
        .name = "stop",
        .handler = handle_rtt_stop_command,
        .mode = COMMAND_ANY,
        .help = "Stop a RTT server",
        .usage = "<port>"
    },
    COMMAND_REGISTRATION_DONE
};

而其他 setup 等命令是在 src\rtt\tcl.c 中实现的。

static const struct command_registration rtt_subcommand_handlers[] = {
    {
        .name = "setup",
        .handler = handle_rtt_setup_command,
        .mode = COMMAND_ANY,
        .help = "setup RTT",
        .usage = "<address> <size> [ID]"
    },
    {
        .name = "start",
        .handler = handle_rtt_start_command,
        .mode = COMMAND_EXEC,
        .help = "start RTT",
        .usage = ""
    },
    {
        .name = "stop",
        .handler = handle_rtt_stop_command,
        .mode = COMMAND_EXEC,
        .help = "stop RTT",
        .usage = ""
    },
    {
        .name = "polling_interval",
        .handler = handle_rtt_polling_interval_command,
        .mode = COMMAND_EXEC,
        .help = "show or set polling interval in ms",
        .usage = "[interval]"
    },
    {
        .name = "channels",
        .handler = handle_rtt_channels_command,
        .mode = COMMAND_EXEC,
        .help = "list available channels",
        .usage = ""
    },
    {
        .name = "channellist",
        .handler = handle_channel_list,
        .mode = COMMAND_EXEC,
        .help = "list available channels",
        .usage = ""
    },
    COMMAND_REGISTRATION_DONE
};

搜索 rtt_target_command_handlers 发现它只被注册到了特定的目标类型中,例如 arc_cmd.ccortex_m.chla_target.c

所以我们需要修改源码,将其也注册到RISC-V的目标命令处理器中。修改位于 src\target\riscv\riscv.criscv_command_handlers 数组。

首先需要包含头文件:

#include <rtt/rtt.h>

然后在 riscv_command_handlers 数组末尾,COMMAND_REGISTRATION_DONE 之前添加:

    {
        .chain = rtt_target_command_handlers,
    },

这样,RISC-V目标就具备了完整的RTT命令支持。

实例

TARGET移植RTT

添加源码

从以下地址下载代码:
https://github.com/SEGGERMicro/RTT

添加以下文件到自己的工程:

  • RTT/SEGGER_RTT.c
  • RTT/SEGGER_RTT.h
  • RTT/SEGGER_RTT_Printf.c
  • Syscalls/SEGGER_RTT_Syscalls_GCC.c (按照编译器选择)
  • Config/SEGGER_RTT_Conf.h

配置

相关配置位于 SEGGER_RTT_Conf.h 中。

LOCK相关实现
SEGGER_RTT_Conf.hSEGGER_RTT_LOCK()SEGGER_RTT_UNLOCK() 需要根据目标平台实现。例如对于RISC-V,可以实现为:

    #define SEGGER_RTT_LOCK()  {                                                         \
                                 unsigned int _SEGGER_RTT__LockState;                       \
                                 __asm volatile ("csrr  %0, mstatus  \n\t"     \
                                                "csrci mstatus, 8   \n\t"     \
                                                "andi  %0, %0,  8   \n\t"     \
                                                : "=r" (_SEGGER_RTT__LockState)            \
                                                :                                         \
                                                :                                         \
                                               );

    #define SEGGER_RTT_UNLOCK()    __asm volatile ("csrr  a1, mstatus  \n\t"     \
                                                   "or    %0, %0, a1   \n\t"     \
                                                   "csrs  mstatus, %0  \n\t"     \
                                                   :                                         \
                                                   : "r" (_SEGGER_RTT__LockState)            \
                                                   : "a1"                        \
                                                  );                             \
                              }

测试代码

可以参考官方示例 Main_RTT_InputEchoApp.c,添加简单的测试代码:

#include "SEGGER_RTT.h"

    static char r;
    SEGGER_RTT_WriteString(0, "SEGGER Real-Time-Terminal Sample\r\n");
    SEGGER_RTT_ConfigUpBuffer(0, NULL, NULL, 0, SEGGER_RTT_MODE_NO_BLOCK_SKIP);
    do {
        r = SEGGER_RTT_WaitKey();
        SEGGER_RTT_Write(0, &r, 1);
        r++;
    } while (1);

OpenOCD配置

首先通过telnet连接到OpenOCD服务:

telnet localhost 4444

接下来需要找到RTT控制块在内存中的地址。可以通过GDB命令查看全局变量 _SEGGER_RTT 的地址:

(gdb) p /x &_SEGGER_RTT
$1 = 0x28100bfc

或者在生成的map文件中搜索:

 .bss._SEGGER_RTT
                0x0000000028100bfc       0xa8 build/acore/app/libapp.a(SEGGER_RTT.o)
                0x0000000028100bfc                _SEGGER_RTT

为了确保RTT控制块已正确初始化,可以在程序运行到RTT初始化之后,通过GDB检查其结构体内容,确认 acID 字段为 "SEGGER RTT”

如果执行命令时提示 [riscv.cpu0] Failed to read priv register.,则需要在halt条件下执行RTT设置命令。

在OpenOCD的telnet会话中,输入以下命令进行设置和启动:

rtt setup 0x28100bfc 1024
rtt start

成功识别到RTT控制块后,会输出类似信息:

rtt: Searching for control block 'SEGGER RTT'
rtt: Control block found at 0x28100bfc

接着,为通道0启动一个TCP服务器,监听端口19021,并发送初始消息:

rtt server start 19021 0 "Hello RTT"

现在,可以使用客户端连接了。可以使用SEGGER提供的 JLinkRTTClient.exe 工具连接 localhost:19021 端口,也可以自己实现一个TCP客户端进行数据收发。

要停止服务,使用命令:

rtt server stop 19021

OpenOCD中RTT相关的关键代码与数据流

首先,RTT相关命令的实现分为两部分:

  • server start/stop 的实现位于 src\server\rtt_server.c,对应 rtt_command_handlers
  • 其他RTT相关命令(如 setup, start, stop, polling_interval 等)实现位于 src/rtt/tcl.c 中,对应 rtt_target_command_handlers

然后是搜寻控制块的实现,位于 src\target\rtt.ctarget_rtt_find_control_block 函数。它会从 setup 设置的起始地址和大小范围内读取内存,搜寻指定的ID字符串(默认为 ”SEGGER RTT”)。

获取通道信息的逻辑在 read_rtt_channel 函数中。它是根据RTT控制块的结构体定义去解析内存布局。控制块结构体定义大致如下:

typedef struct {
  char                    acID[16];                 // Initialized to "SEGGER RTT"
  int                     MaxNumUpBuffers;          // Initialized to SEGGER_RTT_MAX_NUM_UP_BUFFERS (type. 2)
  int                     MaxNumDownBuffers;        // Initialized to SEGGER_RTT_MAX_NUM_DOWN_BUFFERS (type. 2)
  SEGGER_RTT_BUFFER_UP    aUp[SEGGER_RTT_MAX_NUM_UP_BUFFERS];       // Up buffers
  SEGGER_RTT_BUFFER_DOWN  aDown[SEGGER_RTT_MAX_NUM_DOWN_BUFFERS];   // Down buffers
#if SEGGER_RTT__CB_PADDING
  unsigned char           aDummy[SEGGER_RTT__CB_PADDING];
#endif
} SEGGER_RTT_CB;

控制块开头是16字节ID和两个4字节的整数,共24字节。之后就是上行和下行通道数组,每个通道缓冲区描述符(SEGGER_RTT_BUFFER_UP/DOWN)也是24字节。只要找到这个结构体,就可以按此布局解析出所有通道信息。

写数据到通道的实现位于 write_to_channel 函数,其逻辑类似于环形缓冲区(FIFO)的写入操作。
从通道读数据的实现位于 read_from_channel 函数,其逻辑类似于环形缓冲区(FIFO)的读出操作。

整个数据链路可以分为上行和下行两部分:

下行链路 (Host -> Target)

下行链路的目的是将TCP客户端发送的数据写入到目标的down通道。

  1. setup 命令 (handle_rtt_setup_command) 会注册写回调:source.write = &target_rtt_write_callback;
  2. rtt server start 命令会添加一个服务,其输入处理器 (input_handler) 被设置为 rtt_input
  3. 在OpenOCD的主服务循环 (server_loop) 中,会调用 service->input(c),即 rtt_input 函数。
  4. rtt_input 函数从TCP连接读取数据,然后调用 rtt_write_channel 写入通道。
  5. rtt_write_channel 最终调用之前注册的 rtt.source.write,也就是 target_rtt_write_callback
  6. target_rtt_write_callback 调用 write_to_channel 函数,将数据真正写入到目标内存的down通道缓冲区中。

链路总结openocd_thread -> server_loop -> service->input (rtt_input) -> tcp connection_read/rtt_write_channel -> rtt.source.write (target_rtt_write_callback) -> write_to_channel

上行链路 (Target -> Host)

上行链路的目的是将目标up通道中的数据读取并发送给TCP客户端。

  1. setup 命令注册读回调:source.read = &target_rtt_read_callback;
  2. rtt server start 命令在客户端连接时,会通过 rtt_new_connection 为该通道注册一个接收器(sink):rtt_register_sink(service->channel, &read_callback, connection);。这个 read_callback 函数负责将数据通过TCP连接发送出去。
  3. target_rtt_read_callback 函数调用 read_from_channel 从up通道读取数据。读到的数据会遍历所有为该通道注册的sink,并调用 sink->read 回调(即上一步注册的 read_callback)将数据发送出去。
  4. read_callback 函数内部调用 connection_write 将数据写入TCP连接。
  5. 那么,谁触发了 target_rtt_read_callback 呢?这是在 rtt_set_polling_intervalrtt_start 命令中,通过注册一个定时器回调 read_channel_callback 实现的。
  6. read_channel_callback 函数会调用 rtt.source.read,也就是 target_rtt_read_callback,从而启动一次上行数据读取和转发流程。

链路总结:定时器触发 -> read_channel_callback -> rtt.source.read (target_rtt_read_callback) -> read_from_channel -> 回调 sink->read (read_callback, connection) -> connection_write 发送数据到TCP客户端。

总结

以上通过修改OpenOCD的源码,使得TARGET为RISC-V时也支持RTT。请注意,使用RTT有一个关键前提:在芯片未处于HALT(暂停)状态时,调试器也必须能通过“后台内存访问”来读写内存。如果只有在HALT时才能访问内存,那么RTT的“实时”传输意义就大打折扣了。是否支持在未HALT时进行后台内存操作,这个特性依赖于具体调试硬件(如调试探针)和目标芯片本身的调试模块实现。

希望这篇在云栈社区分享的配置指南,能帮助你更高效地进行RISC-V平台的调试工作。如果在实践中遇到问题,欢迎在社区内与其他开发者交流探讨。




上一篇:STM32C0优化GUI:使用TouchGFX L8图像压缩与工程配置详解
下一篇:深入解析芯片LBIST技术:原理、架构与应用场景解析
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-2-27 20:06 , Processed in 1.675819 second(s), 46 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表