Skip to content

概述

Flash(大白话:开发板的"硬盘",断电后数据也不会丢)用于掉电保存数据:设备配置、校准参数、OTA(空中升级,大白话:像手机系统更新一样通过网络升级固件)标志等都需要写入 Flash。Ai-WB2 SDK 将 Flash 划分为 FW、mfg、media、PSM、KEY、DATA、factory 等分区(大白话:像把硬盘分成 C 盘 D 盘,各存各的、互不干扰),推荐通过分区名操作,避免越界。本教程演示 分区读写原始地址读写 两种方式,并校验数据一致性。

用大白话讲:Flash 就像开发板的"硬盘"——手机照片存在硬盘里,关机再开机照片还在;开发板也一样,把数据写进 Flash,断电重启后数据依然在。本教程演示两种"存数据"的方式:按分区存(就像把文件放进指定的文件夹,安全不会乱)和按地址直接存(就像直接往硬盘指定位置写,灵活但写错位置可能损坏系统)。

本教程基于安信可官方 SDKAi-Thinker-Open/Ai-Thinker-WB2,版本 release_bl_iot_sdk_1.6.40)的官方示例 applications/storage/flash 编写,代码可在本地 SDK 中直接找到。

🎯本页目标通过 DATA 分区写入并回读 1KB 数据、以及原始地址读写演示,掌握分区方式与原始地址方式两种 Flash 操作。
🧰前置条件① Ai-WB2 开发板一块(Type-C 数据线)② 已按 [SDK 安装](../sdk/sdk_intro) 完成环境搭建。
🔗相关章节参数存储的高级封装见 [最佳实践-参数存储](../best/);OTA 升级见 [无线网络-OTA](../wireless/)。

进入示例工程

打开终端,进入官方 flash 示例工程目录:

cd ~/Ai-Thinker-WB2/applications/storage/flash

说明:cd 是"进入目录"的命令,~ 表示你的用户主目录。这条命令进入 flash 示例工程,后面所有 make 命令都要在这个目录里执行;如果提示 No such file or directory(没有这个目录),说明路径不对,见文末 FAQ。

编写代码

打开 flash/main.c,本步完整代码已移至文末,见:

📜 完整代码 — 位于本页「完整代码」章节,默认折叠,点击展开,与官方示例(applications/storage/flash/flash/main.c)完全一致。

代码要点:

代码 作用
hosal_flash_open("DATA", HOSAL_FLASH_FLAG_ADDR_0) 按分区名打开 DATA 分区;不"开门"后面所有读写都无从下手
hosal_flash_info_get(p_flash, &part) 取回分区信息;打印出来让你确认分区名、地址、大小是否正常
hosal_flash_erase_write(p_flash, &offset, wbuf, 1024) 先擦后写 1KB 数据;Flash 写前必须擦除,否则写不进正确数据
hosal_flash_read(p_flash, &offset, rbuf, 1024) 把刚写的数据读回来;写读一致才算保存成功
hosal_flash_raw_write/rw_read(buf, addr, len) 按物理地址直读直写;不走分区表,写错地址可能破坏固件

💡 两种方式区别:分区方式(推荐)通过分区名定位,安全不越界;原始地址方式直接指定物理地址,需自行确保地址在可写分区内,写错可能破坏固件。

编译工程

在工程目录执行编译:

make -j8

说明:make 是"编译工程"的命令,把源代码翻译成开发板能运行的机器码;-j8 表示用 8 个核心并行编译,更快。

编译成功后生成固件 build_out/flash.bin(固件:编译后烧进开发板的程序,相当于开发板的"操作系统+你的程序")。

烧录固件

开发板保持 USB 连接,确认串口设备号后执行烧录:

make flash p=/dev/ttyUSB0 b=921600

说明:make flash 是"烧录"命令,把编译好的固件下载进芯片(烧录:把程序写进芯片的过程);p=/dev/ttyUSB0 是串口设备号,要换成你电脑上实际的串口(Windows 下形如 COM3),b=921600 是烧录波特率(传输速度)。

⏳ 烧录过程中按提示长按开发板 EN 键进入下载模式,等待进度条完成即烧录成功。若一直卡在等待或报串口打不开,见文末 FAQ。

运行验证

烧录完成后开发板自动重启运行,打开串口助手(波特率 921600)查看日志:

--------flash write/read demo--------
partition name         : DATA
partition start address: 0x001f3000
partition length       : 20480
hal flash partition data R/W successful!
hal flash addr R/W successful!

两种读写方式均校验通过,说明数据成功写入 Flash 并原样读回。

💡 掉电验证:断开开发板电源重新上电,数据仍保存在 Flash 中(示例每次开机都会重写,可自行改为"先读后写"验证掉电保存效果)。

预期结果:看到 hal flash partition data R/W successful!hal flash addr R/W successful! 即成功。若打印 R/W failed!no partition name DATA!,说明读写失败,见文末 FAQ。

