简体   繁体   中英

How to read from UART peripheral with a DMA channel?

I struggled to figure out, how to read from a UART peripheral into a larger queue with DMA.

The documentation does provide quite a bit of information, but I found it difficult to figure out how to connect everything together.

  • The UART peripheral is capable to signal a DMA controller when data is avaliabe.

  • DMA channels can be triggered by a variety of sources.

  • The DMA can wrap around which is perfect for circular buffers.

I did post the question and this answer, because I was not able to find good examples otherwise. This is not an optimal solution, but demonstrates all the things that were difficult to figure out for me.


Originally, I though that the UART peripheral would "trigger" the DMA channel and that it would then start copying. However, the DMA channel needs to be triggered prior to any signals and will then suspend the copy process unless it is signaled.

When triggered the DMA will copy the transaction count and read/write address registers elsewhere and then modify the copies when it makes progress. If it is triggered again, it copies the originals.

The DMA no longer knows the start address which means that when wrapping is enabled, the buffer needs to be aligned such that the DMA can use bitmasks to figure out where it needs to wrap.


The following snippet should demonstrate how a DMA channel can be configured to read from UART:

#include <string.h>

#include <pico/time.h>
#include <pico/printf.h>
#include <pico/stdio_uart.h>

#include <hardware/dma.h>
#include <hardware/uart.h>

// The buffer size needs to be a power of two and alignment must be the same
__attribute__((aligned(32)))
static char buffer[32];

static void configure_dma(int channel) {
    dma_channel_config config = dma_channel_get_default_config(channel);
    channel_config_set_transfer_data_size(&config, DMA_SIZE_8);

    // The read address is the address of the UART data register which is constant
    channel_config_set_read_increment(&config, false);

    // Write into a ringbuffer with '2^5=32' elements
    channel_config_set_write_increment(&config, true);
    channel_config_set_ring(&config, true, 5);

    // The UART signals when data is avaliable
    channel_config_set_dreq(&config, DREQ_UART0_RX);

    // Transmit '2^32 - 1' symbols, this should suffice for any practical case,
    // otherwise, the channel could be triggered again
    dma_channel_configure(
        channel,
        &config,
        buffer,
        &uart0_hw->dr,
        UINT32_MAX,
        true);
}

static void configure_uart() {
    // The SDK seems to configure sane values for baudrate, etc.
    stdio_uart_init();

    // On my system there is one junk byte on boot
    uart_getc(uart0);
}

int main() {
    const uint32_t channel = 0;

    configure_uart();
    configure_dma(channel);

    memset(buffer, '.', sizeof(buffer));

    for (;;) {
        // Print out the contents of the buffer
        printf("buffer: '%.*s' transfer_count=%u\n",
                (int)sizeof(buffer), buffer,
                dma_channel_hw_addr(channel)->transfer_count);

        sleep_ms(1000);
    }
}

Notes:

  • This implementation does not make any attempt to handle errors.

  • This implementation does not deal with overflow when the buffer is full.

Both these issues could be resolved by setting up interrupt handlers, however, this solution was good enough for me.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM