Skip to content

概念先知道

  • 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(安全读写)。

操作步骤

1
进入例程目录

本页不需要额外接线。在终端进入 SDK 的 Flash 例程目录(前提:已按快速开始(Linux)Windows搭建好环境):

cd examples/peripherals/flash/flash_dma
2
编译工程

执行编译命令。Ai-M62(BL616)与 Ai-M61(BL618)同属一个系列,统一填写引脚最少的 bl616 即可:

make CHIP=bl616 BOARD=bl616dk
3
烧录固件

用 USB 线连接开发板,按住 BOOT 键(Ai-M61-32S-Kit 为 IO2)不放、短按 EN/RST 进入下载模式,然后执行烧录(把串口号换成实际值):

make flash CHIP=bl616 COMX=/dev/ttyUSB0
4
运行验证

打开串口助手(波特率 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

参数:无

返回值:镜像偏移值

bflb_dma_channel_init / lli_reload / start(...)

用 DMA 把 Flash XIP 地址的数据搬运到内存(内存到内存),用法与 DMA 传输 一致。例程的目的缓冲区使用 ATTR_NOCACHE_NOINIT_RAM_SECTION(非缓存段),DMA 写入后 CPU 直接读到的就是最新数据,因此无需缓存失效操作。

参数:同 DMA 例程(通道配置、LLI 链表、启动)

返回值:成功返回 0;失败返回负值错误码

完整代码

以下为 flash_dma/main.c 完整源码,与官方示例(examples/peripherals/flash/flash_dma)一致;点灯引脚已适配 Ai-M61/62-32S-Kit 板载 RGB 灯,默认折叠,点击展开:

📜 点击展开 flash_dma/main.c 完整代码
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

Released under the MIT License. Build Time 2026-09-11 14:52:23