A lightweight, open-source framework for recording and visualising FreeRTOS task scheduling traces. Trace output is produced in two industry-standard formats:
- BTF (Best Trace Format) — a CSV-based format designed for system-level timing and performance analysis of embedded real-time systems. Specification available here.
- VCD (Value Change Dump) — an ASCII-based waveform format compatible with logic simulation tools such as GTKWave.
Identifying performance bottlenecks in real-time embedded systems often requires a full-featured commercial tool such as Percepio Tracealyzer. This project provides a simple, extensible, and completely free alternative. It instruments FreeRTOS with trace hooks, captures context-switch events into a compact in-memory buffer, and converts that buffer to BTF or VCD for offline analysis.
A related approach using BareCTF and Eclipse Trace Compass is available at freertos-barectf.
FreeRTOS trace hooks
→ btf_trace_add_*() (ring buffer in RAM)
→ traceEND() writes trace.bin via fopen/fwrite/fclose
→ tools/gentrace
→ .btf / .vcd
→ BTFViewer (desktop or web) / Trace Compass / GTKWave
FreeRTOS-Trace/ Trace library (hooks, ring buffer, file dump via fopen)
tools/ gentrace — binary dump → BTF or VCD
sim/ RV64 SMP instruction-set simulator (C++, supports --cores N)
Demo/ FreeRTOS SMP demo built for RV64, run under sim/
BTFViewer/ Interactive BTF viewer (PyQt5 desktop + Vue 3 web app)
tracedata/ Sample outputs (example.btf, example.vcd)
images/ Documentation screenshots
The demo targets RISC-V RV64 and runs on the included sim/ simulator (a custom C++ RV64 ISS that supports SMP via --cores N).
Install the xPack RISC-V Embedded GCC toolchain. The default path expected by the build system is:
/opt/xpack-riscv-none-elf-gcc-15.2.0-1/bin/riscv-none-elf-gcc
Override with RISCV_PREFIX if yours is installed elsewhere:
make run RISCV_PREFIX=/path/to/riscv-none-elf-# Single-core (default)
make run
# SMP — 2 cores
make CORES=2 run
# SMP — 8 cores
make CORES=8 runOn the first run, FreeRTOS-Kernel (V11.3.0) is cloned automatically.
The build produces:
build/demo/examples/cores<N>/freertos_test.elf— the FreeRTOS test binarybuild/sim/riscv64-sim— the RV64 simulatorbuild/tools/gentrace— the trace converter
After the simulator exits it writes trace.bin; gentrace converts it to tracedata/trace.btf and tracedata/trace.vcd.
The demo (Demo/examples/freertos_test/) runs six SMP stress tests:
| # | Test | What it exercises |
|---|---|---|
| 1 | Context-switch stress | Rapid voluntary yields across all cores |
| 2 | Mutex contention | Priority-inheritance mutex under load |
| 3 | Counting semaphore + mutex | Mixed synchronisation primitives |
| 4 | Task notifications | Direct-to-task notification API |
| 5 | Event groups | Multi-bit event synchronisation |
| 6 | Queue stress | Producer/consumer queues at speed |
Expected output (CORES=2 example):
freertos_test: starting
cores=2 workers=6 sem_slots=2 iter_fast=50 iter_slow=20
test 1: context-switch stress ... pass
test 2: mutex contention ... pass
test 3: counting-sem + mutex ... pass
test 4: task notifications ... pass
test 5: event group ... pass
test 6: queue stress ... pass
5034 events generated.
freertos_test: all tests passed
An interactive Gantt-style viewer is included in the BTFViewer/ directory (desktop PyQt5 app and browser-based web viewer).
Desktop requirements: Python 3.8+ and PyQt5 ≥ 5.15
pip install PyQt5
python BTFViewer/btf_viewer.py tracedata/trace.btfSee BTFViewer/README.md for the full feature reference (zoom, cursors, trace compare, session restore, export, etc.).
Open tracedata/trace.btf directly in Trace Compass.
gtkwave tracedata/trace.vcdFollow these steps to integrate the trace library into your own FreeRTOS project.
Add the following line to your FreeRTOSConfig.h:
#include "FreeRTOS-Trace/FreeRTOS-Trace.h"Enable tracing in config:
#define configUSE_TRACE_FACILITY 1
#define configMAX_TRACE_EVENTS 4096 /* adjust for RAM budget */
#define configMAX_TRACE_TASKS 64Edit FreeRTOS-Trace/btf_port.h and define the xGetCycles() macro to return the current system cycle counter:
#define xGetCycles() /* your platform timer, e.g. DWT cycle counter */The RISC-V port uses portGET_RUN_TIME_COUNTER_VALUE() which maps to rdcycle.
| Mode | Configuration |
|---|---|
| File dump (default) | HAVE_FILE_DUMP in btf_port.h — writes trace.bin via fopen/fwrite/fclose at traceEND() |
| Live stdout BTF | Uncomment #define PRINT_BTF_DUMP in btf_port.h |
| Buffer only | Define neither — keep events in RAM and dump the trace_data symbol after the run via debugger/JTAG |
Compile FreeRTOS-Trace/btf_trace.c as part of your project.
int main(void)
{
#if configUSE_TRACE_FACILITY
traceSTART();
#endif
xTaskCreate( ... );
vTaskStartScheduler();
}
/* Inside the task that finishes last: */
#if configUSE_TRACE_FACILITY
traceEND(); /* writes trace.bin */
#endif
exit(0);All trace hooks in FreeRTOS-Trace/FreeRTOS-Trace.h are protected by either taskENTER_CRITICAL() (task context) or taskENTER_CRITICAL_FROM_ISR() (ISR / context-switch context).
The RISC-V SMP port (Demo/port/RISC-V/port.c) implements both the task lock and the ISR lock as recursive spinlocks (owner + count), mirroring the Xtensa xt_mutex design. This allows traceTASK_SWITCHED_OUT/IN — which fire inside vTaskSwitchContext while the ISR lock is already held — to safely re-enter the same lock without deadlocking.
# BTF format
tools/gentrace trace.bin trace.btf
# VCD format
tools/gentrace -v trace.bin trace.vcd- BTF: open with
BTFViewer/btf_viewer.py, the web viewer, or Eclipse Trace Compass. - VCD: open with GTKWave or any compatible VCD viewer.
Default trace buffer size is controlled by configMAX_TRACE_EVENTS and configMAX_TRACE_TASKS in FreeRTOSConfig.h. Each event is 12 bytes; task names add max_tasks × max_taskname_len bytes. When the buffer fills, btf_trace_add_event() silently drops new events (no overwrite, no assert).
| Area | Notes |
|---|---|
| Task table overflow | btf_trace_add_task() disables tracing when the task name table is full |
| Parser parity | BTF is parsed independently in Python (btf_viewer.py) and JavaScript (btfParser.js); no shared automated test vectors yet |
| CI / unit tests | No automated test suite in this repository; validate with make run and manual viewer smoke tests |
| Web viewer | Trace files stay client-side; session state is stored in browser localStorage keyed by filename |
MIT License