Skip to content

Commit bf445ea

Browse files
Merge #588
588: Allow dma::Transfer.peripheral() for serial::Rx r=burrbull a=qwerty19106 * Impl `Transfer.is_idle()` and `Transfer.is_rx_not_empty()` for `serial::Rx` and `uart::Rx`. * Relax trait bounds this methods. See #586 for details. Co-authored-by: Роман Кривенков <qwerty19106@gmail.com>
2 parents a0971b2 + b1a041e commit bf445ea

File tree

4 files changed

+184
-5
lines changed

4 files changed

+184
-5
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
4242
[#617]: https://github.com/stm32-rs/stm32f4xx-hal/pull/617
4343
[#618]: https://github.com/stm32-rs/stm32f4xx-hal/pull/618
4444
[#623]: https://github.com/stm32-rs/stm32f4xx-hal/pull/623
45+
### Changed
46+
47+
### Added
48+
49+
- Improve SPI::new* docs [#587]
50+
- Implement `serial::RxISR` for `dma::Transfer<..., PERIPHERAL, ...>` where `PERIPHERAL: serial::RxISR`, add `rtic-serial-dma-rx-idle` example [#588]
51+
52+
### Fixed
4553

4654
## [v0.15.0] - 2023-03-13
4755

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,10 @@ required-features = ["stm32f411", "rtic"] # stm32f411
415415
name = "rtic-i2s-audio-in-out"
416416
required-features = ["stm32f411", "i2s", "rtic"]
417417

418+
[[example]]
419+
name = "rtic-serial-dma-rx-idle"
420+
required-features = ["stm32f411", "rtic"]
421+
418422
[[example]]
419423
name = "rtic-spi-slave-dma"
420424
required-features = ["stm32f411", "rtic"]

examples/rtic-serial-dma-rx-idle.rs

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
// This example implement simple and safe method receiving data with unknown length by UART.
2+
// The data received by using DMA, and IDLE event denote end of data packet.
3+
// See https://github.com/MaJerle/stm32-usart-uart-dma-rx-tx for details.
4+
5+
// If you use big buffers, it is recommended to add memory pools (allocators) and use
6+
// lock-free queues to send buffer without memcpy.
7+
8+
#![deny(unsafe_code)]
9+
#![deny(warnings)]
10+
#![no_main]
11+
#![no_std]
12+
13+
#[rtic::app(device = stm32f4xx_hal::pac, peripherals = true, dispatchers = [TIM2])]
14+
mod app {
15+
16+
use hal::{
17+
dma::{
18+
config::DmaConfig, traits::Stream, traits::StreamISR, PeripheralToMemory, Stream2,
19+
StreamsTuple, Transfer,
20+
},
21+
pac::{DMA2, USART1},
22+
prelude::*,
23+
rcc::RccExt,
24+
serial,
25+
};
26+
use panic_semihosting as _;
27+
use systick_monotonic::*;
28+
29+
use stm32f4xx_hal as hal;
30+
31+
const BUFFER_SIZE: usize = 100;
32+
33+
type RxTransfer = Transfer<
34+
Stream2<DMA2>,
35+
4,
36+
serial::Rx<USART1>,
37+
PeripheralToMemory,
38+
&'static mut [u8; BUFFER_SIZE],
39+
>;
40+
41+
#[shared]
42+
struct Shared {
43+
#[lock_free]
44+
rx_transfer: RxTransfer,
45+
}
46+
47+
#[local]
48+
struct Local {
49+
rx_buffer: Option<&'static mut [u8; BUFFER_SIZE]>,
50+
}
51+
52+
#[monotonic(binds = SysTick, default = true)]
53+
type MyMono = Systick<1000>; // 1000 Hz / 1 ms granularity
54+
55+
#[init(local = [
56+
rx_pool_memory: [u8; 400] = [0; 400],
57+
])]
58+
fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) {
59+
let core = cx.core;
60+
let dp: hal::pac::Peripherals = cx.device;
61+
62+
let rcc = dp.RCC.constrain();
63+
let clocks = rcc.cfgr.freeze();
64+
65+
let mono = Systick::new(core.SYST, clocks.sysclk().to_Hz());
66+
67+
let gpioa = dp.GPIOA.split();
68+
69+
// Initialize UART with DMA events
70+
let rx_pin = gpioa.pa10.into_alternate();
71+
let mut rx = dp
72+
.USART1
73+
.rx(
74+
rx_pin,
75+
serial::Config::default()
76+
.baudrate(9600.bps())
77+
.dma(serial::config::DmaConfig::Rx),
78+
&clocks,
79+
)
80+
.unwrap();
81+
82+
// Listen UART IDLE event, which will be call USART1 interrupt
83+
rx.listen_idle();
84+
85+
let dma2 = StreamsTuple::new(dp.DMA2);
86+
87+
// Note! It is better to use memory pools, such as heapless::pool::Pool. But it not work with embedded_dma yet.
88+
// See CHANGELOG of unreleased main branch and issue https://github.com/japaric/heapless/pull/362 for details.
89+
let rx_buffer1 = cortex_m::singleton!(: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]).unwrap();
90+
let rx_buffer2 = cortex_m::singleton!(: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]).unwrap();
91+
92+
// Initialize and start DMA stream
93+
let mut rx_transfer = Transfer::init_peripheral_to_memory(
94+
dma2.2,
95+
rx,
96+
rx_buffer1,
97+
None,
98+
DmaConfig::default()
99+
.memory_increment(true)
100+
.fifo_enable(true)
101+
.fifo_error_interrupt(true)
102+
.transfer_complete_interrupt(true),
103+
);
104+
105+
rx_transfer.start(|_rx| {});
106+
107+
(
108+
Shared { rx_transfer },
109+
Local {
110+
rx_buffer: Some(rx_buffer2),
111+
},
112+
init::Monotonics(mono),
113+
)
114+
}
115+
116+
// Important! USART1 and DMA2_STREAM2 should the same interrupt priority!
117+
#[task(binds = USART1, priority=1, local = [rx_buffer],shared = [rx_transfer])]
118+
fn usart1(mut cx: usart1::Context) {
119+
let transfer = &mut cx.shared.rx_transfer;
120+
121+
if transfer.is_idle() {
122+
// Calc received bytes count
123+
let bytes_count = BUFFER_SIZE - Stream2::<DMA2>::get_number_of_transfers() as usize;
124+
125+
// Allocate new buffer
126+
let new_buffer = cx.local.rx_buffer.take().unwrap();
127+
128+
// Replace buffer and restart DMA stream
129+
let (buffer, _) = transfer.next_transfer(new_buffer).unwrap();
130+
131+
// Get slice for received bytes
132+
let _bytes = &buffer[..bytes_count];
133+
134+
// Do something with received bytes
135+
// For example, parse it or send (buffer, bytes_count) to lock-free queue.
136+
137+
// Free buffer
138+
*cx.local.rx_buffer = Some(buffer);
139+
}
140+
}
141+
142+
#[task(binds = DMA2_STREAM2, priority=1,shared = [rx_transfer])]
143+
fn dma2_stream2(mut cx: dma2_stream2::Context) {
144+
let transfer = &mut cx.shared.rx_transfer;
145+
146+
if Stream2::<DMA2>::get_fifo_error_flag() {
147+
transfer.clear_fifo_error_interrupt();
148+
}
149+
if Stream2::<DMA2>::get_transfer_complete_flag() {
150+
transfer.clear_transfer_complete_interrupt();
151+
152+
// Buffer is full, but no IDLE received!
153+
// You can process this data or discard data (ignore transfer complete interrupt and wait IDLE).
154+
155+
// Note! If you want process this data, it is recommended to use double buffering.
156+
// See Transfer::init_peripheral_to_memory for details.
157+
}
158+
}
159+
}

