21

RP2040(树莓派Pico) DMA

 3 years ago
source link: https://www.taterli.com/7524/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

RP2040(树莓派Pico) DMA

标配的外设,RP2040的DMA是挂在AHB-Lite上的,在M0内核范畴算比较高性能的总线了,支持的触发源有39个,基本上所有外设源都有了.

总共有12个CH,通过仲裁(方法未知)获得总线使用权,支持8B/16B/32B传输,支持中断时候改配置,没有半传输中断(可以用环形缓冲另类实现),有传输完成中断,支持可编程区块传输(即DMA控制DMA),因为内存分区比较散,所以内存间传输还有讲究,但这里不深入讨论内存分布问题,后续再说.

DMA相关函数和方法比较多,但是操作起来其实很简单,总结起来分为几个:

  • 查找闲置通道/标记通道使用/取消标记通道占用
  • 设置源地址/目标地址自增
  • 设置传输请求/设置传输目标
  • 等待传输完成/中断响应
  • 地址包络/字节翻转/嗅探支持

由于DMA通常不会单独工作,所以这里也只能初步看看.

// Data will be copied from src to dst
const char src[] = "Hello, world! (from DMA)";
char dst[count_of(src)];

int main() {
    stdio_init_all();

    // Get a free channel, panic() if there are none
    int chan = dma_claim_unused_channel(true);

    // 8 bit transfers. Both read and write address increment after each
    // transfer (each pointing to a location in src or dst respectively).
    // No DREQ is selected, so the DMA transfers as fast as it can.

    dma_channel_config c = dma_channel_get_default_config(chan);
    channel_config_set_transfer_data_size(&c, DMA_SIZE_8);
    channel_config_set_read_increment(&c, true);
    channel_config_set_write_increment(&c, true);

    dma_channel_configure(
        chan,          // Channel to be configured
        &c,            // The configuration we just created
        dst,           // The initial write address
        src,           // The initial read address
        count_of(src), // Number of transfers; in this case each is 1 byte.
        true           // Start immediately.
    );

    // We could choose to go and do something else whilst the DMA is doing its
    // thing. In this case the processor has nothing else to do, so we just
    // wait for the DMA to finish.J
    dma_channel_wait_for_finish_blocking(chan);

    // The DMA has now copied our text from the transmit buffer (src) to the
    // receive buffer (dst), so we can print it out from there.
    puts(dst);
}

则例的逻辑是申请一个通道,然后设置传输大小,自增,然后立即开始,然后查询标志,类似完成CPU的memcpy,但是他由DMA完成,速度不如memcpy,但是可以释放CPU来干别的事情.

为了达到释放CPU的目的.需要使用中断,我们从hello dma上开始改,也很容易实现DMA中断.

第一步添加IRQ头文件:


#include "hardware/irq.h"

新建处理Handle,并清除标志位,多源中断函数还应该判断标志位.

void dma_handler() {
    // Clear the interrupt request.
    dma_hw->ints0 = 1u << chan;
    // Print dst
    puts(dst);
}

把查询代码改成中断.

// Tell the DMA to raise IRQ line 0 when the channel finishes a block
dma_channel_set_irq0_enabled(chan, true);

// Configure the processor to run dma_handler() when DMA IRQ 0 is asserted
irq_set_exclusive_handler(DMA_IRQ_0, dma_handler);
irq_set_enabled(DMA_IRQ_0, true);

while (true){}

修改后的整体代码:

/**
 * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

// Use the DMA to copy data between two buffers in memory.

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

// Data will be copied from src to dst
const char src[] = "Hello, world! (from DMA)";
char dst[count_of(src)];

int chan = 0;

void dma_handler() {
    // Clear the interrupt request.
    dma_hw->ints0 = 1u << chan;
    // Print dst
    puts(dst);
}

int main() {
    stdio_init_all();

    // Get a free channel, panic() if there are none
    chan = dma_claim_unused_channel(true);

    // 8 bit transfers. Both read and write address increment after each
    // transfer (each pointing to a location in src or dst respectively).
    // No DREQ is selected, so the DMA transfers as fast as it can.

    dma_channel_config c = dma_channel_get_default_config(chan);
    channel_config_set_transfer_data_size(&c, DMA_SIZE_8);
    channel_config_set_read_increment(&c, true);
    channel_config_set_write_increment(&c, true);

    dma_channel_configure(
        chan,          // Channel to be configured
        &c,            // The configuration we just created
        dst,           // The initial write address
        src,           // The initial read address
        count_of(src), // Number of transfers; in this case each is 1 byte.
        true           // Start immediately.
    );

    // Tell the DMA to raise IRQ line 0 when the channel finishes a block
    dma_channel_set_irq0_enabled(chan, true);

    // Configure the processor to run dma_handler() when DMA IRQ 0 is asserted
    irq_set_exclusive_handler(DMA_IRQ_0, dma_handler);
    irq_set_enabled(DMA_IRQ_0, true);

    while (true){}
}

这样就变成中断驱动了.

最后最难的就是控制传输,其实这个其他芯片也有(猜测),因为他是通过修改DMA CTRL寄存器实现的.这里说下,寄存器填充总共有4个模式寄存器,他们意义分别不同.

image-32-1024x246.png

比如像官方例子里面,使用了Alias 3模式,CTRL和WRITE_ADDR是规定的,WRITE地址是串口,但是TRANS_COUNT和READ_ADDR是可变的,所以每次传输TRANS_COUNT和READ_ADDR,传输了READ_ADDR后就会触发真正的DMA传输,当开始之后,整个队列传输完成,才会中断.通过这种方式,可以实现PING-PONG-BUF,即队列永远不NULL,然后数据不断来回填充…

例子上设置Alias 3,从TRANS_COUNT开始设置2个WORD.

dma_channel_configure(
    ctrl_chan,
    &c,
    &dma_hw->ch[data_chan].al3_transfer_count, // Initial write address
    &control_blocks[0],                        // Initial read address
    2,                                         // Halt after each control block
    false                                      // Don't start yet
);

然后最后只需要开始控制CH,就会自动按照自己规划的列表去传输了.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK