# Trice: printf-style tracing for embedded C that survives interrupt handlers

> Trice is a tracing system for embedded C and C++ where a log call costs a handful of CPU clocks, the records are compact binary, and the calls work from inside an interrupt handler, with a Go-based host side that reads the device and turns the stream into text, structured values or live plots. The target library is not compiler-bound and keeps numeric IDs out of your source.

**rokath/trice** — 🟢 Super-fast, tiny C/C++ tracing with printf look-and-feel, even in interrupts ⚡, plus real-time PC logging 💻

- Repository: https://github.com/rokath/trice
- Website: https://rokath.github.io/trice/
- Stars: 1,008 · Forks: 107
- Language: Go
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/rokath-trice

## What the project is, and the name

Trice is a tracing and logging system for embedded C and C++. The README headline is a promise about cost: log and trace in as few as 6 CPU clocks per call, with printf-like calls, compact records, and the ability to be used even from interrupt handlers. It adds compiler-independent target code, ID-free user code, and aliases for legacy code.

The name is worth a sentence because it explains the design. The name combines TRace IDs, C and Embedded systems. Trace IDs is the central mechanism, and the rest of the system follows from it.

The audience is firmware developers who need to see what the code is doing while it runs, on hardware where a logging library with printf and a mutex is not an option. The README frames the goal in terms of specific moments: a fleeting interrupt, a changing sensor value, or the response to a command. Trice brings them into view while the firmware runs, and the Guide is described as helping you choose a first experiment, discover the features and find the details for your own project, starting on a PC and then moving to hardware.

That last point is significant in practice. The project is designed so you can develop the tracing setup on your desktop, with the same tooling, before flashing anything. The repository has a demo/ directory with deferred/, direct/ and live/ sub-directories, and an examples/ directory, so the development loop is a supported path rather than an accident.

The primary language of the repository is listed as Go, which reflects the split: the target-side library is C, and the host-side tooling that reads the device, formats the output and drives the visualisation is Go. The licence is MIT, the homepage is a documentation site at rokath.github.io/trice, and the release history shows v1.3.0 in July 2026 with v1.2.x before it, the last push being on 2026-10-15.

## Six CPU clocks, and what that claim does and does not cover

The speed claim is the headline, and the README footnote attached to it is where the precision is.

The footnote says: record creation in an optimized configuration; transmission is handled separately. See the measurement and configuration documentation.

That distinction matters more than it first appears, because tracing systems fail in two different ways. A system that is slow to create a record blocks the code that is being traced, which means the instrumentation changes the behaviour you are trying to observe, which is the oldest problem in this field. A system that creates records quickly but cannot move them off the device fills a buffer, and then the oldest data is lost, which is a different failure with a different fix.

Trice splits the two. The target-side cost is a few CPU clocks to make a record, and the record is queued for later transmission. That is why the target library can be used inside an interrupt handler, which the README calls out as a separate feature: Trice calls can be used directly inside interrupt handlers, and the example output shows interrupt-originated ISR: messages mixed with events from normal application code.

That mixing is the point of the whole design. The alternative to a fast, interrupt-safe logger is to disable interrupts around the log call, which changes the timing of the very thing you are measuring, or to accept that the interrupt path is invisible, which is usually the one path you most need to see.

The README is careful to attach the number to a configuration, an optimized one, and to point at a separate document for the measurement method and the configuration knobs. That is the right way to state a performance claim: here is the number, here are the conditions, and here is where the conditions are documented. A reader who wants to rely on it can check whether their configuration matches; a reader who does not can still see the shape of the design.

The topics list gives the rest of the vocabulary: real-time, rpc, structured-logging, time-measurement, encryption, data-recording, rtt, interrupts, rtos, bare-metal, arm, embedded, instrumentation and logging. That is a fairly complete map of what a firmware tracing system ends up needing.

## Bind, and why the numeric IDs stay out of your source

The footnote on ID-free user code points to the Bind documentation, and this is the part of the design that decides whether Trice is pleasant or painful to use.

The problem it solves is mundane and universal. If a log call carries a numeric identifier, the source reads like `TRICE_LOG(42, 'value = %d', x)`, and the mapping from 42 to value is somewhere else, probably in a file. Every new log statement needs a coordinated edit of two places, and the identifiers drift out of sync with the code. It is the same class of problem as an error code table, and it is a permanent tax on the developer.

