diff --git a/.gitignore b/.gitignore index 1c221e876..a0b5d6a70 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ Cargo.lock third_party /Cargo.toml out/ +.zed diff --git a/docs/pages/bootloader.adoc b/docs/pages/bootloader.adoc index d8d50040b..53f85d995 100644 --- a/docs/pages/bootloader.adoc +++ b/docs/pages/bootloader.adoc @@ -88,8 +88,7 @@ Then, to sign your firmware given a declaration of `FIRMWARE_DIR` and a firmware [source, bash] ---- -shasum -a 512 -b $FIRMWARE_DIR/myfirmware > $SECRETS_DIR/message.txt -cat $SECRETS_DIR/message.txt | dd ibs=128 count=1 | xxd -p -r > $SECRETS_DIR/message.txt +shasum -a 512 -b $FIRMWARE_DIR/myfirmware | head -c128 | xxd -p -r > $SECRETS_DIR/message.txt signify -S -s $SECRETS_DIR/key.sec -m $SECRETS_DIR/message.txt -x $SECRETS_DIR/message.txt.sig cp $FIRMWARE_DIR/myfirmware $FIRMWARE_DIR/myfirmware+signed tail -n1 $SECRETS_DIR/message.txt.sig | base64 -d -i - | dd ibs=10 skip=1 >> $FIRMWARE_DIR/myfirmware+signed diff --git a/embassy-boot/src/boot_loader.rs b/embassy-boot/src/boot_loader.rs index 61d61b96e..5bffdc5ea 100644 --- a/embassy-boot/src/boot_loader.rs +++ b/embassy-boot/src/boot_loader.rs @@ -5,7 +5,7 @@ use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_sync::blocking_mutex::Mutex; use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind}; -use crate::{State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; +use crate::{State, DFU_DETACH_MAGIC, REVERT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; /// Errors returned by bootloader #[derive(PartialEq, Eq, Debug)] @@ -276,7 +276,7 @@ impl BootLoader BootLoader FirmwareState<'d, STATE> { // Make sure we are running a booted firmware to avoid reverting to a bad state. async fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> { - if self.get_state().await? == State::Boot { + let state = self.get_state().await?; + if state == State::Boot || state == State::DfuDetach || state == State::Revert { Ok(()) } else { Err(FirmwareUpdaterError::BadState) @@ -303,12 +304,7 @@ impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> { /// `mark_booted`. pub async fn get_state(&mut self) -> Result { self.state.read(0, &mut self.aligned).await?; - - if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) { - Ok(State::Swap) - } else { - Ok(State::Boot) - } + Ok(State::from(&self.aligned)) } /// Mark to trigger firmware swap on next boot. diff --git a/embassy-boot/src/firmware_updater/blocking.rs b/embassy-boot/src/firmware_updater/blocking.rs index d3c723456..08062b0d0 100644 --- a/embassy-boot/src/firmware_updater/blocking.rs +++ b/embassy-boot/src/firmware_updater/blocking.rs @@ -324,7 +324,8 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { // Make sure we are running a booted firmware to avoid reverting to a bad state. fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> { - if self.get_state()? == State::Boot || self.get_state()? == State::DfuDetach { + let state = self.get_state()?; + if state == State::Boot || state == State::DfuDetach || state == State::Revert { Ok(()) } else { Err(FirmwareUpdaterError::BadState) @@ -338,14 +339,7 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { /// `mark_booted`. pub fn get_state(&mut self) -> Result { self.state.read(0, &mut self.aligned)?; - - if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) { - Ok(State::Swap) - } else if !self.aligned.iter().any(|&b| b != DFU_DETACH_MAGIC) { - Ok(State::DfuDetach) - } else { - Ok(State::Boot) - } + Ok(State::from(&self.aligned)) } /// Mark to trigger firmware swap on next boot. diff --git a/embassy-boot/src/lib.rs b/embassy-boot/src/lib.rs index 8849055e8..e2c4cf771 100644 --- a/embassy-boot/src/lib.rs +++ b/embassy-boot/src/lib.rs @@ -25,6 +25,7 @@ pub use firmware_updater::{ FirmwareUpdaterError, }; +pub(crate) const REVERT_MAGIC: u8 = 0xC0; pub(crate) const BOOT_MAGIC: u8 = 0xD0; pub(crate) const SWAP_MAGIC: u8 = 0xF0; pub(crate) const DFU_DETACH_MAGIC: u8 = 0xE0; @@ -37,10 +38,30 @@ pub enum State { Boot, /// Bootloader has swapped the active partition with the dfu partition and will attempt boot. Swap, + /// Bootloader has reverted the active partition with the dfu partition and will attempt boot. + Revert, /// Application has received a request to reboot into DFU mode to apply an update. DfuDetach, } +impl From for State +where + T: AsRef<[u8]>, +{ + fn from(magic: T) -> State { + let magic = magic.as_ref(); + if !magic.iter().any(|&b| b != SWAP_MAGIC) { + State::Swap + } else if !magic.iter().any(|&b| b != REVERT_MAGIC) { + State::Revert + } else if !magic.iter().any(|&b| b != DFU_DETACH_MAGIC) { + State::DfuDetach + } else { + State::Boot + } + } +} + /// Buffer aligned to 32 byte boundary, largest known alignment requirement for embassy-boot. #[repr(align(32))] pub struct AlignedBuffer(pub [u8; N]); @@ -157,6 +178,9 @@ mod tests { // Running again should cause a revert assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); + // Next time we know it was reverted + assert_eq!(State::Revert, bootloader.prepare_boot(&mut page).unwrap()); + let mut read_buf = [0; FIRMWARE_SIZE]; flash.active().read(0, &mut read_buf).unwrap(); assert_eq!(ORIGINAL, read_buf); diff --git a/embassy-net-esp-hosted/src/control.rs b/embassy-net-esp-hosted/src/control.rs index c8cea8503..b1838a425 100644 --- a/embassy-net-esp-hosted/src/control.rs +++ b/embassy-net-esp-hosted/src/control.rs @@ -120,7 +120,7 @@ impl<'a> Control<'a> { pwd: unwrap!(String::try_from(password)), bssid: String::new(), listen_interval: 3, - is_wpa3_supported: false, + is_wpa3_supported: true, }; ioctl!(self, ReqConnectAp, RespConnectAp, req, resp); self.state_ch.set_link_state(LinkState::Up); diff --git a/embassy-net-esp-hosted/src/lib.rs b/embassy-net-esp-hosted/src/lib.rs index c78578bf1..f05e2a70a 100644 --- a/embassy-net-esp-hosted/src/lib.rs +++ b/embassy-net-esp-hosted/src/lib.rs @@ -137,7 +137,7 @@ where let (ch_runner, device) = ch::new(&mut state.ch, ch::driver::HardwareAddress::Ethernet([0; 6])); let state_ch = ch_runner.state_runner(); - let mut runner = Runner { + let runner = Runner { ch: ch_runner, state_ch, shared: &state.shared, @@ -148,7 +148,6 @@ where spi, heartbeat_deadline: Instant::now() + HEARTBEAT_MAX_GAP, }; - runner.init().await; (device, Control::new(state_ch, &state.shared), runner) } @@ -174,8 +173,6 @@ where IN: InputPin + Wait, OUT: OutputPin, { - async fn init(&mut self) {} - /// Run the packet processing. pub async fn run(mut self) -> ! { debug!("resetting..."); diff --git a/embassy-net/src/lib.rs b/embassy-net/src/lib.rs index ef53fb905..a7b7efa87 100644 --- a/embassy-net/src/lib.rs +++ b/embassy-net/src/lib.rs @@ -260,7 +260,10 @@ pub struct Stack<'d> { pub(crate) struct Inner { pub(crate) sockets: SocketSet<'static>, // Lifetime type-erased. pub(crate) iface: Interface, + /// Waker used for triggering polls. pub(crate) waker: WakerRegistration, + /// Waker used for waiting for link up or config up. + state_waker: WakerRegistration, hardware_address: HardwareAddress, next_local_port: u16, link_up: bool, @@ -270,7 +273,6 @@ pub(crate) struct Inner { static_v6: Option, #[cfg(feature = "dhcpv4")] dhcp_socket: Option, - config_waker: WakerRegistration, #[cfg(feature = "dns")] dns_socket: SocketHandle, #[cfg(feature = "dns")] @@ -326,6 +328,7 @@ pub fn new<'d, D: Driver, const SOCK: usize>( sockets, iface, waker: WakerRegistration::new(), + state_waker: WakerRegistration::new(), next_local_port, hardware_address, link_up: false, @@ -335,7 +338,6 @@ pub fn new<'d, D: Driver, const SOCK: usize>( static_v6: None, #[cfg(feature = "dhcpv4")] dhcp_socket: None, - config_waker: WakerRegistration::new(), #[cfg(feature = "dns")] dns_socket, #[cfg(feature = "dns")] @@ -421,10 +423,20 @@ impl<'d> Stack<'d> { v4_up || v6_up } + /// Wait for the network device to obtain a link signal. + pub async fn wait_link_up(&self) { + self.wait(|| self.is_link_up()).await + } + + /// Wait for the network device to lose link signal. + pub async fn wait_link_down(&self) { + self.wait(|| !self.is_link_up()).await + } + /// Wait for the network stack to obtain a valid IP configuration. /// /// ## Notes: - /// - Ensure [`Stack::run`] has been called before using this function. + /// - Ensure [`Runner::run`] has been started before using this function. /// /// - This function may never return (e.g. if no configuration is obtained through DHCP). /// The caller is supposed to handle a timeout for this case. @@ -451,13 +463,17 @@ impl<'d> Stack<'d> { /// // ... /// ``` pub async fn wait_config_up(&self) { - // If the config is up already, we can return immediately. - if self.is_config_up() { - return; - } + self.wait(|| self.is_config_up()).await + } - poll_fn(|cx| { - if self.is_config_up() { + /// Wait for the network stack to lose a valid IP configuration. + pub async fn wait_config_down(&self) { + self.wait(|| !self.is_config_up()).await + } + + fn wait<'a>(&'a self, mut predicate: impl FnMut() -> bool + 'a) -> impl Future + 'a { + poll_fn(move |cx| { + if predicate() { Poll::Ready(()) } else { // If the config is not up, we register a waker that is woken up @@ -465,13 +481,12 @@ impl<'d> Stack<'d> { trace!("Waiting for config up"); self.with_mut(|i| { - i.config_waker.register(cx.waker()); + i.state_waker.register(cx.waker()); }); Poll::Pending } }) - .await; } /// Get the current IPv4 configuration. @@ -775,7 +790,7 @@ impl Inner { .update_servers(&dns_servers[..count]); } - self.config_waker.wake(); + self.state_waker.wake(); } fn poll(&mut self, cx: &mut Context<'_>, driver: &mut D) { @@ -813,6 +828,7 @@ impl Inner { // Print when changed if old_link_up != self.link_up { info!("link_up = {:?}", self.link_up); + self.state_waker.wake(); } #[allow(unused_mut)] diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs index ed146844c..f229a5acd 100644 --- a/embassy-rp/src/clocks.rs +++ b/embassy-rp/src/clocks.rs @@ -847,6 +847,10 @@ impl<'d, T: GpinPin> Gpin<'d, T> { into_ref!(gpin); gpin.gpio().ctrl().write(|w| w.set_funcsel(0x08)); + #[cfg(feature = "_rp235x")] + gpin.pad_ctrl().write(|w| { + w.set_iso(false); + }); Gpin { gpin: gpin.map_into(), @@ -861,6 +865,7 @@ impl<'d, T: GpinPin> Gpin<'d, T> { impl<'d, T: GpinPin> Drop for Gpin<'d, T> { fn drop(&mut self) { + self.gpin.pad_ctrl().write(|_| {}); self.gpin .gpio() .ctrl() @@ -921,11 +926,15 @@ pub struct Gpout<'d, T: GpoutPin> { } impl<'d, T: GpoutPin> Gpout<'d, T> { - /// Create new general purpose cloud output. + /// Create new general purpose clock output. pub fn new(gpout: impl Peripheral

+ 'd) -> Self { into_ref!(gpout); gpout.gpio().ctrl().write(|w| w.set_funcsel(0x08)); + #[cfg(feature = "_rp235x")] + gpout.pad_ctrl().write(|w| { + w.set_iso(false); + }); Self { gpout } } @@ -1005,6 +1014,7 @@ impl<'d, T: GpoutPin> Gpout<'d, T> { impl<'d, T: GpoutPin> Drop for Gpout<'d, T> { fn drop(&mut self) { self.disable(); + self.gpout.pad_ctrl().write(|_| {}); self.gpout .gpio() .ctrl() diff --git a/embassy-rp/src/gpio.rs b/embassy-rp/src/gpio.rs index 31397172c..cb54375e4 100644 --- a/embassy-rp/src/gpio.rs +++ b/embassy-rp/src/gpio.rs @@ -16,9 +16,9 @@ use crate::{interrupt, pac, peripherals, Peripheral, RegExt}; const NEW_AW: AtomicWaker = AtomicWaker::new(); #[cfg(any(feature = "rp2040", feature = "rp235xa"))] -const BANK0_PIN_COUNT: usize = 30; +pub(crate) const BANK0_PIN_COUNT: usize = 30; #[cfg(feature = "rp235xb")] -const BANK0_PIN_COUNT: usize = 48; +pub(crate) const BANK0_PIN_COUNT: usize = 48; static BANK0_WAKERS: [AtomicWaker; BANK0_PIN_COUNT] = [NEW_AW; BANK0_PIN_COUNT]; #[cfg(feature = "qspi-as-gpio")] @@ -603,7 +603,7 @@ impl<'d> Flex<'d> { #[inline] fn bit(&self) -> u32 { - 1 << self.pin.pin() + 1 << (self.pin.pin() % 32) } /// Set the pin's pull. diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs index c357c14c2..d402cf793 100644 --- a/embassy-rp/src/lib.rs +++ b/embassy-rp/src/lib.rs @@ -42,6 +42,8 @@ pub mod rtc; pub mod spi; #[cfg(feature = "time-driver")] pub mod time_driver; +#[cfg(feature = "_rp235x")] +pub mod trng; pub mod uart; pub mod usb; pub mod watchdog; @@ -402,6 +404,8 @@ embassy_hal_internal::peripherals! { WATCHDOG, BOOTSEL, + + TRNG } #[cfg(all(not(feature = "boot2-none"), feature = "rp2040"))] diff --git a/embassy-rp/src/pio/mod.rs b/embassy-rp/src/pio/mod.rs index 68b1d6849..72aa8f104 100644 --- a/embassy-rp/src/pio/mod.rs +++ b/embassy-rp/src/pio/mod.rs @@ -5,7 +5,7 @@ use core::pin::Pin as FuturePin; use core::sync::atomic::{compiler_fence, Ordering}; use core::task::{Context, Poll}; -use atomic_polyfill::{AtomicU32, AtomicU8}; +use atomic_polyfill::{AtomicU64, AtomicU8}; use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; use fixed::types::extra::U8; @@ -731,6 +731,8 @@ impl<'d, PIO: Instance + 'd, const SM: usize> StateMachine<'d, PIO, SM> { w.set_autopull(config.shift_out.auto_fill); w.set_autopush(config.shift_in.auto_fill); }); + + #[cfg(feature = "rp2040")] sm.pinctrl().write(|w| { w.set_sideset_count(config.pins.sideset_count); w.set_set_count(config.pins.set_count); @@ -740,6 +742,52 @@ impl<'d, PIO: Instance + 'd, const SM: usize> StateMachine<'d, PIO, SM> { w.set_set_base(config.pins.set_base); w.set_out_base(config.pins.out_base); }); + + #[cfg(feature = "_rp235x")] + { + let mut low_ok = true; + let mut high_ok = true; + + let in_pins = config.pins.in_base..config.pins.in_base + config.in_count; + let side_pins = config.pins.sideset_base..config.pins.sideset_base + config.pins.sideset_count; + let set_pins = config.pins.set_base..config.pins.set_base + config.pins.set_count; + let out_pins = config.pins.out_base..config.pins.out_base + config.pins.out_count; + + for pin_range in [in_pins, side_pins, set_pins, out_pins] { + for pin in pin_range { + low_ok &= pin < 32; + high_ok &= pin >= 16; + } + } + + if !low_ok && !high_ok { + panic!( + "All pins must either be <32 or >=16, in:{:?}-{:?}, side:{:?}-{:?}, set:{:?}-{:?}, out:{:?}-{:?}", + config.pins.in_base, + config.pins.in_base + config.in_count - 1, + config.pins.sideset_base, + config.pins.sideset_base + config.pins.sideset_count - 1, + config.pins.set_base, + config.pins.set_base + config.pins.set_count - 1, + config.pins.out_base, + config.pins.out_base + config.pins.out_count - 1, + ) + } + let shift = if low_ok { 0 } else { 16 }; + + sm.pinctrl().write(|w| { + w.set_sideset_count(config.pins.sideset_count); + w.set_set_count(config.pins.set_count); + w.set_out_count(config.pins.out_count); + w.set_in_base(config.pins.in_base.checked_sub(shift).unwrap_or_default()); + w.set_sideset_base(config.pins.sideset_base.checked_sub(shift).unwrap_or_default()); + w.set_set_base(config.pins.set_base.checked_sub(shift).unwrap_or_default()); + w.set_out_base(config.pins.out_base.checked_sub(shift).unwrap_or_default()); + }); + + PIO::PIO.gpiobase().write(|w| w.set_gpiobase(shift == 16)); + } + if let Some(origin) = config.origin { unsafe { instr::exec_jmp(self, origin) } } @@ -1006,6 +1054,10 @@ impl<'d, PIO: Instance> Common<'d, PIO> { pub fn make_pio_pin(&mut self, pin: impl Peripheral

+ 'd) -> Pin<'d, PIO> { into_ref!(pin); pin.gpio().ctrl().write(|w| w.set_funcsel(PIO::FUNCSEL as _)); + #[cfg(feature = "_rp235x")] + pin.pad_ctrl().modify(|w| { + w.set_iso(false); + }); // we can be relaxed about this because we're &mut here and nothing is cached PIO::state().used_pins.fetch_or(1 << pin.pin_bank(), Ordering::Relaxed); Pin { @@ -1187,7 +1239,7 @@ impl<'d, PIO: Instance> Pio<'d, PIO> { // other way. pub struct State { users: AtomicU8, - used_pins: AtomicU32, + used_pins: AtomicU64, } fn on_pio_drop() { @@ -1195,8 +1247,7 @@ fn on_pio_drop() { if state.users.fetch_sub(1, Ordering::AcqRel) == 1 { let used_pins = state.used_pins.load(Ordering::Relaxed); let null = pac::io::vals::Gpio0ctrlFuncsel::NULL as _; - // we only have 30 pins. don't test the other two since gpio() asserts. - for i in 0..30 { + for i in 0..crate::gpio::BANK0_PIN_COUNT { if used_pins & (1 << i) != 0 { pac::IO_BANK0.gpio(i).ctrl().write(|w| w.set_funcsel(null)); } @@ -1221,7 +1272,7 @@ trait SealedInstance { fn state() -> &'static State { static STATE: State = State { users: AtomicU8::new(0), - used_pins: AtomicU32::new(0), + used_pins: AtomicU64::new(0), }; &STATE diff --git a/embassy-rp/src/trng.rs b/embassy-rp/src/trng.rs new file mode 100644 index 000000000..9f2f33c4b --- /dev/null +++ b/embassy-rp/src/trng.rs @@ -0,0 +1,405 @@ +//! True Random Number Generator (TRNG) driver. + +use core::future::poll_fn; +use core::marker::PhantomData; +use core::ops::Not; +use core::task::Poll; + +use embassy_hal_internal::Peripheral; +use embassy_sync::waitqueue::AtomicWaker; +use rand_core::Error; + +use crate::interrupt::typelevel::{Binding, Interrupt}; +use crate::peripherals::TRNG; +use crate::{interrupt, pac}; + +trait SealedInstance { + fn regs() -> pac::trng::Trng; + fn waker() -> &'static AtomicWaker; +} + +/// TRNG peripheral instance. +#[allow(private_bounds)] +pub trait Instance: SealedInstance { + /// Interrupt for this peripheral. + type Interrupt: Interrupt; +} + +impl SealedInstance for TRNG { + fn regs() -> rp_pac::trng::Trng { + pac::TRNG + } + + fn waker() -> &'static AtomicWaker { + static WAKER: AtomicWaker = AtomicWaker::new(); + &WAKER + } +} + +impl Instance for TRNG { + type Interrupt = interrupt::typelevel::TRNG_IRQ; +} + +#[derive(Copy, Clone, Debug)] +#[allow(missing_docs)] +/// TRNG ROSC Inverter chain length options. +pub enum InverterChainLength { + None = 0, + One, + Two, + Three, + Four, +} + +impl From for u8 { + fn from(value: InverterChainLength) -> Self { + value as u8 + } +} + +/// Configuration for the TRNG. +/// +/// - Three built in entropy checks +/// - ROSC frequency controlled by selecting one of ROSC chain lengths +/// - Sample period in terms of system clock ticks +/// +/// +/// Default configuration is based on the following from documentation: +/// +/// ---- +/// +/// RP2350 Datasheet 12.12.2 +/// +/// ... +/// +/// When configuring the TRNG block, consider the following principles: +/// • As average generation time increases, result quality increases and failed entropy checks decrease. +/// • A low sample count decreases average generation time, but increases the chance of NIST test-failing results and +/// failed entropy checks. +/// For acceptable results with an average generation time of about 2 milliseconds, use ROSC chain length settings of 0 or +/// 1 and sample count settings of 20-25. +/// +/// --- +/// +/// Note, Pico SDK and Bootrom don't use any of the entropy checks and sample the ROSC directly +/// by setting the sample period to 0. Random data collected this way is then passed through +/// either hardware accelerated SHA256 (Bootrom) or xoroshiro128** (version 1.0!). +#[non_exhaustive] +#[derive(Copy, Clone, Debug)] +pub struct Config { + /// Bypass TRNG autocorrelation test + pub disable_autocorrelation_test: bool, + /// Bypass CRNGT test + pub disable_crngt_test: bool, + /// When set, the Von-Neuman balancer is bypassed (including the + /// 32 consecutive bits test) + pub disable_von_neumann_balancer: bool, + /// Sets the number of rng_clk cycles between two consecutive + /// ring oscillator samples. + /// Note: If the von Neumann decorrelator is bypassed, the minimum value for + /// sample counter must not be less than seventeen + pub sample_count: u32, + /// Selects the number of inverters (out of four possible + /// selections) in the ring oscillator (the entropy source). Higher values select + /// longer inverter chain lengths. + pub inverter_chain_length: InverterChainLength, +} + +impl Default for Config { + fn default() -> Self { + Config { + disable_autocorrelation_test: true, + disable_crngt_test: true, + disable_von_neumann_balancer: true, + sample_count: 25, + inverter_chain_length: InverterChainLength::One, + } + } +} + +/// True Random Number Generator Driver for RP2350 +/// +/// This driver provides async and blocking options. +/// +/// See [Config] for configuration details. +/// +/// Usage example: +/// ```no_run +/// use embassy_executor::Spawner; +/// use embassy_rp::trng::Trng; +/// use embassy_rp::peripherals::TRNG; +/// use embassy_rp::bind_interrupts; +/// +/// bind_interrupts!(struct Irqs { +/// TRNG_IRQ => embassy_rp::trng::InterruptHandler; +/// }); +/// +/// #[embassy_executor::main] +/// async fn main(spawner: Spawner) { +/// let peripherals = embassy_rp::init(Default::default()); +/// let mut trng = Trng::new(peripherals.TRNG, Irqs, embassy_rp::trng::Config::default()); +/// +/// let mut randomness = [0u8; 58]; +/// loop { +/// trng.fill_bytes(&mut randomness).await; +/// assert_ne!(randomness, [0u8; 58]); +/// } +///} +/// ``` +pub struct Trng<'d, T: Instance> { + phantom: PhantomData<&'d mut T>, +} + +/// 12.12.1. Overview +/// On request, the TRNG block generates a block of 192 entropy bits generated by automatically processing a series of +/// periodic samples from the TRNG block’s internal Ring Oscillator (ROSC). +const TRNG_BLOCK_SIZE_BITS: usize = 192; +const TRNG_BLOCK_SIZE_BYTES: usize = TRNG_BLOCK_SIZE_BITS / 8; + +impl<'d, T: Instance> Trng<'d, T> { + /// Create a new TRNG driver. + pub fn new( + _trng: impl Peripheral

+ 'd, + _irq: impl Binding> + 'd, + config: Config, + ) -> Self { + let regs = T::regs(); + + regs.rng_imr().write(|w| w.set_ehr_valid_int_mask(false)); + + let trng_config_register = regs.trng_config(); + trng_config_register.write(|w| { + w.set_rnd_src_sel(config.inverter_chain_length.clone().into()); + }); + + let sample_count_register = regs.sample_cnt1(); + sample_count_register.write(|w| { + *w = config.sample_count; + }); + + let debug_control_register = regs.trng_debug_control(); + debug_control_register.write(|w| { + w.set_auto_correlate_bypass(config.disable_autocorrelation_test); + w.set_trng_crngt_bypass(config.disable_crngt_test); + w.set_vnc_bypass(config.disable_von_neumann_balancer) + }); + + Trng { phantom: PhantomData } + } + + fn start_rng(&self) { + let regs = T::regs(); + let source_enable_register = regs.rnd_source_enable(); + // Enable TRNG ROSC + source_enable_register.write(|w| w.set_rnd_src_en(true)); + } + + fn stop_rng(&self) { + let regs = T::regs(); + let source_enable_register = regs.rnd_source_enable(); + source_enable_register.write(|w| w.set_rnd_src_en(false)); + let reset_bits_counter_register = regs.rst_bits_counter(); + reset_bits_counter_register.write(|w| w.set_rst_bits_counter(true)); + } + + fn enable_irq(&self) { + unsafe { T::Interrupt::enable() } + } + + fn disable_irq(&self) { + T::Interrupt::disable(); + } + + fn blocking_wait_for_successful_generation(&self) { + let regs = T::regs(); + + let trng_busy_register = regs.trng_busy(); + let trng_valid_register = regs.trng_valid(); + + let mut success = false; + while success.not() { + while trng_busy_register.read().trng_busy() {} + if trng_valid_register.read().ehr_valid().not() { + if regs.rng_isr().read().autocorr_err() { + regs.trng_sw_reset().write(|w| w.set_trng_sw_reset(true)); + } else { + panic!("RNG not busy, but ehr is not valid!") + } + } else { + success = true + } + } + } + + fn read_ehr_registers_into_array(&mut self, buffer: &mut [u8; TRNG_BLOCK_SIZE_BYTES]) { + let regs = T::regs(); + let ehr_data_regs = [ + regs.ehr_data0(), + regs.ehr_data1(), + regs.ehr_data2(), + regs.ehr_data3(), + regs.ehr_data4(), + regs.ehr_data5(), + ]; + + for (i, reg) in ehr_data_regs.iter().enumerate() { + buffer[i * 4..i * 4 + 4].copy_from_slice(®.read().to_ne_bytes()); + } + } + + fn blocking_read_ehr_registers_into_array(&mut self, buffer: &mut [u8; TRNG_BLOCK_SIZE_BYTES]) { + self.blocking_wait_for_successful_generation(); + self.read_ehr_registers_into_array(buffer); + } + + /// Fill the buffer with random bytes, async version. + pub async fn fill_bytes(&mut self, destination: &mut [u8]) { + if destination.is_empty() { + return; // Nothing to fill + } + + self.start_rng(); + self.enable_irq(); + + let mut bytes_transferred = 0usize; + let mut buffer = [0u8; TRNG_BLOCK_SIZE_BYTES]; + + let regs = T::regs(); + + let trng_busy_register = regs.trng_busy(); + let trng_valid_register = regs.trng_valid(); + + let waker = T::waker(); + + let destination_length = destination.len(); + + poll_fn(|context| { + waker.register(context.waker()); + if bytes_transferred == destination_length { + self.stop_rng(); + self.disable_irq(); + Poll::Ready(()) + } else { + if trng_busy_register.read().trng_busy() { + Poll::Pending + } else { + if trng_valid_register.read().ehr_valid().not() { + panic!("RNG not busy, but ehr is not valid!") + } + self.read_ehr_registers_into_array(&mut buffer); + let remaining = destination_length - bytes_transferred; + if remaining > TRNG_BLOCK_SIZE_BYTES { + destination[bytes_transferred..bytes_transferred + TRNG_BLOCK_SIZE_BYTES] + .copy_from_slice(&buffer); + bytes_transferred += TRNG_BLOCK_SIZE_BYTES + } else { + destination[bytes_transferred..bytes_transferred + remaining] + .copy_from_slice(&buffer[0..remaining]); + bytes_transferred += remaining + } + if bytes_transferred == destination_length { + self.stop_rng(); + self.disable_irq(); + Poll::Ready(()) + } else { + Poll::Pending + } + } + } + }) + .await + } + + /// Fill the buffer with random bytes, blocking version. + pub fn blocking_fill_bytes(&mut self, destination: &mut [u8]) { + if destination.is_empty() { + return; // Nothing to fill + } + self.start_rng(); + + let mut buffer = [0u8; TRNG_BLOCK_SIZE_BYTES]; + + for chunk in destination.chunks_mut(TRNG_BLOCK_SIZE_BYTES) { + self.blocking_wait_for_successful_generation(); + self.blocking_read_ehr_registers_into_array(&mut buffer); + chunk.copy_from_slice(&buffer[..chunk.len()]) + } + self.stop_rng() + } + + /// Return a random u32, blocking. + pub fn blocking_next_u32(&mut self) -> u32 { + let regs = T::regs(); + self.start_rng(); + self.blocking_wait_for_successful_generation(); + // 12.12.3 After successful generation, read the last result register, EHR_DATA[5] to + // clear all of the result registers. + let result = regs.ehr_data5().read(); + self.stop_rng(); + result + } + + /// Return a random u64, blocking. + pub fn blocking_next_u64(&mut self) -> u64 { + let regs = T::regs(); + self.start_rng(); + self.blocking_wait_for_successful_generation(); + + let low = regs.ehr_data4().read() as u64; + // 12.12.3 After successful generation, read the last result register, EHR_DATA[5] to + // clear all of the result registers. + let result = (regs.ehr_data5().read() as u64) << 32 | low; + self.stop_rng(); + result + } +} + +impl<'d, T: Instance> rand_core::RngCore for Trng<'d, T> { + fn next_u32(&mut self) -> u32 { + self.blocking_next_u32() + } + + fn next_u64(&mut self) -> u64 { + self.blocking_next_u64() + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.blocking_fill_bytes(dest) + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { + self.blocking_fill_bytes(dest); + Ok(()) + } +} +/// TRNG interrupt handler. +pub struct InterruptHandler { + _trng: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let regs = T::regs(); + let isr = regs.rng_isr().read(); + // Clear ehr bit + regs.rng_icr().write(|w| { + w.set_ehr_valid(true); + }); + if isr.ehr_valid() { + T::waker().wake(); + } else { + // 12.12.5. List of Registers + // ... + // TRNG: RNG_ISR Register + // ... + // AUTOCORR_ERR: 1 indicates Autocorrelation test failed four times in a row. + // When set, RNG ceases functioning until next reset + if isr.autocorr_err() { + warn!("TRNG Autocorrect error! Resetting TRNG"); + regs.trng_sw_reset().write(|w| { + w.set_trng_sw_reset(true); + }); + } + } + } +} diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index 3c6484c96..8fc8da006 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -47,7 +47,7 @@ embassy-time = { version = "0.3.2", path = "../embassy-time", optional = true } embassy-time-driver = { version = "0.1", path = "../embassy-time-driver", optional = true } embassy-futures = { version = "0.1.0", path = "../embassy-futures" } embassy-hal-internal = {version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-4"] } -embassy-embedded-hal = {version = "0.2.0", path = "../embassy-embedded-hal" } +embassy-embedded-hal = {version = "0.2.0", path = "../embassy-embedded-hal", default-features = false } embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver" } embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver" } embassy-usb-synopsys-otg = {version = "0.1.0", path = "../embassy-usb-synopsys-otg" } @@ -72,7 +72,7 @@ rand_core = "0.6.3" sdio-host = "0.5.0" critical-section = "1.1" #stm32-metapac = { version = "15" } -stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-ad00827345b4b758b2453082809d6e3b634b5364" } +stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-9b7414490b10ffbd5beb1b0dcf14adb018cbe37f" } vcell = "0.1.3" nb = "1.0.0" @@ -99,7 +99,7 @@ proc-macro2 = "1.0.36" quote = "1.0.15" #stm32-metapac = { version = "15", default-features = false, features = ["metadata"]} -stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-ad00827345b4b758b2453082809d6e3b634b5364", default-features = false, features = ["metadata"] } +stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-9b7414490b10ffbd5beb1b0dcf14adb018cbe37f", default-features = false, features = ["metadata"] } [features] default = ["rt"] @@ -129,7 +129,7 @@ unstable-pac = [] #! ## Time ## Enables additional driver features that depend on embassy-time -time = ["dep:embassy-time"] +time = ["dep:embassy-time", "embassy-embedded-hal/time"] # Features starting with `_` are for internal use only. They're not intended # to be enabled by other crates, and are not covered by semver guarantees. diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index e4cd001e6..fe04dd815 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -55,7 +55,7 @@ fn main() { let mut singletons: Vec = Vec::new(); for p in METADATA.peripherals { if let Some(r) = &p.registers { - if r.kind == "adccommon" || r.kind == "sai" || r.kind == "ucpd" { + if r.kind == "adccommon" || r.kind == "sai" || r.kind == "ucpd" || r.kind == "otg" { // TODO: should we emit this for all peripherals? if so, we will need a list of all // possible peripherals across all chips, so that we can declare the configs // (replacing the hard-coded list of `peri_*` cfgs below) @@ -111,6 +111,8 @@ fn main() { "peri_sai4", "peri_ucpd1", "peri_ucpd2", + "peri_usb_otg_fs", + "peri_usb_otg_hs", ]); cfgs.declare_all(&["mco", "mco1", "mco2"]); diff --git a/embassy-stm32/src/can/fd/peripheral.rs b/embassy-stm32/src/can/fd/peripheral.rs index 07e3dddad..1c7abfcb2 100644 --- a/embassy-stm32/src/can/fd/peripheral.rs +++ b/embassy-stm32/src/can/fd/peripheral.rs @@ -200,7 +200,7 @@ impl Registers { if header_reg.rtr().bit() { F::new_remote(id, len as usize) } else { - F::new(id, &data) + F::new(id, &data[0..(len as usize)]) } } else { // Abort request failed because the frame was already sent (or being sent) on diff --git a/embassy-stm32/src/dma/dma_bdma.rs b/embassy-stm32/src/dma/dma_bdma.rs index df041c4e9..d10b5554f 100644 --- a/embassy-stm32/src/dma/dma_bdma.rs +++ b/embassy-stm32/src/dma/dma_bdma.rs @@ -493,6 +493,26 @@ impl AnyChannel { } } + fn request_pause(&self) { + let info = self.info(); + match self.info().dma { + #[cfg(dma)] + DmaInfo::Dma(r) => { + // Disable the channel without overwriting the existing configuration + r.st(info.num).cr().modify(|w| { + w.set_en(false); + }); + } + #[cfg(bdma)] + DmaInfo::Bdma(r) => { + // Disable the channel without overwriting the existing configuration + r.ch(info.num).cr().modify(|w| { + w.set_en(false); + }); + } + } + } + fn is_running(&self) -> bool { let info = self.info(); match self.info().dma { @@ -667,12 +687,22 @@ impl<'a> Transfer<'a> { } /// Request the transfer to stop. + /// The configuration for this channel will **not be preserved**. If you need to restart the transfer + /// at a later point with the same configuration, see [`request_pause`](Self::request_pause) instead. /// /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. pub fn request_stop(&mut self) { self.channel.request_stop() } + /// Request the transfer to pause, keeping the existing configuration for this channel. + /// To restart the transfer, call [`start`](Self::start) again. + /// + /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. + pub fn request_pause(&mut self) { + self.channel.request_pause() + } + /// Return whether this transfer is still running. /// /// If this returns `false`, it can be because either the transfer finished, or @@ -846,13 +876,23 @@ impl<'a, W: Word> ReadableRingBuffer<'a, W> { DmaCtrlImpl(self.channel.reborrow()).set_waker(waker); } - /// Request DMA to stop. + /// Request the DMA to stop. + /// The configuration for this channel will **not be preserved**. If you need to restart the transfer + /// at a later point with the same configuration, see [`request_pause`](Self::request_pause) instead. /// /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. pub fn request_stop(&mut self) { self.channel.request_stop() } + /// Request the transfer to pause, keeping the existing configuration for this channel. + /// To restart the transfer, call [`start`](Self::start) again. + /// + /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. + pub fn request_pause(&mut self) { + self.channel.request_pause() + } + /// Return whether DMA is still running. /// /// If this returns `false`, it can be because either the transfer finished, or @@ -977,13 +1017,23 @@ impl<'a, W: Word> WritableRingBuffer<'a, W> { DmaCtrlImpl(self.channel.reborrow()).set_waker(waker); } - /// Request DMA to stop. + /// Request the DMA to stop. + /// The configuration for this channel will **not be preserved**. If you need to restart the transfer + /// at a later point with the same configuration, see [`request_pause`](Self::request_pause) instead. /// /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. pub fn request_stop(&mut self) { self.channel.request_stop() } + /// Request the transfer to pause, keeping the existing configuration for this channel. + /// To restart the transfer, call [`start`](Self::start) again. + /// + /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. + pub fn request_pause(&mut self) { + self.channel.request_pause() + } + /// Return whether DMA is still running. /// /// If this returns `false`, it can be because either the transfer finished, or diff --git a/embassy-stm32/src/dma/gpdma.rs b/embassy-stm32/src/dma/gpdma.rs index f9d66ca86..a877bb8d4 100644 --- a/embassy-stm32/src/dma/gpdma.rs +++ b/embassy-stm32/src/dma/gpdma.rs @@ -216,7 +216,10 @@ impl<'a> Transfer<'a> { data_size: WordSize, _options: TransferOptions, ) -> Self { - assert!(mem_len > 0 && mem_len <= 0xFFFF); + // BNDT is specified as bytes, not as number of transfers. + let Ok(bndt) = (mem_len * data_size.bytes()).try_into() else { + panic!("DMA transfers may not be larger than 65535 bytes."); + }; let info = channel.info(); let ch = info.dma.ch(info.num); @@ -226,9 +229,6 @@ impl<'a> Transfer<'a> { let this = Self { channel }; - #[cfg(dmamux)] - super::dmamux::configure_dmamux(&*this.channel, request); - ch.cr().write(|w| w.set_reset(true)); ch.fcr().write(|w| w.0 = 0xFFFF_FFFF); // clear all irqs ch.llr().write(|_| {}); // no linked list @@ -245,10 +245,8 @@ impl<'a> Transfer<'a> { }); w.set_reqsel(request); }); - ch.br1().write(|w| { - // BNDT is specified as bytes, not as number of transfers. - w.set_bndt((mem_len * data_size.bytes()) as u16) - }); + ch.tr3().write(|_| {}); // no address offsets. + ch.br1().write(|w| w.set_bndt(bndt)); match dir { Dir::MemoryToPeripheral => { diff --git a/embassy-stm32/src/eth/mod.rs b/embassy-stm32/src/eth/mod.rs index bfe8a60d6..6442176da 100644 --- a/embassy-stm32/src/eth/mod.rs +++ b/embassy-stm32/src/eth/mod.rs @@ -177,6 +177,20 @@ pub unsafe trait PHY { fn poll_link(&mut self, sm: &mut S, cx: &mut Context) -> bool; } +impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> { + /// Directly expose the SMI interface used by the Ethernet driver. + /// + /// This can be used to for example configure special PHY registers for compliance testing. + /// + /// # Safety + /// + /// Revert any temporary PHY register changes such as to enable test modes before handing + /// the Ethernet device over to the networking stack otherwise things likely won't work. + pub unsafe fn station_management(&mut self) -> &mut impl StationManagement { + &mut self.station_management + } +} + trait SealedInstance { fn regs() -> crate::pac::eth::Eth; } diff --git a/embassy-stm32/src/rcc/h.rs b/embassy-stm32/src/rcc/h.rs index 376a0b454..55fe8ca9d 100644 --- a/embassy-stm32/src/rcc/h.rs +++ b/embassy-stm32/src/rcc/h.rs @@ -34,8 +34,10 @@ pub enum VoltageScale { Scale2, Scale3, } -#[cfg(any(stm32h7rs))] +#[cfg(stm32h7rs)] pub use crate::pac::pwr::vals::Vos as VoltageScale; +#[cfg(all(stm32h7rs, peri_usb_otg_hs))] +pub use crate::pac::rcc::vals::{Usbphycsel, Usbrefcksel}; #[derive(Clone, Copy, Eq, PartialEq)] pub enum HseMode { @@ -557,6 +559,27 @@ pub(crate) unsafe fn init(config: Config) { let rtc = config.ls.init(); + #[cfg(all(stm32h7rs, peri_usb_otg_hs))] + let usb_refck = match config.mux.usbphycsel { + Usbphycsel::HSE => hse, + Usbphycsel::HSE_DIV_2 => hse.map(|hse_val| hse_val / 2u8), + Usbphycsel::PLL3_Q => pll3.q, + _ => None, + }; + #[cfg(all(stm32h7rs, peri_usb_otg_hs))] + let usb_refck_sel = match usb_refck { + Some(clk_val) => match clk_val { + Hertz(16_000_000) => Usbrefcksel::MHZ16, + Hertz(19_200_000) => Usbrefcksel::MHZ19_2, + Hertz(20_000_000) => Usbrefcksel::MHZ20, + Hertz(24_000_000) => Usbrefcksel::MHZ24, + Hertz(26_000_000) => Usbrefcksel::MHZ26, + Hertz(32_000_000) => Usbrefcksel::MHZ32, + _ => panic!("cannot select USBPHYC reference clock with source frequency of {} Hz, must be one of 16, 19.2, 20, 24, 26, 32 MHz", clk_val), + }, + None => Usbrefcksel::MHZ24, + }; + #[cfg(stm32h7)] { RCC.d1cfgr().modify(|w| { @@ -593,6 +616,11 @@ pub(crate) unsafe fn init(config: Config) { w.set_ppre4(config.apb4_pre); w.set_ppre5(config.apb5_pre); }); + + #[cfg(peri_usb_otg_hs)] + RCC.ahbperckselr().modify(|w| { + w.set_usbrefcksel(usb_refck_sel); + }); } #[cfg(stm32h5)] { @@ -698,7 +726,9 @@ pub(crate) unsafe fn init(config: Config) { #[cfg(stm32h7rs)] clk48mohci: None, // TODO #[cfg(stm32h7rs)] - usb: None, // TODO + hse_div_2: hse.map(|clk| clk / 2u32), + #[cfg(stm32h7rs)] + usb: Some(Hertz(48_000_000)), ); } @@ -769,7 +799,7 @@ fn init_pll(num: usize, config: Option, input: &PllInput) -> PllOutput { if num == 0 { // on PLL1, DIVP must be even for most series. // The enum value is 1 less than the divider, so check it's odd. - #[cfg(not(pwr_h7rm0468))] + #[cfg(not(any(pwr_h7rm0468, stm32h7rs)))] assert!(div.to_bits() % 2 == 1); #[cfg(pwr_h7rm0468)] assert!(div.to_bits() % 2 == 1 || div.to_bits() == 0); diff --git a/embassy-stm32/src/spi/mod.rs b/embassy-stm32/src/spi/mod.rs index 20718147a..d034c028e 100644 --- a/embassy-stm32/src/spi/mod.rs +++ b/embassy-stm32/src/spi/mod.rs @@ -311,51 +311,29 @@ impl<'d, M: PeriMode> Spi<'d, M> { } } + /// Set SPI word size. Disables SPI if needed, you have to enable it back yourself. fn set_word_size(&mut self, word_size: word_impl::Config) { if self.current_word_size == word_size { return; } + self.info.regs.cr1().modify(|w| { + w.set_spe(false); + }); + #[cfg(any(spi_v1, spi_f1))] - { - self.info.regs.cr1().modify(|reg| { - reg.set_spe(false); - reg.set_dff(word_size) - }); - self.info.regs.cr1().modify(|reg| { - reg.set_spe(true); - }); - } + self.info.regs.cr1().modify(|reg| { + reg.set_dff(word_size); + }); #[cfg(spi_v2)] - { - self.info.regs.cr1().modify(|w| { - w.set_spe(false); - }); - self.info.regs.cr2().modify(|w| { - w.set_frxth(word_size.1); - w.set_ds(word_size.0); - }); - self.info.regs.cr1().modify(|w| { - w.set_spe(true); - }); - } + self.info.regs.cr2().modify(|w| { + w.set_frxth(word_size.1); + w.set_ds(word_size.0); + }); #[cfg(any(spi_v3, spi_v4, spi_v5))] - { - self.info.regs.cr1().modify(|w| { - w.set_csusp(true); - }); - while self.info.regs.sr().read().eot() {} - self.info.regs.cr1().modify(|w| { - w.set_spe(false); - }); - self.info.regs.cfg1().modify(|w| { - w.set_dsize(word_size); - }); - self.info.regs.cr1().modify(|w| { - w.set_csusp(false); - w.set_spe(true); - }); - } + self.info.regs.cfg1().modify(|w| { + w.set_dsize(word_size); + }); self.current_word_size = word_size; } @@ -365,9 +343,9 @@ impl<'d, M: PeriMode> Spi<'d, M> { // needed in v3+ to avoid overrun causing the SPI RX state machine to get stuck...? #[cfg(any(spi_v3, spi_v4, spi_v5))] self.info.regs.cr1().modify(|w| w.set_spe(false)); + self.set_word_size(W::CONFIG); self.info.regs.cr1().modify(|w| w.set_spe(true)); flush_rx_fifo(self.info.regs); - self.set_word_size(W::CONFIG); for word in words.iter() { // this cannot use `transfer_word` because on SPIv2 and higher, // the SPI RX state machine hangs if no physical pin is connected to the SCK AF. @@ -402,9 +380,9 @@ impl<'d, M: PeriMode> Spi<'d, M> { // needed in v3+ to avoid overrun causing the SPI RX state machine to get stuck...? #[cfg(any(spi_v3, spi_v4, spi_v5))] self.info.regs.cr1().modify(|w| w.set_spe(false)); + self.set_word_size(W::CONFIG); self.info.regs.cr1().modify(|w| w.set_spe(true)); flush_rx_fifo(self.info.regs); - self.set_word_size(W::CONFIG); for word in words.iter_mut() { *word = transfer_word(self.info.regs, W::default())?; } @@ -418,9 +396,9 @@ impl<'d, M: PeriMode> Spi<'d, M> { // needed in v3+ to avoid overrun causing the SPI RX state machine to get stuck...? #[cfg(any(spi_v3, spi_v4, spi_v5))] self.info.regs.cr1().modify(|w| w.set_spe(false)); + self.set_word_size(W::CONFIG); self.info.regs.cr1().modify(|w| w.set_spe(true)); flush_rx_fifo(self.info.regs); - self.set_word_size(W::CONFIG); for word in words.iter_mut() { *word = transfer_word(self.info.regs, *word)?; } @@ -437,9 +415,9 @@ impl<'d, M: PeriMode> Spi<'d, M> { // needed in v3+ to avoid overrun causing the SPI RX state machine to get stuck...? #[cfg(any(spi_v3, spi_v4, spi_v5))] self.info.regs.cr1().modify(|w| w.set_spe(false)); + self.set_word_size(W::CONFIG); self.info.regs.cr1().modify(|w| w.set_spe(true)); flush_rx_fifo(self.info.regs); - 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(); @@ -648,10 +626,10 @@ impl<'d> Spi<'d, Async> { return Ok(()); } - self.set_word_size(W::CONFIG); self.info.regs.cr1().modify(|w| { w.set_spe(false); }); + self.set_word_size(W::CONFIG); let tx_dst = self.info.regs.tx_ptr(); let tx_f = unsafe { self.tx_dma.as_mut().unwrap().write(data, tx_dst, Default::default()) }; @@ -685,6 +663,8 @@ impl<'d> Spi<'d, Async> { w.set_spe(false); }); + self.set_word_size(W::CONFIG); + let comm = regs.cfg2().modify(|w| { let prev = w.comm(); w.set_comm(vals::Comm::RECEIVER); @@ -707,7 +687,6 @@ impl<'d> Spi<'d, Async> { let rx_src = regs.rx_ptr(); for mut chunk in data.chunks_mut(u16::max_value().into()) { - self.set_word_size(W::CONFIG); set_rxdmaen(regs, true); let tsize = chunk.len(); @@ -765,12 +744,12 @@ impl<'d> Spi<'d, Async> { return Ok(()); } - self.set_word_size(W::CONFIG); - self.info.regs.cr1().modify(|w| { w.set_spe(false); }); + self.set_word_size(W::CONFIG); + // SPIv3 clears rxfifo on SPE=0 #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] flush_rx_fifo(self.info.regs); @@ -783,7 +762,7 @@ impl<'d> Spi<'d, Async> { let rx_f = unsafe { self.rx_dma.as_mut().unwrap().read(rx_src, data, Default::default()) }; let tx_dst = self.info.regs.tx_ptr(); - let clock_byte = 0x00u8; + let clock_byte = W::default(); let tx_f = unsafe { self.tx_dma .as_mut() @@ -813,11 +792,12 @@ impl<'d> Spi<'d, Async> { return Ok(()); } - self.set_word_size(W::CONFIG); self.info.regs.cr1().modify(|w| { w.set_spe(false); }); + self.set_word_size(W::CONFIG); + // SPIv3 clears rxfifo on SPE=0 #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] flush_rx_fifo(self.info.regs); @@ -1195,7 +1175,7 @@ trait SealedWord { /// Word sizes usable for SPI. #[allow(private_bounds)] -pub trait Word: word::Word + SealedWord {} +pub trait Word: word::Word + SealedWord + Default {} macro_rules! impl_word { ($T:ty, $config:expr) => { diff --git a/embassy-stm32/src/usart/ringbuffered.rs b/embassy-stm32/src/usart/ringbuffered.rs index b0652046c..75834bf37 100644 --- a/embassy-stm32/src/usart/ringbuffered.rs +++ b/embassy-stm32/src/usart/ringbuffered.rs @@ -71,34 +71,19 @@ impl<'d> UartRx<'d, Async> { } impl<'d> RingBufferedUartRx<'d> { - /// Clear the ring buffer and start receiving in the background - pub fn start(&mut self) -> Result<(), Error> { - // Clear the ring buffer so that it is ready to receive data - self.ring_buf.clear(); - - self.setup_uart(); - - Ok(()) - } - - fn stop(&mut self, err: Error) -> Result { - self.teardown_uart(); - - Err(err) - } - /// Reconfigure the driver pub fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> { reconfigure(self.info, self.kernel_clock, config) } - /// Start uart background receive - fn setup_uart(&mut self) { - // fence before starting DMA. + /// Configure and start the DMA backed UART receiver + /// + /// Note: This is also done automatically by [`read()`] if required. + pub fn start_uart(&mut self) { + // Clear the buffer so that it is ready to receive data compiler_fence(Ordering::SeqCst); - - // start the dma controller self.ring_buf.start(); + self.ring_buf.clear(); let r = self.info.regs; // clear all interrupts and DMA Rx Request @@ -118,9 +103,9 @@ impl<'d> RingBufferedUartRx<'d> { }); } - /// Stop uart background receive - fn teardown_uart(&mut self) { - self.ring_buf.request_stop(); + /// Stop DMA backed UART receiver + fn stop_uart(&mut self) { + self.ring_buf.request_pause(); let r = self.info.regs; // clear all interrupts and DMA Rx Request @@ -153,13 +138,15 @@ impl<'d> RingBufferedUartRx<'d> { pub async fn read(&mut self, buf: &mut [u8]) -> Result { let r = self.info.regs; - // Start background receive if it was not already started + // Start DMA and Uart if it was not already started, + // otherwise check for errors in status register. + let sr = clear_idle_flag(r); if !r.cr3().read().dmar() { - self.start()?; + self.start_uart(); + } else { + check_for_errors(sr)?; } - check_for_errors(clear_idle_flag(r))?; - loop { match self.ring_buf.read(buf) { Ok((0, _)) => {} @@ -167,14 +154,16 @@ impl<'d> RingBufferedUartRx<'d> { return Ok(len); } Err(_) => { - return self.stop(Error::Overrun); + self.stop_uart(); + return Err(Error::Overrun); } } match self.wait_for_data_or_idle().await { Ok(_) => {} Err(err) => { - return self.stop(err); + self.stop_uart(); + return Err(err); } } } @@ -228,7 +217,7 @@ impl<'d> RingBufferedUartRx<'d> { impl Drop for RingBufferedUartRx<'_> { fn drop(&mut self) { - self.teardown_uart(); + self.stop_uart(); self.rx.as_ref().map(|x| x.set_as_disconnected()); self.rts.as_ref().map(|x| x.set_as_disconnected()); super::drop_tx_rx(self.info, self.state); diff --git a/embassy-stm32/src/usb/mod.rs b/embassy-stm32/src/usb/mod.rs index ce9fe0a9b..a473285bf 100644 --- a/embassy-stm32/src/usb/mod.rs +++ b/embassy-stm32/src/usb/mod.rs @@ -13,9 +13,19 @@ fn common_init() { // Check the USB clock is enabled and running at exactly 48 MHz. // frequency() will panic if not enabled let freq = T::frequency(); + + // On the H7RS, the USBPHYC embeds a PLL accepting one of the input frequencies listed below and providing 48MHz to OTG_FS and 60MHz to OTG_HS internally + #[cfg(stm32h7rs)] + if ![16_000_000, 19_200_000, 20_000_000, 24_000_000, 26_000_000, 32_000_000].contains(&freq.0) { + panic!( + "USB clock should be one of 16, 19.2, 20, 24, 26, 32Mhz but is {} Hz. Please double-check your RCC settings.", + freq.0 + ) + } // Check frequency is within the 0.25% tolerance allowed by the spec. // Clock might not be exact 48Mhz due to rounding errors in PLL calculation, or if the user // has tight clock restrictions due to something else (like audio). + #[cfg(not(stm32h7rs))] if freq.0.abs_diff(48_000_000) > 120_000 { panic!( "USB clock should be 48Mhz but is {} Hz. Please double-check your RCC settings.", @@ -48,6 +58,26 @@ fn common_init() { while !crate::pac::PWR.cr3().read().usb33rdy() {} } + #[cfg(stm32h7rs)] + { + // If true, VDD33USB is generated by internal regulator from VDD50USB + // If false, VDD33USB and VDD50USB must be suplied directly with 3.3V (default on nucleo) + // TODO: unhardcode + let internal_regulator = false; + + // Enable USB power + critical_section::with(|_| { + crate::pac::PWR.csr2().modify(|w| { + w.set_usbregen(internal_regulator); + w.set_usb33den(true); + w.set_usbhsregen(true); + }) + }); + + // Wait for USB power to stabilize + while !crate::pac::PWR.csr2().read().usb33rdy() {} + } + #[cfg(stm32u5)] { // Enable USB power diff --git a/embassy-stm32/src/usb/otg.rs b/embassy-stm32/src/usb/otg.rs index e27b164e4..00cafe6e4 100644 --- a/embassy-stm32/src/usb/otg.rs +++ b/embassy-stm32/src/usb/otg.rs @@ -97,6 +97,45 @@ impl<'d, T: Instance> Driver<'d, T> { } } + /// Initializes USB OTG peripheral with internal High-Speed PHY. + /// + /// # Arguments + /// + /// * `ep_out_buffer` - An internal buffer used to temporarily store received packets. + /// Must be large enough to fit all OUT endpoint max packet sizes. + /// Endpoint allocation will fail if it is too small. + pub fn new_hs( + _peri: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + dp: impl Peripheral

> + 'd, + dm: impl Peripheral

> + 'd, + ep_out_buffer: &'d mut [u8], + config: Config, + ) -> Self { + into_ref!(dp, dm); + + dp.set_as_af(dp.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); + dm.set_as_af(dm.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); + + let regs = T::regs(); + + let instance = OtgInstance { + regs, + state: T::state(), + fifo_depth_words: T::FIFO_DEPTH_WORDS, + extra_rx_fifo_words: RX_FIFO_EXTRA_SIZE_WORDS, + endpoint_count: T::ENDPOINT_COUNT, + phy_type: PhyType::InternalHighSpeed, + quirk_setup_late_cnak: quirk_setup_late_cnak(regs), + calculate_trdt_fn: calculate_trdt::, + }; + + Self { + inner: OtgDriver::new(ep_out_buffer, instance, config), + phantom: PhantomData, + } + } + /// Initializes USB OTG peripheral with external Full-speed PHY (usually, a High-speed PHY in Full-speed mode). /// /// # Arguments @@ -272,6 +311,19 @@ impl<'d, T: Instance> Bus<'d, T> { } }); + #[cfg(stm32h7rs)] + critical_section::with(|_| { + let rcc = crate::pac::RCC; + rcc.ahb1enr().modify(|w| { + w.set_usbphycen(true); + w.set_usb_otg_hsen(true); + }); + rcc.ahb1lpenr().modify(|w| { + w.set_usbphyclpen(true); + w.set_usb_otg_hslpen(true); + }); + }); + let r = T::regs(); let core_id = r.cid().read().0; trace!("Core id {:08x}", core_id); @@ -286,6 +338,7 @@ impl<'d, T: Instance> Bus<'d, T> { match core_id { 0x0000_1200 | 0x0000_1100 => self.inner.config_v1(), 0x0000_2000 | 0x0000_2100 | 0x0000_2300 | 0x0000_3000 | 0x0000_3100 => self.inner.config_v2v3(), + 0x0000_5000 => self.inner.config_v5(), _ => unimplemented!("Unknown USB core id {:X}", core_id), } } @@ -501,7 +554,7 @@ fn calculate_trdt(speed: Dspd) -> u8 { match speed { Dspd::HIGH_SPEED => { // From RM0431 (F72xx), RM0090 (F429), RM0390 (F446) - if ahb_freq >= 30_000_000 { + if ahb_freq >= 30_000_000 || cfg!(stm32h7rs) { 0x9 } else { panic!("AHB frequency is too low") diff --git a/embassy-sync/src/pubsub/mod.rs b/embassy-sync/src/pubsub/mod.rs index a97eb7d5b..812302e2b 100644 --- a/embassy-sync/src/pubsub/mod.rs +++ b/embassy-sync/src/pubsub/mod.rs @@ -194,6 +194,25 @@ impl crate::pubsub::PubSubBehavior + for PubSubChannel +{ + fn publish_immediate(&self, message: T) { + self.inner.lock(|s| { + let mut s = s.borrow_mut(); + s.publish_immediate(message) + }) + } + + fn capacity(&self) -> usize { + self.capacity() + } + + fn is_full(&self) -> bool { + self.is_full() + } +} + impl SealedPubSubBehavior for PubSubChannel { @@ -246,13 +265,6 @@ impl usize { - self.capacity() - } - fn free_capacity(&self) -> usize { self.free_capacity() } @@ -286,10 +294,6 @@ impl bool { self.is_empty() } - - fn is_full(&self) -> bool { - self.is_full() - } } /// Internal state for the PubSub channel @@ -445,8 +449,6 @@ pub enum Error { MaximumPublishersReached, } -/// 'Middle level' behaviour of the pubsub channel. -/// This trait is used so that Sub and Pub can be generic over the channel. trait SealedPubSubBehavior { /// Try to get a message from the queue with the given message id. /// @@ -462,12 +464,6 @@ trait SealedPubSubBehavior { /// If the queue is full and a context is given, then its waker is registered in the publisher wakers. fn publish_with_context(&self, message: T, cx: Option<&mut Context<'_>>) -> Result<(), T>; - /// Publish a message immediately - fn publish_immediate(&self, message: T); - - /// Returns the maximum number of elements the channel can hold. - fn capacity(&self) -> usize; - /// Returns the free capacity of the channel. /// /// This is equivalent to `capacity() - len()` @@ -482,9 +478,6 @@ trait SealedPubSubBehavior { /// Returns whether the channel is empty. fn is_empty(&self) -> bool; - /// Returns whether the channel is full. - fn is_full(&self) -> bool; - /// Let the channel know that a subscriber has dropped fn unregister_subscriber(&self, subscriber_next_message_id: u64); @@ -495,9 +488,16 @@ trait SealedPubSubBehavior { /// 'Middle level' behaviour of the pubsub channel. /// This trait is used so that Sub and Pub can be generic over the channel. #[allow(private_bounds)] -pub trait PubSubBehavior: SealedPubSubBehavior {} +pub trait PubSubBehavior: SealedPubSubBehavior { + /// Publish a message immediately + fn publish_immediate(&self, message: T); -impl> PubSubBehavior for C {} + /// Returns the maximum number of elements the channel can hold. + fn capacity(&self) -> usize; + + /// Returns whether the channel is full. + fn is_full(&self) -> bool; +} /// The result of the subscriber wait procedure #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/embassy-usb-synopsys-otg/src/lib.rs b/embassy-usb-synopsys-otg/src/lib.rs index b145f4aa8..f90403936 100644 --- a/embassy-usb-synopsys-otg/src/lib.rs +++ b/embassy-usb-synopsys-otg/src/lib.rs @@ -584,6 +584,29 @@ impl<'d, const MAX_EP_COUNT: usize> Bus<'d, MAX_EP_COUNT> { }); } + /// Applies configuration specific to + /// Core ID 0x0000_5000 + pub fn config_v5(&mut self) { + let r = self.instance.regs; + let phy_type = self.instance.phy_type; + + if phy_type == PhyType::InternalHighSpeed { + r.gccfg_v3().modify(|w| { + w.set_vbvaloven(!self.config.vbus_detection); + w.set_vbvaloval(!self.config.vbus_detection); + w.set_vbden(self.config.vbus_detection); + }); + } else { + r.gotgctl().modify(|w| { + w.set_bvaloen(!self.config.vbus_detection); + w.set_bvaloval(!self.config.vbus_detection); + }); + r.gccfg_v3().modify(|w| { + w.set_vbden(self.config.vbus_detection); + }); + } + } + fn init(&mut self) { let r = self.instance.regs; let phy_type = self.instance.phy_type; diff --git a/embassy-usb-synopsys-otg/src/otg_v1.rs b/embassy-usb-synopsys-otg/src/otg_v1.rs index d3abc328d..18e760fd1 100644 --- a/embassy-usb-synopsys-otg/src/otg_v1.rs +++ b/embassy-usb-synopsys-otg/src/otg_v1.rs @@ -186,6 +186,11 @@ impl Otg { pub const fn gccfg_v2(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x38usize) as _) } } + #[doc = "General core configuration register, for core_id 0x0000_5xxx"] + #[inline(always)] + pub const fn gccfg_v3(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x38usize) as _) } + } #[doc = "Core ID register"] #[inline(always)] pub const fn cid(self) -> Reg { @@ -1831,6 +1836,172 @@ pub mod regs { GccfgV2(0) } } + #[doc = "OTG general core configuration register."] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct GccfgV3(pub u32); + impl GccfgV3 { + #[doc = "Charger detection, result of the current mode (primary or secondary)."] + #[inline(always)] + pub const fn chgdet(&self) -> bool { + let val = (self.0 >> 0usize) & 0x01; + val != 0 + } + #[doc = "Charger detection, result of the current mode (primary or secondary)."] + #[inline(always)] + pub fn set_chgdet(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 0usize)) | (((val as u32) & 0x01) << 0usize); + } + #[doc = "Single-Ended DP indicator This bit gives the voltage level on DP (also result of the comparison with VLGC threshold as defined in BC v1.2 standard)."] + #[inline(always)] + pub const fn fsvplus(&self) -> bool { + let val = (self.0 >> 1usize) & 0x01; + val != 0 + } + #[doc = "Single-Ended DP indicator This bit gives the voltage level on DP (also result of the comparison with VLGC threshold as defined in BC v1.2 standard)."] + #[inline(always)] + pub fn set_fsvplus(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 1usize)) | (((val as u32) & 0x01) << 1usize); + } + #[doc = "Single-Ended DM indicator This bit gives the voltage level on DM (also result of the comparison with VLGC threshold as defined in BC v1.2 standard)."] + #[inline(always)] + pub const fn fsvminus(&self) -> bool { + let val = (self.0 >> 2usize) & 0x01; + val != 0 + } + #[doc = "Single-Ended DM indicator This bit gives the voltage level on DM (also result of the comparison with VLGC threshold as defined in BC v1.2 standard)."] + #[inline(always)] + pub fn set_fsvminus(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 2usize)) | (((val as u32) & 0x01) << 2usize); + } + #[doc = "VBUS session indicator Indicates if VBUS is above VBUS session threshold."] + #[inline(always)] + pub const fn sessvld(&self) -> bool { + let val = (self.0 >> 3usize) & 0x01; + val != 0 + } + #[doc = "VBUS session indicator Indicates if VBUS is above VBUS session threshold."] + #[inline(always)] + pub fn set_sessvld(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 3usize)) | (((val as u32) & 0x01) << 3usize); + } + #[doc = "Host CDP behavior enable."] + #[inline(always)] + pub const fn hcdpen(&self) -> bool { + let val = (self.0 >> 16usize) & 0x01; + val != 0 + } + #[doc = "Host CDP behavior enable."] + #[inline(always)] + pub fn set_hcdpen(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 16usize)) | (((val as u32) & 0x01) << 16usize); + } + #[doc = "Host CDP port voltage detector enable on DP."] + #[inline(always)] + pub const fn hcdpdeten(&self) -> bool { + let val = (self.0 >> 17usize) & 0x01; + val != 0 + } + #[doc = "Host CDP port voltage detector enable on DP."] + #[inline(always)] + pub fn set_hcdpdeten(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 17usize)) | (((val as u32) & 0x01) << 17usize); + } + #[doc = "Host CDP port Voltage source enable on DM."] + #[inline(always)] + pub const fn hvdmsrcen(&self) -> bool { + let val = (self.0 >> 18usize) & 0x01; + val != 0 + } + #[doc = "Host CDP port Voltage source enable on DM."] + #[inline(always)] + pub fn set_hvdmsrcen(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 18usize)) | (((val as u32) & 0x01) << 18usize); + } + #[doc = "Data Contact Detection enable."] + #[inline(always)] + pub const fn dcden(&self) -> bool { + let val = (self.0 >> 19usize) & 0x01; + val != 0 + } + #[doc = "Data Contact Detection enable."] + #[inline(always)] + pub fn set_dcden(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 19usize)) | (((val as u32) & 0x01) << 19usize); + } + #[doc = "Primary detection enable."] + #[inline(always)] + pub const fn pden(&self) -> bool { + let val = (self.0 >> 20usize) & 0x01; + val != 0 + } + #[doc = "Primary detection enable."] + #[inline(always)] + pub fn set_pden(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 20usize)) | (((val as u32) & 0x01) << 20usize); + } + #[doc = "VBUS detection enable Enables VBUS Sensing Comparators in order to detect VBUS presence and/or perform OTG operation."] + #[inline(always)] + pub const fn vbden(&self) -> bool { + let val = (self.0 >> 21usize) & 0x01; + val != 0 + } + #[doc = "VBUS detection enable Enables VBUS Sensing Comparators in order to detect VBUS presence and/or perform OTG operation."] + #[inline(always)] + pub fn set_vbden(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 21usize)) | (((val as u32) & 0x01) << 21usize); + } + #[doc = "Secondary detection enable."] + #[inline(always)] + pub const fn sden(&self) -> bool { + let val = (self.0 >> 22usize) & 0x01; + val != 0 + } + #[doc = "Secondary detection enable."] + #[inline(always)] + pub fn set_sden(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 22usize)) | (((val as u32) & 0x01) << 22usize); + } + #[doc = "Software override value of the VBUS B-session detection."] + #[inline(always)] + pub const fn vbvaloval(&self) -> bool { + let val = (self.0 >> 23usize) & 0x01; + val != 0 + } + #[doc = "Software override value of the VBUS B-session detection."] + #[inline(always)] + pub fn set_vbvaloval(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 23usize)) | (((val as u32) & 0x01) << 23usize); + } + #[doc = "Enables a software override of the VBUS B-session detection."] + #[inline(always)] + pub const fn vbvaloven(&self) -> bool { + let val = (self.0 >> 24usize) & 0x01; + val != 0 + } + #[doc = "Enables a software override of the VBUS B-session detection."] + #[inline(always)] + pub fn set_vbvaloven(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 24usize)) | (((val as u32) & 0x01) << 24usize); + } + #[doc = "Force host mode pull-downs If the ID pin functions are enabled, the host mode pull-downs on DP and DM activate automatically. However, whenever that is not the case, yet host mode is required, this bit must be used to force the pull-downs active."] + #[inline(always)] + pub const fn forcehostpd(&self) -> bool { + let val = (self.0 >> 25usize) & 0x01; + val != 0 + } + #[doc = "Force host mode pull-downs If the ID pin functions are enabled, the host mode pull-downs on DP and DM activate automatically. However, whenever that is not the case, yet host mode is required, this bit must be used to force the pull-downs active."] + #[inline(always)] + pub fn set_forcehostpd(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 25usize)) | (((val as u32) & 0x01) << 25usize); + } + } + impl Default for GccfgV3 { + #[inline(always)] + fn default() -> GccfgV3 { + GccfgV3(0) + } + } #[doc = "I2C access register"] #[repr(transparent)] #[derive(Copy, Clone, Eq, PartialEq)] diff --git a/examples/boot/application/nrf/build.rs b/examples/boot/application/nrf/build.rs index cd1a264c4..e1da69328 100644 --- a/examples/boot/application/nrf/build.rs +++ b/examples/boot/application/nrf/build.rs @@ -31,4 +31,7 @@ fn main() { println!("cargo:rustc-link-arg-bins=--nmagic"); println!("cargo:rustc-link-arg-bins=-Tlink.x"); + if env::var("CARGO_FEATURE_DEFMT").is_ok() { + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + } } diff --git a/examples/boot/application/nrf/src/bin/a.rs b/examples/boot/application/nrf/src/bin/a.rs index 851a3d721..2c1d1a7bb 100644 --- a/examples/boot/application/nrf/src/bin/a.rs +++ b/examples/boot/application/nrf/src/bin/a.rs @@ -2,6 +2,9 @@ #![no_main] #![macro_use] +#[cfg(feature = "defmt")] +use defmt_rtt as _; +use embassy_boot::State; use embassy_boot_nrf::{FirmwareUpdater, FirmwareUpdaterConfig}; use embassy_embedded_hal::adapter::BlockingAsync; use embassy_executor::Spawner; @@ -22,6 +25,7 @@ async fn main(_spawner: Spawner) { let mut button = Input::new(p.P0_11, Pull::Up); let mut led = Output::new(p.P0_13, Level::Low, OutputDrive::Standard); + let mut led_reverted = Output::new(p.P0_14, Level::High, OutputDrive::Standard); //let mut led = Output::new(p.P1_10, Level::Low, OutputDrive::Standard); //let mut button = Input::new(p.P1_02, Pull::Up); @@ -53,6 +57,13 @@ async fn main(_spawner: Spawner) { let config = FirmwareUpdaterConfig::from_linkerfile(&nvmc, &nvmc); let mut magic = [0; 4]; let mut updater = FirmwareUpdater::new(config, &mut magic); + let state = updater.get_state().await.unwrap(); + if state == State::Revert { + led_reverted.set_low(); + } else { + led_reverted.set_high(); + } + loop { led.set_low(); button.wait_for_any_edge().await; diff --git a/examples/rp/src/bin/pio_onewire.rs b/examples/rp/src/bin/pio_onewire.rs new file mode 100644 index 000000000..5076101ec --- /dev/null +++ b/examples/rp/src/bin/pio_onewire.rs @@ -0,0 +1,155 @@ +//! This example shows how you can use PIO to read a `DS18B20` one-wire temperature sensor. + +#![no_std] +#![no_main] +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{self, Common, Config, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut pio = Pio::new(p.PIO0, Irqs); + let mut sensor = Ds18b20::new(&mut pio.common, pio.sm0, p.PIN_2); + + loop { + sensor.start().await; // Start a new measurement + Timer::after_secs(1).await; // Allow 1s for the measurement to finish + match sensor.temperature().await { + Ok(temp) => info!("temp = {:?} deg C", temp), + _ => error!("sensor error"), + } + Timer::after_secs(1).await; + } +} + +/// DS18B20 temperature sensor driver +pub struct Ds18b20<'d, PIO: pio::Instance, const SM: usize> { + sm: StateMachine<'d, PIO, SM>, +} + +impl<'d, PIO: pio::Instance, const SM: usize> Ds18b20<'d, PIO, SM> { + /// Create a new instance the driver + pub fn new(common: &mut Common<'d, PIO>, mut sm: StateMachine<'d, PIO, SM>, pin: impl PioPin) -> Self { + let prg = pio_proc::pio_asm!( + r#" + .wrap_target + again: + pull block + mov x, osr + jmp !x, read + write: + set pindirs, 1 + set pins, 0 + loop1: + jmp x--,loop1 + set pindirs, 0 [31] + wait 1 pin 0 [31] + pull block + mov x, osr + bytes1: + pull block + set y, 7 + set pindirs, 1 + bit1: + set pins, 0 [1] + out pins,1 [31] + set pins, 1 [20] + jmp y--,bit1 + jmp x--,bytes1 + set pindirs, 0 [31] + jmp again + read: + pull block + mov x, osr + bytes2: + set y, 7 + bit2: + set pindirs, 1 + set pins, 0 [1] + set pindirs, 0 [5] + in pins,1 [10] + jmp y--,bit2 + jmp x--,bytes2 + .wrap + "#, + ); + + let pin = common.make_pio_pin(pin); + let mut cfg = Config::default(); + cfg.use_program(&common.load_program(&prg.program), &[]); + cfg.set_out_pins(&[&pin]); + cfg.set_in_pins(&[&pin]); + cfg.set_set_pins(&[&pin]); + cfg.shift_in = ShiftConfig { + auto_fill: true, + direction: ShiftDirection::Right, + threshold: 8, + }; + cfg.clock_divider = 255_u8.into(); + sm.set_config(&cfg); + sm.set_enable(true); + Self { sm } + } + + /// Write bytes over the wire + async fn write_bytes(&mut self, bytes: &[u8]) { + self.sm.tx().wait_push(250).await; + self.sm.tx().wait_push(bytes.len() as u32 - 1).await; + for b in bytes { + self.sm.tx().wait_push(*b as u32).await; + } + } + + /// Read bytes from the wire + async fn read_bytes(&mut self, bytes: &mut [u8]) { + self.sm.tx().wait_push(0).await; + self.sm.tx().wait_push(bytes.len() as u32 - 1).await; + for b in bytes.iter_mut() { + *b = (self.sm.rx().wait_pull().await >> 24) as u8; + } + } + + /// Calculate CRC8 of the data + fn crc8(data: &[u8]) -> u8 { + let mut temp; + let mut data_byte; + let mut crc = 0; + for b in data { + data_byte = *b; + for _ in 0..8 { + temp = (crc ^ data_byte) & 0x01; + crc >>= 1; + if temp != 0 { + crc ^= 0x8C; + } + data_byte >>= 1; + } + } + crc + } + + /// Start a new measurement. Allow at least 1000ms before getting `temperature`. + pub async fn start(&mut self) { + self.write_bytes(&[0xCC, 0x44]).await; + } + + /// Read the temperature. Ensure >1000ms has passed since `start` before calling this. + pub async fn temperature(&mut self) -> Result { + self.write_bytes(&[0xCC, 0xBE]).await; + let mut data = [0; 9]; + self.read_bytes(&mut data).await; + match Self::crc8(&data) == 0 { + true => Ok(((data[1] as u32) << 8 | data[0] as u32) as f32 / 16.), + false => Err(()), + } + } +} diff --git a/examples/rp23/src/bin/trng.rs b/examples/rp23/src/bin/trng.rs new file mode 100644 index 000000000..e146baa2e --- /dev/null +++ b/examples/rp23/src/bin/trng.rs @@ -0,0 +1,64 @@ +//! This example shows TRNG usage + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::TRNG; +use embassy_rp::trng::Trng; +use embassy_time::Timer; +use rand::RngCore; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +bind_interrupts!(struct Irqs { + TRNG_IRQ => embassy_rp::trng::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let peripherals = embassy_rp::init(Default::default()); + + // Initialize the TRNG with default configuration + let mut trng = Trng::new(peripherals.TRNG, Irqs, embassy_rp::trng::Config::default()); + // A buffer to collect random bytes in. + let mut randomness = [0u8; 58]; + + let mut led = Output::new(peripherals.PIN_25, Level::Low); + + loop { + trng.fill_bytes(&mut randomness).await; + info!("Random bytes async {}", &randomness); + trng.blocking_fill_bytes(&mut randomness); + info!("Random bytes blocking {}", &randomness); + let random_u32 = trng.next_u32(); + let random_u64 = trng.next_u64(); + info!("Random u32 {} u64 {}", random_u32, random_u64); + // Random number of blinks between 0 and 31 + let blinks = random_u32 % 32; + for _ in 0..blinks { + led.set_high(); + Timer::after_millis(20).await; + led.set_low(); + Timer::after_millis(20).await; + } + Timer::after_millis(1000).await; + } +} diff --git a/examples/stm32f4/src/bin/eth_compliance_test.rs b/examples/stm32f4/src/bin/eth_compliance_test.rs new file mode 100644 index 000000000..5946fed79 --- /dev/null +++ b/examples/stm32f4/src/bin/eth_compliance_test.rs @@ -0,0 +1,77 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::eth::generic_smi::GenericSMI; +use embassy_stm32::eth::{Ethernet, PacketQueue, StationManagement}; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, eth, peripherals, rng, Config}; +use embassy_time::Timer; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + ETH => eth::InterruptHandler; + HASH_RNG => rng::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Bypass, + }); + config.rcc.pll_src = PllSource::HSE; + config.rcc.pll = Some(Pll { + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL180, + divp: Some(PllPDiv::DIV2), // 8mhz / 4 * 180 / 2 = 180Mhz. + divq: None, + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV4; + config.rcc.apb2_pre = APBPrescaler::DIV2; + config.rcc.sys = Sysclk::PLL1_P; + } + let p = embassy_stm32::init(config); + + info!("Hello Compliance World!"); + + let mac_addr = [0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF]; + + const PHY_ADDR: u8 = 0; + static PACKETS: StaticCell> = StaticCell::new(); + let mut device = Ethernet::new( + PACKETS.init(PacketQueue::<4, 4>::new()), + p.ETH, + Irqs, + p.PA1, + p.PA2, + p.PC1, + p.PA7, + p.PC4, + p.PC5, + p.PG13, + p.PB13, + p.PG11, + GenericSMI::new(PHY_ADDR), + mac_addr, + ); + + let sm = unsafe { device.station_management() }; + + // Just an example. Exact register settings depend on the specific PHY and test. + sm.smi_write(PHY_ADDR, 0, 0x2100); + sm.smi_write(PHY_ADDR, 11, 0xA000); + + // NB: Remember to reset the PHY after testing before starting the networking stack + + loop { + Timer::after_secs(1).await; + } +} diff --git a/examples/stm32h7rs/src/bin/usb_serial.rs b/examples/stm32h7rs/src/bin/usb_serial.rs new file mode 100644 index 000000000..6773f7843 --- /dev/null +++ b/examples/stm32h7rs/src/bin/usb_serial.rs @@ -0,0 +1,140 @@ +#![no_std] +#![no_main] + +use defmt::{panic, *}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_stm32::time::Hertz; +use embassy_stm32::usb::{Driver, Instance}; +use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::Builder; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + OTG_HS => usb::InterruptHandler; +}); + +// If you are trying this and your USB device doesn't connect, the most +// common issues are the RCC config and vbus_detection +// +// See https://embassy.dev/book/#_the_usb_examples_are_not_working_on_my_board_is_there_anything_else_i_need_to_configure +// for more information. +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + let mut config = Config::default(); + + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: Hertz(24_000_000), + mode: HseMode::Oscillator, + }); + config.rcc.pll1 = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV12, + mul: PllMul::MUL300, + divp: Some(PllDiv::DIV1), //600 MHz + divq: Some(PllDiv::DIV2), // 300 MHz + divr: Some(PllDiv::DIV2), // 300 MHz + }); + config.rcc.sys = Sysclk::PLL1_P; // 600 MHz + config.rcc.ahb_pre = AHBPrescaler::DIV2; // 300 MHz + config.rcc.apb1_pre = APBPrescaler::DIV2; // 150 MHz + config.rcc.apb2_pre = APBPrescaler::DIV2; // 150 MHz + config.rcc.apb4_pre = APBPrescaler::DIV2; // 150 MHz + config.rcc.apb5_pre = APBPrescaler::DIV2; // 150 MHz + config.rcc.voltage_scale = VoltageScale::HIGH; + config.rcc.mux.usbphycsel = mux::Usbphycsel::HSE; + } + + let p = embassy_stm32::init(config); + + // Create the driver, from the HAL. + let mut ep_out_buffer = [0u8; 256]; + let mut config = embassy_stm32::usb::Config::default(); + + // Do not enable vbus_detection. This is a safe default that works in all boards. + // However, if your USB device is self-powered (can stay powered on if USB is unplugged), you need + // to enable vbus_detection to comply with the USB spec. If you enable it, the board + // has to support it or USB won't work at all. See docs on `vbus_detection` for details. + config.vbus_detection = false; + + let driver = Driver::new_hs(p.USB_OTG_HS, Irqs, p.PM6, p.PM5, &mut ep_out_buffer, config); + + // Create embassy-usb Config + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial example"); + config.serial_number = Some("12345678"); + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], // no msos descriptors + &mut control_buf, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} diff --git a/tests/stm32/src/bin/spi.rs b/tests/stm32/src/bin/spi.rs index 53d44a94a..9712a8c5a 100644 --- a/tests/stm32/src/bin/spi.rs +++ b/tests/stm32/src/bin/spi.rs @@ -7,7 +7,8 @@ use common::*; use defmt::assert_eq; use embassy_executor::Spawner; use embassy_stm32::gpio::{Level, Output, Speed}; -use embassy_stm32::spi::{self, Spi}; +use embassy_stm32::mode::Blocking; +use embassy_stm32::spi::{self, Spi, Word}; use embassy_stm32::time::Hertz; #[embassy_executor::main] @@ -31,11 +32,58 @@ async fn main(_spawner: Spawner) { spi_config, ); - let data: [u8; 9] = [0x00, 0xFF, 0xAA, 0x55, 0xC0, 0xFF, 0xEE, 0xC0, 0xDE]; + test_txrx::(&mut spi); + test_txrx::(&mut spi); + + // Assert the RCC bit gets disabled on drop. + #[cfg(feature = "stm32f429zi")] + defmt::assert!(embassy_stm32::pac::RCC.apb2enr().read().spi1en()); + drop(spi); + #[cfg(feature = "stm32f429zi")] + defmt::assert!(!embassy_stm32::pac::RCC.apb2enr().read().spi1en()); + + // test rx-only configuration + let mut spi = Spi::new_blocking_rxonly(&mut spi_peri, &mut sck, &mut miso, spi_config); + let mut mosi_out = Output::new(&mut mosi, Level::Low, Speed::VeryHigh); + + test_rx::(&mut spi, &mut mosi_out); + test_rx::(&mut spi, &mut mosi_out); + drop(spi); + drop(mosi_out); + + let mut spi = Spi::new_blocking_txonly(&mut spi_peri, &mut sck, &mut mosi, spi_config); + test_tx::(&mut spi); + test_tx::(&mut spi); + drop(spi); + + let mut spi = Spi::new_blocking_txonly_nosck(&mut spi_peri, &mut mosi, spi_config); + test_tx::(&mut spi); + test_tx::(&mut spi); + drop(spi); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} + +fn test_txrx + defmt::Format + Eq>(spi: &mut Spi<'_, Blocking>) +where + W: core::ops::Not, +{ + let data: [W; 9] = [ + 0x00u8.into(), + 0xFFu8.into(), + 0xAAu8.into(), + 0x55u8.into(), + 0xC0u8.into(), + 0xFFu8.into(), + 0xEEu8.into(), + 0xC0u8.into(), + 0xDEu8.into(), + ]; // Arduino pins D11 and D12 (MOSI-MISO) are connected together with a 1K resistor. // so we should get the data we sent back. - let mut buf = [0; 9]; + let mut buf = [W::default(); 9]; spi.blocking_transfer(&mut buf, &data).unwrap(); assert_eq!(buf, data); @@ -59,47 +107,33 @@ async fn main(_spawner: Spawner) { spi.blocking_transfer_in_place::(&mut []).unwrap(); spi.blocking_read::(&mut []).unwrap(); spi.blocking_write::(&[]).unwrap(); +} - // Assert the RCC bit gets disabled on drop. - #[cfg(feature = "stm32f429zi")] - defmt::assert!(embassy_stm32::pac::RCC.apb2enr().read().spi1en()); - drop(spi); - #[cfg(feature = "stm32f429zi")] - defmt::assert!(!embassy_stm32::pac::RCC.apb2enr().read().spi1en()); +fn test_rx + defmt::Format + Eq>(spi: &mut Spi<'_, Blocking>, mosi_out: &mut Output<'_>) +where + W: core::ops::Not, +{ + let mut buf = [W::default(); 9]; - // test rx-only configuration - let mut spi = Spi::new_blocking_rxonly(&mut spi_peri, &mut sck, &mut miso, spi_config); - let mut mosi_out = Output::new(&mut mosi, Level::Low, Speed::VeryHigh); mosi_out.set_high(); spi.blocking_read(&mut buf).unwrap(); - assert_eq!(buf, [0xff; 9]); + assert_eq!(buf, [!W::default(); 9]); mosi_out.set_low(); spi.blocking_read(&mut buf).unwrap(); - assert_eq!(buf, [0x00; 9]); + assert_eq!(buf, [W::default(); 9]); spi.blocking_read::(&mut []).unwrap(); spi.blocking_read::(&mut []).unwrap(); - drop(mosi_out); - drop(spi); +} + +fn test_tx + defmt::Format + Eq>(spi: &mut Spi<'_, Blocking>) +where + W: core::ops::Not, +{ + let buf = [W::default(); 9]; // Test tx-only. Just check it doesn't hang, not much else we can do without using SPI slave. - let mut spi = Spi::new_blocking_txonly(&mut spi_peri, &mut sck, &mut mosi, spi_config); - spi.blocking_transfer(&mut buf, &data).unwrap(); - spi.blocking_transfer_in_place(&mut buf).unwrap(); - spi.blocking_write(&buf).unwrap(); - spi.blocking_read(&mut buf).unwrap(); - spi.blocking_transfer::(&mut [], &[]).unwrap(); - spi.blocking_transfer_in_place::(&mut []).unwrap(); - spi.blocking_read::(&mut []).unwrap(); - spi.blocking_write::(&[]).unwrap(); - drop(spi); - - // Test tx-only nosck. - let mut spi = Spi::new_blocking_txonly_nosck(&mut spi_peri, &mut mosi, spi_config); spi.blocking_write(&buf).unwrap(); spi.blocking_write::(&[]).unwrap(); spi.blocking_write(&buf).unwrap(); - drop(spi); - - info!("Test OK"); - cortex_m::asm::bkpt(); + spi.blocking_write::(&[]).unwrap(); } diff --git a/tests/stm32/src/bin/spi_dma.rs b/tests/stm32/src/bin/spi_dma.rs index a1cbc0ed1..307409a16 100644 --- a/tests/stm32/src/bin/spi_dma.rs +++ b/tests/stm32/src/bin/spi_dma.rs @@ -7,7 +7,8 @@ use common::*; use defmt::assert_eq; use embassy_executor::Spawner; use embassy_stm32::gpio::{Level, Output, Speed}; -use embassy_stm32::spi::{self, Spi}; +use embassy_stm32::mode::Async; +use embassy_stm32::spi::{self, Spi, Word}; use embassy_stm32::time::Hertz; #[embassy_executor::main] @@ -35,11 +36,61 @@ async fn main(_spawner: Spawner) { spi_config, ); - let data: [u8; 9] = [0x00, 0xFF, 0xAA, 0x55, 0xC0, 0xFF, 0xEE, 0xC0, 0xDE]; + test_txrx::(&mut spi).await; + test_txrx::(&mut spi).await; + drop(spi); + + // test rx-only configuration + let mut spi = Spi::new_rxonly( + &mut spi_peri, + &mut sck, + &mut miso, + // SPIv1/f1 requires txdma even if rxonly. + #[cfg(not(feature = "spi-v345"))] + &mut tx_dma, + &mut rx_dma, + spi_config, + ); + let mut mosi_out = Output::new(&mut mosi, Level::Low, Speed::VeryHigh); + + test_rx::(&mut spi, &mut mosi_out).await; + test_rx::(&mut spi, &mut mosi_out).await; + drop(spi); + drop(mosi_out); + + let mut spi = Spi::new_txonly(&mut spi_peri, &mut sck, &mut mosi, &mut tx_dma, spi_config); + test_tx::(&mut spi).await; + test_tx::(&mut spi).await; + drop(spi); + + let mut spi = Spi::new_txonly_nosck(&mut spi_peri, &mut mosi, &mut tx_dma, spi_config); + test_tx::(&mut spi).await; + test_tx::(&mut spi).await; + drop(spi); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} + +async fn test_txrx + defmt::Format + Eq>(spi: &mut Spi<'_, Async>) +where + W: core::ops::Not, +{ + let data: [W; 9] = [ + 0x00u8.into(), + 0xFFu8.into(), + 0xAAu8.into(), + 0x55u8.into(), + 0xC0u8.into(), + 0xFFu8.into(), + 0xEEu8.into(), + 0xC0u8.into(), + 0xDEu8.into(), + ]; // Arduino pins D11 and D12 (MOSI-MISO) are connected together with a 1K resistor. // so we should get the data we sent back. - let mut buf = [0; 9]; + let mut buf = [W::default(); 9]; spi.transfer(&mut buf, &data).await.unwrap(); assert_eq!(buf, data); @@ -83,44 +134,41 @@ async fn main(_spawner: Spawner) { spi.blocking_write(&buf).unwrap(); spi.blocking_read(&mut buf).unwrap(); spi.write(&buf).await.unwrap(); +} - core::mem::drop(spi); +async fn test_rx + defmt::Format + Eq>(spi: &mut Spi<'_, Async>, mosi_out: &mut Output<'_>) +where + W: core::ops::Not, +{ + let mut buf = [W::default(); 9]; - // test rx-only configuration - let mut spi = Spi::new_rxonly( - &mut spi_peri, - &mut sck, - &mut miso, - // SPIv1/f1 requires txdma even if rxonly. - #[cfg(not(feature = "spi-v345"))] - &mut tx_dma, - &mut rx_dma, - spi_config, - ); - let mut mosi_out = Output::new(&mut mosi, Level::Low, Speed::VeryHigh); mosi_out.set_high(); spi.read(&mut buf).await.unwrap(); - assert_eq!(buf, [0xff; 9]); + assert_eq!(buf, [!W::default(); 9]); spi.blocking_read(&mut buf).unwrap(); - assert_eq!(buf, [0xff; 9]); + assert_eq!(buf, [!W::default(); 9]); spi.read(&mut buf).await.unwrap(); - assert_eq!(buf, [0xff; 9]); + assert_eq!(buf, [!W::default(); 9]); spi.read(&mut buf).await.unwrap(); - assert_eq!(buf, [0xff; 9]); + assert_eq!(buf, [!W::default(); 9]); spi.blocking_read(&mut buf).unwrap(); - assert_eq!(buf, [0xff; 9]); + assert_eq!(buf, [!W::default(); 9]); spi.blocking_read(&mut buf).unwrap(); - assert_eq!(buf, [0xff; 9]); + assert_eq!(buf, [!W::default(); 9]); mosi_out.set_low(); spi.read(&mut buf).await.unwrap(); - assert_eq!(buf, [0x00; 9]); + assert_eq!(buf, [W::default(); 9]); spi.read::(&mut []).await.unwrap(); spi.blocking_read::(&mut []).unwrap(); - drop(mosi_out); - drop(spi); +} + +async fn test_tx + defmt::Format + Eq>(spi: &mut Spi<'_, Async>) +where + W: core::ops::Not, +{ + let buf = [W::default(); 9]; // Test tx-only. Just check it doesn't hang, not much else we can do without using SPI slave. - let mut spi = Spi::new_txonly(&mut spi_peri, &mut sck, &mut mosi, &mut tx_dma, spi_config); spi.blocking_write(&buf).unwrap(); spi.write(&buf).await.unwrap(); spi.blocking_write(&buf).unwrap(); @@ -129,20 +177,4 @@ async fn main(_spawner: Spawner) { spi.write(&buf).await.unwrap(); spi.write::(&[]).await.unwrap(); spi.blocking_write::(&[]).unwrap(); - drop(spi); - - // Test tx-only nosck. - let mut spi = Spi::new_txonly_nosck(&mut spi_peri, &mut mosi, &mut tx_dma, spi_config); - spi.blocking_write(&buf).unwrap(); - spi.write(&buf).await.unwrap(); - spi.blocking_write(&buf).unwrap(); - spi.blocking_write(&buf).unwrap(); - spi.write(&buf).await.unwrap(); - spi.write(&buf).await.unwrap(); - spi.write::(&[]).await.unwrap(); - spi.blocking_write::(&[]).unwrap(); - drop(spi); - - info!("Test OK"); - cortex_m::asm::bkpt(); }