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
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_speedRun the build command. The Ai-M62 (BL616) and Ai-M61 (BL618) belong to the same series, so both use bl616:
make CHIP=bl616 BOARD=bl616dkConnect 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/ttyUSB0Open 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
#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

