Skip to main content

oxstack_cardputer/
i2c.rs

1//! The internal I²C bus (GPIO8/9), shared by the TCA8418 keyboard scanner,
2//! ES8311 codec and BMI270 IMU.
3//!
4//! The bus lives in a `static` behind an async mutex, created once per boot
5//! by [`SysI2cResources::into_bus`]. Each driver gets its own
6//! [`SysI2cDevice`] handle and locks the bus only for the length of one
7//! transaction, so tasks on the same executor can use different chips
8//! concurrently. Code that didn't create the bus can find it with [`bus`].
9
10use embassy_embedded_hal::shared_bus::asynch::i2c::I2cDevice;
11use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
12use embassy_sync::mutex::Mutex;
13use embassy_sync::once_lock::OnceLock;
14use esp_hal::Async;
15use esp_hal::i2c::master::{Config, I2c};
16use esp_hal::peripherals::{GPIO8, GPIO9, I2C0};
17use esp_hal::time::Rate;
18
19/// The shared bus.
20pub type SysI2cBus = Mutex<CriticalSectionRawMutex, I2c<'static, Async>>;
21
22/// A handle to one device on the shared bus; implements
23/// `embedded_hal_async::i2c::I2c`.
24pub type SysI2cDevice = I2cDevice<'static, CriticalSectionRawMutex, I2c<'static, Async>>;
25
26/// Bus clock. All three on-board chips support Fast-mode (400 kHz).
27pub const FREQUENCY: Rate = Rate::from_khz(400);
28
29/// I²C0 on GPIO8 (SDA) / GPIO9 (SCL).
30#[allow(missing_docs)]
31pub struct SysI2cResources<'d> {
32    pub i2c: I2C0<'d>,
33    pub sda: GPIO8<'d>,
34    pub scl: GPIO9<'d>,
35}
36reborrow_group!(SysI2cResources { i2c, sda, scl });
37
38/// `I2c<Async>` is deliberately `!Send` in esp-hal: its interrupt handler is
39/// bound to the core that created it.
40struct SharedBus(SysI2cBus);
41
42// SAFETY: the bus is only ever reached through the async mutex inside, and
43// oxstack runs its executor on the core that created the bus, so no other
44// core touches the driver.
45unsafe impl Sync for SharedBus {}
46
47static BUS: OnceLock<SharedBus> = OnceLock::new();
48
49impl SysI2cResources<'static> {
50    /// Creates the shared bus. Call it once at start-up; get per-chip handles
51    /// with [`device`] and the bus itself, from anywhere, with [`bus`].
52    ///
53    /// # Panics
54    /// If the bus was already created; the resources are unique, so this
55    /// only happens through `unsafe` peripheral stealing.
56    pub fn into_bus(self) -> &'static SysI2cBus {
57        let i2c = I2c::new(self.i2c, Config::default().with_frequency(FREQUENCY))
58            .expect("400 kHz is a valid I2C configuration")
59            .with_sda(self.sda)
60            .with_scl(self.scl)
61            .into_async();
62        if BUS.init(SharedBus(Mutex::new(i2c))).is_err() {
63            panic!("the system I2C bus was already created");
64        }
65        bus().expect("just initialised")
66    }
67}
68
69/// The shared bus, if [`SysI2cResources::into_bus`] has been called.
70pub fn bus() -> Option<&'static SysI2cBus> {
71    BUS.try_get().map(|b| &b.0)
72}
73
74/// A new device handle on the shared bus.
75pub fn device(bus: &'static SysI2cBus) -> SysI2cDevice {
76    I2cDevice::new(bus)
77}