ariel_os_esp/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;
8use esp_hal::{
9 Async,
10 gpio::interconnect::PeripheralOutput,
11 i2c::master::{BusTimeout, I2c as EspI2c},
12 peripherals,
13};
14
15#[non_exhaustive]
17#[derive(Clone)]
18pub struct Config {
19 pub frequency: Frequency,
21}
22
23impl Default for Config {
24 fn default() -> Self {
25 Self {
26 frequency: Frequency::_100k,
27 }
28 }
29}
30
31#[derive(Debug, Copy, Clone, PartialEq, Eq)]
34#[cfg_attr(feature = "defmt", derive(defmt::Format))]
35pub enum Frequency {
36 _100k,
38 _400k,
40}
41
42#[doc(hidden)]
43impl Frequency {
44 #[must_use]
45 pub const fn first() -> Self {
46 Self::_100k
47 }
48
49 #[must_use]
50 pub const fn last() -> Self {
51 Self::_400k
52 }
53
54 #[must_use]
55 pub const fn next(self) -> Option<Self> {
56 match self {
57 Self::_100k => Some(Self::_400k),
58 Self::_400k => None,
59 }
60 }
61
62 #[must_use]
63 pub const fn prev(self) -> Option<Self> {
64 match self {
65 Self::_100k => None,
66 Self::_400k => Some(Self::_100k),
67 }
68 }
69
70 #[must_use]
71 pub const fn khz(self) -> u32 {
72 match self {
73 Self::_100k => 100,
74 Self::_400k => 400,
75 }
76 }
77}
78
79ariel_os_embassy_common::impl_i2c_from_frequency!();
80
81impl From<Frequency> for esp_hal::time::Rate {
82 fn from(freq: Frequency) -> Self {
83 match freq {
84 Frequency::_100k => esp_hal::time::Rate::from_khz(100),
85 Frequency::_400k => esp_hal::time::Rate::from_khz(400),
86 }
87 }
88}
89
90macro_rules! define_i2c_drivers {
91 ($( $peripheral:ident ),* $(,)?) => {
92 $(
93 pub struct $peripheral {
95 twim: EspI2c<'static, Async>,
96 }
97
98 paste::paste! {
100 static [< ACTIVE_ $peripheral >]: AtomicBool = AtomicBool::new(false);
101 }
102
103 impl $peripheral {
104 #[expect(clippy::new_ret_no_self)]
107 #[must_use]
108 pub fn new<SDA: PeripheralOutput<'static>, SCL: PeripheralOutput<'static>>(
109 sda_pin: impl $crate::IntoPeripheral<'static, SDA>,
110 scl_pin: impl $crate::IntoPeripheral<'static, SCL>,
111 config: Config,
112 ) -> I2c {
113 paste::paste! {
116 #[allow(dead_code)]
117 static [<PREVENT_MULTIPLE_ $peripheral>]: () = ();
118 }
119
120 let twim_config = esp_hal::i2c::master::Config::default()
121 .with_frequency(config.frequency.into())
122 .with_timeout(
124 #[cfg(any(context = "esp32c3", context = "esp32c6", context = "esp32s2", context = "esp32s3"))]
125 BusTimeout::Disabled,
126 #[cfg(context = "esp32")]
128 BusTimeout::Maximum
129 );
130
131 paste::paste! {
133 if [< ACTIVE_ $peripheral >].swap(true, Ordering::AcqRel) {
134 panic!("I2C peripheral already initialized")
135 }
136 }
137
138 let i2c_peripheral = unsafe { peripherals::$peripheral::steal() };
142
143 let twim = EspI2c::new(
144 i2c_peripheral,
145 twim_config,
146 )
147 .unwrap()
148 .into_async()
149 .with_sda(sda_pin.into_hal_peripheral())
150 .with_scl(scl_pin.into_hal_peripheral());
151
152 I2c::$peripheral(Self { twim })
153 }
154 }
155
156 impl Drop for $peripheral {
157 fn drop(&mut self) {
158 paste::paste! {
159 [< ACTIVE_ $peripheral >].store(false, Ordering::Release);
160 }
161 }
162 }
163 )*
164
165 pub enum I2c {
167 $(
168 #[doc = concat!(stringify!($peripheral), " peripheral.")]
169 $peripheral($peripheral),
170 )*
171 }
172
173 impl embedded_hal_async::i2c::ErrorType for I2c {
174 type Error = ariel_os_embassy_common::i2c::controller::Error;
175 }
176
177 impl_async_i2c_for_driver_enum!(I2c, $( $peripheral ),*);
178 }
179}
180
181fn from_error(err: esp_hal::i2c::master::Error) -> ariel_os_embassy_common::i2c::controller::Error {
183 use esp_hal::i2c::master::{AcknowledgeCheckFailedReason, Error as EspError};
184
185 use ariel_os_embassy_common::i2c::controller::{Error, NoAcknowledgeSource};
186
187 #[expect(clippy::match_same_arms, reason = "non-exhaustive upstream enum")]
188 match err {
189 EspError::FifoExceeded => Error::Overrun,
190 EspError::AcknowledgeCheckFailed(reason) => {
191 let reason = match reason {
192 AcknowledgeCheckFailedReason::Address => NoAcknowledgeSource::Address,
193 AcknowledgeCheckFailedReason::Data => NoAcknowledgeSource::Data,
194 AcknowledgeCheckFailedReason::Unknown | _ => NoAcknowledgeSource::Unknown,
195 };
196 Error::NoAcknowledge(reason)
197 }
198 EspError::Timeout => Error::Timeout,
199 EspError::ArbitrationLost => Error::ArbitrationLoss,
200 EspError::ExecutionIncomplete
201 | EspError::CommandNumberExceeded
202 | EspError::ZeroLengthInvalid => Error::Other,
203 _ => Error::Other,
204 }
205}
206
207#[cfg(context = "esp32")]
209define_i2c_drivers!(I2C0, I2C1);
210#[cfg(context = "esp32c3")]
211define_i2c_drivers!(I2C0);
212#[cfg(context = "esp32c6")]
213define_i2c_drivers!(I2C0);
214#[cfg(context = "esp32s2")]
215define_i2c_drivers!(I2C0, I2C1);
216#[cfg(context = "esp32s3")]
217define_i2c_drivers!(I2C0, I2C1);