diff --git a/embassy-nrf-examples/src/bin/qspi.rs b/embassy-nrf-examples/src/bin/qspi.rs index 14f21518..1f33192e 100644 --- a/embassy-nrf-examples/src/bin/qspi.rs +++ b/embassy-nrf-examples/src/bin/qspi.rs @@ -33,7 +33,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { let config = qspi::Config::default(); let irq = interrupt::take!(QSPI); - let mut q = qspi::Qspi::new(p.QSPI, irq, sck, csn, io0, io1, io2, io3, config); + let mut q = qspi::Qspi::new(p.QSPI, irq, sck, csn, io0, io1, io2, io3, config).await; let mut id = [1; 3]; q.custom_instruction(0x9F, &[], &mut id).await.unwrap(); diff --git a/embassy-nrf-examples/src/bin/qspi_lowpower.rs b/embassy-nrf-examples/src/bin/qspi_lowpower.rs new file mode 100644 index 00000000..9bbc87ca --- /dev/null +++ b/embassy-nrf-examples/src/bin/qspi_lowpower.rs @@ -0,0 +1,81 @@ +#![no_std] +#![no_main] +#![feature(min_type_alias_impl_trait)] +#![feature(impl_trait_in_bindings)] +#![feature(type_alias_impl_trait)] +#![allow(incomplete_features)] + +#[path = "../example_common.rs"] +mod example_common; + +use core::mem; +use defmt::panic; +use embassy::executor::Spawner; +use embassy::time::{Duration, Timer}; +use embassy::traits::flash::Flash; +use embassy_nrf::Peripherals; +use embassy_nrf::{interrupt, qspi}; +use example_common::*; + +// Workaround for alignment requirements. +// Nicer API will probably come in the future. +#[repr(C, align(4))] +struct AlignedBuf([u8; 64]); + +#[embassy::main] +async fn main(_spawner: Spawner, mut p: Peripherals) { + let mut irq = interrupt::take!(QSPI); + + loop { + let mut config = qspi::Config::default(); + config.deep_power_down = Some(qspi::DeepPowerDownConfig { + enter_time: 3, // tDP = 30uS + exit_time: 3, // tRDP = 35uS + }); + + let mut q = qspi::Qspi::new( + &mut p.QSPI, + &mut irq, + &mut p.P0_19, + &mut p.P0_17, + &mut p.P0_20, + &mut p.P0_21, + &mut p.P0_22, + &mut p.P0_23, + config, + ) + .await; + + let mut id = [1; 3]; + q.custom_instruction(0x9F, &[], &mut id).await.unwrap(); + info!("id: {}", id); + + // Read status register + let mut status = [4; 1]; + q.custom_instruction(0x05, &[], &mut status).await.unwrap(); + + info!("status: {:?}", status[0]); + + if status[0] & 0x40 == 0 { + status[0] |= 0x40; + + q.custom_instruction(0x01, &status, &mut []).await.unwrap(); + + info!("enabled quad in status"); + } + + let mut buf = AlignedBuf([0u8; 64]); + + info!("reading..."); + q.read(0, &mut buf.0).await.unwrap(); + info!("read: {=[u8]:x}", buf.0); + + // Drop the QSPI instance. This disables the peripehral and deconfigures the pins. + // This clears the borrow on the singletons, so they can now be used again. + mem::drop(q); + + // Sleep for 1 second. The executor ensures the core sleeps with a WFE when it has nothing to do. + // During this sleep, the nRF chip should only use ~3uA + Timer::after(Duration::from_secs(1)).await; + } +} diff --git a/embassy-nrf-examples/src/bin/twim.rs b/embassy-nrf-examples/src/bin/twim.rs new file mode 100644 index 00000000..537cea16 --- /dev/null +++ b/embassy-nrf-examples/src/bin/twim.rs @@ -0,0 +1,35 @@ +//! Example on how to read a 24C/24LC i2c eeprom. +//! +//! Connect SDA to P0.03, SCL to P0.04 + +#![no_std] +#![no_main] +#![feature(min_type_alias_impl_trait)] +#![feature(impl_trait_in_bindings)] +#![feature(type_alias_impl_trait)] +#![allow(incomplete_features)] + +#[path = "../example_common.rs"] +mod example_common; + +use defmt::{panic, *}; +use embassy::executor::Spawner; +use embassy_nrf::twim::{self, Twim}; +use embassy_nrf::{interrupt, Peripherals}; + +const ADDRESS: u8 = 0x50; + +#[embassy::main] +async fn main(_spawner: Spawner, p: Peripherals) { + info!("Initializing TWI..."); + let config = twim::Config::default(); + let irq = interrupt::take!(SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); + let mut twi = Twim::new(p.TWISPI0, irq, p.P0_03, p.P0_04, config); + + info!("Reading..."); + + let mut buf = [0u8; 16]; + twi.write_then_read(ADDRESS, &mut [0x00], &mut buf).unwrap(); + + info!("Read: {=[u8]:x}", buf); +} diff --git a/embassy-nrf-examples/src/bin/twim_lowpower.rs b/embassy-nrf-examples/src/bin/twim_lowpower.rs new file mode 100644 index 00000000..1cd66a18 --- /dev/null +++ b/embassy-nrf-examples/src/bin/twim_lowpower.rs @@ -0,0 +1,54 @@ +//! Example on how to read a 24C/24LC i2c eeprom with low power consumption. +//! The eeprom is read every 1 second, while ensuring lowest possible power while +//! sleeping between reads. +//! +//! Connect SDA to P0.03, SCL to P0.04 + +#![no_std] +#![no_main] +#![feature(min_type_alias_impl_trait)] +#![feature(impl_trait_in_bindings)] +#![feature(type_alias_impl_trait)] +#![allow(incomplete_features)] + +#[path = "../example_common.rs"] +mod example_common; + +use core::mem; + +use defmt::{panic, *}; +use embassy::executor::Spawner; +use embassy::time::{Duration, Timer}; +use embassy_nrf::twim::{self, Twim}; +use embassy_nrf::{interrupt, Peripherals}; + +const ADDRESS: u8 = 0x50; + +#[embassy::main] +async fn main(_spawner: Spawner, mut p: Peripherals) { + info!("Started!"); + let mut irq = interrupt::take!(SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); + + loop { + info!("Initializing TWI..."); + let config = twim::Config::default(); + + // Create the TWIM instance with borrowed singletons, so they're not consumed. + let mut twi = Twim::new(&mut p.TWISPI0, &mut irq, &mut p.P0_03, &mut p.P0_04, config); + + info!("Reading..."); + + let mut buf = [0u8; 16]; + twi.write_then_read(ADDRESS, &mut [0x00], &mut buf).unwrap(); + + info!("Read: {=[u8]:x}", buf); + + // Drop the TWIM instance. This disables the peripehral and deconfigures the pins. + // This clears the borrow on the singletons, so they can now be used again. + mem::drop(twi); + + // Sleep for 1 second. The executor ensures the core sleeps with a WFE when it has nothing to do. + // During this sleep, the nRF chip should only use ~3uA + Timer::after(Duration::from_secs(1)).await; + } +} diff --git a/embassy-nrf/src/gpio.rs b/embassy-nrf/src/gpio.rs index 3ae160ca..b02e7787 100644 --- a/embassy-nrf/src/gpio.rs +++ b/embassy-nrf/src/gpio.rs @@ -12,6 +12,8 @@ use gpio::pin_cnf::DRIVE_A; use crate::pac; use crate::pac::p0 as gpio; +use self::sealed::Pin as _; + /// A GPIO port with up to 32 pins. #[derive(Debug, Eq, PartialEq)] pub enum Port { @@ -487,6 +489,17 @@ impl OptionalPin for NoPin { // ==================== +pub(crate) fn deconfigure_pin(psel_bits: u32) { + if psel_bits & 0x8000_0000 != 0 { + return; + } + unsafe { + AnyPin::steal(psel_bits as _).conf().reset(); + } +} + +// ==================== + macro_rules! impl_pin { ($type:ident, $port_num:expr, $pin_num:expr) => { impl crate::gpio::Pin for peripherals::$type {} diff --git a/embassy-nrf/src/qspi.rs b/embassy-nrf/src/qspi.rs index e5d21cea..6cabe116 100644 --- a/embassy-nrf/src/qspi.rs +++ b/embassy-nrf/src/qspi.rs @@ -2,6 +2,7 @@ use core::future::Future; use core::marker::PhantomData; +use core::ptr; use core::task::Poll; use embassy::interrupt::{Interrupt, InterruptExt}; use embassy::traits::flash::{Error, Flash}; @@ -10,7 +11,8 @@ use embassy_extras::unborrow; use futures::future::poll_fn; use crate::fmt::{assert, assert_eq, *}; -use crate::gpio::Pin as GpioPin; +use crate::gpio::sealed::Pin as _; +use crate::gpio::{self, Pin as GpioPin}; use crate::pac; pub use crate::pac::qspi::ifconfig0::ADDRMODE_A as AddressMode; @@ -29,7 +31,9 @@ pub use crate::pac::qspi::ifconfig0::WRITEOC_A as WriteOpcode; // - set gpio in high drive pub struct DeepPowerDownConfig { + /// Time required for entering DPM, in units of 16us pub enter_time: u16, + /// Time required for exiting DPM, in units of 16us pub exit_time: u16, } @@ -55,11 +59,12 @@ impl Default for Config { } pub struct Qspi<'d, T: Instance> { + dpm_enabled: bool, phantom: PhantomData<&'d mut T>, } impl<'d, T: Instance> Qspi<'d, T> { - pub fn new( + pub async fn new( _qspi: impl Unborrow + 'd, irq: impl Unborrow + 'd, sck: impl Unborrow + 'd, @@ -69,20 +74,21 @@ impl<'d, T: Instance> Qspi<'d, T> { io2: impl Unborrow + 'd, io3: impl Unborrow + 'd, config: Config, - ) -> Self { + ) -> Qspi<'d, T> { unborrow!(irq, sck, csn, io0, io1, io2, io3); let r = T::regs(); - for cnf in &[ - sck.conf(), - csn.conf(), - io0.conf(), - io1.conf(), - io2.conf(), - io3.conf(), - ] { - cnf.write(|w| w.dir().output().drive().h0h1()); + let sck = sck.degrade(); + let csn = csn.degrade(); + let io0 = io0.degrade(); + let io1 = io1.degrade(); + let io2 = io2.degrade(); + let io3 = io3.degrade(); + + for pin in [&sck, &csn, &io0, &io1, &io2, &io3] { + pin.set_high(); + pin.conf().write(|w| w.dir().output().drive().h0h1()); } r.psel.sck.write(|w| unsafe { w.bits(sck.psel_bits()) }); @@ -92,53 +98,56 @@ impl<'d, T: Instance> Qspi<'d, T> { r.psel.io2.write(|w| unsafe { w.bits(io2.psel_bits()) }); r.psel.io3.write(|w| unsafe { w.bits(io3.psel_bits()) }); - r.ifconfig0.write(|mut w| { - w = w.addrmode().variant(AddressMode::_24BIT); - if config.deep_power_down.is_some() { - w = w.dpmenable().enable(); - } else { - w = w.dpmenable().disable(); - } - w = w.ppsize().variant(config.write_page_size); - w = w.readoc().variant(config.read_opcode); - w = w.writeoc().variant(config.write_opcode); + r.ifconfig0.write(|w| { + w.addrmode().variant(AddressMode::_24BIT); + w.dpmenable().bit(config.deep_power_down.is_some()); + w.ppsize().variant(config.write_page_size); + w.readoc().variant(config.read_opcode); + w.writeoc().variant(config.write_opcode); w }); if let Some(dpd) = &config.deep_power_down { - r.dpmdur.write(|mut w| unsafe { - w = w.enter().bits(dpd.enter_time); - w = w.exit().bits(dpd.exit_time); + r.dpmdur.write(|w| unsafe { + w.enter().bits(dpd.enter_time); + w.exit().bits(dpd.exit_time); w }) } - r.ifconfig1.write(|w| { - let w = unsafe { w.sckdelay().bits(80) }; - let w = w.dpmen().exit(); - let w = w.spimode().mode0(); - let w = unsafe { w.sckfreq().bits(3) }; + r.ifconfig1.write(|w| unsafe { + w.sckdelay().bits(80); + w.dpmen().exit(); + w.spimode().mode0(); + w.sckfreq().bits(3); w }); - r.xipoffset - .write(|w| unsafe { w.xipoffset().bits(config.xip_offset) }); - - // Enable it - r.enable.write(|w| w.enable().enabled()); - - r.events_ready.reset(); - r.tasks_activate.write(|w| w.tasks_activate().bit(true)); - while r.events_ready.read().bits() == 0 {} - r.events_ready.reset(); + r.xipoffset.write(|w| unsafe { + w.xipoffset().bits(config.xip_offset); + w + }); irq.set_handler(Self::on_interrupt); irq.unpend(); irq.enable(); - Self { + // Enable it + r.enable.write(|w| w.enable().enabled()); + + let mut res = Self { + dpm_enabled: config.deep_power_down.is_some(), phantom: PhantomData, - } + }; + + r.events_ready.reset(); + r.intenset.write(|w| w.ready().set()); + + r.tasks_activate.write(|w| w.tasks_activate().bit(true)); + + res.wait_ready().await; + + res } fn on_interrupt(_: *mut ()) { @@ -151,19 +160,6 @@ impl<'d, T: Instance> Qspi<'d, T> { } } - pub fn sleep(&mut self) { - let r = T::regs(); - - info!("flash: sleeping"); - info!("flash: state = {:?}", r.status.read().bits()); - r.ifconfig1.modify(|_, w| w.dpmen().enter()); - info!("flash: state = {:?}", r.status.read().bits()); - cortex_m::asm::delay(1000000); - info!("flash: state = {:?}", r.status.read().bits()); - - r.tasks_deactivate.write(|w| w.tasks_deactivate().set_bit()); - } - pub async fn custom_instruction( &mut self, opcode: u8, @@ -246,6 +242,44 @@ impl<'d, T: Instance> Qspi<'d, T> { } } +impl<'d, T: Instance> Drop for Qspi<'d, T> { + fn drop(&mut self) { + let r = T::regs(); + + if self.dpm_enabled { + info!("qspi: doing deep powerdown..."); + + r.ifconfig1.modify(|_, w| w.dpmen().enter()); + + // Wait for DPM enter. + // Unfortunately we must spin. There's no way to do this interrupt-driven. + // The READY event does NOT fire on DPM enter (but it does fire on DPM exit :shrug:) + while r.status.read().dpm().is_disabled() {} + } + + // it seems events_ready is not generated in response to deactivate. nrfx doesn't wait for it. + r.tasks_deactivate.write(|w| w.tasks_deactivate().set_bit()); + + // Workaround https://infocenter.nordicsemi.com/topic/errata_nRF52840_Rev1/ERR/nRF52840/Rev1/latest/anomaly_840_122.html?cp=4_0_1_2_1_7 + // Note that the doc has 2 register writes, but the first one is really the write to tasks_deactivate, + // so we only do the second one here. + unsafe { ptr::write_volatile(0x40029054 as *mut u32, 1) } + + r.enable.write(|w| w.enable().disabled()); + + // Note: we do NOT deconfigure CSN here. If DPM is in use and we disconnect CSN, + // leaving it floating, the flash chip might read it as zero which would cause it to + // spuriously exit DPM. + gpio::deconfigure_pin(r.psel.sck.read().bits()); + gpio::deconfigure_pin(r.psel.io0.read().bits()); + gpio::deconfigure_pin(r.psel.io1.read().bits()); + gpio::deconfigure_pin(r.psel.io2.read().bits()); + gpio::deconfigure_pin(r.psel.io3.read().bits()); + + info!("qspi: dropped"); + } +} + impl<'d, T: Instance> Flash for Qspi<'d, T> { #[rustfmt::skip] type ReadFuture<'a> where Self: 'a = impl Future> + 'a; diff --git a/embassy-nrf/src/spim.rs b/embassy-nrf/src/spim.rs index bb43b7c7..a29c1a3e 100644 --- a/embassy-nrf/src/spim.rs +++ b/embassy-nrf/src/spim.rs @@ -14,6 +14,7 @@ use traits::spi::FullDuplex; use crate::gpio::sealed::Pin as _; use crate::gpio::{OptionalPin, Pin as GpioPin}; use crate::interrupt::Interrupt; +use crate::{fmt::*, gpio}; use crate::{pac, util::slice_in_ram_or}; pub use embedded_hal::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3}; @@ -153,6 +154,24 @@ impl<'d, T: Instance> Spim<'d, T> { } } +impl<'d, T: Instance> Drop for Spim<'d, T> { + fn drop(&mut self) { + info!("spim drop"); + + // TODO check for abort, wait for xxxstopped + + // disable! + let r = T::regs(); + r.enable.write(|w| w.enable().disabled()); + + gpio::deconfigure_pin(r.psel.sck.read().bits()); + gpio::deconfigure_pin(r.psel.miso.read().bits()); + gpio::deconfigure_pin(r.psel.mosi.read().bits()); + + info!("spim drop: done"); + } +} + impl<'d, T: Instance> FullDuplex for Spim<'d, T> { type Error = Error; diff --git a/embassy-nrf/src/twim.rs b/embassy-nrf/src/twim.rs index 05c4c260..ea3ac755 100644 --- a/embassy-nrf/src/twim.rs +++ b/embassy-nrf/src/twim.rs @@ -13,10 +13,10 @@ use embassy::util::{AtomicWaker, Unborrow}; use embassy_extras::unborrow; use crate::chip::{EASY_DMA_SIZE, FORCE_COPY_BUFFER_SIZE}; -use crate::fmt::*; use crate::gpio::Pin as GpioPin; use crate::pac; use crate::util::{slice_in_ram, slice_in_ram_or}; +use crate::{fmt::*, gpio}; pub enum Frequency { #[doc = "26738688: 100 kbps"] @@ -30,12 +30,16 @@ pub enum Frequency { #[non_exhaustive] pub struct Config { pub frequency: Frequency, + pub sda_pullup: bool, + pub scl_pullup: bool, } impl Default for Config { fn default() -> Self { Self { frequency: Frequency::K100, + sda_pullup: false, + scl_pullup: false, } } } @@ -61,15 +65,19 @@ impl<'d, T: Instance> Twim<'d, T> { sda.conf().write(|w| { w.dir().input(); w.input().connect(); - w.pull().pullup(); w.drive().s0d1(); + if config.sda_pullup { + w.pull().pullup(); + } w }); scl.conf().write(|w| { w.dir().input(); w.input().connect(); - w.pull().pullup(); w.drive().s0d1(); + if config.scl_pullup { + w.pull().pullup(); + } w }); @@ -422,9 +430,10 @@ impl<'a, T: Instance> Drop for Twim<'a, T> { let r = T::regs(); r.enable.write(|w| w.enable().disabled()); - info!("uarte drop: done"); + gpio::deconfigure_pin(r.psel.sda.read().bits()); + gpio::deconfigure_pin(r.psel.scl.read().bits()); - // TODO: disable pins + info!("twim drop: done"); } } diff --git a/embassy-nrf/src/uarte.rs b/embassy-nrf/src/uarte.rs index 3ad7a787..2f6d1a39 100644 --- a/embassy-nrf/src/uarte.rs +++ b/embassy-nrf/src/uarte.rs @@ -15,7 +15,7 @@ use futures::future::poll_fn; use crate::chip::EASY_DMA_SIZE; use crate::fmt::{assert, panic, *}; use crate::gpio::sealed::Pin as _; -use crate::gpio::{OptionalPin as GpioOptionalPin, Pin as GpioPin}; +use crate::gpio::{self, OptionalPin as GpioOptionalPin, Pin as GpioPin}; use crate::interrupt::Interrupt; use crate::pac; use crate::ppi::{AnyConfigurableChannel, ConfigurableChannel, Event, Ppi, Task}; @@ -166,9 +166,12 @@ impl<'a, T: Instance> Drop for Uarte<'a, T> { // Finally we can disable! r.enable.write(|w| w.enable().disabled()); - info!("uarte drop: done"); + gpio::deconfigure_pin(r.psel.rxd.read().bits()); + gpio::deconfigure_pin(r.psel.txd.read().bits()); + gpio::deconfigure_pin(r.psel.rts.read().bits()); + gpio::deconfigure_pin(r.psel.cts.read().bits()); - // TODO: disable pins + info!("uarte drop: done"); } }