Compare commits

...

60 Commits

Author SHA1 Message Date
Terrence
488dfa5bf9 Bump to 1.5.5 2025-03-30 16:14:10 +08:00
Terrence
45012e38d4 Fix upgrade failure (main task stack overflow) 2025-03-30 16:13:49 +08:00
Xiaoxia
fa899a310e ESP-BOX-3 / LichuangDev enable AEC to support realtime chat (#429)
* read frame duration from server

* fit wechat style emoji size

* Make Wechat UI look better

* Add Realtime Chat to ESP-BOX-3/LichuangDev

* disable debug log

* Fix Sh1106 Compile Error Bug. IDF 5.3.2 Not supporting sh1106-esp-idf. (#424)

* Fix ESP32 Board Led No Light Bug (#427)

* add board esp32-s3-touch-lcd-3.5 (#415)

* add board esp32-s3-touch-lcd-3.5

* add axp2101

---------

Co-authored-by: flyingtjy <flyingtjy@gmail.com>

---------

Co-authored-by: ooxxU <71391474@qq.com>
Co-authored-by: flying1425 <79792003+flying1425@users.noreply.github.com>
Co-authored-by: flyingtjy <flyingtjy@gmail.com>
2025-03-30 09:07:08 +08:00
flying1425
e4c76eaa46 add board esp32-s3-touch-lcd-3.5 (#415)
* add board esp32-s3-touch-lcd-3.5

* add axp2101

---------

Co-authored-by: flyingtjy <flyingtjy@gmail.com>
2025-03-29 20:15:29 +08:00
ooxxU
69f736ac64 Fix ESP32 Board Led No Light Bug (#427) 2025-03-29 20:14:40 +08:00
ooxxU
2e9ce37321 Fix Sh1106 Compile Error Bug. IDF 5.3.2 Not supporting sh1106-esp-idf. (#424) 2025-03-29 12:01:09 +08:00
Lucinhu
8a0bb68c33 fix: 修复微信ui在消息数量超过上限后,气泡位置向上偏移的问题 (#418) 2025-03-27 20:02:45 +08:00
Eason
7ac252637d SH1106 Supported (#416)
* SH1106 Supported

* OLED_SSD1306_128X64_SH1106 -> OLED_SH1106_128X64
2025-03-26 23:11:08 +08:00
Terrence
4c7e00630b fix sensecap watcher partition alignment 2025-03-26 23:07:58 +08:00
Lucinhu
f05b46a0d9 typo: 删除sense-watcher显示修复的重复以及改掉错误的注释描述 (#417) 2025-03-26 22:15:12 +08:00
MOV
e486d8e91e Add feature cuican (#407)
* fix:Modify the README and add Moji images

* fix: Moji LCD initialization configuration.

* fix: DISPLAY_MIRROR_X false >> true

* fix: Moji GPIO conflict

* fix: Moji GPIO conflict

* add-feature-cuican

* update cuican image

* update config.h
2025-03-26 02:20:30 +08:00
ZhouKe
1f29fa44c2 change SPI mode for 7pin LCD (#405)
Co-authored-by: FanCheng <598592060@qq.com>
2025-03-26 00:39:48 +08:00
WMnologo
ae8aec1879 新增了0.85tft屏幕版本的星智开发板 (#397)
* 新增了0.85tft屏幕版本的星智开发板

* 对调了0.85寸屏幕版本的星智连接ml307模块的TX、RX引脚
2025-03-25 20:28:32 +08:00
Chinsyo
50c49023a7 Support adjust volume with knob in SenseCAP Watcher (#399)
* fix typo, add missing prefix `CONFIG` to `ESP_TASK_WDT_TIMEOUT_S`

* add KNOB gpio spec to sensecap config

* create new knob component

* implement ajust output volume with knob

* modify function name to UpperCamelCase

* Tidy up comments and logs
2025-03-25 02:37:43 +08:00
Terrence
af879d7806 Bump to 1.5.2 2025-03-22 23:12:04 +08:00
Xiaoxia
e1e5387a78 Add missing ja-JP/upgrade.p3 2025-03-22 06:54:37 +08:00
Terrence
dfd3069ee9 reduce audio processing stack size from 8192 to 4096 2025-03-22 06:45:46 +08:00
Terrence
1619217bd9 Upgrade esp-sr to 2.0.2, improve performance 2025-03-22 06:09:12 +08:00
Terrence
023dd7fb27 Add lower limit to audio 2025-03-22 06:03:22 +08:00
Terrence
3efef0cf20 fixbug: display spi error when setting brightness 2025-03-22 06:00:22 +08:00
Terrence
80e02d7c70 Add PMIC init to waveshare 1.8 2025-03-22 05:10:38 +08:00
Kevincoooool
8e2cf90d86 Update esp32s3_korvo2_v3_board.cc (#382) 2025-03-21 12:54:52 +08:00
Kevincoooool
34ab004c38 开机检查背光是否为0,如果为0则调为10 (#379)
* Update backlight.cc

* Update backlight.cc

---------

Co-authored-by: Xiaoxia <terrence@tenclass.com>
2025-03-21 12:16:26 +08:00
Terrence
11c79bf086 fix release 2025-03-20 14:07:44 +08:00
ZhouKe
e440aa725a -修复 st7796 参数 (#375)
-增加非IPS的ST7796屏
2025-03-20 13:50:28 +08:00
Terrence
26e47427b9 Bump to 1.5.0 2025-03-20 03:13:14 +08:00
Terrence
f76502cb59 Add screen theme switch to atk boards and atoms3 2025-03-20 03:13:04 +08:00
Terrence
6bb1ab7583 Add theme switch to all LCD boards 2025-03-20 03:00:07 +08:00
Terrence
71799ed85c Bump to 1.4.8 2025-03-20 02:03:55 +08:00
Terrence
f553459121 fix atk-dnesp32s3 backlight output invert 2025-03-20 01:26:39 +08:00
Chinsyo
39e9d49288 Add single-led support for SenseCAP Watcher (#373)
* add single-led support for SenseCAP Watcher

* Rename blaklight.cc to backlight.cc
2025-03-20 00:45:09 +08:00
ZhouKe
d84e27aae7 微调微信界面 (#370)
* 微调微信界面

* 调整气泡框左边距
2025-03-20 00:42:52 +08:00
mtdxc
4884122cc1 cherry-pick #357, fix: 解决esp32-box-lite麦克风收音太小的问题 (#372)
Co-authored-by: cqm <cqm@97kid.com>
2025-03-20 00:41:13 +08:00
Xiaoxia
d0ae468fac add ja-JP sounds 2025-03-18 21:21:30 +08:00
ZhouKe
61cc1a236b add iot command for theme switch (#364) 2025-03-18 21:00:54 +08:00
SunnyBoy-y
13fd170a89 Update atk_dnesp32s3_box.cc (#362)
atk_dnesp32s3_box适配微信界面
2025-03-18 20:55:36 +08:00
SunnyBoy-y
b2bcb96942 修复部分开发版在config中DISPLAY_BACKLIGHT_OUTPUT_INVERT调整失效问题并适配类微信界面 (#361)
* Update atk_dnesp32s3.cc

修复在config中DISPLAY_BACKLIGHT_OUTPUT_INVERT调整失效问题

* Update atk_dnesp32s3_box.cc

修复在config中调整DISPLAY_BACKLIGHT_OUTPUT_INVERT失效的问题

* Update df_k10_board.cc

修复config中DISPLAY_BACKLIGHT_OUTPUT_INVERT调整失效问题

* Update esp_box_board.cc

修复更改config中DISPLAY_BACKLIGHT_OUTPUT_INVERT失效问题

* Update esp_box3_board.cc

修复在config中DISPLAY_BACKLIGHT_OUTPUT_INVERT调整失效问题

* Update atk_dnesp32s3.cc

dnesp32s3适配微信界面
2025-03-18 20:55:03 +08:00
ZhouKe
2c8f2f7d42 add dark mode (#363) 2025-03-18 10:33:22 +08:00
Paul Xu
349267ef23 fix: 解决行空板df-k10麦克风收音太小的问题 (#357)
Fixes 78/xiaozhi-esp32#350
2025-03-18 00:51:23 +08:00
ZhouKe
423ddcb287 在LCD面板使用微信聊天样式。 (#356)
* 在LCD面板使用微信聊天样式。

* 调整换行
2025-03-18 00:47:35 +08:00
Kevincoooool
0e28cd8a54 降低pclk频率/增大背光PWM频率防止电感啸叫 (#355) 2025-03-17 09:45:03 +08:00
Lucinhu
c165d6f7e1 fix: 解决sensecap-watcher显示异常 (#348) 2025-03-14 23:40:13 +08:00
ooxxU
a7ed9fd0cf fix ESP32 IOT Pin Bug, Pin35 are used for input and cannot be used for output. changed to Pin12. (#349) 2025-03-14 23:31:54 +08:00
bruceOrange22
4a7ef0b995 feat:解决第一次编译,报错,找不到release路径的问题 (#342)
* 解决第一次编译,报错,找不到release路径的问题

* Update release.py

---------

Co-authored-by: guoxianwei <xianwei.guo@orioniot.cn>
Co-authored-by: Xiaoxia <terrence@tenclass.com>
2025-03-14 11:12:59 +08:00
Xiaoxia
a1d150a0b4 fix play_p3 time 2025-03-14 03:06:53 +08:00
dujianmin
e777287463 重新整理了代码规范。去掉了一部分多余代码。 (#340)
* 添加嘟嘟开发板CHATX电池检测功能

* 从新整理了代码的书写规范。去掉一部分多余的代码

* Update du-chatx-wifi.cc

---------

Co-authored-by: Xiaoxia <terrence@tenclass.com>
2025-03-14 03:00:29 +08:00
ooxxU
e29fde6fa3 fix esp32 lcd show bug: Pin conflict (#339) 2025-03-14 02:53:52 +08:00
香草味的纳西妲
d235ac4f52 add: 音频/P3批量转换工具 (#336)
Co-authored-by: NyaOH-Nahida <3051979160@qq.com>
2025-03-13 14:21:04 +08:00
香草味的纳西妲
0ef913635d fix: bug修复 (#335) 2025-03-13 13:11:45 +08:00
laride
ff7f396f9d feat: Audio loudness normalization for assets files (#332) 2025-03-13 12:14:17 +08:00
香草味的纳西妲
7eb710d404 add: GUI版P3文件简易播放器 (#334) 2025-03-13 08:11:58 +08:00
Xiaoxia
5a2e9ac87f add play_p3 and README 2025-03-12 23:28:20 +08:00
laride
45fa2ca389 feat: add AtomS3R CAM/M12 + Echo Base (#330)
* feat: add AtomS3R CAM/M12 + Echo Base

* Update README.md

fix typing

---------

Co-authored-by: Xiaoxia <terrence@tenclass.com>
2025-03-12 21:56:47 +08:00
ooxxU
d60446bc53 新增 ESP32 系列开发板 LCD 屏幕显示的支持 (#313)
1. 支持 ESP32 系列开发板: DevKitC / NodeMcu-32S / GoouuuESP32 / ESP32 DoIt / ESP-32S
2. 注意: 非ESP32-C3 / 非ESP32-S3

Co-authored-by: Xiaoxia <terrence@tenclass.com>
2025-03-12 02:30:45 +08:00
wdmomoxx
e23f6cf6d8 增加设备ESP32-CGC (#201)
* Update Kconfig.projbuild

增加设备ESP32-CGC

* Update CMakeLists.txt

新增设备EPS32-CGC

* Update idf_component.yml

添加ST7735显示驱动

* Update lamp.cc

与ESP32的屏幕GPIO引脚冲突修改

* Add files via upload

新增一个引脚控制风扇

* Update wifi_board.cc

编译提示错误没有这个函数,暂时注释掉
//wifi_ap.SetLanguage(Lang::CODE);

* Create README.md

创建新设备ESP32-CGC

* Add files via upload

创建新设备ESP32-CGC

* Add files via upload

ESP32-CGC设备引脚图

* Update esp32_cgc_board.cc

* Update idf_component.yml

* Delete main/iot/things/newfan.cc

* Update lamp.cc

* Update wifi_board.cc

* Update idf_component.yml

* Delete main/boards/esp32-cgc/esp32_cgc_board.cc

* Delete main/boards/esp32-cgc/config.h

* Delete main/boards/esp32-cgc/README.md

* Add files via upload

添加设备ESP32-CGC

* Update Kconfig.projbuild

添加ESP32-CGC多屏选择支持,增加一个ST7735-128x128分辨率

* Update config.json

* Delete docs/ESP32-CGC-PINMAP.png

* Update README.md

* Update README.md

* Update esp32_cgc_board.cc

* Update config.h

* Update esp32_cgc_board.cc

跟随主线修改

* Create README.md

加入了ESP32 CGC 144开发板

* Add files via upload

加入ESP32 CGC 144开发板

* Update esp32_cgc_144_board.cc

* Update config.h

* Update Kconfig.projbuild

增加ESP32 CGC 144

* Update CMakeLists.txt

增加ESP32 CGC 144

* Update config.json

* Update config.h

* Update esp32_cgc_144_board.cc

修改屏幕控制参数

* Add files via upload

* Update Kconfig.projbuild

删除重复定义

* Update esp32_cgc_144_board.cc

修改音量控制,增加语音配网指令

* Add files via upload

* Delete main/boards/esp32-cgc-144/esp32_cgc_144_lcd_display.h

* Delete main/boards/esp32-cgc-144/esp32_cgc_144_lcd_display.cc

* Update board_control.cc

修改背光控制逻辑

* Update esp32_cgc_144_board.cc

跟随主代码更新

* Update config.h

删除不使用的引脚定义

* Update esp32_cgc_board.cc

跟随主代码更新,去除多余引脚

* Update esp32_cgc_board.cc

* Update board_control.cc

* Update config.h

* Update esp32_cgc_144_board.cc

* Update Kconfig.projbuild

* Update CMakeLists.txt

* Update CMakeLists.txt

先移除144开发板

* Update Kconfig.projbuild

先移除144开发板

* Delete main/boards/esp32-cgc-144 directory

先移除144开发板

* Update esp32_cgc_board.cc

---------

Co-authored-by: Xiaoxia <terrence@tenclass.com>
2025-03-12 02:15:53 +08:00
Xiaoxia
2fdcda30ea fix esp-box-lite compiling error 2025-03-11 03:58:33 +08:00
hbs2001
4b46e4890d 添加了ESP32S3_KORVO2_V3官方lcd屏幕的支持 (#323)
* ESP32S3_KORVO2_V3添加官方开发板屏幕支持

* 修正笔误

* Update esp32s3_korvo2_v3_board.cc

---------

Co-authored-by: hbs2001 <wangyuchong@yuchong.wang>
Co-authored-by: Xiaoxia <terrence@tenclass.com>
2025-03-11 01:43:16 +08:00
virgil
236e21ffc2 Fix sensecap-watcher board bug (#322)
* fix: fixed the issue that the device enters re-network configuration mode by mistake when long pressing to start the device.

* fix: Do not shut down when connected to type-c.

* fix: fixed the issue that firmware verification failed during OTA for 32M flash.

* doc: update readme.

* fix: Solve the problem that the partition table configuration is not effective.
2025-03-11 01:38:42 +08:00
Terrence
9bc8be2f25 add IoT led control to kevin c3 2025-03-10 20:50:02 +08:00
Xiaoxia
dd1139b169 Add discharging status 2025-03-09 19:18:10 +08:00
152 changed files with 5294 additions and 603 deletions

View File

@@ -4,7 +4,7 @@
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
set(PROJECT_VER "1.4.6")
set(PROJECT_VER "1.5.5")
# Add this line to disable the specific warning
add_compile_options(-Wno-missing-field-initializers)

View File

@@ -60,6 +60,7 @@
- <a href="https://github.com/Xinyuan-LilyGO/T-Circle-S3" target="_blank" title="LILYGO T-Circle-S3">LILYGO T-Circle-S3</a>
- <a href="https://oshwhub.com/tenclass01/xmini_c3" target="_blank" title="虾哥 Mini C3">虾哥 Mini C3</a>
- <a href="https://oshwhub.com/movecall/moji-xiaozhi-ai-derivative-editi" target="_blank" title="Movecall Moji ESP32S3">Moji 小智AI衍生版</a>
- <a href="https://oshwhub.com/movecall/cuican-ai-pendant-lights-up-y" target="_blank" title="Movecall CuiCan ESP32S3">璀璨·AI吊坠</a>
- <a href="https://github.com/WMnologo/xingzhi-ai" target="_blank" title="无名科技Nologo-星智-1.54">无名科技Nologo-星智-1.54TFT</a>
- <a href="https://github.com/WMnologo/xingzhi-ai" target="_blank" title="无名科技Nologo-星智-0.96">无名科技Nologo-星智-0.96TFT</a>
- <a href="https://www.seeedstudio.com/SenseCAP-Watcher-W1-A-p-5979.html" target="_blank" title="SenseCAP Watcher">SenseCAP Watcher</a>
@@ -91,6 +92,9 @@
<a href="docs/v1/movecall-moji-esp32s3.jpg" target="_blank" title="Movecall Moji 小智AI衍生版">
<img src="docs/v1/movecall-moji-esp32s3.jpg" width="240" />
</a>
<a href="docs/v1/movecall-cuican-esp32s3.jpg" target="_blank" title="CuiCan">
<img src="docs/v1/movecall-cuican-esp32s3.jpg" width="240" />
</a>
<a href="docs/v1/wmnologo_xingzhi_1.54.jpg" target="_blank" title="无名科技Nologo-星智-1.54">
<img src="docs/v1/wmnologo_xingzhi_1.54.jpg" width="240" />
</a>

View File

@@ -60,6 +60,8 @@ Breadboard demonstration:
- <a href="https://github.com/Xinyuan-LilyGO/T-Circle-S3" target="_blank" title="LILYGO T-Circle-S3">LILYGO T-Circle-S3</a>
- <a href="https://oshwhub.com/tenclass01/xmini_c3" target="_blank" title="XiaGe Mini C3">XiaGe Mini C3</a>
- <a href="https://oshwhub.com/movecall/moji-xiaozhi-ai-derivative-editi" target="_blank" title="Movecall Moji ESP32S3">Moji XiaoZhi AI Derivative Version</a>
- <a href="https://oshwhub.com/movecall/cuican-ai-pendant-lights-up-y" target="_blank" title="Movecall CuiCan ESP32S3">CuiCan AI pendant</a>
- <a href="https://www.seeedstudio.com/SenseCAP-Watcher-W1-A-p-5979.html" target="_blank" title="SenseCAP Watcher">SenseCAP Watcher</a>
<div style="display: flex; justify-content: space-between;">
<a href="docs/v1/lichuang-s3.jpg" target="_blank" title="LiChuang ESP32-S3 Development Board">
@@ -92,6 +94,12 @@ Breadboard demonstration:
<a href="docs/v1/movecall-moji-esp32s3.jpg" target="_blank" title="Moji">
<img src="docs/v1/movecall-moji-esp32s3.jpg" width="240" />
</a>
<a href="docs/v1/movecall-cuican-esp32s3.jpg" target="_blank" title="CuiCan">
<img src="docs/v1/movecall-cuican-esp32s3.jpg" width="240" />
</a>
<a href="docs/v1/sensecap_watcher.jpg" target="_blank" title="SenseCAP Watcher">
<img src="docs/v1/sensecap_watcher.jpg" width="240" />
</a>
</div>
## Firmware Section

View File

@@ -60,6 +60,8 @@ Feishu ドキュメントチュートリアルをご覧ください:
- <a href="https://github.com/Xinyuan-LilyGO/T-Circle-S3" target="_blank" title="LILYGO T-Circle-S3">LILYGO T-Circle-S3</a>
- <a href="https://oshwhub.com/tenclass01/xmini_c3" target="_blank" title="XiaGe Mini C3">XiaGe Mini C3</a>
- <a href="https://oshwhub.com/movecall/moji-xiaozhi-ai-derivative-editi" target="_blank" title="Movecall Moji ESP32S3">Moji シャオジー AI 派生版</a>
- <a href="https://oshwhub.com/movecall/cuican-ai-pendant-lights-up-y" target="_blank" title="Movecall CuiCan ESP32S3">Cuican AI ペンダント</a>
- <a href="https://www.seeedstudio.com/SenseCAP-Watcher-W1-A-p-5979.html" target="_blank" title="SenseCAP Watcher">SenseCAP Watcher</a>
<div style="display: flex; justify-content: space-between;">
<a href="docs/v1/lichuang-s3.jpg" target="_blank" title="LiChuang ESP32-S3 開発ボード">
@@ -89,6 +91,12 @@ Feishu ドキュメントチュートリアルをご覧ください:
<a href="docs/v1/movecall-moji-esp32s3.jpg" target="_blank" title="Moji">
<img src="docs/v1/movecall-moji-esp32s3.jpg" width="240" />
</a>
<a href="docs/v1/movecall-cuican-esp32s3.jpg" target="_blank" title="CuiCan">
<img src="docs/v1/movecall-cuican-esp32s3.jpg" width="240" />
</a>
<a href="docs/v1/sensecap_watcher.jpg" target="_blank" title="SenseCAP Watcher">
<img src="docs/v1/sensecap_watcher.jpg" width="240" />
</a>
</div>
## ファームウェア部分

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View File

@@ -38,6 +38,8 @@ elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_ML307)
set(BOARD_TYPE "bread-compact-ml307")
elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_ESP32)
set(BOARD_TYPE "bread-compact-esp32")
elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_ESP32_LCD)
set(BOARD_TYPE "bread-compact-esp32-lcd")
elseif(CONFIG_BOARD_TYPE_DF_K10)
set(BOARD_TYPE "df-k10")
elseif(CONFIG_BOARD_TYPE_ESP_BOX_3)
@@ -76,6 +78,8 @@ elseif(CONFIG_BOARD_TYPE_ATOMS3_ECHO_BASE)
set(BOARD_TYPE "atoms3-echo-base")
elseif(CONFIG_BOARD_TYPE_ATOMS3R_ECHO_BASE)
set(BOARD_TYPE "atoms3r-echo-base")
elseif(CONFIG_BOARD_TYPE_ATOMS3R_CAM_M12_ECHO_BASE)
set(BOARD_TYPE "atoms3r-cam-m12-echo-base")
elseif(CONFIG_BOARD_TYPE_ATOMMATRIX_ECHO_BASE)
set(BOARD_TYPE "atommatrix-echo-base")
elseif(CONFIG_BOARD_TYPE_XMINI_C3)
@@ -92,6 +96,8 @@ elseif(CONFIG_BOARD_TYPE_ESP32S3_Touch_LCD_1_85)
set(BOARD_TYPE "esp32-s3-touch-lcd-1.85")
elseif(CONFIG_BOARD_TYPE_ESP32S3_Touch_LCD_1_46)
set(BOARD_TYPE "esp32-s3-touch-lcd-1.46")
elseif(CONFIG_BOARD_TYPE_ESP32S3_Touch_LCD_3_5)
set(BOARD_TYPE "esp32-s3-touch-lcd-3.5")
elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_WIFI_LCD)
set(BOARD_TYPE "bread-compact-wifi-lcd")
elseif(CONFIG_BOARD_TYPE_TUDOUZI)
@@ -102,6 +108,8 @@ elseif(CONFIG_BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3)
set(BOARD_TYPE "lilygo-t-cameraplus-s3")
elseif(CONFIG_BOARD_TYPE_MOVECALL_MOJI_ESP32S3)
set(BOARD_TYPE "movecall-moji-esp32s3")
elseif(CONFIG_BOARD_TYPE_MOVECALL_CUICAN_ESP32S3)
set(BOARD_TYPE "movecall-cuican-esp32s3")
elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3)
set(BOARD_TYPE "atk-dnesp32s3")
elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3_BOX)
@@ -110,6 +118,10 @@ elseif(CONFIG_BOARD_TYPE_DU_CHATX)
set(BOARD_TYPE "du-chatx")
elseif(CONFIG_BOARD_TYPE_ESP32S3_Taiji_Pi)
set(BOARD_TYPE "taiji-pi-s3")
elseif(CONFIG_BOARD_TYPE_XINGZHI_Cube_0_85TFT_WIFI)
set(BOARD_TYPE "xingzhi-cube-0.85tft-wifi")
elseif(CONFIG_BOARD_TYPE_XINGZHI_Cube_0_85TFT_ML307)
set(BOARD_TYPE "xingzhi-cube-0.85tft-ml307")
elseif(CONFIG_BOARD_TYPE_XINGZHI_Cube_0_96OLED_WIFI)
set(BOARD_TYPE "xingzhi-cube-0.96oled-wifi")
elseif(CONFIG_BOARD_TYPE_XINGZHI_Cube_0_96OLED_ML307)
@@ -120,6 +132,8 @@ elseif(CONFIG_BOARD_TYPE_XINGZHI_Cube_1_54TFT_ML307)
set(BOARD_TYPE "xingzhi-cube-1.54tft-ml307")
elseif(CONFIG_BOARD_TYPE_SENSECAP_WATCHER)
set(BOARD_TYPE "sensecap-watcher")
elseif(CONFIG_BOARD_TYPE_ESP32_CGC)
set(BOARD_TYPE "esp32-cgc")
endif()
file(GLOB BOARD_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_TYPE}/*.cc

View File

@@ -61,7 +61,11 @@ choice BOARD_TYPE
config BOARD_TYPE_BREAD_COMPACT_ML307
bool "面包板新版接线ML307 AT"
config BOARD_TYPE_BREAD_COMPACT_ESP32
bool "面包板 ESP32 DevKit"
bool "面包板WiFi ESP32 DevKit"
config BOARD_TYPE_BREAD_COMPACT_ESP32_LCD
bool "面包板WiFi+ LCD ESP32 DevKit"
config BOARD_TYPE_ESP32_CGC
bool "ESP32 CGC"
config BOARD_TYPE_ESP_BOX_3
bool "ESP BOX 3"
config BOARD_TYPE_ESP_BOX
@@ -100,6 +104,8 @@ choice BOARD_TYPE
bool "AtomS3 + Echo Base"
config BOARD_TYPE_ATOMS3R_ECHO_BASE
bool "AtomS3R + Echo Base"
config BOARD_TYPE_ATOMS3R_CAM_M12_ECHO_BASE
bool "AtomS3R CAM/M12 + Echo Base"
config BOARD_TYPE_ATOMMATRIX_ECHO_BASE
bool "AtomMatrix + Echo Base"
config BOARD_TYPE_XMINI_C3
@@ -116,6 +122,8 @@ choice BOARD_TYPE
bool "Waveshare ESP32-S3-Touch-LCD-1.85"
config BOARD_TYPE_ESP32S3_Touch_LCD_1_46
bool "Waveshare ESP32-S3-Touch-LCD-1.46"
config BOARD_TYPE_ESP32S3_Touch_LCD_3_5
bool "Waveshare ESP32-S3-Touch-LCD-3.5"
config BOARD_TYPE_TUDOUZI
bool "土豆子"
config BOARD_TYPE_LILYGO_T_CIRCLE_S3
@@ -124,6 +132,8 @@ choice BOARD_TYPE
bool "LILYGO T-CameraPlus-S3"
config BOARD_TYPE_MOVECALL_MOJI_ESP32S3
bool "Movecall Moji 小智AI衍生版"
config BOARD_TYPE_MOVECALL_CUICAN_ESP32S3
bool "Movecall CuiCan 璀璨·AI吊坠"
config BOARD_TYPE_ATK_DNESP32S3
bool "正点原子DNESP32S3开发板"
config BOARD_TYPE_ATK_DNESP32S3_BOX
@@ -132,6 +142,10 @@ choice BOARD_TYPE
bool "嘟嘟开发板CHATX(wifi)"
config BOARD_TYPE_ESP32S3_Taiji_Pi
bool "太极小派esp32s3"
config BOARD_TYPE_XINGZHI_Cube_0_85TFT_WIFI
bool "无名科技星智0.85(WIFI)"
config BOARD_TYPE_XINGZHI_Cube_0_85TFT_ML307
bool "无名科技星智0.85(ML307)"
config BOARD_TYPE_XINGZHI_Cube_0_96OLED_WIFI
bool "无名科技星智0.96(WIFI)"
config BOARD_TYPE_XINGZHI_Cube_0_96OLED_ML307
@@ -154,10 +168,12 @@ choice DISPLAY_OLED_TYPE
bool "SSD1306, 分辨率128*32"
config OLED_SSD1306_128X64
bool "SSD1306, 分辨率128*64"
config OLED_SH1106_128X64
bool "SH1106, 分辨率128*64"
endchoice
choice DISPLAY_LCD_TYPE
depends on BOARD_TYPE_BREAD_COMPACT_WIFI_LCD
depends on BOARD_TYPE_BREAD_COMPACT_WIFI_LCD || BOARD_TYPE_BREAD_COMPACT_ESP32_LCD || BOARD_TYPE_ESP32_CGC
prompt "LCD Type"
default LCD_ST7789_240X320
help
@@ -183,7 +199,9 @@ choice DISPLAY_LCD_TYPE
config LCD_ST7735_128X128
bool "ST7735, 分辨率128*128"
config LCD_ST7796_320X480
bool "ST7796, 分辨率320*480"
bool "ST7796, 分辨率320*480 IPS"
config LCD_ST7796_320X480_NO_IPS
bool "ST7796, 分辨率320*480, 非IPS"
config LCD_ILI9341_240X320
bool "ILI9341, 分辨率240*320"
config LCD_ILI9341_240X320_NO_IPS
@@ -194,17 +212,43 @@ choice DISPLAY_LCD_TYPE
bool "自定义屏幕参数"
endchoice
config USE_AUDIO_PROCESSOR
bool "启用音频降噪、增益处理"
default y
depends on IDF_TARGET_ESP32S3 && USE_AFE
choice DISPLAY_ESP32S3_KORVO2_V3
depends on BOARD_TYPE_ESP32S3_KORVO2_V3
prompt "ESP32S3_KORVO2_V3 LCD Type"
default LCD_ST7789
help
需要 ESP32 S3 与 AFE 支持
屏幕类型选择
config LCD_ST7789
bool "ST7789, 分辨率240*280"
config LCD_ILI9341
bool "ILI9341, 分辨率240*320"
endchoice
config USE_WECHAT_MESSAGE_STYLE
bool "使用微信聊天界面风格"
default n
help
使用微信聊天界面风格
config USE_WAKE_WORD_DETECT
bool "启用唤醒词检测"
default y
depends on IDF_TARGET_ESP32S3 && USE_AFE
depends on IDF_TARGET_ESP32S3 && SPIRAM
help
需要 ESP32 S3 与 AFE 支持
config USE_AUDIO_PROCESSOR
bool "启用音频降噪、增益处理"
default y
depends on IDF_TARGET_ESP32S3 && SPIRAM
help
需要 ESP32 S3 与 AFE 支持
config USE_REALTIME_CHAT
bool "启用可语音打断的实时对话模式(需要 AEC 支持)"
default n
depends on USE_AUDIO_PROCESSOR && (BOARD_TYPE_ESP_BOX_3 || BOARD_TYPE_ESP_BOX || BOARD_TYPE_LICHUANG_DEV)
help
需要 ESP32 S3 与 AEC 开启,因为性能不够,不建议和微信聊天界面风格同时开启
endmenu

View File

@@ -203,6 +203,7 @@ void Application::Alert(const char* status, const char* message, const char* emo
display->SetEmotion(emotion);
display->SetChatMessage("system", message);
if (!sound.empty()) {
ResetDecoder();
PlaySound(sound);
}
}
@@ -217,9 +218,8 @@ void Application::DismissAlert() {
}
void Application::PlaySound(const std::string_view& sound) {
auto codec = Board::GetInstance().GetAudioCodec();
codec->EnableOutput(true);
SetDecodeSampleRate(16000);
// The assets are encoded at 16000Hz, 60ms frame duration
SetDecodeSampleRate(16000, 60);
const char* data = sound.data();
size_t size = sound.size();
for (const char* p = data; p < data + size; ) {
@@ -255,9 +255,7 @@ void Application::ToggleChatState() {
return;
}
keep_listening_ = true;
protocol_->SendStartListening(kListeningModeAutoStop);
SetDeviceState(kDeviceStateListening);
SetListeningMode(realtime_chat_enabled_ ? kListeningModeRealtime : kListeningModeAutoStop);
});
} else if (device_state_ == kDeviceStateSpeaking) {
Schedule([this]() {
@@ -281,7 +279,6 @@ void Application::StartListening() {
return;
}
keep_listening_ = false;
if (device_state_ == kDeviceStateIdle) {
Schedule([this]() {
if (!protocol_->IsAudioChannelOpened()) {
@@ -290,14 +287,13 @@ void Application::StartListening() {
return;
}
}
protocol_->SendStartListening(kListeningModeManualStop);
SetDeviceState(kDeviceStateListening);
SetListeningMode(kListeningModeManualStop);
});
} else if (device_state_ == kDeviceStateSpeaking) {
Schedule([this]() {
AbortSpeaking(kAbortReasonNone);
protocol_->SendStartListening(kListeningModeManualStop);
SetDeviceState(kDeviceStateListening);
SetListeningMode(kListeningModeManualStop);
});
}
}
@@ -320,12 +316,12 @@ void Application::Start() {
/* Setup the audio codec */
auto codec = board.GetAudioCodec();
opus_decode_sample_rate_ = codec->output_sample_rate();
opus_decoder_ = std::make_unique<OpusDecoderWrapper>(opus_decode_sample_rate_, 1);
opus_decoder_ = std::make_unique<OpusDecoderWrapper>(codec->output_sample_rate(), 1, OPUS_FRAME_DURATION_MS);
opus_encoder_ = std::make_unique<OpusEncoderWrapper>(16000, 1, OPUS_FRAME_DURATION_MS);
// For ML307 boards, we use complexity 5 to save bandwidth
// For other boards, we use complexity 3 to save CPU
if (board.GetBoardType() == "ml307") {
if (realtime_chat_enabled_) {
ESP_LOGI(TAG, "Realtime chat enabled, setting opus encoder complexity to 0");
opus_encoder_->SetComplexity(0);
} else if (board.GetBoardType() == "ml307") {
ESP_LOGI(TAG, "ML307 board detected, setting opus encoder complexity to 5");
opus_encoder_->SetComplexity(5);
} else {
@@ -337,24 +333,20 @@ void Application::Start() {
input_resampler_.Configure(codec->input_sample_rate(), 16000);
reference_resampler_.Configure(codec->input_sample_rate(), 16000);
}
codec->OnInputReady([this, codec]() {
BaseType_t higher_priority_task_woken = pdFALSE;
xEventGroupSetBitsFromISR(event_group_, AUDIO_INPUT_READY_EVENT, &higher_priority_task_woken);
return higher_priority_task_woken == pdTRUE;
});
codec->OnOutputReady([this]() {
BaseType_t higher_priority_task_woken = pdFALSE;
xEventGroupSetBitsFromISR(event_group_, AUDIO_OUTPUT_READY_EVENT, &higher_priority_task_woken);
return higher_priority_task_woken == pdTRUE;
});
codec->Start();
xTaskCreatePinnedToCore([](void* arg) {
Application* app = (Application*)arg;
app->AudioLoop();
vTaskDelete(NULL);
}, "audio_loop", 4096 * 2, this, 8, &audio_loop_task_handle_, realtime_chat_enabled_ ? 1 : 0);
/* Start the main loop */
xTaskCreate([](void* arg) {
xTaskCreatePinnedToCore([](void* arg) {
Application* app = (Application*)arg;
app->MainLoop();
vTaskDelete(NULL);
}, "main_loop", 4096 * 2, this, 3, nullptr);
}, "main_loop", 4096 * 2, this, 4, &main_loop_task_handle_, 0);
/* Wait for the network to be ready */
board.StartNetwork();
@@ -372,9 +364,7 @@ void Application::Start() {
});
protocol_->OnIncomingAudio([this](std::vector<uint8_t>&& data) {
std::lock_guard<std::mutex> lock(mutex_);
if (device_state_ == kDeviceStateSpeaking) {
audio_decode_queue_.emplace_back(std::move(data));
}
audio_decode_queue_.emplace_back(std::move(data));
});
protocol_->OnAudioChannelOpened([this, codec, &board]() {
board.SetPowerSaveMode(false);
@@ -382,7 +372,7 @@ void Application::Start() {
ESP_LOGW(TAG, "Server sample rate %d does not match device output sample rate %d, resampling may cause distortion",
protocol_->server_sample_rate(), codec->output_sample_rate());
}
SetDecodeSampleRate(protocol_->server_sample_rate());
SetDecodeSampleRate(protocol_->server_sample_rate(), protocol_->server_frame_duration());
auto& thing_manager = iot::ThingManager::GetInstance();
protocol_->SendIotDescriptors(thing_manager.GetDescriptorsJson());
std::string states;
@@ -412,13 +402,12 @@ void Application::Start() {
});
} else if (strcmp(state->valuestring, "stop") == 0) {
Schedule([this]() {
background_task_->WaitForCompletion();
if (device_state_ == kDeviceStateSpeaking) {
background_task_->WaitForCompletion();
if (keep_listening_) {
protocol_->SendStartListening(kListeningModeAutoStop);
SetDeviceState(kDeviceStateListening);
} else {
if (listening_mode_ == kListeningModeManualStop) {
SetDeviceState(kDeviceStateIdle);
} else {
SetDeviceState(kDeviceStateListening);
}
}
});
@@ -474,7 +463,7 @@ void Application::Start() {
}, "check_new_version", 4096 * 2, this, 2, nullptr);
#if CONFIG_USE_AUDIO_PROCESSOR
audio_processor_.Initialize(codec->input_channels(), codec->input_reference());
audio_processor_.Initialize(codec, realtime_chat_enabled_);
audio_processor_.OnOutput([this](std::vector<int16_t>&& data) {
background_task_->Schedule([this, data = std::move(data)]() mutable {
opus_encoder_->Encode(std::move(data), [this](std::vector<uint8_t>&& opus) {
@@ -484,13 +473,9 @@ void Application::Start() {
});
});
});
#endif
#if CONFIG_USE_WAKE_WORD_DETECT
wake_word_detect_.Initialize(codec->input_channels(), codec->input_reference());
wake_word_detect_.OnVadStateChange([this](bool speaking) {
Schedule([this, speaking]() {
if (device_state_ == kDeviceStateListening) {
audio_processor_.OnVadStateChange([this](bool speaking) {
if (device_state_ == kDeviceStateListening) {
Schedule([this, speaking]() {
if (speaking) {
voice_detected_ = true;
} else {
@@ -498,10 +483,13 @@ void Application::Start() {
}
auto led = Board::GetInstance().GetLed();
led->OnStateChanged();
}
});
});
}
});
#endif
#if CONFIG_USE_WAKE_WORD_DETECT
wake_word_detect_.Initialize(codec);
wake_word_detect_.OnWakeWordDetected([this](const std::string& wake_word) {
Schedule([this, &wake_word]() {
if (device_state_ == kDeviceStateIdle) {
@@ -521,16 +509,12 @@ void Application::Start() {
// Set the chat state to wake word detected
protocol_->SendWakeWordDetected(wake_word);
ESP_LOGI(TAG, "Wake word detected: %s", wake_word.c_str());
keep_listening_ = true;
SetDeviceState(kDeviceStateIdle);
SetListeningMode(realtime_chat_enabled_ ? kListeningModeRealtime : kListeningModeAutoStop);
} else if (device_state_ == kDeviceStateSpeaking) {
AbortSpeaking(kAbortReasonWakeWordDetected);
} else if (device_state_ == kDeviceStateActivating) {
SetDeviceState(kDeviceStateIdle);
}
// Resume detection
wake_word_detect_.StartDetection();
});
});
wake_word_detect_.StartDetection();
@@ -538,6 +522,13 @@ void Application::Start() {
SetDeviceState(kDeviceStateIdle);
esp_timer_start_periodic(clock_timer_handle_, 1000000);
#if 0
while (true) {
SystemInfo::PrintRealTimeStats(pdMS_TO_TICKS(1000));
vTaskDelay(pdMS_TO_TICKS(10000));
}
#endif
}
void Application::OnClockTimer() {
@@ -545,7 +536,6 @@ void Application::OnClockTimer() {
// Print the debug info every 10 seconds
if (clock_ticks_ % 10 == 0) {
// SystemInfo::PrintRealTimeStats(pdMS_TO_TICKS(1000));
int free_sram = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
int min_free_sram = heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL);
ESP_LOGI(TAG, "Free internal: %u minimal internal: %u", free_sram, min_free_sram);
@@ -565,6 +555,7 @@ void Application::OnClockTimer() {
}
}
// Add a async task to MainLoop
void Application::Schedule(std::function<void()> callback) {
{
std::lock_guard<std::mutex> lock(mutex_);
@@ -578,16 +569,8 @@ void Application::Schedule(std::function<void()> callback) {
// they should use Schedule to call this function
void Application::MainLoop() {
while (true) {
auto bits = xEventGroupWaitBits(event_group_,
SCHEDULE_EVENT | AUDIO_INPUT_READY_EVENT | AUDIO_OUTPUT_READY_EVENT,
pdTRUE, pdFALSE, portMAX_DELAY);
auto bits = xEventGroupWaitBits(event_group_, SCHEDULE_EVENT, pdTRUE, pdFALSE, portMAX_DELAY);
if (bits & AUDIO_INPUT_READY_EVENT) {
InputAudio();
}
if (bits & AUDIO_OUTPUT_READY_EVENT) {
OutputAudio();
}
if (bits & SCHEDULE_EVENT) {
std::unique_lock<std::mutex> lock(mutex_);
std::list<std::function<void()>> tasks = std::move(main_tasks_);
@@ -599,14 +582,18 @@ void Application::MainLoop() {
}
}
void Application::ResetDecoder() {
std::lock_guard<std::mutex> lock(mutex_);
opus_decoder_->ResetState();
audio_decode_queue_.clear();
last_output_time_ = std::chrono::steady_clock::now();
// The Audio Loop is used to input and output audio data
void Application::AudioLoop() {
auto codec = Board::GetInstance().GetAudioCodec();
while (true) {
OnAudioInput();
if (codec->output_enabled()) {
OnAudioOutput();
}
}
}
void Application::OutputAudio() {
void Application::OnAudioOutput() {
auto now = std::chrono::steady_clock::now();
auto codec = Board::GetInstance().GetAudioCodec();
const int max_silence_seconds = 10;
@@ -628,7 +615,6 @@ void Application::OutputAudio() {
return;
}
last_output_time_ = now;
auto opus = std::move(audio_decode_queue_.front());
audio_decode_queue_.pop_front();
lock.unlock();
@@ -642,27 +628,57 @@ void Application::OutputAudio() {
if (!opus_decoder_->Decode(std::move(opus), pcm)) {
return;
}
// Resample if the sample rate is different
if (opus_decode_sample_rate_ != codec->output_sample_rate()) {
if (opus_decoder_->sample_rate() != codec->output_sample_rate()) {
int target_size = output_resampler_.GetOutputSamples(pcm.size());
std::vector<int16_t> resampled(target_size);
output_resampler_.Process(pcm.data(), pcm.size(), resampled.data());
pcm = std::move(resampled);
}
codec->OutputData(pcm);
last_output_time_ = std::chrono::steady_clock::now();
});
}
void Application::InputAudio() {
auto codec = Board::GetInstance().GetAudioCodec();
void Application::OnAudioInput() {
std::vector<int16_t> data;
if (!codec->InputData(data)) {
#if CONFIG_USE_WAKE_WORD_DETECT
if (wake_word_detect_.IsDetectionRunning()) {
ReadAudio(data, 16000, wake_word_detect_.GetFeedSize());
wake_word_detect_.Feed(data);
return;
}
#endif
#if CONFIG_USE_AUDIO_PROCESSOR
if (audio_processor_.IsRunning()) {
ReadAudio(data, 16000, audio_processor_.GetFeedSize());
audio_processor_.Feed(data);
return;
}
#else
if (device_state_ == kDeviceStateListening) {
ReadAudio(data, 16000, 30 * 16000 / 1000);
background_task_->Schedule([this, data = std::move(data)]() mutable {
opus_encoder_->Encode(std::move(data), [this](std::vector<uint8_t>&& opus) {
Schedule([this, opus = std::move(opus)]() {
protocol_->SendAudio(opus);
});
});
});
return;
}
#endif
vTaskDelay(pdMS_TO_TICKS(30));
}
if (codec->input_sample_rate() != 16000) {
void Application::ReadAudio(std::vector<int16_t>& data, int sample_rate, int samples) {
auto codec = Board::GetInstance().GetAudioCodec();
if (codec->input_sample_rate() != sample_rate) {
data.resize(samples * codec->input_sample_rate() / sample_rate);
if (!codec->InputData(data)) {
return;
}
if (codec->input_channels() == 2) {
auto mic_channel = std::vector<int16_t>(data.size() / 2);
auto reference_channel = std::vector<int16_t>(data.size() / 2);
@@ -684,28 +700,12 @@ void Application::InputAudio() {
input_resampler_.Process(data.data(), data.size(), resampled.data());
data = std::move(resampled);
}
} else {
data.resize(samples);
if (!codec->InputData(data)) {
return;
}
}
#if CONFIG_USE_WAKE_WORD_DETECT
if (wake_word_detect_.IsDetectionRunning()) {
wake_word_detect_.Feed(data);
}
#endif
#if CONFIG_USE_AUDIO_PROCESSOR
if (audio_processor_.IsRunning()) {
audio_processor_.Input(data);
}
#else
if (device_state_ == kDeviceStateListening) {
background_task_->Schedule([this, data = std::move(data)]() mutable {
opus_encoder_->Encode(std::move(data), [this](std::vector<uint8_t>&& opus) {
Schedule([this, opus = std::move(opus)]() {
protocol_->SendAudio(opus);
});
});
});
}
#endif
}
void Application::AbortSpeaking(AbortReason reason) {
@@ -714,6 +714,11 @@ void Application::AbortSpeaking(AbortReason reason) {
protocol_->SendAbortSpeaking(reason);
}
void Application::SetListeningMode(ListeningMode mode) {
listening_mode_ = mode;
SetDeviceState(kDeviceStateListening);
}
void Application::SetDeviceState(DeviceState state) {
if (device_state_ == state) {
return;
@@ -727,7 +732,6 @@ void Application::SetDeviceState(DeviceState state) {
background_task_->WaitForCompletion();
auto& board = Board::GetInstance();
auto codec = board.GetAudioCodec();
auto display = board.GetDisplay();
auto led = board.GetLed();
led->OnStateChanged();
@@ -738,6 +742,9 @@ void Application::SetDeviceState(DeviceState state) {
display->SetEmotion("neutral");
#if CONFIG_USE_AUDIO_PROCESSOR
audio_processor_.Stop();
#endif
#if CONFIG_USE_WAKE_WORD_DETECT
wake_word_detect_.StartDetection();
#endif
break;
case kDeviceStateConnecting:
@@ -748,24 +755,43 @@ void Application::SetDeviceState(DeviceState state) {
case kDeviceStateListening:
display->SetStatus(Lang::Strings::LISTENING);
display->SetEmotion("neutral");
ResetDecoder();
opus_encoder_->ResetState();
#if CONFIG_USE_AUDIO_PROCESSOR
audio_processor_.Start();
#endif
// Update the IoT states before sending the start listening command
UpdateIotStates();
if (previous_state == kDeviceStateSpeaking) {
// FIXME: Wait for the speaker to empty the buffer
vTaskDelay(pdMS_TO_TICKS(120));
// Make sure the audio processor is running
#if CONFIG_USE_AUDIO_PROCESSOR
if (!audio_processor_.IsRunning()) {
#else
if (true) {
#endif
// Send the start listening command
protocol_->SendStartListening(listening_mode_);
if (listening_mode_ == kListeningModeAutoStop && previous_state == kDeviceStateSpeaking) {
// FIXME: Wait for the speaker to empty the buffer
vTaskDelay(pdMS_TO_TICKS(120));
}
opus_encoder_->ResetState();
#if CONFIG_USE_WAKE_WORD_DETECT
wake_word_detect_.StopDetection();
#endif
#if CONFIG_USE_AUDIO_PROCESSOR
audio_processor_.Start();
#endif
}
break;
case kDeviceStateSpeaking:
display->SetStatus(Lang::Strings::SPEAKING);
ResetDecoder();
codec->EnableOutput(true);
if (listening_mode_ != kListeningModeRealtime) {
#if CONFIG_USE_AUDIO_PROCESSOR
audio_processor_.Stop();
audio_processor_.Stop();
#endif
#if CONFIG_USE_WAKE_WORD_DETECT
wake_word_detect_.StartDetection();
#endif
}
ResetDecoder();
break;
default:
// Do nothing
@@ -773,19 +799,28 @@ void Application::SetDeviceState(DeviceState state) {
}
}
void Application::SetDecodeSampleRate(int sample_rate) {
if (opus_decode_sample_rate_ == sample_rate) {
void Application::ResetDecoder() {
std::lock_guard<std::mutex> lock(mutex_);
opus_decoder_->ResetState();
audio_decode_queue_.clear();
last_output_time_ = std::chrono::steady_clock::now();
auto codec = Board::GetInstance().GetAudioCodec();
codec->EnableOutput(true);
}
void Application::SetDecodeSampleRate(int sample_rate, int frame_duration) {
if (opus_decoder_->sample_rate() == sample_rate && opus_decoder_->duration_ms() == frame_duration) {
return;
}
opus_decode_sample_rate_ = sample_rate;
opus_decoder_.reset();
opus_decoder_ = std::make_unique<OpusDecoderWrapper>(opus_decode_sample_rate_, 1);
opus_decoder_ = std::make_unique<OpusDecoderWrapper>(sample_rate, 1, frame_duration);
auto codec = Board::GetInstance().GetAudioCodec();
if (opus_decode_sample_rate_ != codec->output_sample_rate()) {
ESP_LOGI(TAG, "Resampling audio from %d to %d", opus_decode_sample_rate_, codec->output_sample_rate());
output_resampler_.Configure(opus_decode_sample_rate_, codec->output_sample_rate());
if (opus_decoder_->sample_rate() != codec->output_sample_rate()) {
ESP_LOGI(TAG, "Resampling audio from %d to %d", opus_decoder_->sample_rate(), codec->output_sample_rate());
output_resampler_.Configure(opus_decoder_->sample_rate(), codec->output_sample_rate());
}
}

View File

@@ -88,12 +88,20 @@ private:
EventGroupHandle_t event_group_ = nullptr;
esp_timer_handle_t clock_timer_handle_ = nullptr;
volatile DeviceState device_state_ = kDeviceStateUnknown;
bool keep_listening_ = false;
ListeningMode listening_mode_ = kListeningModeAutoStop;
#if CONFIG_USE_REALTIME_CHAT
bool realtime_chat_enabled_ = true;
#else
bool realtime_chat_enabled_ = false;
#endif
bool aborted_ = false;
bool voice_detected_ = false;
int clock_ticks_ = 0;
TaskHandle_t main_loop_task_handle_ = nullptr;
TaskHandle_t check_new_version_task_handle_ = nullptr;
// Audio encode / decode
TaskHandle_t audio_loop_task_handle_ = nullptr;
BackgroundTask* background_task_ = nullptr;
std::chrono::steady_clock::time_point last_output_time_;
std::list<std::vector<uint8_t>> audio_decode_queue_;
@@ -101,19 +109,21 @@ private:
std::unique_ptr<OpusEncoderWrapper> opus_encoder_;
std::unique_ptr<OpusDecoderWrapper> opus_decoder_;
int opus_decode_sample_rate_ = -1;
OpusResampler input_resampler_;
OpusResampler reference_resampler_;
OpusResampler output_resampler_;
void MainLoop();
void InputAudio();
void OutputAudio();
void OnAudioInput();
void OnAudioOutput();
void ReadAudio(std::vector<int16_t>& data, int sample_rate, int samples);
void ResetDecoder();
void SetDecodeSampleRate(int sample_rate);
void SetDecodeSampleRate(int sample_rate, int frame_duration);
void CheckNewVersion();
void ShowActivationCode();
void OnClockTimer();
void SetListeningMode(ListeningMode mode);
void AudioLoop();
};
#endif // _APPLICATION_H_

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
main/assets/ja-JP/0.p3 Normal file

Binary file not shown.

BIN
main/assets/ja-JP/1.p3 Normal file

Binary file not shown.

BIN
main/assets/ja-JP/2.p3 Normal file

Binary file not shown.

BIN
main/assets/ja-JP/3.p3 Normal file

Binary file not shown.

BIN
main/assets/ja-JP/4.p3 Normal file

Binary file not shown.

BIN
main/assets/ja-JP/5.p3 Normal file

Binary file not shown.

BIN
main/assets/ja-JP/6.p3 Normal file

Binary file not shown.

BIN
main/assets/ja-JP/7.p3 Normal file

Binary file not shown.

BIN
main/assets/ja-JP/8.p3 Normal file

Binary file not shown.

BIN
main/assets/ja-JP/9.p3 Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -14,23 +14,11 @@ AudioCodec::AudioCodec() {
AudioCodec::~AudioCodec() {
}
void AudioCodec::OnInputReady(std::function<bool()> callback) {
on_input_ready_ = callback;
}
void AudioCodec::OnOutputReady(std::function<bool()> callback) {
on_output_ready_ = callback;
}
void AudioCodec::OutputData(std::vector<int16_t>& data) {
Write(data.data(), data.size());
}
bool AudioCodec::InputData(std::vector<int16_t>& data) {
int duration = 30;
int input_frame_size = input_sample_rate_ / 1000 * duration * input_channels_;
data.resize(input_frame_size);
int samples = Read(data.data(), data.size());
if (samples > 0) {
return true;
@@ -38,40 +26,20 @@ bool AudioCodec::InputData(std::vector<int16_t>& data) {
return false;
}
IRAM_ATTR bool AudioCodec::on_sent(i2s_chan_handle_t handle, i2s_event_data_t *event, void *user_ctx) {
auto audio_codec = (AudioCodec*)user_ctx;
if (audio_codec->output_enabled_ && audio_codec->on_output_ready_) {
return audio_codec->on_output_ready_();
}
return false;
}
IRAM_ATTR bool AudioCodec::on_recv(i2s_chan_handle_t handle, i2s_event_data_t *event, void *user_ctx) {
auto audio_codec = (AudioCodec*)user_ctx;
if (audio_codec->input_enabled_ && audio_codec->on_input_ready_) {
return audio_codec->on_input_ready_();
}
return false;
}
void AudioCodec::Start() {
Settings settings("audio", false);
output_volume_ = settings.GetInt("output_volume", output_volume_);
// 注册音频数据回调
i2s_event_callbacks_t rx_callbacks = {};
rx_callbacks.on_recv = on_recv;
i2s_channel_register_event_callback(rx_handle_, &rx_callbacks, this);
i2s_event_callbacks_t tx_callbacks = {};
tx_callbacks.on_sent = on_sent;
i2s_channel_register_event_callback(tx_handle_, &tx_callbacks, this);
if (output_volume_ <= 0) {
ESP_LOGW(TAG, "Output volume value (%d) is too small, setting to default (10)", output_volume_);
output_volume_ = 10;
}
ESP_ERROR_CHECK(i2s_channel_enable(tx_handle_));
ESP_ERROR_CHECK(i2s_channel_enable(rx_handle_));
EnableInput(true);
EnableOutput(true);
ESP_LOGI(TAG, "Audio codec started");
}
void AudioCodec::SetOutputVolume(int volume) {

View File

@@ -23,8 +23,6 @@ public:
void Start();
void OutputData(std::vector<int16_t>& data);
bool InputData(std::vector<int16_t>& data);
void OnOutputReady(std::function<bool()> callback);
void OnInputReady(std::function<bool()> callback);
inline bool duplex() const { return duplex_; }
inline bool input_reference() const { return input_reference_; }
@@ -33,13 +31,8 @@ public:
inline int input_channels() const { return input_channels_; }
inline int output_channels() const { return output_channels_; }
inline int output_volume() const { return output_volume_; }
private:
std::function<bool()> on_input_ready_;
std::function<bool()> on_output_ready_;
IRAM_ATTR static bool on_recv(i2s_chan_handle_t handle, i2s_event_data_t *event, void *user_ctx);
IRAM_ATTR static bool on_sent(i2s_chan_handle_t handle, i2s_event_data_t *event, void *user_ctx);
inline bool input_enabled() const { return input_enabled_; }
inline bool output_enabled() const { return output_enabled_; }
protected:
i2s_chan_handle_t tx_handle_ = nullptr;

View File

@@ -6,72 +6,70 @@
static const char* TAG = "AudioProcessor";
AudioProcessor::AudioProcessor()
: afe_communication_data_(nullptr) {
: afe_data_(nullptr) {
event_group_ = xEventGroupCreate();
}
void AudioProcessor::Initialize(int channels, bool reference) {
channels_ = channels;
reference_ = reference;
int ref_num = reference_ ? 1 : 0;
void AudioProcessor::Initialize(AudioCodec* codec, bool realtime_chat) {
codec_ = codec;
int ref_num = codec_->input_reference() ? 1 : 0;
afe_config_t afe_config = {
.aec_init = false,
.se_init = true,
.vad_init = false,
.wakenet_init = false,
.voice_communication_init = true,
.voice_communication_agc_init = true,
.voice_communication_agc_gain = 10,
.vad_mode = VAD_MODE_3,
.wakenet_model_name = NULL,
.wakenet_model_name_2 = NULL,
.wakenet_mode = DET_MODE_90,
.afe_mode = SR_MODE_HIGH_PERF,
.afe_perferred_core = 1,
.afe_perferred_priority = 1,
.afe_ringbuf_size = 50,
.memory_alloc_mode = AFE_MEMORY_ALLOC_MORE_PSRAM,
.afe_linear_gain = 1.0,
.agc_mode = AFE_MN_PEAK_AGC_MODE_2,
.pcm_config = {
.total_ch_num = channels_,
.mic_num = channels_ - ref_num,
.ref_num = ref_num,
.sample_rate = 16000,
},
.debug_init = false,
.debug_hook = {{ AFE_DEBUG_HOOK_MASE_TASK_IN, NULL }, { AFE_DEBUG_HOOK_FETCH_TASK_IN, NULL }},
.afe_ns_mode = NS_MODE_SSP,
.afe_ns_model_name = NULL,
.fixed_first_channel = true,
};
std::string input_format;
for (int i = 0; i < codec_->input_channels() - ref_num; i++) {
input_format.push_back('M');
}
for (int i = 0; i < ref_num; i++) {
input_format.push_back('R');
}
afe_communication_data_ = esp_afe_vc_v1.create_from_config(&afe_config);
srmodel_list_t *models = esp_srmodel_init("model");
char* ns_model_name = esp_srmodel_filter(models, ESP_NSNET_PREFIX, NULL);
afe_config_t* afe_config = afe_config_init(input_format.c_str(), NULL, AFE_TYPE_VC, AFE_MODE_HIGH_PERF);
if (realtime_chat) {
afe_config->aec_init = true;
afe_config->aec_mode = AEC_MODE_VOIP_LOW_COST;
} else {
afe_config->aec_init = false;
}
afe_config->ns_init = true;
afe_config->ns_model_name = ns_model_name;
afe_config->afe_ns_mode = AFE_NS_MODE_NET;
if (realtime_chat) {
afe_config->vad_init = false;
} else {
afe_config->vad_init = true;
afe_config->vad_mode = VAD_MODE_0;
afe_config->vad_min_noise_ms = 100;
}
afe_config->afe_perferred_core = 1;
afe_config->afe_perferred_priority = 1;
afe_config->agc_init = false;
afe_config->memory_alloc_mode = AFE_MEMORY_ALLOC_MORE_PSRAM;
afe_iface_ = esp_afe_handle_from_config(afe_config);
afe_data_ = afe_iface_->create_from_config(afe_config);
xTaskCreate([](void* arg) {
auto this_ = (AudioProcessor*)arg;
this_->AudioProcessorTask();
vTaskDelete(NULL);
}, "audio_communication", 4096 * 2, this, 2, NULL);
}, "audio_communication", 4096, this, 3, NULL);
}
AudioProcessor::~AudioProcessor() {
if (afe_communication_data_ != nullptr) {
esp_afe_vc_v1.destroy(afe_communication_data_);
if (afe_data_ != nullptr) {
afe_iface_->destroy(afe_data_);
}
vEventGroupDelete(event_group_);
}
void AudioProcessor::Input(const std::vector<int16_t>& data) {
input_buffer_.insert(input_buffer_.end(), data.begin(), data.end());
size_t AudioProcessor::GetFeedSize() {
return afe_iface_->get_feed_chunksize(afe_data_) * codec_->input_channels();
}
auto feed_size = esp_afe_vc_v1.get_feed_chunksize(afe_communication_data_) * channels_;
while (input_buffer_.size() >= feed_size) {
auto chunk = input_buffer_.data();
esp_afe_vc_v1.feed(afe_communication_data_, chunk);
input_buffer_.erase(input_buffer_.begin(), input_buffer_.begin() + feed_size);
}
void AudioProcessor::Feed(const std::vector<int16_t>& data) {
afe_iface_->feed(afe_data_, data.data());
}
void AudioProcessor::Start() {
@@ -80,6 +78,7 @@ void AudioProcessor::Start() {
void AudioProcessor::Stop() {
xEventGroupClearBits(event_group_, PROCESSOR_RUNNING);
afe_iface_->reset_buffer(afe_data_);
}
bool AudioProcessor::IsRunning() {
@@ -90,16 +89,20 @@ void AudioProcessor::OnOutput(std::function<void(std::vector<int16_t>&& data)> c
output_callback_ = callback;
}
void AudioProcessor::OnVadStateChange(std::function<void(bool speaking)> callback) {
vad_state_change_callback_ = callback;
}
void AudioProcessor::AudioProcessorTask() {
auto fetch_size = esp_afe_sr_v1.get_fetch_chunksize(afe_communication_data_);
auto feed_size = esp_afe_sr_v1.get_feed_chunksize(afe_communication_data_);
auto fetch_size = afe_iface_->get_fetch_chunksize(afe_data_);
auto feed_size = afe_iface_->get_feed_chunksize(afe_data_);
ESP_LOGI(TAG, "Audio communication task started, feed size: %d fetch size: %d",
feed_size, fetch_size);
while (true) {
xEventGroupWaitBits(event_group_, PROCESSOR_RUNNING, pdFALSE, pdTRUE, portMAX_DELAY);
auto res = esp_afe_vc_v1.fetch(afe_communication_data_);
auto res = afe_iface_->fetch_with_delay(afe_data_, portMAX_DELAY);
if ((xEventGroupGetBits(event_group_) & PROCESSOR_RUNNING) == 0) {
continue;
}
@@ -110,6 +113,17 @@ void AudioProcessor::AudioProcessorTask() {
continue;
}
// VAD state change
if (vad_state_change_callback_) {
if (res->vad_state == VAD_SPEECH && !is_speaking_) {
is_speaking_ = true;
vad_state_change_callback_(true);
} else if (res->vad_state == VAD_SILENCE && is_speaking_) {
is_speaking_ = false;
vad_state_change_callback_(false);
}
}
if (output_callback_) {
output_callback_(std::vector<int16_t>(res->data, res->data + res->data_size / sizeof(int16_t)));
}

View File

@@ -10,25 +10,30 @@
#include <vector>
#include <functional>
#include "audio_codec.h"
class AudioProcessor {
public:
AudioProcessor();
~AudioProcessor();
void Initialize(int channels, bool reference);
void Input(const std::vector<int16_t>& data);
void Initialize(AudioCodec* codec, bool realtime_chat);
void Feed(const std::vector<int16_t>& data);
void Start();
void Stop();
bool IsRunning();
void OnOutput(std::function<void(std::vector<int16_t>&& data)> callback);
void OnVadStateChange(std::function<void(bool speaking)> callback);
size_t GetFeedSize();
private:
EventGroupHandle_t event_group_ = nullptr;
esp_afe_sr_data_t* afe_communication_data_ = nullptr;
std::vector<int16_t> input_buffer_;
esp_afe_sr_iface_t* afe_iface_ = nullptr;
esp_afe_sr_data_t* afe_data_ = nullptr;
std::function<void(std::vector<int16_t>&& data)> output_callback_;
int channels_;
bool reference_;
std::function<void(bool speaking)> vad_state_change_callback_;
AudioCodec* codec_ = nullptr;
bool is_speaking_ = false;
void AudioProcessorTask();
};

View File

@@ -11,7 +11,7 @@
static const char* TAG = "WakeWordDetect";
WakeWordDetect::WakeWordDetect()
: afe_detection_data_(nullptr),
: afe_data_(nullptr),
wake_word_pcm_(),
wake_word_opus_() {
@@ -19,8 +19,8 @@ WakeWordDetect::WakeWordDetect()
}
WakeWordDetect::~WakeWordDetect() {
if (afe_detection_data_ != nullptr) {
esp_afe_sr_v1.destroy(afe_detection_data_);
if (afe_data_ != nullptr) {
afe_iface_->destroy(afe_data_);
}
if (wake_word_encode_task_stack_ != nullptr) {
@@ -30,10 +30,9 @@ WakeWordDetect::~WakeWordDetect() {
vEventGroupDelete(event_group_);
}
void WakeWordDetect::Initialize(int channels, bool reference) {
channels_ = channels;
reference_ = reference;
int ref_num = reference_ ? 1 : 0;
void WakeWordDetect::Initialize(AudioCodec* codec) {
codec_ = codec;
int ref_num = codec_->input_reference() ? 1 : 0;
srmodel_list_t *models = esp_srmodel_init("model");
for (int i = 0; i < models->num; i++) {
@@ -50,61 +49,41 @@ void WakeWordDetect::Initialize(int channels, bool reference) {
}
}
afe_config_t afe_config = {
.aec_init = reference_,
.se_init = true,
.vad_init = true,
.wakenet_init = true,
.voice_communication_init = false,
.voice_communication_agc_init = false,
.voice_communication_agc_gain = 10,
.vad_mode = VAD_MODE_3,
.wakenet_model_name = wakenet_model_,
.wakenet_model_name_2 = NULL,
.wakenet_mode = DET_MODE_90,
.afe_mode = SR_MODE_HIGH_PERF,
.afe_perferred_core = 1,
.afe_perferred_priority = 1,
.afe_ringbuf_size = 50,
.memory_alloc_mode = AFE_MEMORY_ALLOC_MORE_PSRAM,
.afe_linear_gain = 1.0,
.agc_mode = AFE_MN_PEAK_AGC_MODE_2,
.pcm_config = {
.total_ch_num = channels_,
.mic_num = channels_ - ref_num,
.ref_num = ref_num,
.sample_rate = 16000
},
.debug_init = false,
.debug_hook = {{ AFE_DEBUG_HOOK_MASE_TASK_IN, NULL }, { AFE_DEBUG_HOOK_FETCH_TASK_IN, NULL }},
.afe_ns_mode = NS_MODE_SSP,
.afe_ns_model_name = NULL,
.fixed_first_channel = true,
};
afe_detection_data_ = esp_afe_sr_v1.create_from_config(&afe_config);
std::string input_format;
for (int i = 0; i < codec_->input_channels() - ref_num; i++) {
input_format.push_back('M');
}
for (int i = 0; i < ref_num; i++) {
input_format.push_back('R');
}
afe_config_t* afe_config = afe_config_init(input_format.c_str(), models, AFE_TYPE_SR, AFE_MODE_HIGH_PERF);
afe_config->aec_init = codec_->input_reference();
afe_config->aec_mode = AEC_MODE_SR_HIGH_PERF;
afe_config->afe_perferred_core = 1;
afe_config->afe_perferred_priority = 1;
afe_config->memory_alloc_mode = AFE_MEMORY_ALLOC_MORE_PSRAM;
afe_iface_ = esp_afe_handle_from_config(afe_config);
afe_data_ = afe_iface_->create_from_config(afe_config);
xTaskCreate([](void* arg) {
auto this_ = (WakeWordDetect*)arg;
this_->AudioDetectionTask();
vTaskDelete(NULL);
}, "audio_detection", 4096 * 2, this, 2, nullptr);
}, "audio_detection", 4096, this, 3, nullptr);
}
void WakeWordDetect::OnWakeWordDetected(std::function<void(const std::string& wake_word)> callback) {
wake_word_detected_callback_ = callback;
}
void WakeWordDetect::OnVadStateChange(std::function<void(bool speaking)> callback) {
vad_state_change_callback_ = callback;
}
void WakeWordDetect::StartDetection() {
xEventGroupSetBits(event_group_, DETECTION_RUNNING_EVENT);
}
void WakeWordDetect::StopDetection() {
xEventGroupClearBits(event_group_, DETECTION_RUNNING_EVENT);
afe_iface_->reset_buffer(afe_data_);
}
bool WakeWordDetect::IsDetectionRunning() {
@@ -112,25 +91,23 @@ bool WakeWordDetect::IsDetectionRunning() {
}
void WakeWordDetect::Feed(const std::vector<int16_t>& data) {
input_buffer_.insert(input_buffer_.end(), data.begin(), data.end());
afe_iface_->feed(afe_data_, data.data());
}
auto feed_size = esp_afe_sr_v1.get_feed_chunksize(afe_detection_data_) * channels_;
while (input_buffer_.size() >= feed_size) {
esp_afe_sr_v1.feed(afe_detection_data_, input_buffer_.data());
input_buffer_.erase(input_buffer_.begin(), input_buffer_.begin() + feed_size);
}
size_t WakeWordDetect::GetFeedSize() {
return afe_iface_->get_feed_chunksize(afe_data_) * codec_->input_channels();
}
void WakeWordDetect::AudioDetectionTask() {
auto fetch_size = esp_afe_sr_v1.get_fetch_chunksize(afe_detection_data_);
auto feed_size = esp_afe_sr_v1.get_feed_chunksize(afe_detection_data_);
auto fetch_size = afe_iface_->get_fetch_chunksize(afe_data_);
auto feed_size = afe_iface_->get_feed_chunksize(afe_data_);
ESP_LOGI(TAG, "Audio detection task started, feed size: %d fetch size: %d",
feed_size, fetch_size);
while (true) {
xEventGroupWaitBits(event_group_, DETECTION_RUNNING_EVENT, pdFALSE, pdTRUE, portMAX_DELAY);
auto res = esp_afe_sr_v1.fetch(afe_detection_data_);
auto res = afe_iface_->fetch_with_delay(afe_data_, portMAX_DELAY);
if (res == nullptr || res->ret_value == ESP_FAIL) {
continue;;
}
@@ -138,17 +115,6 @@ void WakeWordDetect::AudioDetectionTask() {
// Store the wake word data for voice recognition, like who is speaking
StoreWakeWordData((uint16_t*)res->data, res->data_size / sizeof(uint16_t));
// VAD state change
if (vad_state_change_callback_) {
if (res->vad_state == AFE_VAD_SPEECH && !is_speaking_) {
is_speaking_ = true;
vad_state_change_callback_(true);
} else if (res->vad_state == AFE_VAD_SILENCE && is_speaking_) {
is_speaking_ = false;
vad_state_change_callback_(false);
}
}
if (res->wakeup_state == WAKENET_DETECTED) {
StopDetection();
last_detected_wake_word_ = wake_words_[res->wake_word_index - 1];

View File

@@ -15,34 +15,32 @@
#include <mutex>
#include <condition_variable>
#include "audio_codec.h"
class WakeWordDetect {
public:
WakeWordDetect();
~WakeWordDetect();
void Initialize(int channels, bool reference);
void Initialize(AudioCodec* codec);
void Feed(const std::vector<int16_t>& data);
void OnWakeWordDetected(std::function<void(const std::string& wake_word)> callback);
void OnVadStateChange(std::function<void(bool speaking)> callback);
void StartDetection();
void StopDetection();
bool IsDetectionRunning();
size_t GetFeedSize();
void EncodeWakeWordData();
bool GetWakeWordOpus(std::vector<uint8_t>& opus);
const std::string& GetLastDetectedWakeWord() const { return last_detected_wake_word_; }
private:
esp_afe_sr_data_t* afe_detection_data_ = nullptr;
esp_afe_sr_iface_t* afe_iface_ = nullptr;
esp_afe_sr_data_t* afe_data_ = nullptr;
char* wakenet_model_ = NULL;
std::vector<std::string> wake_words_;
std::vector<int16_t> input_buffer_;
EventGroupHandle_t event_group_;
std::function<void(const std::string& wake_word)> wake_word_detected_callback_;
std::function<void(bool speaking)> vad_state_change_callback_;
bool is_speaking_ = false;
int channels_;
bool reference_;
AudioCodec* codec_ = nullptr;
std::string last_detected_wake_word_;
TaskHandle_t wake_word_encode_task_ = nullptr;

View File

@@ -142,7 +142,7 @@ private:
esp_lcd_panel_reset(panel);
esp_lcd_panel_init(panel);
esp_lcd_panel_invert_color(panel, true);
esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
esp_lcd_panel_set_gap(panel, 0, 0);
uint8_t data0[] = {0x00};
uint8_t data1[] = {0x65};
@@ -157,7 +157,11 @@ private:
{
.text_font = &font_puhui_20_4,
.icon_font = &font_awesome_20_4,
.emoji_font = font_emoji_64_init(),
#if CONFIG_USE_WECHAT_MESSAGE_STYLE
.emoji_font = font_emoji_32_init(),
#else
.emoji_font = DISPLAY_HEIGHT >= 240 ? font_emoji_64_init() : font_emoji_32_init(),
#endif
});
}
@@ -180,6 +184,7 @@ private:
void InitializeIot() {
auto& thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
thing_manager.AddThing(iot::CreateThing("Screen"));
}
public:

View File

@@ -129,7 +129,7 @@ private:
xl9555_->SetOutputState(2, 0);
esp_lcd_panel_init(panel);
esp_lcd_panel_invert_color(panel, true);
esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
display_ = new SpiLcdDisplay(panel_io, panel,
@@ -137,7 +137,11 @@ private:
{
.text_font = &font_puhui_20_4,
.icon_font = &font_awesome_20_4,
.emoji_font = font_emoji_64_init(),
#if CONFIG_USE_WECHAT_MESSAGE_STYLE
.emoji_font = font_emoji_32_init(),
#else
.emoji_font = DISPLAY_HEIGHT >= 240 ? font_emoji_64_init() : font_emoji_32_init(),
#endif
});
}
@@ -145,6 +149,7 @@ private:
void InitializeIot() {
auto& thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
thing_manager.AddThing(iot::CreateThing("Screen"));
}
public:

View File

@@ -38,7 +38,7 @@
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
#endif // _BOARD_CONFIG_H_

View File

@@ -201,6 +201,7 @@ private:
void InitializeIot() {
auto& thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
thing_manager.AddThing(iot::CreateThing("Screen"));
}
public:

View File

@@ -0,0 +1,53 @@
# AtomS3R CAM/M12 + Echo Base
## 简介
<div align="center">
<a href="https://docs.m5stack.com/zh_CN/core/AtomS3R%20Cam"><b> AtomS3R CAM 产品主页 </b></a>
|
<a href="https://docs.m5stack.com/zh_CN/core/AtomS3R-M12"><b> AtomS3R M12 产品主页 </b></a>
|
<a href="https://docs.m5stack.com/zh_CN/atom/Atomic%20Echo%20Base"><b> Echo Base 产品主页 </b></a>
</div>
AtomS3R CAM、AtomS3R M12 是 M5Stack 推出的基于 ESP32-S3-PICO-1-N8R8 的物联网可编程控制器搭载了摄像头。Atomic Echo Base 是一款专为 M5 Atom 系列主机设计的语音识别底座,采用了 ES8311 单声道音频解码器、MEMS 麦克风和 NS4150B 功率放大器的集成方案。
两款开发版均**不带屏幕、不带额外按键**,需要使用语音唤醒。必要时,需要使用 `idf.py monitor` 查看 log 以确定运行状态。
## 配置、编译命令
**配置编译目标为 ESP32S3**
```bash
idf.py set-target esp32s3
```
**打开 menuconfig 并配置**
```bash
idf.py menuconfig
```
分别配置如下选项:
- `Xiaozhi Assistant``Board Type` → 选择 `AtomS3R CAM/M12 + Echo Base`
- `Partition Table``Custom partition CSV file` → 删除原有内容,输入 `partitions_8M.csv`
- `Serial flasher config``Flash size` → 选择 `8 MB`
`S` 保存,按 `Q` 退出。
**编译**
```bash
idf.py build
```
**烧录**
将 AtomS3R CAM/M12 连接到电脑,按住侧面 RESET 按键,直到 RESET 按键下方绿灯闪烁。
```bash
idf.py flash
```
烧录完毕后,按一下 RESET 按钮重启。

View File

@@ -0,0 +1,162 @@
#include "wifi_board.h"
#include "audio_codecs/es8311_audio_codec.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "i2c_device.h"
#include "iot/thing_manager.h"
#include "assets/lang_config.h"
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <wifi_station.h>
#define TAG "AtomS3R M12+EchoBase"
#define PI4IOE_ADDR 0x43
#define PI4IOE_REG_CTRL 0x00
#define PI4IOE_REG_IO_PP 0x07
#define PI4IOE_REG_IO_DIR 0x03
#define PI4IOE_REG_IO_OUT 0x05
#define PI4IOE_REG_IO_PULLUP 0x0D
class Pi4ioe : public I2cDevice {
public:
Pi4ioe(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
WriteReg(PI4IOE_REG_IO_PP, 0x00); // Set to high-impedance
WriteReg(PI4IOE_REG_IO_PULLUP, 0xFF); // Enable pull-up
WriteReg(PI4IOE_REG_IO_DIR, 0x6E); // Set input=0, output=1
WriteReg(PI4IOE_REG_IO_OUT, 0xFF); // Set outputs to 1
}
void SetSpeakerMute(bool mute) {
WriteReg(PI4IOE_REG_IO_OUT, mute ? 0x00 : 0xFF);
}
};
class AtomS3rCamM12EchoBaseBoard : public WifiBoard {
private:
i2c_master_bus_handle_t i2c_bus_;
Pi4ioe* pi4ioe_ = nullptr;
bool is_echo_base_connected_ = false;
void InitializeI2c() {
// Initialize I2C peripheral
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = I2C_NUM_0,
.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN,
.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN,
.clk_source = I2C_CLK_SRC_DEFAULT,
.glitch_ignore_cnt = 7,
.intr_priority = 0,
.trans_queue_depth = 0,
.flags = {
.enable_internal_pullup = 1,
},
};
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
}
void I2cDetect() {
is_echo_base_connected_ = false;
uint8_t echo_base_connected_flag = 0x00;
uint8_t address;
printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n");
for (int i = 0; i < 128; i += 16) {
printf("%02x: ", i);
for (int j = 0; j < 16; j++) {
fflush(stdout);
address = i + j;
esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200));
if (ret == ESP_OK) {
printf("%02x ", address);
if (address == 0x18) {
echo_base_connected_flag |= 0xF0;
} else if (address == 0x43) {
echo_base_connected_flag |= 0x0F;
}
} else if (ret == ESP_ERR_TIMEOUT) {
printf("UU ");
} else {
printf("-- ");
}
}
printf("\r\n");
}
is_echo_base_connected_ = (echo_base_connected_flag == 0xFF);
}
void CheckEchoBaseConnection() {
if (is_echo_base_connected_) {
return;
}
while (1) {
ESP_LOGE(TAG, "Atomic Echo Base is disconnected");
vTaskDelay(pdMS_TO_TICKS(1000));
// Rerun detection
I2cDetect();
if (is_echo_base_connected_) {
vTaskDelay(pdMS_TO_TICKS(500));
I2cDetect();
if (is_echo_base_connected_) {
ESP_LOGI(TAG, "Atomic Echo Base is reconnected");
vTaskDelay(pdMS_TO_TICKS(200));
esp_restart();
}
}
}
}
void InitializePi4ioe() {
ESP_LOGI(TAG, "Init PI4IOE");
pi4ioe_ = new Pi4ioe(i2c_bus_, PI4IOE_ADDR);
pi4ioe_->SetSpeakerMute(false);
}
void EnableCameraPower() {
gpio_reset_pin((gpio_num_t)18);
gpio_set_direction((gpio_num_t)18, GPIO_MODE_OUTPUT);
gpio_set_pull_mode((gpio_num_t)18, GPIO_PULLDOWN_ONLY);
ESP_LOGI(TAG, "Camera Power Enabled");
vTaskDelay(pdMS_TO_TICKS(200));
}
// 物联网初始化,添加对 AI 可见设备
void InitializeIot() {
auto& thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
}
public:
AtomS3rCamM12EchoBaseBoard() {
EnableCameraPower(); // IO18 还会控制指示灯
InitializeI2c();
I2cDetect();
CheckEchoBaseConnection();
InitializePi4ioe();
InitializeIot();
}
virtual AudioCodec* GetAudioCodec() override {
static Es8311AudioCodec audio_codec(
i2c_bus_,
I2C_NUM_0,
AUDIO_INPUT_SAMPLE_RATE,
AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_I2S_GPIO_MCLK,
AUDIO_I2S_GPIO_BCLK,
AUDIO_I2S_GPIO_WS,
AUDIO_I2S_GPIO_DOUT,
AUDIO_I2S_GPIO_DIN,
AUDIO_CODEC_GPIO_PA,
AUDIO_CODEC_ES8311_ADDR,
false);
return &audio_codec;
}
};
DECLARE_BOARD(AtomS3rCamM12EchoBaseBoard);

View File

@@ -0,0 +1,51 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
// AtomS3R M12+EchoBase Board configuration
#include <driver/gpio.h>
#define AUDIO_INPUT_REFERENCE true
#define AUDIO_INPUT_SAMPLE_RATE 24000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_NC
#define AUDIO_I2S_GPIO_WS GPIO_NUM_6
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_5
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_38
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_39
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
#define AUDIO_CODEC_GPIO_PA GPIO_NUM_NC
#define BUILTIN_LED_GPIO GPIO_NUM_NC
#define BOOT_BUTTON_GPIO GPIO_NUM_41
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
#define CAMERA_PIN_PWDN (-1)
#define CAMERA_PIN_RESET (-1)
#define CAMERA_PIN_VSYNC (10)
#define CAMERA_PIN_HREF (14)
#define CAMERA_PIN_PCLK (40)
#define CAMERA_PIN_XCLK (21)
#define CAMERA_PIN_SIOD (12)
#define CAMERA_PIN_SIOC ( 9)
#define CAMERA_PIN_D0 ( 3)
#define CAMERA_PIN_D1 (42)
#define CAMERA_PIN_D2 (46)
#define CAMERA_PIN_D3 (48)
#define CAMERA_PIN_D4 ( 4)
#define CAMERA_PIN_D5 (17)
#define CAMERA_PIN_D6 (11)
#define CAMERA_PIN_D7 (13)
#define CAMERA_XCLK_FREQ (20000000)
#endif // _BOARD_CONFIG_H_

View File

@@ -0,0 +1,12 @@
{
"target": "esp32s3",
"builds": [
{
"name": "atoms3r-cam-m12-echo-base",
"sdkconfig_append": [
"CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions_8M.csv\""
]
}
]
}

View File

@@ -277,7 +277,7 @@ private:
void InitializeIot() {
auto& thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
thing_manager.AddThing(iot::CreateThing("Backlight"));
thing_manager.AddThing(iot::CreateThing("Screen"));
}
public:

View File

@@ -0,0 +1,276 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
#define AUDIO_INPUT_SAMPLE_RATE 16000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
// 如果使用 Duplex I2S 模式,请注释下面一行
#define AUDIO_I2S_METHOD_SIMPLEX
#ifdef AUDIO_I2S_METHOD_SIMPLEX
#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_25
#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_26
#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_32
#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_33
#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_14
#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_27
#else
#define AUDIO_I2S_GPIO_WS GPIO_NUM_4
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7
#endif
#define BOOT_BUTTON_GPIO GPIO_NUM_0
#define TOUCH_BUTTON_GPIO GPIO_NUM_5
#define ASR_BUTTON_GPIO GPIO_NUM_19
#define BUILTIN_LED_GPIO GPIO_NUM_2
#ifdef CONFIG_LCD_ST7789_240X240_7PIN
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_22
#define DISPLAY_CS_PIN GPIO_NUM_NC
#else
#define DISPLAY_CS_PIN GPIO_NUM_22
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_23
#endif
#define DISPLAY_MOSI_PIN GPIO_NUM_4
#define DISPLAY_CLK_PIN GPIO_NUM_15
#define DISPLAY_DC_PIN GPIO_NUM_21
#define DISPLAY_RST_PIN GPIO_NUM_18
#ifdef CONFIG_LCD_ST7789_240X320
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_240X320_NO_IPS
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_170X320
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 170
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 35
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_172X320
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 172
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 34
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_240X280
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 280
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 20
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_240X240
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 240
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_240X240_7PIN
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 240
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 2
#endif
#ifdef CONFIG_LCD_ST7789_240X135
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 135
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY true
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 40
#define DISPLAY_OFFSET_Y 53
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7735_128X160
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 160
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y true
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7735_128X128
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 128
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y true
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 32
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7796_320X480
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 320
#define DISPLAY_HEIGHT 480
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ILI9341_240X320
#define LCD_TYPE_ILI9341_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ILI9341_240X320_NO_IPS
#define LCD_TYPE_ILI9341_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_GC9A01_240X240
#define LCD_TYPE_GC9A01_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 240
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_CUSTOM
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#endif // _BOARD_CONFIG_H_

View File

@@ -0,0 +1,13 @@
{
"target": "esp32",
"builds": [
{
"name": "bread-compact-esp32-lcd",
"sdkconfig_append": [
"CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y",
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions_4M.csv\"",
"LCD_ST7789_240X240_7PIN=y"
]
}
]
}

View File

@@ -0,0 +1,222 @@
#include "wifi_board.h"
#include "audio_codecs/no_audio_codec.h"
#include "display/lcd_display.h"
#include "system_reset.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "iot/thing_manager.h"
#include "led/single_led.h"
#include <wifi_station.h>
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <esp_lcd_panel_vendor.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include <driver/spi_common.h>
#if defined(LCD_TYPE_ILI9341_SERIAL)
#include "esp_lcd_ili9341.h"
#endif
#if defined(LCD_TYPE_GC9A01_SERIAL)
#include "esp_lcd_gc9a01.h"
static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = {
// {cmd, { data }, data_size, delay_ms}
{0xfe, (uint8_t[]){0x00}, 0, 0},
{0xef, (uint8_t[]){0x00}, 0, 0},
{0xb0, (uint8_t[]){0xc0}, 1, 0},
{0xb1, (uint8_t[]){0x80}, 1, 0},
{0xb2, (uint8_t[]){0x27}, 1, 0},
{0xb3, (uint8_t[]){0x13}, 1, 0},
{0xb6, (uint8_t[]){0x19}, 1, 0},
{0xb7, (uint8_t[]){0x05}, 1, 0},
{0xac, (uint8_t[]){0xc8}, 1, 0},
{0xab, (uint8_t[]){0x0f}, 1, 0},
{0x3a, (uint8_t[]){0x05}, 1, 0},
{0xb4, (uint8_t[]){0x04}, 1, 0},
{0xa8, (uint8_t[]){0x08}, 1, 0},
{0xb8, (uint8_t[]){0x08}, 1, 0},
{0xea, (uint8_t[]){0x02}, 1, 0},
{0xe8, (uint8_t[]){0x2A}, 1, 0},
{0xe9, (uint8_t[]){0x47}, 1, 0},
{0xe7, (uint8_t[]){0x5f}, 1, 0},
{0xc6, (uint8_t[]){0x21}, 1, 0},
{0xc7, (uint8_t[]){0x15}, 1, 0},
{0xf0,
(uint8_t[]){0x1D, 0x38, 0x09, 0x4D, 0x92, 0x2F, 0x35, 0x52, 0x1E, 0x0C,
0x04, 0x12, 0x14, 0x1f},
14, 0},
{0xf1,
(uint8_t[]){0x16, 0x40, 0x1C, 0x54, 0xA9, 0x2D, 0x2E, 0x56, 0x10, 0x0D,
0x0C, 0x1A, 0x14, 0x1E},
14, 0},
{0xf4, (uint8_t[]){0x00, 0x00, 0xFF}, 3, 0},
{0xba, (uint8_t[]){0xFF, 0xFF}, 2, 0},
};
#endif
#define TAG "ESP32-LCD-MarsbearSupport"
LV_FONT_DECLARE(font_puhui_14_1);
LV_FONT_DECLARE(font_awesome_14_1);
class CompactWifiBoardLCD : public WifiBoard {
private:
Button boot_button_;
Button touch_button_;
Button asr_button_;
LcdDisplay* display_;
void InitializeSpi() {
spi_bus_config_t buscfg = {};
buscfg.mosi_io_num = DISPLAY_MOSI_PIN;
buscfg.miso_io_num = GPIO_NUM_NC;
buscfg.sclk_io_num = DISPLAY_CLK_PIN;
buscfg.quadwp_io_num = GPIO_NUM_NC;
buscfg.quadhd_io_num = GPIO_NUM_NC;
buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t);
ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
}
void InitializeLcdDisplay() {
esp_lcd_panel_io_handle_t panel_io = nullptr;
esp_lcd_panel_handle_t panel = nullptr;
// 液晶屏控制IO初始化
ESP_LOGD(TAG, "Install panel IO");
esp_lcd_panel_io_spi_config_t io_config = {};
io_config.cs_gpio_num = DISPLAY_CS_PIN;
io_config.dc_gpio_num = DISPLAY_DC_PIN;
io_config.spi_mode = DISPLAY_SPI_MODE;
io_config.pclk_hz = 40 * 1000 * 1000;
io_config.trans_queue_depth = 10;
io_config.lcd_cmd_bits = 8;
io_config.lcd_param_bits = 8;
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io));
// 初始化液晶屏驱动芯片
ESP_LOGD(TAG, "Install LCD driver");
esp_lcd_panel_dev_config_t panel_config = {};
panel_config.reset_gpio_num = DISPLAY_RST_PIN;
panel_config.rgb_ele_order = DISPLAY_RGB_ORDER;
panel_config.bits_per_pixel = 16;
#if defined(LCD_TYPE_ILI9341_SERIAL)
ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel));
#elif defined(LCD_TYPE_GC9A01_SERIAL)
ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(panel_io, &panel_config, &panel));
gc9a01_vendor_config_t gc9107_vendor_config = {
.init_cmds = gc9107_lcd_init_cmds,
.init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t),
};
#else
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
#endif
esp_lcd_panel_reset(panel);
esp_lcd_panel_init(panel);
esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR);
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
#ifdef LCD_TYPE_GC9A01_SERIAL
panel_config.vendor_config = &gc9107_vendor_config;
#endif
display_ = new SpiLcdDisplay(panel_io, panel,
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY,
{
.text_font = &font_puhui_14_1,
.icon_font = &font_awesome_14_1,
.emoji_font = font_emoji_32_init(),
});
}
void InitializeButtons() {
// 配置 GPIO
gpio_config_t io_conf = {
.pin_bit_mask = 1ULL << BUILTIN_LED_GPIO, // 设置需要配置的 GPIO 引脚
.mode = GPIO_MODE_OUTPUT, // 设置为输出模式
.pull_up_en = GPIO_PULLUP_DISABLE, // 禁用上拉
.pull_down_en = GPIO_PULLDOWN_DISABLE, // 禁用下拉
.intr_type = GPIO_INTR_DISABLE // 禁用中断
};
gpio_config(&io_conf); // 应用配置
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
gpio_set_level(BUILTIN_LED_GPIO, 1);
app.ToggleChatState();
});
asr_button_.OnClick([this]() {
std::string wake_word="你好小智";
Application::GetInstance().WakeWordInvoke(wake_word);
});
touch_button_.OnPressDown([this]() {
gpio_set_level(BUILTIN_LED_GPIO, 1);
Application::GetInstance().StartListening();
});
touch_button_.OnPressUp([this]() {
gpio_set_level(BUILTIN_LED_GPIO, 0);
Application::GetInstance().StopListening();
});
}
// 物联网初始化,添加对 AI 可见设备
void InitializeIot() {
auto& thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
thing_manager.AddThing(iot::CreateThing("Screen"));
}
}
public:
CompactWifiBoardLCD() :
boot_button_(BOOT_BUTTON_GPIO), touch_button_(TOUCH_BUTTON_GPIO), asr_button_(ASR_BUTTON_GPIO) {
InitializeSpi();
InitializeLcdDisplay();
InitializeButtons();
InitializeIot();
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
GetBacklight()->RestoreBrightness();
}
}
virtual AudioCodec* GetAudioCodec() override {
#ifdef AUDIO_I2S_METHOD_SIMPLEX
static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
#else
static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN);
#endif
return &audio_codec;
}
virtual Display* GetDisplay() override {
return display_;
}
virtual Backlight* GetBacklight() override {
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
return &backlight;
}
return nullptr;
}
};
DECLARE_BOARD(CompactWifiBoardLCD);

View File

@@ -127,7 +127,11 @@ private:
{
.text_font = &font_puhui_16_4,
.icon_font = &font_awesome_16_4,
#if CONFIG_USE_WECHAT_MESSAGE_STYLE
.emoji_font = font_emoji_32_init(),
#else
.emoji_font = DISPLAY_HEIGHT >= 240 ? font_emoji_64_init() : font_emoji_32_init(),
#endif
});
}
@@ -147,10 +151,8 @@ private:
void InitializeIot() {
auto& thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
thing_manager.AddThing(iot::CreateThing("Screen"));
thing_manager.AddThing(iot::CreateThing("Lamp"));
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
thing_manager.AddThing(iot::CreateThing("Backlight"));
}
}
public:

View File

@@ -145,7 +145,7 @@
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 2
#define DISPLAY_SPI_MODE 3
#endif
#ifdef CONFIG_LCD_ST7789_240X135
@@ -197,11 +197,26 @@
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 320
#define DISPLAY_HEIGHT 480
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7796_320X480_NO_IPS
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 320
#define DISPLAY_HEIGHT 480
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false

View File

@@ -15,6 +15,10 @@
#include <esp_lcd_panel_ops.h>
#include <esp_lcd_panel_vendor.h>
#ifdef SH1106
#include <esp_lcd_panel_sh1106.h>
#endif
#define TAG "CompactWifiBoard"
LV_FONT_DECLARE(font_puhui_14_1);
@@ -76,7 +80,11 @@ private:
};
panel_config.vendor_config = &ssd1306_config;
#ifdef SH1106
ESP_ERROR_CHECK(esp_lcd_new_panel_sh1106(panel_io_, &panel_config, &panel_));
#else
ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_));
#endif
ESP_LOGI(TAG, "SSD1306 driver installed");
// Reset the display

View File

@@ -42,6 +42,9 @@
#define DISPLAY_HEIGHT 32
#elif CONFIG_OLED_SSD1306_128X64
#define DISPLAY_HEIGHT 64
#elif CONFIG_OLED_SH1106_128X64
#define DISPLAY_HEIGHT 64
#define SH1106
#else
#error "未选择 OLED 屏幕类型"
#endif

View File

@@ -31,8 +31,16 @@ Backlight::~Backlight() {
void Backlight::RestoreBrightness() {
// Load brightness from settings
Settings settings("display");
SetBrightness(settings.GetInt("brightness", 75));
Settings settings("display");
int saved_brightness = settings.GetInt("brightness", 75);
// 检查亮度值是否为0或过小设置默认值
if (saved_brightness <= 0) {
ESP_LOGW(TAG, "Brightness value (%d) is too small, setting to default (10)", saved_brightness);
saved_brightness = 10; // 设置一个较低的默认值
}
SetBrightness(saved_brightness);
}
void Backlight::SetBrightness(uint8_t brightness, bool permanent) {
@@ -78,7 +86,7 @@ PwmBacklight::PwmBacklight(gpio_num_t pin, bool output_invert) : Backlight() {
.speed_mode = LEDC_LOW_SPEED_MODE,
.duty_resolution = LEDC_TIMER_10_BIT,
.timer_num = LEDC_TIMER_0,
.freq_hz = 20000, //背光pwm频率需要高一点防止电感啸叫
.freq_hz = 25000, //背光pwm频率需要高一点防止电感啸叫
.clk_cfg = LEDC_AUTO_CLK,
.deconfigure = false
};

View File

@@ -44,7 +44,7 @@ std::string Board::GenerateUuid() {
return std::string(uuid_str);
}
bool Board::GetBatteryLevel(int &level, bool& charging) {
bool Board::GetBatteryLevel(int &level, bool& charging, bool& discharging) {
return false;
}

View File

@@ -45,7 +45,7 @@ public:
virtual Udp* CreateUdp() = 0;
virtual void StartNetwork() = 0;
virtual const char* GetNetworkStateIcon() = 0;
virtual bool GetBatteryLevel(int &level, bool& charging);
virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging);
virtual std::string GetJson();
virtual void SetPowerSaveMode(bool enabled) = 0;
};

View File

@@ -0,0 +1,52 @@
#include "knob.h"
static const char* TAG = "Knob";
Knob::Knob(gpio_num_t pin_a, gpio_num_t pin_b) {
knob_config_t config = {
.default_direction = 0,
.gpio_encoder_a = static_cast<uint8_t>(pin_a),
.gpio_encoder_b = static_cast<uint8_t>(pin_b),
};
esp_err_t err = ESP_OK;
knob_handle_ = iot_knob_create(&config);
if (knob_handle_ == NULL) {
ESP_LOGE(TAG, "Failed to create knob instance");
return;
}
err = iot_knob_register_cb(knob_handle_, KNOB_LEFT, knob_callback, this);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to register left callback: %s", esp_err_to_name(err));
return;
}
err = iot_knob_register_cb(knob_handle_, KNOB_RIGHT, knob_callback, this);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to register right callback: %s", esp_err_to_name(err));
return;
}
ESP_LOGI(TAG, "Knob initialized with pins A:%d B:%d", pin_a, pin_b);
}
Knob::~Knob() {
if (knob_handle_ != NULL) {
iot_knob_delete(knob_handle_);
knob_handle_ = NULL;
}
}
void Knob::OnRotate(std::function<void(bool)> callback) {
on_rotate_ = callback;
}
void Knob::knob_callback(void* arg, void* data) {
Knob* knob = static_cast<Knob*>(data);
knob_event_t event = iot_knob_get_event(arg);
if (knob->on_rotate_) {
knob->on_rotate_(event == KNOB_RIGHT);
}
}

25
main/boards/common/knob.h Normal file
View File

@@ -0,0 +1,25 @@
#ifndef KNOB_H_
#define KNOB_H_
#include <driver/gpio.h>
#include <functional>
#include <esp_log.h>
#include <iot_knob.h>
class Knob {
public:
Knob(gpio_num_t pin_a, gpio_num_t pin_b);
~Knob();
void OnRotate(std::function<void(bool)> callback);
private:
static void knob_callback(void* arg, void* data);
knob_handle_t knob_handle_;
gpio_num_t pin_a_;
gpio_num_t pin_b_;
std::function<void(bool)> on_rotate_;
};
#endif // KNOB_H_

View File

@@ -195,7 +195,7 @@ private:
ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel));
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel));
ESP_ERROR_CHECK(esp_lcd_panel_init(panel));
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, false));
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT));
ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY));
ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y));
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true));
@@ -252,4 +252,4 @@ public:
}
};
DECLARE_BOARD(Df_K10Board);
DECLARE_BOARD(Df_K10Board);

View File

@@ -176,7 +176,7 @@ void K10AudioCodec::EnableInput(bool enable) {
fs.channel_mask |= ESP_CODEC_DEV_MAKE_CHANNEL_MASK(1);
}
ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs));
// ESP_ERROR_CHECK(esp_codec_dev_set_in_channel_gain(input_dev_, ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0), 40.0));
ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(input_dev_, 37.5)); //麦克风增益解决收音太小的问题
} else {
ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
}
@@ -223,4 +223,4 @@ int K10AudioCodec::Write(const int16_t* data, int samples) {
return bytes_written / sizeof(int32_t);
}
return samples;
}
}

View File

@@ -7,17 +7,17 @@
#include "config.h"
#include "iot/thing_manager.h"
#include "led/single_led.h"
#include "power_manager.h"
#include "power_save_timer.h"
#include <wifi_station.h>
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <esp_lcd_panel_vendor.h>
#include <driver/spi_common.h>
#include <driver/rtc_io.h>
#include <esp_sleep.h>
#if defined(LCD_ILI9341_240X320) || defined(LCD_ILI9341_240X320_NO_IPS)
#include "esp_lcd_ili9341.h"
#endif
#define TAG "DuChatX"
LV_FONT_DECLARE(font_puhui_16_4);
@@ -25,10 +25,49 @@ LV_FONT_DECLARE(font_awesome_16_4);
class DuChatX : public WifiBoard {
private:
Button boot_button_;
LcdDisplay* display_;
LcdDisplay *display_;
PowerManager *power_manager_;
PowerSaveTimer *power_save_timer_;
esp_lcd_panel_handle_t panel_ = nullptr;
void InitializePowerManager() {
power_manager_ = new PowerManager(GPIO_NUM_6);
power_manager_->OnChargingStatusChanged([this](bool is_charging) {
if (is_charging) {
power_save_timer_->SetEnabled(false);
} else {
power_save_timer_->SetEnabled(true);
}
});
}
void InitializePowerSaveTimer() {
rtc_gpio_init(GPIO_NUM_1);
rtc_gpio_set_direction(GPIO_NUM_1, RTC_GPIO_MODE_OUTPUT_ONLY);
rtc_gpio_set_level(GPIO_NUM_1, 1);
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
power_save_timer_->OnEnterSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
display_->SetChatMessage("system", "");
display_->SetEmotion("sleepy");
GetBacklight()->SetBrightness(1);
});
power_save_timer_->OnExitSleepMode([this]() {
display_->SetChatMessage("system", "");
display_->SetEmotion("neutral");
GetBacklight()->RestoreBrightness();
});
power_save_timer_->OnShutdownRequest([this]() {
ESP_LOGI(TAG, "Shutting down");
rtc_gpio_set_level(GPIO_NUM_1, 0);
// 启用保持功能,确保睡眠期间电平不变
rtc_gpio_hold_en(GPIO_NUM_1);
esp_lcd_panel_disp_on_off(panel_, false); //关闭显示
esp_deep_sleep_start();
});
power_save_timer_->SetEnabled(true);
}
void InitializeSpi() {
spi_bus_config_t buscfg = {};
buscfg.mosi_io_num = DISPLAY_MOSI_PIN;
@@ -42,9 +81,8 @@ private:
void InitializeLcdDisplay() {
esp_lcd_panel_io_handle_t panel_io = nullptr;
esp_lcd_panel_handle_t panel = nullptr;
// 液晶屏控制IO初始化
ESP_LOGD(TAG, "Install panel IO");
ESP_LOGD(TAG, "Install panel_ IO");
esp_lcd_panel_io_spi_config_t io_config = {};
io_config.cs_gpio_num = DISPLAY_CS_PIN;
io_config.dc_gpio_num = DISPLAY_DC_PIN;
@@ -61,76 +99,87 @@ private:
panel_config.reset_gpio_num = DISPLAY_RST_PIN;
panel_config.rgb_ele_order = DISPLAY_RGB_ORDER;
panel_config.bits_per_pixel = 16;
#if defined(LCD_ILI9341_240X320) || defined(LCD_ILI9341_240X320_NO_IPS)
ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel));
#else
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
#endif
esp_lcd_panel_reset(panel);
esp_lcd_panel_init(panel);
esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR);
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
display_ = new SpiLcdDisplay(panel_io, panel,
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY,
{
.text_font = &font_puhui_16_4,
.icon_font = &font_awesome_16_4,
.emoji_font = DISPLAY_HEIGHT >= 240 ? font_emoji_64_init() : font_emoji_32_init(),
});
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel_));
esp_lcd_panel_reset(panel_);
esp_lcd_panel_init(panel_);
esp_lcd_panel_invert_color(panel_, DISPLAY_INVERT_COLOR);
esp_lcd_panel_swap_xy(panel_, DISPLAY_SWAP_XY);
esp_lcd_panel_mirror(panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
display_ = new SpiLcdDisplay(panel_io, panel_,DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y,DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY,
{
.text_font = &font_puhui_16_4,
.icon_font = &font_awesome_16_4,
.emoji_font = DISPLAY_HEIGHT >= 240 ? font_emoji_64_init() : font_emoji_32_init(),
});
}
void InitializeButtons() {
boot_button_.OnClick([this]() {
power_save_timer_->WakeUp();
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
app.ToggleChatState();
app.ToggleChatState();
});
}
// 物联网初始化,添加对 AI 可见设备
void InitializeIot() {
auto& thing_manager = iot::ThingManager::GetInstance();
auto &thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
thing_manager.AddThing(iot::CreateThing("Backlight"));
thing_manager.AddThing(iot::CreateThing("Screen"));
thing_manager.AddThing(iot::CreateThing("Battery"));
}
public:
DuChatX() :
boot_button_(BOOT_BUTTON_GPIO) {
DuChatX() : boot_button_(BOOT_BUTTON_GPIO) {
InitializeSpi();
InitializeLcdDisplay();
InitializeButtons();
InitializeIot();
GetBacklight()->RestoreBrightness();
InitializePowerSaveTimer();
InitializePowerManager();
}
virtual Led* GetLed() override {
virtual Led *GetLed() override {
static SingleLed led(BUILTIN_LED_GPIO);
return &led;
}
virtual AudioCodec* GetAudioCodec() override {
virtual AudioCodec *GetAudioCodec() override {
static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
return &audio_codec;
}
virtual Display* GetDisplay() override {
virtual Display *GetDisplay() override {
return display_;
}
virtual Backlight* GetBacklight() override {
virtual Backlight *GetBacklight() override {
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
return &backlight;
}
virtual bool GetBatteryLevel(int &level, bool &charging, bool &discharging) override {
static bool last_discharging = false;
charging = power_manager_->IsCharging();
discharging = power_manager_->IsDischarging();
if (discharging != last_discharging) {
power_save_timer_->SetEnabled(discharging);
last_discharging = discharging;
}
level = power_manager_->GetBatteryLevel();
return true;
}
virtual void SetPowerSaveMode(bool enabled) override {
if (!enabled) {
power_save_timer_->WakeUp();
}
WifiBoard::SetPowerSaveMode(enabled);
}
};
DECLARE_BOARD(DuChatX);

View File

@@ -0,0 +1,186 @@
#pragma once
#include <vector>
#include <functional>
#include <esp_timer.h>
#include <driver/gpio.h>
#include <esp_adc/adc_oneshot.h>
class PowerManager {
private:
esp_timer_handle_t timer_handle_;
std::function<void(bool)> on_charging_status_changed_;
std::function<void(bool)> on_low_battery_status_changed_;
gpio_num_t charging_pin_ = GPIO_NUM_NC;
std::vector<uint16_t> adc_values_;
uint32_t battery_level_ = 0;
bool is_charging_ = false;
bool is_low_battery_ = false;
int ticks_ = 0;
const int kBatteryAdcInterval = 60;
const int kBatteryAdcDataCount = 3;
const int kLowBatteryLevel = 20;
adc_oneshot_unit_handle_t adc_handle_;
void CheckBatteryStatus() {
// Get charging status
bool new_charging_status = gpio_get_level(charging_pin_) == 1;
if (new_charging_status != is_charging_) {
is_charging_ = new_charging_status;
if (on_charging_status_changed_) {
on_charging_status_changed_(is_charging_);
}
ReadBatteryAdcData();
return;
}
// 如果电池电量数据不足,则读取电池电量数据
if (adc_values_.size() < kBatteryAdcDataCount) {
ReadBatteryAdcData();
return;
}
// 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据
ticks_++;
if (ticks_ % kBatteryAdcInterval == 0) {
ReadBatteryAdcData();
}
}
void ReadBatteryAdcData() {
int adc_value;
ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_5, &adc_value));
// 将 ADC 值添加到队列中
adc_values_.push_back(adc_value);
if (adc_values_.size() > kBatteryAdcDataCount) {
adc_values_.erase(adc_values_.begin());
}
uint32_t average_adc = 0;
for (auto value : adc_values_) {
average_adc += value;
}
average_adc /= adc_values_.size();
// 定义电池电量区间
const struct {
uint16_t adc;
uint8_t level;
} levels[] = {
{1120, 0},
{1140, 20},
{1160, 40},
{1170, 60},
{1190, 80},
{1217, 100}
};
// 低于最低值时
if (average_adc < levels[0].adc) {
battery_level_ = 0;
}
// 高于最高值时
else if (average_adc >= levels[5].adc) {
battery_level_ = 100;
} else {
// 线性插值计算中间值
for (int i = 0; i < 5; i++) {
if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
break;
}
}
}
// Check low battery status
if (adc_values_.size() >= kBatteryAdcDataCount) {
bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
if (new_low_battery_status != is_low_battery_) {
is_low_battery_ = new_low_battery_status;
if (on_low_battery_status_changed_) {
on_low_battery_status_changed_(is_low_battery_);
}
}
}
ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
}
public:
PowerManager(gpio_num_t pin) : charging_pin_(pin) {
// 初始化充电引脚
gpio_config_t io_conf = {};
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pin_bit_mask = (1ULL << charging_pin_);
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
gpio_config(&io_conf);
// 创建电池电量检查定时器
esp_timer_create_args_t timer_args = {
.callback = [](void* arg) {
PowerManager* self = static_cast<PowerManager*>(arg);
self->CheckBatteryStatus();
},
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
.name = "battery_check_timer",
.skip_unhandled_events = true,
};
ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
// 初始化 ADC
adc_oneshot_unit_init_cfg_t init_config = {
.unit_id = ADC_UNIT_1,
.ulp_mode = ADC_ULP_MODE_DISABLE,
};
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
adc_oneshot_chan_cfg_t chan_config = {
.atten = ADC_ATTEN_DB_12,
.bitwidth = ADC_BITWIDTH_12,
};
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_5, &chan_config));
}
~PowerManager() {
if (timer_handle_) {
esp_timer_stop(timer_handle_);
esp_timer_delete(timer_handle_);
}
if (adc_handle_) {
adc_oneshot_del_unit(adc_handle_);
}
}
bool IsCharging() {
// 如果电量已经满了,则不再显示充电中
if (battery_level_ == 100) {
return false;
}
return is_charging_;
}
bool IsDischarging() {
// 没有区分充电和放电,所以直接返回相反状态
return !is_charging_;
}
uint8_t GetBatteryLevel() {
return battery_level_;
}
void OnLowBatteryStatusChanged(std::function<void(bool)> callback) {
on_low_battery_status_changed_ = callback;
}
void OnChargingStatusChanged(std::function<void(bool)> callback) {
on_charging_status_changed_ = callback;
}
};

View File

@@ -117,7 +117,7 @@ private:
esp_lcd_panel_reset(panel);
esp_lcd_panel_init(panel);
esp_lcd_panel_invert_color(panel, false);
esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
esp_lcd_panel_disp_on_off(panel, true);
@@ -126,7 +126,11 @@ private:
{
.text_font = &font_puhui_20_4,
.icon_font = &font_awesome_20_4,
#if CONFIG_USE_WECHAT_MESSAGE_STYLE
.emoji_font = font_emoji_32_init(),
#else
.emoji_font = font_emoji_64_init(),
#endif
});
}
@@ -134,7 +138,7 @@ private:
void InitializeIot() {
auto& thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
thing_manager.AddThing(iot::CreateThing("Backlight"));
thing_manager.AddThing(iot::CreateThing("Screen"));
}
public:

View File

@@ -196,8 +196,8 @@ void BoxAudioCodecLite::EnableInput(bool enable) {
fs.channel_mask |= ESP_CODEC_DEV_MAKE_CHANNEL_MASK(1);
}
ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs));
// 不支持设置gain暂时注释
// ESP_ERROR_CHECK(esp_codec_dev_set_in_channel_gain(input_dev_, ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0), 40.0));
// 麦克风增益解决收音太小的问题
ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(input_dev_, 37.5));
} else {
ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
}

View File

@@ -1,5 +1,5 @@
#include "wifi_board.h"
#include "audio_codecs/box_audio_codec_lite.h"
#include "box_audio_codec_lite.h"
#include "display/lcd_display.h"
#include "esp_lcd_ili9341.h"
#include "font_awesome_symbols.h"
@@ -203,7 +203,7 @@ private:
void InitializeIot() {
auto& thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
thing_manager.AddThing(iot::CreateThing("Backlight"));
thing_manager.AddThing(iot::CreateThing("Screen"));
}
public:

View File

@@ -117,7 +117,7 @@ private:
esp_lcd_panel_reset(panel);
esp_lcd_panel_init(panel);
esp_lcd_panel_invert_color(panel, false);
esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
esp_lcd_panel_disp_on_off(panel, true);
@@ -134,7 +134,7 @@ private:
void InitializeIot() {
auto& thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
thing_manager.AddThing(iot::CreateThing("Backlight"));
thing_manager.AddThing(iot::CreateThing("Screen"));
}
public:

View File

@@ -7,8 +7,6 @@
#define AUDIO_INPUT_SAMPLE_RATE 16000
#define AUDIO_OUTPUT_SAMPLE_RATE 16000
#define AUDIO_INPUT_REFERENCE false
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_45
#define AUDIO_I2S_GPIO_WS GPIO_NUM_41
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_39

View File

@@ -126,7 +126,7 @@ private:
void InitializeIot() {
auto& thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
thing_manager.AddThing(iot::CreateThing("Backlight"));
thing_manager.AddThing(iot::CreateThing("Screen"));
thing_manager.AddThing(iot::CreateThing("Chassis"));
}

View File

@@ -0,0 +1,46 @@
# 主板开源地址:
[https://oshwhub.com/wdmomo/esp32-xiaozhi-kidpcb](https://oshwhub.com/wdmomo/esp32-xiaozhi-kidpcb)
# 编译配置命令
**配置编译目标为 ESP32**
```bash
idf.py set-target esp32
```
**打开 menuconfig**
```bash
idf.py menuconfig
```
**选择板子:**
```
Xiaozhi Assistant -> Board Type -> ESP32 CGC
```
**选择屏幕类型:**
```
Xiaozhi Assistant -> LCD Type -> "ST7735, 分辨率128*128"
```
**修改 flash 大小:**
```
Serial flasher config -> Flash size -> 4 MB
```
**修改分区表:**
```
Partition Table -> Custom partition CSV file -> partitions_4M.csv
```
**编译:**
```bash
idf.py build
```

View File

@@ -0,0 +1,268 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
#define AUDIO_INPUT_SAMPLE_RATE 16000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
// 如果使用 Duplex I2S 模式,请注释下面一行
#define AUDIO_I2S_METHOD_SIMPLEX
#ifdef AUDIO_I2S_METHOD_SIMPLEX
#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_25
#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_26
#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_32
#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_33
#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_14
#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_27
#else
#define AUDIO_I2S_GPIO_WS GPIO_NUM_4
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7
#endif
#define BOOT_BUTTON_GPIO GPIO_NUM_0
#define ASR_BUTTON_GPIO GPIO_NUM_13
#define DISPLAY_SDA_PIN GPIO_NUM_NC
#define DISPLAY_SCL_PIN GPIO_NUM_NC
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_4
#define DISPLAY_SCLK_PIN GPIO_NUM_18
#define DISPLAY_MOSI_PIN GPIO_NUM_23
#define DISPLAY_CS_PIN GPIO_NUM_5
#define DISPLAY_DC_PIN GPIO_NUM_2
#define DISPLAY_RESET_PIN GPIO_NUM_NC
#define DISPLAY_SPI_SCLK_HZ (20 * 1000 * 1000)
#ifdef CONFIG_LCD_ST7789_240X320
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#endif
#ifdef CONFIG_LCD_ST7789_240X320_NO_IPS
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_170X320
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 170
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 35
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_172X320
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 172
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 34
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_240X280
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 280
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 20
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_240X240
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 240
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_240X240_7PIN
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 240
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 2
#endif
#ifdef CONFIG_LCD_ST7789_240X135
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 135
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY true
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 40
#define DISPLAY_OFFSET_Y 53
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7735_128X160
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 160
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y true
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#endif
#ifdef CONFIG_LCD_ST7735_128X128
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 128
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y true
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 2
#define DISPLAY_OFFSET_Y 3
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7796_320X480
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 320
#define DISPLAY_HEIGHT 480
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ILI9341_240X320
#define LCD_TYPE_ILI9341_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ILI9341_240X320_NO_IPS
#define LCD_TYPE_ILI9341_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_GC9A01_240X240
#define LCD_TYPE_GC9A01_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 240
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_CUSTOM
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#endif // _BOARD_CONFIG_H_

View File

@@ -0,0 +1,13 @@
{
"target": "esp32",
"builds": [
{
"name": "esp32-cgc",
"sdkconfig_append": [
"CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y",
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions_4M.csv\"",
"CONFIG_LCD_ST7735_128X128=y"
]
}
]
}

View File

@@ -0,0 +1,192 @@
#include "wifi_board.h"
#include "audio_codecs/no_audio_codec.h"
#include "display/lcd_display.h"
#include "system_reset.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "iot/thing_manager.h"
#include "led/single_led.h"
#include <wifi_station.h>
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <esp_lcd_panel_vendor.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include <driver/spi_common.h>
#if defined(LCD_TYPE_ILI9341_SERIAL)
#include <esp_lcd_ili9341.h>
#endif
#if defined(LCD_TYPE_GC9A01_SERIAL)
#include <esp_lcd_gc9a01.h>
static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = {
// {cmd, { data }, data_size, delay_ms}
{0xfe, (uint8_t[]){0x00}, 0, 0},
{0xef, (uint8_t[]){0x00}, 0, 0},
{0xb0, (uint8_t[]){0xc0}, 1, 0},
{0xb1, (uint8_t[]){0x80}, 1, 0},
{0xb2, (uint8_t[]){0x27}, 1, 0},
{0xb3, (uint8_t[]){0x13}, 1, 0},
{0xb6, (uint8_t[]){0x19}, 1, 0},
{0xb7, (uint8_t[]){0x05}, 1, 0},
{0xac, (uint8_t[]){0xc8}, 1, 0},
{0xab, (uint8_t[]){0x0f}, 1, 0},
{0x3a, (uint8_t[]){0x05}, 1, 0},
{0xb4, (uint8_t[]){0x04}, 1, 0},
{0xa8, (uint8_t[]){0x08}, 1, 0},
{0xb8, (uint8_t[]){0x08}, 1, 0},
{0xea, (uint8_t[]){0x02}, 1, 0},
{0xe8, (uint8_t[]){0x2A}, 1, 0},
{0xe9, (uint8_t[]){0x47}, 1, 0},
{0xe7, (uint8_t[]){0x5f}, 1, 0},
{0xc6, (uint8_t[]){0x21}, 1, 0},
{0xc7, (uint8_t[]){0x15}, 1, 0},
{0xf0,
(uint8_t[]){0x1D, 0x38, 0x09, 0x4D, 0x92, 0x2F, 0x35, 0x52, 0x1E, 0x0C,
0x04, 0x12, 0x14, 0x1f},
14, 0},
{0xf1,
(uint8_t[]){0x16, 0x40, 0x1C, 0x54, 0xA9, 0x2D, 0x2E, 0x56, 0x10, 0x0D,
0x0C, 0x1A, 0x14, 0x1E},
14, 0},
{0xf4, (uint8_t[]){0x00, 0x00, 0xFF}, 3, 0},
{0xba, (uint8_t[]){0xFF, 0xFF}, 2, 0},
};
#endif
#define TAG "ESP32_CGC"
LV_FONT_DECLARE(font_puhui_14_1);
LV_FONT_DECLARE(font_awesome_14_1);
class ESP32_CGC : public WifiBoard {
private:
Button boot_button_;
LcdDisplay* display_;
Button asr_button_;
void InitializeSpi() {
spi_bus_config_t buscfg = {};
buscfg.mosi_io_num = DISPLAY_MOSI_PIN;
buscfg.miso_io_num = GPIO_NUM_NC;
buscfg.sclk_io_num = DISPLAY_SCLK_PIN;
buscfg.quadwp_io_num = GPIO_NUM_NC;
buscfg.quadhd_io_num = GPIO_NUM_NC;
buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t);
ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
}
void InitializeLcdDisplay() {
esp_lcd_panel_io_handle_t panel_io = nullptr;
esp_lcd_panel_handle_t panel = nullptr;
// 液晶屏控制IO初始化
ESP_LOGD(TAG, "Install panel IO");
esp_lcd_panel_io_spi_config_t io_config = {};
io_config.cs_gpio_num = DISPLAY_CS_PIN;
io_config.dc_gpio_num = DISPLAY_DC_PIN;
io_config.spi_mode = DISPLAY_SPI_MODE;
io_config.pclk_hz = DISPLAY_SPI_SCLK_HZ;
io_config.trans_queue_depth = 10;
io_config.lcd_cmd_bits = 8;
io_config.lcd_param_bits = 8;
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io));
// 初始化液晶屏驱动芯片
ESP_LOGD(TAG, "Install LCD driver");
esp_lcd_panel_dev_config_t panel_config = {};
panel_config.reset_gpio_num = DISPLAY_RESET_PIN;
panel_config.rgb_ele_order = DISPLAY_RGB_ORDER;
panel_config.bits_per_pixel = 16;
#if defined(LCD_TYPE_ILI9341_SERIAL)
ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel));
#elif defined(LCD_TYPE_GC9A01_SERIAL)
ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(panel_io, &panel_config, &panel));
gc9a01_vendor_config_t gc9107_vendor_config = {
.init_cmds = gc9107_lcd_init_cmds,
.init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t),
};
#else
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
#endif
esp_lcd_panel_reset(panel);
esp_lcd_panel_init(panel);
esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR);
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
#ifdef LCD_TYPE_GC9A01_SERIAL
panel_config.vendor_config = &gc9107_vendor_config;
#endif
display_ = new SpiLcdDisplay(panel_io, panel,
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY,
{
.text_font = &font_puhui_14_1,
.icon_font = &font_awesome_14_1,
.emoji_font = font_emoji_32_init(),
});
}
void InitializeButtons() {
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
app.ToggleChatState();
});
asr_button_.OnClick([this]() {
std::string wake_word="你好小智";
Application::GetInstance().WakeWordInvoke(wake_word);
});
}
// 物联网初始化,添加对 AI 可见设备
void InitializeIot() {
auto& thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
thing_manager.AddThing(iot::CreateThing("Screen"));
}
public:
ESP32_CGC() :
boot_button_(BOOT_BUTTON_GPIO), asr_button_(ASR_BUTTON_GPIO) {
InitializeSpi();
InitializeLcdDisplay();
InitializeButtons();
InitializeIot();
GetBacklight()->RestoreBrightness();
}
virtual AudioCodec* GetAudioCodec() override
{
#ifdef AUDIO_I2S_METHOD_SIMPLEX
static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
#else
static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN);
#endif
return &audio_codec;
}
virtual Display* GetDisplay() override {
return display_;
}
virtual Backlight* GetBacklight() override {
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
return &backlight;
}
};
DECLARE_BOARD(ESP32_CGC);

View File

@@ -3,7 +3,9 @@
"builds": [
{
"name": "esp32-s3-touch-amoled-1.8",
"sdkconfig_append": []
"sdkconfig_append": [
"CONFIG_USE_WECHAT_MESSAGE_STYLE=y"
]
}
]
}

View File

@@ -29,7 +29,29 @@ LV_FONT_DECLARE(font_awesome_30_4);
class Pmic : public Axp2101 {
public:
Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) {
// TODO: Configure the power management IC here...
WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable
WriteReg(0x27, 0x10); // hold 4s to power off
// Disable All DCs but DC1
WriteReg(0x80, 0x01);
// Disable All LDOs
WriteReg(0x90, 0x00);
WriteReg(0x91, 0x00);
// Set DC1 to 3.3V
WriteReg(0x82, (3300 - 1500) / 100);
// Set ALDO1 to 3.3V
WriteReg(0x92, (3300 - 500) / 100);
// Enable ALDO1(MIC)
WriteReg(0x90, 0x01);
WriteReg(0x64, 0x02); // CV charger voltage setting to 4.1V
WriteReg(0x61, 0x02); // set Main battery precharge current to 50mA
WriteReg(0x62, 0x08); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA )
WriteReg(0x63, 0x01); // set Main battery term charge current to 25mA
}
};
@@ -45,8 +67,7 @@ static const sh8601_lcd_init_cmd_t vendor_specific_init[] = {
{0x2A, (uint8_t[]){0x00, 0x00, 0x01, 0x6F}, 4, 0},
{0x2B, (uint8_t[]){0x00, 0x00, 0x01, 0xBF}, 4, 0},
{0x51, (uint8_t[]){0x00}, 1, 10},
{0x29, (uint8_t[]){0x00}, 0, 10},
{0x51, (uint8_t[]){0xFF}, 1, 0},
{0x29, (uint8_t[]){0x00}, 0, 10}
};
// 在waveshare_amoled_1_8类之前添加新的显示类
@@ -66,7 +87,11 @@ public:
{
.text_font = &font_puhui_30_4,
.icon_font = &font_awesome_30_4,
#if CONFIG_USE_WECHAT_MESSAGE_STYLE
.emoji_font = font_emoji_32_init(),
#else
.emoji_font = font_emoji_64_init(),
#endif
}) {
DisplayLockGuard lock(this);
lv_obj_set_style_pad_left(status_bar_, LV_HOR_RES * 0.1, 0);
@@ -82,6 +107,8 @@ protected:
esp_lcd_panel_io_handle_t panel_io_;
virtual void SetBrightnessImpl(uint8_t brightness) override {
auto display = Board::GetInstance().GetDisplay();
DisplayLockGuard lock(display);
uint8_t data[1] = {((uint8_t)((255 * brightness) / 100))};
int lcd_cmd = 0x51;
lcd_cmd &= 0xff;
@@ -108,7 +135,7 @@ private:
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("sleepy");
GetBacklight()->SetBrightness(10);
GetBacklight()->SetBrightness(20);
});
power_save_timer_->OnExitSleepMode([this]() {
auto display = GetDisplay();
@@ -217,7 +244,6 @@ private:
esp_lcd_panel_reset(panel);
esp_lcd_panel_init(panel);
esp_lcd_panel_invert_color(panel, false);
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
esp_lcd_panel_disp_on_off(panel, true);
display_ = new CustomLcdDisplay(panel_io, panel,
@@ -230,9 +256,9 @@ private:
void InitializeIot() {
auto& thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
thing_manager.AddThing(iot::CreateThing("BoardControl"));
thing_manager.AddThing(iot::CreateThing("Backlight"));
thing_manager.AddThing(iot::CreateThing("Screen"));
thing_manager.AddThing(iot::CreateThing("Battery"));
thing_manager.AddThing(iot::CreateThing("BoardControl"));
}
public:
@@ -263,21 +289,16 @@ public:
return backlight_;
}
virtual bool GetBatteryLevel(int &level, bool& charging) override {
static bool last_charging = false;
virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override {
static bool last_discharging = false;
charging = pmic_->IsCharging();
if (charging != last_charging) {
power_save_timer_->WakeUp();
last_charging = charging;
discharging = pmic_->IsDischarging();
if (discharging != last_discharging) {
power_save_timer_->SetEnabled(discharging);
last_discharging = discharging;
}
level = pmic_->GetBatteryLevel();
if (pmic_->IsDischarging()) {
power_save_timer_->SetEnabled(true);
} else {
power_save_timer_->SetEnabled(false);
}
return true;
}

View File

@@ -64,7 +64,7 @@ private:
i2c_master_bus_handle_t i2c_bus_;
esp_io_expander_handle_t io_expander = NULL;
LcdDisplay* display_;
button_handle_t boot_btn,pwr_btn;
button_handle_t boot_btn, pwr_btn;
void InitializeI2c() {
// Initialize I2C peripheral
@@ -114,7 +114,7 @@ private:
ESP_ERROR_CHECK(spi_bus_initialize(QSPI_LCD_HOST, &bus_config, SPI_DMA_CH_AUTO));
}
void Initializespd2010Display() {
void InitializeSpd2010Display() {
esp_lcd_panel_io_handle_t panel_io = nullptr;
esp_lcd_panel_handle_t panel = nullptr;
@@ -216,7 +216,7 @@ private:
void InitializeIot() {
auto& thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
thing_manager.AddThing(iot::CreateThing("Backlight"));
thing_manager.AddThing(iot::CreateThing("Screen"));
}
public:
@@ -224,7 +224,7 @@ public:
InitializeI2c();
InitializeTca9554();
InitializeSpi();
Initializespd2010Display();
InitializeSpd2010Display();
InitializeButtons();
InitializeIot();
GetBacklight()->RestoreBrightness();

View File

@@ -219,7 +219,7 @@ private:
i2c_master_bus_handle_t i2c_bus_;
esp_io_expander_handle_t io_expander = NULL;
LcdDisplay* display_;
button_handle_t boot_btn,pwr_btn;
button_handle_t boot_btn, pwr_btn;
void InitializeI2c() {
// Initialize I2C peripheral
@@ -433,7 +433,7 @@ private:
void InitializeIot() {
auto& thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
thing_manager.AddThing(iot::CreateThing("Backlight"));
thing_manager.AddThing(iot::CreateThing("Screen"));
}
public:

View File

@@ -378,7 +378,7 @@ private:
void InitializeIot() {
auto& thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
thing_manager.AddThing(iot::CreateThing("Backlight"));
thing_manager.AddThing(iot::CreateThing("Screen"));
}
public:

View File

@@ -0,0 +1,3 @@
新增 微雪 开发板: ESP32-S3-Touch-LCD-3.5
产品链接:
https://www.waveshare.net/shop/ESP32-S3-Touch-LCD-3.5.htm

View File

@@ -0,0 +1,31 @@
#include <freertos/FreeRTOS.h>
#include <freertos/timers.h>
#include <freertos/task.h>
#include <esp_log.h>
#include "board.h"
#include "boards/common/wifi_board.h"
#include "iot/thing.h"
#define TAG "BoardControl"
namespace iot {
class BoardControl : public Thing {
public:
BoardControl() : Thing("BoardControl", "当前 AI 机器人管理和控制") {
// 修改重新配网
methods_.AddMethod("ResetWifiConfiguration", "重新配网", ParameterList(),
[this](const ParameterList& parameters) {
ESP_LOGI(TAG, "ResetWifiConfiguration");
auto board = static_cast<WifiBoard*>(&Board::GetInstance());
if (board && board->GetBoardType() == "wifi") {
board->ResetWifiConfiguration();
}
});
}
};
} // namespace iot
DECLARE_THING(BoardControl);

View File

@@ -0,0 +1,50 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
#include <driver/spi_master.h>
#define AUDIO_INPUT_SAMPLE_RATE 24000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_12
#define AUDIO_I2S_GPIO_WS GPIO_NUM_15
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_13
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_14
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_16
#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_8
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_7
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
#define BUILTIN_LED_GPIO GPIO_NUM_NC
#define BOOT_BUTTON_GPIO GPIO_NUM_0
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
#define DISPLAY_SPI_MODE 0
#define DISPLAY_CS_PIN GPIO_NUM_NC
#define DISPLAY_MOSI_PIN GPIO_NUM_1
#define DISPLAY_MISO_PIN GPIO_NUM_2
#define DISPLAY_CLK_PIN GPIO_NUM_5
#define DISPLAY_DC_PIN GPIO_NUM_3
#define DISPLAY_RST_PIN GPIO_NUM_NC
#define DISPLAY_WIDTH 480
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_6
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#endif // _BOARD_CONFIG_H_

View File

@@ -0,0 +1,9 @@
{
"target": "esp32s3",
"builds": [
{
"name": "esp32-s3-touch-lcd-3.5",
"sdkconfig_append": []
}
]
}

View File

@@ -0,0 +1,295 @@
#include "wifi_board.h"
#include "audio_codecs/es8311_audio_codec.h"
#include "display/lcd_display.h"
#include "system_reset.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "iot/thing_manager.h"
#include <esp_log.h>
#include "i2c_device.h"
#include <driver/i2c.h>
#include <driver/ledc.h>
#include <wifi_station.h>
#include <esp_lcd_panel_vendor.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include <esp_timer.h>
#include "esp_io_expander_tca9554.h"
#include "axp2101.h"
#include "power_save_timer.h"
#define TAG "waveshare_lcd_3_5"
LV_FONT_DECLARE(font_puhui_16_4);
LV_FONT_DECLARE(font_awesome_16_4);
class Pmic : public Axp2101 {
public:
Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) {
WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable
WriteReg(0x27, 0x10); // hold 4s to power off
// Disable All DCs but DC1
WriteReg(0x80, 0x01);
// Disable All LDOs
WriteReg(0x90, 0x00);
WriteReg(0x91, 0x00);
// Set DC1 to 3.3V
WriteReg(0x82, (3300 - 1500) / 100);
// Set ALDO1 to 3.3V
WriteReg(0x92, (3300 - 500) / 100);
// Enable ALDO1(MIC)
WriteReg(0x90, 0x01);
WriteReg(0x64, 0x02); // CV charger voltage setting to 4.1V
WriteReg(0x61, 0x02); // set Main battery precharge current to 50mA
WriteReg(0x62, 0x08); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA )
WriteReg(0x63, 0x01); // set Main battery term charge current to 25mA
}
};
typedef struct {
int cmd; /*<! The specific LCD command */
const void *data; /*<! Buffer that holds the command specific data */
size_t data_bytes; /*<! Size of `data` in memory, in bytes */
unsigned int delay_ms; /*<! Delay in milliseconds after this command */
} st7796_lcd_init_cmd_t;
typedef struct {
const st7796_lcd_init_cmd_t *init_cmds; /*!< Pointer to initialization commands array. Set to NULL if using default commands.
* The array should be declared as `static const` and positioned outside the function.
* Please refer to `vendor_specific_init_default` in source file.
*/
uint16_t init_cmds_size; /*<! Number of commands in above array */
} st7796_vendor_config_t;
st7796_lcd_init_cmd_t st7796_lcd_init_cmds[] = {
{0x11, (uint8_t []){ 0x00 }, 0, 120},
// {0x36, (uint8_t []){ 0x08 }, 1, 0},
{0x3A, (uint8_t []){ 0x05 }, 1, 0},
{0xF0, (uint8_t []){ 0xC3 }, 1, 0},
{0xF0, (uint8_t []){ 0x96 }, 1, 0},
{0xB4, (uint8_t []){ 0x01 }, 1, 0},
{0xB7, (uint8_t []){ 0xC6 }, 1, 0},
{0xC0, (uint8_t []){ 0x80, 0x45 }, 2, 0},
{0xC1, (uint8_t []){ 0x13 }, 1, 0},
{0xC2, (uint8_t []){ 0xA7 }, 1, 0},
{0xC5, (uint8_t []){ 0x0A }, 1, 0},
{0xE8, (uint8_t []){ 0x40, 0x8A, 0x00, 0x00, 0x29, 0x19, 0xA5, 0x33}, 8, 0},
{0xE0, (uint8_t []){ 0xD0, 0x08, 0x0F, 0x06, 0x06, 0x33, 0x30, 0x33, 0x47, 0x17, 0x13, 0x13, 0x2B, 0x31}, 14, 0},
{0xE1, (uint8_t []){ 0xD0, 0x0A, 0x11, 0x0B, 0x09, 0x07, 0x2F, 0x33, 0x47, 0x38, 0x15, 0x16, 0x2C, 0x32},14, 0},
{0xF0, (uint8_t []){ 0x3C }, 1, 0},
{0xF0, (uint8_t []){ 0x69 }, 1, 120},
{0x21, (uint8_t []){ 0x00 }, 0, 0},
{0x29, (uint8_t []){ 0x00 }, 0, 0},
};
class CustomBoard : public WifiBoard {
private:
Button boot_button_;
Pmic* pmic_ = nullptr;
i2c_master_bus_handle_t i2c_bus_;
esp_io_expander_handle_t io_expander = NULL;
LcdDisplay* display_;
PowerSaveTimer* power_save_timer_;
void InitializePowerSaveTimer() {
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
power_save_timer_->OnEnterSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("sleepy");
GetBacklight()->SetBrightness(20);
});
power_save_timer_->OnExitSleepMode([this]() {
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("neutral");
GetBacklight()->RestoreBrightness();
});
power_save_timer_->OnShutdownRequest([this]() {
pmic_->PowerOff();
});
power_save_timer_->SetEnabled(true);
}
void InitializeI2c() {
// Initialize I2C peripheral
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = (i2c_port_t)0,
.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN,
.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN,
.clk_source = I2C_CLK_SRC_DEFAULT,
};
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
}
void InitializeTca9554(void)
{
esp_err_t ret = esp_io_expander_new_i2c_tca9554(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000, &io_expander);
if(ret != ESP_OK)
ESP_LOGE(TAG, "TCA9554 create returned error");
ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, IO_EXPANDER_OUTPUT);
ESP_ERROR_CHECK(ret);
vTaskDelay(pdMS_TO_TICKS(100));
ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_1, 0);
ESP_ERROR_CHECK(ret);
vTaskDelay(pdMS_TO_TICKS(100));
ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 1);
ESP_ERROR_CHECK(ret);
}
void InitializeAxp2101() {
ESP_LOGI(TAG, "Init AXP2101");
pmic_ = new Pmic(i2c_bus_, 0x34);
}
void InitializeSpi() {
ESP_LOGI(TAG, "Initialize QSPI bus");
spi_bus_config_t buscfg = {};
buscfg.mosi_io_num = DISPLAY_MOSI_PIN;
buscfg.miso_io_num = DISPLAY_MISO_PIN;
buscfg.sclk_io_num = DISPLAY_CLK_PIN;
buscfg.quadwp_io_num = GPIO_NUM_NC;
buscfg.quadhd_io_num = GPIO_NUM_NC;
buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t);
ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
}
void InitializeLcdDisplay() {
esp_lcd_panel_io_handle_t panel_io = nullptr;
esp_lcd_panel_handle_t panel = nullptr;
// 液晶屏控制IO初始化
ESP_LOGI(TAG, "Install panel IO");
esp_lcd_panel_io_spi_config_t io_config = {};
io_config.cs_gpio_num = DISPLAY_CS_PIN;
io_config.dc_gpio_num = DISPLAY_DC_PIN;
io_config.spi_mode = DISPLAY_SPI_MODE;
io_config.pclk_hz = 40 * 1000 * 1000;
io_config.trans_queue_depth = 10;
io_config.lcd_cmd_bits = 8;
io_config.lcd_param_bits = 8;
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io));
st7796_vendor_config_t st7796_vendor_config = {
.init_cmds = st7796_lcd_init_cmds,
.init_cmds_size = sizeof(st7796_lcd_init_cmds) / sizeof(st7796_lcd_init_cmd_t),
};
// 初始化液晶屏驱动芯片
ESP_LOGI(TAG, "Install LCD driver");
esp_lcd_panel_dev_config_t panel_config = {};
panel_config.reset_gpio_num = DISPLAY_RST_PIN;
panel_config.rgb_ele_order = DISPLAY_RGB_ORDER;
panel_config.bits_per_pixel = 16;
panel_config.vendor_config = &st7796_vendor_config;
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
esp_lcd_panel_reset(panel);
esp_lcd_panel_init(panel);
esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR);
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
display_ = new SpiLcdDisplay(panel_io, panel,
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY,
{
.text_font = &font_puhui_16_4,
.icon_font = &font_awesome_16_4,
.emoji_font = font_emoji_32_init(),
});
}
void InitializeButtons() {
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
app.ToggleChatState();
});
}
// 物联网初始化,添加对 AI 可见设备
void InitializeIot() {
auto& thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
thing_manager.AddThing(iot::CreateThing("Screen"));
thing_manager.AddThing(iot::CreateThing("Battery"));
thing_manager.AddThing(iot::CreateThing("BoardControl"));
}
public:
CustomBoard() :
boot_button_(BOOT_BUTTON_GPIO) {
InitializePowerSaveTimer();
InitializeI2c();
InitializeTca9554();
InitializeAxp2101();
InitializeSpi();
InitializeLcdDisplay();
InitializeButtons();
InitializeIot();
GetBacklight()->RestoreBrightness();
}
virtual AudioCodec* GetAudioCodec() override {
static Es8311AudioCodec audio_codec(i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN,
AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR);
return &audio_codec;
}
virtual Display* GetDisplay() override {
return display_;
}
virtual Backlight* GetBacklight() override {
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
return &backlight;
}
virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override {
static bool last_discharging = false;
charging = pmic_->IsCharging();
discharging = pmic_->IsDischarging();
if (discharging != last_discharging) {
power_save_timer_->SetEnabled(discharging);
last_discharging = discharging;
}
level = pmic_->GetBatteryLevel();
return true;
}
virtual void SetPowerSaveMode(bool enabled) override {
if (!enabled) {
power_save_timer_->WakeUp();
}
WifiBoard::SetPowerSaveMode(enabled);
}
};
DECLARE_BOARD(CustomBoard);

