文章總列表

SEGGER RTT (3)

SEGGER RTT (3)

Let’s Build a Trace System

In previous article articles [1][2], I’ve introduced how to use SEGGER RTT as conventional printf method. I’m not fully satisfied with this method as I mentioned in [2]. Because it had several drawbacks
  • printf() requires SRAM to store format string, which may be a problem is SRAM size is limited
  • printf() takes cycles to generate string
    • It must not be trivial job to generate decimal value (%d)
Since a channel (SEGGER RTT) already exists to transmit string, why not reuse the channel to transmit essential values and do string composing in PC-side. Take following code as an example:
printf("idx1=%d, idx2=%d\n", idx1, idx2);
In legacy method, it may require >16bytes and MCU does string formatting. In proposed method, one could only transmit essential values (12bytes)
  • An ID code which indicates the format string
    • ID=1 -> “idx1=%d, idx2=%d\n”
  • Parameters
    • idx1
    • idx2
The article would introduce how to implement the idea in both MCU/PC side.

MCU-Side Program

Our test program is quite easy as shown below. A trace ID macro is defined to facilitate usage. It includes magic number to enable PC-side application to detect the start-of-trace. The payload is loop-count value {i, i<<16}. I put them in an array and invokes SEGGER_RTT_Write() to throw the trace.
#define MK_TRACE_HEADER(id)     ((0xABCD<<16) | ((id)<<0))

int main(void)
{
    unsigned int buf_idx = 0;

    while(1) {
        unsigned int i;
        for(i=0; i<256; i++) {
            unsigned int log_buf[3] = {
                MK_TRACE_HEADER(0x1),
                i,
                i<<16,
            };
            
            SEGGER_RTT_Write(buf_idx, log_buf, sizeof(log_buf));
            nrf_delay_ms(100);
        }
    }
}
Although you may not be familiar with Nordic’s API, I think that’s quite easy to understand the behavior of the code above. That’s nothing more than a loop keeps throwing logs to SEGGER RTT channel.

PC-Side Program

In [2], I already show how to use Python’s telnetlib to build the PC-side application. However, that’s not applicable in this purpose. I tried to use telnetlib to receive the MCU message shown above. However, byte value “0” & “255” are filtered by telnetlib (maybe that’s telnet protocol). So I decided to use SOCKS API:
import socket
import array
import struct

def socket_version():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect(('127.0.0.1', 19021))

    while True:
        incoming_data = sock.recv(1024)
        if incoming_data != None:
            trace_parsing(incoming_data)
            
def main():
    os.system('taskkill -im jlink.exe')
    os.system(r'start "" "c:\Program Files (x86)\SEGGER\JLink_V510i\JLink.exe" -device nrf51822 -if swd -speed 4000 -autoconnect 1')
    time.sleep(1)

    socket_version()
Before invokes socket_version(), we added some delay to ensure JLINK’s telnet server is started. In socket_version(), you may find it’s quite easy to use SOCKS library to connect the JLINK’s telnet server. It contains a loop which keeps draining message from SOCKS interface. The message is sent to trace_parsing() to do message parsing:
def trace_data_lookup(trace_id):
    trace_dict = [
        {'len': 1}, # 0x0000
        {'len': 2}, # 0x0001
    ]
    return trace_dict[trace_id]['len']

def trace_parsing(incoming_data):
    # Incoming data plus existing buffer
    buf = trace_parsing.buf + incoming_data

    while True:
        # Check whether there's sufficient data in buf
        if len(buf) < 4:
            break

        # Get the control word from buffer
        ctrl_word = struct.unpack('I', buf[0:4])[0]
        magic_word = (ctrl_word >> 16) & 0xFFFF  # bit[31:16]
        trace_id =   (ctrl_word >>  0) & 0xFFFF  # bit[15:0]

        # Magic word should be 0xABCD, if not matched, omit 1byte here (because the RTT buffer is not 4 byte sync)
        if magic_word != 0xABCD:
            buf = buf[1:]
            continue
        
        # Check whether there's sufficient words in buf (4byte_header + num_word)
        trace_len = trace_data_lookup(trace_id)
        packet_len = 4 + trace_len*4
        if len(buf) >= packet_len:
            trace_body = buf[4:packet_len]
            emit_trace(trace_id, trace_body)
            buf = buf[packet_len:]
        else:
            break

    # Keep uncompleted buffer to static buf
    trace_parsing.buf = buf

trace_parsing.buf = ''
The function had a property “buf” which behaves like static variable in C. At first, the incoming message is appended to already existed message. In the loop, it first checks whether the buffer size is greater than 4, which is trace-ID size.
Then we use Python’s struct library to interpret the incoming message to extract trace-header. Here’s some interesting synchronization procedure. The header size is 4 byte and we expect 0xABCD in bit[31:16] indicating a valid trace. However, the PC program may start to receive data from byte 0/1/2/3 (with an unknown offset). So the program throws away buffer byte-by-byte until hitting the magic number 0xABCD.
Then trace_data_look() reports how many words in trace. If there’s sufficient data in buffer, then invokes emit_trace() to generate the string:
def emit_trace(trace_id, trace_body):
    trace_len = trace_data_lookup(trace_id)
    struct_fmt = '%dI' % trace_len
    args = struct.unpack(struct_fmt, trace_body)

    if trace_id == 0x0000:
        print('[TRC 0x0001] arg0=%08X, arg1=%08X' % args)

    else:
        print('[TRC #%d]' % trace_id),
        for i, arg in enumerate(args):
            print('arg%d=0x%08X, ' % (i, arg)),
        print('')
Screenshot below shows how the program runs. The code above generate different strings according to different traces. For example, one could throw the content of some registers and do bit-unpack in PC-side. This is more efficient in PC-side rather than in target side.



The framework proposed here is also quite handy to do arbitrary processing. For example, one may use gathered data to have real time FFT plot.

Conclusion

The pro and cons of this method
Pro
  • Save code size, data memory size, MCU-cycle in target side. These code can be existed even within product
Cons
  • Trace code and parsing code in different places. Harder to maintain and coding.
  • Need a SEGGER-JLINK to use this method (compare with UART method, which only requires a cheap FTDI chip)

Reference

[1]: SEGGER-RTT (1), http://lihgong.blogspot.com/2016/04/segger-rtt-1.html
[2]: SEGGER-RTT (2), http://lihgong.blogspot.com/2016/04/segger-rtt-2.html
[3]: SEGGER’s RTT introduction website, https://www.segger.com/jlink-rtt.html

留言

這個網誌中的熱門文章

幼犬書桌椅選擇心得 升降桌 兒童桌椅

STM32 UART + DMA,使用HAL實作TX/RX,以及不定長度接收

CANON G3000 廢墨瓶改裝