Skip to content

概念先知道

  • 内存带宽:单位时间能搬多少数据(MB/s),决定大块数据搬运的性能上限。
  • OCRAM vs PSRAM:OCRAM 是片上高速内存,PSRAM 是外挂内存,速度差异就是本页要测的。
  • CPU 直拷 vs DMA:小数据量 CPU 直拷(带缓存)常常更快,大数据量 DMA 更优——本页给出对比数据。

例程功能简介

本页对应博流官方 SDK 的 ram_speed 例程(examples/peripherals/ram_speed),演示内存带宽测试

  • 搬运 32KB 数据,分别用 CPU memcpyDMA 测量三组路径:OCRAM→OCRAM、OCRAM→PSRAM、PSRAM→PSRAM;
  • bflb_mtimer_get_time_us() 计时,结果以 MB/s 打印;
  • 可直观对比 CPU 搬运与 DMA 搬运、片上内存与外挂 PSRAM 的速度差异。

操作步骤

1
进入例程目录

本页不需要额外接线(需开发板带 PSRAM 才有完整结果)。在终端进入 SDK 的例程目录(前提:已按快速开始(Linux)Windows搭建好环境):

cd examples/peripherals/ram_speed
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)。例程用 memcpy 与 DMA 各测 3 组速度(OCRAM→OCRAM、OCRAM→PSRAM、PSRAM→PSRAM),打印形如 ocram2ocram speed:xxx MB/s 的结果。

代码执行流程

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

例程调用的 API 介绍

bflb_mtimer_get_time_us()

获取微秒时间戳,用于计算搬运耗时。

参数:无

返回值:微秒时间戳

bflb_dma_channel_init / lli_reload / start / isbusy(...)

DMA 内存到内存搬运,bflb_dma_channel_isbusy 轮询等待传输完成(与 DMA 传输 相同用法)。

参数:同 DMA 例程

返回值:成功返回 0;失败返回负值错误码;isbusy 返回忙状态

ATTR_NOINIT_PSRAM_SECTION(宏)

把缓冲区放到 PSRAM(不初始化),配合 __attribute((aligned(32))) 对齐,用于测试 PSRAM 带宽。

参数:无

返回值:无

完整代码

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

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

#define K_NUM             32
#define DMA_BUFFER_LENGTH (K_NUM * 1024)

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

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

struct bflb_dma_channel_lli_pool_s lli[20];

void memcpy_test(void)
{
    uint64_t start_time = 0;

    printf("memcpy speed test\r\n");

    start_time = bflb_mtimer_get_time_us();
    memcpy(dst1_buffer, src1_buffer, DMA_BUFFER_LENGTH);
    printf("ocram2ocram speed:%d MB/s\r\n", K_NUM * 1000000 / 1024 / ((uint32_t)(bflb_mtimer_get_time_us() - start_time)));

    start_time = bflb_mtimer_get_time_us();
    memcpy(dst2_buffer, src1_buffer, DMA_BUFFER_LENGTH);
    printf("ocram2psram speed:%d MB/s\r\n", K_NUM * 1000000 / 1024 / ((uint32_t)(bflb_mtimer_get_time_us() - start_time)));

    start_time = bflb_mtimer_get_time_us();
    memcpy(dst2_buffer, src2_buffer, DMA_BUFFER_LENGTH);
    printf("psram2psram speed:%d MB/s\r\n", K_NUM * 1000000 / 1024 / ((uint32_t)(bflb_mtimer_get_time_us() - start_time)));
}

void dma_test()
{
    struct bflb_device_s *dma_chx;

    uint64_t start_time = 0;

    printf("dma speed test\r\n");

    dma_chx = 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_INCR4;
    config.dst_burst_count = DMA_BURST_INCR4;
    config.src_width = DMA_DATA_WIDTH_32BIT;
    config.dst_width = DMA_DATA_WIDTH_32BIT;
    bflb_dma_channel_init(dma_chx, &config);

    struct bflb_dma_channel_lli_transfer_s transfers;

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

    start_time = bflb_mtimer_get_time_us();
    bflb_dma_channel_lli_reload(dma_chx, lli, 20, &transfers, 1);
    bflb_dma_channel_start(dma_chx);
    while (bflb_dma_channel_isbusy(dma_chx)) {}
    printf("ocram2ocram speed:%d MB/s\r\n", K_NUM * 1000000 / 1024 / ((uint32_t)(bflb_mtimer_get_time_us() - start_time)));

    transfers.src_addr = (uint32_t)src1_buffer;
    transfers.dst_addr = (uint32_t)dst2_buffer;
    transfers.nbytes = DMA_BUFFER_LENGTH;

    start_time = bflb_mtimer_get_time_us();
    bflb_dma_channel_lli_reload(dma_chx, lli, 20, &transfers, 1);
    bflb_dma_channel_start(dma_chx);
    while (bflb_dma_channel_isbusy(dma_chx)) {}
    printf("ocram2psram speed:%d MB/s\r\n", K_NUM * 1000000 / 1024 / ((uint32_t)(bflb_mtimer_get_time_us() - start_time)));

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

    start_time = bflb_mtimer_get_time_us();
    bflb_dma_channel_lli_reload(dma_chx, lli, 20, &transfers, 1);
    bflb_dma_channel_start(dma_chx);
    while (bflb_dma_channel_isbusy(dma_chx)) {}
    printf("psram2psram speed:%d MB/s\r\n", K_NUM * 1000000 / 1024 / ((uint32_t)(bflb_mtimer_get_time_us() - start_time)));
}

int main(void)
{
    board_init();

    memset(src1_buffer, 0x55, DMA_BUFFER_LENGTH);
    memset(src2_buffer, 0x55, DMA_BUFFER_LENGTH);
    memset(dst1_buffer, 0xaa, DMA_BUFFER_LENGTH);
    memset(dst2_buffer, 0xaa, DMA_BUFFER_LENGTH);

    memcpy_test();

    dma_test();

    while (1) {
    }
}

FAQ

PSRAM 相关速度打印为 0 或异常

开发板必须带 PSRAM,且内存分区里为 PSRAM 缓冲区预留了空间(见 defconfig);修改 K_NUM(KB)可改变搬运量。

memcpy 比 DMA 还快

小数据量下 CPU 直拷(含缓存)常常优于 DMA 建链开销,属正常现象;搬运量越大 DMA 优势越明显。结果受时钟/缓存状态影响,仅供参考。

想测别的内存区域

修改 src1_buffer/src2_buffer/dst1_buffer/dst2_buffer 的段属性(OCRAM/PSRAM)即可组合出不同路径。

遇到问题?

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

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