Skip to content

Concepts First

  • Memory bandwidth: how much data can be moved per second (MB/s) — the upper bound for large data copies.
  • OCRAM vs PSRAM: OCRAM is fast on-chip memory; PSRAM is external memory. The speed gap is exactly what this page measures.
  • CPU copy vs DMA: for small sizes, CPU copies (with cache) are often faster; DMA wins with larger data — this page shows the comparison.

Example Overview

This page is based on the ram_speed example in the official Bouffalo SDK (examples/peripherals/ram_speed), which demonstrates memory bandwidth testing:

  • Copies 32 KB of data over three paths — OCRAM→OCRAM, OCRAM→PSRAM, PSRAM→PSRAM — using both CPU memcpy and DMA;
  • Times each copy with bflb_mtimer_get_time_us() and prints MB/s;
  • Lets you compare CPU vs DMA copy and on-chip vs external PSRAM bandwidth.

Operation Steps

1
Enter the Example Directory

No external wiring is needed (a board with PSRAM gives the complete results). Open a terminal and enter the example directory (prerequisite: set up the environment as in Quick Start (Linux) or Windows):

cd examples/peripherals/ram_speed
2
Build the Project

Run the build command. The Ai-M62 (BL616) and Ai-M61 (BL618) belong to the same series, so both use bl616:

make CHIP=bl616 BOARD=bl616dk
3
Flash the Firmware

Connect the board with a USB cable, hold the BOOT button (IO2 on the Ai-M61-32S-Kit), briefly press EN/RST to enter download mode, then flash (replace the serial port with the one on your computer):

make flash CHIP=bl616 COMX=/dev/ttyUSB0
4
Run and Verify

Open a serial tool (baud rate 2000000). The example measures 3 paths with both memcpy and DMA (OCRAM→OCRAM, OCRAM→PSRAM, PSRAM→PSRAM) and prints results like ocram2ocram speed:xxx MB/s.

Code Execution Flow

The complete execution flow from startup to running is shown below (loop arrows mean repeated execution):

APIs Used by the Example

bflb_mtimer_get_time_us()

Gets a microsecond timestamp for measuring copy time.

Parameters: none

Return: microsecond timestamp

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

Memory-to-memory DMA copy; bflb_dma_channel_isbusy polls until the transfer finishes (same usage as DMA Transfer).

Parameters: same as the DMA example

Return: 0 on success; isbusy returns busy state

ATTR_NOINIT_PSRAM_SECTION(macro)

Places a buffer in PSRAM (uninitialized), combined with __attribute((aligned(32))), to test PSRAM bandwidth.

Parameters: none

Return: none

Complete Code

The complete source below matches the effect described on this page. It is based on the official example (examples/peripherals/ram_speed); the LED pins are adapted to the Ai-M61/62-32S-Kit onboard RGB LED. Collapsed by default, click to expand:

📜 Click to expand ram_speed/main.c full code
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 speeds print 0 or look wrong

The board must have PSRAM and the memory layout must reserve space for the PSRAM buffers (see defconfig); change K_NUM (KB) to adjust the copy size.

memcpy is faster than DMA

For small sizes, CPU copying (with cache) can beat DMA link overhead — this is normal; DMA wins as the size grows. Results depend on clocks/cache state and are indicative only.

I want to test other memory regions

Change the section attributes (OCRAM/PSRAM) of src1/src2/dst1/dst2_buffer to combine different paths.

Have questions?

For any other questions, visit the unified Q&A and discussion board: Ai-Thinker Discussions

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