Getting Started with Trice
Contents
- 1. Get it
- 2. Install It
- 3. Try it
- 4. Use It
- 5. Fork It (get a contributor)
- 6. Clone It
- 7. Build It
- 8. Modify It
- 9. Port it
- 10. Avoid it
1. Get it
- Download latest release assets for your system: Compressed source code and binaries.
- OR Get the repo:
- Create a GitHub Account
- Create SSH key pair inside
~/.ssh/:ssh-keygen -t ed25519 - Add content of
~/.ssh/id_ed25519.pubas SSH key to GitHub. - Execute
git clone git@github.com:rokath/trice.gitto get the trice repository.
- OR use the
button
2. Install It
- Place the extracted Trice binary somewhere in your PATH.
- Copy the src folder into your project and add all files.
- Copy a triceConfig.h from a subfolder in the examples or test folder and optionally adapt it. See file triceDefaultConfig.h for help.
- Inside the triceConfig.h file you can control, if Trice works in direct or deferred mode or both parallel.
3. Try it
- Create a file
tryTrice.cand write in it:
#include "trice.h"
int tryIt( void ){
trice( "Hello! 👋🙂\a\n" ); // A message with sound and without target timestamp.
}
You can also edit any of your existing project files accordingly. Just replace any printf with trice. (Handle float or double numbers and runtime-generated strings, according to Trice Similarities and Differences to printf Usage. The file _test/testdata/triceCheck.c shows many usage examples.
The uppercase Trice macros are inlining the complete Trice code and the lowercase Trice macros are function calls, so most probably you want use trice to keep the overall code size smaller.
For new projects, use Bind:
touch til.json li.json # Only on the first setup; retain these files afterward.
trice bind -src . -genDir build/sidecars # Also creates the generated directory.
Add your configuration and build/sidecars to the compiler include path, followed by src and src/default_conf. Run Bind before compilation whenever log sources change. Bind adds generated includes; your Trice statements keep their original form. Keep til.json in version control and retain the local li.json; generated sidecars are build artifacts. The F030 RTT and UART examples show the setup.
The legacy Insert/Clean workflow remains available:
- Create 2 empty files
til.jsonandli.jsonin your project root, if they do not exist yet. - Run
trice insertand the trice code line changes totrice( iD(1234), "Hello! 👋🙂\a\n" );. - The 2 JSON files are now filled with information.
- Run
trice cleanand the trice code line changes back totrice( "Hello! 👋🙂\a\n" );.
With Insert/Clean, use trice insert before compilation and optionally trice clean afterward to remove inline IDs. Choose one workflow for a source tree; do not run Insert over Bind-owned files. See switching workflows.
The optional Trice cache technique avoids un-edited file changes at all, which means no Trice-related build speed disadvantages.
See Trice Cache for Compilation Speed for more details and examples/G1B1_inst/build.sh as example.
- Or, use
trice insertin a post-checkout andtrice cleanin a pre-check-in script to keep just the repository clean of Trice IDs. Using onlytrice insertas pre-compile step is possible too, especially when the code is used just in a single project and you wish to have it as compiled. - When using Trice in libraries for several projects, it may make sense to check-in the libraries with IDs and to use a dedicated ID space for them. See ../_test/testdata/triceCheck.c as an example - especially when building several projects parallel like shown in the examples folder.
A quick setup is possible when using RTT as output channel. Otherwise you need to setup a serial port for Trice data transmission. Other output paths possible too using the auxiliary interface.
4. Use It
- In a console, like git bash, type
trice help -all. You should see the complete Trice tool CLI documentation.- Do not worry, most of it you will never need.
- Start with
trice bindto prepare compilation andtlogto read messages.trice logremains available too.trice help -bind,trice help -insertandtlog -hshow focused help.-
Examples:
CLI command Description touch til.json li.jsonInitialize the metadata files once; keep existing files. trice bind -src . -src ../myLib -genDir build/sidecarsPrepare Bind headers for your project and library. trice i -src . -src ../myLibLegacy alternative: insert inline IDs into your project and library sources. … Compile your project trice c -src . -src ../myLibOptionally restore the current and your ../myLibfolder. This will read|extend|modify./til.jsonand use & create the./li.jsonfile.trice l -p com1 -baud 921600 -lf my/path/autoStart Logging over UART and create automatically a new log file in my/path/.cat filename.logView a recorded log file. trice l -p JLINK -args "..."Start Logging over RTT. Binary log files are collected in ./temp.trice l -p FILEBUFFER -args logfile.binPlay a recorded binary log file. - Add
trice bind ...as a dependency of compilation. With the legacy workflow, usetrice insert ...instead. - For Insert/Clean only, an optional
trice clean ...post-compile step removes inline IDs. The-cacheswitch avoids unnecessary source rewrites. See Trice Cache for Compilation Speed.
- The command
tricedoes not make any assumptions about the target processor - 8-bit to 64-bit, supports little and big endianness. - The command
triceis compiler agnostic - it should work with any compiler. - The VS Code editor is free to download and use, like shown in the
examples/F030_instproject.- Even if you do not have such hardware, you can compile the
examples/F030_instproject just to get started. - After changing Trice calls in examples/F030_inst/Core/Src/main.c,
build.shrefreshes Bind headers automatically. IDs live in those headers rather than in your statements.
- Even if you do not have such hardware, you can compile the
- The examples and test subfolders contain several VS Code Makefile projects and they are also usable as starting points for your configuration.
- You can use Trice calls also inside header files but when running
trice insertas pre- andtrice cleanas post-compile step, all files including these headers will be re-compiled every time, what may be too time consuming. Enable the Trice cache then. See Trice Cache for Compilation Speed for more information.
5. Fork It (get a contributor)
If you wish to get a contributor please fork the Trice repository.
5.1. ✅ What “forking” means
Forking creates your own copy of someone else’s repository under your account.
You can then:
- freely make changes,
- push commits to your fork,
- and later submit a pull request to propose changes back to the original repo.
5.2. 🧭 How to Fork (GitHub)
1. Go to the repository you want to fork
Example: https://github.com/rokath/trice
2. Click the **“Fork” button (top-right)**
You’ll be taken to a Create Fork page.
3. Choose options (usually leave defaults)
- Owner → your GitHub account
- Repository name → auto-filled
- Optional: copy only the default branch
Click Create Fork.
4. Clone your fork locally
git clone https://github.com/YOUR_USERNAME/trice.git && cd trice
5. (Optional but recommended) Add the original repo as upstream
This lets you pull updates later.
git remote add upstream https://github.com/rokath/trice.git
Check remotes:
git remote -v
6. Keep your fork updated
git fetch upstream git merge upstream/main
Or:
git pull upstream main
6. Clone It
1. Make sure Git is installed
Check with:
git --version
If not installed, download from https://git-scm.com
2. Clone the repository
Run this command in your terminal or command prompt:
git clone https://github.com/rokath/trice.git
This creates a local folder named trice with the full project history.
3. (Optional) Enter the project folder
cd trice
7. Build It
See Build Trice tool from Go sources.
8. Modify It
If for example you wish to change the logging capabilities, like changing/extending CLI switches, thanks to Go this is very easy also if you are not familiar with Go. See this example.
9. Port it
Trice should be usable on any MCU with any compiler. On ARM MCUs the easiest way is to use SEGGER J-Link with RTT as output. Setting up UART transmission as alternative or additionally is also no big deal.
Compare folders of one of these folder groups:
| Without Instrumentation | With Trice Instrumentation | Remarks |
|---|---|---|
./examples/F030_bare |
./examples/F030_inst |
no RTOS |
./examples/G0B1_bare |
./examples/G0B1_inst |
FreeRTOS |
./examples/L432_bare |
./examples/L432_inst |
FreeRTOS |
This way you see in a quick way any needed adaptations for your target project to port trice to it.
The chapter Example Projects without and with Trice Instrumentation contains further helpful information.
9.1. Target Macros
The easiest and mostly sufficient way to use Trice on the target side is the Trice macro
trice("Hello world!"); // without timestamp
Trice("Hello world!"); // with 16-bit timestamp
TRice("Hello world!"); // with 32-bit timestamp
which you can mostly use as a printf replacement in legacy code. See Trice Similarities and differences to printf usage for more details. Is uses the TRICE_DEFAULT_PARAMETER_BIT_WIDTH value (usually 32), which is equal for all values.
The additional macros
trice8,trice16,trice32,trice64Trice8,Trice16,Trice32,Trice64TRice8,TRice16,TRice32,TRice64
are always usable and the number 8, 16, 32, 64 specifies the parameter width, which is equal for all values within one macro. Trice macros are partially disabled, when the value TRICE_SINGLE_MAX_SIZE is defined to be smaller than 104. For example with TRICE_SINGLE_MAX_SIZE == 8, TRice32 can have no parameter value (4 byte Trice header, 4 byte stamp) and trice8 can have up to 4 parameter values (4 byte Trice header, 4 byte values) That’s mainly to get compiler errors rather than runtime errors.
More examples:
| Trice | Header | Stamp | max. Values | Trice Size |
|---|---|---|---|---|
trice8 |
4 | 0 | 0 *1 byte | 4 |
| … | … | … | … | … |
trice8 |
4 | 0 | 12 *1 byte | 16 |
Trice8 |
4 | 2 | 0 *1 byte | 6 |
| … | … | … | … | … |
Trice8 |
4 | 2 | 12 *1 byte | 18 |
TRice8 |
4 | 4 | 0 *1 byte | 8 |
| … | … | … | … | … |
TRice8 |
4 | 4 | 12 *1 byte | 20 |
trice16 |
4 | 0 | 2 *2 byte | 8 |
Trice16 |
4 | 2 | 1 *2 byte | 8 |
trice32 |
4 | 0 | 1 *4 byte | 8 |
Trice32 |
4 | 2 | 1 *4 byte | 10 |
TRice32 |
4 | 4 | 2 *4 byte | 16 |
trice64 |
4 | 0 | 1 *8 byte | 12 |
TRice64 |
4 | 4 | 1 *8 byte | 16 |
| … | … | … | … | … |
TRice64 |
4 | 4 | 12 *8 byte | 104 |
The value TRICE_DEFAULT_PARAMETER_BIT_WIDTH is the parameter bit with for the macros trice, Trice, TRice (without number). It can make sense to set this value to 16 on smaller machines.
The full uppercase macro Trice is a Trice macro only using inline code. Because the main design aim was speed, this was the original design. Then it became clear, that several hundred of Trice macros increase the needed code amount too much and that it is better to have just a function call instead of having inline macros. If speed matters use TRICE(id(0), TRICE(Id(0), TRICE(ID(0) else use trice(iD(0), Trice(iD(0), TRice(iD(0) or mix usage as you like. The lower case macros internally use Trice like code but each is only a function call and therefore needs less space.
9.2. Target Trice Stamps
-
If you wish to have your Trice messages stamped, most probably time stamped, add the 2 hardware specific macros/functions to your project (example in ./examples/F030_inst/Core/Inc/triceConfig.h and ./examples/F030_inst/Core/Src/stm32f0xx_it.c ). The time base is in your hands and is allowed to be different for the 16-bit and 32-bit stamps. Example:
//! ms32 is a 32-bit millisecond counter, counting circularly in steps of 1 every ms. extern uint32_t ms32; #define TriceStamp16 (SysTick->VAL) // Counts from 31999 -> 0 in each ms. #define TriceStamp32 ms32 - In the code snippet above the 32-bit timestamp is used for milliseconds and the 16.bit timestamp is used as clock counter what allows fine grained time measurements.
-
In the screenshot below, the 16-bit timestamp is a parallel counter running between 0-9999 milliseconds, which allows 16-bit timestamps all the time and only every 10 seconds is a full 32-bit timestamp needed.

- The trice tool
-ts*CLI switches allow customization. With-hs offhost time stamps are suppressed. - It is also possible to use the stamp option not for time stamps but for any values, like addresses or a voltage or a random number.
Hint: I usually have the 32-bit timestamp as millisecond counter and the 16-bit timestamp as systick counter to measure short execution times.
9.3. Trice Checks
- Optionally copy parts of ./_test/testdata/triceCheck.c to your project if you wish to perform some checks.
- Do not include this file directly, because it could get changed when
updateTestData.shis executed inside the./testfolder. - The only-uppercase
TRICE*macros include trice code sequences what can lead to a significant code amount if you use plenty of them, whereas the lowercase macrostrice,Trice,TRiceand their relatives are just function calls and better suited to be used normally.
- Do not include this file directly, because it could get changed when
- In your source files add line
#include "trice.h"at the top. - In a function write a trice message like:
TRice( "1/11 = %g\n", aFloat( 1.0/11 ) );. - In project root:
- Create empty file:
touch til.json. trice insertshould perform automatically the following things (The numbers are just examples.):- Patch source.c to
TRice( iD(12363), "1/11 = %g\n", aFloat( 1.0/11 ) );- C & H files containing Trice macros, are only modified if needed (missing IDs or changed format strings).
- Extend
til.json- If no
til.jsonis found nothing happens. At least an empty file is needed (Safety feature).
- If no
- Patch source.c to
- Create empty file:
- When the program runs later, it should output something similar to
- Look into Trice Similarities and differences to printf usage for options.
- Read chapter Trice Project Image Size Optimization if needed.
9.4. Communication Ports
- For RTT the SEGGER source is already included. See Trice over RTT for more info.
- If RTT is used, no hardware-specific adaptations needed and it is the fastest possible data transfer. But you cannot use it in the field usually.
- The direct trice mode is recommended for RTT. The single trice execution is a bit longer then, but the log is completely done in one shot. It takes about 100-150 processor clocks, aka 1-2 microseconds.
- Info: All deferred trice modes are faster in the runtime execution but the Trice logs appear slightly delayed. You can tune the Trices down to only 3 Assembler instructions executable within 6 processor clocks. See Trice Speed as example.
- For UART transfer add UART write functionality. The deferred mode is recommended for UART transfer.
- It is possible to log over several channels parallel and to select an ID range for each tag.
- An additional device, like local file, GPIO pin or SPI, is possible by providing an appropriate write functionality.
- See also Trice without UART.
9.5. Target Code Overview
./src: User Interface
| File | description |
|---|---|
| trice.h | trice runtime lib user interface, #include trice.h in project files, where to use Trice macros. Add ./src to your compiler include path. |
triceConfig.h |
Create this file to overwrite triceDefaultConfig.h as needed. |
./src: Internal Components (only partially needed, add all to your project - the configuration selects automatically)
See Trice Project Image Size Optimization for compiling src/*.c and removing unused library sections from the final image.
| File | description |
|---|---|
| cobs.h | message packaging, alternatively for tcobs |
| cobsEncode.c | message encoding, alternatively for tcobs |
| cobsDecode.c | message decoding, normally not needed |
| trice.c | trice core lib |
| trice8McuOrder.h | trice MCU endianness lib |
| trice8McuReverse.h | trice MCU reverse endianness lib |
| trice16McuOrder.h | trice MCU endianness lib |
| trice16McuReverse.h | trice MCU reverse endianness lib |
| trice32McuOrder.h | trice MCU endianness lib |
| trice32McuReverse.h | trice MCU reverse endianness lib |
| trice64McuOrder.h | trice MCU endianness lib |
| trice64McuReverse.h | trice MCU reverse endianness lib |
| SEGGER_RTT.h | Segger RTT code interface |
| SEGGER_RTT.c | Segger RTT code |
| SEGGER_RTT_ConfDefaults.h | SEGGER defaults for settings not supplied by the project |
| SEGGER_RTT_Conf.h | Small Trice fallback configuration; project headers take precedence |
| SEGGER_RTT_LICENSE.md | SEGGER license to retain when redistributing its target sources |
| tcobs.h | message compression and packaging interface |
| tcobsv1Encode.c | message encoding and packaging |
| tcobsv1Decode.c | message decoding and packaging, normally not needed |
| tcobsv1Internal.h | message decoding and packaging internal interface |
| trice8.h | 8-bit trice code interface |
| trice8.c | 8-bit trice code |
| trice16.h | 16-bit trice code interface |
| trice16.c | 16-bit trice code |
| trice32.h | 32-bit trice code interface |
| trice32.c | 32-bit trice code |
| trice64.h | 64-bit trice code interface |
| trice64.c | 64-bit trice code |
| triceAuxiliary.c | trice code for auxiliary interfaces |
| triceDefaultConfig.h | This file contains the most probably settings and serves also as a reference for tuning your project triceConfig.h |
| triceDoubleBuffer.c | trice runtime lib extension needed for fastest deferred mode |
| triceStackBuffer.c | trice runtime lib extension needed for direct mode |
| triceRingBuffer.c | trice runtime lib extension needed for recommended deferred mode |
| xtea.h | XTEA message encryption/decryption interface |
| xtea.c | XTEA message encryption/decryption code |
- The tcobs*.* files are copied from tcobs v1. They are maintained there and extensively tested and probably not a matter of significant change.
- The SEGGER files are copied and you could check for a newer version at https://www.segger.com/downloads/jlink/.
9.6. User Code Adaptation
- Replace all strings
putswith the stringtrice, when the string follows immediately. For runtime generated strings seetriceS. - Replace all strings
printfwith the stringtrice, when the format string follows immediately. -
Check for float and double format specifiers in the format strings. The appropriate parameters need to be covered with
aFloat()oraDouble(). Example:printf( "%d, %3.2f EUR, %g rate\n", i, price, change );trice64( "%d, %3.2f EUR, %g rate\n", i, aFloat(price), aDouble(change) );- Because double needs 8 bytes the trice macro in this case needs to be trice64 (see Trice Parameter Bit Widths).
-
Check for string format specifiers in the format strings. Put each in a separate trice message. Example:
printf( "name: %16s, surname: %32s, birthday: %4u-%02u-%02u\n", n, s, y, m, d);trice( "name: %16s, ", n); trice( "surname: %32s, ", s ); trice( "birthday: %4u-%02u-%02u\n", y, m, d);
The Trice macros are designed for maximal execution speed and therefore we have to pay the price for their limited capabilities.
-
Optionally add tags to get color. Example:
puts( "A message");trice( "msg:A message"); -
Add
#include trice.hto all user files using trice.
9.7. Limitations
- The maximum parameter count per trice is 12. Buffer macros such as
triceBaccept more data, but limit the payload toTRICE_SINGLE_MAX_SIZE - 12bytes: 92 bytes with the default size of 104, or at most 32752 bytes with the largest supported size of 32764. See Binary Encoding for the format limit and buffer allowance. - Each trice must fit into a single line in trice versions before v0.61.0.
-
Not ok before v0.61.0 but ok for later versions:
trice( "hello %u\n", year);
-
- But several trices can be in one line.
-
OK:
trice( "hello %u\n", year); trice( "good time");
-
- Strings directly as parameter are possible now.
-
OK from v0.61.0 with
trice insertandtrice clean:triceS( "hello %s\n", "world" ); -
OK always:
s = "world"; TRICE_S( "hello %s\n", s ); #define WORLD "world" triceS( "hello %s\n", WORLD );
-
You should be aware that these parameter strings go into the target and slow down the execution. So, whenever a string is known at compile time it should be part of the Trice format string.
The Trice source code parser has very limited capabilities, so it cannot handle C-preprocessor string concatenation.
- Excluded trices are seen by the trice insert process.
-
Example: The following code will be patched and get an ID as well:
// trice( "Hi!" );
-
-
All parameters inside one trice have the same bit width. If for example there are a single double and 10 bytes values, the needed trice macro is
trice64providing 8 bytes space for all parameter values, therefore increasing the transmit overhead. With the default TCOBS framing the overhead is marginal because of the compression. Also this can be handled by splitting into 2 trices:// 92 bytes: 4 bytes header plus 11 times 8 bytes trice64( "%g: %c%c%c%c%c%c%c%c%c%c", aDouble(3.14159), 61, 62, 63, 64, 65, 66, 67, 68, 69, 10 ); // 24 bytes: 4 bytes header plus 1 times 8 bytes plus 4 bytes header plus 8 times 1 byte trice64( "%g: ", aDouble(3.14159)); trice8( "%c%c%c%c%c%c%c%c%c%c", 61, 62, 63, 64, 65, 66, 67, 68, 69, 10 ); - See also Avoid it.
9.8. Trice (Time) Stamps
- Trice messages can have no or 16-bit or 32-bit (time) stamps.
-
recommended (function calling) syntax:
trice( "hello %u\n", year); // no (time) stamp Trice( "hello %u\n", year); // 16-bit (time) stamp TRice( "hello %u\n", year); // 32-bit (time) stamp -
legacy (inlining) syntax (usable for fastest execution):
TRICE( id(0), "hello %u\n", year); // no (time) stamp TRICE( Id(0), "hello %u\n", year); // 16-bit (time) stamp TRICE( ID(0), "hello %u\n", year); // 32-bit (time) stamp
-
9.9. Trice Parameter Bit Widths
- The macros
trice,Trice,TRiceandTRICEuse 32-bit parameter values per default. SeeTRICE_DEFAULT_PARAMETER_BIT_WIDTHinside src/triceDefaultConfig.h to change that. -
If for example the bit width of all trice parameters is 8-bit, it is writable as trice8 macro, reducing the transmitted byte count per parameter from 4 to 1:
char b[8] = {1,2,3,4,5,6,7,8}; // 36 bytes: 4 bytes plus 32 (8 times 4) bytes payload trice( "%02x %02x %02x %02x %02x %02x %02x %02x\n", b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]);` // 12 bytes: 4 bytes plus 8 (8 times 1) bytes payload trice8( " %02x %02x %02x %02x %02x %02x %02x %02x\n", b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]);` // 12 bytes: 4 bytes plus 8 (8 times 1) bytes payload in short notation. triceB( "deb: %02x\n", &b, sizeof(b) );
Hint: With the default TCOBS framing 8-bit values as 32-bit parameters typically occupy only 2-bytes during transmission.
10. Avoid it
10.1. Parser Limitation
Because the implemented source code parser for trice insert and trice clean is only a simple one, there is one important limitation:
- Do not use an unescaped single double quote in source code comments. Example:
trice( "hi 0" );
// An "allowed" example comment.
trice( "hi 1");
// An \" allowed example comment.
trice( "hi 2");
// A " NOT allowed example comment. This disrupts the parsing.
trice( "hi 3");
// A " NOT allowed example comment. This enables the parsing after a disruption.
trice( "hi 4");
- The
trice insertandtrice cleanwill not see thetrice( "hi 3");line here, but the compiler will mark an error then. - See also issue #427, issue #465 and see also Limited Trice Parser Capabilities.
10.2. Trice macros in header files
- There is nothing wrong, when putting trice macros into header files.
- But: When you use
trice insertas pre-build command andtrice cleanas post build command, those header files get touched on each build and therefore all source code files including them will be re-translated every time. - For efficiency avoid that.
- With inventing the Trice Cache this is of no relevance.
10.3. Trice macros inside other macros
There is nothing wrong, when putting Trice macros into other macros. But: When running the self made macro, the location information of the inner trice macro will point to the self made macro definition and not to its execution location.
Example: When Functions fnA and fnB are executed, the MY_MESSAGE location information points to file.h and not into the appropriate lines inside file.c.
file.h:
#define MY_MESSAGE trice("msg:Hi\n"); // self made macro
file.c:
void fnA( void ){
...
MY_MESSAGE
...
}
void fnB( void ){
...
MY_MESSAGE
...
}
10.4. Upper case only TRICE macros should be written with id(0), Id(0) or ID(0)
The stamp size 0, 16 or 32 is usually controlled by writing trice, Trice or TRICE or for upper case only Trice macros by using id(0), Id(0) or ID(0). When writing TRICE("hi"); for example, the Trice CLI switch -defaultStampSize controls the ID insertion, but this is then equal for all new TRICE messages.