diff options
| -rw-r--r-- | embassy-stm32/src/spi/mod.rs | 66 | ||||
| -rw-r--r-- | tests/stm32/src/bin/spi.rs | 18 | ||||
| -rw-r--r-- | tests/stm32/src/bin/spi_dma.rs | 23 |
3 files changed, 72 insertions, 35 deletions
diff --git a/embassy-stm32/src/spi/mod.rs b/embassy-stm32/src/spi/mod.rs index 5d6277c33..720e80e0d 100644 --- a/embassy-stm32/src/spi/mod.rs +++ b/embassy-stm32/src/spi/mod.rs | |||
| @@ -351,17 +351,46 @@ impl<'d, M: PeriMode> Spi<'d, M> { | |||
| 351 | 351 | ||
| 352 | /// Blocking write. | 352 | /// Blocking write. |
| 353 | pub fn blocking_write<W: Word>(&mut self, words: &[W]) -> Result<(), Error> { | 353 | pub fn blocking_write<W: Word>(&mut self, words: &[W]) -> Result<(), Error> { |
| 354 | // needed in v3+ to avoid overrun causing the SPI RX state machine to get stuck...? | ||
| 355 | #[cfg(any(spi_v3, spi_v4, spi_v5))] | ||
| 356 | self.info.regs.cr1().modify(|w| w.set_spe(false)); | ||
| 354 | self.info.regs.cr1().modify(|w| w.set_spe(true)); | 357 | self.info.regs.cr1().modify(|w| w.set_spe(true)); |
| 355 | flush_rx_fifo(self.info.regs); | 358 | flush_rx_fifo(self.info.regs); |
| 356 | self.set_word_size(W::CONFIG); | 359 | self.set_word_size(W::CONFIG); |
| 357 | for word in words.iter() { | 360 | for word in words.iter() { |
| 358 | let _ = transfer_word(self.info.regs, *word)?; | 361 | // this cannot use `transfer_word` because on SPIv2 and higher, |
| 362 | // the SPI RX state machine hangs if no physical pin is connected to the SCK AF. | ||
| 363 | // This is the case when the SPI has been created with `new_(blocking_?)txonly_nosck`. | ||
| 364 | // See https://github.com/embassy-rs/embassy/issues/2902 | ||
| 365 | // This is not documented as an errata by ST, and I've been unable to find anything online... | ||
| 366 | #[cfg(not(any(spi_v1, spi_f1)))] | ||
| 367 | write_word(self.info.regs, *word)?; | ||
| 368 | |||
| 369 | // if we're doing tx only, after writing the last byte to FIFO we have to wait | ||
| 370 | // until it's actually sent. On SPIv1 you're supposed to use the BSY flag for this | ||
| 371 | // but apparently it's broken, it clears too soon. Workaround is to wait for RXNE: | ||
| 372 | // when it gets set you know the transfer is done, even if you don't care about rx. | ||
| 373 | // Luckily this doesn't affect SPIv2+. | ||
| 374 | // See http://efton.sk/STM32/gotcha/g68.html | ||
| 375 | // ST doesn't seem to document this in errata sheets (?) | ||
| 376 | #[cfg(any(spi_v1, spi_f1))] | ||
| 377 | transfer_word(self.info.regs, *word)?; | ||
| 359 | } | 378 | } |
| 379 | |||
| 380 | // wait until last word is transmitted. (except on v1, see above) | ||
| 381 | #[cfg(not(any(spi_v1, spi_f1, spi_v2)))] | ||
| 382 | while !self.info.regs.sr().read().txc() {} | ||
| 383 | #[cfg(spi_v2)] | ||
| 384 | while self.info.regs.sr().read().bsy() {} | ||
| 385 | |||
| 360 | Ok(()) | 386 | Ok(()) |
| 361 | } | 387 | } |
| 362 | 388 | ||
| 363 | /// Blocking read. | 389 | /// Blocking read. |
| 364 | pub fn blocking_read<W: Word>(&mut self, words: &mut [W]) -> Result<(), Error> { | 390 | pub fn blocking_read<W: Word>(&mut self, words: &mut [W]) -> Result<(), Error> { |
| 391 | // needed in v3+ to avoid overrun causing the SPI RX state machine to get stuck...? | ||
| 392 | #[cfg(any(spi_v3, spi_v4, spi_v5))] | ||
| 393 | self.info.regs.cr1().modify(|w| w.set_spe(false)); | ||
| 365 | self.info.regs.cr1().modify(|w| w.set_spe(true)); | 394 | self.info.regs.cr1().modify(|w| w.set_spe(true)); |
| 366 | flush_rx_fifo(self.info.regs); | 395 | flush_rx_fifo(self.info.regs); |
| 367 | self.set_word_size(W::CONFIG); | 396 | self.set_word_size(W::CONFIG); |
| @@ -375,6 +404,9 @@ impl<'d, M: PeriMode> Spi<'d, M> { | |||
| 375 | /// | 404 | /// |
| 376 | /// This writes the contents of `data` on MOSI, and puts the received data on MISO in `data`, at the same time. | 405 | /// This writes the contents of `data` on MOSI, and puts the received data on MISO in `data`, at the same time. |
| 377 | pub fn blocking_transfer_in_place<W: Word>(&mut self, words: &mut [W]) -> Result<(), Error> { | 406 | pub fn blocking_transfer_in_place<W: Word>(&mut self, words: &mut [W]) -> Result<(), Error> { |
| 407 | // needed in v3+ to avoid overrun causing the SPI RX state machine to get stuck...? | ||
| 408 | #[cfg(any(spi_v3, spi_v4, spi_v5))] | ||
| 409 | self.info.regs.cr1().modify(|w| w.set_spe(false)); | ||
| 378 | self.info.regs.cr1().modify(|w| w.set_spe(true)); | 410 | self.info.regs.cr1().modify(|w| w.set_spe(true)); |
| 379 | flush_rx_fifo(self.info.regs); | 411 | flush_rx_fifo(self.info.regs); |
| 380 | self.set_word_size(W::CONFIG); | 412 | self.set_word_size(W::CONFIG); |
| @@ -391,6 +423,9 @@ impl<'d, M: PeriMode> Spi<'d, M> { | |||
| 391 | /// The transfer runs for `max(read.len(), write.len())` bytes. If `read` is shorter extra bytes are ignored. | 423 | /// The transfer runs for `max(read.len(), write.len())` bytes. If `read` is shorter extra bytes are ignored. |
| 392 | /// If `write` is shorter it is padded with zero bytes. | 424 | /// If `write` is shorter it is padded with zero bytes. |
| 393 | pub fn blocking_transfer<W: Word>(&mut self, read: &mut [W], write: &[W]) -> Result<(), Error> { | 425 | pub fn blocking_transfer<W: Word>(&mut self, read: &mut [W], write: &[W]) -> Result<(), Error> { |
| 426 | // needed in v3+ to avoid overrun causing the SPI RX state machine to get stuck...? | ||
| 427 | #[cfg(any(spi_v3, spi_v4, spi_v5))] | ||
| 428 | self.info.regs.cr1().modify(|w| w.set_spe(false)); | ||
| 394 | self.info.regs.cr1().modify(|w| w.set_spe(true)); | 429 | self.info.regs.cr1().modify(|w| w.set_spe(true)); |
| 395 | flush_rx_fifo(self.info.regs); | 430 | flush_rx_fifo(self.info.regs); |
| 396 | self.set_word_size(W::CONFIG); | 431 | self.set_word_size(W::CONFIG); |
| @@ -465,7 +500,6 @@ impl<'d> Spi<'d, Blocking> { | |||
| 465 | /// Create a new SPI driver, in TX-only mode, without SCK pin. | 500 | /// Create a new SPI driver, in TX-only mode, without SCK pin. |
| 466 | /// | 501 | /// |
| 467 | /// This can be useful for bit-banging non-SPI protocols. | 502 | /// This can be useful for bit-banging non-SPI protocols. |
| 468 | #[cfg(any(spi_v1, spi_f1))] // no SCK pin causes it to hang on spiv2+ for unknown reasons. | ||
| 469 | pub fn new_blocking_txonly_nosck<T: Instance>( | 503 | pub fn new_blocking_txonly_nosck<T: Instance>( |
| 470 | peri: impl Peripheral<P = T> + 'd, | 504 | peri: impl Peripheral<P = T> + 'd, |
| 471 | mosi: impl Peripheral<P = impl MosiPin<T>> + 'd, | 505 | mosi: impl Peripheral<P = impl MosiPin<T>> + 'd, |
| @@ -550,7 +584,6 @@ impl<'d> Spi<'d, Async> { | |||
| 550 | /// Create a new SPI driver, in TX-only mode, without SCK pin. | 584 | /// Create a new SPI driver, in TX-only mode, without SCK pin. |
| 551 | /// | 585 | /// |
| 552 | /// This can be useful for bit-banging non-SPI protocols. | 586 | /// This can be useful for bit-banging non-SPI protocols. |
| 553 | #[cfg(any(spi_v1, spi_f1))] // no SCK pin causes it to hang on spiv2+ for unknown reasons. | ||
| 554 | pub fn new_txonly_nosck<T: Instance>( | 587 | pub fn new_txonly_nosck<T: Instance>( |
| 555 | peri: impl Peripheral<P = T> + 'd, | 588 | peri: impl Peripheral<P = T> + 'd, |
| 556 | mosi: impl Peripheral<P = impl MosiPin<T>> + 'd, | 589 | mosi: impl Peripheral<P = impl MosiPin<T>> + 'd, |
| @@ -900,8 +933,8 @@ impl RegsExt for Regs { | |||
| 900 | } | 933 | } |
| 901 | } | 934 | } |
| 902 | 935 | ||
| 903 | fn check_error_flags(sr: regs::Sr) -> Result<(), Error> { | 936 | fn check_error_flags(sr: regs::Sr, ovr: bool) -> Result<(), Error> { |
| 904 | if sr.ovr() { | 937 | if sr.ovr() && ovr { |
| 905 | return Err(Error::Overrun); | 938 | return Err(Error::Overrun); |
| 906 | } | 939 | } |
| 907 | #[cfg(not(any(spi_f1, spi_v3, spi_v4, spi_v5)))] | 940 | #[cfg(not(any(spi_f1, spi_v3, spi_v4, spi_v5)))] |
| @@ -927,11 +960,11 @@ fn check_error_flags(sr: regs::Sr) -> Result<(), Error> { | |||
| 927 | Ok(()) | 960 | Ok(()) |
| 928 | } | 961 | } |
| 929 | 962 | ||
| 930 | fn spin_until_tx_ready(regs: Regs) -> Result<(), Error> { | 963 | fn spin_until_tx_ready(regs: Regs, ovr: bool) -> Result<(), Error> { |
| 931 | loop { | 964 | loop { |
| 932 | let sr = regs.sr().read(); | 965 | let sr = regs.sr().read(); |
| 933 | 966 | ||
| 934 | check_error_flags(sr)?; | 967 | check_error_flags(sr, ovr)?; |
| 935 | 968 | ||
| 936 | #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] | 969 | #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] |
| 937 | if sr.txe() { | 970 | if sr.txe() { |
| @@ -948,7 +981,7 @@ fn spin_until_rx_ready(regs: Regs) -> Result<(), Error> { | |||
| 948 | loop { | 981 | loop { |
| 949 | let sr = regs.sr().read(); | 982 | let sr = regs.sr().read(); |
| 950 | 983 | ||
| 951 | check_error_flags(sr)?; | 984 | check_error_flags(sr, true)?; |
| 952 | 985 | ||
| 953 | #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] | 986 | #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] |
| 954 | if sr.rxne() { | 987 | if sr.rxne() { |
| @@ -1032,7 +1065,7 @@ fn finish_dma(regs: Regs) { | |||
| 1032 | } | 1065 | } |
| 1033 | 1066 | ||
| 1034 | fn transfer_word<W: Word>(regs: Regs, tx_word: W) -> Result<W, Error> { | 1067 | fn transfer_word<W: Word>(regs: Regs, tx_word: W) -> Result<W, Error> { |
| 1035 | spin_until_tx_ready(regs)?; | 1068 | spin_until_tx_ready(regs, true)?; |
| 1036 | 1069 | ||
| 1037 | unsafe { | 1070 | unsafe { |
| 1038 | ptr::write_volatile(regs.tx_ptr(), tx_word); | 1071 | ptr::write_volatile(regs.tx_ptr(), tx_word); |
| @@ -1047,6 +1080,21 @@ fn transfer_word<W: Word>(regs: Regs, tx_word: W) -> Result<W, Error> { | |||
| 1047 | Ok(rx_word) | 1080 | Ok(rx_word) |
| 1048 | } | 1081 | } |
| 1049 | 1082 | ||
| 1083 | #[allow(unused)] // unused in SPIv1 | ||
| 1084 | fn write_word<W: Word>(regs: Regs, tx_word: W) -> Result<(), Error> { | ||
| 1085 | // for write, we intentionally ignore the rx fifo, which will cause | ||
| 1086 | // overrun errors that we have to ignore. | ||
| 1087 | spin_until_tx_ready(regs, false)?; | ||
| 1088 | |||
| 1089 | unsafe { | ||
| 1090 | ptr::write_volatile(regs.tx_ptr(), tx_word); | ||
| 1091 | |||
| 1092 | #[cfg(any(spi_v3, spi_v4, spi_v5))] | ||
| 1093 | regs.cr1().modify(|reg| reg.set_cstart(true)); | ||
| 1094 | } | ||
| 1095 | Ok(()) | ||
| 1096 | } | ||
| 1097 | |||
| 1050 | // Note: It is not possible to impl these traits generically in embedded-hal 0.2 due to a conflict with | 1098 | // Note: It is not possible to impl these traits generically in embedded-hal 0.2 due to a conflict with |
| 1051 | // some marker traits. For details, see https://github.com/rust-embedded/embedded-hal/pull/289 | 1099 | // some marker traits. For details, see https://github.com/rust-embedded/embedded-hal/pull/289 |
| 1052 | macro_rules! impl_blocking { | 1100 | macro_rules! impl_blocking { |
diff --git a/tests/stm32/src/bin/spi.rs b/tests/stm32/src/bin/spi.rs index 8be3b1a7c..0ffd0f653 100644 --- a/tests/stm32/src/bin/spi.rs +++ b/tests/stm32/src/bin/spi.rs | |||
| @@ -94,19 +94,11 @@ async fn main(_spawner: Spawner) { | |||
| 94 | drop(spi); | 94 | drop(spi); |
| 95 | 95 | ||
| 96 | // Test tx-only nosck. | 96 | // Test tx-only nosck. |
| 97 | #[cfg(feature = "spi-v1")] | 97 | let mut spi = Spi::new_blocking_txonly_nosck(&mut spi_peri, &mut mosi, spi_config); |
| 98 | { | 98 | spi.blocking_write(&buf).unwrap(); |
| 99 | let mut spi = Spi::new_blocking_txonly_nosck(&mut spi_peri, &mut mosi, spi_config); | 99 | spi.blocking_write::<u8>(&[]).unwrap(); |
| 100 | spi.blocking_transfer(&mut buf, &data).unwrap(); | 100 | spi.blocking_write(&buf).unwrap(); |
| 101 | spi.blocking_transfer_in_place(&mut buf).unwrap(); | 101 | drop(spi); |
| 102 | spi.blocking_write(&buf).unwrap(); | ||
| 103 | spi.blocking_read(&mut buf).unwrap(); | ||
| 104 | spi.blocking_transfer::<u8>(&mut [], &[]).unwrap(); | ||
| 105 | spi.blocking_transfer_in_place::<u8>(&mut []).unwrap(); | ||
| 106 | spi.blocking_read::<u8>(&mut []).unwrap(); | ||
| 107 | spi.blocking_write::<u8>(&[]).unwrap(); | ||
| 108 | drop(spi); | ||
| 109 | } | ||
| 110 | 102 | ||
| 111 | info!("Test OK"); | 103 | info!("Test OK"); |
| 112 | cortex_m::asm::bkpt(); | 104 | cortex_m::asm::bkpt(); |
diff --git a/tests/stm32/src/bin/spi_dma.rs b/tests/stm32/src/bin/spi_dma.rs index a8001a111..fd26d3f71 100644 --- a/tests/stm32/src/bin/spi_dma.rs +++ b/tests/stm32/src/bin/spi_dma.rs | |||
| @@ -132,19 +132,16 @@ async fn main(_spawner: Spawner) { | |||
| 132 | drop(spi); | 132 | drop(spi); |
| 133 | 133 | ||
| 134 | // Test tx-only nosck. | 134 | // Test tx-only nosck. |
| 135 | #[cfg(feature = "spi-v1")] | 135 | let mut spi = Spi::new_txonly_nosck(&mut spi_peri, &mut mosi, &mut tx_dma, spi_config); |
| 136 | { | 136 | spi.blocking_write(&buf).unwrap(); |
| 137 | let mut spi = Spi::new_txonly_nosck(&mut spi_peri, &mut mosi, &mut tx_dma, spi_config); | 137 | spi.write(&buf).await.unwrap(); |
| 138 | spi.blocking_write(&buf).unwrap(); | 138 | spi.blocking_write(&buf).unwrap(); |
| 139 | spi.write(&buf).await.unwrap(); | 139 | spi.blocking_write(&buf).unwrap(); |
| 140 | spi.blocking_write(&buf).unwrap(); | 140 | spi.write(&buf).await.unwrap(); |
| 141 | spi.blocking_write(&buf).unwrap(); | 141 | spi.write(&buf).await.unwrap(); |
| 142 | spi.write(&buf).await.unwrap(); | 142 | spi.write::<u8>(&[]).await.unwrap(); |
| 143 | spi.write(&buf).await.unwrap(); | 143 | spi.blocking_write::<u8>(&[]).unwrap(); |
| 144 | spi.write::<u8>(&[]).await.unwrap(); | 144 | drop(spi); |
| 145 | spi.blocking_write::<u8>(&[]).unwrap(); | ||
| 146 | drop(spi); | ||
| 147 | } | ||
| 148 | 145 | ||
| 149 | info!("Test OK"); | 146 | info!("Test OK"); |
| 150 | cortex_m::asm::bkpt(); | 147 | cortex_m::asm::bkpt(); |
