Skip to content

Concepts First

  • DMA (direct memory access): like a moving company — the CPU tells it once "from where, to where, how much", and the copy runs without the CPU.
  • LLI linked list: chains several transfers into one job list, so one start completes multiple segments without CPU involvement.
  • Cache coherency: DMA bypasses the CPU cache; clean/invalidate the cache (bflb_l1c_dcache_*) around transfers or you may read stale data.

Example Overview

This page is based on the dma_normal example in the official Bouffalo SDK (examples/peripherals/dma/dma_normal), which demonstrates memory-to-memory DMA transfer:

  • An LLI linked list mounts 3 transfer jobs at once, each copying 4100 bytes;
  • When a transfer completes, the ISR prints tc done; the main loop waits for 3 interrupts and reports the total time;
  • Finally it verifies the source and destination buffers byte by byte and prints case end when they match.
  • Sibling examples (examples/peripherals/dma/): dma_normal_cycle (cycle mode), dma_reduce_or_add (data reduction/add).

The copy does not occupy the CPU — that is the value of DMA.

Operation Steps

1
Enter the Example Directory

No external wiring is needed for this page. Open a terminal and enter the DMA example directory (prerequisite: set up the environment as in Quick Start (Linux) or Windows):

cd examples/peripherals/dma/dma_normal
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 copies 3 groups of 4100 bytes from source to destination buffers with DMA. You should see: tc done printed 3 times → copy finished with time=xxx us → data check passes → case end.

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_dma_channel_init(dma_ch, config)

Initializes a DMA channel. In struct bflb_dma_channel_config_s:

  • direction: transfer direction, DMA_MEMORY_TO_MEMORY
  • src_addr_inc / dst_addr_inc: address increment, DMA_ADDR_INCREMENT_ENABLE
  • src_burst_count / dst_burst_count: burst length, DMA_BURST_INCR1
  • src_width / dst_width: data width, DMA_DATA_WIDTH_32BIT

Parameters:

  • dma_ch: DMA channel handle (bflb_device_get_by_name("dma0_ch0"))
  • config: pointer to the config struct

Return: 0 on success; negative error code on failure

bflb_dma_channel_irq_attach(dma_ch, isr, NULL)

Registers the DMA completion callback, invoked by the system when the transfer finishes.

Parameters:

  • dma_ch: DMA channel handle
  • isr: callback function, e.g. void isr(void *arg)

Return: 0 on success; negative error code on failure

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

Writes multiple transfer jobs into an LLI linked list and loads it into the channel, so one start completes several transfers in a row.

Parameters:

  • dma_ch: DMA channel handle
  • lli_pool: aligned LLI memory pool, lli[20] in the example
  • pool_size: pool entries, 20
  • transfers: struct bflb_dma_channel_lli_transfer_s array with src_addr, dst_addr, nbytes each
  • count: number of transfers, 3

Return: 0 on success; negative error code on failure

bflb_dma_channel_start(dma_ch)

Starts the DMA channel to execute the loaded transfers.

Parameters:

  • dma_ch: DMA channel handle

Return: 0 on success; negative error code on failure

bflb_l1c_dcache_clean_range / invalidate_range(addr, len)

Clean the data cache of the source/destination buffers before the transfer and invalidate the destination cache after it, so reads always see the latest data written by DMA.

Parameters:

  • addr: buffer start address
  • len: buffer length in bytes

Return: none

Complete Code

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

📜 Click to expand dma_normal/main.c full code
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 is not printed

Make sure the DMA interrupt is registered (bflb_dma_channel_irq_attach) and the channel is started; if you changed the LLI pool size, verify lli[20] is large enough for the transfer count.

Verification fails with Error

Call bflb_l1c_dcache_clean_range before the transfer and bflb_l1c_dcache_invalidate_range after it, otherwise stale data may be read; buffers also need 32-byte alignment (the example uses __attribute((aligned(32)))).

The transfer takes a long time

Memory-to-memory DMA itself is fast; the measured time includes cache operations and interrupt response. You can narrow the bflb_mtimer_get_time_us() timing to just the DMA start-to-complete window.

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