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

Co-authored-by: Cursor <[email protected]>
2026-10-13 21:54:50 +08:00

1120 lines
43 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 files **************************************************************************/
#include "LVGL/lvgl_init.h"
#include "FLASH/flash.h"
#include <inttypes.h>
#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 <sys/stat.h>
#include <errno.h>
#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, "====================");
}