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
0x10000and 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_dcachecache handling and unaligned reads (offsetDMA_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
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_dmaRun 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 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 offsetlen: 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 offsetbuf: data bufferlen: 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
#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

