## 核心变更 ### 1. 双模式完全隔离 (Phase 2+4) - 拆分 InitializeButtons() 为 InitializeBadgeModeButtons() + InitializeAiModeButtons() - 构造函数按 device_mode 分支:吧唧模式不创建 PowerSaveTimer/BackgroundTask - 吧唧模式不注册音量/故事按键回调,避免调用 GetAudioCodec() 崩溃 - GPIO0 由 iot_button 统一处理,dzbj_button 仅注册 KEY2(GPIO4) - SetDeviceState() 中 background_task_ 空指针保护 ### 2. 吧唧模式 BOOT 按键崩溃修复 - 新增 dzbj_boot_click_handler()(C 函数,避免 lvgl.h 与 display.h 冲突) - 移植 dzbj 的唤醒屏幕/退出手电筒/返回Home 完整逻辑 ### 3. esp_timer 阻塞 LVGL 渲染修复 - iot_button 回调在 esp_timer 任务中执行,vTaskDelay 会阻塞 lv_tick_inc - 改为 xTaskCreate 派发到独立 FreeRTOS 任务,避免冻结 LVGL 渲染 ### 4. 触摸坐标日志 + SPIFFS 预烧录 - esp_lvgl_port_touch.c 添加触摸坐标打印 - CMakeLists.txt 添加 spiffs_create_partition_image 自动打包 spiffs_image/ ### 5. dzbj 模块文件新增 - device_mode: NVS 设备模式管理 (AI=0/吧唧=1) - dzbj_button: GPIO4 KEY2 中断 + BOOT 点击处理 - dzbj_ble: BLE GATT 图传服务 (0x0B00) - dzbj_battery: ADC 电池电压监测 - sleep_mgr: 10s 超时熄屏低功耗管理 - pages: 图片浏览/GIF播放/PWM亮度 - fatfs: SPIFFS 文件管理 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
156 lines
4.6 KiB
C
156 lines
4.6 KiB
C
#include "dzbj_button.h"
|
||
#include "driver/gpio.h"
|
||
#include "esp_log.h"
|
||
#include "esp_timer.h"
|
||
#include "freertos/FreeRTOS.h"
|
||
#include "freertos/task.h"
|
||
#include "freertos/queue.h"
|
||
|
||
static const char *TAG = "DZBJ_BTN";
|
||
|
||
// 去抖间隔(微秒)
|
||
#define DEBOUNCE_US 200000
|
||
|
||
// 按键事件队列
|
||
static QueueHandle_t btn_evt_queue = NULL;
|
||
|
||
// 回调存储
|
||
typedef struct {
|
||
btn_event_cb_t cb;
|
||
void *usr_data;
|
||
} btn_cb_t;
|
||
|
||
static btn_cb_t boot_cb = {0};
|
||
static btn_cb_t key2_cb = {0};
|
||
|
||
// 去抖时间戳
|
||
static int64_t last_boot_us = 0;
|
||
static int64_t last_key2_us = 0;
|
||
|
||
// GPIO中断服务函数(ISR中不做耗时操作,仅发送事件到队列)
|
||
static void IRAM_ATTR gpio_isr_handler(void *arg)
|
||
{
|
||
int gpio_num = (int)arg;
|
||
xQueueSendFromISR(btn_evt_queue, &gpio_num, NULL);
|
||
}
|
||
|
||
// 按键事件处理任务
|
||
static void btn_task(void *pvParameters)
|
||
{
|
||
int gpio_num;
|
||
while (1) {
|
||
if (xQueueReceive(btn_evt_queue, &gpio_num, portMAX_DELAY)) {
|
||
int64_t now = esp_timer_get_time();
|
||
|
||
// BOOT(GPIO0) 由 iot_button 处理,这里仅处理 KEY2
|
||
if (gpio_num == PIN_BTN_KEY2) {
|
||
if (now - last_key2_us > DEBOUNCE_US) {
|
||
last_key2_us = now;
|
||
ESP_LOGI(TAG, "KEY2按键按下 (GPIO%d)", gpio_num);
|
||
if (key2_cb.cb) {
|
||
key2_cb.cb(gpio_num, key2_cb.usr_data);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
esp_err_t dzbj_button_init(void)
|
||
{
|
||
btn_evt_queue = xQueueCreate(10, sizeof(int));
|
||
|
||
// 仅配置 KEY2(GPIO4),BOOT(GPIO0) 由 iot_button 统一处理
|
||
gpio_config_t io_conf = {
|
||
.pin_bit_mask = (1ULL << PIN_BTN_KEY2),
|
||
.mode = GPIO_MODE_INPUT,
|
||
.pull_up_en = GPIO_PULLUP_ENABLE,
|
||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||
.intr_type = GPIO_INTR_NEGEDGE,
|
||
};
|
||
gpio_config(&io_conf);
|
||
|
||
// 安装GPIO中断服务(如果已安装则跳过)
|
||
esp_err_t ret = gpio_install_isr_service(0);
|
||
if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) {
|
||
ESP_LOGE(TAG, "GPIO ISR服务安装失败");
|
||
return ret;
|
||
}
|
||
|
||
gpio_isr_handler_add(PIN_BTN_KEY2, gpio_isr_handler, (void *)PIN_BTN_KEY2);
|
||
|
||
// 按键处理任务
|
||
xTaskCreate(btn_task, "btn_task", 3072, NULL, 5, NULL);
|
||
|
||
ESP_LOGI(TAG, "按键初始化完成 (KEY2=GPIO%d,BOOT由iot_button处理)", PIN_BTN_KEY2);
|
||
return ESP_OK;
|
||
}
|
||
|
||
void dzbj_button_on_boot_press(btn_event_cb_t cb, void *usr_data)
|
||
{
|
||
boot_cb.cb = cb;
|
||
boot_cb.usr_data = usr_data;
|
||
}
|
||
|
||
void dzbj_button_on_key2_press(btn_event_cb_t cb, void *usr_data)
|
||
{
|
||
key2_cb.cb = cb;
|
||
key2_cb.usr_data = usr_data;
|
||
}
|
||
|
||
// === 吧唧模式 BOOT 单击处理(从 dzbj main.c boot_btn_handler 移植) ===
|
||
#include "lvgl.h"
|
||
#include "sleep_mgr/include/sleep_mgr.h"
|
||
|
||
// UI 函数前向声明
|
||
extern lv_obj_t *ui_ScreenHome;
|
||
extern lv_obj_t *ui_ScreenImg;
|
||
extern void ui_ScreenHome_screen_init(void);
|
||
extern void ui_ScreenImg_hide_delete_container(void);
|
||
extern void _ui_screen_change(lv_obj_t **target, lv_scr_load_anim_t fademode, int spd, int delay, void (*target_init)(void));
|
||
extern bool flashlight_is_active(void);
|
||
extern uint8_t flashlight_get_saved_brightness(void);
|
||
extern void flashlight_exit(void);
|
||
extern void pwm_set_brightness(uint8_t percent);
|
||
|
||
void dzbj_boot_click_handler(void)
|
||
{
|
||
bool screen_was_off = sleep_mgr_is_screen_off();
|
||
|
||
if (screen_was_off) {
|
||
// 低功耗模式:只唤醒屏幕
|
||
ESP_LOGI(TAG, "吧唧模式 BOOT:低功耗模式,仅唤醒屏幕");
|
||
sleep_mgr_notify_activity();
|
||
} else {
|
||
// 正常模式:退出手电筒 + 返回ScreenHome
|
||
ESP_LOGI(TAG, "吧唧模式 BOOT:返回ScreenHome");
|
||
|
||
// 如果在ScreenImg界面,先隐藏删除容器
|
||
lv_obj_t *current_screen = lv_scr_act();
|
||
if (current_screen == ui_ScreenImg) {
|
||
ui_ScreenImg_hide_delete_container();
|
||
}
|
||
|
||
sleep_mgr_notify_activity();
|
||
|
||
// 退出手电筒
|
||
bool was_flashlight = flashlight_is_active();
|
||
uint8_t saved_brightness = 0;
|
||
if (was_flashlight) {
|
||
saved_brightness = flashlight_get_saved_brightness();
|
||
flashlight_exit();
|
||
vTaskDelay(pdMS_TO_TICKS(80));
|
||
}
|
||
|
||
// 切换到Home界面
|
||
_ui_screen_change(&ui_ScreenHome, LV_SCR_LOAD_ANIM_NONE, 0, 0, &ui_ScreenHome_screen_init);
|
||
|
||
// 手电筒退出后恢复亮度
|
||
if (was_flashlight) {
|
||
vTaskDelay(pdMS_TO_TICKS(150));
|
||
pwm_set_brightness(saved_brightness);
|
||
ESP_LOGI(TAG, "亮度已恢复到%d%%", saved_brightness);
|
||
}
|
||
}
|
||
}
|