http://www.percepio.com

Trace Recorder Library

Introduction

The trace recorder library is the target system component of Tracealyzer for FreeRTOS. The recorder integrates with FreeRTOS and records a trace of RTOS events and your own User Events. The recorder has two main operating modes, described below.

In snapshot mode, the trace data is kept in target RAM, allowing you to take a "snapshot" at any point by saving the contents of the RAM buffer. The snapshot mode is highly optimized for memory efficiency and the resulting data rate is often just 10-20 KB/s, depending on the system. A trace buffer of a few KB is often sufficient to get a decent trace of the most recent events. This can be used in essentially any system, and even used as "black-box" during field testing or in deployed systems.

In streaming mode, the trace data is transfered continuously to the host PC and thereby allows for tracing over long durations. This supports streaming via SEGGER J-Link debug probes, but may also utilize other interfaces in your system, such as USB, TCP/IP, UARTs, or device file systems.

The recorder library is provided in C source code, and is found in the Tracealyzer installation directory (see "Trace Recorder Library" directory) as a zip file. Since version 3.1, the snapshot and streaming modes are integrated in the same recorder library with a common API.

Snapshot Mode

The snapshot mode keeps the trace in a target RAM buffer, allowing for saving "snapshots" at any point by saving the RAM buffer to a host-side file. This approach has minimal hardware dependencies and can therefore be used on essentially any 32-bit processor, assuming there is some way to retrieve the data, like a debug connection or a file system.

The snapshot recording is optimized for memory efficiency and saves the events as four-byte records, often using only one such record per event, including the timestamp. As a result, our standard demo application only generates about 18 KB/s of trace data in snapshot mode, less than 7 byte per event on average. However, the optimizations used in the snapshot mode prevents streaming the trace, so the streaming mode uses a different implementation and data format.

The trace data can be uploaded to your host PC in several ways:

  • Directly from Tracealyzer, for supported debug probes like SEGGER J-Link.
  • Using a Percepio plugin, available for Eclipse, IAR Embedded Workbench, MPLAB X IDE, and Atmel Studio.
  • Programmatically in your target system, allowing for custom transfer to host.
  • Manually, by saving a RAM dump using your debugger IDE (there is often a "Save Memory" function).
The two latter options are usually always possible, independent of processor and development setup. Tracealyzer can open RAM dump files created by any means, as long as the trace data structure (RecorderDataPtr) is fully included. Tracealyzer supports data files in binary format (.bin) and in Intel Hex format (.hex). Note that the RAM dump does not need to match the exact location of the trace data, since Tracealyzer locates the trace data automatically.

The snapshot mode can be used in two ways, decided by the TRC_CFG_SNAPSHOT_MODE setting in trcSnapshotConfig.h:

  • TRC_SNAPSHOT_MODE_RING_BUFFER
  • TRC_SNAPSHOT_MODE_STOP_WHEN_FULL
If using TRC_SNAPSHOT_MODE_STOP_WHEN_FULL, the recorder stops recording when the buffer gets full. This is suitable when you want to capture the events after a particular point in your code, where the recording is started. When using TRC_SNAPSHOT_MODE_RING_BUFFER, the recording is continuous and overwrites the oldest events with the latest. This is suitable for capturing the events before a point in the code, where the recorder is stopped or the system is simply halted. The ring-buffer mode allows for using the recorder as a "black-box flight recorder", especially if have the possibility to place the recorder buffer in non-volatile RAM or save the trace to a device file system in your general error handling.

It is important that the recorder is not modifying the trace data while saving the snapshot. So before saving the snapshot, you need to either stop the recorder (vTraceStop()) or halt the system, e.g., using a breakpoint. So if you save the trace programmatically while the system is running, make sure to stop the recorder first and also ensure that no new tasks or other kernel objects are created while saving the trace, since this modifies the recorder data.

The recorder library has several settings which you should inspect before using it, such as the two modes described above. The settings are found in the header file trcSnapshotConfig.h, together with detailed documentation.

By default, all events are stored in the same buffer. User events, which are generated by the application, can however be stored in a separate User Event Buffer (UB). This allows for a longer history of less frequent but important user events, since they are not overwritten by kernel events. See the API documentation for xTraceRegisterUBChannel for further information.

For further information on how to use the trace recorder in snapshot mode, see the integration guide, and also the documentation for the Common API and the Extended API for Snapshot mode.

Making a snapshot

This section describes how to upload the trace data from the target system to Tracealyzer, when using the snapshot mode.

Integrated support for SEGGER J-Link
Tracealyzer supports SEGGER J-Link and J-Link compatible debuggers directly (e.g. Atmel SAM-ICE). The J-Link integration works independently of your IDE and supports both snapshot trace (see below) and streaming.

To upload a snapshot using a SEGGER J-Link, select "Read Trace (Snapshot)" in the "J-Link" menu. The trace is then opened in Tracealyzer within a few seconds.

The first time, you get a dialog "Select Memory Region" where you need to enter the address range of the recorder's trace buffer. This can be found can by inspecting the trace buffer (RecorderDataPtr) with your debugger (e.g., "watch"). However, you don't need to use the exact memory range, as long as the trace buffer is fully included in the specified memory range. We therefore recommend specifying a larger memory block, to get some tolerance for minor changes in between builds. This setting is saved and reused in between Tracealyzer sessions. Typical values are 0x0, 0x10000000 or 0x20000000 as start address and 0x10000 or 0x20000 as size (64 KB or 128 KB). J-Link compatible debug probes also include Atmel SAM-ICE and many built-in debug interfaces on demonstration/evaluation boards (where there is a USB connection directly to the board). Look for a SEGGER J-Link label on the board.

Snapshot trace using Eclipse-based IDEs
The Percepio Trace Exporter plugin makes it easy to upload trace snapshots from Eclipse-based IDEs. The plugin supports any debug probe that is supported by your Eclipse IDE.

As shown in the above screenshot, the plugin gives you a new "Percepio" menu from which you can save snapshot traces via the currently active debug session, and then open these in Tracealyzer. The plugin finds the trace data automatically, so no need to enter the address of the trace buffer.

To install the plugin, see https://percepio.com/exporter/ for instructions. Note that the plugin does not include Tracealyzer itself, it is a separate download and runs as a standalone application.

The plugin works with all recent versions of Eclipse, at least since "Mars" (v4.5).

Snapshot trace using Atmel Studio 7
For this purpose, we provide an Atmel Studio extension called "FreeRTOS+Trace" in Atmel Gallery. This installs Tracealyzer for FreeRTOS within Atmel Studio, with a launch button in the Atmel Studio "Tools" menu.

The plugin also establish a data connection between Tracealyzer for FreeRTOS and the Atmel Studio debugger, that allows for uploading trace snapshots using any Atmel-supported debugger. See also the Atmel-specific getting started guide, that includes screenshots and additional details.

When Tracealyzer is launched from Atmel Studio it reads the trace directly, if possible. The upload requires that a debug session is active and the target system is halted, e.g., on a breakpoint.

If the plugin is installed in Atmel Studio, you may also note the "Atmel Studio" menu in Tracealyzer. This contains a command named "Read Trace", that uploads the trace from target into a file, which is then opened.

Note: If you are using an Atmel SAM-ICE or another J-Link compatible device (such as a development board with an integrated J-Link debugger), you may also upload the trace using the integrated J-Link upload in Tracealyzer.

Snapshot trace using Microchip MPLAB X IDE
We provide a plugin for MPLAB X IDE supporting snapshot data upload for Tracealyzer for FreeRTOS. The plugin can be downloaded here and supports any debug probe that is supported by MPLAB. To learn more about this plugin, read this blog post.

Snapshot trace using IAR Embedded Workbench for ARM
The IAR debugger can be configured to save the trace buffer to a host-side file, either manually or automatically, using a debugger macro. Follow these steps:

  1. Create a macro file in the project directory (e.g. "save_trace_buffer.mac") with the following contents:
    __var start;
    __var end;
    
    save_trace_buffer()
    {
      start = __smessage "Memory:0x",RecorderDataPtr:%x;
      end  =  __smessage "Memory:0x",(RecorderDataPtr + 1):%x;
      __memorySave(start,end,"intel-extended", "$PROJ_DIR$\\trace.hex");
      
      return 0;
    }
    

  2. Add the macro file under Options -> Debugger -> Use Macro File(s).
  3. When in a debug session, open View -> Macros -> Debugger Macros and check that "save_trace_buffer" is included.
  4. Now, to call the macro and thereby save the trace, you have three options:
    • Manually: In the Debugger Macros view, right-click on you macro and select "Add to Quicklaunch window". Now you can save the trace by double-clicking on the blue "refresh" icon in the Quicklaunch window.
    • On a breakpoint: Place a breakpoint on a suitable location in the code. Open it in the breakpoint editor and enter your macro name (e.g., "save_trace_buffer()") in the Action -> Expression field. The trace is now saved every time that breakpoint is hit.
    • On every halt: To update the trace on every halt, rename the "save_trace_buffer" function to execUserExecutionStopped(). That is a special hook that is called every time the execution is halted. Note that this can slow down single-stepping if the buffer is large.