代码执行流程

例程从启动到运行的完整流程如下(图中的循环箭头表示反复执行):


本文 API 汇总

hosal_flash_open(name, flags)

按分区名找到分区并返回设备句柄(打开后才能读写)。

参数

  • name:分区名(分区表中定义),可选值:"FW"(固件区)、"mfg""media""PSM""KEY""DATA""factory"(用户数据建议用 "media"
  • flags:地址模式标志,可选值:HOSAL_FLASH_FLAG_ADDR_0(0 地址偏移)/ HOSAL_FLASH_FLAG_ADDR_1 / HOSAL_FLASH_FLAG_BUSADDR(总线地址)

返回值:成功返回设备句柄(hosal_flash_dev_t 指针);失败返回 NULL

hosal_flash_info_get(dev, part)

读取分区的名称、起始地址、长度等信息。

参数

  • devhosal_flash_open 返回的设备句柄
  • partpartition_t 结构体指针(输出),必填。字段:name(分区名)、start_addr(起始地址)、length(长度)

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

hosal_flash_erase_write(dev, off_set, data, size)

先按扇区擦除再写入(Flash 写入前必须擦除),按分区内偏移定位。

参数

  • dev:设备句柄
  • off_set:分区内偏移(字节),可选值:0~分区长度,如 0(起始处)
  • data:待写入数据指针,必填
  • size:写入字节数,可选值:1~4096(对齐 4 字节更佳)

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

hosal_flash_read(dev, off_set, data, size)

从分区指定偏移读取数据。

参数

  • dev:设备句柄
  • off_set:分区内偏移(字节)
  • data:接收缓冲区指针,必填
  • size:读取字节数

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

hosal_flash_raw_write(buf, address, length)

不经过分区表,按 Flash 物理地址直接写入(注意别覆盖固件区!)。

参数

  • buf:待写入数据指针,必填
  • address:Flash 物理地址(查分区表获取),本教程为 0x192000(media 分区)
  • length:写入字节数

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

hosal_flash_raw_read(buf, address, length)

按 Flash 物理地址直接读取数据。

参数

  • buf:接收缓冲区指针,必填
  • address:Flash 物理地址
  • length:读取字节数

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

hosal_flash_close(dev)

关闭句柄释放资源(不再读写时调用)。

参数

  • dev:设备句柄

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

blog_info(fmt, ...)

输出一条 INFO 级日志(UART0,受级别过滤)。

参数

  • fmt:格式化字符串,同 printf 用法,必填
  • ...:变参,可省略

返回值:无


完整代码

以下为 flash/main.c 完整源码,与官方示例(applications/storage/flash/flash/main.c)完全一致:

📜 点击展开 flash/main.c 完整代码
c
/*
 * @Author: Sean Kwok 1026025056@qq.com
 * @Date: 2022-10-09 10:53:46
 * @LastEditors: Sean Kwok 1026025056@qq.com
 * @LastEditTime: 2022-10-31 09:45:55
 * @FilePath: /Ai-Thinker-WB2/applications/storage/flash/flash/main.c
 * @Description: Flash Write/Read Example
 */

#include <stdio.h>
#include <hosal_flash.h>
#include <stdlib.h>
#include <blog.h>

#define DEMO_HOSAL_FLASH_TEST_BLOCK 1024
#define DEMO_HOSAL_FLASH_TEST_ADDR 0x192000
#define DEMO_HOSAL_FLASH_TEST_LEN  1024
#define DEMO_HOSAL_FLASH_TEST_BLOCK 1024

/**
 * hal flash R/W test demo
 */
void flash_partition_rw(void)
{
    int i;
    uint32_t offset = 0;
    uint8_t *p_wbuf, *p_rbuf;
    hosal_flash_dev_t *p_flash;
    hosal_logic_partition_t part;

    /* flash partition open use address 0 */
    p_flash = hosal_flash_open("DATA", HOSAL_FLASH_FLAG_ADDR_0);

    /* Open the partition table and use the bus physical address of flash. */
    // p_flash = hosal_flash_open("DATA", HOSAL_FLASH_FLAG_ADDR_0 | HOSAL_FLASH_FLAG_BUSADDR);


    if (p_flash == NULL) {
        blog_info("no partition name DATA!");
        return;
    }
    
    /* get flash partition info */
    hosal_flash_info_get(p_flash, &part);
    blog_info("partition name         : %s", part.partition_description);
    blog_info("partition start address: 0x%08x", part.partition_start_addr);
    blog_info("partition length       : %d", part.partition_length);

    p_wbuf = malloc(DEMO_HOSAL_FLASH_TEST_BLOCK);
    if (p_wbuf == NULL) {
        blog_info("no memory!");
        return;
    }

    p_rbuf = malloc(DEMO_HOSAL_FLASH_TEST_BLOCK);
    if (p_rbuf == NULL) {
        blog_info("no memory!");
        return;
    }

    for (i = 0; i < DEMO_HOSAL_FLASH_TEST_BLOCK; i++) {
        p_wbuf[i] = i & 0xff;
    }

    /* erase flash partition and write data */
    offset = 0;
    hosal_flash_erase_write(p_flash, &offset, p_wbuf, DEMO_HOSAL_FLASH_TEST_BLOCK);
    
    /* read flash partition data */
    offset = 0;
    hosal_flash_read(p_flash, &offset, p_rbuf, DEMO_HOSAL_FLASH_TEST_BLOCK);
    
    /* check flash read data */
    if (memcmp(p_rbuf, p_wbuf, DEMO_HOSAL_FLASH_TEST_BLOCK) != 0) {
        blog_info("hal flash R/W failed!");
    } else {
        blog_info("hal flash partition data R/W successful!");
    }
    
    /* close the flash partition and free buf */
    hosal_flash_close(p_flash);
    free(p_wbuf);
    free(p_rbuf);
}

/**
 * hal flash R/W test demo
 */

void flash_raw_addr_rw(void)
{
    int i;
    uint8_t *p_wbuf, *p_rbuf;

    p_wbuf = malloc(DEMO_HOSAL_FLASH_TEST_LEN);
    if (p_wbuf == NULL) {
        blog_info("no memory!");
        return;
    }

    p_rbuf = malloc(DEMO_HOSAL_FLASH_TEST_LEN);
    if (p_rbuf == NULL) {
        blog_info("no memory!");
        return;
    }

    for (i = 0; i < DEMO_HOSAL_FLASH_TEST_LEN; i++) {
        p_wbuf[i] = i & 0xff;
    }
    hosal_flash_raw_write(p_wbuf, DEMO_HOSAL_FLASH_TEST_ADDR, DEMO_HOSAL_FLASH_TEST_LEN);
    hosal_flash_raw_read(p_rbuf, DEMO_HOSAL_FLASH_TEST_ADDR, DEMO_HOSAL_FLASH_TEST_LEN);    
    /* check flash read data */
    if (memcmp(p_rbuf, p_wbuf, DEMO_HOSAL_FLASH_TEST_LEN) != 0) {
        blog_info("hal flash addr R/W failed!");
    } else {
        blog_info("hal flash addr R/W successful!");
    }
    free(p_wbuf);
    free(p_rbuf);
}


void main(void)
{
    blog_info("--------flash write/read demo--------");
    flash_partition_rw();
    flash_raw_addr_rw();
}

常见问题与踩坑提示

⚠️ 打印 no partition name DATA!
原因:分区表被修改或工程 Flash 布局与示例不一致
解决:确认 proj_config.mk 的分区配置未被改动;恢复官方默认分区表后重新烧录

⚠️ 原始地址写入后系统异常或无法启动
原因:写入了固件区(FW)等保护分区,破坏了启动代码
解决:原始地址方式只写 media/DATA 等数据分区(示例 0x192000);误写后重新烧录固件恢复

⚠️ 反复擦写后数据读写失败
原因:Flash 擦除以 4KB 扇区为单位,频繁局部写入会损耗 Flash 寿命
解决:业务数据尽量批量写入;频繁更新的参数建议使用 PSM 分区或 easyflash 组件(官方 applications/storage/easyflash

⚠️ 写入较大数据时 malloc 失败
原因:示例中 write/read 缓冲区使用动态内存,RAM 不足
解决:改用静态数组(static uint8_t wbuf[1024])或缩小单次读写长度

⚠️ 串口打不开 / 找不到 /dev/ttyUSB0
原因:USB 转串口驱动未装、串口被占用,Linux 下还可能是没有访问权限
解决:Linux 用 lsusb 确认设备被识别,sudo chmod 666 /dev/ttyUSB0 或把用户加入 dialout 组后重试;Windows 在设备管理器查看 COM 口并安装 CH340/CP210x 驱动

⚠️ 烧录一直卡在等待 / 提示找不到芯片
原因:未进入下载模式、数据线只能充电不能传数据,或波特率不对
解决:烧录时长按 EN 键直到进度条出现;换一根能传数据的数据线;确认 p= 串口号与 b=921600 无误

⚠️ cd 报错 No such file or directory / 找不到 Makefile
原因:没进入示例工程目录就执行了 make,或 SDK 安装路径与教程不同
解决:先 cd ~/Ai-Thinker-WB2/applications/storage/flash 再执行 make;若 ~/Ai-Thinker-WB2 不存在,用 find ~ -name "Ai-Thinker-WB2" 找到 SDK 实际位置

运行自检

串口打印两种读写方式均 successful!,即 Flash 操作验证通过。

遇到问题?

如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

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