cancel
Showing results for 
Search instead for 
Did you mean: 

How to read raw SWO data posted by ST-LINK GDB server to a TCP port

Borcut
Associate II

Hello,

I am trying to route printf-like data streamed from the SWO pin of an STM32L432KB MCU to a Windows command prompt using a STLINK-V3SET/MINI probe. I need to do this while flashing new firmware or debugging. The SWO pin uses UART encoding at 2 MHz and everything is configured in software like this:

// alternate function AF0 for PB3/TRACESWO
GPIOB->MODER    = (GPIOB->MODER & ~0xC0) | 0x80;   
GPIOB->AFR[0]  &= ~0x0000F000;
GPIOB->OSPEEDR |=  0x000000C0;        // maximum speed
GPIOB->PUPDR   &= ~0x000000C0;        // no pull-up/down
 
// enable the SWO pin in async. mode
DBGMCU->CR &= ~DBGMCU_CR_TRACE_MODE;
DBGMCU->CR |=  DBGMCU_CR_TRACE_IOEN;
 
// enable DWT and ITM
CoreDebug->DEMCR |=  CoreDebug_DEMCR_TRCENA_Msk;      
 
TPI->CSPSR =  1;                    // port width = 1 bit
TPI->SPPR  =  2;                    // UART encoding
TPI->ACPR  =  HAL_RCC_GetHCLKFreq()/2000000-1; // clock divider
TPI->FFCR &= ~TPI_FFCR_EnFCont_Msk; // no formatter
ITM->LAR   =  0xC5ACCE55;           // unlock the ITM registers
ITM->TCR   =  ITM_TCR_ITMENA_Msk    // enable the ITM
           |  ITM_TCR_TraceBusID_Msk; // stream identifier
ITM->TPR   =  0b1111;               // enable unprivileged acc.
ITM->TER   =  -1UL;                 // enable all ports

Unfortunately the SWO pin is enabled only after starting a debugger connection (for example by running ST-LINK_gdbserver in the background), otherwise the pin is Hi-Z. I think this has something to do with the C_DEBUGEN bit in CoreDebug->DHCSR register which can be set only by a debugger.

My first question is whether it is possible to stream ITM packets over the SWO pin under MCU control exclusively (no external probe, debugger etc.)?

My second question is how to reliably retrieve raw SWO data. This is what I tried so far:

1. Use STM32_Programmer_CLI in shared mode like this:

STM32_Programmer_CLI -c port=SWD shared -startswv freq=80 portnumber=all

This partially works but there are too many lost packets.

2. Use ST-LINK_gdbserver to publish raw SWO data to a TCP port:

ST-LINK_gdbserver -d --attach -z 3443 -a 80000000 -b 40 –cp...
STMicroelectronics ST-LINK GDB server. Version 7.3.0
Copyright (c) 2023, STMicroelectronics. All rights reserved.
 
Starting server with the following options:
        Persistent Mode            : Disabled
        Logging Level              : 31
        Listen Port Number         : 61234
        Status Refresh Delay       : 15s
        Verbose Mode               : Disabled
        SWD Debug                  : Enabled
 
COM frequency = 24000 kHz
Target connection mode: Attach
Reading ROM table for AP 0 @0xe00fffd0
Hardware watchpoint supported by the target
ST-LINK Firmware version : V3J11M3
Device ID: 0x435
PC: 0x79fb70
ST-LINK device status: RUN_MODE
ST-LINK detects target voltage = 3.26 V
ST-LINK device status: RUN_MODE
ST-LINK device initialization OK
Stm32Device, pollAndNotify running...
SwvSrv state change: 0 -> 1
Waiting for connection on port 3443...
Waiting for debugger connection...
Waiting for connection on port 61234...
Accepted connection on port 3443...
SWV collect poll delay set to 34133µs for baudrate 2000000Hz (buffer size 20480b)
SwvSrv state change: 1 -> 2

In another terminal I run orbcat from the Orbuculum project:

orbcat -s localhost:3443 -c 0,"%c"

I can see the connection but no data is exchanged (checked with TCPView). I also tried in shared mode with the same result.

