diff options
| author | Dario Nieuwenhuis <[email protected]> | 2021-03-01 00:43:17 +0100 |
|---|---|---|
| committer | GitHub <[email protected]> | 2021-03-01 00:43:17 +0100 |
| commit | 282f00e7050653e98b4c0680b54fce8abc43a5a3 (patch) | |
| tree | ef7e317bcc4771acbe4f85102901edd29f66d19c | |
| parent | 8a641d1312f7b63beb7ce245ac815e73c4869347 (diff) | |
| parent | a30c705fd79e8f05177eaa690beac5e8c53296c1 (diff) | |
Merge pull request #27 from akiles/spim
nrf: add spim
| -rw-r--r-- | embassy-nrf-examples/src/bin/spim.rs | 116 | ||||
| -rw-r--r-- | embassy-nrf/src/lib.rs | 40 | ||||
| -rw-r--r-- | embassy-nrf/src/spim.rs | 272 |
3 files changed, 428 insertions, 0 deletions
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 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![feature(type_alias_impl_trait)] | ||
| 4 | |||
| 5 | #[path = "../example_common.rs"] | ||
| 6 | mod example_common; | ||
| 7 | use example_common::*; | ||
| 8 | |||
| 9 | use cortex_m_rt::entry; | ||
| 10 | use defmt::panic; | ||
| 11 | use embassy::executor::{task, Executor}; | ||
| 12 | use embassy::util::Forever; | ||
| 13 | use embedded_hal::digital::v2::*; | ||
| 14 | use futures::pin_mut; | ||
| 15 | use nrf52840_hal::clocks; | ||
| 16 | use nrf52840_hal::gpio; | ||
| 17 | |||
| 18 | use embassy_nrf::{interrupt, pac, rtc, spim}; | ||
| 19 | |||
| 20 | #[task] | ||
| 21 | async fn run() { | ||
| 22 | info!("running!"); | ||
| 23 | |||
| 24 | let p = unsafe { embassy_nrf::pac::Peripherals::steal() }; | ||
| 25 | let p0 = gpio::p0::Parts::new(p.P0); | ||
| 26 | |||
| 27 | let pins = spim::Pins { | ||
| 28 | sck: p0.p0_29.into_push_pull_output(gpio::Level::Low).degrade(), | ||
| 29 | miso: Some(p0.p0_28.into_floating_input().degrade()), | ||
| 30 | mosi: Some(p0.p0_30.into_push_pull_output(gpio::Level::Low).degrade()), | ||
| 31 | }; | ||
| 32 | let config = spim::Config { | ||
| 33 | pins, | ||
| 34 | frequency: spim::Frequency::M16, | ||
| 35 | mode: spim::MODE_0, | ||
| 36 | orc: 0x00, | ||
| 37 | }; | ||
| 38 | |||
| 39 | let mut ncs = p0.p0_31.into_push_pull_output(gpio::Level::High); | ||
| 40 | let spim = spim::Spim::new(p.SPIM3, interrupt::take!(SPIM3), config); | ||
| 41 | pin_mut!(spim); | ||
| 42 | |||
| 43 | // Example on how to talk to an ENC28J60 chip | ||
| 44 | |||
| 45 | // softreset | ||
| 46 | cortex_m::asm::delay(10); | ||
| 47 | ncs.set_low().unwrap(); | ||
| 48 | cortex_m::asm::delay(5); | ||
| 49 | let tx = [0xFF]; | ||
| 50 | unwrap!(spim.as_mut().send_receive(&tx, &mut []).await); | ||
| 51 | cortex_m::asm::delay(10); | ||
| 52 | ncs.set_high().unwrap(); | ||
| 53 | |||
| 54 | cortex_m::asm::delay(100000); | ||
| 55 | |||
| 56 | let mut rx = [0; 2]; | ||
| 57 | |||
| 58 | // read ESTAT | ||
| 59 | cortex_m::asm::delay(5000); | ||
| 60 | ncs.set_low().unwrap(); | ||
| 61 | cortex_m::asm::delay(5000); | ||
| 62 | let tx = [0b000_11101, 0]; | ||
| 63 | unwrap!(spim.as_mut().send_receive(&tx, &mut rx).await); | ||
| 64 | cortex_m::asm::delay(5000); | ||
| 65 | ncs.set_high().unwrap(); | ||
| 66 | info!("estat: {=[?]}", rx); | ||
| 67 | |||
| 68 | // Switch to bank 3 | ||
| 69 | cortex_m::asm::delay(10); | ||
| 70 | ncs.set_low().unwrap(); | ||
| 71 | cortex_m::asm::delay(5); | ||
| 72 | let tx = [0b100_11111, 0b11]; | ||
| 73 | unwrap!(spim.as_mut().send_receive(&tx, &mut rx).await); | ||
| 74 | cortex_m::asm::delay(10); | ||
| 75 | ncs.set_high().unwrap(); | ||
| 76 | |||
| 77 | // read EREVID | ||
| 78 | cortex_m::asm::delay(10); | ||
| 79 | ncs.set_low().unwrap(); | ||
| 80 | cortex_m::asm::delay(5); | ||
| 81 | let tx = [0b000_10010, 0]; | ||
| 82 | unwrap!(spim.as_mut().send_receive(&tx, &mut rx).await); | ||
| 83 | cortex_m::asm::delay(10); | ||
| 84 | ncs.set_high().unwrap(); | ||
| 85 | |||
| 86 | info!("erevid: {=[?]}", rx); | ||
| 87 | } | ||
| 88 | |||
| 89 | static RTC: Forever<rtc::RTC<pac::RTC1>> = Forever::new(); | ||
| 90 | static ALARM: Forever<rtc::Alarm<pac::RTC1>> = Forever::new(); | ||
| 91 | static EXECUTOR: Forever<Executor> = Forever::new(); | ||
| 92 | |||
| 93 | #[entry] | ||
| 94 | fn main() -> ! { | ||
| 95 | info!("Hello World!"); | ||
| 96 | |||
| 97 | let p = unwrap!(embassy_nrf::pac::Peripherals::take()); | ||
| 98 | |||
| 99 | clocks::Clocks::new(p.CLOCK) | ||
| 100 | .enable_ext_hfosc() | ||
| 101 | .set_lfclk_src_external(clocks::LfOscConfiguration::NoExternalNoBypass) | ||
| 102 | .start_lfclk(); | ||
| 103 | |||
| 104 | let rtc = RTC.put(rtc::RTC::new(p.RTC1, interrupt::take!(RTC1))); | ||
| 105 | rtc.start(); | ||
| 106 | |||
| 107 | unsafe { embassy::time::set_clock(rtc) }; | ||
| 108 | |||
| 109 | let alarm = ALARM.put(rtc.alarm0()); | ||
| 110 | let executor = EXECUTOR.put(Executor::new()); | ||
| 111 | executor.set_alarm(alarm); | ||
| 112 | |||
| 113 | executor.run(|spawner| { | ||
| 114 | unwrap!(spawner.spawn(run())); | ||
| 115 | }); | ||
| 116 | } | ||
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; | |||
| 49 | #[cfg(feature = "52840")] | 49 | #[cfg(feature = "52840")] |
| 50 | pub use nrf52840_hal as hal; | 50 | pub use nrf52840_hal as hal; |
| 51 | 51 | ||
| 52 | /// Length of Nordic EasyDMA differs for MCUs | ||
| 53 | #[cfg(any( | ||
| 54 | feature = "52810", | ||
| 55 | feature = "52811", | ||
| 56 | feature = "52832", | ||
| 57 | feature = "51" | ||
| 58 | ))] | ||
| 59 | pub mod target_constants { | ||
| 60 | // NRF52832 8 bits1..0xFF | ||
| 61 | pub const EASY_DMA_SIZE: usize = 255; | ||
| 62 | // Easy DMA can only read from data ram | ||
| 63 | pub const SRAM_LOWER: usize = 0x2000_0000; | ||
| 64 | pub const SRAM_UPPER: usize = 0x3000_0000; | ||
| 65 | } | ||
| 66 | #[cfg(any(feature = "52840", feature = "52833", feature = "9160"))] | ||
| 67 | pub mod target_constants { | ||
| 68 | // NRF52840 and NRF9160 16 bits 1..0xFFFF | ||
| 69 | pub const EASY_DMA_SIZE: usize = 65535; | ||
| 70 | // Limits for Easy DMA - it can only read from data ram | ||
| 71 | pub const SRAM_LOWER: usize = 0x2000_0000; | ||
| 72 | pub const SRAM_UPPER: usize = 0x3000_0000; | ||
| 73 | } | ||
| 74 | |||
| 75 | /// Does this slice reside entirely within RAM? | ||
| 76 | pub(crate) fn slice_in_ram(slice: &[u8]) -> bool { | ||
| 77 | let ptr = slice.as_ptr() as usize; | ||
| 78 | ptr >= target_constants::SRAM_LOWER && (ptr + slice.len()) < target_constants::SRAM_UPPER | ||
| 79 | } | ||
| 80 | |||
| 81 | /// Return an error if slice is not in RAM. | ||
| 82 | #[cfg(not(feature = "51"))] | ||
| 83 | pub(crate) fn slice_in_ram_or<T>(slice: &[u8], err: T) -> Result<(), T> { | ||
| 84 | if slice.len() == 0 || slice_in_ram(slice) { | ||
| 85 | Ok(()) | ||
| 86 | } else { | ||
| 87 | Err(err) | ||
| 88 | } | ||
| 89 | } | ||
| 90 | |||
| 52 | // This mod MUST go first, so that the others see its macros. | 91 | // This mod MUST go first, so that the others see its macros. |
| 53 | pub(crate) mod fmt; | 92 | pub(crate) mod fmt; |
| 54 | pub(crate) mod util; | 93 | pub(crate) mod util; |
| @@ -59,4 +98,5 @@ pub mod interrupt; | |||
| 59 | #[cfg(feature = "52840")] | 98 | #[cfg(feature = "52840")] |
| 60 | pub mod qspi; | 99 | pub mod qspi; |
| 61 | pub mod rtc; | 100 | pub mod rtc; |
| 101 | pub mod spim; | ||
| 62 | pub mod uarte; | 102 | pub mod uarte; |
diff --git a/embassy-nrf/src/spim.rs b/embassy-nrf/src/spim.rs new file mode 100644 index 000000000..3c0f6e047 --- /dev/null +++ b/embassy-nrf/src/spim.rs | |||
| @@ -0,0 +1,272 @@ | |||
| 1 | use core::future::Future; | ||
| 2 | use core::pin::Pin; | ||
| 3 | use core::sync::atomic::{compiler_fence, Ordering}; | ||
| 4 | use core::task::Poll; | ||
| 5 | use embassy::util::WakerRegistration; | ||
| 6 | use futures::future::poll_fn; | ||
| 7 | |||
| 8 | use crate::hal::gpio::Port as GpioPort; | ||
| 9 | use crate::interrupt::{self, Interrupt}; | ||
| 10 | use crate::util::peripheral::{PeripheralMutex, PeripheralState}; | ||
| 11 | use crate::{pac, slice_in_ram_or}; | ||
| 12 | |||
| 13 | pub use crate::hal::spim::{ | ||
| 14 | Frequency, Mode, Phase, Pins, Polarity, MODE_0, MODE_1, MODE_2, MODE_3, | ||
| 15 | }; | ||
| 16 | |||
| 17 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| 18 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 19 | #[non_exhaustive] | ||
| 20 | pub enum Error { | ||
| 21 | TxBufferTooLong, | ||
| 22 | RxBufferTooLong, | ||
| 23 | /// EasyDMA can only read from data memory, read only buffers in flash will fail. | ||
| 24 | DMABufferNotInDataMemory, | ||
| 25 | } | ||
| 26 | |||
| 27 | struct State<T: Instance> { | ||
| 28 | spim: T, | ||
| 29 | waker: WakerRegistration, | ||
| 30 | } | ||
| 31 | |||
| 32 | pub struct Spim<T: Instance> { | ||
| 33 | inner: PeripheralMutex<State<T>>, | ||
| 34 | } | ||
| 35 | |||
| 36 | #[cfg(any(feature = "52833", feature = "52840"))] | ||
| 37 | fn port_bit(port: GpioPort) -> bool { | ||
| 38 | match port { | ||
| 39 | GpioPort::Port0 => false, | ||
| 40 | GpioPort::Port1 => true, | ||
| 41 | } | ||
| 42 | } | ||
| 43 | |||
| 44 | pub struct Config { | ||
| 45 | pub pins: Pins, | ||
| 46 | pub frequency: Frequency, | ||
| 47 | pub mode: Mode, | ||
| 48 | pub orc: u8, | ||
| 49 | } | ||
| 50 | |||
| 51 | impl<T: Instance> Spim<T> { | ||
| 52 | pub fn new(mut spim: T, irq: T::Interrupt, config: Config) -> Self { | ||
| 53 | let r = spim.regs(); | ||
| 54 | |||
| 55 | // Select pins. | ||
| 56 | r.psel.sck.write(|w| { | ||
| 57 | let w = unsafe { w.pin().bits(config.pins.sck.pin()) }; | ||
| 58 | #[cfg(any(feature = "52833", feature = "52840"))] | ||
| 59 | let w = w.port().bit(port_bit(config.pins.sck.port())); | ||
| 60 | w.connect().connected() | ||
| 61 | }); | ||
| 62 | |||
| 63 | match config.pins.mosi { | ||
| 64 | Some(mosi) => r.psel.mosi.write(|w| { | ||
| 65 | let w = unsafe { w.pin().bits(mosi.pin()) }; | ||
| 66 | #[cfg(any(feature = "52833", feature = "52840"))] | ||
| 67 | let w = w.port().bit(port_bit(mosi.port())); | ||
| 68 | w.connect().connected() | ||
| 69 | }), | ||
| 70 | None => r.psel.mosi.write(|w| w.connect().disconnected()), | ||
| 71 | } | ||
| 72 | match config.pins.miso { | ||
| 73 | Some(miso) => r.psel.miso.write(|w| { | ||
| 74 | let w = unsafe { w.pin().bits(miso.pin()) }; | ||
| 75 | #[cfg(any(feature = "52833", feature = "52840"))] | ||
| 76 | let w = w.port().bit(port_bit(miso.port())); | ||
| 77 | w.connect().connected() | ||
| 78 | }), | ||
| 79 | None => r.psel.miso.write(|w| w.connect().disconnected()), | ||
| 80 | } | ||
| 81 | |||
| 82 | // Enable SPIM instance. | ||
| 83 | r.enable.write(|w| w.enable().enabled()); | ||
| 84 | |||
| 85 | // Configure mode. | ||
| 86 | let mode = config.mode; | ||
| 87 | r.config.write(|w| { | ||
| 88 | // Can't match on `mode` due to embedded-hal, see https://github.com/rust-embedded/embedded-hal/pull/126 | ||
| 89 | if mode == MODE_0 { | ||
| 90 | w.order().msb_first(); | ||
| 91 | w.cpol().active_high(); | ||
| 92 | w.cpha().leading(); | ||
| 93 | } else if mode == MODE_1 { | ||
| 94 | w.order().msb_first(); | ||
| 95 | w.cpol().active_high(); | ||
| 96 | w.cpha().trailing(); | ||
| 97 | } else if mode == MODE_2 { | ||
| 98 | w.order().msb_first(); | ||
| 99 | w.cpol().active_low(); | ||
| 100 | w.cpha().leading(); | ||
| 101 | } else { | ||
| 102 | w.order().msb_first(); | ||
| 103 | w.cpol().active_low(); | ||
| 104 | w.cpha().trailing(); | ||
| 105 | } | ||
| 106 | w | ||
| 107 | }); | ||
| 108 | |||
| 109 | // Configure frequency. | ||
| 110 | let frequency = config.frequency; | ||
| 111 | r.frequency.write(|w| w.frequency().variant(frequency)); | ||
| 112 | |||
| 113 | // Set over-read character | ||
| 114 | let orc = config.orc; | ||
| 115 | r.orc.write(|w| | ||
| 116 | // The ORC field is 8 bits long, so any u8 is a valid value to write. | ||
| 117 | unsafe { w.orc().bits(orc) }); | ||
| 118 | |||
| 119 | // Disable all events interrupts | ||
| 120 | r.intenclr.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); | ||
| 121 | |||
| 122 | Self { | ||
| 123 | inner: PeripheralMutex::new( | ||
| 124 | State { | ||
| 125 | spim, | ||
| 126 | waker: WakerRegistration::new(), | ||
| 127 | }, | ||
| 128 | irq, | ||
| 129 | ), | ||
| 130 | } | ||
| 131 | } | ||
| 132 | |||
| 133 | fn inner(self: Pin<&mut Self>) -> Pin<&mut PeripheralMutex<State<T>>> { | ||
| 134 | unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().inner) } | ||
| 135 | } | ||
| 136 | |||
| 137 | pub fn free(self: Pin<&mut Self>) -> (T, T::Interrupt) { | ||
| 138 | let (state, irq) = self.inner().free(); | ||
| 139 | (state.spim, irq) | ||
| 140 | } | ||
| 141 | |||
| 142 | pub fn send_receive<'a>( | ||
| 143 | mut self: Pin<&'a mut Self>, | ||
| 144 | tx: &'a [u8], | ||
| 145 | rx: &'a mut [u8], | ||
| 146 | ) -> impl Future<Output = Result<(), Error>> + 'a { | ||
| 147 | async move { | ||
| 148 | slice_in_ram_or(tx, Error::DMABufferNotInDataMemory)?; | ||
| 149 | slice_in_ram_or(rx, Error::DMABufferNotInDataMemory)?; | ||
| 150 | |||
| 151 | self.as_mut().inner().with(|s, _irq| { | ||
| 152 | // Conservative compiler fence to prevent optimizations that do not | ||
| 153 | // take in to account actions by DMA. The fence has been placed here, | ||
| 154 | // before any DMA action has started. | ||
| 155 | compiler_fence(Ordering::SeqCst); | ||
| 156 | |||
| 157 | let r = s.spim.regs(); | ||
| 158 | |||
| 159 | // Set up the DMA write. | ||
| 160 | r.txd | ||
| 161 | .ptr | ||
| 162 | .write(|w| unsafe { w.ptr().bits(tx.as_ptr() as u32) }); | ||
| 163 | r.txd | ||
| 164 | .maxcnt | ||
| 165 | .write(|w| unsafe { w.maxcnt().bits(tx.len() as _) }); | ||
| 166 | |||
| 167 | // Set up the DMA read. | ||
| 168 | r.rxd | ||
| 169 | .ptr | ||
| 170 | .write(|w| unsafe { w.ptr().bits(rx.as_mut_ptr() as u32) }); | ||
| 171 | r.rxd | ||
| 172 | .maxcnt | ||
| 173 | .write(|w| unsafe { w.maxcnt().bits(rx.len() as _) }); | ||
| 174 | |||
| 175 | // Reset and enable the event | ||
| 176 | r.events_end.reset(); | ||
| 177 | r.intenset.write(|w| w.end().set()); | ||
| 178 | |||
| 179 | // Start SPI transaction. | ||
| 180 | r.tasks_start.write(|w| unsafe { w.bits(1) }); | ||
| 181 | |||
| 182 | // Conservative compiler fence to prevent optimizations that do not | ||
| 183 | // take in to account actions by DMA. The fence has been placed here, | ||
| 184 | // after all possible DMA actions have completed. | ||
| 185 | compiler_fence(Ordering::SeqCst); | ||
| 186 | }); | ||
| 187 | |||
| 188 | // Wait for 'end' event. | ||
| 189 | poll_fn(|cx| { | ||
| 190 | self.as_mut().inner().with(|s, _irq| { | ||
| 191 | let r = s.spim.regs(); | ||
| 192 | if r.events_end.read().bits() != 0 { | ||
| 193 | return Poll::Ready(()); | ||
| 194 | } | ||
| 195 | s.waker.register(cx.waker()); | ||
| 196 | Poll::Pending | ||
| 197 | }) | ||
| 198 | }) | ||
| 199 | .await; | ||
| 200 | |||
| 201 | Ok(()) | ||
| 202 | } | ||
| 203 | } | ||
| 204 | } | ||
| 205 | |||
| 206 | impl<U: Instance> PeripheralState for State<U> { | ||
| 207 | type Interrupt = U::Interrupt; | ||
| 208 | fn on_interrupt(&mut self) { | ||
| 209 | if self.spim.regs().events_end.read().bits() != 0 { | ||
| 210 | self.spim.regs().intenclr.write(|w| w.end().clear()); | ||
| 211 | self.waker.wake() | ||
| 212 | } | ||
| 213 | } | ||
| 214 | } | ||
| 215 | |||
| 216 | mod sealed { | ||
| 217 | pub trait Instance {} | ||
| 218 | } | ||
| 219 | |||
| 220 | pub trait Instance: sealed::Instance { | ||
| 221 | type Interrupt: Interrupt; | ||
| 222 | fn regs(&mut self) -> &pac::spim0::RegisterBlock; | ||
| 223 | } | ||
| 224 | |||
| 225 | impl sealed::Instance for pac::SPIM0 {} | ||
| 226 | impl Instance for pac::SPIM0 { | ||
| 227 | #[cfg(feature = "52810")] | ||
| 228 | type Interrupt = interrupt::SPIM0_SPIS0_SPI0; | ||
| 229 | #[cfg(not(feature = "52810"))] | ||
| 230 | type Interrupt = interrupt::SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0; | ||
| 231 | fn regs(&mut self) -> &pac::spim0::RegisterBlock { | ||
| 232 | self | ||
| 233 | } | ||
| 234 | } | ||
| 235 | |||
| 236 | #[cfg(any(feature = "52832", feature = "52833", feature = "52840"))] | ||
| 237 | impl sealed::Instance for pac::SPIM1 {} | ||
| 238 | #[cfg(any(feature = "52832", feature = "52833", feature = "52840"))] | ||
| 239 | impl Instance for pac::SPIM1 { | ||
| 240 | type Interrupt = interrupt::SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1; | ||
| 241 | fn regs(&mut self) -> &pac::spim0::RegisterBlock { | ||
| 242 | self | ||
| 243 | } | ||
| 244 | } | ||
| 245 | |||
| 246 | #[cfg(any(feature = "52832", feature = "52833", feature = "52840"))] | ||
| 247 | impl sealed::Instance for pac::SPIM2 {} | ||
| 248 | #[cfg(any(feature = "52832", feature = "52833", feature = "52840"))] | ||
| 249 | impl Instance for pac::SPIM2 { | ||
| 250 | type Interrupt = interrupt::SPIM2_SPIS2_SPI2; | ||
| 251 | fn regs(&mut self) -> &pac::spim0::RegisterBlock { | ||
| 252 | self | ||
| 253 | } | ||
| 254 | } | ||
| 255 | |||
| 256 | #[cfg(any(feature = "52833", feature = "52840"))] | ||
| 257 | impl sealed::Instance for pac::SPIM3 {} | ||
| 258 | #[cfg(any(feature = "52833", feature = "52840"))] | ||
| 259 | impl Instance for pac::SPIM3 { | ||
| 260 | type Interrupt = interrupt::SPIM3; | ||
| 261 | fn regs(&mut self) -> &pac::spim0::RegisterBlock { | ||
| 262 | self | ||
| 263 | } | ||
| 264 | } | ||
| 265 | |||
| 266 | impl<T: sealed::Instance> sealed::Instance for &mut T {} | ||
| 267 | impl<T: Instance> Instance for &mut T { | ||
| 268 | type Interrupt = T::Interrupt; | ||
| 269 | fn regs(&mut self) -> &pac::spim0::RegisterBlock { | ||
| 270 | T::regs(*self) | ||
| 271 | } | ||
| 272 | } | ||
