如何使用 DMA 通道读取 UART 外设?
How to read from UART peripheral with a DMA channel?
我一直在想,如何使用 DMA 从 UART 外设读取到更大的队列。
The documentation 确实提供了相当多的信息,但我发现很难弄清楚如何将所有内容联系在一起。
UART 外设能够在数据可用时向 DMA 控制器发送信号。
DMA 通道可以由多种来源触发。
DMA 可以环绕,非常适合循环缓冲区。
我做了 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);
}
}
备注:
此实现不会尝试处理错误。
这个实现不处理缓冲区溢出的情况
已满
这两个问题都可以通过设置中断处理程序来解决,但是,这个解决方案对我来说已经足够好了。
我一直在想,如何使用 DMA 从 UART 外设读取到更大的队列。
The documentation 确实提供了相当多的信息,但我发现很难弄清楚如何将所有内容联系在一起。
UART 外设能够在数据可用时向 DMA 控制器发送信号。
DMA 通道可以由多种来源触发。
DMA 可以环绕,非常适合循环缓冲区。
我做了 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);
}
}
备注:
此实现不会尝试处理错误。
这个实现不处理缓冲区溢出的情况 已满
这两个问题都可以通过设置中断处理程序来解决,但是,这个解决方案对我来说已经足够好了。