實時傳輸(RTT)是SEGER基于基本的存儲讀寫實現(xiàn)的target和主機之間的雙向通訊接口。該規(guī)范獨立于目標架構(gòu), 只要支持“后臺內(nèi)存訪問”,也就是說當目標在運行時,可以讀寫內(nèi)存,那么就可以使用該方式。一個通道對應一個TCP/IP 連接,可以給某個通道開啟多個TCP/IP服務。
當前的OpenOCD版本只支持單target設備,不支持channel buffer flags,并且TARGET為RISCV時還需要一些修改才能支持。
其中target到host為up數(shù)據(jù)流
Host到target為down數(shù)據(jù)流。
先介紹下基本的rtt相關的配置命令。在TARGET為RISCV時,目前的版本默認除了rtt server外的命令都不支持,需要修改源碼,后面會介紹。
rtt setup address size [ID]
ID默認為 字符串 "SEGGER RTT"
設置之后OpenOCD就后臺從address地址處的size大小范圍內(nèi)搜尋ID對應的控制塊。
rtt start
如果控制塊的位置未知則會搜尋。
rtt stop
rtt polling_interval [interval]
設置查詢上行數(shù)據(jù)的間隔為interval單位為ms,不指定interval則為打印當前值。
rtt channels
打印所有通道和其屬性
rtt channellist
和 rtt channels一樣只是rtt channellist
的執(zhí)行結(jié)果返回一個tcl的list,可以放在[]中,腳本中其他地方使用。
rtt server start port channel [message]
給指定通道啟動一個tcp服務端,當指定message不為空時,就會將內(nèi)容發(fā)送給連接到該服務端的客戶端。
rtt server stop port
停止指定通道對應的tcp服務。
在RISCV平臺默認輸入rtt看到只支持以下兩個命令
> rttrttrtt serverrtt server start <port> <channel> [message]rtt server stop <port>>
這是在src\server\rtt_server.c中實現(xiàn)的
只有start和stop倆個命令
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中實現(xiàn)的
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看到
看到只注冊到了以下target中

所以我們需要修改注冊到riscv中
src\target\riscv\riscv.c
riscv_command_handlers中添加
{.chain = rtt_target_command_handlers,},

從以下地址下載代碼
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相關實現(xiàn)
SEGGER_RTT_Conf.h中
SEGGER_RTT_LOCK()
SEGGER_RTT_UNLOCK()
例如對于RISCV
#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
添加測試代碼
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);
連接telnet
telnet localhost 4444
先查看控制塊全局變量的位置
(gdb) p /x &_SEGGER_RTT$1 = 0x28100bfc
或者map文件中搜索
.bss._SEGGER_RTT0x0000000028100bfc 0xa8 build/acore/app/libapp.a(SEGGER_RTT.o)0x0000000028100bfc _SEGGER_RTT
運行到RTT初始化之后查看初始化acID = "SEGGER RTT”
(gdb) p _SEGGER_RTT$2 = {acID = "SEGGER RTT\000\000\000\000\000",MaxNumUpBuffers = 0x3,MaxNumDownBuffers = 0x3,aUp = {{sName = 0x2000e650 "Terminal",pBuffer = 0x28100cb4 <_acUpBuffer> "\377\060SEGGER Real-Time-Terminal Sample\r\nSEGGER Real-Time-Terminal Sample\r\nSEGGER Real-Time-Terminal Sample\r\nSEGGER Real-Time-Terminal Sample\r\nSEGGER Real-Time-Terminal Sample\r\nSEGGER Real-Time-Terminal Sa"...,SizeOfBuffer = 0x400,WrOff = 0x332,RdOff = 0x0,Flags = 0x0}, {sName = 0x0,pBuffer = 0x0,SizeOfBuffer = 0x0,WrOff = 0x0,RdOff = 0x0,Flags = 0x0}, {sName = 0x0,pBuffer = 0x0,SizeOfBuffer = 0x0,WrOff = 0x0,RdOff = 0x0,Flags = 0x0}},aDown = {{sName = 0x2000e650 "Terminal",pBuffer = 0x28100ca4 <_acDownBuffer> "",SizeOfBuffer = 0x10,WrOff = 0x0,RdOff = 0x0,Flags = 0x0}, {sName = 0x0,pBuffer = 0x0,SizeOfBuffer = 0x0,WrOff = 0x0,RdOff = 0x0,Flags = 0x0}, {sName = 0x0,pBuffer = 0x0,SizeOfBuffer = 0x0,WrOff = 0x0,RdOff = 0x0,Flags = 0x0}}}(gdb)
如果提示
[riscv.cpu0] Failed to read priv register.
則要在halt條件下執(zhí)行以下命令
telnet中輸入以下命令
rtt setup 0x28100bfc 1024rtt start
識別到RTT控制塊如下

