如何使用 DMA 通道读取 UART 外设?

How to read from UART peripheral with a DMA channel?

我一直在想,如何使用 DMA 从 UART 外设读取到更大的队列。

The documentation 确实提供了相当多的信息,但我发现很难弄清楚如何将所有内容联系在一起。

我做了 post 这个问题和这个答案,因为否则我找不到很好的例子。这不是最佳解决方案,但展示了所有我难以理解的事情。


最初,我认为 UART 外围设备会“触发”DMA 通道,然后它会开始复制。然而,DMA 通道需要在任何信号之前被触发,然后将暂停复制过程,除非它被发出信号。

触发时,DMA 会将事务计数和 read/write 地址寄存器复制到别处,然后在取得进展时修改副本。如果再次触发,则复制原件。

DMA 不再知道起始地址,这意味着当启用回绕时,缓冲区需要对齐,以便 DMA 可以使用位掩码找出需要回绕的位置。


以下代码片段演示了如何将 DMA 通道配置为从 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);
    }
}

备注:

  • 此实现不会尝试处理错误。

  • 这个实现不处理缓冲区溢出的情况 已满

这两个问题都可以通过设置中断处理程序来解决,但是,这个解决方案对我来说已经足够好了。