3. Use PyOCD to publish raw SWO data to a TCP port:

pyocd gdb -S -Oenable_swv=1 -Oswv_system_clock=80000000 -Oswv_clock=2000000 -Osemihost_console_type=console -t STM32L432KB

In another terminal I run orbcat like before and I can see the data correctly without any lost packets. The problem with this option is that PyOCD blocks the USB connection with ST-Link and I cannot flash or debug without closing the PyOCD server. Starting and closing PyOCD is very slow so this option is very annoying and not practical at all.

4. Connect SWO to an FTDI serial converter and use Orbuculum to publish raw SWO data to a TCP port:

orbuculum -v 2 --serial-port COM10 --serial-speed 2000000

In another terminal I run orbcat and everything works without any lost packets. I can flash and debug using STM32_Programmer_CLI and ST-LINK_gdbserver at the same time. The problem is that the GDB server must be running in the background in order to have the SWO pin enabled (see the first question) which makes a total of 3 background processes in 3 terminals just to print a log. Another annoyance is the requirement of the serial converter.

The best solution would be the option no. 2 which does not work. So how can I make ST-LINK_gdbserver publish the data like PyOCD and Orbuculum? The documentation says it is possible:

-z <port number>, --swo-port <port number>

Specifies the TCP port number at which the server outputs raw SWO data.

Thanks

5 REPLIES 5
ON
Associate

Hello Borcut,

Did you manage to make method 2 work?

Thanks.

Borcut
Associate II

Hello On,

I did try with ST-LINK_gdbserver version 7.5.0 with the same result.

 

 

cBroadbo
Associate II

Hi Borcut,

Were you ever able to get something working that you were happy with?

I would really like to get this working ST-LINK_gdbserver as that would be ideal.  I use CLion as my IDE which launches ST-LINK_gdbserver for flashing/debugging, which all works nicely.  Getting SWO in this scenario would be great.

I am intrigued by your success routing the SWO line to an FTDI converter.  Could you provide some details on how you did that (what FTDI converter, how you connected it, etc.)?  I have a number of different ST-Links from the ST-Link V3Set to the V3-mini, so hopefully I could make something work.  

Thanks,

Craig

I was able to get the SWO connected to my FTDI pretty easily.  The ST-Link V3Set has the SWO coming out to a couple different pins so I hooked up my scope and found that my SWO data rate was 2M Baud.  Note that I am connected to a custom PCB with a STM32F427 with the SWO lined connected to the programming connector.

Once I knew the correct data rate, I simply connected the ST-Link's SWO, VCC, and GND:  

IMG_6759.jpg

to the brainstem(FTDI -> USB) like so:

IMG_6758.jpg

I used CoolTerm (on Mac) and connected to the USB device for the brainstem, connected and voila!:

IMG_6755.jpg

Notice that there's an SWO control character between each log character.  I know there are some utilities out there to cope with that, but it seem simple enough just for me to modify my existing python serial view script that I use inside Clion and skip over that character.  A more robust handling can come later, but for now I'm getting this:

IMG_6756.jpg

 

Here's the python script I use (note that "import serial" is for pyserial).  Note that it color codes if you format your log statements as you see in my screenshot.

 

import signal
import serial
import sys
import argparse
import re

class Color:
    WHITE      = '\x1b[0;30m'
    RED        = '\x1b[0;31m'
    GREEN      = '\x1b[0;32m'
    YELLOW     = '\x1b[0;33m'
    BLUE       = '\x1b[0;34m'
    PURPLE     = '\x1b[0;35m'
    TEAL       = '\x1b[0;36m'
    DARK_GRAY  = '\x1b[0;37m'
    LIGHT_GRAY = '\x1b[0;38m'
    BOLD_WHITE      = '\x1b[1;30m'
    BOLD_RED        = '\x1b[1;31m'
    BOLD_GREEN      = '\x1b[1;32m'
    BOLD_YELLOW     = '\x1b[1;33m'
    BOLD_BLUE       = '\x1b[1;34m'
    BOLD_PURPLE     = '\x1b[1;35m'
    BOLD_TEAL       = '\x1b[1;36m'
    BOLD_DARK_GRAY  = '\x1b[1;37m'
    BOLD_LIGHT_GRAY = '\x1b[1;38m'

