实战四:参数掉电保存与多核运行——Flash 分区、NVS 存储与双核 SMP

在最后一节综合实战中,我们将解决两个高阶工程问题:

  1. 参数持久化:在 qemu_cortex_a53 上通过 app.overlay 挂载 zephyr,sim-flash 模拟 Flash 控制器与 storage_partition 分区,使用 NVS 非易失键值存储 保存启动次数与报警阈值。

  2. 多核对称多处理(SMP):利用 qemu_cortex_a53 内置的双核 Cortex-A53 拓扑,切换目标为 qemu_cortex_a53/qemu_cortex_a53/smp 启动从核 CPU #1。

跨版本兼容提示(Zephyr 3.7 LTS vs 4.x latest)

正如 从旧版教程迁移 所记录,NVS 头文件路径与 Flash Map 分区宏在 3.7 LTS 与 4.1+ 之间进行了规范演进。通过 <zephyr/version.h> 的 ZEPHYR_VERSION_CODE 宏,可以写出同时兼容两个版本的工程代码:

功能项

Zephyr 3.7 LTS

Zephyr 4.1+ / latest

NVS 头文件

#include <zephyr/fs/nvs.h>

#include <zephyr/kvss/nvs.h>

获取分区设备

FIXED_PARTITION_DEVICE(storage_partition)

PARTITION_DEVICE(storage_partition)

获取分区偏移

FIXED_PARTITION_OFFSET(storage_partition)

PARTITION_OFFSET(storage_partition)

Step 1:编写 app.overlay 与 prj.conf

创建 smart_node/app.overlay,在 /soc 下挂载模拟 Flash 控制器并划分 16 KiB 的 storage_partition:

/ {
    soc {
        sim_flash_controller: sim_flash_controller@50000000 {
            compatible = "zephyr,sim-flash";
            reg = <0x0 0x50000000 0x0 0x10000>;
            #address-cells = <1>;
            #size-cells = <1>;
            erase-value = <0xff>;

            flash_sim0: flash_sim@0 {
                compatible = "soc-nv-flash";
                reg = <0x00000000 0x10000>;
                erase-block-size = <4096>;
                write-block-size = <4>;

                partitions {
                    compatible = "fixed-partitions";
                    #address-cells = <1>;
                    #size-cells = <1>;

                    storage_partition: partition@0 {
                        label = "storage";
                        reg = <0x00000000 0x00004000>;
                    };
                };
            };
        };
    };
};

创建 smart_node/prj.conf:

CONFIG_LOG=y
CONFIG_SHELL=y
CONFIG_FLASH=y
CONFIG_FLASH_PAGE_LAYOUT=y
CONFIG_FLASH_SIMULATOR=y
CONFIG_FLASH_MAP=y
CONFIG_NVS=y

Step 2:编写 src/main.c 实现 NVS 挂载与读写验证

#include <zephyr/kernel.h>
#include <zephyr/version.h>
#include <zephyr/device.h>
#include <zephyr/drivers/flash.h>
#include <zephyr/storage/flash_map.h>
#if ZEPHYR_VERSION_CODE >= ZEPHYR_VERSION(4, 1, 0)
#include <zephyr/kvss/nvs.h>
#define STORAGE_DEV PARTITION_DEVICE(storage_partition)
#define STORAGE_OFF PARTITION_OFFSET(storage_partition)
#else
#include <zephyr/fs/nvs.h>
#define STORAGE_DEV FIXED_PARTITION_DEVICE(storage_partition)
#define STORAGE_OFF FIXED_PARTITION_OFFSET(storage_partition)
#endif
#include <zephyr/logging/log.h>
#include <zephyr/shell/shell.h>
#include <stdlib.h>

LOG_MODULE_REGISTER(smart_node_capstone, LOG_LEVEL_INF);

#define NVS_KEY_BOOT_COUNT     1
#define NVS_KEY_TEMP_THRESHOLD 2

static struct nvs_fs g_nvs;
static int32_t g_temp_threshold = 32;

