ariel_os_esp/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::impl_async_i2c_for_driver_enum;
6use esp_hal::{
7    Async,
8    gpio::interconnect::PeripheralOutput,
9    i2c::master::{BusTimeout, I2c as EspI2c},
10    peripherals,
11};
12
13/// I2C bus configuration.
14#[non_exhaustive]
15#[derive(Clone)]
16pub struct Config {
17    /// The frequency at which the bus should operate.
18    pub frequency: Frequency,
19}
20
21impl Default for Config {
22    fn default() -> Self {
23        Self {
24            frequency: Frequency::_100k,
25        }
26    }
27}
28
29/// I2C bus frequency.
30// NOTE(hal): the technical references only mention these frequencies.
31#[derive(Debug, Copy, Clone, PartialEq, Eq)]
32#[cfg_attr(feature = "defmt", derive(defmt::Format))]
33pub enum Frequency {
34    /// Standard mode.
35    _100k,
36    /// Fast mode.
37    _400k,
38}
39
40#[doc(hidden)]
41impl Frequency {
42    #[must_use]
43    pub const fn first() -> Self {
44        Self::_100k
45    }
46
47    #[must_use]
48    pub const fn last() -> Self {
49        Self::_400k
50    }
51
52    #[must_use]
53    pub const fn next(self) -> Option<Self> {
54        match self {
55            Self::_100k => Some(Self::_400k),
56            Self::_400k => None,
57        }
58    }
59
60    #[must_use]
61    pub const fn prev(self) -> Option<Self> {
62        match self {
63            Self::_100k => None,
64            Self::_400k => Some(Self::_100k),
65        }
66    }
67
68    #[must_use]
69    pub const fn khz(self) -> u32 {
70        match self {
71            Self::_100k => 100,
72            Self::_400k => 400,
73        }
74    }
75}
76
77ariel_os_embassy_common::impl_i2c_from_frequency!();
78
79impl From<Frequency> for esp_hal::time::Rate {
80    fn from(freq: Frequency) -> Self {
81        match freq {
82            Frequency::_100k => esp_hal::time::Rate::from_khz(100),
83            Frequency::_400k => esp_hal::time::Rate::from_khz(400),
84        }
85    }
86}
87
88macro_rules! define_i2c_drivers {
89    ($( $peripheral:ident ),* $(,)?) => {
90        $(
91            /// Peripheral-specific I2C driver.
92            pub struct $peripheral {
93                twim: EspI2c<'static, Async>,
94            }
95
96            impl $peripheral {
97                /// Returns a driver implementing [`embedded_hal_async::i2c::I2c`] for this
98                /// I2C peripheral.
99                #[expect(clippy::new_ret_no_self)]
100                #[must_use]
101                pub fn new<SDA: PeripheralOutput<'static>, SCL: PeripheralOutput<'static>>(
102                    sda_pin: impl $crate::IntoPeripheral<'static, SDA>,
103                    scl_pin: impl $crate::IntoPeripheral<'static, SCL>,
104                    config: Config,
105                ) -> I2c {
106                    // Make this struct a compile-time-enforced singleton: having multiple statics
107                    // defined with the same name would result in a compile-time error.
108                    paste::paste! {
109                        #[allow(dead_code)]
110                        static [<PREVENT_MULTIPLE_ $peripheral>]: () = ();
111                    }
112
113                    let twim_config = esp_hal::i2c::master::Config::default()
114                        .with_frequency(config.frequency.into())
115                        // disable timeout as we handle that at a higher level.
116                        .with_timeout(
117                            #[cfg(any(context = "esp32c3", context = "esp32c6", context = "esp32s2", context = "esp32s3"))]
118                            BusTimeout::Disabled,
119                            // Use the maximum value as timeout cannot be disabled.
120                            #[cfg(context = "esp32")]
121                            BusTimeout::Maximum
122                            );
123
124                    // FIXME(safety): enforce that the init code indeed has run
125                    // SAFETY: this struct being a singleton prevents us from stealing the
126                    // peripheral multiple times.
127                    let i2c_peripheral = unsafe { peripherals::$peripheral::steal() };
128
129                    let twim = EspI2c::new(
130                        i2c_peripheral,
131                        twim_config,
132                    )
133                        .unwrap()
134                        .into_async()
135                        .with_sda(sda_pin.into_hal_peripheral())
136                        .with_scl(scl_pin.into_hal_peripheral());
137
138                    I2c::$peripheral(Self { twim })
139                }
140            }
141        )*
142
143        /// Peripheral-agnostic driver.
144        pub enum I2c {
145            $(
146                #[doc = concat!(stringify!($peripheral), " peripheral.")]
147                $peripheral($peripheral),
148            )*
149        }
150
151        impl embedded_hal_async::i2c::ErrorType for I2c {
152            type Error = ariel_os_embassy_common::i2c::controller::Error;
153        }
154
155        impl_async_i2c_for_driver_enum!(I2c, $( $peripheral ),*);
156    }
157}
158
159// We cannot impl From because both types are external to this crate.
160fn from_error(err: esp_hal::i2c::master::Error) -> ariel_os_embassy_common::i2c::controller::Error {
161    use esp_hal::i2c::master::{AcknowledgeCheckFailedReason, Error as EspError};
162
163    use ariel_os_embassy_common::i2c::controller::{Error, NoAcknowledgeSource};
164
165    #[expect(clippy::match_same_arms, reason = "non-exhaustive upstream enum")]
166    match err {
167        EspError::FifoExceeded => Error::Overrun,
168        EspError::AcknowledgeCheckFailed(reason) => {
169            let reason = match reason {
170                AcknowledgeCheckFailedReason::Address => NoAcknowledgeSource::Address,
171                AcknowledgeCheckFailedReason::Data => NoAcknowledgeSource::Data,
172                AcknowledgeCheckFailedReason::Unknown | _ => NoAcknowledgeSource::Unknown,
173            };
174            Error::NoAcknowledge(reason)
175        }
176        EspError::Timeout => Error::Timeout,
177        EspError::ArbitrationLost => Error::ArbitrationLoss,
178        EspError::ExecutionIncomplete
179        | EspError::CommandNumberExceeded
180        | EspError::ZeroLengthInvalid => Error::Other,
181        _ => Error::Other,
182    }
183}
184
185// Define a driver per peripheral
186#[cfg(context = "esp32")]
187define_i2c_drivers!(I2C0, I2C1);
188#[cfg(context = "esp32c3")]
189define_i2c_drivers!(I2C0);
190#[cfg(context = "esp32c6")]
191define_i2c_drivers!(I2C0);
192#[cfg(context = "esp32s2")]
193define_i2c_drivers!(I2C0, I2C1);
194#[cfg(context = "esp32s3")]
195define_i2c_drivers!(I2C0, I2C1);