embassy-rs / embassy

Modern embedded framework, using Rust and async.
https://embassy.dev
Apache License 2.0
5.15k stars 713 forks source link

stm32/usart: rx dma ringbuf is flaky #1427

Open Dirbaio opened 1 year ago

Dirbaio commented 1 year ago

steps to reproduce:

sample output: https://gist.github.com/Dirbaio/49fae6ef02747bcdbd3ad2df5e5f6671

repro on f4:

rmja commented 1 year ago

I cannot reproduce this on f4.

Here is my local diff relative to current master:

Patch ``` diff --git a/tests/stm32/build.rs b/tests/stm32/build.rs index 7ae31177..d3f1f39a 100644 --- a/tests/stm32/build.rs +++ b/tests/stm32/build.rs @@ -9,7 +9,11 @@ fn main() -> Result<(), Box> { println!("cargo:rustc-link-arg-bins=--nmagic"); // too little RAM to run from RAM. - if cfg!(any(feature = "stm32f103c8", feature = "stm32c031c6")) { + if cfg!(any( + feature = "stm32f429zi", + feature = "stm32f103c8", + feature = "stm32c031c6" + )) { println!("cargo:rustc-link-arg-bins=-Tlink.x"); println!("cargo:rerun-if-changed=link.x"); } else { diff --git a/tests/stm32/src/bin/usart_rx_ringbuffered.rs b/tests/stm32/src/bin/usart_rx_ringbuffered.rs index 9d75dbe5..066c0937 100644 --- a/tests/stm32/src/bin/usart_rx_ringbuffered.rs +++ b/tests/stm32/src/bin/usart_rx_ringbuffered.rs @@ -10,7 +10,7 @@ use defmt::{assert_eq, panic}; use embassy_executor::Spawner; use embassy_stm32::interrupt; use embassy_stm32::usart::{Config, DataBits, Parity, RingBufferedUartRx, StopBits, Uart, UartTx}; -use embassy_time::{Duration, Timer}; +use embassy_time::{Duration, Timer, Instant}; use example_common::*; use rand_chacha::ChaCha8Rng; use rand_core::{RngCore, SeedableRng}; @@ -35,9 +35,9 @@ mod board { } #[cfg(feature = "stm32f429zi")] mod board { - pub type Uart = embassy_stm32::peripherals::USART6; - pub type TxDma = embassy_stm32::peripherals::DMA2_CH6; - pub type RxDma = embassy_stm32::peripherals::DMA2_CH1; + pub type Uart = embassy_stm32::peripherals::USART2; + pub type TxDma = embassy_stm32::peripherals::DMA1_CH6; + pub type RxDma = embassy_stm32::peripherals::DMA1_CH5; } #[cfg(feature = "stm32wb55rg")] mod board { @@ -64,7 +64,7 @@ mod board { pub type RxDma = embassy_stm32::peripherals::DMA1_CH2; } -const DMA_BUF_SIZE: usize = 256; +const DMA_BUF_SIZE: usize = 128; #[embassy_executor::main] async fn main(spawner: Spawner) { @@ -90,12 +90,12 @@ async fn main(spawner: Spawner) { (p.PC4, p.PC5, p.USART1, interrupt::take!(USART1), p.DMA1_CH1, p.DMA1_CH2); #[cfg(feature = "stm32f429zi")] let (tx, rx, usart, irq, tx_dma, rx_dma) = ( - p.PG14, - p.PG9, - p.USART6, - interrupt::take!(USART6), - p.DMA2_CH6, - p.DMA2_CH1, + p.PA2, + p.PA3, + p.USART2, + interrupt::take!(USART2), + p.DMA1_CH6, + p.DMA1_CH5, ); #[cfg(feature = "stm32wb55rg")] let (tx, rx, usart, irq, tx_dma, rx_dma) = ( @@ -127,11 +127,17 @@ async fn main(spawner: Spawner) { let mut config = Config::default(); // this is the fastest we can go without tuning RCC // some chips have default pclk=8mhz, and uart can run at max pclk/16 - config.baudrate = 500_000; + config.baudrate = 1_000_000; config.data_bits = DataBits::DataBits8; config.stop_bits = StopBits::STOP1; config.parity = Parity::ParityNone; + // Set same priorities as the usart6/dma2 + let mut nvic: cortex_m::peripheral::NVIC = unsafe { core::mem::transmute(()) }; + unsafe { nvic.set_priority(embassy_stm32::pac::Interrupt::USART2, 78 << 4) }; + unsafe { nvic.set_priority(embassy_stm32::pac::Interrupt::DMA1_STREAM6, 76 << 4) }; + unsafe { nvic.set_priority(embassy_stm32::pac::Interrupt::DMA1_STREAM5, 64 << 4) }; + let usart = Uart::new(usart, rx, tx, irq, tx_dma, rx_dma, config); let (tx, rx) = usart.split(); static mut DMA_BUF: [u8; DMA_BUF_SIZE] = [0; DMA_BUF_SIZE]; @@ -154,7 +160,7 @@ async fn transmit_task(mut tx: UartTx<'static, board::Uart, board::TxDma>) { let mut i: u8 = 0; loop { - let mut buf = [0; 256]; + let mut buf = [0; 32]; let len = 1 + (rng.next_u32() as usize % buf.len()); for b in &mut buf[..len] { *b = i; @@ -162,7 +168,7 @@ async fn transmit_task(mut tx: UartTx<'static, board::Uart, board::TxDma>) { } tx.write(&buf[..len]).await.unwrap(); - Timer::after(Duration::from_micros((rng.next_u32() % 1000) as _)).await; + Timer::after(Duration::from_micros((rng.next_u32() % 500) as _)).await; } } @@ -174,8 +180,9 @@ async fn receive_task(mut rx: RingBufferedUartRx<'static, board::Uart, board::Rx let mut i = 0; let mut expected = 0; + let mut next_print = Instant::now(); loop { - let mut buf = [0; 256]; + let mut buf = [0; 32]; let max_len = 1 + (rng.next_u32() as usize % buf.len()); let received = match rx.read(&mut buf[..max_len]).await { Ok(r) => r, @@ -190,14 +197,20 @@ async fn receive_task(mut rx: RingBufferedUartRx<'static, board::Uart, board::Rx } if received < max_len { - Timer::after(Duration::from_micros((rng.next_u32() % 1000) as _)).await; + Timer::after(Duration::from_micros((rng.next_u32() % 500) as _)).await; } i += received; - if i > 100000 { - info!("Test OK!"); - cortex_m::asm::bkpt(); + let now = Instant::now(); + if now > next_print { + info!("Read {}", i); + next_print = now + Duration::from_secs(1); } + + // if i > 100000 { + // info!("Test OK!"); + // cortex_m::asm::bkpt(); + // } } } diff --git a/tests/utils/src/bin/saturate_serial.rs b/tests/utils/src/bin/saturate_serial.rs index 18ca12fb..9996d2cf 100644 --- a/tests/utils/src/bin/saturate_serial.rs +++ b/tests/utils/src/bin/saturate_serial.rs @@ -9,7 +9,7 @@ pub fn main() { if let Some(port_name) = env::args().nth(1) { let idles = env::args().position(|x| x == "--idles").is_some(); - println!("Saturating port {:?} with 115200 8N1", port_name); + println!("Saturating port {:?} with 1000000 8N1", port_name); println!("Idles: {}", idles); println!("Process ID: {}", process::id()); let mut port = serial::open(&port_name).unwrap(); @@ -26,7 +26,7 @@ pub fn main() { fn saturate(port: &mut T, idles: bool) -> io::Result<()> { port.reconfigure(&|settings| { - settings.set_baud_rate(serial::Baud115200)?; + settings.set_baud_rate(serial::BaudOther(1_000_000))?; settings.set_char_size(serial::Bits8); settings.set_parity(serial::ParityNone); settings.set_stop_bits(serial::Stop1); @@ -41,7 +41,7 @@ fn saturate(port: &mut T, idles: bool) -> io::Result<()> { port.write_all(&buf)?; if idles { - let micros = (random::() % 1000) as u64; + let micros = (random::() % 500) as u64; println!("Sleeping {}us", micros); port.flush().unwrap(); thread::sleep(Duration::from_micros(micros)); ```

