Blog coding article

Knurling-rs changelog #32

Sebastian
Article

Knurling-rs changelog #32

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

    This is the 32nd 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 🎉

    New env_logger-like filter

    We are busy preparing the upcoming 0.3 release, one of the exciting new features is the env_logger-like filter to disable / enable logging in crates (see PR #519). Before this release the use of logging levels for the log statements (e.g. defmt::info, defmt::warn, ..) was set up by cargo features. The new log filter removes these Cargo features and replaces them with the environment variable DEFMT_LOG completely. Therefore the previous Cargo features are not the mechanism to specify log filters anymore.

    The new DEFMT_LOG env var specifies which crates emit the defmt logs at which level. Its syntax is based on env_logger's RUST_LOG environment variable. One of the advantages compared to the Cargo features configuration is the new logger mechanism supports filtering with module level granularity.

    Note this is a target side emit filter and orthogonal to the host side display filter proposed in this probe-run PR #74.

    Disabling logs with this target filter will omit logs from the target program, therefore the resulting binary is smaller (and possibly slightly faster). It reduces the amount of data sent from the target to the host. The proposed host side display filter on the other hand will filter logs after they are sent from the target to the host. The env_logger-like log filter is kind of a pre-process step, while the host side log filter acts as a post-process step.

    Note when the DEFMT_LOG env var changes all crates that transitively depend on the defmt_macros crate will be re-compiled. The log filter is a compile-time mechanism.

    Given the following code in src/bin/hello.rs.

    #![no_main]
    #![no_std]
    use test_logger as _;
    
    #[cortex_m_rt::entry]
    fn main() -> ! {
        defmt::info!("hello");
        foo::foo();
        bar::bar();
        test_logger::exit()
    }
    
    mod foo {
        pub fn foo() {
            defmt::trace!("foo");
        }
    }
    
    mod bar {
        pub fn bar() {
            defmt::info!("bar");
        }
    }
    

    running the program with

    $ DEFMT_LOG=trace cargo rb hello
    

    outputs the following lines:

    (HOST) INFO  flashing program (6.71 KiB)
    (HOST) INFO  success!
    ────────────────────────────────────────────────────────────────────────────────
    0 INFO  hello
    └─ hello::__cortex_m_rt_main @ src/bin/hello.rs:8
    1 TRACE foo
    └─ hello::foo::foo @ src/bin/hello.rs:16
    2 INFO  bar
    └─ hello::bar::bar @ src/bin/hello.rs:22
    ────────────────────────────────────────────────────────────────────────────────
    (HOST) INFO  device halted without error
    

    Let's see a few examples using different values for DEFMT_LOG.

    # filter module 'foo'
    $ DEFMT_LOG=hello::foo cargo r --bin hello
    0 TRACE foo
    └─ hello::foo::foo @ src/bin/hello.rs:16
    
    $ DEFMT_LOG=hello::bar cargo r --bin hello
    0 INFO  bar
    └─ hello::bar::bar @ src/bin/hello.rs:22
    
    $ DEFMT_LOG=hello::foo,hello::bar cargo r --bin hello
    0 TRACE foo
    └─ hello::foo::foo @ src/bin/hello.rs:16
    1 INFO  bar
    └─ hello::bar::bar @ src/bin/hello.rs:22
    
    $ DEFMT_LOG=hello cargo r --bin hello
    0 INFO  hello
    └─ hello::__cortex_m_rt_main @ src/bin/hello.rs:8
    1 TRACE foo
    └─ hello::foo::foo @ src/bin/hello.rs:16
    2 INFO  bar
    └─ hello::bar::bar @ src/bin/hello.rs:22
    

    The DEFMT_LOG env var also accepts the logging levels: error, warn, info, debug, trace. To disable logging for a module or globally, pass the off value.

    $ DEFMT_LOG=info cargo r --bin hello
    0 INFO  hello
    └─ hello::__cortex_m_rt_main @ src/bin/hello.rs:8
    1 INFO  bar
    └─ hello::bar::bar @ src/bin/hello.rs:22
    
    $ DEFMT_LOG=off cargo r --bin hello
    <empty>
    

    Follow the instructions given in pull request #519 if you want to test the new log mechanism.

    New defmt::println! macro

    In this release the defmt::println macro is introduced in PR #569 which is the equivalent to std::println. It works in the same fashion as the logging macros in that its content is serialized, but it's not associated to any log level. A defmt::println statement will always serialize the data & display the content to stdout on the host system.

    Log statements using the error, warn, info, debug, trace macros can be filtered out using the new DEFMT_LOG env var. Using defmt::println in the defmt-test crate now displays the output of the test results regardless of any set log level. Before this change the test suite required a log level to be specified in order to print results, that may have interferred with log statements in the test code itself. With the change the test results are always displayed irrespective of any log level. This brings the behaviour of defmt-test closer to the #[test] macro of "standard" Rust.

    Implement Format for arrays of any length

    The last highlight in this release (in PR #589) is that the default Format implementation now supports arrays of any length by using const generics. Before this release the default Format implementations were defined for a number of fixed sized arrays. Alternatively the user had to provide their own format string in order to display arrays.

    To illustrate this the following code example shows what worked before.

    // works, implementation for arrays with length 32 exists
    defmt::info!("[u8; 32]: {}", [1; 32]);
    // fails to compile, no implementation with array length 33
    defmt::info!("[u8; 33]: {}", [2; 33]);
    // given format string matches the given argument
    defmt::info!("[u8; 33]: {=[?;33]}", [3; 33]);
    

    With the update in #589 there is no need to specify a format string, the default version will automatically determine the right size from the argument. All defmt::info! statements in the example code above work now.

    Improvements 🦀

    defmt

    • #519 Add target-side env_logger-line env filter
    • #598 Recover from decoding errors in defmt-print
    • #569 Add defmt::println! macro
    • #594 Use UDF instruction on nested panics
    • #589 Implement Format for arrays of any length
    • #584 Remove outdated doc "you may only call write! once". Thanks to Dirbaio!

    probe-run

    • #266 Recover from decoding-errors
    • #247 Print trouble shooting information on "probe not found" error
    • #264 Use new Stream Decoder API
    • #58 Print message when linking normally fails

    Internal Improvements 🧽

    defmt

    • #601 Move defmt code into the defmt/ folder
    • #600 Run snapshot & backward compatibility tests in dev mode only
    • #592 Add backward compatibility test to xtask
    • #580 Re-structure Truncate implementations using macro
    • #591 Remove timestamps from snapshot tests
    • #585 Add xtask option to run single snapshot test
    • #587 Tweak inline attributes to remove machine code duplication
    • #574 Refactor defmt-rtt (1/2)

    probe-run

    • #267 Minimize dependencies by disabling default-features

    Fixes 🔨

    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!