View File

@@ -26,6 +26,7 @@
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
#ifdef CONFIG_LCD_ST7789
#define DISPLAY_SDA_PIN GPIO_NUM_NC
#define DISPLAY_SCL_PIN GPIO_NUM_NC
#define DISPLAY_WIDTH 280
@@ -37,6 +38,23 @@
#define DISPLAY_OFFSET_X 20
#define DISPLAY_OFFSET_Y 0
#endif
#ifdef CONFIG_LCD_ILI9341
#define LCD_TYPE_ILI9341_SERIAL
#define DISPLAY_SDA_PIN GPIO_NUM_NC
#define DISPLAY_SCL_PIN GPIO_NUM_NC
#define DISPLAY_WIDTH 320
#define DISPLAY_HEIGHT 240
#define DISPLAY_SWAP_XY false
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y true
#define BACKLIGHT_INVERT false
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#endif
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false

View File

@@ -3,7 +3,6 @@
#include "display/lcd_display.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "i2c_device.h"
#include "iot/thing_manager.h"
@@ -11,6 +10,7 @@
#include <esp_log.h>
#include <esp_lcd_panel_vendor.h>
#include <esp_io_expander_tca9554.h>
#include <esp_lcd_ili9341.h>
#include <driver/i2c_master.h>
#include <driver/spi_common.h>
#include <wifi_station.h>
@@ -20,6 +20,28 @@
LV_FONT_DECLARE(font_puhui_20_4);
LV_FONT_DECLARE(font_awesome_20_4);
// Init ili9341 by custom cmd
static const ili9341_lcd_init_cmd_t vendor_specific_init[] = {
{0xC8, (uint8_t []){0xFF, 0x93, 0x42}, 3, 0},
{0xC0, (uint8_t []){0x0E, 0x0E}, 2, 0},
{0xC5, (uint8_t []){0xD0}, 1, 0},
{0xC1, (uint8_t []){0x02}, 1, 0},
{0xB4, (uint8_t []){0x02}, 1, 0},
{0xE0, (uint8_t []){0x00, 0x03, 0x08, 0x06, 0x13, 0x09, 0x39, 0x39, 0x48, 0x02, 0x0a, 0x08, 0x17, 0x17, 0x0F}, 15, 0},
{0xE1, (uint8_t []){0x00, 0x28, 0x29, 0x01, 0x0d, 0x03, 0x3f, 0x33, 0x52, 0x04, 0x0f, 0x0e, 0x37, 0x38, 0x0F}, 15, 0},
{0xB1, (uint8_t []){00, 0x1B}, 2, 0},
{0x36, (uint8_t []){0x08}, 1, 0},
{0x3A, (uint8_t []){0x55}, 1, 0},
{0xB7, (uint8_t []){0x06}, 1, 0},
{0x11, (uint8_t []){0}, 0x80, 0},
{0x29, (uint8_t []){0}, 0x80, 0},
{0, (uint8_t []){0}, 0xff, 0},
};
class Esp32S3Korvo2V3Board : public WifiBoard {
private:
Button boot_button_;
@@ -123,13 +145,60 @@ private:
});
}
void InitializeIli9341Display() {
esp_lcd_panel_io_handle_t panel_io = nullptr;
esp_lcd_panel_handle_t panel = nullptr;
// 液晶屏控制IO初始化
ESP_LOGD(TAG, "Install panel IO");
esp_lcd_panel_io_spi_config_t io_config = {};
io_config.cs_gpio_num = GPIO_NUM_NC;
io_config.dc_gpio_num = GPIO_NUM_2;
io_config.spi_mode = 0;
io_config.pclk_hz = 40 * 1000 * 1000;
io_config.trans_queue_depth = 10;
io_config.lcd_cmd_bits = 8;
io_config.lcd_param_bits = 8;
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io));
// 初始化液晶屏驱动芯片
ESP_LOGD(TAG, "Install LCD driver");
const ili9341_vendor_config_t vendor_config = {
.init_cmds = &vendor_specific_init[0],
.init_cmds_size = sizeof(vendor_specific_init) / sizeof(ili9341_lcd_init_cmd_t),
};
esp_lcd_panel_dev_config_t panel_config = {};
panel_config.reset_gpio_num = GPIO_NUM_NC;
// panel_config.flags.reset_active_high = 0,
panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
panel_config.bits_per_pixel = 16;
panel_config.vendor_config = (void *)&vendor_config;
ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel));
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel));
EnableLcdCs();
ESP_ERROR_CHECK(esp_lcd_panel_init(panel));
ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY));
ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y));
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, false));
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true));
display_ = new SpiLcdDisplay(panel_io, panel,
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY,
{
.text_font = &font_puhui_20_4,
.icon_font = &font_awesome_20_4,
.emoji_font = font_emoji_64_init(),
});
}
void InitializeSt7789Display() {
esp_lcd_panel_io_handle_t panel_io = nullptr;
esp_lcd_panel_handle_t panel = nullptr;
// 液晶屏控制IO初始化
ESP_LOGD(TAG, "Install panel IO");
esp_lcd_panel_io_spi_config_t io_config = {};
io_config.cs_gpio_num = GPIO_NUM_NC;//酷世diy的korvo板子上cs引脚为GPIO46 官方korvo2 v3的lcd cs引脚由TCA9554的IO3控制 所以这里设置为GPIO_NUM_NC
io_config.cs_gpio_num = GPIO_NUM_46;
io_config.dc_gpio_num = GPIO_NUM_2;
io_config.spi_mode = 0;
io_config.pclk_hz = 60 * 1000 * 1000;
@@ -176,7 +245,11 @@ public:
InitializeTca9554();
InitializeSpi();
InitializeButtons();
InitializeSt7789Display();
#ifdef LCD_TYPE_ILI9341_SERIAL
InitializeIli9341Display();
#else
InitializeSt7789Display();
#endif
InitializeIot();
}

