Skip to the content.

Trice - Trace IDs for Embedded C/C++

The name combines TRace IDs, C/C++, and Embedded systems.

View GitHub Pages

License GitHub release (latest by date) Downloads TRICE Library CI (Nightly Full)

Trice project mascot
Hi, I am Trice.

Trice is a compact ID-based logging framework for embedded C/C++. Firmware code uses printf-like calls, but the target sends only small binary records: an ID plus optional runtime values. The PC-side trice tool reconstructs the readable text using the project-specific til.json which is intentionally cumulative: old IDs and format strings remain available so logs from older firmware can still be decoded.

Use Trice when normal printf logging is too slow, too large, too intrusive in interrupt contexts, or too inefficient for field diagnostics.

User Manual: GitHub - GitHub Pages - PDF - Project page


Log in a trice — even from ↯ interrupt handlers in a few CPU cycles in optimized configurations❗

Small demo animation ![Trice demo](/trice/docs/ref/life0.gif)

Quickstart

Choose one first path:

Situation Start here
You already have a non-blocking byte writer, DMA TX queue, USB CDC writer, socket writer, pipe, or file writer Existing non-blocking byte writer
You have a SEGGER J-Link and want the quickest lab setup SEGGER RTT direct mode
You want UART or USB-VCOM output UART / USB-VCOM deferred output
You want complete STM32 examples Example projects

For most existing projects, the non-blocking byte writer path is the least disruptive first integration.

Details and advanced topics

Basics

