Back in the old days when debugging was precaurius we like to use printf a lot, but a lot. Semihosting was the defacto solution but it was slow, very slow. Then came the RTT, a faster and more reliable solution. The RTT is a feature of the J-Link debugger that allows you to send data from the target to the host in real-time. This is very useful when you need to debug a system that is time-sensitive.
Ok, as you may wonder RTT is a feature that only works when you have a segger jlink debugger, lucky for us some Nucleo boards has ther possibility to change the ST-Link firmware to a J-Link firmware. This is the case of the Nucleo-G0RB1 board, which is the one we use in one of our trainigs.
Download the application SEGGER STLinkReflash utility from this page ( also install the J-Link software package and windows drivers ) ST-Link On board and follow the instruction to reflash the board. This needs to be carried out in Windows
Installing the new debug tools in Linux
In Linux we can use our favorite AUR helper to install the J-Link software package, in this case we will use paru.
$ paru -S jlink-software-and-documentation jlink-systemview
Locate the RTT source files under the J-Link installation directory, in my case it is located at /opt/SEGGER/JLink_V794/RTT/RTT_Sample
or C:\Program Files\SEGGER\JLink_V794\Samples\RTT
copy the files into your project following the next directory structure:
.
├── rtt
│ ├── Source
│ │ ├── SEGGER_RTT_Conf.h # Configuration
│ │ ├── SEGGER_RTT.h # Main header
│ │ ├── SEGGER_RTT.c # Main implementation
│ │ ├── SEGGER_RTT_printf.c # Print functions
│ │ └── SEGGER_RTT_ASM_ARMv7M.S # for Cortex-M3/M4/M7
│ └─ Syscalls
│ ├── SEGGER_RTT_Syscalls_GCC.c # redirection for GCC and newlib
│ ├── SEGGER_RTT_Syscalls_IAR.c # redirection for IAR
│ ├── SEGGER_RTT_Syscalls_KEIL.c # redirection for KEIL ARM
│ └── SEGGER_RTT_Syscalls_SES.c # redirection for Segger Embedded System
We assume you are using our template with a makefile, so we will add the following lines to the makefile to include RTT source file to our build process:
# directories with source files to compiler (.c y .s)
SRC_PATHS = app
SRC_PATHS += cmsisg0/startups
SRC_PATHS += halg0/Src
SRC_PATHS += rtt/Source
SRC_PATHS += rtt/Syscalls # just in case you decide to use redirection
# directories with header files
INC_PATHS = app
INC_PATHS += cmsisg0/core
INC_PATHS += cmsisg0/registers
INC_PATHS += halg0/Inc
INC_PATHS += rtt/Source
Add the follwoing files to be compile as part of the project
SRCS += SEGGER_RTT.c SEGGER_RTT_printf.c
Also coment the following line in the makefile and uncomment the one below to call the J-Link GDB server instead of the OpenOCD server
#---open a debug server conection------------------------------------------------------------------
open :
# openocd -f board/st_nucleo_g0.cfg
JLinkGDBServerCL -if SWD -device stm32g0b1re -nogui -port 3333
And in the .gdbinit
replace the lines for OpenOCD with the following ones
#---connect and load program
target remote localhost:3333
#mon arm semihosting enable
load
#mon reset halt
mon reset
break main
continue
Now we are ready to do some write and read some messages from the RTT, the following code is a simple example of how to use the RTT to send and receive messages from the target to the host.
#include "bsp.h"
#include "SEGGER_RTT.h"
int main( void )
{
int i = 0;
/* Initialize HAL */
HAL_Init( );
/*Init RTT library*/
SEGGER_RTT_Init();
while(1)
{
i++;
SEGGER_RTT_printf( 0, "Hola Mundo %d\n", i );
HAL_Delay( 1000 );
}
return 0u;
}
Open a terminal and run the following command to start the GDB server
$ make open
Open another terminal and run the following command to start the GDB client and the run the program with continue
$ make debug
And now just type in another terminal (the third one) the following command to read the messages from the target
$ JLinkRTTClient
###RTT Client: ************************************************************
###RTT Client: * SEGGER Microcontroller GmbH *
###RTT Client: * Solutions for real time microcontroller applications *
###RTT Client: ************************************************************
###RTT Client: * *
###RTT Client: * (c) 2012 - 2016 SEGGER Microcontroller GmbH *
###RTT Client: * *
###RTT Client: * www.segger.com Support: support@segger.com *
###RTT Client: * *
###RTT Client: ************************************************************
###RTT Client: * *
###RTT Client: * SEGGER J-Link RTT Client Compiled Nov 29 2023 13:43:55 *
###RTT Client: * *
###RTT Client: ************************************************************
###RTT Client: -----------------------------------------------
###RTT Client: Connecting to J-Link RTT Server via localhost:19021 ...###RTT Client: Connected.
SEGGER J-Link V7.94 - Real time terminal output
SEGGER J-Link ST-LINK V1.0, SN=779643008
Process: JLinkGDBServerCL.exe
Hola Mundo 1
Hola Mundo 2
Hola Mundo 3
Hola Mundo 4
You can also stop the program and make it wait to receive a character from the keyboard using the following SEGGER_RTT_WaitKey fucntion.
#include "bsp.h"
#include "SEGGER_RTT.h"
int main( void )
{
char a;
/* Initialize HAL */
HAL_Init( );
/*Init RTT library*/
SEGGER_RTT_Init();
while(1)
{
SEGGER_RTT_printf( 0, "Press a key: ", a );
/*Wait for a key to be pressed*/
a = SEGGER_RTT_WaitKey();
/*Print the value of i in the terminal 0*/
SEGGER_RTT_printf( 0, "\nChar received %c\n", a );
}
return 0u;
}
There is a non-blocking version of this function called SEGGER_RTT_GetKey()
it will return -1 if no key is pressed. ideal to use with if statements.
#include "bsp.h"
#include "SEGGER_RTT.h"
int main( void )
{
char a;
/* Initialize HAL */
HAL_Init( );
/*Init RTT library*/
SEGGER_RTT_Init();
while(1)
{
SEGGER_RTT_printf( 0, "Press a key: ", a );
/*get the key pressed, if there was one*/
a = SEGGER_RTT_GetKey();
/*ask if there was a key stroke or not*/
if( a != -1 )
{
/*Print the value of i in the terminal 0*/
SEGGER_RTT_printf( 0, "\nChar received %c\n", a );
}
/*Delay 50ms, enough to sample any key*/
HAL_Delay( 50 );
}
return 0u;
}
By default the RTT uses channel 0, but you can use up to SEGGER_RTT_MAX_NUM_UP_BUFFERS channels to send and receive messages from the target to the host. The following code shows how to use the RTT with multiple channels
#include "bsp.h"
#include "SEGGER_RTT.h"
/*create a buffer i memoery to hold the messages to be display
in terminal 1, BUFFER_SIZE_UP is defined in SEGGER_RTT_Conf file*/
uint8_t Buffer[ BUFFER_SIZE_UP ];
int main( void )
{
int i = 0;
/* Initialize HAL */
HAL_Init( );
/*Init RTT library*/
SEGGER_RTT_Init();
/*Configure the buffer to be used by the RTT channel 1*/
SEGGER_RTT_ConfigUpBuffer( 1, "Log", Buffer, sizeof(Buffer), SEGGER_RTT_MEMCPY_USE_BYTELOOP );
while(1)
{
i++;
/*Print the value of i in the terminal 0*/
SEGGER_RTT_printf( 0, "Channel 0 %d\n", i );
/*Print the value of i in the terminal 1*/
SEGGER_RTT_printf( 1, "Channel 1 %d\n", i );
HAL_Delay( 1000 );
}
return 0u;
}
In case using VSCode to debug there is a way to add the RTT terminal, to avoid using so many terminals, just go back and read AN003: VSCode Intellisense
Well, this was a very basic introduction, at the end of the day we do not use printf in the final version of our programs, but it is very useful when you need to debug certain cases. There are more functions and fucntionalities that you can use with the RTT but at the end of the day I don't use them because the same debugger is quiet more powerfull.
In case you want to know more about the RTT you can check the offical SEGGER RTT documentation or the follwoing nice tutorial from Code Inside Out