Skip to main content

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