Baji_Rtc_Toy/main/dzbj/dzbj_button.c
Rdzleo 14776acb0a feat: 完成 AI/吧唧双模式完全隔离重构 + 触摸坐标日志 + SPIFFS 预烧录
## 核心变更

### 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>
2026-02-28 10:23:04 +08:00

156 lines
4.6 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "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%dBOOT由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);
}
}
}