microbit Rust Board Support Crate

repository·main·Indexed 43 days ago

https://github.com/nrf-rs/microbit

Board support crates and examples for developing firmware for the BBC micro:bit V1 and V2 using Rust. Provides hardware abstractions for the 5x5 LED matrix (including blocking and non-blocking interfaces), capacitive touch sensing for the V2 logo, and unified ADC types. Supports target thumbv6m-none-eabi for V1 and thumbv7em-none-eabihf for V2, with versioning managed via Cargo features.

Tokens
8.1K
Snippets
28
Records
34
Agent score
30%

What's inside microbit

  1. Identify the correct crate for your micro:bit version

    main

    The micro:bit has different hardware versions that require different Rust crates and compilation targets. Use the following mapping to select the correct crate for your board:

    • micro:bit V1: Use the microbit crate. Target: thumbv6m-none-eabi.
    • micro:bit V2: Use the microbit-v2 crate. Target: thumbv7em-none-eabihf.
  2. Install dependencies for micro:bit development

    main

    To run examples and flash firmware, you need to install probe-rs-tools and flip-link. These tools facilitate debugging and safe stack overflow protection.

    Run the following command to install them via cargo:

    cargo install probe-rs-tools flip-link
  3. Run a micro:bit example

    main

    To flash and run an example from this repository, plug in your micro:bit and use cargo run with the appropriate features and target for your hardware version.

    For micro:bit V1

    Use the --features v1 flag and the thumbv6m-none-eabi target.

    For micro:bit V2

    Use the --features v2 flag and the thumbv7em-none-eabihf target.

    # For micro:bit V1
    cargo run --release --manifest-path ./examples/display-blocking/Cargo.toml --features v1 --target thumbv6m-none-eabi
    
    # For micro:bit V2
    cargo run --release --manifest-path ./examples/display-blocking/Cargo.toml --features v2 --target thumbv7em-none-eabihf
  4. Access micro:bit V2 hardware components via the Board struct

    main

    The Board struct organizes the micro:bit V2 hardware into logical groups. Key components include:

    • buttons: Access to button_a and button_b.
    • display_pins: Pins used to drive the 5x5 LED matrix.
    • microphone_pins: Includes mic_in and mic_run.
    • speaker_pin: The pin connected to the onboard speaker.
    • edge: Unused GPIO pins on the edge connector (e.g., e00, e01, e02, e08, e09, e12, e16).
    • pins: Other unused GPIO pins on P0 and P1.
    • i2c_internal: Internal I2C bus pins (scl, sda).
    • i2c_external: External I2C bus pins (scl, sda).
    • uart: UART pins for the debugger (tx, rx).

    Additionally, the Board struct exposes all nRF52 peripherals (like RADIO, TIMER, TWIM, UARTE, ADC, etc.) and Core peripherals (like NVIC, SYST, SCB) directly as public fields.

  5. Use Board peripherals for buttons, I2C, and UART

    main

    The Board struct organizes hardware into logical groups. You can convert these groups directly into HAL-compatible pin sets using the From trait:

    • Buttons: Access button_a and button_b via board.buttons.
    • I2C: Convert board.i2c into twi::Pins using .into().
    • UART: Convert board.uart into uart::Pins using .into() (configured for the debugger pins).
    // I2C Example
    let i2c_pins: twi::Pins = board.i2c.into();
    
    // UART Example
    let uart_pins: uart::Pins = board.uart.into();
  6. How capacitive touch sensing works on the micro:bit v2 logo

    main

    The gold logo on the front of the micro:bit v2 acts as a capacitive touch sensor connected to pin P1_04. Since there is no dedicated touch peripheral, detection is achieved by measuring the time it takes for the pin to charge via the board's 10 MΩ pull-up resistor.

    The measurement process:

    1. The pin is driven low briefly to drain any existing charge.
    2. The pin is switched to a floating input.
    3. The code counts the microseconds elapsed before the pin reaches a logic high.

    A finger on the logo increases capacitance, causing the charge time to increase. An untouched pad charges in roughly 15 µs, while a touched pad takes significantly longer.

    Hysteresis: To prevent flickering when a finger is near the threshold, the Logo implementation uses hysteresis. It uses a higher threshold (TOUCH_HIGH) to transition from 'untouched' to 'touched', and a lower threshold (TOUCH_LOW) to transition from 'touched' to 'released'.

    # use microbit_common as microbit;
    # use microbit::Board;
    # use microbit::hal::Timer;
    # use microbit::logo::Logo;
    
    let board = Board::take().unwrap();
    let mut timer = Timer::new(board.TIMER0);
    let mut logo = microbit::logo::Logo::new(board.pins.p1_04);
    
    loop {
        if logo.is_touched(&mut timer) {
            // the logo is being touched
        }
    }
  7. Configure micro:bit hardware version via features

    main

    The microbit crate supports both micro:bit v1 and v2 hardware, but they cannot be enabled simultaneously. You must select exactly one version using Cargo features in your Cargo.toml:

    • For micro:bit v1: Enable the v1 feature (uses nrf51_hal).
    • For micro:bit v2: Enable the v2 feature (uses nrf52833_hal).

    Attempting to build with both features enabled will result in a compilation error.

    # Example for micro:bit v2
    [dependencies]
    microbit = { version = "0.16.0", features = ["v2"] }
  8. Reference: Board peripheral fields

    main

    The Board struct contains the following public fields for accessing hardware components:

    FieldDescription
    pinsUnused GPIO pins
    edgeUnused GPIO pins on the edge connector
    display_pinsPins used for the LED matrix display
    buttonsButtons (A and B)
    i2cI2C shared internal and external bus pins
    uartUART pins connected to the debugger
    CBPCore peripheral: Cache and branch predictor
    CPUIDCore peripheral: CPUID
    DCBCore peripheral: Debug Control Block
    DWTCore peripheral: Data Watchpoint and Trace unit
    FPBCore peripheral: Flash Patch and Breakpoint unit
    ITMCore peripheral: Instrumentation Trace Macrocell
    MPUCore peripheral: Memory Protection Unit
    NVICCore peripheral: Nested Vector Interrupt Controller
    SCBCore peripheral: System Control Block
    SYSTCore peripheral: SysTick Timer
    TPIUCore peripheral: Trace Port Interface Unit
    ADCnRF51 peripheral: ADC
    CLOCKnRF51 peripheral: CLOCK
    FICRnRF51 peripheral: FICR
    GPIOTEnRF51 peripheral: GPIOTE
    PPInRF51 peripheral: PPI
    RADIOnRF51 peripheral: RADIO
    RNGnRF51 peripheral: RNG
    RTC0nRF51 peripheral: RTC0
    TEMPnRF51 peripheral: TEMP (use with Temp::new())
    TIMER0nRF51 peripheral: TIMER0
    TIMER1nRF51 peripheral: TIMER1
    TIMER2nRF51 peripheral: TIMER2
    TWI0nRF51 peripheral: TWI0
    UART0nRF51 peripheral: UART0
    POWERnRF51 peripheral: POWER
    SPI0nRF51 peripheral: SPI0
    SPI1nRF51 peripheral: SPI1
    TWI1nRF51 peripheral: TWI1
    SPIS1nRF51 peripheral: SPIS1
    ECBnRF51 peripheral: ECB
    AARnRF51 peripheral: AAR
    CCMnRF51 peripheral: CCM
    WDTnRF51 peripheral: WDT
    RTC1nRF51 peripheral: RTC1
    QDECnRF51 peripheral: QDEC
    LPCOMPnRF51 peripheral: LPCOMP
    SWInRF51 peripheral: SWI
    NVMCnRF51 peripheral: NVMC
    UICRnRF51 peripheral: UICR
  9. Initialize LED display pins using the display_pins! macro

    main

    The display_pins! macro simplifies the construction of the DisplayPins struct by automatically configuring the necessary row and column pins for the micro:bit V1 LED matrix. It takes the P0 GPIO parts as input and initializes all pins as Output<PushPull> with an initial Level::Low.

    # use microbit_common as microbit;
    use microbit::{
        display_pins,
        pac,
        hal::gpio::p0::Parts as P0Parts,
    };
    
    // take the peripherals
    let p = pac::Peripherals::take().unwrap();
    // split off the P0 GPIO port
    let p0parts = P0Parts::new(p.GPIO);
    
    // Initialize the display pins
    let pins = display_pins!(p0parts);
  10. Initialize the micro:bit V1 board with existing peripherals

    main

    If you have already taken the pac::Peripherals and pac::CorePeripherals (for example, in an RTIC application), you can use Board::new(p, cp) to wrap them into a Board instance. This is a fallback method used when the standard take() pattern is not applicable.

    // Assuming p and cp are already obtained via pac::Peripherals::take() and pac::CorePeripherals::take()
    let board = Board::new(p, cp);
  11. Access the micro:bit V1 board using Board::take()

    main

    The Board struct provides a high-level abstraction for all peripherals on a micro:bit V1. To access the board safely, use the take() method. This method returns Some(Board) the first time it is called, but will return None on subsequent calls or if the underlying peripherals have already been taken elsewhere. This ensures singleton access to the hardware.

    let board = Board::take().expect("Failed to take board peripherals");
    // Use board.buttons, board.display_pins, etc.