Minimal source example Trice gives embedded firmware a fast logging path with a familiar source-code style: ```c trice("boot\n"); trice("adc=%d mV\n", adc_mV); ``` Instead of formatting strings on the target, Trice stores the human-readable text on the host and transfers compact binary records from the target. This reduces target FLASH usage, transfer bandwidth, log files and log-call execution time.
How ID-based decoding works 1. Write `trice(...)` calls in firmware code. 2. Run `trice insert` before compilation. 3. The compiler sees stable numeric IDs. 4. The target emits compact binary Trice records. 5. The PC tool decodes them with the accumulated `til.json`. A typical source-level idea is: ```diff - printf("Temperature: %d °C\n", t); + trice("Temperature: %d °C\n", t); ```
What about source code modification? Trice assigns IDs by processing source files with `trice insert`. A call such as this: ```c trice("boot\n"); ``` becomes similar to this before compilation: ```c trice(iD(12345), "boot\n"); ``` This is intentional: the target binary contains the ID, while the host keeps the text in `til.json`. You can choose how visible those IDs are in your workflow: - keep inserted IDs in source, - run `trice clean` after the build, - use the Trice cache to avoid needless rebuilds of unchanged files. For team projects, define this policy early and treat `til.json` like any other build artifact needed for field diagnostics. See [Trice ID management](./docs/TriceUserManual.html#trice-id-management).
Key benefits * **[Easy Migration](./docs/TriceUserManual.html#trice-and-legacy-user-code)** – reuse existing `printf`-style code with minimal changes via the `-alias` option * **[Long-term field decoding](./docs/TriceUserManual.html#versions-and-variants-trice-stability)** – decode logs from released firmware when the matching or accumulated `til.json` is preserved. * **[Reduced target FLASH](./docs/TriceUserManual.html#trice-memory-needs)** - format strings are kept in `til.json`, not in the target image. * **Very low target overhead** - down to a few CPU cycles in optimized configurations; see [Trice Speed](./docs/TriceUserManual.html#trice-speed). * **Transport friendly** - UART, RTT, TCP/UDP, files, or your own non-blocking byte writer. * **[Compact transfer](./docs/TriceUserManual.html#minimal-transfer-bytes-amount)** – a Trice record contains one 32-bit ID plus optional runtime values, reducing bandwidth and log-file size. * **Portable tooling** - the host tool is written in Go and runs on common desktop platforms. * **Further features** - like encryption, timestamps, flexible logging, transport options and tooling are described [here](./docs/TriceUserManual.html#trice-features-overview). * **Fully documented**: [Trice User Manual](./docs/TriceUserManual.html)
Which mode should you start with? | Mode / path | Use when | |---|---| | **Deferred auxiliary 8-bit** | You already have a non-blocking byte writer. This is usually the most universal first integration. | | **Direct SEGGER RTT** | You have J-Link/RTT and want a fast interactive lab setup. | | **Deferred UART / USB-VCOM** | Your board exposes a serial path and you want a conventional PC input stream. | | **Deferred ring buffer** | You want a balanced default for RAM usage and runtime behavior. | | **Deferred double buffer** | You want the shortest target-side log-call path and can spend more RAM. | Direct mode writes each record immediately to the selected backend. Deferred mode writes first into a target buffer; `TriceTransfer()` sends later.

Integration workflow

Two parts of Trice Trice has two cooperating parts: - **Target-side C/C++ macros** provide a familiar `printf`-like interface but emit IDs and values instead of full format strings. - **The PC-side `trice` tool** manages IDs, receives binary data, and reconstructs readable log output. The host tool is written in [Go](https://go.dev/) and runs on common desktop platforms. You can also build your own receiver that reads Trice packages, maps IDs to format strings, and displays or stores the decoded output.
Files: til.json, li.json, and binary logs | File | Purpose | Keep? | |---|---|---| | `til.json` | Maps IDs to format strings | Yes; required for decoding | | `li.json` | Maps IDs to source locations | Useful for diagnostics | | Binary log file | Raw target output | Useful for offline or field decoding | Keep the one accumulated `til.json` to decode all previously released firmware versions. The repository example file is [demoTIL.json](/trice/demoTIL.json).
Three common ID workflows **Keep IDs in source** - good for small projects and maximum robustness. ```bash trice insert -src ./ -i ./til.json -li ./li.json # build normally ``` **Insert before build, clean after build** - good when the repository should stay visually free of IDs. ```bash trice insert -src ./ -i ./til.json -li ./li.json # build trice clean -src ./ -i ./til.json -li ./li.json ``` **Use the Trice cache** - good when repeated insert/clean would otherwise cause unnecessary recompilation. ```bash mkdir -p ~/.trice/cache trice insert -cache -src ./ -i ./til.json -li ./li.json # build trice clean -cache -src ./ -i ./til.json -li ./li.json ``` Pick one workflow and document it in the project build instructions. If other tools also rewrite source files, such as formatters or code generators, run them before `trice insert`.
Trice cache The Trice cache avoids unnecessary rebuilds when using `trice insert` and `trice clean` around the build. It keeps cached inserted and cleaned copies of unchanged files. Typical use: ```bash mkdir -p ~/.trice/cache trice insert -cache -src ./ -i ./til.json -li ./li.json # build trice clean -cache -src ./ -i ./til.json -li ./li.json ``` Use cache when you want to keep IDs out of your source code while avoiding repeated recompilation of unchanged files. Be careful when your build system also modifies source files; for example, run auto-formatters before `trice insert`. How Cache Works: The Trice cache saves copies of all files after processing them with `trice i` or `trice c`. This avoids inserting and removing IDs repeatedly. **The copies are used to get the same results for files that have not been edited.** Edited files are processed normally and the cache updates afterwards. File modification times do not change, so **the build system does not reprocess unchanged files even when IDs are temporarily removed**. See [Trice Cache for Compilation Speed](./docs/TriceUserManual.html#trice-cache-for-compilation-speed).
Normal workflow 1. Add the Trice target sources from [`src/`](./src) to your firmware project. 2. Add a project-specific `triceConfig.h`. 3. Write Trice calls in your code. 4. Run `trice insert` before compiling. This assigns stable IDs and updates `til.json` and `li.json`. 5. Build and flash the target. 6. Run `trice log` on the PC to decode the binary stream. Minimal firmware shape: ```c #include "trice.h" int main(void) { BoardInit(); TriceInit(); trice("boot\n"); trice("adc=%d mV\n", adc_mV); for (;;) { AppRun(); TriceTransfer(); // needed for deferred outputs } } ``` Create the ID files once, then insert IDs: ```bash touch til.json li.json trice insert -src ./ -i ./til.json -li ./li.json ``` Start logging from a serial or USB-VCOM port: ```bash trice log -p COM15 -baud 921600 -i ./til.json -li ./li.json # or, on Linux/macOS: trice log -p /dev/ttyACM0 -baud 921600 -i ./til.json -li ./li.json ```
Legacy code integration Existing projects do not have to migrate all logs at once. Best strategy: > Use Trice aliases so project-specific log macros are processed by `trice insert` without renaming every call site. See [Legacy User Code Option Trice Aliases Adaptation](./docs/TriceUserManual.html#legacy-user-code-option-trice-aliases-adaptation). Alternatively: 1. Keep legacy `printf` output and send Trice on a separate physical channel. 2. Replace selected `printf` calls with Trice calls where speed, bandwidth, or FLASH matters. 3. Use triceS macros to emit runtime generated printf strings. 4. Shared the output channel with [typeX0 User Packets](./docs/TriceUserManual.html#typex0-user-packets). For a first integration, prefer a small vertical slice: - one source file, - one working output path, - one `til.json`, - one build pre-step, - one host `trice log` command. Then expand to aliases and larger source trees. See [Trice and legacy User Code](./docs/TriceUserManual.html#trice-and-legacy-user-code).

Runtime modes and output paths

Direct mode and deferred mode | Mode | What happens during the log call | Good for | |---|---|---| | **Direct mode** | Each Trice record is written immediately to the selected output backend. | Interactive development, especially SEGGER RTT. | | **Deferred mode** | The log call writes only into a target buffer; `TriceTransfer()` sends later. | UART, USB CDC, existing writer functions, production-style logging, shortest target execution path. | A practical rule: - Start with **deferred auxiliary 8-bit output** when your project already has a write function. - Start with **direct SEGGER RTT** when you have J-Link and want the easiest lab setup. - Use **deferred ring buffer** for a balanced RAM/speed setup. - Use **deferred double buffer** when minimizing the target-side log-call cost is more important than RAM. A slow physical output path, especially a blocking UART, can dominate direct-mode timing. Deferred mode avoids this during the log call, but you must call `TriceTransfer()` often enough and size buffers for burst load.
Buffer choice | Buffer | Typical use | Trade-off | |---|---|---| | `TRICE_STACK_BUFFER` | Simple direct-output setups | No persistent deferred storage; stack use matters | | `TRICE_STATIC_BUFFER` | Direct output without stack allocation | One static single-message buffer | | `TRICE_RING_BUFFER` | Balanced deferred output | Less RAM, robust general-purpose default | | `TRICE_DOUBLE_BUFFER` | Minimum target-side Trice execution time | More RAM, best for high-speed deferred extraction | See [Trice Speed](./docs/TriceUserManual.html#trice-speed) and [Trice memory needs](./docs/TriceUserManual.html#trice-memory-needs).
Auxiliary hooks Auxiliary hooks are useful when the project already owns the physical transport. They can write Trice binary data to an SD card, flash partition, UART/USB queue, socket, pipe, file, or other byte sink. | Switch | Hook | Writer shape | Typical use | |---|---|---|---| | `TRICE_DIRECT_AUXILIARY8` | `UserNonBlockingDirectWrite8AuxiliaryFn` | `const uint8_t*`, byte length | Direct byte stream, only if fast/non-blocking | | `TRICE_DEFERRED_AUXILIARY8` | `UserNonBlockingDeferredWrite8AuxiliaryFn` | `const uint8_t*`, byte length | Most universal first integration | | `TRICE_DIRECT_AUXILIARY32` | `UserNonBlockingDirectWrite32AuxiliaryFn` | `const uint32_t*`, word count | Fast word-oriented direct sink | | `TRICE_DEFERRED_AUXILIARY32` | `UserNonBlockingDeferredWrite32AuxiliaryFn` | `const uint32_t*`, word count | Word-oriented deferred sink; also useful with encryption | The deferred 8-bit hook is usually the safest first quickstart because almost every transport can consume bytes. See [Writing the Trice logs into an SD-card or a user-specific output](./docs/TriceUserManual.html#writing-the-trice-logs-into-an-sd-card-or-a-user-specific-output).
Data transfer options Implemented transfer methods include: - [UART](https://en.wikipedia.org/wiki/Universal_asynchronous_receiver-transmitter), including virtual UART over USB, - [RTT](https://www.segger.com/products/debug-probes/j-link/technology/about-real-time-transfer/), including [J-Link](/trice/third_party/segger.com/) and [ST-Link](/trice/third_party/goST/) related paths, - TCP4 input/output, - UDP4 input. Other possible transport approaches include a small bridge microcontroller or FTDI-style adapters for GPIO, I2C, SPI, CAN, LIN, UART, FIFO, or file-based capture. Start with a supported path first; optimize transport later.
Display server for multiple targets `trice ds` can act as a display server. Multiple `trice log` instances can send completed log lines to one display server so several targets appear in one combined output. ```bash trice ds trice log -p COM15 -ds trice log -p COM16 -ds ``` See [Logging Over a Display Server](./docs/TriceUserManual.html#logging-over-a-display-server) and [Several Targets at the same time](./docs/TriceUserManual.html#several-targets-at-the-same-time).

Features beyond basic logging

FLASH and bandwidth reduction Replacing format strings with IDs at compile time acts like compression for target FLASH and transferred log data. A typical Trice record can be only a few bytes plus parameters, independent of the human-readable format-string length. See also: - [Small Size - using Trice frees FLASH Memory](./docs/TriceUserManual.html#small-size---using-trice-frees-flash-memory) - [Minimal Transfer Bytes Amount](./docs/TriceUserManual.html#minimal-transfer-bytes-amount) - [Trice Memory Needs](./docs/TriceUserManual.html#trice-memory-needs)
Field logs and diagnostics Trice can be used as a fast `printf`-debugging replacement and as a compact logging system for field diagnostics. The target emits short binary records; the host produces the readable log later. Keep the newest accumulated `til.json` so logs from any field devices (also older versions) can be decoded after release. Optionally store project-specific `til.json` and `li.json` together with firmware images or diagnostic packages.
Storing Trice logs in FLASH, SD-card, or files Trice binary logs can be written to FLASH, SD-card, a file, or another project-specific output for later analysis. This can be useful when a target is not permanently connected to a PC. See [Writing the Trice logs into an SD-card or a user-specific output](./docs/TriceUserManual.html#writing-the-trice-logs-into-an-sd-card-or-a-user-specific-output).
Encryption Trice supports optional XTEA encryption for transport packets. This can be useful when field logs should only be readable with the matching key and ID list. Enable encryption in `triceConfig.h` and pass the matching password/key option to `trice log`. See [Optional XTEA encryption](./docs/TriceUserManual.html#optional-xtea-encryption).
Host-side wording and translation Because the target sends IDs and values, the host-side `til.json` can map an ID to different text as long as the ID and parameter format remain compatible. This allows host-side wording changes or language variants without changing the target binary. See [Switch the language without changing a bit inside the target code](./docs/TriceUserManual.html#switch-the-language-without-changing-a-bit-inside-the-target-code).
Timing analysis Trice supports host and target timestamps and can help with distributed timing analysis, interrupt timing, and observing target behavior without stopping firmware in a debugger. See [Trice Timestamps (Formatting and Delta Columns)](./docs/TriceUserManual.html#trice-timestamps).
Trice ABC - Asynchronous Broadcast Commands **Trice ABC** extends the normal Trice idea from logging to compact command records. ![Trice ABC overview](/trice/docs/ref/trice_abc_readme_teaser.svg) A sender emits a normal Trice record, for example: ```c trice8C("cmd:setLeds", &mask, 1); ``` After `trice insert`, the command name is represented by a numeric ID in `til.json`. With `trice generate -abc`, receiver-side dispatch tables are generated so each receiver can select which command IDs become local handler calls. ABC is intentionally **not** a full RPC layer by itself. Addressing, ACKs, retries, authentication, timeouts, and return-value semantics remain application policy. See [Trice ABC in the User Manual](./docs/TriceUserManual.html#trice-abc---asynchronous-broadcast-commands) and the [host-native ABC demo](./examples/TriceAbc).
UART example diagram This simplified [draw.io](https://github.com/jgraph/drawio) diagram shows how Trice works over a UART-style path. Read the detailed explanation in [How it works - the main idea](./docs/TriceUserManual.html#how-it-works---the-main-idea). ![Trice UART block diagram](/trice/docs/ref/triceCOBSBlockDiagram.svg)
Routing different ID ranges to different outputs Trice can route different ID ranges or tagged messages to different outputs depending on configuration. See the User Manual for the current configuration details and examples.

</details>

Sharing a User protocol with the same Trice Output Channel The Trice buffer macros allow to transfer any data. But with [typeX0 User Packets](./docs/TriceUserManual.html#typex0-user-packets) the user can also inject own counted buffers in the Trice data stream. The Trice tool will optionally display them as raw bytes using a "%02x" format and could get an extension to forward those packages in any possible direction.

Project information

Installation Use one of these options: ```bash # macOS with Homebrew brew install rokath/tap/trice ``` Or download a release binary from [the latest release](https://github.com/rokath/trice/releases/latest) and put `trice` into your `PATH`. Developers with Go installed can also build/install from source: ```bash go install github.com/rokath/trice/cmd/trice@latest ``` Check the installation: ```bash trice --version trice help -insert trice help -log ```
Documentation and examples - [Trice User Manual](./docs/TriceUserManual.html) - [GitHub Pages documentation](https://rokath.github.io/trice/docs/TriceUserManual.html) - [Generated CLI help](/trice/docs/ref/trice-help-all.txt) - [Target source configuration defaults](/trice/src/triceDefaultConfig.h) - [Examples](./examples) - [Issues](https://github.com/rokath/trice/issues), including closed items - [Discussions](https://github.com/rokath/trice/discussions), including closed items
Development setup and debugging The example projects show Trice integration with VS Code, Makefiles, Clang/GCC, STM32 projects, RTT, and UART-style output. Use bare/instrumented project pairs to inspect the minimal integration diff. Debug a Trice project in Direct-Out Mode over SEGGER-RTT. See [Development Environment Setup](./docs/TriceUserManual.html#development-environment-setup).
Project status ![GitHub commits since latest release](https://img.shields.io/github/commits-since/rokath/trice/latest) ![GitHub issues](https://img.shields.io/github/issues/rokath/trice) [![Coverage Status](https://coveralls.io/repos/github/rokath/trice/badge.svg?branch=main)](https://coveralls.io/github/rokath/trice?branch=main) [![CodeQL](https://github.com/rokath/trice/actions/workflows/codeql.yml/badge.svg)](https://github.com/rokath/trice/actions/workflows/codeql.yml) Trice is usable today and actively maintained. Use the latest release or the `main` branch when building from source. Avoid development branches unless you intentionally want unreleased work. Contributions are welcome: examples, platform recipes, transport backends, documentation improvements, and reproducible benchmark reports are especially valuable.
Future ideas Potential future work includes: - a small `tlog` tool in C, Python, Rust, TinyGo, Wasm, or another runtime for environments where Go is not the best fit, - more structured logging support; see the [structured logging specification draft](./docs/TriceUserManual.html#trice-structured-logging), - additional transport recipes, - adaptation for a visual RTOS/event timeline tool.
Support the project - Star this project if it helps you. - Contribute examples, fixes, documentation, or platform recipes. - If Trice helps a commercial product, consider supporting continued maintenance through the sponsor links on the GitHub project page. Support options: - [Become a Sponsor with your GitHub account](https://github.com/sponsors/rokath/) - Buy Me A Coffee - PayPal
Similar projects Trice overlaps with several logging, tracing, and tokenization approaches, but it targets a specific niche: compact `printf`-like logging for embedded C/C++ with host-side ID decoding. - plain `printf` / UART logging - ARM ITM/SWO (hardware-native) - ARM Keil Event Recorder (hardware-native) - [baical.net](https://baical.net/up7.html) (C) - [call stack logger function instrumentation](https://dev.to/taugustyn/call-stack-logger-function-instrumentation-as-a-way-to-trace-programs-flow-of-execution-419a) (trace program execution flow) - [Debugging with Dynamic Printf Breakpoints](https://mcuoneclipse.com/2022/02/09/debugging-with-dynamic-printf-breakpoints/) (Eclipse IDE option) - [defmt (Rust)](https://github.com/knurling-rs/defmt) - [Diagnostic Log and Trace](https://github.com/COVESA/dlt-daemon) (AUTOSAR) - [Embedded Logger (elog)](https://github.com/martinribelotta/elog) (Embedded logger with minimal footprint and memory usage) - [Logging with symbols - The Embedonomicon](https://docs.rust-embedded.org/embedonomicon/logging.html) - [McuLog (McuOnEclipse)](https://mcuoneclipse.com/2020/06/01/mculog-logging-framework-for-small-embedded-microcontroller-systems/) - [MCUViewer](https://github.com/klonyyy/MCUViewer) - [Memfault Compact Log Library](https://docs.memfault.com/docs/mcu/compact-logs/#host-decoding) (part of firmware SDK) - [Minimal Structured Logging for Autonomous Vehicles](https://youtu.be/FyJI4Z6jD4w) (C++, closed source talk) - [NanoLog (C++11)](https://github.com/PlatformLab/NanoLog) (Linux C++) - [Percepio Tracealyzer (with TraceRecorder)](https://percepio.com/tracealyzer/) (Visual Trace Diagnostics) - [Pigweed Trace Tokenized](https://pigweed.dev/pw_trace_tokenized/) - [Postform](https://github.com/Javier-varez/Postform) (Postponed formatting experiments with string interning in C++, inspired by the defmt Rust crate) - [qpspy](https://www.state-machine.com/qtools/qpspy.html) (C/C++) - [Real-time binary data logging/tracing toolkit](https://github.com/RTEdbg/RTEdbg) - SEGGER RTT - [SEGGER System View](https://www.segger.com/products/development-tools/systemview/technology/what-is-systemview/) - [Serial-Studio](https://github.com/Serial-Studio/Serial-Studio) (Data visualisation) - [Tonbandgerät](https://github.com/schilkp/Tonbandgeraet) (Small embedded systems tracer with support for bare-metal and FreeRTOS-based targets) - [Traces](https://github.com/yotamr/traces) (API tracing framework for Linux C/C++ applications) - [uLog (RD Poor)](https://github.com/rdpoor/ulog) - [Zephyr Dictionary Based Logging](https://docs.zephyrproject.org/latest/services/logging/#dictionary-based-logging) - project-specific binary loggers. Additional generated comparison material: - [Trice Compare (generated 2025-05-26)](https://htmlpreview.github.io/?https://github.com/rokath/trice/blob/main/docs/ChatGPTo4-mini-high_TriceCompare.html) - compact generated comparison with related tools - [Logging & Tracing Solutions for Embedded Systems (generated 2026-02-16)](https://htmlpreview.github.io/?https://github.com/rokath/trice/blob/main/docs/2026-02-16_ChatGPT5.2ProExtThinking_embedded_logging_tracing_comparison_trice_focus.html) - longer generated overview of the broader tool landscape

(back to top)