Skip to content

Concepts First

  • Flash: non-volatile storage where the firmware lives; this page demonstrates reading/writing a data area.
  • Erase before write: Flash can only change 1→0 when writing; turning 0 back to 1 requires an erase, which works in sectors (4 KB in the example).
  • XIP address: Flash content can be mapped into the memory address space for direct reads; the example reads it back with DMA.
  • Be careful: confirm the target address is outside the firmware/partition/OTA areas, or the program will be corrupted.

Example Overview

This page is based on the flash_dma example in the official Bouffalo SDK (examples/peripherals/flash/flash_dma), which demonstrates internal Flash erase, write, and DMA read:

  • Erases 4 KB at offset 0x10000 and writes 260 bytes of test data (away from the firmware area);
  • Uses a DMA channel with 12 (burst × width) combinations to read the data back from the Flash XIP address and verify byte by byte;
  • Also demonstrates bflb_l1c_dcache cache handling and unaligned reads (offset DMA_FLASH_ADDR_OFFSET = 28).
  • Sibling examples (examples/peripherals/flash/): flash_read_write (basic R/W), flash_xip_read (XIP read), flash_iomode (IO modes), flash_get_image_hash (image hash), flash_secreg / flash_secreg_lock (secure registers), flash_secure_read_write (secure R/W).

Operation Steps

1
Enter the Example Directory

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

cd examples/peripherals/flash/flash_dma
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 erases 4 KB at offset 0x10000, writes 260 bytes of test data, then uses DMA with 12 burst/width combinations to read the Flash back and verify; on success it prints flash dam case success.

Code Execution Flow

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

Note

The example operates on the 0x10000 offset area reserved for testing. In a real project, make sure the target address is not inside the firmware, partition table, or OTA areas, or the program will be corrupted.

APIs Used by the Example

bflb_flash_erase(addr, len)

Erases a Flash region (sector-aligned). The example erases 4096 bytes at 0x10000.

Parameters:

  • addr: relative offset
  • len: length in bytes

Return: 0 on success; negative error code on failure

bflb_flash_write(addr, buf, len)

Writes buffer data into Flash (must erase first).

Parameters:

  • addr: relative offset
  • buf: data buffer
  • len: length in bytes

Return: 0 on success; negative error code on failure

bflb_flash_get_image_offset()

Gets the firmware image offset to convert a relative address into an XIP address: FLASH_XIP_BASE - image_offset + addr.

Parameters: none

Return: image offset

bflb_dma_channel_init / lli_reload / start(...)

Uses DMA to copy data from the Flash XIP address to RAM (memory-to-memory), same as DMA Transfer. The example places the destination buffer in ATTR_NOCACHE_NOINIT_RAM_SECTION (a non-cacheable section), so the CPU reads the latest data directly after DMA writes — no cache invalidation is needed.

Parameters: same as the DMA example

Return: 0 on success; negative error code on failure

Complete Code

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

📜 Click to expand flash_dma/main.c full code
c
#include "bflb_flash.h"
#include "bflb_dma.h"
#include "board.h"


#define FLASH_RW_START_ADDR   0x10000
#define DMA_FLASH_ADDR_OFFSET 28  /* 0 or 28 for unaligned case */
#define DMA_BUFFER_LENGTH     260

static ATTR_NOCACHE_NOINIT_RAM_SECTION uint8_t dst_buffer[DMA_BUFFER_LENGTH];
static uint8_t write_buf[DMA_BUFFER_LENGTH];
static uint8_t dma_tc_flag0 = 0;
struct bflb_device_s *dma0_ch0;
static uint64_t start_time;

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

static uint8_t src_burst[] = {
    DMA_BURST_INCR1,
    DMA_BURST_INCR1,
    DMA_BURST_INCR1,
    DMA_BURST_INCR4,
    DMA_BURST_INCR4,
    DMA_BURST_INCR4,
    DMA_BURST_INCR8,
    DMA_BURST_INCR8,
    DMA_BURST_INCR8,
    DMA_BURST_INCR16,
    DMA_BURST_INCR16,
    DMA_BURST_INCR16,
};

static uint8_t dst_burst[] = {
    DMA_BURST_INCR1,
    DMA_BURST_INCR1,
    DMA_BURST_INCR1,
    DMA_BURST_INCR4,
    DMA_BURST_INCR4,
    DMA_BURST_INCR4,
    DMA_BURST_INCR8,
    DMA_BURST_INCR8,
    DMA_BURST_INCR8,
    DMA_BURST_INCR16,
    DMA_BURST_INCR16,
    DMA_BURST_INCR16,
};

static uint8_t src_width[] = {
    DMA_DATA_WIDTH_8BIT,
    DMA_DATA_WIDTH_16BIT,
    DMA_DATA_WIDTH_32BIT,
    DMA_DATA_WIDTH_8BIT,
    DMA_DATA_WIDTH_16BIT,
    DMA_DATA_WIDTH_32BIT,
    DMA_DATA_WIDTH_8BIT,
    DMA_DATA_WIDTH_16BIT,
    DMA_DATA_WIDTH_32BIT,
    DMA_DATA_WIDTH_8BIT,
    DMA_DATA_WIDTH_16BIT,
    DMA_DATA_WIDTH_32BIT,
};

