Blog coding article

Knurling-rs changelog #31


Knurling-rs changelog #31

Published on 4 min read
Open Source Project
Knurling icon
A tool set to develop embedded applications faster.
Details ❤️ Sponsor

    This is the 31st changelog for Knurling-rs, our push to sustainably build better tooling for developing and debugging Rust software for embedded systems. Knurling-rs includes a suite of tools that make it easier to develop, log, debug, and test your embedded Rust libraries and applications!

    Knurling-rs is supported by our sponsors on GitHub. If you're interested in seeing more projects like this, consider becoming a sponsor today!

    Highlights 🎉

    dirbaio work

    Many of you noticed the work Dirbaio put into defmt in the recent weeks. He set out to improve the code size and wire size of firmware using defmt. The tracking issue for this was "RFC: Traits and wire format 2.0" (defmt#492), which summarizes the discussions from various previous issues and puts them into one coherent design.

    His effort resulted consisted of multiple pull requests:

    Additionally he also cared about separating the "crate version" from the "wire format version" (defmt#287), which was implemented in defmt#540.

    Let's take a look how he approached these improvements!

    Traits and wire format 2.0

    trait Logger

    The trait Logger defines how data is moved from the device, where the application runs, to the host, where logs will be formatted and displayed.

    Previously you needed to implement two traits for your defmt-loggers: trait Write for defining how to write bytes to the host, and trait Logger for the acquire-release mechanism.

    Now both of these traits are combined into one unified trait Logger, which handles both acquire-release and writing to the host.

    pub unsafe trait Logger {
        /// Acquire the global logger in the current execution context.
        fn acquire();
        /// Releases the global logger in the current execution context.
        unsafe fn release();
        /// Writes `bytes` to the destination.
        unsafe fn write(bytes: &[u8]);
        unsafe fn flush(); // not of concern right now

    This has the advantage of smaller code size, because the dyn Write doesn't need be stored anymore during log calls.

    This change also included to update the transport crates defmt-rtt, defmt-itm and defmt-semihosting to take advantage of this and adapting the #[global_logger]-macro.

    trait Format

    Dirbaio also updated the format trait, in order to fix #455, #457, #458.

    pub trait Format {
        /// Writes the defmt representation of `self` to `fmt`.
        fn format(&self, fmt: Formatter);
        // omitting provided methods
    /// Handle to a defmt logger.
    #[derive(Copy, Clone)]
    pub struct Formatter<'a> {
        pub(crate) _phantom: PhantomData<&'a ()>,

    The biggest change is that the struct defmt::Formatter is now zero-sized, which positively impacts the code size.

    Encoding + framing

    Previously we encoded the defmt-stream using a mixture of leb128-encoding and bool compression. Dirbaio removed these optimizations, because of their negative impact on the code size. This results in a good code size gain, but a small bump in the wire size. But no worries! This increase in wire size is offset by the new rzCOBS-encoding.

    rz… what? "Reverse-Zerocompressing-COBS encoding (rzCOBS) is a variant of rCOBS which additionally compresses messages known to contain many zero bytes." (see rzcobs crate for more details).

    Since the defmt wire stream is known to contain many zero bytes, rzCOBS is a good fit, because it can compress this kind of data quite fast. Additionally it supports framing, which will allow recovering from a corrupted stream!

    rzCOBS is fast, but still slower than using no compression at all. Therefore users of defmt can select if they want rzCOBS or the raw data. This works via cargo-feature-flags, with a default of rzCOBS.

    # rzCOBS
    defmt = { version = "0.3", features = ["encoding-rzcobs"] }
    # raw data
    defmt = { version = "0.3", features = ["encoding-raw"] }

    (No, defmt 0.3 didn't release yet, but it will be the first version containing this change. If you already want to try this out now you can use the git-version of defmt.)

    Having rzCOBS allows us to drop the u24 unsigned integer type, which was helpful before, but now isn't required anymore.

    Improvements 🦀


    • #581 Add impl Format for alloc::Layout. Thanks to newAM!
    • #550 defmt::flush()
    • #572 defmt-decoder: impl FromStr<<String>> for Encoding
    • #570 Support referring to Self in bitflags constants
    • #568 Encoding docs. Thanks to Dirbaio!


    • #254 Add support for measuring the program's stack usage
    • #248 Print dedicated message on control + c
    • #250 Backtrace options. Thank you, BriocheBerlin!
    • #253 feat: add help message for JtagNoDeviceConnected, Thanks to numero-744!

    Internal Improvements 🧽


    • #263 set blocking mode to 0b10 (BLOCK_IF_FULL)
    • #257 Split "set rtt to blocking-mode" into function
    • #259 Update snapshot tests

    Fixes 🔨


    • #579 defmt-rtt: fix check for blocking RTT
    • #577 Fix typo in cfg of encoding-feature-compile_error!
    • #578 qemu: Allow dead code


    • #260 fix ci on nightly

    Version Update Notification 🆙

    • probe-run v0.2.6
    • panic-probe v0.2.1

    Sponsor this work

    Knurling-rs is mainly funded through GitHub sponsors. Sponsors get early access to the tools we are building and help us to support and grow the knurling tools and courses. Thank you to all of the people already sponsoring our work through the Knurling project!