stm32f1xx-hal icon indicating copy to clipboard operation
stm32f1xx-hal copied to clipboard

UART unfixed-length data receiving based on IDLE interrupt and DMA

Open tfx2001 opened this issue 3 years ago • 4 comments

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));
    }
};

tfx2001 avatar Aug 11 '21 05:08 tfx2001

Can you test https://github.com/stm32-rs/stm32f1xx-hal/tree/serial2 branch, please? Some methods for serial interrupts are added here.

burrbull avatar Aug 11 '21 06:08 burrbull

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>?

tfx2001 avatar Aug 11 '21 08:08 tfx2001

Looks weird, you are right. I need time to investigate this.

burrbull avatar Aug 11 '21 08:08 burrbull

Looks like if we implement preliminary stop for Transfer<W, ..> you could use peek for your needs.

cc @TheZoq2 @thalesfragoso

burrbull avatar Aug 11 '21 09:08 burrbull