Table of content

What is this :grey_question:

The rules of the game are explained in my original.

79th Challenge

Challenge

Prepare for the rust workshop hosted by rustfest.ch and summarize what was learnt.

Solution :white_check_mark:

To get things flashed and get debugging with probe.rs. I am going to try different things starting with the probe.

But first I install all the required dependencies.

    # install compilation targets
    rustup target add thumbv6m-none-eabi
    rustup target add --toolchain nightly thumbv6m-none-eabi
    # necessary linker
    cargo install flip-link
    # Useful to creating UF2 images for the RP2040 USB Bootloader
    cargo install elf2uf2-rs --locked
    # Useful for flashing over the SWD pins using a supported JTAG probe
    # this contains  cargo-embed too. Also available via homebrew
    cargo install cargo-binstall
    cargo binstall probe-rs-tools
    

An we are ready to go!

Hardware Setup

Listing available probes

I have various probes I am going to test with. An ST-Link v2 clone, an st-link nucleo, a segger basic and a rusty-probe

    probe-rs list
    

Listing the three probes is not an issue on Mac OS 14.5. I plug the probes, I run probe-rs (see documentation at https://probe.rs/) list and I see them.

RP2040 compatible debuggers

Next I want to run some code on the chips so I clone the repository and I do cargo run.

    git clone https://github.com/maebli/pico-usb // my personal fork
    cd rp2040-blink
    probe-rs run --chip RP2040 target/thumbv6m-none-eabi/debug/rp2040-blink
    

Somehow I have issues. It turns out, that st-link is vendor locked and can only be used to debug and flash st products. I found this out on the porbe-rs matrix channel. Rusty-probe initially does not work but soon it works due to SWDIOand SWCLK being swapped. Further the segger basic also works, however, I had to connect like I showed in one of my first posts. For the rusty-probe I just need GND, SWIO and SWCLK and I can debug.

Debugging with VS Code

To debug within VS Code I installed the VS Code extension and created two debug settings (see repos, and also here):

    {
        // 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": [
            {
                "type": "probe-rs-debug",
                "request": "launch",
                "name": "debug-rp2040-rtic-usb-serial-blinky",
                "cwd": "${workspaceFolder}",
                "connectUnderReset": false,
                "chip": "RP2040",
                "flashingConfig": {
                    "flashingEnabled": true,
                    "haltAfterReset": true
                },
                "coreConfigs": [
                    {
                        "coreIndex": 0,
                        "programBinary": "rp2040-rtic-usb-serial-blinky/target/thumbv6m-none-eabi/debug/rp2040-rtic-usb-serial-blinky"
                    }
                ]
            },
            {
                "type": "probe-rs-debug",
                "request": "launch",
                "name": "debug-rp2040-blink",
                "cwd": "${workspaceFolder}",
                "connectUnderReset": false,
                "chip": "RP2040",
                "flashingConfig": {
                    "flashingEnabled": true,
                    "haltAfterReset": true
                },
                "coreConfigs": [
                    {
                        "coreIndex": 0,
                        "programBinary": "rp2040-blink/target/thumbv6m-none-eabi/debug/rp2040-blink"
                    }
                ]
            }
        ]
    }
    

With these I was able to debug. Debugging the host code was the same as debugging any other existing program. I found the com port created by Pi used in the host application by listing the devices available and then diffing when plugging the RP2040 in and out.

Software Setup

There are two repos that are mentioned in the workshop. One is https://github.com/mike-kfed/embedded_drivers and the other is the already mentioned https://github.com/bedroombuilds/pico-usb

Understanding the dependencies

The dependencies and a URL to the crates documentation page is listed bellow.

RP2040 code dependencies
Crate Link Description
cortex-m cortex-m Low-level access to Cortex-M processors, providing functionality like registers and interrupts.
cortex-m-rt cortex-m-rt Startup and runtime features for Cortex-M microcontrollers.
embedded-hal embedded-hal Hardware abstraction layer for embedded systems, defining traits for hardware interfacing.
defmt defmt A highly efficient logging framework for embedded systems, designed for resource-constrained devices.
defmt-rtt defmt-rtt Implements the RTT (Real-Time Transfer) transport layer for the defmt logging framework.
panic-probe panic-probe A panic handler for embedded systems that logs panic messages using probe-based debugging.
rp-pico rp-pico Board support package for the Raspberry Pi Pico board, providing low-level access to its features.
usb-device usb-device A platform-agnostic USB device library, allowing the creation of USB devices.
usbd-serial usbd-serial USB serial port implementation for the usb-device library.
rp2040-hal rp2040-hal Hardware abstraction layer for the RP2040 microcontroller, used in Raspberry Pi Pico and similar boards.
rp2040-boot2 rp2040-boot2 Low-level boot configuration for RP2040 microcontrollers.
sparkfun-pro-micro-rp2040 sparkfun-pro-micro-rp2040 Board support package for the SparkFun Pro Micro RP2040 board.

