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    Peri, bind_interrupts,
9    i2c::{EventInterruptHandler, I2c as InnerI2c, SclPin, SdaPin, mode::Master},
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, Master>>>,
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: Peri<'static, impl SdaPin<peripherals::$peripheral>>,
142                    scl_pin: Peri<'static, impl SclPin<peripherals::$peripheral>>,
143                    config: Config,
144                ) -> I2c {
145                    let mut i2c_config = embassy_stm32::i2c::Config::default();
146                    i2c_config.frequency = config.frequency.into();
147                    i2c_config.sda_pullup = config.sda_pullup;
148                    i2c_config.scl_pullup = config.scl_pullup;
149                    i2c_config.timeout = ariel_os_embassy_common::i2c::controller::I2C_TIMEOUT;
150
151                    bind_interrupts!(
152                        struct Irqs {
153                            $ev_interrupt => EventInterruptHandler<peripherals::$peripheral>;
154                            $( $er_interrupt => embassy_stm32::i2c::ErrorInterruptHandler<peripherals::$peripheral>; )?
155                        }
156                    );
157
158                    // Make this struct a compile-time-enforced singleton: having multiple statics
159                    // defined with the same name would result in a compile-time error.
160                    paste::paste! {
161                        #[allow(dead_code)]
162                        static [<PREVENT_MULTIPLE_ $peripheral>]: () = ();
163                    }
164
165                    // FIXME(safety): enforce that the init code indeed has run
166                    // SAFETY: this struct being a singleton prevents us from stealing the
167                    // peripheral multiple times.
168                    let twim_peripheral = unsafe { peripherals::$peripheral::steal() };
169
170                    let i2c = InnerI2c::new_blocking(
171                        twim_peripheral,
172                        scl_pin,
173                        sda_pin,
174                        i2c_config,
175                    );
176
177                    I2c::$peripheral(Self { twim: YieldingAsync::new(BlockingAsync::new(i2c)) })
178                }
179            }
180        )*
181
182        /// Peripheral-agnostic driver.
183        pub enum I2c {
184            $(
185                #[doc = concat!(stringify!($peripheral), " peripheral.")]
186                $peripheral($peripheral),
187            )*
188        }
189
190        impl embedded_hal_async::i2c::ErrorType for I2c {
191            type Error = ariel_os_embassy_common::i2c::controller::Error;
192        }
193
194        impl_async_i2c_for_driver_enum!(I2c, $( $peripheral ),*);
195    }
196}
197
198// We cannot impl From because both types are external to this crate.
199fn from_error(err: embassy_stm32::i2c::Error) -> ariel_os_embassy_common::i2c::controller::Error {
200    use embassy_stm32::i2c::Error::{
201        Arbitration, Bus, Crc, Nack, Overrun, Timeout, ZeroLengthTransfer,
202    };
203
204    use ariel_os_embassy_common::i2c::controller::{Error, NoAcknowledgeSource};
205
206    match err {
207        Bus => Error::Bus,
208        Arbitration => Error::ArbitrationLoss,
209        Nack => Error::NoAcknowledge(NoAcknowledgeSource::Unknown),
210        Timeout => Error::Timeout,
211        Crc | ZeroLengthTransfer => Error::Other,
212        Overrun => Error::Overrun,
213    }
214}
215
216// Define a driver per peripheral
217#[cfg(context = "stm32c031c6")]
218define_i2c_drivers!(
219   I2C1 => I2C1,
220);
221#[cfg(context = "stm32f042k6")]
222define_i2c_drivers!(
223   I2C1 => I2C1,
224);
225#[cfg(any(context = "stm32f401re", context = "stm32f411re"))]
226define_i2c_drivers!(
227   I2C1_EV + I2C1_ER => I2C1,
228   I2C2_EV + I2C2_ER => I2C2,
229   I2C3_EV + I2C3_ER => I2C3,
230);
231#[cfg(any(context = "stm32h755zi", context = "stm32h753zi"))]
232define_i2c_drivers!(
233   I2C1_EV + I2C1_ER => I2C1,
234   I2C2_EV + I2C2_ER => I2C2,
235   I2C3_EV + I2C3_ER => I2C3,
236   I2C4_EV + I2C4_ER => I2C4,
237);
238#[cfg(context = "stm32l475vg")]
239define_i2c_drivers!(
240    I2C1_EV + I2C1_ER => I2C1,
241    I2C2_EV + I2C2_ER => I2C2,
242    I2C3_EV + I2C3_ER => I2C3,
243);
244#[cfg(context = "stm32u585ai")]
245define_i2c_drivers!(
246   I2C1_EV + I2C1_ER => I2C1,
247   I2C2_EV + I2C2_ER => I2C2,
248   I2C3_EV + I2C3_ER => I2C3,
249   I2C4_EV + I2C4_ER => I2C4,
250);
251#[cfg(any(context = "stm32u073kc", context = "stm32u083mc"))]
252define_i2c_drivers!(
253   I2C1 => I2C1,
254   // FIXME: the other three I2C peripherals share the same interrupt
255);
256#[cfg(context = "stm32wb55rg")]
257define_i2c_drivers!(
258   I2C1_EV + I2C1_ER => I2C1,
259   // There is no I2C2
260   I2C3_EV + I2C3_ER => I2C3,
261);