Fix Uart::flush_async (#3186)

* Fix Uart::flush_async

* Apply single-byte workaround
This commit is contained in:
Dániel Buga 2025-02-28 10:50:38 +01:00 committed by GitHub
parent 1050ce5bb4
commit 502d41f313
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 54 additions and 5 deletions

View File

@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
- Full-duplex SPI works when mixed with half-duplex SPI (#3176) - Full-duplex SPI works when mixed with half-duplex SPI (#3176)
- `Uart::flush_async` should no longer return prematurely (#3186)
### Removed ### Removed

View File

@ -632,12 +632,18 @@ where
#[instability::unstable] #[instability::unstable]
pub fn flush(&mut self) -> Result<(), TxError> { pub fn flush(&mut self) -> Result<(), TxError> {
while self.tx_fifo_count() > 0 {} while self.tx_fifo_count() > 0 {}
// The FSM is in the Idle state for a short while after the last byte is moved self.flush_last_byte();
// out of the FIFO. It is unclear how long this takes, but 10us seems to be a Ok(())
// good enough duration to wait, for both fast and slow baud rates. }
fn flush_last_byte(&mut self) {
// This function handles an edge case that happens when the TX FIFO count
// changes to 0. The FSM is in the Idle state for a short while after
// the last byte is moved out of the FIFO. It is unclear how long this
// takes, but 10us seems to be a good enough duration to wait, for both
// fast and slow baud rates.
crate::rom::ets_delay_us(10); crate::rom::ets_delay_us(10);
while !self.is_tx_idle() {} while !self.is_tx_idle() {}
Ok(())
} }
/// Checks if the TX line is idle for this UART instance. /// Checks if the TX line is idle for this UART instance.
@ -1935,10 +1941,15 @@ impl UartTx<'_, Async> {
/// ///
/// This function is cancellation safe. /// This function is cancellation safe.
pub async fn flush_async(&mut self) -> Result<(), TxError> { pub async fn flush_async(&mut self) -> Result<(), TxError> {
if self.tx_fifo_count() > 0 { // Nothing is guaranteed to clear the Done status, so let's loop here in case Tx
// was Done before the last write operation that pushed data into the
// FIFO.
while self.tx_fifo_count() > 0 {
UartTxFuture::new(self.uart.reborrow(), TxEvent::Done).await; UartTxFuture::new(self.uart.reborrow(), TxEvent::Done).await;
} }
self.flush_last_byte();
Ok(()) Ok(())
} }
} }

View File

@ -75,6 +75,43 @@ mod tests {
} }
} }
#[test]
async fn flush_async_does_not_return_prematurely(mut ctx: Context) {
// Force TX_DONE to be set
ctx.tx.write_async(&[0u8; 10]).await.unwrap();
let mut read = [0u8; 10];
ctx.rx.read_exact_async(&mut read).await.unwrap();
// The flush should not return until the data is actually sent, regardless of
// previous TX_DONE status.
for _ in 0..10 {
ctx.tx.write_async(&[1u8; 10]).await.unwrap();
ctx.tx.flush_async().await.unwrap();
let read_count = ctx.rx.read_buffered(&mut read).unwrap();
assert_eq!(read_count, 10);
assert_eq!(&read, &[1u8; 10]);
}
}
#[test]
async fn flush_async_does_not_return_prematurely_even_for_short_writes(mut ctx: Context) {
let mut read = [0u8; 2];
// The flush should not return until the data is actually sent, regardless of
// previous TX_DONE status.
for i in 0..10 {
ctx.tx.write_async(&[i as u8]).await.unwrap();
ctx.tx.flush_async().await.unwrap();
let read_count = ctx.rx.read_buffered(&mut read).unwrap();
assert_eq!(read_count, 1);
assert_eq!(read[0], i as u8);
}
}
#[test] #[test]
async fn rx_overflow_is_detected(mut ctx: Context) { async fn rx_overflow_is_detected(mut ctx: Context) {
let mut to_send: &[u8] = &[0; 250]; let mut to_send: &[u8] = &[0; 250];