Skip to content

Overview

Flash (in plain words: the board's "hard drive" — data survives power loss) is used for power-off persistence: device configuration, calibration parameters, OTA (over-the-air upgrade, in plain words: updating firmware over the network like a phone system update) flags and more all need to be written to Flash. The Ai-WB2 SDK divides Flash into FW, mfg, media, PSM, KEY, DATA, factory partitions (in plain words: like splitting a hard drive into C drive and D drive, each storing its own data without interference), and recommends operating by partition name to avoid going out of bounds. This tutorial demonstrates partition read/write and raw address read/write two ways, with data consistency checks.

In plain words: Flash is like the board's "hard drive" — photos live on the hard drive, and after turning off and on again the photos are still there; same with the board: write data into Flash, and after power-off restart the data remains. This tutorial demonstrates two ways to "store data": by partition (like putting a file into a designated folder — safe and never messy) and by direct address (like writing directly to a designated spot on the hard drive — flexible, but writing to the wrong spot can damage the system).

This tutorial is based on the official Ai-Thinker SDK (Ai-Thinker-Open/Ai-Thinker-WB2, version release_bl_iot_sdk_1.6.40) example applications/storage/flash; the code can be found directly in your local SDK.

🎯Page GoalThrough writing and reading back 1KB of data in the DATA partition plus raw address read/write, master both the partition-based and raw address-based Flash operations.
🧰Prerequisites① Ai-WB2 development board (Type-C data cable) ② Environment set up per [SDK Installation](../sdk/sdk_intro).
🔗RelatedHigh-level parameter storage: [Best Practices - Parameter Storage](../best/); OTA upgrades: [Wireless - OTA](../wireless/).

Enter the Example Project

Open a terminal and enter the official flash example project directory:

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

Note: cd is the “change directory” command and ~ means your user home directory. This enters the flash example project; all subsequent make commands must run in this directory. If it says No such file or directory, the path is wrong — see the FAQ at the end.

Write the Code

Open flash/main.c. The full code for this step has been moved to the end of this page:

📜 Full Code — in the “Full Code” section below, collapsed by default — click to expand, identical to the official example (applications/storage/flash/flash/main.c).

Code highlights:

Code Purpose
hosal_flash_open("DATA", HOSAL_FLASH_FLAG_ADDR_0) Open the DATA partition by name; without “opening the door” none of the later reads/writes can start
hosal_flash_info_get(p_flash, &part) Fetch partition info; printed to confirm the partition name, address and size are normal
hosal_flash_erase_write(p_flash, &offset, wbuf, 1024) Erase then write 1KB of data; Flash must be erased before writing, otherwise correct data can’t be stored
hosal_flash_read(p_flash, &offset, rbuf, 1024) Read back what was just written; write and read must match for the save to be a success
hosal_flash_raw_write/raw_read(buf, addr, len) Read/write directly by physical address; bypasses the partition table, writing the wrong address can damage firmware

💡 The difference between the two ways: the partition way (recommended) locates by partition name — safe, never out of bounds; the raw address way specifies a physical address directly — you must make sure the address is in a writable partition yourself, writing wrong can damage firmware.

Build the Project

Build in the project directory:

make -j8

Note: make is the “build” command, translating the source code into machine code the board can run; -j8 builds with 8 parallel cores, faster.

On success a firmware build_out/flash.bin is generated (firmware: the program burned into the board after compilation, like the board’s “operating system + your program”).

Flash the Firmware

Keep the board connected via USB, confirm the serial device, and flash:

make flash p=/dev/ttyUSB0 b=921600

Note: make flash is the “flash” command, writing the compiled firmware into the chip (flashing: the process of writing a program into the chip); p=/dev/ttyUSB0 is the serial device — change it to your computer’s actual port (like COM3 on Windows), b=921600 is the flash baud rate (transfer speed).

⏳ During flashing, press and hold the EN button on the board when prompted to enter download mode; wait for the progress bar to complete — that means the flash succeeded. If it keeps waiting or reports the serial port can’t open, see the FAQ at the end.

Run and Verify

After flashing, the board automatically restarts and runs. Open a serial assistant (baud rate 921600) and check the logs:

--------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!

Both read/write methods pass the check, meaning data was successfully written into Flash and read back unchanged.

💡 Power-off verification: disconnect the board’s power and power on again — the data is still in Flash (the example rewrites every boot; you can change it to “read first, write later” to verify power-off persistence).

Expected result: seeing hal flash partition data R/W successful! and hal flash addr R/W successful! means success. If R/W failed! or no partition name DATA! prints, the read/write failed — see the FAQ at the end.


API Summary for This Tutorial

hosal_flash_open(name, flags)

Finds the partition by name and returns a device handle (must open before reading/writing).

Parameters:

  • name: partition name (defined in the partition table), values: "FW" (firmware area), "mfg", "media", "PSM", "KEY", "DATA", "factory" (user data recommended: "media")
  • flags: address mode flag, values: HOSAL_FLASH_FLAG_ADDR_0 (0-address offset) / HOSAL_FLASH_FLAG_ADDR_1 / HOSAL_FLASH_FLAG_BUSADDR (bus address)

Return: device handle (hosal_flash_dev_t pointer) on success; NULL on failure

hosal_flash_info_get(dev, part)

Reads the partition's name, start address, length and other info.

Parameters:

  • dev: device handle returned by hosal_flash_open
  • part: partition_t struct pointer (output), required. Fields: name (partition name), start_addr (start address), length (length)

Return: 0 on success; negative error code on failure

hosal_flash_erase_write(dev, off_set, data, size)

Erases by sector first, then writes (Flash must be erased before writing), positioned by in-partition offset.

Parameters:

  • dev: device handle
  • off_set: in-partition offset (bytes), values: 0~partition length, e.g. 0 (the start)
  • data: pointer to the data to write, required
  • size: byte count to write, values: 1~4096 (4-byte alignment is better)

Return: 0 on success; negative error code on failure

hosal_flash_read(dev, off_set, data, size)

Reads data from the specified in-partition offset.

Parameters:

  • dev: device handle
  • off_set: in-partition offset (bytes)
  • data: receive buffer pointer, required
  • size: byte count to read

Return: 0 on success; negative error code on failure

hosal_flash_raw_write(buf, address, length)

Writes directly by Flash physical address, bypassing the partition table (be careful not to overwrite the firmware area!).

Parameters:

  • buf: pointer to the data to write, required
  • address: Flash physical address (from the partition table), 0x192000 (media partition) in this tutorial
  • length: byte count to write

Return: 0 on success; negative error code on failure

hosal_flash_raw_read(buf, address, length)

Reads data directly by Flash physical address.

Parameters:

  • buf: receive buffer pointer, required
  • address: Flash physical address
  • length: byte count to read

Return: 0 on success; negative error code on failure

hosal_flash_close(dev)

Closes the handle and frees resources (call when no longer reading/writing).

Parameters:

  • dev: device handle

Return: 0 on success; negative error code on failure

blog_info(fmt, ...)

Outputs an INFO-level log (UART0, filtered by level).

Parameters:

  • fmt: format string, same usage as printf, required
  • ...: variadic args matching fmt placeholders, optional

Return: none


Full Code

Below is the complete flash/main.c source, identical to the official example (applications/storage/flash/flash/main.c):

📜 Click to expand the full flash/main.c code
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();
}

