diff --git a/.vscode/settings.json b/.vscode/settings.json index 700804dc..9ef7fe1c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -16,10 +16,11 @@ // "embassy-executor/Cargo.toml", // "embassy-sync/Cargo.toml", "examples/nrf52840/Cargo.toml", - //"examples/nrf5340/Cargo.toml", + // "examples/nrf5340/Cargo.toml", // "examples/nrf-rtos-trace/Cargo.toml", // "examples/rp/Cargo.toml", // "examples/std/Cargo.toml", + // "examples/stm32c0/Cargo.toml", // "examples/stm32f0/Cargo.toml", // "examples/stm32f1/Cargo.toml", // "examples/stm32f2/Cargo.toml", @@ -28,6 +29,7 @@ // "examples/stm32f7/Cargo.toml", // "examples/stm32g0/Cargo.toml", // "examples/stm32g4/Cargo.toml", + // "examples/stm32h5/Cargo.toml", // "examples/stm32h7/Cargo.toml", // "examples/stm32l0/Cargo.toml", // "examples/stm32l1/Cargo.toml", @@ -35,9 +37,7 @@ // "examples/stm32l5/Cargo.toml", // "examples/stm32u5/Cargo.toml", // "examples/stm32wb/Cargo.toml", - // "examples/stm32wb55/Cargo.toml", // "examples/stm32wl/Cargo.toml", - // "examples/stm32wl55/Cargo.toml", // "examples/wasm/Cargo.toml", ], } \ No newline at end of file diff --git a/ci.sh b/ci.sh index 1b3fac8b..65797504 100755 --- a/ci.sh +++ b/ci.sh @@ -22,6 +22,7 @@ cargo batch \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt \ --- build --release --manifest-path embassy-sync/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt \ --- build --release --manifest-path embassy-time/Cargo.toml --target thumbv6m-none-eabi --features nightly,unstable-traits,defmt,defmt-timestamp-uptime,tick-hz-32_768,generic-queue-8 \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,medium-ethernet \ --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet \ --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,unstable-traits \ --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,nightly \ diff --git a/ci_stable.sh b/ci_stable.sh index b4b0b83e..18271ee7 100755 --- a/ci_stable.sh +++ b/ci_stable.sh @@ -13,6 +13,7 @@ cargo batch \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features log \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features defmt \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features defmt \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,medium-ethernet \ --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet \ --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,unstable-traits \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52805,gpiote,time-driver-rtc1 \ diff --git a/embassy-embedded-hal/src/adapter.rs b/embassy-embedded-hal/src/adapter.rs index ee919bd8..171ff6c9 100644 --- a/embassy-embedded-hal/src/adapter.rs +++ b/embassy-embedded-hal/src/adapter.rs @@ -131,48 +131,6 @@ where type Error = E; } -#[cfg(feature = "_todo_embedded_hal_serial")] -impl embedded_hal_async::serial::Read for BlockingAsync -where - T: serial::Read, - E: embedded_hal_1::serial::Error + 'static, -{ - type ReadFuture<'a> = impl Future> + 'a where T: 'a; - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - async move { - let mut pos = 0; - while pos < buf.len() { - match self.wrapped.read() { - Err(nb::Error::WouldBlock) => {} - Err(nb::Error::Other(e)) => return Err(e), - Ok(b) => { - buf[pos] = b; - pos += 1; - } - } - } - Ok(()) - } - } -} - -#[cfg(feature = "_todo_embedded_hal_serial")] -impl embedded_hal_async::serial::Write for BlockingAsync -where - T: blocking::serial::Write + serial::Read, - E: embedded_hal_1::serial::Error + 'static, -{ - type WriteFuture<'a> = impl Future> + 'a where T: 'a; - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - async move { self.wrapped.bwrite_all(buf) } - } - - type FlushFuture<'a> = impl Future> + 'a where T: 'a; - fn flush(&mut self) -> Result<(), Self::Error> { - async move { self.wrapped.bflush() } - } -} - /// NOR flash wrapper use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; diff --git a/embassy-executor/src/raw/run_queue.rs b/embassy-executor/src/raw/run_queue.rs index a88174a0..f1ec19ac 100644 --- a/embassy-executor/src/raw/run_queue.rs +++ b/embassy-executor/src/raw/run_queue.rs @@ -4,15 +4,16 @@ use core::ptr::NonNull; use atomic_polyfill::{AtomicPtr, Ordering}; use super::{TaskHeader, TaskRef}; +use crate::raw::util::SyncUnsafeCell; pub(crate) struct RunQueueItem { - next: AtomicPtr, + next: SyncUnsafeCell>, } impl RunQueueItem { pub const fn new() -> Self { Self { - next: AtomicPtr::new(ptr::null_mut()), + next: SyncUnsafeCell::new(None), } } } @@ -51,7 +52,12 @@ impl RunQueue { self.head .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |prev| { was_empty = prev.is_null(); - task.header().run_queue_item.next.store(prev, Ordering::Relaxed); + unsafe { + // safety: the pointer is either null or valid + let prev = NonNull::new(prev).map(|ptr| TaskRef::from_ptr(ptr.as_ptr())); + // safety: there are no concurrent accesses to `next` + task.header().run_queue_item.next.set(prev); + } Some(task.as_ptr() as *mut _) }) .ok(); @@ -64,18 +70,19 @@ impl RunQueue { /// and will be processed by the *next* call to `dequeue_all`, *not* the current one. pub(crate) fn dequeue_all(&self, on_task: impl Fn(TaskRef)) { // Atomically empty the queue. - let mut ptr = self.head.swap(ptr::null_mut(), Ordering::AcqRel); + let ptr = self.head.swap(ptr::null_mut(), Ordering::AcqRel); + + // safety: the pointer is either null or valid + let mut next = unsafe { NonNull::new(ptr).map(|ptr| TaskRef::from_ptr(ptr.as_ptr())) }; // Iterate the linked list of tasks that were previously in the queue. - while let Some(task) = NonNull::new(ptr) { - let task = unsafe { TaskRef::from_ptr(task.as_ptr()) }; + while let Some(task) = next { // If the task re-enqueues itself, the `next` pointer will get overwritten. // Therefore, first read the next pointer, and only then process the task. - let next = task.header().run_queue_item.next.load(Ordering::Relaxed); + // safety: there are no concurrent accesses to `next` + next = unsafe { task.header().run_queue_item.next.get() }; on_task(task); - - ptr = next } } } diff --git a/embassy-hal-common/src/macros.rs b/embassy-hal-common/src/macros.rs index 5e62e048..f06b4600 100644 --- a/embassy-hal-common/src/macros.rs +++ b/embassy-hal-common/src/macros.rs @@ -1,5 +1,5 @@ #[macro_export] -macro_rules! peripherals { +macro_rules! peripherals_definition { ($($(#[$cfg:meta])? $name:ident),*$(,)?) => { /// Types for the peripheral singletons. pub mod peripherals { @@ -26,7 +26,12 @@ macro_rules! peripherals { $crate::impl_peripheral!($name); )* } + }; +} +#[macro_export] +macro_rules! peripherals_struct { + ($($(#[$cfg:meta])? $name:ident),*$(,)?) => { /// Struct containing all the peripheral singletons. /// /// To obtain the peripherals, you must initialize the HAL, by calling [`crate::init`]. @@ -76,6 +81,24 @@ macro_rules! peripherals { }; } +#[macro_export] +macro_rules! peripherals { + ($($(#[$cfg:meta])? $name:ident),*$(,)?) => { + $crate::peripherals_definition!( + $( + $(#[$cfg])? + $name, + )* + ); + $crate::peripherals_struct!( + $( + $(#[$cfg])? + $name, + )* + ); + }; +} + #[macro_export] macro_rules! into_ref { ($($name:ident),*) => { diff --git a/embassy-lora/Cargo.toml b/embassy-lora/Cargo.toml index 79d5660f..50449dd4 100644 --- a/embassy-lora/Cargo.toml +++ b/embassy-lora/Cargo.toml @@ -38,5 +38,5 @@ futures = { version = "0.3.17", default-features = false, features = [ "async-aw embedded-hal = { version = "0.2", features = ["unproven"] } bit_field = { version = "0.10" } -lorawan-device = { version = "0.8.0", default-features = false, features = ["async"] } -lorawan = { version = "0.7.1", default-features = false } +lorawan-device = { version = "0.9.0", default-features = false, features = ["async"] } +lorawan = { version = "0.7.2", default-features = false } diff --git a/embassy-lora/src/lib.rs b/embassy-lora/src/lib.rs index 3e474843..5c919cbb 100644 --- a/embassy-lora/src/lib.rs +++ b/embassy-lora/src/lib.rs @@ -1,5 +1,6 @@ #![no_std] -#![feature(type_alias_impl_trait)] +#![feature(async_fn_in_trait, impl_trait_projections)] +#![allow(incomplete_features)] //! embassy-lora is a collection of async radio drivers that integrate with the lorawan-device //! crate's async LoRaWAN MAC implementation. @@ -34,13 +35,11 @@ impl lorawan_device::async_device::radio::Timer for LoraTimer { self.start = Instant::now(); } - type AtFuture<'m> = impl core::future::Future + 'm; - fn at<'m>(&'m mut self, millis: u64) -> Self::AtFuture<'m> { - Timer::at(self.start + Duration::from_millis(millis)) + async fn at(&mut self, millis: u64) { + Timer::at(self.start + Duration::from_millis(millis)).await } - type DelayFuture<'m> = impl core::future::Future + 'm; - fn delay_ms<'m>(&'m mut self, millis: u64) -> Self::DelayFuture<'m> { - Timer::after(Duration::from_millis(millis)) + async fn delay_ms(&mut self, millis: u64) { + Timer::after(Duration::from_millis(millis)).await } } diff --git a/embassy-lora/src/stm32wl/mod.rs b/embassy-lora/src/stm32wl/mod.rs index 3d52c1cc..d76e8c43 100644 --- a/embassy-lora/src/stm32wl/mod.rs +++ b/embassy-lora/src/stm32wl/mod.rs @@ -1,5 +1,5 @@ //! A radio driver integration for the radio found on STM32WL family devices. -use core::future::{poll_fn, Future}; +use core::future::poll_fn; use core::task::Poll; use embassy_hal_common::{into_ref, Peripheral, PeripheralRef}; @@ -241,14 +241,12 @@ fn configure_radio(radio: &mut SubGhz<'_, NoDma, NoDma>, config: SubGhzRadioConf impl<'d, RS: RadioSwitch> PhyRxTx for SubGhzRadio<'d, RS> { type PhyError = RadioError; - type TxFuture<'m> = impl Future> + 'm where Self: 'm; - fn tx<'m>(&'m mut self, config: TxConfig, buf: &'m [u8]) -> Self::TxFuture<'m> { - async move { self.do_tx(config, buf).await } + async fn tx(&mut self, config: TxConfig, buf: &[u8]) -> Result { + self.do_tx(config, buf).await } - type RxFuture<'m> = impl Future> + 'm where Self: 'm; - fn rx<'m>(&'m mut self, config: RfConfig, buf: &'m mut [u8]) -> Self::RxFuture<'m> { - async move { self.do_rx(config, buf).await } + async fn rx(&mut self, config: RfConfig, buf: &mut [u8]) -> Result<(usize, RxQuality), Self::PhyError> { + self.do_rx(config, buf).await } } diff --git a/embassy-lora/src/sx126x/mod.rs b/embassy-lora/src/sx126x/mod.rs index 8559574c..2f0b8c8e 100644 --- a/embassy-lora/src/sx126x/mod.rs +++ b/embassy-lora/src/sx126x/mod.rs @@ -1,5 +1,3 @@ -use core::future::Future; - use defmt::Format; use embedded_hal::digital::v2::OutputPin; use embedded_hal_async::digital::Wait; @@ -71,83 +69,69 @@ where { type PhyError = RadioError; - type TxFuture<'m> = impl Future> + 'm - where - SPI: 'm, - CTRL: 'm, - WAIT: 'm, - BUS: 'm; - - fn tx<'m>(&'m mut self, config: TxConfig, buffer: &'m [u8]) -> Self::TxFuture<'m> { + async fn tx(&mut self, config: TxConfig, buffer: &[u8]) -> Result { trace!("TX START"); - async move { - self.lora - .set_tx_config( - config.pw, - config.rf.spreading_factor.into(), - config.rf.bandwidth.into(), - config.rf.coding_rate.into(), - 8, - false, - true, - false, - 0, - false, - ) - .await?; - self.lora.set_max_payload_length(buffer.len() as u8).await?; - self.lora.set_channel(config.rf.frequency).await?; - self.lora.send(buffer, 0xffffff).await?; - self.lora.process_irq(None, None, None).await?; - trace!("TX DONE"); - return Ok(0); - } + self.lora + .set_tx_config( + config.pw, + config.rf.spreading_factor.into(), + config.rf.bandwidth.into(), + config.rf.coding_rate.into(), + 8, + false, + true, + false, + 0, + false, + ) + .await?; + self.lora.set_max_payload_length(buffer.len() as u8).await?; + self.lora.set_channel(config.rf.frequency).await?; + self.lora.send(buffer, 0xffffff).await?; + self.lora.process_irq(None, None, None).await?; + trace!("TX DONE"); + return Ok(0); } - type RxFuture<'m> = impl Future> + 'm - where - SPI: 'm, - CTRL: 'm, - WAIT: 'm, - BUS: 'm; - - fn rx<'m>(&'m mut self, config: RfConfig, receiving_buffer: &'m mut [u8]) -> Self::RxFuture<'m> { + async fn rx( + &mut self, + config: RfConfig, + receiving_buffer: &mut [u8], + ) -> Result<(usize, RxQuality), Self::PhyError> { trace!("RX START"); - async move { - self.lora - .set_rx_config( - config.spreading_factor.into(), - config.bandwidth.into(), - config.coding_rate.into(), - 8, - 4, - false, - 0u8, - true, - false, - 0, - true, - true, - ) - .await?; - self.lora.set_max_payload_length(receiving_buffer.len() as u8).await?; - self.lora.set_channel(config.frequency).await?; - self.lora.rx(90 * 1000).await?; - let mut received_len = 0u8; - self.lora - .process_irq(Some(receiving_buffer), Some(&mut received_len), None) - .await?; - trace!("RX DONE"); + self.lora + .set_rx_config( + config.spreading_factor.into(), + config.bandwidth.into(), + config.coding_rate.into(), + 8, + 4, + false, + 0u8, + true, + false, + 0, + true, + true, + ) + .await?; + self.lora.set_max_payload_length(receiving_buffer.len() as u8).await?; + self.lora.set_channel(config.frequency).await?; + self.lora.rx(90 * 1000).await?; + let mut received_len = 0u8; + self.lora + .process_irq(Some(receiving_buffer), Some(&mut received_len), None) + .await?; + trace!("RX DONE"); - let packet_status = self.lora.get_latest_packet_status(); - let mut rssi = 0i16; - let mut snr = 0i8; - if packet_status.is_some() { - rssi = packet_status.unwrap().rssi as i16; - snr = packet_status.unwrap().snr; - } - - Ok((received_len as usize, RxQuality::new(rssi, snr))) + let packet_status = self.lora.get_latest_packet_status(); + let mut rssi = 0i16; + let mut snr = 0i8; + if packet_status.is_some() { + rssi = packet_status.unwrap().rssi as i16; + snr = packet_status.unwrap().snr; } + + Ok((received_len as usize, RxQuality::new(rssi, snr))) } } diff --git a/embassy-lora/src/sx127x/mod.rs b/embassy-lora/src/sx127x/mod.rs index 8904c9a1..4e8dc223 100644 --- a/embassy-lora/src/sx127x/mod.rs +++ b/embassy-lora/src/sx127x/mod.rs @@ -1,5 +1,3 @@ -use core::future::Future; - use embedded_hal::digital::v2::OutputPin; use embedded_hal_async::digital::Wait; use embedded_hal_async::spi::*; @@ -88,101 +86,78 @@ where { type PhyError = Sx127xError; - type TxFuture<'m> = impl Future> + 'm - where - SPI: 'm, - CS: 'm, - RESET: 'm, - E: 'm, - I: 'm, - RFS: 'm; - - fn tx<'m>(&'m mut self, config: TxConfig, buf: &'m [u8]) -> Self::TxFuture<'m> { + async fn tx(&mut self, config: TxConfig, buf: &[u8]) -> Result { trace!("TX START"); - async move { + self.radio.set_mode(RadioMode::Stdby).await.ok().unwrap(); + self.rfs.set_tx(); + self.radio.set_tx_power(14, 0).await?; + self.radio.set_frequency(config.rf.frequency).await?; + // TODO: Modify radio to support other coding rates + self.radio.set_coding_rate_4(5).await?; + self.radio + .set_signal_bandwidth(bandwidth_to_i64(config.rf.bandwidth)) + .await?; + self.radio + .set_spreading_factor(spreading_factor_to_u8(config.rf.spreading_factor)) + .await?; + + self.radio.set_preamble_length(8).await?; + self.radio.set_lora_pa_ramp().await?; + self.radio.set_lora_sync_word().await?; + self.radio.set_invert_iq(false).await?; + self.radio.set_crc(true).await?; + + self.radio.set_dio0_tx_done().await?; + + self.radio.transmit_start(buf).await?; + + loop { + self.irq.wait_for_rising_edge().await.unwrap(); self.radio.set_mode(RadioMode::Stdby).await.ok().unwrap(); - self.rfs.set_tx(); - self.radio.set_tx_power(14, 0).await?; - self.radio.set_frequency(config.rf.frequency).await?; - // TODO: Modify radio to support other coding rates - self.radio.set_coding_rate_4(5).await?; - self.radio - .set_signal_bandwidth(bandwidth_to_i64(config.rf.bandwidth)) - .await?; - self.radio - .set_spreading_factor(spreading_factor_to_u8(config.rf.spreading_factor)) - .await?; - - self.radio.set_preamble_length(8).await?; - self.radio.set_lora_pa_ramp().await?; - self.radio.set_lora_sync_word().await?; - self.radio.set_invert_iq(false).await?; - self.radio.set_crc(true).await?; - - self.radio.set_dio0_tx_done().await?; - - self.radio.transmit_start(buf).await?; - - loop { - self.irq.wait_for_rising_edge().await.unwrap(); - self.radio.set_mode(RadioMode::Stdby).await.ok().unwrap(); - let irq = self.radio.clear_irq().await.ok().unwrap(); - if (irq & IRQ::IrqTxDoneMask.addr()) != 0 { - trace!("TX DONE"); - return Ok(0); - } + let irq = self.radio.clear_irq().await.ok().unwrap(); + if (irq & IRQ::IrqTxDoneMask.addr()) != 0 { + trace!("TX DONE"); + return Ok(0); } } } - type RxFuture<'m> = impl Future> + 'm - where - SPI: 'm, - CS: 'm, - RESET: 'm, - E: 'm, - I: 'm, - RFS: 'm; + async fn rx(&mut self, config: RfConfig, buf: &mut [u8]) -> Result<(usize, RxQuality), Self::PhyError> { + self.rfs.set_rx(); + self.radio.reset_payload_length().await?; + self.radio.set_frequency(config.frequency).await?; + // TODO: Modify radio to support other coding rates + self.radio.set_coding_rate_4(5).await?; + self.radio + .set_signal_bandwidth(bandwidth_to_i64(config.bandwidth)) + .await?; + self.radio + .set_spreading_factor(spreading_factor_to_u8(config.spreading_factor)) + .await?; - 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().await?; - self.radio.set_frequency(config.frequency).await?; - // TODO: Modify radio to support other coding rates - self.radio.set_coding_rate_4(5).await?; - self.radio - .set_signal_bandwidth(bandwidth_to_i64(config.bandwidth)) - .await?; - self.radio - .set_spreading_factor(spreading_factor_to_u8(config.spreading_factor)) - .await?; + self.radio.set_preamble_length(8).await?; + self.radio.set_lora_sync_word().await?; + self.radio.set_invert_iq(true).await?; + self.radio.set_crc(true).await?; - self.radio.set_preamble_length(8).await?; - self.radio.set_lora_sync_word().await?; - self.radio.set_invert_iq(true).await?; - self.radio.set_crc(true).await?; + self.radio.set_dio0_rx_done().await?; + self.radio.set_mode(RadioMode::RxContinuous).await?; - self.radio.set_dio0_rx_done().await?; - self.radio.set_mode(RadioMode::RxContinuous).await?; - - loop { - self.irq.wait_for_rising_edge().await.unwrap(); - self.radio.set_mode(RadioMode::Stdby).await.ok().unwrap(); - let irq = self.radio.clear_irq().await.ok().unwrap(); - if (irq & IRQ::IrqRxDoneMask.addr()) != 0 { - let rssi = self.radio.get_packet_rssi().await.unwrap_or(0) as i16; - let snr = self.radio.get_packet_snr().await.unwrap_or(0.0) as i8; - let response = if let Ok(size) = self.radio.read_packet_size().await { - self.radio.read_packet(buf).await?; - Ok((size, RxQuality::new(rssi, snr))) - } else { - Ok((0, RxQuality::new(rssi, snr))) - }; - trace!("RX DONE"); - return response; - } + loop { + self.irq.wait_for_rising_edge().await.unwrap(); + self.radio.set_mode(RadioMode::Stdby).await.ok().unwrap(); + let irq = self.radio.clear_irq().await.ok().unwrap(); + if (irq & IRQ::IrqRxDoneMask.addr()) != 0 { + let rssi = self.radio.get_packet_rssi().await.unwrap_or(0) as i16; + let snr = self.radio.get_packet_snr().await.unwrap_or(0.0) as i8; + let response = if let Ok(size) = self.radio.read_packet_size().await { + self.radio.read_packet(buf).await?; + Ok((size, RxQuality::new(rssi, snr))) + } else { + Ok((0, RxQuality::new(rssi, snr))) + }; + trace!("RX DONE"); + return response; } } } diff --git a/embassy-net/src/lib.rs b/embassy-net/src/lib.rs index 7b9d0e77..5dfb5843 100644 --- a/embassy-net/src/lib.rs +++ b/embassy-net/src/lib.rs @@ -27,12 +27,10 @@ use embassy_sync::waitqueue::WakerRegistration; use embassy_time::{Instant, Timer}; use futures::pin_mut; use heapless::Vec; +use smoltcp::iface::{Interface, SocketHandle, SocketSet, SocketStorage}; #[cfg(feature = "dhcpv4")] -use smoltcp::iface::SocketHandle; -use smoltcp::iface::{Interface, SocketSet, SocketStorage}; +use smoltcp::socket::dhcpv4::{self, RetryConfig}; #[cfg(feature = "dhcpv4")] -use smoltcp::socket::dhcpv4; -use smoltcp::socket::dhcpv4::RetryConfig; use smoltcp::time::Duration; // smoltcp reexports pub use smoltcp::time::{Duration as SmolDuration, Instant as SmolInstant}; @@ -76,6 +74,7 @@ pub struct StaticConfig { pub dns_servers: Vec, } +#[cfg(feature = "dhcpv4")] #[derive(Debug, Clone, PartialEq, Eq)] pub struct DhcpConfig { pub max_lease_duration: Option, @@ -88,6 +87,7 @@ pub struct DhcpConfig { pub client_port: u16, } +#[cfg(feature = "dhcpv4")] impl Default for DhcpConfig { fn default() -> Self { Self { @@ -384,6 +384,7 @@ impl Inner { self.config = Some(config) } + #[cfg(feature = "dhcpv4")] fn apply_dhcp_config(&self, socket: &mut smoltcp::socket::dhcpv4::Socket, config: DhcpConfig) { socket.set_ignore_naks(config.ignore_naks); socket.set_max_lease_duration(config.max_lease_duration); diff --git a/embassy-nrf/src/uarte.rs b/embassy-nrf/src/uarte.rs index e59b2332..586c88b2 100644 --- a/embassy-nrf/src/uarte.rs +++ b/embassy-nrf/src/uarte.rs @@ -992,80 +992,3 @@ mod eh1 { type Error = Error; } } - -#[cfg(all( - feature = "unstable-traits", - feature = "nightly", - feature = "_todo_embedded_hal_serial" -))] -mod eha { - use core::future::Future; - - use super::*; - - impl<'d, T: Instance> embedded_hal_async::serial::Read for Uarte<'d, T> { - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn read<'a>(&'a mut self, buffer: &'a mut [u8]) -> Self::ReadFuture<'a> { - self.read(buffer) - } - } - - impl<'d, T: Instance> embedded_hal_async::serial::Write for Uarte<'d, T> { - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write<'a>(&'a mut self, buffer: &'a [u8]) -> Self::WriteFuture<'a> { - self.write(buffer) - } - - type FlushFuture<'a> = impl Future> + 'a where Self: 'a; - - fn flush(&mut self) -> Result<(), Self::Error> { - async move { Ok(()) } - } - } - - impl<'d, T: Instance> embedded_hal_async::serial::Write for UarteTx<'d, T> { - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write<'a>(&'a mut self, buffer: &'a [u8]) -> Self::WriteFuture<'a> { - self.write(buffer) - } - - type FlushFuture<'a> = impl Future> + 'a where Self: 'a; - - fn flush(&mut self) -> Result<(), Self::Error> { - async move { Ok(()) } - } - } - - impl<'d, T: Instance> embedded_hal_async::serial::Read for UarteRx<'d, T> { - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn read<'a>(&'a mut self, buffer: &'a mut [u8]) -> Self::ReadFuture<'a> { - self.read(buffer) - } - } - - impl<'d, U: Instance, T: TimerInstance> embedded_hal_async::serial::Read for UarteWithIdle<'d, U, T> { - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn read<'a>(&'a mut self, buffer: &'a mut [u8]) -> Self::ReadFuture<'a> { - self.read(buffer) - } - } - - impl<'d, U: Instance, T: TimerInstance> embedded_hal_async::serial::Write for UarteWithIdle<'d, U, T> { - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write<'a>(&'a mut self, buffer: &'a [u8]) -> Self::WriteFuture<'a> { - self.write(buffer) - } - - type FlushFuture<'a> = impl Future> + 'a where Self: 'a; - - fn flush(&mut self) -> Result<(), Self::Error> { - async move { Ok(()) } - } - } -} diff --git a/embassy-rp/Cargo.toml b/embassy-rp/Cargo.toml index 2ef2c8f0..b7ed6ccb 100644 --- a/embassy-rp/Cargo.toml +++ b/embassy-rp/Cargo.toml @@ -61,8 +61,7 @@ embedded-io = { version = "0.4.0", features = ["async"], optional = true } embedded-storage = { version = "0.3" } rand_core = "0.6.4" -rp2040-pac2 = { git = "https://github.com/embassy-rs/rp2040-pac2", rev="017e3c9007b2d3b6965f0d85b5bf8ce3fa6d7364", features = ["rt"] } -#rp2040-pac2 = { path = "../../rp2040-pac2", features = ["rt"] } +rp-pac = { version = "1", features = ["rt"] } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10", optional = true} diff --git a/embassy-rp/src/intrinsics.rs b/embassy-rp/src/intrinsics.rs index 3e75fb7f..3b63846d 100644 --- a/embassy-rp/src/intrinsics.rs +++ b/embassy-rp/src/intrinsics.rs @@ -274,3 +274,201 @@ macro_rules! intrinsics { intrinsics!($($rest)*); }; } + +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/sio.rs + +// This takes advantage of how AAPCS defines a 64-bit return on 32-bit registers +// by packing it into r0[0:31] and r1[32:63]. So all we need to do is put +// the remainder in the high order 32 bits of a 64 bit result. We can also +// alias the division operators to these for a similar reason r0 is the +// result either way and r1 a scratch register, so the caller can't assume it +// retains the argument value. +#[cfg(target_arch = "arm")] +core::arch::global_asm!( + ".macro hwdivider_head", + "ldr r2, =(0xd0000000)", // SIO_BASE + // Check the DIRTY state of the divider by shifting it into the C + // status bit. + "ldr r3, [r2, #0x078]", // DIV_CSR + "lsrs r3, #2", // DIRTY = 1, so shift 2 down + // We only need to save the state when DIRTY, otherwise we can just do the + // division directly. + "bcs 2f", + "1:", + // Do the actual division now, we're either not DIRTY, or we've saved the + // state and branched back here so it's safe now. + ".endm", + ".macro hwdivider_tail", + // 8 cycle delay to wait for the result. Each branch takes two cycles + // and fits into a 2-byte Thumb instruction, so this is smaller than + // 8 NOPs. + "b 3f", + "3: b 3f", + "3: b 3f", + "3: b 3f", + "3:", + // Read the quotient last, since that's what clears the dirty flag. + "ldr r1, [r2, #0x074]", // DIV_REMAINDER + "ldr r0, [r2, #0x070]", // DIV_QUOTIENT + // Either return to the caller or back to the state restore. + "bx lr", + "2:", + // Since we can't save the signed-ness of the calculation, we have to make + // sure that there's at least an 8 cycle delay before we read the result. + // The push takes 5 cycles, and we've already spent at least 7 checking + // the DIRTY state to get here. + "push {{r4-r6, lr}}", + // Read the quotient last, since that's what clears the dirty flag. + "ldr r3, [r2, #0x060]", // DIV_UDIVIDEND + "ldr r4, [r2, #0x064]", // DIV_UDIVISOR + "ldr r5, [r2, #0x074]", // DIV_REMAINDER + "ldr r6, [r2, #0x070]", // DIV_QUOTIENT + // If we get interrupted here (before a write sets the DIRTY flag) it's + // fine, since we have the full state, so the interruptor doesn't have to + // restore it. Once the write happens and the DIRTY flag is set, the + // interruptor becomes responsible for restoring our state. + "bl 1b", + // If we are interrupted here, then the interruptor will start an incorrect + // calculation using a wrong divisor, but we'll restore the divisor and + // result ourselves correctly. This sets DIRTY, so any interruptor will + // save the state. + "str r3, [r2, #0x060]", // DIV_UDIVIDEND + // If we are interrupted here, the the interruptor may start the + // calculation using incorrectly signed inputs, but we'll restore the + // result ourselves. This sets DIRTY, so any interruptor will save the + // state. + "str r4, [r2, #0x064]", // DIV_UDIVISOR + // If we are interrupted here, the interruptor will have restored + // everything but the quotient may be wrongly signed. If the calculation + // started by the above writes is still ongoing it is stopped, so it won't + // replace the result we're restoring. DIRTY and READY set, but only + // DIRTY matters to make the interruptor save the state. + "str r5, [r2, #0x074]", // DIV_REMAINDER + // State fully restored after the quotient write. This sets both DIRTY + // and READY, so whatever we may have interrupted can read the result. + "str r6, [r2, #0x070]", // DIV_QUOTIENT + "pop {{r4-r6, pc}}", + ".endm", +); + +macro_rules! division_function { + ( + $name:ident $($intrinsic:ident)* ( $argty:ty ) { + $($begin:literal),+ + } + ) => { + #[cfg(all(target_arch = "arm", feature = "intrinsics"))] + core::arch::global_asm!( + // Mangle the name slightly, since this is a global symbol. + concat!(".global _rphal_", stringify!($name)), + concat!(".type _rphal_", stringify!($name), ", %function"), + ".align 2", + concat!("_rphal_", stringify!($name), ":"), + $( + concat!(".global ", stringify!($intrinsic)), + concat!(".type ", stringify!($intrinsic), ", %function"), + concat!(stringify!($intrinsic), ":"), + )* + + "hwdivider_head", + $($begin),+ , + "hwdivider_tail", + ); + + #[cfg(all(target_arch = "arm", not(feature = "intrinsics")))] + core::arch::global_asm!( + // Mangle the name slightly, since this is a global symbol. + concat!(".global _rphal_", stringify!($name)), + concat!(".type _rphal_", stringify!($name), ", %function"), + ".align 2", + concat!("_rphal_", stringify!($name), ":"), + + "hwdivider_head", + $($begin),+ , + "hwdivider_tail", + ); + + #[cfg(target_arch = "arm")] + extern "aapcs" { + // Connect a local name to global symbol above through FFI. + #[link_name = concat!("_rphal_", stringify!($name)) ] + fn $name(n: $argty, d: $argty) -> u64; + } + + #[cfg(not(target_arch = "arm"))] + #[allow(unused_variables)] + unsafe fn $name(n: $argty, d: $argty) -> u64 { 0 } + }; +} + +division_function! { + unsigned_divmod __aeabi_uidivmod __aeabi_uidiv ( u32 ) { + "str r0, [r2, #0x060]", // DIV_UDIVIDEND + "str r1, [r2, #0x064]" // DIV_UDIVISOR + } +} + +division_function! { + signed_divmod __aeabi_idivmod __aeabi_idiv ( i32 ) { + "str r0, [r2, #0x068]", // DIV_SDIVIDEND + "str r1, [r2, #0x06c]" // DIV_SDIVISOR + } +} + +fn divider_unsigned(n: u32, d: u32) -> DivResult { + let packed = unsafe { unsigned_divmod(n, d) }; + DivResult { + quotient: packed as u32, + remainder: (packed >> 32) as u32, + } +} + +fn divider_signed(n: i32, d: i32) -> DivResult { + let packed = unsafe { signed_divmod(n, d) }; + // Double casts to avoid sign extension + DivResult { + quotient: packed as u32 as i32, + remainder: (packed >> 32) as u32 as i32, + } +} + +/// Result of divide/modulo operation +struct DivResult { + /// The quotient of divide/modulo operation + pub quotient: T, + /// The remainder of divide/modulo operation + pub remainder: T, +} + +intrinsics! { + extern "C" fn __udivsi3(n: u32, d: u32) -> u32 { + divider_unsigned(n, d).quotient + } + + extern "C" fn __umodsi3(n: u32, d: u32) -> u32 { + divider_unsigned(n, d).remainder + } + + extern "C" fn __udivmodsi4(n: u32, d: u32, rem: Option<&mut u32>) -> u32 { + let quo_rem = divider_unsigned(n, d); + if let Some(rem) = rem { + *rem = quo_rem.remainder; + } + quo_rem.quotient + } + + extern "C" fn __divsi3(n: i32, d: i32) -> i32 { + divider_signed(n, d).quotient + } + + extern "C" fn __modsi3(n: i32, d: i32) -> i32 { + divider_signed(n, d).remainder + } + + extern "C" fn __divmodsi4(n: i32, d: i32, rem: &mut i32) -> i32 { + let quo_rem = divider_signed(n, d); + *rem = quo_rem.remainder; + quo_rem.quotient + } +} diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs index 9e99b2fb..1d63f6c2 100644 --- a/embassy-rp/src/lib.rs +++ b/embassy-rp/src/lib.rs @@ -44,9 +44,9 @@ pub use embassy_cortex_m::executor; pub use embassy_cortex_m::interrupt::_export::interrupt; pub use embassy_hal_common::{into_ref, Peripheral, PeripheralRef}; #[cfg(feature = "unstable-pac")] -pub use rp2040_pac2 as pac; +pub use rp_pac as pac; #[cfg(not(feature = "unstable-pac"))] -pub(crate) use rp2040_pac2 as pac; +pub(crate) use rp_pac as pac; embassy_hal_common::peripherals! { PIN_0, diff --git a/embassy-rp/src/uart/buffered.rs b/embassy-rp/src/uart/buffered.rs index c620ed08..cb046193 100644 --- a/embassy-rp/src/uart/buffered.rs +++ b/embassy-rp/src/uart/buffered.rs @@ -726,58 +726,3 @@ mod eh1 { } } } - -#[cfg(all( - feature = "unstable-traits", - feature = "nightly", - feature = "_todo_embedded_hal_serial" -))] -mod eha { - use core::future::Future; - - use super::*; - - impl<'d, T: Instance> embedded_hal_async::serial::Write for BufferedUartTx<'d, T> { - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - Self::write(buf) - } - - type FlushFuture<'a> = impl Future> + 'a where Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - Self::flush() - } - } - - impl<'d, T: Instance> embedded_hal_async::serial::Read for BufferedUartRx<'d, T> { - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - Self::read(buf) - } - } - - impl<'d, T: Instance> embedded_hal_async::serial::Write for BufferedUart<'d, T> { - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - BufferedUartTx::<'d, T>::write(buf) - } - - type FlushFuture<'a> = impl Future> + 'a where Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - BufferedUartTx::<'d, T>::flush() - } - } - - impl<'d, T: Instance> embedded_hal_async::serial::Read for BufferedUart<'d, T> { - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - BufferedUartRx::<'d, T>::read(buf) - } - } -} diff --git a/embassy-rp/src/uart/mod.rs b/embassy-rp/src/uart/mod.rs index a945f229..dedc390f 100644 --- a/embassy-rp/src/uart/mod.rs +++ b/embassy-rp/src/uart/mod.rs @@ -5,6 +5,7 @@ use embassy_hal_common::{into_ref, PeripheralRef}; use crate::dma::{AnyChannel, Channel}; use crate::gpio::sealed::Pin; use crate::gpio::AnyPin; +use crate::pac::io::vals::{Inover, Outover}; use crate::{pac, peripherals, Peripheral}; #[cfg(feature = "nightly")] @@ -53,6 +54,14 @@ pub struct Config { pub data_bits: DataBits, pub stop_bits: StopBits, pub parity: Parity, + /// Invert the tx pin output + pub invert_tx: bool, + /// Invert the rx pin input + pub invert_rx: bool, + // Invert the rts pin + pub invert_rts: bool, + // Invert the cts pin + pub invert_cts: bool, } impl Default for Config { @@ -62,6 +71,10 @@ impl Default for Config { data_bits: DataBits::DataBits8, stop_bits: StopBits::STOP1, parity: Parity::ParityNone, + invert_rx: false, + invert_tx: false, + invert_rts: false, + invert_cts: false, } } } @@ -167,7 +180,7 @@ impl<'d, T: Instance> UartTx<'d, T, Async> { } impl<'d, T: Instance, M: Mode> UartRx<'d, T, M> { - /// Create a new DMA-enabled UART which can only send data + /// Create a new DMA-enabled UART which can only recieve data pub fn new( _uart: impl Peripheral

