ariel_os_nrf/i2c/controller/
mod.rs1#![expect(unsafe_code)]
4
5use portable_atomic::{AtomicBool, Ordering};
6
7use ariel_os_embassy_common::impl_async_i2c_for_driver_enum;
8
9use embassy_nrf::{
10 bind_interrupts,
11 gpio::Pin as GpioPin,
12 peripherals,
13 twim::{InterruptHandler, Twim},
14};
15
16use static_cell::ConstStaticCell;
17
18#[non_exhaustive]
20#[expect(clippy::struct_excessive_bools)]
21#[derive(Clone)]
22pub struct Config {
23 pub frequency: Frequency,
25 pub sda_pullup: bool,
27 pub scl_pullup: bool,
29 pub sda_high_drive: bool,
32 pub scl_high_drive: bool,
35}
36
37impl Default for Config {
38 fn default() -> Self {
39 Self {
40 frequency: Frequency::_100k,
41 sda_pullup: false,
42 scl_pullup: false,
43 sda_high_drive: false,
44 scl_high_drive: false,
45 }
46 }
47}
48
49#[cfg(any(
52 context = "nrf52833",
53 context = "nrf52840",
54 context = "nrf5340-app",
55 context = "nrf91"
56))]
57#[derive(Debug, Copy, Clone, PartialEq, Eq)]
58#[cfg_attr(feature = "defmt", derive(defmt::Format))]
59pub enum Frequency {
60 _100k,
62 #[cfg(any(context = "nrf52833", context = "nrf5340-app", context = "nrf91"))]
64 _250k,
65 _400k,
67 }
71
72#[doc(hidden)]
73impl Frequency {
74 #[must_use]
75 pub const fn first() -> Self {
76 Self::_100k
77 }
78
79 #[must_use]
80 pub const fn last() -> Self {
81 Self::_400k
82 }
83
84 #[must_use]
85 pub const fn next(self) -> Option<Self> {
86 match self {
87 #[cfg(context = "nrf52840")]
88 Self::_100k => Some(Self::_400k),
89 #[cfg(any(context = "nrf52833", context = "nrf5340-app", context = "nrf91"))]
90 Self::_100k => Some(Self::_250k),
91 #[cfg(any(context = "nrf52833", context = "nrf5340-app", context = "nrf91"))]
92 Self::_250k => Some(Self::_400k),
93 Self::_400k => None,
94 }
95 }
96
97 #[must_use]
98 pub const fn prev(self) -> Option<Self> {
99 match self {
100 Self::_100k => None,
101 #[cfg(any(context = "nrf52833", context = "nrf5340-app", context = "nrf91"))]
102 Self::_250k => Some(Self::_100k),
103 #[cfg(context = "nrf52840")]
104 Self::_400k => Some(Self::_100k),
105 #[cfg(any(context = "nrf52833", context = "nrf5340-app", context = "nrf91"))]
106 Self::_400k => Some(Self::_250k),
107 }
108 }
109
110 #[must_use]
111 pub const fn khz(self) -> u32 {
112 match self {
113 Self::_100k => 100,
114 #[cfg(any(context = "nrf52833", context = "nrf5340-app", context = "nrf91"))]
115 Self::_250k => 250,
116 Self::_400k => 400,
117 }
118 }
119}
120
121ariel_os_embassy_common::impl_i2c_from_frequency!();
122
123impl From<Frequency> for embassy_nrf::twim::Frequency {
124 fn from(freq: Frequency) -> Self {
125 match freq {
126 Frequency::_100k => embassy_nrf::twim::Frequency::K100,
127 #[cfg(any(context = "nrf52833", context = "nrf5340-app", context = "nrf91"))]
128 Frequency::_250k => embassy_nrf::twim::Frequency::K250,
129 Frequency::_400k => embassy_nrf::twim::Frequency::K400,
130 }
131 }
132}
133
134macro_rules! define_i2c_drivers {
135 ($( $interrupt:ident => $peripheral:ident ),* $(,)?) => {
136 $(
137 pub struct $peripheral {
139 twim: Twim<'static>,
140 }
141
142 paste::paste! {
144 static [< ACTIVE_ $peripheral >]: AtomicBool = AtomicBool::new(false);
145 }
146
147 impl $peripheral {
148 #[expect(clippy::new_ret_no_self)]
151 #[must_use]
152 pub fn new<SDA: GpioPin, SCL: GpioPin>(
153 sda_pin: impl $crate::IntoPeripheral<'static, SDA>,
154 scl_pin: impl $crate::IntoPeripheral<'static, SCL>,
155 config: Config,
156 ) -> I2c {
157 let mut twim_config = embassy_nrf::twim::Config::default();
158 twim_config.frequency = config.frequency.into();
159 twim_config.sda_pullup = config.sda_pullup;
160 twim_config.scl_pullup = config.scl_pullup;
161 twim_config.sda_high_drive = config.sda_high_drive;
162 twim_config.scl_high_drive = config.scl_high_drive;
163
164 bind_interrupts!(
165 struct Irqs {
166 $interrupt => InterruptHandler<peripherals::$peripheral>;
167 }
168 );
169
170 paste::paste! {
173 #[allow(dead_code)]
174 static [<PREVENT_MULTIPLE_ $peripheral>]: () = ();
175 }
176
177 paste::paste! {
179 if [< ACTIVE_ $peripheral >].swap(true, Ordering::AcqRel) {
180 panic!("I2C peripheral already initialized")
181 }
182 }
183
184 let twim_peripheral = unsafe { peripherals::$peripheral::steal() };
188
189 paste::paste! {
192 static [<RAM_BUFFER_ $peripheral>]: ConstStaticCell<[u8; 16]> = ConstStaticCell::new([0; 16]);
193 }
194
195 paste::paste! {
198 let twim = Twim::new(
199 twim_peripheral,
200 Irqs,
201 sda_pin.into_hal_peripheral(),
202 scl_pin.into_hal_peripheral(),
203 twim_config,
204 [<RAM_BUFFER_ $peripheral>].take(),
205 );
206 }
207
208 I2c::$peripheral(Self { twim })
209 }
210 }
211
212 impl Drop for $peripheral {
213 fn drop(&mut self) {
214 paste::paste! {
215 [< ACTIVE_ $peripheral >].store(false, Ordering::Release);
216 }
217 }
218 }
219 )*
220
221 pub enum I2c {
223 $(
224 #[doc = concat!(stringify!($peripheral), " peripheral.")]
225 $peripheral($peripheral),
226 )*
227 }
228
229 impl embedded_hal_async::i2c::ErrorType for I2c {
230 type Error = ariel_os_embassy_common::i2c::controller::Error;
231 }
232
233 impl_async_i2c_for_driver_enum!(I2c, $( $peripheral ),*);
234 }
235}
236
237fn from_error(err: embassy_nrf::twim::Error) -> ariel_os_embassy_common::i2c::controller::Error {
239 use embassy_nrf::twim::Error::{
240 AddressNack, DataNack, Overrun, RAMBufferTooSmall, Receive, RxBufferTooLong, Timeout,
241 Transmit, TxBufferTooLong,
242 };
243
244 use ariel_os_embassy_common::i2c::controller::{Error, NoAcknowledgeSource};
245
246 #[expect(clippy::match_same_arms, reason = "non-exhaustive upstream enum")]
247 match err {
248 TxBufferTooLong | RxBufferTooLong | Transmit | Receive | RAMBufferTooSmall => Error::Other,
249 AddressNack => Error::NoAcknowledge(NoAcknowledgeSource::Address),
250 DataNack => Error::NoAcknowledge(NoAcknowledgeSource::Data),
251 Overrun => Error::Overrun,
252 Timeout => Error::Timeout,
253 _ => Error::Other,
254 }
255}
256
257#[cfg(any(context = "nrf52833", context = "nrf52840"))]
260define_i2c_drivers!(
261 TWISPI0 => TWISPI0,
262 TWISPI1 => TWISPI1,
263);
264#[cfg(context = "nrf5340-app")]
265define_i2c_drivers!(
266 SERIAL0 => SERIAL0,
267 SERIAL1 => SERIAL1,
268);
269#[cfg(context = "nrf91")]
270define_i2c_drivers!(
271 SERIAL0 => SERIAL0,
272 SERIAL1 => SERIAL1,
273);