esp32 基于 ESP-IDF 框架实现数据的存储和读取

在 ESP32 中实现掉电不丢失的数据存储,最常用的方案是 NVS(Non-Volatile Storage,非易失性存储)。NVS 基于键值对(Key-Value)模型,底层使用闪存(Flash)存储,支持字符串、整数、浮点数等常见数据类型,适合存储配置信息、用户参数等小数据量场景。

一、步骤说明

  1. 步骤 1:配置 NVS 分区
    ESP32 的闪存需要预先划分 NVS 专用分区。默认情况下,ESP-IDF 会自动分配一个名为 nvs 的分区(大小约 0x6000,即 24KB
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  2. 初始化 NVS 分区
    app_main 中初始化 NVS 分区,为存储做准备。

  3. 写入数据到 NVS
    使用 nvs_set_xxx 系列函数写入键值对数据。

  4. 提交更改
    调用 nvs_commit 确保数据写入 Flash。

  5. 读取数据
    使用 nvs_get_xxx 系列函数读取已存储的数据。

  6. 错误处理
    检查每个操作的返回值,处理可能的错误。

步骤 1:配置 NVS 分区

ESP32 的闪存需要预先划分 NVS 专用分区。默认情况下,ESP-IDF 会自动分配一个名为 nvs 的分区(大小约 0x6000,即 24KB)。如果需要更大空间或自定义分区,需手动配置分区表:

  1. 在项目根目录创建 partitions.csv 文件,内容示例:

    # Name      Type  SubType  Offset   Size     Flags
    nvs         data  nvs      0x9000   0x6000   # 默认 NVS 分区(24KB)
    # 如果需要更大空间,可调整 Size(如 0x10000 表示 64KB)
    
  2. sdkconfig 中指定分区表路径(通过 idf.py menuconfig 进入配置):

    • 搜索 Partition TableCustom partition table CSV → 选择 partitions.csv

步骤 2:初始化 NVS

在使用 NVS 前,需要初始化闪存驱动并挂载 NVS 分区:

#include "nvs.h"
#include "nvs_flash.h"
#include "esp_log.h"

static const char* TAG = "NVS_Demo";

// 初始化 NVS
esp_err_t nvs_init() 
{
    // 初始化 NVS 闪存(如果分区被损坏,尝试擦除并重新初始化)
    esp_err_t err = nvs_flash_init();
    if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) 
    {
        // 分区表版本冲突或空间不足,擦除后重新初始化
        ESP_ERROR_CHECK(nvs_flash_erase());
        err = nvs_flash_init();
    }
    return err;
}

步骤 3、4:写入数据到 NVS

使用 nvs_set_* 系列函数写入数据(支持 int8_t/int32_t/uint32_t/float/string 等类型),完成后需调用 nvs_commit() 提交更改。

步骤 5:从 NVS 读取数据

使用 nvs_get_* 系列函数读取数据,需提前定义变量存储结果,并检查返回的错误码。

二、实例代码:NVS 数据读写

代码1:

示例代码
#include <stdio.h>
#include "nvs_flash.h"
#include "nvs.h"

void app_main(void)
{
    // 初始化 NVS
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        // 如果 NVS 分区被截断或版本不匹配,擦除并重新初始化
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    // 打开命名空间(类似"文件夹")
    nvs_handle_t nvs_handle;
    ret = nvs_open("storage", NVS_READWRITE, &nvs_handle);
    if (ret != ESP_OK) {
        printf("Error opening NVS: %s\n", esp_err_to_name(ret));
        return;
    }

    // 写入数据
    int32_t value = 42;
    ret = nvs_set_i32(nvs_handle, "my_value", value);
    if (ret != ESP_OK) {
        printf("Failed to write value: %s\n", esp_err_to_name(ret));
    }

    // 写入字符串
    const char* str_data = "Hello, NVS!";
    ret = nvs_set_str(nvs_handle, "my_str", str_data);
    if (ret != ESP_OK) {
        printf("Failed to write string: %s\n", esp_err_to_name(ret));
    }

    // 提交更改到 Flash
    ret = nvs_commit(nvs_handle);
    if (ret != ESP_OK) {
        printf("Commit failed: %s\n", esp_err_to_name(ret));
    }

    // 读取整数
    int32_t read_value = 0;
    ret = nvs_get_i32(nvs_handle, "my_value", &read_value);
    switch (ret) {
        case ESP_OK:
            printf("Read value: %d\n", read_value);
            break;
        case ESP_ERR_NVS_NOT_FOUND:
            printf("Value not set yet\n");
            break;
        default:
            printf("Error reading value: %s\n", esp_err_to_name(ret));
    }

    // 读取字符串
    size_t required_size;
    ret = nvs_get_str(nvs_handle, "my_str", NULL, &required_size);
    if (ret == ESP_OK) {
        char* read_str = malloc(required_size);
        ret = nvs_get_str(nvs_handle, "my_str", read_str, &required_size);
        if (ret == ESP_OK) {
            printf("Read string: %s\n", read_str);
        } else {
            printf("Error reading string: %s\n", esp_err_to_name(ret));
        }
        free(read_str);
    } else {
        printf("String not found: %s\n", esp_err_to_name(ret));
    }

    // 关闭 NVS 句柄
    nvs_close(nvs_handle);
}
关键点解释
  1. NVS 初始化

    • nvs_flash_init() 初始化 NVS 分区。
    • 如果检测到分区损坏或版本问题,需先擦除 (nvs_flash_erase())。
  2. 命名空间

    • 使用 nvs_open 打开一个命名空间(类似文件夹),所有操作在此空间内进行。
  3. 数据类型

    • 支持整数 (i8/u8/i16/u16/i32/u32/i64/u64)、字符串、二进制 Blob。
    • 示例中演示了 i32str 的读写。
  4. 字符串读取

    • 需要两次调用 nvs_get_str:第一次获取长度,第二次读取实际数据。
  5. 错误处理

    • 检查每个函数的返回值 (ESP_OK 表示成功)。
    • 使用 esp_err_to_name() 将错误码转换为可读信息。