To enable quick access to Tracealyzer and the current trace, you may include Tracealyzer in the Tools menu, with "trace.hex" specified as parameter.

  1. Select "Configure Tools..." in the Tools menu
  2. Add a new entry "Tracealyzer"
  3. Set the path to the Tracealyzer executable (the ".exe" file).
  4. Set the argument to "$PROJ_DIR$\trace.hex"
This will open Tracealyzer and load the provided trace file.

Snapshot trace using Renesas High-performance Embedded Workshop (HEW)
In the debugger view, when stopped on a breakpoint:

  • Select "Debug" menu, "Save Memory..." (keyboard shortcut: ALT,d,a)
  • In the Save dialog
    • Format: Binary
    • Filename: "name.bin"
    • Start Address: 00000000 (example start address)
    • End Address: 0000FFFF (example end address)
    • Access size: 1
  • Press "Save" button and open "name.bin" in Tracealyzer.

Snapshot trace using Microchip MPLAB v8

  • Select "View" -> "File Registers". This shows you the memory contents.
  • Make sure "Hex" is selected in the bottom left (instead of "Symbolic"). Hex mode seems to be default.
  • Right click in the view and select "Export Table...".
  • In the dialog ("Export As"), make sure "Single Column Output" is selected (seems to be default).
  • Select start address 0x0000 and make sure the end address is beyond the RecorderData structure. The default values seems to be the whole RAM, so you probably don't need to change this.
  • Save as a .mch file and open this file in Tracealyzer.

Snapshot (and streaming) trace using STM32 ST-Link

ST-Links are not yet supported directly by Tracealyzer, but it is quite easy to reprogram ST-Links to act as J-Links. This also works for the ST-Links integrated on development boards.
In this way, you can use the built-in J-Link integration (also streaming), although the performance is not on the same level as a stand-alone J-Link. This is an official initiative from SEGGER.

See this blog post for instructions.

Otherwise you can use the "ST-Link Utility" to upload snapshot traces:

  • Start "ST-Link Utility"
  • Connect to the device and view the device memory.
  • Set the view to display the entire RAM, or at least the section containing the RecorderData structure.
  • Select "Save as" and choose binary (.bin) or Intel Hex (.hex) format.
  • Open the resulting file in Tracealyzer.

Snapshot trace using Rowley CrossStudio for ARM v3.2

  • Open a "Watch" window and enter "*RecorderDataPtr" (click in the right-side part of the "Name" field to enable editing).
  • Right click on "*RecorderDataPtr" and select "Locate Memory", which opens the Memory view on that specific address range.
  • Right-click anywhere in the Memory view table and select "Save As" and then "Intel Hex" or "Binary".
  • Open the resulting file in Tracealyzer.

Snapshot trace using Keil MDK/µVision

  • Open the "Command" window in µVision
  • Enter the command exec("SAVE \"out.hex\" RecorderDataPtr , (RecorderDataPtr + 1)");
  • Open the resulting file in Tracealyzer, located in the project directory.

You can automate this procedure and get a tool bar button for this command, as illustrated above. To do this, open the "Function Editor" (in "Debug" menu, enabled when debugging…) and add the below code:

FUNC void SavePercepioRecorderData(void) {
printf("Saving Recorder Data in out.hex!\n") ;
exec("SAVE \"out.hex\" RecorderDataPtr , (RecorderDataPtr + 1)"); }
DEFINE BUTTON "Save Recorder Data", "SavePercepioRecorderData()"

Snapshot trace using other IDEs or debug probes
Most debuggers are able to save the RAM contents to a file. Tracealyzer supports the following common formats:

  • Binary (.bin or .dump), supporting gdb, J-Link and Renesas HEW.
  • Intel Hex (.hex), supporting IAR Embedded Workbench and Atmel MemoryLogger.
  • MCH (.mch), supporting Microchip MPLAB 8.
When you store the RAM dump, you must also make sure to select the right region, i.e., start address and size. The recorder data is stored in a single data block, identified by the pointer RecorderDataPtr. This can be found can by inspecting RecorderDataPtr with your debugger (e.g., "watch"). However, you don't need to use the exact memory range, as long as the trace buffer is fully included in the specified memory range. We therefore recommend specifying a larger memory block, to get some tolerance for minor changes in between builds. This setting is saved and reused in between Tracealyzer settings.

Streaming Mode

When using the streaming mode, the data is continuously transferred to the host PC. This way, you can record as long as you have disk space on your PC. Since the amount of data produced by RTOS tracing is moderate (about 20-200 KB/s) you may in theory record traces spanning several days, or even weeks with a big hard drive.

In practice, Tracealyzer as of version 3.1 is however limited by the amount of RAM in your PC. You may open a trace file of any size, but if your PC runs out of physical memory, the resulting page swapping may result in long loading times and a less responsive GUI. Tracealyzer can however load pretty long traces without problems on standard PC, typically 5-10 million events. This is at least several minutes of trace, often 10-30 minutes. Moreover, Tracealyzer detects if a trace file seems to be too large for the available RAM and then gives a warning, asking you to confirm loading the trace.

The implementation of the streaming mode is optimized for speed, and stores an event much faster than the snapshot mode that is highly optimized for low memory usage.

Connecting and Recording

To connect to the target system in streaming mode, select "Connect to Target System..." in the File menu and click "Start Recording". This establishes a connection with the target and activates the tracing. This assumes the following:

  1. The recorder has been properly integrated in your system.
  2. The recorder has been properly configured for streaming.
  3. The system is running and vTraceEnable has been called.
Note: You may trace the system from the startup using vTraceEnable(TRC_START_AWAIT_HOST). This way, the execution is blocked until the Start command has been received from Tracealyzer.

When recording, a live graph shows the CPU load in the target system, updated in real-time.

The blue line shows the CPU usage of the application tasks, i.e., the relative number of clock cycles used. This excludes the Idle task, so 100% load means that the Idle task is not executing at all. If you enable tracing of Interrupt Service Routines (i.e., ISRs), a red line is also displayed showing the CPU usage of the traced ISRs.

Tasks and RTOS API calls are traced automatically. To trace ISRs, you need to call vTraceStoreISRBegin() and vTraceStoreISREnd() in your ISRs. See Recorder API for further details.

  • Start Recording: Sends a "Start Recording" command to the target system (i.e., to the TzCtrl task), and begins capturing trace data from the target. This button toggles between "Start Recording" and "Stop Recording".
  • Stop Recording: Sends a "Stop Recording" command to the target system, waits for the final data, and then finalizes the trace. This button toggles between "Start Recording" and "Stop Recording".
  • Settings: Opens the streaming settings.
  • Save Trace: Allows for saving the trace to a file, without opening it.
  • View Trace: Opens the trace in the main Tracealyzer application. If the trace has not been saved you'll be prompted to do so.

Note
If using Tracealyzer in parallel with a debugger IDE, the recording must not be active while launching a new debug session, otherwise Tracealyzer may get junk data when the device is programmed.

Stream ports

A stream port is a set of macros that define how the recorder shall write the trace data to the streaming interface, and how to read commands (Start/Stop) from Tracealyzer. The recorder includes several predefined stream ports, defined by the trcStreamingPort.h files found in the streamports directory. The header file contain preprocessor macros and prototypes, while the I/O functions referred to are typically implemented in trcStreamingPort.c. These predefined ports include support for streaming via SEGGER J-Link debug probes, as well as examples of USB streaming, TCP/IP streaming and for streaming to a device file system.

The stream port is selected by including the appropriate trcStreamingPort.h in your compiler's search path. The stream ports may also contain some .c files, that also needs to be included in the build.

Most stream ports use an internal RAM buffer in the trace recorder for temporary storage of the data, periodically flushed to the streaming interface by the trace control task (TzCtrl). It is however possible to write directly to the stream interface in the WRITE_DATA macro, assuming this is fast and doesn't generate any new events (e.g., by RTOS calls).

You can also create your own stream port to utilize any suitable communications interface in your system. Check the provided stream ports and this blog post for a quick introduction.