static uint8_t dst_width[] = {
    DMA_DATA_WIDTH_8BIT,
    DMA_DATA_WIDTH_16BIT,
    DMA_DATA_WIDTH_32BIT,
    DMA_DATA_WIDTH_8BIT,
    DMA_DATA_WIDTH_16BIT,
    DMA_DATA_WIDTH_32BIT,
    DMA_DATA_WIDTH_8BIT,
    DMA_DATA_WIDTH_16BIT,
    DMA_DATA_WIDTH_32BIT,
    DMA_DATA_WIDTH_8BIT,
    DMA_DATA_WIDTH_16BIT,
    DMA_DATA_WIDTH_32BIT,
};

#if defined(BL618DG)
static uint8_t dma_config_supported(struct bflb_device_s *dma_ch,
                                    struct bflb_dma_channel_config_s *config)
{
    uint8_t max_burst_bytes = 16;
    uint8_t src_burst_shift = config->src_burst_count ? config->src_burst_count + 1 : 0;
    uint8_t dst_burst_shift = config->dst_burst_count ? config->dst_burst_count + 1 : 0;
    uint32_t src_burst_bytes = (1U << config->src_width) << src_burst_shift;
    uint32_t dst_burst_bytes = (1U << config->dst_width) << dst_burst_shift;

    if (dma_ch->idx == 1 && dma_ch->sub_idx < 2) {
        max_burst_bytes = 128;
    }

    if (src_burst_bytes > max_burst_bytes || dst_burst_bytes > max_burst_bytes) {
        printf("skip: burst bytes exceed dma%u_ch%u limit\r\n",
               dma_ch->idx, dma_ch->sub_idx);
        return 0;
    }

    return 1;
}
#endif

void dma0_ch0_isr(void *arg)
{
    printf("cost time:%d us\r\n", (uint32_t)(bflb_mtimer_get_time_us() - start_time));
    dma_tc_flag0++;
    printf("tc done\r\n");
}

int main(void)
{
    board_init();
    for (uint16_t i = 0; i < DMA_BUFFER_LENGTH; i++) {
        write_buf[i] = (i & 0xff) + i / 256;
    }

    /* erase flash */
    bflb_flash_erase(FLASH_RW_START_ADDR, 4096);

    /* write flash data */
    bflb_flash_write(FLASH_RW_START_ADDR, write_buf, sizeof(write_buf)); /* FLASH_XIP_BASE - 0x2000 + 0x00010000 */

    dma0_ch0 = bflb_device_get_by_name("dma0_ch0");

    struct bflb_dma_channel_config_s config;

    for (uint8_t i = 0; i < sizeof(src_burst); i++) {
        dma_tc_flag0 = 0;
        memset(dst_buffer, 0, DMA_BUFFER_LENGTH);

        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 = src_burst[i];
        config.dst_burst_count = dst_burst[i];
        config.src_width = src_width[i];
        config.dst_width = dst_width[i];

#if defined(BL618DG)
        if (!dma_config_supported(dma0_ch0, &config)) {
            continue;
        }
#endif

        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[1];

        transfers[0].src_addr = (uint32_t)(FLASH_XIP_BASE - bflb_flash_get_image_offset() + FLASH_RW_START_ADDR + DMA_FLASH_ADDR_OFFSET);
        transfers[0].dst_addr = (uint32_t)dst_buffer;
        transfers[0].nbytes = DMA_BUFFER_LENGTH - DMA_FLASH_ADDR_OFFSET;

        bflb_dma_channel_lli_reload(dma0_ch0, lli, 1, transfers, 1);

        start_time = bflb_mtimer_get_time_us();
        bflb_dma_channel_start(dma0_ch0);
        while (dma_tc_flag0 != 1) {
            bflb_mtimer_delay_ms(1);
        }

        for (uint16_t j = 0; j < (DMA_BUFFER_LENGTH - DMA_FLASH_ADDR_OFFSET); j++) {
            if (dst_buffer[j] != write_buf[DMA_FLASH_ADDR_OFFSET + j]) {
                printf("flash test fail at %d, expect:%d but with %d\r\n", i, write_buf[DMA_FLASH_ADDR_OFFSET + j], dst_buffer[j]);
                while (1) {
                }
            }
        }
    }

    printf("flash dam case success\r\n");
    while (1) {
    }
}

FAQ

flash test fail at xxx

Read-back does not match the written data: do not change the test address/length; the unaligned read (offset 28) depends on the DMA config and cache handling — do not modify DMA_FLASH_ADDR_OFFSET related code casually.

Erase/write fails

Make sure the target address is in a valid, unprotected data area; erase before writing; keep the erase length sector-aligned.

I want to read/write my own data area

Change FLASH_RW_START_ADDR to your partition address (see partition_cfg), and adjust the erase length and buffer size accordingly.

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