概念先知道
- Flash:掉电不丢数据的存储芯片,程序就放在里面;本页演示的是对数据区的读写。
- 先擦后写:Flash 只能把 1 变 0(写),把 0 变回 1 必须先擦除,而且擦除按扇区(例程 4KB)进行。
- XIP 地址:Flash 内容可以直接映射到内存地址访问(读),例程用 DMA 从 XIP 地址读回数据。
- 注意安全:写 Flash 前确认目标地址不在固件/分区表/OTA 区域,否则会破坏程序。
例程功能简介
本页对应博流官方 SDK 的 flash_dma 例程(examples/peripherals/flash/flash_dma),演示内部 Flash 的擦除、写入与 DMA 读取:
- 在偏移
0x10000处擦除 4KB,写入 260 字节测试数据(注意避开固件所在区域); - 用 DMA 通道以 12 种(burst × 位宽)组合从 Flash XIP 地址读回数据,逐字节与写入值校验;
- 同时演示了
bflb_l1c_dcache缓存处理与未对齐地址读取(偏移DMA_FLASH_ADDR_OFFSET = 28)。 - 同族例程(
examples/peripherals/flash/):flash_read_write(基础读写)、flash_xip_read(XIP 读取)、flash_iomode(IO 模式)、flash_get_image_hash(镜像哈希)、flash_secreg/flash_secreg_lock(安全寄存器)、flash_secure_read_write(安全读写)。
操作步骤
本页不需要额外接线。在终端进入 SDK 的 Flash 例程目录(前提:已按快速开始(Linux)或Windows搭建好环境):
cd examples/peripherals/flash/flash_dma执行编译命令。Ai-M62(BL616)与 Ai-M61(BL618)同属一个系列,统一填写引脚最少的 bl616 即可:
make CHIP=bl616 BOARD=bl616dk用 USB 线连接开发板,按住 BOOT 键(Ai-M61-32S-Kit 为 IO2)不放、短按 EN/RST 进入下载模式,然后执行烧录(把串口号换成实际值):
make flash CHIP=bl616 COMX=/dev/ttyUSB0打开串口助手(波特率 2000000)。例程先擦除偏移 0x10000 处的 4KB,写入 260 字节测试数据,再用 DMA 以 12 种突发/位宽组合从 Flash 读回校验;全部通过后打印 flash dam case success。
代码执行流程
例程从启动到运行的完整流程如下(图中的循环箭头表示反复执行):
注意
例程操作的是 0x10000 偏移的 Flash 区域,这是官方示例预留的测试区。实际项目写 Flash 前务必确认目标地址不在固件、分区表与 OTA 区域内,否则会破坏程序。
例程调用的 API 介绍
bflb_flash_erase(addr, len)
擦除指定 Flash 区域(按扇区擦除,长度需对齐扇区)。例程擦除 0x10000 处 4096 字节。
参数:
addr:相对偏移地址len:擦除长度(字节)
返回值:成功返回 0;失败返回负值错误码
bflb_flash_write(addr, buf, len)
把缓冲区数据写入 Flash(写入前必须先擦除)。
参数:
addr:相对偏移地址buf:数据缓冲区len:写入长度(字节)
返回值:成功返回 0;失败返回负值错误码
bflb_flash_get_image_offset()
获取固件镜像在 Flash 中的偏移,用于把相对地址换算成 XIP 映射地址:FLASH_XIP_BASE - image_offset + addr。
参数:无
返回值:镜像偏移值
完整代码
以下为 flash_dma/main.c 完整源码,与官方示例(examples/peripherals/flash/flash_dma)一致;点灯引脚已适配 Ai-M61/62-32S-Kit 板载 RGB 灯,默认折叠,点击展开:
📜 点击展开 flash_dma/main.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
读回数据与写入不一致:确认没有修改例程的测试地址/长度;未对齐读取场景(偏移 28)依赖 DMA 配置与缓存处理,不要随意改动 DMA_FLASH_ADDR_OFFSET 相关代码。
擦除/写入失败
确认目标地址在合法数据区且未写保护;写入前必须先擦除;擦除长度按扇区对齐。
想读写自己的数据区
修改 FLASH_RW_START_ADDR 为你自己的分区地址(参考分区表 partition_cfg),并同步调整擦除长度与缓冲区大小。
遇到问题?
如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

