概述
Flash(大白话:开发板的"硬盘",断电后数据也不会丢)用于掉电保存数据:设备配置、校准参数、OTA(空中升级,大白话:像手机系统更新一样通过网络升级固件)标志等都需要写入 Flash。Ai-WB2 SDK 将 Flash 划分为 FW、mfg、media、PSM、KEY、DATA、factory 等分区(大白话:像把硬盘分成 C 盘 D 盘,各存各的、互不干扰),推荐通过分区名操作,避免越界。本教程演示 分区读写 与 原始地址读写 两种方式,并校验数据一致性。
用大白话讲:Flash 就像开发板的"硬盘"——手机照片存在硬盘里,关机再开机照片还在;开发板也一样,把数据写进 Flash,断电重启后数据依然在。本教程演示两种"存数据"的方式:按分区存(就像把文件放进指定的文件夹,安全不会乱)和按地址直接存(就像直接往硬盘指定位置写,灵活但写错位置可能损坏系统)。
本教程基于安信可官方 SDK(Ai-Thinker-Open/Ai-Thinker-WB2,版本
release_bl_iot_sdk_1.6.40)的官方示例applications/storage/flash编写,代码可在本地 SDK 中直接找到。
打开终端,进入官方 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)
读取分区的名称、起始地址、长度等信息。
参数:
dev:hosal_flash_open返回的设备句柄part:partition_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;失败返回负值错误码
完整代码
以下为 flash/main.c 完整源码,与官方示例(applications/storage/flash/flash/main.c)完全一致:
📜 点击展开 flash/main.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

