diff --git a/embassy-nrf-examples/src/bin/spim.rs b/embassy-nrf-examples/src/bin/spim.rs new file mode 100644 index 000000000..0a284dc89 --- /dev/null +++ b/embassy-nrf-examples/src/bin/spim.rs @@ -0,0 +1,116 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +#[path = "../example_common.rs"] +mod example_common; +use example_common::*; + +use cortex_m_rt::entry; +use defmt::panic; +use embassy::executor::{task, Executor}; +use embassy::util::Forever; +use embedded_hal::digital::v2::*; +use futures::pin_mut; +use nrf52840_hal::clocks; +use nrf52840_hal::gpio; + +use embassy_nrf::{interrupt, pac, rtc, spim}; + +#[task] +async fn run() { + info!("running!"); + + let p = unsafe { embassy_nrf::pac::Peripherals::steal() }; + let p0 = gpio::p0::Parts::new(p.P0); + + let pins = spim::Pins { + sck: p0.p0_29.into_push_pull_output(gpio::Level::Low).degrade(), + miso: Some(p0.p0_28.into_floating_input().degrade()), + mosi: Some(p0.p0_30.into_push_pull_output(gpio::Level::Low).degrade()), + }; + let config = spim::Config { + pins, + frequency: spim::Frequency::M16, + mode: spim::MODE_0, + orc: 0x00, + }; + + let mut ncs = p0.p0_31.into_push_pull_output(gpio::Level::High); + let spim = spim::Spim::new(p.SPIM3, interrupt::take!(SPIM3), config); + pin_mut!(spim); + + // Example on how to talk to an ENC28J60 chip + + // softreset + cortex_m::asm::delay(10); + ncs.set_low().unwrap(); + cortex_m::asm::delay(5); + let tx = [0xFF]; + unwrap!(spim.as_mut().send_receive(&tx, &mut []).await); + cortex_m::asm::delay(10); + ncs.set_high().unwrap(); + + cortex_m::asm::delay(100000); + + let mut rx = [0; 2]; + + // read ESTAT + cortex_m::asm::delay(5000); + ncs.set_low().unwrap(); + cortex_m::asm::delay(5000); + let tx = [0b000_11101, 0]; + unwrap!(spim.as_mut().send_receive(&tx, &mut rx).await); + cortex_m::asm::delay(5000); + ncs.set_high().unwrap(); + info!("estat: {=[?]}", rx); + + // Switch to bank 3 + cortex_m::asm::delay(10); + ncs.set_low().unwrap(); + cortex_m::asm::delay(5); + let tx = [0b100_11111, 0b11]; + unwrap!(spim.as_mut().send_receive(&tx, &mut rx).await); + cortex_m::asm::delay(10); + ncs.set_high().unwrap(); + + // read EREVID + cortex_m::asm::delay(10); + ncs.set_low().unwrap(); + cortex_m::asm::delay(5); + let tx = [0b000_10010, 0]; + unwrap!(spim.as_mut().send_receive(&tx, &mut rx).await); + cortex_m::asm::delay(10); + ncs.set_high().unwrap(); + + info!("erevid: {=[?]}", rx); +} + +static RTC: Forever> = Forever::new(); +static ALARM: Forever> = Forever::new(); +static EXECUTOR: Forever = Forever::new(); + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + let p = unwrap!(embassy_nrf::pac::Peripherals::take()); + + clocks::Clocks::new(p.CLOCK) + .enable_ext_hfosc() + .set_lfclk_src_external(clocks::LfOscConfiguration::NoExternalNoBypass) + .start_lfclk(); + + let rtc = RTC.put(rtc::RTC::new(p.RTC1, interrupt::take!(RTC1))); + rtc.start(); + + unsafe { embassy::time::set_clock(rtc) }; + + let alarm = ALARM.put(rtc.alarm0()); + let executor = EXECUTOR.put(Executor::new()); + executor.set_alarm(alarm); + + executor.run(|spawner| { + unwrap!(spawner.spawn(run())); + }); +} diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs index 59bb0e0cf..3de6299e9 100644 --- a/embassy-nrf/src/lib.rs +++ b/embassy-nrf/src/lib.rs @@ -49,6 +49,45 @@ pub use nrf52833_hal as hal; #[cfg(feature = "52840")] pub use nrf52840_hal as hal; +/// Length of Nordic EasyDMA differs for MCUs +#[cfg(any( + feature = "52810", + feature = "52811", + feature = "52832", + feature = "51" +))] +pub mod target_constants { + // NRF52832 8 bits1..0xFF + pub const EASY_DMA_SIZE: usize = 255; + // Easy DMA can only read from data ram + pub const SRAM_LOWER: usize = 0x2000_0000; + pub const SRAM_UPPER: usize = 0x3000_0000; +} +#[cfg(any(feature = "52840", feature = "52833", feature = "9160"))] +pub mod target_constants { + // NRF52840 and NRF9160 16 bits 1..0xFFFF + pub const EASY_DMA_SIZE: usize = 65535; + // Limits for Easy DMA - it can only read from data ram + pub const SRAM_LOWER: usize = 0x2000_0000; + pub const SRAM_UPPER: usize = 0x3000_0000; +} + +/// Does this slice reside entirely within RAM? +pub(crate) fn slice_in_ram(slice: &[u8]) -> bool { + let ptr = slice.as_ptr() as usize; + ptr >= target_constants::SRAM_LOWER && (ptr + slice.len()) < target_constants::SRAM_UPPER +} + +/// Return an error if slice is not in RAM. +#[cfg(not(feature = "51"))] +pub(crate) fn slice_in_ram_or(slice: &[u8], err: T) -> Result<(), T> { + if slice.len() == 0 || slice_in_ram(slice) { + Ok(()) + } else { + Err(err) + } +} + // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; pub(crate) mod util; @@ -59,4 +98,5 @@ pub mod interrupt; #[cfg(feature = "52840")] pub mod qspi; pub mod rtc; +pub mod spim; pub mod uarte; diff --git a/embassy-nrf/src/spim.rs b/embassy-nrf/src/spim.rs new file mode 100644 index 000000000..ad37f0714 --- /dev/null +++ b/embassy-nrf/src/spim.rs @@ -0,0 +1,262 @@ +use core::future::Future; +use core::pin::Pin; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; +use embassy::util::WakerRegistration; +use futures::future::poll_fn; + +use crate::fmt::*; +use crate::hal::gpio::Port as GpioPort; +use crate::interrupt::{self, Interrupt}; +use crate::util::peripheral::{PeripheralMutex, PeripheralState}; +use crate::{pac, slice_in_ram_or}; + +pub use crate::hal::spim::{ + Frequency, Mode, Phase, Pins, Polarity, MODE_0, MODE_1, MODE_2, MODE_3, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + TxBufferTooLong, + RxBufferTooLong, + /// EasyDMA can only read from data memory, read only buffers in flash will fail. + DMABufferNotInDataMemory, +} + +struct State { + spim: T, + waker: WakerRegistration, +} + +pub struct Spim { + inner: PeripheralMutex>, +} + +#[cfg(any(feature = "52833", feature = "52840"))] +fn port_bit(port: GpioPort) -> bool { + match port { + GpioPort::Port0 => false, + GpioPort::Port1 => true, + } +} + +pub struct Config { + pub pins: Pins, + pub frequency: Frequency, + pub mode: Mode, + pub orc: u8, +} + +impl Spim { + pub fn new(mut spim: T, irq: T::Interrupt, config: Config) -> Self { + let r = spim.regs(); + + // Select pins. + r.psel.sck.write(|w| { + let w = unsafe { w.pin().bits(config.pins.sck.pin()) }; + #[cfg(any(feature = "52833", feature = "52840"))] + let w = w.port().bit(port_bit(config.pins.sck.port())); + w.connect().connected() + }); + + match config.pins.mosi { + Some(mosi) => r.psel.mosi.write(|w| { + let w = unsafe { w.pin().bits(mosi.pin()) }; + #[cfg(any(feature = "52833", feature = "52840"))] + let w = w.port().bit(port_bit(mosi.port())); + w.connect().connected() + }), + None => r.psel.mosi.write(|w| w.connect().disconnected()), + } + match config.pins.miso { + Some(miso) => r.psel.miso.write(|w| { + let w = unsafe { w.pin().bits(miso.pin()) }; + #[cfg(any(feature = "52833", feature = "52840"))] + let w = w.port().bit(port_bit(miso.port())); + w.connect().connected() + }), + None => r.psel.miso.write(|w| w.connect().disconnected()), + } + + // Enable SPIM instance. + r.enable.write(|w| w.enable().enabled()); + + // Configure mode. + let mode = config.mode; + r.config.write(|w| { + // Can't match on `mode` due to embedded-hal, see https://github.com/rust-embedded/embedded-hal/pull/126 + if mode == MODE_0 { + w.order().msb_first(); + w.cpol().active_high(); + w.cpha().leading(); + } else if mode == MODE_1 { + w.order().msb_first(); + w.cpol().active_high(); + w.cpha().trailing(); + } else if mode == MODE_2 { + w.order().msb_first(); + w.cpol().active_low(); + w.cpha().leading(); + } else { + w.order().msb_first(); + w.cpol().active_low(); + w.cpha().trailing(); + } + w + }); + + // Configure frequency. + let frequency = config.frequency; + r.frequency.write(|w| w.frequency().variant(frequency)); + + // Set over-read character + let orc = config.orc; + r.orc.write(|w| + // The ORC field is 8 bits long, so any u8 is a valid value to write. + unsafe { w.orc().bits(orc) }); + + // Disable all events interrupts + r.intenclr.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); + + Self { + inner: PeripheralMutex::new( + State { + spim, + waker: WakerRegistration::new(), + }, + irq, + ), + } + } + + fn inner(self: Pin<&mut Self>) -> Pin<&mut PeripheralMutex>> { + unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().inner) } + } + + pub fn free(self: Pin<&mut Self>) -> (T, T::Interrupt) { + let (state, irq) = self.inner().free(); + (state.spim, irq) + } + + pub fn send_receive<'a>( + mut self: Pin<&'a mut Self>, + tx: &'a [u8], + rx: &'a mut [u8], + ) -> impl Future> + 'a { + async move { + slice_in_ram_or(tx, Error::DMABufferNotInDataMemory)?; + slice_in_ram_or(rx, Error::DMABufferNotInDataMemory)?; + + self.as_mut().inner().with(|s, _irq| { + // Conservative compiler fence to prevent optimizations that do not + // take in to account actions by DMA. The fence has been placed here, + // before any DMA action has started. + compiler_fence(Ordering::SeqCst); + + let r = s.spim.regs(); + + // Set up the DMA write. + r.txd + .ptr + .write(|w| unsafe { w.ptr().bits(tx.as_ptr() as u32) }); + r.txd + .maxcnt + .write(|w| unsafe { w.maxcnt().bits(tx.len() as _) }); + + // Set up the DMA read. + r.rxd + .ptr + .write(|w| unsafe { w.ptr().bits(rx.as_mut_ptr() as u32) }); + r.rxd + .maxcnt + .write(|w| unsafe { w.maxcnt().bits(rx.len() as _) }); + + // Reset and enable the event + r.events_end.reset(); + r.intenset.write(|w| w.end().set()); + + // Start SPI transaction. + r.tasks_start.write(|w| unsafe { w.bits(1) }); + + // Conservative compiler fence to prevent optimizations that do not + // take in to account actions by DMA. The fence has been placed here, + // after all possible DMA actions have completed. + compiler_fence(Ordering::SeqCst); + }); + + // Wait for 'end' event. + poll_fn(|cx| { + self.as_mut().inner().with(|s, _irq| { + let r = s.spim.regs(); + if r.events_end.read().bits() != 0 { + return Poll::Ready(()); + } + s.waker.register(cx.waker()); + Poll::Pending + }) + }) + .await; + + Ok(()) + } + } +} + +impl PeripheralState for State { + type Interrupt = U::Interrupt; + fn on_interrupt(&mut self) { + if self.spim.regs().events_end.read().bits() != 0 { + self.spim.regs().intenclr.write(|w| w.end().clear()); + self.waker.wake() + } + } +} + +mod sealed { + pub trait Instance {} + + impl Instance for crate::pac::SPIM0 {} + impl Instance for crate::pac::SPIM1 {} + impl Instance for crate::pac::SPIM2 {} + impl Instance for crate::pac::SPIM3 {} + impl Instance for &mut T {} +} + +pub trait Instance: sealed::Instance { + type Interrupt: Interrupt; + fn regs(&mut self) -> &pac::spim0::RegisterBlock; +} + +impl Instance for pac::SPIM0 { + type Interrupt = interrupt::SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0; + fn regs(&mut self) -> &pac::spim0::RegisterBlock { + self + } +} +impl Instance for pac::SPIM1 { + type Interrupt = interrupt::SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1; + fn regs(&mut self) -> &pac::spim0::RegisterBlock { + self + } +} +impl Instance for pac::SPIM2 { + type Interrupt = interrupt::SPIM2_SPIS2_SPI2; + fn regs(&mut self) -> &pac::spim0::RegisterBlock { + self + } +} +impl Instance for pac::SPIM3 { + type Interrupt = interrupt::SPIM3; + fn regs(&mut self) -> &pac::spim0::RegisterBlock { + self + } +} + +impl Instance for &mut T { + type Interrupt = T::Interrupt; + fn regs(&mut self) -> &pac::spim0::RegisterBlock { + T::regs(*self) + } +}