aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2023-05-01 21:36:10 +0000
committerGitHub <[email protected]>2023-05-01 21:36:10 +0000
commit6096f0cf4b5ef45b97665166be41bfd490748f40 (patch)
tree3e19fd1bacd3cb2d4276eefc224a81ed6c004ff0 /tests
parent855c0d1423cb1aacd4f4f45e255b02b442afde34 (diff)
parenta1d45303c336434929eb8eb7e55629c504a95b0e (diff)
Merge #1404
1404: feat(stm32): Add DMA based, ring-buffer based rx uart, v3 r=Dirbaio a=rmja This PR replaces #1150. Comparing to that PR, this one has the following changes: * The implementation now aligns with the new stm32 dma module, thanks `@Dirbaio!` * Calls to `read()` now returns on either 1) idle line, or 2) ring buffer is at most half full. This is different from the previous pr, which would return a lot of 1 byte reads. Thank you `@chemicstry` for making me realize that it was actually not what I wanted. This is accomplished using half-transfer completed and full-transfer completed interrupts. Both seems to be supported on both dma and bdma. The implementation still have the issue mentioned here: https://github.com/embassy-rs/embassy/pull/1150#discussion_r1094627035 Regarding the todos here: https://github.com/embassy-rs/embassy/pull/1150#issuecomment-1513905925. I have removed the exposure of ndtr from `dma::RingBuffer` to the uart so that the uart now simply calls `ringbuf::reload_position()` to align the position within the ring buffer to that of the actual running dma controller. BDMA and GPDMA is not implemented. I do not have any chips with those dma controllers, so maybe someone else should to this so that it can be tested. The `saturate_serial` test utility inside `tests/utils` has an `--idles` switch which can be used to saturate the uart from a pc, but with random idles. Because embassy-stm32 now can have tests, we should probably run them in ci. I do this locally to test the DmaRingBuffer: `cargo test --no-default-features --features stm32f429ig`. cc `@chemicstry` `@Dirbaio` Co-authored-by: Rasmus Melchior Jacobsen <[email protected]> Co-authored-by: Dario Nieuwenhuis <[email protected]>
Diffstat (limited to 'tests')
-rw-r--r--tests/stm32/Cargo.toml25
-rw-r--r--tests/stm32/src/bin/usart_dma.rs27
-rw-r--r--tests/stm32/src/bin/usart_rx_ringbuffered.rs200
-rw-r--r--tests/stm32/src/example_common.rs11
-rw-r--r--tests/utils/Cargo.toml10
-rw-r--r--tests/utils/src/bin/saturate_serial.rs53
6 files changed, 298 insertions, 28 deletions
diff --git a/tests/stm32/Cargo.toml b/tests/stm32/Cargo.toml
index d10d01e29..5cd949661 100644
--- a/tests/stm32/Cargo.toml
+++ b/tests/stm32/Cargo.toml
@@ -5,24 +5,26 @@ version = "0.1.0"
5license = "MIT OR Apache-2.0" 5license = "MIT OR Apache-2.0"
6 6
7[features] 7[features]
8stm32f103c8 = ["embassy-stm32/stm32f103c8"] # Blue Pill 8stm32f103c8 = ["embassy-stm32/stm32f103c8", "not-gpdma"] # Blue Pill
9stm32f429zi = ["embassy-stm32/stm32f429zi", "sdmmc", "chrono"] # Nucleo 9stm32f429zi = ["embassy-stm32/stm32f429zi", "sdmmc", "chrono", "not-gpdma"] # Nucleo
10stm32g071rb = ["embassy-stm32/stm32g071rb"] # Nucleo 10stm32g071rb = ["embassy-stm32/stm32g071rb", "not-gpdma"] # Nucleo
11stm32c031c6 = ["embassy-stm32/stm32c031c6"] # Nucleo 11stm32c031c6 = ["embassy-stm32/stm32c031c6", "not-gpdma"] # Nucleo
12stm32g491re = ["embassy-stm32/stm32g491re"] # Nucleo 12stm32g491re = ["embassy-stm32/stm32g491re", "not-gpdma"] # Nucleo
13stm32h755zi = ["embassy-stm32/stm32h755zi-cm7"] # Nucleo 13stm32h755zi = ["embassy-stm32/stm32h755zi-cm7", "not-gpdma"] # Nucleo
14stm32wb55rg = ["embassy-stm32/stm32wb55rg"] # Nucleo 14stm32wb55rg = ["embassy-stm32/stm32wb55rg", "not-gpdma"] # Nucleo
15stm32h563zi = ["embassy-stm32/stm32h563zi"] # Nucleo 15stm32h563zi = ["embassy-stm32/stm32h563zi"] # Nucleo
16stm32u585ai = ["embassy-stm32/stm32u585ai"] # IoT board 16stm32u585ai = ["embassy-stm32/stm32u585ai"] # IoT board
17 17
18sdmmc = [] 18sdmmc = []
19chrono = ["embassy-stm32/chrono", "dep:chrono"] 19chrono = ["embassy-stm32/chrono", "dep:chrono"]
20not-gpdma = []
20 21
21[dependencies] 22[dependencies]
22embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } 23embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] }
23embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } 24embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] }
24embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "tick-hz-32_768"] } 25embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "tick-hz-32_768", "defmt-timestamp-uptime"] }
25embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "memory-x", "time-driver-any"] } 26embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "memory-x", "time-driver-any"] }
27embassy-futures = { version = "0.1.0", path = "../../embassy-futures" }
26 28
27defmt = "0.3.0" 29defmt = "0.3.0"
28defmt-rtt = "0.4" 30defmt-rtt = "0.4"
@@ -33,6 +35,8 @@ embedded-hal = "0.2.6"
33embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10" } 35embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10" }
34embedded-hal-async = { version = "=0.2.0-alpha.1" } 36embedded-hal-async = { version = "=0.2.0-alpha.1" }
35panic-probe = { version = "0.3.0", features = ["print-defmt"] } 37panic-probe = { version = "0.3.0", features = ["print-defmt"] }
38rand_core = { version = "0.6", default-features = false }
39rand_chacha = { version = "0.3", default-features = false }
36 40
37chrono = { version = "^0.4", default-features = false, optional = true} 41chrono = { version = "^0.4", default-features = false, optional = true}
38 42
@@ -78,6 +82,11 @@ name = "usart_dma"
78path = "src/bin/usart_dma.rs" 82path = "src/bin/usart_dma.rs"
79required-features = [] 83required-features = []
80 84
85[[bin]]
86name = "usart_rx_ringbuffered"
87path = "src/bin/usart_rx_ringbuffered.rs"
88required-features = [ "not-gpdma",]
89
81# END TESTS 90# END TESTS
82 91
83[profile.dev] 92[profile.dev]
diff --git a/tests/stm32/src/bin/usart_dma.rs b/tests/stm32/src/bin/usart_dma.rs
index d673df0f3..de6cd41d1 100644
--- a/tests/stm32/src/bin/usart_dma.rs
+++ b/tests/stm32/src/bin/usart_dma.rs
@@ -6,6 +6,7 @@
6mod example_common; 6mod example_common;
7use defmt::assert_eq; 7use defmt::assert_eq;
8use embassy_executor::Spawner; 8use embassy_executor::Spawner;
9use embassy_futures::join::join;
9use embassy_stm32::interrupt; 10use embassy_stm32::interrupt;
10use embassy_stm32::usart::{Config, Uart}; 11use embassy_stm32::usart::{Config, Uart};
11use example_common::*; 12use example_common::*;
@@ -76,18 +77,26 @@ async fn main(_spawner: Spawner) {
76 (p.PB6, p.PB7, p.USART1, interrupt::take!(USART1), p.DMA1_CH1, p.DMA1_CH2); 77 (p.PB6, p.PB7, p.USART1, interrupt::take!(USART1), p.DMA1_CH1, p.DMA1_CH2);
77 78
78 let config = Config::default(); 79 let config = Config::default();
79 let mut usart = Uart::new(usart, rx, tx, irq, tx_dma, rx_dma, config); 80 let usart = Uart::new(usart, rx, tx, irq, tx_dma, rx_dma, config);
80 81
81 // We can't send too many bytes, they have to fit in the FIFO. 82 const LEN: usize = 128;
82 // This is because we aren't sending+receiving at the same time. 83 let mut tx_buf = [0; LEN];
83 // For whatever reason, blocking works with 2 bytes but DMA only with 1?? 84 let mut rx_buf = [0; LEN];
85 for i in 0..LEN {
86 tx_buf[i] = i as u8;
87 }
84 88
85 let data = [0x42]; 89 let (mut tx, mut rx) = usart.split();
86 usart.write(&data).await.unwrap();
87 90
88 let mut buf = [0; 1]; 91 let tx_fut = async {
89 usart.read(&mut buf).await.unwrap(); 92 tx.write(&tx_buf).await.unwrap();
90 assert_eq!(buf, data); 93 };
94 let rx_fut = async {
95 rx.read(&mut rx_buf).await.unwrap();
96 };
97 join(rx_fut, tx_fut).await;
98
99 assert_eq!(tx_buf, rx_buf);
91 100
92 info!("Test OK"); 101 info!("Test OK");
93 cortex_m::asm::bkpt(); 102 cortex_m::asm::bkpt();
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..2c4a8fdf4
--- /dev/null
+++ b/tests/stm32/src/bin/usart_rx_ringbuffered.rs
@@ -0,0 +1,200 @@
1// required-features: not-gpdma
2
3#![no_std]
4#![no_main]
5#![feature(type_alias_impl_trait)]
6
7#[path = "../example_common.rs"]
8mod example_common;
9use defmt::{assert_eq, panic};
10use embassy_executor::Spawner;
11use embassy_stm32::interrupt;
12use embassy_stm32::usart::{Config, DataBits, Parity, RingBufferedUartRx, StopBits, Uart, UartTx};
13use embassy_time::{Duration, Timer};
14use example_common::*;
15use rand_chacha::ChaCha8Rng;
16use rand_core::{RngCore, SeedableRng};
17
18#[cfg(feature = "stm32f103c8")]
19mod board {
20 pub type Uart = embassy_stm32::peripherals::USART1;
21 pub type TxDma = embassy_stm32::peripherals::DMA1_CH4;
22 pub type RxDma = embassy_stm32::peripherals::DMA1_CH5;
23}
24#[cfg(feature = "stm32g491re")]
25mod board {
26 pub type Uart = embassy_stm32::peripherals::USART1;
27 pub type TxDma = embassy_stm32::peripherals::DMA1_CH1;
28 pub type RxDma = embassy_stm32::peripherals::DMA1_CH2;
29}
30#[cfg(feature = "stm32g071rb")]
31mod board {
32 pub type Uart = embassy_stm32::peripherals::USART1;
33 pub type TxDma = embassy_stm32::peripherals::DMA1_CH1;
34 pub type RxDma = embassy_stm32::peripherals::DMA1_CH2;
35}
36#[cfg(feature = "stm32f429zi")]
37mod board {
38 pub type Uart = embassy_stm32::peripherals::USART6;
39 pub type TxDma = embassy_stm32::peripherals::DMA2_CH6;
40 pub type RxDma = embassy_stm32::peripherals::DMA2_CH1;
41}
42#[cfg(feature = "stm32wb55rg")]
43mod board {
44 pub type Uart = embassy_stm32::peripherals::LPUART1;
45 pub type TxDma = embassy_stm32::peripherals::DMA1_CH1;
46 pub type RxDma = embassy_stm32::peripherals::DMA1_CH2;
47}
48#[cfg(feature = "stm32h755zi")]
49mod board {
50 pub type Uart = embassy_stm32::peripherals::USART1;
51 pub type TxDma = embassy_stm32::peripherals::DMA1_CH0;
52 pub type RxDma = embassy_stm32::peripherals::DMA1_CH1;
53}
54#[cfg(feature = "stm32u585ai")]
55mod board {
56 pub type Uart = embassy_stm32::peripherals::USART3;
57 pub type TxDma = embassy_stm32::peripherals::GPDMA1_CH0;
58 pub type RxDma = embassy_stm32::peripherals::GPDMA1_CH1;
59}
60#[cfg(feature = "stm32c031c6")]
61mod board {
62 pub type Uart = embassy_stm32::peripherals::USART1;
63 pub type TxDma = embassy_stm32::peripherals::DMA1_CH1;
64 pub type RxDma = embassy_stm32::peripherals::DMA1_CH2;
65}
66
67const DMA_BUF_SIZE: usize = 256;
68
69#[embassy_executor::main]
70async fn main(spawner: Spawner) {
71 let p = embassy_stm32::init(config());
72 info!("Hello World!");
73
74 // Arduino pins D0 and D1
75 // They're connected together with a 1K resistor.
76 #[cfg(feature = "stm32f103c8")]
77 let (tx, rx, usart, irq, tx_dma, rx_dma) = (
78 p.PA9,
79 p.PA10,
80 p.USART1,
81 interrupt::take!(USART1),
82 p.DMA1_CH4,
83 p.DMA1_CH5,
84 );
85 #[cfg(feature = "stm32g491re")]
86 let (tx, rx, usart, irq, tx_dma, rx_dma) =
87 (p.PC4, p.PC5, p.USART1, interrupt::take!(USART1), p.DMA1_CH1, p.DMA1_CH2);
88 #[cfg(feature = "stm32g071rb")]
89 let (tx, rx, usart, irq, tx_dma, rx_dma) =
90 (p.PC4, p.PC5, p.USART1, interrupt::take!(USART1), p.DMA1_CH1, p.DMA1_CH2);
91 #[cfg(feature = "stm32f429zi")]
92 let (tx, rx, usart, irq, tx_dma, rx_dma) = (
93 p.PG14,
94 p.PG9,
95 p.USART6,
96 interrupt::take!(USART6),
97 p.DMA2_CH6,
98 p.DMA2_CH1,
99 );
100 #[cfg(feature = "stm32wb55rg")]
101 let (tx, rx, usart, irq, tx_dma, rx_dma) = (
102 p.PA2,
103 p.PA3,
104 p.LPUART1,
105 interrupt::take!(LPUART1),
106 p.DMA1_CH1,
107 p.DMA1_CH2,
108 );
109 #[cfg(feature = "stm32h755zi")]
110 let (tx, rx, usart, irq, tx_dma, rx_dma) =
111 (p.PB6, p.PB7, p.USART1, interrupt::take!(USART1), p.DMA1_CH0, p.DMA1_CH1);
112 #[cfg(feature = "stm32u585ai")]
113 let (tx, rx, usart, irq, tx_dma, rx_dma) = (
114 p.PD8,
115 p.PD9,
116 p.USART3,
117 interrupt::take!(USART3),
118 p.GPDMA1_CH0,
119 p.GPDMA1_CH1,
120 );
121 #[cfg(feature = "stm32c031c6")]
122 let (tx, rx, usart, irq, tx_dma, rx_dma) =
123 (p.PB6, p.PB7, p.USART1, interrupt::take!(USART1), p.DMA1_CH1, p.DMA1_CH2);
124
125 // To run this test, use the saturating_serial test utility to saturate the serial port
126
127 let mut config = Config::default();
128 // this is the fastest we can go without tuning RCC
129 // some chips have default pclk=8mhz, and uart can run at max pclk/16
130 config.baudrate = 500_000;
131 config.data_bits = DataBits::DataBits8;
132 config.stop_bits = StopBits::STOP1;
133 config.parity = Parity::ParityNone;
134
135 let usart = Uart::new(usart, rx, tx, irq, tx_dma, rx_dma, config);
136 let (tx, rx) = usart.split();
137 static mut DMA_BUF: [u8; DMA_BUF_SIZE] = [0; DMA_BUF_SIZE];
138 let dma_buf = unsafe { DMA_BUF.as_mut() };
139 let rx = rx.into_ring_buffered(dma_buf);
140
141 info!("Spawning tasks");
142 spawner.spawn(transmit_task(tx)).unwrap();
143 spawner.spawn(receive_task(rx)).unwrap();
144}
145
146#[embassy_executor::task]
147async fn transmit_task(mut tx: UartTx<'static, board::Uart, board::TxDma>) {
148 let mut rng = ChaCha8Rng::seed_from_u64(1337);
149
150 info!("Starting random transmissions into void...");
151
152 let mut i: u8 = 0;
153 loop {
154 let mut buf = [0; 32];
155 let len = 1 + (rng.next_u32() as usize % buf.len());
156 for b in &mut buf[..len] {
157 *b = i;
158 i = i.wrapping_add(1);
159 }
160
161 tx.write(&buf[..len]).await.unwrap();
162 Timer::after(Duration::from_micros((rng.next_u32() % 1000) as _)).await;
163 }
164}
165
166#[embassy_executor::task]
167async fn receive_task(mut rx: RingBufferedUartRx<'static, board::Uart, board::RxDma>) {
168 info!("Ready to receive...");
169
170 let mut rng = ChaCha8Rng::seed_from_u64(1337);
171
172 let mut i = 0;
173 let mut expected = 0;
174 loop {
175 let mut buf = [0; 100];
176 let max_len = 1 + (rng.next_u32() as usize % buf.len());
177 let received = match rx.read(&mut buf[..max_len]).await {
178 Ok(r) => r,
179 Err(e) => {
180 panic!("Test fail! read error: {:?}", e);
181 }
182 };
183
184 for byte in &buf[..received] {
185 assert_eq!(*byte, expected);
186 expected = expected.wrapping_add(1);
187 }
188
189 if received < max_len {
190 Timer::after(Duration::from_micros((rng.next_u32() % 1000) as _)).await;
191 }
192
193 i += received;
194
195 if i > 100000 {
196 info!("Test OK!");
197 cortex_m::asm::bkpt();
198 }
199 }
200}
diff --git a/tests/stm32/src/example_common.rs b/tests/stm32/src/example_common.rs
index c47ed75c4..a4f8668c7 100644
--- a/tests/stm32/src/example_common.rs
+++ b/tests/stm32/src/example_common.rs
@@ -1,22 +1,11 @@
1#![macro_use] 1#![macro_use]
2 2
3use core::sync::atomic::{AtomicUsize, Ordering};
4
5pub use defmt::*; 3pub use defmt::*;
6#[allow(unused)] 4#[allow(unused)]
7use embassy_stm32::time::Hertz; 5use embassy_stm32::time::Hertz;
8use embassy_stm32::Config; 6use embassy_stm32::Config;
9use {defmt_rtt as _, panic_probe as _}; 7use {defmt_rtt as _, panic_probe as _};
10 8
11defmt::timestamp! {"{=u64}", {
12 static COUNT: AtomicUsize = AtomicUsize::new(0);
13 // NOTE(no-CAS) `timestamps` runs with interrupts disabled
14 let n = COUNT.load(Ordering::Relaxed);
15 COUNT.store(n + 1, Ordering::Relaxed);
16 n as u64
17 }
18}
19
20pub fn config() -> Config { 9pub fn config() -> Config {
21 #[allow(unused_mut)] 10 #[allow(unused_mut)]
22 let mut config = Config::default(); 11 let mut config = Config::default();
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]
2name = "test-utils"
3version = "0.1.0"
4edition = "2021"
5
6# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7
8[dependencies]
9rand = "0.8"
10serial = "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..18ca12fb7
--- /dev/null
+++ b/tests/utils/src/bin/saturate_serial.rs
@@ -0,0 +1,53 @@
1use std::path::Path;
2use std::time::Duration;
3use std::{env, io, process, thread};
4
5use rand::random;
6use serial::SerialPort;
7
8pub fn main() {
9 if let Some(port_name) = env::args().nth(1) {
10 let idles = env::args().position(|x| x == "--idles").is_some();
11
12 println!("Saturating port {:?} with 115200 8N1", port_name);
13 println!("Idles: {}", idles);
14 println!("Process ID: {}", process::id());
15 let mut port = serial::open(&port_name).unwrap();
16 if saturate(&mut port, idles).is_err() {
17 eprintln!("Unable to saturate port");
18 }
19 } else {
20 let path = env::args().next().unwrap();
21 let basepath = Path::new(&path).with_extension("");
22 let basename = basepath.file_name().unwrap();
23 eprintln!("USAGE: {} <port-name>", basename.to_string_lossy());
24 }
25}
26
27fn saturate<T: SerialPort>(port: &mut T, idles: bool) -> io::Result<()> {
28 port.reconfigure(&|settings| {
29 settings.set_baud_rate(serial::Baud115200)?;
30 settings.set_char_size(serial::Bits8);
31 settings.set_parity(serial::ParityNone);
32 settings.set_stop_bits(serial::Stop1);
33 Ok(())
34 })?;
35
36 let mut written = 0;
37 loop {
38 let len = random::<usize>() % 0x1000;
39 let buf: Vec<u8> = (written..written + len).map(|x| x as u8).collect();
40
41 port.write_all(&buf)?;
42
43 if idles {
44 let micros = (random::<usize>() % 1000) as u64;
45 println!("Sleeping {}us", micros);
46 port.flush().unwrap();
47 thread::sleep(Duration::from_micros(micros));
48 }
49
50 written += len;
51 println!("Written: {}", written);
52 }
53}