From b743979b6119541571ab48e25099372d881c65a9 Mon Sep 17 00:00:00 2001 From: chrysn Date: Sat, 20 Jan 2024 23:04:45 +0100 Subject: [PATCH 01/11] refactor: Import embedded-hal 0.2 as embedded-hal-0-2 --- Cargo.toml | 2 +- src/adc.rs | 4 ++-- src/gpio.rs | 4 ++-- src/i2c.rs | 4 ++-- src/led.rs | 10 +++++----- src/microbit.rs | 2 +- src/spi.rs | 2 +- src/ztimer.rs | 4 ++-- tests/gpio/src/lib.rs | 2 +- 9 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d1ec0417..cd0f9325 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ license = "MIT OR Apache-2.0" [dependencies] -embedded-hal = { version = "0.2.4", features = ["unproven"] } +embedded-hal-0-2 = { package = "embedded-hal", version = "0.2.4", features = ["unproven"] } switch-hal = "0.4.0" nb = "0.1.1" riot-sys = "0.7.8" diff --git a/src/adc.rs b/src/adc.rs index df043957..d3567917 100644 --- a/src/adc.rs +++ b/src/adc.rs @@ -35,14 +35,14 @@ pub struct ADC { pub resolution: riot_sys::adc_res_t, } -impl embedded_hal::adc::Channel for ADCLine { +impl embedded_hal_0_2::adc::Channel for ADCLine { type ID = riot_sys::adc_t; fn channel() -> Self::ID { unimplemented!("See https://github.com/rust-embedded/embedded-hal/issues/110") } } -impl embedded_hal::adc::OneShot for ADC { +impl embedded_hal_0_2::adc::OneShot for ADC { type Error = Never; fn read(&mut self, pin: &mut ADCLine) -> nb::Result { diff --git a/src/gpio.rs b/src/gpio.rs index ca418123..1d077825 100644 --- a/src/gpio.rs +++ b/src/gpio.rs @@ -1,11 +1,11 @@ //! Access to [RIOT's GPIO pins](http://doc.riot-os.org/group__drivers__periph__gpio.html) //! //! The various configured GPIO types ([InputGPIO], [OutputGPIO], [InOutGPIO]) can be used through -//! the [embedded_hal::digital::v2] traits. +//! the [embedded_hal_0_2::digital::v2] traits. use riot_sys::{gpio_clear, gpio_mode_t, gpio_read, gpio_set, gpio_t, gpio_toggle}; -use embedded_hal::digital::v2::{InputPin, OutputPin, ToggleableOutputPin}; +use embedded_hal_0_2::digital::v2::{InputPin, OutputPin, ToggleableOutputPin}; use crate::error::NegativeErrorExt; use crate::Never; diff --git a/src/i2c.rs b/src/i2c.rs index a6fbf4d7..56d0414b 100644 --- a/src/i2c.rs +++ b/src/i2c.rs @@ -1,13 +1,13 @@ //! Controlling the I²C bus -use embedded_hal::blocking; +use embedded_hal_0_2::blocking; use riot_sys::i2c_t; /// An I²C master backed by RIOT's [I2C implementation] /// /// [I2C implementation]: http://doc.riot-os.org/group__drivers__periph__i2c.html /// -/// Actual transactions on this are performed through the [embedded_hal::blocking::i2c] traits +/// Actual transactions on this are performed through the [mbedded_hal_0_2::blocking::i2c] traits /// implemented by this. #[derive(Debug)] pub struct I2CDevice { diff --git a/src/led.rs b/src/led.rs index 017ee1a5..b7a1ffa1 100644 --- a/src/led.rs +++ b/src/led.rs @@ -23,12 +23,12 @@ impl switch_hal::OutputSwitch for LED { type Error = Never; fn on(&mut self) -> Result<(), Self::Error> { - use embedded_hal::digital::v2::OutputPin; + use embedded_hal_0_2::digital::v2::OutputPin; self.set_high() } fn off(&mut self) -> Result<(), Self::Error> { - use embedded_hal::digital::v2::OutputPin; + use embedded_hal_0_2::digital::v2::OutputPin; self.set_low() } } @@ -37,11 +37,11 @@ impl switch_hal::ToggleableOutputSwitch for LED { type Error = Never; fn toggle(&mut self) -> Result<(), Self::Error> { - ::toggle(self) + ::toggle(self) } } -impl embedded_hal::digital::v2::OutputPin for LED { +impl embedded_hal_0_2::digital::v2::OutputPin for LED { type Error = Never; fn set_high(&mut self) -> Result<(), Never> { @@ -81,7 +81,7 @@ impl embedded_hal::digital::v2::OutputPin for LED { } } -impl embedded_hal::digital::v2::ToggleableOutputPin for LED { +impl embedded_hal_0_2::digital::v2::ToggleableOutputPin for LED { type Error = Never; fn toggle(&mut self) -> Result<(), Never> { diff --git a/src/microbit.rs b/src/microbit.rs index 4d10b34b..6d735381 100644 --- a/src/microbit.rs +++ b/src/microbit.rs @@ -14,7 +14,7 @@ use crate::Never; /// The 5x5 LED matrix of the micro:bit boards /// -/// Use the [embedded_hal] mechanisms to paint on them. +/// Use the [embedded_graphics] mechanisms to paint on them. pub struct LEDs { _private: (), } diff --git a/src/spi.rs b/src/spi.rs index 84ffa48c..53c4888b 100644 --- a/src/spi.rs +++ b/src/spi.rs @@ -1,5 +1,5 @@ use crate::Never; -use embedded_hal::blocking; +use embedded_hal_0_2::blocking; use riot_sys::{ spi_acquire, spi_clk_t, diff --git a/src/ztimer.rs b/src/ztimer.rs index caf98c11..39f52a78 100644 --- a/src/ztimer.rs +++ b/src/ztimer.rs @@ -197,13 +197,13 @@ impl Clock<1000000> { } } -impl embedded_hal::blocking::delay::DelayMs for Clock<1000> { +impl embedded_hal_0_2::blocking::delay::DelayMs for Clock<1000> { fn delay_ms(&mut self, ms: u32) { self.sleep_ticks(ms.into()); } } -impl embedded_hal::blocking::delay::DelayUs for Clock<1000000> { +impl embedded_hal_0_2::blocking::delay::DelayUs for Clock<1000000> { fn delay_us(&mut self, us: u32) { self.sleep_ticks(us); } diff --git a/tests/gpio/src/lib.rs b/tests/gpio/src/lib.rs index b1d13f04..1779393c 100644 --- a/tests/gpio/src/lib.rs +++ b/tests/gpio/src/lib.rs @@ -4,7 +4,7 @@ use riot_wrappers::gpio::{InputMode, OutputMode, GPIO}; use riot_wrappers::println; use riot_wrappers::riot_main; -use embedded_hal::digital::v2::{InputPin, OutputPin, PinState}; +use embedded_hal_0_2::digital::v2::{InputPin, OutputPin, PinState}; riot_main!(main); From 209d0bde59e464ec486243bfd3e25a267e18b4a1 Mon Sep 17 00:00:00 2001 From: chrysn Date: Sat, 20 Jan 2024 23:22:22 +0100 Subject: [PATCH 02/11] rename: Move some module to mod.rs to more easily split embedded-hal impl versions --- src/{gpio.rs => gpio/mod.rs} | 0 src/{i2c.rs => i2c/mod.rs} | 0 src/{spi.rs => spi/mod.rs} | 0 src/{ztimer.rs => ztimer/mod.rs} | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename src/{gpio.rs => gpio/mod.rs} (100%) rename src/{i2c.rs => i2c/mod.rs} (100%) rename src/{spi.rs => spi/mod.rs} (100%) rename src/{ztimer.rs => ztimer/mod.rs} (100%) diff --git a/src/gpio.rs b/src/gpio/mod.rs similarity index 100% rename from src/gpio.rs rename to src/gpio/mod.rs diff --git a/src/i2c.rs b/src/i2c/mod.rs similarity index 100% rename from src/i2c.rs rename to src/i2c/mod.rs diff --git a/src/spi.rs b/src/spi/mod.rs similarity index 100% rename from src/spi.rs rename to src/spi/mod.rs diff --git a/src/ztimer.rs b/src/ztimer/mod.rs similarity index 100% rename from src/ztimer.rs rename to src/ztimer/mod.rs From 4f43b2b2cd54df5c04085768279216ea7daf96c0 Mon Sep 17 00:00:00 2001 From: chrysn Date: Sat, 20 Jan 2024 23:24:38 +0100 Subject: [PATCH 03/11] gpio: Split embedded-hal from main implementation --- src/gpio/impl_0_2.rs | 73 +++++++++++++++++++++++++++++++++++++++++++ src/gpio/mod.rs | 74 ++------------------------------------------ 2 files changed, 75 insertions(+), 72 deletions(-) create mode 100644 src/gpio/impl_0_2.rs diff --git a/src/gpio/impl_0_2.rs b/src/gpio/impl_0_2.rs new file mode 100644 index 00000000..0381aa35 --- /dev/null +++ b/src/gpio/impl_0_2.rs @@ -0,0 +1,73 @@ +use super::*; + +use embedded_hal_0_2::digital::v2::{InputPin, OutputPin, ToggleableOutputPin}; + +impl InputPin for InputGPIO { + type Error = Never; + + fn is_high(&self) -> Result { + Ok(unsafe { gpio_read(self.to_c()) } != 0) + } + + fn is_low(&self) -> Result { + Ok(unsafe { gpio_read(self.to_c()) } == 0) + } +} + +impl OutputPin for OutputGPIO { + type Error = Never; + + fn set_high(&mut self) -> Result<(), Never> { + unsafe { gpio_set(self.to_c()) }; + Ok(()) + } + + fn set_low(&mut self) -> Result<(), Never> { + unsafe { gpio_clear(self.to_c()) }; + Ok(()) + } +} + +impl ToggleableOutputPin for OutputGPIO { + type Error = Never; + + fn toggle(&mut self) -> Result<(), Never> { + unsafe { gpio_toggle(self.to_c()) }; + Ok(()) + } +} + +impl InputPin for InOutGPIO { + type Error = Never; + + fn is_high(&self) -> Result { + Ok(unsafe { gpio_read(self.to_c()) } != 0) + } + + fn is_low(&self) -> Result { + Ok(unsafe { gpio_read(self.to_c()) } == 0) + } +} + +impl OutputPin for InOutGPIO { + type Error = Never; + + fn set_high(&mut self) -> Result<(), Never> { + unsafe { gpio_set(self.to_c()) }; + Ok(()) + } + + fn set_low(&mut self) -> Result<(), Never> { + unsafe { gpio_clear(self.to_c()) }; + Ok(()) + } +} + +impl ToggleableOutputPin for InOutGPIO { + type Error = Never; + + fn toggle(&mut self) -> Result<(), Never> { + unsafe { gpio_toggle(self.to_c()) }; + Ok(()) + } +} diff --git a/src/gpio/mod.rs b/src/gpio/mod.rs index 1d077825..96bc1678 100644 --- a/src/gpio/mod.rs +++ b/src/gpio/mod.rs @@ -3,9 +3,9 @@ //! The various configured GPIO types ([InputGPIO], [OutputGPIO], [InOutGPIO]) can be used through //! the [embedded_hal_0_2::digital::v2] traits. -use riot_sys::{gpio_clear, gpio_mode_t, gpio_read, gpio_set, gpio_t, gpio_toggle}; +mod impl_0_2; -use embedded_hal_0_2::digital::v2::{InputPin, OutputPin, ToggleableOutputPin}; +use riot_sys::{gpio_clear, gpio_mode_t, gpio_read, gpio_set, gpio_t, gpio_toggle}; use crate::error::NegativeErrorExt; use crate::Never; @@ -144,29 +144,6 @@ impl OutputGPIO { } } -impl OutputPin for OutputGPIO { - type Error = Never; - - fn set_high(&mut self) -> Result<(), Never> { - unsafe { gpio_set(self.to_c()) }; - Ok(()) - } - - fn set_low(&mut self) -> Result<(), Never> { - unsafe { gpio_clear(self.to_c()) }; - Ok(()) - } -} - -impl ToggleableOutputPin for OutputGPIO { - type Error = Never; - - fn toggle(&mut self) -> Result<(), Never> { - unsafe { gpio_toggle(self.to_c()) }; - Ok(()) - } -} - /// A [GPIO] configured and usable for input pub struct InputGPIO(GPIO); @@ -182,18 +159,6 @@ impl InputGPIO { } } -impl InputPin for InputGPIO { - type Error = Never; - - fn is_high(&self) -> Result { - Ok(unsafe { gpio_read(self.to_c()) } != 0) - } - - fn is_low(&self) -> Result { - Ok(unsafe { gpio_read(self.to_c()) } == 0) - } -} - /// A [GPIO] configured and usable for input and output pub struct InOutGPIO(GPIO); @@ -208,38 +173,3 @@ impl InOutGPIO { self.0 } } - -impl InputPin for InOutGPIO { - type Error = Never; - - fn is_high(&self) -> Result { - Ok(unsafe { gpio_read(self.to_c()) } != 0) - } - - fn is_low(&self) -> Result { - Ok(unsafe { gpio_read(self.to_c()) } == 0) - } -} - -impl OutputPin for InOutGPIO { - type Error = Never; - - fn set_high(&mut self) -> Result<(), Never> { - unsafe { gpio_set(self.to_c()) }; - Ok(()) - } - - fn set_low(&mut self) -> Result<(), Never> { - unsafe { gpio_clear(self.to_c()) }; - Ok(()) - } -} - -impl ToggleableOutputPin for InOutGPIO { - type Error = Never; - - fn toggle(&mut self) -> Result<(), Never> { - unsafe { gpio_toggle(self.to_c()) }; - Ok(()) - } -} From f969be0467f2cac00fe1d22eb4a46385f9b580ad Mon Sep 17 00:00:00 2001 From: chrysn Date: Sat, 20 Jan 2024 23:38:55 +0100 Subject: [PATCH 04/11] Cargo: Add embedded-hal 1 --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index cd0f9325..4a592bde 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ license = "MIT OR Apache-2.0" [dependencies] embedded-hal-0-2 = { package = "embedded-hal", version = "0.2.4", features = ["unproven"] } +embedded-hal = "1" switch-hal = "0.4.0" nb = "0.1.1" riot-sys = "0.7.8" From 5869ce5363cf8b9a4c877beedd679d378cab1ce8 Mon Sep 17 00:00:00 2001 From: chrysn Date: Sat, 20 Jan 2024 23:39:26 +0100 Subject: [PATCH 05/11] gpio: Implement and test embedded-hal 1 --- src/gpio/impl_1.rs | 60 +++++++++++++++++++++++++++++++++++++++++++ src/gpio/mod.rs | 1 + tests/gpio/Cargo.toml | 2 +- tests/gpio/src/lib.rs | 8 +++--- 4 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 src/gpio/impl_1.rs diff --git a/src/gpio/impl_1.rs b/src/gpio/impl_1.rs new file mode 100644 index 00000000..dedc667d --- /dev/null +++ b/src/gpio/impl_1.rs @@ -0,0 +1,60 @@ +use super::*; + +use core::convert::Infallible; +use embedded_hal::digital::{ErrorType, InputPin, OutputPin}; + +impl ErrorType for InputGPIO { + type Error = Infallible; +} + +impl InputPin for InputGPIO { + fn is_high(&mut self) -> Result { + Ok(unsafe { gpio_read(self.to_c()) } != 0) + } + + fn is_low(&mut self) -> Result { + Ok(unsafe { gpio_read(self.to_c()) } == 0) + } +} + +impl ErrorType for OutputGPIO { + type Error = Infallible; +} + +impl OutputPin for OutputGPIO { + fn set_high(&mut self) -> Result<(), Infallible> { + unsafe { gpio_set(self.to_c()) }; + Ok(()) + } + + fn set_low(&mut self) -> Result<(), Infallible> { + unsafe { gpio_clear(self.to_c()) }; + Ok(()) + } +} + +impl ErrorType for InOutGPIO { + type Error = Infallible; +} + +impl InputPin for InOutGPIO { + fn is_high(&mut self) -> Result { + Ok(unsafe { gpio_read(self.to_c()) } != 0) + } + + fn is_low(&mut self) -> Result { + Ok(unsafe { gpio_read(self.to_c()) } == 0) + } +} + +impl OutputPin for InOutGPIO { + fn set_high(&mut self) -> Result<(), Infallible> { + unsafe { gpio_set(self.to_c()) }; + Ok(()) + } + + fn set_low(&mut self) -> Result<(), Infallible> { + unsafe { gpio_clear(self.to_c()) }; + Ok(()) + } +} diff --git a/src/gpio/mod.rs b/src/gpio/mod.rs index 96bc1678..966b05bd 100644 --- a/src/gpio/mod.rs +++ b/src/gpio/mod.rs @@ -4,6 +4,7 @@ //! the [embedded_hal_0_2::digital::v2] traits. mod impl_0_2; +mod impl_1; use riot_sys::{gpio_clear, gpio_mode_t, gpio_read, gpio_set, gpio_t, gpio_toggle}; diff --git a/tests/gpio/Cargo.toml b/tests/gpio/Cargo.toml index ffd6e853..e23a3b41 100644 --- a/tests/gpio/Cargo.toml +++ b/tests/gpio/Cargo.toml @@ -14,4 +14,4 @@ panic = "abort" [dependencies] riot-wrappers = { version = "*", features = [ "set_panic_handler", "panic_handler_format" ] } riot-sys = "*" -embedded-hal = "0.2.4" +embedded-hal = "1" diff --git a/tests/gpio/src/lib.rs b/tests/gpio/src/lib.rs index 1779393c..774b1a2e 100644 --- a/tests/gpio/src/lib.rs +++ b/tests/gpio/src/lib.rs @@ -4,7 +4,7 @@ use riot_wrappers::gpio::{InputMode, OutputMode, GPIO}; use riot_wrappers::println; use riot_wrappers::riot_main; -use embedded_hal_0_2::digital::v2::{InputPin, OutputPin, PinState}; +use embedded_hal::digital::{InputPin, OutputPin, PinState}; riot_main!(main); @@ -22,7 +22,7 @@ fn main() { .expect("Out pin does not exist") .configure_as_output(OutputMode::Out) .expect("Out pin could not be configured"); - let p_in = GPIO::from_port_and_pin(in_port, in_pin) + let mut p_in = GPIO::from_port_and_pin(in_port, in_pin) .expect("In pin does not exist") .configure_as_input(in_mode) .expect("In pin could not be configured"); @@ -30,6 +30,8 @@ fn main() { loop { let value = p_in.is_high().unwrap(); println!("Read GPIO value {}, writing it to the out port", value); - p_out.set_state(if value { PinState::High } else { PinState::Low }); + p_out + .set_state(if value { PinState::High } else { PinState::Low }) + .unwrap(); } } From 97c2903dbb528aacb320d0fd192cdf7783f55695 Mon Sep 17 00:00:00 2001 From: chrysn Date: Sun, 21 Jan 2024 01:16:34 +0100 Subject: [PATCH 06/11] led: Note that no new embedded-hal traits are provided --- src/led.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/led.rs b/src/led.rs index b7a1ffa1..4282ebb7 100644 --- a/src/led.rs +++ b/src/led.rs @@ -8,8 +8,9 @@ use crate::Never; /// /// LEDs are accessible safely; any not implemented on a board are silently ignored. /// -/// LEDs are wrapped into GPIOs for compatibility reasons; GPIO is interpreted such that "high" is -/// having the LED on, and "low" is off. +/// LEDs are wrapped into embedded-hal 0.2 GPIOs for compatibility reasons (but that integration is +/// discontinued with embedded-hal 1.0); GPIO is interpreted such that "high" is having the LED on, +/// and "low" is off. pub struct LED(()); impl LED { From b6f8827bd45d978b81c08f134bc8b7c3a35b862c Mon Sep 17 00:00:00 2001 From: chrysn Date: Sun, 21 Jan 2024 22:08:43 +0100 Subject: [PATCH 07/11] gpio: Provide direct methods in addition to HAL This adds a .set_state() call (which would otherwise not be available as it's a provided method of the traits), which also benefits HAL users because the direct implementation can do without the branching of the provided method. Test pinouts are also provided for microbit-v2 because that's where it was just tested. --- src/gpio/impl_1.rs | 30 ++++++++++++++++------------- src/gpio/mod.rs | 45 +++++++++++++++++++++++++++++++++++++++++-- tests/gpio/src/lib.rs | 25 ++++++++++++++++++------ 3 files changed, 79 insertions(+), 21 deletions(-) diff --git a/src/gpio/impl_1.rs b/src/gpio/impl_1.rs index dedc667d..793cab0a 100644 --- a/src/gpio/impl_1.rs +++ b/src/gpio/impl_1.rs @@ -1,7 +1,7 @@ use super::*; use core::convert::Infallible; -use embedded_hal::digital::{ErrorType, InputPin, OutputPin}; +use embedded_hal::digital::{ErrorType, InputPin, OutputPin, PinState}; impl ErrorType for InputGPIO { type Error = Infallible; @@ -9,11 +9,11 @@ impl ErrorType for InputGPIO { impl InputPin for InputGPIO { fn is_high(&mut self) -> Result { - Ok(unsafe { gpio_read(self.to_c()) } != 0) + Ok(InputGPIO::is_high(self)) } fn is_low(&mut self) -> Result { - Ok(unsafe { gpio_read(self.to_c()) } == 0) + Ok(InputGPIO::is_low(self)) } } @@ -23,13 +23,15 @@ impl ErrorType for OutputGPIO { impl OutputPin for OutputGPIO { fn set_high(&mut self) -> Result<(), Infallible> { - unsafe { gpio_set(self.to_c()) }; - Ok(()) + Ok(OutputGPIO::set_high(self)) } fn set_low(&mut self) -> Result<(), Infallible> { - unsafe { gpio_clear(self.to_c()) }; - Ok(()) + Ok(OutputGPIO::set_low(self)) + } + + fn set_state(&mut self, state: PinState) -> Result<(), Infallible> { + Ok(OutputGPIO::set_state(self, state.into())) } } @@ -39,22 +41,24 @@ impl ErrorType for InOutGPIO { impl InputPin for InOutGPIO { fn is_high(&mut self) -> Result { - Ok(unsafe { gpio_read(self.to_c()) } != 0) + Ok(InOutGPIO::is_high(self)) } fn is_low(&mut self) -> Result { - Ok(unsafe { gpio_read(self.to_c()) } == 0) + Ok(InOutGPIO::is_low(self)) } } impl OutputPin for InOutGPIO { fn set_high(&mut self) -> Result<(), Infallible> { - unsafe { gpio_set(self.to_c()) }; - Ok(()) + Ok(InOutGPIO::set_high(self)) } fn set_low(&mut self) -> Result<(), Infallible> { - unsafe { gpio_clear(self.to_c()) }; - Ok(()) + Ok(InOutGPIO::set_low(self)) + } + + fn set_state(&mut self, state: PinState) -> Result<(), Infallible> { + Ok(InOutGPIO::set_state(self, state.into())) } } diff --git a/src/gpio/mod.rs b/src/gpio/mod.rs index 966b05bd..41bf5e92 100644 --- a/src/gpio/mod.rs +++ b/src/gpio/mod.rs @@ -1,12 +1,13 @@ //! Access to [RIOT's GPIO pins](http://doc.riot-os.org/group__drivers__periph__gpio.html) //! //! The various configured GPIO types ([InputGPIO], [OutputGPIO], [InOutGPIO]) can be used through -//! the [embedded_hal_0_2::digital::v2] traits. +//! the [embedded_hal::digital::v2] traits. As recommended for infallible types, they also +//! provide identically named direct methods, which (for input pins) also work on shared reference. mod impl_0_2; mod impl_1; -use riot_sys::{gpio_clear, gpio_mode_t, gpio_read, gpio_set, gpio_t, gpio_toggle}; +use riot_sys::{gpio_clear, gpio_mode_t, gpio_read, gpio_set, gpio_t, gpio_toggle, gpio_write}; use crate::error::NegativeErrorExt; use crate::Never; @@ -143,6 +144,18 @@ impl OutputGPIO { pub fn deconfigured(self) -> GPIO { self.0 } + + pub fn set_high(&mut self) { + unsafe { gpio_set(self.to_c()) }; + } + + pub fn set_low(&mut self) { + unsafe { gpio_clear(self.to_c()) }; + } + + pub fn set_state(&mut self, state: bool) { + unsafe { gpio_write(self.to_c(), state as _) }; + } } /// A [GPIO] configured and usable for input @@ -158,6 +171,14 @@ impl InputGPIO { pub fn deconfigured(self) -> GPIO { self.0 } + + pub fn is_high(&self) -> bool { + unsafe { gpio_read(self.to_c()) != 0 } + } + + pub fn is_low(&self) -> bool { + unsafe { gpio_read(self.to_c()) == 0 } + } } /// A [GPIO] configured and usable for input and output @@ -173,4 +194,24 @@ impl InOutGPIO { pub fn deconfigured(self) -> GPIO { self.0 } + + pub fn set_high(&mut self) { + unsafe { gpio_set(self.to_c()) }; + } + + pub fn set_low(&mut self) { + unsafe { gpio_clear(self.to_c()) }; + } + + pub fn set_state(&mut self, state: bool) { + unsafe { gpio_write(self.to_c(), state as _) }; + } + + pub fn is_high(&self) -> bool { + unsafe { gpio_read(self.to_c()) != 0 } + } + + pub fn is_low(&self) -> bool { + unsafe { gpio_read(self.to_c()) == 0 } + } } diff --git a/tests/gpio/src/lib.rs b/tests/gpio/src/lib.rs index 774b1a2e..1d006eb2 100644 --- a/tests/gpio/src/lib.rs +++ b/tests/gpio/src/lib.rs @@ -4,16 +4,28 @@ use riot_wrappers::gpio::{InputMode, OutputMode, GPIO}; use riot_wrappers::println; use riot_wrappers::riot_main; -use embedded_hal::digital::{InputPin, OutputPin, PinState}; - riot_main!(main); +fn replicate_through_hal( + p_in: &mut impl embedded_hal::digital::InputPin, + p_out: &mut impl embedded_hal::digital::OutputPin, +) { + let value = p_in.is_high().unwrap(); + println!( + "Read GPIO value {}, writing it to the out port (through embedded-hal)", + value + ); + p_out.set_state(value.into()).unwrap(); +} + fn main() { let (out_port, out_pin, in_port, in_pin, in_mode) = match riot_wrappers::BOARD { // Won't work -- currently, native GPIO don't do anything (but let's not panic already) "native" => (0, 0, 0, 1, InputMode::In), // 0.17 is LED1, 0.13 is button 1 "nrf52dk" => (0, 17, 0, 13, InputMode::InPullUp), + // 0.20 is the MIC enable line (which is the only easily controlled LED), 0.14 is BTN_A + "microbit-v2" => (0, 20, 0, 14, InputMode::In), // Better safe than drive pins that were not supposed to be driven _ => panic!("For this board, no GPIO pins were deemed safe to reconfigure."), @@ -28,10 +40,11 @@ fn main() { .expect("In pin could not be configured"); loop { - let value = p_in.is_high().unwrap(); + let value = p_in.is_high(); println!("Read GPIO value {}, writing it to the out port", value); - p_out - .set_state(if value { PinState::High } else { PinState::Low }) - .unwrap(); + p_out.set_state(value); + + // Does the same, but using the embedded-hal trats + replicate_through_hal(&mut p_in, &mut p_out); } } From 993914e031e63e868804e0df1c6d0bfac050f705 Mon Sep 17 00:00:00 2001 From: chrysn Date: Sun, 21 Jan 2024 22:39:58 +0100 Subject: [PATCH 08/11] tests: Add i2c test --- tests/i2c/Cargo.toml | 17 +++++++++++++++++ tests/i2c/Makefile | 8 ++++++++ tests/i2c/src/lib.rs | 25 +++++++++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 tests/i2c/Cargo.toml create mode 100644 tests/i2c/Makefile create mode 100644 tests/i2c/src/lib.rs diff --git a/tests/i2c/Cargo.toml b/tests/i2c/Cargo.toml new file mode 100644 index 00000000..95cddc18 --- /dev/null +++ b/tests/i2c/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "riot-wrappers-test-i2c" +version = "0.1.0" +authors = ["Christian Amsüss "] +edition = "2021" +publish = false + +[lib] +crate-type = ["staticlib"] + +[profile.release] +panic = "abort" + +[dependencies] +riot-wrappers = { version = "*", features = [ "set_panic_handler", "panic_handler_format" ] } +riot-sys = "*" +embedded-hal = "0.2" diff --git a/tests/i2c/Makefile b/tests/i2c/Makefile new file mode 100644 index 00000000..422f406a --- /dev/null +++ b/tests/i2c/Makefile @@ -0,0 +1,8 @@ +# name of your application +APPLICATION = riot-wrappers-test-i2c +APPLICATION_RUST_MODULE = riot_wrappers_test_i2c +BASELIBS += $(APPLICATION_RUST_MODULE).module +FEATURES_REQUIRED += rust_target +FEATURES_REQUIRED += periph_i2c + +include $(RIOTBASE)/Makefile.include diff --git a/tests/i2c/src/lib.rs b/tests/i2c/src/lib.rs new file mode 100644 index 00000000..57d9f02d --- /dev/null +++ b/tests/i2c/src/lib.rs @@ -0,0 +1,25 @@ +//! This is a primitive I2C scanner, and should thus report *something* interesting if any I2C +//! device is connected. (And reading should be safe on any device / bus). +#![no_std] + +use embedded_hal::blocking::i2c::Read; + +use riot_wrappers::println; +use riot_wrappers::riot_main; + +riot_main!(main); + +fn main() { + let mut i2c = riot_wrappers::i2c::I2CDevice::new(0); // FIXME from_number? + + let mut buf = [0]; + + loop { + for i in 0..=127 { + match i2c.read(i, &mut buf) { + Ok(()) => println!("From {i}, read bytes: {:?}", &buf), + Err(e) => println!("From {i}, error {e:?}"), + } + } + } +} From 6bfbe783f17f72b287a64b68f2aacb50a90f8a66 Mon Sep 17 00:00:00 2001 From: chrysn Date: Sun, 21 Jan 2024 22:42:35 +0100 Subject: [PATCH 09/11] i2c: Split out embedded-hal 0.2 implementation --- src/i2c/impl_0_2.rs | 106 ++++++++++++++++++++++++++++++++++++++++++++ src/i2c/mod.rs | 104 +++---------------------------------------- 2 files changed, 112 insertions(+), 98 deletions(-) create mode 100644 src/i2c/impl_0_2.rs diff --git a/src/i2c/impl_0_2.rs b/src/i2c/impl_0_2.rs new file mode 100644 index 00000000..b3c0e98e --- /dev/null +++ b/src/i2c/impl_0_2.rs @@ -0,0 +1,106 @@ +//! Implementation of embedded-hal 0.2's I2C for [I2CDevice] +//! +//! As the implementation is on the [I2CDevice directly], all that is in this module is the +//! suitable [Error] type. + +use embedded_hal_0_2::blocking; + +use super::*; + +use riot_sys::libc; +use riot_sys::{i2c_acquire, i2c_read_bytes, i2c_release, i2c_write_bytes}; + +#[derive(Debug)] +#[non_exhaustive] +pub enum Error { + AcquireError, + WriteError(i32), + ReadError(i32), +} + +impl blocking::i2c::WriteRead for I2CDevice { + type Error = Error; + + fn write_read( + &mut self, + address: u8, + bytes: &[u8], + buffer: &mut [u8], + ) -> Result<(), Self::Error> { + unsafe { i2c_acquire(self.dev) }; + let err = unsafe { + i2c_write_bytes( + self.dev, + address as u16, + bytes.as_ptr() as *const libc::c_void, + bytes.len() as _, + 0, + ) + }; + if err != 0 { + unsafe { i2c_release(self.dev) }; + return Err(Error::WriteError(err)); + } + let err = unsafe { + i2c_read_bytes( + self.dev, + address as u16, + buffer.as_ptr() as *mut libc::c_void, + buffer.len() as _, + 0, + ) + }; + if err != 0 { + unsafe { i2c_release(self.dev) }; + return Err(Error::ReadError(err)); + } + unsafe { i2c_release(self.dev) }; + Ok(()) + } +} + +impl blocking::i2c::Write for I2CDevice { + type Error = Error; + + fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Self::Error> { + unsafe { i2c_acquire(self.dev) }; + let err = unsafe { + i2c_write_bytes( + self.dev, + address as u16, + bytes.as_ptr() as *const libc::c_void, + bytes.len() as _, + 0, + ) + }; + if err != 0 { + unsafe { i2c_release(self.dev) }; + return Err(Error::WriteError(err)); + } + unsafe { i2c_release(self.dev) }; + Ok(()) + } +} + +impl blocking::i2c::Read for I2CDevice { + type Error = Error; + + fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { + unsafe { i2c_acquire(self.dev) }; + let err = unsafe { + i2c_read_bytes( + self.dev, + address as u16, + buffer.as_ptr() as *mut libc::c_void, + buffer.len() as _, + 0, + ) + }; + if err != 0 { + unsafe { i2c_release(self.dev) }; + return Err(Error::ReadError(err)); + } + unsafe { i2c_release(self.dev) }; + Ok(()) + } +} diff --git a/src/i2c/mod.rs b/src/i2c/mod.rs index 56d0414b..bbb0799b 100644 --- a/src/i2c/mod.rs +++ b/src/i2c/mod.rs @@ -1,6 +1,7 @@ //! Controlling the I²C bus -use embedded_hal_0_2::blocking; +pub mod impl_0_2; + use riot_sys::i2c_t; /// An I²C master backed by RIOT's [I2C implementation] @@ -24,100 +25,7 @@ impl I2CDevice { } } -#[derive(Debug)] -#[non_exhaustive] -pub enum Error { - AcquireError, - WriteError(i32), - ReadError(i32), -} - -use riot_sys::libc; -use riot_sys::{i2c_acquire, i2c_read_bytes, i2c_release, i2c_write_bytes}; - -impl blocking::i2c::WriteRead for I2CDevice { - type Error = Error; - - fn write_read( - &mut self, - address: u8, - bytes: &[u8], - buffer: &mut [u8], - ) -> Result<(), Self::Error> { - unsafe { i2c_acquire(self.dev) }; - let err = unsafe { - i2c_write_bytes( - self.dev, - address as u16, - bytes.as_ptr() as *const libc::c_void, - bytes.len() as _, - 0, - ) - }; - if err != 0 { - unsafe { i2c_release(self.dev) }; - return Err(Error::WriteError(err)); - } - let err = unsafe { - i2c_read_bytes( - self.dev, - address as u16, - buffer.as_ptr() as *mut libc::c_void, - buffer.len() as _, - 0, - ) - }; - if err != 0 { - unsafe { i2c_release(self.dev) }; - return Err(Error::ReadError(err)); - } - unsafe { i2c_release(self.dev) }; - Ok(()) - } -} - -impl blocking::i2c::Write for I2CDevice { - type Error = Error; - - fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Self::Error> { - unsafe { i2c_acquire(self.dev) }; - let err = unsafe { - i2c_write_bytes( - self.dev, - address as u16, - bytes.as_ptr() as *const libc::c_void, - bytes.len() as _, - 0, - ) - }; - if err != 0 { - unsafe { i2c_release(self.dev) }; - return Err(Error::WriteError(err)); - } - unsafe { i2c_release(self.dev) }; - Ok(()) - } -} - -impl blocking::i2c::Read for I2CDevice { - type Error = Error; - - fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { - unsafe { i2c_acquire(self.dev) }; - let err = unsafe { - i2c_read_bytes( - self.dev, - address as u16, - buffer.as_ptr() as *mut libc::c_void, - buffer.len() as _, - 0, - ) - }; - if err != 0 { - unsafe { i2c_release(self.dev) }; - return Err(Error::ReadError(err)); - } - unsafe { i2c_release(self.dev) }; - Ok(()) - } -} +#[deprecated( + note = "This error type applies to embedded-hal 0.2 only, use it through the impl_0_2 module." +)] +pub use impl_0_2::Error; From d76355d055e3e775520e5db2c4df1962f54f3e66 Mon Sep 17 00:00:00 2001 From: chrysn Date: Tue, 23 Jan 2024 21:50:57 +0100 Subject: [PATCH 10/11] i2c: Add embedded-hal 1 implementation --- src/i2c/impl_1.rs | 143 +++++++++++++++++++++++++++++++++++++++++++ src/i2c/mod.rs | 1 + tests/i2c/Cargo.toml | 2 +- tests/i2c/src/lib.rs | 2 +- 4 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 src/i2c/impl_1.rs diff --git a/src/i2c/impl_1.rs b/src/i2c/impl_1.rs new file mode 100644 index 00000000..72a1dcd7 --- /dev/null +++ b/src/i2c/impl_1.rs @@ -0,0 +1,143 @@ +use embedded_hal::i2c; + +use riot_sys::{i2c_acquire, i2c_read_bytes, i2c_release, i2c_write_bytes}; + +use crate::error::{NegativeErrorExt, NumericError}; + +use super::*; + +const I2C_NOSTOP: u8 = riot_sys::i2c_flags_t_I2C_NOSTOP; +const I2C_NOSTART: u8 = riot_sys::i2c_flags_t_I2C_NOSTART; + +#[derive(Debug)] +pub struct Error(NumericError); + +impl From for Error { + fn from(err: NumericError) -> Self { + Self(err) + } +} + +impl i2c::Error for Error { + fn kind(&self) -> i2c::ErrorKind { + match -self.0.number() as _ { + // The list documented with all the RIOT I2C functions + riot_sys::EIO => i2c::ErrorKind::NoAcknowledge(i2c::NoAcknowledgeSource::Data), + riot_sys::ENXIO => i2c::ErrorKind::NoAcknowledge(i2c::NoAcknowledgeSource::Address), + riot_sys::ETIMEDOUT => i2c::ErrorKind::Other, + riot_sys::EINVAL => i2c::ErrorKind::Other, + riot_sys::EOPNOTSUPP => i2c::ErrorKind::Other, // We should avoid this at type level, + // but can't because RIOT is not telling + // us at setup time. + riot_sys::EAGAIN => i2c::ErrorKind::ArbitrationLoss, + _ => i2c::ErrorKind::Other, + } + } +} + +impl i2c::ErrorType for I2CDevice { + type Error = Error; +} + +fn with_acquire(dev: &mut I2CDevice, f: impl FnOnce(&mut I2CDevice) -> R) -> R { + unsafe { i2c_acquire(dev.dev) }; + let result = f(dev); + unsafe { i2c_release(dev.dev) }; + result +} + +impl i2c::I2c for I2CDevice { + fn transaction( + &mut self, + address: i2c::SevenBitAddress, + operations: &mut [i2c::Operation<'_>], + ) -> Result<(), Self::Error> { + with_acquire(self, |dev| { + #[derive(PartialEq)] + enum LastOperation { + Initial, + Read, + Write, + } + use LastOperation::*; + + impl LastOperation { + fn from(op: &i2c::Operation) -> Self { + match op { + i2c::Operation::Read(_) => Read, + i2c::Operation::Write(_) => Write, + } + } + } + + let mut last_operation = Initial; + let last_index = operations.len() - 1; + + for (i, op) in operations.iter_mut().enumerate() { + let this_operation = LastOperation::from(op); + let is_last = i == last_index; + + let maybe_nostop = if is_last { 0 } else { I2C_NOSTOP }; + + // The regular read and write functions in RIOT automatically issue a repeated + // start condition when called after a I2C_NOSTOP operation. + if last_operation != this_operation { + match op { + i2c::Operation::Read(slice) => { + slice[0] = 0xff; + let result = (unsafe { + i2c_read_bytes( + dev.dev, + address as u16, + slice.as_mut_ptr() as _, + slice.len() as _, + maybe_nostop, + ) + }) + .negative_to_error()?; + result + } + i2c::Operation::Write(slice) => (unsafe { + i2c_write_bytes( + dev.dev, + address as u16, + slice.as_ptr() as _, + slice.len() as _, + maybe_nostop, + ) + }) + .negative_to_error()?, + }; + } else { + // No "repeated start", no address, just a different scatter-gather slice + match op { + i2c::Operation::Read(slice) => (unsafe { + slice[0] = 0xff; + i2c_read_bytes( + dev.dev, + 0, + slice.as_mut_ptr() as _, + slice.len() as _, + I2C_NOSTART | maybe_nostop, + ) + }) + .negative_to_error()?, + i2c::Operation::Write(slice) => (unsafe { + i2c_write_bytes( + dev.dev, + 0, + slice.as_ptr() as _, + slice.len() as _, + I2C_NOSTART | maybe_nostop, + ) + }) + .negative_to_error()?, + }; + } + + last_operation = this_operation; + } + Ok(()) + }) + } +} diff --git a/src/i2c/mod.rs b/src/i2c/mod.rs index bbb0799b..7bd09683 100644 --- a/src/i2c/mod.rs +++ b/src/i2c/mod.rs @@ -1,6 +1,7 @@ //! Controlling the I²C bus pub mod impl_0_2; +pub mod impl_1; use riot_sys::i2c_t; diff --git a/tests/i2c/Cargo.toml b/tests/i2c/Cargo.toml index 95cddc18..baf2d865 100644 --- a/tests/i2c/Cargo.toml +++ b/tests/i2c/Cargo.toml @@ -14,4 +14,4 @@ panic = "abort" [dependencies] riot-wrappers = { version = "*", features = [ "set_panic_handler", "panic_handler_format" ] } riot-sys = "*" -embedded-hal = "0.2" +embedded-hal = "1" diff --git a/tests/i2c/src/lib.rs b/tests/i2c/src/lib.rs index 57d9f02d..cb539039 100644 --- a/tests/i2c/src/lib.rs +++ b/tests/i2c/src/lib.rs @@ -2,7 +2,7 @@ //! device is connected. (And reading should be safe on any device / bus). #![no_std] -use embedded_hal::blocking::i2c::Read; +use embedded_hal::i2c::I2c; use riot_wrappers::println; use riot_wrappers::riot_main; From 24de1e3ff38a641291cad7ff6d014be8ab77f02b Mon Sep 17 00:00:00 2001 From: chrysn Date: Fri, 26 Jan 2024 13:26:45 +0100 Subject: [PATCH 11/11] ztimer: Implement embedded-hal 1.0 trait This is not a high quality implementation, but it satisfies the trait requirements. --- src/ztimer/mod.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/ztimer/mod.rs b/src/ztimer/mod.rs index 39f52a78..0be8b6ed 100644 --- a/src/ztimer/mod.rs +++ b/src/ztimer/mod.rs @@ -209,6 +209,32 @@ impl embedded_hal_0_2::blocking::delay::DelayUs for Clock<1000000> { } } +impl embedded_hal::delay::DelayNs for Clock { + // FIXME: Provide delay_us and delay_ms, at least for the clocks where those fit, to avoid the + // loops where the provided function wakes up every 4.3s + + #[inline(always)] + fn delay_ns(&mut self, ns: u32) { + if F > NANOS_PER_SEC { + // On really fast ZTimers, we may need to loop (but let's implement this when anyone + // ever implements a faster-than-nanosecond timer) + todo!("Test for whether this needs to loop") + } else { + // No need to loop, but we need to take care not to overflow -- and we can't + // pre-calculate (F / NANOS_PER_SEC) because that's rounded to 0 + + // FIXME: There has to be a more efficient way -- for now we're relying on inlining and + // hope that constant propagation takes care of things + + // FIXME: This does not round correctly (it should round up the ticks), but ztimer + // ticks have some uncertainty on their own anyway. + + let ticks = (ns as u64) * (F as u64) / (NANOS_PER_SEC as u64); + self.sleep_ticks(ticks as u32); + } + } +} + /// The error type of fallible conversions to ticks. /// /// Overflow is the only ever indicated error type; lack of accuracy in the timer does not