stm32f1xx-hal
stm32f1xx-hal copied to clipboard
UART unfixed-length data receiving based on IDLE interrupt and DMA
I'm trying to implement unfixed-length data receiving based on IDLE interrupt and DMA, and here is my code. But there are still some problems and I think it's not convenient enough. Is there a better way?
#![no_std]
#![no_main]
use core::fmt::Write;
use cortex_m::singleton;
use panic_halt as _;
use rtic::app;
use stm32f1xx_hal::{
dma::CircBuffer,
pac::{self, Interrupt},
prelude::*,
serial::{self, Serial},
};
#[app(device = stm32f1xx_hal::pac, peripherals = true)]
const APP: () = {
struct Resources {
rx_transfer: Option<CircBuffer<[u8; 8], serial::RxDma1>>,
usart1_tx: serial::Tx<pac::USART1>,
}
#[init]
fn init(cx: init::Context) -> init::LateResources {
// Cortex-M peripherals
let _cp: cortex_m::Peripherals = cx.core;
// Device specific peripherals
let dp: pac::Peripherals = cx.device;
let mut flash = dp.FLASH.constrain();
let rcc = dp.RCC.constrain();
let clocks = rcc.cfgr
.use_hse(8.mhz())
.sysclk(72.mhz())
.pclk1(36.mhz())
.pclk2(72.mhz())
.freeze(&mut flash.acr);
let mut afio = dp.AFIO.constrain();
let dma_channels = dp.DMA1.split();
let mut gpioa = dp.GPIOA.split();
let pa9 = gpioa.pa9.into_alternate_push_pull(&mut gpioa.crh);
let pa10 = gpioa.pa10.into_floating_input(&mut gpioa.crh);
let mut usart1 = Serial::usart1(
dp.USART1,
(pa9, pa10),
&mut afio.mapr,
serial::Config::default().baudrate(115200.bps()),
clocks,
);
rtic::pend(Interrupt::USART1);
usart1.listen(serial::Event::Idle);
let (mut tx, rx) = usart1.split();
let rx = rx.with_dma(dma_channels.5);
let buf = singleton!(: [[u8; 8]; 2] = [[0; 8]; 2]).unwrap();
let rx_transfer = rx.circ_read(buf);
write!(tx, "Init.\r\n").unwrap();
init::LateResources {
rx_transfer: Some(rx_transfer),
usart1_tx: tx,
}
}
#[idle]
fn idle(_: idle::Context) -> ! {
loop {}
}
#[task(binds = USART1, resources = [rx_transfer, usart1_tx])]
fn usart1_isr(cx: usart1_isr::Context) {
let rx: CircBuffer<[u8; 8], serial::RxDma1> = cx.resources.rx_transfer.take().unwrap();
let tx: &mut serial::Tx<pac::USART1> = cx.resources.usart1_tx;
let (buf, mut rx) = rx.stop();
let len = (buf[0].len() as u32 * 2) - rx.channel.ch().ndtr.read().bits();
write!(tx, "len: {} data: {}\r\n", len, core::str::from_utf8(&buf[0][..]).unwrap()).unwrap();
// Clear SR IDLE bit
let usart1 = unsafe { &*pac::USART1::ptr() };
usart1.sr.read();
usart1.dr.read();
cx.resources.rx_transfer.replace(rx.circ_read(buf));
}
};
Can you test https://github.com/stm32-rs/stm32f1xx-hal/tree/serial2 branch, please? Some methods for serial interrupts are added here.
Good, it works, here's the current code:
#[task(binds = USART1, resources = [rx_transfer, usart1_tx])]
fn usart1_isr(cx: usart1_isr::Context) {
let rx: CircBuffer<[u8; 8], serial::RxDma1> = cx.resources.rx_transfer.take().unwrap();
let tx: &mut serial::Tx<pac::USART1> = cx.resources.usart1_tx;
let (buf, mut rx) = rx.stop();
let len = (buf[0].len() as u32 * 2) - rx.channel.ch().ndtr.read().bits();
write!(tx, "len: {} data: {}\r\n", len, core::str::from_utf8(&buf[0][..]).unwrap()).unwrap();
let (rx, channel) = rx.release();
rx.clear_idle_interrupt();
let rx = rx.with_dma(channel);
cx.resources.rx_transfer.replace(rx.circ_read(buf));
}
And I have another question, because Transfer<MODE, BUFFER, PAYLOAD>
not implement method like stop()
, I cannot stop the DMA transfer (to restart the transfer) until the transfer is finished, I have to use circ_read()
to get CircBuffer<B, Self>
which has stop()
method. Can we add a stop()
method to Transfer<MODE, BUFFER, PAYLOAD>
?
Looks weird, you are right. I need time to investigate this.
Looks like if we implement preliminary stop
for Transfer<W, ..>
you could use peek
for your needs.
cc @TheZoq2 @thalesfragoso