stm32/usart: half-duplex support for buffered usart
This commit is contained in:
parent
7b7ac1bd3e
commit
a7983668da
@ -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};
|
||||
@ -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 {
|
||||
@ -126,6 +129,7 @@ pub(super) struct State {
|
||||
tx_buf: RingBuffer,
|
||||
tx_done: AtomicBool,
|
||||
tx_rx_refcount: AtomicU8,
|
||||
half_duplex_readback: AtomicBool,
|
||||
}
|
||||
|
||||
impl State {
|
||||
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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>>,
|
||||
@ -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,
|
||||
@ -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();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user