Bind is the tool that keeps the numbers out of the source. The README says Bind keeps numeric IDs out of user log calls, and points to its supported constructs and compiler requirements. The mechanism, given the dependency on a Go program in the repository, cmd/ and the script trice_bindIDs_in_examples_and_test_folder.sh, is that the tool reads your source and the compiler's output to assign the IDs, then the IDs are compiled into a table the target library uses at runtime. Your call sites stay readable, and the mapping is generated.

The cost is a build step and a compiler dependency, and the documentation names the limits explicitly, with a link to a section called bind limits. That is a good sign. A tool that generated IDs for arbitrary source without documenting where it stopped working would be a trap, and a link to a limits section is a promise that the author knows where the edges are.

The third footnote is about legacy code. Aliases for existing logging and assert macros means you can adapt an existing codebase to Trice without rewriting every log statement, which is the difference between a tool that gets adopted on a five-year-old firmware project and one that does not. The documentation covers the legacy user code option, and the topic list includes instrumentation, which is what a legacy adaptation exercise actually is.

The compiler independence claim points at portability and modularity documentation. Since Trice targets bare-metal and RTOS environments, the target code has to compile with whatever toolchain your MCU vendor ships, and it has to be modular enough to be left out of a production build entirely.

## The output side: text, structured values, and live plots

The README describes three ways of consuming what the target produces, and they are not variations on the same thing.

The first is text output with event boundaries. The footnote points to the structured logging documentation for output formats and event boundaries, and the key phrase is boundaries. A trace stream where you cannot tell where one event ends and the next begins is a trace stream you cannot read, because the variable-length part of a record has no framing. So the event boundary is a property of the format, and the documentation treats it as such.

The second is structured logging with named values, which is the difference between a log line you can grep and one a program can consume. The demonstration in the demo animation shows coloured output with source locations and timestamps, which is the target-side experience: the developer sees which file and line a value came from, which means the Bind step has to preserve that information through to the host side.

The third is live visualisation with the LabPlot demo, documented in the command line interface section. This is where the tool moves from debugging to analysis. A sensor value that changes over time is a curve, and a curve is not something a terminal can show you well, so the project integrates with a plotting tool for it.

The repository layout shows the split clearly. There is a cmd/ directory for the Go host tooling, an internal/ directory, a pkg/ directory, a src/ directory for the target-side C, a docs/ directory with the reference material the footnotes link to, and a third_party/ directory. The demo/ directory has deferred/, direct/ and live/ sub-directories, which look like the three output modes in directory form, and the _test/ directory with an underscore prefix suggests the target-side test harness is kept separate from the Go module's own test layout.

The Go module dependencies also tell you about the transport. go.bug.st/serial is the serial port library, golang.org/x/crypto is present, and the topic list includes encryption, so the host side speaks to the device over a serial link and the documentation covers what happens to the data on that link.

The target-side protocol is compact binary records, which the README links to a section on minimal transfer bytes amount, and the compactness is not a vanity goal. On an MCU with a 115200 baud serial port, a text line costs more bytes than a measurement, and a plot of a thousand samples is a bandwidth question before it is a rendering question.

## The host side: commands, responses and the Go tooling

A tracing system is two programs, and the second one does more than print.

The README references asynchronous broadcast commands and responses, in a document of that name, and the footnote about commands describes them as the mechanism for interacting with the running firmware. The idea is that the target is not just a passive source of records. A command is sent down the same link, the firmware reacts, and the reaction comes back as events. That is what turns a logger into an instrument, and it is why the topic list includes rpc.

The Go module's dependencies support that reading. fsnotify watches the file system, which is how the host knows when a build changed and a rerun is needed. afero provides a filesystem abstraction, which lets the tooling run against a test filesystem. go-gitignore is there to decide which paths matter, and mgutz/ansi provides terminal colouring. github.com/rokath/cobs and github.com/rokath/tcobs are the author's own Cobs and T-Cobs packages, and the second is a dependency of the first, so the compact binary record format has its own versioned implementation.

