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 Peri, bind_interrupts,
9 gpio::Pin as GpioPin,
10 peripherals,
11 twim::{InterruptHandler, Twim},
12};
13
14use static_cell::ConstStaticCell;
15
16#[non_exhaustive]
18#[expect(clippy::struct_excessive_bools)]
19#[derive(Clone)]
20pub struct Config {
21 pub frequency: Frequency,
23 pub sda_pullup: bool,
25 pub scl_pullup: bool,
27 pub sda_high_drive: bool,
30 pub scl_high_drive: bool,
33}
34
35impl Default for Config {
36 fn default() -> Self {
37 Self {
38 frequency: Frequency::_100k,
39 sda_pullup: false,
40 scl_pullup: false,
41 sda_high_drive: false,
42 scl_high_drive: false,
43 }
44 }
45}
46
47#[cfg(any(
50 context = "nrf52833",
51 context = "nrf52840",
52 context = "nrf5340-app",
53 context = "nrf91"
54))]
55#[derive(Debug, Copy, Clone, PartialEq, Eq)]
56#[cfg_attr(feature = "defmt", derive(defmt::Format))]
57pub enum Frequency {
58 _100k,
60 #[cfg(any(context = "nrf52833", context = "nrf5340-app", context = "nrf91"))]
62 _250k,
63 _400k,
65 }
69
70#[doc(hidden)]
71impl Frequency {
72 #[must_use]
73 pub const fn first() -> Self {
74 Self::_100k
75 }
76
77 #[must_use]
78 pub const fn last() -> Self {
79 Self::_400k
80 }
81
82 #[must_use]
83 pub const fn next(self) -> Option<Self> {
84 match self {
85 #[cfg(context = "nrf52840")]
86 Self::_100k => Some(Self::_400k),
87 #[cfg(any(context = "nrf52833", context = "nrf5340-app", context = "nrf91"))]
88 Self::_100k => Some(Self::_250k),
89 #[cfg(any(context = "nrf52833", context = "nrf5340-app", context = "nrf91"))]
90 Self::_250k => Some(Self::_400k),
91 Self::_400k => None,
92 }
93 }
94
95 #[must_use]
96 pub const fn prev(self) -> Option<Self> {
97 match self {
98 Self::_100k => None,
99 #[cfg(any(context = "nrf52833", context = "nrf5340-app", context = "nrf91"))]
100 Self::_250k => Some(Self::_100k),
101 #[cfg(context = "nrf52840")]
102 Self::_400k => Some(Self::_100k),
103 #[cfg(any(context = "nrf52833", context = "nrf5340-app", context = "nrf91"))]
104 Self::_400k => Some(Self::_250k),
105 }
106 }
107
108 #[must_use]
109 pub const fn khz(self) -> u32 {
110 match self {
111 Self::_100k => 100,
112 #[cfg(any(context = "nrf52833", context = "nrf5340-app", context = "nrf91"))]
113 Self::_250k => 250,
114 Self::_400k => 400,
115 }
116 }
117}
118
119ariel_os_embassy_common::impl_i2c_from_frequency!();
120
121impl From<Frequency> for embassy_nrf::twim::Frequency {
122 fn from(freq: Frequency) -> Self {
123 match freq {
124 Frequency::_100k => embassy_nrf::twim::Frequency::K100,
125 #[cfg(any(context = "nrf52833", context = "nrf5340-app", context = "nrf91"))]
126 Frequency::_250k => embassy_nrf::twim::Frequency::K250,
127 Frequency::_400k => embassy_nrf::twim::Frequency::K400,
128 }
129 }
130}
131
132macro_rules! define_i2c_drivers {
133 ($( $interrupt:ident => $peripheral:ident ),* $(,)?) => {
134 $(
135 pub struct $peripheral {
137 twim: Twim<'static>,
138 }
139
140 impl $peripheral {
141 #[expect(clippy::new_ret_no_self)]
144 #[must_use]
145 pub fn new(
146 sda_pin: Peri<'static, impl GpioPin>,
147 scl_pin: Peri<'static, impl GpioPin>,
148 config: Config,
149 ) -> I2c {
150 let mut twim_config = embassy_nrf::twim::Config::default();
151 twim_config.frequency = config.frequency.into();
152 twim_config.sda_pullup = config.sda_pullup;
153 twim_config.scl_pullup = config.scl_pullup;
154 twim_config.sda_high_drive = config.sda_high_drive;
155 twim_config.scl_high_drive = config.scl_high_drive;
156
157 bind_interrupts!(
158 struct Irqs {
159 $interrupt => InterruptHandler<peripherals::$peripheral>;
160 }
161 );
162
163 paste::paste! {
166 #[allow(dead_code)]
167 static [<PREVENT_MULTIPLE_ $peripheral>]: () = ();
168 }
169
170 let twim_peripheral = unsafe { peripherals::$peripheral::steal() };
174
175 paste::paste! {
178 static [<RAM_BUFFER_ $peripheral>]: ConstStaticCell<[u8; 16]> = ConstStaticCell::new([0; 16]);
179 }
180
181 paste::paste! {
184 let twim = Twim::new(twim_peripheral, Irqs, sda_pin, scl_pin, twim_config, [<RAM_BUFFER_ $peripheral>].take());
185 }
186
187 I2c::$peripheral(Self { twim })
188 }
189 }
190 )*
191
192 pub enum I2c {
194 $(
195 #[doc = concat!(stringify!($peripheral), " peripheral.")]
196 $peripheral($peripheral),
197 )*
198 }
199
200 impl embedded_hal_async::i2c::ErrorType for I2c {
201 type Error = ariel_os_embassy_common::i2c::controller::Error;
202 }
203
204 impl_async_i2c_for_driver_enum!(I2c, $( $peripheral ),*);
205 }
206}
207
208fn from_error(err: embassy_nrf::twim::Error) -> ariel_os_embassy_common::i2c::controller::Error {
210 use embassy_nrf::twim::Error::{
211 AddressNack, DataNack, Overrun, RAMBufferTooSmall, Receive, RxBufferTooLong, Timeout,
212 Transmit, TxBufferTooLong,
213 };
214
215 use ariel_os_embassy_common::i2c::controller::{Error, NoAcknowledgeSource};
216
217 #[expect(clippy::match_same_arms, reason = "non-exhaustive upstream enum")]
218 match err {
219 TxBufferTooLong | RxBufferTooLong | Transmit | Receive | RAMBufferTooSmall => Error::Other,
220 AddressNack => Error::NoAcknowledge(NoAcknowledgeSource::Address),
221 DataNack => Error::NoAcknowledge(NoAcknowledgeSource::Data),
222 Overrun => Error::Overrun,
223 Timeout => Error::Timeout,
224 _ => Error::Other,
225 }
226}
227
228#[cfg(any(context = "nrf52833", context = "nrf52840"))]
231define_i2c_drivers!(
232 TWISPI0 => TWISPI0,
233 TWISPI1 => TWISPI1,
234);
235#[cfg(context = "nrf5340-app")]
236define_i2c_drivers!(
237 SERIAL0 => SERIAL0,
238 SERIAL1 => SERIAL1,
239);
240#[cfg(context = "nrf91")]
241define_i2c_drivers!(
242 SERIAL0 => SERIAL0,
243 SERIAL1 => SERIAL1,
244);