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>
This commit is contained in:
parent
bcfd35b9b8
commit
14776acb0a
558
05-最新日志.txt
558
05-最新日志.txt
@ -1,207 +1,44 @@
|
||||
rdzleo@RdzleodeMac-Studio Baji_Rtc_Toy % '/Users/rdzleo/.espressif/python_env/idf5.4_py3.13_env/bin/python3' '/Users/rdzleo/esp/esp-idf/v5.4.2/esp-idf/t
|
||||
ools/idf_monitor.py' -p /dev/tty.usbmodem834401 -b 115200 --toolchain-prefix xtensa-esp32s3-elf- --make ''/Users/rdzleo/.espressif/python_env/idf5.4_py3
|
||||
.13_env/bin/python3' '/Users/rdzleo/esp/esp-idf/v5.4.2/esp-idf/tools/idf.py'' --target esp32s3 '/Users/rdzleo/Desktop/Baji_Rtc_Toy/build/kapi.elf'
|
||||
--- Warning: Serial ports accessed as /dev/tty.* will hang gdb if launched.
|
||||
--- Using /dev/cu.usbmodem834401 instead...
|
||||
--- esp-idf-monitor 1.8.0 on /dev/cu.usbmodem834401 115200
|
||||
--- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H
|
||||
I (49) WeatherApi: 初始化天气API配置 - 默认城市: 北京
|
||||
I (49) WeatherApi: WiFi位置缓存限制已设置为: 5 条
|
||||
I (50) coexist: coex firmware version: 831ec70
|
||||
I (50) coexist: coexist rom version e7ae62f
|
||||
I (51) main_task: Started on CPU0
|
||||
I (61) main_task: Calling app_main()
|
||||
I (81) Application: 🎴 吧唧模式:跳过 WiFi/协议/音频初始化
|
||||
I (81) Application: 打印设置设备状态日志: idle
|
||||
|
||||
abort() was called at PC 0x421cd333 on core 0
|
||||
.--- 0x421cd333: __cxxabiv1::__terminate(void (*)()) at /builds/idf/crosstool-NG/.build/xtensa-esp-elf/src/gcc/libstdc++-v3/libsupc++/eh_terminate.cc:48
|
||||
|
||||
|
||||
Backtrace: 0x40379f49:0x3fcbcbe0 0x4038731d:0x3fcbcc00 0x4038f9d9:0x3fcbcc20 0x421cd333:0x3fcbcc90 0x421cd368:0x3fcbccb0 0x421cd443:0x3fcbccd0 0x421ddb09:0x3fcbccf0 0x42013a05:0x3fcbcd30 0x4201bf6f:0x3fcbcd50 0x420174b6:0x3fcbcd80 0x42018c69:0x3fcbcde0 0x4202069d:0x3fcbd370 0x42229453:0x3fcbd390 0x40387e11:0x3fcbd3c0
|
||||
--- 0x40379f49: panic_abort at /Users/rdzleo/esp/esp-idf/components/esp_system/panic.c:469
|
||||
--- 0x4038731d: esp_system_abort at /Users/rdzleo/esp/esp-idf/components/esp_system/port/esp_system_chip.c:87
|
||||
--- 0x4038f9d9: abort at /Users/rdzleo/esp/esp-idf/components/newlib/abort.c:38
|
||||
--- 0x421cd333: __cxxabiv1::__terminate(void (*)()) at /builds/idf/crosstool-NG/.build/xtensa-esp-elf/src/gcc/libstdc++-v3/libsupc++/eh_terminate.cc:48
|
||||
--- 0x421cd368: std::terminate() at /builds/idf/crosstool-NG/.build/xtensa-esp-elf/src/gcc/libstdc++-v3/libsupc++/eh_terminate.cc:58
|
||||
--- 0x421cd443: __cxa_throw at /builds/idf/crosstool-NG/.build/xtensa-esp-elf/src/gcc/libstdc++-v3/libsupc++/eh_throw.cc:98
|
||||
--- 0x421ddb09: std::__throw_system_error(int) at /builds/idf/crosstool-NG/.build/xtensa-esp-elf/src/gcc/libstdc++-v3/src/c++11/system_error.cc:595
|
||||
--- 0x42013a05: std::unique_lock<std::mutex>::lock() at /Users/rdzleo/.espressif/tools/xtensa-esp-elf/esp-14.2.0_20241119/xtensa-esp-elf/xtensa-esp-elf/include/c++/14.2.0/bits/unique_lock.h:142
|
||||
--- 0x4201bf6f: std::unique_lock<std::mutex>::unique_lock(std::mutex&) at /Users/rdzleo/.espressif/tools/xtensa-esp-elf/esp-14.2.0_20241119/xtensa-esp-elf/xtensa-esp-elf/include/c++/14.2.0/bits/unique_lock.h:73
|
||||
--- (inlined by) BackgroundTask::WaitForCompletion() at /Users/rdzleo/Desktop/Baji_Rtc_Toy/main/background_task.cc:46
|
||||
--- 0x420174b6: Application::SetDeviceState(DeviceState) at /Users/rdzleo/Desktop/Baji_Rtc_Toy/main/application.cc:2262
|
||||
--- 0x42018c69: Application::Start() at /Users/rdzleo/Desktop/Baji_Rtc_Toy/main/application.cc:533
|
||||
--- 0x4202069d: app_main at /Users/rdzleo/Desktop/Baji_Rtc_Toy/main/main.cc:105
|
||||
--- 0x42229453: main_task at /Users/rdzleo/esp/esp-idf/components/freertos/app_startup.c:208
|
||||
--- 0x40387e11: vPortTaskWrapper at /Users/rdzleo/esp/esp-idf/components/freertos/FreeRTOS-Kernel/portable/xtensa/port.c:139
|
||||
|
||||
|
||||
|
||||
|
||||
ELF file SHA256: 0061c1350
|
||||
|
||||
Rebooting...
|
||||
ESP-ROM:esp32s3-20210327
|
||||
Build:Mar 27 2021
|
||||
rst:0x15 (USB_UART_CHIP_RESET),boot:0xb (SPI_FAST_FLASH_BOOT)
|
||||
Saved PC:0x42143b2f
|
||||
--- 0x42143b2f: timer_process_alarm at /Users/rdzleo/esp/esp-idf/components/esp_timer/src/esp_timer.c:413
|
||||
SPIWP:0xee
|
||||
mode:DIO, clock div:1
|
||||
load:0x3fce2820,len:0x56c
|
||||
load:0x403c8700,len:0x4
|
||||
load:0x403c8704,len:0xb88
|
||||
load:0x403cb700,len:0x2df4
|
||||
entry 0x403c88f4
|
||||
I (50) WeatherApi: 初始化天气API配置 - 默认城市: 北京
|
||||
I (50) WeatherApi: WiFi位置缓存限制已设置为: 5 条
|
||||
I (51) coexist: coex firmware version: 831ec70
|
||||
I (51) coexist: coexist rom version e7ae62f
|
||||
I (52) main_task: Started on CPU0
|
||||
I (62) main_task: Calling app_main()
|
||||
I (82) BackgroundTask: background_task started
|
||||
I (82) BluetoothProvisioning: 蓝牙配网对象创建完成
|
||||
I (82) button: IoT Button Version: 3.5.0
|
||||
I (82) gpio: GPIO[0]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (82) button: IoT Button Version: 3.5.0
|
||||
I (82) gpio: GPIO[4]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (82) Airhub1: 初始化按钮...
|
||||
I (82) Airhub1: Boot button initialized on GPIO0
|
||||
I (82) Airhub1: Volume up button initialized on GPIO-1
|
||||
I (82) Airhub1: Volume down button initialized on GPIO-1
|
||||
I (82) Airhub1: 故事按键已初始化,GPIO引脚 =4
|
||||
I (82) Airhub1: 所有按键已成功初始化!
|
||||
I (82) Airhub1: Initializing I2C master bus for audio codec...
|
||||
I (82) Airhub1: Scanning I2C bus for devices...
|
||||
I (82) Airhub1: I2C设备在线: 0x18
|
||||
I (82) Airhub1: I2C设备在线: 0x40
|
||||
I (82) Airhub1: I2C scan completed. Found 2 devices
|
||||
I (82) DZBJ: 开始初始化 dzbj 显示模块...
|
||||
I (82) gpio: GPIO[7]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
|
||||
I (82) st77916: LCD panel create success, version: 1.0.1
|
||||
W (212) st77916: The 3Ah command has been used and will be overwritten by external initialization sequence
|
||||
I (332) LCD: LCD GRAM cleared (black filled)
|
||||
I (332) DZBJ: LCD 硬件初始化完成
|
||||
I (332) gpio: GPIO[5]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:2
|
||||
I (332) gpio: GPIO[6]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
|
||||
I (732) CST816S: IC id: 182
|
||||
I (732) LCD: Touch controller initialized successfully
|
||||
I (732) LVGL: Starting LVGL task
|
||||
I (732) LCD: LVGL buffer: 14400 bytes (W:360, Lines:20, DMA, single)
|
||||
I (732) LCD: Touch controller added to LVGL
|
||||
I (732) DZBJ: LVGL 初始化完成
|
||||
I (742) DZBJ: UI 初始化完成
|
||||
I (842) DZBJ: 背光已点亮,dzbj 显示模块初始化完成
|
||||
I (842) Airhub1: IMU传感器未初始化,跳过IoT注册
|
||||
I (842) Airhub1: Initializing battery monitor...
|
||||
I (842) Airhub1: ADC calibration initialized
|
||||
I (842) Airhub1: 电池状态监控已初始化,GPIO:3
|
||||
I (842) Airhub1: 非生产测试模式且不在对话状态,姿态传感器业务已禁用以节约资源
|
||||
I (842) PowerSaveTimer: Power save timer enabled
|
||||
I (842) Airhub1: 🔋 PowerSaveTimer已启用,20秒无活动将进入低功耗模式
|
||||
I (842) Airhub1: 电容触摸板按钮已禁用 (ENABLE_TOUCH_PAD_BUTTONS=0)
|
||||
I (842) Application: 打印设置设备状态日志: starting
|
||||
I (842) Application: 正常启动流程,将执行开机播报和网络连接播报
|
||||
I (842) Airhub1: Initializing audio codec (duplex)...
|
||||
I (842) Airhub1: Creating BoxAudioCodec (ES8311+ES7210, without reference) ...
|
||||
I (842) BoxAudioCodec: Duplex channels created
|
||||
I (852) ES8311: Work in Slave mode
|
||||
I (852) gpio: GPIO[48]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
|
||||
I (852) ES7210: Work in Slave mode
|
||||
I (862) ES7210: Enable ES7210_INPUT_MIC1
|
||||
I (862) ES7210: Enable ES7210_INPUT_MIC2
|
||||
I (872) BoxAudioCodec: BoxAudioDevice initialized (duplex)
|
||||
I (872) Airhub1: Audio codec initialized successfully
|
||||
I (872) Application: 检测到WiFi板卡,将opus编码器复杂度设置为3
|
||||
I (872) OpusResampler: Resampler configured with input sample rate 16000, output sample rate 8000, and channels 1
|
||||
I (872) I2S_IF: channel mode 2 bits:16/16 channel:2 mask:1
|
||||
I (872) I2S_IF: TDM Mode 0 bits:16/16 channel:2 sample_rate:16000 mask:1
|
||||
I (872) I2S_IF: channel mode 0 bits:16/16 channel:2 mask:1
|
||||
I (872) I2S_IF: STD Mode 1 bits:16/16 channel:2 sample_rate:16000 mask:1
|
||||
I (872) ES7210: Bits 16
|
||||
I (882) ES7210: Enable ES7210_INPUT_MIC1
|
||||
I (882) ES7210: Enable ES7210_INPUT_MIC2
|
||||
I (892) ES7210: Unmuted
|
||||
I (892) Adev_Codec: Open codec device OK
|
||||
I (892) BoxAudioCodec: Input opened: sr=16000 ch=1 mask=0x1 ref=0
|
||||
I (892) AudioCodec: Set input enable to true
|
||||
I (892) I2S_IF: channel mode 0 bits:16/16 channel:2 mask:1
|
||||
I (892) I2S_IF: STD Mode 1 bits:16/16 channel:2 sample_rate:16000 mask:1
|
||||
I (912) Adev_Codec: Open codec device OK
|
||||
I (912) AudioCodec: Set output enable to true
|
||||
I (912) AudioCodec: Audio codec started
|
||||
I (1012) Airhub1: ADC: 2370, 原始电压: 2.37V, 计算电池电压: 10.29V, 电量: 100%, 满电电压: 4.20V
|
||||
I (1012) BluetoothMAC: Bluetooth MAC Address: d0:cf:13:03:bb:f2
|
||||
I (1112) Airhub1: ADC: 2368, 原始电压: 2.37V, 计算电池电压: 10.28V, 电量: 100%, 满电电压: 4.20V
|
||||
I (1112) BluetoothMAC: Bluetooth MAC Address: d0:cf:13:03:bb:f2
|
||||
I (1212) Airhub1: ADC: 2367, 原始电压: 2.37V, 计算电池电压: 10.27V, 电量: 100%, 满电电压: 4.20V
|
||||
I (1212) BluetoothMAC: Bluetooth MAC Address: d0:cf:13:03:bb:f2
|
||||
I (1322) Airhub1: ADC: 2367, 原始电压: 2.37V, 计算电池电压: 10.27V, 电量: 100%, 满电电压: 4.20V
|
||||
I (1322) BluetoothMAC: Bluetooth MAC Address: d0:cf:13:03:bb:f2
|
||||
I (1422) Airhub1: ADC: 2367, 原始电压: 2.37V, 计算电池电压: 10.27V, 电量: 100%, 满电电压: 4.20V
|
||||
I (1422) BluetoothMAC: Bluetooth MAC Address: d0:cf:13:03:bb:f2
|
||||
I (1522) Airhub1: ADC: 2367, 原始电压: 2.37V, 计算电池电压: 10.27V, 电量: 100%, 满电电压: 4.20V
|
||||
I (1522) BluetoothMAC: Bluetooth MAC Address: d0:cf:13:03:bb:f2
|
||||
I (1622) Airhub1: ADC: 2367, 原始电压: 2.37V, 计算电池电压: 10.27V, 电量: 100%, 满电电压: 4.20V
|
||||
I (1622) BluetoothMAC: Bluetooth MAC Address: d0:cf:13:03:bb:f2
|
||||
I (1722) Airhub1: ADC: 2367, 原始电压: 2.37V, 计算电池电压: 10.27V, 电量: 100%, 满电电压: 4.20V
|
||||
I (1722) BluetoothMAC: Bluetooth MAC Address: d0:cf:13:03:bb:f2
|
||||
I (1822) Airhub1: ADC: 2367, 原始电压: 2.37V, 计算电池电压: 10.27V, 电量: 100%, 满电电压: 4.20V
|
||||
I (1822) BluetoothMAC: Bluetooth MAC Address: d0:cf:13:03:bb:f2
|
||||
I (1922) Airhub1: ADC: 2367, 原始电压: 2.37V, 计算电池电压: 10.27V, 电量: 100%, 满电电压: 4.20V
|
||||
I (1922) BluetoothMAC: Bluetooth MAC Address: d0:cf:13:03:bb:f2
|
||||
I (1942) Airhub1: ADC: 2367, 原始电压: 2.37V, 计算电池电压: 10.27V, 电量: 100%, 满电电压: 4.20V
|
||||
I (1942) BluetoothMAC: Bluetooth MAC Address: d0:cf:13:03:bb:f2
|
||||
I (2022) Airhub1: ADC: 2367, 原始电压: 2.37V, 计算电池电压: 10.27V, 电量: 100%, 满电电压: 4.20V
|
||||
I (2022) BluetoothMAC: Bluetooth MAC Address: d0:cf:13:03:bb:f2
|
||||
I (2122) Airhub1: ADC: 2367, 原始电压: 2.37V, 计算电池电压: 10.27V, 电量: 100%, 满电电压: 4.20V
|
||||
I (2122) BluetoothMAC: Bluetooth MAC Address: d0:cf:13:03:bb:f2
|
||||
I (2222) Airhub1: ADC: 2367, 原始电压: 2.37V, 计算电池电压: 10.27V, 电量: 100%, 满电电压: 4.20V
|
||||
I (2222) BluetoothMAC: Bluetooth MAC Address: d0:cf:13:03:bb:f2
|
||||
I (2322) Airhub1: ADC: 2367, 原始电压: 2.37V, 计算电池电压: 10.27V, 电量: 100%, 满电电压: 4.20V
|
||||
I (2322) BluetoothMAC: Bluetooth MAC Address: d0:cf:13:03:bb:f2
|
||||
I (2422) Airhub1: ADC: 2367, 原始电压: 2.37V, 计算电池电压: 10.27V, 电量: 100%, 满电电压: 4.20V
|
||||
I (2422) BluetoothMAC: Bluetooth MAC Address: d0:cf:13:03:bb:f2
|
||||
I (2522) Airhub1: ADC: 2367, 原始电压: 2.37V, 计算电池电压: 10.27V, 电量: 100%, 满电电压: 4.20V
|
||||
I (2522) BluetoothMAC: Bluetooth MAC Address: d0:cf:13:03:bb:f2
|
||||
I (2622) Airhub1: ADC: 2367, 原始电压: 2.37V, 计算电池电压: 10.27V, 电量: 100%, 满电电压: 4.20V
|
||||
I (2622) BluetoothMAC: Bluetooth MAC Address: d0:cf:13:03:bb:f2
|
||||
I (2722) Airhub1: ADC: 2367, 原始电压: 2.37V, 计算电池电压: 10.27V, 电量: 100%, 满电电压: 4.20V
|
||||
I (2722) BluetoothMAC: Bluetooth MAC Address: d0:cf:13:03:bb:f2
|
||||
I (2822) Airhub1: ADC: 2367, 原始电压: 2.37V, 计算电池电压: 10.27V, 电量: 100%, 满电电压: 4.20V
|
||||
I (2822) BluetoothMAC: Bluetooth MAC Address: d0:cf:13:03:bb:f2
|
||||
I (2922) Airhub1: ADC: 2367, 原始电压: 2.37V, 计算电池电压: 10.27V, 电量: 100%, 满电电压: 4.20V
|
||||
I (2922) BluetoothMAC: Bluetooth MAC Address: d0:cf:13:03:bb:f2
|
||||
I (2922) AudioCodec: 将运行时输出音量设置为:80
|
||||
I (2922) Application: 设备启动完成,播放开机播报语音
|
||||
I (2922) pp: pp rom version: e7ae62f
|
||||
I (2922) net80211: net80211 rom version: e7ae62f
|
||||
I (2932) wifi:wifi driver task: 3fce5690, prio:23, stack:6656, core=0
|
||||
I (2932) wifi:wifi firmware version: 3263cda
|
||||
I (2932) wifi:wifi certification version: v7.0
|
||||
I (2932) wifi:config NVS flash: disabled
|
||||
I (2932) wifi:config nano formatting: disabled
|
||||
I (2932) wifi:Init data frame dynamic rx buffer num: 32
|
||||
I (2932) wifi:Init dynamic rx mgmt buffer num: 5
|
||||
I (2932) wifi:Init management short buffer num: 32
|
||||
I (2932) wifi:Init static tx buffer num: 8
|
||||
I (2932) wifi:Init tx cache buffer num: 32
|
||||
I (2932) wifi:Init static tx FG buffer num: 2
|
||||
I (2932) wifi:Init static rx buffer size: 1600
|
||||
I (2932) wifi:Init static rx buffer num: 10
|
||||
I (2932) wifi:Init dynamic rx buffer num: 32
|
||||
I (2932) wifi_init: rx ba win: 16
|
||||
I (2932) wifi_init: accept mbox: 6
|
||||
I (2932) wifi_init: tcpip mbox: 32
|
||||
I (2932) wifi_init: udp mbox: 6
|
||||
I (2932) wifi_init: tcp mbox: 6
|
||||
I (2932) wifi_init: tcp tx win: 5760
|
||||
I (2932) wifi_init: tcp rx win: 5760
|
||||
I (2932) wifi_init: tcp mss: 1440
|
||||
I (2932) wifi_init: WiFi/LWIP prefer SPIRAM
|
||||
I (2932) phy_init: phy_version 701,f4f1da3a,Mar 3 2025,15:50:10
|
||||
I (2972) phy_init: Saving new calibration data due to checksum failure or outdated calibration data, mode(0)
|
||||
I (2972) Application: 开始播放下行音频: 样本=960 采样率=16000
|
||||
I (3022) wifi:mode : sta (d0:cf:13:03:bb:f0)
|
||||
I (3022) wifi:enable tsf
|
||||
I (5432) wifi: 发现可连接 AP: airhub, BSSID: 70:2a:d7:85:bc:eb, RSSI: -36, Channel: 1, Authmode: 3
|
||||
I (5432) WifiBoard: Starting WiFi connection, playing network connection sound
|
||||
W (5432) wifi:Password length matches WPA2 standards, authmode threshold changes from OPEN to WPA2
|
||||
I (5522) wifi:new:<1,0>, old:<1,0>, ap:<255,255>, sta:<1,0>, prof:1, snd_ch_cfg:0x0
|
||||
I (5522) wifi:state: init -> auth (0xb0)
|
||||
I (5532) wifi:state: auth -> assoc (0x0)
|
||||
I (5542) wifi:state: assoc -> run (0x10)
|
||||
I (5582) wifi:connected with airhub, aid = 3, channel 1, BW20, bssid = 70:2a:d7:85:bc:eb
|
||||
I (5582) wifi:security: WPA2-PSK, phy: bgn, rssi: -38
|
||||
I (5582) wifi:pm start, type: 1
|
||||
|
||||
I (5582) wifi:dp: 1, bi: 102400, li: 3, scale listen interval from 307200 us to 307200 us
|
||||
I (5582) wifi:set rx beacon pti, rx_bcn_pti: 14, bcn_timeout: 25000, mt_pti: 14, mt_time: 10000
|
||||
I (5682) wifi:AP's beacon interval = 102400 us, DTIM period = 1
|
||||
I (5842) Airhub1: 📤 设备状态上报已启用,每30秒上报一次
|
||||
I (6992) Airhub1: BOOT button clicked
|
||||
I (6992) Airhub1: 🔄 BOOT按键触发:设备状态=1,WiFi连接状态=未连接
|
||||
I (6992) Airhub1: 🔄 开始重置WiFi配置,清除已保存的WiFi凭据
|
||||
I (6992) wifi:state: run -> init (0x0)
|
||||
I (6992) wifi:pm stop, total sleep time: 1072363 us / 1413544 us
|
||||
|
||||
I (6992) wifi:new:<1,0>, old:<1,0>, ap:<255,255>, sta:<1,0>, prof:1, snd_ch_cfg:0x0
|
||||
I (6992) wifi: Reconnecting airhub (attempt 1 / 5)
|
||||
I (7102) wifi:flush txq
|
||||
I (7102) wifi:stop sw txq
|
||||
I (7102) wifi:lmac stop hw txq
|
||||
I (7102) Airhub1: ✅ 已清除所有WiFi凭据,设备将进入配网模式
|
||||
I (7102) WifiBoard: 🔄 重置WiFi配置,设备将重启进入配网模式
|
||||
I (7612) WifiBoard: 🔄 正在重启设备...
|
||||
ESP-ROM:esp32s3-20210327
|
||||
Build:Mar 27 2021
|
||||
rst:0xc (RTC_SW_CPU_RST),boot:0xb (SPI_FAST_FLASH_BOOT)
|
||||
Saved PC:0x40379e85
|
||||
--- 0x40379e85: esp_restart_noos at /Users/rdzleo/esp/esp-idf/components/esp_system/port/soc/esp32s3/system_internal.c:162
|
||||
rst:0xc (RTC_SW_CPU_RST),boot:0x2b (SPI_FAST_FLASH_BOOT)
|
||||
Saved PC:0x40379e89
|
||||
--- 0x40379e89: esp_restart_noos at /Users/rdzleo/esp/esp-idf/components/esp_system/port/soc/esp32s3/system_internal.c:162
|
||||
SPIWP:0xee
|
||||
mode:DIO, clock div:1
|
||||
load:0x3fce2820,len:0x56c
|
||||
@ -210,307 +47,38 @@ load:0x403c8704,len:0xb88
|
||||
load:0x403cb700,len:0x2df4
|
||||
entry 0x403c88f4
|
||||
I (49) WeatherApi: 初始化天气API配置 - 默认城市: 北京
|
||||
I (50) WeatherApi: WiFi位置缓存限制已设置为: 5 条
|
||||
I (49) WeatherApi: WiFi位置缓存限制已设置为: 5 条
|
||||
I (50) coexist: coex firmware version: 831ec70
|
||||
I (51) coexist: coexist rom version e7ae62f
|
||||
I (50) coexist: coexist rom version e7ae62f
|
||||
I (51) main_task: Started on CPU0
|
||||
I (61) main_task: Calling app_main()
|
||||
I (81) BackgroundTask: background_task started
|
||||
I (81) BluetoothProvisioning: 蓝牙配网对象创建完成
|
||||
I (81) WifiBoard: force_ap is set to 1, will clear in StartNetwork()
|
||||
I (81) button: IoT Button Version: 3.5.0
|
||||
I (81) gpio: GPIO[0]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (81) button: IoT Button Version: 3.5.0
|
||||
I (81) gpio: GPIO[4]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (81) Airhub1: 初始化按钮...
|
||||
I (81) Airhub1: Boot button initialized on GPIO0
|
||||
I (81) Airhub1: Volume up button initialized on GPIO-1
|
||||
I (81) Airhub1: Volume down button initialized on GPIO-1
|
||||
I (81) Airhub1: 故事按键已初始化,GPIO引脚 =4
|
||||
I (81) Airhub1: 所有按键已成功初始化!
|
||||
I (81) Airhub1: Initializing I2C master bus for audio codec...
|
||||
I (81) Airhub1: Scanning I2C bus for devices...
|
||||
I (81) Airhub1: I2C设备在线: 0x18
|
||||
I (81) Airhub1: I2C设备在线: 0x40
|
||||
I (81) Airhub1: I2C scan completed. Found 2 devices
|
||||
I (81) DZBJ: 开始初始化 dzbj 显示模块...
|
||||
I (81) gpio: GPIO[7]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
|
||||
I (81) st77916: LCD panel create success, version: 1.0.1
|
||||
W (211) st77916: The 3Ah command has been used and will be overwritten by external initialization sequence
|
||||
I (331) LCD: LCD GRAM cleared (black filled)
|
||||
I (331) DZBJ: LCD 硬件初始化完成
|
||||
I (331) gpio: GPIO[5]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:2
|
||||
I (331) gpio: GPIO[6]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
|
||||
I (731) CST816S: IC id: 182
|
||||
I (731) LCD: Touch controller initialized successfully
|
||||
I (731) LVGL: Starting LVGL task
|
||||
I (731) LCD: LVGL buffer: 14400 bytes (W:360, Lines:20, DMA, single)
|
||||
I (731) LCD: Touch controller added to LVGL
|
||||
I (731) DZBJ: LVGL 初始化完成
|
||||
I (741) DZBJ: UI 初始化完成
|
||||
I (841) DZBJ: 背光已点亮,dzbj 显示模块初始化完成
|
||||
I (841) Airhub1: IMU传感器未初始化,跳过IoT注册
|
||||
I (841) Airhub1: 配网模式:跳过电池检测、IMU传感器、低功耗管理
|
||||
I (841) Airhub1: 电容触摸板按钮已禁用 (ENABLE_TOUCH_PAD_BUTTONS=0)
|
||||
I (841) Application: 打印设置设备状态日志: starting
|
||||
I (841) Application: 正常启动流程,将执行开机播报和网络连接播报
|
||||
I (841) Airhub1: Initializing audio codec (output only)...
|
||||
I (841) Airhub1: Creating BoxAudioCodec (ES8311, without reference) ...
|
||||
I (841) BoxAudioCodec: TX-only channel created (provisioning mode)
|
||||
I (841) ES8311: Work in Slave mode
|
||||
I (851) gpio: GPIO[48]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
|
||||
I (851) BoxAudioCodec: BoxAudioDevice initialized (output only)
|
||||
I (851) Airhub1: Audio codec initialized successfully
|
||||
I (851) Application: 配网模式:跳过 Opus 编码器、输入重采样器、麦克风输入
|
||||
I (851) I2S_IF: channel mode 0 bits:16/16 channel:2 mask:1
|
||||
I (851) I2S_IF: STD Mode 1 bits:16/16 channel:2 sample_rate:16000 mask:1
|
||||
I (861) Adev_Codec: Open codec device OK
|
||||
I (861) AudioCodec: Set output enable to true
|
||||
I (871) AudioCodec: Audio codec started (output only, provisioning mode)
|
||||
I (871) AudioCodec: 将运行时输出音量设置为:80
|
||||
I (871) Application: 设备启动完成,播放开机播报语音
|
||||
I (881) WifiBoard: force_ap cleared to 0
|
||||
I (881) WifiBoard: 🔵 进入配网模式 - BLE蓝牙配网
|
||||
I (881) WifiBoard: 🔵 进入配网模式 - 使用BLE蓝牙配网
|
||||
I (881) WifiBoard: 🔵 正在启动BLE蓝牙配网服务...
|
||||
I (881) Application: 🎵 测试模式:音频开始播放,等待播放完成
|
||||
I (881) Application: ✅ 测试模式:音频播放完成
|
||||
I (881) BluetoothProvisioning: 🔄 配网状态变化: IDLE -> INITIALIZING
|
||||
I (881) BluetoothProvisioning: 初始化WiFi...
|
||||
I (881) pp: pp rom version: e7ae62f
|
||||
I (881) net80211: net80211 rom version: e7ae62f
|
||||
I (891) wifi:wifi driver task: 3fce2524, prio:23, stack:6656, core=0
|
||||
I (891) wifi:wifi firmware version: 3263cda
|
||||
I (891) wifi:wifi certification version: v7.0
|
||||
I (891) wifi:config NVS flash: enabled
|
||||
I (891) wifi:config nano formatting: disabled
|
||||
I (891) wifi:Init data frame dynamic rx buffer num: 32
|
||||
I (891) wifi:Init dynamic rx mgmt buffer num: 5
|
||||
I (891) wifi:Init management short buffer num: 32
|
||||
I (891) wifi:Init static tx buffer num: 8
|
||||
I (891) wifi:Init tx cache buffer num: 32
|
||||
I (891) wifi:Init static tx FG buffer num: 2
|
||||
I (891) wifi:Init static rx buffer size: 1600
|
||||
I (901) wifi:Init static rx buffer num: 10
|
||||
I (901) wifi:Init dynamic rx buffer num: 32
|
||||
I (901) wifi_init: rx ba win: 16
|
||||
I (901) wifi_init: accept mbox: 6
|
||||
I (901) wifi_init: tcpip mbox: 32
|
||||
I (901) wifi_init: udp mbox: 6
|
||||
I (901) wifi_init: tcp mbox: 6
|
||||
I (901) wifi_init: tcp tx win: 5760
|
||||
I (901) wifi_init: tcp rx win: 5760
|
||||
I (901) wifi_init: tcp mss: 1440
|
||||
I (901) wifi_init: WiFi/LWIP prefer SPIRAM
|
||||
I (901) phy_init: phy_version 701,f4f1da3a,Mar 3 2025,15:50:10
|
||||
I (941) wifi:mode : sta (d0:cf:13:03:bb:f0)
|
||||
I (941) wifi:enable tsf
|
||||
I (941) BluetoothProvisioning: WiFi初始化完成
|
||||
I (941) BluetoothProvisioning: 初始化蓝牙控制器...
|
||||
I (941) BLE_INIT: BT controller compile version [2edb0b0]
|
||||
I (941) BLE_INIT: Using main XTAL as clock source
|
||||
I (941) BLE_INIT: Feature Config, ADV:1, BLE_50:0, DTM:1, SCAN:1, CCA:0, SMP:1, CONNECT:1
|
||||
I (941) BLE_INIT: Bluetooth MAC: d0:cf:13:03:bb:f2
|
||||
I (951) BluetoothProvisioning: 初始化Bluedroid协议栈...
|
||||
I (961) BluetoothProvisioning: 注册 BLE GAP/GATTS 回调...
|
||||
I (961) BluetoothProvisioning: ✅ GATTS App 注册成功, gatts_if=3
|
||||
I (961) BluetoothProvisioning: Service 创建成功, handle=40
|
||||
I (961) BluetoothProvisioning: WRITE 特征添加成功, handle=42
|
||||
I (961) BluetoothProvisioning: NOTIFY 特征添加成功, handle=44
|
||||
I (961) BluetoothProvisioning: CCCD 添加成功, handle=45
|
||||
I (961) BluetoothProvisioning: ✅ GATT Service 启动成功
|
||||
I (961) BluetoothProvisioning: 注册WiFi事件处理器...
|
||||
I (961) BluetoothProvisioning: 🔄 配网状态变化: INITIALIZING -> IDLE
|
||||
I (961) BluetoothProvisioning: 蓝牙配网初始化完成 (GATT Server 模式)
|
||||
I (961) BluetoothProvisioning: 蓝牙MAC地址: d0:cf:13:03:bb:f2
|
||||
I (961) WifiBoard: 🔍 BLE Initialize返回结果: true
|
||||
I (961) BluetoothProvisioning: 🔵 开始启动蓝牙配网服务 (GATT Server)...
|
||||
I (961) BluetoothProvisioning: 🔍 检查初始化状态: initialized_ = true
|
||||
I (961) BluetoothProvisioning: MAC地址发送状态已重置
|
||||
I (961) BluetoothProvisioning: 🔄 MAC地址发送状态已重置
|
||||
I (961) BluetoothProvisioning: 🧹 清除之前的WiFi凭据...
|
||||
I (961) BluetoothProvisioning: ✅ WiFi凭据清除完成,准备接收新的配网信息
|
||||
I (961) BluetoothProvisioning: 📡 蓝牙设备名称: Airhub_d0:cf:13:03:bb:f2
|
||||
I (961) BluetoothProvisioning: 📡 广播数据构建完成,长度: 29 字节
|
||||
I (961) BluetoothProvisioning: 📡 扫描响应数据构建完成,长度: 7 字节
|
||||
I (971) BluetoothProvisioning: 📡 广播数据设置完成,配置扫描响应数据
|
||||
E (971) BLE_INIT: Malloc failed
|
||||
E (971) BT_HCI: CC evt: op=0x2009, status=0x7
|
||||
I (971) BluetoothProvisioning: 📡 扫描响应数据设置完成,启动广播
|
||||
I (971) BluetoothProvisioning: ✅ 广播启动成功
|
||||
I (971) BluetoothProvisioning: 🔄 配网状态变化: IDLE -> ADVERTISING
|
||||
I (971) BluetoothProvisioning: 蓝牙配网广播已启动,等待客户端连接...
|
||||
I (971) WifiBoard: ✅ BLE蓝牙配网启动成功
|
||||
I (971) WifiBoard: 📱 请使用支持BLE的手机APP连接设备进行配网
|
||||
W (971) Application: Alert BLE配网模式: 请使用手机APP搜索Airhub_开头的蓝牙设备 []
|
||||
I (971) WifiBoard: 🔍 BLE配网启动结果: 成功
|
||||
I (971) WifiBoard: ✅ BLE配网启动成功,等待手机连接
|
||||
I (971) Application: 配网模式:跳过协议初始化、位置检测等网络业务
|
||||
I (971) Application: 打印设置设备状态日志: idle
|
||||
I (971) WeatherApi: [AutoDetectAndSetLocation] 调用全局函数自动检测位置
|
||||
I (971) WeatherApi: [AutoDetectLocation] ===== 开始自动检测位置 =====
|
||||
W (971) wifi:Haven't to connect to a suitable AP now!
|
||||
I (971) WeatherApi: [AutoDetectLocation] 从NVS命中位置: '广州市',已更新默认城市
|
||||
I (971) WeatherApi: [AutoDetectLocation] ===== 位置检测完成 =====
|
||||
I (971) main_task: Returned from app_main()
|
||||
I (1001) Application: 开始播放下行音频: 样本=960 采样率=16000
|
||||
I (13481) AudioCodec: Set output enable to false
|
||||
I (23591) BluetoothProvisioning: 📱 客户端已连接, conn_id=0, addr=6b:a1:99:6d:51:25
|
||||
I (23591) BluetoothProvisioning: 🔍 [DEBUG] 设置client_connected_为true
|
||||
I (23591) BluetoothProvisioning: MAC地址发送状态已重置
|
||||
I (23591) BluetoothProvisioning: 🔄 MAC地址发送状态已重置
|
||||
I (23591) BluetoothProvisioning: 🔄 配网状态变化: ADVERTISING -> CONNECTED
|
||||
I (23591) WifiBoard: BLE client connected
|
||||
I (23591) BluetoothProvisioning: 🔍 [DEBUG] BLE连接处理完成,client_connected_=true
|
||||
I (23591) BluetoothProvisioning: 广播已停止
|
||||
I (23951) BluetoothProvisioning: 连接参数更新: status=0, conn_int=24, latency=0, timeout=400
|
||||
I (24281) BluetoothProvisioning: 连接参数更新: status=0, conn_int=6, latency=0, timeout=500
|
||||
I (24351) BluetoothProvisioning: MTU 更新: 512
|
||||
I (24431) BluetoothProvisioning: 连接参数更新: status=0, conn_int=24, latency=0, timeout=400
|
||||
I (24791) BluetoothProvisioning: NOTIFY 已启用
|
||||
I (24851) BluetoothProvisioning: 📱 手机请求获取WiFi列表,开始扫描
|
||||
W (24851) wifi:Error! Should use default active scan time parameter for WiFi scan when Bluetooth is enabled!!!!!!
|
||||
I (81) Application: 🎴 吧唧模式:跳过 WiFi/协议/音频初始化
|
||||
I (81) Application: 打印设置设备状态日志: idle
|
||||
|
||||
I (24861) BluetoothProvisioning: 🔍 WiFi扫描已启动
|
||||
I (33621) BluetoothProvisioning: 📡 WiFi扫描完成,准备发送WiFi列表
|
||||
I (33621) BluetoothProvisioning: 📊 扫描到 57 个WiFi热点
|
||||
I (33631) BluetoothProvisioning: ✅ 成功获取WiFi扫描结果
|
||||
I (33631) BluetoothProvisioning: 📊 过滤后剩余 33 个2.4GHz热点 (原始: 57)
|
||||
I (33631) BluetoothProvisioning: 向客户端发送WiFi列表,共33个AP
|
||||
I (34291) BluetoothProvisioning: 📤 WiFi列表已发送给客户端,包含 33 个热点
|
||||
I (34291) BluetoothProvisioning: 📤 WiFi列表已发送,包含 33 个热点
|
||||
I (45611) BluetoothProvisioning: 📶 收到WiFi SSID: airhub
|
||||
I (45761) BluetoothProvisioning: 🔐 收到WiFi密码 (长度: 9)
|
||||
W (45761) wifi:Password length matches WPA2 standards, authmode threshold changes from OPEN to WPA2
|
||||
I (45791) BluetoothProvisioning: 📡 已发起WiFi连接请求,启动超时监控
|
||||
I (45791) WifiBoard: WiFi credentials received via BLE
|
||||
I (45951) wifi:new:<1,0>, old:<1,0>, ap:<255,255>, sta:<1,0>, prof:1, snd_ch_cfg:0x0
|
||||
I (45951) wifi:state: init -> auth (0xb0)
|
||||
I (46381) wifi:state: auth -> assoc (0x0)
|
||||
I (46411) wifi:state: assoc -> run (0x10)
|
||||
I (46491) wifi:connected with airhub, aid = 3, channel 1, BW20, bssid = 70:2a:d7:85:bc:eb
|
||||
I (46491) wifi:security: WPA2-PSK, phy: bgn, rssi: -27
|
||||
I (46501) wifi:pm start, type: 1
|
||||
abort() was called at PC 0x421cd333 on core 0
|
||||
--- 0x421cd333: __cxxabiv1::__terminate(void (*)()) at /builds/idf/crosstool-NG/.build/xtensa-esp-elf/src/gcc/libstdc++-v3/libsupc++/eh_terminate.cc:48
|
||||
|
||||
I (46501) wifi:dp: 1, bi: 102400, li: 3, scale listen interval from 307200 us to 307200 us
|
||||
I (46501) wifi:set rx beacon pti, rx_bcn_pti: 14, bcn_timeout: 25000, mt_pti: 14, mt_time: 10000
|
||||
I (46501) BluetoothProvisioning: ✅ WiFi连接成功,SSID: airhub,等待获取IP地址
|
||||
I (46501) wifi:AP's beacon interval = 102400 us, DTIM period = 1
|
||||
I (48151) wifi:<ba-add>idx:0 (ifx:0, 70:2a:d7:85:bc:eb), tid:0, ssn:0, winSize:64
|
||||
I (49371) esp_netif_handlers: sta ip: 192.168.124.22, mask: 255.255.255.0, gw: 192.168.124.1
|
||||
I (49371) BluetoothProvisioning: ✅ WiFi获取IP地址成功: 192.168.124.22
|
||||
I (49371) BluetoothProvisioning: 💾 启用WiFi配置自动保存到NVS存储...
|
||||
I (49371) BluetoothProvisioning: ✅ WiFi配置将自动保存到NVS存储
|
||||
I (49371) BluetoothProvisioning: 📋 获取当前WiFi配置成功,SSID: airhub
|
||||
I (49371) SsidManager: compare [airhub:6] [airhub:6]
|
||||
W (49371) SsidManager: SSID airhub already exists, overwrite it
|
||||
I (49371) BluetoothProvisioning: ✅ WiFi凭据已保存到NVS列表
|
||||
I (49371) BluetoothProvisioning: BluetoothProvisioning WIFI_CONNECTED skip_session=0
|
||||
I (49371) BluetoothProvisioning: 🔍 准备设置状态为SUCCESS并触发回调
|
||||
I (49371) BluetoothProvisioning: 🔄 配网状态变化: CONNECTED -> SUCCESS
|
||||
I (49371) WifiBoard: 设备配网成功,已连接到WiFi网络!
|
||||
I (49371) BluetoothProvisioning: 🔍 [DEBUG] ReportWiFiStatus调用: success=true, client_connected_=true
|
||||
I (49371) BluetoothProvisioning: 向客户端报告设备连接WiFi成功!
|
||||
I (49371) BluetoothProvisioning: 📋 配网流程完成,状态: SUCCESS, client_connected_: true
|
||||
I (49371) BluetoothProvisioning: ⏰ 延迟2000ms后重启设备以确保配置生效...
|
||||
W (50081) wifi:m f null
|
||||
|
||||
W (50121) wifi:m f null
|
||||
Backtrace: 0x40379f49:0x3fcbcbe0 0x4038731d:0x3fcbcc00 0x4038f9d9:0x3fcbcc20 0x421cd333:0x3fcbcc90 0x421cd368:0x3fcbccb0 0x421cd443:0x3fcbccd0 0x421ddb09:0x3fcbccf0 0x42013a05:0x3fcbcd30 0x4201bf6f:0x3fcbcd50 0x420174b6:0x3fcbcd80 0x42018c69:0x3fcbcde0 0x4202069d:0x3fcbd370 0x42229453:0x3fcbd390 0x40387e11:0x3fcbd3c0
|
||||
.--- 0x40379f49: panic_abort at /Users/rdzleo/esp/esp-idf/components/esp_system/panic.c:469
|
||||
--- 0x4038731d: esp_system_abort at /Users/rdzleo/esp/esp-idf/components/esp_system/port/esp_system_chip.c:87
|
||||
--- 0x4038f9d9: abort at /Users/rdzleo/esp/esp-idf/components/newlib/abort.c:38
|
||||
--- 0x421cd333: __cxxabiv1::__terminate(void (*)()) at /builds/idf/crosstool-NG/.build/xtensa-esp-elf/src/gcc/libstdc++-v3/libsupc++/eh_terminate.cc:48
|
||||
--- 0x421cd368: std::terminate() at /builds/idf/crosstool-NG/.build/xtensa-esp-elf/src/gcc/libstdc++-v3/libsupc++/eh_terminate.cc:58
|
||||
--- 0x421cd443: __cxa_throw at /builds/idf/crosstool-NG/.build/xtensa-esp-elf/src/gcc/libstdc++-v3/libsupc++/eh_throw.cc:98
|
||||
--- 0x421ddb09: std::__throw_system_error(int) at /builds/idf/crosstool-NG/.build/xtensa-esp-elf/src/gcc/libstdc++-v3/src/c++11/system_error.cc:595
|
||||
--- 0x42013a05: std::unique_lock<std::mutex>::lock() at /Users/rdzleo/.espressif/tools/xtensa-esp-elf/esp-14.2.0_20241119/xtensa-esp-elf/xtensa-esp-elf/include/c++/14.2.0/bits/unique_lock.h:142
|
||||
--- 0x4201bf6f: std::unique_lock<std::mutex>::unique_lock(std::mutex&) at /Users/rdzleo/.espressif/tools/xtensa-esp-elf/esp-14.2.0_20241119/xtensa-esp-elf/xtensa-esp-elf/include/c++/14.2.0/bits/unique_lock.h:73
|
||||
--- (inlined by) BackgroundTask::WaitForCompletion() at /Users/rdzleo/Desktop/Baji_Rtc_Toy/main/background_task.cc:46
|
||||
--- 0x420174b6: Application::SetDeviceState(DeviceState) at /Users/rdzleo/Desktop/Baji_Rtc_Toy/main/application.cc:2262
|
||||
--- 0x42018c69: Application::Start() at /Users/rdzleo/Desktop/Baji_Rtc_Toy/main/application.cc:533
|
||||
--- 0x4202069d: app_main at /Users/rdzleo/Desktop/Baji_Rtc_Toy/main/main.cc:105
|
||||
--- 0x42229453: main_task at /Users/rdzleo/esp/esp-idf/components/freertos/app_startup.c:208
|
||||
--- 0x40387e11: vPortTaskWrapper at /Users/rdzleo/esp/esp-idf/components/freertos/FreeRTOS-Kernel/portable/xtensa/port.c:139
|
||||
|
||||
W (50181) wifi:m f null
|
||||
|
||||
W (51101) wifi:m f null
|
||||
|
||||
W (51151) wifi:m f null
|
||||
|
||||
W (51201) wifi:m f null
|
||||
ELF file SHA256: 0061c1350
|
||||
|
||||
W (52071) wifi:m f null
|
||||
|
||||
W (52131) wifi:m f null
|
||||
|
||||
W (60981) wifi:m f null
|
||||
|
||||
W (61041) wifi:m f null
|
||||
|
||||
W (70981) wifi:m f null
|
||||
|
||||
W (71091) wifi:m f null
|
||||
|
||||
W (71181) wifi:m f null
|
||||
|
||||
W (71281) wifi:m f null
|
||||
|
||||
W (71391) wifi:m f null
|
||||
|
||||
W (71491) wifi:m f null
|
||||
|
||||
W (71591) wifi:m f null
|
||||
|
||||
W (71691) wifi:m f null
|
||||
|
||||
W (71801) wifi:m f null
|
||||
|
||||
W (71901) wifi:m f null
|
||||
|
||||
W (72001) wifi:m f null
|
||||
|
||||
W (72101) wifi:m f null
|
||||
|
||||
W (72211) wifi:m f null
|
||||
|
||||
W (72311) wifi:m f null
|
||||
|
||||
W (72411) wifi:m f null
|
||||
|
||||
W (72511) wifi:m f null
|
||||
|
||||
W (72621) wifi:m f null
|
||||
|
||||
W (72721) wifi:m f null
|
||||
|
||||
W (72831) wifi:m f null
|
||||
|
||||
W (72921) wifi:m f null
|
||||
|
||||
W (73031) wifi:m f null
|
||||
|
||||
W (73131) wifi:m f null
|
||||
|
||||
W (73231) wifi:m f null
|
||||
|
||||
W (73331) wifi:m f null
|
||||
|
||||
W (73441) wifi:m f null
|
||||
|
||||
W (73541) wifi:m f null
|
||||
|
||||
W (73641) wifi:m f null
|
||||
|
||||
W (73741) wifi:m f null
|
||||
|
||||
W (73841) wifi:m f null
|
||||
|
||||
W (73951) wifi:m f null
|
||||
|
||||
W (74051) wifi:m f null
|
||||
|
||||
W (74251) wifi:m f null
|
||||
|
||||
W (74361) wifi:m f null
|
||||
|
||||
W (74661) wifi:m f null
|
||||
|
||||
W (74781) wifi:m f null
|
||||
|
||||
W (74871) wifi:m f null
|
||||
|
||||
W (74981) wifi:m f null
|
||||
|
||||
W (75071) wifi:m f null
|
||||
|
||||
W (75181) wifi:m f null
|
||||
|
||||
W (75281) wifi:m f null
|
||||
|
||||
W (75381) wifi:m f null
|
||||
Rebooting...
|
||||
@ -1,9 +1,9 @@
|
||||
# AI对话 + 电子吧唧 双模式适配可行性分析
|
||||
# AI对话 + 电子吧唧 双模式适配说明
|
||||
|
||||
> 分析日期:2026-02-27
|
||||
> 更新日期:2026-02-27
|
||||
> 硬件平台:movecall-moji-esp32s3 (ESP32-S3-N16R8)
|
||||
> ESP-IDF版本:5.4.2
|
||||
> LVGL版本:8.3.11 (dzbj项目)
|
||||
> LVGL版本:8.3.11
|
||||
|
||||
---
|
||||
|
||||
@ -11,55 +11,31 @@
|
||||
|
||||
### 1.1 主项目 (Baji_Rtc_Toy)
|
||||
|
||||
基于 AI小智 开源项目改造,当前功能:
|
||||
基于 AI小智 开源项目改造,当前已集成功能:
|
||||
- 火山引擎 RTC 语音对话(WiFi 连接)
|
||||
- BLE 配网(Bluedroid,Service 0xABF0)
|
||||
- 音频编解码(ES8311 + Opus)
|
||||
- 唤醒词检测(esp-sr AFE)
|
||||
- **无 LCD 显示**(`lcd_display.cc` 已注释,managed_components 中无 LVGL)
|
||||
|
||||
关键文件:
|
||||
- `main/application.cc` — 应用主类,状态管理
|
||||
- `main/boards/movecall-moji-esp32s3/` — 板级实现
|
||||
- `main/boards/common/wifi_board.cc` — WiFi/BLE 网络管理
|
||||
- `main/protocols/volc_rtc_protocol.cc` — 火山 RTC 协议
|
||||
- `main/bluetooth_provisioning.cc` — BLE 配网
|
||||
- `main/display/` — Display 抽象层(已预留 LCD 接口,未编译)
|
||||
- **LVGL 8.3.11 LCD 显示**(Phase 1 已完成,开机显示 ScreenHome)
|
||||
- ST77916 QSPI 360×360 LCD + CST816S 触摸(已初始化)
|
||||
|
||||
### 1.2 dzbj 子项目 (电子吧唧)
|
||||
|
||||
独立的 ESP32-S3 LVGL 项目,位于 `/dzbj/` 目录,当前功能:
|
||||
独立的 ESP32-S3 LVGL 项目,位于 `/dzbj/` 目录,功能:
|
||||
- 360×360 ST77916 QSPI LCD + CST816S 触摸
|
||||
- LVGL 8.3.11 三屏界面(ScreenHome/ScreenImg/ScreenSet)
|
||||
- BLE GATT 图片传输服务(Service 0x0B00)
|
||||
- GIF 播放、JPEG 解码、SPIFFS 图片管理
|
||||
- 低功耗休眠/唤醒管理
|
||||
- PWM 背光控制、手电筒功能
|
||||
- 低功耗休眠/唤醒管理(10s 超时熄屏)
|
||||
- PWM 背光控制
|
||||
|
||||
关键文件:
|
||||
- `dzbj/main/lcd/lcd.c` — ST77916 QSPI + CST816S 驱动
|
||||
- `dzbj/main/ui/` — SquareLine Studio 生成的 UI 代码
|
||||
- `dzbj/main/pages/pages.c` — 图片处理 + PWM 亮度
|
||||
- `dzbj/main/ble/ble.c` — BLE 图片传输 GATT Server
|
||||
- `dzbj/main/sleep_mgr/sleep_mgr.c` — 低功耗管理
|
||||
### 1.3 实施进度
|
||||
|
||||
### 1.3 AI小智原生 LVGL 版本
|
||||
|
||||
**AI小智原生项目不使用 LVGL 9.2.2**。实际情况:
|
||||
|
||||
| 项目 | LVGL 版本 | 状态 |
|
||||
|------|-----------|------|
|
||||
| 主项目 (Baji_Rtc_Toy) | **无 LVGL** | `lcd_display.cc` 已注释,managed_components 中无 lvgl |
|
||||
| dzbj 子项目 | **8.3.11** | 完整集成 LVGL + esp_lvgl_port 2.5.0 |
|
||||
| AI小智 Display 框架 | **预留接口** | `Display` 基类已编译,方法为空操作(no-op) |
|
||||
|
||||
`main/CMakeLists.txt` 第11-12行明确注释了 LCD 支持:
|
||||
```cmake
|
||||
#"display/lcd_display.cc" # 移除LCD显示器支持
|
||||
#"display/oled_display.cc" # 移除OLED显示器支持
|
||||
```
|
||||
|
||||
AI小智框架的 `LcdDisplay` 类已预留 emoji 表情(21种)、聊天气泡(微信风格)、主题切换(深色/浅色) 功能,但当前未编译链接。
|
||||
| 阶段 | 状态 | 说明 |
|
||||
|------|------|------|
|
||||
| Phase 1: 点亮屏幕 | **已完成** | LCD + LVGL + ScreenHome 显示 |
|
||||
| Phase 2+4: 完整模式 + 切换 | **实施中** | 移植 dzbj 全模块 + 双模式切换 |
|
||||
| Phase 3: AI 聊天 UI | 待定 | 基于 LVGL 的 emoji + 聊天气泡 |
|
||||
|
||||
---
|
||||
|
||||
@ -69,41 +45,57 @@ AI小智框架的 `LcdDisplay` 类已预留 emoji 表情(21种)、聊天气泡(
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ LVGL 8.3.11 │
|
||||
│ (常驻,两个模式共享) │
|
||||
│ LVGL 8.3.11 + LCD │
|
||||
│ (常驻,两个模式共享显示硬件) │
|
||||
├────────────────────┬────────────────────────────┤
|
||||
│ AI 聊天模式 │ 电子吧唧模式 │
|
||||
│ AI 对话模式 │ 电子吧唧模式 │
|
||||
│ (mode=0, 默认) │ (mode=1) │
|
||||
│ │ │
|
||||
│ WiFi + RTC 协议 │ BLE GATT Server │
|
||||
│ emoji 表情显示 │ ScreenHome/Img/Set │
|
||||
│ 聊天气泡文本 │ 图片浏览 + GIF │
|
||||
│ 唤醒词检测 │ BLE 图片传输 │
|
||||
│ 音频编解码 │ 手电筒/低功耗 │
|
||||
│ ScreenHome (仅显示) │ ScreenHome/Img/Set │
|
||||
│ 音频编解码 + 唤醒词 │ BLE 图片传输 (0x0B00) │
|
||||
│ PowerSaveTimer │ sleep_mgr (10s熄屏) │
|
||||
│ IMU 传感器 │ battery 电池监测 │
|
||||
│ 电量检测 (板级) │ SPIFFS 图片管理 │
|
||||
├────────────────────┴────────────────────────────┤
|
||||
│ 长按 BOOT 5秒 切换 │
|
||||
│ AI→吧唧: 关WiFi+RTC → 启BLE → 切换UI │
|
||||
│ 吧唧→AI: 关BLE → 启WiFi+RTC → 切换UI │
|
||||
│ BOOT 双击 切换(写NVS + 重启) │
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2.2 模式定义
|
||||
|
||||
**AI 聊天模式**:
|
||||
**AI 对话模式** (device_mode=0, 默认):
|
||||
- 网络:WiFi 连接
|
||||
- 协议:火山引擎 RTC 实时对话
|
||||
- 音频:唤醒词检测 + Opus 编解码 + I2S 输出
|
||||
- 显示:emoji 表情 + 聊天气泡文本 + 状态栏
|
||||
- BLE:**关闭**
|
||||
- 显示:ScreenHome(仅显示,无触摸交互)
|
||||
- BLE:**关闭**(仅配网时启动)
|
||||
|
||||
**电子吧唧模式**:
|
||||
**电子吧唧模式** (device_mode=1):
|
||||
- 网络:**WiFi 关闭**
|
||||
- BLE:GATT Server(图片传输 + 配网服务)
|
||||
- 显示:ScreenHome(主界面)→ ScreenImg(图片浏览)→ ScreenSet(设置)
|
||||
- 功能:GIF 播放、JPEG 解码、SPIFFS 图片管理、手电筒、低功耗
|
||||
- BLE:GATT Server(图片传输服务 0x0B00)
|
||||
- 显示:ScreenHome → ScreenImg(图片浏览)→ ScreenSet(设置)
|
||||
- 功能:JPEG 解码、GIF 播放、SPIFFS 图片管理、低功耗、电池监测
|
||||
|
||||
### 2.3 模式切换方案
|
||||
|
||||
**方案:重启切换(NVS 标志)**
|
||||
|
||||
```
|
||||
BOOT 双击 → 读取 NVS device_mode → 切换 0↔1 → 写入 NVS → esp_restart()
|
||||
```
|
||||
|
||||
切换时间约 3-4 秒(重启时间),NVS 擦写寿命 10-100万次,无限次切换。
|
||||
|
||||
**选择重启而非热切换的原因**:
|
||||
1. WiFi + BLE Bluedroid 同时运行内部 SRAM 不足(约需 280KB,可用 ~334KB)
|
||||
2. 热切换需处理大量资源释放/重建(协议、音频管道、FreeRTOS 任务),复杂度极高
|
||||
3. Application 单例内部状态(event_group, opus 编解码器, background_task)难以干净重置
|
||||
4. 重启方式简单可靠,避免内存泄漏和碎片化风险
|
||||
|
||||
---
|
||||
|
||||
## 三、内存预算分析(核心瓶颈)
|
||||
## 三、内存预算分析
|
||||
|
||||
### 3.1 硬件规格
|
||||
|
||||
@ -111,228 +103,149 @@ AI小智框架的 `LcdDisplay` 类已预留 emoji 表情(21种)、聊天气泡(
|
||||
- **PSRAM**:8MB OCT-SPI 80MHz
|
||||
- **Flash**:16MB
|
||||
|
||||
### 3.2 常驻组件(两模式共享)
|
||||
### 3.2 各模式内存占用
|
||||
|
||||
| 组件 | DIRAM 占用 | 说明 |
|
||||
|------|-----------|------|
|
||||
| LVGL 库 (.bss/.data) | ~34KB | 图形引擎核心 |
|
||||
| FreeRTOS | ~22KB | 内核 + idle/timer 任务 |
|
||||
| HAL + SPI Flash | ~30KB | 硬件抽象 + Flash 驱动 |
|
||||
| Heap 管理器 | ~8KB | 内存分配器 |
|
||||
| esp_system / esp_hw | ~18KB | 系统支持 |
|
||||
| lwip 协议栈 | ~4KB | TCP/IP(即使 WiFi 关闭也常驻) |
|
||||
| 主应用 (main) | ~5KB | Application 框架 |
|
||||
| LVGL 任务栈 | 8KB | LVGL 刷新任务 |
|
||||
| **常驻小计** | **~129KB** | |
|
||||
|
||||
### 3.3 AI 聊天模式额外占用
|
||||
|
||||
| 组件 | DIRAM 占用 | 说明 |
|
||||
|------|-----------|------|
|
||||
| WiFi 驱动 (net80211+pp) | ~13KB 静态 + ~40-50KB 动态 | TX/RX 缓冲区 |
|
||||
| RTC 协议 | ~5-10KB | 火山引擎连接 |
|
||||
| 音频处理 | ~10-15KB | Opus编解码 + 管道 |
|
||||
| 唤醒词 (esp-sr) | ~15-20KB | AFE + 模型加载 |
|
||||
| **AI模式小计** | **~83-108KB** | |
|
||||
| **AI模式总计** | **~212-237KB** | 常驻 + AI |
|
||||
| **AI模式剩余** | **~97-122KB** | 用于堆分配 |
|
||||
|
||||
### 3.4 电子吧唧模式额外占用
|
||||
|
||||
| 组件 | DIRAM 占用 | 说明 |
|
||||
|------|-----------|------|
|
||||
| BLE Bluedroid 静态 | ~13KB | libbt.a 静态数据 |
|
||||
| BLE 控制器 | ~15KB | 动态分配(PSRAM优先后减少) |
|
||||
| BLE 任务栈 | ~15KB | BTC(3KB) + BTU(4KB) + 控制器 |
|
||||
| dzbj 业务任务 | ~18KB | GIF(4KB) + 按键(3KB) + 电池(4KB) + 睡眠(3KB) + BLE处理(8KB) |
|
||||
| **吧唧模式小计** | **~61KB** | |
|
||||
| **吧唧模式总计** | **~190KB** | 常驻 + 吧唧 |
|
||||
| **吧唧模式剩余** | **~144KB** | 充裕 |
|
||||
|
||||
### 3.5 关键结论
|
||||
|
||||
| 场景 | 内存占用 | 剩余 | 可行性 |
|
||||
| 场景 | 估算占用 | 剩余 | 可行性 |
|
||||
|------|----------|------|--------|
|
||||
| AI 聊天模式(单独) | ~212-237KB | ~97-122KB | **可行**(偏紧) |
|
||||
| 电子吧唧模式(单独) | ~190KB | ~144KB | **可行**(充裕) |
|
||||
| AI 对话模式(WiFi+RTC+音频+LVGL) | ~212-237KB | ~97-122KB | **可行**(偏紧) |
|
||||
| 电子吧唧模式(BLE+LVGL+SPIFFS) | ~190KB | ~144KB | **可行**(充裕) |
|
||||
| 两模式同时运行 | ~274-345KB | 不足 | **不可行** |
|
||||
| 模式切换(互斥) | 单次一个模式 | 够用 | **可行,需验证** |
|
||||
|
||||
**验证数据**:之前测试中 WiFi + BLE 同时运行导致 `assert failed: vQueueDelete queue.c:2355`(FreeRTOS 信号量分配失败),确认内部 SRAM 不足以支撑两者同时运行。
|
||||
### 3.3 关键验证数据
|
||||
|
||||
- Phase 1 测试:WiFi + BLE 同时运行导致 `assert failed: vQueueDelete queue.c:2355`(FreeRTOS 信号量分配失败)
|
||||
- BLE 配网成功后 `xTaskCreate` 分配 2048 栈失败(已改用 `esp_timer` 解决)
|
||||
- 确认两模式必须互斥运行
|
||||
|
||||
---
|
||||
|
||||
## 四、模式切换技术方案
|
||||
## 四、启动流程
|
||||
|
||||
### 4.1 AI → 电子吧唧 切换流程
|
||||
### 4.1 双模式启动序列
|
||||
|
||||
```
|
||||
用户长按 BOOT 5秒
|
||||
│
|
||||
├─ 1. 关闭 AI 资源
|
||||
│ ├─ protocol_->CloseAudioChannel() // 关闭 RTC 音频通道
|
||||
│ ├─ volc_rtc_stop() + volc_rtc_destroy() // 销毁 RTC 实例
|
||||
│ ├─ StopAudioProcessor() // 停止音频处理器
|
||||
│ ├─ 停止唤醒词检测
|
||||
│ └─ esp_wifi_stop() + esp_wifi_deinit() // 完全释放 WiFi
|
||||
│ → 释放 ~83-108KB 内部 SRAM
|
||||
│
|
||||
├─ 2. 启动吧唧资源
|
||||
│ ├─ esp_bt_controller_init() // 初始化 BLE 控制器
|
||||
│ ├─ esp_bluedroid_init() + enable() // 启动 Bluedroid
|
||||
│ ├─ 注册 GATT Server(图片传输服务)
|
||||
│ ├─ 启动 BLE 广播
|
||||
│ └─ 启动 dzbj 业务任务(按键/电池/睡眠管理)
|
||||
│
|
||||
└─ 3. 切换界面
|
||||
└─ lv_scr_load_anim(ui_ScreenHome, ...)
|
||||
开机
|
||||
│
|
||||
├── 板级构造函数(通用)
|
||||
│ ├── PowerSaveTimer 初始化
|
||||
│ ├── InitializeButtons()(主项目 Button 类,双击注册在此)
|
||||
│ ├── InitializeCodecI2c()
|
||||
│ ├── dzbj_display_init() ← LCD + LVGL 始终初始化
|
||||
│ │
|
||||
│ ├── if device_mode == BADGE (吧唧模式)
|
||||
│ │ └── InitializeBadgeMode()
|
||||
│ │ ├── fatfs_init() // SPIFFS 文件系统
|
||||
│ │ ├── init_spiffs_image_list() // 扫描图片
|
||||
│ │ ├── dzbj_button_init() // ISR按键
|
||||
│ │ ├── battery_init() // 电池检测
|
||||
│ │ ├── dzbj_ble_init() // BLE 图传
|
||||
│ │ └── sleep_mgr_init() // 低功耗管理
|
||||
│ │
|
||||
│ └── else (AI模式, 默认)
|
||||
│ ├── InitializeIot()
|
||||
│ ├── InitializeBatteryMonitor()
|
||||
│ ├── InitializeImuSensor()
|
||||
│ └── PowerSaveTimer 启用
|
||||
│
|
||||
├── Application::Start()
|
||||
│ ├── if device_mode == BADGE
|
||||
│ │ └── SetDeviceState(Idle); return; // 不启动WiFi/协议/音频
|
||||
│ │
|
||||
│ └── else (AI模式)
|
||||
│ ├── Opus 编解码器初始化
|
||||
│ ├── 音频管道启动
|
||||
│ ├── board.StartNetwork() // WiFi 连接
|
||||
│ ├── RTC 协议初始化
|
||||
│ └── MainLoop + AudioLoop 启动
|
||||
```
|
||||
|
||||
### 4.2 电子吧唧 → AI 切换流程
|
||||
### 4.2 BOOT 按键行为
|
||||
|
||||
```
|
||||
用户长按 BOOT 5秒
|
||||
│
|
||||
├─ 1. 关闭吧唧资源
|
||||
│ ├─ esp_ble_gap_stop_advertising() // 停止广播
|
||||
│ ├─ esp_ble_gatts_app_unregister() // 注销 GATT
|
||||
│ ├─ esp_bluedroid_disable() + deinit() // 关闭 Bluedroid
|
||||
│ ├─ esp_bt_controller_disable() + deinit() // 关闭控制器
|
||||
│ └─ 停止 dzbj 业务任务
|
||||
│ → 释放 ~43-61KB 内部 SRAM
|
||||
│
|
||||
├─ 2. 启动 AI 资源
|
||||
│ ├─ esp_wifi_init() + esp_wifi_start() // 初始化 WiFi
|
||||
│ ├─ WifiStation::Start() // 连接 WiFi
|
||||
│ ├─ WaitForConnected() // 等待连接
|
||||
│ ├─ 创建 VolcRtcProtocol 实例
|
||||
│ ├─ protocol_->Start() // 启动 RTC 连接
|
||||
│ └─ 启动音频处理器 + 唤醒词检测
|
||||
│
|
||||
└─ 3. 切换界面
|
||||
└─ lv_scr_load_anim(ai_chat_screen, ...)
|
||||
```
|
||||
|
||||
### 4.3 BOOT 按键检测逻辑
|
||||
|
||||
```
|
||||
当前逻辑(movecall_moji_esp32s3.cc):
|
||||
- 单击: Idle↔Listening 切换
|
||||
- 长按 5s(仅配网模式): 生产测试
|
||||
|
||||
需要改为:
|
||||
- 单击: 保持原逻辑(AI模式下 Idle↔Listening,吧唧模式下返回 ScreenHome)
|
||||
- 长按 5s: 模式切换(AI ↔ 吧唧)
|
||||
```
|
||||
| 事件 | AI模式 | 吧唧模式 | 配网模式 |
|
||||
|------|--------|---------|---------|
|
||||
| 单击 | Idle↔Listening 切换 | 待定(返回 ScreenHome?) | 显示 MAC 地址 |
|
||||
| 双击 | **切换到吧唧模式** | **切换到AI模式** | 无响应 |
|
||||
| 长按5s | 无响应 | 无响应 | 进入生产测试 |
|
||||
|
||||
---
|
||||
|
||||
## 五、实施方案
|
||||
## 五、模块移植清单
|
||||
|
||||
### 5.1 改动清单
|
||||
### 5.1 从 dzbj 移植的模块
|
||||
|
||||
#### 第一步:硬件层(改动小)
|
||||
| 模块 | 源文件 | 目标文件 | 适配要点 |
|
||||
|------|--------|---------|---------|
|
||||
| fatfs | `dzbj/main/fatfs/` | `main/dzbj/fatfs.c/h` | `gpio.h` → `dzbj_gpio.h` |
|
||||
| pages | `dzbj/main/pages/pages.c` | `main/dzbj/pages.c` | 移除 `wifi.h`,PWM 去重 |
|
||||
| BLE图传 | `dzbj/main/ble/ble.c` | `main/dzbj/dzbj_ble.c/h` | **新增 deinit 函数** |
|
||||
| sleep_mgr | `dzbj/main/sleep_mgr/` | `main/dzbj/sleep_mgr.c` | 按键回调适配 |
|
||||
| button | `dzbj/main/button/` | `main/dzbj/dzbj_button.c/h` | ISR+队列+去抖 |
|
||||
| battery | `dzbj/main/battery/` | `main/dzbj/battery.c/h` | ADC 校准 + UI 更新 |
|
||||
|
||||
| 文件 | 改动内容 | 改动程度 |
|
||||
|------|----------|----------|
|
||||
| `movecall-moji-esp32s3/config.h` | 添加 LCD/Touch GPIO 定义 | 小 |
|
||||
| `movecall_moji_esp32s3.cc` | 初始化 LCD 驱动(参考 dzbj lcd.c) | 中 |
|
||||
### 5.2 新建模块
|
||||
|
||||
**GPIO 冲突注意**:
|
||||
- movecall-moji `BUILTIN_LED_GPIO = GPIO_NUM_21` 与 dzbj LCD D3 (GPIO 21) 冲突
|
||||
- 需要重新映射 LED 引脚或调整 LCD 引脚
|
||||
- dzbj 触摸用独立 I2C 引脚(GPIO 5/6),与音频 ES8311 (GPIO 17/18) 不冲突
|
||||
| 模块 | 文件 | 功能 |
|
||||
|------|------|------|
|
||||
| device_mode | `main/dzbj/device_mode.c/h` | NVS 模式读写 + 重启切换 |
|
||||
|
||||
#### 第二步:LVGL 集成(改动中等)
|
||||
### 5.3 修改的现有文件
|
||||
|
||||
| 文件 | 改动内容 | 改动程度 |
|
||||
|------|----------|----------|
|
||||
| `main/idf_component.yml` | 添加 lvgl 8.3.11 + esp_lvgl_port 2.5.0 + esp_lcd_st77916 | 小 |
|
||||
| `main/CMakeLists.txt` | 取消注释 lcd_display.cc,添加 dzbj 模块源文件 | 中 |
|
||||
| `main/ui/` | 从 dzbj 复制 SquareLine 生成的 UI 代码 | 复制 |
|
||||
| `main/pages/` | 从 dzbj 复制页面管理模块 | 复制+小改 |
|
||||
| `main/sleep_mgr/` | 从 dzbj 复制低功耗管理模块 | 复制+小改 |
|
||||
| 文件 | 修改内容 |
|
||||
|------|---------|
|
||||
| `movecall_moji_esp32s3.cc` | 模式分支 + InitializeBadgeMode() + BOOT 双击回调 |
|
||||
| `application.cc` | Start() 模式分支(吧唧模式早返回) |
|
||||
| `main/CMakeLists.txt` | 添加新源文件 |
|
||||
| `main/idf_component.yml` | 添加 esp_jpeg 依赖 |
|
||||
| `main/sleep_mgr/include/sleep_mgr.h` | stub 改为真实函数声明 |
|
||||
|
||||
#### 第三步:AI 聊天 UI(新开发)
|
||||
### 5.4 删除的文件
|
||||
|
||||
| 内容 | 说明 |
|
||||
| 文件 | 原因 |
|
||||
|------|------|
|
||||
| 创建 AI 聊天屏幕 | 基于 LVGL 8.3.11,包含 emoji 显示区 + 聊天气泡容器 + 状态栏 |
|
||||
| emoji 表情渲染 | 参考 `LcdDisplay::SetEmotion()` 实现,21种表情映射 |
|
||||
| 聊天气泡 | 参考 `LcdDisplay::SetChatMessage()` 实现微信风格气泡 |
|
||||
| 对接 Application | SetEmotion/SetChatMessage 调用新 UI |
|
||||
|
||||
**注意**:`main/display/lcd_display.h` 中使用了 `lv_draw_buf_t`(LVGL 9.x 类型),需要适配为 8.3.11 的 `lv_disp_draw_buf_t`。
|
||||
|
||||
#### 第四步:模式切换管理(核心改动)
|
||||
|
||||
| 文件 | 改动内容 | 改动程度 |
|
||||
|------|----------|----------|
|
||||
| 新增 `mode_manager.h/cc` | 双模式状态机 + WiFi/BLE init/deinit | 新建 |
|
||||
| `movecall_moji_esp32s3.cc` | BOOT 长按 5s 检测 | 中 |
|
||||
| `application.cc` | 添加模式切换回调 | 中 |
|
||||
|
||||
### 5.2 不需要大改动的模块
|
||||
|
||||
| 模块 | 改动程度 | 说明 |
|
||||
|------|----------|------|
|
||||
| dzbj UI 代码 (ui_ScreenHome/Img/Set) | **几乎不改** | 直接复制使用 |
|
||||
| dzbj pages.c (GIF/JPEG/PWM) | **小改** | 适配新的 GPIO 定义 |
|
||||
| dzbj sleep_mgr | **小改** | 与 AI小智的 PowerSaveTimer 整合 |
|
||||
| dzbj ble.c (图片传输) | **不改** | 电子吧唧模式下直接使用 |
|
||||
| bluetooth_provisioning.cc | **不改** | 配网逻辑保持不变 |
|
||||
| VolcRtcProtocol | **不改** | AI模式下原样使用 |
|
||||
| `main/pages/pages_stub.c` | 被 `main/dzbj/pages.c` 真实实现替代 |
|
||||
|
||||
---
|
||||
|
||||
## 六、风险评估
|
||||
## 六、GPIO 引脚分配(已解决)
|
||||
|
||||
### 6.1 高风险
|
||||
Phase 1 已完成的 GPIO 冲突解决:
|
||||
|
||||
| 风险 | 影响 | 缓解方案 |
|
||||
|------|------|----------|
|
||||
| **WiFi deinit 内存泄漏** | 每次切换泄漏几KB,多次切换后崩溃 | 实测 `esp_wifi_deinit()` 后用 `heap_caps_get_free_size()` 验证回收量 |
|
||||
| **BLE deinit 内存泄漏** | Bluedroid 完全释放困难 | 考虑使用 NimBLE 替代 Bluedroid(更轻量,deinit 更可靠) |
|
||||
| **内部 SRAM 碎片化** | 反复 init/deinit 导致碎片,大块分配失败 | 用 `heap_caps_get_largest_free_block()` 监控最大连续块 |
|
||||
|
||||
### 6.2 中等风险
|
||||
|
||||
| 风险 | 影响 | 缓解方案 |
|
||||
|------|------|----------|
|
||||
| **GPIO 引脚冲突** | LCD 引脚与现有外设冲突 (GPIO 21) | 仔细对照两个项目的 GPIO 分配表,重新映射 |
|
||||
| **LVGL API 版本差异** | `lcd_display.h` 用了 `lv_draw_buf_t` (9.x) | 适配为 8.3.11 的 `lv_disp_draw_buf_t` |
|
||||
| **Flash 空间** | 新增 LVGL(323KB) + emoji字体 + UI资源 | 当前 app 分区 5MB,固件 ~3.5MB,剩余 ~1.5MB 充足 |
|
||||
|
||||
### 6.3 低风险
|
||||
|
||||
| 风险 | 影响 | 缓解方案 |
|
||||
|------|------|----------|
|
||||
| I2C 总线共享 | 音频 ES8311 (GPIO 17/18) vs 触摸 CST816S | dzbj 触摸用独立 I2C 引脚(GPIO 5/6),不冲突 |
|
||||
| PSRAM 带宽 | LVGL DMA + 音频 + WiFi 并行 | AI模式无 GIF 播放,PSRAM 带宽充足 |
|
||||
| LVGL 界面切换 | 两套 UI 共存 | LVGL 对象可存放 PSRAM,界面切换无需重启 LVGL |
|
||||
| GPIO | 主项目原用途 | dzbj用途 | 解决方案 |
|
||||
|------|------------|---------|---------|
|
||||
| 21 | BUILTIN_LED | LCD D3 | LED 改为 GPIO_NUM_NC |
|
||||
| 1 | Touch1 (电容触摸) | LCD 背光 EN | Touch1 改为 GPIO_NUM_NC |
|
||||
| 7 | Touch4 (电容触摸) | LCD RST | Touch4 改为 GPIO_NUM_NC |
|
||||
| 6 | Battery ADC | Touch RST | Battery ADC 改为 GPIO 3 |
|
||||
| 17/18 | I2C_NUM_1 (音频) | I2C_NUM_0 (触摸) | 统一为 I2C_NUM_1 共享 |
|
||||
|
||||
---
|
||||
|
||||
## 七、NimBLE vs Bluedroid 选型建议
|
||||
## 七、风险评估
|
||||
|
||||
当前 dzbj 和配网都使用 Bluedroid,但在双模式切换场景下 NimBLE 更优:
|
||||
### 7.1 重启切换方案(已选定)
|
||||
|
||||
| 对比项 | Bluedroid | NimBLE |
|
||||
|--------|-----------|--------|
|
||||
| Flash 占用 | ~277KB (libbt+libbtdm) | ~120KB |
|
||||
| 内部 SRAM | ~35-45KB 动态 | ~15-20KB 动态 |
|
||||
| deinit 可靠性 | 一般(可能有泄漏) | 较好 |
|
||||
| Classic BT | 支持 | 不支持(仅 BLE) |
|
||||
| GATT Server | 支持 | 支持 |
|
||||
| 迁移工作量 | — | 中等(API 不同,逻辑相同) |
|
||||
| 风险 | 等级 | 说明 |
|
||||
|------|------|------|
|
||||
| 内存泄漏 | **无** | 每次重启全新初始化,无残留 |
|
||||
| 内存碎片化 | **无** | 重启清除所有堆分配 |
|
||||
| WiFi/BLE deinit 不可靠 | **无** | 无需 deinit,重启自然释放 |
|
||||
| NVS 擦写寿命 | **极低** | 10-100万次,日常使用完全足够 |
|
||||
| 切换体验 | **低** | ~3-4秒重启时间,可加转场动画优化 |
|
||||
|
||||
**建议**:如果不需要经典蓝牙,优先考虑迁移到 NimBLE。节省 ~150KB Flash + ~20KB 内部 SRAM,并且 deinit 更可靠。
|
||||
### 7.2 其他风险
|
||||
|
||||
| 风险 | 等级 | 缓解方案 |
|
||||
|------|------|---------|
|
||||
| 符号冲突(pages_stub vs pages.c) | 中 | 删除 stub,真实实现始终编译 |
|
||||
| button 模块冲突(C++ Button vs C ISR) | 中 | 条件初始化,两模式用不同实现 |
|
||||
| SPIFFS 分区未配置 | 中 | 检查分区表是否有 spiffs 分区 |
|
||||
| Flash 空间 | 低 | 当前 app 分区 5MB,固件 ~3.5MB,剩余充足 |
|
||||
|
||||
---
|
||||
|
||||
## 八、分区表设计
|
||||
## 八、分区表
|
||||
|
||||
当前分区表(已移除 storage):
|
||||
当前分区表:
|
||||
|
||||
```csv
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
@ -344,51 +257,35 @@ ota_0, app, ota_0, 0x310000, 5M,
|
||||
ota_1, app, ota_1, 0x820000, 5M,
|
||||
```
|
||||
|
||||
**建议**:如果需要 SPIFFS 存储图片(dzbj 的图片浏览功能),需要重新添加 storage 分区,或复用 model 分区的一部分空间。
|
||||
dzbj 图片浏览功能需要 SPIFFS 存储。`model` 分区(3MB, spiffs 类型)可复用,或需新增 storage 分区。
|
||||
|
||||
---
|
||||
|
||||
## 九、推荐实施路线
|
||||
## 九、验证计划
|
||||
|
||||
```
|
||||
阶段 1: 点亮屏幕(基础验证) 预计改动量: 小
|
||||
├─ 确认 LCD GPIO 映射(解决 GPIO 21 冲突)
|
||||
├─ 在 main/idf_component.yml 添加 LVGL 8.3.11 依赖
|
||||
├─ 在 movecall-moji 板上初始化 LCD + LVGL
|
||||
├─ 显示 dzbj ScreenHome 界面
|
||||
└─ 验证 LVGL + WiFi 内存占用(确认不冲突)
|
||||
|
||||
阶段 2: 电子吧唧模式完整复制 预计改动量: 中
|
||||
├─ 复制 dzbj 的 UI/pages/ble/sleep_mgr 模块到主项目
|
||||
├─ 关闭 WiFi 后启动 BLE + ScreenHome
|
||||
├─ 验证 BLE 图片传输功能
|
||||
└─ 验证低功耗管理
|
||||
|
||||
阶段 3: AI 聊天 UI 开发 预计改动量: 中
|
||||
├─ 基于 LVGL 8.3.11 创建聊天屏幕(emoji + 气泡)
|
||||
├─ 对接 Application 的 SetEmotion/SetChatMessage
|
||||
├─ 关闭 BLE 后启动 WiFi + RTC
|
||||
└─ 验证语音对话 + 屏幕显示联动
|
||||
|
||||
阶段 4: 模式切换集成 预计改动量: 中
|
||||
├─ 实现 BOOT 长按 5s 检测
|
||||
├─ 实现 WiFi ↔ BLE 完整 init/deinit 切换
|
||||
├─ 界面切换 + 资源释放验证
|
||||
└─ 长时间稳定性测试(反复切换 100+ 次)
|
||||
### 9.1 编译验证
|
||||
```bash
|
||||
idf.py build
|
||||
```
|
||||
|
||||
---
|
||||
### 9.2 AI 模式验证(默认 mode=0)
|
||||
- [ ] 开机正常进入 WiFi 连接 + RTC 对话
|
||||
- [ ] LVGL 显示 ScreenHome
|
||||
- [ ] BOOT 单击切换对话状态
|
||||
- [ ] BOOT 双击 → 切换到吧唧模式,设备重启
|
||||
- [ ] 内存剩余 > 80KB
|
||||
|
||||
## 十、结论
|
||||
### 9.3 吧唧模式验证(mode=1)
|
||||
- [ ] 开机日志显示"电子吧唧模式启动"
|
||||
- [ ] 不连接 WiFi,不播放开机语音
|
||||
- [ ] BLE 广播可见(手机搜索 "Airhub_XX:XX:XX")
|
||||
- [ ] 手机 APP 可传输图片到设备
|
||||
- [ ] 屏幕显示传输的图片
|
||||
- [ ] 10s 无操作后屏幕熄灭
|
||||
- [ ] 按键或触摸唤醒屏幕
|
||||
- [ ] BOOT 双击 → 切换回 AI 模式,设备重启
|
||||
- [ ] 内存剩余 > 150KB
|
||||
|
||||
| 问题 | 结论 |
|
||||
|------|------|
|
||||
| AI小智用 LVGL 9.2.2? | **不是**,当前项目无 LVGL,框架预留了 LCD 接口 |
|
||||
| 显示 emoji 需要 LVGL? | **是**,emoji 字体渲染和聊天气泡都依赖 LVGL |
|
||||
| 保持 LVGL 8.3.11? | **可行**,dzbj 代码直接复用 |
|
||||
| 双模式切换可行? | **可行**,但需验证 WiFi/BLE deinit 的内存回收 |
|
||||
| 内存够用? | **单模式够用**(AI剩余~100KB,吧唧剩余~144KB),同时运行不够 |
|
||||
| dzbj 代码大改? | **不需要**,UI/pages/ble 模块几乎原样复制 |
|
||||
| 最大技术风险? | **WiFi/BLE 反复 init/deinit 的内存泄漏和碎片化** |
|
||||
|
||||
**总体评估**:双模式互斥切换方案在技术上可行,资源预算满足单模式运行。最大不确定性在于 WiFi/BLE 的完整 deinit 是否能可靠回收内存,需要实际编码验证。建议从阶段1(点亮屏幕)开始逐步推进。
|
||||
### 9.4 稳定性验证
|
||||
- [ ] 来回切换 10+ 次,功能正常
|
||||
- [ ] 各模式下长时间运行(>1小时)无崩溃
|
||||
|
||||
@ -16,3 +16,6 @@ add_compile_options(-Wno-missing-field-initializers)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(kapi)
|
||||
|
||||
# 自动生成并烧录 SPIFFS 镜像(将 spiffs_image/ 目录中的图片打包到 storage 分区)
|
||||
spiffs_create_partition_image(storage spiffs_image FLASH_IN_PROJECT)
|
||||
|
||||
@ -107,6 +107,16 @@ dependencies:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
version: 1.3.6
|
||||
espressif/esp_jpeg:
|
||||
component_hash: defb83669293cbf86d0fa86b475ba5517aceed04ed70db435388c151ab37b5d7
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.0'
|
||||
source:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
version: 1.3.1
|
||||
espressif/esp_lcd_st77916:
|
||||
component_hash: 5fa0f8b1274576d4484e2b8d9358e2a5d09c721511bef0dce6a55b4206b5f0e9
|
||||
dependencies:
|
||||
@ -202,6 +212,7 @@ direct_dependencies:
|
||||
- espressif/button
|
||||
- espressif/esp-sr
|
||||
- espressif/esp_codec_dev
|
||||
- espressif/esp_jpeg
|
||||
- espressif/esp_lcd_st77916
|
||||
- espressif/esp_lcd_touch
|
||||
- espressif/esp_lcd_touch_cst816s
|
||||
@ -210,6 +221,6 @@ direct_dependencies:
|
||||
- espressif/led_strip
|
||||
- idf
|
||||
- lvgl/lvgl
|
||||
manifest_hash: f912ad61bf8c653f10e6eb6988299d0c0083fc812504487ba14571084326b35a
|
||||
manifest_hash: 567fb06fed7b7df9c9bbd2a0615df5b600cd13d08df4b38a71d28971feaec792
|
||||
target: esp32s3
|
||||
version: 2.0.0
|
||||
|
||||
@ -26,6 +26,13 @@ set(SOURCES "audio_codecs/audio_codec.cc"
|
||||
"dzbj/lcd.c"
|
||||
"dzbj/pages_pwm.c"
|
||||
"dzbj/dzbj_init.c"
|
||||
"dzbj/device_mode.c"
|
||||
"dzbj/fatfs.c"
|
||||
"dzbj/pages.c"
|
||||
"dzbj/dzbj_ble.c"
|
||||
"dzbj/sleep_mgr.c"
|
||||
"dzbj/dzbj_button.c"
|
||||
"dzbj/dzbj_battery.c"
|
||||
# SquareLine Studio UI 文件
|
||||
"ui/ui.c"
|
||||
"ui/ui_helpers.c"
|
||||
@ -33,8 +40,6 @@ set(SOURCES "audio_codecs/audio_codec.cc"
|
||||
"ui/screens/ui_ScreenImg.c"
|
||||
"ui/screens/ui_ScreenSet.c"
|
||||
"ui/components/ui_comp_hook.c"
|
||||
# dzbj stub 实现(Phase 1 空实现)
|
||||
"pages/pages_stub.c"
|
||||
# UI 图片资源
|
||||
"ui/images/ui_img_s1_png.c"
|
||||
"ui/images/ui_img_s6_png.c"
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
#include "boards/common/qmi8658a.h" // 添加qmi8658a_data_t类型的头文件
|
||||
#include "boards/movecall-moji-esp32s3/movecall_moji_esp32s3.h" // 添加MovecallMojiESP32S3类的头文件
|
||||
#include "weather_api.h"
|
||||
#include "dzbj/device_mode.h" // 设备模式管理(AI/吧唧)
|
||||
|
||||
#include <cstring>
|
||||
#include <esp_log.h>
|
||||
@ -53,7 +54,12 @@ static const char* const STATE_STRINGS[] = {
|
||||
|
||||
Application::Application() {
|
||||
event_group_ = xEventGroupCreate();
|
||||
background_task_ = new BackgroundTask(4096 * 8);
|
||||
// 吧唧模式不需要后台任务(节省32KB栈内存)
|
||||
if (!device_mode_is_badge()) {
|
||||
background_task_ = new BackgroundTask(4096 * 8);
|
||||
} else {
|
||||
background_task_ = nullptr;
|
||||
}
|
||||
last_audible_output_time_ = std::chrono::steady_clock::now(); // 初始化最后一次有声音输出的时间点
|
||||
skip_dialog_idle_session_ = false; // 初始化跳过对话待机会话标志为false
|
||||
dialog_watchdog_running_ = false; // 初始化对话看门狗运行标志
|
||||
@ -521,6 +527,13 @@ void Application::SendTextMessage(const std::string& text) {
|
||||
}
|
||||
|
||||
void Application::Start() {
|
||||
// 电子吧唧模式:不启动 WiFi、协议、音频,所有交互由 LVGL + BLE 处理
|
||||
if (device_mode_is_badge()) {
|
||||
ESP_LOGI(TAG, "🎴 吧唧模式:跳过 WiFi/协议/音频初始化");
|
||||
SetDeviceState(kDeviceStateIdle);
|
||||
return;
|
||||
}
|
||||
|
||||
auto& board = Board::GetInstance();
|
||||
SetDeviceState(kDeviceStateStarting);
|
||||
|
||||
@ -2246,7 +2259,7 @@ void Application::SetDeviceState(DeviceState state) {
|
||||
}
|
||||
ESP_LOGI(TAG, "打印设置设备状态日志: %s", STATE_STRINGS[device_state_]);// 打印设置设备状态日志
|
||||
// The state is changed, wait for all background tasks to finish
|
||||
background_task_->WaitForCompletion();
|
||||
if (background_task_) background_task_->WaitForCompletion();
|
||||
|
||||
auto& board = Board::GetInstance();
|
||||
auto display = board.GetDisplay();
|
||||
|
||||
@ -7,8 +7,7 @@ static const char* TAG = "Button";
|
||||
Button::Button(const button_adc_config_t& adc_cfg) {
|
||||
button_config_t button_config = {
|
||||
.type = BUTTON_TYPE_ADC,
|
||||
// .long_press_time = 1000, // 原有长按3秒时的时间
|
||||
.long_press_time = 5000, // 长按5秒时间
|
||||
.long_press_time = 3000, // 长按3秒触发(模式切换)
|
||||
.short_press_time = 50,
|
||||
.adc_button_config = adc_cfg
|
||||
};
|
||||
@ -26,8 +25,7 @@ Button::Button(gpio_num_t gpio_num, bool active_high) : gpio_num_(gpio_num) {
|
||||
}
|
||||
button_config_t button_config = {
|
||||
.type = BUTTON_TYPE_GPIO,
|
||||
// .long_press_time = 1000, // 原有长按3秒时的时间
|
||||
.long_press_time = 5000, // 长按5秒时间
|
||||
.long_press_time = 3000, // 长按3秒触发(模式切换)
|
||||
.short_press_time = 50,
|
||||
.gpio_button_config = {
|
||||
.gpio_num = gpio_num,
|
||||
|
||||
@ -18,6 +18,10 @@ public:
|
||||
void OnLongPress(std::function<void()> callback);
|
||||
void OnClick(std::function<void()> callback);
|
||||
void OnDoubleClick(std::function<void()> callback);
|
||||
|
||||
// 获取底层 iot_button 句柄(用于 iot_button_register_event_cb 等高级 API)
|
||||
button_handle_t GetHandle() const { return button_handle_; }
|
||||
|
||||
private:
|
||||
gpio_num_t gpio_num_;
|
||||
button_handle_t button_handle_ = nullptr;
|
||||
|
||||
@ -15,6 +15,12 @@
|
||||
#include "system_info.h" // 引入系统信息头文件
|
||||
#include "settings.h"
|
||||
#include "dzbj/dzbj_init.h" // dzbj 显示模块初始化
|
||||
#include "dzbj/device_mode.h" // 设备模式管理(AI/吧唧)
|
||||
#include "dzbj/fatfs.h" // SPIFFS 文件系统
|
||||
#include "dzbj/dzbj_ble.h" // BLE 图传服务
|
||||
#include "dzbj/dzbj_battery.h" // 电池监测
|
||||
#include "dzbj/dzbj_button.h" // 按键驱动
|
||||
#include "sleep_mgr/include/sleep_mgr.h" // 休眠管理
|
||||
#include <cmath> // 添加数学函数头文件
|
||||
|
||||
#include <wifi_station.h>
|
||||
@ -37,6 +43,12 @@
|
||||
#define TAG "Airhub1"
|
||||
#define Pro_TAG "Airhub"
|
||||
|
||||
// 前向声明(pages.h 与 display.h 的 lv_font_t 冲突,改用前向声明)
|
||||
extern "C" void init_spiffs_image_list(void);
|
||||
|
||||
// 吧唧模式 BOOT 单击处理(实现在 dzbj_button.c,避免 lvgl.h 与 display.h 冲突)
|
||||
extern "C" void dzbj_boot_click_handler(void);
|
||||
|
||||
#if ENABLE_TOUCH_PAD_BUTTONS
|
||||
#include <driver/touch_pad.h>
|
||||
#include <driver/touch_sensor.h>
|
||||
@ -201,84 +213,90 @@ public:
|
||||
touched_pad_index_ = -1;
|
||||
#endif
|
||||
|
||||
// 使用240MHz作为CPU最大频率,10秒进入睡眠,-1表示不自动关机
|
||||
power_save_timer_ = new PowerSaveTimer(240, 10, -1);
|
||||
|
||||
// 设置低功耗模式回调
|
||||
power_save_timer_->OnEnterSleepMode([this]() {
|
||||
ESP_LOGI(TAG, "🔋 进入低功耗模式:CPU降频、Light Sleep启用、功放关闭");
|
||||
|
||||
// 关闭功放,进一步节省电量
|
||||
auto codec = GetAudioCodec();
|
||||
if (codec) {
|
||||
codec->EnableOutput(false);
|
||||
ESP_LOGI(TAG, "🔊 功放已关闭");
|
||||
}
|
||||
});
|
||||
|
||||
power_save_timer_->OnExitSleepMode([this]() {
|
||||
ESP_LOGI(TAG, "🔋 退出低功耗模式:CPU恢复正常、Light Sleep禁用、功放打开");
|
||||
|
||||
// 打开功放,恢复正常音频输出
|
||||
auto codec = GetAudioCodec();
|
||||
if (codec) {
|
||||
codec->EnableOutput(true);
|
||||
ESP_LOGI(TAG, "🔊 功放已打开");
|
||||
}
|
||||
});
|
||||
|
||||
// 初始化按钮
|
||||
InitializeButtons();
|
||||
InitializeStoryButton();
|
||||
|
||||
// 初始化I2C总线(必须在IMU传感器初始化之前)
|
||||
InitializeCodecI2c();
|
||||
|
||||
// 初始化 dzbj 显示模块(LCD + Touch + LVGL + UI)
|
||||
dzbj_display_init(codec_i2c_bus_);
|
||||
|
||||
// 初始化IoT功能,启用语音音量控制
|
||||
InitializeIot();
|
||||
|
||||
// 配网模式下跳过非必要外设,节省内部 DRAM 给 WiFi+BLE 使用
|
||||
bool provisioning_mode = WifiBoard::NeedsProvisioning();
|
||||
if (provisioning_mode) {
|
||||
ESP_LOGI(TAG, "配网模式:跳过电池检测、IMU传感器、低功耗管理");
|
||||
battery_level_ = 100; // 设置默认电量
|
||||
// === 根据设备模式分支初始化(完全隔离,互不干扰) ===
|
||||
if (device_mode_is_badge()) {
|
||||
// ===== 电子吧唧模式 =====
|
||||
// 不创建 PowerSaveTimer(吧唧模式使用 sleep_mgr)
|
||||
// 不初始化 AI 音频/协议/WiFi 相关资源
|
||||
ESP_LOGI(TAG, "🎴 电子吧唧模式启动");
|
||||
battery_level_ = 100; // 默认电量,后续由 dzbj_battery 接管
|
||||
InitializeBadgeModeButtons(); // 仅注册吧唧专用回调
|
||||
InitializeBadgeMode();
|
||||
} else {
|
||||
// 初始化电量检测
|
||||
InitializeBatteryMonitor();
|
||||
// ===== AI 对话模式 =====
|
||||
ESP_LOGI(TAG, "🤖 AI对话模式启动");
|
||||
|
||||
// 初始化IMU传感器
|
||||
InitializeImuSensor();
|
||||
// 创建 PowerSaveTimer(仅 AI 模式需要)
|
||||
power_save_timer_ = new PowerSaveTimer(240, 10, -1);
|
||||
power_save_timer_->OnEnterSleepMode([this]() {
|
||||
ESP_LOGI(TAG, "🔋 进入低功耗模式:CPU降频、Light Sleep启用、功放关闭");
|
||||
auto codec = GetAudioCodec();
|
||||
if (codec) {
|
||||
codec->EnableOutput(false);
|
||||
ESP_LOGI(TAG, "🔊 功放已关闭");
|
||||
}
|
||||
});
|
||||
power_save_timer_->OnExitSleepMode([this]() {
|
||||
ESP_LOGI(TAG, "🔋 退出低功耗模式:CPU恢复正常、Light Sleep禁用、功放打开");
|
||||
auto codec = GetAudioCodec();
|
||||
if (codec) {
|
||||
codec->EnableOutput(true);
|
||||
ESP_LOGI(TAG, "🔊 功放已打开");
|
||||
}
|
||||
});
|
||||
|
||||
// 启用PowerSaveTimer,启用完整的低功耗管理
|
||||
power_save_timer_->SetEnabled(true);
|
||||
ESP_LOGI(TAG, "🔋 PowerSaveTimer已启用,20秒无活动将进入低功耗模式");
|
||||
}
|
||||
InitializeAiModeButtons(); // 完整 AI 回调(含模式切换、音量、生产测试)
|
||||
InitializeStoryButton();
|
||||
|
||||
// 初始化IoT功能,启用语音音量控制
|
||||
InitializeIot();
|
||||
|
||||
// 配网模式下跳过非必要外设,节省内部 DRAM 给 WiFi+BLE 使用
|
||||
bool provisioning_mode = WifiBoard::NeedsProvisioning();
|
||||
if (provisioning_mode) {
|
||||
ESP_LOGI(TAG, "配网模式:跳过电池检测、IMU传感器、低功耗管理");
|
||||
battery_level_ = 100;
|
||||
} else {
|
||||
// 初始化电量检测
|
||||
InitializeBatteryMonitor();
|
||||
|
||||
// 初始化IMU传感器
|
||||
InitializeImuSensor();
|
||||
|
||||
// 启用PowerSaveTimer,启用完整的低功耗管理
|
||||
power_save_timer_->SetEnabled(true);
|
||||
ESP_LOGI(TAG, "🔋 PowerSaveTimer已启用,20秒无活动将进入低功耗模式");
|
||||
}
|
||||
|
||||
#if ENABLE_TOUCH_PAD_BUTTONS
|
||||
// 延迟调用触摸板初始化,避免在构造函数中就调用
|
||||
ESP_LOGI(TAG, "在构造函数完成后调用触摸初始化");
|
||||
xTaskCreate([](void* arg) {
|
||||
MovecallMojiESP32S3* board = static_cast<MovecallMojiESP32S3*>(arg);
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
ESP_LOGI(TAG, "开始延迟初始化触摸板...");
|
||||
if (board) {
|
||||
board->InitializeTouchPads();
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}, "touch_init", 4096, this, 5, NULL);
|
||||
// 延迟调用触摸板初始化,避免在构造函数中就调用
|
||||
ESP_LOGI(TAG, "在构造函数完成后调用触摸初始化");
|
||||
xTaskCreate([](void* arg) {
|
||||
MovecallMojiESP32S3* board = static_cast<MovecallMojiESP32S3*>(arg);
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
ESP_LOGI(TAG, "开始延迟初始化触摸板...");
|
||||
if (board) {
|
||||
board->InitializeTouchPads();
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}, "touch_init", 4096, this, 5, NULL);
|
||||
#else
|
||||
ESP_LOGI(TAG, "电容触摸板按钮已禁用 (ENABLE_TOUCH_PAD_BUTTONS=0)");
|
||||
ESP_LOGI(TAG, "电容触摸板按钮已禁用 (ENABLE_TOUCH_PAD_BUTTONS=0)");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if ENABLE_TOUCH_PAD_BUTTONS
|
||||
// 发送触摸消息
|
||||
void SendTouchMessage(int touch_pad_num) {
|
||||
const char* message = nullptr;
|
||||
power_save_timer_->WakeUp();
|
||||
if (power_save_timer_) power_save_timer_->WakeUp();
|
||||
|
||||
auto& app = Application::GetInstance();
|
||||
auto current_state = app.GetDeviceState();
|
||||
@ -458,8 +476,52 @@ public:
|
||||
|
||||
|
||||
|
||||
// 按钮初始化 函数
|
||||
void InitializeButtons() {
|
||||
void InitializeBadgeMode() {
|
||||
ESP_LOGI(TAG, "初始化电子吧唧模式外设...");
|
||||
fatfs_init(); // SPIFFS 文件系统
|
||||
fatfs_remove_nullData("/spiflash"); // 清理空文件
|
||||
init_spiffs_image_list(); // 扫描图片列表
|
||||
dzbj_button_init(); // ISR 按键驱动
|
||||
dzbj_battery_init(); // 电池检测
|
||||
dzbj_battery_monitor_start(); // 电池监控任务
|
||||
dzbj_ble_init(); // BLE 图传服务
|
||||
sleep_mgr_init(); // 低功耗管理(最后启动)
|
||||
ESP_LOGI(TAG, "电子吧唧模式初始化完成");
|
||||
}
|
||||
|
||||
// === 吧唧模式按钮初始化(仅注册吧唧专用回调,不涉及 AI 音频/协议资源) ===
|
||||
void InitializeBadgeModeButtons() {
|
||||
ESP_LOGI(TAG, "初始化吧唧模式按钮...");
|
||||
|
||||
// BOOT 单击 → 唤醒屏幕 / 退出手电筒 / 返回Home
|
||||
// 注意:iot_button 回调在 esp_timer 任务中执行,不能调用 vTaskDelay
|
||||
// (会阻塞 lv_tick_inc 导致 LVGL 渲染停滞),必须转发到独立任务
|
||||
boot_button_.OnClick([this]() {
|
||||
static uint32_t last_click_time = 0;
|
||||
uint32_t current_time = esp_timer_get_time() / 1000;
|
||||
if (last_click_time > 0 && current_time - last_click_time < 500) {
|
||||
return;
|
||||
}
|
||||
last_click_time = current_time;
|
||||
ESP_LOGI(TAG, "吧唧模式 BOOT 单击");
|
||||
xTaskCreate([](void *arg) {
|
||||
dzbj_boot_click_handler();
|
||||
vTaskDelete(NULL);
|
||||
}, "boot_click", 4096, NULL, 5, NULL);
|
||||
});
|
||||
|
||||
// BOOT 长按 3 秒 → 切换到 AI 模式
|
||||
boot_button_.OnLongPress([this]() {
|
||||
ESP_LOGI(TAG, "BOOT长按3秒:吧唧→AI模式");
|
||||
device_mode_set(DEVICE_MODE_AI);
|
||||
});
|
||||
|
||||
ESP_LOGI(TAG, "Boot button initialized on GPIO%d", BOOT_BUTTON_GPIO);
|
||||
ESP_LOGI(TAG, "吧唧模式按钮初始化完成");
|
||||
}
|
||||
|
||||
// === AI 模式按钮初始化(完整的 AI 对话相关回调) ===
|
||||
void InitializeAiModeButtons() {
|
||||
ESP_LOGI(TAG, "初始化按钮...");// 初始化按钮...
|
||||
|
||||
// BOOT按键单击事件 - 用于WiFi重置和触摸解锁
|
||||
@ -657,23 +719,42 @@ public:
|
||||
}
|
||||
});
|
||||
|
||||
// 配网模式下长按 BOOT 按键5秒进入 生产测试模式 新增代码
|
||||
// ==============================================================================
|
||||
// 添加BOOT按键长按事件处理 - 仅在配网模式下长按5秒进入测试模式
|
||||
// BOOT 长按 3 秒 → 切换设备模式(AI ↔ 吧唧)
|
||||
// 配网模式下跳过,因为配网模式有自己的 5s 长按处理
|
||||
boot_button_.OnLongPress([this]() {
|
||||
//ESP_LOGI(TAG, "🔧 BOOT button long pressed - checking if in provisioning mode");
|
||||
|
||||
// 检查是否处于BLE配网状态,只有在配网模式下才允许进入测试模式
|
||||
// 配网模式下不切换模式(留给 5s 长按进入生产测试)
|
||||
auto* wifi_board = dynamic_cast<WifiBoard*>(this);
|
||||
if (wifi_board && wifi_board->IsBleProvisioningActive()) {
|
||||
// ESP_LOGI(TAG, "🔧 设备正在进行BLE配网,长按5秒进入生产测试模式");
|
||||
EnterProductionTestMode();
|
||||
} else {
|
||||
ESP_LOGI(TAG, "🔵 非配网模式下,BOOT长按被屏蔽,无法进入测试模式");
|
||||
ESP_LOGI(TAG, "配网模式下长按3秒,等待5秒进入生产测试...");
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "BOOT长按3秒:切换设备模式");
|
||||
if (device_mode_is_badge()) {
|
||||
ESP_LOGI(TAG, "吧唧模式 → AI模式");
|
||||
device_mode_set(DEVICE_MODE_AI); // 写NVS + 重启
|
||||
} else {
|
||||
ESP_LOGI(TAG, "AI模式 → 吧唧模式");
|
||||
device_mode_set(DEVICE_MODE_BADGE); // 写NVS + 重启
|
||||
}
|
||||
});
|
||||
// ==============================================================================
|
||||
|
||||
// BOOT 长按 5 秒 → 仅在配网模式下进入生产测试模式
|
||||
// 使用 iot_button_register_event_cb 注册 5s 阈值(独立于 3s 的 OnLongPress)
|
||||
{
|
||||
static MovecallMojiESP32S3* self = this;
|
||||
button_event_config_t evt_cfg = {};
|
||||
evt_cfg.event = BUTTON_LONG_PRESS_START;
|
||||
evt_cfg.event_data.long_press.press_time = 5000;
|
||||
iot_button_register_event_cb(boot_button_.GetHandle(), evt_cfg,
|
||||
[](void* handle, void* usr_data) {
|
||||
auto* wifi_board = dynamic_cast<WifiBoard*>(self);
|
||||
if (wifi_board && wifi_board->IsBleProvisioningActive()) {
|
||||
ESP_LOGI(TAG, "BOOT长按5秒:进入生产测试模式");
|
||||
self->EnterProductionTestMode();
|
||||
}
|
||||
}, nullptr);
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Boot button initialized on GPIO%d", BOOT_BUTTON_GPIO);
|
||||
|
||||
|
||||
37
main/dzbj/device_mode.c
Normal file
37
main/dzbj/device_mode.c
Normal file
@ -0,0 +1,37 @@
|
||||
#include "device_mode.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_system.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#define TAG "DeviceMode"
|
||||
#define NVS_NAMESPACE "device"
|
||||
#define NVS_KEY "mode"
|
||||
|
||||
device_mode_t device_mode_get(void) {
|
||||
nvs_handle_t h;
|
||||
int32_t mode = DEVICE_MODE_AI;
|
||||
if (nvs_open(NVS_NAMESPACE, NVS_READONLY, &h) == ESP_OK) {
|
||||
nvs_get_i32(h, NVS_KEY, &mode);
|
||||
nvs_close(h);
|
||||
}
|
||||
return (device_mode_t)mode;
|
||||
}
|
||||
|
||||
void device_mode_set(device_mode_t mode) {
|
||||
nvs_handle_t h;
|
||||
if (nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h) == ESP_OK) {
|
||||
nvs_set_i32(h, NVS_KEY, (int32_t)mode);
|
||||
nvs_commit(h);
|
||||
nvs_close(h);
|
||||
}
|
||||
ESP_LOGI(TAG, "模式切换为 %s,即将重启...",
|
||||
mode == DEVICE_MODE_BADGE ? "吧唧" : "AI");
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
esp_restart();
|
||||
}
|
||||
|
||||
bool device_mode_is_badge(void) {
|
||||
return device_mode_get() == DEVICE_MODE_BADGE;
|
||||
}
|
||||
38
main/dzbj/device_mode.h
Normal file
38
main/dzbj/device_mode.h
Normal file
@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief 设备运行模式枚举
|
||||
*/
|
||||
typedef enum {
|
||||
DEVICE_MODE_AI = 0, // AI对话模式(WiFi + RTC)
|
||||
DEVICE_MODE_BADGE = 1, // 电子吧唧模式(BLE + 图片)
|
||||
} device_mode_t;
|
||||
|
||||
/**
|
||||
* @brief 从 NVS 读取当前设备模式
|
||||
* @return 当前模式,默认 DEVICE_MODE_AI
|
||||
*/
|
||||
device_mode_t device_mode_get(void);
|
||||
|
||||
/**
|
||||
* @brief 设置设备模式并重启
|
||||
* @param mode 目标模式
|
||||
* 写入 NVS 后延迟 500ms 调用 esp_restart()
|
||||
*/
|
||||
void device_mode_set(device_mode_t mode);
|
||||
|
||||
/**
|
||||
* @brief 快捷判断是否为电子吧唧模式
|
||||
* @return true 吧唧模式, false AI模式
|
||||
*/
|
||||
bool device_mode_is_badge(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
228
main/dzbj/dzbj_battery.c
Normal file
228
main/dzbj/dzbj_battery.c
Normal file
@ -0,0 +1,228 @@
|
||||
#include "dzbj_battery.h"
|
||||
#include "esp_adc/adc_oneshot.h"
|
||||
#include "esp_adc/adc_cali.h"
|
||||
#include "esp_adc/adc_cali_scheme.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_check.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_lvgl_port.h"
|
||||
#include "../ui/screens/ui_ScreenSet.h"
|
||||
// ScreenHome界面不关联电池电量显示
|
||||
#include <stdio.h>
|
||||
|
||||
static const char *TAG = "DZBJ_BAT";
|
||||
|
||||
// ADC句柄
|
||||
static adc_oneshot_unit_handle_t adc_handle = NULL;
|
||||
static adc_cali_handle_t cali_handle = NULL;
|
||||
static bool cali_enabled = false;
|
||||
|
||||
// 当前电池数据
|
||||
static uint32_t bat_voltage_mv = 0;
|
||||
static uint8_t bat_level = 0;
|
||||
|
||||
// 锂电池放电曲线查找表(基于典型3.7V单节锂电池放电特性)
|
||||
// 电压单位:毫伏,电量单位:百分比
|
||||
typedef struct {
|
||||
uint16_t voltage_mv;
|
||||
uint8_t level;
|
||||
} bat_curve_point_t;
|
||||
|
||||
static const bat_curve_point_t bat_curve[] = {
|
||||
{4200, 100},
|
||||
{4150, 95},
|
||||
{4110, 90},
|
||||
{4080, 85},
|
||||
{4020, 80},
|
||||
{3980, 75},
|
||||
{3950, 70},
|
||||
{3910, 65},
|
||||
{3870, 60},
|
||||
{3840, 55},
|
||||
{3800, 50},
|
||||
{3760, 45},
|
||||
{3730, 40},
|
||||
{3700, 35},
|
||||
{3680, 30},
|
||||
{3650, 25},
|
||||
{3630, 20},
|
||||
{3600, 15},
|
||||
{3570, 10},
|
||||
{3530, 5},
|
||||
{3400, 2},
|
||||
{3000, 0},
|
||||
};
|
||||
|
||||
#define BAT_CURVE_SIZE (sizeof(bat_curve) / sizeof(bat_curve[0]))
|
||||
|
||||
// 电压转电量(线性插值,提高精度)
|
||||
static uint8_t voltage_to_level(uint32_t voltage_mv)
|
||||
{
|
||||
// 超出上限
|
||||
if (voltage_mv >= bat_curve[0].voltage_mv) {
|
||||
return 100;
|
||||
}
|
||||
// 低于下限
|
||||
if (voltage_mv <= bat_curve[BAT_CURVE_SIZE - 1].voltage_mv) {
|
||||
return 0;
|
||||
}
|
||||
// 在查找表中线性插值
|
||||
for (int i = 0; i < BAT_CURVE_SIZE - 1; i++) {
|
||||
if (voltage_mv >= bat_curve[i + 1].voltage_mv) {
|
||||
uint32_t v_range = bat_curve[i].voltage_mv - bat_curve[i + 1].voltage_mv;
|
||||
uint32_t l_range = bat_curve[i].level - bat_curve[i + 1].level;
|
||||
uint32_t v_offset = voltage_mv - bat_curve[i + 1].voltage_mv;
|
||||
return bat_curve[i + 1].level + (uint8_t)((v_offset * l_range) / v_range);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 初始化ADC校准
|
||||
static void battery_cali_init(void)
|
||||
{
|
||||
#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
|
||||
// ESP32-S3 使用曲线拟合校准
|
||||
adc_cali_curve_fitting_config_t cali_cfg = {
|
||||
.unit_id = ADC_UNIT_1,
|
||||
.chan = BAT_ADC_CHANNEL,
|
||||
.atten = ADC_ATTEN_DB_12,
|
||||
.bitwidth = ADC_BITWIDTH_DEFAULT,
|
||||
};
|
||||
esp_err_t ret = adc_cali_create_scheme_curve_fitting(&cali_cfg, &cali_handle);
|
||||
#elif ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED
|
||||
// 备用:线性拟合校准
|
||||
adc_cali_line_fitting_config_t cali_cfg = {
|
||||
.unit_id = ADC_UNIT_1,
|
||||
.atten = ADC_ATTEN_DB_12,
|
||||
.bitwidth = ADC_BITWIDTH_DEFAULT,
|
||||
};
|
||||
esp_err_t ret = adc_cali_create_scheme_line_fitting(&cali_cfg, &cali_handle);
|
||||
#else
|
||||
esp_err_t ret = ESP_ERR_NOT_SUPPORTED;
|
||||
#endif
|
||||
if (ret == ESP_OK) {
|
||||
cali_enabled = true;
|
||||
ESP_LOGI(TAG, "ADC校准初始化成功");
|
||||
} else {
|
||||
ESP_LOGW(TAG, "ADC校准不可用,将使用原始值换算");
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t dzbj_battery_init(void)
|
||||
{
|
||||
// 初始化ADC单元
|
||||
adc_oneshot_unit_init_cfg_t unit_cfg = {
|
||||
.unit_id = ADC_UNIT_1,
|
||||
.ulp_mode = ADC_ULP_MODE_DISABLE,
|
||||
};
|
||||
ESP_RETURN_ON_ERROR(adc_oneshot_new_unit(&unit_cfg, &adc_handle),
|
||||
TAG, "ADC单元初始化失败");
|
||||
|
||||
// 配置ADC通道(11dB衰减,量程约0~2500mV)
|
||||
adc_oneshot_chan_cfg_t chan_cfg = {
|
||||
.atten = ADC_ATTEN_DB_12,
|
||||
.bitwidth = ADC_BITWIDTH_DEFAULT,
|
||||
};
|
||||
ESP_RETURN_ON_ERROR(adc_oneshot_config_channel(adc_handle, BAT_ADC_CHANNEL, &chan_cfg),
|
||||
TAG, "ADC通道配置失败");
|
||||
|
||||
// 初始化校准
|
||||
battery_cali_init();
|
||||
|
||||
ESP_LOGI(TAG, "电池ADC初始化完成 (GPIO%d, ADC1_CH%d, 分压比=%d)",
|
||||
PIN_BAT_ADC, BAT_ADC_CHANNEL, BAT_VOLTAGE_DIVIDER);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
uint32_t dzbj_battery_get_voltage_mv(void)
|
||||
{
|
||||
return bat_voltage_mv;
|
||||
}
|
||||
|
||||
uint8_t dzbj_battery_get_level(void)
|
||||
{
|
||||
return bat_level;
|
||||
}
|
||||
|
||||
// 读取ADC并计算电池电压和电量
|
||||
static void battery_read(void)
|
||||
{
|
||||
int adc_sum = 0;
|
||||
int valid_count = 0;
|
||||
|
||||
// 多次采样取平均,滤除噪声
|
||||
for (int i = 0; i < BAT_SAMPLE_COUNT; i++) {
|
||||
int raw;
|
||||
if (adc_oneshot_read(adc_handle, BAT_ADC_CHANNEL, &raw) == ESP_OK) {
|
||||
adc_sum += raw;
|
||||
valid_count++;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(2));
|
||||
}
|
||||
|
||||
if (valid_count == 0) {
|
||||
ESP_LOGE(TAG, "ADC采样全部失败");
|
||||
return;
|
||||
}
|
||||
|
||||
int adc_avg = adc_sum / valid_count;
|
||||
|
||||
// 使用校准值或原始换算得到ADC引脚电压
|
||||
int adc_voltage_mv = 0;
|
||||
if (cali_enabled) {
|
||||
adc_cali_raw_to_voltage(cali_handle, adc_avg, &adc_voltage_mv);
|
||||
} else {
|
||||
// 无校准时按3300mV参考电压线性换算
|
||||
adc_voltage_mv = (adc_avg * 3300) / 4095;
|
||||
}
|
||||
|
||||
// 乘以分压系数得到实际电池电压
|
||||
bat_voltage_mv = (uint32_t)adc_voltage_mv * BAT_VOLTAGE_DIVIDER;
|
||||
|
||||
// 查找表+插值计算电量百分比
|
||||
bat_level = voltage_to_level(bat_voltage_mv);
|
||||
|
||||
ESP_LOGI(TAG, "ADC原始值=%d, ADC电压=%dmV, 电池电压=%lumV, 电量=%d%%",
|
||||
adc_avg, adc_voltage_mv, (unsigned long)bat_voltage_mv, bat_level);
|
||||
}
|
||||
|
||||
// 更新UI电量显示(线程安全)
|
||||
static void battery_update_ui(void)
|
||||
{
|
||||
if (!lvgl_port_lock(100)) {
|
||||
return;
|
||||
}
|
||||
|
||||
char buf[8];
|
||||
snprintf(buf, sizeof(buf), "%d%%", bat_level);
|
||||
|
||||
// 只更新ScreenSet界面的电量圆弧和标签
|
||||
if (ui_ArcPowerLevel) {
|
||||
lv_arc_set_value(ui_ArcPowerLevel, bat_level);
|
||||
}
|
||||
if (ui_LabelPowerLevel) {
|
||||
lv_label_set_text(ui_LabelPowerLevel, buf);
|
||||
}
|
||||
|
||||
// ScreenHome界面的Arc1和Label1保持默认值,不关联电池电量
|
||||
|
||||
lvgl_port_unlock();
|
||||
}
|
||||
|
||||
// 电池监控任务
|
||||
static void battery_monitor_task(void *pvParameters)
|
||||
{
|
||||
while (1) {
|
||||
battery_read();
|
||||
battery_update_ui();
|
||||
vTaskDelay(pdMS_TO_TICKS(BAT_MONITOR_INTERVAL_MS));
|
||||
}
|
||||
}
|
||||
|
||||
void dzbj_battery_monitor_start(void)
|
||||
{
|
||||
xTaskCreate(battery_monitor_task, "bat_mon", 4096, NULL, 3, NULL);
|
||||
ESP_LOGI(TAG, "电池监控任务已启动,更新间隔%dms", BAT_MONITOR_INTERVAL_MS);
|
||||
}
|
||||
38
main/dzbj/dzbj_battery.h
Normal file
38
main/dzbj/dzbj_battery.h
Normal file
@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "esp_err.h"
|
||||
#include <stdint.h>
|
||||
|
||||
// 电池ADC引脚配置
|
||||
#define PIN_BAT_ADC 3 // GPIO3
|
||||
#define BAT_ADC_CHANNEL ADC_CHANNEL_2 // ADC1_CH2
|
||||
|
||||
// 分压比(实际电池电压 = ADC测量电压 * 此系数)
|
||||
// 根据硬件电路中的分压电阻调整,1:1分压器设为2
|
||||
#define BAT_VOLTAGE_DIVIDER 2
|
||||
|
||||
// 采样次数(取平均值,提高精度)
|
||||
#define BAT_SAMPLE_COUNT 32
|
||||
|
||||
// 监控间隔(毫秒)
|
||||
#define BAT_MONITOR_INTERVAL_MS 5000
|
||||
|
||||
// 初始化电池ADC检测
|
||||
esp_err_t dzbj_battery_init(void);
|
||||
|
||||
// 获取电池电压(毫伏)
|
||||
uint32_t dzbj_battery_get_voltage_mv(void);
|
||||
|
||||
// 获取电池电量百分比(0-100)
|
||||
uint8_t dzbj_battery_get_level(void);
|
||||
|
||||
// 启动电池监控任务(周期性读取ADC并更新UI)
|
||||
void dzbj_battery_monitor_start(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
410
main/dzbj/dzbj_ble.c
Normal file
410
main/dzbj/dzbj_ble.c
Normal file
@ -0,0 +1,410 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_bt.h"
|
||||
#include "esp_gap_ble_api.h"
|
||||
#include "esp_gatts_api.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_bt_device.h"
|
||||
#include "esp_gatt_common_api.h"
|
||||
#include "esp_mac.h"
|
||||
#include "fatfs.h"
|
||||
#include "pages.h"
|
||||
|
||||
|
||||
#define APP_ID_PLACEHOLDER 0
|
||||
|
||||
#define IMAGE_SERVICE_INSTID 0x0B
|
||||
#define IMAGE_SERVICE_UUID 0x0B00
|
||||
#define IMAGE_WRITE_UUID 0x0B01
|
||||
#define IMAGE_EDIT_UUID 0x0B02
|
||||
|
||||
static uint16_t image_service_handle = 0;
|
||||
static uint16_t image_write_handle = 0;
|
||||
static uint16_t image_edit_handle = 0;
|
||||
|
||||
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
|
||||
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
|
||||
|
||||
|
||||
static const char *CONN_TAG = "DZBJ_BLE";
|
||||
static char ble_device_name[32];
|
||||
static uint8_t adv_raw_len = 0;
|
||||
|
||||
static uint16_t conn_id;
|
||||
|
||||
static char *filepath;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8_t type;
|
||||
char filename[23];
|
||||
uint32_t len;
|
||||
} Megtype;
|
||||
|
||||
typedef struct{
|
||||
bool isSend;
|
||||
uint32_t port;
|
||||
} MegStatus;
|
||||
|
||||
Megtype firstMeg;
|
||||
MegStatus SendStatus = {false,0};
|
||||
|
||||
uint8_t *img_data = 0;
|
||||
FILE *file_img;
|
||||
|
||||
// BLE 图片处理任务(NVS 写入 + 导航显示在独立任务中执行,避免 BTC_TASK 栈溢出)
|
||||
static TaskHandle_t ble_process_task_handle = NULL;
|
||||
static char ble_pending_filename[24];
|
||||
static uint8_t *ble_pending_data = NULL; // 传输完成的图片数据(直通显示,跳过 SPIFFS 重读)
|
||||
static size_t ble_pending_data_size = 0;
|
||||
|
||||
static void ble_process_task(void *arg) {
|
||||
while (1) {
|
||||
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
|
||||
nvs_change_img(ble_pending_filename);
|
||||
ble_image_navigate_with_data(ble_pending_filename, ble_pending_data, ble_pending_data_size);
|
||||
ble_pending_data = NULL; // 所有权已转移,不再释放
|
||||
ble_pending_data_size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t attr_value_write[512] = {0};
|
||||
static uint8_t attr_value_edit[20] = {0};
|
||||
|
||||
static esp_attr_value_t char_val_image_write = {
|
||||
.attr_max_len = 512,
|
||||
.attr_len = 512,
|
||||
.attr_value = attr_value_write
|
||||
} ;
|
||||
static esp_attr_value_t char_val_image_edit = {
|
||||
.attr_max_len = 20,
|
||||
.attr_len = 20,
|
||||
.attr_value = attr_value_edit
|
||||
} ;
|
||||
|
||||
static esp_attr_control_t control_image_write = {
|
||||
.auto_rsp = ESP_GATT_AUTO_RSP
|
||||
};
|
||||
static esp_attr_control_t control_image_edit = {
|
||||
.auto_rsp = ESP_GATT_AUTO_RSP
|
||||
};
|
||||
|
||||
// 图片传输服务
|
||||
static esp_gatt_srvc_id_t server_id_image = {
|
||||
.id.uuid.len = ESP_UUID_LEN_16,
|
||||
.id.uuid.uuid.uuid16 = IMAGE_SERVICE_UUID,
|
||||
.id.inst_id = IMAGE_SERVICE_INSTID,
|
||||
.is_primary = true,
|
||||
};
|
||||
static esp_bt_uuid_t image_write_uuid = {
|
||||
.len = ESP_UUID_LEN_16,
|
||||
.uuid.uuid16 = IMAGE_WRITE_UUID,
|
||||
};
|
||||
static esp_bt_uuid_t image_edit_uuid = {
|
||||
.len = ESP_UUID_LEN_16,
|
||||
.uuid.uuid16 = IMAGE_EDIT_UUID,
|
||||
};
|
||||
|
||||
|
||||
static esp_ble_adv_params_t adv_params = {
|
||||
.adv_int_min = 0x20,
|
||||
.adv_int_max = 0x20,
|
||||
.adv_type = ADV_TYPE_IND,
|
||||
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
|
||||
.channel_map = ADV_CHNL_ALL,
|
||||
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
|
||||
};
|
||||
|
||||
|
||||
static uint8_t adv_raw_data[31];
|
||||
|
||||
// Scan Response 数据:厂商标识 + 服务UUID
|
||||
static uint8_t scan_rsp_data[] = {
|
||||
0x07, ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE, 0x4C, 0x44, 0x64, 0x7A, 0x62, 0x6A, // "LDdzbj"
|
||||
0x03, ESP_BLE_AD_TYPE_16SRV_CMPL, 0x00, 0x0B, // 服务UUID 0x0B00
|
||||
};
|
||||
|
||||
|
||||
void dzbj_ble_init(void)
|
||||
{
|
||||
esp_err_t ret;
|
||||
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
|
||||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_bt_controller_init(&bt_cfg);
|
||||
if (ret) {
|
||||
ESP_LOGE(CONN_TAG, "%s initialize controller failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
if (ret) {
|
||||
ESP_LOGE(CONN_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
ret = esp_bluedroid_init();
|
||||
if (ret) {
|
||||
ESP_LOGE(CONN_TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
ret = esp_bluedroid_enable();
|
||||
if (ret) {
|
||||
ESP_LOGE(CONN_TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
ret = esp_ble_gap_register_callback(esp_gap_cb);
|
||||
if (ret) {
|
||||
ESP_LOGE(CONN_TAG, "%s gap register failed, error code = %x", __func__, ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_ble_gatts_register_callback(gatts_event_handler);
|
||||
if (ret) {
|
||||
ESP_LOGE(CONN_TAG, "%s gatts register failed, error code = %x", __func__, ret);
|
||||
return;
|
||||
}
|
||||
ret = esp_ble_gatts_app_register(APP_ID_PLACEHOLDER);
|
||||
if (ret) {
|
||||
ESP_LOGE(CONN_TAG, "%s gatts app register failed, error code = %x", __func__, ret);
|
||||
return;
|
||||
}
|
||||
ret = esp_ble_gatt_set_local_mtu(512);
|
||||
if (ret) {
|
||||
ESP_LOGE(CONN_TAG, "set local MTU failed, error code = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取 BLE MAC 地址并构建设备名称: Airhub_xx:xx:xx:xx:xx:xx
|
||||
const uint8_t *ble_addr = esp_bt_dev_get_address();
|
||||
if (ble_addr) {
|
||||
snprintf(ble_device_name, sizeof(ble_device_name),
|
||||
"Airhub_%02x:%02x:%02x:%02x:%02x:%02x",
|
||||
ble_addr[0], ble_addr[1], ble_addr[2],
|
||||
ble_addr[3], ble_addr[4], ble_addr[5]);
|
||||
ESP_LOGI(CONN_TAG, "BLE MAC: %02x:%02x:%02x:%02x:%02x:%02x",
|
||||
ble_addr[0], ble_addr[1], ble_addr[2],
|
||||
ble_addr[3], ble_addr[4], ble_addr[5]);
|
||||
} else {
|
||||
strcpy(ble_device_name, "Airhub_BLE");
|
||||
ESP_LOGW(CONN_TAG, "获取BLE MAC失败,使用默认名称: %s", ble_device_name);
|
||||
}
|
||||
|
||||
ret = esp_ble_gap_set_device_name(ble_device_name);
|
||||
if (ret) {
|
||||
ESP_LOGE(CONN_TAG, "set device name failed, error code = %x", ret);
|
||||
return;
|
||||
}
|
||||
ESP_LOGI(CONN_TAG, "蓝牙设备名称: %s", ble_device_name);
|
||||
|
||||
// 构建广播数据: Flags + Complete Local Name
|
||||
uint8_t name_len = strlen(ble_device_name);
|
||||
int offset = 0;
|
||||
adv_raw_data[offset++] = 0x02;
|
||||
adv_raw_data[offset++] = ESP_BLE_AD_TYPE_FLAG;
|
||||
adv_raw_data[offset++] = 0x06;
|
||||
adv_raw_data[offset++] = name_len + 1;
|
||||
adv_raw_data[offset++] = ESP_BLE_AD_TYPE_NAME_CMPL;
|
||||
memcpy(&adv_raw_data[offset], ble_device_name, name_len);
|
||||
offset += name_len;
|
||||
adv_raw_len = offset;
|
||||
|
||||
ret = esp_ble_gap_config_adv_data_raw(adv_raw_data, adv_raw_len);
|
||||
if (ret) {
|
||||
ESP_LOGE(CONN_TAG, "config adv data failed, error code = %x", ret);
|
||||
}
|
||||
|
||||
// 配置 Scan Response 数据(厂商标识 "dzbj" + 服务UUID)
|
||||
ret = esp_ble_gap_config_scan_rsp_data_raw(scan_rsp_data, sizeof(scan_rsp_data));
|
||||
if (ret) {
|
||||
ESP_LOGE(CONN_TAG, "config scan response data failed, error code = %x", ret);
|
||||
}
|
||||
|
||||
// 创建图片处理任务(8KB 栈,足够 SPIFFS 扫描 + LVGL + GIF 解码)
|
||||
xTaskCreate(ble_process_task, "ble_img", 8192, NULL, 5, &ble_process_task_handle);
|
||||
}
|
||||
|
||||
void dzbj_ble_deinit(void)
|
||||
{
|
||||
esp_ble_gap_stop_advertising();
|
||||
esp_ble_gatts_app_unregister(0);
|
||||
esp_bluedroid_disable();
|
||||
esp_bluedroid_deinit();
|
||||
esp_bt_controller_disable();
|
||||
esp_bt_controller_deinit();
|
||||
}
|
||||
|
||||
|
||||
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
|
||||
{
|
||||
switch (event) {
|
||||
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
|
||||
ESP_LOGI(CONN_TAG, "Advertising data set, status %d", param->adv_data_raw_cmpl.status);
|
||||
// ADV 数据设置完成,等待 Scan Response 也设置完成后再开始广播
|
||||
break;
|
||||
case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT:
|
||||
ESP_LOGI(CONN_TAG, "Scan response data set, status %d", param->scan_rsp_data_raw_cmpl.status);
|
||||
esp_ble_gap_start_advertising(&adv_params);
|
||||
break;
|
||||
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
|
||||
if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGE(CONN_TAG, "Advertising start failed, status %d", param->adv_start_cmpl.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(CONN_TAG, "Advertising start successfully");
|
||||
break;
|
||||
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
|
||||
if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGE(CONN_TAG, "Advertising stop failed, status %d", param->adv_stop_cmpl.status);
|
||||
}
|
||||
ESP_LOGI(CONN_TAG, "Advertising stop successfully");
|
||||
break;
|
||||
case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
|
||||
ESP_LOGI(CONN_TAG, "Connection params update, status %d, conn_int %d, latency %d, timeout %d",
|
||||
param->update_conn_params.status,
|
||||
param->update_conn_params.conn_int,
|
||||
param->update_conn_params.latency,
|
||||
param->update_conn_params.timeout);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// GATT服务器事件处理函数
|
||||
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
|
||||
{
|
||||
switch (event) {
|
||||
case ESP_GATTS_REG_EVT:
|
||||
ESP_LOGI(CONN_TAG, "GATT server register, status %d, app_id %d",param->reg.status, param->reg.app_id);
|
||||
// 创建图片传输服务
|
||||
esp_ble_gatts_create_service(gatts_if,&server_id_image,10);
|
||||
break;
|
||||
case ESP_GATTS_CREATE_EVT:
|
||||
if (param->create.status == ESP_GATT_OK) {
|
||||
image_service_handle = param->create.service_handle;
|
||||
esp_ble_gatts_add_char(
|
||||
image_service_handle,
|
||||
&image_write_uuid,
|
||||
ESP_GATT_PERM_WRITE,
|
||||
ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_WRITE_NR,
|
||||
&char_val_image_write,
|
||||
&control_image_write
|
||||
);
|
||||
esp_ble_gatts_add_char(
|
||||
image_service_handle,
|
||||
&image_edit_uuid,
|
||||
ESP_GATT_PERM_WRITE,
|
||||
ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_WRITE_NR,
|
||||
&char_val_image_edit,
|
||||
&control_image_edit
|
||||
);
|
||||
ESP_LOGI(CONN_TAG, "图片传输服务创建成功,句柄: %x", image_service_handle);
|
||||
} else {
|
||||
ESP_LOGE(CONN_TAG, "服务创建失败,状态: %d", param->create.status);
|
||||
}
|
||||
break;
|
||||
|
||||
case ESP_GATTS_ADD_CHAR_EVT:
|
||||
if (param->add_char.status == ESP_GATT_OK) {
|
||||
if (param->add_char.char_uuid.uuid.uuid16 == (uint16_t)IMAGE_WRITE_UUID) {
|
||||
image_write_handle = param->add_char.attr_handle;
|
||||
ESP_LOGI(CONN_TAG, "图片写入特征创建成功,句柄: %d", image_write_handle);
|
||||
} else if (param->add_char.char_uuid.uuid.uuid16 == (uint16_t)IMAGE_EDIT_UUID) {
|
||||
image_edit_handle = param->add_char.attr_handle;
|
||||
ESP_LOGI(CONN_TAG, "图片编辑特征创建成功,句柄: %d", image_edit_handle);
|
||||
esp_ble_gatts_start_service(image_service_handle);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(CONN_TAG, "特征创建失败,状态: %d", param->add_char.status);
|
||||
}
|
||||
break;
|
||||
case ESP_GATTS_WRITE_EVT:
|
||||
if(param->write.handle == image_write_handle){
|
||||
uint8_t *value = param->write.value;
|
||||
if(!SendStatus.isSend){
|
||||
ESP_LOGI(CONN_TAG, "处理前序数据");
|
||||
firstMeg.type = value[0];
|
||||
memcpy(firstMeg.filename, value + 1, 22);
|
||||
firstMeg.filename[22] = '\0';
|
||||
firstMeg.len = (value[23] << 16) | (value[24] << 8) | value[25];
|
||||
ESP_LOGI(CONN_TAG, "图片数据长度:%d",(int)firstMeg.len);
|
||||
if(firstMeg.type == 0xfd){
|
||||
SendStatus.isSend = true;
|
||||
img_data = malloc((int)firstMeg.len);
|
||||
filepath = malloc(sizeof(char) * 33);
|
||||
sprintf(filepath,"/spiflash/%s",firstMeg.filename);
|
||||
file_img = fopen(filepath,"wb");
|
||||
ESP_LOGI(CONN_TAG,"传输通道建立成功,数据指针:%p,文件名称:%s,文件大小:%d",img_data,firstMeg.filename,(int)firstMeg.len);
|
||||
}
|
||||
}else if(SendStatus.isSend){
|
||||
uint8_t pkt_no = *value;
|
||||
uint8_t isEnd = *(value + 1);
|
||||
// 每 100 包或最后一包打印日志(减少串口输出提升传输速度)
|
||||
if (pkt_no % 100 == 0 || isEnd) {
|
||||
ESP_LOGI(CONN_TAG, "获取到数据:第:%d包,长度:%d,是否结束:%d", pkt_no+1, (int)param->write.len, isEnd);
|
||||
}
|
||||
uint8_t *data = value + 2;
|
||||
memcpy(img_data + SendStatus.port,data,(int)param->write.len-2);
|
||||
SendStatus.port += param->write.len-2;
|
||||
if(isEnd){
|
||||
ESP_LOGI(CONN_TAG,"数据接收完毕,累计:%d字节,预期:%d字节,首字节:%02X %02X",
|
||||
(int)SendStatus.port,(int)firstMeg.len,img_data[0],img_data[1]);
|
||||
fwrite(img_data,sizeof(uint8_t),firstMeg.len,file_img);
|
||||
fclose(file_img);
|
||||
SendStatus.isSend = false;
|
||||
SendStatus.port = 0;
|
||||
// img_data 不释放,传给显示任务直通显示(跳过 SPIFFS 重读)
|
||||
ble_pending_data = img_data;
|
||||
ble_pending_data_size = firstMeg.len;
|
||||
img_data = NULL; // 转移所有权
|
||||
free(filepath);
|
||||
ESP_LOGI(CONN_TAG,"图片接收成功,数据直通显示(%d字节)", (int)ble_pending_data_size);
|
||||
strncpy(ble_pending_filename, firstMeg.filename, sizeof(ble_pending_filename) - 1);
|
||||
ble_pending_filename[sizeof(ble_pending_filename) - 1] = '\0';
|
||||
xTaskNotifyGive(ble_process_task_handle);
|
||||
}
|
||||
}
|
||||
}// 图片编辑特征写入事件
|
||||
else if(param->write.handle == image_edit_handle){
|
||||
uint8_t *value = param->write.value;
|
||||
char imgName[23];
|
||||
uint8_t type = *(value + param->write.len - 1);
|
||||
memcpy(imgName, value, 23);
|
||||
if(type == 0xff){
|
||||
// 耗时操作转移到独立任务执行
|
||||
strncpy(ble_pending_filename, imgName, sizeof(ble_pending_filename) - 1);
|
||||
ble_pending_filename[sizeof(ble_pending_filename) - 1] = '\0';
|
||||
xTaskNotifyGive(ble_process_task_handle);
|
||||
}else if(type == 0xF1){
|
||||
remove(filepath);
|
||||
SendStatus.isSend = false;
|
||||
SendStatus.port = 0;
|
||||
free(img_data);
|
||||
free(filepath);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ESP_GATTS_CONNECT_EVT: {
|
||||
esp_ble_conn_update_params_t conn_params = {0};
|
||||
memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t));
|
||||
conn_params.latency = 0;
|
||||
conn_params.max_int = 16; // 16 × 1.25ms = 20ms(缩短连接间隔提升传输吞吐量)
|
||||
conn_params.min_int = 6; // 6 × 1.25ms = 7.5ms
|
||||
conn_params.timeout = 400;
|
||||
conn_id = param->connect.conn_id;
|
||||
ESP_LOGI(CONN_TAG, "Connected, conn_id %u, remote "ESP_BD_ADDR_STR"",
|
||||
param->connect.conn_id, ESP_BD_ADDR_HEX(param->connect.remote_bda));
|
||||
esp_ble_gap_update_conn_params(&conn_params);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTS_DISCONNECT_EVT:
|
||||
ESP_LOGI(CONN_TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x",
|
||||
ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason);
|
||||
esp_ble_gap_start_advertising(&adv_params);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
15
main/dzbj/dzbj_ble.h
Normal file
15
main/dzbj/dzbj_ble.h
Normal file
@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// 初始化 BLE 图传服务
|
||||
void dzbj_ble_init(void);
|
||||
|
||||
// 反初始化 BLE 图传服务(释放蓝牙协议栈资源)
|
||||
void dzbj_ble_deinit(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
155
main/dzbj/dzbj_button.c
Normal file
155
main/dzbj/dzbj_button.c
Normal file
@ -0,0 +1,155 @@
|
||||
#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);
|
||||
}
|
||||
}
|
||||
}
|
||||
30
main/dzbj/dzbj_button.h
Normal file
30
main/dzbj/dzbj_button.h
Normal file
@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
// 按键引脚定义
|
||||
#define PIN_BTN_BOOT 0 // GPIO0 BOOT按键(低电平有效)
|
||||
#define PIN_BTN_KEY2 4 // GPIO4 KEY2按键(低电平有效)
|
||||
|
||||
// 按键事件回调函数类型
|
||||
typedef void (*btn_event_cb_t)(int gpio_num, void *usr_data);
|
||||
|
||||
// 初始化按键驱动(GPIO中断 + 软件去抖)
|
||||
esp_err_t dzbj_button_init(void);
|
||||
|
||||
// 注册BOOT按键按下回调
|
||||
void dzbj_button_on_boot_press(btn_event_cb_t cb, void *usr_data);
|
||||
|
||||
// 注册KEY2按键按下回调
|
||||
void dzbj_button_on_key2_press(btn_event_cb_t cb, void *usr_data);
|
||||
|
||||
// 吧唧模式 BOOT 单击处理(唤醒屏幕 / 退出手电筒 / 返回Home)
|
||||
void dzbj_boot_click_handler(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
248
main/dzbj/fatfs.c
Normal file
248
main/dzbj/fatfs.c
Normal file
@ -0,0 +1,248 @@
|
||||
/**
|
||||
* @file fatfs.c
|
||||
* @brief SPIFFS 文件系统管理模块(从 dzbj 移植)
|
||||
*
|
||||
* 提供 SPIFFS 挂载、文件读写、JPEG 解码等功能。
|
||||
*/
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_spiffs.h"
|
||||
#include "fatfs.h"
|
||||
#include <dirent.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
static const char *TAG = "FATFS";
|
||||
|
||||
// 初始化SPIFFS文件系统
|
||||
void fatfs_init(void) {
|
||||
esp_vfs_spiffs_conf_t conf = {
|
||||
.base_path = "/spiflash",
|
||||
.partition_label = "storage",
|
||||
.max_files = 5,
|
||||
.format_if_mount_failed = true,
|
||||
};
|
||||
esp_err_t err = esp_vfs_spiffs_register(&conf);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to mount SPIFFS (%s)", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
size_t total = 0, used = 0;
|
||||
err = esp_spiffs_info("storage", &total, &used);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to get SPIFFS info (%s)", esp_err_to_name(err));
|
||||
} else {
|
||||
ESP_LOGI(TAG, "SPIFFS: Total size: %d, Used: %d", total, used);
|
||||
}
|
||||
}
|
||||
|
||||
// 读取图片数据到内存
|
||||
void read_img(uint8_t *img_p) {
|
||||
FILE *f = fopen("/spiflash/img.bin", "r");
|
||||
if (f == NULL) {
|
||||
ESP_LOGE(TAG, "OPEN ERROR");
|
||||
return;
|
||||
}
|
||||
size_t size = fread(img_p, sizeof(uint8_t), 129600 * 2, f);
|
||||
fclose(f);
|
||||
if (size != 0) {
|
||||
ESP_LOGI(TAG, "read success!");
|
||||
}
|
||||
}
|
||||
|
||||
// 测试FATFS文件系统
|
||||
void fs_test(void) {
|
||||
FILE *f = fopen("/spiflash/img.bin", "r");
|
||||
if (f == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to open file for reading");
|
||||
return;
|
||||
}
|
||||
uint8_t line[2];
|
||||
fread(line, sizeof(uint8_t), 2, f);
|
||||
fclose(f);
|
||||
ESP_LOGI(TAG, "Read from file: %x %x", line[0], line[1]);
|
||||
}
|
||||
|
||||
// 列出目录下所有文件名
|
||||
void fatfs_list_all_filenames(const char *dir_path, bool recursive) {
|
||||
DIR *dir = opendir(dir_path);
|
||||
if (dir == NULL) {
|
||||
ESP_LOGE(TAG, "无法打开目录: %s", dir_path);
|
||||
return;
|
||||
}
|
||||
struct dirent *entry;
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
|
||||
continue;
|
||||
}
|
||||
char full_path[512];
|
||||
snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, entry->d_name);
|
||||
struct stat file_stat;
|
||||
if (stat(full_path, &file_stat) == 0 && S_ISDIR(file_stat.st_mode)) {
|
||||
if (recursive) {
|
||||
fatfs_list_all_filenames(full_path, recursive);
|
||||
}
|
||||
} else if (stat(full_path, &file_stat) == 0 && S_ISREG(file_stat.st_mode)) {
|
||||
ESP_LOGI(TAG, "文件名: %s, 大小:%d", full_path, (int)file_stat.st_size);
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
// 删除目录下所有空文件
|
||||
void fatfs_remove_nullData(const char *dir_path) {
|
||||
DIR *dir = opendir(dir_path);
|
||||
if (dir == NULL) {
|
||||
ESP_LOGE(TAG, "无法打开目录: %s", dir_path);
|
||||
return;
|
||||
}
|
||||
struct dirent *entry;
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
|
||||
continue;
|
||||
}
|
||||
char full_path[512];
|
||||
snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, (char*)entry->d_name);
|
||||
struct stat file_stat;
|
||||
stat(full_path, &file_stat);
|
||||
if ((int)file_stat.st_size == 0) {
|
||||
remove(full_path);
|
||||
ESP_LOGE(TAG, "删除空文件: %s", full_path);
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
// 删除目录下所有文件
|
||||
void fatfs_remove_allData(const char *dir_path) {
|
||||
DIR *dir = opendir(dir_path);
|
||||
if (dir == NULL) {
|
||||
ESP_LOGE(TAG, "无法打开目录: %s", dir_path);
|
||||
return;
|
||||
}
|
||||
struct dirent *entry;
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
|
||||
continue;
|
||||
}
|
||||
char full_path[512];
|
||||
snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, (char*)entry->d_name);
|
||||
remove(full_path);
|
||||
ESP_LOGE(TAG, "删除文件: %s", full_path);
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
// 检查图片是否有效(文件大小不为0)
|
||||
bool fats_img_isOK(char* img_path) {
|
||||
struct stat file_stat;
|
||||
stat(img_path, &file_stat);
|
||||
return file_stat.st_size > 0;
|
||||
}
|
||||
|
||||
// JPEG 解码:从 SPIFFS 读取 JPEG 并解码为 RGB565
|
||||
esp_err_t DecodeImg(char *imgpath, uint8_t** imgData, esp_jpeg_image_output_t *outimage) {
|
||||
FILE *f = fopen(imgpath, "rb");
|
||||
if (f == NULL) {
|
||||
ESP_LOGE(TAG, "OPEN ERROR: %s", imgpath);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
struct stat file_stat;
|
||||
stat(imgpath, &file_stat);
|
||||
|
||||
// 分配输出缓冲区(360×360 RGB565)
|
||||
*imgData = malloc(360 * 360 * 2);
|
||||
if (*imgData == NULL) {
|
||||
ESP_LOGE(TAG, "输出缓冲区分配失败");
|
||||
fclose(f);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
// 分配输入缓冲区(JPEG 原始数据)
|
||||
uint8_t *imgEncoderData = malloc(file_stat.st_size);
|
||||
if (imgEncoderData == NULL) {
|
||||
ESP_LOGE(TAG, "输入缓冲区分配失败(需%d字节)", (int)file_stat.st_size);
|
||||
free(*imgData);
|
||||
*imgData = NULL;
|
||||
fclose(f);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
size_t read_len = fread(imgEncoderData, sizeof(uint8_t), file_stat.st_size, f);
|
||||
fclose(f);
|
||||
|
||||
if (read_len != (size_t)file_stat.st_size) {
|
||||
ESP_LOGE(TAG, "文件读取不完整(预期:%d,实际:%zu)",
|
||||
(int)file_stat.st_size, read_len);
|
||||
free(imgEncoderData);
|
||||
free(*imgData);
|
||||
*imgData = NULL;
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
// 验证 JPEG 头
|
||||
if (file_stat.st_size < 2 || imgEncoderData[0] != 0xFF || imgEncoderData[1] != 0xD8) {
|
||||
ESP_LOGE(TAG, "不是有效JPEG文件: %s", imgpath);
|
||||
free(imgEncoderData);
|
||||
free(*imgData);
|
||||
*imgData = NULL;
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
uint32_t outbuf_size = 360 * 360 * sizeof(uint8_t) * 2;
|
||||
esp_jpeg_image_cfg_t jpeg_cfg = {
|
||||
.indata = imgEncoderData,
|
||||
.indata_size = file_stat.st_size,
|
||||
.outbuf = *imgData,
|
||||
.outbuf_size = outbuf_size,
|
||||
.out_format = JPEG_IMAGE_FORMAT_RGB565,
|
||||
.flags = {
|
||||
.swap_color_bytes = true,
|
||||
},
|
||||
};
|
||||
esp_err_t ret = esp_jpeg_decode(&jpeg_cfg, outimage);
|
||||
free(imgEncoderData);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// 测试读取图片数据
|
||||
void test_readimg(char *imgpath, uint16_t size) {
|
||||
FILE *f = fopen(imgpath, "r");
|
||||
if (f == NULL) {
|
||||
ESP_LOGE(TAG, "OPEN ERROR");
|
||||
return;
|
||||
}
|
||||
uint8_t *head = malloc(size);
|
||||
if (head == NULL) {
|
||||
fclose(f);
|
||||
return;
|
||||
}
|
||||
fread(head, sizeof(uint8_t), size, f);
|
||||
fclose(f);
|
||||
for (int i = 0; i < size; i++) {
|
||||
printf("%x ", *(head + i));
|
||||
}
|
||||
printf("\n");
|
||||
free(head);
|
||||
}
|
||||
|
||||
// 获取目录下所有图片文件名
|
||||
void fat_getAllimgList(const char *dir_path, char** list, uint8_t* num) {
|
||||
*num = 0;
|
||||
DIR *dir = opendir(dir_path);
|
||||
if (dir == NULL) {
|
||||
ESP_LOGE(TAG, "无法打开目录: %s", dir_path);
|
||||
return;
|
||||
}
|
||||
struct dirent *entry;
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
|
||||
continue;
|
||||
}
|
||||
list[*num] = strdup(entry->d_name);
|
||||
(*num)++;
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
22
main/dzbj/fatfs.h
Normal file
22
main/dzbj/fatfs.h
Normal file
@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "jpeg_decoder.h"
|
||||
|
||||
void fatfs_init(void);
|
||||
void fs_test(void);
|
||||
void read_img(uint8_t *img);
|
||||
void fatfs_list_all_filenames(const char *dir_path, bool recursive);
|
||||
void fatfs_remove_nullData(const char *dir_path);
|
||||
esp_err_t DecodeImg(char *imgpath, uint8_t** imgData, esp_jpeg_image_output_t *outimage);
|
||||
void test_readimg(char* imgpath, uint16_t size);
|
||||
void fatfs_remove_allData(const char *dir_path);
|
||||
bool fats_img_isOK(char* img_path);
|
||||
void fat_getAllimgList(const char *dir_path, char** list, uint8_t* num);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
1042
main/dzbj/pages.c
Normal file
1042
main/dzbj/pages.c
Normal file
File diff suppressed because it is too large
Load Diff
37
main/dzbj/pages.h
Normal file
37
main/dzbj/pages.h
Normal file
@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "lvgl.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void app_test_display(); // 测试显示
|
||||
void app_img_display(); // 显示图片
|
||||
esp_err_t nvs_change_img(char *imgname); // 改变NVS中的图片路径
|
||||
void app_img_change(const char *img_name); // 改变图片
|
||||
void img_switch_task(void *pvParameters); // 图片切换任务
|
||||
void img_loop_task(void *pvParameters); // 图片循环任务
|
||||
|
||||
// 图片管理函数
|
||||
const char* get_current_image(void); // 获取当前图片文件名
|
||||
bool delete_current_image(void); // 删除当前图片
|
||||
void init_spiffs_image_list(void); // 初始化/扫描SPIFFS图片列表
|
||||
void free_spiffs_image_list(void); // 重置图片列表
|
||||
bool set_image_index_by_name(const char *name); // 根据文件名设置当前图片索引
|
||||
const char* get_next_image(void); // 获取下一张图片
|
||||
const char* get_prev_image(void); // 获取上一张图片
|
||||
void update_ui_ImgBle(const char *img_name); // 更新ui_ImgBle控件的图片
|
||||
void ble_image_navigate(const char *filename); // BLE接收后导航到ScreenImg显示
|
||||
void ble_image_navigate_with_data(const char *filename, uint8_t *data, size_t data_size); // BLE接收后直通显示(跳过SPIFFS重读)
|
||||
|
||||
#if LV_USE_GIF
|
||||
void pages_cleanup_gif(void); // 清理 GIF 控件资源
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
229
main/dzbj/sleep_mgr.c
Normal file
229
main/dzbj/sleep_mgr.c
Normal file
@ -0,0 +1,229 @@
|
||||
#include "../sleep_mgr/include/sleep_mgr.h"
|
||||
#include "dzbj_button.h"
|
||||
#include "pages.h"
|
||||
#include "pages_pwm.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_lvgl_port.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "lvgl.h"
|
||||
#include "../ui/screens/ui_ScreenSet.h"
|
||||
#include "lcd.h"
|
||||
#include <stdio.h>
|
||||
|
||||
static const char *TAG = "SLEEP";
|
||||
|
||||
static bool sleep_enabled = false;
|
||||
static bool screen_off = false;
|
||||
static int64_t last_activity_us = 0;
|
||||
static uint8_t saved_brightness = 50;
|
||||
static const uint8_t DEFAULT_BRIGHTNESS = 50; // 默认亮度
|
||||
static const uint8_t SLEEP_MODE_BRIGHTNESS = 10; // 休眠模式亮度
|
||||
|
||||
// 通知有用户活动
|
||||
void sleep_mgr_notify_activity(void)
|
||||
{
|
||||
last_activity_us = esp_timer_get_time();
|
||||
|
||||
// 如果屏幕已关闭,立即唤醒
|
||||
if (screen_off) {
|
||||
screen_off = false;
|
||||
|
||||
// 恢复 LVGL 和触摸输入
|
||||
if (lvgl_port_lock(100)) {
|
||||
// 1. 启用所有输入设备(恢复触摸事件处理)
|
||||
lv_indev_t *indev = lv_indev_get_next(NULL);
|
||||
while (indev) {
|
||||
lv_indev_enable(indev, true);
|
||||
ESP_LOGI(TAG, "输入设备已启用");
|
||||
indev = lv_indev_get_next(indev);
|
||||
}
|
||||
|
||||
// 2. 恢复刷新定时器(恢复屏幕重绘)
|
||||
lv_timer_t *refr_timer = _lv_disp_get_refr_timer(NULL);
|
||||
if (refr_timer) {
|
||||
lv_timer_resume(refr_timer);
|
||||
ESP_LOGI(TAG, "LVGL 刷新定时器已恢复");
|
||||
}
|
||||
|
||||
// 3. 强制刷新当前屏幕(因为GRAM被清空为黑色,需要重绘)
|
||||
lv_obj_invalidate(lv_scr_act());
|
||||
ESP_LOGI(TAG, "已标记屏幕需要重绘");
|
||||
|
||||
lvgl_port_unlock();
|
||||
}
|
||||
|
||||
// 延迟50ms等待LVGL完成至少一次重绘,避免看到黑屏
|
||||
vTaskDelay(pdMS_TO_TICKS(50));
|
||||
|
||||
// 恢复背光
|
||||
pwm_set_brightness(saved_brightness);
|
||||
ESP_LOGI(TAG, "屏幕唤醒,恢复亮度%d%%", saved_brightness);
|
||||
}
|
||||
}
|
||||
|
||||
// 按键活动回调(BOOT和KEY2共用)
|
||||
static void btn_activity_cb(int gpio_num, void *usr_data)
|
||||
{
|
||||
sleep_mgr_notify_activity();
|
||||
}
|
||||
|
||||
// 关闭屏幕(熄屏进入低功耗)
|
||||
static void screen_turn_off(void)
|
||||
{
|
||||
if (screen_off) return;
|
||||
|
||||
// 保存当前亮度
|
||||
saved_brightness = pwm_get_brightness();
|
||||
if (saved_brightness == 0) {
|
||||
saved_brightness = 50; // 防止保存到0值
|
||||
}
|
||||
|
||||
// 暂停 LVGL 并禁用触摸输入
|
||||
if (lvgl_port_lock(100)) {
|
||||
// 1. 暂停刷新定时器(停止屏幕重绘)
|
||||
lv_timer_t *refr_timer = _lv_disp_get_refr_timer(NULL);
|
||||
if (refr_timer) {
|
||||
lv_timer_pause(refr_timer);
|
||||
ESP_LOGI(TAG, "LVGL 刷新定时器已暂停");
|
||||
}
|
||||
|
||||
// 2. 禁用所有输入设备(停止触摸事件处理)
|
||||
lv_indev_t *indev = lv_indev_get_next(NULL);
|
||||
while (indev) {
|
||||
lv_indev_enable(indev, false);
|
||||
ESP_LOGI(TAG, "输入设备已禁用");
|
||||
indev = lv_indev_get_next(indev);
|
||||
}
|
||||
|
||||
lvgl_port_unlock();
|
||||
}
|
||||
|
||||
// 清空LCD GRAM为黑色(避免关闭背光后看到残影)
|
||||
lcd_clear_screen_black();
|
||||
|
||||
// 关闭背光
|
||||
screen_off = true;
|
||||
pwm_set_brightness(0);
|
||||
|
||||
ESP_LOGI(TAG, "屏幕已关闭(亮度=%d%%),系统进入真正低功耗模式(Light Sleep + LVGL暂停 + LCD GRAM清空)", saved_brightness);
|
||||
}
|
||||
|
||||
// 休眠管理任务
|
||||
static void sleep_mgr_task(void *pvParameters)
|
||||
{
|
||||
while (1) {
|
||||
uint32_t delay_ms = 500; // 默认轮询间隔 500ms
|
||||
|
||||
if (sleep_enabled) {
|
||||
// 检查LVGL触摸活动(屏幕开启时)
|
||||
if (!screen_off) {
|
||||
if (lvgl_port_lock(50)) {
|
||||
uint32_t inactive_ms = lv_disp_get_inactive_time(NULL);
|
||||
lvgl_port_unlock();
|
||||
|
||||
// 屏幕开启状态:检测到新触摸(< 500ms)立即更新活动时间
|
||||
if (inactive_ms < 500) {
|
||||
sleep_mgr_notify_activity();
|
||||
}
|
||||
}
|
||||
|
||||
// 检查超时熄屏
|
||||
int64_t now = esp_timer_get_time();
|
||||
int64_t elapsed_ms = (now - last_activity_us) / 1000;
|
||||
if (elapsed_ms >= SLEEP_TIMEOUT_MS) {
|
||||
screen_turn_off();
|
||||
}
|
||||
}
|
||||
|
||||
// 屏幕关闭状态:禁用触摸唤醒,只允许按键唤醒
|
||||
// 如需启用触摸唤醒,取消注释以下代码:
|
||||
/*
|
||||
else {
|
||||
if (lvgl_port_lock(50)) {
|
||||
uint32_t inactive_ms = lv_disp_get_inactive_time(NULL);
|
||||
lvgl_port_unlock();
|
||||
|
||||
// 检测到触摸(< 2000ms)立即唤醒
|
||||
if (inactive_ms < 2000) {
|
||||
sleep_mgr_notify_activity();
|
||||
ESP_LOGI(TAG, "触摸唤醒屏幕(inactive=%lums)", inactive_ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(delay_ms));
|
||||
}
|
||||
}
|
||||
|
||||
void sleep_mgr_init(void)
|
||||
{
|
||||
last_activity_us = esp_timer_get_time();
|
||||
|
||||
// 注意:BOOT按键由main.c的boot_btn_handler统一处理(唤醒+退出手电筒+返回Home)
|
||||
// 这里只注册KEY2按键唤醒功能
|
||||
dzbj_button_on_key2_press(btn_activity_cb, NULL);
|
||||
|
||||
xTaskCreate(sleep_mgr_task, "sleep_mgr", 3072, NULL, 3, NULL);
|
||||
ESP_LOGI(TAG, "休眠管理器初始化完成(超时=%ds)", SLEEP_TIMEOUT_MS / 1000);
|
||||
}
|
||||
|
||||
// 更新ScreenSet界面的亮度UI控件
|
||||
static void update_brightness_ui(uint8_t brightness)
|
||||
{
|
||||
if (!lvgl_port_lock(100)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新滑块位置
|
||||
if (ui_SliderBrightness) {
|
||||
lv_slider_set_value(ui_SliderBrightness, brightness, LV_ANIM_OFF);
|
||||
}
|
||||
|
||||
// 更新亮度文本标签
|
||||
if (ui_LabelBrightness) {
|
||||
char buf[8];
|
||||
snprintf(buf, sizeof(buf), "%d%%", brightness);
|
||||
lv_label_set_text(ui_LabelBrightness, buf);
|
||||
}
|
||||
|
||||
lvgl_port_unlock();
|
||||
}
|
||||
|
||||
void sleep_mgr_set_enabled(bool enabled)
|
||||
{
|
||||
sleep_enabled = enabled;
|
||||
if (enabled) {
|
||||
last_activity_us = esp_timer_get_time();
|
||||
// 进入休眠模式时,将亮度调节到10%
|
||||
pwm_set_brightness(SLEEP_MODE_BRIGHTNESS);
|
||||
update_brightness_ui(SLEEP_MODE_BRIGHTNESS);
|
||||
ESP_LOGI(TAG, "休眠模式已启用,亮度已调节至%d%%,%ds无操作将熄屏",
|
||||
SLEEP_MODE_BRIGHTNESS, SLEEP_TIMEOUT_MS / 1000);
|
||||
} else {
|
||||
// 禁用休眠模式时,恢复到默认亮度50%
|
||||
if (screen_off) {
|
||||
screen_off = false;
|
||||
pwm_set_brightness(DEFAULT_BRIGHTNESS);
|
||||
update_brightness_ui(DEFAULT_BRIGHTNESS);
|
||||
ESP_LOGI(TAG, "休眠模式已禁用,屏幕已恢复,亮度恢复到%d%%", DEFAULT_BRIGHTNESS);
|
||||
} else {
|
||||
pwm_set_brightness(DEFAULT_BRIGHTNESS);
|
||||
update_brightness_ui(DEFAULT_BRIGHTNESS);
|
||||
ESP_LOGI(TAG, "休眠模式已禁用,亮度恢复到%d%%", DEFAULT_BRIGHTNESS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool sleep_mgr_is_enabled(void)
|
||||
{
|
||||
return sleep_enabled;
|
||||
}
|
||||
|
||||
bool sleep_mgr_is_screen_off(void)
|
||||
{
|
||||
return screen_off;
|
||||
}
|
||||
@ -15,6 +15,8 @@ dependencies:
|
||||
esp_lcd_st77916: "1.0.1"
|
||||
esp_lcd_touch: "1.1.2"
|
||||
esp_lcd_touch_cst816s: "1.1.0"
|
||||
## JPEG 解码(dzbj 图片显示)
|
||||
esp_jpeg: "*"
|
||||
## Required IDF version
|
||||
idf:
|
||||
version: ">=5.3"
|
||||
|
||||
@ -1,13 +1,29 @@
|
||||
#ifndef _SLEEP_MGR_STUB_H_
|
||||
#define _SLEEP_MGR_STUB_H_
|
||||
|
||||
// Stub 头文件:dzbj ui_ScreenSet.c 引用,Phase 1 仅提供声明
|
||||
// 实际实现将在后续阶段添加
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
static inline void sleep_mgr_set_enabled(bool enabled) {
|
||||
(void)enabled;
|
||||
}
|
||||
// 休眠超时时间(毫秒)
|
||||
#define SLEEP_TIMEOUT_MS 10000
|
||||
|
||||
#endif // _SLEEP_MGR_STUB_H_
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// 初始化休眠管理器(需在UI、按键初始化之后调用)
|
||||
void sleep_mgr_init(void);
|
||||
|
||||
// 启用/禁用休眠模式
|
||||
void sleep_mgr_set_enabled(bool enabled);
|
||||
|
||||
// 获取休眠模式是否启用
|
||||
bool sleep_mgr_is_enabled(void);
|
||||
|
||||
// 通知有用户活动(按键按下、触摸屏幕时调用)
|
||||
void sleep_mgr_notify_activity(void);
|
||||
|
||||
// 查询屏幕是否已关闭
|
||||
bool sleep_mgr_is_screen_off(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -95,6 +95,7 @@ static void lvgl_port_touchpad_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *
|
||||
data->point.x = touchpad_x[0];
|
||||
data->point.y = touchpad_y[0];
|
||||
data->state = LV_INDEV_STATE_PRESSED;
|
||||
ESP_LOGI(TAG, "Touch detected: x=%d, y=%d, count=%d", touchpad_x[0], touchpad_y[0], touchpad_cnt);
|
||||
} else {
|
||||
data->state = LV_INDEV_STATE_RELEASED;
|
||||
}
|
||||
|
||||
@ -6,3 +6,4 @@ phy_init, data, phy, 0xf000, 0x1000,
|
||||
model, data, spiffs, 0x10000, 0x300000,
|
||||
ota_0, app, ota_0, 0x310000, 5M,
|
||||
ota_1, app, ota_1, 0x820000, 5M,
|
||||
storage, data, spiffs, 0xD20000, 0x2E0000,
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user