25

RP2040(树莓派Pico) DMA

 4 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.
neoserver,ios ssh client

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,就会自动按照自己规划的列表去传输了.


Recommend

  • 13
    • www.taterli.com 4 years ago
    • Cache

    RP2040(树莓派Pico) ADC

    RP2040(树莓派Pico) ADC由 TaterLi2021年1月29日2021年1月29日 Pico模块有3个ADC通道,固定在4个引脚上,总共5个通道,其中一个内部通道...

  • 12
    • www.taterli.com 4 years ago
    • Cache

    RP2040(树莓派Pico) 时钟系统

    RP2040(树莓派Pico) 时钟系统由 TaterLi2021年1月29日2021年1月29日 RP2040的时钟结构相当的简单: ROSC(环形振荡器),频...

  • 6
    • www.taterli.com 4 years ago
    • Cache

    RP2040(树莓派Pico) First Examples

    RP2040(树莓派Pico) First Examples 学习一个新的片子,无外乎就是掌握他的外设,知道他的外设限制在哪里,功能在哪里,然后就能针对性的应用在项目上,我就按照官方例子都走一遍,结合手册来食用.

  • 11

    RP2040(树莓派Pico) PIO – 外设概述 RP2040中有2个相同的PIO块,每个PIO块都有专用的连接到总线结构,GPIO和中断控制器.单个PIO块的示意图如图所示. PIO是一种通用的硬件接口,它可以支持多种IO标准.包括实现以下功...

  • 10
    • www.taterli.com 4 years ago
    • Cache

    RP2040(树莓派Pico) 看门狗

    RP2040(树莓派Pico) 看门狗 这个芯片内置看门狗,但是只是最基础的看门狗,不过RP2040的很多外设都是很基础的配置,按照手册来说,他可以定时范围从你绝对喂不上狗(1个WDG周期)到16秒左右,但是根据SDK限制,你最短是50ms,最长是8秒,...

  • 12
    • www.taterli.com 4 years ago
    • Cache

    RP2040(树莓派Pico) Coremark

    RP2040(树莓派Pico) Coremark由 TaterLi2021年2月4日2021年2月4日 这是第一个我自己写的工程,主要也很简单,毕竟printf有,时间函数有,...

  • 13

    RP2040(树莓派Pico) PIO – 实例分析&编程由 TaterLi2021年2月4日2021年2月4日 这次拿来开刀的是WS2812,具体代码可见 => https:...

  • 9

    RP2040(树莓派Pico) PIO – 指令集描述 之前已经说过,PIO支持的多条指令. JMP命令 => JMP (条件) [地址],条件可以是如下数值: 000 = 无条件(默认)!X = X 寄存器为0X– = X 寄...

  • 5
    • www.taterli.com 4 years ago
    • Cache

    RP2040(树莓派Pico) USB

    RP2040(树莓派Pico) USB RP2040的C SDK中的USB基本上是用TinyUSB框架实现的,关于这个框架,建议到框架的GitHub页面去查看,因为都是USB,实际上都很通用,不妨参照下我之前写的<使用STM32CubeMX编写USB复合设备>这篇文章....

  • 3
    • www.taterli.com 4 years ago
    • Cache

    RP2040(树莓派Pico) 普通定时器&RTC

    RP2040(树莓派Pico) 普通定时器&RTC由 TaterLi2021年2月3日2021年2月3日 定时器功能也很基础,而且只有一个,主要功能如下:...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK