初始提交卡包 USB 工程

纳入固件源码、屏幕组件、nand_xfer 工具和文档。忽略本机构建产物、sdkconfig,以及含本机路径的依赖锁。

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
zhengxin
2026-10-13 21:54:50 +08:00
co-authored by Cursor
commit 851e347cf5
260 changed files with 269349 additions and 0 deletions
+54
View File
@@ -0,0 +1,54 @@
#include "crc.h"
#include "config.h"
// 蓝牙标准CRC16-CCITT多项式
#define CRC16_CCITT_POLY 0x1021
// 增量式更新CRC(核心函数)
uint16_t crc16_ccitt_update(uint16_t crc_prev, uint8_t data)
{
// 将当前字节与CRC高8位异或
crc_prev ^= (uint16_t)data << 8;
// 逐位处理8个bit
for (uint8_t i = 0; i < 8; i++)
{
// 判断最高位是否为1
if (crc_prev & 0x8000)
{
// 左移1位后异或多项式
crc_prev = (crc_prev << 1) ^ CRC16_CCITT_POLY;
}
else
{
// 仅左移1位
crc_prev <<= 1;
}
}
return crc_prev;
}
// 完整数据CRC计算
uint16_t crc16_ccitt_calculate(const uint8_t *data, size_t length)
{
// 入参校验:空指针/长度0直接返回初始值
if (data == NULL || length == 0)
{
return 0xFFFF;
}
// 初始化CRC为蓝牙标准初始值
uint16_t crc = 0xFFFF;
// 遍历所有字节计算
for (size_t i = 0; i < length; i++)
{
crc = crc16_ccitt_update(crc, data[i]);
}
// 返回最终CRC值(无反转、无异或)
return crc;
}
+13
View File
@@ -0,0 +1,13 @@
#ifndef __CRC_H__
#define __CRC_H__
#include "config.h"
#include <stdint.h>
#include <stdio.h>
uint16_t crc16_ccitt_update(uint16_t crc_prev, uint8_t data);
uint16_t crc16_ccitt_calculate(const uint8_t *data, size_t length);
#endif
+1568
View File
File diff suppressed because it is too large Load Diff
+87
View File
@@ -0,0 +1,87 @@
#ifndef _BLE_H_
#define _BLE_H_
#include "esp_err.h"
#include <stdbool.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#ifdef __cplusplus
extern "C" {
#endif
/* NimBLE 在 ble_hs.c 中实现,公开 host/ble_hs.h 未导出 */
void ble_hs_lock(void);
void ble_hs_unlock(void);
extern const char *BLE_TAG;
extern bool notify_enabled;
extern volatile bool sentinel_mode_is_active;
extern char s_emoji_delete_target_pack;
extern TaskHandle_t notification_task_handle;
extern TaskHandle_t sentinel_task_handle;
// 设备名称
#define DEVICE_NAME "咔宝"
#define MAX_NOTIFY_LEN 256
// BLE 特征值
#define BLE_SERVICE_UUID 0xFFFF
#define BLE_CHAR_UUID_TX 0xFF01 // ESP32发送,APP接收
#define BLE_CHAR_UUID_RX 0xFF02 // APP发送,ESP32接收
#define BLE_CHAR_UUID_NOTIFY 0xFF03 // 通知特征
#define SENTINEL_TASK_PRIORITY 5
#define SENTINEL_TURN_ON 0x01
#define SENTINEL_TURN_OFF 0x02
#define SENTINEL_LISTEN_START_PULSE 0x04
#define SENTINEL_LISTEN_STOP_PULSE 0x08
#define SENTINEL_PULSE_TIMEOUT_MS 3000
#define SENTINEL_TOTAL_DURATION_MS (20 * 60 * 1000)
#define BLE_SEND_PROGRESS (1<<0)
#define BLE_SEND_VERSION (1<<1)
#define BLE_SEND_DL_EMOJI_FAILED (1<<2)
#define BLE_SEND_DL_EMOJI_SUCCESS (1<<3)
#define BLE_SEND_DOWNLOADED_PACKS (1<<4)
#define BLE_SEND_WIFI_STATE (1<<5)
#define BLE_SEND_EMOJI_USING (1<<6)
#define BLE_SEND_EMOJI_EXCHANGE_RESULT (1<<7)
#define BLE_SEND_OTA_RESULT (1<<8)
#define BLE_SEND_EMOJI_DELETE_RESULT (1<<9)
#define BLE_SEND_EMOJI_CLEAR_RESULT (1<<10)
/** 仅发送 Feedback 2.2A brightness 单帧(如设置页亮度滑条松手),由 notification_task 发送 */
#define BLE_SEND_BRIGHTNESS (1<<11)
/** 表情包下载成功后,等待首次切换指令做“收尾”(不重启,停留在目标包) */
extern volatile bool g_emoji_upgrade_pending_finalize;
/** 本次下载成功的 pack('A'/'B'/'C'/'D',或 '0') */
extern volatile char g_emoji_upgrade_downloaded_pack;
/** 最近一次收到的“切换表情包”目标 pack(供收尾逻辑判断) */
extern volatile char g_emoji_upgrade_last_switch_target;
/** 请求通过 BLE 发送 OTA 升级进度 1.11_B(0~100),由 notification_task 实际发送 */
void ble_send_ota_progress(uint8_t progress);
/** 请求通过 BLE 发送 OTA 升级结果 1.11_A(0=失败,1=成功),由 notification_task 实际发送 */
void ble_send_ota_result_request(uint8_t success);
/** 设置本次「切换表情包」指令的目标 Pack_id,供 notification_task 发送切换结果前执行保存 */
void set_emoji_exchange_target_pack(char pack_id);
/** 开始 BLE 广播(GAP 回调等 NimBLE 上下文可直接调用) */
void ble_app_advertise(void);
/** 整机初始化完成后首次起广播:从 app_main 等任务调用,内部持 ble_hs_lock */
void ble_app_advertise_after_system_init(void);
/** 设置本次「删除表情包」指令的目标 Pack_id,供 notification_task 发送删除结果前执行删除 */
void ble_set_emoji_delete_target(char pack_id);
void ble_main(void);
void send_notification_to_app(const char *message);
/** 请求通过 BLE 发送 WiFi 状态反馈(连接成功 0x01 / 断开 0x00),由 notification_task 实际发送 */
void ble_send_wifi_state_request(uint8_t connected);
void send_protocol_frame(const uint8_t *data, size_t len);
bool ble_is_connected(void);
#ifdef __cplusplus
}
#endif
#endif /* _BLE_H_ */
File diff suppressed because it is too large Load Diff
+43
View File
@@ -0,0 +1,43 @@
#ifndef __BLE_ANSY_H__
#define __BLE_ANSY_H__
#include "config.h"
#include <stdint.h>
#include <stdio.h>
#define play_sticker (uint16_t)0x0001
#define sentinel_mode (uint16_t)0x0002
#define exit_all_modes (uint16_t)0x0003
#define set_brightness (uint16_t)0x0004
#define date_info (uint16_t)0x0005
#define time_info (uint16_t)0x0006
#define city_info (uint16_t)0x0007
#define w_t_info (uint16_t)0x0008
#define wifi_config (uint16_t)0x0009
#define ota_request (uint16_t)0x000A
#define emoji_pack_download (uint16_t)0x000B
#define ota_cancel (uint16_t)0x000C /* 1.13 OTA 升级取消 */
#define speed_gear (uint16_t)0x000D /* V2 车机→设备:档位(1)+速度(2BE,KM/h),见 ble_data_ansy */
#define get_sys_info (uint16_t)0x0010
#define emojo_cmd (uint16_t)0x0020
#define clear_emojo (uint16_t)0x0021
#define reboot (uint16_t)0x0022 /* 重启设备 */
int ble_data_ansy(uint8_t *input, size_t length);
int ble_wv_data_ansy2(uint8_t *input, size_t length);
/** 1.12 表情包下载:获取本次要下载的目标系列 (A/B/C/D),由指令 0x000B 的 Target_pack 解析得到 */
char ble_get_emoji_download_target_pack(void);
/**
* UI 页面创建完成后调用:回放在 UI 未就绪阶段缓存的日期/时间。
*/
void ble_datetime_flush_cached_to_ui(void);
/**
* UI 页面创建完成后调用:回放在 UI 未就绪阶段缓存的天气数据(当前/+2h/+4h)。
*/
void ble_weather_flush_cached_to_ui(void);
#endif
+588
View File
@@ -0,0 +1,588 @@
#include "ble_send.h"
#include "ble.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "config.h"
#include "esp_log.h"
#include "ALGORITHM/crc.h"
#include "EMOJI_STORAGE/emoji_storage.h"
#include "event.h"
/* 统一亮度真源(定义在 main.c) */
extern int get_system_brightness_percent(void);
volatile uint8_t s_ota_progress_to_send = 0;
volatile bool g_emoji_upgrade_pending_finalize = false;
volatile char g_emoji_upgrade_downloaded_pack = '0';
volatile char g_emoji_upgrade_last_switch_target = '0';
static uint8_t s_wifi_state_to_send = 0;
bool g_wifi_state = 0;
static uint8_t s_ota_result_to_send = 0;
static char s_emoji_switch_target_pack = '0';
volatile uint8_t g_brightness = 85;
void notification_task(void *arg)//上报任务
{
ESP_LOGI(BLE_TAG, "通知任务启动");
uint32_t bits = 0;
BaseType_t rec;
while (1)
{
rec = xTaskNotifyWait(0, ULONG_MAX, &bits, portMAX_DELAY);//永久等待通知
ESP_LOGI("NOTIFICATION", "recv notify");
if (rec != pdPASS)
{
ESP_LOGE("NOTIFICATION", "接受任务通知失败");
continue;
}
if (bits & BLE_SEND_PROGRESS)//BLE 发送OTA 升级进度
{
ESP_LOGI("NOTIFICATION", "recv BLE_SEND_PROGRESS, 发送OTA进度(十进制)=%d%%", (int)s_ota_progress_to_send);
notify_enabled = 1;
send_ota_progress_frame(s_ota_progress_to_send);
}
else if (bits & BLE_SEND_OTA_RESULT)//返回OTA 升级结果
{
ESP_LOGI("NOTIFICATION", "recv BLE_SEND_OTA_RESULT, 发送OTA结果 %s", s_ota_result_to_send ? "成功" : "失败");
notify_enabled = 1;
send_ota_result_frame(s_ota_result_to_send);
}
else if (bits & BLE_SEND_VERSION) // 收到 get_system_info (0x0010) 请求后发送版本号、亮度和系统状态
{
ESP_LOGI("NOTIFICATION", "recv BLE_SEND_VERSION (get_system_info 请求)");
int current_brightness_percent = get_system_brightness_percent();
if (current_brightness_percent < 0)
current_brightness_percent = 0;
if (current_brightness_percent > 100)
current_brightness_percent = 100;
g_brightness = (uint8_t)current_brightness_percent; /* 保持兼容镜像 */
notify_enabled = 1;
// 发送3个协议反馈帧
// feedback2.1A: get_system_ok
send_get_system_ok_frame();
vTaskDelay(pdMS_TO_TICKS(10)); // 短暂延时,避免发送过快
// feedback2.2A: brightness
send_brightness_frame((uint8_t)current_brightness_percent);
vTaskDelay(pdMS_TO_TICKS(10));
// feedback2.3A: version
send_version_frame(DEVICE_FIRMWARE_VERSION);
vTaskDelay(pdMS_TO_TICKS(10));
// 1.10_A: 返回当前 WiFi 状态(配网成功为 0x01,未连为 0x00)
send_wifi_state_frame(s_wifi_state_to_send);
ESP_LOGI(BLE_TAG, "已发送get_system_info反馈: version=%s, 亮度=%u, WiFi状态=%s", DEVICE_FIRMWARE_VERSION, (unsigned int)g_brightness, s_wifi_state_to_send ? "已连接" : "断开");
}
else if (bits & BLE_SEND_BRIGHTNESS)
{
ESP_LOGI("NOTIFICATION", "recv BLE_SEND_BRIGHTNESS");
int current_brightness_percent = get_system_brightness_percent();
if (current_brightness_percent < 0)
current_brightness_percent = 0;
if (current_brightness_percent > 100)
current_brightness_percent = 100;
g_brightness = (uint8_t)current_brightness_percent;
notify_enabled = 1;
send_brightness_frame((uint8_t)current_brightness_percent);
}
else if (bits & BLE_SEND_DL_EMOJI_FAILED)//返回下载表情失败
{
ESP_LOGI("NOTIFICATION", "BLE_SEND_DL_EMOJI_FAILED");
notify_enabled = 1;
send_emoji_download_result_frame(0);
}
else if (bits & BLE_SEND_DL_EMOJI_SUCCESS)//返回下载表情成功
{
ESP_LOGI("NOTIFICATION", "BLE_SEND_DL_EMOJI_SUCCESS");
notify_enabled = 1;
send_emoji_download_result_frame(1);
}
else if (bits & BLE_SEND_DOWNLOADED_PACKS)//返回已下载表情包列表
{
ESP_LOGI("NOTIFICATION", "BLE_SEND_DOWNLOADED_PACKS");
notify_enabled = 1;
send_downloaded_packs_frame();
}
else if (bits & BLE_SEND_WIFI_STATE)//返回WiFi状态
{
ESP_LOGI("NOTIFICATION", "BLE_SEND_WIFI_STATE, 主动发送 1.10_A WiFi状态=%s", s_wifi_state_to_send ? "已连接(0x01)" : "断开(0x00)");
notify_enabled = 1;
send_wifi_state_frame(s_wifi_state_to_send);
}
else if (bits & BLE_SEND_EMOJI_EXCHANGE_RESULT)//返回表情包切换结果
{
ESP_LOGI("NOTIFICATION", "BLE_SEND_EMOJI_EXCHANGE_RESULT");
notify_enabled = 1;
esp_err_t err = emoji_storage_save_using_pack(s_emoji_switch_target_pack);
send_emoji_switch_result_frame(err == ESP_OK ? 1 : 0);
ESP_LOGI("NOTIFICATION", "g_emoji_upgrade_pending_finalize = %d", (int)g_emoji_upgrade_pending_finalize);
if (g_emoji_upgrade_pending_finalize)
{
/* 下载成功后第一次切换:收尾升级(不重启,不回到原来) */
g_emoji_upgrade_pending_finalize = false;
g_emoji_upgrade_last_switch_target = s_emoji_switch_target_pack;
set_system_event(EMOJI_UPGRADE_FINALIZE_BIT);
}
}
else if (bits & BLE_SEND_EMOJI_USING)//返回正在使用的表情包
{
ESP_LOGI("NOTIFICATION", "BLE_SEND_EMOJI_USING");
notify_enabled = 1;
send_emoji_using_frame(emoji_storage_get_using_pack());
}
else if (bits & BLE_SEND_EMOJI_DELETE_RESULT)//返回删除表情包结果
{
ESP_LOGI("NOTIFICATION", "BLE_SEND_EMOJI_DELETE_RESULT");
notify_enabled = 1;
emoji_storage_remove_pack_files(s_emoji_delete_target_pack);
esp_err_t err = emoji_storage_remove_downloaded_pack(s_emoji_delete_target_pack);
send_emoji_delete_result_frame(err == ESP_OK ? 1 : 0);
}
else if (bits & BLE_SEND_EMOJI_CLEAR_RESULT)
{
ESP_LOGI("NOTIFICATION", "BLE_SEND_EMOJI_CLEAR_RESULT");
notify_enabled = 1;
esp_err_t err = emoji_storage_clear_all_packs();
send_emoji_clear_result_frame(err == ESP_OK ? 1 : 0);
}
else
{
}
}
}
/* Feedback 3.4 返回删除表情包结果 0x0020 op=0x03:帧头(1)+类(2)+指令(2)+op(1)+Exchange_result(1)+CRC16(2)+帧尾(1)=10 */
void send_emoji_delete_result_frame(uint8_t success)
{
uint8_t frame[10] = {0};
uint16_t crc16;
frame[0] = 0xAA;
frame[1] = 0xFF;
frame[2] = 0xFF;
frame[3] = 0x00;
frame[4] = 0x20;
frame[5] = 0x03; /* op=0x03 删除结果 */
frame[6] = success ? 0x01 : 0x00;
crc16 = crc16_ccitt_calculate(frame, 7);
frame[7] = (crc16 >> 8) & 0xFF;
frame[8] = crc16 & 0xFF;
frame[9] = 0x55;
send_protocol_frame(frame, sizeof(frame));
ESP_LOGI(BLE_TAG, "发送Feedback3.4删除表情包结果: %s, 发送 CRC16=0x%04X", success ? "成功(0x01)" : "失败(0x00)", crc16);
}
/* Feedback clear_emojo 返回清除所有表情包结果 0x0021:帧头(1)+类(2)+指令(2)+result(1)+CRC16(2)+帧尾(1)=9 */
void send_emoji_clear_result_frame(uint8_t success)
{
uint8_t frame[9] = {0};
uint16_t crc16;
frame[0] = 0xAA;
frame[1] = 0xFF;
frame[2] = 0xFF;
frame[3] = 0x00;
frame[4] = 0x21;
frame[5] = success ? 0x01 : 0x00;
crc16 = crc16_ccitt_calculate(frame, 6);
frame[6] = (crc16 >> 8) & 0xFF;
frame[7] = crc16 & 0xFF;
frame[8] = 0x55;
send_protocol_frame(frame, sizeof(frame));
ESP_LOGI(BLE_TAG, "发送清除所有表情包结果: %s, CRC16=0x%04X", success ? "成功(0x01)" : "失败(0x00)", crc16);
}
/* Feedback 表情包查询/切换 0x0020 op=0x02 切换结果:帧头(1)+类(2)+指令(2)+op(1)+result(1)+CRC16(2)+帧尾(1)=10 */
void send_emoji_switch_result_frame(uint8_t success)
{
uint8_t frame[11] = {0};
uint16_t crc16;
frame[0] = 0xAA;
frame[1] = 0xFF;
frame[2] = 0xFF;
frame[3] = 0x00;
frame[4] = 0x20;
frame[5] = 0x02; /* op=0x02 切换结果 */
frame[6] = success ? 0x01 : 0x00;
frame[7] = s_emoji_switch_target_pack;//pack_id
crc16 = crc16_ccitt_calculate(frame, 8);
frame[8] = (crc16 >> 8) & 0xFF;
frame[9] = crc16 & 0xFF;
frame[10] = 0x55;
send_protocol_frame(frame, sizeof(frame));
ESP_LOGI(BLE_TAG, "发送Feedback3.2切换结果: %s, 发送 CRC16=0x%04X", success ? "成功(0x01)" : "失败(0x00)", crc16);
}
/* Feedback 表情包下载结果 0x0003:帧头(1)+类(2)+指令(2)+result(1)+CRC16(2)+帧尾(1)=9 */
void send_emoji_download_result_frame(uint8_t success)
{
uint8_t frame[9] = {0};
uint16_t crc16;
frame[0] = 0xAA;
frame[1] = 0xFF;
frame[2] = 0xFF;
frame[3] = 0x00;
frame[4] = 0x03; /* 表情包下载结果 */
frame[5] = success ? 0x01 : 0x00;
crc16 = crc16_ccitt_calculate(frame, 6);
frame[6] = (crc16 >> 8) & 0xFF;
frame[7] = crc16 & 0xFF;
frame[8] = 0x55;
send_protocol_frame(frame, sizeof(frame));
ESP_LOGI(BLE_TAG, "发送表情包下载结果: %s, 发送 CRC16=0x%04X", success ? "成功(0x01)" : "失败(0x00)", crc16);
}
/* Feedback 0x0020 op=0x00 返回已安装表情包列表:帧头(1)+类(2)+指令(2)+op(1)+count(1)+[id1..idN]+CRC16(2)+帧尾(1);默认系列 0 自带,无论如何都先发 */
#define EMOJI_PACK_LIST_MAX 16
void send_downloaded_packs_frame(void)
{
char list_buf[64];
int len = emoji_storage_get_downloaded_packs(list_buf, sizeof(list_buf));
uint8_t ids[EMOJI_PACK_LIST_MAX];
int count = 0;
/* 默认表情包系列 0 始终第一位 */
ids[count++] = (uint8_t)'0';
if (len > 0)
{
for (int i = 0; i < len && count < EMOJI_PACK_LIST_MAX; i++)
{
if (list_buf[i] != ',' && list_buf[i] != '0')
{
ids[count++] = (uint8_t)list_buf[i];
}
}
}
size_t frame_len = 10 + count;
uint8_t *frame = (uint8_t *)malloc(frame_len);
if (!frame)
{
ESP_LOGE(BLE_TAG, "已安装列表帧分配失败");
return;
}
frame[0] = 0xAA;
frame[1] = 0xFF;
frame[2] = 0xFF;
frame[3] = 0x00;
frame[4] = 0x20;
frame[5] = 0x00; /* op=0x00 返回已安装列表 */
frame[6] = (uint8_t)count;
for (int i = 0; i < count; i++)
{
frame[7 + i] = ids[i];
}
uint16_t crc16 = crc16_ccitt_calculate(frame, 7 + count);
frame[7 + count] = (crc16 >> 8) & 0xFF;
frame[8 + count] = crc16 & 0xFF;
frame[9 + count] = 0x55;
send_protocol_frame(frame, frame_len);
ESP_LOGI(BLE_TAG, "发送已安装表情包列表: count=%d, 发送 CRC16=0x%04X", count, crc16);
free(frame);
}
/* 发送get_system_info反馈帧2.3A: version */
void send_version_frame(const char *version_str)
{
size_t version_len = strlen(version_str);
size_t frame_len = 7 + version_len + 2 + 1; // 帧头(1) + 类(2) + 指令(2) + 长度(2) + 版本号 + CRC16(2) + 帧尾(1)
uint8_t *frame = (uint8_t *)malloc(frame_len);
if (!frame)
{
ESP_LOGE(BLE_TAG, "分配内存失败");
return;
}
uint16_t crc16;
size_t pos = 0;
// 帧头
frame[pos++] = 0xAA;
// 类 (Feedback)
frame[pos++] = 0xFF;
frame[pos++] = 0xFF;
// 指令 (version_cmd)
frame[pos++] = 0x00;
frame[pos++] = 0x06;
// 长度 (2字节,大端序)
frame[pos++] = (version_len >> 8) & 0xFF;
frame[pos++] = version_len & 0xFF;
// 版本号字符串
memcpy(&frame[pos], version_str, version_len);
pos += version_len;
// 计算CRC16 (不包括帧尾)
crc16 = crc16_ccitt_calculate(frame, pos);
frame[pos++] = (crc16 >> 8) & 0xFF;
frame[pos++] = crc16 & 0xFF;
// 帧尾
frame[pos++] = 0x55;
send_protocol_frame(frame, frame_len);
ESP_LOGI(BLE_TAG, "发送version帧: %s (长度: %d), 发送 CRC16=0x%04X", version_str, frame_len, crc16);
free(frame);
}
/* 1.11_B: 发送 OTA 升级进度 Feedback OTA_progress_cmd 0x0002,进度 0~100 */
void send_ota_progress_frame(uint8_t progress)
{
uint8_t frame[9] = {0};
uint16_t crc16;
frame[0] = 0xAA;
frame[1] = 0xFF;
frame[2] = 0xFF;
frame[3] = 0x00;
frame[4] = 0x02;
frame[5] = progress > 100 ? 100 : progress;
crc16 = crc16_ccitt_calculate(frame, 6);
frame[6] = (crc16 >> 8) & 0xFF;
frame[7] = crc16 & 0xFF;
frame[8] = 0x55;
send_protocol_frame(frame, sizeof(frame));
ESP_LOGI(BLE_TAG, "发送OTA进度帧 1.11_B: 进度(十进制)=%d%%, 发送 CRC16=0x%04X", (int)frame[5], crc16);
}
/* 1.11_A: 发送 OTA 升级结果 Feedback OTA_result_cmd 0x0001,0x01=成功 0x00=失败 */
void send_ota_result_frame(uint8_t result)
{
uint8_t frame[9] = {0};
uint16_t crc16;
frame[0] = 0xAA;
frame[1] = 0xFF;
frame[2] = 0xFF;
frame[3] = 0x00;
frame[4] = 0x01;
frame[5] = result ? 0x01 : 0x00;
crc16 = crc16_ccitt_calculate(frame, 6);
frame[6] = (crc16 >> 8) & 0xFF;
frame[7] = crc16 & 0xFF;
frame[8] = 0x55;
send_protocol_frame(frame, sizeof(frame));
ESP_LOGI(BLE_TAG, "发送OTA结果帧 1.11_A: %s, 发送 CRC16=0x%04X", result ? "成功(0x01)" : "失败(0x00)", crc16);
}
void ble_send_ota_progress(uint8_t progress)
{
s_ota_progress_to_send = progress > 100 ? 100 : progress;
if (notification_task_handle)
{
xTaskNotify(notification_task_handle, BLE_SEND_PROGRESS, eSetBits);
}
}
void ble_send_ota_result_request(uint8_t success)
{
s_ota_result_to_send = success ? 1 : 0;
if (notification_task_handle)
{
xTaskNotify(notification_task_handle, BLE_SEND_OTA_RESULT, eSetBits);
}
else
{
ESP_LOGE(BLE_TAG, "notification_task_handle 不存在");
}
}
/* 发送get_system_info反馈帧2.1A: get_system_ok */
void send_get_system_ok_frame(void)
{
uint8_t frame[9] = {0};
uint16_t crc16;
// 帧头
frame[0] = 0xAA;
// 类 (Feedback)
frame[1] = 0xFF;
frame[2] = 0xFF;
// 指令 (get_sysinfo_feedback)
frame[3] = 0x00;
frame[4] = 0x04;
// 指令返回 (get_system_ok = 0x01)
frame[5] = 0x01;
// 计算CRC16 (不包括帧尾)
crc16 = crc16_ccitt_calculate(frame, 6);
frame[6] = (crc16 >> 8) & 0xFF;
frame[7] = crc16 & 0xFF;
// 帧尾
frame[8] = 0x55;
send_protocol_frame(frame, sizeof(frame));
ESP_LOGI(BLE_TAG, "发送get_system_ok帧, 发送 CRC16=0x%04X", crc16);
}
/* 发送get_system_info反馈帧2.2A: brightness */
void send_brightness_frame(uint8_t brightness)
{
uint8_t frame[9] = {0};
uint16_t crc16;
// 帧头
frame[0] = 0xAA;
// 类 (Feedback)
frame[1] = 0xFF;
frame[2] = 0xFF;
// 指令 (Brightness_cmd)
frame[3] = 0x00;
frame[4] = 0x05;
frame[5] = brightness > 100 ? 100 : brightness;
// 计算CRC16 (不包括帧尾)
crc16 = crc16_ccitt_calculate(frame, 6);
frame[6] = (crc16 >> 8) & 0xFF;
frame[7] = crc16 & 0xFF;
// 帧尾
frame[8] = 0x55;
send_protocol_frame(frame, sizeof(frame));
ESP_LOGI(BLE_TAG, "发送brightness帧: 亮度(十进制)=%d, 发送 CRC16=0x%04X", (int)frame[5], crc16);
}
/* Feedback 3.2: 返回正在使用的表情包 帧头(1)+类(2)+指令(2)+Check_using(1)+Pack_id(1)+CRC16(2)+帧尾(1)=10 */
void send_emoji_using_frame(uint8_t pack_id)
{
uint8_t frame[10] = {0};
uint16_t crc16;
frame[0] = 0xAA;
frame[1] = 0xFF;
frame[2] = 0xFF;
frame[3] = 0x00;
frame[4] = 0x20; /* Emoji_cmd 0x0020 */
frame[5] = 0x01; /* Check_using 查询正在使用 */
frame[6] = pack_id; /* Pack_id ASCII 1 byte */
crc16 = crc16_ccitt_calculate(frame, 7);
frame[7] = (crc16 >> 8) & 0xFF;
frame[8] = crc16 & 0xFF;
frame[9] = 0x55;
send_protocol_frame(frame, sizeof(frame));
ESP_LOGI(BLE_TAG, "发送Feedback3.2正在使用表情包: Pack_id=%c (0x%02X), 发送 CRC16=0x%04X", (char)pack_id, pack_id, crc16);
}
/* 发送 WiFi 状态反馈帧 1.10_A: Feedback Wifi_state_cmd 0x0010 */
void send_wifi_state_frame(uint8_t state)
{
uint8_t frame[9] = {0};
uint16_t crc16;
frame[0] = 0xAA;
frame[1] = 0xFF;
frame[2] = 0xFF;
frame[3] = 0x00;
frame[4] = 0x10;
frame[5] = state ? 0x01 : 0x00; /* 0x01=已连接, 0x00=断开 */
crc16 = crc16_ccitt_calculate(frame, 6);
frame[6] = (crc16 >> 8) & 0xFF;
frame[7] = crc16 & 0xFF;
frame[8] = 0x55;
send_protocol_frame(frame, sizeof(frame));
ESP_LOGI(BLE_TAG, "发送WiFi状态帧: %s, 发送 CRC16=0x%04X", state ? "已连接" : "断开", crc16);
}
void ble_send_wifi_state_request(uint8_t connected)
{
s_wifi_state_to_send = connected ? 1 : 0;
if (notification_task_handle)
{
xTaskNotify(notification_task_handle, BLE_SEND_WIFI_STATE, eSetBits);
}
}
void ble_request_send_brightness(void)
{
if (notification_task_handle)
{
xTaskNotify(notification_task_handle, BLE_SEND_BRIGHTNESS, eSetBits);
}
}
/* 切换表情包指令的目标 Pack_id,由 ble_data_ansy 在收到 op=0x02 时设置 */
// static char s_emoji_switch_target_pack = '0';
void ble_set_emoji_switch_target(char pack_id)
{
s_emoji_switch_target_pack = pack_id;
}
/* 删除表情包指令的目标 Pack_id,由 ble_data_ansy 在收到 op=0x03 时设置 */
void ble_set_emoji_delete_target(char pack_id)
{
s_emoji_delete_target_pack = pack_id;
}
void robot_sleep_frame(uint8_t state)
{
(void)state;
uint8_t frame[8] = {0};
uint16_t crc16;
frame[0] = 0xAA;
frame[1] = 0xFF;
frame[2] = 0xFF;
frame[3] = 0x00;
frame[4] = 0x07;
crc16 = crc16_ccitt_calculate(frame, 5);
frame[5] = (crc16 >> 8) & 0xFF;
frame[6] = crc16 & 0xFF;
frame[7] = 0x55;
send_protocol_frame(frame, sizeof(frame));
ESP_LOGI(BLE_TAG, "发送robot睡眠状态帧: 发送 CRC16=0x%04X", crc16);
}
void robot_wake_frame(uint8_t state)
{
(void)state;
uint8_t frame[8] = {0};
uint16_t crc16;
frame[0] = 0xAA;
frame[1] = 0xFF;
frame[2] = 0xFF;
frame[3] = 0x00;
frame[4] = 0x08;
crc16 = crc16_ccitt_calculate(frame, 5);
frame[5] = (crc16 >> 8) & 0xFF;
frame[6] = crc16 & 0xFF;
frame[7] = 0x55;
send_protocol_frame(frame, sizeof(frame));
ESP_LOGI(BLE_TAG, "发送robot唤醒状态帧: 发送 CRC16=0x%04X", crc16);
}
+38
View File
@@ -0,0 +1,38 @@
#ifndef __BLE_SEND_H__
#define __BLE_SEND_H__
#include <stdint.h>
#include <stdio.h>
extern volatile uint8_t g_brightness;
void send_emoji_delete_result_frame(uint8_t success);
void send_emoji_clear_result_frame(uint8_t success);
void send_emoji_switch_result_frame(uint8_t success);
void send_emoji_download_result_frame(uint8_t success);
void send_downloaded_packs_frame(void);
void send_version_frame(const char *version_str);
void send_ota_progress_frame(uint8_t progress);
void send_ota_result_frame(uint8_t result);
void ble_send_ota_progress(uint8_t progress);
void ble_send_ota_result_request(uint8_t success);
void send_get_system_ok_frame(void);
void send_brightness_frame(uint8_t brightness);
void send_emoji_using_frame(uint8_t pack_id);
void send_wifi_state_frame(uint8_t state);
void ble_send_wifi_state_request(uint8_t connected);
/** 请求发送当前亮度单帧(0x0005),由 notification_task 调用 send_protocol_frame */
void ble_request_send_brightness(void);
void ble_set_emoji_switch_target(char pack_id);
void ble_set_emoji_delete_target(char pack_id);
void send_version_frame(const char *version_str);
void send_ota_progress_frame(uint8_t progress);
void send_ota_result_frame(uint8_t result);
void robot_sleep_frame(uint8_t state);
void robot_wake_frame(uint8_t state);
void notification_task(void *arg);
#endif
+121
View File
@@ -0,0 +1,121 @@
# 1. 自动搜索 ui 目录下所有的 .c 文件 (包含子目录)
file(GLOB_RECURSE GUI_SOURCES "${CMAKE_CURRENT_LIST_DIR}/ui/*.c")
file(GLOB GUIDER_FONT_SOURCES "${CMAKE_CURRENT_LIST_DIR}/ui/generated/guider_fonts/*.c")
# 下列文件已在 SRCS 中显式列出,避免 GLOB 与显式重复导致同一 .c 编译/链接两次
list(REMOVE_ITEM GUI_SOURCES
"${CMAKE_CURRENT_LIST_DIR}/ui/ui_events.c"
"${CMAKE_CURRENT_LIST_DIR}/ui/ui_nav.c"
"${CMAKE_CURRENT_LIST_DIR}/ui/ui_test_page.c"
)
# 注释内置 guider_fonts:改为运行时外部字体
list(REMOVE_ITEM GUI_SOURCES ${GUIDER_FONT_SOURCES})
# 2. 定义头文件路径
set(GUI_INCLUDES
"ui/custom"
"ui/generated"
"ui/generated/guider_customer_fonts"
)
# 3. 注册组件
idf_component_register(
SRCS
"main.c"
"OTA/ota_upgrade.c"
"ota_trigger.c"
"MQTT/mqtt.c"
"FLASH/flash.c"
"ZIP_EXTRACTOR/zip_extractor.c"
"MINIZ/miniz.c"
"MOTOR/motor.c"
"MOTOR/step_motor_init.c"
"MOTOR/foc.c"
"MOTOR/motor_timer.c"
"MOTOR/motor_control.c"
"MOTOR/motor_io.c"
"BLE/ble.c"
"BLE/ble_data_ansy.c"
"BLE/ble_send.c"
"WIFI/wifi.c"
"SC7U22/SC7U22.c"
"EVENT/event.c"
"ES7243E/ES7243E.c"
"SPEECH/speech_app.c"
"SPEECH/speech_init.c"
"SCREEN/screen_init.c"
"SCREEN/screen_brightness.c"
"SCREEN/screen_sleep.c"
"UI_DISPATCH/ui_dispatch.c"
"PET/pet_init.c"
"NS4168/ns4168.c"
"NS4168/wav_demux.c"
"NS4168/ogg_demux.c"
"NS4168/micro_decoder_wrapper.cpp"
"NS4168/AudioSourceFile.cpp"
"UI_DISPATCH/almanac_service.c"
"ui_cmd.c"
"ALGORITHM/crc.c"
"SYSTEM/system.c"
"EMOJI_STORAGE/emoji_storage.c"
"LVGL/lvgl_src.c"
"LVGL/lvgl_init.c"
"LVGL/lvgl_lottie.c"
"LVGL/lvgl_lottie_logic.c"
"remapping.c"
"ui/ui_events.c"
"ui/ui_nav.c"
"ui/ui_test_page.c"
${GUI_SOURCES}
INCLUDE_DIRS
"."
"EVENT"
"ui"
${GUI_INCLUDES}
EMBED_TXTFILES
"UI_DISPATCH/yiji_data_2026_2030.json"
REQUIRES
# --- GUI & Screen ---
lvgl
esp_lcd_qspi_amoled
esp_lcd_touch_cst820
# --- 系统基础 ---
nvs_flash
esp_event
esp_wifi
esp_netif
driver
log
freertos
spi_flash
# --- 网络与通信 ---
mqtt
esp_http_client
esp_https_ota
bt
# --- 存储与文件系统 ---
app_update
esp_partition
fatfs
spiffs
sdmmc
# --- 其他 ---
json
mbedtls
)
# 内置 guider_fonts 已不参与编译,保留以下属性配置作为历史参考
# set_source_files_properties(
# ${GUIDER_FONT_SOURCES}
# PROPERTIES COMPILE_OPTIONS
# -DLV_LVGL_H_INCLUDE_SIMPLE
# )
# 5. 生成分区镜像(storage 已废弃,表情已迁移至 /extflash)
# spiffs_create_partition_image(storage lottie_files FLASH_IN_PROJECT)
+638
View File
@@ -0,0 +1,638 @@
#include "emoji_storage.h"
#include "FLASH/flash.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "esp_log.h"
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include "cjson.h"
static const char *TAG = "emoji_storage";
// NVS命名空间
#define NVS_NAMESPACE "emoji_config"//"emoji_config"
#define USING_PACK_KEY "using_pack"
/* NVS key 最大 15 字符,原 "downloaded_packs" 超长导致 ESP_ERR_NVS_KEY_TOO_LONG */
#define DOWNLOADED_PACKS_KEY "dl_packs"
#define EXTFLASH_BASE "/extflash"
// 内存缓存
static char g_using_pack = '0'; // 默认表情包
static char g_downloaded_packs[256] = {0}; // 存储JSON格式的已下载列表
/**
* @brief 根据 /extflash 下实际存在的 A/B/C/D 目录同步已下载列表并写回 NVS
* 解决“昨天下载了 A 但当时 NVS key 超长保存失败、查询只返回 0”的问题
*/
static void emoji_storage_sync_downloaded_packs_from_fs(void)
{
extflash_lock();
DIR *dir = opendir(EXTFLASH_BASE);
if (!dir) {
extflash_unlock();
return;
}
cJSON *json_array = cJSON_Parse(g_downloaded_packs);
if (!json_array || !cJSON_IsArray(json_array)) {
if (json_array) {
cJSON_Delete(json_array);
}
json_array = cJSON_CreateArray();
if (!json_array) {
closedir(dir);
extflash_unlock();
return;
}
cJSON_AddItemToArray(json_array, cJSON_CreateString("0"));
}
bool changed = false;
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
if (entry->d_name[0] == '.') {
continue;
}
if (entry->d_name[1] != '\0') {
continue;
}
char pack_id = entry->d_name[0];
if (pack_id != 'A' && pack_id != 'B' && pack_id != 'C' && pack_id != 'D') {
continue;
}
char path[32];
snprintf(path, sizeof(path), "%s/%c", EXTFLASH_BASE, pack_id);
struct stat st;
if (stat(path, &st) != 0 || !S_ISDIR(st.st_mode)) {
continue;
}
bool found = false;
int n = cJSON_GetArraySize(json_array);
for (int i = 0; i < n; i++) {
cJSON *item = cJSON_GetArrayItem(json_array, i);
if (item && cJSON_IsString(item) && item->valuestring[0] == pack_id) {
found = true;
break;
}
}
if (!found) {
char pack_str[2] = {pack_id, '\0'};
cJSON_AddItemToArray(json_array, cJSON_CreateString(pack_str));
changed = true;
ESP_LOGI(TAG, "从 /extflash 同步到已下载列表: %c", pack_id);
}
}
closedir(dir);
extflash_unlock();
/* 已安装列表始终包含默认表情包系列 0(内置) */
{
bool has_zero = false;
int n = cJSON_GetArraySize(json_array);
for (int i = 0; i < n; i++) {
cJSON *item = cJSON_GetArrayItem(json_array, i);
if (item && cJSON_IsString(item) && item->valuestring[0] == '0') {
has_zero = true;
break;
}
}
if (!has_zero) {
cJSON_AddItemToArray(json_array, cJSON_CreateString("0"));
changed = true;
}
}
if (!changed) {
cJSON_Delete(json_array);
return;
}
char *json_string = cJSON_PrintUnformatted(json_array);
cJSON_Delete(json_array);
if (!json_string) {
return;
}
strncpy(g_downloaded_packs, json_string, sizeof(g_downloaded_packs) - 1);
g_downloaded_packs[sizeof(g_downloaded_packs) - 1] = '\0';
free(json_string);
nvs_handle_t nvs_handle;
if (nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle) != ESP_OK) {
return;
}
if (nvs_set_str(nvs_handle, DOWNLOADED_PACKS_KEY, g_downloaded_packs) == ESP_OK) {
nvs_commit(nvs_handle);
ESP_LOGI(TAG, "已下载列表已同步到 NVS: %s", g_downloaded_packs);
}
nvs_close(nvs_handle);
}
/**
* @brief 从NVS加载表情包信息到内存
*/
esp_err_t emoji_storage_load(void)
{
nvs_handle_t nvs_handle;
esp_err_t err;
size_t required_size;
// 打开NVS命名空间
err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &nvs_handle);
if (err != ESP_OK) {
ESP_LOGW(TAG, "打开NVS命名空间失败: %s,使用默认值", esp_err_to_name(err));
g_using_pack = '0';
strcpy(g_downloaded_packs, "[\"0\"]"); // 默认只有'0'
return err;
}
// 读取正在使用的表情包
char using_pack_str[2] = {0};
required_size = sizeof(using_pack_str);
err = nvs_get_str(nvs_handle, USING_PACK_KEY, using_pack_str, &required_size);
if (err == ESP_OK && required_size > 0) {
g_using_pack = using_pack_str[0];
ESP_LOGI(TAG, "加载正在使用的表情包: %c", g_using_pack);
} else {
ESP_LOGW(TAG, "读取using_pack失败: %s,使用默认值'0'", esp_err_to_name(err));
g_using_pack = '0';
}
// 读取已下载的表情包列表
required_size = sizeof(g_downloaded_packs);
err = nvs_get_str(nvs_handle, DOWNLOADED_PACKS_KEY, g_downloaded_packs, &required_size);
if (err == ESP_OK && required_size > 0) {
ESP_LOGI(TAG, "加载已下载表情包列表: %s", g_downloaded_packs);
} else {
strcpy(g_downloaded_packs, "[\"0\"]");
}
nvs_close(nvs_handle);
/* 根据 /extflash 下实际存在的 A/B/C/D 目录同步已下载列表,避免“昨天下了 A 但 NVS 未保存”只返回 0 */
emoji_storage_sync_downloaded_packs_from_fs();
return ESP_OK;
}
/**
* @brief 保存当前使用的表情包ID到NVS
*/
esp_err_t emoji_storage_save_using_pack(char pack_id)
{
ESP_LOGI(TAG, "[EMOJI_DEBUG] emoji_storage_save_using_pack('%c') 被调用, 当前 g_using_pack='%c'", pack_id, g_using_pack);
nvs_handle_t nvs_handle;
esp_err_t err;
char pack_str[2] = {pack_id, '\0'};
err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "[EMOJI_DEBUG] 打开NVS命名空间失败: %s", esp_err_to_name(err));
return err;
}
err = nvs_set_str(nvs_handle, USING_PACK_KEY, pack_str);
if (err != ESP_OK) {
ESP_LOGE(TAG, "[EMOJI_DEBUG] 保存 using_pack 到 NVS 失败: %s", esp_err_to_name(err));
nvs_close(nvs_handle);
return err;
}
err = nvs_commit(nvs_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "[EMOJI_DEBUG] 提交NVS更改失败: %s", esp_err_to_name(err));
nvs_close(nvs_handle);
return err;
}
g_using_pack = pack_id;
ESP_LOGI(TAG, "[EMOJI_DEBUG] 成功保存 using_pack='%c' 到 NVS, g_using_pack 已更新", pack_id);
nvs_close(nvs_handle);
return ESP_OK;
}
/**
* @brief 添加已下载的表情包ID到列表并保存到NVS
*/
esp_err_t emoji_storage_add_downloaded_pack(char pack_id)
{
ESP_LOGI(TAG, "[EMOJI_DEBUG] emoji_storage_add_downloaded_pack('%c') 被调用", pack_id);
ESP_LOGI(TAG, "[EMOJI_DEBUG] 当前 g_using_pack='%c', g_downloaded_packs=%s", g_using_pack, g_downloaded_packs);
nvs_handle_t nvs_handle;
esp_err_t err;
cJSON *json_array = NULL;
cJSON *item = NULL;
char pack_str[2] = {pack_id, '\0'};
bool found = false;
if (emoji_storage_is_downloaded(pack_id)) {
ESP_LOGW(TAG, "[EMOJI_DEBUG] 表情包 %c 已存在于下载列表中, 直接返回 ESP_OK", pack_id);
return ESP_OK;
}
// 打开NVS命名空间
err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "打开NVS命名空间失败: %s", esp_err_to_name(err));
return err;
}
// 读取现有的JSON数组
size_t required_size = sizeof(g_downloaded_packs);
err = nvs_get_str(nvs_handle, DOWNLOADED_PACKS_KEY, g_downloaded_packs, &required_size);
if (err != ESP_OK) {
// 如果不存在,创建新数组
json_array = cJSON_CreateArray();
} else {
// 解析现有JSON
json_array = cJSON_Parse(g_downloaded_packs);
if (json_array == NULL || !cJSON_IsArray(json_array)) {
// 解析失败,创建新数组
if (json_array) {
cJSON_Delete(json_array);
}
json_array = cJSON_CreateArray();
}
}
// 检查是否已存在
int array_size = cJSON_GetArraySize(json_array);
for (int i = 0; i < array_size; i++) {
cJSON *existing_item = cJSON_GetArrayItem(json_array, i);
if (existing_item && cJSON_IsString(existing_item)) {
if (existing_item->valuestring[0] == pack_id) {
found = true;
break;
}
}
}
// 如果不存在,添加到数组
if (!found) {
item = cJSON_CreateString(pack_str);
cJSON_AddItemToArray(json_array, item);
ESP_LOGI(TAG, "添加表情包 %c 到下载列表", pack_id);
}
// 转换为JSON字符串
char *json_string = cJSON_PrintUnformatted(json_array);
if (json_string == NULL) {
ESP_LOGE(TAG, "JSON序列化失败");
cJSON_Delete(json_array);
nvs_close(nvs_handle);
return ESP_ERR_NO_MEM;
}
// 保存到NVS
err = nvs_set_str(nvs_handle, DOWNLOADED_PACKS_KEY, json_string);
if (err != ESP_OK) {
ESP_LOGE(TAG, "保存downloaded_packs失败: %s", esp_err_to_name(err));
free(json_string);
cJSON_Delete(json_array);
nvs_close(nvs_handle);
return err;
}
// 提交更改
err = nvs_commit(nvs_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "提交NVS更改失败: %s", esp_err_to_name(err));
free(json_string);
cJSON_Delete(json_array);
nvs_close(nvs_handle);
return err;
}
// 更新内存缓存
strncpy(g_downloaded_packs, json_string, sizeof(g_downloaded_packs) - 1);
g_downloaded_packs[sizeof(g_downloaded_packs) - 1] = '\0';
free(json_string);
cJSON_Delete(json_array);
nvs_close(nvs_handle);
return ESP_OK;
}
/**
* @brief 获取当前使用的表情包ID
*/
char emoji_storage_get_using_pack(void)
{
return g_using_pack;
}
/**
* @brief 获取已下载的表情包列表(JSON格式字符串)
*/
int emoji_storage_get_downloaded_packs_json(char *packs, int max_len)
{
if (packs == NULL || max_len <= 0) {
return -1;
}
int len = strlen(g_downloaded_packs);
if (len >= max_len) {
ESP_LOGW(TAG, "缓冲区太小,需要 %d 字节,但只有 %d 字节", len + 1, max_len);
return -1;
}
strncpy(packs, g_downloaded_packs, max_len - 1);
packs[max_len - 1] = '\0';
return len;
}
/**
* @brief 获取已下载的表情包列表(逗号分隔字符串)
*/
int emoji_storage_get_downloaded_packs(char *packs, int max_len)
{
if (packs == NULL || max_len <= 0) {
return -1;
}
// 解析JSON数组
cJSON *json_array = cJSON_Parse(g_downloaded_packs);
if (json_array == NULL || !cJSON_IsArray(json_array)) {
ESP_LOGE(TAG, "解析JSON失败");
if (json_array) {
cJSON_Delete(json_array);
}
return -1;
}
int array_size = cJSON_GetArraySize(json_array);
int pos = 0;
for (int i = 0; i < array_size; i++) {
cJSON *item = cJSON_GetArrayItem(json_array, i);
if (item && cJSON_IsString(item) && item->valuestring[0] != '\0') {
if (pos > 0 && pos < max_len - 1) {
packs[pos++] = ',';
}
if (pos < max_len - 1) {
packs[pos++] = item->valuestring[0];
}
}
}
packs[pos] = '\0';
cJSON_Delete(json_array);
return pos;
}
/**
* @brief 检查指定表情包是否已下载
*/
bool emoji_storage_is_downloaded(char pack_id)
{
// 解析JSON数组
cJSON *json_array = cJSON_Parse(g_downloaded_packs);
if (json_array == NULL || !cJSON_IsArray(json_array)) {
if (json_array) {
cJSON_Delete(json_array);
}
return false;
}
int array_size = cJSON_GetArraySize(json_array);
for (int i = 0; i < array_size; i++) {
cJSON *item = cJSON_GetArrayItem(json_array, i);
if (item && cJSON_IsString(item)) {
if (item->valuestring[0] == pack_id) {
cJSON_Delete(json_array);
return true;
}
}
}
cJSON_Delete(json_array);
return false;
}
/**
* @brief 从列表中删除指定的表情包
*/
esp_err_t emoji_storage_remove_downloaded_pack(char pack_id)
{
// 不允许删除默认表情包'0'
if (pack_id == '0') {
ESP_LOGW(TAG, "不允许删除默认表情包'0'");
return ESP_ERR_INVALID_ARG;
}
nvs_handle_t nvs_handle;
esp_err_t err;
// 打开NVS命名空间
err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "打开NVS命名空间失败: %s", esp_err_to_name(err));
return err;
}
// 解析JSON数组
cJSON *json_array = cJSON_Parse(g_downloaded_packs);
if (json_array == NULL || !cJSON_IsArray(json_array)) {
ESP_LOGE(TAG, "解析JSON失败");
if (json_array) {
cJSON_Delete(json_array);
}
nvs_close(nvs_handle);
return ESP_ERR_INVALID_STATE;
}
// 查找并删除
int array_size = cJSON_GetArraySize(json_array);
for (int i = 0; i < array_size; i++) {
cJSON *item = cJSON_GetArrayItem(json_array, i);
if (item && cJSON_IsString(item)) {
if (item->valuestring[0] == pack_id) {
cJSON_DeleteItemFromArray(json_array, i);
ESP_LOGI(TAG, "从下载列表删除表情包: %c", pack_id);
break;
}
}
}
// 转换为JSON字符串
char *json_string = cJSON_PrintUnformatted(json_array);
if (json_string == NULL) {
ESP_LOGE(TAG, "JSON序列化失败");
cJSON_Delete(json_array);
nvs_close(nvs_handle);
return ESP_ERR_NO_MEM;
}
// 保存到NVS
err = nvs_set_str(nvs_handle, DOWNLOADED_PACKS_KEY, json_string);
if (err != ESP_OK) {
ESP_LOGE(TAG, "保存downloaded_packs失败: %s", esp_err_to_name(err));
free(json_string);
cJSON_Delete(json_array);
nvs_close(nvs_handle);
return err;
}
// 提交更改
err = nvs_commit(nvs_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "提交NVS更改失败: %s", esp_err_to_name(err));
free(json_string);
cJSON_Delete(json_array);
nvs_close(nvs_handle);
return err;
}
// 更新内存缓存
strncpy(g_downloaded_packs, json_string, sizeof(g_downloaded_packs) - 1);
g_downloaded_packs[sizeof(g_downloaded_packs) - 1] = '\0';
free(json_string);
cJSON_Delete(json_array);
nvs_close(nvs_handle);
if (g_using_pack == pack_id)
{
ESP_LOGW(TAG, "被删除的表情包 '%c' 正是当前使用中的,重置为默认 '0'", pack_id);
emoji_storage_save_using_pack('0');
}
return ESP_OK;
}
/**
* @brief 递归删除指定目录及其内容
*/
static esp_err_t remove_directory_recursive(const char *path)
{
DIR *dir = opendir(path);
if (!dir) {
ESP_LOGW(TAG, "无法打开目录: %s", path);
return ESP_ERR_NOT_FOUND;
}
struct dirent *entry;
char filepath[512];
while ((entry = readdir(dir)) != NULL) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
continue;
}
snprintf(filepath, sizeof(filepath), "%s/%s", path, entry->d_name);
struct stat st;
if (stat(filepath, &st) != 0) {
continue;
}
if (S_ISDIR(st.st_mode)) {
remove_directory_recursive(filepath);
} else {
if (unlink(filepath) != 0) {
ESP_LOGW(TAG, "删除文件失败: %s", filepath);
}
}
}
closedir(dir);
if (rmdir(path) != 0) {
ESP_LOGW(TAG, "删除目录失败: %s", path);
return ESP_FAIL;
}
ESP_LOGI(TAG, "已删除目录: %s", path);
return ESP_OK;
}
/**
* @brief 删除指定表情包的文件(目录 + zip)
*/
esp_err_t emoji_storage_remove_pack_files(char pack_id)
{
if (pack_id == '0') {
ESP_LOGW(TAG, "不允许删除默认表情包'0'的文件");
return ESP_ERR_INVALID_ARG;
}
char dir_path[32];
char zip_path[32];
snprintf(dir_path, sizeof(dir_path), "%s/%c", EXTFLASH_BASE, pack_id);
snprintf(zip_path, sizeof(zip_path), "%s/%c.zip", EXTFLASH_BASE, pack_id);
struct stat st;
if (stat(dir_path, &st) == 0 && S_ISDIR(st.st_mode)) {
remove_directory_recursive(dir_path);
}
if (stat(zip_path, &st) == 0 && !S_ISDIR(st.st_mode)) {
if (unlink(zip_path) == 0) {
ESP_LOGI(TAG, "已删除zip文件: %s", zip_path);
} else {
ESP_LOGW(TAG, "删除zip文件失败: %s", zip_path);
}
}
return ESP_OK;
}
/**
* @brief 清除所有已下载的表情包(删除文件 + 清空NVS + 重置为默认表情包)
*/
esp_err_t emoji_storage_clear_all_packs(void)
{
const char packs[] = {'A', 'B', 'C', 'D'};
for (int i = 0; i < (int)(sizeof(packs) / sizeof(packs[0])); i++) {
emoji_storage_remove_pack_files(packs[i]);
}
esp_err_t err = emoji_storage_clear_downloaded_packs();
if (err != ESP_OK) {
ESP_LOGE(TAG, "清空NVS已下载列表失败: %s", esp_err_to_name(err));
return err;
}
if (g_using_pack != '0') {
err = emoji_storage_save_using_pack('0');
if (err != ESP_OK) {
ESP_LOGE(TAG, "重置使用中的表情包失败: %s", esp_err_to_name(err));
}
}
ESP_LOGI(TAG, "已清除所有已下载的表情包,恢复为默认表情包'0'");
return ESP_OK;
}
/**
* @brief 清空已下载的表情包列表(保留默认表情包'0')
*/
esp_err_t emoji_storage_clear_downloaded_packs(void)
{
nvs_handle_t nvs_handle;
esp_err_t err;
// 打开NVS命名空间
err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "打开NVS命名空间失败: %s", esp_err_to_name(err));
return err;
}
// 只保留默认表情包'0'
const char *default_packs = "[\"0\"]";
err = nvs_set_str(nvs_handle, DOWNLOADED_PACKS_KEY, default_packs);
if (err != ESP_OK) {
ESP_LOGE(TAG, "保存downloaded_packs失败: %s", esp_err_to_name(err));
nvs_close(nvs_handle);
return err;
}
// 提交更改
err = nvs_commit(nvs_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "提交NVS更改失败: %s", esp_err_to_name(err));
nvs_close(nvs_handle);
return err;
}
// 更新内存缓存
strcpy(g_downloaded_packs, default_packs);
ESP_LOGI(TAG, "清空已下载表情包列表,保留默认表情包'0'");
nvs_close(nvs_handle);
return ESP_OK;
}
+91
View File
@@ -0,0 +1,91 @@
#ifndef __EMOJI_STORAGE_H__
#define __EMOJI_STORAGE_H__
#include "esp_err.h"
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief 从NVS加载表情包信息到内存
* @return esp_err_t ESP_OK表示成功
*/
esp_err_t emoji_storage_load(void);
/**
* @brief 保存当前使用的表情包ID到NVS
* @param pack_id 表情包ID(字符,如'0', 'A', 'B', 'C', 'D'等)
* @return esp_err_t ESP_OK表示成功
*/
esp_err_t emoji_storage_save_using_pack(char pack_id);
/**
* @brief 添加已下载的表情包ID到列表并保存到NVS
* @param pack_id 表情包ID(字符,如'0', 'A', 'B', 'C', 'D'等)
* @return esp_err_t ESP_OK表示成功
*/
esp_err_t emoji_storage_add_downloaded_pack(char pack_id);
/**
* @brief 获取当前使用的表情包ID
* @return char 表情包ID,'0'表示默认表情包
*/
char emoji_storage_get_using_pack(void);
/**
* @brief 获取已下载的表情包列表(JSON格式字符串)
* @param packs 输出缓冲区,存储JSON格式字符串(如"[\"0\",\"A\",\"B\"]")
* @param max_len 缓冲区最大长度
* @return int 实际写入的字符数(不包括'\0'),-1表示失败
*/
int emoji_storage_get_downloaded_packs_json(char *packs, int max_len);
/**
* @brief 获取已下载的表情包列表(逗号分隔字符串)
* @param packs 输出缓冲区,存储逗号分隔字符串(如"0,A,B")
* @param max_len 缓冲区最大长度
* @return int 实际写入的字符数(不包括'\0'),-1表示失败
*/
int emoji_storage_get_downloaded_packs(char *packs, int max_len);
/**
* @brief 检查指定表情包是否已下载
* @param pack_id 表情包ID(字符)
* @return true 已下载,false 未下载
*/
bool emoji_storage_is_downloaded(char pack_id);
/**
* @brief 从列表中删除指定的表情包
* @param pack_id 表情包ID(字符)
* @return esp_err_t ESP_OK表示成功
*/
esp_err_t emoji_storage_remove_downloaded_pack(char pack_id);
/**
* @brief 清空已下载的表情包列表(保留默认表情包'0')
* @return esp_err_t ESP_OK表示成功
*/
esp_err_t emoji_storage_clear_downloaded_packs(void);
/**
* @brief 删除指定表情包的文件(目录 + zip)
* @param pack_id 表情包ID(字符,'A'~'D')
* @return esp_err_t ESP_OK表示成功
*/
esp_err_t emoji_storage_remove_pack_files(char pack_id);
/**
* @brief 清除所有已下载的表情包(删除文件 + 清空NVS + 重置为默认表情包)
* @return esp_err_t ESP_OK表示成功
*/
esp_err_t emoji_storage_clear_all_packs(void);
#ifdef __cplusplus
}
#endif
#endif /* __EMOJI_STORAGE_H__ */
+270
View File
@@ -0,0 +1,270 @@
#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
// #include "esp_flash.h"
#include "esp_system.h"
#include "esp_idf_version.h"
// #include "unity.h"
#include "esp_codec_dev.h"
#include "esp_codec_dev_defaults.h"
#include "ES7243E.h"
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
#include "driver/i2s_std.h"
#include "driver/i2s_tdm.h"
#include "soc/soc_caps.h"
#else
#include "driver/i2s.h"
#endif
#include "driver/i2c_master.h"
//esp_codec_dev_handle_t codec_dev = NULL;
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) && !CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE
#define USE_IDF_I2C_MASTER
#include "driver/i2c_master.h"
#else
#include "driver/i2c.h"
#endif
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
#define I2S_MAX_KEEP SOC_I2S_NUM
typedef struct {
i2s_chan_handle_t tx_handle;
i2s_chan_handle_t rx_handle;
} i2s_keep_t;
static i2s_comm_mode_t i2s_in_mode = I2S_COMM_MODE_STD;
static i2s_comm_mode_t i2s_out_mode = I2S_COMM_MODE_STD;
static i2s_keep_t *i2s_keep[I2S_MAX_KEEP];
#endif
static void codec_max_sample(uint8_t *data, int size, int *max_value, int *min_value)
{
int16_t *s = (int16_t *) data;
size >>= 1;
int i = 1, max, min;
max = min = s[0];
while (i < size) {
if (s[i] > max) {
max = s[i];
} else if (s[i] < min) {
min = s[i];
}
i++;
}
*max_value = max;
*min_value = min;
}
#ifdef USE_IDF_I2C_MASTER
// 定义为全局变量,供BMI270使用
i2c_master_bus_handle_t i2c_bus_handle = NULL;
#endif
int ut_i2c_init(uint8_t port)
{
#ifdef USE_IDF_I2C_MASTER
i2c_master_bus_config_t i2c_bus_config = {0};
i2c_bus_config.clk_source = I2C_CLK_SRC_DEFAULT;
i2c_bus_config.i2c_port = port;
i2c_bus_config.scl_io_num = TEST_BOARD_I2C_SCL_PIN;
i2c_bus_config.sda_io_num = TEST_BOARD_I2C_SDA_PIN;
i2c_bus_config.glitch_ignore_cnt = 7;
i2c_bus_config.flags.enable_internal_pullup = true;
return i2c_new_master_bus(&i2c_bus_config, &i2c_bus_handle);
#else
i2c_config_t i2c_cfg = {
.mode = I2C_MODE_MASTER,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = 100000,
};
i2c_cfg.sda_io_num = GPIO_NUM_0;
i2c_cfg.scl_io_num = GPIO_NUM_1;
esp_err_t ret = i2c_param_config(port, &i2c_cfg);
if (ret != ESP_OK) {
return -1;
}
return i2c_driver_install(port, i2c_cfg.mode, 0, 0, 0);
#endif
}
static int ut_i2s_init(uint8_t port)
{
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
if (port >= I2S_MAX_KEEP) {
return -1;
}
// Already installed
if (i2s_keep[port]) {
return 0;
}
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
i2s_std_config_t std_cfg = {
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(16000),
.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(16, I2S_SLOT_MODE_STEREO),
.gpio_cfg ={
.mclk = TEST_BOARD_I2S_MCK_PIN,
.bclk = TEST_BOARD_I2S_BCK_PIN,
.ws = TEST_BOARD_I2S_DATA_WS_PIN,
.dout = -1,
.din = TEST_BOARD_I2S_DATA_IN_PIN,
},
};
i2s_keep[port] = (i2s_keep_t *) calloc(1, sizeof(i2s_keep_t));
if (i2s_keep[port] == NULL) {
return -1;
}
#if SOC_I2S_SUPPORTS_TDM
i2s_tdm_slot_mask_t slot_mask = I2S_TDM_SLOT0 | I2S_TDM_SLOT1 | I2S_TDM_SLOT2 | I2S_TDM_SLOT3;
i2s_tdm_config_t tdm_cfg = {
.slot_cfg = I2S_TDM_PHILIPS_SLOT_DEFAULT_CONFIG(16, I2S_SLOT_MODE_MONO, slot_mask),
.clk_cfg = I2S_TDM_CLK_DEFAULT_CONFIG(16000),
.gpio_cfg = {
.mclk = TEST_BOARD_I2S_MCK_PIN,
.bclk = TEST_BOARD_I2S_BCK_PIN,
.ws = TEST_BOARD_I2S_DATA_WS_PIN,
.dout = -1,
.din = TEST_BOARD_I2S_DATA_IN_PIN,
},
};
tdm_cfg.slot_cfg.total_slot = 4;
#endif
int ret = i2s_new_channel(&chan_cfg, &i2s_keep[port]->tx_handle, &i2s_keep[port]->rx_handle);
if(ret == ESP_OK) printf("i2s_new_channel success.\n");
else printf("i2s_new_channel failed.\n");
if (i2s_out_mode == I2S_COMM_MODE_STD) {
ret = i2s_channel_init_std_mode(i2s_keep[port]->tx_handle, &std_cfg);
}
#if SOC_I2S_SUPPORTS_TDM
else if (i2s_out_mode == I2S_COMM_MODE_TDM) {
ret = i2s_channel_init_tdm_mode(i2s_keep[port]->tx_handle, &tdm_cfg);
}
#endif
if(ret == ESP_OK) printf("2 success.\n");
else printf("2 failed.\n");
if (i2s_in_mode == I2S_COMM_MODE_STD) {
ret = i2s_channel_init_std_mode(i2s_keep[port]->rx_handle, &std_cfg);
}
#if SOC_I2S_SUPPORTS_TDM
else if (i2s_in_mode == I2S_COMM_MODE_TDM) {
ret = i2s_channel_init_tdm_mode(i2s_keep[port]->rx_handle, &tdm_cfg);
}
#endif
if(ret == ESP_OK) printf("3 success.\n");
else printf("3 failed.\n");
// For tx master using duplex mode
ret=i2s_channel_enable(i2s_keep[port]->tx_handle);
if(ret == ESP_OK) printf("i2s_channel_enable tx success.\n");
ret=i2s_channel_enable(i2s_keep[port]->rx_handle);
if(ret == ESP_OK) printf("i2s_channel_enable rx success.\n");
#else
i2s_config_t i2s_config = {
.mode = (i2s_mode_t) (I2S_MODE_TX | I2S_MODE_RX | I2S_MODE_MASTER),
.sample_rate = 16000,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL2 | ESP_INTR_FLAG_IRAM,
.dma_buf_count = 2,
.dma_buf_len = 128,
.use_apll = true,
.tx_desc_auto_clear = true,
};
int ret = i2s_driver_install(port, &i2s_config, 0, NULL);
i2s_pin_config_t i2s_pin_cfg = {
.mck_io_num = TEST_BOARD_I2S_MCK_PIN,
.bck_io_num = TEST_BOARD_I2S_BCK_PIN,
.ws_io_num = TEST_BOARD_I2S_DATA_WS_PIN,
.data_out_num = TEST_BOARD_I2S_DATA_OUT_PIN,
.data_in_num = TEST_BOARD_I2S_DATA_IN_PIN,
};
i2s_set_pin(port, &i2s_pin_cfg);
#endif
return ret;
}
extern void i2c_scan_devices(void);
extern i2c_master_bus_handle_t s_i2c_bus;
extern i2c_master_bus_handle_t touch_bus_handle_BMI;
//*****************外部方法*************************** */
esp_codec_dev_handle_t ES7243E_Init(void)
{
//为编解码器设备的控制和数据总线安装驱动
int ret;
ret = ut_i2c_init(0);// 初始化I2C总线0
if(ret == ESP_OK) printf("ut_i2c_init success.\n");
else printf("ut_i2c_init failed.\n");
ret = ut_i2s_init(0); // 初始化I2S总线0
if(ret == ESP_OK) printf("ut_i2s_init success.\n");
else printf("ut_i2s_init failed.\n");
s_i2c_bus = i2c_bus_handle;
i2c_scan_devices(); // 扫描I2C总线上的设备
vTaskDelay(pdMS_TO_TICKS(100));
//初始化与音频编解码器相关的接口,包括数据接口(I2S)、控制接口(I2C)和GPIO接口
audio_codec_i2s_cfg_t i2s_cfg = {
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
.rx_handle = i2s_keep[0]->rx_handle, // 接收句柄
.tx_handle = i2s_keep[0]->tx_handle, // 发送句柄
#endif
};
const audio_codec_data_if_t *data_if = audio_codec_new_i2s_data(&i2s_cfg); // 创建I2S数据接口
audio_codec_i2c_cfg_t i2c_cfg = {.addr = ES7243E_CODEC_DEFAULT_ADDR};// 设置编解码器的默认地址
printf("sign1\n");
#ifdef USE_IDF_I2C_MASTER
i2c_cfg.bus_handle = i2c_bus_handle; // 如果定义了USE_IDF_I2C_MASTER宏,则设置I2C总线句柄i2c_bus_handle touch_bus_handle_BMI
#endif
const audio_codec_ctrl_if_t *in_ctrl_if = audio_codec_new_i2c_ctrl(&i2c_cfg);
printf("sign2\n");
const audio_codec_gpio_if_t *gpio_if = audio_codec_new_gpio(); // 创建GPIO接口
es7243e_codec_cfg_t es7243e_cfg = {
.ctrl_if = in_ctrl_if,
};
const audio_codec_if_t *in_codec_if = es7243e_codec_new(&es7243e_cfg);//wenti
printf("sign3\n");
esp_codec_dev_cfg_t dev_cfg = {
.codec_if = in_codec_if,
.data_if = data_if,
.dev_type = ESP_CODEC_DEV_TYPE_IN,
};
esp_codec_dev_handle_t record_dev = esp_codec_dev_new(&dev_cfg);
printf("sign4\n");
ret = esp_codec_dev_set_in_gain(record_dev, 30);
esp_codec_dev_sample_info_t fs = {
.sample_rate = 16000, // 采样率
.channel = 2,
.bits_per_sample = 16, // 每个样本的位数
};
ret = esp_codec_dev_open(record_dev, &fs);
printf("sign5\n");
return record_dev;
}
void ES7243E_Record(esp_codec_dev_handle_t codec_dev, int16_t* audio_data, size_t buffer_size)
{
// printf("开始录音.\n");
esp_codec_dev_read(codec_dev, audio_data, buffer_size); // 读取音频数据(录音)
}
// 获取ES7243E的I2C总线句柄,供BMI270使用
i2c_master_bus_handle_t ES7243E_get_i2c_bus_handle(void)
{
#ifdef USE_IDF_I2C_MASTER
return i2c_bus_handle;
#else
return NULL;
#endif
}
+43
View File
@@ -0,0 +1,43 @@
#ifndef _ES7243E_H_
#define _ES7243E_H_
#include "esp_codec_dev.h"
#include "esp_codec_dev_defaults.h"
#include "driver/i2c_master.h"
#ifdef __cplusplus
extern "C" {
#endif
#define USE_IDF_I2C_MASTER
/**
* @brief Codec configuration by ESP32S3_s3_N18r8
*/
#define TEST_BOARD_I2C_SDA_PIN GPIO_NUM_50
#define TEST_BOARD_I2C_SCL_PIN GPIO_NUM_51
#define TEST_BOARD_I2S_BCK_PIN GPIO_NUM_28 //SCLK
#define TEST_BOARD_I2S_MCK_PIN GPIO_NUM_29 //MCLK
#define TEST_BOARD_I2S_DATA_WS_PIN GPIO_NUM_27 //LRCK
#define TEST_BOARD_I2S_DATA_IN_PIN GPIO_NUM_26
// #define TEST_BOARD_I2S_DATA_OUT_PIN GPIO_NUM_42
// #define TEST_BOARD_PA_PIN GPIO_NUM_46 //PA_CTRL
#define NST112_I2C_SDA_PIN GPIO_NUM_2
#define NST112_I2C_SCL_PIN GPIO_NUM_3
esp_codec_dev_handle_t ES7243E_Init(void);
void ES7243E_Record(esp_codec_dev_handle_t codec_dev, int16_t* audio_data, size_t buffer_size);
int ut_i2c_init(uint8_t port);
i2c_master_bus_handle_t ES7243E_get_i2c_bus_handle(void);
#ifdef __cplusplus
}
#endif
#endif
+109
View File
@@ -0,0 +1,109 @@
#include <stdio.h>
#include <stdlib.h>
#include "event.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_log.h"
EventGroupHandle_t system_event_group = NULL;
QueueHandle_t system_event_queue = NULL;
bool init_event_system(void)
{
// 检查是否已经初始化
if (system_event_group != NULL)
{
ESP_LOGW("EVENT", "Event system already initialized");
return true;
}
// 创建事件组
system_event_group = xEventGroupCreate();
if (system_event_group == NULL)
{
ESP_LOGE("EVENT", "Failed to create event group");
return false;
}
// 创建事件队列(BLE 回调等通过队列投递,避免在回调中直接操作事件组/任务通知)
system_event_queue = xQueueCreate(8, sizeof(uint32_t));
if (system_event_queue == NULL)
{
ESP_LOGE("EVENT", "Failed to create event queue");
vEventGroupDelete(system_event_group);
system_event_group = NULL;
return false;
}
ESP_LOGI("EVENT", "Event system initialized (group + queue)");
return true;
}
// 清理函数
void deinit_event_system(void)
{
if (system_event_queue != NULL)
{
vQueueDelete(system_event_queue);
system_event_queue = NULL;
}
if (system_event_group != NULL)
{
vEventGroupDelete(system_event_group);
system_event_group = NULL;
}
ESP_LOGI("EVENT", "Event system deinitialized");
}
void set_system_event(uint32_t bits)
{
ESP_LOGI("EVENT", "[DEBUG] set_system_event(0x%lX) group=%p queue=%p main_handle=%p",
(unsigned long)bits, (void *)system_event_group, (void *)system_event_queue, (void *)main_task_handle);
if (system_event_group)
{
xEventGroupSetBits(system_event_group, bits);
}
/* 通过队列投递,main_task 阻塞接收,避免 BLE 回调上下文限制导致事件丢失 */
if (system_event_queue)
{
UBaseType_t q_waiting = uxQueueMessagesWaiting(system_event_queue);
UBaseType_t q_spaces = uxQueueSpacesAvailable(system_event_queue);
ESP_LOGI("EVENT", "[DEBUG] queue before send: pending=%u, spaces=%u",
(unsigned)q_waiting, (unsigned)q_spaces);
BaseType_t ok = xQueueSend(system_event_queue, &bits, 0);
ESP_LOGI("EVENT", "[DEBUG] xQueueSend result=%d (1=ok, 0=full)", (int)ok);
if (!ok)
{
ESP_LOGE("EVENT", "Event queue FULL! bits=0x%lX DROPPED! pending=%u",
(unsigned long)bits, (unsigned)q_waiting);
}
}
else
{
ESP_LOGW("EVENT", "[DEBUG] system_event_queue is NULL!");
}
if (main_task_handle)
{
xTaskNotify(main_task_handle, bits, eSetBits);
}
else
{
ESP_LOGW("EVENT", "[DEBUG] main_task_handle is NULL!");
}
}
void clear_system_event(uint32_t bits)
{
if (system_event_group)
{
xEventGroupClearBits(system_event_group, bits);
}
}
uint32_t wait_system_event(uint32_t bits, TickType_t timeout)
{
if (system_event_group)
{
return xEventGroupWaitBits(system_event_group, bits, pdTRUE, pdFALSE, timeout);
}
return 0;
}
+59
View File
@@ -0,0 +1,59 @@
#ifndef EVENT_H
#define EVENT_H
#include "freertos/FreeRTOS.h" // 必须第一个包含
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "freertos/queue.h"
// 声明全局变量(extern 表示在其他文件中定义)
extern EventGroupHandle_t system_event_group;
extern QueueHandle_t system_event_queue; /* 事件队列:BLE 等回调通过队列投递,main_task 阻塞接收 */
extern TaskHandle_t main_task_handle;
// 事件标志定义
#define BLE_CONNECTED_BIT (1 << 0)
#define WIFI_CONNECTED_BIT (1 << 1)
#define WIFI_DISCONNECTED_BIT (1 << 2)
#define BLE_DISCONNECTED_BIT (1 << 14)
#define WEBSOCKET_CONNECTED_BIT (1 << 3)
#define WEBSOCKET_DISCONNECTED_BIT (1 << 4)
#define WEBSOCKET_DATA_BIT (1 << 5)
#define IMU_DATA_READY_BIT (1 << 6)
#define USER_INPUT_BIT (1 << 7)
#define SYSTEM_ERROR_BIT (1 << 8)
#define OTA_UPDATE_STARTED_BIT (1 << 9)
#define OTA_UPDATE_COMPLETED_BIT (1 << 10)
#define DOWNLOAD_EMOJI_BIT (1 << 11)
#define DOWNLOAD_SUCCESS_BIT (1 << 12)
#define DOWNLOAD_FAILED_BIT (1 << 13)
#define OTA_RECOVER_REQUIRED_BIT (1 << 15)
/** OTA 任务因失败退出:先等 WiFi 重连结果再恢复 UI(由 main 处理) */
#define OTA_FAIL_RECOVER_PENDING_BIT (1 << 16)
/** BLE 触发的 WiFi 配网:重试用尽/最终失败后,释放 WiFi 并恢复 LVGL/UI(仅此场景使用) */
#define WIFI_BLE_PROV_FAIL_RECOVER_UI_BIT (1 << 17)
/** 表情包下载成功后的第一次切换:收尾升级;若切到下载包则强制停在该包 default.json */
#define EMOJI_UPGRADE_FINALIZE_BIT (1 << 18)
/* main_task 监听的系统事件掩码(用于 xEventGroupWaitBits) */
#define MAIN_TASK_EVENT_MASK (BLE_CONNECTED_BIT | WIFI_CONNECTED_BIT | WIFI_DISCONNECTED_BIT | \
WEBSOCKET_CONNECTED_BIT | WEBSOCKET_DISCONNECTED_BIT | WEBSOCKET_DATA_BIT | \
IMU_DATA_READY_BIT | USER_INPUT_BIT | SYSTEM_ERROR_BIT | \
OTA_UPDATE_STARTED_BIT | OTA_UPDATE_COMPLETED_BIT | \
DOWNLOAD_EMOJI_BIT | DOWNLOAD_SUCCESS_BIT | DOWNLOAD_FAILED_BIT | \
BLE_DISCONNECTED_BIT | OTA_RECOVER_REQUIRED_BIT | OTA_FAIL_RECOVER_PENDING_BIT | \
WIFI_BLE_PROV_FAIL_RECOVER_UI_BIT | EMOJI_UPGRADE_FINALIZE_BIT)
// 函数声明
void set_system_event(uint32_t bits);
void clear_system_event(uint32_t bits);
uint32_t wait_system_event(uint32_t bits, TickType_t timeout);
// 新增:初始化和清理函数
bool init_event_system(void);
void deinit_event_system(void);
/* WiFi 配网守卫:收到配网指令后开启故障重启策略 */
void notify_wifi_provision_signal_received(void);
#endif // EVENT_H
+742
View File
@@ -0,0 +1,742 @@
#include "FLASH/flash.h"
#include "esp_http_client.h"
#include "esp_timer.h"
#include "ZIP_EXTRACTOR/zip_extractor.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <inttypes.h>
#include <sys/stat.h>
#include "esp_system.h"
#include "soc/spi_pins.h"
#include "esp_vfs_fat_nand.h"
#include "driver/spi_master.h"
#include "EVENT/event.h"
#include "BLE/ble.h"
#include "EMOJI_STORAGE/emoji_storage.h"
#include "LVGL/lvgl_init.h"
#include <errno.h>
static const char *TAG_FLASH = "example";
/* 字体已由产线直接烧录到外部 Flash,不再从 app 内置资源同步 */
#define UI_EMBED_FONT_BIN_TO_APP 0
/* extflash 访问互斥锁:保护所有对 /extflash 的 fopen/fread/fwrite/stat/opendir 操作 */
static SemaphoreHandle_t s_extflash_mutex = NULL;
/* 解压进行中:动画切换应延后,避免与解压写操作竞争 */
static volatile bool s_extraction_in_progress = false;
void extflash_lock(void)
{
if (s_extflash_mutex) {
xSemaphoreTake(s_extflash_mutex, portMAX_DELAY);
}
}
void extflash_unlock(void)
{
if (s_extflash_mutex) {
xSemaphoreGive(s_extflash_mutex);
}
}
void extflash_extraction_started(void)
{
s_extraction_in_progress = true;
}
void extflash_extraction_finished(void)
{
s_extraction_in_progress = false;
}
bool extflash_is_extracting(void)
{
return s_extraction_in_progress;
}
#define EXTRACT_TASK_STACK_SIZE 16384 // 16KB 栈
#define EXTRACT_TASK_PRIORITY 4
// 分区的挂载路径
const char *base_path = "/extflash";//
// ********** 适配GD5F1GM7UEYIG(SPI NAND)+ WP/HD硬件接3.3V **********
#define EXAMPLE_FLASH_FREQ_KHZ 40000 // 先升到40MHz做稳定性/性能验证(后续可再评估到80MHz)
static spi_nand_flash_device_t *g_nand_flash_handle = NULL;
static spi_device_handle_t g_spi_handle = NULL;
// 磨损均衡句柄(原NOR的WL,NAND驱动内部已实现,保留兼容)
static wl_handle_t s_wl_handle = WL_INVALID_HANDLE;
void list_files_in_directory(const char *path);
static bool copy_file(const char *source_path, const char *dest_path);
static void extract_task(void *pvParameters);
static StackType_t *s_extract_stack = NULL;
static StaticTask_t *s_extract_tcb = NULL;
static bool example_mount_fatfs(void);
static esp_err_t example_init_nand_flash(void);
static esp_err_t read_chip_id(spi_device_handle_t handle, uint8_t *id_data, size_t len);
esp_err_t nand_read_jedec_id(spi_device_handle_t handle, uint8_t id[3]);
static bool sync_embedded_fonts_to_extflash(void);
static bool is_zip_file(const char *filename);
static void example_list_data_partitions(void);
static bool sync_embedded_fonts_to_extflash(void)
{
static const char *fonts[] = {
"iconfont_25.bin",
"MiSans_VF_25.bin",
"MiSans_VF_30.bin",
"MiSans_VF_40.bin",
"MiSans_VF_60.bin",
"MiSans_VF_100.bin",
};
bool all_ok = true;
char fonts_dir[64] = {0};
snprintf(fonts_dir, sizeof(fonts_dir), "%s/fonts", base_path);
#if UI_EMBED_FONT_BIN_TO_APP
ESP_LOGI(TAG_FLASH, "font sync mode: app embed -> extflash");
#else
ESP_LOGI(TAG_FLASH, "font sync mode: extflash preloaded only");
#endif
extflash_lock();
for (size_t i = 0; i < sizeof(fonts) / sizeof(fonts[0]); ++i) {
char dst_path[128] = {0};
struct stat st;
snprintf(dst_path, sizeof(dst_path), "%s/%s", fonts_dir, fonts[i]);
if (stat(dst_path, &st) == 0 && S_ISREG(st.st_mode) && st.st_size > 0) {
ESP_LOGI(TAG_FLASH, "font ready: %s (%ld bytes)", dst_path, (long)st.st_size);
continue;
}
ESP_LOGE(TAG_FLASH, "font missing or invalid: %s", dst_path);
all_ok = false;
}
extflash_unlock();
return all_ok;
}
// 初始化外部Flash芯片
static esp_err_t example_init_nand_flash(void)
{
// SPI总线配置:WP/HD设为-1(硬件接3.3V),适配大传输量
const spi_bus_config_t bus_config = {
.mosi_io_num = PIN_MOSI,
.miso_io_num = PIN_MISO,
.sclk_io_num = PIN_CLK,
.quadhd_io_num = PIN_HD, // -1:禁用HD引脚(硬件接3.3V)
.quadwp_io_num = PIN_WP, // -1:禁用WP引脚(硬件接3.3V)
.max_transfer_sz = 4096 * 2, // 增大传输缓冲区,适配NAND页操作
};
ESP_LOGI(TAG_FLASH, "Initializing GD5F1GM7UEYIG SPI NAND Flash (WP/HD HW 3.3V)");
ESP_LOGI(TAG_FLASH, "Pin assignments:");
ESP_LOGI(TAG_FLASH, "MOSI: %2d MISO: %2d SCLK: %2d CS: %2d WP: %2d HD: %2d",
PIN_MOSI, PIN_MISO, PIN_CLK, PIN_CS, PIN_WP, PIN_HD);
ESP_LOGI(TAG_FLASH, "DMA CHANNEL: %d", SPI_DMA_CHAN);
// 初始化SPI总线
ESP_ERROR_CHECK(spi_bus_initialize(HOST_ID, &bus_config, SPI_DMA_CHAN));
// SPI设备配置:半双工(NAND推荐)、80MHz时钟
spi_device_interface_config_t devcfg = {
.clock_speed_hz = EXAMPLE_FLASH_FREQ_KHZ * 1000,
.mode = 0,
.spics_io_num = PIN_CS,
.queue_size = 10,
.flags = 0,
};
// 添加SPI设备到总线
ESP_ERROR_CHECK(spi_bus_add_device(HOST_ID, &devcfg, &g_spi_handle));
uint8_t id[4] = {0};
nand_read_jedec_id(g_spi_handle, id);
printf("NAND ID: 0x%02X 0x%02X 0x%02X\n", id[0], id[1], id[2]);
if (id[1] != 0xC8 || id[2] != 0x91) {
ESP_LOGE(TAG_FLASH, "Flash chip ID error! Not GD5F1GM7");
return ESP_ERR_INVALID_VERSION;
}
// SPI NAND Flash专用配置:适配GD5F1GM7的SIO模式(默认),WP/HD硬件禁用
spi_nand_flash_config_t nand_flash_config = {
.device_handle = g_spi_handle,
.io_mode = SPI_NAND_IO_MODE_SIO, // GD5F1GM7支持SIO/DIO/QIO,先选SIO(兼容)
.flags = 0, // 与SPI设备保持一致
};
assert(devcfg.flags == nand_flash_config.flags);
// 初始化NAND Flash设备(驱动自动识别GD5F1GM7芯片ID/容量)
ESP_ERROR_CHECK(spi_nand_flash_init_device(&nand_flash_config, &g_nand_flash_handle));
ESP_LOGI(TAG_FLASH, "GD5F1GM7UEYIG NAND Flash initialized successfully");
return ESP_OK;
}
// ********** 复制JSON文件到NAND Flash(原逻辑不变) **********
static bool copy_json_files_to_ext_flash(const char *source_dir, const char *dest_dir)
{
DIR *dir;
struct dirent *entry;
char source_path[256];
char dest_path[256];
ESP_LOGI(TAG_FLASH, "开始复制JSON文件从 %s 到 %s", source_dir, dest_dir);
dir = opendir(source_dir);
if (dir == NULL) {
ESP_LOGE(TAG_FLASH, "无法打开源目录: %s, err: %s", source_dir, strerror(errno));
return false;
}
int file_count = 0;
while ((entry = readdir(dir)) != NULL) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
char *ext = strrchr(entry->d_name, '.');
if (ext != NULL && strcasecmp(ext, ".json") == 0) {
snprintf(source_path, sizeof(source_path), "%s/%s", source_dir, entry->d_name);
snprintf(dest_path, sizeof(dest_path), "%s/%s", dest_dir, entry->d_name);
ESP_LOGI(TAG_FLASH, "复制文件: %s -> %s", source_path, dest_path);
if (copy_file(source_path, dest_path)) {
file_count++;
ESP_LOGI(TAG_FLASH, "文件复制成功: %s", entry->d_name);
} else {
ESP_LOGE(TAG_FLASH, "文件复制失败: %s", entry->d_name);
}
}
}
closedir(dir);
ESP_LOGI(TAG_FLASH, "总共复制了 %d 个JSON文件", file_count);
return (file_count > 0);
}
// ********** 复制单个文件(原逻辑不变,NAND写入优化) **********
static bool copy_file(const char *source_path, const char *dest_path)
{
FILE *src_file, *dest_file;
char buffer[4096]; // 增大缓冲区,适配NAND写入
size_t bytes_read;
bool need_lock = (strstr(dest_path, "/extflash") != NULL);
if (need_lock) extflash_lock();
src_file = fopen(source_path, "rb");
if (src_file == NULL) {
ESP_LOGE(TAG_FLASH, "无法打开源文件: %s, err: %s", source_path, strerror(errno));
if (need_lock) extflash_unlock();
return false;
}
dest_file = fopen(dest_path, "wb");
if (dest_file == NULL) {
ESP_LOGE(TAG_FLASH, "无法创建目标文件: %s, err: %s", dest_path, strerror(errno));
fclose(src_file);
if (need_lock) extflash_unlock();
return false;
}
while ((bytes_read = fread(buffer, 1, sizeof(buffer), src_file)) > 0) {
if (fwrite(buffer, 1, bytes_read, dest_file) != bytes_read) {
ESP_LOGE(TAG_FLASH, "写入文件失败: %s", dest_path);
fclose(src_file);
fclose(dest_file);
if (need_lock) extflash_unlock();
return false;
}
fflush(dest_file); // NAND建议及时刷新,避免数据丢失
}
fclose(src_file);
fclose(dest_file);
if (need_lock) extflash_unlock();
return true;
}
// ********** 列出目录中的文件(原逻辑不变) **********
void list_files_in_directory(const char *path)
{
DIR *dir;
struct dirent *entry;
struct stat file_stat;
char file_path[256];
dir = opendir(path);
if (dir == NULL) {
ESP_LOGE(TAG_FLASH, "无法打开目录: %s, err: %s", path, strerror(errno));
return;
}
ESP_LOGI(TAG_FLASH, "目录 %s 下的文件/目录:", path);
while ((entry = readdir(dir)) != NULL) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
snprintf(file_path, sizeof(file_path), "%s/%s", path, entry->d_name);
if (stat(file_path, &file_stat) == 0) {
if (S_ISREG(file_stat.st_mode)) {
ESP_LOGI(TAG_FLASH, " 文件: %s, 大小: %ld 字节", entry->d_name, file_stat.st_size);
} else if (S_ISDIR(file_stat.st_mode)) {
ESP_LOGI(TAG_FLASH, " 目录: %s", entry->d_name);
}
}
}
closedir(dir);
}
// ********** 下载文件到NAND Flash(原逻辑微调,适配NAND写入) **********
esp_err_t download_file(char *firmware_url, char *filename)
{
if (filename == NULL || filename[0] == '\0' || !strcmp(filename, ".") || !strcmp(filename, "..")) {
ESP_LOGE(TAG_FLASH, "download_file: 无效文件名");
return ESP_ERR_INVALID_ARG;
}
esp_http_client_config_t config = {
.method = HTTP_METHOD_GET,
.url = firmware_url,
.timeout_ms = 300000,
.buffer_size = 32768,
.buffer_size_tx = 4096,
.keep_alive_enable = true,
.keep_alive_idle = 30,
.keep_alive_interval = 10,
.keep_alive_count = 5,
.disable_auto_redirect = false,
.max_redirection_count = 3,
.auth_type = HTTP_AUTH_TYPE_NONE,
};
typedef struct {
uint32_t total_size;
uint32_t downloaded;
bool download_complete;
esp_err_t last_error;
} download_status_t;
static download_status_t g_download_status = {0};
memset(&g_download_status, 0, sizeof(g_download_status));
esp_err_t ret = ESP_FAIL;
FILE *file = NULL;
uint8_t *buffer = heap_caps_malloc(16384, MALLOC_CAP_SPIRAM);
if (!buffer) {
ESP_LOGE(TAG_FLASH, "Failed to allocate buffer");
return ESP_ERR_NO_MEM;
}
char full_path[256];
snprintf(full_path, sizeof(full_path), "%s/%s", base_path, filename);
extflash_lock();
struct stat st;
if (stat(full_path, &st) == 0 && !S_ISDIR(st.st_mode)) {
ESP_LOGI(TAG_FLASH, "Removing existing file: %s", full_path);
remove(full_path);
}
mkdir(base_path, 0755);
/* 文件名可带子目录,例如 eaf/foo.eaf → /extflash/eaf/ */
{
char parent[256];
snprintf(parent, sizeof(parent), "%s", full_path);
char *slash = strrchr(parent, '/');
if (slash && slash != parent) {
*slash = '\0';
if (strcmp(parent, base_path) != 0 && mkdir(parent, 0755) < 0 && errno != EEXIST) {
ESP_LOGW(TAG_FLASH, "mkdir %s 失败: %s", parent, strerror(errno));
}
}
}
// 断点重连循环(减少重试次数,NAND不建议频繁重试)
for (int retry_count = 0; retry_count <= 5; retry_count++) {
if (retry_count > 0) {
extflash_unlock();
ESP_LOGW(TAG_FLASH, "Retry attempt %d/5", retry_count);
vTaskDelay(pdMS_TO_TICKS(2000 * retry_count));
extflash_lock();
}
file = fopen(full_path, "ab+");
if (!file) {
ESP_LOGE(TAG_FLASH, "Failed to open file: %s, err: %s", full_path, strerror(errno));
continue;
}
fseek(file, 0, SEEK_END);
size_t current_size = ftell(file);
ESP_LOGI(TAG_FLASH, "Current file size: %zu bytes", current_size);
esp_http_client_handle_t client = esp_http_client_init(&config);
if (!client) {
ESP_LOGE(TAG_FLASH, "Failed to initialize HTTP client");
fclose(file);
extflash_unlock();
continue;
}
esp_http_client_set_header(client, "Content-Type", "text/plain");
if (current_size > 0) {
char range_header[64];
snprintf(range_header, sizeof(range_header), "bytes=%zu-", current_size);
esp_http_client_set_header(client, "Range", range_header);
ESP_LOGI(TAG_FLASH, "Setting Range header: %s", range_header);
}
ret = esp_http_client_open(client, 0);
if (ret != ESP_OK) {
ESP_LOGE(TAG_FLASH, "Failed to open HTTP connection: %s", esp_err_to_name(ret));
esp_http_client_cleanup(client);
fclose(file);
extflash_unlock();
continue;
}
int content_length = esp_http_client_fetch_headers(client);
g_download_status.total_size = (content_length > 0) ? (content_length + current_size) : 0;
if (content_length > 0) {
ESP_LOGI(TAG_FLASH, "Content length: %d bytes, Total expected: %" PRIu32 " bytes",
content_length, (uint32_t)g_download_status.total_size);
}
g_download_status.downloaded = current_size;
// 下载数据
int read_len;
bool download_error = false;
int64_t last_progress_time = esp_timer_get_time();
uint32_t last_progress_size = current_size;
while (true) {
read_len = esp_http_client_read(client, (char *)buffer, 16384);
if (read_len < 0) {
ESP_LOGE(TAG_FLASH, "Error reading HTTP data");
download_error = true;
break;
} else if (read_len == 0) {
ESP_LOGI(TAG_FLASH, "HTTP read complete");
break;
}
if (fwrite(buffer, 1, read_len, file) != read_len) {
ESP_LOGE(TAG_FLASH, "Write error: expected %d, got %zu", read_len, (size_t)fwrite(buffer,1,read_len,file));
download_error = true;
break;
}
if (g_download_status.downloaded % 65536 == 0) {
fflush(file);
}
g_download_status.downloaded += read_len;
// 打印下载进度
int64_t current_time = esp_timer_get_time();
if (current_time - last_progress_time > 1000000) {
uint32_t speed = (g_download_status.downloaded - last_progress_size) / 1024;
if (g_download_status.total_size > 0) {
int percent = (g_download_status.downloaded * 100) / g_download_status.total_size;
ESP_LOGI(TAG_FLASH, "Progress: %" PRIu32 "/%" PRIu32 " bytes (%d%%) - Speed: %" PRIu32 " KB/s",
(uint32_t)g_download_status.downloaded, (uint32_t)g_download_status.total_size, percent, (uint32_t)speed);
} else {
ESP_LOGI(TAG_FLASH, "Progress: %" PRIu32 " bytes - Speed: %" PRIu32 " KB/s",
(uint32_t)g_download_status.downloaded, (uint32_t)speed);
}
last_progress_time = current_time;
last_progress_size = g_download_status.downloaded;
}
if (esp_http_client_get_status_code(client) == 408) {
ESP_LOGE(TAG_FLASH, "HTTP timeout occurred");
download_error = true;
break;
}
}
fflush(file);
fclose(file);
if (download_error) {
esp_http_client_cleanup(client);
extflash_unlock();
continue;
}
if (g_download_status.total_size > 0 && g_download_status.downloaded < g_download_status.total_size) {
ESP_LOGW(TAG_FLASH, "Download incomplete: %" PRIu32 "/%" PRIu32 " bytes",
(uint32_t)g_download_status.downloaded, (uint32_t)g_download_status.total_size);
esp_http_client_cleanup(client);
extflash_unlock();
continue;
}
// 下载成功,创建解压任务
ESP_LOGI(TAG_FLASH, "Download completed successfully: %" PRIu32 " bytes",
(uint32_t)g_download_status.downloaded);
esp_http_client_cleanup(client);
if (!is_zip_file(filename)) {
ESP_LOGI(TAG_FLASH, "非 ZIP,已写入 %s(%" PRIu32 " 字节),跳过解压",
full_path, (uint32_t)g_download_status.downloaded);
extflash_unlock();
free(buffer);
return ESP_OK;
}
char *filename_copy = strdup(filename);
if (!filename_copy) {
ESP_LOGE(TAG_FLASH, "strdup(filename) failed");
extflash_unlock();
free(buffer);
return ESP_ERR_NO_MEM;
}
size_t stack_size = 256 * 1024;
heap_caps_free(s_extract_stack);
vPortFree(s_extract_tcb);
s_extract_stack = heap_caps_malloc(stack_size, MALLOC_CAP_SPIRAM);
s_extract_tcb = pvPortMalloc(sizeof(StaticTask_t));
if (s_extract_stack && s_extract_tcb) {
ESP_LOGI(TAG_FLASH, "[DEBUG] Creating extract_task (static, SPIRAM stack %u bytes)", (unsigned)stack_size);
xTaskCreateStatic(extract_task, "extract_task", stack_size / sizeof(StackType_t),
(void *)filename_copy, configMAX_PRIORITIES - 1,
s_extract_stack, s_extract_tcb);
} else {
ESP_LOGW(TAG_FLASH, "[DEBUG] SPIRAM alloc failed, falling back to dynamic task");
xTaskCreatePinnedToCore(extract_task, "extract_task", stack_size, (void *)filename_copy,
configMAX_PRIORITIES - 1, NULL, 1);
heap_caps_free(s_extract_stack);
vPortFree(s_extract_tcb);
s_extract_stack = NULL;
s_extract_tcb = NULL;
}
extflash_unlock();
free(buffer);
return ESP_OK;
}
extflash_unlock();
free(buffer);
ESP_LOGE(TAG_FLASH, "Download failed after all retries");
return ESP_FAIL;
}
static void extract_task(void *pvParameters)
{
char *filename_copy = (char *)pvParameters;
ESP_LOGI("EXTRACT", "[DEBUG] extract_task started, file=%s, core=%d, prio=%d, stack_hwm=%u",
filename_copy ? filename_copy : "NULL",
(int)xPortGetCoreID(),
(int)uxTaskPriorityGet(NULL),
(unsigned)uxTaskGetStackHighWaterMark(NULL));
extflash_extraction_started();
extflash_lock();
if (filename_copy) {
extract_json_zip(filename_copy);
free(filename_copy);
}
list_files_in_directory(base_path);
/* ── Fallback: main_task 不可用时直接处理下载结果 ── */
if (main_task_handle == NULL)
{
char pack_id = zip_extractor_get_last_downloaded_pack();
if (pack_id != '0')
{
ESP_LOGW("EXTRACT", "main_task not running, handling DOWNLOAD_SUCCESS directly (pack='%c')", pack_id);
if (notification_task_handle)
xTaskNotify(notification_task_handle, BLE_SEND_DL_EMOJI_SUCCESS, eSetBits);
emoji_storage_add_downloaded_pack(pack_id);
emoji_storage_save_using_pack(pack_id);
ESP_LOGI("EXTRACT", "pack '%c' saved as downloaded & using", pack_id);
char path_buf[64];
snprintf(path_buf, sizeof(path_buf), "/extflash/%c/default.json", pack_id);
struct stat st;
if (stat(path_buf, &st) == 0)
{
ESP_LOGI("EXTRACT", "Playing animation: %s (%ld bytes)", path_buf, (long)st.st_size);
request_play_lottie_path(path_buf);
}
else
{
ESP_LOGW("EXTRACT", "Animation file not found: %s, skip play", path_buf);
}
g_emoji_upgrade_downloaded_pack = pack_id;
g_emoji_upgrade_pending_finalize = true;
ESP_LOGI("EXTRACT", "下载成功 pack='%c':等待首次切换后收尾(不重启,不回原来)", pack_id);
clear_system_event(DOWNLOAD_SUCCESS_BIT);
}
else
{
ESP_LOGW("EXTRACT", "main_task not running, handling DOWNLOAD_FAILED directly");
if (notification_task_handle)
xTaskNotify(notification_task_handle, BLE_SEND_DL_EMOJI_FAILED, eSetBits);
clear_system_event(DOWNLOAD_FAILED_BIT);
}
}
extflash_unlock();
extflash_extraction_finished();
/*
ESP_LOGI("EXTRACT", "[DEBUG] extract_task finished, about to delete self. "
"queue_pending=%d, stack_hwm=%u",
(int)uxQueueMessagesWaiting(system_event_queue),
(unsigned)uxTaskGetStackHighWaterMark(NULL)); //这里解压时注意可能死机
*/
vTaskDelete(NULL);
}
// ********** 检查文件是否为ZIP格式(原逻辑不变) **********
static bool is_zip_file(const char *filename)
{
if (!filename) return false;
const char *dot = strrchr(filename, '.');
return (dot && strcasecmp(dot, ".zip") == 0);
}
// ********** 反初始化NAND Flash(新增,用于程序退出/复位) **********
static void example_deinit_nand_flash(void)
{
if (g_nand_flash_handle) {
esp_vfs_fat_nand_unmount(base_path, g_nand_flash_handle);
spi_nand_flash_deinit_device(g_nand_flash_handle);
g_nand_flash_handle = NULL;
}
if (g_spi_handle) {
spi_bus_remove_device(g_spi_handle);
g_spi_handle = NULL;
}
spi_bus_free(HOST_ID);
ESP_LOGI(TAG_FLASH, "GD5F1GM7UEYIG NAND Flash deinitialized");
}
// ********** 正确 NAND 版本:无参数 **********
static bool example_mount_fatfs(void)
{
ESP_LOGI(TAG_FLASH, "挂载 NAND FAT 文件系统");
if (g_nand_flash_handle == NULL) {
ESP_LOGE(TAG_FLASH, "NAND Flash 句柄为空");
return false;
}
// FAT 配置(NAND 专用)
const esp_vfs_fat_mount_config_t mount_config = {
.max_files = 6,
#ifdef CONFIG_EXAMPLE_FORMAT_IF_MOUNT_FAILED
.format_if_mount_failed = true,
#else
.format_if_mount_failed = false,
#endif
.allocation_unit_size = 16 * 1024,
};
// NAND 专用挂载接口(不是 NOR 的 spiflash_mount!)
esp_err_t err = esp_vfs_fat_nand_mount(base_path, g_nand_flash_handle, &mount_config);
if (err != ESP_OK) {
ESP_LOGE(TAG_FLASH, "NAND FATFS 挂载失败: %s (0x%x)", esp_err_to_name(err), err);
return false;
}
// 打印容量
uint64_t bytes_total, bytes_free;
esp_vfs_fat_info(base_path, &bytes_total, &bytes_free);
ESP_LOGI(TAG_FLASH, "NAND FAT 挂载成功: %s", base_path);
ESP_LOGI(TAG_FLASH, "总容量: %" PRIu64 " KB, 剩余: %" PRIu64 " KB",
bytes_total / 1024, bytes_free / 1024);
return true;
}
// GD5F1GM7UEYIG 读 ID 函数
// 输出:id[0] = 0xC8, id[1] = 0x91, id[2] = 0x01
esp_err_t nand_read_jedec_id(spi_device_handle_t handle, uint8_t id[3])
{
if (handle == NULL || id == NULL) {
return ESP_ERR_INVALID_ARG;
}
memset(id, 0, 3);
// GD5F1GM7 标准 Read ID 时序:必须读 4 字节
uint8_t tx_data[4] = {0x9F, 0x00, 0x00, 0x00};
uint8_t rx_data[4] = {0};
spi_transaction_t t = {
.length = 4 * 8, // 必须 32bit,才能读到 0x91
.tx_buffer = tx_data,
.rx_buffer = rx_data,
};
esp_err_t ret = spi_device_transmit(handle, &t);
if (ret == ESP_OK) {
id[0] = rx_data[1]; // 0xC8 厂商ID
id[1] = rx_data[2]; // 0x91 设备ID ✅
id[2] = rx_data[3]; // 扩展ID
}
return ret;
}
static void example_list_data_partitions(void)
{
ESP_LOGI(TAG_FLASH, "Listing data partitions:");
// 查找所有数据类型的分区
esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, NULL);
// 遍历并打印每个分区的信息
for (; it != NULL; it = esp_partition_next(it))
{
const esp_partition_t *part = esp_partition_get(it);
ESP_LOGI(TAG_FLASH, "- partition '%s', subtype %d, offset 0x%" PRIx32 ", size %" PRIu32 " kB",
part->label, part->subtype, part->address, part->size / 1024);
}
esp_partition_iterator_release(it); // 释放迭代器
}
// ********** 主入口函数(替换原flash_main,适配NAND+WP/HD接3.3V) **********
void flash_main(void)
{
if (s_extflash_mutex == NULL) {
s_extflash_mutex = xSemaphoreCreateMutex();
if (s_extflash_mutex == NULL) {
ESP_LOGE(TAG_FLASH, "Failed to create extflash mutex");
}
}
// 1. 初始化GD5F1GM7UEYIG SPI NAND Flash(WP/HD硬件接3.3V)
if (example_init_nand_flash() != ESP_OK) {
ESP_LOGE(TAG_FLASH, "GD5F1GM7UEYIG NAND Flash初始化失败");
return;
}
// 2. 挂载NAND FAT文件系统
if (!example_mount_fatfs()) {
ESP_LOGE(TAG_FLASH, "NAND FAT文件系统挂载失败,无法继续");
example_deinit_nand_flash();
return;
}
example_list_data_partitions();
// 3. 校验 /extflash/fonts 下字体是否就绪(不再从 app 内同步)
if (!sync_embedded_fonts_to_extflash()) {
ESP_LOGW(TAG_FLASH, "部分字体缺失/异常,运行时可能回退到默认字体");
}
// 4. 列出NAND Flash中的文件(含 fonts 目录,便于产线验收字体)
extflash_lock();
ESP_LOGI(TAG_FLASH, "GD5F1GM7UEYIG NAND Flash中的文件列表:");
list_files_in_directory(base_path);
char fonts_dir[64] = {0};
snprintf(fonts_dir, sizeof(fonts_dir), "%s/fonts", base_path);
ESP_LOGI(TAG_FLASH, "字体目录文件列表: %s", fonts_dir);
list_files_in_directory(fonts_dir);
extflash_unlock();
ESP_LOGI(TAG_FLASH, "GD5F1GM7UEYIG NAND Flash初始化&挂载完成,业务逻辑就绪");
}
+46
View File
@@ -0,0 +1,46 @@
#ifndef FLASH_H
#define FLASH_H
#pragma GCC diagnostic ignored "-Wformat-truncation"
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "esp_flash.h"
#include "esp_flash_spi_init.h"
#include "esp_partition.h"
#include "esp_vfs.h"
#include "esp_vfs_fat.h"
#include "esp_system.h"
#include "soc/spi_pins.h"
#include <dirent.h>
#include <sys/stat.h>
#define HOST_ID SPI3_HOST // 使用SPI3主机(VSPI)
#define PIN_MOSI 39
#define PIN_MISO 41
#define PIN_CLK 40
#define PIN_CS 42
#define PIN_WP -1
#define PIN_HD -1
#define SPI_DMA_CHAN SPI_DMA_CH_AUTO // DMA通道:自动选择
/* extflash 访问互斥:所有对 /extflash 的 fopen/fread/fwrite/stat/opendir 等操作需在持锁期间进行 */
void extflash_lock(void);
void extflash_unlock(void);
/* 解压进行中标志:动画切换应延后,避免与解压写操作竞争 */
void extflash_extraction_started(void);
void extflash_extraction_finished(void);
bool extflash_is_extracting(void);
void list_files_in_directory(const char *path);
static bool copy_file(const char *source_path, const char *dest_path);
static void extract_task(void *pvParameters);
static bool example_mount_fatfs(void);
void flash_main(void);
esp_err_t download_file(char *firmware_url, char *filename);
static esp_err_t read_chip_id(spi_device_handle_t handle, uint8_t *id_data, size_t len);
#endif // FLASH_H
+35
View File
@@ -0,0 +1,35 @@
#include "cJSON.h"
#include "json.h"
// KEY_VALUE_PAIR data_pair =
// {
// .key = "action",
// .value = NULL,
// .son1_pair =
// }
void json_create(const char *str)
{
cJSON *report_msg = cJSON_CreateObject();
cJSON_AddStringToObject(report_msg, "action", "message");
cJSON_AddStringToObject(report_msg, "target_id", "0");
cJSON *data = cJSON_CreateObject();
cJSON_AddStringToObject(data, "status", "download_finish_ok");
cJSON_AddItemToObject(report_msg, "data", data);
char *report_str = cJSON_PrintUnformatted(report_msg);
}
cJSON* json_create_any_type(int type,cJSON *root, const char *key, const char *value)
{
cJSON *root = cJSON_CreateObject();
switch(type)
{
case 1://string
}
}
+18
View File
@@ -0,0 +1,18 @@
#ifndef __JSON_H__
#define __JSON_H__
typedef struct
{
char* key;
char* value;
KEY_VALUE_PAIR son1_pair;
KEY_VALUE_PAIR son2_pair;
KEY_VALUE_PAIR son3_pair;
KEY_VALUE_PAIR son4_pair;
KEY_VALUE_PAIR son5_pair;
}KEY_VALUE_PAIR;
#endif
+20
View File
@@ -0,0 +1,20 @@
menu "Example Configuration"
config BROKER_URL
string "Broker URL"
default "mqtt://mqtt.eclipseprojects.io"
help
URL of the broker to connect to
config BROKER_URL_FROM_STDIN
bool
default y if BROKER_URL = "FROM_STDIN"
config EXAMPLE_FORMAT_IF_MOUNT_FAILED
bool "Format the flash if mount failed"
default n
help
If this config item is set, format_if_mount_failed will be set to true and the
NAND FAT filesystem will be formatted if the mount has failed.
endmenu
File diff suppressed because it is too large Load Diff
+103
View File
@@ -0,0 +1,103 @@
#ifndef __LVGL_INIT_H__
#define __LVGL_INIT_H__
#include "config.h"
#define extraflash_flag (1 << 0)
#define NOTIFY_EXIT_ALL_MODES 0xFFFE
/* 下载成功后按路径播放:request_play_lottie_path 写入路径,change_task 收到 0xFFFF 时从此缓冲区取路径播放 */
#define NOTIFY_PLAY_BY_PATH 0xFFFF
/* 动画完成回调中不能持锁切换,由 change_task 延迟执行 */
#define NOTIFY_RESUME_MUSIC_LOOP 0xFFFC
#define NOTIFY_MUSIC_INTRO_DONE 0xFFFB
#define NOTIFY_SENTINEL_DO_SWITCH 0xFFFA
#define NOTIFY_SWITCH_BY_QUEUE 0xFFF9
#define NOTIFY_MUSIC_LISTEN_ANOTHER_PASS 0xFFF8
#define NOTIFY_MUSIC_REPLAY_INTRO 0xFFF7
#define NOTIFY_ACTION_QUEUE_READY 0xFFF6
#define NOTIFY_RESUME_CALLING_LOOP 0xFFF5
/* 通话中插播表情时收到挂断:等插播播完后由 completed_cb 通知,再回默认 */
#define NOTIFY_DEFERRED_HANGUP 0xFFF4
#define NOTIFY_START_AI_RESPOND_LOOP 0xFFF3
#define NOTIFY_RESUME_AI_RESPOND_LOOP 0xFFF2
#define LOTTIE_BUF_SIZE (LOTTIE_ANIM_WIDTH * LOTTIE_ANIM_HEIGHT * 4)
extern char *lottie_file_path;
extern int lottie_id;
extern int last_lottie_id;
/* 系统主初始化流程完成标志:true 后才允许处理 BLE 业务指令 */
extern volatile bool g_system_init_done;
/** BLE play_sticker 正在展示表情时置 true,回到默认等场景清 false;IMU 幸运表情需检查此标志 */
extern volatile bool g_ble_play_sticker_active;
/* BLE play_sticker 单槽覆盖队列:播放中收到新指令时,仅保留最后一条,待当前动画播完后切换 */
extern volatile bool g_ble_play_sticker_pending_valid;
extern volatile uint16_t g_ble_play_sticker_pending_action_id;
extern volatile uint8_t g_ble_play_sticker_pending_direction;
/* KTV 持续循环播放态标志(用于 BLE 直通插播判断) */
extern volatile bool g_is_ktv_playing;
extern bool back2default;
extern bool first_display;
extern StackType_t *main_task_stack;
extern StaticTask_t main_task_tcb;
extern _lock_t lvgl_api_lock;
extern esp_lcd_panel_handle_t panel_handle;
extern esp_lcd_panel_io_handle_t io_handle;
extern const qspi_amoled_lcd_init_cmd_t lcd_init_cmds[9];
extern volatile char g_target_path[32];
extern TaskHandle_t change_task_handle;
/** 当前任务是否为 LVGL port 任务(该任务上调用 LVGL API 时通常已持有 lvgl_api_lock) */
bool lvgl_port_task_is_current(void);
extern esp_lcd_panel_handle_t panel_handle;
extern const char *lottie_files[];
extern int64_t last_activity_timestamp;
extern bool block_auto_sleep;
/** 表情包下载流程进行中:收到新的下载指令时应忽略(避免并发) */
extern volatile bool g_emoji_download_in_progress;
/** 刷新自动休眠空闲时间戳(触摸、BLE 指令等) */
void system_keep_alive(void);
/** 设置是否阻止自动休眠(OTA / 配网等场景使用) */
void set_block_auto_sleep(bool block);
typedef struct
{
wakenet_state_t wakenet_mode;
esp_mn_state_t state;
int command_id;
} sr_result_t;
void set_lottie_id(int id);
void lottie_init(void);
esp_err_t app_sr_init(void);
esp_err_t app_lcd_init(void);
esp_err_t app_lvgl_init(void);
esp_err_t app_change_init(void);
esp_err_t BMI_init(void);
void switch_lottie_animation(int anim_index);
void load_brightness_from_nvs(void);
/** 将设置页亮度滑条与 get_system_brightness_percent() 对齐;可从任意任务调用,内部经 lv_async_call 投递到 LVGL 线程 */
void request_brightness_slider_sync(void);
esp_err_t app_touch_init(void);
void switch_lottie_animation_cross_path(const char *emoji_path);
void random_emoji(uint16_t n);
void request_play_lottie_path(const char *path);
void set_current_lottie_range_ratio(float start_ratio, float end_ratio);
void lvgl_deinit_for_wifi(void);
/* 向 change_task 投递 BLE 动作指令(队列化,避免与音乐内部通知互相覆盖) */
bool change_task_post_action(uint16_t action_id);
/** 获取当前正在展示的 Lottie 路径(升级快照等场景使用) */
const char *lvgl_get_current_lottie_path(void);
/** 清空当前路径缓存(用于强制重播同一路径) */
void lvgl_clear_current_lottie_path(void);
void lottie_anim_completed_cb(lv_anim_t *anim);
bool example_notify_lvgl_flush_ready(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx);
#endif
File diff suppressed because it is too large Load Diff
+40
View File
@@ -0,0 +1,40 @@
#ifndef __LVGL_LOTTIE_H__
#define __LVGL_LOTTIE_H__
#include "config.h"
extern lv_obj_t *ui_Screen_Lottie;
extern lv_obj_t *g_music_preload_lottie;
extern uint8_t *g_music_preload_fb;
extern char g_music_preload_path[];
extern bool g_music_preload_ready;
extern bool resume_music_loop_after_oneshot; /* 持续播放音乐时收到其他指令,播完该指令一次后切回音乐段 */
extern bool resume_calling_loop_after_oneshot; /* 通话循环段被插播时,插播播完后恢复 20%-80% 循环 */
void handle_normal_sticker(uint16_t action_id, const char *path_to_play, int *last_active_page_ptr);
bool handle_play_music(uint16_t action_id, const char *path_to_play, int *last_active_page_ptr);
bool handle_sentinel_mode(uint16_t action_id, const char *path_to_play, int *last_active_page_ptr);
void reset_all_playback_state(void);
void switch_to_default_emoji(void);
bool handle_stop_music(uint16_t action_id, int *last_active_page_ptr);
bool handle_internal_commands(uint32_t bits, int *last_active_page_ptr);
void clear_music_preload(void);
void try_preload_music_listen_lottie(const char *path);
void set_current_lottie_range_time(float start_s, float end_s);
/** 通话接听后处于循环段时为 true;与 is_playing_music 类似,供 BLE 直通打断 pending */
bool lvgl_lottie_calling_active_for_ble(void);
/** AI 回复循环态(conversation.json)时为 true,供 BLE 持续态直通判断 */
bool lvgl_lottie_ai_respond_loop_active_for_ble(void);
/** play_sticker 是否跳过电机:仅 AI 循环内 0x0045、及 direction==0+resume/循环态(误回中);同向重复由 motor 层处理 */
bool lvgl_lottie_should_skip_play_sticker_motor(uint16_t action_id, uint8_t direction);
/** 正念 breath 两遍播放窗口内返回 true;BLE 侧据此丢弃所有指令 */
bool lvgl_lottie_mindfulness_block_ble_for_ble(void);
/** lottie_anim_completed_cb 内优先调用:若存在「插播中挂起的挂断」,则通知 change_task 回默认并返回 true */
bool lvgl_lottie_completed_try_deferred_hangup(void);
/** lottie_anim_completed_cb 内调用:若 AI 回复循环被插播,通知恢复并返回 true */
bool lvgl_lottie_completed_try_resume_ai_respond_loop(void);
/** lottie_anim_completed_cb 内调用:若 0x0020/21/2E/2F hearing 段已播完,通知启动 AI 回复循环并返回 true */
bool lvgl_lottie_completed_try_start_ai_respond_loop(void);
#endif
+6
View File
@@ -0,0 +1,6 @@
#include "lvgl_lottie_logic.h"
+6
View File
@@ -0,0 +1,6 @@
#ifndef __LVGL_LOTTIE_LOGIC_H__
#define __LVGL_LOTTIE_LOGIC_H__
#include "lvgl_init.h"
#include "config.h"
#endif
+82
View File
@@ -0,0 +1,82 @@
#include "LVGL/lvgl_src.h"
const char* lottie_files[] =
{
"/extflash/0/default.json", // 默认表情1 √
"/extflash/0/calling.json", // 拨打电话2 √
"/extflash/0/camping.json", // 露营模式3 √
"/extflash/0/charging.json", // 充电中4 √
"/extflash/0/charging-complete.json", // 充电完成5 √
"/extflash/0/cold.json", // 打开冷气6 √
"/extflash/0/guard.json", // 戴上墨镜?7 √
"/extflash/0/hearing.json", // 倾听中8 √
"/extflash/0/hello.json", // 打招呼9 √
"/extflash/0/hot.json", // 打开暖气10 √
"/extflash/0/music.json", // 打开音乐11 √
"/extflash/0/navigation.json", // 导航模式12 √
"/extflash/0/protect.json", // 哨兵模式13 √
"/extflash/0/saftybelt-no.json", // 安全带未系好14 √
"/extflash/0/saftybelt.json", // 安全带已系好15 √
"/extflash/0/snap.json", // 打瞌睡16 √
"/extflash/0/start.json", // 启动17 √
"/extflash/0/turnaround.json", // 左看右看18 √
"/extflash/0/washing.json", // 清洗中19 √
"/extflash/0/welcome.json", // 欢迎20 √
"/extflash/0/window-left.json", // 打开左窗21 √
"/extflash/0/window-right.json", // 打开右窗22 √
"/extflash/0/arrive.json", // 导航到达23 √
"/extflash/0/bless.json", // 抽签24 √
"/extflash/0/break.json", // 急刹车25 √
"/extflash/0/breath.json", // 冥想26 √
"/extflash/0/horoscope.json", // 星座运势27 √
"/extflash/0/money.json", // 颠簸28 √
"/extflash/0/sing.json", // 唱歌29 √
"/extflash/0/speedup.json", // 急加速30 √
"/extflash/0/turnright.json", // 右转弯31 √
"/extflash/0/turnleft.json", // 左转弯32 √
"/extflash/0/helloleft.json", // 打招呼左33 √
"/extflash/0/music-listen.json", // 持续播放音乐34 √
//待机随机表情包
"/extflash/0/tired.json", // 疲劳驾驶tired35 --- 新增12个表情;
"/extflash/0/trafficjam.json", // 堵车 36
"/extflash/0/holiday.json", //假期holiday 37 √
"/extflash/0/calmdown.json", //冷静 38 √
"/extflash/0/becool.json", // 装酷becool 39 --- 新增12个表情; √
"/extflash/0/boring.json", // 无聊boring 40 √
"/extflash/0/cheer.json", // 庆祝cheer 41 √
"/extflash/0/cry.json", // 哭cry 42 √
"/extflash/0/curious.json", // 好奇curious 43 √
"/extflash/0/eating.json", // 吃eating 44 √
"/extflash/0/happy.json", // 高兴happy 45 √
"/extflash/0/like.json", // 喜欢like 46 √
"/extflash/0/liuliuliu.json", // 666🤙🤙🤙 47 √
"/extflash/0/love.json", // 爱心 love 48 √
"/extflash/0/shy.json", // 害羞 shy 49 √
"/extflash/0/thinking.json", // 思考thinking 🤔 50 √
"/extflash/0/autodrive.json", //智能驾驶模式 51 √
"/extflash/0/backhome.json", //回家 52 √
"/extflash/0/outhome.json", //离家 53 √
"/extflash/0/card.json", //智付 刷卡 54 √
"/extflash/0/toofast.json", //超速120 55 √
"/extflash/0/2hours.json", //连续驾驶2小时 56 √
"/extflash/0/trafficlight.json", //交通灯 57 √
"/extflash/0/conversation.json", //AI 语音回答 58 √
"/extflash/0/parktronic.json", //自动泊车 59 √
"/extflash/0/backcar.json", //倒车 60 √
"/extflash/0/distract.json", //分心担忧紧张 61 √
"/extflash/0/deng.json", //大灯 62 √
"/extflash/0/windshield-wiper.json", //雨刮 63 √
"/extflash/0/birthday.json", //生日庆祝 64 √
"/extflash/0/child-lock.json", //儿童锁--上锁 65 √
"/extflash/0/child-unlock.json", //儿童锁--解锁 66 √
"/extflash/0/conversation1.json", //语音回复随机表情1 √
"/extflash/0/conversation2.json", //语音回复随机表情2 √
};
const uint32_t lottie_files_length = sizeof(lottie_files)/sizeof(lottie_files[0]);
+86
View File
@@ -0,0 +1,86 @@
#ifndef __LVGL_SRC_H__
#define __LVGL_SRC_H__
#include <stdint.h>
typedef enum
{
default_emojo = 0, //默认表情 默认表情 1
calling_emojo, //打电话 打电话2
camping_emojo, //露营模式 露营模式3
charging_emojo, //开始充电 开始充电4
charged_over_emojo, //充电完成 充电完成5
cold_air_emojo, //冷风 冷风6
guard_emojo, //墨镜😎哨兵模式 哨兵模式7
hearing_emojo, //聆听 聆听8
hello_emojo, //打招呼 打招呼9
warm_air_emojo, //暖风 暖风10
music_emojo, //播放音乐 播放音乐11
navigation_modle_emojo, //开始导航 开始导航12
protect_emojo, //保护--哨兵模式 左看右看表情13//保护
saftybelt_no_emojo, //未系好安全带 未系好安全带14
saftybelt_emojo, //系安全带 系安全带15
snap_emojo, //小憩模式 小憩模式16
start_emojo, //驾车出发 驾车出发17
turn_around_emojo, //左看右看表情 左看右18
washing_emojo, //洗车模式 洗车模式19
welcome_emojo, //迎宾模式 迎宾模式20
open_left_window_emojo, //主驾开窗 主驾开窗21
open_right_window_emojo, //副驾开窗 副驾开窗22
arrive_emojo, //到达目的地 到达目的地23
bless_emojo, //祈祷🙏 祈祷24
break_emojo, //刹车 刹车25
breath_emojo, //冥想 冥想26
horoscope_emojo, //星座运势 星座运势27
money_emojo, //金币 掉金币28
sing_emojo, //唱歌 唱歌29
speedup_emojo, //超速 超速30
turn_right_emojo, //右转向 右转向31
turn_left_emojo, //左转向 左转向32
hello_left_emojo, //打招呼左 打招呼左33
music_listen_emojo, //持续播放音乐 持续播放音乐34
tried_emojo, //疲劳驾驶 喝咖啡35
trafficjam_emojo, //堵车 🐌 堵车36
holiday_emojo, //假期 假期 庆祝🎉37
calm_down_emojo, //冷静 冷静38
becool_emojo, //装酷 39
boring_emojo, //无聊 40
cheer_emojo, //庆祝 41
cry_emojo, //哭 42
curious_emojo, //好奇 43
eating_emojo, //吃 44
happy_emojo, //高兴 45
like_emojo, //喜欢 46
liuliuliu_emojo, //666 47
love_emojo, //爱心 48
shy_emojo, //害羞 49
thinking_emojo, //思考 50
autodrive_emojo, //智能驾驶模式 智能驾驶模式51
backhome_emojo, //回家 52
outhome_emojo, //离家 53
card_emojo, //智付 刷卡 54
toofast_emojo, //超速120 55
_2h_emojo, //连续驾驶2小时 56
traffic_light_emojo, //红绿灯 57
AI_respond_emojo, //AI 回复 58
auto_parking_emojo, //自动泊车 59
back_car_emojo, //倒车 60
distract_emojo, //分心担忧紧张 61
deng_emojo, //大灯 62
windshield_wiper_emojo, //雨刮 63
birthday_emojo, //生日庆祝 64
child_lock_emojo, //儿童锁--上锁 65
child_unlock_emojo, //儿童锁--解锁 66
conversation1_emojo, //语音回复随机表情1 67
conversation2_emojo, //语音回复随机表情2 68
}LOTTIE_FILES;
extern const char* lottie_files[];
extern const uint32_t lottie_files_length;
#endif
+7674
View File
File diff suppressed because it is too large Load Diff
+1338
View File
File diff suppressed because it is too large Load Diff
+46
View File
@@ -0,0 +1,46 @@
## Miniz
Miniz is a lossless, high performance data compression library in a single source file that implements the zlib (RFC 1950) and Deflate (RFC 1951) compressed data format specification standards. It supports the most commonly used functions exported by the zlib library, but is a completely independent implementation so zlib's licensing requirements do not apply. Miniz also contains simple to use functions for writing .PNG format image files and reading/writing/appending .ZIP format archives. Miniz's compression speed has been tuned to be comparable to zlib's, and it also has a specialized real-time compressor function designed to compare well against fastlz/minilzo.
## Usage
Releases are available at the [releases page](https://github.com/richgel999/miniz/releases) as a pair of `miniz.c`/`miniz.h` files which can be simply added to a project. To create this file pair the different source and header files are [amalgamated](https://www.sqlite.org/amalgamation.html) during build. Alternatively use as cmake or meson module (or build system of your choice).
## Features
* MIT licensed
* A portable, single source and header file library written in plain C. Tested with GCC, clang and Visual Studio.
* Easily tuned and trimmed down by defines
* A drop-in replacement for zlib's most used API's (tested in several open source projects that use zlib, such as libpng and libzip).
* Fills a single threaded performance vs. compression ratio gap between several popular real-time compressors and zlib. For example, at level 1, miniz.c compresses around 5-9% better than minilzo, but is approx. 35% slower. At levels 2-9, miniz.c is designed to compare favorably against zlib's ratio and speed. See the miniz performance comparison page for example timings.
* Not a block based compressor: miniz.c fully supports stream based processing using a coroutine-style implementation. The zlib-style API functions can be called a single byte at a time if that's all you've got.
* Easy to use. The low-level compressor (tdefl) and decompressor (tinfl) have simple state structs which can be saved/restored as needed with simple memcpy's. The low-level codec API's don't use the heap in any way.
* Entire inflater (including optional zlib header parsing and Adler-32 checking) is implemented in a single function as a coroutine, which is separately available in a small (~550 line) source file: miniz_tinfl.c
* A fairly complete (but totally optional) set of .ZIP archive manipulation and extraction API's. The archive functionality is intended to solve common problems encountered in embedded, mobile, or game development situations. (The archive API's are purposely just powerful enough to write an entire archiver given a bit of additional higher-level logic.)
## Building miniz - Using vcpkg
You can download and install miniz using the [vcpkg](https://github.com/Microsoft/vcpkg) dependency manager:
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh
./vcpkg integrate install
./vcpkg install miniz
The miniz port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository.
## Known Problems
* No support for encrypted archives. Not sure how useful this stuff is in practice.
* Minimal documentation. The assumption is that the user is already familiar with the basic zlib API. I need to write an API wiki - for now I've tried to place key comments before each enum/API, and I've included 6 examples that demonstrate how to use the module's major features.
## Special Thanks
Thanks to Alex Evans for the PNG writer function. Also, thanks to Paul Holden and Thorsten Scheuermann for feedback and testing, Matt Pritchard for all his encouragement, and Sean Barrett's various public domain libraries for inspiration (and encouraging me to write miniz.c in C, which was much more enjoyable and less painful than I thought it would be considering I've been programming in C++ for so long).
Thanks to Bruce Dawson for reporting a problem with the level_and_flags archive API parameter (which is fixed in v1.12) and general feedback, and Janez Zemva for indirectly encouraging me into writing more examples.
## Patents
I was recently asked if miniz avoids patent issues. miniz purposely uses the same core algorithms as the ones used by zlib. The compressor uses vanilla hash chaining as described [here](https://datatracker.ietf.org/doc/html/rfc1951#section-4). Also see the [gzip FAQ](https://web.archive.org/web/20160308045258/http://www.gzip.org/#faq11). In my opinion, if miniz falls prey to a patent attack then zlib/gzip are likely to be at serious risk too.
+195
View File
@@ -0,0 +1,195 @@
#include <math.h>
#include "MOTOR/motor_control.h"
#include "foc.h"
#include "esp_check.h"
#include "motor_timer.h"
#include "step_motor_init.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "motor_control.h"
volatile short Motor_Speed = 0;
volatile short A_PULS_DUTY = 0;
volatile short A_MIUNS_DUTY = 0;
volatile short B_PULS_DUTY = 0;
volatile short B_MIUNS_DUTY = 0;
volatile float Ud = 0;
volatile float Uq = 0;
volatile float theta = 0;
volatile float U_ALPHA = 0;
volatile float U_BETA = 0;
#if MOTOR_WITH_MAGNET_RING
static const float speed_adj = 0.1f; //0.22
static const float voltage_adj = 0.22f; //0.32
#else
static const float speed_adj = 0.13f; //0.22
static const float voltage_adj = 0.22f; //0.32
#endif
static TaskHandle_t s_foc_task_handle = NULL;
static const char *TAG_FOC_CTRL = "foc_ctrl";
static int64_t s_last_duty_log_us = 0;
static uint16_t clamp_duty(short duty)
{
if (duty < 0) {
return 0;
}
if (duty > (short)STEPPER_DUTY_MAX) {
return STEPPER_DUTY_MAX;
}
return (uint16_t)duty;
}
static void foc_update_output_once(void)
{
float speed;
Uq = voltage_adj;
Ud = 0;
if(motor_dir == MOTOR_CW)
{
speed = speed_adj;
}
if(motor_dir == MOTOR_CCW)
{
speed = -speed_adj;
}
theta += speed;
if (theta >= (2.0f * (float)M_PI)) {
theta -= (2.0f * (float)M_PI);
}
inv_park(Ud, Uq, theta, &U_ALPHA, &U_BETA);
PWM_DRIVER_OPENLOOP(U_ALPHA, U_BETA);
stepper_motor_set_duty_raw(clamp_duty(A_PULS_DUTY),
clamp_duty(A_MIUNS_DUTY),
clamp_duty(B_PULS_DUTY),
clamp_duty(B_MIUNS_DUTY));
// int64_t now_us = esp_timer_get_time();
// if (now_us - s_last_duty_log_us >= 1000000) {
// s_last_duty_log_us = now_us;
// // ESP_LOGI(TAG_FOC_CTRL,
// // "duty A+:%d A-:%d B+:%d B-:%d theta:%.3f U_ALPHA:%.3f U_BETA:%.3f",
// // A_PULS_DUTY,
// // A_MIUNS_DUTY,
// // B_PULS_DUTY,
// // B_MIUNS_DUTY,
// // (double)theta,
// // (double)U_ALPHA,
// // (double)U_BETA);
// // }
}
static void foc_task(void *arg)
{
(void)arg;
while (1) {
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
foc_update_output_once();
}
}
void park(float ia, float ib, float theta, float *id, float *iq)
{
float cos_theta = cosf(theta);
float sin_theta = sinf(theta);
*id = ia * cos_theta + ib * sin_theta;
*iq = -ia * sin_theta + ib * cos_theta;
}
void inv_park(float vd, float vq, float theta, float *valpha, float *vbeta)
{
float cos_theta = cosf(theta);
float sin_theta = sinf(theta);
*valpha = vd * cos_theta - vq * sin_theta;
*vbeta = vd * sin_theta + vq * cos_theta;
}
void PWM_DRIVER_OPENLOOP(float U_ALPHA, float U_BETA)
{
int IA = (int)(U_ALPHA * 1572.0f);
int IB = (int)(U_BETA * 1572.0f);
// A_PULS_DUTY = 0;
// A_MIUNS_DUTY = 250;//电流为负数
// B_PULS_DUTY = 250;
// B_MIUNS_DUTY = 0;//电流为负数90.00
// B_PULS_DUTY = (short)(IA + 510);
// A_MIUNS_DUTY = (short)(-IA + 510);
if (U_ALPHA == 0) {
A_PULS_DUTY = 500;
A_MIUNS_DUTY = 500;
}
if (U_BETA == 0) {
B_PULS_DUTY = 500;
B_MIUNS_DUTY = 500;
}
if (U_ALPHA < 0) {
A_PULS_DUTY = 0;
A_MIUNS_DUTY = (short)(-IA + 510);
}
if (U_ALPHA > 0) {
A_MIUNS_DUTY = 0;
A_PULS_DUTY = (short)(IA + 510);
}
if (U_BETA > 0) {
B_MIUNS_DUTY = 0;
B_PULS_DUTY = (short)(IB + 510);
}
if (U_BETA < 0) {
B_PULS_DUTY = 0;
B_MIUNS_DUTY = (short)(-IB + 510);
}
}
bool IRAM_ATTR foc_cal_cb(void *user_data)
{
TaskHandle_t task_handle = (TaskHandle_t)user_data;
BaseType_t high_task_wakeup = pdFALSE;
if (task_handle != NULL) {
vTaskNotifyGiveFromISR(task_handle, &high_task_wakeup);
}
return high_task_wakeup == pdTRUE;
}
esp_err_t foc_openloop_start(void)
{
if (s_foc_task_handle == NULL) {
BaseType_t task_ok = xTaskCreatePinnedToCore(foc_task,
"foc_task",
4096,
NULL,
configMAX_PRIORITIES - 2,
&s_foc_task_handle,
0);
if (task_ok != pdPASS) {
return ESP_ERR_NO_MEM;
}
}
// if (!stepper_motor_output_is_enabled()) {
// stepper_motor_output_enable();
// }
foc_update_output_once();
ESP_RETURN_ON_ERROR(foc_timer_init(FOC_TIMER_DEFAULT_HZ, foc_cal_cb, s_foc_task_handle),
TAG_FOC_CTRL,
"foc timer init failed");
ESP_RETURN_ON_ERROR(foc_timer_start(),
TAG_FOC_CTRL,
"foc timer start failed");
return ESP_OK;
}
+26
View File
@@ -0,0 +1,26 @@
#ifndef __FOC_H__
#define __FOC_H__
#include <stdbool.h>
#include <stdint.h>
#include "esp_err.h"
#include "esp_check.h"
extern volatile short Motor_Speed;
extern volatile short A_PULS_DUTY;
extern volatile short A_MIUNS_DUTY;
extern volatile short B_PULS_DUTY;
extern volatile short B_MIUNS_DUTY;
extern volatile float Ud;
extern volatile float Uq;
extern volatile float theta;
extern volatile float U_ALPHA;
extern volatile float U_BETA;
void park(float ia, float ib, float theta, float *id, float *iq);
void inv_park(float vd, float vq, float theta, float *valpha, float *vbeta);
void PWM_DRIVER_OPENLOOP(float U_ALPHA, float U_BETA);
bool foc_cal_cb(void *user_data);
esp_err_t foc_openloop_start(void);
#endif
+307
View File
@@ -0,0 +1,307 @@
// #include <MOTOR/motor.h>
// // #include "freertos/FreeRTOS.h"
// // #include "freertos/task.h"
// static const char *TAG_MOTOR = "MotorControl";
// /** 置位后 motor_run_for_ms 与哨兵停 2 秒会提前退出,用于随时停止哨兵并回正 */
// static volatile bool motor_stop_request = false;
// static bool flag = true;
// void angle_control_task(void *pvParameter);
// float current_angle = 0;
// float target_angle = 0;
// float motor_speed = 90;
// uint32_t motor_rpm = 10;
// uint32_t motor_pwm = 0;
// uint32_t PWM_QUEUE[2] = {MOTOR_60_PWM, MOTOR_90_PWM};
// uint32_t SPEED_QUEUE[2] = {MOTOR_60_SPEED, MOTOR_90_SPEED};
// TaskHandle_t angle_control_handle = NULL;
// // 电机控制结构体
// typedef struct
// {
// gpio_num_t ain1_pin;
// gpio_num_t ain2_pin;
// ledc_channel_t ain1_channel;
// ledc_channel_t ain2_channel;
// } motor_config_t;
// static motor_config_t motor = {
// .ain1_pin = MOTOR_AIN1_GPIO,
// .ain2_pin = MOTOR_AIN2_GPIO,
// .ain1_channel = LEDC_CHANNEL_AIN1,
// .ain2_channel = LEDC_CHANNEL_AIN2};
// // 初始化PWM
// void pwm_init(void)
// {
// // 配置PWM定时器
// ledc_timer_config_t ledc_timer = {
// .speed_mode = LEDC_MODE,
// .duty_resolution = LEDC_DUTY_RES,
// .timer_num = LEDC_TIMER,
// .freq_hz = LEDC_FREQ,
// .clk_cfg = LEDC_AUTO_CLK};
// ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));
// // 配置PWM通道
// ledc_channel_config_t ledc_channel_ain1 = {
// .speed_mode = LEDC_MODE,
// .channel = motor.ain1_channel,
// .timer_sel = LEDC_TIMER,
// .intr_type = LEDC_INTR_DISABLE,
// .gpio_num = motor.ain1_pin,
// .duty = 0,
// .hpoint = 0};
// ledc_channel_config_t ledc_channel_ain2 = {
// .speed_mode = LEDC_MODE,
// .channel = motor.ain2_channel,
// .timer_sel = LEDC_TIMER,
// .intr_type = LEDC_INTR_DISABLE,
// .gpio_num = motor.ain2_pin,
// .duty = 0,
// .hpoint = 0};
// ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel_ain1));
// ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel_ain2));
// }
// void gpio_init(void)
// {
// // 配置GPIO为输出模式[2](@ref)
// gpio_config_t io_conf = {
// .pin_bit_mask = (1ULL << motor.ain1_pin | motor.ain2_pin),
// .mode = GPIO_MODE_OUTPUT,
// .pull_up_en = GPIO_PULLUP_DISABLE,
// .pull_down_en = GPIO_PULLDOWN_ENABLE,
// .intr_type = GPIO_INTR_DISABLE};
// gpio_config(&io_conf);
// }
// /**
// * 按固定时长运行电机(可被 motor_stop_request 中断,收到停止时立即停)
// * @param pwm 电机 PWM,正负表示方向
// * @param duration_ms 运行时长(毫秒)
// * @return 实际运行的毫秒数(若被中断则小于 duration_ms,用于按比例回正)
// */
// static uint32_t motor_run_for_ms(int32_t pwm, uint32_t duration_ms)
// {
// if (duration_ms == 0)
// return 0;
// TickType_t xWake = xTaskGetTickCount();
// TickType_t xStart = xWake;
// const TickType_t xPeriod = pdMS_TO_TICKS(1);
// TickType_t xEnd = xWake + pdMS_TO_TICKS(duration_ms);
// while (xTaskGetTickCount() < xEnd)
// {
// if (motor_stop_request)
// {
// motor_set_speed(0);
// return (uint32_t)pdTICKS_TO_MS(xTaskGetTickCount() - xStart);
// }
// motor_set_speed(pwm);
// vTaskDelayUntil(&xWake, xPeriod);
// }
// return duration_ms;
// }
// /** 可被 motor_stop_request 中断的延时(毫秒),用于哨兵停 2 秒等 */
// static void delay_ms_check_stop(uint32_t duration_ms)
// {
// TickType_t xWake = xTaskGetTickCount();
// TickType_t xEnd = xWake + pdMS_TO_TICKS(duration_ms);
// while (xTaskGetTickCount() < xEnd)
// {
// if (motor_stop_request)
// return;
// vTaskDelay(pdMS_TO_TICKS(1));
// }
// }
// void motor_request_stop(void)
// {
// motor_stop_request = true;
// }
// static void motor_clear_stop_request(void)
// {
// motor_stop_request = false;
// }
// // 设置电机速度和方向
// void motor_set_speed(int32_t speed)
// {
// speed = -speed;
// if (speed == 0)
// {
// // 停止电机
// ledc_set_duty(LEDC_MODE, motor.ain1_channel, 0);
// ledc_set_duty(LEDC_MODE, motor.ain2_channel, 0);
// ledc_update_duty(LEDC_MODE, motor.ain1_channel);
// ledc_update_duty(LEDC_MODE, motor.ain2_channel);
// }
// else if (speed > 0)
// {
// // 正转
// ledc_set_duty(LEDC_MODE, motor.ain1_channel, speed * 1023 / MOTOR_MAX_PWM);
// ledc_set_duty(LEDC_MODE, motor.ain2_channel, 0);
// ledc_update_duty(LEDC_MODE, motor.ain1_channel);
// ledc_update_duty(LEDC_MODE, motor.ain2_channel);
// }
// else
// {
// // 反转
// speed = -speed;
// ledc_set_duty(LEDC_MODE, motor.ain1_channel, 0);
// ledc_set_duty(LEDC_MODE, motor.ain2_channel, speed * 1023 / MOTOR_MAX_PWM);
// ledc_update_duty(LEDC_MODE, motor.ain1_channel);
// ledc_update_duty(LEDC_MODE, motor.ain2_channel);
// }
// /* motor_speed = (speed < 0) ? -speed : speed; */
// // ESP_LOGI(TAG_MOTOR, "Motor speed set to: %ld", speed);
// }
// // 设置目标角度
// void motor_set_angle(float angle)
// {
// set_flag(false);
// target_angle = angle;
// int angle_milli = (int)(target_angle * 1000.0f);
// ESP_LOGI(TAG_MOTOR, "Target angle set to: %d.%03d degrees", angle_milli / 1000, abs(angle_milli % 1000));
// }
// // 转速控制函数,单位为度每秒
// void motor_set_motor_speed(uint32_t speed)
// {
// motor_speed = speed;
// ESP_LOGI(TAG_MOTOR, "Motor_speed set to: %lu", speed);
// }
// // 电机初始化
// void motor_init(void)
// {
// gpio_init();
// pwm_init();
// target_angle = 0;
// current_angle = 0;
// motor_set_motor_speed(MOTOR_90_SPEED);
// /* motor_set_speed(MOTOR_30_PWM);
// vTaskDelay(pdMS_TO_TICKS(12000));
// motor_set_speed(0); */
// xTaskCreate(angle_control_task, "angle_control", 4096, NULL, 5, &angle_control_handle);
// }
// // 角度控制任务(需要编码器反馈,这里是开环控制示例)
// void angle_control_task(void *pvParameter)
// {
// TickType_t xLastWakeTime = xTaskGetTickCount();
// const TickType_t xControlPeriod = pdMS_TO_TICKS(1); // pdMS_TO_TICKS(20)
// ESP_LOGI(TAG_MOTOR, "Starting angle control task");
// uint32_t frist_back_time = 450;
// uint32_t pwm_value = 42;//53
// uint32_t back_time_period = 2000;//ms -- 5800
// for (size_t i = 0; i < 1; i++)
// // while(1)
// {
// motor_set_speed(pwm_value );
// vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(800));
// motor_set_speed(0);
// vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(1000));
// motor_set_speed(-pwm_value );
// vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(frist_back_time)); // 150
// ESP_LOGE("angle_task","back_time :%lu",frist_back_time);
// motor_set_speed(0);
// }
// uint32_t bits = 0;
// // int target_position = 0;
// bool motor_run_flag = false;
// MOTOR_STATE motor_state;
// while (1)
// {
// // 必须是有效的任务句柄
// BaseType_t notify_ret = xTaskNotifyWait(0x00000000, ULONG_MAX, &bits, 0); // pdMS_TO_TICKS(100)
// if (notify_ret == pdPASS)
// {
// motor_run_flag = true;
// if (bits == 0x00)
// {
// motor_clear_stop_request();
// motor_set_speed(0);
// }
// else if (bits == 0x01)
// {
// motor_run_for_ms(-pwm_value , 600);//200
// motor_set_speed(0);
// vTaskDelay(pdMS_TO_TICKS(back_time_period));
// motor_run_for_ms(pwm_value , 450);
// motor_set_speed(0);
// }
// else if (bits == 0x02)
// {
// motor_run_for_ms(pwm_value , 600);//110
// motor_set_speed(0);
// vTaskDelay(pdMS_TO_TICKS(back_time_period));
// motor_run_for_ms(-pwm_value,450);//310
// motor_set_speed(0);
// }
// else if (bits == 0x05)
// {
// motor_run_for_ms(-pwm_value, 150);
// motor_set_speed(0);
// vTaskDelay(pdMS_TO_TICKS(2000));
// motor_run_for_ms(pwm_value, 150);
// motor_set_speed(0);
// }
// else if (bits == 0x06)
// {
// motor_run_for_ms(pwm_value , 150);
// motor_set_speed(0);
// vTaskDelay(pdMS_TO_TICKS(2000));
// motor_run_for_ms(-pwm_value , 115);
// motor_set_speed(0);
// }
// else if (bits == 0x07)
// {
// motor_run_for_ms(-pwm_value , 1000);
// motor_set_speed(0);
// vTaskDelay(pdMS_TO_TICKS(2000));
// motor_run_for_ms(pwm_value , 900);
// motor_set_speed(0);
// }
// else if (bits == 0x11)
// {
// motor_run_for_ms(-pwm_value , 10);
// motor_set_speed(0);
// }
// else if (bits == 0x12)
// {
// motor_run_for_ms(pwm_value , 10);
// motor_set_speed(0);
// }
// else
// {
// frist_back_time = bits;
// // ESP_LOGE("angle_task","back_time :%lu",back_time);
// }
// }
// }
// }
// static void set_flag(bool state)
// {
// flag = state;
// }
// bool get_flag(void)
// {
// return flag;
// }
+104
View File
@@ -0,0 +1,104 @@
// #ifndef _MOTOR_H_
// #define _MOTOR_H_
// #ifdef __cplusplus
// extern "C" {
// #endif
// #include <math.h>
// #include <stdio.h>
// #include "freertos/FreeRTOS.h"
// #include "freertos/task.h"
// #include "freertos/queue.h"
// #include "driver/ledc.h"
// #include "driver/gpio.h"
// #include "esp_log.h"
// #include "esp_timer.h"
// #include <string.h>
// #include "nvs.h"
// #include "nvs_flash.h"
// #include <stdint.h>
// void motor_init(void);
// void motor_set_speed(int32_t speed);
// void motor_set_angle(float angle); //外部接口 -60 主驾 +60副驾
// void motor_set_motor_speed(uint32_t speed); //调整目标转速
// static void set_flag(bool state);
// bool get_flag(void);
// // 用于标识角度数据的键名
// #define STORAGE_NAMESPACE "motor_data"
// #define ANGLE_KEY "current_angle"
// // DRV8837引脚定义
// #define MOTOR_AIN1_GPIO GPIO_NUM_1
// #define MOTOR_AIN2_GPIO GPIO_NUM_2
// // #define MOTOR_AIN1_GPIO GPIO_NUM_9
// // #define MOTOR_AIN2_GPIO GPIO_NUM_46
// //#define MOTOR_BIN1_GPIO GPIO_NUM_14 // 可选,用于第二个电机
// //#define MOTOR_BIN2_GPIO GPIO_NUM_15 // 可选,用于第二个电机
// // PWM配置
// #define LEDC_TIMER LEDC_TIMER_0
// #define LEDC_MODE LEDC_LOW_SPEED_MODE
// #define LEDC_CHANNEL_AIN1 LEDC_CHANNEL_0
// #define LEDC_CHANNEL_AIN2 LEDC_CHANNEL_1
// #define LEDC_DUTY_RES LEDC_TIMER_10_BIT // 10位分辨率(0-1023)
// #define LEDC_FREQ 1000 // 1kHz PWM频率
// // 电机参数
// #define MOTOR_MAX_PWM 100
// #define MOTOR_90_SPEED 90
// #define MOTOR_90_PWM 42
// #define MOTOR_60_SPEED 60
// #define MOTOR_60_PWM 53 //30 50
// // #define MOTOR_30_SPEED 30
// // #define MOTOR_50_SPEED 50
// // #define MOTOR_50_PWM 44
// // #define MOTOR_45_SPEED 45
// // #define MOTOR_45_PWM 34
// // #define MOTOR_40_SPEED 40
// // #define MOTOR_40_PWM 28
// // #define MOTOR_36_SPEED 36
// // #define MOTOR_36_PWM 25
// // #define MOTOR_30_SPEED 30
// // #define MOTOR_30_PWM 21
// #define ANGLE_LIMIT 3
// extern TaskHandle_t angle_control_handle;
// typedef enum
// {
// motor_stop = 0 ,
// motor_turn_to_right,
// motor_turn_to_left,
// }MOTOR_STATE;
// // typedef enum
// // {
// // position_zero = 0,
// // position_right_15,//右边偏移15度
// // position_right_30,//右边偏移30度
// // position_right_60,//右边偏移60度
// // position_left_15,//左边偏移15度
// // position_left_30,//左边偏移30度
// // position_left_60,//左边偏移60度
// // position_left_180,//左边偏移180度
// // position_left_120,//左边偏移180度
// // position_left_240,//左边偏移180度
// // }MOTOR_POSITION;
// void motor_turn_aroud(uint8_t cmd);
// /** 请求电机立即停止(用于哨兵模式等可中断场景) */
// void motor_request_stop(void);
// #ifdef __cplusplus
// }
// #endif
// #endif /* TEST_H_ */
+413
View File
@@ -0,0 +1,413 @@
#include "motor_control.h"
#include "freertos/projdefs.h"
#include "freertos/queue.h"
#include "step_motor_init.h"
#include "motor_io.h"
#include "foc.h"
#include <limits.h>
#include <stdint.h>
#include <stdbool.h>
#include "esp_check.h"
#include "esp_log.h"
#include "BLE/ble.h"
static const char *TAG_MOTOR_CTL = "motor_control";
TaskHandle_t motor_control_handle = NULL;
#define MOTOR_CMD_QUEUE_LEN 16
static QueueHandle_t s_motor_cmd_q = NULL;
typedef enum
{
MOTOR_LAT_UNKNOWN = 0,
MOTOR_LAT_LEFT,
MOTOR_LAT_RIGHT,
MOTOR_LAT_CENTER,
} motor_lateral_t;
/** 仅电机任务写、其它任务读;用于左右切换时是否插 0x0A */
static volatile motor_lateral_t s_motor_last_lateral = MOTOR_LAT_CENTER;
/** 最近一次成功入队的 play_sticker 方向字节;同字节+同侧向则不再重复入队(如已是 0x01 又来 0x01) */
static uint8_t s_last_play_sticker_direction = 0xFF;
// MOTOR_POSITION motor_position ;//记录步进电机的位置
MOTOR_POSITION motor_current_position = MOTOR_POSITION_ZERO;
MOTOR_POSITION motor_target_position = MOTOR_POSITION_ZERO;
MOTOR_DIR motor_dir = MOTOR_CW;
SemaphoreHandle_t motor_action_down_sem = NULL;
static volatile bool s_sentinel_motor_running = false;
static volatile bool s_sentinel_stop_requested = false;
#if MOTOR_WITH_MAGNET_RING
const MOTOR_RUN_TIME motor_run_time =
{
.run_to_left_15 = 250,
.run_to_left_30 = 500,
.run_to_left_90 = 1000,
.run_to_left_180 = 1800,
.run_to_right_15 = 250,
.run_to_right_30 = 500,
.run_to_right_180 = 1800,
};
uint16_t OB_BACK_TIME = 130;
#elif OLD_MOTOR
const MOTOR_RUN_TIME motor_run_time =
{
.run_to_left_15 = 250,
.run_to_left_30 = 500,
.run_to_left_90 = 750,
.run_to_left_180 = 1300,
.run_to_right_15 = 250,
.run_to_right_30 = 500,
.run_to_right_180 = 1300,
};
uint16_t OB_BACK_TIME = 55;
#else
const MOTOR_RUN_TIME motor_run_time =
{
.run_to_left_15 = 250,
.run_to_left_30 = 450,
.run_to_left_90 = 1250,
.run_to_left_180 = 1950,
.run_to_right_15 = 250,
.run_to_right_30 = 450,
.run_to_right_180 = 1950,
};
uint16_t OB_BACK_TIME = 55;
#endif
static void motor_run_steps(uint32_t command);
/** 与上次已在电机任务里执行完的 notify 值相同则忽略,避免 BLE 连发 / completed_cb 重复补发导致同向连转 */
static uint32_t s_last_executed_motor_notify = UINT32_MAX;
static bool motor_cmd_is_left(uint32_t cmd)
{
return cmd == 0x01u || cmd == 0x05u || cmd == 0x07u;
}
static bool motor_cmd_is_right(uint32_t cmd)
{
return cmd == 0x02u || cmd == 0x06u || cmd == 0x08u;
}
static void motor_update_lateral_after_cmd(uint32_t cmd)
{
if (motor_cmd_is_left(cmd))
{
s_motor_last_lateral = MOTOR_LAT_LEFT;
}
else if (motor_cmd_is_right(cmd))
{
s_motor_last_lateral = MOTOR_LAT_RIGHT;
}
else if (cmd == 0x00u || cmd == 0x0Au || cmd == 0x0Bu)
{
s_motor_last_lateral = MOTOR_LAT_CENTER;
}
}
bool motor_post_cmd(uint32_t cmd)
{
if (s_motor_cmd_q == NULL)
{
ESP_LOGW(TAG_MOTOR_CTL, "motor_post_cmd: queue not ready cmd=0x%02lX", (unsigned long)cmd);
return false;
}
if (xQueueSend(s_motor_cmd_q, &cmd, 0) != pdPASS)
{
uint32_t dropped = 0;
(void)xQueueReceive(s_motor_cmd_q, &dropped, 0);
if (xQueueSend(s_motor_cmd_q, &cmd, 0) != pdPASS)
{
ESP_LOGW(TAG_MOTOR_CTL, "motor_post_cmd: enqueue failed cmd=0x%02lX", (unsigned long)cmd);
return false;
}
ESP_LOGW(TAG_MOTOR_CTL, "motor_post_cmd: queue full dropped=0x%02lX enq=0x%02lX",
(unsigned long)dropped, (unsigned long)cmd);
}
return true;
}
bool motor_post_play_sticker_direction(uint8_t direction)
{
const uint32_t cmd = (uint32_t)direction;
/* 0x00:协议表示「本条不追加转向」;若头仍在左/右侧,必须用 0x0A 物理回中,
* 不能发 0x00(openloop stop 与回中语义不同),否则左播途中接 0x10/0x00 会丢回中。 */
if (cmd == 0x00u)
{
const motor_lateral_t last = s_motor_last_lateral;
if (last == MOTOR_LAT_LEFT || last == MOTOR_LAT_RIGHT)
{
if (!motor_post_cmd(0x0Au))
{
return false;
}
s_last_play_sticker_direction = 0xFF;
return true;
}
return true;
}
if (cmd == 0x0Au || cmd == 0x0Bu)
{
if (!motor_post_cmd(cmd))
{
return false;
}
s_last_play_sticker_direction = 0xFF;
return true;
}
if (!motor_cmd_is_left(cmd) && !motor_cmd_is_right(cmd))
{
if (!motor_post_cmd(cmd))
{
return false;
}
s_last_play_sticker_direction = (uint8_t)cmd;
return true;
}
const motor_lateral_t last = s_motor_last_lateral;
/* 侧向未变且与上次下发的方向字节相同:不重复入队(如当前已是 0x01 姿态又来 0x01) */
if ((motor_cmd_is_left(cmd) && last == MOTOR_LAT_LEFT) || (motor_cmd_is_right(cmd) && last == MOTOR_LAT_RIGHT))
{
if ((uint8_t)cmd == s_last_play_sticker_direction)
{
ESP_LOGI(TAG_MOTOR_CTL, "skip redundant play_sticker direction 0x%02lX (same as last)", cmd);
return true;
}
}
const bool need_home =
(last == MOTOR_LAT_LEFT && motor_cmd_is_right(cmd)) || (last == MOTOR_LAT_RIGHT && motor_cmd_is_left(cmd));
if (need_home && !motor_post_cmd(0x0Au))
{
return false;
}
if (!motor_post_cmd(cmd))
{
return false;
}
s_last_play_sticker_direction = (uint8_t)cmd;
return true;
}
void motor_post_cmd_from_isr(uint32_t cmd, BaseType_t *pxHigherPriorityTaskWoken)
{
if (s_motor_cmd_q == NULL)
{
return;
}
if (xQueueSendFromISR(s_motor_cmd_q, &cmd, pxHigherPriorityTaskWoken) != pdPASS)
{
uint32_t dropped = 0;
(void)xQueueReceiveFromISR(s_motor_cmd_q, &dropped, pxHigherPriorityTaskWoken);
(void)xQueueSendFromISR(s_motor_cmd_q, &cmd, pxHigherPriorityTaskWoken);
}
}
void motor_control_init(void)
{
// PWM 初始化 任务创建
//motor_io_init();
stepper_motor_init();
s_motor_cmd_q = xQueueCreate(MOTOR_CMD_QUEUE_LEN, sizeof(uint32_t));
if (s_motor_cmd_q == NULL)
{
ESP_LOGE(TAG_MOTOR_CTL, "motor cmd queue create failed");
}
// 固定到 CPU1,避免与 CPU0 上的 LVGL task 竞争;优先级降为 5
xTaskCreatePinnedToCore(motor_control_task, "motor_control_task", 4096, NULL, 8, &motor_control_handle, 0);
}
void motor_control_task(void *pvParameter)
{
uint32_t bits = 0;
motor_action_down_sem = xSemaphoreCreateBinary();
xSemaphoreGive(motor_action_down_sem);
(void)pvParameter;
s_motor_last_lateral = MOTOR_LAT_CENTER;
motor_current_position =MOTOR_POSITION_ZERO;
motor_run_dir(MOTOR_CW);
stepper_motor_output_enable();
vTaskDelay(pdMS_TO_TICKS(motor_run_time.run_to_left_180));
motor_current_position = MOTOR_POSITION_LEFT_180;
stepper_motor_output_disable();
ESP_LOGW("TAG_MOTOR_CTL", "motor_control_task: TURNED LEFT DONE");
s_motor_last_lateral = MOTOR_LAT_LEFT;
vTaskDelay(pdMS_TO_TICKS(2000));
motor_run_dir(MOTOR_CCW);
stepper_motor_output_enable();
vTaskDelay(pdMS_TO_TICKS(motor_run_time.run_to_right_180));
stepper_motor_output_disable();
s_motor_last_lateral = MOTOR_LAT_CENTER;
motor_current_position = MOTOR_POSITION_ZERO;
ESP_LOGW("TAG_MOTOR_CTL", "motor_control_task: TURNED RIGHT DONE");
while (1)
{
if (s_motor_cmd_q == NULL)
{
vTaskDelay(pdMS_TO_TICKS(20));
continue;
}
if (xQueueReceive(s_motor_cmd_q, &bits, pdMS_TO_TICKS(20)) != pdPASS)
{
continue;
}
if (bits == s_last_executed_motor_notify)
{
ESP_LOGD(TAG_MOTOR_CTL, "skip duplicate motor cmd 0x%02lX", (unsigned long)bits);
continue;
}
motor_run_steps(bits);
s_last_executed_motor_notify = bits;
motor_update_lateral_after_cmd(bits);
}
}
static void motor_run_steps(uint32_t command)
{
uint32_t back_time_ms = 0;
if (command == 0x00)
{
stepper_motor_stop();
motor_current_position = MOTOR_POSITION_ZERO;
}
else if (command == 0x01)
{
motor_run_dir(MOTOR_CW);
stepper_motor_output_enable();
vTaskDelay(pdMS_TO_TICKS(motor_run_time.run_to_left_30));
stepper_motor_output_disable();
motor_current_position = MOTOR_POSITION_LEFT_30;
}
else if (command == 0x02)
{
motor_run_dir(MOTOR_CCW);
stepper_motor_output_enable();
vTaskDelay(pdMS_TO_TICKS(motor_run_time.run_to_right_30));
stepper_motor_output_disable();
motor_current_position = MOTOR_POSITION_RIGHT_30;
}
else if (command == 0x05)
{
// stepper_motor_run_steps(STEPPER_DIR_FORWARD, ANGLE_STEP_15 * 4);
motor_run_dir(MOTOR_CW);
stepper_motor_output_enable();
vTaskDelay(pdMS_TO_TICKS(motor_run_time.run_to_left_15));
stepper_motor_output_disable();
motor_current_position = MOTOR_POSITION_LEFT_15;
}
else if (command == 0x06)
{
// stepper_motor_run_steps(STEPPER_DIR_REVERSE, ANGLE_STEP_15 * 4);
motor_run_dir(MOTOR_CCW);
stepper_motor_output_enable();
vTaskDelay(pdMS_TO_TICKS(motor_run_time.run_to_right_15));
stepper_motor_output_disable();
motor_current_position = MOTOR_POSITION_RIGHT_15;
}
else if (command == 0x07)
{
// stepper_motor_run_steps(STEPPER_DIR_FORWARD, ANGLE_STEP_180 * 4);
motor_run_dir(MOTOR_CW);
stepper_motor_output_enable();
vTaskDelay(pdMS_TO_TICKS(motor_run_time.run_to_left_180));
stepper_motor_output_disable();
motor_current_position = MOTOR_POSITION_LEFT_180;
if(sentinel_mode_is_active == true)
{
vTaskDelay(pdMS_TO_TICKS(2000));
xSemaphoreGive(motor_action_down_sem);
}
}
else if (command == 0x08)
{
// stepper_motor_run_steps(STEPPER_DIR_FORWARD, ANGLE_STEP_180 * 4);
motor_run_dir(MOTOR_CCW);
stepper_motor_output_enable();
vTaskDelay(pdMS_TO_TICKS(motor_run_time.run_to_right_180));
stepper_motor_output_disable();
motor_current_position = MOTOR_POSITION_RIGHT_180;
if(sentinel_mode_is_active == true)
{
vTaskDelay(pdMS_TO_TICKS(2000));
xSemaphoreGive(motor_action_down_sem);
}
}
else if (command == 0x0A)
{
// stepper_motor_run_steps(STEPPER_DIR_FORWARD, ANGLE_STEP_30 * 4);
if(motor_current_position == MOTOR_POSITION_ZERO)back_time_ms = 0;
else if(motor_current_position == MOTOR_POSITION_LEFT_15 || motor_current_position == MOTOR_POSITION_LEFT_30 || motor_current_position == MOTOR_POSITION_LEFT_180)
{
motor_run_dir(MOTOR_CCW);
if(motor_current_position == MOTOR_POSITION_LEFT_15)back_time_ms = motor_run_time.run_to_right_15;
else if(motor_current_position == MOTOR_POSITION_LEFT_30)back_time_ms = motor_run_time.run_to_right_30;
else if(motor_current_position == MOTOR_POSITION_LEFT_180)back_time_ms = motor_run_time.run_to_right_180;
}
else if(motor_current_position == MOTOR_POSITION_RIGHT_15 || motor_current_position == MOTOR_POSITION_RIGHT_30|| motor_current_position == MOTOR_POSITION_RIGHT_180)
{
motor_run_dir(MOTOR_CW);
if(motor_current_position == MOTOR_POSITION_RIGHT_15)back_time_ms = motor_run_time.run_to_left_15;
else if(motor_current_position == MOTOR_POSITION_RIGHT_30)back_time_ms = motor_run_time.run_to_left_30;
else if(motor_current_position == MOTOR_POSITION_RIGHT_180)back_time_ms = motor_run_time.run_to_left_180;
}
if(back_time_ms != 0)
{
ESP_LOGW(TAG_MOTOR_CTL, "0X0A back_time_ms START,motor_current_position=%d,back_time_ms=%d",motor_current_position,back_time_ms);
stepper_motor_output_enable();
vTaskDelay(pdMS_TO_TICKS(back_time_ms));
}
stepper_motor_output_disable();
ESP_LOGW(TAG_MOTOR_CTL, "0X0A back_time_ms END");
if(sentinel_mode_is_active == true)
{
vTaskDelay(pdMS_TO_TICKS(2000));
xSemaphoreGive(motor_action_down_sem);
}
motor_current_position = MOTOR_POSITION_ZERO;
}
else if (command == 0x0B)
{
ESP_LOGE("MOTOR_CONTROL", "MOTOR_CONTROL_STOP");
vTaskDelay(pdMS_TO_TICKS(100));
// motor_run_dir(MOTOR_CW);
// stepper_motor_output_enable();
// vTaskDelay(pdMS_TO_TICKS(35));
stepper_motor_output_disable();
motor_current_position = MOTOR_POSITION_ZERO;
}
}
void motor_run_dir(MOTOR_DIR dir)
{
if(dir == MOTOR_CW)
{
motor_dir = MOTOR_CW;
}
if(dir == MOTOR_CCW)
{
motor_dir = MOTOR_CCW;
}
}
+68
View File
@@ -0,0 +1,68 @@
#ifndef __MOTOR_CONTROL_H__
#define __MOTOR_CONTROL_H__
#ifdef __cplisplus
extern "C" {
#endif
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#define MOTOR_WITH_MAGNET_RING 0 //带磁环的时候
#define OLD_MOTOR 0 //旧电机参数
typedef enum
{
MOTOR_POSITION_ZERO = 0,
MOTOR_POSITION_LEFT_15 ,
MOTOR_POSITION_RIGHT_15 ,
MOTOR_POSITION_LEFT_30 ,
MOTOR_POSITION_RIGHT_30 ,
MOTOR_POSITION_LEFT_90 ,
MOTOR_POSITION_RIGHT_90 ,
MOTOR_POSITION_LEFT_180 ,
MOTOR_POSITION_RIGHT_180,
MOTOR_POSITION_LEFT_2 ,//微步
}MOTOR_POSITION;
typedef enum
{
MOTOR_CW,
MOTOR_CCW,
}MOTOR_DIR;
typedef struct
{
uint32_t run_to_left_15;
uint32_t run_to_left_30;
uint32_t run_to_left_90;
uint32_t run_to_left_180;
uint32_t run_to_right_15;
uint32_t run_to_right_30;
uint32_t run_to_right_180;
}MOTOR_RUN_TIME;
extern TaskHandle_t motor_control_handle;
extern MOTOR_DIR motor_dir;
extern MOTOR_POSITION motor_current_position ;
extern MOTOR_POSITION motor_target_position ;
extern SemaphoreHandle_t motor_action_down_sem;
void motor_control_init(void);
void motor_control_task(void *pvParameter);
void motor_run_dir(MOTOR_DIR dir);
/** 串行入队执行;满则丢最旧再入队,避免 Overwrite 丢命令 */
bool motor_post_cmd(uint32_t cmd);
/** play_sticker 方向:左右切换时先排队 0x0A;0x00 在头仍左/右时排队 0x0A;与上次同字节且侧向未变则跳过 */
bool motor_post_play_sticker_direction(uint8_t direction);
void motor_post_cmd_from_isr(uint32_t cmd, BaseType_t *pxHigherPriorityTaskWoken);
#ifdef __cplusplus
}
#endif
#endif
+194
View File
@@ -0,0 +1,194 @@
#include "motor_io.h"
#include <stdbool.h>
#include "MOTOR/motor_control.h"
#include "step_motor_init.h"
#include "driver/gpio.h"
#include "esp_err.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
static const char *TAG = "motor_io";
static TaskHandle_t s_motor_io_task_handle = NULL;
static volatile bool s_stop_transition_enabled = false;
static volatile int s_stop_from_14 = 0;
static volatile int s_stop_from_47 = 0;
static volatile int s_stop_to_14 = 0;
static volatile int s_stop_to_47 = 0;
volatile int s_io14_last_level ;
volatile int s_io47_last_level ;
volatile int s_io14_now_level ;
volatile int s_io47_now_level ;
static void motor_io_log_levels(const char *reason, int level_14, int level_47)
{
ESP_LOGI(TAG, "%s GPIO14=%d GPIO47=%d", reason, level_14, level_47);
}
static void IRAM_ATTR motor_io_isr_handler(void *arg)
{
(void)arg;
BaseType_t hpw = pdFALSE;
s_io14_now_level = motor_io_get_level_14();
s_io47_now_level = motor_io_get_level_47();
if (motor_control_handle != NULL)
{
if(s_io14_last_level == 0 && s_io47_last_level == 1)
{
if(s_io14_now_level == 1 && s_io47_now_level == 1)
{
//发送停止信号
if(motor_current_position == MOTOR_POSITION_LEFT_2)
{
stepper_motor_output_disable();
motor_post_cmd_from_isr((uint32_t)0x0Bu, &hpw);
}
}
}
if(s_io14_last_level == 0 && s_io47_last_level == 1)
{
if(s_io14_now_level == 0 && s_io47_now_level == 0)
{
//发送停止信号
// xTaskNotifyFromISR(motor_control_handle,(uint32_t)0x0B,eSetValueWithOverwrite,NULL);
}
}
}
s_io14_last_level = s_io14_now_level;
s_io47_last_level = s_io47_now_level;
if (s_motor_io_task_handle != NULL)
{
vTaskNotifyGiveFromISR(s_motor_io_task_handle, &hpw);
}
if (hpw == pdTRUE)
{
portYIELD_FROM_ISR();
}
}
int motor_io_get_level_14(void)
{
return gpio_get_level(MOTOR_IO_PIN_14);
}
int motor_io_get_level_47(void)
{
return gpio_get_level(MOTOR_IO_PIN_47);
}
void motor_io_get_levels(int *level_14, int *level_47)
{
if (level_14 != NULL)
{
*level_14 = motor_io_get_level_14();
}
if (level_47 != NULL)
{
*level_47 = motor_io_get_level_47();
}
}
void motor_io_set_stop_transition(int from_14, int from_47, int to_14, int to_47)
{
s_stop_from_14 = from_14;
s_stop_from_47 = from_47;
s_stop_to_14 = to_14;
s_stop_to_47 = to_47;
s_stop_transition_enabled = true;
}
void motor_io_clear_stop_transition(void)
{
s_stop_transition_enabled = false;
}
static void motor_io_monitor_task(void *pvParameter)
{
(void)pvParameter;
int last_level_14 = motor_io_get_level_14();
int last_level_47 = motor_io_get_level_47();
motor_io_log_levels("初始电平:", last_level_14, last_level_47);
while (1)
{
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
int level_14 = motor_io_get_level_14();
int level_47 = motor_io_get_level_47();
if ((level_14 != last_level_14) || (level_47 != last_level_47))
{
motor_io_log_levels("检测到电平变化:", level_14, level_47);
if (s_stop_transition_enabled &&
(last_level_14 == s_stop_from_14) &&
(last_level_47 == s_stop_from_47) &&
(level_14 == s_stop_to_14) &&
(level_47 == s_stop_to_47))
{
ESP_LOGI(TAG, "命中停机跳变: GPIO14=%d GPIO47=%d -> GPIO14=%d GPIO47=%d",
last_level_14,
last_level_47,
level_14,
level_47);
stepper_motor_stop();
}
last_level_14 = level_14;
last_level_47 = level_47;
}
}
}
void motor_io_init(void)
{
const gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << MOTOR_IO_PIN_14) | (1ULL << MOTOR_IO_PIN_47),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_ANYEDGE,
};
ESP_ERROR_CHECK(gpio_config(&io_conf));
if (s_motor_io_task_handle == NULL)
{
xTaskCreatePinnedToCore(motor_io_monitor_task,
"motor_io_task",
MOTOR_IO_TASK_STACK_SIZE,
NULL,
MOTOR_IO_TASK_PRIORITY,
&s_motor_io_task_handle,
1);
}
esp_err_t ret = gpio_install_isr_service(0);
if ((ret != ESP_OK) && (ret != ESP_ERR_INVALID_STATE))
{
ESP_ERROR_CHECK(ret);
}
ESP_ERROR_CHECK(gpio_isr_handler_add(MOTOR_IO_PIN_14, motor_io_isr_handler, NULL));
ESP_ERROR_CHECK(gpio_isr_handler_add(MOTOR_IO_PIN_47, motor_io_isr_handler, NULL));
s_io14_last_level = motor_io_get_level_14();
s_io47_last_level = motor_io_get_level_47();
}
+26
View File
@@ -0,0 +1,26 @@
#ifndef __MOTOR_IO_H__
#define __MOTOR_IO_H__
#include "driver/gpio.h"
#ifdef __cplusplus
extern "C" {
#endif
#define MOTOR_IO_PIN_14 GPIO_NUM_14
#define MOTOR_IO_PIN_47 GPIO_NUM_47
#define MOTOR_IO_TASK_STACK_SIZE 4096
#define MOTOR_IO_TASK_PRIORITY 8 // 优先级 ---
void motor_io_init(void);
int motor_io_get_level_14(void);
int motor_io_get_level_47(void);
void motor_io_get_levels(int *level_14, int *level_47);
void motor_io_set_stop_transition(int from_14, int from_47, int to_14, int to_47);
void motor_io_clear_stop_transition(void);
#ifdef __cplusplus
}
#endif
#endif
+153
View File
@@ -0,0 +1,153 @@
#include "motor_timer.h"
#include <math.h>
#include "driver/gptimer.h"
#include "esp_attr.h"
#include "esp_check.h"
#include "esp_log.h"
static const char *TAG_FOC = "foc_timer"; //打印标签
static gptimer_handle_t s_foc_timer = NULL; //定时器timer句柄
static foc_timer_isr_cb_t s_foc_timer_user_cb = NULL; //用户回调函数--FOC计算
static void *s_foc_timer_user_data = NULL; //用户回调传入一个私有参数
static uint32_t s_foc_timer_freq_hz = FOC_TIMER_DEFAULT_HZ; //定时器频率 3K hz
static bool s_foc_timer_started = false; //记录定时器启动标记
static bool IRAM_ATTR foc_timer_on_alarm_cb(gptimer_handle_t timer,
const gptimer_alarm_event_data_t *edata,
void *user_data)
{
(void)timer;
(void)edata;
(void)user_data;
if (s_foc_timer_user_cb) {
return s_foc_timer_user_cb(s_foc_timer_user_data);
}
return false;
}
static esp_err_t foc_timer_apply_alarm_locked(uint32_t freq_hz)
{
if (freq_hz == 0 || s_foc_timer == NULL) {
return ESP_ERR_INVALID_ARG;
}
uint64_t alarm_count = 1000000ULL / freq_hz;
if (alarm_count == 0) {
alarm_count = 1;
}
gptimer_alarm_config_t alarm_config = {
.reload_count = 0,
.alarm_count = alarm_count,
.flags.auto_reload_on_alarm = true,
};
return gptimer_set_alarm_action(s_foc_timer, &alarm_config);
}
esp_err_t foc_timer_init(uint32_t freq_hz, foc_timer_isr_cb_t cb, void *user_data)
{
if (freq_hz == 0) {
freq_hz = FOC_TIMER_DEFAULT_HZ;
}
if (s_foc_timer != NULL) {
s_foc_timer_user_cb = cb;
s_foc_timer_user_data = user_data;
s_foc_timer_freq_hz = freq_hz;
return foc_timer_apply_alarm_locked(freq_hz);
}
gptimer_config_t timer_config = {
.clk_src = GPTIMER_CLK_SRC_DEFAULT,
.direction = GPTIMER_COUNT_UP,
.resolution_hz = 1000000,
};
ESP_RETURN_ON_ERROR(gptimer_new_timer(&timer_config, &s_foc_timer), TAG_FOC, "create gptimer failed");
gptimer_event_callbacks_t cbs = {
.on_alarm = foc_timer_on_alarm_cb,
};
ESP_RETURN_ON_ERROR(gptimer_register_event_callbacks(s_foc_timer, &cbs, NULL), TAG_FOC, "register gptimer callback failed");
s_foc_timer_user_cb = cb;
s_foc_timer_user_data = user_data;
s_foc_timer_freq_hz = freq_hz;
s_foc_timer_started = false;
ESP_RETURN_ON_ERROR(foc_timer_apply_alarm_locked(freq_hz), TAG_FOC, "set gptimer alarm failed");
esp_err_t err = gptimer_enable(s_foc_timer);
if ((err != ESP_OK) && (err != ESP_ERR_INVALID_STATE)) {
return err;
}
ESP_LOGI(TAG_FOC, "FOC timer init done, freq=%lu Hz", (unsigned long)s_foc_timer_freq_hz);
return ESP_OK;
}
esp_err_t foc_timer_set_frequency(uint32_t freq_hz)
{
if (freq_hz == 0 || s_foc_timer == NULL) {
return ESP_ERR_INVALID_ARG;
}
bool restart = s_foc_timer_started;
if (restart) {
ESP_RETURN_ON_ERROR(gptimer_stop(s_foc_timer), TAG_FOC, "stop gptimer before retune failed");
s_foc_timer_started = false;
}
ESP_RETURN_ON_ERROR(foc_timer_apply_alarm_locked(freq_hz), TAG_FOC, "retune gptimer alarm failed");
s_foc_timer_freq_hz = freq_hz;
if (restart) {
ESP_RETURN_ON_ERROR(gptimer_start(s_foc_timer), TAG_FOC, "restart gptimer failed");
s_foc_timer_started = true;
}
ESP_LOGI(TAG_FOC, "FOC timer frequency set to %lu Hz", (unsigned long)s_foc_timer_freq_hz);
return ESP_OK;
}
esp_err_t foc_timer_start(void)
{
if (s_foc_timer == NULL) {
return ESP_ERR_INVALID_STATE;
}
if (s_foc_timer_started) {
return ESP_OK;
}
ESP_RETURN_ON_ERROR(gptimer_set_raw_count(s_foc_timer, 0), TAG_FOC, "reset gptimer count failed");
esp_err_t err = gptimer_start(s_foc_timer);
if ((err != ESP_OK) && (err != ESP_ERR_INVALID_STATE)) {
return err;
}
s_foc_timer_started = true;
ESP_LOGI(TAG_FOC, "FOC timer started");
return ESP_OK;
}
esp_err_t foc_timer_stop(void)
{
if (s_foc_timer == NULL) {
return ESP_ERR_INVALID_STATE;
}
if (!s_foc_timer_started) {
return ESP_OK;
}
ESP_RETURN_ON_ERROR(gptimer_stop(s_foc_timer), TAG_FOC, "stop gptimer failed");
s_foc_timer_started = false;
ESP_LOGI(TAG_FOC, "FOC timer stopped");
return ESP_OK;
}
uint32_t foc_timer_get_frequency(void)
{
return s_foc_timer_freq_hz;
}
+19
View File
@@ -0,0 +1,19 @@
#ifndef __MOTOR_TIMER_H__
#define __MOTOR_TIMER_H__
#include <stdbool.h>
#include <stdint.h>
#include "esp_err.h"
#include "config.h"
typedef bool (*foc_timer_isr_cb_t)(void *user_data);
#define FOC_TIMER_DEFAULT_HZ 8000U
esp_err_t foc_timer_init(uint32_t freq_hz, foc_timer_isr_cb_t cb, void *user_data);
esp_err_t foc_timer_set_frequency(uint32_t freq_hz);
esp_err_t foc_timer_start(void);
esp_err_t foc_timer_stop(void);
uint32_t foc_timer_get_frequency(void);
#endif
+276
View File
@@ -0,0 +1,276 @@
#include "step_motor_init.h"
#include <stdbool.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_check.h"
#include "esp_timer.h"
static mcpwm_timer_handle_t s_timer;
static mcpwm_oper_handle_t s_oper[2];
static mcpwm_cmpr_handle_t s_cmpr[4]; /* [0]=A+ [1]=A- [2]=B+ [3]=B- */
static mcpwm_gen_handle_t s_gen[4];
static esp_timer_handle_t s_openloop_timer;
static uint32_t s_pwm_period_ticks = STEPPER_PWM_RESOL_HZ / STEPPER_PWM_FREQ_HZ;
static bool s_running = false;
static bool s_output_enabled = false;
static bool s_openloop_running = false;
static uint8_t s_openloop_step_idx = 0;
static stepper_motor_direction_t s_direction = STEPPER_DIR_FORWARD;
static const char *TAG = "stepper_motor";
static const uint16_t s_openloop_phase_table[4][4] = {
{ STEPPER_STARTUP_DUTY_RAW, 0, STEPPER_STARTUP_DUTY_RAW, 0 },
{ 0, STEPPER_STARTUP_DUTY_RAW, STEPPER_STARTUP_DUTY_RAW, 0 },
{ 0, STEPPER_STARTUP_DUTY_RAW, 0, STEPPER_STARTUP_DUTY_RAW },
{ STEPPER_STARTUP_DUTY_RAW, 0, 0, STEPPER_STARTUP_DUTY_RAW },
};
static uint32_t duty_raw_to_ticks(uint16_t duty_raw)
{
if (duty_raw > STEPPER_DUTY_MAX) {
duty_raw = STEPPER_DUTY_MAX;
}
return ((uint64_t)duty_raw * s_pwm_period_ticks) / STEPPER_DUTY_MAX;
// return ((uint64_t)duty_raw * s_pwm_period_ticks);
// return ((uint64_t)duty_raw );
}
static void apply_openloop_phase(uint8_t step_idx)
{
const uint16_t *phase = s_openloop_phase_table[step_idx & 0x03];
stepper_motor_set_duty_raw(phase[0], phase[1], phase[2], phase[3]);
}
static void stepper_motor_step_once(stepper_motor_direction_t direction)
{
if (direction == STEPPER_DIR_FORWARD) {
s_openloop_step_idx = (uint8_t)((s_openloop_step_idx + 1) & 0x03);
} else {
s_openloop_step_idx = (uint8_t)((s_openloop_step_idx + 3) & 0x03);
}
apply_openloop_phase(s_openloop_step_idx);
}
static void openloop_timer_cb(void *arg)
{
(void)arg;
stepper_motor_step_once(s_direction);
}
void stepper_motor_output_enable(void)
{
ESP_ERROR_CHECK(mcpwm_generator_set_force_level(s_gen[0], -1, true));
ESP_ERROR_CHECK(mcpwm_generator_set_force_level(s_gen[1], -1, true));
ESP_ERROR_CHECK(mcpwm_generator_set_force_level(s_gen[2], -1, true));
ESP_ERROR_CHECK(mcpwm_generator_set_force_level(s_gen[3], -1, true));
s_output_enabled = true;
}
void stepper_motor_output_disable(void)
{
ESP_ERROR_CHECK(mcpwm_generator_set_force_level(s_gen[0], 0, true));
ESP_ERROR_CHECK(mcpwm_generator_set_force_level(s_gen[1], 0, true));
ESP_ERROR_CHECK(mcpwm_generator_set_force_level(s_gen[2], 0, true));
ESP_ERROR_CHECK(mcpwm_generator_set_force_level(s_gen[3], 0, true));
s_output_enabled = false;
}
bool stepper_motor_output_is_enabled(void)
{
return s_output_enabled;
}
stepper_motor_direction_t stepper_motor_get_direction(void)
{
return s_direction;
}
void stepper_motor_set_duty_raw(uint16_t a_plus, uint16_t a_minus, uint16_t b_plus, uint16_t b_minus)
{
if (!s_running) {
return;
}
ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(s_cmpr[0], duty_raw_to_ticks(a_plus)));
ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(s_cmpr[1], duty_raw_to_ticks(a_minus)));
ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(s_cmpr[2], duty_raw_to_ticks(b_plus)));
ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(s_cmpr[3], duty_raw_to_ticks(b_minus)));
}
void stepper_motor_openloop_start_dir(stepper_motor_direction_t direction)
{
if (s_openloop_timer == NULL) {
esp_timer_create_args_t timer_args = {
.callback = openloop_timer_cb,
.arg = NULL,
.dispatch_method = ESP_TIMER_TASK,
.name = "step_ol",
.skip_unhandled_events = true,
};
ESP_ERROR_CHECK(esp_timer_create(&timer_args, &s_openloop_timer));
}
if (!s_running) {
return;
}
s_direction = (direction == STEPPER_DIR_REVERSE) ? STEPPER_DIR_REVERSE : STEPPER_DIR_FORWARD;
if (s_openloop_running) {
ESP_ERROR_CHECK(esp_timer_stop(s_openloop_timer));
s_openloop_running = false;
}
if (!s_output_enabled) {
stepper_motor_output_enable();
}
s_openloop_step_idx = 0;
apply_openloop_phase(s_openloop_step_idx);
const uint64_t period_us = 1000000ULL / (uint64_t)(STEPPER_OPENLOOP_STEP_HZ * 4U);
ESP_ERROR_CHECK(esp_timer_start_periodic(s_openloop_timer, period_us));
s_openloop_running = true;
ESP_LOGI(TAG,
"openloop start: dir=%s duty=%u step_hz=%u",
s_direction == STEPPER_DIR_FORWARD ? "forward" : "reverse",
STEPPER_STARTUP_DUTY_RAW,
STEPPER_OPENLOOP_STEP_HZ);
}
void stepper_motor_openloop_start(void)
{
stepper_motor_openloop_start_dir(s_direction);
}
void stepper_motor_forward(void)
{
stepper_motor_openloop_start_dir(STEPPER_DIR_FORWARD);
}
void stepper_motor_reverse(void)
{
stepper_motor_openloop_start_dir(STEPPER_DIR_REVERSE);
}
void stepper_motor_run_steps(stepper_motor_direction_t direction, uint32_t steps)
{
if (!s_running || steps == 0) {
return;
}
if (s_openloop_timer != NULL && s_openloop_running) {
ESP_ERROR_CHECK(esp_timer_stop(s_openloop_timer));
s_openloop_running = false;
}
s_direction = (direction == STEPPER_DIR_REVERSE) ? STEPPER_DIR_REVERSE : STEPPER_DIR_FORWARD;
if (!s_output_enabled) {
stepper_motor_output_enable();
}
for (uint32_t i = 0; i < steps; ++i) {
stepper_motor_step_once(s_direction);
vTaskDelay(pdMS_TO_TICKS(1000 / (STEPPER_OPENLOOP_STEP_HZ * 4U)));
}
stepper_motor_stop();
}
void stepper_motor_openloop_stop(void)
{
if (s_openloop_timer != NULL && s_openloop_running) {
ESP_ERROR_CHECK(esp_timer_stop(s_openloop_timer));
s_openloop_running = false;
}
stepper_motor_set_duty_raw(0, 0, 0, 0);
stepper_motor_output_disable();
ESP_LOGI(TAG, "openloop stop");
}
void stepper_motor_stop(void)
{
stepper_motor_openloop_stop();
}
void stepper_motor_init(void)
{
gpio_config_t gpio_cfg = {
.pin_bit_mask = (1ULL << STEPPER_PIN_A1) | (1ULL << STEPPER_PIN_A2) |
(1ULL << STEPPER_PIN_B1) | (1ULL << STEPPER_PIN_B2),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_ERROR_CHECK(gpio_config(&gpio_cfg));
gpio_set_level(STEPPER_PIN_A1, 0);
gpio_set_level(STEPPER_PIN_A2, 0);
gpio_set_level(STEPPER_PIN_B1, 0);
gpio_set_level(STEPPER_PIN_B2, 0);
ESP_LOGI(TAG, "Step1: GPIO done");
mcpwm_timer_config_t timer_cfg = {
.group_id = STEPPER_MCPWM_GROUP,
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
.resolution_hz = STEPPER_PWM_RESOL_HZ,
.period_ticks = s_pwm_period_ticks,
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
.flags = { .update_period_on_empty = true },
};
ESP_ERROR_CHECK(mcpwm_new_timer(&timer_cfg, &s_timer));
ESP_LOGI(TAG, "Step2: timer created | pwm=%lu Hz | period=%lu",
(unsigned long)STEPPER_PWM_FREQ_HZ,
(unsigned long)s_pwm_period_ticks);
mcpwm_operator_config_t oper_cfg = { .group_id = STEPPER_MCPWM_GROUP };
ESP_ERROR_CHECK(mcpwm_new_operator(&oper_cfg, &s_oper[0]));
ESP_ERROR_CHECK(mcpwm_operator_connect_timer(s_oper[0], s_timer));
ESP_ERROR_CHECK(mcpwm_new_operator(&oper_cfg, &s_oper[1]));
ESP_ERROR_CHECK(mcpwm_operator_connect_timer(s_oper[1], s_timer));
ESP_LOGI(TAG, "Step3: operators connected");
mcpwm_comparator_config_t cmpr_cfg = { .flags.update_cmp_on_tez = true };
ESP_ERROR_CHECK(mcpwm_new_comparator(s_oper[0], &cmpr_cfg, &s_cmpr[0]));
ESP_ERROR_CHECK(mcpwm_new_comparator(s_oper[0], &cmpr_cfg, &s_cmpr[1]));
ESP_ERROR_CHECK(mcpwm_new_comparator(s_oper[1], &cmpr_cfg, &s_cmpr[2]));
ESP_ERROR_CHECK(mcpwm_new_comparator(s_oper[1], &cmpr_cfg, &s_cmpr[3]));
ESP_LOGI(TAG, "Step4: comparators created");
mcpwm_generator_config_t gen_cfg = { .gen_gpio_num = STEPPER_PIN_A1 };
ESP_ERROR_CHECK(mcpwm_new_generator(s_oper[0], &gen_cfg, &s_gen[0]));
gen_cfg.gen_gpio_num = STEPPER_PIN_A2;
ESP_ERROR_CHECK(mcpwm_new_generator(s_oper[0], &gen_cfg, &s_gen[1]));
gen_cfg.gen_gpio_num = STEPPER_PIN_B1;
ESP_ERROR_CHECK(mcpwm_new_generator(s_oper[1], &gen_cfg, &s_gen[2]));
gen_cfg.gen_gpio_num = STEPPER_PIN_B2;
ESP_ERROR_CHECK(mcpwm_new_generator(s_oper[1], &gen_cfg, &s_gen[3]));
ESP_LOGI(TAG, "Step5: generators created");
for (int i = 0; i < 4; ++i) {
ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_timer_event(s_gen[i],
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP,
MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_LOW),//MCPWM_GEN_ACTION_LOW
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_compare_event(s_gen[i],
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP,
s_cmpr[i], MCPWM_GEN_ACTION_HIGH),//
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
}
ESP_LOGI(TAG, "Step6: pwm actions configured");
stepper_motor_output_disable();
ESP_ERROR_CHECK(mcpwm_timer_enable(s_timer));
ESP_ERROR_CHECK(mcpwm_timer_start_stop(s_timer, MCPWM_TIMER_START_NO_STOP));
s_running = true;
stepper_motor_set_duty_raw(0, 0, 0, 0);
ESP_LOGI(TAG, "MCPWM init done, motor standby on boot");
}
+50
View File
@@ -0,0 +1,50 @@
#ifndef __STEP_MOTOT_INIT_H__
#define __STEP_MOTOT_INIT_H__
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stdint.h>
#include "driver/gpio.h"
#include "esp_log.h"
#include "driver/mcpwm_prelude.h"
/* ---- 步进电机引脚 ---- */
#define STEPPER_PIN_A1 GPIO_NUM_20 /* A 相正 */
#define STEPPER_PIN_A2 GPIO_NUM_21 /* A 相负 */
#define STEPPER_PIN_B1 GPIO_NUM_23 /* B 相正 */
#define STEPPER_PIN_B2 GPIO_NUM_22 /* B 相负 */
/* ---- MCPWM 配置 ---- */
#define STEPPER_MCPWM_GROUP 0
#define STEPPER_PWM_RESOL_HZ 4000000U /* 定时器时钟分辨率 4 MHz */
#define STEPPER_PWM_FREQ_HZ 8000U /* PWM 载波频率 8 kHz */
#define STEPPER_DUTY_MAX 1000U /* 占空比满量程,和 A_PULS_DUTY 等变量一致 */
#define STEPPER_STARTUP_DUTY_RAW (STEPPER_DUTY_MAX / 2)
#define STEPPER_OPENLOOP_STEP_HZ 100U /* 上电演示转动的步进频率 */
#define STEPPER_DEFAULT_RUN_STEPS (STEPPER_OPENLOOP_STEP_HZ * 4U)
typedef enum {
STEPPER_DIR_FORWARD = 1,//原为 1
STEPPER_DIR_REVERSE = -1,//原为 -1
} stepper_motor_direction_t;
void stepper_motor_init(void);
void stepper_motor_set_duty_raw(uint16_t a_plus, uint16_t a_minus, uint16_t b_plus, uint16_t b_minus);
void stepper_motor_output_enable(void);
void stepper_motor_output_disable(void);
void stepper_motor_openloop_start(void);
void stepper_motor_openloop_start_dir(stepper_motor_direction_t direction);
void stepper_motor_forward(void);
void stepper_motor_reverse(void);
void stepper_motor_run_steps(stepper_motor_direction_t direction, uint32_t steps);
void stepper_motor_stop(void);
void stepper_motor_openloop_stop(void);
bool stepper_motor_output_is_enabled(void);
stepper_motor_direction_t stepper_motor_get_direction(void);
#ifdef __cplusplus
}
#endif
#endif /* __STEP_MOTOR_INIT_H__ */
+1290
View File
File diff suppressed because it is too large Load Diff
+55
View File
@@ -0,0 +1,55 @@
#ifndef MQTT_H
#define MQTT_H
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include "nvs_flash.h"
#include "esp_netif.h"
#include "protocol_examples_common.h"
#include "mqtt_client.h"
#include "cjson.h"
#include "esp_timer.h"
#define DEVICE_FIRMWARE_VERSION "v1.0.3"//原为V1.0.0
void handle_received_data(const char* topic, int topic_len, const char* data, int data_len);
void handle_event(const char* data, int data_len);
void report_firmware_version(char* event_time);
void handle_ota_command(const char* data);
void ota_timeout_handler(void *arg);
void setup_ota_timeout_timer(void);
void clear_ota_timeout_timer(void);
static esp_mqtt_client_handle_t esp_mqtt_client_get_handle(void);
static void handle_version_query_manual(const char* data, int data_len);
static void find_field_manually(cJSON *root, const char* field_name);
static void print_json_structure_recursive(cJSON *item, int depth);
void mqtt_main(void);
/** 停止并销毁 MQTT 客户端,释放 TLS/TCP 任务与缓冲;下次 mqtt_main() 会重新创建 */
void mqtt_client_shutdown(void);
esp_err_t subscribe_ota();
static cJSON* create_data(char* service_id, char* event_type);
esp_err_t publish_json_download(char* filename);
bool Get_MQTT_FLAG(void);
extern bool mqtt_connected;
static char current_request_id[64] = {0};
static esp_timer_handle_t ota_timeout_timer;
static bool ota_command_received = false;
static esp_mqtt_client_handle_t client = NULL;
static esp_mqtt_client_handle_t client_http = NULL;
static char time_event[64] = {0};
extern char object_device_id[70];
static char version[64] = {0};
extern char custom_info[32];
void trigger_ota_upgrade(char* url);
void check_ota_progress(void);
void transmit_ota_success(void);
#define ota_upgrade 1
#define ota_flash_upgrade 2
static int ota_type = 0;
void get_object_device_id(char *product_id);
#endif // MQTT_H
+28
View File
@@ -0,0 +1,28 @@
#include <stdio.h>
#include <stdint.h>
class AudioSourceFile {
public:
FILE* fp;
AudioSourceFile(const char* path) {
fp = fopen(path, "rb");
}
int read(uint8_t* buf, int len) {
if (!fp) return 0;
return fread(buf, 1, len, fp);
}
void seek(int offset) {
if (fp) fseek(fp, offset, SEEK_SET);
}
bool isOpen() {
return fp != nullptr;
}
~AudioSourceFile() {
if (fp) fclose(fp);
}
};
+29
View File
@@ -0,0 +1,29 @@
#include "micro_decoder/decoder_source.h"
#include "micro_decoder/types.h"
#include <stdio.h>
static FILE* g_fp = nullptr;
/*
extern "C" {
void md_init(const char* path)
{
g_fp = fopen(path, "rb");
}
int md_read(uint8_t* buf, int len)
{
if (!g_fp) return 0;
return fread(buf, 1, len, g_fp);
}
void md_deinit()
{
if (g_fp) {
fclose(g_fp);
g_fp = nullptr;
}
}
}
*/
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
//void md_init(const char* path);
//int md_read(uint8_t* buf, int len);
//void md_deinit();
#ifdef __cplusplus
}
#endif
+132
View File
@@ -0,0 +1,132 @@
#include "ns4168.h"
#include <stdint.h>
#include <stdlib.h>
#include "driver/gpio.h"
#include "driver/i2s.h"
#include "esp_log.h"
#include "esp_random.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
static const char *TAG = "ns4168";
static void ns4168_white_noise_task(void *arg) //白噪音测试
{
uint8_t buffer[256];
size_t bytes_written = 0;
while (1) {
for (int i = 0; i < (int)sizeof(buffer); i += 2) {
uint16_t sample = (uint16_t)(esp_random() & 0xFFFF);
buffer[i] = (uint8_t)(sample & 0xFF);
buffer[i + 1] = (uint8_t)((sample >> 8) & 0xFF);
}
esp_err_t ret = i2s_write(NS4168_I2S_PORT, buffer, sizeof(buffer), &bytes_written, portMAX_DELAY);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "i2s_write failed: %s", esp_err_to_name(ret));
}
vTaskDelay(pdMS_TO_TICKS(10));
}
}
/**
* @brief 初始化 NS4168 的 CTRL 引脚并将其拉高(选择右声道,且退出关断模式)
*/
void ns4168_ctrl_init(void)
{
// 1. 配置 GPIO 参数结构体
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << NS4168_CTRL_GPIO), // 选中 GPIO33
.mode = GPIO_MODE_OUTPUT, // 设置为推挽输出
.intr_type = GPIO_INTR_DISABLE, // 禁止中断
.pull_up_en = GPIO_PULLUP_DISABLE, // 关闭内部上拉(不需要)
.pull_down_en = GPIO_PULLDOWN_DISABLE, // 关闭内部下拉
};
// 2. 应用配置
esp_err_t ret = gpio_config(&io_conf);
if (ret != ESP_OK) {
ESP_LOGE("NS4168", "GPIO33 配置失败!");
return;
}
// 3. 输出高电平(3.3V)
gpio_set_level(NS4168_CTRL_GPIO, 1);
ESP_LOGI("NS4168", "NS4168 CTRL 引脚已拉高 (GPIO33),选择右声道");
}
esp_err_t ns4168_i2s_init(void)
{
ns4168_ctrl_init();
esp_err_t ret = ESP_OK;
i2s_config_t i2s_cfg = {
.mode = I2S_MODE_MASTER | I2S_MODE_TX,
.sample_rate = NS4168_I2S_SAMPLE_RATE,
.bits_per_sample = 16,
.channel_format = NS4168_I2S_CHANNELS,
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 16,
.dma_buf_len = 1024,
.tx_desc_auto_clear = true,
.use_apll = false,
.fixed_mclk = 0,
};
i2s_pin_config_t pin_cfg = {
.mck_io_num = -1,
.bck_io_num = NS4168_I2S_BCK_PIN,
.ws_io_num = NS4168_I2S_WS_PIN,
.data_out_num = NS4168_I2S_DOUT_PIN,
.data_in_num = I2S_PIN_NO_CHANGE,
};
ret = i2s_driver_install(NS4168_I2S_PORT, &i2s_cfg, 0, NULL);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "i2s_driver_install failed: %s", esp_err_to_name(ret));
return ret;
}
ret = i2s_set_pin(NS4168_I2S_PORT, &pin_cfg);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "i2s_set_pin failed: %s", esp_err_to_name(ret));
return ret;
}
ret = i2s_zero_dma_buffer(NS4168_I2S_PORT);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "i2s_zero_dma_buffer failed: %s", esp_err_to_name(ret));
return ret;
}
ESP_LOGI(TAG, "NS4168 I2S init OK");
return ESP_OK;
}
esp_err_t ns4168_start_white_noise_test(void)
{
esp_err_t ret = ns4168_i2s_init();
if (ret != ESP_OK) {
return ret;
}
BaseType_t task_ret = xTaskCreatePinnedToCore(ns4168_white_noise_task, "ns4168_noise", 4096, NULL, 5, NULL, 1);
if (task_ret != pdPASS) {
ESP_LOGE(TAG, "create white noise task failed");
return ESP_FAIL;
}
return ESP_OK;
}
+29
View File
@@ -0,0 +1,29 @@
#ifndef __NS4168_H__
#define __NS4168_H__
#include "driver/gpio.h"
#include "driver/i2s.h"
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
/* 先用宏定义占位引脚,后续再改成实际板子上的 GPIO */
#define NS4168_I2S_PORT I2S_NUM_1
#define NS4168_CTRL_GPIO GPIO_NUM_33
#define NS4168_I2S_BCK_PIN GPIO_NUM_31
#define NS4168_I2S_WS_PIN GPIO_NUM_32
#define NS4168_I2S_DOUT_PIN GPIO_NUM_30
#define NS4168_I2S_SAMPLE_RATE 24000
#define NS4168_I2S_BITS I2S_BITS_PER_SAMPLE_16BIT
#define NS4168_I2S_CHANNELS I2S_CHANNEL_FMT_ONLY_RIGHT
esp_err_t ns4168_i2s_init(void);
esp_err_t ns4168_start_white_noise_test(void);
#ifdef __cplusplus
}
#endif
#endif /* __NS4168_H__ */
+58
View File
@@ -0,0 +1,58 @@
#include "ogg_demux.h"
static uint32_t le32(const uint8_t* p)
{
return (uint32_t)p[0]
| ((uint32_t)p[1] << 8)
| ((uint32_t)p[2] << 16)
| ((uint32_t)p[3] << 24);
}
int ogg_parse_page(const uint8_t* buf, int buf_len, ogg_page_t* page)
{
if (buf_len < 27)
return -1;
/* 校验 "OggS" */
if (buf[0] != 'O' || buf[1] != 'g' || buf[2] != 'g' || buf[3] != 'S')
return -2;
/* 拷贝页头 */
for (int i = 0; i < 27; ++i)
page->header[i] = buf[i];
page->seg_count = buf[26];
if (buf_len < 27 + page->seg_count)
return -3;
/* 读取 segment table */
for (int i = 0; i < page->seg_count; ++i)
page->seg_table[i] = buf[27 + i];
/* 计算 body 大小 */
uint32_t body_size = 0;
for (int i = 0; i < page->seg_count; ++i)
body_size += page->seg_table[i];
page->body_size = body_size;
if (buf_len < 27 + page->seg_count + body_size)
return -4;
return (int)body_size;
}
int ogg_extract_packet(const uint8_t* body, const ogg_page_t* page,
uint8_t* packet, int packet_buf_size)
{
/* 极简版:假设一个页里只有一个完整 packet(常见于 OpusHead / OpusTags) */
uint32_t total = page->body_size;
if (total > (uint32_t)packet_buf_size)
return -1;
for (uint32_t i = 0; i < total; ++i)
packet[i] = body[i];
return (int)total;
}
+21
View File
@@ -0,0 +1,21 @@
#ifndef OGG_DEMUX_H
#define OGG_DEMUX_H
#include <stdint.h>
typedef struct {
uint8_t header[27]; /* Ogg 页头 */
uint8_t seg_count;
uint8_t seg_table[255];
uint32_t body_size;
} ogg_page_t;
/* 从数据缓冲中解析一页 Ogg,返回页体大小,<0 表示错误 */
int ogg_parse_page(const uint8_t* buf, int buf_len, ogg_page_t* page);
/* 根据 seg_table 把页体里的数据拼成一个 packet
返回 packet 长度,<0 表示错误 */
int ogg_extract_packet(const uint8_t* body, const ogg_page_t* page,
uint8_t* packet, int packet_buf_size);
#endif
+379
View File
@@ -0,0 +1,379 @@
#include <stdint.h>
#include "esp_log.h"
#include "NS4168/micro_decoder_wrapper.h"
#include "NS4168/wav_demux.h"
#include "NS4168/ns4168.h"
#include <math.h>
#include <stdio.h>
#include <stdint.h>
static FILE* g_fp = NULL;
void md_init(const char* path)
{
g_fp = fopen(path, "rb"); // 标准 C
}
int md_read(uint8_t* buf, int len)
{
if (!g_fp) return 0;
return fread(buf, 1, len, g_fp); // 标准 C
}
void md_deinit()
{
if (g_fp) {
fclose(g_fp); // 标准 C
g_fp = NULL; // 标准 C(替换 nullptr)
}
}
void play_wav(void)
{
ns4168_i2s_init();
uint8_t header[70];
size_t written = 0;
int ret;
md_init("/extflash/ring2.wav");
md_read(header, sizeof(header));
ESP_LOG_BUFFER_HEX("WAV", header, sizeof(header));
ESP_LOGI("WAV", "WAV解析成功,开始播放");
uint8_t buf[1024];
int n;
while ((n = md_read(buf, sizeof(buf))) > 0) {
i2s_write(NS4168_I2S_PORT, buf, n, &written, portMAX_DELAY);
}
md_deinit();
ESP_LOGI("WAV", "WAV播放结束");
}
/**
* @param volume 音量系数,0.0=静音,1.0=原始音量,推荐 0.0~1.0
*/
void play_wav2(float volume)
{
uint8_t header[70];
size_t written = 0;
md_init("/extflash/ring2.wav");
md_read(header, sizeof(header));
ESP_LOG_BUFFER_HEX("WAV", header, sizeof(header));
ESP_LOGI("WAV", "WAV解析成功,开始播放");
uint8_t buf[1024];
int n;
while ((n = md_read(buf, sizeof(buf))) > 0) {
// 仅当音量不是 1.0 时才缩放(优化性能)
if (volume != 1.0f) {
int16_t *samples = (int16_t *)buf;
int num_samples = n / 2; // 假定为 16-bit PCM
for (int i = 0; i < num_samples; i++) {
int32_t val = (int32_t)(samples[i] * volume);
// 饱和限幅,防止溢出破音
if (val > 32767) val = 32767;
if (val < -32768) val = -32768;
samples[i] = (int16_t)val;
}
}
i2s_write(NS4168_I2S_PORT, buf, n, &written, portMAX_DELAY);
}
md_deinit();
ESP_LOGI("WAV", "WAV播放结束");
}
void play_wav3(float volume)
{
// 1. 打开文件
md_init("/extflash/ring2.wav");
// 2. 读取头部(沿用 70 字节,兼容你的原逻辑)
uint8_t header[70];
md_read(header, sizeof(header));
ESP_LOG_BUFFER_HEX("WAV", header, sizeof(header));
ESP_LOGI("WAV", "WAV解析成功,开始播放,音量=%.2f", volume);
// 3. 将浮点音量转换为 Q15 定点数 (范围 0 ~ 32768)
int32_t vol_fixed = (int32_t)(volume * 32768.0f + 0.5f);
if (vol_fixed > 32768) vol_fixed = 32768;
if (vol_fixed < 0) vol_fixed = 0;
size_t written = 0;
uint8_t buf[1024];
int n;
// 4. 循环读取音频数据并处理
while ((n = md_read(buf, sizeof(buf))) > 0) {
// 确保处理偶数个字节,防止破坏样本对齐
int bytes_to_process = (n / 2) * 2;
int num_samples = bytes_to_process / 2;
int16_t *samples = (int16_t *)buf;
// 音量接近静音时直接填零(避免处理微小信号产生的噪声)
if (vol_fixed < 32) {
memset(buf, 0, bytes_to_process);
}
else if (vol_fixed != 32768) { // 不等于 1.0 时才缩放
for (int i = 0; i < num_samples; i++) {
// 整数定点缩放 + 四舍五入
int32_t val = ((int32_t)samples[i] * vol_fixed + 16384) >> 15;
// 饱和限幅(实际不会溢出,但保留保护)
if (val > 32767) val = 32767;
if (val < -32768) val = -32768;
samples[i] = (int16_t)val;
}
}
// 发送对齐后的数据(丢弃奇数尾字节)
i2s_write(NS4168_I2S_PORT, buf, bytes_to_process, &written, portMAX_DELAY);
}
// 5. 关闭文件
md_deinit();
ESP_LOGI("WAV", "WAV播放结束");
}
void play_wav4(float volume)
{
uint8_t header[70];
size_t written = 0;
md_init("/extflash/ring2.wav");
md_read(header, sizeof(header));
ESP_LOG_BUFFER_HEX("WAV", header, sizeof(header));
ESP_LOGI("WAV", "WAV解析成功,开始播放");
uint8_t buf[1024];
int n;
while ((n = md_read(buf, sizeof(buf))) > 0) {
if (volume != 1.0f) {
int16_t *samples = (int16_t *)buf;
int num_samples = n / 2; // 16-bit PCM
for (int i = 0; i < num_samples; i++) {
// 浮点缩放
float scaled = (float)samples[i] * volume;
// 四舍五入(关键)
int32_t val = lrintf(scaled);
// 饱和限幅
if (val > 32767) val = 32767;
if (val < -32768) val = -32768;
samples[i] = (int16_t)val;
}
}
i2s_write(NS4168_I2S_PORT, buf, n, &written, portMAX_DELAY);
}
md_deinit();
ESP_LOGI("WAV", "WAV播放结束");
}
void play_wav5(float volume)
{
uint8_t header[70];
size_t written = 0;
md_init("/extflash/ring2.wav");
md_read(header, sizeof(header));
ESP_LOG_BUFFER_HEX("WAV", header, sizeof(header));
ESP_LOGI("WAV", "WAV解析成功,开始播放");
uint8_t buf[1024];
int n;
// 将浮点音量转换为 8-bit 定点(0~256)
int32_t vol_fixed = (int32_t)(volume * 256.0f);
while ((n = md_read(buf, sizeof(buf))) > 0) {
if (volume != 1.0f) {
int16_t *samples = (int16_t *)buf;
int num_samples = n / 2; // 16-bit PCM
for (int i = 0; i < num_samples; i++) {
// 原始 16-bit PCM
int32_t v = samples[i];
// 定点缩放:扩大到 24-bit 精度再缩回
v = (v * vol_fixed) >> 8;
// 饱和限幅
if (v > 32767) v = 32767;
if (v < -32768) v = -32768;
samples[i] = (int16_t)v;
}
}
i2s_write(NS4168_I2S_PORT, buf, n, &written, portMAX_DELAY);
}
md_deinit();
ESP_LOGI("WAV", "WAV播放结束");
}
void play_wav_p4(float volume)
{
uint8_t header[70];
size_t written = 0;
md_init("/extflash/ring2.wav");
md_read(header, sizeof(header));
ESP_LOG_BUFFER_HEX("WAV", header, sizeof(header));
ESP_LOGI("WAV", "WAV解析成功,开始播放");
uint8_t buf[1024];
int n;
// 如果音量是 1.0,直接原样播放,完全无损
bool bypass = (fabsf(volume - 1.0f) < 0.0001f);
// 简单 dither 强度(可以微调)
const float dither_amp = 1.0f / 512.0f; // 很小的抖动
while ((n = md_read(buf, sizeof(buf))) > 0) {
if (!bypass) {
int16_t *samples = (int16_t *)buf;
int num_samples = n / 2; // 16-bit PCM
for (int i = 0; i < num_samples; i++) {
// 提升到 float 32-bit 精度
float v = (float)samples[i];
// 音量缩放
v *= volume;
// 加一点点 dither,打散量化误差
// 简单线性同余随机数(不依赖 rand())
static uint32_t rnd = 0x12345678;
rnd = rnd * 1664525u + 1013904223u;
float noise = ((int32_t)(rnd >> 16) - 32768) * (dither_amp / 32768.0f);
v += noise;
// 高精度四舍五入
float v_rounded = floorf(v + 0.5f * (v >= 0 ? 1.0f : -1.0f));
// 饱和限幅
if (v_rounded > 32767.0f) v_rounded = 32767.0f;
if (v_rounded < -32768.0f) v_rounded = -32768.0f;
samples[i] = (int16_t)v_rounded;
}
}
i2s_write(NS4168_I2S_PORT, buf, n, &written, portMAX_DELAY);
}
md_deinit();
ESP_LOGI("WAV", "WAV播放结束");
}
#include <stdbool.h>
#include "driver/i2s.h"
// 简单的 32-bit 伪随机数,用于 dither
static inline uint32_t prng32(void)
{
static uint32_t s = 0x12345678u;
s = s * 1664525u + 1013904223u;
return s;
}
void play_wav_ring_with_volume(float volume)
{
const char *TAG = "WAV_RING";
uint8_t header[70];
size_t written = 0;
// 初始化并读取 WAV 头
md_init("/extflash/ring2.wav");
md_read(header, sizeof(header));
ESP_LOG_BUFFER_HEX(TAG, header, sizeof(header));
ESP_LOGI(TAG, "WAV解析成功,开始播放");
uint8_t buf[1024];
int n;
// 音量为 1.0 时完全绕过处理,保证绝对无损
bool bypass = (fabsf(volume - 1.0f) < 0.0001f);
// dither 强度(非常小,人耳几乎感知不到)
const float dither_amp = 1.0f / 4096.0f; // 可微调
while ((n = md_read(buf, sizeof(buf))) > 0) {
if (!bypass) {
int16_t *samples = (int16_t *)buf;
int num_samples = n / 2; // 16-bit PCM
for (int i = 0; i < num_samples; i++) {
// 提升到 float 32-bit 精度
float v = (float)samples[i];
// 音量缩放
v *= volume;
// 三角 dither(TDPF):(rand1 - rand2) * scale
uint32_t r1 = prng32();
uint32_t r2 = prng32();
float noise = ((int32_t)(r1 & 0xFFFF) - (int32_t)(r2 & 0xFFFF))
* (dither_amp / 65536.0f);
v += noise;
// 高精度四舍五入
float v_rounded = floorf(v + 0.5f * (v >= 0.0f ? 1.0f : -1.0f));
// 轻微 soft-clip,避免极端削波毛刺
if (v_rounded > 32767.0f) {
float over = v_rounded - 32767.0f;
v_rounded = 32767.0f + over * 0.01f; // 把硬削波变成软削波
} else if (v_rounded < -32768.0f) {
float under = v_rounded + 32768.0f;
v_rounded = -32768.0f + under * 0.01f;
}
// 最终饱和限幅
if (v_rounded > 32767.0f) v_rounded = 32767.0f;
if (v_rounded < -32768.0f) v_rounded = -32768.0f;
samples[i] = (int16_t)v_rounded;
}
}
// 写入 I2S
i2s_write(NS4168_I2S_PORT, buf, n, &written, portMAX_DELAY);
}
md_deinit();
ESP_LOGI(TAG, "WAV播放结束");
}
+9
View File
@@ -0,0 +1,9 @@
#ifndef WAV_DEMUX_H
#define WAV_DEMUX_H
#include <stdint.h>
void play_wav(void);
#endif
+43
View File
@@ -0,0 +1,43 @@
#include "nvs_flash.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "nvs.h"
#include "esp_log.h"
#include <string.h>
static const char *TAG = "emoji_information";
//NVS 命名空间
#define NVS_NAMESPACE "emoji_config"
//版本号的键名
#define VERSION_KEY "emoji_version"
#define VERSION_BUF_SIZE 32
esp_err_t nvs_emoji_init(void) // nvs 初始化
{
esp_err_t err = nvs_flash_init();
if(err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
ESP_LOGI(TAG,"NVS paratition need erase , reinitializing ...");
err = nvs_flash_erase();
if(err != ESP_OK)
{
ESP_LOGI(TAG,"NVS flash erase failed : %s ", esp_err_to_name(err));
return err;
}
err = nvs_flash_init();
}
if(err != ESP_OK)
{
ESP_LOGI(TAG,"NVS initialization failed : %s",esp_err_to_name(err));
return err;
}
ESP_LOGI(TAG,"NVS initialized successfully");
return ESP_OK;
}
+5
View File
@@ -0,0 +1,5 @@
#ifndef __NVS_FLASH_H__
#define __NVS_FLASH_H__
#endif
+842
View File
@@ -0,0 +1,842 @@
// version 1.0.2
#include <string.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_system.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_ota_ops.h"
#include "esp_app_format.h"
#include "esp_http_client.h"
#include "esp_partition.h"
#include "nvs_flash.h"
#include "protocol_examples_common.h"
#include "errno.h"
#include "esp_wifi.h"
#include "esp_attr.h"
#include "hal/cache_ll.h"
#include "hal/cache_hal.h"
#include "esp_private/cache_utils.h"
#include "esp_task_wdt.h" // 看门狗函数
#include "esp_heap_caps.h"
#include <unistd.h> // 添加这行
#include "config.h"
#include "BLE/ble.h"
#include "ota_upgrade.h"
#include "event.h"
extern void ota_main_notify_recover_pending_after_fail(void);
StaticTask_t ota_task_tcb;
StackType_t *ota_task_stack;
extern esp_mqtt_client_handle_t client_http;
extern const char *base_path;
extern char time_event[64];
extern char version[64];
extern char object_device_id[70];
extern char custom_info[32];
extern wl_handle_t s_wl_handle;
esp_err_t extract_running_app_to_storage(const char *filename, esp_partition_t *running_partition);
esp_err_t save_extraction_info(const char *filename, uint32_t original_size, uint32_t extracted_size);
static const char *TAG_OTA = "OTA";
static ota_upgrade_context_t ota_ctx = {0};
int g_progress = 0;
/** OTA 任务结束时释放,供 ota_upgrade_cancel() 等待,避免无限阻塞 */
static SemaphoreHandle_t s_ota_done_sem = NULL;
// 内部函数声明
static void ota_task(void *pvParameter);
static void notify_status(const char *status, int progress, const char *message);
static void http_cleanup(esp_http_client_handle_t client);
esp_err_t ota_upgrade_init(void) // 初始化OTA升级
{
if (ota_ctx.mutex == NULL)
{
ota_ctx.mutex = xSemaphoreCreateMutex(); // 创建互斥锁
if (ota_ctx.mutex == NULL)
{
ESP_LOGE(TAG_OTA, "Failed to create mutex");
return ESP_FAIL;
}
}
if (s_ota_done_sem == NULL)
{
s_ota_done_sem = xSemaphoreCreateBinary();
if (s_ota_done_sem == NULL)
{
ESP_LOGE(TAG_OTA, "Failed to create OTA done semaphore");
return ESP_FAIL;
}
}
ota_ctx.progress = 0;
ota_ctx.is_running = false;
ota_ctx.cancel_requested = false;
ota_ctx.status_callback = NULL;
return ESP_OK;
}
void ota_upgrade_set_callback(ota_status_callback_t callback) //
{
if (xSemaphoreTake(ota_ctx.mutex, portMAX_DELAY))
{
ota_ctx.status_callback = callback;
xSemaphoreGive(ota_ctx.mutex);
}
}
esp_err_t ota_upgrade_start(const char *firmware_url) // OTA 升级开始
{
if (firmware_url == NULL)
{
ESP_LOGE(TAG_OTA, "Firmware URL is NULL");
return ESP_ERR_INVALID_ARG;
}
if (xSemaphoreTake(ota_ctx.mutex, portMAX_DELAY))
{
if (ota_ctx.is_running)
{
xSemaphoreGive(ota_ctx.mutex);
ESP_LOGW(TAG_OTA, "OTA upgrade is already running");
return ESP_ERR_INVALID_STATE;
}
// 复制URL到动态内存
char *url_copy = strdup(firmware_url);
if (url_copy == NULL)
{
xSemaphoreGive(ota_ctx.mutex);
ESP_LOGE(TAG_OTA, "Failed to allocate memory for URL");
return ESP_ERR_NO_MEM;
}
ota_ctx.is_running = true;
ota_ctx.progress = 0;
ota_ctx.cancel_requested = false;
xSemaphoreGive(ota_ctx.mutex); // 发送互斥信号量
/* 消费可能存在的上一次任务结束信号,避免 cancel 误唤醒 */
if (s_ota_done_sem != NULL)
{
xSemaphoreTake(s_ota_done_sem, 0);
}
ESP_LOGI(TAG_OTA, "Allocating OTA task stack: %d words (%d bytes) from SPIRAM",
OTA_TASK_STACK_SIZE, (int)(OTA_TASK_STACK_SIZE * sizeof(StackType_t)));
ESP_LOGI(TAG_OTA, "Note: esp_ota_end() will run on a separate internal-RAM stack");
ota_task_stack = heap_caps_malloc(OTA_TASK_STACK_SIZE * sizeof(StackType_t), MALLOC_CAP_SPIRAM);
if (ota_task_stack == NULL)
{
ESP_LOGE(TAG_OTA, "Failed to allocate stack for OTA task");
free(url_copy);
if (xSemaphoreTake(ota_ctx.mutex, portMAX_DELAY))
{
ota_ctx.is_running = false;
xSemaphoreGive(ota_ctx.mutex);
}
return ESP_FAIL;
}
ota_ctx.task_handle = xTaskCreateStaticPinnedToCore(
ota_task,
"ota_task",
OTA_TASK_STACK_SIZE,
url_copy,
configMAX_PRIORITIES - 1, // 高优先级
ota_task_stack,
&ota_task_tcb,
1 // 固定核心
);
if (ota_ctx.task_handle == NULL)
{
ESP_LOGE(TAG_OTA, "Failed to create OTA task");
free(url_copy);
if (xSemaphoreTake(ota_ctx.mutex, portMAX_DELAY))
{
ota_ctx.is_running = false;
xSemaphoreGive(ota_ctx.mutex);
}
return ESP_FAIL;
}
return ESP_OK;
}
return ESP_FAIL;
}
esp_err_t ota_upgrade_start_blocking(const char *firmware_url) // OTA启动阻塞
{
esp_err_t ret = ESP_FAIL;
if (firmware_url == NULL)
{
return ESP_ERR_INVALID_ARG;
}
// 直接调用OTA任务函数(阻塞)
ota_task((void *)firmware_url);
// 如果OTA成功,这里不会返回(设备会重启)
// 如果失败,会返回到这里
if (xSemaphoreTake(ota_ctx.mutex, portMAX_DELAY))
{
ret = ota_ctx.is_running ? ESP_FAIL : ESP_OK;
xSemaphoreGive(ota_ctx.mutex);
}
return ret;
}
int ota_upgrade_get_progress(void) // 获取OTA升级进度
{
int progress = -1;
if (xSemaphoreTake(ota_ctx.mutex, portMAX_DELAY))
{
progress = ota_ctx.progress;
xSemaphoreGive(ota_ctx.mutex);
}
return progress;
}
bool ota_upgrade_is_running(void)
{
bool is_running = false;
/* 未调用 ota_upgrade_init() 时 mutex 可能为 NULL,避免 xQueueSemaphoreTake 断言崩溃 */
if (ota_ctx.mutex == NULL)
return false;
if (xSemaphoreTake(ota_ctx.mutex, portMAX_DELAY)) // 如果拿到了信号量
{
is_running = ota_ctx.is_running;
xSemaphoreGive(ota_ctx.mutex);
}
return is_running;
}
esp_err_t ota_upgrade_cancel(void) // OTA取消升级
{
/* 若从未调用 ota_upgrade_init(),mutex 为 NULL,直接返回,避免 xQueueSemaphoreTake 断言崩溃 */
if (ota_ctx.mutex == NULL)
{
ESP_LOGW(TAG_OTA, "OTA not initialized, nothing to cancel");
return ESP_ERR_INVALID_STATE;
}
if (xSemaphoreTake(ota_ctx.mutex, portMAX_DELAY))
{
if (!ota_ctx.is_running)
{
xSemaphoreGive(ota_ctx.mutex);
return ESP_ERR_INVALID_STATE;
}
ota_ctx.cancel_requested = true;
xSemaphoreGive(ota_ctx.mutex);
/* 等待 OTA 任务结束(任务退出时会 Give 此信号量),避免无限阻塞 */
if (s_ota_done_sem != NULL && ota_ctx.task_handle != NULL)
{
xSemaphoreTake(s_ota_done_sem, portMAX_DELAY);
}
return ESP_OK;
}
return ESP_FAIL;
}
/* ─── Generic helper: run a function on an internal-RAM stack ───
*
* Functions like esp_ota_end() and esp_ota_set_boot_partition() call
* esp_image_verify() → spi_flash_mmap() which disables the cache,
* making SPIRAM inaccessible. If the calling task's stack lives in
* SPIRAM the assertion esp_task_stack_is_sane_cache_disabled() fires.
* This helper spawns a short-lived task whose stack is explicitly
* allocated from internal SRAM.
*/
#define INTERNAL_STACK_SIZE (1024 * 4) /* 4096 words = 16 KB */
typedef esp_err_t (*internal_fn_t)(void *arg);
typedef struct {
internal_fn_t fn;
void *arg;
esp_err_t result;
SemaphoreHandle_t done;
} internal_op_t;
static void internal_stack_task(void *param)
{
internal_op_t *op = (internal_op_t *)param;
op->result = op->fn(op->arg);
xSemaphoreGive(op->done);
vTaskSuspend(NULL);
}
static esp_err_t run_on_internal_stack(internal_fn_t fn, void *arg, const char *label)
{
ESP_LOGI(TAG_OTA, "[%s] Preparing internal-RAM task (free: %u bytes)",
label, (unsigned)heap_caps_get_free_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT));
internal_op_t op = {
.fn = fn,
.arg = arg,
.result = ESP_FAIL,
.done = xSemaphoreCreateBinary(),
};
if (op.done == NULL) {
ESP_LOGE(TAG_OTA, "[%s] Failed to create semaphore", label);
return ESP_FAIL;
}
StackType_t *stk = heap_caps_malloc(INTERNAL_STACK_SIZE * sizeof(StackType_t),
MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
StaticTask_t *tcb = heap_caps_malloc(sizeof(StaticTask_t),
MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
if (stk == NULL || tcb == NULL) {
ESP_LOGE(TAG_OTA, "[%s] No internal RAM (need %u bytes)", label,
(unsigned)(INTERNAL_STACK_SIZE * sizeof(StackType_t) + sizeof(StaticTask_t)));
heap_caps_free(stk);
heap_caps_free(tcb);
vSemaphoreDelete(op.done);
return ESP_ERR_NO_MEM;
}
TaskHandle_t th = xTaskCreateStaticPinnedToCore(
internal_stack_task, "int_op",
INTERNAL_STACK_SIZE, &op,
configMAX_PRIORITIES - 1,
stk, tcb, 1);
if (th == NULL) {
ESP_LOGE(TAG_OTA, "[%s] Failed to create task", label);
heap_caps_free(stk);
heap_caps_free(tcb);
vSemaphoreDelete(op.done);
return ESP_FAIL;
}
ESP_LOGI(TAG_OTA, "[%s] Task created, waiting...", label);
xSemaphoreTake(op.done, portMAX_DELAY);
vTaskDelay(pdMS_TO_TICKS(10));
vTaskDelete(th);
heap_caps_free(stk);
heap_caps_free(tcb);
vSemaphoreDelete(op.done);
ESP_LOGI(TAG_OTA, "[%s] Done, result: %s (0x%x)", label,
esp_err_to_name(op.result), (unsigned)op.result);
return op.result;
}
static esp_err_t wrap_ota_end(void *arg)
{
return esp_ota_end((esp_ota_handle_t)(uintptr_t)arg);
}
static esp_err_t wrap_set_boot_partition(void *arg)
{
return esp_ota_set_boot_partition((const esp_partition_t *)arg);
}
/* ─── Main OTA task ─── */
static void ota_task(void *pvParameter)
{
char *firmware_url = (char *)pvParameter;
ESP_LOGI(TAG_OTA, "===== OTA task started =====");
ESP_LOGI(TAG_OTA, " URL: %s", firmware_url);
ESP_LOGI(TAG_OTA, " Free internal RAM : %u bytes",
(unsigned)heap_caps_get_free_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT));
ESP_LOGI(TAG_OTA, " Free SPIRAM : %u bytes",
(unsigned)heap_caps_get_free_size(MALLOC_CAP_SPIRAM));
ESP_LOGI(TAG_OTA, " Task stack in SPIRAM, size: %d words (%d bytes)",
OTA_TASK_STACK_SIZE, (int)(OTA_TASK_STACK_SIZE * sizeof(StackType_t)));
esp_http_client_config_t config = {
.url = firmware_url,
.timeout_ms = 300000, // 5分钟超时
.buffer_size = 10240, // 增大缓冲区
.buffer_size_tx = 10240,
.keep_alive_enable = true, // 启用长连接
.keep_alive_idle = 30, // 30秒空闲时间
.keep_alive_interval = 10, // 10秒保活间隔
.keep_alive_count = 5, // 5次保活探测
.disable_auto_redirect = false, // 允许重定向
.max_redirection_count = 3, // 最多3次重定向
.auth_type = HTTP_AUTH_TYPE_NONE,
.user_data = NULL,
.is_async = false,
.use_global_ca_store = false,
.skip_cert_common_name_check = true,
.crt_bundle_attach = NULL,
.cert_pem = NULL, // (const char *)server_cert_pem_start,
};
esp_err_t err = ESP_FAIL;
esp_ota_handle_t update_handle = 0;
const esp_partition_t *update_partition = NULL;
esp_http_client_handle_t client = NULL;
int binary_file_length = 0;
int content_length = 0;
bool image_header_was_checked = false;
char ota_write_data[BUFFSIZE + 1] = {0};
static int s_last_sent_progress = -1;
s_last_sent_progress = -1; /* 每次 OTA 开始时重置,确保进度每增加 1% 只发一次、不重复发相同值 */
notify_status("starting", 0, "Starting OTA upgrade");
#ifdef CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK
config.skip_cert_common_name_check = true;
#endif
client = esp_http_client_init(&config);
esp_http_client_set_header(client, "Content-Type", "text/plain");
// 优化TCP连接参数
// esp_http_client_set_tcp_nodelay(client, true); // 禁用Nagle算法,减少延迟
// esp_http_client_set_redirection(client, true); // 启用重定向
if (client == NULL)
{
notify_status("error", 0, "Failed to initialize HTTP client");
goto cleanup;
}
// 打开HTTP连接
err = esp_http_client_open(client, 0);
if (err != ESP_OK)
{
notify_status("error", 0, "Failed to open HTTP connection");
goto cleanup;
}
content_length = esp_http_client_fetch_headers(client);
if (content_length <= 0)
{
err = ESP_FAIL;
notify_status("error", 0, "Invalid content length");
goto cleanup;
}
ESP_LOGI(TAG_OTA, "HTTP connected, content_length=%d bytes", content_length);
notify_status("downloading", 0, "Connected to server, starting download");
update_partition = esp_ota_get_next_update_partition(NULL);
if (update_partition == NULL)
{
err = ESP_FAIL;
notify_status("error", 0, "No OTA update partition found");
goto cleanup;
}
ESP_LOGI(TAG_OTA, "OTA partition: label=%s, addr=0x%lx, size=%lu bytes",
update_partition->label, update_partition->address, update_partition->size);
// 处理接收数据
while (1)
{
// 检查取消请求
if (xSemaphoreTake(ota_ctx.mutex, 0))
{
if (ota_ctx.cancel_requested)
{
xSemaphoreGive(ota_ctx.mutex);
notify_status("cancelled", ota_ctx.progress, "OTA upgrade cancelled");
err = ESP_FAIL;
goto cleanup;
}
xSemaphoreGive(ota_ctx.mutex);
}
int data_read = esp_http_client_read(client, ota_write_data, BUFFSIZE);
if (data_read < 0)
{
err = ESP_FAIL;
ESP_LOGE(TAG_OTA, "[error] %d%%: Network read error (connection lost)", ota_ctx.progress);
notify_status("error", ota_ctx.progress, "Network read error");
goto cleanup;
}
else if (data_read > 0)
{
if (!image_header_was_checked)
{
esp_app_desc_t new_app_info;
if (data_read > sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t))
{
memcpy(&new_app_info, &ota_write_data[sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t)], sizeof(esp_app_desc_t));
notify_status("downloading", 5, "Firmware header verified");
err = esp_ota_begin(update_partition, OTA_WITH_SEQUENTIAL_WRITES, &update_handle);
if (err != ESP_OK)
{
notify_status("error", 0, "Failed to begin OTA update");
goto cleanup;
}
image_header_was_checked = true;
}
}
// 写入OTA数据
err = esp_ota_write(update_handle, (const void *)ota_write_data, data_read);
if (err != ESP_OK)
{
notify_status("error", ota_ctx.progress, "Failed to write OTA data");
goto cleanup;
}
binary_file_length += data_read;
// 更新进度
if (content_length > 0)
{
// int progress = ota_upgrade_get_progress();
int progress = (binary_file_length * 95) / content_length; // 留5%给最后的验证和设置
if (xSemaphoreTake(ota_ctx.mutex, portMAX_DELAY))
{
ota_ctx.progress = progress;
xSemaphoreGive(ota_ctx.mutex);
}
g_progress = progress;
ESP_LOGI(TAG_OTA, "OTA progress %d%%", progress);
/* 进度每增加 1% 仅通过 BLE 协议帧 1.11_B 发送,不使用 JSON */
if (progress > s_last_sent_progress)
{
s_last_sent_progress = progress;
ble_send_ota_progress((uint8_t)progress);
}
notify_status("downloading", progress, "Downloading firmware");
}
}
else if (data_read == 0)
{
if (esp_http_client_is_complete_data_received(client))
{
break;
}
}
}
// 验证数据完整性
if (!esp_http_client_is_complete_data_received(client))
{
err = ESP_FAIL;
notify_status("error", ota_ctx.progress, "Incomplete data received");
goto cleanup;
}
notify_status("verifying", 95, "Download complete, verifying firmware");
ESP_LOGI(TAG_OTA, "Download finished: %d bytes received (content_length=%d)",
binary_file_length, content_length);
ESP_LOGI(TAG_OTA, "Free internal RAM before verify: %u bytes",
(unsigned)heap_caps_get_free_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT));
ESP_LOGI(TAG_OTA, "Free SPIRAM before verify : %u bytes",
(unsigned)heap_caps_get_free_size(MALLOC_CAP_SPIRAM));
err = run_on_internal_stack(wrap_ota_end, (void *)(uintptr_t)update_handle, "ota_end");
if (err != ESP_OK)
{
ESP_LOGE(TAG_OTA, "esp_ota_end failed: %s (0x%x)", esp_err_to_name(err), (unsigned)err);
notify_status("error", 100, "Firmware verification failed");
goto cleanup;
}
update_handle = 0;
notify_status("finalizing", 98, "Setting boot partition");
// 设置启动分区(storage 区已废弃,不再提取到 storage,统一走 set_boot_partition)
if (0 && strstr(custom_info, "flash") != NULL) /* 原:提取到 storage 分区,已禁用 */
{
err = extract_running_app_to_storage("/extflash/flash.bin", update_partition);
}
else
{
err = run_on_internal_stack(wrap_set_boot_partition,
(void *)update_partition, "set_boot");
}
if (err != ESP_OK)
{
notify_status("error", 100, "Failed to set boot partition");
goto cleanup;
}
notify_status("complete", 100, "OTA upgrade complete, restarting...");
ble_send_ota_progress(100);
vTaskDelay(pdMS_TO_TICKS(500));
ble_send_ota_result_request(1); /* 1.11_A: 通知蓝牙主机 OTA 升级成功 */
vTaskDelay(pdMS_TO_TICKS(500)); /* 留时间给 BLE 发出结果帧 */
transmit_ota_success();
//send_ota_result_frame(0x01);
set_system_event(OTA_UPDATE_COMPLETED_BIT);
/* main_task 可能不存在,直接在此重启,不依赖事件队列 */
ESP_LOGI(TAG_OTA, "OTA 升级完成,将重启设备...");
vTaskDelay(pdMS_TO_TICKS(3000));
esp_restart();
cleanup:
ESP_LOGI(TAG_OTA, "===== OTA cleanup: err=%s (0x%x) =====",
esp_err_to_name(err), (unsigned)err);
ESP_LOGI(TAG_OTA, " Free internal RAM: %u bytes",
(unsigned)heap_caps_get_free_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT));
/* 1. 安全终止 OTA 写入(仅失败时) */
if (err != ESP_OK && update_handle != 0)
{
ESP_LOGW(TAG_OTA, "Aborting OTA update handle");
esp_ota_abort(update_handle);
}
/* 2. 关闭 HTTP 连接,释放资源 */
if (client != NULL)
{
http_cleanup(client);
}
/* 3. 立即清除运行状态,使系统可再次发起 OTA / 正常业务 */
bool ota_cancelled_by_user = false;
if (xSemaphoreTake(ota_ctx.mutex, portMAX_DELAY))
{
ota_cancelled_by_user = ota_ctx.cancel_requested;
ota_ctx.is_running = false;
ota_ctx.task_handle = NULL;
xSemaphoreGive(ota_ctx.mutex);
}
free(firmware_url);
/* 4. 失败时:上报 OTA 结果并安全退出,不卡死流程 */
if (err != ESP_OK)
{
notify_status("error", ota_ctx.progress, "OTA upgrade failed");
if (ble_is_connected())
{
ble_send_ota_result_request(0); /* 1.11_A: 通知蓝牙主机 OTA 升级失败 */
vTaskDelay(pdMS_TO_TICKS(300)); /* 留时间给 BLE 发出结果帧 */
}
else
{
ESP_LOGW(TAG_OTA, "BLE 已断开,跳过 OTA 失败结果上报");
}
}
/* 5. 通知可能正在 ota_upgrade_cancel() 中等待的调用者,然后退出任务 */
if (s_ota_done_sem != NULL)
{
xSemaphoreGive(s_ota_done_sem);
}
/* 失败且非用户取消:延后恢复 UI,等 WiFi 重连结束(由 main 根据 WiFi 状态再 ota_restore) */
if (err != ESP_OK && !ota_cancelled_by_user)
{
ota_main_notify_recover_pending_after_fail();
}
vTaskDelete(NULL);
}
static void notify_status(const char *status, int progress, const char *message)
{
ESP_LOGI(TAG_OTA, "[%s] %d%%: %s", status, progress, message);
if (xSemaphoreTake(ota_ctx.mutex, portMAX_DELAY))
{
if (ota_ctx.status_callback != NULL)
{
ota_ctx.status_callback(status, progress, message);
}
xSemaphoreGive(ota_ctx.mutex);
}
}
static void http_cleanup(esp_http_client_handle_t client)
{
esp_http_client_close(client);
esp_http_client_cleanup(client);
}
// 在需要触发OTA升级的地方调用(例如按钮按下、定时器到期等)
void trigger_ota_upgrade(char *url)
{
const char *firmware_url = url;
ESP_LOGI(TAG_OTA, "[DEBUG] trigger_ota_upgrade url=%s", url ? url : "NULL");
if (!ota_upgrade_is_running())
{
esp_err_t ret = ota_upgrade_start(firmware_url);
ESP_LOGI(TAG_OTA, "[DEBUG] ota_upgrade_start 返回: %s", ret == ESP_OK ? "OK" : esp_err_to_name(ret));
if (ret == ESP_OK)
{
printf("OTA upgrade started successfully\n");
}
else
{
printf("Failed to start OTA upgrade: %d\n", ret);
}
}
else
{
ESP_LOGW(TAG_OTA, "[DEBUG] OTA 已在运行中,忽略");
printf("OTA upgrade is already running\n");
}
}
// 取消OTA升级
void cancel_ota_upgrade(void)
{
if (ota_upgrade_cancel() == ESP_OK)
{
printf("OTA upgrade cancelled\n");
}
else
{
printf("Failed to cancel OTA upgrade\n");
}
}
// 获取升级进度
void check_ota_progress(void)
{
int progress = ota_upgrade_get_progress();
if (progress >= 0)
{
printf("Current OTA progress: %d%%\n", progress);
}
else
{
printf("No OTA upgrade in progress\n");
}
}
// 提取当前运行分区的bin文件到storage分区
esp_err_t extract_running_app_to_storage(const char *filename, esp_partition_t *running_partition)
{
printf("=== 提取运行中的应用到storage分区 ===\n");
// 2. 查找storage分区
const esp_partition_t *storage_partition = esp_partition_find_first(
ESP_PARTITION_TYPE_DATA, 129, "storage");
if (storage_partition == NULL)
{
printf("❌ 找不到storage分区\n");
return ESP_FAIL;
}
printf("目标storage分区: 地址: 0x%ld, 大小: %ld\n",
storage_partition->address, storage_partition->size);
// 3. 检查storage分区空间是否足够
if (storage_partition->size < running_partition->size)
{
printf("❌ storage分区空间不足,需要%ld字节,只有%ld字节\n",
running_partition->size, storage_partition->size);
return ESP_ERR_INVALID_SIZE;
}
// 4. 分配读取缓冲区
size_t buffer_size = 4096; // 4KB缓冲区
uint8_t *buffer = malloc(buffer_size);
if (buffer == NULL)
{
printf("❌ 内存分配失败\n");
return ESP_ERR_NO_MEM;
}
// 5. 读取应用分区并写入storage分区
size_t total_read = 0;
esp_err_t err = ESP_OK;
printf("开始提取应用bin文件...\n");
while (total_read < running_partition->size)
{
size_t to_read = (running_partition->size - total_read) < buffer_size ? (running_partition->size - total_read) : buffer_size;
// 从应用分区读取
err = esp_partition_read(running_partition, total_read, buffer, to_read);
if (err != ESP_OK)
{
printf("❌ 读取应用分区失败: %s\n", esp_err_to_name(err));
break;
}
// 写入storage分区
err = esp_partition_write(storage_partition, total_read, buffer, to_read);
if (err != ESP_OK)
{
printf("❌ 写入storage分区失败: %s\n", esp_err_to_name(err));
break;
}
total_read += to_read;
// 显示进度
int progress = (total_read * 100) / running_partition->size;
if (progress % 10 == 0)
{ // 每10%显示一次进度
printf("提取进度: %d%%, 已提取: %d/%ld 字节\n",
progress, total_read, running_partition->size);
}
}
free(buffer);
if (err == ESP_OK)
{
printf("✅ 应用bin文件提取完成,总共提取 %d 字节\n", total_read);
// 保存提取信息到NVS
save_extraction_info(filename, running_partition->size, total_read);
}
else
{
printf("❌ 提取失败\n");
}
return err;
}
// 保存提取信息到NVS
esp_err_t save_extraction_info(const char *filename, uint32_t original_size, uint32_t extracted_size)
{
nvs_handle_t nvs_handle;
esp_err_t err = nvs_open("extraction", NVS_READWRITE, &nvs_handle);
if (err != ESP_OK)
return err;
extraction_info_t info = {
.original_size = original_size,
.extracted_size = extracted_size,
.timestamp = (uint32_t)time(NULL),
.compressed = false,
.verified = false};
strncpy(info.filename, filename, sizeof(info.filename) - 1);
// 计算CRC(简化示例)
info.crc = original_size ^ extracted_size;
err = nvs_set_blob(nvs_handle, filename, &info, sizeof(info));
nvs_commit(nvs_handle);
nvs_close(nvs_handle);
if (err == ESP_OK)
{
printf("✅ 提取信息已保存: %s\n", filename);
}
return err;
}
+109
View File
@@ -0,0 +1,109 @@
#ifndef OTA_UPGRADE_H
#define OTA_UPGRADE_H
#include "esp_err.h"
#include <stdbool.h>
#include <string.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_system.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_ota_ops.h"
#include "esp_app_format.h"
#include "esp_http_client.h"
#include "esp_partition.h"
#include "nvs_flash.h"
#include "protocol_examples_common.h"
#include "errno.h"
#include "esp_wifi.h"
#define BUFFSIZE (1024*4)
#define OTA_TASK_STACK_SIZE 1024*24//1024*24
#define OTA_TASK_PRIORITY 5
/**
* @brief OTA升级状态回调函数类型
* @param status 升级状态
* @param progress 升级进度(0-100)
* @param message 状态信息
*/
typedef void (*ota_status_callback_t)(const char* status, int progress, const char* message);
// 提取信息管理
typedef struct
{
char filename[64];
uint32_t original_size;
uint32_t extracted_size;
uint32_t timestamp;
uint32_t crc;
bool compressed;
bool verified;
} extraction_info_t;
// OTA升级状态结构体
typedef struct
{
TaskHandle_t task_handle;
ota_status_callback_t status_callback;
SemaphoreHandle_t mutex; //互斥信号量
bool is_running;
int progress;
bool cancel_requested;
} ota_upgrade_context_t;
esp_err_t http_json_download_flash(char* filename, const char* firmware_url);
void trigger_ota_upgrade(char* url);
void check_ota_progress(void);
/**
* @brief 初始化OTA升级模块
* @return esp_err_t ESP_OK表示成功
*/
esp_err_t ota_upgrade_init(void);
/**
* @brief 设置OTA升级状态回调函数
* @param callback 回调函数指针
*/
void ota_upgrade_set_callback(ota_status_callback_t callback);
/**
* @brief 开始OTA升级(非阻塞方式,在新任务中运行)
* @param firmware_url 固件下载URL
* @return esp_err_t ESP_OK表示任务创建成功
*/
esp_err_t ota_upgrade_start(const char* firmware_url);
/**
* @brief 开始OTA升级(阻塞方式,在当前任务中运行)
* @param firmware_url 固件下载URL
* @return esp_err_t 升级结果
*/
esp_err_t ota_upgrade_start_blocking(const char* firmware_url);
/**
* @brief 获取当前升级进度
* @return int 进度百分比(0-100),-1表示未在升级
*/
int ota_upgrade_get_progress(void);
/**
* @brief 检查是否正在升级
* @return true 正在升级
* @return false 未在升级
*/
bool ota_upgrade_is_running(void);
/**
* @brief 取消OTA升级
* @return esp_err_t ESP_OK表示取消成功
*/
esp_err_t ota_upgrade_cancel(void);
#endif // OTA_UPGRADE_H
+763
View File
@@ -0,0 +1,763 @@
#include "pet_init.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <sys/time.h>
#include <stdio.h>
#include "esp_netif_sntp.h"
#include "freertos/queue.h"
#include "esp_random.h"
#define TAG "PET"
static TaskHandle_t pet_task_handle = NULL;
static void pet_time_sync_task(void *arg);
static void pet_user_write_task(void *arg);
QueueHandle_t user_interact_Queue = NULL;
bool is_ntp_initalized = false;
static void feed_ready_handler(TimerHandle_t xTimer);
TimerHandle_t feed_timer=NULL;
static void travel_ready_handler(TimerHandle_t xTimer);
TimerHandle_t travel_timer=NULL;
uint8_t remaining_hunger=0;
uint8_t remaining_cleanliness=0;
uint8_t remaining_mood=0;
static pet_status_t s_pet_status = {
.hunger = 0, //饱腹值
.cleanliness = 0, //清洁度
.mood = 0, //心情
.intimacy = 0, //亲密度
.stage = PET_STAGE_BABY, //成长状态
.vehicle = PET_VEHICLE_WALK, //出行方式
.last_timestamp = 0, //时间
.last_time = {0},
.travel_time =0, //当前旅行所需时间
.is_travelling = false, //是否在旅游
.travel_timestamp = 0, //触发旅游出行的时间(可计算)
.can_travel = false, //能否旅游状态(可计算)
.feed_time = 14400, //喂食所需时间(固定值)
.feed_timestamp = 0, //上一次喂食时间
.is_feed_cd = false, //是否在喂食冷却
};
/* 城市中文名数组 */
const char *city_names[CITY_COUNT] = {
"公园", "街道", "河流", "古镇", "山道", "水库", "聚落", "果园", "惠州", "东莞", "佛山", "中山", "江门", "清远", "肇庆", "河源", "梅州",
"重庆", "西安", "广州", "南京", "三亚", "北京", "长沙", "张家界", "湘西", "凤凰古镇", "上海", "香格里拉", "丽江", "大理", "西藏",
"厦门", "景德镇", "平潭", "青岛", "北海", "万宁", "淄博", "延吉", "柳州", "宜昌", "漠河", "桂林", "杭州", "西塘", "青海",
"西双版纳", "敦煌", "云南",
};
/* 宠物阶段介绍文案 */
static const char baby_introduction[] = "我还小小的呢,先在附近逛逛就很开心啦!";
static const char teen_introduction[] = "我长大啦!现在可以去更远的城市看一看咯!";
static const char adult_introduction[] = "谢谢你一直陪着我!现在,天涯海角我都能去到啦!";
//static const char travel_return[] = "我回来啦!快看我带回来的好东西~";
//static const char hunger[] = "我回来啦!快看我带回来的好东西~";
static const char *pet_stage_introduction[3] = {
baby_introduction,
teen_introduction,
adult_introduction,
};
/* 全局状态标志 */
bool g_pet_need_first_sync = false; //第一次初始化NVS
bool g_pet_ntp_synced = false; //开机第一次同步NTP
bool g_pet_animation_ready = false; //授时完成等待显示
bool go_travelling() {
// 1. 获取一个32位硬件随机数
uint32_t random_num = esp_random();
// 2. 将随机数映射到 0-99 的范围,判断是否小于20
// 由于随机数在32位空间均匀分布[reference:5],取余100即可得到0-99的均匀分布
if (random_num % 100 < 20) {
return true; // 旅行,award = 1
} else {
return false; // 不旅行,award = 0
}
}
void print_date_time(void) {
time_t now;
struct tm timeinfo;
time(&now); // 获取当前时间戳(秒)
localtime_r(&now, &timeinfo); // 转换为本地时间(需先设置时区)
// 提取年、月、日
int year = timeinfo.tm_year + 1900; // tm_year 从 1900 开始计数
int month = timeinfo.tm_mon + 1; // tm_mon 范围 0~11,需加 1
int day = timeinfo.tm_mday; // tm_mday 为当月第几天(1~31)
// 也可以同时获取时、分、秒
int hour = timeinfo.tm_hour;
int minute = timeinfo.tm_min;
int second = timeinfo.tm_sec;
// 打印日期和时间
ESP_LOGI("NTP", "日期: %04d-%02d-%02d", year, month, day);
ESP_LOGI("NTP", "时间: %02d:%02d:%02d", hour, minute, second);
// 或者格式化输出完整时间
char buffer[64];
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &timeinfo);
ESP_LOGI("NTP", "当前本地时间: %s", buffer);
}
bool obtain_time(void)
{
ESP_LOGI("NTP", "Initializing SNTP");
if(!is_ntp_initalized){
// 1. 设置SNTP服务器
esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG("pool.ntp.org");
// 国内用户可更换为: "cn.pool.ntp.org" 或 "ntp.aliyun.com"
// 2. 初始化并启动SNTP服务
esp_netif_sntp_init(&config);
is_ntp_initalized = true;
}
// 3. 等待时间同步完成 (超时10秒)
if (esp_netif_sntp_sync_wait(pdMS_TO_TICKS(10000)) == ESP_OK) {
ESP_LOGI("NTP", "Time synchronized successfully");
} else {
ESP_LOGE("NTP", "Failed to synchronize time");
return false; // 同步失败,返回 false
}
// 4. 设置时区 (中国时区 UTC+8)
setenv("TZ", "CST-8", 1);
tzset();
// 5. 获取并打印当前时间
time_t now;
struct tm timeinfo;
time(&now);
localtime_r(&now, &timeinfo);
char strftime_buf[64];
strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
ESP_LOGI("NTP", "Current time: %s", strftime_buf);
return true; // 同步成功,返回 true
}
static uint8_t clamp_u8(uint8_t value, uint8_t min, uint8_t max)
{
if (value < min) return min;
if (value > max) return max;
return value;
}
static uint16_t clamp_u16(uint16_t value, uint16_t min, uint16_t max)
{
if (value < min) return min;
if (value > max) return max;
return value;
}
static void pet_save_status_to_nvs(void)
{
nvs_handle_t handle;
esp_err_t err = nvs_open("pet_data", NVS_READWRITE, &handle);
if (err != ESP_OK) {
ESP_LOGW(TAG, "打开 NVS pet_data 失败: %s", esp_err_to_name(err));
return;
}
err = nvs_set_u8(handle, "hunger", s_pet_status.hunger);
if (err != ESP_OK) {
ESP_LOGW(TAG, "保存 hunger 失败: %s", esp_err_to_name(err));
}
err = nvs_set_u8(handle, "cleanliness", s_pet_status.cleanliness);
if (err != ESP_OK) {
ESP_LOGW(TAG, "保存 cleanliness 失败: %s", esp_err_to_name(err));
}
err = nvs_set_u8(handle, "mood", s_pet_status.mood);
if (err != ESP_OK) {
ESP_LOGW(TAG, "保存 mood 失败: %s", esp_err_to_name(err));
}
err = nvs_set_u16(handle, "intimacy", s_pet_status.intimacy);
if (err != ESP_OK) {
ESP_LOGW(TAG, "保存 intimacy 失败: %s", esp_err_to_name(err));
}
/* 保存最后时间戳(u64) */
err = nvs_set_u64(handle, "last_ts", s_pet_status.last_timestamp);
if (err != ESP_OK) {
ESP_LOGW(TAG, "保存 last_ts 失败: %s", esp_err_to_name(err));
}
err = nvs_set_u8(handle, "is_travelling", s_pet_status.is_travelling);
if (err != ESP_OK) {
ESP_LOGW(TAG, "保存 is_travelling 失败: %s", esp_err_to_name(err));
}
err = nvs_set_u8(handle, "is_feed_cd", s_pet_status.is_feed_cd);
if (err != ESP_OK) {
ESP_LOGW(TAG, "保存 is_feed_cd 失败: %s", esp_err_to_name(err));
}
err = nvs_set_u64(handle, "travel_ts", s_pet_status.travel_timestamp);
if (err != ESP_OK) {
ESP_LOGW(TAG, "保存 travel_timestamp 失败: %s", esp_err_to_name(err));
}
err = nvs_set_u64(handle, "feed_timestamp", s_pet_status.feed_timestamp);
if (err != ESP_OK) {
ESP_LOGW(TAG, "保存 feed_timestamp 失败: %s", esp_err_to_name(err));
}
err = nvs_commit(handle);
if (err != ESP_OK) {
ESP_LOGW(TAG, "NVS 提交失败: %s", esp_err_to_name(err));
}
nvs_close(handle);
}
static void pet_load_status_from_nvs(void)
{
nvs_handle_t handle;
esp_err_t err = nvs_open("pet_data", NVS_READWRITE, &handle);
uint8_t hunger = 0;
uint8_t cleanliness = 0;
uint8_t mood = 0;
uint16_t intimacy = 0;
uint8_t is_travelling = 0;
uint8_t is_feed_cd = 0;
esp_err_t err_hunger = nvs_get_u8(handle, "hunger", &hunger);
esp_err_t err_cleanliness = nvs_get_u8(handle, "cleanliness", &cleanliness);
esp_err_t err_mood = nvs_get_u8(handle, "mood", &mood);
esp_err_t err_intimacy = nvs_get_u16(handle, "intimacy", &intimacy);
esp_err_t err_last_ts = nvs_get_u64(handle, "last_ts", &s_pet_status.last_timestamp);
esp_err_t err_is_travelling = nvs_get_u8(handle,"is_travelling",&is_travelling);
if (err_is_travelling == ESP_OK) {
s_pet_status.is_travelling = is_travelling;
};
esp_err_t err_travel_timestamp = nvs_get_u64(handle,"travel_ts",&s_pet_status.travel_timestamp);
esp_err_t err_is_feed_cd = nvs_get_u8(handle,"is_feed_cd",&is_feed_cd);
if (err_is_feed_cd == ESP_OK) {
s_pet_status.is_feed_cd = is_feed_cd;
};
esp_err_t err_feed_timestamp = nvs_get_u64(handle,"feed_timestamp",&s_pet_status.feed_timestamp);
if (err_hunger == ESP_OK && err_cleanliness == ESP_OK && err_mood == ESP_OK && err_intimacy == ESP_OK && err_last_ts == ESP_OK) {
ESP_LOGW(TAG, "打开 NVSdata 成功,进入读取分支 ");
s_pet_status.hunger = clamp_u8(hunger, 0, 100);
s_pet_status.cleanliness = clamp_u8(cleanliness, 0, 100);
s_pet_status.mood = clamp_u8(mood, 0, 100);
s_pet_status.intimacy = clamp_u16(intimacy, 0, 300);
/* 将 u64 转换为 struct tm(本地时区) */
time_t t = (time_t)s_pet_status.last_timestamp;
localtime_r(&t, &s_pet_status.last_time);
g_pet_need_first_sync = false;
nvs_close(handle);
}else{
ESP_LOGW(TAG, "打开 NVSdata 失败,进入初始化分支 ");
s_pet_status.hunger = 0;
s_pet_status.cleanliness = 0;
s_pet_status.mood = 0;
s_pet_status.intimacy = 0;
nvs_set_u8(handle, "hunger", s_pet_status.hunger);
nvs_set_u8(handle, "cleanliness", s_pet_status.cleanliness);
nvs_set_u8(handle, "mood", s_pet_status.mood);
nvs_set_u16(handle, "intimacy", s_pet_status.intimacy);
nvs_set_u8(handle,"is_travelling",s_pet_status.is_travelling);
nvs_set_u64(handle,"travel_ts",s_pet_status.travel_timestamp);
nvs_set_u8(handle,"is_feed_cd",s_pet_status.is_feed_cd);
nvs_set_u64(handle,"feed_timestamp",s_pet_status.feed_timestamp);
err = nvs_commit(handle);
if (err != ESP_OK) {
ESP_LOGW(TAG, "初始化 NVS pet_data 提交失败: %s", esp_err_to_name(err));
}
g_pet_need_first_sync = true; // 标记需要第一次时间同步
nvs_close(handle);
}
return;
}
pet_stage_t pet_get_stage(uint16_t intimacy)
{
if (intimacy >= 200) {
return PET_STAGE_ADULT;
}
if (intimacy >= 100) {
return PET_STAGE_TEEN;
}
return PET_STAGE_BABY;
}
static void pet_update_stage(void)
{
s_pet_status.stage = pet_get_stage(s_pet_status.intimacy);
}
static void pet_update_can_travel(void)
{
if (s_pet_status.is_travelling) {
s_pet_status.can_travel = false;
return;
}
if (s_pet_status.mood >= 60 && s_pet_status.intimacy >= 30 && s_pet_status.hunger >= 30) {
s_pet_status.can_travel = true;
} else {
s_pet_status.can_travel = false;
}
}
static void pet_update_travel_time(void)
{
switch (s_pet_status.vehicle) {
case PET_VEHICLE_WALK:
s_pet_status.travel_time = 30 * 60; /* 30 minutes */
break;
case PET_VEHICLE_BIKE:
s_pet_status.travel_time = 60 * 60; /* 60 minutes */
break;
case PET_VEHICLE_MOTORCYCLE:
s_pet_status.travel_time = 3 * 60 * 60; /* 3 hours */
break;
case PET_VEHICLE_CAR:
s_pet_status.travel_time = 6 * 60 * 60; /* 6 hours */
break;
case PET_VEHICLE_PLANE:
s_pet_status.travel_time = 12 * 60 * 60; /* 12 hours */
break;
default:
s_pet_status.travel_time = 30 * 60;
break;
}
}
pet_vehicle_t pet_get_vehicle_stage(uint16_t intimacy)
{
if (intimacy >= 200) {
return PET_VEHICLE_PLANE;
}
if (intimacy >= 150) {
return PET_VEHICLE_CAR;
}
if (intimacy >= 100) {
return PET_VEHICLE_MOTORCYCLE;
}
if (intimacy >= 50) {
return PET_VEHICLE_BIKE;
}
return PET_VEHICLE_WALK;
}
static void pet_update_vehicle_stage(void)
{
s_pet_status.vehicle = pet_get_vehicle_stage(s_pet_status.intimacy);
}
static const char *pet_stage_name(pet_stage_t stage)
{
switch (stage) {
case PET_STAGE_BABY:
return "幼崽";
case PET_STAGE_TEEN:
return "少年";
case PET_STAGE_ADULT:
return "成年";
default:
return "未知";
}
}
static const char *pet_stage_vehicle_name(pet_vehicle_t stage)
{
switch (stage) {
case PET_VEHICLE_WALK:
return "步行";
case PET_VEHICLE_BIKE:
return "单车";
case PET_VEHICLE_MOTORCYCLE:
return "摩托";
case PET_VEHICLE_CAR:
return "汽车";
case PET_VEHICLE_PLANE:
return "飞机";
default:
return "未知";
}
}
static void pet_log_status(const char *action)
{
ESP_LOGI(TAG, "%s: 饱腹=%u, 清洁=%u, 心情=%u, 亲密度=%u, 形态=%s, 出行方式=%s",
action,
s_pet_status.hunger,
s_pet_status.cleanliness,
s_pet_status.mood,
s_pet_status.intimacy,
pet_stage_name(s_pet_status.stage),
pet_stage_vehicle_name(s_pet_status.vehicle));
}
void pet_init(void)
{
nvs_handle_t handle;
esp_err_t err = nvs_open("pet_data", NVS_READWRITE, &handle);
nvs_set_u8(handle, "hunger", 30);
nvs_set_u8(handle, "cleanliness", 30);
nvs_set_u8(handle, "mood", 30);
nvs_set_u8(handle,"is_travelling",0);
nvs_set_u64(handle,"travel_ts",s_pet_status.travel_timestamp);
nvs_set_u8(handle,"is_feed_cd",0);
nvs_set_u64(handle,"feed_timestamp",s_pet_status.feed_timestamp);
err = nvs_commit(handle);
nvs_close(handle);
pet_load_status_from_nvs();
pet_update_stage();
pet_update_vehicle_stage();
ESP_LOGI(TAG, "宠物加载完成,当前形态=%s,出行方式=%s",
pet_stage_name(s_pet_status.stage),
pet_stage_vehicle_name(s_pet_status.vehicle));
pet_log_status("加载后状态");
pet_task_stack = heap_caps_malloc(PET_TASK_STACK_SIZE, MALLOC_CAP_INTERNAL);
if (pet_task_stack == NULL)
{
ESP_LOGE("LVGL", "Failed to allocate PET task stack");
return;
}
write_nvs_task_stack = heap_caps_malloc(HOUR_WRITE_STACK_SIZE, MALLOC_CAP_INTERNAL);
if (write_nvs_task_stack == NULL)
{
ESP_LOGE("LVGL", "Failed to allocate PET_WRITE task stack");
return;
}
user_interact_Queue = xQueueCreate(5, sizeof(int));
if (user_interact_Queue == NULL) {
ESP_LOGE(TAG, "Failed to create user_interact_Queue");
return;
}
xTaskCreateStaticPinnedToCore(pet_time_sync_task, "PET", PET_TASK_STACK_SIZE, NULL, PET_TASK_PRIORITY, pet_task_stack, &pet_task_tcb, 1);
xTaskCreateStaticPinnedToCore(pet_user_write_task, "PET_WRITE", HOUR_WRITE_STACK_SIZE, NULL, HOUR_WRITE_TASK_PRIORITY, write_nvs_task_stack, &write_nvs_task_tcb, 1);
}
const pet_status_t *pet_get_status(void)
{
return &s_pet_status;
}
/*交互动作数值计算*/
void pet_feed(void)
{
s_pet_status.hunger = clamp_u8(s_pet_status.hunger + 30, 0, 100);
s_pet_status.mood = clamp_u8(s_pet_status.mood + 5, 0, 100);
s_pet_status.intimacy = clamp_u16(s_pet_status.intimacy + 2, 0, 300);
pet_update_stage();
pet_update_vehicle_stage();
time_t now;
time(&now);
s_pet_status.last_timestamp = (uint64_t)now;
localtime_r(&now, &s_pet_status.last_time);
pet_save_status_to_nvs();
pet_log_status("喂食");
}
void pet_bath(void)
{
s_pet_status.cleanliness = clamp_u8(s_pet_status.cleanliness + 100, 0, 100);
s_pet_status.mood = clamp_u8(s_pet_status.mood + 5, 0, 100);
s_pet_status.intimacy = clamp_u16(s_pet_status.intimacy + 2, 0, 300);
pet_update_stage();
pet_update_vehicle_stage();
time_t now;
time(&now);
s_pet_status.last_timestamp = (uint64_t)now;
localtime_r(&now, &s_pet_status.last_time);
pet_save_status_to_nvs();
pet_log_status("洗澡");
}
void pet_interact(void)
{
s_pet_status.mood = clamp_u8(s_pet_status.mood + 5, 0, 100);
s_pet_status.intimacy = clamp_u16(s_pet_status.intimacy + 1, 0, 300);
pet_update_stage();
pet_update_vehicle_stage();
time_t now;
time(&now);
s_pet_status.last_timestamp = (uint64_t)now;
localtime_r(&now, &s_pet_status.last_time);
pet_save_status_to_nvs();
pet_log_status("点击互动");
}
void pet_travel_return(void)
{
s_pet_status.mood = clamp_u8(s_pet_status.mood + 20, 0, 100);
s_pet_status.intimacy = clamp_u16(s_pet_status.intimacy + 5, 0, 300);
pet_update_stage();
pet_update_vehicle_stage();
time_t now;
time(&now);
s_pet_status.last_timestamp = (uint64_t)now;
localtime_r(&now, &s_pet_status.last_time);
pet_save_status_to_nvs();
pet_log_status("旅行归来");
}
void cal_deacy(uint64_t elapsed){
if(elapsed> 172800){
s_pet_status.hunger = 0;
s_pet_status.cleanliness = 0;
s_pet_status.mood = 0;
}
else{
uint8_t decay_hunger = elapsed / 450;
uint8_t decay_cleanliness = elapsed / 1200;
uint8_t decay_mood = elapsed / 1500;
uint8_t remaining_hunger = elapsed % 450;
uint8_t remaining_cleanliness = elapsed % 1200;
uint8_t remaining_mood = elapsed % 1500;
s_pet_status.hunger = (decay_hunger >= s_pet_status.hunger) ? 0 : s_pet_status.hunger - decay_hunger;
s_pet_status.cleanliness = (decay_cleanliness >= s_pet_status.cleanliness) ? 0 : s_pet_status.cleanliness - decay_cleanliness;
s_pet_status.mood = (decay_mood >= s_pet_status.mood) ? 0 : s_pet_status.mood - decay_mood;
pet_log_status("加载后状态");
}
}
static void compare_time_cd(uint64_t now){
if(s_pet_status.is_feed_cd){
uint64_t elapsed = now - s_pet_status.feed_timestamp;
if(elapsed > 14400){
s_pet_status.is_feed_cd = false;
s_pet_status.feed_timestamp = 0;
}
}
if(s_pet_status.is_travelling){
uint64_t elapsed = now - s_pet_status.travel_timestamp;
if(elapsed > s_pet_status.travel_time){
s_pet_status.is_travelling = false;
s_pet_status.travel_timestamp = 0;
}
}
return;
}
static void feed_ready_handler(TimerHandle_t xTimer) {
s_pet_status.is_feed_cd = false;
s_pet_status.feed_timestamp = 0;
ESP_LOGW("PET", "进入定时器结算");
pet_save_status_to_nvs();
feed_timer = NULL;
xTimerDelete(xTimer, 0);
}
static void travel_ready_handler(TimerHandle_t xTimer) {
s_pet_status.is_travelling = false;
s_pet_status.travel_timestamp = 0;
ESP_LOGW("PET", "进入定时器结算");
pet_save_status_to_nvs();
travel_timer = NULL;
xTimerDelete(xTimer, 0);
}
/*
* 时间同步任务骨架
* - 如果无网络/未能 NTP,同步失败后进入休眠/等待
* - 同步成功后设置 g_pet_animation_ready
* - 如果是第一次初始化(g_pet_need_first_sync==true),写入当前时间到 NVS
* - 否则根据过去时间计算状态衰减(此处留占位注释,具体公式可在 TODO 中实现)
*/
static void pet_time_sync(void){
if (!g_wifi_state){
// 无网络则休眠,等待外部事件唤醒或下次循环
ESP_LOGE("PET", "无WIFI进入待机状态");
vTaskDelay(pdMS_TO_TICKS(5000));
}
else{
g_pet_ntp_synced = obtain_time();
if(!g_pet_ntp_synced){
ESP_LOGE("PET", "有WIFI但网络同步失败,进入休眠状态");
vTaskDelay(pdMS_TO_TICKS(5000));
}
else{
ESP_LOGW("PET", "有WIFI且时钟同步成功,开启动画窗口");
time_t now;
time(&now);
if (g_pet_need_first_sync) {
ESP_LOGW("PET", "第一次同步,写入当前时间到 NVS");
s_pet_status.last_timestamp = (uint64_t)now;
localtime_r(&now, &s_pet_status.last_time);
pet_save_status_to_nvs();
g_pet_need_first_sync = false;
g_pet_animation_ready = true;
}else {
ESP_LOGW("PET", "不是第一次同步,计算过去时间并衰减属性");
uint64_t elapsed = (uint64_t)now - s_pet_status.last_timestamp;
ESP_LOGW("PET", "相比之前过去了%llu秒",elapsed);
s_pet_status.last_timestamp = (uint64_t)now;
localtime_r(&now, &s_pet_status.last_time);
compare_time_cd((uint64_t)now);
char strftime_buf[64];
strftime(strftime_buf, sizeof(strftime_buf), "%c", &s_pet_status.last_time);//覆盖上次启动时间
ESP_LOGI("NTP", "同步时间: %s", strftime_buf);
cal_deacy(elapsed);
pet_save_status_to_nvs();
pet_update_stage();
pet_update_vehicle_stage();
g_pet_animation_ready = true;
}
}
}
}
static void pet_time_timer(void){
const TickType_t delay_ms = pdMS_TO_TICKS(60000*5); // 5 分钟间隔
TickType_t last_wake = xTaskGetTickCount();
uint32_t tick_count = 0;
int times_hunger;
int times_cleanliness;
int times_mood;
while (1) {
vTaskDelayUntil(&last_wake, delay_ms);
tick_count++;
ESP_LOGI("NTP", "五分钟间隔");
time_t now;
time(&now);
//uint64_t elapsed = (uint64_t)now - s_pet_status.last_timestamp;
uint8_t compensate_tickcount_hunger= remaining_hunger / 300; //掉电补偿
uint8_t compensate_tickcount_cleanliness= remaining_cleanliness / 300; //掉电补偿
uint8_t compensate_tickcount_mood= remaining_mood / 300;
int cmd1=1;int cmd2=2;int cmd3=3;int cmd4=4;
if(s_pet_status.is_feed_cd){
uint64_t elapsed = (uint64_t)now - s_pet_status.feed_timestamp;
if(elapsed > s_pet_status.feed_time - 300){ //最后五分钟
feed_timer = xTimerCreate("feed_ready_can_use", pdMS_TO_TICKS((s_pet_status.feed_time-elapsed)*1000),
pdFALSE, (void*)0,
feed_ready_handler);
xTimerStart(feed_timer, 0);
ESP_LOGW("PET", "即将完成喂食冷却,启用高精度定时");
}
}
if(s_pet_status.is_travelling){
uint64_t elapsed = (uint64_t)now - s_pet_status.travel_timestamp;
if(elapsed > s_pet_status.travel_time - 300){ //最后五分钟
travel_timer = xTimerCreate("travel_ready_done", pdMS_TO_TICKS((s_pet_status.travel_time-elapsed)*1000),
pdFALSE, (void*)0,
travel_ready_handler);
xTimerStart(travel_timer, 0);
ESP_LOGW("PET", "即将完成旅行冷却,启用高精度定时");
}
}
/* (15 分钟) 饱腹 -2 */
if ((tick_count + compensate_tickcount_hunger) % 3 == 0) {
if (s_pet_status.hunger > 2) s_pet_status.hunger -= 2;
else s_pet_status.hunger = 0;
s_pet_status.last_timestamp = (uint64_t)now;
localtime_r(&now, &s_pet_status.last_time);
remaining_hunger = 0;
ESP_LOGW("PET", "饱腹-2");
}
/* (20 分钟) 清洁 -1 */
if ((tick_count + compensate_tickcount_cleanliness) % 4 == 0) {
if (s_pet_status.cleanliness > 0) s_pet_status.cleanliness -= 1;
else s_pet_status.cleanliness = 0;
s_pet_status.last_timestamp = (uint64_t)now;
localtime_r(&now, &s_pet_status.last_time);
ESP_LOGW("PET", "清洁-1");
}
/* (25 分钟) 心情 -1 */
if ((tick_count + compensate_tickcount_cleanliness)% 5 == 0) {
if (s_pet_status.mood > 0) s_pet_status.mood -= 1;
else s_pet_status.mood = 0;
s_pet_status.last_timestamp = (uint64_t)now;
localtime_r(&now, &s_pet_status.last_time);
ESP_LOGW("PET", "心情-1");
}
if(tick_count % 2 ==0 && go_travelling() && s_pet_status.can_travel){//缺少场景模式判断
xQueueSend(user_interact_Queue, &cmd4,0);
}
/* 每 12 次(60 分钟) 写入 NVS */
if ((tick_count % 12) == 0) {
s_pet_status.last_timestamp = (uint64_t)now;
localtime_r(&now, &s_pet_status.last_time);
pet_save_status_to_nvs();
ESP_LOGI(TAG, "Hourly NVS write (tick %u)", tick_count);
}
}
}
static void pet_time_sync_task(void *arg)
{
while (1){
uxTaskGetStackHighWaterMark(NULL);
if(g_pet_animation_ready == false){
pet_time_sync();
}else{
pet_time_timer();
}
}
}
static void pet_user_write_task(void *arg)
{
int received_cmd = 0;
while (1) {
/* 阻塞直到收到数据,等待用户命令 */
uxTaskGetStackHighWaterMark(NULL);
if (xQueueReceive(user_interact_Queue, &received_cmd, portMAX_DELAY) == pdPASS) {
ESP_LOGI(TAG, "pet_user_write_task: received command %d", received_cmd);
switch (received_cmd) {
case 1:
pet_feed();
ESP_LOGI(TAG, "命令1执行");
break;
case 2:
pet_bath();
ESP_LOGI(TAG, "命令2执行");
break;
case 3:
pet_interact();
ESP_LOGI(TAG, "命令3执行");
break;
case 4:
pet_travel_return();
//pet_start_travelling();
ESP_LOGI(TAG, "命令4执行");
break;
default:
ESP_LOGW(TAG, "未知用户命令 %d", received_cmd);
break;
}
}
}
}
void pet_start_travelling(void)
{
s_pet_status.mood = clamp_u8(s_pet_status.mood + 20, 0, 100);
}
+128
View File
@@ -0,0 +1,128 @@
#ifndef PET_INIT_H
#define PET_INIT_H
#include <stdint.h>
#include <stdbool.h>
#include <time.h>
#include "main.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
PET_STAGE_BABY = 0,
PET_STAGE_TEEN,
PET_STAGE_ADULT,
} pet_stage_t;
typedef enum {
PET_VEHICLE_WALK = 0,
PET_VEHICLE_BIKE,
PET_VEHICLE_MOTORCYCLE,
PET_VEHICLE_CAR,
PET_VEHICLE_PLANE,
} pet_vehicle_t;
typedef enum {
PARK = 0,
STREET, // 街道
RIVER, // 河流
ANCIENT_TOWN, // 古镇
MOUNTAIN_TRAIL, // 山道
RESERVOIR, // 水库
SETTLEMENT, // 聚落
ORCHARD, // 果园
HUIZHOU, // 惠州
DONGGUAN, // 东莞
FOSHAN, // 佛山
ZHONGSHAN, // 中山
JIANGMEN, // 江门
QINGYUAN, // 清远
ZHAOQING, // 肇庆
HEYUAN, // 河源
MEIZHOU, // 梅州
CHONGQING, // 重庆
XIAN, // 西安
GUANGZHOU, // 广州
NANJING, // 南京
SANYA, // 三亚
BEIJING, // 北京
CHANGSHA, // 长沙
ZHANGJIAJIE, // 张家界
XIANGXI, // 湘西
FENGHUANG_GUZHEN, // 凤凰古镇
SHANGHAI, // 上海
XIANGGELILA, // 香格里拉
LIJIANG, // 丽江
DALI, // 大理
XIZANG, // 西藏
XIAMEN, // 厦门
JINGDEZHEN, // 景德镇
PINGTAN, // 平潭
QINGDAO, // 青岛
BEIHAI, // 北海
WANNING, // 万宁
ZIBO, // 淄博
YANJI, // 延吉
LIUZHOU, // 柳州
YICHANG, // 宜昌
MOHE, // 漠河
GUILIN, // 桂林
HANGZHOU, // 杭州
XITANG, // 西塘
QINGHAI, // 青海
XISHUANGBANNA, // 西双版纳
DUNHUANG, // 敦煌
YUNNAN, // 云南(省/区域)
CITY_COUNT, // 枚举计数(必须放最后)
} travel_city_t;
extern const char *city_names[CITY_COUNT];
typedef struct {
uint8_t hunger; // 0-100
uint8_t cleanliness; // 0-100
uint8_t mood; // 0-100
uint16_t intimacy; // 0-300
pet_stage_t stage;
pet_vehicle_t vehicle;
uint64_t last_timestamp; // UTC seconds stored as u64
struct tm last_time; // 本地时间表示
bool is_travelling; //是否在旅行
uint64_t travel_timestamp;
bool can_travel;
uint16_t travel_time;
uint16_t feed_time; //喂食状态
uint64_t feed_timestamp;
bool is_feed_cd;
} pet_status_t;
/* 全局标志(在 pet_init.c 中定义) */
extern bool g_pet_need_first_sync; // true 表示需要第一次 NTP 同步并写入时间
extern bool g_pet_ntp_synced; // NTP 同步成功
extern bool g_pet_animation_ready; // 同步成功后,动画可用标志
/* 对外接口 */
void pet_init(void);
const pet_status_t *pet_get_status(void);
pet_stage_t pet_get_stage(uint16_t intimacy);
pet_vehicle_t pet_get_vehicle_stage(uint16_t intimacy);
void pet_feed(void);
void pet_bath(void);
void pet_interact(void);
void pet_travel_return(void);
/* 时间同步任务控制 */
void pet_start_time_task(void);
extern bool g_wifi_state;
#ifdef __cplusplus
}
#endif
#endif
+989
View File
@@ -0,0 +1,989 @@
#include <stdio.h>
#include <inttypes.h>
#include "../config.h"
#if !EXAMPLE_USE_TOUCH
#error "SC7U22 requires EXAMPLE_USE_TOUCH (IMU shares the touch I2C bus)."
#endif
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include <math.h>
#include "esp_timer.h"
#include "../ui/generated/gui_guider.h" // 包含GUI引导器头文件
#include "lvgl.h" // 包含LVGL头文件
#include "LVGL/lvgl_init.h"
#include "ui_cmd.h"
#include "main.h"
static bool event_cooldown_active = false; // 是否处于冷却屏蔽中
static int64_t cooldown_end_time = 0; // 冷却结束时间戳
static const char *TAG_IMU = "SCU722";
i2c_master_dev_handle_t SC7U22_handle;
/** 与触摸共用的 I2C 总线句柄(SC7U22_init 写入,供扫描/查询) */
i2c_master_bus_handle_t s_i2c_bus;
sensor_data_t data_buffer[DATA_BUFFER_SIZE];
SC7U22_DataType sensor_data = {0};
static int buffer_index = 0;
static int buffer_count = 0;
struct acc_offset offset_data = {0};
noise_datatype noise_data = {0};
float ax=0,ay=0,az=0,gx=0,gy=0,gz=0;
//float acc_pre_x=0,acc_pre_y=0,acc_pre_z=0;
float acc_sum = 0;
float acc_sign_sum = 0;
float acc_hp = 0;
uint32_t bum_energy = 0;
float iir_state = 0;
uint16_t start_stop_cnt = 0;
uint16_t stop_cnt = 0;
uint8_t sharp_turn_flag = 0,pre_st_flag = 0;
bool sharpturn_ret_L = false,sharpturn_ret_R = false;
uint8_t rapid_acc_flag = 0,pre_ra_flag = 0,rapid_acc_ret = 0;
uint8_t rapid_dec_flag = 0,pre_rd_flag = 0,rapid_dec_ret = 0;
bool brake_fir_flag = false, rapid_fir_flag = false;
const char* MYTAG = "SC7U22";
extern int lottie_id;
extern int current_page_idx;
extern lv_ui guider_ui; // 外部声明GUI引导器
/*
* @brief This function converts lsb to meter per second squared for 16 bit accelerometer at
* range 2G, 4G, 8G or 16G.
*/
float lsb_to_mps2(int16_t val, float g_range, uint8_t bit_width)
{
double power = 2;
float half_scale = (float)((pow((double)power, (double)bit_width) / 2.0f));
return (GRAVITY_EARTH * val * g_range) / half_scale;
}
/*
* @brief This function converts lsb to degree per second for 16 bit gyro at
* range 125, 250, 500, 1000 or 2000dps.
*/
float lsb_to_dps(int16_t val, float dps, uint8_t bit_width)
{
double power = 2;
float half_scale = (float)((pow((double)power, (double)bit_width) / 2.0f));
return (dps / (half_scale)) * (val);
}
void i2c_scan_devices(void)
{
if (s_i2c_bus == NULL) {
ESP_LOGW(MYTAG, "i2c_scan: bus not ready (call SC7U22_init first)");
return;
}
uint8_t address;
esp_err_t ret;
bool found = false;
ESP_LOGI(MYTAG, "Scanning I2C devices on shared bus...");
for (address = 1; address < 127; address++) {
ret = i2c_master_probe(s_i2c_bus, (uint16_t)address, 100);
if (ret == ESP_OK) {
ESP_LOGI(MYTAG, "Device found at address: 0x%02X", address);
found = true;
}
vTaskDelay(50 / portTICK_PERIOD_MS);
}
if (!found) {
ESP_LOGI(MYTAG, "No I2C devices found");
}
}
void SC7U22_init(i2c_master_bus_handle_t i2c_bus)
{
if (i2c_bus == NULL) {
ESP_LOGE(MYTAG, "SC7U22_init: I2C bus handle is NULL");
return;
}
s_i2c_bus = i2c_bus;
ESP_LOGI(MYTAG, "SC7U22 on shared I2C bus: %p", (void *)s_i2c_bus);
i2c_device_config_t dev_cfg = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = SC7U22_I2C_ADDR,
.scl_speed_hz = I2C_MASTER_FREQ_HZ,
};
esp_err_t ret = i2c_master_bus_add_device(s_i2c_bus, &dev_cfg, &SC7U22_handle);
if (ret != ESP_OK) {
ESP_LOGE(MYTAG, "添加设备失败,错误码: %s", esp_err_to_name(ret));
return;
}
SC7U22_Sensor_enable();
ESP_LOGI(MYTAG, "SC7U22 初始化完成(硬件就绪,校准将在后台进行)");
}
esp_err_t SC7U22_Reg_W(uint8_t reg_addr, uint8_t data)
{
uint8_t write_buf[2] = {reg_addr, data};
return i2c_master_transmit(SC7U22_handle, write_buf, sizeof(write_buf), 1000 / portTICK_PERIOD_MS);
}
esp_err_t SC7U22_Reg_R(uint8_t reg_addr, uint8_t *data)
{
return i2c_master_transmit_receive(SC7U22_handle, &reg_addr, 1, data, 1, 1000 / portTICK_PERIOD_MS);
}
void SC7U22_Sensor_enable(void)
{
SC7U22_Reg_W(0x7F, 0x00); // Bank / page(如需要)
vTaskDelay(pdMS_TO_TICKS(2));
SC7U22_Reg_W(0x7D, 0x0E); // PWR_CTRL enable acc + gyro
vTaskDelay(pdMS_TO_TICKS(5));
// 加速度
SC7U22_Reg_W(0x40, 0xA8); // ACC_CONF(用缺省)
SC7U22_Reg_W(0x41, 0x02); // ±4g
// 陀螺仪(关键)
SC7U22_Reg_W(0x42, 0xA9); // GYR_CONF(缺省、安全)
SC7U22_Reg_W(0x43, 0x00); // ±2000 dps
}
void SC7U22_DaMYTAGet(SC7U22_DataType* SC7U22_DataStucture)
{
esp_err_t ret;
uint8_t raw_acc[6]; // 用于存放 AX_L, AX_H, AY_L, AY_H, AZ_L, AZ_H
uint8_t raw_gyr[6]; // 用于存放 GX_L, GX_H, GY_L, GY_H, GZ_L, GZ_H
// ================= 加速度计读取 (一次读6字节) =================
// 假设 XOUT_L 是起始地址。如果你的宏定义 XOUT_H 比 XOUT_L 小,请改用 XOUT_H
uint8_t acc_reg_addr = SC7U22_ACCEL_XOUT_H;
ret = i2c_master_transmit_receive(SC7U22_handle, &acc_reg_addr, 1, raw_acc, 6, 1000 / portTICK_PERIOD_MS);
if (ret == ESP_OK) {
// 拼接数据。注意:通常 I2C 传感器先发低位(L),后发高位(H)。
// 具体的 buffer 顺序取决于寄存器地址顺序。
// 假设地址顺序是: X_L(0), X_H(1), Y_L(2), Y_H(3), Z_L(4), Z_H(5)
SC7U22_DataStucture->Ax = (int16_t)((raw_acc[0] << 8) | raw_acc[1]);
SC7U22_DataStucture->Ay = (int16_t)((raw_acc[2] << 8) | raw_acc[3]);
SC7U22_DataStucture->Az = (int16_t)((raw_acc[4] << 8) | raw_acc[5]);
} else {
ESP_LOGE(MYTAG, "Accel read failed: %s", esp_err_to_name(ret));
}
// ================= 陀螺仪读取 (一次读6字节) =================
// 假设 XOUT_L 是起始地址
uint8_t gyr_reg_addr = SC7U22_GYRO_XOUT_H;
//暂时注释掉
ret = i2c_master_transmit_receive(SC7U22_handle, &gyr_reg_addr, 1, raw_gyr, 6, 1000 / portTICK_PERIOD_MS);
if (ret == ESP_OK) {
SC7U22_DataStucture->Gx = (int16_t)((raw_gyr[0] << 8) | raw_gyr[1]);
SC7U22_DataStucture->Gy = (int16_t)((raw_gyr[2] << 8) | raw_gyr[3]);
SC7U22_DataStucture->Gz = (int16_t)((raw_gyr[4] << 8) | raw_gyr[5]);
} else {
ESP_LOGE(MYTAG, "Gyro read failed");
}
}
void baseline_remove(void)
{
// 加速度计去偏
ax = lsb_to_mps2(sensor_data.Ax, (float)4, 16) - offset_data.acc_x_offset;
ay = lsb_to_mps2(sensor_data.Ay, (float)4, 16) - offset_data.acc_y_offset;
az = lsb_to_mps2(sensor_data.Az, (float)4, 16) - offset_data.acc_z_offset;
// 陀螺仪去偏 (新增)
gx = lsb_to_dps(sensor_data.Gx, (float)2000, 16) - offset_data.gyr_x_offset;
gy = lsb_to_dps(sensor_data.Gy, (float)2000, 16) - offset_data.gyr_y_offset;
gz = lsb_to_dps(sensor_data.Gz, (float)2000, 16) - offset_data.gyr_z_offset;
}
void noise_threshold_get(void)
{
uint8_t ax_cnt = 0,ay_cnt = 0,az_cnt = 0,gx_cnt = 0,gy_cnt = 0,gz_cnt = 0;
SC7U22_DaMYTAGet(&sensor_data);
baseline_remove();
for(int i = 0;i<50;i++)
{
if(ax!=0) {noise_data.accx_noise+=fabs(ax); ax_cnt++;}
if(ay!=0) {noise_data.accy_noise+=fabs(ay); ay_cnt++;}
if(az!=0) {noise_data.accz_noise+=fabs(az); az_cnt++;}
if(gx!=0) {noise_data.gyrx_noise+=fabs(gx); gx_cnt++;}
if(gy!=0) {noise_data.gyry_noise+=fabs(gy); gy_cnt++;}
if(gz!=0) {noise_data.gyrz_noise+=fabs(gz); gz_cnt++;}
vTaskDelay(pdMS_TO_TICKS(5));
}
noise_data.accx_noise/=(float)ax_cnt;
noise_data.accy_noise/=(float)ay_cnt;
noise_data.accz_noise/=(float)az_cnt;
noise_data.gyrx_noise/=(float)gx_cnt;
noise_data.gyry_noise/=(float)gy_cnt;
noise_data.gyrz_noise/=(float)gz_cnt;
}
void data_deal_with_noise(void)
{
if(fabs(ax)<fabs(noise_data.accx_noise)*1.5f+0.05) ax = 0;
if(fabs(ay)<fabs(noise_data.accy_noise)*1.5f+0.05) ay = 0;
if(fabs(az)<fabs(noise_data.accz_noise)*1.5f+0.05) az = 0;
if(fabs(gx)<fabs(noise_data.gyrx_noise)*1.5f+1) gx = 0;
if(fabs(gy)<fabs(noise_data.gyry_noise)*1.5f+1) gy = 0;
if(fabs(gz)<fabs(noise_data.gyrz_noise)*1.5f+1) gz = 0;
}
void mySC7_getdata(void)
{
SC7U22_DaMYTAGet(&sensor_data);
baseline_remove(); //去零偏处理
data_deal_with_noise();//去噪声处理
data_buffer[buffer_index].accel_x = ax;
data_buffer[buffer_index].accel_y = ay;
data_buffer[buffer_index].accel_z = az;
data_buffer[buffer_index].timestamp = esp_timer_get_time();
buffer_index = (buffer_index + 1) % DATA_BUFFER_SIZE;
if (buffer_count < DATA_BUFFER_SIZE) {
buffer_count++;
}
}
void accel_Getoffset(void)
{
float ax_sum=0, ay_sum=0, az_sum=0;
float gx_sum=0, gy_sum=0, gz_sum=0; // 新增
ESP_LOGI(MYTAG, "正在校准传感器,请保持静止...");
for(int i = 0; i < 100; i++) // 采样100次
{
SC7U22_DaMYTAGet(&sensor_data);
// 累加加速度
ax_sum += lsb_to_mps2(sensor_data.Ax, (float)4, 16);
ay_sum += lsb_to_mps2(sensor_data.Ay, (float)4, 16);
az_sum += lsb_to_mps2(sensor_data.Az, (float)4, 16);
// 累加陀螺仪 (注意:这里使用 2000dps 量程)
gx_sum += lsb_to_dps(sensor_data.Gx, (float)2000, 16);
gy_sum += lsb_to_dps(sensor_data.Gy, (float)2000, 16);
gz_sum += lsb_to_dps(sensor_data.Gz, (float)2000, 16);
vTaskDelay(pdMS_TO_TICKS(10));
}
// 计算平均值
offset_data.acc_x_offset = ax_sum / 100.0f;
offset_data.acc_y_offset = ay_sum / 100.0f;
offset_data.acc_z_offset = (az_sum / 100.0f) - GRAVITY_EARTH; // Z轴要扣除重力
// 计算陀螺仪偏移 (理论上静止时全是0,所以读数即为偏移)
offset_data.gyr_x_offset = gx_sum / 100.0f;
offset_data.gyr_y_offset = gy_sum / 100.0f;
offset_data.gyr_z_offset = gz_sum / 100.0f;
int gz_milli = (int)(offset_data.gyr_z_offset * 1000.0f);
ESP_LOGI(MYTAG, "校准完成! Gz Offset: %d.%03d", gz_milli / 1000, abs(gz_milli % 1000));
}
void bum_energy_get(void)
{
acc_sum += fabs(az);
acc_sign_sum += az;
iir_state = ALPHA * iir_state + (1 - ALPHA) * acc_sum;
acc_hp = acc_sum -iir_state; //高通滤波
bum_energy += (uint32_t)(acc_hp*acc_hp*1000); //处理颠簸能量
}
bool is_bump(void)
{
uint32_t temp = bum_energy;
static bool wasbump = false;
float variance = 0;
bum_energy = 0;
acc_sum = 0;
acc_sign_sum /= DATA_BUFFER_SIZE;
for(int i = 0;i<DATA_BUFFER_SIZE;i++)
{
variance += fabs(data_buffer[i].accel_z - acc_sign_sum); //平均绝对值误差
}
acc_sign_sum = 0;
bool isbump = (temp>=BUM_THRESHOLD&&variance>=400);
if(isbump&&!wasbump)
{
wasbump = isbump;
return true;
}
wasbump = isbump;
return false;
}
void sharp_turn_detect(void)
{
if(fabs(gz) > turn_gyro_threshold && fabs(ay) > turn_accel_threshold)
{
sharp_turn_flag++;
}
else
{
sharp_turn_flag = 0;
}
if(sharp_turn_flag == 0 && pre_st_flag >= 5) // 连续触发次数稍微减少一点,灵敏些
{
pre_st_flag = 0;
// 左转通常 gyrz > 0 (逆时针),右转 gyrz < 0 (顺时针)
// 具体正负取决于芯片安装面,你测试时如果不准,互换一下即可
if(gz > 0) sharpturn_ret_L = true;
else if(gz < 0) sharpturn_ret_R = true;
}
pre_st_flag = sharp_turn_flag;
}
driving_event_t is_sharp_turn(void)
{
if(sharpturn_ret_L)
{
sharpturn_ret_L = 0;
return EVENT_SHARP_LTURN;
}
else if(sharpturn_ret_R)
{
sharpturn_ret_R = 0;
return EVENT_SHARP_RTURN;
}
return EVENT_NONE;
}
static float calculate_acceleration_magnitude(void)
{
return sqrtf(ax * ax + ay * ay + az * az);
}
void start_stop_state(void)
{
static uint8_t st_cnt = 0;
static uint8_t sp_cnt = 0;
static bool stop_flag = false;
bool is_stop = calculate_acceleration_magnitude()<= is_stop_magnitude;
if(is_stop&&!stop_flag) sp_cnt++;
if(sp_cnt>=3)
{
stop_flag = true;
sp_cnt = 0;
}
if(is_stop&&stop_flag)
{
stop_cnt++;
}
if(stop_flag&&!is_stop)
{
st_cnt++;
}
else{
st_cnt = 0;
}
if(st_cnt>=3)
{
start_stop_cnt++;
st_cnt = 0;
stop_flag = false;
}
}
// 判断急停急加速
void detect_driving_events(void)
{
//获取数据
mySC7_getdata();
//颠簸能量累加
bum_energy_get();
//急转弯检测
sharp_turn_detect();
//急加速检测 急刹车检测
if (buffer_count < 2) return ;
int current_idx = (buffer_index - 1 + DATA_BUFFER_SIZE) % DATA_BUFFER_SIZE;
int prev_idx = (buffer_index - 2 + DATA_BUFFER_SIZE) % DATA_BUFFER_SIZE;
// 计算时间差(秒)
float time_diff = (data_buffer[current_idx].timestamp -
data_buffer[prev_idx].timestamp) / 1000000.0f;
if (time_diff <= 0) return ;
float accel_diff_x = data_buffer[current_idx].accel_x - data_buffer[prev_idx].accel_x;
float jerk_x = accel_diff_x / time_diff; // 急动度
// 急刹车/减速检测
// 逻辑:当前有一个很大的负向加速度(刹车),且变化率很大
// 注意:如果是“前倾 accx 变大”,说明 +X 是车头向下或向前。
// 假设 +X 是向前加速,那么 -X 就是刹车。
if ((jerk_x < -rapid_accel_threshold/0.02) && (data_buffer[current_idx].accel_x < -rapid_accel_threshold))
rapid_dec_flag = 1;
if ((data_buffer[current_idx].accel_x < -rapid_accel_threshold) && rapid_dec_flag)
rapid_dec_flag++;
else
rapid_dec_flag = 0;
if (rapid_dec_flag == 0 && pre_rd_flag >= 4) rapid_dec_ret = 1; // 阈值改小一点容易触发
pre_rd_flag = rapid_dec_flag;
// 急加速检测
// 逻辑:当前有一个很大的正向加速度
if ((jerk_x > rapid_accel_threshold/0.02) && (data_buffer[current_idx].accel_x > rapid_accel_threshold))
rapid_acc_flag = 1;
if ((data_buffer[current_idx].accel_x > rapid_accel_threshold) && rapid_acc_flag)
rapid_acc_flag++;
else
rapid_acc_flag = 0;
if (rapid_acc_flag == 0 && pre_ra_flag >= 4) rapid_acc_ret = 1;
pre_ra_flag = rapid_acc_flag;
}
driving_event_t is_rapid_acc_or_dec(void)
{
if(rapid_acc_ret)
{
rapid_acc_ret = 0;
return EVENT_SUDDEN_ACCEL;
}
if(rapid_dec_ret)
{
rapid_dec_ret = 0;
return EVENT_SUDDEN_BRAKE;
}
return EVENT_NONE;
}
bool is_jam(void)
{
float stop_proportion = (float)stop_cnt/600.0;
uint16_t temp_cnt = start_stop_cnt;
stop_cnt = 0;
start_stop_cnt = 0;
if((stop_proportion>=stop_proportion_threshold&&temp_cnt>=start_stop_threshold)||stop_proportion>=0.8f)
{
return true;
}
return false;
}
// 在IMU任务函数外定义(全局区),避免栈溢出,同时供回调函数访问
// 驾驶事件冷却检查:冷却中返回true,允许触发返回false
bool is_event_in_cooldown(void)
{
if (!event_cooldown_active) {
return false;
}
// 当前时间 < 结束时间 = 还在冷却
if (esp_timer_get_time() < cooldown_end_time) {
return true;
} else {
// 冷却结束,复位
event_cooldown_active = false;
ESP_LOGI(MYTAG, "冷却时间已结束,可以再次触发驾驶事件");
return false;
}
}
// 触发一次事件后,启动50秒冷却
void start_event_cooldown(void)
{
event_cooldown_active = true;
cooldown_end_time = esp_timer_get_time() + (EVENT_COOLDOWN_MS * 1000); // 转微秒
ESP_LOGI(MYTAG, "已启动冷却,%d 毫秒内不再触发", EVENT_COOLDOWN_MS);
}
/** 诊断:因 g_ble_play_sticker_active 抑制幸运表情时打印(节流,避免刷屏) */
#define BLE_GUARD_LOG_THROTTLE_MS 3000
static uint32_t s_ble_guard_last_log_tick;
static void ble_guard_log_lucky_suppressed(const char *reason)
{
uint32_t t = lv_tick_get();
if (s_ble_guard_last_log_tick != 0 && (t - s_ble_guard_last_log_tick) < BLE_GUARD_LOG_THROTTLE_MS)
return;
s_ble_guard_last_log_tick = t;
ESP_LOGW("BLE_STICKER_GUARD",
"Lucky suppressed: %s (g_ble_play_sticker_active still true)",
reason);
}
void imu_task(void *param)
{
ESP_LOGI(MYTAG, "Starting IMU task");
driving_event_t drive_even = EVENT_NONE;
uint16_t i = 0;
/* 保持原校准逻辑不变,只调整执行时机:由主初始化路径改为后台任务执行。 */
uint32_t calib_start_ms = (uint32_t)(esp_timer_get_time() / 1000ULL);
ESP_LOGI(MYTAG, "IMU 后台校准开始");
vTaskDelay(pdMS_TO_TICKS(20));
accel_Getoffset();
noise_threshold_get();
uint32_t calib_elapsed_ms = (uint32_t)(esp_timer_get_time() / 1000ULL) - calib_start_ms;
ESP_LOGI(MYTAG, "IMU 后台校准完成,耗时: %" PRIu32 " ms", calib_elapsed_ms);
while(1)
{
// ========== 原有逻辑保持不变 ==========
mySC7_getdata(); // 永远读取传感器数据
ESP_LOGW(MYTAG,"正在读取IMU数据...");
if (current_page_idx == 0)
{
detect_driving_events();
drive_even = is_sharp_turn();
if(drive_even == EVENT_SHARP_LTURN) //急转弯判断 结束一个急转弯后判定
{
if (is_event_in_cooldown()) continue; // 冷却中直接跳过
if (g_ble_play_sticker_active)
{
ble_guard_log_lucky_suppressed("left sharp turn");
continue; /* BLE 正在播表情时不插播 IMU 幸运 */
}
start_event_cooldown(); // 启动50秒冷却
/*左急转弯反馈入口*/
lottie_id = 29;
ESP_LOGE(MYTAG,"左急转!");
/*左急转弯反馈结束*/
}else if(drive_even == EVENT_SHARP_RTURN)
{
if (is_event_in_cooldown()) continue; // 冷却中直接跳过
if (g_ble_play_sticker_active)
{
ble_guard_log_lucky_suppressed("right sharp turn");
continue;
}
start_event_cooldown(); // 启动50秒冷却
/*右急转弯反馈入口*/
lottie_id = 29;
ESP_LOGE(MYTAG,"右急转!");
/*右急转弯反馈入口*/
}
drive_even = is_rapid_acc_or_dec();
if(drive_even==EVENT_SUDDEN_ACCEL)
{
if (is_event_in_cooldown()) continue; // 冷却中直接跳过
if (g_ble_play_sticker_active)
{
ble_guard_log_lucky_suppressed("sudden accel");
continue;
}
start_event_cooldown(); // 启动50秒冷却
/*急加速反馈入口*/
lottie_id = 29;
ESP_LOGE(MYTAG,"急加速!");
/*急加速反馈结束*/
}
else if(drive_even==EVENT_SUDDEN_BRAKE)
{
if (is_event_in_cooldown()) continue; // 冷却中直接跳过
if (g_ble_play_sticker_active)
{
ble_guard_log_lucky_suppressed("sudden brake");
continue;
}
start_event_cooldown(); // 启动50秒冷却
/*急刹车反馈入口*/
lottie_id = 29;
ESP_LOGE(MYTAG,"急刹车!");
/*急刹车反馈结束*/
}
}
// ========== 4. 通用逻辑(堵车/颠簸检测) ==========
vTaskDelay(pdMS_TO_TICKS(20));
i++;
if(i%25==0 && current_page_idx == 0)
{
//堵车数据检测
start_stop_state();
// printf("accx:%4.2f, accy:%4.2f, accz:%4.2f, gyrx:%4.2f, gyry:%4.2f, gyrz:%4.2f\n",
// ax,ay,az,gx,gy,gz);
}
if(i%200==0 && current_page_idx == 0) //2秒累加检测 反馈程序入口
{
if(is_bump()) //判断路面颠簸
{
ESP_LOGE(MYTAG,"路面颠簸!");
}
}
if(i>=15000 && current_page_idx == 0) //5分钟判定一次堵车
{
if(is_jam())
{
ESP_LOGE(MYTAG,"堵车!");
}
i = 0;
}
vTaskDelay(pdMS_TO_TICKS(30));
}
}
i2c_master_bus_handle_t get_i2c_master_bus_handle(void)
{
return s_i2c_bus;
}
i2c_master_bus_handle_t touch_bus_handle_BMI;
i2c_master_bus_handle_t touch_bus_handle_NST112;
i2c_master_bus_handle_t touch_bus_handle_MS523;
i2c_master_bus_handle_t n_i2c_bus;
//i2c_master_bus_config_t i2c_bus_config = {0};
esp_err_t BMI_init(void)
{
ESP_LOGI(TAG_IMU, "开始初始化 SC7U22...");
vTaskDelay(pdMS_TO_TICKS(100));
#if !EXAMPLE_USE_TOUCH
#error "BMI_init requires EXAMPLE_USE_TOUCH (SC7U22 shares touch I2C bus)."
#endif
//extern i2c_master_bus_handle_t touch_bus_handle; I2C主总线
i2c_master_bus_config_t i2c_bus_config = {0};
i2c_bus_config.clk_source = I2C_CLK_SRC_DEFAULT;
i2c_bus_config.i2c_port = 0;
i2c_bus_config.scl_io_num = TEST_BOARD_I2C_SCL_PIN;
i2c_bus_config.sda_io_num = TEST_BOARD_I2C_SDA_PIN;
i2c_bus_config.glitch_ignore_cnt = 7;
i2c_bus_config.flags.enable_internal_pullup = true;
//i2c_new_master_bus(&i2c_bus_config, &i2c_bus_handle);
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_config, &touch_bus_handle_BMI));
s_i2c_bus = touch_bus_handle_BMI;
i2c_scan_devices();
SC7U22_init(touch_bus_handle_BMI);
bmi_task_stack = heap_caps_malloc(BMI_TASK_STACK_SIZE, MALLOC_CAP_SPIRAM);
if (bmi_task_stack == NULL)
{
ESP_LOGE("LVGL", "Failed to allocate BMI task stack");
return ESP_FAIL;
}
TaskHandle_t task_handle = xTaskCreateStaticPinnedToCore(imu_task, "BMI", BMI_TASK_STACK_SIZE, NULL, BMI_TASK_PRIORITY, bmi_task_stack, &bmi_task_tcb, 0);
if (task_handle == NULL)
{
ESP_LOGE("LVGL", "Failed to create BMI task");
heap_caps_free(bmi_task_stack);
bmi_task_stack = NULL;
return ESP_FAIL;
}
ESP_LOGI("LVGL", "BMI task created successfully");
return ESP_OK;
}
i2c_master_dev_handle_t NST112_handle;
uint8_t temp_data[2] ={0};
uint8_t config_data[2] = {0};
uint8_t H_tmp_data[2] = {0};
uint8_t L_tmp_data[2] = {0};
esp_err_t NST112_Reg_W(uint8_t reg_addr, uint8_t data)
{
uint8_t write_buf[] = {reg_addr, data};
return i2c_master_transmit(NST112_handle, write_buf, sizeof(write_buf), 1000 / portTICK_PERIOD_MS);
}
esp_err_t NST112_Reg_R(uint8_t reg_addr)
{
esp_err_t ret = i2c_master_transmit_receive(NST112_handle,&reg_addr,1,temp_data,2,1000 / portTICK_PERIOD_MS);
if(ret != ESP_OK){
ESP_LOGE("NST112", "读取 NST112 寄存器失败,错误码: %s", esp_err_to_name(ret));
return ret;
}
else{
return ESP_OK;
}
}
extern StaticTask_t nst112_task_tcb;
extern StackType_t *nst112_task_stack;
void nst112_task(void *param)
{
ESP_LOGI("NST112", "Starting NST112 task");
uint8_t read_addr = 0x00;
float temp_c = 0;
while(1)
{
i2c_master_transmit_receive(NST112_handle, &read_addr, 1, temp_data, 2, 1000 / portTICK_PERIOD_MS);
int16_t raw = (int16_t)((temp_data[0] << 8) | temp_data[1]);
temp_c = raw / 256.0f;
ESP_LOGI("NST112", "TEMP: %02X %02X", temp_data[0], temp_data[1]);
ESP_LOGI("NST112", "TEMP (C): %f", temp_c);
if(temp_c > 70){
ESP_LOGI("NST112", "TEMP: WARNING");
}
if(temp_c < (-20)){
ESP_LOGI("NST112", "TEMP: WARNING");
}
vTaskDelay(pdMS_TO_TICKS(2000));
}
}
esp_err_t NST112_init(void)
{
//extern i2c_master_bus_handle_t touch_bus_handle_touch;
i2c_master_bus_config_t i2c_bus_config = {0};
i2c_bus_config.clk_source = I2C_CLK_SRC_DEFAULT;
i2c_bus_config.i2c_port = 1;
i2c_bus_config.scl_io_num = NST112_I2C_SCL_PIN;
i2c_bus_config.sda_io_num = NST112_I2C_SDA_PIN;
i2c_bus_config.glitch_ignore_cnt = 7;
i2c_bus_config.flags.enable_internal_pullup = true;
//i2c_new_master_bus(&i2c_bus_config, &i2c_bus_handle);
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_config, &touch_bus_handle_NST112)); //此处单传感器调试用,正式使用时改为传入共享总线句柄
n_i2c_bus = touch_bus_handle_NST112;//touch_bus_handle_touch
s_i2c_bus = touch_bus_handle_NST112;
i2c_scan_devices();
ESP_LOGI("NST112", "NST112 on shared I2C bus: %p", (void *)n_i2c_bus);
i2c_device_config_t dev_cfg = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = NST112_I2C_ADDR,
.scl_speed_hz = I2C_MASTER_FREQ_HZ,
};
esp_err_t ret = i2c_master_bus_add_device(n_i2c_bus, &dev_cfg, &NST112_handle);
if (ret != ESP_OK) {
ESP_LOGE("NST112", "添加 NST112 设备失败");
return ret;
}
uint8_t write_buf[] = {0x01,0x60,0xA0}; // 0x01 寄存器地址,0x60 和 0xA0 是要写入的数据
uint8_t high_temp_config_reg[] = {0x03,0x05,0x50};
uint8_t low_temp_config_reg[] = {0x02,0x00,0xA0};
uint8_t read_config_addr = 0x01;
uint8_t read_H_TMP_addr = 0x02;
uint8_t read_L_TMP_addr = 0x03;
i2c_master_transmit(NST112_handle, write_buf, sizeof(write_buf), 1000 / portTICK_PERIOD_MS);
vTaskDelay(pdMS_TO_TICKS(1000));
ret = i2c_master_transmit_receive(NST112_handle,&read_config_addr,1 ,config_data,2,pdMS_TO_TICKS(100));
vTaskDelay(pdMS_TO_TICKS(1000));
ret = i2c_master_transmit_receive(NST112_handle,&read_L_TMP_addr,1 ,H_tmp_data,2,pdMS_TO_TICKS(100));
vTaskDelay(pdMS_TO_TICKS(1000));
ret = i2c_master_transmit_receive(NST112_handle,&read_H_TMP_addr,1 ,L_tmp_data,2,pdMS_TO_TICKS(100));
vTaskDelay(pdMS_TO_TICKS(1000));
ESP_LOGI("NST112", "TEMP: %02X %02X L:%02X %02X H:%02X %02X ", config_data[0], config_data[1],L_tmp_data[0],L_tmp_data[1],H_tmp_data[0],L_tmp_data[1]);
//i2c_master_transmit(NST112_handle, high_temp_config_reg, sizeof(high_temp_config_reg), 1000 / portTICK_PERIOD_MS);
//i2c_master_transmit(NST112_handle, low_temp_config_reg, sizeof(low_temp_config_reg), 1000 / portTICK_PERIOD_MS);
vTaskDelay(pdMS_TO_TICKS(100));
uint8_t read_addr = 0x00;
//i2c_master_transmit_receive(NST112_handle, &read_addr, 1, temp_data, 2, 1000 / portTICK_PERIOD_MS);
nst112_task_stack = heap_caps_malloc(NST112_TASK_STACK_SIZE, MALLOC_CAP_SPIRAM);
if (nst112_task_stack == NULL)
{
ESP_LOGE("LVGL", "Failed to allocate NST112 task stack");
return ESP_FAIL;
}
xTaskCreateStaticPinnedToCore(nst112_task, "NST112", NST112_TASK_STACK_SIZE, NULL, NST112_TASK_PRIORITY, nst112_task_stack, &nst112_task_tcb,0);
return ESP_OK;
}
#define MS523_CTRL_GPIO GPIO_NUM_48
i2c_master_dev_handle_t MS523_handle;
extern StaticTask_t MS523_task_tcb;
extern StackType_t *MS523_task_stack;
esp_err_t ms523_write_byte(uint8_t reg_addr, uint8_t data) {
uint8_t write_buf[2] = {reg_addr, data};
esp_err_t ret = i2c_master_transmit(MS523_handle, write_buf, sizeof(write_buf), pdMS_TO_TICKS(100));
if (ret != ESP_OK) {
ESP_LOGE("MS523", "Write reg 0x%02X failed: %s", reg_addr, esp_err_to_name(ret));
}
return ret;
}
esp_err_t ms523_read_byte(uint8_t reg_addr, uint8_t *data) {
esp_err_t ret = i2c_master_transmit_receive(MS523_handle, &reg_addr, 1, data, 1, pdMS_TO_TICKS(100));
if (ret == ESP_OK) {
ESP_LOGI("MS523", "Read reg 0x%02X = 0x%02X", reg_addr, *data);
} else {
ESP_LOGE("MS523", "Read reg 0x%02X failed: %s", reg_addr, esp_err_to_name(ret));
}
return ret;
}
#define GPIO_OUTPUT_PIN GPIO_NUM_48 // 以GPIO2为例
void MS523_ctrl_init(void){
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << GPIO_OUTPUT_PIN), // 选择要配置的引脚[reference:5]
.mode = GPIO_MODE_OUTPUT, // 设置为输出模式[reference:6]
.pull_up_en = GPIO_PULLUP_DISABLE, // 禁用内部上拉电阻[reference:7]
.pull_down_en = GPIO_PULLDOWN_DISABLE, // 禁用内部下拉电阻[reference:8]
.intr_type = GPIO_INTR_DISABLE // 禁用中断[reference:9]
};
gpio_config(&io_conf); // 应用配置[reference:10]
// 2. 设置GPIO输出高电平
gpio_set_level(GPIO_OUTPUT_PIN, 1);
vTaskDelay(pdMS_TO_TICKS(1000));
ESP_LOGI("MS523", "当前输出高电平");
}
void ms523_detect_card(void) {
// 1-4:发送寻卡指令
ms523_write_byte(0x0A, 0x80);
ms523_write_byte(0x09, 0x52);
ms523_write_byte(0x0D, 0x87);
ms523_write_byte(0x01, 0x0C);
// 5:轮询等待 RxIRq(超时 50ms)
uint8_t irq = 0;
int timeout_ms = 50;
TickType_t start = xTaskGetTickCount();
while ((xTaskGetTickCount() - start) < pdMS_TO_TICKS(timeout_ms)) {
ms523_read_byte(0x04, &irq);
if (irq & 0x20) {
ESP_LOGI("MS523", "✅ Card detected!");
return; // 检测到卡,退出函数
}
vTaskDelay(pdMS_TO_TICKS(1));
}
// 检查碰撞
uint8_t err;
ms523_read_byte(0x06, &err);
if (err & 0x08) {
ESP_LOGI("MS523", "✅ Card detected (Collision)!");
return;
}
// 无卡
ESP_LOGW("MS523", "No card detected.");
}
void MS523_task(void *param){
while (1)
{
ms523_detect_card();
ESP_LOGW("MS523", "运行中");
vTaskDelay(500);
}
}
esp_err_t MS523_init(void)
{
MS523_ctrl_init();
ESP_LOGI(TAG_IMU, "开始初始化 MS523");
vTaskDelay(pdMS_TO_TICKS(100));
#if !EXAMPLE_USE_TOUCH
#error "BMI_init requires EXAMPLE_USE_TOUCH (SC7U22 shares touch I2C bus)."
#endif
//extern i2c_master_bus_handle_t touch_bus_handle; I2C主总线
i2c_master_bus_config_t i2c_bus_config = {0};
i2c_bus_config.clk_source = I2C_CLK_SRC_DEFAULT;
i2c_bus_config.i2c_port = 0;
i2c_bus_config.scl_io_num = TEST_BOARD_I2C_SCL_PIN;
i2c_bus_config.sda_io_num = TEST_BOARD_I2C_SDA_PIN;
i2c_bus_config.glitch_ignore_cnt = 7;
i2c_bus_config.flags.enable_internal_pullup = true;
//i2c_new_master_bus(&i2c_bus_config, &i2c_bus_handle);
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_config, &touch_bus_handle_MS523));
s_i2c_bus = touch_bus_handle_MS523;
//i2c_scan_devices();
i2c_device_config_t dev_cfg = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = MS523_I2C_ADDR,
.scl_speed_hz = I2C_MASTER_FREQ_HZ,
};
esp_err_t ret = i2c_master_bus_add_device(s_i2c_bus, &dev_cfg, &MS523_handle);
uint8_t reg_addr = 0x37;
uint8_t version = 0;
uint8_t data = 0;
ms523_read_byte(reg_addr,&version);
ms523_write_byte(0x14, 0x83); // 开启 TX1/TX2 天线驱动
ms523_write_byte(0x12, 0x00); // 发送速率 106k
ms523_write_byte(0x13, 0x08); // 接收速率 106k
ms523_write_byte(0x26, 0x48); // 接收增益 48dB
if (ret == ESP_OK) {
ESP_LOGI("MS523", "NFC version:0x%02X", version);
} else {
ESP_LOGE("MS523", "Read version failed: %s", esp_err_to_name(ret));
}
MS523_task_stack = heap_caps_malloc(MS523_TASK_STACK_SIZE, MALLOC_CAP_SPIRAM);
if (MS523_task_stack == NULL)
{
ESP_LOGE("LVGL", "Failed to allocate NST112 task stack");
return ESP_FAIL;
}
//ms523_write_byte(0x0A, 0x80);
//ms523_read_byte(0x0A,&data);
//ms523_write_byte(0x09, 0x52);
//ms523_read_byte(0x09,&data);
//ms523_write_byte(0x0D, 0x87);
//ms523_read_byte(0x0D,&data);
//ms523_write_byte(0x01, 0x0C);
//ms523_read_byte(0x01,&data);
xTaskCreateStaticPinnedToCore(MS523_task, "MS523", MS523_TASK_STACK_SIZE, NULL, MS523_TASK_PRIORITY, MS523_task_stack, &MS523_task_tcb,1);
return ESP_OK;
}
+130
View File
@@ -0,0 +1,130 @@
#include "driver/i2c_master.h"
#ifndef __SC7U22_H_
#define __SC7U22_H_
#define SC7U22_CONFIG 0x1A
#define SC7U22_GYRO_CONFIG 0x42
#define SC7U22_ACCEL_CONFIG 0x40
#define SC7U22_ACCEL_XOUT_H 0x0C
#define SC7U22_ACCEL_XOUT_L 0x0D
#define SC7U22_ACCEL_YOUT_H 0x0E
#define SC7U22_ACCEL_YOUT_L 0x0F
#define SC7U22_ACCEL_ZOUT_H 0x10
#define SC7U22_ACCEL_ZOUT_L 0x11
#define SC7U22_GYRO_XOUT_H 0x12
#define SC7U22_GYRO_XOUT_L 0x13
#define SC7U22_GYRO_YOUT_H 0x14
#define SC7U22_GYRO_YOUT_L 0x15
#define SC7U22_GYRO_ZOUT_H 0x16
#define SC7U22_GYRO_ZOUT_L 0x17
#define SC7U22_WHO_AM_I 0x01
#define SC7U22_I2C_ADDR 0x18
#define NST112_I2C_ADDR 0x48
#define MS523_I2C_ADDR 0x28
#define I2C_MASTER_FREQ_HZ 800000 // kHz 800000
#define I2C_MASTER_FREQ_TEMP_HZ 100000 // kHz 800000
#define GRAVITY_EARTH (9.80665f)
#define ALPHA 0.90f // 高通系数
#define BUM_THRESHOLD 4.0e8f // 颠簸能量阈值(暂定)
#define turn_gyro_threshold 30.0f //急转弯角速度阈值
#define turn_accel_threshold 2.0f //急转弯向心加速度阈值
#define rapid_accel_threshold 3.0f //急加速阈值
#define rapid_decel_threshold -3.0f //急刹车阈值
#define DATA_BUFFER_SIZE 200
#define is_stop_magnitude 0.35f //启动
#define stop_proportion_threshold 0.5f //停止时间比例阈值
#define start_stop_threshold 15 //启停次数阈值
// 驾驶事件冷却屏蔽(10秒内不再触发表情)
#define EVENT_COOLDOWN_MS 10000 // 冷却时间 10 秒
extern float ax,ay,az,gx,gy,gz;
typedef struct
{
int16_t Ax;
int16_t Ay;
int16_t Az;
int16_t Gx;
int16_t Gy;
int16_t Gz;
}SC7U22_DataType;
struct acc_offset
{
float acc_x_offset;
float acc_y_offset;
float acc_z_offset;
float gyr_x_offset;
float gyr_y_offset;
float gyr_z_offset;
};
typedef struct{
float accx_noise;
float accy_noise;
float accz_noise;
float gyrx_noise;
float gyry_noise;
float gyrz_noise;
}noise_datatype;
typedef struct {
float accel_x;
float accel_y;
float accel_z;
float gyro_x;
float gyro_y;
float gyro_z;
int64_t timestamp;
} sensor_data_t;
typedef enum {
EVENT_NONE = 0,
EVENT_SUDDEN_ACCEL, // 急加速
EVENT_SUDDEN_BRAKE, // 急刹车
EVENT_SHARP_LTURN, //急左转弯
EVENT_SHARP_RTURN, //急右转弯
} driving_event_t;
void SC7U22_init(i2c_master_bus_handle_t i2c_bus);
esp_err_t SC7U22_Reg_W(uint8_t reg_addr, uint8_t data);
esp_err_t SC7U22_Reg_R(uint8_t reg_addr, uint8_t *data);
// void SC7U22_DataGet(SC7U22_DataType* SC7U22_DataStucture);
void mySC7_getdata(void); //获得处理后的数据
void SC7U22_Sensor_enable(void);
void noise_threshold_get(void);
void accel_Getoffset(void);
float lsb_to_mps2(int16_t val, float g_range, uint8_t bit_width);
float lsb_to_dps(int16_t val, float dps, uint8_t bit_width);
void bum_energy_get(void);
bool is_bump(void);
void sharp_turn_detect(void);
driving_event_t is_sharp_turn(void);
void start_stop_state(void);
void detect_driving_events(void);
driving_event_t is_rapid_acc_or_dec(void);
bool is_jam(void);
void imu_task(void *param);
i2c_master_bus_handle_t get_i2c_master_bus_handle(void);
esp_err_t BMI_init(void);
esp_err_t NST112_init(void);
esp_err_t MS523_init(void);
#endif
+171
View File
@@ -0,0 +1,171 @@
/*****************************************************include files **************************************************************************/
#include "screen_brightness.h"
#include "LVGL/lvgl_init.h"
#include "ui.h"
/******************************************************* variable declaration ************************************************************************/
/*
* 亮度模型:UI/BLE 的 0~100% 线性映射到面板 PWM [BRIGHTNESS_PWM_MIN, BRIGHTNESS_PWM_MAX]。
* 选 55 作为下限:约 21.5% 全量程,且 (255-55)=200,duty = 55 + percent*2,0~100 与整数 PWM 一一对应、无除法舍入误差。
* 若硬件需其它暗场门限,只改 BRIGHTNESS_PWM_MIN(需保持 SPAN 与映射公式一致或同步改除法)。
*/
#define BRIGHTNESS_PWM_MIN 55
#define BRIGHTNESS_PWM_MAX 255
#define BRIGHTNESS_PWM_SPAN (BRIGHTNESS_PWM_MAX - BRIGHTNESS_PWM_MIN)
// 当前面板 PWM(BRIGHTNESS_PWM_MIN~BRIGHTNESS_PWM_MAX);未读 NVS 前取中间偏亮,避免明显过暗
uint8_t g_current_brightness = (uint8_t)(BRIGHTNESS_PWM_MIN + BRIGHTNESS_PWM_SPAN * 3 / 4);
static bool g_brightness_dirty = false;
static uint8_t g_brightness_commit_pending = 0;
static TimerHandle_t g_brightness_commit_timer = NULL;
/******************************************************* functions declaration ************************************************************************/
void save_brightness_to_nvs(uint8_t value); /* forward declaration for timer callback */
void commit_brightness_if_dirty(void);
extern esp_err_t panel_qspi_amoled_set_brightness(esp_lcd_panel_handle_t panel, uint8_t brightness);
/******************************************************* functions definition ************************************************************************/
// 保存亮度到 NVS
void save_brightness_to_nvs(uint8_t value)
{
nvs_handle_t my_handle;
esp_err_t err = nvs_open("system_cfg", NVS_READWRITE, &my_handle);
if (err == ESP_OK)
{
nvs_set_u8(my_handle, "brightness", value);
nvs_commit(my_handle);
nvs_close(my_handle);
// ESP_LOGI("NVS", "亮度已保存: %d", value);
}
}
static void brightness_commit_timer_cb(TimerHandle_t xTimer)
{
(void)xTimer;
save_brightness_to_nvs(g_brightness_commit_pending);
}
// 从 NVS 读取亮度
void load_brightness_from_nvs(void)
{
nvs_handle_t my_handle;
esp_err_t err = nvs_open("system_cfg", NVS_READONLY, &my_handle);
if (err == ESP_OK)
{
uint8_t saved_val = 0;
if (nvs_get_u8(my_handle, "brightness", &saved_val) == ESP_OK)
{
g_current_brightness = saved_val;
if (g_current_brightness < BRIGHTNESS_PWM_MIN)
g_current_brightness = BRIGHTNESS_PWM_MIN;
if (g_current_brightness > BRIGHTNESS_PWM_MAX)
g_current_brightness = BRIGHTNESS_PWM_MAX;
ESP_LOGI("NVS", "加载保存的亮度: %d", g_current_brightness);
}
nvs_close(my_handle);
}
}
// ================= 对外接口 =================
// 设置亮度 (供 UI 和 BLE 调用)
// 参数 percent: 0-100,0 对应 PWM_MIN、100 对应 PWM_MAX
void set_system_brightness(int percent)
{
if (percent < 0)
percent = 0;
if (percent > 100)
percent = 100;
uint8_t duty = (uint8_t)(BRIGHTNESS_PWM_MIN + ((unsigned)percent * BRIGHTNESS_PWM_SPAN) / 100U);
// 3. 应用到硬件
if (panel_handle)
{
panel_qspi_amoled_set_brightness(panel_handle, duty);
}
// 4. 更新全局变量并保存
if (g_current_brightness != duty)
{
g_current_brightness = duty;
g_brightness_dirty = true;
request_brightness_slider_sync();
}
/* BLE/UI 共用:有变更时经定时器落盘 NVS(避免在回调里同步写) */
commit_brightness_if_dirty();
}
/* 退出亮度界面时调用:只写一次 NVS,避免频繁保存导致不稳定 */
void commit_brightness_if_dirty(void)
{
if (!g_brightness_dirty)
return;
g_brightness_commit_pending = g_current_brightness;
g_brightness_dirty = false;
/* NVS 写放到 timer 任务上下文,避免在 LVGL 点击事件回调里同步写导致崩溃 */
if (g_brightness_commit_timer == NULL)
{
g_brightness_commit_timer = xTimerCreate("brightness_commit_tmr",
pdMS_TO_TICKS(50),
pdFALSE,
NULL,
brightness_commit_timer_cb);
}
if (g_brightness_commit_timer)
{
xTimerStop(g_brightness_commit_timer, 0);
if (xTimerStart(g_brightness_commit_timer, 0) != pdPASS)
{
/* 定时器队列满等异常时仍落盘,避免已清 dirty 却永不写入 NVS */
save_brightness_to_nvs(g_brightness_commit_pending);
}
}
else
{
/* xTimerCreate 失败(如堆不足)时直接写 NVS,否则与蓝牙路径行为不一致 */
save_brightness_to_nvs(g_brightness_commit_pending);
}
}
// 获取当前亮度百分比 (供 UI 初始化滑块与 BLE 查询,0~100)
int get_system_brightness_percent(void)
{
unsigned d = g_current_brightness;
if (d <= BRIGHTNESS_PWM_MIN)
return 0;
if (d >= BRIGHTNESS_PWM_MAX)
return 100;
return (int)((d - BRIGHTNESS_PWM_MIN) * 100U / BRIGHTNESS_PWM_SPAN);
}
// 【核心函数 2】设置是否阻止休眠 (例如 OTA 时调用 set_block_sleep(true))
void set_block_auto_sleep(bool block)
{
block_auto_sleep = block;
if (block)
{
system_keep_alive(); // 阻止时顺便刷新一下时间
}
}
/* 亮度滑动条同步:将设置页滑动条与当前系统亮度对齐 */
void request_brightness_slider_sync(void)
{
extern lv_ui guider_ui;
extern int get_system_brightness_percent(void);
int percent = get_system_brightness_percent();
if (guider_ui.screen_set_slider_light != NULL &&
lv_obj_is_valid(guider_ui.screen_set_slider_light)) {
lv_slider_set_value(guider_ui.screen_set_slider_light, percent, LV_ANIM_OFF);
}
}
+20
View File
@@ -0,0 +1,20 @@
#ifndef __SCREEN_BRIGHTNESS_H__
#define __SCREEN_BRIGHTNESS_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "config.h"
extern uint8_t g_current_brightness;
/** percent: 0~100,与滑动条/BLE 一致;0→PWM 下限、100→PWM 上限(见 screen_brightness.c) */
void set_system_brightness(int percent);
/** 有亮度脏标记时写 NVS(内部 50ms 防抖定时器);供退出设置页等场景也可直接调用 */
void commit_brightness_if_dirty(void);
#ifdef __cplusplus
}
#endif
#endif
+237
View File
@@ -0,0 +1,237 @@
#include "screen_init.h"
#include "config.h"
#include "LVGL/lvgl_init.h"
#include "driver/i2c_master.h"
const char *TAG_LVGL = "lvgl_init";//打印标签
#if EXAMPLE_USE_TOUCH
esp_lcd_touch_handle_t tp = NULL;
bool is_anim_paused = false;
i2c_master_bus_handle_t touch_bus_handle_touch; // 定义触摸屏I2C总线句柄(全局,供BMI270共享)
#endif
static void read_lcd_id(esp_lcd_panel_io_handle_t read_io)
{
uint8_t id_buf[3] = {0};
esp_err_t ret = esp_lcd_panel_io_rx_param(read_io, 0x03000400, id_buf, 3);
if (ret == ESP_OK) {
ESP_LOGI(TAG_LVGL, "LCD ID: %02X %02X %02X", id_buf[0], id_buf[1], id_buf[2]);
} else {
ESP_LOGE(TAG_LVGL, "Read ID failed: %s", esp_err_to_name(ret));
}
uint8_t id1 = 0;
uint8_t id2 = 0;
uint8_t id3 = 0;
esp_err_t ret1 = esp_lcd_panel_io_rx_param(read_io, 0x0300DA00, &id1, 1);
esp_err_t ret2 = esp_lcd_panel_io_rx_param(read_io, 0x0300DB00, &id2, 1);
esp_err_t ret3 = esp_lcd_panel_io_rx_param(read_io, 0x0300DC00, &id3, 1);
if (ret1 == ESP_OK && ret2 == ESP_OK && ret3 == ESP_OK) {
ESP_LOGI(TAG_LVGL, "LCD RDID: %02X %02X %02X", id1, id2, id3);
} else {
ESP_LOGE(TAG_LVGL, "Read RDID failed: %s %s %s",
esp_err_to_name(ret1), esp_err_to_name(ret2), esp_err_to_name(ret3));
}
}
/*
const qspi_amoled_lcd_init_cmd_t lcd_init_cmds[13] = {
{0xFE, (uint8_t[]){0x00}, 1, 0},
{0xC4, (uint8_t[]){0x80}, 1, 0},
{0x35, (uint8_t[]){0x00}, 1, 0},
{0x3A, (uint8_t[]){0x77}, 1, 0},
{0x53, (uint8_t[]){0x20}, 1, 0},
{0x51, (uint8_t[]){0x00}, 1, 0},
{0x63, (uint8_t[]){0xFF}, 1, 0},
{0x2A, (uint8_t[]){0x00, 0x16, 0x01, 0xAF}, 4, 0},
{0x2B, (uint8_t[]){0x00, 0x00, 0x01, 0xF5}, 4, 0},
{0x11, (uint8_t[]){0x00}, 0, 120},
{0x29, (uint8_t[]){0x00}, 0, 60},
{0x58, (uint8_t[]){0x07}, 1, 10},
{0x36, (uint8_t[]){0xA8}, 1, 0},
};
*/
const qspi_amoled_lcd_init_cmd_t lcd_init_cmds[] = {
// 0x11: 退出睡眠模式(Sleep Out),之后延时 60ms
{0x11, (uint8_t[]){0x00}, 0, 60},
// 0x2A: 列地址范围 0~239(0x00 0x00 ~ 0x00 0xEF)
{0x2A, (uint8_t[]){0x00, 0x00, 0x00, 0xEF}, 4, 0},
// 0x2B: 行地址范围 0~295(0x00 0x00 ~ 0x01 0x27)
{0x2B, (uint8_t[]){0x00, 0x00, 0x01, 0x27}, 4, 0},
// 0x44: 撕裂效应行(TE Line)设为 295
{0x44, (uint8_t[]){0x01, 0x27}, 2, 0},
// 0x3A: 像素格式(Pixel Format)0x55(通常为 16bit/18bit)
{0x3A, (uint8_t[]){0x55}, 1, 0},
// 0x35: TE 信号控制,0x00 = 关闭
{0x35, (uint8_t[]){0x00}, 1, 0},
// 0x53: 亮度控制寄存器,设为 0x20
{0x53, (uint8_t[]){0x20}, 1, 0},
// 0x51: 亮度值设为最高 0xFF,之后延时 60ms 再开启显示
{0x51, (uint8_t[]){0xFF}, 1, 60},
// 0x29: 开启显示(Display On),无需额外延时
{0x29, (uint8_t[]){0x00}, 0, 0},
};
// QSPI AMOLED供应商配置
qspi_amoled_vendor_config_t vendor_config =
{
.init_cmds = lcd_init_cmds,
.init_cmds_size = sizeof(lcd_init_cmds) / sizeof(lcd_init_cmds[0]),
.flags =
{
.use_qspi_interface = 1,
},
};
// LCD面板配置
const esp_lcd_panel_dev_config_t panel_config =
{
.reset_gpio_num = EXAMPLE_PIN_NUM_LCD_RST,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER,
.bits_per_pixel = LCD_BIT_PER_PIXEL,
.vendor_config = &vendor_config,
};
extern esp_lcd_panel_io_handle_t io_handle;
void read_co5300_response(void) {
}
esp_err_t app_lcd_init(void)//QSPI初始化
{
esp_err_t ret = ESP_OK;
ESP_LOGI(TAG_LVGL, "Initialize QSPI bus");
// 使用原始的SPI总线最大传输大小,确保稳定传输
const spi_bus_config_t buscfg = QSPI_AMOLED_PANEL_BUS_QSPI_CONFIG(EXAMPLE_PIN_NUM_LCD_PCLK,
EXAMPLE_PIN_NUM_LCD_DATA0,
EXAMPLE_PIN_NUM_LCD_DATA1,
EXAMPLE_PIN_NUM_LCD_DATA2,
EXAMPLE_PIN_NUM_LCD_DATA3,
(EXAMPLE_LCD_H_RES * EXAMPLE_LVGL_BUF_HEIGHT * LCD_BIT_PER_PIXEL / 8) + 64);
ESP_ERROR_CHECK(spi_bus_initialize(LCD_HOST, &buscfg, SPI_DMA_CH_AUTO));
ESP_LOGI(TAG_LVGL, "Install panel IO");
esp_lcd_panel_io_spi_config_t io_config = {
.cs_gpio_num = EXAMPLE_PIN_NUM_LCD_CS,
.dc_gpio_num = -1,
.spi_mode = AMOLED_QSPI_MODE,
.pclk_hz = AMOLED_QSPI_MAX_PCLK,
.trans_queue_depth = 3,
.on_color_trans_done = example_notify_lvgl_flush_ready,
.user_ctx = NULL,
.lcd_cmd_bits = 32,
.lcd_param_bits = 8,
.flags = {
.quad_mode = true,
},
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_HOST, &io_config, &io_handle));
ESP_LOGI(TAG_LVGL, "Install QSPI_AMOLED panel driver");
ESP_ERROR_CHECK(esp_lcd_new_panel_qspi_amoled(io_handle, &panel_config, &panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_set_gap(panel_handle, EXAMPLE_LCD_X_GAP, EXAMPLE_LCD_Y_GAP));
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
/* 初始化时设置亮度为0,避免白光闪烁,等待 UI 加载后渐变亮屏 */
esp_err_t br = panel_qspi_amoled_set_brightness(panel_handle,100);
//uint8_t read_buf[3] = {0}; // RDDID 返回 3 个字节
/*
// 发送读指令 0x04,读取 3 个字节的回应数据
esp_err_t ret2 = esp_lcd_panel_io_rx_param(io_handle, 0x04, read_buf, 3);
if (ret2 == ESP_OK) {
ESP_LOGI("TAG", "读取回应成功!ID1: 0x%02X, ID2: 0x%02X, ID3: 0x%02X",
read_buf[0], read_buf[1], read_buf[2]);
// 根据数据手册,正常回应通常为:33h, 10h, 00h 或 33h, 11h, 00h
} else {
ESP_LOGE("TAG", "读取回应失败:%s", esp_err_to_name(ret2));
}*/
//read_lcd_id(io_handle);
//ESP_LOGI(TAG_LVGL,"br=%d",br);
//uint8_t blue_color[] = {0x00, 0x00, 0xFF};
//esp_err_t brt =panel_qspi_amoled_draw_bitmap(panel_handle, 0, 0, EXAMPLE_LCD_H_RES - 1, EXAMPLE_LCD_V_RES - 1, blue_color);
/*
if (br != ESP_OK)
{
ESP_LOGE(TAG_LVGL, "panel_qspi_amoled_set_brightness failed: %s", esp_err_to_name(br));
}
else
{
ESP_LOGI(TAG_LVGL, "Screen brightness set to 0 (等待 UI 加载后渐变亮屏)");
}
*/
/* Register done callback */
//ESP_ERROR_CHECK(esp_lcd_panel_io_register_event_callbacks(io_handle, &cbs, disp));
return ret;
}
esp_err_t app_touch_init(void)
{
ESP_LOGI(TAG_LVGL, "初始化 I2C 总线 (新驱动模式)");
// 1. 配置 I2C 总线
i2c_master_bus_config_t i2c_bus_config = {
.clk_source = I2C_CLK_SRC_DEFAULT,
.i2c_port = TOUCH_HOST,
.scl_io_num = EXAMPLE_PIN_NUM_TOUCH_SCL,
.sda_io_num = EXAMPLE_PIN_NUM_TOUCH_SDA,
.glitch_ignore_cnt = 7,
.flags.enable_internal_pullup = true,
};
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_config, &touch_bus_handle_touch));
// 2. 配置触摸面板 IO(这里会自动适配新驱动)
esp_lcd_panel_io_handle_t tp_io_handle = NULL;
const esp_lcd_panel_io_i2c_config_t tp_io_config = {
.dev_addr = 0x15, // CST816/CST820 的默认地址,根据实际修改
.on_color_trans_done = NULL,
.user_ctx = NULL,
.control_phase_bytes = 1,
.dc_bit_offset = 0,
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
.scl_speed_hz = 400 * 1000,
.flags = {
.disable_control_phase = 1,
}};
// 使用新总线句柄创建 IO
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(touch_bus_handle_touch, &tp_io_config, &tp_io_handle));
// 3. 配置触摸芯片参数
const esp_lcd_touch_config_t tp_cfg = {
.x_max = EXAMPLE_LCD_H_RES,
.y_max = EXAMPLE_LCD_V_RES,
.rst_gpio_num = EXAMPLE_PIN_NUM_TOUCH_RST,
.int_gpio_num = EXAMPLE_PIN_NUM_TOUCH_INT,
.levels = {
.reset = 0,
.interrupt = 0,
},
.flags = {
.swap_xy = 1,
.mirror_x = 0,
.mirror_y = 1,
},
};
ESP_LOGI(TAG_LVGL, "创建触摸控制器句柄");
return esp_lcd_touch_new_i2c(tp_io_handle, &tp_cfg, &tp);
}
+21
View File
@@ -0,0 +1,21 @@
#ifndef __SCREEN_INIT_H__
#define __SCREEN_INIT_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "config.h"
extern const esp_lcd_panel_dev_config_t panel_config;
extern const char *TAG_LVGL ;
esp_err_t app_lcd_init(void);
#ifdef __cplusplus
}
#endif
#endif
+177
View File
@@ -0,0 +1,177 @@
/*****************************************************include files **************************************************************************/
#include "screen_sleep.h"
#include "ui/generated/events_init.h"
#include "ui/ui.h"
#define AUTO_SLEEP_DEFAULT_MS 0//默认休眠时间
/******************************************************* variable declaration ************************************************************************/
static uint32_t g_auto_sleep_timeout_ms = AUTO_SLEEP_DEFAULT_MS;
// 初始化为当前开机时间
int64_t last_activity_timestamp = 0; //最后一次用户活动的时间戳
static bool g_sleep_timeout_dirty = false; //休眠时间是否被改过但是没有写入NVS
static uint32_t g_sleep_commit_pending_ms = 0; //待提交到NVS的休眠时间缓存值
/* ===== 休眠设置 NVS 提交:离开休眠界面时写一次 ===== */
static TimerHandle_t g_sleep_commit_timer = NULL; //创建了freeRTOS的软件定时器句柄
extern volatile bool is_system_sleeping; //系统睡眠标记
extern bool block_auto_sleep ; //是否阻止自动休眠
extern int current_page_idx ; //当前页面索引
/******************************************************* functions declaration ************************************************************************/
static void save_timeout_to_nvs(uint32_t ms);
static void sleep_commit_timer_cb(TimerHandle_t xTimer);
/******************************************************* functions definition ************************************************************************/
// 【新增】从 NVS 加载休眠时间
void load_timeout_from_nvs(void)//从nvs加载休眠时间
{
nvs_handle_t my_handle;
esp_err_t err = nvs_open("system_cfg", NVS_READONLY, &my_handle);
if (err != ESP_OK)
{
ESP_LOGW("NVS", "打开 system_cfg 失败: %s,休眠时间使用默认 %lu ms",
esp_err_to_name(err), (unsigned long)AUTO_SLEEP_DEFAULT_MS);
g_auto_sleep_timeout_ms = AUTO_SLEEP_DEFAULT_MS;
system_keep_alive();
return;
}
uint32_t saved_val = 0;
if (nvs_get_u32(my_handle, "sleep_ms", &saved_val) == ESP_OK)
{
g_auto_sleep_timeout_ms = saved_val;
ESP_LOGI("NVS", "从 NVS 加载休眠时间: %lu ms", (unsigned long)g_auto_sleep_timeout_ms);
}
else
{
g_auto_sleep_timeout_ms = AUTO_SLEEP_DEFAULT_MS;
ESP_LOGW("NVS", "未找到 sleep_ms,使用默认 %lu ms", (unsigned long)AUTO_SLEEP_DEFAULT_MS);
}
nvs_close(my_handle);
/* 以完成 NVS 加载时刻为空闲计时起点,避免 last_activity 长期为 0 误判超时 */
system_keep_alive();
}
// 【新增】保存设置到 NVS
static void save_timeout_to_nvs(uint32_t ms)//保存休眠时间到nvs
{
nvs_handle_t my_handle;
esp_err_t err = nvs_open("system_cfg", NVS_READWRITE, &my_handle);
if (err == ESP_OK)
{
nvs_set_u32(my_handle, "sleep_ms", ms);
nvs_commit(my_handle); // 提交保存
nvs_close(my_handle);
ESP_LOGI("NVS", "休眠时间已保存: %lu ms", (unsigned long)ms);
}
else
{
ESP_LOGE("NVS", "无法打开 NVS 保存设置");
}
}
void persist_auto_sleep_timeout_ms_now(uint32_t ms)
{
if (g_sleep_commit_timer != NULL)
{
xTimerStop(g_sleep_commit_timer, pdMS_TO_TICKS(100));
}
g_auto_sleep_timeout_ms = ms;
g_sleep_timeout_dirty = false;
g_sleep_commit_pending_ms = ms;
system_keep_alive();
save_timeout_to_nvs(ms);
}
// 2. 【新增】设置超时时间的函数 (提供给按钮调用)
// 输入 0 代表永不休眠 设置自动休眠时间
void set_auto_sleep_timeout(uint32_t ms)
{
if (g_auto_sleep_timeout_ms == ms) {
return;
}
g_auto_sleep_timeout_ms = ms;
system_keep_alive();
ESP_LOGI("PM", "休眠设置更新: %lu ms (mark dirty)", (unsigned long)ms);
g_sleep_timeout_dirty = true;
}
// 【核心函数 1】系统保活:任何操作都应该调用这个函数
void system_keep_alive(void)//更新当前的活动时间戳
{
last_activity_timestamp = esp_timer_get_time() / 1000; // 更新为当前时间(ms)
}
//获取自动休眠时间
uint32_t get_auto_sleep_timeout(void)
{
return g_auto_sleep_timeout_ms;
}
//如果脏标记被设置过之后 发送自动休眠时间
void commit_auto_sleep_timeout_if_dirty(void)
{
if (!g_sleep_timeout_dirty)
return;
g_sleep_commit_pending_ms = g_auto_sleep_timeout_ms;
g_sleep_timeout_dirty = false;
/* 真正的 NVS 写放到 timer 回调里,避免在 LVGL 事件栈上下文里写 NVS */
if (g_sleep_commit_timer == NULL)
{
g_sleep_commit_timer = xTimerCreate("sleep_commit_tmr",
pdMS_TO_TICKS(50),
pdFALSE,
NULL,
sleep_commit_timer_cb);
}
if (g_sleep_commit_timer)
{
if (xTimerStart(g_sleep_commit_timer, 0) != pdPASS)
{
save_timeout_to_nvs(g_sleep_commit_pending_ms);
}
}
else
{
save_timeout_to_nvs(g_sleep_commit_pending_ms);
}
}
static void sleep_commit_timer_cb(TimerHandle_t xTimer)
{
(void)xTimer;
save_timeout_to_nvs(g_sleep_commit_pending_ms);
}
void auto_sleep_checker_cb(lv_timer_t *timer)
{
// 如果已经在睡觉,或者被阻止,或者设置为 0 (永不),直接返回
if (is_system_sleeping || block_auto_sleep || g_auto_sleep_timeout_ms == 0)
{
return;
}
int64_t now = esp_timer_get_time() / 1000;
// 使用变量 g_auto_sleep_timeout_ms 替代原来的 30000
if ((now - last_activity_timestamp) > g_auto_sleep_timeout_ms)
{
ESP_LOGW("PM", "系统空闲超时,自动进入休眠...");
if (current_page_idx != 0)
{
ui_change_page(0, LV_SCR_LOAD_ANIM_FADE_ON);
}
toggle_sleep_mode(true);
}
}
+29
View File
@@ -0,0 +1,29 @@
#ifndef __SCREEN_SLEEP_H__
#define __SCREEN_SLEEP_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "config.h"
extern const esp_lcd_panel_dev_config_t panel_config;
extern const char *TAG_LVGL ;
esp_err_t app_lcd_init(void);
void system_keep_alive(void);
void load_timeout_from_nvs(void);
void set_auto_sleep_timeout(uint32_t ms);
uint32_t get_auto_sleep_timeout(void);
void commit_auto_sleep_timeout_if_dirty(void);
/** 立即把 ms 写入 RAM 与 NVS(停掉延迟提交定时器);用于恢复出厂等须在 esp_restart 前落盘的场景 */
void persist_auto_sleep_timeout_ms_now(uint32_t ms);
void auto_sleep_checker_cb(lv_timer_t *timer);
#ifdef __cplusplus
}
#endif
#endif
+473
View File
@@ -0,0 +1,473 @@
#include "speech_app.h"
#include "speech_init.h"
#include "LVGL/lvgl_init.h"
#include <math.h>
static const char *TAG_LVGL = "sr";
// 音频设备句柄
esp_codec_dev_handle_t record_dev = NULL;
const char *cmd_phoneme[] =
{
"ni hao xiao qi",
"xiao qi zai jian", // 小奇,再见
"xiao qi tan ge zi ta", // 小奇,弹个吉他
"xiao qi bi ge xin", // 小奇,比个心
"xiao qi si kao yi xia zhe ge wen ti", // 小奇,思考一下这个问题
};
const uint32_t cmd_phoneme_number = sizeof(cmd_phoneme)/sizeof(cmd_phoneme[0]);
typedef struct __attribute__((packed)) {
char chunkID[4];
uint32_t chunkSize;
char format[4];
char subchunk1ID[4];
uint32_t subchunk1Size;
uint16_t audioFormat;
uint16_t numChannels;
uint32_t sampleRate;
uint32_t byteRate;
uint16_t blockAlign;
uint16_t bitsPerSample;
char subchunk2ID[4];
uint32_t subchunk2Size;
} wav_header_t;
void write_wav_header(FILE* fp, uint32_t total_data_bytes) {
wav_header_t wav = {
.chunkID = {'R','I','F','F'},
.format = {'W','A','V','E'},
.subchunk1ID = {'f','m','t',' '},
.subchunk1Size = 16,
.audioFormat = 1,
.numChannels = 2, // 你的参数是resdata
.sampleRate = 16000, // 你的参数是16kHz
.bitsPerSample = 16, // 你的参数是16-bit
.subchunk2ID = {'d','a','t','a'},
.subchunk2Size = total_data_bytes,
};
// 计算其他依赖的字段
wav.byteRate = wav.sampleRate * wav.numChannels * wav.bitsPerSample / 8;
wav.blockAlign = wav.numChannels * wav.bitsPerSample / 8;
wav.chunkSize = 36 + wav.subchunk2Size; // 文件总大小 - 8
// 写入文件头
fwrite(&wav, sizeof(wav), 1, fp);
}
int slience_count = 0;
#define GAIN 1.5f // 例如增益 1.5(约 +3.5 dB)
#define MAX_VAL 32767
#define MIN_VAL -32768
/**
* @brief 音频数据采集任务
* 从I2S接口读取音频数据并喂给音频前端处理
*
* @param arg 音频前端数据句柄
*/
void feed_Task(void *arg)
{
ESP_LOGI(TAG_LVGL, "Starting feed task");
esp_afe_sr_data_t *afe_data_ = arg;
// 获取音频前端处理的块大小和通道数
/*
int feed_chunksize = afe_handle->get_feed_chunksize(afe_data_);
int feed_nch = afe_handle->get_feed_channel_num(afe_data_);
ESP_LOGE(TAG_LVGL, "feed_chunksize: %d, feed_nch: %d", feed_chunksize, feed_nch);
*/
/*
FILE* fp = fopen("/extflash/audio1.wav", "wb"); // 或 "/extflash/audio.wav"
if (!fp) {
ESP_LOGW(TAG_MAIN, "WAV文件打开失败,无法写入");
}
uint32_t sampleRate = 16000;
uint16_t numChannels = 2;
uint16_t bitsPerSample = 16;
uint32_t totalSamples = sampleRate * 2; // 2秒总采样数(每声道)
uint32_t totalDataBytes = totalSamples * numChannels * (bitsPerSample / 8);
write_wav_header(fp, totalDataBytes);
int counter = 0;
*/
while (1)
{
size_t bytesIn = 0;
// 从I2S接口读取音频数据
//int ret=esp_codec_dev_read(record_dev, feed_buff, feed_chunksize * feed_nch * sizeof(int16_t)); // 读取音频数据(录音)
// 将左右声道数据求和取半,转为单声道数据
//for (int i = 0; i < feed_chunksize; i++) {
// int left = feed_buff[2*i];
// int right = feed_buff[2*i + 1];
// 求和并取平均,防止数值溢出
// mono_buff[i] = (left + right) >> 1;
//}
//ESP_LOGW(TAG_LVGL, "codec read error: %d", ret);
/*
if(counter < 33) // 采集100次后关闭文件(约10秒音频)
{
fwrite(feed_buff, sizeof(int16_t), feed_chunksize * feed_nch, fp);
counter++;
}
if(counter == 33) // 采集100次后关闭文件(约10秒音频)
{
fclose(fp);
counter++;
FILE* fp = fopen("/extflash/audio2.wav", "wb");
write_wav_header(fp, totalDataBytes);
ESP_LOGW(TAG_MAIN,"WAV 文件已保存:/extflash/audio1.wav\n");
ESP_LOGW(TAG_MAIN,"开始写入第二个文件:/extflash/audio2.wav\n");
}
if(counter > 33 && counter < 66)
{
fwrite(feed_buff, sizeof(int16_t), feed_chunksize * feed_nch, fp);
counter++;
}
if(counter == 66) // 采集100次后关闭文件(约10秒音频)
{
fclose(fp);
ESP_LOGW(TAG_MAIN,"第二个 WAV 文件已保存:/extflash/audio2.wav\n");
counter++;
FILE* fp = fopen("/extflash/audio3.wav", "wb");
write_wav_header(fp, totalDataBytes);
ESP_LOGW(TAG_MAIN,"开始写入第三个文件:/extflash/audio3.wav\n");
}
if(counter > 66 && counter < 99)
{
fwrite(feed_buff, sizeof(int16_t), feed_chunksize * feed_nch, fp);
counter++;
}
if(counter == 99) // 采集100次后关闭文件(约10秒音频)
{
fclose(fp);
ESP_LOGW(TAG_MAIN,"第三个 WAV 文件已保存:/extflash/audio3.wav\n");
counter++;
break; // 结束循环,停止采集
}
*/
// 将音频数据喂给音频前端处理
//ESP_LOGI("I2S RAW", "L:%d, R:%d, L:%d, R:%d,L:%d, R:%d,L:%d, R:%d", feed_buff[0], feed_buff[1], feed_buff[2], feed_buff[3], feed_buff[4], feed_buff[5], feed_buff[6], feed_buff[7]);
//int num_samples = feed_chunksize * feed_nch;
/*
long long sum_squares = 0;
for (int i = 0; i < num_samples; i++) {
sum_squares += (long long)feed_buff[i] * feed_buff[i];
}
double rms = sqrt((double)sum_squares / num_samples);
#define REFERENCE_RMS 750
double db = 20.0 * log10(rms / REFERENCE_RMS);
//ESP_LOGI("AUDIO", "RMS: %.2f, dB: %.2f", rms, db);
*/
//afe_handle->feed(afe_data_, feed_buff);
vTaskDelay(pdMS_TO_TICKS(1));
//ESP_LOGW(TAG_LVGL,"feed_task");
}
// 任务退出时释放资源
vTaskDelete(NULL);
}
extern bool is_ai_recording; // AI语音交互状态标志(外部声明)
extern bool is_ai_create_pcmfile; // AI语音交互生成pcm文件状态标志(外部声明)
extern bool is_ai_end_recording; // AI语音交互结束录音状态标志(外部声明)
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);
}
/**
* @brief 语音检测任务
* 从音频前端获取处理后的数据并进行语音命令识别
*
* @param arg 音频前端数据句柄
*/
extern int64_t init_start_us;
int silence_count = 0;
void detect_Task(void *arg)
{
ESP_LOGI(TAG_LVGL, "Starting detect task");
int feed_chunksize=512;
int feed_nch=2;
// 分配音频数据缓冲区
int16_t *feed_buff = (int16_t *)heap_caps_malloc(feed_chunksize * feed_nch * sizeof(int16_t), MALLOC_CAP_SPIRAM);
assert(feed_buff); // 确保内存分配成功
int16_t *mono_buff = (int16_t *)malloc(feed_chunksize * sizeof(int16_t));
if (mono_buff == NULL) {
ESP_LOGE(TAG_LVGL, "Failed to allocate mono buffer");
return;
}
record_dev = ES7243E_Init();
//esp_afe_sr_data_t *afe_data_ = arg;
// 获取获取数据的块大小并分配缓冲区
//int afe_chunksize = afe_handle->get_fetch_chunksize(afe_data_);
//ESP_LOGW(TAG_LVGL, "afe_chunksize: %d", afe_chunksize);
/*
FILE* fp = fopen("/extflash/audio1res.wav", "wb");
if (!fp) {
ESP_LOGW(TAG_MAIN, "WAV文件打开失败,无法写入");
}
uint32_t sampleRate = 16000;
uint16_t numChannels = 1;
uint16_t bitsPerSample = 16;
uint32_t totalSamples = sampleRate * 3.2; // 3.2秒总采样数(每声道)
uint32_t totalDataBytes = totalSamples * numChannels * (bitsPerSample / 8);
write_wav_header(fp, totalDataBytes);
int counter = 0;
*/
ESP_LOGW(TAG_LVGL, "开始识别");
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);
while (1)
{
//对话开启,启动PCM文件创建
if(is_ai_create_pcmfile == true){
FILE* fp = fopen("/extflash/audio_ai.pcm", "wb");
if (!fp) {
ESP_LOGW(TAG_MAIN, "PCM文件打开失败,无法写入,语音对话阶段一失败");
}
uint32_t sampleRate = 16000;
uint16_t numChannels = 2;
uint16_t bitsPerSample = 16;
uint32_t totalSamples = sampleRate * 2; // 2秒总采样数(每声道)
uint32_t totalDataBytes = 0; // PCM文件不需要头部,数据大小可以动态增长,需要最后填入
write_wav_header(fp, totalDataBytes);
is_ai_create_pcmfile = false;
ESP_LOGW(TAG_LVGL, "创建PCM基础格式成功");
is_ai_recording = true;//进入录音状态
fclose(fp);
}
int ret=esp_codec_dev_read(record_dev, feed_buff, feed_chunksize * feed_nch * sizeof(int16_t)); // 读取音频数据(录音)
//分贝检测
//音频录音
uint32_t totalDataBytes = 0;
if(is_ai_recording == true){
FILE* fp = fopen("/extflash/audio_ai.pcm", "ab");
if (!fp) {
ESP_LOGW(TAG_MAIN, "PCM文件录音失败,无法写入,语音对话阶段二失败");
}
fwrite(feed_buff, sizeof(int16_t), feed_chunksize * feed_nch, fp);
totalDataBytes += feed_chunksize * feed_nch * sizeof(int16_t);
//结束录音状态
fclose(fp);
//RMS对话结束检测函数
int num_samples = feed_chunksize * feed_nch;
long long sum_squares = 0;
for (int i = 0; i < num_samples; i++) {
sum_squares += (long long)feed_buff[i] * feed_buff[i];
}
double rms = sqrt((double)sum_squares / num_samples);
#define REFERENCE_RMS 750
double db = 20.0 * log10(rms / REFERENCE_RMS);
ESP_LOGI("AUDIO", "RMS: %.2f, dB: %.2f", rms, db);
if(rms<400){
silence_count++;
}
if(silence_count==53){
is_ai_recording = false;
is_ai_end_recording = true;
silence_count = 0;
}
}
// 录制结束,更新 PCM 文件头中的数据大小
if(is_ai_end_recording == true){
FILE* fp = fopen("/extflash/audio_ai.pcm", "ab");
if (!fp) {
ESP_LOGW(TAG_MAIN, "PCM文件录音结束失败,语音对话阶段三失败");
}
fseek(fp, 0, SEEK_SET); // 定位到文件开头
write_wav_header(fp, totalDataBytes); // 更新 WAV 头部中的数据
fclose(fp);
get_file_size("/extflash/audio_ai.pcm");
ESP_LOGW(TAG_LVGL, "PCM文件录制完成并更新头部成功");
change_task_post_action(0X000C);
is_ai_end_recording = false; //重置结束录音状态
}
// 将左右声道数据求和取半,转为单声道数据
for (int i = 0; i < feed_chunksize; i++) {
int left = feed_buff[2*i];
int right = feed_buff[2*i + 1];
// 求和并取平均,防止数值溢出
int avg = (left + right) >> 1; // 取平均
mono_buff[i] = (int)avg;
}
ESP_LOGI("I2S RAW", "L:%d, R:%d, L:%d, R:%d,L:%d, R:%d,L:%d, R:%d", feed_buff[0], feed_buff[1], feed_buff[2], feed_buff[3], feed_buff[4], feed_buff[5], feed_buff[6], feed_buff[7]);
ESP_LOGI("I2S RAW", "1:%d, 2:%d, 3:%d, 4:%d,5:%d, 6:%d,7:%d, 8:%d", mono_buff[0], mono_buff[1], mono_buff[2], mono_buff[3], mono_buff[4], mono_buff[5], mono_buff[6], mono_buff[7]);
// 从音频前端获取处理后的数据
//ESP_LOGW(TAG_LVGL, "detect_task");
//afe_fetch_result_t *res = afe_handle->fetch(afe_data_);
//int sample_count = res->data_size / sizeof(int16_t);
// 检查获取结果是否有效
//if (!res || res->ret_value == ESP_FAIL)
//{
// ESP_LOGE(TAG_LVGL, "获取音频数据失败!");
// res = afe_handle->fetch(afe_data_);
// break;
//}
/*
if(counter < 100) // 采集100次后关闭文件(约10秒音频)
{
fwrite(res->data, sizeof(int16_t), sample_count, fp);
counter++;
}
if(counter == 100) // 采集100次后关闭文件(约10秒音频)
{
fclose(fp);
counter++;
FILE* fp = fopen("/extflash/audio2res.wav", "wb");
write_wav_header(fp, totalDataBytes);
ESP_LOGW(TAG_MAIN,"WAV 文件已保存:/extflash/audio1res.wav\n");
ESP_LOGW(TAG_MAIN,"开始写入第二个文件:/extflash/audio2res.wav\n");
}
if(counter > 100 && counter < 200)
{
fwrite(res->data, sizeof(int16_t), sample_count, fp);
counter++;
}
if(counter == 200) // 采集100次后关闭文件(约10秒音频)
{
fclose(fp);
ESP_LOGW(TAG_MAIN,"第二个 WAV 文件已保存:/extflash/audio2res.wav\n");
counter++;
FILE* fp = fopen("/extflash/audio3res.wav", "wb");
write_wav_header(fp, totalDataBytes);
ESP_LOGW(TAG_MAIN,"开始写入第三个文件:/extflash/audio3res.wav\n");
}
if(counter > 200 && counter < 300)
{
fwrite(res->data, sizeof(int16_t), sample_count, fp);
counter++;
}
if(counter == 300) // 采集100次后关闭文件(约10秒音频)
{
fclose(fp);
ESP_LOGW(TAG_MAIN,"第三个 WAV 文件已保存:/extflash/audio3res.wav\n");
counter++;
break; // 结束循环,停止采集
}
// 获取并处理 VAD 状态
if (res->vad_state == 1) {
// 检测到语音活动
ESP_LOGI(TAG_LVGL, "VAD: Speech detected.");
} else if (res->vad_state == 0) {
// 当前为静音或噪声
ESP_LOGE(TAG_LVGL, "VAD: Silence.");
}
*/
/*
int sample_count = res->data_size / sizeof(int16_t);
int16_t *samples = res->data;
ESP_LOGI(TAG_LVGL, "data[0~5] = %d, %d, %d, %d, %d, %d",
samples[0], samples[1], samples[2],
samples[3], samples[4], samples[5]);
int16_t *samples2 = res->raw_data;
ESP_LOGI(TAG_LVGL, "raw_data[0~5] = %d, %d, %d, %d, %d, %d",
samples2[0], samples2[1], samples2[2],
samples2[3], samples2[4], samples2[5]);
*/
//ESP_LOGI(TAG_LVGL, "data_volume = %.2f dB, ringbuff_free_pct = %.2f%%",
// res->data_volume, res->ringbuff_free_pct * 100);*/
// 直接进行语音命令识别,跳过唤醒词检测
// 使用多网络模型进行语音命令检测
esp_mn_state_t mn_state = ESP_MN_STATE_DETECTING;
mn_state = multinet->detect(model_data, mono_buff);
// 如果仍在检测中,继续循环
if (ESP_MN_STATE_DETECTING == mn_state)
{
//ESP_LOGI(TAG_LVGL, "语音识别中...");
continue;
}
// 处理超时情况
if (ESP_MN_STATE_TIMEOUT == mn_state)
{
ESP_LOGW(TAG_LVGL, "语音识别超时");
sr_result_t result =
{
.state = mn_state,
.command_id = 0,
};
// xQueueSend(g_result_que, &result, 10);
continue;
}
// 处理识别到命令的情况
if (ESP_MN_STATE_DETECTED == mn_state)
{
// 获取识别结果
esp_mn_results_t *mn_result = multinet->get_results(model_data);
for (int i = 0; i < mn_result->num; i++)
{
int prob_permille = (int)(mn_result->prob[i] * 1000.0f);
ESP_LOGI(TAG_LVGL, "TOP %d, command_id: %d, phrase_id: %d, prob: %d.%03d",
i + 1, mn_result->command_id[i], mn_result->phrase_id[i],
prob_permille / 1000, abs(prob_permille % 1000));
}
int sr_command_id = mn_result->command_id[0];
// 在这里添加指令对应的中文打印
switch (sr_command_id)
{
case 0: // "ni hao xiao qi"
ESP_LOGE(TAG_LVGL, "识别到指令:你好小奇");
change_task_post_action(0X000B);
is_ai_create_pcmfile = true;
//ret =lv_obj_send_event(ui_Screen_Lottie, LV_EVENT_KEY, (void *)LV_KEY_ENTER);
ESP_LOGW(TAG_LVGL, "RET: %d", ret);
break;
case 1: // "huan ge feng ge"
ESP_LOGE(TAG_LVGL, "识别到指令:小奇,再见");
change_task_post_action(0X000C);
//ret =lv_obj_send_event(ui_Screen_Lottie, LV_EVENT_KEY, (void *)LV_KEY_NEXT);
ESP_LOGW(TAG_LVGL, "RET: %d", ret);
break;
case 2: // "yao tou huang nao"
ESP_LOGE(TAG_LVGL, "识别到指令:小奇,弹个吉他");
break;
case 3: // "yao tou huang nao"
ESP_LOGE(TAG_LVGL, "识别到指令:小奇,比个心");
break;
case 4: // "yao tou huang nao"
ESP_LOGE(TAG_LVGL, "识别到指令:小奇,思考一下这个问题");
break;
default:
ESP_LOGE(TAG_LVGL, "识别到未知指令:%d", sr_command_id);
break;
}
// 组织识别结果
sr_result_t result = {
.state = mn_state,
.command_id = sr_command_id,
};
}
vTaskDelay(pdMS_TO_TICKS(1));
}
if (feed_buff)
{
free(feed_buff);
feed_buff = NULL;
}
if (mono_buff)
{
free(mono_buff);
mono_buff = NULL;
}
vTaskDelete(NULL);
}
+30
View File
@@ -0,0 +1,30 @@
#ifndef _SPEECH_APP_H_
#define _SPEECH_APP_H_
#ifdef __cplusplus
extern "C" {
#endif
// #include "_stdint.h"
#include "config.h"
extern const uint32_t cmd_phoneme_number;
/* 拼音命令词表(与 multinet 命令短语一一对应) */
extern const char *cmd_phoneme[];
// 音频设备句柄
extern esp_codec_dev_handle_t record_dev;
void feed_Task(void *arg);
void detect_Task(void *arg);
extern lv_obj_t *ui_Screen_Lottie; // 专门用于播放动画的独立屏幕
#ifdef __cplusplus
}
#endif
#endif /* _SPEECH_APP_H_ */
+99
View File
@@ -0,0 +1,99 @@
#include "speech_init.h"
#include "speech_app.h"
#include "main.h"
srmodel_list_t *models = NULL;
afe_config_t *afe_config = NULL;
esp_afe_sr_iface_t *afe_handle = NULL;
esp_afe_sr_data_t *afe_data = NULL;
const esp_mn_iface_t *multinet = NULL;
model_iface_data_t *model_data = NULL;
char *mn_name = NULL;
static const char *TAG_LVGL = "sr";
esp_err_t app_sr_init(void)
{
//record_dev = ES7243E_Init();
/* 配置音频前端参数 */
// srmodel_list_t *models = esp_srmodel_init("model");
/*
models = esp_srmodel_init("model");
// afe_config_t *afe_config = afe_config_init("MM", models, AFE_TYPE_SR, AFE_MODE_LOW_COST);
afe_config = afe_config_init("MM", models, AFE_TYPE_SR, AFE_MODE_LOW_COST);
// afe_config>memory_alloc_mode = AFE_MEMORY_ALLOC_MORE_PSRAM;
afe_config->wakenet_model_name = NULL;
afe_config->aec_init = false; // 关闭回声消除(根据需求调整)
afe_config->vad_init = false; // 启用VAD(语音活动检测)
afe_config->pcm_config.total_ch_num = 2; // 总通道数 = 2
afe_config->pcm_config.mic_num = 2; // 麦克风通道数 = 2
afe_config->pcm_config.ref_num = 0; // 回放参考通道数 = 0
afe_config->afe_ringbuf_size = 20;
//afe_config->vad_mode = VAD_MODE_0;
afe_config->agc_init = false; // 关闭自动增益控制(根据需求调整)
afe_config->ns_init = false;
afe_config->afe_linear_gain = 2.0;
ESP_LOGE(TAG_LVGL, "配置前 afe_ringbuf_size = %d", afe_config->afe_ringbuf_size);
afe_handle = esp_afe_handle_from_config(afe_config);
// esp_afe_sr_data_t *afe_data = afe_handle->create_from_config(afe_config);
afe_data = afe_handle->create_from_config(afe_config);
*/
// char *mn_name = esp_srmodel_filter(models, ESP_MN_CHINESE, NULL);
srmodel_list_t *models = esp_srmodel_init("model");
if (models == NULL) {
ESP_LOGE(TAG_LVGL, "未找到 model 分区,跳过语音模型");
return ESP_OK;
}
mn_name = esp_srmodel_filter(models, ESP_MN_CHINESE, NULL);
if (NULL == mn_name)
{
ESP_LOGE(TAG_LVGL, "未找到多网络模型");
return ESP_OK;
}
multinet = esp_mn_handle_from_name(mn_name);
model_data = multinet->create(mn_name, 30000); // 设置语音识别超时时间
/* 配置语音命令 */
esp_mn_commands_clear(); // 清除现有命令
// 添加所有语音命令
for (int i = 0; i < cmd_phoneme_number; i++)
{
esp_mn_commands_add(i, (char *)cmd_phoneme[i]);
}
esp_mn_commands_update(); // 更新命令词列表
esp_mn_commands_print();
multinet->print_active_speech_commands(model_data); // 输出目前激活的命令词
afe_config_free(afe_config); /* 释放配置内存 */
feed_task_stack = heap_caps_malloc(FEED_TASK_STACK_SIZE, MALLOC_CAP_SPIRAM);
detect_task_stack = heap_caps_malloc(DETECT_TASK_STACK_SIZE, MALLOC_CAP_SPIRAM);
if (feed_task_stack == NULL || detect_task_stack == NULL)
{
ESP_LOGE(TAG_LVGL, "PSRAM 内存分配失败");
return ESP_OK;
}
//xTaskCreateStaticPinnedToCore(feed_Task, "feed", FEED_TASK_STACK_SIZE, (void *)afe_data, FEED_TASK_PRIORITY, feed_task_stack, &feed_task_tcb, 1);
xTaskCreateStaticPinnedToCore(detect_Task, "detect", DETECT_TASK_STACK_SIZE, (void *)afe_data, DETECT_TASK_PRIORITY, detect_task_stack, &detect_task_tcb, 1);
return ESP_OK;
}
+27
View File
@@ -0,0 +1,27 @@
#ifndef _SPEECH_INIT_H_
#define _SPEECH_INIT_H_
#ifdef __cplusplus
extern "C" {
#endif
#include "esp_err.h"
#include "config.h"
extern srmodel_list_t *models;
extern afe_config_t *afe_config;
extern esp_afe_sr_iface_t *afe_handle;
extern esp_afe_sr_data_t *afe_data;
extern const esp_mn_iface_t *multinet;
extern model_iface_data_t *model_data;
extern char *mn_name;
// /** 语音模块初始化(模型/命令词/任务等) */
// esp_err_t speech_init(void);
// /** 语音模块反初始化(释放模型/队列/任务等) */
// esp_err_t speech_deinit(void);
#ifdef __cplusplus
}
#endif
#endif /* _SPEECH_INIT_H_ */
+15
View File
@@ -0,0 +1,15 @@
#include "system.h"
SYSTEM_INFO system_info =
{
.s_emoji_pack =
{
.using_pack = 0,
}
};
void get_system_info(void)
{
system_info.s_emoji_pack.using_pack = '0';//表示正在使用默认的表情包
}
+17
View File
@@ -0,0 +1,17 @@
#ifndef __SYSTEM_H__
#define __SYSTEM_H__
typedef struct
{
char using_pack;//正在使用的表情包系列
}EMOJI_PACK;
typedef struct
{
EMOJI_PACK s_emoji_pack;
}SYSTEM_INFO;
extern SYSTEM_INFO system_info;
#endif
+332
View File
@@ -0,0 +1,332 @@
#include "UI_DISPATCH/almanac_service.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "cjson.h"
#include "esp_log.h"
static almanac_provider_t s_provider = {0};
static const char *TAG = "almanac_service";
/* main/CMakeLists.txt: EMBED_TXTFILES "UI_DISPATCH/yiji_data_2026_2030.json" */
extern const char yiji_json_start[] asm("_binary_yiji_data_2026_2030_json_start");
extern const char yiji_json_end[] asm("_binary_yiji_data_2026_2030_json_end");
static cJSON *s_yiji_root = NULL;
static bool s_yiji_load_attempted = false;
/* 1900~2099 农历数据,bit 编码规则沿用常见农历算法实现 */
static const uint32_t LUNAR_INFO[] = {
0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2,
0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977,
0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970,
0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950,
0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557,
0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5d0, 0x14573, 0x052d0, 0x0a9a8, 0x0e950, 0x06aa0,
0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0,
0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6,
0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570,
0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x05ac0, 0x0ab60, 0x096d5, 0x092e0,
0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5,
0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930,
0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530,
0x05aa0, 0x076a3, 0x096d0, 0x04bd7, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45,
0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0,
0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06aa0, 0x1a6c4, 0x0aae0,
0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4,
0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0,
0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160,
0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252,
};
typedef struct {
int year;
int month;
int day;
bool is_leap_month;
} lunar_date_t;
static int64_t days_from_civil(int y, unsigned m, unsigned d)
{
y -= (m <= 2);
{
const int era = (y >= 0 ? y : y - 399) / 400;
const unsigned yoe = (unsigned)(y - era * 400);
const unsigned doy = (153 * (m + (m > 2 ? (unsigned)-3 : 9)) + 2) / 5 + d - 1;
const unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
return era * 146097 + (int64_t)doe - 719468;
}
}
static int lunar_leap_month(int year)
{
return (int)(LUNAR_INFO[year - 1900] & 0x0f);
}
static int lunar_leap_days(int year)
{
const int lm = lunar_leap_month(year);
if (lm == 0) {
return 0;
}
return (LUNAR_INFO[year - 1900] & 0x10000) ? 30 : 29;
}
static int lunar_month_days(int year, int month)
{
return (LUNAR_INFO[year - 1900] & (0x10000 >> month)) ? 30 : 29;
}
static int lunar_year_days(int year)
{
int sum = 348;
uint32_t mask = 0;
for (mask = 0x8000; mask > 0x8; mask >>= 1) {
if (LUNAR_INFO[year - 1900] & mask) {
sum += 1;
}
}
return sum + lunar_leap_days(year);
}
static bool solar_to_lunar(int year, int month, int day, lunar_date_t *out)
{
int64_t offset = 0;
int lunar_year = 0;
int year_days = 0;
int leap = 0;
bool in_leap = false;
int lunar_month = 0;
int month_days = 0;
if (out == NULL) {
return false;
}
if (year < 1900 || year > 2099 || month < 1 || month > 12 || day < 1 || day > 31) {
return false;
}
offset = days_from_civil(year, (unsigned)month, (unsigned)day) - days_from_civil(1900, 1, 31);
if (offset < 0) {
return false;
}
lunar_year = 1900;
year_days = lunar_year_days(lunar_year);
while (lunar_year < 2100 && offset >= year_days) {
offset -= year_days;
lunar_year++;
if (lunar_year <= 2099) {
year_days = lunar_year_days(lunar_year);
}
}
if (lunar_year > 2099) {
return false;
}
leap = lunar_leap_month(lunar_year);
lunar_month = 1;
while (lunar_month <= 12) {
if (leap > 0 && lunar_month == (leap + 1) && !in_leap) {
lunar_month -= 1;
in_leap = true;
month_days = lunar_leap_days(lunar_year);
} else {
month_days = lunar_month_days(lunar_year, lunar_month);
}
if (offset < month_days) {
break;
}
offset -= month_days;
if (in_leap && lunar_month == leap) {
in_leap = false;
}
lunar_month++;
}
out->year = lunar_year;
out->month = lunar_month;
out->day = (int)offset + 1;
out->is_leap_month = in_leap;
return true;
}
static const char *lunar_month_to_text(int month)
{
static const char *month_text[] = {
"", "正", "二", "三", "四", "五", "六", "七", "八", "九", "十", "冬", "腊",
};
if (month < 1 || month > 12) {
return "";
}
return month_text[month];
}
static const char *lunar_day_to_text(int day)
{
static const char *day_text[] = {
"", "初一", "初二", "初三", "初四", "初五", "初六", "初七", "初八", "初九", "初十",
"十一", "十二", "十三", "十四", "十五", "十六", "十七", "十八", "十九", "二十",
"廿一", "廿二", "廿三", "廿四", "廿五", "廿六", "廿七", "廿八", "廿九", "三十",
};
if (day < 1 || day > 30) {
return "";
}
return day_text[day];
}
static bool almanac_builtin_get(uint16_t year, uint8_t month, uint8_t day, almanac_entry_t *out)
{
lunar_date_t lunar = {0};
cJSON *item = NULL;
cJSON *node_year = NULL;
cJSON *node_month = NULL;
cJSON *node_day = NULL;
cJSON *yi = NULL;
cJSON *ji = NULL;
int n = 0;
int i = 0;
size_t used = 0;
const char *word = NULL;
if (out == NULL) {
return false;
}
memset(out, 0, sizeof(*out));
if (solar_to_lunar((int)year, (int)month, (int)day, &lunar)) {
snprintf(out->lunar, sizeof(out->lunar), "%s%s月%s",
lunar.is_leap_month ? "闰" : "",
lunar_month_to_text(lunar.month),
lunar_day_to_text(lunar.day));
}
/* 内置 JSON 懒加载:只解析一次,失败后不重复尝试 */
if (!s_yiji_load_attempted) {
const char *json_start = yiji_json_start;
const char *json_end = yiji_json_end;
s_yiji_load_attempted = true;
if (json_end > json_start) {
const size_t json_len = (size_t)(json_end - json_start);
char *json_buf = (char *)malloc(json_len + 1);
if (json_buf != NULL) {
memcpy(json_buf, json_start, json_len);
json_buf[json_len] = '\0';
s_yiji_root = cJSON_Parse(json_buf);
free(json_buf);
if (s_yiji_root == NULL) {
ESP_LOGW(TAG, "parse yiji_data json failed");
}
} else {
ESP_LOGW(TAG, "malloc yiji_data buffer failed");
}
}
}
if (s_yiji_root != NULL && cJSON_IsArray(s_yiji_root)) {
n = cJSON_GetArraySize(s_yiji_root);
for (i = 0; i < n; i++) {
item = cJSON_GetArrayItem(s_yiji_root, i);
if (!cJSON_IsObject(item)) {
continue;
}
node_year = cJSON_GetObjectItem(item, "lunar_year");
node_month = cJSON_GetObjectItem(item, "lunar_month");
node_day = cJSON_GetObjectItem(item, "lunar_day");
if (!cJSON_IsNumber(node_year) || !cJSON_IsNumber(node_month) || !cJSON_IsNumber(node_day)) {
continue;
}
/*
* 约定:JSON 中 lunar_year/lunar_month/lunar_day 作为“查表日期键”使用,
* 这里按传入公历日期(year/month/day)直接匹配。
* 这样修改 JSON 的 2026-09-17 会直接影响界面显示。
*/
if (node_year->valueint != (int)year ||
node_month->valueint != (int)month ||
node_day->valueint != (int)day) {
continue;
}
yi = cJSON_GetObjectItem(item, "yi");
ji = cJSON_GetObjectItem(item, "ji");
used = 0;
if (cJSON_IsArray(yi)) {
const int yi_n = cJSON_GetArraySize(yi) < 4 ? cJSON_GetArraySize(yi) : 4;
for (int k = 0; k < yi_n; k++) {
cJSON *w = cJSON_GetArrayItem(yi, k);
if (!cJSON_IsString(w) || w->valuestring == NULL) {
continue;
}
word = w->valuestring;
if (used > 0 && used < sizeof(out->dos) - 1) {
out->dos[used++] = ' ';
out->dos[used] = '\0';
}
if (used < sizeof(out->dos) - 1) {
used += (size_t)snprintf(out->dos + used, sizeof(out->dos) - used, "%s", word);
if (used >= sizeof(out->dos)) {
used = sizeof(out->dos) - 1;
}
}
}
}
used = 0;
if (cJSON_IsArray(ji)) {
const int ji_n = cJSON_GetArraySize(ji) < 4 ? cJSON_GetArraySize(ji) : 4;
for (int k = 0; k < ji_n; k++) {
cJSON *w = cJSON_GetArrayItem(ji, k);
if (!cJSON_IsString(w) || w->valuestring == NULL) {
continue;
}
word = w->valuestring;
if (used > 0 && used < sizeof(out->donts) - 1) {
out->donts[used++] = ' ';
out->donts[used] = '\0';
}
if (used < sizeof(out->donts) - 1) {
used += (size_t)snprintf(out->donts + used, sizeof(out->donts) - used, "%s", word);
if (used >= sizeof(out->donts)) {
used = sizeof(out->donts) - 1;
}
}
}
}
break;
}
}
if (out->dos[0] == '\0') {
snprintf(out->dos, sizeof(out->dos), "-- -- -- --");
}
if (out->donts[0] == '\0') {
snprintf(out->donts, sizeof(out->donts), "-- -- -- --");
}
return true;
}
void almanac_service_set_provider(const almanac_provider_t *provider)
{
if (provider == NULL || provider->get == NULL) {
s_provider.get = NULL;
s_provider.ctx = NULL;
return;
}
s_provider = *provider;
}
bool almanac_service_get(uint16_t year, uint8_t month, uint8_t day, almanac_entry_t *out)
{
if (out == NULL) {
return false;
}
if (s_provider.get != NULL && s_provider.get(year, month, day, out, s_provider.ctx)) {
return true;
}
return almanac_builtin_get(year, month, day, out);
}
+26
View File
@@ -0,0 +1,26 @@
#ifndef ALMANAC_SERVICE_H
#define ALMANAC_SERVICE_H
#include <stdbool.h>
#include <stdint.h>
typedef struct {
char lunar[24];
char dos[64];
char donts[64];
} almanac_entry_t;
typedef bool (*almanac_get_fn_t)(uint16_t year, uint8_t month, uint8_t day, almanac_entry_t *out, void *ctx);
typedef struct {
almanac_get_fn_t get;
void *ctx;
} almanac_provider_t;
/* 设置外部数据提供者(例如 JSON 查表);传 NULL 则回退内置实现 */
void almanac_service_set_provider(const almanac_provider_t *provider);
/* 按公历日期获取农历+宜忌;无外部 provider 时使用内置回退逻辑 */
bool almanac_service_get(uint16_t year, uint8_t month, uint8_t day, almanac_entry_t *out);
#endif /* ALMANAC_SERVICE_H */
+738
View File
@@ -0,0 +1,738 @@
#include "UI_DISPATCH/ui_dispatch.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "LVGL/lvgl_init.h"
#include "gui_guider.h"
#include "BLE/ble_send.h"
#include "SCREEN/screen_brightness.h"
#include "UI_DISPATCH/almanac_service.h"
#define UI_DISPATCH_QUEUE_LEN 16
#define UI_DISPATCH_TASK_STACK_SIZE 4096
#define UI_DISPATCH_TASK_PRIORITY 6
/* Weather keyword constants - UNUSED: BLE uses direct path via ble_data_ansy.c */
#if 0
static const char WEATHER_HAIL[] = {0xe5, 0x86, 0xb0, 0xe9, 0x9b, 0xb9, 0x00}; /* 冰雹 */
static const char WEATHER_SLEET[] = {0xe9, 0x9b, 0xa8, 0xe5, 0xa4, 0xb9, 0xe9, 0x9b, 0xaa, 0x00}; /* 雨夹雪 */
static const char WEATHER_FREEZING_RAIN[] = {0xe5, 0x86, 0xbb, 0xe9, 0x9b, 0xa8, 0x00}; /* 冻雨 */
static const char WEATHER_SNOW[] = {0xe9, 0x9b, 0xaa, 0x00}; /* 雪 */
static const char WEATHER_GRAUPEL[] = {0xe9, 0x9c, 0xb0, 0x00}; /* 霰 */
static const char WEATHER_THUNDER[] = {0xe9, 0x9b, 0xb7, 0x00}; /* 雷 */
static const char WEATHER_RAIN[] = {0xe9, 0x9b, 0xa8, 0x00}; /* 雨 */
static const char WEATHER_FOG[] = {0xe9, 0x9b, 0xbe, 0x00}; /* 雾 */
static const char WEATHER_HAZE[] = {0xe9, 0x9c, 0xbe, 0x00}; /* 霾 */
static const char WEATHER_DUST[] = {0xe5, 0xb0, 0x98, 0x00}; /* 尘 */
static const char WEATHER_SAND[] = {0xe6, 0xb2, 0x99, 0x00}; /* 沙 */
static const char WEATHER_FLOATING_DUST[] = {0xe6, 0xb5, 0xae, 0xe5, 0xb0, 0x98, 0x00}; /* 浮尘 */
static const char WEATHER_CLOUD[] = {0xe4, 0xba, 0x91, 0x00}; /* 云 */
static const char WEATHER_OVERCAST[] = {0xe9, 0x98, 0xb4, 0x00}; /* 阴 */
/* Weather text display constants */
static const char WEATHER_TEXT_RAIN[] = {0xe9, 0x9b, 0xa8, 0x00}; /* 雨 */
static const char WEATHER_TEXT_THUNDER[] = {0xe9, 0x9b, 0xb7, 0xe9, 0x98, 0xb5, 0xe9, 0x9b, 0xa8, 0x00}; /* 雷阵雨 */
static const char WEATHER_TEXT_SNOW[] = {0xe9, 0x9b, 0xaa, 0x00}; /* 雪 */
static const char WEATHER_TEXT_SLEET[] = {0xe9, 0x9b, 0xa8, 0xe5, 0xa4, 0xb9, 0xe9, 0x9b, 0xaa, 0x00}; /* 雨夹雪 */
static const char WEATHER_TEXT_HAIL[] = {0xe5, 0x86, 0xb0, 0xe9, 0x9b, 0xb9, 0x00}; /* 冰雹 */
static const char WEATHER_TEXT_FOG[] = {0xe9, 0x9b, 0xbe, 0x00}; /* 雾 */
static const char WEATHER_TEXT_CLOUDY[] = {0xe5, 0xa4, 0x9a, 0xe4, 0xba, 0x91, 0x00}; /* 多云 */
static const char WEATHER_TEXT_SUNNY[] = {0xe6, 0x99, 0xb4, 0x00}; /* 晴 */
#endif
typedef enum {
UI_DISPATCH_CMD_SET_BRIGHTNESS = 0,
UI_DISPATCH_CMD_SET_DATE,
UI_DISPATCH_CMD_SET_TIME,
// UI_DISPATCH_CMD_SET_WEATHER, // UNUSED: BLE uses direct path
} ui_dispatch_cmd_t;
typedef struct {
ui_dispatch_cmd_t type;
union {
struct {
uint8_t brightness_percent;
} brightness;
struct {
uint16_t year;
uint8_t month;
uint8_t day;
uint8_t week;
} date;
struct {
uint8_t hour;
uint8_t minute;
uint8_t second;
} time;
#if 0 // UNUSED: BLE uses direct path
struct {
uint8_t timing;
uint16_t temp_enc;
char weather[32];
} weather;
#endif
} data;
} ui_dispatch_msg_t;
static const char *TAG = "ui_dispatch";
static QueueHandle_t s_ui_dispatch_queue = NULL;
static TaskHandle_t s_ui_dispatch_task = NULL;
extern lv_ui guider_ui;
extern int screen_time_digital_clock_now_hour_value;
extern int screen_time_digital_clock_now_min_value;
extern int screen_time_digital_clock_now_sec_value;
static const char *WEEK_TEXT[] = {
"星期一",
"星期二",
"星期三",
"星期四",
"星期五",
"星期六",
"星期日",
};
/** Same order as time page `WEEK_TEXT` (Mon=0 ... Sun=6), short format for perpetual calendar */
static const char *WEEK_SHORT_TEXT[] = {
"周一", "周二", "周三", "周四", "周五", "周六", "周日",
};
static int s_perpetual_last_week_idx = -1;
static uint16_t s_perpetual_last_year = 0;
static uint8_t s_perpetual_last_month = 0;
static uint8_t s_perpetual_last_day = 0;
/** 与界面一致的「当前日历」,供午夜翻日;由 SET_DATE / BLE 日期同步更新 */
static portMUX_TYPE s_clock_cal_mux = portMUX_INITIALIZER_UNLOCKED;
static bool s_clock_cal_valid = false;
static uint16_t s_clock_cal_y = 0;
static uint8_t s_clock_cal_m = 0;
static uint8_t s_clock_cal_d = 0;
static uint8_t s_clock_cal_w = 0;
/** 方案 A:校时锚点 + esp_timer 推算,减轻 lv_timer 累计误差 */
static bool s_mono_anchor_valid = false;
static uint32_t s_mono_anchor_sec_in_day = 0;
static int64_t s_mono_anchor_us = 0;
static uint64_t s_mono_last_day_index = 0;
static void ui_dispatch_perpetual_calendar_set_labels(int week_idx, uint16_t year, uint8_t month, uint8_t day)
{
almanac_entry_t almanac = {0};
if (week_idx < 0 || week_idx > 6 || year == 0 || month == 0 || month > 12 || day == 0 || day > 31) {
return;
}
char day_buf[8];
snprintf(day_buf, sizeof(day_buf), "%02u", (unsigned int)day);
if (guider_ui.screen_Perpetual_Calendar_label_week != NULL &&
lv_obj_is_valid(guider_ui.screen_Perpetual_Calendar_label_week)) {
lv_label_set_text(guider_ui.screen_Perpetual_Calendar_label_week, WEEK_SHORT_TEXT[week_idx]);
}
if (guider_ui.screen_Perpetual_Calendar_label_day != NULL &&
lv_obj_is_valid(guider_ui.screen_Perpetual_Calendar_label_day)) {
lv_label_set_text(guider_ui.screen_Perpetual_Calendar_label_day, day_buf);
}
if (almanac_service_get(year, month, day, &almanac) &&
almanac.lunar[0] != '\0' &&
guider_ui.screen_Perpetual_Calendar_label_Lunar_Calendar != NULL &&
lv_obj_is_valid(guider_ui.screen_Perpetual_Calendar_label_Lunar_Calendar)) {
lv_label_set_text(guider_ui.screen_Perpetual_Calendar_label_Lunar_Calendar, almanac.lunar);
}
if (almanac.dos[0] != '\0' &&
guider_ui.screen_Perpetual_Calendar_label_Dos != NULL &&
lv_obj_is_valid(guider_ui.screen_Perpetual_Calendar_label_Dos)) {
lv_label_set_text(guider_ui.screen_Perpetual_Calendar_label_Dos, almanac.dos);
}
if (almanac.donts[0] != '\0' &&
guider_ui.screen_Perpetual_Calendar_label_Donts != NULL &&
lv_obj_is_valid(guider_ui.screen_Perpetual_Calendar_label_Donts)) {
lv_label_set_text(guider_ui.screen_Perpetual_Calendar_label_Donts, almanac.donts);
}
}
/* 调用方须已持有 lvgl_api_lock(与 ui_dispatch_apply_date 一致) */
static void ui_dispatch_put_date_on_ui_locked(uint16_t year, uint8_t month, uint8_t day, uint8_t week)
{
char date[32] = {0};
snprintf(date, sizeof(date), "%04u年%02u月%02u日",
(unsigned int)year,
(unsigned int)month,
(unsigned int)day);
if (guider_ui.screen_time_label_date != NULL &&
lv_obj_is_valid(guider_ui.screen_time_label_date)) {
lv_label_set_text(guider_ui.screen_time_label_date, date);
}
uint8_t w = week;
int idx = -1;
if (w >= 1 && w <= 7) {
idx = (int)w - 1;
} else if (w <= 6) {
idx = ((int)w + 6) % 7;
}
if (idx >= 0 && idx < 7) {
s_perpetual_last_week_idx = idx;
s_perpetual_last_year = year;
s_perpetual_last_month = month;
s_perpetual_last_day = day;
ui_dispatch_perpetual_calendar_set_labels(idx, year, month, day);
if (guider_ui.screen_time_label_week != NULL &&
lv_obj_is_valid(guider_ui.screen_time_label_week)) {
lv_label_set_text(guider_ui.screen_time_label_week, WEEK_TEXT[idx]);
}
portENTER_CRITICAL(&s_clock_cal_mux);
s_clock_cal_valid = true;
s_clock_cal_y = year;
s_clock_cal_m = month;
s_clock_cal_d = day;
s_clock_cal_w = w;
portEXIT_CRITICAL(&s_clock_cal_mux);
} else {
ESP_LOGW(TAG, "date week out of range: %u", (unsigned int)w);
portENTER_CRITICAL(&s_clock_cal_mux);
s_clock_cal_valid = false;
portEXIT_CRITICAL(&s_clock_cal_mux);
}
}
#if 0 // UNUSED: BLE uses direct path via ble_data_ansy.c
typedef enum {
UI_WEATHER_CLASS_SUNNY = 0, // 晴
UI_WEATHER_CLASS_CLOUDY, // 多云/阴
UI_WEATHER_CLASS_FOG, // 雾/霾/沙尘
UI_WEATHER_CLASS_RAIN, // 雨
UI_WEATHER_CLASS_THUNDER, // 雷阵雨
UI_WEATHER_CLASS_SNOW, // 雪
UI_WEATHER_CLASS_SLEET, // 雨夹雪/冻雨
UI_WEATHER_CLASS_HAIL, // 冰雹
} ui_weather_class_t;
static ui_weather_class_t ui_dispatch_classify_weather_8class(const char *weather)
{
if (weather == NULL || weather[0] == '\0') {
return UI_WEATHER_CLASS_SUNNY;
}
/* Priority 1: Hail */
if (strstr(weather, WEATHER_HAIL) != NULL) {
return UI_WEATHER_CLASS_HAIL;
}
/* Priority 2: Sleet/Freezing rain */
if (strstr(weather, WEATHER_SLEET) != NULL || strstr(weather, WEATHER_FREEZING_RAIN) != NULL) {
return UI_WEATHER_CLASS_SLEET;
}
/* Priority 3: Snow */
if (strstr(weather, WEATHER_SNOW) != NULL || strstr(weather, WEATHER_GRAUPEL) != NULL) {
return UI_WEATHER_CLASS_SNOW;
}
/* Priority 4: Thunderstorm */
if (strstr(weather, WEATHER_THUNDER) != NULL) {
return UI_WEATHER_CLASS_THUNDER;
}
/* Priority 5: Rain */
if (strstr(weather, WEATHER_RAIN) != NULL) {
return UI_WEATHER_CLASS_RAIN;
}
/* Priority 6: Fog/Haze/Dust */
if (strstr(weather, WEATHER_FOG) != NULL || strstr(weather, WEATHER_HAZE) != NULL ||
strstr(weather, WEATHER_DUST) != NULL || strstr(weather, WEATHER_SAND) != NULL ||
strstr(weather, WEATHER_FLOATING_DUST) != NULL) {
return UI_WEATHER_CLASS_FOG;
}
/* Priority 7: Cloudy */
if (strstr(weather, WEATHER_CLOUD) != NULL || strstr(weather, WEATHER_OVERCAST) != NULL) {
return UI_WEATHER_CLASS_CLOUDY;
}
/* Default: Sunny */
return UI_WEATHER_CLASS_SUNNY;
}
static const char *ui_dispatch_weather_text_from_class(ui_weather_class_t weather_class)
{
switch (weather_class) {
case UI_WEATHER_CLASS_RAIN:
return WEATHER_TEXT_RAIN;
case UI_WEATHER_CLASS_THUNDER:
return WEATHER_TEXT_THUNDER;
case UI_WEATHER_CLASS_SNOW:
return WEATHER_TEXT_SNOW;
case UI_WEATHER_CLASS_SLEET:
return WEATHER_TEXT_SLEET;
case UI_WEATHER_CLASS_HAIL:
return WEATHER_TEXT_HAIL;
case UI_WEATHER_CLASS_FOG:
return WEATHER_TEXT_FOG;
case UI_WEATHER_CLASS_CLOUDY:
return WEATHER_TEXT_CLOUDY;
case UI_WEATHER_CLASS_SUNNY:
default:
return WEATHER_TEXT_SUNNY;
}
}
static const lv_image_dsc_t *ui_dispatch_weather_icon_from_class(ui_weather_class_t weather_class)
{
switch (weather_class) {
case UI_WEATHER_CLASS_RAIN:
return &_rainy_RGB565_60x50;
case UI_WEATHER_CLASS_THUNDER:
return &_thunder_RGB565_60x50;
case UI_WEATHER_CLASS_SNOW:
return &_snow_RGB565_60x50;
case UI_WEATHER_CLASS_SLEET:
return &_sleet_RGB565_60x50;
case UI_WEATHER_CLASS_HAIL:
return &_hail_RGB565_60x50;
case UI_WEATHER_CLASS_FOG:
return &_fog_RGB565_60x50;
case UI_WEATHER_CLASS_CLOUDY:
return &_cloudy_RGB565_60x50;
case UI_WEATHER_CLASS_SUNNY:
default:
return &_sun_RGB565_60x50;
}
}
static void ui_dispatch_set_weather_icon_if_valid(lv_obj_t *img_obj, ui_weather_class_t weather_class)
{
if (img_obj != NULL && lv_obj_is_valid(img_obj)) {
lv_image_set_src(img_obj, ui_dispatch_weather_icon_from_class(weather_class));
}
}
#endif // End of unused weather functions
/*
* Synced with time page: `screen_time_digital_clock_now_*` global hour/min/sec.
* `label_3hlate` = current hour +2, `label_5hlate` = current hour +4, both show HH:00 format.
* Only update when hour value changes to avoid frequent label updates.
*/
void ui_dispatch_refresh_home_weather_slot_hour_labels(void)
{
int h = screen_time_digital_clock_now_hour_value;
if (h < 0) {
h = 0;
} else if (h > 23) {
h %= 24;
}
static int s_last_applied_hour = -1;
if (s_last_applied_hour >= 0 && h == s_last_applied_hour) {
return;
}
s_last_applied_hour = h;
int h_plus2 = (h + 2) % 24;
int h_plus4 = (h + 4) % 24;
char buf[8];
snprintf(buf, sizeof(buf), "%02d:00", h_plus2);
if (guider_ui.screen_home_weather_label_3hlate != NULL &&
lv_obj_is_valid(guider_ui.screen_home_weather_label_3hlate)) {
lv_label_set_text(guider_ui.screen_home_weather_label_3hlate, buf);
}
snprintf(buf, sizeof(buf), "%02d:00", h_plus4);
if (guider_ui.screen_home_weather_label_5hlate != NULL &&
lv_obj_is_valid(guider_ui.screen_home_weather_label_5hlate)) {
lv_label_set_text(guider_ui.screen_home_weather_label_5hlate, buf);
}
}
static bool ui_dispatch_send(const ui_dispatch_msg_t *msg)
{
if (msg == NULL) {
return false;
}
if (s_ui_dispatch_queue == NULL) {
ESP_LOGW(TAG, "ui_dispatch 未初始化,丢弃 cmd=%d", (int)msg->type);
return false;
}
if (xQueueSend(s_ui_dispatch_queue, msg, pdMS_TO_TICKS(10)) != pdPASS) {
ESP_LOGW(TAG, "ui dispatch queue full, drop cmd=%d", (int)msg->type);
return false;
}
return true;
}
static void ui_dispatch_apply_date(const ui_dispatch_msg_t *msg)
{
ui_dispatch_put_date_on_ui_locked(msg->data.date.year,
msg->data.date.month,
msg->data.date.day,
msg->data.date.week);
}
static void ui_dispatch_apply_time(const ui_dispatch_msg_t *msg)
{
screen_time_digital_clock_now_hour_value = (int)msg->data.time.hour;
screen_time_digital_clock_now_min_value = (int)msg->data.time.minute;
screen_time_digital_clock_now_sec_value = (int)msg->data.time.second;
{
const uint32_t sid = (uint32_t)msg->data.time.hour * 3600u +
(uint32_t)msg->data.time.minute * 60u + (uint32_t)msg->data.time.second;
s_mono_anchor_sec_in_day = sid % 86400u;
s_mono_anchor_us = esp_timer_get_time();
s_mono_anchor_valid = true;
s_mono_last_day_index = 0;
}
if (guider_ui.screen_time_digital_clock_now != NULL &&
lv_obj_is_valid(guider_ui.screen_time_digital_clock_now)) {
lv_label_set_text_fmt(guider_ui.screen_time_digital_clock_now, "%02u:%02u",
(unsigned int)msg->data.time.hour,
(unsigned int)msg->data.time.minute);
}
ui_dispatch_refresh_home_weather_slot_hour_labels();
}
#if 0 // UNUSED: BLE uses direct path via ble_data_ansy.c
/*
* BLE `w_t_info` handler: timing, temp_enc, weather string from vehicle.
* Purpose: classify weather into 8 types and map to corresponding icons.
* Update images by timing: timing=0 -> img_1+img_2; timing=2 -> img_3; timing=4 -> img_4.
* Other timing values: no UI update (only 0/2/4 supported).
*/
static void ui_dispatch_apply_weather(const ui_dispatch_msg_t *msg)
{
const uint8_t ble_timing = msg->data.weather.timing;
const char *ble_weather = msg->data.weather.weather;
const ui_weather_class_t weather_class = ui_dispatch_classify_weather_8class(ble_weather);
if (ble_timing != 0x00 && ble_timing != 0x02 && ble_timing != 0x04)
{
return;
}
if (ble_timing == 0x00)
{
/* Main slot: update weather text label and temperature */
int tenths = (int)msg->data.weather.temp_enc - 1000;
int temp_int = (tenths >= 0) ? (tenths + 5) / 10 : (tenths - 5) / 10;
char temp_string[20] = {0};
snprintf(temp_string, sizeof(temp_string), "%d", temp_int);
if (guider_ui.screen_home_weather_label_weather != NULL &&
lv_obj_is_valid(guider_ui.screen_home_weather_label_weather)) {
lv_label_set_text(guider_ui.screen_home_weather_label_weather,
ui_dispatch_weather_text_from_class(weather_class));
}
if (guider_ui.screen_home_weather_label_temp != NULL &&
lv_obj_is_valid(guider_ui.screen_home_weather_label_temp)) {
lv_label_set_text(guider_ui.screen_home_weather_label_temp, temp_string);
}
ui_dispatch_set_weather_icon_if_valid(guider_ui.screen_home_weather_img_1, weather_class);
ui_dispatch_set_weather_icon_if_valid(guider_ui.screen_home_weather_img_2, weather_class);
return;
}
if (ble_timing == 0x02) {
ui_dispatch_set_weather_icon_if_valid(guider_ui.screen_home_weather_img_3, weather_class);
return;
}
/* ble_timing == 0x04 */
ui_dispatch_set_weather_icon_if_valid(guider_ui.screen_home_weather_img_4, weather_class);
}
#endif // End of unused ui_dispatch_apply_weather
static void ui_dispatch_task(void *arg)
{
ui_dispatch_msg_t msg;
(void)arg;
while (1) {
if (xQueueReceive(s_ui_dispatch_queue, &msg, portMAX_DELAY) != pdPASS) {
continue;
}
_lock_acquire(&lvgl_api_lock);
switch (msg.type) {
case UI_DISPATCH_CMD_SET_BRIGHTNESS: {
int p = (int)msg.data.brightness.brightness_percent;
if (p < 0) {
p = 0;
} else if (p > 100) {
p = 100;
}
set_system_brightness(p);
g_brightness = (uint8_t)p;
break;
}
case UI_DISPATCH_CMD_SET_DATE:
ui_dispatch_apply_date(&msg);
break;
case UI_DISPATCH_CMD_SET_TIME:
ui_dispatch_apply_time(&msg);
break;
#if 0 // UNUSED: BLE uses direct path
case UI_DISPATCH_CMD_SET_WEATHER:
ui_dispatch_apply_weather(&msg);
break;
#endif
default:
break;
}
_lock_release(&lvgl_api_lock);
}
}
esp_err_t ui_dispatch_init(void)
{
if (s_ui_dispatch_queue != NULL && s_ui_dispatch_task != NULL) {
return ESP_OK;
}
if (s_ui_dispatch_queue == NULL) {
s_ui_dispatch_queue = xQueueCreate(UI_DISPATCH_QUEUE_LEN, sizeof(ui_dispatch_msg_t));
if (s_ui_dispatch_queue == NULL) {
ESP_LOGE(TAG, "failed to create queue");
return ESP_ERR_NO_MEM;
}
}
if (s_ui_dispatch_task == NULL) {
BaseType_t ret = xTaskCreate(ui_dispatch_task, "ui_dispatch",
UI_DISPATCH_TASK_STACK_SIZE, NULL,
UI_DISPATCH_TASK_PRIORITY, &s_ui_dispatch_task);
if (ret != pdPASS) {
ESP_LOGE(TAG, "failed to create task");
vQueueDelete(s_ui_dispatch_queue);
s_ui_dispatch_queue = NULL;
return ESP_FAIL;
}
}
return ESP_OK;
}
void ui_dispatch_set_brightness(uint8_t brightness_percent)
{
ui_dispatch_msg_t msg = {
.type = UI_DISPATCH_CMD_SET_BRIGHTNESS,
};
msg.data.brightness.brightness_percent = brightness_percent;
ui_dispatch_send(&msg);
}
bool ui_dispatch_set_date(uint16_t year, uint8_t month, uint8_t day, uint8_t week)
{
ui_dispatch_msg_t msg;
memset(&msg, 0, sizeof(msg));
msg.type = UI_DISPATCH_CMD_SET_DATE;
msg.data.date.year = year;
msg.data.date.month = month;
msg.data.date.day = day;
msg.data.date.week = week;
return ui_dispatch_send(&msg);
}
void ui_dispatch_set_time(uint8_t hour, uint8_t minute, uint8_t second)
{
ui_dispatch_msg_t msg = {
.type = UI_DISPATCH_CMD_SET_TIME,
};
msg.data.time.hour = hour;
msg.data.time.minute = minute;
msg.data.time.second = second;
ui_dispatch_send(&msg);
}
#if 0 // UNUSED: BLE uses direct path via ble_data_ansy.c
void ui_dispatch_set_weather(uint8_t timing, uint16_t temp_enc, const char *weather)
{
ui_dispatch_msg_t msg = {
.type = UI_DISPATCH_CMD_SET_WEATHER,
};
msg.data.weather.timing = timing;
msg.data.weather.temp_enc = temp_enc;
if (weather != NULL) {
strncpy(msg.data.weather.weather, weather, sizeof(msg.data.weather.weather) - 1);
msg.data.weather.weather[sizeof(msg.data.weather.weather) - 1] = '\0';
}
ui_dispatch_send(&msg);
}
#endif // End of unused ui_dispatch_set_weather
void ui_dispatch_sync_perpetual_calendar_from_time(void)
{
if (s_perpetual_last_week_idx >= 0 &&
s_perpetual_last_year >= 1900 &&
s_perpetual_last_month >= 1 && s_perpetual_last_month <= 12 &&
s_perpetual_last_day >= 1 && s_perpetual_last_day <= 31) {
ui_dispatch_perpetual_calendar_set_labels(s_perpetual_last_week_idx,
s_perpetual_last_year,
s_perpetual_last_month,
s_perpetual_last_day);
portENTER_CRITICAL(&s_clock_cal_mux);
s_clock_cal_valid = true;
s_clock_cal_y = s_perpetual_last_year;
s_clock_cal_m = s_perpetual_last_month;
s_clock_cal_d = s_perpetual_last_day;
s_clock_cal_w = (uint8_t)(s_perpetual_last_week_idx + 1);
portEXIT_CRITICAL(&s_clock_cal_mux);
return;
}
if (guider_ui.screen_time_label_date == NULL || !lv_obj_is_valid(guider_ui.screen_time_label_date)) {
return;
}
if (guider_ui.screen_time_label_week == NULL || !lv_obj_is_valid(guider_ui.screen_time_label_week)) {
return;
}
const char *ds = lv_label_get_text(guider_ui.screen_time_label_date);
const char *ws = lv_label_get_text(guider_ui.screen_time_label_week);
unsigned y = 0;
unsigned m = 0;
unsigned day = 0;
if (sscanf(ds, "%04u年%02u月%02u日", &y, &m, &day) != 3) {
return;
}
int idx = -1;
for (int i = 0; i < 7; i++) {
if (strcmp(ws, WEEK_TEXT[i]) == 0) {
idx = i;
break;
}
}
if (idx < 0) {
return;
}
s_perpetual_last_week_idx = idx;
s_perpetual_last_year = (uint16_t)y;
s_perpetual_last_month = (uint8_t)m;
s_perpetual_last_day = (uint8_t)day;
ui_dispatch_perpetual_calendar_set_labels(idx, (uint16_t)y, (uint8_t)m, (uint8_t)day);
portENTER_CRITICAL(&s_clock_cal_mux);
s_clock_cal_valid = true;
s_clock_cal_y = (uint16_t)y;
s_clock_cal_m = (uint8_t)m;
s_clock_cal_d = (uint8_t)day;
s_clock_cal_w = (uint8_t)(idx + 1);
portEXIT_CRITICAL(&s_clock_cal_mux);
}
static bool ui_dispatch_clock_calendar_advance_one_day(void)
{
portENTER_CRITICAL(&s_clock_cal_mux);
const bool valid = s_clock_cal_valid;
const uint16_t y = s_clock_cal_y;
const uint8_t m = s_clock_cal_m;
const uint8_t d = s_clock_cal_d;
portEXIT_CRITICAL(&s_clock_cal_mux);
if (!valid || m < 1 || m > 12 || d < 1 || d > 31 || y < 1970) {
return false;
}
struct tm t;
memset(&t, 0, sizeof(t));
t.tm_year = (int)y - 1900;
t.tm_mon = (int)m - 1;
t.tm_mday = (int)d + 1;
t.tm_hour = 12;
t.tm_min = 0;
t.tm_sec = 0;
t.tm_isdst = -1;
if (mktime(&t) == (time_t)-1) {
ESP_LOGW(TAG, "mktime failed in midnight calendar roll");
return false;
}
const uint16_t ny = (uint16_t)(t.tm_year + 1900);
const uint8_t nm = (uint8_t)(t.tm_mon + 1);
const uint8_t nd = (uint8_t)t.tm_mday;
const int idx = (t.tm_wday + 6) % 7;
const uint8_t nw = (uint8_t)(idx + 1);
ui_dispatch_put_date_on_ui_locked(ny, nm, nd, nw);
ESP_LOGI(TAG, "时钟午夜翻日: %04u-%02u-%02u", (unsigned)ny, (unsigned)nm, (unsigned)nd);
return true;
}
void ui_dispatch_clock_calendar_try_roll_at_midnight(int prev_h, int prev_m, int prev_s,
int new_h, int new_m, int new_s)
{
if (prev_h != 23 || prev_m != 59 || prev_s != 59) {
return;
}
if (new_h != 0 || new_m != 0 || new_s != 0) {
return;
}
(void)ui_dispatch_clock_calendar_advance_one_day();
}
bool ui_dispatch_digital_clock_monotonic_tick(void)
{
if (!s_mono_anchor_valid) {
return false;
}
int64_t now_us = esp_timer_get_time();
int64_t d_us = now_us - s_mono_anchor_us;
if (d_us < 0) {
d_us = 0;
}
const uint64_t elapsed = (uint64_t)(d_us / 1000000LL);
const uint64_t total = (uint64_t)s_mono_anchor_sec_in_day + elapsed;
const uint64_t day = total / 86400ULL;
const uint32_t max_steps = 400u;
uint32_t guard = 0u;
while (s_mono_last_day_index < day && guard < max_steps) {
if (!ui_dispatch_clock_calendar_advance_one_day()) {
ESP_LOGW(TAG, "mono 跨日翻页跳过(无有效车机日期),对齐 day_index");
s_mono_last_day_index = day;
break;
}
s_mono_last_day_index++;
guard++;
}
if (s_mono_last_day_index < day) {
ESP_LOGW(TAG, "mono 跨日次数超过上限 %lu,强制对齐 day_index", (unsigned long)max_steps);
s_mono_last_day_index = day;
}
const uint32_t sid = (uint32_t)(total % 86400ULL);
screen_time_digital_clock_now_hour_value = (int)(sid / 3600u);
screen_time_digital_clock_now_min_value = (int)((sid / 60u) % 60u);
screen_time_digital_clock_now_sec_value = (int)(sid % 60u);
return true;
}
+32
View File
@@ -0,0 +1,32 @@
#ifndef UI_DISPATCH_H
#define UI_DISPATCH_H
#include <stdbool.h>
#include <stdint.h>
#include "esp_err.h"
esp_err_t ui_dispatch_init(void);
void ui_dispatch_set_brightness(uint8_t brightness_percent);
/** @return true 已入队,false 未初始化或队列满 */
bool ui_dispatch_set_date(uint16_t year, uint8_t month, uint8_t day, uint8_t week);
void ui_dispatch_set_time(uint8_t hour, uint8_t minute, uint8_t second);
// void ui_dispatch_set_weather(uint8_t timing, uint16_t temp_enc, const char *weather); // UNUSED: BLE uses direct path
void ui_dispatch_refresh_home_weather_slot_hour_labels(void);
/** 万年历 week/day 与时间页联动:与最近一次日期同步一致;进入万年历屏时可再调一次 */
void ui_dispatch_sync_perpetual_calendar_from_time(void);
/**
* 软件时钟整秒进位后调用:仅在 23:59:59 -> 00:00:00 且已有有效车机日历时,把日期推进一天。
* 须在持有 lvgl_api_lock 的 LVGL 任务上下文中调用(如 LVGL 定时器回调内)。
*/
void ui_dispatch_clock_calendar_try_roll_at_midnight(int prev_h, int prev_m, int prev_s,
int new_h, int new_m, int new_s);
/**
* LVGL 秒定时器内调用:若已收到过 ui_dispatch 校时,则按 esp_timer 单调时钟推算时分秒并处理跨午夜翻日。
* @return true 已更新全局 `screen_time_digital_clock_now_*`(勿再 clock_count / try_roll)
*/
bool ui_dispatch_digital_clock_monotonic_tick(void);
#endif
File diff suppressed because it is too large Load Diff
+399
View File
@@ -0,0 +1,399 @@
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "esp_wifi.h"
#include "esp_log.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include "config.h"
#include "event.h"
#include "cjson.h"
#include "BLE/ble.h"
#include "esp_netif.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "wifi.h"
#include "BLE/ble_send.h"
#include "LVGL/lvgl_init.h"
#include "OTA/ota_upgrade.h"
#include "wifi_prov_ui.h"
static bool wifi_initialized = false;
static bool netif_created = false;
static bool netif_inited = false;
static const char *WIFI_TAG = "wifi_station";
static int s_retry_num = 0;
/* 仅 BLE 下发配网指令触发:失败时执行“释放 WiFi + 恢复 UI”,成功不恢复(等待后续 OTA) */
static volatile bool s_ble_wifi_prov_session = false;
/** wifi_network_shutdown_full() 主动 stop/deinit 时置位,避免 STA_DISCONNECTED 误走重连 */
static volatile bool s_wifi_intentional_shutdown = false;
extern bool g_wifi_state;
// 定义WiFi配置变量
static char ssid[32]; // 存放WIFI 名称
static char password[64]; // 存放WIFI 密码
// 事件组和位定义
static EventGroupHandle_t s_wifi_event_group; // 定义事件组
static volatile bool s_wifi_connected_now = false;
static const char* wifi_disconnect_reason_str(int reason);
static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
// 先新增:原因码转文字的辅助函数(放在事件回调外,比如文件开头)
static const char* wifi_disconnect_reason_str(int reason) {
switch (reason) {
case WIFI_REASON_AUTH_EXPIRE: return "认证超时(心跳包丢失/共存干扰)";
case WIFI_REASON_AUTH_FAIL: return "认证失败(密码错误/加密模式不匹配)";
case WIFI_REASON_ASSOC_FAIL: return "关联失败(路由器拒绝/MAC过滤/连接数满)";
case WIFI_REASON_BEACON_TIMEOUT: return "Beacon超时(路由器离线/信号弱/共存干扰)";
case WIFI_REASON_HANDSHAKE_TIMEOUT: return "握手超时(射频干扰/距离过远)";
case WIFI_REASON_ROAMING: return "路由器信道切换(漫游)";
case WIFI_REASON_UNSPECIFIED: return "未知原因";
default: return "未定义原因码";
}
}
// 修改 event_handler 函数
static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) // 事件ID
{
// 收到 STA 启动信号,开始连接
esp_wifi_connect(); // 开始连接WIFI
}
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) // WIFI 事件 == 6
{
if (s_wifi_intentional_shutdown)
{
/* esp_wifi_stop/deinit 会派发本事件,并非链路异常,禁止 esp_wifi_connect 重连 */
s_wifi_connected_now = false;
ESP_LOGI(WIFI_TAG, "STA_DISCONNECTED:主动关闭 WiFi 过程中,跳过重连与断开上报");
return;
}
s_wifi_connected_now = false;
g_wifi_state = false;
set_system_event(WIFI_DISCONNECTED_BIT); // 任何断开都立即上报,供上层做3秒恢复判断
/********************* 新增:打印失败原因 *********************/
// 1. 提取断开事件的详细信息(核心)
wifi_event_sta_disconnected_t* disconnect_info = (wifi_event_sta_disconnected_t*)event_data;
// 2. 打印原因码 + 文字解释(一目了然)
ESP_LOGE(WIFI_TAG, "===== WiFi断开/连接失败 ======");
ESP_LOGE(WIFI_TAG, "失败原因码:%d", disconnect_info->reason);
ESP_LOGE(WIFI_TAG, "失败原因:%s", wifi_disconnect_reason_str(disconnect_info->reason));
ESP_LOGE(WIFI_TAG, "==============================");
/************************************************************/
// 断开后尝试重连
if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY)
{
esp_wifi_connect(); // 检测到断开 开始重连
s_retry_num++;
ESP_LOGI(WIFI_TAG, "WiFi连接失败,正在尝试第 %d 次重连...", s_retry_num);
}
else
{
ESP_LOGI(WIFI_TAG, "重试多次无效,连接失败");
xEventGroupSetBits(s_wifi_event_group, WIFI_EVENT_FAIL_BIT); // WIFI 连接失败
if (ble_is_connected())
{
ble_send_wifi_state_request(0); // 通过 BLE 反馈:WiFi 断开/未连上
}
else
{
ESP_LOGW(WIFI_TAG, "BLE 已断开,跳过 WiFi 断开状态上报");
}
if (ota_upgrade_is_running())
{
ESP_LOGW(WIFI_TAG, "OTA 过程中 WiFi 重连失败,请求恢复 OTA 前状态");
set_system_event(OTA_RECOVER_REQUIRED_BIT);
}
/* 仅 BLE 配网会话:最终失败后释放 WiFi 并恢复 UI */
if (s_ble_wifi_prov_session)
{
s_ble_wifi_prov_session = false;
ESP_LOGW(WIFI_TAG, "BLE 配网最终失败:停止并释放 WiFi,准备恢复 UI");
(void)esp_wifi_stop();
(void)esp_wifi_deinit();
s_retry_num = 0;
set_system_event(WIFI_BLE_PROV_FAIL_RECOVER_UI_BIT);
}
wifi_event_sta_disconnected_t *event = (wifi_event_sta_disconnected_t *)event_data;
ESP_LOGE(WIFI_TAG, "Disconnect reason: %d", event->reason);
}
}
else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) // 获取IP 事件 IP_EVENT_STA_GOT_IP
{
s_wifi_connected_now = true;
/* BLE 配网成功:结束会话,但不恢复 UI(通常紧跟 OTA) */
if (s_ble_wifi_prov_session)
{
s_ble_wifi_prov_session = false;
}
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
ESP_LOGI(WIFI_TAG, "获取到IP: " IPSTR, IP2STR(&event->ip_info.ip)); // 显示IP 地址
// ============================================================
// === 新增:强制设置 DNS 服务器 (解决 202 域名解析错误) ===
// ============================================================
esp_netif_t *netif = event->esp_netif;
esp_netif_dns_info_t dns_info;
// 1. 设置主 DNS 为阿里 DNS (223.5.5.5) - 解析国内域名最快
IP4_ADDR(&dns_info.ip.u_addr.ip4, 223, 5, 5, 5);
dns_info.ip.type = IPADDR_TYPE_V4;
esp_netif_set_dns_info(netif, ESP_NETIF_DNS_MAIN, &dns_info);
// 2. 设置备用 DNS 为谷歌 DNS (8.8.8.8) - 防止阿里挂了
IP4_ADDR(&dns_info.ip.u_addr.ip4, 8, 8, 8, 8);
dns_info.ip.type = IPADDR_TYPE_V4;
esp_netif_set_dns_info(netif, ESP_NETIF_DNS_BACKUP, &dns_info);
ESP_LOGI(WIFI_TAG, "已强制设置DNS: Main=223.5.5.5, Backup=8.8.8.8");
// ============================================================
s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_EVENT_CONNECTED_BIT); // 事件组
set_system_event(WIFI_CONNECTED_BIT);
/* 配网成功,主动通过 BLE 1.10_A 反馈:WiFi 已连接 (0x01) */
ESP_LOGI(WIFI_TAG, "配网成功,主动通过 BLE 1.10_A 反馈 WiFi 已连接");
ble_send_wifi_state_request(1);
g_wifi_state = true;
}
}
// 初始化 WiFi
static esp_netif_t *get_existing_wifi_netif(void) // wifi 网络接口管理函数
{
esp_netif_t *netif = NULL;
// 遍历系统当前所有的网络接口
while ((netif = esp_netif_next(netif)) != NULL) // 遍历所有网络接口
{
const char *desc = esp_netif_get_desc(netif);
const char *key = esp_netif_get_ifkey(netif);
ESP_LOGI(WIFI_TAG, "发现现有接口 - Key: %s, Desc: %s",
key ? key : "NULL",
desc ? desc : "NULL");
// 检查这个接口是否是 WiFi STA
// 通常 Key 是 "WIFI_STA_DEF" 或者 Desc 包含 "sta"
if ((key && strcmp(key, "WIFI_STA_DEF") == 0) || (desc && strstr(desc, "sta") != NULL))
{
return netif;
}
}
return NULL;
}
WIFI_STATE wifi_init_sta(void)
{
ESP_LOGI(WIFI_TAG, "===== 开始WiFi初始化 =====");
// 1. 初始化 TCP/IP 堆栈
esp_err_t err = esp_netif_init();
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE)
{
ESP_LOGE(WIFI_TAG, "esp_netif_init 失败: %d", err);
return wifi_disconnected;
}
// 2. 【关键修复】创建默认事件循环
// 如果没有这个,创建 netif 会直接崩溃或报错
err = esp_event_loop_create_default();
if (err == ESP_OK)
{
ESP_LOGI(WIFI_TAG, "默认事件循环创建成功");
}
else if (err == ESP_ERR_INVALID_STATE)
{
ESP_LOGI(WIFI_TAG, "默认事件循环已存在,跳过");
}
else
{
ESP_LOGE(WIFI_TAG, "事件循环创建失败: %d", err);
return wifi_disconnected;
}
// 3. 创建事件组
if (s_wifi_event_group == NULL)
{
s_wifi_event_group = xEventGroupCreate();
}
// 4. 【强力修复】获取或创建 Network Interface
// 不再只依赖 key 查找,而是遍历查找
esp_netif_t *netif = get_existing_wifi_netif();
if (netif != NULL)
{
ESP_LOGI(WIFI_TAG, "检测到已存在 WiFi STA 接口,直接复用");
}
else
{
// 只有真的找不到时,才创建
ESP_LOGI(WIFI_TAG, "未找到现有接口,正在创建默认 WiFi STA...");
netif = esp_netif_create_default_wifi_sta();
if (netif == NULL)
{
ESP_LOGE(WIFI_TAG, "致命错误:创建 WiFi 接口失败,可能驱动已异常");
// 这里返回,防止后续操作导致崩溃
return wifi_disconnected;
}
}
// 5. 初始化 WiFi 驱动
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
esp_err_t ret = esp_wifi_init(&cfg);
if (ret == ESP_OK)
{
ESP_LOGI(WIFI_TAG, "WiFi 驱动初始化完成");
wifi_initialized = true;
}
else if (ret == ESP_ERR_INVALID_STATE)
{
ESP_LOGI(WIFI_TAG, "WiFi 驱动此前已初始化,继续配置");
wifi_initialized = true;
}
else
{
ESP_LOGE(WIFI_TAG, "WiFi 初始化失败: %s", esp_err_to_name(ret));
return wifi_disconnected;
}
// 6. 注册事件回调 (防止重复注册)
static bool handlers_registered = false;
if (!handlers_registered)
{
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&event_handler,
NULL,
NULL));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&event_handler,
NULL,
NULL));
handlers_registered = true;
}
// 7. 配置 WiFi 参数
wifi_config_t wifi_config = {0};
memset(&wifi_config, 0, sizeof(wifi_config)); // 清零很重要
strncpy((char *)wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid) - 1);
strncpy((char *)wifi_config.sta.password, password, sizeof(wifi_config.sta.password) - 1);
wifi_config.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;
wifi_config.sta.scan_method = WIFI_ALL_CHANNEL_SCAN;
wifi_config.sta.sort_method = WIFI_CONNECT_AP_BY_SIGNAL;
ESP_LOGI(WIFI_TAG, "正在设置 WiFi 模式为 STA...");
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_LOGI(WIFI_TAG, "正在应用 WiFi 配置...");
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
s_retry_num = 0;
// 8. 启动与连接
ESP_LOGI(WIFI_TAG, "启动 WiFi...");
ret = esp_wifi_start();
esp_wifi_set_ps(WIFI_PS_NONE);
if (ret == ESP_OK)
{
ESP_LOGI(WIFI_TAG, "启动成功,Event Handler 将自动发起连接");
}
else
{
ESP_LOGW(WIFI_TAG, "WiFi 可能已在运行,手动发起连接...");
esp_wifi_connect();
}
/* 非阻塞:不在此等待连接结果,避免阻塞 BLE 任务导致无法处理 OTA 等后续指令。
* 连接成功/失败由 event_handler 在 IP_EVENT_STA_GOT_IP 或 WIFI_EVENT_STA_DISCONNECTED 中
* 通过 ble_send_wifi_state_request(1/0) 反馈给车机。 */
ESP_LOGI(WIFI_TAG, "已发起连接,结果将通过 BLE 1.10_A 反馈");
return wifi_connected; /* 仅表示已发起连接,实际结果由事件回调上报 */
}
// 主函数
void wifi_main(char *ss, char *pass)
{
ESP_LOGI(WIFI_TAG, "===== 开始WiFi配置处理 =====");
// 检查输入参数
if (ss == NULL || pass == NULL)
{
ESP_LOGE(WIFI_TAG, "WiFi配置参数无效");
return;
}
// 复制WiFi配置
strncpy((char *)ssid, ss, sizeof(ssid) - 1);
strncpy((char *)password, pass, sizeof(password) - 1);
// 输出WiFi配置(注意:实际生产环境中不应输出密码)
ESP_LOGI(WIFI_TAG, "接收到WiFi配置:");
ESP_LOGI(WIFI_TAG, "SSID: %s", ssid);
ESP_LOGI(WIFI_TAG, "PASSWORD: %s", password);
ESP_LOGI(WIFI_TAG, "开始连接WiFi...");
/* BLE 配网:保存状态并拆 UI,失败时由 main_task 恢复;成功不恢复 */
/*
s_ble_wifi_prov_session = true;
wifi_prov_capture_pre_state();
lvgl_deinit_for_wifi();
wifi_prov_mark_lvgl_deinited();
set_block_auto_sleep(true);
*/
if (wifi_init_sta() == wifi_disconnected)
{
}
else
{
}
// 通过蓝牙通知车机 wifi状态
ESP_LOGI(WIFI_TAG, "===== WiFi配置处理完成 =====");
}
bool wifi_is_connected_now(void)
{
return s_wifi_connected_now;
}
bool wifi_sta_reconnect_exhausted(void)
{
return s_retry_num >= EXAMPLE_ESP_MAXIMUM_RETRY;
}
bool wifi_sta_still_reconnecting(void)
{
return !s_wifi_connected_now && s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY;
}
void wifi_network_shutdown_full(void)
{
ESP_LOGW(WIFI_TAG, "wifi_network_shutdown_full: stop + deinit (释放协议栈供 LVGL 重建)");
s_wifi_intentional_shutdown = true;
s_wifi_connected_now = false;
s_ble_wifi_prov_session = false;
(void)esp_wifi_stop();
esp_err_t de = esp_wifi_deinit();
if (de != ESP_OK && de != ESP_ERR_WIFI_NOT_INIT)
{
ESP_LOGW(WIFI_TAG, "esp_wifi_deinit: %s", esp_err_to_name(de));
}
wifi_initialized = false;
s_retry_num = 0;
/* 等默认事件循环处理完本次断开,再清标志,避免竞态 */
vTaskDelay(pdMS_TO_TICKS(80));
s_wifi_intentional_shutdown = false;
}
+27
View File
@@ -0,0 +1,27 @@
#ifndef _WIFI_H_
#define _WIFI_H_
#include <stdbool.h>
#define WIFI_EVENT_CONNECTED_BIT BIT0 // WIFI 已经连接
#define WIFI_EVENT_FAIL_BIT BIT1
#define EXAMPLE_ESP_MAXIMUM_RETRY 3
typedef enum
{
wifi_disconnected = 0,
wifi_connected =!wifi_disconnected,
}WIFI_STATE;
void wifi_main(char* ss, char* pass);
/** 停止 STA 并 deinit WiFi 驱动(表情包收尾恢复 LVGL 前腾内存;需网络时再次 wifi_init_sta/mqtt_main) */
void wifi_network_shutdown_full(void);
WIFI_STATE wifi_init_sta(void);
bool wifi_is_connected_now(void);
/** STA 已用尽自动重连次数(与 event_handler 中 else 分支一致) */
bool wifi_sta_reconnect_exhausted(void);
/** 未拿到 IP 且重连次数未用尽,驱动仍可能自动 esp_wifi_connect */
bool wifi_sta_still_reconnecting(void);
#endif /* _WIFI_H_ */
+438
View File
@@ -0,0 +1,438 @@
//version 1.0.1
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <dirent.h>
#include "esp_log.h"
#include "MINIZ/miniz.h"
#include <stdbool.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_task_wdt.h"
#include "event.h"
#include <errno.h>
static const char *TAG_ZIP = "ZIP_EXTRACTOR";
/* 最近一次解压成功对应的表情包 ID(如 'A'/'B'),供 main 识别下载成功并播放 */
static char s_last_downloaded_pack_id = '0';
char zip_extractor_get_last_downloaded_pack(void)
{
return s_last_downloaded_pack_id;
}
void extract_zip_file(const char* zip_path, const char* extract_dir);
// static mz_bool extract_file_with_callback(mz_zip_archive* zip_archive, mz_uint file_index, const char* output_path);
// static mz_bool extract_file_low_memory(mz_zip_archive* zip_archive, mz_uint file_index, const char* output_path);
// static void check_memory_status(void);
// void extract_zip_file_optimized(const char* zip_path, const char* extract_dir);
const char* get_zip_error_string(mz_zip_error error);
void extract_zip_file(const char* zip_path, const char* extract_dir);
// 创建目录的函数
static int create_directory(const char *path) {
char dir_path[256];
char *p;
strncpy(dir_path, path, sizeof(dir_path) - 1);
dir_path[sizeof(dir_path) - 1] = '\0';
// 逐级创建目录
for (p = dir_path + 1; *p; p++) {
if (*p == '/') {
*p = '\0';
mkdir(dir_path, 0755); // 创建目录,忽略错误(目录可能已存在)
*p = '/';
}
}
printf("%s\n0",dir_path);
return mkdir(dir_path, 0755); // 创建最终目录
}
// 检查文件是否存在
static bool file_exists(const char *path) {
struct stat st;
return (stat(path, &st) == 0);
}
// // 从文件路径中提取目录部分
static void extract_directory(const char *filepath, char *dir_path, size_t dir_path_size) {
const char *last_slash = strrchr(filepath, '/');
printf("%s\n",last_slash);
if (last_slash) {
size_t dir_len = last_slash - filepath;
if (dir_len < dir_path_size - 1) {
strncpy(dir_path, filepath, dir_len);
printf("%d\n",dir_len);
dir_path[dir_len] = '\0';
} else {
dir_path[0] = '\0';
}
} else {
// 没有目录,只有文件名
dir_path[0] = '\0';
}
}
void extract_zip_file(const char* zip_path, const char* extract_dir) {
ESP_LOGI(TAG_ZIP, "开始解压ZIP文件: %s 到目录: %s", zip_path, extract_dir);
// 使用静态或堆分配的大缓冲区,避免栈分配
mz_zip_archive* zip_archive = (mz_zip_archive*)malloc(sizeof(mz_zip_archive));
mz_zip_zero_struct(zip_archive); // 使用 miniz 的专用初始化函数
if (zip_archive == NULL) {
ESP_LOGE(TAG_ZIP, "内存不足");
return;
}
ESP_LOGI(TAG_ZIP, "Main task stack free: %d", uxTaskGetStackHighWaterMark(NULL));
ESP_LOGW(TAG_ZIP, "内存足够");
// 1. 初始化ZIP读取器
if (!mz_zip_reader_init_file(zip_archive, zip_path, 0)) {
ESP_LOGE(TAG_ZIP, "无法初始化ZIP读取器: %s", zip_path);
return;
}
ESP_LOGI(TAG_ZIP, "ZIP文件打开成功");
// 2. 获取文件数量
mz_uint num_files = mz_zip_reader_get_num_files(zip_archive);
ESP_LOGI(TAG_ZIP, "ZIP文件中包含 %u 个文件", num_files);
// 使用堆分配路径缓冲区,避免大数组在栈上
char *output_path = malloc(1024);
char *dir_path = malloc(1024);
if (!output_path || !dir_path) {
ESP_LOGE(TAG_ZIP, "内存分配失败");
goto cleanup;
}
// 3. 遍历并解压每个文件
for (mz_uint i = 0; i < num_files; i++) {
mz_zip_archive_file_stat file_stat;
if (mz_zip_reader_file_stat(zip_archive, i, &file_stat)) {
// 跳过目录条目
if (mz_zip_reader_is_file_a_directory(zip_archive, i)) {
ESP_LOGI(TAG_ZIP, "跳过目录: %s", file_stat.m_filename);
continue;
}
ESP_LOGI(TAG_ZIP, "处理文件: %s (大小: %llu 字节)",
file_stat.m_filename, file_stat.m_uncomp_size);
// 构建完整的输出文件路径
snprintf(output_path, 1024, "%s/%s", extract_dir, file_stat.m_filename);
//char *underscore = strchr(output_path, '_');
//if (underscore) *underscore = '\0';
// 检查并创建必要的目录
extract_directory(output_path, dir_path, 1024);
if (strlen(dir_path) > 0) {
create_directory(dir_path);
}
char* last_slash = strrchr(output_path, '/');
if (last_slash != NULL) {
// 斜杠后面的部分就是文件名
char* filename = last_slash + 1;
printf("文件名: %s\n", filename);
snprintf(output_path, 512, "%s/%s", extract_dir, filename);
} else {
printf("没有找到斜杠\n");
}
FILE *f = fopen(output_path, "wb");
if (!f) {
ESP_LOGE("FILE", "无法打开文件: %s, errno=%d (%s)", output_path, errno, strerror(errno));
return;
}
// printf("%s0\n",output_path);
// FILE* f = fopen(output_path, "wb");
fclose(f);
//taskENTER_CRITICAL();
/*
FILE* f = fopen(output_path, "wb");
if (!f) {
ESP_LOGE("FILE", "无法打开文件: %s", output_path);
return;
}
// 验证文件指针
if (fseek(f, 0, SEEK_SET) != 0) {
ESP_LOGE("FILE", "文件指针无效: %s", output_path);
fclose(f);
return;
}*/
// ESP_LOGI("FILE", "文件打开成功: %p, 路径: %s", f, output_path);
// void*ff = (void*)f;
// ESP_LOGI("void*addr", "文件打开成功: %p, 路径: %s", ff, output_path);
// FILE* fff = (FILE*)ff;
// ESP_LOGI("FILE", "文件打开成功: %p, 路径: %s", fff, output_path);
//fclose(f);
mz_bool result = mz_zip_reader_extract_to_file(zip_archive, i, output_path, MZ_ZIP_FLAG_CASE_SENSITIVE | MZ_ZIP_FLAG_IGNORE_PATH);
// 8. 检查解压结果
if (!result) {
mz_zip_error error = mz_zip_get_last_error(zip_archive);
ESP_LOGE(TAG_ZIP, "解压失败, 错误: %s (%d)",
get_zip_error_string(error), error);
// 根据错误类型提供具体建议
switch (error) {
case MZ_ZIP_NO_ERROR:
ESP_LOGI(TAG_ZIP, "操作成功完成");
break;
case MZ_ZIP_FILE_OPEN_FAILED:
ESP_LOGE(TAG_ZIP, "建议: 检查文件路径、权限或磁盘空间,确认文件存在");
break;
case MZ_ZIP_FILE_CREATE_FAILED:
ESP_LOGE(TAG_ZIP, "建议: 检查输出目录权限和磁盘空间");
break;
case MZ_ZIP_FILE_WRITE_FAILED:
ESP_LOGE(TAG_ZIP, "建议: 磁盘空间不足或文件系统只读");
break;
case MZ_ZIP_FILE_READ_FAILED:
ESP_LOGE(TAG_ZIP, "建议: 文件可能被占用或损坏");
break;
case MZ_ZIP_DECOMPRESSION_FAILED:
ESP_LOGE(TAG_ZIP, "建议: ZIP文件可能已损坏或使用不支持的压缩算法");
break;
case MZ_ZIP_CRC_CHECK_FAILED:
ESP_LOGE(TAG_ZIP, "建议: 文件校验失败,可能下载不完整或被修改");
break;
case MZ_ZIP_UNSUPPORTED_METHOD:
ESP_LOGE(TAG_ZIP, "建议: 使用不支持的压缩方法,尝试使用标准ZIP格式");
break;
case MZ_ZIP_UNSUPPORTED_ENCRYPTION:
ESP_LOGE(TAG_ZIP, "建议: 不支持加密的ZIP文件");
break;
case MZ_ZIP_ALLOC_FAILED:
ESP_LOGE(TAG_ZIP, "建议: 系统内存不足,关闭其他应用或减小文件大小");
break;
case MZ_ZIP_BUF_TOO_SMALL:
ESP_LOGE(TAG_ZIP, "建议: 缓冲区太小,增加缓冲区大小");
break;
case MZ_ZIP_INVALID_HEADER_OR_CORRUPTED:
ESP_LOGE(TAG_ZIP, "建议: ZIP文件头损坏或不是有效的ZIP文件");
break;
case MZ_ZIP_NOT_AN_ARCHIVE:
ESP_LOGE(TAG_ZIP, "建议: 文件不是有效的ZIP归档文件");
break;
case MZ_ZIP_FILE_TOO_LARGE:
ESP_LOGE(TAG_ZIP, "建议: 文件太大,超出系统限制");
break;
case MZ_ZIP_ARCHIVE_TOO_LARGE:
ESP_LOGE(TAG_ZIP, "建议: 归档文件总体积太大");
break;
case MZ_ZIP_TOO_MANY_FILES:
ESP_LOGE(TAG_ZIP, "建议: 归档内文件数量过多");
break;
case MZ_ZIP_FAILED_FINDING_CENTRAL_DIR:
ESP_LOGE(TAG_ZIP, "建议: 无法找到中央目录,ZIP文件不完整");
break;
case MZ_ZIP_UNSUPPORTED_MULTIDISK:
ESP_LOGE(TAG_ZIP, "建议: 不支持多卷ZIP文件");
break;
case MZ_ZIP_WRITE_CALLBACK_FAILED:
ESP_LOGE(TAG_ZIP, "建议: 写回调函数失败,检查文件系统状态");
break;
case MZ_ZIP_FILE_SEEK_FAILED:
ESP_LOGE(TAG_ZIP, "建议: 文件定位失败,可能文件被截断");
break;
case MZ_ZIP_INVALID_FILENAME:
ESP_LOGE(TAG_ZIP, "建议: 文件名包含非法字符或路径过长");
break;
case MZ_ZIP_INVALID_PARAMETER:
ESP_LOGE(TAG_ZIP, "建议: 函数参数无效,检查调用代码");
break;
case MZ_ZIP_FILE_NOT_FOUND:
ESP_LOGE(TAG_ZIP, "建议: 指定的文件不存在");
break;
case MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE:
ESP_LOGE(TAG_ZIP, "建议: 解压后大小与预期不符,文件可能损坏");
break;
case MZ_ZIP_COMPRESSION_FAILED:
ESP_LOGE(TAG_ZIP, "建议: 压缩过程失败");
break;
case MZ_ZIP_FILE_CLOSE_FAILED:
ESP_LOGE(TAG_ZIP, "建议: 文件关闭失败,可能磁盘错误");
break;
case MZ_ZIP_FILE_STAT_FAILED:
ESP_LOGE(TAG_ZIP, "建议: 无法获取文件状态信息");
break;
case MZ_ZIP_VALIDATION_FAILED:
ESP_LOGE(TAG_ZIP, "建议: ZIP文件验证失败");
break;
case MZ_ZIP_UNDEFINED_ERROR:
case MZ_ZIP_INTERNAL_ERROR:
case MZ_ZIP_TOTAL_ERRORS:
default:
ESP_LOGE(TAG_ZIP, "建议: 检查系统资源、文件完整性和库版本");
break;
}
}
// 直接解压到文件
// if (mz_zip_reader_extract_to_file(&zip_archive, i, output_path, 0)) {
// ESP_LOGI(TAG, "成功解压到: %s", output_path);
// }
else {
ESP_LOGI(TAG_ZIP, "解压成功: %s", output_path);
}
// 让出CPU,避免长时间占用
if (i % 5 == 0) {
vTaskDelay(pdMS_TO_TICKS(1));
}
}
}
cleanup:
free(output_path);
free(dir_path);
// 4. 结束读取
if (!mz_zip_reader_end(zip_archive)) {
ESP_LOGE(TAG_ZIP, "[EMOJI_DEBUG] ZIP读取器结束失败");
set_system_event(DOWNLOAD_FAILED_BIT);
ESP_LOGE(TAG_ZIP, "[EMOJI_DEBUG] 已设置 DOWNLOAD_FAILED_BIT");
} else {
ESP_LOGI(TAG_ZIP, "[EMOJI_DEBUG] ZIP解压完成, extract_dir=%s", extract_dir);
const char *last_slash = strrchr(extract_dir, '/');
ESP_LOGI(TAG_ZIP, "[EMOJI_DEBUG] strrchr(extract_dir, '/') = %s", last_slash ? last_slash : "NULL");
if (last_slash && *(last_slash + 1))
{
s_last_downloaded_pack_id = *(last_slash + 1);
ESP_LOGI(TAG_ZIP, "[EMOJI_DEBUG] 从目录名解析 pack_id='%c'", s_last_downloaded_pack_id);
}
else
{
s_last_downloaded_pack_id = '0';
ESP_LOGW(TAG_ZIP, "[EMOJI_DEBUG] 无法从目录名解析 pack_id, 设为 '0'");
}
/* 列出解压目录下的文件,确认解压结果 */
DIR *dir = opendir(extract_dir);
if (dir)
{
struct dirent *ent;
int file_count = 0;
ESP_LOGI(TAG_ZIP, "[EMOJI_DEBUG] 解压目录 %s 文件列表:", extract_dir);
while ((ent = readdir(dir)) != NULL)
{
if (ent->d_name[0] == '.') continue;
ESP_LOGI(TAG_ZIP, "[EMOJI_DEBUG] - %s", ent->d_name);
file_count++;
}
closedir(dir);
ESP_LOGI(TAG_ZIP, "[EMOJI_DEBUG] 解压目录共 %d 个文件", file_count);
char default_check[128];
snprintf(default_check, sizeof(default_check), "%s/default.json", extract_dir);
struct stat st;
if (stat(default_check, &st) == 0)
{
ESP_LOGI(TAG_ZIP, "[EMOJI_DEBUG] default.json 存在, 大小=%ld bytes", (long)st.st_size);
}
else
{
ESP_LOGW(TAG_ZIP, "[EMOJI_DEBUG] default.json 不存在于 %s !", extract_dir);
}
}
else
{
ESP_LOGE(TAG_ZIP, "[EMOJI_DEBUG] 无法打开解压目录 %s", extract_dir);
}
ESP_LOGI(TAG_ZIP, "[EMOJI_DEBUG] 准备设置 DOWNLOAD_SUCCESS_BIT, pack_id='%c'", s_last_downloaded_pack_id);
set_system_event(DOWNLOAD_SUCCESS_BIT);
ESP_LOGI(TAG_ZIP, "[EMOJI_DEBUG] DOWNLOAD_SUCCESS_BIT 已设置");
}
free(zip_archive);
}
const char* get_zip_error_string(mz_zip_error error) {
switch (error) {
case MZ_ZIP_NO_ERROR: return "MZ_ZIP_NO_ERROR";
case MZ_ZIP_UNDEFINED_ERROR: return "MZ_ZIP_UNDEFINED_ERROR";
case MZ_ZIP_TOO_MANY_FILES: return "MZ_ZIP_TOO_MANY_FILES";
case MZ_ZIP_FILE_TOO_LARGE: return "MZ_ZIP_FILE_TOO_LARGE";
case MZ_ZIP_UNSUPPORTED_METHOD: return "MZ_ZIP_UNSUPPORTED_METHOD";
case MZ_ZIP_UNSUPPORTED_ENCRYPTION: return "MZ_ZIP_UNSUPPORTED_ENCRYPTION";
case MZ_ZIP_UNSUPPORTED_FEATURE: return "MZ_ZIP_UNSUPPORTED_FEATURE";
case MZ_ZIP_FAILED_FINDING_CENTRAL_DIR: return "MZ_ZIP_FAILED_FINDING_CENTRAL_DIR";
case MZ_ZIP_NOT_AN_ARCHIVE: return "MZ_ZIP_NOT_AN_ARCHIVE";
case MZ_ZIP_INVALID_HEADER_OR_CORRUPTED: return "MZ_ZIP_INVALID_HEADER_OR_CORRUPTED";
case MZ_ZIP_UNSUPPORTED_MULTIDISK: return "MZ_ZIP_UNSUPPORTED_MULTIDISK";
case MZ_ZIP_DECOMPRESSION_FAILED: return "MZ_ZIP_DECOMPRESSION_FAILED";
case MZ_ZIP_COMPRESSION_FAILED: return "MZ_ZIP_COMPRESSION_FAILED";
case MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE: return "MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE";
case MZ_ZIP_CRC_CHECK_FAILED: return "MZ_ZIP_CRC_CHECK_FAILED";
case MZ_ZIP_UNSUPPORTED_CDIR_SIZE: return "MZ_ZIP_UNSUPPORTED_CDIR_SIZE";
case MZ_ZIP_ALLOC_FAILED: return "MZ_ZIP_ALLOC_FAILED";
case MZ_ZIP_FILE_OPEN_FAILED: return "MZ_ZIP_FILE_OPEN_FAILED";
case MZ_ZIP_FILE_CREATE_FAILED: return "MZ_ZIP_FILE_CREATE_FAILED";
case MZ_ZIP_FILE_WRITE_FAILED: return "MZ_ZIP_FILE_WRITE_FAILED";
case MZ_ZIP_FILE_READ_FAILED: return "MZ_ZIP_FILE_READ_FAILED";
case MZ_ZIP_FILE_CLOSE_FAILED: return "MZ_ZIP_FILE_CLOSE_FAILED";
case MZ_ZIP_FILE_SEEK_FAILED: return "MZ_ZIP_FILE_SEEK_FAILED";
case MZ_ZIP_FILE_STAT_FAILED: return "MZ_ZIP_FILE_STAT_FAILED";
case MZ_ZIP_INVALID_PARAMETER: return "MZ_ZIP_INVALID_PARAMETER";
case MZ_ZIP_INVALID_FILENAME: return "MZ_ZIP_INVALID_FILENAME";
case MZ_ZIP_BUF_TOO_SMALL: return "MZ_ZIP_BUF_TOO_SMALL";
case MZ_ZIP_INTERNAL_ERROR: return "MZ_ZIP_INTERNAL_ERROR";
case MZ_ZIP_FILE_NOT_FOUND: return "MZ_ZIP_FILE_NOT_FOUND";
case MZ_ZIP_ARCHIVE_TOO_LARGE: return "MZ_ZIP_ARCHIVE_TOO_LARGE";
case MZ_ZIP_VALIDATION_FAILED: return "MZ_ZIP_VALIDATION_FAILED";
case MZ_ZIP_WRITE_CALLBACK_FAILED: return "MZ_ZIP_WRITE_CALLBACK_FAILED";
default: return "UNKNOWN_ERROR";
}
}
void extract_json_zip(char* filename) {
ESP_LOGI(TAG_ZIP, "[EMOJI_DEBUG] ===== extract_json_zip 开始 =====");
ESP_LOGI(TAG_ZIP, "[EMOJI_DEBUG] filename=%s", filename ? filename : "NULL");
if (!filename || filename[0] == '\0') {
ESP_LOGE(TAG_ZIP, "[EMOJI_DEBUG] extract_json_zip: filename 为空");
return;
}
char zip_file_path[64] = {0};
char extract_directory[96] = {0};
const char *base = strrchr(filename, '/');
if (base)
base++;
else
base = filename;
ESP_LOGI(TAG_ZIP, "[EMOJI_DEBUG] base (去除路径后)=%s", base);
snprintf(zip_file_path, sizeof(zip_file_path), "%s/%s", "/extflash", filename);
char pack_name[16] = {0};
size_t blen = strlen(base);
if (blen > 4 && strcasecmp(base + blen - 4, ".zip") == 0) {
strncpy(pack_name, base, blen - 4);
pack_name[blen - 4] = '\0';
} else {
strncpy(pack_name, base, sizeof(pack_name) - 1);
pack_name[sizeof(pack_name) - 1] = '\0';
}
snprintf(extract_directory, sizeof(extract_directory), "%s/%s", "/extflash", pack_name);
ESP_LOGI(TAG_ZIP, "[EMOJI_DEBUG] zip_file_path=%s", zip_file_path);
ESP_LOGI(TAG_ZIP, "[EMOJI_DEBUG] pack_name=%s", pack_name);
ESP_LOGI(TAG_ZIP, "[EMOJI_DEBUG] extract_directory=%s", extract_directory);
struct stat st;
if (stat(zip_file_path, &st) != 0) {
ESP_LOGE(TAG_ZIP, "[EMOJI_DEBUG] ZIP文件不存在: %s", zip_file_path);
set_system_event(DOWNLOAD_FAILED_BIT);
return;
}
ESP_LOGI(TAG_ZIP, "[EMOJI_DEBUG] 找到ZIP文件, 大小=%ld bytes", (long)st.st_size);
int mk_ret = mkdir(extract_directory, 0755);
ESP_LOGI(TAG_ZIP, "[EMOJI_DEBUG] mkdir(%s) 返回: %d", extract_directory, mk_ret);
extract_zip_file(zip_file_path, extract_directory);
ESP_LOGI(TAG_ZIP, "[EMOJI_DEBUG] ===== extract_json_zip 结束 =====");
}
+147
View File
@@ -0,0 +1,147 @@
#ifndef ZIP_EXTRACTOR_H
#define ZIP_EXTRACTOR_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include "esp_err.h"
/**
* @brief ZIP解压器配置参数
*/
typedef struct {
const char *zip_path; ///< ZIP文件路径
const char *extract_dir; ///< 解压目标目录
bool overwrite_existing; ///< 是否覆盖已存在的文件
bool create_directories; ///< 是否自动创建目录
bool skip_directories; ///< 是否跳过ZIP中的目录条目
} zip_extractor_config_t;
/**
* @brief 默认配置参数
*/
#define ZIP_EXTRACTOR_CONFIG_DEFAULT() { \
.zip_path = NULL, \
.extract_dir = NULL, \
.overwrite_existing = true, \
.create_directories = true, \
.skip_directories = true \
}
/**
* @brief 解压结果结构体
*/
typedef struct {
int total_files; ///< ZIP中总文件数
int extracted_files; ///< 成功解压的文件数
int skipped_files; ///< 跳过的文件数(目录或已存在且不覆盖)
int failed_files; ///< 解压失败的文件数
size_t total_size; ///< 解压的总字节数
} zip_extractor_result_t;
/**
* @brief 解压进度回调函数类型
* @param filename 当前正在处理的文件名
* @param current 当前处理的文件索引
* @param total 总文件数
* @param user_data 用户数据
*/
typedef void (*zip_extractor_progress_cb_t)(const char *filename, int current, int total, void *user_data);
/**
* @brief 初始化ZIP解压器
*
* @note 在使用其他函数前必须先调用此函数进行初始化
*
* @return esp_err_t ESP_OK表示成功,其他表示错误
*/
esp_err_t zip_extractor_init(void);
/**
* @brief 解压ZIP文件(使用配置结构体)
*
* @param config 解压配置参数
* @param result 解压结果(可选,可为NULL)
* @param progress_cb 进度回调函数(可选,可为NULL)
* @param user_data 传递给进度回调的用户数据(可选)
* @return esp_err_t ESP_OK表示成功,其他表示错误
*/
esp_err_t zip_extractor_extract(const zip_extractor_config_t *config,
zip_extractor_result_t *result,
zip_extractor_progress_cb_t progress_cb,
void *user_data);
/**
* @brief 解压ZIP文件(简化版本)
*
* @param zip_path ZIP文件路径
* @param extract_dir 解压目标目录
* @return esp_err_t ESP_OK表示成功,其他表示错误
*/
esp_err_t zip_extractor_extract_simple(const char *zip_path, const char *extract_dir);
/**
* @brief 获取ZIP文件信息
*
* @param zip_path ZIP文件路径
* @param file_count 返回文件数量(可选,可为NULL)
* @param total_size 返回总大小(字节,可选,可为NULL)
* @return esp_err_t ESP_OK表示成功,其他表示错误
*/
esp_err_t zip_extractor_get_info(const char *zip_path, int *file_count, size_t *total_size);
/**
* @brief 列出ZIP文件内容
*
* @param zip_path ZIP文件路径
* @param file_list 文件列表缓冲区(二维数组)
* @param max_files 最大文件数量
* @return int 实际列出的文件数量,负数表示错误
*/
int zip_extractor_list_files(const char *zip_path, char **file_list, int max_files);
/**
* @brief 检查ZIP文件是否有效
*
* @param zip_path ZIP文件路径
* @return bool true表示有效,false表示无效
*/
bool zip_extractor_is_valid(const char *zip_path);
/**
* @brief 释放ZIP解压器资源
*
* @note 在应用程序结束时调用
*/
void zip_extractor_cleanup(void);
// 特定于您需求的便捷函数
/**
* @brief 解压/extflash/json.zip到/extflash目录
*
* @return esp_err_t ESP_OK表示成功,其他表示错误
*/
void extract_json_zip(char* filename);
/**
* @brief 检查JSON ZIP文件是否存在且有效
*
* @return bool true表示存在且有效
*/
bool is_json_zip_available(void);
/**
* @brief 获取最近一次解压成功对应的表情包 ID(如 'A'/'B'/'C'/'D')
* 解压成功时在 extract_zip_file 内设置,供 main 识别下载成功并按路径播放动画
* @return char 表情包 ID,'0' 表示无效或未下载
*/
char zip_extractor_get_last_downloaded_pack(void);
#ifdef __cplusplus
}
#endif
#endif // ZIP_EXTRACTOR_H
+215
View File
@@ -0,0 +1,215 @@
#include <stdio.h>
#include <unistd.h>
#include <sys/lock.h>
#include <sys/param.h>
#include <string.h>
#include <math.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "freertos/event_groups.h"
#include "driver/gpio.h"
// #include "driver/i2c.h"
// #include "driver/i2c_master.h"
#include "driver/spi_master.h"
#include "esp_timer.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "esp_err.h"
#include "esp_log.h"
#include "lvgl.h"
#include "lv_demos.h"
#include "esp_lcd_qspi_amoled.h"
#include "esp_spiffs.h"
#include "sys/stat.h"
#include "driver/i2s.h"
#include "esp_afe_sr_models.h"
#include "esp_websocket_client.h"
#include "esp_mn_speech_commands.h"
#include "esp_mn_iface.h"
#include "esp_wn_iface.h"
#include "esp_wn_models.h"
#include "model_path.h"
#include "esp_mn_models.h"
#include "nvs_flash.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_mac.h"
// #include "BluFi.h"
// #include "WebSocket.h"
#include "FLASH/flash.h"
#include "OTA/ota_upgrade.h"
#include "MQTT/mqtt.h"
#include "cjson.h"
#include "miniz.h"
#include "MOTOR/motor.h"
#include "ZIP_EXTRACTOR/zip_extractor.h"
#include "SC7U22/SC7U22.h"
#include "event.h"
#include "esp_random.h"
#include "ES7243E/ES7243E.h"
// static const char *TAG = "example";
// 使用独特的TAG名称,避免冲突
extern const char *TAG_MAIN;
extern esp_lcd_panel_handle_t panel_handle;
#define LV_MEM_CUSTOM 1
#define LV_MEM_CUSTOM_INCLUDE "esp_heap_caps.h"
#define LV_MEM_CUSTOM_ALLOC(size) heap_caps_malloc(size, MALLOC_CAP_SPIRAM)
#define LV_MEM_CUSTOM_FREE(ptr) heap_caps_free(ptr)
#define LV_MEM_CUSTOM_REALLOC(ptr, size) heap_caps_realloc(ptr, size, MALLOC_CAP_SPIRAM)
#define LOTTIE_ANIM_WIDTH 516
#define LOTTIE_ANIM_HEIGHT 410
// #define LOTTIE_ANIM_WIDTH 200
// #define LOTTIE_ANIM_HEIGHT 200
#define EXAMPLE_LVGL_TASK_PRIORITY 7
#define FEED_TASK_PRIORITY 4
#define DETECT_TASK_PRIORITY 5
#define CHANGE_TASK_PRIORITY 7 //7
#define MAIN_TASK_PRIORITY 8 //5
#define BMI_TASK_PRIORITY 8
#define NST112_TASK_PRIORITY 8
#define MS523_TASK_PRIORITY 8
#define PET_TASK_PRIORITY 8
#define HOUR_WRITE_TASK_PRIORITY 8
// #define LVGL_TASK_STACK_SIZE (40 * 1024)
// #define FEED_TASK_STACK_SIZE (8 * 1024)
// #define DETECT_TASK_STACK_SIZE (8 * 1024)
// #define CHANGE_TASK_STACK_SIZE (30 * 1024)
// #define MAIN_TASK_STACK_SIZE (8 * 1024)
// #define BMI_TASK_STACK_SIZE (8192)
#define LVGL_TASK_STACK_SIZE (40 * 1024)
#define FEED_TASK_STACK_SIZE (16 * 1024)
#define DETECT_TASK_STACK_SIZE (32 * 1024)
#define CHANGE_TASK_STACK_SIZE (50 * 1024) //30 * 1024
#define MAIN_TASK_STACK_SIZE (16 * 1024)
#define BMI_TASK_STACK_SIZE (8192)
#define NST112_TASK_STACK_SIZE (8192)
#define MS523_TASK_STACK_SIZE (8192)
#define PET_TASK_STACK_SIZE (4096)
#define HOUR_WRITE_STACK_SIZE (4096)
#define LCD_HOST SPI2_HOST
// #define TOUCH_HOST I2C_NUM_1
#define TOUCH_HOST I2C_NUM_0
#if LV_COLOR_DEPTH == 16
#define LCD_BIT_PER_PIXEL (16)
#define LV_COLOR_FORMAT LV_COLOR_FORMAT_RGB565
#define LCD_RGB_ELEMENT_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#elif LV_COLOR_DEPTH == 24
#define LCD_BIT_PER_PIXEL (24)
#define LV_COLOR_FORMAT LV_COLOR_FORMAT_RGB888
#define LCD_RGB_ELEMENT_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#endif
#define EXAMPLE_USE_TOUCH 1 //开启或者关闭触摸
extern esp_lcd_panel_handle_t panel_handle;
extern esp_lcd_panel_io_handle_t io_handle;
#define CST816_ID 1
#define CST820_ID 2
#define CHSC6417_ID 3
#define EXAMPLE_LVGL_TICK_PERIOD_MS 2
#define EXAMPLE_LVGL_TASK_MAX_DELAY_MS 500
#define EXAMPLE_LVGL_TASK_MIN_DELAY_MS 1
#define EXAMPLE_LVGL_TASK_STACK_SIZE (4 * 1024 * 2)
#define AM196Q410502LK_196_410x502 1
#define AM178Q368448LK_178_368x448 2
#define AM151Q466466LK_151_466x466_C 3
#define AM160Q480480LK_160_480x480_C 4
#define AM201Q240296LK_201_240x296 5
#define AM200Q460460LK_200_460x460 6
#define AM160Q320360ZS_160_320x360 7
#define AM119Q390390FLS2_119_390x390 8
#define AM165Q348442FLS_165_348x442 9
#define CURRENT_SCREEN_SIZE AM196Q410502LK_196_410x502
#if CURRENT_SCREEN_SIZE == AM196Q410502LK_196_410x502
// #define EXAMPLE_PIN_NUM_LCD_CS (GPIO_NUM_40)
// #define EXAMPLE_PIN_NUM_LCD_PCLK (GPIO_NUM_21)
// #define EXAMPLE_PIN_NUM_LCD_DATA0 (GPIO_NUM_47)
// #define EXAMPLE_PIN_NUM_LCD_DATA1 (GPIO_NUM_45)
// #define EXAMPLE_PIN_NUM_LCD_DATA2 (GPIO_NUM_38)
// #define EXAMPLE_PIN_NUM_LCD_DATA3 (GPIO_NUM_39)
// #define EXAMPLE_PIN_NUM_LCD_RST (GPIO_NUM_41)
// #define EXAMPLE_PIN_NUM_LCD_CS (GPIO_NUM_8)
// #define EXAMPLE_PIN_NUM_LCD_PCLK (GPIO_NUM_3)
// #define EXAMPLE_PIN_NUM_LCD_DATA0 (GPIO_NUM_9)
// #define EXAMPLE_PIN_NUM_LCD_DATA1 (GPIO_NUM_46)
// #define EXAMPLE_PIN_NUM_LCD_DATA2 (GPIO_NUM_16)
// #define EXAMPLE_PIN_NUM_LCD_DATA3 (GPIO_NUM_15)
// #define EXAMPLE_PIN_NUM_LCD_RST (GPIO_NUM_17)
// #define EXAMPLE_PIN_NUM_LCD_TE (GPIO_NUM_18)
//#define EXAMPLE_PIN_NUM_LCD_CS (GPIO_NUM_7)
//#define EXAMPLE_PIN_NUM_LCD_PCLK (GPIO_NUM_1)
//#define EXAMPLE_PIN_NUM_LCD_DATA0 (GPIO_NUM_8)
//#define EXAMPLE_PIN_NUM_LCD_DATA1 (GPIO_NUM_10)
//#define EXAMPLE_PIN_NUM_LCD_DATA2 (GPIO_NUM_11)
//#define EXAMPLE_PIN_NUM_LCD_DATA3 (GPIO_NUM_6)
//#define EXAMPLE_PIN_NUM_LCD_RST (GPIO_NUM_20)
//#define EXAMPLE_PIN_NUM_LCD_TE (GPIO_NUM_21)
#define EXAMPLE_PIN_NUM_LCD_CS (GPIO_NUM_7)
#define EXAMPLE_PIN_NUM_LCD_PCLK (GPIO_NUM_9)
#define EXAMPLE_PIN_NUM_LCD_DATA0 (GPIO_NUM_8) //8
#define EXAMPLE_PIN_NUM_LCD_DATA1 (GPIO_NUM_10) //11
#define EXAMPLE_PIN_NUM_LCD_DATA2 (GPIO_NUM_11)
#define EXAMPLE_PIN_NUM_LCD_DATA3 (GPIO_NUM_6)
#define EXAMPLE_PIN_NUM_LCD_RST (GPIO_NUM_4)
#define EXAMPLE_PIN_NUM_LCD_TE (GPIO_NUM_5)
#define EXAMPLE_PIN_NUM_TOUCH_SCL (GPIO_NUM_51)
#define EXAMPLE_PIN_NUM_TOUCH_SDA (GPIO_NUM_50)
#define EXAMPLE_PIN_NUM_TOUCH_RST (GPIO_NUM_0)
#define EXAMPLE_PIN_NUM_TOUCH_INT (GPIO_NUM_1)
#define EXAMPLE_LCD_H_RES 410
#define EXAMPLE_LCD_V_RES 502
// #define EXAMPLE_LCD_H_RES 310
// #define EXAMPLE_LCD_V_RES 402
#define EXAMPLE_LCD_X_GAP 0
#define EXAMPLE_LCD_Y_GAP 22
#define EXAMPLE_TOUCH_MIRROR_X false
#define EXAMPLE_TOUCH_MIRROR_Y false
#define TOUCH_IC_CONFIG CST820_ID
#define AMOLED_QSPI_MODE 0
#define AMOLED_QSPI_MAX_PCLK 20 * 1000 * 1000
#define EXAMPLE_LVGL_BUF_HEIGHT (EXAMPLE_LCD_V_RES / 15)
#endif
#if TOUCH_IC_CONFIG == CST816_ID
#include "esp_lcd_touch_cst816.h"
#define TOUCH_IO_I2C_CONFIG ESP_LCD_TOUCH_IO_I2C_CST816_CONFIG
#define esp_lcd_touch_new_i2c esp_lcd_touch_new_i2c_cst816
#elif TOUCH_IC_CONFIG == CST820_ID
#include "esp_lcd_touch_cst820.h"
#define TOUCH_IO_I2C_CONFIG ESP_LCD_TOUCH_IO_I2C_CST820_CONFIG
#define esp_lcd_touch_new_i2c esp_lcd_touch_new_i2c_cst820
#elif TOUCH_IC_CONFIG == CHSC6417_ID
#include "esp_lcd_touch_chsc6417.h"
#define TOUCH_IO_I2C_CONFIG ESP_LCD_TOUCH_IO_I2C_CHSC6417_CONFIG
#define esp_lcd_touch_new_i2c esp_lcd_touch_new_i2c_chsc6417
#endif
extern esp_lcd_touch_handle_t tp;
+17
View File
@@ -0,0 +1,17 @@
dependencies:
espressif/esp_websocket_client: ^1.6.0
espressif/esp-sr: ^2.2.1
lvgl/lvgl: ^9.4.0
espressif/esp_lcd_co5300: ^1.0.0
espressif/esp_lcd_touch: ^1.1.2
espressif/esp_codec_dev: ^1.5.4
espressif/esp_lcd_qspi_amoled: ^0.1.0
protocol_examples_common:
path: ${IDF_PATH}/examples/common_components/protocol_examples_common
idf:
version: '>=4.1.0'
espressif/spi_nand_flash: ^0.20.0
espressif/esp_wifi_remote: '1.6.4'
espressif/esp_hosted: '3.0.7'
esphome/micro-decoder: '*'
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More