Skip to main content
Access SEGGER Real-Time Transfer (RTT) for high-speed bidirectional communication with embedded devices during debugging.

Import

from lager.core import RTT

Overview

RTT (Real-Time Transfer) is a technology from SEGGER that allows high-speed bidirectional communication between a host and an embedded target through the debug probe. It’s significantly faster than traditional UART and doesn’t require dedicated pins. Key Benefits:
  • Very high transfer speeds (up to several MB/s)
  • No additional hardware pins required
  • Works during debugging sessions
  • Minimal target overhead

RTT Class

RTT(attach=False)

Create an RTT connection instance.
from lager.core import RTT

with RTT() as rtt:
    data = rtt.read_some()
    print(data.decode('utf-8'))
Parameters:
ParameterTypeDescription
attachboolReserved for future use (default: False)
Note: Requires an active debug connection with J-Link.

Methods

Reading Data

read_until(expected, timeout=None)

Read data until an expected byte sequence is found.
with RTT() as rtt:
    # Read until newline
    data = rtt.read_until(b'\n', timeout=5.0)
    print(data.decode('utf-8'))
Parameters:
ParameterTypeDescription
expectedbytesByte sequence to wait for
timeoutfloatTimeout in seconds (None = no timeout)
Returns: bytes - Data read including the expected sequence

read_line(ending=b'\n', timeout=None)

Read a single line of text.
with RTT() as rtt:
    line = rtt.read_line(timeout=2.0)
    print(line.decode('utf-8').strip())
Parameters:
ParameterTypeDescription
endingbytesLine ending to wait for (default: b'\n')
timeoutfloatTimeout in seconds
Returns: bytes - Line including the ending character

read_all()

Read all available data until EOF.
with RTT() as rtt:
    data = rtt.read_all()
Returns: bytes - All available data

read_some()

Read at least one byte of data (blocking).
with RTT() as rtt:
    data = rtt.read_some()
Returns: bytes - Available data (at least 1 byte)

read_eager()

Read immediately available data (non-blocking, may return empty).
with RTT() as rtt:
    data = rtt.read_eager()
    if data:
        print(f"Received: {data}")
Returns: bytes - Available data (may be empty)

read_very_eager()

Read all immediately available data (non-blocking).
with RTT() as rtt:
    data = rtt.read_very_eager()
Returns: bytes - All immediately available data

read_lazy()

Read data without blocking.
data = rtt.read_lazy()
Returns: bytes - Available data without blocking

read_very_lazy()

Read all immediately available data without blocking (similar to read_very_eager()).
with RTT() as rtt:
    data = rtt.read_very_lazy()
Returns: bytes - All immediately available data

Writing Data

write(data)

Write data to the RTT channel.
with RTT() as rtt:
    rtt.write(b'Hello from host!\n')
Parameters:
ParameterTypeDescription
databytesData to send

Pattern Matching

expect(patterns, timeout=None)

Wait for one of several patterns to appear.
with RTT() as rtt:
    # Wait for OK or ERROR
    index, match, data = rtt.expect([b'OK', b'ERROR'], timeout=10.0)

    if index == 0:
        print("Success!")
    elif index == 1:
        print("Error occurred")
    else:
        print("Timeout or no match")
Parameters:
ParameterTypeDescription
patternslistList of byte patterns (or regex) to match
timeoutfloatTimeout in seconds
Returns: tuple - (index, match_object, text) where index is -1 on timeout

Examples

Basic RTT Communication

from lager.core import RTT
from lager.debug.api import connect_jlink, disconnect
import time

# First, connect to the debug target
connect_jlink(speed='4000', device='NRF52840_XXAA', transport='SWD')

try:
    with RTT() as rtt:
        # Read log output
        print("Reading RTT output...")
        for _ in range(10):
            data = rtt.read_eager()
            if data:
                print(data.decode('utf-8', errors='ignore'), end='')
            time.sleep(0.1)
finally:
    disconnect()

Interactive RTT Console

from lager.core import RTT
import threading
import sys

def reader_thread(rtt):
    """Background thread to read RTT output."""
    while True:
        try:
            data = rtt.read_some()
            print(data.decode('utf-8', errors='ignore'), end='', flush=True)
        except Exception:
            break

with RTT() as rtt:
    # Start reader thread
    reader = threading.Thread(target=reader_thread, args=(rtt,), daemon=True)
    reader.start()

    # Read user input and send to device
    print("RTT Console (Ctrl+C to exit)")
    print("-" * 40)

    try:
        while True:
            line = input()
            rtt.write((line + '\n').encode('utf-8'))
    except KeyboardInterrupt:
        print("\nExiting...")