View File

@@ -250,21 +250,16 @@ public:
return display_;
}
virtual bool GetBatteryLevel(int &level, bool& charging) override {
static bool last_charging = false;
virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override {
static bool last_discharging = false;
charging = pmic_->IsCharging();
if (charging != last_charging) {
power_save_timer_->WakeUp();
last_charging = charging;
discharging = pmic_->IsDischarging();
if (discharging != last_discharging) {
power_save_timer_->SetEnabled(discharging);
last_discharging = discharging;
}
level = pmic_->GetBatteryLevel();
if (pmic_->IsDischarging()) {
power_save_timer_->SetEnabled(true);
} else {
power_save_timer_->SetEnabled(false);
}
return true;
}
};

View File

@@ -5,6 +5,7 @@
#include "config.h"
#include "iot/thing_manager.h"
#include "led/circular_strip.h"
#include "led_strip_control.h"
#include <wifi_station.h>
#include <esp_log.h>
@@ -17,6 +18,7 @@ class KevinBoxBoard : public WifiBoard {
private:
i2c_master_bus_handle_t codec_i2c_bus_;
Button boot_button_;
CircularStrip* led_strip_;
void InitializeCodecI2c() {
// Initialize I2C peripheral
@@ -54,6 +56,10 @@ private:
void InitializeIot() {
auto& thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
led_strip_ = new CircularStrip(BUILTIN_LED_GPIO, 8);
auto led_strip_control = new LedStripControl(led_strip_);
thing_manager.AddThing(led_strip_control);
}
public:
@@ -67,8 +73,7 @@ public:
}
virtual Led* GetLed() override {
static CircularStrip led(BUILTIN_LED_GPIO, 8);
return &led;
return led_strip_;
}
virtual AudioCodec* GetAudioCodec() override {

View File

@@ -0,0 +1,123 @@
#include "led_strip_control.h"
#include "settings.h"
#include <esp_log.h>
#define TAG "LedStripControl"
int LedStripControl::LevelToBrightness(int level) const {
if (level < 0) level = 0;
if (level > 8) level = 8;
return (1 << level) - 1; // 2^n - 1
}
StripColor LedStripControl::RGBToColor(int red, int green, int blue) {
if (red < 0) red = 0;
if (red > 255) red = 255;
if (green < 0) green = 0;
if (green > 255) green = 255;
if (blue < 0) blue = 0;
if (blue > 255) blue = 255;
return {static_cast<uint8_t>(red), static_cast<uint8_t>(green), static_cast<uint8_t>(blue)};
}
LedStripControl::LedStripControl(CircularStrip* led_strip)
: Thing("LedStripControl", "LED 灯带控制一共有8个灯珠"), led_strip_(led_strip) {
// 从设置中读取亮度等级
Settings settings("led_strip");
brightness_level_ = settings.GetInt("brightness", 4); // 默认等级4
led_strip_->SetBrightness(LevelToBrightness(brightness_level_), 4);
// 定义设备的属性
properties_.AddNumberProperty("brightness", "对话时的亮度等级(0-8)", [this]() -> int {
return brightness_level_;
});
// 定义设备可以被远程执行的指令
methods_.AddMethod("SetBrightness", "设置对话时的亮度等级", ParameterList({
Parameter("level", "亮度等级(0-8)", kValueTypeNumber, true)
}), [this](const ParameterList& parameters) {
int level = static_cast<int>(parameters["level"].number());
ESP_LOGI(TAG, "Set LedStrip brightness level to %d", level);
if (level < 0) level = 0;
if (level > 8) level = 8;
brightness_level_ = level;
led_strip_->SetBrightness(LevelToBrightness(brightness_level_), 4);
// 保存设置
Settings settings("led_strip", true);
settings.SetInt("brightness", brightness_level_);
});
methods_.AddMethod("SetSingleColor", "设置单个灯颜色", ParameterList({
Parameter("index", "灯珠索引0-7", kValueTypeNumber, true),
Parameter("red", "红色0-255", kValueTypeNumber, true),
Parameter("green", "绿色0-255", kValueTypeNumber, true),
Parameter("blue", "蓝色0-255", kValueTypeNumber, true)
}), [this](const ParameterList& parameters) {
int index = parameters["index"].number();
StripColor color = RGBToColor(
parameters["red"].number(),
parameters["green"].number(),
parameters["blue"].number()
);
ESP_LOGI(TAG, "Set led strip single color %d to %d, %d, %d",
index, color.red, color.green, color.blue);
led_strip_->SetSingleColor(index, color);
});
methods_.AddMethod("SetAllColor", "设置所有灯颜色", ParameterList({
Parameter("red", "红色0-255", kValueTypeNumber, true),
Parameter("green", "绿色0-255", kValueTypeNumber, true),
Parameter("blue", "蓝色0-255", kValueTypeNumber, true)
}), [this](const ParameterList& parameters) {
StripColor color = RGBToColor(
parameters["red"].number(),
parameters["green"].number(),
parameters["blue"].number()
);
ESP_LOGI(TAG, "Set led strip color to %d, %d, %d",
color.red, color.green, color.blue
);
led_strip_->SetAllColor(color);
});
methods_.AddMethod("Blink", "闪烁动画", ParameterList({
Parameter("red", "红色0-255", kValueTypeNumber, true),
Parameter("green", "绿色0-255", kValueTypeNumber, true),
Parameter("blue", "蓝色0-255", kValueTypeNumber, true),
Parameter("interval", "间隔(ms)", kValueTypeNumber, true)
}), [this](const ParameterList& parameters) {
int interval = parameters["interval"].number();
StripColor color = RGBToColor(
parameters["red"].number(),
parameters["green"].number(),
parameters["blue"].number()
);
ESP_LOGI(TAG, "Blink led strip with color %d, %d, %d, interval %dms",
color.red, color.green, color.blue, interval);
led_strip_->Blink(color, interval);
});
methods_.AddMethod("Scroll", "跑马灯动画", ParameterList({
Parameter("red", "红色0-255", kValueTypeNumber, true),
Parameter("green", "绿色0-255", kValueTypeNumber, true),
Parameter("blue", "蓝色0-255", kValueTypeNumber, true),
Parameter("length", "滚动条长度1-7", kValueTypeNumber, true),
Parameter("interval", "间隔(ms)", kValueTypeNumber, true)
}), [this](const ParameterList& parameters) {
int interval = parameters["interval"].number();
int length = parameters["length"].number();
StripColor low = RGBToColor(4, 4, 4);
StripColor high = RGBToColor(
parameters["red"].number(),
parameters["green"].number(),
parameters["blue"].number()
);
ESP_LOGI(TAG, "Scroll led strip with color %d, %d, %d, length %d, interval %dms",
high.red, high.green, high.blue, length, interval);
led_strip_->Scroll(low, high, length, interval);
});
}

View File

@@ -0,0 +1,21 @@
#ifndef LED_STRIP_CONTROL_H
#define LED_STRIP_CONTROL_H
#include "iot/thing.h"
#include "led/circular_strip.h"
using namespace iot;
class LedStripControl : public Thing {
private:
CircularStrip* led_strip_;
int brightness_level_; // 亮度等级 (0-8)
int LevelToBrightness(int level) const; // 将等级转换为实际亮度值
StripColor RGBToColor(int red, int green, int blue);
public:
explicit LedStripControl(CircularStrip* led_strip);
};
#endif // LED_STRIP_CONTROL_H

View File

@@ -95,8 +95,8 @@ private:
void InitializeIot() {
auto& thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
thing_manager.AddThing(iot::CreateThing("Screen"));
thing_manager.AddThing(iot::CreateThing("Lamp"));
thing_manager.AddThing(iot::CreateThing("Backlight"));
}
public:

View File

@@ -108,8 +108,8 @@ private:
void InitializeIot() {
auto& thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
thing_manager.AddThing(iot::CreateThing("Screen"));
thing_manager.AddThing(iot::CreateThing("Lamp"));
thing_manager.AddThing(iot::CreateThing("Backlight"));
}
public:

View File

@@ -122,7 +122,7 @@ esp_err_t esp_lcd_new_panel_gc9503(const esp_lcd_panel_io_handle_t io, const esp
*/
#define GC9503_376_960_PANEL_60HZ_RGB_TIMING() \
{ \
.pclk_hz = 20 * 1000 * 1000, \
.pclk_hz = 16 * 1000 * 1000, \
.h_res = 376, \
.v_res = 960, \
.hsync_pulse_width = 8, \

View File

@@ -147,7 +147,7 @@ private:
void InitializeIot() {
auto& thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
thing_manager.AddThing(iot::CreateThing("Backlight"));
thing_manager.AddThing(iot::CreateThing("Screen"));
}
public:

View File

@@ -5,7 +5,6 @@
#define AUDIO_INPUT_SAMPLE_RATE 24000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
#define AUDIO_INPUT_REFERENCE true
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_10
#define AUDIO_I2S_GPIO_WS GPIO_NUM_12

View File

@@ -104,7 +104,7 @@ private:
void InitializeIot() {
auto& thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
thing_manager.AddThing(iot::CreateThing("Backlight"));
thing_manager.AddThing(iot::CreateThing("Screen"));
}
public:

View File

@@ -122,7 +122,11 @@ private:
{
.text_font = &font_puhui_20_4,
.icon_font = &font_awesome_20_4,
#if CONFIG_USE_WECHAT_MESSAGE_STYLE
.emoji_font = font_emoji_32_init(),
#else
.emoji_font = font_emoji_64_init(),
#endif
});
}
@@ -130,7 +134,7 @@ private:
void InitializeIot() {
auto& thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
thing_manager.AddThing(iot::CreateThing("Backlight"));
thing_manager.AddThing(iot::CreateThing("Screen"));
}
public:

View File

@@ -208,7 +208,7 @@ private:
void InitializeIot() {
auto &thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
thing_manager.AddThing(iot::CreateThing("Backlight"));
thing_manager.AddThing(iot::CreateThing("Screen"));
}
public:

View File

@@ -226,7 +226,7 @@ private:
void InitializeIot() {
auto &thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
thing_manager.AddThing(iot::CreateThing("Backlight"));
thing_manager.AddThing(iot::CreateThing("Screen"));
}
public:

View File

@@ -309,7 +309,7 @@ private:
void InitializeIot() {
auto& thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
thing_manager.AddThing(iot::CreateThing("Backlight"));
thing_manager.AddThing(iot::CreateThing("Screen"));
thing_manager.AddThing(iot::CreateThing("Battery"));
}
@@ -346,21 +346,16 @@ public:
return display_;
}
virtual bool GetBatteryLevel(int &level, bool& charging) override {
static bool last_charging = false;
virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override {
static bool last_discharging = false;
charging = pmic_->IsCharging();
if (charging != last_charging) {
power_save_timer_->WakeUp();
last_charging = charging;
discharging = pmic_->IsDischarging();
if (discharging != last_discharging) {
power_save_timer_->SetEnabled(discharging);
last_discharging = discharging;
}
level = pmic_->GetBatteryLevel();
if (pmic_->IsDischarging()) {
power_save_timer_->SetEnabled(true);
} else {
power_save_timer_->SetEnabled(false);
}
return true;
}

View File

@@ -180,7 +180,7 @@ private:
void InitializeIot() {
auto& thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
thing_manager.AddThing(iot::CreateThing("Backlight"));
thing_manager.AddThing(iot::CreateThing("Screen"));
}
public:

View File

@@ -224,7 +224,7 @@ private:
void InitializeIot() {
auto& thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
thing_manager.AddThing(iot::CreateThing("Backlight"));
thing_manager.AddThing(iot::CreateThing("Screen"));
}
public:

View File

@@ -211,7 +211,7 @@ private:
void InitializeIot() {
auto& thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
thing_manager.AddThing(iot::CreateThing("Backlight"));
thing_manager.AddThing(iot::CreateThing("Screen"));
}
public:

View File

@@ -169,7 +169,7 @@ private:
void InitializeIot() {
auto& thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
thing_manager.AddThing(iot::CreateThing("Backlight"));
thing_manager.AddThing(iot::CreateThing("Screen"));
}
public:

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