代码2:
演示了如何初始化 NVS、写入/读取整数、字符串和浮点数,并在 ESP32 重启后验证数据持久性。


#include <stdio.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "esp_log.h"
#include "string.h"

static const char* TAG = "NVS_Demo";
static const char* NVS_NAMESPACE = "my_storage";  // NVS 命名空间(可自定义)

// 初始化 NVS(包含错误处理)
esp_err_t nvs_init() 
{
    esp_err_t err = nvs_flash_init();
    if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) 
    {
        ESP_LOGW(TAG, "NVS 分区需要擦除,重新初始化...");
        ESP_ERROR_CHECK(nvs_flash_erase());
        err = nvs_flash_init();
    }
    return err;
}

// 写入数据到 NVS
void nvs_write_demo() 
{
    nvs_handle_t my_handle;
    esp_err_t err;

    // 打开命名空间(读/写模式)
    err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &my_handle);
    if (err != ESP_OK) 
    {
        ESP_LOGE(TAG, "打开命名空间失败: %s", esp_err_to_name(err));
        return;
    }

    // 写入整数
    err = nvs_set_i32(my_handle, "int_key", 132);
    if (err != ESP_OK) ESP_LOGE(TAG, "写入整数失败: %s", esp_err_to_name(err));

    // 写入字符串
    const char* str_value = "Hello, NVS.hi!";
    err = nvs_set_str(my_handle, "str_key", str_value);
    if (err != ESP_OK) ESP_LOGE(TAG, "写入字符串失败: %s", esp_err_to_name(err));

    // 写入浮点数(NVS 不直接支持 float,需转换为 uint32_t 存储)
    float float_value = 1.23456f;
    uint32_t float_as_uint = *(uint32_t*)&float_value;  // 内存级转换
    err = nvs_set_u32(my_handle, "float_key", float_as_uint);
    if (err != ESP_OK) ESP_LOGE(TAG, "写入浮点数失败: %s", esp_err_to_name(err));

    // 提交更改(必须调用,否则数据不会保存到闪存)
    err = nvs_commit(my_handle);
    if (err != ESP_OK) ESP_LOGE(TAG, "提交失败: %s", esp_err_to_name(err));

    // 关闭命名空间
    nvs_close(my_handle);
    ESP_LOGI(TAG, "数据写入完成");
}

// 从 NVS 读取数据
int nvs_read_demo() 
{
    nvs_handle_t my_handle;
    esp_err_t err;

    // 打开命名空间(只读模式)
    err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &my_handle);
    if (err != ESP_OK) 
    {
        ESP_LOGE(TAG, "打开命名空间失败: %s", esp_err_to_name(err));
        return err;
    }

    // 读取整数
    int32_t int_value;
    err = nvs_get_i32(my_handle, "int_key", &int_value);
    if (err == ESP_OK) 
    {
        ESP_LOGI(TAG, "读取整数: %ld", int_value);
    } 
    else
    {
        ESP_LOGE(TAG, "读取整数失败: %s", esp_err_to_name(err));
        return err;
    }

    // 读取字符串
    char str_value[32];  // 预分配缓冲区
    size_t str_len = sizeof(str_value);
    err = nvs_get_str(my_handle, "str_key", str_value, &str_len);
    if (err == ESP_OK) 
    {
        ESP_LOGI(TAG, "读取字符串: %s", str_value);
    } 
    else if (err == ESP_ERR_NVS_NOT_FOUND) 
    {
        ESP_LOGW(TAG, "字符串键未找到");
        return err;
    } 
    else 
    {
        ESP_LOGE(TAG, "读取字符串失败: %s", esp_err_to_name(err));
        return err;
    }

    // 读取浮点数(从 uint32_t 转换回 float)
    uint32_t float_as_uint;
    err = nvs_get_u32(my_handle, "float_key", &float_as_uint);
    if (err == ESP_OK) 
    {
        float float_value = *(float*)&float_as_uint;  // 内存级转换
        ESP_LOGI(TAG, "读取浮点数: %.5f", float_value);
    } 
    else 
    {
        ESP_LOGE(TAG, "读取浮点数失败: %s", esp_err_to_name(err));
        return err;
    }

    // 关闭命名空间
    nvs_close(my_handle);
    return err;
}

