Skip to main content

blackbox_rs/
lib.rs

1//! Board support crate for the Blackbox board (STM32H743XI, rev.Y).
2//!
3//! [`init`] brings the whole board up at its design clocks and returns a [`Board`] holding
4//! ready-to-use drivers. Each subsystem also stands alone in its own module if you want to
5//! wire a custom subset.
6//!
7//! ```ignore
8//! #[embassy_executor::main]
9//! async fn main(_spawner: Spawner) -> ! {
10//!     let mut board = blackbox_rs::init().await;
11//!     loop {
12//!         let touch = board.touch.poll(&mut board.i2c);
13//!         board.display.target().clear(Rgb565::BLACK).ok();
14//!         board.display.swap().await;
15//!     }
16//! }
17//! ```
18
19#![no_std]
20
21pub mod audio;
22pub mod buttons;
23pub mod clock;
24pub mod cpu;
25pub mod display;
26pub mod knobs;
27pub mod leds;
28pub mod sdram;
29pub mod touch;
30
31use embassy_stm32::adc::AdcChannel;
32use embassy_stm32::fmc::Fmc;
33use embassy_stm32::gpio::{Level, Output, Speed};
34use embassy_stm32::i2c::{self, I2c, Master};
35use embassy_stm32::mode::Blocking;
36use embassy_stm32::time::Hertz;
37use embassy_stm32::{bind_interrupts, peripherals};
38
39bind_interrupts!(pub struct Irqs {
40    LTDC => embassy_stm32::ltdc::InterruptHandler<peripherals::LTDC>;
41});
42
43/// The fully initialized board. Touch owns its handle to the shared I2C1 bus.
44pub struct Board {
45    pub display: display::Display,
46    pub leds: leds::Leds,
47    pub buttons: buttons::Buttons,
48    pub knobs: knobs::Knobs,
49    pub touch: touch::Touch,
50    /// Shared I2C1 bus — pass to `touch.poll(&mut board.i2c)` (the codec also lives here).
51    pub i2c: I2c<'static, Blocking, Master>,
52    /// Whether the CS42528 codec acked its init sequence.
53    pub codec_ok: bool,
54}
55
56/// Bring up clocks, CPU/MPU, SDRAM and every on-board peripheral, then start a 440 Hz tone.
57pub async fn init() -> Board {
58    let p = embassy_stm32::init(clock::config());
59    defmt::info!("blackbox: STM32H743XI, sysclk 399.36 MHz");
60
61    cpu::init();
62    cpu::dual_pad_fix();
63
64    // SDRAM (FMC bank1). The constructor consumes ~50 GPIO singletons (all AF12).
65    let mut sdram = Fmc::sdram_a13bits_d32bits_4banks_bank1(
66        p.FMC, //
67        p.PF0, p.PF1, p.PF2, p.PF3, p.PF4, p.PF5, p.PF12, p.PF13, p.PF14, p.PF15, p.PG0, p.PG1,
68        p.PG2, // A0-A12
69        p.PG4, p.PG5, // BA0-1
70        p.PD14, p.PD15, p.PD0, p.PD1, p.PE7, p.PE8, p.PE9, p.PE10, p.PE11, p.PE12, p.PE13, p.PE14,
71        p.PE15, p.PD8, p.PD9, p.PD10, p.PH8, p.PH9, p.PH10, p.PH11, p.PH12, p.PH13, p.PH14, p.PH15,
72        p.PI0, p.PI1, p.PI2, p.PI3, p.PI6, p.PI7, p.PI9, p.PI10, // D0-D31
73        p.PE0, p.PE1, p.PI4, p.PI5, // NBL0-3
74        p.PH2, p.PG8, p.PG15, p.PH3, p.PF11, p.PH5, // ctrl
75        sdram::Is42s16160j,
76    );
77    let mut delay = sdram::BusyDelay(800);
78    let base = sdram.init(&mut delay) as usize;
79    sdram::smoke_test(base);
80
81    let leds = leds::Leds::new([
82        p.PG9.into(),
83        p.PJ8.into(),
84        p.PB10.into(),
85        p.PB8.into(),
86        p.PB9.into(),
87        p.PK2.into(),
88        p.PA5.into(),
89        p.PJ5.into(),
90        p.PJ4.into(),
91        p.PB11.into(),
92        p.PA4.into(),
93    ]);
94
95    let buttons = buttons::Buttons::new([
96        p.PI8.into(),
97        p.PD4.into(),
98        p.PD7.into(),
99        p.PI12.into(),
100        p.PI14.into(),
101        p.PG3.into(),
102        p.PH7.into(),
103        p.PC13.into(),
104        p.PB13.into(),
105        p.PJ1.into(),
106        p.PH4.into(),
107        p.PC2.into(),
108        p.PC3.into(),
109    ]);
110
111    let knobs = knobs::Knobs::new(
112        p.ADC1,
113        [
114            p.PA0.degrade_adc(), // TL a
115            p.PA1.degrade_adc(), // TL b
116            p.PA6.degrade_adc(), // TR a
117            p.PC4.degrade_adc(), // TR b
118            p.PB1.degrade_adc(), // BL a
119            p.PA7.degrade_adc(), // BL b
120            p.PC5.degrade_adc(), // BR a
121            p.PB0.degrade_adc(), // BR b
122        ],
123    );
124
125    // Display first (panel power), matching the proven order — then the I2C devices.
126    let display = display::Display::new(p.LTDC, p.PK7, p.TIM8, p.PJ6, base).await;
127
128    // Shared I2C1 bus (touch + codec), accessed sequentially on this one executor.
129    let mut i2c_config = i2c::Config::default();
130    i2c_config.frequency = Hertz(400_000);
131    let mut i2c = I2c::new_blocking(p.I2C1, p.PB6, p.PB7, i2c_config);
132
133    let mut codec_rst = Output::new(p.PG13, Level::High, Speed::Low);
134    let codec_ok = audio::init_codec(&mut i2c, &mut codec_rst).await;
135
136    // Touch INT (PG12) reset/address strap: one clean drive-low, held through Touch::new,
137    // which floats it — the low→float edge re-latches the operational 0x5D address.
138    let touch_int = touch::bias_int_low(p.PG12.into());
139    let touch = touch::Touch::new(&mut i2c, touch_int).await;
140
141    audio::play_sine(440);
142    defmt::info!("blackbox: board up — controls + display + audio live");
143
144    Board {
145        display,
146        leds,
147        buttons,
148        knobs,
149        touch,
150        i2c,
151        codec_ok,
152    }
153}