embassy-rs / embassy

Modern embedded framework, using Rust and async.
https://embassy.dev
Apache License 2.0
4.86k stars 660 forks source link

Add support for Ringbuffers on gpdma #2059

Open tyler-gilbert opened 9 months ago

tyler-gilbert commented 9 months ago

The DMA on newer devices like the STM32U5 have a GPDMA hardware block. This is distinct from the classic DMA found on, say, STM32F4 devices. The classic DMA has built-in support for circular reads and writes. GPDMA hardware doesn't have built-in support for circular DMA is capable of circular DMA using it's linked-list design structure.

The simplest way to have circular reads/writes on GPDMA is to provide a single linked-list item that resets the write destination for DMA reads and the read source for DMA writes. Considering reads, the LLR is set to update only the source address to point to the start of the buffer.

It looks something like this to set up:

    if options.circular {
        STATE.circular_address[channel.index()] = mem_addr as u32;
        let lli1 = &STATE.circular_address[channel.index()] as *const u32 as u32;
        if lli1 & 0b11 != 0 {
            panic!("circular address must be 4-byte aligned");
        }
        ch.llr().write(|w| {
            if options.circular {
                w.set_usa(true); //update only the source address
                // lower 16 bites of the address of STATE.circular_address[channel.index()]
                w.set_la((lli1 >> 2usize) & 0x3fff);
            }
        });
        ch.lbar().write(|w| {
            if options.circular {
                // upper 16 bits of the address of STATE.circular_address[channel.index()]
                w.set_lba(lli1 >> 16usize);
            }
        });
    } else {
        ch.llr().write(|_| 0);
    }

The GPDMA will need to store a 32-bit memory address in static memory for each channel (STM32U5 has 15-channels, so 60 bytes of RAM): STATE.circular_address[channel.index()]. The lower 2 bits must be zero (requires 4-byte alignment) are always zero. Bits 3 to 15 are stored use w.set_la((lli1 >> 2usize) & 0x3fff) while the upper 16-bits are stored using w.set_lba(lli1 >> 16usize).

I still need to do some testing to make sure this approach will work but this seems like a good approach to match the circular transfers of classic DMA. Fully supporting linked-list could maybe come later as a cargo feature.

Comments and suggestions are welcome. If I can get this working, I will open a PR.

xoviat commented 9 months ago

linked-list dma is on the todo list but has not yet been implemented. the goal is to match the current public api for readable and writable ringbuffer.

Comments and suggestions are welcome. If I can get this working, I will open a PR.

if you can implement a readable/writable ringbuffer for gpdma, it would be a welcome contribution.

tyler-gilbert commented 7 months ago

Work in progress:

https://github.com/tyler-gilbert/embassy/tree/issue-2059-ringbuffers-on-gpdma

This branch is working with the SAI both read and write. I still need to test with a speaker and amplifier but this is almost ready.

tyler-gilbert commented 6 months ago

@xoviat PR is awaiting review here: https://github.com/embassy-rs/embassy/pull/2302

Dillonmcewan commented 2 months ago

I just started running into this issue. I'm switching from a STM32F4 board to a STM32U5 board. I had previously implemented UART comms using the RingBufferedUartRx struct, but it's now failing to compile due to that struct missing on the U5. Seems like I could switch to the BufferedUartRx, but it would be nice to keep the code as similar as possible between chip types. Any idea when this might get merged?

tyler-gilbert commented 2 months ago

@Dillonmcewan I can work on this over the next week. I have merged several prerequisite changes. I think I am down to just merging the circular buffer. I have been testing on I2S. I will let you know before I merge so that you can test on the UART.

Dillonmcewan commented 2 months ago

Thanks @tyler-gilbert! Happy to help with testing when ready