ariel_os_stm32/i2c/controller/
mod.rs

1//! Provides support for the I2C communication bus in controller mode.
2
3#![expect(unsafe_code)]
4
5use ariel_os_embassy_common::{i2c::controller::Kilohertz, impl_async_i2c_for_driver_enum};
6use embassy_embedded_hal::adapter::{BlockingAsync, YieldingAsync};
7use embassy_stm32::{
8    Peripheral, bind_interrupts,
9    i2c::{EventInterruptHandler, I2c as InnerI2c, SclPin, SdaPin},
10    mode::Blocking,
11    peripherals,
12    time::Hertz,
13};
14
15/// I2C bus configuration.
16#[non_exhaustive]
17#[derive(Clone)]
18pub struct Config {
19    /// The frequency at which the bus should operate.
20    pub frequency: Frequency,
21    /// Whether to enable the internal pull-up resistor on the SDA pin.
22    pub sda_pullup: bool,
23    /// Whether to enable the internal pull-up resistor on the SCL pin.
24    pub scl_pullup: bool,
25}
26
27impl Default for Config {
28    fn default() -> Self {
29        Self {
30            frequency: Frequency::UpTo100k(Kilohertz::kHz(100)),
31            sda_pullup: false,
32            scl_pullup: false,
33        }
34    }
35}
36
37/// I2C bus frequency.
38// FIXME(embassy): fast mode plus is supported by hardware but requires additional configuration
39// that Embassy does not seem to currently provide.
40#[derive(Debug, Copy, Clone, PartialEq, Eq)]
41#[cfg_attr(feature = "defmt", derive(defmt::Format))]
42#[repr(u32)]
43pub enum Frequency {
44    /// Standard mode.
45    UpTo100k(Kilohertz), // FIXME: use a ranged integer?
46    /// Fast mode.
47    UpTo400k(Kilohertz), // FIXME: use a ranged integer?
48}
49
50#[doc(hidden)]
51impl Frequency {
52    #[must_use]
53    pub const fn first() -> Self {
54        Self::UpTo100k(Kilohertz::kHz(1))
55    }
56
57    #[must_use]
58    pub const fn last() -> Self {
59        Self::UpTo400k(Kilohertz::kHz(400))
60    }
61
62    #[must_use]
63    pub const fn next(self) -> Option<Self> {
64        match self {
65            Self::UpTo100k(f) => {
66                if f.to_kHz() < 100 {
67                    // NOTE(no-overflow): `f` is small enough due to if condition
68                    Some(Self::UpTo100k(Kilohertz::kHz(f.to_kHz() + 1)))
69                } else {
70                    Some(Self::UpTo400k(Kilohertz::kHz(self.khz() + 1)))
71                }
72            }
73            Self::UpTo400k(f) => {
74                if f.to_kHz() < 400 {
75                    // NOTE(no-overflow): `f` is small enough due to if condition
76                    Some(Self::UpTo400k(Kilohertz::kHz(f.to_kHz() + 1)))
77                } else {
78                    None
79                }
80            }
81        }
82    }
83
84    #[must_use]
85    pub const fn prev(self) -> Option<Self> {
86        match self {
87            Self::UpTo100k(f) => {
88                if f.to_kHz() > 1 {
89                    // NOTE(no-overflow): `f` is large enough due to if condition
90                    Some(Self::UpTo100k(Kilohertz::kHz(f.to_kHz() - 1)))
91                } else {
92                    None
93                }
94            }
95            Self::UpTo400k(f) => {
96                if f.to_kHz() > 100 + 1 {
97                    // NOTE(no-overflow): `f` is large enough due to if condition
98                    Some(Self::UpTo400k(Kilohertz::kHz(f.to_kHz() - 1)))
99                } else {
100                    Some(Self::UpTo100k(Kilohertz::kHz(self.khz() - 1)))
101                }
102            }
103        }
104    }
105
106    #[must_use]
107    pub const fn khz(self) -> u32 {
108        match self {
109            Self::UpTo100k(f) | Self::UpTo400k(f) => f.to_kHz(),
110        }
111    }
112}
113
114ariel_os_embassy_common::impl_i2c_from_frequency_up_to!();
115
116impl From<Frequency> for Hertz {
117    fn from(freq: Frequency) -> Self {
118        match freq {
119            Frequency::UpTo100k(f) | Frequency::UpTo400k(f) => Hertz::khz(f.to_kHz()),
120        }
121    }
122}
123
124macro_rules! define_i2c_drivers {
125    ($( $ev_interrupt:ident $( + $er_interrupt:ident )? => $peripheral:ident ),* $(,)?) => {
126        $(
127            /// Peripheral-specific I2C driver.
128            // NOTE(hal): this is not required in this HAL, as the inner I2C type is
129            // not generic over the I2C peripheral, and is only done for consistency with
130            // other HALs.
131            pub struct $peripheral {
132                twim: YieldingAsync<BlockingAsync<InnerI2c<'static, Blocking>>>,
133            }
134
135            impl $peripheral {
136                /// Returns a driver implementing [`embedded_hal_async::i2c::I2c`] for this
137                /// I2C peripheral.
138                #[expect(clippy::new_ret_no_self)]
139                #[must_use]
140                pub fn new(
141                    sda_pin: impl Peripheral<P: SdaPin<peripherals::$peripheral>> + 'static,
142                    scl_pin: impl Peripheral<P: SclPin<peripherals::$peripheral>> + 'static,
143                    config: Config,
144                ) -> I2c {
145                    let mut i2c_config = embassy_stm32::i2c::Config::default();
146                    i2c_config.sda_pullup = config.sda_pullup;
147                    i2c_config.scl_pullup = config.scl_pullup;
148                    i2c_config.timeout = ariel_os_embassy_common::i2c::controller::I2C_TIMEOUT;
149
150                    bind_interrupts!(
151                        struct Irqs {
152                            $ev_interrupt => EventInterruptHandler<peripherals::$peripheral>;
153                            $( $er_interrupt => embassy_stm32::i2c::ErrorInterruptHandler<peripherals::$peripheral>; )?
154                        }
155                    );
156
157                    // Make this struct a compile-time-enforced singleton: having multiple statics
158                    // defined with the same name would result in a compile-time error.
159                    paste::paste! {
160                        #[allow(dead_code)]
161                        static [<PREVENT_MULTIPLE_ $peripheral>]: () = ();
162                    }
163
164                    // FIXME(safety): enforce that the init code indeed has run
165                    // SAFETY: this struct being a singleton prevents us from stealing the
166                    // peripheral multiple times.
167                    let twim_peripheral = unsafe { peripherals::$peripheral::steal() };
168
169                    let frequency = config.frequency;
170                    let i2c = InnerI2c::new_blocking(
171                        twim_peripheral,
172                        scl_pin,
173                        sda_pin,
174                        frequency.into(),
175                        i2c_config,
176                    );
177
178                    I2c::$peripheral(Self { twim: YieldingAsync::new(BlockingAsync::new(i2c)) })
179                }
180            }
181        )*
182
183        /// Peripheral-agnostic driver.
184        pub enum I2c {
185            $(
186                #[doc = concat!(stringify!($peripheral), " peripheral.")]
187                $peripheral($peripheral),
188            )*
189        }
190
191        impl embedded_hal_async::i2c::ErrorType for I2c {
192            type Error = ariel_os_embassy_common::i2c::controller::Error;
193        }
194
195        impl_async_i2c_for_driver_enum!(I2c, $( $peripheral ),*);
196    }
197}
198
199// We cannot impl From because both types are external to this crate.
200fn from_error(err: embassy_stm32::i2c::Error) -> ariel_os_embassy_common::i2c::controller::Error {
201    use embassy_stm32::i2c::Error::{
202        Arbitration, Bus, Crc, Nack, Overrun, Timeout, ZeroLengthTransfer,
203    };
204
205    use ariel_os_embassy_common::i2c::controller::{Error, NoAcknowledgeSource};
206
207    match err {
208        Bus => Error::Bus,
209        Arbitration => Error::ArbitrationLoss,
210        Nack => Error::NoAcknowledge(NoAcknowledgeSource::Unknown),
211        Timeout => Error::Timeout,
212        Crc | ZeroLengthTransfer => Error::Other,
213        Overrun => Error::Overrun,
214    }
215}
216
217// Define a driver per peripheral
218#[cfg(context = "stm32c031c6")]
219define_i2c_drivers!(
220   I2C1 => I2C1,
221);
222#[cfg(context = "stm32f042k6")]
223define_i2c_drivers!(
224   I2C1 => I2C1,
225);
226#[cfg(any(context = "stm32f401re", context = "stm32f411re"))]
227define_i2c_drivers!(
228   I2C1_EV + I2C1_ER => I2C1,
229   I2C2_EV + I2C2_ER => I2C2,
230   I2C3_EV + I2C3_ER => I2C3,
231);
232#[cfg(any(context = "stm32h755zi", context = "stm32h753zi"))]
233define_i2c_drivers!(
234   I2C1_EV + I2C1_ER => I2C1,
235   I2C2_EV + I2C2_ER => I2C2,
236   I2C3_EV + I2C3_ER => I2C3,
237   I2C4_EV + I2C4_ER => I2C4,
238);
239#[cfg(context = "stm32l475vg")]
240define_i2c_drivers!(
241    I2C1_EV + I2C1_ER => I2C1,
242    I2C2_EV + I2C2_ER => I2C2,
243    I2C3_EV + I2C3_ER => I2C3,
244);
245#[cfg(context = "stm32u585ai")]
246define_i2c_drivers!(
247   I2C1_EV + I2C1_ER => I2C1,
248   I2C2_EV + I2C2_ER => I2C2,
249   I2C3_EV + I2C3_ER => I2C3,
250   I2C4_EV + I2C4_ER => I2C4,
251);
252#[cfg(any(context = "stm32u073kc", context = "stm32u083mc"))]
253define_i2c_drivers!(
254   I2C1 => I2C1,
255   // FIXME: the other three I2C peripherals share the same interrupt
256);
257#[cfg(context = "stm32wb55rg")]
258define_i2c_drivers!(
259   I2C1_EV + I2C1_ER => I2C1,
260   // There is no I2C2
261   I2C3_EV + I2C3_ER => I2C3,
262);