Here are the tests that I have done. Note that I use usart2 instead of usart6. I have adjusted the interrupt priorities accordingly. The priorities does not seem to make any difference.

1) Loopback: Simply short the rx and tx pin. Build embassy-stm32-tests with cargo build --bin usart_rx_ringbuffered --release --features=stm32f429zi. The output from defmt gives an indication that the test runs.

2) With host PC. Wire rx pin to a serial port on a pc. Build embassy-stm32-tests with cargo build --bin usart_rx_ringbuffered --release --features=stm32f429zi. Run the build firmware. Run test-utils with cargo run com2 --idles. The output from defmt gives an indication that the test runs.

Setting a lower DMA_BUF_SIZE to, say 64 bytes gives me immediate overrun errors.

dragonnn commented 7 months ago

I think I am getting the same issue, if I am recreating the Uart instance on each loop iteration it works fine, but if use it constantly it stops working after a few kB received, doesn't metter if the rx buffer is 256 or 512, it stops basically always at the same point. Sometimes it does recover after a few secs more but not always. What is strange is that this does seam to only happen on USART2, my program uses USART6 too and even more data goes through it and it works perfectly fine. My MCU is STM32F401RET6.

rmja commented 7 months ago

@dragonnn Could you try and assign the same interrupt priority to USART2 as you have on USART6, and verify that it still does not work?

dragonnn commented 7 months ago

I am running default interrupt priority and as far I can tell both are running at P0? Using this code to check:

use embassy_stm32::{
    interrupt::{InterruptExt as _, Priority},
    pac::Interrupt,
};

info!("current usart2 priority: {:?}", Interrupt::USART2.get_priority());
info!("current usart6 priority: {:?}", Interrupt::USART6.get_priority());
rmja commented 7 months ago

How about the dma priority? USART2 uses DMA1 and USART6 DMA2.

It is actually strange, as if not configured, USART2 and DMA1 should have default higher priority than USART6 and DMA2 according to table 38 in the resource manual.

I guess it is not a priority issue then.

dragonnn commented 7 months ago

Hmmm

0.003969 INFO  current usart2 priority: P0
└─ <mod path> @ └─ <invalid location: defmt frame-index: 309>:0   
0.004005 INFO  current usart2 dma1 ch6 priority: P0
└─ <mod path> @ └─ <invalid location: defmt frame-index: 310>:0   
0.004042 INFO  current usart2 dma1 ch5 priority: P0
└─ <mod path> @ └─ <invalid location: defmt frame-index: 311>:0   
0.004117 INFO  current usart6 priority: P0
└─ <mod path> @ └─ <invalid location: defmt frame-index: 314>:0   
0.004153 INFO  current usart6 dma2 ch6 priority: P0
└─ <mod path> @ └─ <invalid location: defmt frame-index: 315>:0   
0.004189 INFO  current usart6 dma2 ch1 priority: P0

Does embassy zero out all priority?