ariel_os_nrf/i2c/controller/
mod.rs1#![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#[non_exhaustive]
16#[expect(clippy::struct_excessive_bools)]
17#[derive(Clone)]
18pub struct Config {
19 pub frequency: Frequency,
21 pub sda_pullup: bool,
23 pub scl_pullup: bool,
25 pub sda_high_drive: bool,
28 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#[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 _100k,
58 #[cfg(any(context = "nrf52833", context = "nrf5340", context = "nrf91"))]
60 _250k,
61 _400k,
63 }
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 pub struct $peripheral {
135 twim: Twim<'static, peripherals::$peripheral>,
136 }
137
138 impl $peripheral {
139 #[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 paste::paste! {
164 #[allow(dead_code)]
165 static [<PREVENT_MULTIPLE_ $peripheral>]: () = ();
166 }
167
168 let twim_peripheral = unsafe { peripherals::$peripheral::steal() };
172
173 let twim = Twim::new(twim_peripheral, Irqs, sda_pin, scl_pin, twim_config);
176
177 I2c::$peripheral(Self { twim })
178 }
179 }
180 )*
181
182 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
198fn 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#[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);