diff options
| author | Daniel Trnka <[email protected]> | 2024-12-21 13:29:00 +0100 |
|---|---|---|
| committer | Daniel Trnka <[email protected]> | 2024-12-22 19:55:16 +0100 |
| commit | a7983668da2947f7846f0abc4736708539aedc42 (patch) | |
| tree | 073961668d62edf6006b6b253d9c88d38d5e4745 | |
| parent | 7b7ac1bd3ea2eb4897b8fcab706da968188bb700 (diff) | |
stm32/usart: half-duplex support for buffered usart
| -rw-r--r-- | embassy-stm32/src/usart/buffered.rs | 100 |
1 files changed, 98 insertions, 2 deletions
diff --git a/embassy-stm32/src/usart/buffered.rs b/embassy-stm32/src/usart/buffered.rs index 814be2858..7bbe01a00 100644 --- a/embassy-stm32/src/usart/buffered.rs +++ b/embassy-stm32/src/usart/buffered.rs | |||
| @@ -12,8 +12,9 @@ use embassy_sync::waitqueue::AtomicWaker; | |||
| 12 | #[cfg(not(any(usart_v1, usart_v2)))] | 12 | #[cfg(not(any(usart_v1, usart_v2)))] |
| 13 | use super::DePin; | 13 | use super::DePin; |
| 14 | use super::{ | 14 | use super::{ |
| 15 | clear_interrupt_flags, configure, rdr, reconfigure, send_break, set_baudrate, sr, tdr, Config, ConfigError, CtsPin, | 15 | clear_interrupt_flags, configure, half_duplex_set_rx_tx_before_write, rdr, reconfigure, send_break, set_baudrate, |
| 16 | Error, Info, Instance, Regs, RtsPin, RxPin, TxPin, | 16 | sr, tdr, Config, ConfigError, CtsPin, Duplex, Error, HalfDuplexConfig, HalfDuplexReadback, Info, Instance, Regs, |
| 17 | RtsPin, RxPin, TxPin, | ||
| 17 | }; | 18 | }; |
| 18 | use crate::gpio::{AfType, AnyPin, OutputType, Pull, SealedPin as _, Speed}; | 19 | use crate::gpio::{AfType, AnyPin, OutputType, Pull, SealedPin as _, Speed}; |
| 19 | use crate::interrupt::{self, InterruptExt}; | 20 | use crate::interrupt::{self, InterruptExt}; |
| @@ -108,6 +109,8 @@ unsafe fn on_interrupt(r: Regs, state: &'static State) { | |||
| 108 | }); | 109 | }); |
| 109 | } | 110 | } |
| 110 | 111 | ||
| 112 | half_duplex_set_rx_tx_before_write(&r, state.half_duplex_readback.load(Ordering::Relaxed)); | ||
| 113 | |||
| 111 | tdr(r).write_volatile(buf[0].into()); | 114 | tdr(r).write_volatile(buf[0].into()); |
| 112 | tx_reader.pop_done(1); | 115 | tx_reader.pop_done(1); |
| 113 | } else { | 116 | } else { |
| @@ -126,6 +129,7 @@ pub(super) struct State { | |||
| 126 | tx_buf: RingBuffer, | 129 | tx_buf: RingBuffer, |
| 127 | tx_done: AtomicBool, | 130 | tx_done: AtomicBool, |
| 128 | tx_rx_refcount: AtomicU8, | 131 | tx_rx_refcount: AtomicU8, |
| 132 | half_duplex_readback: AtomicBool, | ||
| 129 | } | 133 | } |
| 130 | 134 | ||
| 131 | impl State { | 135 | impl State { |
| @@ -137,6 +141,7 @@ impl State { | |||
| 137 | tx_waker: AtomicWaker::new(), | 141 | tx_waker: AtomicWaker::new(), |
| 138 | tx_done: AtomicBool::new(true), | 142 | tx_done: AtomicBool::new(true), |
| 139 | tx_rx_refcount: AtomicU8::new(0), | 143 | tx_rx_refcount: AtomicU8::new(0), |
| 144 | half_duplex_readback: AtomicBool::new(false), | ||
| 140 | } | 145 | } |
| 141 | } | 146 | } |
| 142 | } | 147 | } |
| @@ -321,6 +326,84 @@ impl<'d> BufferedUart<'d> { | |||
| 321 | ) | 326 | ) |
| 322 | } | 327 | } |
| 323 | 328 | ||
| 329 | /// Create a single-wire half-duplex Uart transceiver on a single Tx pin. | ||
| 330 | /// | ||
| 331 | /// See [`new_half_duplex_on_rx`][`Self::new_half_duplex_on_rx`] if you would prefer to use an Rx pin | ||
| 332 | /// (when it is available for your chip). There is no functional difference between these methods, as both | ||
| 333 | /// allow bidirectional communication. | ||
| 334 | /// | ||
| 335 | /// The TX pin is always released when no data is transmitted. Thus, it acts as a standard | ||
| 336 | /// I/O in idle or in reception. It means that the I/O must be configured so that TX is | ||
| 337 | /// configured as alternate function open-drain with an external pull-up | ||
| 338 | /// Apart from this, the communication protocol is similar to normal USART mode. Any conflict | ||
| 339 | /// on the line must be managed by software (for instance by using a centralized arbiter). | ||
| 340 | #[doc(alias("HDSEL"))] | ||
| 341 | pub fn new_half_duplex<T: Instance>( | ||
| 342 | peri: impl Peripheral<P = T> + 'd, | ||
| 343 | tx: impl Peripheral<P = impl TxPin<T>> + 'd, | ||
| 344 | _irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd, | ||
| 345 | tx_buffer: &'d mut [u8], | ||
| 346 | rx_buffer: &'d mut [u8], | ||
| 347 | mut config: Config, | ||
| 348 | readback: HalfDuplexReadback, | ||
| 349 | half_duplex: HalfDuplexConfig, | ||
| 350 | ) -> Result<Self, ConfigError> { | ||
| 351 | #[cfg(not(any(usart_v1, usart_v2)))] | ||
| 352 | { | ||
| 353 | config.swap_rx_tx = false; | ||
| 354 | } | ||
| 355 | config.duplex = Duplex::Half(readback); | ||
| 356 | |||
| 357 | Self::new_inner( | ||
| 358 | peri, | ||
| 359 | None, | ||
| 360 | new_pin!(tx, half_duplex.af_type()), | ||
| 361 | None, | ||
| 362 | None, | ||
| 363 | None, | ||
| 364 | tx_buffer, | ||
| 365 | rx_buffer, | ||
| 366 | config, | ||
| 367 | ) | ||
| 368 | } | ||
| 369 | |||
| 370 | /// Create a single-wire half-duplex Uart transceiver on a single Rx pin. | ||
| 371 | /// | ||
| 372 | /// See [`new_half_duplex`][`Self::new_half_duplex`] if you would prefer to use an Tx pin. | ||
| 373 | /// There is no functional difference between these methods, as both allow bidirectional communication. | ||
| 374 | /// | ||
| 375 | /// The pin is always released when no data is transmitted. Thus, it acts as a standard | ||
| 376 | /// I/O in idle or in reception. | ||
| 377 | /// Apart from this, the communication protocol is similar to normal USART mode. Any conflict | ||
| 378 | /// on the line must be managed by software (for instance by using a centralized arbiter). | ||
| 379 | #[cfg(not(any(usart_v1, usart_v2)))] | ||
| 380 | #[doc(alias("HDSEL"))] | ||
| 381 | pub fn new_half_duplex_on_rx<T: Instance>( | ||
| 382 | peri: impl Peripheral<P = T> + 'd, | ||
| 383 | rx: impl Peripheral<P = impl RxPin<T>> + 'd, | ||
| 384 | _irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd, | ||
| 385 | tx_buffer: &'d mut [u8], | ||
| 386 | rx_buffer: &'d mut [u8], | ||
| 387 | mut config: Config, | ||
| 388 | readback: HalfDuplexReadback, | ||
| 389 | half_duplex: HalfDuplexConfig, | ||
| 390 | ) -> Result<Self, ConfigError> { | ||
| 391 | config.swap_rx_tx = true; | ||
| 392 | config.duplex = Duplex::Half(readback); | ||
| 393 | |||
| 394 | Self::new_inner( | ||
| 395 | peri, | ||
| 396 | new_pin!(rx, half_duplex.af_type()), | ||
| 397 | None, | ||
| 398 | None, | ||
| 399 | None, | ||
| 400 | None, | ||
| 401 | tx_buffer, | ||
| 402 | rx_buffer, | ||
| 403 | config, | ||
| 404 | ) | ||
| 405 | } | ||
| 406 | |||
| 324 | fn new_inner<T: Instance>( | 407 | fn new_inner<T: Instance>( |
| 325 | _peri: impl Peripheral<P = T> + 'd, | 408 | _peri: impl Peripheral<P = T> + 'd, |
| 326 | rx: Option<PeripheralRef<'d, AnyPin>>, | 409 | rx: Option<PeripheralRef<'d, AnyPin>>, |
| @@ -336,6 +419,11 @@ impl<'d> BufferedUart<'d> { | |||
| 336 | let state = T::buffered_state(); | 419 | let state = T::buffered_state(); |
| 337 | let kernel_clock = T::frequency(); | 420 | let kernel_clock = T::frequency(); |
| 338 | 421 | ||
| 422 | state.half_duplex_readback.store( | ||
| 423 | config.duplex == Duplex::Half(HalfDuplexReadback::Readback), | ||
| 424 | Ordering::Relaxed, | ||
| 425 | ); | ||
| 426 | |||
| 339 | let mut this = Self { | 427 | let mut this = Self { |
| 340 | rx: BufferedUartRx { | 428 | rx: BufferedUartRx { |
| 341 | info, | 429 | info, |
| @@ -381,12 +469,20 @@ impl<'d> BufferedUart<'d> { | |||
| 381 | w.set_ctse(self.tx.cts.is_some()); | 469 | w.set_ctse(self.tx.cts.is_some()); |
| 382 | #[cfg(not(any(usart_v1, usart_v2)))] | 470 | #[cfg(not(any(usart_v1, usart_v2)))] |
| 383 | w.set_dem(self.tx.de.is_some()); | 471 | w.set_dem(self.tx.de.is_some()); |
| 472 | w.set_hdsel(config.duplex.is_half()); | ||
| 384 | }); | 473 | }); |
| 385 | configure(info, self.rx.kernel_clock, &config, true, true)?; | 474 | configure(info, self.rx.kernel_clock, &config, true, true)?; |
| 386 | 475 | ||
| 387 | info.regs.cr1().modify(|w| { | 476 | info.regs.cr1().modify(|w| { |
| 388 | w.set_rxneie(true); | 477 | w.set_rxneie(true); |
| 389 | w.set_idleie(true); | 478 | w.set_idleie(true); |
| 479 | |||
| 480 | if config.duplex.is_half() { | ||
| 481 | // The te and re bits will be set by write, read and flush methods. | ||
| 482 | // Receiver should be enabled by default for Half-Duplex. | ||
| 483 | w.set_te(false); | ||
| 484 | w.set_re(true); | ||
| 485 | } | ||
| 390 | }); | 486 | }); |
| 391 | 487 | ||
| 392 | info.interrupt.unpend(); | 488 | info.interrupt.unpend(); |
