From 16a47a0ad9eb5f40aa1f202d3fbaa03c7b77b836 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Thu, 30 Sep 2021 09:25:45 +0200 Subject: [PATCH 01/12] Add embassy-lora crate This crate contains async radio drivers for various lora drivers that work with embassy timers. The code is imported from Drogue Device ( https://github.com/drogue-iot/drogue-device) The radio drivers integrate with the async LoRaWAN MAC layer in the lorawan-device crate. Also added is an example for the STM32WL55 and for STM32L0 (requires the LoRa Discovery board) for LoRaWAN. Future work is to make the underlying radio drivers using fully async SPI when communicating with the peripheral. --- embassy-lora/Cargo.toml | 33 + embassy-lora/src/fmt.rs | 225 +++++++ embassy-lora/src/lib.rs | 23 + embassy-lora/src/stm32wl/mod.rs | 368 +++++++++++ embassy-lora/src/sx127x/mod.rs | 201 ++++++ embassy-lora/src/sx127x/sx127x_lora/mod.rs | 593 ++++++++++++++++++ .../src/sx127x/sx127x_lora/register.rs | 107 ++++ examples/stm32l0/Cargo.toml | 4 + examples/stm32l0/src/bin/lorawan.rs | 104 +++ examples/stm32wl55/Cargo.toml | 6 +- examples/stm32wl55/src/bin/lorawan.rs | 79 +++ 11 files changed, 1742 insertions(+), 1 deletion(-) create mode 100644 embassy-lora/Cargo.toml create mode 100644 embassy-lora/src/fmt.rs create mode 100644 embassy-lora/src/lib.rs create mode 100644 embassy-lora/src/stm32wl/mod.rs create mode 100644 embassy-lora/src/sx127x/mod.rs create mode 100644 embassy-lora/src/sx127x/sx127x_lora/mod.rs create mode 100644 embassy-lora/src/sx127x/sx127x_lora/register.rs create mode 100644 examples/stm32l0/src/bin/lorawan.rs create mode 100644 examples/stm32wl55/src/bin/lorawan.rs diff --git a/embassy-lora/Cargo.toml b/embassy-lora/Cargo.toml new file mode 100644 index 00000000..af0f25d5 --- /dev/null +++ b/embassy-lora/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "embassy-lora" +version = "0.1.0" +authors = ["Ulf Lilleengen "] +edition = "2018" + +[lib] + +[features] +sx127x = [] +stm32wl = ["embassy-stm32", "embassy-stm32/subghz"] +time = [] + +defmt-trace = [] +defmt-debug = [] +defmt-info = [] +defmt-warn = [] +defmt-error = [] + +[dependencies] + +defmt = { version = "0.2.3", optional = true } +log = { version = "0.4.14", optional = true } + +embassy = { version = "0.1.0", path = "../embassy", default-features = false } +embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32", default-features = false, optional = true } +embassy-hal-common = { version = "0.1.0", path = "../embassy-hal-common", default-features = false } +futures = { version = "0.3.17", default-features = false, features = [ "async-await" ] } +embedded-hal = { version = "0.2", features = ["unproven"] } +bit_field = { version = "0.10" } + +lorawan-device = { git = "https://github.com/lulf/rust-lorawan.git", rev = "a373d06fa8858d251bc70d5789cebcd9a638ec42", default-features = false, features = ["async"] } +lorawan-encoding = { git = "https://github.com/lulf/rust-lorawan.git", rev = "a373d06fa8858d251bc70d5789cebcd9a638ec42", default-features = false } \ No newline at end of file diff --git a/embassy-lora/src/fmt.rs b/embassy-lora/src/fmt.rs new file mode 100644 index 00000000..06697081 --- /dev/null +++ b/embassy-lora/src/fmt.rs @@ -0,0 +1,225 @@ +#![macro_use] +#![allow(unused_macros)] + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} diff --git a/embassy-lora/src/lib.rs b/embassy-lora/src/lib.rs new file mode 100644 index 00000000..b2da2209 --- /dev/null +++ b/embassy-lora/src/lib.rs @@ -0,0 +1,23 @@ +#![no_std] +#![feature(type_alias_impl_trait)] +#![feature(generic_associated_types)] +//! embassy-lora is a collection of async radio drivers that integrate with the lorawan-device +//! crate's async LoRaWAN MAC implementation. + +pub(crate) mod fmt; + +#[cfg(feature = "stm32wl")] +pub mod stm32wl; +#[cfg(feature = "sx127x")] +pub mod sx127x; + +/// A convenience timer to use with the LoRaWAN crate +pub struct LoraTimer; + +#[cfg(feature = "time")] +impl lorawan_device::async_device::radio::Timer for LoraTimer { + type DelayFuture<'m> = impl core::future::Future + 'm; + fn delay_ms<'m>(&'m mut self, millis: u64) -> Self::DelayFuture<'m> { + embassy::time::Timer::after(embassy::time::Duration::from_millis(millis)) + } +} diff --git a/embassy-lora/src/stm32wl/mod.rs b/embassy-lora/src/stm32wl/mod.rs new file mode 100644 index 00000000..8cac46f7 --- /dev/null +++ b/embassy-lora/src/stm32wl/mod.rs @@ -0,0 +1,368 @@ +//! A radio driver integration for the radio found on STM32WL family devices. +use core::future::Future; +use core::mem::MaybeUninit; +use embassy::channel::signal::Signal; +use embassy::interrupt::InterruptExt; +use embassy::util::Unborrow; +use embassy_hal_common::unborrow; +use embassy_stm32::{ + dma::NoDma, + gpio::{AnyPin, Output}, + interrupt::SUBGHZ_RADIO, + subghz::{ + CalibrateImage, CfgIrq, CodingRate, HeaderType, Irq, LoRaBandwidth, LoRaModParams, + LoRaPacketParams, LoRaSyncWord, Ocp, PaConfig, PaSel, PacketType, RampTime, RegMode, + RfFreq, SpreadingFactor as SF, StandbyClk, Status, SubGhz, TcxoMode, TcxoTrim, Timeout, + TxParams, + }, +}; +use embedded_hal::digital::v2::OutputPin; +use lorawan_device::async_device::{ + radio::{Bandwidth, PhyRxTx, RfConfig, RxQuality, SpreadingFactor, TxConfig}, + Timings, +}; + +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum State { + Idle, + Txing, + Rxing, +} + +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct RadioError; + +static IRQ: Signal<(Status, u16)> = Signal::new(); + +struct StateInner<'a> { + radio: SubGhz<'a, NoDma, NoDma>, + switch: RadioSwitch<'a>, +} + +/// External state storage for the radio state +pub struct SubGhzState<'a>(MaybeUninit>); +impl<'a> SubGhzState<'a> { + pub const fn new() -> Self { + Self(MaybeUninit::uninit()) + } +} + +/// The radio peripheral keeping the radio state and owning the radio IRQ. +pub struct SubGhzRadio<'a> { + state: *mut StateInner<'a>, + _irq: SUBGHZ_RADIO, +} + +impl<'a> SubGhzRadio<'a> { + /// Create a new instance of a SubGhz radio for LoRaWAN. + /// + /// # Safety + /// Do not leak self or futures + pub unsafe fn new( + state: &'a mut SubGhzState<'a>, + radio: SubGhz<'a, NoDma, NoDma>, + switch: RadioSwitch<'a>, + irq: impl Unborrow, + ) -> Self { + unborrow!(irq); + + let mut inner = StateInner { radio, switch }; + inner.radio.reset(); + + let state_ptr = state.0.as_mut_ptr(); + state_ptr.write(inner); + + irq.disable(); + irq.set_handler(|p| { + // This is safe because we only get interrupts when configured for, so + // the radio will be awaiting on the signal at this point. If not, the ISR will + // anyway only adjust the state in the IRQ signal state. + let state = unsafe { &mut *(p as *mut StateInner<'a>) }; + state.on_interrupt(); + }); + irq.set_handler_context(state_ptr as *mut ()); + irq.enable(); + + Self { + state: state_ptr, + _irq: irq, + } + } +} + +impl<'a> StateInner<'a> { + /// Configure radio settings in preparation for TX or RX + pub(crate) fn configure(&mut self) -> Result<(), RadioError> { + trace!("Configuring STM32WL SUBGHZ radio"); + self.radio.set_standby(StandbyClk::Rc)?; + let tcxo_mode = TcxoMode::new() + .set_txco_trim(TcxoTrim::Volts1pt7) + .set_timeout(Timeout::from_duration_sat( + core::time::Duration::from_millis(40), + )); + + self.radio.set_tcxo_mode(&tcxo_mode)?; + self.radio.set_regulator_mode(RegMode::Ldo)?; + + self.radio.calibrate_image(CalibrateImage::ISM_863_870)?; + + self.radio.set_buffer_base_address(0, 0)?; + + self.radio.set_pa_config( + &PaConfig::new() + .set_pa_duty_cycle(0x1) + .set_hp_max(0x0) + .set_pa(PaSel::Lp), + )?; + + self.radio.set_pa_ocp(Ocp::Max140m)?; + + // let tx_params = TxParams::LP_14.set_ramp_time(RampTime::Micros40); + self.radio.set_tx_params( + &TxParams::new() + .set_ramp_time(RampTime::Micros40) + .set_power(0x0A), + )?; + + self.radio.set_packet_type(PacketType::LoRa)?; + self.radio.set_lora_sync_word(LoRaSyncWord::Public)?; + trace!("Done initializing STM32WL SUBGHZ radio"); + Ok(()) + } + + /// Perform a transmission with the given parameters and payload. Returns any time adjustements needed form + /// the upcoming RX window start. + async fn do_tx(&mut self, config: TxConfig, buf: &[u8]) -> Result { + //trace!("TX Request: {}", config); + trace!("TX START"); + self.switch.set_tx_lp(); + self.configure()?; + + self.radio + .set_rf_frequency(&RfFreq::from_frequency(config.rf.frequency))?; + + let mod_params = LoRaModParams::new() + .set_sf(convert_spreading_factor(config.rf.spreading_factor)) + .set_bw(convert_bandwidth(config.rf.bandwidth)) + .set_cr(CodingRate::Cr45) + .set_ldro_en(true); + self.radio.set_lora_mod_params(&mod_params)?; + + let packet_params = LoRaPacketParams::new() + .set_preamble_len(8) + .set_header_type(HeaderType::Variable) + .set_payload_len(buf.len() as u8) + .set_crc_en(true) + .set_invert_iq(false); + + self.radio.set_lora_packet_params(&packet_params)?; + + let irq_cfg = CfgIrq::new() + .irq_enable_all(Irq::TxDone) + .irq_enable_all(Irq::RxDone) + .irq_enable_all(Irq::Timeout); + self.radio.set_irq_cfg(&irq_cfg)?; + + self.radio.set_buffer_base_address(0, 0)?; + self.radio.write_buffer(0, buf)?; + + self.radio.set_tx(Timeout::DISABLED)?; + + loop { + let (_status, irq_status) = IRQ.wait().await; + IRQ.reset(); + + if irq_status & Irq::TxDone.mask() != 0 { + let stats = self.radio.lora_stats()?; + let (status, error_mask) = self.radio.op_error()?; + trace!( + "TX done. Stats: {:?}. OP error: {:?}, mask {:?}", + stats, + status, + error_mask + ); + + return Ok(0); + } else if irq_status & Irq::Timeout.mask() != 0 { + trace!("TX timeout"); + return Err(RadioError); + } + } + } + + /// Perform a radio receive operation with the radio config and receive buffer. The receive buffer must + /// be able to hold a single LoRaWAN packet. + async fn do_rx( + &mut self, + config: RfConfig, + buf: &mut [u8], + ) -> Result<(usize, RxQuality), RadioError> { + assert!(buf.len() >= 255); + trace!("RX START"); + // trace!("Starting RX: {}", config); + self.switch.set_rx(); + self.configure()?; + + self.radio + .set_rf_frequency(&RfFreq::from_frequency(config.frequency))?; + + let mod_params = LoRaModParams::new() + .set_sf(convert_spreading_factor(config.spreading_factor)) + .set_bw(convert_bandwidth(config.bandwidth)) + .set_cr(CodingRate::Cr45) + .set_ldro_en(true); + self.radio.set_lora_mod_params(&mod_params)?; + + let packet_params = LoRaPacketParams::new() + .set_preamble_len(8) + .set_header_type(HeaderType::Variable) + .set_payload_len(0xFF) + .set_crc_en(true) + .set_invert_iq(true); + self.radio.set_lora_packet_params(&packet_params)?; + + let irq_cfg = CfgIrq::new() + .irq_enable_all(Irq::RxDone) + .irq_enable_all(Irq::PreambleDetected) + .irq_enable_all(Irq::HeaderErr) + .irq_enable_all(Irq::Timeout) + .irq_enable_all(Irq::Err); + self.radio.set_irq_cfg(&irq_cfg)?; + + self.radio.set_rx(Timeout::DISABLED)?; + trace!("RX started"); + + loop { + let (status, irq_status) = IRQ.wait().await; + IRQ.reset(); + trace!("RX IRQ {:?}, {:?}", status, irq_status); + if irq_status & Irq::RxDone.mask() != 0 { + let (status, len, ptr) = self.radio.rx_buffer_status()?; + + let packet_status = self.radio.lora_packet_status()?; + let rssi = packet_status.rssi_pkt().to_integer(); + let snr = packet_status.snr_pkt().to_integer(); + trace!( + "RX done. Received {} bytes. RX status: {:?}. Pkt status: {:?}", + len, + status.cmd(), + packet_status, + ); + self.radio.read_buffer(ptr, &mut buf[..len as usize])?; + self.radio.set_standby(StandbyClk::Rc)?; + return Ok((len as usize, RxQuality::new(rssi, snr as i8))); + } else if irq_status & (Irq::Timeout.mask() | Irq::TxDone.mask()) != 0 { + return Err(RadioError); + } + } + } + + /// Read interrupt status and store in global signal + fn on_interrupt(&mut self) { + let (status, irq_status) = self.radio.irq_status().expect("error getting irq status"); + self.radio + .clear_irq_status(irq_status) + .expect("error clearing irq status"); + if irq_status & Irq::PreambleDetected.mask() != 0 { + trace!("Preamble detected, ignoring"); + } else { + IRQ.signal((status, irq_status)); + } + } +} + +impl PhyRxTx for SubGhzRadio<'static> { + type PhyError = RadioError; + + type TxFuture<'m> = impl Future> + 'm; + fn tx<'m>(&'m mut self, config: TxConfig, buf: &'m [u8]) -> Self::TxFuture<'m> { + async move { + let inner = unsafe { &mut *self.state }; + inner.do_tx(config, buf).await + } + } + + type RxFuture<'m> = impl Future> + 'm; + fn rx<'m>(&'m mut self, config: RfConfig, buf: &'m mut [u8]) -> Self::RxFuture<'m> { + async move { + let inner = unsafe { &mut *self.state }; + inner.do_rx(config, buf).await + } + } +} + +impl<'a> From for RadioError { + fn from(_: embassy_stm32::spi::Error) -> Self { + RadioError + } +} + +impl<'a> Timings for SubGhzRadio<'a> { + fn get_rx_window_offset_ms(&self) -> i32 { + -200 + } + fn get_rx_window_duration_ms(&self) -> u32 { + 800 + } +} + +/// Represents the radio switch found on STM32WL based boards, used to control the radio for transmission or reception. +pub struct RadioSwitch<'a> { + ctrl1: Output<'a, AnyPin>, + ctrl2: Output<'a, AnyPin>, + ctrl3: Output<'a, AnyPin>, +} + +impl<'a> RadioSwitch<'a> { + pub fn new( + ctrl1: Output<'a, AnyPin>, + ctrl2: Output<'a, AnyPin>, + ctrl3: Output<'a, AnyPin>, + ) -> Self { + Self { + ctrl1, + ctrl2, + ctrl3, + } + } + + pub(crate) fn set_rx(&mut self) { + self.ctrl1.set_high().unwrap(); + self.ctrl2.set_low().unwrap(); + self.ctrl3.set_high().unwrap(); + } + + pub(crate) fn set_tx_lp(&mut self) { + self.ctrl1.set_high().unwrap(); + self.ctrl2.set_high().unwrap(); + self.ctrl3.set_high().unwrap(); + } + + #[allow(dead_code)] + pub(crate) fn set_tx_hp(&mut self) { + self.ctrl2.set_high().unwrap(); + self.ctrl1.set_low().unwrap(); + self.ctrl3.set_high().unwrap(); + } +} + +fn convert_spreading_factor(sf: SpreadingFactor) -> SF { + match sf { + SpreadingFactor::_7 => SF::Sf7, + SpreadingFactor::_8 => SF::Sf8, + SpreadingFactor::_9 => SF::Sf9, + SpreadingFactor::_10 => SF::Sf10, + SpreadingFactor::_11 => SF::Sf11, + SpreadingFactor::_12 => SF::Sf12, + } +} + +fn convert_bandwidth(bw: Bandwidth) -> LoRaBandwidth { + match bw { + Bandwidth::_125KHz => LoRaBandwidth::Bw125, + Bandwidth::_250KHz => LoRaBandwidth::Bw250, + Bandwidth::_500KHz => LoRaBandwidth::Bw500, + } +} diff --git a/embassy-lora/src/sx127x/mod.rs b/embassy-lora/src/sx127x/mod.rs new file mode 100644 index 00000000..a9736b85 --- /dev/null +++ b/embassy-lora/src/sx127x/mod.rs @@ -0,0 +1,201 @@ +use core::future::Future; +use embassy::traits::gpio::WaitForRisingEdge; +use embedded_hal::blocking::delay::DelayMs; +use embedded_hal::blocking::spi::{Transfer, Write}; +use embedded_hal::digital::v2::OutputPin; +use lorawan_device::async_device::{ + radio::{Bandwidth, PhyRxTx, RfConfig, RxQuality, SpreadingFactor, TxConfig}, + Timings, +}; + +mod sx127x_lora; +use sx127x_lora::{Error as RadioError, LoRa, RadioMode, IRQ}; + +/// Trait representing a radio switch for boards using the Sx127x radio. One some +/// boards, this will be a dummy implementation that does nothing. +pub trait RadioSwitch { + fn set_tx(&mut self); + fn set_rx(&mut self); +} + +/// Semtech Sx127x radio peripheral +pub struct Sx127xRadio +where + SPI: Transfer + Write + 'static, + E: 'static, + CS: OutputPin + 'static, + RESET: OutputPin + 'static, + I: WaitForRisingEdge + 'static, + RFS: RadioSwitch + 'static, +{ + radio: LoRa, + rfs: RFS, + irq: I, +} + +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum State { + Idle, + Txing, + Rxing, +} + +impl Sx127xRadio +where + SPI: Transfer + Write + 'static, + CS: OutputPin + 'static, + RESET: OutputPin + 'static, + I: WaitForRisingEdge + 'static, + RFS: RadioSwitch + 'static, +{ + pub fn new>( + spi: SPI, + cs: CS, + reset: RESET, + irq: I, + rfs: RFS, + d: &mut D, + ) -> Result> { + let mut radio = LoRa::new(spi, cs, reset); + radio.reset(d)?; + Ok(Self { radio, irq, rfs }) + } +} + +impl Timings for Sx127xRadio +where + SPI: Transfer + Write + 'static, + CS: OutputPin + 'static, + RESET: OutputPin + 'static, + I: WaitForRisingEdge + 'static, + RFS: RadioSwitch + 'static, +{ + fn get_rx_window_offset_ms(&self) -> i32 { + -500 + } + fn get_rx_window_duration_ms(&self) -> u32 { + 800 + } +} + +impl PhyRxTx for Sx127xRadio +where + SPI: Transfer + Write + 'static, + CS: OutputPin + 'static, + E: 'static, + RESET: OutputPin + 'static, + I: WaitForRisingEdge + 'static, + RFS: RadioSwitch + 'static, +{ + type PhyError = Sx127xError; + + #[rustfmt::skip] + type TxFuture<'m> where SPI: 'm, CS: 'm, RESET: 'm, E: 'm, I: 'm, RFS: 'm = impl Future> + 'm; + + fn tx<'m>(&'m mut self, config: TxConfig, buf: &'m [u8]) -> Self::TxFuture<'m> { + trace!("TX START"); + async move { + self.rfs.set_tx(); + self.radio.set_tx_power(14, 0)?; + self.radio.set_frequency(config.rf.frequency)?; + // TODO: Modify radio to support other coding rates + self.radio.set_coding_rate_4(5)?; + self.radio + .set_signal_bandwidth(bandwidth_to_i64(config.rf.bandwidth))?; + self.radio + .set_spreading_factor(spreading_factor_to_u8(config.rf.spreading_factor))?; + + self.radio.set_preamble_length(8)?; + self.radio.set_lora_pa_ramp()?; + self.radio.set_lora_sync_word()?; + self.radio.set_invert_iq(false)?; + self.radio.set_crc(true)?; + + self.radio.set_dio0_tx_done()?; + self.radio.transmit_payload(buf)?; + + loop { + self.irq.wait_for_rising_edge().await; + self.radio.set_mode(RadioMode::Stdby).ok().unwrap(); + let irq = self.radio.clear_irq().ok().unwrap(); + if (irq & IRQ::IrqTxDoneMask.addr()) != 0 { + trace!("TX DONE"); + return Ok(0); + } + } + } + } + + #[rustfmt::skip] + type RxFuture<'m> where SPI: 'm, CS: 'm, RESET: 'm, E: 'm, I: 'm, RFS: 'm = impl Future> + 'm; + + fn rx<'m>(&'m mut self, config: RfConfig, buf: &'m mut [u8]) -> Self::RxFuture<'m> { + trace!("RX START"); + async move { + self.rfs.set_rx(); + self.radio.reset_payload_length()?; + self.radio.set_frequency(config.frequency)?; + // TODO: Modify radio to support other coding rates + self.radio.set_coding_rate_4(5)?; + self.radio + .set_signal_bandwidth(bandwidth_to_i64(config.bandwidth))?; + self.radio + .set_spreading_factor(spreading_factor_to_u8(config.spreading_factor))?; + + self.radio.set_preamble_length(8)?; + self.radio.set_lora_sync_word()?; + self.radio.set_invert_iq(true)?; + self.radio.set_crc(true)?; + + self.radio.set_dio0_rx_done()?; + self.radio.set_mode(RadioMode::RxContinuous)?; + + loop { + self.irq.wait_for_rising_edge().await; + self.radio.set_mode(RadioMode::Stdby).ok().unwrap(); + let irq = self.radio.clear_irq().ok().unwrap(); + if (irq & IRQ::IrqRxDoneMask.addr()) != 0 { + let rssi = self.radio.get_packet_rssi().unwrap_or(0) as i16; + let snr = self.radio.get_packet_snr().unwrap_or(0.0) as i8; + let response = if let Ok(size) = self.radio.read_packet_size() { + self.radio.read_packet(buf)?; + Ok((size, RxQuality::new(rssi, snr))) + } else { + Ok((0, RxQuality::new(rssi, snr))) + }; + trace!("RX DONE"); + return response; + } + } + } + } +} + +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Sx127xError; + +impl From> for Sx127xError { + fn from(_: sx127x_lora::Error) -> Self { + Sx127xError + } +} + +fn spreading_factor_to_u8(sf: SpreadingFactor) -> u8 { + match sf { + SpreadingFactor::_7 => 7, + SpreadingFactor::_8 => 8, + SpreadingFactor::_9 => 9, + SpreadingFactor::_10 => 10, + SpreadingFactor::_11 => 11, + SpreadingFactor::_12 => 12, + } +} + +fn bandwidth_to_i64(bw: Bandwidth) -> i64 { + match bw { + Bandwidth::_125KHz => 125_000, + Bandwidth::_250KHz => 250_000, + Bandwidth::_500KHz => 500_000, + } +} diff --git a/embassy-lora/src/sx127x/sx127x_lora/mod.rs b/embassy-lora/src/sx127x/sx127x_lora/mod.rs new file mode 100644 index 00000000..c541815f --- /dev/null +++ b/embassy-lora/src/sx127x/sx127x_lora/mod.rs @@ -0,0 +1,593 @@ +// Copyright Charles Wade (https://github.com/mr-glt/sx127x_lora). Licensed under the Apache 2.0 +// license +// +// Modifications made to make the driver work with the rust-lorawan link layer. + +#![allow(dead_code)] + +use bit_field::BitField; +use embedded_hal::blocking::{ + delay::DelayMs, + spi::{Transfer, Write}, +}; +use embedded_hal::digital::v2::OutputPin; +use embedded_hal::spi::{Mode, Phase, Polarity}; + +mod register; +use self::register::PaConfig; +use self::register::Register; +pub use self::register::IRQ; + +/// Provides the necessary SPI mode configuration for the radio +pub const MODE: Mode = Mode { + phase: Phase::CaptureOnSecondTransition, + polarity: Polarity::IdleHigh, +}; + +/// Provides high-level access to Semtech SX1276/77/78/79 based boards connected to a Raspberry Pi +pub struct LoRa { + spi: SPI, + cs: CS, + reset: RESET, + pub explicit_header: bool, + pub mode: RadioMode, +} + +#[allow(clippy::upper_case_acronyms)] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + Uninformative, + VersionMismatch(u8), + CS(CS), + Reset(RESET), + SPI(SPI), + Transmitting, +} + +use super::sx127x_lora::register::{FskDataModulationShaping, FskRampUpRamDown}; +use Error::*; + +#[cfg(not(feature = "version_0x09"))] +const VERSION_CHECK: u8 = 0x12; + +#[cfg(feature = "version_0x09")] +const VERSION_CHECK: u8 = 0x09; + +impl LoRa +where + SPI: Transfer + Write, + CS: OutputPin, + RESET: OutputPin, +{ + /// Builds and returns a new instance of the radio. Only one instance of the radio should exist at a time. + /// This also preforms a hardware reset of the module and then puts it in standby. + pub fn new(spi: SPI, cs: CS, reset: RESET) -> Self { + Self { + spi, + cs, + reset, + explicit_header: true, + mode: RadioMode::Sleep, + } + } + + pub fn reset>( + &mut self, + d: &mut D, + ) -> Result<(), Error> { + self.reset.set_low().map_err(Reset)?; + d.delay_ms(10_u32); + self.reset.set_high().map_err(Reset)?; + d.delay_ms(10_u32); + let version = self.read_register(Register::RegVersion.addr())?; + if version == VERSION_CHECK { + self.set_mode(RadioMode::Sleep)?; + self.write_register(Register::RegFifoTxBaseAddr.addr(), 0)?; + self.write_register(Register::RegFifoRxBaseAddr.addr(), 0)?; + let lna = self.read_register(Register::RegLna.addr())?; + self.write_register(Register::RegLna.addr(), lna | 0x03)?; + self.write_register(Register::RegModemConfig3.addr(), 0x04)?; + self.set_tcxo(true)?; + self.set_mode(RadioMode::Stdby)?; + self.cs.set_high().map_err(CS)?; + Ok(()) + } else { + Err(Error::VersionMismatch(version)) + } + } + + /// Transmits up to 255 bytes of data. To avoid the use of an allocator, this takes a fixed 255 u8 + /// array and a payload size and returns the number of bytes sent if successful. + pub fn transmit_payload_busy( + &mut self, + buffer: [u8; 255], + payload_size: usize, + ) -> Result> { + if self.transmitting()? { + Err(Transmitting) + } else { + self.set_mode(RadioMode::Stdby)?; + if self.explicit_header { + self.set_explicit_header_mode()?; + } else { + self.set_implicit_header_mode()?; + } + + self.write_register(Register::RegIrqFlags.addr(), 0)?; + self.write_register(Register::RegFifoAddrPtr.addr(), 0)?; + self.write_register(Register::RegPayloadLength.addr(), 0)?; + for byte in buffer.iter().take(payload_size) { + self.write_register(Register::RegFifo.addr(), *byte)?; + } + self.write_register(Register::RegPayloadLength.addr(), payload_size as u8)?; + self.set_mode(RadioMode::Tx)?; + while self.transmitting()? {} + Ok(payload_size) + } + } + + pub fn set_dio0_tx_done(&mut self) -> Result<(), Error> { + self.write_register(Register::RegIrqFlagsMask.addr(), 0b1111_0111)?; + let mapping = self.read_register(Register::RegDioMapping1.addr())?; + self.write_register(Register::RegDioMapping1.addr(), (mapping & 0x3F) | 0x40) + } + + pub fn set_dio0_rx_done(&mut self) -> Result<(), Error> { + self.write_register(Register::RegIrqFlagsMask.addr(), 0b0001_1111)?; + let mapping = self.read_register(Register::RegDioMapping1.addr())?; + self.write_register(Register::RegDioMapping1.addr(), mapping & 0x3F) + } + + pub fn transmit_payload( + &mut self, + buffer: &[u8], + ) -> Result<(), Error> { + assert!(buffer.len() < 255); + if self.transmitting()? { + Err(Transmitting) + } else { + self.set_mode(RadioMode::Stdby)?; + if self.explicit_header { + self.set_explicit_header_mode()?; + } else { + self.set_implicit_header_mode()?; + } + + self.write_register(Register::RegIrqFlags.addr(), 0)?; + self.write_register(Register::RegFifoAddrPtr.addr(), 0)?; + self.write_register(Register::RegPayloadLength.addr(), 0)?; + for byte in buffer.iter() { + self.write_register(Register::RegFifo.addr(), *byte)?; + } + self.write_register(Register::RegPayloadLength.addr(), buffer.len() as u8)?; + self.set_mode(RadioMode::Tx)?; + Ok(()) + } + } + + pub fn packet_ready(&mut self) -> Result> { + Ok(self.read_register(Register::RegIrqFlags.addr())?.get_bit(6)) + } + + pub fn irq_flags_mask(&mut self) -> Result> { + Ok(self.read_register(Register::RegIrqFlagsMask.addr())? as u8) + } + + pub fn irq_flags(&mut self) -> Result> { + Ok(self.read_register(Register::RegIrqFlags.addr())? as u8) + } + + pub fn read_packet_size(&mut self) -> Result> { + let size = self.read_register(Register::RegRxNbBytes.addr())?; + Ok(size as usize) + } + + /// Returns the contents of the fifo as a fixed 255 u8 array. This should only be called is there is a + /// new packet ready to be read. + pub fn read_packet( + &mut self, + buffer: &mut [u8], + ) -> Result<(), Error> { + self.clear_irq()?; + let size = self.read_register(Register::RegRxNbBytes.addr())?; + assert!(size as usize <= buffer.len()); + let fifo_addr = self.read_register(Register::RegFifoRxCurrentAddr.addr())?; + self.write_register(Register::RegFifoAddrPtr.addr(), fifo_addr)?; + for i in 0..size { + let byte = self.read_register(Register::RegFifo.addr())?; + buffer[i as usize] = byte; + } + self.write_register(Register::RegFifoAddrPtr.addr(), 0)?; + Ok(()) + } + + /// Returns true if the radio is currently transmitting a packet. + pub fn transmitting(&mut self) -> Result> { + if (self.read_register(Register::RegOpMode.addr())? & RadioMode::Tx.addr()) + == RadioMode::Tx.addr() + { + Ok(true) + } else { + if (self.read_register(Register::RegIrqFlags.addr())? & IRQ::IrqTxDoneMask.addr()) == 1 + { + self.write_register(Register::RegIrqFlags.addr(), IRQ::IrqTxDoneMask.addr())?; + } + Ok(false) + } + } + + /// Clears the radio's IRQ registers. + pub fn clear_irq(&mut self) -> Result> { + let irq_flags = self.read_register(Register::RegIrqFlags.addr())?; + self.write_register(Register::RegIrqFlags.addr(), 0xFF)?; + Ok(irq_flags) + } + + /// Sets the transmit power and pin. Levels can range from 0-14 when the output + /// pin = 0(RFO), and form 0-20 when output pin = 1(PaBoost). Power is in dB. + /// Default value is `17`. + pub fn set_tx_power( + &mut self, + mut level: i32, + output_pin: u8, + ) -> Result<(), Error> { + if PaConfig::PaOutputRfoPin.addr() == output_pin { + // RFO + if level < 0 { + level = 0; + } else if level > 14 { + level = 14; + } + self.write_register(Register::RegPaConfig.addr(), (0x70 | level) as u8) + } else { + // PA BOOST + if level > 17 { + if level > 20 { + level = 20; + } + // subtract 3 from level, so 18 - 20 maps to 15 - 17 + level -= 3; + + // High Power +20 dBm Operation (Semtech SX1276/77/78/79 5.4.3.) + self.write_register(Register::RegPaDac.addr(), 0x87)?; + self.set_ocp(140)?; + } else { + if level < 2 { + level = 2; + } + //Default value PA_HF/LF or +17dBm + self.write_register(Register::RegPaDac.addr(), 0x84)?; + self.set_ocp(100)?; + } + level -= 2; + self.write_register( + Register::RegPaConfig.addr(), + PaConfig::PaBoost.addr() | level as u8, + ) + } + } + + pub fn get_modem_stat(&mut self) -> Result> { + Ok(self.read_register(Register::RegModemStat.addr())? as u8) + } + + /// Sets the over current protection on the radio(mA). + pub fn set_ocp(&mut self, ma: u8) -> Result<(), Error> { + let mut ocp_trim: u8 = 27; + + if ma <= 120 { + ocp_trim = (ma - 45) / 5; + } else if ma <= 240 { + ocp_trim = (ma + 30) / 10; + } + self.write_register(Register::RegOcp.addr(), 0x20 | (0x1F & ocp_trim)) + } + + /// Sets the state of the radio. Default mode after initiation is `Standby`. + pub fn set_mode(&mut self, mode: RadioMode) -> Result<(), Error> { + if self.explicit_header { + self.set_explicit_header_mode()?; + } else { + self.set_implicit_header_mode()?; + } + self.write_register( + Register::RegOpMode.addr(), + RadioMode::LongRangeMode.addr() | mode.addr(), + )?; + + self.mode = mode; + Ok(()) + } + + pub fn reset_payload_length(&mut self) -> Result<(), Error> { + self.write_register(Register::RegPayloadLength.addr(), 0xFF) + } + + /// Sets the frequency of the radio. Values are in megahertz. + /// I.E. 915 MHz must be used for North America. Check regulation for your area. + pub fn set_frequency(&mut self, freq: u32) -> Result<(), Error> { + const FREQ_STEP: f64 = 61.03515625; + // calculate register values + let frf = (freq as f64 / FREQ_STEP) as u32; + // write registers + self.write_register( + Register::RegFrfMsb.addr(), + ((frf & 0x00FF_0000) >> 16) as u8, + )?; + self.write_register(Register::RegFrfMid.addr(), ((frf & 0x0000_FF00) >> 8) as u8)?; + self.write_register(Register::RegFrfLsb.addr(), (frf & 0x0000_00FF) as u8) + } + + /// Sets the radio to use an explicit header. Default state is `ON`. + fn set_explicit_header_mode(&mut self) -> Result<(), Error> { + let reg_modem_config_1 = self.read_register(Register::RegModemConfig1.addr())?; + self.write_register(Register::RegModemConfig1.addr(), reg_modem_config_1 & 0xfe)?; + self.explicit_header = true; + Ok(()) + } + + /// Sets the radio to use an implicit header. Default state is `OFF`. + fn set_implicit_header_mode(&mut self) -> Result<(), Error> { + let reg_modem_config_1 = self.read_register(Register::RegModemConfig1.addr())?; + self.write_register(Register::RegModemConfig1.addr(), reg_modem_config_1 & 0x01)?; + self.explicit_header = false; + Ok(()) + } + + /// Sets the spreading factor of the radio. Supported values are between 6 and 12. + /// If a spreading factor of 6 is set, implicit header mode must be used to transmit + /// and receive packets. Default value is `7`. + pub fn set_spreading_factor( + &mut self, + mut sf: u8, + ) -> Result<(), Error> { + if sf < 6 { + sf = 6; + } else if sf > 12 { + sf = 12; + } + + if sf == 6 { + self.write_register(Register::RegDetectionOptimize.addr(), 0xc5)?; + self.write_register(Register::RegDetectionThreshold.addr(), 0x0c)?; + } else { + self.write_register(Register::RegDetectionOptimize.addr(), 0xc3)?; + self.write_register(Register::RegDetectionThreshold.addr(), 0x0a)?; + } + let modem_config_2 = self.read_register(Register::RegModemConfig2.addr())?; + self.write_register( + Register::RegModemConfig2.addr(), + (modem_config_2 & 0x0f) | ((sf << 4) & 0xf0), + )?; + self.set_ldo_flag()?; + + self.write_register(Register::RegSymbTimeoutLsb.addr(), 0x05)?; + + Ok(()) + } + + pub fn set_tcxo(&mut self, external: bool) -> Result<(), Error> { + if external { + self.write_register(Register::RegTcxo.addr(), 0x10) + } else { + self.write_register(Register::RegTcxo.addr(), 0x00) + } + } + + /// Sets the signal bandwidth of the radio. Supported values are: `7800 Hz`, `10400 Hz`, + /// `15600 Hz`, `20800 Hz`, `31250 Hz`,`41700 Hz` ,`62500 Hz`,`125000 Hz` and `250000 Hz` + /// Default value is `125000 Hz` + pub fn set_signal_bandwidth( + &mut self, + sbw: i64, + ) -> Result<(), Error> { + let bw: i64 = match sbw { + 7_800 => 0, + 10_400 => 1, + 15_600 => 2, + 20_800 => 3, + 31_250 => 4, + 41_700 => 5, + 62_500 => 6, + 125_000 => 7, + 250_000 => 8, + _ => 9, + }; + let modem_config_1 = self.read_register(Register::RegModemConfig1.addr())?; + self.write_register( + Register::RegModemConfig1.addr(), + (modem_config_1 & 0x0f) | ((bw << 4) as u8), + )?; + self.set_ldo_flag()?; + Ok(()) + } + + /// Sets the coding rate of the radio with the numerator fixed at 4. Supported values + /// are between `5` and `8`, these correspond to coding rates of `4/5` and `4/8`. + /// Default value is `5`. + pub fn set_coding_rate_4( + &mut self, + mut denominator: u8, + ) -> Result<(), Error> { + if denominator < 5 { + denominator = 5; + } else if denominator > 8 { + denominator = 8; + } + let cr = denominator - 4; + let modem_config_1 = self.read_register(Register::RegModemConfig1.addr())?; + self.write_register( + Register::RegModemConfig1.addr(), + (modem_config_1 & 0xf1) | (cr << 1), + ) + } + + /// Sets the preamble length of the radio. Values are between 6 and 65535. + /// Default value is `8`. + pub fn set_preamble_length( + &mut self, + length: i64, + ) -> Result<(), Error> { + self.write_register(Register::RegPreambleMsb.addr(), (length >> 8) as u8)?; + self.write_register(Register::RegPreambleLsb.addr(), length as u8) + } + + /// Enables are disables the radio's CRC check. Default value is `false`. + pub fn set_crc(&mut self, value: bool) -> Result<(), Error> { + let modem_config_2 = self.read_register(Register::RegModemConfig2.addr())?; + if value { + self.write_register(Register::RegModemConfig2.addr(), modem_config_2 | 0x04) + } else { + self.write_register(Register::RegModemConfig2.addr(), modem_config_2 & 0xfb) + } + } + + /// Inverts the radio's IQ signals. Default value is `false`. + pub fn set_invert_iq(&mut self, value: bool) -> Result<(), Error> { + if value { + self.write_register(Register::RegInvertiq.addr(), 0x66)?; + self.write_register(Register::RegInvertiq2.addr(), 0x19) + } else { + self.write_register(Register::RegInvertiq.addr(), 0x27)?; + self.write_register(Register::RegInvertiq2.addr(), 0x1d) + } + } + + /// Returns the spreading factor of the radio. + pub fn get_spreading_factor(&mut self) -> Result> { + Ok(self.read_register(Register::RegModemConfig2.addr())? >> 4) + } + + /// Returns the signal bandwidth of the radio. + pub fn get_signal_bandwidth(&mut self) -> Result> { + let bw = self.read_register(Register::RegModemConfig1.addr())? >> 4; + let bw = match bw { + 0 => 7_800, + 1 => 10_400, + 2 => 15_600, + 3 => 20_800, + 4 => 31_250, + 5 => 41_700, + 6 => 62_500, + 7 => 125_000, + 8 => 250_000, + 9 => 500_000, + _ => -1, + }; + Ok(bw) + } + + /// Returns the RSSI of the last received packet. + pub fn get_packet_rssi(&mut self) -> Result> { + Ok(i32::from(self.read_register(Register::RegPktRssiValue.addr())?) - 157) + } + + /// Returns the signal to noise radio of the the last received packet. + pub fn get_packet_snr(&mut self) -> Result> { + Ok(f64::from( + self.read_register(Register::RegPktSnrValue.addr())?, + )) + } + + /// Returns the frequency error of the last received packet in Hz. + pub fn get_packet_frequency_error(&mut self) -> Result> { + let mut freq_error: i32; + freq_error = i32::from(self.read_register(Register::RegFreqErrorMsb.addr())? & 0x7); + freq_error <<= 8i64; + freq_error += i32::from(self.read_register(Register::RegFreqErrorMid.addr())?); + freq_error <<= 8i64; + freq_error += i32::from(self.read_register(Register::RegFreqErrorLsb.addr())?); + + let f_xtal = 32_000_000; // FXOSC: crystal oscillator (XTAL) frequency (2.5. Chip Specification, p. 14) + let f_error = ((f64::from(freq_error) * (1i64 << 24) as f64) / f64::from(f_xtal)) + * (self.get_signal_bandwidth()? as f64 / 500_000.0f64); // p. 37 + Ok(f_error as i64) + } + + fn set_ldo_flag(&mut self) -> Result<(), Error> { + let sw = self.get_signal_bandwidth()?; + // Section 4.1.1.5 + let symbol_duration = 1000 / (sw / ((1_i64) << self.get_spreading_factor()?)); + + // Section 4.1.1.6 + let ldo_on = symbol_duration > 16; + + let mut config_3 = self.read_register(Register::RegModemConfig3.addr())?; + config_3.set_bit(3, ldo_on); + //config_3.set_bit(2, true); + self.write_register(Register::RegModemConfig3.addr(), config_3) + } + + fn read_register(&mut self, reg: u8) -> Result> { + self.cs.set_low().map_err(CS)?; + + let mut buffer = [reg & 0x7f, 0]; + let transfer = self.spi.transfer(&mut buffer).map_err(SPI)?; + self.cs.set_high().map_err(CS)?; + Ok(transfer[1]) + } + + fn write_register( + &mut self, + reg: u8, + byte: u8, + ) -> Result<(), Error> { + self.cs.set_low().map_err(CS)?; + + let buffer = [reg | 0x80, byte]; + self.spi.write(&buffer).map_err(SPI)?; + self.cs.set_high().map_err(CS)?; + Ok(()) + } + + pub fn put_in_fsk_mode(&mut self) -> Result<(), Error> { + // Put in FSK mode + let mut op_mode = 0; + op_mode + .set_bit(7, false) // FSK mode + .set_bits(5..6, 0x00) // FSK modulation + .set_bit(3, false) //Low freq registers + .set_bits(0..2, 0b011); // Mode + + self.write_register(Register::RegOpMode as u8, op_mode) + } + + pub fn set_fsk_pa_ramp( + &mut self, + modulation_shaping: FskDataModulationShaping, + ramp: FskRampUpRamDown, + ) -> Result<(), Error> { + let mut pa_ramp = 0; + pa_ramp + .set_bits(5..6, modulation_shaping as u8) + .set_bits(0..3, ramp as u8); + + self.write_register(Register::RegPaRamp as u8, pa_ramp) + } + + pub fn set_lora_pa_ramp(&mut self) -> Result<(), Error> { + self.write_register(Register::RegPaRamp as u8, 0b1000) + } + + pub fn set_lora_sync_word(&mut self) -> Result<(), Error> { + self.write_register(Register::RegSyncWord as u8, 0x34) + } +} +/// Modes of the radio and their corresponding register values. +#[derive(Clone, Copy)] +pub enum RadioMode { + LongRangeMode = 0x80, + Sleep = 0x00, + Stdby = 0x01, + Tx = 0x03, + RxContinuous = 0x05, + RxSingle = 0x06, +} + +impl RadioMode { + /// Returns the address of the mode. + pub fn addr(self) -> u8 { + self as u8 + } +} diff --git a/embassy-lora/src/sx127x/sx127x_lora/register.rs b/embassy-lora/src/sx127x/sx127x_lora/register.rs new file mode 100644 index 00000000..2445e21b --- /dev/null +++ b/embassy-lora/src/sx127x/sx127x_lora/register.rs @@ -0,0 +1,107 @@ +// Copyright Charles Wade (https://github.com/mr-glt/sx127x_lora). Licensed under the Apache 2.0 +// license +// +// Modifications made to make the driver work with the rust-lorawan link layer. +#![allow(dead_code, clippy::enum_variant_names)] + +#[derive(Clone, Copy)] +pub enum Register { + RegFifo = 0x00, + RegOpMode = 0x01, + RegFrfMsb = 0x06, + RegFrfMid = 0x07, + RegFrfLsb = 0x08, + RegPaConfig = 0x09, + RegPaRamp = 0x0a, + RegOcp = 0x0b, + RegLna = 0x0c, + RegFifoAddrPtr = 0x0d, + RegFifoTxBaseAddr = 0x0e, + RegFifoRxBaseAddr = 0x0f, + RegFifoRxCurrentAddr = 0x10, + RegIrqFlagsMask = 0x11, + RegIrqFlags = 0x12, + RegRxNbBytes = 0x13, + RegPktSnrValue = 0x19, + RegModemStat = 0x18, + RegPktRssiValue = 0x1a, + RegModemConfig1 = 0x1d, + RegModemConfig2 = 0x1e, + RegSymbTimeoutLsb = 0x1f, + RegPreambleMsb = 0x20, + RegPreambleLsb = 0x21, + RegPayloadLength = 0x22, + RegMaxPayloadLength = 0x23, + RegModemConfig3 = 0x26, + RegFreqErrorMsb = 0x28, + RegFreqErrorMid = 0x29, + RegFreqErrorLsb = 0x2a, + RegRssiWideband = 0x2c, + RegDetectionOptimize = 0x31, + RegInvertiq = 0x33, + RegDetectionThreshold = 0x37, + RegSyncWord = 0x39, + RegInvertiq2 = 0x3b, + RegDioMapping1 = 0x40, + RegVersion = 0x42, + RegTcxo = 0x4b, + RegPaDac = 0x4d, +} +#[derive(Clone, Copy)] +pub enum PaConfig { + PaBoost = 0x80, + PaOutputRfoPin = 0, +} + +#[derive(Clone, Copy)] +pub enum IRQ { + IrqTxDoneMask = 0x08, + IrqPayloadCrcErrorMask = 0x20, + IrqRxDoneMask = 0x40, +} + +impl Register { + pub fn addr(self) -> u8 { + self as u8 + } +} + +impl PaConfig { + pub fn addr(self) -> u8 { + self as u8 + } +} + +impl IRQ { + pub fn addr(self) -> u8 { + self as u8 + } +} + +#[derive(Clone, Copy)] +pub enum FskDataModulationShaping { + None = 1, + GaussianBt1d0 = 2, + GaussianBt0d5 = 10, + GaussianBt0d3 = 11, +} + +#[derive(Clone, Copy)] +pub enum FskRampUpRamDown { + _3d4ms = 0b000, + _2ms = 0b0001, + _1ms = 0b0010, + _500us = 0b0011, + _250us = 0b0100, + _125us = 0b0101, + _100us = 0b0110, + _62us = 0b0111, + _50us = 0b1000, + _40us = 0b1001, + _31us = 0b1010, + _25us = 0b1011, + _20us = 0b1100, + _15us = 0b1101, + _12us = 0b1110, + _10us = 0b1111, +} diff --git a/examples/stm32l0/Cargo.toml b/examples/stm32l0/Cargo.toml index 7dfcdb0f..371ac68c 100644 --- a/examples/stm32l0/Cargo.toml +++ b/examples/stm32l0/Cargo.toml @@ -23,6 +23,10 @@ embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [" embassy-hal-common = {version = "0.1.0", path = "../../embassy-hal-common" } embassy-macros = { path = "../../embassy-macros" } +embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["sx127x", "time"] } +lorawan-device = { git = "https://github.com/lulf/rust-lorawan.git", rev = "a373d06fa8858d251bc70d5789cebcd9a638ec42", default-features = false, features = ["async"] } +lorawan-encoding = { git = "https://github.com/lulf/rust-lorawan.git", rev = "a373d06fa8858d251bc70d5789cebcd9a638ec42", default-features = false, features = ["default-crypto"] } + defmt = "0.2.3" defmt-rtt = "0.2.0" diff --git a/examples/stm32l0/src/bin/lorawan.rs b/examples/stm32l0/src/bin/lorawan.rs new file mode 100644 index 00000000..5ca69f9a --- /dev/null +++ b/examples/stm32l0/src/bin/lorawan.rs @@ -0,0 +1,104 @@ +//! This example runs on the STM32 LoRa Discovery board which has a builtin Semtech Sx127x radio +#![no_std] +#![no_main] +#![macro_use] +#![allow(dead_code)] +#![feature(generic_associated_types)] +#![feature(type_alias_impl_trait)] + +#[path = "../example_common.rs"] +mod example_common; + +use embassy_lora::{sx127x::*, LoraTimer}; +use embassy_stm32::{ + dbgmcu::Dbgmcu, + dma::NoDma, + exti::ExtiInput, + gpio::{Input, Level, Output, Pull, Speed}, + rcc, + rng::Rng, + spi, + time::U32Ext, + Peripherals, +}; +use lorawan_device::async_device::{region, Device, JoinMode}; +use lorawan_encoding::default_crypto::DefaultFactory as Crypto; + +fn config() -> embassy_stm32::Config { + let mut config = embassy_stm32::Config::default(); + config.rcc = config.rcc.clock_src(embassy_stm32::rcc::ClockSrc::HSI16); + config +} + +#[embassy::main(config = "config()")] +async fn main(_spawner: embassy::executor::Spawner, mut p: Peripherals) { + unsafe { + Dbgmcu::enable_all(); + } + + let mut rcc = rcc::Rcc::new(p.RCC); + let _ = rcc.enable_hsi48(&mut p.SYSCFG, p.CRS); + + // SPI for sx127x + let spi = spi::Spi::new( + p.SPI1, + p.PB3, + p.PA7, + p.PA6, + NoDma, + NoDma, + 200_000.hz(), + spi::Config::default(), + ); + + let cs = Output::new(p.PA15, Level::High, Speed::Low); + let reset = Output::new(p.PC0, Level::High, Speed::Low); + let _ = Input::new(p.PB1, Pull::None); + + let ready = Input::new(p.PB4, Pull::Up); + let ready_pin = ExtiInput::new(ready, p.EXTI4); + + let radio = Sx127xRadio::new( + spi, + cs, + reset, + ready_pin, + DummySwitch, + &mut embassy::time::Delay, + ) + .unwrap(); + + let region = region::EU868::default().into(); + let mut radio_buffer = [0; 256]; + let mut device: Device<'_, _, Crypto, _, _> = Device::new( + region, + radio, + LoraTimer, + Rng::new(p.RNG), + &mut radio_buffer[..], + ); + + defmt::info!("Joining LoRaWAN network"); + + // TODO: Adjust the EUI and Keys according to your network credentials + device + .join(&JoinMode::OTAA { + deveui: [0, 0, 0, 0, 0, 0, 0, 0], + appeui: [0, 0, 0, 0, 0, 0, 0, 0], + appkey: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + }) + .await + .ok() + .unwrap(); + defmt::info!("LoRaWAN network joined"); + + defmt::info!("Sending 'PING'"); + device.send(b"PING", 1, false).await.ok().unwrap(); + defmt::info!("Message sent!"); +} + +pub struct DummySwitch; +impl RadioSwitch for DummySwitch { + fn set_rx(&mut self) {} + fn set_tx(&mut self) {} +} diff --git a/examples/stm32wl55/Cargo.toml b/examples/stm32wl55/Cargo.toml index d0c72786..4688bdad 100644 --- a/examples/stm32wl55/Cargo.toml +++ b/examples/stm32wl55/Cargo.toml @@ -19,8 +19,12 @@ defmt-error = [] [dependencies] embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt", "defmt-trace"] } embassy-traits = { version = "0.1.0", path = "../../embassy-traits", features = ["defmt"] } -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "defmt-trace", "stm32wl55jc_cm4", "time-driver-tim2", "memory-x", "subghz"] } +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "defmt-trace", "stm32wl55jc_cm4", "time-driver-tim2", "memory-x", "subghz", "unstable-pac"] } embassy-hal-common = {version = "0.1.0", path = "../../embassy-hal-common" } +embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["stm32wl", "time"] } + +lorawan-device = { git = "https://github.com/lulf/rust-lorawan.git", rev = "a373d06fa8858d251bc70d5789cebcd9a638ec42", default-features = false, features = ["async"] } +lorawan-encoding = { git = "https://github.com/lulf/rust-lorawan.git", rev = "a373d06fa8858d251bc70d5789cebcd9a638ec42", default-features = false, features = ["default-crypto"] } defmt = "0.2.3" defmt-rtt = "0.2.0" diff --git a/examples/stm32wl55/src/bin/lorawan.rs b/examples/stm32wl55/src/bin/lorawan.rs new file mode 100644 index 00000000..155905ae --- /dev/null +++ b/examples/stm32wl55/src/bin/lorawan.rs @@ -0,0 +1,79 @@ +#![no_std] +#![no_main] +#![macro_use] +#![allow(dead_code)] +#![feature(generic_associated_types)] +#![feature(type_alias_impl_trait)] + +#[path = "../example_common.rs"] +mod example_common; + +use embassy_lora::{stm32wl::*, LoraTimer}; +use embassy_stm32::{ + dbgmcu::Dbgmcu, + dma::NoDma, + gpio::{Level, Output, Pin, Speed}, + interrupt, pac, rcc, + rng::Rng, + subghz::*, + Peripherals, +}; +use lorawan_device::async_device::{region, Device, JoinMode}; +use lorawan_encoding::default_crypto::DefaultFactory as Crypto; + +fn config() -> embassy_stm32::Config { + let mut config = embassy_stm32::Config::default(); + config.rcc = config.rcc.clock_src(embassy_stm32::rcc::ClockSrc::HSI16); + config +} + +#[embassy::main(config = "config()")] +async fn main(_spawner: embassy::executor::Spawner, p: Peripherals) { + unsafe { + Dbgmcu::enable_all(); + let mut rcc = rcc::Rcc::new(p.RCC); + rcc.enable_lsi(); + pac::RCC.ccipr().modify(|w| { + w.set_rngsel(0b01); + }); + } + + let ctrl1 = Output::new(p.PC3.degrade(), Level::High, Speed::High); + let ctrl2 = Output::new(p.PC4.degrade(), Level::High, Speed::High); + let ctrl3 = Output::new(p.PC5.degrade(), Level::High, Speed::High); + let rfs = RadioSwitch::new(ctrl1, ctrl2, ctrl3); + + let radio = SubGhz::new(p.SUBGHZSPI, p.PA5, p.PA7, p.PA6, NoDma, NoDma); + + let irq = interrupt::take!(SUBGHZ_RADIO); + static mut RADIO_STATE: SubGhzState<'static> = SubGhzState::new(); + let radio = unsafe { SubGhzRadio::new(&mut RADIO_STATE, radio, rfs, irq) }; + + let region = region::EU868::default().into(); + let mut radio_buffer = [0; 256]; + let mut device: Device<'_, _, Crypto, _, _> = Device::new( + region, + radio, + LoraTimer, + Rng::new(p.RNG), + &mut radio_buffer[..], + ); + + defmt::info!("Joining LoRaWAN network"); + + // TODO: Adjust the EUI and Keys according to your network credentials + device + .join(&JoinMode::OTAA { + deveui: [0, 0, 0, 0, 0, 0, 0, 0], + appeui: [0, 0, 0, 0, 0, 0, 0, 0], + appkey: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + }) + .await + .ok() + .unwrap(); + defmt::info!("LoRaWAN network joined"); + + defmt::info!("Sending 'PING'"); + device.send(b"PING", 1, false).await.ok().unwrap(); + defmt::info!("Message sent!"); +} From cef6158c31794795f16099e3df4c9049b976dae6 Mon Sep 17 00:00:00 2001 From: huntc Date: Thu, 7 Oct 2021 18:00:03 +1100 Subject: [PATCH 02/12] Extend SAADC one shot support One-shot mode now permits the sampling of differential pins, and the sampling of multiple pins simultaneously. A new ChannelConfig structure has been introduced so that multiple channels can be configured individually. Further, the `sample` method now accepts a buffer into which samples are written. Along the way, I've reset some default configuration to align with Nordic's settings in their nrfx saadc driver. Specifically, the channel gain defaults to 6 (from 4) and the time defaults to 10us (from 20us). --- README.md | 2 + embassy-nrf/src/saadc.rs | 177 ++++++++++++++++++++++++++-------- examples/nrf/src/bin/saadc.rs | 10 +- 3 files changed, 145 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 13bcf6e5..f8898dcd 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,8 @@ The `embassy-nrf` crate contains implementations for nRF 52 series SoCs. - `uarte`: UARTE driver implementing `AsyncBufRead` and `AsyncWrite`. - `qspi`: QSPI driver implementing `Flash`. - `gpiote`: GPIOTE driver. Allows `await`ing GPIO pin changes. Great for reading buttons or receiving interrupts from external chips. +- `saadc`: SAADC driver. Provides a full implementation of the one-shot sampling for analog channels. + - `rtc`: RTC driver implementing `Clock` and `Alarm`, for use with `embassy::executor`. ## Examples diff --git a/embassy-nrf/src/saadc.rs b/embassy-nrf/src/saadc.rs index b6e8f4e4..12c302d5 100644 --- a/embassy-nrf/src/saadc.rs +++ b/embassy-nrf/src/saadc.rs @@ -1,3 +1,4 @@ +use core::convert::TryInto; use core::marker::PhantomData; use core::sync::atomic::{compiler_fence, Ordering}; use core::task::Poll; @@ -15,6 +16,7 @@ use pac::{saadc, SAADC}; pub use saadc::{ ch::{ config::{GAIN_A as Gain, REFSEL_A as Reference, RESP_A as Resistor, TACQ_A as Time}, + pseln::PSELN_A as NegativeChannel, pselp::PSELP_A as PositiveChannel, }, oversample::OVERSAMPLE_A as Oversample, @@ -27,7 +29,7 @@ pub use saadc::{ pub enum Error {} /// One-shot saadc. Continuous sample mode TODO. -pub struct OneShot<'d> { +pub struct OneShot<'d, const N: usize> { phantom: PhantomData<&'d mut peripherals::SAADC>, } @@ -36,11 +38,29 @@ static WAKER: AtomicWaker = AtomicWaker::new(); /// Used to configure the SAADC peripheral. /// /// See the `Default` impl for suitable default values. +#[non_exhaustive] pub struct Config { /// Output resolution in bits. pub resolution: Resolution, /// Average 2^`oversample` input samples before transferring the result into memory. pub oversample: Oversample, +} + +impl Default for Config { + /// Default configuration for single channel sampling. + fn default() -> Self { + Self { + resolution: Resolution::_14BIT, + oversample: Oversample::BYPASS, + } + } +} + +/// Used to configure an individual SAADC peripheral channel. +/// +/// See the `Default` impl for suitable default values. +#[non_exhaustive] +pub struct ChannelConfig { /// Reference voltage of the SAADC input. pub reference: Reference, /// Gain used to control the effective input range of the SAADC. @@ -49,26 +69,48 @@ pub struct Config { pub resistor: Resistor, /// Acquisition time in microseconds. pub time: Time, + /// Positive channel to sample + p_channel: PositiveChannel, + /// An optional negative channel to sample + n_channel: Option, } -impl Default for Config { - fn default() -> Self { +impl ChannelConfig { + /// Default configuration for single ended channel sampling. + pub fn single_ended(pin: impl Unborrow) -> Self { + unborrow!(pin); Self { - resolution: Resolution::_14BIT, - oversample: Oversample::OVER8X, - reference: Reference::VDD1_4, - gain: Gain::GAIN1_4, + reference: Reference::INTERNAL, + gain: Gain::GAIN1_6, resistor: Resistor::BYPASS, - time: Time::_20US, + time: Time::_10US, + p_channel: pin.channel(), + n_channel: None, + } + } + /// Default configuration for differential channel sampling. + pub fn differential( + ppin: impl Unborrow, + npin: impl Unborrow, + ) -> Self { + unborrow!(ppin, npin); + Self { + reference: Reference::VDD1_4, + gain: Gain::GAIN1_6, + resistor: Resistor::BYPASS, + time: Time::_10US, + p_channel: ppin.channel(), + n_channel: Some(npin.channel()), } } } -impl<'d> OneShot<'d> { +impl<'d, const N: usize> OneShot<'d, N> { pub fn new( _saadc: impl Unborrow + 'd, irq: impl Unborrow + 'd, config: Config, + channel_configs: [ChannelConfig; N], ) -> Self { unborrow!(irq); @@ -77,31 +119,37 @@ impl<'d> OneShot<'d> { let Config { resolution, oversample, - reference, - gain, - resistor, - time, } = config; - // Configure pins + // Configure channels r.enable.write(|w| w.enable().enabled()); r.resolution.write(|w| w.val().variant(resolution)); r.oversample.write(|w| w.oversample().variant(oversample)); - r.ch[0].config.write(|w| { - w.refsel().variant(reference); - w.gain().variant(gain); - w.tacq().variant(time); - w.mode().se(); - w.resp().variant(resistor); - w.resn().bypass(); - if !matches!(oversample, Oversample::BYPASS) { - w.burst().enabled(); - } else { - w.burst().disabled(); + for (i, cc) in channel_configs.iter().enumerate() { + r.ch[i].pselp.write(|w| w.pselp().variant(cc.p_channel)); + if let Some(npin) = cc.n_channel.as_ref() { + r.ch[i].pseln.write(|w| w.pseln().variant(*npin)); } - w - }); + r.ch[i].config.write(|w| { + w.refsel().variant(cc.reference); + w.gain().variant(cc.gain); + w.tacq().variant(cc.time); + if cc.n_channel.is_none() { + w.mode().se(); + } else { + w.mode().diff(); + } + w.resp().variant(cc.resistor); + w.resn().bypass(); + if !matches!(oversample, Oversample::BYPASS) { + w.burst().enabled(); + } else { + w.burst().disabled(); + } + w + }); + } // Disable all events interrupts r.intenclr.write(|w| unsafe { w.bits(0x003F_FFFF) }); @@ -128,18 +176,16 @@ impl<'d> OneShot<'d> { unsafe { &*SAADC::ptr() } } - pub async fn sample(&mut self, pin: &mut impl PositivePin) -> i16 { + pub async fn sample(&mut self, buf: &mut [i16; N]) { let r = Self::regs(); - // Set positive channel - r.ch[0].pselp.write(|w| w.pselp().variant(pin.channel())); - // Set up the DMA - let mut val: i16 = 0; r.result .ptr - .write(|w| unsafe { w.ptr().bits(((&mut val) as *mut _) as u32) }); - r.result.maxcnt.write(|w| unsafe { w.maxcnt().bits(1) }); + .write(|w| unsafe { w.ptr().bits(buf.as_mut_ptr() as u32) }); + r.result + .maxcnt + .write(|w| unsafe { w.maxcnt().bits(N.try_into().unwrap()) }); // Reset and enable the end event r.events_end.reset(); @@ -166,13 +212,10 @@ impl<'d> OneShot<'d> { Poll::Pending }) .await; - - // The DMA wrote the sampled value to `val`. - val } } -impl<'d> Drop for OneShot<'d> { +impl<'d, const N: usize> Drop for OneShot<'d, N> { fn drop(&mut self) { let r = Self::regs(); r.enable.write(|w| w.enable().disabled()); @@ -180,8 +223,6 @@ impl<'d> Drop for OneShot<'d> { } /// A pin that can be used as the positive end of a ADC differential in the SAADC periperhal. -/// -/// Currently negative is always shorted to ground (0V). pub trait PositivePin { fn channel(&self) -> PositiveChannel; } @@ -198,6 +239,38 @@ macro_rules! positive_pin_mappings { }; } +/// A pin that can be used as the negative end of a ADC differential in the SAADC periperhal. +pub trait NegativePin { + fn channel(&self) -> NegativeChannel; +} + +macro_rules! negative_pin_mappings { + ( $($ch:ident => $pin:ident,)*) => { + $( + impl NegativePin for crate::peripherals::$pin { + fn channel(&self) -> NegativeChannel { + NegativeChannel::$ch + } + } + )* + }; +} + +/// Represents an unconnected pin +pub struct NotConnected {} + +impl PositivePin for NotConnected { + fn channel(&self) -> PositiveChannel { + PositiveChannel::NC + } +} + +impl NegativePin for NotConnected { + fn channel(&self) -> NegativeChannel { + NegativeChannel::NC + } +} + // TODO the variant names are unchecked // the pins are copied from nrf hal #[cfg(feature = "9160")] @@ -223,3 +296,27 @@ positive_pin_mappings! { ANALOGINPUT6 => P0_30, ANALOGINPUT7 => P0_31, } + +#[cfg(feature = "9160")] +negative_pin_mappings! { + ANALOGINPUT0 => P0_13, + ANALOGINPUT1 => P0_14, + ANALOGINPUT2 => P0_15, + ANALOGINPUT3 => P0_16, + ANALOGINPUT4 => P0_17, + ANALOGINPUT5 => P0_18, + ANALOGINPUT6 => P0_19, + ANALOGINPUT7 => P0_20, +} + +#[cfg(not(feature = "9160"))] +negative_pin_mappings! { + ANALOGINPUT0 => P0_02, + ANALOGINPUT1 => P0_03, + ANALOGINPUT2 => P0_04, + ANALOGINPUT3 => P0_05, + ANALOGINPUT4 => P0_28, + ANALOGINPUT5 => P0_29, + ANALOGINPUT6 => P0_30, + ANALOGINPUT7 => P0_31, +} diff --git a/examples/nrf/src/bin/saadc.rs b/examples/nrf/src/bin/saadc.rs index c4d23360..d12717c0 100644 --- a/examples/nrf/src/bin/saadc.rs +++ b/examples/nrf/src/bin/saadc.rs @@ -7,18 +7,20 @@ mod example_common; use defmt::panic; use embassy::executor::Spawner; use embassy::time::{Duration, Timer}; -use embassy_nrf::saadc::{Config, OneShot}; +use embassy_nrf::saadc::{ChannelConfig, Config, OneShot}; use embassy_nrf::{interrupt, Peripherals}; use example_common::*; #[embassy::main] async fn main(_spawner: Spawner, mut p: Peripherals) { let config = Config::default(); - let mut saadc = OneShot::new(p.SAADC, interrupt::take!(SAADC), config); + let channel_config = ChannelConfig::single_ended(&mut p.P0_02); + let mut saadc = OneShot::new(p.SAADC, interrupt::take!(SAADC), config, [channel_config]); loop { - let sample = saadc.sample(&mut p.P0_02).await; - info!("sample: {=i16}", sample); + let mut buf = [0; 1]; + saadc.sample(&mut buf).await; + info!("sample: {=i16}", &buf[0]); Timer::after(Duration::from_millis(100)).await; } } From 5f5470a320c47f0cda207e515aaf36e4f63ac043 Mon Sep 17 00:00:00 2001 From: huntc Date: Mon, 11 Oct 2021 08:52:45 +1100 Subject: [PATCH 03/12] Need to borrow the pins for the lifetime of the config, and subsequently the one shot. --- embassy-nrf/src/saadc.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/embassy-nrf/src/saadc.rs b/embassy-nrf/src/saadc.rs index 12c302d5..d7ccd2fc 100644 --- a/embassy-nrf/src/saadc.rs +++ b/embassy-nrf/src/saadc.rs @@ -60,7 +60,7 @@ impl Default for Config { /// /// See the `Default` impl for suitable default values. #[non_exhaustive] -pub struct ChannelConfig { +pub struct ChannelConfig<'d> { /// Reference voltage of the SAADC input. pub reference: Reference, /// Gain used to control the effective input range of the SAADC. @@ -73,11 +73,13 @@ pub struct ChannelConfig { p_channel: PositiveChannel, /// An optional negative channel to sample n_channel: Option, + + phantom: PhantomData<&'d ()>, } -impl ChannelConfig { +impl<'d> ChannelConfig<'d> { /// Default configuration for single ended channel sampling. - pub fn single_ended(pin: impl Unborrow) -> Self { + pub fn single_ended(pin: impl Unborrow + 'd) -> Self { unborrow!(pin); Self { reference: Reference::INTERNAL, @@ -86,12 +88,13 @@ impl ChannelConfig { time: Time::_10US, p_channel: pin.channel(), n_channel: None, + phantom: PhantomData, } } /// Default configuration for differential channel sampling. pub fn differential( - ppin: impl Unborrow, - npin: impl Unborrow, + ppin: impl Unborrow + 'd, + npin: impl Unborrow + 'd, ) -> Self { unborrow!(ppin, npin); Self { @@ -101,6 +104,7 @@ impl ChannelConfig { time: Time::_10US, p_channel: ppin.channel(), n_channel: Some(npin.channel()), + phantom: PhantomData, } } } From 617a976e96282a103a30667be402da27dc4bbbb3 Mon Sep 17 00:00:00 2001 From: huntc Date: Mon, 11 Oct 2021 08:54:24 +1100 Subject: [PATCH 04/12] No need for unwrap --- embassy-nrf/src/saadc.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/embassy-nrf/src/saadc.rs b/embassy-nrf/src/saadc.rs index d7ccd2fc..d8ed5765 100644 --- a/embassy-nrf/src/saadc.rs +++ b/embassy-nrf/src/saadc.rs @@ -1,4 +1,3 @@ -use core::convert::TryInto; use core::marker::PhantomData; use core::sync::atomic::{compiler_fence, Ordering}; use core::task::Poll; @@ -189,7 +188,7 @@ impl<'d, const N: usize> OneShot<'d, N> { .write(|w| unsafe { w.ptr().bits(buf.as_mut_ptr() as u32) }); r.result .maxcnt - .write(|w| unsafe { w.maxcnt().bits(N.try_into().unwrap()) }); + .write(|w| unsafe { w.maxcnt().bits(N as _) }); // Reset and enable the end event r.events_end.reset(); From 25d6a2cd131257d83949424f7eebf47c88d95e0b Mon Sep 17 00:00:00 2001 From: huntc Date: Mon, 11 Oct 2021 08:56:53 +1100 Subject: [PATCH 05/12] No use case understood for NotConnected, so hiding it for now --- embassy-nrf/src/saadc.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/embassy-nrf/src/saadc.rs b/embassy-nrf/src/saadc.rs index d8ed5765..481f886e 100644 --- a/embassy-nrf/src/saadc.rs +++ b/embassy-nrf/src/saadc.rs @@ -259,8 +259,7 @@ macro_rules! negative_pin_mappings { }; } -/// Represents an unconnected pin -pub struct NotConnected {} +struct NotConnected {} impl PositivePin for NotConnected { fn channel(&self) -> PositiveChannel { From b043778f750f60f1361bb56f4b57e8ad56a13dc3 Mon Sep 17 00:00:00 2001 From: huntc Date: Mon, 11 Oct 2021 09:08:58 +1100 Subject: [PATCH 06/12] Removed the NotConnected as it isn't used. --- embassy-nrf/src/saadc.rs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/embassy-nrf/src/saadc.rs b/embassy-nrf/src/saadc.rs index 481f886e..b09b909d 100644 --- a/embassy-nrf/src/saadc.rs +++ b/embassy-nrf/src/saadc.rs @@ -259,20 +259,6 @@ macro_rules! negative_pin_mappings { }; } -struct NotConnected {} - -impl PositivePin for NotConnected { - fn channel(&self) -> PositiveChannel { - PositiveChannel::NC - } -} - -impl NegativePin for NotConnected { - fn channel(&self) -> NegativeChannel { - NegativeChannel::NC - } -} - // TODO the variant names are unchecked // the pins are copied from nrf hal #[cfg(feature = "9160")] From 8c9e50b37871368c31ed23208329fbe28bcc0a7e Mon Sep 17 00:00:00 2001 From: huntc Date: Mon, 11 Oct 2021 09:38:35 +1100 Subject: [PATCH 07/12] Conflates the negative and positive types as they are the same, and renames pin to input as they can be more than pins --- embassy-nrf/src/saadc.rs | 92 ++++++++++++---------------------------- 1 file changed, 26 insertions(+), 66 deletions(-) diff --git a/embassy-nrf/src/saadc.rs b/embassy-nrf/src/saadc.rs index b09b909d..96116b36 100644 --- a/embassy-nrf/src/saadc.rs +++ b/embassy-nrf/src/saadc.rs @@ -15,8 +15,7 @@ use pac::{saadc, SAADC}; pub use saadc::{ ch::{ config::{GAIN_A as Gain, REFSEL_A as Reference, RESP_A as Resistor, TACQ_A as Time}, - pseln::PSELN_A as NegativeChannel, - pselp::PSELP_A as PositiveChannel, + pselp::PSELP_A as InputChannel, // We treat the positive and negative channels with the same enum values to keep our type tidy and given they are the same }, oversample::OVERSAMPLE_A as Oversample, resolution::VAL_A as Resolution, @@ -69,40 +68,40 @@ pub struct ChannelConfig<'d> { /// Acquisition time in microseconds. pub time: Time, /// Positive channel to sample - p_channel: PositiveChannel, + p_channel: InputChannel, /// An optional negative channel to sample - n_channel: Option, + n_channel: Option, phantom: PhantomData<&'d ()>, } impl<'d> ChannelConfig<'d> { /// Default configuration for single ended channel sampling. - pub fn single_ended(pin: impl Unborrow + 'd) -> Self { - unborrow!(pin); + pub fn single_ended(input: impl Unborrow + 'd) -> Self { + unborrow!(input); Self { reference: Reference::INTERNAL, gain: Gain::GAIN1_6, resistor: Resistor::BYPASS, time: Time::_10US, - p_channel: pin.channel(), + p_channel: input.channel(), n_channel: None, phantom: PhantomData, } } /// Default configuration for differential channel sampling. pub fn differential( - ppin: impl Unborrow + 'd, - npin: impl Unborrow + 'd, + p_input: impl Unborrow + 'd, + n_input: impl Unborrow + 'd, ) -> Self { - unborrow!(ppin, npin); + unborrow!(p_input, n_input); Self { reference: Reference::VDD1_4, gain: Gain::GAIN1_6, resistor: Resistor::BYPASS, time: Time::_10US, - p_channel: ppin.channel(), - n_channel: Some(npin.channel()), + p_channel: p_input.channel(), + n_channel: Some(n_input.channel()), phantom: PhantomData, } } @@ -131,8 +130,10 @@ impl<'d, const N: usize> OneShot<'d, N> { for (i, cc) in channel_configs.iter().enumerate() { r.ch[i].pselp.write(|w| w.pselp().variant(cc.p_channel)); - if let Some(npin) = cc.n_channel.as_ref() { - r.ch[i].pseln.write(|w| w.pseln().variant(*npin)); + if let Some(n_channel) = cc.n_channel { + r.ch[i] + .pseln + .write(|w| unsafe { w.pseln().bits(n_channel as u8) }); } r.ch[i].config.write(|w| { w.refsel().variant(cc.reference); @@ -225,34 +226,17 @@ impl<'d, const N: usize> Drop for OneShot<'d, N> { } } -/// A pin that can be used as the positive end of a ADC differential in the SAADC periperhal. -pub trait PositivePin { - fn channel(&self) -> PositiveChannel; +/// An input that can be used as either or negative end of a ADC differential in the SAADC periperhal. +pub trait Input { + fn channel(&self) -> InputChannel; } -macro_rules! positive_pin_mappings { - ( $($ch:ident => $pin:ident,)*) => { +macro_rules! input_mappings { + ( $($ch:ident => $input:ident,)*) => { $( - impl PositivePin for crate::peripherals::$pin { - fn channel(&self) -> PositiveChannel { - PositiveChannel::$ch - } - } - )* - }; -} - -/// A pin that can be used as the negative end of a ADC differential in the SAADC periperhal. -pub trait NegativePin { - fn channel(&self) -> NegativeChannel; -} - -macro_rules! negative_pin_mappings { - ( $($ch:ident => $pin:ident,)*) => { - $( - impl NegativePin for crate::peripherals::$pin { - fn channel(&self) -> NegativeChannel { - NegativeChannel::$ch + impl Input for crate::peripherals::$input { + fn channel(&self) -> InputChannel { + InputChannel::$ch } } )* @@ -260,9 +244,9 @@ macro_rules! negative_pin_mappings { } // TODO the variant names are unchecked -// the pins are copied from nrf hal +// the inputs are copied from nrf hal #[cfg(feature = "9160")] -positive_pin_mappings! { +input_mappings! { ANALOGINPUT0 => P0_13, ANALOGINPUT1 => P0_14, ANALOGINPUT2 => P0_15, @@ -274,31 +258,7 @@ positive_pin_mappings! { } #[cfg(not(feature = "9160"))] -positive_pin_mappings! { - ANALOGINPUT0 => P0_02, - ANALOGINPUT1 => P0_03, - ANALOGINPUT2 => P0_04, - ANALOGINPUT3 => P0_05, - ANALOGINPUT4 => P0_28, - ANALOGINPUT5 => P0_29, - ANALOGINPUT6 => P0_30, - ANALOGINPUT7 => P0_31, -} - -#[cfg(feature = "9160")] -negative_pin_mappings! { - ANALOGINPUT0 => P0_13, - ANALOGINPUT1 => P0_14, - ANALOGINPUT2 => P0_15, - ANALOGINPUT3 => P0_16, - ANALOGINPUT4 => P0_17, - ANALOGINPUT5 => P0_18, - ANALOGINPUT6 => P0_19, - ANALOGINPUT7 => P0_20, -} - -#[cfg(not(feature = "9160"))] -negative_pin_mappings! { +input_mappings! { ANALOGINPUT0 => P0_02, ANALOGINPUT1 => P0_03, ANALOGINPUT2 => P0_04, From 0b26ab7bbc0be784c011118574a84132d6747618 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 11 Oct 2021 00:55:17 +0200 Subject: [PATCH 08/12] nrf/saadc: nrf52820 doesn't have it. --- embassy-nrf/src/chips/nrf52820.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/embassy-nrf/src/chips/nrf52820.rs b/embassy-nrf/src/chips/nrf52820.rs index 4c0d3b0b..e434c009 100644 --- a/embassy-nrf/src/chips/nrf52820.rs +++ b/embassy-nrf/src/chips/nrf52820.rs @@ -22,9 +22,6 @@ embassy_hal_common::peripherals! { TWISPI0, TWISPI1, - // SAADC - SAADC, - // TIMER TIMER0, TIMER1, From 668364da9ce97e924be9aceca4cc8a6bfb286eb4 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 11 Oct 2021 00:55:31 +0200 Subject: [PATCH 09/12] nrf/saadc: nrf52805 only has 2 channels --- embassy-nrf/src/saadc.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/embassy-nrf/src/saadc.rs b/embassy-nrf/src/saadc.rs index 96116b36..2f2baefa 100644 --- a/embassy-nrf/src/saadc.rs +++ b/embassy-nrf/src/saadc.rs @@ -245,7 +245,7 @@ macro_rules! input_mappings { // TODO the variant names are unchecked // the inputs are copied from nrf hal -#[cfg(feature = "9160")] +#[cfg(feature = "nrf9160")] input_mappings! { ANALOGINPUT0 => P0_13, ANALOGINPUT1 => P0_14, @@ -256,8 +256,13 @@ input_mappings! { ANALOGINPUT6 => P0_19, ANALOGINPUT7 => P0_20, } +#[cfg(feature = "nrf52805")] +input_mappings! { + ANALOGINPUT2 => P0_04, + ANALOGINPUT3 => P0_05, +} -#[cfg(not(feature = "9160"))] +#[cfg(not(any(feature = "nrf52805", feature = "nrf9160")))] input_mappings! { ANALOGINPUT0 => P0_02, ANALOGINPUT1 => P0_03, From 6e7abba9ff0d1b17e0b81b1a3f87794e59e65128 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 11 Oct 2021 01:09:57 +0200 Subject: [PATCH 10/12] nrf: rename Cargo feature references from "51" to "nrf51". Note: the feature doesn't actually exist, references are there just in case nrf51xx is supported in the future. --- embassy-nrf/src/gpiote.rs | 8 ++++---- embassy-nrf/src/ppi.rs | 18 +++++++++--------- embassy-nrf/src/util.rs | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/embassy-nrf/src/gpiote.rs b/embassy-nrf/src/gpiote.rs index 001ee7fb..25d09db4 100644 --- a/embassy-nrf/src/gpiote.rs +++ b/embassy-nrf/src/gpiote.rs @@ -239,14 +239,14 @@ impl<'d, C: Channel, T: GpioPin> OutputChannel<'d, C, T> { } /// Triggers `task set` (set associated pin high). - #[cfg(not(feature = "51"))] + #[cfg(not(feature = "nrf51"))] pub fn set(&self) { let g = unsafe { &*pac::GPIOTE::ptr() }; g.tasks_set[self.ch.number()].write(|w| unsafe { w.bits(1) }); } /// Triggers `task clear` (set associated pin low). - #[cfg(not(feature = "51"))] + #[cfg(not(feature = "nrf51"))] pub fn clear(&self) { let g = unsafe { &*pac::GPIOTE::ptr() }; g.tasks_clr[self.ch.number()].write(|w| unsafe { w.bits(1) }); @@ -259,14 +259,14 @@ impl<'d, C: Channel, T: GpioPin> OutputChannel<'d, C, T> { } /// Returns the CLR task, for use with PPI. - #[cfg(not(feature = "51"))] + #[cfg(not(feature = "nrf51"))] pub fn task_clr(&self) -> Task { let g = unsafe { &*pac::GPIOTE::ptr() }; Task::from_reg(&g.tasks_clr[self.ch.number()]) } /// Returns the SET task, for use with PPI. - #[cfg(not(feature = "51"))] + #[cfg(not(feature = "nrf51"))] pub fn task_set(&self) -> Task { let g = unsafe { &*pac::GPIOTE::ptr() }; Task::from_reg(&g.tasks_set[self.ch.number()]) diff --git a/embassy-nrf/src/ppi.rs b/embassy-nrf/src/ppi.rs index 61028c03..a18dec12 100644 --- a/embassy-nrf/src/ppi.rs +++ b/embassy-nrf/src/ppi.rs @@ -31,7 +31,7 @@ impl<'d, C: Channel> Ppi<'d, C> { ch, phantom: PhantomData, }; - #[cfg(not(feature = "51"))] + #[cfg(not(feature = "nrf51"))] this.clear_fork_task(); this } @@ -50,7 +50,7 @@ impl<'d, C: Channel> Ppi<'d, C> { .write(|w| unsafe { w.bits(1 << self.ch.number()) }); } - #[cfg(not(feature = "51"))] + #[cfg(not(feature = "nrf51"))] /// Sets the fork task that must be triggered when the configured event occurs. The user must /// provide a reference to the task. pub fn set_fork_task(&mut self, task: Task) { @@ -60,7 +60,7 @@ impl<'d, C: Channel> Ppi<'d, C> { .write(|w| unsafe { w.bits(task.0.as_ptr() as u32) }) } - #[cfg(not(feature = "51"))] + #[cfg(not(feature = "nrf51"))] /// Clear the fork task endpoint. Previously set task will no longer be triggered. pub fn clear_fork_task(&mut self) { let r = unsafe { &*pac::PPI::ptr() }; @@ -199,13 +199,13 @@ impl_channel!(PPI_CH12, 12, configurable); impl_channel!(PPI_CH13, 13, configurable); impl_channel!(PPI_CH14, 14, configurable); impl_channel!(PPI_CH15, 15, configurable); -#[cfg(not(feature = "51"))] +#[cfg(not(feature = "nrf51"))] impl_channel!(PPI_CH16, 16, configurable); -#[cfg(not(feature = "51"))] +#[cfg(not(feature = "nrf51"))] impl_channel!(PPI_CH17, 17, configurable); -#[cfg(not(feature = "51"))] +#[cfg(not(feature = "nrf51"))] impl_channel!(PPI_CH18, 18, configurable); -#[cfg(not(feature = "51"))] +#[cfg(not(feature = "nrf51"))] impl_channel!(PPI_CH19, 19, configurable); impl_channel!(PPI_CH20, 20); impl_channel!(PPI_CH21, 21); @@ -249,7 +249,7 @@ impl_group!(PPI_GROUP0, 0); impl_group!(PPI_GROUP1, 1); impl_group!(PPI_GROUP2, 2); impl_group!(PPI_GROUP3, 3); -#[cfg(not(feature = "51"))] +#[cfg(not(feature = "nrf51"))] impl_group!(PPI_GROUP4, 4); -#[cfg(not(feature = "51"))] +#[cfg(not(feature = "nrf51"))] impl_group!(PPI_GROUP5, 5); diff --git a/embassy-nrf/src/util.rs b/embassy-nrf/src/util.rs index 7c3974ee..344fb01f 100644 --- a/embassy-nrf/src/util.rs +++ b/embassy-nrf/src/util.rs @@ -8,7 +8,7 @@ pub(crate) fn slice_in_ram(slice: &[u8]) -> bool { } /// Return an error if slice is not in RAM. -#[cfg(not(feature = "51"))] +#[cfg(not(feature = "nrf51"))] pub(crate) fn slice_in_ram_or(slice: &[u8], err: T) -> Result<(), T> { if slice.len() == 0 || slice_in_ram(slice) { Ok(()) From cf13dd7df860afeb9bccee2ca64004f0ac3643d2 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 11 Oct 2021 01:19:06 +0200 Subject: [PATCH 11/12] nrf/ppi: nrf52805 only has 10 configurable channels. --- embassy-nrf/src/chips/nrf52805.rs | 23 ++++++++++++++ embassy-nrf/src/chips/nrf52810.rs | 33 +++++++++++++++++++ embassy-nrf/src/chips/nrf52811.rs | 33 +++++++++++++++++++ embassy-nrf/src/chips/nrf52820.rs | 33 +++++++++++++++++++ embassy-nrf/src/chips/nrf52832.rs | 33 +++++++++++++++++++ embassy-nrf/src/chips/nrf52833.rs | 33 +++++++++++++++++++ embassy-nrf/src/chips/nrf52840.rs | 33 +++++++++++++++++++ embassy-nrf/src/ppi.rs | 53 ++++++------------------------- 8 files changed, 230 insertions(+), 44 deletions(-) diff --git a/embassy-nrf/src/chips/nrf52805.rs b/embassy-nrf/src/chips/nrf52805.rs index f214d927..3bf87e3d 100644 --- a/embassy-nrf/src/chips/nrf52805.rs +++ b/embassy-nrf/src/chips/nrf52805.rs @@ -159,6 +159,29 @@ impl_pin!(P0_29, 0, 29); impl_pin!(P0_30, 0, 30); impl_pin!(P0_31, 0, 31); +impl_ppi_channel!(PPI_CH0, 0, configurable); +impl_ppi_channel!(PPI_CH1, 1, configurable); +impl_ppi_channel!(PPI_CH2, 2, configurable); +impl_ppi_channel!(PPI_CH3, 3, configurable); +impl_ppi_channel!(PPI_CH4, 4, configurable); +impl_ppi_channel!(PPI_CH5, 5, configurable); +impl_ppi_channel!(PPI_CH6, 6, configurable); +impl_ppi_channel!(PPI_CH7, 7, configurable); +impl_ppi_channel!(PPI_CH8, 8, configurable); +impl_ppi_channel!(PPI_CH9, 9, configurable); +impl_ppi_channel!(PPI_CH20, 20); +impl_ppi_channel!(PPI_CH21, 21); +impl_ppi_channel!(PPI_CH22, 22); +impl_ppi_channel!(PPI_CH23, 23); +impl_ppi_channel!(PPI_CH24, 24); +impl_ppi_channel!(PPI_CH25, 25); +impl_ppi_channel!(PPI_CH26, 26); +impl_ppi_channel!(PPI_CH27, 27); +impl_ppi_channel!(PPI_CH28, 28); +impl_ppi_channel!(PPI_CH29, 29); +impl_ppi_channel!(PPI_CH30, 30); +impl_ppi_channel!(PPI_CH31, 31); + pub mod irqs { use crate::pac::Interrupt as InterruptEnum; use embassy_macros::interrupt_declare as declare; diff --git a/embassy-nrf/src/chips/nrf52810.rs b/embassy-nrf/src/chips/nrf52810.rs index caef3e7e..d3241364 100644 --- a/embassy-nrf/src/chips/nrf52810.rs +++ b/embassy-nrf/src/chips/nrf52810.rs @@ -164,6 +164,39 @@ impl_pin!(P0_29, 0, 29); impl_pin!(P0_30, 0, 30); impl_pin!(P0_31, 0, 31); +impl_ppi_channel!(PPI_CH0, 0, configurable); +impl_ppi_channel!(PPI_CH1, 1, configurable); +impl_ppi_channel!(PPI_CH2, 2, configurable); +impl_ppi_channel!(PPI_CH3, 3, configurable); +impl_ppi_channel!(PPI_CH4, 4, configurable); +impl_ppi_channel!(PPI_CH5, 5, configurable); +impl_ppi_channel!(PPI_CH6, 6, configurable); +impl_ppi_channel!(PPI_CH7, 7, configurable); +impl_ppi_channel!(PPI_CH8, 8, configurable); +impl_ppi_channel!(PPI_CH9, 9, configurable); +impl_ppi_channel!(PPI_CH10, 10, configurable); +impl_ppi_channel!(PPI_CH11, 11, configurable); +impl_ppi_channel!(PPI_CH12, 12, configurable); +impl_ppi_channel!(PPI_CH13, 13, configurable); +impl_ppi_channel!(PPI_CH14, 14, configurable); +impl_ppi_channel!(PPI_CH15, 15, configurable); +impl_ppi_channel!(PPI_CH16, 16, configurable); +impl_ppi_channel!(PPI_CH17, 17, configurable); +impl_ppi_channel!(PPI_CH18, 18, configurable); +impl_ppi_channel!(PPI_CH19, 19, configurable); +impl_ppi_channel!(PPI_CH20, 20); +impl_ppi_channel!(PPI_CH21, 21); +impl_ppi_channel!(PPI_CH22, 22); +impl_ppi_channel!(PPI_CH23, 23); +impl_ppi_channel!(PPI_CH24, 24); +impl_ppi_channel!(PPI_CH25, 25); +impl_ppi_channel!(PPI_CH26, 26); +impl_ppi_channel!(PPI_CH27, 27); +impl_ppi_channel!(PPI_CH28, 28); +impl_ppi_channel!(PPI_CH29, 29); +impl_ppi_channel!(PPI_CH30, 30); +impl_ppi_channel!(PPI_CH31, 31); + pub mod irqs { use crate::pac::Interrupt as InterruptEnum; use embassy_macros::interrupt_declare as declare; diff --git a/embassy-nrf/src/chips/nrf52811.rs b/embassy-nrf/src/chips/nrf52811.rs index cbf14df2..1231ae62 100644 --- a/embassy-nrf/src/chips/nrf52811.rs +++ b/embassy-nrf/src/chips/nrf52811.rs @@ -165,6 +165,39 @@ impl_pin!(P0_29, 0, 29); impl_pin!(P0_30, 0, 30); impl_pin!(P0_31, 0, 31); +impl_ppi_channel!(PPI_CH0, 0, configurable); +impl_ppi_channel!(PPI_CH1, 1, configurable); +impl_ppi_channel!(PPI_CH2, 2, configurable); +impl_ppi_channel!(PPI_CH3, 3, configurable); +impl_ppi_channel!(PPI_CH4, 4, configurable); +impl_ppi_channel!(PPI_CH5, 5, configurable); +impl_ppi_channel!(PPI_CH6, 6, configurable); +impl_ppi_channel!(PPI_CH7, 7, configurable); +impl_ppi_channel!(PPI_CH8, 8, configurable); +impl_ppi_channel!(PPI_CH9, 9, configurable); +impl_ppi_channel!(PPI_CH10, 10, configurable); +impl_ppi_channel!(PPI_CH11, 11, configurable); +impl_ppi_channel!(PPI_CH12, 12, configurable); +impl_ppi_channel!(PPI_CH13, 13, configurable); +impl_ppi_channel!(PPI_CH14, 14, configurable); +impl_ppi_channel!(PPI_CH15, 15, configurable); +impl_ppi_channel!(PPI_CH16, 16, configurable); +impl_ppi_channel!(PPI_CH17, 17, configurable); +impl_ppi_channel!(PPI_CH18, 18, configurable); +impl_ppi_channel!(PPI_CH19, 19, configurable); +impl_ppi_channel!(PPI_CH20, 20); +impl_ppi_channel!(PPI_CH21, 21); +impl_ppi_channel!(PPI_CH22, 22); +impl_ppi_channel!(PPI_CH23, 23); +impl_ppi_channel!(PPI_CH24, 24); +impl_ppi_channel!(PPI_CH25, 25); +impl_ppi_channel!(PPI_CH26, 26); +impl_ppi_channel!(PPI_CH27, 27); +impl_ppi_channel!(PPI_CH28, 28); +impl_ppi_channel!(PPI_CH29, 29); +impl_ppi_channel!(PPI_CH30, 30); +impl_ppi_channel!(PPI_CH31, 31); + pub mod irqs { use crate::pac::Interrupt as InterruptEnum; use embassy_macros::interrupt_declare as declare; diff --git a/embassy-nrf/src/chips/nrf52820.rs b/embassy-nrf/src/chips/nrf52820.rs index e434c009..fc7a367e 100644 --- a/embassy-nrf/src/chips/nrf52820.rs +++ b/embassy-nrf/src/chips/nrf52820.rs @@ -160,6 +160,39 @@ impl_pin!(P0_29, 0, 29); impl_pin!(P0_30, 0, 30); impl_pin!(P0_31, 0, 31); +impl_ppi_channel!(PPI_CH0, 0, configurable); +impl_ppi_channel!(PPI_CH1, 1, configurable); +impl_ppi_channel!(PPI_CH2, 2, configurable); +impl_ppi_channel!(PPI_CH3, 3, configurable); +impl_ppi_channel!(PPI_CH4, 4, configurable); +impl_ppi_channel!(PPI_CH5, 5, configurable); +impl_ppi_channel!(PPI_CH6, 6, configurable); +impl_ppi_channel!(PPI_CH7, 7, configurable); +impl_ppi_channel!(PPI_CH8, 8, configurable); +impl_ppi_channel!(PPI_CH9, 9, configurable); +impl_ppi_channel!(PPI_CH10, 10, configurable); +impl_ppi_channel!(PPI_CH11, 11, configurable); +impl_ppi_channel!(PPI_CH12, 12, configurable); +impl_ppi_channel!(PPI_CH13, 13, configurable); +impl_ppi_channel!(PPI_CH14, 14, configurable); +impl_ppi_channel!(PPI_CH15, 15, configurable); +impl_ppi_channel!(PPI_CH16, 16, configurable); +impl_ppi_channel!(PPI_CH17, 17, configurable); +impl_ppi_channel!(PPI_CH18, 18, configurable); +impl_ppi_channel!(PPI_CH19, 19, configurable); +impl_ppi_channel!(PPI_CH20, 20); +impl_ppi_channel!(PPI_CH21, 21); +impl_ppi_channel!(PPI_CH22, 22); +impl_ppi_channel!(PPI_CH23, 23); +impl_ppi_channel!(PPI_CH24, 24); +impl_ppi_channel!(PPI_CH25, 25); +impl_ppi_channel!(PPI_CH26, 26); +impl_ppi_channel!(PPI_CH27, 27); +impl_ppi_channel!(PPI_CH28, 28); +impl_ppi_channel!(PPI_CH29, 29); +impl_ppi_channel!(PPI_CH30, 30); +impl_ppi_channel!(PPI_CH31, 31); + pub mod irqs { use crate::pac::Interrupt as InterruptEnum; use embassy_macros::interrupt_declare as declare; diff --git a/embassy-nrf/src/chips/nrf52832.rs b/embassy-nrf/src/chips/nrf52832.rs index b95aa604..9bbef543 100644 --- a/embassy-nrf/src/chips/nrf52832.rs +++ b/embassy-nrf/src/chips/nrf52832.rs @@ -178,6 +178,39 @@ impl_pin!(P0_29, 0, 29); impl_pin!(P0_30, 0, 30); impl_pin!(P0_31, 0, 31); +impl_ppi_channel!(PPI_CH0, 0, configurable); +impl_ppi_channel!(PPI_CH1, 1, configurable); +impl_ppi_channel!(PPI_CH2, 2, configurable); +impl_ppi_channel!(PPI_CH3, 3, configurable); +impl_ppi_channel!(PPI_CH4, 4, configurable); +impl_ppi_channel!(PPI_CH5, 5, configurable); +impl_ppi_channel!(PPI_CH6, 6, configurable); +impl_ppi_channel!(PPI_CH7, 7, configurable); +impl_ppi_channel!(PPI_CH8, 8, configurable); +impl_ppi_channel!(PPI_CH9, 9, configurable); +impl_ppi_channel!(PPI_CH10, 10, configurable); +impl_ppi_channel!(PPI_CH11, 11, configurable); +impl_ppi_channel!(PPI_CH12, 12, configurable); +impl_ppi_channel!(PPI_CH13, 13, configurable); +impl_ppi_channel!(PPI_CH14, 14, configurable); +impl_ppi_channel!(PPI_CH15, 15, configurable); +impl_ppi_channel!(PPI_CH16, 16, configurable); +impl_ppi_channel!(PPI_CH17, 17, configurable); +impl_ppi_channel!(PPI_CH18, 18, configurable); +impl_ppi_channel!(PPI_CH19, 19, configurable); +impl_ppi_channel!(PPI_CH20, 20); +impl_ppi_channel!(PPI_CH21, 21); +impl_ppi_channel!(PPI_CH22, 22); +impl_ppi_channel!(PPI_CH23, 23); +impl_ppi_channel!(PPI_CH24, 24); +impl_ppi_channel!(PPI_CH25, 25); +impl_ppi_channel!(PPI_CH26, 26); +impl_ppi_channel!(PPI_CH27, 27); +impl_ppi_channel!(PPI_CH28, 28); +impl_ppi_channel!(PPI_CH29, 29); +impl_ppi_channel!(PPI_CH30, 30); +impl_ppi_channel!(PPI_CH31, 31); + pub mod irqs { use crate::pac::Interrupt as InterruptEnum; use embassy_macros::interrupt_declare as declare; diff --git a/embassy-nrf/src/chips/nrf52833.rs b/embassy-nrf/src/chips/nrf52833.rs index 570e4b10..d9495dd9 100644 --- a/embassy-nrf/src/chips/nrf52833.rs +++ b/embassy-nrf/src/chips/nrf52833.rs @@ -218,6 +218,39 @@ impl_pin!(P1_13, 1, 13); impl_pin!(P1_14, 1, 14); impl_pin!(P1_15, 1, 15); +impl_ppi_channel!(PPI_CH0, 0, configurable); +impl_ppi_channel!(PPI_CH1, 1, configurable); +impl_ppi_channel!(PPI_CH2, 2, configurable); +impl_ppi_channel!(PPI_CH3, 3, configurable); +impl_ppi_channel!(PPI_CH4, 4, configurable); +impl_ppi_channel!(PPI_CH5, 5, configurable); +impl_ppi_channel!(PPI_CH6, 6, configurable); +impl_ppi_channel!(PPI_CH7, 7, configurable); +impl_ppi_channel!(PPI_CH8, 8, configurable); +impl_ppi_channel!(PPI_CH9, 9, configurable); +impl_ppi_channel!(PPI_CH10, 10, configurable); +impl_ppi_channel!(PPI_CH11, 11, configurable); +impl_ppi_channel!(PPI_CH12, 12, configurable); +impl_ppi_channel!(PPI_CH13, 13, configurable); +impl_ppi_channel!(PPI_CH14, 14, configurable); +impl_ppi_channel!(PPI_CH15, 15, configurable); +impl_ppi_channel!(PPI_CH16, 16, configurable); +impl_ppi_channel!(PPI_CH17, 17, configurable); +impl_ppi_channel!(PPI_CH18, 18, configurable); +impl_ppi_channel!(PPI_CH19, 19, configurable); +impl_ppi_channel!(PPI_CH20, 20); +impl_ppi_channel!(PPI_CH21, 21); +impl_ppi_channel!(PPI_CH22, 22); +impl_ppi_channel!(PPI_CH23, 23); +impl_ppi_channel!(PPI_CH24, 24); +impl_ppi_channel!(PPI_CH25, 25); +impl_ppi_channel!(PPI_CH26, 26); +impl_ppi_channel!(PPI_CH27, 27); +impl_ppi_channel!(PPI_CH28, 28); +impl_ppi_channel!(PPI_CH29, 29); +impl_ppi_channel!(PPI_CH30, 30); +impl_ppi_channel!(PPI_CH31, 31); + pub mod irqs { use crate::pac::Interrupt as InterruptEnum; use embassy_macros::interrupt_declare as declare; diff --git a/embassy-nrf/src/chips/nrf52840.rs b/embassy-nrf/src/chips/nrf52840.rs index 1221f23b..ad89afbc 100644 --- a/embassy-nrf/src/chips/nrf52840.rs +++ b/embassy-nrf/src/chips/nrf52840.rs @@ -223,6 +223,39 @@ impl_pin!(P1_13, 1, 13); impl_pin!(P1_14, 1, 14); impl_pin!(P1_15, 1, 15); +impl_ppi_channel!(PPI_CH0, 0, configurable); +impl_ppi_channel!(PPI_CH1, 1, configurable); +impl_ppi_channel!(PPI_CH2, 2, configurable); +impl_ppi_channel!(PPI_CH3, 3, configurable); +impl_ppi_channel!(PPI_CH4, 4, configurable); +impl_ppi_channel!(PPI_CH5, 5, configurable); +impl_ppi_channel!(PPI_CH6, 6, configurable); +impl_ppi_channel!(PPI_CH7, 7, configurable); +impl_ppi_channel!(PPI_CH8, 8, configurable); +impl_ppi_channel!(PPI_CH9, 9, configurable); +impl_ppi_channel!(PPI_CH10, 10, configurable); +impl_ppi_channel!(PPI_CH11, 11, configurable); +impl_ppi_channel!(PPI_CH12, 12, configurable); +impl_ppi_channel!(PPI_CH13, 13, configurable); +impl_ppi_channel!(PPI_CH14, 14, configurable); +impl_ppi_channel!(PPI_CH15, 15, configurable); +impl_ppi_channel!(PPI_CH16, 16, configurable); +impl_ppi_channel!(PPI_CH17, 17, configurable); +impl_ppi_channel!(PPI_CH18, 18, configurable); +impl_ppi_channel!(PPI_CH19, 19, configurable); +impl_ppi_channel!(PPI_CH20, 20); +impl_ppi_channel!(PPI_CH21, 21); +impl_ppi_channel!(PPI_CH22, 22); +impl_ppi_channel!(PPI_CH23, 23); +impl_ppi_channel!(PPI_CH24, 24); +impl_ppi_channel!(PPI_CH25, 25); +impl_ppi_channel!(PPI_CH26, 26); +impl_ppi_channel!(PPI_CH27, 27); +impl_ppi_channel!(PPI_CH28, 28); +impl_ppi_channel!(PPI_CH29, 29); +impl_ppi_channel!(PPI_CH30, 30); +impl_ppi_channel!(PPI_CH31, 31); + pub mod irqs { use crate::pac::Interrupt as InterruptEnum; use embassy_macros::interrupt_declare as declare; diff --git a/embassy-nrf/src/ppi.rs b/embassy-nrf/src/ppi.rs index a18dec12..1b12b1e9 100644 --- a/embassy-nrf/src/ppi.rs +++ b/embassy-nrf/src/ppi.rs @@ -1,3 +1,5 @@ +#![macro_use] + //! HAL interface for the PPI peripheral. //! //! The Programmable Peripheral Interconnect interface allows for an autonomous interoperability @@ -109,7 +111,7 @@ impl Event { } } -mod sealed { +pub(crate) mod sealed { pub trait ConfigurableChannel {} pub trait Channel {} pub trait Group {} @@ -167,15 +169,15 @@ impl Channel for AnyConfigurableChannel { } } -macro_rules! impl_channel { +macro_rules! impl_ppi_channel { ($type:ident, $number:expr, configurable) => { - impl_channel!($type, $number); - impl sealed::ConfigurableChannel for peripherals::$type {} - impl ConfigurableChannel for peripherals::$type {} + impl_ppi_channel!($type, $number); + impl crate::ppi::sealed::ConfigurableChannel for peripherals::$type {} + impl crate::ppi::ConfigurableChannel for peripherals::$type {} }; ($type:ident, $number:expr) => { - impl sealed::Channel for peripherals::$type {} - impl Channel for peripherals::$type { + impl crate::ppi::sealed::Channel for peripherals::$type {} + impl crate::ppi::Channel for peripherals::$type { fn number(&self) -> usize { $number } @@ -183,43 +185,6 @@ macro_rules! impl_channel { }; } -impl_channel!(PPI_CH0, 0, configurable); -impl_channel!(PPI_CH1, 1, configurable); -impl_channel!(PPI_CH2, 2, configurable); -impl_channel!(PPI_CH3, 3, configurable); -impl_channel!(PPI_CH4, 4, configurable); -impl_channel!(PPI_CH5, 5, configurable); -impl_channel!(PPI_CH6, 6, configurable); -impl_channel!(PPI_CH7, 7, configurable); -impl_channel!(PPI_CH8, 8, configurable); -impl_channel!(PPI_CH9, 9, configurable); -impl_channel!(PPI_CH10, 10, configurable); -impl_channel!(PPI_CH11, 11, configurable); -impl_channel!(PPI_CH12, 12, configurable); -impl_channel!(PPI_CH13, 13, configurable); -impl_channel!(PPI_CH14, 14, configurable); -impl_channel!(PPI_CH15, 15, configurable); -#[cfg(not(feature = "nrf51"))] -impl_channel!(PPI_CH16, 16, configurable); -#[cfg(not(feature = "nrf51"))] -impl_channel!(PPI_CH17, 17, configurable); -#[cfg(not(feature = "nrf51"))] -impl_channel!(PPI_CH18, 18, configurable); -#[cfg(not(feature = "nrf51"))] -impl_channel!(PPI_CH19, 19, configurable); -impl_channel!(PPI_CH20, 20); -impl_channel!(PPI_CH21, 21); -impl_channel!(PPI_CH22, 22); -impl_channel!(PPI_CH23, 23); -impl_channel!(PPI_CH24, 24); -impl_channel!(PPI_CH25, 25); -impl_channel!(PPI_CH26, 26); -impl_channel!(PPI_CH27, 27); -impl_channel!(PPI_CH28, 28); -impl_channel!(PPI_CH29, 29); -impl_channel!(PPI_CH30, 30); -impl_channel!(PPI_CH31, 31); - // ====================== // groups From 14c3260d88b95a7354dd1d0686d113602e25becb Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 11 Oct 2021 01:22:01 +0200 Subject: [PATCH 12/12] nrf/saadc: move chip-specific mappings to chips/ --- embassy-nrf/src/chips/nrf52805.rs | 3 ++ embassy-nrf/src/chips/nrf52810.rs | 9 ++++++ embassy-nrf/src/chips/nrf52811.rs | 9 ++++++ embassy-nrf/src/chips/nrf52832.rs | 9 ++++++ embassy-nrf/src/chips/nrf52833.rs | 9 ++++++ embassy-nrf/src/chips/nrf52840.rs | 9 ++++++ embassy-nrf/src/saadc.rs | 47 ++++++------------------------- 7 files changed, 56 insertions(+), 39 deletions(-) diff --git a/embassy-nrf/src/chips/nrf52805.rs b/embassy-nrf/src/chips/nrf52805.rs index 3bf87e3d..db1fac2f 100644 --- a/embassy-nrf/src/chips/nrf52805.rs +++ b/embassy-nrf/src/chips/nrf52805.rs @@ -182,6 +182,9 @@ impl_ppi_channel!(PPI_CH29, 29); impl_ppi_channel!(PPI_CH30, 30); impl_ppi_channel!(PPI_CH31, 31); +impl_saadc_input!(P0_04, ANALOGINPUT2); +impl_saadc_input!(P0_05, ANALOGINPUT3); + pub mod irqs { use crate::pac::Interrupt as InterruptEnum; use embassy_macros::interrupt_declare as declare; diff --git a/embassy-nrf/src/chips/nrf52810.rs b/embassy-nrf/src/chips/nrf52810.rs index d3241364..06b9bfb3 100644 --- a/embassy-nrf/src/chips/nrf52810.rs +++ b/embassy-nrf/src/chips/nrf52810.rs @@ -197,6 +197,15 @@ impl_ppi_channel!(PPI_CH29, 29); impl_ppi_channel!(PPI_CH30, 30); impl_ppi_channel!(PPI_CH31, 31); +impl_saadc_input!(P0_02, ANALOGINPUT0); +impl_saadc_input!(P0_03, ANALOGINPUT1); +impl_saadc_input!(P0_04, ANALOGINPUT2); +impl_saadc_input!(P0_05, ANALOGINPUT3); +impl_saadc_input!(P0_28, ANALOGINPUT4); +impl_saadc_input!(P0_29, ANALOGINPUT5); +impl_saadc_input!(P0_30, ANALOGINPUT6); +impl_saadc_input!(P0_31, ANALOGINPUT7); + pub mod irqs { use crate::pac::Interrupt as InterruptEnum; use embassy_macros::interrupt_declare as declare; diff --git a/embassy-nrf/src/chips/nrf52811.rs b/embassy-nrf/src/chips/nrf52811.rs index 1231ae62..2f8d98b3 100644 --- a/embassy-nrf/src/chips/nrf52811.rs +++ b/embassy-nrf/src/chips/nrf52811.rs @@ -198,6 +198,15 @@ impl_ppi_channel!(PPI_CH29, 29); impl_ppi_channel!(PPI_CH30, 30); impl_ppi_channel!(PPI_CH31, 31); +impl_saadc_input!(P0_02, ANALOGINPUT0); +impl_saadc_input!(P0_03, ANALOGINPUT1); +impl_saadc_input!(P0_04, ANALOGINPUT2); +impl_saadc_input!(P0_05, ANALOGINPUT3); +impl_saadc_input!(P0_28, ANALOGINPUT4); +impl_saadc_input!(P0_29, ANALOGINPUT5); +impl_saadc_input!(P0_30, ANALOGINPUT6); +impl_saadc_input!(P0_31, ANALOGINPUT7); + pub mod irqs { use crate::pac::Interrupt as InterruptEnum; use embassy_macros::interrupt_declare as declare; diff --git a/embassy-nrf/src/chips/nrf52832.rs b/embassy-nrf/src/chips/nrf52832.rs index 9bbef543..47cf27de 100644 --- a/embassy-nrf/src/chips/nrf52832.rs +++ b/embassy-nrf/src/chips/nrf52832.rs @@ -211,6 +211,15 @@ impl_ppi_channel!(PPI_CH29, 29); impl_ppi_channel!(PPI_CH30, 30); impl_ppi_channel!(PPI_CH31, 31); +impl_saadc_input!(P0_02, ANALOGINPUT0); +impl_saadc_input!(P0_03, ANALOGINPUT1); +impl_saadc_input!(P0_04, ANALOGINPUT2); +impl_saadc_input!(P0_05, ANALOGINPUT3); +impl_saadc_input!(P0_28, ANALOGINPUT4); +impl_saadc_input!(P0_29, ANALOGINPUT5); +impl_saadc_input!(P0_30, ANALOGINPUT6); +impl_saadc_input!(P0_31, ANALOGINPUT7); + pub mod irqs { use crate::pac::Interrupt as InterruptEnum; use embassy_macros::interrupt_declare as declare; diff --git a/embassy-nrf/src/chips/nrf52833.rs b/embassy-nrf/src/chips/nrf52833.rs index d9495dd9..b3d813e2 100644 --- a/embassy-nrf/src/chips/nrf52833.rs +++ b/embassy-nrf/src/chips/nrf52833.rs @@ -251,6 +251,15 @@ impl_ppi_channel!(PPI_CH29, 29); impl_ppi_channel!(PPI_CH30, 30); impl_ppi_channel!(PPI_CH31, 31); +impl_saadc_input!(P0_02, ANALOGINPUT0); +impl_saadc_input!(P0_03, ANALOGINPUT1); +impl_saadc_input!(P0_04, ANALOGINPUT2); +impl_saadc_input!(P0_05, ANALOGINPUT3); +impl_saadc_input!(P0_28, ANALOGINPUT4); +impl_saadc_input!(P0_29, ANALOGINPUT5); +impl_saadc_input!(P0_30, ANALOGINPUT6); +impl_saadc_input!(P0_31, ANALOGINPUT7); + pub mod irqs { use crate::pac::Interrupt as InterruptEnum; use embassy_macros::interrupt_declare as declare; diff --git a/embassy-nrf/src/chips/nrf52840.rs b/embassy-nrf/src/chips/nrf52840.rs index ad89afbc..473036f6 100644 --- a/embassy-nrf/src/chips/nrf52840.rs +++ b/embassy-nrf/src/chips/nrf52840.rs @@ -256,6 +256,15 @@ impl_ppi_channel!(PPI_CH29, 29); impl_ppi_channel!(PPI_CH30, 30); impl_ppi_channel!(PPI_CH31, 31); +impl_saadc_input!(P0_02, ANALOGINPUT0); +impl_saadc_input!(P0_03, ANALOGINPUT1); +impl_saadc_input!(P0_04, ANALOGINPUT2); +impl_saadc_input!(P0_05, ANALOGINPUT3); +impl_saadc_input!(P0_28, ANALOGINPUT4); +impl_saadc_input!(P0_29, ANALOGINPUT5); +impl_saadc_input!(P0_30, ANALOGINPUT6); +impl_saadc_input!(P0_31, ANALOGINPUT7); + pub mod irqs { use crate::pac::Interrupt as InterruptEnum; use embassy_macros::interrupt_declare as declare; diff --git a/embassy-nrf/src/saadc.rs b/embassy-nrf/src/saadc.rs index 2f2baefa..2ce7ef16 100644 --- a/embassy-nrf/src/saadc.rs +++ b/embassy-nrf/src/saadc.rs @@ -1,3 +1,5 @@ +#![macro_use] + use core::marker::PhantomData; use core::sync::atomic::{compiler_fence, Ordering}; use core::task::Poll; @@ -231,45 +233,12 @@ pub trait Input { fn channel(&self) -> InputChannel; } -macro_rules! input_mappings { - ( $($ch:ident => $input:ident,)*) => { - $( - impl Input for crate::peripherals::$input { - fn channel(&self) -> InputChannel { - InputChannel::$ch - } +macro_rules! impl_saadc_input { + ($pin:ident, $ch:ident) => { + impl crate::saadc::Input for crate::peripherals::$pin { + fn channel(&self) -> crate::saadc::InputChannel { + crate::saadc::InputChannel::$ch } - )* + } }; } - -// TODO the variant names are unchecked -// the inputs are copied from nrf hal -#[cfg(feature = "nrf9160")] -input_mappings! { - ANALOGINPUT0 => P0_13, - ANALOGINPUT1 => P0_14, - ANALOGINPUT2 => P0_15, - ANALOGINPUT3 => P0_16, - ANALOGINPUT4 => P0_17, - ANALOGINPUT5 => P0_18, - ANALOGINPUT6 => P0_19, - ANALOGINPUT7 => P0_20, -} -#[cfg(feature = "nrf52805")] -input_mappings! { - ANALOGINPUT2 => P0_04, - ANALOGINPUT3 => P0_05, -} - -#[cfg(not(any(feature = "nrf52805", feature = "nrf9160")))] -input_mappings! { - ANALOGINPUT0 => P0_02, - ANALOGINPUT1 => P0_03, - ANALOGINPUT2 => P0_04, - ANALOGINPUT3 => P0_05, - ANALOGINPUT4 => P0_28, - ANALOGINPUT5 => P0_29, - ANALOGINPUT6 => P0_30, - ANALOGINPUT7 => P0_31, -}