diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index dcc4525d6..252ed103e 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -55,7 +55,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `adc::{AdcCalSource, Attenuation, Resolution}` now implement `Hash` and `defmt::Format` (#2840) - `rtc_cntl::{RtcFastClock, RtcSlowClock, RtcCalSel}` now implement `PartialEq`, `Eq`, `Hash` and `defmt::Format` (#2840) - Added `tsens::TemperatureSensor` peripheral for ESP32C6 and ESP32C3 (#2875) -- Added `with_rx()` and `with_tx()` methods to Uart, UartRx, and UartTx () +- Added `with_rx()` and `with_tx()` methods to Uart, UartRx, and UartTx (#2904) +- SPI: Added support for 3-wire SPI (#2919) ### Changed diff --git a/esp-hal/src/spi/master.rs b/esp-hal/src/spi/master.rs index 5840107eb..5c7837caa 100644 --- a/esp-hal/src/spi/master.rs +++ b/esp-hal/src/spi/master.rs @@ -552,8 +552,8 @@ impl<'d> Spi<'d, Blocking> { this.apply_config(&config)?; let this = this - .with_mosi(NoPin) - .with_miso(NoPin) + .with_sio0(NoPin) + .with_sio1(NoPin) .with_sck(NoPin) .with_cs(NoPin); @@ -645,9 +645,21 @@ where { /// Assign the MOSI (Master Out Slave In) pin for the SPI instance. /// + /// Enables output functionality for the pin, and connects it to the MOSI. + pub fn with_mosi(self, mosi: impl Peripheral

+ 'd) -> Self { + crate::into_mapped_ref!(mosi); + mosi.enable_output(false, private::Internal); + + self.driver().info.mosi.connect_to(&mut mosi); + + self + } + + /// Assign the SIO0 pin for the SPI instance. + /// /// Enables both input and output functionality for the pin, and connects it /// to the MOSI signal and SIO0 input signal. - pub fn with_mosi(self, mosi: impl Peripheral

+ 'd) -> Self { + pub fn with_sio0(self, mosi: impl Peripheral

+ 'd) -> Self { crate::into_mapped_ref!(mosi); mosi.enable_output(true, private::Internal); mosi.enable_input(true, private::Internal); @@ -3123,12 +3135,25 @@ impl Driver { no_mosi_miso: bool, data_mode: DataMode, ) -> Result<(), Error> { + let three_wire = cmd.mode() == DataMode::SingleThreeWire + || address.mode() == DataMode::SingleThreeWire + || data_mode == DataMode::SingleThreeWire; + + if three_wire + && ((cmd != Command::None && cmd.mode() != DataMode::SingleThreeWire) + || (address != Address::None && address.mode() != DataMode::SingleThreeWire) + || data_mode != DataMode::SingleThreeWire) + { + return Err(Error::ArgumentsInvalid); + } + self.init_spi_data_mode(cmd.mode(), address.mode(), data_mode)?; let reg_block = self.register_block(); reg_block.user().modify(|_, w| { w.usr_miso_highpart().clear_bit(); w.usr_mosi_highpart().clear_bit(); + w.sio().bit(three_wire); w.doutdin().clear_bit(); w.usr_miso().bit(!is_write && !no_mosi_miso); w.usr_mosi().bit(is_write && !no_mosi_miso); diff --git a/esp-hal/src/spi/mod.rs b/esp-hal/src/spi/mod.rs index 7ee0cf22b..50300cd91 100644 --- a/esp-hal/src/spi/mod.rs +++ b/esp-hal/src/spi/mod.rs @@ -35,6 +35,8 @@ pub enum Error { /// Error indicating that the operation is unsupported by the current /// implementation. Unsupported, + /// The given arguments are invalid. + ArgumentsInvalid, /// An unknown error occurred during SPI communication. Unknown, } @@ -98,14 +100,16 @@ pub enum BitOrder { #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[instability::unstable] pub enum DataMode { - /// `Single` Data Mode - 1 bit, 2 wires. + /// Clock, CS and one data line (SIO0) + SingleThreeWire, + /// `Single` Data Mode - 1 bit, two data lines. (SIO0, SIO1) Single, - /// `Dual` Data Mode - 2 bit, 2 wires + /// `Dual` Data Mode - 2 bits, two data lines. (SIO0, SIO1) Dual, - /// `Quad` Data Mode - 4 bit, 4 wires + /// `Quad` Data Mode - 4 bit, 4 data lines. (SIO0 .. SIO3) Quad, #[cfg(spi_octal)] - /// `Octal` Data Mode - 8 bit, 8 wires + /// `Octal` Data Mode - 8 bit, 8 data lines. (SIO0 .. SIO7) Octal, } diff --git a/hil-test/tests/qspi.rs b/hil-test/tests/qspi.rs index 79e10dfc4..612be767a 100644 --- a/hil-test/tests/qspi.rs +++ b/hil-test/tests/qspi.rs @@ -231,7 +231,7 @@ mod tests { let [pin, pin_mirror, _] = ctx.gpios; let pin_mirror = Output::new(pin_mirror, Level::High); - let spi = ctx.spi.with_mosi(pin).with_dma(ctx.dma_channel); + let spi = ctx.spi.with_sio0(pin).with_dma(ctx.dma_channel); super::execute_read(spi, pin_mirror, 0b0001_0001); } @@ -241,7 +241,7 @@ mod tests { let [pin, pin_mirror, _] = ctx.gpios; let pin_mirror = Output::new(pin_mirror, Level::High); - let spi = ctx.spi.with_miso(pin).with_dma(ctx.dma_channel); + let spi = ctx.spi.with_sio1(pin).with_dma(ctx.dma_channel); super::execute_read(spi, pin_mirror, 0b0010_0010); } @@ -271,7 +271,7 @@ mod tests { let [pin, pin_mirror, _] = ctx.gpios; let pin_mirror = Output::new(pin_mirror, Level::High); - let spi = ctx.spi.with_mosi(pin).with_dma(ctx.dma_channel); + let spi = ctx.spi.with_sio0(pin).with_dma(ctx.dma_channel); super::execute_write_read(spi, pin_mirror, 0b0001_0001); } @@ -324,7 +324,7 @@ mod tests { .channel0 .set_input_mode(EdgeMode::Hold, EdgeMode::Increment); - let spi = ctx.spi.with_mosi(mosi).with_dma(ctx.dma_channel); + let spi = ctx.spi.with_sio0(mosi).with_dma(ctx.dma_channel); super::execute_write(unit0, unit1, spi, 0b0000_0001, false); } @@ -355,7 +355,7 @@ mod tests { let spi = ctx .spi - .with_mosi(mosi) + .with_sio0(mosi) .with_sio1(gpio) .with_dma(ctx.dma_channel); @@ -388,7 +388,7 @@ mod tests { let spi = ctx .spi - .with_mosi(mosi) + .with_sio0(mosi) .with_sio2(gpio) .with_dma(ctx.dma_channel); @@ -421,7 +421,7 @@ mod tests { let spi = ctx .spi - .with_mosi(mosi) + .with_sio0(mosi) .with_sio3(gpio) .with_dma(ctx.dma_channel); diff --git a/hil-test/tests/spi_half_duplex_write.rs b/hil-test/tests/spi_half_duplex_write.rs index 6cf5f44f7..b13cb4a11 100644 --- a/hil-test/tests/spi_half_duplex_write.rs +++ b/hil-test/tests/spi_half_duplex_write.rs @@ -27,6 +27,81 @@ struct Context { pcnt_source: InputSignal, } +fn perform_spi_writes_are_correctly_by_pcnt(ctx: Context, mode: DataMode) { + const DMA_BUFFER_SIZE: usize = 4; + + let (_, _, buffer, descriptors) = dma_buffers!(0, DMA_BUFFER_SIZE); + let mut dma_tx_buf = DmaTxBuf::new(descriptors, buffer).unwrap(); + + let unit = ctx.pcnt_unit; + let mut spi = ctx.spi; + + unit.channel0.set_edge_signal(ctx.pcnt_source); + unit.channel0 + .set_input_mode(EdgeMode::Hold, EdgeMode::Increment); + + // Fill the buffer where each byte has 3 pos edges. + dma_tx_buf.fill(&[0b0110_1010; DMA_BUFFER_SIZE]); + + let transfer = spi + .half_duplex_write( + mode, + Command::None, + Address::None, + 0, + dma_tx_buf.len(), + dma_tx_buf, + ) + .map_err(|e| e.0) + .unwrap(); + (spi, dma_tx_buf) = transfer.wait(); + + assert_eq!(unit.value(), (3 * DMA_BUFFER_SIZE) as _); + + let transfer = spi + .half_duplex_write( + mode, + Command::None, + Address::None, + 0, + dma_tx_buf.len(), + dma_tx_buf, + ) + .map_err(|e| e.0) + .unwrap(); + // dropping SPI would make us see an additional edge - so let's keep SPI alive + let (_spi, _) = transfer.wait(); + + assert_eq!(unit.value(), (6 * DMA_BUFFER_SIZE) as _); +} + +fn perform_spidmabus_writes_are_correctly_by_pcnt(ctx: Context, mode: DataMode) { + const DMA_BUFFER_SIZE: usize = 4; + + let (rx, rxd, buffer, descriptors) = dma_buffers!(1, DMA_BUFFER_SIZE); + let dma_rx_buf = DmaRxBuf::new(rxd, rx).unwrap(); + let dma_tx_buf = DmaTxBuf::new(descriptors, buffer).unwrap(); + + let unit = ctx.pcnt_unit; + let mut spi = ctx.spi.with_buffers(dma_rx_buf, dma_tx_buf); + + unit.channel0.set_edge_signal(ctx.pcnt_source); + unit.channel0 + .set_input_mode(EdgeMode::Hold, EdgeMode::Increment); + + let buffer = [0b0110_1010; DMA_BUFFER_SIZE]; + // Write the buffer where each byte has 3 pos edges. + spi.half_duplex_write(mode, Command::None, Address::None, 0, &buffer) + .unwrap(); + + assert_eq!(unit.value(), (3 * DMA_BUFFER_SIZE) as _); + + spi.half_duplex_write(mode, Command::None, Address::None, 0, &buffer) + .unwrap(); + + assert_eq!(unit.value(), (6 * DMA_BUFFER_SIZE) as _); +} + #[cfg(test)] #[embedded_test::tests(default_timeout = 3)] mod tests { @@ -59,7 +134,7 @@ mod tests { ) .unwrap() .with_sck(sclk) - .with_mosi(mosi) + .with_sio0(mosi) .with_dma(dma_channel); Context { @@ -71,78 +146,21 @@ mod tests { #[test] fn test_spi_writes_are_correctly_by_pcnt(ctx: Context) { - const DMA_BUFFER_SIZE: usize = 4; - - let (_, _, buffer, descriptors) = dma_buffers!(0, DMA_BUFFER_SIZE); - let mut dma_tx_buf = DmaTxBuf::new(descriptors, buffer).unwrap(); - - let unit = ctx.pcnt_unit; - let mut spi = ctx.spi; - - unit.channel0.set_edge_signal(ctx.pcnt_source); - unit.channel0 - .set_input_mode(EdgeMode::Hold, EdgeMode::Increment); - - // Fill the buffer where each byte has 3 pos edges. - dma_tx_buf.fill(&[0b0110_1010; DMA_BUFFER_SIZE]); - - let transfer = spi - .half_duplex_write( - DataMode::Single, - Command::None, - Address::None, - 0, - dma_tx_buf.len(), - dma_tx_buf, - ) - .map_err(|e| e.0) - .unwrap(); - (spi, dma_tx_buf) = transfer.wait(); - - assert_eq!(unit.value(), (3 * DMA_BUFFER_SIZE) as _); - - let transfer = spi - .half_duplex_write( - DataMode::Single, - Command::None, - Address::None, - 0, - dma_tx_buf.len(), - dma_tx_buf, - ) - .map_err(|e| e.0) - .unwrap(); - // dropping SPI would make us see an additional edge - so let's keep SPI alive - let (_spi, _) = transfer.wait(); - - assert_eq!(unit.value(), (6 * DMA_BUFFER_SIZE) as _); + super::perform_spi_writes_are_correctly_by_pcnt(ctx, DataMode::Single); } #[test] fn test_spidmabus_writes_are_correctly_by_pcnt(ctx: Context) { - const DMA_BUFFER_SIZE: usize = 4; + super::perform_spidmabus_writes_are_correctly_by_pcnt(ctx, DataMode::Single); + } - let (rx, rxd, buffer, descriptors) = dma_buffers!(1, DMA_BUFFER_SIZE); - let dma_rx_buf = DmaRxBuf::new(rxd, rx).unwrap(); - let dma_tx_buf = DmaTxBuf::new(descriptors, buffer).unwrap(); + #[test] + fn test_spi_writes_are_correctly_by_pcnt_tree_wire(ctx: Context) { + super::perform_spi_writes_are_correctly_by_pcnt(ctx, DataMode::SingleThreeWire); + } - let unit = ctx.pcnt_unit; - let mut spi = ctx.spi.with_buffers(dma_rx_buf, dma_tx_buf); - - unit.channel0.set_edge_signal(ctx.pcnt_source); - unit.channel0 - .set_input_mode(EdgeMode::Hold, EdgeMode::Increment); - - let buffer = [0b0110_1010; DMA_BUFFER_SIZE]; - // Write the buffer where each byte has 3 pos edges. - spi.half_duplex_write(DataMode::Single, Command::None, Address::None, 0, &buffer) - .unwrap(); - - assert_eq!(unit.value(), (3 * DMA_BUFFER_SIZE) as _); - - spi.half_duplex_write(DataMode::Single, Command::None, Address::None, 0, &buffer) - .unwrap(); - - assert_eq!(unit.value(), (6 * DMA_BUFFER_SIZE) as _); + #[test] + fn test_spidmabus_writes_are_correctly_by_pcnt_tree_wire(ctx: Context) { + super::perform_spidmabus_writes_are_correctly_by_pcnt(ctx, DataMode::SingleThreeWire); } } diff --git a/hil-test/tests/spi_half_duplex_write_psram.rs b/hil-test/tests/spi_half_duplex_write_psram.rs index 2071fa5d5..bb480f05f 100644 --- a/hil-test/tests/spi_half_duplex_write_psram.rs +++ b/hil-test/tests/spi_half_duplex_write_psram.rs @@ -71,7 +71,7 @@ mod tests { ) .unwrap() .with_sck(sclk) - .with_mosi(mosi) + .with_sio0(mosi) .with_dma(dma_channel); Context { diff --git a/qa-test/src/bin/spi_halfduplex_read_manufacturer_id.rs b/qa-test/src/bin/spi_halfduplex_read_manufacturer_id.rs index af7fa06ee..ff4b0ca8c 100644 --- a/qa-test/src/bin/spi_halfduplex_read_manufacturer_id.rs +++ b/qa-test/src/bin/spi_halfduplex_read_manufacturer_id.rs @@ -71,8 +71,8 @@ fn main() -> ! { ) .unwrap() .with_sck(sclk) - .with_mosi(mosi) - .with_miso(miso) + .with_sio0(mosi) + .with_sio1(miso) .with_sio2(sio2) .with_sio3(sio3) .with_cs(cs);