cancel
Showing results for 
Search instead for 
Did you mean: 

DMA Transfer Error for GPIO on STM32G031

nolanhergert
Associate
I am trying to periodically transfer data to the first byte of GPIOA (PA0-PA7) using DMA Channel 1 and TIMER1, but am hitting a transfer error immediately after enabling the DMA channel for transferring.

 

* I can write to GPIOA->ODR using the CPU and I've confirmed the output on a scope, so the GPIOA timer is enabled and the pins are set as outputs.
* I am able to confirm that TIMER1 is running at ~20Khz, because it is able to toggle its associated GPIO pin (PA8) at the desired frequency. This is plenty low compared to the system frequency, which is north of 16Mhz (not sure exact value, PLL is enabled)
* I believe the DMAMUX is connected correctly because if I disable the timer, no transfer error shows up for DMA ch 1 ISR and NDTR doesn't change (as expected).
* I've confirmed the peripheral address is set to 0x50000014, which lines up with GPIO->ODR.
* I've confirmed the memory address is set to 0x20001F60, which lines up with the buffer on the stack.
* NDTR is non-zero
* The ISR is 0 before enabling DMA.
* I've tried various transfer sizes, 8-bit, 16-bit, 32-bit, to no avail.
* I've confirmed that DMA mem-to-mem transfers work as expected.
* GPIOA lock register is 0.

Potential explanations:
* DMA isn't allowed to access GPIOA. It seems unlikely, as there is only DMA1 with 5 channels on the chip, it's a key feature for MCUs, and I don't see anything preventing it in the memory / system architecture chapter.

I've attached the full file, but the relevant section is below. It's in Rust, but it should be very readable.
 
 
 
fn setup_dma(
    dma_ch: &stm32g0xx_hal::pac::dma::CH,
    peripheral_address: u32,
    buffer_address: u32,
    buffer_size: u16,
) {
    let dma_mux: &stm32g0xx_hal::pac::dmamux::RegisterBlock = unsafe { &*DMAMUX::ptr() };
    let rcc: &stm32g0xx_hal::pac::rcc::RegisterBlock = unsafe { &*RCC::ptr() };
    rcc.ahbenr.modify(|_, w| w.dmaen().set_bit()); // Enable DMA clock
    dma_mux
        .c0cr
        .write(|w| unsafe { w.dmareq_id().bits(DmaMuxIndex::TIM1_CH1 as u8) });

    dma_ch.par.write(|w| unsafe { w.bits(peripheral_address) });
    dma_ch.mar.write(|w| unsafe { w.bits(buffer_address) });
    dma_ch.ndtr.write(|w| unsafe { w.ndt().bits(buffer_size) });
    rprint!("DMA PAR: {:#X}\n", dma_ch.par.read().bits());
    rprint!("DMA MAR: {:#X}\n", dma_ch.mar.read().bits());
    rprint!("DMA NDTR: {:#X}\n", dma_ch.ndtr.read().bits());

    // Configure the DMA channel// Transfer complete interrupt enable
    dma_ch.cr.write(|w| {
        unsafe {
            w.mem2mem()
                .clear_bit() // Memory-to-memory mode disabled
                .pl()
                .bits(stm32g0xx_hal::dma::Priority::VeryHigh as u8)
                .msize()
                .bits(stm32g0xx_hal::dma::WordSize::BITS32 as u8)
                .psize()
                .bits(stm32g0xx_hal::dma::WordSize::BITS32 as u8)
                .minc()
                .clear_bit()
                .pinc()
                .clear_bit()
                .circ()
                .clear_bit()
                .dir()
                .set_bit()
                .teie()
                .set_bit()
                .tcie()
                .set_bit()
        }
    });
    rprintln!("DMA CR: {:#X}\n", dma_ch.cr.read().bits());
}

pub fn write_parallel_data_dma(buffer: &mut [u32]) -> &[u32] {
    let dma: &stm32g0xx_hal::pac::dma::RegisterBlock = unsafe { &*DMA::ptr() };
    let gpioa = unsafe { &*stm32g0xx_hal::pac::GPIOA::ptr() };

    rprintln!(
        "Moder: {:#X} PUPDR: {:#X}\n",
        gpioa.moder.read().bits(),
        gpioa.pupdr.read().bits()
    );

    setup_dma(
        &dma.ch1,
        &gpioa.odr as *const _ as u32,
        buffer.as_mut_ptr() as u32,
        buffer.len() as u16,
    );

    rprint!("ISR before: {}\n", dma.isr.read().bits());
    // Start DMA transfer
    dma.ch1.cr.modify(|_, w| w.en().set_bit());
    asm::delay(100000);
    rprint!("GPIOA LCKR: {}\n", gpioa.lckr.read().bits());

    // Wait for DMA interrupt cause flag to be set
    loop {

        rprint!("DMA NDTR: {}\n", dma.ch1.ndtr.read().bits());
        rprint!("ISR during: {}\n", dma.isr.read().bits());

        let isr_val = dma.isr.read();
        if isr_val.tcif1().bit_is_set() {
            rprintln!("DMA transfer completed successfully");
            break;
        } else if isr_val.teif1().bit_is_set() {
            panic!(
                "DMA did not complete successfully. ISR state: {:#018b}",
                 isr_val.bits()
            );
        }
    }
    // Stop DMA
    dma.ch1.cr.modify(|_, w| w.en().clear_bit());

    buffer
}

  

Output:

Moder: 0xEBFE5555 PUPDR: 0x24000000
20:07:31.135:
20:07:31.135: DMA PAR: 0x50000014
20:07:31.135: DMA MAR: 0x20001F60
20:07:31.135: DMA NDTR: 0x8
20:07:31.135: DMA CR: 0x3A1A
20:07:31.135:
20:07:31.135: ISR before: 0
20:07:31.135: GPIOA LCKR: 0
20:07:31.135: DMA NDTR: 8
20:07:31.135: ISR during: 9
20:07:31.135: panicked at src/main.rs:171:13:
20:07:31.135: DMA did not complete successfully. ISR state: 0b0000000000001001

 

I appreciate the assistance!

1 ACCEPTED SOLUTION

Accepted Solutions

Indeed, the GPIO are not accessable by DMA on a G0 and many others which use a Cortex-M0+ core implementing a single-cycle IO port.

See the reference manual, Figure 1. System architecture.

The (debatable) advantage is faster and more predictable GPIO bit-banging. 

hth

KnarfB

View solution in original post

3 REPLIES 3
nolanhergert
Associate

With a few small additions, I'm able to write a 2000 char string to USART2 with no issues. Copying those additions back to my original code doesn't change its failing behavior.

Indeed, the GPIO are not accessable by DMA on a G0 and many others which use a Cortex-M0+ core implementing a single-cycle IO port.

See the reference manual, Figure 1. System architecture.

The (debatable) advantage is faster and more predictable GPIO bit-banging. 

hth

KnarfB

That is unfortunate for my application, which involves a lot of dumb <but fast> transfers that would benefit from 3X less power draw. Bummer. Thanks KnarfB!