FAQ & Troubleshooting

⚠️ Prints no partition name DATA!
Cause: the partition table was modified, or the project's Flash layout differs from the example
Fix: confirm the partition config in proj_config.mk wasn't changed; restore the official default partition table and re-flash

⚠️ System malfunctions or can't boot after a raw address write
Cause: a protected partition like the firmware area (FW) was written, breaking the boot code
Fix: only write data partitions like media/DATA with the raw address way (0x192000 in the example); re-flash the firmware to recover after a mistaken write

⚠️ Read/write failures after repeated erase/write cycles
Cause: Flash erases in 4KB sectors, so frequent partial writes wear out the Flash lifetime
Fix: batch-write business data as much as possible; for frequently-updated parameters use the PSM partition or the easyflash component (official applications/storage/easyflash)

⚠️ malloc fails when writing larger data
Cause: the example's write/read buffers use dynamic memory, and RAM is insufficient
Fix: use a static array (static uint8_t wbuf[1024]) or reduce the per-call read/write length

⚠️ Serial port won't open / /dev/ttyUSB0 not found
Cause: USB-to-serial driver not installed, port occupied, or (on Linux) no access permission
Fix: on Linux confirm the device is recognized with lsusb, run sudo chmod 666 /dev/ttyUSB0 or add your user to the dialout group and retry; on Windows check the COM port in Device Manager and install the CH340/CP210x driver

⚠️ Flashing stuck waiting / chip not found
Cause: download mode wasn't entered, the cable only charges and can't transfer data, or the baud rate is wrong
Fix: press and hold EN during flashing until the progress bar appears; try a data cable; confirm p= port and b=921600 are correct

⚠️ cd reports No such file or directory / no Makefile found
Cause: make ran outside the example project directory, or the SDK install path differs from the tutorial
Fix: cd ~/Ai-Thinker-WB2/applications/storage/flash first, then run make; if ~/Ai-Thinker-WB2 doesn't exist, find the SDK with find ~ -name "Ai-Thinker-WB2"

Self-Check

The serial prints both read/write methods as successful! — Flash operations are verified.

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