diff options
| author | Rasmus Melchior Jacobsen <[email protected]> | 2023-04-26 10:51:23 +0200 |
|---|---|---|
| committer | Dario Nieuwenhuis <[email protected]> | 2023-05-01 22:42:36 +0200 |
| commit | 49455792cb5406f3e2df0f3850c0d650965e6b2b (patch) | |
| tree | 6811a5ef17a7fa7809467e5aa6dc5552cabfe5c6 /tests | |
| parent | 855c0d1423cb1aacd4f4f45e255b02b442afde34 (diff) | |
Ring-buffered uart rx with one-period overrun detection
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/stm32/Cargo.toml | 2 | ||||
| -rw-r--r-- | tests/stm32/src/bin/usart_rx_ringbuffered.rs | 188 | ||||
| -rw-r--r-- | tests/utils/Cargo.toml | 10 | ||||
| -rw-r--r-- | tests/utils/src/bin/saturate_serial.rs | 52 |
4 files changed, 252 insertions, 0 deletions
diff --git a/tests/stm32/Cargo.toml b/tests/stm32/Cargo.toml index d10d01e29..240fad522 100644 --- a/tests/stm32/Cargo.toml +++ b/tests/stm32/Cargo.toml | |||
| @@ -33,6 +33,8 @@ embedded-hal = "0.2.6" | |||
| 33 | embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10" } | 33 | embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10" } |
| 34 | embedded-hal-async = { version = "=0.2.0-alpha.1" } | 34 | embedded-hal-async = { version = "=0.2.0-alpha.1" } |
| 35 | panic-probe = { version = "0.3.0", features = ["print-defmt"] } | 35 | panic-probe = { version = "0.3.0", features = ["print-defmt"] } |
| 36 | rand_core = { version = "0.6", default-features = false } | ||
| 37 | rand_chacha = { version = "0.3", default-features = false } | ||
| 36 | 38 | ||
| 37 | chrono = { version = "^0.4", default-features = false, optional = true} | 39 | chrono = { version = "^0.4", default-features = false, optional = true} |
| 38 | 40 | ||
diff --git a/tests/stm32/src/bin/usart_rx_ringbuffered.rs b/tests/stm32/src/bin/usart_rx_ringbuffered.rs new file mode 100644 index 000000000..3ea8bfb7b --- /dev/null +++ b/tests/stm32/src/bin/usart_rx_ringbuffered.rs | |||
| @@ -0,0 +1,188 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![feature(type_alias_impl_trait)] | ||
| 4 | |||
| 5 | #[path = "../example_common.rs"] | ||
| 6 | mod example_common; | ||
| 7 | use embassy_executor::Spawner; | ||
| 8 | use embassy_stm32::interrupt; | ||
| 9 | use embassy_stm32::usart::{Config, DataBits, Parity, RingBufferedUartRx, StopBits, Uart, UartTx}; | ||
| 10 | use embassy_time::{Duration, Timer}; | ||
| 11 | use example_common::*; | ||
| 12 | use rand_chacha::ChaCha8Rng; | ||
| 13 | use rand_core::{RngCore, SeedableRng}; | ||
| 14 | |||
| 15 | #[cfg(feature = "stm32f103c8")] | ||
| 16 | mod board { | ||
| 17 | pub type Uart = embassy_stm32::peripherals::USART1; | ||
| 18 | pub type TxDma = embassy_stm32::peripherals::DMA1_CH4; | ||
| 19 | pub type RxDma = embassy_stm32::peripherals::DMA1_CH5; | ||
| 20 | } | ||
| 21 | #[cfg(feature = "stm32g491re")] | ||
| 22 | mod board { | ||
| 23 | pub type Uart = embassy_stm32::peripherals::USART1; | ||
| 24 | pub type TxDma = embassy_stm32::peripherals::DMA1_CH1; | ||
| 25 | pub type RxDma = embassy_stm32::peripherals::DMA1_CH2; | ||
| 26 | } | ||
| 27 | #[cfg(feature = "stm32g071rb")] | ||
| 28 | mod board { | ||
| 29 | pub type Uart = embassy_stm32::peripherals::USART1; | ||
| 30 | pub type TxDma = embassy_stm32::peripherals::DMA1_CH1; | ||
| 31 | pub type RxDma = embassy_stm32::peripherals::DMA1_CH2; | ||
| 32 | } | ||
| 33 | #[cfg(feature = "stm32f429zi")] | ||
| 34 | mod board { | ||
| 35 | pub type Uart = embassy_stm32::peripherals::USART2; | ||
| 36 | pub type TxDma = embassy_stm32::peripherals::DMA1_CH6; | ||
| 37 | pub type RxDma = embassy_stm32::peripherals::DMA1_CH5; | ||
| 38 | } | ||
| 39 | #[cfg(feature = "stm32wb55rg")] | ||
| 40 | mod board { | ||
| 41 | pub type Uart = embassy_stm32::peripherals::LPUART1; | ||
| 42 | pub type TxDma = embassy_stm32::peripherals::DMA1_CH1; | ||
| 43 | pub type RxDma = embassy_stm32::peripherals::DMA1_CH2; | ||
| 44 | } | ||
| 45 | #[cfg(feature = "stm32h755zi")] | ||
| 46 | mod board { | ||
| 47 | pub type Uart = embassy_stm32::peripherals::USART1; | ||
| 48 | pub type TxDma = embassy_stm32::peripherals::DMA1_CH0; | ||
| 49 | pub type RxDma = embassy_stm32::peripherals::DMA1_CH1; | ||
| 50 | } | ||
| 51 | #[cfg(feature = "stm32u585ai")] | ||
| 52 | mod board { | ||
| 53 | pub type Uart = embassy_stm32::peripherals::USART3; | ||
| 54 | pub type TxDma = embassy_stm32::peripherals::GPDMA1_CH0; | ||
| 55 | pub type RxDma = embassy_stm32::peripherals::GPDMA1_CH1; | ||
| 56 | } | ||
| 57 | |||
| 58 | const ONE_BYTE_DURATION_US: u32 = 9_000_000 / 115200; | ||
| 59 | |||
| 60 | #[embassy_executor::main] | ||
| 61 | async fn main(spawner: Spawner) { | ||
| 62 | let p = embassy_stm32::init(config()); | ||
| 63 | info!("Hello World!"); | ||
| 64 | |||
| 65 | // Arduino pins D0 and D1 | ||
| 66 | // They're connected together with a 1K resistor. | ||
| 67 | #[cfg(feature = "stm32f103c8")] | ||
| 68 | let (tx, rx, usart, irq, tx_dma, rx_dma) = ( | ||
| 69 | p.PA9, | ||
| 70 | p.PA10, | ||
| 71 | p.USART1, | ||
| 72 | interrupt::take!(USART1), | ||
| 73 | p.DMA1_CH4, | ||
| 74 | p.DMA1_CH5, | ||
| 75 | ); | ||
| 76 | #[cfg(feature = "stm32g491re")] | ||
| 77 | let (tx, rx, usart, irq, tx_dma, rx_dma) = | ||
| 78 | (p.PC4, p.PC5, p.USART1, interrupt::take!(USART1), p.DMA1_CH1, p.DMA1_CH2); | ||
| 79 | #[cfg(feature = "stm32g071rb")] | ||
| 80 | let (tx, rx, usart, irq, tx_dma, rx_dma) = | ||
| 81 | (p.PC4, p.PC5, p.USART1, interrupt::take!(USART1), p.DMA1_CH1, p.DMA1_CH2); | ||
| 82 | #[cfg(feature = "stm32f429zi")] | ||
| 83 | let (tx, rx, usart, irq, tx_dma, rx_dma) = | ||
| 84 | (p.PA2, p.PA3, p.USART2, interrupt::take!(USART2), p.DMA1_CH6, p.DMA1_CH5); | ||
| 85 | #[cfg(feature = "stm32wb55rg")] | ||
| 86 | let (tx, rx, usart, irq, tx_dma, rx_dma) = ( | ||
| 87 | p.PA2, | ||
| 88 | p.PA3, | ||
| 89 | p.LPUART1, | ||
| 90 | interrupt::take!(LPUART1), | ||
| 91 | p.DMA1_CH1, | ||
| 92 | p.DMA1_CH2, | ||
| 93 | ); | ||
| 94 | #[cfg(feature = "stm32h755zi")] | ||
| 95 | let (tx, rx, usart, irq, tx_dma, rx_dma) = | ||
| 96 | (p.PB6, p.PB7, p.USART1, interrupt::take!(USART1), p.DMA1_CH0, p.DMA1_CH1); | ||
| 97 | #[cfg(feature = "stm32u585ai")] | ||
| 98 | let (tx, rx, usart, irq, tx_dma, rx_dma) = ( | ||
| 99 | p.PD8, | ||
| 100 | p.PD9, | ||
| 101 | p.USART3, | ||
| 102 | interrupt::take!(USART3), | ||
| 103 | p.GPDMA1_CH0, | ||
| 104 | p.GPDMA1_CH1, | ||
| 105 | ); | ||
| 106 | |||
| 107 | // To run this test, use the saturating_serial test utility to saturate the serial port | ||
| 108 | |||
| 109 | let mut config = Config::default(); | ||
| 110 | config.baudrate = 115200; | ||
| 111 | config.data_bits = DataBits::DataBits8; | ||
| 112 | config.stop_bits = StopBits::STOP1; | ||
| 113 | config.parity = Parity::ParityNone; | ||
| 114 | |||
| 115 | let usart = Uart::new(usart, rx, tx, irq, tx_dma, rx_dma, config); | ||
| 116 | let (tx, rx) = usart.split(); | ||
| 117 | static mut DMA_BUF: [u8; 64] = [0; 64]; | ||
| 118 | let dma_buf = unsafe { DMA_BUF.as_mut() }; | ||
| 119 | let rx = rx.into_ring_buffered(dma_buf); | ||
| 120 | |||
| 121 | info!("Spawning tasks"); | ||
| 122 | spawner.spawn(transmit_task(tx)).unwrap(); | ||
| 123 | spawner.spawn(receive_task(rx)).unwrap(); | ||
| 124 | } | ||
| 125 | |||
| 126 | #[embassy_executor::task] | ||
| 127 | async fn transmit_task(mut tx: UartTx<'static, board::Uart, board::TxDma>) { | ||
| 128 | let mut rng = ChaCha8Rng::seed_from_u64(1337); | ||
| 129 | |||
| 130 | info!("Starting random transmissions into void..."); | ||
| 131 | |||
| 132 | let mut i: u8 = 0; | ||
| 133 | loop { | ||
| 134 | let mut buf = [0; 32]; | ||
| 135 | let len = 1 + (rng.next_u32() as usize % (buf.len() - 1)); | ||
| 136 | for b in &mut buf[..len] { | ||
| 137 | *b = i; | ||
| 138 | i = i.wrapping_add(1); | ||
| 139 | } | ||
| 140 | |||
| 141 | tx.write(&buf[..len]).await.unwrap(); | ||
| 142 | Timer::after(Duration::from_micros((rng.next_u32() % 10000) as _)).await; | ||
| 143 | |||
| 144 | //i += 1; | ||
| 145 | //if i % 1000 == 0 { | ||
| 146 | // trace!("Wrote {} times", i); | ||
| 147 | //} | ||
| 148 | } | ||
| 149 | } | ||
| 150 | |||
| 151 | #[embassy_executor::task] | ||
| 152 | async fn receive_task(mut rx: RingBufferedUartRx<'static, board::Uart, board::RxDma>) { | ||
| 153 | info!("Ready to receive..."); | ||
| 154 | |||
| 155 | let mut rng = ChaCha8Rng::seed_from_u64(1337); | ||
| 156 | |||
| 157 | let mut i = 0; | ||
| 158 | let mut expected: Option<u8> = None; | ||
| 159 | loop { | ||
| 160 | let mut buf = [0; 100]; | ||
| 161 | let max_len = 1 + (rng.next_u32() as usize % (buf.len() - 1)); | ||
| 162 | let received = rx.read(&mut buf[..max_len]).await.unwrap(); | ||
| 163 | |||
| 164 | if expected.is_none() { | ||
| 165 | info!("Test started"); | ||
| 166 | expected = Some(buf[0]); | ||
| 167 | } | ||
| 168 | |||
| 169 | for byte in &buf[..received] { | ||
| 170 | if byte != &expected.unwrap() { | ||
| 171 | error!("Test fail! received {}, expected {}", *byte, expected.unwrap()); | ||
| 172 | cortex_m::asm::bkpt(); | ||
| 173 | return; | ||
| 174 | } | ||
| 175 | expected = Some(expected.unwrap().wrapping_add(1)); | ||
| 176 | } | ||
| 177 | |||
| 178 | if received < max_len { | ||
| 179 | let byte_count = rng.next_u32() % 64; | ||
| 180 | Timer::after(Duration::from_micros((byte_count * ONE_BYTE_DURATION_US) as _)).await; | ||
| 181 | } | ||
| 182 | |||
| 183 | i += 1; | ||
| 184 | if i % 1000 == 0 { | ||
| 185 | trace!("Read {} times", i); | ||
| 186 | } | ||
| 187 | } | ||
| 188 | } | ||
diff --git a/tests/utils/Cargo.toml b/tests/utils/Cargo.toml new file mode 100644 index 000000000..7d66fd586 --- /dev/null +++ b/tests/utils/Cargo.toml | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | [package] | ||
| 2 | name = "test-utils" | ||
| 3 | version = "0.1.0" | ||
| 4 | edition = "2021" | ||
| 5 | |||
| 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
| 7 | |||
| 8 | [dependencies] | ||
| 9 | rand = "0.8" | ||
| 10 | serial = "0.4" | ||
diff --git a/tests/utils/src/bin/saturate_serial.rs b/tests/utils/src/bin/saturate_serial.rs new file mode 100644 index 000000000..28480516d --- /dev/null +++ b/tests/utils/src/bin/saturate_serial.rs | |||
| @@ -0,0 +1,52 @@ | |||
| 1 | use std::path::Path; | ||
| 2 | use std::time::Duration; | ||
| 3 | use std::{env, io, thread}; | ||
| 4 | |||
| 5 | use rand::random; | ||
| 6 | use serial::SerialPort; | ||
| 7 | |||
| 8 | pub fn main() { | ||
| 9 | if let Some(port_name) = env::args().nth(1) { | ||
| 10 | let sleep = env::args().position(|x| x == "--sleep").is_some(); | ||
| 11 | |||
| 12 | println!("Saturating port {:?} with 115200 8N1", port_name); | ||
| 13 | println!("Sleep: {}", sleep); | ||
| 14 | let mut port = serial::open(&port_name).unwrap(); | ||
| 15 | if saturate(&mut port, sleep).is_err() { | ||
| 16 | eprintln!("Unable to saturate port"); | ||
| 17 | } | ||
| 18 | } else { | ||
| 19 | let path = env::args().next().unwrap(); | ||
| 20 | let basepath = Path::new(&path).with_extension(""); | ||
| 21 | let basename = basepath.file_name().unwrap(); | ||
| 22 | eprintln!("USAGE: {} <port-name>", basename.to_string_lossy()); | ||
| 23 | } | ||
| 24 | } | ||
| 25 | |||
| 26 | fn saturate<T: SerialPort>(port: &mut T, sleep: bool) -> io::Result<()> { | ||
| 27 | port.reconfigure(&|settings| { | ||
| 28 | settings.set_baud_rate(serial::Baud115200)?; | ||
| 29 | settings.set_char_size(serial::Bits8); | ||
| 30 | settings.set_parity(serial::ParityNone); | ||
| 31 | settings.set_stop_bits(serial::Stop1); | ||
| 32 | Ok(()) | ||
| 33 | })?; | ||
| 34 | |||
| 35 | let mut written = 0; | ||
| 36 | loop { | ||
| 37 | let len = random::<usize>() % 0x1000; | ||
| 38 | let buf: Vec<u8> = (written..written + len).map(|x| x as u8).collect(); | ||
| 39 | |||
| 40 | port.write_all(&buf)?; | ||
| 41 | |||
| 42 | if sleep { | ||
| 43 | let micros = (random::<usize>() % 1000) as u64; | ||
| 44 | println!("Sleeping {}us", micros); | ||
| 45 | port.flush().unwrap(); | ||
| 46 | thread::sleep(Duration::from_micros(micros)); | ||
| 47 | } | ||
| 48 | |||
| 49 | written += len; | ||
| 50 | println!("Written: {}", written); | ||
| 51 | } | ||
| 52 | } \ No newline at end of file | ||