+ 'd, rx: impl Peripheral

> + 'd, @@ -175,7 +188,7 @@ impl<'d, T: Instance, M: Mode> UartRx<'d, T, M> { config: Config, ) -> Self { into_ref!(rx, rx_dma); - Uart::::init(Some(rx.map_into()), None, None, None, config); + Uart::::init(None, Some(rx.map_into()), None, None, config); Self::new_inner(Some(rx_dma.map_into())) } @@ -381,19 +394,47 @@ impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> { let r = T::regs(); unsafe { if let Some(pin) = &tx { - pin.io().ctrl().write(|w| w.set_funcsel(2)); + pin.io().ctrl().write(|w| { + w.set_funcsel(2); + w.set_outover(if config.invert_tx { + Outover::INVERT + } else { + Outover::NORMAL + }); + }); pin.pad_ctrl().write(|w| w.set_ie(true)); } if let Some(pin) = &rx { - pin.io().ctrl().write(|w| w.set_funcsel(2)); + pin.io().ctrl().write(|w| { + w.set_funcsel(2); + w.set_inover(if config.invert_rx { + Inover::INVERT + } else { + Inover::NORMAL + }); + }); pin.pad_ctrl().write(|w| w.set_ie(true)); } if let Some(pin) = &cts { - pin.io().ctrl().write(|w| w.set_funcsel(2)); + pin.io().ctrl().write(|w| { + w.set_funcsel(2); + w.set_inover(if config.invert_cts { + Inover::INVERT + } else { + Inover::NORMAL + }); + }); pin.pad_ctrl().write(|w| w.set_ie(true)); } if let Some(pin) = &rts { - pin.io().ctrl().write(|w| w.set_funcsel(2)); + pin.io().ctrl().write(|w| { + w.set_funcsel(2); + w.set_outover(if config.invert_rts { + Outover::INVERT + } else { + Outover::NORMAL + }); + }); pin.pad_ctrl().write(|w| w.set_ie(true)); } @@ -651,61 +692,6 @@ mod eh1 { } } -#[cfg(all( - feature = "unstable-traits", - feature = "nightly", - feature = "_todo_embedded_hal_serial" -))] -mod eha { - use core::future::Future; - - use super::*; - - impl<'d, T: Instance, M: Mode> embedded_hal_async::serial::Write for UartTx<'d, T, M> { - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - self.write(buf) - } - - type FlushFuture<'a> = impl Future> + 'a where Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - async move { Ok(()) } - } - } - - impl<'d, T: Instance, M: Mode> embedded_hal_async::serial::Read for UartRx<'d, T, M> { - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - self.read(buf) - } - } - - impl<'d, T: Instance, M: Mode> embedded_hal_async::serial::Write for Uart<'d, T, M> { - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - self.write(buf) - } - - type FlushFuture<'a> = impl Future> + 'a where Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - async move { Ok(()) } - } - } - - impl<'d, T: Instance, M: Mode> embedded_hal_async::serial::Read for Uart<'d, T, M> { - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - self.read(buf) - } - } -} - mod sealed { use super::*; diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index 969dc3b7..f27cf6a5 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -55,7 +55,7 @@ cortex-m = "0.7.6" futures = { version = "0.3.17", default-features = false, features = ["async-await"] } rand_core = "0.6.3" sdio-host = "0.5.0" -embedded-sdmmc = { git = "https://github.com/embassy-rs/embedded-sdmmc-rs", rev = "46d1b1c2ff13e31e282ec1e352421721694f126a", optional = true } +embedded-sdmmc = { git = "https://github.com/embassy-rs/embedded-sdmmc-rs", rev = "a4f293d3a6f72158385f79c98634cb8a14d0d2fc", optional = true } critical-section = "1.1" atomic-polyfill = "1.0.1" stm32-metapac = "6" @@ -66,6 +66,7 @@ stm32-fmc = "0.2.4" seq-macro = "0.3.0" cfg-if = "1.0.0" embedded-io = { version = "0.4.0", features = ["async"], optional = true } +chrono = { version = "^0.4", default-features = false, optional = true} [dev-dependencies] critical-section = { version = "1.1", features = ["std"] } diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index 73bd29fc..a00c6c41 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -81,11 +81,74 @@ fn main() { singletons.push(c.name.to_string()); } + // ======== + // Handle time-driver-XXXX features. + + let time_driver = match env::vars() + .map(|(a, _)| a) + .filter(|x| x.starts_with("CARGO_FEATURE_TIME_DRIVER_")) + .get_one() + { + Ok(x) => Some( + x.strip_prefix("CARGO_FEATURE_TIME_DRIVER_") + .unwrap() + .to_ascii_lowercase(), + ), + Err(GetOneError::None) => None, + Err(GetOneError::Multiple) => panic!("Multiple stm32xx Cargo features enabled"), + }; + + let time_driver_singleton = match time_driver.as_ref().map(|x| x.as_ref()) { + None => "", + Some("tim2") => "TIM2", + Some("tim3") => "TIM3", + Some("tim4") => "TIM4", + Some("tim5") => "TIM5", + Some("tim12") => "TIM12", + Some("tim15") => "TIM15", + Some("any") => { + if singletons.contains(&"TIM2".to_string()) { + "TIM2" + } else if singletons.contains(&"TIM3".to_string()) { + "TIM3" + } else if singletons.contains(&"TIM4".to_string()) { + "TIM4" + } else if singletons.contains(&"TIM5".to_string()) { + "TIM5" + } else if singletons.contains(&"TIM12".to_string()) { + "TIM12" + } else if singletons.contains(&"TIM15".to_string()) { + "TIM15" + } else { + panic!("time-driver-any requested, but the chip doesn't have TIM2, TIM3, TIM4, TIM5, TIM12 or TIM15.") + } + } + _ => panic!("unknown time_driver {:?}", time_driver), + }; + + if time_driver_singleton != "" { + println!("cargo:rustc-cfg=time_driver_{}", time_driver_singleton.to_lowercase()); + } + + // ======== + // Write singletons + let mut g = TokenStream::new(); let singleton_tokens: Vec<_> = singletons.iter().map(|s| format_ident!("{}", s)).collect(); + g.extend(quote! { - embassy_hal_common::peripherals!(#(#singleton_tokens),*); + embassy_hal_common::peripherals_definition!(#(#singleton_tokens),*); + }); + + let singleton_tokens: Vec<_> = singletons + .iter() + .filter(|s| *s != &time_driver_singleton.to_string()) + .map(|s| format_ident!("{}", s)) + .collect(); + + g.extend(quote! { + embassy_hal_common::peripherals_struct!(#(#singleton_tokens),*); }); // ======== @@ -192,12 +255,20 @@ fn main() { ]; }); + let max_erase_size = flash_memory_regions + .iter() + .map(|region| region.settings.as_ref().unwrap().erase_size) + .max() + .unwrap(); + + g.extend(quote! { pub const MAX_ERASE_SIZE: usize = #max_erase_size as usize; }); + g.extend(quote! { pub mod flash_regions { #flash_regions } }); // ======== // Generate DMA IRQs. - let mut dma_irqs: HashMap<&str, Vec<(&str, &str)>> = HashMap::new(); + let mut dma_irqs: HashMap<&str, Vec<(&str, &str, &str)>> = HashMap::new(); for p in METADATA.peripherals { if let Some(r) = &p.registers { @@ -207,7 +278,10 @@ fn main() { continue; } for irq in p.interrupts { - dma_irqs.entry(irq.interrupt).or_default().push((p.name, irq.signal)); + dma_irqs + .entry(irq.interrupt) + .or_default() + .push((r.kind, p.name, irq.signal)); } } } @@ -216,13 +290,14 @@ fn main() { for (irq, channels) in dma_irqs { let irq = format_ident!("{}", irq); - let channels = channels.iter().map(|(dma, ch)| format_ident!("{}_{}", dma, ch)); + let xdma = format_ident!("{}", channels[0].0); + let channels = channels.iter().map(|(_, dma, ch)| format_ident!("{}_{}", dma, ch)); g.extend(quote! { #[crate::interrupt] unsafe fn #irq () { #( - ::on_irq(); + ::on_irq(); )* } }); @@ -838,51 +913,6 @@ fn main() { println!("cargo:rustc-cfg={}x", &chip_name[..8]); // stm32f42x println!("cargo:rustc-cfg={}x{}", &chip_name[..7], &chip_name[8..9]); // stm32f4x9 - // ======== - // Handle time-driver-XXXX features. - - let time_driver = match env::vars() - .map(|(a, _)| a) - .filter(|x| x.starts_with("CARGO_FEATURE_TIME_DRIVER_")) - .get_one() - { - Ok(x) => Some( - x.strip_prefix("CARGO_FEATURE_TIME_DRIVER_") - .unwrap() - .to_ascii_lowercase(), - ), - Err(GetOneError::None) => None, - Err(GetOneError::Multiple) => panic!("Multiple stm32xx Cargo features enabled"), - }; - - match time_driver.as_ref().map(|x| x.as_ref()) { - None => {} - Some("tim2") => println!("cargo:rustc-cfg=time_driver_tim2"), - Some("tim3") => println!("cargo:rustc-cfg=time_driver_tim3"), - Some("tim4") => println!("cargo:rustc-cfg=time_driver_tim4"), - Some("tim5") => println!("cargo:rustc-cfg=time_driver_tim5"), - Some("tim12") => println!("cargo:rustc-cfg=time_driver_tim12"), - Some("tim15") => println!("cargo:rustc-cfg=time_driver_tim15"), - Some("any") => { - if singletons.contains(&"TIM2".to_string()) { - println!("cargo:rustc-cfg=time_driver_tim2"); - } else if singletons.contains(&"TIM3".to_string()) { - println!("cargo:rustc-cfg=time_driver_tim3"); - } else if singletons.contains(&"TIM4".to_string()) { - println!("cargo:rustc-cfg=time_driver_tim4"); - } else if singletons.contains(&"TIM5".to_string()) { - println!("cargo:rustc-cfg=time_driver_tim5"); - } else if singletons.contains(&"TIM12".to_string()) { - println!("cargo:rustc-cfg=time_driver_tim12"); - } else if singletons.contains(&"TIM15".to_string()) { - println!("cargo:rustc-cfg=time_driver_tim15"); - } else { - panic!("time-driver-any requested, but the chip doesn't have TIM2, TIM3, TIM4, TIM5, TIM12 or TIM15.") - } - } - _ => panic!("unknown time_driver {:?}", time_driver), - } - // Handle time-driver-XXXX features. if env::var("CARGO_FEATURE_TIME_DRIVER_ANY").is_ok() {} println!("cargo:rustc-cfg={}", &chip_name[..chip_name.len() - 2]); diff --git a/embassy-stm32/src/dcmi.rs b/embassy-stm32/src/dcmi.rs index 20e1a407..c19be86c 100644 --- a/embassy-stm32/src/dcmi.rs +++ b/embassy-stm32/src/dcmi.rs @@ -4,6 +4,7 @@ use core::task::Poll; use embassy_hal_common::{into_ref, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; +use crate::dma::Transfer; use crate::gpio::sealed::AFType; use crate::gpio::Speed; use crate::interrupt::{Interrupt, InterruptExt}; @@ -385,14 +386,11 @@ where return self.capture_giant(buffer).await; } } - async fn capture_small(&mut self, buffer: &mut [u32]) -> Result<(), Error> { - let channel = &mut self.dma; - let request = channel.request(); - let r = self.inner.regs(); let src = r.dr().ptr() as *mut u32; - let dma_read = crate::dma::read(channel, request, src, buffer); + let request = self.dma.request(); + let dma_read = unsafe { Transfer::new_read(&mut self.dma, request, src, buffer, Default::default()) }; Self::clear_interrupt_flags(); Self::enable_irqs(); @@ -436,6 +434,12 @@ where result } + #[cfg(not(dma))] + async fn capture_giant(&mut self, _buffer: &mut [u32]) -> Result<(), Error> { + panic!("capturing to buffers larger than 0xffff is only supported on DMA for now, not on BDMA or GPDMA."); + } + + #[cfg(dma)] async fn capture_giant(&mut self, buffer: &mut [u32]) -> Result<(), Error> { use crate::dma::TransferOptions; @@ -460,16 +464,24 @@ where let r = self.inner.regs(); let src = r.dr().ptr() as *mut u32; - unsafe { - channel.start_double_buffered_read(request, src, m0ar, m1ar, chunk_size, TransferOptions::default()); - } + let mut transfer = unsafe { + crate::dma::DoubleBuffered::new_read( + &mut self.dma, + request, + src, + m0ar, + m1ar, + chunk_size, + TransferOptions::default(), + ) + }; let mut last_chunk_set_for_transfer = false; let mut buffer0_last_accessible = false; let dma_result = poll_fn(|cx| { - channel.set_waker(cx.waker()); + transfer.set_waker(cx.waker()); - let buffer0_currently_accessible = unsafe { channel.is_buffer0_accessible() }; + let buffer0_currently_accessible = transfer.is_buffer0_accessible(); // check if the accessible buffer changed since last poll if buffer0_last_accessible == buffer0_currently_accessible { @@ -480,21 +492,21 @@ where if remaining_chunks != 0 { if remaining_chunks % 2 == 0 && buffer0_currently_accessible { m0ar = unsafe { m0ar.add(2 * chunk_size) }; - unsafe { channel.set_buffer0(m0ar) } + unsafe { transfer.set_buffer0(m0ar) } remaining_chunks -= 1; } else if !buffer0_currently_accessible { m1ar = unsafe { m1ar.add(2 * chunk_size) }; - unsafe { channel.set_buffer1(m1ar) }; + unsafe { transfer.set_buffer1(m1ar) }; remaining_chunks -= 1; } } else { if buffer0_currently_accessible { - unsafe { channel.set_buffer0(buffer.as_mut_ptr()) } + unsafe { transfer.set_buffer0(buffer.as_mut_ptr()) } } else { - unsafe { channel.set_buffer1(buffer.as_mut_ptr()) } + unsafe { transfer.set_buffer1(buffer.as_mut_ptr()) } } if last_chunk_set_for_transfer { - channel.request_stop(); + transfer.request_stop(); return Poll::Ready(()); } last_chunk_set_for_transfer = true; diff --git a/embassy-stm32/src/dma/bdma.rs b/embassy-stm32/src/dma/bdma.rs index 5a7a408b..a23bb8cd 100644 --- a/embassy-stm32/src/dma/bdma.rs +++ b/embassy-stm32/src/dma/bdma.rs @@ -1,18 +1,32 @@ #![macro_use] +use core::future::Future; +use core::pin::Pin; use core::sync::atomic::{fence, Ordering}; -use core::task::Waker; +use core::task::{Context, Poll}; use embassy_cortex_m::interrupt::Priority; +use embassy_hal_common::{into_ref, Peripheral, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; -use super::{TransferOptions, Word, WordSize}; +use super::word::{Word, WordSize}; +use super::Dir; use crate::_generated::BDMA_CHANNEL_COUNT; -use crate::dma::Request; use crate::interrupt::{Interrupt, InterruptExt}; use crate::pac; use crate::pac::bdma::vals; +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct TransferOptions {} + +impl Default for TransferOptions { + fn default() -> Self { + Self {} + } +} + impl From for vals::Size { fn from(raw: WordSize) -> Self { match raw { @@ -23,6 +37,15 @@ impl From for vals::Size { } } +impl From