The Go side is versioned separately from the target library, and the two have different jobs. The go.mod requires go 1.25.0, and the tool is built with goreleaser, which is why there is a .goreleaser.yaml. The CHANGELOG.md, SECURITY.md, AGENTS.md and CONTRIBUTING.md at the root are the working files of a project with real process around it, and the .code_snippets/ directory plus the .idea/ and .vscode/ directories suggest it is developed as an editor-assisted workflow rather than a bare script.

The Go side also carries the tooling that keeps the target side honest. The script trice_bindIDs_in_examples_and_test_folder.sh suggests that the ID binding is run over the example and test trees automatically, which is how you keep the ID tables consistent across a large project. The lychee.toml is a link checker configuration, and the .markdownlint.yaml is a lint configuration for the documentation, which is consistent with a project that treats its docs as part of the product rather than an afterthought.

## Where Trice is the wrong tool

There are three limits, and the first is about where the code runs.

The first is that this is firmware tooling. The whole cost model, the interrupt safety, the compact binary records, the serial transport and the Bind tool exist because the target is a microcontroller. If your code runs on a hosted runtime, on a server, or in a browser, you have a logging library already, and the nearest equivalent of Trice's cost is a format string and a structured logger. The 6 CPU clock claim is not a reason to adopt it off-target; it is a reason to adopt it on target.

The second is the toolchain coupling. Bind requires reading your compiler's output, and the documentation has a link to a section about the supported constructs and compiler requirements, and another on bind limits. A build that depends on a code generator which parses compiler output is a build with an extra failure mode, and the set of supported compilers and versions is not something the README enumerates. Teams on an unusual toolchain, or on a compiler version that the tool has not been updated for, will discover this at build time rather than at design time.

The third is the link budget. A trace system has to move data off the device, and the compact binary record is the answer to that, but no encoding makes a thousand samples per second fit on a slow serial link. The tool's own answer is the LabPlot integration for offline analysis and the live mode for the cases where the bandwidth is there. The decision for an adopter is which measurements you can afford to record at the rate you need, and the README does not give you a per-record size to compute that with, so you have to measure it.

There is a fourth point about scope. The feature list includes encryption and RPC, and both are meaningful additions to a link between a device and a host, but they are also features with a security surface. The link is a serial port on a device you own, and the threat model depends on how the device is physically deployed. The repository carries a SECURITY.md, which is a good sign, and the encryption documentation exists, but the point stands that a device logger added to a firmware image is a component that ships with your product and has to be reviewed like one.

## Conclusion

Adopt Trice if you are writing firmware for an MCU and you need instrumentation that survives an interrupt context, streams over a serial link, and can be read as text or as live plots without writing a logging framework. Do not adopt it if you are on a hosted runtime with a logging library already, since the whole value is in the target-side cost. Verify first that the Bind tool can process your compiler's output on your platform, that the target has the serial bandwidth for the record rate you will produce, and that the encryption and RPC features in the documentation cover what you need without adding a dependency you cannot audit.

## FAQ

### What is Trice and what is it used for?

Trice is a tracing and logging system for embedded C and C++ that logs and traces in as few as 6 CPU clocks per call, with printf-like calls, compact binary records, and support for use directly inside interrupt handlers. The name combines TRace IDs, C and Embedded systems.

### How does the Bind tool work in Trice?

Bind keeps the numeric IDs out of your user log calls by generating the identifier mapping from your source and the compiler's output, so the call sites stay readable while the runtime table is produced by the tool. The README links its documentation for the supported constructs, the compiler requirements and a section on bind limits.

### What output formats does Trice support?

Three, according to the README: text output with event boundaries, structured logging with named values, and live visualisation through the LabPlot demo. The target side produces compact binary records, and the Go host tooling decodes them into whichever of these you ask for.

### Can Trice be used in existing codebases with a different logging library?

The README lists aliases for existing logging and assert macros among the features, with a dedicated document on the legacy user code option. That is the path for adapting an existing firmware codebase without rewriting every log statement.

## Sources

- [License: MIT](https://github.com/rokath/trice/blob/main/LICENSE)
- [Project website](https://rokath.github.io/trice/)
- [README](https://github.com/rokath/trice/blob/main/README.md)
- [Releases](https://github.com/rokath/trice/releases)
- [rokath/trice on GitHub](https://github.com/rokath/trice)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/rokath-trice