Dependencies that were mentioned in the talk related to RTOS:

Crate Link Description
embassy embassy An async, more rigid alternative to traditional Rust async frameworks.
RTIC v2 rtic Version 2 of the Real-Time Interrupt-driven Concurrency framework, preferred for its async support.
Bern RTOS No Link Available A Swiss-made real-time operating system noted for its cool and efficient performance.
Tock tock A highly sandboxed OS; complex and large, ideal for managing intricate systems like train controls.

Dependencies that were mentioned related to debugging / tracing:

Crate Link Description
defmt defmt A highly efficient logging framework for embedded systems.
defmt-rtt defmt-rtt Implements the RTT (Real-Time Transfer) transport layer for the defmt logging framework.
ufmt ufmt A minimal, #![no_std] compatible formatting library, designed for resource-constrained environments.
modular-bitfield modular-bitfield Provides macros to define bitfields in a modular way for improved code safety and readability.
heapless heapless Enables the use of static memory allocation for data structures like Vec and HashMap, with ufmt implementation support.

And others

Crate Link Description
kebab-case kebab-case A utility crate for converting strings to kebab-case, commonly used in URLs and IDs.
nom nom A parser combinator library that allows for easy string parsing and protocol analysis.
Host code dependencies
Crate Link Description
serialport serialport Provides an interface for working with serial ports in Rust.
anyhow anyhow Provides flexible error handling with ‘anyhow::Error’, a trait object based error system.
defmt defmt A highly efficient logging framework for embedded systems.
heapless heapless Enables the use of static memory allocation for data structures like Vec and HashMap.
postcard postcard A no_std, zero-allocation serialization library based on Serde.
serde serde A serialization/deserialization framework that is both powerful and generic.
Developing for embedded targets in rust
General

Here are some important things to know about rust embedded development.

  • Rust 1.0 came out in 2015, embedded_hal 1.0 only recently.
  • embedded development is done with no_std , meaning things like Smart pointers and Display traits are not included i.e. “batteries not included”. The box type is also not included but may be added with ``core::alloc`
  • PAC/HAL/BSP: often contain unsafe code
    • BSP: implements hardware layer
    • HAL: “only” traits, this way it works on all uP, mostly works, has improved a lot. Interrupts are still hard to abstract, injecting an interrupt into code is hard
    • PAC: is specific to a micro controller and generated directly from the micro controller’s description (usually in SVD or similar format). A PAC provides low-level register access that is faithful to the micro controller’s specification, allowing developers direct interaction with the hardware through Rust’s type system, which ensures memory safety and concurrency.
  • Targets have varying level of support. Best supported are ARM / RISC. There is a platform support page that puts support in different categories. [Tier-1 are the best.]https://doc.rust-lang.org/nightly/rustc/platform-support.html#tier-1-with-host-tools)

  • read embedded-hal docs
Setup Clippy / Lint to be more strict can help

`.clippy.toml for settings of clippy following settings can be helpful

    allow-unwrap-in-tests = true
    allow-unexpect-in-tests = true
    

some additional helpful lints are:

    [lints.clippy]
    unwrap_used = "deny"
    unwrap_in_result = "deny"
    panic = "deny"
    expect_used = "deny"
    unimplemented = "deny"
    suspicious = {level = "deny", priority = -1}
    style = { level = "deny", priority = -1}
    complexity = { level = "deny", priority = -1}
    perf = { level = "deny", priority = -1}
    
Typestate pattern

With this, method / pattern can be bound to states. It is checked at compile time that state allows function / transition. The example that was given was the following:

    struct Mario;
    struct SuperMario;

    impl Mario {
        fn die(self) { drop(self) ; }
        fn grow(self) -> SuperMario {
            SuperMario::from(self)
        }
    }


    impl SuperMario {
        fn shrink(self) -> Mario {
            Mario::from(self)
        }
    }
    

With this method, super mario cannot die. It is clear at compile time.

Note: Methods should also be testable with another method

Traits

trait bounds (refresher):

(a) with “where” for a lot of trait bounds (b) with “dyn” for few e.g. “delay: &mut dyn DelaysMs"

see also rust by example

  • Rust has no inheritance, only traits
  • important for embedded Hal, if we want to implement an i2c, we implement a trait for it
  • default traits can be overridden
  • critical section used for adding for example stuff that the HAL does not support like two CS for SPI

challenge: write drivers that are platform-agnostic.

Usually trait bounds are defined for the implementation not for the struct, even though it is possible.

Associated type for traits

        trait Animal {
            Error = i32; ////<-- associated type
            fn eat
        }
    
some embedded files in a crate that are not normally found in a rust workspace
  • build.rs <- script to read the linker file
  • memory.x <- linker file
  • .cargo, this is where the probe-rs run --chipis hidden! makes thing magically run with cargo run