Skip to content

Commit

Permalink
stm32/usart: half-duplex support for buffered usart
Browse files Browse the repository at this point in the history
  • Loading branch information
trnila committed Dec 22, 2024
1 parent 7b7ac1b commit a798366
Showing 1 changed file with 98 additions and 2 deletions.
100 changes: 98 additions & 2 deletions embassy-stm32/src/usart/buffered.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ use embassy_sync::waitqueue::AtomicWaker;
#[cfg(not(any(usart_v1, usart_v2)))]
use super::DePin;
use super::{
clear_interrupt_flags, configure, rdr, reconfigure, send_break, set_baudrate, sr, tdr, Config, ConfigError, CtsPin,
Error, Info, Instance, Regs, RtsPin, RxPin, TxPin,
clear_interrupt_flags, configure, half_duplex_set_rx_tx_before_write, rdr, reconfigure, send_break, set_baudrate,
sr, tdr, Config, ConfigError, CtsPin, Duplex, Error, HalfDuplexConfig, HalfDuplexReadback, Info, Instance, Regs,
RtsPin, RxPin, TxPin,
};
use crate::gpio::{AfType, AnyPin, OutputType, Pull, SealedPin as _, Speed};
use crate::interrupt::{self, InterruptExt};
Expand Down Expand Up @@ -108,6 +109,8 @@ unsafe fn on_interrupt(r: Regs, state: &'static State) {
});
}

half_duplex_set_rx_tx_before_write(&r, state.half_duplex_readback.load(Ordering::Relaxed));

tdr(r).write_volatile(buf[0].into());
tx_reader.pop_done(1);
} else {
Expand All @@ -126,6 +129,7 @@ pub(super) struct State {
tx_buf: RingBuffer,
tx_done: AtomicBool,
tx_rx_refcount: AtomicU8,
half_duplex_readback: AtomicBool,
}

impl State {
Expand All @@ -137,6 +141,7 @@ impl State {
tx_waker: AtomicWaker::new(),
tx_done: AtomicBool::new(true),
tx_rx_refcount: AtomicU8::new(0),
half_duplex_readback: AtomicBool::new(false),
}
}
}
Expand Down Expand Up @@ -321,6 +326,84 @@ impl<'d> BufferedUart<'d> {
)
}

/// Create a single-wire half-duplex Uart transceiver on a single Tx pin.
///
/// See [`new_half_duplex_on_rx`][`Self::new_half_duplex_on_rx`] if you would prefer to use an Rx pin
/// (when it is available for your chip). There is no functional difference between these methods, as both
/// allow bidirectional communication.
///
/// The TX pin is always released when no data is transmitted. Thus, it acts as a standard
/// I/O in idle or in reception. It means that the I/O must be configured so that TX is
/// configured as alternate function open-drain with an external pull-up
/// Apart from this, the communication protocol is similar to normal USART mode. Any conflict
/// on the line must be managed by software (for instance by using a centralized arbiter).
#[doc(alias("HDSEL"))]
pub fn new_half_duplex<T: Instance>(
peri: impl Peripheral<P = T> + 'd,
tx: impl Peripheral<P = impl TxPin<T>> + 'd,
_irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
tx_buffer: &'d mut [u8],
rx_buffer: &'d mut [u8],
mut config: Config,
readback: HalfDuplexReadback,
half_duplex: HalfDuplexConfig,
) -> Result<Self, ConfigError> {
#[cfg(not(any(usart_v1, usart_v2)))]
{
config.swap_rx_tx = false;
}
config.duplex = Duplex::Half(readback);

Self::new_inner(
peri,
None,
new_pin!(tx, half_duplex.af_type()),
None,
None,
None,
tx_buffer,
rx_buffer,
config,
)
}

/// Create a single-wire half-duplex Uart transceiver on a single Rx pin.
///
/// See [`new_half_duplex`][`Self::new_half_duplex`] if you would prefer to use an Tx pin.
/// There is no functional difference between these methods, as both allow bidirectional communication.
///
/// The pin is always released when no data is transmitted. Thus, it acts as a standard
/// I/O in idle or in reception.
/// Apart from this, the communication protocol is similar to normal USART mode. Any conflict
/// on the line must be managed by software (for instance by using a centralized arbiter).
#[cfg(not(any(usart_v1, usart_v2)))]
#[doc(alias("HDSEL"))]
pub fn new_half_duplex_on_rx<T: Instance>(
peri: impl Peripheral<P = T> + 'd,
rx: impl Peripheral<P = impl RxPin<T>> + 'd,
_irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
tx_buffer: &'d mut [u8],
rx_buffer: &'d mut [u8],
mut config: Config,
readback: HalfDuplexReadback,
half_duplex: HalfDuplexConfig,
) -> Result<Self, ConfigError> {
config.swap_rx_tx = true;
config.duplex = Duplex::Half(readback);

Self::new_inner(
peri,
new_pin!(rx, half_duplex.af_type()),
None,
None,
None,
None,
tx_buffer,
rx_buffer,
config,
)
}

fn new_inner<T: Instance>(
_peri: impl Peripheral<P = T> + 'd,
rx: Option<PeripheralRef<'d, AnyPin>>,
Expand All @@ -336,6 +419,11 @@ impl<'d> BufferedUart<'d> {
let state = T::buffered_state();
let kernel_clock = T::frequency();

state.half_duplex_readback.store(
config.duplex == Duplex::Half(HalfDuplexReadback::Readback),
Ordering::Relaxed,
);

let mut this = Self {
rx: BufferedUartRx {
info,
Expand Down Expand Up @@ -381,12 +469,20 @@ impl<'d> BufferedUart<'d> {
w.set_ctse(self.tx.cts.is_some());
#[cfg(not(any(usart_v1, usart_v2)))]
w.set_dem(self.tx.de.is_some());
w.set_hdsel(config.duplex.is_half());
});
configure(info, self.rx.kernel_clock, &config, true, true)?;

info.regs.cr1().modify(|w| {
w.set_rxneie(true);
w.set_idleie(true);

if config.duplex.is_half() {
// The te and re bits will be set by write, read and flush methods.
// Receiver should be enabled by default for Half-Duplex.
w.set_te(false);
w.set_re(true);
}
});

info.interrupt.unpend();
Expand Down

0 comments on commit a798366

Please sign in to comment.