diff --git a/Cargo.toml b/Cargo.toml index 6e323744..3bdeb0cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,5 +25,4 @@ cortex-m-rt = "0.7.0" futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] } embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.9" } -embedded-hal-async = { version = "0.2.0-alpha.0" } num_enum = { version = "0.5.7", default-features = false } diff --git a/examples/rpi-pico-w/Cargo.toml b/examples/rpi-pico-w/Cargo.toml index 99b82ca3..4a531c88 100644 --- a/examples/rpi-pico-w/Cargo.toml +++ b/examples/rpi-pico-w/Cargo.toml @@ -5,10 +5,10 @@ edition = "2021" [dependencies] -cyw43 = { path = "../../", features = ["defmt", "firmware-logs"]} +cyw43 = { path = "../../", features = ["defmt", "firmware-logs"] } embassy-executor = { version = "0.1.0", features = ["defmt", "integrated-timers"] } embassy-time = { version = "0.1.0", features = ["defmt", "defmt-timestamp-uptime"] } -embassy-rp = { version = "0.1.0", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver"] } +embassy-rp = { version = "0.1.0", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver", "pio"] } embassy-net = { version = "0.1.0", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "unstable-traits", "nightly"] } atomic-polyfill = "0.1.5" static_cell = "1.0" @@ -17,12 +17,12 @@ defmt = "0.3" defmt-rtt = "0.3" 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 = ["critical-section-single-core"] } cortex-m-rt = "0.7.0" futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] } +pio-proc = "0.2" +pio = "0.2.1" -embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.9" } -embedded-hal-async = { version = "0.2.0-alpha.0" } embedded-io = { version = "0.4.0", features = ["async", "defmt"] } heapless = "0.7.15" diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index c51f1b24..67348e45 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -1,24 +1,26 @@ -#![no_std] +#![no_std] #![no_main] #![feature(type_alias_impl_trait)] #![feature(async_fn_in_trait)] #![allow(incomplete_features)] -use core::convert::Infallible; +mod pio; + +use core::slice; +use core::str::from_utf8; use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; use embassy_net::{Config, Stack, StackResources}; use embassy_rp::gpio::{Flex, Level, Output}; -use embassy_rp::peripherals::{PIN_23, PIN_24, PIN_25, PIN_29}; -use embedded_hal_1::spi::ErrorType; -use embedded_hal_async::spi::{ExclusiveDevice, SpiBusFlush, SpiBusRead, SpiBusWrite}; +use embassy_rp::peripherals::{DMA_CH0, PIN_23, PIN_24, PIN_25, PIN_29}; +use embassy_rp::pio::{Pio0, PioPeripherial, PioStateMachineInstance, Sm0}; use embedded_io::asynch::Write; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; -use core::str::from_utf8; +use crate::pio::PioSpi; macro_rules! singleton { ($val:expr) => {{ @@ -30,7 +32,11 @@ macro_rules! singleton { #[embassy_executor::task] async fn wifi_task( - runner: cyw43::Runner<'static, Output<'static, PIN_23>, ExclusiveDevice>>, + runner: cyw43::Runner< + 'static, + Output<'static, PIN_23>, + PioSpi, DMA_CH0>, + >, ) -> ! { runner.run().await } @@ -59,13 +65,15 @@ async fn main(spawner: Spawner) { let pwr = Output::new(p.PIN_23, Level::Low); let cs = Output::new(p.PIN_25, Level::High); - let clk = Output::new(p.PIN_29, Level::Low); - let mut dio = Flex::new(p.PIN_24); - dio.set_low(); - dio.set_as_output(); + // let clk = Output::new(p.PIN_29, Level::Low); + // let mut dio = Flex::new(p.PIN_24); + // dio.set_low(); + // dio.set_as_output(); + // // let bus = MySpi { clk, dio }; - let bus = MySpi { clk, dio }; - let spi = ExclusiveDevice::new(bus, cs); + let (_, sm, _, _, _) = p.PIO0.split(); + let dma = p.DMA_CH0; + let spi = PioSpi::new(sm, cs, p.PIN_24, p.PIN_29, dma); let state = singleton!(cyw43::State::new()); let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; @@ -109,6 +117,7 @@ async fn main(spawner: Spawner) { let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10))); + control.gpio_set(0, false).await; info!("Listening on TCP:1234..."); if let Err(e) = socket.accept(1234).await { warn!("accept error: {:?}", e); @@ -116,6 +125,7 @@ async fn main(spawner: Spawner) { } info!("Received connection from {:?}", socket.remote_endpoint()); + control.gpio_set(0, true).await; loop { let n = match socket.read(&mut buf).await { @@ -132,7 +142,6 @@ async fn main(spawner: Spawner) { info!("rxd {}", from_utf8(&buf[..n]).unwrap()); - match socket.write_all(&buf[..n]).await { Ok(()) => {} Err(e) => { @@ -154,20 +163,13 @@ struct MySpi { /// - IRQ /// - strap to set to gSPI mode on boot. dio: Flex<'static, PIN_24>, + + /// Chip select + cs: Output<'static, PIN_25>, } -impl ErrorType for MySpi { - type Error = Infallible; -} - -impl SpiBusFlush for MySpi { - async fn flush(&mut self) -> Result<(), Self::Error> { - Ok(()) - } -} - -impl SpiBusRead for MySpi { - async fn read(&mut self, words: &mut [u32]) -> Result<(), Self::Error> { +impl MySpi { + async fn read(&mut self, words: &mut [u32]) { self.dio.set_as_input(); for word in words { let mut w = 0; @@ -185,13 +187,9 @@ impl SpiBusRead for MySpi { } *word = w } - - Ok(()) } -} -impl SpiBusWrite for MySpi { - async fn write(&mut self, words: &[u32]) -> Result<(), Self::Error> { + async fn write(&mut self, words: &[u32]) { self.dio.set_as_output(); for word in words { let mut word = *word; @@ -213,6 +211,20 @@ impl SpiBusWrite for MySpi { self.clk.set_low(); self.dio.set_as_input(); - Ok(()) + } +} + +impl cyw43::SpiBusCyw43 for MySpi { + async fn cmd_write(&mut self, write: &[u32]) { + self.cs.set_low(); + self.write(write).await; + self.cs.set_high(); + } + + async fn cmd_read(&mut self, write: u32, read: &mut [u32]) { + self.cs.set_low(); + self.write(slice::from_ref(&write)).await; + self.read(read).await; + self.cs.set_high(); } } diff --git a/examples/rpi-pico-w/src/pio.rs b/examples/rpi-pico-w/src/pio.rs new file mode 100644 index 00000000..8017f4f4 --- /dev/null +++ b/examples/rpi-pico-w/src/pio.rs @@ -0,0 +1,169 @@ +use core::slice; + +use cyw43::SpiBusCyw43; +use embassy_rp::dma::Channel; +use embassy_rp::gpio::{Drive, Output, Pin, Pull, SlewRate}; +use embassy_rp::pio::{PioStateMachine, ShiftDirection}; +use embassy_rp::relocate::RelocatedProgram; +use embassy_rp::{pio_instr_util, Peripheral}; +use pio::Wrap; +use pio_proc::pio_asm; + +pub struct PioSpi { + cs: Output<'static, CS>, + sm: SM, + dma: DMA, + wrap_target: u8, +} + +impl PioSpi +where + SM: PioStateMachine, + DMA: Channel, + CS: Pin, +{ + pub fn new(mut sm: SM, cs: Output<'static, CS>, dio: DIO, clk: CLK, dma: DMA) -> Self + where + DIO: Pin, + CLK: Pin, + { + let program = pio_asm!( + ".side_set 1" + // "set pindirs, 1 side 0" + // "set pins, 0 side 0" + ".wrap_target" + "lp:", + "out pins, 1 side 0" + "jmp x-- lp side 1" + "set pindirs, 0 side 0" + "nop side 1" + "lp2:" + "in pins, 1 side 1" + "jmp y-- lp2 side 0" + + ".wrap" + ); + + let relocated = RelocatedProgram::new(&program.program); + + let mut pin_io = sm.make_pio_pin(dio); + pin_io.set_pull(Pull::Down); + pin_io.set_schmitt(true); + pin_io.set_input_sync_bypass(true); + + let mut pin_clk = sm.make_pio_pin(clk); + pin_clk.set_drive_strength(Drive::_12mA); + pin_clk.set_slew_rate(SlewRate::Fast); + + sm.write_instr(relocated.origin() as usize, relocated.code()); + + // 32 Mhz + sm.set_clkdiv(0x03E8); + + // 16 Mhz + // sm.set_clkdiv(0x07d0); + + // 8Mhz + // sm.set_clkdiv(0x0a_00); + + // 1Mhz + // sm.set_clkdiv(0x7d_00); + + // slowest possible + // sm.set_clkdiv(0xffff_00); + + sm.set_autopull(true); + // sm.set_pull_threshold(32); + sm.set_autopush(true); + // sm.set_push_threshold(32); + + sm.set_out_pins(&[&pin_io]); + sm.set_in_base_pin(&pin_io); + + sm.set_set_pins(&[&pin_clk]); + pio_instr_util::set_pindir(&mut sm, 0b1); + sm.set_set_pins(&[&pin_io]); + pio_instr_util::set_pindir(&mut sm, 0b1); + + sm.set_sideset_base_pin(&pin_clk); + sm.set_sideset_count(1); + + sm.set_out_shift_dir(ShiftDirection::Left); + sm.set_in_shift_dir(ShiftDirection::Left); + + let Wrap { source, target } = relocated.wrap(); + sm.set_wrap(source, target); + + // pull low for startup + pio_instr_util::set_pin(&mut sm, 0); + + Self { + cs, + sm, + dma, + wrap_target: target, + } + } + + pub async fn write(&mut self, write: &[u32]) { + let write_bits = write.len() * 32 - 1; + let read_bits = 31; + + defmt::trace!("write={} read={}", write_bits, read_bits); + + let mut dma = Peripheral::into_ref(&mut self.dma); + pio_instr_util::set_x(&mut self.sm, write_bits as u32); + pio_instr_util::set_y(&mut self.sm, read_bits as u32); + pio_instr_util::set_pindir(&mut self.sm, 0b1); + pio_instr_util::exec_jmp(&mut self.sm, self.wrap_target); + + self.sm.set_enable(true); + + self.sm.dma_push(dma.reborrow(), write).await; + + let mut status = 0; + self.sm.dma_pull(dma, slice::from_mut(&mut status)).await; + defmt::trace!("{:#08x}", status); + + self.sm.set_enable(false); + } + + pub async fn cmd_read(&mut self, cmd: u32, read: &mut [u32]) { + let write_bits = 31; + let read_bits = read.len() * 32 - 1; + + defmt::trace!("write={} read={}", write_bits, read_bits); + + let mut dma = Peripheral::into_ref(&mut self.dma); + pio_instr_util::set_y(&mut self.sm, read_bits as u32); + pio_instr_util::set_x(&mut self.sm, write_bits as u32); + pio_instr_util::set_pindir(&mut self.sm, 0b1); + pio_instr_util::exec_jmp(&mut self.sm, self.wrap_target); + // self.cs.set_low(); + self.sm.set_enable(true); + + self.sm.dma_push(dma.reborrow(), slice::from_ref(&cmd)).await; + self.sm.dma_pull(dma, read).await; + + self.sm.set_enable(false); + } +} + +impl SpiBusCyw43 for PioSpi +where + CS: Pin, + SM: PioStateMachine, + DMA: Channel, +{ + async fn cmd_write(&mut self, write: &[u32]) { + self.cs.set_low(); + self.write(write).await; + self.cs.set_high(); + } + + async fn cmd_read(&mut self, write: u32, read: &mut [u32]) { + self.cs.set_low(); + self.cmd_read(write, read).await; + self.cs.set_high(); + } +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml index ffbcbd6f..20c10c3f 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-2022-11-22" +channel = "nightly-2023-03-19" components = [ "rust-src", "rustfmt" ] targets = [ "thumbv6m-none-eabi", diff --git a/src/bus.rs b/src/bus.rs index f64c0abb..f77b890d 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -2,10 +2,25 @@ use core::slice; use embassy_time::{Duration, Timer}; use embedded_hal_1::digital::OutputPin; -use embedded_hal_async::spi::{transaction, SpiBusRead, SpiBusWrite, SpiDevice}; +use futures::FutureExt; use crate::consts::*; +/// Custom Spi Trait that _only_ supports the bus operation of the cyw43 +/// Implementors are expected to hold the CS pin low during an operation. +pub trait SpiBusCyw43 { + /// Issues a write command on the bus + /// First 32 bits of `word` are expected to be a cmd word + async fn cmd_write(&mut self, write: &[u32]); + + /// Issues a read command on the bus + /// `write` is expected to be a 32 bit cmd word + /// `read` will contain the response of the device + /// Backplane reads have a response delay that produces one extra unspecified word at the beginning of `read`. + /// Callers that want to read `n` word from the backplane, have to provide a slice that is `n+1` words long. + async fn cmd_read(&mut self, write: u32, read: &mut [u32]); +} + pub(crate) struct Bus { backplane_window: u32, pwr: PWR, @@ -15,8 +30,7 @@ pub(crate) struct Bus { impl Bus where PWR: OutputPin, - SPI: SpiDevice, - SPI::Bus: SpiBusRead + SpiBusWrite, + SPI: SpiBusCyw43, { pub(crate) fn new(pwr: PWR, spi: SPI) -> Self { Self { @@ -33,42 +47,50 @@ where self.pwr.set_high().unwrap(); Timer::after(Duration::from_millis(250)).await; - while self.read32_swapped(REG_BUS_TEST_RO).await != FEEDBEAD {} + while self + .read32_swapped(REG_BUS_TEST_RO) + .inspect(|v| defmt::trace!("{:#x}", v)) + .await + != FEEDBEAD + {} self.write32_swapped(REG_BUS_TEST_RW, TEST_PATTERN).await; let val = self.read32_swapped(REG_BUS_TEST_RW).await; + defmt::trace!("{:#x}", val); assert_eq!(val, TEST_PATTERN); + let val = self.read32_swapped(REG_BUS_CTRL).await; + defmt::trace!("{:#010b}", (val & 0xff)); + // 32-bit word length, little endian (which is the default endianess). self.write32_swapped(REG_BUS_CTRL, WORD_LENGTH_32 | HIGH_SPEED).await; + let val = self.read8(FUNC_BUS, REG_BUS_CTRL).await; + defmt::trace!("{:#b}", val); + let val = self.read32(FUNC_BUS, REG_BUS_TEST_RO).await; + defmt::trace!("{:#x}", val); assert_eq!(val, FEEDBEAD); let val = self.read32(FUNC_BUS, REG_BUS_TEST_RW).await; + defmt::trace!("{:#x}", val); assert_eq!(val, TEST_PATTERN); } pub async fn wlan_read(&mut self, buf: &mut [u32], len_in_u8: u32) { let cmd = cmd_word(READ, INC_ADDR, FUNC_WLAN, 0, len_in_u8); let len_in_u32 = (len_in_u8 as usize + 3) / 4; - transaction!(&mut self.spi, |bus| async { - bus.write(&[cmd]).await?; - bus.read(&mut buf[..len_in_u32]).await?; - Ok(()) - }) - .await - .unwrap(); + + self.spi.cmd_read(cmd, &mut buf[..len_in_u32]).await; } pub async fn wlan_write(&mut self, buf: &[u32]) { let cmd = cmd_word(WRITE, INC_ADDR, FUNC_WLAN, 0, buf.len() as u32 * 4); - transaction!(&mut self.spi, |bus| async { - bus.write(&[cmd]).await?; - bus.write(buf).await?; - Ok(()) - }) - .await - .unwrap(); + //TODO try to remove copy? + let mut cmd_buf = [0_u32; 513]; + cmd_buf[0] = cmd; + cmd_buf[1..][..buf.len()].copy_from_slice(buf); + + self.spi.cmd_write(&cmd_buf).await; } #[allow(unused)] @@ -79,7 +101,8 @@ where // To simplify, enforce 4-align for now. assert!(addr % 4 == 0); - let mut buf = [0u32; BACKPLANE_MAX_TRANSFER_SIZE / 4]; + // Backplane read buffer has one extra word for the response delay. + let mut buf = [0u32; BACKPLANE_MAX_TRANSFER_SIZE / 4 + 1]; while !data.is_empty() { // Ensure transfer doesn't cross a window boundary. @@ -92,21 +115,11 @@ where let cmd = cmd_word(READ, INC_ADDR, FUNC_BACKPLANE, window_offs, len as u32); - transaction!(&mut self.spi, |bus| async { - bus.write(&[cmd]).await?; + // round `buf` to word boundary, add one extra word for the response delay + self.spi.cmd_read(cmd, &mut buf[..(len + 3) / 4 + 1]).await; - // 4-byte response delay. - let mut junk = [0; 1]; - bus.read(&mut junk).await?; - - // Read data - bus.read(&mut buf[..(len + 3) / 4]).await?; - Ok(()) - }) - .await - .unwrap(); - - data[..len].copy_from_slice(&slice8_mut(&mut buf)[..len]); + // when writing out the data, we skip the response-delay byte + data[..len].copy_from_slice(&slice8_mut(&mut buf[1..])[..len]); // Advance ptr. addr += len as u32; @@ -121,7 +134,7 @@ where // To simplify, enforce 4-align for now. assert!(addr % 4 == 0); - let mut buf = [0u32; BACKPLANE_MAX_TRANSFER_SIZE / 4]; + let mut buf = [0u32; BACKPLANE_MAX_TRANSFER_SIZE / 4 + 1]; while !data.is_empty() { // Ensure transfer doesn't cross a window boundary. @@ -129,19 +142,14 @@ where let window_remaining = BACKPLANE_WINDOW_SIZE - window_offs as usize; let len = data.len().min(BACKPLANE_MAX_TRANSFER_SIZE).min(window_remaining); - slice8_mut(&mut buf)[..len].copy_from_slice(&data[..len]); + slice8_mut(&mut buf[1..])[..len].copy_from_slice(&data[..len]); self.backplane_set_window(addr).await; let cmd = cmd_word(WRITE, INC_ADDR, FUNC_BACKPLANE, window_offs, len as u32); + buf[0] = cmd; - transaction!(&mut self.spi, |bus| async { - bus.write(&[cmd]).await?; - bus.write(&buf[..(len + 3) / 4]).await?; - Ok(()) - }) - .await - .unwrap(); + self.spi.cmd_write(&buf[..(len + 3) / 4 + 1]).await; // Advance ptr. addr += len as u32; @@ -253,58 +261,41 @@ where async fn readn(&mut self, func: u32, addr: u32, len: u32) -> u32 { let cmd = cmd_word(READ, INC_ADDR, func, addr, len); - let mut buf = [0; 1]; + let mut buf = [0; 2]; + // if we are reading from the backplane, we need an extra word for the response delay + let len = if func == FUNC_BACKPLANE { 2 } else { 1 }; - transaction!(&mut self.spi, |bus| async { - bus.write(&[cmd]).await?; - if func == FUNC_BACKPLANE { - // 4-byte response delay. - bus.read(&mut buf).await?; - } - bus.read(&mut buf).await?; - Ok(()) - }) - .await - .unwrap(); + self.spi.cmd_read(cmd, &mut buf[..len]).await; - buf[0] + // if we read from the backplane, the result is in the second word, after the response delay + if func == FUNC_BACKPLANE { + buf[1] + } else { + buf[0] + } } async fn writen(&mut self, func: u32, addr: u32, val: u32, len: u32) { let cmd = cmd_word(WRITE, INC_ADDR, func, addr, len); - transaction!(&mut self.spi, |bus| async { - bus.write(&[cmd, val]).await?; - Ok(()) - }) - .await - .unwrap(); + self.spi.cmd_write(&[cmd, val]).await; } async fn read32_swapped(&mut self, addr: u32) -> u32 { let cmd = cmd_word(READ, INC_ADDR, FUNC_BUS, addr, 4); + let cmd = swap16(cmd); let mut buf = [0; 1]; - transaction!(&mut self.spi, |bus| async { - bus.write(&[swap16(cmd)]).await?; - bus.read(&mut buf).await?; - Ok(()) - }) - .await - .unwrap(); + self.spi.cmd_read(cmd, &mut buf).await; swap16(buf[0]) } async fn write32_swapped(&mut self, addr: u32, val: u32) { let cmd = cmd_word(WRITE, INC_ADDR, FUNC_BUS, addr, 4); + let buf = [swap16(cmd), swap16(val)]; - transaction!(&mut self.spi, |bus| async { - bus.write(&[swap16(cmd), swap16(val)]).await?; - Ok(()) - }) - .await - .unwrap(); + self.spi.cmd_write(&buf).await; } } diff --git a/src/lib.rs b/src/lib.rs index c58ac8e7..1b7d603d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ #![no_std] #![no_main] -#![feature(type_alias_impl_trait, concat_bytes)] +#![allow(incomplete_features)] +#![feature(async_fn_in_trait, type_alias_impl_trait, concat_bytes)] #![deny(unused_must_use)] // This mod MUST go first, so that the others see its macros. @@ -22,10 +23,10 @@ use embassy_net_driver_channel as ch; use embassy_sync::pubsub::PubSubBehavior; use embassy_time::{block_for, Duration, Timer}; use embedded_hal_1::digital::OutputPin; -use embedded_hal_async::spi::{SpiBusRead, SpiBusWrite, SpiDevice}; use events::EventQueue; use crate::bus::Bus; +pub use crate::bus::SpiBusCyw43; use crate::consts::*; use crate::events::{Event, EventStatus}; use crate::structs::*; @@ -533,8 +534,7 @@ pub async fn new<'a, PWR, SPI>( ) -> (NetDriver<'a>, Control<'a>, Runner<'a, PWR, SPI>) where PWR: OutputPin, - SPI: SpiDevice, - SPI::Bus: SpiBusRead + SpiBusWrite, + SPI: SpiBusCyw43, { let (ch_runner, device) = ch::new(&mut state.ch, [0; 6]); let state_ch = ch_runner.state_runner(); @@ -575,8 +575,7 @@ where impl<'a, PWR, SPI> Runner<'a, PWR, SPI> where PWR: OutputPin, - SPI: SpiDevice, - SPI::Bus: SpiBusRead + SpiBusWrite, + SPI: SpiBusCyw43, { async fn init(&mut self, firmware: &[u8]) { self.bus.init().await;