diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4ae33e55d..b832709a0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -65,8 +65,10 @@ jobs: { id: m5stack-core2, arch: esp32 }, { id: m5stack-cores3, arch: esp32s3 }, { id: m5stack-papers3, arch: esp32s3 }, + { id: m5stack-stackchan, arch: esp32s3 }, { id: m5stack-stickc-plus, arch: esp32 }, { id: m5stack-stickc-plus2, arch: esp32 }, + { id: m5stack-sticks3, arch: esp32s3 }, { id: m5stack-tab5, arch: esp32p4 }, { id: unphone, arch: esp32s3 }, { id: waveshare-esp32-s3-geek, arch: esp32s3 }, diff --git a/Buildscripts/DevicetreeCompiler/source/generator.py b/Buildscripts/DevicetreeCompiler/source/generator.py index 9f7d79171..bbbd3dc36 100644 --- a/Buildscripts/DevicetreeCompiler/source/generator.py +++ b/Buildscripts/DevicetreeCompiler/source/generator.py @@ -256,6 +256,15 @@ def generate_devicetree_c(filename: str, items: list[object], bindings: list[Bin file.write("};\n") # Gather module symbols module_symbol_names = [] + # Device's own module goes first (started before its dependency modules) + if config.dts: + device_dir = os.path.dirname(os.path.normpath(config.dts)) + device_name = os.path.basename(device_dir) + if device_name: + device_module_name = device_name.replace('-', '_') + if not device_module_name.endswith("_module"): + device_module_name += "_module" + module_symbol_names.append(device_module_name) for dependency in config.dependencies: dependency_name = os.path.basename(os.path.normpath(dependency)) module_symbol_name = f"{dependency_name.replace('-', '_')}" diff --git a/Buildscripts/DevicetreeCompiler/tests/data/expected_devicetree.c b/Buildscripts/DevicetreeCompiler/tests/data/expected_devicetree.c index 28448359f..207ecff44 100644 --- a/Buildscripts/DevicetreeCompiler/tests/data/expected_devicetree.c +++ b/Buildscripts/DevicetreeCompiler/tests/data/expected_devicetree.c @@ -51,7 +51,9 @@ struct DtsDevice dts_devices[] = { DTS_DEVICE_TERMINATOR }; +extern struct Module data_module; struct Module* dts_modules[] = { + &data_module, NULL }; diff --git a/Buildscripts/TactilitySDK/CMakeLists.txt b/Buildscripts/TactilitySDK/CMakeLists.txt index 7fd6e152b..7e757c19e 100644 --- a/Buildscripts/TactilitySDK/CMakeLists.txt +++ b/Buildscripts/TactilitySDK/CMakeLists.txt @@ -3,6 +3,13 @@ idf_component_register( "Libraries/TactilityC/include" "Libraries/TactilityKernel/include" "Libraries/lvgl/include" + "Modules/lvgl-module/include" + "Drivers/bm8563-module/include" + "Drivers/bmi270-module/include" + "Drivers/mpu6886-module/include" + "Drivers/pi4ioe5v6408-module/include" + "Drivers/qmi8658-module/include" + "Drivers/rx8130ce-module/include" REQUIRES esp_timer ) diff --git a/Devices/btt-panda-touch/bigtreetech,panda-touch.dts b/Devices/btt-panda-touch/bigtreetech,panda-touch.dts index 0c42d851a..36c9646b9 100644 --- a/Devices/btt-panda-touch/bigtreetech,panda-touch.dts +++ b/Devices/btt-panda-touch/bigtreetech,panda-touch.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include @@ -9,6 +10,10 @@ compatible = "root"; model = "BigTreeTech Panda Touch"; + ble0 { + compatible = "espressif,esp32-ble"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <49>; diff --git a/Devices/btt-panda-touch/device.properties b/Devices/btt-panda-touch/device.properties index a1d965ae6..dd58ccb82 100644 --- a/Devices/btt-panda-touch/device.properties +++ b/Devices/btt-panda-touch/device.properties @@ -12,6 +12,7 @@ spiRam=true spiRamMode=OCT spiRamSpeed=120M esptoolFlashFreq=120M +bluetooth=true [display] size=5" diff --git a/Devices/cyd-4848s040c/cyd,4848s040c.dts b/Devices/cyd-4848s040c/cyd,4848s040c.dts index bc1cff4ff..06dffdbd3 100644 --- a/Devices/cyd-4848s040c/cyd,4848s040c.dts +++ b/Devices/cyd-4848s040c/cyd,4848s040c.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -9,6 +10,10 @@ compatible = "root"; model = "CYD 4848S040C"; + ble0 { + compatible = "espressif,esp32-ble"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <49>; diff --git a/Devices/cyd-4848s040c/device.properties b/Devices/cyd-4848s040c/device.properties index 26e6a8eed..1520827d1 100644 --- a/Devices/cyd-4848s040c/device.properties +++ b/Devices/cyd-4848s040c/device.properties @@ -11,6 +11,7 @@ flashSize=16MB spiRam=true spiRamMode=OCT spiRamSpeed=80M +bluetooth=true [display] size=4" diff --git a/Devices/cyd-8048s043c/cyd,8048s043c.dts b/Devices/cyd-8048s043c/cyd,8048s043c.dts index 54cc8116c..9054bf0bc 100644 --- a/Devices/cyd-8048s043c/cyd,8048s043c.dts +++ b/Devices/cyd-8048s043c/cyd,8048s043c.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -10,6 +11,10 @@ compatible = "root"; model = "CYD 8048S043C"; + ble0 { + compatible = "espressif,esp32-ble"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <49>; diff --git a/Devices/cyd-8048s043c/device.properties b/Devices/cyd-8048s043c/device.properties index 80114bee8..54d406cf3 100644 --- a/Devices/cyd-8048s043c/device.properties +++ b/Devices/cyd-8048s043c/device.properties @@ -13,6 +13,7 @@ spiRam=true spiRamMode=OCT spiRamSpeed=80M esptoolFlashFreq=80M +bluetooth=true [display] size=4.3" diff --git a/Devices/elecrow-crowpanel-advance-28/device.properties b/Devices/elecrow-crowpanel-advance-28/device.properties index 060b73340..fad2d476b 100644 --- a/Devices/elecrow-crowpanel-advance-28/device.properties +++ b/Devices/elecrow-crowpanel-advance-28/device.properties @@ -13,6 +13,7 @@ spiRamMode=OCT spiRamSpeed=120M tinyUsb=true esptoolFlashFreq=120M +bluetooth=true [display] size=2.8" diff --git a/Devices/elecrow-crowpanel-advance-28/elecrow,crowpanel-advance-28.dts b/Devices/elecrow-crowpanel-advance-28/elecrow,crowpanel-advance-28.dts index dddea22e6..b0b50de79 100644 --- a/Devices/elecrow-crowpanel-advance-28/elecrow,crowpanel-advance-28.dts +++ b/Devices/elecrow-crowpanel-advance-28/elecrow,crowpanel-advance-28.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -10,6 +11,10 @@ compatible = "root"; model = "Elecrow CrowPanel Advance 2.8"; + ble0 { + compatible = "espressif,esp32-ble"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <49>; diff --git a/Devices/elecrow-crowpanel-advance-35/device.properties b/Devices/elecrow-crowpanel-advance-35/device.properties index 6722c43f7..5ce9900a3 100644 --- a/Devices/elecrow-crowpanel-advance-35/device.properties +++ b/Devices/elecrow-crowpanel-advance-35/device.properties @@ -13,6 +13,7 @@ spiRamMode=OCT spiRamSpeed=120M tinyUsb=true esptoolFlashFreq=120M +bluetooth=true [display] size=3.5" diff --git a/Devices/elecrow-crowpanel-advance-35/elecrow,crowpanel-advance-35.dts b/Devices/elecrow-crowpanel-advance-35/elecrow,crowpanel-advance-35.dts index c66bac39b..f2abee9cf 100644 --- a/Devices/elecrow-crowpanel-advance-35/elecrow,crowpanel-advance-35.dts +++ b/Devices/elecrow-crowpanel-advance-35/elecrow,crowpanel-advance-35.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -10,6 +11,10 @@ compatible = "root"; model = "Elecrow CrowPanel Advance 3.5"; + ble0 { + compatible = "espressif,esp32-ble"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <49>; diff --git a/Devices/elecrow-crowpanel-advance-50/device.properties b/Devices/elecrow-crowpanel-advance-50/device.properties index f12aba6f0..ddf3b78ea 100644 --- a/Devices/elecrow-crowpanel-advance-50/device.properties +++ b/Devices/elecrow-crowpanel-advance-50/device.properties @@ -13,6 +13,7 @@ spiRamMode=OCT spiRamSpeed=120M tinyUsb=true esptoolFlashFreq=120M +bluetooth=true [display] size=5" diff --git a/Devices/elecrow-crowpanel-advance-50/elecrow,crowpanel-advance-50.dts b/Devices/elecrow-crowpanel-advance-50/elecrow,crowpanel-advance-50.dts index cb61e9826..17d236d36 100644 --- a/Devices/elecrow-crowpanel-advance-50/elecrow,crowpanel-advance-50.dts +++ b/Devices/elecrow-crowpanel-advance-50/elecrow,crowpanel-advance-50.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -10,6 +11,10 @@ compatible = "root"; model = "Elecrow CrowPanel Advance 5.0"; + ble0 { + compatible = "espressif,esp32-ble"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <49>; diff --git a/Devices/elecrow-crowpanel-basic-50/device.properties b/Devices/elecrow-crowpanel-basic-50/device.properties index 6372dc6b6..529b1067b 100644 --- a/Devices/elecrow-crowpanel-basic-50/device.properties +++ b/Devices/elecrow-crowpanel-basic-50/device.properties @@ -13,6 +13,7 @@ spiRamMode=OCT spiRamSpeed=120M tinyUsb=true esptoolFlashFreq=120M +bluetooth=true [display] size=5.0" diff --git a/Devices/elecrow-crowpanel-basic-50/elecrow,crowpanel-basic-50.dts b/Devices/elecrow-crowpanel-basic-50/elecrow,crowpanel-basic-50.dts index 69f851f5b..e35ecd568 100644 --- a/Devices/elecrow-crowpanel-basic-50/elecrow,crowpanel-basic-50.dts +++ b/Devices/elecrow-crowpanel-basic-50/elecrow,crowpanel-basic-50.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -10,6 +11,10 @@ compatible = "root"; model = "Elecrow CrowPanel Basic 5.0"; + ble0 { + compatible = "espressif,esp32-ble"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <49>; diff --git a/Devices/guition-jc1060p470ciwy/device.properties b/Devices/guition-jc1060p470ciwy/device.properties index 85396ac10..418658815 100644 --- a/Devices/guition-jc1060p470ciwy/device.properties +++ b/Devices/guition-jc1060p470ciwy/device.properties @@ -12,6 +12,7 @@ spiRam=true spiRamMode=OCT spiRamSpeed=200M esptoolFlashFreq=80M +bluetooth=true [display] size=7" diff --git a/Devices/guition-jc1060p470ciwy/guition,jc1060p470ciwy.dts b/Devices/guition-jc1060p470ciwy/guition,jc1060p470ciwy.dts index 52c20fb55..e328c0ee7 100644 --- a/Devices/guition-jc1060p470ciwy/guition,jc1060p470ciwy.dts +++ b/Devices/guition-jc1060p470ciwy/guition,jc1060p470ciwy.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -17,6 +18,10 @@ compatible = "root"; model = "Guition JC1060P470C-I-W-Y"; + ble0 { + compatible = "espressif,esp32-ble"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <57>; diff --git a/Devices/guition-jc3248w535c/device.properties b/Devices/guition-jc3248w535c/device.properties index 9032aebf8..c1f3a08ae 100644 --- a/Devices/guition-jc3248w535c/device.properties +++ b/Devices/guition-jc3248w535c/device.properties @@ -13,6 +13,7 @@ spiRamMode=OCT spiRamSpeed=120M tinyUsb=true esptoolFlashFreq=120M +bluetooth=true [display] size=3.5" diff --git a/Devices/guition-jc3248w535c/guition,jc3248w535c.dts b/Devices/guition-jc3248w535c/guition,jc3248w535c.dts index ecee4c1a1..4749ddc35 100644 --- a/Devices/guition-jc3248w535c/guition,jc3248w535c.dts +++ b/Devices/guition-jc3248w535c/guition,jc3248w535c.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -11,6 +12,10 @@ compatible = "root"; model = "Guition JC3248W535C"; + ble0 { + compatible = "espressif,esp32-ble"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <49>; diff --git a/Devices/guition-jc8048w550c/device.properties b/Devices/guition-jc8048w550c/device.properties index e405ed7b6..0dda312f1 100644 --- a/Devices/guition-jc8048w550c/device.properties +++ b/Devices/guition-jc8048w550c/device.properties @@ -12,6 +12,7 @@ spiRam=true spiRamMode=OCT spiRamSpeed=80M esptoolFlashFreq=80M +bluetooth=true [display] size=5" diff --git a/Devices/guition-jc8048w550c/guition,jc8048w550c.dts b/Devices/guition-jc8048w550c/guition,jc8048w550c.dts index 253a995bf..97ac59477 100644 --- a/Devices/guition-jc8048w550c/guition,jc8048w550c.dts +++ b/Devices/guition-jc8048w550c/guition,jc8048w550c.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -11,6 +12,10 @@ compatible = "root"; model = "Guition JC8048W550C"; + ble0 { + compatible = "espressif,esp32-ble"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <49>; diff --git a/Devices/heltec-wifi-lora-32-v3/device.properties b/Devices/heltec-wifi-lora-32-v3/device.properties index 73ce3992e..ab9ef2649 100644 --- a/Devices/heltec-wifi-lora-32-v3/device.properties +++ b/Devices/heltec-wifi-lora-32-v3/device.properties @@ -13,6 +13,7 @@ flashSize=8MB spiRam=false tinyUsb=true esptoolFlashFreq=120M +bluetooth=true [display] size=0.96" diff --git a/Devices/heltec-wifi-lora-32-v3/heltec,wifi-lora-32-v3.dts b/Devices/heltec-wifi-lora-32-v3/heltec,wifi-lora-32-v3.dts index 16296e3d3..f0d1f570b 100644 --- a/Devices/heltec-wifi-lora-32-v3/heltec,wifi-lora-32-v3.dts +++ b/Devices/heltec-wifi-lora-32-v3/heltec,wifi-lora-32-v3.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include @@ -8,6 +9,10 @@ compatible = "root"; model = "Heltec WiFi LoRa 32 V3"; + ble0 { + compatible = "espressif,esp32-ble"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <49>; diff --git a/Devices/lilygo-tdeck/Source/devices/Display.cpp b/Devices/lilygo-tdeck/Source/devices/Display.cpp index bb17a6d31..92a679b19 100644 --- a/Devices/lilygo-tdeck/Source/devices/Display.cpp +++ b/Devices/lilygo-tdeck/Source/devices/Display.cpp @@ -33,7 +33,8 @@ std::shared_ptr createDisplay() { .touch = createTouch(), .backlightDutyFunction = driver::pwmbacklight::setBacklightDuty, .resetPin = GPIO_NUM_NC, - .lvglSwapBytes = false + .lvglSwapBytes = false, + .buffSpiram = true }; auto spi_configuration = std::make_shared(St7789Display::SpiConfiguration { diff --git a/Devices/lilygo-tdeck/lilygo,tdeck.dts b/Devices/lilygo-tdeck/lilygo,tdeck.dts index 97859eab1..ecb1f2264 100644 --- a/Devices/lilygo-tdeck/lilygo,tdeck.dts +++ b/Devices/lilygo-tdeck/lilygo,tdeck.dts @@ -1,10 +1,10 @@ /dts-v1/; #include +#include #include #include #include -#include #include #include @@ -25,7 +25,7 @@ i2c_internal: i2c0 { compatible = "espressif,esp32-i2c"; port = ; - clock-frequency = <400000>; + clock-frequency = <100000>; pin-sda = <&gpio0 18 GPIO_FLAG_NONE>; pin-scl = <&gpio0 8 GPIO_FLAG_NONE>; }; diff --git a/Devices/lilygo-tdisplay-s3/device.properties b/Devices/lilygo-tdisplay-s3/device.properties index 1ea1a14f0..144f72795 100644 --- a/Devices/lilygo-tdisplay-s3/device.properties +++ b/Devices/lilygo-tdisplay-s3/device.properties @@ -14,6 +14,7 @@ spiRamMode=OCT spiRamSpeed=120M tinyUsb=true esptoolFlashFreq=120M +bluetooth=true [display] size=1.9" diff --git a/Devices/lilygo-tdisplay-s3/lilygo,tdisplay-s3.dts b/Devices/lilygo-tdisplay-s3/lilygo,tdisplay-s3.dts index 4713d519f..d9822732e 100644 --- a/Devices/lilygo-tdisplay-s3/lilygo,tdisplay-s3.dts +++ b/Devices/lilygo-tdisplay-s3/lilygo,tdisplay-s3.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -9,6 +10,10 @@ compatible = "root"; model = "LilyGO T-Display S3"; + ble0 { + compatible = "espressif,esp32-ble"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <49>; diff --git a/Devices/lilygo-tdongle-s3/device.properties b/Devices/lilygo-tdongle-s3/device.properties index ec4f21869..9c05614fe 100644 --- a/Devices/lilygo-tdongle-s3/device.properties +++ b/Devices/lilygo-tdongle-s3/device.properties @@ -13,6 +13,7 @@ flashSize=16MB spiRam=false tinyUsb=true esptoolFlashFreq=120M +bluetooth=true [display] size=0.96" diff --git a/Devices/lilygo-tdongle-s3/lilygo,tdongle-s3.dts b/Devices/lilygo-tdongle-s3/lilygo,tdongle-s3.dts index 147987a44..616fbb706 100644 --- a/Devices/lilygo-tdongle-s3/lilygo,tdongle-s3.dts +++ b/Devices/lilygo-tdongle-s3/lilygo,tdongle-s3.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -11,6 +12,10 @@ compatible = "root"; model = "LilyGO T-Dongle S3"; + ble0 { + compatible = "espressif,esp32-ble"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <49>; diff --git a/Devices/lilygo-thmi/Source/module.cpp b/Devices/lilygo-thmi/Source/module.cpp index 799711a1a..8cc6172bc 100644 --- a/Devices/lilygo-thmi/Source/module.cpp +++ b/Devices/lilygo-thmi/Source/module.cpp @@ -12,7 +12,7 @@ static error_t stop() { return ERROR_NONE; } -struct Module lilygo_thmi_s3_module = { +struct Module lilygo_thmi_module = { .name = "lilygo-thmi", .start = start, .stop = stop, diff --git a/Devices/lilygo-thmi/device.properties b/Devices/lilygo-thmi/device.properties index 787069cbf..0012b3d9c 100644 --- a/Devices/lilygo-thmi/device.properties +++ b/Devices/lilygo-thmi/device.properties @@ -13,6 +13,7 @@ spiRamMode=OCT spiRamSpeed=120M tinyUsb=true esptoolFlashFreq=120M +bluetooth=true [display] size=2.8" diff --git a/Devices/lilygo-thmi/lilygo,thmi.dts b/Devices/lilygo-thmi/lilygo,thmi.dts index 94b415c8d..defb61d1c 100644 --- a/Devices/lilygo-thmi/lilygo,thmi.dts +++ b/Devices/lilygo-thmi/lilygo,thmi.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -10,6 +11,10 @@ compatible = "root"; model = "LilyGO T-HMI"; + ble0 { + compatible = "espressif,esp32-ble"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <49>; diff --git a/Devices/lilygo-tlora-pager/Source/drivers/TloraPager.cpp b/Devices/lilygo-tlora-pager/Source/drivers/TloraPager.cpp index f659b8729..6a1f07f08 100644 --- a/Devices/lilygo-tlora-pager/Source/drivers/TloraPager.cpp +++ b/Devices/lilygo-tlora-pager/Source/drivers/TloraPager.cpp @@ -6,7 +6,7 @@ extern "C" { -extern struct Module device_module; +extern struct Module lilygo_tlora_pager_module; static int start(Device* device) { return 0; @@ -23,7 +23,7 @@ Driver tlora_pager_driver = { .stop_device = stop, .api = nullptr, .device_type = nullptr, - .owner = &device_module, + .owner = &lilygo_tlora_pager_module, .internal = nullptr }; diff --git a/Devices/lilygo-tlora-pager/device.properties b/Devices/lilygo-tlora-pager/device.properties index 33cceeb96..d3c82f7ad 100644 --- a/Devices/lilygo-tlora-pager/device.properties +++ b/Devices/lilygo-tlora-pager/device.properties @@ -14,6 +14,7 @@ spiRamMode=AUTO spiRamSpeed=120M tinyUsb=true esptoolFlashFreq=40M +bluetooth=true [display] size=2.33" diff --git a/Devices/lilygo-tlora-pager/lilygo,tlora-pager.dts b/Devices/lilygo-tlora-pager/lilygo,tlora-pager.dts index a0c52fd36..45b12562e 100644 --- a/Devices/lilygo-tlora-pager/lilygo,tlora-pager.dts +++ b/Devices/lilygo-tlora-pager/lilygo,tlora-pager.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -12,6 +13,10 @@ compatible = "lilygo,tlora-pager"; model = "LilyGO T-Lora Pager"; + ble0 { + compatible = "espressif,esp32-ble"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <49>; diff --git a/Devices/m5stack-cardputer-adv/device.properties b/Devices/m5stack-cardputer-adv/device.properties index 63dd51268..5e72b513d 100644 --- a/Devices/m5stack-cardputer-adv/device.properties +++ b/Devices/m5stack-cardputer-adv/device.properties @@ -11,6 +11,7 @@ flashSize=8MB spiRam=false tinyUsb=true esptoolFlashFreq=120M +bluetooth=true [display] size=1.14" diff --git a/Devices/m5stack-cardputer-adv/m5stack,cardputer-adv.dts b/Devices/m5stack-cardputer-adv/m5stack,cardputer-adv.dts index fc8f5865b..da1cb215e 100644 --- a/Devices/m5stack-cardputer-adv/m5stack,cardputer-adv.dts +++ b/Devices/m5stack-cardputer-adv/m5stack,cardputer-adv.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -13,6 +14,10 @@ compatible = "root"; model = "M5Stack Cardputer Adv"; + ble0 { + compatible = "espressif,esp32-ble"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <49>; diff --git a/Devices/m5stack-cardputer/device.properties b/Devices/m5stack-cardputer/device.properties index 76936fc6b..55e7cb1ea 100644 --- a/Devices/m5stack-cardputer/device.properties +++ b/Devices/m5stack-cardputer/device.properties @@ -11,6 +11,7 @@ flashSize=8MB spiRam=false tinyUsb=true esptoolFlashFreq=120M +bluetooth=true [display] size=1.14" diff --git a/Devices/m5stack-cardputer/m5stack,cardputer.dts b/Devices/m5stack-cardputer/m5stack,cardputer.dts index 436cd101a..d30a7a65d 100644 --- a/Devices/m5stack-cardputer/m5stack,cardputer.dts +++ b/Devices/m5stack-cardputer/m5stack,cardputer.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -12,6 +13,10 @@ compatible = "root"; model = "M5Stack Cardputer"; + ble0 { + compatible = "espressif,esp32-ble"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <49>; diff --git a/Devices/m5stack-core2/Source/devices/Display.cpp b/Devices/m5stack-core2/Source/devices/Display.cpp index 314079eaf..88d9d2726 100644 --- a/Devices/m5stack-core2/Source/devices/Display.cpp +++ b/Devices/m5stack-core2/Source/devices/Display.cpp @@ -6,13 +6,16 @@ std::shared_ptr createTouch() { auto configuration = std::make_unique( I2C_NUM_0, - GPIO_NUM_39, LCD_HORIZONTAL_RESOLUTION, - LCD_VERTICAL_RESOLUTION + LCD_VERTICAL_RESOLUTION, + false, + false, + false, + GPIO_NUM_NC, + GPIO_NUM_39 ); - auto touch = std::make_shared(std::move(configuration)); - return std::reinterpret_pointer_cast(touch); + return std::make_shared(std::move(configuration)); } std::shared_ptr createDisplay() { diff --git a/Devices/m5stack-cores3/CMakeLists.txt b/Devices/m5stack-cores3/CMakeLists.txt index b2553f94b..cacfe7fd4 100644 --- a/Devices/m5stack-cores3/CMakeLists.txt +++ b/Devices/m5stack-cores3/CMakeLists.txt @@ -3,5 +3,5 @@ file(GLOB_RECURSE SOURCE_FILES Source/*.c*) idf_component_register( SRCS ${SOURCE_FILES} INCLUDE_DIRS "Source" - REQUIRES Tactility esp_lvgl_port ILI934x FT5x06 AXP2101 AW9523 driver vfs fatfs + REQUIRES Tactility esp_lvgl_port ILI934x FT6x36 AXP2101 AW9523 driver vfs fatfs ) diff --git a/Devices/m5stack-cores3/Source/devices/Display.cpp b/Devices/m5stack-cores3/Source/devices/Display.cpp index 0409a1c17..1da65d856 100644 --- a/Devices/m5stack-cores3/Source/devices/Display.cpp +++ b/Devices/m5stack-cores3/Source/devices/Display.cpp @@ -1,7 +1,7 @@ #include "Display.h" #include -#include +#include #include #include #include @@ -17,14 +17,16 @@ static void setBacklightDuty(uint8_t backlightDuty) { } static std::shared_ptr createTouch() { - auto configuration = std::make_unique( + auto configuration = std::make_unique( I2C_NUM_0, - LCD_HORIZONTAL_RESOLUTION, - LCD_VERTICAL_RESOLUTION + 319,//LCD_HORIZONTAL_RESOLUTION, + 239,//LCD_VERTICAL_RESOLUTION, + false, + false, + false ); - auto touch = std::make_shared(std::move(configuration)); - return std::reinterpret_pointer_cast(touch); + return std::make_shared(std::move(configuration)); } std::shared_ptr createDisplay() { diff --git a/Devices/m5stack-cores3/device.properties b/Devices/m5stack-cores3/device.properties index 6c19a64da..c7bf4ef5e 100644 --- a/Devices/m5stack-cores3/device.properties +++ b/Devices/m5stack-cores3/device.properties @@ -13,6 +13,7 @@ spiRamMode=QUAD spiRamSpeed=120M tinyUsb=true esptoolFlashFreq=120M +bluetooth=true [display] size=2" diff --git a/Devices/m5stack-cores3/m5stack,cores3.dts b/Devices/m5stack-cores3/m5stack,cores3.dts index 1a5e2643d..53d9e30fc 100644 --- a/Devices/m5stack-cores3/m5stack,cores3.dts +++ b/Devices/m5stack-cores3/m5stack,cores3.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -14,6 +15,10 @@ compatible = "root"; model = "M5Stack CoreS3"; + ble0 { + compatible = "espressif,esp32-ble"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <49>; diff --git a/Devices/m5stack-papers3/device.properties b/Devices/m5stack-papers3/device.properties index c8c46d27e..48bf0bc41 100644 --- a/Devices/m5stack-papers3/device.properties +++ b/Devices/m5stack-papers3/device.properties @@ -14,6 +14,7 @@ spiRamMode=OPI spiRamSpeed=80M esptoolFlashFreq=80M tinyUsb=true +bluetooth=true [display] size=4.7" diff --git a/Devices/m5stack-papers3/m5stack,papers3.dts b/Devices/m5stack-papers3/m5stack,papers3.dts index 207b351c2..9367a3aa2 100644 --- a/Devices/m5stack-papers3/m5stack,papers3.dts +++ b/Devices/m5stack-papers3/m5stack,papers3.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -11,6 +12,10 @@ compatible = "root"; model = "M5Stack PaperS3"; + ble0 { + compatible = "espressif,esp32-ble"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <49>; diff --git a/Devices/m5stack-stackchan/CMakeLists.txt b/Devices/m5stack-stackchan/CMakeLists.txt new file mode 100644 index 000000000..0803166e6 --- /dev/null +++ b/Devices/m5stack-stackchan/CMakeLists.txt @@ -0,0 +1,7 @@ +file(GLOB_RECURSE SOURCE_FILES Source/*.c*) + +idf_component_register( + SRCS ${SOURCE_FILES} + INCLUDE_DIRS "Source" + REQUIRES Tactility esp_lvgl_port ILI934x FT6x36 AXP2101 AW9523 driver vfs fatfs ina226-module py32ioexpander-module +) diff --git a/Devices/m5stack-stackchan/Source/Configuration.cpp b/Devices/m5stack-stackchan/Source/Configuration.cpp new file mode 100644 index 000000000..cb80cb478 --- /dev/null +++ b/Devices/m5stack-stackchan/Source/Configuration.cpp @@ -0,0 +1,254 @@ +#include "devices/Display.h" +#include "devices/Power.h" +#include "devices/SdCard.h" +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace tt::hal; + +static const auto* TAG = "StackChan"; + +// --------------------------------------------------------------------------- +// I2C addresses +// --------------------------------------------------------------------------- +static constexpr uint8_t AXP2101_ADDR = 0x34; +static constexpr uint8_t AW9523B_ADDR = 0x58; +static constexpr uint8_t AW88298_ADDR = 0x36; +static constexpr uint8_t ES7210_ADDR = 0x40; + +// --------------------------------------------------------------------------- +// AW9523B GPIO expander — same wiring as CoreS3 +// --------------------------------------------------------------------------- +// P0 pins: 0=touch reset, 1=bus out enable, 2=AW88298 reset, 4=SD card, 5=USB OTG +// P1 pins: 0=cam reset, 1=LCD reset, 7=boost enable (SY7088) +static constexpr uint8_t AW9523B_CTL_REG = 0x11; // P0 push-pull mode +static constexpr uint8_t AW9523B_P0_REG = 0x02; +static constexpr uint8_t AW9523B_P1_REG = 0x03; + +static bool initGpioExpander(::Device* i2c) { + // P0: touch, bus enable, AW88298 reset, SD card switch + constexpr uint8_t p0 = (1U << 0U) | (1U << 1U) | (1U << 2U) | (1U << 4U); + // P1: LCD reset, boost enable + constexpr uint8_t p1 = (1U << 1U) | (1U << 7U); + + // Set P0 to push-pull mode + if (i2c_controller_register8_set(i2c, AW9523B_ADDR, AW9523B_CTL_REG, 0x10, pdMS_TO_TICKS(1000)) != ERROR_NONE) { + LOG_E(TAG, "AW9523B: Failed to set CTL"); + return false; + } + if (i2c_controller_register8_set(i2c, AW9523B_ADDR, AW9523B_P0_REG, p0, pdMS_TO_TICKS(1000)) != ERROR_NONE) { + LOG_E(TAG, "AW9523B: Failed to set P0"); + return false; + } + if (i2c_controller_register8_set(i2c, AW9523B_ADDR, AW9523B_P1_REG, p1, pdMS_TO_TICKS(1000)) != ERROR_NONE) { + LOG_E(TAG, "AW9523B: Failed to set P1"); + return false; + } + return true; +} + +// --------------------------------------------------------------------------- +// AXP2101 power management — same voltage rails as CoreS3 +// --------------------------------------------------------------------------- +static bool initPowerControl(::Device* i2c) { + // Source: https://github.com/m5stack/M5Unified/blob/b8cfec7fed046242da7f7b8024a4e92004a51ff7/src/utility/Power_Class.cpp#L64 + static constexpr uint8_t reg_data[] = { + 0x90U, 0xBFU, // LDOS ON/OFF control 0 (backlight) + 0x92U, 18U - 5U, // ALDO1 = 1.8V (AW88298) + 0x93U, 33U - 5U, // ALDO2 = 3.3V (ES7210) + 0x94U, 33U - 5U, // ALDO3 = 3.3V (camera) + 0x95U, 33U - 5U, // ALDO4 = 3.3V (TF card) + 0x27U, 0x00U, // PowerKey Hold=1sec / PowerOff=4sec + 0x69U, 0x11U, // CHGLED setting + 0x10U, 0x30U, // PMU common config + 0x30U, 0x0FU, // ADC enabled + }; + + if (i2c_controller_write_register_array(i2c, AXP2101_ADDR, reg_data, sizeof(reg_data), pdMS_TO_TICKS(1000)) != ERROR_NONE) { + LOG_E(TAG, "AXP2101: Failed to set registers"); + return false; + } + return true; +} + +// --------------------------------------------------------------------------- +// AW88298 speaker amplifier +// Called once in initBoot() at 16kHz. AW88298 default state after hardware reset +// is sufficient for SfxEngine. Sequence mirrors M5Unified _speaker_enabled_cb_cores3(). +// NOTE: 44100Hz (AudioPlayer/MusicPlayer) needs esp_codec_dev integration — see memory notes. +// --------------------------------------------------------------------------- +static bool initSpeaker(::Device* i2c, uint32_t sample_rate_hz) { + // M5Unified rate table: (sample_rate + 1102) / 2205 steps, first entry >= result + static constexpr uint8_t rate_tbl[] = { 4, 5, 6, 8, 10, 11, 15, 20, 22, 44 }; + size_t reg06_idx = 0; + size_t rate = (sample_rate_hz + 1102) / 2205; + while (rate > rate_tbl[reg06_idx] && ++reg06_idx < sizeof(rate_tbl)) {} + if (reg06_idx >= sizeof(rate_tbl)) { + reg06_idx = sizeof(rate_tbl) - 1; // clamp to max supported rate + } + // 0x14C0: M5Unified's upper byte for CoreS3, I2SBCK=0 (BCK 16*2) + const uint16_t reg06 = static_cast(0x14C0U | reg06_idx); + + // Hardware reset AW88298 via AW9523B P0 bit 2: pull LOW (reset), then HIGH (release). + if (i2c_controller_register8_reset_bits(i2c, AW9523B_ADDR, AW9523B_P0_REG, 0b00000100, pdMS_TO_TICKS(100)) != ERROR_NONE) { + LOG_E(TAG, "AW9523B: failed to assert AW88298 reset"); + return false; + } + vTaskDelay(pdMS_TO_TICKS(10)); + if (i2c_controller_register8_set_bits(i2c, AW9523B_ADDR, AW9523B_P0_REG, 0b00000100, pdMS_TO_TICKS(100)) != ERROR_NONE) { + LOG_E(TAG, "AW9523B: failed to release AW88298 reset"); + return false; + } + vTaskDelay(pdMS_TO_TICKS(50)); + + // Exact sequence from M5Unified _speaker_enabled_cb_cores3() — no I2C software reset. + struct { uint8_t reg; uint16_t val; } regs[] = { + { 0x61, 0x0673 }, // boost mode disabled + { 0x04, 0x4040 }, // I2SEN=1, AMPPD=0, PWDN=0 + { 0x05, 0x0008 }, // RMSE=0, HAGCE=0, HDCCE=0, HMUTE=0 + { 0x06, reg06 }, // I2S mode + sample rate + { 0x0C, 0x3064 }, // volume -24dB + }; + for (auto& r : regs) { + if (i2c_controller_register16be_set(i2c, AW88298_ADDR, r.reg, r.val, pdMS_TO_TICKS(100)) != ERROR_NONE) { + LOG_E(TAG, "AW88298: failed reg 0x%02X", r.reg); + return false; + } + } + + LOG_I(TAG, "AW88298 initialized (%luHz, reg06=0x%04X)", (unsigned long)sample_rate_hz, reg06); + return true; +} + +// --------------------------------------------------------------------------- +// ES7210 microphone ADC +// Source: https://github.com/m5stack/M5Unified +// --------------------------------------------------------------------------- +static bool initMicrophone(::Device* i2c) { + static constexpr uint8_t reg_data[] = { + 0x00, 0x41, // RESET_CTL + 0x01, 0x1F, // CLK_ON_OFF (initial) + 0x06, 0x00, // DIGITAL_PDN + 0x07, 0x20, // ADC_OSR + 0x08, 0x10, // MODE_CFG + 0x09, 0x30, // TCT0_CHPINI + 0x0A, 0x30, // TCT1_CHPINI + 0x20, 0x0A, // ADC34_HPF2 + 0x21, 0x2A, // ADC34_HPF1 + 0x22, 0x0A, // ADC12_HPF2 + 0x23, 0x2A, // ADC12_HPF1 + 0x02, 0xC1, + 0x04, 0x01, + 0x05, 0x00, + 0x11, 0x60, + 0x40, 0x42, // ANALOG_SYS + 0x41, 0x70, // MICBIAS12 + 0x42, 0x70, // MICBIAS34 + 0x43, 0x1B, // MIC1_GAIN + 0x44, 0x1B, // MIC2_GAIN + 0x45, 0x00, // MIC3_GAIN + 0x46, 0x00, // MIC4_GAIN + 0x47, 0x00, // MIC1_LP + 0x48, 0x00, // MIC2_LP + 0x49, 0x00, // MIC3_LP + 0x4A, 0x00, // MIC4_LP + 0x4B, 0x00, // MIC12_PDN + 0x4C, 0xFF, // MIC34_PDN + 0x01, 0x14, // CLK_ON_OFF (final) + }; + + if (i2c_controller_write_register_array(i2c, ES7210_ADDR, reg_data, sizeof(reg_data), pdMS_TO_TICKS(1000)) != ERROR_NONE) { + LOG_E(TAG, "ES7210: Failed to set registers"); + return false; + } + return true; +} + +// --------------------------------------------------------------------------- +// initBoot — called before device tree is started +// --------------------------------------------------------------------------- +static std::shared_ptr axp2101; + +bool initBoot() { + auto* i2c = device_find_by_name("i2c_internal"); + if (i2c == nullptr) { + LOG_E(TAG, "i2c_internal not found"); + return false; + } + // Boost enable via AXP2101 before GPIO expander init (same as CoreS3) + // AW9523B P1 bit 7 = SY7088 boost enable — set after AXP2101 is configured + if (!initPowerControl(i2c)) { + LOG_E(TAG, "AXP2101 init failed"); + return false; + } + + if (!initGpioExpander(i2c)) { + LOG_E(TAG, "AW9523B init failed"); + return false; + } + + // AW88298 default state after reset is sufficient for SfxEngine (16kHz). + // Full 44100Hz support requires esp_codec_dev integration (see memory notes). + if (!initSpeaker(i2c, 16000)) { + LOG_W(TAG, "AW88298 init failed (non-fatal)"); + } + + if (!initMicrophone(i2c)) { + LOG_W(TAG, "ES7210 init failed (non-fatal)"); + } + + // Boot LED pattern — confirms PY32IOExpander is working. + // PY32 pin 0 = servo VM_EN (output), pin 13 = WS2812C data line. + auto* py32 = device_find_by_name("py32"); + if (py32 != nullptr) { + static constexpr uint8_t LED_COUNT = 12; + static constexpr uint8_t COLORS[][3] = { + { 255, 0, 0 }, // red + { 0, 255, 0 }, // green + { 0, 0, 255 }, // blue + }; + py32_led_set_count(py32, LED_COUNT); + for (auto& c : COLORS) { + for (uint8_t i = 0; i < LED_COUNT; i++) { + py32_led_set_color(py32, i, c[0], c[1], c[2]); + } + py32_led_refresh(py32); + vTaskDelay(pdMS_TO_TICKS(150)); + } + py32_led_disable(py32); + } else { + LOG_W(TAG, "py32 not found — LED boot pattern skipped"); + } + + // Keep Axp2101 C++ wrapper alive for Axp2101Power (backlight + battery) + axp2101 = std::make_shared(I2C_NUM_0); + return true; +} + +// --------------------------------------------------------------------------- +// Device list +// --------------------------------------------------------------------------- +static DeviceVector createDevices() { + return { + axp2101, + std::make_shared(axp2101), + createPower(), + createSdCard(), + createDisplay(), + }; +} + +extern const Configuration hardwareConfiguration = { + .initBoot = initBoot, + .createDevices = createDevices +}; diff --git a/Devices/m5stack-stackchan/Source/devices/Display.cpp b/Devices/m5stack-stackchan/Source/devices/Display.cpp new file mode 100644 index 000000000..cca040771 --- /dev/null +++ b/Devices/m5stack-stackchan/Source/devices/Display.cpp @@ -0,0 +1,58 @@ +#include "Display.h" + +#include +#include +#include +#include +#include + +static const auto LOGGER = tt::Logger("StackChanDisplay"); + +static void setBacklightDuty(uint8_t backlightDuty) { + const uint8_t voltage = 20 + ((8 * backlightDuty) / 255); // [0b00000, 0b11100] + if (!tt::hal::i2c::masterWriteRegister(I2C_NUM_0, AXP2101_ADDRESS, 0x99, &voltage, 1, 1000)) { + LOGGER.error("Failed to set display backlight voltage"); + } +} + +static std::shared_ptr createTouch() { + auto configuration = std::make_unique( + I2C_NUM_0, + 319,//LCD_HORIZONTAL_RESOLUTION, + 239,//LCD_VERTICAL_RESOLUTION, + false, + false, + false + ); + + return std::make_shared(std::move(configuration)); +} + +std::shared_ptr createDisplay() { + Ili934xDisplay::Configuration panel_configuration = { + .horizontalResolution = LCD_HORIZONTAL_RESOLUTION, + .verticalResolution = LCD_VERTICAL_RESOLUTION, + .gapX = 0, + .gapY = 0, + .swapXY = false, + .mirrorX = false, + .mirrorY = false, + .invertColor = true, + .swapBytes = true, + .bufferSize = LCD_BUFFER_SIZE, + .touch = createTouch(), + .backlightDutyFunction = ::setBacklightDuty, + .resetPin = GPIO_NUM_NC, + .rgbElementOrder = LCD_RGB_ELEMENT_ORDER_BGR + }; + + auto spi_configuration = std::make_shared(Ili934xDisplay::SpiConfiguration { + .spiHostDevice = LCD_SPI_HOST, + .csPin = LCD_PIN_CS, + .dcPin = LCD_PIN_DC, + .pixelClockFrequency = 40'000'000, + .transactionQueueDepth = 10 + }); + + return std::make_shared(panel_configuration, spi_configuration, true); +} diff --git a/Devices/m5stack-stackchan/Source/devices/Display.h b/Devices/m5stack-stackchan/Source/devices/Display.h new file mode 100644 index 000000000..bfb965229 --- /dev/null +++ b/Devices/m5stack-stackchan/Source/devices/Display.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include +#include + +// Display +constexpr auto LCD_SPI_HOST = SPI2_HOST; +constexpr auto LCD_PIN_CS = GPIO_NUM_3; +constexpr auto LCD_PIN_DC = GPIO_NUM_35; +constexpr auto LCD_HORIZONTAL_RESOLUTION = 320; +constexpr auto LCD_VERTICAL_RESOLUTION = 240; +constexpr auto LCD_BUFFER_HEIGHT = LCD_VERTICAL_RESOLUTION / 10; +constexpr auto LCD_BUFFER_SIZE = LCD_HORIZONTAL_RESOLUTION * LCD_BUFFER_HEIGHT; +constexpr auto LCD_SPI_TRANSFER_SIZE_LIMIT = LCD_BUFFER_SIZE * LV_COLOR_DEPTH / 8; + +std::shared_ptr createDisplay(); diff --git a/Devices/m5stack-stackchan/Source/devices/Power.cpp b/Devices/m5stack-stackchan/Source/devices/Power.cpp new file mode 100644 index 000000000..b08c312ef --- /dev/null +++ b/Devices/m5stack-stackchan/Source/devices/Power.cpp @@ -0,0 +1,86 @@ +#include "Power.h" + +#include +#include +#include +#include + +using namespace tt::hal::power; + +static constexpr auto* TAG = "StackChanPower"; + +// 1S Li-ion cell (550 mAh): 3.2V (empty) – 4.2V (full) +static constexpr float MIN_BATTERY_VOLTAGE_MV = 3200.0f; +static constexpr float MAX_BATTERY_VOLTAGE_MV = 4200.0f; + +class StackChanPower final : public PowerDevice { +public: + explicit StackChanPower(::Device* ina226Device) : ina226(ina226Device) {} + + std::string getName() const override { return "M5Stack StackChan Power"; } + std::string getDescription() const override { return "Battery monitoring via INA226 over I2C"; } + + bool supportsMetric(MetricType type) const override { + switch (type) { + using enum MetricType; + case BatteryVoltage: + case ChargeLevel: + case Current: + return ina226 != nullptr; + default: + return false; + } + } + + bool getMetric(MetricType type, MetricData& data) override { + switch (type) { + using enum MetricType; + + case BatteryVoltage: { + if (ina226 == nullptr) return false; + float volts = 0.0f; + if (ina226_read_bus_voltage(ina226, &volts) != ERROR_NONE) return false; + data.valueAsUint32 = static_cast(volts * 1000.0f); + return true; + } + + case ChargeLevel: { + if (ina226 == nullptr) return false; + float volts = 0.0f; + if (ina226_read_bus_voltage(ina226, &volts) != ERROR_NONE) return false; + float voltage_mv = volts * 1000.0f; + if (voltage_mv >= MAX_BATTERY_VOLTAGE_MV) { + data.valueAsUint8 = 100; + } else if (voltage_mv <= MIN_BATTERY_VOLTAGE_MV) { + data.valueAsUint8 = 0; + } else { + float factor = (voltage_mv - MIN_BATTERY_VOLTAGE_MV) / (MAX_BATTERY_VOLTAGE_MV - MIN_BATTERY_VOLTAGE_MV); + data.valueAsUint8 = static_cast(factor * 100.0f); + } + return true; + } + + case Current: { + if (ina226 == nullptr) return false; + float amps = 0.0f; + if (ina226_read_shunt_current(ina226, &s) != ERROR_NONE) return false; + data.valueAsInt32 = static_cast(amps * 1000.0f); + return true; + } + + default: + return false; + } + } + +private: + ::Device* ina226; +}; + +std::shared_ptr createPower() { + auto* ina226 = device_find_by_name("ina226"); + if (ina226 == nullptr) { + LOG_E(TAG, "ina226 device not found"); + } + return std::make_shared(ina226); +} diff --git a/Devices/m5stack-stackchan/Source/devices/Power.h b/Devices/m5stack-stackchan/Source/devices/Power.h new file mode 100644 index 000000000..7598ded6f --- /dev/null +++ b/Devices/m5stack-stackchan/Source/devices/Power.h @@ -0,0 +1,6 @@ +#pragma once + +#include +#include + +std::shared_ptr createPower(); diff --git a/Devices/m5stack-stackchan/Source/devices/SdCard.cpp b/Devices/m5stack-stackchan/Source/devices/SdCard.cpp new file mode 100644 index 000000000..0fbc268b0 --- /dev/null +++ b/Devices/m5stack-stackchan/Source/devices/SdCard.cpp @@ -0,0 +1,30 @@ +#include "SdCard.h" + +#include +#include +#include + +constexpr auto STACKCHAN_SDCARD_PIN_CS = GPIO_NUM_4; +constexpr auto STACKCHAN_LCD_PIN_CS = GPIO_NUM_3; + +using tt::hal::sdcard::SpiSdCardDevice; + +std::shared_ptr createSdCard() { + auto configuration = std::make_unique( + STACKCHAN_SDCARD_PIN_CS, + GPIO_NUM_NC, + GPIO_NUM_NC, + GPIO_NUM_NC, + SdCardDevice::MountBehaviour::AtBoot, + tt::lvgl::getSyncLock(), + std::vector { STACKCHAN_LCD_PIN_CS } + ); + + auto* spi_controller = device_find_by_name("spi0"); + check(spi_controller, "spi0 not found"); + + return std::make_shared( + std::move(configuration), + spi_controller + ); +} diff --git a/Devices/m5stack-stackchan/Source/devices/SdCard.h b/Devices/m5stack-stackchan/Source/devices/SdCard.h new file mode 100644 index 000000000..5cb65a735 --- /dev/null +++ b/Devices/m5stack-stackchan/Source/devices/SdCard.h @@ -0,0 +1,7 @@ +#pragma once + +#include "Tactility/hal/sdcard/SdCardDevice.h" + +using tt::hal::sdcard::SdCardDevice; + +std::shared_ptr createSdCard(); diff --git a/Devices/m5stack-stackchan/Source/module.cpp b/Devices/m5stack-stackchan/Source/module.cpp new file mode 100644 index 000000000..0441dba2d --- /dev/null +++ b/Devices/m5stack-stackchan/Source/module.cpp @@ -0,0 +1,21 @@ +#include + +extern "C" { + +static error_t start() { + return ERROR_NONE; +} + +static error_t stop() { + return ERROR_NONE; +} + +struct Module m5stack_stackchan_module = { + .name = "m5stack-stackchan", + .start = start, + .stop = stop, + .symbols = nullptr, + .internal = nullptr +}; + +} diff --git a/Devices/m5stack-stackchan/device.properties b/Devices/m5stack-stackchan/device.properties new file mode 100644 index 000000000..7567bd6ff --- /dev/null +++ b/Devices/m5stack-stackchan/device.properties @@ -0,0 +1,24 @@ +[general] +vendor=M5Stack +name=StackChan + +[apps] +launcherAppId=Launcher + +[hardware] +target=ESP32S3 +flashSize=16MB +spiRam=true +spiRamMode=QUAD +spiRamSpeed=120M +tinyUsb=true +esptoolFlashFreq=120M +bluetooth=true + +[display] +size=2" +shape=rectangle +dpi=200 + +[lvgl] +colorDepth=16 diff --git a/Devices/m5stack-stackchan/devicetree.yaml b/Devices/m5stack-stackchan/devicetree.yaml new file mode 100644 index 000000000..d922e6394 --- /dev/null +++ b/Devices/m5stack-stackchan/devicetree.yaml @@ -0,0 +1,7 @@ +dependencies: +- Platforms/platform-esp32 +- Drivers/bmi270-module +- Drivers/bm8563-module +- Drivers/ina226-module +- Drivers/py32ioexpander-module +dts: m5stack,stackchan.dts diff --git a/Devices/m5stack-stackchan/m5stack,stackchan.dts b/Devices/m5stack-stackchan/m5stack,stackchan.dts new file mode 100644 index 000000000..d42c25d4c --- /dev/null +++ b/Devices/m5stack-stackchan/m5stack,stackchan.dts @@ -0,0 +1,123 @@ +/dts-v1/; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Reference: https://docs.m5stack.com/en/StackChan +/ { + compatible = "root"; + model = "M5Stack StackChan"; + + ble0 { + compatible = "espressif,esp32-ble"; + }; + + gpio0 { + compatible = "espressif,esp32-gpio"; + gpio-count = <49>; + }; + + i2c_internal { + compatible = "espressif,esp32-i2c"; + port = ; + clock-frequency = <100000>; + pin-sda = <&gpio0 12 GPIO_FLAG_NONE>; + pin-scl = <&gpio0 11 GPIO_FLAG_NONE>; + + // PY32L020 body IO expander — controls WS2812C LED ring (12 LEDs) + py32 { + compatible = "m5stack,py32ioexpander"; + reg = <0x6F>; + }; + + bmi270 { + compatible = "bosch,bmi270"; + reg = <0x69>; + }; + + bm8563 { + compatible = "belling,bm8563"; + reg = <0x51>; + }; + + ina226 { + compatible = "ti,ina226"; + reg = <0x41>; + shunt-milliohms = <10>; + }; + + // AXP2101 PMIC @ 0x34 — initialized manually in initBoot() + // AW9523B GPIO expander @ 0x58 — initialized manually in initBoot() (same as CoreS3) + // AW88298 speaker amp @ 0x36 — initialized manually in initBoot() + // ES7210 microphone ADC @ 0x40 — initialized manually in initBoot() + // FT6336U capacitive touch @ 0x38 — used by Display driver (FT6x36 library) + + // TODO: Si12T 3-zone head touch @ 0x68 — INT active-low, 10kΩ pull-up to 3.3V; driver not yet implemented + // TODO: GC0308 camera @ 0x21 — requires i2c_master driver, not yet available + // TODO: LTR-553ALS-WA proximity/light @ 0x23 — no driver yet + // TODO: BMM150 magnetometer @ 0x10 — accessible only via BMI270 aux I2C + }; + + i2c_port_a { + compatible = "espressif,esp32-i2c"; + port = ; + clock-frequency = <400000>; + pin-sda = <&gpio0 2 GPIO_FLAG_NONE>; + pin-scl = <&gpio0 1 GPIO_FLAG_NONE>; + }; + + i2c_port_b { + compatible = "espressif,esp32-i2c"; + status = "disabled"; + port = ; + clock-frequency = <400000>; + pin-sda = <&gpio0 9 GPIO_FLAG_NONE>; + pin-scl = <&gpio0 8 GPIO_FLAG_NONE>; + }; + + i2c_port_c { + compatible = "espressif,esp32-i2c"; + status = "disabled"; + port = ; + clock-frequency = <400000>; + pin-sda = <&gpio0 18 GPIO_FLAG_NONE>; + pin-scl = <&gpio0 17 GPIO_FLAG_NONE>; + }; + + spi0 { + compatible = "espressif,esp32-spi"; + host = ; + pin-mosi = <&gpio0 37 GPIO_FLAG_NONE>; + pin-miso = <&gpio0 35 GPIO_FLAG_NONE>; + pin-sclk = <&gpio0 36 GPIO_FLAG_NONE>; + }; + + // AW88298 speaker + ES7210 microphone + i2s0 { + compatible = "espressif,esp32-i2s"; + port = ; + pin-bclk = <&gpio0 34 GPIO_FLAG_NONE>; + pin-ws = <&gpio0 33 GPIO_FLAG_NONE>; + pin-data-out = <&gpio0 13 GPIO_FLAG_NONE>; + pin-data-in = <&gpio0 14 GPIO_FLAG_NONE>; + pin-mclk = <&gpio0 0 GPIO_FLAG_NONE>; + }; + + // TODO: Servo UART (SCS9009, 1 Mbaud) — TX=GPIO6, RX=GPIO7 + uart_port_a: uart1 { + compatible = "espressif,esp32-uart"; + status = "disabled"; + port = ; + pin-tx = <&gpio0 1 GPIO_FLAG_NONE>; + pin-rx = <&gpio0 2 GPIO_FLAG_NONE>; + }; +}; diff --git a/Devices/m5stack-sticks3/CMakeLists.txt b/Devices/m5stack-sticks3/CMakeLists.txt index a6e098d03..a46669714 100644 --- a/Devices/m5stack-sticks3/CMakeLists.txt +++ b/Devices/m5stack-sticks3/CMakeLists.txt @@ -3,5 +3,5 @@ file(GLOB_RECURSE SOURCE_FILES Source/*.c*) idf_component_register( SRCS ${SOURCE_FILES} INCLUDE_DIRS "Source" - REQUIRES Tactility esp_lvgl_port esp_lcd ST7789 PwmBacklight ButtonControl m5pm1-module + REQUIRES Tactility esp_lvgl_port esp_lcd ST7789 PwmBacklight ButtonControl m5pm1-module vfs fatfs ) diff --git a/Devices/m5stack-sticks3/Source/Configuration.cpp b/Devices/m5stack-sticks3/Source/Configuration.cpp index 4c3619234..6743208c6 100644 --- a/Devices/m5stack-sticks3/Source/Configuration.cpp +++ b/Devices/m5stack-sticks3/Source/Configuration.cpp @@ -1,14 +1,101 @@ #include "devices/Display.h" +#include "devices/SdCard.h" #include "devices/Power.h" #include +#include +#include +#include + #include #include #include using namespace tt::hal; +static constexpr auto* TAG = "StickS3"; + +static constexpr uint8_t ES8311_I2C_ADDR = 0x18; + +static error_t initSound(::Device* i2c_controller) { + // Init data from M5Unified: + // https://github.com/m5stack/M5Unified/blob/master/src/M5Unified.cpp#L454 + static constexpr uint8_t ENABLED_BULK_DATA[] = { + 0x00, 0x80, // 0x00 RESET/ CSM POWER ON + 0x01, 0x3F, // 0x01 CLOCK_MANAGER/ use MCLK pin (external), all clocks on + 0x02, 0x00, // 0x02 CLOCK_MANAGER/ pre_div=1 pre_multi=1 (256x MCLK from ESP32 is correct ratio) + 0x0D, 0x01, // 0x0D SYSTEM/ Power up analog circuitry + 0x12, 0x00, // 0x12 SYSTEM/ power-up DAC - NOT default + 0x13, 0x10, // 0x13 SYSTEM/ Enable output to HP drive - NOT default + 0x32, 0xBF, // 0x32 DAC/ DAC volume (0xBF == ±0 dB ) + 0x37, 0x08, // 0x37 DAC/ Bypass DAC equalizer - NOT default + }; + + error_t error = i2c_controller_write_register_array( + i2c_controller, + ES8311_I2C_ADDR, + ENABLED_BULK_DATA, + sizeof(ENABLED_BULK_DATA), + pdMS_TO_TICKS(1000) + ); + if (error != ERROR_NONE) { + LOG_E(TAG, "Failed to enable ES8311: %s", error_to_string(error)); + return error; + } + + // Enable speaker amp via M5PM1 driver + auto* m5pm1 = device_find_by_name("m5pm1"); + if (m5pm1 != nullptr) { + m5pm1_set_speaker_enable(m5pm1, true); + } else { + LOG_W(TAG, "m5pm1 not found — speaker amp not enabled"); + } + + return ERROR_NONE; +} + +static error_t initMicrophone(::Device* i2c_controller) { + // Init data from M5Unified: + // https://github.com/m5stack/M5Unified/blob/master/src/M5Unified.cpp#L842 + static constexpr uint8_t ENABLED_BULK_DATA[] = { + 0x00, 0x80, // 0x00 RESET/ CSM POWER ON + 0x01, 0x3F, // 0x01 CLOCK_MANAGER/ use MCLK pin (external), all clocks on + 0x02, 0x00, // 0x02 CLOCK_MANAGER/ pre_div=1 pre_multi=1 (256x MCLK from ESP32 is correct ratio) + 0x0D, 0x01, // 0x0D SYSTEM/ Power up analog circuitry + 0x0E, 0x02, // 0x0E SYSTEM/ : Enable analog PGA, enable ADC modulator + 0x14, 0x10, // ES8311_ADC_REG14 : select Mic1p-Mic1n / PGA GAIN (minimum) + 0x17, 0xFF, // ES8311_ADC_REG17 : ADC_VOLUME (MAXGAIN) // (0xBF == ± 0 dB ) + 0x1C, 0x6A, // ES8311_ADC_REG1C : ADC Equalizer bypass, cancel DC offset in digital domain + }; + + error_t error = i2c_controller_write_register_array( + i2c_controller, + ES8311_I2C_ADDR, + ENABLED_BULK_DATA, + sizeof(ENABLED_BULK_DATA), + pdMS_TO_TICKS(1000) + ); + if (error != ERROR_NONE) { + LOG_E(TAG, "Failed to enable ES8311: %s", error_to_string(error)); + return error; + } + + return ERROR_NONE; +} + bool initBoot() { + auto* i2c_internal = device_find_by_name("i2c_internal"); + check(i2c_internal, "i2c_internal not found"); + + error_t error = initSound(i2c_internal); + if (error != ERROR_NONE) { + LOG_E(TAG, "Failed to enable ES8311 speaker"); + } + + error = initMicrophone(i2c_internal); + if (error != ERROR_NONE) { + LOG_E(TAG, "Failed to enable ES8311 microphone"); + } return driver::pwmbacklight::init(GPIO_NUM_38, 512); } @@ -16,7 +103,8 @@ static DeviceVector createDevices() { return { createPower(), ButtonControl::createTwoButtonControl(11, 12), // top button, side button - createDisplay() + createDisplay(), + createSdCard() }; } diff --git a/Devices/m5stack-sticks3/Source/devices/SdCard.cpp b/Devices/m5stack-sticks3/Source/devices/SdCard.cpp new file mode 100644 index 000000000..361dcf551 --- /dev/null +++ b/Devices/m5stack-sticks3/Source/devices/SdCard.cpp @@ -0,0 +1,30 @@ +#include "SdCard.h" + +#include +#include + +constexpr auto SDCARD_SPI_HOST = SPI3_HOST; +constexpr auto SDCARD_PIN_CS = GPIO_NUM_7; + +using tt::hal::sdcard::SpiSdCardDevice; + +std::shared_ptr createSdCard() { + auto configuration = std::make_unique( + SDCARD_PIN_CS, + GPIO_NUM_NC, + GPIO_NUM_NC, + GPIO_NUM_NC, + SdCardDevice::MountBehaviour::AtBoot, + nullptr, + std::vector(), + SDCARD_SPI_HOST + ); + + auto* spi_controller = device_find_by_name("spi1"); + check(spi_controller, "spi1 not found"); + + return std::make_shared( + std::move(configuration), + spi_controller + ); +} diff --git a/Devices/m5stack-sticks3/Source/devices/SdCard.h b/Devices/m5stack-sticks3/Source/devices/SdCard.h new file mode 100644 index 000000000..98b222fa6 --- /dev/null +++ b/Devices/m5stack-sticks3/Source/devices/SdCard.h @@ -0,0 +1,8 @@ +#pragma once + +#include +#include + +using tt::hal::sdcard::SdCardDevice; + +std::shared_ptr createSdCard(); diff --git a/Devices/m5stack-sticks3/device.properties b/Devices/m5stack-sticks3/device.properties index 4bcc3b9e3..d1adebb92 100644 --- a/Devices/m5stack-sticks3/device.properties +++ b/Devices/m5stack-sticks3/device.properties @@ -4,7 +4,6 @@ name=StickS3 [apps] launcherAppId=Launcher -autoStartAppId=ApWebServer [hardware] target=ESP32S3 @@ -14,6 +13,7 @@ spiRamMode=OCT spiRamSpeed=80M esptoolFlashFreq=80M tinyUsb=true +bluetooth=true [display] size=1.14" diff --git a/Devices/m5stack-sticks3/m5stack,sticks3.dts b/Devices/m5stack-sticks3/m5stack,sticks3.dts index 37ae19fa2..4d194c8d9 100644 --- a/Devices/m5stack-sticks3/m5stack,sticks3.dts +++ b/Devices/m5stack-sticks3/m5stack,sticks3.dts @@ -1,5 +1,6 @@ /dts-v1/; #include +#include #include #include #include @@ -12,6 +13,10 @@ compatible = "root"; model = "M5Stack StickS3"; + ble0 { + compatible = "espressif,esp32-ble"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <49>; @@ -50,6 +55,15 @@ pin-sclk = <&gpio0 40 GPIO_FLAG_NONE>; }; + //DIY SD Card - https://wiki.bruce.computer/wiring-diagrams/m5sticks3/sd-card/ + spi1 { + compatible = "espressif,esp32-spi"; + host = ; + pin-mosi = <&gpio0 6 GPIO_FLAG_NONE>; + pin-miso = <&gpio0 4 GPIO_FLAG_NONE>; + pin-sclk = <&gpio0 5 GPIO_FLAG_NONE>; + }; + // Speaker and microphone (ES8311) i2s0 { compatible = "espressif,esp32-i2s"; diff --git a/Devices/m5stack-tab5/CMakeLists.txt b/Devices/m5stack-tab5/CMakeLists.txt index 13e48e7e8..ec6ee1f43 100644 --- a/Devices/m5stack-tab5/CMakeLists.txt +++ b/Devices/m5stack-tab5/CMakeLists.txt @@ -3,5 +3,5 @@ file(GLOB_RECURSE SOURCE_FILES Source/*.c*) idf_component_register( SRCS ${SOURCE_FILES} INCLUDE_DIRS "Source" - REQUIRES Tactility esp_lvgl_port esp_lcd EspLcdCompat esp_lcd_ili9881c esp_lcd_st7123 esp_lcd_touch_st7123 GT911 PwmBacklight driver vfs fatfs + REQUIRES Tactility esp_lvgl_port esp_lcd EspLcdCompat esp_lcd_ili9881c esp_lcd_st7123 esp_lcd_touch_st7123 GT911 PwmBacklight driver vfs fatfs ina226-module ) diff --git a/Devices/m5stack-tab5/Source/Configuration.cpp b/Devices/m5stack-tab5/Source/Configuration.cpp index cf8c79153..cde373d9f 100644 --- a/Devices/m5stack-tab5/Source/Configuration.cpp +++ b/Devices/m5stack-tab5/Source/Configuration.cpp @@ -1,5 +1,6 @@ #include "devices/Display.h" #include "devices/SdCard.h" +#include "devices/Power.h" #include #include @@ -13,6 +14,7 @@ static constexpr auto* TAG = "Tab5"; static DeviceVector createDevices() { return { + createPower(), createDisplay(), createSdCard(), }; @@ -195,6 +197,57 @@ static error_t initSound(::Device* i2c_controller, ::Device* io_expander0 = null return ERROR_NONE; } +static error_t initMicrophone(::Device* i2c_controller) { + // ES7210 quad-channel microphone ADC at 0x40. + // Register sequence from M5Unified (M5Unified.cpp, _microphone_enabled_cb_tab5). + // Configures 4-slot TDM output at 48kHz/16-bit with MIC1+MIC2 active and MICBIAS enabled. + static constexpr uint8_t ES7210_I2C_ADDR = 0x40; + static constexpr uint8_t INIT_DATA[] = { + 0x00, 0xFF, // RESET_CTL: full reset + 0x00, 0x41, // RESET_CTL: release reset, keep CSM active + 0x01, 0x1F, // CLK_ON_OFF: enable all clocks + 0x06, 0x00, // DIGITAL_PDN: power up all digital blocks + 0x07, 0x20, // ADC_OSR: OSR=256 + 0x08, 0x10, // MODE_CFG: I2S slave, TDM mode + 0x09, 0x30, // TCT0_CHPINI: chopper init period + 0x0A, 0x30, // TCT1_CHPINI + 0x20, 0x0A, // ADC34_HPF2 + 0x21, 0x2A, // ADC34_HPF1 + 0x22, 0x0A, // ADC12_HPF2 + 0x23, 0x2A, // ADC12_HPF1 + 0x02, 0xC1, // CLK_CTRL: MCLK from I2S, PLL off + 0x04, 0x01, // SDPOUT_CTL1: TDM output enable + 0x05, 0x00, // SDPOUT_CTL0 + 0x11, 0x60, // DBIAS: adjust reference voltage for P4 + 0x40, 0x42, // ANALOG_SYS: enable analog supply + 0x41, 0x70, // MICBIAS12: enable MICBIAS for MIC1+MIC2 + 0x42, 0x70, // MICBIAS34: enable MICBIAS for MIC3+MIC4 + 0x43, 0x1B, // MIC1_GAIN: +30 dB + 0x44, 0x1B, // MIC2_GAIN: +30 dB + 0x45, 0x00, // MIC3_GAIN: AEC ref, no gain + 0x46, 0x00, // MIC4_GAIN: AEC ref, no gain + 0x47, 0x00, // MIC1_LP + 0x48, 0x00, // MIC2_LP + 0x49, 0x00, // MIC3_LP + 0x4A, 0x00, // MIC4_LP + 0x4B, 0x00, // MIC12_PDN: power up MIC1+MIC2 + 0x4C, 0xFF, // MIC34_PDN: keep MIC3+MIC4 in power-down (AEC ref not needed) + 0x01, 0x14, // CLK_ON_OFF: final clock config + }; + + error_t error = i2c_controller_write_register_array( + i2c_controller, + ES7210_I2C_ADDR, + INIT_DATA, + sizeof(INIT_DATA), + pdMS_TO_TICKS(1000) + ); + if (error != ERROR_NONE) { + LOG_E(TAG, "Failed to init ES7210: %s", error_to_string(error)); + } + return error; +} + static bool initBoot() { auto* i2c0 = device_find_by_name("i2c0"); check(i2c0, "i2c0 not found"); @@ -212,6 +265,11 @@ static bool initBoot() { LOG_E(TAG, "Failed to enable ES8388"); } + error = initMicrophone(i2c0); + if (error != ERROR_NONE) { + LOG_E(TAG, "Failed to init ES7210"); + } + return true; } diff --git a/Devices/m5stack-tab5/Source/devices/Power.cpp b/Devices/m5stack-tab5/Source/devices/Power.cpp new file mode 100644 index 000000000..1cc9f6b1f --- /dev/null +++ b/Devices/m5stack-tab5/Source/devices/Power.cpp @@ -0,0 +1,192 @@ +#include "Power.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace tt::hal::power; + +static constexpr auto* TAG = "Tab5Power"; + +// NP-F550 is a 2S Li-ion pack; INA226 measures the pack voltage on BAT_IN +// before the DC-DC converter. Per-cell range 3.2-4.2V → pack range 6.4-8.4V. +static constexpr float MIN_BATTERY_VOLTAGE_MV = 6400.0f; +static constexpr float MAX_BATTERY_VOLTAGE_MV = 8400.0f; + +// INA226 convention: negative raw current = charging, positive = discharging. +// After negation in getMetric(Current), >50mA means charging into the battery. +static constexpr float CHARGING_CURRENT_THRESHOLD_AMPS = 0.05f; + +// GPIO expander 1 (0x44) pin 4: PWROFF_PULSE +static constexpr int GPIO_EXP1_PIN_DEVICE_POWER = 4; +// GPIO expander 1 (0x44) pin 5: IP2326 nCHG_QC_EN (active-low: LOW = QC enabled) +static constexpr int GPIO_EXP1_PIN_IP2326_NCHG_QC_EN = 5; +// GPIO expander 1 (0x44) pin 7: IP2326 CHG_EN (HIGH = charging enabled, LOW = disabled) +static constexpr int GPIO_EXP1_PIN_IP2326_CHG_EN = 7; + +class Tab5Power final : public PowerDevice { +public: + Tab5Power(::Device* ina226Device, ::Device* ioExpander1Device) + : ina226(ina226Device), ioExpander1(ioExpander1Device) { + // Initialize CHG_EN as output HIGH (charging enabled at startup). + setAllowedToCharge(true); + } + + std::string getName() const override { return "M5Stack Tab5 Power"; } + std::string getDescription() const override { return "Battery monitoring via INA226 over I2C"; } + + bool supportsMetric(MetricType type) const override { + switch (type) { + using enum MetricType; + case BatteryVoltage: + case ChargeLevel: + case Current: + case IsCharging: + return ina226 != nullptr; + default: + return false; + } + } + + bool getMetric(MetricType type, MetricData& data) override { + switch (type) { + using enum MetricType; + + case BatteryVoltage: { + if (ina226 == nullptr) return false; + float volts = 0.0f; + if (ina226_read_bus_voltage(ina226, &volts) != ERROR_NONE) return false; + data.valueAsUint32 = static_cast(volts * 1000.0f); + return true; + } + + case ChargeLevel: { + if (ina226 == nullptr) return false; + float volts = 0.0f; + if (ina226_read_bus_voltage(ina226, &volts) != ERROR_NONE) return false; + float voltage_mv = volts * 1000.0f; + if (voltage_mv >= MAX_BATTERY_VOLTAGE_MV) { + data.valueAsUint8 = 100; + } else if (voltage_mv <= MIN_BATTERY_VOLTAGE_MV) { + data.valueAsUint8 = 0; + } else { + float factor = (voltage_mv - MIN_BATTERY_VOLTAGE_MV) / (MAX_BATTERY_VOLTAGE_MV - MIN_BATTERY_VOLTAGE_MV); + data.valueAsUint8 = static_cast(factor * 100.0f); + } + return true; + } + + case Current: { + if (ina226 == nullptr) return false; + float amps = 0.0f; + if (ina226_read_shunt_current(ina226, &s) != ERROR_NONE) return false; + // INA226 convention: negative = charging, positive = discharging. + // Negate so the HAL value is positive when charging, negative when discharging. + data.valueAsInt32 = static_cast(-amps * 1000.0f); + return true; + } + + case IsCharging: { + if (ina226 == nullptr) return false; + float amps = 0.0f; + if (ina226_read_shunt_current(ina226, &s) != ERROR_NONE) return false; + // Raw INA226: negative = charging. Threshold in raw terms = -0.05A. + data.valueAsBool = amps < -CHARGING_CURRENT_THRESHOLD_AMPS; + return true; + } + + default: + return false; + } + } + + bool supportsChargeControl() const override { return ioExpander1 != nullptr; } + + bool isAllowedToCharge() const override { return chargingAllowed; } + + void setAllowedToCharge(bool allowed) override { + if (ioExpander1 == nullptr) return; + auto* pin = gpio_descriptor_acquire(ioExpander1, GPIO_EXP1_PIN_IP2326_CHG_EN, GPIO_OWNER_GPIO); + if (pin == nullptr) { + LOG_W(TAG, "Failed to acquire CHG_EN pin"); + return; + } + if (gpio_descriptor_set_flags(pin, GPIO_FLAG_DIRECTION_OUTPUT) != ERROR_NONE) { + LOG_W(TAG, "Failed to set CHG_EN pin direction"); + gpio_descriptor_release(pin); + return; + } + if (gpio_descriptor_set_level(pin, allowed) != ERROR_NONE) { + LOG_W(TAG, "Failed to set CHG_EN pin level"); + gpio_descriptor_release(pin); + return; + } + gpio_descriptor_release(pin); + chargingAllowed = allowed; + } + + bool supportsQuickCharge() const override { return ioExpander1 != nullptr; } + + bool isQuickChargeEnabled() const override { return quickChargeEnabled; } + + void setQuickChargeEnabled(bool enabled) override { + if (ioExpander1 == nullptr) return; + auto* pin = gpio_descriptor_acquire(ioExpander1, GPIO_EXP1_PIN_IP2326_NCHG_QC_EN, GPIO_OWNER_GPIO); + if (pin == nullptr) { + LOG_W(TAG, "Failed to acquire nCHG_QC_EN pin"); + return; + } + if (gpio_descriptor_set_flags(pin, GPIO_FLAG_DIRECTION_OUTPUT) != ERROR_NONE) { + LOG_W(TAG, "Failed to set nCHG_QC_EN pin direction"); + gpio_descriptor_release(pin); + return; + } + if (gpio_descriptor_set_level(pin, !enabled) != ERROR_NONE) { + LOG_W(TAG, "Failed to set nCHG_QC_EN pin level"); + gpio_descriptor_release(pin); + return; + } + gpio_descriptor_release(pin); + quickChargeEnabled = enabled; + } + + bool supportsPowerOff() const override { return ioExpander1 != nullptr; } + + void powerOff() override { + if (ioExpander1 == nullptr) return; + auto* pin = gpio_descriptor_acquire(ioExpander1, GPIO_EXP1_PIN_DEVICE_POWER, GPIO_OWNER_GPIO); + if (pin == nullptr) { + LOG_E(TAG, "Failed to acquire DEVICE_POWER pin"); + return; + } + for (int i = 0; i < 3; i++) { + gpio_descriptor_set_level(pin, true); + vTaskDelay(pdMS_TO_TICKS(100)); + gpio_descriptor_set_level(pin, false); + vTaskDelay(pdMS_TO_TICKS(100)); + } + gpio_descriptor_release(pin); + } + +private: + ::Device* ina226; + ::Device* ioExpander1; + bool chargingAllowed = true; + bool quickChargeEnabled = false; +}; + +std::shared_ptr createPower() { + auto* ina226 = device_find_by_name("ina226"); + if (ina226 == nullptr) { + LOG_E(TAG, "ina226 device not found"); + } + auto* io_expander1 = device_find_by_name("io_expander1"); + if (io_expander1 == nullptr) { + LOG_E(TAG, "io_expander1 not found"); + } + return std::make_shared(ina226, io_expander1); +} diff --git a/Devices/m5stack-tab5/Source/devices/Power.h b/Devices/m5stack-tab5/Source/devices/Power.h new file mode 100644 index 000000000..7598ded6f --- /dev/null +++ b/Devices/m5stack-tab5/Source/devices/Power.h @@ -0,0 +1,6 @@ +#pragma once + +#include +#include + +std::shared_ptr createPower(); diff --git a/Devices/m5stack-tab5/Source/module.cpp b/Devices/m5stack-tab5/Source/module.cpp index bad9c9c94..17174a07a 100644 --- a/Devices/m5stack-tab5/Source/module.cpp +++ b/Devices/m5stack-tab5/Source/module.cpp @@ -1,14 +1,113 @@ #include +#include +#include +#include + +#include +#include + +#include + +#define TAG "Tab5" + +constexpr auto GPIO_EXP0_PIN_SPEAKER_ENABLE = 1; +constexpr auto GPIO_EXP0_PIN_HEADPHONE_DETECT = 7; +constexpr auto HP_DETECT_POLL_MS = 1000; + +// hp_detect_timer is only touched from start()/stop(), which are called serially +// by the module manager — no atomic needed for the handle itself. +static TimerHandle_t hp_detect_timer = nullptr; +static std::atomic io_expander0_cached { nullptr }; +// Flags are written by the timer daemon task and read by start()/stop() — use atomics. +static std::atomic hp_detect_last { false }; +static std::atomic hp_detect_initialized { false }; + +static void headphoneDetectCallback(TimerHandle_t /*timer*/) { + Device* cached = io_expander0_cached.load(std::memory_order_acquire); + if (!cached) { + cached = device_find_by_name("io_expander0"); + io_expander0_cached.store(cached, std::memory_order_release); + } + auto* io_expander0 = cached; + if (!io_expander0) { + return; // Not ready yet, will retry on next tick + } + + auto* hp_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_HEADPHONE_DETECT, GPIO_OWNER_GPIO); + if (!hp_pin) { + LOG_W(TAG, "hp_detect: HP_DET pin busy"); + return; + } + + bool hp = false; + error_t err = gpio_descriptor_get_level(hp_pin, &hp); + gpio_descriptor_release(hp_pin); + + if (err != ERROR_NONE) { + LOG_W(TAG, "hp_detect: HP_DET read error: %s", error_to_string(err)); + return; + } + + LOG_D(TAG, "hp_detect: HP_DET=%d", (int)hp); + + if (!hp_detect_initialized || hp != hp_detect_last) { + auto* spk_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_SPEAKER_ENABLE, GPIO_OWNER_GPIO); + if (!spk_pin) { + LOG_W(TAG, "hp_detect: SPK_EN pin busy, will retry"); + return; + } + error_t spk_err = gpio_descriptor_set_level(spk_pin, !hp); + gpio_descriptor_release(spk_pin); + if (spk_err != ERROR_NONE) { + LOG_W(TAG, "hp_detect: SPK_EN set error: %s, will retry", error_to_string(spk_err)); + return; + } + hp_detect_last = hp; + hp_detect_initialized = true; + LOG_I(TAG, "Headphones %s, speaker %s", hp ? "detected" : "removed", hp ? "disabled" : "enabled"); + } +} extern "C" { static error_t start() { - // Empty for now + + if (hp_detect_timer != nullptr) { + LOG_W(TAG, "hp_detect timer already running"); + return ERROR_NONE; + } + + hp_detect_initialized = false; + hp_detect_last = false; + + hp_detect_timer = xTimerCreate("hp_detect", pdMS_TO_TICKS(HP_DETECT_POLL_MS), pdTRUE, nullptr, headphoneDetectCallback); + if (!hp_detect_timer) { + LOG_E(TAG, "Failed to create hp_detect timer"); + return ERROR_RESOURCE; + } + if (xTimerStart(hp_detect_timer, pdMS_TO_TICKS(100)) != pdPASS) { + LOG_E(TAG, "Failed to start hp_detect timer"); + xTimerDelete(hp_detect_timer, pdMS_TO_TICKS(100)); + hp_detect_timer = nullptr; + return ERROR_RESOURCE; + } return ERROR_NONE; } static error_t stop() { - // Empty for now + if (hp_detect_timer == nullptr) { + return ERROR_NONE; + } + if (xTimerStop(hp_detect_timer, pdMS_TO_TICKS(100)) != pdPASS) { + LOG_W(TAG, "Failed to stop hp_detect timer"); + } + if (xTimerDelete(hp_detect_timer, pdMS_TO_TICKS(100)) != pdPASS) { + LOG_E(TAG, "Failed to delete hp_detect timer"); + } + // Always clear the handle — stale non-null handle is worse than a resource leak, + // as it would cause start() to silently skip re-creating the timer. + hp_detect_timer = nullptr; + io_expander0_cached.store(nullptr, std::memory_order_release); return ERROR_NONE; } diff --git a/Devices/m5stack-tab5/device.properties b/Devices/m5stack-tab5/device.properties index a9df7aac5..65fe43dc3 100644 --- a/Devices/m5stack-tab5/device.properties +++ b/Devices/m5stack-tab5/device.properties @@ -41,3 +41,9 @@ CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D3_4BIT_BUS_SLOT_1=8 CONFIG_ESP_HOSTED_SDIO_GPIO_RESET_SLAVE=15 # Fixes recent changes to esp_hosted CONFIG_ESP_HOSTED_USE_MEMPOOL=n +# Performance: larger L2 cache reduces PSRAM stalls for draw/DPI buffers +CONFIG_CACHE_L2_CACHE_256KB=y +# Performance: use P4's PPA (pixel processing accelerator for rotation) +CONFIG_LVGL_PORT_ENABLE_PPA=y +CONFIG_LV_DRAW_BUF_ALIGN=64 +CONFIG_LV_DEF_REFR_PERIOD=15 diff --git a/Devices/m5stack-tab5/devicetree.yaml b/Devices/m5stack-tab5/devicetree.yaml index d6cb798ab..e3f5b1abb 100644 --- a/Devices/m5stack-tab5/devicetree.yaml +++ b/Devices/m5stack-tab5/devicetree.yaml @@ -2,5 +2,6 @@ dependencies: - Platforms/platform-esp32 - Drivers/pi4ioe5v6408-module - Drivers/bmi270-module +- Drivers/ina226-module - Drivers/rx8130ce-module dts: m5stack,tab5.dts diff --git a/Devices/m5stack-tab5/m5stack,tab5.dts b/Devices/m5stack-tab5/m5stack,tab5.dts index d92ca4123..1611b1b36 100644 --- a/Devices/m5stack-tab5/m5stack,tab5.dts +++ b/Devices/m5stack-tab5/m5stack,tab5.dts @@ -1,12 +1,14 @@ /dts-v1/; #include +#include #include #include #include #include -#include +#include #include +#include #include #include @@ -49,6 +51,12 @@ compatible = "epson,rx8130ce"; reg = <0x32>; }; + + ina226 { + compatible = "ti,ina226"; + reg = <0x41>; + shunt-milliohms = <5>; + }; }; i2c_port_a: i2c1 { @@ -77,4 +85,12 @@ pin-data-in = <&gpio0 28 GPIO_FLAG_NONE>; pin-mclk = <&gpio0 30 GPIO_FLAG_NONE>; }; + + uart_port_a: uart1 { + compatible = "espressif,esp32-uart"; + status = "disabled"; + port = ; + pin-tx = <&gpio0 53 GPIO_FLAG_NONE>; + pin-rx = <&gpio0 54 GPIO_FLAG_NONE>; + }; }; diff --git a/Devices/unphone/device.properties b/Devices/unphone/device.properties index aa9afbee9..6ff02cfa3 100644 --- a/Devices/unphone/device.properties +++ b/Devices/unphone/device.properties @@ -11,6 +11,7 @@ flashSize=8MB spiRam=true spiRamMode=OCT spiRamSpeed=80M +bluetooth=true [display] size=3.5" diff --git a/Devices/unphone/unphone.dts b/Devices/unphone/unphone.dts index 3e6ad9e21..5dcc2ef58 100644 --- a/Devices/unphone/unphone.dts +++ b/Devices/unphone/unphone.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -9,6 +10,10 @@ compatible = "root"; model = "unPhone"; + ble0 { + compatible = "espressif,esp32-ble"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <49>; diff --git a/Devices/waveshare-esp32-s3-geek/device.properties b/Devices/waveshare-esp32-s3-geek/device.properties index 6861b7255..5c12f0770 100644 --- a/Devices/waveshare-esp32-s3-geek/device.properties +++ b/Devices/waveshare-esp32-s3-geek/device.properties @@ -15,6 +15,7 @@ spiRamMode=QUAD spiRamSpeed=120M tinyUsb=true esptoolFlashFreq=120M +bluetooth=true [display] size=1.14" diff --git a/Devices/waveshare-esp32-s3-geek/waveshare,esp32-s3-geek.dts b/Devices/waveshare-esp32-s3-geek/waveshare,esp32-s3-geek.dts index 28b3c9327..dc51c36f0 100644 --- a/Devices/waveshare-esp32-s3-geek/waveshare,esp32-s3-geek.dts +++ b/Devices/waveshare-esp32-s3-geek/waveshare,esp32-s3-geek.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -11,6 +12,10 @@ compatible = "root"; model = "Waveshare ESP32-S3-Geek"; + ble0 { + compatible = "espressif,esp32-ble"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <49>; diff --git a/Devices/waveshare-s3-lcd-13/device.properties b/Devices/waveshare-s3-lcd-13/device.properties index 918f3fd93..f22f10836 100644 --- a/Devices/waveshare-s3-lcd-13/device.properties +++ b/Devices/waveshare-s3-lcd-13/device.properties @@ -15,6 +15,7 @@ spiRamMode=OCT spiRamSpeed=120M tinyUsb=true esptoolFlashFreq=120M +bluetooth=true [display] size=1.3" diff --git a/Devices/waveshare-s3-lcd-13/waveshare,s3-lcd-13.dts b/Devices/waveshare-s3-lcd-13/waveshare,s3-lcd-13.dts index 0acee4dfe..0d11d1bfa 100644 --- a/Devices/waveshare-s3-lcd-13/waveshare,s3-lcd-13.dts +++ b/Devices/waveshare-s3-lcd-13/waveshare,s3-lcd-13.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -11,6 +12,10 @@ compatible = "root"; model = "Waveshare S3 LCD 1.3"; + ble0 { + compatible = "espressif,esp32-ble"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <49>; diff --git a/Devices/waveshare-s3-touch-lcd-128/device.properties b/Devices/waveshare-s3-touch-lcd-128/device.properties index 9038d7875..055d736a0 100644 --- a/Devices/waveshare-s3-touch-lcd-128/device.properties +++ b/Devices/waveshare-s3-touch-lcd-128/device.properties @@ -15,6 +15,7 @@ spiRamMode=QUAD spiRamSpeed=120M tinyUsb=true esptoolFlashFreq=120M +bluetooth=true [display] size=1.28" diff --git a/Devices/waveshare-s3-touch-lcd-128/waveshare,s3-touch-lcd-128.dts b/Devices/waveshare-s3-touch-lcd-128/waveshare,s3-touch-lcd-128.dts index 5a9ca28a7..5a781dd60 100644 --- a/Devices/waveshare-s3-touch-lcd-128/waveshare,s3-touch-lcd-128.dts +++ b/Devices/waveshare-s3-touch-lcd-128/waveshare,s3-touch-lcd-128.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -11,6 +12,10 @@ compatible = "root"; model = "Waveshare S3 Touch LCD 1.28"; + ble0 { + compatible = "espressif,esp32-ble"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <49>; diff --git a/Devices/waveshare-s3-touch-lcd-147/device.properties b/Devices/waveshare-s3-touch-lcd-147/device.properties index df7fb19e7..645596849 100644 --- a/Devices/waveshare-s3-touch-lcd-147/device.properties +++ b/Devices/waveshare-s3-touch-lcd-147/device.properties @@ -15,6 +15,7 @@ spiRamMode=OCT spiRamSpeed=120M tinyUsb=true esptoolFlashFreq=120M +bluetooth=true [display] size=1.47" diff --git a/Devices/waveshare-s3-touch-lcd-147/waveshare,s3-touch-lcd-147.dts b/Devices/waveshare-s3-touch-lcd-147/waveshare,s3-touch-lcd-147.dts index 620061c3e..8afd056cc 100644 --- a/Devices/waveshare-s3-touch-lcd-147/waveshare,s3-touch-lcd-147.dts +++ b/Devices/waveshare-s3-touch-lcd-147/waveshare,s3-touch-lcd-147.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -10,6 +11,10 @@ compatible = "root"; model = "Waveshare S3 Touch LCD 1.47"; + ble0 { + compatible = "espressif,esp32-ble"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <49>; diff --git a/Devices/waveshare-s3-touch-lcd-43/device.properties b/Devices/waveshare-s3-touch-lcd-43/device.properties index 1d373a5aa..00a1e3f79 100644 --- a/Devices/waveshare-s3-touch-lcd-43/device.properties +++ b/Devices/waveshare-s3-touch-lcd-43/device.properties @@ -13,6 +13,7 @@ spiRamMode=OCT spiRamSpeed=120M tinyUsb=true esptoolFlashFreq=120M +bluetooth=true [display] size=4.3" diff --git a/Devices/waveshare-s3-touch-lcd-43/waveshare,s3-touch-lcd-43.dts b/Devices/waveshare-s3-touch-lcd-43/waveshare,s3-touch-lcd-43.dts index 6f0b8213f..22513fa40 100644 --- a/Devices/waveshare-s3-touch-lcd-43/waveshare,s3-touch-lcd-43.dts +++ b/Devices/waveshare-s3-touch-lcd-43/waveshare,s3-touch-lcd-43.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -10,6 +11,10 @@ compatible = "root"; model = "Waveshare ESP32-S3-Touch-LCD-4.3"; + ble0 { + compatible = "espressif,esp32-ble"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <49>; diff --git a/Devices/wireless-tag-wt32-sc01-plus/Source/devices/Display.cpp b/Devices/wireless-tag-wt32-sc01-plus/Source/devices/Display.cpp index f92c13d05..6176b3244 100644 --- a/Devices/wireless-tag-wt32-sc01-plus/Source/devices/Display.cpp +++ b/Devices/wireless-tag-wt32-sc01-plus/Source/devices/Display.cpp @@ -10,13 +10,16 @@ constexpr auto LCD_VERTICAL_RESOLUTION = 480; std::shared_ptr createTouch() { auto configuration = std::make_unique( I2C_NUM_0, - GPIO_NUM_7, LCD_HORIZONTAL_RESOLUTION, - LCD_VERTICAL_RESOLUTION + LCD_VERTICAL_RESOLUTION, + false, + false, + false, + GPIO_NUM_NC, + GPIO_NUM_7 ); - auto touch = std::make_shared(std::move(configuration)); - return std::static_pointer_cast(touch); + return std::make_shared(std::move(configuration)); } std::shared_ptr createDisplay() { diff --git a/Devices/wireless-tag-wt32-sc01-plus/device.properties b/Devices/wireless-tag-wt32-sc01-plus/device.properties index 32066d887..c46939aee 100644 --- a/Devices/wireless-tag-wt32-sc01-plus/device.properties +++ b/Devices/wireless-tag-wt32-sc01-plus/device.properties @@ -13,6 +13,7 @@ spiRamMode=QUAD spiRamSpeed=80M tinyUsb=true esptoolFlashFreq=80M +bluetooth=true [display] size=3.5" diff --git a/Devices/wireless-tag-wt32-sc01-plus/wireless-tag,wt32-sc01-plus.dts b/Devices/wireless-tag-wt32-sc01-plus/wireless-tag,wt32-sc01-plus.dts index d10e336f2..6beff46d9 100644 --- a/Devices/wireless-tag-wt32-sc01-plus/wireless-tag,wt32-sc01-plus.dts +++ b/Devices/wireless-tag-wt32-sc01-plus/wireless-tag,wt32-sc01-plus.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -9,6 +10,10 @@ compatible = "root"; model = "Wireless-Tag WT32-SC01 Plus"; + ble0 { + compatible = "espressif,esp32-ble"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <49>; diff --git a/Drivers/FT6x36/CMakeLists.txt b/Drivers/FT6x36/CMakeLists.txt index 811725f25..83b9fcc06 100644 --- a/Drivers/FT6x36/CMakeLists.txt +++ b/Drivers/FT6x36/CMakeLists.txt @@ -1,7 +1,5 @@ -file(GLOB_RECURSE SOURCE_FILES Source/*.c*) - idf_component_register( - SRCS ${SOURCE_FILES} + SRC_DIRS "Source" INCLUDE_DIRS "Source" - REQUIRES Tactility esp_lvgl_port driver + REQUIRES Tactility EspLcdCompat esp_lcd_touch_ft6336u driver ) diff --git a/Drivers/FT6x36/Source/Ft6x36Touch.cpp b/Drivers/FT6x36/Source/Ft6x36Touch.cpp index 0ff7848af..45a7fdb8b 100644 --- a/Drivers/FT6x36/Source/Ft6x36Touch.cpp +++ b/Drivers/FT6x36/Source/Ft6x36Touch.cpp @@ -1,134 +1,36 @@ #include "Ft6x36Touch.h" -#include -#include - +#include #include #include -static const auto LOGGER = tt::Logger("FT6x36"); - -void Ft6x36Touch::touchReadCallback(lv_indev_t* indev, lv_indev_data_t* data) { - auto* touch = (Ft6x36Touch*)lv_indev_get_driver_data(indev); - touch->mutex.lock(); - data->point = touch->lastPoint; - data->state = touch->lastState; - touch->mutex.unlock(); -} - -Ft6x36Touch::Ft6x36Touch(std::unique_ptr inConfiguration) : - configuration(std::move(inConfiguration)) { - nativeTouch = std::make_shared(*this); -} - -Ft6x36Touch::~Ft6x36Touch() { - if (driverThread != nullptr && driverThread->getState() != tt::Thread::State::Stopped) { - interruptDriverThread = true; - driverThread->join(); - } -} - -void Ft6x36Touch::driverThreadMain() { - TPoint point = { .x = 0, .y = 0 }; - TEvent event = TEvent::None; - - while (!shouldInterruptDriverThread()) { - driver.processTouch(); - driver.poll(&point, &event); - - if (mutex.lock(100)) { - switch (event) { - case TEvent::TouchStart: - case TEvent::TouchMove: - case TEvent::DragStart: - case TEvent::DragMove: - case TEvent::DragEnd: - lastState = LV_INDEV_STATE_PR; - lastPoint.x = point.x; - lastPoint.y = point.y; - break; - case TEvent::TouchEnd: - lastState = LV_INDEV_STATE_REL; - lastPoint.x = point.x; - lastPoint.y = point.y; - break; - case TEvent::Tap: - case TEvent::None: - break; - } - mutex.unlock(); - } - } -} - -bool Ft6x36Touch::shouldInterruptDriverThread() const { - bool interrupt = false; - if (mutex.lock(50 / portTICK_PERIOD_MS)) { - interrupt = interruptDriverThread; - mutex.unlock(); - } - return interrupt; -} - -bool Ft6x36Touch::start() { - LOGGER.info("Start"); - - if (!driver.begin(FT6X36_DEFAULT_THRESHOLD, configuration->width, configuration->height)) { - LOGGER.error("driver.begin() failed"); - return false; - } - - mutex.lock(); - - interruptDriverThread = false; - - driverThread = std::make_shared("ft6x36", 4096, [this] { - driverThreadMain(); - return 0; - }); - - driverThread->start(); - - mutex.unlock(); - - return true; -} - -bool Ft6x36Touch::stop() { - LOGGER.info("Stop"); - - mutex.lock(); - interruptDriverThread = true; - mutex.unlock(); - - driverThread->join(); - - mutex.lock(); - driverThread = nullptr; - mutex.unlock(); - - return false; -} - -bool Ft6x36Touch::startLvgl(lv_display_t* display) { - if (deviceHandle != nullptr) { - return false; - } - - deviceHandle = lv_indev_create(); - lv_indev_set_type(deviceHandle, LV_INDEV_TYPE_POINTER); - lv_indev_set_driver_data(deviceHandle, this); - lv_indev_set_read_cb(deviceHandle, touchReadCallback); - - return true; -} - -bool Ft6x36Touch::stopLvgl() { - if (deviceHandle == nullptr) { - return false; - } - - lv_indev_delete(deviceHandle); - deviceHandle = nullptr; - return true; +bool Ft6x36Touch::createIoHandle(esp_lcd_panel_io_handle_t& outHandle) { + esp_lcd_panel_io_i2c_config_t io_config = ESP_LCD_TOUCH_IO_I2C_FT6x36_CONFIG(); + return esp_lcd_new_panel_io_i2c(configuration->port, &io_config, &outHandle) == ESP_OK; +} + +bool Ft6x36Touch::createTouchHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_touch_config_t& configuration, esp_lcd_touch_handle_t& panelHandle) { + return esp_lcd_touch_new_i2c_ft6x36(ioHandle, &configuration, &panelHandle) == ESP_OK; +} + +esp_lcd_touch_config_t Ft6x36Touch::createEspLcdTouchConfig() { + return { + .x_max = configuration->xMax, + .y_max = configuration->yMax, + .rst_gpio_num = configuration->pinReset, + .int_gpio_num = configuration->pinInterrupt, + .levels = { + .reset = configuration->pinResetLevel, + .interrupt = configuration->pinInterruptLevel, + }, + .flags = { + .swap_xy = configuration->swapXy, + .mirror_x = configuration->mirrorX, + .mirror_y = configuration->mirrorY, + }, + .process_coordinates = nullptr, + .interrupt_callback = nullptr, + .user_data = nullptr, + .driver_data = nullptr + }; } diff --git a/Drivers/FT6x36/Source/Ft6x36Touch.h b/Drivers/FT6x36/Source/Ft6x36Touch.h index bbeb90232..b1bfd8540 100644 --- a/Drivers/FT6x36/Source/Ft6x36Touch.h +++ b/Drivers/FT6x36/Source/Ft6x36Touch.h @@ -2,12 +2,11 @@ #include #include -#include #include -#include "ft6x36/FT6X36.h" +#include -class Ft6x36Touch final : public tt::hal::touch::TouchDevice { +class Ft6x36Touch final : public EspLcdTouch { public: @@ -16,78 +15,56 @@ class Ft6x36Touch final : public tt::hal::touch::TouchDevice { Configuration( i2c_port_t port, - gpio_num_t pinInterrupt, - uint16_t width, - uint16_t height + uint16_t xMax, + uint16_t yMax, + bool swapXy = false, + bool mirrorX = false, + bool mirrorY = false, + gpio_num_t pinReset = GPIO_NUM_NC, + gpio_num_t pinInterrupt = GPIO_NUM_NC, + unsigned int pinResetLevel = 0, + unsigned int pinInterruptLevel = 0 ) : port(port), + xMax(xMax), + yMax(yMax), + swapXy(swapXy), + mirrorX(mirrorX), + mirrorY(mirrorY), + pinReset(pinReset), pinInterrupt(pinInterrupt), - width(width), - height(height) + pinResetLevel(pinResetLevel), + pinInterruptLevel(pinInterruptLevel) {} i2c_port_t port; + uint16_t xMax; + uint16_t yMax; + bool swapXy; + bool mirrorX; + bool mirrorY; + gpio_num_t pinReset; gpio_num_t pinInterrupt; - uint16_t width; - uint16_t height; -}; + unsigned int pinResetLevel; + unsigned int pinInterruptLevel; + }; private: std::unique_ptr configuration; - lv_indev_t* deviceHandle = nullptr; - FT6X36 driver = FT6X36(configuration->port, configuration->pinInterrupt); - std::shared_ptr driverThread; - bool interruptDriverThread = false; - tt::Mutex mutex; - std::shared_ptr nativeTouch; - - lv_point_t lastPoint = { .x = 0, .y = 0 }; - lv_indev_state_t lastState = LV_INDEV_STATE_RELEASED; - bool shouldInterruptDriverThread() const; + bool createIoHandle(esp_lcd_panel_io_handle_t& outHandle) override; - void driverThreadMain(); + bool createTouchHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_touch_config_t& configuration, esp_lcd_touch_handle_t& panelHandle) override; - static void touchReadCallback(lv_indev_t* indev, lv_indev_data_t* data); + esp_lcd_touch_config_t createEspLcdTouchConfig() override; public: - explicit Ft6x36Touch(std::unique_ptr inConfiguration); - ~Ft6x36Touch() override; + explicit Ft6x36Touch(std::unique_ptr inConfiguration) : configuration(std::move(inConfiguration)) { + assert(configuration != nullptr); + } std::string getName() const override { return "FT6x36"; } - std::string getDescription() const override { return "FT6x36 I2C touch driver"; } - - bool start() override; - bool stop() override; - - bool supportsLvgl() const override { return true; } - bool startLvgl(lv_display_t* display) override; - bool stopLvgl() override; - lv_indev_t* getLvglIndev() override { return deviceHandle; } - - class Ft6TouchDriver : public tt::hal::touch::TouchDriver { - public: - const Ft6x36Touch& parent; - Ft6TouchDriver(const Ft6x36Touch& parent) : parent(parent) {} - - bool getTouchedPoints(uint16_t* x, uint16_t* y, uint16_t* _Nullable strength, uint8_t* pointCount, uint8_t maxPointCount) { - auto lock = parent.mutex.asScopedLock(); - lock.lock(); - if (parent.lastState == LV_INDEV_STATE_PRESSED) { - *x = parent.lastPoint.x; - *y = parent.lastPoint.y; - *pointCount = 1; - return true; - } else { - *pointCount = 0; - return false; - } - } - }; - - bool supportsTouchDriver() override { return true; } - - std::shared_ptr _Nullable getTouchDriver() override { return nativeTouch; } + std::string getDescription() const override { return "FT6x36 I2C touch driver"; } }; diff --git a/Drivers/FT6x36/Source/ft6x36/FT6X36.cpp b/Drivers/FT6x36/Source/ft6x36/FT6X36.cpp deleted file mode 100644 index 48ba32fef..000000000 --- a/Drivers/FT6x36/Source/ft6x36/FT6X36.cpp +++ /dev/null @@ -1,378 +0,0 @@ -#include "FT6X36.h" - -#include "freertos/FreeRTOS.h" - -#define CONFIG_FT6X36_DEBUG false - -FT6X36 *FT6X36::_instance = nullptr; -static const char *TAG = "i2c-touch"; - -//Handle indicating I2C is ready to read the touch -SemaphoreHandle_t TouchSemaphore = xSemaphoreCreateBinary(); - -FT6X36::FT6X36(i2c_port_t port, gpio_num_t interruptPin) -{ - _instance = this; - _port = port; - _intPin = interruptPin; -} - -// Destructor should detach interrupt to the pin -FT6X36::~FT6X36() -{ - if (_intPin >= 0) - gpio_isr_handler_remove((gpio_num_t)_intPin); -} - -bool FT6X36::begin(uint8_t threshold, uint16_t width, uint16_t height) -{ - _touch_width = width; - _touch_height = height; - if (width == 0 || height ==0) { - ESP_LOGE(TAG,"begin(uint8_t threshold, uint16_t width, uint16_t height) did not receive the width / height so touch cannot be rotation aware"); - } - - uint8_t data_panel_id; - readRegister8(FT6X36_REG_PANEL_ID, &data_panel_id); - - if (data_panel_id != FT6X36_VENDID) { - ESP_LOGE(TAG,"FT6X36_VENDID does not match. Received:0x%x Expected:0x%x\n",data_panel_id,FT6X36_VENDID); - return false; - } - ESP_LOGI(TAG, "\tDevice ID: 0x%02x", data_panel_id); - - uint8_t chip_id; - readRegister8(FT6X36_REG_CHIPID, &chip_id); - if (chip_id != FT6206_CHIPID && chip_id != FT6236_CHIPID && chip_id != FT6336_CHIPID) { - ESP_LOGE(TAG,"FT6206_CHIPID does not match. Received:0x%x\n",chip_id); - return false; - } - ESP_LOGI(TAG, "\tFound touch controller with Chip ID: 0x%02x", chip_id); - - if (_intPin >= 0) - { - // INT pin triggers the callback function on the Falling edge of the GPIO - gpio_config_t io_conf; - io_conf.intr_type = GPIO_INTR_NEGEDGE; // GPIO_INTR_NEGEDGE repeats always interrupt - io_conf.pin_bit_mask = 1ULL<<_intPin; - io_conf.mode = GPIO_MODE_INPUT; - io_conf.pull_down_en = (gpio_pulldown_t) 0; // disable pull-down mode - io_conf.pull_up_en = (gpio_pullup_t) 1; // pull-up mode - gpio_config(&io_conf); - - esp_err_t isr_service = gpio_install_isr_service(0); - printf("ISR trigger install response: 0x%x %s\n", isr_service, (isr_service==0)?"ESP_OK":""); - gpio_isr_handler_add((gpio_num_t)_intPin, isr, (void*) 1); - } - - writeRegister8(FT6X36_REG_DEVICE_MODE, 0x00); - writeRegister8(FT6X36_REG_THRESHHOLD, threshold); - writeRegister8(FT6X36_REG_TOUCHRATE_ACTIVE, 0x0E); - return true; -} - -void FT6X36::registerTouchHandler(void (*fn)(TPoint point, TEvent e)) -{ - _touchHandler = fn; - if (CONFIG_FT6X36_DEBUG) printf("Touch handler function registered\n"); -} - -uint8_t FT6X36::touched() -{ - uint8_t data_buf; - esp_err_t ret = readRegister8(FT6X36_REG_NUM_TOUCHES, &data_buf); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Error reading from device: %s", esp_err_to_name(ret)); - } - - if (data_buf > 2) - { - data_buf = 0; - } - - return data_buf; -} - -void FT6X36::loop() -{ - processTouch(); -} - -void IRAM_ATTR FT6X36::isr(void* arg) -{ - /* Un-block the interrupt processing task now */ - xSemaphoreGive(TouchSemaphore); - //xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL); -} - -void FT6X36::processTouch() -{ - /* Task move to Block state to wait for interrupt event */ - if (_intPin >= 0) - { - if (xSemaphoreTake(TouchSemaphore, portMAX_DELAY) == false) return; - } - - readData(); - uint8_t n = 0; - TRawEvent event = (TRawEvent)_touchEvent[n]; - TPoint point{_touchX[n], _touchY[n]}; - - switch (event) { - - case TRawEvent::PressDown: - _points[0] = point; - _dragMode = false; - // Note: Is in microseconds. Ref https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/esp_timer.html - _touchStartTime = esp_timer_get_time()/1000; - fireEvent(point, TEvent::TouchStart); - break; - - case TRawEvent::Contact: - // Dragging makes no sense IMHO. Since the X & Y are not getting updated while dragging - // Dragging && _points[0].aboutEqual(point) - Not used IDEA 2: && (lastEvent == 2) - if (!_dragMode && - (abs(lastX-_touchX[n]) <= maxDeviation || abs(lastY-_touchY[n])<=maxDeviation) && - esp_timer_get_time()/1000 - _touchStartTime > 300) { - _dragMode = true; - fireEvent(point, TEvent::DragStart); - #if defined(CONFIG_FT6X36_DEBUG_EVENTS) && CONFIG_FT6X36_DEBUG_EVENTS==1 - printf("EV: DragStart\n"); - #endif - - } else if (_dragMode) { - fireEvent(point, TEvent::DragMove); - #if defined(CONFIG_FT6X36_DEBUG_EVENTS) && CONFIG_FT6X36_DEBUG_EVENTS==1 - printf("EV: DragMove\n"); - #endif - } - fireEvent(point, TEvent::TouchMove); - - // For me the _touchStartTime shouold be set in both PressDown & Contact events, but after Drag detection - _touchStartTime = esp_timer_get_time()/1000; - break; - - case TRawEvent::LiftUp: - - _points[9] = point; - _touchEndTime = esp_timer_get_time()/1000; - - //printf("TIMEDIFF: %lu End: %lu\n", _touchEndTime - _touchStartTime, _touchEndTime); - - fireEvent(point, TEvent::TouchEnd); - if (_dragMode) { - fireEvent(point, TEvent::DragEnd); - #if defined(CONFIG_FT6X36_DEBUG_EVENTS) && CONFIG_FT6X36_DEBUG_EVENTS==1 - printf("EV: DragEnd\n"); - #endif - _dragMode = false; - } - - if ( _touchEndTime - _touchStartTime <= 900) { - // Do not get why this: _points[0].aboutEqual(point) (Original library) - fireEvent(point, TEvent::Tap); - _points[0] = {0, 0}; - _touchStartTime = 0; - - #if defined(CONFIG_FT6X36_DEBUG_EVENTS) && CONFIG_FT6X36_DEBUG_EVENTS==1 - printf("EV: Tap\n"); - #endif - _dragMode = false; - } - - break; - - case TRawEvent::NoEvent: - #if defined(CONFIG_FT6X36_DEBUG_EVENTS) && CONFIG_FT6X36_DEBUG_EVENTS==1 - printf("EV: NoEvent\n"); - #endif - break; - } - // Store lastEvent - lastEvent = (int) event; - lastX = _touchX[0]; - lastY = _touchY[0]; -} - -void FT6X36::poll(TPoint * point, TEvent * e) -{ - readData(); - // TPoint point{_touchX[0], _touchY[0]}; - TRawEvent event = (TRawEvent)_touchEvent[0]; - - if (point != NULL) - { - point->x = _touchX[0]; - point->y = _touchY[0]; - } - if (e != NULL) - { - switch (event) - { - case TRawEvent::PressDown: - *e = TEvent::TouchStart; - break; - case TRawEvent::Contact: - *e = TEvent::TouchMove; - break; - case TRawEvent::LiftUp: - default: - *e = TEvent::TouchEnd; - break; - } - } -} - -uint8_t FT6X36::read8(uint8_t regName) { - uint8_t buf; - readRegister8(regName, &buf); - return buf; -} - -#define data_size 16 // Discarding last 2: 0x0E & 0x0F as not relevant -bool FT6X36::readData(void) -{ - esp_err_t ret; - uint8_t data[data_size]; - uint8_t touch_pnt_cnt; // Number of detected touch points - readRegister8(FT6X36_REG_NUM_TOUCHES, &touch_pnt_cnt); - - // Read data - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); - i2c_master_start(cmd); - i2c_master_write_byte(cmd, (FT6X36_ADDR<<1), ACK_CHECK_EN); - i2c_master_write_byte(cmd, 0, ACK_CHECK_EN); - i2c_master_stop(cmd); - ret = i2c_master_cmd_begin(_port, cmd, 1000 / portTICK_PERIOD_MS); - i2c_cmd_link_delete(cmd); - if (ret != ESP_OK) { - return ret; - } - - cmd = i2c_cmd_link_create(); - i2c_master_start(cmd); - i2c_master_write_byte(cmd, (FT6X36_ADDR<<1)|1, ACK_CHECK_EN); - i2c_master_read(cmd, data, data_size, I2C_MASTER_LAST_NACK); - i2c_master_stop(cmd); - ret = i2c_master_cmd_begin(_port, cmd, 1000 / portTICK_PERIOD_MS); - i2c_cmd_link_delete(cmd); - - if (CONFIG_FT6X36_DEBUG) { - //printf("REGISTERS:\n"); - for (int16_t i = 0; i < data_size; i++) - { - printf("%x:%x ", i, data[i]); - } - printf("\n"); - } - - const uint8_t addrShift = 6; - - // READ X, Y and Touch events (X 2) - for (uint8_t i = 0; i < 2; i++) - { - _touchX[i] = data[FT6X36_REG_P1_XH + i * addrShift] & 0x0F; - _touchX[i] <<= 8; - _touchX[i] |= data[FT6X36_REG_P1_XL + i * addrShift]; - _touchY[i] = data[FT6X36_REG_P1_YH + i * addrShift] & 0x0F; - _touchY[i] <<= 8; - _touchY[i] |= data[FT6X36_REG_P1_YL + i * addrShift]; - _touchEvent[i] = data[FT6X36_REG_P1_XH + i * addrShift] >> 6; - } - - // Make _touchX[idx] and _touchY[idx] rotation aware - switch (_rotation) - { - case 1: - swap(_touchX[0], _touchY[0]); - swap(_touchX[1], _touchY[1]); - _touchY[0] = _touch_width - _touchY[0] -1; - _touchY[1] = _touch_width - _touchY[1] -1; - break; - case 2: - _touchX[0] = _touch_width - _touchX[0] - 1; - _touchX[1] = _touch_width - _touchX[1] - 1; - _touchY[0] = _touch_height - _touchY[0] - 1; - _touchY[1] = _touch_height - _touchY[1] - 1; - break; - case 3: - swap(_touchX[0], _touchY[0]); - swap(_touchX[1], _touchY[1]); - _touchX[0] = _touch_height - _touchX[0] - 1; - _touchX[1] = _touch_height - _touchX[1] - 1; - break; - } - if (CONFIG_FT6X36_DEBUG) { - printf("X0:%d Y0:%d EVENT:%d\n", _touchX[0], _touchY[0], _touchEvent[0]); - //printf("X1:%d Y1:%d EVENT:%d\n", _touchX[1], _touchY[1], _touchEvent[1]); - } - return true; -} - -void FT6X36::writeRegister8(uint8_t reg, uint8_t value) -{ - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); - i2c_master_start(cmd); - i2c_master_write_byte(cmd, FT6X36_ADDR << 1 | I2C_MASTER_WRITE, ACK_CHECK_EN); - i2c_master_write_byte(cmd, reg , ACK_CHECK_EN); - i2c_master_write_byte(cmd, value , ACK_CHECK_EN); - i2c_master_stop(cmd); - i2c_master_cmd_begin(_port, cmd, 1000 / portTICK_PERIOD_MS); - i2c_cmd_link_delete(cmd); -} - -uint8_t FT6X36::readRegister8(uint8_t reg, uint8_t *data_buf) -{ - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); - i2c_master_start(cmd); - i2c_master_write_byte(cmd, FT6X36_ADDR << 1 | I2C_MASTER_WRITE, ACK_CHECK_EN); - i2c_master_write_byte(cmd, reg, I2C_MASTER_ACK); - // Research: Why it's started a 2nd time here - i2c_master_start(cmd); - i2c_master_write_byte(cmd, (FT6X36_ADDR << 1) | I2C_MASTER_READ, true); - - i2c_master_read_byte(cmd, data_buf, I2C_MASTER_NACK); - i2c_master_stop(cmd); - esp_err_t ret = i2c_master_cmd_begin(_port, cmd, 1000 / portTICK_PERIOD_MS); - i2c_cmd_link_delete(cmd); - - - //FT6X36_REG_GESTURE_ID. Check if it can be read! -#if defined(FT6X36_DEBUG) && FT6X36_DEBUG==1 - printf("REG 0x%x: 0x%x\n",reg,ret); -#endif - - return ret; -} - -void FT6X36::fireEvent(TPoint point, TEvent e) -{ - if (_touchHandler) - _touchHandler(point, e); -} - -void FT6X36::debugInfo() -{ - printf(" TH_DIFF: %d CTRL: %d\n", read8(FT6X36_REG_FILTER_COEF), read8(FT6X36_REG_CTRL)); - printf(" TIMEENTERMONITOR: %d PERIODACTIVE: %d\n", read8(FT6X36_REG_TIME_ENTER_MONITOR), read8(FT6X36_REG_TOUCHRATE_ACTIVE)); - printf(" PERIODMONITOR: %d RADIAN_VALUE: %d\n", read8(FT6X36_REG_TOUCHRATE_MONITOR), read8(FT6X36_REG_RADIAN_VALUE)); - printf(" OFFSET_LEFT_RIGHT: %d OFFSET_UP_DOWN: %d\n", read8(FT6X36_REG_OFFSET_LEFT_RIGHT), read8(FT6X36_REG_OFFSET_UP_DOWN)); - printf("DISTANCE_LEFT_RIGHT: %d DISTANCE_UP_DOWN: %d\n", read8(FT6X36_REG_DISTANCE_LEFT_RIGHT), read8(FT6X36_REG_DISTANCE_UP_DOWN)); - printf(" DISTANCE_ZOOM: %d CIPHER: %d\n", read8(FT6X36_REG_DISTANCE_ZOOM), read8(FT6X36_REG_CHIPID)); - printf(" G_MODE: %d PWR_MODE: %d\n", read8(FT6X36_REG_INTERRUPT_MODE), read8(FT6X36_REG_POWER_MODE)); - printf(" FIRMID: %d FOCALTECH_ID: %d STATE: %d\n", read8(FT6X36_REG_FIRMWARE_VERSION), read8(FT6X36_REG_PANEL_ID), read8(FT6X36_REG_STATE)); -} - -void FT6X36::setRotation(uint8_t rotation) { - _rotation = rotation; -} - -void FT6X36::setTouchWidth(uint16_t width) { - printf("touch w:%d\n",width); - _touch_width = width; -} - -void FT6X36::setTouchHeight(uint16_t height) { - printf("touch h:%d\n",height); - _touch_height = height; -} diff --git a/Drivers/FT6x36/Source/ft6x36/FT6X36.h b/Drivers/FT6x36/Source/ft6x36/FT6X36.h deleted file mode 100644 index dca25c2da..000000000 --- a/Drivers/FT6x36/Source/ft6x36/FT6X36.h +++ /dev/null @@ -1,184 +0,0 @@ -#include -#include - -#include "driver/gpio.h" -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "freertos/semphr.h" -#include "esp_log.h" -#include "driver/i2c.h" -#include "sdkconfig.h" -#include - -#ifndef ft6x36_h -#define ft6x36_h -// I2C Constants -#define I2C_MASTER_TX_BUF_DISABLE 0 /*!< I2C master doesn't need buffer */ -#define I2C_MASTER_RX_BUF_DISABLE 0 /*!< I2C master doesn't need buffer */ - -#define ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/ -#define ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */ -#define ACK_VAL 0x0 /*!< I2C ack value */ -#define NACK_VAL 0x1 /*!< I2C nack value */ - -//SemaphoreHandle_t print_mux = NULL; - -#define FT6X36_ADDR 0x38 - -#define FT6X36_REG_DEVICE_MODE 0x00 -#define FT6X36_REG_GESTURE_ID 0x01 -#define FT6X36_REG_NUM_TOUCHES 0x02 -#define FT6X36_REG_P1_XH 0x03 -#define FT6X36_REG_P1_XL 0x04 -#define FT6X36_REG_P1_YH 0x05 -#define FT6X36_REG_P1_YL 0x06 -#define FT6X36_REG_P1_WEIGHT 0x07 -#define FT6X36_REG_P1_MISC 0x08 -#define FT6X36_REG_P2_XH 0x09 -#define FT6X36_REG_P2_XL 0x0A -#define FT6X36_REG_P2_YH 0x0B -#define FT6X36_REG_P2_YL 0x0C -#define FT6X36_REG_P2_WEIGHT 0x0D -#define FT6X36_REG_P2_MISC 0x0E -#define FT6X36_REG_THRESHHOLD 0x80 -#define FT6X36_REG_FILTER_COEF 0x85 -#define FT6X36_REG_CTRL 0x86 -#define FT6X36_REG_TIME_ENTER_MONITOR 0x87 -#define FT6X36_REG_TOUCHRATE_ACTIVE 0x88 -#define FT6X36_REG_TOUCHRATE_MONITOR 0x89 // value in ms -#define FT6X36_REG_RADIAN_VALUE 0x91 -#define FT6X36_REG_OFFSET_LEFT_RIGHT 0x92 -#define FT6X36_REG_OFFSET_UP_DOWN 0x93 -#define FT6X36_REG_DISTANCE_LEFT_RIGHT 0x94 -#define FT6X36_REG_DISTANCE_UP_DOWN 0x95 -#define FT6X36_REG_DISTANCE_ZOOM 0x96 -#define FT6X36_REG_LIB_VERSION_H 0xA1 -#define FT6X36_REG_LIB_VERSION_L 0xA2 -#define FT6X36_REG_CHIPID 0xA3 -#define FT6X36_REG_INTERRUPT_MODE 0xA4 -#define FT6X36_REG_POWER_MODE 0xA5 -#define FT6X36_REG_FIRMWARE_VERSION 0xA6 -#define FT6X36_REG_PANEL_ID 0xA8 -#define FT6X36_REG_STATE 0xBC - -#define FT6X36_PMODE_ACTIVE 0x00 -#define FT6X36_PMODE_MONITOR 0x01 -#define FT6X36_PMODE_STANDBY 0x02 -#define FT6X36_PMODE_HIBERNATE 0x03 - -/* Possible values returned by FT6X36_GEST_ID_REG */ -#define FT6X36_GEST_ID_NO_GESTURE 0x00 -#define FT6X36_GEST_ID_MOVE_UP 0x10 -#define FT6X36_GEST_ID_MOVE_RIGHT 0x14 -#define FT6X36_GEST_ID_MOVE_DOWN 0x18 -#define FT6X36_GEST_ID_MOVE_LEFT 0x1C -#define FT6X36_GEST_ID_ZOOM_IN 0x48 -#define FT6X36_GEST_ID_ZOOM_OUT 0x49 - -#define FT6X36_VENDID 0x11 -#define FT6206_CHIPID 0x06 -#define FT6236_CHIPID 0x36 -#define FT6336_CHIPID 0x64 - -#define FT6X36_DEFAULT_THRESHOLD 22 - -// From: https://github.com/lvgl/lv_port_esp32/blob/master/components/lvgl_esp32_drivers/lvgl_touch/ft6x36.h -#define FT6X36_MSB_MASK 0x0F -#define FT6X36_LSB_MASK 0xFF - -enum class TRawEvent -{ - PressDown, - LiftUp, - Contact, - NoEvent -}; - -enum class TEvent -{ - None, - TouchStart, - TouchMove, - TouchEnd, - Tap, - DragStart, - DragMove, - DragEnd -}; - -struct TPoint -{ - uint16_t x; - uint16_t y; - /** - * This is being used in the original library but I'm not using it in this implementation - */ - bool aboutEqual(const TPoint point) - { - return abs(x - point.x) <= 5 && abs(y - point.y) <= 5; - } -}; - - - -class FT6X36 -{ - static void IRAM_ATTR isr(void* arg); -public: - // TwoWire * wire will be replaced by ESP-IDF https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2c.html - FT6X36(i2c_port_t = I2C_NUM_0, gpio_num_t interruptPin = GPIO_NUM_NC); - ~FT6X36(); - bool begin(uint8_t threshold = FT6X36_DEFAULT_THRESHOLD, uint16_t width = 0, uint16_t height = 0); - void registerTouchHandler(void(*fn)(TPoint point, TEvent e)); - uint8_t touched(); - void loop(); - void processTouch(); - void debugInfo(); - void poll(TPoint * point, TEvent * event); - // Helper functions to make the touch display aware - void setRotation(uint8_t rotation); - void setTouchWidth(uint16_t width); - void setTouchHeight(uint16_t height); - // Pending implementation. How much x->touch y↓touch is placed (In case is smaller than display) - void setXoffset(uint16_t x_offset); - void setYoffset(uint16_t y_offset); - // Smart template from EPD to swap x,y: - template static inline void - swap(T& a, T& b) - { - T t = a; - a = b; - b = t; - } - void(*_touchHandler)(TPoint point, TEvent e) = nullptr; - - bool readData(void); -private: - void writeRegister8(uint8_t reg, uint8_t val); - uint8_t readRegister8(uint8_t reg, uint8_t *data_buf); - void fireEvent(TPoint point, TEvent e); - uint8_t read8(uint8_t regName); - static FT6X36 * _instance; - - i2c_port_t _port; - int8_t _intPin; - - // Make touch rotation aware: - uint8_t _rotation = 0; - uint16_t _touch_width = 0; - uint16_t _touch_height = 0; - - uint8_t _touches; - uint16_t _touchX[2], _touchY[2], _touchEvent[2]; - TPoint _points[10]; - uint8_t _pointIdx = 0; - unsigned long _touchStartTime = 0; - unsigned long _touchEndTime = 0; - uint8_t lastEvent = 3; // No event - uint16_t lastX = 0; - uint16_t lastY = 0; - bool _dragMode = false; - const uint8_t maxDeviation = 5; -}; - -#endif \ No newline at end of file diff --git a/Drivers/FT6x36/Source/ft6x36/LICENSE b/Drivers/FT6x36/Source/ft6x36/LICENSE deleted file mode 100644 index 607a0afb2..000000000 --- a/Drivers/FT6x36/Source/ft6x36/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2019 strange_v - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/Drivers/FT6x36/Source/ft6x36/README.md b/Drivers/FT6x36/Source/ft6x36/README.md deleted file mode 100644 index a9146b24c..000000000 --- a/Drivers/FT6x36/Source/ft6x36/README.md +++ /dev/null @@ -1,8 +0,0 @@ -This project is an adaption of the code at https://github.com/martinberlin/FT6X36-IDF which is an adaptation of https://github.com/strange-v/FT6X36 -The original license is an MIT license and is included in this directory. - -Changes: -- Remove Kconfig-based configuratio -- Removed I2C init code -- Allow for passing a different I2C port - \ No newline at end of file diff --git a/Drivers/ST7789/Source/St7789Display.cpp b/Drivers/ST7789/Source/St7789Display.cpp index 05c4ea52d..817786749 100644 --- a/Drivers/ST7789/Source/St7789Display.cpp +++ b/Drivers/ST7789/Source/St7789Display.cpp @@ -14,6 +14,7 @@ std::shared_ptr St7789Display::createEspLcdConfiguration(co .mirrorY = configuration.mirrorY, .invertColor = configuration.invertColor, .bufferSize = configuration.bufferSize, + .buffSpiram = configuration.buffSpiram, .touch = configuration.touch, .backlightDutyFunction = configuration.backlightDutyFunction, .resetPin = configuration.resetPin, diff --git a/Drivers/ST7789/Source/St7789Display.h b/Drivers/ST7789/Source/St7789Display.h index fe227af65..b67b7b580 100644 --- a/Drivers/ST7789/Source/St7789Display.h +++ b/Drivers/ST7789/Source/St7789Display.h @@ -25,6 +25,7 @@ class St7789Display final : public EspLcdSpiDisplay { std::function _Nullable backlightDutyFunction; gpio_num_t resetPin; bool lvglSwapBytes; + bool buffSpiram = false; lcd_rgb_element_order_t rgbElementOrder = LCD_RGB_ELEMENT_ORDER_RGB; }; diff --git a/Drivers/ina226-module/CMakeLists.txt b/Drivers/ina226-module/CMakeLists.txt new file mode 100644 index 000000000..1ca00507f --- /dev/null +++ b/Drivers/ina226-module/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.20) + +include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake") + +file(GLOB_RECURSE SOURCE_FILES "source/*.c*") + +tactility_add_module(ina226-module + SRCS ${SOURCE_FILES} + INCLUDE_DIRS include/ + REQUIRES TactilityKernel +) diff --git a/Drivers/ina226-module/README.md b/Drivers/ina226-module/README.md new file mode 100644 index 000000000..4bff64198 --- /dev/null +++ b/Drivers/ina226-module/README.md @@ -0,0 +1,5 @@ +# INA226 + +Texas Instruments INA226 High-Side or Low-Side Measurement, Bi-Directional Current and Power Monitor. + +- Datasheet: https://www.ti.com/lit/ds/symlink/ina226.pdf diff --git a/Drivers/ina226-module/bindings/ti,ina226.yaml b/Drivers/ina226-module/bindings/ti,ina226.yaml new file mode 100644 index 000000000..aed2c7647 --- /dev/null +++ b/Drivers/ina226-module/bindings/ti,ina226.yaml @@ -0,0 +1,12 @@ +description: TI INA226 voltage/current monitor + +include: ["i2c-device.yaml"] + +compatible: "ti,ina226" + +properties: + shunt-milliohms: + type: int + required: true + # shunt value should be a minimum of 1 + description: Shunt resistor value in milliohms diff --git a/Drivers/ina226-module/devicetree.yaml b/Drivers/ina226-module/devicetree.yaml new file mode 100644 index 000000000..a07d6f334 --- /dev/null +++ b/Drivers/ina226-module/devicetree.yaml @@ -0,0 +1,3 @@ +dependencies: + - TactilityKernel +bindings: bindings diff --git a/Drivers/ina226-module/include/bindings/ina226.h b/Drivers/ina226-module/include/bindings/ina226.h new file mode 100644 index 000000000..4ab056033 --- /dev/null +++ b/Drivers/ina226-module/include/bindings/ina226.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +DEFINE_DEVICETREE(ina226, struct Ina226Config) + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/ina226-module/include/drivers/ina226.h b/Drivers/ina226-module/include/drivers/ina226.h new file mode 100644 index 000000000..cdb8b026c --- /dev/null +++ b/Drivers/ina226-module/include/drivers/ina226.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +struct Device; + +#ifdef __cplusplus +extern "C" { +#endif + +struct Ina226Config { + uint8_t address; + uint16_t shunt_milliohms; +}; + +/** Bus voltage in volts (1.25 mV/LSB) */ +error_t ina226_read_bus_voltage(struct Device* device, float* volts); + +/** Shunt current in amps (positive = charging, negative = discharging) */ +error_t ina226_read_shunt_current(struct Device* device, float* amps); + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/ina226-module/include/ina226_module.h b/Drivers/ina226-module/include/ina226_module.h new file mode 100644 index 000000000..6e3874e7c --- /dev/null +++ b/Drivers/ina226-module/include/ina226_module.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct Module ina226_module; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/ina226-module/source/ina226.cpp b/Drivers/ina226-module/source/ina226.cpp new file mode 100644 index 000000000..460591856 --- /dev/null +++ b/Drivers/ina226-module/source/ina226.cpp @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include +#include +#include + +#define TAG "INA226" + +// --------------------------------------------------------------------------- +// Register map +// --------------------------------------------------------------------------- +static constexpr uint8_t REG_CONFIG = 0x00; ///< RW - Configuration +static constexpr uint8_t REG_SHUNT_VOLT = 0x01; ///< R - Shunt voltage (2.5 µV/LSB, signed) +static constexpr uint8_t REG_BUS_VOLT = 0x02; ///< R - Bus voltage (1.25 mV/LSB) +static constexpr uint8_t REG_CURRENT = 0x04; ///< R - Current (CURRENT_LSB/LSB, signed; valid after calibration) +static constexpr uint8_t REG_CALIBRATION = 0x05; ///< RW - Calibration + +static constexpr uint8_t REG_MANUFACTURER_ID = 0xFE; +static constexpr uint16_t EXPECTED_MFR_ID = 0x5449; // "TI" + +// CONFIG: AVG=16 (010), VBUSCT=1100µs (100), VSHCT=1100µs (100), MODE=continuous shunt+bus (111) +static constexpr uint16_t CONFIG_VALUE = 0x4527; +static constexpr uint16_t CONFIG_SHUTDOWN = 0x4520; // Same as CONFIG_VALUE but MODE=000 + +static constexpr float MAX_CURRENT_A = 8.192f; +static constexpr float CURRENT_LSB = MAX_CURRENT_A / 32768.0f; + +static constexpr TickType_t TIMEOUT = pdMS_TO_TICKS(50); + +#define GET_CONFIG(device) (static_cast((device)->config)) + +// --------------------------------------------------------------------------- +// Driver lifecycle +// --------------------------------------------------------------------------- + +static error_t start(Device* device) { + Device* i2c = device_get_parent(device); + if (device_get_type(i2c) != &I2C_CONTROLLER_TYPE) { + LOG_E(TAG, "Parent is not an I2C controller"); + return ERROR_RESOURCE; + } + + const uint8_t addr = GET_CONFIG(device)->address; + + uint16_t mfr_id = 0; + error_t err = i2c_controller_register16be_get(i2c, addr, REG_MANUFACTURER_ID, &mfr_id, TIMEOUT); + if (err != ERROR_NONE) { + LOG_E(TAG, "Failed to read INA226 manufacturer ID"); + return ERROR_RESOURCE; + } + + if (mfr_id != EXPECTED_MFR_ID) { + LOG_E(TAG, "Wrong device detected (mfr_id=0x%04X, expected=0x%04X)", mfr_id, EXPECTED_MFR_ID); + return ERROR_RESOURCE; + } + + if (i2c_controller_register16be_set(i2c, addr, REG_CONFIG, CONFIG_VALUE, TIMEOUT) != ERROR_NONE) { + LOG_E(TAG, "Failed to configure INA226"); + return ERROR_RESOURCE; + } + + const uint16_t shunt_milliohms = GET_CONFIG(device)->shunt_milliohms; + if (shunt_milliohms == 0) { + LOG_E(TAG, "Invalid shunt value: 0 mOhms"); + return ERROR_INVALID_ARGUMENT; + } + const float shunt_ohms = shunt_milliohms / 1000.0f; + const float cal_f = 0.00512f / (CURRENT_LSB * shunt_ohms); + if (cal_f < 1.0f || cal_f > 65535.0f) { + LOG_E(TAG, "Calibration out of range (shunt=%u mOhms)", shunt_milliohms); + return ERROR_INVALID_ARGUMENT; + } + const uint16_t cal_value = static_cast(cal_f); + if (i2c_controller_register16be_set(i2c, addr, REG_CALIBRATION, cal_value, TIMEOUT) != ERROR_NONE) { + LOG_E(TAG, "Failed to calibrate INA226"); + return ERROR_RESOURCE; + } + + LOG_I(TAG, "INA226 started (addr=0x%02X, shunt=%umΩ, cal=0x%04X)", addr, GET_CONFIG(device)->shunt_milliohms, cal_value); + return ERROR_NONE; +} + +static error_t stop(Device* device) { + auto* i2c = device_get_parent(device); + if (device_get_type(i2c) != &I2C_CONTROLLER_TYPE) { + LOG_E(TAG, "Parent is not an I2C controller"); + return ERROR_RESOURCE; + } + + auto addr = GET_CONFIG(device)->address; + + // Put device into shutdown mode to save power + if (i2c_controller_register16be_set(i2c, addr, REG_CONFIG, CONFIG_SHUTDOWN, TIMEOUT) != ERROR_NONE) { + LOG_E(TAG, "Failed to shutdown INA226 (ignored)"); + } + + return ERROR_NONE; +} + +// --------------------------------------------------------------------------- +// Public API +// --------------------------------------------------------------------------- + +extern "C" { + +error_t ina226_read_bus_voltage(Device* device, float* volts) { + if (device == nullptr) return ERROR_INVALID_ARGUMENT; + if (volts == nullptr) return ERROR_INVALID_ARGUMENT; + uint16_t raw = 0; + error_t err = i2c_controller_register16be_get(device_get_parent(device), GET_CONFIG(device)->address, REG_BUS_VOLT, &raw, TIMEOUT); + if (err != ERROR_NONE) return err; + *volts = static_cast(raw) * 0.00125f; + return ERROR_NONE; +} + +error_t ina226_read_shunt_current(Device* device, float* amps) { + if (device == nullptr) return ERROR_INVALID_ARGUMENT; + if (amps == nullptr) return ERROR_INVALID_ARGUMENT; + uint16_t raw = 0; + error_t err = i2c_controller_register16be_get(device_get_parent(device), GET_CONFIG(device)->address, REG_CURRENT, &raw, TIMEOUT); + if (err != ERROR_NONE) return err; + *amps = static_cast(static_cast(raw)) * CURRENT_LSB; + return ERROR_NONE; +} + +Driver ina226_driver = { + .name = "ina226", + .compatible = (const char*[]) { "ti,ina226", nullptr }, + .start_device = start, + .stop_device = stop, + .api = nullptr, + .device_type = nullptr, + .owner = &ina226_module, + .internal = nullptr +}; + +} // extern "C" diff --git a/Drivers/ina226-module/source/module.cpp b/Drivers/ina226-module/source/module.cpp new file mode 100644 index 000000000..0708b24ce --- /dev/null +++ b/Drivers/ina226-module/source/module.cpp @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include + +extern "C" { + +extern Driver ina226_driver; + +static error_t start() { + /* We crash when construct fails, because if a single driver fails to construct, + * there is no guarantee that the previously constructed drivers can be destroyed */ + check(driver_construct_add(&ina226_driver) == ERROR_NONE); + return ERROR_NONE; +} + +static error_t stop() { + /* We crash when destruct fails, because if a single driver fails to destruct, + * there is no guarantee that the previously destroyed drivers can be recovered */ + check(driver_remove_destruct(&ina226_driver) == ERROR_NONE); + return ERROR_NONE; +} + +extern const ModuleSymbol ina226_module_symbols[]; + +Module ina226_module = { + .name = "ina226", + .start = start, + .stop = stop, + .symbols = ina226_module_symbols, + .internal = nullptr +}; + +} // extern "C" diff --git a/Drivers/ina226-module/source/symbols.c b/Drivers/ina226-module/source/symbols.c new file mode 100644 index 000000000..585bc41b0 --- /dev/null +++ b/Drivers/ina226-module/source/symbols.c @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +const struct ModuleSymbol ina226_module_symbols[] = { + DEFINE_MODULE_SYMBOL(ina226_read_bus_voltage), + DEFINE_MODULE_SYMBOL(ina226_read_shunt_current), + MODULE_SYMBOL_TERMINATOR +}; diff --git a/Drivers/m5pm1-module/include/drivers/m5pm1.h b/Drivers/m5pm1-module/include/drivers/m5pm1.h index 32c6a2752..b73e81dd4 100644 --- a/Drivers/m5pm1-module/include/drivers/m5pm1.h +++ b/Drivers/m5pm1-module/include/drivers/m5pm1.h @@ -41,6 +41,8 @@ error_t m5pm1_is_charging(struct Device* device, bool* charging); error_t m5pm1_set_charge_enable(struct Device* device, bool enable); error_t m5pm1_set_boost_enable(struct Device* device, bool enable); ///< 5V BOOST / Grove power error_t m5pm1_set_ldo_enable(struct Device* device, bool enable); ///< 3.3V LDO +/** PM1_G3: speaker amplifier enable (HIGH = on) */ +error_t m5pm1_set_speaker_enable(struct Device* device, bool enable); // --------------------------------------------------------------------------- // Temperature (internal chip sensor) diff --git a/Drivers/m5pm1-module/source/m5pm1.cpp b/Drivers/m5pm1-module/source/m5pm1.cpp index c972eb051..ba50e354e 100644 --- a/Drivers/m5pm1-module/source/m5pm1.cpp +++ b/Drivers/m5pm1-module/source/m5pm1.cpp @@ -49,6 +49,9 @@ static constexpr uint8_t ADC_CH_TEMP = 6; // PM1_G2: LCD power enable on M5Stack StickS3 static constexpr uint8_t LCD_POWER_BIT = (1U << 2U); +// PM1_G3: Speaker amplifier enable on M5Stack StickS3 +static constexpr uint8_t SPEAKER_AMP_BIT = (1U << 3U); +static constexpr uint8_t SPEAKER_AMP_FUNC_MASK = (0x3U << 6U); // GPIO3 function select bits static constexpr TickType_t TIMEOUT = pdMS_TO_TICKS(50); @@ -90,6 +93,13 @@ static error_t start(Device* device) { LOG_W(TAG, "Failed to disable I2C sleep (non-fatal)"); } + // BOOST_EN → EXT_5V / Grove / Hat power rail always on + if (i2c_controller_register8_set_bits(i2c, addr, REG_PWR_CFG, PWR_CFG_BOOST_EN, TIMEOUT) == ERROR_NONE) { + LOG_I(TAG, "EXT_5V boost enabled"); + } else { + LOG_W(TAG, "Failed to enable EXT_5V boost (non-fatal)"); + } + // PM1_G2 → LCD power enable (L3B rail on StickS3) // Sequence matches M5GFX: clear FUNC0 bit2, set MODE bit2 output, clear DRV bit2 push-pull, set OUT bit2 high bool lcd_ok = @@ -104,6 +114,18 @@ static error_t start(Device* device) { LOG_E(TAG, "Failed to enable LCD power via PM1_G2"); } + // PM1_G3 → speaker amp EN, initially LOW (amp off until audio starts) + bool spk_ok = + i2c_controller_register8_reset_bits(i2c, addr, REG_GPIO_FUNC0, SPEAKER_AMP_FUNC_MASK, TIMEOUT) == ERROR_NONE && + i2c_controller_register8_set_bits (i2c, addr, REG_GPIO_MODE, SPEAKER_AMP_BIT, TIMEOUT) == ERROR_NONE && + i2c_controller_register8_reset_bits(i2c, addr, REG_GPIO_DRV, SPEAKER_AMP_BIT, TIMEOUT) == ERROR_NONE && + i2c_controller_register8_reset_bits(i2c, addr, REG_GPIO_OUT, SPEAKER_AMP_BIT, TIMEOUT) == ERROR_NONE; + if (spk_ok) { + LOG_I(TAG, "Speaker amp pin configured"); + } else { + LOG_W(TAG, "Failed to configure speaker amp pin"); + } + return ERROR_NONE; } @@ -170,6 +192,16 @@ error_t m5pm1_set_ldo_enable(Device* device, bool enable) { } } +error_t m5pm1_set_speaker_enable(Device* device, bool enable) { + Device* i2c = device_get_parent(device); + const uint8_t addr = GET_CONFIG(device)->address; + if (enable) { + return i2c_controller_register8_set_bits(i2c, addr, REG_GPIO_OUT, SPEAKER_AMP_BIT, TIMEOUT); + } else { + return i2c_controller_register8_reset_bits(i2c, addr, REG_GPIO_OUT, SPEAKER_AMP_BIT, TIMEOUT); + } +} + error_t m5pm1_get_temperature(Device* device, uint16_t* decidegc) { Device* i2c = device_get_parent(device); uint8_t addr = GET_CONFIG(device)->address; diff --git a/Drivers/py32ioexpander-module/CMakeLists.txt b/Drivers/py32ioexpander-module/CMakeLists.txt new file mode 100644 index 000000000..6f56f2799 --- /dev/null +++ b/Drivers/py32ioexpander-module/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.20) + +include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake") + +file(GLOB_RECURSE SOURCE_FILES "source/*.c*") + +tactility_add_module(py32ioexpander-module + SRCS ${SOURCE_FILES} + INCLUDE_DIRS include/ + REQUIRES TactilityKernel +) diff --git a/Drivers/py32ioexpander-module/bindings/m5stack,py32ioexpander.yaml b/Drivers/py32ioexpander-module/bindings/m5stack,py32ioexpander.yaml new file mode 100644 index 000000000..184faf59f --- /dev/null +++ b/Drivers/py32ioexpander-module/bindings/m5stack,py32ioexpander.yaml @@ -0,0 +1,5 @@ +description: M5Stack PY32 IO Expander (StackChan body module) + +include: ["i2c-device.yaml"] + +compatible: "m5stack,py32ioexpander" diff --git a/Drivers/py32ioexpander-module/devicetree.yaml b/Drivers/py32ioexpander-module/devicetree.yaml new file mode 100644 index 000000000..a07d6f334 --- /dev/null +++ b/Drivers/py32ioexpander-module/devicetree.yaml @@ -0,0 +1,3 @@ +dependencies: + - TactilityKernel +bindings: bindings diff --git a/Drivers/py32ioexpander-module/include/bindings/py32ioexpander.h b/Drivers/py32ioexpander-module/include/bindings/py32ioexpander.h new file mode 100644 index 000000000..f9aff5c96 --- /dev/null +++ b/Drivers/py32ioexpander-module/include/bindings/py32ioexpander.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +DEFINE_DEVICETREE(py32ioexpander, struct Py32IoExpanderConfig) + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/py32ioexpander-module/include/drivers/py32ioexpander.h b/Drivers/py32ioexpander-module/include/drivers/py32ioexpander.h new file mode 100644 index 000000000..5a7631a3f --- /dev/null +++ b/Drivers/py32ioexpander-module/include/drivers/py32ioexpander.h @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include +#include + +struct Device; + +#ifdef __cplusplus +extern "C" { +#endif + +struct Py32IoExpanderConfig { + uint8_t address; +}; + +// --------------------------------------------------------------------------- +// GPIO (16 pins, index 0–15) +// --------------------------------------------------------------------------- +error_t py32_gpio_set_output(struct Device* device, uint8_t pin, bool value); +error_t py32_gpio_get_input(struct Device* device, uint8_t pin, bool* value); + +// --------------------------------------------------------------------------- +// NeoPixel LED ring (up to 32 WS2812C LEDs) +// --------------------------------------------------------------------------- +error_t py32_led_set_count(struct Device* device, uint8_t count); +error_t py32_led_set_color(struct Device* device, uint8_t index, uint8_t r, uint8_t g, uint8_t b); +error_t py32_led_refresh(struct Device* device); +error_t py32_led_disable(struct Device* device); + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/py32ioexpander-module/include/py32ioexpander_module.h b/Drivers/py32ioexpander-module/include/py32ioexpander_module.h new file mode 100644 index 000000000..9a112b2ac --- /dev/null +++ b/Drivers/py32ioexpander-module/include/py32ioexpander_module.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct Module py32ioexpander_module; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/py32ioexpander-module/source/module.cpp b/Drivers/py32ioexpander-module/source/module.cpp new file mode 100644 index 000000000..5cbd2123a --- /dev/null +++ b/Drivers/py32ioexpander-module/source/module.cpp @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include + +extern "C" { + +extern Driver py32ioexpander_driver; + +static error_t start() { + check(driver_construct_add(&py32ioexpander_driver) == ERROR_NONE); + return ERROR_NONE; +} + +static error_t stop() { + check(driver_remove_destruct(&py32ioexpander_driver) == ERROR_NONE); + return ERROR_NONE; +} + +Module py32ioexpander_module = { + .name = "py32ioexpander", + .start = start, + .stop = stop, + .symbols = nullptr, + .internal = nullptr +}; + +} // extern "C" diff --git a/Drivers/py32ioexpander-module/source/py32ioexpander.cpp b/Drivers/py32ioexpander-module/source/py32ioexpander.cpp new file mode 100644 index 000000000..5584a7add --- /dev/null +++ b/Drivers/py32ioexpander-module/source/py32ioexpander.cpp @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: Apache-2.0 +// Reference: https://github.com/m5stack/StackChan/tree/main/firmware/main/hal/drivers/PY32IOExpander_Class +#include +#include +#include +#include +#include + +#define TAG "PY32IOExpander" + +// --------------------------------------------------------------------------- +// Register map +// --------------------------------------------------------------------------- +static constexpr uint8_t REG_UID_L = 0x00; ///< R - Unique ID low byte +static constexpr uint8_t REG_VERSION = 0x02; ///< R - Firmware version +static constexpr uint8_t REG_GPIO_MODE_L = 0x03; ///< RW - GPIO direction [15:0] (1=output, 0=input), little-endian +static constexpr uint8_t REG_GPIO_OUT_L = 0x05; ///< RW - GPIO output level [15:0], little-endian +static constexpr uint8_t REG_GPIO_IN_L = 0x07; ///< R - GPIO input state [15:0], little-endian +static constexpr uint8_t REG_LED_CFG = 0x24; ///< RW - [6]=REFRESH [5:0]=LED_COUNT +static constexpr uint8_t REG_LED_DATA = 0x30; ///< RW - RGB565 data, 2 bytes per LED (max 32) + +static constexpr TickType_t TIMEOUT = pdMS_TO_TICKS(50); + +#define GET_CONFIG(device) (static_cast((device)->config)) + +// --------------------------------------------------------------------------- +// Driver lifecycle +// --------------------------------------------------------------------------- + +static error_t start(Device* device) { + Device* i2c = device_get_parent(device); + if (device_get_type(i2c) != &I2C_CONTROLLER_TYPE) { + LOG_E(TAG, "Parent is not an I2C controller"); + return ERROR_RESOURCE; + } + + const uint8_t addr = GET_CONFIG(device)->address; + + // PY32 may need a moment after power-on. Retry with increasing delays. + uint8_t version = 0; + bool online = false; + for (int attempt = 0; attempt < 5; attempt++) { + if (i2c_controller_register8_get(i2c, addr, REG_VERSION, &version, TIMEOUT) == ERROR_NONE && + version != 0x00 && version != 0xFF) { + online = true; + break; + } + vTaskDelay(pdMS_TO_TICKS(20 * (attempt + 1))); + } + + if (!online) { + LOG_E(TAG, "PY32IOExpander not responding at 0x%02X — LED ring will not work", addr); + return ERROR_NONE; // non-fatal: don't crash the kernel + } + + LOG_I(TAG, "PY32IOExpander online (addr=0x%02X, fw=0x%02X)", addr, version); + return ERROR_NONE; +} + +static error_t stop(Device* device) { + return ERROR_NONE; +} + +// --------------------------------------------------------------------------- +// Public API +// --------------------------------------------------------------------------- + +extern "C" { + +error_t py32_gpio_set_output(Device* device, uint8_t pin, bool value) { + if (pin > 15U) return ERROR_RESOURCE; + Device* i2c = device_get_parent(device); + const uint8_t addr = GET_CONFIG(device)->address; + + // Ensure pin is in output mode + uint16_t mode = 0; + error_t err = i2c_controller_register16le_get(i2c, addr, REG_GPIO_MODE_L, &mode, TIMEOUT); + if (err != ERROR_NONE) return err; + mode |= static_cast(1U << pin); + err = i2c_controller_register16le_set(i2c, addr, REG_GPIO_MODE_L, mode, TIMEOUT); + if (err != ERROR_NONE) return err; + + // Set output level + uint16_t out = 0; + err = i2c_controller_register16le_get(i2c, addr, REG_GPIO_OUT_L, &out, TIMEOUT); + if (err != ERROR_NONE) return err; + if (value) { + out |= static_cast(1U << pin); + } else { + out &= static_cast(~(1U << pin)); + } + return i2c_controller_register16le_set(i2c, addr, REG_GPIO_OUT_L, out, TIMEOUT); +} + +error_t py32_gpio_get_input(Device* device, uint8_t pin, bool* value) { + if (pin > 15U) return ERROR_RESOURCE; + Device* i2c = device_get_parent(device); + uint16_t in = 0; + error_t err = i2c_controller_register16le_get(i2c, GET_CONFIG(device)->address, REG_GPIO_IN_L, &in, TIMEOUT); + if (err != ERROR_NONE) return err; + *value = (in >> pin) & 1U; + return ERROR_NONE; +} + +error_t py32_led_set_count(Device* device, uint8_t count) { + if (count > 32U) return ERROR_RESOURCE; + Device* i2c = device_get_parent(device); + uint8_t cfg = count & 0x3FU; + return i2c_controller_register8_set(i2c, GET_CONFIG(device)->address, REG_LED_CFG, cfg, TIMEOUT); +} + +error_t py32_led_set_color(Device* device, uint8_t index, uint8_t r, uint8_t g, uint8_t b) { + if (index > 32U) return ERROR_RESOURCE; + Device* i2c = device_get_parent(device); + // RGB565: [15:11]=R5 [10:5]=G6 [4:0]=B5, stored little-endian + uint16_t rgb565 = static_cast(((r >> 3U) << 11U) | ((g >> 2U) << 5U) | (b >> 3U)); + uint8_t buf[2] = {static_cast(rgb565 & 0xFFU), static_cast(rgb565 >> 8U)}; + return i2c_controller_write_register(i2c, GET_CONFIG(device)->address, static_cast(REG_LED_DATA + index * 2U), buf, 2, TIMEOUT); +} + +error_t py32_led_refresh(Device* device) { + Device* i2c = device_get_parent(device); + const uint8_t addr = GET_CONFIG(device)->address; + // Read current LED_CFG (contains LED count), then set the REFRESH bit (6) + uint8_t cfg = 0; + error_t err = i2c_controller_register8_get(i2c, addr, REG_LED_CFG, &cfg, TIMEOUT); + if (err != ERROR_NONE) return err; + return i2c_controller_register8_set(i2c, addr, REG_LED_CFG, cfg | 0x40U, TIMEOUT); +} + +error_t py32_led_disable(Device* device) { + // Write black to all 32 possible LED slots then refresh with count=32 + // Using the maximum count ensures no stale color data is displayed. + static constexpr uint8_t BLACK[64] = {}; // 32 LEDs × 2 bytes, all zero + Device* i2c = device_get_parent(device); + uint8_t addr = GET_CONFIG(device)->address; + error_t err = i2c_controller_write_register(i2c, addr, REG_LED_DATA, BLACK, sizeof(BLACK), TIMEOUT); + if (err != ERROR_NONE) return err; + // count=32 | REFRESH + return i2c_controller_register8_set(i2c, addr, REG_LED_CFG, 0x60U, TIMEOUT); +} + +Driver py32ioexpander_driver = { + .name = "py32ioexpander", + .compatible = (const char*[]) {"m5stack,py32ioexpander", nullptr}, + .start_device = start, + .stop_device = stop, + .api = nullptr, + .device_type = nullptr, + .owner = &py32ioexpander_module, + .internal = nullptr +}; + +} // extern "C" diff --git a/Firmware/idf_component.yml b/Firmware/idf_component.yml index ac03ba56a..adb53d712 100644 --- a/Firmware/idf_component.yml +++ b/Firmware/idf_component.yml @@ -30,6 +30,7 @@ dependencies: espressif/esp_io_expander: "1.0.1" espressif/esp_io_expander_tca95xx_16bit: "1.0.1" espressif/esp_lcd_axs15231b: "2.0.2" + lambage/esp_lcd_touch_ft6336u: "1.0.8" espressif/esp_lcd_st7701: version: "1.1.3" rules: @@ -64,7 +65,7 @@ dependencies: version: "1.7.6~1" rules: - if: "target == esp32s3" - espressif/esp_lvgl_port: "2.5.0" + espressif/esp_lvgl_port: "2.7.2" lvgl/lvgl: "9.3.0" epdiy: git: https://github.com/Shadowtrance/epdiy.git diff --git a/Modules/lvgl-module/source/symbols.c b/Modules/lvgl-module/source/symbols.c index 021d38ae7..c890546d2 100644 --- a/Modules/lvgl-module/source/symbols.c +++ b/Modules/lvgl-module/source/symbols.c @@ -41,6 +41,8 @@ const struct ModuleSymbol lvgl_module_symbols[] = { DEFINE_MODULE_SYMBOL(lv_color_white), DEFINE_MODULE_SYMBOL(lv_color_lighten), DEFINE_MODULE_SYMBOL(lv_color_darken), + DEFINE_MODULE_SYMBOL(lv_color_hsv_to_rgb), + DEFINE_MODULE_SYMBOL(lv_color_to_32), DEFINE_MODULE_SYMBOL(lv_obj_center), DEFINE_MODULE_SYMBOL(lv_obj_clean), DEFINE_MODULE_SYMBOL(lv_obj_create), @@ -264,6 +266,7 @@ const struct ModuleSymbol lvgl_module_symbols[] = { DEFINE_MODULE_SYMBOL(lv_chart_set_axis_range), DEFINE_MODULE_SYMBOL(lv_chart_set_update_mode), DEFINE_MODULE_SYMBOL(lv_chart_set_point_count), + DEFINE_MODULE_SYMBOL(lv_chart_refresh), // lv_dropdown DEFINE_MODULE_SYMBOL(lv_dropdown_create), DEFINE_MODULE_SYMBOL(lv_dropdown_add_option), diff --git a/Platforms/platform-esp32/source/drivers/esp32_i2s.cpp b/Platforms/platform-esp32/source/drivers/esp32_i2s.cpp index b9c350269..879c05e17 100644 --- a/Platforms/platform-esp32/source/drivers/esp32_i2s.cpp +++ b/Platforms/platform-esp32/source/drivers/esp32_i2s.cpp @@ -1,6 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 #include #include +#include +#ifdef SOC_I2S_SUPPORTS_TDM +#include +#endif #include #include @@ -21,6 +25,10 @@ struct Esp32I2sInternal { Mutex mutex {}; I2sConfig config {}; bool config_set = false; +#ifdef SOC_I2S_SUPPORTS_TDM + bool rx_tdm_mode = false; + I2sTdmRxConfig tdm_config {}; +#endif GpioDescriptor* bclk_descriptor = nullptr; GpioDescriptor* ws_descriptor = nullptr; GpioDescriptor* data_out_descriptor = nullptr; @@ -187,6 +195,9 @@ static error_t set_config(Device* device, const struct I2sConfig* config) { cleanup_channel_handles(internal); internal->config_set = false; +#ifdef SOC_I2S_SUPPORTS_TDM + internal->rx_tdm_mode = false; +#endif // Create new channel handles i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(dts_config->port, I2S_ROLE_MASTER); @@ -279,12 +290,141 @@ static error_t reset(Device* device) { return ERROR_NONE; } +#ifdef SOC_I2S_SUPPORTS_TDM +static error_t set_rx_tdm_config(Device* device, const struct I2sTdmRxConfig* config) { + if (xPortInIsrContext()) return ERROR_ISR_STATUS; + + if (config->bits_per_sample != 8 && + config->bits_per_sample != 16 && + config->bits_per_sample != 24 && + config->bits_per_sample != 32) { + return ERROR_INVALID_ARGUMENT; + } + + if (config->slot_count == 0 || config->slot_count > 16) { + return ERROR_INVALID_ARGUMENT; + } + + if (config->bclk_div == 0) { + return ERROR_INVALID_ARGUMENT; + } + + if (config->sample_rate_hz == 0) { + return ERROR_INVALID_ARGUMENT; + } + + uint32_t bytes_per_sample = config->bits_per_sample / 8u; + + // Size DMA buffers so that each descriptor covers at most 4096 bytes. + // Start at 512 frames and halve until one frame × slot_count × bytes fits. + uint32_t frame_num = 512u; + uint32_t dma_desc_num = 8u; + while (frame_num > 64u && frame_num * (uint32_t)config->slot_count * bytes_per_sample > 4096u) { + frame_num /= 2u; + } + + // Reject if even the minimum frame count overflows one descriptor (slot_count too large). + if (frame_num * (uint32_t)config->slot_count * bytes_per_sample > 4096u) { + return ERROR_INVALID_ARGUMENT; + } + + auto* internal = GET_DATA(device); + auto* dts_config = GET_CONFIG(device); + lock(internal); + + // Tear down only the RX channel; TX stays in standard mode for playback. + if (internal->rx_handle) { + i2s_channel_disable(internal->rx_handle); + i2s_del_channel(internal->rx_handle); + internal->rx_handle = nullptr; + } + + i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(dts_config->port, I2S_ROLE_MASTER); + chan_cfg.dma_desc_num = dma_desc_num; + chan_cfg.dma_frame_num = (uint32_t)frame_num; + i2s_chan_handle_t new_rx = nullptr; + esp_err_t esp_error = i2s_new_channel(&chan_cfg, nullptr, &new_rx); + if (esp_error != ESP_OK) { + LOG_E(TAG, "TDM: failed to create RX channel: %s", esp_err_to_name(esp_error)); + // RX channel is gone; caller must call set_config() to restore standard mode. + unlock(internal); + return ERROR_RESOURCE; + } + + gpio_num_t mclk_pin = get_native_pin(internal->mclk_descriptor); + gpio_num_t bclk_pin = get_native_pin(internal->bclk_descriptor); + gpio_num_t ws_pin = get_native_pin(internal->ws_descriptor); + gpio_num_t data_in_pin = get_native_pin(internal->data_in_descriptor); + + // slot_mask: bits 0..(slot_count-1) set; slot_count validated above (1-16) + i2s_tdm_slot_mask_t slot_mask = (i2s_tdm_slot_mask_t)((1u << config->slot_count) - 1u); + + i2s_tdm_config_t tdm_cfg = { + .clk_cfg = { + .sample_rate_hz = config->sample_rate_hz, + .clk_src = I2S_CLK_SRC_DEFAULT, + .ext_clk_freq_hz = 0, + .mclk_multiple = (i2s_mclk_multiple_t)config->mclk_multiple, + .bclk_div = config->bclk_div, + }, + .slot_cfg = { + .data_bit_width = to_esp32_bits_per_sample(config->bits_per_sample), + .slot_bit_width = (config->slot_bit_width == 0) ? I2S_SLOT_BIT_WIDTH_AUTO : (i2s_slot_bit_width_t)config->slot_bit_width, + .slot_mode = I2S_SLOT_MODE_STEREO, + .slot_mask = slot_mask, + .ws_width = I2S_TDM_AUTO_WS_WIDTH, + .ws_pol = false, + .bit_shift = true, + .left_align = false, + .big_endian = false, + .bit_order_lsb = false, + .skip_mask = false, + .total_slot = I2S_TDM_AUTO_SLOT_NUM, + }, + .gpio_cfg = { + .mclk = mclk_pin, + .bclk = bclk_pin, + .ws = ws_pin, + .dout = I2S_GPIO_UNUSED, + .din = data_in_pin, + .invert_flags = { + .mclk_inv = is_pin_inverted(internal->mclk_descriptor), + .bclk_inv = is_pin_inverted(internal->bclk_descriptor), + .ws_inv = is_pin_inverted(internal->ws_descriptor), + }, + }, + }; + + esp_error = i2s_channel_init_tdm_mode(new_rx, &tdm_cfg); + if (esp_error == ESP_OK) esp_error = i2s_channel_enable(new_rx); + + if (esp_error != ESP_OK) { + LOG_E(TAG, "TDM: failed to init/enable RX channel: %s", esp_err_to_name(esp_error)); + i2s_del_channel(new_rx); + // RX channel is gone; caller must call set_config() to restore standard mode. + unlock(internal); + return esp_err_to_error(esp_error); + } + + internal->rx_handle = new_rx; + internal->rx_tdm_mode = true; + memcpy(&internal->tdm_config, config, sizeof(I2sTdmRxConfig)); + unlock(internal); + return ERROR_NONE; +} +#endif // SOC_I2S_SUPPORTS_TDM + const static I2sControllerApi esp32_i2s_api = { .read = read, .write = write, .set_config = set_config, .get_config = get_config, - .reset = reset + .reset = reset, +#ifdef SOC_I2S_SUPPORTS_TDM + .set_rx_tdm_config = set_rx_tdm_config, +#else + .set_rx_tdm_config = nullptr, +#endif }; extern struct Module platform_esp32_module; diff --git a/Tactility/Include/Tactility/hal/power/PowerDevice.h b/Tactility/Include/Tactility/hal/power/PowerDevice.h index 2177f46af..1733f4741 100644 --- a/Tactility/Include/Tactility/hal/power/PowerDevice.h +++ b/Tactility/Include/Tactility/hal/power/PowerDevice.h @@ -40,6 +40,10 @@ class PowerDevice : public Device { virtual bool isAllowedToCharge() const { return false; } virtual void setAllowedToCharge(bool canCharge) { /* NO-OP*/ } + virtual bool supportsQuickCharge() const { return false; } + virtual bool isQuickChargeEnabled() const { return false; } + virtual void setQuickChargeEnabled(bool enabled) { /* NO-OP */ } + virtual bool supportsPowerOff() const { return false; } virtual void powerOff() { /* NO-OP*/ } }; diff --git a/Tactility/Include/Tactility/settings/DisplaySettings.h b/Tactility/Include/Tactility/settings/DisplaySettings.h index 5d9e45865..da0e0ba83 100644 --- a/Tactility/Include/Tactility/settings/DisplaySettings.h +++ b/Tactility/Include/Tactility/settings/DisplaySettings.h @@ -17,6 +17,7 @@ enum class ScreensaverType { BouncingBalls, Mystify, MatrixRain, + StackChan, Count // Sentinel for bounds checking - must be last }; diff --git a/Tactility/Source/app/display/Display.cpp b/Tactility/Source/app/display/Display.cpp index 080bd1cf7..941777325 100644 --- a/Tactility/Source/app/display/Display.cpp +++ b/Tactility/Source/app/display/Display.cpp @@ -286,7 +286,7 @@ class DisplayApp final : public App { screensaverDropdown = lv_dropdown_create(screensaver_wrapper); // Note: order correlates with settings::display::ScreensaverType enum order - lv_dropdown_set_options(screensaverDropdown, "None\nBouncing Balls\nMystify\nMatrix Rain"); + lv_dropdown_set_options(screensaverDropdown, "None\nBouncing Balls\nMystify\nMatrix Rain\nStackChan"); lv_obj_align(screensaverDropdown, LV_ALIGN_RIGHT_MID, 0, 0); lv_obj_add_event_cb(screensaverDropdown, onScreensaverChanged, LV_EVENT_VALUE_CHANGED, this); lv_dropdown_set_selected(screensaverDropdown, static_cast(displaySettings.screensaverType)); diff --git a/Tactility/Source/app/power/Power.cpp b/Tactility/Source/app/power/Power.cpp index 6d7065097..1eb4d948b 100644 --- a/Tactility/Source/app/power/Power.cpp +++ b/Tactility/Source/app/power/Power.cpp @@ -38,6 +38,8 @@ class PowerApp : public App { lv_obj_t* enableLabel = nullptr; lv_obj_t* enableSwitch = nullptr; + lv_obj_t* quickChargeLabel = nullptr; + lv_obj_t* quickChargeSwitch = nullptr; lv_obj_t* batteryVoltageLabel = nullptr; lv_obj_t* chargeStateLabel = nullptr; lv_obj_t* chargeLevelLabel = nullptr; @@ -68,7 +70,29 @@ class PowerApp : public App { app->onPowerEnabledChanged(event); } + void onQuickChargeChanged(lv_event_t* event) { + lv_event_code_t code = lv_event_get_code(event); + auto* qc_switch = static_cast(lv_event_get_target(event)); + if (code == LV_EVENT_VALUE_CHANGED) { + bool is_on = lv_obj_has_state(qc_switch, LV_STATE_CHECKED); + + if (power->isQuickChargeEnabled() != is_on) { + power->setQuickChargeEnabled(is_on); + updateUi(); + } + } + } + + static void onQuickChargeChangedCallback(lv_event_t* event) { + auto* app = (PowerApp*)lv_event_get_user_data(event); + app->onQuickChargeChanged(event); + } + void updateUi() { + if (chargeStateLabel == nullptr) { + return; + } + const char* charge_state; hal::power::PowerDevice::MetricData metric_data; if (power->getMetric(hal::power::PowerDevice::MetricType::IsCharging, metric_data)) { @@ -87,6 +111,9 @@ class PowerApp : public App { bool charging_enabled_set = power->supportsChargeControl(); bool charging_enabled_and_allowed = power->supportsChargeControl() && power->isAllowedToCharge(); + bool quick_charge_set = power->supportsQuickCharge(); + bool quick_charge_enabled = power->supportsQuickCharge() && power->isQuickChargeEnabled(); + int32_t current; bool current_set = false; if (power->getMetric(hal::power::PowerDevice::MetricType::Current, metric_data)) { @@ -112,6 +139,15 @@ class PowerApp : public App { lv_obj_add_flag(enableLabel, LV_OBJ_FLAG_HIDDEN); } + if (quick_charge_set) { + lv_obj_set_state(quickChargeSwitch, LV_STATE_CHECKED, quick_charge_enabled); + lv_obj_remove_flag(quickChargeSwitch, LV_OBJ_FLAG_HIDDEN); + lv_obj_remove_flag(quickChargeLabel, LV_OBJ_FLAG_HIDDEN); + } else { + lv_obj_add_flag(quickChargeSwitch, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(quickChargeLabel, LV_OBJ_FLAG_HIDDEN); + } + lv_label_set_text_fmt(chargeStateLabel, "Charging: %s", charge_state); if (battery_voltage_set) { @@ -127,7 +163,7 @@ class PowerApp : public App { } if (current_set) { - lv_label_set_text_fmt(currentLabel, "Current: %ld mAh", current); + lv_label_set_text_fmt(currentLabel, "Current: %ld mA", current); } else { lv_label_set_text_fmt(currentLabel, "Current: N/A"); } @@ -157,7 +193,7 @@ class PowerApp : public App { lv_obj_set_flex_grow(wrapper, 1); lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_COLUMN); - // Top row: enable/disable + // Row: charge enable/disable lv_obj_t* switch_container = lv_obj_create(wrapper); lv_obj_set_width(switch_container, LV_PCT(100)); lv_obj_set_height(switch_container, LV_SIZE_CONTENT); @@ -172,8 +208,25 @@ class PowerApp : public App { lv_obj_t* enable_switch = lv_switch_create(switch_container); lv_obj_add_event_cb(enable_switch, onPowerEnabledChangedCallback, LV_EVENT_VALUE_CHANGED, this); lv_obj_set_align(enable_switch, LV_ALIGN_RIGHT_MID); - enableSwitch = enable_switch; + + // Row: quick charge enable/disable + lv_obj_t* qc_container = lv_obj_create(wrapper); + lv_obj_set_width(qc_container, LV_PCT(100)); + lv_obj_set_height(qc_container, LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(qc_container, 0, 0); + lv_obj_set_style_pad_gap(qc_container, 0, 0); + lvgl::obj_set_style_bg_invisible(qc_container); + + quickChargeLabel = lv_label_create(qc_container); + lv_label_set_text(quickChargeLabel, "Quick charge"); + lv_obj_set_align(quickChargeLabel, LV_ALIGN_LEFT_MID); + + lv_obj_t* qc_switch = lv_switch_create(qc_container); + lv_obj_add_event_cb(qc_switch, onQuickChargeChangedCallback, LV_EVENT_VALUE_CHANGED, this); + lv_obj_set_align(qc_switch, LV_ALIGN_RIGHT_MID); + quickChargeSwitch = qc_switch; + chargeStateLabel = lv_label_create(wrapper); chargeLevelLabel = lv_label_create(wrapper); batteryVoltageLabel = lv_label_create(wrapper); @@ -186,6 +239,14 @@ class PowerApp : public App { void onHide(AppContext& app) override { update_timer.stop(); + enableLabel = nullptr; + enableSwitch = nullptr; + quickChargeLabel = nullptr; + quickChargeSwitch = nullptr; + chargeStateLabel = nullptr; + chargeLevelLabel = nullptr; + batteryVoltageLabel = nullptr; + currentLabel = nullptr; } }; diff --git a/Tactility/Source/service/displayidle/DisplayIdle.cpp b/Tactility/Source/service/displayidle/DisplayIdle.cpp index bcb42460f..577619612 100644 --- a/Tactility/Source/service/displayidle/DisplayIdle.cpp +++ b/Tactility/Source/service/displayidle/DisplayIdle.cpp @@ -6,6 +6,7 @@ #include "BouncingBallsScreensaver.h" #include "MatrixRainScreensaver.h" #include "MystifyScreensaver.h" +#include "StackChanScreensaver.h" #include #include @@ -99,6 +100,9 @@ void DisplayIdleService::activateScreensaver() { case settings::display::ScreensaverType::MatrixRain: screensaver = std::make_unique(); break; + case settings::display::ScreensaverType::StackChan: + screensaver = std::make_unique(); + break; case settings::display::ScreensaverType::None: default: // Just black screen, no animated screensaver @@ -135,31 +139,31 @@ void DisplayIdleService::tick() { uint32_t inactive_ms = 0; - inactive_ms = lv_display_get_inactive_time(nullptr); - - // Only update if not stopping (prevents lag on touch) - if (displayDimmed && screensaverOverlay && !stopScreensaverRequested.load(std::memory_order_acquire)) { - // Check if screensaver should auto-off after 5 minutes - if (!backlightOff) { - screensaverActiveCounter++; - if (screensaverActiveCounter >= SCREENSAVER_AUTO_OFF_TICKS) { - // Stop screensaver animation and turn off backlight - if (screensaver) { - screensaver->stop(); - screensaver.reset(); - } - auto display = getDisplay(); - if (display) { - display->setBacklightDuty(0); - } - backlightOff = true; - } else { - updateScreensaver(); + inactive_ms = lv_display_get_inactive_time(nullptr); + + // Only update if not stopping (prevents lag on touch) + if (displayDimmed && screensaverOverlay && !stopScreensaverRequested.load(std::memory_order_acquire)) { + // Check if screensaver should auto-off after 5 minutes + if (!backlightOff) { + screensaverActiveCounter++; + if (screensaverActiveCounter >= SCREENSAVER_AUTO_OFF_TICKS) { + // Stop screensaver animation and turn off backlight + if (screensaver) { + screensaver->stop(); + screensaver.reset(); + } + auto display = getDisplay(); + if (display) { + display->setBacklightDuty(0); } + backlightOff = true; + } else { + updateScreensaver(); } } + } - lvgl::unlock(); + lvgl::unlock(); // Check stop request early for faster response if (stopScreensaverRequested.load(std::memory_order_acquire)) { diff --git a/Tactility/Source/service/displayidle/StackChanScreensaver.cpp b/Tactility/Source/service/displayidle/StackChanScreensaver.cpp new file mode 100644 index 000000000..5dbdab690 --- /dev/null +++ b/Tactility/Source/service/displayidle/StackChanScreensaver.cpp @@ -0,0 +1,102 @@ +#ifdef ESP_PLATFORM + +#include "StackChanScreensaver.h" +#include + +namespace tt::service::displayidle { + +static lv_obj_t* makeFacePart(lv_obj_t* parent, lv_coord_t w, lv_coord_t h, lv_coord_t offX, lv_coord_t offY, bool circle) { + lv_obj_t* obj = lv_obj_create(parent); + lv_obj_remove_style_all(obj); + lv_obj_set_size(obj, w, h); + lv_obj_set_style_bg_opa(obj, LV_OPA_COVER, 0); + lv_obj_set_style_bg_color(obj, lv_color_black(), 0); + lv_obj_set_style_radius(obj, circle ? LV_RADIUS_CIRCLE : 0, 0); + lv_obj_align(obj, LV_ALIGN_CENTER, offX, offY); + return obj; +} + +void StackChanScreensaver::start(lv_obj_t* overlay, lv_coord_t screenW, lv_coord_t screenH) { + // Scale face to ~12% of the shorter screen dimension, preserving 4:3 aspect ratio + lv_coord_t shorter = (screenW < screenH) ? screenW : screenH; + logoH_ = shorter / 8; + logoW_ = logoH_ * 4 / 3; + + // Face part sizes scaled proportionally to original (64x48 base with 6px eyes, 18x2 mouth) + lv_coord_t eyeSize = std::max(2, logoH_ / 8); + lv_coord_t eyeOffX = logoW_ / 4; + lv_coord_t eyeOffY = -(logoH_ / 10); + lv_coord_t mouthW = logoW_ * 18 / 64; + lv_coord_t mouthH = std::max(2, logoH_ / 24); + lv_coord_t mouthOffY = logoH_ / 10; + + // Speed: ~5% of shorter dimension per second at 50ms tick = 0.25% per tick + lv_coord_t speed = std::max(2, shorter / 80); + + logo_ = lv_obj_create(overlay); + lv_obj_remove_style_all(logo_); + lv_obj_set_size(logo_, logoW_, logoH_); + lv_obj_set_style_bg_opa(logo_, LV_OPA_COVER, 0); + lv_obj_set_style_bg_color(logo_, lv_color_hex(COLORS[0]), 0); + lv_obj_set_style_radius(logo_, 0, 0); + lv_obj_clear_flag(logo_, LV_OBJ_FLAG_SCROLLABLE); + + leftEye_ = makeFacePart(logo_, eyeSize, eyeSize, -eyeOffX, eyeOffY, true); + rightEye_ = makeFacePart(logo_, eyeSize, eyeSize, eyeOffX, eyeOffY, true); + mouth_ = makeFacePart(logo_, mouthW, mouthH, 0, mouthOffY, false); + + x_ = (screenW - logoW_) / 2; + y_ = (screenH - logoH_) / 2; + dx_ = speed; + dy_ = speed; + colorIndex_ = 0; + + lv_obj_set_pos(logo_, x_, y_); +} + +void StackChanScreensaver::stop() { + logo_ = nullptr; + leftEye_ = nullptr; + rightEye_ = nullptr; + mouth_ = nullptr; +} + +void StackChanScreensaver::update(lv_coord_t screenW, lv_coord_t screenH) { + if (!logo_) return; + + x_ += dx_; + y_ += dy_; + + bool collided = false; + + if (x_ <= 0) { + x_ = 0; + dx_ = -dx_; + collided = true; + } else if (x_ >= screenW - logoW_) { + x_ = screenW - logoW_; + dx_ = -dx_; + collided = true; + } + + if (y_ <= 0) { + y_ = 0; + dy_ = -dy_; + collided = true; + } else if (y_ >= screenH - logoH_) { + y_ = screenH - logoH_; + dy_ = -dy_; + collided = true; + } + + if (collided) { + colorIndex_ = (colorIndex_ + 1) % static_cast(COLORS.size()); + lv_obj_set_style_bg_color(logo_, lv_color_hex(COLORS[colorIndex_]), 0); + } + + lv_obj_set_pos(logo_, x_, y_); +} + +} // namespace tt::service::displayidle + +#endif // ESP_PLATFORM diff --git a/Tactility/Source/service/displayidle/StackChanScreensaver.h b/Tactility/Source/service/displayidle/StackChanScreensaver.h new file mode 100644 index 000000000..36ea6032d --- /dev/null +++ b/Tactility/Source/service/displayidle/StackChanScreensaver.h @@ -0,0 +1,46 @@ +#pragma once +#ifdef ESP_PLATFORM + +#include "Screensaver.h" +#include +#include + +namespace tt::service::displayidle { + +class StackChanScreensaver final : public Screensaver { +public: + StackChanScreensaver() = default; + ~StackChanScreensaver() override = default; + StackChanScreensaver(const StackChanScreensaver&) = delete; + StackChanScreensaver& operator=(const StackChanScreensaver&) = delete; + StackChanScreensaver(StackChanScreensaver&&) = delete; + StackChanScreensaver& operator=(StackChanScreensaver&&) = delete; + + void start(lv_obj_t* overlay, lv_coord_t screenW, lv_coord_t screenH) override; + void stop() override; + void update(lv_coord_t screenW, lv_coord_t screenH) override; + +private: + static constexpr std::array COLORS = { + 0xffffff, 0xfffa01, 0xff8300, 0x00feff, + 0xff2600, 0xbe00ff, 0x0026ff, 0xff008b, + }; + static_assert(COLORS.size() > 0); + + lv_obj_t* logo_ = nullptr; + lv_obj_t* leftEye_ = nullptr; + lv_obj_t* rightEye_ = nullptr; + lv_obj_t* mouth_ = nullptr; + + lv_coord_t logoW_ = 0; + lv_coord_t logoH_ = 0; + lv_coord_t x_ = 0; + lv_coord_t y_ = 0; + lv_coord_t dx_ = 0; + lv_coord_t dy_ = 0; + int colorIndex_ = 0; +}; + +} // namespace tt::service::displayidle + +#endif // ESP_PLATFORM diff --git a/Tactility/Source/settings/DisplaySettings.cpp b/Tactility/Source/settings/DisplaySettings.cpp index 815cbc0ac..ab3a94461 100644 --- a/Tactility/Source/settings/DisplaySettings.cpp +++ b/Tactility/Source/settings/DisplaySettings.cpp @@ -76,6 +76,8 @@ static std::string toString(ScreensaverType type) { return "Mystify"; case MatrixRain: return "MatrixRain"; + case StackChan: + return "StackChan"; default: std::unreachable(); } @@ -94,6 +96,9 @@ static bool fromString(const std::string& str, ScreensaverType& type) { } else if (str == "MatrixRain") { type = ScreensaverType::MatrixRain; return true; + } else if (str == "StackChan") { + type = ScreensaverType::StackChan; + return true; } else { return false; } diff --git a/TactilityC/Source/symbols/freertos.cpp b/TactilityC/Source/symbols/freertos.cpp index fbc943c0e..ae98521af 100644 --- a/TactilityC/Source/symbols/freertos.cpp +++ b/TactilityC/Source/symbols/freertos.cpp @@ -39,6 +39,7 @@ const esp_elfsym freertos_symbols[] = { ESP_ELFSYM_EXPORT(xTaskDelayUntil), ESP_ELFSYM_EXPORT(xTaskGenericNotify), ESP_ELFSYM_EXPORT(xTaskGenericNotifyFromISR), + ESP_ELFSYM_EXPORT(ulTaskGenericNotifyTake), ESP_ELFSYM_EXPORT(xTaskGetTickCount), ESP_ELFSYM_EXPORT(xTaskGetTickCountFromISR), ESP_ELFSYM_EXPORT(pvTaskGetThreadLocalStoragePointer), diff --git a/TactilityC/Source/symbols/stl.cpp b/TactilityC/Source/symbols/stl.cpp index 7bc96f60d..7def89d3a 100644 --- a/TactilityC/Source/symbols/stl.cpp +++ b/TactilityC/Source/symbols/stl.cpp @@ -26,6 +26,7 @@ const esp_elfsym stl_symbols[] = { { "_ZSt20__throw_length_errorPKc", (void*)&(std::__throw_length_error) }, { "_ZSt19__throw_logic_errorPKc", (void*)&std::__throw_logic_error }, { "_ZSt24__throw_out_of_range_fmtPKcz", (void*)&std::__throw_out_of_range_fmt }, + { "_ZSt20__throw_system_errori", (void*)&std::__throw_system_error }, // std::map / std::set (red-black tree internals) DEFINE_MODULE_SYMBOL(_ZSt18_Rb_tree_decrementPSt18_Rb_tree_node_base), DEFINE_MODULE_SYMBOL(_ZSt18_Rb_tree_incrementPSt18_Rb_tree_node_base), diff --git a/TactilityKernel/include/tactility/drivers/i2c_controller.h b/TactilityKernel/include/tactility/drivers/i2c_controller.h index 1cd8da057..5663f9dc9 100644 --- a/TactilityKernel/include/tactility/drivers/i2c_controller.h +++ b/TactilityKernel/include/tactility/drivers/i2c_controller.h @@ -218,6 +218,39 @@ error_t i2c_controller_register8_reset_bits(struct Device* device, uint8_t addre */ error_t i2c_controller_register16le_get(struct Device* device, uint8_t address, uint8_t reg, uint16_t* value, TickType_t timeout); +/** + * @brief Writes a little-endian 16-bit value to a register of an I2C device. + * @param[in] device the I2C controller device + * @param[in] address the 7-bit I2C address of the slave device + * @param[in] reg the register address + * @param[in] value the 16-bit value to write (low byte first) + * @param[in] timeout the maximum time to wait for the operation to complete + * @retval ERROR_NONE when the write operation was successful + */ +error_t i2c_controller_register16le_set(struct Device* device, uint8_t address, uint8_t reg, uint16_t value, TickType_t timeout); + +/** + * @brief Reads a big-endian 16-bit register value from an I2C device. + * @param[in] device the I2C controller device + * @param[in] address the 7-bit I2C address of the slave device + * @param[in] reg the register address of the high byte + * @param[out] value a pointer to the variable to store the 16-bit result + * @param[in] timeout the maximum time to wait for the operation to complete + * @retval ERROR_NONE when the read operation was successful + */ +error_t i2c_controller_register16be_get(struct Device* device, uint8_t address, uint8_t reg, uint16_t* value, TickType_t timeout); + +/** + * @brief Writes a big-endian 16-bit value to a register of an I2C device. + * @param[in] device the I2C controller device + * @param[in] address the 7-bit I2C address of the slave device + * @param[in] reg the register address + * @param[in] value the 16-bit value to write (high byte first) + * @param[in] timeout the maximum time to wait for the operation to complete + * @retval ERROR_NONE when the write operation was successful + */ +error_t i2c_controller_register16be_set(struct Device* device, uint8_t address, uint8_t reg, uint16_t value, TickType_t timeout); + extern const struct DeviceType I2C_CONTROLLER_TYPE; #ifdef __cplusplus diff --git a/TactilityKernel/include/tactility/drivers/i2s_controller.h b/TactilityKernel/include/tactility/drivers/i2s_controller.h index 13f473091..cf9f94e14 100644 --- a/TactilityKernel/include/tactility/drivers/i2s_controller.h +++ b/TactilityKernel/include/tactility/drivers/i2s_controller.h @@ -28,6 +28,18 @@ enum I2sCommunicationFormat { #define I2S_CHANNEL_NONE -1 +/** + * @brief I2S TDM RX config (e.g. for ES7210 4-slot microphone ADC) + */ +struct I2sTdmRxConfig { + uint32_t sample_rate_hz; + uint32_t mclk_multiple; // e.g. 256 → I2S_MCLK_MULTIPLE_256 + uint8_t bclk_div; // e.g. 8; must not be 0 + uint8_t slot_count; // number of TDM slots, e.g. 4; valid range: 1–16 + uint8_t bits_per_sample; // 16, 24, or 32 + uint8_t slot_bit_width; // bit width of each TDM slot (0 = auto, matches bits_per_sample) +}; + /** * @brief I2S Config */ @@ -89,6 +101,16 @@ struct I2sControllerApi { * @retval ERROR_NONE when the operation was successful */ error_t (*reset)(struct Device* device); + + /** + * @brief Reconfigures the RX channel to TDM mode (e.g. for ES7210). + * Must be called after set_config() which creates the channel handles. + * @param[in] device the I2S controller device + * @param[in] config TDM parameters + * @retval ERROR_NONE when the operation was successful + * @retval ERROR_NOT_SUPPORTED if the driver does not implement TDM + */ + error_t (*set_rx_tdm_config)(struct Device* device, const struct I2sTdmRxConfig* config); }; /** @@ -136,6 +158,16 @@ error_t i2s_controller_get_config(struct Device* device, struct I2sConfig* confi */ error_t i2s_controller_reset(struct Device* device); +/** + * @brief Reconfigures the RX channel to TDM mode (e.g. for ES7210 4-slot mic ADC). + * Must be called after i2s_controller_set_config() which creates the channel handles. + * @param[in] device the I2S controller device + * @param[in] config TDM parameters + * @retval ERROR_NONE when the operation was successful + * @retval ERROR_NOT_SUPPORTED if the driver does not implement TDM + */ +error_t i2s_controller_set_rx_tdm_config(struct Device* device, const struct I2sTdmRxConfig* config); + extern const struct DeviceType I2S_CONTROLLER_TYPE; #ifdef __cplusplus diff --git a/TactilityKernel/source/drivers/i2c_controller.cpp b/TactilityKernel/source/drivers/i2c_controller.cpp index cba7437dd..521f5d77c 100644 --- a/TactilityKernel/source/drivers/i2c_controller.cpp +++ b/TactilityKernel/source/drivers/i2c_controller.cpp @@ -85,6 +85,24 @@ error_t i2c_controller_register16le_get(Device* device, uint8_t address, uint8_t return ERROR_NONE; } +error_t i2c_controller_register16le_set(Device* device, uint8_t address, uint8_t reg, uint16_t value, TickType_t timeout) { + uint8_t buf[2] = { static_cast(value & 0xFF), static_cast(value >> 8) }; + return i2c_controller_write_register(device, address, reg, buf, 2, timeout); +} + +error_t i2c_controller_register16be_get(Device* device, uint8_t address, uint8_t reg, uint16_t* value, TickType_t timeout) { + uint8_t buf[2] = {}; + error_t err = i2c_controller_read_register(device, address, reg, buf, 2, timeout); + if (err != ERROR_NONE) return err; + *value = static_cast((buf[0] << 8) | buf[1]); + return ERROR_NONE; +} + +error_t i2c_controller_register16be_set(Device* device, uint8_t address, uint8_t reg, uint16_t value, TickType_t timeout) { + uint8_t buf[2] = { static_cast(value >> 8), static_cast(value & 0xFF) }; + return i2c_controller_write_register(device, address, reg, buf, 2, timeout); +} + const struct DeviceType I2C_CONTROLLER_TYPE { .name = "i2c-controller" }; diff --git a/TactilityKernel/source/drivers/i2s_controller.cpp b/TactilityKernel/source/drivers/i2s_controller.cpp index a228d3bdd..3942ed47e 100644 --- a/TactilityKernel/source/drivers/i2s_controller.cpp +++ b/TactilityKernel/source/drivers/i2s_controller.cpp @@ -32,6 +32,12 @@ error_t i2s_controller_reset(struct Device* device) { return I2S_DRIVER_API(driver)->reset(device); } +error_t i2s_controller_set_rx_tdm_config(struct Device* device, const struct I2sTdmRxConfig* config) { + const auto* driver = device_get_driver(device); + if (!I2S_DRIVER_API(driver)->set_rx_tdm_config) return ERROR_NOT_SUPPORTED; + return I2S_DRIVER_API(driver)->set_rx_tdm_config(device, config); +} + const struct DeviceType I2S_CONTROLLER_TYPE { .name = "i2s-controller" }; diff --git a/TactilityKernel/source/kernel_symbols.c b/TactilityKernel/source/kernel_symbols.c index 57772fd37..59f6e2577 100644 --- a/TactilityKernel/source/kernel_symbols.c +++ b/TactilityKernel/source/kernel_symbols.c @@ -100,6 +100,7 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = { DEFINE_MODULE_SYMBOL(i2s_controller_set_config), DEFINE_MODULE_SYMBOL(i2s_controller_get_config), DEFINE_MODULE_SYMBOL(i2s_controller_reset), + DEFINE_MODULE_SYMBOL(i2s_controller_set_rx_tdm_config), DEFINE_MODULE_SYMBOL(I2S_CONTROLLER_TYPE), // drivers/root DEFINE_MODULE_SYMBOL(root_is_model), diff --git a/device.py b/device.py index 84e09aee4..dc4b5c2ca 100644 --- a/device.py +++ b/device.py @@ -142,6 +142,16 @@ def write_core_variables(output_file, device_properties: ConfigParser): output_file.write("# Enable usage of MALLOC_CAP_EXEC on IRAM:\n") output_file.write("CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n\n") output_file.write("CONFIG_ESP_SYSTEM_MEMPROT_FEATURE_LOCK=n\n") + else: + # Original ESP32 has very limited IRAM (~328KB shared with Wi-Fi/BT). + # Disable Wi-Fi IRAM optimizations to free ~27KB; throughput impact is + # acceptable for these embedded devices. Also move heap/ringbuf ISR + # stubs to flash. + output_file.write("# Free IRAM on original ESP32\n") + output_file.write("CONFIG_ESP_WIFI_IRAM_OPT=n\n") + output_file.write("CONFIG_ESP_WIFI_RX_IRAM_OPT=n\n") + output_file.write("CONFIG_HEAP_PLACE_FUNCTION_INTO_FLASH=y\n") + output_file.write("CONFIG_RINGBUF_PLACE_ISR_FUNCTIONS_INTO_FLASH=y\n") def write_flash_variables(output_file, device_properties: ConfigParser): flash_size = get_property_or_exit(device_properties, "hardware", "flashSize") diff --git a/partitions-16mb.csv b/partitions-16mb.csv index 65708c9d0..89987213c 100644 --- a/partitions-16mb.csv +++ b/partitions-16mb.csv @@ -2,6 +2,6 @@ # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap nvs, data, nvs, 0x9000, 0x6000, phy_init, data, phy, 0xf000, 0x1000, -factory, app, factory, 0x10000, 3M, +factory, app, factory, 0x10000, 4M, system, data, fat, , 300k, -data, data, fat, , 12600k, +data, data, fat, , 11600k, diff --git a/partitions-8mb.csv b/partitions-8mb.csv index 8b378d833..6051c95d2 100644 --- a/partitions-8mb.csv +++ b/partitions-8mb.csv @@ -2,6 +2,6 @@ # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap nvs, data, nvs, 0x9000, 0x6000, phy_init, data, phy, 0xf000, 0x1000, -factory, app, factory, 0x10000, 3M, +factory, app, factory, 0x10000, 4M, system, data, fat, , 300k, -data, data, fat, , 4600k, +data, data, fat, , 3600k,