Compare commits
3 Commits
bcfd35b9b8
...
38b71ce221
| Author | SHA1 | Date | |
|---|---|---|---|
| 38b71ce221 | |||
| c4de88d7ee | |||
| 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,15 +0,0 @@
|
||||
// Stub 实现:dzbj UI 文件引用的函数,Phase 1 仅提供空实现
|
||||
// 实际实现将在后续阶段添加
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
const char* get_current_image(void) { return NULL; }
|
||||
bool delete_current_image(void) { return false; }
|
||||
void init_spiffs_image_list(void) {}
|
||||
void free_spiffs_image_list(void) {}
|
||||
bool set_image_index_by_name(const char *name) { (void)name; return false; }
|
||||
const char* get_next_image(void) { return NULL; }
|
||||
const char* get_prev_image(void) { return NULL; }
|
||||
void update_ui_ImgBle(const char *img_name) { (void)img_name; }
|
||||
@ -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
|
||||
|
||||
1
managed_components/espressif__esp_jpeg/.component_hash
Normal file
1
managed_components/espressif__esp_jpeg/.component_hash
Normal file
@ -0,0 +1 @@
|
||||
defb83669293cbf86d0fa86b475ba5517aceed04ed70db435388c151ab37b5d7
|
||||
40
managed_components/espressif__esp_jpeg/CHANGELOG.md
Normal file
40
managed_components/espressif__esp_jpeg/CHANGELOG.md
Normal file
@ -0,0 +1,40 @@
|
||||
## 1.3.1
|
||||
|
||||
- Fixed the format of Kconfig file
|
||||
|
||||
## 1.3.0
|
||||
|
||||
- Added option to get image size without decoding it
|
||||
|
||||
## 1.2.1
|
||||
|
||||
- Fixed decoding of non-conforming 0xFFFF marker
|
||||
|
||||
## 1.2.0
|
||||
|
||||
- Added option to for passing user defined working buffer
|
||||
|
||||
## 1.1.0
|
||||
|
||||
- Added support for decoding images without Huffman tables
|
||||
- Fixed undefined configuration options from Kconfig
|
||||
|
||||
## 1.0.5~3
|
||||
|
||||
- Added option to swap output color bytes regardless of JD_FORMAT
|
||||
|
||||
## 1.0.4
|
||||
|
||||
- Added ROM implementation support for ESP32-C6
|
||||
|
||||
## 1.0.2
|
||||
|
||||
- Fixed compiler warnings
|
||||
|
||||
## 1.0.1
|
||||
|
||||
- Fixed: exclude ESP32-C2 from list of ROM implementations
|
||||
|
||||
## 1.0.0
|
||||
|
||||
- Initial version
|
||||
1
managed_components/espressif__esp_jpeg/CHECKSUMS.json
Normal file
1
managed_components/espressif__esp_jpeg/CHECKSUMS.json
Normal file
File diff suppressed because one or more lines are too long
14
managed_components/espressif__esp_jpeg/CMakeLists.txt
Normal file
14
managed_components/espressif__esp_jpeg/CMakeLists.txt
Normal file
@ -0,0 +1,14 @@
|
||||
set(sources "jpeg_decoder.c")
|
||||
set(includes "include")
|
||||
|
||||
# Compile only when cannot use ROM code
|
||||
if(NOT CONFIG_JD_USE_ROM)
|
||||
list(APPEND sources "tjpgd/tjpgd.c")
|
||||
list(APPEND includes "tjpgd")
|
||||
endif()
|
||||
|
||||
if(CONFIG_JD_DEFAULT_HUFFMAN)
|
||||
list(APPEND sources "jpeg_default_huffman_table.c")
|
||||
endif()
|
||||
|
||||
idf_component_register(SRCS ${sources} INCLUDE_DIRS ${includes})
|
||||
80
managed_components/espressif__esp_jpeg/Kconfig
Normal file
80
managed_components/espressif__esp_jpeg/Kconfig
Normal file
@ -0,0 +1,80 @@
|
||||
menu "JPEG Decoder"
|
||||
|
||||
config JD_USE_ROM
|
||||
bool "Use TinyJPG Decoder from ROM"
|
||||
depends on ESP_ROM_HAS_JPEG_DECODE
|
||||
default y
|
||||
help
|
||||
By default, Espressif SoCs use TJpg decoder implemented in ROM code.
|
||||
If this feature is disabled, new configuration of TJpg decoder can be used.
|
||||
Refer to REAME.md for more details.
|
||||
|
||||
config JD_SZBUF
|
||||
int "Size of stream input buffer"
|
||||
depends on !JD_USE_ROM
|
||||
default 512
|
||||
|
||||
config JD_FORMAT
|
||||
int
|
||||
depends on !JD_USE_ROM
|
||||
default 0 if JD_FORMAT_RGB888
|
||||
default 1 if JD_FORMAT_RGB565
|
||||
|
||||
choice
|
||||
prompt "Output pixel format"
|
||||
depends on !JD_USE_ROM
|
||||
default JD_FORMAT_RGB888
|
||||
help
|
||||
Output format is selected at runtime.
|
||||
|
||||
config JD_FORMAT_RGB888
|
||||
bool "Support RGB565 and RGB888 output (16-bit/pix and 24-bit/pix)"
|
||||
config JD_FORMAT_RGB565
|
||||
bool "Support RGB565 output (16-bit/pix)"
|
||||
endchoice
|
||||
|
||||
config JD_USE_SCALE
|
||||
bool "Enable descaling"
|
||||
depends on !JD_USE_ROM
|
||||
default y
|
||||
help
|
||||
If scaling is enabled, size of output image can be lowered during decoding.
|
||||
|
||||
config JD_TBLCLIP
|
||||
bool "Use table conversion for saturation arithmetic"
|
||||
depends on !JD_USE_ROM
|
||||
default y
|
||||
help
|
||||
Use table conversion for saturation arithmetic. A bit faster, but increases 1 KB of code size.
|
||||
|
||||
config JD_FASTDECODE
|
||||
int
|
||||
depends on !JD_USE_ROM
|
||||
default 0 if JD_FASTDECODE_BASIC
|
||||
default 1 if JD_FASTDECODE_32BIT
|
||||
default 2 if JD_FASTDECODE_TABLE
|
||||
|
||||
choice
|
||||
prompt "Optimization level"
|
||||
depends on !JD_USE_ROM
|
||||
default JD_FASTDECODE_32BIT
|
||||
|
||||
config JD_FASTDECODE_BASIC
|
||||
bool "Basic optimization. Suitable for 8/16-bit MCUs"
|
||||
config JD_FASTDECODE_32BIT
|
||||
bool "+ 32-bit barrel shifter. Suitable for 32-bit MCUs"
|
||||
config JD_FASTDECODE_TABLE
|
||||
bool "+ Table conversion for huffman decoding (wants 6 << HUFF_BIT bytes of RAM)"
|
||||
endchoice
|
||||
|
||||
config JD_DEFAULT_HUFFMAN
|
||||
bool "Support images without Huffman table"
|
||||
depends on !JD_USE_ROM
|
||||
default n
|
||||
help
|
||||
Enable this option to support decoding JPEG images that lack an embedded Huffman table.
|
||||
When enabled, a default Huffman table is used during decoding, allowing the JPEG decoder to handle
|
||||
images without explicitly provided Huffman tables.
|
||||
|
||||
Note: Enabling this option increases ROM usage due to the inclusion of default Huffman tables.
|
||||
endmenu
|
||||
112
managed_components/espressif__esp_jpeg/README.md
Normal file
112
managed_components/espressif__esp_jpeg/README.md
Normal file
@ -0,0 +1,112 @@
|
||||
# JPEG Decoder: TJpgDec - Tiny JPEG Decompressor
|
||||
|
||||
[](https://components.espressif.com/components/espressif/esp_jpeg)
|
||||

|
||||
|
||||
TJpgDec is a lightweight JPEG image decompressor optimized for embedded systems with minimal memory consumption.
|
||||
|
||||
On some microcontrollers, TJpgDec is available in ROM and will be used by default, though this can be disabled in menuconfig if desired[^1].
|
||||
|
||||
[^1]: **_NOTE:_** When the ROM decoder is used, the configuration can't be changed. The configuration is fixed.
|
||||
|
||||
## Features
|
||||
|
||||
**Compilation configuration:**
|
||||
- Stream input buffer size (default: 512 bytes)
|
||||
- Output pixel format (default: RGB888; options: RGB888/RGB565)
|
||||
- Enable/disable output descaling (default: enabled)
|
||||
- Use table-based saturation for arithmetic operations (default: enabled)
|
||||
- Use default Huffman tables: Useful from decoding frames from cameras, that do not provide Huffman tables (default: disabled to save ROM)
|
||||
- Three optimization levels (default: 32-bit MCUs) for different CPU types:
|
||||
- 8/16-bit MCUs
|
||||
- 32-bit MCUs
|
||||
- Table-based Huffman decoding
|
||||
|
||||
**Runtime configuration:**
|
||||
- Pixel format options: RGB888, RGB565
|
||||
- Selectable scaling ratios: 1/1, 1/2, 1/4, or 1/8 (chosen at decompression)
|
||||
- Option to swap the first and last bytes of color values
|
||||
|
||||
## TJpgDec in ROM
|
||||
|
||||
On certain microcontrollers, TJpgDec is available in ROM and used by default. This can be disabled in menuconfig if you prefer to use the library code provided in this component.
|
||||
|
||||
### List of MCUs, which have TJpgDec in ROM
|
||||
- ESP32
|
||||
- ESP32-S3
|
||||
- ESP32-C3
|
||||
- ESP32-C6
|
||||
- ESP32-C5
|
||||
- ESP32-C61
|
||||
|
||||
### Fixed compilation configuration of the ROM code
|
||||
The ROM version uses the following fixed settings:
|
||||
- Stream input buffer: 512 bytes
|
||||
- Output pixel format: RGB888
|
||||
- Output descaling: enabled
|
||||
- Saturation table: enabled
|
||||
- Optimization level: Basic (JD_FASTDECODE = 0)
|
||||
|
||||
### Pros and cons using ROM code
|
||||
|
||||
**Advantages:**
|
||||
- Saves approximately 5 KB of flash memory with the same configuration
|
||||
|
||||
**Disadvantages:**
|
||||
- Compilation configuration cannot be changed
|
||||
- Certain configurations may provide faster performance
|
||||
|
||||
## Speed comparison
|
||||
|
||||
The table below shows example decoding times for a JPEG image using various configurations:
|
||||
* Image size: 320 x 180 px
|
||||
* Output format: RGB565
|
||||
* CPU: ESP32-S3
|
||||
* CPU frequency: 240 MHz
|
||||
* SPI mode: DIO
|
||||
* Internal RAM used
|
||||
* Measured in 1000 retries
|
||||
|
||||
| ROM used | JD_SZBUF | JD_FORMAT | JD_USE_SCALE | JD_TBLCLIP | JD_FASTDECODE | RAM buffer | Flash size | Approx. time |
|
||||
| :------: | :------: | :-------: | :----------: | :--------: | :-----------: | :--------: | :--------: | :----------: |
|
||||
| YES | 512 | RGB888 | 1 | 1 | 0 | 3.1 kB | 0 kB | 52 ms |
|
||||
| NO | 512 | RGB888 | 1 | 1 | 0 | 3.1 kB | 5 kB | 50 ms |
|
||||
| NO | 512 | RGB888 | 1 | 0 | 0 | 3.1 kB | 4 kB | 68 ms |
|
||||
| NO | 512 | RGB888 | 1 | 1 | 1 | 3.1 kB | 5 kB | 50 ms |
|
||||
| NO | 512 | RGB888 | 1 | 0 | 1 | 3.1 kB | 4 kB | 62 ms |
|
||||
| NO | 512 | RGB888 | 1 | 1 | 2 | 65.5 kB | 5.5 kB | 46 ms |
|
||||
| NO | 512 | RGB888 | 1 | 0 | 2 | 65.5 kB | 4.5 kB | 59 ms |
|
||||
| NO | 512 | RGB565 | 1 | 1 | 0 | 5 kB | 5 kB | 60 ms |
|
||||
| NO | 512 | RGB565 | 1 | 1 | 1 | 5 kB | 5 kB | 59 ms |
|
||||
| NO | 512 | RGB565 | 1 | 1 | 2 | 65.5 kB | 5.5 kB | 56 ms |
|
||||
|
||||
## Add to project
|
||||
|
||||
Packages from this repository are uploaded to [Espressif's component service](https://components.espressif.com/).
|
||||
You can add them to your project via `idf.py add-dependancy`, e.g.
|
||||
```
|
||||
idf.py add-dependency esp_jpeg==1.0.0
|
||||
```
|
||||
|
||||
Alternatively, you can create `idf_component.yml`. More is in [Espressif's documentation](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/tools/idf-component-manager.html).
|
||||
|
||||
## Example use
|
||||
|
||||
Here is example of usage. This calling is **blocking**.
|
||||
|
||||
```
|
||||
esp_jpeg_image_cfg_t jpeg_cfg = {
|
||||
.indata = (uint8_t *)jpeg_img_buf,
|
||||
.indata_size = jpeg_img_buf_size,
|
||||
.outbuf = out_img_buf,
|
||||
.outbuf_size = out_img_buf_size,
|
||||
.out_format = JPEG_IMAGE_OUT_FORMAT_RGB565,
|
||||
.out_scale = JPEG_IMAGE_SCALE_0,
|
||||
.flags = {
|
||||
.swap_color_bytes = 1,
|
||||
}
|
||||
};
|
||||
esp_jpeg_image_output_t outimg;
|
||||
|
||||
esp_jpeg_decode(&jpeg_cfg, &outimg);
|
||||
```
|
||||
@ -0,0 +1,7 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
set(COMPONENTS main)
|
||||
project(lcd_tjpgd)
|
||||
@ -0,0 +1,54 @@
|
||||
# LCD tjpgd example
|
||||
|
||||
This example shows how to decode a jpeg image and display it on an SPI-interfaced LCD, and rotates the image periodically.
|
||||
|
||||
Example using initialization of the LCD from [ESP-BSP](https://github.com/espressif/esp-bsp) project. For change the Espressif's board, go to [idf_component.yml](main/idf_component.yml) and change `esp-box` to another board from BSP.
|
||||
|
||||
## How to Use Example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
* An ESP development board
|
||||
* An SPI-interfaced LCD
|
||||
* An USB cable for power supply and programming
|
||||
|
||||
### Hardware Connection
|
||||
|
||||
The connection between ESP Board and the LCD is as follows:
|
||||
|
||||
```text
|
||||
ESP Board LCD Screen
|
||||
+---------+ +---------------------------------+
|
||||
| | | |
|
||||
| 3V3 +--------------+ VCC +----------------------+ |
|
||||
| | | | | |
|
||||
| GND +--------------+ GND | | |
|
||||
| | | | | |
|
||||
| DATA0 +--------------+ MOSI | | |
|
||||
| | | | | |
|
||||
| PCLK +--------------+ SCK | | |
|
||||
| | | | | |
|
||||
| CS +--------------+ CS | | |
|
||||
| | | | | |
|
||||
| D/C +--------------+ D/C | | |
|
||||
| | | | | |
|
||||
| RST +--------------+ RST | | |
|
||||
| | | | | |
|
||||
|BK_LIGHT +--------------+ BCKL +----------------------+ |
|
||||
| | | |
|
||||
+---------+ +---------------------------------+
|
||||
```
|
||||
|
||||
The GPIO numbers used by this example is taken from BSP.
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Run `idf.py -p PORT flash monitor` to build, flash and monitor the project. A flowing picture will be shown on the LCD screen.
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
For any technical queries, please open an [issue] (https://github.com/espressif/idf-extra-components/issues) on GitHub. We will get back to you soon.
|
||||
@ -0,0 +1,9 @@
|
||||
set(srcs "pretty_effect.c"
|
||||
"lcd_tjpgd_example_main.c"
|
||||
"decode_image.c"
|
||||
)
|
||||
|
||||
idf_component_register(SRCS ${srcs}
|
||||
INCLUDE_DIRS "."
|
||||
EMBED_FILES image.jpg
|
||||
PRIV_REQUIRES esp_lcd)
|
||||
@ -0,0 +1,9 @@
|
||||
menu "Example Configuration"
|
||||
config EXAMPLE_LCD_FLUSH_PARALLEL_LINES
|
||||
int "LCD flush parallel lines"
|
||||
default 12 if IDF_TARGET_ESP32C2
|
||||
default 16
|
||||
help
|
||||
To speed up transfers, every SPI transfer sends a bunch of lines.
|
||||
|
||||
endmenu
|
||||
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
|
||||
/*
|
||||
The image used for the effect on the LCD in the SPI master example is stored in flash
|
||||
as a jpeg file. This file contains the decode_image routine, which uses the tiny JPEG
|
||||
decoder library to decode this JPEG into a format that can be sent to the display.
|
||||
|
||||
Keep in mind that the decoder library cannot handle progressive files (will give
|
||||
``Image decoder: jd_prepare failed (8)`` as an error) so make sure to save in the correct
|
||||
format if you want to use a different image file.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "decode_image.h"
|
||||
#include "jpeg_decoder.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_check.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
|
||||
//Reference the binary-included jpeg file
|
||||
extern const uint8_t image_jpg_start[] asm("_binary_image_jpg_start");
|
||||
extern const uint8_t image_jpg_end[] asm("_binary_image_jpg_end");
|
||||
//Define the height and width of the jpeg file. Make sure this matches the actual jpeg
|
||||
//dimensions.
|
||||
|
||||
const char *TAG = "ImageDec";
|
||||
|
||||
//Decode the embedded image into pixel lines that can be used with the rest of the logic.
|
||||
esp_err_t decode_image(uint16_t **pixels)
|
||||
{
|
||||
*pixels = NULL;
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
//Alocate pixel memory. Each line is an array of IMAGE_W 16-bit pixels; the `*pixels` array itself contains pointers to these lines.
|
||||
*pixels = calloc(IMAGE_H * IMAGE_W, sizeof(uint16_t));
|
||||
ESP_GOTO_ON_FALSE((*pixels), ESP_ERR_NO_MEM, err, TAG, "Error allocating memory for lines");
|
||||
|
||||
//JPEG decode config
|
||||
esp_jpeg_image_cfg_t jpeg_cfg = {
|
||||
.indata = (uint8_t *)image_jpg_start,
|
||||
.indata_size = image_jpg_end - image_jpg_start,
|
||||
.outbuf = (uint8_t *)(*pixels),
|
||||
.outbuf_size = IMAGE_W * IMAGE_H * sizeof(uint16_t),
|
||||
.out_format = JPEG_IMAGE_FORMAT_RGB565,
|
||||
.out_scale = JPEG_IMAGE_SCALE_0,
|
||||
.flags = {
|
||||
.swap_color_bytes = 1,
|
||||
}
|
||||
};
|
||||
|
||||
//JPEG decode
|
||||
esp_jpeg_image_output_t outimg;
|
||||
esp_jpeg_decode(&jpeg_cfg, &outimg);
|
||||
|
||||
ESP_LOGI(TAG, "JPEG image decoded! Size of the decoded image is: %dpx x %dpx", outimg.width, outimg.height);
|
||||
|
||||
return ret;
|
||||
err:
|
||||
//Something went wrong! Exit cleanly, de-allocating everything we allocated.
|
||||
if (*pixels != NULL) {
|
||||
free(*pixels);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
|
||||
#define IMAGE_W 320
|
||||
#define IMAGE_H 240
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Decode the jpeg ``image.jpg`` embedded into the program file into pixel data.
|
||||
*
|
||||
* @param pixels A pointer to a pointer for an array of rows, which themselves are an array of pixels.
|
||||
* Effectively, you can get the pixel data by doing ``decode_image(&myPixels); pixelval=myPixels[ypos][xpos];``
|
||||
* @return - ESP_ERR_NOT_SUPPORTED if image is malformed or a progressive jpeg file
|
||||
* - ESP_ERR_NO_MEM if out of memory
|
||||
* - ESP_OK on succesful decode
|
||||
*/
|
||||
esp_err_t decode_image(uint16_t **pixels);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@ -0,0 +1,16 @@
|
||||
dependencies:
|
||||
esp-box:
|
||||
rules:
|
||||
- if: target == esp32s3
|
||||
version: ^2.4
|
||||
esp32_s2_kaluga_kit:
|
||||
rules:
|
||||
- if: target == esp32s2
|
||||
version: ^3.0
|
||||
esp_jpeg:
|
||||
version: '>=1.0.2'
|
||||
esp_wrover_kit:
|
||||
rules:
|
||||
- if: target == esp32
|
||||
version: ^1.5
|
||||
idf: '>=5.0'
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_lcd_panel_ops.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "pretty_effect.h"
|
||||
#include "bsp/esp-bsp.h"
|
||||
#include "bsp/display.h"
|
||||
|
||||
// Using SPI2 in the example, as it also supports octal modes on some targets
|
||||
#define LCD_HOST SPI2_HOST
|
||||
// To speed up transfers, every SPI transfer sends a bunch of lines. This define specifies how many.
|
||||
// More means more memory use, but less overhead for setting up / finishing transfers. Make sure 240
|
||||
// is dividable by this.
|
||||
#define PARALLEL_LINES CONFIG_EXAMPLE_LCD_FLUSH_PARALLEL_LINES
|
||||
// The number of frames to show before rotate the graph
|
||||
#define ROTATE_FRAME 30
|
||||
|
||||
#if BSP_LCD_H_RES > BSP_LCD_V_RES
|
||||
#define EXAMPLE_LCD_SWAP 0
|
||||
#define EXAMPLE_LCD_H_RES BSP_LCD_H_RES
|
||||
#define EXAMPLE_LCD_V_RES BSP_LCD_V_RES
|
||||
#else
|
||||
#define EXAMPLE_LCD_SWAP 1
|
||||
#define EXAMPLE_LCD_H_RES BSP_LCD_V_RES
|
||||
#define EXAMPLE_LCD_V_RES BSP_LCD_H_RES
|
||||
#endif
|
||||
|
||||
// Simple routine to generate some patterns and send them to the LCD. Because the
|
||||
// SPI driver handles transactions in the background, we can calculate the next line
|
||||
// while the previous one is being sent.
|
||||
static uint16_t *s_lines[2];
|
||||
static void display_pretty_colors(esp_lcd_panel_handle_t panel_handle)
|
||||
{
|
||||
int frame = 0;
|
||||
// Indexes of the line currently being sent to the LCD and the line we're calculating
|
||||
int sending_line = 0;
|
||||
int calc_line = 0;
|
||||
|
||||
// After ROTATE_FRAME frames, the image will be rotated
|
||||
while (frame <= ROTATE_FRAME) {
|
||||
frame++;
|
||||
for (int y = 0; y < EXAMPLE_LCD_V_RES; y += PARALLEL_LINES) {
|
||||
// Calculate a line
|
||||
pretty_effect_calc_lines(s_lines[calc_line], y, frame, PARALLEL_LINES);
|
||||
sending_line = calc_line;
|
||||
calc_line = !calc_line;
|
||||
// Send the calculated data
|
||||
esp_lcd_panel_draw_bitmap(panel_handle, 0, y, 0 + EXAMPLE_LCD_H_RES, y + PARALLEL_LINES, s_lines[sending_line]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
esp_lcd_panel_io_handle_t io_handle = NULL;
|
||||
esp_lcd_panel_handle_t panel_handle = NULL;
|
||||
|
||||
bsp_display_config_t disp_cfg = {
|
||||
.max_transfer_sz = EXAMPLE_LCD_H_RES * PARALLEL_LINES * sizeof(uint16_t),
|
||||
};
|
||||
// Display initialize from BSP
|
||||
bsp_display_new(&disp_cfg, &panel_handle, &io_handle);
|
||||
esp_lcd_panel_disp_on_off(panel_handle, true);
|
||||
bsp_display_backlight_on();
|
||||
|
||||
// Initialize the effect displayed
|
||||
ESP_ERROR_CHECK(pretty_effect_init());
|
||||
|
||||
// "Rotate or not" flag
|
||||
bool is_rotated = false;
|
||||
|
||||
// Allocate memory for the pixel buffers
|
||||
for (int i = 0; i < 2; i++) {
|
||||
s_lines[i] = heap_caps_malloc(EXAMPLE_LCD_H_RES * PARALLEL_LINES * sizeof(uint16_t), MALLOC_CAP_DMA);
|
||||
assert(s_lines[i] != NULL);
|
||||
}
|
||||
|
||||
#if EXAMPLE_LCD_SWAP
|
||||
esp_lcd_panel_swap_xy(panel_handle, true);
|
||||
#endif
|
||||
|
||||
// Start and rotate
|
||||
while (1) {
|
||||
// Set driver configuration to rotate 180 degrees each time
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, is_rotated, is_rotated));
|
||||
// Display
|
||||
display_pretty_colors(panel_handle);
|
||||
is_rotated = !is_rotated;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
#include "pretty_effect.h"
|
||||
#include "decode_image.h"
|
||||
|
||||
uint16_t *pixels;
|
||||
|
||||
//Grab a rgb16 pixel from the esp32_tiles image
|
||||
static inline uint16_t get_bgnd_pixel(int x, int y)
|
||||
{
|
||||
//Get color of the pixel on x,y coords
|
||||
return (uint16_t) * (pixels + (y * IMAGE_W) + x);
|
||||
}
|
||||
|
||||
//This variable is used to detect the next frame.
|
||||
static int prev_frame = -1;
|
||||
|
||||
//Instead of calculating the offsets for each pixel we grab, we pre-calculate the valueswhenever a frame changes, then re-use
|
||||
//these as we go through all the pixels in the frame. This is much, much faster.
|
||||
static int8_t xofs[320], yofs[240];
|
||||
static int8_t xcomp[320], ycomp[240];
|
||||
|
||||
//Calculate the pixel data for a set of lines (with implied line size of 320). Pixels go in dest, line is the Y-coordinate of the
|
||||
//first line to be calculated, linect is the amount of lines to calculate. Frame increases by one every time the entire image
|
||||
//is displayed; this is used to go to the next frame of animation.
|
||||
void pretty_effect_calc_lines(uint16_t *dest, int line, int frame, int linect)
|
||||
{
|
||||
if (frame != prev_frame) {
|
||||
//We need to calculate a new set of offset coefficients. Take some random sines as offsets to make everything
|
||||
//look pretty and fluid-y.
|
||||
for (int x = 0; x < 320; x++) {
|
||||
xofs[x] = sin(frame * 0.15 + x * 0.06) * 4;
|
||||
}
|
||||
for (int y = 0; y < 240; y++) {
|
||||
yofs[y] = sin(frame * 0.1 + y * 0.05) * 4;
|
||||
}
|
||||
for (int x = 0; x < 320; x++) {
|
||||
xcomp[x] = sin(frame * 0.11 + x * 0.12) * 4;
|
||||
}
|
||||
for (int y = 0; y < 240; y++) {
|
||||
ycomp[y] = sin(frame * 0.07 + y * 0.15) * 4;
|
||||
}
|
||||
prev_frame = frame;
|
||||
}
|
||||
for (int y = line; y < line + linect; y++) {
|
||||
for (int x = 0; x < 320; x++) {
|
||||
*dest++ = get_bgnd_pixel(x + yofs[y] + xcomp[x], y + xofs[x] + ycomp[y]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
esp_err_t pretty_effect_init(void)
|
||||
{
|
||||
return decode_image(&pixels);
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Calculate the effect for a bunch of lines.
|
||||
*
|
||||
* @param dest Destination for the pixels. Assumed to be LINECT * 320 16-bit pixel values.
|
||||
* @param line Starting line of the chunk of lines.
|
||||
* @param frame Current frame, used for animation
|
||||
* @param linect Amount of lines to calculate
|
||||
*/
|
||||
void pretty_effect_calc_lines(uint16_t *dest, int line, int frame, int linect);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Initialize the effect
|
||||
*
|
||||
* @return ESP_OK on success, an error from the jpeg decoder otherwise.
|
||||
*/
|
||||
esp_err_t pretty_effect_init(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@ -0,0 +1,4 @@
|
||||
# This file was generated using idf.py save-defconfig. It can be edited manually.
|
||||
# Espressif IoT Development Framework (ESP-IDF) 5.5.0 Project Minimal Configuration
|
||||
#
|
||||
CONFIG_TOUCH_SUPPRESS_DEPRECATE_WARN=y
|
||||
9
managed_components/espressif__esp_jpeg/idf_component.yml
Normal file
9
managed_components/espressif__esp_jpeg/idf_component.yml
Normal file
@ -0,0 +1,9 @@
|
||||
dependencies:
|
||||
idf: '>=5.0'
|
||||
description: 'JPEG Decoder: TJpgDec'
|
||||
repository: git://github.com/espressif/idf-extra-components.git
|
||||
repository_info:
|
||||
commit_sha: 746e83ddbea0db9c3d24993a87c4c737a60337ae
|
||||
path: esp_jpeg
|
||||
url: https://github.com/espressif/idf-extra-components/tree/master/esp_jpeg/
|
||||
version: 1.3.1
|
||||
106
managed_components/espressif__esp_jpeg/include/jpeg_decoder.h
Normal file
106
managed_components/espressif__esp_jpeg/include/jpeg_decoder.h
Normal file
@ -0,0 +1,106 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Scale of output image
|
||||
*
|
||||
*/
|
||||
typedef enum {
|
||||
JPEG_IMAGE_SCALE_0 = 0, /*!< No scale */
|
||||
JPEG_IMAGE_SCALE_1_2, /*!< Scale 1:2 */
|
||||
JPEG_IMAGE_SCALE_1_4, /*!< Scale 1:4 */
|
||||
JPEG_IMAGE_SCALE_1_8, /*!< Scale 1:8 */
|
||||
} esp_jpeg_image_scale_t;
|
||||
|
||||
/**
|
||||
* @brief Format of output image
|
||||
*
|
||||
*/
|
||||
typedef enum {
|
||||
JPEG_IMAGE_FORMAT_RGB888 = 0, /*!< Format RGB888 */
|
||||
JPEG_IMAGE_FORMAT_RGB565, /*!< Format RGB565 */
|
||||
} esp_jpeg_image_format_t;
|
||||
|
||||
/**
|
||||
* @brief JPEG Configuration Type
|
||||
*
|
||||
*/
|
||||
typedef struct esp_jpeg_image_cfg_s {
|
||||
uint8_t *indata; /*!< Input JPEG image */
|
||||
uint32_t indata_size; /*!< Size of input image */
|
||||
uint8_t *outbuf; /*!< Output buffer */
|
||||
uint32_t outbuf_size; /*!< Output buffer size */
|
||||
esp_jpeg_image_format_t out_format; /*!< Output image format */
|
||||
esp_jpeg_image_scale_t out_scale; /*!< Output scale */
|
||||
|
||||
struct {
|
||||
uint8_t swap_color_bytes: 1; /*!< Swap first and last color bytes */
|
||||
} flags;
|
||||
|
||||
struct {
|
||||
void *working_buffer; /*!< If set to NULL, a working buffer will be allocated in esp_jpeg_decode().
|
||||
Tjpgd does not use dynamic allocation, se we pass this buffer to Tjpgd that uses it as scratchpad */
|
||||
size_t working_buffer_size; /*!< Size of the working buffer. Must be set it working_buffer != NULL.
|
||||
Default size is 3.1kB or 65kB if JD_FASTDECODE == 2 */
|
||||
} advanced;
|
||||
|
||||
struct {
|
||||
uint32_t read; /*!< Internal count of read bytes */
|
||||
} priv;
|
||||
} esp_jpeg_image_cfg_t;
|
||||
|
||||
/**
|
||||
* @brief JPEG output info
|
||||
*/
|
||||
typedef struct esp_jpeg_image_output_s {
|
||||
uint16_t width; /*!< Width of the output image */
|
||||
uint16_t height; /*!< Height of the output image */
|
||||
size_t output_len; /*!< Length of the output image in bytes */
|
||||
} esp_jpeg_image_output_t;
|
||||
|
||||
/**
|
||||
* @brief Decode JPEG image
|
||||
*
|
||||
* @note This function is blocking.
|
||||
*
|
||||
* @param[in] cfg: Configuration structure
|
||||
* @param[out] img: Output image info
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_NO_MEM if there is no memory for allocating main structure
|
||||
* - ESP_FAIL if there is an error in decoding JPEG
|
||||
*/
|
||||
esp_err_t esp_jpeg_decode(esp_jpeg_image_cfg_t *cfg, esp_jpeg_image_output_t *img);
|
||||
|
||||
/**
|
||||
* @brief Get information about the JPEG image
|
||||
*
|
||||
* Use this function to get the size of the JPEG image without decoding it.
|
||||
* Allocate a buffer of size img->output_len to store the decoded image.
|
||||
*
|
||||
* @note cfg->outbuf and cfg->outbuf_size are not used in this function.
|
||||
* @param[in] cfg: Configuration structure
|
||||
* @param[out] img: Output image info
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_INVALID_ARG if cfg or img is NULL
|
||||
* - ESP_FAIL if there is an error in decoding JPEG
|
||||
*/
|
||||
esp_err_t esp_jpeg_get_image_info(esp_jpeg_image_cfg_t *cfg, esp_jpeg_image_output_t *img);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
287
managed_components/espressif__esp_jpeg/jpeg_decoder.c
Normal file
287
managed_components/espressif__esp_jpeg/jpeg_decoder.c
Normal file
@ -0,0 +1,287 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_rom_caps.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_check.h"
|
||||
#include "jpeg_decoder.h"
|
||||
|
||||
#if CONFIG_JD_USE_ROM
|
||||
/* When supported in ROM, use ROM functions */
|
||||
#if defined(ESP_ROM_HAS_JPEG_DECODE)
|
||||
#include "rom/tjpgd.h"
|
||||
#else
|
||||
#error Using JPEG decoder from ROM is not supported for selected target. Please select external code in menuconfig.
|
||||
#endif
|
||||
|
||||
/* The ROM code of TJPGD is older and has different return type in decode callback */
|
||||
typedef unsigned int jpeg_decode_out_t;
|
||||
#else
|
||||
/* When Tiny JPG Decoder is not in ROM or selected external code */
|
||||
#include "tjpgd.h"
|
||||
|
||||
/* The TJPGD outside the ROM code is newer and has different return type in decode callback */
|
||||
typedef int jpeg_decode_out_t;
|
||||
#endif
|
||||
|
||||
static const char *TAG = "JPEG";
|
||||
|
||||
#define LOBYTE(u16) ((uint8_t)(((uint16_t)(u16)) & 0xff))
|
||||
#define HIBYTE(u16) ((uint8_t)((((uint16_t)(u16))>>8) & 0xff))
|
||||
|
||||
#if defined(JD_FASTDECODE) && (JD_FASTDECODE == 2)
|
||||
#define JPEG_WORK_BUF_SIZE 65472
|
||||
#else
|
||||
#define JPEG_WORK_BUF_SIZE 3100 /* Recommended buffer size; Independent on the size of the image */
|
||||
#endif
|
||||
|
||||
/* If not set JD_FORMAT, it is set in ROM to RGB888, otherwise, it can be set in config */
|
||||
#ifndef JD_FORMAT
|
||||
#define JD_FORMAT 0
|
||||
#endif
|
||||
|
||||
/* Output color bytes from tjpgd (depends on JD_FORMAT) */
|
||||
#if (JD_FORMAT==0)
|
||||
#define ESP_JPEG_COLOR_BYTES 3
|
||||
#elif (JD_FORMAT==1)
|
||||
#define ESP_JPEG_COLOR_BYTES 2
|
||||
#elif (JD_FORMAT==2)
|
||||
#error Grayscale image output format is not supported
|
||||
#define ESP_JPEG_COLOR_BYTES 1
|
||||
#endif
|
||||
|
||||
/*******************************************************************************
|
||||
* Function definitions
|
||||
*******************************************************************************/
|
||||
static uint8_t jpeg_get_div_by_scale(esp_jpeg_image_scale_t scale);
|
||||
static uint8_t jpeg_get_color_bytes(esp_jpeg_image_format_t format);
|
||||
|
||||
static unsigned int jpeg_decode_in_cb(JDEC *jd, uint8_t *buff, unsigned int nbyte);
|
||||
static jpeg_decode_out_t jpeg_decode_out_cb(JDEC *jd, void *bitmap, JRECT *rect);
|
||||
static inline uint16_t ldb_word(const void *ptr);
|
||||
/*******************************************************************************
|
||||
* Public API functions
|
||||
*******************************************************************************/
|
||||
|
||||
esp_err_t esp_jpeg_decode(esp_jpeg_image_cfg_t *cfg, esp_jpeg_image_output_t *img)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
uint8_t *workbuf = NULL;
|
||||
JRESULT res;
|
||||
JDEC JDEC;
|
||||
|
||||
assert(cfg != NULL);
|
||||
assert(img != NULL);
|
||||
|
||||
const bool allocate_buffer = (cfg->advanced.working_buffer == NULL);
|
||||
const size_t workbuf_size = allocate_buffer ? JPEG_WORK_BUF_SIZE : cfg->advanced.working_buffer_size;
|
||||
if (allocate_buffer) {
|
||||
workbuf = heap_caps_malloc(JPEG_WORK_BUF_SIZE, MALLOC_CAP_DEFAULT);
|
||||
ESP_GOTO_ON_FALSE(workbuf, ESP_ERR_NO_MEM, err, TAG, "no mem for JPEG work buffer");
|
||||
} else {
|
||||
workbuf = cfg->advanced.working_buffer;
|
||||
ESP_RETURN_ON_FALSE(workbuf_size != 0, ESP_ERR_INVALID_ARG, TAG, "Working buffer size not defined!");
|
||||
}
|
||||
|
||||
|
||||
cfg->priv.read = 0;
|
||||
|
||||
/* Prepare image */
|
||||
res = jd_prepare(&JDEC, jpeg_decode_in_cb, workbuf, workbuf_size, cfg);
|
||||
ESP_GOTO_ON_FALSE((res == JDR_OK), ESP_FAIL, err, TAG, "Error in preparing JPEG image! %d", res);
|
||||
|
||||
const uint8_t scale_div = jpeg_get_div_by_scale(cfg->out_scale);
|
||||
const uint8_t out_color_bytes = jpeg_get_color_bytes(cfg->out_format);
|
||||
|
||||
/* Size of output image */
|
||||
const uint32_t outsize = (JDEC.height / scale_div) * (JDEC.width / scale_div) * out_color_bytes;
|
||||
ESP_GOTO_ON_FALSE((outsize <= cfg->outbuf_size), ESP_ERR_NO_MEM, err, TAG, "Not enough size in output buffer!");
|
||||
|
||||
/* Size of output image */
|
||||
img->height = JDEC.height / scale_div;
|
||||
img->width = JDEC.width / scale_div;
|
||||
img->output_len = outsize;
|
||||
|
||||
/* Decode JPEG */
|
||||
res = jd_decomp(&JDEC, jpeg_decode_out_cb, cfg->out_scale);
|
||||
ESP_GOTO_ON_FALSE((res == JDR_OK), ESP_FAIL, err, TAG, "Error in decoding JPEG image! %d", res);
|
||||
|
||||
err:
|
||||
if (workbuf && allocate_buffer) {
|
||||
free(workbuf);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t esp_jpeg_get_image_info(esp_jpeg_image_cfg_t *cfg, esp_jpeg_image_output_t *img)
|
||||
{
|
||||
if (cfg == NULL || img == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
} else if (cfg->indata == NULL || cfg->indata_size < 5) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
esp_err_t ret = ESP_FAIL;
|
||||
|
||||
if (ldb_word(cfg->indata) != 0xFFD8) {
|
||||
return ESP_FAIL; /* Err: SOI is not detected */
|
||||
}
|
||||
unsigned ofs = 2; // Start after SOI marker
|
||||
|
||||
while (true) {
|
||||
/* Get a JPEG marker */
|
||||
uint8_t *seg = cfg->indata + ofs; /* Segment pointer */
|
||||
unsigned short marker = ldb_word(seg); /* Marker */
|
||||
unsigned int len = ldb_word(seg + 2); /* Length field */
|
||||
if (len <= 2 || (marker >> 8) != 0xFF) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
ofs += 2 + len; /* Number of bytes loaded */
|
||||
if (ofs > cfg->indata_size) {
|
||||
return ESP_FAIL; // No more data
|
||||
}
|
||||
|
||||
if ((marker & 0xFF) == 0xC0) { /* SOF0 (baseline JPEG) */
|
||||
seg += 4; /* Skip marker and length field */
|
||||
|
||||
/* Size of output image */
|
||||
img->height = ldb_word(seg + 1);
|
||||
img->width = ldb_word(seg + 3);
|
||||
const uint8_t scale_div = jpeg_get_div_by_scale(cfg->out_scale);
|
||||
const uint8_t out_color_bytes = jpeg_get_color_bytes(cfg->out_format);
|
||||
img->output_len = (img->height / scale_div) * (img->width / scale_div) * out_color_bytes;
|
||||
ret = ESP_OK;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Private API functions
|
||||
*******************************************************************************/
|
||||
|
||||
static unsigned int jpeg_decode_in_cb(JDEC *dec, uint8_t *buff, unsigned int nbyte)
|
||||
{
|
||||
assert(dec != NULL);
|
||||
|
||||
uint32_t to_read = nbyte;
|
||||
esp_jpeg_image_cfg_t *cfg = (esp_jpeg_image_cfg_t *)dec->device;
|
||||
assert(cfg != NULL);
|
||||
|
||||
if (buff) {
|
||||
if (cfg->priv.read + to_read > cfg->indata_size) {
|
||||
to_read = cfg->indata_size - cfg->priv.read;
|
||||
}
|
||||
|
||||
/* Copy data from JPEG image */
|
||||
memcpy(buff, &cfg->indata[cfg->priv.read], to_read);
|
||||
cfg->priv.read += to_read;
|
||||
} else if (buff == NULL) {
|
||||
/* Skip data */
|
||||
cfg->priv.read += to_read;
|
||||
}
|
||||
|
||||
return to_read;
|
||||
}
|
||||
|
||||
static jpeg_decode_out_t jpeg_decode_out_cb(JDEC *dec, void *bitmap, JRECT *rect)
|
||||
{
|
||||
uint16_t color = 0;
|
||||
assert(dec != NULL);
|
||||
|
||||
esp_jpeg_image_cfg_t *cfg = (esp_jpeg_image_cfg_t *)dec->device;
|
||||
assert(cfg != NULL);
|
||||
assert(bitmap != NULL);
|
||||
assert(rect != NULL);
|
||||
|
||||
uint8_t scale_div = jpeg_get_div_by_scale(cfg->out_scale);
|
||||
uint8_t out_color_bytes = jpeg_get_color_bytes(cfg->out_format);
|
||||
|
||||
/* Copy decoded image data to output buffer */
|
||||
uint8_t *in = (uint8_t *)bitmap;
|
||||
uint32_t line = dec->width / scale_div;
|
||||
uint8_t *dst = (uint8_t *)cfg->outbuf;
|
||||
for (int y = rect->top; y <= rect->bottom; y++) {
|
||||
for (int x = rect->left; x <= rect->right; x++) {
|
||||
if ( (JD_FORMAT == 0 && cfg->out_format == JPEG_IMAGE_FORMAT_RGB888) ||
|
||||
(JD_FORMAT == 1 && cfg->out_format == JPEG_IMAGE_FORMAT_RGB565) ) {
|
||||
/* Output image format is same as set in TJPGD */
|
||||
for (int b = 0; b < ESP_JPEG_COLOR_BYTES; b++) {
|
||||
if (cfg->flags.swap_color_bytes) {
|
||||
dst[(y * line * out_color_bytes) + x * out_color_bytes + b] = in[out_color_bytes - b - 1];
|
||||
} else {
|
||||
dst[(y * line * out_color_bytes) + x * out_color_bytes + b] = in[b];
|
||||
}
|
||||
}
|
||||
} else if (JD_FORMAT == 0 && cfg->out_format == JPEG_IMAGE_FORMAT_RGB565) {
|
||||
/* Output image format is not same as set in TJPGD */
|
||||
/* We need to convert the 3 bytes in `in` to a rgb565 value */
|
||||
color = ((in[0] & 0xF8) << 8);
|
||||
color |= ((in[1] & 0xFC) << 3);
|
||||
color |= (in[2] >> 3);
|
||||
|
||||
if (cfg->flags.swap_color_bytes) {
|
||||
dst[(y * line * out_color_bytes) + (x * out_color_bytes)] = HIBYTE(color);
|
||||
dst[(y * line * out_color_bytes) + (x * out_color_bytes) + 1] = LOBYTE(color);
|
||||
} else {
|
||||
dst[(y * line * out_color_bytes) + (x * out_color_bytes) + 1] = HIBYTE(color);
|
||||
dst[(y * line * out_color_bytes) + (x * out_color_bytes)] = LOBYTE(color);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Selected output format is not supported!");
|
||||
assert(0);
|
||||
}
|
||||
in += ESP_JPEG_COLOR_BYTES;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static uint8_t jpeg_get_div_by_scale(esp_jpeg_image_scale_t scale)
|
||||
{
|
||||
switch (scale) {
|
||||
/* Not scaled */
|
||||
case JPEG_IMAGE_SCALE_0:
|
||||
return 1;
|
||||
/* Scaled 1:2 */
|
||||
case JPEG_IMAGE_SCALE_1_2:
|
||||
return 2;
|
||||
/* Scaled 1:4 */
|
||||
case JPEG_IMAGE_SCALE_1_4:
|
||||
return 4;
|
||||
/* Scaled 1:8 */
|
||||
case JPEG_IMAGE_SCALE_1_8:
|
||||
return 8;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static uint8_t jpeg_get_color_bytes(esp_jpeg_image_format_t format)
|
||||
{
|
||||
switch (format) {
|
||||
/* RGB888 (24-bit/pix) */
|
||||
case JPEG_IMAGE_FORMAT_RGB888:
|
||||
return 3;
|
||||
/* RGB565 (16-bit/pix) */
|
||||
case JPEG_IMAGE_FORMAT_RGB565:
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static inline uint16_t ldb_word(const void *ptr)
|
||||
{
|
||||
const uint8_t *p = (const uint8_t *)ptr;
|
||||
return ((uint16_t)p[0] << 8) | p[1];
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
// Default Huffman tables for baseline JPEG
|
||||
|
||||
// These values are taken directly from CCITT Rec. T.81 (1992 E) Appendix K.3.3
|
||||
// The *_num_bits array always contains exactly 16 elements.
|
||||
// Each element represents the number of Huffman codes of a specific length:
|
||||
// - The first element corresponds to codes of length 1 bit,
|
||||
// - The second element to codes of length 2 bits, and so forth up to 16 bits.
|
||||
//
|
||||
// The *_values array has a length equal to the sum of all elements in the *_num_bits array,
|
||||
// representing the actual values associated with each Huffman code in order.
|
||||
|
||||
// Luminance DC Table
|
||||
const unsigned char esp_jpeg_lum_dc_num_bits[16] = {0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0};
|
||||
const unsigned esp_jpeg_lum_dc_codes_total = 12;
|
||||
const unsigned char esp_jpeg_lum_dc_values[12] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
|
||||
|
||||
// Chrominance DC Table
|
||||
const unsigned char esp_jpeg_chrom_dc_num_bits[16] = {0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0};
|
||||
const unsigned esp_jpeg_chrom_dc_codes_total = 12;
|
||||
const unsigned char esp_jpeg_chrom_dc_values[12] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
|
||||
|
||||
// Luminance AC Table
|
||||
const unsigned char esp_jpeg_lum_ac_num_bits[16] = {0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125};
|
||||
const unsigned esp_jpeg_lum_ac_codes_total = 162;
|
||||
const unsigned char esp_jpeg_lum_ac_values[162] = {
|
||||
0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07,
|
||||
0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xA1, 0x08, 0x23, 0x42, 0xB1, 0xC1, 0x15, 0x52, 0xD1, 0xF0,
|
||||
0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0A, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x25, 0x26, 0x27, 0x28,
|
||||
0x29, 0x2A, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
|
||||
0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
|
||||
0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
|
||||
0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
|
||||
0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5,
|
||||
0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE1, 0xE2,
|
||||
0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8,
|
||||
0xF9, 0xFA
|
||||
};
|
||||
|
||||
// Chrominance AC Table
|
||||
const unsigned char esp_jpeg_chrom_ac_num_bits[16] = {0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 119};
|
||||
const unsigned esp_jpeg_chrom_ac_codes_total = 162;
|
||||
const unsigned char esp_jpeg_chrom_ac_values[162] = {
|
||||
0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71,
|
||||
0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xA1, 0xB1, 0xC1, 0x09, 0x23, 0x33, 0x52, 0xF0,
|
||||
0x15, 0x62, 0x72, 0xD1, 0x0A, 0x16, 0x24, 0x34, 0xE1, 0x25, 0xF1, 0x17, 0x18, 0x19, 0x1A, 0x26,
|
||||
0x27, 0x28, 0x29, 0x2A, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
|
||||
0x49, 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
||||
0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
|
||||
0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5,
|
||||
0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3,
|
||||
0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA,
|
||||
0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8,
|
||||
0xF9, 0xFA
|
||||
};
|
||||
202
managed_components/espressif__esp_jpeg/license.txt
Normal file
202
managed_components/espressif__esp_jpeg/license.txt
Normal file
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@ -0,0 +1,5 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
set(COMPONENTS main)
|
||||
project(esp_jpeg_test)
|
||||
@ -0,0 +1,5 @@
|
||||
idf_component_register(SRCS "tjpgd_test.c" "test_tjpgd_main.c"
|
||||
INCLUDE_DIRS "."
|
||||
PRIV_REQUIRES "unity"
|
||||
WHOLE_ARCHIVE
|
||||
EMBED_FILES "logo.jpg" "usb_camera.jpg" "usb_camera_2.jpg")
|
||||
@ -0,0 +1,4 @@
|
||||
dependencies:
|
||||
espressif/esp_jpeg:
|
||||
version: "*"
|
||||
override_path: "../../"
|
||||
@ -0,0 +1,64 @@
|
||||
from PIL import Image
|
||||
|
||||
|
||||
def jpg_to_rgb888_hex_c_array(input_filename: str, output_filename: str) -> str:
|
||||
"""
|
||||
Convert a .jpg file to RGB888 hex data and format it as a C-style array.
|
||||
|
||||
Parameters:
|
||||
input_filename (str): The path to the JPEG file.
|
||||
|
||||
Returns:
|
||||
str: A string representing the RGB888 hex data formatted as a C array.
|
||||
"""
|
||||
# Open the image file
|
||||
with Image.open(input_filename) as img:
|
||||
# Ensure the image is in RGB mode
|
||||
rgb_img = img.convert("RGB")
|
||||
|
||||
# Get image dimensions
|
||||
width, height = rgb_img.size
|
||||
|
||||
# List to store hex values as C-style entries
|
||||
hex_data = []
|
||||
|
||||
# Iterate over each pixel to get RGB values
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
r, g, b = rgb_img.getpixel((x, y))
|
||||
# Format each RGB value as C-style hex (e.g., 0xRRGGBB)
|
||||
hex_data.append(f"0x{r:02X}{g:02X}{b:02X}")
|
||||
|
||||
# Format as a C-style array with line breaks for readability
|
||||
hex_array = ",\n ".join(hex_data)
|
||||
c_array = f"unsigned int image_data[{width * height}] = {{\n {hex_array}\n}};"
|
||||
|
||||
# Write the C array to the output file
|
||||
with open(output_filename, "w") as file:
|
||||
file.write(c_array)
|
||||
|
||||
print(f"C-style RGB888 hex array saved to {output_filename}")
|
||||
|
||||
return c_array
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main function to convert a JPEG file to an RGB888 C-style hex array.
|
||||
|
||||
Instructions:
|
||||
1. Replace 'input.jpg' with the path to your JPEG file.
|
||||
2. Run the script to get the C-style array output.
|
||||
"""
|
||||
# Input JPEG file path
|
||||
input_filename = "usb_camera.jpg" # Replace with your JPEG file path
|
||||
|
||||
# Output file path for the C array
|
||||
output_filename = "output_array.c" # Specify your desired output filename
|
||||
|
||||
# Convert JPEG to C-style RGB888 hex array
|
||||
jpg_to_rgb888_hex_c_array(input_filename, output_filename)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
BIN
managed_components/espressif__esp_jpeg/test_apps/main/logo.jpg
Normal file
BIN
managed_components/espressif__esp_jpeg/test_apps/main/logo.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.4 KiB |
@ -0,0 +1,7 @@
|
||||
// JPEG encoded image 46x46, 7561 bytes
|
||||
extern const unsigned char logo_jpg[] asm("_binary_logo_jpg_start");
|
||||
|
||||
extern char _binary_logo_jpg_start;
|
||||
extern char _binary_logo_jpg_end;
|
||||
// Must be defined as macro because extern variables are not known at compile time (but at link time)
|
||||
#define logo_jpg_len (&_binary_logo_jpg_end - &_binary_logo_jpg_start)
|
||||
File diff suppressed because one or more lines are too long
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "unity.h"
|
||||
#include "unity_test_runner.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_newlib.h"
|
||||
|
||||
#include "unity_test_utils_memory.h"
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
unity_utils_record_free_mem();
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
{
|
||||
esp_reent_cleanup(); //clean up some of the newlib's lazy allocations
|
||||
unity_utils_evaluate_leaks_direct(0);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
printf("Running esp_jpeg component tests\n");
|
||||
unity_run_menu();
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
/*
|
||||
Raw data from Logitech C170 USB camera was reconstructed to usb_camera_2.jpg
|
||||
It was converted to RGB888 array with jpg_to_rgb888_hex.py
|
||||
*/
|
||||
|
||||
// JPEG encoded frame 160x120, 1384 bytes, has broken 0xFFFF marker
|
||||
extern const unsigned char camera_2_jpg[] asm("_binary_usb_camera_2_jpg_start");
|
||||
|
||||
extern char _binary_usb_camera_2_jpg_start;
|
||||
extern char _binary_usb_camera_2_jpg_end;
|
||||
// Must be defined as macro because extern variables are not known at compile time (but at link time)
|
||||
#define camera_2_jpg_len (&_binary_usb_camera_2_jpg_end - &_binary_usb_camera_2_jpg_start)
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,12 @@
|
||||
/*
|
||||
Raw data from Logitech C270 USB camera was reconstructed to usb_camera.jpg
|
||||
It was converted to RGB888 array with jpg_to_rgb888_hex.py
|
||||
*/
|
||||
|
||||
// JPEG encoded frame 160x120, 2632 bytes, no huffman tables, double block size (16x8 pixels)
|
||||
extern const unsigned char jpeg_no_huffman[] asm("_binary_usb_camera_jpg_start");
|
||||
|
||||
extern char _binary_usb_camera_jpg_start;
|
||||
extern char _binary_usb_camera_jpg_end;
|
||||
// Must be defined as macro because extern variables are not known at compile time (but at link time)
|
||||
#define jpeg_no_huffman_len (&_binary_usb_camera_jpg_end - &_binary_usb_camera_jpg_start)
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,328 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "unity.h"
|
||||
|
||||
|
||||
#include "jpeg_decoder.h"
|
||||
#include "test_logo_jpg.h"
|
||||
#include "test_logo_rgb888.h"
|
||||
#include "test_usb_camera_2_jpg.h"
|
||||
#include "test_usb_camera_2_rgb888.h"
|
||||
|
||||
#define TESTW 46
|
||||
#define TESTH 46
|
||||
|
||||
void esp_jpeg_print_ascii(unsigned char *rgb888, esp_jpeg_image_output_t *outimg)
|
||||
{
|
||||
char aapix[] = " .:;+=xX$$";
|
||||
unsigned char *p = rgb888 + 2;
|
||||
|
||||
for (int y = 0; y < outimg->width; y++) {
|
||||
for (int x = 0; x < outimg->height; x++) {
|
||||
int v = ((*p) * (sizeof(aapix) - 2) * 2) / 256;
|
||||
printf("%c%c", aapix[v / 2], aapix[(v + 1) / 2]);
|
||||
p += 3;
|
||||
}
|
||||
printf("%c%c", ' ', '\n');
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test JPEG decompression library", "[esp_jpeg]")
|
||||
{
|
||||
unsigned char *decoded, *p;
|
||||
const unsigned char *o;
|
||||
int decoded_outsize = TESTW * TESTH * 3;
|
||||
|
||||
decoded = malloc(decoded_outsize);
|
||||
for (int x = 0; x < decoded_outsize; x += 2) {
|
||||
decoded[x] = 0;
|
||||
decoded[x + 1] = 0xff;
|
||||
}
|
||||
|
||||
/* JPEG decode */
|
||||
esp_jpeg_image_cfg_t jpeg_cfg = {
|
||||
.indata = (uint8_t *)logo_jpg,
|
||||
.indata_size = logo_jpg_len,
|
||||
.outbuf = decoded,
|
||||
.outbuf_size = decoded_outsize,
|
||||
.out_format = JPEG_IMAGE_FORMAT_RGB888,
|
||||
.out_scale = JPEG_IMAGE_SCALE_0,
|
||||
.flags = {
|
||||
.swap_color_bytes = 0,
|
||||
}
|
||||
};
|
||||
esp_jpeg_image_output_t outimg;
|
||||
esp_err_t err = esp_jpeg_decode(&jpeg_cfg, &outimg);
|
||||
TEST_ASSERT_EQUAL(err, ESP_OK);
|
||||
|
||||
/* Decoded image size */
|
||||
TEST_ASSERT_EQUAL(outimg.width, TESTW);
|
||||
TEST_ASSERT_EQUAL(outimg.height, TESTH);
|
||||
|
||||
p = decoded;
|
||||
o = logo_rgb888;
|
||||
for (int x = 0; x < outimg.width * outimg.height; x++) {
|
||||
/* The color can be +- 2 */
|
||||
TEST_ASSERT_UINT8_WITHIN(2, o[0], p[0]);
|
||||
TEST_ASSERT_UINT8_WITHIN(2, o[1], p[1]);
|
||||
TEST_ASSERT_UINT8_WITHIN(2, o[2], p[2]);
|
||||
|
||||
p += 3;
|
||||
o += 3;
|
||||
}
|
||||
|
||||
esp_jpeg_print_ascii(decoded, &outimg);
|
||||
|
||||
free(decoded);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief JPEG unknown size test
|
||||
*
|
||||
* This test case verifies the functionality of the JPEG decompression library
|
||||
* when decoding an image with unknown size. The image is decoded from a
|
||||
* JPEG file, and the output size is determined dynamically. The test checks
|
||||
* that the decoded image dimensions match the expected values and that the
|
||||
* pixel data is within an acceptable tolerance range.
|
||||
*/
|
||||
TEST_CASE("Test JPEG unknown size", "[esp_jpeg]")
|
||||
{
|
||||
unsigned char *decoded, *p;
|
||||
const unsigned char *o;
|
||||
|
||||
/* JPEG decode */
|
||||
esp_jpeg_image_cfg_t jpeg_cfg = {
|
||||
.indata = (uint8_t *)logo_jpg,
|
||||
.indata_size = logo_jpg_len,
|
||||
.out_format = JPEG_IMAGE_FORMAT_RGB888,
|
||||
};
|
||||
|
||||
// 1. Get required output size
|
||||
esp_jpeg_image_output_t outimg;
|
||||
esp_err_t err = esp_jpeg_get_image_info(&jpeg_cfg, &outimg);
|
||||
TEST_ASSERT_EQUAL(err, ESP_OK);
|
||||
TEST_ASSERT_EQUAL(TESTW * TESTH * 3, outimg.output_len);
|
||||
TEST_ASSERT_EQUAL(outimg.width, TESTW);
|
||||
TEST_ASSERT_EQUAL(outimg.height, TESTH);
|
||||
|
||||
// 2. Allocate output buffer and assign it to the config
|
||||
decoded = malloc(outimg.output_len);
|
||||
TEST_ASSERT_NOT_NULL(decoded);
|
||||
jpeg_cfg.outbuf = decoded;
|
||||
jpeg_cfg.outbuf_size = outimg.output_len;
|
||||
|
||||
// 3. Decode the image
|
||||
err = esp_jpeg_decode(&jpeg_cfg, &outimg);
|
||||
TEST_ASSERT_EQUAL(err, ESP_OK);
|
||||
|
||||
/* Decoded image size */
|
||||
TEST_ASSERT_EQUAL(TESTW * TESTH * 3, outimg.output_len);
|
||||
TEST_ASSERT_EQUAL(outimg.width, TESTW);
|
||||
TEST_ASSERT_EQUAL(outimg.height, TESTH);
|
||||
|
||||
p = decoded;
|
||||
o = logo_rgb888;
|
||||
for (int x = 0; x < outimg.width * outimg.height; x++) {
|
||||
/* The color can be +- 2 */
|
||||
TEST_ASSERT_UINT8_WITHIN(2, o[0], p[0]);
|
||||
TEST_ASSERT_UINT8_WITHIN(2, o[1], p[1]);
|
||||
TEST_ASSERT_UINT8_WITHIN(2, o[2], p[2]);
|
||||
|
||||
p += 3;
|
||||
o += 3;
|
||||
}
|
||||
free(decoded);
|
||||
}
|
||||
|
||||
#define WORKING_BUFFER_SIZE 4096
|
||||
TEST_CASE("Test JPEG decompression library: User defined working buffer", "[esp_jpeg]")
|
||||
{
|
||||
unsigned char *decoded, *p;
|
||||
const unsigned char *o;
|
||||
int decoded_outsize = TESTW * TESTH * 3;
|
||||
|
||||
decoded = malloc(decoded_outsize);
|
||||
uint8_t *working_buf = malloc(WORKING_BUFFER_SIZE);
|
||||
assert(decoded);
|
||||
assert(working_buf);
|
||||
|
||||
for (int x = 0; x < decoded_outsize; x += 2) {
|
||||
decoded[x] = 0;
|
||||
decoded[x + 1] = 0xff;
|
||||
}
|
||||
|
||||
/* JPEG decode */
|
||||
esp_jpeg_image_cfg_t jpeg_cfg = {
|
||||
.indata = (uint8_t *)logo_jpg,
|
||||
.indata_size = logo_jpg_len,
|
||||
.outbuf = decoded,
|
||||
.outbuf_size = decoded_outsize,
|
||||
.out_format = JPEG_IMAGE_FORMAT_RGB888,
|
||||
.out_scale = JPEG_IMAGE_SCALE_0,
|
||||
.flags = {
|
||||
.swap_color_bytes = 0,
|
||||
},
|
||||
.advanced = {
|
||||
.working_buffer = working_buf,
|
||||
.working_buffer_size = WORKING_BUFFER_SIZE,
|
||||
},
|
||||
};
|
||||
esp_jpeg_image_output_t outimg;
|
||||
esp_err_t err = esp_jpeg_decode(&jpeg_cfg, &outimg);
|
||||
TEST_ASSERT_EQUAL(err, ESP_OK);
|
||||
|
||||
/* Decoded image size */
|
||||
TEST_ASSERT_EQUAL(outimg.width, TESTW);
|
||||
TEST_ASSERT_EQUAL(outimg.height, TESTH);
|
||||
|
||||
p = decoded;
|
||||
o = logo_rgb888;
|
||||
for (int x = 0; x < outimg.width * outimg.height; x++) {
|
||||
/* The color can be +- 2 */
|
||||
TEST_ASSERT_UINT8_WITHIN(2, o[0], p[0]);
|
||||
TEST_ASSERT_UINT8_WITHIN(2, o[1], p[1]);
|
||||
TEST_ASSERT_UINT8_WITHIN(2, o[2], p[2]);
|
||||
|
||||
p += 3;
|
||||
o += 3;
|
||||
}
|
||||
free(working_buf);
|
||||
free(decoded);
|
||||
}
|
||||
|
||||
#if CONFIG_JD_DEFAULT_HUFFMAN
|
||||
#include "test_usb_camera_jpg.h"
|
||||
#include "test_usb_camera_rgb888.h"
|
||||
|
||||
/**
|
||||
* @brief Test for JPEG decompression without Huffman tables
|
||||
*
|
||||
* This test case verifies the functionality of the JPEG decompression library
|
||||
* when decoding an image that lacks Huffman tables, such as a USB frame
|
||||
* from a Logitech C270 USB camera. The image was reconstructed from raw USB data
|
||||
* (using `hex_to_jpg.py`) and then converted into an RGB888 C-style array
|
||||
* (using `jpg_to_rgb888_hex.py`).
|
||||
*
|
||||
* Due to the unique structure of the JPEG data (double block size, 16x8 pixels)
|
||||
* and absence of Huffman tables, this test assesses whether the decompression
|
||||
* library correctly decodes the image and outputs RGB888 pixel data within
|
||||
* an acceptable tolerance range.
|
||||
*
|
||||
* The test performs the following steps:
|
||||
* - Allocates a buffer for the decoded image.
|
||||
* - Configures and runs the JPEG decoder with the RGB888 output format.
|
||||
* - Checks that the decoded image dimensions match expected values.
|
||||
* - Compares the decompressed image data against the reference RGB888 data,
|
||||
* allowing a tolerance of ±16 in each color component due to potential
|
||||
* differences in Huffman tables or decompression accuracy.
|
||||
*
|
||||
* @note This test allows a margin of error in pixel values due to potential
|
||||
* differences in how color data is interpreted across different decoders.
|
||||
*
|
||||
* @param None
|
||||
*
|
||||
* @return None
|
||||
*
|
||||
* @test Requirements:
|
||||
* - JPEG decompression library support for images without Huffman tables.
|
||||
* - JPEG decompression accuracy within acceptable error margins.
|
||||
*/
|
||||
TEST_CASE("Test JPEG decompression library: No Huffman tables", "[esp_jpeg]")
|
||||
{
|
||||
unsigned char *decoded, *p;
|
||||
const unsigned int *o;
|
||||
int decoded_outsize = 160 * 120 * 3;
|
||||
|
||||
decoded = malloc(decoded_outsize);
|
||||
|
||||
/* JPEG decode */
|
||||
esp_jpeg_image_cfg_t jpeg_cfg = {
|
||||
.indata = (uint8_t *)jpeg_no_huffman,
|
||||
.indata_size = jpeg_no_huffman_len,
|
||||
.outbuf = decoded,
|
||||
.outbuf_size = decoded_outsize,
|
||||
.out_format = JPEG_IMAGE_FORMAT_RGB888,
|
||||
.out_scale = JPEG_IMAGE_SCALE_0,
|
||||
.flags = {
|
||||
.swap_color_bytes = 0,
|
||||
}
|
||||
};
|
||||
esp_jpeg_image_output_t outimg;
|
||||
esp_err_t err = esp_jpeg_decode(&jpeg_cfg, &outimg);
|
||||
TEST_ASSERT_EQUAL(err, ESP_OK);
|
||||
|
||||
/* Decoded image size */
|
||||
TEST_ASSERT_EQUAL(outimg.width, 160);
|
||||
TEST_ASSERT_EQUAL(outimg.height, 120);
|
||||
|
||||
p = decoded;
|
||||
o = jpeg_no_huffman_rgb888;
|
||||
for (int x = 0; x < outimg.width * outimg.height; x++) {
|
||||
/* The color can be +- 16 */
|
||||
// Here we allow bigger decoding error
|
||||
// It might be that the Windows decoder used slightly different Huffman tables
|
||||
TEST_ASSERT_UINT8_WITHIN(16, (*o) & 0xff, p[0]);
|
||||
TEST_ASSERT_UINT8_WITHIN(16, (*o >> 8) & 0xff, p[1]);
|
||||
TEST_ASSERT_UINT8_WITHIN(16, (*o >> 16) & 0xff, p[2]);
|
||||
|
||||
p += 3; // this is uint8_t
|
||||
o ++; // this is unt32_t
|
||||
}
|
||||
|
||||
free(decoded);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Invalid JPEG marker test
|
||||
*
|
||||
* This test case verifies the behavior of the JPEG decompression library
|
||||
* when encountering an invalid marker (0xFFFF) in the JPEG data stream.
|
||||
* The test uses a known JPEG image (camera_2_jpg) that contains this invalid
|
||||
* marker. The test checks whether the library can handle the invalid marker
|
||||
* gracefully and still decode the image correctly.
|
||||
*/
|
||||
TEST_CASE("Test JPEG invalid marker 0xFFFF", "[esp_jpeg]")
|
||||
{
|
||||
unsigned char *decoded;
|
||||
int decoded_outsize = 160 * 120 * 3;
|
||||
|
||||
decoded = malloc(decoded_outsize);
|
||||
assert(decoded);
|
||||
for (int x = 0; x < decoded_outsize; x += 2) {
|
||||
decoded[x] = 0;
|
||||
decoded[x + 1] = 0xff;
|
||||
}
|
||||
|
||||
/* JPEG decode */
|
||||
esp_jpeg_image_cfg_t jpeg_cfg = {
|
||||
.indata = (uint8_t *)camera_2_jpg,
|
||||
.indata_size = camera_2_jpg_len,
|
||||
.outbuf = decoded,
|
||||
.outbuf_size = decoded_outsize,
|
||||
.out_format = JPEG_IMAGE_FORMAT_RGB888,
|
||||
.out_scale = JPEG_IMAGE_SCALE_0,
|
||||
.flags = {
|
||||
.swap_color_bytes = 0,
|
||||
}
|
||||
};
|
||||
esp_jpeg_image_output_t outimg;
|
||||
esp_err_t err = esp_jpeg_decode(&jpeg_cfg, &outimg);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
|
||||
/* Decoded image size */
|
||||
TEST_ASSERT_EQUAL(160, outimg.width);
|
||||
TEST_ASSERT_EQUAL(120, outimg.height);
|
||||
|
||||
free(decoded);
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
@ -0,0 +1,6 @@
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.generic
|
||||
def test_esp_jpeg(dut) -> None:
|
||||
dut.run_all_single_board_cases()
|
||||
@ -0,0 +1,6 @@
|
||||
# This file was generated using idf.py save-defconfig. It can be edited manually.
|
||||
# Espressif IoT Development Framework (ESP-IDF) 5.4.0 Project Minimal Configuration
|
||||
#
|
||||
CONFIG_ESP_TASK_WDT_INIT=n
|
||||
CONFIG_JD_USE_ROM=n
|
||||
CONFIG_JD_DEFAULT_HUFFMAN=y
|
||||
@ -0,0 +1,4 @@
|
||||
# This file was generated using idf.py save-defconfig. It can be edited manually.
|
||||
# Espressif IoT Development Framework (ESP-IDF) 5.4.0 Project Minimal Configuration
|
||||
#
|
||||
CONFIG_ESP_TASK_WDT_INIT=n
|
||||
1392
managed_components/espressif__esp_jpeg/tjpgd/tjpgd.c
Normal file
1392
managed_components/espressif__esp_jpeg/tjpgd/tjpgd.c
Normal file
File diff suppressed because it is too large
Load Diff
102
managed_components/espressif__esp_jpeg/tjpgd/tjpgd.h
Normal file
102
managed_components/espressif__esp_jpeg/tjpgd/tjpgd.h
Normal file
@ -0,0 +1,102 @@
|
||||
/*----------------------------------------------------------------------------/
|
||||
/ TJpgDec - Tiny JPEG Decompressor R0.03 include file (C)ChaN, 2021
|
||||
/----------------------------------------------------------------------------*/
|
||||
#ifndef DEF_TJPGDEC
|
||||
#define DEF_TJPGDEC
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "tjpgdcnf.h"
|
||||
#include <string.h>
|
||||
|
||||
#if defined(_WIN32) /* VC++ or some compiler without stdint.h */
|
||||
typedef unsigned char uint8_t;
|
||||
typedef unsigned short uint16_t;
|
||||
typedef short int16_t;
|
||||
typedef unsigned long uint32_t;
|
||||
typedef long int32_t;
|
||||
#else /* Embedded platform */
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
|
||||
#if JD_FASTDECODE >= 1
|
||||
typedef int16_t jd_yuv_t;
|
||||
#else
|
||||
typedef uint8_t jd_yuv_t;
|
||||
#endif
|
||||
|
||||
|
||||
/* Error code */
|
||||
typedef enum {
|
||||
JDR_OK = 0, /* 0: Succeeded */
|
||||
JDR_INTR, /* 1: Interrupted by output function */
|
||||
JDR_INP, /* 2: Device error or wrong termination of input stream */
|
||||
JDR_MEM1, /* 3: Insufficient memory pool for the image */
|
||||
JDR_MEM2, /* 4: Insufficient stream input buffer */
|
||||
JDR_PAR, /* 5: Parameter error */
|
||||
JDR_FMT1, /* 6: Data format error (may be broken data) */
|
||||
JDR_FMT2, /* 7: Right format but not supported */
|
||||
JDR_FMT3 /* 8: Not supported JPEG standard */
|
||||
} JRESULT;
|
||||
|
||||
|
||||
|
||||
/* Rectangular region in the output image */
|
||||
typedef struct {
|
||||
uint16_t left; /* Left end */
|
||||
uint16_t right; /* Right end */
|
||||
uint16_t top; /* Top end */
|
||||
uint16_t bottom; /* Bottom end */
|
||||
} JRECT;
|
||||
|
||||
|
||||
|
||||
/* Decompressor object structure */
|
||||
typedef struct JDEC JDEC;
|
||||
struct JDEC {
|
||||
size_t dctr; /* Number of bytes available in the input buffer */
|
||||
uint8_t *dptr; /* Current data read ptr */
|
||||
uint8_t *inbuf; /* Bit stream input buffer */
|
||||
uint8_t dbit; /* Number of bits availavble in wreg or reading bit mask */
|
||||
uint8_t scale; /* Output scaling ratio */
|
||||
uint8_t msx, msy; /* MCU size in unit of block (width, height) */
|
||||
uint8_t qtid[3]; /* Quantization table ID of each component, Y, Cb, Cr */
|
||||
uint8_t ncomp; /* Number of color components 1:grayscale, 3:color */
|
||||
int16_t dcv[3]; /* Previous DC element of each component */
|
||||
uint16_t nrst; /* Restart inverval */
|
||||
uint16_t width, height; /* Size of the input image (pixel) */
|
||||
uint8_t *huffbits[2][2]; /* Huffman bit distribution tables [id][dcac] */
|
||||
uint16_t *huffcode[2][2]; /* Huffman code word tables [id][dcac] */
|
||||
uint8_t *huffdata[2][2]; /* Huffman decoded data tables [id][dcac] */
|
||||
int32_t *qttbl[4]; /* Dequantizer tables [id] */
|
||||
#if JD_FASTDECODE >= 1
|
||||
uint32_t wreg; /* Working shift register */
|
||||
uint8_t marker; /* Detected marker (0:None) */
|
||||
#if JD_FASTDECODE == 2
|
||||
uint8_t longofs[2][2]; /* Table offset of long code [id][dcac] */
|
||||
uint16_t *hufflut_ac[2]; /* Fast huffman decode tables for AC short code [id] */
|
||||
uint8_t *hufflut_dc[2]; /* Fast huffman decode tables for DC short code [id] */
|
||||
#endif
|
||||
#endif
|
||||
void *workbuf; /* Working buffer for IDCT and RGB output */
|
||||
jd_yuv_t *mcubuf; /* Working buffer for the MCU */
|
||||
void *pool; /* Pointer to available memory pool */
|
||||
size_t sz_pool; /* Size of momory pool (bytes available) */
|
||||
size_t (*infunc)(JDEC *, uint8_t *, size_t); /* Pointer to jpeg stream input function */
|
||||
void *device; /* Pointer to I/O device identifiler for the session */
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* TJpgDec API functions */
|
||||
JRESULT jd_prepare (JDEC *jd, size_t (*infunc)(JDEC *, uint8_t *, size_t), void *pool, size_t sz_pool, void *dev);
|
||||
JRESULT jd_decomp (JDEC *jd, int (*outfunc)(JDEC *, void *, JRECT *), uint8_t scale);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _TJPGDEC */
|
||||
48
managed_components/espressif__esp_jpeg/tjpgd/tjpgdcnf.h
Normal file
48
managed_components/espressif__esp_jpeg/tjpgd/tjpgdcnf.h
Normal file
@ -0,0 +1,48 @@
|
||||
/*----------------------------------------------*/
|
||||
/* TJpgDec System Configurations R0.03 */
|
||||
/*----------------------------------------------*/
|
||||
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#define JD_SZBUF CONFIG_JD_SZBUF
|
||||
/* Specifies size of stream input buffer */
|
||||
|
||||
#define JD_FORMAT CONFIG_JD_FORMAT
|
||||
/* Specifies output pixel format.
|
||||
/ 0: RGB888 (24-bit/pix)
|
||||
/ 1: RGB565 (16-bit/pix)
|
||||
/ 2: Grayscale (8-bit/pix)
|
||||
*/
|
||||
|
||||
#if defined(CONFIG_JD_USE_SCALE)
|
||||
#define JD_USE_SCALE CONFIG_JD_USE_SCALE
|
||||
#else
|
||||
#define JD_USE_SCALE 0
|
||||
#endif
|
||||
/* Switches output descaling feature.
|
||||
/ 0: Disable
|
||||
/ 1: Enable
|
||||
*/
|
||||
|
||||
#if defined(CONFIG_JD_TBLCLIP)
|
||||
#define JD_TBLCLIP CONFIG_JD_TBLCLIP
|
||||
#else
|
||||
#define JD_TBLCLIP 0
|
||||
#endif
|
||||
/* Use table conversion for saturation arithmetic. A bit faster, but increases 1 KB of code size.
|
||||
/ 0: Disable
|
||||
/ 1: Enable
|
||||
*/
|
||||
|
||||
#define JD_FASTDECODE CONFIG_JD_FASTDECODE
|
||||
/* Optimization level
|
||||
/ 0: Basic optimization. Suitable for 8/16-bit MCUs.
|
||||
/ 1: + 32-bit barrel shifter. Suitable for 32-bit MCUs.
|
||||
/ 2: + Table conversion for huffman decoding (wants 6 << HUFF_BIT bytes of RAM)
|
||||
*/
|
||||
|
||||
#if defined(CONFIG_JD_DEFAULT_HUFFMAN)
|
||||
#define JD_DEFAULT_HUFFMAN CONFIG_JD_DEFAULT_HUFFMAN
|
||||
#else
|
||||
#define JD_DEFAULT_HUFFMAN 0
|
||||
#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