1.ESP32分区表
- 为什么ESP32要分区 00:34--
- 简述:其他单片机生成文件少,功能少,而ESP32功能多,文件多
- 分区表各个文件简介 --7:31
- vscode查看分区表 --9:33
- ota通过idf.py menuconfig配置命令调出配置菜单 --12.35
- 创建自己的分区 --17.41
- 写.c文件
-
#include <stdio.h> #include "esp_partition.h" #include "esp_log.h" #include <string.h>static const char* TAG = "partition"; //TAG 区分日志#define USER_PARTITION_TYPE 0x40 #define USER_PARTITION_SUBTYPE 0x00 //定义分区类型static const esp_partition_t* partition_user = NULL; //定义分区指针操作分区的void app_main(void) {//找到自定义的分区,找得到之后会返回分区指针/*知识点1find与find_first的区别 --21.10find_first: 找到第一个符合条件的分区find: 找到符合条件的所有分区*/partition_user = esp_partition_find_first(USER_PARTITION_TYPE, USER_PARTITION_SUBTYPE, NULL); if (partition_user == NULL) {ESP_LOGE(TAG, "Failed to find user partition");return;}esp_partition_erase_range(partition_user, 0, 0x1000); //擦除分区const char *data = "Hello, ESP32!";esp_partition_write(partition_user, 0, data, strlen(data)); //写入分区 char buffer[100];memset(buffer, 0, sizeof(buffer));esp_partition_read(partition_user, 0, buffer,strlen(data)); //读取分区ESP_LOGI(TAG, "Read from user partition: %s", buffer);return;}
-
补充1:分区表
# Name, Type, SubType, Offset, Size, Flags
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
nvs, data, nvs, , 0x6000,
phy_init, data, phy, , 0x1000,
factory, app, factory, , 1M,
user, 0x40, 0x01, , 0x1000,列含义
Name
:分区的名称,方便识别和引用。Type
:分区的类型,常见有app
(应用程序)、data
(数据),也可以用十六进制表示自定义类型。SubType
:分区类型的子类型,进一步细分分区用途。Offset
:分区在存储设备上的起始偏移地址。Size
:分区的大小,可以用十六进制(如0x1000
)或单位(如1M
)表示。Flags
:分区的额外标志,可选字段。每行解释
nvs
:非易失性存储分区,用于存储系统和应用的配置信息。Type
为data
,SubType
为nvs
,大小是0x6000
。phy_init
:物理层初始化数据分区,存储与硬件物理层初始化相关的数据。Type
为data
,SubType
为phy
,大小是0x1000
。factory
:工厂应用分区,用于存储出厂默认的应用程序。Type
为app
,SubType
为factory
,大小是1M
。user
:自定义分区,Type
是十六进制0x40
,SubType
是0x01
,大小是0x1000
,可用于用户自定义的数据或应用。补充2 OTA
OTA 分区作用
设备软件更新时,新固件先下载到 OTA 分区,验证通过后,系统从该分区加载新固件,避免直接覆盖运行中的程序,提升更新安全性与可靠性。
OTA 分区常见配置示例及解释
# Name, Type, SubType, Offset, Size, Flags ota_0, app, ota_0, , 1M, ota_1, app, ota_1, , 1M,
- Name:常见的 OTA 分区名称有 ota_0 和 ota_1,系统会在这两个分区间切换来实现固件更新。
- Type:分区类型为 app,用于存储应用程序固件。
- SubType:ota_0 和 ota_1 是 OTA 分区的子类型,可区分不同 OTA 槽位,让系统确定当前使用的分区。
- Offset:即分区在存储设备上的起始偏移地址,留空由系统自动分配。
- Size:每个 OTA 分区大小设为 1M,最多能存储 1MB 的应用程序固件。
设备实际使用时,通过 ota_0 和 ota_1 分区交替实现更新。若当前运行 ota_0 固件,新固件下载至 ota_1,验证通过后,下次启动便从 ota_1 加载新固件 。
本章脉络
2.ESP中的NVS
如何更方便的管理NVS存储操作
- 背景 ---3.20
- 操作
- 新建nvs测试程序
- 创建项目文件夹
- esp32下idf.py create-project nvs_test
- nvs_test下idf.py build
- VScode下打开文件夹
- 代码
- 定义两个命名空间,两个空间中有相同的键,向这两个键写入不同的值,观察他们会不会受到影响
- 创建项目文件夹
- 新建nvs测试程序
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_err.h"#define NAME_SPACE_WIFI1 "wifi1"
#define NAME_SPACE_WIFI2 "wifi2"#define KEY_SSID_KEY "ssid"
#define KEY_PASSWORE_KEY "password"void nvs_blob_read(const char *name_space, const char *key, void *buffer, int maxlen)
{nvs_handle_t nvs_handle; //用于存储NVS分区的句柄size_t length = 0;nvs_open(name_space, NVS_READONLY, &nvs_handle);nvs_get_blob(nvs_handle, key, (void*)&length, &length); //读取键数据,并获取键数据的长度(主要是这个)if (length && length <= maxlen) //如果键数据存在且长度不超过maxlen{nvs_get_blob(nvs_handle, key, buffer, &length); //读取键数据}else{printf("nvs_blob_read: %s is not exist or too long\n", key); //打印错误信息}nvs_close(nvs_handle); //关闭NVS分区
}void app_main(void)
{nvs_handle_t nvs_handle1;esp_err_t ret = nvs_flash_init(); //初始化NVS分区if (ret != ESP_OK) { //如果初始化失败,擦除NVS分区,再初始化NVS分区nvs_flash_erase(); //擦除NVS分区ESP_ERROR_CHECK(nvs_flash_init()); //ESP_ERROR_CHECK()是一个宏定义,用于检查函数的返回值,如果返回值不为ESP_OK,则打印错误信息并终止程序}/*命名空间1 NVS写入*/nvs_open(NAME_SPACE_WIFI1, NVS_READWRITE, &nvs_handle1); //打开NVS分区,读写模式nvs_set_blob(nvs_handle1, KEY_SSID_KEY, "wifi_esp32", strlen("wifi_esp32")); //写入键数据nvs_set_blob(nvs_handle1, KEY_PASSWORE_KEY, "12345678", strlen("12345678")); //写入密码数据// strlen()函数是一个C语言库函数,用于计算字符串的长度。nvs_commit(nvs_handle1); //提交数据/*如果不调用nvs_commit()函数,那么数据将不会被写入到NVS分区中(flash)。写入的时机要跟随系统走如果调用nvs_commit()函数,那么数据将立即被写入到NVS分区中(flash)。不许推迟写入*/nvs_close(nvs_handle1); //关闭NVS分区/*命名空间2 NVS写入*/ nvs_open(NAME_SPACE_WIFI2, NVS_READWRITE, &nvs_handle1); //打开NVS分区,读写模式nvs_set_blob(nvs_handle1, KEY_SSID_KEY, "hello,world", strlen("hello,world")); //写入键数据nvs_set_blob(nvs_handle1, KEY_PASSWORE_KEY, "654321", strlen("654321")); //写入密码数据nvs_commit(nvs_handle1); //提交数据nvs_close(nvs_handle1); //关闭NVS分区vTaskDelay(1000 / portTICK_PERIOD_MS); //延时1秒char read_buffer[64];//用于存储键数据//命名空间WIFI1 SSID的值memset(read_buffer, 0, sizeof(read_buffer)); //清空read_buffer数组nvs_blob_read(NAME_SPACE_WIFI1, KEY_SSID_KEY, read_buffer, sizeof(read_buffer)); //读取键数据ESP_LOGI("NVS", "namespace: %s , key:%s -> value:%s", NAME_SPACE_WIFI1,KEY_SSID_KEY,read_buffer); //打印键数据
//命名空间WIFI1 PASSWORD的值memset(read_buffer, 0, sizeof(read_buffer)); nvs_blob_read(NAME_SPACE_WIFI1, KEY_SSID_KEY, read_buffer, sizeof(read_buffer)); ESP_LOGI("NVS", "namespace: %s , key:%s -> value:%s", NAME_SPACE_WIFI1,KEY_PASSWORE_KEY,read_buffer);
//命名空间WIFI2 SSID的值memset(read_buffer, 0, sizeof(read_buffer)); nvs_blob_read(NAME_SPACE_WIFI2, KEY_SSID_KEY, read_buffer, sizeof(read_buffer)); ESP_LOGI("NVS", "namespace: %s , key:%s -> value:%s", NAME_SPACE_WIFI2,KEY_SSID_KEY,read_buffer);
//命名空间WIFI2 PASSWORD的值memset(read_buffer, 0, sizeof(read_buffer)); nvs_blob_read(NAME_SPACE_WIFI2, KEY_SSID_KEY, read_buffer, sizeof(read_buffer)); ESP_LOGI("NVS", "namespace: %s , key:%s -> value:%s", NAME_SPACE_WIFI2,KEY_PASSWORE_KEY,read_buffer);
}
3.ESP32中的SPIFFS文件系统
- 新建SPIFFS测试程序
- 创建项目文件夹----4.00
- esp32下idf.py create-project spiffs_test
- spiffs_test下 cp ../esp-idf/components/partition_table/partitions_singleapp.csv .
- 注意这个partitions,这里后面的s不要拉下
- 不要忘了后面有个点
- 通过idf.py menuconfig指定分区表
- spiffs_test下idf.py build
- VScode下打开文件夹
- 改分区表 5.48--6.30
- 记得改成512k而不是1M,后面会改
- 代码
- 配置spiffs文件系统
-
esp_vfs_spiffs_register具体实现逻辑 9.10
-
-
文件操作
- 文件打开:
f = fopen("/spiffs/hello.txt","r");
尝试以只读模式打开位于 SPIFFS 文件系统下的hello.txt
文件。如果打开失败(f
为NULL
),则通过ESP_LOGE
输出错误日志并返回,结束当前函数执行。
- 文件读取:
fgets(line,sizeof(line),f);
使用fgets
函数从打开的文件f
中读取一行内容,存储到字符数组line
中,最多读取sizeof(line) - 1
个字符。fclose(f);
读取完成后关闭文件。
- 处理换行符:
char *pos = strchr(line,'\n');
查找字符数组line
中第一次出现换行符\n
的位置。- 如果找到换行符(
pos
不为NULL
),则将该位置的字符设置为'\0'
,即把换行符替换为字符串结束符,从而去掉换行符。
- 日志输出:
ESP_LOGI("spiffs","read str:%s",line);
使用ESP_LOGI
输出读取到的字符串内容。
- 文件系统注销:
esp_vfs_spiffs_unregister(conf.partition_label);
注销之前注册的 SPIFFS 文件系统,这里的conf
应该是之前定义好的配置结构体,partition_label
是文件系统分区标签。
- 文件打开:
- 配置spiffs文件系统
- 创建项目文件夹----4.00
VFS(虚拟文件系统)是一个抽象层,它让操作系统能以统一的方式管理不同类型的文件系统,就像一个万能的 “翻译官”,让你不用管具体的文件系统差异,就能方便地操作文件。
抽象层是一种编程和系统设计里常用的概念。
现实中存在很多不同类型的文件系统,比如 FAT、NTFS、ext4 等,它们存储和管理文件的方式各不相同。
抽象层就像是一个 “翻译官” 和 “协调者”。VFS 作为抽象层,把不同文件系统的具体操作细节都隐藏起来,给操作系统和用户程序提供一套统一的接口。无论你用的是哪种底层文件系统,操作系统和程序都可以通过这套统一接口来进行文件的创建、读取、修改、删除等操作。
这样一来,操作系统和程序开发者就不用关心具体是哪种文件系统在工作,降低了开发和使用的复杂度。
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_spiffs.h"
#include "esp_err.h"
#include "esp_log.h"
#include <string.h>void app_main(void)
{// 定义 SPIFFS 配置结构体esp_vfs_spiffs_conf_t conf = {.base_path = "/spiffs", // 挂载基础路径.format_if_mount_failed = true, // 挂载失败时格式化.max_files = 5, // 最大可打开文件数.partition_label = NULL // 分区标签为 NULL};// 注册 SPIFFS 文件系统esp_err_t ret = esp_vfs_spiffs_register(&conf);if (ret != ESP_OK) {ESP_LOGE("spiffs", "spiffs mount fail!");return;}// 检查 SPIFFS 分区完整性ret = esp_spiffs_check(conf.partition_label);if (ret != ESP_OK) {ESP_LOGE("spiffs", "spiffs check fail!");return;}// 获取 SPIFFS 分区信息size_t total = 0, used = 0;ret = esp_spiffs_info(conf.partition_label, &total, &used);if (ret != ESP_OK) {ESP_LOGE("spiffs", "spiffs info fail!");return;}ESP_LOGI("spiffs", "spiffs total size:%d,used:%d", total, used);// 若使用空间大于总空间,再次检查分区完整性if (used > total) {ret = esp_spiffs_check(conf.partition_label);if (ret != ESP_OK) {ESP_LOGE("spiffs", "used > total & spiffs check fail!");return;}}// 以写入模式打开文件FILE *f = fopen("/spiffs/helloworld.txt", "w");if (f == NULL) {ESP_LOGE("spiffs", "Failed to open file");return;}// 写入内容fprintf(f, "Hello, World!\n");// 关闭写入的文件fclose(f);// 延时 1 秒vTaskDelay(pdMS_TO_TICKS(1000));// 以读取模式打开文件f = fopen("/spiffs/helloworld.txt", "r");if (f == NULL) {ESP_LOGE("spiffs", "Failed to open file for read");return;}// 读取文件一行内容char line[64];fgets(line, sizeof(line), f);// 关闭读取的文件fclose(f);// 去除换行符char *pos = strchr(line, '\n');if (pos) {*pos = 0;}// 打印读取的内容ESP_LOGI("spiffs", "read str:%s", line);// 注销 SPIFFS 文件系统esp_vfs_spiffs_unregister(conf.partition_label);
}