void app_main(void) 
{
    // 初始化 NVS
    esp_err_t err = nvs_init();
    if (err != ESP_OK) 
    {
        ESP_LOGE(TAG, "NVS 初始化失败: %s", esp_err_to_name(err));
        return;
    }

    // 尝试读取数据(如果是首次运行,数据不存在)
    ESP_LOGI(TAG, "--- 首次读取 ---");
    err=nvs_read_demo();

    if(err != ESP_OK)
    {
        // 写入数据
        ESP_LOGI(TAG, "\n--- 开始写入数据 ---");
        nvs_write_demo();
            // 重新读取验证
        ESP_LOGI(TAG, "\n--- 写入后读取 ---");
        nvs_read_demo();
    }



    // 提示用户重启开发板验证掉电存储
    ESP_LOGI(TAG, "\n请重启开发板,观察数据是否保留");
}
    

在这里插入图片描述

重启之后

在这里插入图片描述

代码说明
  1. 命名空间(Namespace):NVS 通过命名空间隔离不同功能的数据(如 my_storage),避免键冲突。
  2. 数据类型支持
    • 整数:直接使用 nvs_set_i32/nvs_get_i32
    • 字符串:使用 nvs_set_str/nvs_get_str,需预分配足够大的缓冲区。
    • 浮点数:NVS 不直接支持,需通过内存转换为 uint32_t 存储(或自行实现字符串格式化)。
  3. 提交更改nvs_commit() 是关键,否则数据仅保存在内存中,未写入闪存。
  4. 错误处理:通过 esp_err_t 检查每一步操作,避免因闪存损坏或空间不足导致数据丢失。
验证掉电存储
  1. 编译并烧录代码到 ESP32,观察日志:
    • 首次运行时,读取会提示“键未找到”。
    • 写入后重新读取,会显示已写入的数据。
  2. 手动重启开发板(或断开电源后重新上电),再次运行代码,日志会显示读取到之前写入的数据,验证掉电不丢失。

三、注意事项

  • 键名长度:键名最长为 15 个字符。
  • 频繁写入:避免频繁写入以减少 Flash 磨损,必要时合并数据。
  • 分区大小:默认 NVS 分区通常足够,如需调整需修改 partitions.csv
  • 数据大小限制:单个键值对的字符串最大长度为 NVS_MAX_VALUE_LEN(默认 496 字节),大文件需拆分或使用 SPIFFS(文件系统)。
  • 性能:NVS 写入操作会消耗闪存擦写次数(约 10 万次),频繁写入需优化(如合并多次修改后再提交)。
  • 分区管理:自定义分区表时,需确保分区大小合理,避免与其他功能(如 OTA 升级)冲突。

参考:
在这里插入图片描述

https://github.com/espressif/esp-idf/blob/v5.2.5/examples/storage/nvsgen/main/nvsgen_example_main.c

分区表

