ove/embedded_hal_impl.rs
1// Copyright (C) 2026 Kamil Lulko <kamil.lulko@gmail.com>
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4//
5// This file is part of oveRTOS.
6
7//! `embedded-hal` 1.0 sync trait impls.
8//!
9//! Compiled only when the `embedded-hal` Cargo feature is enabled.
10//! Each section is further gated by the substrate `has_*` cfg for the
11//! corresponding peripheral — a target build with I2C compiled out
12//! omits the I2C impls automatically, same for SPI / GPIO / TIME.
13//!
14//! Provides the ecosystem-compatible trait surface for
15//! `ove::I2c` / `ove::Spi` / `ove::gpio::OutputPin` / `ove::gpio::InputPin`
16//! / `ove::Delay` so that any `embedded-hal`-aware crate.io driver
17//! (BME280, MPU6050, SX1276, …) composes with oveRTOS peripherals at
18//! zero per-call cost (monomorphisation inlines the wrappers).
19//!
20//! Error mapping into the `embedded_hal::*::ErrorKind` enums is best-
21//! effort — the substrate's `OVE_ERR_*` taxonomy is coarser than
22//! `embedded-hal`'s. Unmappable variants fall back to `ErrorKind::Other`.
23
24use crate::error::Error;
25
26// ---------------------------------------------------------------------------
27// I2C
28// ---------------------------------------------------------------------------
29
30#[cfg(has_i2c)]
31mod i2c_impl {
32 use super::*;
33 use crate::I2c;
34 use embedded_hal::i2c;
35
36 impl i2c::Error for Error {
37 fn kind(&self) -> i2c::ErrorKind {
38 match self {
39 Error::BusNack => i2c::ErrorKind::NoAcknowledge(i2c::NoAcknowledgeSource::Unknown),
40 // `embedded-hal` 1.0 has no "busy" ErrorKind; ArbitrationLoss
41 // is the closest retryable bus-contention signal, so drivers
42 // that back off on arbitration loss also back off on a busy bus.
43 Error::BusBusy => i2c::ErrorKind::ArbitrationLoss,
44 Error::BusError => i2c::ErrorKind::Bus,
45 _ => i2c::ErrorKind::Other,
46 }
47 }
48 }
49
50 impl i2c::ErrorType for I2c {
51 type Error = Error;
52 }
53
54 impl i2c::I2c for I2c {
55 fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
56 I2c::read(
57 self,
58 address as u16,
59 read,
60 core::time::Duration::from_secs(u64::MAX / 2),
61 )
62 }
63
64 fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
65 I2c::write(
66 self,
67 address as u16,
68 write,
69 core::time::Duration::from_secs(u64::MAX / 2),
70 )
71 }
72
73 fn write_read(
74 &mut self,
75 address: u8,
76 write: &[u8],
77 read: &mut [u8],
78 ) -> Result<(), Self::Error> {
79 I2c::write_read(
80 self,
81 address as u16,
82 write,
83 read,
84 core::time::Duration::from_secs(u64::MAX / 2),
85 )
86 }
87
88 fn transaction(
89 &mut self,
90 address: u8,
91 operations: &mut [i2c::Operation<'_>],
92 ) -> Result<(), Self::Error> {
93 // The substrate doesn't expose explicit START/STOP control,
94 // so atomicity across a heterogeneous sequence isn't
95 // guaranteed. We do fuse Write+Read into `write_read`
96 // (which DOES emit a repeated START) for the common
97 // "register address then read" pattern.
98 let mut i = 0;
99 while i < operations.len() {
100 if i + 1 < operations.len() {
101 let (left, right) = operations.split_at_mut(i + 1);
102 if let (i2c::Operation::Write(w), i2c::Operation::Read(r)) =
103 (&left[i], &mut right[0])
104 {
105 self.write_read(address, w, r)?;
106 i += 2;
107 continue;
108 }
109 }
110 match &mut operations[i] {
111 i2c::Operation::Read(buf) => self.read(address, buf)?,
112 i2c::Operation::Write(buf) => self.write(address, buf)?,
113 }
114 i += 1;
115 }
116 Ok(())
117 }
118 }
119}
120
121// ---------------------------------------------------------------------------
122// SPI
123// ---------------------------------------------------------------------------
124
125#[cfg(has_spi)]
126mod spi_impl {
127 use super::*;
128 use crate::Spi;
129 use crate::bindings;
130 use embedded_hal::spi;
131
132 impl spi::Error for Error {
133 fn kind(&self) -> spi::ErrorKind {
134 spi::ErrorKind::Other
135 }
136 }
137
138 impl spi::ErrorType for Spi {
139 type Error = Error;
140 }
141
142 impl spi::SpiBus<u8> for Spi {
143 fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
144 Spi::read(
145 self,
146 None,
147 words,
148 core::time::Duration::from_secs(u64::MAX / 2),
149 )
150 }
151
152 fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> {
153 Spi::write(
154 self,
155 None,
156 words,
157 core::time::Duration::from_secs(u64::MAX / 2),
158 )
159 }
160
161 fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
162 Spi::transfer(
163 self,
164 None,
165 write,
166 read,
167 core::time::Duration::from_secs(u64::MAX / 2),
168 )
169 }
170
171 fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
172 // Substrate accepts `tx_ptr == rx_ptr` — SPI hardware
173 // shifts bits through the same memory location. No tmp
174 // buffer needed.
175 let len = words.len();
176 let p = words.as_mut_ptr();
177 let rc = unsafe {
178 bindings::ove_spi_transfer(
179 self.raw(),
180 core::ptr::null(),
181 p as *const _,
182 p as *mut _,
183 len,
184 u64::MAX,
185 )
186 };
187 Error::from_code(rc)
188 }
189
190 fn flush(&mut self) -> Result<(), Self::Error> {
191 // Substrate transfers are synchronous — completion implies
192 // bus quiescence.
193 Ok(())
194 }
195 }
196}
197
198// ---------------------------------------------------------------------------
199// Digital GPIO
200// ---------------------------------------------------------------------------
201
202#[cfg(has_gpio)]
203mod gpio_impl {
204 use super::*;
205 use crate::gpio::{InputPin, OutputPin};
206 use embedded_hal::digital;
207
208 impl digital::Error for Error {
209 fn kind(&self) -> digital::ErrorKind {
210 digital::ErrorKind::Other
211 }
212 }
213
214 impl digital::ErrorType for OutputPin {
215 type Error = Error;
216 }
217
218 impl digital::OutputPin for OutputPin {
219 fn set_low(&mut self) -> Result<(), Self::Error> {
220 OutputPin::set_low(self)
221 }
222
223 fn set_high(&mut self) -> Result<(), Self::Error> {
224 OutputPin::set_high(self)
225 }
226 }
227
228 impl digital::ErrorType for InputPin {
229 type Error = Error;
230 }
231
232 impl digital::InputPin for InputPin {
233 fn is_high(&mut self) -> Result<bool, Self::Error> {
234 InputPin::is_high(self)
235 }
236
237 fn is_low(&mut self) -> Result<bool, Self::Error> {
238 InputPin::is_low(self)
239 }
240 }
241}
242
243// ---------------------------------------------------------------------------
244// Delay
245// ---------------------------------------------------------------------------
246
247#[cfg(has_time)]
248mod delay_impl {
249 use crate::time::{Delay, delay_ms, delay_us};
250 use embedded_hal::delay::DelayNs;
251
252 impl DelayNs for Delay {
253 fn delay_ns(&mut self, ns: u32) {
254 if ns == 0 {
255 return;
256 }
257 // Round up to microsecond — substrate has no ns resolution.
258 let us = ns.div_ceil(1_000);
259 delay_us(us);
260 }
261
262 fn delay_us(&mut self, us: u32) {
263 delay_us(us);
264 }
265
266 fn delay_ms(&mut self, ms: u32) {
267 delay_ms(ms);
268 }
269 }
270}