ariel_os_nrf/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;
6
7use embassy_nrf::{
8    Peripheral, bind_interrupts,
9    gpio::Pin as GpioPin,
10    peripherals,
11    twim::{InterruptHandler, Twim},
12};
13
14/// I2C bus configuration.
15#[non_exhaustive]
16#[expect(clippy::struct_excessive_bools)]
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    /// Whether to set the SDA pin's drive strength to
26    /// [`DriveStrength::High`](crate::gpio::DriveStrength::High).
27    pub sda_high_drive: bool,
28    /// Whether to set the SCL pin's drive strength to
29    /// [`DriveStrength::High`](crate::gpio::DriveStrength::High).
30    pub scl_high_drive: bool,
31}
32
33impl Default for Config {
34    fn default() -> Self {
35        Self {
36            frequency: Frequency::_100k,
37            sda_pullup: false,
38            scl_pullup: false,
39            sda_high_drive: false,
40            scl_high_drive: false,
41        }
42    }
43}
44
45/// I2C bus frequency.
46// NOTE(hal): the datasheets only mention these frequencies.
47#[cfg(any(
48    context = "nrf52833",
49    context = "nrf52840",
50    context = "nrf5340",
51    context = "nrf91"
52))]
53#[derive(Debug, Copy, Clone, PartialEq, Eq)]
54#[cfg_attr(feature = "defmt", derive(defmt::Format))]
55pub enum Frequency {
56    /// Standard mode.
57    _100k,
58    /// 250 kHz.
59    #[cfg(any(context = "nrf52833", context = "nrf5340", context = "nrf91"))]
60    _250k,
61    /// Fast mode.
62    _400k,
63    // FIXME(embassy): the upstream Embassy crate does not support this frequency
64    // #[cfg(context = "nrf5340", context = "nrf91")]
65    // _1M,
66}
67
68#[doc(hidden)]
69impl Frequency {
70    #[must_use]
71    pub const fn first() -> Self {
72        Self::_100k
73    }
74
75    #[must_use]
76    pub const fn last() -> Self {
77        Self::_400k
78    }
79
80    #[must_use]
81    pub const fn next(self) -> Option<Self> {
82        match self {
83            #[cfg(context = "nrf52840")]
84            Self::_100k => Some(Self::_400k),
85            #[cfg(any(context = "nrf52833", context = "nrf5340", context = "nrf91"))]
86            Self::_100k => Some(Self::_250k),
87            #[cfg(any(context = "nrf52833", context = "nrf5340", context = "nrf91"))]
88            Self::_250k => Some(Self::_400k),
89            Self::_400k => None,
90        }
91    }
92
93    #[must_use]
94    pub const fn prev(self) -> Option<Self> {
95        match self {
96            Self::_100k => None,
97            #[cfg(any(context = "nrf52833", context = "nrf5340", context = "nrf91"))]
98            Self::_250k => Some(Self::_100k),
99            #[cfg(context = "nrf52840")]
100            Self::_400k => Some(Self::_100k),
101            #[cfg(any(context = "nrf52833", context = "nrf5340", context = "nrf91"))]
102            Self::_400k => Some(Self::_250k),
103        }
104    }
105
106    #[must_use]
107    pub const fn khz(self) -> u32 {
108        match self {
109            Self::_100k => 100,
110            #[cfg(any(context = "nrf52833", context = "nrf5340", context = "nrf91"))]
111            Self::_250k => 250,
112            Self::_400k => 400,
113        }
114    }
115}
116
117ariel_os_embassy_common::impl_i2c_from_frequency!();
118
119impl From<Frequency> for embassy_nrf::twim::Frequency {
120    fn from(freq: Frequency) -> Self {
121        match freq {
122            Frequency::_100k => embassy_nrf::twim::Frequency::K100,
123            #[cfg(any(context = "nrf52833", context = "nrf5340", context = "nrf91"))]
124            Frequency::_250k => embassy_nrf::twim::Frequency::K250,
125            Frequency::_400k => embassy_nrf::twim::Frequency::K400,
126        }
127    }
128}
129
130macro_rules! define_i2c_drivers {
131    ($( $interrupt:ident => $peripheral:ident ),* $(,)?) => {
132        $(
133            /// Peripheral-specific I2C driver.
134            pub struct $peripheral {
135                twim: Twim<'static, peripherals::$peripheral>,
136            }
137
138            impl $peripheral {
139                /// Returns a driver implementing [`embedded_hal_async::i2c::I2c`] for this
140                /// I2C peripheral.
141                #[expect(clippy::new_ret_no_self)]
142                #[must_use]
143                pub fn new(
144                    sda_pin: impl Peripheral<P: GpioPin> + 'static,
145                    scl_pin: impl Peripheral<P: GpioPin> + 'static,
146                    config: Config,
147                ) -> I2c {
148                    let mut twim_config = embassy_nrf::twim::Config::default();
149                    twim_config.frequency = config.frequency.into();
150                    twim_config.sda_pullup = config.sda_pullup;
151                    twim_config.scl_pullup = config.scl_pullup;
152                    twim_config.sda_high_drive = config.sda_high_drive;
153                    twim_config.scl_high_drive = config.scl_high_drive;
154
155                    bind_interrupts!(
156                        struct Irqs {
157                            $interrupt => InterruptHandler<peripherals::$peripheral>;
158                        }
159                    );
160
161                    // Make this struct a compile-time-enforced singleton: having multiple statics
162                    // defined with the same name would result in a compile-time error.
163                    paste::paste! {
164                        #[allow(dead_code)]
165                        static [<PREVENT_MULTIPLE_ $peripheral>]: () = ();
166                    }
167
168                    // FIXME(safety): enforce that the init code indeed has run
169                    // SAFETY: this struct being a singleton prevents us from stealing the
170                    // peripheral multiple times.
171                    let twim_peripheral = unsafe { peripherals::$peripheral::steal() };
172
173                    // NOTE(hal): the I2C peripheral and driver do not have any built-in timeout,
174                    // we implement it at a higher level, not in this HAL-specific module.
175                    let twim = Twim::new(twim_peripheral, Irqs, sda_pin, scl_pin, twim_config);
176
177                    I2c::$peripheral(Self { twim })
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_nrf::twim::Error) -> ariel_os_embassy_common::i2c::controller::Error {
200    use embassy_nrf::twim::Error::{
201        AddressNack, BufferNotInRAM, DataNack, Overrun, Receive, RxBufferTooLong, Timeout,
202        Transmit, TxBufferTooLong,
203    };
204
205    use ariel_os_embassy_common::i2c::controller::{Error, NoAcknowledgeSource};
206
207    #[expect(clippy::match_same_arms, reason = "non-exhaustive upstream enum")]
208    match err {
209        TxBufferTooLong | RxBufferTooLong | Transmit | Receive | BufferNotInRAM => Error::Other,
210        AddressNack => Error::NoAcknowledge(NoAcknowledgeSource::Address),
211        DataNack => Error::NoAcknowledge(NoAcknowledgeSource::Data),
212        Overrun => Error::Overrun,
213        Timeout => Error::Timeout,
214        _ => Error::Other,
215    }
216}
217
218// FIXME: support other nRF archs
219// Define a driver per peripheral
220#[cfg(any(context = "nrf52833", context = "nrf52840"))]
221define_i2c_drivers!(
222    TWISPI0 => TWISPI0,
223    TWISPI1 => TWISPI1,
224);
225#[cfg(context = "nrf5340")]
226define_i2c_drivers!(
227    SERIAL0 => SERIAL0,
228    SERIAL1 => SERIAL1,
229);
230#[cfg(context = "nrf91")]
231define_i2c_drivers!(
232    SERIAL0 => SERIAL0,
233    SERIAL1 => SERIAL1,
234);