class ConnectionState:
    DISCONNECTED = 1
    CONNECTING = 2
    CONNECTED = 3

global connectionState

def signal_handler(signal, frame):
    global interrupted
    interrupted = True

signal.signal(signal.SIGINT, signal_handler)

def stream(device, baudrate):
    global interrupted
    interrupted = False

    colorDict = {
        'A' : Color.BOLD_TEAL,
        'V' : Color.DARK_GRAY,
        'D' : Color.LIGHT_GRAY,
        'I' : Color.LIGHT_GRAY,
        'S' : Color.BOLD_BLUE,
        'R' : Color.BOLD_GREEN,
        'W' : Color.BOLD_YELLOW,
        'E' : Color.BOLD_RED,
    }

    global connectionState
    if connectionState == ConnectionState.DISCONNECTED:
        connectionState = ConnectionState.CONNECTING
        sys.stdout.write(Color.BOLD_PURPLE)
        print('Connecting to %s at %d baud...' % (device, baudrate))

    with serial.Serial(port=device, baudrate=baudrate, timeout=0.5) as ser:

        regex = re.compile(r"^.* [0-9] ([AVDISRWE]).+[:]")
        if connectionState == ConnectionState.CONNECTING:
            connectionState = ConnectionState.CONNECTED
            print('Connected')

        exceptionCount = 0
        lineBuffer = ""
        while not interrupted:
            try:
                char = ser.read(1)
                if char:
                    #For now just skip over the SWO byte.  A more robust solution would/could do something
                    #with these other than skip them.  But for now, this yields printf style logging
                    if char != b'\x01':
                        lineBuffer += char.decode('windows-1252')
                        if char == b'\n':
                            match = regex.search(lineBuffer)
                            if match and match.group(1):
                                color = colorDict.get(match.group(1), None)
                                if color:
                                    sys.stdout.write(color)
                            sys.stdout.write(lineBuffer)
                            sys.stdout.flush()
                            lineBuffer = ""
            except Exception as e:
                raise e

def main():
    # global interrupted
    parser = argparse.ArgumentParser(description='Stream serial port to stdout')
    parser.add_argument('device', nargs=1, type=str, help='the device to stream')
    parser.add_argument('-b', '--baud', nargs=1, type=int, help='baud rate', required=False, default=2000000)
    args = parser.parse_args()
    global connectionState
    connectionState = ConnectionState.DISCONNECTED
    global interrupted
    interrupted = False

    while not interrupted:
        try:
            stream(args.device[0], args.baud)
        except:
            if connectionState == ConnectionState.CONNECTED:
                connectionState = ConnectionState.DISCONNECTED
                sys.stdout.write(Color.BOLD_PURPLE)
                print('Disconnected')

if __name__ == "__main__":
    main()

 

 

You'll probably want to modify it to your liking.  

Hope others find this useful.

Craig

Borcut
Associate II

Hi cBroadbo,


Were you ever able to get something working that you were happy with?


No, I kind of abandoned trying because I had no more insights. I just use a serial port for logging which is a shame really but it is simple to use and it works everywhere.

Could you provide some details on how you did that (what FTDI converter, how you connected it, etc.)?

Notice that there's an SWO control character between each log character.  I know there are some utilities out there to cope with that, but it seems simple enough just for me to modify my existing python serial view script that I use inside Clion and skip over that character.  A more robust handling can come later, ...

As you found out it is quite easy to route the TRACESWO stream to any serial converter. You just have to make sure the SWO output is enabled by a debugger probe. As for the extra data in the stream you can find a lot of digested info in the Embedded Debugging with the Black Magic Probe book, in particular

  • TRACESWO Protocol on page 9
  • SWO Tracing on page 75
  • Emulating asynchronous mode on page 81

You can find the book on GitHub: 

https://github.com/compuphase/Black-Magic-Probe-Book 

You could replace the serial converter with the VCP serial port already present on your STLink-V3SET or even with the secondary VCP port on the bridge interface but in this case I think you need an alternative firmware.

Borcut