The main definitions of a stream port (in trcStreamingPort.h) are:

  • TRC_STREAM_PORT_READ_DATA - How to read commands (start/stop) from Tracealyzer via the streaming interface. Should return 0 on success. If streaming e.g. to a device file system (without a PC connection to Tracealyzer) this should be defined as 0.
  • TRC_STREAM_PORT_WRITE_DATA - How the recorder should write trace data to the streaming interface. Should return 0 on success.
There are other TRC_STREAM_PORT_ definitions in trcRecorder.h that can be overridden by redefining them in trcStreamingPort.h, but this is rarely required. One exception is perhaps TRC_STREAM_PORT_USE_INTERNAL_BUFFER, that should be defined as 0 if the stream port is writing directly to the streaming interface instead of using the internal RAM buffer.

SEGGER J-Link Streaming

The J-Link stream port works on all ARM Cortex-M MCUs and Renesas RX MCUs. It also works on many ARM Cortex-R and ARM Cortex-A processors, see this application note from SEGGER. With a sufficient RAM buffer size, this allows for streaming the trace data at speeds upwards 700 KB/s on a standard J-Link (Version 9) and upwards 2 MB/s on the high-end models. It works even on the J-Link On Board (OB), found integrated on many development boards, although these have lower performance (around 100 KB/s). RTOS tracing usually generates about 20-200 KB/s, so systems with high event rate may require a proper stand-alone J-Link probe.

The SEGGER J-Link streaming uses a proprietary SEGGER feature called RTT, Real-Time Transfer. It relies on RAM buffers in the target system, which the J-Link reads in the background without disturbing the execution. The Tracealyzer recorder uses two RTT buffers, one control channel for receiving commands from Tracealyzer and a data channel for streaming the trace. All source code required for SEGGER RTT is included in the J-Link stream port directory and relevant settings are found in trcStreamingPort.h.

  • TRC_CFG_RTT_BUFFER_SIZE_UP: The size of the target->host RTT buffer, used for streaming the trace data. Default 5000 bytes.
  • TRC_CFG_RTT_BUFFER_SIZE_DOWN: The size of the host->target RTT buffer, used for start/stop commands. Default 32 bytes.
  • TRC_CFG_RTT_UP_BUFFER_INDEX: The RTT buffer index to use for the UP buffer target->host). Must match the corresponding setting in Tracealyzer (File -> Settings). Default 1.
  • TRC_CFG_RTT_DOWN_BUFFER_INDEX: The RTT buffer index to use for the UP buffer target->host). Must match the corresponding setting in Tracealyzer (File -> Settings). Default 1.
  • TRC_CFG_RTT_MODE: What to do if the streaming buffer becomes full...
    • SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL: The recorder blocks (stalls) the system until the J-Link has made room in the buffer. This is default, since it ensures correct data transfer.
    • SEGGER_RTT_MODE_NO_BLOCK_SKIP: Events are skipped if the RTT buffer is full. If this should occur, the number of dropped events is displayed in Tracealyzer while receiving the trace. Moreover, Tracealyzer also highlights the intervals with incomplete data using a light red background color. We recommend trying this mode as well, to see that the RTT buffer is large enough.

Note that the UP buffer (target->host) is quite large by default (5000 bytes). If you have a stand-alone J-Link probe, you can typically reduce this buffer size to 1-2 KB. If using an integrated J-Link On-board, 5-10 KB can be necessary to maximize throughput - the maximum seem to be about 140 KB/s in this case.

If there would be recorder blocking or dropped trace events, try the following:

  • Verify the J-Link Speed in the Settings dialog of Tracealyzer. Default is 4 MHz, but can be increased a lot depending on your J-Link model. In many cases speeds of 10-20 MHz is possible, but according to SEGGER there is a small risk of getting poor signal quality (i.e., problems) if going above 6 MHz, depending on the development board. But don't worry, nothing will break and any corrupted data will result in obvious error messages when processing the trace. And if you set the J-Link Speed higher than the maximum supported speed, the J-Link driver will instead use the highest speed supported.
  • Increase the trace buffer size (TRC_CFG_RTT_BUFFER_SIZE_UP in trcStreamingPort.h).
  • Reduce the amount of data produced, e.g., by excluding frequent User Events.
  • If using an integrated J-Link OB on your development board, consider upgrading to a stand-alone J-Link probe (which are several times faster).

Before making your first trace recording, please install the latest J-Link drivers and make sure all listed development tools are updated, including Tracealyzer. Also make sure the firmware of your J-Link debug probe is updated correctly, if the J-Link drivers would propose this.

