Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

stm32/usart: configurable readback for half-duplex to support 1-wire + ds18b20 example #3679

Merged
merged 4 commits into from
Dec 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
77 changes: 55 additions & 22 deletions embassy-stm32/src/usart/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,33 @@ pub enum StopBits {
STOP1P5,
}

#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
/// Enables or disables receiver so written data are read back in half-duplex mode
pub enum HalfDuplexReadback {
/// Disables receiver so written data are not read back
NoReadback,
/// Enables receiver so written data are read back
Readback,
}

#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
/// Duplex mode
pub enum Duplex {
/// Full duplex
Full,
/// Half duplex with possibility to read back written data
Half(HalfDuplexReadback),
}

impl Duplex {
/// Returns true if half-duplex
fn is_half(&self) -> bool {
matches!(self, Duplex::Half(_))
}
}

#[non_exhaustive]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
Expand Down Expand Up @@ -181,7 +208,7 @@ pub struct Config {
pub rx_pull: Pull,

// private: set by new_half_duplex, not by the user.
half_duplex: bool,
duplex: Duplex,
}

impl Config {
Expand Down Expand Up @@ -220,7 +247,7 @@ impl Default for Config {
#[cfg(any(usart_v3, usart_v4))]
invert_rx: false,
rx_pull: Pull::None,
half_duplex: false,
duplex: Duplex::Full,
}
}
}
Expand Down Expand Up @@ -308,6 +335,7 @@ pub struct UartTx<'d, M: Mode> {
cts: Option<PeripheralRef<'d, AnyPin>>,
de: Option<PeripheralRef<'d, AnyPin>>,
tx_dma: Option<ChannelAndRequest<'d>>,
duplex: Duplex,
_phantom: PhantomData<M>,
}

Expand Down Expand Up @@ -409,13 +437,7 @@ impl<'d> UartTx<'d, Async> {
pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> {
let r = self.info.regs;

// Enable Transmitter and disable Receiver for Half-Duplex mode
let mut cr1 = r.cr1().read();
if r.cr3().read().hdsel() && !cr1.te() {
cr1.set_te(true);
cr1.set_re(false);
r.cr1().write_value(cr1);
}
half_duplex_set_rx_tx_before_write(&r, self.duplex == Duplex::Half(HalfDuplexReadback::Readback));

let ch = self.tx_dma.as_mut().unwrap();
r.cr3().modify(|reg| {
Expand Down Expand Up @@ -485,6 +507,7 @@ impl<'d, M: Mode> UartTx<'d, M> {
cts,
de: None,
tx_dma,
duplex: config.duplex,
_phantom: PhantomData,
};
this.enable_and_configure(&config)?;
Expand Down Expand Up @@ -515,13 +538,7 @@ impl<'d, M: Mode> UartTx<'d, M> {
pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> {
let r = self.info.regs;

// Enable Transmitter and disable Receiver for Half-Duplex mode
let mut cr1 = r.cr1().read();
if r.cr3().read().hdsel() && !cr1.te() {
cr1.set_te(true);
cr1.set_re(false);
r.cr1().write_value(cr1);
}
half_duplex_set_rx_tx_before_write(&r, self.duplex == Duplex::Half(HalfDuplexReadback::Readback));

for &b in buffer {
while !sr(r).read().txe() {}
Expand Down Expand Up @@ -600,6 +617,17 @@ pub fn send_break(regs: &Regs) {
regs.rqr().write(|w| w.set_sbkrq(true));
}

/// Enable Transmitter and disable Receiver for Half-Duplex mode
/// In case of readback, keep Receiver enabled
fn half_duplex_set_rx_tx_before_write(r: &Regs, enable_readback: bool) {
let mut cr1 = r.cr1().read();
if r.cr3().read().hdsel() && !cr1.te() {
cr1.set_te(true);
cr1.set_re(enable_readback);
r.cr1().write_value(cr1);
}
}

impl<'d> UartRx<'d, Async> {
/// Create a new rx-only UART with no hardware flow control.
///
Expand Down Expand Up @@ -1149,13 +1177,14 @@ impl<'d> Uart<'d, Async> {
tx_dma: impl Peripheral<P = impl TxDma<T>> + 'd,
rx_dma: impl Peripheral<P = impl RxDma<T>> + 'd,
mut config: Config,
readback: HalfDuplexReadback,
half_duplex: HalfDuplexConfig,
) -> Result<Self, ConfigError> {
#[cfg(not(any(usart_v1, usart_v2)))]
{
config.swap_rx_tx = false;
}
config.half_duplex = true;
config.duplex = Duplex::Half(readback);

Self::new_inner(
peri,
Expand Down Expand Up @@ -1188,10 +1217,11 @@ impl<'d> Uart<'d, Async> {
tx_dma: impl Peripheral<P = impl TxDma<T>> + 'd,
rx_dma: impl Peripheral<P = impl RxDma<T>> + 'd,
mut config: Config,
readback: HalfDuplexReadback,
half_duplex: HalfDuplexConfig,
) -> Result<Self, ConfigError> {
config.swap_rx_tx = true;
config.half_duplex = true;
config.duplex = Duplex::Half(readback);

Self::new_inner(
peri,
Expand Down Expand Up @@ -1307,13 +1337,14 @@ impl<'d> Uart<'d, Blocking> {
peri: impl Peripheral<P = T> + 'd,
tx: impl Peripheral<P = impl TxPin<T>> + 'd,
mut config: Config,
readback: HalfDuplexReadback,
half_duplex: HalfDuplexConfig,
) -> Result<Self, ConfigError> {
#[cfg(not(any(usart_v1, usart_v2)))]
{
config.swap_rx_tx = false;
}
config.half_duplex = true;
config.duplex = Duplex::Half(readback);

Self::new_inner(
peri,
Expand Down Expand Up @@ -1343,10 +1374,11 @@ impl<'d> Uart<'d, Blocking> {
peri: impl Peripheral<P = T> + 'd,
rx: impl Peripheral<P = impl RxPin<T>> + 'd,
mut config: Config,
readback: HalfDuplexReadback,
half_duplex: HalfDuplexConfig,
) -> Result<Self, ConfigError> {
config.swap_rx_tx = true;
config.half_duplex = true;
config.duplex = Duplex::Half(readback);

Self::new_inner(
peri,
Expand Down Expand Up @@ -1388,6 +1420,7 @@ impl<'d, M: Mode> Uart<'d, M> {
cts,
de,
tx_dma,
duplex: config.duplex,
},
rx: UartRx {
_phantom: PhantomData,
Expand Down Expand Up @@ -1667,14 +1700,14 @@ fn configure(
r.cr3().modify(|w| {
#[cfg(not(usart_v1))]
w.set_onebit(config.assume_noise_free);
w.set_hdsel(config.half_duplex);
w.set_hdsel(config.duplex.is_half());
});

r.cr1().write(|w| {
// enable uart
w.set_ue(true);

if config.half_duplex {
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);
Expand Down
Loading
Loading