首页 > 解决方案 > 如何使用 DMA 通道从 UART 外设读取?

问题描述

我很难弄清楚如何使用 DMA 从 UART 外设读取到更大的队列中。

该文档确实提供了相当多的信息,但我发现很难弄清楚如何将所有内容连接在一起。

标签: cuartdmaraspberry-pi-pico

解决方案


我确实发布了这个问题和这个答案,因为否则我无法找到好的例子。这不是一个最佳解决方案,但展示了所有对我来说难以弄清楚的事情。


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

触发后,DMA 将在其他地方复制事务计数和读/写地址寄存器,然后在进行时修改副本。如果再次触发,它会复制原件。

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

笔记:

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

  • 当缓冲区已满时,此实现不处理溢出。

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


推荐阅读