ariel_os_rp/i2c/controller/
mod.rs1#![expect(unsafe_code)]
4
5use portable_atomic::{AtomicBool, Ordering};
6
7use ariel_os_embassy_common::{i2c::controller::Kilohertz, impl_async_i2c_for_driver_enum};
8use embassy_rp::{
9 bind_interrupts,
10 i2c::{InterruptHandler, SclPin, SdaPin},
11 peripherals,
12};
13
14const KHZ_TO_HZ: u32 = 1000;
15
16#[derive(Clone)]
20#[non_exhaustive]
21pub struct Config {
22 pub frequency: Frequency,
24 pub sda_pullup: bool,
26 pub scl_pullup: bool,
28}
29
30impl Default for Config {
31 fn default() -> Self {
32 Self {
33 frequency: Frequency::UpTo100k(Kilohertz::kHz(100)),
34 sda_pullup: false,
35 scl_pullup: false,
36 }
37 }
38}
39
40#[derive(Debug, Copy, Clone, PartialEq, Eq)]
44#[cfg_attr(feature = "defmt", derive(defmt::Format))]
45#[repr(u32)]
46pub enum Frequency {
47 UpTo100k(Kilohertz), UpTo400k(Kilohertz), }
52
53#[doc(hidden)]
54impl Frequency {
55 #[must_use]
56 pub const fn first() -> Self {
57 Self::UpTo100k(Kilohertz::kHz(1))
58 }
59
60 #[must_use]
61 pub const fn last() -> Self {
62 Self::UpTo400k(Kilohertz::kHz(400))
63 }
64
65 #[must_use]
66 pub const fn next(self) -> Option<Self> {
67 match self {
68 Self::UpTo100k(f) => {
69 if f.to_kHz() < 100 {
70 Some(Self::UpTo100k(Kilohertz::kHz(f.to_kHz() + 1)))
72 } else {
73 Some(Self::UpTo400k(Kilohertz::kHz(self.khz() + 1)))
74 }
75 }
76 Self::UpTo400k(f) => {
77 if f.to_kHz() < 400 {
78 Some(Self::UpTo400k(Kilohertz::kHz(f.to_kHz() + 1)))
80 } else {
81 None
82 }
83 }
84 }
85 }
86
87 #[must_use]
88 pub const fn prev(self) -> Option<Self> {
89 match self {
90 Self::UpTo100k(f) => {
91 if f.to_kHz() > 1 {
92 Some(Self::UpTo100k(Kilohertz::kHz(f.to_kHz() - 1)))
94 } else {
95 None
96 }
97 }
98 Self::UpTo400k(f) => {
99 if f.to_kHz() > 100 + 1 {
100 Some(Self::UpTo400k(Kilohertz::kHz(f.to_kHz() - 1)))
102 } else {
103 Some(Self::UpTo100k(Kilohertz::kHz(self.khz() - 1)))
104 }
105 }
106 }
107 }
108
109 #[must_use]
110 pub const fn khz(self) -> u32 {
111 match self {
112 Self::UpTo100k(f) | Self::UpTo400k(f) => f.to_kHz(),
113 }
114 }
115}
116
117ariel_os_embassy_common::impl_i2c_from_frequency_up_to!();
118
119macro_rules! define_i2c_drivers {
120 ($( $interrupt:ident => $peripheral:ident ),* $(,)?) => {
121 $(
122 pub struct $peripheral {
124 twim: embassy_rp::i2c::I2c<'static, peripherals::$peripheral, embassy_rp::i2c::Async>,
125 }
126
127 paste::paste! {
129 static [< ACTIVE_ $peripheral >]: AtomicBool = AtomicBool::new(false);
130 }
131
132 impl $peripheral {
133 #[expect(clippy::new_ret_no_self)]
136 #[must_use]
137 pub fn new<SDA: SdaPin<peripherals::$peripheral>, SCL: SclPin<peripherals::$peripheral>>(
138 sda_pin: impl $crate::IntoPeripheral<'static, SDA>,
139 scl_pin: impl $crate::IntoPeripheral<'static, SCL>,
140 config: Config,
141 ) -> I2c {
142 paste::paste! {
145 #[allow(dead_code)]
146 static [<PREVENT_MULTIPLE_ $peripheral>]: () = ();
147 }
148
149 let mut i2c_config = embassy_rp::i2c::Config::default();
150 i2c_config.frequency = config.frequency.khz() * KHZ_TO_HZ;
151 i2c_config.sda_pullup = config.sda_pullup;
152 i2c_config.scl_pullup = config.scl_pullup;
153
154 bind_interrupts!(
155 struct Irqs {
156 $interrupt => InterruptHandler<peripherals::$peripheral>;
157 }
158 );
159
160 paste::paste! {
162 if [< ACTIVE_ $peripheral >].swap(true, Ordering::AcqRel) {
163 panic!("I2C peripheral already initialized")
164 }
165 }
166
167 let i2c_peripheral = unsafe { peripherals::$peripheral::steal() };
171
172 let i2c = embassy_rp::i2c::I2c::new_async(
175 i2c_peripheral,
176 scl_pin.into_hal_peripheral(),
177 sda_pin.into_hal_peripheral(),
178 Irqs,
179 i2c_config,
180 );
181
182 I2c::$peripheral(Self { twim: i2c })
183 }
184 }
185
186 impl Drop for $peripheral {
187 fn drop(&mut self) {
188 paste::paste! {
189 [< ACTIVE_ $peripheral >].store(false, Ordering::Release);
190 }
191 }
192 }
193 )*
194
195 pub enum I2c {
197 $(
198 #[doc = concat!(stringify!($peripheral), " peripheral.")]
199 $peripheral($peripheral),
200 )*
201 }
202
203 impl embedded_hal_async::i2c::ErrorType for I2c {
204 type Error = ariel_os_embassy_common::i2c::controller::Error;
205 }
206
207 impl_async_i2c_for_driver_enum!(I2c, $( $peripheral ),*);
208 }
209}
210
211fn from_error(err: embassy_rp::i2c::Error) -> ariel_os_embassy_common::i2c::controller::Error {
213 #[allow(deprecated)]
214 use embassy_rp::i2c::{
215 AbortReason,
216 Error::{
217 Abort, AddressOutOfRange, AddressReserved, InvalidReadBufferLength,
218 InvalidWriteBufferLength,
219 },
220 };
221
222 use ariel_os_embassy_common::i2c::controller::{Error, NoAcknowledgeSource};
223
224 match err {
225 Abort(reason) => match reason {
226 AbortReason::NoAcknowledge => Error::NoAcknowledge(NoAcknowledgeSource::Unknown),
227 AbortReason::ArbitrationLoss => Error::ArbitrationLoss,
228 AbortReason::TxNotEmpty(_) | AbortReason::Other(_) => Error::Other,
229 },
230 #[allow(deprecated)]
231 AddressReserved(_)
232 | InvalidWriteBufferLength
233 | AddressOutOfRange(_)
234 | InvalidReadBufferLength => Error::Other,
235 }
236}
237
238define_i2c_drivers!(
240 I2C0_IRQ => I2C0,
241 I2C1_IRQ => I2C1,
242);