RP2040 DMA Ring (地址环回) – 详解

/ 0评 / 1

不得不吐槽下,资料是真的少,手册也有些地方说的摸棱两可,其中我最不解的是DMA中Ring的设置,压根也没说Ring是什么,估计是环形缓冲区的某个控制.

手册里没有明确说是个什么东西,也没图示,只有短短一句话.

Size of address wrap region. If 0, don’t wrap. For values n>0, only the lower n bits of the address will change. This wraps the address on a (1 << n) byte boundary, facilitating access to naturally-aligned ring buffers.

Ring sizes between 2 and 32768 bytes are possible. This can apply to either read or write addresses, based on value of RING_SEL.

0x0 → RING_NONE

地址包覆区域的大小。如果是0,则不包裹。对于n>0的值,只有地址的低n位会改变。这 在一个(1<n)字节的边界上包裹地址,有利于 访问自然对齐的环形缓冲区。

环的大小在2到32768字节之间是可能的。这 可以适用于读或写地址,基于 RING_SEL的值。

0x0 → RING_NONE

函数分为对写地址环绕或对读地址环绕.

Set address wrapping parameters in a channel configuration object.

Size of address wrap region. If 0, don’t wrap. For values n > 0, only the lower n bits of the address will change. This wraps the address on a (1 << n) byte boundary, facilitating access to naturally-aligned ring buffers. Ring sizes between 2 and 32768 bytes are possible (size_bits from 1 - 15)

0x0 -> No wrapping.

Parameters
c	Pointer to channel configuration object
write	True to apply to write addresses, false to apply to read addresses
size_bits	0 to disable wrapping. Otherwise the size in bits of the changing part of the address. Effectively wraps the address on a (1 << size_bits) byte boundary.

还是用官方例子说明.

代码地址:https://github.com/raspberrypi/pico-examples/blob/master/dma/control_blocks/control_blocks.c

    dma_channel_config c = dma_channel_get_default_config(ctrl_chan);
    channel_config_set_transfer_data_size(&c, DMA_SIZE_32);
    channel_config_set_read_increment(&c, true);
    channel_config_set_write_increment(&c, true);
    channel_config_set_ring(&c, true, 3); // 1 << 3 byte boundary on write ptr
 
    dma_channel_configure(
        ctrl_chan,
        &c,
        &dma_hw->ch[data_chan].al3_transfer_count, // Initial write address
        &control_blocks,                        // Initial read address
        2,                                         // Halt after each control block
        false                                      // Don't start yet
    );

其中data_chan = 1,那么al3_transfer_count是0x50000064,写入两个32位后,即在他基础上移动8个字节,所以ring的wrap边界是8字节,再下一个就会环回到0x50000064,继续每一次搬运+4字节(根据设置).

但是,这里面有坑,下面来分析一下这个功能,这里做一个实验,删除了一些闲杂的信息,先做第一步实现.

#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/dma.h"
#include "hardware/structs/uart.h"

const char word0[] = "ABCDEFGH";

const uint32_t len = count_of(word0) - 1;

int main() {
    stdio_init_all();

    uart_init(uart0, 9600);
    gpio_set_function(0 /* UART_TX_PIN */, GPIO_FUNC_UART);
    gpio_set_function(1 /* UART_RX_PIN */, GPIO_FUNC_UART);
    
    puts("DMA control block example:");

    int ctrl_chan = dma_claim_unused_channel(true);
    int data_chan = dma_claim_unused_channel(true);

    dma_channel_config c = dma_channel_get_default_config(ctrl_chan);
    channel_config_set_transfer_data_size(&c, DMA_SIZE_32);
    channel_config_set_read_increment(&c, false); // 因为就一个地址永远地写到同一个位置,目的是循环触发同一个DMA.
    channel_config_set_write_increment(&c, false);
 
    dma_channel_configure(
        ctrl_chan,
        &c,
        &dma_hw->ch[data_chan].al1_transfer_count_trig,
        &len,
        1,
        false
    );

    c = dma_channel_get_default_config(data_chan);
    channel_config_set_transfer_data_size(&c, DMA_SIZE_8);
    channel_config_set_dreq(&c, uart_get_dreq(uart_default, true));
    channel_config_set_chain_to(&c, ctrl_chan); // 当数据DMA传输完成就回到控制DMA传输,控制DMA又因为写Trigger,会发起数据DMA传输,循环往复.
    channel_config_set_irq_quiet(&c, true);
    channel_config_set_ring(&c, false, 3); // 环回边界是8个字节(刚好buf就是8个字节.)
    channel_config_set_read_increment(&c, true); // 读地址是递增的,但是到ring wrap时候会返回.
    channel_config_set_write_increment(&c, false);

    dma_channel_configure(
        data_chan,
        &c,
        &uart_get_hw(uart_default)->dr,
        &word0,
        0,
        false           
    );

    dma_start_channel_mask(1u << ctrl_chan); // 开始传输

     while (!(dma_hw->intr & 1u << data_chan)) 
        tight_loop_contents();
    dma_hw->ints0 = 1u << data_chan;

    puts("DMA finished.");
}

然后运行后发现只循环输出.ABCD,循环输出这个,为什么呢,分析一下,首先看word0地址.

0x1000698c的ring wrap 3bit就是从范围0x10006988-0x1000698f,没法理解的看计算器.

很不幸,实际输出结果就是0x46,0x00,0x00,0x00,'A','B','C','D',而0x00不可见,所以串口上也看不到.这样能理解ring wrap了吧,那么如何修正这个问题.只需要对齐word0就行.

const char word0[] __attribute__((aligned(8))) = "ABCDEFGH";

但是说实话,用这个来实现循环发送有一个局限性,buf必须2^n个大小,如果不是,那又怎么办呢.我想,级联多个DMA控制块应该是可以实现的,ctrl1控制ctrl2,让ctrl2每次从同一个buf里取数据,ctrl2实际控制data dma,这样等于每次重新填写data dma,自然一切问题都不是问题了,至于效率嘛,释放了CPU还不够嘛?

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注