啟動tcp服務
rtt server start 19021 0 "Hello RTT"打開JLinkRTTClient.exe

停止服務
rtt server stop 10921首先rtt相關命令的實現(xiàn)
server start/stop的實現(xiàn)位于
src\server\rtt_server.c
rtt_command_handlers
其他rtt相關命令實現(xiàn)位于rtt/tcl.c中
rtt_target_command_handlers

然后是搜尋控制的實現(xiàn)位于
src\target\rtt.c
target_rtt_find_control_block
就從setup設置的開始地址和大小的范圍內(nèi)讀mem,搜尋
Id,id沒有在setup設置,則默認為”SEGGER RTT”

獲取通道信息
位于
read_rtt_channel
即根據(jù)控制塊結(jié)構(gòu)體的定義去查找
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, transferring information up from target via debug probe to hostSEGGER_RTT_BUFFER_DOWN aDown[SEGGER_RTT_MAX_NUM_DOWN_BUFFERS]; // Down buffers, transferring information down from host via debug probe to targetunsigned char aDummy[SEGGER_RTT__CB_PADDING];} SEGGER_RTT_CB;
最開始16+4+4=24字節(jié)的其他信息,后續(xù)就是每個通道對應一個SEGGER_RTT_BUFFER_UP/SEGGER_RTT_BUFFER_DOWN都是24字節(jié)
所以只要找到該結(jié)構(gòu)體就可以進行解析

寫數(shù)據(jù)到通道實現(xiàn)位于
write_to_channel,類似于FIFO的寫入操作,相關實現(xiàn)看代碼結(jié)即可

從通道讀數(shù)據(jù)實現(xiàn)位于
read_from_channel,類似于FIFO的讀出操作

所以對應的鏈路如下
Tcp客戶端-> target_rtt_write_callback -> write_to_channel寫數(shù)據(jù)到down 通道
Setup時handle_rtt_setup_command
注冊
source.write = &target_rtt_write_callback;
rtt start命令時執(zhí)行handle_rtt_start_command
注冊驅(qū)動
ret = add_service(&rtt_service_driver, CMD_ARGV[0], CONNECTION_LIMIT_UNLIMITED, service);
即
.input_handler = rtt_input,
add_service中c->input = driver->input_handler;
所以在server_loop中
調(diào)用retval = service->input(c);
即調(diào)用rtt_input
rtt_input即先讀tcp connection_read然后
寫入通道中rtt_write_channel實際是調(diào)用rtt.source.write即target_rtt_write_callback
而server_loop是在openocd_thread線程中循環(huán)調(diào)用的。
合起來就是
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_rtt_read_callback -> read_from_channel 從up通道讀數(shù)據(jù)
Setup時注冊
handle_rtt_setup_command
source.read = &target_rtt_read_callback;
target_rtt_read_callback中調(diào)用sink->read
for (struct rtt_sink_list *sink = sinks[i]; sink; sink = sink->next)sink->read(i, buffer, length, sink->user_data);
Sink是在客戶端連接時rtt_new_connection注冊的
ret = rtt_register_sink(service->channel, &read_callback, connection);
實現(xiàn)將target_rtt_read_callback讀到的buffer,通過sink即read_callback從tcp發(fā)送出去

read_channel_callback中調(diào)用rtt.source.read
而read_channel_callback是在
rtt_set_polling_interval和rtt_start兩個命令中,注冊定時器回調(diào)的。
合起來就是
ttt start命令 ->
注冊定時器回調(diào)read_channel_callback ->
rtt.source.read ->
target_rtt_read_callback ->
read_from_channel -> 讀到數(shù)據(jù)回調(diào)
sink->read ->
read_callback, connection ->
connection_write 發(fā)送數(shù)據(jù)到tcp客戶端
以上通過修改OpenOCD的源碼,是的TARGET為RISCV時也支持RTT。注意要求是在未HALT時也能后臺訪問mem才能使用RTT,否則需要HALT時在能訪問就RTT實時傳輸?shù)囊饬x。是否支持未HALT時進行后臺mem操作,這個依賴于具體硬件的實現(xiàn)。