Files
P4-USB-transfer/main/main.c
T

1120 lines
43 KiB
C
Raw Normal View History

2026-10-13 21:54:50 +08:00
/*****************************************************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, "====================");
}