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-30 17:31:25