blackbox-rs 0.1.0

Embassy-based board support crate for the Blackbox board (STM32H743XI): clocks, SDRAM, LTDC display, LEDs, buttons, encoders, GT9147 touch and CS42528 audio.
Documentation

blackbox-rs

Board support crate for the Blackbox board — STM32H743XI (Cortex-M7 @ 399.36 MHz, rev.Y), built on Embassy.

One call brings the whole board up at its design clocks and hands you ready-to-use drivers for every on-board peripheral:

  • 11 LEDs — active-high indicators
  • 13 buttons — named, active-low (Button::Play, Button::Rec, …)
  • 4 endless encoders — dual-wiper, absolute angle via ADC1 (Knob::Tl, …)
  • GT9147 touchscreen — I2C, panel-aligned coordinates
  • 320×240 RGB565 display — LTDC, vblank-synced double buffer, embedded-graphics draw target, backlight
  • CS42528 audio — SAI + DMA, 8 DAC outputs / 2 ADC inputs, 48 kHz

The tricky bits (rev.Y cache erratum, the exact SDRAM timings, the SAI OLM topology the HAL can't express, the LTDC pin map with the unrouted R2 line) are all handled inside the crate — see Hardware below.

The bundled example renders a live debug screen — this is its framebuffer, captured straight off the panel's SDRAM over SWD:

Debug screen captured from the panel framebuffer

Quick start

# Cargo.toml
[dependencies]
blackbox-rs = "0.1"
embassy-executor = { version = "0.7", features = ["arch-cortex-m", "executor-thread"] }
embassy-time = "0.4"
#![no_std]
#![no_main]

use embassy_executor::Spawner;
use blackbox_rs::buttons::Button;
use {defmt_rtt as _, panic_probe as _};

#[embassy_executor::main]
async fn main(_spawner: Spawner) {
    // Clocks, CPU/MPU, SDRAM, every peripheral — plus a 440 Hz tone on the phones.
    let mut board = blackbox_rs::init().await;

    loop {
        if board.buttons.is_pressed(Button::Play) {
            defmt::info!("play!");
        }
        // draw with embedded-graphics on board.display.target(), then present at vblank:
        board.display.swap().await;
    }
}

blackbox_rs::init() returns a [Board]:

pub struct Board {
    pub display: display::Display,
    pub leds:    leds::Leds,
    pub buttons: buttons::Buttons,
    pub knobs:   knobs::Knobs,
    pub touch:   touch::Touch,
    pub i2c:     I2c<'static, Blocking>, // shared by codec + touch
    pub codec_ok: bool,
}

Using the peripherals

// LEDs — set individually, or light exactly one
board.leds.set(0, true);
board.leds.only(3);

// Buttons — typed, no magic indices
for b in Button::ALL {
    if board.buttons.is_pressed(b) {
        defmt::info!("{}", b.label());
    }
}

// Knobs — absolute angle from the two wipers
let r = board.knobs.read(Knob::Tl);
defmt::info!("TL = {} deg (a={}, b={})", r.angle_deg(), r.a, r.b);

// Touch — panel coordinates, None when idle
if let Some(tp) = board.touch.poll(&mut board.i2c) {
    defmt::info!("touch {},{}", tp.x, tp.y);
}

// Display — embedded-graphics on the back buffer, then swap
use embedded_graphics::{prelude::*, pixelcolor::Rgb565, primitives::*};
let mut fb = board.display.target();
Circle::new(Point::new(160, 120), 40)
    .into_styled(PrimitiveStyle::with_fill(Rgb565::CYAN))
    .draw(&mut fb)
    .ok();
board.display.swap().await;        // tear-free, paces to ~59 Hz
board.display.set_backlight(20);   // percent, clamped to the 35% ceiling

// Audio — route a sine to a stereo output port, or to raw DAC channels
use blackbox_rs::audio::{self, Dac, Output};
audio::play_sine_out(1000, Output::Jack1);          // → DAC3/4
audio::play_sine_on(1000, &[Dac::Dac1, Dac::Dac6]); // arbitrary channel set

Controls reference

Control Type Variants
buttons::Button enum Pads Keys Seqs Song Fx Mix Pset Tools Rec Stop Play Back Info
knobs::Knob enum Tl Tr Bl Br
audio::Dac enum Dac1Dac8
audio::Output enum Phones Jack1 Jack2 Jack3 (stereo pairs → .left()/.right()/.channels())
audio::AudioIn enum Adc1 Adc2

Output port → DAC mapping: Phones=DAC1/2, Jack1=DAC3/4, Jack2=DAC5/6, Jack3=DAC7/8 (assumed — adjust in audio::Output if the hardware differs).

Each enum has ::ALL, .index() and .label().

Prerequisites

rustup target add thumbv7em-none-eabihf
cargo install probe-rs-tools          # provides `probe-rs`

A debug probe wired to the board's SWD. The repo's .cargo/config.toml sets the target and a probe-rs run --chip STM32H743XI runner, so flashing the example is just:

cargo run --release        # builds, flashes, streams defmt/RTT logs
DEFMT_LOG=debug cargo run --release   # more verbose

The bundled src/main.rs is a live debug screen example: every control and its state on the panel, each LED lit while its button is held, and a 440 Hz tone — a quick bring-up smoke test.

Notes & safety

  • Backlight is hard-capped at 35% (display::MAX_BACKLIGHT_PCT) — the boost regulator's thermal limit. set_backlight clamps; do not raise without a thermal basis.
  • D-cache is off by design. SDRAM and the audio buffers are coherent with the LTDC/SAI DMA with no maintenance; the MPU still marks those regions non-cacheable so enabling D-cache later is correct (rev.Y erratum ES0392).
  • Audio currently renders a sine. The DMA-ISR double-buffer is in place; swap the sine source for a real render to stream arbitrary audio.
  • Touch latches its I2C address from the INT (PG12) level at the chip's own power-on: low → 0x5D (operational, config loaded), high → 0x14 (degraded, no scanning). init drives PG12 low across bring-up to bias 0x5D, but the chip only re-latches on a real power cycle (not an SWD reset) — if touch is dead and the log shows @ 0x14, power-cycle the board.

Hardware

STM32H743XI (TFBGA240, rev.Y), Cortex-M7 @ 399.36 MHz. Cargo feature stm32h743 (not stm32h743v — the v variant hangs ADC power-up). Facts recovered from working silicon.

Clocks — HSE is a 6.144 MHz crystal (clean audio divisors):

PLL Output Use
PLL1 P 399.36 MHz sysclk (AHB ÷2 → 199.68 MHz, SDCLK 100 MHz)
PLL2 P 12.288 MHz SAI kernel (256 × 48 kHz)
PLL3 R 6.4 MHz LTDC pixel clock (~59.4 Hz)

Memory

Region Base Size Notes
FLASH 0x0800_0000 2 MB
AXI SRAM 0x2400_0000 512 KB main RAM (.data/.bss/stack)
D2 SRAM 0x3000_0000 256 KB audio DMA buffers; clock-gated off at reset
SDRAM 0xC000_0000 64 MB external (FMC), framebuffers

rev.Y erratum ES0392: D-cache stays off; MPU marks SDRAM + D2 SRAM non-cacheable so the LTDC/SAI DMA stay coherent without maintenance.

Peripherals (pin → function)

Block Pins Notes
Display LTDC 25 pins, AF14 (R7=PJ0/G6=PI11 AF9) 320×240 RGB565; panel power PK7; R2 not routed
Backlight PJ6 (TIM8_CH2, AF3) 50 kHz, 35% hard cap (regulator thermal limit)
Audio SAI1/2 PE2/4/5 PB2 PE3 (AF6), PD11/12/13 (AF10) CS42528 codec, OLM 20-bit, 48 kHz
SDRAM FMC A/BA/D/NBL/ctrl, all AF12 2× IS42S16160J, CL2
I2C1 PB6 SCL / PB7 SDA (AF4) 400 kHz blocking; codec 0x4C + touch share it
Touch GT9147 INT PG12 addr 0x5D (low at POR) / 0x14 (degraded)
Buttons ×13 active-low, board pull-ups PA0/PA1/PC2/PC3 need the SYSCFG dual-pad fix
LEDs ×11 active-high
Knobs ×4 8 wipers on ADC1 endless encoders, atan2 angle

License

MIT OR Apache-2.0