Command/Response Protocol

from lager.core import RTT

def send_command(rtt, command, timeout=5.0):
    """Send command and wait for response."""
    # Clear any pending data
    rtt.read_very_eager()

    # Send command
    rtt.write(f"{command}\n".encode('utf-8'))

    # Wait for response (ends with OK or ERROR)
    index, match, data = rtt.expect([b'OK\r\n', b'ERROR\r\n'], timeout=timeout)

    if index == 0:
        return True, data.decode('utf-8')
    elif index == 1:
        return False, data.decode('utf-8')
    else:
        return None, "Timeout"

with RTT() as rtt:
    # Send commands
    success, response = send_command(rtt, "GET VERSION")
    if success:
        print(f"Version: {response}")

    success, response = send_command(rtt, "GET STATUS")
    if success:
        print(f"Status: {response}")

Log Capture

from lager.core import RTT
import time

def capture_logs(duration=10.0, output_file='rtt_log.txt'):
    """Capture RTT logs to a file."""
    with RTT() as rtt:
        with open(output_file, 'w') as f:
            start_time = time.time()

            while time.time() - start_time < duration:
                data = rtt.read_eager()
                if data:
                    text = data.decode('utf-8', errors='ignore')
                    f.write(text)
                    print(text, end='')
                time.sleep(0.01)

    print(f"\nLogs saved to {output_file}")

capture_logs(duration=30.0)

Production Test with RTT

from lager.core import RTT
from lager.debug.api import connect_jlink, disconnect, flash_device

def rtt_functional_test(firmware_path, expected_messages):
    """Flash firmware and verify RTT output."""
    results = {
        'flash': False,
        'boot': False,
        'messages': []
    }

    # Connect and flash
    connect_jlink(speed='4000', device='NRF52840_XXAA', transport='SWD')

    try:
        # Flash firmware
        for msg in flash_device(([firmware_path], [], [])):
            print(msg)
        results['flash'] = True

        # Wait for boot message via RTT
        with RTT() as rtt:
            # Wait for boot complete message
            index, match, data = rtt.expect([b'BOOT COMPLETE'], timeout=10.0)

            if index == 0:
                results['boot'] = True
                print("Device booted successfully")
            else:
                print("Boot timeout")
                return results

            # Check for expected messages
            for expected in expected_messages:
                index, match, data = rtt.expect([expected.encode()], timeout=5.0)
                if index == 0:
                    results['messages'].append((expected, True))
                else:
                    results['messages'].append((expected, False))

    finally:
        disconnect()

    return results

# Run test
results = rtt_functional_test(
    'firmware.hex',
    ['SELF TEST PASS', 'SENSORS OK', 'READY']
)

print("\n=== TEST RESULTS ===")
print(f"Flash: {'PASS' if results['flash'] else 'FAIL'}")
print(f"Boot: {'PASS' if results['boot'] else 'FAIL'}")
for msg, passed in results['messages']:
    print(f"  {msg}: {'PASS' if passed else 'FAIL'}")

RTT Auto-Detection

When connecting with J-Link, the system automatically searches RAM for the RTT control block. This is necessary because:
  • J-Link only searches at 4KB-aligned addresses by default
  • Many firmwares place RTT at non-aligned addresses
  • Without auto-detection, RTT may fail even with RTT-enabled firmware
The auto-detection searches the standard ARM Cortex-M RAM region (0x20000000 - 0x20010000) for the “SEGGER RTT” signature.

Prerequisites

RequirementDescription
Debug ConnectionActive J-Link debug connection
RTT FirmwareTarget firmware must include RTT library
J-Link SoftwareJLinkGDBServer must be running

Firmware Setup

To use RTT, your embedded firmware must include the SEGGER RTT library:
// In your firmware
#include "SEGGER_RTT.h"

void main(void) {
    SEGGER_RTT_Init();
    SEGGER_RTT_printf(0, "Hello from RTT!\n");

    while (1) {
        // Your application code
        SEGGER_RTT_printf(0, "Status: %d\n", get_status());
    }
}

Notes

  • RTT communicates over the debug probe (J-Link), not a separate connection
  • Default RTT channel is 0 (most firmware uses channel 0 for terminal)
  • RTT buffer sizes are configured in firmware (typically 1KB-4KB)
  • The RTT class uses telnet to port 9090 (J-Link RTT server)
  • Context manager (with statement) ensures proper cleanup
  • For very high-speed logging, consider using binary format instead of text