https://docs.espressif.com/projects/esp-idf/zh_CN/v5.2.5/esp32/api-guides/partition-tables.html
<think>好的,用户问的是如何区分PC和H5获取录音权限。首先,我需要明确用户的需求。他们可能是在开发一个需要录音功能的Web应用,想要了解在桌面浏览器(PC)和移动浏览器(H5)上获取麦克风权限的不同方法。需要从技术实现、API使用、用户交互、兼容性等方面进行分析。 首先,回忆一下Web Audio API的相关知识。在Web开发中,获取用户麦克风权限通常使用navigator.mediaDevices.getUserMedia方法。这个方法在PC和移动端的基本调用方式是一样的,但实际应用中可能会有差异。需要分步骤说明两者的不同点。 接下来,权限请求的触发方式。在PC端,大多数浏览器要求用户必须在用户交互(比如点击事件)中触发权限请求,否则会被阻止。而在移动端,尤其是H5页面,可能同样需要用户交互,但不同移动浏览器的行为可能不同。需要确认这一点,是否有例外情况。 然后,用户提示的差异。PC端浏览器通常会在地址栏显示权限请求的图标,而移动端可能会以弹窗或底部提示的方式出现。这里可能需要具体举例,比如Chrome在PC和手机上的不同表现。 安全上下文的要求。无论是PC还是移动端,使用getUserMedia都需要在HTTPS环境下,本地开发环境(localhost)除外。这一点是否一致?需要确认。 设备检测方面,移动端可能需要处理更多设备适配问题,比如前后麦克风的选择,而PC端可能通常只有一个麦克风。但这里可能需要更准确的描述,比如移动设备可能有多个麦克风,但通常由系统管理,而开发者可能不需要特别处理。 兼容性问题。需要列举不同浏览器在PC和移动端对录音权限的支持情况。比如Safari在iOS上的限制,或者某些安卓浏览器的不同行为。 接下来,代码示例部分。需要提供一个基本的请求麦克风权限的代码示例,并指出在PC和H5中的注意事项。比如在移动端可能需要处理自动播放策略,或者用户拒绝后的不同处理方式。 另外,权限持久化方面,PC端浏览器可能会记住用户的选择,而移动端可能每次都需要重新请求,或者有不同的会话管理方式。需要确认不同浏览器的具体行为。 最后,总结关键点,帮助用户区分两者的主要差异,比如触发方式、用户提示、兼容性处理等。确保回答结构清晰,步骤明确,用户能够逐步理解和应用。 需要验证一些不确定的点,比如移动端是否真的每次都需要用户交互触发,或者不同浏览器是否有不同的策略。例如,iOS上的Safari对getUserMedia的调用是否有严格的用户手势要求,而Chrome on Android是否允许在某些情况下自动触发。此外,权限请求的UI展示在不同平台上的具体表现,可能需要进一步查证。 另外,设备检测方面,是否需要使用mediaDevices.enumerateDevices来列出麦克风设备,这在移动端是否有必要,或者是否通常只有一个可用麦克风。这部分可能需要简化,避免复杂化问题。 最后,确保回答符合用户可见层的要求,结构清晰,逐步解决问题,并且内容真实可靠。避免技术错误,比如错误的API名称或不兼容的浏览器示例。</think>区分PC端和移动端H5获取录音权限的实现差异,主要涉及浏览器API调用方式、用户交互流程及兼容性处理。以下是关键点分析: --- ### **一、核心API调用** 无论PC或H5均使用Web API `navigator.mediaDevices.getUserMedia()`,但实现细节不同: ```javascript // 通用代码框架 async function requestMicrophone() { try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); // 成功获取音频流 } catch (error) { // 处理权限拒绝或错误 } } ``` --- ### **二、用户交互差异** | **场景** | **PC端** | **H5(移动端)** | |----------------|----------------------------------------|---------------------------------------| | **触发条件** | 需用户主动交互(如点击按钮) | 同样需用户手势触发,部分浏览器更严格 | | **权限弹窗** | 浏览器顶部地址栏提示 | 系统级弹窗或底部提示栏 | | **持久化策略** | 浏览器可记住用户选择 | 部分移动浏览器每次刷新需重新授权 | --- ### **三、兼容性注意事项** 1. **PC端重点问题** - 旧版Edge/Firefox 需处理 `getUserMedia` 前缀 - Safari 要求 HTTPS 环境 2. **H5移动端特殊处理** - **iOS Safari**:首次拒绝后需手动开启(设置 > Safari > 麦克风) - **微信浏览器**:需配置JS接口安全域名 - **安卓WebView**:需应用层声明录音权限 --- ### **四、关键代码适配技巧** 1. **检测设备类型** ```javascript const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); ``` 2. **错误处理优化** ```javascript catch (error) { if (error.name === 'NotAllowedError') { // 提示用户手动开启权限 if (isMobile) alert('请前往系统设置启用麦克风权限'); } } ``` 3. **自动播放策略(H5重点)** ```javascript // 移动端需在用户交互后立即播放音频,避免静音阻塞 audioElement.play().catch(() => { /* 处理自动播放失败 */ }); ``` --- ### **五、调试建议** 1. **PC端**:使用Chrome `chrome://settings/content/microphone` 管理权限 2. **H5移动端**: - Android Chrome:长按页面 > 站点设置 - iOS Safari:需完全关闭页面后重新进入 --- ### **总结** - **本质相同**:均依赖 `getUserMedia` API,但交互流程和系统限制不同 - **核心差异**:移动端涉及更多系统级权限管控和浏览器兼容问题 - **适配关键**:结合设备检测优化提示逻辑,优先在HTTPS环境部署
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值