static int storage_init(void)
{
    struct flash_pages_info info;
    int rc;

    g_nvs.flash_device = STORAGE_DEV;
    if (!device_is_ready(g_nvs.flash_device)) {
        LOG_ERR("Simulated Flash device not ready");
        return -ENODEV;
    }

    g_nvs.offset = STORAGE_OFF;
    rc = flash_get_page_info_by_offs(g_nvs.flash_device, g_nvs.offset, &info);
    if (rc != 0) {
        return rc;
    }

    g_nvs.sector_size = info.size;
    g_nvs.sector_count = 3U;

    rc = nvs_mount(&g_nvs);
    if (rc != 0) {
        LOG_ERR("nvs_mount failed: %d", rc);
        return rc;
    }

    uint32_t boot_cnt = 0;
    if (nvs_read(&g_nvs, NVS_KEY_BOOT_COUNT, &boot_cnt, sizeof(boot_cnt)) > 0) {
        boot_cnt++;
    } else {
        boot_cnt = 1;
    }
    (void)nvs_write(&g_nvs, NVS_KEY_BOOT_COUNT, &boot_cnt, sizeof(boot_cnt));

    if (nvs_read(&g_nvs, NVS_KEY_TEMP_THRESHOLD, &g_temp_threshold,
                 sizeof(g_temp_threshold)) <= 0) {
        (void)nvs_write(&g_nvs, NVS_KEY_TEMP_THRESHOLD, &g_temp_threshold,
                        sizeof(g_temp_threshold));
    }

    LOG_INF("NVS mounted (sector=%u B): boot_cnt=%u, saved_threshold=%d C",
            g_nvs.sector_size, boot_cnt, g_temp_threshold);
    return 0;
}

static int cmd_node_save(const struct shell *sh, size_t argc, char **argv)
{
    ARG_UNUSED(argc);
    int32_t new_th = (int32_t)strtol(argv[1], NULL, 10);
    g_temp_threshold = new_th;
    ssize_t written = nvs_write(&g_nvs, NVS_KEY_TEMP_THRESHOLD,
                                &g_temp_threshold, sizeof(g_temp_threshold));
    int32_t verify_th = -1;
    (void)nvs_read(&g_nvs, NVS_KEY_TEMP_THRESHOLD, &verify_th, sizeof(verify_th));
    shell_print(sh, "Saved threshold=%d C to NVS (written=%zd B, readback=%d C)",
                new_th, written, verify_th);
    return 0;
}

SHELL_STATIC_SUBCMD_SET_CREATE(sub_node,
    SHELL_CMD_ARG(save, NULL, "Save threshold to NVS: node save <C>", cmd_node_save, 2, 0),
    SHELL_SUBCMD_SET_END
);
SHELL_CMD_REGISTER(node, &sub_node, "Smart Node NVS & Capstone commands", NULL);

int main(void)
{
    LOG_INF("=== Smart Node Capstone on %s (CPU #%u, MP_MAX_NUM_CPUS=%d) ===",
            CONFIG_BOARD, arch_curr_cpu()->id, CONFIG_MP_MAX_NUM_CPUS);

    storage_init();
    return 0;
}

Step 3:以双核 SMP 目标构建并验证

使用 qemu_cortex_a53/qemu_cortex_a53/smp 目标构建并启动:

west build -p always -b qemu_cortex_a53/qemu_cortex_a53/smp -d build_step4 smart_node
west build -d build_step4 -t run

实测终端输出如下(Secondary CPU core 1 (MPID:0x1) is up 表明双核已激活,node save 42 成功写入 NVS 并读回验证):

*** Booting Zephyr OS build 321e39a45766 ***
Secondary CPU core 1 (MPID:0x1) is up
[00:00:00.000,000] <inf> smart_node_capstone: === Smart Node Capstone on qemu_cortex_a53 (CPU #0, MP_MAX_NUM_CPUS=2) ===
[00:00:00.000,000] <inf> fs_nvs: 3 Sectors of 4096 bytes
[00:00:00.000,000] <inf> fs_nvs: alloc wra: 0, fe8
[00:00:00.000,000] <inf> fs_nvs: data wra: 0, 0
[00:00:00.000,000] <inf> smart_node_capstone: NVS mounted (sector=%u B): boot_cnt=1, saved_threshold=32 C
uart:~$ node save 42
Saved threshold=42 C to NVS (written=4 B, readback=42 C)

相关参考