knurling-rs app-template

repository·main·Indexed 19 days ago

https://github.com/knurling-rs/app-template

A project template for quickly setting up embedded Rust projects. It integrates probe-rs for flashing and debugging, defmt for logging, and flip-link for stack protection. The template provides examples for no_std application structure, custom type formatting and bitfield logging with defmt, and guidance on configuring HAL crates, target chips, and running tests on embedded hardware.

Tokens
2.4K
Snippets
14
Records
15
Agent score
67%

What's inside knurling-rs/app-template

  1. Run your application

    main

    Once configured, you can run your application using cargo run. If you have defined binary targets in src/bin/, you can run specific ones using the -b or rb alias.

    Adjusting RTT Buffer Size

    If flip-link reports a memory overflow, you can decrease the defmt RTT buffer size using the DEFMT_RTT_BUFFER_SIZE environment variable. Use powers of two for optimal performance.

    # Run a specific binary (e.g., hello.rs)
    cargo rb hello
    
    # Run with a smaller RTT buffer if memory is tight
    DEFMT_RTT_BUFFER_SIZE=64 cargo rb hello
  2. Configure probe-rs chip and compilation target

    main

    After initializing, you must configure .cargo/config.toml to match your hardware.

    Set the chip

    Run probe-rs chip list to find your chip name, then replace the $CHIP placeholder in the runner configuration.

    Set the target

    Select the appropriate Cortex-M target for your board and add it via rustup.

    Example for a Cortex-M4F (e.g., nRF52840):

    1. Update .cargo/config.toml with target = "thumbv7em-none-eabihf".
    2. Run rustup target add thumbv7em-none-eabihf.
    # .cargo/config.toml
    
    # 1. Set the chip
    runner = ["probe-rs", "run", "--chip", "nRF52840_xxAA", "--log-format=oneline"]
    
    # 2. Set the target
    [build]
    target = "thumbv7em-none-eabihf"
  3. Run unit and integration tests on target

    main

    The template supports running tests directly on the embedded hardware.

    • Unit Tests: Located in the library crate (src/lib.rs). Run them with cargo test --lib.
    • Integration Tests: Located in the tests/ directory. Run them with cargo test --test <filename_without_extension> (e.g., cargo test --test integration).

    Note: To add new integration tests, you must add a corresponding [[test]] section in Cargo.toml.

    # Run unit tests
    cargo test --lib
    
    # Run integration tests
    cargo test --test integration
  4. Initialize a new project from the template

    main

    Use cargo generate to create a new project. Replace my-app with your desired project name. The generated project will contain TODO markers in various files that you must resolve to match your specific hardware.

    cargo generate \
        --git https://github.com/knurling-rs/app-template \
        --branch main \
        --name my-app
  5. Install dependencies for app-template

    main

    To use this template, you must install the following tools:

    1. flip-link: A linker wrapper that provides stack overflow protection.
    2. probe-rs: For flashing and debugging embedded devices. Follow the official installation guide.
    3. cargo-generate: To scaffold new projects from this template.
    # Install flip-link
    cargo install flip-link
    
    # Install cargo-generate
    cargo install cargo-generate
  6. Add and import a Hardware Abstraction Layer (HAL)

    main

    To interact with your hardware, you need to add a HAL crate as a dependency and import it for memory layout purposes.

    1. Add to Cargo.toml: Add your board's HAL (e.g., nrf52840-hal = "0.14.0"). Note: For RP2040 users, you must use a Board Support Crate (BSP) that includes a second-stage bootloader.
    2. Import in src/lib.rs: Add the HAL to your library to ensure the correct memory layout is used.
    3. Linker Script: Some HALs require a memory.x file in your project root. Check your HAL's documentation if you encounter linker errors.
    # Cargo.toml
    [dependencies]
    nrf52840-hal = "0.14.0"
    // src/lib.rs
    use nrf52840_hal as _; // memory layout
  7. Configure rust-analyzer for VS Code

    main

    To enable proper IDE features (like autocomplete and type checking) in VS Code, add the following to your .vscode/settings.json to ensure rust-analyzer recognizes your workspace structure.

    {
        "rust-analyzer.linkedProjects": [
            "Cargo.toml",
            "firmware/Cargo.toml"
        ]
    }
  8. Demonstrate panic behavior and defmt output with the panic example

    main

    The panic.rs binary is an example application designed to demonstrate how the template handles panics and how defmt logs the output. When run, it initializes the global logger, prints main via defmt, and then immediately triggers a panic using defmt::panic!().

    #![no_main]
    #![no_std]
    
    use {{crate_name}} as _; // global logger + panicking-behavior + memory layout
    
    #[cortex_m_rt::entry]
    fn main() -> ! {
        defmt::println!("main");
    
        defmt::panic!()
    }
  9. Troubleshoot log levels using the `levels` binary

    main

    The levels binary is a troubleshooting tool used to verify that the logging infrastructure (specifically defmt) is correctly configured and to test different log levels.

    To test specific log levels, you must set the DEFMT_LOG environment variable before running the application. This allows you to control which log levels are visible in your output. Common values for DEFMT_LOG include info, trace, warn, debug, and error.

    Note: This binary relies on the crate's global logger, panicking behavior, and memory layout configuration.

    # Example: Run the levels binary with trace logging enabled
    DEFMT_LOG=trace cargo rb levels
    
    # Example: Set the environment variable for the current session
    export DEFMT_LOG=info
    cargo rb levels
  10. Use defmt formatting specifiers

    main

    When using defmt::println!, you can use specific format specifiers to control how values are logged:

    • {:?}: Uses the defmt::Format implementation for the type (standard debug-style logging).
    • {=TYPE}: Forces a specific type for the value (e.g., {=u8}). This is useful for ensuring the value is logged with the intended bit-width or type representation.

    Example:

    let x: u32 = 42;
    // Force x to be logged as a u8
    defmt::println!("x={=u8}", x);
    let x = 42;
    defmt::println!("x={=u8}", x);
  11. Format custom types for defmt logging

    main

    To log custom structs or enums using defmt, derive the defmt::Format trait on your types. This allows you to use the {:?} formatter in defmt::println! calls. Note that all fields within the struct must also implement defmt::Format.

    Example usage:

    use defmt::Format;
    
    #[derive(Format)]
    struct MyData {
        id: u32,
        value: f32,
    }
    
    // Inside a function:
    defmt::println!("data={:?}", MyData { id: 1, value: 3.14 });
    #[derive(Format)]
    struct S1<T> {
        x: u8,
        y: T,
    }
    
    #[derive(Format)]
    struct S2 {
        z: u8,
    }
    
    // Usage:
    defmt::println!("s={:?}", s);
  12. Implement a basic 'Hello World' application

    main

    This example demonstrates the minimal structure for a no_std application using the app-template. It relies on the crate's global logger, panicking behavior, and memory layout provided by the {{crate_name}} dependency.

    To implement this pattern:

    1. Use #![no_main] and #![no_std] attributes.
    2. Import the crate as {{crate_name}} as _ to ensure its global setup (logging, panic handlers, etc.) is linked.
    3. Use the #[cortex_m_rt::entry] macro to define the entry point.
    4. Use defmt::println! for logging.
    5. Call {{crate_name}}::exit() to handle the application exit sequence.
    #![no_main]
    #![no_std]
    
    use {{crate_name}} as _; // global logger + panicking-behavior + memory layout
    
    #[cortex_m_rt::entry]
    fn main() -> ! {
        defmt::println!("Hello, world!");
    
        {{crate_name}}::exit()
    }