#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 #include #include #include #include #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 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初始化&挂载完成,业务逻辑就绪"); }