Skip to content

概念先知道

  • DMA(直接内存访问):像“搬家公司”——CPU 只需交代一次“从哪搬到哪、搬多少”,搬运过程完全不占用 CPU。
  • LLI 链表:把多段搬运任务串成清单,一次启动连续完成,不用 CPU 一段段地交代。
  • 缓存一致性:DMA 直接读写内存,可能绕过 CPU 缓存;搬运前后要清理/失效缓存(bflb_l1c_dcache_*),否则可能读到旧数据。

例程功能简介

本页对应博流官方 SDK 的 dma_normal 例程(examples/peripherals/dma/dma_normal),演示 DMA 的内存到内存搬运:

  • 通过 LLI 链表一次性挂载 3 组传输任务,每组搬运 4100 字节;
  • 搬运完成后进入中断回调打印 tc done,主循环等待 3 次中断后统计总耗时;
  • 最后逐字节校验源与目的缓冲区数据一致性,全部相同则打印 case end
  • 同族例程examples/peripherals/dma/):dma_normal_cycle(循环模式)、dma_reduce_or_add(数据累加/规约)。

搬运过程不占用 CPU,期间 CPU 可以继续做其他事情——这就是 DMA 的价值。

操作步骤

1
进入例程目录

本页不需要额外接线。在终端进入 SDK 的 DMA 例程目录(前提:已按快速开始(Linux)Windows搭建好环境):

cd examples/peripherals/dma/dma_normal
2
编译工程

执行编译命令。Ai-M62(BL616)与 Ai-M61(BL618)同属一个系列,统一填写引脚最少的 bl616 即可:

make CHIP=bl616 BOARD=bl616dk
3
烧录固件

用 USB 线连接开发板,按住 BOOT 键(Ai-M61-32S-Kit 为 IO2)不放、短按 EN/RST 进入下载模式,然后执行烧录(把串口号换成实际值):

make flash CHIP=bl616 COMX=/dev/ttyUSB0
4
运行验证

打开串口助手(波特率 2000000)。例程用 DMA 把 3 组各 4100 字节数据从源缓冲区搬运到目的缓冲区,应看到:tc done 打印 3 次 → copy finished with time=xxx us → 数据校验通过 → case end

代码执行流程

例程从启动到运行的完整流程如下(图中的循环箭头表示反复执行):

例程调用的 API 介绍

bflb_dma_channel_init(dma_ch, config)

初始化 DMA 通道。struct bflb_dma_channel_config_s 中:

  • direction:传输方向,DMA_MEMORY_TO_MEMORY(内存到内存)
  • src_addr_inc / dst_addr_inc:源/目的地址自增,DMA_ADDR_INCREMENT_ENABLE
  • src_burst_count / dst_burst_count:突发长度,DMA_BURST_INCR1
  • src_width / dst_width:数据宽度,DMA_DATA_WIDTH_32BIT

