/*****************************************************include files **************************************************************************/ #include "LVGL/lvgl_init.h" #include "FLASH/flash.h" #include #include "esp_heap_caps.h" #include "event.h" #include "freertos/timers.h" #include "BLE/ble.h" #include "BLE/ble_send.h" #include "BLE/ble_data_ansy.h" #include "nvs_flash.h" #include "esp_timer.h" #include "ui.h" #include "EMOJI_STORAGE/emoji_storage.h" #include "ZIP_EXTRACTOR/zip_extractor.h" #include "events_init.h" #include "WIFI/wifi.h" #include "OTA/ota_upgrade.h" #include "wifi_prov_ui.h" #include "main.h" #include "SCREEN/screen_brightness.h" #include "SCREEN/screen_sleep.h" #include "MOTOR/motor_control.h" #include "MOTOR/foc.h" #include #include #include "SC7U22/SC7U22.h" #include "soc/io_mux_reg.h" #include "driver/pcnt.h" #include "esp_netif_sntp.h" #include "pet/pet_init.h" #include "esp_flash.h" #include "hal/gpio_hal.h" #include "soc/gpio_reg.h" // 包含 GPIO 寄存器定义 #include "soc/gpio_struct.h" // 包含 GPIO 寄存器结构体 #include "NS4168/ns4168.h" #include "NS4168/wav_demux.h" // 假设要检查GPIO_NUM (例如:GPIO_NUM_12) extern void play_wav2(float volume); /**********************************************************task stack *********************************************************************/ StaticTask_t lvgl_task_tcb; // 静态内存分配的freeRTOS 任务结构,是高级的任务创建方式 StackType_t *lvgl_task_stack = NULL; // 栈单元类型 指向任务栈内存的指针 StaticTask_t feed_task_tcb; StackType_t *feed_task_stack = NULL; StaticTask_t detect_task_tcb; StackType_t *detect_task_stack = NULL; StaticTask_t change_task_tcb; StackType_t *change_task_stack = NULL; StaticTask_t bt_task_tcb; StackType_t *bt_task_stack = NULL; StaticTask_t main_task_tcb; StackType_t *main_task_stack = NULL; TaskHandle_t main_task_handle = NULL; StaticTask_t bmi_task_tcb; StackType_t *bmi_task_stack = NULL; StaticTask_t nst112_task_tcb; StackType_t *nst112_task_stack = NULL; StaticTask_t MS523_task_tcb; StackType_t *MS523_task_stack = NULL; StaticTask_t pet_task_tcb; StackType_t *pet_task_stack = NULL; StaticTask_t extract_task_tcb; StackType_t *extract_task_stack = NULL; StaticTask_t write_nvs_task_tcb; StackType_t *write_nvs_task_stack = NULL; /******************************************************* variable declaration ************************************************************************/ volatile bool g_system_init_done = false; //系统初始化完成标记 bool is_ui_mode = false; //UI_MODE 标记 /* ─── ota ─── */ static bool g_ota_pre_block_auto_sleep = false; static bool g_ota_pre_state_saved = false; static bool g_ota_lvgl_deinit_done = false; /* ─── BLE 配网失败恢复(成功不恢复) ─── */ static bool g_wifi_prov_pre_state_saved = false; static bool g_wifi_prov_lvgl_deinit_done = false; static bool g_wifi_prov_pre_block_auto_sleep = false; /* ─── 表情包升级:下载成功后的第一次切换“收尾”,不重启,不回到原来 ─── */ volatile bool g_emoji_download_in_progress = false; uint8_t *canvas_fb = NULL; char *lottie_file_path = "/extflash/0/default.json"; struct stat file_stat; static esp_websocket_client_handle_t websocket_handle = NULL; static char target_id1[17] = {0}; char filename_emoji[64] = {0}; static char client_id[13] = {0}; static uint64_t chip_id = 0; const char *TAG_MAIN = "example"; void play_wav(void); lv_ui guider_ui; // 1. 声明设置亮度的驱动函数 (防止隐式声明警告) /** OTA 下载任务已失败退出:在 WiFi 仍可能重连时,延后恢复 UI,避免早于重连完成 */ static volatile bool g_ota_recover_pending_after_fail = false; /******************************************************* functions declaration ************************************************************************/ static void get_boot_address(void); //获取启动地址 static void show_all_nvs_entries(void); //nvs区域展示 void set_block_auto_sleep(bool block); //设置自动睡眠 static void print_memory_usage(const char *step_name); /******************************************************* TOUCH ************************************************************************/ /******************************************************* 不清楚是啥 ************************************************************************/ void wifi_prov_capture_pre_state(void) { if (g_wifi_prov_pre_state_saved) return; g_wifi_prov_pre_block_auto_sleep = block_auto_sleep; g_wifi_prov_pre_state_saved = true; g_wifi_prov_lvgl_deinit_done = false; ESP_LOGI(TAG_MAIN, "已保存 BLE 配网前状态: block_auto_sleep=%d", (int)g_wifi_prov_pre_block_auto_sleep); } void wifi_prov_mark_lvgl_deinited(void) { g_wifi_prov_lvgl_deinit_done = true; } static void wifi_prov_restore_pre_state(const char *reason) { if (g_wifi_prov_pre_state_saved) { set_block_auto_sleep(g_wifi_prov_pre_block_auto_sleep); g_wifi_prov_pre_state_saved = false; } if (g_wifi_prov_lvgl_deinit_done) { ESP_LOGW(TAG_MAIN, "BLE 配网失败恢复:重建显示/UI (%s)", reason ? reason : "unknown"); (void)app_lcd_init(); (void)app_lvgl_init(); (void)app_change_init(); g_wifi_prov_lvgl_deinit_done = false; } back2default = true; ESP_LOGW(TAG_MAIN, "BLE 配网失败:已恢复配网前状态: %s", reason ? reason : "unknown"); } /******************************************************* 未确定 ************************************************************************/ static void ota_capture_pre_command_state(void) { if (g_ota_pre_state_saved) return; g_ota_pre_block_auto_sleep = block_auto_sleep; g_ota_pre_state_saved = true; g_ota_lvgl_deinit_done = false; // wait until OTA start actually de-inits LVGL ESP_LOGI(TAG_MAIN, "已保存 OTA 前状态: block_auto_sleep=%d", (int)g_ota_pre_block_auto_sleep); } static void ota_restore_pre_command_state(const char *reason) { if (g_ota_pre_state_saved) { set_block_auto_sleep(g_ota_pre_block_auto_sleep); g_ota_pre_state_saved = false; } /* OTA 退出(成功/失败/取消)后:重建 LCD/LVGL/UI,避免黑屏 */ if (g_ota_lvgl_deinit_done) { ESP_LOGW(TAG_MAIN, "OTA 恢复:重建显示/UI (%s)", reason ? reason : "unknown"); (void)app_lcd_init(); (void)app_lvgl_init(); (void)app_change_init(); g_ota_lvgl_deinit_done = false; } /* OTA 恢复后强制确保“屏已开 + 亮度非 0”。 * - 若 OTA 前/过程中进入了休眠并关屏,这里必须唤醒,否则 UI 虽重建但仍黑屏。 * - 即使未处于休眠态,也补写一次硬件亮度,避免停留在 init 阶段的 0。 */ toggle_sleep_mode(false); /* 非休眠态会直接 return,无副作用 */ if (panel_handle) { panel_qspi_amoled_set_brightness(panel_handle, g_current_brightness); } /* OTA 恢复后回到默认表情页,确保屏幕可见且状态可预期 */ back2default = true; ESP_LOGW(TAG_MAIN, "已恢复 OTA 前状态: %s", reason ? reason : "unknown"); } /******************************************************* 未确定 ************************************************************************/ static void ota_try_pending_recover_after_fail(void) { if (!g_ota_recover_pending_after_fail) { return; } if (wifi_is_connected_now()) { g_ota_recover_pending_after_fail = false; ota_restore_pre_command_state("OTA failed, WiFi back online"); return; } if (wifi_sta_reconnect_exhausted()) { g_ota_recover_pending_after_fail = false; ota_restore_pre_command_state("OTA failed, WiFi reconnect exhausted"); return; } ESP_LOGW(TAG_MAIN, "OTA 失败恢复暂缓:WiFi 仍在自动重连中"); } /** 供 ota_upgrade 任务在失败清理后调用:先排队,由 main 在 WiFi 稳定或重试用尽后再恢复 */ void ota_main_notify_recover_pending_after_fail(void) { g_ota_recover_pending_after_fail = true; set_system_event(OTA_FAIL_RECOVER_PENDING_BIT); } void notify_wifi_provision_signal_received(void) { /* WiFi 配网守卫功能已停用:保留接口兼容调用方 */ ESP_LOGI(TAG_MAIN, "已收到WiFi配网信号(守卫功能已停用)"); } /******************************************************* main task ************************************************************************/ static void main_task(void *arg) // 主任务 { ESP_LOGI(TAG_MAIN, "[DEBUG] main_task 已启动,等待队列事件 (queue=%p)", (void *)system_event_queue); uint32_t event_bits; int heartbeat_counter = 0; while (1) { //UBaseType_t uxHighWaterMark = uxTaskGetStackHighWaterMark(NULL); //ESP_LOGW(TAG_MAIN, "main_task stack high water mark: %u bytes", (unsigned)uxHighWaterMark * sizeof(StackType_t)); /* 阻塞等待队列事件,BLE 回调通过 xQueueSend 投递,避免回调上下文限制 */ BaseType_t ret = xQueueReceive(system_event_queue, &event_bits, pdMS_TO_TICKS(5000)); if (ret != pdPASS) { heartbeat_counter++; if (heartbeat_counter % 6 == 0) { ESP_LOGI(TAG_MAIN, "[HEARTBEAT] main_task alive, idle=%ds, queue_pending=%d, stack_hwm=%u", heartbeat_counter * 5, (int)uxQueueMessagesWaiting(system_event_queue), (unsigned)uxTaskGetStackHighWaterMark(NULL)); } continue; } heartbeat_counter = 0; ESP_LOGI(TAG_MAIN, "[DEBUG] main_task 收到事件 bits=0x%lX, queue_pending=%d", (unsigned long)event_bits, (int)uxQueueMessagesWaiting(system_event_queue)); if (event_bits & BLE_CONNECTED_BIT) // BLE 连接成功 { ESP_LOGI(TAG_MAIN, "处理BLE连接事件"); // 不再自动发送系统信息,改为接收到 get_system_info 请求后再返回 // 发送正在使用的表情包系列(可选,如不需要也可移除) // xTaskNotify(notification_task_handle, BLE_SEND_EMOJI_USING, eSetBits); } if (event_bits & BLE_DISCONNECTED_BIT) // BLE 断开连接 { ESP_LOGI(TAG_MAIN, "处理BLE断开事件"); if (ota_upgrade_is_running()) { if (!wifi_is_connected_now()) { if (wifi_sta_still_reconnecting()) { ESP_LOGW(TAG_MAIN, "OTA中 BLE断开且WiFi正在重连,暂不恢复,等待重连结果"); } else { ESP_LOGW(TAG_MAIN, "OTA中 BLE断开且WiFi已不可用(重连已用尽),准备恢复到OTA前状态"); set_system_event(OTA_RECOVER_REQUIRED_BIT); } } else { ESP_LOGI(TAG_MAIN, "OTA中 BLE断开但WiFi在线,继续当前OTA流程"); } } } if (event_bits & WIFI_CONNECTED_BIT) // WiFi 连接成功 { ESP_LOGI(TAG_MAIN, "处理WiFi连接事件"); // on_wifi_connected(); ota_try_pending_recover_after_fail(); } if (event_bits & WIFI_DISCONNECTED_BIT) // WiFi 断开连接 { ESP_LOGI(TAG_MAIN, "处理WiFi断开事件"); if (ota_upgrade_is_running()) { ESP_LOGW(TAG_MAIN, "OTA中 WiFi断开,先走重连流程,等待重连失败再恢复状态"); } ota_try_pending_recover_after_fail(); } if (event_bits & WEBSOCKET_CONNECTED_BIT) // websocket 连接成功 { ESP_LOGI(TAG_MAIN, "处理WebSocket连接事件"); } if (event_bits & WEBSOCKET_DISCONNECTED_BIT) // websocket 断开连接 { ESP_LOGI(TAG_MAIN, "处理WebSocketd断开事件"); } if (event_bits & WEBSOCKET_DATA_BIT) // 接收到 websocket 数据 { ESP_LOGI(TAG_MAIN, "处理WebSocket数据事件"); } if (event_bits & IMU_DATA_READY_BIT) // 读取IMU 的数据 { ESP_LOGI(TAG_MAIN, "处理传感器事件"); } if (event_bits & USER_INPUT_BIT) // 用户输入事件 { ESP_LOGI(TAG_MAIN, "处理用户输入事件"); } if (event_bits & SYSTEM_ERROR_BIT) // 处理系统错误 { ESP_LOGI(TAG_MAIN, "处理系统错误事件"); } if (event_bits & OTA_UPDATE_STARTED_BIT) // OTA 更新开始 { ESP_LOGI(TAG_MAIN, "[DEBUG] 进入 OTA 流程,准备 mqtt_main"); ESP_LOGI("main_task", "ota start!"); ota_capture_pre_command_state(); /* OTA 开始前:释放内部 RAM(LVGL/显示资源),降低 TLS/MQTT 的内部堆失败概率 */ lvgl_deinit_for_wifi(); g_ota_lvgl_deinit_done = true; set_block_auto_sleep(true); if (!Get_MQTT_FLAG()) mqtt_main(); /* 等待 MQTT 连接,带超时(60s),避免无限阻塞导致动画/触屏卡死 */ const TickType_t mqtt_wait_timeout = pdMS_TO_TICKS(60000); TickType_t wait_start = xTaskGetTickCount(); while (!Get_MQTT_FLAG()) { vTaskDelay(pdMS_TO_TICKS(500)); if ((xTaskGetTickCount() - wait_start) >= mqtt_wait_timeout) { ESP_LOGW(TAG_MAIN, "等待MQTT超时,退出OTA流程,恢复主循环"); break; } ESP_LOGI("MAIN", "等待MQTT连接..."); } bool ota_subscribe_ok = false; if (Get_MQTT_FLAG()) { ESP_LOGI(TAG_MAIN, "订阅OTA更新主题"); if (subscribe_ota() == ESP_OK) { ESP_LOGI(TAG_MAIN, "成功订阅OTA更新主题"); ota_subscribe_ok = true; } } /* MQTT 连接/发布失败时:必须把 LVGL/UI 恢复回来,否则会黑屏 */ if (!ota_subscribe_ok) { ota_restore_pre_command_state("OTA not started (MQTT/subscribe failed)"); } clear_system_event(OTA_UPDATE_STARTED_BIT); } if (event_bits & OTA_UPDATE_COMPLETED_BIT) // OTA 更新成功 { /* 成功结果已由 ota_task 经 BLE 发 1.11_A;重启由 ota_task 内 esp_restart() 执行, * 此处不恢复 LVGL/UI(避免无意义重建后马上重启) */ ESP_LOGI(TAG_MAIN, "处理OTA更新完成事件(等待 ota_task 重启,不恢复 UI)"); clear_system_event(OTA_UPDATE_COMPLETED_BIT); } if (event_bits & OTA_FAIL_RECOVER_PENDING_BIT) { clear_system_event(OTA_FAIL_RECOVER_PENDING_BIT); ota_try_pending_recover_after_fail(); } if (event_bits & OTA_RECOVER_REQUIRED_BIT) { ESP_LOGW(TAG_MAIN, "处理 OTA 恢复事件:取消当前OTA并恢复到命令前状态"); if (ota_upgrade_is_running()) { esp_err_t cancel_ret = ota_upgrade_cancel(); ESP_LOGW(TAG_MAIN, "ota_upgrade_cancel 返回: %s", esp_err_to_name(cancel_ret)); } ota_restore_pre_command_state("OTA recover required"); clear_system_event(OTA_RECOVER_REQUIRED_BIT); } if (event_bits & WIFI_BLE_PROV_FAIL_RECOVER_UI_BIT) { ESP_LOGW(TAG_MAIN, "处理 BLE 配网失败恢复事件:释放 WiFi 后重建 UI"); wifi_prov_restore_pre_state("BLE WiFi provision failed"); clear_system_event(WIFI_BLE_PROV_FAIL_RECOVER_UI_BIT); } if (event_bits & EMOJI_UPGRADE_FINALIZE_BIT) { /* 下载成功后的第一次切换:收尾升级;若切到下载包则强制停在该包 default.json */ char target = g_emoji_upgrade_last_switch_target; char downloaded = g_emoji_upgrade_downloaded_pack; ESP_LOGW(TAG_MAIN, "[EMOJI_UPGRADE] finalize: downloaded='%c', target='%c'", downloaded, target); if (downloaded != '0' && target == downloaded) { char path_buf[64]; snprintf(path_buf, sizeof(path_buf), "/extflash/%c/default.json", target); ESP_LOGW(TAG_MAIN, "[EMOJI_UPGRADE] 强制停留到目标包默认页: %s", path_buf); /* 若此前因配网/OTA 拆过 LVGL,先重建;否则 request_play_lottie_path 会失败并保持黑屏 */ if (panel_handle == NULL || change_task_handle == NULL) { ESP_LOGW(TAG_MAIN, "[EMOJI_UPGRADE] LVGL 未就绪(panel=%p, change_task=%p),先重建 UI", (void *)panel_handle, (void *)change_task_handle); /* 表情包流程已结束:全停网络栈,腾出内部 RAM/DMA 再建 LVGL(与 OTA 前拆 LVGL 对称) */ mqtt_client_shutdown(); vTaskDelay(pdMS_TO_TICKS(150)); wifi_network_shutdown_full(); vTaskDelay(pdMS_TO_TICKS(150)); (void)app_lcd_init(); (void)app_lvgl_init(); (void)app_change_init(); } /* 即使路径相同也要重播,避免“同路径跳过通知” */ lvgl_clear_current_lottie_path(); request_play_lottie_path(path_buf); } g_emoji_upgrade_downloaded_pack = '0'; g_emoji_upgrade_last_switch_target = '0'; clear_system_event(EMOJI_UPGRADE_FINALIZE_BIT); } if (event_bits & DOWNLOAD_EMOJI_BIT) // 处理下载表情事件 { char pack = ble_get_emoji_download_target_pack(); snprintf(filename_emoji, sizeof(filename_emoji), "icar/%c.zip", pack != '0' ? pack : 'A'); ESP_LOGI(TAG_MAIN, "处理下载表情事件: %s", filename_emoji); if (!g_emoji_download_in_progress) g_emoji_download_in_progress = true; if (!Get_MQTT_FLAG()) mqtt_main(); TickType_t wait_start = xTaskGetTickCount(); const TickType_t mqtt_wait_timeout = pdMS_TO_TICKS(60000); while (!Get_MQTT_FLAG()) { vTaskDelay(pdMS_TO_TICKS(500)); if ((xTaskGetTickCount() - wait_start) >= mqtt_wait_timeout) { ESP_LOGW(TAG_MAIN, "等待MQTT超时,退出表情下载流程"); break; } ESP_LOGI("MAIN", "等待MQTT连接..."); } if (Get_MQTT_FLAG()) publish_json_download(filename_emoji); clear_system_event(DOWNLOAD_EMOJI_BIT); } if (event_bits & DOWNLOAD_SUCCESS_BIT) // 下载表情成功 { ESP_LOGI(TAG_MAIN, "========== [EMOJI_DEBUG] DOWNLOAD_SUCCESS_BIT 收到 =========="); xTaskNotify(notification_task_handle, BLE_SEND_DL_EMOJI_SUCCESS, eSetBits); char pack_id = zip_extractor_get_last_downloaded_pack(); ESP_LOGI(TAG_MAIN, "[EMOJI_DEBUG] zip_extractor_get_last_downloaded_pack() 返回: '%c' (0x%02X)", pack_id, (unsigned char)pack_id); if (pack_id != '0') { esp_err_t add_ret = emoji_storage_add_downloaded_pack(pack_id); ESP_LOGI(TAG_MAIN, "[EMOJI_DEBUG] emoji_storage_add_downloaded_pack('%c') 返回: %s", pack_id, esp_err_to_name(add_ret)); } else { ESP_LOGW(TAG_MAIN, "[EMOJI_DEBUG] pack_id == '0', 跳过表情替换"); } g_emoji_upgrade_downloaded_pack = pack_id; g_emoji_upgrade_pending_finalize = true; ESP_LOGI(TAG_MAIN, "[EMOJI_UPGRADE] 下载成功: pack='%c',等待首次切换后收尾(不重启,不回原来)", pack_id); /* 托管(A):若 BLE 已断开,则不等待车机下发切换指令,系统直接完成切换到下载包并收尾 */ if (!ble_is_connected() && pack_id != '0') { ESP_LOGW(TAG_MAIN, "[EMOJI_UPGRADE] BLE 未连接,触发托管切换到 pack='%c'", pack_id); /* 1) 直接保存 using_pack,等同于收到一次切换指令 */ (void)emoji_storage_save_using_pack(pack_id); /* 2) 直接收尾:设置“最后切换目标=下载包”,并触发 finalize 强制停在 default.json */ g_emoji_upgrade_last_switch_target = pack_id; g_emoji_upgrade_pending_finalize = false; /* 防止后续真正收到切换指令时重复 finalize */ set_system_event(EMOJI_UPGRADE_FINALIZE_BIT); } g_emoji_download_in_progress = false; clear_system_event(DOWNLOAD_SUCCESS_BIT); ESP_LOGI(TAG_MAIN, "========== [EMOJI_DEBUG] DOWNLOAD_SUCCESS_BIT 处理完毕 =========="); } if (event_bits & DOWNLOAD_FAILED_BIT) { ESP_LOGI(TAG_MAIN, "处理下载表情失败事件"); xTaskNotify(notification_task_handle, BLE_SEND_DL_EMOJI_FAILED, eSetBits); g_emoji_download_in_progress = false; clear_system_event(DOWNLOAD_FAILED_BIT); } clear_system_event(event_bits); } ESP_LOGE(TAG_MAIN, "[CRITICAL] main_task while loop exited! This should never happen."); main_task_handle = NULL; vTaskDelete(NULL); } void read_mac_address() { uint8_t mac[6]; // 读取 Wi-Fi Station 接口的 MAC 地址 esp_read_mac(mac, ESP_MAC_WIFI_STA); printf("Wi-Fi Station MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); // 读取 Wi-Fi SoftAP 接口的 MAC 地址 esp_read_mac(mac, ESP_MAC_WIFI_SOFTAP); printf("Wi-Fi SoftAP MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } void read_flash_unique_id() { //地址检查 uint64_t unique_id; // 使用新API,第一个参数传入默认Flash芯片指针 esp_err_t err = esp_flash_read_unique_chip_id(esp_flash_default_chip, &unique_id); if (err == ESP_OK) { printf("Flash Unique ID: 0x%016llx\n", unique_id); } else if (err == ESP_ERR_NOT_SUPPORTED) { printf("This flash chip does not support reading Unique ID.\n"); } else { printf("Failed to read Flash Unique ID, error: %d\n", err); } } void list_files_in_dir(const char *path) { DIR *dir = opendir(path); if (dir == NULL) { printf("Failed to open directory: %s\n", path); return; } struct dirent *entry; printf("Files in '%s':\n", path); while ((entry = readdir(dir)) != NULL) { // 跳过 . 和 .. if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; // 可以进一步判断 entry->d_type == DT_REG 是否为普通文件 printf(" %s\n", entry->d_name); } closedir(dir); } /* //简单文件测试工具 void get_file_size(const char *path) { struct stat st; if (stat(path, &st) != 0) { ESP_LOGW(TAG_MAIN,"stat failed for %s\n", path); return; } float size_kb = st.st_size / 1024.0; ESP_LOGI(TAG_MAIN, "File %s size: %.2f KB", path, size_kb); } */ // 调用示例:假设FAT挂载点为基础路径 "/sdcard" void gpio_input_init(void) { gpio_config_t io_conf = { .pin_bit_mask = (1ULL << 53), // 选择要配置的引脚[reference:2] .mode = GPIO_MODE_INPUT, // 设置为输入模式[reference:3][reference:4] .pull_up_en = GPIO_PULLUP_ENABLE, // 使能内部上拉电阻[reference:5] .pull_down_en = GPIO_PULLDOWN_DISABLE, // 禁用内部下拉电阻[reference:6] .intr_type = GPIO_INTR_DISABLE, // 禁用中断[reference:7] }; gpio_config(&io_conf); // 应用配置[reference:8] } void gpio_input_init2(void) { gpio_config_t io_conf = { .pin_bit_mask = (1ULL << 54), // 选择要配置的引脚[reference:2] .mode = GPIO_MODE_INPUT, // 设置为输入模式[reference:3][reference:4] .pull_up_en = GPIO_PULLUP_ENABLE, // 使能内部上拉电阻[reference:5] .pull_down_en = GPIO_PULLDOWN_DISABLE, // 禁用内部下拉电阻[reference:6] .intr_type = GPIO_INTR_DISABLE, // 禁用中断[reference:7] }; gpio_config(&io_conf); // 应用配置[reference:8] } /******************************************************* app_main ************************************************************************/ uint32_t get_pin_func(int gpio_num)//直连MUX测试 { // 直接返回 MCU_SEL 值 return REG_GET_FIELD(IO_MUX_GPIO30_REG + gpio_num * 4, MCU_SEL); } extern bool g_wifi_state; //wifi状态 extern void test_decrypt(void); //加解密 #define ENC_A_GPIO 53 #define ENC_B_GPIO 54 #define PCNT_UNIT PCNT_UNIT_0 // 使用PCNT单元0 #define PCNT_CHANNEL PCNT_CHANNEL_0 // 使用通道0 extern void play_wav4(float volume); void loop_play_with_decreasing_volume(void) { // 1. 硬件只初始化一次 ns4168_i2s_init(); float volume = 1.0f; // 起始最大音量 float step = 0.1f; // 每次递减 0.1(可调) const float min_volume = 0.0f; // 静音阈值,小于等于此值停止 while (volume > min_volume) { // 播放当前音量的音频 play_wav2(volume); ESP_LOGI("MAIN", "播放完成,音量=%.1f,等待3秒", volume); // 延时 3 秒 vTaskDelay(pdMS_TO_TICKS(3000)); // 音量递减 volume -= step; if (volume < min_volume) volume = min_volume; // 避免负数 } ESP_LOGI("MAIN", "音量已减至零,循环结束"); } int64_t init_start_us = 0; #define MUSIC_DOWNLOAD 0 #define MUSIC_TEST 0 /* S3:下载工具固件。PC 与板子同一热点后,把 DL_HTTP_BASE 改成 ipconfig 看到的 IPv4。 * 置 1:下载 EAF 到 /extflash/eaf/。 */ #define EAF_DOWNLOAD 1 #define DL_HTTP_BASE "http://10.70.24.125:8000/" #define DL_WIFI_SSID "REDMIK80U" #define DL_WIFI_PASS "33333333" #if EAF_DOWNLOAD static const char *const s_eaf_files[] = { "maomi_xizao_304x240.eaf", "dog_test_304x240.eaf", }; #endif void play_wav3(float volume); extern void play_wav5(float volume); extern void play_wav_p4(float volume); extern void play_wav_ring_with_volume(float volume); void app_main(void) { //app_sr_init(); #if EAF_DOWNLOAD ESP_LOGI(TAG_MAIN, "资源下载模式:EAF -> /extflash/eaf/"); ESP_LOGI(TAG_MAIN, "WiFi SSID=%s HTTP=%s", DL_WIFI_SSID, DL_HTTP_BASE); flash_main(); if (mkdir("/extflash/eaf", 0777) < 0 && errno != EEXIST) { ESP_LOGE(TAG_MAIN, "mkdir /extflash/eaf 失败: %s", strerror(errno)); } print_memory_usage("WIFI初始化前"); wifi_main(DL_WIFI_SSID, DL_WIFI_PASS); { int waited_ms = 0; const int wait_limit_ms = 45000; while (!wifi_is_connected_now() && waited_ms < wait_limit_ms) { vTaskDelay(pdMS_TO_TICKS(500)); waited_ms += 500; } if (!wifi_is_connected_now()) { ESP_LOGE(TAG_MAIN, "WiFi 在 %d ms 内未拿到 IP,放弃下载", wait_limit_ms); } else { for (size_t i = 0; i < sizeof(s_eaf_files) / sizeof(s_eaf_files[0]); i++) { char url[192]; char rel[96]; snprintf(url, sizeof(url), "%s%s", DL_HTTP_BASE, s_eaf_files[i]); snprintf(rel, sizeof(rel), "eaf/%s", s_eaf_files[i]); ESP_LOGI(TAG_MAIN, "开始下载 %s -> /extflash/%s", url, rel); if (download_file(url, rel) != ESP_OK) { ESP_LOGE(TAG_MAIN, "下载失败: %s", s_eaf_files[i]); } } } } list_files_in_directory("/extflash/eaf"); ESP_LOGI(TAG_MAIN, "验收:maomi_xizao_304x240.eaf=719328 dog_test_304x240.eaf=1711719"); while (1) { vTaskDelay(pdMS_TO_TICKS(10000)); } #else app_lcd_init(); ESP_ERROR_CHECK(app_lvgl_init()); #endif //MS523_init(); //NST112_init(); //BMI_init(); #if MUSIC_TEST flash_main(); ns4168_i2s_init(); while (1) { float volume = 1.0f; while (volume > 0.0f) { play_wav2(volume); vTaskDelay(pdMS_TO_TICKS(3000)); volume -= 0.1f; ESP_LOGW("TEST MODE","当前音量 :%.1f",volume); } // 一轮结束,下一轮自动从头开始(音量重置) } #endif //ns4168_start_white_noise_test(); //play_wav(); /* flash_main(); int res = mkdir("/extflash/fonts", 0777); if (res < 0) { ESP_LOGE("example", "mkdir 失败: %s", strerror(errno)); } else { ESP_LOGI("example", "目录创建成功: /extflash/fonts"); } rename("/extflash/0/MiSans_VF_25.bin", "/extflash/fonts/MiSans_VF_25.bin"); rename("/extflash/0/MiSans_VF_30.bin", "/extflash/fonts/MiSans_VF_30.bin"); rename("/extflash/0/MiSans_VF_40.bin", "/extflash/fonts/MiSans_VF_40.bin"); rename("/extflash/0/MiSans_VF_60.bin", "/extflash/fonts/MiSans_VF_60.bin"); rename("/extflash/0/MiSans_VF_100.bin", "/extflash/fonts/MiSans_VF_100.bin"); rename("/extflash/0/MiSans_VF_180.bin", "/extflash/fonts/MiSans_VF_180.bin"); rename("/extflash/0/iconfont_25.bin", "/extflash/fonts/iconfont_25.bin"); list_files_in_dir("/extflash/fonts");*/ #if MUSIC_DOWNLOAD flash_main(); print_memory_usage("WIFI初始化前"); wifi_main("REDMIK80U","33333333"); vTaskDelay(pdMS_TO_TICKS(10000)); download_file("http://10.70.24.125:8000/ring2.wav","ring2.wav"); list_files_in_dir("/extflash/0/"); #endif /* mkdir("/extflash/fonts", 0755); list_files_in_dir("/extflash/fonts"); list_files_in_dir("/extflash/0"); rename("/extflash/0/MiSans_VF_25.bin", "/extflash/fonts/MiSans_VF_25.bin"); rename("/extflash/0/MiSans_VF_30.bin", "/extflash/fonts/MiSans_VF_30.bin"); rename("/extflash/0/MiSans_VF_40.bin", "/extflash/fonts/MiSans_VF_40.bin"); rename("/extflash/0/MiSans_VF_60.bin", "/extflash/fonts/MiSans_VF_60.bin"); rename("/extflash/0/MiSans_VF_100.bin", "/extflash/fonts/MiSans_VF_100.bin"); rename("/extflash/0/MiSans_VF_180.bin", "/extflash/fonts/MiSans_VF_180.bin"); rename("/extflash/0/iconfont_25.bin", "/extflash/fonts/iconfont_25.bin"); */ //flash_main(); //list_files_in_dir("/extflash/fonts"); //list_files_in_dir("/extflash/0"); //rename("/extflash/0/MiSans_VF_180.bin", "/extflash/fonts/MiSans_VF_180.bin"); //app_sr_init(); //flash_main(); //ble_main(); //vTaskDelay(pdMS_TO_TICKS(500)); //ble_app_advertise_after_system_init(); //ESP_ERROR_CHECK(appBMI_init();_lcd_init()); #if EXAMPLE_USE_TOUCH //ESP_ERROR_CHECK(app_touch_init()); #endif //app_touch_init(); //ESP_ERROR_CHECK(app_change_init()); //NST112_init(); //app_touch_init(); //ES7243E_Init(); //ESP_ERROR_CHECK(nvs_flash_init()); //read_mac_address(); //read_flash_unique_id(); //wifi_main("REDMIK80U","33333333"); //pet_init(); /* while (1) { ESP_LOGW("TAG","WIFI_STATE:%d",g_wifi_state); vTaskDelay(pdMS_TO_TICKS(500)); } */ //motor_control_init(); //ESP_ERROR_CHECK(foc_openloop_start()); //gpio_input_init(); //gpio_input_init2(); /* pcnt_config_t pcnt_config = { .pulse_gpio_num = ENC_A_GPIO, // A相作为脉冲输入 .ctrl_gpio_num = ENC_B_GPIO, // B相作为控制输入 // 关键:设置计数规则[reference:10] // 正转(A相超前B相90°):A上升沿时B为低,计数+1;A下降沿时B为高,计数+1 .pos_mode = PCNT_COUNT_INC, // 上升沿计数增加 .neg_mode = PCNT_COUNT_INC, // 下降沿计数增加 .lctrl_mode = PCNT_MODE_REVERSE, // 控制脚为低时,反向计数(即增加) .hctrl_mode = PCNT_MODE_KEEP, // 控制脚为高时,保持计数方向(即增加) .counter_h_lim = 32767, .counter_l_lim = -32768, .unit = PCNT_UNIT, .channel = PCNT_CHANNEL, }; pcnt_unit_config(&pcnt_config); pcnt_set_filter_value(PCNT_UNIT, 50); // 设置滤波阈值(APB时钟周期数) pcnt_filter_enable(PCNT_UNIT); // 3. 启动PCNT pcnt_counter_resume(PCNT_UNIT); pcnt_counter_clear(PCNT_UNIT); // 4. 读取计数值 int16_t count = 0; while (1) { pcnt_get_counter_value(PCNT_UNIT, &count); //printf("Encoder Count: %d\n", count); if(count>30000||count<-30000){ count=0; } ESP_LOGW("TAG","A:%d",gpio_get_level(ENC_A_GPIO)); ESP_LOGW("TAG","B:%d",gpio_get_level(ENC_B_GPIO)); vTaskDelay(pdMS_TO_TICKS(50)); } */ //test_decrypt(); //ble_main(); //wifi_main("REDMIK80U","33333333"); //vTaskDelay(pdMS_TO_TICKS(10000)); //obtain_time(); //print_date_time(); /* while (1) { time_t now; struct tm timeinfo; time(&now); localtime_r(&now, &timeinfo); ESP_LOGI("NTP", "当前时间: %02d:%02d:%02d", timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec); vTaskDelay(10000 / portTICK_PERIOD_MS); } */ /* //esp_log_level_set("i2c.master", ESP_LOG_NONE); const int64_t init_start_us = esp_timer_get_time(); //flash_main(); ESP_ERROR_CHECK(nvs_flash_init()); if (emoji_storage_load() != ESP_OK) { ESP_LOGW(TAG_MAIN, "表情包存储加载失败,使用默认"); } //extract_json_zip("0.zip"); //list_files_in_dir("/extflash"); if (!init_event_system()) { ESP_LOGE(TAG_MAIN, "事件系统初始化失败"); ESP_LOGE(TAG_MAIN, "设备初始化失败前已耗时: %" PRId64 " ms", (esp_timer_get_time() - init_start_us) / 1000); return; } ESP_ERROR_CHECK(app_lcd_init()); #if EXAMPLE_USE_TOUCH //ESP_ERROR_CHECK(app_touch_init()); #endif app_touch_init(); //NST112_init(); //print_memory_usage("LCD初始化完成"); //load_brightness_from_nvs(); //BMI_init(); //vTaskDelay(pdMS_TO_TICKS(10000)); //app_sr_init(); print_memory_usage("APP初始化完成"); const int64_t init_elapsed_us = esp_timer_get_time() - init_start_us; ESP_LOGI(TAG_MAIN, "设备初始化耗时: %" PRId64 " ms (%" PRId64 " us)", init_elapsed_us / 1000, init_elapsed_us); //motor_control_init(); //ESP_ERROR_CHECK(foc_openloop_start()); BaseType_t ret = xTaskCreatePinnedToCore(main_task, "MAIN", MAIN_TASK_STACK_SIZE, NULL, MAIN_TASK_PRIORITY, &main_task_handle,1); if (ret == pdPASS) { print_memory_usage("主任务创建(内部RAM)"); ESP_LOGI(TAG_MAIN, "[DEBUG] main_task_handle=%p (内部RAM栈)", (void *)main_task_handle); } else { ESP_LOGW(TAG_MAIN, "内部RAM不足(需%d bytes),尝试SPIRAM创建main_task", MAIN_TASK_STACK_SIZE); size_t stack_words = MAIN_TASK_STACK_SIZE / sizeof(StackType_t); main_task_stack = heap_caps_malloc(MAIN_TASK_STACK_SIZE, MALLOC_CAP_SPIRAM); if (main_task_stack) { main_task_handle = xTaskCreateStaticPinnedToCore( main_task, "MAIN", stack_words, NULL, MAIN_TASK_PRIORITY, main_task_stack, &main_task_tcb, 1); if (main_task_handle) { // print_memory_usage("主任务创建(SPIRAM)"); ESP_LOGI(TAG_MAIN, "[DEBUG] main_task_handle=%p (SPIRAM栈)", (void *)main_task_handle); } else { ESP_LOGE(TAG_MAIN, "[CRITICAL] main_task SPIRAM创建也失败!"); heap_caps_free(main_task_stack); main_task_stack = NULL; } } else { ESP_LOGE(TAG_MAIN, "[CRITICAL] 无法为main_task分配SPIRAM栈!"); } } //print_memory_usage("MAIN初始化完成"); ble_main(); print_memory_usage("蓝牙初始化完成"); g_system_init_done = true; ESP_ERROR_CHECK(app_lvgl_init()); //ESP_ERROR_CHECK(app_change_init()); const int64_t init_end_us = esp_timer_get_time(); //下载json压缩包,解压到外部闪存,准备表情包资源 //remove("/extflash/0.zip"); //list_files_in_dir("/extflash/0"); //list_files_in_dir("/extflash/0/A"); //get_file_size("/extflash/0.zip"); */ /* mkdir("/extflash/fonts", 0755); rename("/extflash/0/MiSans_VF_25.bin", "/extflash/fonts/MiSans_VF_25.bin"); rename("/extflash/0/MiSans_VF_30.bin", "/extflash/fonts/MiSans_VF_30.bin"); rename("/extflash/0/MiSans_VF_40.bin", "/extflash/fonts/MiSans_VF_40.bin"); rename("/extflash/0/MiSans_VF_60.bin", "/extflash/fonts/MiSans_VF_60.bin"); rename("/extflash/0/MiSans_VF_100.bin", "/extflash/fonts/MiSans_VF_100.bin"); rename("/extflash/0/iconfont_25.bin", "/extflash/fonts/iconfont_25.bin"); */ //ESP_LOGI("HEX", "文件名长度: %d", strlen("2hours.json")); //ESP_LOG_BUFFER_HEX("FILENAME", "2hours.json", strlen("2hours.json")); // 1. 检查父路径 /extflash/0 /* struct stat st; if (stat("/extflash/0", &st) == 0) { if (S_ISDIR(st.st_mode)) { ESP_LOGI("DIAG", "/extflash/0 是一个目录 ✓"); } else if (S_ISREG(st.st_mode)) { ESP_LOGE("DIAG", "/extflash/0 是一个普通文件 ✗ 这就是问题!"); } else { ESP_LOGE("DIAG", "/extflash/0 存在但不是目录也不是普通文件"); } } else { ESP_LOGE("DIAG", "/extflash/0 不存在,errno=%d", errno); } // 2. 尝试在根目录创建同名文件 FILE *test = fopen("/extflash/text.bin", "wb"); if (test) { ESP_LOGI("DIAG", "在根目录创建文件成功 ✓ 说明文件名本身合法"); fclose(test); unlink("/extflash/text.bin"); } else { ESP_LOGE("DIAG", "在根目录也失败,errno=%d", errno); } list_files_in_dir("/extflash"); uint32_t reg_val = REG_READ(IO_MUX_GPIO0_REG); ESP_LOGI(TAG_MAIN, "IO_MUX_GPIO0_REG: 0x%d", reg_val); reg_val = REG_READ(IO_MUX_GPIO29_REG); ESP_LOGI(TAG_MAIN, "IO_MUX_GPIO29_REG: 0x%d", reg_val); reg_val = REG_READ(IO_MUX_GPIO30_REG); ESP_LOGI(TAG_MAIN, "IO_MUX_GPIO30_REG: 0x%d", reg_val); reg_val = REG_READ(IO_MUX_GPIO39_REG); ESP_LOGI(TAG_MAIN, "IO_MUX_GPIO39_REG: 0x%d", reg_val); */ //vTaskDelay(pdMS_TO_TICKS(5000)); //motor_post_play_sticker_direction(0x02); //vTaskDelay(pdMS_TO_TICKS(5000)); //motor_post_play_sticker_direction(0x0A); //vTaskDelay(pdMS_TO_TICKS(5000)); //motor_post_play_sticker_direction(0x01); //vTaskDelay(pdMS_TO_TICKS(2000)); print_memory_usage("系统初始化完成"); } static void get_boot_address(void) { // 1. 获取「当前设置的启动分区」(下次重启会从这个分区启动) const esp_partition_t *boot_part = esp_ota_get_boot_partition(); if (boot_part) { ESP_LOGE(TAG_MAIN, "===== 当前启动分区信息 ====="); ESP_LOGE(TAG_MAIN, "分区名称(标识):%s", boot_part->label); // 如 factory/ota_0/ota_1 ESP_LOGE(TAG_MAIN, "Flash物理起始地址:0x%08X", (unsigned int)boot_part->address); // 核心:启动物理地址 ESP_LOGE(TAG_MAIN, "分区大小:0x%08X 字节", (unsigned int)boot_part->size); ESP_LOGE(TAG_MAIN, "分区类型:%s(App分区=0x00)", boot_part->type == ESP_PARTITION_TYPE_APP ? "App分区" : "其他分区"); } // 2. 获取「当前正在运行的分区」(和启动分区可能一致,OTA后重启才会变) const esp_partition_t *running_part = esp_ota_get_running_partition(); if (running_part) { ESP_LOGE(TAG_MAIN, "===== 当前运行分区信息 ====="); ESP_LOGE(TAG_MAIN, "分区名称(标识):%s", running_part->label); ESP_LOGE(TAG_MAIN, "Flash物理起始地址:0x%08X", (unsigned int)running_part->address); } // 3. 单独获取Factory分区的地址(精准定位出厂分区) const esp_partition_t *factory_part = esp_partition_find_first( ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_FACTORY, NULL); if (factory_part) { ESP_LOGE(TAG_MAIN, "===== Factory分区信息 ====="); ESP_LOGE(TAG_MAIN, "Flash物理起始地址:0x%08X", (unsigned int)factory_part->address); } } static void show_all_nvs_entries(void) { nvs_iterator_t iter = NULL; esp_err_t err = nvs_entry_find(NVS_DEFAULT_PART_NAME, NULL, NVS_TYPE_ANY, &iter); if (err != ESP_OK || iter == NULL) { ESP_LOGW("NVS", "NVS 为空,没有数据"); return; } ESP_LOGI("NVS", "=================== NVS 全部内容 ===================="); while (err == ESP_OK) { nvs_entry_info_t info; nvs_entry_info(iter, &info); ESP_LOGI("NVS", "命名空间:%-12s key: %-15s 类型 :%d", info.namespace_name, info.key, info.type); err = nvs_entry_next(&iter); } ESP_LOGI("NVS", "====================================================="); nvs_release_iterator(iter); } // 内存监控辅助函数 static void print_memory_usage(const char *step_name) // 打印内存使用情况 { // 内部SRAM uint32_t internal_sram_total = heap_caps_get_total_size(MALLOC_CAP_INTERNAL); uint32_t internal_sram_free = heap_caps_get_free_size(MALLOC_CAP_INTERNAL); uint32_t internal_sram_used = internal_sram_total - internal_sram_free; // PSRAM uint32_t psram_total = heap_caps_get_total_size(MALLOC_CAP_SPIRAM); uint32_t psram_free = heap_caps_get_free_size(MALLOC_CAP_SPIRAM); uint32_t psram_used = psram_total - psram_free; // 总SRAM(内部+PSRAM) uint32_t total_ram_total = internal_sram_total + psram_total; uint32_t total_ram_free = internal_sram_free + psram_free; ESP_LOGI(TAG_MAIN, "=== %s 后内存使用情况 ===", step_name); ESP_LOGI(TAG_MAIN, "内部SRAM: 已用 %8" PRIu32 " bytes, 剩余 %8" PRIu32 " bytes, 总计 %8" PRIu32 " bytes", internal_sram_used, internal_sram_free, internal_sram_total); ESP_LOGI(TAG_MAIN, "PSRAM: 已用 %8" PRIu32 " bytes, 剩余 %8" PRIu32 " bytes, 总计 %8" PRIu32 " bytes", psram_used, psram_free, psram_total); ESP_LOGI(TAG_MAIN, "总内存: 已用 %8" PRIu32 " bytes, 剩余 %8" PRIu32 " bytes, 总计 %8" PRIu32 " bytes", internal_sram_used + psram_used, total_ram_free, total_ram_total); ESP_LOGI(TAG_MAIN, "===================="); }