Skip to the content.

Trice Reference Manual

+ Speed of Light `printf` Comfort Within Interrupts And Everywhere +
-   (TL;DR)   ->  Too Long; Don't Read - use it as reference only❗

(go to bottom)


Table of Contents

Show/hide Table of Contents

(back to top)


./ref/TriceCheckOutput.gif

(Animated GIFs appear as still images in PDFs.)


1. Abstract

If you develop software for an embedded system, you need some kind of system feedback. Debuggers are awesome tools, but when it comes to analyzing dynamic behavior in the field, they are not usable.

Logging then, usually done with printf-like functions, quickly yields a result after having i.e. putchar() implemented. This turns out to be an expensive way in terms of processor clocks and needed FLASH memory, when you regard the library code and all the strings needing FLASH memory space. For small microcontrollers that’s it.

Bigger microcontrollers are coming with embedded trace hardware. To use it, an expensive tool is needed. Useful for analyzing complex systems, but for in-field related issues at least unhandy.

Unhappy with this situation, the developer starts thinking of using digital pins or starts emitting some proprietary LED blinking codes or byte sequences, difficult to interpret.

The Trice technique tries to fill this gap, being minimal invasive for the target and as comfortable as possible. It is the result of a long-year dissatisfaction and several attempts to find a loophole to make embedded programming more fun and this way more effective.

Trice is an unusual software tracer-logger, using internally IDs instead of format strings to get maximum speed but provides the user with a printf-like comfort:

trice("Hello! 👋🙂");

int a = -4;
float x = 3.14159265;
trice("info:π/%d is %f with the bit pattern %032b\n", a, aFloat(x/a), x );

string s = "world";
triceS("msg:A runtime generated string: %s", s);

Replacing a printf library, the Trice target source code occupies 1-4 KB Flash memory and less than 1 KB RAM depending on the configuration which is done with a user file named triceConfig.h:

#define TRICE_DEFERRED_OUTPUT 1
#define TRICE_BUFFER TRICE_DOUBLE_BUFFER
#define TRICE_DEFERRED_UARTA 1
#define TRICE_UARTA USART2

The open-source Trice PC tool is executable on all Go platforms, at least:

In the future other ports are possible:

./ref/life0.gif

(back to top)

2. A brief history of Trice

Developing firmware means to deal also with interrupts and often with timing. How do you check, if an interrupt occurred? OK, increment a counter and display it in a background loop with some printf-like function. What about time measurement? Set a digital output to 1 and 0 and connect a measurement device. Once, developing software for a real-time image processing device, I had no clue where in detail the processing time exploded when the image quality got bad. A spare analog output with a video interrupt synced oscilloscope gave me the needed information, after I changed the analog output on several points in my algorithm. But, hey guys, I want to deal with my programming tasks and do not like all this hassle connecting wires and steer into instruments.

A printf is so cool on a PC, developing software there. But an embedded device often cannot use it for performance reasons. My very first attempt was writing the format string .const offset together with its values in a FIFO during a log statement and to do the printf it in the background. But that is compiler specific. OK the full string address is better but needs buffer space. Zephyr for example does something like that calling it “deferred logging”.

Then, one day I had the idea to compute short checksums for the format strings in a pre-compile step and to use them as ID in a list together with the format strings. That was a step forward but needed to write a supporting PC program. I did that in C++ in the assumption to get it better done that way. Finally, it worked, but I hated my PC code, as I dislike C++ now because of all its nuts and bolts to handle, accompanied by missing libraries on the next PC. The tool usability was also unhandy and therefore error prone and the need became clear for a full automated solution. Also, what is, if 2 different format strings accidentally generate the same short checksum? There was a way around, but an ID based message filtering will never be possible that way.

The need became clear for controllable IDs and management options. And there was Go now, an as-fast-as-C language, easy to learn, promising high programming efficiency and portability. It would be interesting to try it out on a real PC project.

Trying to add tags in form of partial Trice macro names was blowing up the header code amount and was a too rigid design. Which are the right tags? One lucky day I came to the conclusion to handle tags just as format string parts like "debug:Here we are!\n" and getting rid of them in the target code this way also giving the user freedom to invent any tags.

Another point in the design was the question how to re-sync after data stream interruption, because that happens often during firmware development. Several encodings were tried out and a proprietary escape sequence format and an alternative flexible data format with more ID bits were working reliably but with COBS things got satisfactory. A side result of that trials is the Trice tool option to add different decoders if needed. Now the default Trice message framing is TCOBSv1 which includes short message compression and this way allows very low transmit bandwidths and/or saves storage, when binary Trice data are stored in Flash memory.

There was a learning not to reduce the transmit byte count to an absolute minimum, but to focus more on Trice macro speed and universality. That led to a double buffer on the target side as an alternative to the ring buffer solution. The actual binary encoding, allowing alongside user protocols, is the result of the optional target timestamps and location info some users asked for, keeping the target code as light as possible. Float and double number support was implementable for free because this work is done mainly on the host side.

Trice grew, and as it got usable I decided to make it Open Source to say “Thank You” to the community this way.

Learning that Trice is also a baby girl name, our daughter Ida designed the little girl with the pen symbolizing the Trice macro for recording and the eyeglasses standing for the PC tool Trice visualizing the logs.

./ref/TriceGirlS.png

(back to top)

3. How it works - the main idea

Trice performs no costly printf-like functions on the target at all. The Trice macro, instead, just copies an ID together with the optional values to a buffer and is done. In the minimum case this can happen in 6(six!) processor clocks even with target timestamps included. When running on a 64 MHz clock, light can travel about 30 meters in that time.

To achieve that, a pre-compile step is needed, executing a trice insert command on the PC. This is fast enough not to disturb the build process. The Trice tool parses then the source tree for macros like trice( "msg: %d Kelvin\n", k ); and patches them to trice( iD(12345), "msg: %d Kelvin\n", k );, where 12345 is a generated 14-bit identifier (ID) copied into a Trice ID List. During compilation, the Trice macro is translated to the 12345 ID only, and the optional parameter values. The format string is ignored by the compiler.

The target code is project specific configurable. In direct mode the stack or a static buffer is used as Trice buffer and the Trice macro execution includes optionally the quick COBS encoding and the data transfer. This more straightforward and slower architecture can be interesting for many cases because it is anyway much faster than printf-like functions calls. Especially when using Trice over RTT a single Trice is executable within ~100 processor clocks. See TRICE_DIRECT_SEGGER_RTT_32BIT_WRITE inside triceDefaultConfig.h and look into the examples folder. In deferred mode a service swaps the Trice double buffer or reads the Trice ring buffer periodically, the configured encoding, default is TCOBS, takes part and with the filled buffer the background transfer is triggered. Out buffer and Trice buffer share the same memory for efficiency.

During runtime the PC Trice tool receives all what happened in the last ~100ms as a package from the UART port. The 0x30 0x39 is the ID 12345 and a map lookup delivers the format string “msg: %d Kelvin\n” and also the bit width information. Now the Trice tool can write target timestamp, set msg color and execute printf("%d Kelvin\n", 0x0000000e);


./ref/triceCOBSBlockDiagram.svg

The Trice tool is a background helper giving the developer focus on its programming task. The once generated ID is not changed anymore without need. If for example the format string gets changed into "msg: %d Kelvin!\n", a new ID is inserted automatically and the reference list gets extended. Obsolete IDs are kept inside the Trice ID List for compatibility with older firmware versions. It could be possible, when merging code, an ID is used twice for different format strings. In that case, the ID inside the reference list wins and the additional source gets patched with a new ID. This maybe unwanted patching is avoidable with proper Trice ID management. The reference list should be kept under source code control.

Moreover, using trice i -cache && make && trice c -cache in a build script makes the IDs invisible to the developer reducing the data noise giving more space to focus on the development task. See build.sh as a working example and the Trice Cache chapter for details.

(back to top)

4. Trice Features (Overview)

4.1. No Dynamic Memory Management needed

All internal Buffers are static allocations and usually need only a few Hundred bytes size.

4.2. Open source

Target code and PC tool are open source. The MIT license gives full usage freedom. Users are invited to support the further Trice development.

4.3. Easy-to-use

Making it facile for a user to use Trice was the driving point just to have

Trice understands itself as a silent helper in the background to give the developer more focus on its real task. If, for example, trice log is running and you re-flash the target, there is no need to restart the Trice tool just to load changed decoding tables. When til.json is updated in a pre-build step, the Trice tool automatically reloads it during logging. The location table selected with -li is also reloaded if it was present when logging started.

For example, keep trice log -i til.json -li li.json running while you rebuild with trice bind or trice insert. New IDs, changed structured field names, and updated source locations become available to subsequent records in text, JSON, and KV output. Both ordinary file writes and replacement by a newly generated file are supported. Each save is read after a short settling interval (normally about 100 ms), including saves made shortly after one another. Active visualization rules check new or changed records against their existing rules; a rule already disabled because of an incompatible format stays disabled until the logger is restarted.

If a file is temporarily missing, empty, or invalid JSON while being saved, the logger keeps that table’s last valid contents, reports a warning on stderr, and retries automatically. The two files reload independently; updating them is not a single atomic transaction. A valid JSON object replaces the respective in-memory table completely, including removal of keys no longer in the file. {} intentionally clears a table. Successful reloads are silent unless -v is enabled; reload diagnostics never enter the application logfile or JSON/KV output.

Use -li off to disable location information. If no location file existed at startup, create it before starting the logger to enable its automatic reload. File watching requires an available local directory; if setup fails, the logger warns which path cannot be watched and continues using the loaded data. Restart it after resolving that problem. Reloading dictionaries does not repair a disconnected RTT server or other transport connection, and location information still needs to match the firmware actually running on the target.

The Trice tool comes with many command line switches (trice help -all) for tailoring various needs, but mostly these are not needed. The generated file ref/trice-help-all.txt contains this information as well.

Normal Trice tool usage is:

In this example, the user code gets not polluted with Trice IDs - they exists only during the compilation step and the Trice cache makes this invisible for the user and the build system.

4.4. Small size - using Trice frees FLASH memory

Compared to a printf-library code which occupies 1 to over 20 KB FLASH memory, the Trice code is normally smaller but provides full support.

4.5. Execution speed

Can it get faster than 6 clocks only? Only 3 runtime Assembler instructions per Trice needed in the minimum case! Optional target timestamp, critical sections, cycle counter, diagnostics and overflow protection can consume a few more processor clocks, if enabled, but a Trice is still incomparable fast.

4.6. Robustness

When a Trice data stream is interrupted, the optional COBS or TCOBS encoding allows an immediate re-sync with the next COBS/TCOBS package delimiter byte and a default Trice cycle counter gives a high chance to detect lost Trice messages. See also Versions and Variants Trice Stability.

4.7. Minimal Transfer Bytes Amount

A Trice message is 4 bytes long (2 ID bytes and 2 count bytes) plus optional time stamps and/or values. In conjunction with the compressing TCOBS framing the Trice data stream is as small as possible. Use the -debug switch to see the compressed and framed packages alongside the decompressed ones together with the decoded messages.

To see the encoding for each single message #define TRICE_DEFERRED_TRANSFER_MODE TRICE_SINGLE_PACK_MODE inside the project specific triceConfig.h.

Without -debug CLI switch:

ms@MacBook-Pro G0B1_inst % trice log -p /dev/tty.usbmodem0007722641261 -prefix off -li off -hs off -ts off
...
This is a message without values and without stamp.
...

With -debug CLI switch:

ms@MacBook-Pro G0B1_inst % trice log -p /dev/tty.usbmodem0007722641261 -prefix off -li off -hs off -ts off -debug
...
TCOBSv1: c1 74 e2 23 00 
->TRICE: c1 74 e2 00 
This is a message without values and without stamp.
...

The TCOBS encoding cannot compress in the example above, because the data are too small, but here is a significant compression result shown:

ms@MacBook-Pro G0B1_inst % trice log -p /dev/tty.usbmodem0007722641261 -prefix off -hs off -debug
...
TCOBSv1: b8 76 7b 18 84 fe e1 fd e1 fc e1 fb e1 fa e1 00 
->TRICE: b8 76 7b 18 ff ff ff ff fe ff ff ff fd ff ff ff fc ff ff ff fb ff ff ff fa ff ff ff 
_test/testdata/triceCheck.c   805              value=-1, -2, -3, -4, -5, -6
...

When encryption is active, a compression makes no sense, but the TRICE_MULTI_PACK_MODE can help to reduce the total amount of padding bytes, because each encrypted package must have a multiple of 8 as length.

ms@MacBook-Pro G0B1_inst % trice log -p /dev/tty.usbmodem0007722641261 -prefix off -hs off -pw MySecret -pf cobs -debug    
...
cobs: 21 84 b7 60 8b 21 89 1e e3 07 6d dc d9 2d 6f 59 04 8e 50 8f 24 1c a2 63 2e 3d 4a 57 ef 39 63 01 cb 00 
->TRICE: 84 b7 60 8b 21 89 1e e3 07 6d dc d9 2d 6f 59 04 8e 50 8f 24 1c a2 63 2e 3d 4a 57 ef 39 63 01 cb 
-> DEC:  cc b6 63 01 71 02 ff fe cd 76 72 03 ff fe fd ce f6 64 81 00 00 73 04 ff fe fd fc 00 00 00 00 00 
_test/testdata/triceCheck.c   827        0_355 value=-1, -2
_test/testdata/triceCheck.c   828              value=-1, -2, -3
_test/testdata/triceCheck.c   829    0,033_124 value=-1, -2, -3, -4
cobs: 19 50 70 79 d7 75 6f d7 99 dc d8 ec 06 e1 66 e7 a7 c1 0d 96 85 df 19 25 55 00 
->TRICE: 50 70 79 d7 75 6f d7 99 dc d8 ec 06 e1 66 e7 a7 c1 0d 96 85 df 19 25 55 
-> DEC:  cf b6 64 01 74 05 ff fe fd fc fb d0 76 75 06 ff fe fd fc fb fa 00 00 00 
_test/testdata/triceCheck.c   830        0_356 value=-1, -2, -3, -4, -5
_test/testdata/triceCheck.c   831              value=-1, -2, -3, -4, -5, -6
...

4.8. More comfort than printf-like functions but small differences

Trice is usable also inside interrupts and extended format specifier possibilities give options like binary or bool output. Transmitting runtime generated strings could be a need, so a triceS macro exists supporting the %s format specifier for strings up to 32737 bytes long. It is possible to log float/double numbers using %f and its relatives, but the numbers need to be covered with the fast converter function aFloat(x) or aDouble(y). Also UTF-8 encoded strings are implicitly supported, if you use UTF-8 for the source code. See chapter Trice Similarities and differences to printf usage for more details.

./ref/UTF-8Example.PNG

4.9. Tags, Color and Log Levels

You can label each Trice with a tag specifier to colorize the output. This is free of any runtime costs because the tags are part of the Trice log format strings, which are not compiled into the target. The Trice tool will strip full lowercase tag descriptors from the format string after setting the appropriate color, making it possible to give each message its color.

Loggers use log levels and offer a setting like “log all above INFO” for example. The Trice tags can cover that but can do better: Inside package emitter.ColorChannels in a single file ./internal/emitter/lineTransformerANSI.go all common log levels defined as Trice tags alongside with user tags. The user can adjust this. The Trice tool has the -pick and -ban switches to control the display in detail. Also a -logLevel switch is usable to determine a display threshold as tag position inside ColorChannels.

If an inside-target log selection is needed (routing), the Trice tool can assign each log tag a separate ID range and a target side ID based log selector can control which IDs are transmitted over which output channel. See chapter Trice ID management or type trice help -insert and look for -IDRange.

./ref/COLOR_output.PNG

4.10. Compile Time Enable/Disable Trice Macros on File or Project Level

After debugging code in a file, there is no need to remove or comment out Trice macros. Write a #define TRICE_OFF 1 just before the #include "trice.h" line and all Trice macros in this file are ignored completely by the compiler, but not by the Trice tool. In case of reconstructing the Trice ID List, these no code generating macros are regarded.

#define TRICE_OFF 1 // Disable trice code generation for this file object.
#include "trice.h"

When you wish to build a firmware without any Trice code, it is sufficient to add

C_DEFS += -DTRICE_OFF=1 // Define TRICE_OFF=1 for the whole project.

or similar to your Makefile.

4.11. Target and host timestamps

For each Trice you can have (time) stamps or not:

The optional 16- or 32-bit value then carries the system clock, a millisecond counter, or another event counter configured in the project specific triceConfig.h. The Trice tool will automatically recognize and display the stamps in a mode you can control. If several Trice macros form a single line, the Trice tool only displays the target timestamp of the first Trice macro.

Embedded devices often lack a real-time clock and some scenarios can last for weeks. Therefore the Trice tool precedes each Trice line with a PC timestamp, if not disabled. This is the Trice reception time on the PC, which can be some milliseconds later than the target Trice event.

4.12. Target source code location

Some developers like to see the filename.c and line in front of each log line for quick source location. During trice i a file li.json is generated containing the location information. If trice log finds this file, filename and line number are displayed in front of each log line, otherwise not.

Because software is a matter of change it could happen you get obsolete information this way. Therefore the Trice tool log option -showID exists to display the Trice ID in front of each log line what gives a more reliable way for event localization in some cases. Also you can get it for free, because no target code is needed for that.

4.13. Several target devices in one log output

Several Trice tool instances can run in parallel on one or different PCs. Each Trice tool instance receives Trices from one embedded device. Instead of displaying the log lines, the Trice tool instances can transmit them over TCP/IP (trice l -p COMx -ds) to a Trice tool instance acting as display server (trice ds). The display server can fold these log lines in one output. For each embedded device a separate Trice line prefix and suffix is definable. This allows comparable time measurements in distributed systems.

4.14. Any byte-capable 1-wire connection usable

The usual Trice output device is an UART but also SEGGER-RTT is supported over J-Link or ST-Link devices. Many microcontroller boards can act as Trice bridge to a serial port from any port (Trice without UART).

4.15. Scalability

The various Trice ID management options allow the organization also of bigger software systems. 16383 possible different IDs should match also large projects. Just in case: 16-bit for the ID is a not too hard changeable value.

4.16. Portability and Modularity

The Trice tool is written in the open source language Go and is therefore usable on many platforms. That means the automatic code patching and ID handling side with trice insert.

All C-compilers should be usable to compile the target Trice code and there is no hardware dependency despite the byte transmission. MCUs with 8-bit to 64-bit, little or big endian are supported.

Any user program able to read a JSON file, can receive the documented Trice message format, look-up the ID and perform a printf-like action to translate into log strings. The Trice tool with its log switch is a working example.

Using no framing, COBS or TCOBS packages starting with a package descriptor allows alongside user protocols. The other way around is also implementable: In a user protocol embedded Trice messages.

The Trice tool is expandable with several decoders. So it is possible to implement a minimal Trice encoding, if bandwidth matters heavily and control that with switches.

When less RAM usage is more important the target double buffer is replaceable with a ring buffer. So the user will be able to decide at compile time about that. A ring buffer mode is selectable inside triceConfig.h avoiding any buffer by paying a time toll.

The Trice tool supports many command line switches.

4.17. Optional Trice messages encryption

The encryption opportunity makes it possible to test thoroughly a binary with log output and releasing it without the need to change any bit but to make the log output unreadable for a not authorized person. Implemented is the lightweight XTEA as option, what will be sufficient for many cases. It should be no big deal to add a different algorithm.

4.18. Trice Protection

When using Trice, data are written into buffers. A buffer overflow is impossible with the default configuration #define TRICE_PROTECT 1 by simply ignoring possible overflow causing Trice statements. Those cases are not detectable by the cycle counter evaluation because non-existing Trice data on the embedded system cannot cause cycle errors. Therefore overflow error counters exists, which the user can watch. In ./examples/exampleData/triceLogDiagData.c an option is shown. Of course this buffer overflow protection costs valuable execution time. If you prefer speed over protection, simply write into your project specific triceConfig.h #define TRICE_PROTECT 0.

4.19. Trice Diagnostics

A trice statement produces 4 bytes buffer data plus optional values data. When for example TRice16("Voltage=%u\n", x); is called inside the ms system-tick interrupt every 5th time, 10 bytes data are generated each 5 millisecond. This needs a transfer baudrate of at least 20.000 bit/s. A UART running at 115.200 baud can easily handle that. Anyway after 100 ms, a 200 Bytes buffer is filled and the question arises what is the optimal Trice buffer size. A calculation is error prone, so measuring is better. So configure the buffer sizes bigger than estimated and watch the max depth of their usage. In ./examples/exampleData/triceLogDiagData.c an option is shown. After you optimized your buffer sizes, you can deactivate the Trice diagnostics in your project specific triceConfig.h with #define TRICE_DIAGNOSTICS 0.

4.20. Trice Cache

One may think, automatically cleaning the IDs in the target code with trice c after building and re-inserting them just for the compilation needs file modifications all the time and a permanent rebuild of all files containing Trices will slow down the re-build process. That is true, but by using the Trice cache this is avoidable. Simply one-time create a .trice/cache folder in your home directory and use trice insert -cache and trice clean -cache in your build.sh script. Find more details in chapter Trice Cache for Compilation Speed.

4.21. Avoiding False-Positive Editor Warnings

When the user writes

trice("msg: Hello! 👋🙂\n");

after trice insert this gets

trice(iD(123), "msg: Hello! 👋🙂\n");

and the compiler builds and then with trice clean, this gets again

trice("msg: Hello! 👋🙂\n");

Sophisticated editors may detect the missing ID and warn by underlining the trice command:

x

To avoid this you can add the following line to your project specific triceConfig.h file:

#define TRICE_CLEAN 1

The Trice tool, will change the value to 0 and change it back to 1, when performing the ID insertion and cleaning, when this line occurs inside the triceConfig.h file. This way these false-positive editor warnings are avoidable:

x x

It is recommended to use the Trice cache in conjunction with this to avoid a permanent re-translation of files including Trice code.

TRICE_CLEAN==1 changes all Trice macros into empty ones. It is used only to silence sophisticated editors. In the cleaned state, when the IDs are removed from the files, the editor could underline the Trice macros indicating a false positive.

Do not use TRICE_CLEAN for disabling Trice macros. The triceConfig.h line #define TRICE_CLEAN 0 changes to 1 with every trice clean and to 0 with every trice insert. This line is optional and must not be in a different file. If you want to disable Trice macros use TRICE_OFF.

4.22. Trice Generator

The Trice tool is able to generate colors or code to support various tasks. One interesting option is the Asynchronous Broadcast Command support, allowing ABC usage in a network of embedded devices.

Read chapter Trice ABC - Asynchronous Broadcast Commands or type:

trice help -generate

4.23. Versions and Variants Trice Stability

When developing firmware, we get often different versions and variants in the developing process. When, for example, getting an older device back, it could be, we do not know the flashed firmware version at all. Because the Trice tool adds only IDs and their Trices to the project specific til.json file, the complete development history remains in that file. So connecting an old device to the Trice tool will deliver correct output. Of course the location information will be outdated. But when reading the Trice logs the compiled version should get visible and it is no big deal to get the corresponding li.json from the repository. If not, using the -showID "%6d" Trice log option displays the Trice IDs and you can easily grab the source code file and line.

4.24. Legacy Project Code Integration

When it comes to instrument legacy project with Trice or to integrate legacy project files into a Trice instrumented project different approaches are possible:

  1. Use for user specific log statements a different output channel. No special care has to be taken. This is maybe acceptable in some cases.
  2. Replace user specific log statements with Trice statements using a text processor and adapt the float, double or runtime strings handling manually. This is acceptable for small code amounts and when it is no problem to edit the legacy sources.
  3. Get the legacy output packages before transmitting them, add a 2-byte count in little-endian (0-16383) in front and frame them the same way the trice packages get framed (for example with COBS). This will set the 2 most significant bits to 00 and the Trice tool, can get informed via CLI switch to treat those packages accordingly. The user code containing specific logs will work unchanged together with Trice code over the same output channel.
  4. Take advantage of the new support for dynamic trice and triceS macro aliases (Legacy User Code Option Trice Aliases Adaptation).

(back to top)

5. Start with Trice

5.1. Get it

5.2. Install It

5.3. Try 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.

You can use trice insert as pre- and trice clean as post-compile step, to not spoil your source code with IDs.

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.

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.

5.4. Use It

(back to top)

5.5. Fork It (get a contributor)

If you wish to get a contributor please fork the Trice repository.

5.5.1. ✅ What “forking” means

Forking creates your own copy of someone else’s repository under your account.
You can then:

5.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)

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

5.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

5.7. Build It

See Build Trice tool from Go sources.

5.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.

5.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.

5.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

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.

5.9.2. Target Trice Stamps

Hint: I usually have the 32-bit timestamp as millisecond counter and the 16-bit timestamp as systick counter to measure short execution times.

5.9.3. Trice Checks

5.9.4. Communication Ports

5.9.5. Target Code Overview

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.
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
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

(back to top)

5.9.6. User Code Adaptation

The Trice macros are designed for maximal execution speed and therefore we have to pay the price for their limited capabilities.

5.9.7. Limitations

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.

5.9.8. Trice (Time) Stamps

5.9.9. Trice Parameter Bit Widths

Hint: With the default TCOBS framing 8-bit values as 32-bit parameters typically occupy only 2-bytes during transmission.

5.10. Avoid it

5.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:

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");

5.10.2. Trice macros in header files

5.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
  ...
}

5.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.

(back to top)

6. Quickstarts

6.1. Quickstart: Existing non-blocking byte writer, deferred auxiliary 8-bit

Use this path when your project already has a tested output function, for example:

This is often the most universal first integration because Trice does not need to know your peripheral driver. It only needs a function that accepts a byte buffer and length.

6.1.1. Add Trice target sources

Add the complete src folder to your target project unchanged and add src to the compiler include path. Create a project-specific triceConfig.h in your application include path.

6.1.2. Configure deferred auxiliary 8-bit output

Minimal triceConfig.h starting point:

#ifndef TRICE_CONFIG_H_
#define TRICE_CONFIG_H_

#define TRICE_DEFERRED_OUTPUT 1
#define TRICE_BUFFER TRICE_RING_BUFFER
#define TRICE_DEFERRED_AUXILIARY8 1

/* Adapt these to your target if Trice can run from interrupts or multiple contexts. */
#define TRICE_ENTER_CRITICAL_SECTION {
#define TRICE_LEAVE_CRITICAL_SECTION }

#endif /* TRICE_CONFIG_H_ */

Notes:

6.1.3. Assign your writer function

Example:

#include "trice.h"

static void MyNonBlockingByteWrite(const uint8_t* data, size_t len) {
    /* Replace this with your project's existing writer. */
    ExistingTxQueueWrite(data, len);
}

void AppInit(void) {
    BoardInit();
    TriceInit(); // normally only needed when SEGGER_RTT is used. Otherwise it is an empty function.

    UserNonBlockingDeferredWrite8AuxiliaryFn = MyNonBlockingByteWrite;

    trice("boot\n");
}

void AppMainLoop(void) {
    for (;;) {
        AppRun();
        TriceTransfer();
    }
}

TriceTransfer() moves accumulated Trice records from the deferred buffer to your writer. Call it cyclically from the main loop, a low-priority task, or another context that is safe for your output driver.

6.1.4. Insert IDs before compiling

From your project root:

touch til.json li.json
trice insert -src ./ -i ./til.json -li ./li.json

Then build and flash your target.

6.1.5. Decode on the PC

For a serial or USB virtual COM port:

trice log -p COM15 -baud 921600 -i ./til.json -li ./li.json

On Linux/macOS, adapt the port:

trice log -p /dev/ttyACM0 -baud 921600 -i ./til.json -li ./li.json

If your writer produces a file, pipe, TCP stream, or another source, use the matching trice log -p ... input port.

6.1.6. Common first checks

6.1.7. Why this quickstart matters

The old README led many first-time readers toward SEGGER RTT because it is convenient and fast. That path is still valuable, but it also implies J-Link hardware and SEGGER tooling. The auxiliary writer path is more portable: many projects already have a byte-stream output, and Trice can reuse it.

Use this path when you have a SEGGER J-Link and want the smallest amount of target-specific transport code. See Convert Evaluation Board onboard ST-Link to J-Link for a cheap option.

6.2.1. Install tools

6.2.2. Add target sources

Add the complete src folder to your target project unchanged and add src to the compiler include path.

6.2.3. Configure direct RTT

Minimal triceConfig.h:

#ifndef TRICE_CONFIG_H_
#define TRICE_CONFIG_H_

#define TRICE_DIRECT_OUTPUT 1
#define TRICE_BUFFER TRICE_STACK_BUFFER
#define TRICE_DIRECT_SEGGER_RTT_32BIT_WRITE 1

#endif /* TRICE_CONFIG_H_ */

6.2.4. Add a first Trice call

#include "trice.h"

int main(void) {
    BoardInit();
    TriceInit(); // normally only needed when SEGGER_RTT is used. Otherwise it is an empty function.

    trice("Hello RTT\n");

    for (;;) {
        AppRun();
    }
}

Direct RTT does not require TriceTransfer() for the normal direct output path.

6.2.5. Insert, build, flash

touch til.json li.json
trice insert -src ./ -i ./til.json -li ./li.json

Build and flash the target.

Example command; adapt the device name and speed:

trice log -p JLINK \
  -args "-Device STM32G0B1RE -if SWD -Speed 4000 -RTTChannel 0" \
  -pf none -prefix off -hs off -d16 \
  -i ./til.json -li ./li.json

Alternative file-based workflow:

rm -f ./temp/trice.bin
mkdir -p ./temp
touch ./temp/trice.bin
JLinkRTTLogger -Device STM32G0B1RE -If SWD -Speed 4000 -RTTChannel 0 ./temp/trice.bin

In a second terminal:

trice log -p FILE -args ./temp/trice.bin \
  -pf none -prefix off -hs off -d16 \
  -i ./til.json -li ./li.json

6.2.7. When this path is ideal

Direct RTT is excellent for lab development because the target writes to RTT memory and the probe drains it. It avoids UART setup and usually feels close to printf debugging.

6.2.8. When this path is not ideal

It depends on J-Link/RTT infrastructure. If that hardware or closed host tooling is a blocker, start with the Quickstart: Existing non-blocking byte writer, deferred auxiliary 8-bit or a UART/VCOM deferred path.

6.3. Quickstart: UART or USB-VCOM deferred output

See also Communication Ports, the example projects, and the UART-related configuration examples. This section is intentionally short because UART setup is MCU/vendor-specific.

Use this path when the target has a UART, USB CDC/VCOM, or board-specific serial path and you want Trice to use the built-in UART backend rather than an auxiliary writer.

Minimal shape of triceConfig.h:

#ifndef TRICE_CONFIG_H_
#define TRICE_CONFIG_H_

#include "main.h" /* or your MCU/vendor header */

#define TRICE_DEFERRED_OUTPUT 1
#define TRICE_BUFFER TRICE_RING_BUFFER
#define TRICE_DEFERRED_UARTA 1
#define TRICE_UARTA USART2 /* adapt to your project */

#endif /* TRICE_CONFIG_H_ */

Application shape:

#include "trice.h"

int main(void) {
    BoardInit();
    UartInit();
    TriceInit(); // normally only needed when SEGGER_RTT is used. Otherwise it is an empty function.

    trice("boot\n");

    for (;;) {
        AppRun();
        TriceTransfer();
    }
}

Host side:

trice log -p COM15 -baud 921600 -i ./til.json -li ./li.json

On Linux/macOS:

trice log -p /dev/ttyACM0 -baud 921600 -i ./til.json -li ./li.json

For a quick first success, the Quickstart: Existing non-blocking byte writer, deferred auxiliary 8-bit may be easier if your project already has a working serial or USB write function.

(back to top)

7. Trice Trouble Shooting Hints

7.1. Initial Data Transfer Setup Hints

If you do not succeed initially, you can try this:

triceConfig.h:

#define TriceStamp32 0x44434241 // a fixed value

#define TRICE_DIRECT_OUT_FRAMING TRICE_FRAMING_NONE   // default
#define TRICE_DEFERRED_OUT_FRAMING TRICE_FRAMING_NONE // no framing to interpret the byte stream manually

main.c:

int main( void) {
    // system init...
    TriceInit();
    TRice(iD(170), "Fun %x!\n", 0xadded ); // with "fixed" iD(170), 32-bit stamp, and with `\n`
    TriceTransfer(); // call cyclically for deferred mode
    // system run ...
}
trice log -s -port com1 -v -ts32="att:%08x fix" # enter this (adapted)
#       /-------------------------------------- ID low byte (170)
#       |  /----------------------------------- ID high byte (6 bits=0) with 2 most significant bits set (32-bit stamp follows)
#       |  |       /--------------------------- 32-bit (time) stamp
#       |  |       |      /-------------------- initial cycle counter: 192
#       |  |       |      |  /----------------- payload size
#       |  |       |      |  |       /--------- payload (0x00added) 
#       |  |       |      |  |       |      / - 0-delimiter or next Trice
#       |  |       |      |  |       |      |
#       v  v  vvvvvvvvvvv v  v  vvvvvvvvvvv v
# Input(aa c0 41 42 43 44 c0 04 ed dd 0a 00 ... ) # expected byte stream
# ...
#              main.c    84 44434241 fix   170 Fun added!
# ...

7.2. Short Trouble Shooting Hints

Problem Hint
Missing objcopy in macOS brew install binutils
Small GUI Editor for macOS brew install cotedit Usage: cot (not as root)
Small In-Terminal Editor Linux https://cte.e10labs.com/, tilde, micro, joe, https://craigbarnes.gitlab.io/dte/ (also as root)
Nothing shown with trice -s Check that format strings end with \n and/or use -addNL

(back to top)

8. Trice Cache for Compilation Speed

The trice insert and trice clean commands are parsing and modifying the source code files. Even this is a reasonable fast procedure, this could get time consuming on large projects, especially when using these commands as permanent pre-compile and post-compile steps. It is assumed, that usually between 2 compile steps not all project files are changed. The project files majority will stay unchanged despite the ID insertion and removal. This repeated parsing and modifying of unchanged source code is avoidable with the Trice cache technique. Also it could get annoying to recompile files all the time only because they got Trice IDs removed and inserted. With the Trice cache we get also a solution not to re-compile un-edited files as well.

8.1. Trice Cache Idea

Lets talk about just one source file $HOME/my/src/foo.c and imagine we process many in one shot.

8.2. Trice Cache Logic

When id.TriceCacheEnabled is true (applied -cache CLI switch) and the folder ~/.trice/cache exists, we have

8.3. Trice Cache Remarks

Should the .trice/cache be better located inside the project folder? What, if the user has several projects and several users on the same machine working on projects together? What about libraries containing trice code?

8.4. Trice Cache Tests

Nr Action cCache iCache ID state Edited state Test function
0,1 0:clean 0:inval 0:inval 0:cleaned X:any Test_0_1_0000X_clean_on_invalid_cCache_invalid_iCache_cleaned_file
2,3 0:clean 0:inval 0:inval 1:inserted X:any Test_2_3_00011_clean_on_inalid_cCache_invalid_iCache_inserted_edited_file
4,5 0:clean 0:inval 1:valid 0:cleaned X:any Test_4_5_0010X_clean_on_invalid_cCache_valid_iCache_cleaned_file
6 0:clean 0:inval 1:valid 1:inserted 0:not Test_6_00110_clean_on_invalid_cCache_valid_iCache_inserted_not_edited_file
7 0:clean 0:inval 1:valid 1:inserted 1:yes Test_7_00111_clean_on_invalid_cCache_valid_iCache_inserted_edited_file
8 0:clean 1:valid 0:inval 0:cleaned 0:not Test_8_01000_clean_on_valid_cCache_invalid_iCache_cleaned_not_edited_file
9 0:clean 1:valid 0:inval 0:cleaned 1:yes Test_9_01001_clean_on_valid_cCache_invalid_iCache_cleaned_edited_file
10 0:clean 1:valid 0:inval 1:inserted 0:not Test_10_01011_clean_on_valid_cCache_invalid_iCache_inserted_not_edited_file
11 0:clean 1:valid 0:inval 1:inserted 1:yes Test_11_01011_clean_on_valid_cCache_invalid_iCache_inserted_edited_file
12 0:clean 1:valid 1:valid 0:cleaned 0:not Test_12_01100_clean_on_valid_iCache_valid_cCache_clean_file_not_edited
13 0:clean 1:valid 1:valid 0:cleaned 1:yes Test_13_01101_clean_on_valid_iCache_valid_cCache_clean_file_edited
14 0:clean 1:valid 1:valid 1:inserted 0:not Test_14_01110_clean_on_valid_iCache_valid_cCache_inserted_file_not_edited
15 0:clean 1:valid 1:valid 1:inserted 1:yes Test_15_01111_clean_on_valid_iCache_valid_cCache_inserted_file_edited
16,17 1:insert 0:inval 0:inval 0:cleaned X:any Test_16_17_1000X_insert_on_invalid_cCache_invalid_iCache_cleaned_file
18,19 1:insert 0:inval 0:inval 1:inserted X:any Test_18_19_1001X_insert_on_invalid_cCache_invalid_iCache_inserted_edited_file
20,21 1:insert 0:inval 1:valid 0:cleaned X:any Test_20_21_1010X_insert_on_invalid_cCache_valid_iCache_cleaned_file
22 1:insert 0:inval 1:valid 1:inserted 0:not Test_22_10100_insert_on_invalid_cCache_valid_iCache_inserted_not_edited_file
23 1:insert 0:inval 1:valid 1:inserted 1:yes Test_23_10101_insert_on_invalid_cCache_valid_iCache_inserted_edited_file
24 1:insert 1:valid 0:inval 0:cleaned 0:not Test_24_11000_insert_on_valid_cCache_invalid_iCache_cleaned_not_edited_file
25 1:insert 1:valid 0:inval 0:cleaned 1:yes Test_25_11001_insert_on_valid_cCache_invalid_iCache_cleaned_edited_file
26,27 1:insert 1:valid 0:inval 1:inserted X:any Test_26_27_1010X_insert_on_invalid_cCache_valid_iCache_cleaned_file
28 1:insert 1:valid 1:valid 0:cleaned 0:not Test_28_11100_insert_on_valid_cCache_valid_iCache_cleaned_not_edited_file
29 1:insert 1:valid 1:valid 0:cleaned 1:yes Test_29_11100_insert_on_valid_cCache_valid_iCache_cleaned_edited_file
30 1:insert 1:valid 1:valid 1:inserted 0:not Test_30_11110_insert_on_valid_cCache_valid_iCache_inserted_not_edited_file
31 1:insert 1:valid 1:valid 1:inserted 1:yes Test_31_11111_insert_on_valid_cCache_valid_iCache_inserted_edited_file

8.5. Possible Trice Cache Editor-Issues And How To Get Around

8.6. Activating the Trice Cache

mkdir -p ~/.trice/cache

(back to top)

9. Embedded system code configuration

Check comments inside triceDefaultConfig.h and adapt your project configuration like shown in triceConfig.h as example.

A Trice macro is avoiding all the printf() internal overhead (space and time) but is nearly as easy to use. For example instead of writing

printf("time is %d:%d:%d\n", hour, min, sec);

you can write

trice8("time is %d:%d:%d\n", hour, min, sec);

into a source file of your project. The 8 stands here for 8 bit values (16, 32 and 64 also possible). Values of mixed size up to 32-bit size are allowed in one trice macro, so you can use Trice consequently to match most cases for the prize of little data overhead.

(back to top)


10. Trice tool in logging action

With trice log -port COM12 you can visualize the trices on the PC, if for example COM12 is receiving the data from the embedded device at the 115200 default baudrate.

The following capture output comes from an (old) example project inside ../examples.

life.gif

See ../_test/testdata/triceCheck.c for reference. The Trices can come mixed from inside interrupts (light blue ISR:...) or from normal code. For usage with a RTOS, Trices are protected against breaks (TRICE_ENTER_CRITICAL_SECTION, TRICE_LEAVE_CRITICAL_SECTION). Regard the differences in the read SysTick values inside the GIF above These differences are the MCU clocks needed for one trice (~0,25µs@48MHz).

Use the -color off switch for piping output in a file. More convenient is the -lf auto switch.

(back to top)

11. Optional XTEA Encryption

(back to top)

12. Trice Command Line Interface & Examples

The trice tool is very easy to use even it has a plenty of options. Most of them normally not needed. The trice tool can be started in several modes (sub-commands), each with several mandatory or optional switches. Switches can have a single parameter string or not.

trice sub-command -switch1 -switch2 parameter -switch3 ...

Which sub-command switches are usable for each sub-command is shown with trice help -all. This gives also information about their default values.

Info for a special sub-command is shown with trice help -log for example.

12.1. Common information

12.2. Further examples

12.2.1. Automated pre-build insert command example

trice i -v -i ../../../til.json -src ../src -src ../lib/src -src ./

This is a typical line you can add to your project as an automatic pre-compile step.

12.2.2. Some Log examples

trice log -i ./myProject/til.json -p=COM3
trice l -s COM3 -baud=9600

12.2.3. Logging over a display server

trice ds
trice l -ds -p COM3
trice sd -r 192.168.1.23:45678

The IP address and port are free selectable. Using a display server, allows to watch the logs of one or many MCUs on a local or remote machine with the same or different display servers.

A local Trice instance sends Trice messages to a display server only, when a log line is complete (if consisting of several Trices). By using the CLI switches -prefix and -suffix you can decorate the loglines target specific to distinguish them in the output window(s).

12.2.4. Logfile output

trice l -p COM3 -logfile auto

This creates a new logfile 2022-05-16_2216-40_trice.log with the actual timestamp on each Trice start.

trice l -p COM3 -logfile trice.log

This creates a new logfile trice.log on first start and appends to it on each next Trice start.

Logfiles are text files one can see with 3rd party tools. Example: cat trice.log. They contain also the PC reception timestamps if where enabled.

12.2.5. Binary Logfile

trice l -p COM3 -binaryLogfile auto

This creates a new binary logfile 2022-05-16_2216-40_trice.bin with the actual timestamp on each Trice start.

trice l -p COM3 -binaryLogfile trice.bin

This creates a new binary logfile trice.bin on first start and appends to it on each next Trice start.

Binary logfiles store the Trice messages as they come out of the target in binary form. They are much smaller than normal logfiles, but the Trice tool with the matching til.json is needed for displaying them and the PC timestamps are the displaying time: trice l -p FILEBUFFER -args trice.bin.

Recording happens before decoding, -pick, -ban, -logLevel, and visualization. These host options therefore do not remove received bytes from the binary recording, even with -logLevel off. Partial or malformed input is also recorded; a recording write failure is returned as an error. Data already lost or suppressed on the target cannot be recovered from this file.

Replay can use a different selection, for example trice log -p FILEBUFFER -args trice.bin -pick err:wrn. Keep the matching dictionary and decoding settings (encoding, framing, and encryption) with the recording. An explicit binary filename is appended to across runs; use a new filename when separate captures are needed. Keep binary recording disabled during replay, or write to a different file, never the replay input itself.

Binary logfiles are handy in the field for long data recordings.

When using RTT, the data are exchanged over a file interface. These binary logfiles are stored in the project [./temp] folder and accessible for later view: trice l -p FILEBUFFER -args ./temp/logfileName.bin. Of course the host timestamps are the playing time then.

12.2.6. TCP4 output

trice l -p COM3 -tcp 127.0.0.1:23

This additionally sends Trice output to a 3rd party TCP listener, for example like Putty:

./ref/PuttyConfig1.PNG ./ref/PuttyConfig2.PNG ./ref/Putty.PNG

12.2.7. TCP4 input

trice l -p TCP4 -args "192.168.2.3:45678"

This expects a TCP4 server at IP address 192.168.2.3 with port number 45678 to read binary Trice data from.

12.2.8. UDP4 input

The pull request #529 introduces key enhancement:

    IPv4 UDP Receiver
    Adds support for receiving data over IPv4 using UDP. This enables integration with systems that broadcast or transmit telemetry, logs, or other messages over the network.

-port UDP4 Example

To receive Trice logs over IPv4 UDP, use the -port UDP4 option. By default, it listens on 0.0.0.0:17005, which accepts packets on all network interfaces. You can specify a different address or multicast group via -args.

trice log -p UDP4

12.2.9. Stimulate target with a user command over UART

Sometimes it is handy to stimulate the target during development. For that a 2nd screen is helpful what is possible using the display server option:

./ref/UARTCommandAnimation.gif

12.2.10. Explore and modify tags and their colors

See chapter Trice Tags and Color.

12.2.11. Location Information

The add, insert, and clean commands generate the file selected by -li|locationInformation. Each entry stores one canonical source path and its line number:

{
  "1234": {
    "File": "examples/TriceABC/src/main.c",
    "Line": 42
  }
}

File is relative to -liRoot and always uses / separators. The default root is the directory containing the selected li.json. An explicit relative -liRoot is resolved from the current working directory. If a relative path cannot be represented, for example across Windows volumes, Trice stores a normalized absolute path without resolving symbolic links.

For example, when build/demoLI.json and examples/TriceABC/src/main.c are below the project directory, the default stores ../examples/TriceABC/src/main.c. To store a project-relative path instead, run:

trice insert -li build/demoLI.json -liRoot . -src examples/TriceABC

During logging, -liMaxDirs controls how much of the stored path is shown. Its default 0 shows only the filename. For examples/TriceABC/src/main.c, values 1, 2, and 3 show src/main.c, TriceABC/src/main.c, and the complete stored path respectively. Leading .. components describe the relation to -liRoot and are not displayed or counted. -liFmt continues to control the surrounding filename and line-number format.

Location information must match the exact firmware version. In field deployments, keeping li.json private and showing the numeric ID with -showID can be preferable. When trice clean is used, consider versioning the matching li.json so later insert operations can reuse locations consistently.

12.3. Visualization output with -vis

tlog and trice log support the same repeatable -vis option for sending selected numeric measurements to external visualization tools. Consumers can, for example, be LabPlot, Serial Studio, PlotJuggler, uPlot, Grafana, or a custom program; these names do not imply a tool-specific protocol. Trice itself does not draw a graph. It transforms one typed Trice message into one user-defined text record and writes that record to a file or UDP destination.

The syntax is:

-vis='<tag>:printf("<go-fmt>",<expression-list>)@<file-path-or-udp-sink>[;log=keep|drop]'

For example, this target message keeps its visualization details independent of the host tool:

TRice("imu:ax=%f,ay=%f,az=%f\n", aFloat(ax), aFloat(ay), aFloat(az));

It can be written as CSV:

tlog ... \
  -vis='imu:printf("%d,%0.3f,%0.3f,%0.3f\n",ts32,v0,v1,v2)@imu.csv'

or sent as one UDP datagram per record:

tlog ... \
  -vis='imu:printf("%0.3f,%0.3f,%0.3f\n",v0*0.5,v1*0.5,v2*0.5)@udp://127.0.0.1:7010;log=drop'

The selector matches the original Trice format prefix <tag>:. -pick and -ban run first. A message removed by either existing filter is therefore invisible both to normal output and to -vis.

The supported fields are:

id                unsigned Trice ID
ts                raw 16- or 32-bit Target-Stamp, with one width latched per rule
ts16              raw 16-bit Target-Stamp
ts32              raw 32-bit Target-Stamp
v0 ... v11        typed positional Trice values

The value fields are positional and can be reordered in the expression list. -vis does not extract names such as ax or rpm from the human-readable target format and does not accept those names as identifiers. For example, printf("%g,%g\n",v2,v0) deliberately emits the third value before the first. Structured Logging field names are not visualization expression identifiers; use v0 through v11 for this interface.

A Target-Stamp is an unscaled number. -vis does not assume that it represents time and does not perform unit conversion, wrap extension, or mixed-width reconstruction. Scaling is explicit in an expression, for example ts32*0.001. A rule may use ts16 or ts32, but not both. A generic ts rule is disabled with a warning if an otherwise eligible message later changes between 16 and 32 bits.

Separate rules make the expected stamp width explicit:

tlog ... \
  -vis='fast:printf("%d,%g\n",ts16,v0)@fast.csv' \
  -vis='slow:printf("%d,%g\n",ts32,v0)@slow.csv'

Expressions support decimal, floating-point, and hexadecimal literals, parentheses, unary minus, and +, -, *, /. A direct field retains its signed, unsigned, floating-point, or Boolean type. Arithmetic is evaluated as float64. A floating result used with an integer verb must be finite and inside the int64 range; it is then truncated toward zero.

The printf encoder supports:

integer:       %d %b %o %x %X
floating:      %f %e %E %g %G
generic:       %v
Boolean:       %t with a direct Boolean field
literal:       %%
formatting:    literal width and precision, such as %08x or %0.3f

%u, dynamic * width or precision, explicit argument indexes, string conversions, and other Go formatting verbs are not supported. The expression count must equal the number of consuming verbs. The encoder adds no implicit newline; include \n in the format when the receiving tool expects one. A one-line JSON record is possible as well:

tlog ... \
  -vis='imu:printf("{\"stamp\":%d,\"x\":%g,\"y\":%g,\"z\":%g}\n",ts32,v0,v1,v2)@udp://127.0.0.1:7011'

A bare path and file:<path> both select an append-only file:

@out.csv
@logs/imu.csv
@file:out.csv

Missing files are created. Rules using the same normalized file path share one open file. file://out.csv is rejected because standard URI parsing treats out.csv as a host, not as a relative path. Full file-URI semantics are not part of this implementation.

UDP destinations use:

@udp://127.0.0.1:7010
@udp://localhost:7010

The address is resolved and opened before decoding starts. File and UDP writes are synchronous. There is no queue, retry, reconnect, acknowledgement, TCP, WebSocket, or named-pipe support in this first implementation.

log=keep is the default and leaves the decoded message in normal output. log=drop removes it from normal output only after that rule has encoded and written the visualization record successfully. All overlapping rules are still attempted; one successful log=drop rule wins. An ignored record or a failed encoder or sink write does not drop the normal log.

Only fixed-width numeric Trice messages are eligible. The first twelve values are addressable as v0 through v11; additional values do not prevent a rule from using that addressable prefix and remain available to normal logging. Trice string, buffer, function-display, character, typeX0, CHAR, and DUMP inputs are not supported. Named values, specialized JSON or binary encoders, TCP, WebSocket, named pipes, and process pipes are deferred behind the same selector/encoder/sink separation. One eligible Trice must also form one complete log line by itself. Partial, multi-line, and multi-Trice lines continue through normal logging but are ignored by -vis; verbose mode reports every such occurrence.

At startup, each rule checks all matching historical til.json entries. Incompatible old entries are excluded independently, so one stale ID does not block another compatible ID. A rule with no compatible entry is disabled with a prominent warning. Rules are also disabled, never silently, after a generic Target-Stamp width conflict, an unsafe runtime expression conversion, or a sink failure. Normal logging continues.

12.4. Setting up the LabPlot Demo

./ref/LabPlotDemo.gif

This section uses LabPlot, a cross-platform interactive plotting application. The finished, ready-to-run example is in ./examples/LabPlotDemo/; the guided learning path is in ./examples/LabPlotUser/. The project uses a UDP socket so that it can display an endless stream without repeatedly importing files.

12.4.1. The common live-data format

Both producers describe the same three signals: x, y, and z. LabPlot receives normalized numeric CSV records with this column layout:

time_s,x,y,z

The finished project predeclares the four numeric columns and performs one initial read while loading. This prepares LabPlot’s UDP socket before the first live record arrives. The UDP stream therefore needs no header row.

The LabPlot demo rate is 50 samples per second. The project retains 500 rows. The time plot is configured for the last 500 values, so its horizontal resolution stays constant and it always displays approximately the most recent ten seconds. The Lissajous plot uses only the last 150 values, which creates a moving three-second trace instead of an increasingly dense full history. A slow phase modulation of y makes the figure change continuously. The CSV producer sends this format directly. The Trice producer sends binary Trice records to tlog; tlog decodes them and sends the same CSV format onward. This separation means that one LabPlot project works for both examples.

12.4.2. ./examples/DemoData_CSV

The CSV producer is a small C11 program for Windows, macOS, and Linux. Each newline-terminated record contains time_s,x,y,z, all represented as double; time is in seconds. It writes to standard output by default, to a fresh file with --output FILE, or to UDP with one record per datagram. Use --help for all options.

Both data producers require a C compiler and CMake 3.16 or newer. Run their build.sh in Git Bash on Windows or a POSIX shell on macOS/Linux; restore its executable permission with chmod +x build.sh if necessary. Executables are installed in each project’s bin/, with intermediate files in build/. For the CSV producer, the equivalent explicit CMake commands are:

cd examples/DemoData_CSV
cmake -S . -B build
cmake --build build --config Release
cmake --install build --config Release --prefix .

These commands also work in PowerShell. Its executable invocation is .\bin\DemoData_CSV.exe; in Git Bash use ./bin/DemoData_CSV.

From the CSV project directory, try:

./build.sh
./bin/DemoData_CSV
./bin/DemoData_CSV --rate 50 --samples 500 --no-delay --header --output DemoData_CSV.csv
./bin/DemoData_CSV --udp 127.0.0.1 9000

Run these alternatives separately. The second command runs continuously at 50 Hz; interrupt it with Ctrl-C. The third writes ten seconds of data without real-time waiting. On Windows PowerShell the UDP command is .\bin\DemoData_CSV.exe --udp 127.0.0.1 9000.

For Serial Studio, choose Quick Plot (Comma Separated Values), then Network Socket > UDP, set local port 9000, connect, and start the UDP producer. Quick Plot treats all four columns as values. A custom project can instead name the columns and use the first column as a timestamp axis. Its input must be seconds,x,y,z; do not send --header on the live stream. There is currently no versioned DemoData.ssproj in this repository.

Both producers use the following signal model for time t in seconds:

phase = (pi/3) * sin(2*pi*0.04*t)
x = sin(2*pi*0.70*t)
y = sin(2*pi*0.91*t + pi/2 + phase)
z = 0.6*sin(2*pi*0.13*t) + 0.2*x*y + pulse

The pulse has height 0.8 during the final 250 ms of every eight-second interval. The slow phase modulation keeps the Lissajous plot moving. The Trice producer calculates the signals as doubles and transmits float32 values.

After running build.sh inside ./examples/DemoData_CSV/, the executable is installed in the local bin/ folder. You can run it there:

th@Thomass-MacBook-Pro-7 bin % ./DemoData_CSV --header -o log.csv
^C
th@Thomass-MacBook-Pro-7 bin % head log.csv                           
time_s,x,y,z
0.000000,0.000000,1.000000,0.000000
0.020000,0.087851,0.992854,0.027246
0.040000,0.175023,0.971519,0.053608
0.060000,0.260842,0.936300,0.078239
0.080000,0.344643,0.887701,0.100367
0.100000,0.425779,0.826415,0.119328
0.120000,0.503623,0.753319,0.134594
0.140000,0.577573,0.669459,0.145795
0.160000,0.647056,0.576032,0.152736
th@Thomass-MacBook-Pro-7 bin % 

12.4.3. ./examples/DemoData_Trice

The Trice producer transports the same signals as binary Trice records. Keep it inside the repository: its CMake project uses the unchanged target library from ../../src. Its build script prepares Bind and the repository-root demoTIL.json before building; it requires the Trice host tool in addition to the CSV producer’s prerequisites. Use this script rather than plain CMake commands that omit Bind preparation. There is no private til.json or fixed iD(1000) for this ID-free producer.

The 32-bit target stamp uses units of 10 ms: at 50 Hz the stamps are 0, 2, 4, .... Convert with seconds = ts/100.0 or milliseconds = ts*10; ts*100 does not give seconds.

From examples/DemoData_Trice:

./build.sh
./bin/DemoData_Trice --samples 500 --no-delay
trice log -p FILEBUFFER -args DemoData_Trice.bin -pf TCOBS -til ../../demoTIL.json -li off

Without an output option, the producer recreates DemoData_Trice.bin in the current directory (wb truncates its previous contents). --output FILE selects another fresh file, --stdout writes binary data to standard output, and --udp HOST PORT sends one complete TCOBS-framed record per datagram. Without --samples it runs until interrupted. --samples 500 counts signal samples, not all log records: startup and periodic diagnostic logs are extra. Use --help for all options. On Windows PowerShell, run .\bin\DemoData_Trice.exe with the same arguments.

After running build.sh inside ./examples/DemoData_Trice/, the executable is installed in the local bin/ folder. You can run it there:

th@Thomass-MacBook-Pro-7 bin % ./DemoData_Trice -o log.bin
Writing log.bin
^C
th@Thomass-MacBook-Pro-7 bin % tlog -p FILEBUFFER -args log.bin -til ../../../demoTIL.json -ulabel vis_demo | head
Jul 25 15:38:55.411676  FILEBUFFER:    0,000_000 0.000000,1.000000,0.000000
Jul 25 15:38:55.411691  FILEBUFFER:    0,000_002 0.087851,0.992854,0.027246
Jul 25 15:38:55.411705  FILEBUFFER:    0,000_004 0.175023,0.971519,0.053608
Jul 25 15:38:55.411714  FILEBUFFER:    0,000_006 0.260842,0.936300,0.078239
Jul 25 15:38:55.411725  FILEBUFFER:    0,000_008 0.344643,0.887701,0.100367
Jul 25 15:38:55.411737  FILEBUFFER:    0,000_010 0.425779,0.826415,0.119328
Jul 25 15:38:55.411752  FILEBUFFER:    0,000_012 0.503623,0.753319,0.134594
Jul 25 15:38:55.411765  FILEBUFFER:    0,000_014 0.577573,0.669459,0.145795
Jul 25 15:38:55.411776  FILEBUFFER:    0,000_016 0.647056,0.576032,0.152736
th@Thomass-MacBook-Pro-7 bin %
th@Thomass-MacBook-Pro-7 bin % tlog -p FILEBUFFER -args log.bin -til ../../../demoTIL.json -ulabel vis_demo -vis='vis_demo:printf("%0.3f,%0.3f,%0.3f,%0.3f\n",ts/100.0,v0,v1,v2)@log.csv;header="time_s,X,Y,Z\n";log=drop'
th@Thomass-MacBook-Pro-7 bin % head log.csv
time_s,X,Y,Z
0.000,0.000,1.000,0.000
0.020,0.088,0.993,0.027
0.040,0.175,0.972,0.054
0.060,0.261,0.936,0.078
0.080,0.345,0.888,0.100
0.100,0.426,0.826,0.119
0.120,0.504,0.753,0.135
0.140,0.578,0.669,0.146
0.160,0.647,0.576,0.153
th@Thomass-MacBook-Pro-7 bin % 

The preceding file visualization rule recreates log.csv when tlog starts. Its header is written once per sink; the quoted Go string supports \n. Keep a single backslash in the shell’s single-quoted rule. log=drop suppresses the successfully visualized records, while unrelated diagnostic logs remain.

For a live Serial Studio or other CSV viewer listening on UDP 9000, start the decoder from the repository root before starting the binary producer:

trice log -p UDP4 -args 127.0.0.1:9001 -pf TCOBS -til demoTIL.json -ulabel vis_demo \
  -vis='vis_demo:printf("%0.6f,%0.6f,%0.6f,%0.6f\n",ts/100.0,v0,v1,v2)@udp://127.0.0.1:9000;log=drop'

In another terminal, also from the repository root:

examples/DemoData_Trice/bin/DemoData_Trice --udp 127.0.0.1 9001

On Windows the executable has an .exe suffix. The producer sends binary Trice to 9001, not CSV; connecting it directly to the viewer on 9000 cannot work. The decoder converts its records to seconds,x,y,z. The LabPlot launchers below automate this pipeline and its receiver-readiness checks.

12.4.4. Quick LabPlot demonstration

Install LabPlot 2.12 or newer. From the repository root, run one of these commands in a POSIX shell:

./examples/LabPlotDemo/run_csv.sh
./examples/LabPlotDemo/run_trice.sh

The script opens LabPlotDemo.lml and starts the selected producer. The first script sends CSV directly to UDP port 9000. The second uses UDP port 9001 for binary Trice input and runs tlog as the decoder/forwarder to port 9000. The script first waits until LabPlot has opened port 9000, then starts tlog and waits until its input port 9001 is ready. Only then does it start the Trice producer. The project opens one worksheet containing two plots side by side:

Press Ctrl-C in the shell to stop the producer and decoder. If LabPlot is not found automatically, set LABPLOT to its executable. On Windows, Git Bash is a suitable shell; for example, use LABPLOT=/c/Program\ Files/LabPlot/bin/labplot.exe.

12.4.5. Recreate the project in LabPlot

The following steps explain the project without requiring prior LabPlot knowledge. Start run_csv.sh first and leave it running.

  1. Create a new LabPlot project and choose Add New > Live Data Source.
  2. Select Network UDP Socket, enter host 127.0.0.1 and port 9000.
  3. Select the ASCII filter, comma as separator, and disable header detection. Set all four data types to Double and enter the names time_s, x, y, and z.
  4. Select Update on new data and retain 500 values. This is the moving ten-second window at the demo’s 50 Hz rate.
  5. Add a worksheet with a Cartesian plot. Add three XY curves. For every curve choose time_s as the X column and choose x, y, or z as the Y column. Enable the legend, label the axes time [s] and value, and enable automatic range scaling. In the plot’s range settings select Last values and enter 500; otherwise the time axis keeps growing and the curves become increasingly compressed.
  6. Add a second Cartesian plot to the same worksheet and select a horizontal two-column worksheet layout. Add one XY curve with x as its X column and y as its Y column. Select Last values and enter 150. Fixed X and Y ranges from -1.1 to 1.1 keep the scale stable while the three-second trace and the signal’s slow phase drift make the movement visible.
  7. Save the project as LabPlotUser.lml.

The finished LabPlotDemo.lml contains these settings and no machine-specific paths. Open it manually if LabPlot is already running. The LabPlotUser directory is the place for your recreated LabPlotUser.lml; this section is its complete rebuild guide. Both producers end at the same numeric UDP stream, for example 0.000000,0.000000,1.000000,0.000000.

Stop the producer and start the other launcher without changing the LabPlot project. Keep only one producer sending to UDP 9000 and keep the live source connected. The Trice launcher first waits for LabPlot on 9000, then for its decoder on 9001. The supplied project predeclares all four numeric columns and triggers an initial read when loaded so that LabPlot prepares its socket.

12.4.6. Troubleshooting and adaptations

(back to top)

13. Limitations

13.1. Permanent Limitations

13.1.1. Limitation TRICE in TRICE not possible

int f0( void ){ TRICE( "msg:f0\n"); return 0; }
void f1( void ){ TRICE( "No; %d", f0() ); }

The reason is: When f1() gets active, the “No” Trice header is created, than the f0() Trice is executed and afterwards the “No” Trice tail is written. This works well during compile time but causes a mismatch during runtime.

int f0( void ){ TRICE( "msg:f0\n"); return 0; }
void f1( void ){ int x = f0(); TRICE( "Yes: %d", x ); }

13.2. Current Limitations

13.2.1. String Concatenation Within TRICE Macros Not Possible

String concatenation within TRICE macros does not work. The reason lays inside the way the trice tool parser works:

void f0( void ){ TRICE( "msg:" ## "Hello\n" ); } // ERROR!

To implement this would need to build a trice preprocessor or to run the C preprocessor first and to modify the preprocessor output with the trice tool. That would make things unneccessary complicate and fragile for now.

13.2.2. Limited Trice Parser Capabilities

The Trice tool internal parser has only limited capabilities. In works well in most cases, but could lead to problems in some cases. The compiler run will for sure end up with some error messages in the following examples, so the developer can fix the code.

An example, provided by @KammutierSpule, is this:

void trice0_test() {
    Trice0( "OK");
    Trice( InvalidUse );
    Trice( "%u", Variable );
}
void trice0_test() {
    Trice0( iD(2740), "OK"); // ok, iD is added
    Trice( InvalidUse ); // no warning or error
    Trice( "%u", Variable ); // id is not added / inserted
}

As said, the compiler will complain about that in any case.

13.2.3. Special Care Demands

More than 12 printf parameters
Float Numbers
Double numbers
Runtime Generated Strings

The triceS macro is ment to be used with strings not known at compile time.

Usage intention and recommendation: (given by @escherstair)

char runtime_string[50];
fillRuntimeStringFromSomewhere(runtime_string); // the content of runtime_string is filled at run time
triceS( "msg:This part of the string is known at compile time. This part is dynamic: %s\n", runtime_string);

All the string literals (i.e. compile-time known strings) should be put inside the format string. Only the runtime strings should be used as variables in triceS macro for best performance.

(back to top)

14. Additional hints

14.1. Pre-built executables are available

See https://github.com/rokath/trice/releases.

14.2. Configuration file triceConfig.h

14.3. Setting up the very first connection

If you see nothing in the beginning, what is normal ;-), add the -s (-showInputBytes) switch to see if any data arrive. There is also a switch -debug showing you the received packages, if you are interested in.

14.4. Avoid buffer overruns

It is your responsibility to produce less data than transmittable. If this is not guarantied, a data loss is not avoidable or you have to slow down the user application. The buffers have an optional overflow protection (TRICE_PROTECT), which is enabled by default. Recommendation: Make the buffer big and emit the maxDepth cyclically, every 10 or 1000 seconds. Then you know the needed size. It is influenced by the max Trice data burst and the buffer switch interval. See ./examples/exampleData/triceLogDiagData.c for help.

If the target application produces more Trice data than transmittable, a buffer overrun can let the target crash, because for performance reasons no overflow check is implemented in versions before v0.65.0. Such a check is added now per default using TRICE_PROTECT, but the Trice code can only throw data away in such case. Of course you can disable this protection to get more speed.

Configuring the ring buffer option with TRICE_PROTECT == 0 makes buffer overruns not completely impossible, because due to partial Trice log overwrites, false data are not excluded anymore and overwriting the buffer boundaries is possible, because of wrong length information. Also losses will occur when producing more data than transmittable. This is detectable with the cycle counter. The internal 8-bit cycle counter is usually enabled. If Trice data are lost, the receiver side will detect that because the cycle counter is not as expected. There is a chance of 1/256 that the detection does not work for a single case. You can check the detection by unplugging the trice UART cable for a time. Also resetting the target during transmission should display a cycle error.

Gennerally it is recommended to enable TRICE_PROTECT during development and to disable it for performance, if you are 100% sure, that not more data are producable than transmittable.

Important to know: If the TRICE_PROTECT code inhibits the writing into a buffer, there will be later no cycle error because a non existing Trice cannot cause a cycle error. Therefore the TriceDirectOverflowCount and TriceDeferredOverflowCount values exist, which could be monitored.

14.5. Buffer Macros

(Examples in ../_test/testdata/triceCheck.c)

Macro Name Description
triceS |TriceS |TRiceS |TRICE_S Output of runtime generated 0-terminated strings.
triceN |TriceN |TRiceN |TRICE_N Is for byte buffer output as string until the specified size. It allows limiting the string size to a specific value and does not rely on a terminating 0. If for example len = 7 is given and “Hello\0World\n” is in the buffer, the byte sequence “Hello\0W” is transmitted but the trice tool probably shows only “Hello”.
triceB |TriceB |TRiceB |TRICE_B Is buffer output according to the given format specifier for a default unit according to configuration (8|16|32|64-bit value) - default is #define TRICE_B TRICE8_B.
trice8B |Trice8B |TRice8B |TRICE8_B Is for byte buffer output according to the given format specifier for a single byte.
trice16B|Trice16B|TRice16B|TRICE16_B Is for 16-bit buffer output according to the given format specifier for a 16-bit value.
trice32B|Trice32B|TRice32B|TRICE32_B Is for 32-bit buffer output according to the given format specifier for a 32-bit value.
  See chapter Trice ABC - Asynchronous Broadcast Commands for the following lines.
triceC |TriceC |TRiceC |TRICE_C Is for ABC buffer output according to the given function handler for a default unit according to configuration (8|16|32|64-bit value) - default is #define TRICE_C TRICE8_C.
trice8C |Trice8C |TRice8C |TRICE8_C Is for ABC byte buffer output according to the given function handler.
trice16C|Trice16C|TRice16C|TRICE16_C Is for ABC 16-bit buffer output according to the given function handler for a 16-bit wide buffer.
trice32C|Trice32C|TRice32C|TRICE32_C Is for ABC 32-bit buffer output according to the given function handler for a 32-bit wide buffer.

14.6. Logfile viewing

Logfiles, Trice tool generated with sub-command switch -color off, are normal ASCII files. If they are with color codes, these are ANSI escape sequences.

14.7. Using the Trice tool with 3rd party tools

Parallel output as logfile, TCP or binary logfile is possible. See examples above.

14.8. Several targets at the same time

You can connect each target over its transmit channel with an own Trice instance and integrate all transmissions line by line in an additional Trice instance acting as display server. See https://github.com/rokath/trice#display-server-option.

14.9. Executing go test -race -count 100 ./...

The C-code is executed during some tests. Prerequisite is an installed GCC.

14.10. TRICE_STACK_BUFFER could cause stack overflow with -o0 optimization

As discussed in issue #294 it can happen, that several TRICE macros within one function call increase the stack usage more than expected, when compiler optimization is totally switched off.

14.11. Cycle Counter

(back to top)

15. Switching Trice ON and OFF

15.1. Target side compile-time Trice On-Off

#define TRICE_OFF 1
#include "trice.h"
void fn(void) {
    trice( iD(123), "Hi"); // Will generate code only, when TRICE_OFF == 0.
    trice( "Lo");          // Will generate code only, when TRICE_OFF == 0.
}

With #define TRICE_OFF 1, macros in this file are ignored completely by the compiler, but not by the Trice tool. In case of reconstructing the Trice ID List these no code generating macros are regarded and go into (or stay inside) the ID reference list.

(back to top)

15.2. Host side Trice On-Off

(back to top)

16. Framing

(back to top)

17. Endianness

(back to top)

18. Trice (Time)Stamps

It is up to the user to provide the functions TriceStamp16 and/or TriceStamp32. Normally they return a µs or ms tick count but any values are allowed.

The PC feature tour makes this distinction visible without hardware: its 16-bit stamp is a sample phase, while its 32-bit stamp counts milliseconds. The matching G0B1 feature tour uses the board’s own timers.

18.1. Target (Time)Stamps Formatting

To get a short overview run trice help -log and read about the CLI switches ts, ts0, ts16, ts32, ts0delta, ts16delta, ts32delta in the generated CLI help file. The ts32 switch supports also “epoch” now as format. That is useful for example, if the binary logs are stored internally in the device flash and read out later. Such usage assumes 1 second as ts32 unit in uint32_t format and the Trice tool displays the UTC time. It is also possible to adapt the displayed format like this for example: trice log -ts32='epoch"06-01-02_15:04:05"'. The additional passed string must match the Go time package capabilities. A few examples:

trice log -port FILEBUFFER -args myLogs.bin -ts32='"Mon Jan _2 15:04:05 2006"'             # ANSIC   
trice log -port FILEBUFFER -args myLogs.bin -ts32='"Mon Jan _2 15:04:05 MST 2006"'         # UnixDate    
trice log -port FILEBUFFER -args myLogs.bin -ts32='"Mon Jan 02 15:04:05 -0700 2006"'       # RubyDate      
trice log -port FILEBUFFER -args myLogs.bin -ts32='"02 Jan 06 15:04 MST"'                  # RFC822    
trice log -port FILEBUFFER -args myLogs.bin -ts32='"02 Jan 06 15:04 -0700"'                # RFC822Z     (RFC822 with numeric zone)     
trice log -port FILEBUFFER -args myLogs.bin -ts32='"Monday, 02-Jan-06 15:04:05 MST"'       # RFC850    
trice log -port FILEBUFFER -args myLogs.bin -ts32='"Mon, 02 Jan 2006 15:04:05 MST"'        # RFC1123     
trice log -port FILEBUFFER -args myLogs.bin -ts32='"Mon, 02 Jan 2006 15:04:05 -0700"'      # RFC1123Z    (RFC1123 with numeric zone)        
trice log -port FILEBUFFER -args myLogs.bin -ts32='"2006-01-02T15:04:05Z07:00"'            # RFC3339    
trice log -port FILEBUFFER -args myLogs.bin -ts32='"2006-01-02T15:04:05.999999999Z07:00"'  # RFC3339Nano        
trice log -port FILEBUFFER -args myLogs.bin -ts32='"3:04PM"'                               # Kitchen    

After the year 2106 the Trice tool needs a small modification to correctly compute the epoch time then. Probably I will not be alive anymore to do that then, but, hey, Trice is Open Source!

18.2. Target (Time)Stamp Delta Columns

trice can display target timestamps not only as absolute values, but also as deltas to the previous target timestamp of the same size. For that purpose the CLI provides three additional switches:

These switches are delta variants of -ts0, -ts16, and -ts32. All three default to "", which means disabled.

18.2.1. Purpose

The delta switches add a second, independent timestamp column. This makes it possible to show:

This is useful when absolute time is needed for long-term orientation, while delta time is needed for short-term timing analysis.

18.2.2. General Behavior

-ts16delta and -ts32delta behave like the corresponding absolute timestamp switches, except that they print the difference to the previous timestamp of the same type:

Wraparound is handled naturally:

If no previous timestamp of that type exists yet, the delta column shows an aligned placeholder:

-ts0delta does not calculate a delta value. It exists only to generate a matching placeholder column for trices without target timestamps, so absolute and delta columns can stay aligned independently.

An explicitly passed empty delta switch is treated as a hard disable for that stamp size:

18.2.3. Column Order

When both absolute and delta timestamps are enabled, the output order is:

  1. absolute timestamp column
  2. delta timestamp column
  3. message text

This applies independently for ts0, ts16, and ts32.

18.2.4. Independence from -ts

The general switch -ts still sets defaults only for:

It does not set defaults for:

Delta columns are therefore always explicit and opt-in.

18.2.5. Formatting Rules

The delta switches use the same general formatting logic as the corresponding absolute timestamp switches, but independently from them. Examples:

This means the absolute column and the delta column can use different formats and widths.

For the first delta value, trice prints the same aligned placeholder behavior described above: - for simple numeric directives, blank space for the built-in "us"/"ms" delta formats.

18.2.6. Special Case: -ts32 epoch

-ts32 supports epoch-based formatting for absolute 32-bit timestamps, for example:

-ts32=epoch
-ts32=epoch2006-01-02 15:04:05 UTC

This is intended only for absolute timestamps.

For -ts32delta, the input values are still treated as plain numeric 32-bit values, and the delta is computed from those raw values before any epoch formatting would apply. Therefore -ts32delta accepts numeric formats, not epoch formats.

Typical usage with epoch-based absolute timestamps is:

trice log -ts32=epoch -ts32delta="dt:%8d"

Here the absolute column shows human-readable UTC time, while the delta column shows the difference in seconds between consecutive 32-bit timestamps.

18.2.7. Automatic -ts0delta Placeholder

If -ts0delta is not passed explicitly at all, trice can derive it automatically from the active delta formats.

The generated placeholder is blank space with the width of the widest active -ts16delta or -ts32delta column.

For that width derivation:

This mechanism keeps the delta column aligned for messages without target timestamps even when the active delta columns use different widths.

If -ts0delta "" is passed explicitly, this automatic placeholder generation is disabled.

18.2.8. Typical Use Cases

Show only delta values instead of absolute 16-bit timestamps:

trice log -ts16="" -ts16delta="dt:%6d"

This suppresses absolute ts16 output and shows only the delta to the previous 16-bit timestamp.

Show both absolute and delta 16-bit timestamps in separate columns:

trice log -ts16="t:%6d " -ts16delta="dt:%6d "

Show absolute 32-bit timestamps as UTC epoch time and the delta in seconds:

trice log -ts32=epoch -ts32delta="dt:%8d "

Show absolute timestamps for all messages, but add a delta column only for 32-bit timestamps:

trice log -ts0="time:            " -ts16="time:%6d " -ts32=epoch -ts32delta="dt:%8d "

If alignment for messages without target timestamps should follow the delta column too, add -ts0delta explicitly:

trice log -ts0="time:            " -ts0delta="           " -ts16="time:%6d " -ts32=epoch -ts32delta="dt:%8d "

Use only a delta column and keep no-stamp lines aligned:

trice log -ts0="" -ts16="" -ts32="" -ts16delta="dt:%6d "

If -ts0delta is omitted, trice derives it automatically from the widest active delta column.

18.2.9. Example Screenshots

1) Add a column to show just the ts16 delta values (microseconds).

trice log -p jlink -args "-Device STM32G0B1RE" -pf none -prefix off -hs off -d16 -i ../../demoTIL.json -li ../../demoLI.json -ts16delta "uS:%6d"

Screenshot_2026-03-26_142458.png

2) Same as 1) but with continuously colored row.

trice log -p jlink -args "-Device STM32G0B1RE" -pf none -prefix off -hs off -d16 -i ../../demoTIL.json -li ../../demoLI.json -ts16delta "uS:%6d" -ts0delta "time:         "

Screenshot_2026-03-26_143209.png

3) Add a column to show just the ts32 delta values (microseconds).

 trice log -p jlink -args "-Device STM32G0B1RE" -pf none -prefix off -hs off -d16 -i ../../demoTIL.json -li ../../demoLI.json -ts32delta "att:%4d"

Screenshot_2026-03-26_144857.png

4) Show only ts16 absolute values in microseconds together with ts32delta values. Because -ts16delta "" is passed explicitly, ts16 lines get no delta placeholder. Lines without timestamps are decorated explicitly to show formatting options.

trice log -p jlink -args "-Device STM32G0B1RE" -pf none -prefix off -hs off -d16 -i ../../demoTIL.json -li ../../demoLI.json -ts32 "" -ts32delta "deb:%12d" -ts16 us -ts16delta "" -ts0 "rd:~~~~~~" -ts0delta "att:_____"

Screenshot_2026-03-26_140426.png

5) Show ts16 absolute values together with ts32delta values and explicit no-stamp separators. Again, -ts16delta "" suppresses any automatic placeholder on ts16 lines.

trice log -p jlink -args "-Device STM32G0B1RE" -pf none -prefix off -hs off -d16 -i ../../demoTIL.json -li ../../demoLI.json -ts32 "" -ts32delta "att:%12d" -ts16 us -ts16delta "" -ts0 "|    " -ts0delta "     |"

Screenshot_2026-03-26_150038.png

6) Show ts32 as epoch followed by ts32delta in seconds. (Hint: In translator.go inside function formatTargetStamp32 the call of correctWrappedTimestamp(uint32(timestamp)) was temporarily deactivated for this check)

trice log -p jlink -args "-Device STM32G0B1RE" -pf none -prefix off -hs off -d16 -i ../../demoTIL.json -li ../../demoLI.json -ts32 "epoch2006-01-02_15:04:05" -ts32delta "note:%4d" -ts0delta "           "

Screenshot_2026-03-26_152743.png

7) Like 6 but additionally show ts16delta in microseconds

trice log -p jlink -args "-Device STM32G0B1RE" -pf none -prefix off -hs off -d16 -i ../../demoTIL.json -li ../../demoLI.json -ts32 "epoch2006-01-02_15:04:05" -ts32delta "note:%5d" -ts16delta us -ts0delta "            "

Screenshot_2026-03-26_153316.png

18.2.10. Summary

The tsdelta switches make timestamp display more flexible by separating:

This allows trice output to be tailored for debugging, profiling, timing analysis, and mixed absolute/delta log views without changing the target-side encoding.

(back to top)

19. Binary Encoding

19.1. Symbols

Symbol Meaning
i ID bit
I iiiiiiii = ID byte
n number bit
z count selector bit
s stamp selector bit
N znnnnnnnn = count selector bit plus 7-bit number byte
c cycle counter bit
C z==0 ? cccccccc : nnnnnnnn = cycle counter byte or number byte extension
t (time)stamp bit
T tttttttt = (time)stamp byte
d data bit
D dddddddd = data byte
... 0 to 32767 data bytes
"..." format string
W bit width 8, 16, 32 or 64 (uW stands for u8, u16, or u64)
x unspecified bit
X =xxxxxxxx unspecified byte

19.2. Package Format

Example for Trices without timestamps

value byte offset type comment
IdLo 0 byte The first byte is always the Trice ID lower 8 bits.
IdHi 1 byte The second byte 2 most significant bits are 01 and the 6 least significant bits are the Trice ID upper 6 bits.
NC 2 u16 The most significant bit is the count selector bit z and usually 0, telling, that the following 7 bits are the payload byte count and that the 8 least significant bits are the cycle counter. If z is 1, the current Trice contains no cycle counter and has a 15-bit payload count instead (for payloads > 127).
payload 4 u8|u16|u32|u64 The payload contains a number of equal size values.

19.2.1. typeX0 Records

The user can insert any data with a well-defined structure into the Trice data stream. When interpreting the Trice binary data, the Trice tool handles selector-0/typeX0 records according to the CLI switch -typeX0.

One possible use case is to have user printi statements parallel to Trices (see Legacy User Code Option Print Buffer Wrapping and Framing). For the counted typeX0 variant, the user prepends a generated printi buffer with its payload size as a 16-bit count smaller than 16384. See ./_test/userprint_dblB_de_tcobs_ua/TargetActivity.c for an implementation option. See chapter 20. typeX0 User Packets for further details.

19.2.2. Framing - NONE or with COBS or TCOBS encoding

Summary Information for Trice Data Parsing

Details

Framing NONE Overview Table:

mode packed -pf= encr wr use pad stream remark
di single none32 NONE 32 32 0-3 aligned done
de single none8 NONE 8 8 0 compact done
de single none32 NONE 8 32 0 aligned plan
de multi none8 NONE 8 8 0 compact done
de multi none32 NONE 8 32 0 unknown forbid
de multi none NONE 8 32 0 unknown forbid
di single none64 XTEA 32 32 0-7 aligned done
de single none64 XTEA 8 8 0-7 aligned done
de single none64 XTEA 8 32 0-7 aligned plan
de multi none XTEA 8 8 0-7 unknown forbid
de multi none XTEA 8 32 0-7 unknown forbid

(back to top)

20. typeX0 User Packets

Trice already has buffer macros for transferring runtime data buffers. triceN transfers a byte buffer as a counted string, and trice8B, trice16B, trice32B, trice64B and triceB transfer buffers as sequences of equally sized values formatted on the host side. These macros are the preferred choice when the data belongs to a normal Trice message and should use the usual Trice ID, til.json entry and format string handling.

typeX0 is an additional, more decoupled way to move user data through the same Trice transport path. It uses selector bits 00 in the first 16-bit word, interpreted in the configured Trice byte order (default little endian), and therefore carries no 14-bit Trice ID. The host-side meaning is selected by the Trice tool option -typeX0=.... This makes it useful for user payloads that should share the same UART/RTT/file/framing interface as Trice messages, but should not require an ID, a til.json entry or a fixed Trice format string.

Important: The typeX0 packages do not influence the cycle counter and do not carry a cycle counter value (or you implement your own). They also do not carry a Trice ID, target timestamp or location information normally. If log metadata columns such as -showID, -li or target timestamps are enabled, X0 output keeps the column alignment but blanks the actual values.

The current implementation supports -typeX0=counted:<formatstring>. This is intentionally just one example implementation of the selector-0 extension space. Other interpretations, for example forwarding, extended counted buffers or application-specific binary formats, can be added later without changing regular Trice messages. The counted implementation is expected to cover most use cases.

20.1. Packet Classification

For each decoded record or package, the receiver first checks the available length:

len == 0:
    invalid or ignored transport artifact

len == 1:
    error: unsupported short packet

len >= 2:
    read first uint16 using the configured Trice byte order
    selector = firstWord >> 14

    if selector == 0:
        handle as typeX0 packet

    else if len < 4:
        error: unsupported short packet

    else:
        handle as regular Trice packet

Summary:

Packet length Selector Packet Type Trice Tool Action
0 - user0B ignored (reserved)
1 - user1B error (reserved)
2..3 != 0 user2B, user3B error (reserved)
>= 2 0 typeX0 according -typeX0 CLI
>= 4 != 0 regular Trice default

Packet length 0 is possible when framing like COBS is used and 2 delimiter bytes (usually 0) occur without a package in between.

Short user packets such as user0B, …, user3B are intentionally not supported. They can be added later if a concrete requirement appears. They do not carry length information and therefore cannot safely share a framed group with following records.

Short non-X0 user packets are not supported initially. They should be reported as errors and can be specified later if a real requirement appears.

20.2. The typeX0 Counted Format

A counted typeX0 record starts with one 16-bit word:

bits 15..14 = 00
bits 13..0  = count

The lower 14 bits contain the payload byte count:

firstWord = count
count     = firstWord & 0x3fff
payload   = record[2 : 2+count]

The logical payload length is exactly count bytes. Counts 0 and 1 are valid. Little endian examples without alignment padding are:

00 00                         count 0, empty payload
01 00 xx                      count 1, one payload byte, little endian example
02 00 xx yy                   count 2, two payload bytes, little endian example

The optional target helper src/triceX0.c writes into the normal Trice target buffer and keeps the next record 32-bit aligned. Therefore its physical buffer use is:

physicalRecordLen = align4(2 + count)

The zero padding bytes are not part of the payload. A host decoder shall use the count field to determine the payload and shall skip alignment padding where required by the decoded Trice buffer stream. Padding bytes written by the target helper are zero. The decoder consumes alignment padding only when the expected bytes are present and zero; otherwise following bytes can be the next record in a mixed package. Zero-only padding after a regular framed Trice message is removed before selector-0/typeX0 handling.

Malformed counted X0 examples are:

available bytes < 2 + count
alignment padding is consumed but not zero

Configuration errors such as an unsupported -typeX0 mode are reported separately.

20.3. CLI Option -typeX0

The Trice tool option is:

-typeX0=[mode:]<format>

Supported values:

-typeX0=error

Treat every typeX0 packet as an error after normal Trice padding has been removed. This is also the default when -typeX0 is not specified.

-typeX0=counted:ignore
-typeX0=ignore

Silently discard valid counted typeX0 packets. Malformed X0 packets are still errors. The second form is a shorthand for counted:ignore.

-typeX0=all:ignore

Silently discard the complete decoded selector-0 package without checking an X0 length. This is only valid for non-mixed X0 packages. If normal Trices or other counted X0 records are behind the first selector-0 word in the same decoded package, they are discarded too.

-typeX0=counted:<format>
-typeX0=<format>

Interpret typeX0 packets as counted payloads and print the payload with Go fmt. The second form is a shorthand for counted:<format>. The shorthand form is valid only when <format> contains no colon. If the format string contains a colon, use the explicit counted: prefix.

A future mode:argument prefix is a possible extension.

The format receives exactly one Go argument:

payload []byte

Conceptually:

fmt.Fprintf(out, format, payload)

Go fmt consumes arguments, not bytes. Therefore a normal format should contain one non-indexed formatting verb. To print the same payload more than once, use Go’s explicit argument index syntax.

Examples:

-typeX0="%s"

Print payload bytes as a string.

-typeX0="% x\n"

Print payload bytes as lower-case hex with spaces.

-typeX0="counted:X0: %q\n"

Print the payload quoted.

-typeX0="%[1]s %[1] x\n"

Print the same payload twice, once as string and once as spaced hex.

20.4. typeX0 Target Code

typeX0 is a free format selector-0 space the user can define. The only protocol requirement is that the 2 most significant bits in the very first uint16_t word, in the configured endianness, are zero. If the encoding carries length information, as the counted example does, multiple X0 records and normal Trice messages can be interleaved in one framed package. If no length information is encoded in the X0 record, each X0 record needs individual framing with COBS, TCOBS or another application-defined framing method.

triceX0.c contains the counted buffer reference implementation. The Trice tool then needs the CLI switch -typeX0=[mode:]<format>, where counted is the default mode for values without a colon. Examples:

CLI switch trice log ... Meaning
-typeX0=all:ignore ignore the complete non-mixed X0 package without length checking
-typeX0=counted:ignore just check for valid length and ignore
-typeX0=ignore just check for valid length and ignore (short for counted:ignore)
-typeX0=counted:"sig:%60s\n" print a right-aligned string with tag sig:
-typeX0="%60s\n" print a right-aligned string without a tag (short form, no colon)
-typeX0="sig:%60s\n" invalid sig: mode because shorthand cannot contain a colon
-typeX0=sig:"%60s\n" invalid sig: mode
-typeX0=forward:<ADDRESS> send X0 package to ADDRESS (not implemented)

Hint: Depending on shell quoting, these forms can all pass the same raw option value counted:sig:%60s\n to the CLI parser:

The typeX0 CLI parser takes the string in front of the first colon as typeX0 mode. Therefore format strings containing a colon cannot be given in the short form.

Known modes are:

This can be extended in many ways. typeX0 is just a way to mix application-defined binary data with Trice messages over the same output channel. Many cases are probably already covered by using Trice macros such as trice8B, trice16B, trice32B, trice64B, triceS, triceN, …

20.4.1. Target-side counted helper

The counted helper is optional target code and lives in:

src/triceX0.h
src/triceX0.c

The public function is intentionally small:

void triceX0(const void* buf, uint16_t len);

In the counted mode (default) it writes selector 00, stores the resulting payload length in the lower 14 bits and appends that many payload bytes. If len exceeds the supported range, the helper truncates to the smaller limit of TRICE_SINGLE_MAX_SIZE - 5 and 0x3fff, increments the dynamic buffer truncation diagnostic counter, and sends the truncated payload. It uses the normal Trice critical-section model with TRICE_ENTER / TRICE_LEAVE and the normal Trice output path. It does not use TRICE_PUT_BUFFER() after TRICE_PUT16(), because X0 has only a 2-byte header and the physical record size must be aligned as align4(2 + len).

For projects or tests that want this helper, define in triceConfig.h:

#define TRICE_TX_X0_COUNTED_BUFFER_SUPPORT 1

A project can also provide its own selector-0 writer. The counted helper is only the reference implementation for the -typeX0=counted:<format> use case.

20.4.2. typeX0 Build Switches

The counted typeX0 helper is controlled independently from the normal Trice macro switch:

#define TRICE_TX_X0_COUNTED_BUFFER_SUPPORT 1

When TRICE_TX_X0_COUNTED_BUFFER_SUPPORT == 1, the target-side function

void triceX0(const void* buf, uint16_t len);

is a real function and writes counted selector-0 records into the configured Trice output backend. The project must then compile src/triceX0.c together with the other Trice target sources. When TRICE_TX_X0_COUNTED_BUFFER_SUPPORT == 0, triceX0() is an inline no-op helper and no X0 backend code is needed.

This switch is intentionally independent from TRICE_OFF:

TRICE_OFF TRICE_TX_X0_COUNTED_BUFFER_SUPPORT Setting
== 0 == 0 normal Trice macros are active, triceX0() is a no-op
== 0 == 1 normal Trice macros are active, triceX0() emits counted X0 records
== 1 == 0 normal Trice macros are off, triceX0() is a no-op
== 1 == 1 normal Trice macros are off, triceX0() still emits counted X0 records

The 4th combination is useful for applications that want to use only selector-0 user packets while keeping all normal Trice statements compiled out. The Trice tool can still scan normal Trice statements in the source code for ID maintenance, but the compiler receives no normal Trice output code from them.

TRICE_CLEAN is a tool-managed source state and should not be used as an application switch. trice clean may set it to 1 to make cleaned source files compile without inserted IDs and without editor warnings; trice insert sets it back to 0. With TRICE_TX_X0_COUNTED_BUFFER_SUPPORT == 1, the counted X0 helper is intended to compile in both states. Normal Trice macros remain disabled while TRICE_CLEAN == 1, but triceX0() stays available as a real function.

In short:

A project configuration that wants counted X0 support and still allows command-line overrides can use:

#ifndef TRICE_TX_X0_COUNTED_BUFFER_SUPPORT
#define TRICE_TX_X0_COUNTED_BUFFER_SUPPORT 1
#endif

Then builds can explicitly test or select the behavior with compiler defines such as:

-DTRICE_OFF=1 -DTRICE_TX_X0_COUNTED_BUFFER_SUPPORT=1
-DTRICE_OFF=1 -DTRICE_TX_X0_COUNTED_BUFFER_SUPPORT=0

20.4.3. typeX0 Usage in ./examples/G0B1_inst

alt text

The point here is that with CLI switch -typeX0=ignore the counted X0 packages are invisible after their length has been checked. A future -typeX0=forward:<ADDRESS> option could be implemented as a trice log extension.

20.5. Go implementation layout

Keep the typeX0 mode parsing and handling centralized so later modes can be added without spreading switch logic through the decoder.

Code layout:

internal/args/...
    define CLI flag -typeX0 and help text

internal/decoder/typeX0.go
    hold the configured TypeX0 value
    parse error, ignore, all:ignore, counted:<format>, <format>
    format counted payloads with Go fmt
    reject unsupported mode prefixes clearly

internal/trexDecoder/trexDecoder.go
    detect selector == 0
    pass the remaining record buffer to the typeX0 helper
    append returned output to the decoder result
    consume the logical X0 record and zero alignment padding when present

Unsupported future modes shall fail with a clear diagnostic, for example:

unsupported typeX0 mode "forward"

This structure keeps future extensions local. For example:

-typeX0=forward:<ADDRESS>

could later forward valid X0 payload bytes to another sink instead of formatting them.

20.6. Tests

The counted X0 path is tested through the existing _test/testdata/triceCheck.c mechanism, because these test lines are processed by many Trice configurations.

The shared test configurations that build triceCheck.c define:

#define TRICE_TX_X0_COUNTED_BUFFER_SUPPORT 1

src/triceX0.c is compiled into the CGO test target through the master file _test/testdata/cgoPackage.go. Generated generated_cgoPackage.go copies are refreshed from that master file by scripts/_330_renew_ids_and_refresh_tests.sh.

The X0 block in _test/testdata/triceCheck.c is guarded by TRICE_TX_X0_COUNTED_BUFFER_SUPPORT == 1 and intentionally mixes different counted X0 lengths with normal Trices:

#if TRICE_TX_X0_COUNTED_BUFFER_SUPPORT == 1
        break; case __LINE__: triceX0(x0Payload, 0);
        break; case __LINE__: triceX0(x0Payload, 5); trice8B("wr:X0-B: %02x\n", x0Payload, 5);
        break; case __LINE__: triceX0(x0Payload, 2); triceX0(x0Payload + 2, 4); trice("wr:X0 tail\n");
#endif

The current shared CGO test option formats the X0 payload with the sig: prefix:

counted:sig:% x\n

For maintenance, keep these parts aligned:

  1. The master CGO file includes ../../src/triceX0.c.
  2. triceCheck.c uses TRICE_TX_X0_COUNTED_BUFFER_SUPPORT == 1 as guard.
  3. The X0 test block uses different lengths and mixed packages with trice, triceS, TriceS, trice8B, Trice8B and TRice8B.
  4. _test/.../triceConfig.h files that build triceCheck.c enable TRICE_TX_X0_COUNTED_BUFFER_SUPPORT.
  5. _test CGO checks set decoder.TypeX0 to counted:sig:% x\n.
  6. The full _test/ matrix remains the final coverage check.

Additional focused Go unit tests cover:

no -typeX0        -> X0 packet is an error
-typeX0=error     -> X0 packet is an error
-typeX0=ignore    -> valid X0 packet produces no output
-typeX0=all:ignore -> complete selector-0 package is consumed without counted parsing
-typeX0="%s"      -> payload is printed as string
-typeX0="[%s]"    -> payload is printed with formatting
-typeX0="% x"     -> payload is printed as spaced hex
-typeX0=counted:sig:%s -> explicit counted mode is needed when the format contains a colon
-typeX0=sig:%s   -> unsupported mode "sig"
malformed X0      -> error, also with -typeX0=ignore
unsupported mode  -> clear error
mixed package     -> counted X0 can be followed by a regular Trice in framed data
NONE framing      -> counted X0 consumes zero alignment padding when present

20.7. Initial scope

The initial implementation includes only:

selector-0 detection for records with len >= 2
counted X0 payloads
-typeX0=error as default
-typeX0=counted:ignore
-typeX0=ignore as counted shorthand
-typeX0=all:ignore
-typeX0=counted:<format>
-typeX0=<format> as counted shorthand only when <format> contains no colon
errors for unsupported short non-X0 packets
clear errors for malformed X0 packets and unsupported modes

No counted32, forward, JSON descriptor, plugin interface or user1B / user2B / user3B handling is part of the initial scope.

(back to top)

21. Trice Decoding

The 14-bit IDs are used to display the log strings. These IDs are pointing in two reference files.

21.1. Trice ID list til.json

21.2. Trice location information file li.json

(back to top)

22. Trice ID Numbers

22.1. ID number selection

22.1.1. Trice tool internal Method to get fast a random ID

22.2. ID number usage and stability

22.3. Trice ID 0

(back to top)

23. Trice ID management

23.1. Trice inside source code

23.1.1. Trice in source code comments

23.1.2. Trice parser exclusion markers

Use TRICE_INSERT_OFF and TRICE_INSERT_ON markers to exclude a source section from trice insert, trice clean, trice add and ID refresh parsing.

// TRICE_INSERT_OFF
TRice("This text is ignored by the trice tool.");
// TRICE_INSERT_ON

The markers are case-sensitive, must be written as comments, and affect only the Trice tool parser. TRICE_INSERT_OFF without a following TRICE_INSERT_ON disables Trice parsing until the end of the file. The marker state is local to the scanned file and does not affect source files including that file. This is useful for Trice target sources or other source sections containing Trice-like comments or helper macros that should not create or change IDs.

23.1.3. Different IDs for same Trices

Every active textual Trice site needs its own ID so that location information remains unambiguous. Copying a call with its explicit ID does not create shared ownership: trice insert resolves the duplicate and allocates or reuses another eligible ID.

Both trice insert and trice bind then order identical, interchangeable Trices by normalized relative file path, numeric line number, and position from left to right within the line. Paths use / separators and an ordinal, case-sensitive comparison (A.c precedes a.c), independent of locale. The root is -liRoot, or the directory containing the selected LI file by default; the absolute checkout directory does not determine the order. Overlapping -src roots visit each physical path only once.

Within each group, the IDs already selected by normal allocation are sorted numerically ascending and assigned to the sites in that order. Identical means the same normalized transport type and exact canonical format, including any structured field schema and Context Enrichment. Equal visible messages with different types or field names are separate groups. Existing tag-specific ID ranges still apply. New ID selection through -IDMethod random, upward, or downward is unchanged: this rule orders the selected pool; it does not introduce a new global allocation strategy or renumber unrelated Trices.

For example, assume these three IDs belong to one group:

Source state Site in path order Assigned ID
Before b.c:10 100
Before d.c:20 200
After adding a.c:5, with ID 300 also selected a.c:5 100
After b.c:10 200
After d.c:20 300

Both existing sites may change IDs when an identical site is added, removed, or moved. This is intentional. An unchanged source selection with the same selected ID pool produces the same mapping on subsequent runs, regardless of scan-root order or worker completion. The same rule applies after Clean/Insert; Clean itself does not allocate IDs. An explicit ID in an Insert source is not a request to exempt that site from ordering. Bind changes its own generated descriptors and leaves Insert-owned source files and their IDs alone.

A partial scan orders only its selected sites. IDs recorded in the primary LI as belonging to files outside that selection, including excluded files, are reserved and cannot be taken by the selected sites. Keep the shared LI available for partial scans: without location data, ownership of unscanned IDs cannot be inferred from TIL alone. For one order across the whole project, scan the complete intended source tree. A rename can leave the old path recorded in LI; its ID stays reserved while another eligible ID is selected for the new path.

The final assignment is written consistently to Insert sources or Bind sidecars and to LI. Historical TIL mappings remain available for older recordings. Rebuild firmware after an assignment changes and retain the matching metadata for recordings whose exact old source positions matter.

The behavioral tests cover group changes, partial scans, path and column order, cache reuse, and ownership. The target/decoder integration checks actual C/C++ records from both workflows against the generated mapping and decoded fields.

23.1.4. Same IDs for different Trices

23.1.5. ID Routing

With trice insert or trice bind, -IDRange tag:min,max assigns a range to a complete tag group. Register a free tag with -ulabel first; option order does not matter. Groups without a specific range use -IDMin through -IDMax.

Tag-specific ranges include both endpoints and must satisfy 1 <= min <= max <= 16383. They must not overlap the general range or any other tag range. A shared endpoint is an overlap; adjacent disjoint ranges and a range containing one ID are valid. Two aliases of the same group cannot define two separate ranges. Missing parts, malformed numbers, unknown tags, reversed bounds, and out-of-range values are rejected before source, dictionary, or bind-artifact changes. All supplied rules are validated together.

trice insert -src src -IDMin 1000 -IDMax 1999 -IDRange err:10,99

The current policy also applies to IDs recovered from sources, location data, or the dictionary. An active Error site with ID 250 is reassigned to a usable ID in 10..99 on the next run. Its old mapping remains in til.json so recordings from older firmware can still be decoded. A subsequent unchanged run retains the corrected ID. Only the selected source scope is processed. Bind updates its generated descriptors; an insert-owned source requiring correction must first be processed by trice insert. Rebuild affected firmware after reassignment.

With -v, each run reports historical dictionary entries outside the current policy in one additional warning: their total count, the smallest affected ID as one example, and its expected range. The historical 250 remains part of that count after reassignment; the new compliant ID does not. No warning is emitted without -v or when every entry complies. The warning neither changes the dictionary nor turns a successful command into a failure. A historical entry alone does not prove that an ID is still active.

Target routing is configured separately in the project-specific triceConfig.h. For the enabled deferred UARTA, UARTB, Auxiliary8, Auxiliary32, and SEGGER RTT 8-bit outputs, the corresponding *_MIN_ID and *_MAX_ID select inclusive ranges. Missing bounds default to zero. 0/0 disables ID selection for that output, so its ordinary output path accepts all IDs; it does not disable the output itself. Exactly one nonzero bound, reversed bounds, or bounds outside 1..16383 cause a compile-time error naming the defines.

Active deferred ID selection requires TRICE_DEFERRED_TRANSFER_MODE to be TRICE_SINGLE_PACK_MODE, with both ring and double buffers. Multi-pack is allowed when no ID range is active. This requirement concerns deferred ID routing, not TCOBS framing in general or custom/direct routing. See triceDefaultConfig.h for the output-specific define names.

23.1.6. Possibility to create new tags without modifying trice tool source

According to the demand in 541 a CLI switch -ulabel exists now.

Use -ulabel for additional user labels. Try this example in an empty folder:

#include "trice.h"

int main(void){
    trice("msg:hi\n");
    trice("man:hi\n");
    trice("wife:hi\n");
    trice("any:hi\n");
}
touch til.json li.json
trice i -IDMin 1004 -IDMax 6999 -IDRange wife:16000,16009 -IDRange man:1000,1003 -ulabel man -ulabel wife
#include "trice.h"

int main(void){
    trice(iD(5778), "msg:hi\n");
    trice(iD(1002), "man:hi\n");
    trice(iD(16004), "wife:hi\n");
    trice(iD(2184), "any:hi\n");
}

(back to top)

24. Trice Bind

This chapter is the reference for the current trice bind workflow, supported source constructs and limitations. Ordinary sites use file-and-line binding; supported ambiguous sites use automatic local counter rebasing. The restrictions for Context Enrichment are narrower and are explained under Bind Limits.

This chapter describes the supported workflow, generated files, compiler requirements and troubleshooting. Use Bind Limits to choose between Bind and the explicit-ID insert/clean workflow for your source constructs.

24.1. Overview

trice bind assigns and manages stable Trice IDs without writing numeric IDs into bind-managed user Trice calls.

For example, the user code remains:

trice("msg:module initialized\n");

trice bind scans the project sources, uses the existing ID management with til.json and li.json, and generates one temporary sidecar header for each bind-managed source or header file. The compiler still compiles the original sources directly.

The normal workflow is:

trice bind
Build

A subsequent trice clean is not required in a stable bind project.

trice bind must be run after every change to a scanned source or header file and before the build. An outdated sidecar may still compile in some cases but contain an ID that no longer matches.

24.2. Requirements

A bind project requires:

The default directory is:

./generated

trice bind creates the directory when needed. It is normally not version-controlled.

24.3. Quick Start

24.3.1. New ID-Free Project

  1. Write Trice calls without numeric IDs:

    trice("msg:start\n");
    
  2. Run trice bind:

    trice bind [shared insert options]
    
  3. Add the sidecar directory to the compiler include path:

    -I./generated
    
  4. Build the project.

trice bind adds a file-local include to bind-managed files, for example:

#include "trice_module_c_K73A915E9C4021B8.h" // trice-bind: keep as last include before this file's Trice calls

24.3.2. Migration from trice insert

For a project previously managed entirely with insert:

trice clean
trice bind
Build

trice clean removes inserted IDs. trice bind then generates file keys, sidecar includes, and sidecars.

24.4. Persistent and Generated Files

The following files and lines are persistent and normally version-controlled:

The following are generated and normally not version-controlled:

The owner include line stores the stable file key. The sidecar and rebase helper headers can be regenerated by trice bind at any time. Rebase include pairs are validated and logically removed during every bind run, then regenerated from the current source analysis. Formatter-owned horizontal whitespace is retained when the regenerated boundary has the same identity. The lines must not be moved, renamed, or partially edited manually.

If the last managed Trice call is removed from a previously bound file, its owner include and file key remain. Bind generates an owner sidecar without site descriptors, preserving the file identity for later calls. This does not allocate an ID for an absent log site.

24.5. Hierarchical Metadata Reuse

Each trice bind invocation has one writable primary TIL, LI, and generated-file directory selected with -genDir. Files selected by -src may be individual files or directories. Existing valid File Keys remain unchanged; a bind-owned file without a File Key receives one when needed.

For each source, bind performs a bounded search from its directory up to its -src anchor, optionally one level higher, and around the configured TIL and LI paths. Hidden directories such as .git and .trice are ignored. Immediate *.json files are recognized as TIL or LI data by their contents, so custom names such as demoIDs.json work without another option.

Discovered JSON and historical sidecars in build/triceIDs are read-only evidence. Sidecars are parsed to recover earlier assignments but are never copied because their line descriptors may be stale. Current sidecars are always regenerated from the current source into the selected generated-file directory.

The primary TIL always wins a numeric-ID conflict. A conflicting subproject ID quietly yields to another matching or newly allocated primary ID; -verbose explains such decisions. A conflict-free historical ID is retained and only its actively used mapping is added to the primary TIL. Secondary TILs, LIs, and build artifacts are never modified.

For repeated identical Trice calls (the same normalized type and exact format string) in one file, a valid sidecar assignment takes precedence over conflicting LI positions when selecting ID candidates. A sidecar in the current build directory has priority over discovered sidecars. File modification times do not decide ownership. TIL format compatibility and existing file ownership still have to match. Candidate selection is followed by the identical-Trice ordering rule, which determines the final per-site assignment.

Without a usable sidecar assignment, LI candidates for repeated calls are consumed in stored line order as the current calls are visited in source order. Metadata search priority is retained; equal stored lines are ordered by numeric ID. Bind does not independently choose the closest old line for each repeated call. A single current call still uses line proximity to select among matching LI candidates. These rules choose the usable pool, not its final permutation: if IDs 15982 and 15849 are selected for two identical calls, the earlier site receives 15849 and the later site 15982.

li.json stores one position per ID, not a sequence of past versions. Bind writes the newly assigned positions back to the primary LI. The final ordering uses the already parsed source sites; it requires no additional source scan. Adding, removing, or moving identical calls may deliberately change their earlier IDs. The original per-call association cannot be recovered from TIL alone and is not preserved across such changes.

All discovery and conflict resolution completes before regular output is written. A fatal ambiguity therefore leaves sources, JSON files, and generated outputs unchanged. The complete normative implementation strategy and fallback order are documented in internal/id/bindIDs_doc.go.

24.6. File Key and Sidecar Name

Every bind-managed file receives a randomly generated 64-bit key once:

K73A915E9C4021B8

Example:

module.c
→ trice_module_c_K73A915E9C4021B8.h

The base name improves readability. The key also distinguishes files with identical names in different directories.

The key has a K prefix followed by 16 uppercase hexadecimal digits. It is generated once with crypto/rand and retained when the file contents, path or name change. The readable sidecar name normalizes characters outside [A-Za-z0-9_] to _; the key supplies its identity. The key itself is not a transmitted Trice ID and adds no target runtime data.

If a source is copied together with its sidecar include line, both files initially have the same key. trice bind detects this as a conflict; one of the files must receive a new key.

Including the same header in multiple translation units is expected and supported.

24.7. Sidecar Contents

A sidecar may look like this:

/// \file trice_module_c_K73A915E9C4021B8.h
/// \brief Generated by trice bind. Do not edit.

#undef TRICE_BIND_FILE_KEY
#define TRICE_BIND_FILE_KEY K73A915E9C4021B8
#define TRICE_BIND_ROUTE_K73A915E9C4021B8 BIND

// -defaultStampSize 16
#define TRICE_BIND_SITE_K73A915E9C4021B8_L9 TRICE_BIND_AUTO, Id(12345u) // TRICE("Hello");
#define TRICE_BIND_SITE_K73A915E9C4021B8_L10 TRICE_BIND_REPLACE, id(12346u) // TRICE(id(0), "world");
#define TRICE_BIND_SITE_K73A915E9C4021B8_L11 TRICE_BIND_AUTO, iD(12347u) // trice("!\n");

Meaning:

During preprocessing, the ID becomes a normal compile-time constant. The target needs neither a string lookup nor an additional runtime mapping table.

Owner sidecars intentionally have no conventional include guard: including an owner sidecar again must reactivate that physical file’s bind context. Do not add a guard or maintain generated descriptors manually.

24.8. Why the Sidecar Include Is File-Local

The include does more than provide ID definitions. It activates the bind context of the physical file for the following Trice calls.

Within one translation unit, sidecars for several headers and the .c file can become active in sequence. Therefore, each file must activate its own file key before its own Trice calls.

Typical .c file:

#include "trice.h"
#include "module.h"
#include "driver.h"
#include "trice_module_c_K73A915E9C4021B8.h" // trice-bind: keep as last include before this file's Trice calls

void moduleInit(void)
{
    trice("msg:module initialized\n");
}

A central include of all sidecars in triceConfig.h cannot replace this local selection.

24.9. Headers and static inline

Headers containing direct Trice calls receive their own sidecar:

#ifndef MODULE_H
#define MODULE_H

#include "trice.h"
#include "dependency.h"
#include "trice_module_h_K1111111111111111.h" // trice-bind: keep as last include before this file's Trice calls

static inline void moduleCheck(int value)
{
    trice("msg:value=%d\n", value);
}

#endif

All translation units use the same stable ID for this textual header site.

For reusable logging helpers with multiple Trice sites, a static inline function is normally preferable to a preprocessor macro. Its Trice sites can use the normal file-and-line path and therefore need no rebase includes at each call site. Preferred Form: Normal or static inline Function shows the recommended implementation and the semantic differences.

After the header include, the .c file activates its own file key again through its own sidecar.

The same owner sidecar may be included several times within a file if a later header switches the active file key.

24.10. Automatic Include Position

If the sidecar include is missing, trice bind uses a conservative heuristic:

  1. It finds the last include before the first bindable Trice site.
  2. It inserts the sidecar immediately after that include.
  3. If there is no preceding include, it inserts the sidecar directly before the first bindable Trice site.
  4. If a bindable Trice site appears before a later include, no unsafe automatic change is made; the user receives a diagnostic.

An existing valid include is not moved unnecessarily.

The // trice-bind: ... comment is a developer aid. Technical detection uses the include directive, sidecar name, and file key; removing only the comment is allowed.

24.11. File Classification and Mixed Projects

trice bind classifies every physical file.

24.11.1. Insert-Owned

All managed Trice calls have explicit IDs greater than zero:

trice(iD(123), "msg:legacy\n");

trice bind validates the file but does not modify it or generate a sidecar.

24.11.2. Bind-Owned

The file contains only ID-free calls and/or zero placeholders:

trice("msg:bound\n");
TRICE(ID(0), "msg:bound with stamp\n");

trice bind manages the file key, include, and sidecar.

24.11.3. Mixed

A file contains both forms:

trice(iD(123), "msg:legacy\n");
trice("msg:new\n");

This state is not allowed. The file must be managed entirely by either insert or bind. Regions excluded with TRICE_INSERT_OFF and TRICE_INSERT_ON are not managed and do not contribute to this classification.

24.11.4. Insert-Owned File After a Bind Header

A bind-owned header can be included by an insert-owned file. Before its own explicitly instrumented Trice calls, the file must remove the bind context:

#include "bound_header.h"

#undef TRICE_BIND_FILE_KEY

trice(iD(123), "msg:insert-owned source\n");

This hybrid case is possible but is not the preferred normal workflow.

24.12. Supported Trice Calls

trice bind uses the same parser, ID assignment and user-level macro detection as trice insert.

In particular, the following are supported:

TRICE_INSERT_OFF and TRICE_INSERT_ON behave as they do with trice insert.

24.13. ID and Stamp Forms

24.13.1. ID-Free

trice("msg:hello\n");
TRICE8_3("msg:%d %d %d\n", a, b, c);

For macro names containing at least one lowercase letter, the sidecar uses iD(...).

For all-uppercase user-level macros, -defaultStampSize determines the form:

24.13.2. Zero Placeholders

TRICE8_3(id(0), "msg:%d %d %d\n", a, b, c);
TRICE8_3(Id(0), "msg:%d %d %d\n", a, b, c);
TRICE8_3(ID(0), "msg:%d %d %d\n", a, b, c);

The wrapper form is retained; semantically, the sidecar replaces only the zero with the stable ID. An explicit zero placeholder therefore takes precedence over -defaultStampSize. Zero placeholders are supported on ordinary line-addressable sites, not in counter-selected regions.

24.14. Command Line

Basic form:

trice bind [options]

Short form:

trice b [options]

In general, bind accepts the insert options relevant to source search, parsing, ID assignment, alias handling, til.json, and li.json.

bind, insert, and generate accept -genDir:

-genDir string
    Directory for generated Trice files, relative to the invocation directory.
    Default: ./generated

bind writes sidecar headers and trice-fields.txt there; insert writes trice-fields.txt. generate -logC reads bind sidecars there and writes til.c there when no output path is supplied. generate -onelineJSON writes <name>.oneline.json views there; generate -abc target writes target.h and target.c there. Explicit -logC=path/file.c and -abc path/target paths retain their own location. Add ./generated to the compiler include path when building bound sources. -buildDir and -bindDir are no longer accepted.

Bind automatically excludes its selected generated-file directory from the source scan. This prevents generated descriptors from being treated as new user log sites, including when -src names a parent directory.

With:

trice bind -dry-run

planned changes are calculated and displayed, but no user, JSON, or sidecar files are written.

24.15. Build Integration

trice bind is a required generator step before compilation:

Source change
→ trice bind
→ C/C++ build

The build system should:

The generator replaces a sidecar file only if its contents change. This keeps incremental builds limited to the translation units that are actually affected.

24.16. TRICE_CLEAN

TRICE_CLEAN remains optional. trice bind does not introduce a new global TRICE_MODE.

If TRICE_CLEAN exists in triceConfig.h:

24.16.1. trice clean After trice bind

Bind-owned Trice calls already contain no IDs greater than zero. Therefore, trice clean does not remove IDs from them and deletes neither sidecar includes nor sidecars.

With an existing definition:

#define TRICE_CLEAN 0

trice clean changes it as before to:

#define TRICE_CLEAN 1

The library then uses the clean/off path. Existing sidecars remain as artifacts but do not produce normal logging code.

Without TRICE_CLEAN, running trice clean after a bind run has practically no effect:

Running trice bind again resets an existing definition to 0 and updates the sidecars.

TRICE_CLEAN=1 disables logging; it does not make an included header optional. If an owner sidecar or rebase helper is physically missing, the compiler may still report a missing include even in a disabled build. Regenerate the files with trice bind before compiling; do not delete isolated include lines to silence the error.

24.17. Re-Migration to trice insert

A public re-migration subcommand is still not part of the normal user workflow. However, the repository helper script for returning to the clean state uses the same validated bind re-migration as the tests. It removes complete rebase include pairs, their helper headers, sidecar includes, and owner sidecars together, and corrects the affected lines in li.json.

Anyone performing the return manually must remove all related artifacts and must not leave behind an individual begin or end include line. The previous workflow then follows:

trice insert
Build

24.18. Automatic Local Counter Rebase

Automatic local counter rebasing does not add another command to the normal workflow:

trice bind
Build

The user neither sees nor maintains counter values or local ordinals. __COUNTER__ is used only as a local compile-time selector; its value is never a Trice ID.

24.18.1. When the Normal Line Path Is Sufficient

Unambiguous sites continue to use only the file key and __LINE__. These include:

Such source and header files receive no counter guard, rebase boundaries, or rebase helper headers. A compiler without __COUNTER__ can build them as before.

24.18.2. When trice bind Rebases Locally

A local rebase is generated automatically only where file and line cannot distinguish an expansion unambiguously:

trice("msg:first\n"); trice("msg:second\n"); trice("msg:third\n");

This also applies to wrappers with several inner Trice sites, multiple wrapper calls on the same physical line, and a wrapper call written across several lines. Before the smallest safely enclosing region, the generated code captures a local counter base value. Immediately afterwards, it restores the normal bind path. Earlier counter consumption in the same translation unit is therefore irrelevant.

The smallest region is normally exactly one physical source line. Two independent adjacent lines are deliberately not combined into a common rebase. A larger region could save include lines, but it could also contain unrelated macro expansions, conditional compilation, or an additional indirect use of __COUNTER__. A local change would then unnecessarily affect several log sites.

One necessary exception is a single, syntactically connected wrapper call whose argument list spans several physical lines:

LOG_ERROR(
    determineErrorCode(
        fileHandle,
        operation
    )
);

A preprocessor directive such as #include must occupy its own physical line and cannot be inserted into an open argument list. Therefore, the minimal rebase in this case covers the complete call from the macro name through the terminating semicolon. This does not combine independent statements; it is the smallest syntactically possible enclosure of a single call.

24.18.3. Concrete Wrapper Example

An ordinary statement macro can be defined and used as follows:

#define LOG_ERROR(value)                                      \
    do {                                                      \
        switch (value) {                                      \
        case 0:                                               \
            break;                                            \
        case 7:                                               \
            trice("cannot open file\n");                     \
            break;                                            \
        default:                                              \
            trice("error=%d", 8);                             \
            break;                                            \
        }                                                     \
    } while (0)

void report(int status)
{
    LOG_ERROR(status);
    LOG_ERROR(7); LOG_ERROR(8);
}

The two Trice sites in LOG_ERROR receive a total of two stable IDs. The first ID permanently belongs to cannot open file, and the second to error=%d. Every wrapper call uses these two definition IDs in definition order; it does not create additional IDs. The fact that only one switch branch executes at runtime does not change the preprocessor order.

For both IDs, li.json points to the respective inner definition site in the macro. The call sites contain only generated selection descriptors.

24.18.4. Preferred Form: Normal or static inline Function

If a logging helper does not need genuine preprocessor functionality, it should preferably be written as a normal or static inline function. The recommended form of the previous example is:

static inline void logError(int value)
{
    switch (value) {
    case 0:
        break;
    case 7:
        trice("cannot open file\n");
        break;
    default:
        trice("error=%d", 8);
        break;
    }
}

void report(int status)
{
    logError(status);
    logError(7);
    logError(8);
}

The two Trice calls now occupy two unambiguous physical lines inside the function. They use the normal file-key-plus-__LINE__ path. The three logError call sites require neither local counter scopes nor additional begin/end includes. This minimizes the source insertions made by trice bind and the number of generated rebase helper headers. A target compiler without __COUNTER__ can also compile this construct.

static inline does not imply that a runtime function call must be generated. Depending on optimization, size, and target architecture, common C and C++ compilers can insert the function directly at the call site. Whether they actually inline it remains a compiler decision; the stable Trice ID does not depend on that decision.

For a definition in a header file, the sidecar belongs to the header. All translation units use the same two textual Trice sites and therefore the same stable IDs. For a definition in a .c file, the sites belong to that .c file accordingly.

When converting a macro to a function, observe the normal C/C++ differences:

For ordinary helpers such as LOG_ERROR(value), which merely select among several fixed Trice messages based on a value, static inline is the most robust and simplest form. A logging macro with several inner Trices should be used only when its preprocessor semantics are actually required.

24.18.5. Headers and Translation Units

If LOG_ERROR is defined in logging.h and called from several .c files, its definition IDs remain identical in all translation units. The rebase for a call resides in the respective calling file.

If the wrapper call or a line with several direct Trices is itself located in a header, the rebase is also located in that header. Every translation unit that processes this exact header region then requires __COUNTER__. Unrelated files and ordinary headers do not become counter-dependent. The local base value makes the number of counter values consumed before the header irrelevant.

24.18.6. Compact Source Boundaries and Generated Helper Headers

An affected single-line statement is enclosed by exactly two clearly marked include lines:

#include "trice_module_c_K73A915E9C4021B8_R0_begin.h" // trice-bind: generated rebase begin K73A915E9C4021B8_R0
trice("first"); trice("second");
#include "trice_module_c_K73A915E9C4021B8_R0_end.h" // trice-bind: generated rebase end K73A915E9C4021B8_R0

One affected user line therefore becomes three source lines. Scope definitions, phase macros, and cleanup directives that older generator versions exposed directly in the source are now located entirely in the two generated helper headers under ./generated. The begin file captures the local counter base and activates the appropriate selection descriptor. The end file checks the consumed counter count and restores the normal bind path.

Two independent lines remain two independent regions:

#include "trice_module_c_K73A915E9C4021B8_R0_begin.h" // trice-bind: generated rebase begin K73A915E9C4021B8_R0
trice("first"); trice("second");
#include "trice_module_c_K73A915E9C4021B8_R0_end.h" // trice-bind: generated rebase end K73A915E9C4021B8_R0
#include "trice_module_c_K73A915E9C4021B8_R1_begin.h" // trice-bind: generated rebase begin K73A915E9C4021B8_R1
trice("third"); trice("fourth");
#include "trice_module_c_K73A915E9C4021B8_R1_end.h" // trice-bind: generated rebase end K73A915E9C4021B8_R1

trice bind does not combine these lines even when they are adjacent. Saving two include lines does not justify increasing the region affected by the counter. In particular, an unrelated macro between two log lines must never endanger the mapping of both lines together.

A single multiline wrapper call, however, is enclosed as one syntactic unit:

#include "trice_module_c_K73A915E9C4021B8_R2_begin.h" // trice-bind: generated rebase begin K73A915E9C4021B8_R2
LOG_ERROR(
    determineErrorCode(
        fileHandle,
        operation
    )
);
#include "trice_module_c_K73A915E9C4021B8_R2_end.h" // trice-bind: generated rebase end K73A915E9C4021B8_R2

The boundary appears before the call’s first line and after the line containing its semicolon. No directive is inserted into the open argument list. If this minimal region itself contains a preprocessor directive, another unassignable Trice site on a boundary line, or a __COUNTER__ expansion that cannot be safely bounded, trice bind rejects the site and changes no regular output files.

A rebase over an entire function, several independent statements, or the whole file is deliberately not generated. Technically, such a region could work as long as exactly the expected Trice macros—and no other expansion—consume __COUNTER__. In practice, every included line increases the dependency on unrelated macros, build configurations, and conditional compilation. Minimal enclosure limits a possible error to exactly one source site, and the final check turns a discrepancy into a compiler error instead of a silently incorrect ID.

The include lines and helper headers are related generator artifacts. They must not be moved, renamed, split, or deleted individually. After relevant source changes, trice bind must run again; the generator validates existing artifacts and transactionally updates source boundaries, helper headers, descriptors, IDs, and location information. Another bind run can restore missing helper headers. Modified helper headers are rejected with a diagnostic instead of being silently overwritten. Helper headers that are no longer needed are removed.

Older multiline rebase blocks from a previous generator version are still recognized. A successful new bind run replaces them with the compact include boundaries.

24.18.7. Missing __COUNTER__

Only the affected rebase region contains a capability guard. If __COUNTER__ is unavailable, the target compiler stops with a message explicitly stating that normal bind sites are unaffected.

The available alternatives are:

The static inline form is especially advisable for frequently called LOG_ERROR-style helpers: one function definition replaces rebase includes and helper headers at every individual call site.

Compile-time checks also detect additional counter consumption within the region, an incorrect expansion count, and missing generated descriptors. Such discrepancies stop the build instead of silently selecting a different ID.

24.18.8. Unchanged Interfaces

The rebase introduces no mutable runtime state, dynamic allocation, or runtime ID table. til.json, li.json, the Trice wire format, decoders, and public CLI options remain unchanged.

TRICE_CLEAN=1 and TRICE_OFF=1 still disable the Trice macros completely. Generated rebase helper headers are processed without a counter check in these build modes, so a disabled build does not require __COUNTER__, even for a file that would otherwise depend on it.

24.19. Supported Boundaries and Remaining Limitations

ID-free Trice calls with a statically and directly recognizable format string are supported, as are ordinary function-like statement macros with one or more direct Trice calls. A single wrapper call may span several physical lines if its complete region through the semicolon can be enclosed unambiguously and safely.

Within a counter-selected region, the following are still rejected with a precise diagnostic:

Zero placeholders on ordinary bind sites that are unambiguous by line remain supported. Format strings must still be statically recognizable within the scope of the shared insert/bind parser.

For an unsupported site, trice bind does not silently fall back to insert and does not write a numeric ID into the user Trice call.

24.19.1. bind-limits

When bind rejects a source construct, it cannot safely map or support the log sites it contains. The short hint Search UM for "bind-limits". points to this section. The error message still includes the file, line and specific cause. A compiler error for a required but unavailable __COUNTER__ also includes this reference.

For a direct Trice call, the file and source line normally suffice for mapping. Multiple calls on the same line, or a wrapper macro containing multiple calls, may need additional support. Bind uses the compiler counter __COUNTER__ for this. It counts during compilation; it is neither a runtime counter nor a cycle counter. Not every compiler provides it. Direct, uniquely addressable log sites work without it.

With Context Enrichment (bind -ce), additional values must be available precisely at the selected log site. A variable that exists only inside one function or block must not also be required at an unrelated site. The existing implementation of complex Bind sites would make the compiler check expressions from other scopes as well. Therefore, CE currently supports only direct sites uniquely addressable by source line. A direct call spanning multiple lines is also possible if none of its lines contains another Bind log site. A selected wrapper/rebase site is rejected before files are changed. The separate architecture proof is documented in the CE PoC appendix; integrating that approach into production remains deferred. Available __COUNTER__ alone is insufficient. Without a matching CE rule, existing Bind capabilities still apply.

Possible adjustments are:

Automatic insert/clean -ce is also available: Insert extends recognized source calls, including static wrapper definitions, and Clean removes matching extensions using the same rules. This path needs neither Bind line mapping nor __COUNTER__. Additional values can still be specified explicitly in the format string and arguments, for example trice("msg:Value=%d, x={x}", value, x);. Examples and removal conditions are described in the CE chapter.

24.20. Diagnostics and Troubleshooting

24.20.1. Sidecar Not Found

Check:

24.20.2. File-Key Conflict

Typical cause: A source was copied together with its sidecar include line.

Solution: Remove the copied sidecar include from one copy and run trice bind again so that a new key is generated.

24.20.3. File Is mixed

Either:

24.20.4. Bind Include Is in the Wrong Place

The sidecar must be active when the direct Trice calls of the physical file are expanded. In particular, headers included later can activate a different file key.

24.20.5. Unexpected Message After a Source Change

Run trice bind again. The line number is part of the build-local site name.

24.20.6. Advanced Construct Requires __COUNTER__

The compiler is processing a generated rebase region but does not provide __COUNTER__. Only this source construct is affected. Use one of the alternatives described under Missing __COUNTER__ or a target compiler with local counter support.

24.20.7. Counter Count or Rebase Descriptor Does Not Match

The build is using outdated or manually modified generator artifacts, or an additional counter is expanded inside the region. Do not repair generated include boundaries and helper headers manually. Inspect the source construct and run trice bind again.

24.21. Result

With trice bind, bind-managed user Trice calls remain free of numeric IDs. The stable ID truth remains in til.json and li.json; a reproducible sidecar passes the ID as a compile-time constant to the existing Trice transport path. Unambiguous sites continue to use the existing line path, while only ambiguous regions are locally rebased and checked at compile time.


24.22. Appendix: Preprocessor Fundamentals

24.22.1. Local Insert/Bind Dispatch

A simple #ifdef TRICE_BIND_FILE_KEY while reading trice.h is insufficient because the sidecar is normally included later.

Instead, the selection is expanded at the actual call site:

#define TRICE_BIND_ROUTE_TRICE_BIND_FILE_KEY INSERT
#define TRICE_BIND_ROUTE_I(key) TRICE_BIND_ROUTE_##key
#define TRICE_BIND_ROUTE(key) TRICE_BIND_ROUTE_I(key)

#define TRICE_DISPATCH_I(route, ...) TRICE_ROUTE_##route(__VA_ARGS__)
#define TRICE_DISPATCH(route, ...) TRICE_DISPATCH_I(route, __VA_ARGS__)

#define trice(...) TRICE_DISPATCH(TRICE_BIND_ROUTE(TRICE_BIND_FILE_KEY), __VA_ARGS__)

Without an active sidecar, TRICE_BIND_FILE_KEY remains as a token:

TRICE_BIND_ROUTE(TRICE_BIND_FILE_KEY)
→ TRICE_BIND_ROUTE_TRICE_BIND_FILE_KEY
→ INSERT

With an active sidecar:

#define TRICE_BIND_FILE_KEY K73A915E9C4021B8
#define TRICE_BIND_ROUTE_K73A915E9C4021B8 BIND

it expands to:

TRICE_BIND_ROUTE(TRICE_BIND_FILE_KEY)
→ TRICE_BIND_ROUTE(K73A915E9C4021B8)
→ TRICE_BIND_ROUTE_K73A915E9C4021B8
→ BIND

24.22.2. Site Descriptor

The site name is formed from the file key and __LINE__:

#define TRICE_BIND_SITE_I(key, line) TRICE_BIND_SITE_##key##_L##line
#define TRICE_BIND_SITE(key, line) TRICE_BIND_SITE_I(key, line)
#define TRICE_BIND_SITE_HERE() TRICE_BIND_SITE(TRICE_BIND_FILE_KEY, __LINE__)

A descriptor:

#define TRICE_BIND_SITE_K73A915E9C4021B8_L10 TRICE_BIND_REPLACE, id(12346u)

provides both the operation and the complete TID expression.

TRICE_BIND_AUTO inserts the TID. TRICE_BIND_REPLACE discards an existing id(0), Id(0), or ID(0) and uses the bound TID.

The generated-target integration tests check these mechanisms using current Bind output, including C/C++ compilation, invalid counter sequences and emitted runtime IDs.


24.23. Appendix: Stable ID Assignment and Binding Background

The development of trice bind is based on separating two tasks.

24.23.1. Stable ID Assignment

The first task is:

logical Trice site → stable numeric ID

It includes:

This persistent mapping does not have to reside in the source code.

24.23.2. Transfer into the Target Code

The second task is:

stable numeric ID → target code

trice insert solves it with a numeric ID in the user Trice call. trice bind solves it with a generated sidecar and standardized preprocessor facilities.

After preprocessing, the compiler likewise sees a normal constant. Therefore, there is:

24.23.3. Why the Source Scan Remains Authoritative

trice bind scans the project sources before preprocessing. This allows sites in currently inactive #if branches to retain a stable ID as well.

That is beneficial for ID stability: changing the build configuration does not remove the persistent identity of a log site.

A future analysis of the active configuration or the final image would be an additional reporting function. It is not required for binding.

24.23.4. Requirements Met by the Sidecar Approach

The chosen approach combines:

The former standalone architecture paper “Trice IDs Without Source-Code Patching” has been superseded by the current Bind description in this manual. Its conclusions that remain valid are summarized in this appendix.


24.24. Appendix: TRICE_CLEAN States at a Glance

State TRICE_CLEAN Own sidecar active Effect
Inserted undefined or 0 no Explicit TIDs from the source
Bound undefined or 0 yes TIDs from the sidecar
Clean/Off 1 irrelevant Existing clean/off path

Tool effect when the definition exists:

Tool Effect on TRICE_CLEAN
trice insert sets it to 0
trice clean sets it to 1
trice bind sets it to 0

If the definition is absent, trice bind does not add it.


24.25. Appendix: Why Bind Uses Local Counter Rebasing

The original design comparison considered three ways to distinguish log sites that share a source line or occur inside a wrapper macro. These were alternatives for transferring an already assigned stable ID into the target code, not alternative ID databases. The existing TIL/LI assignment remains authoritative in all three designs.

A translation unit is one C or C++ source file together with the headers processed for that compilation. __LINE__ cannot distinguish two calls on the same physical line. __COUNTER__ can distinguish expansions, but its absolute value also depends on unrelated macros and headers in that translation unit. Binding a stable ID directly to that absolute value would make unrelated source edits affect the mapping.

Approach How it distinguishes sites Benefit Cost and limitation Current status
Local counter rebase Record a counter base immediately before a small source region and select IDs by the difference from that base. Compile the original source; no target-compiler invocation or compilation database is required by bind. Earlier unrelated counter use does not affect the region. Generated begin/end includes are necessary. Counter use inside the region must match exactly; only safely bounded source constructs are accepted. Implemented for the ordinary Bind constructs described in this chapter.
Exact target-preprocessor pass Observe expansions using the actual target compiler and the build’s options. Can observe macros and active branches in a particular build configuration. Requires the real compiler, defines, include paths, forced includes and any precompiled headers. Bind and the later build must agree; multiple configurations may require distinct mappings. Preprocessed text alone does not portably recover every wrapper’s definition identity. Considered as an alternative; not a normal Bind build step. The separate CE PoC investigates a related approach without providing production CE wrapper support.
Generated compiler input Give sites explicit ordinals in generated copies of sources and headers. Site selection need not depend on a compiler counter. The compiler processes generated copies. Build integration must preserve relative includes, dependencies, diagnostics, debugging paths and IDE navigation. Considered as an alternative; no supported shadow-source mode is provided.

For example, a local base of 87 followed by three Trice counter expansions at 88, 89 and 90 gives local ordinals 0, 1 and 2. If an earlier header consumes another ten counter values, the base and those three values all increase by ten; the ordinals remain unchanged. If an unrelated macro consumes a counter value inside the region, that property no longer holds. Generated range and final-count checks turn the discrepancy into a compile error instead of accepting a silently shifted ID.

The implemented rebase selects stable IDs with generated constant expressions. An early design sketch used an ID array, but that sketch is not the current target interface and does not introduce a runtime lookup table. The user maintains neither counters nor ordinals. A wrapper’s inner definition sites retain their IDs across invocations; runtime if or switch decisions do not change the preprocessor expansion order.

Compiler capability is checked in the generated region that needs it. Finding a host compiler on the developer’s machine would not prove what an embedded target compiler supports, so bind does not use host-compiler discovery as an ID-binding guarantee. Ordinary file-and-line sites require no counter. For affected sites on a compiler without the necessary capability, use separate source lines, a suitable ordinary function, or the explicitly selected insert/clean workflow. See Bind Limits.

Checking whether a compiler defines __COUNTER__ and checking whether a region consumes the expected sequence are different tasks; the generated code addresses both. A successful historical PoC run is evidence for that experiment’s compiler and language modes, not a promise for every compiler, precompiled-header setup or build configuration. The CE wrapper/rebase appendix describes its separate evidence and remaining integration limits.

24.26. Appendix: Bind and Insert Test Evidence

Bind and Insert tests share canonical sources, ID configuration and build/test workers. The workflow wrapper prepares the source state and include paths; the shared worker performs the actual compiler or decoder checks. This avoids maintaining independent copies of triceCheck.c or weakening one workflow’s expected output. The relevant states are ID-free without active Bind artifacts, Inserted with explicit IDs, and Bound with owner includes and generated headers.

Check or component What it establishes Repository entry point
Shared ID settings and transitions The repository helpers use the same source scope, aliases, TIL, LI, ID policy and generated directory. _120_setup_trice_environment.sh, _130_trice_id_workflow.sh
Managed state restoration Snapshot affected source/metadata bytes and generated artifacts; restore the initial state after success, failure, SIGINT or SIGTERM. Failed restoration makes the wrapper fail. _140_trice_test_state.sh, portability tests
Generator behavior File ownership, stable IDs, stamps, include placement, idempotence, ordered diagnostics and rollback on rejected input or write failure. bindIDs_test.go, bindMVP2_test.go
Generated target behavior Compile real generated headers as C/C++; check counter guards, expansion invariants, emitted IDs and canonical Trice macro coverage. bindIntegration_test.go, _500_test_bind.sh
Return from Bound to Inserted Remove only validated owner and rebase artifacts, correct LI positions and reject ambiguous or modified artifacts without partial changes. bindRemigrate_test.go, _250_legacy_remigrate_bind_to_clean.sh
Shared PC target matrix Run the same selected configurations and expectations in Insert and Bind state, with separate logs and restored inputs. _160_pc_target_test_worker.sh, _630_test_pc_targets_insert.sh, _640_test_pc_targets_bind.sh

The compiler-build matrices also use shared workers for Insert and Bind. TRICE_OFF is checked separately because disabling logging does not depend on ID binding. See Testing the Trice Library C-Code for the Target for current selections, required tools, parallelism, logs and failure handling. Standalone build/ID maintenance helpers can intentionally leave a new source state; the restoration contract belongs to the managed test wrappers. An uncatchable process kill or machine failure cannot execute a shell restoration trap, so retained recovery data must be reviewed in that case.

To check current Bind generation and generated C/C++ target code from the repository root:

./scripts/_500_test_bind.sh

To run both PC target workflows with the same quick selection:

./scripts/_170_pc_target_tests_all_workflows.sh quick

These focused checks do not replace the repository’s final full regression run. Check each run’s reported tool availability: a missing compiler causes a skip, not a successful compiler check.


(back to top)

25. Trice version 1.0 Log-level Control

25.1. Trice version 1.0 Compile-time Log-level Control

In Trice version 1.0 is no compile-time log-level control. You can only disable all Trice logs

25.2. Trice version 1.0 Run-time Log-level Control

Because the target Trice code is so fast and generates only a few bytes per log, in Trice version 1.0 is no direct run-time log-level control inside the target code. The user has the Trice CLI switches -ban, -pick and -logLevel, to control, which Trice messages are displayed by the Trice tool.

25.3. Trice Version 1.0 Compile-time - Run-time Log-level Control

During compilation the developer can control which Trice tags, like info in trice( "info:...\n"); get which ID range. Look for -IDRange in trice h -i output. By defining values like TRICE_UARTA_MIN_ID in the project specific triceConfig.h during compile-time is controllable, which Trice tags get routed to an output device or not.

(back to top)

26. ID reference list til.json

26.1. Compatibility with firmware and host-tool versions

Keep each released firmware together with its til.json, matching li.json if needed, host-tool version, and decoding options such as framing, byte order, and default value width. Retaining an ID preserves its dictionary entry; it does not make every dictionary compatible with every host version. Location information must describe the firmware actually running, even when one cumulative TIL covers multiple firmware versions.

Firmware and dictionary Host tool Supported use
v1.3.0 firmware with its unchanged dictionaries v1.3.0 Reproduce historical output with the corresponding original options.
Existing firmware with classic printf formats and retained IDs Current host Supported when the formats are valid under the current template syntax and transport settings match. Literal braces are a relevant exception; see the examples below.
Current firmware and current dictionaries, including structured fields or CE Current host Supported within the documented record families and CE limits. Current instrumentation and its matching target sources are required when building these features.
A dictionary containing current named fields or doubled literal braces v1.3.0 Not a supported interpretation of the new templates. The old host does not understand fields and prints doubled braces literally.
C headers, implementation files, or generated Bind sidecars mixed from different releases Any host No general source/build compatibility guarantee. Use target files from one release and regenerate sidecars with the corresponding host tool.

The following examples use the same unstamped TREX record layout and ID in both host versions. Strg is the stored dictionary string; the scalar case carries the value 7.

Stored Strg Record payload v1.3.0 interpretation Current interpretation
msg:count=%d One 32-bit value count=7 count=7
hi No values hi hi; JSON/KV classify it as untagged without inserting a tag into message.
msg:literal={x} No values literal={x} {x} requires a value; the record is rejected with a diagnostic.
msg:set={1,2} No values set={1,2} Invalid field name; the record is rejected with a diagnostic.
msg:literal= No values literal= literal={x}
msg:value={x} One 32-bit value Not a supported one-value printf format value=7, with field x=7 in structured output.

For unchanged historical firmware that used literal braces, replay with its archived dictionary and matching old host tool. In sources built with the current tools, write literal braces as ``, then regenerate IDs/dictionaries and rebuild. Logging does not rewrite supplied dictionaries, and historical dictionaries are not automatically converted. An unchanged binary record layout alone does not establish template compatibility. Replay checks must inspect output and diagnostics: a rejected record does not necessarily make the logger exit with a nonzero status.

Published command-line changes relative to v1.3.0 also matter when updating scripts:

v1.3.0 usage Current contract
trice generate -tilC Use -logC. This is a source-based generator for current sites already resolved by Insert or Bind, not a spelling alias for dumping every historical TIL entry. Its default output is generated/til.c.
-liPath Use -liRoot during ID management for stored source paths; use -liMaxDirs during logging for displayed parent directories.
-ulabel alpha:beta for two user tags Use -ulabel alpha -ulabel beta. A colon now specifies a weight or color for one tag.
Tag selection using -pick, -ban, or -logLevel Registered aliases select their whole group. -logLevel uses priority weights, and unknown selectors are rejected before opening the input. Untagged events participate in filtering. Metadata no longer determines application selection.
trice generate -abc deviceX Bare names produce generated/deviceX.h and .c. Use -genDir to select the directory or an explicit target path to keep a chosen location.

26.2. til.json Version control

--> Deleting til.json should not not be done when the sources are without IDs. 
--> That would result in a loss of the complete ID history and a assignment of a complete new set of IDs.

You could write a small bash script similar to this (untested):

trice insert -cache # Insert the IDs into the source code.
git restore til.json # Forget the todays garbage.

# Add the todays IDs to the restored til.json and clean the code.
# We have to deactivate the cache to force the file processing to get the new IDs into til.json.
trice clean # Remove the IDs from the source code with deactivated cache.  

26.3. Long Time Availability

(back to top)

27. The Trice Insert Algorithm

27.1. Starting Conditions

@@ To understand this chapter you should look into the Trice tool source code. @@

27.2. Aims

27.3. Method

27.3.1. Trice Insert Initialization

// insertIDsData holds the insert run specific data.
type insertIDsData struct {
    idToFmt    TriceIDLookUp     // idToFmt is a trice ID lookup map and is generated from existing til.json file at the begin of SubCmdIdInsert. This map is only extended during SubCmdIdInsert and goes back into til.json afterwards.
    fmtToId    triceFmtLookUp    // fmtToId is a trice fmt lookup map (reversed idToFmt for faster operation) and kept in sync with idToFmt. Each fmt can have several trice IDs (slice).
    idToLocRef TriceIDLookUpLI   // idToLocInf is the trice ID location information as reference generated from li.json (if exists) at the begin of SubCmdIdInsert and is not modified at all. At the end of SubCmdIdInsert a new li.json is generated from itemToId.
    itemToId   TriceItemLookUpID // itemToId is a trice item lookup ID map, extended from source tree during SubCmdIdInsert after each found and maybe modified trice item.
    idToItem   TriceIDLookupItem // idToItem is a trice ID lookup item map (reversed itemToId for faster operation) and kept in sync with itemToId.
}

Until here the algorithm seem to be ok.

27.4. User Code Patching (trice insert)

27.5. User Code Patching Examples

27.6. Exclude folders & files from being parsed (pull request 529)

The pull request #529 introduces key enhancement:

    -exclude Flag
    Introduces a command-line flag -exclude that allows users to specify one or more source addresses to be omitted from scanning or processing. This improves flexibility in environments with known noisy or irrelevant sources.

-exclude Flag Example

The -exclude flag can be used multiple times to omit specific files or directories from scanning. Wildcards are not supported.

trice insert -v -src ./_test/ -exclude _test/src/trice.h -exclude _test/generated/

27.7. ID Usage Options

27.8. General ID Management Information

27.8.1. Option Cleaning in a Post-build process

27.8.2. Option Let the inserted Trice ID be a Part of the User Code

27.8.3. Option Cleaning on Repository Check-In

(back to top)

28. Trice Speed

A Trice macro execution can be as cheap like 3 Assembler instructions or 6 processor clocks:

A more realistic (typical) timing with target location and µs timestamps, critical section and parameters is shown here with the STM32F030 M0 core:

./ref/F030FullTiming.PNG

The MCU is clocked with 48 MHz and a Trice duration is about 2 µs, where alone the internal ReadUs() call is already nearly 1 µs long:

./ref/ReadUsF030.PNG

28.1. Target Implementation Options

All trice macros use internally this sub-macro:

#define TRICE_PUT(x) do{ *TriceBufferWritePosition++ = TRICE_HTOTL(x); }while(0); //! PUT copies a 32 bit x into the TRICE buffer.

The usual case is #define TRICE_HTOTL(x) (x). The uint32_t* TriceBufferWritePosition points to a buffer, which is codified and used with the Trice framing sub-macros TRICE_ENTER and TRICE_LEAVE depending on the use case.

28.1.1. Trice Use Cases TRICE_STATIC_BUFFER and TRICE_STACK_BUFFER - direct mode only

  1. Each single Trice is build inside a common buffer and finally copied inside the sub-macro TRICE_LEAVE.
  2. Disabled relevant interrupts between TRICE_ENTER and TRICE_LEAVE are mantadory for TRICE_STATIC_BUFFER.
  3. Usable for multiple non-blocking physical Trice channels but not recommended for some time blocking channels.
  4. A copy call is executed inside TRICE_LEAVE.

28.1.2. Trice Use Case TRICE_DOUBLE_BUFFER - deferred mode, fastest Trice execution, more RAM needed

  1. Several trices are build in a half buffer.
  2. No stack used.
  3. Disabled interrupts between TRICE_ENTER and TRICE_LEAVE.
  4. Usable for multiple blocking and non-blocking physical Trice channels.
  5. No copy call inside TRICE_LEAVE but optionally an additional direct mode is supported.

28.1.3. Trice Use Case TRICE_RING_BUFFER - deferred mode, balanced Trice execution time and needed RAM

  1. Each single trices is build in a ring buffer segment.
  2. No stack used.
  3. Disabled interrupts between TRICE_ENTER and TRICE_LEAVE.
  4. Usable for multiple blocking and non-blocking physical Trice channels.
  5. No copy call inside TRICE_LEAVE but optionally an additional direct mode is supported.
  6. Allocation call inside TRICE_ENTER

28.2. A configuration for maximum Trice execution speed with the L432_inst example

#define TriceStamp16 (*DWT_CYCCNT) // @64MHz wraps after a bit more than 1ms (MCU clocks)
#define TriceStamp32 (*DWT_CYCCNT) // @64MHz -> 1 µs, wraps after 2^32 µs ~= 1.2 hours

#define TRICE_DEFERRED_UARTA 1
#define TRICE_UARTA USART2

#define TRICE_DEFERRED_OUTPUT 1
#define TRICE_BUFFER TRICE_DOUBLE_BUFFER

#define TRICE_PROTECT 0
#define TRICE_DIAGNOSTICS 0
#define TRICE_CYCLE_COUNTER 0

After running ./build.sh, executing ` arm-none-eabi-objdump.exe -D -S -l out.clang/triceExamples.o` shows:

out.clang/triceExamples.o:     file format elf32-littlearm


Disassembly of section .text.TriceHeadLine:

00000000 <TriceHeadLine>:
TriceHeadLine():
C:\Users\ms\repos\trice_wt_devel\examples\L432_inst/../exampleData/triceExamples.c:10
#include "trice.h"

//! TriceHeadLine emits a decorated name. The name length should be 18 characters.
void TriceHeadLine(char * name) {
	//! This is usable as the very first trice sequence after restart. Adapt it. Use a UTF-8 capable editor like VS-Code or use pure ASCII.
	TriceS("w: Hello! 👋🙂\n\n        ✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨        \n        🎈🎈🎈🎈%s🎈🎈🎈🎈\n        🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃        \n\n\n", name);
   0:	f240 0100 	movw	r1, #0
   4:	4602      	mov	r2, r0
   6:	f2c0 0100 	movt	r1, #0
   a:	f643 70f1 	movw	r0, #16369	@ 0x3ff1
   e:	f7ff bffe 	b.w	0 <TriceS>

Disassembly of section .ARM.exidx.text.TriceHeadLine:

00000000 <.ARM.exidx.text.TriceHeadLine>:
   0:	00000000 	andeq	r0, r0, r0
   4:	00000001 	andeq	r0, r0, r1

Disassembly of section .text.SomeExampleTrices:

00000000 <SomeExampleTrices>:
SomeExampleTrices():
C:\Users\ms\repos\trice_wt_devel\examples\L432_inst/../exampleData/triceExamples.c:14
}

//! SomeExampleTrices generates a few Trice example logs and a burst of Trices.
void SomeExampleTrices(int burstCount) {
   0:	b5f0      	push	{r4, r5, r6, r7, lr}
   2:	af03      	add	r7, sp, #12
   4:	e92d 0700 	stmdb	sp!, {r8, r9, sl}
C:\Users\ms\repos\trice_wt_devel\examples\L432_inst/../exampleData/triceExamples.c:15
	TRICE(ID(0), "att:🐁 Speedy Gonzales A  32-bit timestamp\n");
   8:	f240 0100 	movw	r1, #0
   c:	f2c0 0100 	movt	r1, #0
  10:	680d      	ldr	r5, [r1, #0]
  12:	f240 0800 	movw	r8, #0
  16:	f2c0 0800 	movt	r8, #0
  1a:	4604      	mov	r4, r0
  1c:	6829      	ldr	r1, [r5, #0]
  1e:	f8d8 0000 	ldr.w	r0, [r8]
  22:	f06f 0212 	mvn.w	r2, #18
  26:	8041      	strh	r1, [r0, #2]
  28:	0c09      	lsrs	r1, r1, #16
  2a:	1cd3      	adds	r3, r2, #3
  2c:	8081      	strh	r1, [r0, #4]
  2e:	21c0      	movs	r1, #192	@ 0xc0
  30:	8003      	strh	r3, [r0, #0]
  32:	80c1      	strh	r1, [r0, #6]
C:\Users\ms\repos\trice_wt_devel\examples\L432_inst/../exampleData/triceExamples.c:16
	TRICE(ID(0), "att:🐁 Speedy Gonzales B  32-bit timestamp\n");
  34:	682b      	ldr	r3, [r5, #0]
  36:	1c96      	adds	r6, r2, #2
  38:	8143      	strh	r3, [r0, #10]
  3a:	0c1b      	lsrs	r3, r3, #16
  3c:	8106      	strh	r6, [r0, #8]
  3e:	8183      	strh	r3, [r0, #12]
  40:	81c1      	strh	r1, [r0, #14]
C:\Users\ms\repos\trice_wt_devel\examples\L432_inst/../exampleData/triceExamples.c:17
	TRICE(ID(0), "att:🐁 Speedy Gonzales C  32-bit timestamp\n");
  42:	682b      	ldr	r3, [r5, #0]
  44:	1c56      	adds	r6, r2, #1
  46:	8243      	strh	r3, [r0, #18]
  48:	0c1b      	lsrs	r3, r3, #16
  4a:	8206      	strh	r6, [r0, #16]
  4c:	8283      	strh	r3, [r0, #20]
  4e:	82c1      	strh	r1, [r0, #22]
C:\Users\ms\repos\trice_wt_devel\examples\L432_inst/../exampleData/triceExamples.c:18
	TRICE(ID(0), "att:🐁 Speedy Gonzales D  32-bit timestamp\n");
  50:	682b      	ldr	r3, [r5, #0]
  52:	8302      	strh	r2, [r0, #24]
  54:	0c1a      	lsrs	r2, r3, #16
  56:	8343      	strh	r3, [r0, #26]
  58:	8382      	strh	r2, [r0, #28]
  5a:	83c1      	strh	r1, [r0, #30]
  5c:	f64b 7ad4 	movw	sl, #49108	@ 0xbfd4
C:\Users\ms\repos\trice_wt_devel\examples\L432_inst/../exampleData/triceExamples.c:19
	TRICE(Id(0), "att:🐁 Speedy Gonzales E  16-bit timestamp\n");
  60:	682a      	ldr	r2, [r5, #0]
  62:	f6cb 7ad4 	movt	sl, #49108	@ 0xbfd4
  66:	f10a 1318 	add.w	r3, sl, #1572888	@ 0x180018
  6a:	6203      	str	r3, [r0, #32]
  6c:	8482      	strh	r2, [r0, #36]	@ 0x24
  6e:	84c1      	strh	r1, [r0, #38]	@ 0x26
C:\Users\ms\repos\trice_wt_devel\examples\L432_inst/../exampleData/triceExamples.c:20
	TRICE(Id(0), "att:🐁 Speedy Gonzales F  16-bit timestamp\n");
  70:	682a      	ldr	r2, [r5, #0]
  72:	f10a 1317 	add.w	r3, sl, #1507351	@ 0x170017
  76:	6283      	str	r3, [r0, #40]	@ 0x28
  78:	8582      	strh	r2, [r0, #44]	@ 0x2c
  7a:	85c1      	strh	r1, [r0, #46]	@ 0x2e
C:\Users\ms\repos\trice_wt_devel\examples\L432_inst/../exampleData/triceExamples.c:21
	TRICE(Id(0), "att:🐁 Speedy Gonzales G  16-bit timestamp\n");
  7c:	682a      	ldr	r2, [r5, #0]
  7e:	f10a 1316 	add.w	r3, sl, #1441814	@ 0x160016
  82:	6303      	str	r3, [r0, #48]	@ 0x30
  84:	8682      	strh	r2, [r0, #52]	@ 0x34
  86:	86c1      	strh	r1, [r0, #54]	@ 0x36
C:\Users\ms\repos\trice_wt_devel\examples\L432_inst/../exampleData/triceExamples.c:22
	TRICE(Id(0), "att:🐁 Speedy Gonzales H  16-bit timestamp\n");
  88:	682a      	ldr	r2, [r5, #0]
  8a:	f10a 1315 	add.w	r3, sl, #1376277	@ 0x150015
  8e:	8782      	strh	r2, [r0, #60]	@ 0x3c
  90:	f647 72e8 	movw	r2, #32744	@ 0x7fe8
C:\Users\ms\repos\trice_wt_devel\examples\L432_inst/../exampleData/triceExamples.c:23
	TRICE(id(0), "att:🐁 Speedy Gonzales I without timestamp\n");
  94:	f8a0 2040 	strh.w	r2, [r0, #64]	@ 0x40
  98:	f647 72e7 	movw	r2, #32743	@ 0x7fe7
C:\Users\ms\repos\trice_wt_devel\examples\L432_inst/../exampleData/triceExamples.c:24
	TRICE(id(0), "att:🐁 Speedy Gonzales J without timestamp\n");
  9c:	f8a0 2044 	strh.w	r2, [r0, #68]	@ 0x44
  a0:	f647 72e6 	movw	r2, #32742	@ 0x7fe6
C:\Users\ms\repos\trice_wt_devel\examples\L432_inst/../exampleData/triceExamples.c:25
	TRICE(id(0), "att:🐁 Speedy Gonzales K without timestamp\n");
  a4:	f8a0 2048 	strh.w	r2, [r0, #72]	@ 0x48
  a8:	f647 72e5 	movw	r2, #32741	@ 0x7fe5
C:\Users\ms\repos\trice_wt_devel\examples\L432_inst/../exampleData/triceExamples.c:22
	TRICE(Id(0), "att:🐁 Speedy Gonzales H  16-bit timestamp\n");
  ac:	6383      	str	r3, [r0, #56]	@ 0x38
  ae:	87c1      	strh	r1, [r0, #62]	@ 0x3e
C:\Users\ms\repos\trice_wt_devel\examples\L432_inst/../exampleData/triceExamples.c:23
	TRICE(id(0), "att:🐁 Speedy Gonzales I without timestamp\n");
  b0:	f8a0 1042 	strh.w	r1, [r0, #66]	@ 0x42
C:\Users\ms\repos\trice_wt_devel\examples\L432_inst/../exampleData/triceExamples.c:24
	TRICE(id(0), "att:🐁 Speedy Gonzales J without timestamp\n");
  b4:	f8a0 1046 	strh.w	r1, [r0, #70]	@ 0x46
C:\Users\ms\repos\trice_wt_devel\examples\L432_inst/../exampleData/triceExamples.c:25
	TRICE(id(0), "att:🐁 Speedy Gonzales K without timestamp\n");
  b8:	f8a0 104a 	strh.w	r1, [r0, #74]	@ 0x4a
C:\Users\ms\repos\trice_wt_devel\examples\L432_inst/../exampleData/triceExamples.c:26
	TRICE(id(0), "att:🐁 Speedy Gonzales L without timestamp\n");
  bc:	f8a0 204c 	strh.w	r2, [r0, #76]	@ 0x4c
  c0:	f8a0 104e 	strh.w	r1, [r0, #78]	@ 0x4e
TRice0():
C:\Users\ms\repos\trice_wt_devel\examples\L432_inst/../../src/trice.h:741
	Trice32m_0(tid);
	TRICE_UNUSED(pFmt)
}

...

2024-12-05_L432_inst_maxSpeed.png

As you can see in the highlighted blue timestamp bar, typical 8-10 clocks are needed for one Trice macro. One clock duration @64MHz is 15.625 ns, so we need about 150 ns for a Trice. Light can travel about 50 meter in that time.

28.3. A configuration for normal Trice execution speed with the G0B1_inst example

// hardware specific trice lib settings
#include "main.h"
#define TriceStamp16 TIM17->CNT     // 0...999 us
#define TriceStamp32 HAL_GetTick()  // 0...2^32-1 ms (wraps after 49.7 days)

#define TRICE_BUFFER TRICE_RING_BUFFER

// trice l -p JLINK -args="-Device STM32G0B1RE -if SWD -Speed 4000 -RTTChannel 0" -pf none  -d16 -ts ms
//#define TRICE_DIRECT_OUTPUT 1
//#define TRICE_DIRECT_SEGGER_RTT_32BIT_WRITE 1

// trice log -p com7 -pw MySecret -pf COBS
#define TRICE_DEFERRED_OUTPUT 1
#define TRICE_DEFERRED_XTEA_ENCRYPT 1
#define TRICE_DEFERRED_OUT_FRAMING TRICE_FRAMING_COBS
#define TRICE_DEFERRED_UARTA 1
#define TRICE_UARTA USART2

#include "cmsis_gcc.h"
#define TRICE_ENTER_CRITICAL_SECTION { uint32_t primaskstate = __get_PRIMASK(); __disable_irq(); {
#define TRICE_LEAVE_CRITICAL_SECTION } __set_PRIMASK(primaskstate); }

2024-12-05_G0B1_inst_normalSpeed.png

2024-12-05_G0B1_inst_slowSpeed.png

🛑 The Trice execution time is now over 20 microseconds❗

Still fast enough for many cases but you hopefully have a good knowledge now how to tune Trice best for your application.

(back to top)

29. Trice memory needs

Depending on your target configuration the needed space can differ:

29.1. F030_bare Size

arm-none-eabi-size build/F030_bare.elf
   text    data     bss     dec     hex filename
   2428      12    1564    4004     fa4 build/F030_bare.elf

That is the basic size of an empty generated project just containing some drivers.

29.2. F030_inst Size with TRICE_OFF=1

arm-none-eabi-size build/F030_inst.elf
   text    data     bss     dec     hex filename
   2428      12    1564    4004     fa4 build/F030_inst.elf

This is exactly the same result, proofing that TRICE_OFF 1 is working correctly.

29.3. F030_inst with ring buffer

arm-none-eabi-size out/F030_inst.elf
   text    data     bss     dec     hex filename
   9416      28    2692   12136    2f68 out/F030_inst.elf

This is about 7 KB Flash and 1.2 KB RAM size for the Trice library and we see:

ms@DESKTOP-7POEGPB MINGW64 ~/repos/trice_wt_devel/examples/F030_inst (devel)
$ trice l -p com5 -ts16 "time:     #%6d" -hs off
com5:       triceExamples.c    12      # 65535  Hello! 👋🙂
com5:
com5:         ✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨
com5:         🎈🎈🎈🎈  𝕹𝖀𝕮𝕷𝕰𝕺-F030R8   🎈🎈🎈🎈
com5:         🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃
com5:
com5:
com5:       triceExamples.c    61              TRICE_DIRECT_OUTPUT == 0, TRICE_DEFERRED_OUTPUT == 1
com5:       triceExamples.c    67              TRICE_DOUBLE_BUFFER, TRICE_MULTI_PACK_MODE
com5:       triceExamples.c    76              _CYCLE == 1, _PROTECT == 1, _DIAG == 1, XTEA == 0
com5:       triceExamples.c    77              _SINGLE_MAX_SIZE=104, _BUFFER_SIZE=172, _DEFERRED_BUFFER_SIZE=1024
com5:       triceExamples.c    29    0,031_804 🐁 Speedy Gonzales a  32-bit timestamp
com5:       triceExamples.c    30    0,031_646 🐁 Speedy Gonzales b  32-bit timestamp
com5:       triceExamples.c    31    0,031_488 🐁 Speedy Gonzales c  32-bit timestamp
com5:       triceExamples.c    32    0,031_330 🐁 Speedy Gonzales d  32-bit timestamp
com5:       triceExamples.c    33      # 31172 🐁 Speedy Gonzales e  16-bit timestamp
com5:       triceExamples.c    34      # 31012 🐁 Speedy Gonzales f  16-bit timestamp
com5:       triceExamples.c    35      # 30852 🐁 Speedy Gonzales g  16-bit timestamp
com5:       triceExamples.c    36      # 30692 🐁 Speedy Gonzales h  16-bit timestamp
com5:       triceExamples.c    42      # 30224 2.71828182845904523536 <- float number as string
com5:       triceExamples.c    43      # 29517 2.71828182845904509080 (double with more ciphers than precision)
com5:       triceExamples.c    44      # 29322 2.71828174591064453125 (float  with more ciphers than precision)
com5:       triceExamples.c    45      # 29145 2.718282 (default rounded float)
com5:       triceExamples.c    46      # 28969 A Buffer:
com5:       triceExamples.c    47      # 28790 32 2e 37 31 38 32 38 31 38 32 38 34 35 39 30 34 35 32 33 35 33 36
com5:       triceExamples.c    48      # 28076 31372e32  31383238  34383238  34303935  35333235
com5:       triceExamples.c    49      # 27430 ARemoteFunctionName(2e32)(3137)(3238)(3138)(3238)(3438)(3935)(3430)(3235)(3533)(3633)
com5:       triceExamples.c    50              5 times a 16 byte long Trice messages, which may not be written all if the buffer is too small:
com5:       triceExamples.c    52      # 26554 i=44444400 aaaaaa00
com5:       triceExamples.c    52      # 26342 i=44444401 aaaaaa01
com5:       triceExamples.c    52      # 26130 i=44444402 aaaaaa02
com5:       triceExamples.c    52      # 25918 i=44444403 aaaaaa03
com5:       triceExamples.c    52      # 25706 i=44444404 aaaaaa04
com5:       triceExamples.c    29    0,031_790 🐁 Speedy Gonzales a  32-bit timestamp
com5:       triceExamples.c    30    0,031_632 🐁 Speedy Gonzales b  32-bit timestamp
com5:       triceExamples.c    31    0,031_474 🐁 Speedy Gonzales c  32-bit timestamp
com5:       triceExamples.c    32    0,031_316 🐁 Speedy Gonzales d  32-bit timestamp
com5:       triceExamples.c    33      # 31158 🐁 Speedy Gonzales e  16-bit timestamp
com5:       triceExamples.c    34      # 30998 🐁 Speedy Gonzales f  16-bit timestamp
com5:       triceExamples.c    35      # 30838 🐁 Speedy Gonzales g  16-bit timestamp
com5:       triceExamples.c    36      # 30678 🐁 Speedy Gonzales h  16-bit timestamp
com5:       triceExamples.c    42      # 30210 2.71828182845904523536 <- float number as string
com5:       triceExamples.c    43      # 29503 2.71828182845904509080 (double with more ciphers than precision)
com5:       triceExamples.c    44      # 29308 2.71828174591064453125 (float  with more ciphers than precision)
com5:       triceExamples.c    45      # 29131 2.718282 (default rounded float)
com5:       triceExamples.c    46      # 28955 A Buffer:
com5:       triceExamples.c    47      # 28776 32 2e 37 31 38 32 38 31 38 32 38 34 35 39 30 34 35 32 33 35 33 36
com5:       triceExamples.c    48      # 28062 31372e32  31383238  34383238  34303935  35333235
com5:       triceExamples.c    49      # 27416 ARemoteFunctionName(2e32)(3137)(3238)(3138)(3238)(3438)(3935)(3430)(3235)(3533)(3633)
com5:       triceExamples.c    50              5 times a 16 byte long Trice messages, which may not be written all if the buffer is too small:
com5:       triceExamples.c    52      # 26540 i=44444400 aaaaaa00
com5:       triceExamples.c    52      # 26328 i=44444401 aaaaaa01
com5:       triceExamples.c    52      # 26116 i=44444402 aaaaaa02
com5:       triceExamples.c    52      # 25904 i=44444403 aaaaaa03
com5:       triceExamples.c    52      # 25692 i=44444404 aaaaaa04
com5:    triceLogDiagData.c    44              triceSingleDepthMax = 108 of 172 (TRICE_BUFFER_SIZE)
com5:    triceLogDiagData.c    67              TriceHalfBufferDepthMax = 388 of  512
com5:       triceExamples.c    29    0,031_344 🐁 Speedy Gonzales a  32-bit timestamp
com5:       triceExamples.c    30    0,031_186 🐁 Speedy Gonzales b  32-bit timestamp

29.4. F030_inst with ring buffer

We need 600 bytes more Flash but could have less RAM used:

   text    data     bss     dec     hex filename
  10060      24    2688   12772    31e4 out/F030_inst.elf
ms@DESKTOP-7POEGPB MINGW64 ~/repos/trice_wt_devel/examples/F030_inst (devel)
$ trice l -p com5 -ts16 "time:     #%6d" -hs off
com5:       triceExamples.c    12      # 65535  Hello! 👋🙂
com5:
com5:         ✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨
com5:         🎈🎈🎈🎈  𝕹𝖀𝕮𝕷𝕰𝕺-F030R8   🎈🎈🎈🎈
com5:         🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃
com5:
com5:
com5:       triceExamples.c    61              TRICE_DIRECT_OUTPUT == 0, TRICE_DEFERRED_OUTPUT == 1
com5:       triceExamples.c    69              TRICE_RING_BUFFER, TRICE_MULTI_PACK_MODE
com5:       triceExamples.c    76              _CYCLE == 1, _PROTECT == 1, _DIAG == 1, XTEA == 0
com5:       triceExamples.c    77              _SINGLE_MAX_SIZE=104, _BUFFER_SIZE=172, _DEFERRED_BUFFER_SIZE=1024
com5:       triceExamples.c    29    0,031_732 🐁 Speedy Gonzales a  32-bit timestamp
com5:       triceExamples.c    30    0,031_531 🐁 Speedy Gonzales b  32-bit timestamp
com5:       triceExamples.c    31    0,031_330 🐁 Speedy Gonzales c  32-bit timestamp
com5:       triceExamples.c    32    0,031_129 🐁 Speedy Gonzales d  32-bit timestamp
com5:       triceExamples.c    33      # 30928 🐁 Speedy Gonzales e  16-bit timestamp
com5:       triceExamples.c    34      # 30725 🐁 Speedy Gonzales f  16-bit timestamp
com5:       triceExamples.c    35      # 30522 🐁 Speedy Gonzales g  16-bit timestamp
com5:       triceExamples.c    36      # 30319 🐁 Speedy Gonzales h  16-bit timestamp
com5:       triceExamples.c    42      # 29808 2.71828182845904523536 <- float number as string
com5:       triceExamples.c    43      # 29058 2.71828182845904509080 (double with more ciphers than precision)
com5:       triceExamples.c    44      # 28821 2.71828174591064453125 (float  with more ciphers than precision)
com5:       triceExamples.c    45      # 28602 2.718282 (default rounded float)
com5:       triceExamples.c    46      # 28383 A Buffer:
com5:       triceExamples.c    47      # 28162 32 2e 37 31 38 32 38 31 38 32 38 34 35 39 30 34 35 32 33 35 33 36
com5:       triceExamples.c    48      # 27406 31372e32  31383238  34383238  34303935  35333235
com5:       triceExamples.c    49      # 26718 ARemoteFunctionName(2e32)(3137)(3238)(3138)(3238)(3438)(3935)(3430)(3235)(3533)(3633)
com5:       triceExamples.c    50              5 times a 16 byte long Trice messages, which may not be written all if the buffer is too small:
com5:       triceExamples.c    52      # 25757 i=44444400 aaaaaa00
com5:       triceExamples.c    52      # 25502 i=44444401 aaaaaa01
com5:       triceExamples.c    52      # 25247 i=44444402 aaaaaa02
com5:       triceExamples.c    52      # 24992 i=44444403 aaaaaa03
com5:       triceExamples.c    52      # 24737 i=44444404 aaaaaa04
com5:       triceExamples.c    29    0,031_746 🐁 Speedy Gonzales a  32-bit timestamp
com5:       triceExamples.c    30    0,031_545 🐁 Speedy Gonzales b  32-bit timestamp
com5:       triceExamples.c    31    0,031_344 🐁 Speedy Gonzales c  32-bit timestamp
com5:       triceExamples.c    32    0,031_143 🐁 Speedy Gonzales d  32-bit timestamp
com5:       triceExamples.c    33      # 30942 🐁 Speedy Gonzales e  16-bit timestamp
com5:       triceExamples.c    34      # 30739 🐁 Speedy Gonzales f  16-bit timestamp
com5:       triceExamples.c    35      # 30536 🐁 Speedy Gonzales g  16-bit timestamp
com5:       triceExamples.c    36      # 30333 🐁 Speedy Gonzales h  16-bit timestamp
com5:       triceExamples.c    42      # 29822 2.71828182845904523536 <- float number as string
com5:       triceExamples.c    43      # 29072 2.71828182845904509080 (double with more ciphers than precision)
com5:       triceExamples.c    44      # 28835 2.71828174591064453125 (float  with more ciphers than precision)
com5:       triceExamples.c    45      # 28616 2.718282 (default rounded float)
com5:       triceExamples.c    46      # 28397 A Buffer:
com5:       triceExamples.c    47      # 28176 32 2e 37 31 38 32 38 31 38 32 38 34 35 39 30 34 35 32 33 35 33 36
com5:       triceExamples.c    48      # 27420 31372e32  31383238  34383238  34303935  35333235
com5:       triceExamples.c    49      # 26732 ARemoteFunctionName(2e32)(3137)(3238)(3138)(3238)(3438)(3935)(3430)(3235)(3533)(3633)
com5:       triceExamples.c    50              5 times a 16 byte long Trice messages, which may not be written all if the buffer is too small:
com5:       triceExamples.c    52      # 25771 i=44444400 aaaaaa00
com5:       triceExamples.c    52      # 25516 i=44444401 aaaaaa01
com5:       triceExamples.c    52      # 25261 i=44444402 aaaaaa02
com5:       triceExamples.c    52      # 25006 i=44444403 aaaaaa03
com5:       triceExamples.c    52      # 24751 i=44444404 aaaaaa04
com5:    triceLogDiagData.c    44              triceSingleDepthMax = 108 of 172 (TRICE_BUFFER_SIZE)
com5:    triceLogDiagData.c    75              triceRingBufferDepthMax = 324 of 1024
com5:       triceExamples.c    29    0,031_188 🐁 Speedy Gonzales a  32-bit timestamp
com5:       triceExamples.c    30    0,030_987 🐁 Speedy Gonzales b  32-bit timestamp

29.5. A developer setting, only enabling SEGGER_RTT

arm-none-eabi-size out/F030_inst.elf
   text    data     bss     dec     hex filename
   6656      16    2768    9440    24e0 out/F030_inst.elf

About 4 KB Flash needed and we see:

ms@DESKTOP-7POEGPB MINGW64 ~/repos/trice_wt_devel/examples/F030_inst (devel)
$ trice l -p jlink -args "-Device STM32F030R8 -if SWD -Speed 4000 -RTTChannel 0" -pf none -d16 -showID "deb:%5d"
Dec  6 16:14:38.276356  jlink:       triceExamples.c    12       65_535 16369  Hello! 👋🙂
Dec  6 16:14:38.276356  jlink:
Dec  6 16:14:38.276356  jlink:         ✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨
Dec  6 16:14:38.276356  jlink:         🎈🎈🎈🎈  𝕹𝖀𝕮𝕷𝕰𝕺-F030R8   🎈🎈🎈🎈
Dec  6 16:14:38.276356  jlink:         🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃
Dec  6 16:14:38.276356  jlink:
Dec  6 16:14:38.276356  jlink:
Dec  6 16:14:38.276356  jlink:       triceExamples.c    61              16334 TRICE_DIRECT_OUTPUT == 1, TRICE_DEFERRED_OUTPUT == 0
Dec  6 16:14:38.276356  jlink:       triceExamples.c    63              16333 TRICE_STACK_BUFFER, TRICE_MULTI_PACK_MODE
Dec  6 16:14:38.276920  jlink:       triceExamples.c    76              16327 _CYCLE == 1, _PROTECT == 1, _DIAG == 1, XTEA == 0
Dec  6 16:14:38.277424  jlink:       triceExamples.c    77              16326 _SINGLE_MAX_SIZE=104, _BUFFER_SIZE=172, _DEFERRED_BUFFER_SIZE=1024
Dec  6 16:14:39.181228  jlink:       triceExamples.c    29    0,031_848 16356 🐁 Speedy Gonzales a  32-bit timestamp
Dec  6 16:14:39.181228  jlink:       triceExamples.c    30    0,031_292 16355 🐁 Speedy Gonzales b  32-bit timestamp
Dec  6 16:14:39.181228  jlink:       triceExamples.c    31    0,030_736 16354 🐁 Speedy Gonzales c  32-bit timestamp
Dec  6 16:14:39.181228  jlink:       triceExamples.c    32    0,030_180 16353 🐁 Speedy Gonzales d  32-bit timestamp
Dec  6 16:14:39.181228  jlink:       triceExamples.c    33       29_624 16352 🐁 Speedy Gonzales e  16-bit timestamp
Dec  6 16:14:39.181228  jlink:       triceExamples.c    34       29_066 16351 🐁 Speedy Gonzales f  16-bit timestamp
Dec  6 16:14:39.181228  jlink:       triceExamples.c    35       28_508 16350 🐁 Speedy Gonzales g  16-bit timestamp
Dec  6 16:14:39.181228  jlink:       triceExamples.c    36       27_950 16349 🐁 Speedy Gonzales h  16-bit timestamp
Dec  6 16:14:39.181228  jlink:       triceExamples.c    42       27_086 16344 2.71828182845904523536 <- float number as string
Dec  6 16:14:39.181228  jlink:       triceExamples.c    43       25_906 16343 2.71828182845904509080 (double with more ciphers than precision)
Dec  6 16:14:39.181798  jlink:       triceExamples.c    44       25_305 16342 2.71828174591064453125 (float  with more ciphers than precision)
Dec  6 16:14:39.181798  jlink:       triceExamples.c    45       24_727 16341 2.718282 (default rounded float)
Dec  6 16:14:39.181798  jlink:       triceExamples.c    46       24_148 16340 A Buffer:
Dec  6 16:14:39.181798  jlink:       triceExamples.c    47       23_578 16339 32 2e 37 31 38 32 38 31 38 32 38 34 35 39 30 34 35 32 33 35 33 36
Dec  6 16:14:39.181798  jlink:       triceExamples.c    48       22_394 16338 31372e32  31383238  34383238  34303935  35333235
Dec  6 16:14:39.181798  jlink:       triceExamples.c    49       21_295 16337 ARemoteFunctionName(2e32)(3137)(3238)(3138)(3238)(3438)(3935)(3430)(3235)(3533)(3633)
Dec  6 16:14:39.181798  jlink:       triceExamples.c    50              16200 5 times a 16 byte long Trice messages, which may not be written all if the buffer is too small:
Dec  6 16:14:39.182303  jlink:       triceExamples.c    52       19_555 16335 i=44444400 aaaaaa00
Dec  6 16:14:39.182303  jlink:       triceExamples.c    52       18_941 16335 i=44444401 aaaaaa01
Dec  6 16:14:39.182303  jlink:       triceExamples.c    52       18_327 16335 i=44444402 aaaaaa02
Dec  6 16:14:39.182834  jlink:       triceExamples.c    52       17_713 16335 i=44444403 aaaaaa03
Dec  6 16:14:39.182834  jlink:       triceExamples.c    52       17_099 16335 i=44444404 aaaaaa04
Dec  6 16:14:40.187121  jlink:       triceExamples.c    29    0,031_855 16356 🐁 Speedy Gonzales a  32-bit timestamp
Dec  6 16:14:40.187121  jlink:       triceExamples.c    30    0,031_299 16355 🐁 Speedy Gonzales b  32-bit timestamp
Dec  6 16:14:40.187121  jlink:       triceExamples.c    31    0,030_743 16354 🐁 Speedy Gonzales c  32-bit timestamp
Dec  6 16:14:40.187121  jlink:       triceExamples.c    32    0,030_187 16353 🐁 Speedy Gonzales d  32-bit timestamp
Dec  6 16:14:40.187121  jlink:       triceExamples.c    33       29_631 16352 🐁 Speedy Gonzales e  16-bit timestamp
Dec  6 16:14:40.187121  jlink:       triceExamples.c    34       29_073 16351 🐁 Speedy Gonzales f  16-bit timestamp
Dec  6 16:14:40.187121  jlink:       triceExamples.c    35       28_515 16350 🐁 Speedy Gonzales g  16-bit timestamp
Dec  6 16:14:40.187121  jlink:       triceExamples.c    36       27_957 16349 🐁 Speedy Gonzales h  16-bit timestamp
Dec  6 16:14:40.187121  jlink:       triceExamples.c    42       27_093 16344 2.71828182845904523536 <- float number as string
Dec  6 16:14:40.187121  jlink:       triceExamples.c    43       25_913 16343 2.71828182845904509080 (double with more ciphers than precision)
Dec  6 16:14:40.187121  jlink:       triceExamples.c    44       25_310 16342 2.71828174591064453125 (float  with more ciphers than precision)
Dec  6 16:14:40.187121  jlink:       triceExamples.c    45       24_730 16341 2.718282 (default rounded float)
Dec  6 16:14:40.187121  jlink:       triceExamples.c    46       24_149 16340 A Buffer:
Dec  6 16:14:40.187121  jlink:       triceExamples.c    47       23_577 16339 32 2e 37 31 38 32 38 31 38 32 38 34 35 39 30 34 35 32 33 35 33 36
Dec  6 16:14:40.187630  jlink:       triceExamples.c    48       22_391 16338 31372e32  31383238  34383238  34303935  35333235
Dec  6 16:14:40.187690  jlink:       triceExamples.c    49       21_290 16337 ARemoteFunctionName(2e32)(3137)(3238)(3138)(3238)(3438)(3935)(3430)(3235)(3533)(3633)
Dec  6 16:14:40.187690  jlink:       triceExamples.c    50              16200 5 times a 16 byte long Trice messages, which may not be written all if the buffer is too small:
Dec  6 16:14:40.187690  jlink:       triceExamples.c    52       19_546 16335 i=44444400 aaaaaa00
Dec  6 16:14:40.188195  jlink:       triceExamples.c    52       18_930 16335 i=44444401 aaaaaa01
Dec  6 16:14:40.188195  jlink:       triceExamples.c    52       18_314 16335 i=44444402 aaaaaa02
Dec  6 16:14:40.188195  jlink:       triceExamples.c    52       17_698 16335 i=44444403 aaaaaa03
Dec  6 16:14:40.188195  jlink:       triceExamples.c    52       17_082 16335 i=44444404 aaaaaa04
Dec  6 16:14:41.191648  jlink:    triceLogDiagData.c    21              16382 RTT0_writeDepthMax=325 (BUFFER_SIZE_UP=1024)
Dec  6 16:14:41.191648  jlink:    triceLogDiagData.c    44              16378 triceSingleDepthMax = 108 of 172 (TRICE_BUFFER_SIZE)
Dec  6 16:14:41.191648  jlink:       triceExamples.c    29    0,030_628 16356 🐁 Speedy Gonzales a  32-bit timestamp
Dec  6 16:14:41.191648  jlink:       triceExamples.c    30    0,030_072 16355 🐁 Speedy Gonzales b  32-bit timestamp

“🐁 Speedy Gonzales” needs about 500 MCU clocks.

29.6. A developer setting, only enabling SEGGER_RTT and without deferred output gives after running ./build.sh TRICE_DIAGNOSTICS=0 TRICE_PROTECT=0:

arm-none-eabi-size out/F030_inst.elf
   text    data     bss     dec     hex filename
   5796      16    2736    8548    2164 out/F030_inst.elf

That is nearly 1 KB less Flash needs.

The output:

ms@DESKTOP-7POEGPB MINGW64 ~/repos/trice_wt_devel/examples/F030_inst (devel)
$ trice l -p jlink -args "-Device STM32F030R8 -if SWD -Speed 4000 -RTTChannel 0" -pf none -d16 -showID "deb:%5d"
Dec  6 16:20:10.545274  jlink:       triceExamples.c    12       65_535 16369  Hello! 👋🙂
Dec  6 16:20:10.545274  jlink:
Dec  6 16:20:10.545274  jlink:         ✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨
Dec  6 16:20:10.545274  jlink:         🎈🎈🎈🎈  𝕹𝖀𝕮𝕷𝕰𝕺-F030R8   🎈🎈🎈🎈
Dec  6 16:20:10.545274  jlink:         🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃
Dec  6 16:20:10.545274  jlink:
Dec  6 16:20:10.545274  jlink:
Dec  6 16:20:10.545274  jlink:       triceExamples.c    61              16334 TRICE_DIRECT_OUTPUT == 1, TRICE_DEFERRED_OUTPUT == 0
Dec  6 16:20:10.545274  jlink:       triceExamples.c    63              16333 TRICE_STACK_BUFFER, TRICE_MULTI_PACK_MODE
Dec  6 16:20:10.545890  jlink:       triceExamples.c    76              16327 _CYCLE == 1, _PROTECT == 0, _DIAG == 0, XTEA == 0
Dec  6 16:20:10.546396  jlink:       triceExamples.c    77              16326 _SINGLE_MAX_SIZE=104, _BUFFER_SIZE=172, _DEFERRED_BUFFER_SIZE=1024
Dec  6 16:20:11.448885  jlink:       triceExamples.c    29    0,031_859 16356 🐁 Speedy Gonzales a  32-bit timestamp
Dec  6 16:20:11.448885  jlink:       triceExamples.c    30    0,031_661 16355 🐁 Speedy Gonzales b  32-bit timestamp
Dec  6 16:20:11.448885  jlink:       triceExamples.c    31    0,031_463 16354 🐁 Speedy Gonzales c  32-bit timestamp
Dec  6 16:20:11.448885  jlink:       triceExamples.c    32    0,031_265 16353 🐁 Speedy Gonzales d  32-bit timestamp
Dec  6 16:20:11.448885  jlink:       triceExamples.c    33       31_067 16352 🐁 Speedy Gonzales e  16-bit timestamp
Dec  6 16:20:11.448885  jlink:       triceExamples.c    34       30_867 16351 🐁 Speedy Gonzales f  16-bit timestamp
Dec  6 16:20:11.448885  jlink:       triceExamples.c    35       30_667 16350 🐁 Speedy Gonzales g  16-bit timestamp
Dec  6 16:20:11.448885  jlink:       triceExamples.c    36       30_467 16349 🐁 Speedy Gonzales h  16-bit timestamp
Dec  6 16:20:11.549660  jlink:       triceExamples.c    42       29_961 16344 2.71828182845904523536 <- float number as string
Dec  6 16:20:11.549660  jlink:       triceExamples.c    43       29_141 16343 2.71828182845904509080 (double with more ciphers than precision)
Dec  6 16:20:11.549660  jlink:       triceExamples.c    44       28_897 16342 2.71828174591064453125 (float  with more ciphers than precision)
Dec  6 16:20:11.549660  jlink:       triceExamples.c    45       28_675 16341 2.718282 (default rounded float)
Dec  6 16:20:11.549660  jlink:       triceExamples.c    46       28_452 16340 A Buffer:
Dec  6 16:20:11.550166  jlink:       triceExamples.c    47       28_238 16339 32 2e 37 31 38 32 38 31 38 32 38 34 35 39 30 34 35 32 33 35 33 36
Dec  6 16:20:11.550247  jlink:       triceExamples.c    48       27_412 16338 31372e32  31383238  34383238  34303935  35333235
Dec  6 16:20:11.550247  jlink:       triceExamples.c    49       26_671 16337 ARemoteFunctionName(2e32)(3137)(3238)(3138)(3238)(3438)(3935)(3430)(3235)(3533)(3633)
Dec  6 16:20:11.550247  jlink:       triceExamples.c    50              16200 5 times a 16 byte long Trice messages, which may not be written all if the buffer is too small:
Dec  6 16:20:11.550754  jlink:       triceExamples.c    52       25_646 16335 i=44444400 aaaaaa00
Dec  6 16:20:11.550754  jlink:       triceExamples.c    52       25_389 16335 i=44444401 aaaaaa01
Dec  6 16:20:11.550754  jlink:       triceExamples.c    52       25_132 16335 i=44444402 aaaaaa02
Dec  6 16:20:11.551285  jlink:       triceExamples.c    52       24_875 16335 i=44444403 aaaaaa03
Dec  6 16:20:11.551285  jlink:       triceExamples.c    52       24_618 16335 i=44444404 aaaaaa04
Dec  6 16:20:12.453968  jlink:       triceExamples.c    29    0,031_859 16356 🐁 Speedy Gonzales a  32-bit timestamp
Dec  6 16:20:12.453968  jlink:       triceExamples.c    30    0,031_661 16355 🐁 Speedy Gonzales b  32-bit timestamp

“🐁 Speedy Gonzales” direct outout needs about 200 MCU clocks and not 500 as before.

29.7. Settings Conclusion

29.8. Legacy Trice Space Example (Old Version)

29.9. Memory Needs for Old Example 1

The following numbers are measured with a legacy encoding, showing that the instrumentation code can be even smaller.

Program Size (STM32-F030R8 demo project) trice instrumentation buffer size compiler optimize for time comment
Code=1592 RO-data=236 RW-data= 4 ZI-data=1028 none 0 off CubeMX generated, no trice
Code=1712 RO-data=240 RW-data=24 ZI-data=1088 core 64 off core added without trices
Code=3208 RO-data=240 RW-data=36 ZI-data=1540 TriceCheckSet() 512 off TRICE_SHORT_MEMORY is 1 (small)
Code=3808 RO-data=240 RW-data=36 ZI-data=1540 TriceCheckSet() 512 on TRICE_SHORT_MEMORY is 0 (fast)

29.10. Memory Needs for Old Example 2

Project Compiler Optimization Link-Time-Optimization Result Remark
MDK-ARM_STM32F030_bareerated CLANG v6.19 -Oz yes Code=1020 RO-data=196 RW-data=0 ZI-data=1024 This is the plain generated project without trice instrumentation.
MDK-ARM_STM32F030_instrumented CLANG v6.19 -Oz yes Code=4726 RO-data=238 RW-data=16 ZI-data=4608 This is with full trice instrumentation with example messages.

(back to top)

30. Trice Project Image Size Optimization

Modern compilers are optimizing out unused code automatically, but you can help to reduce trice code size if your compiler is not perfect.

30.1. Code Optimization -o3 or -oz (if supported)

For debugging it could be helpful to switch off code optimization what increases the code size. A good choice is -o1. See also TRICE_STACK_BUFFER could cause stack overflow with -o0 optimization.

30.2. Compiler Independent Setting (a bit outdated)

Maybe the following is a bit unhandy but it decreases the code amount, build time and the image size.

When having lots of program memory simply let all values be 1. With specific linker optimization unused functions can get stripped out automatically.

It is possible to #define TRICE_SINGLE_MAX_SIZE 12 for example in triceConfig.h. This automaticaly disables all Trice messages with payloads > 8 bytes (Trice size is 4 bytes).

30.3. Linker Option –split-sections (if supported)

In ARM-MDK uVision Project -> Options -> C/C++ -> "One EFL section for each function" allows good optimization and getting rid of unused code without additional linker optimization. This leads to a faster build process and is fine for most cases. It allows excluding unused functions.

30.4. Linker Optimization -flto (if supported)

30.4.1. ARMCC Compiler v5 Linker Feedback

30.4.3. GCC

With GCC use the -flto CLI switch directly.

30.4.4. LLVM ARM Clang

This compiler is much faster and creates the smallest images. Right now it uses the GCC libs and linker.

30.4.5. Other IDE´s and compilers

Please check the manuals and create a pull request or simply let me know.

30.5. Legacy STM32F030 Example Project - Different Build Sizes

30.5.1. ARMCC compiler v5

Compiler Linker Result Comment
o0   Code=46942 RO-data=266 RW-data=176 ZI-data=4896 very big
o1   Code=22582 RO-data=258 RW-data=168 ZI-data=4896  
o3   Code=21646 RO-data=258 RW-data=168 ZI-data=4896  
o0 split sections Code= 7880 RO-data=268 RW-data=156 ZI-data=4892 for debugging
o1 split sections Code= 5404 RO-data=260 RW-data=148 ZI-data=4892 for debugging
o3 split sections Code= 4996 RO-data=260 RW-data=148 ZI-data=4892 good balance
o0 flto Code= 8150 RO-data=266 RW-data=176 ZI-data=4896 builds slower
o1 flto Code= 5210 RO-data=258 RW-data=148 ZI-data=4892 builds slower
o3 flto Code= 4818 RO-data=258 RW-data=148 ZI-data=4892 builds slower, smallest image

(back to top)

31. Trice Tags, Color, and Weights

Tags label Trice messages on the host. They can control presentation, selection, ID assignment, and weight-based filtering without adding target runtime data because the tag is part of the format string stored in til.json.

Context Enrichment also uses tags as selectors: a matching -ce rule adds values to selected log messages.

31.1. How to use tags

Add a tag and a colon in front of a Trice format string:

trice("wrn:Motor temperature is %d C\n", temperature);

The Trice tool recognizes wrn, applies the Warning color, and removes the prefix when it is completely lower case. A mixed-case or uppercase prefix remains visible:

wrn:fox  -> fox
Wrn:fox  -> Wrn:fox

The colors, aliases, and weights are defined in lineTransformerANSI.go. The tag itself does not require a target-side enable switch. Use -pick or -ban to select complete tag groups during host logging. Tag-specific ID ranges can additionally assign and route target IDs.

Short aliases have one unambiguous meaning. For example W and w select Write, while wrn, WARN, and WARNING select Warning. Configurations created for older Trice versions should replace an ambiguous short alias with the intended explicit name before using it for -pick, -ban, -logLevel, or -IDRange.

It is possible to concatenate individually tagged fragments to produce output such as:

Colored Trice output

The source for this example is in triceCheck.c. For each such color change a separate Trice message is needed, because tags can only occur at the beginning of a format string.

31.2. Tag weights

Each tag group has one integer weight in the range 0..999. A larger value means greater importance. The weight belongs to the group and therefore applies to every alias in that group. It is independent of the group’s position in the tag table and independent of its color.

The following table lists the default weights defined in lineTransformerANSI.go. Re-assignment for logging using the CLI is possible: -ulabel warn:650.

Group Weight
Fatal 800
Critical 750
Emergency 700
Error, Assert, Alarm 650
Warning, Notice 600
Attention, Alert 550
INFO, Config 500
Time, Message, Read, Write, Receive, Transmit, Diag, Interrupt, Signal, Test, Default 400
untagged 400
Microsecond, Millisecond, Second, DeltaTime 350
Debug 300
Trace 200
Verbose 100

CYCLE_ERROR is a Trice tool diagnostic rather than an application tag. Its stored value does not define application-message priority.

31.3. Selecting tags and priority

Use the repeatable -pick option to display selected tag groups, or -ban to suppress selected groups. The two options are mutually exclusive. Separate names with colons or repeat the option:

trice log -pick err:wrn -pick notice
trice log -ban dbg -ban trace:verbose

Aliases select the complete group. -pick all selects every message and -pick off selects none; -ban all suppresses every message and -ban off suppresses none. Empty list entries and unknown names are command-line errors.

Use -logLevel with all, off, a registered tag or alias, or a numeric weight from 0 through 999. An application event passes when its tag group’s weight is greater than or equal to the threshold. A lower threshold therefore displays more messages:

trice log -logLevel info
trice log -logLevel 500

Both commands use the same threshold with the default INFO weight of 500. Unknown level names and numeric values outside 0..999 are rejected before the input channel is opened. Application messages without a recognized format-string tag use the built-in untagged group with default weight 400. -logLevel off suppresses all application events.

With the default weights, -logLevel info excludes Message and untagged events because their weight is 400. Use -logLevel msg or -logLevel 400 to include these groups and all higher-weight groups while still excluding Debug (300), Trace (200), and Verbose (100). Without a -logLevel option, the threshold defaults to all.

Use a threshold when the requirement is “Warning and everything more important”, including new user tags assigned sufficiently high weights. For example, trice log -ulabel motor:780 -logLevel wrn includes motor alongside Warning, Notice, Error, and higher-weight groups. Use -pick for a specific set of groups such as Receive and Transmit, regardless of their weights. Combining both expresses a selected subsystem or category set with a minimum priority.

These are host-side filters. They do not avoid target argument evaluation or reduce data already transmitted by the target. Target ID routing is configured separately as described in ID Routing; received raw bytes can still be kept in a binary logfile.

For a short capture to experiment with, run the PC feature tour and compare ./show_json.sh, ./show_json.sh -pick info, and ./show_json.sh -logLevel wrn. The G0B1 feature tour applies the same output choices to a board capture.

All -ulabel values are applied before -pick, -ban, and -logLevel are resolved. Option order therefore does not matter:

trice log -pick motor -ulabel motor:650
trice log -ulabel motor:650 -pick motor

-pick or -ban and -logLevel jointly decide whether each application event is displayed. An accepted event keeps its timestamps, source location, ID, prefix, suffix, and all lines of its text; a rejected event leaves none of these behind. For example, -pick err:wrn -logLevel err shows only Error events. A line assembled from several Trice calls contains only the accepted calls, including their accepted newline characters. The same decision also applies to visualization routing. Byte-oriented CHAR/DUMP chunks have no typed event boundary and retain their fragment-based selection behavior.

Each Trice call is one event. Accepted fragments are concatenated until an accepted newline arrives; a rejected newline does not finish the visible line. For example, the three calls msg:A, dbg:B\n, msg:C\n produce AC\n with either -pick msg or -logLevel msg using the default weights. A multi-line event is accepted or rejected as a whole, preserving its continuation indentation. At the end of buffered input, a remaining visible fragment is flushed with a newline.

Metadata columns belong to the first accepted event of each visible line. Target timestamp differences use the previous visible line’s first accepted event; rejected events and later fragments on the same line do not update that reference. -addNL appends a newline to every event, including one already ending in a newline; the latter produces an additional indented blank line. Without -addNL, an empty event produces no visible output, while an accepted newline-only event produces a blank line.

31.4. Decoder diagnostics

Decoder and transport diagnostics are tool output rather than application messages. Examples include an unknown Trice ID, an invalid COBS/TCOBS frame, an unsupported or truncated packet, and a cycle-counter mismatch. These diagnostics remain visible with restrictive -pick, -ban, and -logLevel off settings. They receive no application metadata, do not enter visualization routing, and are not assigned an application tag.

The translator keeps the diagnostic writer separate from the application line composer. The command currently directs both to its normal local output, while remote display receives application lines only. A machine-readable application sink must keep the separate diagnostic writer on a human-readable tool channel instead of inserting diagnostic text into records. Recoverable decoder diagnostics do not stop logging; writer and input errors retain their existing error handling. Binary recording occurs before decoding and is unaffected.

31.5. User-defined tags, weights, and colors

Use the repeatable -ulabel name, -ulabel name:weight, or -ulabel name:color option to register a new tag or change a known group’s weight or color for one command:

trice log -ulabel motor -ulabel sensor:150
trice insert -ulabel motor:250
trice bind -ulabel motor:250
trice log -ulabel motor:red:blue
trice log -ulabel msg:300 -ulabel msg:red:blue

A new tag without an explicit weight receives the final INFO weight after all -ulabel options have been processed. An explicit weight must be between 0 and 999, inclusive. The color must be exactly one of the tokens printed by trice generate -colors, such as red:blue. An unknown color is rejected with a hint to that command.

An existing name without either property leaves its group unchanged. An existing name with a weight changes the complete group, including every alias. The last explicit assignment wins:

-ulabel msg:150 -ulabel M:600 -ulabel msg

This results in weight 600 for the complete Message group. It does not create additional groups for msg or M.

Weight and color are independent. -ulabel msg:300 -ulabel msg:"yellow+h:green" gives every built-in Message alias (msg, message, MSG, MESSAGE, and the other aliases) weight 300 and the same color. Repeating either property changes only that property; the last value for each property wins. Free user labels are literal: -ulabel new:300 -ulabel NEW:400 creates two distinct labels with no user-defined alias relationship.

Each option accepts exactly one name. The old colon-separated name-list form is invalid. Empty names or values, unknown colors, weights outside 0..999, purely numeric names, all, and off are rejected before the command opens or changes its input and output files. A color token itself contains a colon between foreground and background.

For insert and bind, only the registered name participates in tag and -IDRange handling. Weight and color change no target data and are not stored in the source or til.json.

Command-specific user tags, weight overrides, and color overrides are discarded before a later command in the same process starts.

31.6. Untagged application events

The host assigns the built-in untagged group once to an application event whose stored format string has no recognized tag. This also applies to an empty prefix, an unknown prefix caused by a typo, or ordinary text containing a colon. For ID-based messages, classification uses the format string from the Trice ID lookup table rather than text supplied as a runtime value.

Stored format string Visible text with -color off
Hello Hello
untagged:Hello untagged:Hello
mgs:blah mgs:blah
msg:Hello msg:Hello

The host never adds untagged: to visible application text. It appears only if the application supplied those characters. For example, trice("Hello") displays Hello with every palette, while an unknown prefix such as mgs:blah remains visible so a typo can be spotted. An explicitly written untagged: follows the usual presentation rules for lowercase tags: -color off keeps it, while -color none and default remove the tag from the visible text.

-pick untagged, -ban untagged, -logLevel, and statistics handle this group like other application tags. Its weight is independent of INFO and can be changed for one command with -ulabel untagged:150. An optional color such as -ulabel untagged:red:default still styles text output with -color default, without printing the group name. -color none and -color off leave it uncolored.

Decoder and transport diagnostics are not untagged application events. Byte-oriented CHAR and DUMP decoder chunks also receive no synthetic event tag because they do not identify individual application events. Classification changes neither source format strings, lookup-table entries, IDs, nor recorded raw bytes.

31.7. Event statistics

-tagStat counts successfully decoded application events by tag group, including untagged; -triceStat counts successfully decoded ID-based Trice events by ID. -stat prints both reports. These totals are recorded before -pick, -ban, -logLevel, and visualization routing, so they include events hidden from the text display. One call counts once even if it spans several output lines; several calls on one line count separately. Palette changes, metadata columns, and repeated report printing do not change the totals. Decoder diagnostics and malformed records are excluded. Formatted ID-less typeX0 records contribute to tag statistics but have no Trice ID to count. Byte-oriented CHAR/DUMP chunks have no event boundary and do not contribute to these event totals.

31.8. Output options

Trice color output options

With -color default, recognized lower-case tag prefixes are removed and their configured colors are applied. With -color none, lower-case prefixes are removed without adding colors. With -color off, prefixes remain unchanged and no colors are added.

31.9. Check color alternatives

There are over 1000 foreground, background, and style combinations:

Trice color alternatives

Run trice generate -colors to display them. Use -ulabel name:color for a per-command override. Modify lineTransformerANSI.go and rebuild the Trice tool with go install ./... or ./scripts/buildTriceTool.sh to change the built-in palette.

31.10. Color issues under Windows

If a Windows console displays ANSI escape sequences instead of colors, use a terminal with ANSI color support, such as Windows Terminal, Git Bash, or Alacritty. Additional background is available in Windows console with ANSI colors handling.

(back to top)

32. Structured Logging

Machine-readable output requires the standard TREX wire format. CHAR and DUMP do not provide suitable event boundaries and are rejected with -logFormat json or -logFormat kv.

Structured Logging adds named, typed values to a readable message. A regular Trice call is enough:

trice("info:Motor {motor_id}: {temperature_c:%.1f C}", motor_id, aFloat(temperature_c));

With motor_id = 3 and temperature_c = 87.5, the output depends on the selected format:

These three outputs use -li off -showID '' -hs off -ts off, so no optional host or target metadata appears.

tlog -logFormat text (default):

Motor 3: 87.5 C

tlog -logFormat json (NDJSON):

{"tag":"INFO","level":"INFO","message":"Motor 3: 87.5 C","fields":{"motor_id":3,"temperature_c":87.5}}

tlog -logFormat kv:

tag=INFO level=INFO message="Motor 3: 87.5 C" field.motor_id=3 field.temperature_c=87.5

Further output formats, such as CSV, could be added when needed.

The target continues to transmit the ID and values using the existing wire format. Field names are not transmitted as additional runtime arguments or payload; they reside in the dictionary on the host.

Scalar Trices with 8, 16, 32 or 64 bits and strings through triceS and triceN are supported. The target macros and their bit-width rules remain authoritative. Context Enrichment (bind -ce) can add supported structured fields; see Context Enrichment.

Try the PC Feature Tour or the G0B1 Feature Tour: both demonstrate named numeric and string fields and text, NDJSON and KV output. The PC tour requires no hardware and immediately produces a short binary capture.

Named fields are currently unavailable for buffer formats such as triceB. These repeat a printf placeholder for each buffer element, whereas a structured field describes one named value. The current field schema does not define whether a named buffer should appear as a numeric list, a byte sequence or text. Therefore, bind and insert reject trice8B("msg:{bytes:%02x}", bytes, 2) with an error. triceF does not support named fields either.

Buffer logging without a named field remains available: for 0x01 and 0x02, trice8B("msg:%02x ", bytes, 2) produces JSON {"tag":"MESSAGE","message":"01 02 "} without a fields object. For a fixed number of individually named values, use scalar Trices instead. Structured output of entire buffers requires a separate format and schema definition.

32.1. Placeholders and Names

Format string syntax Meaning
{motor_id} Explicit field name, default display.
{} Derive the name from the corresponding C argument.
{plant.} Prefix the derived name with plant..
{:%.1f} Derived name and explicit display.
{temperature:%.1f C} Explicit name and display.
{: = %.1f C} Derived name and display.
{motor_id:: %d, } Name motor_id, display : %d, .
`` Literal opening and closing braces.

The first colon separates the name from its display. The display may contain free text and further colons, but must contain exactly one supported format specifier. %% is not an additional value. Dynamic widths such as %*d are not allowed in a structured field.

Without an explicit display, %d is used. If the entire argument is wrapped in aFloat(...) or aDouble(...), the default is %f. There is no general C type inference: an unsigned value needs, for example, {counter:%u}, an address {address:%p}, and a string {text:%s}.

trice("info:{} {}", motor_id, aFloat(temperature_c));
trice("info:{plant.}", motor->temperature);
trice("info:{temperature:%.2f}", aFloat(read_temperature()));
triceS("info:{message:%s}", "motor ready");
triceN("info:{message:%s}", buffer, length);

Names can be derived from individual identifiers and simple member chains. Both motor.temperature and motor->temperature yield motor.temperature. The aFloat and aDouble wrappers are removed when deriving names. Expressions such as values[0], a+b or read_temperature() need an explicit name. Names consist of identifiers with optional dot-separated segments; whitespace within an identifier, empty segments and leading digits are invalid.

Canonical names must be unique within a call. {motor->id} and {motor.id} collide. However, motor and motor.id are two distinct, flat keys; dotted names do not create nested JSON objects.

Ordinary %... conversions and structured placeholders consume arguments together, from left to right:

trice("info:%u {temperature:%.1f} %x {last}", sequence, aFloat(temperature), flags, last);

Only temperature and last are exported as fields here. sequence and flags appear only in the message. Instrumentation checks missing or extra arguments and duplicate or malformed fields. Schema errors prevent publication of partial changes from the instrumentation run.

The new syntax for literal braces also applies in text mode:

trice("info:set=1, value={value}", value);

This produces set={1,2}, value=7 when value = 7. A backslash before a brace does not replace doubling it.

32.2. Field Types and Display

Format specifier Structured value
%d, %i Signed integer of the Trice bit width.
%u, %o, %O, %x, %X, %b Unsigned integer. The selected numeric base affects only the message.
%f, %F, %e, %E, %g, %G Floating-point number; 32 bits with aFloat, 64 bits with aDouble.
%c, %q, %U Character as a string; invalid Unicode code points become the replacement character.
%t Boolean: zero is false, other values are true.
%s String from triceS or triceN.
%p Address in the form 0x...; does not imply the type of the referenced object.

%% is a literal percent sign and creates neither a field nor an additional argument.

Supported C length modifiers such as %lu or %llX do not change these semantics; the Trice family determines the transmitted bit width. Precision, field width and display text affect only message. For example, {value:%.1f} displays 1.2 for a value of 1.25, while the field contains 1.25. Similarly, {text:%.3s} truncates only the message, not the exported string.

-unsigned=false does not change the meaning of structured unsigned fields either. It continues to control the corresponding classical text display.

Named string fields use %s; alternative classical string displays such as %x remain message text without a named field. A structured floating-point field in a 64-bit Trice requires aDouble(...); aFloat(...) is rejected there to prevent interpreting the transmitted bits as a double.

32.3. Instrumentation and Dictionary

Both bind and insert support structured templates. As before, clean removes inserted IDs and preserves the original template spelling. Source shorthand is not replaced with canonical field names.

trice bind -src app -genDir generated -til til.json -li li.json

Alternatively, for the Insert/Clean workflow:

trice insert -src app -genDir generated -til til.json -li li.json
trice clean -src app -til til.json -li li.json

Like the existing workflows, these examples require an initialized TIL. The usual build integration of Bind sidecars is still required.

The C test examples also demonstrate 8-, 16-, 32- and 64-bit values, different stamps, triceS/triceN, mixed placeholders and the aFloat()/aDouble() wrappers. Their integration tests verify text output after bind and insert, using the same CLI settings as the PC target tests. Names from motor.state and motorPtr->rpm appear canonically in the field registry as motor.state and motorPtr.rpm.

In til.json, the only schema fields remain Type and Strg. Strg contains all information needed for decoding, including derived names and floating-point defaults. For example,

trice("info:Motor {}: {} C", motor_id, aFloat(temperature_c));

is stored with a canonical Strg such as:

{"Type":"trice","Strg":"info:Motor {motor_id}: {temperature_c:%f} C"}

The actual spelling of Type still follows the respective instrumentation path. Schema identity remains Type + Strg. Renaming a field creates a new schema identity and therefore a different ID; historical TIL entries remain available for older firmware. Equivalent name spellings such as motor->temperature and motor.temperature retain the same canonical identity. Repeating unchanged runs creates no additional schemas. Generated local C format data contains the derived printf format string.

32.4. Output Formats and Event Boundaries

trice log and tlog support -logFormat text, -logFormat json and -logFormat kv, with -logFormat key-value as an alias for kv. Values are case-insensitive; the default remains text. The text-based remote display mode and test table output cannot be combined with machine-readable formats.

trice log -p FILEBUFFER -args capture.bin -pf TCOBSv1 -til til.json -li off -hs off -ts off -logFormat json

Framing and dictionary must match the capture. -logFormat kv reads the same capture as key/value output; text retains the existing text output.

-logFormat json produces NDJSON (JSON Lines): one JSON object followed by LF per accepted Trice event. There is no separate CLI value ndjson. KV also produces exactly one line per event. Partial calls are not combined, and multiline messages are not split into multiple events. An empty message is also an event. Newlines within the message are escaped.

Text prefixes, suffixes, colors, indentation, time-difference columns and -addNL do not decorate JSON/KV records. Actual metadata appears in separate fields instead. In JSON/KV mode, diagnostics and status messages go to stderr; stdout, -logfile and TCP log output contain application records. Write errors are returned to the caller.

-pick, -ban and -logLevel continue to select whole application events. Statistics count successfully decoded events before this selection. Visualization also follows selection; its existing restrictions, such as supported numeric single-line messages, still apply. After successful visualization, log=drop suppresses the entire normal record. Binary captures remain unfiltered and unaffected by the selected output format.

32.5. JSON and KV Contract

Every record contains tag and message. tag is the canonical name of a registered format string tag; alias lookup for this metadata field is case-insensitive. For example, inf:Hi and Inf:Hi both yield tag=INFO. If no registered tag matches, the value is untagged.

message takes the message content of text output without ANSI colors, outer metadata, text prefixes or suffixes. With -color none or default, only an exactly registered, entirely lowercase format string tag is removed: inf:Hi becomes Hi, whereas Inf:Hi remains Inf:Hi. An unknown prefix such as mgs:Hi also remains visible. With -color off, explicitly written tag prefixes remain, as in text mode. Trice never adds untagged: to message text: trice("Hi") yields message="Hi" and tag="untagged"; trice("mgs:Hi") yields message="mgs:Hi" and tag="untagged". Leading and trailing whitespace, empty messages and messages consisting only of whitespace are preserved.

A runtime string does not change tag classification: with triceS("{text:%s}", value) and a value starting with err:, tag remains untagged. Existing text output converts sequences such as \n and \t for display, including within runtime strings; message follows that display. The named field fields.text retains the transmitted string, except for outer whitespace.

An optional level is determined through a fixed alias table independent of colors and weights. Comparison is case-insensitive. For example, err, ERR and Error map to ERROR; warn, wrn and Warning map to WARNING. Supported canonical values are FATAL, CRITICAL, EMERGENCY, ERROR, WARNING, ATTENTION, INFO, DEBUG, TRACE, NOTICE, ALERT, ASSERT, ALARM and VERBOSE. A display-only tag such as msg or a freely defined user label receives no invented level. Alias lookup for tag does not change the existing tag registry or its filtering behavior; level classification remains independent of it as well.

In JSON, user fields reside under fields. Host and user fields therefore cannot overwrite each other: a user field named tag appears under fields.tag. User fields are emitted in template order. A record without exportable user fields has no empty fields object.

Integers remain JSON numbers, including the full 64-bit boundary values. Readers must use a sufficiently precise numeric type; converting every number to an IEEE-754 double may round large integers. Addresses are JSON strings such as "0x20001234". Non-finite floating-point values (NaN, +Inf, -Inf) are omitted as JSON fields; their text display remains in message. Other fields of the same event are preserved.

In KV, user fields are named field.<name> and also follow template order. Numbers, Booleans and addresses are unquoted; strings, characters and messages are always double-quoted. Quotes, backslashes, LF, CR and tabs are escaped. Non-finite floats appear in KV as NaN, +Inf or -Inf.

Outer whitespace is removed from other string values such as hs, file and named string fields. A named field containing only whitespace is emitted as an empty string. This trimming does not apply to message.

tag=INFO level=INFO message="Motor \"A\"\nready" field.message="Motor \"A\"\nready" field.address=0x20001234

32.6. Optional Metadata

Field Prerequisite and content
id ID-based event and enabled -showID; numeric Trice ID without text padding.
file, line Existing LI entry, enabled -li and -liFmt; only an available filename or a nonzero line number is emitted.
ts16, ts32 Existing 16- or 32-bit target stamp and output enabled through -ts or the corresponding -ts16/-ts32 option. Each value is a string in its own CLI display format; an existing zero value is also emitted.
ts16Delta, ts32Delta Enabled -ts16delta or -ts32delta option and a previous stamp of the same bit width. The first stamp has no delta field at all. Display follows the respective delta option.
hs Enabled -hs; formatted host time string without trailing column padding. -hs off or none omits it.

Target stamp values have no leading stamp tag such as time: or dt:, but retain configured additional text and units. Outer whitespace is removed. The four kinds remain separate: for example, ts16 may represent a temperature and ts32 a time. -ts0 and -ts0delta are text placeholders only and create no metadata fields.

For example, -ts off -ts16 'temp:%d C' -ts16delta 'step:%d C' with two successive 16-bit stamps of 8 and 11 first yields "ts16":"8 C" and then "ts16":"11 C","ts16Delta":"3 C". With -logFormat kv, these values appear as ts16="11 C" ts16Delta="3 C".

The fixed order is tag, optional level, message, then any available id, file, line, target stamp and delta, hs, and finally user fields. Missing metadata is omitted rather than simulated with null or substitute values. Formatted ID-less typeX0 events have no ID, TIL fields or target timestamps; they still receive tag, message and, where applicable, level and hs.

32.7. Field Registry

A successful bind or insert run produces trice-fields.txt, listing field names and their frequencies in the last successful run. -genDir selects the directory for both commands; the default is ./generated, relative to the invocation directory. For bind, the sidecar headers are stored there too. -buildDir and -bindDir are rejected.

       1 motor_id
       1 temperature_c
       4 motor.state

The count covers instrumented sites with that user field in the current invocation. The file is regenerated completely, without adding historical TIL fields. A run over part of the sources describes only that part; project-wide checks should therefore include all relevant sources. Cache hits are counted. Host metadata is excluded, but a field actually named tag by the user is included.

Sorting is by ascending count, then alphabetically by field name for equal counts. The line format is %8d %s\n; field names have no artificial length limit. A successful run without user fields creates an empty file. -dry-run publishes no new file and preserves an existing registry (trice-fields.txt).

(back to top)

33. Trice Context Enrichment

Context Enrichment (CE) adds extra values to selected Trice messages, such as task context, position or operating state. A CLI rule applies to all log sites with the matching selector prefix. This enables additional diagnostics for a build without extending every log site by hand.

The repeatable option has the same syntax for bind, insert and clean:

-ce 'selector:"format-extension"[, comma-free C-expression]...'

For example, -ce 'ctx7:", clock={}", clock' adds the value of clock, valid at that site, to trice(“msg:ctx7:hi\n”);. With clock == 42 and -color none, the message is hi, clock=42. The custom ctx7: remains in the source as a selection marker. It disappears from the final log when it is entirely lowercase. The trailing \n remains after the appended value. (*Note: {} could also be %d or %08x` in this example. The braces simply demonstrate that Structured Logging and Context Enrichment are orthogonal: they can be used independently or together.*)

Command Effect
trice bind -ce … Extends generated sidecars; the Trice calls themselves remain unchanged. Supports direct sites uniquely addressable by source line.
trice insert -ce … Writes the ID, extension and arguments into recognized source calls. Repeating the same rules does not add the extension again.
trice clean -ce … Removes the matching CE extension using the same rules and cleans IDs according to the usual rules. Repetition is harmless.

CE requires no global runtime context or push/pop calls on the target. Every executed record transmits its own additional values. CE is therefore independent of Structured Logging: an extension may use classical printf placeholders or also create named fields.

Two runnable applications demonstrate the same idea: in the PC example, bind -ce adds a cycle value at a shared log site. In the FreeRTOS example, derived directly from G0B1_inst, the same log site adds the identity of its calling task. Both examples also use triceS for a runtime string; CE does not append additional runtime arguments to string Trices.

33.1. Getting Started with Position and Speed

Start with a normally configured Bind project. ./generated (the default, relative to the invocation directory) must be on the compiler include path. These values are visible at the log site:

struct Position {
    int32_t x;
    int32_t y;
};
struct Position pos = {-444, 77};
float velocity = 33.33f;

The log site contains two freely chosen selector prefixes:

trice32("info:pos:speed:Moving sample={sample}\n", 3);

The Bind invocation adds position and speed:

trice bind -ce 'pos:", x={}, y={}", pos.x, pos.y' -ce 'speed:", m/s=%f", aFloat(velocity)'

The final template now contains:

info:Moving sample={sample}, x={pos.x}, y={pos.y}, m/s=%f\n

The transmitted values are the original 3, followed by pos.x, pos.y and the floating-point bit representation of velocity. With -color none, the message portion of text output is:

Moving sample=3, x=-444, y=77, m/s=33.330002

The same result could also be obtained with:

trice32("info:Moving sample={sample}\n", 3);

and this Bind invocation:

trice bind -ce 'info:", x={}, y={}, m/s=%f", pos.x, pos.y, aFloat(velocity)'

The decimal digits follow the 32-bit floating-point representation and %f; %.2f would display 33.33. JSON and KV contain the same message, including its trailing newline, as an escaped string. They also include the numeric fields sample, pos.x and pos.y. %f alone creates no named field; a rule such as speed:", m/s={speed:%.2f}", aFloat(velocity) can create one.

The CE examples in triceCheck.c immediately follow the Structured Logging examples. Their //exp: expectations describe the normal run without -ce. CE integration tests use the same calls and verify the actually transmitted values with the rules shown above.

33.2. Rules and Selectors

bind, insert for adding an extension, and clean for removing it use the same syntax. The resulting rule group at a log site must match completely in format and argument order:

-ce 'selector:"format-extension"[, comma-free C-expression]...'

The shell must pass the entire option value as one argument; the examples use single quotes for this. Extensions use the same C escapes and placeholders as a Trice format string. They are placed before a trailing \n in the original template, or at its end otherwise. Leading and trailing whitespace is preserved.

Selectors are looked up in the contiguous prefix sequence at the beginning of the format string, for example info:pos:speed:. Comparison is case-insensitive; known built-in tag aliases belong to the same group, such as warn and WARNING.

Thus -ce 'Wrn:", attempt={attempt}", 7' and -ce 'WARNING:", attempt={attempt}", 7' select the same log sites, even when their tag aliases use mixed spellings in the source:

trice("WARNING:Connection lost");
trice("wrn:Retrying");

Both calls receive the extension; WARNING: or wrn: retains its original spelling in the source. This alias resolution applies to log site selection with bind, insert and clean. The complete match of format and argument extensions for insert and clean is checked separately.

Thus info:pos:speed: adds position first and speed second, even if the CLI lists the speed rule first. The same name may serve as both a user label and a CE selector; -ulabel and -ce have separate purposes.

33.3. Reversible Workflow with insert and clean

Starting point in main.c:

trice("msg:ctx7:hi\n");

Insert:

trice insert -src main.c -ce 'ctx7:", clock={}", clock'

The call then looks like this (ID 1234 is only an example):

trice(iD(1234), "msg:ctx7:hi, clock={}\n", clock);

No ownership comments or additional CE metadata files are created. Recognition depends solely on the matching selector and the complete extension at the end of the format string and argument list. Whether that text was written by hand or by an earlier Insert invocation does not matter.

For this ID, til.json contains the canonical template msg:hi, clock={clock}\n. The source retains ctx7: and the original field spelling. The compiler receives the additional value; the decoder receives the matching schema. A second identical Insert invocation preserves the extension and ID.

Remove:

trice clean -src main.c -ce 'ctx7:", clock={}", clock'

The source then contains trice("msg:ctx7:hi\n"); again. Fixed argument counts are adjusted accordingly: removing one CE argument turns TRICE16_2(Id(1234), …) into TRICE16_1(Id(0), …). A generic fixed zero-argument form is written as trice0 or TRICE0; without ownership data, the historical spelling trice_0 cannot be distinguished. Usual Clean rules still apply to IDs: IDs of lowercase macro families are removed, while IDs of the corresponding uppercase variants are set to zero.

A complete match must occur at exactly the right position. For the rule above, , clock={} must end the format string, and clock must end the argument list. A trailing message \n remains after the extension. If the rule itself contains a trailing \n, that newline belongs to the extension and is removed with it. Format text, whitespace in the format and field spelling must match: {} and {clock} differ for this comparison. When comparing arguments, C comments are replaced with whitespace as during normal parsing, and outer whitespace is ignored; different expressions such as clock, readClock() or clock + 0 are not considered equal.

Even an apparently matching text ending is not a match if it belongs to an existing format conversion. With -ce 'ctx:"d"', the following format string ends in d, but that character is part of %d, the placeholder for x:

trice("ctx:value=%d", x);

clean -ce leaves the call unchanged: removing d would turn %d into a lone %. Instead, insert -ce appends a separate d, producing trice("ctx:value=%dd", x);.

Similarly, with -ce 'ctx:"%d", clock', the visible %d at the end of the next format string is not a value placeholder:

trice("ctx:value=%d %%d", clock);

The first %d prints clock; %%d prints the literal text %d and needs no additional argument. Although the last characters %d and the last argument clock seem to match the rule, they do not belong together here. clean -ce leaves the call unchanged. insert -ce adds its own placeholder and argument, producing trice("ctx:value=%d %%d%d", clock, clock);.

State at the selected log site insert -ce clean -ce
Complete format and argument extension present Add nothing Remove one complete extension
No complete match, including a partial match Append the entire extension Leave CE unchanged

Normal ID processing occurs in both cases. When several rules match, the entire rule group is compared in application order. Individual matching parts are neither skipped nor removed separately. Repeated insert therefore adds nothing twice. clean removes at most one complete group per invocation; if two identical groups occur consecutively, a second Clean invocation can remove the second group too.

For example, this call is a partial match for the rule ctx7:", clock=%d", clock:

trice("msg:ctx7:hi, clock=%d\n", other);

The format suffix matches, but the last argument other does not. clean -ce leaves the CE part unchanged. insert -ce appends the complete extension:

trice(iD(1234), "msg:ctx7:hi, clock=%d, clock=%d\n", other, clock);

A subsequent insert now recognizes the complete match at the end. With the same rule, clean removes exactly the final , clock=%d and final argument clock; the previous partial match using other remains. Normal checks for valid Trice calls and unique structured field names still apply when appending to a partial match.

To replace an existing extension with another one, first remove the old matching group:

trice clean -src main.c -ce 'ctx7:", clock={}", clock'
trice insert -src main.c -ce 'ctx7:", clock={clock}", readClock()'

An insert or clean invocation without -ce performs only its normal ID task. It does not undo an existing CE extension. For full removal, supply the previous -ce options. Other options, such as -src, -til, -li and any Trice aliases, must match the project as in the normal workflow.

This handwritten call also contains a complete match:

trice("msg:ctx7:manual, clock={clock}\n", clock);

clean -ce 'ctx7:", clock={clock}", clock' removes the field and last argument. A corresponding insert -ce adds nothing. The call may be moved to another line or file; recognition depends neither on its previous location nor on build files.

insert -ce and clean -ce respect -src, -exclude and TRICE_INSERT_OFF/TRICE_INSERT_ON. CE does not modify Trice calls in ordinary C comments; existing ID processing of those examples remains in place. Files already bound through sidecar includes are not automatically converted to Insert. Use re-migration to trice insert for these files.

The CE path validates all selected files before publication and writes source, TIL, LI and the Insert field registry together, rolling back on write errors. -dry-run publishes nothing. With -ce, the experimental timestamp-only -cache is bypassed to prevent changed rules from mixing with old source copies. trice-fields.txt still describes the last successful Insert/Bind run; Clean creates no new field registry.

33.4. Expressions, Fields and Evaluation

Every CE expression must be comma-free, valid and visible at every selected log site. Suitable examples include pos.x, motor->speed, array[i], x + 1, aFloat(velocity) and condition ? a : b. getValue(a, b) and the comma operator are not allowed in the CLI list; calculate such results in a local variable beforehand. Each option value occupies one complete line; // comments are not allowed in expressions.

{} derives a field name from a simple expression: pos.x becomes pos.x, and motor->speed becomes motor.speed. More complex expressions require a name, for example ctx:", next={next}", x + 1. Classical printf placeholders and named fields can be mixed. Literal braces are written as ``. Duplicate field names in the final record are an error, including when one occurs in the source and another in a CE rule.

Original arguments precede additional CE arguments. Each additional expression is evaluated exactly once per actually executed call. A call that is not executed, or a build with TRICE_OFF or TRICE_CLEAN, does not evaluate it. CE adds no ordering guarantee between different C expressions; dependent side effects belong in separate statements before the call.

Scalar Trices still transmit at most twelve values of the same bit width. CE preserves bit width and stamp type and adjusts fixed arity, for example from TRice32_1 to TRice32_3. Floating-point values explicitly require aFloat(...) at 32 bits and aDouble(...) at 64 bits; there is no automatic conversion. 8-/16-bit Trices cannot transmit floating-point values. The compiler checks actual C types and identifier visibility.

String, buffer and other special Trice families receive no additional runtime arguments through CE. A text-only extension without additional values is possible if the final format remains valid for the original family, for example label:" online" on a triceS. Named buffer fields remain excluded, as in Structured Logging.

33.5. Using Global and Local Values

Global state values and functions available everywhere are often especially convenient:

trice insert -ce 'ctx7:", clock={clock}", readClock()'

Every selected log site must be able to call readClock(); its declaration must be known there. The value is read when the log call actually executes, not when the Trice tool runs. Without an executed log call, CE does not call the function either.

Local variables are also useful when all selected sites can use the same expression. For example, the rule -ce 'job:", job={job}", jobId' fits both functions:

void startJob(int jobId) {
    trice("info:job:start\n");
}

void finishJob(int jobId) {
    trice("info:job:finish\n");
}

The shared rule reads the local parameter of whichever function executes. There is no global jobId storage or mixing of different calls.

The following variant cannot use that same rule:

void startJob(int jobId) {
    trice("info:job:start\n");
}

void finishJob(int finishedJobId) {
    trice("info:job:finish\n");
}

jobId does not exist in finishJob. The compiler reports the missing name automatically; no additional CLI option is needed. Solutions include a consistent parameter name, a local helper value, separate selectors with appropriate rules, or a directly specified field: trice("info:finish, job={job}\n", finishedJobId);.

An ordinary helper function cannot access its caller’s local variables either. This also applies to static inline: inlining adds no visibility. Values must be passed as parameters:

static inline void logJob(int jobId) {
    trice("info:job:progress\n");
}

void worker(void) {
    int currentJob = 17;
    logJob(currentJob);
}

This limitation remains because CE produces ordinary C/C++ code. The Trice tool knows neither all types and declarations nor the preprocessor branches selected by the actual compiler. It checks syntax, schema and supported log forms itself; the compiler, already required for the build, checks exact visibility. A separate full compiler preprocessing pass solely to report errors earlier would unnecessarily complicate usage and builds.

33.6. Build, IDs and Generated Files

CE is applied before schema and ID determination. The ID follows the final Trice type and canonical template, including field names. A different expression with the same schema does not change the ID: ctx:", x={position}", pos.x may change to ctx:", x={position}", pos.y. Changes to the field name, format or final type follow the existing ID assignment rules instead. Historical TIL entries remain available for older firmware.

After every source or CE configuration change, rerun bind with the complete desired rule list and rebuild the firmware. Without -ce, the next Bind run produces normal schemas without CE again. Rules are not carried forward from earlier runs. The dictionary and generated firmware must belong together; changing only til.json cannot create additional target values.

Normal Bind setup steps, such as the initial sidecar include, still apply. CE itself writes neither the extension nor additional arguments into user log sites. Repeated runs with the same configuration preserve source, IDs and generated contents. trice-fields.txt counts final CE fields together with directly specified fields for the current run. -dry-run publishes no changes. Invalid rules, field conflicts and selected unsupported Bind sites are rejected before writes; publication failures use the existing Bind rollback.

generate -logC uses the final CE schemas from TIL. Bind sidecars provide the mapping; Insert uses explicit IDs in the source. Rules need not be supplied again:

trice generate -til til.json -genDir generated -logC triceLog.c

With Bind, stale or contradictory CE metadata causes an error; after changing a Trice call, first bind again with the desired rules. With Insert, the source may contain custom lowercase selectors in addition to the TIL template. Message, field schema and Trice type must match the explicit ID’s entry. For changed messages, rerun insert first; to replace a CE extension, use the workflow above: clean -ce, then insert -ce. The same source scope and TIL must be accessible; Bind also requires its sidecars in the corresponding build directory.

33.7. Supported Log Sites and Alternatives

bind -ce supports direct Trice calls uniquely addressable by file and source line, including within ordinary and static inline functions. This path requires no __COUNTER__. Multiline calls are also possible if no other Bind log site occupies their lines.

Selected wrapper macros and counter-rebase sites remain deferred. A typical error is:

main.c:42: error: CE requires a direct, line-addressable bind site. Search UM for "bind-limits".

The bind-limits section explains the cause and possible code adjustments without requiring compiler expertise. Suitable changes include separate source lines or ordinary functions with explicitly passed local values. Wrapper/rebase sites not selected by CE retain existing Bind behavior, including its compiler requirements.

insert -ce writes final arguments directly into each recognized log site. This avoids Bind selection through source lines or compiler counters. In particular, these calls can be extended with -ce 'ctx7:", clock={}", clock':

trice("msg:ctx7:first\n"); trice("msg:ctx7:second\n");

#define LOG_STATUS() trice("msg:ctx7:status\n")

For a wrapper, Insert extends the Trice call in the macro definition. Every later call to LOG_STATUS() uses this extension. clock must be visible at every expansion. This does not configure different rules per call site for the same wrapper; selectors belong to the recognized format string in its definition.

Parameters of such a wrapper can also be used:

#define LOG_JOB(jobId) trice("info:job:progress\n")

void worker(void) {
    LOG_JOB(17);
}

With insert -ce 'job:", job={job}", jobId', jobId is inserted directly into the definition and replaced with 17 during macro expansion. Normal macro rules remain unchanged; in particular, avoid arguments with mutually dependent side effects.

This requires a call with a known format string that the Trice parser can recognize. A definition such as #define LOG_ANY(format) trice(format) provides neither a static selector nor a complete schema. CE is not a general C preprocessor and does not promise support for arbitrary macro-assembled formats. An explicit Trice call or a function with a fixed format and passed values remains a simple alternative.

Log form bind -ce insert/clean -ce
Direct, uniquely addressable call, including in an inline function Supported Supported
Multiple direct calls on the same line Rejected when selected by CE Recognizable calls are extended individually
Wrapper definition with a static Trice format Still rejected when selected by CE The recognized definition is extended and restored
Local name missing at the actual expansion Compiler error Compiler error
String/buffer record with additional scalar CE arguments Error before publication Error before publication

Why the Bind limitation remains despite a successful PoC: Existing counter rebasing placed expressions from different log sites into multiple C branches. The compiler checks even branches that are not executed. A local variable valid only on the left can therefore cause an artificial error on the right. The extended PoC selects the adapter during macro expansion and avoids this error for the tested cases. It requires an additional preprocessing pass with the actual compiler per translation unit and build configuration, plus matching generated mapping files.

This build effort has not been introduced as a production workflow. Also, __COUNTER__ is not available in every compiler and is neither a runtime counter nor a cycle counter. Even globally visible CE values therefore do not separately enable complex Bind sites. The uniform limitation is easy to explain and reject during Bind. insert/clean -ce requires neither this mapping nor this preliminary pass. Exact evidence, costs and open questions appear in the PoC appendix within this chapter.

33.8. Test Coverage

The rule and Bind tests check selectors, aliases, order, invalid rules, limits, stable IDs, configuration changes, the field registry and unchanged files after rejection or write errors. The Insert/Clean tests additionally cover complete and partial matches, format and argument suffix positions, multi-part rule groups, newlines, repetition, handwritten fields, comments, moved calls, exclusions and rollback after write errors. The CLI and target tests exercise both public workflows through generate -logC and actual target records to text/JSON/KV output.

Evidence for the production implementation covers Clang in C11 and C++17, clangd with a real compile configuration, 8/16/32/64-bit values, different stamp types and builds without __COUNTER__. It checks separate local scopes, once-only evaluation, TRICE_OFF, TRICE_CLEAN and understandable compiler/editor errors for missing identifiers. Insert additionally tests two log sites in separate local blocks on the same wrapper line and their complete removal. The editor check excludes only clangd’s SwapBinaryOperands refactoring action: Clangd 21 proposes overlapping edits within an explicit Id(...) for this action. Compiler diagnostics and missing-identifier errors remain checked. Other compilers are assessed separately in the PoC appendix.

Repeat the focused acceptance tests from the repository root:

TRICE_BIND_INTEGRATION=1 go test ./internal/id ./internal/args -run '^(TestBindContext|TestContextEnrichment|TestInsertCleanContext|TestSourceContext|TestContextInsertClean)' -count=1

33.9. Appendix: CE Feasibility Proofs

As of 27 September 2026, the isolated proof for direct Bind log sites passes. It is in context_enrichment_poc_test.go. The production option trice bind -ce has since been implemented on this foundation; usage and acceptance are described in this chapter. This appendix also contains the original rebase counterexample and the extended PoC for wrapper macros and counter rebasing. The latter informs a future decision and enables no additional production CE support.

33.9.1. Mechanism Under Test

The existing Bind descriptor contains both the ID and a macro to apply. At selected log sites, the PoC uses a generated adapter macro that accepts the original arguments and appends the context expressions. These expressions are evaluated only during expansion of the original Trice call, where they can access local variables.

For an originally argument-free log site, the adapter is equivalent to:

#define TRICE_CE_POC_SITE(ignoredImplementation, tid, format) \
    TRICE_INSERT_trice(tid, format, (x))

For calls with existing arguments, the adapter macro receives appropriate additional parameters. Each appears exactly once in the final call. Generic Trice macros determine arity from the extended argument list. For fixed-arity macros, the adapter selects the appropriate implementation, for example TRICE_INSERT_trice_1 for an extended trice_0 site.

The test first creates the extended template string and additional arguments solely in a private in-memory source view. Existing SubCmdIdBind runs on that view with the normal Structured Logging parser and ID assignment. The ID is therefore determined from the final schema. The compiler still sees unchanged user source and the generated sidecar with adapter macros. This is a test adapter for proving the architecture, not the production CE integration.

The fixture already contains the regular Bind sidecar include and a fixed file key. The test therefore checks the source preservation required by CE independently of initial Bind project setup.

33.9.2. Verified Behavior

Case Expected result Evidence
Argument-free trice Local x is transmitted as an additional value Binary record contains 7
Already parameterized trice Original value precedes CE value Binary record contains 1, 1
Simple expression x + 1 is evaluated at the call site Binary record contains 8
Fixed arity trice_0 and trice_1 receive the appropriate final arity Binary records contain 7 and 22, 7 respectively
Inner block scope Uses blockValue, visible only there Binary record contains 11
Unselected log site No additional values Binary record remains argument-free
Side effects Original and CE expressions are each evaluated exactly once Two separate runtime counters equal 1
Unexecuted call No record or CE side effect Seven records despite eight instrumented sites; CE counter remains 1
TIL consistency Final templates, arity, IDs and payload agree Explicit template expectations and the actual Trice record parser/resolver
Repetition Identical IDs and generated contents Byte comparison of source, configuration, TIL, LI, sidecar and field registry after two PoC Bind runs
Invalid context An invisible identifier is diagnosed Compiler and clangd reject ceMissingLocal

The runtime test uses the actual target macros and Trice library. Auxiliary output delivers the generated binary records to TriceParseRecord; TriceResolveLog checks them against a C metadata table generated from the final TIL. This also detects incorrect payload lengths or parameter counts. The PoC generates that C table directly from TIL; it does not test the public generate -logC workflow.

33.9.3. Compiler and Editor Diagnostics

The test compiles and runs the same fixture as C11 and C++17 with -Wall -Wextra -Werror. Library sources are compiled as C. For both language modes, it creates a compile_commands.json with the actual compiler arguments. clangd --check must load this database and complete without errors. The negative test additionally demonstrates that missing context identifiers remain visibly diagnosed.

Tested environment: macOS on ARM64, Apple Clang/Clang++ 21.0.0 and Apple clangd 21.0.0. Both language modes and negative diagnostic checks passed. This demonstrates the language-server path for clangd-based editors with the generated include directory and real compile configuration. Other language servers, IDE-specific parsers, GCC and MSVC were not tested in this direct-site proof.

33.9.4. Reproducing the Direct-Site Proof

Run from the repository root:

TRICE_BIND_INTEGRATION=1 go test ./internal/id -run '^TestContextEnrichmentPoC$' -count=1 -v

A GCC-/Clang-compatible C and C++ compiler and clangd must be in PATH. The focused test explicitly requires these tools. Without TRICE_BIND_INTEGRATION=1, it is skipped. All fixtures and build artifacts are created in a temporary test directory and removed afterwards. Existing repository workflows are not modified.

33.9.5. Relationship to Production Support

The proof covers the minimum cases from the CE chapter. It checks direct scalar 32-bit log sites with one site per physical line and the iD stamp type. PoC rules are fixed test data; the direct-site proof contains no CLI parser, complete selector/alias policy or production error validation.

The production implementation applies the transformation before schema/ID assignment and generates the sidecar extension persistently. Its first stage remains limited to direct sites uniquely addressable by source line. Additional Bind behavior tests and CLI/target tests check broader acceptance separately from the direct-site PoC: 8/16/32/64 bits, different stamp types, TRICE_OFF/TRICE_CLEAN, rule conflicts, configuration changes, multiline calls, inline functions and real compiler/editor runs without __COUNTER__. Four examples come from triceCheck.c; fourteen records in total pass through the public generate -logC resolver and Go decoder for text, JSON and KV. CE for wrapper macros and counter rebasing remains a separate follow-up task.

For supported source constructs and practical alternatives, see Bind Limits. The feasibility tests below do not enable CE for Bind wrappers or counter-rebase sites.

33.9.6. Counterexample for the Original Rebase Approach

On 27 September 2026, direct transfer of the adapter approach to counter rebasing was tested. The additional TestContextEnrichmentPoCRebaseScopeBoundary test shows a limitation: two log sites supported by Bind, on the same source line, occupy separate blocks and each use a variable visible only in that block. The normal Bind build passes. Inserting both CE expressions into their respective branches of the generated rebase dispatcher makes compilation fail on names belonging to the other scope.

The reason is ordinal selection in C: even an if branch not selected at runtime is checked by the compiler for valid identifiers. The affected expressions are valid at their intended log sites. The error would therefore be an invalid additional scope requirement imposed by instrumentation. The test expects and demonstrates precisely this failed extension; it is not a successful CE rebase acceptance test.

The direct-site proof remains valid. Simply appending CE arguments to rebase branches is insufficient for general CE support, however. On 27 September, the first implementation stage was therefore limited to direct Bind sites uniquely addressable by source line. Production CE rejects selected wrapper/rebase sites before modifying files and points to the explanation in the Reference Manual with Search UM for "bind-limits". Without a matching CE rule, existing Bind capabilities remain available. The additional architecture proof for complex CE sites was deferred; this counterexample alone does not establish that a later solution is impossible.

The counterexample can be reproduced separately:

TRICE_BIND_INTEGRATION=1 go test ./internal/id -run '^TestContextEnrichmentPoCRebaseScopeBoundary$' -count=1 -v

33.9.7. Extended PoC for Wrapper Macros and Counter Rebasing

Result: Selecting the CE adapter in the preprocessor eliminates the demonstrated problem with local variables from other scopes. The new context_enrichment_rebase_poc_test.go test demonstrates a working approach with an additional compiler preprocessing pass. It also contains a simpler special case: a wrapper with exactly one log site can be enriched without this pass and without __COUNTER__ if its line mapping is unambiguous. Both remain test code; bind -ce still rejects the previously excluded constructs.

How the Investigated Approach Works

With the existing C branching, expressions from all possible log sites reach the compiler. In the new PoC, macro expansion selects exactly one adapter. Only that adapter’s expressions appear in the final C/C++ code. A wrapper can therefore use branchLeft in its left branch and branchRight in its right branch without either name needing to exist in the other block.

The mapping requires a value that the preprocessor can use directly as part of a macro name. The existing relative calculation from __COUNTER__ and a C enum constant is unsuitable. The PoC therefore determines actual absolute counter values with the compiler being used:

  1. Existing Bind sets up the temporary test sources normally. A private source view receives CE extensions and goes through existing schema/ID assignment. The user source actually compiled retains its original Trice calls and wrapper definitions.
  2. The compiler preprocesses these sources with the actual language, target and preprocessor options. A file included only for the test emits a marker with the region and counter value for each rebase expansion.
  3. The test maps these markers to numeric definition/location descriptors and expansion order from the generated Bind sidecar. It does not guess IDs from format strings. This produces a header with exactly one macro per observed expansion.
  4. During normal compilation, __COUNTER__ selects this macro. It passes existing arguments and adds only the associated CE expressions. Existing rebase end checks remain active. An additional base-value check rejects a shifted mapping even if it would happen to hit another valid entry.

This preliminary pass is required per translation unit and concrete build configuration. Here, a translation unit is, for example, a .c or .cpp file together with its included headers. The same shared wrapper may therefore need different counter mappings for different translation units while retaining the same logical IDs.

Verified Cases
Case Verified result
Simple wrapper with one log site CE at the call site works even with __COUNTER__ removed; an ordinary line descriptor suffices.
Wrapper with two log sites Original values precede their CE values; generic and fixed arity work.
Repeated wrapper calls The same logical IDs transmit different local context values from their callers.
Wrapper with separate branches branchLeft and branchRight remain confined to their own blocks.
Two direct calls on the same line Separate blocks with onlyLeft and onlyRight work without imposing visibility requirements from other scopes.
Unselected rebase sites Existing records remain unchanged and have no additional values.
Side effects Original and CE expressions are each evaluated exactly once per executed call. An unexecuted wrapper call has no effect.
Actual records Eleven records are produced by the real target library, resolved against the final C TIL, and checked for IDs, parameter counts and all ordered values.
Repetition Repeated private Bind generation preserves schema, IDs, region mapping and source. The same compiler pass reproduces the same mapping header.
Other counter uses before log sites Additional uses at offsets 0, 1 and 7 change the mapping while IDs and expected records remain the same.
Stale mapping header A normal build fails. In particular, a shift by one is detected even when it could otherwise hit an adjacent valid adapter.
Missing context identifier The compiler and tested clangd variants report cePocMissingLocal as a real error.
Additional counter consumption in a CE expression Rejected; the investigated preliminary pass assumes one counter per rebase expansion.
TRICE_OFF and TRICE_CLEAN No records and no argument/CE evaluation, even without available __COUNTER__.
Active rebase without __COUNTER__ Clear build error including Search UM for "bind-limits". The PoC claims no solution for this case.

The runtime proof uses scalar 32-bit records with iD. It does not replace the broader bit-width/stamp acceptance of production CE and is not complete acceptance of every conceivable wrapper.

Compiler Matrix and Evidence Limits

On 27 September 2026, the following installed tool variants were tested:

Tool and target Language modes Evidence
Apple Clang/Clang++ 21.0.0, macOS ARM64 C99, C11, C17, C++11, C++17 Preprocessing, compilation with -Wall -Wextra -Werror, linking and actual program execution passed.
ARM GNU Toolchain 13.3.Rel1, GCC/G++ 13.3.1, Cortex-M0/Thumb C99, C11, C17, C++11, C++17 Preprocessing and generation of real ARM object files with -Wall -Wextra -Werror passed; no execution on an MCU or emulator.
Same ARM GCC version, Cortex-M4/Thumb C99, C11, C17, C++11, C++17 Preprocessing and generation of real ARM object files passed; no target runtime claim.
All three configurations C++20 Existing Bind code fails even without experimental CE on an enum warning promoted by -Werror. With only that warning downgraded to warning status, the CE PoC passes; Clang includes runtime execution, ARM GCC produces object files.
Apple clangd 21.0.0 Host C11 and host C++17 Loads a real compile_commands.json including the experimental header. Valid sources are error-free; the deliberately missing identifier is diagnosed.

This covers 18 combinations of toolchain/target and language mode, each with three initial counter states and additional negative and disabled-build cases. In this environment, the macOS gcc/g++ commands are Clang aliases and explicitly do not count as GCC evidence. Independent GCC evidence comes from the ARM cross-compiler. Native GCC variants are also included in the test matrix when available. MSVC, IAR, Arm Compiler/armclang and other language servers were not tested; their support cannot be inferred from these results.

The C++20 limitation comes from the existing rebase check: it subtracts values of different anonymous enum types. The test first demonstrates this with ordinary Bind, then uses only -Wno-error=deprecated-anon-enum-enum-conversion for Clang or -Wno-error=deprecated-enum-enum-conversion for GCC. This is explicitly not a successful strict C++20 build. Product code was not changed for the PoC. When simulating an unavailable __COUNTER__, the warning about undefining a built-in macro is also not treated as an error.

Implications for a Possible Implementation

The scope obstacle is solved for the tested cases; the cost of this approach is an additional compiler-dependent build step. A production decision must explicitly include this effort. The PoC provides neither such a workflow nor a new CLI option.

Before implementation, the following points in particular would need decisions or evidence:

A second compiler pass is therefore a demonstrated possibility, not a claim that no simpler approach could exist. The initial direct-site CE stage remains unchanged. This PoC does not investigate source transformation. The now available insert/clean -ce is implemented separately and described above with its own tests.

Repeating the Extended PoC

Run from the repository root:

TRICE_BIND_INTEGRATION=1 go test ./internal/id -run '^TestContextEnrichmentRebasePoC$' -count=1 -v

The test detects installed Clang/GCC C/C++ toolchains and ARM GCC itself, reports missing tools and compiler aliases, and installs nothing. At least one suitable C/C++ toolchain is required. Without TRICE_BIND_INTEGRATION=1, the compiler test is skipped. clangd is checked when available; a missing tool is explicitly reported. All source copies, experimental headers and build artifacts are created in temporary test directories. The production CLI, target headers and build scripts remain unchanged.

33.10. Approach and Boundaries

CE is optional build-time instrumentation: rules select log sites whose records contain additional runtime values. It introduces no general implicit context state. There are therefore no push/pop calls, task-local state or context handles requiring special management during task switches or interrupts.

Other logging systems offer related but differently structured concepts, such as Go slog.Logger.With, Microsoft ILogger.BeginScope, Serilog LogContext and Rust tracing spans. These references imply no Trice dependencies or compatibility guarantees.

34. Trice without UART

A very performant output path is RTT, if your MCU supports background memory access like the ARM-M ones.

Because the Trice tool needs only to receive, a single target UART-TX pin will do. But it is also possible to use a GPIO-Pin for Trice messages without occupying a UART resource.

(back to top)

35. Trice over RTT

Allows Trice over the debug probe without using a pin or UART.

(back to top)

35.1. For the impatient (2 possibilities)

The default SEGGER tools only suport RTT channel 0.

<h5>Setup TCP4 server providing the trace data</h5>

This is just the SEGGER J-Link server here for demonstration, but if your target device has an TCP4 interface, you can replace this with your target server.

ms@DESKTOP-7POEGPB MINGW64 ~/repos/trice (main)
$ jlink
SEGGER J-Link Commander V7.92g (Compiled Sep 27 2023 15:36:46)
DLL version V7.92g, compiled Sep 27 2023 15:35:10

Connecting to J-Link via USB...O.K.
Firmware: J-Link STLink V21 compiled Aug 12 2019 10:29:20
Hardware version: V1.00
J-Link uptime (since boot): N/A (Not supported by this model)
S/N: 770806762
VTref=3.300V


Type "connect" to establish a target connection, '?' for help
J-Link>connect
Please specify device / core. <Default>: STM32G0B1RE
Type '?' for selection dialog
Device>
Please specify target interface:
  J) JTAG (Default)
  S) SWD
  T) cJTAG
TIF>s
Specify target interface speed [kHz]. <Default>: 4000 kHz
Speed>
Device "STM32G0B1RE" selected.


Connecting to target via SWD
InitTarget() start
SWD selected. Executing JTAG -> SWD switching sequence.
DAP initialized successfully.
InitTarget() end - Took 36.3ms
Found SW-DP with ID 0x0BC11477
DPv0 detected
CoreSight SoC-400 or earlier
Scanning AP map to find all available APs
AP[1]: Stopped AP scan as end of AP map has been reached
AP[0]: AHB-AP (IDR: 0x04770031)
Iterating through AP map to find AHB-AP to use
AP[0]: Core found
AP[0]: AHB-AP ROM base: 0xF0000000
CPUID register: 0x410CC601. Implementer code: 0x41 (ARM)
Found Cortex-M0 r0p1, Little endian.
FPUnit: 4 code (BP) slots and 0 literal slots
CoreSight components:
ROMTbl[0] @ F0000000
[0][0]: E00FF000 CID B105100D PID 000BB4C0 ROM Table
ROMTbl[1] @ E00FF000
[1][0]: E000E000 CID B105E00D PID 000BB008 SCS
[1][1]: E0001000 CID B105E00D PID 000BB00A DWT
[1][2]: E0002000 CID B105E00D PID 000BB00B FPB
Memory zones:
  Zone: "Default" Description: Default access mode
Cortex-M0 identified.
J-Link>

Now the TCP4 server is running and you can start the Trice tool as TCP4 client, which connects to the TCP4 server to receive the binary log data:

$ trice l -p TCP4 -args="127.0.0.1:19021" -til ../examples/G0B1_inst/til.json -li ../examples/G0B1_inst/li.json -d16 -pf none

In this G0B1_inst example we use the additional -d16 and -pf none switches to decode the RTT data correctly.

This is a demonstration and test for the -port TCP4 usage possibility. Using RTT with J-Link is more easy possible as shown in the next point.

35.1.2. Start using JLinkRTTLogger

35.1.3. JLinkRTTLogger Issue

mkdir -p ./temp
rm -f ./temp/trice.bin
touch ./temp/trice.bin
tmux new -s "tricerttlog" -d "JLinkRTTLogger -Device STM32G0B1RE -If SWD -Speed 4000 -RTTChannel 0 ./temp/trice.bin"
trice log -p FILE -args ./temp/trice.bin -pf none -prefix off -hs off -d16 -ts16 "time:offs:%4d µs" -showID "deb:%5d" -i ../../demoTIL.json -li ../../demoLI.json -stat
tmux kill-session -t "tricerttlog"

(back to top)

35.2. Segger Real Time Transfer (RTT)

        -args string
        Use to pass port specific parameters. The "default" value depends on the used port:
        port "COMn": default="", use "TARM" for a different driver. (For baud rate settings see -baud.)
        port "J-LINK": default="-Device STM32F030R8 -if SWD -Speed 4000 -RTTChannel 0 -RTTSearchRanges 0x20000000_0x1000",
                The -RTTSearchRanges "..." need to be written without extra "" and with _ instead of space.
                For args options see JLinkRTTLogger in SEGGER UM08001_JLink.pdf.
        port "ST-LINK": default="-Device STM32F030R8 -if SWD -Speed 4000 -RTTChannel 0 -RTTSearchRanges 0x20000000_0x1000",
                The -RTTSearchRanges "..." need to be written without extra "" and with _ instead of space.
                For args options see JLinkRTTLogger in SEGGER UM08001_JLink.pdf.
        port "BUFFER": default="0 0 0 0", Option for args is any byte sequence.
         (default "default")

(back to top)

<h5>First step (to do if some issues occur - otherwise you can skip it)</h5>

Video

See also https://github.com/stlink-org/stlink

<h5>Second step</h5>

35.3.2. Some SEGGER tools in short

<h5>JLink.exe</h5>

<h5>JLinkRTTLogger.exe</h5>

35.3.3. JLinkRTTClient.exe

35.3.4. JLinkRTTViewer.exe

(back to top)

35.4. Segger RTT

(back to top)

(back to top)

35.6. Additional Notes (leftovers)

(back to top)

35.7. Further development

libusb-1.0.23\examples\bin64> .\listdevs.exe
2109:2811 (bus 2, device 8) path: 6
1022:145f (bus 1, device 0)
1022:43d5 (bus 2, device 0)
0a12:0001 (bus 2, device 1) path: 13
1366:0105 (bus 2, device 10) path: 5
libusb-1.0.23\examples\bin64> .\listdevs.exe
2109:2811 (bus 2, device 8) path: 6
1022:145f (bus 1, device 0)
1022:43d5 (bus 2, device 0)
0a12:0001 (bus 2, device 1) path: 13

(back to top)

35.8. NUCLEO-F030R8 example

Info: https://www.st.com/en/evaluation-tools/nucleo-F030r8.html

./ref/STLinkReflash.PNG

./ref/J-LinkRTT.PNG

(back to top)

35.9. Possible issues

(back to top)

35.10. OpenOCD with Darwin (macOS)

Terminal 1:

brew install open-ocd
...
cd ./trice/examples/G0B1_inst
openocd -f openocd.cfg
Open On-Chip Debugger 0.12.0
Licensed under GNU GPL v2
For bug reports, read
    http://openocd.org/doc/doxygen/bugs.html
srst_only separate srst_nogate srst_open_drain connect_deassert_srst

Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : J-Link STLink V21 compiled Aug 12 2019 10:29:20
Info : Hardware version: 1.00
Info : VTarget = 3.300 V
Info : clock speed 2000 kHz
Info : SWD DPIDR 0x0bc11477
Info : [stm32g0x.cpu] Cortex-M0+ r0p1 processor detected
Info : [stm32g0x.cpu] target has 4 breakpoints, 2 watchpoints
Info : starting gdb server for stm32g0x.cpu on 3333
Info : Listening on port 3333 for gdb connections
Info : rtt: Searching for control block 'SEGGER RTT'
Info : rtt: Control block found at 0x20001238
Info : Listening on port 9090 for rtt connections
Channels: up=1, down=0
Up-channels:
0: Terminal 1024 0
Down-channels:

Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections

Terminal 2:

ms@MacBook-Pro G0B1_inst % trice l -p TCP4 -args localhost:9090  -pf none -d16
Nov 14 17:32:33.319451  TCP4:       triceExamples.c    10        0_000  Hello! 👋🙂
Nov 14 17:32:33.319463  TCP4:
Nov 14 17:32:33.319463  TCP4:         ✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨
Nov 14 17:32:33.319463  TCP4:         🎈🎈🎈🎈  NUCLEO-G0B1RE   🎈🎈🎈🎈
Nov 14 17:32:33.319463  TCP4:         🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃
Nov 14 17:32:33.319463  TCP4:
Nov 14 17:32:33.319463  TCP4:
Nov 14 17:32:33.406455  TCP4:       triceExamples.c    16        0_037 2.71828182845904523536 <- float number as string
Nov 14 17:32:33.505116  TCP4:       triceExamples.c    17        0_087 2.71828182845904509080 (double with more ciphers than precision)
Nov 14 17:32:33.607518  TCP4:       triceExamples.c    18        0_117 2.71828174591064453125 (float  with more ciphers than precision)
Nov 14 17:32:33.707851  TCP4:       triceExamples.c    19        0_146 2.718282 (default rounded float)
Nov 14 17:32:33.807685  TCP4:       triceExamples.c    20        0_175 A Buffer:
Nov 14 17:32:33.908202  TCP4:       triceExamples.c    21        0_204 32 2e 37 31 38 32 38 31 38 32 38 34 35 39 30 34 35 32 33 35 33 36
Nov 14 17:32:34.007148  TCP4:       triceExamples.c    22        0_254 31372e32  31383238  34383238  34303935  35333235
Nov 14 17:32:35.007949  TCP4:       triceExamples.c    23        0_301 ARemoteFunctionName(2e32)(3137)(3238)(3138)(3238)(3438)(3935)(3430)(3235)(3533)(3633)
Nov 14 17:32:35.112304  TCP4:       triceExamples.c    24              100 times a 16 byte long Trice messages, which not all will be written because of the TRICE_PROTECT:
Nov 14 17:32:35.307567  TCP4:       triceExamples.c    26        0_379 i=44444400 aaaaaa00
Nov 14 17:32:35.408257  TCP4:       triceExamples.c    27    0,000_002 i=44444400 aaaaaa00
Nov 14 17:32:35.509022  TCP4:       triceExamples.c    26        0_441 i=44444401 aaaaaa01
Nov 14 17:32:35.609439  TCP4:       triceExamples.c    27    0,000_002 i=44444401 aaaaaa01
Nov 14 17:32:35.710201  TCP4:       triceExamples.c    26        0_504 i=44444402 aaaaaa02
...

TODO: Working example with SEGGER_RTT J-Link and Open OCD

(back to top)

36. Writing the Trice logs into an SD-card (or a user specific output)

Related issues/discussions: #253 #405 #425 #447 #537

(back to top)

37. Trice Target Code Implementation

37.1. TRICE Macro structure

37.1.1. TRICE_ENTER

37.1.2. TRICE_PUT

37.1.3. TRICE_LEAVE

37.2. TRICE_STACK_BUFFER

37.3. TRICE_STATIC_BUFFER

37.4. TRICE_DOUBLE_BUFFER

37.5. TRICE_RING_BUFFER

The TRICE_RING_BUFFER allocates incremental ring buffer space and each trice location is read by a deferred task.

37.6. Deferred Out

37.6.1. Double Buffer

37.6.2. Ring Buffer

37.6.3. Local Deferred Text Log

Local deferred logging keeps every time-critical Trice producer binary and short, but turns complete records into plain text later on the target. A common use is a low-priority FreeRTOS logging task which writes to UART, USB, a file, or an existing application console without a host-side trice log process.

tasks and interrupts
        |
        | trice(...)
        v
ring or double buffer containing ordinary binary Trice records
        |
        | one background consumer
        v
TriceLog(applicationBuffer, applicationBufferSize)
        |
        v
application UART, USB, file, or console writer

TriceLog() and TriceTransfer() are alternative consumers of the same deferred buffer. Never call both for one buffer. The producer-side wire format, the normal host decoder, til.json, and existing trice insert/trice bind workflows are unchanged.

Basic configuration

Applications set local-log options in their ordinary triceConfig.h. The library defaults and the detailed description of every switch live in src/triceLogDefaultConfig.h; applications do not copy or edit that file.

#define TRICE_BUFFER TRICE_RING_BUFFER
#define TRICE_DEFERRED_OUTPUT 1
#define TRICE_DIRECT_OUTPUT 0
#define TRICE_LOCAL_LOG 1

TRICE_DOUBLE_BUFFER is supported as an alternative. Producer tasks and interrupts still only append compact records. Formatting begins when the background consumer calls TriceLog().

Local formatting needs an ID-to-format table in the target build. First make source IDs authoritative with either trice bind or legacy trice insert, then generate a table containing only the currently selected sources:

trice bind -src <source> -til til.json
# Alternatively: trice insert -src <source> -til til.json

trice generate -src <source> -til til.json -logC

Repeat -src for additional files or directories. Bind sidecars are read from ./generated by default; specify -genDir for a different sidecar directory. Bare -logC writes ./generated/til.c; -logC=build/til.c chooses an explicit location. -logC and -abc are separate generator modes and cannot be used together.

The generated C file includes compile-time guards derived from its Trice type, format string, and payload width. It therefore uses the same local-log switches as the formatter. A disabled capability removes the affected format strings from the compiled target image; regenerating til.c for every option change is not required. Regeneration is required after source IDs, Trice types, or format strings change.

Configuration switches

The switches are deliberately independent of any printf implementation. Their names are similar to familiar nanoprintf choices where useful, but Trice neither includes nanoprintf nor defines NANOPRINTF_* macros.

Switch Default Effect when set to 1
TRICE_LOCAL_LOG 0 Includes the local consumer and formatter.
TRICE_LOCAL_LOG_USE_PRINTF_HOOK 1 Declares and uses the runtime UserTriceLogPrintfFn hook.
TRICE_LOCAL_LOG_USE_MINIMAL_FORMATTER 1 Includes the separate exact %d/%x fallback.
TRICE_LOCAL_LOG_USE_FIELD_WIDTH_FORMAT_SPECIFIERS 1 Accepts fixed widths such as %8d, %02x, and %-12s.
TRICE_LOCAL_LOG_USE_PRECISION_FORMAT_SPECIFIERS 1 Accepts fixed precision such as %.3f and %.5s.
TRICE_LOCAL_LOG_USE_FLOAT_FORMAT_SPECIFIERS 1 Accepts %e, %E, %f, %F, %g, and %G through the hook.
TRICE_LOCAL_LOG_USE_64_BIT_VALUES 1 Includes 64-bit integer, buffer, and aDouble() paths.
TRICE_LOCAL_LOG_USE_BINARY_FORMAT_SPECIFIERS 0 Includes Trice’s internal %b conversion.
TRICE_LOCAL_LOG_USE_ALT_FORM_FLAG 1 Accepts #, including the internal %#b prefix.
TRICE_LOCAL_LOG_USE_EXTENDED_FORMAT_SPECIFIERS 0 Includes internal %O, %t, %p, and %q conversions.
TRICE_LOCAL_LOG_USE_DYNAMIC_STRING_TRICES 1 Includes bounded triceS and string-form triceN.
TRICE_LOCAL_LOG_USE_BUFFER_TRICES 0 Includes TRICE8_B through TRICE64_B.
TRICE_LOCAL_LOG_USE_PREFIX_HOOK 0 Declares and calls UserTriceLogPrefixFn before each message body.
TRICE_LOCAL_LOG_USE_ANSI_COLORS 0 Colors records which begin with a recognized Trice tag.
TRICE_LOCAL_LOG_STRIP_LOWER_CASE_TAGS 0 Removes a recognized all-lower-case tag and its first colon.
TRICE_LOCAL_LOG_KEEP_DISABLED_IDS 1 Keeps ID and shape metadata, but no string, for disabled generated entries.

Every switch is Boolean and invalid values fail during preprocessing. Defaults preserve the original local-log integer, string, width, precision, float, and 64-bit capabilities. The newer binary, extended, buffer, and prefix paths are opt-in so an existing target does not acquire avoidable code or data.

TRICE_LOCAL_LOG_KEEP_DISABLED_IDS == 1 lets TriceLog() distinguish a known ID whose formatter feature is off from an absent or obsolete ID. It returns TRICE_LOG_ERR_FEATURE_DISABLED for the former. Set it to 0 for the smallest table; such an ID then returns TRICE_LOG_ERR_ID because its row is absent.

Optional tag presentation

The two presentation switches are independent. With both set to 0, local logging returns exactly the formatted record body as before. Enabling only TRICE_LOCAL_LOG_STRIP_LOWER_CASE_TAGS changes msg:Hello to Hello, but keeps MSG:Hello, Message:Hello, and unknown project:Hello prefixes. The test is an exact match against a recognized built-in alias; arbitrary text before a colon is never removed.

Enabling only TRICE_LOCAL_LOG_USE_ANSI_COLORS retains the tag and surrounds the complete record body with its matching ANSI Select Graphic Rendition sequence. Enabling both removes a recognized lower-case tag and colors the remaining body. A reset is inserted before trailing CR and LF bytes in every record. Color state therefore never crosses a TriceLog() call, even when several tasks produce records or the consumer encounters an error. An optional prefix hook remains outside the colored body.

The target alias and palette table is in src/triceLogAnsi.c. It intentionally mirrors the established host hints in internal/emitter/lineTransformerANSI.go instead of generating a target dependency on Go. Reciprocal comments in those files identify the manual, optional synchronization point. User-defined host labels and host log-level filtering are not part of local presentation. Applications which compile an explicit source list must add triceLogAnsi.c when either presentation switch is enabled; source-wildcard builds already pick it up with the other Trice C files.

ANSI bytes are ordinary output bytes. A compatible serial terminal interprets them as colors, while a redirected file retains the escape sequences. Disable the color switch for consumers which require plain text. When both presentation switches are off, the separately compiled triceLogAnsi.c contributes neither code nor its alias and palette strings, even without link-time optimization.

Selecting a printf implementation

The optional hook has the snprintf contract:

typedef int (*TriceLogPrintfFn_t)(
    char *buffer,
    size_t size,
    const char *format,
    ...);

When hook support is enabled, install the function before consuming records:

#include <stdio.h>
#include "trice.h"

UserTriceLogPrintfFn = snprintf;

Possible implementations include a system or C-library snprintf, newlib, picolibc, nanoprintf, eyalroz/printf, and an snprintf-compatible adapter around stb_sprintf. The hook must return the number of bytes which would have been written without the final NUL, including when its destination is too small. A negative hook result becomes TRICE_LOG_ERR_PRINTF.

Trice calls the hook once per ordinary scalar conversion. It does not build an argument array. This keeps stack use independent of the number of values and lets the user formatter handle its normal %d, %u, %x, or float path. The following features are deliberately handled inside Trice and never delegated:

The small internal formatter lives in triceLogMinimal.c, separately from the record decoder. With TRICE_LOCAL_LOG_USE_MINIMAL_FORMATTER == 1, a null printf hook supports only exact %d and lowercase %x. Set the option to 0 when an external hook is always installed. The fallback then contributes no code even without LTO. Literal text, %%, and enabled dynamic strings need neither formatter implementation.

For nanoprintf, configure both layers consistently. For example, float output needs both TRICE_LOCAL_LOG_USE_FLOAT_FORMAT_SPECIFIERS == 1 and NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1. Trice’s field-width, precision, 64-bit, and alternative-form switches describe what records Trice retains and accepts; the corresponding nanoprintf switches describe what the selected hook can actually print. Trice’s internal %b does not require nanoprintf binary support.

Formatter families

With a printf hook, ordinary fixed scalar Trices support %d, %i, %u, %o, %x, %X, and %c. Constant flags, widths, and precisions are passed to the hook when their Trice switches are enabled. Source integer length modifiers are normalized to the long or long long argument supplied by Trice. Dynamic * width or precision remains unsupported because it would consume an additional argument which is not represented as a separate Trice value.

TRICE_LOCAL_LOG_USE_BINARY_FORMAT_SPECIFIERS adds %b. The formatter supports fixed width, precision, zero padding, left alignment, and the 0b prefix for %#b when the related switches are enabled.

TRICE_LOCAL_LOG_USE_EXTENDED_FORMAT_SPECIFIERS adds the established host meanings:

TRICE_LOCAL_LOG_USE_DYNAMIC_STRING_TRICES accepts exactly one %s, or one %q when extended formats are enabled, in a dynamic string record. The encoded payload length is always the read boundary. An embedded NUL in an explicitly sized triceN retains normal string semantics and ends visible text early. Fixed width, precision, and left alignment are supported when enabled. Wide strings and multiple dynamic conversions are rejected.

Buffer Trices use one scalar item conversion:

uint16_t samples[] = {1u, 0x2au, 0x1234u};
TRICE16_B("buffer:%04x \n", samples, 3u);

Text through the first colon is written once. The remainder, without its final newline, is repeated for each aligned payload element; one newline is appended after all elements. The configured item conversion and its payload width must also be enabled. The record stays at its ring- or double-buffer location while the elements are interpreted.

For floating point, retain the established source convention:

trice32("float:value=%.3f\n", aFloat(valueF));
trice64("float:value=%.9f\n", aDouble(valueD));

Trice reconstructs aFloat() from 32 payload bits and aDouble() from 64 bits, then passes a promoted double to the hook. %f, %F, %e, %E, %g, and %G are accepted. Float formats need hook support; aDouble() additionally needs 64-bit support. The selected hook controls decimal rendering quality and its Flash cost.

Dynamic function (F) and ABC records are not local text records and remain unsupported. Other deliberate exclusions are %n, dynamic *, wide %lc and %ls, byte-slice %x/% x formatting of dynamic string records, host log-level filtering, user-defined host labels, and host-side location presentation. These restrictions apply only to TriceLog() and its generated target table; they do not remove the corresponding records or decoder behavior from normal binary logging.

Optional prefix hook

An application can prepend presentation data without adding it to every format string:

#define TRICE_LOCAL_LOG_USE_PREFIX_HOOK 1

static int LocalPrefix(
    char *buffer,
    size_t size,
    uint16_t id,
    uint8_t stampBits,
    uint32_t stamp) {
    return snprintf(buffer, size, "[%u:%lu] ",
                    (unsigned)id, (unsigned long)stamp);
}

UserTriceLogPrefixFn = LocalPrefix;

The hook receives the parsed ID and raw stamp facts. It deliberately does not assume that every stamp is time. It follows the same snprintf size contract; a negative result becomes TRICE_LOG_ERR_PREFIX. A null hook produces no prefix.

Calling TriceLog

One background task usually drains all complete records:

char text[160];
int length;

while ((length = TriceLog(text, sizeof(text))) > 0) {
    ApplicationTextWrite(text, (size_t)length);
}

The API contract is:

Invalid API arguments do not consume a record. Success and record-local errors consume exactly one complete record so the next call can progress. Structural corruption or conflicting metadata clears the queue because the next record boundary is not trustworthy. Unknown IDs, disabled features, insufficient output space, and formatter-hook errors consume only their current record.

There is no dynamic allocation. Payload data is interpreted at its current deferred-buffer location and released only after formatting completes. The application owns the separate text buffer and may immediately pass its first length bytes to an existing writer.

Size-oriented configurations

A literal/string-oriented target without any printf code can use:

#define TRICE_LOCAL_LOG 1
#define TRICE_LOCAL_LOG_USE_PRINTF_HOOK 0
#define TRICE_LOCAL_LOG_USE_MINIMAL_FORMATTER 0
#define TRICE_LOCAL_LOG_USE_FIELD_WIDTH_FORMAT_SPECIFIERS 0
#define TRICE_LOCAL_LOG_USE_PRECISION_FORMAT_SPECIFIERS 0
#define TRICE_LOCAL_LOG_USE_FLOAT_FORMAT_SPECIFIERS 0
#define TRICE_LOCAL_LOG_USE_64_BIT_VALUES 0

An integer target can leave the exact %d/%x minimal formatter enabled and disable the hook. A feature-rich console can enable hook, width, precision, float, 64-bit, binary, extended, string, and buffer switches explicitly. The two approaches can use the same generated til.c; its preprocessor guards select only rows valid for the active triceConfig.h.

Examples

examples/PC_log is an immediately runnable host program using the system snprintf and standard output. After a short introductory sequence, it scans the complete shared _test/testdata/triceCheck.c producer corpus and fails with the exact selector and local-log error if an emitted record cannot be formatted.

examples/G0B1_log is an independent STM32G0B1 FreeRTOS project using nanoprintf in its background task and plain-text USART2 output. Its existing default and diagnostics tasks retain their CubeMX names, priorities, and stack sizes; the default task scans the same shared producer corpus while the diagnostics task drains it in the background. Both examples explicitly configure and exercise dynamic strings, float, double, Trice-specific conversions, and a Buffer Trice. Their target configurations disable command/RPC and selector-0 transport families, and triceCheck.c guards its two host-only dynamic-byte-string conversions with TRICE_LOCAL_LOG; ordinary host-decoder and legacy test configurations remain unchanged.

37.7. Direct Transfer

37.8. Possible Target Code Improvements

There have been 3 similar implementations for trice encode

static size_t triceDirectEncode(   uint8_t* enc, const uint8_t * buf, size_t len );
       size_t TriceDeferredEncode( uint8_t* enc, const uint8_t * buf, size_t len );

unsigned TriceEncryptAndCobsFraming32( uint32_t * const triceStart, unsigned wordCount ){

Now:

size_t TriceEncode( unsigned encrypt, unsigned framing, uint8_t* dst, const uint8_t * buf, size_t len ){
unsigned TriceEncryptAndCobsFraming32( uint32_t * const triceStart, unsigned wordCount ){

Currently there are 3 similar implementations for trice buffer reads

static size_t triceIDAndLen(    uint32_t* pBuf,               uint8_t** ppStart, int*      triceID );
static int    TriceNext(        uint8_t** buf,                size_t* pSize,     uint8_t** pStart,    size_t* pLen );
static int    TriceIDAndBuffer( uint32_t const * const pData, int* pWordCount,   uint8_t** ppStart,   size_t* pLength );
//! \param pTriceID is filled with ID for routing
//! \param pCount is used for double or ring buffer to advance inside the buffer
//! \param dest provides space for the encoded trice
//! \param src is the location of the trice message we want encode
//! \retval is the netto size of the encoded trice data
size_t TriceEncode(int* pTriceID, unsigned int pCount, uint32_t * const dest, uint32_t const * const src );

(back to top)

38. Trice Similarities and Differences to printf Usage

38.1. Printf-like functions

…have a lot of things to do: Copy format string from FLASH memory into a RAM buffer and parse it for format specifiers. Also parse the variadic parameter list and convert each parameter according to its format specifier into a character sequences, what includes several divisions - costly function calls. Concatenate the parts to a new string and deliver it to the output, what often means copying again. A full-featured printf library consumes plenty space and processing time and several open source projects try to make it better in this or that way. Never ever call a printf-like function in time critical code, like an interrupt - it would crash your target in most cases. The trice calls are usable inside interrupts, because they only need a few MCU clocks for execution. Porting legacy code to use it with the Trice library, means mainly to replace Printf-like function calls with trice function calls. See also chapter Legacy User Code Option Print Buffer Wrapping and Framing.

38.2. Trice IDs

38.3. Trice values bit width

38.4. Many value parameters

38.5. Floating Point Values

These types are mixable with integer types but need to be covered by converter function.


// aFloat returns passed float value x as bit pattern in a uint32_t type.
static inline uint32_t aFloat( float x ){
    union {
        float f;
        uint32_t u;
    } t;
    t.f = x;
    return t.u;
}

// aDouble returns passed double value x as bit pattern in a uint64_t type.
static inline uint64_t aDouble( double x ){
    union {
        double d;
        uint64_t u;
    } t;
    t.d = x;
    return t.u;
}

38.6. Runtime Generated 0-terminated Strings Transfer with triceS

Trice was designed mainly for speed. An universal trice like printf would cost too much runtime and destroy this main Trice advantage.

If you need several %s, like in

char* n = "Ann";
char* f = "Fox";
uint8_t dd = 22;
uint8_t mm = 11;
uint16_t yyyy = 1988;
// ...
print( "Name: %12s, Family: %s, Birthday %2u-%02u-%4u\n",  n, f, dd, mm, yyyy );

you can do:

// ...
triceS( "Name: %12s, ",  n );
triceS( "Family: %s, ", f );
trice( "Birthday %2u-%02u-%4u\n", dd, mm, yyyy );

or also

// ...
triceS( "Name: %12s, ",  n ); triceS( "Family: %s, ", f ); trice( "Birthday %2u-%02u-%4u\n", dd, mm, yyyy );

38.7. Runtime Generated counted Strings Transfer with triceN

38.8. Runtime Generated Buffer Transfer with triceB

  s = "abcde 12345"; // assume this as runtime generated string
  triceS( "msg:Show s with triceS: %s\n", s );
  len = strlen(s);
  triceN( "sig:Show s with triceN:%s\n", s, len );
  triceB( "dbg: %02x\n", s, len ); // Show s as colored code sequence in hex code.
  triceB( "msg: %4d\n", s, len ); // Show s as colored code sequence in decimal code.

This gives output similar to: ./ref/TRICE_B.PNG

Channel specifier within the TRICE_B format string are supported in Trice versions >= v0.66.0.

If the buffer is not 8 but 16, 32 or 32 bits wide, the macros TRICE8_B, TRICE16_B, TRICE32_B and TRICE64_B, are usable in the same manner.

38.9. Extended format specifier possibilities

38.9.1. Trice format specifier

38.9.2. Length modifier support

38.9.3. Overview Table

Format Specifier Type C Go T (T =Trice) | remark
signed decimal integer d d d Supported.
unsigned decimal integer u - u The Trice tool changes %u into %d and treats value as unsigned.
signed decimal integer i d i The Trice tool changes %i into %d and treats value as signed.
signed octal integer - o o With trice log -unsigned=false value is treated as signed.
unsigned octal integer o - o With trice log value is treated as unsigned.
signed octal integer with 0o prefix - O O With trice log -unsigned=false value is treated as signed.
unsigned octal integer with 0o prefix - - O With trice log value is treated as unsigned.
signed hexadecimal integer lowercase - x x With trice log -unsigned=false value is treated as signed.
unsigned hexadecimal integer lowercase x - x With trice log value is treated as unsigned.
signed hexadecimal integer uppercase - X X With trice log -unsigned=false value is treated as signed.
unsigned hexadecimal integer uppercase X - X With trice log value is treated as unsigned.
signed binary integer - b b With trice log -unsigned=false value is treated as signed.
unsigned binary integer - - b With trice log value is treated as unsigned.
decimal floating point, lowercase f f f aFloat(value)|aDouble(value)
decimal floating point, uppercase - F F aFloat(value)|aDouble(value)
scientific notation (mantissa/exponent), lowercase e e e aFloat(value)|aDouble(value)
scientific notation (mantissa/exponent), uppercase E E E aFloat(value)|aDouble(value)
the shortest representation of %e or %f g g g aFloat(value)|aDouble(value)
the shortest representation of %E or %F G G G aFloat(value)|aDouble(value)
a character as byte c - c Value can contain ASCII character.
a character represented by the corresponding Unicode code point c c c Value can contain UTF-8 characters if the C-File is edited in UTF-8 format.
a quoted character - q q Supported.
the word true or false - t t Supported.
a string s s s Use triceS macro with one and only one runtime generated string.
pointer address p p p Supported.
a double %% prints a single % % % % Supported.
Unicode escape sequence - U - Not supported.
value in default format - v - Not supported.
Go-syntax representation of the value - #v - Not supported.
a Go-syntax representation of the type of the value - T - Not supported.
nothing printed n - - Not supported.

./ref/TriceCheckOutput.gif

38.10. Unsupported printf format features

Trice supports the common printf-style format specifiers used for embedded logging. Some less common printf features are intentionally not supported yet, because they do not fit well into the current lightweight Trice argument handling model or because they introduce side effects that are unsuitable for logging.

This mainly concerns:

38.10.1. Dynamic field width with *

In standard printf, a field width can either be fixed inside the format string or supplied dynamically.

Example with fixed width:

printf("%10d", value);

Example with dynamic width:

printf("%*d", width, value);

The * is not the value to be printed. It tells printf to consume an additional int argument from the argument list and to use that value as the field width. Therefore:

printf("%*d", 10, value);

behaves like:

printf("%10d", value);

A negative dynamic width has a special meaning and implies left-aligned output, similar to the - flag.

38.10.2. Dynamic precision with *

The same principle exists for precision.

Example with fixed precision:

printf("%.3f", value);

Example with dynamic precision:

printf("%.*f", precision, value);

Again, the * consumes an additional int argument. For strings this is often used to limit the maximum number of emitted characters:

printf("%.*s", maxLen, text);

38.10.3. Dynamic width and precision together

Both dynamic field width and dynamic precision can be used in the same conversion:

printf("%*.*f", width, precision, value);

This consumes three arguments:

int width;
int precision;
double value;

The important point is that each * consumes an additional int argument before the actual value argument. This means that %*.*f does not correspond to one runtime value only. It corresponds to int, int, double.

Trice is designed so that a format string usually maps to a compact and predictable sequence of transmitted values. Dynamic width and dynamic precision break that simple mapping because the * tokens are not visible output conversions, but they still consume additional arguments.

For that reason, Trice currently does not support these forms. Supporting them correctly would require the Trice format analysis to count and encode the hidden width and precision arguments in addition to the visible value arguments.

38.10.4. Wide character and wide string formats: %lc and %ls

The %lc and %ls conversions are valid C printf forms for wide character and wide string data.

They are not equivalent to %c and %s:

For Trice this matters because triceS and related string handling transport runtime buffers, but the Trice tool does not automatically know how a target represents wide characters internally.

Without additional target-specific metadata, the host side would not know:

Therefore, simply removing the l and treating %lc like %c or %ls like %s would not be correct. That would silently change the meaning of the original C format string and could decode the payload differently from what the target-side C code actually describes.

For that reason, Trice currently does not support %lc and %ls. Proper support would require extra target-side type or encoding information so that the host can decode the transported data in an unambiguous and portable way.

38.10.5. The special %n conversion specifier

The %n specifier is fundamentally different from ordinary printf conversions.

Most conversions produce output:

printf("%d", value);
printf("%s", text);
printf("%f", number);

The %n specifier prints nothing. Instead, it writes the number of characters printed so far into the object pointed to by the corresponding argument.

Example:

int count = 0;

printf("abc%nxyz", &count);

The visible output is:

abcxyz

After the call, count contains 3, because three characters were printed before %n was reached.

Depending on the length modifier, %n expects different pointer types:

printf("%n",   &i);   // int *
printf("%hn",  &s);   // short *
printf("%hhn", &c);   // signed char *
printf("%ln",  &l);   // long *
printf("%lln", &ll);  // long long *

This means %n is not a pure logging conversion. It has a side effect because it writes to memory.

38.10.6. Security implications of %n

The %n specifier is also relevant for format-string security.

This is unsafe when userInput is not trusted:

printf(userInput);

If userInput contains format specifiers, printf interprets them. If it contains %n, printf expects a pointer argument and writes through it. If no valid pointer was actually passed, this can cause undefined behavior, memory corruption, or a crash. In more serious cases, uncontrolled format strings can become a security vulnerability.

The correct way to print uncontrolled text is:

printf("%s", userInput);

Because %n writes to memory and is strongly associated with format-string vulnerabilities, many coding standards and safety-oriented code bases discourage or forbid it.

38.10.7. Why Trice does not support %n

Trice is a logging and tracing system. Its purpose is to transfer compact log information from the target to the host, where it is decoded into readable text.

The %n specifier does not fit this model for several reasons:

  1. %n produces no log output.
  2. %n writes to target memory through a pointer argument.
  3. The written value depends on the number of characters formatted so far.
  4. Trice intentionally avoids full target-side formatting in order to remain small and fast.
  5. Supporting %n would introduce a side effect into what should be a side-effect-free logging operation.
  6. %n has known security implications when format strings are not fully controlled.

For these reasons, Trice currently does not support %n.

This is intentional. Trice log statements should describe data to be logged, not modify application memory as a side effect of formatting.

38.11. UTF-8 Support

This is gratis, if you edit your source files containing the format strings in UTF-8:

./ref/UTF-8Example.PNG

The target does not even “know” about that, because it gets only the Trice IDs.

38.12. Switch the language without changing a bit inside the target code

Once the til.json list is done the user can translate it in any language and exchanging the list switches to another language. This is nowadays a simple AI agent task.

38.13. Format tags prototype specifier examples

This syntax is supported: %[flags][width][.precision][length]

(back to top)

39. Trice ABC - Asynchronous Broadcast Commands

Trice ABC adds command-style communication to normal Trice records. The API is intentionally small and meant as a building block.

The TriceAbc example demonstrates ABC communication with COBS framing and without encryption. Its node configurations define the framing explicitly; when experimenting with a different transport setup, update the sender, receiver and logging options together.

ABC means:

The key idea is the generated ID/function-pointer list on the receiver:

sender source
  trice8C("cmd:setLeds", &mask, 1)
        |
        | trice insert
        v
til.json
  ID <-> "cmd:setLeds"
        |
        | trice generate -i til.json -abc device_abc
        v
device_abc.c
  { ID, 8, setLeds }
        |
        | receive runtime
        v
TriceParseRecord() -> TriceResolveAbc() -> TriceDispatchAbc() -> setLeds(&rx)

Only the Trice ID, optional ABC stamp, and optional payload are transferred. The command string stays in the TIL data and is used during receiver-code generation.

39.1. Quick use

Trice ABC core workflow

  1. Enable sending and/or receiving in triceConfig.h:
#define TRICE_TX_ABC_SUPPORT 1 // needed for ABC send macros
#define TRICE_RX_ABC_SUPPORT 1 // needed for ABC receive/dispatch

Sending and receiving are independent. A device may be send-only, receive-only, or both.

  1. Send a command with an ABC macro:
#include "trice.h"

void SetLeds(uint8_t mask) {
    trice8C("cmd:setLeds", &mask, 1);
}
  1. Insert IDs into the sources and TIL file:
trice insert -til til.json -li li.json -src ./project_src
  1. Generate the receiver selection/header pair and table:
trice generate -i til.json -abc ./device_abc

This creates:

device_abc.h   generated once, then user-owned receiver selection header
device_abc.c   generated table, regenerated from til.json and device_abc.h
  1. Edit device_abc.h and keep only the commands this target shall receive:
#ifndef DEVICE_ABC_H_
#define DEVICE_ABC_H_

#include "triceRx.h"

#ifdef __cplusplus
extern "C" {
#endif

void setLeds(const triceRx_t* rx);

#ifdef __cplusplus
}
#endif

#endif /* DEVICE_ABC_H_ */
  1. Implement the selected handlers:
#include <stdint.h>
#include "device_abc.h"

static uint8_t boardLeds;

void setLeds(const triceRx_t* rx) {
    if (rx == 0 || rx->payloadBytes != 1u) {
        return;
    }
    boardLeds = rx->payload[0];
}
  1. Feed decoded Trice records to the receive runtime:
triceRx_t rx;
int used = TriceParseRecord(&rx, record, recordLen);

if (used > 0 && TriceResolveAbc(&rx, triceAbc, triceAbcElements) == TRICE_RX_RESULT_OK) {
    (void)TriceDispatchAbc(&rx);
}

triceRx parses already deframed and decrypted Trice records. UART, RTT, file, socket, COBS, TCOBS, and encryption handling stay outside this small receive core.

Trice ABC core workflow

39.2. ABC macro families

The suffix C means command. The optional number in the macro name is the payload element width.

no stamp 16-bit stamp 32-bit stamp payload element width
triceC TriceC TRiceC no payload
trice8C Trice8C TRice8C 8-bit
trice16C Trice16C TRice16C 16-bit
trice32C Trice32C TRice32C 32-bit
trice64C Trice64C TRice64C 64-bit

Examples:

triceC("cmd:motorStop"); // no stamp, no value

uint16_t seq16 = NextSeq16();
TriceC("cmd:getLeds", seq16); // 16-bit stamp, no value

uint32_t unixTime = BoardTime();
trice32C("cmd:setTime", &unixTime, 1); // no stamp, one 32-bit value

int16_t step[] = { -50, 0, 300, 0 };
TRice16C("cmd:motorStep", 0x12345678, step, 4); // 32-bit stamp, 4 16-bit values

For stamped ABC macros, the explicit ABC stamp follows the command string and precedes the payload arguments.

ABC stamps are application-defined correlation values. They are not automatically generated Trice timestamps. Pass TriceStamp16 or TriceStamp32 explicitly if a real timestamp is desired.

TriceC("cmd:sample", TriceStamp16);
TRiceC("cmd:sample", TriceStamp32);

39.3. Command names and handler names

The ABC command name is written where a normal Trice format string would stand. Treat it as a command name, not as a printf format string.

triceC("cmd:motorStop");
trice32C("cmd:setTime", &unixTime, 1);

Everything before the last colon is tag/grouping text. The generator uses the part after the last colon as C handler name:

cmd:motorStop       -> motorStop
cmd:deviceA:sample  -> sample
abc:LedsState       -> LedsState

The prefixes are not ABC addresses. They are useful for readable TIL data, filtering, grouping, and examples.

The final command part must be a valid C identifier. Do not add a trailing newline to ABC command strings.

39.4. Receiver selection and generated table

trice generate -abc target creates target.h and target.c in -genDir (default ./generated). The header is user-owned after its first creation: edit and version it to select the commands compiled into that target. The repository ignores generated C files but leaves this editable header visible to Git. An explicit target path such as -abc path/target keeps its existing location relative to the TIL directory.

The generator regenerates the C table from the intersection of:

A command present in til.json but not declared in device_abc.h is ignored by this receiver. A declaration without a matching TIL entry is a build/configuration issue.

Generated device_abc.c has the essential shape:

#include "device_abc.h"

const triceAbc_t triceAbc[] = {
    /* id, bitWidth, function pointer */
    { 5150u, 8u, setLeds },
    { 4818u, 0u, getLeds },
};

const unsigned triceAbcElements = sizeof(triceAbc) / sizeof(triceAbc[0]);

Do not edit device_abc.c. Implement the selected handlers in normal application code. Missing handler implementations fail as normal linker errors.

39.5. Receive runtime contract

The common receive API is in src/triceRx.h and src/triceRx.c.

Use TriceParseRecord() to parse one decoded Trice record. It fills a triceRx_t:

uint16_t id;              // Trice ID
uint8_t  bitWidth;        // payload element width after resolution
uint8_t  stampBits;       // 0, 16, or 32
uint32_t stamp;           // application-defined ABC stamp
const uint8_t* payload;   // points into caller-owned input buffer
uint16_t payloadBytes;    // payload byte count

The payload is not copied. Do not store rx->payload beyond the lifetime of the input buffer unless the handler copies the data.

TriceResolveAbc() looks up the parsed ID in the generated triceAbc[] table and attaches the resolved bit width and function pointer to rx.

TriceDispatchAbc() validates the payload size against the resolved bit width and calls the selected handler. Unknown IDs are normal in mixed streams and can be ignored.

For simple one-record receive paths, TriceAbcOnReceive(pBuf, len) is available as a convenience wrapper. Stream receivers should usually parse records explicitly, advance by the positive consumed byte count, and decide per record whether it is ABC, normal log traffic, counted typeX0 traffic, or unknown traffic.

39.6. Handler payload handling

Handlers get only const triceRx_t*. Application state must come from normal program context.

Use rx->bitWidth, rx->payloadBytes, and rx->payload to interpret the payload. For multi-byte values, prefer copying from the byte buffer instead of casting the pointer (alignment).

#include <string.h>

void setTime(const triceRx_t* rx) {
    uint32_t t;

    if (rx == 0 || rx->bitWidth != 32u || rx->payloadBytes != sizeof(t)) {
        return;
    }

    memcpy(&t, rx->payload, sizeof(t));
    BoardSetTime(t);
}

Use the configured Trice transfer order consistently if payload values are exchanged between different endian architectures.

39.7. Responses

ABC has no built-in response model. A handler may send no response, one response, or several responses.

A common pattern is:

// request
TriceC("cmd:getLeds", seq16);

// response from interested receiver
TRice8C("abc:LedsState", responseStamp32, &leds, 1);

The response is just another Trice message. It may be a normal log message or another ABC command. Use stamps to correlate responses with requests.

39.8. What ABC is not

ABC is not RPC by itself.

ABC does not define:

Build these policies above ABC when needed.

ABC is also not remote code execution. A receiver can execute only handlers already compiled into the firmware and selected by its generated ABC table.

39.9. Example: examples/TriceAbc

The host-native demo shows ABC without embedded hardware.

Run it from the example directory:

cd examples/TriceAbc
./build.sh
./demo.sh

Trice ABC host demo bus topology

The demo uses BcSim as a small byte bus. It transports bytes only; the Trice-specific logic is in NodeLib and the node programs.

The build script demonstrates the full workflow:

trice insert ...
trice generate -i ../../demoTIL.json -abc NodeLib/nodeAbc
trice generate -i ../../demoTIL.json -src . -logC=NodeLib/til.c

It produces:

NodeLib/nodeAbc.h   shared user-owned ABC selection header
NodeLib/nodeAbc.c   shared generated ABC table
NodeLib/til.c       compact generated log metadata for normal-log resolving

The demo nodes have different roles:

The demo commands include:

cmd:getLeds and cmd:divide show request/response behavior built above ABC. The stamped requests use low stamp bits as a small responder bitmap in the demo. That is application policy, not ABC core behavior.

The shared runtime flow is:

normal Trice macro / ABC macro
  -> TriceWriteDevice()
  -> BcSim byte bus
  -> COBS frame collector
  -> TriceParseRecord()
  -> TriceResolveAbc() / TriceResolveLog()
  -> node handler or demo log printer

The example intentionally parses once and then decides whether the record is ABC, normal log traffic, counted typeX0 traffic, or unknown traffic. This is the recommended style for mixed receive streams.

An example log snippet:

...
N3_tx: ABC-> cmd:setLeds(0c)
N6_rx: log:tick=203
N6_rx: log:from=3 phase=3
N6_rx: log:text=N3 bidirectional
N6_rx: x0 3 bytes: 33 34 35
N6_rx: leds=[  **    ]
N7_bi: log:tick=203
N7_bi: log:from=3 phase=3
N7_bi: log:text=N3 bidirectional
N7_bi: x0 3 bytes: 33 34 35
N7_bi: leds=[  **    ]
N5_rx: x0 3 bytes: 33 34 35
N5_rx: leds=[  **    ]
...

The broadcast simulation abc.bus log starts with:

# BcSim traffic log
# bc.bus is a pure binary byte stream. This text log is diagnostic only.
# offset and len are decimal values. Bytes are hexadecimal %02x values.
#   offset  len device       dir status               bytes
# -------- ---- ------------ --- -------------------- --------------------------------
         0   10 N3_bi        TX  trice                06 d2 53 c0 04 c8 01 01 01 00
        10   14 N3_bi        TX  trice                06 54 55 c0 08 03 01 01 01 01 01 01 01 00
        24   22 N3_bi        TX  trice                15 99 53 c0 10 4e 33 20 62 69 64 69 72 65 63 74 69 6f 6e 61 6c 00
        46    6 N3_bi        TX  trice                02 02 03 30 31 00
         0   52 N8_bi        RX  poll                 06 d2 53 c0 04 c8 01 01 01 00 06 54 55 c0 08 03 01 01 01 01 01 01 01 00 15 99 53 c0 10 4e 33 20 62 69 64 69 72 65 63 74 69 6f 6e 61 6c 00 02 02 03 30 31 00
         0   52 N6_rx        RX  poll                 06 d2 53 c0 04 c8 01 01 01 00 06 54 55 c0 08 03 01 01 01 01 01 01 01 00 15 99 53 c0 10 4e 33 20 62 69 64 69 72 65 63 74 69 6f 6e 61 6c 00 02 02 03 30 31 00
...

With tlog.sh one can see the Trice logs as well:

th@Thomass-MacBook-Pro-7 TriceAbc % ./tlog.sh 
Jul 27 22:26:51.574387  FILEBUFFER:          N3_bi/main.c    14              log:(from node=N3_bi) tick=200
Jul 27 22:26:51.574475  FILEBUFFER:          N3_bi/main.c    18              log:(from node=N3_bi) from=3 phase=0
Jul 27 22:26:51.574491  FILEBUFFER:          N3_bi/main.c    22              log:(from node=N3_bi) text=N3 bidirectional
Jul 27 22:26:51.574503  FILEBUFFER:                                          typeX0 buffer: [48 49]
Jul 27 22:26:51.574729  FILEBUFFER:          N3_bi/main.c    14              log:(from node=N3_bi) tick=201
Jul 27 22:26:51.574773  FILEBUFFER:          N3_bi/main.c    18              log:(from node=N3_bi) from=3 phase=1
Jul 27 22:26:51.574921  FILEBUFFER:          N3_bi/main.c    22              log:(from node=N3_bi) text=N3 bidirectional
Jul 27 22:26:51.574942  FILEBUFFER:                                          typeX0 buffer: [49 50 51]
Jul 27 22:26:51.574952  FILEBUFFER:          N3_bi/main.c    50        0_103 cmd:getLeds
Jul 27 22:26:51.574964  FILEBUFFER:        NodeLib/node.c   654        0_103 abc:LedsState(00)
...
Jul 27 22:26:51.576238  FILEBUFFER:          N3_bi/main.c    22              log:(from node=N3_bi) text=N3 bidirectional
Jul 27 22:26:51.576246  FILEBUFFER:                                          typeX0 buffer: [59 60 61]
Jul 27 22:26:51.576254  FILEBUFFER:          N3_bi/main.c   102    0,000_103 cmd:getLeds
Jul 27 22:26:51.576262  FILEBUFFER:        NodeLib/node.c   656    0,000_103 abc:LedsState(80)
Jul 27 22:26:51.576271  FILEBUFFER:        NodeLib/node.c   656    0,000_103 abc:LedsState(80)
Jul 27 22:26:51.576279  FILEBUFFER:        NodeLib/node.c   656    0,000_103 abc:LedsState(80)
Jul 27 22:26:51.576287  FILEBUFFER:          N2_tx/main.c    14              log:(from node=N2_tx) tick=107
Jul 27 22:26:51.576296  FILEBUFFER:          N2_tx/main.c    18              log:(from node=N2_tx) from=2 phase=0
Jul 27 22:26:51.576304  FILEBUFFER:          N2_tx/main.c    22              log:(from node=N2_tx) text=N2 sends data
Jul 27 22:26:51.576312  FILEBUFFER:                                          typeX0 buffer: [39 41 43 45 47]
Jul 27 22:26:51.576320  FILEBUFFER:          N2_tx/main.c    62        0_102 cmd:getLeds
Jul 27 22:26:51.576328  FILEBUFFER:        NodeLib/node.c   654        0_102 abc:LedsState(80)
Jul 27 22:26:51.576337  FILEBUFFER:        NodeLib/node.c   654        0_102 abc:LedsState(80)
th@Thomass-MacBook-Pro-7 TriceAbc %

Trice ABC host demo bus topology

39.9.1. ABC Demo Layout, Startup, and Runtime Policy

The host-native example requires Bash and a C compiler (gcc, clang, or cc); CC selects a compiler explicitly. Its build script uses TRICE_BIN if supplied, otherwise Go when available, or trice in PATH. It builds nine executables below build (.exe on Windows), inserts IDs before generating the tables, and runs trice clean on exit after a successful insert. NodeLib/til.c is regenerated for the selected source roots and is not a checked-in source. NodeLib/nodeAbc.h is the shared user-owned command selection, so preserve it when cleaning generated files.

The layout separates BcSim (reusable protocol-neutral transport), BcSimChk (standalone random-byte check), NodeLib (shared Trice runtime and generated tables), and the nine node directories. tx, rx, and bi describe bus capability, not command vocabulary. N1_tx/N2_tx emit logs, counted typeX0 buffers, and commands; N3_bi also receives and replies; N4_rx/N5_rx execute commands only; N6_rx adds received log presentation; N7_bi combines replies and log presentation; N8_bi/N9_bi reply without the normal-log printer.

demo.sh starts receive-capable nodes first, then pure transmitters. A BcSim participant joins at the current end of the bus and does not replay earlier traffic. Runtime files are abc.bus (binary framed stream), abc.log (human hex log), and abc.bus.lock/ (writer lock). They are separate from BcSimChk’s bc.* files. abc.console.lock/ keeps each complete node or shell status line together; the console lock waits rather than falling back to interleaved writes. A killed lock owner may require manual cleanup after all participants stop.

The command shapes and effects are intentionally small:

Command Payload and effect
cmd:setLeds One 8-bit mask; update the local simulated LED bar.
cmd:getLeds No payload; every other bidirectional node can answer abc:LedsState with an 8-bit mask. Receive-only nodes cannot reply.
cmd:setKey Counted 8-bit byte buffer; store a local key.
cmd:logState No payload; local printf side effect, without a Trice/ABC response.
cmd:divide Two 32-bit floats; bidirectional nodes answer abc:DivideResult with one float.

Unstamped requests broadcast, so several identical-looking replies are expected. For stamped getLeds and divide, the demo’s application policy uses low bits 0x0001, 0x0002, and 0x0004 to select N7_bi, N8_bi, and N9_bi. N3_bi demonstrates one and multiple selected responders. Replies retain stamp width and value. This is demonstration routing above ABC, not a built-in addressing protocol.

The node-local triceConfig.h files select TX, ABC RX, normal-log resolution, and direct output. Transmitting nodes select TRICE_DIRECT_OUT_FRAMING TRICE_FRAMING_COBS, for example in N3_bi/triceConfig.h; the shared NodeLib/node.c collects and decodes COBS frames. All participants must agree on framing. The former separate triceRxConfig.h is no longer present. NodeLib implements the real generated handlers once; runtime canSend decides whether a node replies, avoiding forwarding wrappers in each node.

The host bridge preserves the normal send macros through TriceWriteDevice(). A persistent input buffer splits COBS frames at zero delimiters, keeps the incomplete tail, then iterates logical records inside each decoded frame. It skips record-alignment bytes only when the expected bytes are zero. Parsing happens once, followed by ABC, normal-log, typeX0, or unknown-record dispatch; TriceAbcOnReceive() is not the primary demo entry point. Selector-0 buffers have no ID or TIL lookup and are displayed as raw bytes. Nodes without normal-log resolution show ignored IDs; the small generated-til.c log printer is not a replacement for the Go host decoder and has no source-location column.

Self-written bus ranges are filtered, so a node does not receive its own frames or display its own logs as received traffic. nodeSleepMs() handles shared pacing. Process-local TRICE_ENTER_CRITICAL_SECTION hooks cannot protect a multi-process console; NodeLib’s separate console lock does that. Each node formats a full line before acquiring the lock. LED output uses * for on and space for off, for example:

N4_rx: leds=[**  *   ]
N6_rx: key=bravo7 leds=[***     ]
N7_bi: abc:DivideResult=3.140000
N6_rx: log:tick=4
N7_bi: x0 5 bytes: 10 11 12 13 14

39.10. BcSim Broadcast Byte-Stream Simulator

BcSim is a standalone C module: several PC processes append to a shared local file and poll bytes written by others. It knows nothing about IDs, framing, encryption, packet boundaries, source addresses, commands, or handlers. bc.bus contains exactly the supplied bytes, with no inserted name, timestamp, length, or metadata; optional bc.log is human-readable diagnostic output only.

Each process owns one BcSim_t. bcSimOpen() opens a local view and starts reading at the current end of the bus. bcSimWrite() appends bytes and remembers its own written offset ranges; bcSimRead() filters those ranges from subsequent reads; bcSimClose() closes the view and resets state. Filtering by offsets rather than byte contents preserves identical data legitimately sent by different processes or repeated by one process.

Writers serialize through an atomically created bc.bus.lock/ directory: acquire lock, obtain file size, append, remember the [start,end) range, optionally append the TX log line, then remove the lock. Competing writers retry until the timeout. Reads normally take no writer lock and may see partial data, which the higher stream layer must buffer. Define BCSIM_READ_USES_LOCK 1 for deterministic reads under the same writer lock.

The public API is:

int bcSimOpen(BcSim_t* io, const char* busPath,
              const char* logPath, const char* deviceName);
int bcSimRead(BcSim_t* io, uint8_t* p, size_t max, const char* status);
int bcSimWrite(BcSim_t* io, const uint8_t* p, size_t n, const char* status);
void bcSimClose(BcSim_t* io);

The three integer-returning functions return a non-negative byte count or a negative BCSIM_ERR_* value. The log starts with a header, then one TX/RX line per event: right-aligned decimal offsets and lengths without leading zeros, device, direction, optional status, and space-separated two-digit lowercase hexadecimal bytes.

# BcSim traffic log
# bus file: bc.bus
# Columns: offset, len, device, direction, status, bytes
      0      12  A                 TX   tx-0                    35 6a 11 8e ...
     12      12  B                 RX   poll-1                  35 6a 11 8e ...

Try the transport alone, without Trice tables:

cd examples/TriceAbc/BcSimChk
./demo.sh

Its build script compiles main.c and ../BcSim/BcSim.c; CC and CFLAGS allow experiments, such as CFLAGS='-DBCSIM_READ_USES_LOCK=1' ./build.sh. The demo starts four participants with random byte blocks and shows bc.log and a bus hex dump. The reusable library files are BcSim_config.h, BcSim.h, and BcSim.c; BcSimChk is not needed by applications reusing the transport.

This is a local demonstration medium, not high-performance IPC or a real embedded link. The bus grows until removed, only finitely many self-write ranges are remembered, and a restarted process cannot identify a previous instance’s writes. A killed writer can leave a lock directory requiring cleanup. Network filesystems may not offer the same atomic directory and visibility behavior as local filesystems.

39.11. Host tests

_test/abc_tx_host checks the transmit side. It compiles a small C fixture with ABC TX support, emits selected triceC, TriceC, TRiceC, trice8C, trice16C, and trice32C calls, and compares the produced bytes with fixed fixtures. It verifies wire format generation only; it does not use a receiver table.

_test/abc_rx_host checks the receive side against the production src/triceRx.c runtime and a generated device_abc pair. The tests cover selected IDs, unknown IDs, no-payload records, 8/16/32/64-bit payloads, 16/32-bit stamps, malformed payload lengths, truncated records, nested dispatch, long-count payload encoding, log-resolution coexistence, and one-record-at-a-time stream consumption.

Together, these tests document the current ABC boundary: transmit macros create normal Trice records, the generated table maps selected IDs to handlers, and the receive runtime parses/resolves/dispatches one decoded record at a time.

39.12. Building RPC-like protocols on top

Use ABC as the transport primitive and define the RPC policy in the application.

A minimal RPC-like pattern is:

  1. Define request commands, for example rpc:getValue. (requester)
  2. Define response commands, for example rpc:getValueResult. (a receiver acting as “requester” in a response)
  3. Put a correlation value into the 16-bit or 32-bit ABC stamp.
  4. Encode arguments in the payload.
  5. Let the receiver validate the payload, execute local code, and send a response (rpc:getValueResult) with the same or derived stamp.
  6. Let the requester also be an ABC receiver and keep a pending-request table.
  7. Implement timeout, retry, duplicate handling, authorization, and addressing at application level.

For addressed RPC over a broadcast bus, put the destination into the stamp or payload and let non-matching receivers ignore the command. ABC itself still broadcasts the record.

39.13. Security boundary

ABC receiving allows incoming Trice records to trigger selected local application handlers. Do not enable ABC receive processing on untrusted inputs without an application-level trust model.

Typical protections are:

39.14. Summary

ABC turns selected Trice IDs into asynchronous broadcast commands.

The small core is:

ABC send macro
  -> Trice ID in til.json
  -> generated *_abc.h selection
  -> generated *_abc.c ID/function-pointer table
  -> user handler(const triceRx_t* rx)

Everything else, including addressing, responses, reliability, retries, authorization, and RPC semantics, belongs to the application layer above ABC.

Using Trice ABC for the same (remote) handler from different devices assigns different IDs to the same handler. This way one can consider a Trice ABC ID as senders address and the activated handler has access over the passed triceRx_t pointer to it.

(back to top)

40. Development Environment Setup

40.1. Common Information

40.2. Important to know

The ARM-Keil µVision IDE does sometimes not recognize external file modifications. That means for example: After editing main.c by adding a trice( "Hi!\n" ) and executing trice insert as pre-compile step it could happen, that an updated trice( iD(12345), "Hi!\n" ) was inserted and correct compiled but the update in main.c is not shown. Simply close and reopen main.c before editing again. This seems to be a ARM-Keil µVision IDE “feature” or be caused Windows not signaling a file change.

40.3. Animation

(The trice IDs occur just during the compilation.)

40.4. Setup Linux PC - Example with Debian12 - KDE Desktop

40.4.1. Basic setup

su
apt install sudo
adduser <your_user_name> sudo
exit
groups
sudo apt update
sudo apt upgrade
sudo apt install build-essential
make --version
gcc --version
git --version
git config --global user.email "you@example.com"
git config --global user.name "Your Name"

40.4.2. GitHub

40.4.3. VS Code

Open the repository root to use the shared Go launch configurations. Install the Go extension and its debugger tools first. The configurations use ${workspaceFolder} rather than a particular checkout location. UART and TCP entries ask for the serial port or RTT endpoint at launch; these are VS Code input variables.

The G0B1 and L432 hardware entries use the root demoTIL.json and demoLI.json, which belong to those instrumented examples. Build and flash the corresponding example before connecting. The G0B1 UART entry matches its encrypted COBS output at 115200 baud with password MySecret; the RTT entries match unframed direct output with doubled IDs. TCP4 expects that same G0B1 RTT stream forwarded by a server, normally at localhost:17001. These settings do not apply to arbitrary firmware or captures: use the ID/location tables and framing from that firmware.

The generate entry creates generated/til.c from the current G0B1 main source; run the example’s bind/build step first so that its sidecars and ID table agree. The insert/clean entries operate on that main source and update the root tables; they are maintenance commands, not log viewers. Already bound code follows the normal insert/clean ownership rules. The test entry runs one existing bind test. For a hardware-free decoder session, select trice l -p DUMP: its retained capture uses testTIL.json/testLI.json and includes a missing cycle-counter value 194, so one cycle-error diagnostic is expected. Entries whose captures, source folders or command variants no longer exist have been removed.

When opening a G0B1 example itself, its C/C++ configuration finds arm-none-eabi-gcc through PATH. Start VS Code from an environment containing the installed ARM compiler and install the C/C++ and Makefile Tools extensions. Only USE_HAL_DRIVER and STM32G0B1xx are supplied as project defines; builtin defines are queried from the compiler with -mcpu=cortex-m0plus rather than copied from an older version or inferred for the compiler’s default CPU. IntelliSense uses gcc-arm instead of the host platform’s default architecture.

The shared JetBrains settings retain the CLion CMake review host, project inspections and XML spelling dictionary. Choose the host toolchain locally; workspace layout and personal state are ignored. IDE launch, serial/RTT connections and indexing still need checking in your installed IDE and hardware environment; parsing the templates alone cannot establish those results.

40.4.4. Go

40.4.5. Gitkraken (or other GUI for git)

40.4.6. arm-none-eabi toolchain (or other target system compiler)

sudo apt install gcc-arm-none-eabi
sudo apt install binutils-arm-none-eabi
sudo apt install gdb-arm-none-eabi
sudo apt install openocd
arm-none-eabi-gcc --version
arm-none-eabi-gcc (15:12.2.rel1-1) 12.2.1 20221205
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  ls -l /usr/bin/ | grep arm-none-eabi
  -rwxr-xr-x 1 root root     1033504 Feb 28  2023 arm-none-eabi-addr2line
  -rwxr-xr-x 2 root root     1066088 Feb 28  2023 arm-none-eabi-ar
  -rwxr-xr-x 2 root root     2095024 Feb 28  2023 arm-none-eabi-as
  -rwxr-xr-x 2 root root     1514496 Dec 22  2022 arm-none-eabi-c++
  -rwxr-xr-x 1 root root     1032992 Feb 28  2023 arm-none-eabi-c++filt
  -rwxr-xr-x 1 root root     1514496 Dec 22  2022 arm-none-eabi-cpp
  -rwxr-xr-x 1 root root       43640 Feb 28  2023 arm-none-eabi-elfedit
  -rwxr-xr-x 2 root root     1514496 Dec 22  2022 arm-none-eabi-g++
  -rwxr-xr-x 2 root root     1514496 Dec 22  2022 arm-none-eabi-gcc
  -rwxr-xr-x 2 root root     1514496 Dec 22  2022 arm-none-eabi-gcc-12.2.1
  -rwxr-xr-x 1 root root       35376 Dec 22  2022 arm-none-eabi-gcc-ar
  -rwxr-xr-x 1 root root       35376 Dec 22  2022 arm-none-eabi-gcc-nm
  -rwxr-xr-x 1 root root       35376 Dec 22  2022 arm-none-eabi-gcc-ranlib
  -rwxr-xr-x 1 root root      749664 Dec 22  2022 arm-none-eabi-gcov
  -rwxr-xr-x 1 root root      585688 Dec 22  2022 arm-none-eabi-gcov-dump
  -rwxr-xr-x 1 root root      610328 Dec 22  2022 arm-none-eabi-gcov-tool
  -rwxr-xr-x 1 root root     1104256 Feb 28  2023 arm-none-eabi-gprof
  -rwxr-xr-x 4 root root     1709968 Feb 28  2023 arm-none-eabi-ld
  -rwxr-xr-x 4 root root     1709968 Feb 28  2023 arm-none-eabi-ld.bfd
  -rwxr-xr-x 1 root root    24982344 Dec 22  2022 arm-none-eabi-lto-dump
  -rwxr-xr-x 2 root root     1054720 Feb 28  2023 arm-none-eabi-nm
  -rwxr-xr-x 2 root root     1180744 Feb 28  2023 arm-none-eabi-objcopy
  -rwxr-xr-x 2 root root     1867744 Feb 28  2023 arm-none-eabi-objdump
  -rwxr-xr-x 2 root root     1066120 Feb 28  2023 arm-none-eabi-ranlib
  -rwxr-xr-x 2 root root      973400 Feb 28  2023 arm-none-eabi-readelf
  -rwxr-xr-x 1 root root     1033280 Feb 28  2023 arm-none-eabi-size
  -rwxr-xr-x 1 root root     1037504 Feb 28  2023 arm-none-eabi-strings
  -rwxr-xr-x 2 root root     1180744 Feb 28  2023 arm-none-eabi-strip

For the Trice bare-metal examples, use a complete, internally consistent toolchain containing GCC, GNU Binutils, Newlib, and Newlib-Nano. The official Arm GNU Toolchain installation guide covers matching packages for Linux, macOS, and Windows. Select the package for the host platform whose target name ends in arm-none-eabi, and verify its accompanying SHA-256 file before installing it.

Arm GNU Toolchain 15.3.Rel1 is the currently tested version for the Trice GCC example builds. It reports:

arm-none-eabi-gcc (Arm GNU Toolchain 15.3.Rel1 (Build arm-15.149)) 15.3.1 20260627
GNU assembler (Arm GNU Toolchain 15.3.Rel1 (Build arm-15.149)) 2.45.1.20260126

Install new releases side by side instead of replacing a working toolchain immediately. Prepend the selected installation’s bin directory to PATH for the current shell or configure it permanently using the host operating system’s normal environment-variable settings. On Linux and macOS, an unpacked archive can be selected temporarily as follows:

toolchain_dir="$HOME/opt/arm-gnu-toolchain-15.3.rel1"
export PATH="$toolchain_dir/bin:$PATH"

On Windows, keep versioned installations side by side and prepend the selected bin directory for the current terminal. See Inventory, select, and remove compiler versions. Make the selection persistent only after it passes the tests. Do not combine GCC, as, libraries, or specifications from different toolchain installations.

Check which installation is active and whether the required runtime files are present:

command -v arm-none-eabi-gcc
command -v arm-none-eabi-as
arm-none-eabi-gcc --version
arm-none-eabi-as --version
arm-none-eabi-gcc -print-file-name=nano.specs
arm-none-eabi-gcc -print-file-name=libnosys.a

Use where.exe instead of command -v in a Windows command prompt. The last two commands must print resolved paths. Output containing only nano.specs or libnosys.a means that the active installation is incomplete.

On macOS, brew install --cask gcc-arm-embedded installs an official complete Arm package, but the cask can lag behind the newest Arm release. In contrast, the Homebrew formula installed by brew install arm-none-eabi-gcc builds GCC with --without-headers and installs GCC plus libgcc, but not Newlib/Newlib-Nano. A newer GCC version number from that formula therefore does not make it a complete replacement for the official package used by these examples.

GNU assembler unable to rebuffer file warning

The G0B1 build requests assembler listings with -Wa,-a,.... While generating such a listing, GNU as reopens and rereads source text associated with the temporary compiler-generated assembly file. A diagnostic such as

ccXXXX.s: Warning: unable to rebuffer file: path/to/source.c

means that this second source-file read returned fewer bytes than expected. It is an assembler-listing diagnostic, not a C-language warning. The object and executable may still have been generated correctly, but the Trice full test intentionally treats every warning as a failure.

The warning was observed once with Arm GNU Toolchain 15.2.Rel1 on macOS and did not recur when the identical G0B1 build was repeated. The complete test with 15.3.Rel1, including the G0B1 X0 matrix and listing generation, completed without warnings. This establishes the warning as intermittent; it does not prove that 15.3.Rel1 contains a specific fix for it.

If the warning occurs:

  1. Keep listing generation and strict warning checks enabled.
  2. Confirm the active GCC and assembler paths and versions with the commands above.
  3. Ensure that no editor, generator, formatter, synchronization tool, or parallel build step rewrites the named source file while as is running.
  4. Repeat the affected build once with the complete 15.3.Rel1 package.
  5. If it is reproducible, retain the complete compiler command, tool versions, source file, and generated listing and report the case as a GNU Binutils or Arm GNU Toolchain issue.
sudo apt install ~/Downloads/JLink_Linux_V812_x86_64.deb
th@P51-DebianKDE:~/Downloads$ JLinkRTTLogger -?
SEGGER J-Link RTT Logger
Compiled Dec 18 2024 15:48:21
(c) 2016-2017 SEGGER Microcontroller GmbH, www.segger.com
         Solutions for real time microcontroller applications

Default logfile path: /home/th/.config/SEGGER

------------------------------------------------------------ 

Available options:
-Device <devicename>
-If <ifname>
-Speed <speed>
-USB <SN>
-IP <SN>
-RTTAddress <RTTAddress>
-RTTSearchRanges "<Rangestart> <RangeSize>[, <Range1Start> <Range1Size>, ...]
"-RTTChannel <RTTChannel>
-JLinkScriptFile <PathToScript>
<OutFilename>

Shutting down... Done.th@P51-DebianKDE:~/Downloads$ 

40.4.8. Beyond Compare (if no other diff tool)

40.5. Setup Windows PC Example

Setting up a PC is for Linux mostly straightforward but Windows PCs are more problematic. The steps shown here are just one example.

40.5.1. Choose the right Windows compiler

Three different compiler roles occur in this repository. A higher GCC version number does not make one role a replacement for another:

Role Command or target Used for Recommended source
ARM bare-metal GCC cross-toolchain arm-none-eabi-gcc; target arm-none-eabi Firmware and scripts/_210_gcc_example_builds_all_workflows.sh Official Arm GNU Toolchain installation guide; choose a Windows package ending in arm-none-eabi
Windows host GCC gcc; target such as x86_64-w64-mingw32 or i686-w64-mingw32 CGO and native Windows C tests Optional MinGW-w64 distribution, for example WinLibs
Clang frontend clang; use --target=arm-none-eabi for firmware Optional ARM firmware builds and, when correctly configured, native host tests Official LLVM releases

WinLibs is a third-party distribution of upstream GCC and MinGW-w64 for Windows. Its GCC 16.1 packages are Windows host compilers, not arm-none-eabi-gcc. They cannot build the ARM examples or replace the Arm GNU Toolchain. WinLibs is useful only when a Windows host GCC is needed. Normally choose its Win64 x86_64 build for a 64-bit Go installation; a Win32 package reports i686-w64-mingw32. Use go env GOARCH to check the Go architecture; amd64 normally needs the Win64 host compiler.

Do not infer the compiler target from the download page, folder name, or --version alone. Check it explicitly:

arm-none-eabi-gcc -dumpmachine # must print: arm-none-eabi
gcc -dumpmachine               # host GCC normally prints: x86_64-w64-mingw32
clang --target=arm-none-eabi -dumpmachine

For example, a C:\bin\mingw32\bin\gcc.exe reporting GCC 16.1 and i686-w64-mingw32 is an optional 32-bit Windows host compiler. The executable needed by Step 12 is named arm-none-eabi-gcc.exe and comes from a separate Arm GNU Toolchain installation.

At the time of writing, GCC 16.1 is the newest upstream GCC major release and WinLibs offers it as its current Windows host build. The current official Arm GNU Toolchain version can differ because Arm publishes an integrated cross-toolchain on its own release schedule. Use the version identified as tested in Recommended complete Arm GNU Toolchain for the ARM examples; do not select a host GCC merely because its GCC number is higher.

40.5.2. Setup Trice

OR

40.5.3. Setup ARM Environment Example

<h5>Install make</h5>

ms@PaulPCWin11 MINGW64 ~/repos/trice/examples (devel)
$ winget install ezwinports.make
The `msstore` source requires that you view the following agreements before using.
Terms of Transaction: https://aka.ms/microsoft-store-terms-of-transaction
The source requires the current machine's 2-letter geographic region to be sent to the backend service to function properly (ex. "US").

Do you agree to all the source agreements terms?
[Y] Yes  [N] No: Y
Found ezwinports: make [ezwinports.make] Version 4.4.1
This application is licensed to you by its owner.
Microsoft is not responsible for, nor does it grant any licenses to, third-party packages.
Downloading https://downloads.sourceforge.net/project/ezwinports/make-4.4.1-without-guile-w32-bin.zip
  ██████████████████████████████   383 KB /  383 KB
Successfully verified installer hash
Extracting archive...
Successfully extracted archive
Starting package install...
Path environment variable modified; restart your shell to use the new value.
Command line alias added: "make"
Successfully installed

ms@PaulPCWin11 MINGW64 ~/repos/trice/examples (devel)
$ make --version
GNU Make 4.4.1
Built for Windows32
Copyright (C) 1988-2023 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

<h5>Install ARM GCC</h5>

<h5>macOS</h5>

<h5>Install ARM Clang (optional)</h5>

With the ARM Clang you get quicker compilation runs and smaller images.

On Windows, a globally visible clang is also detected by some Go regression tests as a host C compiler. LLVM does not include a Windows C runtime or its standard headers. Therefore, a host Clang installation must use one of these runtime setups:

Check the host installation before running the Go test suite:

printf '#include <string.h>\n' | clang -std=c99 -fsyntax-only -x c -

The command must finish without diagnostics. An error such as fatal error: 'string.h' file not found means that the compiler executable exists, but its matching host C runtime headers are not configured. Do not add ARM include directories globally to work around that host error.

<h5>Check Project Makefile (if it already exists)</h5>

$ make version
/c/bin/ArmGNUToolchain/bin/arm-none-eabi-gcc
arm-none-eabi-gcc (Arm GNU Toolchain 12.3.Rel1 (Build arm-12.35)) 12.3.1 20230626
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

/c/bin/ArmClang/bin/clang --target=arm-none-eabi
clang version 17.0.0
Target: arm-none-unknown-eabi
Thread model: posix
InstalledDir: C:\bin\ArmClang\bin

The paths and versions must match the installations selected in the current terminal.

40.5.4. Inventory, select, and remove compiler versions

An extracted ZIP toolchain is usually not registered as an installed Windows application. Therefore no single Windows dialog lists every compiler. Inspect both command resolution and likely installation directories before changing anything.

In PowerShell, list every matching executable visible through Path:

Get-Command arm-none-eabi-gcc,gcc,clang -All -ErrorAction SilentlyContinue |
    Format-Table Name,Source
where.exe arm-none-eabi-gcc
where.exe gcc
where.exe clang

arm-none-eabi-gcc -dumpmachine
gcc -dumpmachine
clang --version

[Environment]::GetEnvironmentVariable('Path', 'User') -split ';'
[Environment]::GetEnvironmentVariable('Path', 'Machine') -split ';'
Get-ChildItem C:\bin -Directory

In Git Bash, use:

type -a arm-none-eabi-gcc
type -a gcc
type -a clang
arm-none-eabi-gcc -dumpmachine
gcc -dumpmachine
clang --version
printf '%s\n' "$PATH" | tr : '\n'

where.exe and type -a show all visible duplicates in search order. The first entry is executed. winget list and Windows Installed apps provide an additional list of registered installers, but they do not include manually extracted archives.

Install compiler versions side by side and test a selection in a fresh terminal before modifying the persistent user Path. For example, select Arm GNU Toolchain 15.3.Rel1 for only the current PowerShell session:

$savedPath = $env:Path
$env:Path = 'C:\bin\ArmGNUToolchain-15.3.Rel1\bin;' + $savedPath

where.exe arm-none-eabi-gcc
arm-none-eabi-gcc -dumpmachine
arm-none-eabi-gcc --version

# Restore the session when the test is complete.
$env:Path = $savedPath

The equivalent Git Bash commands are:

saved_path=$PATH
export PATH="/c/bin/ArmGNUToolchain-15.3.Rel1/bin:$saved_path"
hash -r

type -a arm-none-eabi-gcc
arm-none-eabi-gcc -dumpmachine
arm-none-eabi-gcc --version

# Restore the session when the test is complete.
export PATH="$saved_path"
hash -r

Use the same pattern for Clang or host GCC by prepending that version’s bin directory. Start from a fresh terminal for each comparison so that a path from the previous test cannot leak into the next one. Shell command caches are cleared by hash -r in Git Bash. Do not put several directories containing the same compiler command permanently in Path; their order otherwise becomes an implicit and easily missed version switch.

After selecting ARM GCC, test the known serial baseline and then the desired parallelism from the repository root:

MAKE_JOBS=-j1 ./scripts/_210_gcc_example_builds_all_workflows.sh
MAKE_JOBS=-j4 ./scripts/_210_gcc_example_builds_all_workflows.sh

Record type -a arm-none-eabi-gcc, both tool versions, MAKE_JOBS, and the temp/log/_5*_test_gcc_*.log files with every comparison. Windows exit code -1073741819 is 0xC0000005 (STATUS_ACCESS_VIOLATION): a compiler process crashed; it is not a normal C diagnostic. If -j1 succeeds but a bounded parallel build crashes, preserve the evidence and compare another complete official Arm GNU Toolchain before concluding that source code or job count is the root cause.

Remove an old compiler only after all of the following are true:

  1. A new terminal resolves every command to the intended replacement.
  2. The relevant ARM, CGO, and Clang tests pass with that replacement.
  3. Neither the user nor machine Path, a Makefile, clang.cfg, IDE setting, or debugger configuration refers to the old directory.
  4. The old package name, version, source URL, and checksum have been recorded so that the setup can be reproduced.

Use Installed apps or the package manager that installed a registered toolchain. For a manually extracted archive, first remove its Path entry, open a new terminal, repeat the inventory commands, and only then delete that one version directory. Keep at least one complete arm-none-eabi toolchain; ARM Clang needs its target headers and libraries. Keep one Windows host compiler when CGO tests require it. A 32-bit i686-w64-mingw32 WinLibs installation is normally unnecessary when Go and the required host builds are all 64-bit, but verify that no project depends on 32-bit output before removing it.

40.5.5. Setup STM32

<h5>Generate Base Project</h5>

<h5>Update NUCLEO Onboard Debugger (other ST evaluation boards too)</h5>

(https://www.st.com/en/development-tools/stsw-link007.html)

This step is recommended before re-flashing with the J-Link onboard debugger software.

(https://www.segger.com/products/debug-probes/j-link/models/other-j-links/st-link-on-board/)

Using the J-Link onboard debugger software allows parallel debugging and RTT usage.

Unfortunately this is not possible with v3 onboard debugger hardware! But you can use a J-Link hardware instead. Also it is possible to use a v2 onboard debugger from a different evaluation board or a “Bluepill” Development Board Module with ARM Cortex M3 processor”.

40.5.7. Setup VS-Code

40.6. Makefile with Clang too

40.7. Download Locations

40.7.1. Clang

https://releases.llvm.org/download.html -> https://github.com/llvm/llvm-project/releases/ (example)

The LLVM download supplies Clang and its builtin headers. It does not supply the target C runtime:

40.7.2. GCC

These downloads are not interchangeable. Confirm the target with -dumpmachine after selecting the compiler.

40.8. Install Locations

Do not use locations containing spaces, like C:\Program Files. Take C:\bin for example. This avoids trouble caused by spaces inside path names.

Keep roles and versions distinguishable. For example, use C:\bin\ArmGNUToolchain-15.3.Rel1 for arm-none-eabi-gcc, C:\bin\LLVM-22.1.6 for Clang, and C:\bin\WinLibs-GCC-16.1-x86_64 for a MinGW-w64 host compiler. A compiler executable in Path is not sufficient by itself; its matching standard headers, libraries, support programs, and DLLs must also remain in the same installation.

40.9. Environment Variables

Prepend only the currently selected compiler’s bin directory to Path. Prefer a temporary terminal selection while comparing versions. If the selection is made persistent, place it before other directories containing the same command and verify it from a new terminal with where.exe or type -a. See Inventory, select, and remove compiler versions.

The debugger path can be added independently, for example C:\Program Files\SEGGER\JLink or a versioned JLink_V... directory.

40.10. Build command

40.11. Run & Debug

40.12. Logging

trice l -p JLINK -args="-Device STM32G0B1RE -if SWD -Speed 4000 -RTTChannel 0" -pf none -ts ms -d16 (example)

40.13. Setting up a new project

(back to top)

40.14. Third-party packages and retained versions

The third_party directory stores optional transport tools, terminal software, vendor documentation, and reference source snapshots. It is not a list of mandatory installations. The example projects contain their required target sources, including configured RTT sources where used; building them does not require extracting these ZIPs. Select additional host tools only for the transport you intend to use. The packages below are stored versions, not a statement that they are current or compatible with every host.

Stored archive Contents and retained role
cobs-c-0.5.0.zip and cobs-c-version_1.0.zip Craig McQueen’s COBS/COBS-R source snapshots, both with LICENSE.txt and README.rst. They are comparison/reference sources, not the COBS files compiled from src. No active build extracts either version; the reason a manual user may still need both is unconfirmed, so both are retained.
cJSON-1.7.15.zip cJSON source snapshot with its MIT LICENSE and README. No active Trice build or structured-log output depends on this ZIP. Its original/manual use is unconfirmed; retaining it does not introduce a JSON dependency.
SEGGER_RTT_V812a.zip RTT target sources, configuration, examples, README, and LICENSE.md. It is a source reference for the 8.12a RTT files stored in src; the archive is not extracted by normal builds.
JLinkRTTLogger.zip Windows JLinkRTTLogger.exe and JLinkARM.dll, retained for the optional J-Link transport. No version manifest or license file is bundled in this ZIP; its exact version and redistribution provenance are unconfirmed.
STRTTLogger.zip Windows stRttLogger.exe and libusb-1.0.dll, retained for optional ST-Link RTT logging. The earlier repository notes identify phryniszak/strtt and gostlink as related sources. The ZIP has no license or version manifest; that relationship does not verify the exact binary build.
STLinkReflash_190812.zip Windows STLinkReflash.exe and JLinkARM.dll for the documented onboard ST-Link/J-Link conversion. Retained as a dated vendor utility; the ZIP has no license or version manifest.
en.stsw-link007_V2-37-26.zip ST-Link firmware upgrade package with Windows and Java/native platform tools. Its README lists V2J37S7/V2J37M26 and STLINK-V3 V3J7M2 firmware. Retained for the documented upgrade/conversion setup.
stsw-link007.zip Earlier upgrade package whose README lists V2J24S4/V2J24M11 firmware and older host prerequisites. It is a distinct legacy snapshot, not a duplicate of the V2-37-26 package. The continuing need for that old version is unconfirmed, so it is retained without recommending it as the default.
en.stsw-link009_v2.0.2.zip Stored Windows USB driver package. Its README identifies Windows 7/8/10 and 32/64-bit support. It is an optional driver reference, not a verified claim of support for newer Windows versions.
Alacritty.zip A single Windows Alacritty.exe. Earlier repository notes identify it as the renamed Alacritty-v0.7.2-portable.exe from Alacritty. No version/license manifest is bundled; the exact binary provenance is unconfirmed. It is an optional ANSI-capable terminal, not a Trice build dependency.

For automatic RTT capture, the host tool resolves JLinkRTTLogger or stRttLogger through PATH; it does not automatically unpack or locate these ZIPs under third_party. For example, on Windows, extract a selected logger and its accompanying DLL into the same directory and add that directory to PATH. Installing the appropriate vendor package is another way to provide the logger. See Trice over RTT for the capture workflow and onboard probe conversion for the device-specific setup. No probe firmware is changed by a Trice build.

The stored J-Link manual and online-manual snapshot are offline vendor references. Their chapter numbers and platform details belong to those copies. The J-Link download page, RTT page, and ST website provide the original vendor context.

Preserve the copyright and license notices supplied with source snapshots; the Trice MIT license does not replace third-party terms. Replacing RTT sources in src or an example is a separate, reviewed vendor update, including its configuration and target validation. An archive’s age or lack of an active build reference alone does not authorize its removal. No archive contents, vendor sources, or Drivers/Middlewares directories are changed by this inventory.

41. Example Projects without and with Trice Instrumentation

Project Name Description
   
F030_bare This is a minimal STM32CubeMX generated Makefile project adapted to Clang and GCC. It serves as a reference for diff to F030_inst so see quickly the needed instrumentation steps you need for your own project.
F030_inst This is a minimal STM32CubeMX generated Makefile project adapted to Clang and GCC and afterward instrumented with the Trice library. Compare it with F030_bare to see quickly how to instrument your project.
   
G0B1_bare This is a minimal FreeRTOS STM32CubeMX generated Makefile project adapted to Clang and GCC.
G0B1_inst This is a minimal FreeRTOS STM32CubeMX generated Makefile project adapted to Clang and GCC and afterward instrumented with the Trice library.
PC_features Small PC capture with structured fields, CE, tags, runtime strings, timestamps, and text/JSON/KV decoder scripts.
G0B1_features A copy of G0B1_inst showing CE task handles from two FreeRTOS tasks and matching decoder scripts.
   
L432_bare This is a minimal FreeRTOS STM32CubeMX generated Makefile project extended to compile also with Clang trying to perform minimal changes. It produces some warnings, because it is not finetuned. The L432_inst project is then a next step performable.
L432_inst This is a minimal FreeRTOS STM32CubeMX generated Makefile project adapted to Clang and GCC and afterward instrumented with the Trice library.
   

(back to top)

41.1. Minimal PC Demos: Direct and Deferred

The two programs under demo use the same binary output channel in two modes. direct writes each record immediately to build/log.bin; deferred first stores records in a ring buffer and drains it through TriceTransfer(). Both compile the repository’s src directly, without copying a library or requiring a separate build system.

Put trice and a C compiler named cc or gcc in PATH, then use a POSIX shell (Git Bash on Windows):

cd demo
LC_ALL=C sh ./demo.sh

The script binds both programs once, builds and runs deferred followed by direct, and decodes both captures using trice log -p FILEBUFFER. Calling it through sh works even though its versioned file has no executable bit; alternatively, use chmod +x demo.sh before ./demo.sh. LC_ALL=C makes the source glob’s lowercase selection predictable. tlog is not required. The optional prerequisite checks near the start of the script can be enabled; the script installs nothing. On Windows the executables receive the .exe suffix automatically.

Ignoring optional location/prefix columns, the messages are:

Hello from deferred mode.
Deferred value=42.
Hello from direct mode.
Direct value=42.

The layout separates project data from generated outputs:

demo/til.json, demo/li.json   shared, persistent project ID/location tables
demo/generated/              generated sidecars and field registry
demo/deferred/main.c          deferred application
demo/deferred/triceConfig.h   deferred configuration
demo/deferred/build/          executable and log.bin
demo/direct/main.c            direct application
demo/direct/triceConfig.h     direct configuration
demo/direct/build/            executable and log.bin

Binding uses the defaults til.json, li.json, and generated relative to demo. On the first bind, a missing generated #include "trice_main_c_K...h" is inserted automatically; users neither invent nor maintain its name. The compiler’s ../src/[a-z]*.c glob is intended to exclude the uppercase vendor source SEGGER_RTT.c, so these demos need no RTT configuration. Inspect the selected source list if a locale causes that glob to include the vendor file.

Compare direct/main.c and deferred/main.c: the latter explicitly transfers until its ring buffer is empty. Change the value 42, rerun the script, and compare the two decoded logs. The shared workflow is maintained only in demo.sh.

41.2. PC Feature Tour

The PC program emits a short capture.bin for the normal host decoder. It groups Structured Logging, a runtime string, Context Enrichment (CE), tags, an untagged message, a buffer record, and both target-stamp widths in one editable application.

With trice and cc or gcc in PATH, run:

cd examples/PC_features
./build_and_run.sh
./show_text.sh
./show_json.sh
./show_kv.sh
./check_output.sh

The build script binds local IDs and applies -ce 'ctx:", cycle={cycle:%u}", pc_sample_phase'. The shared emit_sample call therefore gains a cycle field without editing its Supply {voltage_mv:%u} format. The device name uses TriceS because CE does not append runtime arguments to string Trices. til.json and li.json are versioned project tables; the capture, executable, and generated headers are build outputs. Rebuild after changing the source or CE rule.

Feature Source to edit Observable result
Numeric fields and runtime string emit_sample and the device-name TriceS JSON fields.voltage_mv and fields.device; the device is pump A.
CE at one shared call site info:ctx: and pc_sample_phase Supply readings contain cycle=7 and cycle=11.
Built-in and custom tags wrn:, dbg:, sensor: Warning threshold filters events; sensor has weight 450 in the show scripts.
Two stamp widths TRice16 for Phase, Trice8 for Humidity, and TRice32 for Supply Phase has a 32-bit stamp; Humidity has a 16-bit stamp.
Stamp delta Two Supply calls Second 32-bit stamp is 125 ms, with a 25 ms delta.
Untagged message and buffer Last calls in main Message A message without a tag, metadata tag untagged, and bytes 41 00 ff .

The text, JSON, and KV scripts append your extra arguments to their trice log command:

./show_json.sh -logLevel wrn
./show_text.sh -pick info
./show_json.sh -ulabel sensor:650 -logLevel wrn
./show_text.sh -tagStat

The first retains Warning and higher weights; the third raises sensor so it also passes that threshold. JSON produces one object per event (NDJSON). Tag statistics count decoded groups, including events hidden by filters. For a complete macro/format corpus see triceCheck.c; for live plotting see the data producers, and for local formatting see the local-log examples.

41.2.1. Updating the PC Tour’s Output Checks

check_output.sh checks concrete values from main.c, the CE rule, and the show-script options. After editing any of those, rebuild, inspect JSON and KV output, update the corresponding shell case pattern, and run the check again. Keep each check tied to an observable result. If a feature is removed, deliberately replace or remove its assertion rather than leaving a commented-out check and a misleading PASS message.

Macro capitalization chooses stamp width: trice... has no target stamp, Trice... has 16 bits, and TRice... has 32 bits. Changing Trice16(...) to TRice16(...) changes the stamp, not the 16-bit payload value. The -ts16 and -ts32 options change display only. In this tour the 16-bit stamp represents a sample phase; the 32-bit stamp counts milliseconds. Adding stamped events can change subsequent ts16Delta or ts32Delta expectations. JSON displays a source newline as the two characters \n, which shell patterns must match literally.

41.3. G0B1 Feature Tour

G0B1_features is a direct copy of G0B1_inst with a short tour in its two existing FreeRTOS tasks. The original hardware configuration remains in place; the large TriceCheck loop is omitted to make task records easy to find. Its companion is the hardware-free PC feature tour.

With trice, GNU Make, and the Arm GNU toolchain in PATH:

cd examples/G0B1_features
./demo_build.sh
./check_build.sh

The build script binds this copy and its shared exampleData producers into a private til.json, applying -ce 'ctx:", task={task:%p}", osThreadGetId()'. The call in LogFeatureSample executes from both tasks, so the records have different task handles at the same C call site. The neighboring triceS transports the worker name. Edit Core/Src/main.c to experiment with the named sample and load_pct fields, 16-/32-bit stamps, Warning, untagged text, buffer output, and the custom sensor: tag.

check_build.sh verifies the generated task adapter and the string, field, stamp, tag, and buffer entries; it needs no board. It is a compiler/build check, not evidence that firmware ran on an MCU.

Flash out.gcc/G0B1.elf using the original board setup. In a separate terminal capture RTT channel 0 with J-Link:

mkdir -p temp
JLinkRTTLogger -Device STM32G0B1RE -If SWD -Speed 4000 -RTTChannel 0 temp/trice.bin

Stop the logger once startup records have arrived, then decode the saved capture:

./show_text.sh
./show_json.sh
./show_kv.sh
./show_json.sh -pick info
./show_kv.sh -logLevel wrn

The text, JSON, and KV decoder scripts expect this project’s til.json and accept extra trice log arguments. They use 16-bit stamps as microseconds and 32-bit stamps as milliseconds; JSON is NDJSON. The custom tag has weight 450. Rebuild and recapture after editing calls or CE rules. A board and J-Link are required for capture, but an existing temp/trice.bin can be decoded without hardware.

41.4. Local Logging Example Projects

These applications demonstrate local deferred text logging: producers remain binary and short; one background consumer formats records on the target. TriceLog() and TriceTransfer() must never consume the same deferred buffer together.

41.4.1. PC Local Logging

The PC application uses a ring buffer, system snprintf, and standard output. With trice and cc or gcc in PATH:

cd examples/PC_log
./build_and_run.sh

The script binds the application and shared triceCheck.c corpus, generates build/til.c with trice generate -logC, compiles against ../../src, and runs the result. Sidecars are in generated; the table and executable are in build. No serial connection, RTT/J-Link installation, or host decoder is needed. A startup sequence shows integers, a runtime %s, string width/precision, aFloat(), aDouble(), Trice-specific conversions, and a buffer; the shared corpus then runs line by line.

The explicit switches in triceConfig.h are a readable full-feature configuration. Command/RPC and selector-0 cases are disabled only for local logging; the two host-only dynamic-string byte-dump forms are likewise guarded only by TRICE_LOCAL_LOG, preserving ordinary corpus users.

41.4.2. G0B1 FreeRTOS Local Logging

The independent G0B1_log copy retains the original CubeMX setup, task names, priorities, and stack sizes. With trice and the Arm GNU toolchain in PATH:

cd examples/G0B1_log
./build.sh

The build script binds the application and shared corpus, generates build/til.c, and builds out.gcc/G0B1_log.elf; Bind sidecars remain in generated. The default task executes the corpus. The idle diagnostics task StartTask02 alone calls TriceLog() with nanoprintf and may block while transmitting already formatted text:

tasks and interrupts -> binary Trice ring buffer
                    -> idle StartTask02 -> TriceLog + nanoprintf
                    -> USART2 text at 115200 baud

Producer contexts neither call printf nor wait for USART2. Connect the USART2 virtual COM port to a serial terminal at 115200 baud. At runtime no trice log, TIL file, or binary host decoder is needed. The startup feature set matches the PC local-log example, including runtime strings, bounded string formatting, float/double, special conversions, and a buffer.

Both configurations enable ANSI colors and strip recognized all-lower-case tags independently. Set TRICE_LOCAL_LOG_USE_ANSI_COLORS to 0 for plain redirected text; an ANSI-capable terminal is required to display colors. TRICE_LOCAL_LOG_STRIP_LOWER_CASE_TAGS separately controls retaining tags. Floating-point nanoprintf support increases target code size; integer/string-only applications can disable both the corresponding nanoprintf options and Trice local-log options. See configuration switches and formatter hooks and the local-log integration tests for their behavior and limits.

41.5. Shared Example Producers

The C files under examples/exampleData are shared producer sources used by several installed examples; this is not a standalone application. A Bind scan can generate sidecars for included shared producers even when an application does not invoke their demo functions at runtime. The large triceCheck.c corpus is separate and supplies the PC target tests and installed local-log examples.

41.6. Nucleo-F030R8 Examples

41.6.1. F030_bare

Folder: ../examples/F030_bare/

This is a STMCubeMX generated project without Trice instrumentation for easy compare with F030_inst to figure out the needed changes to set up trice.

Steps performed as potential guide:
PS E:\repos\trice\examples\F030_bare> make -j
mkdir build
arm-none-eabi-gcc -c -mcpu=cortex-m0 -mthumb   -DUSE_FULL_LL_DRIVER -DHSE_VALUE=8000000 -DHSE_STARTUP_TIMEOUT=100 -DLSE_STARTUP_TIMEOUT=5000 -DLSE_VALUE=32768 -DHSI_VALUE=8000000 -DLSI_VALUE=40000 -DVDD_VALUE=3300 -DPREFETCH_ENABLE=1 -DINSTRUCTION_CACHE_ENABLE=0 -DDATA_CACHE_ENABLE=0 -DSTM32F030x8 -ICore/Inc -IDrivers/STM32F0xx_HAL_Driver/Inc -IDrivers/CMSIS/Device/ST/STM32F0xx/Include -IDrivers/CMSIS/Include -Og -Wall -fdata-sections -ffunction-sections -g -gdwarf-2 -MMD -MP -MF"build/main.d" -Wa,-a,-ad,-alms=build/main.lst Core/Src/main.c -o build/main.o

...

arm-none-eabi-gcc -x assembler-with-cpp -c -mcpu=cortex-m0 -mthumb   -DUSE_FULL_LL_DRIVER -DHSE_VALUE=8000000 -DHSE_STARTUP_TIMEOUT=100 -DLSE_STARTUP_TIMEOUT=5000 -DLSE_VALUE=32768 -DHSI_VALUE=8000000 -DLSI_VALUE=40000 -DVDD_VALUE=3300 -DPREFETCH_ENABLE=1 -DINSTRUCTION_CACHE_ENABLE=0 -DDATA_CACHE_ENABLE=0 -DSTM32F030x8 -ICore/Inc -IDrivers/STM32F0xx_HAL_Driver/Inc -IDrivers/CMSIS/Device/ST/STM32F0xx/Include -IDrivers/CMSIS/Include -Og -Wall -fdata-sections -ffunction-sections -g -gdwarf-2 -MMD -MP -MF"build/startup_stm32F030x8.d" startup_stm32F030x8.s -o build/startup_stm32F030x8.o
arm-none-eabi-gcc build/main.o build/stm32f0xx_it.o build/stm32f0xx_ll_gpio.o build/stm32f0xx_ll_pwr.o build/stm32f0xx_ll_exti.o build/stm32f0xx_ll_usart.o build/stm32f0xx_ll_rcc.o build/stm32f0xx_ll_dma.o build/stm32f0xx_ll_utils.o build/system_stm32f0xx.o build/sysmem.o build/syscalls.o build/startup_stm32F030x8.o  -mcpu=cortex-m0 -mthumb   -specs=nano.specs -TSTM32F030R8Tx_FLASH.ld  -lc -lm -lnosys  -Wl,-Map=build/F030_bare.map,--cref -Wl,--gc-sections -o build/F030_bare.elf
C:/bin/ArmGNUToolchain/bin/../lib/gcc/arm-none-eabi/13.2.1/../../../../arm-none-eabi/bin/ld.exe: warning: build/F030_bare.elf has a LOAD segment with RWX permissions
arm-none-eabi-size build/F030_bare.elf
   text    data     bss     dec     hex filename
   2428      12    1564    4004     fa4 build/F030_bare.elf
arm-none-eabi-objcopy -O ihex build/F030_bare.elf build/F030_bare.hex
arm-none-eabi-objcopy -O binary -S build/F030_bare.elf build/F030_bare.bin
PS E:\repos\trice\examples\F030_bare>
{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Cortex Debug",
            "cwd": "${workspaceFolder}",
            "executable": "./build/F030_bare.elf",
            "request": "launch",
            "type": "cortex-debug",
            "runToEntryPoint": "main",
            "servertype": "jlink",
            "device": "STM32F030R8",
            "svdFile": "./STM32F030R8.svd",
            "runToMain": true

        }
    ]
}
Hint

41.6.2. F030_inst

Folder: ../examples/F030_inst/

This is a working example with deferred encrypted out over UART. By uncommenting 2 lines in triceConfig.h, you get also parallel direct out over RTT. For setup see Trice over RTT and adapt steps from F030_bare.

Intrumenting:

(back to top)

41.7. Nucleo-G0B1 Examples

41.7.1. G0B1_bare

Folder: ../examples/G0B1_bare/

<h5>G0B1_bare Description</h5>

<h5>Setting Up G0B1_bare</h5>

41.7.2. G0B1_inst

Folder: ../examples/G0B1_inst/

This is an example with direct out without framing over RTT and deferred out in TCOBS framing over UART.

<h5>Setting Up</h5>

<h5>Instrumenting</h5>

(back to top)

41.8. Nucleo-L432KC Examples

41.8.1. L432_bare

Folder: ../examples/L432_bare/

41.8.2. L432_inst

Folder: ../examples/L432_inst/

Build:

Run ./build.sh for configuration 0 or ./build.sh CONFIGURATION=34 for example.

Deferred Mode for max Speed

The stamps are MCU clocks here, so 🐁 Speedy Gonzales lasts 9 processor clocks here.

ms@DESKTOP-7POEGPB MINGW64 ~/repos/trice_wt_devel/examples/L432_inst (devel)
$ trice l -p com8 -hs off -prefix off
      triceExamples.c    10        0_272  Hello! 👋🙂

        ✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨
        🎈🎈🎈🎈  𝕹𝖀𝕮𝕷𝕰𝕺-L432KC   🎈🎈🎈🎈
        🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃


        triceConfig.h   369              CONFIGURATION == 34 - UART, no cycle counter, no critical sections.
      triceExamples.c    45              TRICE_DIRECT_OUTPUT == 0, TRICE_DEFERRED_OUTPUT == 1
      triceExamples.c    51              TRICE_DOUBLE_BUFFER, TRICE_MULTI_PACK_MODE
      triceExamples.c    60              _CYCLE == 0, _PROTECT == 0, _DIAG == 0, XTEA == 0
      triceExamples.c    61              _SINGLE_MAX_SIZE=512, _BUFFER_SIZE=580, _DEFERRED_BUFFER_SIZE=4096
      triceExamples.c    15    0,000_731 🐁 Speedy Gonzales
      triceExamples.c    16    0,000_745 🐁 Speedy Gonzales
      triceExamples.c    17    0,000_754 🐁 Speedy Gonzales
      triceExamples.c    18    0,000_763 🐁 Speedy Gonzales
      triceExamples.c    19    0,000_772 🐁 Speedy Gonzales
      triceExamples.c    20    0,000_781 🐁 Speedy Gonzales
      triceExamples.c    21    0,000_790 🐁 Speedy Gonzales
      triceExamples.c    22    0,000_799 🐁 Speedy Gonzales
      triceExamples.c    24        0_981 2.71828182845904523536 <- float number as string
      triceExamples.c    25        1_230 2.71828182845904509080 (double with more ciphers than precision)
      triceExamples.c    26        1_268 2.71828174591064453125 (float  with more ciphers than precision)
      triceExamples.c    27        1_296 2.718282 (default rounded float)
      triceExamples.c    28        1_310 A Buffer:
      triceExamples.c    29        1_348 32 2e 37 31 38 32 38 31 38 32 38 34 35 39 30 34 35 32 33 35 33 36
      triceExamples.c    30        1_603 31372e32  31383238  34383238  34303935  35333235
      triceExamples.c    31        1_799 ARemoteFunctionName(2e32)(3137)(3238)(3138)(3238)(3438)(3935)(3430)(3235)(3533)(3633)
      triceExamples.c    32              10 times a 16 byte long Trice messages, which not all will be written because of the TRICE_PROTECT:
      triceExamples.c    34        2_072 i=44444400 aaaaaa00
      triceExamples.c    34        2_119 i=44444401 aaaaaa01
      triceExamples.c    34        2_166 i=44444402 aaaaaa02

<h5>“Hardware” Changes</h5>

<h5>Using RTT with on-board J-Link and JLinkRTTLogger</h5>

<h5>Using RTT with on-board J-Link and OpenOCD</h5>

<h6>With Windows not possible</h6>

<h6>Darwin (macOS)</h6>

<h5>Using RTT with on-board ST-Link and OpenOCD</h5>

Terminal 1:

ms@LenovoP51Win11 MINGW64 /e/repos/trice/examples/L432_inst (devel)
$ openocd -f STLinkOpenOCD.cfg
Open On-Chip Debugger 0.12.0 (2024-09-16) [https://github.com/sysprogs/openocd]
Licensed under GNU GPL v2
libusb1 d52e355daa09f17ce64819122cb067b8a2ee0d4b
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
Info : clock speed 100 kHz
Info : STLINK V2J24M11 (API v2) VID:PID 0483:374B
Info : Target voltage: 72.811768
Info : [stm32l4x.cpu] Cortex-M4 r0p1 processor detected
Info : [stm32l4x.cpu] target has 6 breakpoints, 4 watchpoints
Info : [stm32l4x.cpu] Examination succeed
Info : [stm32l4x.cpu] starting gdb server on 3333
Info : Listening on port 3333 for gdb connections
Info : rtt: Searching for control block 'SEGGER RTT'
Info : rtt: Control block found at 0x2000145c
Info : Listening on port 9090 for rtt connections
Channels: up=1, down=3
Up-channels:
0: Terminal 1024 0
Down-channels:
0: Terminal 16 0
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections

Terminal2:

ms@LenovoP51Win11 MINGW64 /e/repos/trice/examples/L432_inst (devel)
$ trice l -p TCP4 -args localhost:9090  -pf none -d16
Nov 16 20:38:12.376056  TCP4:       triceExamples.c    10        1_595  Hello! 👋🙂
Nov 16 20:38:12.376056  TCP4:
Nov 16 20:38:12.376056  TCP4:         ✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨
Nov 16 20:38:12.376056  TCP4:         🎈🎈🎈🎈  𝕹𝖀𝕮𝕷𝕰𝕺-L432KC   🎈🎈🎈🎈
Nov 16 20:38:12.376056  TCP4:         🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃🍃
Nov 16 20:38:12.376056  TCP4:
Nov 16 20:38:12.376056  TCP4:
Nov 16 20:38:13.891033  TCP4:       triceExamples.c    16       43_439 2.71828182845904523536 <- float number as string
Nov 16 20:38:14.874024  TCP4:       triceExamples.c    17       44_949 2.71828182845904509080 (double with more ciphers than precision)
Nov 16 20:38:15.692614  TCP4:       triceExamples.c    18       45_802 2.71828174591064453125 (float  with more ciphers than precision)
Nov 16 20:38:16.323665  TCP4:       triceExamples.c    19       46_536 2.718282 (default rounded float)

<h5>Using On-board ST-Link and VS-Code Cortex-Debug Extension</h5>

<h6>Fail</h6>

<h6>OK</h6>

(back to top)

42. Trice Generate

For a compact, readable copy of the ID dictionaries, run trice generate -onelineJSON -til til.json -li li.json. This writes til.oneline.json and li.oneline.json in -genDir (default ./generated) as complete JSON objects with one ID entry per line. In the LI copy, each entry shows Line before File. The original files remain authoritative and unchanged; rerun the command after updating them. Use -li off to export only the TIL copy. Missing or invalid requested input files cause an error without replacing either copy. This option cannot be combined with -logC or -abc.

42.1. Colors

Support for finding a color style:

generateColors.PNG

See Check Alternatives chapter.

42.2. C-Code

To generate a compact C metadata table for current target-side Trice sites, first run trice insert or trice bind and then run trice generate -src <source> -logC[=<output.c>]. Multiple -src options are accepted. Explicit Insert IDs and numeric Bind sidecar descriptors are validated against the selected TIL; no ID is guessed from a matching format string. Historical TIL entries that are absent from the selected sources are omitted without changing the TIL itself. Bind sidecars are read from ./generated by default; specify -genDir for a different directory. Bare -logC writes ./generated/til.c; an explicit output path takes precedence. -logC and -abc are alternative generation modes and cannot be combined.

Commented Trice calls with explicit Insert IDs remain selectable. An ID-free call that exists only in a C comment has no Bind preprocessor site and therefore no exact sidecar ID; -logC reports it instead of guessing or silently omitting it. Use trice insert for such retained commented calls, give the commented example an explicit authoritative ID, or exclude that source from this generated table.

// SPDX-License-Identifier: MIT

#include "triceRx.h"

const triceLog_t triceLog[] = {
	/* Trice type ( extended ) */ /*   id, bitWidth, paramCount, format-string */
	/* trice      ( trice_0   ) */ { 1000u, 32u, 0u, "ready\n" },
	/* trice      ( trice32_2 ) */ { 1001u, 32u, 2u, "value=%d hex=%x\n" },
};

const unsigned triceLogElements = sizeof(triceLog) / sizeof(triceLog[0]);

42.3. C#-Code

The current trice generate command does not provide a C# source generator. C# applications can read the generated til.json as input to their own decoder or use the Trice host tool to produce text, JSON, or KV output.

42.4. Generating a Trice ABC Function Pointer List

Use -abc=<target> to generate the target-specific ABC receive selection and table files:

trice generate -i til.json -abc=deviceX

Run from the project directory. This creates generated/deviceX.h if it does not exist, otherwise uses it as the user-edited selection input. It always regenerates generated/deviceX.c from til.json and the active declarations in generated/deviceX.h. -genDir changes the generated directory; an explicit target path such as -abc=custom/deviceX keeps that path. For the workflow and examples see Trice ABC - Asynchronous Broadcast Commands.

(back to top)

43. Testing the Trice Library C-Code for the Target

43.1. General info

This folder is per default named to _test to avoid VS Code slow down. Also, when running go test ./..., the tests in the _test folder are excluded, because they take a long time. Run ./scripts/testAll.sh to include them.

The main aim of these tests is to automatic compile and run the target code in different compiler switch variants avoiding manual testing this way.

scripts/testAll.sh quick performs the standard Bind compiler matrices and focused CE/SL checks for both Bind and Insert/Clean. scripts/testAll.sh full also runs the complete legacy Insert/Clean and extended compiler matrices; its duration depends strongly on the host. The runner orders short checks before long matrices and shows a hardware-independent percentage of expected relative test work. On an interactive terminal, a spinner changes in place every few seconds during a long step; it does not add repeated log lines or claim a time-based ETA.

Each result line shows the elapsed time for that script before its name, with right-aligned minutes and seconds: [24/26 | ~ 76.7%] ( 4m 30s) _620_test_l432_configs.sh: PASS. The format is (%3dm%3ds); four hours appear as (240m 0s). This measures elapsed time including any preparation and restoration performed by the script, not accumulated CPU time or time since the entire suite started. The same column appears for WARN, FAIL and ABORTED, and is saved in temp/log/testAll_summary.log. The interactive spinner updates the current script’s elapsed time in place.

Both selections include step 515, which runs the existing compiler-to-decoder CE/SL checks and the feature examples:

Check Evidence
TestContextEnrichmentTargetToDecoder and TestContextInsertCleanTargetToDecoder Real C/C++ records, text/JSON/KV output, structured fields, stamps, reversible Insert/Clean, disabled logging and exactly-once argument evaluation.
TestContextEnrichmentPoC and TestContextEnrichmentPoCRebaseScopeBoundary Retained proofs for direct callsites and the scope limitation of the existing Rebase dispatcher.
PC feature example Runtime string, fields, stamps/deltas, CE, tag filtering and KV output from the built example.
G0B1 feature example Firmware build, task-context adapter, runtime-string and structured-field metadata, nonempty ELF/HEX/BIN artifacts. This does not execute the firmware on an MCU.

The compiler/decoder checks require Go, Clang, Clang++ and clangd. The examples require the current repository’s trice tool, Git and tar; the PC example needs cc or gcc, and G0B1 needs Make and ARM GNU GCC/objcopy/size with its bare-metal libraries. In quick, an unavailable group is explicitly skipped and the runner shows WARN. In full, missing tools make this step fail. A selected Go test that reports SKIP, or fails to report its named PASS, is also an error. This prevents an empty test selection from appearing to validate the features. The shared Library CI workflow invokes this same step in full mode.

The example checks run in a fresh copy under temp/log/logging-features.*. It contains the current bytes of tracked sources, including uncommitted source edits, and the same relative layout as the checkout. Existing captures, generated directories and object files are not reused or modified. Successful copies are removed; failed copies remain for inspection. Details are written to temp/log/_515_test_logging_features.log. Run just this acceptance step from the repository root with:

./scripts/_480_test_build_trice_tool.sh
./scripts/_515_test_logging_features.sh full

The larger experimental CE Rebase/Wrapper proof remains a separate, explicit investigation. It does not establish productive support for those constructs. To rerun it with the locally available compiler variants:

TRICE_BIND_INTEGRATION=1 go test ./internal/id -run '^TestContextEnrichmentRebasePoC$' -count=1 -v

It reports unavailable compiler variants and optional clangd evidence; missing variants are not platform acceptance. Ordinary Go unit/coverage runs continue to cover SL parser/renderer behavior; step 515 selects only the additional compiler checks instead of repeating those suites.

The PC matrix uses four independent configuration processes by default. Set TRICE_PC_TEST_JOBS=1 for serial execution or choose another positive limit. ID preparation and source restoration remain sequential. A failing configuration stops at its first mismatch. testAll.sh continues with the remaining configurations and test steps by default (--no-stop), but the final result remains FAIL if any check failed. Use --stop to stop after the first failure; cancellation always stops the run. Failure summaries include source references, expected/actual output and the detailed log path.

For example, from the repository root:

TRICE_PC_TEST_JOBS=4 ./scripts/_640_test_pc_targets_bind.sh full
TRICE_PC_TEST_JOBS=1 TRICE_PC_TEST_MODE=line-by-line ./scripts/_630_test_pc_targets_insert.sh full

The second command explicitly selects the diagnostic single-expectation path. Normal runs use TRICE_PC_TEST_MODE=auto: bulk where packet boundaries are preserved, single-expectation decoding for unframed or special configurations. All expectations remain enabled.

The L432 matrix builds all 101 configurations (CONFIGURATION=0 through 100). It prepares the shared Bind state once and builds configurations concurrently, each with make -j1. The default concurrency follows the online CPU count; on Windows it uses the bounded budget from the shared build setup, which prefers physical cores. If detection fails, the matrix uses four jobs. Set TRICE_L432_TEST_JOBS=1 for serial execution or choose another positive limit; this limits the total number of simultaneous compiler/linker commands, even when MAKE_JOBS normally requests unlimited parallelism. For example, to limit a run to four jobs from the repository root:

TRICE_L432_TEST_JOBS=4 ./scripts/_620_test_l432_configs.sh

Every invocation uses fresh, separate object directories for every configuration. All code-generation options, source files and ELF/HEX/BIN targets remain enabled; there is no reuse of potentially stale objects after a header, configuration, workflow or compiler change. The matrix skips the expensive assembler .lst text listings (GCC_LISTINGS=0); compiler warnings and errors remain enabled. Ordinary build.sh CONFIGURATION=N builds still generate listings for manual inspection. Existing examples/L432_inst/out.gcc builds remain untouched by the matrix. Full compiler logs stay in temp/log/l432.*/config-N.log. Successful temporary build outputs are removed to save disk space; failed or interrupted outputs are retained beside their logs.

The matrix reports each configuration’s result, prints compiler error excerpts and gives the command to reproduce a failure. Under testAll.sh, the selected --no-stop or --stop policy applies. A directly invoked L432 matrix stops after a failed batch by default; use TRICE_TEST_NO_STOP=1 to finish the remaining configurations. Already started jobs finish before source restoration. Cancellation terminates the compiler processes too and prevents further configurations from starting. The managed wrapper above restores the initial source and metadata state on success, failure and cancellation; a direct examples/L432_inst/all_configs_build.sh invocation performs the same Bind preparation as build.sh and leaves sources in Bind state.

For the user it could be helpful to start with a triceConfig.hfile from here and to adapt the Trice tool command line from the matching cgo_test.go if no close match in the examples folder was found.

43.2. How to run the tests

43.3. Tests Details

All folders despite testdata are test folders and the name tf is used as a place holder for them in this document.

To exclude a specific folder temporary, simply rename it to start with an underscore _tf.

The tf are serving for target code testing in different configuration variants on the host machine. The file ./testdata/triceCheck.c is the main file for most tests and serves also as example usage.

_test/testdata/cgoPackage.go is the common main for the generated_cgoPackage.go files and contains the common test code.

The folders tf are Go packages just for tests. They all have the same package name cgot and are not included into the trice tool. The different cgot packages are independent and could have any names. They do not see each other and are used for target code testing independently. When the tests are executed for each package, a separate test binary is build and these run parallel.

The tf/triceConfig.h files differ and correspondent to the tf/cgo_test.go files in the same folder. On test execution, the ./testdata/*.c files are compiled into the trice test executable together with the trice sources ../src using the tf/triceConfig.h file.

The individual tests collect the expected results (//exp: result) together with the line numbers into a slice to execute the test loop on it. The triceLogTest function gets the triceLog function as parameter.

triceLogTest iterates over the results slice and calls for each line the C-function triceCheck. Then the line specific binary data buffer is passed to the triceLog parameter function which “logs” the passed buffer into an actual result string which in turn is compared with the expected result.

The bulk path still executes each C test site. It collects binary output and starts the host logger once per output channel, then compares every expected text range with its source line. This avoids repeated logger initialization. Finite replay inputs finish when their buffered records are drained; they do not wait for a fixed timeout after each expectation.

Framed direct and deferred channels are collected separately. Configurations that previously transferred after every test site retain that transfer schedule, so small target buffers cannot overflow merely because host decoding is batched. Existing deferred bulk tests retain their multi-site transfer schedule to exercise buffering. Unframed configurations keep the single-expectation path because concatenation can lose packet boundaries or change padding interpretation. Successful bulk runs are not repeated completely line by line.

Each configuration has its own output.log below temp/log/pc-<workflow>.<run>/. On a bulk mismatch, the original binary and text output are saved there, and the worker reruns that configuration line by line. A passing diagnostic rerun does not clear the bulk failure: it points to an interaction involving framing, buffering or state. The reported source line is the first divergent expectation, which may follow the actual cause. Expected and actual strings show escaped control characters; nearby text helps identify shifts. The failure report also gives a reproduction command, which requires the same prepared ID state and compiler include paths. Run directories are retained for diagnosis; subsequent runs use a new directory.

The testdata\cgoPackage.go file contains a variable testLines = n, which limits the amount of performed trices for each test case to n. Changing this value will heavily influence the test duration. The value -1 is reserved for testing all test lines.

43.4. How to add new test cases

43.5. Test Internals

The ./trice/_test/testdata/*.c and ./trice/src/*.c are compiled together with the actual cgot package into one single Trice test binary, resulting in as many test binaries as there are test folders. Calling its TeCEFunction(s) causes the activation of the Trice statement(s) inside triceCheck.c. The ususally into an embedded device compiled Trice code generates a few bytes according to the configuration into a buffer. These bytes are transmitted usually in real life over a (serial) port or RTT. In the tests here, this buffer is then read out by the Trice tool handler function according to the used CLI switches and processed to a log string using the til.json file. This string is then compared to the expected string for the activated line.

Each tf is a Go package, which is not part of any Go application. They are all named cgot and are only used independently for testing different configurations. The tf/generated_cgoPackage.go file is identical in all tf. Its master is _test/testdata/cgoPackage.go. The test worker overlays the master during normal test runs. The maintenance script ./scripts/_330_renew_ids_and_refresh_tests.sh copies the master into the listed packages, but also renews IDs and clears ID history by default; use keepHistory only when you deliberately run that wider maintenance workflow.

The test specific target code configuration is inside tf/trice.Config.h and the appropriate Trice tool CLI switches are in tf/cgo_test.go.

When running go test ./tf, a Trice tool test executable is build, using the Trice tool packages and the tf package cgot, and the function TestLogs is executed. Its internal closure triceLog contains the Trice tool CLI switches and is passed to the ccgot package function triceLogTest together with the number of testLines and the trice mode (directTransfer or deferrerdTransfer).

During the test, the file triceCheck.c is scanned for lines like

break; case __LINE__: TRice( iD(3537), "info:This is a message without values and a 32-bit stamp.\n" ); //exp: time: 842,150_450default: info:This is a message without values and a 32-bit stamp.

Some C-code lines contain Trice statements and comments starting with //exp: followed by the expected Trice tool output for that specific line. The Go teCEFunction collects these outputs in a slice together with the line numbers. Then for each found line number the execution of the Go function func triceCheck(n int) takes part, which in turn calls the CGO compiled C-function TriceCheck(n). The now activated Trice C-code writes the generated trice bytes in a between C and Go shared buffer using the C-function TriceWriteDeviceCgo. After returning from the Go function func triceCheck(n int) and optionally calling TriceTransfer in deferred mode the Trice tool triceLog() function converts the Trice buffer bytes to the log string and compares the result with the expected data. The between Go and C shared buffer limits the executed Trices per line to one, because they use the same buffer from the beginning. This could be done better with an increment to allow several trices in one single line.

Because each test runs a different configuration, all possible combinations are testable.

43.6. Test Results

ms@DESKTOP-7POEGPB MINGW64 ~/repos/trice (main)
$ ./scripts/testAll.sh
Thu, Dec 12, 2024  4:51:26 PM
This can take several minutes ...
?       github.com/rokath/trice/internal/decoder        [no test files]
?       github.com/rokath/trice/internal/do     [no test files]
?       github.com/rokath/trice/internal/translator     [no test files]
?       github.com/rokath/trice/pkg/ant [no test files]
ok      github.com/rokath/trice/cmd/trice       1.392s
ok      github.com/rokath/trice/internal/args   0.415s
ok      github.com/rokath/trice/internal/charDecoder    0.298s
ok      github.com/rokath/trice/internal/com    15.845s
ok      github.com/rokath/trice/internal/dumpDecoder    0.339s
ok      github.com/rokath/trice/internal/emitter        0.326s
ok      github.com/rokath/trice/internal/id     3.088s
ok      github.com/rokath/trice/internal/keybcmd        0.233s
ok      github.com/rokath/trice/internal/link   0.196s
ok      github.com/rokath/trice/internal/receiver       0.246s
?       github.com/rokath/trice/internal/translator     [no test files]
?       github.com/rokath/trice/pkg/ant [no test files]
ok      github.com/rokath/trice/cmd/trice       1.392s
ok      github.com/rokath/trice/internal/args   0.415s
ok      github.com/rokath/trice/internal/charDecoder    0.298s
ok      github.com/rokath/trice/internal/com    15.845s
ok      github.com/rokath/trice/internal/dumpDecoder    0.339s
ok      github.com/rokath/trice/internal/emitter        0.326s
ok      github.com/rokath/trice/internal/id     3.088s
ok      github.com/rokath/trice/internal/keybcmd        0.233s
ok      github.com/rokath/trice/internal/link   0.196s
ok      github.com/rokath/trice/internal/receiver       0.246s
ok      github.com/rokath/trice/internal/trexDecoder    0.264s
ok      github.com/rokath/trice/pkg/cipher      0.230s
ok      github.com/rokath/trice/pkg/endian      0.161s
ok      github.com/rokath/trice/internal/args   0.415s
ok      github.com/rokath/trice/internal/charDecoder    0.298s
ok      github.com/rokath/trice/internal/com    15.845s
ok      github.com/rokath/trice/internal/dumpDecoder    0.339s
ok      github.com/rokath/trice/internal/emitter        0.326s
ok      github.com/rokath/trice/internal/id     3.088s
ok      github.com/rokath/trice/internal/keybcmd        0.233s
ok      github.com/rokath/trice/internal/link   0.196s
ok      github.com/rokath/trice/internal/receiver       0.246s
ok      github.com/rokath/trice/internal/trexDecoder    0.264s
ok      github.com/rokath/trice/pkg/cipher      0.230s
ok      github.com/rokath/trice/pkg/endian      0.161s
ok      github.com/rokath/trice/internal/id     3.088s
ok      github.com/rokath/trice/internal/keybcmd        0.233s
ok      github.com/rokath/trice/internal/link   0.196s
ok      github.com/rokath/trice/internal/receiver       0.246s
ok      github.com/rokath/trice/internal/trexDecoder    0.264s
ok      github.com/rokath/trice/pkg/cipher      0.230s
ok      github.com/rokath/trice/pkg/endian      0.161s
ok      github.com/rokath/trice/pkg/msg 0.157s
ok      github.com/rokath/trice/pkg/tst 0.261s
ok      github.com/rokath/trice/_test/be_dblB_de_tcobs_ua       123.142s
ok      github.com/rokath/trice/_test/be_staticB_di_xtea_cobs_rtt32     123.159s
ok      github.com/rokath/trice/internal/trexDecoder    0.264s
ok      github.com/rokath/trice/pkg/cipher      0.230s
ok      github.com/rokath/trice/pkg/endian      0.161s
ok      github.com/rokath/trice/pkg/msg 0.157s
ok      github.com/rokath/trice/pkg/tst 0.261s
ok      github.com/rokath/trice/_test/be_dblB_de_tcobs_ua       123.142s
ok      github.com/rokath/trice/_test/be_staticB_di_xtea_cobs_rtt32     123.159s
ok      github.com/rokath/trice/pkg/msg 0.157s
ok      github.com/rokath/trice/pkg/tst 0.261s
ok      github.com/rokath/trice/_test/be_dblB_de_tcobs_ua       123.142s
ok      github.com/rokath/trice/_test/be_staticB_di_xtea_cobs_rtt32     123.159s
ok      github.com/rokath/trice/_test/dblB_de_cobs_ua   122.964s
ok      github.com/rokath/trice/_test/dblB_de_multi_cobs_ua     123.308s
ok      github.com/rokath/trice/_test/be_dblB_de_tcobs_ua       123.142s
ok      github.com/rokath/trice/_test/be_staticB_di_xtea_cobs_rtt32     123.159s
ok      github.com/rokath/trice/_test/dblB_de_cobs_ua   122.964s
ok      github.com/rokath/trice/_test/dblB_de_multi_cobs_ua     123.308s
ok      github.com/rokath/trice/_test/dblB_de_cobs_ua   122.964s
ok      github.com/rokath/trice/_test/dblB_de_multi_cobs_ua     123.308s
ok      github.com/rokath/trice/_test/dblB_de_multi_cobs_ua     123.308s
ok      github.com/rokath/trice/_test/dblB_de_multi_nopf_ua     123.244s
ok      github.com/rokath/trice/_test/dblB_de_multi_nopf_ua     123.244s
ok      github.com/rokath/trice/_test/dblB_de_multi_tcobs_ua    123.109s
ok      github.com/rokath/trice/_test/dblB_de_multi_tcobs_ua    123.109s
ok      github.com/rokath/trice/_test/dblB_de_multi_xtea_cobs_ua        123.213s
ok      github.com/rokath/trice/_test/dblB_de_multi_xtea_tcobs_ua       123.001s
ok      github.com/rokath/trice/_test/dblB_de_multi_xtea_cobs_ua        123.213s
ok      github.com/rokath/trice/_test/dblB_de_multi_xtea_tcobs_ua       123.001s
ok      github.com/rokath/trice/_test/dblB_de_nopf_ua   123.092s
ok      github.com/rokath/trice/_test/dblB_de_multi_xtea_tcobs_ua       123.001s
ok      github.com/rokath/trice/_test/dblB_de_nopf_ua   123.092s
ok      github.com/rokath/trice/_test/dblB_de_tcobs_ua  122.324s
ok      github.com/rokath/trice/_test/dblB_de_nopf_ua   123.092s
ok      github.com/rokath/trice/_test/dblB_de_tcobs_ua  122.324s
ok      github.com/rokath/trice/_test/dblB_de_tcobs_ua  122.324s
ok      github.com/rokath/trice/_test/dblB_de_xtea_cobs_ua      123.149s
ok      github.com/rokath/trice/_test/dblB_de_xtea_tcobs_ua     122.883s
ok      github.com/rokath/trice/_test/dblB_de_xtea_cobs_ua      123.149s
ok      github.com/rokath/trice/_test/dblB_de_xtea_tcobs_ua     122.883s
ok      github.com/rokath/trice/_test/dblB_di_nopf_rtt32__de_cobs_ua    246.703s
ok      github.com/rokath/trice/_test/dblB_de_xtea_tcobs_ua     122.883s
ok      github.com/rokath/trice/_test/dblB_di_nopf_rtt32__de_cobs_ua    246.703s
ok      github.com/rokath/trice/_test/dblB_di_nopf_rtt32__de_cobs_ua    246.703s
ok      github.com/rokath/trice/_test/dblB_di_nopf_rtt32__de_multi_cobs_ua      247.125s
ok      github.com/rokath/trice/_test/dblB_di_nopf_rtt32__de_multi_tcobs_ua     246.862s
ok      github.com/rokath/trice/_test/dblB_di_nopf_rtt32__de_tcobs_ua   246.531s
ok      github.com/rokath/trice/_test/dblB_di_nopf_rtt32__de_xtea_cobs_ua       247.072s
ok      github.com/rokath/trice/_test/dblB_di_nopf_rtt8__de_cobs_ua     246.639s
ok      github.com/rokath/trice/_test/dblB_di_nopf_rtt8__de_multi_cobs_ua       246.599s
ok      github.com/rokath/trice/_test/dblB_di_nopf_rtt8__de_multi_tcobs_ua      247.114s
ok      github.com/rokath/trice/_test/dblB_di_nopf_rtt8__de_tcobs_ua    246.851s
ok      github.com/rokath/trice/_test/ringB_de_cobs_ua  123.578s
ok      github.com/rokath/trice/_test/ringB_de_multi_tcobs_ua   123.517s
ok      github.com/rokath/trice/_test/ringB_de_multi_xtea_cobs_ua       123.497s
ok      github.com/rokath/trice/_test/ringB_de_multi_xtea_tcobs_ua      123.379s
ok      github.com/rokath/trice/_test/ringB_de_nopf_ua  123.555s
ok      github.com/rokath/trice/_test/ringB_de_tcobs_ua 123.300s
ok      github.com/rokath/trice/_test/ringB_de_xtea_cobs_ua     123.487s
ok      github.com/rokath/trice/_test/ringB_de_xtea_tcobs_ua    123.846s
ok      github.com/rokath/trice/_test/ringB_di_cobs_rtt32__de_tcobs_ua  247.400s
ok      github.com/rokath/trice/_test/ringB_di_cobs_rtt8__de_tcobs_ua   247.202s
ok      github.com/rokath/trice/_test/ringB_di_nopf_rtt32__de_tcobs_ua  247.204s
ok      github.com/rokath/trice/_test/ringB_di_nopf_rtt32__de_xtea_cobs_ua      246.818s
ok      github.com/rokath/trice/_test/ringB_di_nopf_rtt8__de_tcobs_ua   247.006s
ok      github.com/rokath/trice/_test/ringB_di_tcobs_rtt32__de_tcobs_ua 247.000s
ok      github.com/rokath/trice/_test/ringB_di_xtea_cobs_rtt32__de_xtea_cobs_ua 246.872s
ok      github.com/rokath/trice/_test/special_protect_dblB_de_tcobs_ua  0.444s
ok      github.com/rokath/trice/_test/stackB_di_nopf_aux32      123.819s
ok      github.com/rokath/trice/_test/stackB_di_nopf_aux8       123.830s
ok      github.com/rokath/trice/_test/stackB_di_nopf_rtt32      123.912s
ok      github.com/rokath/trice/_test/stackB_di_nopf_rtt8       123.976s
ok      github.com/rokath/trice/_test/stackB_di_xtea_cobs_rtt8  123.719s
ok      github.com/rokath/trice/_test/staticB_di_nopf_aux32     123.553s
ok      github.com/rokath/trice/_test/staticB_di_nopf_aux8      123.551s
ok      github.com/rokath/trice/_test/staticB_di_nopf_rtt32     123.596s
ok      github.com/rokath/trice/_test/staticB_di_nopf_rtt8      123.618s
ok      github.com/rokath/trice/_test/staticB_di_tcobs_rtt32    123.177s
ok      github.com/rokath/trice/_test/staticB_di_tcobs_rtt8     123.353s
ok      github.com/rokath/trice/_test/staticB_di_xtea_cobs_rtt32        123.126s

real    10m31.130s
user    0m0.000s
sys     0m0.015s

ms@DESKTOP-7POEGPB MINGW64 ~/repos/trice (main)
$

43.7. Special tests

43.8. Test Cases

43.8.1. Folder Naming Convention

Folder Name Part Meaning
testdata This is no test folder. It contains data common to all tests.
_... Folder starting with an undescore _ are excluded when go test ./... is executed.
_di_ direct mode
_de_ deferred mode
special_ a test, not using ./testdata/triceCheck.c
staticB_ static buffer, direct mode only possible
stackB_ stack buffer, direct mode only possible
ringB_ ring buffer, deferred mode and optional parallel direct mode
dblB_ double buffer, deferred mode and optional parallel direct mode
_rtt8_ (simulated) SEGGER_RTT byte transfer
_rtt32_ (simulated) SEGGER_RTT word transfer
__ direct and deferred mode together
_xtea_ with encryption, otherwise without encryption
_tcobs TCOBS package framing
_cobs COBS package framing
_nopf no package framing
_multi_ Usually each Trice is handled separately. In multi mode, groups of available Trices are framed together.
_ua simulated UART A output (for deferred modes)

(back to top)

44. Test Issues

Test folders starting with ERROR_ have issues. These cases are usable on the target. These tests fail for an unknown reason. Probably it is a test implementation issue. Especially when XTEA is used in one output but not in the other, the tests fail.

(back to top)

45. Add-On Hints

45.1. Trice on LibOpenCM3

LibOpenCM3 is a hardware abstraction library for many microcontrollers.

This is an exampe using STM’s STM32F411 Nucleo board.

--> This code uses a legacy Trice version and needs adaptation!

45.1.1. Prerequisites

45.1.2. triceConfig.h

/*! \file triceConfig.h
\author Thomas.Hoehenleitner [at] seerose.net
LibOpenCM3 adapatation by Kalle Raiskila.
*******************************************************************************/

#ifndef TRICE_CONFIG_H_
#define TRICE_CONFIG_H_

#ifdef __cplusplus
extern "C" {
#endif

#include <stdint.h>
#include <libopencm3/cm3/cortex.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/usart.h>

// Local (to this demo) time keeping functions
#include "time.h"

#define TRICE_UART USART2 //!< Enable and set UART for serial output.
// The alternative, TRICE_RTT_CHANNEL is not available with OpenCM3
// #define TRICE_RTT_CHANNEL 0

// Timestamping function to be provided by user. In this demo from time.h
#define TRICE_TIMESTAMP wallclock_ms() // todo: replace with TRICE_TREX_ENCODING stuff

// Enabling next 2 lines results in XTEA TriceEncryption  with the key.
// #define TRICE_ENCRYPT XTEA_KEY( ea, bb, ec, 6f, 31, 80, 4e, b9, 68, e2, fa, ea, ae, f1, 50, 54 ); //!< -password MySecret
// #define TRICE_DECRYPT //!< TRICE_DECRYPT is usually not needed. Enable for checks.

// #define TRICE_BIG_ENDIANNESS //!< TRICE_BIG_ENDIANNESS needs to be defined for TRICE64 macros on big endian devices. (Untested!)

//
///////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////
// Predefined trice modes: Adapt or creeate your own trice mode.
//
#ifndef TRICE_MODE
#error Define TRICE_MODE to 0, 200 or 201
#endif

//! Direct output to UART or RTT with cycle counter. Trices inside interrupts forbidden. Direct TRICE macro execution.
//! This mode is mainly for a quick tryout start or if no timing constrains for the TRICE macros exist.
//! Only a putchar() function is required - look for triceBlockingPutChar().
//! UART Command line similar to: `trice log -p COM1 -baud 115200`
//! RTT needs additional tools installed - see RTT documentation.
//! J-LINK Command line similar to: `trice log -args="-Device STM32G071RB -if SWD -Speed 4000 -RTTChannel 0 -RTTSearchRanges 0x20000000_0x1000"`
//! ST-LINK Command line similar to: `trice log -p ST-LINK -args="-Device STM32G071RB -if SWD -Speed 4000 -RTTChannel 0 -RTTSearchRanges 0x20000000_0x1000"`
#if TRICE_MODE == 0                     // must not use TRICE_ENCRYPT!
#define TRICE_STACK_BUFFER_MAX_SIZE 128 //!< This  minus TRICE_DATA_OFFSET the max allowed single trice size. Usually ~40 is enough.
#ifndef TRICE_ENTER
#define TRICE_ENTER                                                                          \
	{                                                  /*! Start of TRICE macro */           \
		uint32_t co[TRICE_STACK_BUFFER_MAX_SIZE >> 2]; /* Check TriceDepthMax at runtime. */ \
		uint32_t* TriceBufferWritePosition = co + (TRICE_DATA_OFFSET >> 2);
#endif
#ifndef TRICE_LEAVE
#define TRICE_LEAVE                                                                 \
	{ /*! End of TRICE macro */                                                     \
		unsigned tLen = ((TriceBufferWritePosition - co) << 2) - TRICE_DATA_OFFSET; \
		TriceOut(co, tLen);                                                         \
	}                                                                               \
	}
#endif
#endif // #if TRICE_MODE == 0

//! Double Buffering output to RTT or UART with cycle counter. Trices inside interrupts allowed. Fast TRICE macro execution.
//! UART Command line similar to: `trice log -p COM1 -baud 115200`
//! RTT Command line similar to: `trice l -args="-Device STM32F030R8 -if SWD -Speed 4000 -RTTChannel 0 -RTTSearchRanges 0x20000000_0x1000"`
#if TRICE_MODE == 200
#ifndef TRICE_ENTER
#define TRICE_ENTER TRICE_ENTER_CRITICAL_SECTION //! TRICE_ENTER is the start of TRICE macro. The TRICE macros are a bit slower. Inside interrupts TRICE macros allowed.
#endif
#ifndef TRICE_LEAVE
#define TRICE_LEAVE TRICE_LEAVE_CRITICAL_SECTION //! TRICE_LEAVE is the end of TRICE macro.
#endif
#define TRICE_HALF_BUFFER_SIZE 1000 //!< This is the size of each of both buffers. Must be able to hold the max TRICE burst count within TRICE_TRANSFER_INTERVAL_MS or even more, if the write out speed is small. Must not exceed SEGGER BUFFER_SIZE_UP
#define TRICE_SINGLE_MAX_SIZE 100   //!< must not exeed TRICE_HALF_BUFFER_SIZE!
#endif                              // #if TRICE_MODE == 200

//! Double Buffering output to UART without cycle counter. No trices inside interrupts allowed. Fastest TRICE macro execution.
//! Command line similar to: `trice log -p COM1 -baud 115200`
#if TRICE_MODE == 201
#define TRICE_CYCLE_COUNTER 0       //! Do not add cycle counter, The TRICE macros are a bit faster. Lost TRICEs are not detectable by the trice tool.
#define TRICE_ENTER                 //! TRICE_ENTER is the start of TRICE macro. The TRICE macros are a bit faster. Inside interrupts TRICE macros forbidden.
#define TRICE_LEAVE                 //! TRICE_LEAVE is the end of TRICE macro.
#define TRICE_HALF_BUFFER_SIZE 2000 //!< This is the size of each of both buffers. Must be able to hold the max TRICE burst count within TRICE_TRANSFER_INTERVAL_MS or even more, if the write out speed is small. Must not exceed SEGGER BUFFER_SIZE_UP
#define TRICE_SINGLE_MAX_SIZE 800   //!< must not exeed TRICE_HALF_BUFFER_SIZE!
#endif                              // #if TRICE_MODE == 201

//
///////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////
// Headline info
//

#ifdef TRICE_HALF_BUFFER_SIZE
#define TRICE_BUFFER_INFO                                                              \
	do {                                                                               \
		TRICE32(Id(0), "att: Trice 2x half buffer size:%4u ", TRICE_HALF_BUFFER_SIZE); \
	} while (0)
#else
#define TRICE_BUFFER_INFO                                                                                 \
	do {                                                                                                  \
		TRICE32(Id(0), "att:Single Trice Stack buf size:%4u", TRICE_SINGLE_MAX_SIZE + TRICE_DATA_OFFSET); \
	} while (0)
#endif

//! This is usable as the very first trice sequence after restart. Adapt and use it or ignore it.
#define TRICE_HEADLINE                                                           \
	TRICE0(Id(0), "s:                                          \n");             \
	TRICE8(Id(0), "s:     NUCLEO-F411RE     TRICE_MODE %3u     \n", TRICE_MODE); \
	TRICE0(Id(0), "s:                                          \n");             \
	TRICE0(Id(0), "s:     ");                                                    \
	TRICE_BUFFER_INFO;                                                           \
	TRICE0(Id(0), "s:     \n");                                                  \
	TRICE0(Id(0), "s:                                          \n");

//
///////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////
// Compiler Adaptation
//

#if defined(__GNUC__) /* gnu compiler ###################################### */

#define TRICE_INLINE static inline //! used for trice code

#define ALIGN4                                 //!< align to 4 byte boundary preamble
#define ALIGN4_END __attribute__((aligned(4))) //!< align to 4 byte boundary post declaration

//! TRICE_ENTER_CRITICAL_SECTION saves interrupt state and disables Interrupts.
#define TRICE_ENTER_CRITICAL_SECTION               \
	{                                              \
		uint32_t old_mask = cm_mask_interrupts(1); \
		{

//! TRICE_LEAVE_CRITICAL_SECTION restores interrupt state.
#define TRICE_LEAVE_CRITICAL_SECTION \
	}                                \
	cm_mask_interrupts(old_mask);    \
	}

#else
#error unknown compliler
#endif // compiler adaptations ##################################################

//
///////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////
// Optical feedback: Adapt to your device.
//

TRICE_INLINE void ToggleOpticalFeedbackLED(void) {
	// The only user controllable LED available on the
	// Nucleo is LD2, on port A5. This is set up in main.c
	gpio_toggle(GPIOA, GPIO5);
}

//
///////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////
// UART interface: Adapt to your device.
//

#ifdef TRICE_UART

//! Check if a new byte can be written into trice transmit register.
//! \retval 0 == not empty
//! \retval !0 == empty
//! User must provide this function.
TRICE_INLINE uint32_t triceTxDataRegisterEmpty(void) {
	uint32_t reg = USART_SR(TRICE_UART);
	return (reg & USART_SR_TXE);
}

//! Write value v into trice transmit register.
//! \param v byte to transmit
//! User must provide this function.
TRICE_INLINE void triceTransmitData8(uint8_t v) {
	usart_send_blocking(TRICE_UART, v);
	ToggleOpticalFeedbackLED();
}

//! Allow interrupt for empty trice data transmit register.
//! User must provide this function.
TRICE_INLINE void triceEnableTxEmptyInterrupt(void) {
	usart_enable_tx_interrupt(TRICE_UART);
}

//! Disallow interrupt for empty trice data transmit register.
//! User must provide this function.
TRICE_INLINE void triceDisableTxEmptyInterrupt(void) {
	usart_disable_tx_interrupt(TRICE_UART);
}

#endif // #ifdef TRICE_UART

///////////////////////////////////////////////////////////////////////////////
// Default TRICE macro bitwidth: 32 (optionally adapt to MCU bit width)
//

#define TRICE_1 TRICE32_1   //!< Default parameter bit width for 1  parameter count TRICE is 32, change for a different value.
#define TRICE_2 TRICE32_2   //!< Default parameter bit width for 2  parameter count TRICE is 32, change for a different value.
#define TRICE_3 TRICE32_3   //!< Default parameter bit width for 3  parameter count TRICE is 32, change for a different value.
#define TRICE_4 TRICE32_4   //!< Default parameter bit width for 4  parameter count TRICE is 32, change for a different value.
#define TRICE_5 TRICE32_5   //!< Default parameter bit width for 5  parameter count TRICE is 32, change for a different value.
#define TRICE_6 TRICE32_6   //!< Default parameter bit width for 6  parameter count TRICE is 32, change for a different value.
#define TRICE_7 TRICE32_7   //!< Default parameter bit width for 7  parameter count TRICE is 32, change for a different value.
#define TRICE_8 TRICE32_8   //!< Default parameter bit width for 8  parameter count TRICE is 32, change for a different value.
#define TRICE_9 TRICE32_9   //!< Default parameter bit width for 9  parameter count TRICE is 32, change for a different value.
#define TRICE_10 TRICE32_10 //!< Default parameter bit width for 10 parameter count TRICE is 32, change for a different value.
#define TRICE_11 TRICE32_11 //!< Default parameter bit width for 11 parameter count TRICE is 32, change for a different value.
#define TRICE_12 TRICE32_12 //!< Default parameter bit width for 12 parameter count TRICE is 32, change for a different value.

//
///////////////////////////////////////////////////////////////////////////////

#ifdef __cplusplus
}
#endif

#endif /* TRICE_CONFIG_H_ */

45.1.3. main.c

/*
 * Demo to test/show TRICE usage in a libopencm3
 * environment.
 */

#include <libopencm3/cm3/systick.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/exti.h>
#include <libopencm3/stm32/usart.h>
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/cm3/nvic.h>

#include <stdint.h>
void msleep(uint32_t delay);
uint32_t wallclock_ms(void);

#include "trice.h"

static void hardware_setup(void)
{
	/* Set device clocks from opencm3 provided preset.*/
	const struct rcc_clock_scale *clocks = &rcc_hsi_configs[RCC_CLOCK_3V3_84MHZ];
	rcc_clock_setup_pll( clocks );

	/* Set up driving the LED connected to port A, pin 5. */
	rcc_periph_clock_enable(RCC_GPIOA);
	gpio_mode_setup(GPIOA, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO5);

	/* User-button is connected to port C, pin 13. Set up button push
	 * to cause an interrupt. */
	gpio_mode_setup(GPIOC, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO13);
	rcc_periph_clock_enable(RCC_SYSCFG);  // clock for the EXTI handler
	nvic_enable_irq(NVIC_EXTI15_10_IRQ);
	exti_select_source(EXTI13, GPIOC);
	exti_set_trigger(EXTI13, EXTI_TRIGGER_FALLING);
	exti_enable_request(EXTI13);

	/* USART2 is connected to the nucleo's onboard ST-Link, which forwards
	 * it as a serial terminal over the ST-Link USB connection.
	 * This UART is given to the Trice data. */
	rcc_periph_clock_enable(RCC_USART2);
	usart_set_baudrate(USART2, 115200);
	usart_set_databits(USART2, 8);
	usart_set_stopbits(USART2, USART_STOPBITS_1);
	usart_set_mode(USART2, USART_MODE_TX);
	usart_set_parity(USART2, USART_PARITY_NONE);
	usart_set_flow_control(USART2, USART_FLOWCONTROL_NONE);

	// Enable UART2 interrupts in the system's interrupt controller
	// but do NOT enable generating interrupts in the UART at this
	// time. Let Trice enable them with triceEnableTxEmptyInterrupt()
	nvic_enable_irq(NVIC_USART2_IRQ);
	//usart_enable_tx_interrupt(USART2);
	usart_enable(USART2);

	/* Configure USART TX pin only. We don't get any input via the TRICE
	 * channel, so the RX pin can be left unconnected to the USART2 */
	gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO2);
	gpio_set_af(GPIOA, GPIO_AF7, GPIO2);

	/* Enable systick at a 1mS interrupt rate */
	systick_set_reload(84000);
	systick_set_clocksource(STK_CSR_CLKSOURCE_AHB);
	systick_counter_enable();
	systick_interrupt_enable();
}

//////////////////////////
// Time handling utilities
static volatile uint32_t system_millis;

/* "sleep" for delay milliseconds */
void msleep(uint32_t delay)
{
	uint32_t wake = system_millis + delay;
	while (wake > system_millis);
}

uint32_t wallclock_ms(void)
{
	return system_millis;
}

//////////////////////////
// Interupt handlers
// These are weak symbols in libopencm3
// that get overridden here.

// Trice USART
void usart2_isr(void)
{
	#if TRICE_MODE == 200
	triceServeTransmit();
	#endif
}

// External interrupts on pins 10-15, all ports.
// Only PC13 (User button on Nucleo) is enabled in this program.
void exti15_10_isr(void)
{
	exti_reset_request(EXTI13);
	#if TRICE_MODE == 200
	TRICE(Id(0), "Button press at, %d\n", system_millis);
	#endif
}

// Systick timer set to 1ms
void sys_tick_handler(void)
{
	system_millis++;
	#if TRICE_MODE == 200
	// Start sending what is currently in the Trice transmit buffer
	triceTriggerTransmit();
	#endif
}

int main(void)
{
	hardware_setup();
	TRICE_HEADLINE;
	while (1) {
		msleep(1000);

		// Depending on mode, either print this string to
		// UART (mode 0), or the Trice write buffer (mode 200).
		TRICE(Id(0), "Hello, TRICE, %d\n", 42);

		// TRICE("") with a string parameter only is problematic.
		// See discussion on https://github.com/rokath/trice/issues/279
		// TRICE0("") works in either case
		#ifdef __STRICT_ANSI__
		// if compiled with e.g. --std=c99
		TRICE0(Id(0), "Hello, TRICE\n");
		#else
		TRICE(Id(0), "Hello, TRICE\n");
		TRICE0(Id(0), "Hello, TRICE0()\n");
		#endif

		#if TRICE_MODE == 200
		// Swap Trice transmit/write ping-pong buffers.
		// Stuff printed with TRICE() since the last
		// call to TriceTransfer() will be sent once
		// triceTriggerTransmit() is called.
		TriceTransfer();
		#endif
	}

	return 0;
}

45.1.4. nucleo-f411re.ld

/* Use the LibOpenCM3-provided defaults for the linker details.
 */
MEMORY
{
	rom (rx)  : ORIGIN = 0x08000000, LENGTH = 512K
	ram (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}

INCLUDE cortex-m-generic.ld

45.1.5. Makefile

# Makefile for compiling the Trice demo on LibOpenCM3
# for STM32F411-Nucleo boards
CC=arm-none-eabi-gcc
C_FLAGS=-O0 -std=c99 -ggdb3
C_FLAGS+=-mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16
C_FLAGS+=-Wextra -Wshadow -Wimplicit-function-declaration -Wredundant-decls -Wmissing-prototypes -Wstrict-prototypes
C_FLAGS+=-fno-common -ffunction-sections -fdata-sections  -MD -Wall -Wundef
C_FLAGS+=-DSTM32F4 -I/home/kraiskil/stuff/libopencm3/include
# These two are for trice.h and triceConfig.h
C_FLAGS+=-I../../pkg/src/ -I.

LFLAGS=-L${OPENCM3_DIR}/lib -lopencm3_stm32f4 -lm -Wl,--start-group -lc -lgcc -lnosys -Wl,--end-group
LFLAGS+=-T nucleo-f411re.ld
LFLAGS+=--static -nostartfiles
LFLAGS+=-Wl,-Map=memorymap.txt

all: direct_mode.elf irq_mode.elf
.PHONY: flash clean

# Annotate Trice-enabled code.
# trice does this annotation in-place, so here we take
# a copy before running trice.
# I.e. write TRICE macros in foo.c, and this will generate
# the TRICE( Id(1234) .. ) macros into foo.trice.c
%.trice.c: %.c til.json
	cp -f $< $<.bak
	trice update
	cp -f $< $@
	cp -f $<.bak $<

# trice expects this file to exist, can be empty.
til.json:
	touch til.json

direct_mode.elf: main.trice.c ../../pkg/src/trice.c
	${CC} ${C_FLAGS} $^ -o $@ ${LFLAGS} -DTRICE_MODE=0

flash_direct_mode: direct_mode.elf
	openocd -f interface/stlink-v2.cfg -f target/stm32f4x.cfg -c "program direct_mode.elf verify reset exit"

irq_mode.elf: main.trice.c ../../pkg/src/trice.c
	${CC} ${C_FLAGS} $^ -o $@ ${LFLAGS} -DTRICE_MODE=200

flash_irq_mode: irq_mode.elf
	openocd -f interface/stlink-v2.cfg -f target/stm32f4x.cfg -c "program irq_mode.elf verify reset exit"


clean:
	@rm -f *.elf til.json main.trice.c

45.1.6. Usage

45.2. Get all project files containing Trice messages

We check the location information file. Every Trice is registered here.

cat demoLI.json | grep '"File":' | sort | uniq
		"File": "_test/_ringB_protect_de_tcobs_ua/TargetActivity.c",
		"File": "_test/special_dblB_de_tcobs_ua/TargetActivity.c",
		"File": "_test/special_for_debug/TargetActivity.c",
		"File": "_test/special_protect_dblB_de_tcobs_ua/TargetActivity.c",
		"File": "_test/testdata/triceCheck.c",
		"File": "examples/F030_inst/Core/Src/stm32f0xx_it.c",
		"File": "examples/G0B1_inst/Core/Src/main.c",
		"File": "examples/G0B1_inst/Core/Src/stm32g0xx_it.c",
		"File": "examples/L432_inst/Core/Inc/triceConfig.h",
		"File": "examples/L432_inst/Core/Src/main.c",
		"File": "examples/L432_inst/Core/Src/stm32l4xx_it.c",
		"File": "examples/exampleData/triceExamples.c",
		"File": "examples/exampleData/triceLogDiagData.c",

45.3. Building a trice library?

The triceConfig.h is mandatory for the trice code. It controls which parts of the trice code are included. There is no big advantage having a trice library, because it would work only with unchanged settings in the project specific triceConfig.h. Once the trice source files are translated, their objects are rebuilt automatically and only when the triceConfig.h is changed. So only the linker has a bit less to do when it finds a trice library compared to a bunch of trice objects. But does that influence the build time heavily?

The triceConfig.h is the only part of the trice sources which should be modified by the users. It is ment to be a individual part of the user projects. The examples folder shows the usage.

45.4. Possible Compiler Issue when using Trice macros without parameters on old compiler or with strict-C settings

If you encounter a compilation error on trice( "hi"); for example, but not on trice( "%u stars", 5 );, this is probably caused by the way your compiler interprets variadic macros. Simply change to trice0( "hi"); or change your compiler settings. See issue #279 for more details. If your project needs to be translated with strict-C settings for some reason, you have to use the trice0 macros when no values exist for the Trice macros.

(back to top)

46. Trice And Legacy User Code

When it comes to use legacy sources together with Trice, there are several ways doing so, which do not exclude each other:

46.1. Legacy User Code Option Separate Physical Output Channel

Advantages:

Disadvantages:

Details:

46.2. Legacy User Code Option Trice Adaptation Edits

Advantages:

Disadvantages:

Details:

46.3. Legacy User Code Option Print Buffer Wrapping and Framing

Trice >= v1.1 feature, see also issue #550

Advantages:

Disadvantages:

Details:

The Trice binary encoding uses states 1, 2, 3 of the 4 states, the 2 Binary Encoding stamp selector bits can have. They located in the starting uint16_t ID value to encode the Trice (time) stamp size. If both bits are zero (state 0), the Trice tool can interpret the incoming data buffer according to a passed CLI switch; in this special case just printing it as string.

If the Trice library and the user print both write to the same output, an easy modification would be, to prepend the user print output with a 2-byte count as long its size is < 16383, so that the 2 most significant bits are zero. Additionally, the this way counted buffer needs the same buffer framing as the Trice binary data.

46.4. Legacy User Code Option Trice Aliases Adaptation

Trice >= v1.1 feature, see also accepted pull requests #533 and #536

Advantages:

Disadvantages:

Details:

This cool functionality was contributed by @srgg in pull requests (PR) #533 and #536 (to be considered as one PR only). It allows code integration containing user specific log statements into Trice instrumented projects without the need to rename the user specific log statements.

In the assumption, most user printi statements having only up to 12 integers, those user prints could get covered by adding -alias printi to the trice insert and trice clean commands.

The user printi statements containing floats, doubles, strings could get simply renamed into user prints and then -salias prints will cover them too. That, of course, is a legacy user code change, but it allows to use this slightly modified legacy user code parallel in other projects.

Yes, user printi and user prints need to be defined too. See ./_test/alias_dblB_de_tcobs_ua/triceConfig.h/triceConfig as a simple example and its usage in ./_test/alias_dblB_de_tcobs_ua/TargetActivity.c

This technique allows also to cover legacy user code specific ASSERT macros, as shown in ./_test/aliasassert_dblB_de_tcobs_ua/triceConfig.h and used in the tests ./_test/aliasassert_dblB_de_tcobs_ua/TargetActivity.c.

Despite of these 2 CGO tests the real-world example ./examples/G0B1_inst shows the usage too.

The following sub-chapters are mainly written by @srgg as accompanying documentation to its pull requests.

46.4.1. PR533 Doc

46.4.2. PR533 Summary

This PR introduces support for treating user-defined macros as aliases to trice and triceS within the Trice CLI toolchain. The goal is to enable project-specific logging macros to be processed just like built-in Trice macros — including ID generation, decoding, and binary format support — without requiring projects to directly call trice() or triceS() in their source code.

PR leverages the -exclude source feature added in #529.

46.4.3. PR533 Motivation

Trice uses a source-scanning and ID generation approach, where the toolchain scans for trice(...) and triceS(...) calls, injects numeric trace IDs, and builds a mapping database. However, it currently only supports built-in(hardcoded) macros and allows only global on/off control via compile-time flags.

This makes it difficult to:

46.4.4. What This PR533 Adds

CLI-level aliasing: Developers can now declare custom macros to be treated as trice or triceS equivalents. These user-defined macros will be recognized during scanning, ID injection, and decoding.

46.4.5. PR533 Example

print_macro.h:

#ifndef TRICE_OFF
  #define DEBUG_PRINT(...)  trice(__VA_ARGS__)
  #define DEBUG_PRINT_S(...)  triceS(__VA_ARGS__)
#else
  #define DEBUG_PRINT(...)  Serial.println(__VA_ARGS__)
  #define DEBUG_PRINT_S(...)  Serial.printf(__VA_ARGS__)
#endif

example.c:

#include "trice.h"
#include "print_macro.h"

void setup() {
    Serial.begin(115200);

    while (!Serial) {
        delay(10);
    }

   // Add code here to initialize whatever Trice sender TCP/UDP/UART, etc.
   
  // No argument
  DEBUG_PRINT("DEBUG_PRINT test: no args\n");

  char* str = "Test string";
  DEBUG_PRINT_S("DEBUG_PRINT_S test: %s\n", str);
 }
PR533 Check with Trice

Insert trice IDs:

trice insert -alias DEBUG_PRINT -salias DEBUG_PRINT_S  -exclude ./print_macro.h -v

Flash the MCU and run the trice receiver on your host machine to receive probes (cli command is config and receiver dependent), for UDP4, it can be:

   trice log -p UDP4 -v -pf none
PR533 Check without Trice:

Clean trice IDs, if any:

trice clean -alias DEBUG_PRINT -salias DEBUG_PRINT_S  -exclude ./print_macro.h -v

Flash with -DTRICE_OFF.

46.4.6. PR536 Doc

What This PR536 Adds

This is a follow-up to #533. It enforces the consistent use of the “%s” format in all triceS aliases and fixes that behavior in the newly added test case.

The following simplified example reflects a real use case where custom macros wrap formatting logic:

#define CUSTOM_PRINT_S(id, fmt, ....) triceS(id, "%s", format_message(fmt, ##__VA_ARGS__))
PR536 - The Problem Statement

Determining the Strg argument reliably is challenging due to:

For instance, custom macros like these show the variability:

CUSTOM_ASSERT(false, "Message: %s", msg);
CUSTOM_ASSERT(false, "Message without args");
CUSTOM_ASSERT(false);

Improving this would likely require Clang integration—adding complexity (e.g., full build flags, complete source context)—whereas Trice’s current regex-based approach remains lightweight and simple to use.

PR536 Implementation Details:

matchTrice() was re-implemented to improve robustness. It now:

This approach simplifies the logic and allows the parser to skip invalid or partial matches without aborting, enabling continued scanning of the file for valid constructs.

46.4.7. Alias Example Project

To use the Alias technique with examples/G0B1_inst the following adaptations were made:

G0B1AliasExample.png

(back to top)

47. Future Development

(back to top)

47.1. Further Context Enrichment Variants

Context Enrichment supports direct Bind log sites and reversible source extensions with insert/clean. Bind rejects selected CE sites in wrapper macros or counter-rebase regions. Use an ordinary function, put direct calls on separate source lines, or use insert/clean as described under bind-limits.

(back to top)

47.2. Improving the Trice Tool Internal Parser (not planned right now)

47.2.1. Trice Internal Log Code Short Description

Trice v1.0 Code

Hint: To follow this explanation with the debugger, you can open in VSCode the trice folder, klick the Run & Debug Button or press CTRL-SHIFT-D, select trice l -p DUMP and set a breakpoint at func main() in ./cmd/trice/main.go or directly in translator.Translate ./internal/translator/translator.go.

Disadvantages of Trice v1.0 Implementation
Aims for a better implementation

47.3. Using Trice on Servers

A server can ingest and analyze Trice streams from devices. Using Trice as the server application’s own logger is a separate use case and needs evidence of a practical benefit. Claims about speed, energy use, storage, and additional compression require measurements with equivalent retained information.

(back to top)

48. Working with the Trice Git Repository

Action Command
Get a local repository copy. git clone github.com/rokath/trice.git trice
Show current folder pwd
Show repository status. git status
Clean the repo, if needed. git stash push
Show all branches. git branch -a
Switch to main. git switch main
Fetch a pull request as new branch PRIDa. git fetch origin pull/ID/head:PRIDa
List worktree. git worktree list
Add to worktree. git worktree add ../trice_wt_PRIDa PRIDa
Add branch dev to worktree git worktree add ../trice-dev dev
Rstore the repo if needed. git stash pop
Change to new folder. cd ../trice_wt_PRIDa
Show repository status. git status
Test pull request. ./scripts/testAll.sh full
Show repository status. git status
Clean pull request. git restore .
Change to previous folder. cd -
Delete worktree branch. git worktree remove ../trice_wt_PRIDa
Delete git branch. git branch -d PRIDa
Log last 3 commits in branch maste git log -3 main
Checkout by hash git checkout <hash>
One Liner Log until shortly before v1.0.0 git log --graph --decorate --all --pretty=format:'%C(bold yellow)%h%Creset %C(bold green)%ad%Creset %C(bold cyan)%d%Creset %C(white)%s%Creset' --date=format:'%Y-%m-%d %H:%M' --since 2025-04-01
One Liner Log for branch devel git log --graph --decorate devel --pretty=format:'%C(bold yellow)%h%Creset %C(bold green)%ad%Creset %C(bold cyan)%d%Creset %C(white)%s%Creset' --date=format:'%Y-%m-%d %H:%M'
One Liner Log with author git log --graph --decorate --all --pretty=format:'%C(bold yellow)%h%Creset %C(bold green)%ad%Creset %C(bold blue)%an%Creset %C(bold cyan)%d%Creset %C(white)%s%Creset' --date=format:'%Y-%m-%d %H:%M'
New worktree detached branch for compare git worktree add --detach ../trice_9995fdc4b 9995fdc4b
Add a special commit worktree ./AddWorktreeFromGitLogLineData.sh <commit-hash> <YYYY-MM-DD> <HH:MM>
Create a bunch of worktrees ./AddWorktreesBetween.sh "<since-date>" "<until-date>" or ./AddWorktreesBetween.sh <older-hash> <newer-hash>
Delete all trice_* worktrees cd ~/repos && rm trice_* && cd trice && git worktree prune && git worktree list
Delete all trice_* branches git branch -D `git branch \| grep -E 'trice_'`
Show all opencommit parameter oco config describe
Show some config settings oco config get OCO_MODEL && oco config get OCO_PROMPT_MODULE && oco config get OCO_EMOJI

48.1. Install opencommit on macOS


🧰 Prerequisites

Before you begin, make sure you have:


🔑 Step 1 — Get Your OpenAI API Key


⚙️ Step 2 — Install OpenCommit


🔧 Step 3 — Set Up Your API Key on macOS


⚙️ Step 4 — Optional Configuration

export OCO_LANG="en"           # or "de", "fr", etc. 
export OCO_MODEL="gpt-5"       # or another model like "gpt-4-turbo" 
export OCO_PROMPT_MODULE="conventional" 
export OCO_EMOJI=true

Add these to your ~/.zshrc for persistence.


🚀 Step 5 — Use OpenCommit


🔁 Step 6 — (Optional) Install Git Hook


🧩 Step 7 — Troubleshooting

If OpenCommit says:


48.2. Install opencommit on Windows

🧭 Overview

OpenCommit is a tool that uses AI (like GPT models) to automatically generate meaningful Git commit messages based on your code changes.

This guide explains how to install and configure OpenCommit on Windows step by step.


⚙️ Prerequisites


🪄 Installation Steps

1. Install OpenCommit Globally

2. Configure the API Key

3. (Optional) Configure Defaults


🚀 Usage

🔧 Troubleshooting


✅ Example

git add .
oco

Output:

Generated commit message:
...

(back to top)

49. Trice Maintenance

49.1. Trice Project structure (Files and Folders)

Trice Root Folder File Details
.clang-format See GitHub Action clang-format.yml - Check C Code Formatting
.clang-format-ignore See GitHub Action clang-format.yml - Check C Code Formatting
.code_snippets Some legacy helper code for copying where to use
.editorconfig See GitHub Action clang-format.yml - Check C Code Formatting
.git/ Git repository metadata (exists locally after cloning; not part of the repository content)
.gitattributes See GitHub Action clang-format.yml - Check C Code Formatting
.github/ 📁 The .github Folder — Purpose and Contents
.gitignore git ignores these files
.goreleaser.yaml goreleaser configuration
.idea/ GoLand settings
lychee.toml GitHub Action link-check.yml - Broken Links Check
.markdownlint.yaml Cleaning the Sources
.markdownlintignore Cleaning the Sources
.vscode/ VS Code settings
AUTHORS.md contributors
CHANGELOG.md History
CODE_OF_CONDUCT.md How to communicate
CONTRIBUTING.md Helper
LICENSE.md MIT
README.md GitHub first page
_config.yml jekyll configuration
_test automatic target code tests
scripts/buildTriceTool.sh Build Trice tool from Go sources
scripts/_150_setup_build_environment.sh see inside
scripts/_280_format_c_code.sh See GitHub Action clang-format.yml - Check C Code Formatting
scripts/_300_clean_dsstore.sh Run to remove macOS artifacts
temp/log/coverage.out Go test coverage output
cmd/clang-filter ReadMe
cmd/trice Trice tool command Go sources
demoLI.json location information example
demoTIL.json Trice ID list example
dist/ local distribution files folder created by GoReleaser
docs documentation folder with link forwarding
examples/ example target projects
scripts/_310_refresh_trice_user_manual.sh Trice Reference Manual Maintenance (or any *.md file)
scripts/gitAddWorktreeFromGitLogLineData.sh helper to get easy a git worktree folder from any git hash for easy folder compare, see inside
scripts/gitAddWorktreesBetween.sh helper to get easy git worktree folders from any time range
scripts/gitLogWithBranches.sh helper to get easy a history view
go.mod Go modules file
go.sum Go modules sums
index.md Jekyll index site for README.md
internal/ Trice tool internal Go packages
pkg/ Trice tool common Go packages
scripts/_330_renew_ids_and_refresh_tests.sh renew all ID data
src/ C sources for trice instrumentation -> Add to target project
temp/ ignored local workspace for binary logfiles and other runtime artifacts like scripts/testAll.sh helper files under ./temp/log
testAll.log ignored local output of the last ./scripts/testAll.sh run
scripts/testAll.sh run all tests
third_party/ external components
trice_bindIDs_in_examples_and_test_folder.sh run the canonical Trice Bind workflow
scripts/_240_legacy_clean_ids.sh Cleaning the Sources Activating the Trice Cache
scripts/_120_setup_trice_environment.sh Cleaning the Sources Activating the Trice Cache
scripts/_230_legacy_insert_ids.sh Cleaning the Sources Activating the Trice Cache

(back to top)

49.2. 📁 The .github Folder — Purpose and Contents

GitHub automatically recognizes and uses everything contained inside the .github/ directory. This folder defines how the project behaves on GitHub: issue templates, automated workflows, labels, code scanning, greetings, and release automation. Details:

49.2.1. 📁 .github Root

It contains issue templates, labels, workflow automation, code scanning, linting, and the CI/CD release pipeline.

49.2.2. 📂 .github/workflows — GitHub Actions Workflows

The .github/workflows/ folder contains YAML descriptions for various actions, which will be triggered automatically on certain events or are started manually. Every yml file in this directory defines an automated process. These processes run on GitHub’s servers (CI/CD).

GitHub Action About
clang-format.yml GitHub Action clang-format.yml - Check C Code Formatting
codeql.yml GitHub Action codeql.yml - Static Code Analysis
coverage.yml GitHub Action coverage.yml - Test Coverage and Coveralls Integration
go.yml GitHub Action go.yml - Building and Testing Go Code
goreleaser.yml GitHub Action goreleaser.yml - Build & Pack Trice Distribution
label.yml GitHub Action label.yml - Automatic Labeling Rules
link-check.yml GitHub Action link-check.yml - Broken Links Check
shellcheck.yml GitHub Action shellcheck.yml - Catching Common Bash Scripts Bugs
shfmt.yml GitHub Action shfmt.yml - Ensure Consistent Shell Scripts Formatting
stale.yml GitHub Action stale.yml - Automatic Stale Issue Handling
superlinter.ym GitHub Action superlinter.yml - Ensure Consistent YAML and Markdown Formatting
pages.yml GitHub Action pages.yml - Creates The Trice GitHub Pages

49.2.3. GitHub Action clang-format.yml - Check C Code Formatting

49.2.4. GitHub Action codeql.yml - Static Code Analysis

49.2.5. GitHub Action coverage.yml - Test Coverage and Coveralls Integration

Trice uses Go’s built-in coverage tooling to measure how much of the Go codebase is exercised by automated tests.

Action Command
Generate a coverage profile locally go test ./... -covermode=atomic -coverprofile=./temp/log/coverage.out
Show results as list in terminal go tool cover -func=./temp/log/coverage.out
Show results colored file specific in browser go tool cover -html=./temp/log/coverage.out

Coverage badge:
The README displays the current coverage status for the default branch using the Coveralls badge:

[![Coverage Status](https://coveralls.io/repos/github/rokath/trice/badge.svg?branch=master)](https://coveralls.io/github/rokath/trice?branch=master)

This badge is updated whenever the CI workflow successfully uploads a new coverage report for the master branch.

49.2.6. GitHub Action go.yml - Building and Testing Go Code

49.2.7. GitHub Action goreleaser.yml - Build & Pack Trice Distribution

This workflow runs GoReleaser, the tool that builds and packages Trice for distribution.

See also Trigger a real Trice release via CI (with git tag)

49.2.8. GitHub Action label.yml - Automatic Labeling Rules

49.2.10. GitHub Action manual.ym - To Be Triggered Manually

A workflow that is designed to be triggered manually (similar to workflow_dispatch workflows). Common use cases:

49.2.11. GitHub Action shellcheck.yml - Catching Common Bash Scripts Bugs

Runs ShellCheck on all *.sh files, catching common bugs in Bash scripts.

49.2.12. GitHub Action shfmt.yml - Ensure Consistent Shell Scripts Formatting

Runs shfmt in diff mode on pull requests to ensure consistent formatting of shell scripts.

49.2.13. GitHub Action stale.yml - Automatic Stale Issue Handling

Automates stale issue handling. Function:

Mark stale issues and pull requests

49.2.14. GitHub Action superlinter.yml - Ensure Consistent YAML and Markdown Formatting

49.2.15. GitHub Action pages.yml - Creates The Trice GitHub Pages

This workflow creates the Trice github pages avaliable under rokath.github.io/trice/.

49.3. Trice Reference Manual Maintenance (or any *.md file)

49.4. Cleaning the Sources

In GitHub are some Actions defined. Some of them get triggered on a git push and perform some checks. To get no fail, some scripts should run before committing:

(back to top)

50. Build and Release the Trice Tool

50.1. Build Trice tool from Go sources

To execute the target code tests, you can run scripts/testAll.sh or cd into _test and run go test ./... from there. ATTENTION: These tests run a significant long time (many minutes depending on your machine), because the Go - C border is crossed very often. The last tests can last quite a while, depending on your machine.

ms@DESKTOP-7POEGPB MINGW64 /c/repos/trice (main)
$ go install ./cmd/trice/

Afterwards you should find an executable trice inside $GOPATH/bin/ and you can modify its source code.

After installing Go, in your home folder should exist a folder ./go/bin. Please add it to your path variable. OR: Copy the Trice binaries from there into a folder of your path after creating them with go install ./cmd/trice/... ./cmd/tlog/.... There is now a remommended script ./scripts/buildTriceTool.sh. Using it, depending on your system you may need to enter bash ./scripts/buildTriceTool.sh, includes the actual Trice repository state into the Trice binaries, which is shown with trice version and tlog --version then - useful in case of issues.

50.2. Prepare A Release

Prerequisite: Installed goreleaser.

50.2.1. Check a GoReleaser Release before Publishing

By cloning the Trice repo into an empty folder, you make sure no other files exist in the Trice folder.

mkdir ./tmp
cd /tmp
git clone https://github.com/rokath/trice.git
cd trice
goreleaser release --clean --snapshot --skip=publish

This just generates the artifacts locally in /tmp/trice/dist using the ./trice/.goreleaser.yaml copy in ./temp/trice.

Alternatively you can do in your local Trice clone directly, by removing everything not a part of the Trice repo, if you are sure not to loose any data:

git status
git clean -xfd
goreleaser release --clean --snapshot --skip=publish

Explanation:

What you should see:

If this succeeds, you’ve already tested 90% of what CI will do for a real release. If it fails, fix the problem locally first (missing files, bad paths, etc.) – it would fail the same way in CI.

50.3. Trigger a real Trice release via CI (with git tag)

Letting CI build and publish an official release.

50.3.1. Make sure your workflow reacts to tags

In .github/workflows/goreleaser.yml, you need on: workflow_dispatch: push: tags: - 'v*'.

Commit & push this change (if you haven’t already):

git add .github/workflows/goreleaser.yml
git commit -m "Configure GoReleaser workflow to run on tags"
git push origin main

50.3.2. Final checks before tagging

In your local trice repo:

If all of that is green, you’re ready to “bless” a version.

50.3.3. Choose a version and create a git tag

Decide on a version, for example:

Create an annotated tag:

git tag -a v0.44.0 -m "Trice v0.44.0"

Check your tags:

git tag

You should see v0.44.0 in the list.

💡 The tag is what GoReleaser uses as the release version (.Tag, .Version, etc.) in your .goreleaser.yaml.
Your ldflags like -X main.version= will use this.

50.3.4. Push the tag to GitHub (this triggers CI)

Now push the tag:

git push origin v0.44.0

This does not push all tags, only v0.44.0.

Because of your workflow’s on: push: tags: 'v*', this automatically starts the GoReleaser workflow in GitHub Actions.

50.3.5. Watch the CI release run on GitHub

  1. Open your browser and go to your repo:

    https://github.com/rokath/trice

  2. Click the “Actions” tab at the top.

  3. In the list of workflows, click on “goreleaser”.

  4. You should see a new run with something like:

  1. Click on that run, then on the job (e.g. goreleaser):

If “Run GoReleaser” is green ✔, the CI release has succeeded.

50.3.6. Check the GitHub Release

Finally, verify the published release:

  1. In your repo, click the “Releases” section (right side or under “Code”).

  2. You should see a new release v0.44.0 created by GoReleaser.

  3. Inside it you’ll find:

This is now your official Trice release built by CI.

(back to top)

51. Ctrl-C robust use of trice insert and trice clean

trice insert and trice clean can modify many source files. This is useful for build workflows where IDs are inserted before compilation and removed afterwards, but it also means that interruption handling matters.

This chapter summarizes practical recommendations for robust build scripts and explains the background of GitHub issue #658.

51.1. Background: GitHub issue #658

GitHub issue #658 discusses the risk that trice insert or trice clean may be interrupted while source files are being modified.

There are two different kinds of interruption effects:

1. Repository-level mixed state
   Some files are already processed, while others are not.

2. Single-file write-back risk
   A file could be left partially written if it is overwritten directly and
   the process is interrupted at the wrong time.

The first case is inconvenient but usually recoverable.

The second case is more serious, because a source file could become empty or incomplete.

The robust tool-side solution is that Trice should write changed files atomically:

1. Write the new content to a temporary file next to the target file.
2. Flush and close the temporary file.
3. Atomically rename it over the target file.

The temporary file should be created in the same directory as the target file, for example:

target: src/foo.c
temp:   src/.foo.c.trice-tmp-<pid>-<random>

This avoids cross-filesystem rename problems and ensures that the replacement is local to the target file.

The -cache mechanism can still be useful for recovery metadata, hashes, transaction manifests, and diagnostics, but it should not be required for the atomic replacement of source files.

51.2. What Bash scripts can and cannot protect against

A shell script can improve the workflow by making sure that trice clean is called after a successful trice insert, even when a build fails or the user presses Ctrl-C.

However, a shell script cannot fully protect against interruption exactly inside the Trice process while Trice is writing one file. That must be solved inside Trice itself with atomic write-back.

Therefore, shell-script robustness and Trice-internal atomic writes solve different parts of the problem:

Bash script:
- Can run cleanup after build failure.
- Can run cleanup after Ctrl-C during make.
- Can avoid leaving the repository intentionally inserted.

Trice implementation:
- Must prevent partially written files.
- Must handle interruption during file write-back safely.
- Can provide transaction/recovery diagnostics.

Only one script level should own the full sequence:

trice clean -> trice insert -> build -> trice clean

If an outer script calls many inner build scripts, and each inner build script already performs its own trice insert and trice clean, then the outer script should not also perform one global insert/clean around the whole sequence.

Otherwise both levels modify the same source tree, which can cause confusing behavior such as cleanup happening while another build step still expects inserted IDs.

Recommended ownership models:

Model A: Outer script owns Trice state
------------------------------------
outer script:
  trice clean
  trice insert
  run all builds
  trice clean

inner build scripts:
  do not call trice insert/clean


Model B: Inner scripts own Trice state
-------------------------------------
outer script:
  run child build scripts
  optionally run final safety clean

inner build script:
  trice clean
  trice insert
  build
  trice clean

Do not mix both models unintentionally.

The following pattern is a simplified example. It keeps cleanup in one place and makes the normal success path use the same cleanup logic as error and interruption paths.

#!/usr/bin/env bash

set -euo pipefail

SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
ROOT="$(cd -- "${SCRIPT_DIR}/../.." && pwd)"

ids_inserted=0

run_trice_clean_if_needed() {
  if [ "${ids_inserted}" -eq 1 ]; then
    echo "cleanup: running trice clean"

    local clean_status=0
    (
      cd "${ROOT}" || exit 1
      bash "${ROOT}/scripts/_240_legacy_clean_ids.sh"
    ) || clean_status=$?

    if [ "${clean_status}" -ne 0 ]; then
      echo "warning: cleanup: trice clean failed with exit code ${clean_status}" >&2
      return "${clean_status}"
    fi

    ids_inserted=0
  fi

  return 0
}

cleanup_and_exit() {
  local status="${1:-$?}"
  local clean_status=0

  trap - INT TERM EXIT

  run_trice_clean_if_needed || clean_status=$?
  if [ "${status}" -eq 0 ] && [ "${clean_status}" -ne 0 ]; then
    status="${clean_status}"
  fi

  exit "${status}"
}

trap 'cleanup_and_exit $?' EXIT
trap 'cleanup_and_exit 130' INT
trap 'cleanup_and_exit 143' TERM

(
  cd "${ROOT}" || exit 1
  bash "${ROOT}/scripts/_240_legacy_clean_ids.sh"
  bash "${ROOT}/scripts/_230_legacy_insert_ids.sh"
)

ids_inserted=1

cd "${SCRIPT_DIR}"
make

clean_status=0
run_trice_clean_if_needed || clean_status=$?

trap - INT TERM EXIT
exit "${clean_status}"

Important points in this pattern:

- ids_inserted becomes 1 only after insert completed successfully.
- cleanup runs clean only if insert completed successfully.
- cleanup disables traps first to avoid recursive cleanup.
- helper scripts run from a controlled directory.
- directory changes for helper calls are done inside subshells where possible.
- the normal success path and abnormal paths use the same cleanup helper.

51.5. Preserve the build exit code

If the build command may fail and the script still needs to run cleanup afterwards, do not let set -e abort before the exit code is captured.

For example:

set +e
make ${MAKE_JOBS} TRICE_FLAGS="${flags}" gcc
make_status=$?
set -e

clean_status=0
run_trice_clean_if_needed || clean_status=$?
if [ "${make_status}" -eq 0 ] && [ "${clean_status}" -ne 0 ]; then
  make_status="${clean_status}"
fi

trap - INT TERM EXIT
exit "${make_status}"

This keeps the original build result unless cleanup itself fails after an otherwise successful build.

51.6. Be careful with current working directory changes

A subtle problem can occur when a cleanup helper changes the current working directory and does not change it back.

For example:

run_trice_clean_if_needed() {
  cd "${ROOT}" || exit 1
  bash "${ROOT}/scripts/_240_legacy_clean_ids.sh"
}

After this function returns, the caller is still in ${ROOT}.

If the script later runs:

make clean

it may accidentally run in the repository root instead of the example directory.

Prefer one of these forms:

(
  cd "${ROOT}" || exit 1
  bash "${ROOT}/scripts/_240_legacy_clean_ids.sh"
)

or explicitly return to the build directory:

cd "${SCRIPT_DIR}"
make clean

51.7. Prefer Makefile clean targets when available

If an example Makefile provides a clean target, prefer:

make clean

over hard-coded shell cleanup such as:

rm -rf out out.gcc

The Makefile knows the actual build output directories, for example:

.PHONY: clean

clean:
	@rm -rf "$(GCC_BUILD)" "$(CLANG_BUILD)"

A direct rm -rf out out.gcc can be used as a fallback, but it is less precise.

51.8. Example scripts

The following scripts are useful examples for Ctrl-C robust wrapping of trice insert and trice clean.

They illustrate slightly different situations:

scripts/_160_pc_target_test_worker.sh
  PC/CGO test wrapper.
  Shows how an outer test script can run cleanup after insert and restore
  temporary environment changes such as C_INCLUDE_PATH.

scripts/_200_gcc_example_build_worker.sh
  First Step-12 variant.
  Shows a global outer-script cleanup owner.

scripts/_210_gcc_example_builds_all_workflows.sh
  Improved Step-12 orchestrator variant.
  Shows the case where child example build scripts own insert/clean, while the
  outer script only runs a final safety clean.

build_trice_safe_cleanup.sh
  Generic example build script.
  Shows pre-clean, insert, build, and final cleanup in one script.

build_with_clang_trice_safe_cleanup.sh
  Clang build script.
  Shows the same cleanup pattern for a clang build target.

build_pattern_preserving_trice_safe_cleanup.sh
  Pattern-preserving GCC build script with TRICE_OFF handling.
  Shows conditional insert behavior.

build_gcc_preserve_make_exit_trice_safe_cleanup.sh
  GCC build script that preserves the make exit code.
  Shows how to temporarily disable set -e around make and still run cleanup.

G0B1_inst_build_fixed_cwd_cleanup.sh
  Corrected G0B1_inst build script.
  Shows how to avoid current-working-directory bugs by running helper commands
  in subshells and returning to the example directory before make clean.

These scripts are examples for build-wrapper robustness. They do not replace the need for atomic file write-back inside Trice itself.

51.9. Summary

Recommended practical rules:

1. Let exactly one script level own each insert/build/clean sequence.
2. Set a state flag only after trice insert completed successfully.
3. Run trice clean on every exit path after successful insert.
4. Disable traps at the beginning of cleanup to avoid recursion.
5. Preserve the original build exit code where needed.
6. Use subshells for cleanup helper calls that change directories.
7. Prefer Makefile clean targets over hard-coded rm -rf.
8. Treat Bash cleanup as a workflow aid, not as a substitute for atomic writes.

For the core safety issue, the Trice implementation should still ensure:

A source file is never left partially written after Ctrl-C, SIGTERM, crash, or write error.

(back to top)