参数

  • dma_ch:DMA 通道句柄(bflb_device_get_by_name("dma0_ch0")
  • config:配置结构体指针

返回值:成功返回 0;失败返回负值错误码

bflb_dma_channel_irq_attach(dma_ch, isr, NULL)

注册 DMA 完成中断回调,传输完成后系统调用该回调。

参数

  • dma_ch:DMA 通道句柄
  • isr:回调函数,形如 void isr(void *arg)

返回值:成功返回 0;失败返回负值错误码

bflb_dma_channel_lli_reload(dma_ch, lli_pool, pool_size, transfers, count)

把多组传输任务写入 LLI 链表并装载到通道,一次启动连续完成多段搬运。

参数

  • dma_ch:DMA 通道句柄
  • lli_pool:LLI 链表内存池(需对齐且足够大),例程 lli[20]
  • pool_size:链表池项数,例程 20
  • transfersstruct bflb_dma_channel_lli_transfer_s 数组,每项含 src_addrdst_addrnbytes
  • count:传输段数,例程 3

返回值:成功返回 0;失败返回负值错误码

bflb_dma_channel_start(dma_ch)

启动 DMA 通道,开始执行已装载的传输任务。

参数

  • dma_ch:DMA 通道句柄

返回值:成功返回 0;失败返回负值错误码

bflb_l1c_dcache_clean_range / invalidate_range(addr, len)

搬运前清理源/目的缓冲区的数据缓存,搬运后失效目的缓冲区缓存,保证读到的是 DMA 写入的最新数据。

参数

  • addr:缓冲区起始地址
  • len:缓冲区长度(字节)

返回值:无

完整代码

以下为 dma_normal/main.c 完整源码,与官方示例(examples/peripherals/dma/dma_normal)一致;点灯引脚已适配 Ai-M61/62-32S-Kit 板载 RGB 灯,默认折叠,点击展开:

📜 点击展开 dma_normal/main.c 完整代码
c
#include "bflb_mtimer.h"
#include "bflb_dma.h"
#include "bflb_l1c.h"
#include "board.h"

#define DMA_BUFFER_LENGTH 4100

static __attribute((aligned(32))) uint8_t src1_buffer[DMA_BUFFER_LENGTH];
static __attribute((aligned(32))) uint8_t src2_buffer[DMA_BUFFER_LENGTH];
static __attribute((aligned(32))) uint8_t src3_buffer[DMA_BUFFER_LENGTH];

static __attribute((aligned(32))) uint8_t dst1_buffer[DMA_BUFFER_LENGTH];
static __attribute((aligned(32))) uint8_t dst2_buffer[DMA_BUFFER_LENGTH];
static __attribute((aligned(32))) uint8_t dst3_buffer[DMA_BUFFER_LENGTH];

static volatile uint8_t dma_tc_flag0 = 0;

struct bflb_device_s *dma0_ch0;

struct bflb_dma_channel_lli_pool_s lli[20]; /* max trasnfer size 4064 * 20 */

void dma0_ch0_isr(void *arg)
{
    dma_tc_flag0++;
    printf("tc done\r\n");
}

void sram_init()
{
    uint32_t i;

    for (i = 0; i < DMA_BUFFER_LENGTH; i++) {
        src1_buffer[i] = i & 0xff;
        src2_buffer[i] = (i * 0x07) & 0xff;
        src3_buffer[i] = (i * 0x0b) & 0xff;
    }

    memset(dst1_buffer, 0, DMA_BUFFER_LENGTH);
    memset(dst2_buffer, 0, DMA_BUFFER_LENGTH);
    memset(dst3_buffer, 0, DMA_BUFFER_LENGTH);

    bflb_l1c_dcache_clean_range(src1_buffer, sizeof(src1_buffer));
    bflb_l1c_dcache_clean_range(src2_buffer, sizeof(src2_buffer));
    bflb_l1c_dcache_clean_range(src3_buffer, sizeof(src3_buffer));
    bflb_l1c_dcache_clean_range(dst1_buffer, sizeof(dst1_buffer));
    bflb_l1c_dcache_clean_range(dst2_buffer, sizeof(dst2_buffer));
    bflb_l1c_dcache_clean_range(dst3_buffer, sizeof(dst3_buffer));
}

int main(void)
{
    uint32_t i;
    uint64_t start_time;

    board_init();

    sram_init();

    printf("dma memory case:\r\n");
    dma0_ch0 = bflb_device_get_by_name("dma0_ch0");

    struct bflb_dma_channel_config_s config;

    config.direction = DMA_MEMORY_TO_MEMORY;
    config.src_req = 0;
    config.dst_req = 0;
    config.src_addr_inc = DMA_ADDR_INCREMENT_ENABLE;
    config.dst_addr_inc = DMA_ADDR_INCREMENT_ENABLE;
    config.src_burst_count = DMA_BURST_INCR1;
    config.dst_burst_count = DMA_BURST_INCR1;
    config.src_width = DMA_DATA_WIDTH_32BIT;
    config.dst_width = DMA_DATA_WIDTH_32BIT;
    bflb_dma_channel_init(dma0_ch0, &config);

    bflb_dma_channel_irq_attach(dma0_ch0, dma0_ch0_isr, NULL);

    struct bflb_dma_channel_lli_transfer_s transfers[3];

    transfers[0].src_addr = (uint32_t)src1_buffer;
    transfers[0].dst_addr = (uint32_t)dst1_buffer;
    transfers[0].nbytes = DMA_BUFFER_LENGTH;

    transfers[1].src_addr = (uint32_t)src2_buffer;
    transfers[1].dst_addr = (uint32_t)dst2_buffer;
    transfers[1].nbytes = DMA_BUFFER_LENGTH;

    transfers[2].src_addr = (uint32_t)src3_buffer;
    transfers[2].dst_addr = (uint32_t)dst3_buffer;
    transfers[2].nbytes = DMA_BUFFER_LENGTH;

    start_time = bflb_mtimer_get_time_us();

    bflb_dma_channel_lli_reload(dma0_ch0, lli, 20, transfers, 3);
    bflb_dma_channel_start(dma0_ch0);
    while (dma_tc_flag0 != 3) {
    }
    printf("copy finished with time=%dus\r\n", (int)(bflb_mtimer_get_time_us() - start_time));

    /* Check data */
    bflb_l1c_dcache_invalidate_range(dst1_buffer, sizeof(dst1_buffer));
    bflb_l1c_dcache_invalidate_range(dst2_buffer, sizeof(dst2_buffer));
    bflb_l1c_dcache_invalidate_range(dst3_buffer, sizeof(dst3_buffer));
    for (i = 0; i < DMA_BUFFER_LENGTH; i++) {
        if (src1_buffer[i] != dst1_buffer[i]) {
            printf("Error! index: %ld, src1: 0x%02x, dst1: 0x%02x\r\n", i, src1_buffer[i], dst1_buffer[i]);
        }
        if (src2_buffer[i] != dst2_buffer[i]) {
            printf("Error! index: %ld, src2: 0x%02x, dst2: 0x%02x\r\n", i, src2_buffer[i], dst2_buffer[i]);
        }
        if (src3_buffer[i] != dst3_buffer[i]) {
            printf("Error! index: %ld, src3: 0x%02x, dst3: 0x%02x\r\n", i, src3_buffer[i], dst3_buffer[i]);
        }
    }

    printf("case end\r\n");
    while (1) {
    }
}

FAQ

没有打印 tc done

确认 DMA 中断已注册(bflb_dma_channel_irq_attach)且通道已启动;如果修改过 LLI 池大小,确认 lli[20] 足够容纳传输段数。

校验失败,打印 Error

搬运前要调用 bflb_l1c_dcache_clean_range 清理缓存、搬运后调用 bflb_l1c_dcache_invalidate_range 失效缓存,否则可能读到旧数据;缓冲区还需要按 32 字节对齐(例程用 __attribute((aligned(32))))。

搬运耗时很长

内存到内存搬运本身很快,耗时会包含缓存操作与中断响应。可把 bflb_mtimer_get_time_us() 计时范围缩小到仅统计 DMA 启动到中断完成。

遇到问题?

如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

Released under the MIT License. Build Time 2026-09-11 14:52:23