for vals::Dir { + fn from(raw: Dir) -> Self { + match raw { + Dir::MemoryToPeripheral => Self::FROMMEMORY, + Dir::PeripheralToMemory => Self::FROMPERIPHERAL, + } + } +} + struct State { ch_wakers: [AtomicWaker; BDMA_CHANNEL_COUNT], } @@ -55,228 +78,267 @@ foreach_dma_channel! { // BDMA1 in H7 doesn't use DMAMUX, which breaks }; ($channel_peri:ident, $dma_peri:ident, bdma, $channel_num:expr, $index:expr, $dmamux:tt) => { - impl crate::dma::sealed::Channel for crate::peripherals::$channel_peri { - - unsafe fn start_write(&mut self, _request: Request, buf: *const[W], reg_addr: *mut W, options: TransferOptions) { - let (ptr, len) = super::slice_ptr_parts(buf); - low_level_api::start_transfer( - pac::$dma_peri, - $channel_num, - #[cfg(any(bdma_v2, dmamux))] - _request, - vals::Dir::FROMMEMORY, - reg_addr as *const u32, - ptr as *mut u32, - len, - true, - vals::Size::from(W::bits()), - options, - #[cfg(dmamux)] - ::DMAMUX_REGS, - #[cfg(dmamux)] - ::DMAMUX_CH_NUM, - ); + impl sealed::Channel for crate::peripherals::$channel_peri { + fn regs(&self) -> pac::bdma::Dma { + pac::$dma_peri } - - unsafe fn start_write_repeated(&mut self, _request: Request, repeated: *const W, count: usize, reg_addr: *mut W, options: TransferOptions) { - low_level_api::start_transfer( - pac::$dma_peri, - $channel_num, - #[cfg(any(bdma_v2, dmamux))] - _request, - vals::Dir::FROMMEMORY, - reg_addr as *const u32, - repeated as *mut u32, - count, - false, - vals::Size::from(W::bits()), - options, - #[cfg(dmamux)] - ::DMAMUX_REGS, - #[cfg(dmamux)] - ::DMAMUX_CH_NUM, - ) + fn num(&self) -> usize { + $channel_num } - - unsafe fn start_read(&mut self, _request: Request, reg_addr: *const W, buf: *mut [W], options: TransferOptions) { - let (ptr, len) = super::slice_ptr_parts_mut(buf); - low_level_api::start_transfer( - pac::$dma_peri, - $channel_num, - #[cfg(any(bdma_v2, dmamux))] - _request, - vals::Dir::FROMPERIPHERAL, - reg_addr as *const u32, - ptr as *mut u32, - len, - true, - vals::Size::from(W::bits()), - options, - #[cfg(dmamux)] - ::DMAMUX_REGS, - #[cfg(dmamux)] - ::DMAMUX_CH_NUM, - ); + fn index(&self) -> usize { + $index } - - unsafe fn start_double_buffered_read( - &mut self, - _request: Request, - _reg_addr: *const W, - _buffer0: *mut W, - _buffer1: *mut W, - _buffer_len: usize, - _options: TransferOptions, - ) { - panic!("Unsafe double buffered mode is unavailable on BDMA"); - } - - unsafe fn set_buffer0(&mut self, _buffer: *mut W) { - panic!("Unsafe double buffered mode is unavailable on BDMA"); - } - - unsafe fn set_buffer1(&mut self, _buffer: *mut W) { - panic!("Unsafe double buffered mode is unavailable on BDMA"); - } - - unsafe fn is_buffer0_accessible(&mut self) -> bool { - panic!("Unsafe double buffered mode is unavailable on BDMA"); - } - - fn request_stop(&mut self){ - unsafe {low_level_api::request_stop(pac::$dma_peri, $channel_num);} - } - - fn is_running(&self) -> bool { - unsafe {low_level_api::is_running(pac::$dma_peri, $channel_num)} - } - fn remaining_transfers(&mut self) -> u16 { - unsafe {low_level_api::get_remaining_transfers(pac::$dma_peri, $channel_num)} - } - - fn set_waker(&mut self, waker: &Waker) { - unsafe { low_level_api::set_waker($index, waker) } - } - fn on_irq() { - unsafe { - low_level_api::on_irq_inner(pac::$dma_peri, $channel_num, $index); - } + unsafe { on_irq_inner(pac::$dma_peri, $channel_num, $index) } } } - impl crate::dma::Channel for crate::peripherals::$channel_peri {} + impl Channel for crate::peripherals::$channel_peri {} }; } -mod low_level_api { +/// Safety: Must be called with a matching set of parameters for a valid dma channel +pub(crate) unsafe fn on_irq_inner(dma: pac::bdma::Dma, channel_num: usize, index: usize) { + let isr = dma.isr().read(); + let cr = dma.ch(channel_num).cr(); + + if isr.teif(channel_num) { + panic!("DMA: error on BDMA@{:08x} channel {}", dma.0 as u32, channel_num); + } + if isr.tcif(channel_num) && cr.read().tcie() { + cr.write(|_| ()); // Disable channel interrupts with the default value. + STATE.ch_wakers[index].wake(); + } +} + +#[cfg(any(bdma_v2, dmamux))] +pub type Request = u8; +#[cfg(not(any(bdma_v2, dmamux)))] +pub type Request = (); + +#[cfg(dmamux)] +pub trait Channel: sealed::Channel + Peripheral

+ 'static + super::dmamux::MuxChannel {} +#[cfg(not(dmamux))] +pub trait Channel: sealed::Channel + Peripheral

+ 'static {} + +pub(crate) mod sealed { use super::*; - pub unsafe fn start_transfer( - dma: pac::bdma::Dma, - channel_number: u8, - #[cfg(any(bdma_v2, dmamux))] request: Request, - dir: vals::Dir, + pub trait Channel { + fn regs(&self) -> pac::bdma::Dma; + fn num(&self) -> usize; + fn index(&self) -> usize; + fn on_irq(); + } +} + +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Transfer<'a, C: Channel> { + channel: PeripheralRef<'a, C>, +} + +impl<'a, C: Channel> Transfer<'a, C> { + pub unsafe fn new_read( + channel: impl Peripheral

+ 'a, + request: Request, + peri_addr: *mut W, + buf: &'a mut [W], + options: TransferOptions, + ) -> Self { + Self::new_read_raw(channel, request, peri_addr, buf, options) + } + + pub unsafe fn new_read_raw( + channel: impl Peripheral

+ 'a, + request: Request, + peri_addr: *mut W, + buf: *mut [W], + options: TransferOptions, + ) -> Self { + into_ref!(channel); + + let (ptr, len) = super::slice_ptr_parts_mut(buf); + assert!(len > 0 && len <= 0xFFFF); + + Self::new_inner( + channel, + request, + Dir::PeripheralToMemory, + peri_addr as *const u32, + ptr as *mut u32, + len, + true, + W::size(), + options, + ) + } + + pub unsafe fn new_write( + channel: impl Peripheral

+ 'a, + request: Request, + buf: &'a [W], + peri_addr: *mut W, + options: TransferOptions, + ) -> Self { + Self::new_write_raw(channel, request, buf, peri_addr, options) + } + + pub unsafe fn new_write_raw( + channel: impl Peripheral

+ 'a, + request: Request, + buf: *const [W], + peri_addr: *mut W, + options: TransferOptions, + ) -> Self { + into_ref!(channel); + + let (ptr, len) = super::slice_ptr_parts(buf); + assert!(len > 0 && len <= 0xFFFF); + + Self::new_inner( + channel, + request, + Dir::MemoryToPeripheral, + peri_addr as *const u32, + ptr as *mut u32, + len, + true, + W::size(), + options, + ) + } + + pub unsafe fn new_write_repeated( + channel: impl Peripheral

+ 'a, + request: Request, + repeated: &'a W, + count: usize, + peri_addr: *mut W, + options: TransferOptions, + ) -> Self { + into_ref!(channel); + + Self::new_inner( + channel, + request, + Dir::MemoryToPeripheral, + peri_addr as *const u32, + repeated as *const W as *mut u32, + count, + false, + W::size(), + options, + ) + } + + unsafe fn new_inner( + channel: PeripheralRef<'a, C>, + _request: Request, + dir: Dir, peri_addr: *const u32, mem_addr: *mut u32, mem_len: usize, incr_mem: bool, - data_size: vals::Size, - options: TransferOptions, - #[cfg(dmamux)] dmamux_regs: pac::dmamux::Dmamux, - #[cfg(dmamux)] dmamux_ch_num: u8, - ) { - assert!(options.mburst == crate::dma::Burst::Single, "Burst mode not supported"); - assert!(options.pburst == crate::dma::Burst::Single, "Burst mode not supported"); - assert!( - options.flow_ctrl == crate::dma::FlowControl::Dma, - "Peripheral flow control not supported" - ); - assert!(options.fifo_threshold.is_none(), "FIFO mode not supported"); - - let ch = dma.ch(channel_number as _); - - reset_status(dma, channel_number); - - #[cfg(dmamux)] - super::super::dmamux::configure_dmamux(dmamux_regs, dmamux_ch_num, request); - - #[cfg(bdma_v2)] - critical_section::with(|_| dma.cselr().modify(|w| w.set_cs(channel_number as _, request))); + data_size: WordSize, + _options: TransferOptions, + ) -> Self { + let ch = channel.regs().ch(channel.num()); // "Preceding reads and writes cannot be moved past subsequent writes." fence(Ordering::SeqCst); + #[cfg(bdma_v2)] + critical_section::with(|_| channel.regs().cselr().modify(|w| w.set_cs(channel.num(), _request))); + + let mut this = Self { channel }; + this.clear_irqs(); + + #[cfg(dmamux)] + super::dmamux::configure_dmamux(&mut *this.channel, _request); + ch.par().write_value(peri_addr as u32); ch.mar().write_value(mem_addr as u32); ch.ndtr().write(|w| w.set_ndt(mem_len as u16)); ch.cr().write(|w| { - w.set_psize(data_size); - w.set_msize(data_size); + w.set_psize(data_size.into()); + w.set_msize(data_size.into()); if incr_mem { w.set_minc(vals::Inc::ENABLED); } else { w.set_minc(vals::Inc::DISABLED); } - w.set_dir(dir); + w.set_dir(dir.into()); w.set_teie(true); w.set_tcie(true); w.set_en(true); }); + + this } - pub unsafe fn request_stop(dma: pac::bdma::Dma, channel_number: u8) { - reset_status(dma, channel_number); - - let ch = dma.ch(channel_number as _); - - // Disable the channel and interrupts with the default value. - ch.cr().write(|_| ()); - - // "Subsequent reads and writes cannot be moved ahead of preceding reads." - fence(Ordering::SeqCst); + fn clear_irqs(&mut self) { + unsafe { + self.channel.regs().ifcr().write(|w| { + w.set_tcif(self.channel.num(), true); + w.set_teif(self.channel.num(), true); + }) + } } - pub unsafe fn is_running(dma: pac::bdma::Dma, ch: u8) -> bool { - let ch = dma.ch(ch as _); - ch.cr().read().en() + pub fn request_stop(&mut self) { + let ch = self.channel.regs().ch(self.channel.num()); + + // Disable the channel. Keep the IEs enabled so the irqs still fire. + unsafe { + ch.cr().write(|w| { + w.set_teie(true); + w.set_tcie(true); + }) + } + } + + pub fn is_running(&mut self) -> bool { + let ch = self.channel.regs().ch(self.channel.num()); + unsafe { ch.cr().read() }.en() } /// Gets the total remaining transfers for the channel /// Note: this will be zero for transfers that completed without cancellation. - pub unsafe fn get_remaining_transfers(dma: pac::bdma::Dma, ch: u8) -> u16 { - // get a handle on the channel itself - let ch = dma.ch(ch as _); - // read the remaining transfer count. If this is zero, the transfer completed fully. - ch.ndtr().read().ndt() as u16 + pub fn get_remaining_transfers(&self) -> u16 { + let ch = self.channel.regs().ch(self.channel.num()); + unsafe { ch.ndtr().read() }.ndt() } - /// Sets the waker for the specified DMA channel - pub unsafe fn set_waker(state_number: usize, waker: &Waker) { - STATE.ch_wakers[state_number].register(waker); + pub fn blocking_wait(mut self) { + while self.is_running() {} + + // "Subsequent reads and writes cannot be moved ahead of preceding reads." + fence(Ordering::SeqCst); + + core::mem::forget(self); } +} - pub unsafe fn reset_status(dma: pac::bdma::Dma, channel_number: u8) { - dma.ifcr().write(|w| { - w.set_tcif(channel_number as _, true); - w.set_teif(channel_number as _, true); - }); +impl<'a, C: Channel> Drop for Transfer<'a, C> { + fn drop(&mut self) { + self.request_stop(); + while self.is_running() {} + + // "Subsequent reads and writes cannot be moved ahead of preceding reads." + fence(Ordering::SeqCst); } +} - /// Safety: Must be called with a matching set of parameters for a valid dma channel - pub unsafe fn on_irq_inner(dma: pac::bdma::Dma, channel_num: u8, index: u8) { - let channel_num = channel_num as usize; - let index = index as usize; +impl<'a, C: Channel> Unpin for Transfer<'a, C> {} +impl<'a, C: Channel> Future for Transfer<'a, C> { + type Output = (); + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + STATE.ch_wakers[self.channel.index()].register(cx.waker()); - let isr = dma.isr().read(); - let cr = dma.ch(channel_num).cr(); - - if isr.teif(channel_num) { - panic!("DMA: error on BDMA@{:08x} channel {}", dma.0 as u32, channel_num); - } - if isr.tcif(channel_num) && cr.read().tcie() { - cr.write(|_| ()); // Disable channel interrupts with the default value. - STATE.ch_wakers[index].wake(); + if self.is_running() { + Poll::Pending + } else { + Poll::Ready(()) } } } diff --git a/embassy-stm32/src/dma/dma.rs b/embassy-stm32/src/dma/dma.rs index 59937f4b..ef1d2757 100644 --- a/embassy-stm32/src/dma/dma.rs +++ b/embassy-stm32/src/dma/dma.rs @@ -1,15 +1,46 @@ +use core::future::Future; +use core::marker::PhantomData; +use core::pin::Pin; use core::sync::atomic::{fence, Ordering}; -use core::task::Waker; +use core::task::{Context, Poll, Waker}; use embassy_cortex_m::interrupt::Priority; +use embassy_hal_common::{into_ref, Peripheral, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; +use pac::dma::regs; -use super::{Burst, FifoThreshold, FlowControl, Request, TransferOptions, Word, WordSize}; +use super::word::{Word, WordSize}; +use super::Dir; use crate::_generated::DMA_CHANNEL_COUNT; use crate::interrupt::{Interrupt, InterruptExt}; -use crate::pac::dma::{regs, vals}; +use crate::pac::dma::vals; use crate::{interrupt, pac}; +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct TransferOptions { + /// Peripheral burst transfer configuration + pub pburst: Burst, + /// Memory burst transfer configuration + pub mburst: Burst, + /// Flow control configuration + pub flow_ctrl: FlowControl, + /// FIFO threshold for DMA FIFO mode. If none, direct mode is used. + pub fifo_threshold: Option, +} + +impl Default for TransferOptions { + fn default() -> Self { + Self { + pburst: Burst::Single, + mburst: Burst::Single, + flow_ctrl: FlowControl::Dma, + fifo_threshold: None, + } + } +} + impl From for vals::Size { fn from(raw: WordSize) -> Self { match raw { @@ -20,6 +51,28 @@ impl From for vals::Size { } } +impl From

for vals::Dir { + fn from(raw: Dir) -> Self { + match raw { + Dir::MemoryToPeripheral => Self::MEMORYTOPERIPHERAL, + Dir::PeripheralToMemory => Self::PERIPHERALTOMEMORY, + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Burst { + /// Single transfer + Single, + /// Incremental burst of 4 beats + Incr4, + /// Incremental burst of 8 beats + Incr8, + /// Incremental burst of 16 beats + Incr16, +} + impl From for vals::Burst { fn from(burst: Burst) -> Self { match burst { @@ -31,6 +84,15 @@ impl From for vals::Burst { } } +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum FlowControl { + /// Flow control by DMA + Dma, + /// Flow control by peripheral + Peripheral, +} + impl From for vals::Pfctrl { fn from(flow: FlowControl) -> Self { match flow { @@ -40,6 +102,19 @@ impl From for vals::Pfctrl { } } +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum FifoThreshold { + /// 1/4 full FIFO + Quarter, + /// 1/2 full FIFO + Half, + /// 3/4 full FIFO + ThreeQuarters, + /// Full FIFO + Full, +} + impl From for vals::Fth { fn from(value: FifoThreshold) -> Self { match value { @@ -51,27 +126,15 @@ impl From for vals::Fth { } } -struct ChannelState { - waker: AtomicWaker, -} - -impl ChannelState { - const fn new() -> Self { - Self { - waker: AtomicWaker::new(), - } - } -} - struct State { - channels: [ChannelState; DMA_CHANNEL_COUNT], + ch_wakers: [AtomicWaker; DMA_CHANNEL_COUNT], } impl State { const fn new() -> Self { - const CH: ChannelState = ChannelState::new(); + const AW: AtomicWaker = AtomicWaker::new(); Self { - channels: [CH; DMA_CHANNEL_COUNT], + ch_wakers: [AW; DMA_CHANNEL_COUNT], } } } @@ -92,158 +155,183 @@ pub(crate) unsafe fn init(irq_priority: Priority) { foreach_dma_channel! { ($channel_peri:ident, $dma_peri:ident, dma, $channel_num:expr, $index:expr, $dmamux:tt) => { - impl crate::dma::sealed::Channel for crate::peripherals::$channel_peri { - unsafe fn start_write(&mut self, request: Request, buf: *const [W], reg_addr: *mut W, options: TransferOptions) { - let (ptr, len) = super::slice_ptr_parts(buf); - low_level_api::start_transfer( - pac::$dma_peri, - $channel_num, - request, - vals::Dir::MEMORYTOPERIPHERAL, - reg_addr as *const u32, - ptr as *mut u32, - len, - true, - vals::Size::from(W::bits()), - options, - #[cfg(dmamux)] - ::DMAMUX_REGS, - #[cfg(dmamux)] - ::DMAMUX_CH_NUM, - ) + impl sealed::Channel for crate::peripherals::$channel_peri { + fn regs(&self) -> pac::dma::Dma { + pac::$dma_peri } - - unsafe fn start_write_repeated(&mut self, request: Request, repeated: *const W, count: usize, reg_addr: *mut W, options: TransferOptions) { - low_level_api::start_transfer( - pac::$dma_peri, - $channel_num, - request, - vals::Dir::MEMORYTOPERIPHERAL, - reg_addr as *const u32, - repeated as *mut u32, - count, - false, - vals::Size::from(W::bits()), - options, - #[cfg(dmamux)] - ::DMAMUX_REGS, - #[cfg(dmamux)] - ::DMAMUX_CH_NUM, - ) + fn num(&self) -> usize { + $channel_num } - - unsafe fn start_read(&mut self, request: Request, reg_addr: *const W, buf: *mut [W], options: TransferOptions) { - let (ptr, len) = super::slice_ptr_parts_mut(buf); - low_level_api::start_transfer( - pac::$dma_peri, - $channel_num, - request, - vals::Dir::PERIPHERALTOMEMORY, - reg_addr as *const u32, - ptr as *mut u32, - len, - true, - vals::Size::from(W::bits()), - options, - #[cfg(dmamux)] - ::DMAMUX_REGS, - #[cfg(dmamux)] - ::DMAMUX_CH_NUM, - ); + fn index(&self) -> usize { + $index } - - unsafe fn start_double_buffered_read( - &mut self, - request: Request, - reg_addr: *const W, - buffer0: *mut W, - buffer1: *mut W, - buffer_len: usize, - options: TransferOptions, - ) { - low_level_api::start_dbm_transfer( - pac::$dma_peri, - $channel_num, - request, - vals::Dir::PERIPHERALTOMEMORY, - reg_addr as *const u32, - buffer0 as *mut u32, - buffer1 as *mut u32, - buffer_len, - true, - vals::Size::from(W::bits()), - options, - #[cfg(dmamux)] - ::DMAMUX_REGS, - #[cfg(dmamux)] - ::DMAMUX_CH_NUM, - ); - } - - unsafe fn set_buffer0(&mut self, buffer: *mut W) { - low_level_api::set_dbm_buffer0(pac::$dma_peri, $channel_num, buffer as *mut u32); - } - - unsafe fn set_buffer1(&mut self, buffer: *mut W) { - low_level_api::set_dbm_buffer1(pac::$dma_peri, $channel_num, buffer as *mut u32); - } - - unsafe fn is_buffer0_accessible(&mut self) -> bool { - low_level_api::is_buffer0_accessible(pac::$dma_peri, $channel_num) - } - - fn request_stop(&mut self) { - unsafe {low_level_api::request_stop(pac::$dma_peri, $channel_num);} - } - - fn is_running(&self) -> bool { - unsafe {low_level_api::is_running(pac::$dma_peri, $channel_num)} - } - - fn remaining_transfers(&mut self) -> u16 { - unsafe {low_level_api::get_remaining_transfers(pac::$dma_peri, $channel_num)} - } - - fn set_waker(&mut self, waker: &Waker) { - unsafe {low_level_api::set_waker($index, waker )} - } - fn on_irq() { - unsafe { - low_level_api::on_irq_inner(pac::$dma_peri, $channel_num, $index); - } + unsafe { on_irq_inner(pac::$dma_peri, $channel_num, $index) } } } - impl crate::dma::Channel for crate::peripherals::$channel_peri { } + + impl Channel for crate::peripherals::$channel_peri {} }; } -mod low_level_api { +/// Safety: Must be called with a matching set of parameters for a valid dma channel +pub(crate) unsafe fn on_irq_inner(dma: pac::dma::Dma, channel_num: usize, index: usize) { + let cr = dma.st(channel_num).cr(); + let isr = dma.isr(channel_num / 4).read(); + + if isr.teif(channel_num % 4) { + panic!("DMA: error on DMA@{:08x} channel {}", dma.0 as u32, channel_num); + } + + if isr.tcif(channel_num % 4) && cr.read().tcie() { + /* acknowledge transfer complete interrupt */ + dma.ifcr(channel_num / 4).write(|w| w.set_tcif(channel_num % 4, true)); + STATE.ch_wakers[index].wake(); + } +} + +#[cfg(any(dma_v2, dmamux))] +pub type Request = u8; +#[cfg(not(any(dma_v2, dmamux)))] +pub type Request = (); + +#[cfg(dmamux)] +pub trait Channel: sealed::Channel + Peripheral

+ 'static + super::dmamux::MuxChannel {} +#[cfg(not(dmamux))] +pub trait Channel: sealed::Channel + Peripheral

+ 'static {} + +pub(crate) mod sealed { use super::*; - pub unsafe fn start_transfer( - dma: pac::dma::Dma, - channel_number: u8, + pub trait Channel { + fn regs(&self) -> pac::dma::Dma; + fn num(&self) -> usize; + fn index(&self) -> usize; + fn on_irq(); + } +} + +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Transfer<'a, C: Channel> { + channel: PeripheralRef<'a, C>, +} + +impl<'a, C: Channel> Transfer<'a, C> { + pub unsafe fn new_read( + channel: impl Peripheral

+ 'a, request: Request, - dir: vals::Dir, + peri_addr: *mut W, + buf: &'a mut [W], + options: TransferOptions, + ) -> Self { + Self::new_read_raw(channel, request, peri_addr, buf, options) + } + + pub unsafe fn new_read_raw( + channel: impl Peripheral

+ 'a, + request: Request, + peri_addr: *mut W, + buf: *mut [W], + options: TransferOptions, + ) -> Self { + into_ref!(channel); + + let (ptr, len) = super::slice_ptr_parts_mut(buf); + assert!(len > 0 && len <= 0xFFFF); + + Self::new_inner( + channel, + request, + Dir::PeripheralToMemory, + peri_addr as *const u32, + ptr as *mut u32, + len, + true, + W::size(), + options, + ) + } + + pub unsafe fn new_write( + channel: impl Peripheral

+ 'a, + request: Request, + buf: &'a [W], + peri_addr: *mut W, + options: TransferOptions, + ) -> Self { + Self::new_write_raw(channel, request, buf, peri_addr, options) + } + + pub unsafe fn new_write_raw( + channel: impl Peripheral

+ 'a, + request: Request, + buf: *const [W], + peri_addr: *mut W, + options: TransferOptions, + ) -> Self { + into_ref!(channel); + + let (ptr, len) = super::slice_ptr_parts(buf); + assert!(len > 0 && len <= 0xFFFF); + + Self::new_inner( + channel, + request, + Dir::MemoryToPeripheral, + peri_addr as *const u32, + ptr as *mut u32, + len, + true, + W::size(), + options, + ) + } + + pub unsafe fn new_write_repeated( + channel: impl Peripheral

+ 'a, + request: Request, + repeated: &'a W, + count: usize, + peri_addr: *mut W, + options: TransferOptions, + ) -> Self { + into_ref!(channel); + + Self::new_inner( + channel, + request, + Dir::MemoryToPeripheral, + peri_addr as *const u32, + repeated as *const W as *mut u32, + count, + false, + W::size(), + options, + ) + } + + unsafe fn new_inner( + channel: PeripheralRef<'a, C>, + _request: Request, + dir: Dir, peri_addr: *const u32, mem_addr: *mut u32, mem_len: usize, incr_mem: bool, - data_size: vals::Size, + data_size: WordSize, options: TransferOptions, - #[cfg(dmamux)] dmamux_regs: pac::dmamux::Dmamux, - #[cfg(dmamux)] dmamux_ch_num: u8, - ) { - #[cfg(dmamux)] - super::super::dmamux::configure_dmamux(dmamux_regs, dmamux_ch_num, request); + ) -> Self { + let ch = channel.regs().st(channel.num()); // "Preceding reads and writes cannot be moved past subsequent writes." fence(Ordering::SeqCst); - reset_status(dma, channel_number); + let mut this = Self { channel }; + this.clear_irqs(); + + #[cfg(dmamux)] + super::dmamux::configure_dmamux(&mut *this.channel, _request); - let ch = dma.st(channel_number as _); ch.par().write_value(peri_addr as u32); ch.m0ar().write_value(mem_addr as u32); ch.ndtr().write_value(regs::Ndtr(mem_len as _)); @@ -258,15 +346,14 @@ mod low_level_api { } }); ch.cr().write(|w| { - w.set_dir(dir); - w.set_msize(data_size); - w.set_psize(data_size); + w.set_dir(dir.into()); + w.set_msize(data_size.into()); + w.set_psize(data_size.into()); w.set_pl(vals::Pl::VERYHIGH); - if incr_mem { - w.set_minc(vals::Inc::INCREMENTED); - } else { - w.set_minc(vals::Inc::FIXED); - } + w.set_minc(match incr_mem { + true => vals::Inc::INCREMENTED, + false => vals::Inc::FIXED, + }); w.set_pinc(vals::Inc::FIXED); w.set_teie(true); w.set_tcie(true); @@ -274,7 +361,7 @@ mod low_level_api { w.set_trbuff(true); #[cfg(dma_v2)] - w.set_chsel(request); + w.set_chsel(_request); w.set_pburst(options.pburst.into()); w.set_mburst(options.mburst.into()); @@ -282,159 +369,232 @@ mod low_level_api { w.set_en(true); }); + + this } - pub unsafe fn start_dbm_transfer( - dma: pac::dma::Dma, - channel_number: u8, - request: Request, - dir: vals::Dir, - peri_addr: *const u32, - mem0_addr: *mut u32, - mem1_addr: *mut u32, - mem_len: usize, - incr_mem: bool, - data_size: vals::Size, - options: TransferOptions, - #[cfg(dmamux)] dmamux_regs: pac::dmamux::Dmamux, - #[cfg(dmamux)] dmamux_ch_num: u8, - ) { - #[cfg(dmamux)] - super::super::dmamux::configure_dmamux(dmamux_regs, dmamux_ch_num, request); + fn clear_irqs(&mut self) { + let isrn = self.channel.num() / 4; + let isrbit = self.channel.num() % 4; - trace!( - "Starting DBM transfer with 0: 0x{:x}, 1: 0x{:x}, len: 0x{:x}", - mem0_addr as u32, - mem1_addr as u32, - mem_len - ); - - // "Preceding reads and writes cannot be moved past subsequent writes." - fence(Ordering::SeqCst); - - reset_status(dma, channel_number); - - let ch = dma.st(channel_number as _); - ch.par().write_value(peri_addr as u32); - ch.m0ar().write_value(mem0_addr as u32); - // configures the second buffer for DBM - ch.m1ar().write_value(mem1_addr as u32); - ch.ndtr().write_value(regs::Ndtr(mem_len as _)); - ch.cr().write(|w| { - w.set_dir(dir); - w.set_msize(data_size); - w.set_psize(data_size); - w.set_pl(vals::Pl::VERYHIGH); - if incr_mem { - w.set_minc(vals::Inc::INCREMENTED); - } else { - w.set_minc(vals::Inc::FIXED); - } - w.set_pinc(vals::Inc::FIXED); - w.set_teie(true); - w.set_tcie(true); - - #[cfg(dma_v1)] - w.set_trbuff(true); - - #[cfg(dma_v2)] - w.set_chsel(request); - - // enable double buffered mode - w.set_dbm(vals::Dbm::ENABLED); - - w.set_pburst(options.pburst.into()); - w.set_mburst(options.mburst.into()); - w.set_pfctrl(options.flow_ctrl.into()); - - w.set_en(true); - }); + unsafe { + self.channel.regs().ifcr(isrn).write(|w| { + w.set_tcif(isrbit, true); + w.set_teif(isrbit, true); + }) + } } - pub unsafe fn set_dbm_buffer0(dma: pac::dma::Dma, channel_number: u8, mem_addr: *mut u32) { - // get a handle on the channel itself - let ch = dma.st(channel_number as _); - // change M0AR to the new address - ch.m0ar().write_value(mem_addr as _); - } - - pub unsafe fn set_dbm_buffer1(dma: pac::dma::Dma, channel_number: u8, mem_addr: *mut u32) { - // get a handle on the channel itself - let ch = dma.st(channel_number as _); - // change M1AR to the new address - ch.m1ar().write_value(mem_addr as _); - } - - pub unsafe fn is_buffer0_accessible(dma: pac::dma::Dma, channel_number: u8) -> bool { - // get a handle on the channel itself - let ch = dma.st(channel_number as _); - // check the current target register value - ch.cr().read().ct() == vals::Ct::MEMORY1 - } - - /// Stops the DMA channel. - pub unsafe fn request_stop(dma: pac::dma::Dma, channel_number: u8) { - // get a handle on the channel itself - let ch = dma.st(channel_number as _); + pub fn request_stop(&mut self) { + let ch = self.channel.regs().st(self.channel.num()); // Disable the channel. Keep the IEs enabled so the irqs still fire. - ch.cr().write(|w| { - w.set_teie(true); - w.set_tcie(true); - }); - - // "Subsequent reads and writes cannot be moved ahead of preceding reads." - fence(Ordering::SeqCst); + unsafe { + ch.cr().write(|w| { + w.set_teie(true); + w.set_tcie(true); + }) + } } - /// Gets the running status of the channel - pub unsafe fn is_running(dma: pac::dma::Dma, ch: u8) -> bool { - // get a handle on the channel itself - let ch = dma.st(ch as _); - // Get whether it's enabled (running) - ch.cr().read().en() + pub fn is_running(&mut self) -> bool { + let ch = self.channel.regs().st(self.channel.num()); + unsafe { ch.cr().read() }.en() } /// Gets the total remaining transfers for the channel /// Note: this will be zero for transfers that completed without cancellation. - pub unsafe fn get_remaining_transfers(dma: pac::dma::Dma, ch: u8) -> u16 { - // get a handle on the channel itself - let ch = dma.st(ch as _); - // read the remaining transfer count. If this is zero, the transfer completed fully. - ch.ndtr().read().ndt() + pub fn get_remaining_transfers(&self) -> u16 { + let ch = self.channel.regs().st(self.channel.num()); + unsafe { ch.ndtr().read() }.ndt() } - /// Sets the waker for the specified DMA channel - pub unsafe fn set_waker(state_number: usize, waker: &Waker) { - STATE.channels[state_number].waker.register(waker); + pub fn blocking_wait(mut self) { + while self.is_running() {} + + // "Subsequent reads and writes cannot be moved ahead of preceding reads." + fence(Ordering::SeqCst); + + core::mem::forget(self); } +} - pub unsafe fn reset_status(dma: pac::dma::Dma, channel_number: u8) { - let isrn = channel_number as usize / 4; - let isrbit = channel_number as usize % 4; +impl<'a, C: Channel> Drop for Transfer<'a, C> { + fn drop(&mut self) { + self.request_stop(); + while self.is_running() {} - dma.ifcr(isrn).write(|w| { - w.set_tcif(isrbit, true); - w.set_teif(isrbit, true); - }); + // "Subsequent reads and writes cannot be moved ahead of preceding reads." + fence(Ordering::SeqCst); } +} - /// Safety: Must be called with a matching set of parameters for a valid dma channel - pub unsafe fn on_irq_inner(dma: pac::dma::Dma, channel_num: u8, state_index: u8) { - let channel_num = channel_num as usize; - let state_index = state_index as usize; +impl<'a, C: Channel> Unpin for Transfer<'a, C> {} +impl<'a, C: Channel> Future for Transfer<'a, C> { + type Output = (); + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + STATE.ch_wakers[self.channel.index()].register(cx.waker()); - let cr = dma.st(channel_num).cr(); - let isr = dma.isr(channel_num / 4).read(); - - if isr.teif(channel_num % 4) { - panic!("DMA: error on DMA@{:08x} channel {}", dma.0 as u32, channel_num); - } - - if isr.tcif(channel_num % 4) && cr.read().tcie() { - /* acknowledge transfer complete interrupt */ - dma.ifcr(channel_num / 4).write(|w| w.set_tcif(channel_num % 4, true)); - STATE.channels[state_index].waker.wake(); + if self.is_running() { + Poll::Pending + } else { + Poll::Ready(()) } } } + +// ================================== + +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct DoubleBuffered<'a, C: Channel, W: Word> { + channel: PeripheralRef<'a, C>, + _phantom: PhantomData, +} + +impl<'a, C: Channel, W: Word> DoubleBuffered<'a, C, W> { + pub unsafe fn new_read( + channel: impl Peripheral

+ 'a, + _request: Request, + peri_addr: *mut W, + buf0: *mut W, + buf1: *mut W, + len: usize, + options: TransferOptions, + ) -> Self { + into_ref!(channel); + assert!(len > 0 && len <= 0xFFFF); + + let dir = Dir::PeripheralToMemory; + let data_size = W::size(); + + let channel_number = channel.num(); + let dma = channel.regs(); + + // "Preceding reads and writes cannot be moved past subsequent writes." + fence(Ordering::SeqCst); + + let mut this = Self { + channel, + _phantom: PhantomData, + }; + this.clear_irqs(); + + #[cfg(dmamux)] + super::dmamux::configure_dmamux(&mut *this.channel, _request); + + let ch = dma.st(channel_number); + ch.par().write_value(peri_addr as u32); + ch.m0ar().write_value(buf0 as u32); + ch.m1ar().write_value(buf1 as u32); + ch.ndtr().write_value(regs::Ndtr(len as _)); + ch.fcr().write(|w| { + if let Some(fth) = options.fifo_threshold { + // FIFO mode + w.set_dmdis(vals::Dmdis::DISABLED); + w.set_fth(fth.into()); + } else { + // Direct mode + w.set_dmdis(vals::Dmdis::ENABLED); + } + }); + ch.cr().write(|w| { + w.set_dir(dir.into()); + w.set_msize(data_size.into()); + w.set_psize(data_size.into()); + w.set_pl(vals::Pl::VERYHIGH); + w.set_minc(vals::Inc::INCREMENTED); + w.set_pinc(vals::Inc::FIXED); + w.set_teie(true); + w.set_tcie(true); + #[cfg(dma_v1)] + w.set_trbuff(true); + + #[cfg(dma_v2)] + w.set_chsel(_request); + + w.set_pburst(options.pburst.into()); + w.set_mburst(options.mburst.into()); + w.set_pfctrl(options.flow_ctrl.into()); + + w.set_en(true); + }); + + this + } + + fn clear_irqs(&mut self) { + let channel_number = self.channel.num(); + let dma = self.channel.regs(); + let isrn = channel_number / 4; + let isrbit = channel_number % 4; + + unsafe { + dma.ifcr(isrn).write(|w| { + w.set_tcif(isrbit, true); + w.set_teif(isrbit, true); + }) + } + } + + pub unsafe fn set_buffer0(&mut self, buffer: *mut W) { + let ch = self.channel.regs().st(self.channel.num()); + ch.m0ar().write_value(buffer as _); + } + + pub unsafe fn set_buffer1(&mut self, buffer: *mut W) { + let ch = self.channel.regs().st(self.channel.num()); + ch.m1ar().write_value(buffer as _); + } + + pub fn is_buffer0_accessible(&mut self) -> bool { + let ch = self.channel.regs().st(self.channel.num()); + unsafe { ch.cr().read() }.ct() == vals::Ct::MEMORY1 + } + + pub fn set_waker(&mut self, waker: &Waker) { + STATE.ch_wakers[self.channel.index()].register(waker); + } + + pub fn request_stop(&mut self) { + let ch = self.channel.regs().st(self.channel.num()); + + // Disable the channel. Keep the IEs enabled so the irqs still fire. + unsafe { + ch.cr().write(|w| { + w.set_teie(true); + w.set_tcie(true); + }) + } + } + + pub fn is_running(&mut self) -> bool { + let ch = self.channel.regs().st(self.channel.num()); + unsafe { ch.cr().read() }.en() + } + + /// Gets the total remaining transfers for the channel + /// Note: this will be zero for transfers that completed without cancellation. + pub fn get_remaining_transfers(&self) -> u16 { + let ch = self.channel.regs().st(self.channel.num()); + unsafe { ch.ndtr().read() }.ndt() + } + + pub fn blocking_wait(mut self) { + while self.is_running() {} + + // "Subsequent reads and writes cannot be moved ahead of preceding reads." + fence(Ordering::SeqCst); + + core::mem::forget(self); + } +} + +impl<'a, C: Channel, W: Word> Drop for DoubleBuffered<'a, C, W> { + fn drop(&mut self) { + self.request_stop(); + while self.is_running() {} + + // "Subsequent reads and writes cannot be moved ahead of preceding reads." + fence(Ordering::SeqCst); + } +} diff --git a/embassy-stm32/src/dma/dmamux.rs b/embassy-stm32/src/dma/dmamux.rs index e9967e34..a8c4c582 100644 --- a/embassy-stm32/src/dma/dmamux.rs +++ b/embassy-stm32/src/dma/dmamux.rs @@ -2,8 +2,8 @@ use crate::{pac, peripherals}; -pub(crate) unsafe fn configure_dmamux(dmamux_regs: pac::dmamux::Dmamux, dmamux_ch_num: u8, request: u8) { - let ch_mux_regs = dmamux_regs.ccr(dmamux_ch_num as _); +pub(crate) unsafe fn configure_dmamux(channel: &mut M, request: u8) { + let ch_mux_regs = channel.mux_regs().ccr(channel.mux_num()); ch_mux_regs.write(|reg| { reg.set_nbreq(0); reg.set_dmareq_id(request); @@ -14,11 +14,11 @@ pub(crate) unsafe fn configure_dmamux(dmamux_regs: pac::dmamux::Dmamux, dmamux_c }); } -pub(crate) mod sealed { +pub(crate) mod dmamux_sealed { use super::*; pub trait MuxChannel { - const DMAMUX_CH_NUM: u8; - const DMAMUX_REGS: pac::dmamux::Dmamux; + fn mux_regs(&self) -> pac::dmamux::Dmamux; + fn mux_num(&self) -> usize; } } @@ -26,15 +26,19 @@ pub struct DMAMUX1; #[cfg(stm32h7)] pub struct DMAMUX2; -pub trait MuxChannel: sealed::MuxChannel + super::Channel { +pub trait MuxChannel: dmamux_sealed::MuxChannel { type Mux; } foreach_dma_channel! { ($channel_peri:ident, $dma_peri:ident, $version:ident, $channel_num:expr, $index:expr, {dmamux: $dmamux:ident, dmamux_channel: $dmamux_channel:expr}) => { - impl sealed::MuxChannel for peripherals::$channel_peri { - const DMAMUX_CH_NUM: u8 = $dmamux_channel; - const DMAMUX_REGS: pac::dmamux::Dmamux = pac::$dmamux; + impl dmamux_sealed::MuxChannel for peripherals::$channel_peri { + fn mux_regs(&self) -> pac::dmamux::Dmamux { + pac::$dmamux + } + fn mux_num(&self) -> usize { + $dmamux_channel + } } impl MuxChannel for peripherals::$channel_peri { type Mux = $dmamux; diff --git a/embassy-stm32/src/dma/gpdma.rs b/embassy-stm32/src/dma/gpdma.rs index 6f26fd19..5a516ccd 100644 --- a/embassy-stm32/src/dma/gpdma.rs +++ b/embassy-stm32/src/dma/gpdma.rs @@ -1,13 +1,31 @@ -use core::sync::atomic::{fence, Ordering}; -use core::task::Waker; +#![macro_use] +use core::future::Future; +use core::pin::Pin; +use core::sync::atomic::{fence, Ordering}; +use core::task::{Context, Poll}; + +use embassy_cortex_m::interrupt::Priority; +use embassy_hal_common::{into_ref, Peripheral, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; -use super::{Request, TransferOptions, Word, WordSize}; +use super::word::{Word, WordSize}; +use super::Dir; use crate::_generated::GPDMA_CHANNEL_COUNT; use crate::interrupt::{Interrupt, InterruptExt}; -use crate::pac::gpdma::{vals, Gpdma}; -use crate::{interrupt, pac}; +use crate::pac; +use crate::pac::gpdma::vals; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct TransferOptions {} + +impl Default for TransferOptions { + fn default() -> Self { + Self {} + } +} impl From for vals::ChTr1Dw { fn from(raw: WordSize) -> Self { @@ -19,27 +37,15 @@ impl From for vals::ChTr1Dw { } } -struct ChannelState { - waker: AtomicWaker, -} - -impl ChannelState { - const fn new() -> Self { - Self { - waker: AtomicWaker::new(), - } - } -} - struct State { - channels: [ChannelState; GPDMA_CHANNEL_COUNT], + ch_wakers: [AtomicWaker; GPDMA_CHANNEL_COUNT], } impl State { const fn new() -> Self { - const CH: ChannelState = ChannelState::new(); + const AW: AtomicWaker = AtomicWaker::new(); Self { - channels: [CH; GPDMA_CHANNEL_COUNT], + ch_wakers: [AW; GPDMA_CHANNEL_COUNT], } } } @@ -47,10 +53,12 @@ impl State { static STATE: State = State::new(); /// safety: must be called only once -pub(crate) unsafe fn init() { +pub(crate) unsafe fn init(irq_priority: Priority) { foreach_interrupt! { ($peri:ident, gpdma, $block:ident, $signal_name:ident, $irq:ident) => { - interrupt::$irq::steal().enable(); + let irq = crate::interrupt::$irq::steal(); + irq.set_priority(irq_priority); + irq.enable(); }; } crate::_generated::init_gpdma(); @@ -58,117 +66,171 @@ pub(crate) unsafe fn init() { foreach_dma_channel! { ($channel_peri:ident, $dma_peri:ident, gpdma, $channel_num:expr, $index:expr, $dmamux:tt) => { - impl crate::dma::sealed::Channel for crate::peripherals::$channel_peri { - unsafe fn start_write(&mut self, request: Request, buf: *const [W], reg_addr: *mut W, options: TransferOptions) { - let (ptr, len) = super::slice_ptr_parts(buf); - low_level_api::start_transfer( - pac::$dma_peri, - $channel_num, - request, - low_level_api::Dir::MemoryToPeripheral, - reg_addr as *const u32, - ptr as *mut u32, - len, - true, - W::bits(), - options, - ) + impl sealed::Channel for crate::peripherals::$channel_peri { + fn regs(&self) -> pac::gpdma::Gpdma { + pac::$dma_peri } - - unsafe fn start_write_repeated(&mut self, request: Request, repeated: *const W, count: usize, reg_addr: *mut W, options: TransferOptions) { - low_level_api::start_transfer( - pac::$dma_peri, - $channel_num, - request, - low_level_api::Dir::MemoryToPeripheral, - reg_addr as *const u32, - repeated as *mut u32, - count, - false, - W::bits(), - options, - ) + fn num(&self) -> usize { + $channel_num } - - unsafe fn start_read(&mut self, request: Request, reg_addr: *const W, buf: *mut [W], options: TransferOptions) { - let (ptr, len) = super::slice_ptr_parts_mut(buf); - low_level_api::start_transfer( - pac::$dma_peri, - $channel_num, - request, - low_level_api::Dir::PeripheralToMemory, - reg_addr as *const u32, - ptr as *mut u32, - len, - true, - W::bits(), - options, - ); + fn index(&self) -> usize { + $index } - - unsafe fn start_double_buffered_read( - &mut self, - _request: Request, - _reg_addr: *const W, - _buffer0: *mut W, - _buffer1: *mut W, - _buffer_len: usize, - _options: TransferOptions, - ) { - panic!("Unsafe double buffered mode is unavailable on GPBDMA"); - } - - unsafe fn set_buffer0(&mut self, _buffer: *mut W) { - panic!("Unsafe double buffered mode is unavailable on GPBDMA"); - } - - unsafe fn set_buffer1(&mut self, _buffer: *mut W) { - panic!("Unsafe double buffered mode is unavailable on GPBDMA"); - } - - unsafe fn is_buffer0_accessible(&mut self) -> bool { - panic!("Unsafe double buffered mode is unavailable on GPBDMA"); - } - - fn request_stop(&mut self) { - unsafe {low_level_api::request_stop(pac::$dma_peri, $channel_num);} - } - - fn is_running(&self) -> bool { - unsafe {low_level_api::is_running(pac::$dma_peri, $channel_num)} - } - - fn remaining_transfers(&mut self) -> u16 { - unsafe {low_level_api::get_remaining_transfers(pac::$dma_peri, $channel_num)} - } - - fn set_waker(&mut self, waker: &Waker) { - unsafe {low_level_api::set_waker($index, waker )} - } - fn on_irq() { - unsafe { - low_level_api::on_irq_inner(pac::$dma_peri, $channel_num, $index); - } + unsafe { on_irq_inner(pac::$dma_peri, $channel_num, $index) } } } - impl crate::dma::Channel for crate::peripherals::$channel_peri { } + + impl Channel for crate::peripherals::$channel_peri {} }; } -mod low_level_api { - use super::*; +/// Safety: Must be called with a matching set of parameters for a valid dma channel +pub(crate) unsafe fn on_irq_inner(dma: pac::gpdma::Gpdma, channel_num: usize, index: usize) { + let ch = dma.ch(channel_num); + let sr = ch.sr().read(); - #[derive(Debug, Copy, Clone, PartialEq, Eq)] - #[cfg_attr(feature = "defmt", derive(defmt::Format))] - pub enum Dir { - MemoryToPeripheral, - PeripheralToMemory, + if sr.dtef() { + panic!( + "DMA: data transfer error on DMA@{:08x} channel {}", + dma.0 as u32, channel_num + ); + } + if sr.usef() { + panic!( + "DMA: user settings error on DMA@{:08x} channel {}", + dma.0 as u32, channel_num + ); } - pub unsafe fn start_transfer( - dma: Gpdma, - channel_number: u8, + if sr.suspf() || sr.tcf() { + // disable all xxIEs to prevent the irq from firing again. + ch.cr().write(|_| {}); + + // Wake the future. It'll look at tcf and see it's set. + STATE.ch_wakers[index].wake(); + } +} + +pub type Request = u8; + +#[cfg(dmamux)] +pub trait Channel: sealed::Channel + Peripheral

+ 'static + super::dmamux::MuxChannel {} +#[cfg(not(dmamux))] +pub trait Channel: sealed::Channel + Peripheral

+ 'static {} + +pub(crate) mod sealed { + use super::*; + + pub trait Channel { + fn regs(&self) -> pac::gpdma::Gpdma; + fn num(&self) -> usize; + fn index(&self) -> usize; + fn on_irq(); + } +} + +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Transfer<'a, C: Channel> { + channel: PeripheralRef<'a, C>, +} + +impl<'a, C: Channel> Transfer<'a, C> { + pub unsafe fn new_read( + channel: impl Peripheral

+ 'a, + request: Request, + peri_addr: *mut W, + buf: &'a mut [W], + options: TransferOptions, + ) -> Self { + Self::new_read_raw(channel, request, peri_addr, buf, options) + } + + pub unsafe fn new_read_raw( + channel: impl Peripheral

+ 'a, + request: Request, + peri_addr: *mut W, + buf: *mut [W], + options: TransferOptions, + ) -> Self { + into_ref!(channel); + + let (ptr, len) = super::slice_ptr_parts_mut(buf); + assert!(len > 0 && len <= 0xFFFF); + + Self::new_inner( + channel, + request, + Dir::PeripheralToMemory, + peri_addr as *const u32, + ptr as *mut u32, + len, + true, + W::size(), + options, + ) + } + + pub unsafe fn new_write( + channel: impl Peripheral

+ 'a, + request: Request, + buf: &'a [W], + peri_addr: *mut W, + options: TransferOptions, + ) -> Self { + Self::new_write_raw(channel, request, buf, peri_addr, options) + } + + pub unsafe fn new_write_raw( + channel: impl Peripheral

+ 'a, + request: Request, + buf: *const [W], + peri_addr: *mut W, + options: TransferOptions, + ) -> Self { + into_ref!(channel); + + let (ptr, len) = super::slice_ptr_parts(buf); + assert!(len > 0 && len <= 0xFFFF); + + Self::new_inner( + channel, + request, + Dir::MemoryToPeripheral, + peri_addr as *const u32, + ptr as *mut u32, + len, + true, + W::size(), + options, + ) + } + + pub unsafe fn new_write_repeated( + channel: impl Peripheral

+ 'a, + request: Request, + repeated: &'a W, + count: usize, + peri_addr: *mut W, + options: TransferOptions, + ) -> Self { + into_ref!(channel); + + Self::new_inner( + channel, + request, + Dir::MemoryToPeripheral, + peri_addr as *const u32, + repeated as *const W as *mut u32, + count, + false, + W::size(), + options, + ) + } + + unsafe fn new_inner( + channel: PeripheralRef<'a, C>, request: Request, dir: Dir, peri_addr: *const u32, @@ -176,24 +238,19 @@ mod low_level_api { mem_len: usize, incr_mem: bool, data_size: WordSize, - options: TransferOptions, - ) { - assert!(options.mburst == crate::dma::Burst::Single, "Burst mode not supported"); - assert!(options.pburst == crate::dma::Burst::Single, "Burst mode not supported"); - assert!( - options.flow_ctrl == crate::dma::FlowControl::Dma, - "Peripheral flow control not supported" - ); - assert!(options.fifo_threshold.is_none(), "FIFO mode not supported"); + _options: TransferOptions, + ) -> Self { + let ch = channel.regs().ch(channel.num()); // "Preceding reads and writes cannot be moved past subsequent writes." fence(Ordering::SeqCst); - let ch = dma.ch(channel_number as _); + let this = Self { channel }; + + #[cfg(dmamux)] + super::dmamux::configure_dmamux(&mut *this.channel, request); - // Reset ch ch.cr().write(|w| w.set_reset(true)); - ch.llr().write(|_| {}); // no linked list ch.tr1().write(|w| { w.set_sdw(data_size.into()); @@ -234,72 +291,66 @@ mod low_level_api { // Start it w.set_en(true); }); + + this } - /// Stops the DMA channel. - pub unsafe fn request_stop(dma: Gpdma, channel_number: u8) { - // get a handle on the channel itself - let ch = dma.ch(channel_number as _); + pub fn request_stop(&mut self) { + let ch = self.channel.regs().ch(self.channel.num()); // Disable the channel. Keep the IEs enabled so the irqs still fire. - ch.cr().write(|w| { - w.set_tcie(true); - w.set_useie(true); - w.set_dteie(true); - w.set_suspie(true); - }); - - // "Subsequent reads and writes cannot be moved ahead of preceding reads." - fence(Ordering::SeqCst); + unsafe { + ch.cr().write(|w| { + w.set_tcie(true); + w.set_useie(true); + w.set_dteie(true); + w.set_suspie(true); + }) + } } - /// Gets the running status of the channel - pub unsafe fn is_running(dma: Gpdma, ch: u8) -> bool { - let ch = dma.ch(ch as _); - !ch.sr().read().tcf() + pub fn is_running(&mut self) -> bool { + let ch = self.channel.regs().ch(self.channel.num()); + !unsafe { ch.sr().read() }.tcf() } /// Gets the total remaining transfers for the channel /// Note: this will be zero for transfers that completed without cancellation. - pub unsafe fn get_remaining_transfers(dma: Gpdma, ch: u8) -> u16 { - // get a handle on the channel itself - let ch = dma.ch(ch as _); - // read the remaining transfer count. If this is zero, the transfer completed fully. - ch.br1().read().bndt() + pub fn get_remaining_transfers(&self) -> u16 { + let ch = self.channel.regs().ch(self.channel.num()); + unsafe { ch.br1().read() }.bndt() } - /// Sets the waker for the specified DMA channel - pub unsafe fn set_waker(state_number: usize, waker: &Waker) { - STATE.channels[state_number].waker.register(waker); + pub fn blocking_wait(mut self) { + while self.is_running() {} + + // "Subsequent reads and writes cannot be moved ahead of preceding reads." + fence(Ordering::SeqCst); + + core::mem::forget(self); } +} - /// Safety: Must be called with a matching set of parameters for a valid dma channel - pub unsafe fn on_irq_inner(dma: Gpdma, channel_num: u8, state_index: u8) { - let channel_num = channel_num as usize; - let state_index = state_index as usize; +impl<'a, C: Channel> Drop for Transfer<'a, C> { + fn drop(&mut self) { + self.request_stop(); + while self.is_running() {} - let ch = dma.ch(channel_num); - let sr = ch.sr().read(); + // "Subsequent reads and writes cannot be moved ahead of preceding reads." + fence(Ordering::SeqCst); + } +} - if sr.dtef() { - panic!( - "DMA: data transfer error on DMA@{:08x} channel {}", - dma.0 as u32, channel_num - ); - } - if sr.usef() { - panic!( - "DMA: user settings error on DMA@{:08x} channel {}", - dma.0 as u32, channel_num - ); - } +impl<'a, C: Channel> Unpin for Transfer<'a, C> {} +impl<'a, C: Channel> Future for Transfer<'a, C> { + type Output = (); + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + STATE.ch_wakers[self.channel.index()].register(cx.waker()); - if sr.suspf() || sr.tcf() { - // disable all xxIEs to prevent the irq from firing again. - ch.cr().write(|_| {}); - - // Wake the future. It'll look at tcf and see it's set. - STATE.channels[state_index].waker.wake(); + if self.is_running() { + Poll::Pending + } else { + Poll::Ready(()) } } } diff --git a/embassy-stm32/src/dma/mod.rs b/embassy-stm32/src/dma/mod.rs index 0030bd57..3312ca75 100644 --- a/embassy-stm32/src/dma/mod.rs +++ b/embassy-stm32/src/dma/mod.rs @@ -1,329 +1,47 @@ -#[cfg(bdma)] -pub(crate) mod bdma; #[cfg(dma)] pub(crate) mod dma; +#[cfg(dma)] +pub use dma::*; + +// stm32h7 has both dma and bdma. In that case, we export dma as "main" dma, +// and bdma as "secondary", under `embassy_stm32::dma::bdma`. +#[cfg(all(bdma, dma))] +pub mod bdma; + +#[cfg(all(bdma, not(dma)))] +pub(crate) mod bdma; +#[cfg(all(bdma, not(dma)))] +pub use bdma::*; + +#[cfg(gpdma)] +pub(crate) mod gpdma; +#[cfg(gpdma)] +pub use gpdma::*; + #[cfg(dmamux)] mod dmamux; -#[cfg(gpdma)] -mod gpdma; -use core::future::Future; +pub mod word; + use core::mem; -use core::pin::Pin; -use core::task::{Context, Poll, Waker}; -#[cfg(any(dma, bdma))] use embassy_cortex_m::interrupt::Priority; -use embassy_hal_common::{impl_peripheral, into_ref}; +use embassy_hal_common::impl_peripheral; #[cfg(dmamux)] pub use self::dmamux::*; -use crate::Peripheral; - -#[cfg(feature = "unstable-pac")] -pub mod low_level { - pub use super::transfers::*; -} - -pub(crate) use transfers::*; - -#[cfg(any(bdma_v2, dma_v2, dmamux, gpdma))] -pub type Request = u8; -#[cfg(not(any(bdma_v2, dma_v2, dmamux, gpdma)))] -pub type Request = (); - -pub(crate) mod sealed { - use super::*; - - pub trait Word {} - - pub trait Channel { - /// Starts this channel for writing a stream of words. - /// - /// Safety: - /// - `buf` must point to a valid buffer for DMA reading. - /// - `buf` must be alive for the entire duration of the DMA transfer. - /// - `reg_addr` must be a valid peripheral register address to write to. - unsafe fn start_write( - &mut self, - request: Request, - buf: *const [W], - reg_addr: *mut W, - options: TransferOptions, - ); - - /// Starts this channel for writing a word repeatedly. - /// - /// Safety: - /// - `reg_addr` must be a valid peripheral register address to write to. - unsafe fn start_write_repeated( - &mut self, - request: Request, - repeated: *const W, - count: usize, - reg_addr: *mut W, - options: TransferOptions, - ); - - /// Starts this channel for reading a stream of words. - /// - /// Safety: - /// - `buf` must point to a valid buffer for DMA writing. - /// - `buf` must be alive for the entire duration of the DMA transfer. - /// - `reg_addr` must be a valid peripheral register address to read from. - unsafe fn start_read( - &mut self, - request: Request, - reg_addr: *const W, - buf: *mut [W], - options: TransferOptions, - ); - - /// DMA double-buffered mode is unsafe as UB can happen when the hardware writes to a buffer currently owned by the software - /// more information can be found here: https://github.com/embassy-rs/embassy/issues/702 - /// This feature is now used solely for the purposes of implementing giant DMA transfers required for DCMI - unsafe fn start_double_buffered_read( - &mut self, - request: Request, - reg_addr: *const W, - buffer0: *mut W, - buffer1: *mut W, - buffer_len: usize, - options: TransferOptions, - ); - - unsafe fn set_buffer0(&mut self, buffer: *mut W); - - unsafe fn set_buffer1(&mut self, buffer: *mut W); - - unsafe fn is_buffer0_accessible(&mut self) -> bool; - - /// Requests the channel to stop. - /// NOTE: The channel does not immediately stop, you have to wait - /// for `is_running() = false`. - fn request_stop(&mut self); - - /// Returns whether this channel is running or stopped. - /// - /// The channel stops running when it either completes or is manually stopped. - fn is_running(&self) -> bool; - - /// Returns the total number of remaining transfers. - fn remaining_transfers(&mut self) -> u16; - - /// Sets the waker that is called when this channel stops (either completed or manually stopped) - fn set_waker(&mut self, waker: &Waker); - - /// This is called when this channel triggers an interrupt. - /// Note: Because some channels share an interrupt, this function might be - /// called for a channel that didn't trigger an interrupt. - fn on_irq(); - } -} #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum WordSize { - OneByte, - TwoBytes, - FourBytes, +enum Dir { + MemoryToPeripheral, + PeripheralToMemory, } -impl WordSize { - pub fn bytes(&self) -> usize { - match self { - Self::OneByte => 1, - Self::TwoBytes => 2, - Self::FourBytes => 4, - } - } -} - -pub trait Word: sealed::Word { - fn bits() -> WordSize; -} - -impl sealed::Word for u8 {} -impl Word for u8 { - fn bits() -> WordSize { - WordSize::OneByte - } -} - -impl sealed::Word for u16 {} -impl Word for u16 { - fn bits() -> WordSize { - WordSize::TwoBytes - } -} - -impl sealed::Word for u32 {} -impl Word for u32 { - fn bits() -> WordSize { - WordSize::FourBytes - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Burst { - /// Single transfer - Single, - /// Incremental burst of 4 beats - Incr4, - /// Incremental burst of 8 beats - Incr8, - /// Incremental burst of 16 beats - Incr16, -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum FlowControl { - /// Flow control by DMA - Dma, - /// Flow control by peripheral - Peripheral, -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum FifoThreshold { - /// 1/4 full FIFO - Quarter, - /// 1/2 full FIFO - Half, - /// 3/4 full FIFO - ThreeQuarters, - /// Full FIFO - Full, -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct TransferOptions { - /// Peripheral burst transfer configuration - pub pburst: Burst, - /// Memory burst transfer configuration - pub mburst: Burst, - /// Flow control configuration - pub flow_ctrl: FlowControl, - /// FIFO threshold for DMA FIFO mode. If none, direct mode is used. - pub fifo_threshold: Option, -} - -impl Default for TransferOptions { - fn default() -> Self { - Self { - pburst: Burst::Single, - mburst: Burst::Single, - flow_ctrl: FlowControl::Dma, - fifo_threshold: None, - } - } -} - -mod transfers { - use embassy_hal_common::PeripheralRef; - - use super::*; - - #[allow(unused)] - pub fn read<'a, W: Word>( - channel: impl Peripheral

+ 'a, - request: Request, - reg_addr: *mut W, - buf: &'a mut [W], - ) -> impl Future + 'a { - assert!(buf.len() > 0 && buf.len() <= 0xFFFF); - into_ref!(channel); - - unsafe { channel.start_read::(request, reg_addr, buf, Default::default()) }; - - Transfer::new(channel) - } - - #[allow(unused)] - pub fn write<'a, W: Word>( - channel: impl Peripheral

+ 'a, - request: Request, - buf: &'a [W], - reg_addr: *mut W, - ) -> impl Future + 'a { - assert!(buf.len() > 0 && buf.len() <= 0xFFFF); - into_ref!(channel); - - unsafe { channel.start_write::(request, buf, reg_addr, Default::default()) }; - - Transfer::new(channel) - } - - #[allow(unused)] - pub fn write_repeated<'a, W: Word>( - channel: impl Peripheral

+ 'a, - request: Request, - repeated: *const W, - count: usize, - reg_addr: *mut W, - ) -> impl Future + 'a { - into_ref!(channel); - - unsafe { channel.start_write_repeated::(request, repeated, count, reg_addr, Default::default()) }; - - Transfer::new(channel) - } - - #[must_use = "futures do nothing unless you `.await` or poll them"] - pub(crate) struct Transfer<'a, C: Channel> { - channel: PeripheralRef<'a, C>, - } - - impl<'a, C: Channel> Transfer<'a, C> { - pub(crate) fn new(channel: impl Peripheral

+ 'a) -> Self { - into_ref!(channel); - Self { channel } - } - } - - impl<'a, C: Channel> Drop for Transfer<'a, C> { - fn drop(&mut self) { - self.channel.request_stop(); - while self.channel.is_running() {} - } - } - - impl<'a, C: Channel> Unpin for Transfer<'a, C> {} - impl<'a, C: Channel> Future for Transfer<'a, C> { - type Output = (); - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - self.channel.set_waker(cx.waker()); - if self.channel.is_running() { - Poll::Pending - } else { - Poll::Ready(()) - } - } - } -} - -pub trait Channel: sealed::Channel + Peripheral

+ 'static {} - pub struct NoDma; impl_peripheral!(NoDma); -// safety: must be called only once at startup -pub(crate) unsafe fn init(#[cfg(bdma)] bdma_priority: Priority, #[cfg(dma)] dma_priority: Priority) { - #[cfg(bdma)] - bdma::init(bdma_priority); - #[cfg(dma)] - dma::init(dma_priority); - #[cfg(dmamux)] - dmamux::init(); - #[cfg(gpdma)] - gpdma::init(); -} - // TODO: replace transmutes with core::ptr::metadata once it's stable #[allow(unused)] pub(crate) fn slice_ptr_parts(slice: *const [T]) -> (usize, usize) { @@ -334,3 +52,19 @@ pub(crate) fn slice_ptr_parts(slice: *const [T]) -> (usize, usize) { pub(crate) fn slice_ptr_parts_mut(slice: *mut [T]) -> (usize, usize) { unsafe { mem::transmute(slice) } } + +// safety: must be called only once at startup +pub(crate) unsafe fn init( + #[cfg(bdma)] bdma_priority: Priority, + #[cfg(dma)] dma_priority: Priority, + #[cfg(gpdma)] gpdma_priority: Priority, +) { + #[cfg(bdma)] + bdma::init(bdma_priority); + #[cfg(dma)] + dma::init(dma_priority); + #[cfg(gpdma)] + gpdma::init(gpdma_priority); + #[cfg(dmamux)] + dmamux::init(); +} diff --git a/embassy-stm32/src/dma/word.rs b/embassy-stm32/src/dma/word.rs new file mode 100644 index 00000000..aef6e970 --- /dev/null +++ b/embassy-stm32/src/dma/word.rs @@ -0,0 +1,79 @@ +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum WordSize { + OneByte, + TwoBytes, + FourBytes, +} + +impl WordSize { + pub fn bytes(&self) -> usize { + match self { + Self::OneByte => 1, + Self::TwoBytes => 2, + Self::FourBytes => 4, + } + } +} + +mod sealed { + pub trait Word {} +} + +pub trait Word: sealed::Word + Default + Copy + 'static { + fn size() -> WordSize; + fn bits() -> usize; +} + +macro_rules! impl_word { + (_, $T:ident, $bits:literal, $size:ident) => { + impl sealed::Word for $T {} + impl Word for $T { + fn bits() -> usize { + $bits + } + fn size() -> WordSize { + WordSize::$size + } + } + }; + ($T:ident, $uX:ident, $bits:literal, $size:ident) => { + #[repr(transparent)] + #[derive(Copy, Clone, Default)] + pub struct $T(pub $uX); + impl_word!(_, $T, $bits, $size); + }; +} + +impl_word!(U1, u8, 1, OneByte); +impl_word!(U2, u8, 2, OneByte); +impl_word!(U3, u8, 3, OneByte); +impl_word!(U4, u8, 4, OneByte); +impl_word!(U5, u8, 5, OneByte); +impl_word!(U6, u8, 6, OneByte); +impl_word!(U7, u8, 7, OneByte); +impl_word!(_, u8, 8, OneByte); +impl_word!(U9, u16, 9, TwoBytes); +impl_word!(U10, u16, 10, TwoBytes); +impl_word!(U11, u16, 11, TwoBytes); +impl_word!(U12, u16, 12, TwoBytes); +impl_word!(U13, u16, 13, TwoBytes); +impl_word!(U14, u16, 14, TwoBytes); +impl_word!(U15, u16, 15, TwoBytes); +impl_word!(_, u16, 16, TwoBytes); +impl_word!(U17, u32, 17, FourBytes); +impl_word!(U18, u32, 18, FourBytes); +impl_word!(U19, u32, 19, FourBytes); +impl_word!(U20, u32, 20, FourBytes); +impl_word!(U21, u32, 21, FourBytes); +impl_word!(U22, u32, 22, FourBytes); +impl_word!(U23, u32, 23, FourBytes); +impl_word!(U24, u32, 24, FourBytes); +impl_word!(U25, u32, 25, FourBytes); +impl_word!(U26, u32, 26, FourBytes); +impl_word!(U27, u32, 27, FourBytes); +impl_word!(U28, u32, 28, FourBytes); +impl_word!(U29, u32, 29, FourBytes); +impl_word!(U30, u32, 30, FourBytes); +impl_word!(U31, u32, 31, FourBytes); +impl_word!(_, u32, 32, FourBytes); diff --git a/embassy-stm32/src/flash/common.rs b/embassy-stm32/src/flash/common.rs index 1189e447..6d7f5597 100644 --- a/embassy-stm32/src/flash/common.rs +++ b/embassy-stm32/src/flash/common.rs @@ -2,7 +2,7 @@ use atomic_polyfill::{fence, Ordering}; use embassy_hal_common::drop::OnDrop; use embassy_hal_common::{into_ref, PeripheralRef}; -use super::{family, Error, FlashLayout, FlashRegion, FlashSector, FLASH_BASE, FLASH_SIZE, WRITE_SIZE}; +use super::{family, Error, FlashLayout, FlashRegion, FlashSector, FLASH_BASE, FLASH_SIZE, MAX_ERASE_SIZE, WRITE_SIZE}; use crate::flash::FlashBank; use crate::Peripheral; @@ -162,6 +162,35 @@ impl FlashRegion { } } +impl embedded_storage::nor_flash::ErrorType for Flash<'_> { + type Error = Error; +} + +impl embedded_storage::nor_flash::ReadNorFlash for Flash<'_> { + const READ_SIZE: usize = 1; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_read(offset, bytes) + } + + fn capacity(&self) -> usize { + FLASH_SIZE + } +} + +impl embedded_storage::nor_flash::NorFlash for Flash<'_> { + const WRITE_SIZE: usize = WRITE_SIZE; + const ERASE_SIZE: usize = MAX_ERASE_SIZE; + + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(offset, bytes) + } + + fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.blocking_erase(from, to) + } +} + foreach_flash_region! { ($type_name:ident, $write_size:literal, $erase_size:literal) => { impl crate::_generated::flash_regions::$type_name<'_> { diff --git a/embassy-stm32/src/flash/mod.rs b/embassy-stm32/src/flash/mod.rs index 231ff1f9..7d5596b1 100644 --- a/embassy-stm32/src/flash/mod.rs +++ b/embassy-stm32/src/flash/mod.rs @@ -7,6 +7,7 @@ mod common; pub use common::*; pub use crate::_generated::flash_regions::*; +pub use crate::_generated::MAX_ERASE_SIZE; pub use crate::pac::{FLASH_BASE, FLASH_SIZE, WRITE_SIZE}; #[derive(Debug)] diff --git a/embassy-stm32/src/i2c/v2.rs b/embassy-stm32/src/i2c/v2.rs index 7218f770..39e6702e 100644 --- a/embassy-stm32/src/i2c/v2.rs +++ b/embassy-stm32/src/i2c/v2.rs @@ -8,7 +8,7 @@ use embassy_hal_common::drop::OnDrop; use embassy_hal_common::{into_ref, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; -use crate::dma::NoDma; +use crate::dma::{NoDma, Transfer}; use crate::gpio::sealed::AFType; use crate::gpio::Pull; use crate::i2c::{Error, Instance, SclPin, SdaPin}; @@ -476,7 +476,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { let ch = &mut self.tx_dma; let request = ch.request(); - crate::dma::write(ch, request, write, dst) + Transfer::new_write(ch, request, write, dst, Default::default()) }; let state = T::state(); @@ -576,7 +576,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { let ch = &mut self.rx_dma; let request = ch.request(); - crate::dma::read(ch, request, src, buffer) + Transfer::new_read(ch, request, src, buffer, Default::default()) }; let state = T::state(); diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index d4d7155b..bbde2da5 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -49,6 +49,8 @@ pub mod pwm; pub mod qspi; #[cfg(rng)] pub mod rng; +#[cfg(all(rtc, not(any(rtc_v1, rtc_v2f0, rtc_v2f7, rtc_v3, rtc_v3u5))))] +pub mod rtc; #[cfg(sdmmc)] pub mod sdmmc; #[cfg(spi)] @@ -76,7 +78,6 @@ pub(crate) mod _generated { // Reexports pub use _generated::{peripherals, Peripherals}; pub use embassy_cortex_m::executor; -#[cfg(any(dma, bdma))] use embassy_cortex_m::interrupt::Priority; pub use embassy_cortex_m::interrupt::_export::interrupt; pub use embassy_hal_common::{into_ref, Peripheral, PeripheralRef}; @@ -94,6 +95,8 @@ pub struct Config { pub bdma_interrupt_priority: Priority, #[cfg(dma)] pub dma_interrupt_priority: Priority, + #[cfg(gpdma)] + pub gpdma_interrupt_priority: Priority, } impl Default for Config { @@ -106,6 +109,8 @@ impl Default for Config { bdma_interrupt_priority: Priority::P0, #[cfg(dma)] dma_interrupt_priority: Priority::P0, + #[cfg(gpdma)] + gpdma_interrupt_priority: Priority::P0, } } } @@ -149,6 +154,8 @@ pub fn init(config: Config) -> Peripherals { config.bdma_interrupt_priority, #[cfg(dma)] config.dma_interrupt_priority, + #[cfg(gpdma)] + config.gpdma_interrupt_priority, ); #[cfg(feature = "exti")] exti::init(); diff --git a/embassy-stm32/src/qspi/mod.rs b/embassy-stm32/src/qspi/mod.rs index f3331962..c3126b37 100644 --- a/embassy-stm32/src/qspi/mod.rs +++ b/embassy-stm32/src/qspi/mod.rs @@ -5,7 +5,7 @@ pub mod enums; use embassy_hal_common::{into_ref, PeripheralRef}; use enums::*; -use crate::dma::TransferOptions; +use crate::dma::Transfer; use crate::gpio::sealed::AFType; use crate::gpio::AnyPin; use crate::pac::quadspi::Quadspi as Regs; @@ -230,9 +230,6 @@ impl<'d, T: Instance, Dma> Qspi<'d, T, Dma> { unsafe { self.setup_transaction(QspiMode::IndirectWrite, &transaction); - let request = self.dma.request(); - let options = TransferOptions::default(); - T::REGS.ccr().modify(|v| { v.set_fmode(QspiMode::IndirectRead.into()); }); @@ -241,12 +238,18 @@ impl<'d, T: Instance, Dma> Qspi<'d, T, Dma> { v.set_address(current_ar); }); - self.dma - .start_read(request, T::REGS.dr().ptr() as *mut u8, buf, options); + let request = self.dma.request(); + let transfer = Transfer::new_read( + &mut self.dma, + request, + T::REGS.dr().ptr() as *mut u8, + buf, + Default::default(), + ); T::REGS.cr().modify(|v| v.set_dmaen(true)); - while self.dma.is_running() {} + transfer.blocking_wait(); } } @@ -257,19 +260,22 @@ impl<'d, T: Instance, Dma> Qspi<'d, T, Dma> { unsafe { self.setup_transaction(QspiMode::IndirectWrite, &transaction); - let request = self.dma.request(); - let options = TransferOptions::default(); - T::REGS.ccr().modify(|v| { v.set_fmode(QspiMode::IndirectWrite.into()); }); - self.dma - .start_write(request, buf, T::REGS.dr().ptr() as *mut u8, options); + let request = self.dma.request(); + let transfer = Transfer::new_write( + &mut self.dma, + request, + buf, + T::REGS.dr().ptr() as *mut u8, + Default::default(), + ); T::REGS.cr().modify(|v| v.set_dmaen(true)); - while self.dma.is_running() {} + transfer.blocking_wait(); } } diff --git a/embassy-stm32/src/rcc/f4.rs b/embassy-stm32/src/rcc/f4.rs index 2a17eb9b..e0929ca4 100644 --- a/embassy-stm32/src/rcc/f4.rs +++ b/embassy-stm32/src/rcc/f4.rs @@ -29,10 +29,66 @@ pub struct Config { pub pclk1: Option, pub pclk2: Option, + #[cfg(not(any(stm32f410, stm32f411, stm32f412, stm32f413, stm32f423, stm32f446)))] + pub plli2s: Option, + pub pll48: bool, } -unsafe fn setup_pll(pllsrcclk: u32, use_hse: bool, pllsysclk: Option, pll48clk: bool) -> PllResults { +#[cfg(stm32f410)] +unsafe fn setup_i2s_pll(_vco_in: u32, _plli2s: Option) -> Option { + None +} + +// Not currently implemented, but will be in the future +#[cfg(any(stm32f411, stm32f412, stm32f413, stm32f423, stm32f446))] +unsafe fn setup_i2s_pll(_vco_in: u32, _plli2s: Option) -> Option { + None +} + +#[cfg(not(any(stm32f410, stm32f411, stm32f412, stm32f413, stm32f423, stm32f446)))] +unsafe fn setup_i2s_pll(vco_in: u32, plli2s: Option) -> Option { + let min_div = 2; + let max_div = 7; + let target = match plli2s { + Some(target) => target, + None => return None, + }; + + // We loop through the possible divider values to find the best configuration. Looping + // through all possible "N" values would result in more iterations. + let (n, outdiv, output, _error) = (min_div..=max_div) + .filter_map(|outdiv| { + let target_vco_out = match target.checked_mul(outdiv) { + Some(x) => x, + None => return None, + }; + let n = (target_vco_out + (vco_in >> 1)) / vco_in; + let vco_out = vco_in * n; + if !(100_000_000..=432_000_000).contains(&vco_out) { + return None; + } + let output = vco_out / outdiv; + let error = (output as i32 - target as i32).unsigned_abs(); + Some((n, outdiv, output, error)) + }) + .min_by_key(|(_, _, _, error)| *error)?; + + RCC.plli2scfgr().modify(|w| { + w.set_plli2sn(n as u16); + w.set_plli2sr(outdiv as u8); + }); + + Some(output) +} + +unsafe fn setup_pll( + pllsrcclk: u32, + use_hse: bool, + pllsysclk: Option, + plli2s: Option, + pll48clk: bool, +) -> PllResults { use crate::pac::rcc::vals::{Pllp, Pllsrc}; let sysclk = pllsysclk.unwrap_or(pllsrcclk); @@ -43,6 +99,7 @@ unsafe fn setup_pll(pllsrcclk: u32, use_hse: bool, pllsysclk: Option, pll48 use_pll: false, pllsysclk: None, pll48clk: None, + plli2sclk: None, }; } // Input divisor from PLL source clock, must result to frequency in @@ -101,6 +158,7 @@ unsafe fn setup_pll(pllsrcclk: u32, use_hse: bool, pllsysclk: Option, pll48 use_pll: true, pllsysclk: Some(real_pllsysclk), pll48clk: if pll48clk { Some(real_pll48clk) } else { None }, + plli2sclk: setup_i2s_pll(vco_in, plli2s), } } @@ -286,6 +344,10 @@ pub(crate) unsafe fn init(config: Config) { pllsrcclk, config.hse.is_some(), if sysclk_on_pll { Some(sysclk) } else { None }, + #[cfg(not(any(stm32f410, stm32f411, stm32f412, stm32f413, stm32f423, stm32f446)))] + config.plli2s.map(|i2s| i2s.0), + #[cfg(any(stm32f410, stm32f411, stm32f412, stm32f413, stm32f423, stm32f446))] + None, config.pll48, ); @@ -376,6 +438,13 @@ pub(crate) unsafe fn init(config: Config) { while !RCC.cr().read().pllrdy() {} } + #[cfg(not(stm32f410))] + if plls.plli2sclk.is_some() { + RCC.cr().modify(|w| w.set_plli2son(true)); + + while !RCC.cr().read().plli2srdy() {} + } + RCC.cfgr().modify(|w| { w.set_ppre2(Ppre(ppre2_bits)); w.set_ppre1(Ppre(ppre1_bits)); @@ -409,6 +478,12 @@ pub(crate) unsafe fn init(config: Config) { ahb3: Hertz(hclk), pll48: plls.pll48clk.map(Hertz), + + #[cfg(not(stm32f410))] + plli2s: plls.plli2sclk.map(Hertz), + + #[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479))] + pllsai: None, }); } @@ -416,6 +491,8 @@ struct PllResults { use_pll: bool, pllsysclk: Option, pll48clk: Option, + #[allow(dead_code)] + plli2sclk: Option, } mod max { diff --git a/embassy-stm32/src/rcc/mod.rs b/embassy-stm32/src/rcc/mod.rs index d6a31f17..d6816d6a 100644 --- a/embassy-stm32/src/rcc/mod.rs +++ b/embassy-stm32/src/rcc/mod.rs @@ -60,6 +60,12 @@ pub struct Clocks { #[cfg(any(rcc_f2, rcc_f4, rcc_f410, rcc_f7))] pub pll48: Option, + #[cfg(all(rcc_f4, not(stm32f410)))] + pub plli2s: Option, + + #[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479))] + pub pllsai: Option, + #[cfg(stm32f1)] pub adc: Hertz, diff --git a/embassy-stm32/src/rtc/datetime.rs b/embassy-stm32/src/rtc/datetime.rs new file mode 100644 index 00000000..6274c1e0 --- /dev/null +++ b/embassy-stm32/src/rtc/datetime.rs @@ -0,0 +1,203 @@ +#[cfg(feature = "chrono")] +use core::convert::From; + +#[cfg(feature = "chrono")] +use chrono::{self, Datelike, NaiveDate, Timelike, Weekday}; + +use super::byte_to_bcd2; +use crate::pac::rtc::Rtc; + +/// Errors regarding the [`DateTime`] struct. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Error { + /// The [DateTime] contains an invalid year value. Must be between `0..=4095`. + InvalidYear, + /// The [DateTime] contains an invalid month value. Must be between `1..=12`. + InvalidMonth, + /// The [DateTime] contains an invalid day value. Must be between `1..=31`. + InvalidDay, + /// The [DateTime] contains an invalid day of week. Must be between `0..=6` where 0 is Sunday. + InvalidDayOfWeek( + /// The value of the DayOfWeek that was given. + u8, + ), + /// The [DateTime] contains an invalid hour value. Must be between `0..=23`. + InvalidHour, + /// The [DateTime] contains an invalid minute value. Must be between `0..=59`. + InvalidMinute, + /// The [DateTime] contains an invalid second value. Must be between `0..=59`. + InvalidSecond, +} + +/// Structure containing date and time information +pub struct DateTime { + /// 0..4095 + pub year: u16, + /// 1..12, 1 is January + pub month: u8, + /// 1..28,29,30,31 depending on month + pub day: u8, + /// + pub day_of_week: DayOfWeek, + /// 0..23 + pub hour: u8, + /// 0..59 + pub minute: u8, + /// 0..59 + pub second: u8, +} + +#[cfg(feature = "chrono")] +impl From for DateTime { + fn from(date_time: chrono::NaiveDateTime) -> Self { + Self { + year: (date_time.year() - 1970) as u16, + month: date_time.month() as u8, + day: date_time.day() as u8, + day_of_week: date_time.weekday().into(), + hour: date_time.hour() as u8, + minute: date_time.minute() as u8, + second: date_time.second() as u8, + } + } +} + +#[cfg(feature = "chrono")] +impl From for chrono::NaiveDateTime { + fn from(date_time: DateTime) -> Self { + NaiveDate::from_ymd_opt( + (date_time.year + 1970) as i32, + date_time.month as u32, + date_time.day as u32, + ) + .unwrap() + .and_hms_opt(date_time.hour as u32, date_time.minute as u32, date_time.second as u32) + .unwrap() + } +} + +/// A day of the week +#[repr(u8)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] +#[allow(missing_docs)] +pub enum DayOfWeek { + Monday = 0, + Tuesday = 1, + Wednesday = 2, + Thursday = 3, + Friday = 4, + Saturday = 5, + Sunday = 6, +} + +#[cfg(feature = "chrono")] +impl From for DayOfWeek { + fn from(weekday: Weekday) -> Self { + day_of_week_from_u8(weekday.number_from_monday() as u8).unwrap() + } +} + +#[cfg(feature = "chrono")] +impl From for chrono::Weekday { + fn from(weekday: DayOfWeek) -> Self { + match weekday { + DayOfWeek::Monday => Weekday::Mon, + DayOfWeek::Tuesday => Weekday::Tue, + DayOfWeek::Wednesday => Weekday::Wed, + DayOfWeek::Thursday => Weekday::Thu, + DayOfWeek::Friday => Weekday::Fri, + DayOfWeek::Saturday => Weekday::Sat, + DayOfWeek::Sunday => Weekday::Sun, + } + } +} + +fn day_of_week_from_u8(v: u8) -> Result { + Ok(match v { + 0 => DayOfWeek::Monday, + 1 => DayOfWeek::Tuesday, + 2 => DayOfWeek::Wednesday, + 3 => DayOfWeek::Thursday, + 4 => DayOfWeek::Friday, + 5 => DayOfWeek::Saturday, + 6 => DayOfWeek::Sunday, + x => return Err(Error::InvalidDayOfWeek(x)), + }) +} + +pub(super) fn day_of_week_to_u8(dotw: DayOfWeek) -> u8 { + dotw as u8 +} + +pub(super) fn validate_datetime(dt: &DateTime) -> Result<(), Error> { + if dt.year > 4095 { + Err(Error::InvalidYear) + } else if dt.month < 1 || dt.month > 12 { + Err(Error::InvalidMonth) + } else if dt.day < 1 || dt.day > 31 { + Err(Error::InvalidDay) + } else if dt.hour > 23 { + Err(Error::InvalidHour) + } else if dt.minute > 59 { + Err(Error::InvalidMinute) + } else if dt.second > 59 { + Err(Error::InvalidSecond) + } else { + Ok(()) + } +} + +pub(super) fn write_date_time(rtc: &Rtc, t: DateTime) { + let (ht, hu) = byte_to_bcd2(t.hour as u8); + let (mnt, mnu) = byte_to_bcd2(t.minute as u8); + let (st, su) = byte_to_bcd2(t.second as u8); + + let (dt, du) = byte_to_bcd2(t.day as u8); + let (mt, mu) = byte_to_bcd2(t.month as u8); + let yr = t.year as u16; + let yr_offset = (yr - 1970_u16) as u8; + let (yt, yu) = byte_to_bcd2(yr_offset); + + unsafe { + rtc.tr().write(|w| { + w.set_ht(ht); + w.set_hu(hu); + w.set_mnt(mnt); + w.set_mnu(mnu); + w.set_st(st); + w.set_su(su); + w.set_pm(stm32_metapac::rtc::vals::Ampm::AM); + }); + + rtc.dr().write(|w| { + w.set_dt(dt); + w.set_du(du); + w.set_mt(mt > 0); + w.set_mu(mu); + w.set_yt(yt); + w.set_yu(yu); + w.set_wdu(day_of_week_to_u8(t.day_of_week)); + }); + } +} + +pub(super) fn datetime( + year: u16, + month: u8, + day: u8, + day_of_week: u8, + hour: u8, + minute: u8, + second: u8, +) -> Result { + let day_of_week = day_of_week_from_u8(day_of_week)?; + Ok(DateTime { + year, + month, + day, + day_of_week, + hour, + minute, + second, + }) +} diff --git a/embassy-stm32/src/rtc/datetime_chrono.rs b/embassy-stm32/src/rtc/datetime_chrono.rs new file mode 100644 index 00000000..b46316cc --- /dev/null +++ b/embassy-stm32/src/rtc/datetime_chrono.rs @@ -0,0 +1,85 @@ +use chrono::{Datelike, Timelike}; + +use super::byte_to_bcd2; +use crate::pac::rtc::Rtc; + +/// Alias for [`chrono::NaiveDateTime`] +pub type DateTime = chrono::NaiveDateTime; +/// Alias for [`chrono::Weekday`] +pub type DayOfWeek = chrono::Weekday; + +/// Errors regarding the [`DateTime`] and [`DateTimeFilter`] structs. +/// +/// [`DateTimeFilter`]: struct.DateTimeFilter.html +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Error { + /// The [DateTime] has an invalid year. The year must be between 0 and 4095. + InvalidYear, + /// The [DateTime] contains an invalid date. + InvalidDate, + /// The [DateTime] contains an invalid time. + InvalidTime, +} + +pub(super) fn day_of_week_to_u8(dotw: DayOfWeek) -> u8 { + dotw.num_days_from_monday() as u8 +} + +pub(crate) fn validate_datetime(dt: &DateTime) -> Result<(), Error> { + if dt.year() < 0 || dt.year() > 4095 { + // rp2040 can't hold these years + Err(Error::InvalidYear) + } else { + // The rest of the chrono date is assumed to be valid + Ok(()) + } +} + +pub(super) fn write_date_time(rtc: &Rtc, t: DateTime) { + let (ht, hu) = byte_to_bcd2(t.hour() as u8); + let (mnt, mnu) = byte_to_bcd2(t.minute() as u8); + let (st, su) = byte_to_bcd2(t.second() as u8); + + let (dt, du) = byte_to_bcd2(t.day() as u8); + let (mt, mu) = byte_to_bcd2(t.month() as u8); + let yr = t.year() as u16; + let yr_offset = (yr - 1970_u16) as u8; + let (yt, yu) = byte_to_bcd2(yr_offset); + + unsafe { + rtc.tr().write(|w| { + w.set_ht(ht); + w.set_hu(hu); + w.set_mnt(mnt); + w.set_mnu(mnu); + w.set_st(st); + w.set_su(su); + w.set_pm(stm32_metapac::rtc::vals::Ampm::AM); + }); + + rtc.dr().write(|w| { + w.set_dt(dt); + w.set_du(du); + w.set_mt(mt > 0); + w.set_mu(mu); + w.set_yt(yt); + w.set_yu(yu); + w.set_wdu(day_of_week_to_u8(t.weekday())); + }); + } +} + +pub(super) fn datetime( + year: u16, + month: u8, + day: u8, + _day_of_week: u8, + hour: u8, + minute: u8, + second: u8, +) -> Result { + let date = chrono::NaiveDate::from_ymd_opt(year.into(), month.try_into().unwrap(), day.into()) + .ok_or(Error::InvalidDate)?; + let time = chrono::NaiveTime::from_hms_opt(hour.into(), minute.into(), second.into()).ok_or(Error::InvalidTime)?; + Ok(DateTime::new(date, time)) +} diff --git a/embassy-stm32/src/rtc/mod.rs b/embassy-stm32/src/rtc/mod.rs new file mode 100644 index 00000000..170783b2 --- /dev/null +++ b/embassy-stm32/src/rtc/mod.rs @@ -0,0 +1,235 @@ +//! RTC peripheral abstraction +use core::marker::PhantomData; +mod datetime; + +pub use self::datetime::{DateTime, DayOfWeek, Error as DateTimeError}; + +/// refer to AN4759 to compare features of RTC2 and RTC3 +#[cfg_attr(any(rtc_v1), path = "v1.rs")] +#[cfg_attr( + any( + rtc_v2f0, rtc_v2f2, rtc_v2f3, rtc_v2f4, rtc_v2f7, rtc_v2h7, rtc_v2l0, rtc_v2l1, rtc_v2l4, rtc_v2wb + ), + path = "v2/mod.rs" +)] +#[cfg_attr(any(rtc_v3, rtc_v3u5), path = "v3.rs")] +mod versions; +use embassy_hal_common::Peripheral; +pub use versions::*; + +/// Errors that can occur on methods on [RtcClock] +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum RtcError { + /// An invalid DateTime was given or stored on the hardware. + InvalidDateTime(DateTimeError), + + /// The RTC clock is not running + NotRunning, +} + +/// RTC Abstraction +pub struct Rtc<'d, T: Instance> { + phantom: PhantomData<&'d mut T>, + rtc_config: RtcConfig, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +#[repr(u8)] +pub enum RtcClockSource { + /// 00: No clock + NoClock = 0b00, + /// 01: LSE oscillator clock used as RTC clock + LSE = 0b01, + /// 10: LSI oscillator clock used as RTC clock + LSI = 0b10, + /// 11: HSE oscillator clock divided by 32 used as RTC clock + HSE = 0b11, +} + +#[derive(Copy, Clone, PartialEq)] +pub struct RtcConfig { + /// RTC clock source + clock_config: RtcClockSource, + /// Asynchronous prescaler factor + /// This is the asynchronous division factor: + /// ck_apre frequency = RTCCLK frequency/(PREDIV_A+1) + /// ck_apre drives the subsecond register + async_prescaler: u8, + /// Synchronous prescaler factor + /// This is the synchronous division factor: + /// ck_spre frequency = ck_apre frequency/(PREDIV_S+1) + /// ck_spre must be 1Hz + sync_prescaler: u16, +} + +impl Default for RtcConfig { + /// LSI with prescalers assuming 32.768 kHz. + /// Raw sub-seconds in 1/256. + fn default() -> Self { + RtcConfig { + clock_config: RtcClockSource::LSI, + async_prescaler: 127, + sync_prescaler: 255, + } + } +} + +impl RtcConfig { + /// Sets the clock source of RTC config + pub fn clock_config(mut self, cfg: RtcClockSource) -> Self { + self.clock_config = cfg; + self + } + + /// Set the asynchronous prescaler of RTC config + pub fn async_prescaler(mut self, prescaler: u8) -> Self { + self.async_prescaler = prescaler; + self + } + + /// Set the synchronous prescaler of RTC config + pub fn sync_prescaler(mut self, prescaler: u16) -> Self { + self.sync_prescaler = prescaler; + self + } +} + +#[derive(Copy, Clone, Debug, PartialEq)] +#[repr(u8)] +pub enum RtcCalibrationCyclePeriod { + /// 8-second calibration period + Seconds8, + /// 16-second calibration period + Seconds16, + /// 32-second calibration period + Seconds32, +} + +impl Default for RtcCalibrationCyclePeriod { + fn default() -> Self { + RtcCalibrationCyclePeriod::Seconds32 + } +} + +impl<'d, T: Instance> Rtc<'d, T> { + pub fn new(_rtc: impl Peripheral

+ 'd, rtc_config: RtcConfig) -> Self { + unsafe { enable_peripheral_clk() }; + + let mut rtc_struct = Self { + phantom: PhantomData, + rtc_config, + }; + + rtc_struct.apply_config(rtc_config); + + rtc_struct + } + + /// Set the datetime to a new value. + /// + /// # Errors + /// + /// Will return `RtcError::InvalidDateTime` if the datetime is not a valid range. + pub fn set_datetime(&mut self, t: DateTime) -> Result<(), RtcError> { + self::datetime::validate_datetime(&t).map_err(RtcError::InvalidDateTime)?; + self.write(true, |rtc| self::datetime::write_date_time(rtc, t)); + + Ok(()) + } + + /// Return the current datetime. + /// + /// # Errors + /// + /// Will return an `RtcError::InvalidDateTime` if the stored value in the system is not a valid [`DayOfWeek`]. + pub fn now(&self) -> Result { + let r = T::regs(); + unsafe { + let tr = r.tr().read(); + let second = bcd2_to_byte((tr.st(), tr.su())); + let minute = bcd2_to_byte((tr.mnt(), tr.mnu())); + let hour = bcd2_to_byte((tr.ht(), tr.hu())); + // Reading either RTC_SSR or RTC_TR locks the values in the higher-order + // calendar shadow registers until RTC_DR is read. + let dr = r.dr().read(); + + let weekday = dr.wdu(); + let day = bcd2_to_byte((dr.dt(), dr.du())); + let month = bcd2_to_byte((dr.mt() as u8, dr.mu())); + let year = bcd2_to_byte((dr.yt(), dr.yu())) as u16 + 1970_u16; + + self::datetime::datetime(year, month, day, weekday, hour, minute, second).map_err(RtcError::InvalidDateTime) + } + } + + /// Check if daylight savings time is active. + pub fn get_daylight_savings(&self) -> bool { + let cr = unsafe { T::regs().cr().read() }; + cr.bkp() + } + + /// Enable/disable daylight savings time. + pub fn set_daylight_savings(&mut self, daylight_savings: bool) { + self.write(true, |rtc| { + unsafe { rtc.cr().modify(|w| w.set_bkp(daylight_savings)) }; + }) + } + + pub fn get_config(&self) -> RtcConfig { + self.rtc_config + } + + pub const BACKUP_REGISTER_COUNT: usize = BACKUP_REGISTER_COUNT; + + /// Read content of the backup register. + /// + /// The registers retain their values during wakes from standby mode or system resets. They also + /// retain their value when Vdd is switched off as long as V_BAT is powered. + pub fn read_backup_register(&self, register: usize) -> Option { + read_backup_register(&T::regs(), register) + } + + /// Set content of the backup register. + /// + /// The registers retain their values during wakes from standby mode or system resets. They also + /// retain their value when Vdd is switched off as long as V_BAT is powered. + pub fn write_backup_register(&self, register: usize, value: u32) { + write_backup_register(&T::regs(), register, value) + } +} + +pub(crate) fn byte_to_bcd2(byte: u8) -> (u8, u8) { + let mut bcd_high: u8 = 0; + let mut value = byte; + + while value >= 10 { + bcd_high += 1; + value -= 10; + } + + (bcd_high, ((bcd_high << 4) | value) as u8) +} + +pub(crate) fn bcd2_to_byte(bcd: (u8, u8)) -> u8 { + let value = bcd.1 | bcd.0 << 4; + + let tmp = ((value & 0xF0) >> 0x4) * 10; + + tmp + (value & 0x0F) +} + +pub(crate) mod sealed { + pub trait Instance { + fn regs() -> crate::pac::rtc::Rtc; + } +} + +pub trait Instance: sealed::Instance + 'static {} + +impl sealed::Instance for crate::peripherals::RTC { + fn regs() -> crate::pac::rtc::Rtc { + crate::pac::RTC + } +} + +impl Instance for crate::peripherals::RTC {} diff --git a/embassy-stm32/src/rtc/v2/mod.rs b/embassy-stm32/src/rtc/v2/mod.rs new file mode 100644 index 00000000..296adae8 --- /dev/null +++ b/embassy-stm32/src/rtc/v2/mod.rs @@ -0,0 +1,171 @@ +use stm32_metapac::rtc::vals::{Init, Osel, Pol}; + +use super::{Instance, RtcConfig}; +use crate::pac::rtc::Rtc; + +#[cfg_attr(rtc_v2f0, path = "v2f0.rs")] +#[cfg_attr(rtc_v2f2, path = "v2f2.rs")] +#[cfg_attr(rtc_v2f3, path = "v2f3.rs")] +#[cfg_attr(rtc_v2f4, path = "v2f4.rs")] +#[cfg_attr(rtc_v2f7, path = "v2f7.rs")] +#[cfg_attr(rtc_v2h7, path = "v2h7.rs")] +#[cfg_attr(rtc_v2l0, path = "v2l0.rs")] +#[cfg_attr(rtc_v2l1, path = "v2l1.rs")] +#[cfg_attr(rtc_v2l4, path = "v2l4.rs")] +#[cfg_attr(rtc_v2wb, path = "v2wb.rs")] +mod family; + +pub use family::*; + +impl<'d, T: Instance> super::Rtc<'d, T> { + /// Applies the RTC config + /// It this changes the RTC clock source the time will be reset + pub(super) fn apply_config(&mut self, rtc_config: RtcConfig) { + // Unlock the backup domain + unsafe { + unlock_backup_domain(rtc_config.clock_config as u8); + } + + self.write(true, |rtc| unsafe { + rtc.cr().modify(|w| { + #[cfg(rtc_v2f2)] + w.set_fmt(false); + #[cfg(not(rtc_v2f2))] + w.set_fmt(stm32_metapac::rtc::vals::Fmt::TWENTY_FOUR_HOUR); + w.set_osel(Osel::DISABLED); + w.set_pol(Pol::HIGH); + }); + + rtc.prer().modify(|w| { + w.set_prediv_s(rtc_config.sync_prescaler); + w.set_prediv_a(rtc_config.async_prescaler); + }); + }); + + self.rtc_config = rtc_config; + } + + /// Calibrate the clock drift. + /// + /// `clock_drift` can be adjusted from -487.1 ppm to 488.5 ppm and is clamped to this range. + /// + /// ### Note + /// + /// To perform a calibration when `async_prescaler` is less then 3, `sync_prescaler` + /// has to be reduced accordingly (see RM0351 Rev 9, sec 38.3.12). + #[cfg(not(rtc_v2f2))] + pub fn calibrate(&mut self, mut clock_drift: f32, period: super::RtcCalibrationCyclePeriod) { + const RTC_CALR_MIN_PPM: f32 = -487.1; + const RTC_CALR_MAX_PPM: f32 = 488.5; + const RTC_CALR_RESOLUTION_PPM: f32 = 0.9537; + + if clock_drift < RTC_CALR_MIN_PPM { + clock_drift = RTC_CALR_MIN_PPM; + } else if clock_drift > RTC_CALR_MAX_PPM { + clock_drift = RTC_CALR_MAX_PPM; + } + + clock_drift = clock_drift / RTC_CALR_RESOLUTION_PPM; + + self.write(false, |rtc| { + unsafe { + rtc.calr().write(|w| { + match period { + super::RtcCalibrationCyclePeriod::Seconds8 => { + w.set_calw8(stm32_metapac::rtc::vals::Calw8::EIGHT_SECOND); + } + super::RtcCalibrationCyclePeriod::Seconds16 => { + w.set_calw16(stm32_metapac::rtc::vals::Calw16::SIXTEEN_SECOND); + } + super::RtcCalibrationCyclePeriod::Seconds32 => { + // Set neither `calw8` nor `calw16` to use 32 seconds + } + } + + // Extra pulses during calibration cycle period: CALP * 512 - CALM + // + // CALP sets whether pulses are added or omitted. + // + // CALM contains how many pulses (out of 512) are masked in a + // given calibration cycle period. + if clock_drift > 0.0 { + // Maximum (about 512.2) rounds to 512. + clock_drift += 0.5; + + // When the offset is positive (0 to 512), the opposite of + // the offset (512 - offset) is masked, i.e. for the + // maximum offset (512), 0 pulses are masked. + w.set_calp(stm32_metapac::rtc::vals::Calp::INCREASEFREQ); + w.set_calm(512 - clock_drift as u16); + } else { + // Minimum (about -510.7) rounds to -511. + clock_drift -= 0.5; + + // When the offset is negative or zero (-511 to 0), + // the absolute offset is masked, i.e. for the minimum + // offset (-511), 511 pulses are masked. + w.set_calp(stm32_metapac::rtc::vals::Calp::NOCHANGE); + w.set_calm((clock_drift * -1.0) as u16); + } + }); + } + }) + } + + pub(super) fn write(&mut self, init_mode: bool, f: F) -> R + where + F: FnOnce(&crate::pac::rtc::Rtc) -> R, + { + let r = T::regs(); + // Disable write protection. + // This is safe, as we're only writin the correct and expected values. + unsafe { + r.wpr().write(|w| w.set_key(0xca)); + r.wpr().write(|w| w.set_key(0x53)); + + // true if initf bit indicates RTC peripheral is in init mode + if init_mode && !r.isr().read().initf() { + // to update calendar date/time, time format, and prescaler configuration, RTC must be in init mode + r.isr().modify(|w| w.set_init(Init::INITMODE)); + // wait till init state entered + // ~2 RTCCLK cycles + while !r.isr().read().initf() {} + } + } + + let result = f(&r); + + unsafe { + if init_mode { + r.isr().modify(|w| w.set_init(Init::FREERUNNINGMODE)); // Exits init mode + } + + // Re-enable write protection. + // This is safe, as the field accepts the full range of 8-bit values. + r.wpr().write(|w| w.set_key(0xff)); + } + result + } +} + +/// Read content of the backup register. +/// +/// The registers retain their values during wakes from standby mode or system resets. They also +/// retain their value when Vdd is switched off as long as V_BAT is powered. +pub fn read_backup_register(rtc: &Rtc, register: usize) -> Option { + if register < BACKUP_REGISTER_COUNT { + Some(unsafe { rtc.bkpr(register).read().bkp() }) + } else { + None + } +} + +/// Set content of the backup register. +/// +/// The registers retain their values during wakes from standby mode or system resets. They also +/// retain their value when Vdd is switched off as long as V_BAT is powered. +pub fn write_backup_register(rtc: &Rtc, register: usize, value: u32) { + if register < BACKUP_REGISTER_COUNT { + unsafe { rtc.bkpr(register).write(|w| w.set_bkp(value)) } + } +} diff --git a/embassy-stm32/src/rtc/v2/v2f0.rs b/embassy-stm32/src/rtc/v2/v2f0.rs new file mode 100644 index 00000000..d6871d91 --- /dev/null +++ b/embassy-stm32/src/rtc/v2/v2f0.rs @@ -0,0 +1,41 @@ +use stm32_metapac::rcc::vals::Rtcsel; + +pub const BACKUP_REGISTER_COUNT: usize = 20; + +/// Unlock the backup domain +pub(super) unsafe fn unlock_backup_domain(clock_config: u8) { + crate::pac::PWR.cr1().modify(|w| w.set_dbp(true)); + while !crate::pac::PWR.cr1().read().dbp() {} + + let reg = crate::pac::RCC.bdcr().read(); + assert!(!reg.lsecsson(), "RTC is not compatible with LSE CSS, yet."); + + if !reg.rtcen() || reg.rtcsel().0 != clock_config { + crate::pac::RCC.bdcr().modify(|w| w.set_bdrst(true)); + + crate::pac::RCC.bdcr().modify(|w| { + // Reset + w.set_bdrst(false); + + // Select RTC source + w.set_rtcsel(Rtcsel(clock_config)); + w.set_rtcen(true); + + // Restore bcdr + w.set_lscosel(reg.lscosel()); + w.set_lscoen(reg.lscoen()); + + w.set_lseon(reg.lseon()); + w.set_lsedrv(reg.lsedrv()); + w.set_lsebyp(reg.lsebyp()); + }); + } +} + +pub(crate) unsafe fn enable_peripheral_clk() { + // enable peripheral clock for communication + crate::pac::RCC.apb1enr1().modify(|w| w.set_rtcapben(true)); + + // read to allow the pwr clock to enable + crate::pac::PWR.cr1().read(); +} diff --git a/embassy-stm32/src/rtc/v2/v2f2.rs b/embassy-stm32/src/rtc/v2/v2f2.rs new file mode 100644 index 00000000..e041f3f4 --- /dev/null +++ b/embassy-stm32/src/rtc/v2/v2f2.rs @@ -0,0 +1,31 @@ +use stm32_metapac::rcc::vals::Rtcsel; + +pub const BACKUP_REGISTER_COUNT: usize = 20; + +/// Unlock the backup domain +pub(super) unsafe fn unlock_backup_domain(clock_config: u8) { + crate::pac::PWR.cr().modify(|w| w.set_dbp(true)); + while !crate::pac::PWR.cr().read().dbp() {} + + let reg = crate::pac::RCC.bdcr().read(); + + if !reg.rtcen() || reg.rtcsel().0 != clock_config { + crate::pac::RCC.bdcr().modify(|w| w.set_bdrst(true)); + + crate::pac::RCC.bdcr().modify(|w| { + // Reset + w.set_bdrst(false); + + // Select RTC source + w.set_rtcsel(Rtcsel(clock_config)); + w.set_rtcen(true); + + w.set_lseon(reg.lseon()); + w.set_lsebyp(reg.lsebyp()); + }); + } +} + +pub(crate) unsafe fn enable_peripheral_clk() { + // Nothing to do +} diff --git a/embassy-stm32/src/rtc/v2/v2f3.rs b/embassy-stm32/src/rtc/v2/v2f3.rs new file mode 100644 index 00000000..e041f3f4 --- /dev/null +++ b/embassy-stm32/src/rtc/v2/v2f3.rs @@ -0,0 +1,31 @@ +use stm32_metapac::rcc::vals::Rtcsel; + +pub const BACKUP_REGISTER_COUNT: usize = 20; + +/// Unlock the backup domain +pub(super) unsafe fn unlock_backup_domain(clock_config: u8) { + crate::pac::PWR.cr().modify(|w| w.set_dbp(true)); + while !crate::pac::PWR.cr().read().dbp() {} + + let reg = crate::pac::RCC.bdcr().read(); + + if !reg.rtcen() || reg.rtcsel().0 != clock_config { + crate::pac::RCC.bdcr().modify(|w| w.set_bdrst(true)); + + crate::pac::RCC.bdcr().modify(|w| { + // Reset + w.set_bdrst(false); + + // Select RTC source + w.set_rtcsel(Rtcsel(clock_config)); + w.set_rtcen(true); + + w.set_lseon(reg.lseon()); + w.set_lsebyp(reg.lsebyp()); + }); + } +} + +pub(crate) unsafe fn enable_peripheral_clk() { + // Nothing to do +} diff --git a/embassy-stm32/src/rtc/v2/v2f4.rs b/embassy-stm32/src/rtc/v2/v2f4.rs new file mode 100644 index 00000000..4dd21cae --- /dev/null +++ b/embassy-stm32/src/rtc/v2/v2f4.rs @@ -0,0 +1,31 @@ +use stm32_metapac::rcc::vals::Rtcsel; + +pub const BACKUP_REGISTER_COUNT: usize = 20; + +/// Unlock the backup domain +pub(super) unsafe fn unlock_backup_domain(clock_config: u8) { + crate::pac::PWR.cr1().modify(|w| w.set_dbp(true)); + while !crate::pac::PWR.cr1().read().dbp() {} + + let reg = crate::pac::RCC.bdcr().read(); + + if !reg.rtcen() || reg.rtcsel().0 != clock_config { + crate::pac::RCC.bdcr().modify(|w| w.set_bdrst(true)); + + crate::pac::RCC.bdcr().modify(|w| { + // Reset + w.set_bdrst(false); + + // Select RTC source + w.set_rtcsel(Rtcsel(clock_config)); + w.set_rtcen(true); + + w.set_lseon(reg.lseon()); + w.set_lsebyp(reg.lsebyp()); + }); + } +} + +pub(crate) unsafe fn enable_peripheral_clk() { + // Nothing to do +} diff --git a/embassy-stm32/src/rtc/v2/v2f7.rs b/embassy-stm32/src/rtc/v2/v2f7.rs new file mode 100644 index 00000000..d6871d91 --- /dev/null +++ b/embassy-stm32/src/rtc/v2/v2f7.rs @@ -0,0 +1,41 @@ +use stm32_metapac::rcc::vals::Rtcsel; + +pub const BACKUP_REGISTER_COUNT: usize = 20; + +/// Unlock the backup domain +pub(super) unsafe fn unlock_backup_domain(clock_config: u8) { + crate::pac::PWR.cr1().modify(|w| w.set_dbp(true)); + while !crate::pac::PWR.cr1().read().dbp() {} + + let reg = crate::pac::RCC.bdcr().read(); + assert!(!reg.lsecsson(), "RTC is not compatible with LSE CSS, yet."); + + if !reg.rtcen() || reg.rtcsel().0 != clock_config { + crate::pac::RCC.bdcr().modify(|w| w.set_bdrst(true)); + + crate::pac::RCC.bdcr().modify(|w| { + // Reset + w.set_bdrst(false); + + // Select RTC source + w.set_rtcsel(Rtcsel(clock_config)); + w.set_rtcen(true); + + // Restore bcdr + w.set_lscosel(reg.lscosel()); + w.set_lscoen(reg.lscoen()); + + w.set_lseon(reg.lseon()); + w.set_lsedrv(reg.lsedrv()); + w.set_lsebyp(reg.lsebyp()); + }); + } +} + +pub(crate) unsafe fn enable_peripheral_clk() { + // enable peripheral clock for communication + crate::pac::RCC.apb1enr1().modify(|w| w.set_rtcapben(true)); + + // read to allow the pwr clock to enable + crate::pac::PWR.cr1().read(); +} diff --git a/embassy-stm32/src/rtc/v2/v2h7.rs b/embassy-stm32/src/rtc/v2/v2h7.rs new file mode 100644 index 00000000..f3b18068 --- /dev/null +++ b/embassy-stm32/src/rtc/v2/v2h7.rs @@ -0,0 +1,33 @@ +use stm32_metapac::rcc::vals::Rtcsel; + +pub const BACKUP_REGISTER_COUNT: usize = 20; + +/// Unlock the backup domain +pub(super) unsafe fn unlock_backup_domain(clock_config: u8) { + crate::pac::PWR.cr1().modify(|w| w.set_dbp(true)); + while !crate::pac::PWR.cr1().read().dbp() {} + + let reg = crate::pac::RCC.bdcr().read(); + assert!(!reg.lsecsson(), "RTC is not compatible with LSE CSS, yet."); + + if !reg.rtcen() || reg.rtcsel().0 != clock_config { + crate::pac::RCC.bdcr().modify(|w| w.set_bdrst(true)); + + crate::pac::RCC.bdcr().modify(|w| { + // Reset + w.set_bdrst(false); + + // Select RTC source + w.set_rtcsel(Rtcsel(clock_config)); + w.set_rtcen(true); + + w.set_lseon(reg.lseon()); + w.set_lsedrv(reg.lsedrv()); + w.set_lsebyp(reg.lsebyp()); + }); + } +} + +pub(crate) unsafe fn enable_peripheral_clk() { + // Nothing to do +} diff --git a/embassy-stm32/src/rtc/v2/v2l0.rs b/embassy-stm32/src/rtc/v2/v2l0.rs new file mode 100644 index 00000000..dbd3b088 --- /dev/null +++ b/embassy-stm32/src/rtc/v2/v2l0.rs @@ -0,0 +1,26 @@ +pub const BACKUP_REGISTER_COUNT: usize = 20; + +/// Unlock the backup domain +pub(super) unsafe fn unlock_backup_domain(clock_config: u8) { + // TODO: Missing from PAC? + // crate::pac::PWR.cr().modify(|w| w.set_dbp(true)); + // while !crate::pac::PWR.cr().read().dbp() {} + + let reg = crate::pac::RCC.csr().read(); + + if !reg.rtcen() || reg.rtcsel().0 != clock_config { + crate::pac::RCC.csr().modify(|w| { + // Select RTC source + w.set_rtcsel(crate::pac::rcc::vals::Rtcsel(clock_config)); + w.set_rtcen(true); + + w.set_lseon(reg.lseon()); + w.set_lsedrv(reg.lsedrv()); + w.set_lsebyp(reg.lsebyp()); + }); + } +} + +pub(crate) unsafe fn enable_peripheral_clk() { + // Nothing to do +} diff --git a/embassy-stm32/src/rtc/v2/v2l1.rs b/embassy-stm32/src/rtc/v2/v2l1.rs new file mode 100644 index 00000000..1ac78b31 --- /dev/null +++ b/embassy-stm32/src/rtc/v2/v2l1.rs @@ -0,0 +1,24 @@ +pub const BACKUP_REGISTER_COUNT: usize = 20; + +/// Unlock the backup domain +pub(super) unsafe fn unlock_backup_domain(clock_config: u8) { + crate::pac::PWR.cr().modify(|w| w.set_dbp(true)); + while !crate::pac::PWR.cr().read().dbp() {} + + let reg = crate::pac::RCC.csr().read(); + + if !reg.rtcen() || reg.rtcsel().0 != clock_config { + crate::pac::RCC.csr().modify(|w| { + // Select RTC source + w.set_rtcsel(crate::pac::rcc::vals::Rtcsel(clock_config)); + w.set_rtcen(true); + + w.set_lseon(reg.lseon()); + w.set_lsebyp(reg.lsebyp()); + }); + } +} + +pub(crate) unsafe fn enable_peripheral_clk() { + // Nothing to do +} diff --git a/embassy-stm32/src/rtc/v2/v2l4.rs b/embassy-stm32/src/rtc/v2/v2l4.rs new file mode 100644 index 00000000..d6871d91 --- /dev/null +++ b/embassy-stm32/src/rtc/v2/v2l4.rs @@ -0,0 +1,41 @@ +use stm32_metapac::rcc::vals::Rtcsel; + +pub const BACKUP_REGISTER_COUNT: usize = 20; + +/// Unlock the backup domain +pub(super) unsafe fn unlock_backup_domain(clock_config: u8) { + crate::pac::PWR.cr1().modify(|w| w.set_dbp(true)); + while !crate::pac::PWR.cr1().read().dbp() {} + + let reg = crate::pac::RCC.bdcr().read(); + assert!(!reg.lsecsson(), "RTC is not compatible with LSE CSS, yet."); + + if !reg.rtcen() || reg.rtcsel().0 != clock_config { + crate::pac::RCC.bdcr().modify(|w| w.set_bdrst(true)); + + crate::pac::RCC.bdcr().modify(|w| { + // Reset + w.set_bdrst(false); + + // Select RTC source + w.set_rtcsel(Rtcsel(clock_config)); + w.set_rtcen(true); + + // Restore bcdr + w.set_lscosel(reg.lscosel()); + w.set_lscoen(reg.lscoen()); + + w.set_lseon(reg.lseon()); + w.set_lsedrv(reg.lsedrv()); + w.set_lsebyp(reg.lsebyp()); + }); + } +} + +pub(crate) unsafe fn enable_peripheral_clk() { + // enable peripheral clock for communication + crate::pac::RCC.apb1enr1().modify(|w| w.set_rtcapben(true)); + + // read to allow the pwr clock to enable + crate::pac::PWR.cr1().read(); +} diff --git a/embassy-stm32/src/rtc/v2/v2wb.rs b/embassy-stm32/src/rtc/v2/v2wb.rs new file mode 100644 index 00000000..98761fa6 --- /dev/null +++ b/embassy-stm32/src/rtc/v2/v2wb.rs @@ -0,0 +1,39 @@ +pub const BACKUP_REGISTER_COUNT: usize = 20; + +/// Unlock the backup domain +pub(super) unsafe fn unlock_backup_domain(clock_config: u8) { + crate::pac::PWR.cr1().modify(|w| w.set_dbp(true)); + while !crate::pac::PWR.cr1().read().dbp() {} + + let reg = crate::pac::RCC.bdcr().read(); + assert!(!reg.lsecsson(), "RTC is not compatible with LSE CSS, yet."); + + if !reg.rtcen() || reg.rtcsel() != clock_config { + crate::pac::RCC.bdcr().modify(|w| w.set_bdrst(true)); + + crate::pac::RCC.bdcr().modify(|w| { + // Reset + w.set_bdrst(false); + + // Select RTC source + w.set_rtcsel(clock_config); + w.set_rtcen(true); + + // Restore bcdr + w.set_lscosel(reg.lscosel()); + w.set_lscoen(reg.lscoen()); + + w.set_lseon(reg.lseon()); + w.set_lsedrv(reg.lsedrv()); + w.set_lsebyp(reg.lsebyp()); + }); + } +} + +pub(crate) unsafe fn enable_peripheral_clk() { + // enable peripheral clock for communication + crate::pac::RCC.apb1enr1().modify(|w| w.set_rtcapben(true)); + + // read to allow the pwr clock to enable + crate::pac::PWR.cr1().read(); +} diff --git a/embassy-stm32/src/rtc/v3.rs b/embassy-stm32/src/rtc/v3.rs new file mode 100644 index 00000000..6998c48c --- /dev/null +++ b/embassy-stm32/src/rtc/v3.rs @@ -0,0 +1,226 @@ +use stm32_metapac::rtc::vals::{Calp, Calw16, Calw8, Fmt, Init, Key, Osel, Pol, TampalrmPu, TampalrmType}; + +use super::{Instance, RtcCalibrationCyclePeriod, RtcConfig}; +use crate::pac::rtc::Rtc; + +impl<'d, T: Instance> super::Rtc<'d, T> { + /// Applies the RTC config + /// It this changes the RTC clock source the time will be reset + pub(super) fn apply_config(&mut self, rtc_config: RtcConfig) { + // Unlock the backup domain + unsafe { + #[cfg(feature = "stm32g0c1ve")] + { + crate::pac::PWR.cr1().modify(|w| w.set_dbp(true)); + while !crate::pac::PWR.cr1().read().dbp() {} + } + + #[cfg(not(any( + feature = "stm32g0c1ve", + feature = "stm32g491re", + feature = "stm32u585zi", + feature = "stm32g473cc" + )))] + { + crate::pac::PWR + .cr1() + .modify(|w| w.set_dbp(stm32_metapac::pwr::vals::Dbp::ENABLED)); + while crate::pac::PWR.cr1().read().dbp() != stm32_metapac::pwr::vals::Dbp::DISABLED {} + } + + let reg = crate::pac::RCC.bdcr().read(); + assert!(!reg.lsecsson(), "RTC is not compatible with LSE CSS, yet."); + + let config_rtcsel = rtc_config.clock_config as u8; + #[cfg(not(any( + feature = "stm32wl54jc-cm0p", + feature = "stm32wle5ub", + feature = "stm32g0c1ve", + feature = "stm32wl55jc-cm4", + feature = "stm32wl55uc-cm4", + feature = "stm32g491re", + feature = "stm32g473cc", + feature = "stm32u585zi", + feature = "stm32wle5jb" + )))] + let config_rtcsel = stm32_metapac::rtc::vals::Rtcsel(config_rtcsel); + #[cfg(feature = "stm32g0c1ve")] + let config_rtcsel = stm32_metapac::rcc::vals::Rtcsel(config_rtcsel); + + if !reg.rtcen() || reg.rtcsel() != config_rtcsel { + crate::pac::RCC.bdcr().modify(|w| w.set_bdrst(true)); + + crate::pac::RCC.bdcr().modify(|w| { + // Reset + w.set_bdrst(false); + + // Select RTC source + w.set_rtcsel(config_rtcsel); + + w.set_rtcen(true); + + // Restore bcdr + w.set_lscosel(reg.lscosel()); + w.set_lscoen(reg.lscoen()); + + w.set_lseon(reg.lseon()); + w.set_lsedrv(reg.lsedrv()); + w.set_lsebyp(reg.lsebyp()); + }); + } + } + + self.write(true, |rtc| { + unsafe { + rtc.cr().modify(|w| { + w.set_fmt(Fmt::TWENTYFOURHOUR); + w.set_osel(Osel::DISABLED); + w.set_pol(Pol::HIGH); + }); + + rtc.prer().modify(|w| { + w.set_prediv_s(rtc_config.sync_prescaler); + w.set_prediv_a(rtc_config.async_prescaler); + }); + + // TODO: configuration for output pins + rtc.cr().modify(|w| { + w.set_out2en(false); + w.set_tampalrm_type(TampalrmType::PUSHPULL); + w.set_tampalrm_pu(TampalrmPu::NOPULLUP); + }); + } + }); + + self.rtc_config = rtc_config; + } + + const RTC_CALR_MIN_PPM: f32 = -487.1; + const RTC_CALR_MAX_PPM: f32 = 488.5; + const RTC_CALR_RESOLUTION_PPM: f32 = 0.9537; + + /// Calibrate the clock drift. + /// + /// `clock_drift` can be adjusted from -487.1 ppm to 488.5 ppm and is clamped to this range. + /// + /// ### Note + /// + /// To perform a calibration when `async_prescaler` is less then 3, `sync_prescaler` + /// has to be reduced accordingly (see RM0351 Rev 9, sec 38.3.12). + pub fn calibrate(&mut self, mut clock_drift: f32, period: RtcCalibrationCyclePeriod) { + if clock_drift < Self::RTC_CALR_MIN_PPM { + clock_drift = Self::RTC_CALR_MIN_PPM; + } else if clock_drift > Self::RTC_CALR_MAX_PPM { + clock_drift = Self::RTC_CALR_MAX_PPM; + } + + clock_drift = clock_drift / Self::RTC_CALR_RESOLUTION_PPM; + + self.write(false, |rtc| { + unsafe { + rtc.calr().write(|w| { + match period { + RtcCalibrationCyclePeriod::Seconds8 => { + w.set_calw8(Calw8::EIGHTSECONDS); + } + RtcCalibrationCyclePeriod::Seconds16 => { + w.set_calw16(Calw16::SIXTEENSECONDS); + } + RtcCalibrationCyclePeriod::Seconds32 => { + // Set neither `calw8` nor `calw16` to use 32 seconds + } + } + + // Extra pulses during calibration cycle period: CALP * 512 - CALM + // + // CALP sets whether pulses are added or omitted. + // + // CALM contains how many pulses (out of 512) are masked in a + // given calibration cycle period. + if clock_drift > 0.0 { + // Maximum (about 512.2) rounds to 512. + clock_drift += 0.5; + + // When the offset is positive (0 to 512), the opposite of + // the offset (512 - offset) is masked, i.e. for the + // maximum offset (512), 0 pulses are masked. + w.set_calp(Calp::INCREASEFREQ); + w.set_calm(512 - clock_drift as u16); + } else { + // Minimum (about -510.7) rounds to -511. + clock_drift -= 0.5; + + // When the offset is negative or zero (-511 to 0), + // the absolute offset is masked, i.e. for the minimum + // offset (-511), 511 pulses are masked. + w.set_calp(Calp::NOCHANGE); + w.set_calm((clock_drift * -1.0) as u16); + } + }); + } + }) + } + + pub(super) fn write(&mut self, init_mode: bool, f: F) -> R + where + F: FnOnce(&crate::pac::rtc::Rtc) -> R, + { + let r = T::regs(); + // Disable write protection. + // This is safe, as we're only writin the correct and expected values. + unsafe { + r.wpr().write(|w| w.set_key(Key::DEACTIVATE1)); + r.wpr().write(|w| w.set_key(Key::DEACTIVATE2)); + + if init_mode && !r.icsr().read().initf() { + r.icsr().modify(|w| w.set_init(Init::INITMODE)); + // wait till init state entered + // ~2 RTCCLK cycles + while !r.icsr().read().initf() {} + } + } + + let result = f(&r); + + unsafe { + if init_mode { + r.icsr().modify(|w| w.set_init(Init::FREERUNNINGMODE)); // Exits init mode + } + + // Re-enable write protection. + // This is safe, as the field accepts the full range of 8-bit values. + r.wpr().write(|w| w.set_key(Key::ACTIVATE)); + } + result + } +} + +pub(super) unsafe fn enable_peripheral_clk() { + // Nothing to do +} + +pub const BACKUP_REGISTER_COUNT: usize = 32; + +/// Read content of the backup register. +/// +/// The registers retain their values during wakes from standby mode or system resets. They also +/// retain their value when Vdd is switched off as long as V_BAT is powered. +pub fn read_backup_register(_rtc: &Rtc, register: usize) -> Option { + if register < BACKUP_REGISTER_COUNT { + //Some(rtc.bkpr()[register].read().bits()) + None // RTC3 backup registers come from the TAMP peripe=heral, not RTC. Not() even in the L412 PAC + } else { + None + } +} + +/// Set content of the backup register. +/// +/// The registers retain their values during wakes from standby mode or system resets. They also +/// retain their value when Vdd is switched off as long as V_BAT is powered. +pub fn write_backup_register(_rtc: &Rtc, register: usize, _value: u32) { + if register < BACKUP_REGISTER_COUNT { + // RTC3 backup registers come from the TAMP peripe=heral, not RTC. Not() even in the L412 PAC + //unsafe { self.rtc.bkpr()[register].write(|w| w.bits(value)) } + } +} diff --git a/embassy-stm32/src/sdmmc/mod.rs b/embassy-stm32/src/sdmmc/mod.rs index 03d24dcb..433f73d7 100644 --- a/embassy-stm32/src/sdmmc/mod.rs +++ b/embassy-stm32/src/sdmmc/mod.rs @@ -41,7 +41,7 @@ impl Default for Signalling { } #[repr(align(4))] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct DataBlock(pub [u8; 512]); @@ -61,7 +61,7 @@ impl DerefMut for DataBlock { /// Errors #[non_exhaustive] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Error { Timeout, @@ -135,60 +135,71 @@ enum Response { Long = 3, } -cfg_if::cfg_if! { - if #[cfg(sdmmc_v1)] { - /// Calculate clock divisor. Returns a SDMMC_CK less than or equal to - /// `sdmmc_ck` in Hertz. - /// - /// Returns `(bypass, clk_div, clk_f)`, where `bypass` enables clock divisor bypass (only sdmmc_v1), - /// `clk_div` is the divisor register value and `clk_f` is the resulting new clock frequency. - fn clk_div(ker_ck: Hertz, sdmmc_ck: u32) -> Result<(bool, u8, Hertz), Error> { - // sdmmc_v1 maximum clock is 50 MHz - if sdmmc_ck > 50_000_000 { - return Err(Error::BadClock); - } +/// Calculate clock divisor. Returns a SDMMC_CK less than or equal to +/// `sdmmc_ck` in Hertz. +/// +/// Returns `(bypass, clk_div, clk_f)`, where `bypass` enables clock divisor bypass (only sdmmc_v1), +/// `clk_div` is the divisor register value and `clk_f` is the resulting new clock frequency. +#[cfg(sdmmc_v1)] +fn clk_div(ker_ck: Hertz, sdmmc_ck: u32) -> Result<(bool, u8, Hertz), Error> { + // sdmmc_v1 maximum clock is 50 MHz + if sdmmc_ck > 50_000_000 { + return Err(Error::BadClock); + } - // bypass divisor - if ker_ck.0 <= sdmmc_ck { - return Ok((true, 0, ker_ck)); - } + // bypass divisor + if ker_ck.0 <= sdmmc_ck { + return Ok((true, 0, ker_ck)); + } - // `ker_ck / sdmmc_ck` rounded up - let clk_div = match (ker_ck.0 + sdmmc_ck - 1) / sdmmc_ck { - 0 | 1 => Ok(0), - x @ 2..=258 => { - Ok((x - 2) as u8) - } - _ => Err(Error::BadClock), - }?; + // `ker_ck / sdmmc_ck` rounded up + let clk_div = match (ker_ck.0 + sdmmc_ck - 1) / sdmmc_ck { + 0 | 1 => Ok(0), + x @ 2..=258 => Ok((x - 2) as u8), + _ => Err(Error::BadClock), + }?; - // SDIO_CK frequency = SDIOCLK / [CLKDIV + 2] - let clk_f = Hertz(ker_ck.0 / (clk_div as u32 + 2)); - Ok((false, clk_div, clk_f)) - } - } else if #[cfg(sdmmc_v2)] { - /// Calculate clock divisor. Returns a SDMMC_CK less than or equal to - /// `sdmmc_ck` in Hertz. - /// - /// Returns `(bypass, clk_div, clk_f)`, where `bypass` enables clock divisor bypass (only sdmmc_v1), - /// `clk_div` is the divisor register value and `clk_f` is the resulting new clock frequency. - fn clk_div(ker_ck: Hertz, sdmmc_ck: u32) -> Result<(bool, u16, Hertz), Error> { - // `ker_ck / sdmmc_ck` rounded up - match (ker_ck.0 + sdmmc_ck - 1) / sdmmc_ck { - 0 | 1 => Ok((false, 0, ker_ck)), - x @ 2..=2046 => { - // SDMMC_CK frequency = SDMMCCLK / [CLKDIV + 2] - let clk_div = ((x + 1) / 2) as u16; - let clk = Hertz(ker_ck.0 / (clk_div as u32 * 2)); - - Ok((false, clk_div, clk)) - } - _ => Err(Error::BadClock), - } + // SDIO_CK frequency = SDIOCLK / [CLKDIV + 2] + let clk_f = Hertz(ker_ck.0 / (clk_div as u32 + 2)); + Ok((false, clk_div, clk_f)) +} + +/// Calculate clock divisor. Returns a SDMMC_CK less than or equal to +/// `sdmmc_ck` in Hertz. +/// +/// Returns `(bypass, clk_div, clk_f)`, where `bypass` enables clock divisor bypass (only sdmmc_v1), +/// `clk_div` is the divisor register value and `clk_f` is the resulting new clock frequency. +#[cfg(sdmmc_v2)] +fn clk_div(ker_ck: Hertz, sdmmc_ck: u32) -> Result<(bool, u16, Hertz), Error> { + // `ker_ck / sdmmc_ck` rounded up + match (ker_ck.0 + sdmmc_ck - 1) / sdmmc_ck { + 0 | 1 => Ok((false, 0, ker_ck)), + x @ 2..=2046 => { + // SDMMC_CK frequency = SDMMCCLK / [CLKDIV * 2] + let clk_div = ((x + 1) / 2) as u16; + let clk = Hertz(ker_ck.0 / (clk_div as u32 * 2)); + + Ok((false, clk_div, clk)) } + _ => Err(Error::BadClock), } } +#[cfg(sdmmc_v1)] +type Transfer<'a, C> = crate::dma::Transfer<'a, C>; +#[cfg(sdmmc_v2)] +type Transfer<'a, C> = core::marker::PhantomData<&'a mut C>; + +#[cfg(all(sdmmc_v1, dma))] +const DMA_TRANSFER_OPTIONS: crate::dma::TransferOptions = crate::dma::TransferOptions { + pburst: crate::dma::Burst::Incr4, + mburst: crate::dma::Burst::Incr4, + flow_ctrl: crate::dma::FlowControl::Peripheral, + fifo_threshold: Some(crate::dma::FifoThreshold::Full), +}; +#[cfg(all(sdmmc_v1, not(dma)))] +const DMA_TRANSFER_OPTIONS: crate::dma::TransferOptions = crate::dma::TransferOptions {}; + /// SDMMC configuration /// /// Default values: @@ -208,9 +219,10 @@ impl Default for Config { } /// Sdmmc device -pub struct Sdmmc<'d, T: Instance, Dma = NoDma> { +pub struct Sdmmc<'d, T: Instance, Dma: SdmmcDma = NoDma> { _peri: PeripheralRef<'d, T>, irq: PeripheralRef<'d, T::Interrupt>, + #[allow(unused)] dma: PeripheralRef<'d, Dma>, clk: PeripheralRef<'d, AnyPin>, @@ -309,49 +321,6 @@ impl<'d, T: Instance, Dma: SdmmcDma> Sdmmc<'d, T, Dma> { config, ) } - - fn new_inner( - sdmmc: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, - dma: impl Peripheral

+ 'd, - clk: PeripheralRef<'d, AnyPin>, - cmd: PeripheralRef<'d, AnyPin>, - d0: PeripheralRef<'d, AnyPin>, - d1: Option>, - d2: Option>, - d3: Option>, - config: Config, - ) -> Self { - into_ref!(sdmmc, irq, dma); - - T::enable(); - T::reset(); - - let inner = T::inner(); - unsafe { inner.new_inner() }; - - irq.set_handler(Self::on_interrupt); - irq.unpend(); - irq.enable(); - - Self { - _peri: sdmmc, - irq, - dma, - - clk, - cmd, - d0, - d1, - d2, - d3, - - config, - clock: SD_INIT_FREQ, - signalling: Default::default(), - card: None, - } - } } #[cfg(sdmmc_v2)] @@ -379,6 +348,7 @@ impl<'d, T: Instance> Sdmmc<'d, T, NoDma> { Self::new_inner( sdmmc, irq, + NoDma.into_ref(), clk.map_into(), cmd.map_into(), d0.map_into(), @@ -421,6 +391,7 @@ impl<'d, T: Instance> Sdmmc<'d, T, NoDma> { Self::new_inner( sdmmc, irq, + NoDma.into_ref(), clk.map_into(), cmd.map_into(), d0.map_into(), @@ -430,10 +401,13 @@ impl<'d, T: Instance> Sdmmc<'d, T, NoDma> { config, ) } +} +impl<'d, T: Instance, Dma: SdmmcDma + 'd> Sdmmc<'d, T, Dma> { fn new_inner( sdmmc: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd, + dma: impl Peripheral

+ 'd, clk: PeripheralRef<'d, AnyPin>, cmd: PeripheralRef<'d, AnyPin>, d0: PeripheralRef<'d, AnyPin>, @@ -442,22 +416,41 @@ impl<'d, T: Instance> Sdmmc<'d, T, NoDma> { d3: Option>, config: Config, ) -> Self { - into_ref!(sdmmc, irq); + into_ref!(sdmmc, irq, dma); T::enable(); T::reset(); - let inner = T::inner(); - unsafe { inner.new_inner() }; - irq.set_handler(Self::on_interrupt); irq.unpend(); irq.enable(); + let regs = T::regs(); + unsafe { + regs.clkcr().write(|w| { + w.set_pwrsav(false); + w.set_negedge(false); + + // Hardware flow control is broken on SDIOv1 and causes clock glitches, which result in CRC errors. + // See chip erratas for more details. + #[cfg(sdmmc_v1)] + w.set_hwfc_en(false); + #[cfg(sdmmc_v2)] + w.set_hwfc_en(true); + + #[cfg(sdmmc_v1)] + w.set_clken(true); + }); + + // Power off, writen 00: Clock to the card is stopped; + // D[7:0], CMD, and CK are driven high. + regs.power().modify(|w| w.set_pwrctrl(PowerCtrl::Off as u8)); + } + Self { _peri: sdmmc, irq, - dma: NoDma.into_ref(), + dma, clk, cmd, @@ -472,593 +465,169 @@ impl<'d, T: Instance> Sdmmc<'d, T, NoDma> { card: None, } } -} - -impl<'d, T: Instance, Dma: SdmmcDma> Sdmmc<'d, T, Dma> { - #[inline(always)] - pub async fn init_card(&mut self, freq: Hertz) -> Result<(), Error> { - let inner = T::inner(); - let freq = freq.into(); - - let bus_width = match self.d3.is_some() { - true => BusWidth::Four, - false => BusWidth::One, - }; - - inner - .init_card( - freq, - bus_width, - &mut self.card, - &mut self.signalling, - T::kernel_clk(), - &mut self.clock, - T::state(), - self.config.data_transfer_timeout, - &mut *self.dma, - ) - .await - } - - #[inline(always)] - pub async fn read_block(&mut self, block_idx: u32, buffer: &mut DataBlock) -> Result<(), Error> { - let card_capacity = self.card()?.card_type; - let inner = T::inner(); - let state = T::state(); - - // NOTE(unsafe) DataBlock uses align 4 - let buf = unsafe { &mut *((&mut buffer.0) as *mut [u8; 512] as *mut [u32; 128]) }; - inner - .read_block( - block_idx, - buf, - card_capacity, - state, - self.config.data_transfer_timeout, - &mut *self.dma, - ) - .await - } - - pub async fn write_block(&mut self, block_idx: u32, buffer: &DataBlock) -> Result<(), Error> { - let card = self.card.as_mut().ok_or(Error::NoCard)?; - let inner = T::inner(); - let state = T::state(); - - // NOTE(unsafe) DataBlock uses align 4 - let buf = unsafe { &*((&buffer.0) as *const [u8; 512] as *const [u32; 128]) }; - inner - .write_block( - block_idx, - buf, - card, - state, - self.config.data_transfer_timeout, - &mut *self.dma, - ) - .await - } - - /// Get a reference to the initialized card - /// - /// # Errors - /// - /// Returns Error::NoCard if [`init_card`](#method.init_card) - /// has not previously succeeded - #[inline(always)] - pub fn card(&self) -> Result<&Card, Error> { - self.card.as_ref().ok_or(Error::NoCard) - } - - /// Get the current SDMMC bus clock - pub fn clock(&self) -> Hertz { - self.clock - } - - #[inline(always)] - fn on_interrupt(_: *mut ()) { - let regs = T::inner(); - let state = T::state(); - - regs.data_interrupts(false); - state.wake(); - } -} - -impl<'d, T: Instance, Dma> Drop for Sdmmc<'d, T, Dma> { - fn drop(&mut self) { - self.irq.disable(); - let inner = T::inner(); - unsafe { inner.on_drop() }; - - critical_section::with(|_| unsafe { - self.clk.set_as_disconnected(); - self.cmd.set_as_disconnected(); - self.d0.set_as_disconnected(); - if let Some(x) = &mut self.d1 { - x.set_as_disconnected(); - } - if let Some(x) = &mut self.d2 { - x.set_as_disconnected(); - } - if let Some(x) = &mut self.d3 { - x.set_as_disconnected(); - } - }); - } -} - -pub struct SdmmcInner(pub(crate) RegBlock); - -impl SdmmcInner { - /// # Safety - /// - /// Access to `regs` registers should be exclusive - unsafe fn new_inner(&self) { - let regs = self.0; - - regs.clkcr().write(|w| { - w.set_pwrsav(false); - w.set_negedge(false); - - // Hardware flow control is broken on SDIOv1 and causes clock glitches, which result in CRC errors. - // See chip erratas for more details. - #[cfg(sdmmc_v1)] - w.set_hwfc_en(false); - #[cfg(sdmmc_v2)] - w.set_hwfc_en(true); - - #[cfg(sdmmc_v1)] - w.set_clken(true); - }); - - // Power off, writen 00: Clock to the card is stopped; - // D[7:0], CMD, and CK are driven high. - regs.power().modify(|w| w.set_pwrctrl(PowerCtrl::Off as u8)); - } - - /// Initializes card (if present) and sets the bus at the - /// specified frequency. - #[allow(clippy::too_many_arguments)] - async fn init_card>( - &self, - freq: Hertz, - bus_width: BusWidth, - old_card: &mut Option, - signalling: &mut Signalling, - ker_ck: Hertz, - clock: &mut Hertz, - waker_reg: &AtomicWaker, - data_transfer_timeout: u32, - dma: &mut Dma, - ) -> Result<(), Error> { - let regs = self.0; - - // NOTE(unsafe) We have exclusive access to the peripheral - unsafe { - // While the SD/SDIO card or eMMC is in identification mode, - // the SDMMC_CK frequency must be no more than 400 kHz. - let (_bypass, clkdiv, init_clock) = unwrap!(clk_div(ker_ck, SD_INIT_FREQ.0)); - *clock = init_clock; - - // CPSMACT and DPSMACT must be 0 to set WIDBUS - self.wait_idle(); - - regs.clkcr().modify(|w| { - w.set_widbus(0); - w.set_clkdiv(clkdiv); - #[cfg(sdmmc_v1)] - w.set_bypass(_bypass); - }); - - regs.power().modify(|w| w.set_pwrctrl(PowerCtrl::On as u8)); - self.cmd(Cmd::idle(), false)?; - - // Check if cards supports CMD8 (with pattern) - self.cmd(Cmd::hs_send_ext_csd(0x1AA), false)?; - let r1 = regs.respr(0).read().cardstatus(); - - let mut card = if r1 == 0x1AA { - // Card echoed back the pattern. Must be at least v2 - Card::default() - } else { - return Err(Error::UnsupportedCardVersion); - }; - - let ocr = loop { - // Signal that next command is a app command - self.cmd(Cmd::app_cmd(0), false)?; // CMD55 - - let arg = CmdAppOper::VOLTAGE_WINDOW_SD as u32 - | CmdAppOper::HIGH_CAPACITY as u32 - | CmdAppOper::SD_SWITCH_1_8V_CAPACITY as u32; - - // Initialize card - match self.cmd(Cmd::app_op_cmd(arg), false) { - // ACMD41 - Ok(_) => (), - Err(Error::Crc) => (), - Err(err) => return Err(err), - } - let ocr: OCR = regs.respr(0).read().cardstatus().into(); - if !ocr.is_busy() { - // Power up done - break ocr; - } - }; - - if ocr.high_capacity() { - // Card is SDHC or SDXC or SDUC - card.card_type = CardCapacity::SDHC; - } else { - card.card_type = CardCapacity::SDSC; - } - card.ocr = ocr; - - self.cmd(Cmd::all_send_cid(), false)?; // CMD2 - let cid0 = regs.respr(0).read().cardstatus() as u128; - let cid1 = regs.respr(1).read().cardstatus() as u128; - let cid2 = regs.respr(2).read().cardstatus() as u128; - let cid3 = regs.respr(3).read().cardstatus() as u128; - let cid = (cid0 << 96) | (cid1 << 64) | (cid2 << 32) | (cid3); - card.cid = cid.into(); - - self.cmd(Cmd::send_rel_addr(), false)?; - card.rca = regs.respr(0).read().cardstatus() >> 16; - - self.cmd(Cmd::send_csd(card.rca << 16), false)?; - let csd0 = regs.respr(0).read().cardstatus() as u128; - let csd1 = regs.respr(1).read().cardstatus() as u128; - let csd2 = regs.respr(2).read().cardstatus() as u128; - let csd3 = regs.respr(3).read().cardstatus() as u128; - let csd = (csd0 << 96) | (csd1 << 64) | (csd2 << 32) | (csd3); - card.csd = csd.into(); - - self.select_card(Some(&card))?; - - self.get_scr(&mut card, waker_reg, data_transfer_timeout, dma).await?; - - // Set bus width - let (width, acmd_arg) = match bus_width { - BusWidth::Eight => unimplemented!(), - BusWidth::Four if card.scr.bus_width_four() => (BusWidth::Four, 2), - _ => (BusWidth::One, 0), - }; - self.cmd(Cmd::app_cmd(card.rca << 16), false)?; - self.cmd(Cmd::cmd6(acmd_arg), false)?; - - // CPSMACT and DPSMACT must be 0 to set WIDBUS - self.wait_idle(); - - regs.clkcr().modify(|w| { - w.set_widbus(match width { - BusWidth::One => 0, - BusWidth::Four => 1, - BusWidth::Eight => 2, - _ => panic!("Invalid Bus Width"), - }) - }); - - // Set Clock - if freq.0 <= 25_000_000 { - // Final clock frequency - self.clkcr_set_clkdiv(freq.0, width, ker_ck, clock)?; - } else { - // Switch to max clock for SDR12 - self.clkcr_set_clkdiv(25_000_000, width, ker_ck, clock)?; - } - - // Read status - self.read_sd_status(&mut card, waker_reg, data_transfer_timeout, dma) - .await?; - - if freq.0 > 25_000_000 { - // Switch to SDR25 - *signalling = self - .switch_signalling_mode(Signalling::SDR25, waker_reg, data_transfer_timeout, dma) - .await?; - - if *signalling == Signalling::SDR25 { - // Set final clock frequency - self.clkcr_set_clkdiv(freq.0, width, ker_ck, clock)?; - - if self.read_status(&card)?.state() != CurrentState::Transfer { - return Err(Error::SignalingSwitchFailed); - } - } - } - // Read status after signalling change - self.read_sd_status(&mut card, waker_reg, data_transfer_timeout, dma) - .await?; - old_card.replace(card); - } - - Ok(()) - } - - async fn read_block>( - &self, - block_idx: u32, - buffer: &mut [u32; 128], - capacity: CardCapacity, - waker_reg: &AtomicWaker, - data_transfer_timeout: u32, - dma: &mut Dma, - ) -> Result<(), Error> { - // Always read 1 block of 512 bytes - // SDSC cards are byte addressed hence the blockaddress is in multiples of 512 bytes - let address = match capacity { - CardCapacity::SDSC => block_idx * 512, - _ => block_idx, - }; - self.cmd(Cmd::set_block_length(512), false)?; // CMD16 - - let regs = self.0; - let on_drop = OnDrop::new(|| unsafe { self.on_drop() }); - - unsafe { - self.prepare_datapath_read(buffer as *mut [u32; 128], 512, 9, data_transfer_timeout, dma); - self.data_interrupts(true); - } - self.cmd(Cmd::read_single_block(address), true)?; - - let res = poll_fn(|cx| { - waker_reg.register(cx.waker()); - let status = unsafe { regs.star().read() }; - - if status.dcrcfail() { - return Poll::Ready(Err(Error::Crc)); - } else if status.dtimeout() { - return Poll::Ready(Err(Error::Timeout)); - } else if status.dataend() { - return Poll::Ready(Ok(())); - } - Poll::Pending - }) - .await; - self.clear_interrupt_flags(); - - if res.is_ok() { - on_drop.defuse(); - self.stop_datapath(); - } - res - } - - async fn write_block>( - &self, - block_idx: u32, - buffer: &[u32; 128], - card: &mut Card, - waker_reg: &AtomicWaker, - data_transfer_timeout: u32, - dma: &mut Dma, - ) -> Result<(), Error> { - // Always read 1 block of 512 bytes - // SDSC cards are byte addressed hence the blockaddress is in multiples of 512 bytes - let address = match card.card_type { - CardCapacity::SDSC => block_idx * 512, - _ => block_idx, - }; - self.cmd(Cmd::set_block_length(512), false)?; // CMD16 - - let regs = self.0; - let on_drop = OnDrop::new(|| unsafe { self.on_drop() }); - - // sdmmc_v1 uses different cmd/dma order than v2, but only for writes - #[cfg(sdmmc_v1)] - self.cmd(Cmd::write_single_block(address), true)?; - - unsafe { - self.prepare_datapath_write(buffer as *const [u32; 128], 512, 9, data_transfer_timeout, dma); - self.data_interrupts(true); - } - - #[cfg(sdmmc_v2)] - self.cmd(Cmd::write_single_block(address), true)?; - - let res = poll_fn(|cx| { - waker_reg.register(cx.waker()); - let status = unsafe { regs.star().read() }; - - if status.dcrcfail() { - return Poll::Ready(Err(Error::Crc)); - } else if status.dtimeout() { - return Poll::Ready(Err(Error::Timeout)); - } else if status.dataend() { - return Poll::Ready(Ok(())); - } - Poll::Pending - }) - .await; - self.clear_interrupt_flags(); - - match res { - Ok(_) => { - on_drop.defuse(); - self.stop_datapath(); - - // TODO: Make this configurable - let mut timeout: u32 = 0x00FF_FFFF; - - // Try to read card status (ACMD13) - while timeout > 0 { - match self.read_sd_status(card, waker_reg, data_transfer_timeout, dma).await { - Ok(_) => return Ok(()), - Err(Error::Timeout) => (), // Try again - Err(e) => return Err(e), - } - timeout -= 1; - } - Err(Error::SoftwareTimeout) - } - Err(e) => Err(e), - } - } /// Data transfer is in progress #[inline(always)] - fn data_active(&self) -> bool { - let regs = self.0; + fn data_active() -> bool { + let regs = T::regs(); // NOTE(unsafe) Atomic read with no side-effects unsafe { let status = regs.star().read(); - cfg_if::cfg_if! { - if #[cfg(sdmmc_v1)] { - status.rxact() || status.txact() - } else if #[cfg(sdmmc_v2)] { - status.dpsmact() - } - } + #[cfg(sdmmc_v1)] + return status.rxact() || status.txact(); + #[cfg(sdmmc_v2)] + return status.dpsmact(); } } /// Coammand transfer is in progress #[inline(always)] - fn cmd_active(&self) -> bool { - let regs = self.0; + fn cmd_active() -> bool { + let regs = T::regs(); // NOTE(unsafe) Atomic read with no side-effects unsafe { let status = regs.star().read(); - cfg_if::cfg_if! { - if #[cfg(sdmmc_v1)] { - status.cmdact() - } else if #[cfg(sdmmc_v2)] { - status.cpsmact() - } - } + #[cfg(sdmmc_v1)] + return status.cmdact(); + #[cfg(sdmmc_v2)] + return status.cpsmact(); } } /// Wait idle on CMDACT, RXACT and TXACT (v1) or DOSNACT and CPSMACT (v2) #[inline(always)] - fn wait_idle(&self) { - while self.data_active() || self.cmd_active() {} + fn wait_idle() { + while Self::data_active() || Self::cmd_active() {} } /// # Safety /// /// `buffer` must be valid for the whole transfer and word aligned - unsafe fn prepare_datapath_read>( - &self, - buffer: *mut [u32], + fn prepare_datapath_read<'a>( + &'a mut self, + buffer: &'a mut [u32], length_bytes: u32, block_size: u8, - data_transfer_timeout: u32, - #[allow(unused_variables)] dma: &mut Dma, - ) { + ) -> Transfer<'a, Dma> { assert!(block_size <= 14, "Block size up to 2^14 bytes"); - let regs = self.0; + let regs = T::regs(); // Command AND Data state machines must be idle - self.wait_idle(); - self.clear_interrupt_flags(); + Self::wait_idle(); + Self::clear_interrupt_flags(); // NOTE(unsafe) We have exclusive access to the regisers + unsafe { + regs.dtimer() + .write(|w| w.set_datatime(self.config.data_transfer_timeout)); + regs.dlenr().write(|w| w.set_datalength(length_bytes)); - regs.dtimer().write(|w| w.set_datatime(data_transfer_timeout)); - regs.dlenr().write(|w| w.set_datalength(length_bytes)); - - cfg_if::cfg_if! { - if #[cfg(sdmmc_v1)] { - let request = dma.request(); - dma.start_read(request, regs.fifor().ptr() as *const u32, buffer, crate::dma::TransferOptions { - pburst: crate::dma::Burst::Incr4, - mburst: crate::dma::Burst::Incr4, - flow_ctrl: crate::dma::FlowControl::Peripheral, - fifo_threshold: Some(crate::dma::FifoThreshold::Full), - ..Default::default() - }); - } else if #[cfg(sdmmc_v2)] { - regs.idmabase0r().write(|w| w.set_idmabase0(buffer as *mut u32 as u32)); - regs.idmactrlr().modify(|w| w.set_idmaen(true)); - } - } - - regs.dctrl().modify(|w| { - w.set_dblocksize(block_size); - w.set_dtdir(true); #[cfg(sdmmc_v1)] - { - w.set_dmaen(true); - w.set_dten(true); - } - }); + let transfer = { + let request = self.dma.request(); + Transfer::new_read( + &mut self.dma, + request, + regs.fifor().ptr() as *mut u32, + buffer, + DMA_TRANSFER_OPTIONS, + ) + }; + #[cfg(sdmmc_v2)] + let transfer = { + regs.idmabase0r().write(|w| w.set_idmabase0(buffer.as_mut_ptr() as u32)); + regs.idmactrlr().modify(|w| w.set_idmaen(true)); + core::marker::PhantomData + }; + + regs.dctrl().modify(|w| { + w.set_dblocksize(block_size); + w.set_dtdir(true); + #[cfg(sdmmc_v1)] + { + w.set_dmaen(true); + w.set_dten(true); + } + }); + + transfer + } } /// # Safety /// /// `buffer` must be valid for the whole transfer and word aligned - unsafe fn prepare_datapath_write>( - &self, - buffer: *const [u32], + fn prepare_datapath_write<'a>( + &'a mut self, + buffer: &'a [u32], length_bytes: u32, block_size: u8, - data_transfer_timeout: u32, - #[allow(unused_variables)] dma: &mut Dma, - ) { + ) -> Transfer<'a, Dma> { assert!(block_size <= 14, "Block size up to 2^14 bytes"); - let regs = self.0; + let regs = T::regs(); // Command AND Data state machines must be idle - self.wait_idle(); - self.clear_interrupt_flags(); + Self::wait_idle(); + Self::clear_interrupt_flags(); // NOTE(unsafe) We have exclusive access to the regisers + unsafe { + regs.dtimer() + .write(|w| w.set_datatime(self.config.data_transfer_timeout)); + regs.dlenr().write(|w| w.set_datalength(length_bytes)); - regs.dtimer().write(|w| w.set_datatime(data_transfer_timeout)); - regs.dlenr().write(|w| w.set_datalength(length_bytes)); - - cfg_if::cfg_if! { - if #[cfg(sdmmc_v1)] { - let request = dma.request(); - dma.start_write(request, buffer, regs.fifor().ptr() as *mut u32, crate::dma::TransferOptions { - pburst: crate::dma::Burst::Incr4, - mburst: crate::dma::Burst::Incr4, - flow_ctrl: crate::dma::FlowControl::Peripheral, - fifo_threshold: Some(crate::dma::FifoThreshold::Full), - ..Default::default() - }); - } else if #[cfg(sdmmc_v2)] { - regs.idmabase0r().write(|w| w.set_idmabase0(buffer as *const u32 as u32)); - regs.idmactrlr().modify(|w| w.set_idmaen(true)); - } - } - - regs.dctrl().modify(|w| { - w.set_dblocksize(block_size); - w.set_dtdir(false); #[cfg(sdmmc_v1)] - { - w.set_dmaen(true); - w.set_dten(true); - } - }); + let transfer = { + let request = self.dma.request(); + Transfer::new_write( + &mut self.dma, + request, + buffer, + regs.fifor().ptr() as *mut u32, + DMA_TRANSFER_OPTIONS, + ) + }; + #[cfg(sdmmc_v2)] + let transfer = { + regs.idmabase0r().write(|w| w.set_idmabase0(buffer.as_ptr() as u32)); + regs.idmactrlr().modify(|w| w.set_idmaen(true)); + core::marker::PhantomData + }; + + regs.dctrl().modify(|w| { + w.set_dblocksize(block_size); + w.set_dtdir(false); + #[cfg(sdmmc_v1)] + { + w.set_dmaen(true); + w.set_dten(true); + } + }); + + transfer + } } /// Stops the DMA datapath - fn stop_datapath(&self) { - let regs = self.0; + fn stop_datapath() { + let regs = T::regs(); unsafe { - cfg_if::cfg_if! { - if #[cfg(sdmmc_v1)] { - regs.dctrl().modify(|w| { - w.set_dmaen(false); - w.set_dten(false); - }); - } else if #[cfg(sdmmc_v2)] { - regs.idmactrlr().modify(|w| w.set_idmaen(false)); - } - } + #[cfg(sdmmc_v1)] + regs.dctrl().modify(|w| { + w.set_dmaen(false); + w.set_dten(false); + }); + #[cfg(sdmmc_v2)] + regs.idmactrlr().modify(|w| w.set_idmaen(false)); } } /// Sets the CLKDIV field in CLKCR. Updates clock field in self - fn clkcr_set_clkdiv(&self, freq: u32, width: BusWidth, ker_ck: Hertz, clock: &mut Hertz) -> Result<(), Error> { - let regs = self.0; + fn clkcr_set_clkdiv(&mut self, freq: u32, width: BusWidth) -> Result<(), Error> { + let regs = T::regs(); let width_u32 = match width { BusWidth::One => 1u32, @@ -1067,18 +636,19 @@ impl SdmmcInner { _ => panic!("Invalid Bus Width"), }; + let ker_ck = T::kernel_clk(); let (_bypass, clkdiv, new_clock) = clk_div(ker_ck, freq)?; // Enforce AHB and SDMMC_CK clock relation. See RM0433 Rev 7 // Section 55.5.8 let sdmmc_bus_bandwidth = new_clock.0 * width_u32; assert!(ker_ck.0 > 3 * sdmmc_bus_bandwidth / 32); - *clock = new_clock; + self.clock = new_clock; // NOTE(unsafe) We have exclusive access to the regblock unsafe { // CPSMACT and DPSMACT must be 0 to set CLKDIV - self.wait_idle(); + Self::wait_idle(); regs.clkcr().modify(|w| { w.set_clkdiv(clkdiv); #[cfg(sdmmc_v1)] @@ -1094,13 +664,7 @@ impl SdmmcInner { /// Attempt to set a new signalling mode. The selected /// signalling mode is returned. Expects the current clock /// frequency to be > 12.5MHz. - async fn switch_signalling_mode>( - &self, - signalling: Signalling, - waker_reg: &AtomicWaker, - data_transfer_timeout: u32, - dma: &mut Dma, - ) -> Result { + async fn switch_signalling_mode(&mut self, signalling: Signalling) -> Result { // NB PLSS v7_10 4.3.10.4: "the use of SET_BLK_LEN command is not // necessary" @@ -1117,17 +681,15 @@ impl SdmmcInner { let mut status = [0u32; 16]; // Arm `OnDrop` after the buffer, so it will be dropped first - let regs = self.0; - let on_drop = OnDrop::new(|| unsafe { self.on_drop() }); + let regs = T::regs(); + let on_drop = OnDrop::new(|| unsafe { Self::on_drop() }); - unsafe { - self.prepare_datapath_read(&mut status as *mut [u32; 16], 64, 6, data_transfer_timeout, dma); - self.data_interrupts(true); - } - self.cmd(Cmd::cmd6(set_function), true)?; // CMD6 + let transfer = self.prepare_datapath_read(&mut status, 64, 6); + Self::data_interrupts(true); + Self::cmd(Cmd::cmd6(set_function), true)?; // CMD6 let res = poll_fn(|cx| { - waker_reg.register(cx.waker()); + T::state().register(cx.waker()); let status = unsafe { regs.star().read() }; if status.dcrcfail() { @@ -1140,7 +702,7 @@ impl SdmmcInner { Poll::Pending }) .await; - self.clear_interrupt_flags(); + Self::clear_interrupt_flags(); // Host is allowed to use the new functions at least 8 // clocks after the end of the switch command @@ -1153,7 +715,8 @@ impl SdmmcInner { match res { Ok(_) => { on_drop.defuse(); - self.stop_datapath(); + Self::stop_datapath(); + drop(transfer); // Function Selection of Function Group 1 let selection = (u32::from_be(status[4]) >> 24) & 0xF; @@ -1173,10 +736,10 @@ impl SdmmcInner { /// Query the card status (CMD13, returns R1) fn read_status(&self, card: &Card) -> Result { - let regs = self.0; + let regs = T::regs(); let rca = card.rca; - self.cmd(Cmd::card_status(rca << 16), false)?; // CMD13 + Self::cmd(Cmd::card_status(rca << 16), false)?; // CMD13 // NOTE(unsafe) Atomic read with no side-effects let r1 = unsafe { regs.respr(0).read().cardstatus() }; @@ -1184,31 +747,25 @@ impl SdmmcInner { } /// Reads the SD Status (ACMD13) - async fn read_sd_status>( - &self, - card: &mut Card, - waker_reg: &AtomicWaker, - data_transfer_timeout: u32, - dma: &mut Dma, - ) -> Result<(), Error> { + async fn read_sd_status(&mut self) -> Result<(), Error> { + let card = self.card.as_mut().ok_or(Error::NoCard)?; let rca = card.rca; - self.cmd(Cmd::set_block_length(64), false)?; // CMD16 - self.cmd(Cmd::app_cmd(rca << 16), false)?; // APP + + Self::cmd(Cmd::set_block_length(64), false)?; // CMD16 + Self::cmd(Cmd::app_cmd(rca << 16), false)?; // APP let mut status = [0u32; 16]; // Arm `OnDrop` after the buffer, so it will be dropped first - let regs = self.0; - let on_drop = OnDrop::new(|| unsafe { self.on_drop() }); + let regs = T::regs(); + let on_drop = OnDrop::new(|| unsafe { Self::on_drop() }); - unsafe { - self.prepare_datapath_read(&mut status as *mut [u32; 16], 64, 6, data_transfer_timeout, dma); - self.data_interrupts(true); - } - self.cmd(Cmd::card_status(0), true)?; + let transfer = self.prepare_datapath_read(&mut status, 64, 6); + Self::data_interrupts(true); + Self::cmd(Cmd::card_status(0), true)?; let res = poll_fn(|cx| { - waker_reg.register(cx.waker()); + T::state().register(cx.waker()); let status = unsafe { regs.star().read() }; if status.dcrcfail() { @@ -1221,16 +778,17 @@ impl SdmmcInner { Poll::Pending }) .await; - self.clear_interrupt_flags(); + Self::clear_interrupt_flags(); if res.is_ok() { on_drop.defuse(); - self.stop_datapath(); + Self::stop_datapath(); + drop(transfer); for byte in status.iter_mut() { *byte = u32::from_be(*byte); } - card.status = status.into(); + self.card.as_mut().unwrap().status = status.into(); } res } @@ -1243,7 +801,7 @@ impl SdmmcInner { // Determine Relative Card Address (RCA) of given card let rca = card.map(|c| c.rca << 16).unwrap_or(0); - let r = self.cmd(Cmd::sel_desel_card(rca), false); + let r = Self::cmd(Cmd::sel_desel_card(rca), false); match (r, rca) { (Err(Error::Timeout), 0) => Ok(()), _ => r, @@ -1252,8 +810,8 @@ impl SdmmcInner { /// Clear flags in interrupt clear register #[inline(always)] - fn clear_interrupt_flags(&self) { - let regs = self.0; + fn clear_interrupt_flags() { + let regs = T::regs(); // NOTE(unsafe) Atomic write unsafe { regs.icr().write(|w| { @@ -1287,8 +845,8 @@ impl SdmmcInner { /// Enables the interrupts for data transfer #[inline(always)] - fn data_interrupts(&self, enable: bool) { - let regs = self.0; + fn data_interrupts(enable: bool) { + let regs = T::regs(); // NOTE(unsafe) Atomic write unsafe { regs.maskr().write(|w| { @@ -1302,31 +860,23 @@ impl SdmmcInner { } } - async fn get_scr>( - &self, - card: &mut Card, - waker_reg: &AtomicWaker, - data_transfer_timeout: u32, - dma: &mut Dma, - ) -> Result<(), Error> { + async fn get_scr(&mut self, card: &mut Card) -> Result<(), Error> { // Read the the 64-bit SCR register - self.cmd(Cmd::set_block_length(8), false)?; // CMD16 - self.cmd(Cmd::app_cmd(card.rca << 16), false)?; + Self::cmd(Cmd::set_block_length(8), false)?; // CMD16 + Self::cmd(Cmd::app_cmd(card.rca << 16), false)?; let mut scr = [0u32; 2]; // Arm `OnDrop` after the buffer, so it will be dropped first - let regs = self.0; - let on_drop = OnDrop::new(move || unsafe { self.on_drop() }); + let regs = T::regs(); + let on_drop = OnDrop::new(|| unsafe { Self::on_drop() }); - unsafe { - self.prepare_datapath_read(&mut scr as *mut [u32], 8, 3, data_transfer_timeout, dma); - self.data_interrupts(true); - } - self.cmd(Cmd::cmd51(), true)?; + let transfer = self.prepare_datapath_read(&mut scr[..], 8, 3); + Self::data_interrupts(true); + Self::cmd(Cmd::cmd51(), true)?; let res = poll_fn(|cx| { - waker_reg.register(cx.waker()); + T::state().register(cx.waker()); let status = unsafe { regs.star().read() }; if status.dcrcfail() { @@ -1339,11 +889,12 @@ impl SdmmcInner { Poll::Pending }) .await; - self.clear_interrupt_flags(); + Self::clear_interrupt_flags(); if res.is_ok() { on_drop.defuse(); - self.stop_datapath(); + Self::stop_datapath(); + drop(transfer); unsafe { let scr_bytes = &*(&scr as *const [u32; 2] as *const [u8; 8]); @@ -1355,14 +906,14 @@ impl SdmmcInner { /// Send command to card #[allow(unused_variables)] - fn cmd(&self, cmd: Cmd, data: bool) -> Result<(), Error> { - let regs = self.0; + fn cmd(cmd: Cmd, data: bool) -> Result<(), Error> { + let regs = T::regs(); - self.clear_interrupt_flags(); + Self::clear_interrupt_flags(); // NOTE(safety) Atomic operations unsafe { // CP state machine must be idle - while self.cmd_active() {} + while Self::cmd_active() {} // Command arg regs.argr().write(|w| w.set_cmdarg(cmd.arg)); @@ -1411,13 +962,13 @@ impl SdmmcInner { /// # Safety /// /// Ensure that `regs` has exclusive access to the regblocks - unsafe fn on_drop(&self) { - let regs = self.0; - if self.data_active() { - self.clear_interrupt_flags(); + unsafe fn on_drop() { + let regs = T::regs(); + if Self::data_active() { + Self::clear_interrupt_flags(); // Send abort // CP state machine must be idle - while self.cmd_active() {} + while Self::cmd_active() {} // Command arg regs.argr().write(|w| w.set_cmdarg(0)); @@ -1437,11 +988,318 @@ impl SdmmcInner { }); // Wait for the abort - while self.data_active() {} + while Self::data_active() {} } - self.data_interrupts(false); - self.clear_interrupt_flags(); - self.stop_datapath(); + Self::data_interrupts(false); + Self::clear_interrupt_flags(); + Self::stop_datapath(); + } + + /// Initializes card (if present) and sets the bus at the + /// specified frequency. + pub async fn init_card(&mut self, freq: Hertz) -> Result<(), Error> { + let regs = T::regs(); + let ker_ck = T::kernel_clk(); + + let bus_width = match self.d3.is_some() { + true => BusWidth::Four, + false => BusWidth::One, + }; + + // NOTE(unsafe) We have exclusive access to the peripheral + unsafe { + // While the SD/SDIO card or eMMC is in identification mode, + // the SDMMC_CK frequency must be no more than 400 kHz. + let (_bypass, clkdiv, init_clock) = unwrap!(clk_div(ker_ck, SD_INIT_FREQ.0)); + self.clock = init_clock; + + // CPSMACT and DPSMACT must be 0 to set WIDBUS + Self::wait_idle(); + + regs.clkcr().modify(|w| { + w.set_widbus(0); + w.set_clkdiv(clkdiv); + #[cfg(sdmmc_v1)] + w.set_bypass(_bypass); + }); + + regs.power().modify(|w| w.set_pwrctrl(PowerCtrl::On as u8)); + Self::cmd(Cmd::idle(), false)?; + + // Check if cards supports CMD8 (with pattern) + Self::cmd(Cmd::hs_send_ext_csd(0x1AA), false)?; + let r1 = regs.respr(0).read().cardstatus(); + + let mut card = if r1 == 0x1AA { + // Card echoed back the pattern. Must be at least v2 + Card::default() + } else { + return Err(Error::UnsupportedCardVersion); + }; + + let ocr = loop { + // Signal that next command is a app command + Self::cmd(Cmd::app_cmd(0), false)?; // CMD55 + + let arg = CmdAppOper::VOLTAGE_WINDOW_SD as u32 + | CmdAppOper::HIGH_CAPACITY as u32 + | CmdAppOper::SD_SWITCH_1_8V_CAPACITY as u32; + + // Initialize card + match Self::cmd(Cmd::app_op_cmd(arg), false) { + // ACMD41 + Ok(_) => (), + Err(Error::Crc) => (), + Err(err) => return Err(err), + } + let ocr: OCR = regs.respr(0).read().cardstatus().into(); + if !ocr.is_busy() { + // Power up done + break ocr; + } + }; + + if ocr.high_capacity() { + // Card is SDHC or SDXC or SDUC + card.card_type = CardCapacity::SDHC; + } else { + card.card_type = CardCapacity::SDSC; + } + card.ocr = ocr; + + Self::cmd(Cmd::all_send_cid(), false)?; // CMD2 + let cid0 = regs.respr(0).read().cardstatus() as u128; + let cid1 = regs.respr(1).read().cardstatus() as u128; + let cid2 = regs.respr(2).read().cardstatus() as u128; + let cid3 = regs.respr(3).read().cardstatus() as u128; + let cid = (cid0 << 96) | (cid1 << 64) | (cid2 << 32) | (cid3); + card.cid = cid.into(); + + Self::cmd(Cmd::send_rel_addr(), false)?; + card.rca = regs.respr(0).read().cardstatus() >> 16; + + Self::cmd(Cmd::send_csd(card.rca << 16), false)?; + let csd0 = regs.respr(0).read().cardstatus() as u128; + let csd1 = regs.respr(1).read().cardstatus() as u128; + let csd2 = regs.respr(2).read().cardstatus() as u128; + let csd3 = regs.respr(3).read().cardstatus() as u128; + let csd = (csd0 << 96) | (csd1 << 64) | (csd2 << 32) | (csd3); + card.csd = csd.into(); + + self.select_card(Some(&card))?; + + self.get_scr(&mut card).await?; + + // Set bus width + let (width, acmd_arg) = match bus_width { + BusWidth::Eight => unimplemented!(), + BusWidth::Four if card.scr.bus_width_four() => (BusWidth::Four, 2), + _ => (BusWidth::One, 0), + }; + Self::cmd(Cmd::app_cmd(card.rca << 16), false)?; + Self::cmd(Cmd::cmd6(acmd_arg), false)?; + + // CPSMACT and DPSMACT must be 0 to set WIDBUS + Self::wait_idle(); + + regs.clkcr().modify(|w| { + w.set_widbus(match width { + BusWidth::One => 0, + BusWidth::Four => 1, + BusWidth::Eight => 2, + _ => panic!("Invalid Bus Width"), + }) + }); + + // Set Clock + if freq.0 <= 25_000_000 { + // Final clock frequency + self.clkcr_set_clkdiv(freq.0, width)?; + } else { + // Switch to max clock for SDR12 + self.clkcr_set_clkdiv(25_000_000, width)?; + } + + self.card = Some(card); + + // Read status + self.read_sd_status().await?; + + if freq.0 > 25_000_000 { + // Switch to SDR25 + self.signalling = self.switch_signalling_mode(Signalling::SDR25).await?; + + if self.signalling == Signalling::SDR25 { + // Set final clock frequency + self.clkcr_set_clkdiv(freq.0, width)?; + + if self.read_status(&card)?.state() != CurrentState::Transfer { + return Err(Error::SignalingSwitchFailed); + } + } + } + // Read status after signalling change + self.read_sd_status().await?; + } + + Ok(()) + } + + #[inline(always)] + pub async fn read_block(&mut self, block_idx: u32, buffer: &mut DataBlock) -> Result<(), Error> { + let card_capacity = self.card()?.card_type; + + // NOTE(unsafe) DataBlock uses align 4 + let buffer = unsafe { &mut *((&mut buffer.0) as *mut [u8; 512] as *mut [u32; 128]) }; + + // Always read 1 block of 512 bytes + // SDSC cards are byte addressed hence the blockaddress is in multiples of 512 bytes + let address = match card_capacity { + CardCapacity::SDSC => block_idx * 512, + _ => block_idx, + }; + Self::cmd(Cmd::set_block_length(512), false)?; // CMD16 + + let regs = T::regs(); + let on_drop = OnDrop::new(|| unsafe { Self::on_drop() }); + + let transfer = self.prepare_datapath_read(buffer, 512, 9); + Self::data_interrupts(true); + Self::cmd(Cmd::read_single_block(address), true)?; + + let res = poll_fn(|cx| { + T::state().register(cx.waker()); + let status = unsafe { regs.star().read() }; + + if status.dcrcfail() { + return Poll::Ready(Err(Error::Crc)); + } else if status.dtimeout() { + return Poll::Ready(Err(Error::Timeout)); + } else if status.dataend() { + return Poll::Ready(Ok(())); + } + Poll::Pending + }) + .await; + Self::clear_interrupt_flags(); + + if res.is_ok() { + on_drop.defuse(); + Self::stop_datapath(); + drop(transfer); + } + res + } + + pub async fn write_block(&mut self, block_idx: u32, buffer: &DataBlock) -> Result<(), Error> { + let card = self.card.as_mut().ok_or(Error::NoCard)?; + + // NOTE(unsafe) DataBlock uses align 4 + let buffer = unsafe { &*((&buffer.0) as *const [u8; 512] as *const [u32; 128]) }; + + // Always read 1 block of 512 bytes + // SDSC cards are byte addressed hence the blockaddress is in multiples of 512 bytes + let address = match card.card_type { + CardCapacity::SDSC => block_idx * 512, + _ => block_idx, + }; + Self::cmd(Cmd::set_block_length(512), false)?; // CMD16 + + let regs = T::regs(); + let on_drop = OnDrop::new(|| unsafe { Self::on_drop() }); + + // sdmmc_v1 uses different cmd/dma order than v2, but only for writes + #[cfg(sdmmc_v1)] + Self::cmd(Cmd::write_single_block(address), true)?; + + let transfer = self.prepare_datapath_write(buffer, 512, 9); + Self::data_interrupts(true); + + #[cfg(sdmmc_v2)] + Self::cmd(Cmd::write_single_block(address), true)?; + + let res = poll_fn(|cx| { + T::state().register(cx.waker()); + let status = unsafe { regs.star().read() }; + + if status.dcrcfail() { + return Poll::Ready(Err(Error::Crc)); + } else if status.dtimeout() { + return Poll::Ready(Err(Error::Timeout)); + } else if status.dataend() { + return Poll::Ready(Ok(())); + } + Poll::Pending + }) + .await; + Self::clear_interrupt_flags(); + + match res { + Ok(_) => { + on_drop.defuse(); + Self::stop_datapath(); + drop(transfer); + + // TODO: Make this configurable + let mut timeout: u32 = 0x00FF_FFFF; + + // Try to read card status (ACMD13) + while timeout > 0 { + match self.read_sd_status().await { + Ok(_) => return Ok(()), + Err(Error::Timeout) => (), // Try again + Err(e) => return Err(e), + } + timeout -= 1; + } + Err(Error::SoftwareTimeout) + } + Err(e) => Err(e), + } + } + + /// Get a reference to the initialized card + /// + /// # Errors + /// + /// Returns Error::NoCard if [`init_card`](#method.init_card) + /// has not previously succeeded + #[inline(always)] + pub fn card(&self) -> Result<&Card, Error> { + self.card.as_ref().ok_or(Error::NoCard) + } + + /// Get the current SDMMC bus clock + pub fn clock(&self) -> Hertz { + self.clock + } + + #[inline(always)] + fn on_interrupt(_: *mut ()) { + Self::data_interrupts(false); + T::state().wake(); + } +} + +impl<'d, T: Instance, Dma: SdmmcDma + 'd> Drop for Sdmmc<'d, T, Dma> { + fn drop(&mut self) { + self.irq.disable(); + unsafe { Self::on_drop() }; + + critical_section::with(|_| unsafe { + self.clk.set_as_disconnected(); + self.cmd.set_as_disconnected(); + self.d0.set_as_disconnected(); + if let Some(x) = &mut self.d1 { + x.set_as_disconnected(); + } + if let Some(x) = &mut self.d2 { + x.set_as_disconnected(); + } + if let Some(x) = &mut self.d3 { + x.set_as_disconnected(); + } + }); } } @@ -1540,7 +1398,7 @@ pub(crate) mod sealed { pub trait Instance { type Interrupt: Interrupt; - fn inner() -> SdmmcInner; + fn regs() -> RegBlock; fn state() -> &'static AtomicWaker; fn kernel_clk() -> Hertz; } @@ -1560,15 +1418,14 @@ pin_trait!(D5Pin, Instance); pin_trait!(D6Pin, Instance); pin_trait!(D7Pin, Instance); -cfg_if::cfg_if! { - if #[cfg(sdmmc_v1)] { - dma_trait!(SdmmcDma, Instance); - } else if #[cfg(sdmmc_v2)] { - // SDMMCv2 uses internal DMA - pub trait SdmmcDma {} - impl SdmmcDma for NoDma {} - } -} +#[cfg(sdmmc_v1)] +dma_trait!(SdmmcDma, Instance); + +// SDMMCv2 uses internal DMA +#[cfg(sdmmc_v2)] +pub trait SdmmcDma {} +#[cfg(sdmmc_v2)] +impl SdmmcDma for NoDma {} cfg_if::cfg_if! { // TODO, these could not be implemented, because required clocks are not exposed in RCC: @@ -1630,9 +1487,8 @@ foreach_peripheral!( impl sealed::Instance for peripherals::$inst { type Interrupt = crate::interrupt::$inst; - fn inner() -> SdmmcInner { - const INNER: SdmmcInner = SdmmcInner(crate::pac::$inst); - INNER + fn regs() -> RegBlock { + crate::pac::$inst } fn state() -> &'static ::embassy_sync::waitqueue::AtomicWaker { @@ -1651,8 +1507,6 @@ foreach_peripheral!( #[cfg(feature = "embedded-sdmmc")] mod sdmmc_rs { - use core::future::Future; - use embedded_sdmmc::{Block, BlockCount, BlockDevice, BlockIdx}; use super::*; @@ -1660,49 +1514,37 @@ mod sdmmc_rs { impl<'d, T: Instance, Dma: SdmmcDma> BlockDevice for Sdmmc<'d, T, Dma> { type Error = Error; - type ReadFuture<'a> = impl Future> + 'a - where - Self: 'a; - - type WriteFuture<'a> = impl Future> + 'a - where - Self: 'a; - - fn read<'a>( - &'a mut self, - blocks: &'a mut [Block], + async fn read( + &mut self, + blocks: &mut [Block], start_block_idx: BlockIdx, _reason: &str, - ) -> Self::ReadFuture<'a> { - async move { - let mut address = start_block_idx.0; + ) -> Result<(), Self::Error> { + let mut address = start_block_idx.0; - for block in blocks.iter_mut() { - let block: &mut [u8; 512] = &mut block.contents; + for block in blocks.iter_mut() { + let block: &mut [u8; 512] = &mut block.contents; - // NOTE(unsafe) Block uses align(4) - let block = unsafe { &mut *(block as *mut _ as *mut DataBlock) }; - self.read_block(address, block).await?; - address += 1; - } - Ok(()) + // NOTE(unsafe) Block uses align(4) + let block = unsafe { &mut *(block as *mut _ as *mut DataBlock) }; + self.read_block(address, block).await?; + address += 1; } + Ok(()) } - fn write<'a>(&'a mut self, blocks: &'a [Block], start_block_idx: BlockIdx) -> Self::WriteFuture<'a> { - async move { - let mut address = start_block_idx.0; + async fn write(&mut self, blocks: &[Block], start_block_idx: BlockIdx) -> Result<(), Self::Error> { + let mut address = start_block_idx.0; - for block in blocks.iter() { - let block: &[u8; 512] = &block.contents; + for block in blocks.iter() { + let block: &[u8; 512] = &block.contents; - // NOTE(unsafe) DataBlock uses align 4 - let block = unsafe { &*(block as *const _ as *const DataBlock) }; - self.write_block(address, block).await?; - address += 1; - } - Ok(()) + // NOTE(unsafe) DataBlock uses align 4 + let block = unsafe { &*(block as *const _ as *const DataBlock) }; + self.write_block(address, block).await?; + address += 1; } + Ok(()) } fn num_blocks(&self) -> Result { diff --git a/embassy-stm32/src/spi/mod.rs b/embassy-stm32/src/spi/mod.rs index 481ea4ab..492d0649 100644 --- a/embassy-stm32/src/spi/mod.rs +++ b/embassy-stm32/src/spi/mod.rs @@ -7,8 +7,7 @@ use embassy_futures::join::join; use embassy_hal_common::{into_ref, PeripheralRef}; pub use embedded_hal_02::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3}; -use self::sealed::WordSize; -use crate::dma::{slice_ptr_parts, Transfer}; +use crate::dma::{slice_ptr_parts, word, Transfer}; use crate::gpio::sealed::{AFType, Pin as _}; use crate::gpio::{AnyPin, Pull}; use crate::pac::spi::{regs, vals, Spi as Regs}; @@ -78,7 +77,7 @@ pub struct Spi<'d, T: Instance, Tx, Rx> { miso: Option>, txdma: PeripheralRef<'d, Tx>, rxdma: PeripheralRef<'d, Rx>, - current_word_size: WordSize, + current_word_size: word_impl::Config, } impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { @@ -178,6 +177,23 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { ) } + pub fn new_txonly_nosck( + peri: impl Peripheral

+ 'd, + mosi: impl Peripheral

> + 'd, + txdma: impl Peripheral

+ 'd, + rxdma: impl Peripheral

+ 'd, // TODO: remove + freq: Hertz, + config: Config, + ) -> Self { + into_ref!(mosi); + unsafe { + mosi.set_as_af_pull(mosi.af_num(), AFType::OutputPushPull, Pull::Down); + mosi.set_speed(crate::gpio::Speed::Medium); + } + + Self::new_inner(peri, None, Some(mosi.map_into()), None, txdma, rxdma, freq, config) + } + /// Useful for on chip peripherals like SUBGHZ which are hardwired. /// The bus can optionally be exposed externally with `Spi::new()` still. #[allow(dead_code)] @@ -234,14 +250,15 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { if mosi.is_none() { w.set_rxonly(vals::Rxonly::OUTPUTDISABLED); } - w.set_dff(WordSize::EightBit.dff()) + w.set_dff(::CONFIG) }); } #[cfg(spi_v2)] unsafe { T::REGS.cr2().modify(|w| { - w.set_frxth(WordSize::EightBit.frxth()); - w.set_ds(WordSize::EightBit.ds()); + let (ds, frxth) = ::CONFIG; + w.set_frxth(frxth); + w.set_ds(ds); w.set_ssoe(false); }); T::REGS.cr1().modify(|w| { @@ -279,7 +296,8 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { T::REGS.cfg1().modify(|w| { w.set_crcen(false); w.set_mbr(br); - w.set_dsize(WordSize::EightBit.dsize()); + w.set_dsize(::CONFIG); + w.set_fthlv(vals::Fthlv::ONEFRAME); }); T::REGS.cr2().modify(|w| { w.set_tsize(0); @@ -297,7 +315,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { miso, txdma, rxdma, - current_word_size: WordSize::EightBit, + current_word_size: ::CONFIG, } } @@ -355,7 +373,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { } } - fn set_word_size(&mut self, word_size: WordSize) { + fn set_word_size(&mut self, word_size: word_impl::Config) { if self.current_word_size == word_size { return; } @@ -364,7 +382,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { unsafe { T::REGS.cr1().modify(|reg| { reg.set_spe(false); - reg.set_dff(word_size.dff()) + reg.set_dff(word_size) }); T::REGS.cr1().modify(|reg| { reg.set_spe(true); @@ -376,8 +394,8 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { w.set_spe(false); }); T::REGS.cr2().modify(|w| { - w.set_frxth(word_size.frxth()); - w.set_ds(word_size.ds()); + w.set_frxth(word_size.1); + w.set_ds(word_size.0); }); T::REGS.cr1().modify(|w| { w.set_spe(true); @@ -393,7 +411,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { w.set_spe(false); }); T::REGS.cfg1().modify(|w| { - w.set_dsize(word_size.dsize()); + w.set_dsize(word_size); }); T::REGS.cr1().modify(|w| { w.set_csusp(false); @@ -412,7 +430,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { return Ok(()); } - self.set_word_size(W::WORDSIZE); + self.set_word_size(W::CONFIG); unsafe { T::REGS.cr1().modify(|w| { w.set_spe(false); @@ -421,8 +439,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { let tx_request = self.txdma.request(); let tx_dst = T::REGS.tx_ptr(); - unsafe { self.txdma.start_write(tx_request, data, tx_dst, Default::default()) } - let tx_f = Transfer::new(&mut self.txdma); + let tx_f = unsafe { Transfer::new_write(&mut self.txdma, tx_request, data, tx_dst, Default::default()) }; unsafe { set_txdmaen(T::REGS, true); @@ -451,7 +468,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { return Ok(()); } - self.set_word_size(W::WORDSIZE); + self.set_word_size(W::CONFIG); unsafe { T::REGS.cr1().modify(|w| { w.set_spe(false); @@ -468,13 +485,21 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { let rx_request = self.rxdma.request(); let rx_src = T::REGS.rx_ptr(); - unsafe { self.rxdma.start_read(rx_request, rx_src, data, Default::default()) }; - let rx_f = Transfer::new(&mut self.rxdma); + let rx_f = unsafe { Transfer::new_read(&mut self.rxdma, rx_request, rx_src, data, Default::default()) }; let tx_request = self.txdma.request(); let tx_dst = T::REGS.tx_ptr(); let clock_byte = 0x00u8; - let tx_f = crate::dma::write_repeated(&mut self.txdma, tx_request, &clock_byte, clock_byte_count, tx_dst); + let tx_f = unsafe { + Transfer::new_write_repeated( + &mut self.txdma, + tx_request, + &clock_byte, + clock_byte_count, + tx_dst, + Default::default(), + ) + }; unsafe { set_txdmaen(T::REGS, true); @@ -506,7 +531,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { return Ok(()); } - self.set_word_size(W::WORDSIZE); + self.set_word_size(W::CONFIG); unsafe { T::REGS.cr1().modify(|w| { w.set_spe(false); @@ -521,13 +546,11 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { let rx_request = self.rxdma.request(); let rx_src = T::REGS.rx_ptr(); - unsafe { self.rxdma.start_read(rx_request, rx_src, read, Default::default()) }; - let rx_f = Transfer::new(&mut self.rxdma); + let rx_f = unsafe { Transfer::new_read_raw(&mut self.rxdma, rx_request, rx_src, read, Default::default()) }; let tx_request = self.txdma.request(); let tx_dst = T::REGS.tx_ptr(); - unsafe { self.txdma.start_write(tx_request, write, tx_dst, Default::default()) } - let tx_f = Transfer::new(&mut self.txdma); + let tx_f = unsafe { Transfer::new_write_raw(&mut self.txdma, tx_request, write, tx_dst, Default::default()) }; unsafe { set_txdmaen(T::REGS, true); @@ -566,7 +589,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { pub fn blocking_write(&mut self, words: &[W]) -> Result<(), Error> { unsafe { T::REGS.cr1().modify(|w| w.set_spe(true)) } flush_rx_fifo(T::REGS); - self.set_word_size(W::WORDSIZE); + self.set_word_size(W::CONFIG); for word in words.iter() { let _ = transfer_word(T::REGS, *word)?; } @@ -576,7 +599,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { pub fn blocking_read(&mut self, words: &mut [W]) -> Result<(), Error> { unsafe { T::REGS.cr1().modify(|w| w.set_spe(true)) } flush_rx_fifo(T::REGS); - self.set_word_size(W::WORDSIZE); + self.set_word_size(W::CONFIG); for word in words.iter_mut() { *word = transfer_word(T::REGS, W::default())?; } @@ -586,7 +609,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { pub fn blocking_transfer_in_place(&mut self, words: &mut [W]) -> Result<(), Error> { unsafe { T::REGS.cr1().modify(|w| w.set_spe(true)) } flush_rx_fifo(T::REGS); - self.set_word_size(W::WORDSIZE); + self.set_word_size(W::CONFIG); for word in words.iter_mut() { *word = transfer_word(T::REGS, *word)?; } @@ -596,7 +619,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { pub fn blocking_transfer(&mut self, read: &mut [W], write: &[W]) -> Result<(), Error> { unsafe { T::REGS.cr1().modify(|w| w.set_spe(true)) } flush_rx_fifo(T::REGS); - self.set_word_size(W::WORDSIZE); + self.set_word_size(W::CONFIG); let len = read.len().max(write.len()); for i in 0..len { let wb = write.get(i).copied().unwrap_or_default(); @@ -928,70 +951,89 @@ pub(crate) mod sealed { const REGS: Regs; } - pub trait Word: Copy + 'static { - const WORDSIZE: WordSize; - } - - impl Word for u8 { - const WORDSIZE: WordSize = WordSize::EightBit; - } - impl Word for u16 { - const WORDSIZE: WordSize = WordSize::SixteenBit; - } - - #[derive(Copy, Clone, PartialOrd, PartialEq)] - pub enum WordSize { - EightBit, - SixteenBit, - } - - impl WordSize { - #[cfg(any(spi_v1, spi_f1))] - pub fn dff(&self) -> vals::Dff { - match self { - WordSize::EightBit => vals::Dff::EIGHTBIT, - WordSize::SixteenBit => vals::Dff::SIXTEENBIT, - } - } - - #[cfg(spi_v2)] - pub fn ds(&self) -> vals::Ds { - match self { - WordSize::EightBit => vals::Ds::EIGHTBIT, - WordSize::SixteenBit => vals::Ds::SIXTEENBIT, - } - } - - #[cfg(spi_v2)] - pub fn frxth(&self) -> vals::Frxth { - match self { - WordSize::EightBit => vals::Frxth::QUARTER, - WordSize::SixteenBit => vals::Frxth::HALF, - } - } - - #[cfg(any(spi_v3, spi_v4, spi_v5))] - pub fn dsize(&self) -> u8 { - match self { - WordSize::EightBit => 0b0111, - WordSize::SixteenBit => 0b1111, - } - } - - #[cfg(any(spi_v3, spi_v4, spi_v5))] - pub fn _frxth(&self) -> vals::Fthlv { - match self { - WordSize::EightBit => vals::Fthlv::ONEFRAME, - WordSize::SixteenBit => vals::Fthlv::ONEFRAME, - } - } + pub trait Word { + const CONFIG: word_impl::Config; } } -pub trait Word: Copy + 'static + sealed::Word + Default + crate::dma::Word {} +pub trait Word: word::Word + sealed::Word {} -impl Word for u8 {} -impl Word for u16 {} +macro_rules! impl_word { + ($T:ty, $config:expr) => { + impl sealed::Word for $T { + const CONFIG: Config = $config; + } + impl Word for $T {} + }; +} + +#[cfg(any(spi_v1, spi_f1))] +mod word_impl { + use super::*; + + pub type Config = vals::Dff; + + impl_word!(u8, vals::Dff::EIGHTBIT); + impl_word!(u16, vals::Dff::SIXTEENBIT); +} + +#[cfg(any(spi_v2))] +mod word_impl { + use super::*; + + pub type Config = (vals::Ds, vals::Frxth); + + impl_word!(word::U4, (vals::Ds::FOURBIT, vals::Frxth::QUARTER)); + impl_word!(word::U5, (vals::Ds::FIVEBIT, vals::Frxth::QUARTER)); + impl_word!(word::U6, (vals::Ds::SIXBIT, vals::Frxth::QUARTER)); + impl_word!(word::U7, (vals::Ds::SEVENBIT, vals::Frxth::QUARTER)); + impl_word!(u8, (vals::Ds::EIGHTBIT, vals::Frxth::QUARTER)); + impl_word!(word::U9, (vals::Ds::NINEBIT, vals::Frxth::HALF)); + impl_word!(word::U10, (vals::Ds::TENBIT, vals::Frxth::HALF)); + impl_word!(word::U11, (vals::Ds::ELEVENBIT, vals::Frxth::HALF)); + impl_word!(word::U12, (vals::Ds::TWELVEBIT, vals::Frxth::HALF)); + impl_word!(word::U13, (vals::Ds::THIRTEENBIT, vals::Frxth::HALF)); + impl_word!(word::U14, (vals::Ds::FOURTEENBIT, vals::Frxth::HALF)); + impl_word!(word::U15, (vals::Ds::FIFTEENBIT, vals::Frxth::HALF)); + impl_word!(u16, (vals::Ds::SIXTEENBIT, vals::Frxth::HALF)); +} + +#[cfg(any(spi_v3, spi_v4, spi_v5))] +mod word_impl { + use super::*; + + pub type Config = u8; + + impl_word!(word::U4, 4 - 1); + impl_word!(word::U5, 5 - 1); + impl_word!(word::U6, 6 - 1); + impl_word!(word::U7, 7 - 1); + impl_word!(u8, 8 - 1); + impl_word!(word::U9, 9 - 1); + impl_word!(word::U10, 10 - 1); + impl_word!(word::U11, 11 - 1); + impl_word!(word::U12, 12 - 1); + impl_word!(word::U13, 13 - 1); + impl_word!(word::U14, 14 - 1); + impl_word!(word::U15, 15 - 1); + impl_word!(u16, 16 - 1); + impl_word!(word::U17, 17 - 1); + impl_word!(word::U18, 18 - 1); + impl_word!(word::U19, 19 - 1); + impl_word!(word::U20, 20 - 1); + impl_word!(word::U21, 21 - 1); + impl_word!(word::U22, 22 - 1); + impl_word!(word::U23, 23 - 1); + impl_word!(word::U24, 24 - 1); + impl_word!(word::U25, 25 - 1); + impl_word!(word::U26, 26 - 1); + impl_word!(word::U27, 27 - 1); + impl_word!(word::U28, 28 - 1); + impl_word!(word::U29, 29 - 1); + impl_word!(word::U30, 30 - 1); + impl_word!(word::U31, 31 - 1); + impl_word!(u32, 32 - 1); +} pub trait Instance: Peripheral

+ sealed::Instance + RccPeripheral {} pin_trait!(SckPin, Instance); diff --git a/embassy-stm32/src/traits.rs b/embassy-stm32/src/traits.rs index 45cc4e72..ffce7bd4 100644 --- a/embassy-stm32/src/traits.rs +++ b/embassy-stm32/src/traits.rs @@ -34,7 +34,7 @@ macro_rules! dma_trait_impl { (crate::$mod:ident::$trait:ident, $instance:ident, {dmamux: $dmamux:ident}, $request:expr) => { impl crate::$mod::$trait for T where - T: crate::dma::MuxChannel, + T: crate::dma::Channel + crate::dma::MuxChannel, { fn request(&self) -> crate::dma::Request { $request diff --git a/embassy-stm32/src/usart/mod.rs b/embassy-stm32/src/usart/mod.rs index a42eede1..b8656b58 100644 --- a/embassy-stm32/src/usart/mod.rs +++ b/embassy-stm32/src/usart/mod.rs @@ -6,11 +6,11 @@ use core::sync::atomic::{compiler_fence, Ordering}; use core::task::Poll; use embassy_cortex_m::interrupt::InterruptExt; -use embassy_futures::select::{select, Either}; use embassy_hal_common::drop::OnDrop; use embassy_hal_common::{into_ref, PeripheralRef}; +use futures::future::{select, Either}; -use crate::dma::NoDma; +use crate::dma::{NoDma, Transfer}; use crate::gpio::sealed::AFType; #[cfg(any(lpuart_v1, lpuart_v2))] use crate::pac::lpuart::{regs, vals, Lpuart as Regs}; @@ -91,7 +91,7 @@ enum ReadCompletionEvent { // DMA Read transfer completed first DmaCompleted, // Idle line detected first - Idle, + Idle(usize), } pub struct Uart<'d, T: BasicInstance, TxDma = NoDma, RxDma = NoDma> { @@ -183,7 +183,7 @@ impl<'d, T: BasicInstance, TxDma> UartTx<'d, T, TxDma> { } // If we don't assign future to a variable, the data register pointer // is held across an await and makes the future non-Send. - let transfer = crate::dma::write(ch, request, buffer, tdr(T::regs())); + let transfer = unsafe { Transfer::new_write(ch, request, buffer, tdr(T::regs()), Default::default()) }; transfer.await; Ok(()) } @@ -430,10 +430,12 @@ impl<'d, T: BasicInstance, RxDma> UartRx<'d, T, RxDma> { let ch = &mut self.rx_dma; let request = ch.request(); + let buffer_len = buffer.len(); + // Start USART DMA // will not do anything yet because DMAR is not yet set // future which will complete when DMA Read request completes - let transfer = crate::dma::read(ch, request, rdr(T::regs()), buffer); + let transfer = unsafe { Transfer::new_read(ch, request, rdr(T::regs()), buffer, Default::default()) }; // SAFETY: The only way we might have a problem is using split rx and tx // here we only modify or read Rx related flags, interrupts and DMA channel @@ -565,13 +567,15 @@ impl<'d, T: BasicInstance, RxDma> UartRx<'d, T, RxDma> { // when transfer is dropped, it will stop the DMA request let r = match select(transfer, idle).await { // DMA transfer completed first - Either::First(()) => Ok(ReadCompletionEvent::DmaCompleted), + Either::Left(((), _)) => Ok(ReadCompletionEvent::DmaCompleted), // Idle line detected first - Either::Second(Ok(())) => Ok(ReadCompletionEvent::Idle), + Either::Right((Ok(()), transfer)) => Ok(ReadCompletionEvent::Idle( + buffer_len - transfer.get_remaining_transfers() as usize, + )), // error occurred - Either::Second(Err(e)) => Err(e), + Either::Right((Err(e), _)) => Err(e), }; drop(on_drop); @@ -594,14 +598,9 @@ impl<'d, T: BasicInstance, RxDma> UartRx<'d, T, RxDma> { // wait for DMA to complete or IDLE line detection if requested let res = self.inner_read_run(buffer, enable_idle_line_detection).await; - let ch = &mut self.rx_dma; - match res { Ok(ReadCompletionEvent::DmaCompleted) => Ok(buffer_len), - Ok(ReadCompletionEvent::Idle) => { - let n = buffer_len - (ch.remaining_transfers() as usize); - Ok(n) - } + Ok(ReadCompletionEvent::Idle(n)) => Ok(n), Err(e) => Err(e), } } @@ -973,73 +972,6 @@ mod eio { } } -#[cfg(all( - feature = "unstable-traits", - feature = "nightly", - feature = "_todo_embedded_hal_serial" -))] -mod eha { - use core::future::Future; - - use super::*; - - impl<'d, T: BasicInstance, TxDma> embedded_hal_async::serial::Write for UartTx<'d, T, TxDma> - where - TxDma: crate::usart::TxDma, - { - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - self.write(buf) - } - - type FlushFuture<'a> = impl Future> + 'a where Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - async move { Ok(()) } - } - } - - impl<'d, T: BasicInstance, RxDma> embedded_hal_async::serial::Read for UartRx<'d, T, RxDma> - where - RxDma: crate::usart::RxDma, - { - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - self.read(buf) - } - } - - impl<'d, T: BasicInstance, TxDma, RxDma> embedded_hal_async::serial::Write for Uart<'d, T, TxDma, RxDma> - where - TxDma: crate::usart::TxDma, - { - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - self.write(buf) - } - - type FlushFuture<'a> = impl Future> + 'a where Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - async move { Ok(()) } - } - } - - impl<'d, T: BasicInstance, TxDma, RxDma> embedded_hal_async::serial::Read for Uart<'d, T, TxDma, RxDma> - where - RxDma: crate::usart::RxDma, - { - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - self.read(buf) - } - } -} - #[cfg(feature = "nightly")] pub use buffered::*; #[cfg(feature = "nightly")] diff --git a/examples/boot/application/nrf/Cargo.toml b/examples/boot/application/nrf/Cargo.toml index cda1917b..d6a30602 100644 --- a/examples/boot/application/nrf/Cargo.toml +++ b/examples/boot/application/nrf/Cargo.toml @@ -18,9 +18,9 @@ defmt-rtt = { version = "0.4", optional = true } panic-reset = { version = "0.1.1" } embedded-hal = { version = "0.2.6" } -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" [features] ed25519-dalek = ["embassy-boot/ed25519-dalek"] -ed25519-salty = ["embassy-boot/ed25519-salty"] \ No newline at end of file +ed25519-salty = ["embassy-boot/ed25519-salty"] diff --git a/examples/boot/application/rp/Cargo.toml b/examples/boot/application/rp/Cargo.toml index 9d34a369..62ef42d6 100644 --- a/examples/boot/application/rp/Cargo.toml +++ b/examples/boot/application/rp/Cargo.toml @@ -18,7 +18,7 @@ panic-probe = { version = "0.3", features = ["print-defmt"], optional = true } panic-reset = { version = "0.1.1", optional = true } embedded-hal = { version = "0.2.6" } -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" [features] diff --git a/examples/boot/application/stm32f3/Cargo.toml b/examples/boot/application/stm32f3/Cargo.toml index 8e978eb2..e5fb1b01 100644 --- a/examples/boot/application/stm32f3/Cargo.toml +++ b/examples/boot/application/stm32f3/Cargo.toml @@ -17,7 +17,7 @@ defmt-rtt = { version = "0.4", optional = true } panic-reset = { version = "0.1.1" } embedded-hal = { version = "0.2.6" } -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" [features] diff --git a/examples/boot/application/stm32f7/Cargo.toml b/examples/boot/application/stm32f7/Cargo.toml index fb55b166..a6ac1cad 100644 --- a/examples/boot/application/stm32f7/Cargo.toml +++ b/examples/boot/application/stm32f7/Cargo.toml @@ -17,7 +17,7 @@ defmt-rtt = { version = "0.4", optional = true } panic-reset = { version = "0.1.1" } embedded-hal = { version = "0.2.6" } -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" [features] diff --git a/examples/boot/application/stm32h7/Cargo.toml b/examples/boot/application/stm32h7/Cargo.toml index ea6b905a..5b8ee555 100644 --- a/examples/boot/application/stm32h7/Cargo.toml +++ b/examples/boot/application/stm32h7/Cargo.toml @@ -17,7 +17,7 @@ defmt-rtt = { version = "0.4", optional = true } panic-reset = { version = "0.1.1" } embedded-hal = { version = "0.2.6" } -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" [features] diff --git a/examples/boot/application/stm32l0/Cargo.toml b/examples/boot/application/stm32l0/Cargo.toml index 6ba18564..05ce5c10 100644 --- a/examples/boot/application/stm32l0/Cargo.toml +++ b/examples/boot/application/stm32l0/Cargo.toml @@ -17,7 +17,7 @@ defmt-rtt = { version = "0.4", optional = true } panic-reset = { version = "0.1.1" } embedded-hal = { version = "0.2.6" } -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" [features] diff --git a/examples/boot/application/stm32l1/Cargo.toml b/examples/boot/application/stm32l1/Cargo.toml index d5b8e3e0..14af99e9 100644 --- a/examples/boot/application/stm32l1/Cargo.toml +++ b/examples/boot/application/stm32l1/Cargo.toml @@ -17,7 +17,7 @@ defmt-rtt = { version = "0.4", optional = true } panic-reset = { version = "0.1.1" } embedded-hal = { version = "0.2.6" } -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" [features] diff --git a/examples/boot/application/stm32l4/Cargo.toml b/examples/boot/application/stm32l4/Cargo.toml index ccd1fe2e..90ae9772 100644 --- a/examples/boot/application/stm32l4/Cargo.toml +++ b/examples/boot/application/stm32l4/Cargo.toml @@ -17,7 +17,7 @@ defmt-rtt = { version = "0.4", optional = true } panic-reset = { version = "0.1.1" } embedded-hal = { version = "0.2.6" } -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" [features] diff --git a/examples/boot/application/stm32wl/Cargo.toml b/examples/boot/application/stm32wl/Cargo.toml index 128afd51..08403a4e 100644 --- a/examples/boot/application/stm32wl/Cargo.toml +++ b/examples/boot/application/stm32wl/Cargo.toml @@ -17,7 +17,7 @@ defmt-rtt = { version = "0.4", optional = true } panic-reset = { version = "0.1.1" } embedded-hal = { version = "0.2.6" } -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" [features] diff --git a/examples/boot/bootloader/nrf/Cargo.toml b/examples/boot/bootloader/nrf/Cargo.toml index 8a6f5364..cd0be5b4 100644 --- a/examples/boot/bootloader/nrf/Cargo.toml +++ b/examples/boot/bootloader/nrf/Cargo.toml @@ -11,7 +11,7 @@ defmt-rtt = { version = "0.4", optional = true } embassy-nrf = { path = "../../../../embassy-nrf", default-features = false, features = ["nightly"] } embassy-boot-nrf = { path = "../../../../embassy-boot/nrf", default-features = false } -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = { version = "0.7" } cfg-if = "1.0.0" diff --git a/examples/boot/bootloader/rp/Cargo.toml b/examples/boot/bootloader/rp/Cargo.toml index a16cebe3..b4167bcd 100644 --- a/examples/boot/bootloader/rp/Cargo.toml +++ b/examples/boot/bootloader/rp/Cargo.toml @@ -13,7 +13,7 @@ embassy-rp = { path = "../../../../embassy-rp", default-features = false, featur embassy-boot-rp = { path = "../../../../embassy-boot/rp", default-features = false } embassy-time = { path = "../../../../embassy-time", features = ["nightly"] } -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = { version = "0.7" } embedded-storage = "0.3.0" embedded-storage-async = "0.4.0" diff --git a/examples/boot/bootloader/stm32/Cargo.toml b/examples/boot/bootloader/stm32/Cargo.toml index b1791620..f2675aa7 100644 --- a/examples/boot/bootloader/stm32/Cargo.toml +++ b/examples/boot/bootloader/stm32/Cargo.toml @@ -11,7 +11,7 @@ defmt-rtt = { version = "0.4", optional = true } embassy-stm32 = { path = "../../../../embassy-stm32", default-features = false, features = ["nightly"] } embassy-boot-stm32 = { path = "../../../../embassy-boot/stm32", default-features = false } -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = { version = "0.7" } embedded-storage = "0.3.0" embedded-storage-async = "0.4.0" diff --git a/examples/nrf-rtos-trace/Cargo.toml b/examples/nrf-rtos-trace/Cargo.toml index 6f7cb887..0a7141c4 100644 --- a/examples/nrf-rtos-trace/Cargo.toml +++ b/examples/nrf-rtos-trace/Cargo.toml @@ -21,7 +21,7 @@ embassy-executor = { version = "0.1.0", path = "../../embassy-executor", feature embassy-time = { version = "0.1.0", path = "../../embassy-time" } embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] } -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" panic-probe = { version = "0.3" } futures = { version = "0.3.17", default-features = false, features = ["async-await"] } diff --git a/examples/nrf52840/Cargo.toml b/examples/nrf52840/Cargo.toml index fc614cb8..10c269a7 100644 --- a/examples/nrf52840/Cargo.toml +++ b/examples/nrf52840/Cargo.toml @@ -20,14 +20,14 @@ embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defm embedded-io = "0.4.0" embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["sx126x", "time", "defmt"], optional = true } -lorawan-device = { version = "0.8.0", default-features = false, features = ["async"], optional = true } -lorawan = { version = "0.7.1", default-features = false, features = ["default-crypto"], optional = true } +lorawan-device = { version = "0.9.0", default-features = false, features = ["async"], optional = true } +lorawan = { version = "0.7.2", default-features = false, features = ["default-crypto"], optional = true } defmt = "0.3" defmt-rtt = "0.4" static_cell = "1.0" -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" panic-probe = { version = "0.3", features = ["print-defmt"] } futures = { version = "0.3.17", default-features = false, features = ["async-await"] } diff --git a/examples/nrf5340/Cargo.toml b/examples/nrf5340/Cargo.toml index 2c3a1296..ebbc25bc 100644 --- a/examples/nrf5340/Cargo.toml +++ b/examples/nrf5340/Cargo.toml @@ -43,7 +43,7 @@ defmt = "0.3" defmt-rtt = "0.4" static_cell = "1.0" -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" panic-probe = { version = "0.3", features = ["print-defmt"] } futures = { version = "0.3.17", default-features = false, features = [ diff --git a/examples/rp/Cargo.toml b/examples/rp/Cargo.toml index f0fd2799..8067f7ba 100644 --- a/examples/rp/Cargo.toml +++ b/examples/rp/Cargo.toml @@ -20,7 +20,7 @@ defmt = "0.3" defmt-rtt = "0.4" #cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } -cortex-m = { version = "0.7.6" } +cortex-m = { version = "0.7.6", features = ["inline-asm"] } cortex-m-rt = "0.7.0" panic-probe = { version = "0.3", features = ["print-defmt"] } futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] } diff --git a/examples/stm32f0/Cargo.toml b/examples/stm32f0/Cargo.toml index 2ff25262..e969538d 100644 --- a/examples/stm32f0/Cargo.toml +++ b/examples/stm32f0/Cargo.toml @@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" defmt = "0.3" defmt-rtt = "0.4" diff --git a/examples/stm32f1/Cargo.toml b/examples/stm32f1/Cargo.toml index d08e00b0..706b5a72 100644 --- a/examples/stm32f1/Cargo.toml +++ b/examples/stm32f1/Cargo.toml @@ -15,7 +15,7 @@ embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } defmt = "0.3" defmt-rtt = "0.4" -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" panic-probe = { version = "0.3", features = ["print-defmt"] } diff --git a/examples/stm32f2/Cargo.toml b/examples/stm32f2/Cargo.toml index 02045a79..62947bf4 100644 --- a/examples/stm32f2/Cargo.toml +++ b/examples/stm32f2/Cargo.toml @@ -13,7 +13,7 @@ embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [" defmt = "0.3" defmt-rtt = "0.4" -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" panic-probe = { version = "0.3", features = ["print-defmt"] } diff --git a/examples/stm32f3/Cargo.toml b/examples/stm32f3/Cargo.toml index a62eba9e..7ba9ff0c 100644 --- a/examples/stm32f3/Cargo.toml +++ b/examples/stm32f3/Cargo.toml @@ -15,7 +15,7 @@ embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } defmt = "0.3" defmt-rtt = "0.4" -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" panic-probe = { version = "0.3", features = ["print-defmt"] } diff --git a/examples/stm32f4/Cargo.toml b/examples/stm32f4/Cargo.toml index 4b2f3d21..77985a01 100644 --- a/examples/stm32f4/Cargo.toml +++ b/examples/stm32f4/Cargo.toml @@ -8,14 +8,14 @@ license = "MIT OR Apache-2.0" embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers", "arch-cortex-m", "executor-thread", "executor-interrupt"] } embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "unstable-traits", "tick-hz-32_768"] } -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "unstable-traits", "defmt", "stm32f429zi", "unstable-pac", "memory-x", "time-driver-any", "exti"] } +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "unstable-traits", "defmt", "stm32f429zi", "unstable-pac", "memory-x", "time-driver-any", "exti", "embedded-sdmmc", "chrono"] } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "nightly"] } defmt = "0.3" defmt-rtt = "0.4" -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" embedded-io = "0.4.0" diff --git a/examples/stm32f4/src/bin/sdmmc.rs b/examples/stm32f4/src/bin/sdmmc.rs index ebdfdb22..eeecbd32 100644 --- a/examples/stm32f4/src/bin/sdmmc.rs +++ b/examples/stm32f4/src/bin/sdmmc.rs @@ -39,7 +39,18 @@ async fn main(_spawner: Spawner) { // Should print 400kHz for initialization info!("Configured clock: {}", sdmmc.clock().0); - unwrap!(sdmmc.init_card(mhz(48)).await); + let mut err = None; + loop { + match sdmmc.init_card(mhz(24)).await { + Ok(_) => break, + Err(e) => { + if err != Some(e) { + info!("waiting for card error, retrying: {:?}", e); + err = Some(e); + } + } + } + } let card = unwrap!(sdmmc.card()); diff --git a/examples/stm32f7/Cargo.toml b/examples/stm32f7/Cargo.toml index 898e05c1..d9e9d668 100644 --- a/examples/stm32f7/Cargo.toml +++ b/examples/stm32f7/Cargo.toml @@ -16,7 +16,7 @@ embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defm defmt = "0.3" defmt-rtt = "0.4" -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" panic-probe = { version = "0.3", features = ["print-defmt"] } diff --git a/examples/stm32g0/Cargo.toml b/examples/stm32g0/Cargo.toml index a522fb42..6bbd3a53 100644 --- a/examples/stm32g0/Cargo.toml +++ b/examples/stm32g0/Cargo.toml @@ -13,7 +13,7 @@ embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [" defmt = "0.3" defmt-rtt = "0.4" -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" panic-probe = { version = "0.3", features = ["print-defmt"] } diff --git a/examples/stm32g0/src/bin/spi_neopixel.rs b/examples/stm32g0/src/bin/spi_neopixel.rs new file mode 100644 index 00000000..81fdd15c --- /dev/null +++ b/examples/stm32g0/src/bin/spi_neopixel.rs @@ -0,0 +1,101 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::dma::word::U5; +use embassy_stm32::dma::NoDma; +use embassy_stm32::spi::{Config, Spi}; +use embassy_stm32::time::Hertz; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +const NR_PIXELS: usize = 15; +const BITS_PER_PIXEL: usize = 24; // 24 for rgb, 32 for rgbw +const TOTAL_BITS: usize = NR_PIXELS * BITS_PER_PIXEL; + +struct RGB { + r: u8, + g: u8, + b: u8, +} +impl Default for RGB { + fn default() -> RGB { + RGB { r: 0, g: 0, b: 0 } + } +} +pub struct Ws2812 { + // Note that the U5 type controls the selection of 5 bits to output + bitbuffer: [U5; TOTAL_BITS], +} + +impl Ws2812 { + pub fn new() -> Ws2812 { + Ws2812 { + bitbuffer: [U5(0); TOTAL_BITS], + } + } + fn len(&self) -> usize { + return NR_PIXELS; + } + fn set(&mut self, idx: usize, rgb: RGB) { + self.render_color(idx, 0, rgb.g); + self.render_color(idx, 8, rgb.r); + self.render_color(idx, 16, rgb.b); + } + // transform one color byte into an array of 8 byte. Each byte in the array does represent 1 neopixel bit pattern + fn render_color(&mut self, pixel_idx: usize, offset: usize, color: u8) { + let mut bits = color as usize; + let mut idx = pixel_idx * BITS_PER_PIXEL + offset; + + // render one bit in one spi byte. High time first, then the low time + // clock should be 4 Mhz, 5 bits, each bit is 0.25 us. + // a one bit is send as a pulse of 0.75 high -- 0.50 low + // a zero bit is send as a pulse of 0.50 high -- 0.75 low + // clock frequency for the neopixel is exact 800 khz + // note that the mosi output should have a resistor to ground of 10k, + // to assure that between the bursts the line is low + for _i in 0..8 { + if idx >= TOTAL_BITS { + return; + } + let pattern = match bits & 0x80 { + 0x80 => 0b0000_1110, + _ => 0b000_1100, + }; + bits = bits << 1; + self.bitbuffer[idx] = U5(pattern); + idx += 1; + } + } +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Start test using spi as neopixel driver"); + + let mut spi = Spi::new_txonly_nosck(p.SPI1, p.PB5, p.DMA1_CH3, NoDma, Hertz(4_000_000), Config::default()); + + let mut neopixels = Ws2812::new(); + + loop { + let mut cnt: usize = 0; + for _i in 0..10 { + for idx in 0..neopixels.len() { + let color = match (cnt + idx) % 3 { + 0 => RGB { r: 0x21, g: 0, b: 0 }, + 1 => RGB { r: 0, g: 0x31, b: 0 }, + _ => RGB { r: 0, g: 0, b: 0x41 }, + }; + neopixels.set(idx, color); + } + cnt += 1; + // start sending the neopixel bit patters over spi to the neopixel string + spi.write(&neopixels.bitbuffer).await.ok(); + Timer::after(Duration::from_millis(500)).await; + } + Timer::after(Duration::from_millis(1000)).await; + } +} diff --git a/examples/stm32g4/src/bin/pwm.rs b/examples/stm32g4/src/bin/pwm.rs index 017e89e4..8f7842ed 100644 --- a/examples/stm32g4/src/bin/pwm.rs +++ b/examples/stm32g4/src/bin/pwm.rs @@ -15,8 +15,8 @@ async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); info!("Hello World!"); - let ch1 = PwmPin::new_ch1(p.PA5); - let mut pwm = SimplePwm::new(p.TIM2, Some(ch1), None, None, None, khz(10)); + let ch1 = PwmPin::new_ch1(p.PC0); + let mut pwm = SimplePwm::new(p.TIM1, Some(ch1), None, None, None, khz(10)); let max = pwm.get_max_duty(); pwm.enable(Channel::Ch1); diff --git a/examples/stm32h5/Cargo.toml b/examples/stm32h5/Cargo.toml index 149f8a58..67e6c76a 100644 --- a/examples/stm32h5/Cargo.toml +++ b/examples/stm32h5/Cargo.toml @@ -16,7 +16,7 @@ embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defm defmt = "0.3" defmt-rtt = "0.4" -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10" } diff --git a/examples/stm32h7/Cargo.toml b/examples/stm32h7/Cargo.toml index 8316498c..bd175a5b 100644 --- a/examples/stm32h7/Cargo.toml +++ b/examples/stm32h7/Cargo.toml @@ -16,7 +16,7 @@ embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defm defmt = "0.3" defmt-rtt = "0.4" -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10" } diff --git a/examples/stm32l0/Cargo.toml b/examples/stm32l0/Cargo.toml index d446d41b..d08e2b61 100644 --- a/examples/stm32l0/Cargo.toml +++ b/examples/stm32l0/Cargo.toml @@ -15,8 +15,8 @@ embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["de embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32l072cz", "time-driver-any", "exti", "unstable-traits", "memory-x"] } embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["sx127x", "time", "defmt"], optional = true} -lorawan-device = { version = "0.8.0", default-features = false, features = ["async"], optional = true } -lorawan = { version = "0.7.1", default-features = false, features = ["default-crypto"], optional = true } +lorawan-device = { version = "0.9.0", default-features = false, features = ["async"], optional = true } +lorawan = { version = "0.7.2", default-features = false, features = ["default-crypto"], optional = true } defmt = "0.3" defmt-rtt = "0.4" @@ -24,7 +24,7 @@ defmt-rtt = "0.4" embedded-storage = "0.3.0" embedded-io = "0.4.0" -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" panic-probe = { version = "0.3", features = ["print-defmt"] } futures = { version = "0.3.17", default-features = false, features = ["async-await"] } diff --git a/examples/stm32l0/src/bin/lorawan.rs b/examples/stm32l0/src/bin/lorawan.rs index 27d7c29c..ea01f610 100644 --- a/examples/stm32l0/src/bin/lorawan.rs +++ b/examples/stm32l0/src/bin/lorawan.rs @@ -45,7 +45,7 @@ async fn main(_spawner: Spawner) { let radio = Sx127xRadio::new(spi, cs, reset, ready_pin, DummySwitch).await.unwrap(); - let region = region::EU868::default().into(); + let region = region::Configuration::new(region::Region::EU868); let mut device: Device<_, Crypto, _, _> = Device::new(region, radio, LoraTimer::new(), Rng::new(p.RNG)); defmt::info!("Joining LoRaWAN network"); diff --git a/examples/stm32l1/Cargo.toml b/examples/stm32l1/Cargo.toml index e071c5d2..ff95571e 100644 --- a/examples/stm32l1/Cargo.toml +++ b/examples/stm32l1/Cargo.toml @@ -13,7 +13,7 @@ embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [" defmt = "0.3" defmt-rtt = "0.4" -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" panic-probe = { version = "0.3", features = ["print-defmt"] } diff --git a/examples/stm32l5/Cargo.toml b/examples/stm32l5/Cargo.toml index af305a19..d3dee525 100644 --- a/examples/stm32l5/Cargo.toml +++ b/examples/stm32l5/Cargo.toml @@ -18,7 +18,7 @@ defmt = "0.3" defmt-rtt = "0.4" panic-probe = { version = "0.3", features = ["print-defmt"] } -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" futures = { version = "0.3.17", default-features = false, features = ["async-await"] } diff --git a/examples/stm32u5/Cargo.toml b/examples/stm32u5/Cargo.toml index 65fc1b98..86bc83da 100644 --- a/examples/stm32u5/Cargo.toml +++ b/examples/stm32u5/Cargo.toml @@ -14,7 +14,7 @@ embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defm defmt = "0.3" defmt-rtt = "0.4" -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" panic-probe = { version = "0.3", features = ["print-defmt"] } diff --git a/examples/stm32wb/Cargo.toml b/examples/stm32wb/Cargo.toml index 835985ec..18b27b28 100644 --- a/examples/stm32wb/Cargo.toml +++ b/examples/stm32wb/Cargo.toml @@ -13,7 +13,7 @@ embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [" defmt = "0.3" defmt-rtt = "0.4" -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" panic-probe = { version = "0.3", features = ["print-defmt"] } diff --git a/examples/stm32wl/Cargo.toml b/examples/stm32wl/Cargo.toml index df295ca4..07f136b4 100644 --- a/examples/stm32wl/Cargo.toml +++ b/examples/stm32wl/Cargo.toml @@ -11,13 +11,13 @@ embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["de embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32wl55jc-cm4", "time-driver-any", "memory-x", "unstable-pac", "exti"] } embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["stm32wl", "time", "defmt"] } -lorawan-device = { version = "0.8.0", default-features = false, features = ["async"] } -lorawan = { version = "0.7.1", default-features = false, features = ["default-crypto"] } +lorawan-device = { version = "0.9.0", default-features = false, features = ["async"] } +lorawan = { version = "0.7.2", default-features = false, features = ["default-crypto"] } defmt = "0.3" defmt-rtt = "0.4" -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" embedded-storage = "0.3.0" diff --git a/examples/stm32wl/src/bin/lorawan.rs b/examples/stm32wl/src/bin/lorawan.rs index 7f34dd30..32f29cc5 100644 --- a/examples/stm32wl/src/bin/lorawan.rs +++ b/examples/stm32wl/src/bin/lorawan.rs @@ -63,7 +63,7 @@ async fn main(_spawner: Spawner) { radio_config.calibrate_image = CalibrateImage::ISM_863_870; let radio = SubGhzRadio::new(radio, rfs, irq, radio_config).unwrap(); - let mut region: region::Configuration = region::EU868::default().into(); + let mut region = region::Configuration::new(region::Region::EU868); // NOTE: This is specific for TTN, as they have a special RX1 delay region.set_receive_delay1(5000); diff --git a/rust-toolchain.toml b/rust-toolchain.toml index f7183d16..2301ddc8 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,7 +1,7 @@ # Before upgrading check that everything is available on all tier1 targets here: # https://rust-lang.github.io/rustup-components-history [toolchain] -channel = "nightly-2023-04-11" +channel = "nightly-2023-04-18" components = [ "rust-src", "rustfmt", "llvm-tools-preview" ] targets = [ "thumbv7em-none-eabi", diff --git a/tests/stm32/Cargo.toml b/tests/stm32/Cargo.toml index 8b70a101..3047c34c 100644 --- a/tests/stm32/Cargo.toml +++ b/tests/stm32/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [features] stm32f103c8 = ["embassy-stm32/stm32f103c8"] # Blue Pill -stm32f429zi = ["embassy-stm32/stm32f429zi"] # Nucleo +stm32f429zi = ["embassy-stm32/stm32f429zi", "sdmmc"] # Nucleo stm32g071rb = ["embassy-stm32/stm32g071rb"] # Nucleo stm32c031c6 = ["embassy-stm32/stm32c031c6"] # Nucleo stm32g491re = ["embassy-stm32/stm32g491re"] # Nucleo @@ -15,6 +15,8 @@ stm32wb55rg = ["embassy-stm32/stm32wb55rg"] # Nucleo stm32h563zi = ["embassy-stm32/stm32h563zi"] # Nucleo stm32u585ai = ["embassy-stm32/stm32u585ai"] # IoT board +sdmmc = [] + [dependencies] embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } @@ -31,6 +33,45 @@ embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10" } embedded-hal-async = { version = "=0.2.0-alpha.1" } panic-probe = { version = "0.3.0", features = ["print-defmt"] } +# BEGIN TESTS +# Generated by gen_test.py. DO NOT EDIT. +[[bin]] +name = "gpio" +path = "src/bin/gpio.rs" +required-features = [] + +[[bin]] +name = "sdmmc" +path = "src/bin/sdmmc.rs" +required-features = [ "sdmmc",] + +[[bin]] +name = "spi" +path = "src/bin/spi.rs" +required-features = [] + +[[bin]] +name = "spi_dma" +path = "src/bin/spi_dma.rs" +required-features = [] + +[[bin]] +name = "timer" +path = "src/bin/timer.rs" +required-features = [] + +[[bin]] +name = "usart" +path = "src/bin/usart.rs" +required-features = [] + +[[bin]] +name = "usart_dma" +path = "src/bin/usart_dma.rs" +required-features = [] + +# END TESTS + [profile.dev] debug = 2 debug-assertions = true diff --git a/tests/stm32/gen_test.py b/tests/stm32/gen_test.py new file mode 100644 index 00000000..8ff156c0 --- /dev/null +++ b/tests/stm32/gen_test.py @@ -0,0 +1,44 @@ +import os +import toml +from glob import glob + +abspath = os.path.abspath(__file__) +dname = os.path.dirname(abspath) +os.chdir(dname) + +# ======= load test list +tests = {} +for f in sorted(glob('./src/bin/*.rs')): + name = os.path.splitext(os.path.basename(f))[0] + features = [] + with open(f, 'r') as f: + for line in f: + if line.startswith('// required-features:'): + features = line.split(':', 2)[1].strip().split(',') + + tests[name] = features + +# ========= Update Cargo.toml + +things = { + 'bin': [ + { + 'name': f'{name}', + 'path': f'src/bin/{name}.rs', + 'required-features': features, + } + for name, features in tests.items() + ] +} + +SEPARATOR_START = '# BEGIN TESTS\n' +SEPARATOR_END = '# END TESTS\n' +HELP = '# Generated by gen_test.py. DO NOT EDIT.\n' +with open('Cargo.toml', 'r') as f: + data = f.read() +before, data = data.split(SEPARATOR_START, maxsplit=1) +_, after = data.split(SEPARATOR_END, maxsplit=1) +data = before + SEPARATOR_START + HELP + \ + toml.dumps(things) + SEPARATOR_END + after +with open('Cargo.toml', 'w') as f: + f.write(data) diff --git a/tests/stm32/src/bin/sdmmc.rs b/tests/stm32/src/bin/sdmmc.rs new file mode 100644 index 00000000..c4e50cb4 --- /dev/null +++ b/tests/stm32/src/bin/sdmmc.rs @@ -0,0 +1,148 @@ +// required-features: sdmmc +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::{assert_eq, *}; +use embassy_executor::Spawner; +use embassy_stm32::sdmmc::{DataBlock, Sdmmc}; +use embassy_stm32::time::mhz; +use embassy_stm32::{interrupt, Config}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + let mut config = Config::default(); + config.rcc.sys_ck = Some(mhz(48)); + config.rcc.pll48 = true; + let p = embassy_stm32::init(config); + + #[cfg(feature = "stm32f429zi")] + let (mut sdmmc, mut irq, mut dma, mut clk, mut cmd, mut d0, mut d1, mut d2, mut d3) = ( + p.SDIO, + interrupt::take!(SDIO), + p.DMA2_CH3, + p.PC12, + p.PD2, + p.PC8, + p.PC9, + p.PC10, + p.PC11, + ); + + // Arbitrary block index + let block_idx = 16; + + let mut pattern1 = DataBlock([0u8; 512]); + let mut pattern2 = DataBlock([0u8; 512]); + for i in 0..512 { + pattern1[i] = i as u8; + pattern2[i] = !i as u8; + } + + let mut block = DataBlock([0u8; 512]); + + // ======== Try 4bit. ============== + info!("initializing in 4-bit mode..."); + let mut s = Sdmmc::new_4bit( + &mut sdmmc, + &mut irq, + &mut dma, + &mut clk, + &mut cmd, + &mut d0, + &mut d1, + &mut d2, + &mut d3, + Default::default(), + ); + + let mut err = None; + loop { + match s.init_card(mhz(24)).await { + Ok(_) => break, + Err(e) => { + if err != Some(e) { + info!("waiting for card: {:?}", e); + err = Some(e); + } + } + } + } + + let card = unwrap!(s.card()); + + info!("Card: {:#?}", Debug2Format(card)); + info!("Clock: {}", s.clock()); + + info!("writing pattern1..."); + s.write_block(block_idx, &pattern1).await.unwrap(); + + info!("reading..."); + s.read_block(block_idx, &mut block).await.unwrap(); + assert_eq!(block, pattern1); + + info!("writing pattern2..."); + s.write_block(block_idx, &pattern2).await.unwrap(); + + info!("reading..."); + s.read_block(block_idx, &mut block).await.unwrap(); + assert_eq!(block, pattern2); + + drop(s); + + // ======== Try 1bit. ============== + info!("initializing in 1-bit mode..."); + let mut s = Sdmmc::new_1bit( + &mut sdmmc, + &mut irq, + &mut dma, + &mut clk, + &mut cmd, + &mut d0, + Default::default(), + ); + + let mut err = None; + loop { + match s.init_card(mhz(24)).await { + Ok(_) => break, + Err(e) => { + if err != Some(e) { + info!("waiting for card: {:?}", e); + err = Some(e); + } + } + } + } + + let card = unwrap!(s.card()); + + info!("Card: {:#?}", Debug2Format(card)); + info!("Clock: {}", s.clock()); + + info!("reading pattern2 written in 4bit mode..."); + s.read_block(block_idx, &mut block).await.unwrap(); + assert_eq!(block, pattern2); + + info!("writing pattern1..."); + s.write_block(block_idx, &pattern1).await.unwrap(); + + info!("reading..."); + s.read_block(block_idx, &mut block).await.unwrap(); + assert_eq!(block, pattern1); + + info!("writing pattern2..."); + s.write_block(block_idx, &pattern2).await.unwrap(); + + info!("reading..."); + s.read_block(block_idx, &mut block).await.unwrap(); + assert_eq!(block, pattern2); + + drop(s); + + info!("Test OK"); + cortex_m::asm::bkpt(); +}