src/dma/mod.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,16 +1002,24 @@ where
10021002
}
10031003
}
10041004

1005-
impl<STREAM, const CHANNEL: u8, PERIPHERAL, BUF>
1006-
Transfer<STREAM, CHANNEL, PERIPHERAL, PeripheralToMemory, BUF>
1005+
impl<STREAM, const CHANNEL: u8, PERIPHERAL, BUF> RxISR
1006+
for Transfer<STREAM, CHANNEL, PERIPHERAL, PeripheralToMemory, BUF>
10071007
where
10081008
STREAM: Stream,
1009-
ChannelX<CHANNEL>: Channel,
10101009
PERIPHERAL: PeriAddress + DMASet<STREAM, CHANNEL, PeripheralToMemory> + RxISR,
1011-
BUF: ReadBuffer<Word = <PERIPHERAL as PeriAddress>::MemSize>,
10121010
{
1011+
/// Return true if the line idle status is set
1012+
fn is_idle(&self) -> bool {
1013+
self.peripheral.is_idle()
1014+
}
1015+
1016+
/// Return true if the rx register is not empty (and can be read)
1017+
fn is_rx_not_empty(&self) -> bool {
1018+
self.peripheral.is_rx_not_empty()
1019+
}
1020+
10131021
/// Clear idle line interrupt flag
1014-
pub fn clear_idle_interrupt(&self) {
1022+
fn clear_idle_interrupt(&self) {
10151023
self.peripheral.clear_idle_interrupt();
10161024
}
10171025
}

0 commit comments

Comments
 (0)