Also remember to check J-Link Settings and Streaming Trace Settings, found under File -> Settings... in Tracealyzer.

  • Debugger: Select the connected J-Link debug probe to use.
  • J-Link Speed: The debugger clock frequency for the JTAG/SWD interface (KHz).
  • Debugger Interface:
    • JTAG: Configures the debug probe for JTAG. This is the default setting of J-Link debug probes.
    • SWD: Configures the debug probe for SWD.
    • Default (don't change): Tracealyzer does not change the JTAG/SWD setting of the debug probe.
  • Target Device: Opens a SEGGER J-Link dialog where you can select the device (processor). This is necessary in order for SEGGER RTT to connect automatically.

  • Target Connection: Should be "SEGGER RTT".
  • RTT Up Buffer Index: The RTT buffer to use for the trace data channel (target -> host). Default is 1 and must match the setting in trcStreamingPort.h.
  • RTT Down Buffer Index: The RTT buffer to use for the control channel (host -> target). Default is 1 and must match the setting in trcStreamingPort.h.

Note
All tools using the J-Link driver (i.e., Tracealyzer and the debugger IDE) must use the same setting for Debugger Interface (JTAG/SWD). So if you sometimes start a Tracealyzer session without an active debug session in your IDE, it is recommended to specify this setting explicitly, using the same setting as your debugger IDE. This to avoid conflicts in the J-Link drivers.

J-Link Connection Issues

The first time you try to connect over SEGGER RTT you may be presented with a dialog where you should specify your device. This is required for Tracealyzer to locate the RAM address range(s) and find the RTT data structure. You can also access this dialog via File -> Settings... -> J-Link Settings -> Select Device....

When using the Debugger Interface setting "Default (don't change)" and the J-Link has just been powered up, the following issue may occur when starting a parallel debug session. If opening the Tracealyzer Target Connection before the debugger session is launched, the Tracealyzer J-Link connection will use the default mode, JTAG. When then launching a debug session using SWD mode, the J-Link driver will get conflicting settings. In that case, the J-Link driver will ask what to do, as illustrated below.

In this case, click YES to make sure that the debugger IDE works as expected, and after that close and re-open the Target Connection dialog to establish a new connection in SWD mode.

See also the Troubleshooting section.

TCP/IP Streaming

To stream the trace data over a network connection, i.e., TCP/IP transfer, select TCPIP stream port. The existing example is for lwIP, so if using another TCP/IP stack you need to modify this accordingly.

The TCP/IP stream port should act as a server, to which Tracealyzer connects as a client. You need to decide on an available TCP port and specify this in the stream port as well as in the Tracealyzer TCP/IP settings, shown below.

This stream port uses the recorder's internal RAM buffer. This is necessary since a TCP/IP stack is quite complex and should not be called directly from the kernel instrumentation code. That may even cause infinite recursive calls, if the TCP/IP stack generates new RTOS events when sending the data.

In Tracealyzer, check the Streaming Trace Settings, found under File -> Settings... in Tracealyzer.

  • Target Connection: Should be "TCP".
  • TCP/IP Address: The IP address of your target system.
  • Port: The port used by target-side TCP/IP socket that you have created for the recorder.

USB Streaming

The USB_CDC stream port provides an example of how to stream the trace data over USB connection, using a CDC class endpoint. The example provided is for STM32, with USB drivers generated by STM32Cube. If using another MCU, you need to modify the stream port accordingly.

If your system is already using the USB controller for other purposes, you may consider setting up a Composite Device that includes also a CDC endpoint. The performance of USB (even USB 2.0) is many several times more than required for RTOS trace streaming.

The USB stream port uses the recorder's internal RAM buffer. This may not be necessary, depending on the internal buffers in the USB stack. If you need to reduce the RAM usage, you can try writing directly in the WRITE_DATA macro, like in the J-Link stream port.

In Tracealyzer, check the Streaming Trace Settings, found under File -> Settings... in Tracealyzer. Select "SerialPort" and specify the COM port (e.g. "COM5") created for this connection. The other settings are not relevant for USB CDC connections. They allow for streaming over a classic UART-based serial connection. This however requires a fast UART and an appropriate stream port.

  • Target Connection: Should be "SerialPort".
  • Device: The COM port created for your USB CDC connection (e.g. "COM5").
  • Others: Not applicable for USB CDC connections.

Custom Streaming

You may configure the streaming to use any interface you have available between target and the host PC, assuming it provides sufficient throughput. The data rate resulting from RTOS tracing depends on your application and RTOS configuration (like tick rate), but typically you need something like 20-200 KB/s. Read more about custom trace streaming in this blog post, RTOS Tracing, your way. For instance, if you have a flash file system on your target system (e.g., a memory card) you can stream to a local file.

To setup a custom transfer method, copy one of the stream port directories and use that as example. The main file is trcStreamingPort.h.

Typically, you only need to define the following macros, together with prototypes for the functions they use:

  • TRC_STREAM_PORT_READ_DATA - How to read commands (start/stop) from Tracealyzer via the streaming interface. Should return 0 on success. If streaming e.g. to a device file system (without a PC connection to Tracealyzer) this should be defined as 0.
  • TRC_STREAM_PORT_WRITE_DATA - How the recorder should write trace data to the streaming interface. Should return 0 on success.
There are other TRC_STREAM_PORT_ definitions in trcRecorder.h that can be overridden by redefining them in trcStreamingPort.h, but this is rarely required. One exception is perhaps TRC_STREAM_PORT_USE_INTERNAL_BUFFER, that should be defined as 0 if the stream port is writing directly to the streaming interface instead of using the internal RAM buffer.

Most stream ports should use the internal RAM buffer in the recorder, in the same way as the TCP/IP port and the USB CDC port. In this case, trcStreamingRecorder.c writes the data to the internal buffer, which is periodically flushed by the trace control task (TzCtrl) to the streaming interface. Note that the buffer used a multi-page structure. The data is not sent until a page becomes full. You may want to tweak the settings for trace control task (periodicity and priority) and also the buffer layout (bytes per page and number of pages). These settings are found in trcStreamingConfig.h.

It is however possible to omit the internal buffer and instead write directly to the transfer channel, but only if (1) the streaming channel is dedicated for this purpose, and (2) the writes do not generate any kernel events, e.g., from Mutex or Semaphore operations.

Tracealyzer can receive the data in three ways, using a TCP connection, a COM port, or by opening a file. If there is no direct connection between Tracealyzer and the target system, you can start the tracing programmatically from target using vTraceEnable(TRC_START), which assumes that the trace interface is ready to receive the data. If using another target connection, you may consider creating a proxy tool that forwards the data to a TCP or COM port, or writes it to a host-side file. The trace data is a binary format and should be stored to a binary file exactly as received. Name the output file ".psf" to make it easier to find in Tracealyzer.

File Structure

The recorder consists of the below files and directories. Some code is specific for the snapshot or streaming modes, but we recommend including all these source files in your build. Any code that is not used by the selected recording mode, is automatically excluded by the preprocessor when compiling the recorder. Check the Recorder Integration section for a step-by-step guide of how to integrate the recorder in your system.

/config trcConfig.h The main configuration of the recorder library.
/config trcSnapshotConfig.h Specific configuration for the snapshot mode.
/config trcStreamingConfig.h Specific configuration for the streaming mode.
/include trcRecorder.h The public API of the recorder. Needs to be included from FreeRTOSConfig.h, as described in the integration guide.
/include trcHardwarePort.h All hardware dependencies. Contains several predefined hardware ports, including ARM Cortex-M, PIC32, Renesas RX etc.
/include trcKernelPort.h FreeRTOS-specific definitions, most notably the trace hook definitions.
/include trcPortDefines.h Various constants for the configuration files.
/streamports JLink_RTT A stream port for SEGGER J-Link debug probes. See trcStreamingPort.h for relevant settings.
/streamports TCPIP A stream port for TCP/IP network connections. The example provided is for lwIP. Defined by trcStreamingPort.h.
/streamports USB_CDC A stream port for USB CDC connections. The example provided is for STM32, with USB drivers generated by STM32Cube. Defined by trcStreamingPort.h.
/ trcSnapshotRecorder.c Implementation of snapshot recording mode.
/ trcStreamingRecorder.c Implementation of streaming recording mode.
/ trcKernelPort.c FreeRTOS-specific functions and definitions.

Integrating the Recorder

Follow the steps below to integrate the trace recorder in an existing project, using snapshot mode with default settings. To use streaming mode, some additional steps are needed.

Step 1. Make sure your project uses FreeRTOS version 7.3 or later. If using an older version of FreeRTOS, upgrading is usually just a drop-in replacement, since backwards-compatible.

Step 2. Download the trace recorder library and extract to a suitable location in your code repository, e.g. next to FreeRTOS. The recorder code is also found uncompressed in the Tracealyzer installation directory, with a link in the Help menu.

Step 3. Add all three .c files to your project. The files are stripped from any unused code by the preprocessor, based on the recorder configuration.
Step 4. Copy all config headers to a suitable location your project structure, e.g., next to FreeRTOSConfig.h. It is not advised to share these between multiple projects.
Step 5. Add the include directory to your compilers "include paths" to ensure the header files are found.

Step 6. Configure TRC_CFG_HARDWARE_PORT and TRC_CFG_FREERTOS_VERSION in trcConfig.h to reflect your system. Also check the settings in trcSnapshotConfig.h, especially TRC_CFG_NTASK, TRC_CFG_NQUEUE etc. Read more in Configuration and in Tuning the Snapshot Configuration.

Step 7. In your FreeRTOSConfig.h, ensure that you have the following setting:

#define configUSE_TRACE_FACILITY 1

This is the "master switch" for the trace recorder. If you set this to 0, the trace recorder is completely disabled and excluded from the build.

Step 8. Insert the following in FreeRTOSConfig.h, in the end.

/* Integrates the Tracealyzer recorder with FreeRTOS */
#if ( configUSE_TRACE_FACILITY == 1 )
#include "trcRecorder.h"
#endif

Note: Since FreeRTOSConfig.h is also included from some FreeRTOS assembly files, depending on your IDEs you may need to use a conditional include, like in the examples below.

/* IAR Embedded Workbench */
#ifdef __ICCARM__
#if ( configUSE_TRACE_FACILITY == 1 )
#include "trcRecorder.h"
#endif
#endif

/* Microchip MPLAB X IDE */
#ifndef __LANGUAGE_ASSEMBLY
#if ( configUSE_TRACE_FACILITY == 1 )
#include "trcRecorder.h"
#endif
#endif

Step 9. Call vTraceEnable(TRC_START) in your main function to initialize and start the recording. This must be after the initial hardware setup, but before any RTOS objects (tasks etc.) have been created.

Note
For ARM Cortex-M devices, the recorder needs the ARM's CMSIS library. To ensure this is accessible from the recorder code, we recommend including your processor's header file (e.g., "lpc17xx.h", "stm32f4xx.h", "board.h", etc.) from your trcConfig.h.

If you would run into problems, please refer to the Troubleshooting section.

Setting up Streaming

Assuming you have followed the recommended steps for basic integration, the following additional steps are needed to enable streaming.

  1. In trcConfig.h, set TRC_CFG_RECORDER_MODE to TRC_RECORDER_MODE_STREAMING.
  2. Decide on which stream port to use, i.e., how to transfer the trace data from target to host.
  3. Specify the stream port to use. This is done by updating your compiler's include search paths to include the directory of your trcStreamingPort.h (e.g, "/streamports/Jlink_RTT/include").
  4. If the selected stream port contain some .c files, those should also be added to your project.
  5. In your vTraceEnable call, for streaming you should normally use TRC_INIT or TRC_START_AWAIT_HOST. See the vTraceEnable documentation for details.

If you would run into problems, please refer to the Troubleshooting section.

Configuration

The trace recorder library have several settings that allow for tuning the recorder setup. The main settings are found in trcConfig.h.

  • TRC_CFG_RECORDER_MODE - Selects the recording mode (snapshot or streaming mode).
  • TRC_CFG_HARDWARE_PORT - Selects the hardware port, i.e., the hardware-specific definitions in trcHardwarePort.h.
  • TRC_CFG_FREERTOS_VERSION - Must match your version of FreeRTOS.
  • TRC_CFG_RECORDER_BUFFER_ALLOCATION - Select how the recorder's data structure are allocated:
    • TRC_RECORDER_BUFFER_ALLOCATION_STATIC: Static allocation in compile time, inside the recorder library.
    • TRC_RECORDER_BUFFER_ALLOCATION_DYNAMIC: Dynamic allocation on the heap, inside vTraceEnable. May fail if there is not enough heap space.
    • TRC_RECORDER_BUFFER_ALLOCATION_CUSTOM: In the custom allocation mode, you make the necessary allocation yourself and then register the buffer using vTraceSetRecorderDataBuffer, before calling vTraceEnable. If combined with linker directives, you can place the recorder data in a fixed memory location of your choice.

trcSnapshotConfig.h contains specific settings for the snapshot mode. Some examples are:

  • TRC_CFG_SNAPSHOT_MODE - If to stop recording when the buffer becomes full, or use ring-buffer mode.
  • TRC_CFG_EVENT_BUFFER_SIZE - The size of the event buffer, expressed as the number of 4-byte records.
  • TRC_CFG_NTASK, TRC_CFG_NQUEUE, etc. - The maximum number of simultaneously active objects, i.e., the number of created objects minus the number of deleted/closed objects, of each type.
  • TRC_CFG_SYMBOL_TABLE_SIZE - The size of the Symbol Table, used for storing strings in an efficient manner, e.g., for User Events channel names and the names of deleted/closed objects.
  • USE_SEPARATE_USER_EVENT_BUFFER – Allows longer history of User Events by storing them in the separate User Event Buffer (UB).

trcStreamingConfig.h contains specific settings for the streaming mode. Some examples are:

  • TRC_CFG_SYMBOL_TABLE_SLOTS - The maximum number of symbols names that can be stored, including names for tasks and traced ISRs, other named kernel objects and user event channels.
  • TRC_CFG_SYMBOL_MAX_LENGTH - The maximum length of symbol names.
  • TRC_CFG_OBJECT_DATA_SLOTS - The maximum number of "object data entries", used to store task priorities. Must be sufficient for all simultaneously active tasks, i.e., the number of created tasks minus the number of deleted/closed tasks.
  • TRC_CFG_CTRL_TASK_PRIORITY - The scheduling priority of the TzCtrl task, that receive start/stop commands from Tracealyzer. Some stream ports also rely on this task to transmit the data from the internal buffer to the stream interface (most except for the J-Link port). If your system is highly loaded, you may need to increase the priority of this task to ensure it can execute periodically.
  • TRC_CFG_PAGED_EVENT_BUFFER_PAGE_COUNT - The number of pages used by the internal trace buffer. This may need to be increased if there are missed events.
    (Not used by the J-Link stream port, see below).
  • TRC_CFG_PAGED_EVENT_BUFFER_PAGE_SIZE - Specifies the size of each page in the internal trace buffer. This can be tuned to match any internal low-level buffers used by the streaming interface, like the Ethernet MTU (Maximum Transmission Unit).
    (Not used by the J-Link stream port, see below).

Note
If using the J-Link stream port, the "page count" and "page size" settings are not relevant. Important settings for the RTT buffers are instead found in trcStreamingPort.h.

Tuning the Snapshot Configuration

In snapshot mode, there are several settings that affects the RAM usage, e.g., TRC_CFG_EVENT_BUFFER_SIZE, TRC_CFG_SYMBOL_TABLE_SIZE, TRC_CFG_NTASK, TRC_CFG_NQUEUE and a few more. TRC_CFG_EVENT_BUFFER_SIZE is usually the main factor, as it controls the size of the main event buffer. This is by default 4000 bytes. The usage of this buffer can be reduced in several ways described in the Recorder API e.g. by excluding certain objects or by only including task-switches.

TRC_CFG_SYMBOL_TABLE_SIZE is typically the second largest factor, by default 800 bytes. This controls the size of the symbol table, allowing efficient storage of names, format strings, etc.
TRC_CFG_NTASK, TRC_CFG_NQUEUE and a few similar ones controls the size of the object table, that is used for keeping track of the active RTOS objects.

TRC_CFG_SYMBOL_TABLE_SIZE and TRC_CFG_NTASK etc. must not be too small, then the recorder reports an error and stops recording. It is therefore advised to start with relatively large values. But too large values in these settings will waste precious RAM. Tracealyzer can however report the resource usage from a trace session, allowing you to tune these settings and thereby optimize the RAM usage. Assuming you have a snapshot trace open in Tracealyzer, this is found under View -> Trace Details.

Note the entries under Object Table - they show the number of slots allocated, based on TRC_CFG_NTASK, TRC_CFG_NQUEUE, etc., and also the actual usage of such slots. Each Object Table slot holds meta data about an active RTOS object, like a task or a queue, and require about 20 bytes each, depending on the maximum name lengths specified in TRC_CFG_NAME_LEN_TASK etc. (this can also be tuned, to further reduce RAM usage). In this example, TRC_CFG_NQUEUE was set to 10 but only 3 queues was created by the system, meaning that TRC_CFG_NQUEUE can be reduced to 3. If optimizing all these settings, we can thereby eliminate 34 slots and free up about 680 bytes of RAM.

Troubleshooting

If you get build errors, make sure that TRC_CFG_FREERTOS_VERSION is specified correctly in trcConfig.h.

Runtime errors in the recorder are often captured by the ASSERT macros. They call prvTraceError or prvTraceWarning if an internal error is detected in the recorder code, e.g., due to incorrect parameters or configuration. This attempts to save the error message in the trace and then stops the recorder. The error message is then presented when opening the trace.

But if no error message is presented, or you like to see the context of the error, it is advised to put a breakpoint inside prvTraceError and/or prvTraceWarning. Note that this function has two implementations, one in trcSnapshotRecorder.c and another in trcStreamingRecorder.c.

You may also call xTraceGetLastError() in order to obtain any error messages in your target code. This returns NULL if no error messages are available.

In case you have problems connecting to the recorder in streaming mode, or it reports lost events, make sure to check the following.

  • Double-check the streaming settings in Tracealyzer (File -> Settings). Also check that the stream port settings match where relevant.
  • If using the SEGGER J-Link streaming, make sure to install the latest J-Link drivers from SEGGER. Make sure the installer updates both Tracealyzer and your IDE, and if the J-Link driver wants to update the J-Link firmware, it is advised to allow that.
  • Put a breakpoint in prvIsValidCommand() in trcStreamingRecorder.c. Then press "Start Recording" in Tracealyzer, which will connect and send two commands (Stop, Start). If the breakpoint is not hit, no data is received from the connection. If this function would return 0, the received data is not a valid command and probably junk data.
  • Is the trace control task (TzCtrl) executing? Put a breakpoint in the start of this task to verify this. Note that is normally started automatically from vTraceEnable.
  • Is your streaming interface sufficiently fast to handle the data produced? If not, there may be lost events or blocking in the recorder, depending on your recorder settings. If blocking mode is used, this may affect the timing of your system, but ensures that data is not lost. In case of data loss, Tracealyzer will show a counter for "dropped events" in the "Connect to target system" window.
    • Make sure the trace buffer is sufficiently large. If using J-Link streaming, you find the buffer size setting in trcStreamingPort.h, otherwise in trcStreamingConfig.h.
    • In most stream ports except J-Link, the trace control task (TzCtrl) performs the writing to the streaming interface. In such cases, try reducing TRC_CFG_CTRL_TASK_DELAY to make execute more frequently. Especially if you don't have enough RAM for a larger buffer.
    • If your system is highly loaded, you might need to increase the priority of the trace control task to make sure it runs periodically. This is done by increasing TRC_CFG_CTRL_TASK_PRIORITY. This is relevant for stream ports that use the internal paged buffer, e.g., TCP/IP and UDB CDC, but not J-Link.
  • Check if you get any warnings related to TRC_STREAM_PORT_WRITE_DATA or TRC_STREAM_PORT_READ_DATA. These define the I/O functions used by the stream port to read commands and write trace data, and should return zero (0) on success. Any other return values result in warnings (e.g. "TRC_STREAM_PORT_WRITE_DATA returned error (!= 0)"). This can be checked by calling xTraceGetLastError() (returns a string, or NULL if no error), or by placing a breakpoint in prvTraceWarning().

What if the trace can be recorded, loaded into Tracealyzer but doesn't contain any task-switches or kernel calls? This typically means that the trace macro definitions are not included into the kernel, so the recorder is not notified of the events. Make sure you have included trcRecorder.h from your FreeRTOSConfig.h, as described in the integration guide.

And finally, don't hesitate to contact support@percepio.com.

Recorder API

This section describes the public API of the trace recorder library, used in both snapshot and streaming mode. The API is found in trcRecorder.h.

Note that the API has two parts, the Common API (just below) and the Extended Snapshot API, with functions that are only relevant for advanced usage of the snapshot mode.

If you would run into problems, please refer to the Troubleshooting section.

Common API

vTraceEnable

void vTraceEnable(int startOption)

Initializes and optionally starts the trace, depending on the start option.

When the trace recorder is included, you must call vTraceEnable in your startup code, before any RTOS calls are made (including "create" calls) but after the initial hardware setup (of clocks etc.).

The parameter startOption should be one of:

  • TRC_START:

    Starts the tracing directly. The typical option in snapshot mode.

    vTraceEnable(TRC_START) can be called in two ways - either directly in the startup, or from a task after the kernel has started. However, the recorder must always be initialized before the RTOS is started, so if not starting the trace directly, you should call vTraceEnable(TRC_INIT) in the startup code.

    In streaming mode, this option can be used if the recorder is controlled from target (no Tracealyzer connection), e.g., if streaming to a device file system.

  • TRC_START_AWAIT_HOST:

    Waits for a Start command from Tracealyzer ("Start Recording" button) and then begins tracing from this point. This way, you don't miss the initial events. This is the typical option for streaming mode.

    This is not supported in snapshot mode, but for streaming mode only. Intentionally blocking!

    This option is only intended to be used when calling vTraceEnable from the startup code, i.e., before the RTOS has been started.

  • TRC_INIT:

    Initializes the trace recorder, but does not start the tracing.

    You don't need to call vTraceEnable(TRC_INIT) if you call vTraceEnable(TRC_START) or vTraceEnable(TRC_START_AWAIT_HOST) in the startup code, as these option also initializes the recorder if needed. However, if you prefer to start the recording at a later point (after the RTOS has started) you need to call vTraceEnable(TRC_INIT) in the startup code.

    If using streaming mode, you can start the tracing from Tracealyzer ("Start Recording" button) as long as vTraceEnable(TRC_INIT) or vTraceEnable(TRC_START_AWAIT_HOST) has been called.

Examples:

Snapshot mode Streaming mode
myBoardInit();
...
/* From startup */ 
vTraceEnable(TRC_START);
...
/* RTOS scheduler starts */
vTaskStartScheduler();
myBoardInit();
...
/* From startup - blocks until start from host */
vTraceEnable(TRC_START_AWAIT_HOST); 
...
/* RTOS scheduler starts */
vTaskStartScheduler();
myBoardInit();
...
/* Init only, trace starts later...*/
vTraceEnable(TRC_INIT);
...
/* RTOS scheduler starts */
vTaskStartScheduler();
...
/* In a task or ISR */
vTraceEnable(TRC_START); 
myBoardInit();
...
/* Init only, trace starts later...*/
vTraceEnable(TRC_INIT);
...
/* RTOS scheduler starts */
vTaskStartScheduler();
...
/* "Trace Start" command from Tracealyzer 
is received by TzCtrl task. */

The tracing does not start if the recorder has detected an error, e.g. related to the configuration. An error message is then presented when opening the trace. To see the context of the error, put a breakpoint inside prvTraceError. Note that prvTraceError has two alternative implementations, one for snapshot mode and another for streaming.

vTraceStop

void vTraceStop(void)

Stops the recording. Mainly intended for snapshot mode or if streaming to device storage or a USB drive, without host control.

vTracePrintF

void vTracePrintF(traceString chn, const char* fmt, ...)

Generates "User Events", formatted text and data, with similar interface as a classic "printf" call. User Events can be used for very efficient logging from your application code. It is very fast since the actual string formatting is done on the host side, when the trace is displayed. The execution time is just some microseconds on a 32-bit MCU.

User Events are shown as yellow labels in the main trace view, if enabled in the View Filter.

User Events can be displayed separately in the User Event Log view and also in the more generic and advanced Event Log view.

User Events with data arguments can also be plotted in the User Event Signal Plot. This way, you can visualizing any kind of data that your code can access, such as software states, hardware states, system inputs and outputs.

You may group User Events into named channels, for filtering in Tracealyzer. The yellow User Event labels show the logged string, preceded by the channel name within brackets ("[MyChannel] Hello World!").

The User Event Channels are shown in the View Filter, which makes it easy to select what User Events you wish to display. User Event Channels are created using xTraceRegisterString().

Example:

traceString adc_uechannel = xTraceRegisterString("ADC User Events");
...
vTracePrintF(adc_uechannel,	"ADC channel %d: %d volts", ch, adc_reading);

The following format specifiers are supported in both modes:
%d - signed integer.
%u - unsigned integer.
%X - hexadecimal, uppercase.
%x - hexadecimal, lowercase.
%s - string (see comment below)

For integer formats (%d, %u, %x, %X) you may also use width and padding specifiers. For example, assuming -42 as data argument, two examples are:

"%05d" -> "-0042"
"%5d" -> " -42"

String arguments are supported in both snapshot and streaming, but in streaming mode you need to use xTraceRegisterString and use the returned traceString as the argument. In snapshot mode you simply provide a char* as argument.

Snapshot:

vTracePrintF(myChn, "my string: %s", str);
Streaming:
vTracePrintF(myChn, "my string: %s", xTraceRegisterString(str));

In snapshot mode you can specify 8-bit or 16-bit arguments to reduce RAM usage.

vTracePrintF(myChn, "%hd", val); // 16 bit (h) signed integer (d)
vTracePrintF(myChn, "%bu", val); // 8 bit (b) unsigned integer (u)

In streaming mode all data arguments are assumed to be 32 bit wide. Width specifiers (e.g. %hd) are accepted but ignored, so e.g., %hd is treated like %d.

The maximum event size also differs between the modes. In streaming this is limited by a maximum payload size of 52 bytes, including format string and data arguments. So if using one data argument, the format string is limited to 48 byte, etc. If this is exceeded, the format string is truncated and you get a warning in Tracealyzer.
In snapshot mode you are limited to maximum 15 arguments, that must not exceed 32 bytes in total (not counting the format string). If exceeded, the recorder logs an internal error (displayed when opening the trace) and stops recording.

vTracePrint

void vTracePrint(traceString chn, const char* str)

A faster version of vTracePrintF for logging strings without data arguments.

Example:

traceString chn = xTraceRegisterString("MyChannel");
...
vTracePrint(chn, "Hello World!");

xTraceRegisterString

traceString xTraceRegisterString(const char* name)

Storing strings generally require that they are assigned a traceString handle, provided by this function. Duplicates are avoided by reusing any previous handle, if there already is an identical string saved.

Example:
traceString chn = xTraceRegisterString("MyChannel");
...
vTracePrint(chn, "Hello World!");
vTracePrintF(chn, "Value: %d", myValue);

vTraceSet...Name

void vTraceSetQueueName(void* object, const char* name)
void vTraceSetSemaphoreName(void* object, const char* name)
void vTraceSetMutexName(void* object, const char* name)

Functions for setting names of kernel objects, for display in Tracealyzer.

Parameter object: pointer to the kernel object that shall be named (e.g., a queue).
Parameter name: the name to display in Tracealyzer.

xTraceSetISRProperties

traceHandle xTraceSetISRProperties(const char* name, uint8_t priority)

Stores a name and priority level for an Interrupt Service Routine, to allow for better visualization. Returns a traceHandle used by vTraceStoreISRBegin.

Example:

#define PRIO_ISR_TIMER1 3 /* the hardware priority level */
...
traceHandle Timer1Handle = xTraceSetISRProperties("ISRTimer1", PRIO_ISR_TIMER1);
...
void ISR_handler()
{
	vTraceStoreISRBegin(Timer1Handle);
	...
	vTraceStoreISREnd(0);
}

vTraceStoreISRBegin

void vTraceStoreISRBegin(traceHandle handle);

Registers the beginning of an Interrupt Service Routine, using a traceHandle provided by xTraceSetISRProperties.

Example:

#define PRIO_ISR_TIMER1 3 /* the hardware priority level */
...
traceHandle Timer1Handle = xTraceSetISRProperties("ISRTimer1", PRIO_ISR_TIMER1);
...
void ISR_handler()
{
	vTraceStoreISRBegin(Timer1Handle);
	...
	vTraceStoreISREnd(0);
}

vTraceStoreISREnd

void vTraceStoreISREnd(int isTaskSwitchRequired)

Registers the end of an Interrupt Service Routine.

The parameter pendingISR indicates if the interrupt has caused a task-switch (= 1), e.g., by signaling a semaphore. Otherwise (= 0) the interrupt is assumed to return to the previous context.

Example:

#define PRIO_ISR_TIMER1 3 /* the hardware priority level */
...
traceHandle Timer1Handle = xTraceSetISRProperties("ISRTimer1", PRIO_ISR_TIMER1);
...
void ISR_handler()
{
	signed BaseType_t xHigherPriorityTaskWoken;
	vTraceStoreISRBegin(Timer1Handle);
	/* Wakes up a pending task, causing a task-switch */
	xSemaphoreGiveFromISR(xSemaphore, &xHigherPriorityTaskWoken);
	vTraceStoreISREnd(xHigherPriorityTaskWoken);
}

vTraceInstanceFinishedNow

void vTraceInstanceFinishedNow(void)

Creates an event that ends the current task instance at this very instant.
This makes the viewer to splits the current fragment at this point and begin a new actor instance, even if no task-switch has occurred.

vTraceInstanceFinishedNext

void vTraceInstanceFinishedNext(void)

Marks the current "task instance" as finished on the next kernel call.

If that kernel call is blocking, the instance ends after the blocking event and the corresponding return event is then the start of the next instance.
If the kernel call is not blocking, the viewer instead splits the current fragment right before the kernel call, which makes this call the first event of the next instance.

vTraceSetRecorderDataBuffer

void vTraceSetRecorderDataBuffer(void* pRecorderData)

The recorder supports three modes of allocating the trace data structure, static, dynamic and custom allocation mode. In custom allocation mode (TRC_RECORDER_BUFFER_ALLOCATION_CUSTOM) you are expected to declare and allocate the buffer yourself, intended for maximum control. If you combine this with linker directives, you can place the recorder data in a specific memory location. Then use vTraceSetRecorderDataBuffer to register the buffer in the recorder, before calling vTraceEnable.

To declare a trace buffer, use the macro TRC_ALLOC_CUSTOM_BUFFER. This is portable and independent of recording mode. Moreover, it is ignored if you don't use custom allocation mode.

Example:

/* Allocate, typically in global context */
TRC_ALLOC_CUSTOM_BUFFER(myTraceBuffer)

/* Register the buffer, before vTraceEnable */
vTraceSetRecorderDataBuffer(&myTraceBuffer);
	
vTraceEnable(TRC_INIT);

xTraceGetLastError

char* xTraceGetLastError(void)

Returns the first registered error in the recorder, if any, as a string. The return value is NULL if no errors have been registered. The errors reported here are typically from internal "assert" statements within the recorder code, that checks the configuration and inputs.

Any error or warning is also shown in Tracealyzer when loading the trace, assuming a trace could be created. In streaming mode, this however requires User Events to be enabled (see trcConfig.h).

To get more information about the context of the error, put a breakpoint in prvTraceError and/or prvTraceWarning. Then start a debug session and reproduce the problem. Then check the call stack in your debugger to see what recorder operation that resulted in the error.

vTraceClearError

void vTraceClearError(void)

Clears any internal errors from the recorder (see also xTraceGetLastError).

vTraceSetFrequency

void vTraceSetFrequency(uint32_t frequency)

Registers the clock rate of the timestamping time source. Allows for overriding the default value (TRC_HWTC_FREQ_HZ) in case this would be incorrect for your setup.

Note: In snapshot mode, the frequency value is divided by the TRC_HWTC_DIVISOR, and may therefore seem lower than expected in the Trace Details view.

xTraceIsRecordingEnabled

int xTraceIsRecordingEnabled(void)

Returns true (1) if the recorder is enabled (i.e. is recording), otherwise 0.

vTraceSetFilterMask

void vTraceSetFilterMask(uint16_t filterMask)

Sets the Filter Mask, used for filtering the events based on the object they refer to, such as tasks, queues, semaphores and mutexes. This can be used to reduce the trace data rate, i.e., if your streaming interface is a bottleneck or if you want longer snapshot traces without increasing the buffer size.

The parameter filterMask should include one or several Filter Group IDs (FilterGroup0 .. FilterGroup15). Multiple filter groups can be included using a bitwise OR expression, for example: FilterGroup0 | FilterGroup3 | FilterGroup6.

Every kernel object is assigned to a specific filter group. By default, all kernel object are assigned to FilterGroup0, but this can be changed using vTraceSetFilterGroup.

An event is included if the following bitwise AND condition is true:

Object.FilterGroup & FilterMask != 0
This means that if calling vTraceSetFilterMask(FilterGroup0 | FilterGroup2), we include events from filter groups 0 and 2, but exclude all events from the other filter groups.

Example:

// Assign tasks T1 to FilterGroup0 (default)
[Create Task T1]  

// Assign Q1 and Q2 to FilterGroup1
vTraceSetFilterGroup(FilterGroup1);
[Create Queue Q1] 
[Create Queue Q2]

// Assigns Q3 to FilterGroup2
vTraceSetFilterGroup(FilterGroup2);
[Create Queue Q3]

// Only include FilterGroup0 and FilterGroup2, exclude FilterGroup1 (Q1 and Q2) from the trace
vTraceSetFilterMask( FilterGroup0 | FilterGroup2 );

// Assign the default RTOS objects (e.g. Idle task) to FilterGroup0
vTraceSetFilterGroup(FilterGroup0);
[Start the RTOS scheduler]
Note: There are two kinds of filters in the recorder. While vTraceSetFilterMask and vTraceSetFilterGroup provides filtering on object-level, it is also possible to filter by event type/category, regardless of object. See the TRC_CFG_INCLUDE... settings in trcConfig.h.

vTraceSetFilterGroup

void vTraceSetFilterGroup(uint16_t filterGroup)

Sets the Filter Group for kernel objects created after this point, such as tasks, queues, semaphores and mutexes. Together with vTraceSetFilterMask, this allows you to filter the events based on the objects they refer to. This can be used to reduce the trace data rate, i.e., if your streaming interface is a bottleneck or if you want longer snapshot traces without increasing the buffer size. And by grouping related objects into filter groups, you can exclude whole subsystems from the trace simply by changing the filter mask.

The parameter filterGroup should be one of the 16 predefined filter groups IDs, named FilterGroup0 .. FilterGroup15, corresponding to each bit in the filter mask. The provided ID is stored and assigned to every kernel object created after this point, including tasks, queues, semaphores and mutexes. You typically call vTraceSetFilterGroup at multiple occations in your startup code to divide your kernel objects into multiple filter groups.
Note: We don't recommend filtering out tasks, as this can affect the visualizations in unexpected ways. It can however be motivated for frequent, periodic tasks with short execution time. To avoid filtering out the Idle task by accident, make sure to call vTraceSetFilterGroup(FilterGroup0) before starting the RTOS scheduler in order to restore the default assignment.

Example:

// Assign tasks T1 to FilterGroup0 (default)
[Create Task T1]  

// Assign Q1 and Q2 to FilterGroup1
vTraceSetFilterGroup(FilterGroup1);
[Create Queue Q1] 
[Create Queue Q2]

// Assigns Q3 to FilterGroup2
vTraceSetFilterGroup(FilterGroup2);
[Create Queue Q3]

// Only include FilterGroup0 and FilterGroup2, exclude FilterGroup1 (Q1 and Q2) from the trace
vTraceSetFilterMask( FilterGroup0 | FilterGroup2 );

// Assign the default RTOS objects (e.g. Idle task) to FilterGroup0
vTraceSetFilterGroup(FilterGroup0);
[Start the RTOS scheduler]

Note that you may define your own names for the filter groups using preprocessor definitions, to make the code easier to understand. Example:

#define BASE FilterGroup0
#define USB_EVENTS FilterGroup1
#define CAN_EVENTS FilterGroup2
...
vTraceSetFilterMask( BASE | CAN_EVENTS );
Note: There are two kinds of filters in the recorder. While vTraceSetFilterMask and vTraceSetFilterGroup provides filtering on object-level, it is also possible to filter by event type/category, regardless of object. See the TRC_CFG_INCLUDE... settings in trcConfig.h.

Extended API for Snapshot Mode

This section describes the extended API for snapshot mode, with extra functions that are typically only needed for more advanced setups. The extended API is not self-contained but should be used together with the Common API when in snapshot mode.

xTraceGetTraceBuffer

void* xTraceGetTraceBuffer(void)

Returns a pointer to the snapshot trace buffer, for programmatic access. See also uiTraceGetTraceBufferSize.

Example:

/* Save the snapshot trace buffer */
f = fopen("mytrace.bin","wb");
ptr = xTraceGetTraceBuffer();
size = uiTraceGetTraceBufferSize();
vTraceStop();
fwrite(ptr, size, 1, f);
fclose(f);	
vTraceClear();
vTraceEnable(TRC_START);

uiTraceGetTraceBufferSize

uint32_t uiTraceGetTraceBufferSize(void)

Returns the size of the snapshot trace buffer, in bytes. See also xTraceGetTraceBuffer.

Example:

/* Save the snapshot trace buffer */
f = fopen("mytrace.bin","wb");
ptr = xTraceGetTraceBuffer();
size = uiTraceGetTraceBufferSize();
vTraceStop();
fwrite(ptr, size, 1, f);
fclose(f);
vTraceClear();
vTraceEnable(TRC_START);

vTraceSetStopHook

void vTraceSetStopHook(TRACE_STOP_HOOK stopHookFunction)

Sets a callback function to be called when the recorder is stopped. The data type TRACE_STOP_HOOK is defined as typedef void(TRACE_STOP_HOOK)(void).

Example:

void mySaveTraceFunction(void);
...
vTraceSetStopHook(mySaveTraceFunction);

uiTraceStart

(DEPRECATED) Use vTraceEnable instead.

Starts the recorder. The recorder will not be started if an error has been indicated using prvTraceError, e.g. if any of the Nx constants in trcSnapshotConfig.h has a too small value (TRC_CFG_NTASK, TRC_CFG_NQUEUE, etc).

Returns 1 if the recorder was started successfully.
Returns 0 if the recorder start was prevented due to a previous internal error.
In that case, check xTraceGetLastError to get the error message.
Any error message is also presented when opening a trace file.

vTraceStart

(DEPRECATED) Use vTraceEnable instead.

Starts the recorder. The recorder will not be started if an error has been indicated using prvTraceError, e.g. if any of the Nx constants in trcSnapshotConfig.h has a too small value (TRC_CFG_NTASK, TRC_CFG_NQUEUE, etc).

vTraceClear

void vTraceClear(void)

Resets the recorder. This is not needed in the normal initialization, only if you want a clean restart of the tracing while the system is running.

xTraceRegisterUBChannel

traceUBChannel xTraceRegisterUBChannel(traceString channel, traceString formatStr)

The separate user event buffer (UB) allows for storing User Events separately from other events, e.g., RTOS events. Thereby you can get a much longer history of user events as they don't need to share the buffer space with more frequent events. User event in the UB are structured as UB channels, which contains a channel name and a default format string.

This function looks up an existing UB channel matching the parameters, or registers a new UB channel if no match is found.

The parameter channel is the name of the UB channel, that is to be shown in Tracealyzer. The parameter formatStr is a format string or a plain event name, that is to be used for all events stored on this channel using vTraceUBEvent and vTraceUBData. Both parameters are traceStrings, obtained using xTraceRegisterString.

Returns a channel handle for use in vTraceUBEvent and vTraceUBData.

Events and data arguments are written using vTraceUBEvent and vTraceUBData. They are designed to provide efficient logging of repeating events, using the same format string within each channel.

Examples:

traceString chn1 = xTraceRegisterString("Channel 1");
traceString fmt1 = xTraceRegisterString("Event!");
traceUBChannel UBCh1 = xTraceRegisterUBChannel(chn1, fmt1);

traceString chn2 = xTraceRegisterString("Channel 2");
traceString fmt2 = xTraceRegisterString("X: %d, Y: %d");
traceUBChannel UBCh2 = xTraceRegisterUBChannel(chn2, fmt2);

// Results in "[Channel 1] Event!"
vTraceUBEvent(UBCh1);

// Results in "[Channel 2] X: 23, Y: 19"
vTraceUBData(UBCh2, 23, 19);

Set TRC_CFG_USE_SEPARATE_USER_EVENT_BUFFER to 1 in trcSnapshotConfig.h to enable the UB mode and related functions.

Only a certain number of UB channels are allowed, by default 32, defined by TRC_CFG_UB_CHANNELS.

The buffer uses ring-buffer semantics, meaning that older events are overwritten if the buffer gets full. The buffer size is defined by TRC_CFG_SEPARATE_USER_EVENT_BUFFER_SIZE, by default 200 slots (x 4 byte each).

When using the UB, if using ring-buffer mode also for the main event buffer (quite typical), the earliest part of the trace often only contains User Event data (from the UB) and no RTOS events (since overwritten). Tracealyzer displays such intervals using a placeholder "task" labeled "Unknown Actor".

You may use vTracePrintF and vTracePrint also in UB mode, as they are then rerouted to the UB instead of the main event buffer. vTracePrintF then looks up the correct UB channel based on the provided channel name and format string, or creates a new UB channel if no match is found (combination of channel name and format string). To avoid running out of UB channels, the format string should therefore not contain "random" messages but mainly format specifiers. Random strings should be stored using %s and with the string as an argument.

vTraceUBData

void vTraceUBData(traceUBChannel channel, ...)

Writes a user event to the separate user event buffer (UB), with data arguments. The parameter channel is obtained from xTraceRegisterUBChannel and specifies the format string for this event.

In UB mode, user events are stored separately from other events, e.g., RTOS events. Thereby you can get a much longer history of user events as they don't need to share the buffer space with more frequent events.

Set TRC_CFG_USE_SEPARATE_USER_EVENT_BUFFER to 1 in trcSnapshotConfig.h to enable the UB mode and related functions.

Examples:

traceString chn2 = xTraceRegisterString("Channel 2");
traceString fmt2 = xTraceRegisterString("X: %d, Y: %d");
traceUBChannel UBCh2 = xTraceRegisterUBChannel(chn2, fmt2);

// Results in "[Channel 2] X: 23, Y: 19"
vTraceUBData(UBCh2, 23, 19);

vTraceUBEvent

void vTraceUBEvent(traceUBChannel channel)

Writes a user event to the separate user event buffer (UB), without data arguments. The parameter channel is obtained from xTraceRegisterUBChannel and specifies the format string for this event. Since this function is intended for basic events without data arguments, the "format string" set in the UB channel should not contain any format specifiers, but is simply a string.

In UB mode, user events are stored separately from other events, e.g., RTOS events. Thereby you can get a much longer history of user events as they don't need to share the buffer space with more frequent events.

Set TRC_CFG_USE_SEPARATE_USER_EVENT_BUFFER to 1 in trcSnapshotConfig.h to enable the UB mode and related functions.

Examples:


traceString chn1 = xTraceRegisterString("Channel 1");
traceString fmt1 = xTraceRegisterString("Event!");
traceUBChannel UBCh1 = xTraceRegisterUBChannel(chn1, fmt1);

// Results in "[Channel 1] Event!"
vTraceUBEvent(UBCh1);

Hardware Ports

The recorder library needs a hardware timer port in order to give accurate timestamps on the recorded events. This is the only hardware dependency of the recorder library. Some hardware architectures are already supported in the recorder library, such as ARM Cortex M0, M3 and M4 (all chips using Cortex M cores), and other ports are in development. However, there are many chips that we do not yet have direct support for, with respect to hardware timestamping. In case your chip is not yet directly supported, the recorder library includes a hardware independent fall-back option providing low resolution time stamping equivalent to the Real Time Engineers ltd tick, typically 1 ms. However, it is strongly recommended to use to a hardware timer for any serious use of this tool. Fortunately, developing a hardware timer port yourself is quite easy.

Developing a hardware port is mainly a matter of setting of the timestamping. This relies on a set of macros (TRC_HWTC...) that you define according to your specific hardware in trcHardwarePort.h. The central macro is TRC_HWTC_COUNT, that is expected to provide the current value of a suitable hardware timer/counter. You typically use the same interrupt timer that drives the FreeRTOS tick. Please refer to the comments in trcHardwarePort.h for additional instructions.

To make a custom hardware port, you may also need to provide a definition for interrupt-safe critical sections within the recorder. This since the standard critical sections offered by FreeRTOS, like portENTER_CRITICAL/portEXIT_CRITICAL, might not safe to use from interrupt (kernel) context, and some recorder functions may be called from both contexts. It is therefore advised to disable interrupts completely in the recorder's critical sections, and store the previous state to avoid enabling interrupts when not suitable. If your FreeRTOS port implements portSET_INTERRUPT_MASK_FROM_ISR and portCLEAR_INTERRUPT_MASK_FROM_ISR, you can use the following definitions:

#define TRACE_ALLOC_CRITICAL_SECTION() int __irq_status;
#define TRACE_ENTER_CRITICAL_SECTION() {__irq_status = portSET_INTERRUPT_MASK_FROM_ISR();}
#define TRACE_EXIT_CRITICAL_SECTION() {portCLEAR_INTERRUPT_MASK_FROM_ISR(__irq_status);}

The definitions of TRACE_ENTER_CRITICAL_SECTION etc. are normally found in trcKernelPort.h, feel free to modify those if necessary.


Copyright Percepio AB 2017, all rights reserved.