Files
P4-USB-transfer/main/FLASH/flash.c
T
zhengxinandCursor 851e347cf5 初始提交卡包 USB 工程
纳入固件源码、屏幕组件、nand_xfer 工具和文档。忽略本机构建产物、sdkconfig,以及含本机路径的依赖锁。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-13 21:54:50 +08:00

743 lines
26 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "FLASH/flash.h"
#include "esp_http_client.h"
#include "esp_timer.h"
#include "ZIP_EXTRACTOR/zip_extractor.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <inttypes.h>
#include <sys/stat.h>
#include "esp_system.h"
#include "soc/spi_pins.h"
#include "esp_vfs_fat_nand.h"
#include "driver/spi_master.h"
#include "EVENT/event.h"
#include "BLE/ble.h"
#include "EMOJI_STORAGE/emoji_storage.h"
#include "LVGL/lvgl_init.h"
#include <errno.h>
static const char *TAG_FLASH = "example";
/* 字体已由产线直接烧录到外部 Flash,不再从 app 内置资源同步 */
#define UI_EMBED_FONT_BIN_TO_APP 0
/* extflash 访问互斥锁:保护所有对 /extflash 的 fopen/fread/fwrite/stat/opendir 操作 */
static SemaphoreHandle_t s_extflash_mutex = NULL;
/* 解压进行中:动画切换应延后,避免与解压写操作竞争 */
static volatile bool s_extraction_in_progress = false;
void extflash_lock(void)
{
if (s_extflash_mutex) {
xSemaphoreTake(s_extflash_mutex, portMAX_DELAY);
}
}
void extflash_unlock(void)
{
if (s_extflash_mutex) {
xSemaphoreGive(s_extflash_mutex);
}
}
void extflash_extraction_started(void)
{
s_extraction_in_progress = true;
}
void extflash_extraction_finished(void)
{
s_extraction_in_progress = false;
}
bool extflash_is_extracting(void)
{
return s_extraction_in_progress;
}
#define EXTRACT_TASK_STACK_SIZE 16384 // 16KB 栈
#define EXTRACT_TASK_PRIORITY 4
// 分区的挂载路径
const char *base_path = "/extflash";//
// ********** 适配GD5F1GM7UEYIG(SPI NAND)+ WP/HD硬件接3.3V **********
#define EXAMPLE_FLASH_FREQ_KHZ 40000 // 先升到40MHz做稳定性/性能验证(后续可再评估到80MHz)
static spi_nand_flash_device_t *g_nand_flash_handle = NULL;
static spi_device_handle_t g_spi_handle = NULL;
// 磨损均衡句柄(原NOR的WL,NAND驱动内部已实现,保留兼容)
static wl_handle_t s_wl_handle = WL_INVALID_HANDLE;
void list_files_in_directory(const char *path);
static bool copy_file(const char *source_path, const char *dest_path);
static void extract_task(void *pvParameters);
static StackType_t *s_extract_stack = NULL;
static StaticTask_t *s_extract_tcb = NULL;
static bool example_mount_fatfs(void);
static esp_err_t example_init_nand_flash(void);
static esp_err_t read_chip_id(spi_device_handle_t handle, uint8_t *id_data, size_t len);
esp_err_t nand_read_jedec_id(spi_device_handle_t handle, uint8_t id[3]);
static bool sync_embedded_fonts_to_extflash(void);
static bool is_zip_file(const char *filename);
static void example_list_data_partitions(void);
static bool sync_embedded_fonts_to_extflash(void)
{
static const char *fonts[] = {
"iconfont_25.bin",
"MiSans_VF_25.bin",
"MiSans_VF_30.bin",
"MiSans_VF_40.bin",
"MiSans_VF_60.bin",
"MiSans_VF_100.bin",
};
bool all_ok = true;
char fonts_dir[64] = {0};
snprintf(fonts_dir, sizeof(fonts_dir), "%s/fonts", base_path);
#if UI_EMBED_FONT_BIN_TO_APP
ESP_LOGI(TAG_FLASH, "font sync mode: app embed -> extflash");
#else
ESP_LOGI(TAG_FLASH, "font sync mode: extflash preloaded only");
#endif
extflash_lock();
for (size_t i = 0; i < sizeof(fonts) / sizeof(fonts[0]); ++i) {
char dst_path[128] = {0};
struct stat st;
snprintf(dst_path, sizeof(dst_path), "%s/%s", fonts_dir, fonts[i]);
if (stat(dst_path, &st) == 0 && S_ISREG(st.st_mode) && st.st_size > 0) {
ESP_LOGI(TAG_FLASH, "font ready: %s (%ld bytes)", dst_path, (long)st.st_size);
continue;
}
ESP_LOGE(TAG_FLASH, "font missing or invalid: %s", dst_path);
all_ok = false;
}
extflash_unlock();
return all_ok;
}
// 初始化外部Flash芯片
static esp_err_t example_init_nand_flash(void)
{
// SPI总线配置:WP/HD设为-1(硬件接3.3V),适配大传输量
const spi_bus_config_t bus_config = {
.mosi_io_num = PIN_MOSI,
.miso_io_num = PIN_MISO,
.sclk_io_num = PIN_CLK,
.quadhd_io_num = PIN_HD, // -1:禁用HD引脚(硬件接3.3V)
.quadwp_io_num = PIN_WP, // -1:禁用WP引脚(硬件接3.3V)
.max_transfer_sz = 4096 * 2, // 增大传输缓冲区,适配NAND页操作
};
ESP_LOGI(TAG_FLASH, "Initializing GD5F1GM7UEYIG SPI NAND Flash (WP/HD HW 3.3V)");
ESP_LOGI(TAG_FLASH, "Pin assignments:");
ESP_LOGI(TAG_FLASH, "MOSI: %2d MISO: %2d SCLK: %2d CS: %2d WP: %2d HD: %2d",
PIN_MOSI, PIN_MISO, PIN_CLK, PIN_CS, PIN_WP, PIN_HD);
ESP_LOGI(TAG_FLASH, "DMA CHANNEL: %d", SPI_DMA_CHAN);
// 初始化SPI总线
ESP_ERROR_CHECK(spi_bus_initialize(HOST_ID, &bus_config, SPI_DMA_CHAN));
// SPI设备配置:半双工(NAND推荐)、80MHz时钟
spi_device_interface_config_t devcfg = {
.clock_speed_hz = EXAMPLE_FLASH_FREQ_KHZ * 1000,
.mode = 0,
.spics_io_num = PIN_CS,
.queue_size = 10,
.flags = 0,
};
// 添加SPI设备到总线
ESP_ERROR_CHECK(spi_bus_add_device(HOST_ID, &devcfg, &g_spi_handle));
uint8_t id[4] = {0};
nand_read_jedec_id(g_spi_handle, id);
printf("NAND ID: 0x%02X 0x%02X 0x%02X\n", id[0], id[1], id[2]);
if (id[1] != 0xC8 || id[2] != 0x91) {
ESP_LOGE(TAG_FLASH, "Flash chip ID error! Not GD5F1GM7");
return ESP_ERR_INVALID_VERSION;
}
// SPI NAND Flash专用配置:适配GD5F1GM7的SIO模式(默认),WP/HD硬件禁用
spi_nand_flash_config_t nand_flash_config = {
.device_handle = g_spi_handle,
.io_mode = SPI_NAND_IO_MODE_SIO, // GD5F1GM7支持SIO/DIO/QIO,先选SIO(兼容)
.flags = 0, // 与SPI设备保持一致
};
assert(devcfg.flags == nand_flash_config.flags);
// 初始化NAND Flash设备(驱动自动识别GD5F1GM7芯片ID/容量)
ESP_ERROR_CHECK(spi_nand_flash_init_device(&nand_flash_config, &g_nand_flash_handle));
ESP_LOGI(TAG_FLASH, "GD5F1GM7UEYIG NAND Flash initialized successfully");
return ESP_OK;
}
// ********** 复制JSON文件到NAND Flash(原逻辑不变) **********
static bool copy_json_files_to_ext_flash(const char *source_dir, const char *dest_dir)
{
DIR *dir;
struct dirent *entry;
char source_path[256];
char dest_path[256];
ESP_LOGI(TAG_FLASH, "开始复制JSON文件从 %s 到 %s", source_dir, dest_dir);
dir = opendir(source_dir);
if (dir == NULL) {
ESP_LOGE(TAG_FLASH, "无法打开源目录: %s, err: %s", source_dir, strerror(errno));
return false;
}
int file_count = 0;
while ((entry = readdir(dir)) != NULL) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
char *ext = strrchr(entry->d_name, '.');
if (ext != NULL && strcasecmp(ext, ".json") == 0) {
snprintf(source_path, sizeof(source_path), "%s/%s", source_dir, entry->d_name);
snprintf(dest_path, sizeof(dest_path), "%s/%s", dest_dir, entry->d_name);
ESP_LOGI(TAG_FLASH, "复制文件: %s -> %s", source_path, dest_path);
if (copy_file(source_path, dest_path)) {
file_count++;
ESP_LOGI(TAG_FLASH, "文件复制成功: %s", entry->d_name);
} else {
ESP_LOGE(TAG_FLASH, "文件复制失败: %s", entry->d_name);
}
}
}
closedir(dir);
ESP_LOGI(TAG_FLASH, "总共复制了 %d 个JSON文件", file_count);
return (file_count > 0);
}
// ********** 复制单个文件(原逻辑不变,NAND写入优化) **********
static bool copy_file(const char *source_path, const char *dest_path)
{
FILE *src_file, *dest_file;
char buffer[4096]; // 增大缓冲区,适配NAND写入
size_t bytes_read;
bool need_lock = (strstr(dest_path, "/extflash") != NULL);
if (need_lock) extflash_lock();
src_file = fopen(source_path, "rb");
if (src_file == NULL) {
ESP_LOGE(TAG_FLASH, "无法打开源文件: %s, err: %s", source_path, strerror(errno));
if (need_lock) extflash_unlock();
return false;
}
dest_file = fopen(dest_path, "wb");
if (dest_file == NULL) {
ESP_LOGE(TAG_FLASH, "无法创建目标文件: %s, err: %s", dest_path, strerror(errno));
fclose(src_file);
if (need_lock) extflash_unlock();
return false;
}
while ((bytes_read = fread(buffer, 1, sizeof(buffer), src_file)) > 0) {
if (fwrite(buffer, 1, bytes_read, dest_file) != bytes_read) {
ESP_LOGE(TAG_FLASH, "写入文件失败: %s", dest_path);
fclose(src_file);
fclose(dest_file);
if (need_lock) extflash_unlock();
return false;
}
fflush(dest_file); // NAND建议及时刷新,避免数据丢失
}
fclose(src_file);
fclose(dest_file);
if (need_lock) extflash_unlock();
return true;
}
// ********** 列出目录中的文件(原逻辑不变) **********
void list_files_in_directory(const char *path)
{
DIR *dir;
struct dirent *entry;
struct stat file_stat;
char file_path[256];
dir = opendir(path);
if (dir == NULL) {
ESP_LOGE(TAG_FLASH, "无法打开目录: %s, err: %s", path, strerror(errno));
return;
}
ESP_LOGI(TAG_FLASH, "目录 %s 下的文件/目录:", path);
while ((entry = readdir(dir)) != NULL) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
snprintf(file_path, sizeof(file_path), "%s/%s", path, entry->d_name);
if (stat(file_path, &file_stat) == 0) {
if (S_ISREG(file_stat.st_mode)) {
ESP_LOGI(TAG_FLASH, " 文件: %s, 大小: %ld 字节", entry->d_name, file_stat.st_size);
} else if (S_ISDIR(file_stat.st_mode)) {
ESP_LOGI(TAG_FLASH, " 目录: %s", entry->d_name);
}
}
}
closedir(dir);
}
// ********** 下载文件到NAND Flash(原逻辑微调,适配NAND写入) **********
esp_err_t download_file(char *firmware_url, char *filename)
{
if (filename == NULL || filename[0] == '\0' || !strcmp(filename, ".") || !strcmp(filename, "..")) {
ESP_LOGE(TAG_FLASH, "download_file: 无效文件名");
return ESP_ERR_INVALID_ARG;
}
esp_http_client_config_t config = {
.method = HTTP_METHOD_GET,
.url = firmware_url,
.timeout_ms = 300000,
.buffer_size = 32768,
.buffer_size_tx = 4096,
.keep_alive_enable = true,
.keep_alive_idle = 30,
.keep_alive_interval = 10,
.keep_alive_count = 5,
.disable_auto_redirect = false,
.max_redirection_count = 3,
.auth_type = HTTP_AUTH_TYPE_NONE,
};
typedef struct {
uint32_t total_size;
uint32_t downloaded;
bool download_complete;
esp_err_t last_error;
} download_status_t;
static download_status_t g_download_status = {0};
memset(&g_download_status, 0, sizeof(g_download_status));
esp_err_t ret = ESP_FAIL;
FILE *file = NULL;
uint8_t *buffer = heap_caps_malloc(16384, MALLOC_CAP_SPIRAM);
if (!buffer) {
ESP_LOGE(TAG_FLASH, "Failed to allocate buffer");
return ESP_ERR_NO_MEM;
}
char full_path[256];
snprintf(full_path, sizeof(full_path), "%s/%s", base_path, filename);
extflash_lock();
struct stat st;
if (stat(full_path, &st) == 0 && !S_ISDIR(st.st_mode)) {
ESP_LOGI(TAG_FLASH, "Removing existing file: %s", full_path);
remove(full_path);
}
mkdir(base_path, 0755);
/* 文件名可带子目录,例如 eaf/foo.eaf → /extflash/eaf/ */
{
char parent[256];
snprintf(parent, sizeof(parent), "%s", full_path);
char *slash = strrchr(parent, '/');
if (slash && slash != parent) {
*slash = '\0';
if (strcmp(parent, base_path) != 0 && mkdir(parent, 0755) < 0 && errno != EEXIST) {
ESP_LOGW(TAG_FLASH, "mkdir %s 失败: %s", parent, strerror(errno));
}
}
}
// 断点重连循环(减少重试次数,NAND不建议频繁重试)
for (int retry_count = 0; retry_count <= 5; retry_count++) {
if (retry_count > 0) {
extflash_unlock();
ESP_LOGW(TAG_FLASH, "Retry attempt %d/5", retry_count);
vTaskDelay(pdMS_TO_TICKS(2000 * retry_count));
extflash_lock();
}
file = fopen(full_path, "ab+");
if (!file) {
ESP_LOGE(TAG_FLASH, "Failed to open file: %s, err: %s", full_path, strerror(errno));
continue;
}
fseek(file, 0, SEEK_END);
size_t current_size = ftell(file);
ESP_LOGI(TAG_FLASH, "Current file size: %zu bytes", current_size);
esp_http_client_handle_t client = esp_http_client_init(&config);
if (!client) {
ESP_LOGE(TAG_FLASH, "Failed to initialize HTTP client");
fclose(file);
extflash_unlock();
continue;
}
esp_http_client_set_header(client, "Content-Type", "text/plain");
if (current_size > 0) {
char range_header[64];
snprintf(range_header, sizeof(range_header), "bytes=%zu-", current_size);
esp_http_client_set_header(client, "Range", range_header);
ESP_LOGI(TAG_FLASH, "Setting Range header: %s", range_header);
}
ret = esp_http_client_open(client, 0);
if (ret != ESP_OK) {
ESP_LOGE(TAG_FLASH, "Failed to open HTTP connection: %s", esp_err_to_name(ret));
esp_http_client_cleanup(client);
fclose(file);
extflash_unlock();
continue;
}
int content_length = esp_http_client_fetch_headers(client);
g_download_status.total_size = (content_length > 0) ? (content_length + current_size) : 0;
if (content_length > 0) {
ESP_LOGI(TAG_FLASH, "Content length: %d bytes, Total expected: %" PRIu32 " bytes",
content_length, (uint32_t)g_download_status.total_size);
}
g_download_status.downloaded = current_size;
// 下载数据
int read_len;
bool download_error = false;
int64_t last_progress_time = esp_timer_get_time();
uint32_t last_progress_size = current_size;
while (true) {
read_len = esp_http_client_read(client, (char *)buffer, 16384);
if (read_len < 0) {
ESP_LOGE(TAG_FLASH, "Error reading HTTP data");
download_error = true;
break;
} else if (read_len == 0) {
ESP_LOGI(TAG_FLASH, "HTTP read complete");
break;
}
if (fwrite(buffer, 1, read_len, file) != read_len) {
ESP_LOGE(TAG_FLASH, "Write error: expected %d, got %zu", read_len, (size_t)fwrite(buffer,1,read_len,file));
download_error = true;
break;
}
if (g_download_status.downloaded % 65536 == 0) {
fflush(file);
}
g_download_status.downloaded += read_len;
// 打印下载进度
int64_t current_time = esp_timer_get_time();
if (current_time - last_progress_time > 1000000) {
uint32_t speed = (g_download_status.downloaded - last_progress_size) / 1024;
if (g_download_status.total_size > 0) {
int percent = (g_download_status.downloaded * 100) / g_download_status.total_size;
ESP_LOGI(TAG_FLASH, "Progress: %" PRIu32 "/%" PRIu32 " bytes (%d%%) - Speed: %" PRIu32 " KB/s",
(uint32_t)g_download_status.downloaded, (uint32_t)g_download_status.total_size, percent, (uint32_t)speed);
} else {
ESP_LOGI(TAG_FLASH, "Progress: %" PRIu32 " bytes - Speed: %" PRIu32 " KB/s",
(uint32_t)g_download_status.downloaded, (uint32_t)speed);
}
last_progress_time = current_time;
last_progress_size = g_download_status.downloaded;
}
if (esp_http_client_get_status_code(client) == 408) {
ESP_LOGE(TAG_FLASH, "HTTP timeout occurred");
download_error = true;
break;
}
}
fflush(file);
fclose(file);
if (download_error) {
esp_http_client_cleanup(client);
extflash_unlock();
continue;
}
if (g_download_status.total_size > 0 && g_download_status.downloaded < g_download_status.total_size) {
ESP_LOGW(TAG_FLASH, "Download incomplete: %" PRIu32 "/%" PRIu32 " bytes",
(uint32_t)g_download_status.downloaded, (uint32_t)g_download_status.total_size);
esp_http_client_cleanup(client);
extflash_unlock();
continue;
}
// 下载成功,创建解压任务
ESP_LOGI(TAG_FLASH, "Download completed successfully: %" PRIu32 " bytes",
(uint32_t)g_download_status.downloaded);
esp_http_client_cleanup(client);
if (!is_zip_file(filename)) {
ESP_LOGI(TAG_FLASH, "非 ZIP,已写入 %s(%" PRIu32 " 字节),跳过解压",
full_path, (uint32_t)g_download_status.downloaded);
extflash_unlock();
free(buffer);
return ESP_OK;
}
char *filename_copy = strdup(filename);
if (!filename_copy) {
ESP_LOGE(TAG_FLASH, "strdup(filename) failed");
extflash_unlock();
free(buffer);
return ESP_ERR_NO_MEM;
}
size_t stack_size = 256 * 1024;
heap_caps_free(s_extract_stack);
vPortFree(s_extract_tcb);
s_extract_stack = heap_caps_malloc(stack_size, MALLOC_CAP_SPIRAM);
s_extract_tcb = pvPortMalloc(sizeof(StaticTask_t));
if (s_extract_stack && s_extract_tcb) {
ESP_LOGI(TAG_FLASH, "[DEBUG] Creating extract_task (static, SPIRAM stack %u bytes)", (unsigned)stack_size);
xTaskCreateStatic(extract_task, "extract_task", stack_size / sizeof(StackType_t),
(void *)filename_copy, configMAX_PRIORITIES - 1,
s_extract_stack, s_extract_tcb);
} else {
ESP_LOGW(TAG_FLASH, "[DEBUG] SPIRAM alloc failed, falling back to dynamic task");
xTaskCreatePinnedToCore(extract_task, "extract_task", stack_size, (void *)filename_copy,
configMAX_PRIORITIES - 1, NULL, 1);
heap_caps_free(s_extract_stack);
vPortFree(s_extract_tcb);
s_extract_stack = NULL;
s_extract_tcb = NULL;
}
extflash_unlock();
free(buffer);
return ESP_OK;
}
extflash_unlock();
free(buffer);
ESP_LOGE(TAG_FLASH, "Download failed after all retries");
return ESP_FAIL;
}
static void extract_task(void *pvParameters)
{
char *filename_copy = (char *)pvParameters;
ESP_LOGI("EXTRACT", "[DEBUG] extract_task started, file=%s, core=%d, prio=%d, stack_hwm=%u",
filename_copy ? filename_copy : "NULL",
(int)xPortGetCoreID(),
(int)uxTaskPriorityGet(NULL),
(unsigned)uxTaskGetStackHighWaterMark(NULL));
extflash_extraction_started();
extflash_lock();
if (filename_copy) {
extract_json_zip(filename_copy);
free(filename_copy);
}
list_files_in_directory(base_path);
/* ── Fallback: main_task 不可用时直接处理下载结果 ── */
if (main_task_handle == NULL)
{
char pack_id = zip_extractor_get_last_downloaded_pack();
if (pack_id != '0')
{
ESP_LOGW("EXTRACT", "main_task not running, handling DOWNLOAD_SUCCESS directly (pack='%c')", pack_id);
if (notification_task_handle)
xTaskNotify(notification_task_handle, BLE_SEND_DL_EMOJI_SUCCESS, eSetBits);
emoji_storage_add_downloaded_pack(pack_id);
emoji_storage_save_using_pack(pack_id);
ESP_LOGI("EXTRACT", "pack '%c' saved as downloaded & using", pack_id);
char path_buf[64];
snprintf(path_buf, sizeof(path_buf), "/extflash/%c/default.json", pack_id);
struct stat st;
if (stat(path_buf, &st) == 0)
{
ESP_LOGI("EXTRACT", "Playing animation: %s (%ld bytes)", path_buf, (long)st.st_size);
request_play_lottie_path(path_buf);
}
else
{
ESP_LOGW("EXTRACT", "Animation file not found: %s, skip play", path_buf);
}
g_emoji_upgrade_downloaded_pack = pack_id;
g_emoji_upgrade_pending_finalize = true;
ESP_LOGI("EXTRACT", "下载成功 pack='%c':等待首次切换后收尾(不重启,不回原来)", pack_id);
clear_system_event(DOWNLOAD_SUCCESS_BIT);
}
else
{
ESP_LOGW("EXTRACT", "main_task not running, handling DOWNLOAD_FAILED directly");
if (notification_task_handle)
xTaskNotify(notification_task_handle, BLE_SEND_DL_EMOJI_FAILED, eSetBits);
clear_system_event(DOWNLOAD_FAILED_BIT);
}
}
extflash_unlock();
extflash_extraction_finished();
/*
ESP_LOGI("EXTRACT", "[DEBUG] extract_task finished, about to delete self. "
"queue_pending=%d, stack_hwm=%u",
(int)uxQueueMessagesWaiting(system_event_queue),
(unsigned)uxTaskGetStackHighWaterMark(NULL)); //这里解压时注意可能死机
*/
vTaskDelete(NULL);
}
// ********** 检查文件是否为ZIP格式(原逻辑不变) **********
static bool is_zip_file(const char *filename)
{
if (!filename) return false;
const char *dot = strrchr(filename, '.');
return (dot && strcasecmp(dot, ".zip") == 0);
}
// ********** 反初始化NAND Flash(新增,用于程序退出/复位) **********
static void example_deinit_nand_flash(void)
{
if (g_nand_flash_handle) {
esp_vfs_fat_nand_unmount(base_path, g_nand_flash_handle);
spi_nand_flash_deinit_device(g_nand_flash_handle);
g_nand_flash_handle = NULL;
}
if (g_spi_handle) {
spi_bus_remove_device(g_spi_handle);
g_spi_handle = NULL;
}
spi_bus_free(HOST_ID);
ESP_LOGI(TAG_FLASH, "GD5F1GM7UEYIG NAND Flash deinitialized");
}
// ********** 正确 NAND 版本:无参数 **********
static bool example_mount_fatfs(void)
{
ESP_LOGI(TAG_FLASH, "挂载 NAND FAT 文件系统");
if (g_nand_flash_handle == NULL) {
ESP_LOGE(TAG_FLASH, "NAND Flash 句柄为空");
return false;
}
// FAT 配置(NAND 专用)
const esp_vfs_fat_mount_config_t mount_config = {
.max_files = 6,
#ifdef CONFIG_EXAMPLE_FORMAT_IF_MOUNT_FAILED
.format_if_mount_failed = true,
#else
.format_if_mount_failed = false,
#endif
.allocation_unit_size = 16 * 1024,
};
// NAND 专用挂载接口(不是 NOR 的 spiflash_mount!)
esp_err_t err = esp_vfs_fat_nand_mount(base_path, g_nand_flash_handle, &mount_config);
if (err != ESP_OK) {
ESP_LOGE(TAG_FLASH, "NAND FATFS 挂载失败: %s (0x%x)", esp_err_to_name(err), err);
return false;
}
// 打印容量
uint64_t bytes_total, bytes_free;
esp_vfs_fat_info(base_path, &bytes_total, &bytes_free);
ESP_LOGI(TAG_FLASH, "NAND FAT 挂载成功: %s", base_path);
ESP_LOGI(TAG_FLASH, "总容量: %" PRIu64 " KB, 剩余: %" PRIu64 " KB",
bytes_total / 1024, bytes_free / 1024);
return true;
}
// GD5F1GM7UEYIG 读 ID 函数
// 输出:id[0] = 0xC8, id[1] = 0x91, id[2] = 0x01
esp_err_t nand_read_jedec_id(spi_device_handle_t handle, uint8_t id[3])
{
if (handle == NULL || id == NULL) {
return ESP_ERR_INVALID_ARG;
}
memset(id, 0, 3);
// GD5F1GM7 标准 Read ID 时序:必须读 4 字节
uint8_t tx_data[4] = {0x9F, 0x00, 0x00, 0x00};
uint8_t rx_data[4] = {0};
spi_transaction_t t = {
.length = 4 * 8, // 必须 32bit,才能读到 0x91
.tx_buffer = tx_data,
.rx_buffer = rx_data,
};
esp_err_t ret = spi_device_transmit(handle, &t);
if (ret == ESP_OK) {
id[0] = rx_data[1]; // 0xC8 厂商ID
id[1] = rx_data[2]; // 0x91 设备ID ✅
id[2] = rx_data[3]; // 扩展ID
}
return ret;
}
static void example_list_data_partitions(void)
{
ESP_LOGI(TAG_FLASH, "Listing data partitions:");
// 查找所有数据类型的分区
esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, NULL);
// 遍历并打印每个分区的信息
for (; it != NULL; it = esp_partition_next(it))
{
const esp_partition_t *part = esp_partition_get(it);
ESP_LOGI(TAG_FLASH, "- partition '%s', subtype %d, offset 0x%" PRIx32 ", size %" PRIu32 " kB",
part->label, part->subtype, part->address, part->size / 1024);
}
esp_partition_iterator_release(it); // 释放迭代器
}
// ********** 主入口函数(替换原flash_main,适配NAND+WP/HD接3.3V) **********
void flash_main(void)
{
if (s_extflash_mutex == NULL) {
s_extflash_mutex = xSemaphoreCreateMutex();
if (s_extflash_mutex == NULL) {
ESP_LOGE(TAG_FLASH, "Failed to create extflash mutex");
}
}
// 1. 初始化GD5F1GM7UEYIG SPI NAND Flash(WP/HD硬件接3.3V)
if (example_init_nand_flash() != ESP_OK) {
ESP_LOGE(TAG_FLASH, "GD5F1GM7UEYIG NAND Flash初始化失败");
return;
}
// 2. 挂载NAND FAT文件系统
if (!example_mount_fatfs()) {
ESP_LOGE(TAG_FLASH, "NAND FAT文件系统挂载失败,无法继续");
example_deinit_nand_flash();
return;
}
example_list_data_partitions();
// 3. 校验 /extflash/fonts 下字体是否就绪(不再从 app 内同步)
if (!sync_embedded_fonts_to_extflash()) {
ESP_LOGW(TAG_FLASH, "部分字体缺失/异常,运行时可能回退到默认字体");
}
// 4. 列出NAND Flash中的文件(含 fonts 目录,便于产线验收字体)
extflash_lock();
ESP_LOGI(TAG_FLASH, "GD5F1GM7UEYIG NAND Flash中的文件列表:");
list_files_in_directory(base_path);
char fonts_dir[64] = {0};
snprintf(fonts_dir, sizeof(fonts_dir), "%s/fonts", base_path);
ESP_LOGI(TAG_FLASH, "字体目录文件列表: %s", fonts_dir);
list_files_in_directory(fonts_dir);
extflash_unlock();
ESP_LOGI(TAG_FLASH, "GD5F1GM7UEYIG NAND Flash初始化&挂载完成,业务逻辑就绪");
}