USB device-mode support (#613)
This commit is contained in:
@@ -8,7 +8,7 @@ hardware.flashSize=16MB
|
|||||||
hardware.spiRam=true
|
hardware.spiRam=true
|
||||||
hardware.spiRamMode=OCT
|
hardware.spiRamMode=OCT
|
||||||
hardware.spiRamSpeed=120M
|
hardware.spiRamSpeed=120M
|
||||||
hardware.tinyUsb=true
|
hardware.tinyUsbMsc=true
|
||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <tactility/bindings/esp32_i2c.h>
|
#include <tactility/bindings/esp32_i2c.h>
|
||||||
#include <tactility/bindings/esp32_spi.h>
|
#include <tactility/bindings/esp32_spi.h>
|
||||||
#include <tactility/bindings/esp32_uart.h>
|
#include <tactility/bindings/esp32_uart.h>
|
||||||
|
#include <tactility/bindings/esp32_usbdevice.h>
|
||||||
#include <tactility/bindings/esp32_sdspi.h>
|
#include <tactility/bindings/esp32_sdspi.h>
|
||||||
#include <tactility/bindings/esp32_pwm_ledc.h>
|
#include <tactility/bindings/esp32_pwm_ledc.h>
|
||||||
#include <tactility/bindings/pwm_backlight.h>
|
#include <tactility/bindings/pwm_backlight.h>
|
||||||
@@ -105,4 +106,12 @@
|
|||||||
pin-tx = <&gpio0 17 GPIO_FLAG_NONE>;
|
pin-tx = <&gpio0 17 GPIO_FLAG_NONE>;
|
||||||
pin-rx = <&gpio0 18 GPIO_FLAG_NONE>;
|
pin-rx = <&gpio0 18 GPIO_FLAG_NONE>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
usbdevice0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice";
|
||||||
|
|
||||||
|
usbdevicemsc0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice-msc";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ hardware.flashSize=16MB
|
|||||||
hardware.spiRam=true
|
hardware.spiRam=true
|
||||||
hardware.spiRamMode=OCT
|
hardware.spiRamMode=OCT
|
||||||
hardware.spiRamSpeed=120M
|
hardware.spiRamSpeed=120M
|
||||||
hardware.tinyUsb=true
|
hardware.tinyUsbMsc=true
|
||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <tactility/bindings/esp32_i2c.h>
|
#include <tactility/bindings/esp32_i2c.h>
|
||||||
#include <tactility/bindings/esp32_spi.h>
|
#include <tactility/bindings/esp32_spi.h>
|
||||||
#include <tactility/bindings/esp32_uart.h>
|
#include <tactility/bindings/esp32_uart.h>
|
||||||
|
#include <tactility/bindings/esp32_usbdevice.h>
|
||||||
#include <tactility/bindings/esp32_sdspi.h>
|
#include <tactility/bindings/esp32_sdspi.h>
|
||||||
#include <tactility/bindings/esp32_pwm_ledc.h>
|
#include <tactility/bindings/esp32_pwm_ledc.h>
|
||||||
#include <tactility/bindings/pwm_backlight.h>
|
#include <tactility/bindings/pwm_backlight.h>
|
||||||
@@ -110,4 +111,12 @@
|
|||||||
pin-tx = <&gpio0 17 GPIO_FLAG_NONE>;
|
pin-tx = <&gpio0 17 GPIO_FLAG_NONE>;
|
||||||
pin-rx = <&gpio0 18 GPIO_FLAG_NONE>;
|
pin-rx = <&gpio0 18 GPIO_FLAG_NONE>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
usbdevice0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice";
|
||||||
|
|
||||||
|
usbdevicemsc0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice-msc";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ hardware.flashSize=16MB
|
|||||||
hardware.spiRam=true
|
hardware.spiRam=true
|
||||||
hardware.spiRamMode=OCT
|
hardware.spiRamMode=OCT
|
||||||
hardware.spiRamSpeed=120M
|
hardware.spiRamSpeed=120M
|
||||||
hardware.tinyUsb=true
|
hardware.tinyUsbMsc=true
|
||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <tactility/bindings/esp32_i2c.h>
|
#include <tactility/bindings/esp32_i2c.h>
|
||||||
#include <tactility/bindings/esp32_spi.h>
|
#include <tactility/bindings/esp32_spi.h>
|
||||||
#include <tactility/bindings/esp32_uart.h>
|
#include <tactility/bindings/esp32_uart.h>
|
||||||
|
#include <tactility/bindings/esp32_usbdevice.h>
|
||||||
#include <tactility/bindings/esp32_sdspi.h>
|
#include <tactility/bindings/esp32_sdspi.h>
|
||||||
#include <tactility/bindings/gpio_backlight.h>
|
#include <tactility/bindings/gpio_backlight.h>
|
||||||
#include <bindings/rgb_display.h>
|
#include <bindings/rgb_display.h>
|
||||||
@@ -123,4 +124,12 @@
|
|||||||
pin-tx = <&gpio0 20 GPIO_FLAG_NONE>;
|
pin-tx = <&gpio0 20 GPIO_FLAG_NONE>;
|
||||||
pin-rx = <&gpio0 19 GPIO_FLAG_NONE>;
|
pin-rx = <&gpio0 19 GPIO_FLAG_NONE>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
usbdevice0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice";
|
||||||
|
|
||||||
|
usbdevicemsc0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice-msc";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ hardware.flashSize=4MB
|
|||||||
hardware.spiRam=true
|
hardware.spiRam=true
|
||||||
hardware.spiRamMode=OCT
|
hardware.spiRamMode=OCT
|
||||||
hardware.spiRamSpeed=120M
|
hardware.spiRamSpeed=120M
|
||||||
hardware.tinyUsb=true
|
hardware.tinyUsbMsc=true
|
||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <tactility/bindings/esp32_i2c.h>
|
#include <tactility/bindings/esp32_i2c.h>
|
||||||
#include <tactility/bindings/esp32_spi.h>
|
#include <tactility/bindings/esp32_spi.h>
|
||||||
#include <tactility/bindings/esp32_uart.h>
|
#include <tactility/bindings/esp32_uart.h>
|
||||||
|
#include <tactility/bindings/esp32_usbdevice.h>
|
||||||
#include <tactility/bindings/esp32_sdspi.h>
|
#include <tactility/bindings/esp32_sdspi.h>
|
||||||
#include <tactility/bindings/esp32_pwm_ledc.h>
|
#include <tactility/bindings/esp32_pwm_ledc.h>
|
||||||
#include <tactility/bindings/pwm_backlight.h>
|
#include <tactility/bindings/pwm_backlight.h>
|
||||||
@@ -119,4 +120,12 @@
|
|||||||
pin-tx = <&gpio0 43 GPIO_FLAG_NONE>;
|
pin-tx = <&gpio0 43 GPIO_FLAG_NONE>;
|
||||||
pin-rx = <&gpio0 44 GPIO_FLAG_NONE>;
|
pin-rx = <&gpio0 44 GPIO_FLAG_NONE>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
usbdevice0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice";
|
||||||
|
|
||||||
|
usbdevicemsc0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice-msc";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ hardware.spiRam=true
|
|||||||
hardware.spiRamMode=OCT
|
hardware.spiRamMode=OCT
|
||||||
hardware.spiRamSpeed=120M
|
hardware.spiRamSpeed=120M
|
||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.tinyUsb=true
|
hardware.tinyUsbMsc=true
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
display.size=2.8"
|
display.size=2.8"
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <tactility/bindings/esp32_pwm_ledc.h>
|
#include <tactility/bindings/esp32_pwm_ledc.h>
|
||||||
#include <tactility/bindings/esp32_sdmmc.h>
|
#include <tactility/bindings/esp32_sdmmc.h>
|
||||||
#include <tactility/bindings/esp32_spi.h>
|
#include <tactility/bindings/esp32_spi.h>
|
||||||
|
#include <tactility/bindings/esp32_usbdevice.h>
|
||||||
#include <tactility/bindings/esp32_wifi_pinned.h>
|
#include <tactility/bindings/esp32_wifi_pinned.h>
|
||||||
#include <tactility/bindings/battery_sense.h>
|
#include <tactility/bindings/battery_sense.h>
|
||||||
#include <tactility/bindings/gpio_hog.h>
|
#include <tactility/bindings/gpio_hog.h>
|
||||||
@@ -139,4 +140,12 @@
|
|||||||
slot = <SDMMC_HOST_SLOT_1>;
|
slot = <SDMMC_HOST_SLOT_1>;
|
||||||
bus-width = <4>;
|
bus-width = <4>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
usbdevice0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice";
|
||||||
|
|
||||||
|
usbdevicemsc0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice-msc";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ apps.autoStartAppId=ApWebServer
|
|||||||
hardware.target=ESP32S3
|
hardware.target=ESP32S3
|
||||||
hardware.flashSize=8MB
|
hardware.flashSize=8MB
|
||||||
hardware.spiRam=false
|
hardware.spiRam=false
|
||||||
hardware.tinyUsb=true
|
hardware.tinyUsbMsc=true
|
||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <tactility/bindings/esp32_gpio.h>
|
#include <tactility/bindings/esp32_gpio.h>
|
||||||
#include <tactility/bindings/esp32_i2c.h>
|
#include <tactility/bindings/esp32_i2c.h>
|
||||||
#include <tactility/bindings/esp32_adc_oneshot.h>
|
#include <tactility/bindings/esp32_adc_oneshot.h>
|
||||||
|
#include <tactility/bindings/esp32_usbdevice.h>
|
||||||
#include <tactility/bindings/battery_sense.h>
|
#include <tactility/bindings/battery_sense.h>
|
||||||
#include <tactility/bindings/gpio_hog.h>
|
#include <tactility/bindings/gpio_hog.h>
|
||||||
#include <bindings/button_control.h>
|
#include <bindings/button_control.h>
|
||||||
@@ -113,4 +114,12 @@
|
|||||||
compatible = "tactility,button-control";
|
compatible = "tactility,button-control";
|
||||||
pin-primary = <&gpio0 0 GPIO_FLAG_NONE>;
|
pin-primary = <&gpio0 0 GPIO_FLAG_NONE>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
usbdevice0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice";
|
||||||
|
|
||||||
|
usbdevicemsc0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice-msc";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ hardware.flashMode=DIO
|
|||||||
hardware.spiRam=true
|
hardware.spiRam=true
|
||||||
hardware.spiRamMode=AUTO
|
hardware.spiRamMode=AUTO
|
||||||
hardware.spiRamSpeed=80M
|
hardware.spiRamSpeed=80M
|
||||||
hardware.tinyUsb=true
|
hardware.tinyUsbMsc=true
|
||||||
hardware.esptoolFlashFreq=80M
|
hardware.esptoolFlashFreq=80M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <tactility/bindings/esp32_spi.h>
|
#include <tactility/bindings/esp32_spi.h>
|
||||||
#include <tactility/bindings/esp32_sdspi.h>
|
#include <tactility/bindings/esp32_sdspi.h>
|
||||||
#include <tactility/bindings/esp32_pwm_ledc.h>
|
#include <tactility/bindings/esp32_pwm_ledc.h>
|
||||||
|
#include <tactility/bindings/esp32_usbdevice.h>
|
||||||
#include <tactility/bindings/pwm_backlight.h>
|
#include <tactility/bindings/pwm_backlight.h>
|
||||||
#include <bindings/sx1262.h>
|
#include <bindings/sx1262.h>
|
||||||
#include <bindings/xl9555.h>
|
#include <bindings/xl9555.h>
|
||||||
@@ -179,4 +180,12 @@
|
|||||||
dio2-as-rf-switch;
|
dio2-as-rf-switch;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
usbdevice0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice";
|
||||||
|
|
||||||
|
usbdevicemsc0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice-msc";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ hardware.flashSize=16MB
|
|||||||
hardware.spiRam=true
|
hardware.spiRam=true
|
||||||
hardware.spiRamMode=OCT
|
hardware.spiRamMode=OCT
|
||||||
hardware.spiRamSpeed=120M
|
hardware.spiRamSpeed=120M
|
||||||
hardware.tinyUsb=true
|
hardware.tinyUsbMsc=true
|
||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#include <tactility/bindings/esp32_spi.h>
|
#include <tactility/bindings/esp32_spi.h>
|
||||||
#include <tactility/bindings/esp32_wifi_pinned.h>
|
#include <tactility/bindings/esp32_wifi_pinned.h>
|
||||||
#include <tactility/bindings/esp32_uart.h>
|
#include <tactility/bindings/esp32_uart.h>
|
||||||
|
#include <tactility/bindings/esp32_usbdevice.h>
|
||||||
|
|
||||||
#include <bindings/gt911.h>
|
#include <bindings/gt911.h>
|
||||||
#include <bindings/st7789.h>
|
#include <bindings/st7789.h>
|
||||||
@@ -198,4 +199,12 @@
|
|||||||
model = <GPS_MODEL_UBLOX10>;
|
model = <GPS_MODEL_UBLOX10>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
usbdevice0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice";
|
||||||
|
|
||||||
|
usbdevicemsc0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice-msc";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ hardware.flashMode=DIO
|
|||||||
hardware.spiRam=true
|
hardware.spiRam=true
|
||||||
hardware.spiRamMode=AUTO
|
hardware.spiRamMode=AUTO
|
||||||
hardware.spiRamSpeed=80M
|
hardware.spiRamSpeed=80M
|
||||||
hardware.tinyUsb=true
|
hardware.tinyUsbMsc=true
|
||||||
hardware.esptoolFlashFreq=80M
|
hardware.esptoolFlashFreq=80M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <tactility/bindings/esp32_spi.h>
|
#include <tactility/bindings/esp32_spi.h>
|
||||||
#include <tactility/bindings/esp32_sdspi.h>
|
#include <tactility/bindings/esp32_sdspi.h>
|
||||||
#include <tactility/bindings/esp32_pwm_ledc.h>
|
#include <tactility/bindings/esp32_pwm_ledc.h>
|
||||||
|
#include <tactility/bindings/esp32_usbdevice.h>
|
||||||
#include <tactility/bindings/pwm_backlight.h>
|
#include <tactility/bindings/pwm_backlight.h>
|
||||||
#include <bindings/cst328.h>
|
#include <bindings/cst328.h>
|
||||||
#include <bindings/gdeq031t10.h>
|
#include <bindings/gdeq031t10.h>
|
||||||
@@ -135,4 +136,12 @@
|
|||||||
frequency-khz = <20000>;
|
frequency-khz = <20000>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
usbdevice0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice";
|
||||||
|
|
||||||
|
usbdevicemsc0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice-msc";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ hardware.flashSize=16MB
|
|||||||
hardware.spiRam=true
|
hardware.spiRam=true
|
||||||
hardware.spiRamMode=OCT
|
hardware.spiRamMode=OCT
|
||||||
hardware.spiRamSpeed=120M
|
hardware.spiRamSpeed=120M
|
||||||
hardware.tinyUsb=true
|
hardware.tinyUsbMsc=true
|
||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#include <tactility/bindings/esp32_spi.h>
|
#include <tactility/bindings/esp32_spi.h>
|
||||||
#include <tactility/bindings/esp32_wifi_pinned.h>
|
#include <tactility/bindings/esp32_wifi_pinned.h>
|
||||||
#include <tactility/bindings/esp32_uart.h>
|
#include <tactility/bindings/esp32_uart.h>
|
||||||
|
#include <tactility/bindings/esp32_usbdevice.h>
|
||||||
|
|
||||||
#include <bindings/gt911.h>
|
#include <bindings/gt911.h>
|
||||||
#include <bindings/st7789.h>
|
#include <bindings/st7789.h>
|
||||||
@@ -192,4 +193,12 @@
|
|||||||
i2cPort = <I2C_NUM_1>;
|
i2cPort = <I2C_NUM_1>;
|
||||||
i2cClockFrequency = <400000>;
|
i2cClockFrequency = <400000>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
usbdevice0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice";
|
||||||
|
|
||||||
|
usbdevicemsc0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice-msc";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ hardware.flashSize=16MB
|
|||||||
hardware.spiRam=true
|
hardware.spiRam=true
|
||||||
hardware.spiRamMode=OCT
|
hardware.spiRamMode=OCT
|
||||||
hardware.spiRamSpeed=120M
|
hardware.spiRamSpeed=120M
|
||||||
hardware.tinyUsb=true
|
hardware.tinyUsbMsc=true
|
||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <tactility/bindings/battery_sense.h>
|
#include <tactility/bindings/battery_sense.h>
|
||||||
#include <tactility/bindings/esp32_i8080.h>
|
#include <tactility/bindings/esp32_i8080.h>
|
||||||
#include <tactility/bindings/esp32_pwm_ledc.h>
|
#include <tactility/bindings/esp32_pwm_ledc.h>
|
||||||
|
#include <tactility/bindings/esp32_usbdevice.h>
|
||||||
#include <tactility/bindings/pwm_backlight.h>
|
#include <tactility/bindings/pwm_backlight.h>
|
||||||
#include <bindings/st7789_i8080.h>
|
#include <bindings/st7789_i8080.h>
|
||||||
#include <bindings/button_control.h>
|
#include <bindings/button_control.h>
|
||||||
@@ -95,4 +96,12 @@
|
|||||||
pin-primary = <&gpio0 0 GPIO_FLAG_NONE>;
|
pin-primary = <&gpio0 0 GPIO_FLAG_NONE>;
|
||||||
pin-secondary = <&gpio0 14 GPIO_FLAG_NONE>;
|
pin-secondary = <&gpio0 14 GPIO_FLAG_NONE>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
usbdevice0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice";
|
||||||
|
|
||||||
|
usbdevicemsc0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice-msc";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ apps.autoStartAppId=ApWebServer
|
|||||||
hardware.target=ESP32S3
|
hardware.target=ESP32S3
|
||||||
hardware.flashSize=16MB
|
hardware.flashSize=16MB
|
||||||
hardware.spiRam=false
|
hardware.spiRam=false
|
||||||
hardware.tinyUsb=true
|
hardware.tinyUsbMsc=true
|
||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <tactility/bindings/esp32_sdmmc.h>
|
#include <tactility/bindings/esp32_sdmmc.h>
|
||||||
#include <tactility/bindings/esp32_spi.h>
|
#include <tactility/bindings/esp32_spi.h>
|
||||||
#include <tactility/bindings/esp32_uart.h>
|
#include <tactility/bindings/esp32_uart.h>
|
||||||
|
#include <tactility/bindings/esp32_usbdevice.h>
|
||||||
#include <tactility/bindings/esp32_pwm_ledc.h>
|
#include <tactility/bindings/esp32_pwm_ledc.h>
|
||||||
#include <tactility/bindings/pwm_backlight.h>
|
#include <tactility/bindings/pwm_backlight.h>
|
||||||
#include <bindings/st7735.h>
|
#include <bindings/st7735.h>
|
||||||
@@ -95,4 +96,12 @@
|
|||||||
pin-tx = <&gpio0 43 GPIO_FLAG_NONE>;
|
pin-tx = <&gpio0 43 GPIO_FLAG_NONE>;
|
||||||
pin-rx = <&gpio0 44 GPIO_FLAG_NONE>;
|
pin-rx = <&gpio0 44 GPIO_FLAG_NONE>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
usbdevice0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice";
|
||||||
|
|
||||||
|
usbdevicemsc0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice-msc";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ hardware.flashSize=16MB
|
|||||||
hardware.spiRam=true
|
hardware.spiRam=true
|
||||||
hardware.spiRamMode=OCT
|
hardware.spiRamMode=OCT
|
||||||
hardware.spiRamSpeed=120M
|
hardware.spiRamSpeed=120M
|
||||||
hardware.tinyUsb=true
|
hardware.tinyUsbMsc=true
|
||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include <tactility/bindings/esp32_sdmmc.h>
|
#include <tactility/bindings/esp32_sdmmc.h>
|
||||||
#include <tactility/bindings/esp32_i8080.h>
|
#include <tactility/bindings/esp32_i8080.h>
|
||||||
#include <tactility/bindings/esp32_pwm_ledc.h>
|
#include <tactility/bindings/esp32_pwm_ledc.h>
|
||||||
|
#include <tactility/bindings/esp32_usbdevice.h>
|
||||||
#include <tactility/bindings/pwm_backlight.h>
|
#include <tactility/bindings/pwm_backlight.h>
|
||||||
#include <tactility/bindings/gpio_hog.h>
|
#include <tactility/bindings/gpio_hog.h>
|
||||||
#include <bindings/button_control.h>
|
#include <bindings/button_control.h>
|
||||||
@@ -131,4 +132,12 @@
|
|||||||
bus-width = <1>;
|
bus-width = <1>;
|
||||||
pullups;
|
pullups;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
usbdevice0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice";
|
||||||
|
|
||||||
|
usbdevicemsc0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice-msc";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ hardware.flashMode=DIO
|
|||||||
hardware.spiRam=true
|
hardware.spiRam=true
|
||||||
hardware.spiRamMode=AUTO
|
hardware.spiRamMode=AUTO
|
||||||
hardware.spiRamSpeed=120M
|
hardware.spiRamSpeed=120M
|
||||||
hardware.tinyUsb=true
|
hardware.tinyUsbMsc=true
|
||||||
hardware.esptoolFlashFreq=40M
|
hardware.esptoolFlashFreq=40M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include <tactility/bindings/esp32_uart.h>
|
#include <tactility/bindings/esp32_uart.h>
|
||||||
#include <tactility/bindings/esp32_sdspi.h>
|
#include <tactility/bindings/esp32_sdspi.h>
|
||||||
#include <tactility/bindings/esp32_pwm_ledc.h>
|
#include <tactility/bindings/esp32_pwm_ledc.h>
|
||||||
|
#include <tactility/bindings/esp32_usbdevice.h>
|
||||||
#include <tactility/bindings/pwm_backlight.h>
|
#include <tactility/bindings/pwm_backlight.h>
|
||||||
#include <gps_generic/bindings.h>
|
#include <gps_generic/bindings.h>
|
||||||
#include <bindings/st7796.h>
|
#include <bindings/st7796.h>
|
||||||
@@ -205,4 +206,12 @@
|
|||||||
pin-b = <&gpio0 41 GPIO_FLAG_NONE>;
|
pin-b = <&gpio0 41 GPIO_FLAG_NONE>;
|
||||||
pin-enter = <&gpio0 7 GPIO_FLAG_NONE>;
|
pin-enter = <&gpio0 7 GPIO_FLAG_NONE>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
usbdevice0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice";
|
||||||
|
|
||||||
|
usbdevicemsc0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice-msc";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ apps.launcherAppId=Launcher
|
|||||||
hardware.target=ESP32S3
|
hardware.target=ESP32S3
|
||||||
hardware.flashSize=8MB
|
hardware.flashSize=8MB
|
||||||
hardware.spiRam=false
|
hardware.spiRam=false
|
||||||
hardware.tinyUsb=true
|
hardware.tinyUsbMsc=true
|
||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <tactility/bindings/esp32_i2s.h>
|
#include <tactility/bindings/esp32_i2s.h>
|
||||||
#include <tactility/bindings/esp32_spi.h>
|
#include <tactility/bindings/esp32_spi.h>
|
||||||
#include <tactility/bindings/esp32_uart.h>
|
#include <tactility/bindings/esp32_uart.h>
|
||||||
|
#include <tactility/bindings/esp32_usbdevice.h>
|
||||||
#include <bindings/bmi270.h>
|
#include <bindings/bmi270.h>
|
||||||
#include <tactility/bindings/esp32_sdspi.h>
|
#include <tactility/bindings/esp32_sdspi.h>
|
||||||
#include <tactility/bindings/esp32_pwm_ledc.h>
|
#include <tactility/bindings/esp32_pwm_ledc.h>
|
||||||
@@ -148,4 +149,12 @@
|
|||||||
pin-data-out = <&gpio0 42 GPIO_FLAG_NONE>;
|
pin-data-out = <&gpio0 42 GPIO_FLAG_NONE>;
|
||||||
pin-data-in = <&gpio0 46 GPIO_FLAG_NONE>;
|
pin-data-in = <&gpio0 46 GPIO_FLAG_NONE>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
usbdevice0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice";
|
||||||
|
|
||||||
|
usbdevicemsc0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice-msc";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ apps.launcherAppId=Launcher
|
|||||||
hardware.target=ESP32S3
|
hardware.target=ESP32S3
|
||||||
hardware.flashSize=8MB
|
hardware.flashSize=8MB
|
||||||
hardware.spiRam=false
|
hardware.spiRam=false
|
||||||
hardware.tinyUsb=true
|
hardware.tinyUsbMsc=true
|
||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <tactility/bindings/esp32_i2s.h>
|
#include <tactility/bindings/esp32_i2s.h>
|
||||||
#include <tactility/bindings/esp32_spi.h>
|
#include <tactility/bindings/esp32_spi.h>
|
||||||
#include <tactility/bindings/esp32_uart.h>
|
#include <tactility/bindings/esp32_uart.h>
|
||||||
|
#include <tactility/bindings/esp32_usbdevice.h>
|
||||||
#include <tactility/bindings/esp32_sdspi.h>
|
#include <tactility/bindings/esp32_sdspi.h>
|
||||||
#include <tactility/bindings/esp32_pwm_ledc.h>
|
#include <tactility/bindings/esp32_pwm_ledc.h>
|
||||||
#include <tactility/bindings/pwm_backlight.h>
|
#include <tactility/bindings/pwm_backlight.h>
|
||||||
@@ -164,4 +165,12 @@
|
|||||||
i2s = <&i2s0>;
|
i2s = <&i2s0>;
|
||||||
channels = <1>;
|
channels = <1>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
usbdevice0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice";
|
||||||
|
|
||||||
|
usbdevicemsc0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice-msc";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ hardware.flashSize=16MB
|
|||||||
hardware.spiRam=true
|
hardware.spiRam=true
|
||||||
hardware.spiRamMode=QUAD
|
hardware.spiRamMode=QUAD
|
||||||
hardware.spiRamSpeed=120M
|
hardware.spiRamSpeed=120M
|
||||||
hardware.tinyUsb=true
|
hardware.tinyUsbMsc=true
|
||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <tactility/bindings/esp32_i2s.h>
|
#include <tactility/bindings/esp32_i2s.h>
|
||||||
#include <tactility/bindings/esp32_spi.h>
|
#include <tactility/bindings/esp32_spi.h>
|
||||||
#include <tactility/bindings/esp32_uart.h>
|
#include <tactility/bindings/esp32_uart.h>
|
||||||
|
#include <tactility/bindings/esp32_usbdevice.h>
|
||||||
#include <tactility/bindings/gpio_hog.h>
|
#include <tactility/bindings/gpio_hog.h>
|
||||||
#include <bindings/aw88298.h>
|
#include <bindings/aw88298.h>
|
||||||
#include <bindings/aw9523b.h>
|
#include <bindings/aw9523b.h>
|
||||||
@@ -205,4 +206,12 @@
|
|||||||
backlight = <&display_backlight>;
|
backlight = <&display_backlight>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
usbdevice0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice";
|
||||||
|
|
||||||
|
usbdevicemsc0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice-msc";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ hardware.spiRam=true
|
|||||||
hardware.spiRamMode=OPI
|
hardware.spiRamMode=OPI
|
||||||
hardware.spiRamSpeed=80M
|
hardware.spiRamSpeed=80M
|
||||||
hardware.esptoolFlashFreq=80M
|
hardware.esptoolFlashFreq=80M
|
||||||
hardware.tinyUsb=true
|
hardware.tinyUsbMsc=true
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
storage.userDataLocation=SD
|
storage.userDataLocation=SD
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <tactility/bindings/esp32_spi.h>
|
#include <tactility/bindings/esp32_spi.h>
|
||||||
#include <tactility/bindings/esp32_adc_oneshot.h>
|
#include <tactility/bindings/esp32_adc_oneshot.h>
|
||||||
#include <tactility/bindings/esp32_pwm_ledc.h>
|
#include <tactility/bindings/esp32_pwm_ledc.h>
|
||||||
|
#include <tactility/bindings/esp32_usbdevice.h>
|
||||||
#include <tactility/bindings/battery_sense.h>
|
#include <tactility/bindings/battery_sense.h>
|
||||||
#include <bindings/bmi270.h>
|
#include <bindings/bmi270.h>
|
||||||
#include <bindings/bm8563.h>
|
#include <bindings/bm8563.h>
|
||||||
@@ -114,4 +115,12 @@
|
|||||||
// correct orientation.
|
// correct orientation.
|
||||||
rotation = <EPD_ROT_INVERTED_PORTRAIT>;
|
rotation = <EPD_ROT_INVERTED_PORTRAIT>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
usbdevice0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice";
|
||||||
|
|
||||||
|
usbdevicemsc0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice-msc";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ hardware.flashSize=16MB
|
|||||||
hardware.spiRam=true
|
hardware.spiRam=true
|
||||||
hardware.spiRamMode=QUAD
|
hardware.spiRamMode=QUAD
|
||||||
hardware.spiRamSpeed=120M
|
hardware.spiRamSpeed=120M
|
||||||
hardware.tinyUsb=true
|
hardware.tinyUsbMsc=true
|
||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include <tactility/bindings/esp32_sdspi.h>
|
#include <tactility/bindings/esp32_sdspi.h>
|
||||||
#include <tactility/bindings/esp32_spi.h>
|
#include <tactility/bindings/esp32_spi.h>
|
||||||
#include <tactility/bindings/esp32_uart.h>
|
#include <tactility/bindings/esp32_uart.h>
|
||||||
|
#include <tactility/bindings/esp32_usbdevice.h>
|
||||||
#include <tactility/bindings/gpio_hog.h>
|
#include <tactility/bindings/gpio_hog.h>
|
||||||
#include <bindings/aw88298.h>
|
#include <bindings/aw88298.h>
|
||||||
#include <bindings/aw9523b.h>
|
#include <bindings/aw9523b.h>
|
||||||
@@ -238,4 +239,12 @@
|
|||||||
frequency-khz = <20000>;
|
frequency-khz = <20000>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
usbdevice0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice";
|
||||||
|
|
||||||
|
usbdevicemsc0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice-msc";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ hardware.spiRam=true
|
|||||||
hardware.spiRamMode=OCT
|
hardware.spiRamMode=OCT
|
||||||
hardware.spiRamSpeed=80M
|
hardware.spiRamSpeed=80M
|
||||||
hardware.esptoolFlashFreq=80M
|
hardware.esptoolFlashFreq=80M
|
||||||
hardware.tinyUsb=true
|
hardware.tinyUsbMsc=true
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
storage.userDataLocation=Internal
|
storage.userDataLocation=Internal
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <tactility/bindings/esp32_i2s.h>
|
#include <tactility/bindings/esp32_i2s.h>
|
||||||
#include <tactility/bindings/esp32_spi.h>
|
#include <tactility/bindings/esp32_spi.h>
|
||||||
#include <tactility/bindings/esp32_uart.h>
|
#include <tactility/bindings/esp32_uart.h>
|
||||||
|
#include <tactility/bindings/esp32_usbdevice.h>
|
||||||
#include <bindings/bmi270.h>
|
#include <bindings/bmi270.h>
|
||||||
#include <bindings/m5pm1.h>
|
#include <bindings/m5pm1.h>
|
||||||
#include <bindings/es8311.h>
|
#include <bindings/es8311.h>
|
||||||
@@ -131,4 +132,12 @@
|
|||||||
pin-tx = <&gpio0 9 GPIO_FLAG_NONE>;
|
pin-tx = <&gpio0 9 GPIO_FLAG_NONE>;
|
||||||
pin-rx = <&gpio0 10 GPIO_FLAG_NONE>;
|
pin-rx = <&gpio0 10 GPIO_FLAG_NONE>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
usbdevice0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice";
|
||||||
|
|
||||||
|
usbdevicemsc0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice-msc";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -96,10 +96,10 @@ static constexpr HidMapping KEY_MATRIX_HID_BASE[70] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static constexpr HidMapping KEY_MATRIX_HID_SYM[70] = {
|
static constexpr HidMapping KEY_MATRIX_HID_SYM[70] = {
|
||||||
// Row 0: identical to base
|
// Row 0: Esc F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 Del
|
||||||
{0x29, 0x00}, {0x1E, 0x00}, {0x1F, 0x00}, {0x20, 0x00}, {0x21, 0x00}, {0x22, 0x00},
|
{0x29, 0x00}, {0x3A, 0x00}, {0x3B, 0x00}, {0x3C, 0x00}, {0x3D, 0x00}, {0x3E, 0x00},
|
||||||
{0x23, 0x00}, {0x24, 0x00}, {0x25, 0x00}, {0x26, 0x00}, {0x27, 0x00}, {0x2D, 0x00},
|
{0x3F, 0x00}, {0x40, 0x00}, {0x41, 0x00}, {0x42, 0x00}, {0x43, 0x00}, {0x44, 0x00},
|
||||||
{0x2E, 0x02}, {0x4C, 0x00},
|
{0x45, 0x00}, {0x4C, 0x00},
|
||||||
// Row 1: Sym deltas: ` → ~, ! → ?, * → /, ( → <, ) → >, [ → {, ] → }, backslash → |
|
// Row 1: Sym deltas: ` → ~, ! → ?, * → /, ( → <, ) → >, [ → {, ] → }, backslash → |
|
||||||
{0x35, 0x02}, {0x38, 0x02}, {0x1F, 0x02}, {0x20, 0x02}, {0x21, 0x02}, {0x22, 0x02},
|
{0x35, 0x02}, {0x38, 0x02}, {0x1F, 0x02}, {0x20, 0x02}, {0x21, 0x02}, {0x22, 0x02},
|
||||||
{0x23, 0x02}, {0x24, 0x02}, {0x38, 0x00}, {0x36, 0x02}, {0x37, 0x02}, {0x2F, 0x02},
|
{0x23, 0x02}, {0x24, 0x02}, {0x38, 0x00}, {0x36, 0x02}, {0x37, 0x02}, {0x2F, 0x02},
|
||||||
@@ -148,6 +148,10 @@ static uint32_t tab5_translate_key(uint8_t keycode, uint8_t modifier, bool ctrl)
|
|||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// F1-F12 (Sym layer over the number row) have no LVGL/ASCII representation - callers that
|
||||||
|
// want them read KeyboardKeyData::hid_keycode instead (see drain_events()), which is
|
||||||
|
// populated for every key regardless of whether `key` itself has a meaningful value.
|
||||||
|
|
||||||
// Letters a–z / A–Z
|
// Letters a–z / A–Z
|
||||||
if (keycode >= 0x04U && keycode <= 0x1DU) {
|
if (keycode >= 0x04U && keycode <= 0x1DU) {
|
||||||
uint32_t c = static_cast<uint32_t>('a' + (keycode - 0x04U));
|
uint32_t c = static_cast<uint32_t>('a' + (keycode - 0x04U));
|
||||||
@@ -191,6 +195,8 @@ struct Tab5KeyEvent {
|
|||||||
uint32_t key;
|
uint32_t key;
|
||||||
bool ctrl;
|
bool ctrl;
|
||||||
bool alt;
|
bool alt;
|
||||||
|
uint8_t hid_keycode;
|
||||||
|
uint8_t hid_modifier;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Tab5KeyboardInternal {
|
struct Tab5KeyboardInternal {
|
||||||
@@ -368,36 +374,40 @@ static void drain_events(Device* device, Tab5KeyboardInternal* internal) {
|
|||||||
? KEY_MATRIX_HID_SYM[row * 14U + col]
|
? KEY_MATRIX_HID_SYM[row * 14U + col]
|
||||||
: KEY_MATRIX_HID_BASE[row * 14U + col];
|
: KEY_MATRIX_HID_BASE[row * 14U + col];
|
||||||
if (m.keycode != 0U) {
|
if (m.keycode != 0U) {
|
||||||
const uint8_t modifier = static_cast<uint8_t>(m.modifier | (aa_active ? 0x02U : 0U));
|
uint8_t modifier = static_cast<uint8_t>(m.modifier | (aa_active ? 0x02U : 0U));
|
||||||
|
if (internal->ctrl_held) modifier |= 0x01U; // HID LeftCtrl
|
||||||
|
if (internal->alt_held) modifier |= 0x04U; // HID LeftAlt
|
||||||
const uint32_t lv_key = tab5_translate_key(m.keycode, modifier, internal->ctrl_held);
|
const uint32_t lv_key = tab5_translate_key(m.keycode, modifier, internal->ctrl_held);
|
||||||
if (lv_key != 0U) {
|
// Queue whenever there's a HID keycode, even if this key has no LVGL/ASCII
|
||||||
if (pressed) {
|
// representation (e.g. F1-F12) - lv_key stays 0 for those, callers that only
|
||||||
// A real key was pressed — this hold is a chord, not a tap
|
// care about LVGL/ASCII text ignore a 0 key the same way they always have.
|
||||||
internal->aa_tapped = false;
|
if (pressed) {
|
||||||
// Note: ESC used to stop the foreground app directly (tt::app::stop()) in
|
// A real key was pressed — this hold is a chord, not a tap
|
||||||
// the deprecated-HAL version - that's an app-layer concern the driver has
|
internal->aa_tapped = false;
|
||||||
// no business reaching into, so ESC is now just queued as a normal key
|
// Note: ESC used to stop the foreground app directly (tt::app::stop()) in
|
||||||
// like everything else (LVGL/app code already handles ESC via focus/group
|
// the deprecated-HAL version - that's an app-layer concern the driver has
|
||||||
// navigation the same way a dedicated ESC key on any other keyboard would).
|
// no business reaching into, so ESC is now just queued as a normal key
|
||||||
const Tab5KeyEvent event = { lv_key, internal->ctrl_held, internal->alt_held };
|
// like everything else (LVGL/app code already handles ESC via focus/group
|
||||||
xQueueSend(internal->queue, &event, 0);
|
// navigation the same way a dedicated ESC key on any other keyboard would).
|
||||||
// Arm software repeat tracking by row/col to survive modifier changes
|
const Tab5KeyEvent event = { lv_key, internal->ctrl_held, internal->alt_held,
|
||||||
const uint32_t now = now_ms();
|
m.keycode, modifier };
|
||||||
internal->repeat_event = event;
|
xQueueSend(internal->queue, &event, 0);
|
||||||
internal->repeat_row = row;
|
// Arm software repeat tracking by row/col to survive modifier changes
|
||||||
internal->repeat_col = col;
|
const uint32_t now = now_ms();
|
||||||
internal->repeat_start_ms = now;
|
internal->repeat_event = event;
|
||||||
internal->repeat_last_ms = 0;
|
internal->repeat_row = row;
|
||||||
// Consume sticky Aa after one keypress
|
internal->repeat_col = col;
|
||||||
if (internal->aa_sticky) {
|
internal->repeat_start_ms = now;
|
||||||
internal->aa_sticky = false;
|
internal->repeat_last_ms = 0;
|
||||||
internal->aa_held = false;
|
// Consume sticky Aa after one keypress
|
||||||
update_leds(device, internal);
|
if (internal->aa_sticky) {
|
||||||
}
|
internal->aa_sticky = false;
|
||||||
} else if (row == internal->repeat_row && col == internal->repeat_col) {
|
internal->aa_held = false;
|
||||||
// Match release by position, not translated value — survives sticky Aa clear
|
update_leds(device, internal);
|
||||||
internal->repeat_event.key = 0;
|
|
||||||
}
|
}
|
||||||
|
} else if (row == internal->repeat_row && col == internal->repeat_col) {
|
||||||
|
// Match release by position, not translated value — survives sticky Aa clear
|
||||||
|
internal->repeat_event.key = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -560,12 +570,16 @@ static error_t tab5_keyboard_read_key(Device* device, KeyboardKeyData* data) {
|
|||||||
data->continue_reading = uxQueueMessagesWaiting(internal->queue) > 0;
|
data->continue_reading = uxQueueMessagesWaiting(internal->queue) > 0;
|
||||||
data->ctrl = event.ctrl;
|
data->ctrl = event.ctrl;
|
||||||
data->alt = event.alt;
|
data->alt = event.alt;
|
||||||
|
data->hid_keycode = event.hid_keycode;
|
||||||
|
data->hid_modifier = event.hid_modifier;
|
||||||
} else {
|
} else {
|
||||||
data->key = 0;
|
data->key = 0;
|
||||||
data->pressed = false;
|
data->pressed = false;
|
||||||
data->continue_reading = false;
|
data->continue_reading = false;
|
||||||
data->ctrl = false;
|
data->ctrl = false;
|
||||||
data->alt = false;
|
data->alt = false;
|
||||||
|
data->hid_keycode = 0;
|
||||||
|
data->hid_modifier = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ERROR_NONE;
|
return ERROR_NONE;
|
||||||
|
|||||||
@@ -11,7 +11,10 @@ hardware.spiRamSpeed=200M
|
|||||||
hardware.esptoolFlashFreq=80M
|
hardware.esptoolFlashFreq=80M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
hardware.usbHostEnabled=true
|
hardware.usbHostEnabled=true
|
||||||
hardware.tinyUsb=true
|
hardware.tinyUsbMsc=true
|
||||||
|
hardware.tinyUsbHid=true
|
||||||
|
hardware.tinyUsbMidi=true
|
||||||
|
hardware.tinyUsbCdc=true
|
||||||
|
|
||||||
storage.userDataLocation=SD
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <tactility/bindings/esp32_pwm_ledc.h>
|
#include <tactility/bindings/esp32_pwm_ledc.h>
|
||||||
#include <tactility/bindings/esp32_sdmmc.h>
|
#include <tactility/bindings/esp32_sdmmc.h>
|
||||||
#include <tactility/bindings/esp32_uart.h>
|
#include <tactility/bindings/esp32_uart.h>
|
||||||
|
#include <tactility/bindings/esp32_usbdevice.h>
|
||||||
#include <tactility/bindings/esp32_usbhost.h>
|
#include <tactility/bindings/esp32_usbhost.h>
|
||||||
#include <tactility/bindings/esp32_wifi.h>
|
#include <tactility/bindings/esp32_wifi.h>
|
||||||
#include <tactility/bindings/gpio_hog.h>
|
#include <tactility/bindings/gpio_hog.h>
|
||||||
@@ -210,4 +211,24 @@
|
|||||||
compatible = "espressif,esp32-usbhost-msc";
|
compatible = "espressif,esp32-usbhost-msc";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
usbdevice0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice";
|
||||||
|
|
||||||
|
usbdevicehid0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice-hid";
|
||||||
|
};
|
||||||
|
|
||||||
|
usbdevicemsc0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice-msc";
|
||||||
|
};
|
||||||
|
|
||||||
|
usbdevicemidi0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice-midi";
|
||||||
|
};
|
||||||
|
|
||||||
|
usbdevicecdc0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice-cdc";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ hardware.flashSize=16MB
|
|||||||
hardware.spiRam=true
|
hardware.spiRam=true
|
||||||
hardware.spiRamMode=QUAD
|
hardware.spiRamMode=QUAD
|
||||||
hardware.spiRamSpeed=120M
|
hardware.spiRamSpeed=120M
|
||||||
hardware.tinyUsb=true
|
hardware.tinyUsbMsc=true
|
||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <tactility/bindings/esp32_sdmmc.h>
|
#include <tactility/bindings/esp32_sdmmc.h>
|
||||||
#include <tactility/bindings/esp32_spi.h>
|
#include <tactility/bindings/esp32_spi.h>
|
||||||
#include <tactility/bindings/esp32_uart.h>
|
#include <tactility/bindings/esp32_uart.h>
|
||||||
|
#include <tactility/bindings/esp32_usbdevice.h>
|
||||||
#include <tactility/bindings/esp32_pwm_ledc.h>
|
#include <tactility/bindings/esp32_pwm_ledc.h>
|
||||||
#include <tactility/bindings/pwm_backlight.h>
|
#include <tactility/bindings/pwm_backlight.h>
|
||||||
|
|
||||||
@@ -103,4 +104,12 @@
|
|||||||
pin-tx = <&gpio0 43 GPIO_FLAG_NONE>;
|
pin-tx = <&gpio0 43 GPIO_FLAG_NONE>;
|
||||||
pin-rx = <&gpio0 44 GPIO_FLAG_NONE>;
|
pin-rx = <&gpio0 44 GPIO_FLAG_NONE>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
usbdevice0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice";
|
||||||
|
|
||||||
|
usbdevicemsc0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice-msc";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ hardware.flashSize=16MB
|
|||||||
hardware.spiRam=true
|
hardware.spiRam=true
|
||||||
hardware.spiRamMode=OCT
|
hardware.spiRamMode=OCT
|
||||||
hardware.spiRamSpeed=120M
|
hardware.spiRamSpeed=120M
|
||||||
hardware.tinyUsb=true
|
hardware.tinyUsbMsc=true
|
||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <bindings/qmi8658.h>
|
#include <bindings/qmi8658.h>
|
||||||
#include <tactility/bindings/esp32_sdspi.h>
|
#include <tactility/bindings/esp32_sdspi.h>
|
||||||
#include <tactility/bindings/esp32_pwm_ledc.h>
|
#include <tactility/bindings/esp32_pwm_ledc.h>
|
||||||
|
#include <tactility/bindings/esp32_usbdevice.h>
|
||||||
#include <tactility/bindings/pwm_backlight.h>
|
#include <tactility/bindings/pwm_backlight.h>
|
||||||
#include <bindings/st7789.h>
|
#include <bindings/st7789.h>
|
||||||
|
|
||||||
@@ -90,4 +91,12 @@
|
|||||||
frequency-khz = <20000>;
|
frequency-khz = <20000>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
usbdevice0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice";
|
||||||
|
|
||||||
|
usbdevicemsc0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice-msc";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ hardware.flashSize=16MB
|
|||||||
hardware.spiRam=true
|
hardware.spiRam=true
|
||||||
hardware.spiRamMode=QUAD
|
hardware.spiRamMode=QUAD
|
||||||
hardware.spiRamSpeed=120M
|
hardware.spiRamSpeed=120M
|
||||||
hardware.tinyUsb=true
|
hardware.tinyUsbMsc=true
|
||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <bindings/qmi8658.h>
|
#include <bindings/qmi8658.h>
|
||||||
#include <tactility/bindings/esp32_sdspi.h>
|
#include <tactility/bindings/esp32_sdspi.h>
|
||||||
#include <tactility/bindings/esp32_pwm_ledc.h>
|
#include <tactility/bindings/esp32_pwm_ledc.h>
|
||||||
|
#include <tactility/bindings/esp32_usbdevice.h>
|
||||||
#include <tactility/bindings/pwm_backlight.h>
|
#include <tactility/bindings/pwm_backlight.h>
|
||||||
#include <bindings/cst816t.h>
|
#include <bindings/cst816t.h>
|
||||||
#include <bindings/gc9a01.h>
|
#include <bindings/gc9a01.h>
|
||||||
@@ -93,4 +94,12 @@
|
|||||||
backlight = <&display_backlight>;
|
backlight = <&display_backlight>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
usbdevice0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice";
|
||||||
|
|
||||||
|
usbdevicemsc0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice-msc";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ hardware.flashSize=16MB
|
|||||||
hardware.spiRam=true
|
hardware.spiRam=true
|
||||||
hardware.spiRamMode=OCT
|
hardware.spiRamMode=OCT
|
||||||
hardware.spiRamSpeed=120M
|
hardware.spiRamSpeed=120M
|
||||||
hardware.tinyUsb=true
|
hardware.tinyUsbMsc=true
|
||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <tactility/bindings/esp32_spi.h>
|
#include <tactility/bindings/esp32_spi.h>
|
||||||
#include <tactility/bindings/esp32_sdspi.h>
|
#include <tactility/bindings/esp32_sdspi.h>
|
||||||
#include <tactility/bindings/esp32_pwm_ledc.h>
|
#include <tactility/bindings/esp32_pwm_ledc.h>
|
||||||
|
#include <tactility/bindings/esp32_usbdevice.h>
|
||||||
#include <tactility/bindings/pwm_backlight.h>
|
#include <tactility/bindings/pwm_backlight.h>
|
||||||
#include <bindings/axs5106.h>
|
#include <bindings/axs5106.h>
|
||||||
#include <bindings/button_control.h>
|
#include <bindings/button_control.h>
|
||||||
@@ -104,4 +105,12 @@
|
|||||||
compatible = "tactility,button-control";
|
compatible = "tactility,button-control";
|
||||||
pin-primary = <&gpio0 0 GPIO_FLAG_NONE>;
|
pin-primary = <&gpio0 0 GPIO_FLAG_NONE>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
usbdevice0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice";
|
||||||
|
|
||||||
|
usbdevicemsc0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice-msc";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ hardware.flashSize=8MB
|
|||||||
hardware.spiRam=true
|
hardware.spiRam=true
|
||||||
hardware.spiRamMode=OCT
|
hardware.spiRamMode=OCT
|
||||||
hardware.spiRamSpeed=120M
|
hardware.spiRamSpeed=120M
|
||||||
hardware.tinyUsb=true
|
hardware.tinyUsbMsc=true
|
||||||
hardware.esptoolFlashFreq=120M
|
hardware.esptoolFlashFreq=120M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <tactility/bindings/esp32_spi.h>
|
#include <tactility/bindings/esp32_spi.h>
|
||||||
#include <tactility/bindings/esp32_uart.h>
|
#include <tactility/bindings/esp32_uart.h>
|
||||||
#include <tactility/bindings/esp32_sdspi.h>
|
#include <tactility/bindings/esp32_sdspi.h>
|
||||||
|
#include <tactility/bindings/esp32_usbdevice.h>
|
||||||
#include <tactility/bindings/gpio_hog.h>
|
#include <tactility/bindings/gpio_hog.h>
|
||||||
#include <bindings/ch422g.h>
|
#include <bindings/ch422g.h>
|
||||||
#include <bindings/gt911.h>
|
#include <bindings/gt911.h>
|
||||||
@@ -126,4 +127,12 @@
|
|||||||
pin-tx = <&gpio0 43 GPIO_FLAG_NONE>;
|
pin-tx = <&gpio0 43 GPIO_FLAG_NONE>;
|
||||||
pin-rx = <&gpio0 44 GPIO_FLAG_NONE>;
|
pin-rx = <&gpio0 44 GPIO_FLAG_NONE>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
usbdevice0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice";
|
||||||
|
|
||||||
|
usbdevicemsc0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice-msc";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ hardware.flashSize=16MB
|
|||||||
hardware.spiRam=true
|
hardware.spiRam=true
|
||||||
hardware.spiRamMode=QUAD
|
hardware.spiRamMode=QUAD
|
||||||
hardware.spiRamSpeed=80M
|
hardware.spiRamSpeed=80M
|
||||||
hardware.tinyUsb=true
|
hardware.tinyUsbMsc=true
|
||||||
hardware.esptoolFlashFreq=80M
|
hardware.esptoolFlashFreq=80M
|
||||||
hardware.bluetooth=true
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <tactility/bindings/esp32_sdspi.h>
|
#include <tactility/bindings/esp32_sdspi.h>
|
||||||
#include <tactility/bindings/esp32_i8080.h>
|
#include <tactility/bindings/esp32_i8080.h>
|
||||||
#include <tactility/bindings/esp32_pwm_ledc.h>
|
#include <tactility/bindings/esp32_pwm_ledc.h>
|
||||||
|
#include <tactility/bindings/esp32_usbdevice.h>
|
||||||
#include <tactility/bindings/pwm_backlight.h>
|
#include <tactility/bindings/pwm_backlight.h>
|
||||||
#include <bindings/ft6x36.h>
|
#include <bindings/ft6x36.h>
|
||||||
#include <bindings/st7796_i8080.h>
|
#include <bindings/st7796_i8080.h>
|
||||||
@@ -105,4 +106,12 @@
|
|||||||
frequency-khz = <20000>;
|
frequency-khz = <20000>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
usbdevice0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice";
|
||||||
|
|
||||||
|
usbdevicemsc0 {
|
||||||
|
compatible = "espressif,esp32-usbdevice-msc";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,6 +13,16 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum simultaneous touch points lvgl_pointer_add() will create indev \slots for.
|
||||||
|
*
|
||||||
|
* \slots Each lv_indev_t carries one point per read, so per-finger widget interaction needs
|
||||||
|
* one indev per finger, pooled over a single device read - see pointer.cpp. Distinct from
|
||||||
|
* LVGL's own lv_indev_touch_data_t gesture path, which reports all points to one indev for
|
||||||
|
* gestures rather than per-widget press handling; unused here.
|
||||||
|
*/
|
||||||
|
#define LVGL_POINTER_MAX_SLOTS 5
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Linear per-axis calibration range for raw pointer coordinates.
|
* @brief Linear per-axis calibration range for raw pointer coordinates.
|
||||||
*
|
*
|
||||||
@@ -31,7 +41,8 @@ struct LvglPointerCalibration {
|
|||||||
/**
|
/**
|
||||||
* @brief Sets (or clears, when calibration is NULL) the calibration applied to raw coordinates
|
* @brief Sets (or clears, when calibration is NULL) the calibration applied to raw coordinates
|
||||||
* read from the device before they are written into LVGL indev data, on an indev previously
|
* read from the device before they are written into LVGL indev data, on an indev previously
|
||||||
* created with lvgl_pointer_add().
|
* created with lvgl_pointer_add(). Applies to every slot of the same physical device (one panel,
|
||||||
|
* one calibration) - pass any one of lvgl_pointer_add()'s out_indevs, it doesn't matter which.
|
||||||
*
|
*
|
||||||
* @warning Caller must hold the LVGL lock (see lvgl_lock() in lvgl_module.h).
|
* @warning Caller must hold the LVGL lock (see lvgl_lock() in lvgl_module.h).
|
||||||
*
|
*
|
||||||
@@ -44,19 +55,20 @@ struct LvglPointerCalibration {
|
|||||||
error_t lvgl_pointer_set_calibration(lv_indev_t* indev, const struct LvglPointerCalibration* calibration);
|
error_t lvgl_pointer_set_calibration(lv_indev_t* indev, const struct LvglPointerCalibration* calibration);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Retrieves the calibration currently active on indev, if any.
|
* @brief Retrieves the calibration currently active on indev's device, if any.
|
||||||
* @warning Caller must hold the LVGL lock.
|
* @warning Caller must hold the LVGL lock.
|
||||||
* @return true when a calibration is currently set on indev (out_calibration is filled), false otherwise
|
* @return true when a calibration is currently set on indev (out_calibration is filled), false otherwise
|
||||||
*/
|
*/
|
||||||
bool lvgl_pointer_get_calibration(lv_indev_t* indev, struct LvglPointerCalibration* out_calibration);
|
bool lvgl_pointer_get_calibration(lv_indev_t* indev, struct LvglPointerCalibration* out_calibration);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns the first indev created by lvgl_pointer_add() that hasn't been removed yet.
|
* @brief Returns the first indev created by lvgl_pointer_add() that hasn't been removed yet
|
||||||
|
* (specifically, slot 0 of the first pool created).
|
||||||
*
|
*
|
||||||
* Unlike iterating LVGL's own indev list, this only ever returns an indev created by
|
* Unlike iterating LVGL's own indev list, this only ever returns an indev created by
|
||||||
* lvgl_pointer_add() — safe to pass to lvgl_pointer_set_calibration()/lvgl_pointer_get_calibration()
|
* lvgl_pointer_add() — safe to pass to lvgl_pointer_set_calibration()/lvgl_pointer_get_calibration()
|
||||||
* without risking a foreign indev (e.g. one registered by the deprecated HAL layer) whose driver
|
* without risking a foreign indev (e.g. one registered by the deprecated HAL layer) whose driver
|
||||||
* data isn't a struct LvglPointerCtx*.
|
* data isn't this module's own context type.
|
||||||
*
|
*
|
||||||
* @warning Caller must hold the LVGL lock.
|
* @warning Caller must hold the LVGL lock.
|
||||||
* @return the indev, or NULL if none is currently registered.
|
* @return the indev, or NULL if none is currently registered.
|
||||||
@@ -64,23 +76,47 @@ bool lvgl_pointer_get_calibration(lv_indev_t* indev, struct LvglPointerCalibrati
|
|||||||
lv_indev_t* lvgl_pointer_get_default(void);
|
lv_indev_t* lvgl_pointer_get_default(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Creates an lv_indev_t bound to the given POINTER_TYPE device and registers a read callback
|
* @brief Creates up to max_touch_points lv_indev_t instances bound to the given POINTER_TYPE
|
||||||
* that polls the device through its PointerApi.
|
* device, each independently tracking one simultaneous touch point ("slot"). All slots share one
|
||||||
|
* underlying read of the device per LVGL polling round (see pointer.cpp) — this does not multiply
|
||||||
|
* bus traffic by max_touch_points.
|
||||||
|
*
|
||||||
|
* Each returned indev behaves like an ordinary single-touch pointer indev to LVGL: normal widget
|
||||||
|
* press/click/drag interaction (LV_EVENT_PRESSED, dragging, etc.) works independently per-indev
|
||||||
|
* out of the box. Event callbacks that need to know which finger fired them should use
|
||||||
|
* lv_event_get_indev() (never lv_indev_active()/lvgl_pointer_get_default(), which only ever
|
||||||
|
* resolve to whichever indev happens to be currently processing) and, if per-finger app state is
|
||||||
|
* needed, lvgl_pointer_get_slot_index() to index into it.
|
||||||
*
|
*
|
||||||
* @warning Caller must hold the LVGL lock (see lvgl_lock() in lvgl_module.h) — call this from
|
* @warning Caller must hold the LVGL lock (see lvgl_lock() in lvgl_module.h) — call this from
|
||||||
* LvglModuleConfig.on_start, or after calling lvgl_lock() explicitly.
|
* LvglModuleConfig.on_start, or after calling lvgl_lock() explicitly.
|
||||||
|
* @warning Do not call lv_indev_enable(false) on individual slots. The pool's shared bus read
|
||||||
|
* only runs once every slot_count read callbacks (round_pos); disabling one slot skips its
|
||||||
|
* callback and desyncs that cadence, staling the remaining active slots' data.
|
||||||
*
|
*
|
||||||
* @param[in] device a device of type POINTER_TYPE
|
* @param[in] device a device of type POINTER_TYPE
|
||||||
* @param[in] display the display this indev should be associated with, or NULL to leave it unset
|
* @param[in] display the display this indev should be associated with, or NULL to leave it unset
|
||||||
* @param[out] out_indev the created indev, valid only when ERROR_NONE is returned
|
* @param[in] max_touch_points how many simultaneous touch slots to create (1 for classic
|
||||||
|
* single-touch behavior; clamped to [1, LVGL_POINTER_MAX_SLOTS])
|
||||||
|
* @param[out] out_indevs array of at least max_touch_points entries; filled with the created
|
||||||
|
* indevs on success, valid only when ERROR_NONE is returned
|
||||||
* @retval ERROR_NONE on success
|
* @retval ERROR_NONE on success
|
||||||
* @retval ERROR_INVALID_ARGUMENT if device or out_indev is NULL, or device is not of type POINTER_TYPE
|
* @retval ERROR_INVALID_ARGUMENT if device or out_indevs is NULL, or device is not of type POINTER_TYPE
|
||||||
* @retval ERROR_OUT_OF_MEMORY if allocation failed
|
* @retval ERROR_OUT_OF_MEMORY if allocation failed
|
||||||
*/
|
*/
|
||||||
error_t lvgl_pointer_add(struct Device* device, lv_display_t* display, lv_indev_t** out_indev);
|
error_t lvgl_pointer_add(struct Device* device, lv_display_t* display, uint8_t max_touch_points, lv_indev_t** out_indevs);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Removes an indev previously created with lvgl_pointer_add().
|
* @brief Returns which slot index (0-based) of its pool the given indev is, or -1 if indev wasn't
|
||||||
|
* created by lvgl_pointer_add() (or is NULL). Useful for indexing small per-finger app state
|
||||||
|
* arrays from an event callback's lv_event_get_indev() result.
|
||||||
|
* @warning Caller must hold the LVGL lock.
|
||||||
|
*/
|
||||||
|
int8_t lvgl_pointer_get_slot_index(lv_indev_t* indev);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Removes every slot indev in the same pool as the given indev (i.e. all indevs returned
|
||||||
|
* together by one lvgl_pointer_add() call).
|
||||||
* @warning Caller must hold the LVGL lock.
|
* @warning Caller must hold the LVGL lock.
|
||||||
*/
|
*/
|
||||||
void lvgl_pointer_remove(lv_indev_t* indev);
|
void lvgl_pointer_remove(lv_indev_t* indev);
|
||||||
|
|||||||
@@ -68,6 +68,16 @@ lv_obj_t* lvgl_toolbar_add_text_button_action(lv_obj_t* obj, const char* text, l
|
|||||||
*/
|
*/
|
||||||
lv_obj_t* lvgl_toolbar_add_switch_action(lv_obj_t* obj);
|
lv_obj_t* lvgl_toolbar_add_switch_action(lv_obj_t* obj);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create and add a dropdown to the toolbar actions.
|
||||||
|
* @param[in] obj the toolbar instance
|
||||||
|
* @param[in] options newline-separated dropdown options
|
||||||
|
* @param[in] width the dropdown's width in pixels, or 0 to leave LVGL's default dropdown width
|
||||||
|
* @param[in] text fixed text to show instead of the selected option or NULL to show the selected option as usual
|
||||||
|
* @return an instance created by lv_dropdown_create()
|
||||||
|
*/
|
||||||
|
lv_obj_t* lvgl_toolbar_add_dropdown_action(lv_obj_t* obj, const char* options, lv_coord_t width, const char* text);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create and add a spinner to the toolbar actions.
|
* Create and add a spinner to the toolbar actions.
|
||||||
* @param[in] obj the toolbar instance
|
* @param[in] obj the toolbar instance
|
||||||
|
|||||||
@@ -97,15 +97,21 @@ void lvgl_devices_attach() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
lv_indev_t* lvgl_pointer_device;
|
// Each physical touch device gets up to LVGL_POINTER_MAX_SLOTS independent indevs (a
|
||||||
if (lvgl_pointer_add(kernel_pointer_device, lvgl_display, &lvgl_pointer_device) == ERROR_NONE) {
|
// "pool", see lvgl_pointer_add()) so LVGL can track that many simultaneous touch points -
|
||||||
LOG_I(TAG, "Bound %s to LVGL", kernel_pointer_device->name);
|
// touch controllers report multiple points but expose no per-point hardware max, so this
|
||||||
|
// is a fixed ceiling rather than a per-device query.
|
||||||
|
lv_indev_t* lvgl_pointer_slots[LVGL_POINTER_MAX_SLOTS];
|
||||||
|
if (lvgl_pointer_add(kernel_pointer_device, lvgl_display, LVGL_POINTER_MAX_SLOTS, lvgl_pointer_slots) == ERROR_NONE) {
|
||||||
|
LOG_I(TAG, "Bound %s to LVGL (%d touch slots)", kernel_pointer_device->name, (int)LVGL_POINTER_MAX_SLOTS);
|
||||||
// Slow panels cause taps to be missed due to the long update time, prevent that
|
// Slow panels cause taps to be missed due to the long update time, prevent that
|
||||||
if (display_updates_slowly ) {
|
if (display_updates_slowly) {
|
||||||
lv_indev_set_long_press_time(lvgl_pointer_device, 2000);
|
for (uint8_t slot = 0; slot < LVGL_POINTER_MAX_SLOTS; slot++) {
|
||||||
|
lv_indev_set_long_press_time(lvgl_pointer_slots[slot], 2000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LOG_E(TAG, "Failed to bind %s to LVG", kernel_pointer_device->name);
|
LOG_E(TAG, "Failed to bind %s to LVGL", kernel_pointer_device->name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,13 @@
|
|||||||
#include <lvgl/devices/pointer.h>
|
#include <lvgl/devices/pointer.h>
|
||||||
#include <lvgl/devices/device_context.h>
|
#include <lvgl/devices/device_context.h>
|
||||||
|
|
||||||
|
#include <tactility/device.h>
|
||||||
#include <tactility/drivers/pointer.h>
|
#include <tactility/drivers/pointer.h>
|
||||||
|
|
||||||
constexpr auto* TAG = "lvgl_pointer";
|
#include <cstring>
|
||||||
|
#include <new>
|
||||||
|
|
||||||
struct LvglPointerCtx {
|
constexpr auto* TAG = "lvgl_pointer";
|
||||||
bool calibration_enabled;
|
|
||||||
struct LvglPointerCalibration calibration;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Bus reads are expected to complete quickly; bound the wait so a stalled controller can't block the LVGL indev poll.
|
// Bus reads are expected to complete quickly; bound the wait so a stalled controller can't block the LVGL indev poll.
|
||||||
static const TickType_t LVGL_POINTER_READ_TIMEOUT = pdMS_TO_TICKS(10);
|
static const TickType_t LVGL_POINTER_READ_TIMEOUT = pdMS_TO_TICKS(10);
|
||||||
@@ -22,6 +21,61 @@ static lv_indev_t* default_pointer_indev = NULL;
|
|||||||
// Mirrors Tactility/Source/settings/TouchCalibrationSettings.cpp's isValid().
|
// Mirrors Tactility/Source/settings/TouchCalibrationSettings.cpp's isValid().
|
||||||
static const int32_t LVGL_POINTER_CALIBRATION_MIN_RANGE = 20;
|
static const int32_t LVGL_POINTER_CALIBRATION_MIN_RANGE = 20;
|
||||||
|
|
||||||
|
// Caps nearest-neighbor slot tracking (lvgl_pointer_pool_assign) to a fraction of screen width,
|
||||||
|
// so a lifted finger's slot doesn't jump to grab an unrelated new touch elsewhere on screen.
|
||||||
|
// Scaled by resolution, not a flat pixel value, so it stays proportionate on any display size.
|
||||||
|
// Set generously \wide: a too-tight cap misreads a fast scroll's own motion as release+re-press,
|
||||||
|
// firing whatever is under the finger mid-drag.
|
||||||
|
//
|
||||||
|
// \wide False positives (treating one continued drag as two separate touches) are far more
|
||||||
|
// disruptive than false negatives (merging an unrelated same-spot lift+relanding), which
|
||||||
|
// favors erring toward a larger cap.
|
||||||
|
static const int32_t LVGL_POINTER_MAX_TRACK_DIST_FRACTION = 3; // 1/3 of screen width
|
||||||
|
|
||||||
|
// One physical touch device backs LVGL_POINTER_MAX_SLOTS independent lv_indev_t instances (a
|
||||||
|
// "pool"), so LVGL can track that many simultaneous fingers - LVGL v9 has no multi-point indev
|
||||||
|
// concept (confirmed against lv_indev.h/lv_indev.c: lv_indev_data_t carries exactly one
|
||||||
|
// lv_point_t/state pair), so N simultaneous independently-clickable widgets requires N indevs.
|
||||||
|
// Each slot's read callback shares one raw multi-touch read per round rather than each slot
|
||||||
|
// hitting the bus independently - see lvgl_pointer_read_cb().
|
||||||
|
struct LvglPointerPool {
|
||||||
|
struct Device* device;
|
||||||
|
bool calibration_enabled;
|
||||||
|
struct LvglPointerCalibration calibration;
|
||||||
|
|
||||||
|
uint8_t slot_count;
|
||||||
|
lv_indev_t* slot_indev[LVGL_POINTER_MAX_SLOTS];
|
||||||
|
|
||||||
|
// Per-slot "which finger" tracking. A slot with active=false reports RELEASED and is up for
|
||||||
|
// grabs by any unmatched raw point next round.
|
||||||
|
bool slot_active[LVGL_POINTER_MAX_SLOTS];
|
||||||
|
lv_point_t slot_point[LVGL_POINTER_MAX_SLOTS];
|
||||||
|
|
||||||
|
// Shared raw-read cache for this round. Refreshed by whichever slot's read callback runs
|
||||||
|
// first each round (see round_pos); the rest just consume it - one bus transaction per
|
||||||
|
// slot_count read callbacks, not one per slot.
|
||||||
|
uint8_t round_pos;
|
||||||
|
uint16_t raw_x[LVGL_POINTER_MAX_SLOTS];
|
||||||
|
uint16_t raw_y[LVGL_POINTER_MAX_SLOTS];
|
||||||
|
uint8_t raw_count;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Safely narrows an arbitrary lv_indev_t's driver data down to this module's LvglPointerPool,
|
||||||
|
// or NULL if indev is NULL, wasn't created by lvgl_pointer_add() (LvglDeviceContext is shared by
|
||||||
|
// every LVGL indev/display this module creates - pointer, trackball, keyboard - so driver_data
|
||||||
|
// alone doesn't prove it's a pointer pool), or is mid-teardown (context nulled by
|
||||||
|
// lvgl_pointer_remove()/lvgl_pointer_add()'s cleanup path before the wrapper itself is freed).
|
||||||
|
static struct LvglPointerPool* lvgl_pointer_pool_from_indev(lv_indev_t* indev) {
|
||||||
|
if (indev == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
auto* wrapper = (struct LvglDeviceContext*)lv_indev_get_driver_data(indev);
|
||||||
|
if (wrapper == NULL || wrapper->device == NULL || device_get_type(wrapper->device) != &POINTER_TYPE) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return (struct LvglPointerPool*)wrapper->context;
|
||||||
|
}
|
||||||
|
|
||||||
static bool lvgl_pointer_calibration_is_valid(const struct LvglPointerCalibration* calibration) {
|
static bool lvgl_pointer_calibration_is_valid(const struct LvglPointerCalibration* calibration) {
|
||||||
return calibration->x_max > calibration->x_min &&
|
return calibration->x_max > calibration->x_min &&
|
||||||
calibration->y_max > calibration->y_min &&
|
calibration->y_max > calibration->y_min &&
|
||||||
@@ -53,91 +107,193 @@ static void lvgl_pointer_calibration_apply(
|
|||||||
*y = (uint16_t)mapped_y;
|
*y = (uint16_t)mapped_y;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reads the touch controller and applies calibration entirely in the graphics driver's own
|
// Reads all currently-touched points from the device into pool->raw_x/raw_y/raw_count, applying
|
||||||
// native (LV_DISPLAY_ROTATION_0) coordinate space - native_x_max/native_y_max are just the panel's
|
// calibration in the graphics driver's own native (LV_DISPLAY_ROTATION_0) coordinate space -
|
||||||
// fixed pixel dimensions, not a rotation. This function has no notion of LVGL rotation at all:
|
// native_x_max/native_y_max are just the panel's fixed pixel dimensions, not a rotation. This
|
||||||
// calibration corrects the raw sensor's fixed physical mapping, which never changes with on-screen
|
// function has no notion of LVGL rotation at all: calibration corrects the raw sensor's fixed
|
||||||
// orientation, so it doesn't belong anywhere near rotation math.
|
// physical mapping, which never changes with on-screen orientation, so it doesn't belong anywhere
|
||||||
static bool lvgl_pointer_read_calibrated(struct Device* device, struct LvglPointerCtx* ctx, int32_t native_x_max, int32_t native_y_max, uint16_t* x, uint16_t* y) {
|
// near rotation math.
|
||||||
if (pointer_read_data(device, LVGL_POINTER_READ_TIMEOUT) != ERROR_NONE) {
|
static void lvgl_pointer_pool_refresh(struct LvglPointerPool* pool, int32_t native_x_max, int32_t native_y_max) {
|
||||||
return false;
|
pool->raw_count = 0;
|
||||||
|
|
||||||
|
if (pointer_read_data(pool->device, LVGL_POINTER_READ_TIMEOUT) != ERROR_NONE) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t point_count = 0;
|
uint8_t point_count = 0;
|
||||||
if (!pointer_get_touched_points(device, x, y, NULL, &point_count, 1) || point_count == 0) {
|
if (!pointer_get_touched_points(pool->device, pool->raw_x, pool->raw_y, NULL, &point_count, LVGL_POINTER_MAX_SLOTS) || point_count == 0) {
|
||||||
return false;
|
return;
|
||||||
|
}
|
||||||
|
if (point_count > LVGL_POINTER_MAX_SLOTS) point_count = LVGL_POINTER_MAX_SLOTS;
|
||||||
|
|
||||||
|
if (pool->calibration_enabled && native_x_max > 0 && native_y_max > 0) {
|
||||||
|
for (uint8_t i = 0; i < point_count; i++) {
|
||||||
|
lvgl_pointer_calibration_apply(&pool->calibration, native_x_max, native_y_max, &pool->raw_x[i], &pool->raw_y[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctx->calibration_enabled && native_x_max > 0 && native_y_max > 0) {
|
pool->raw_count = point_count;
|
||||||
lvgl_pointer_calibration_apply(&ctx->calibration, native_x_max, native_y_max, x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The actual LVGL indev read callback: wraps lvgl_pointer_read_calibrated() and, only here, applies
|
// Matches this round's raw points onto pool slots by nearest-neighbor to each slot's last known
|
||||||
// the rotation needed to place the (still native-space) point into the currently active LVGL
|
// position, so a slot "follows" the same physical finger across rounds instead of jumping when
|
||||||
// logical space - unconditionally, since native-space coordinates always need this regardless of
|
// the touch controller reports points in a different order (no touch-ID/tracking field exists
|
||||||
// whether calibration is enabled.
|
// anywhere in this stack - see esp_lcd_touch_get_coordinates()/PointerApi.get_touched_points()).
|
||||||
|
// Unmatched raw points (new touches) claim the nearest inactive slot. Slots with no matching
|
||||||
|
// point this round go inactive (RELEASED).
|
||||||
|
static void lvgl_pointer_pool_assign(struct LvglPointerPool* pool, int32_t native_x_max) {
|
||||||
|
// Touch drivers clamp raw coordinates to the panel's configured native resolution regardless
|
||||||
|
// of calibration, so native_x_max is a valid scale reference for the distance cap even when calibration is disabled.
|
||||||
|
// Falls back to a conservative fixed pixel value if the display/resolution isn't available for some reason.
|
||||||
|
const int32_t max_track_dist = native_x_max > 0 ? (native_x_max / LVGL_POINTER_MAX_TRACK_DIST_FRACTION) : 150;
|
||||||
|
const int32_t max_track_dist_sq = max_track_dist * max_track_dist;
|
||||||
|
|
||||||
|
bool raw_claimed[LVGL_POINTER_MAX_SLOTS] = {};
|
||||||
|
// A slot released this round must report RELEASED for at least one round before it can host
|
||||||
|
// a new touch - otherwise pass 2 immediately reassigns it, and LVGL sees a jump instead of a
|
||||||
|
// release-then-press.
|
||||||
|
bool slot_released_now[LVGL_POINTER_MAX_SLOTS] = {};
|
||||||
|
|
||||||
|
// First pass: let already-active slots keep following their nearest raw point, so a held
|
||||||
|
// finger doesn't get reshuffled onto a different slot just because another finger moved.
|
||||||
|
for (uint8_t s = 0; s < pool->slot_count; s++) {
|
||||||
|
if (!pool->slot_active[s]) continue;
|
||||||
|
int32_t best_dist = -1;
|
||||||
|
int8_t best_raw = -1;
|
||||||
|
for (uint8_t r = 0; r < pool->raw_count; r++) {
|
||||||
|
if (raw_claimed[r]) continue;
|
||||||
|
int32_t dx = (int32_t)pool->raw_x[r] - pool->slot_point[s].x;
|
||||||
|
int32_t dy = (int32_t)pool->raw_y[r] - pool->slot_point[s].y;
|
||||||
|
int32_t dist = dx * dx + dy * dy;
|
||||||
|
if (best_raw < 0 || dist < best_dist) {
|
||||||
|
best_dist = dist;
|
||||||
|
best_raw = (int8_t)r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (best_raw >= 0 && best_dist <= max_track_dist_sq) {
|
||||||
|
raw_claimed[best_raw] = true;
|
||||||
|
pool->slot_point[s].x = (lv_coord_t)pool->raw_x[best_raw];
|
||||||
|
pool->slot_point[s].y = (lv_coord_t)pool->raw_y[best_raw];
|
||||||
|
} else {
|
||||||
|
pool->slot_active[s] = false;
|
||||||
|
slot_released_now[s] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second pass: any unclaimed raw point is a new touch - hand it to the first inactive slot
|
||||||
|
// that wasn't just released this round.
|
||||||
|
for (uint8_t r = 0; r < pool->raw_count; r++) {
|
||||||
|
if (raw_claimed[r]) continue;
|
||||||
|
for (uint8_t s = 0; s < pool->slot_count; s++) {
|
||||||
|
if (pool->slot_active[s] || slot_released_now[s]) continue;
|
||||||
|
pool->slot_active[s] = true;
|
||||||
|
pool->slot_point[s].x = (lv_coord_t)pool->raw_x[r];
|
||||||
|
pool->slot_point[s].y = (lv_coord_t)pool->raw_y[r];
|
||||||
|
raw_claimed[r] = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The actual LVGL indev read callback, shared by every slot in the pool. Only the first slot to
|
||||||
|
// be read each round (round_pos wraps 0..slot_count-1) triggers the real bus read + reassignment;
|
||||||
|
// the rest just report whatever lvgl_pointer_pool_assign() decided for their slot. Which slot
|
||||||
|
// happens to run first varies (LVGL calls each indev's timer independently), but since all slots
|
||||||
|
// share one timer period they complete one full round every slot_count calls regardless of order.
|
||||||
static void lvgl_pointer_read_cb(lv_indev_t* indev, lv_indev_data_t* data) {
|
static void lvgl_pointer_read_cb(lv_indev_t* indev, lv_indev_data_t* data) {
|
||||||
struct LvglDeviceContext* wrapper = (struct LvglDeviceContext*)lv_indev_get_driver_data(indev);
|
struct LvglPointerPool* pool = lvgl_pointer_pool_from_indev(indev);
|
||||||
struct LvglPointerCtx* ctx = (struct LvglPointerCtx*)wrapper->context;
|
if (pool == NULL) {
|
||||||
lv_display_t* display = lv_indev_get_display(indev);
|
|
||||||
|
|
||||||
// lv_display_get_original_*_resolution() is the native (LV_DISPLAY_ROTATION_0) size,
|
|
||||||
// unaffected by the display's current rotation - no rotation lookup needed to get it.
|
|
||||||
int32_t native_x_max = display != NULL ? lv_display_get_original_horizontal_resolution(display) - 1 : 0;
|
|
||||||
int32_t native_y_max = display != NULL ? lv_display_get_original_vertical_resolution(display) - 1 : 0;
|
|
||||||
|
|
||||||
uint16_t x = 0;
|
|
||||||
uint16_t y = 0;
|
|
||||||
if (!lvgl_pointer_read_calibrated(wrapper->device, ctx, native_x_max, native_y_max, &x, &y)) {
|
|
||||||
data->state = LV_INDEV_STATE_RELEASED;
|
data->state = LV_INDEV_STATE_RELEASED;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
data->point.x = x;
|
uint8_t slot = 0;
|
||||||
data->point.y = y;
|
for (; slot < pool->slot_count; slot++) {
|
||||||
data->state = LV_INDEV_STATE_PRESSED;
|
if (pool->slot_indev[slot] == indev) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pool->round_pos == 0) {
|
||||||
|
lv_display_t* display = lv_indev_get_display(indev);
|
||||||
|
// lv_display_get_original_*_resolution() is the native (LV_DISPLAY_ROTATION_0) size,
|
||||||
|
// unaffected by the display's current rotation - no rotation lookup needed to get it.
|
||||||
|
int32_t native_x_max = display != NULL ? lv_display_get_original_horizontal_resolution(display) - 1 : 0;
|
||||||
|
int32_t native_y_max = display != NULL ? lv_display_get_original_vertical_resolution(display) - 1 : 0;
|
||||||
|
lvgl_pointer_pool_refresh(pool, native_x_max, native_y_max);
|
||||||
|
lvgl_pointer_pool_assign(pool, native_x_max);
|
||||||
|
}
|
||||||
|
pool->round_pos = (uint8_t)((pool->round_pos + 1) % pool->slot_count);
|
||||||
|
|
||||||
|
if (slot < pool->slot_count && pool->slot_active[slot]) {
|
||||||
|
data->point = pool->slot_point[slot];
|
||||||
|
data->state = LV_INDEV_STATE_PRESSED;
|
||||||
|
} else {
|
||||||
|
data->state = LV_INDEV_STATE_RELEASED;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
error_t lvgl_pointer_add(struct Device* device, lv_display_t* display, lv_indev_t** out_indev) {
|
error_t lvgl_pointer_add(struct Device* device, lv_display_t* display, uint8_t max_touch_points, lv_indev_t** out_indevs) {
|
||||||
if (device == NULL || out_indev == NULL) {
|
if (device == NULL || out_indevs == NULL) {
|
||||||
return ERROR_INVALID_ARGUMENT;
|
return ERROR_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
if (device_get_type(device) != &POINTER_TYPE) {
|
if (device_get_type(device) != &POINTER_TYPE) {
|
||||||
return ERROR_INVALID_ARGUMENT;
|
return ERROR_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
|
if (max_touch_points == 0) max_touch_points = 1;
|
||||||
|
if (max_touch_points > LVGL_POINTER_MAX_SLOTS) max_touch_points = LVGL_POINTER_MAX_SLOTS;
|
||||||
|
|
||||||
struct LvglPointerCtx* ctx = new(std::nothrow) LvglPointerCtx();
|
auto* pool = new(std::nothrow) LvglPointerPool();
|
||||||
if (ctx == NULL) {
|
if (pool == NULL) {
|
||||||
return ERROR_OUT_OF_MEMORY;
|
return ERROR_OUT_OF_MEMORY;
|
||||||
}
|
}
|
||||||
struct LvglDeviceContext* wrapper = new(std::nothrow) LvglDeviceContext(ctx);
|
pool->device = device;
|
||||||
if (wrapper == NULL) {
|
pool->slot_count = max_touch_points;
|
||||||
delete ctx;
|
|
||||||
return ERROR_OUT_OF_MEMORY;
|
|
||||||
}
|
|
||||||
wrapper->device = device;
|
|
||||||
|
|
||||||
lv_indev_t* indev = lv_indev_create();
|
// Every slot gets its own LvglDeviceContext wrapper, but all of them point at this same pool.
|
||||||
if (indev == NULL) {
|
// On the way out - success or failure - every wrapper's context pointer is nulled before any
|
||||||
delete wrapper;
|
// of them are deleted, then the pool itself is freed exactly once: LvglDeviceContext's
|
||||||
return ERROR_OUT_OF_MEMORY;
|
// destructor does `::operator delete(context)`, so leaving more than one wrapper owning the
|
||||||
|
// same pool pointer would double-free it (see lvgl_pointer_remove(), same pattern).
|
||||||
|
uint8_t created = 0;
|
||||||
|
for (; created < max_touch_points; created++) {
|
||||||
|
auto* wrapper = new(std::nothrow) LvglDeviceContext(pool);
|
||||||
|
if (wrapper == NULL) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
wrapper->device = device;
|
||||||
|
|
||||||
|
lv_indev_t* indev = lv_indev_create();
|
||||||
|
if (indev == NULL) {
|
||||||
|
delete wrapper;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
|
||||||
|
lv_indev_set_read_cb(indev, lvgl_pointer_read_cb);
|
||||||
|
lv_indev_set_driver_data(indev, wrapper);
|
||||||
|
if (display != NULL) {
|
||||||
|
lv_indev_set_display(indev, display);
|
||||||
|
}
|
||||||
|
|
||||||
|
pool->slot_indev[created] = indev;
|
||||||
|
out_indevs[created] = indev;
|
||||||
}
|
}
|
||||||
|
|
||||||
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
|
if (created < max_touch_points) {
|
||||||
lv_indev_set_read_cb(indev, lvgl_pointer_read_cb);
|
for (uint8_t j = 0; j < created; j++) {
|
||||||
lv_indev_set_driver_data(indev, wrapper);
|
lv_indev_t* slot_indev = pool->slot_indev[j];
|
||||||
if (display != NULL) {
|
auto* slot_wrapper = (LvglDeviceContext*)lv_indev_get_driver_data(slot_indev);
|
||||||
lv_indev_set_display(indev, display);
|
slot_wrapper->context = NULL;
|
||||||
|
lv_indev_delete(slot_indev);
|
||||||
|
delete slot_wrapper;
|
||||||
|
}
|
||||||
|
delete pool;
|
||||||
|
return ERROR_OUT_OF_MEMORY;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (default_pointer_indev == NULL) {
|
if (default_pointer_indev == NULL) {
|
||||||
default_pointer_indev = indev;
|
default_pointer_indev = pool->slot_indev[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
*out_indev = indev;
|
|
||||||
return ERROR_NONE;
|
return ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,48 +301,78 @@ lv_indev_t* lvgl_pointer_get_default(void) {
|
|||||||
return default_pointer_indev;
|
return default_pointer_indev;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Any slot indev's wrapper->context points at the same shared pool, so calibration set through
|
||||||
|
// any one of them applies to the whole physical device/all its slots (one panel, one calibration).
|
||||||
error_t lvgl_pointer_set_calibration(lv_indev_t* indev, const struct LvglPointerCalibration* calibration) {
|
error_t lvgl_pointer_set_calibration(lv_indev_t* indev, const struct LvglPointerCalibration* calibration) {
|
||||||
if (indev == NULL) {
|
struct LvglPointerPool* pool = lvgl_pointer_pool_from_indev(indev);
|
||||||
|
if (pool == NULL) {
|
||||||
return ERROR_INVALID_ARGUMENT;
|
return ERROR_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
struct LvglDeviceContext* wrapper = (struct LvglDeviceContext*)lv_indev_get_driver_data(indev);
|
|
||||||
struct LvglPointerCtx* ctx = (struct LvglPointerCtx*)wrapper->context;
|
|
||||||
|
|
||||||
if (calibration == NULL) {
|
if (calibration == NULL) {
|
||||||
ctx->calibration_enabled = false;
|
pool->calibration_enabled = false;
|
||||||
return ERROR_NONE;
|
return ERROR_NONE;
|
||||||
}
|
}
|
||||||
if (!lvgl_pointer_calibration_is_valid(calibration)) {
|
if (!lvgl_pointer_calibration_is_valid(calibration)) {
|
||||||
return ERROR_INVALID_ARGUMENT;
|
return ERROR_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx->calibration = *calibration;
|
pool->calibration = *calibration;
|
||||||
ctx->calibration_enabled = true;
|
pool->calibration_enabled = true;
|
||||||
return ERROR_NONE;
|
return ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool lvgl_pointer_get_calibration(lv_indev_t* indev, struct LvglPointerCalibration* out_calibration) {
|
bool lvgl_pointer_get_calibration(lv_indev_t* indev, struct LvglPointerCalibration* out_calibration) {
|
||||||
if (indev == NULL || out_calibration == NULL) {
|
if (out_calibration == NULL) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
struct LvglDeviceContext* wrapper = (struct LvglDeviceContext*)lv_indev_get_driver_data(indev);
|
struct LvglPointerPool* pool = lvgl_pointer_pool_from_indev(indev);
|
||||||
struct LvglPointerCtx* ctx = (struct LvglPointerCtx*)wrapper->context;
|
if (pool == NULL || !pool->calibration_enabled) {
|
||||||
if (!ctx->calibration_enabled) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
*out_calibration = ctx->calibration;
|
*out_calibration = pool->calibration;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int8_t lvgl_pointer_get_slot_index(lv_indev_t* indev) {
|
||||||
|
struct LvglPointerPool* pool = lvgl_pointer_pool_from_indev(indev);
|
||||||
|
if (pool == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
for (uint8_t i = 0; i < pool->slot_count; i++) {
|
||||||
|
if (pool->slot_indev[i] == indev) return (int8_t)i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removes every slot indev belonging to the same pool as `indev` (a partial pool removal isn't a
|
||||||
|
// case that comes up: apps ask for "the pointer device" and get every slot back from
|
||||||
|
// lvgl_pointer_add(), so they hold either all of a pool's indevs or none).
|
||||||
void lvgl_pointer_remove(lv_indev_t* indev) {
|
void lvgl_pointer_remove(lv_indev_t* indev) {
|
||||||
if (indev == NULL) {
|
if (indev == NULL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LvglDeviceContext* wrapper = (struct LvglDeviceContext*)lv_indev_get_driver_data(indev);
|
struct LvglDeviceContext* wrapper = (struct LvglDeviceContext*)lv_indev_get_driver_data(indev);
|
||||||
if (default_pointer_indev == indev) {
|
struct LvglPointerPool* pool = (struct LvglPointerPool*)wrapper->context;
|
||||||
default_pointer_indev = NULL;
|
uint8_t slot_count = pool->slot_count;
|
||||||
|
lv_indev_t* slot_indevs[LVGL_POINTER_MAX_SLOTS];
|
||||||
|
memcpy(slot_indevs, pool->slot_indev, sizeof(lv_indev_t*) * slot_count);
|
||||||
|
|
||||||
|
// Every LvglDeviceContext wrapper points at the same pool; null `context` out on all of them
|
||||||
|
// before deleting any (LvglDeviceContext's destructor frees `context`, and pool must stay
|
||||||
|
// valid for every wrapper's own delete to run safely) - then free the pool exactly once
|
||||||
|
// ourselves at the end.
|
||||||
|
for (uint8_t i = 0; i < slot_count; i++) {
|
||||||
|
lv_indev_t* slot_indev = slot_indevs[i];
|
||||||
|
if (slot_indev == NULL) continue;
|
||||||
|
if (default_pointer_indev == slot_indev) {
|
||||||
|
default_pointer_indev = NULL;
|
||||||
|
}
|
||||||
|
struct LvglDeviceContext* slot_wrapper = (struct LvglDeviceContext*)lv_indev_get_driver_data(slot_indev);
|
||||||
|
slot_wrapper->context = NULL;
|
||||||
|
lv_indev_delete(slot_indev);
|
||||||
|
delete slot_wrapper;
|
||||||
}
|
}
|
||||||
lv_indev_delete(indev);
|
delete pool;
|
||||||
delete wrapper;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ const struct ModuleSymbol lvgl_module_symbols[] = {
|
|||||||
DEFINE_MODULE_SYMBOL(lvgl_pointer_get_calibration),
|
DEFINE_MODULE_SYMBOL(lvgl_pointer_get_calibration),
|
||||||
DEFINE_MODULE_SYMBOL(lvgl_pointer_get_default),
|
DEFINE_MODULE_SYMBOL(lvgl_pointer_get_default),
|
||||||
DEFINE_MODULE_SYMBOL(lvgl_pointer_add),
|
DEFINE_MODULE_SYMBOL(lvgl_pointer_add),
|
||||||
|
DEFINE_MODULE_SYMBOL(lvgl_pointer_get_slot_index),
|
||||||
DEFINE_MODULE_SYMBOL(lvgl_pointer_remove),
|
DEFINE_MODULE_SYMBOL(lvgl_pointer_remove),
|
||||||
// lvgl_spinner
|
// lvgl_spinner
|
||||||
DEFINE_MODULE_SYMBOL(lvgl_spinner_create),
|
DEFINE_MODULE_SYMBOL(lvgl_spinner_create),
|
||||||
@@ -56,6 +57,7 @@ const struct ModuleSymbol lvgl_module_symbols[] = {
|
|||||||
DEFINE_MODULE_SYMBOL(lvgl_toolbar_add_image_button_action),
|
DEFINE_MODULE_SYMBOL(lvgl_toolbar_add_image_button_action),
|
||||||
DEFINE_MODULE_SYMBOL(lvgl_toolbar_add_text_button_action),
|
DEFINE_MODULE_SYMBOL(lvgl_toolbar_add_text_button_action),
|
||||||
DEFINE_MODULE_SYMBOL(lvgl_toolbar_add_switch_action),
|
DEFINE_MODULE_SYMBOL(lvgl_toolbar_add_switch_action),
|
||||||
|
DEFINE_MODULE_SYMBOL(lvgl_toolbar_add_dropdown_action),
|
||||||
DEFINE_MODULE_SYMBOL(lvgl_toolbar_add_spinner_action),
|
DEFINE_MODULE_SYMBOL(lvgl_toolbar_add_spinner_action),
|
||||||
DEFINE_MODULE_SYMBOL(lvgl_toolbar_clear_actions),
|
DEFINE_MODULE_SYMBOL(lvgl_toolbar_clear_actions),
|
||||||
// lvgl_sliderbox
|
// lvgl_sliderbox
|
||||||
@@ -123,6 +125,8 @@ const struct ModuleSymbol lvgl_module_symbols[] = {
|
|||||||
DEFINE_MODULE_SYMBOL(lv_obj_set_flex_flow),
|
DEFINE_MODULE_SYMBOL(lv_obj_set_flex_flow),
|
||||||
DEFINE_MODULE_SYMBOL(lv_obj_set_flex_grow),
|
DEFINE_MODULE_SYMBOL(lv_obj_set_flex_grow),
|
||||||
DEFINE_MODULE_SYMBOL(lv_obj_set_layout),
|
DEFINE_MODULE_SYMBOL(lv_obj_set_layout),
|
||||||
|
DEFINE_MODULE_SYMBOL(lv_obj_set_grid_dsc_array),
|
||||||
|
DEFINE_MODULE_SYMBOL(lv_obj_set_grid_cell),
|
||||||
DEFINE_MODULE_SYMBOL(lv_obj_is_layout_positioned),
|
DEFINE_MODULE_SYMBOL(lv_obj_is_layout_positioned),
|
||||||
DEFINE_MODULE_SYMBOL(lv_obj_mark_layout_as_dirty),
|
DEFINE_MODULE_SYMBOL(lv_obj_mark_layout_as_dirty),
|
||||||
DEFINE_MODULE_SYMBOL(lv_obj_get_style_layout),
|
DEFINE_MODULE_SYMBOL(lv_obj_get_style_layout),
|
||||||
@@ -192,6 +196,9 @@ const struct ModuleSymbol lvgl_module_symbols[] = {
|
|||||||
DEFINE_MODULE_SYMBOL(lv_obj_set_style_transform_pivot_x),
|
DEFINE_MODULE_SYMBOL(lv_obj_set_style_transform_pivot_x),
|
||||||
DEFINE_MODULE_SYMBOL(lv_obj_set_style_transform_pivot_y),
|
DEFINE_MODULE_SYMBOL(lv_obj_set_style_transform_pivot_y),
|
||||||
DEFINE_MODULE_SYMBOL(lv_obj_set_style_transform_rotation),
|
DEFINE_MODULE_SYMBOL(lv_obj_set_style_transform_rotation),
|
||||||
|
DEFINE_MODULE_SYMBOL(lv_obj_set_style_transform_scale),
|
||||||
|
DEFINE_MODULE_SYMBOL(lv_obj_set_style_transform_scale_x),
|
||||||
|
DEFINE_MODULE_SYMBOL(lv_obj_set_style_transform_scale_y),
|
||||||
DEFINE_MODULE_SYMBOL(lv_obj_scroll_to_y),
|
DEFINE_MODULE_SYMBOL(lv_obj_scroll_to_y),
|
||||||
DEFINE_MODULE_SYMBOL(lv_obj_set_scrollbar_mode),
|
DEFINE_MODULE_SYMBOL(lv_obj_set_scrollbar_mode),
|
||||||
DEFINE_MODULE_SYMBOL(lv_obj_get_child_count),
|
DEFINE_MODULE_SYMBOL(lv_obj_get_child_count),
|
||||||
@@ -361,6 +368,14 @@ const struct ModuleSymbol lvgl_module_symbols[] = {
|
|||||||
DEFINE_MODULE_SYMBOL(lv_textarea_set_max_length),
|
DEFINE_MODULE_SYMBOL(lv_textarea_set_max_length),
|
||||||
DEFINE_MODULE_SYMBOL(lv_textarea_set_cursor_click_pos),
|
DEFINE_MODULE_SYMBOL(lv_textarea_set_cursor_click_pos),
|
||||||
DEFINE_MODULE_SYMBOL(lv_textarea_add_text),
|
DEFINE_MODULE_SYMBOL(lv_textarea_add_text),
|
||||||
|
DEFINE_MODULE_SYMBOL(lv_textarea_add_char),
|
||||||
|
DEFINE_MODULE_SYMBOL(lv_textarea_delete_char),
|
||||||
|
DEFINE_MODULE_SYMBOL(lv_textarea_delete_char_forward),
|
||||||
|
DEFINE_MODULE_SYMBOL(lv_textarea_set_cursor_pos),
|
||||||
|
DEFINE_MODULE_SYMBOL(lv_textarea_cursor_left),
|
||||||
|
DEFINE_MODULE_SYMBOL(lv_textarea_cursor_right),
|
||||||
|
DEFINE_MODULE_SYMBOL(lv_textarea_cursor_up),
|
||||||
|
DEFINE_MODULE_SYMBOL(lv_textarea_cursor_down),
|
||||||
// lv_palette
|
// lv_palette
|
||||||
DEFINE_MODULE_SYMBOL(lv_palette_main),
|
DEFINE_MODULE_SYMBOL(lv_palette_main),
|
||||||
DEFINE_MODULE_SYMBOL(lv_palette_darken),
|
DEFINE_MODULE_SYMBOL(lv_palette_darken),
|
||||||
@@ -409,6 +424,7 @@ const struct ModuleSymbol lvgl_module_symbols[] = {
|
|||||||
DEFINE_MODULE_SYMBOL(lv_indev_get_next),
|
DEFINE_MODULE_SYMBOL(lv_indev_get_next),
|
||||||
DEFINE_MODULE_SYMBOL(lv_indev_set_group),
|
DEFINE_MODULE_SYMBOL(lv_indev_set_group),
|
||||||
DEFINE_MODULE_SYMBOL(lv_indev_wait_release),
|
DEFINE_MODULE_SYMBOL(lv_indev_wait_release),
|
||||||
|
DEFINE_MODULE_SYMBOL(lv_indev_enable),
|
||||||
// lv_timer
|
// lv_timer
|
||||||
DEFINE_MODULE_SYMBOL(lv_timer_handler),
|
DEFINE_MODULE_SYMBOL(lv_timer_handler),
|
||||||
DEFINE_MODULE_SYMBOL(lv_timer_handler_run_in_period),
|
DEFINE_MODULE_SYMBOL(lv_timer_handler_run_in_period),
|
||||||
@@ -438,6 +454,7 @@ const struct ModuleSymbol lvgl_module_symbols[] = {
|
|||||||
DEFINE_MODULE_SYMBOL(lv_line_set_points),
|
DEFINE_MODULE_SYMBOL(lv_line_set_points),
|
||||||
DEFINE_MODULE_SYMBOL(lv_line_set_points_mutable),
|
DEFINE_MODULE_SYMBOL(lv_line_set_points_mutable),
|
||||||
DEFINE_MODULE_SYMBOL(lv_tick_get),
|
DEFINE_MODULE_SYMBOL(lv_tick_get),
|
||||||
|
DEFINE_MODULE_SYMBOL(lv_delay_ms),
|
||||||
DEFINE_MODULE_SYMBOL(lv_tick_elaps),
|
DEFINE_MODULE_SYMBOL(lv_tick_elaps),
|
||||||
// lv_slider
|
// lv_slider
|
||||||
DEFINE_MODULE_SYMBOL(lv_slider_create),
|
DEFINE_MODULE_SYMBOL(lv_slider_create),
|
||||||
|
|||||||
@@ -234,6 +234,29 @@ lv_obj_t* lvgl_toolbar_add_switch_action(lv_obj_t* obj) {
|
|||||||
return widget;
|
return widget;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lv_obj_t* lvgl_toolbar_add_dropdown_action(lv_obj_t* obj, const char* options, lv_coord_t width, const char* text) {
|
||||||
|
auto* toolbar = reinterpret_cast<Toolbar*>(obj);
|
||||||
|
check(toolbar->action_count < TOOLBAR_ACTION_LIMIT, "max actions reached");
|
||||||
|
toolbar->action_count++;
|
||||||
|
|
||||||
|
auto ui_density = lvgl_get_ui_density();
|
||||||
|
auto* wrapper = create_action_wrapper(toolbar->action_container, ui_density);
|
||||||
|
lv_obj_set_style_pad_hor(wrapper, 4, LV_STATE_DEFAULT);
|
||||||
|
|
||||||
|
lv_obj_t* widget = lv_dropdown_create(wrapper);
|
||||||
|
lv_dropdown_set_options(widget, options);
|
||||||
|
lv_dropdown_set_selected_highlight(widget, false);
|
||||||
|
if (width > 0) {
|
||||||
|
lv_obj_set_width(widget, width);
|
||||||
|
}
|
||||||
|
if (text != nullptr) {
|
||||||
|
lv_dropdown_set_text(widget, text);
|
||||||
|
}
|
||||||
|
lv_obj_set_align(widget, LV_ALIGN_CENTER);
|
||||||
|
|
||||||
|
return widget;
|
||||||
|
}
|
||||||
|
|
||||||
lv_obj_t* lvgl_toolbar_add_spinner_action(lv_obj_t* obj) {
|
lv_obj_t* lvgl_toolbar_add_spinner_action(lv_obj_t* obj) {
|
||||||
auto* toolbar = reinterpret_cast<Toolbar*>(obj);
|
auto* toolbar = reinterpret_cast<Toolbar*>(obj);
|
||||||
check(toolbar->action_count < TOOLBAR_ACTION_LIMIT, "max actions reached");
|
check(toolbar->action_count < TOOLBAR_ACTION_LIMIT, "max actions reached");
|
||||||
|
|||||||
@@ -2,11 +2,20 @@ cmake_minimum_required(VERSION 3.20)
|
|||||||
|
|
||||||
file(GLOB_RECURSE SOURCES "source/*.c**")
|
file(GLOB_RECURSE SOURCES "source/*.c**")
|
||||||
|
|
||||||
|
set(PRIV_REQUIRES_LIST "")
|
||||||
|
if ("${IDF_TARGET}" STREQUAL "esp32s3" OR "${IDF_TARGET}" STREQUAL "esp32p4")
|
||||||
|
# esp32_usb_device_controller.cpp / esp32_usb_hid_device.cpp include tinyusb.h - only
|
||||||
|
# available on targets with TinyUSB device-mode support (mirrors Tactility/CMakeLists.txt's
|
||||||
|
# own esp_tinyusb requirement).
|
||||||
|
list(APPEND PRIV_REQUIRES_LIST esp_tinyusb)
|
||||||
|
endif ()
|
||||||
|
|
||||||
idf_component_register(
|
idf_component_register(
|
||||||
SRCS ${SOURCES}
|
SRCS ${SOURCES}
|
||||||
INCLUDE_DIRS "include/"
|
INCLUDE_DIRS "include/"
|
||||||
PRIV_INCLUDE_DIRS "private/"
|
PRIV_INCLUDE_DIRS "private/"
|
||||||
REQUIRES TactilityKernel driver esp_adc esp_driver_i2c esp_lcd vfs fatfs esp_wifi esp_netif esp_event
|
REQUIRES TactilityKernel driver esp_adc esp_driver_i2c esp_lcd vfs fatfs esp_wifi esp_netif esp_event
|
||||||
|
PRIV_REQUIRES ${PRIV_REQUIRES_LIST}
|
||||||
)
|
)
|
||||||
|
|
||||||
if (DEFINED ENV{ESP_IDF_VERSION})
|
if (DEFINED ENV{ESP_IDF_VERSION})
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
description: ESP32 USB device-mode CDC-ACM addon (composites a USB serial console interface into whichever primary class - HID or MIDI - is active, or stands alone with no primary; deliberately excluded from MSC, which always presents as plain mass storage with no console). Requires CONFIG_TINYUSB_CDC_ENABLED.
|
||||||
|
|
||||||
|
compatible: "espressif,esp32-usbdevice-cdc"
|
||||||
|
|
||||||
|
properties:
|
||||||
|
_unused:
|
||||||
|
type: int
|
||||||
|
default: 0
|
||||||
|
description: Placeholder required by the binding schema; this driver has no device-tree configuration.
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
description: ESP32 USB device-mode HID class driver (present the board as a USB HID peripheral, e.g. a keyboard, to a host). Requires CONFIG_TINYUSB_HID_COUNT > 0.
|
||||||
|
|
||||||
|
compatible: "espressif,esp32-usbdevice-hid"
|
||||||
|
|
||||||
|
properties:
|
||||||
|
_unused:
|
||||||
|
type: int
|
||||||
|
default: 0
|
||||||
|
description: Placeholder required by the binding schema; this driver has no device-tree configuration.
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
description: ESP32 USB device-mode MIDI class driver (present the board as a USB MIDI peripheral to a host). Requires CONFIG_TINYUSB_MIDI_COUNT > 0.
|
||||||
|
|
||||||
|
compatible: "espressif,esp32-usbdevice-midi"
|
||||||
|
|
||||||
|
properties:
|
||||||
|
_unused:
|
||||||
|
type: int
|
||||||
|
default: 0
|
||||||
|
description: Placeholder required by the binding schema; this driver has no device-tree configuration.
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
description: ESP32 USB device-mode MSC class driver (present the board's SD card or internal flash as a USB mass storage device to a host). Requires CONFIG_TINYUSB_MSC_ENABLED.
|
||||||
|
|
||||||
|
compatible: "espressif,esp32-usbdevice-msc"
|
||||||
|
|
||||||
|
properties:
|
||||||
|
_unused:
|
||||||
|
type: int
|
||||||
|
default: 0
|
||||||
|
description: Placeholder required by the binding schema; this driver has no device-tree configuration.
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
description: ESP32 USB device-mode (peripheral) controller. Owns the single TinyUSB device-mode slot shared by its child classes (HID, MSC, ...); only one child class may be active at a time.
|
||||||
|
|
||||||
|
compatible: "espressif,esp32-usbdevice"
|
||||||
|
|
||||||
|
properties:
|
||||||
|
_unused:
|
||||||
|
type: int
|
||||||
|
default: 0
|
||||||
|
description: |
|
||||||
|
USB device-mode (peripheral) controller: presents itself as a USB device to a host (HID keyboard, mass storage, ...).
|
||||||
|
Only one of usbdevicehid0/usbdevicemsc0/usbdevicemidi0 may be active at a time -
|
||||||
|
usbdevicecdc0 is not part of that exclusion - it's an addon composited into whichever of HID/MIDI is active (never MSC)
|
||||||
|
toggle its status to enable/disable the USB serial console independent of which primary is running.
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <tactility/bindings/bindings.h>
|
||||||
|
#include <tactility/drivers/esp32_usbdevice.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
DEFINE_DEVICETREE(esp32_usbdevice, struct Esp32UsbDeviceConfig)
|
||||||
|
DEFINE_DEVICETREE(esp32_usbdevice_hid, struct Esp32UsbDeviceChildConfig)
|
||||||
|
DEFINE_DEVICETREE(esp32_usbdevice_msc, struct Esp32UsbDeviceChildConfig)
|
||||||
|
DEFINE_DEVICETREE(esp32_usbdevice_midi, struct Esp32UsbDeviceChildConfig)
|
||||||
|
DEFINE_DEVICETREE(esp32_usbdevice_cdc, struct Esp32UsbDeviceChildConfig)
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct Esp32UsbDeviceConfig {
|
||||||
|
int _unused;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Esp32UsbDeviceChildConfig {
|
||||||
|
int _unused;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ---- Shared HID report descriptor tables ----
|
||||||
|
//
|
||||||
|
// HID report descriptors are transport-independent (the same USB HID Usage Tables spec applies
|
||||||
|
// regardless of whether the bytes travel over USB or BLE GATT), so these tables are shared
|
||||||
|
// between esp32_usb_hid_device.cpp and esp32_ble_hid.cpp rather than each keeping its own copy.
|
||||||
|
// Report ID scheme matches BtHidDeviceMode/UsbHidDeviceMode 1:1 across both transports:
|
||||||
|
// Keyboard mode: report ID 1 = keyboard, report ID 2 = consumer control
|
||||||
|
// Mouse mode: report ID 1 = mouse
|
||||||
|
// Keyboard+Mouse mode: report ID 1 = keyboard, 2 = consumer, 3 = mouse
|
||||||
|
// Gamepad mode: report ID 1 = gamepad
|
||||||
|
|
||||||
|
/** Keyboard (report ID 1, 8 bytes: modifier, reserved, keycode[6]) + Consumer (report ID 2,
|
||||||
|
* 16-bit usage code, 2 bytes). Keyboard collection uses the standard boot-protocol byte
|
||||||
|
* layout, though the descriptor itself is Report protocol (report-ID-multiplexed with
|
||||||
|
* Consumer, which boot protocol can't express). */
|
||||||
|
extern const uint8_t hid_report_map_keyboard_consumer[];
|
||||||
|
extern const size_t hid_report_map_keyboard_consumer_len;
|
||||||
|
|
||||||
|
/** Mouse only (report ID 1, 4 bytes: buttons[3]+5 padding bits, X, Y, wheel). */
|
||||||
|
extern const uint8_t hid_report_map_mouse[];
|
||||||
|
extern const size_t hid_report_map_mouse_len;
|
||||||
|
|
||||||
|
/** Keyboard (report ID 1) + Consumer (report ID 2) + Mouse (report ID 3) combined. */
|
||||||
|
extern const uint8_t hid_report_map_keyboard_consumer_mouse[];
|
||||||
|
extern const size_t hid_report_map_keyboard_consumer_mouse_len;
|
||||||
|
|
||||||
|
/** Gamepad only (report ID 1, 8 bytes). Xbox-360-style layout: X/Y (1 byte each, left stick),
|
||||||
|
* Rx/Ry (1 byte each, right stick), Z (1 byte, triggers - L=positive, R=negative, centered=0),
|
||||||
|
* hat/dpad (1 byte, 1-8 = 8-direction clockwise from Up, 0 = centered/released), buttons[10]
|
||||||
|
* (2 bytes, 1=A 2=B 3=X 4=Y 5=LB 6=RB 7=Select 8=Start 9=L3 10=R3, 6 padding bits at the end). */
|
||||||
|
extern const uint8_t hid_report_map_gamepad[];
|
||||||
|
extern const size_t hid_report_map_gamepad_len;
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <host/ble_hs_mbuf.h>
|
#include <host/ble_hs_mbuf.h>
|
||||||
#include <services/gap/ble_svc_gap.h>
|
#include <services/gap/ble_svc_gap.h>
|
||||||
#include <services/gatt/ble_svc_gatt.h>
|
#include <services/gatt/ble_svc_gatt.h>
|
||||||
|
#include <tactility/drivers/hid_report_descriptors.h>
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
@@ -58,77 +59,10 @@ static uint8_t hid_protocol_mode = 0x01; // 0x00=Boot, 0x01=Report
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Per-profile HID Report Maps
|
// Per-profile HID Report Maps
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
//
|
||||||
// Keyboard + Consumer (IDs 1 and 2)
|
// Shared with esp32_usb_hid_device.cpp - see hid_report_descriptors.h for the byte tables
|
||||||
static const uint8_t hid_rpt_map_kb_consumer[] = {
|
// (report ID scheme, per-field layout) - HID report descriptors are transport-independent
|
||||||
0x05, 0x01, 0x09, 0x06, 0xA1, 0x01,
|
// so the same bytes apply verbatim whether carried over USB or BLE GATT.
|
||||||
0x85, 0x01,
|
|
||||||
0x05, 0x07, 0x19, 0xE0, 0x29, 0xE7, 0x15, 0x00, 0x25, 0x01,
|
|
||||||
0x75, 0x01, 0x95, 0x08, 0x81, 0x02,
|
|
||||||
0x75, 0x08, 0x95, 0x01, 0x81, 0x01,
|
|
||||||
0x05, 0x08, 0x19, 0x01, 0x29, 0x05, 0x75, 0x01, 0x95, 0x05, 0x91, 0x02,
|
|
||||||
0x75, 0x03, 0x95, 0x01, 0x91, 0x01,
|
|
||||||
0x15, 0x00, 0x25, 0x73, 0x05, 0x07, 0x19, 0x00, 0x29, 0x73,
|
|
||||||
0x75, 0x08, 0x95, 0x06, 0x81, 0x00,
|
|
||||||
0xC0,
|
|
||||||
0x05, 0x0C, 0x09, 0x01, 0xA1, 0x01,
|
|
||||||
0x85, 0x02,
|
|
||||||
0x15, 0x00, 0x26, 0xFF, 0x03, 0x19, 0x00, 0x2A, 0xFF, 0x03,
|
|
||||||
0x75, 0x10, 0x95, 0x01, 0x81, 0x00,
|
|
||||||
0xC0,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Mouse only (ID 1, 4 bytes)
|
|
||||||
static const uint8_t hid_rpt_map_mouse[] = {
|
|
||||||
0x05, 0x01, 0x09, 0x02, 0xA1, 0x01,
|
|
||||||
0x85, 0x01,
|
|
||||||
0x09, 0x01, 0xA1, 0x00,
|
|
||||||
0x05, 0x09, 0x19, 0x01, 0x29, 0x05,
|
|
||||||
0x15, 0x00, 0x25, 0x01, 0x95, 0x05, 0x75, 0x01, 0x81, 0x02,
|
|
||||||
0x95, 0x01, 0x75, 0x03, 0x81, 0x01,
|
|
||||||
0x05, 0x01, 0x09, 0x30, 0x09, 0x31, 0x09, 0x38,
|
|
||||||
0x15, 0x81, 0x25, 0x7F, 0x75, 0x08, 0x95, 0x03, 0x81, 0x06,
|
|
||||||
0xC0, 0xC0,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Keyboard + Consumer + Mouse (IDs 1, 2, 3)
|
|
||||||
static const uint8_t hid_rpt_map_kb_mouse[] = {
|
|
||||||
0x05, 0x01, 0x09, 0x06, 0xA1, 0x01,
|
|
||||||
0x85, 0x01,
|
|
||||||
0x05, 0x07, 0x19, 0xE0, 0x29, 0xE7, 0x15, 0x00, 0x25, 0x01,
|
|
||||||
0x75, 0x01, 0x95, 0x08, 0x81, 0x02,
|
|
||||||
0x75, 0x08, 0x95, 0x01, 0x81, 0x01,
|
|
||||||
0x05, 0x08, 0x19, 0x01, 0x29, 0x05, 0x75, 0x01, 0x95, 0x05, 0x91, 0x02,
|
|
||||||
0x75, 0x03, 0x95, 0x01, 0x91, 0x01,
|
|
||||||
0x15, 0x00, 0x25, 0x73, 0x05, 0x07, 0x19, 0x00, 0x29, 0x73,
|
|
||||||
0x75, 0x08, 0x95, 0x06, 0x81, 0x00,
|
|
||||||
0xC0,
|
|
||||||
0x05, 0x0C, 0x09, 0x01, 0xA1, 0x01,
|
|
||||||
0x85, 0x02,
|
|
||||||
0x15, 0x00, 0x26, 0xFF, 0x03, 0x19, 0x00, 0x2A, 0xFF, 0x03,
|
|
||||||
0x75, 0x10, 0x95, 0x01, 0x81, 0x00,
|
|
||||||
0xC0,
|
|
||||||
0x05, 0x01, 0x09, 0x02, 0xA1, 0x01,
|
|
||||||
0x85, 0x03,
|
|
||||||
0x09, 0x01, 0xA1, 0x00,
|
|
||||||
0x05, 0x09, 0x19, 0x01, 0x29, 0x05,
|
|
||||||
0x15, 0x00, 0x25, 0x01, 0x95, 0x05, 0x75, 0x01, 0x81, 0x02,
|
|
||||||
0x95, 0x01, 0x75, 0x03, 0x81, 0x01,
|
|
||||||
0x05, 0x01, 0x09, 0x30, 0x09, 0x31, 0x09, 0x38,
|
|
||||||
0x15, 0x81, 0x25, 0x7F, 0x75, 0x08, 0x95, 0x03, 0x81, 0x06,
|
|
||||||
0xC0, 0xC0,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Gamepad only (ID 1, 8 bytes)
|
|
||||||
static const uint8_t hid_rpt_map_gamepad[] = {
|
|
||||||
0x05, 0x01, 0x09, 0x05, 0xA1, 0x01,
|
|
||||||
0x85, 0x01,
|
|
||||||
0x05, 0x09, 0x19, 0x01, 0x29, 0x10,
|
|
||||||
0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x10, 0x81, 0x02,
|
|
||||||
0x05, 0x01, 0x09, 0x30, 0x09, 0x31, 0x09, 0x32, 0x09, 0x35, 0x09, 0x33, 0x09, 0x34,
|
|
||||||
0x15, 0x81, 0x25, 0x7F, 0x75, 0x08, 0x95, 0x06, 0x81, 0x02,
|
|
||||||
0xC0,
|
|
||||||
};
|
|
||||||
|
|
||||||
// ---- Per-profile Report Reference descriptor data ----
|
// ---- Per-profile Report Reference descriptor data ----
|
||||||
// Format: {Report ID, Report Type} — Type: 1=Input, 2=Output
|
// Format: {Report ID, Report Type} — Type: 1=Input, 2=Output
|
||||||
@@ -181,6 +115,7 @@ static int hid_chr_access(uint16_t /*conn_handle*/, uint16_t attr_handle,
|
|||||||
size_t report_len = 8;
|
size_t report_len = 8;
|
||||||
if (attr_handle == hid_consumer_input_handle) report_len = 2;
|
if (attr_handle == hid_consumer_input_handle) report_len = 2;
|
||||||
else if (attr_handle == hid_mouse_input_handle) report_len = 4;
|
else if (attr_handle == hid_mouse_input_handle) report_len = 4;
|
||||||
|
else if (attr_handle == hid_gamepad_input_handle) report_len = 8;
|
||||||
int rc = os_mbuf_append(ctxt->om, zeros, report_len);
|
int rc = os_mbuf_append(ctxt->om, zeros, report_len);
|
||||||
return (rc == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
|
return (rc == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
|
||||||
}
|
}
|
||||||
@@ -354,10 +289,10 @@ bool ble_hid_switch_profile(struct Device* device, BleHidProfile profile) {
|
|||||||
const uint8_t* new_rpt_map = nullptr;
|
const uint8_t* new_rpt_map = nullptr;
|
||||||
size_t new_rpt_map_len = 0;
|
size_t new_rpt_map_len = 0;
|
||||||
switch (profile) {
|
switch (profile) {
|
||||||
case BleHidProfile::KbConsumer: svcs = gatt_svcs_kb_consumer; new_rpt_map = hid_rpt_map_kb_consumer; new_rpt_map_len = sizeof(hid_rpt_map_kb_consumer); break;
|
case BleHidProfile::KbConsumer: svcs = gatt_svcs_kb_consumer; new_rpt_map = hid_report_map_keyboard_consumer; new_rpt_map_len = hid_report_map_keyboard_consumer_len; break;
|
||||||
case BleHidProfile::Mouse: svcs = gatt_svcs_mouse; new_rpt_map = hid_rpt_map_mouse; new_rpt_map_len = sizeof(hid_rpt_map_mouse); break;
|
case BleHidProfile::Mouse: svcs = gatt_svcs_mouse; new_rpt_map = hid_report_map_mouse; new_rpt_map_len = hid_report_map_mouse_len; break;
|
||||||
case BleHidProfile::KbMouse: svcs = gatt_svcs_kb_mouse; new_rpt_map = hid_rpt_map_kb_mouse; new_rpt_map_len = sizeof(hid_rpt_map_kb_mouse); break;
|
case BleHidProfile::KbMouse: svcs = gatt_svcs_kb_mouse; new_rpt_map = hid_report_map_keyboard_consumer_mouse; new_rpt_map_len = hid_report_map_keyboard_consumer_mouse_len; break;
|
||||||
case BleHidProfile::Gamepad: svcs = gatt_svcs_gamepad; new_rpt_map = hid_rpt_map_gamepad; new_rpt_map_len = sizeof(hid_rpt_map_gamepad); break;
|
case BleHidProfile::Gamepad: svcs = gatt_svcs_gamepad; new_rpt_map = hid_report_map_gamepad; new_rpt_map_len = hid_report_map_gamepad_len; break;
|
||||||
default: svcs = gatt_svcs_none; break;
|
default: svcs = gatt_svcs_none; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,225 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#include <tactility/drivers/hid_report_descriptors.h>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
// Keyboard (report ID 1) + Consumer (report ID 2)
|
||||||
|
const uint8_t hid_report_map_keyboard_consumer[] = {
|
||||||
|
0x05, 0x01, // Usage Page: Generic Desktop
|
||||||
|
0x09, 0x06, // Usage: Keyboard
|
||||||
|
0xA1, 0x01, // Collection: Application
|
||||||
|
// --- Keyboard
|
||||||
|
0x85, 0x01, // Report ID: 1
|
||||||
|
0x05, 0x07, // Usage Page: Keyboard/Keypad
|
||||||
|
0x19, 0xE0, // Usage Minimum: Left Control
|
||||||
|
0x29, 0xE7, // Usage Maximum: Right GUI
|
||||||
|
0x15, 0x00, // Logical Minimum: 0
|
||||||
|
0x25, 0x01, // Logical Maximum: 1
|
||||||
|
0x75, 0x01, // Report Size: 1
|
||||||
|
0x95, 0x08, // Report Count: 8
|
||||||
|
0x81, 0x02, // Input: Data, Variable, Absolute (modifier byte)
|
||||||
|
0x75, 0x08, // Report Size: 8
|
||||||
|
0x95, 0x01, // Report Count: 1
|
||||||
|
0x81, 0x01, // Input: Constant, Array, Absolute (reserved byte)
|
||||||
|
0x05, 0x08, // Usage Page: LEDs
|
||||||
|
0x19, 0x01, // Usage Minimum: 0x01
|
||||||
|
0x29, 0x05, // Usage Maximum: 0x05
|
||||||
|
0x75, 0x01, // Report Size: 1
|
||||||
|
0x95, 0x05, // Report Count: 5
|
||||||
|
0x91, 0x02, // Output: Data, Variable, Absolute (LED state)
|
||||||
|
0x75, 0x03, // Report Size: 3
|
||||||
|
0x95, 0x01, // Report Count: 1
|
||||||
|
0x91, 0x01, // Output: Constant, Array, Absolute (LED padding)
|
||||||
|
0x15, 0x00, // Logical Minimum: 0
|
||||||
|
0x25, 0x73, // Logical Maximum: 115
|
||||||
|
0x05, 0x07, // Usage Page: Keyboard/Keypad
|
||||||
|
0x19, 0x00, // Usage Minimum: 0x00
|
||||||
|
0x29, 0x73, // Usage Maximum: 0x73
|
||||||
|
0x75, 0x08, // Report Size: 8
|
||||||
|
0x95, 0x06, // Report Count: 6
|
||||||
|
0x81, 0x00, // Input: Data, Array, Absolute (keycode[6])
|
||||||
|
0xC0, // End Collection
|
||||||
|
// --- Consumer / Media Keys
|
||||||
|
0x05, 0x0C, // Usage Page: Consumer
|
||||||
|
0x09, 0x01, // Usage: Consumer Control
|
||||||
|
0xA1, 0x01, // Collection: Application
|
||||||
|
0x85, 0x02, // Report ID: 2
|
||||||
|
0x15, 0x00, // Logical Minimum: 0
|
||||||
|
0x26, 0xFF, 0x03, // Logical Maximum: 1023
|
||||||
|
0x19, 0x00, // Usage Minimum: 0x00
|
||||||
|
0x2A, 0xFF, 0x03, // Usage Maximum: 0x3FF
|
||||||
|
0x75, 0x10, // Report Size: 16
|
||||||
|
0x95, 0x01, // Report Count: 1
|
||||||
|
0x81, 0x00, // Input: Data, Array, Absolute
|
||||||
|
0xC0, // End Collection
|
||||||
|
};
|
||||||
|
const size_t hid_report_map_keyboard_consumer_len = sizeof(hid_report_map_keyboard_consumer);
|
||||||
|
|
||||||
|
// Mouse only (report ID 1)
|
||||||
|
const uint8_t hid_report_map_mouse[] = {
|
||||||
|
0x05, 0x01, // Usage Page: Generic Desktop
|
||||||
|
0x09, 0x02, // Usage: Mouse
|
||||||
|
0xA1, 0x01, // Collection: Application
|
||||||
|
0x85, 0x01, // Report ID: 1
|
||||||
|
0x09, 0x01, // Usage: Pointer
|
||||||
|
0xA1, 0x00, // Collection: Physical
|
||||||
|
0x05, 0x09, // Usage Page: Button
|
||||||
|
0x19, 0x01, // Usage Minimum: 1
|
||||||
|
0x29, 0x03, // Usage Maximum: 3
|
||||||
|
0x15, 0x00, // Logical Minimum: 0
|
||||||
|
0x25, 0x01, // Logical Maximum: 1
|
||||||
|
0x95, 0x03, // Report Count: 3
|
||||||
|
0x75, 0x01, // Report Size: 1
|
||||||
|
0x81, 0x02, // Input: Data, Variable, Absolute (3 buttons)
|
||||||
|
0x95, 0x01, // Report Count: 1
|
||||||
|
0x75, 0x05, // Report Size: 5
|
||||||
|
0x81, 0x01, // Input: Constant, Array, Absolute (padding to byte boundary)
|
||||||
|
0x05, 0x01, // Usage Page: Generic Desktop
|
||||||
|
0x09, 0x30, // Usage: X
|
||||||
|
0x09, 0x31, // Usage: Y
|
||||||
|
0x09, 0x38, // Usage: Wheel
|
||||||
|
0x15, 0x81, // Logical Minimum: -127
|
||||||
|
0x25, 0x7F, // Logical Maximum: 127
|
||||||
|
0x75, 0x08, // Report Size: 8
|
||||||
|
0x95, 0x03, // Report Count: 3
|
||||||
|
0x81, 0x06, // Input: Data, Variable, Relative
|
||||||
|
0xC0, // End Collection (Physical)
|
||||||
|
0xC0, // End Collection (Application)
|
||||||
|
};
|
||||||
|
const size_t hid_report_map_mouse_len = sizeof(hid_report_map_mouse);
|
||||||
|
|
||||||
|
// Keyboard (report ID 1) + Consumer (report ID 2) + Mouse (report ID 3)
|
||||||
|
const uint8_t hid_report_map_keyboard_consumer_mouse[] = {
|
||||||
|
0x05, 0x01, // Usage Page: Generic Desktop
|
||||||
|
0x09, 0x06, // Usage: Keyboard
|
||||||
|
0xA1, 0x01, // Collection: Application
|
||||||
|
// --- Keyboard
|
||||||
|
0x85, 0x01, // Report ID: 1
|
||||||
|
0x05, 0x07, // Usage Page: Keyboard/Keypad
|
||||||
|
0x19, 0xE0, // Usage Minimum: Left Control
|
||||||
|
0x29, 0xE7, // Usage Maximum: Right GUI
|
||||||
|
0x15, 0x00, // Logical Minimum: 0
|
||||||
|
0x25, 0x01, // Logical Maximum: 1
|
||||||
|
0x75, 0x01, // Report Size: 1
|
||||||
|
0x95, 0x08, // Report Count: 8
|
||||||
|
0x81, 0x02, // Input: Data, Variable, Absolute (modifier byte)
|
||||||
|
0x75, 0x08, // Report Size: 8
|
||||||
|
0x95, 0x01, // Report Count: 1
|
||||||
|
0x81, 0x01, // Input: Constant, Array, Absolute (reserved byte)
|
||||||
|
0x05, 0x08, // Usage Page: LEDs
|
||||||
|
0x19, 0x01, // Usage Minimum: 0x01
|
||||||
|
0x29, 0x05, // Usage Maximum: 0x05
|
||||||
|
0x75, 0x01, // Report Size: 1
|
||||||
|
0x95, 0x05, // Report Count: 5
|
||||||
|
0x91, 0x02, // Output: Data, Variable, Absolute (LED state)
|
||||||
|
0x75, 0x03, // Report Size: 3
|
||||||
|
0x95, 0x01, // Report Count: 1
|
||||||
|
0x91, 0x01, // Output: Constant, Array, Absolute (LED padding)
|
||||||
|
0x15, 0x00, // Logical Minimum: 0
|
||||||
|
0x25, 0x73, // Logical Maximum: 115
|
||||||
|
0x05, 0x07, // Usage Page: Keyboard/Keypad
|
||||||
|
0x19, 0x00, // Usage Minimum: 0x00
|
||||||
|
0x29, 0x73, // Usage Maximum: 0x73
|
||||||
|
0x75, 0x08, // Report Size: 8
|
||||||
|
0x95, 0x06, // Report Count: 6
|
||||||
|
0x81, 0x00, // Input: Data, Array, Absolute (keycode[6])
|
||||||
|
0xC0, // End Collection
|
||||||
|
// --- Consumer / Media Keys
|
||||||
|
0x05, 0x0C, // Usage Page: Consumer
|
||||||
|
0x09, 0x01, // Usage: Consumer Control
|
||||||
|
0xA1, 0x01, // Collection: Application
|
||||||
|
0x85, 0x02, // Report ID: 2
|
||||||
|
0x15, 0x00, // Logical Minimum: 0
|
||||||
|
0x26, 0xFF, 0x03, // Logical Maximum: 1023
|
||||||
|
0x19, 0x00, // Usage Minimum: 0x00
|
||||||
|
0x2A, 0xFF, 0x03, // Usage Maximum: 0x3FF
|
||||||
|
0x75, 0x10, // Report Size: 16
|
||||||
|
0x95, 0x01, // Report Count: 1
|
||||||
|
0x81, 0x00, // Input: Data, Array, Absolute
|
||||||
|
0xC0, // End Collection
|
||||||
|
// --- Mouse
|
||||||
|
0x05, 0x01, // Usage Page: Generic Desktop
|
||||||
|
0x09, 0x02, // Usage: Mouse
|
||||||
|
0xA1, 0x01, // Collection: Application
|
||||||
|
0x85, 0x03, // Report ID: 3
|
||||||
|
0x09, 0x01, // Usage: Pointer
|
||||||
|
0xA1, 0x00, // Collection: Physical
|
||||||
|
0x05, 0x09, // Usage Page: Button
|
||||||
|
0x19, 0x01, // Usage Minimum: 1
|
||||||
|
0x29, 0x03, // Usage Maximum: 3
|
||||||
|
0x15, 0x00, // Logical Minimum: 0
|
||||||
|
0x25, 0x01, // Logical Maximum: 1
|
||||||
|
0x95, 0x03, // Report Count: 3
|
||||||
|
0x75, 0x01, // Report Size: 1
|
||||||
|
0x81, 0x02, // Input: Data, Variable, Absolute (3 buttons)
|
||||||
|
0x95, 0x01, // Report Count: 1
|
||||||
|
0x75, 0x05, // Report Size: 5
|
||||||
|
0x81, 0x01, // Input: Constant, Array, Absolute (padding to byte boundary)
|
||||||
|
0x05, 0x01, // Usage Page: Generic Desktop
|
||||||
|
0x09, 0x30, // Usage: X
|
||||||
|
0x09, 0x31, // Usage: Y
|
||||||
|
0x09, 0x38, // Usage: Wheel
|
||||||
|
0x15, 0x81, // Logical Minimum: -127
|
||||||
|
0x25, 0x7F, // Logical Maximum: 127
|
||||||
|
0x75, 0x08, // Report Size: 8
|
||||||
|
0x95, 0x03, // Report Count: 3
|
||||||
|
0x81, 0x06, // Input: Data, Variable, Relative
|
||||||
|
0xC0, // End Collection (Physical)
|
||||||
|
0xC0, // End Collection (Application)
|
||||||
|
};
|
||||||
|
const size_t hid_report_map_keyboard_consumer_mouse_len = sizeof(hid_report_map_keyboard_consumer_mouse);
|
||||||
|
|
||||||
|
// Gamepad only (report ID 1):
|
||||||
|
// left stick = X/Y, right stick = Rx/Ry, triggers share a single Z axis (LT = positive, RT = negative, centered = 0,),
|
||||||
|
// hat/dpad, 10 buttons (1=A 2=B 3=X 4=Y 5=LB 6=RB 7=Back/Select 8=Start 9=L3 10=R3).
|
||||||
|
// DirectInput assigns axis usages to lX/lY/lZ/lRx/lRy by USAGE TAG, not by declaration order, so X/Y/Rx/Ry/Z in that order (not X/Y/Z/Rx/Ry) is correct.
|
||||||
|
const uint8_t hid_report_map_gamepad[] = {
|
||||||
|
0x05, 0x01, // Usage Page: Generic Desktop
|
||||||
|
0x09, 0x05, // Usage: Game Pad
|
||||||
|
0xA1, 0x01, // Collection: Application
|
||||||
|
0x85, 0x01, // Report ID: 1
|
||||||
|
// --- 8-bit X, Y (left stick), Rx, Ry (right stick), Z (triggers, LT=+, RT=-) (min -127, max 127)
|
||||||
|
0x05, 0x01, // Usage Page: Generic Desktop
|
||||||
|
0x09, 0x30, // Usage: X
|
||||||
|
0x09, 0x31, // Usage: Y
|
||||||
|
0x09, 0x33, // Usage: Rx
|
||||||
|
0x09, 0x34, // Usage: Ry
|
||||||
|
0x09, 0x32, // Usage: Z
|
||||||
|
0x15, 0x81, // Logical Minimum: -127
|
||||||
|
0x25, 0x7F, // Logical Maximum: 127
|
||||||
|
0x75, 0x08, // Report Size: 8
|
||||||
|
0x95, 0x05, // Report Count: 5
|
||||||
|
0x81, 0x02, // Input: Data, Variable, Absolute
|
||||||
|
// --- 4-bit hat/dpad (0-7 = direction clockwise from Up, 8 = centered/released via Null State).
|
||||||
|
// Logical range must be 0-7 (not 1-8) and size must be a nibble (not a full byte) - both are required for DirectInput to recognize this as a POV.
|
||||||
|
0x09, 0x39, // Usage: Hat Switch
|
||||||
|
0x15, 0x00, // Logical Minimum: 0
|
||||||
|
0x25, 0x07, // Logical Maximum: 7
|
||||||
|
0x35, 0x00, // Physical Minimum: 0
|
||||||
|
0x46, 0x3B, 0x01, // Physical Maximum: 315
|
||||||
|
0x65, 0x14, // Unit: Eng Rot:Angular Pos (degrees)
|
||||||
|
0x75, 0x04, // Report Size: 4
|
||||||
|
0x95, 0x01, // Report Count: 1
|
||||||
|
0x81, 0x42, // Input: Data, Variable, Absolute, Null State (value 8 = centered/released, outside the declared 0-7 logical range)
|
||||||
|
0x65, 0x00, // Unit: None - resets the Unit global item so it doesn't leak onto the button/padding items below (Unit is global, not local, in HID).
|
||||||
|
0x75, 0x04, // Report Size: 4
|
||||||
|
0x95, 0x01, // Report Count: 1
|
||||||
|
0x81, 0x03, // Input: Constant, Variable, Absolute (pad hat nibble to a full byte)
|
||||||
|
// --- 10 one-bit buttons: 1=A 2=B 3=X 4=Y 5=LB 6=RB 7=Back/Select 8=Start 9=L3 10=R3
|
||||||
|
0x05, 0x09, // Usage Page: Button
|
||||||
|
0x19, 0x01, // Usage Minimum: 1
|
||||||
|
0x29, 0x0A, // Usage Maximum: 10
|
||||||
|
0x15, 0x00, // Logical Minimum: 0
|
||||||
|
0x25, 0x01, // Logical Maximum: 1
|
||||||
|
0x75, 0x01, // Report Size: 1
|
||||||
|
0x95, 0x0A, // Report Count: 10
|
||||||
|
0x81, 0x02, // Input: Data, Variable, Absolute
|
||||||
|
// --- 6-bit padding to byte-align the buttons field (10 bits -> 16 bits / 2 bytes)
|
||||||
|
0x75, 0x06, // Report Size: 6
|
||||||
|
0x95, 0x01, // Report Count: 1
|
||||||
|
0x81, 0x03, // Input: Constant, Variable, Absolute
|
||||||
|
0xC0, // End Collection
|
||||||
|
};
|
||||||
|
const size_t hid_report_map_gamepad_len = sizeof(hid_report_map_gamepad);
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
#include <sdkconfig.h>
|
||||||
|
#if CONFIG_SOC_USB_OTG_SUPPORTED && CONFIG_TINYUSB_CDC_ENABLED
|
||||||
|
|
||||||
|
#include <tactility/device.h>
|
||||||
|
#include <tactility/driver.h>
|
||||||
|
#include <tactility/drivers/esp32_usbdevice.h>
|
||||||
|
#include <tactility/drivers/usb_cdc_device.h>
|
||||||
|
#include <tactility/drivers/usb_device_controller.h>
|
||||||
|
#include <tactility/log.h>
|
||||||
|
|
||||||
|
#include <tinyusb.h>
|
||||||
|
#include <tusb_cdc_acm.h>
|
||||||
|
#include <esp_log.h>
|
||||||
|
|
||||||
|
#include <cstdarg>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#define TAG "esp32_usb_cdc_device"
|
||||||
|
#define GET_CONFIG(device) ((const Esp32UsbDeviceChildConfig*)(device)->config)
|
||||||
|
|
||||||
|
// ---- CDC device state ----
|
||||||
|
// A single presence flag, flipped by start_device()/stop_device() - matches how the USB device
|
||||||
|
// controller checks "is this board's usbdevicecdc0 child enabled" (usb_cdc_device_is_present()),
|
||||||
|
// independent of whether the console is actually installed right now (build_contribution()/
|
||||||
|
// start_console()/stop_console() are called by the controller, not tied to this device's own
|
||||||
|
// start/stop, since CDC's console lifecycle follows whichever primary claim is active - HID or
|
||||||
|
// MIDI; MSC is deliberately excluded, see esp32_usb_device_controller.cpp's claim()).
|
||||||
|
|
||||||
|
struct UsbCdcDeviceCtx {
|
||||||
|
bool present = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---- CDC console (log mirroring over the CDC ACM interface) ----
|
||||||
|
|
||||||
|
static bool cdc_console_installed = false;
|
||||||
|
static vprintf_like_t previous_vprintf = nullptr;
|
||||||
|
|
||||||
|
// Mirrors every log line to the CDC ACM interface in addition to whatever the log vprintf
|
||||||
|
// hook already does (UART0, ...) - deliberately additive rather than esp_tinyusb's own
|
||||||
|
// esp_tusb_init_console(), which freopen()s stdout/stderr and would replace the existing
|
||||||
|
// console instead of adding a second one alongside it.
|
||||||
|
static int cdc_mirroring_vprintf(const char* fmt, va_list args) {
|
||||||
|
// args must be copied before the first vprintf-family call consumes it - passing an
|
||||||
|
// already-consumed va_list to vsnprintf below is undefined behavior.
|
||||||
|
va_list cdc_args;
|
||||||
|
va_copy(cdc_args, args);
|
||||||
|
|
||||||
|
int result = previous_vprintf ? previous_vprintf(fmt, args) : vprintf(fmt, args);
|
||||||
|
|
||||||
|
if (tud_cdc_connected()) {
|
||||||
|
char buf[256];
|
||||||
|
int len = vsnprintf(buf, sizeof(buf), fmt, cdc_args);
|
||||||
|
if (len > 0) {
|
||||||
|
size_t to_write = static_cast<size_t>(len) < sizeof(buf) ? static_cast<size_t>(len) : sizeof(buf) - 1;
|
||||||
|
tinyusb_cdcacm_write_queue(TINYUSB_CDC_ACM_0, reinterpret_cast<const uint8_t*>(buf), to_write);
|
||||||
|
tinyusb_cdcacm_write_flush(TINYUSB_CDC_ACM_0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
va_end(cdc_args);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- CDC descriptor contribution ----
|
||||||
|
|
||||||
|
static char cdc_interface_string[32] = "Tactility Console";
|
||||||
|
static uint8_t cdc_descriptor_bytes[TUD_CDC_DESC_LEN]; // one CDC instance per board
|
||||||
|
|
||||||
|
// ---- CDC device API ----
|
||||||
|
|
||||||
|
static bool cdc_device_is_present(struct Device* device) {
|
||||||
|
auto* ctx = static_cast<UsbCdcDeviceCtx*>(device_get_driver_data(device));
|
||||||
|
return ctx != nullptr && ctx->present;
|
||||||
|
}
|
||||||
|
|
||||||
|
static error_t cdc_device_build_contribution(struct Device* device, struct Device* controller,
|
||||||
|
uint8_t interface_string_index,
|
||||||
|
struct UsbInterfaceContribution* out_contribution,
|
||||||
|
const char** out_interface_string) {
|
||||||
|
(void)device;
|
||||||
|
// TUD_CDC_DESCRIPTOR consumes 2 interfaces internally (control at itfnum, data at itfnum+1 -
|
||||||
|
// it emits its own IAD covering both, see usbd.h) - same shape as MIDI's 2-interface layout.
|
||||||
|
// Also needs 2 IN endpoints (notif + data-in) and 1 OUT (data-out), mirroring the old
|
||||||
|
// HID-composited layout (HID_EP_NOTIF/HID_EP_CDC_IN/HID_EP_CDC_OUT), just dynamically
|
||||||
|
// assigned now instead of fixed literals.
|
||||||
|
struct UsbInterfaceAllocation alloc;
|
||||||
|
error_t err = usb_device_controller_allocate_interfaces(controller, /*interface_count=*/2,
|
||||||
|
/*in_endpoint_count=*/2, /*out_endpoint_count=*/1, &alloc);
|
||||||
|
if (err != ERROR_NONE) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t ep_notif = alloc.first_in_endpoint;
|
||||||
|
const uint8_t ep_data_in = (uint8_t)(alloc.first_in_endpoint + 1);
|
||||||
|
const uint8_t ep_data_out = alloc.first_out_endpoint;
|
||||||
|
|
||||||
|
const uint8_t built[] = {
|
||||||
|
TUD_CDC_DESCRIPTOR(alloc.first_interface_number, interface_string_index,
|
||||||
|
ep_notif, 8, ep_data_out, ep_data_in, 64),
|
||||||
|
};
|
||||||
|
memcpy(cdc_descriptor_bytes, built, sizeof(built));
|
||||||
|
|
||||||
|
out_contribution->descriptor_bytes = cdc_descriptor_bytes;
|
||||||
|
out_contribution->descriptor_bytes_len = sizeof(cdc_descriptor_bytes);
|
||||||
|
out_contribution->interface_count = 2;
|
||||||
|
out_contribution->in_endpoint_count = 2;
|
||||||
|
out_contribution->out_endpoint_count = 1;
|
||||||
|
*out_interface_string = cdc_interface_string;
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static error_t cdc_device_start_console(struct Device* device) {
|
||||||
|
(void)device;
|
||||||
|
const tinyusb_config_cdcacm_t acm_cfg = {
|
||||||
|
.usb_dev = TINYUSB_USBDEV_0,
|
||||||
|
.cdc_port = TINYUSB_CDC_ACM_0,
|
||||||
|
.callback_rx = nullptr,
|
||||||
|
.callback_rx_wanted_char = nullptr,
|
||||||
|
.callback_line_state_changed = nullptr,
|
||||||
|
.callback_line_coding_changed = nullptr,
|
||||||
|
};
|
||||||
|
if (tusb_cdc_acm_init(&acm_cfg) != ESP_OK) {
|
||||||
|
LOG_E(TAG, "tusb_cdc_acm_init failed - CDC console unavailable");
|
||||||
|
return ERROR_RESOURCE;
|
||||||
|
}
|
||||||
|
previous_vprintf = esp_log_set_vprintf(cdc_mirroring_vprintf);
|
||||||
|
cdc_console_installed = true;
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static error_t cdc_device_stop_console(struct Device* device) {
|
||||||
|
(void)device;
|
||||||
|
if (!cdc_console_installed) {
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
esp_log_set_vprintf(previous_vprintf ? previous_vprintf : vprintf);
|
||||||
|
previous_vprintf = nullptr;
|
||||||
|
tusb_cdc_acm_deinit(TINYUSB_CDC_ACM_0);
|
||||||
|
cdc_console_installed = false;
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern const UsbCdcDeviceApi esp32_usb_cdc_device_api = {
|
||||||
|
.is_present = cdc_device_is_present,
|
||||||
|
.build_contribution = cdc_device_build_contribution,
|
||||||
|
.start_console = cdc_device_start_console,
|
||||||
|
.stop_console = cdc_device_stop_console,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---- Driver lifecycle ----
|
||||||
|
// Defined in each board's .dts as a child of usbdevice0 (usbdevicecdc0). Presence is boolean -
|
||||||
|
// this driver doesn't participate in usb_device_controller_claim()/release() itself; the
|
||||||
|
// controller discovers this device once via device_for_each_child() in its own start_device()
|
||||||
|
// and calls is_present()/build_contribution()/start_console()/stop_console() directly.
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
static error_t start_device(struct Device* device) {
|
||||||
|
(void)GET_CONFIG(device); // no configuration - placeholder only
|
||||||
|
auto* ctx = new UsbCdcDeviceCtx();
|
||||||
|
ctx->present = true;
|
||||||
|
device_set_driver_data(device, ctx);
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static error_t stop_device(struct Device* device) {
|
||||||
|
auto* ctx = static_cast<UsbCdcDeviceCtx*>(device_get_driver_data(device));
|
||||||
|
delete ctx;
|
||||||
|
device_set_driver_data(device, nullptr);
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
Driver esp32_usb_cdc_device_driver = {
|
||||||
|
.name = "esp32_usb_cdc_device",
|
||||||
|
.compatible = (const char*[]) { "espressif,esp32-usbdevice-cdc", nullptr },
|
||||||
|
.start_device = start_device,
|
||||||
|
.stop_device = stop_device,
|
||||||
|
.api = &esp32_usb_cdc_device_api,
|
||||||
|
.device_type = &USB_CDC_DEVICE_TYPE,
|
||||||
|
.owner = nullptr,
|
||||||
|
.internal = nullptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
|
|
||||||
|
#endif // CONFIG_SOC_USB_OTG_SUPPORTED && CONFIG_TINYUSB_CDC_ENABLED
|
||||||
@@ -0,0 +1,414 @@
|
|||||||
|
#include <sdkconfig.h>
|
||||||
|
#if CONFIG_SOC_USB_OTG_SUPPORTED && (CONFIG_TINYUSB_HID_COUNT || CONFIG_TINYUSB_MSC_ENABLED || CONFIG_TINYUSB_MIDI_COUNT || CONFIG_TINYUSB_CDC_ENABLED)
|
||||||
|
|
||||||
|
#include <tactility/device.h>
|
||||||
|
#include <tactility/driver.h>
|
||||||
|
#include <tactility/drivers/esp32_usbdevice.h>
|
||||||
|
#include <tactility/drivers/usb_cdc_device.h>
|
||||||
|
#include <tactility/drivers/usb_device_controller.h>
|
||||||
|
#include <tactility/log.h>
|
||||||
|
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
|
|
||||||
|
#include <tinyusb.h>
|
||||||
|
#include <tusb.h>
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32P4
|
||||||
|
#include <hal/usb_wrap_ll.h>
|
||||||
|
#include <soc/usb_wrap_struct.h>
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
#include <hal/usb_serial_jtag_ll.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define TAG "esp32_usb_device_controller"
|
||||||
|
#define GET_CONFIG(device) ((const Esp32UsbDeviceConfig*)(device)->config)
|
||||||
|
|
||||||
|
// Composite descriptor scratch buffer, sized for worst case (any one primary + CDC). Primary
|
||||||
|
// contributions today max out at MIDI's ~54 bytes (TUD_CONFIG_DESC_LEN + 2*9 + 2*9 + ...); 96
|
||||||
|
// leaves headroom without tracking each primary's exact size here.
|
||||||
|
static constexpr size_t COMPOSITE_CONFIG_DESCRIPTOR_MAX = TUD_CONFIG_DESC_LEN + 96 + TUD_CDC_DESC_LEN;
|
||||||
|
// Worst case: primary's own table (lang/mfr/product/serial/interface = 5) + CDC's own interface string.
|
||||||
|
static constexpr size_t COMPOSITE_STRING_DESCRIPTOR_MAX = 6;
|
||||||
|
|
||||||
|
// ---- Controller state ----
|
||||||
|
// One TinyUSB device-mode slot, shared by every USB device class (each a separate child device
|
||||||
|
// under usbdevice0 in the devicetree - see esp32_usb_hid_device.cpp / esp32_usb_device_msc.cpp /
|
||||||
|
// esp32_usb_midi_device.cpp). Only one primary class may be installed at a time - see
|
||||||
|
// UsbDeviceControllerApi::claim(). CDC (esp32_usb_cdc_device.cpp) is a separate, orthogonal
|
||||||
|
// devicetree-presence addon composited into whichever primary is active (HID or MIDI; MSC is
|
||||||
|
// deliberately excluded, see claim()'s cdc_enabled computation) - see claim() below.
|
||||||
|
|
||||||
|
struct UsbDeviceControllerCtx {
|
||||||
|
enum UsbDeviceClass active_class = USB_DEVICE_CLASS_NONE;
|
||||||
|
bool phy_routed = false;
|
||||||
|
bool cdc_console_started = false;
|
||||||
|
|
||||||
|
// Allocation state, live from begin_claim() through claim()'s call to tinyusb_driver_install()
|
||||||
|
// (or until the claim attempt is abandoned by a caller that never follows through with claim()).
|
||||||
|
bool allocation_open = false;
|
||||||
|
uint8_t next_interface = 0;
|
||||||
|
uint8_t next_in_endpoint = 1; // EP0 reserved
|
||||||
|
uint8_t next_out_endpoint = 1;
|
||||||
|
|
||||||
|
// Composite descriptor scratch, rebuilt on every claim() - can't be `static const` per-class
|
||||||
|
// anymore now that CDC's presence is a runtime devicetree fact, not compile-time. Two buffers
|
||||||
|
// since a primary (MSC) may need different bytes per speed (see UsbInterfaceContribution's
|
||||||
|
// hs_descriptor_bytes) - under !TUD_OPT_HIGH_SPEED only the fs buffer is ever used.
|
||||||
|
uint8_t composite_fs_config_descriptor[COMPOSITE_CONFIG_DESCRIPTOR_MAX];
|
||||||
|
#if (TUD_OPT_HIGH_SPEED)
|
||||||
|
uint8_t composite_hs_config_descriptor[COMPOSITE_CONFIG_DESCRIPTOR_MAX];
|
||||||
|
#endif
|
||||||
|
const char* composite_string_descriptor[COMPOSITE_STRING_DESCRIPTOR_MAX];
|
||||||
|
tusb_desc_device_t composite_device_descriptor;
|
||||||
|
#if (TUD_OPT_HIGH_SPEED)
|
||||||
|
tusb_desc_device_qualifier_t composite_device_qualifier;
|
||||||
|
#endif
|
||||||
|
tinyusb_config_t composite_tusb_cfg;
|
||||||
|
|
||||||
|
struct Device* cdc_child = nullptr; // resolved once in start_device(); nullptr if no usbdevicecdc0 node
|
||||||
|
};
|
||||||
|
|
||||||
|
static void route_phy_for_device_mode() {
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32P4
|
||||||
|
// Tab5's USB-C is wired to ESP32-P4 FSLS PHY0 (GPIO24/25). ESP-IDF's default USB_WRAP
|
||||||
|
// route uses FSLS PHY1 (GPIO26/27), so switch it here before installing TinyUSB.
|
||||||
|
usb_wrap_ll_phy_select(&USB_WRAP, 0);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void restore_default_phy_route() {
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32P4
|
||||||
|
usb_wrap_ll_phy_select(&USB_WRAP, 1);
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
// S2/S3 share one FSLS PHY between USB-OTG and USB-Serial-JTAG. esp_tinyusb's teardown
|
||||||
|
// leaves the PHY muxed to USB-OTG \afterward, so the native console never comes back
|
||||||
|
// without this.
|
||||||
|
//
|
||||||
|
// \afterward ESP-IDF's usb_del_phy() (called from tinyusb_driver_uninstall() above).
|
||||||
|
usb_serial_jtag_ll_phy_enable_external(false);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool find_cdc_child(struct Device* child, void* context) {
|
||||||
|
if (device_get_type(child) == &USB_CDC_DEVICE_TYPE) {
|
||||||
|
*static_cast<struct Device**>(context) = child;
|
||||||
|
return false; // stop iterating
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Devicetree children are constructed/added/started strictly after their parent
|
||||||
|
// (kernel_init.cpp's dts_devices[] loop starts each device in list order, parent first) - so the
|
||||||
|
// usbdevicecdc0 child does not exist yet when this controller's own start_device() runs.
|
||||||
|
// Discovering it lazily here (called from is_cdc_enabled(), well after all devicetree devices
|
||||||
|
// have finished starting) instead of once at start_device() time is the only way to actually see
|
||||||
|
// it. Cheap to re-scan every call: at most one child of this type per board.
|
||||||
|
static struct Device* find_cdc_child_lazy(struct Device* device, struct UsbDeviceControllerCtx* ctx) {
|
||||||
|
if (ctx->cdc_child == nullptr) {
|
||||||
|
device_for_each_child(device, &ctx->cdc_child, find_cdc_child);
|
||||||
|
}
|
||||||
|
return ctx->cdc_child;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Controller API ----
|
||||||
|
|
||||||
|
static error_t begin_claim(struct Device* device) {
|
||||||
|
auto* ctx = static_cast<UsbDeviceControllerCtx*>(device_get_driver_data(device));
|
||||||
|
if (ctx->active_class != USB_DEVICE_CLASS_NONE) {
|
||||||
|
LOG_E(TAG, "begin_claim: slot busy (active_class=%d)", ctx->active_class);
|
||||||
|
return ERROR_RESOURCE_BUSY;
|
||||||
|
}
|
||||||
|
ctx->allocation_open = true;
|
||||||
|
ctx->next_interface = 0;
|
||||||
|
ctx->next_in_endpoint = 1;
|
||||||
|
ctx->next_out_endpoint = 1;
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static error_t allocate_interfaces(struct Device* device, uint8_t interface_count,
|
||||||
|
uint8_t in_endpoint_count, uint8_t out_endpoint_count,
|
||||||
|
struct UsbInterfaceAllocation* out_allocation) {
|
||||||
|
auto* ctx = static_cast<UsbDeviceControllerCtx*>(device_get_driver_data(device));
|
||||||
|
if (!ctx->allocation_open) {
|
||||||
|
LOG_E(TAG, "allocate_interfaces: called without a preceding begin_claim()");
|
||||||
|
return ERROR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
out_allocation->first_interface_number = ctx->next_interface;
|
||||||
|
out_allocation->first_in_endpoint = in_endpoint_count > 0 ? (uint8_t)(0x80 | ctx->next_in_endpoint) : 0;
|
||||||
|
out_allocation->first_out_endpoint = out_endpoint_count > 0 ? ctx->next_out_endpoint : 0;
|
||||||
|
|
||||||
|
ctx->next_interface = (uint8_t)(ctx->next_interface + interface_count);
|
||||||
|
ctx->next_in_endpoint = (uint8_t)(ctx->next_in_endpoint + in_endpoint_count);
|
||||||
|
ctx->next_out_endpoint = (uint8_t)(ctx->next_out_endpoint + out_endpoint_count);
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_cdc_enabled(struct Device* device) {
|
||||||
|
auto* ctx = static_cast<UsbDeviceControllerCtx*>(device_get_driver_data(device));
|
||||||
|
struct Device* cdc_child = find_cdc_child_lazy(device, ctx);
|
||||||
|
return cdc_child != nullptr && usb_cdc_device_is_present(cdc_child);
|
||||||
|
}
|
||||||
|
|
||||||
|
static error_t claim(struct Device* device, enum UsbDeviceClass usb_class, const struct UsbDeviceClaimConfig* config) {
|
||||||
|
auto* ctx = static_cast<UsbDeviceControllerCtx*>(device_get_driver_data(device));
|
||||||
|
|
||||||
|
if (ctx->active_class != USB_DEVICE_CLASS_NONE) {
|
||||||
|
LOG_E(TAG, "claim: slot busy (active_class=%d, requested=%d)", ctx->active_class, usb_class);
|
||||||
|
return ERROR_RESOURCE_BUSY;
|
||||||
|
}
|
||||||
|
if (config == nullptr || config->device_descriptor == nullptr || config->primary.descriptor_bytes == nullptr) {
|
||||||
|
return ERROR_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
if (!ctx->allocation_open) {
|
||||||
|
LOG_E(TAG, "claim: no begin_claim()/allocate_interfaces() session in progress");
|
||||||
|
return ERROR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MSC is deliberately excluded from CDC compositing - a MSC-presenting device is meant to
|
||||||
|
// look/behave like plain mass storage to the host's storage stack, and there's no real use
|
||||||
|
// case for a console interface while acting as a flash drive, unlike HID/MIDI which
|
||||||
|
// plausibly want a console alongside. (A Windows safe-eject hang was seen during development
|
||||||
|
// of this compositing code but traced to a stale Windows-side driver/device-cache state,
|
||||||
|
// cleared by a host reboot - not caused by CDC compositing or this exclusion. Kept the
|
||||||
|
// exclusion anyway since it matches MSC's actual intended behavior.)
|
||||||
|
const bool cdc_enabled = usb_class != USB_DEVICE_CLASS_MSC && is_cdc_enabled(device);
|
||||||
|
|
||||||
|
// ---- String table: primary's table, plus CDC's own interface string appended last.
|
||||||
|
size_t string_count = config->string_descriptor_count;
|
||||||
|
if (string_count > COMPOSITE_STRING_DESCRIPTOR_MAX) {
|
||||||
|
ctx->allocation_open = false;
|
||||||
|
LOG_E(TAG, "claim: primary string table too large (%u > %u)", (unsigned)string_count, (unsigned)COMPOSITE_STRING_DESCRIPTOR_MAX);
|
||||||
|
return ERROR_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
memcpy(ctx->composite_string_descriptor, config->string_descriptor, string_count * sizeof(const char*));
|
||||||
|
|
||||||
|
struct UsbInterfaceContribution cdc_contribution = {};
|
||||||
|
if (cdc_enabled) {
|
||||||
|
const char* cdc_interface_string = nullptr;
|
||||||
|
if (string_count >= COMPOSITE_STRING_DESCRIPTOR_MAX) {
|
||||||
|
ctx->allocation_open = false;
|
||||||
|
LOG_E(TAG, "claim: no room for CDC's interface string");
|
||||||
|
return ERROR_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
error_t err = usb_cdc_device_build_contribution(ctx->cdc_child, device, (uint8_t)string_count,
|
||||||
|
&cdc_contribution, &cdc_interface_string);
|
||||||
|
if (err != ERROR_NONE) {
|
||||||
|
ctx->allocation_open = false;
|
||||||
|
LOG_E(TAG, "claim: CDC build_contribution failed");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
ctx->composite_string_descriptor[string_count] = cdc_interface_string;
|
||||||
|
string_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Descriptor byte assembly: primary's bytes, then CDC's (if enabled), behind one config
|
||||||
|
// header. Built twice (fs/hs) under TUD_OPT_HIGH_SPEED if the primary supplied distinct
|
||||||
|
// high-speed bytes (see UsbInterfaceContribution::hs_descriptor_bytes) - CDC never varies by
|
||||||
|
// speed, so its bytes are reused verbatim in both buffers.
|
||||||
|
auto assemble = [&](uint8_t* dest, size_t dest_capacity, const uint8_t* primary_bytes, size_t primary_len) -> error_t {
|
||||||
|
const size_t total_len = TUD_CONFIG_DESC_LEN + primary_len + (cdc_enabled ? cdc_contribution.descriptor_bytes_len : 0);
|
||||||
|
if (total_len > dest_capacity) {
|
||||||
|
LOG_E(TAG, "claim: composite descriptor too large (%u > %u)", (unsigned)total_len, (unsigned)dest_capacity);
|
||||||
|
return ERROR_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
const uint8_t config_header[] = {
|
||||||
|
TUD_CONFIG_DESCRIPTOR(1, ctx->next_interface, 0, total_len, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
|
||||||
|
};
|
||||||
|
uint8_t* p = dest;
|
||||||
|
memcpy(p, config_header, sizeof(config_header));
|
||||||
|
p += sizeof(config_header);
|
||||||
|
memcpy(p, primary_bytes, primary_len);
|
||||||
|
p += primary_len;
|
||||||
|
if (cdc_enabled) {
|
||||||
|
memcpy(p, cdc_contribution.descriptor_bytes, cdc_contribution.descriptor_bytes_len);
|
||||||
|
}
|
||||||
|
return ERROR_NONE;
|
||||||
|
};
|
||||||
|
|
||||||
|
error_t assemble_err = assemble(ctx->composite_fs_config_descriptor, sizeof(ctx->composite_fs_config_descriptor),
|
||||||
|
config->primary.descriptor_bytes, config->primary.descriptor_bytes_len);
|
||||||
|
if (assemble_err != ERROR_NONE) {
|
||||||
|
ctx->allocation_open = false;
|
||||||
|
return assemble_err;
|
||||||
|
}
|
||||||
|
#if (TUD_OPT_HIGH_SPEED)
|
||||||
|
const uint8_t* hs_primary_bytes = config->primary.hs_descriptor_bytes != nullptr
|
||||||
|
? config->primary.hs_descriptor_bytes : config->primary.descriptor_bytes;
|
||||||
|
const size_t hs_primary_len = config->primary.hs_descriptor_bytes != nullptr
|
||||||
|
? config->primary.hs_descriptor_bytes_len : config->primary.descriptor_bytes_len;
|
||||||
|
assemble_err = assemble(ctx->composite_hs_config_descriptor, sizeof(ctx->composite_hs_config_descriptor),
|
||||||
|
hs_primary_bytes, hs_primary_len);
|
||||||
|
if (assemble_err != ERROR_NONE) {
|
||||||
|
ctx->allocation_open = false;
|
||||||
|
return assemble_err;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ctx->allocation_open = false;
|
||||||
|
|
||||||
|
// ---- Device descriptor: primary's metadata (idVendor/idProduct/strings), controller decides
|
||||||
|
// the class triad since CDC's presence (not the primary's identity) is what needs IAD in the
|
||||||
|
// general case - except MSC, which always got MISC/IAD unconditionally even standalone in
|
||||||
|
// the pre-refactor code (see esp32_usb_device_msc.cpp history). Kept that behavior verbatim
|
||||||
|
// for MSC (regardless of cdc_enabled, which is always false for MSC anyway - see the
|
||||||
|
// cdc_enabled computation above) simply to match what shipped before rather than introduce
|
||||||
|
// an unrelated behavior change while separating CDC out of HID's descriptor.
|
||||||
|
ctx->composite_device_descriptor = *static_cast<const tusb_desc_device_t*>(config->device_descriptor);
|
||||||
|
if (cdc_enabled || usb_class == USB_DEVICE_CLASS_MSC) {
|
||||||
|
ctx->composite_device_descriptor.bDeviceClass = TUSB_CLASS_MISC;
|
||||||
|
ctx->composite_device_descriptor.bDeviceSubClass = MISC_SUBCLASS_COMMON;
|
||||||
|
ctx->composite_device_descriptor.bDeviceProtocol = MISC_PROTOCOL_IAD;
|
||||||
|
} else {
|
||||||
|
ctx->composite_device_descriptor.bDeviceClass = TUSB_CLASS_UNSPECIFIED;
|
||||||
|
ctx->composite_device_descriptor.bDeviceSubClass = 0x00;
|
||||||
|
ctx->composite_device_descriptor.bDeviceProtocol = 0x00;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->composite_tusb_cfg = {};
|
||||||
|
ctx->composite_tusb_cfg.device_descriptor = &ctx->composite_device_descriptor;
|
||||||
|
ctx->composite_tusb_cfg.string_descriptor = ctx->composite_string_descriptor;
|
||||||
|
ctx->composite_tusb_cfg.string_descriptor_count = string_count;
|
||||||
|
ctx->composite_tusb_cfg.external_phy = false;
|
||||||
|
#if (TUD_OPT_HIGH_SPEED)
|
||||||
|
ctx->composite_tusb_cfg.fs_configuration_descriptor = ctx->composite_fs_config_descriptor;
|
||||||
|
ctx->composite_tusb_cfg.hs_configuration_descriptor = ctx->composite_hs_config_descriptor;
|
||||||
|
// The qualifier descriptor's class triad must mirror the device descriptor's (same IAD
|
||||||
|
// reasoning) - built here rather than supplied per-primary since its content is boilerplate
|
||||||
|
// (same bMaxPacketSize0/bNumConfigurations for every class) and the controller already knows
|
||||||
|
// the class triad.
|
||||||
|
ctx->composite_device_qualifier = {
|
||||||
|
.bLength = sizeof(tusb_desc_device_qualifier_t),
|
||||||
|
.bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER,
|
||||||
|
.bcdUSB = 0x0200,
|
||||||
|
.bDeviceClass = ctx->composite_device_descriptor.bDeviceClass,
|
||||||
|
.bDeviceSubClass = ctx->composite_device_descriptor.bDeviceSubClass,
|
||||||
|
.bDeviceProtocol = ctx->composite_device_descriptor.bDeviceProtocol,
|
||||||
|
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
|
||||||
|
.bNumConfigurations = 0x01,
|
||||||
|
.bReserved = 0x00,
|
||||||
|
};
|
||||||
|
ctx->composite_tusb_cfg.qualifier_descriptor = &ctx->composite_device_qualifier;
|
||||||
|
#else
|
||||||
|
ctx->composite_tusb_cfg.configuration_descriptor = ctx->composite_fs_config_descriptor;
|
||||||
|
#endif
|
||||||
|
ctx->composite_tusb_cfg.self_powered = false;
|
||||||
|
ctx->composite_tusb_cfg.vbus_monitor_io = 0;
|
||||||
|
|
||||||
|
route_phy_for_device_mode();
|
||||||
|
ctx->phy_routed = true;
|
||||||
|
|
||||||
|
if (tinyusb_driver_install(&ctx->composite_tusb_cfg) != ESP_OK) {
|
||||||
|
LOG_E(TAG, "claim: tinyusb_driver_install failed for class %d", usb_class);
|
||||||
|
if (ctx->phy_routed) {
|
||||||
|
restore_default_phy_route();
|
||||||
|
ctx->phy_routed = false;
|
||||||
|
}
|
||||||
|
return ERROR_RESOURCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->active_class = usb_class;
|
||||||
|
|
||||||
|
if (cdc_enabled) {
|
||||||
|
if (usb_cdc_device_start_console(ctx->cdc_child) == ERROR_NONE) {
|
||||||
|
ctx->cdc_console_started = true;
|
||||||
|
} else {
|
||||||
|
LOG_E(TAG, "claim: CDC start_console failed - continuing without console");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static error_t release(struct Device* device, enum UsbDeviceClass usb_class) {
|
||||||
|
auto* ctx = static_cast<UsbDeviceControllerCtx*>(device_get_driver_data(device));
|
||||||
|
|
||||||
|
if (ctx->active_class != usb_class) {
|
||||||
|
LOG_E(TAG, "release: class %d does not hold the slot (active=%d)", usb_class, ctx->active_class);
|
||||||
|
return ERROR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->cdc_console_started) {
|
||||||
|
usb_cdc_device_stop_console(ctx->cdc_child);
|
||||||
|
ctx->cdc_console_started = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signal disconnect before tearing down so the host notices promptly.
|
||||||
|
tud_disconnect();
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(250));
|
||||||
|
|
||||||
|
tinyusb_driver_uninstall();
|
||||||
|
|
||||||
|
if (ctx->phy_routed) {
|
||||||
|
restore_default_phy_route();
|
||||||
|
ctx->phy_routed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->active_class = USB_DEVICE_CLASS_NONE;
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static enum UsbDeviceClass get_active_class(struct Device* device) {
|
||||||
|
auto* ctx = static_cast<UsbDeviceControllerCtx*>(device_get_driver_data(device));
|
||||||
|
return ctx->active_class;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern const UsbDeviceControllerApi usb_device_controller_api = {
|
||||||
|
.begin_claim = begin_claim,
|
||||||
|
.allocate_interfaces = allocate_interfaces,
|
||||||
|
.claim = claim,
|
||||||
|
.release = release,
|
||||||
|
.get_active_class = get_active_class,
|
||||||
|
.is_cdc_enabled = is_cdc_enabled,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---- Driver lifecycle ----
|
||||||
|
// This device is defined in each board's .dts (usbdevice0), same pattern as usbhost0 - its
|
||||||
|
// child classes (usbdevicehid0, usbdevicemsc0, usbdevicemidi0, usbdevicecdc0) are separate .dts
|
||||||
|
// nodes with their own drivers, wired to this device as their parent by the devicetree compiler.
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
static error_t start_device(struct Device* device) {
|
||||||
|
(void)GET_CONFIG(device); // no configuration - placeholder only
|
||||||
|
auto* ctx = new UsbDeviceControllerCtx();
|
||||||
|
device_set_driver_data(device, ctx);
|
||||||
|
// cdc_child is NOT resolved here: usbdevicecdc0 (a child of this device in the devicetree)
|
||||||
|
// hasn't been constructed/started yet at this point - kernel_init.cpp starts devicetree
|
||||||
|
// devices in list order, parent before children. Resolved lazily instead, see
|
||||||
|
// find_cdc_child_lazy() / is_cdc_enabled().
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static error_t stop_device(struct Device* device) {
|
||||||
|
auto* ctx = static_cast<UsbDeviceControllerCtx*>(device_get_driver_data(device));
|
||||||
|
// Safety cleanup: if a class still holds the slot (e.g. this device is stopped while HID/
|
||||||
|
// MSC/MIDI is still active), tear TinyUSB down and restore the PHY route before freeing ctx -
|
||||||
|
// otherwise the installed TinyUSB driver and mis-routed PHY would outlive the context that
|
||||||
|
// tracks them. Mirrors the same safety-release pattern each child driver's own stop_device
|
||||||
|
// uses (see esp32_usb_hid_device.cpp / esp32_usb_device_msc.cpp / esp32_usb_midi_device.cpp).
|
||||||
|
if (ctx->active_class != USB_DEVICE_CLASS_NONE) {
|
||||||
|
release(device, ctx->active_class);
|
||||||
|
}
|
||||||
|
delete ctx;
|
||||||
|
device_set_driver_data(device, nullptr);
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
Driver esp32_usb_device_controller_driver = {
|
||||||
|
.name = "esp32_usb_device_controller",
|
||||||
|
.compatible = (const char*[]) { "espressif,esp32-usbdevice", nullptr },
|
||||||
|
.start_device = start_device,
|
||||||
|
.stop_device = stop_device,
|
||||||
|
.api = &usb_device_controller_api,
|
||||||
|
.device_type = &USB_DEVICE_CONTROLLER_TYPE,
|
||||||
|
.owner = nullptr,
|
||||||
|
.internal = nullptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
|
|
||||||
|
#endif // CONFIG_SOC_USB_OTG_SUPPORTED && (CONFIG_TINYUSB_HID_COUNT || CONFIG_TINYUSB_MSC_ENABLED || CONFIG_TINYUSB_MIDI_COUNT)
|
||||||
@@ -0,0 +1,250 @@
|
|||||||
|
#include <sdkconfig.h>
|
||||||
|
#if CONFIG_SOC_USB_OTG_SUPPORTED && CONFIG_TINYUSB_MSC_ENABLED
|
||||||
|
|
||||||
|
#include <tactility/device.h>
|
||||||
|
#include <tactility/driver.h>
|
||||||
|
#include <tactility/drivers/esp32_usbdevice.h>
|
||||||
|
#include <tactility/drivers/usb_device_controller.h>
|
||||||
|
#include <tactility/drivers/usb_msc_device.h>
|
||||||
|
#include <tactility/log.h>
|
||||||
|
|
||||||
|
#include <tinyusb.h>
|
||||||
|
#include <tusb_msc_storage.h>
|
||||||
|
#include <wear_levelling.h>
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#define TAG "esp32_usb_device_msc"
|
||||||
|
#define GET_CONFIG(device) ((const Esp32UsbDeviceChildConfig*)(device)->config)
|
||||||
|
|
||||||
|
// ---- MSC device descriptor set ----
|
||||||
|
|
||||||
|
// Mutable, matching the other primary classes' descriptors, even though MSC's own
|
||||||
|
// bDeviceClass/SubClass/Protocol below are never actually rewritten by the controller - MSC is
|
||||||
|
// excluded from CDC compositing (see esp32_usb_device_controller.cpp's claim(), cdc_enabled
|
||||||
|
// computation) and always keeps its own unconditional MISC/IAD triad regardless.
|
||||||
|
static tusb_desc_device_t msc_device_descriptor = {
|
||||||
|
.bLength = sizeof(tusb_desc_device_t),
|
||||||
|
.bDescriptorType = TUSB_DESC_DEVICE,
|
||||||
|
.bcdUSB = 0x0200,
|
||||||
|
.bDeviceClass = TUSB_CLASS_MISC,
|
||||||
|
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
|
||||||
|
.bDeviceProtocol = MISC_PROTOCOL_IAD,
|
||||||
|
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
|
||||||
|
.idVendor = 0x303A, // Espressif VID
|
||||||
|
.idProduct = 0x4002,
|
||||||
|
.bcdDevice = 0x0100,
|
||||||
|
.iManufacturer = 0x01,
|
||||||
|
.iProduct = 0x02,
|
||||||
|
.iSerialNumber = 0x03,
|
||||||
|
.bNumConfigurations = 0x01,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char* msc_string_descriptor[] = {
|
||||||
|
(const char[]) { 0x09, 0x04 }, // 0: English (0x0409)
|
||||||
|
"Tactility", // 1: Manufacturer
|
||||||
|
"Tactility Device", // 2: Product
|
||||||
|
"42", // 3: Serial
|
||||||
|
"Tactility Mass Storage", // 4: MSC
|
||||||
|
};
|
||||||
|
|
||||||
|
// Interface/endpoint numbers here are the values usb_device_controller_allocate_interfaces()
|
||||||
|
// always returns for MSC in practice (MSC is always the first/only allocation for itself, per
|
||||||
|
// the controller's fixed primary-first ordering) - still requested via allocate_interfaces() at
|
||||||
|
// claim time rather than assumed, so this stays correct if that ordering ever changes.
|
||||||
|
static uint8_t msc_fs_configuration_descriptor[TUD_MSC_DESC_LEN];
|
||||||
|
#if (TUD_OPT_HIGH_SPEED)
|
||||||
|
static uint8_t msc_hs_configuration_descriptor[TUD_MSC_DESC_LEN];
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ---- MSC device state ----
|
||||||
|
|
||||||
|
struct UsbMscDeviceCtx {
|
||||||
|
UsbMscDeviceMountChangedCallback mount_changed_cb = nullptr;
|
||||||
|
void* mount_changed_context = nullptr;
|
||||||
|
bool storage_active = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// device pointer isn't threaded through the TinyUSB mount-changed callback, so this driver
|
||||||
|
// supports a single active MSC device instance at a time - matches the single TinyUSB
|
||||||
|
// device-mode slot the controller already enforces.
|
||||||
|
static struct Device* active_msc_device = nullptr;
|
||||||
|
|
||||||
|
static void storage_mount_changed_cb(tinyusb_msc_event_t* event) {
|
||||||
|
if (active_msc_device == nullptr) return;
|
||||||
|
auto* ctx = static_cast<UsbMscDeviceCtx*>(device_get_driver_data(active_msc_device));
|
||||||
|
if (ctx == nullptr) return;
|
||||||
|
|
||||||
|
const bool mounted = event->mount_changed_data.is_mounted;
|
||||||
|
LOG_I(TAG, "%s", mounted ? "MSC mounted" : "MSC unmounted");
|
||||||
|
if (ctx->mount_changed_cb != nullptr) {
|
||||||
|
ctx->mount_changed_cb(mounted, ctx->mount_changed_context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- MSC device API ----
|
||||||
|
|
||||||
|
static error_t msc_device_start(struct Device* device, enum UsbMscDeviceSource source, void* source_handle,
|
||||||
|
UsbMscDeviceMountChangedCallback mount_changed_cb, void* context) {
|
||||||
|
if (source_handle == nullptr) {
|
||||||
|
return ERROR_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* controller = device_get_parent(device);
|
||||||
|
|
||||||
|
error_t begin_result = usb_device_controller_begin_claim(controller);
|
||||||
|
if (begin_result != ERROR_NONE) {
|
||||||
|
return begin_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UsbInterfaceAllocation alloc;
|
||||||
|
error_t alloc_result = usb_device_controller_allocate_interfaces(controller, /*interface_count=*/1,
|
||||||
|
/*in_endpoint_count=*/1, /*out_endpoint_count=*/1, &alloc);
|
||||||
|
if (alloc_result != ERROR_NONE) {
|
||||||
|
return alloc_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t msc_fs_bytes[] = {
|
||||||
|
TUD_MSC_DESCRIPTOR(alloc.first_interface_number, 4, alloc.first_out_endpoint, alloc.first_in_endpoint, 64),
|
||||||
|
};
|
||||||
|
memcpy(msc_fs_configuration_descriptor, msc_fs_bytes, sizeof(msc_fs_bytes));
|
||||||
|
#if (TUD_OPT_HIGH_SPEED)
|
||||||
|
const uint8_t msc_hs_bytes[] = {
|
||||||
|
TUD_MSC_DESCRIPTOR(alloc.first_interface_number, 4, alloc.first_out_endpoint, alloc.first_in_endpoint, 512),
|
||||||
|
};
|
||||||
|
memcpy(msc_hs_configuration_descriptor, msc_hs_bytes, sizeof(msc_hs_bytes));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct UsbDeviceClaimConfig claim_config = {};
|
||||||
|
claim_config.device_descriptor = &msc_device_descriptor;
|
||||||
|
claim_config.string_descriptor = msc_string_descriptor;
|
||||||
|
claim_config.string_descriptor_count = sizeof(msc_string_descriptor) / sizeof(msc_string_descriptor[0]);
|
||||||
|
claim_config.primary.descriptor_bytes = msc_fs_configuration_descriptor;
|
||||||
|
claim_config.primary.descriptor_bytes_len = sizeof(msc_fs_configuration_descriptor);
|
||||||
|
#if (TUD_OPT_HIGH_SPEED)
|
||||||
|
claim_config.primary.hs_descriptor_bytes = msc_hs_configuration_descriptor;
|
||||||
|
claim_config.primary.hs_descriptor_bytes_len = sizeof(msc_hs_configuration_descriptor);
|
||||||
|
#endif
|
||||||
|
claim_config.primary.interface_count = 1;
|
||||||
|
claim_config.primary.in_endpoint_count = 1;
|
||||||
|
claim_config.primary.out_endpoint_count = 1;
|
||||||
|
|
||||||
|
error_t claim_result = usb_device_controller_claim(controller, USB_DEVICE_CLASS_MSC, &claim_config);
|
||||||
|
if (claim_result != ERROR_NONE) {
|
||||||
|
return claim_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* ctx = static_cast<UsbMscDeviceCtx*>(device_get_driver_data(device));
|
||||||
|
ctx->mount_changed_cb = mount_changed_cb;
|
||||||
|
ctx->mount_changed_context = context;
|
||||||
|
active_msc_device = device;
|
||||||
|
|
||||||
|
esp_err_t result;
|
||||||
|
if (source == USB_MSC_DEVICE_SOURCE_SDMMC) {
|
||||||
|
const tinyusb_msc_sdmmc_config_t config_sdmmc = {
|
||||||
|
.card = static_cast<sdmmc_card_t*>(source_handle),
|
||||||
|
.callback_mount_changed = storage_mount_changed_cb,
|
||||||
|
.callback_premount_changed = nullptr,
|
||||||
|
.mount_config = {
|
||||||
|
.format_if_mount_failed = false,
|
||||||
|
.max_files = 5,
|
||||||
|
.allocation_unit_size = 0,
|
||||||
|
.disk_status_check_enable = false,
|
||||||
|
.use_one_fat = false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
result = tinyusb_msc_storage_init_sdmmc(&config_sdmmc);
|
||||||
|
} else {
|
||||||
|
const tinyusb_msc_spiflash_config_t config_flash = {
|
||||||
|
.wl_handle = *static_cast<wl_handle_t*>(source_handle),
|
||||||
|
.callback_mount_changed = storage_mount_changed_cb,
|
||||||
|
.callback_premount_changed = nullptr,
|
||||||
|
.mount_config = {
|
||||||
|
.format_if_mount_failed = false,
|
||||||
|
.max_files = 5,
|
||||||
|
.allocation_unit_size = 0,
|
||||||
|
.disk_status_check_enable = false,
|
||||||
|
.use_one_fat = false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
result = tinyusb_msc_storage_init_spiflash(&config_flash);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result != ESP_OK) {
|
||||||
|
LOG_E(TAG, "storage init failed: %s", esp_err_to_name(result));
|
||||||
|
active_msc_device = nullptr;
|
||||||
|
usb_device_controller_release(controller, USB_DEVICE_CLASS_MSC);
|
||||||
|
return ERROR_RESOURCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->storage_active = true;
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static error_t msc_device_stop(struct Device* device) {
|
||||||
|
auto* ctx = static_cast<UsbMscDeviceCtx*>(device_get_driver_data(device));
|
||||||
|
if (ctx == nullptr || !ctx->storage_active) {
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
tinyusb_msc_storage_deinit();
|
||||||
|
|
||||||
|
auto* controller = device_get_parent(device);
|
||||||
|
usb_device_controller_release(controller, USB_DEVICE_CLASS_MSC);
|
||||||
|
|
||||||
|
ctx->storage_active = false;
|
||||||
|
ctx->mount_changed_cb = nullptr;
|
||||||
|
ctx->mount_changed_context = nullptr;
|
||||||
|
if (active_msc_device == device) {
|
||||||
|
active_msc_device = nullptr;
|
||||||
|
}
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool msc_device_is_connected(struct Device* device) {
|
||||||
|
auto* ctx = static_cast<UsbMscDeviceCtx*>(device_get_driver_data(device));
|
||||||
|
return ctx != nullptr && ctx->storage_active && tud_mounted();
|
||||||
|
}
|
||||||
|
|
||||||
|
extern const UsbMscDeviceApi esp32_usb_msc_device_api = {
|
||||||
|
.start = msc_device_start,
|
||||||
|
.stop = msc_device_stop,
|
||||||
|
.is_connected = msc_device_is_connected,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---- Driver lifecycle ----
|
||||||
|
// Defined in each board's .dts as a child of usbdevice0 (e.g. usbdevicemsc0).
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
static error_t start_device(struct Device* device) {
|
||||||
|
(void)GET_CONFIG(device); // no configuration - placeholder only
|
||||||
|
auto* ctx = new UsbMscDeviceCtx();
|
||||||
|
device_set_driver_data(device, ctx);
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static error_t stop_device(struct Device* device) {
|
||||||
|
auto* ctx = static_cast<UsbMscDeviceCtx*>(device_get_driver_data(device));
|
||||||
|
if (ctx != nullptr && ctx->storage_active) {
|
||||||
|
msc_device_stop(device);
|
||||||
|
}
|
||||||
|
delete ctx;
|
||||||
|
device_set_driver_data(device, nullptr);
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
Driver esp32_usb_msc_device_driver = {
|
||||||
|
.name = "esp32_usb_msc_device",
|
||||||
|
.compatible = (const char*[]) { "espressif,esp32-usbdevice-msc", nullptr },
|
||||||
|
.start_device = start_device,
|
||||||
|
.stop_device = stop_device,
|
||||||
|
.api = &esp32_usb_msc_device_api,
|
||||||
|
.device_type = &USB_MSC_DEVICE_TYPE,
|
||||||
|
.owner = nullptr,
|
||||||
|
.internal = nullptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
|
|
||||||
|
#endif // CONFIG_SOC_USB_OTG_SUPPORTED && CONFIG_TINYUSB_MSC_ENABLED
|
||||||
@@ -0,0 +1,352 @@
|
|||||||
|
#include <sdkconfig.h>
|
||||||
|
#if CONFIG_SOC_USB_OTG_SUPPORTED && CONFIG_TINYUSB_HID_COUNT
|
||||||
|
|
||||||
|
#include <tactility/device.h>
|
||||||
|
#include <tactility/driver.h>
|
||||||
|
#include <tactility/drivers/esp32_usbdevice.h>
|
||||||
|
#include <tactility/drivers/hid_report_descriptors.h>
|
||||||
|
#include <tactility/drivers/usb_device_controller.h>
|
||||||
|
#include <tactility/drivers/usb_hid_device.h>
|
||||||
|
#include <tactility/log.h>
|
||||||
|
|
||||||
|
#include <tinyusb.h>
|
||||||
|
#include <tusb.h>
|
||||||
|
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#define TAG "esp32_usb_hid_device"
|
||||||
|
#define GET_CONFIG(device) ((const Esp32UsbDeviceChildConfig*)(device)->config)
|
||||||
|
|
||||||
|
// ---- HID Report Maps ----
|
||||||
|
// Defined once in Platforms/platform-esp32/source/drivers/hid_report_descriptors.cpp and shared
|
||||||
|
// with esp32_ble_hid.cpp - HID report descriptors are transport-independent (same USB HID Usage
|
||||||
|
// Tables spec applies to both USB and BLE HID), so the same bytes work verbatim over either
|
||||||
|
// transport.
|
||||||
|
|
||||||
|
// Report IDs, matching the shared hid_report_map_* tables.
|
||||||
|
static constexpr uint8_t REPORT_ID_KEYBOARD = 1;
|
||||||
|
static constexpr uint8_t REPORT_ID_CONSUMER = 2;
|
||||||
|
static constexpr uint8_t REPORT_ID_MOUSE_SOLO = 1; // USB_HID_DEVICE_MODE_MOUSE
|
||||||
|
static constexpr uint8_t REPORT_ID_MOUSE_COMBO = 3; // USB_HID_DEVICE_MODE_KEYBOARD_MOUSE
|
||||||
|
static constexpr uint8_t REPORT_ID_GAMEPAD = 1;
|
||||||
|
|
||||||
|
// Active mode/report-map state - the report descriptor TinyUSB serves via
|
||||||
|
// tud_hid_descriptor_report_cb() must match whichever mode start() was last called with, since
|
||||||
|
// (unlike BLE's mutable GATT) USB's descriptor is fixed for the lifetime of one claim()
|
||||||
|
// session; switching modes means stop() + start() with the new mode, re-enumerating.
|
||||||
|
static enum UsbHidDeviceMode active_mode = USB_HID_DEVICE_MODE_KEYBOARD;
|
||||||
|
|
||||||
|
static const uint8_t* active_report_map() {
|
||||||
|
switch (active_mode) {
|
||||||
|
case USB_HID_DEVICE_MODE_MOUSE: return hid_report_map_mouse;
|
||||||
|
case USB_HID_DEVICE_MODE_KEYBOARD_MOUSE: return hid_report_map_keyboard_consumer_mouse;
|
||||||
|
case USB_HID_DEVICE_MODE_GAMEPAD: return hid_report_map_gamepad;
|
||||||
|
default: return hid_report_map_keyboard_consumer; // KEYBOARD
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t active_report_map_len() {
|
||||||
|
switch (active_mode) {
|
||||||
|
case USB_HID_DEVICE_MODE_MOUSE: return hid_report_map_mouse_len;
|
||||||
|
case USB_HID_DEVICE_MODE_KEYBOARD_MOUSE: return hid_report_map_keyboard_consumer_mouse_len;
|
||||||
|
case USB_HID_DEVICE_MODE_GAMEPAD: return hid_report_map_gamepad_len;
|
||||||
|
default: return hid_report_map_keyboard_consumer_len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Per-mode default product name and idProduct offset. Distinct idProduct per mode matters more
|
||||||
|
// than it might seem: most hosts (Windows in particular) cache device metadata - including
|
||||||
|
// which icon/appearance to show - keyed by VID/PID/serial, and a HID descriptor swap alone
|
||||||
|
// isn't always enough to make the host re-read it on a fast re-enumeration. Giving each mode
|
||||||
|
// its own PID makes the host treat a mode switch as connecting a genuinely different device,
|
||||||
|
// which reliably clears the stale-appearance problem (was previously showing "Keyboard" for
|
||||||
|
// every mode almost all the time).
|
||||||
|
static const char* default_name_for_mode(enum UsbHidDeviceMode mode) {
|
||||||
|
switch (mode) {
|
||||||
|
case USB_HID_DEVICE_MODE_MOUSE: return "Tactility Mouse";
|
||||||
|
case USB_HID_DEVICE_MODE_KEYBOARD_MOUSE: return "Tactility Keyboard+Mouse";
|
||||||
|
case USB_HID_DEVICE_MODE_GAMEPAD: return "Tactility Gamepad";
|
||||||
|
default: return "Tactility Keyboard"; // KEYBOARD
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint16_t product_id_for_mode(enum UsbHidDeviceMode mode) {
|
||||||
|
switch (mode) {
|
||||||
|
case USB_HID_DEVICE_MODE_MOUSE: return 0x4006;
|
||||||
|
case USB_HID_DEVICE_MODE_KEYBOARD_MOUSE: return 0x4007;
|
||||||
|
case USB_HID_DEVICE_MODE_GAMEPAD: return 0x4008;
|
||||||
|
default: return 0x4004; // KEYBOARD (unchanged - existing PID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set via usb_hid_device_set_name() before start(); empty means "use the mode's default name".
|
||||||
|
// Mirrors bluetooth_set_device_name()'s pattern of being set once before starting the profile.
|
||||||
|
static char custom_name[32] = {};
|
||||||
|
|
||||||
|
// HID's own interface/endpoint numbers come from allocate_interfaces() at claim time
|
||||||
|
// (hid_device_start()) - CDC, if the board's usbdevicecdc0 child is enabled,
|
||||||
|
// is composited in by the USB device controller itself after HID's own contribution,
|
||||||
|
// so HID no longer needs to know or care whether CDC exists.
|
||||||
|
|
||||||
|
// The report descriptor length varies by mode (KEYBOARD_MOUSE's combined map is the largest),
|
||||||
|
// so the config descriptor's total length is computed at claim time in hid_device_start().
|
||||||
|
|
||||||
|
// Built fresh in start() (see build_hid_device_descriptor()) so idProduct/iProduct can vary per
|
||||||
|
// mode - see default_name_for_mode()/product_id_for_mode() above for why.
|
||||||
|
// Mutable for the same reason, plus: the USB device controller patches bDeviceClass/SubClass/Protocol at
|
||||||
|
// claim() time depending on whether CDC is composited in.
|
||||||
|
static tusb_desc_device_t hid_device_descriptor = {
|
||||||
|
.bLength = sizeof(tusb_desc_device_t),
|
||||||
|
.bDescriptorType = TUSB_DESC_DEVICE,
|
||||||
|
.bcdUSB = 0x0200,
|
||||||
|
.bDeviceClass = TUSB_CLASS_UNSPECIFIED,
|
||||||
|
.bDeviceSubClass = 0x00,
|
||||||
|
.bDeviceProtocol = 0x00,
|
||||||
|
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
|
||||||
|
.idVendor = 0x303A, // Espressif VID
|
||||||
|
.idProduct = 0x4004, // overwritten per-mode in start()
|
||||||
|
.bcdDevice = 0x0100,
|
||||||
|
.iManufacturer = 0x01,
|
||||||
|
.iProduct = 0x02,
|
||||||
|
.iSerialNumber = 0x03,
|
||||||
|
.bNumConfigurations = 0x01,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char hid_langid_descriptor[] = { 0x09, 0x04 };
|
||||||
|
// index 2 (iProduct) is overwritten per-mode/per-custom-name in start(), see
|
||||||
|
// build_hid_device_descriptor().
|
||||||
|
static char hid_product_string[sizeof(custom_name)] = "Tactility Keyboard";
|
||||||
|
// index 4 is the HID interface string, read directly by some HID apps/testers (gamepad testers, joystick tools) instead of iProduct (index 2).
|
||||||
|
// Point it at the same buffer as iProduct so both stay in sync with whatever mode/custom name is active.
|
||||||
|
static const char* hid_string_descriptor[] = {
|
||||||
|
hid_langid_descriptor, "Tactility", hid_product_string, "123456", hid_product_string,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Built fresh in hid_device_start(), sized for the active mode's report map. TinyUSB only reads
|
||||||
|
// this once during tinyusb_driver_install() (inside claim()), so it's safe to mutate between
|
||||||
|
// claim sessions (i.e. stop() + start() with a different mode) as long as it's finalized before
|
||||||
|
// claim() is called.
|
||||||
|
static uint8_t hid_configuration_descriptor[TUD_HID_DESC_LEN];
|
||||||
|
|
||||||
|
// ---- TinyUSB HID callbacks (required by the TinyUSB HID class driver) ----
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
uint8_t const* tud_hid_descriptor_report_cb(uint8_t instance) {
|
||||||
|
(void)instance;
|
||||||
|
return active_report_map();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type,
|
||||||
|
uint8_t* buffer, uint16_t reqlen) {
|
||||||
|
(void)instance; (void)report_id; (void)report_type; (void)buffer; (void)reqlen;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type,
|
||||||
|
uint8_t const* buffer, uint16_t bufsize) {
|
||||||
|
(void)instance; (void)report_id; (void)report_type; (void)buffer; (void)bufsize;
|
||||||
|
// LED state (caps/num/scroll lock) - not currently surfaced to callers.
|
||||||
|
}
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
|
|
||||||
|
// ---- HID device API ----
|
||||||
|
|
||||||
|
// Fills in idProduct/iProduct for the given mode, honoring a set_name() override if one was
|
||||||
|
// set. Must run before every claim() - the name/PID need to match whatever mode is about to
|
||||||
|
// start, not whatever they were left as from a previous session.
|
||||||
|
static void build_hid_device_descriptor(enum UsbHidDeviceMode mode) {
|
||||||
|
hid_device_descriptor.idProduct = product_id_for_mode(mode);
|
||||||
|
const char* name = custom_name[0] ? custom_name : default_name_for_mode(mode);
|
||||||
|
strncpy(hid_product_string, name, sizeof(hid_product_string) - 1);
|
||||||
|
hid_product_string[sizeof(hid_product_string) - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
static error_t hid_device_start(struct Device* device, enum UsbHidDeviceMode mode) {
|
||||||
|
auto* controller = device_get_parent(device);
|
||||||
|
|
||||||
|
error_t begin_result = usb_device_controller_begin_claim(controller);
|
||||||
|
if (begin_result != ERROR_NONE) {
|
||||||
|
return begin_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UsbInterfaceAllocation alloc;
|
||||||
|
error_t alloc_result = usb_device_controller_allocate_interfaces(controller, /*interface_count=*/1,
|
||||||
|
/*in_endpoint_count=*/1, /*out_endpoint_count=*/0, &alloc);
|
||||||
|
if (alloc_result != ERROR_NONE) {
|
||||||
|
return alloc_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// active_mode/hid_device_descriptor commit only once resources are reserved - reads of
|
||||||
|
// active_mode (tud_hid_descriptor_report_cb(), the mode gates in send_*) must keep matching
|
||||||
|
// whatever session is actually installed if begin_claim()/allocate_interfaces() above failed.
|
||||||
|
active_mode = mode;
|
||||||
|
build_hid_device_descriptor(mode);
|
||||||
|
|
||||||
|
const uint8_t hid_bytes[] = {
|
||||||
|
TUD_HID_DESCRIPTOR(alloc.first_interface_number, 4, HID_ITF_PROTOCOL_NONE,
|
||||||
|
active_report_map_len(), alloc.first_in_endpoint, 16, 10),
|
||||||
|
};
|
||||||
|
memcpy(hid_configuration_descriptor, hid_bytes, sizeof(hid_bytes));
|
||||||
|
|
||||||
|
struct UsbDeviceClaimConfig claim_config = {};
|
||||||
|
claim_config.device_descriptor = &hid_device_descriptor;
|
||||||
|
claim_config.string_descriptor = hid_string_descriptor;
|
||||||
|
claim_config.string_descriptor_count = sizeof(hid_string_descriptor) / sizeof(hid_string_descriptor[0]);
|
||||||
|
claim_config.primary.descriptor_bytes = hid_configuration_descriptor;
|
||||||
|
claim_config.primary.descriptor_bytes_len = sizeof(hid_configuration_descriptor);
|
||||||
|
claim_config.primary.interface_count = 1;
|
||||||
|
claim_config.primary.in_endpoint_count = 1;
|
||||||
|
claim_config.primary.out_endpoint_count = 0;
|
||||||
|
|
||||||
|
return usb_device_controller_claim(controller, USB_DEVICE_CLASS_HID_KEYBOARD, &claim_config);
|
||||||
|
}
|
||||||
|
|
||||||
|
static error_t hid_device_stop(struct Device* device) {
|
||||||
|
auto* controller = device_get_parent(device);
|
||||||
|
return usb_device_controller_release(controller, USB_DEVICE_CLASS_HID_KEYBOARD);
|
||||||
|
}
|
||||||
|
|
||||||
|
static error_t hid_device_set_name(struct Device* device, const char* name) {
|
||||||
|
(void)device;
|
||||||
|
if (name == nullptr) {
|
||||||
|
custom_name[0] = '\0'; // clear override, fall back to the mode's default name
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
strncpy(custom_name, name, sizeof(custom_name) - 1);
|
||||||
|
custom_name[sizeof(custom_name) - 1] = '\0';
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// tud_hid_report() fails outright (no queueing) if the endpoint is still busy sending a
|
||||||
|
// previous report - a single fire-and-forget call is unsafe whenever a caller sends two reports
|
||||||
|
// back to back (e.g. press immediately followed by release: the second call can lose the race and drop the release, leaving
|
||||||
|
// the host thinking a key/button/consumer-control is still held down (which then either does
|
||||||
|
// nothing further or, for keys the host auto-repeats, keeps repeating). Retry with a short
|
||||||
|
// backoff instead of failing immediately.
|
||||||
|
static error_t hid_send_report(uint8_t report_id, const uint8_t* report, size_t len) {
|
||||||
|
if (!tud_mounted()) {
|
||||||
|
LOG_W(TAG, "hid_send_report(id=%d): tud_mounted() false - real disconnect, not just a busy endpoint", report_id);
|
||||||
|
return ERROR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
for (int attempt = 0; attempt < 10; attempt++) {
|
||||||
|
if (tud_hid_ready() && tud_hid_report(report_id, report, static_cast<uint16_t>(len))) {
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(5));
|
||||||
|
}
|
||||||
|
LOG_W(TAG, "hid_send_report(id=%d): gave up after 10 retries (~50ms) - endpoint stayed busy/not-ready the whole time", report_id);
|
||||||
|
return ERROR_RESOURCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static error_t hid_device_send_keyboard(struct Device* device, const uint8_t* report, size_t len) {
|
||||||
|
(void)device;
|
||||||
|
if (active_mode != USB_HID_DEVICE_MODE_KEYBOARD && active_mode != USB_HID_DEVICE_MODE_KEYBOARD_MOUSE) {
|
||||||
|
return ERROR_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
uint8_t buf[8] = {};
|
||||||
|
memcpy(buf, report, len < sizeof(buf) ? len : sizeof(buf));
|
||||||
|
return hid_send_report(REPORT_ID_KEYBOARD, buf, sizeof(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
static error_t hid_device_send_consumer(struct Device* device, const uint8_t* report, size_t len) {
|
||||||
|
(void)device;
|
||||||
|
if (active_mode != USB_HID_DEVICE_MODE_KEYBOARD && active_mode != USB_HID_DEVICE_MODE_KEYBOARD_MOUSE) {
|
||||||
|
return ERROR_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
uint8_t buf[2] = {};
|
||||||
|
memcpy(buf, report, len < sizeof(buf) ? len : sizeof(buf));
|
||||||
|
return hid_send_report(REPORT_ID_CONSUMER, buf, sizeof(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
static error_t hid_device_send_mouse(struct Device* device, const uint8_t* report, size_t len) {
|
||||||
|
(void)device;
|
||||||
|
uint8_t report_id;
|
||||||
|
if (active_mode == USB_HID_DEVICE_MODE_MOUSE) {
|
||||||
|
report_id = REPORT_ID_MOUSE_SOLO;
|
||||||
|
} else if (active_mode == USB_HID_DEVICE_MODE_KEYBOARD_MOUSE) {
|
||||||
|
report_id = REPORT_ID_MOUSE_COMBO;
|
||||||
|
} else {
|
||||||
|
return ERROR_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
uint8_t buf[4] = {};
|
||||||
|
memcpy(buf, report, len < sizeof(buf) ? len : sizeof(buf));
|
||||||
|
return hid_send_report(report_id, buf, sizeof(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
static error_t hid_device_send_gamepad(struct Device* device, const uint8_t* report, size_t len) {
|
||||||
|
(void)device;
|
||||||
|
if (active_mode != USB_HID_DEVICE_MODE_GAMEPAD) {
|
||||||
|
return ERROR_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
// 8 bytes: X,Y,Rx,Ry,Z (1 each - Xbox-360-style, Z shared by triggers), hat/dpad (1),
|
||||||
|
// buttons[2] (10 buttons + 6 padding bits). See hid_report_descriptors.cpp for the full
|
||||||
|
// layout/rationale.
|
||||||
|
uint8_t buf[8] = {};
|
||||||
|
memcpy(buf, report, len < sizeof(buf) ? len : sizeof(buf));
|
||||||
|
return hid_send_report(REPORT_ID_GAMEPAD, buf, sizeof(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool hid_device_is_connected(struct Device* device) {
|
||||||
|
(void)device;
|
||||||
|
// tud_hid_ready() deliberately NOT included here: it reflects whether the endpoint is idle
|
||||||
|
// right now (false during every in-flight report send, which is normal and momentary, not
|
||||||
|
// a disconnect), not whether a host is actually connected. Including it made this function
|
||||||
|
// flap constantly under any traffic - explains the "Connected" <-> "waiting for host..."
|
||||||
|
// flicker apps were seeing on every keypress/click even though nothing was actually wrong
|
||||||
|
// (hid_send_report()'s own retry logic was succeeding the whole time; no warning ever
|
||||||
|
// logged because there was nothing to warn about). tud_mounted() alone is the correct
|
||||||
|
// "is a host connected" signal.
|
||||||
|
return tud_mounted();
|
||||||
|
}
|
||||||
|
|
||||||
|
extern const UsbHidDeviceApi esp32_usb_hid_device_api = {
|
||||||
|
.start = hid_device_start,
|
||||||
|
.stop = hid_device_stop,
|
||||||
|
.set_name = hid_device_set_name,
|
||||||
|
.send_keyboard = hid_device_send_keyboard,
|
||||||
|
.send_consumer = hid_device_send_consumer,
|
||||||
|
.send_mouse = hid_device_send_mouse,
|
||||||
|
.send_gamepad = hid_device_send_gamepad,
|
||||||
|
.is_connected = hid_device_is_connected,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---- Driver lifecycle ----
|
||||||
|
// Defined in each board's .dts as a child of usbdevice0 (e.g. usbdevicehid0) - the devicetree
|
||||||
|
// compiler wires device_get_parent() to the controller automatically.
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
static error_t start_device(struct Device* device) {
|
||||||
|
(void)GET_CONFIG(device); // no configuration - placeholder only
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static error_t stop_device(struct Device* device) {
|
||||||
|
// Safety cleanup: release the slot if it's still held (e.g. this device is stopped while
|
||||||
|
// HID is still active).
|
||||||
|
auto* controller = device_get_parent(device);
|
||||||
|
if (controller != nullptr && usb_device_controller_get_active_class(controller) == USB_DEVICE_CLASS_HID_KEYBOARD) {
|
||||||
|
usb_device_controller_release(controller, USB_DEVICE_CLASS_HID_KEYBOARD);
|
||||||
|
}
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
Driver esp32_usb_hid_device_driver = {
|
||||||
|
.name = "esp32_usb_hid_device",
|
||||||
|
.compatible = (const char*[]) { "espressif,esp32-usbdevice-hid", nullptr },
|
||||||
|
.start_device = start_device,
|
||||||
|
.stop_device = stop_device,
|
||||||
|
.api = &esp32_usb_hid_device_api,
|
||||||
|
.device_type = &USB_HID_DEVICE_TYPE,
|
||||||
|
.owner = nullptr,
|
||||||
|
.internal = nullptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
|
|
||||||
|
#endif // CONFIG_SOC_USB_OTG_SUPPORTED && CONFIG_TINYUSB_HID_COUNT
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
#include <sdkconfig.h>
|
||||||
|
#if CONFIG_SOC_USB_OTG_SUPPORTED && CONFIG_TINYUSB_MIDI_COUNT
|
||||||
|
|
||||||
|
#include <tactility/device.h>
|
||||||
|
#include <tactility/driver.h>
|
||||||
|
#include <tactility/drivers/esp32_usbdevice.h>
|
||||||
|
#include <tactility/drivers/usb_device_controller.h>
|
||||||
|
#include <tactility/drivers/usb_midi_device.h>
|
||||||
|
#include <tactility/log.h>
|
||||||
|
|
||||||
|
#include <tinyusb.h>
|
||||||
|
#include <tusb.h>
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#define TAG "esp32_usb_midi_device"
|
||||||
|
#define GET_CONFIG(device) ((const Esp32UsbDeviceChildConfig*)(device)->config)
|
||||||
|
|
||||||
|
// ---- MIDI device descriptor set ----
|
||||||
|
// TUD_MIDI_DESCRIPTOR consumes 2 interfaces internally (Audio Control + MIDI Streaming,
|
||||||
|
// itfnum and itfnum+1 - see TUD_MIDI_DESC_HEAD in usbd.h), unlike HID/MSC/CDC which use 1
|
||||||
|
// each - allocate_interfaces(2, 1, 1, ...) in midi_device_start() reflects that.
|
||||||
|
|
||||||
|
// Mutable: the USB device controller patches bDeviceClass/SubClass/Protocol at
|
||||||
|
// claim() time depending on whether CDC is composited in (see esp32_usb_device_controller.cpp).
|
||||||
|
static tusb_desc_device_t midi_device_descriptor = {
|
||||||
|
.bLength = sizeof(tusb_desc_device_t),
|
||||||
|
.bDescriptorType = TUSB_DESC_DEVICE,
|
||||||
|
.bcdUSB = 0x0200,
|
||||||
|
.bDeviceClass = TUSB_CLASS_MISC,
|
||||||
|
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
|
||||||
|
.bDeviceProtocol = MISC_PROTOCOL_IAD,
|
||||||
|
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
|
||||||
|
.idVendor = 0x303A, // Espressif VID
|
||||||
|
.idProduct = 0x4005,
|
||||||
|
.bcdDevice = 0x0100,
|
||||||
|
.iManufacturer = 0x01,
|
||||||
|
.iProduct = 0x02,
|
||||||
|
.iSerialNumber = 0x03,
|
||||||
|
.bNumConfigurations = 0x01,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char midi_langid_descriptor[] = { 0x09, 0x04 };
|
||||||
|
// index 2 (iProduct) is overwritten by set_name() before start(), see
|
||||||
|
// midi_device_set_name()/midi_device_start(). Index 4 is the jack name (see
|
||||||
|
// midi_configuration_descriptor below).
|
||||||
|
//
|
||||||
|
// The interface string index is literal 0 ("no string" - a real, well-defined USB concept: the
|
||||||
|
// host skips string lookup entirely, distinct from an empty string AT a valid index), not an
|
||||||
|
// index into this table at all. An earlier attempt used an empty "" entry at a real index
|
||||||
|
// instead of literal 0 - that crashed the device outright on Windows (Code 10,
|
||||||
|
// STATUS_DEVICE_DATA_ERROR): empty-string-at-a-valid-index and "no string" (index 0) are not
|
||||||
|
// the same thing, only the latter is safe. Both Windows' newer MIDI naming and MIDIBerry were
|
||||||
|
// confirmed (live test) to concatenate interface-string + jack-string into one displayed name,
|
||||||
|
// so leaving the interface string as "no string" (0) means only the jack string shows, instead
|
||||||
|
// of duplicating/prefixing the product name.
|
||||||
|
static char midi_product_string[32] = "Tactility MIDI Device";
|
||||||
|
static const char* midi_string_descriptor[] = {
|
||||||
|
midi_langid_descriptor, "Tactility", midi_product_string, "123456", midi_product_string,
|
||||||
|
};
|
||||||
|
static constexpr uint8_t MIDI_STRIDX_JACK = 4;
|
||||||
|
|
||||||
|
// TUD_MIDI_DESCRIPTOR (the usbd.h convenience macro) hardcodes its jack descriptors' string
|
||||||
|
// index to 0 (no string) - it isn't a parameter the macro exposes. Built by hand here with a
|
||||||
|
// real string index for the jack (instead of the macro's hardcoded 0), while keeping the
|
||||||
|
// interface string index at literal 0 (see comment above on midi_string_descriptor for why 0,
|
||||||
|
// not an empty table entry). Interface/endpoint numbers come from allocate_interfaces() at claim
|
||||||
|
// time rather than compile-time constants, since CDC may now be composited in after MIDI.
|
||||||
|
static uint8_t midi_configuration_descriptor[TUD_MIDI_DESC_LEN];
|
||||||
|
|
||||||
|
// ---- MIDI device API ----
|
||||||
|
|
||||||
|
static error_t midi_device_start(struct Device* device) {
|
||||||
|
auto* controller = device_get_parent(device);
|
||||||
|
|
||||||
|
error_t begin_result = usb_device_controller_begin_claim(controller);
|
||||||
|
if (begin_result != ERROR_NONE) {
|
||||||
|
return begin_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UsbInterfaceAllocation alloc;
|
||||||
|
error_t alloc_result = usb_device_controller_allocate_interfaces(controller, /*interface_count=*/2,
|
||||||
|
/*in_endpoint_count=*/1, /*out_endpoint_count=*/1, &alloc);
|
||||||
|
if (alloc_result != ERROR_NONE) {
|
||||||
|
return alloc_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t itf_ac = alloc.first_interface_number;
|
||||||
|
const uint8_t midi_bytes[] = {
|
||||||
|
TUD_MIDI_DESC_HEAD(itf_ac, 0, 1),
|
||||||
|
TUD_MIDI_DESC_JACK_DESC(1, MIDI_STRIDX_JACK),
|
||||||
|
TUD_MIDI_DESC_EP(alloc.first_out_endpoint, 64, 1),
|
||||||
|
TUD_MIDI_JACKID_IN_EMB(1),
|
||||||
|
TUD_MIDI_DESC_EP(alloc.first_in_endpoint, 64, 1),
|
||||||
|
TUD_MIDI_JACKID_OUT_EMB(1),
|
||||||
|
};
|
||||||
|
memcpy(midi_configuration_descriptor, midi_bytes, sizeof(midi_bytes));
|
||||||
|
|
||||||
|
struct UsbDeviceClaimConfig claim_config = {};
|
||||||
|
claim_config.device_descriptor = &midi_device_descriptor;
|
||||||
|
claim_config.string_descriptor = midi_string_descriptor;
|
||||||
|
claim_config.string_descriptor_count = sizeof(midi_string_descriptor) / sizeof(midi_string_descriptor[0]);
|
||||||
|
claim_config.primary.descriptor_bytes = midi_configuration_descriptor;
|
||||||
|
claim_config.primary.descriptor_bytes_len = sizeof(midi_configuration_descriptor);
|
||||||
|
claim_config.primary.interface_count = 2;
|
||||||
|
claim_config.primary.in_endpoint_count = 1;
|
||||||
|
claim_config.primary.out_endpoint_count = 1;
|
||||||
|
|
||||||
|
return usb_device_controller_claim(controller, USB_DEVICE_CLASS_MIDI, &claim_config);
|
||||||
|
}
|
||||||
|
|
||||||
|
static error_t midi_device_stop(struct Device* device) {
|
||||||
|
auto* controller = device_get_parent(device);
|
||||||
|
return usb_device_controller_release(controller, USB_DEVICE_CLASS_MIDI);
|
||||||
|
}
|
||||||
|
|
||||||
|
static error_t midi_device_set_name(struct Device* device, const char* name) {
|
||||||
|
(void)device;
|
||||||
|
if (name == nullptr) {
|
||||||
|
strncpy(midi_product_string, "Tactility MIDI Device", sizeof(midi_product_string) - 1);
|
||||||
|
midi_product_string[sizeof(midi_product_string) - 1] = '\0';
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
strncpy(midi_product_string, name, sizeof(midi_product_string) - 1);
|
||||||
|
midi_product_string[sizeof(midi_product_string) - 1] = '\0';
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static error_t midi_device_send(struct Device* device, const uint8_t* msg, size_t len) {
|
||||||
|
auto* controller = device_get_parent(device);
|
||||||
|
if (controller == nullptr || usb_device_controller_get_active_class(controller) != USB_DEVICE_CLASS_MIDI) {
|
||||||
|
return ERROR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
if (!tud_mounted()) {
|
||||||
|
return ERROR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
uint32_t written = tud_midi_stream_write(0, msg, static_cast<uint32_t>(len));
|
||||||
|
return written == len ? ERROR_NONE : ERROR_RESOURCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool midi_device_is_connected(struct Device* device) {
|
||||||
|
auto* controller = device_get_parent(device);
|
||||||
|
if (controller == nullptr || usb_device_controller_get_active_class(controller) != USB_DEVICE_CLASS_MIDI) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return tud_mounted();
|
||||||
|
}
|
||||||
|
|
||||||
|
extern const UsbMidiDeviceApi esp32_usb_midi_device_api = {
|
||||||
|
.start = midi_device_start,
|
||||||
|
.stop = midi_device_stop,
|
||||||
|
.set_name = midi_device_set_name,
|
||||||
|
.send = midi_device_send,
|
||||||
|
.is_connected = midi_device_is_connected,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---- Driver lifecycle ----
|
||||||
|
// Defined in each board's .dts as a child of usbdevice0 (e.g. usbdevicemidi0) - the devicetree
|
||||||
|
// compiler wires device_get_parent() to the controller automatically.
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
// tud_midi_rx_cb is intentionally not implemented: this driver is send-only, matching
|
||||||
|
// bluetooth_midi.h's API shape (no receive exposed at the Tactility layer either). Incoming
|
||||||
|
// bytes are simply left in TinyUSB's RX FIFO and eventually overwritten/dropped.
|
||||||
|
|
||||||
|
static error_t start_device(struct Device* device) {
|
||||||
|
(void)GET_CONFIG(device); // no configuration - placeholder only
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static error_t stop_device(struct Device* device) {
|
||||||
|
// Safety cleanup: release the slot if it's still held (e.g. this device is stopped while
|
||||||
|
// MIDI is still active).
|
||||||
|
auto* controller = device_get_parent(device);
|
||||||
|
if (controller != nullptr && usb_device_controller_get_active_class(controller) == USB_DEVICE_CLASS_MIDI) {
|
||||||
|
usb_device_controller_release(controller, USB_DEVICE_CLASS_MIDI);
|
||||||
|
}
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
Driver esp32_usb_midi_device_driver = {
|
||||||
|
.name = "esp32_usb_midi_device",
|
||||||
|
.compatible = (const char*[]) { "espressif,esp32-usbdevice-midi", nullptr },
|
||||||
|
.start_device = start_device,
|
||||||
|
.stop_device = stop_device,
|
||||||
|
.api = &esp32_usb_midi_device_api,
|
||||||
|
.device_type = &USB_MIDI_DEVICE_TYPE,
|
||||||
|
.owner = nullptr,
|
||||||
|
.internal = nullptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
|
|
||||||
|
#endif // CONFIG_SOC_USB_OTG_SUPPORTED && CONFIG_TINYUSB_MIDI_COUNT
|
||||||
@@ -43,6 +43,21 @@ extern Driver esp32_usbhost_hid_driver;
|
|||||||
extern Driver esp32_usbhost_midi_driver;
|
extern Driver esp32_usbhost_midi_driver;
|
||||||
extern Driver esp32_usbhost_msc_driver;
|
extern Driver esp32_usbhost_msc_driver;
|
||||||
#endif
|
#endif
|
||||||
|
#if SOC_USB_OTG_SUPPORTED && (CONFIG_TINYUSB_HID_COUNT || CONFIG_TINYUSB_MSC_ENABLED || CONFIG_TINYUSB_MIDI_COUNT || CONFIG_TINYUSB_CDC_ENABLED)
|
||||||
|
extern Driver esp32_usb_device_controller_driver;
|
||||||
|
#endif
|
||||||
|
#if SOC_USB_OTG_SUPPORTED && CONFIG_TINYUSB_HID_COUNT
|
||||||
|
extern Driver esp32_usb_hid_device_driver;
|
||||||
|
#endif
|
||||||
|
#if SOC_USB_OTG_SUPPORTED && CONFIG_TINYUSB_MSC_ENABLED
|
||||||
|
extern Driver esp32_usb_msc_device_driver;
|
||||||
|
#endif
|
||||||
|
#if SOC_USB_OTG_SUPPORTED && CONFIG_TINYUSB_MIDI_COUNT
|
||||||
|
extern Driver esp32_usb_midi_device_driver;
|
||||||
|
#endif
|
||||||
|
#if SOC_USB_OTG_SUPPORTED && CONFIG_TINYUSB_CDC_ENABLED
|
||||||
|
extern Driver esp32_usb_cdc_device_driver;
|
||||||
|
#endif
|
||||||
|
|
||||||
static error_t start() {
|
static error_t start() {
|
||||||
/* We crash when construct fails, because if a single driver fails to construct,
|
/* We crash when construct fails, because if a single driver fails to construct,
|
||||||
@@ -78,6 +93,25 @@ static error_t start() {
|
|||||||
check(driver_construct_add(&esp32_usbhost_hid_driver) == ERROR_NONE);
|
check(driver_construct_add(&esp32_usbhost_hid_driver) == ERROR_NONE);
|
||||||
check(driver_construct_add(&esp32_usbhost_midi_driver) == ERROR_NONE);
|
check(driver_construct_add(&esp32_usbhost_midi_driver) == ERROR_NONE);
|
||||||
check(driver_construct_add(&esp32_usbhost_msc_driver) == ERROR_NONE);
|
check(driver_construct_add(&esp32_usbhost_msc_driver) == ERROR_NONE);
|
||||||
|
#endif
|
||||||
|
// usbdevice0 and its children (usbdevicehid0, usbdevicemsc0, ...) are declared per-board in
|
||||||
|
// .dts, same pattern as usbhost0 above - the devicetree compiler constructs/adds/starts their
|
||||||
|
// Device instances and wires parent/child relationships. Only driver registration happens
|
||||||
|
// here.
|
||||||
|
#if SOC_USB_OTG_SUPPORTED && (CONFIG_TINYUSB_HID_COUNT || CONFIG_TINYUSB_MSC_ENABLED || CONFIG_TINYUSB_MIDI_COUNT || CONFIG_TINYUSB_CDC_ENABLED)
|
||||||
|
check(driver_construct_add(&esp32_usb_device_controller_driver) == ERROR_NONE);
|
||||||
|
#endif
|
||||||
|
#if SOC_USB_OTG_SUPPORTED && CONFIG_TINYUSB_HID_COUNT
|
||||||
|
check(driver_construct_add(&esp32_usb_hid_device_driver) == ERROR_NONE);
|
||||||
|
#endif
|
||||||
|
#if SOC_USB_OTG_SUPPORTED && CONFIG_TINYUSB_MSC_ENABLED
|
||||||
|
check(driver_construct_add(&esp32_usb_msc_device_driver) == ERROR_NONE);
|
||||||
|
#endif
|
||||||
|
#if SOC_USB_OTG_SUPPORTED && CONFIG_TINYUSB_MIDI_COUNT
|
||||||
|
check(driver_construct_add(&esp32_usb_midi_device_driver) == ERROR_NONE);
|
||||||
|
#endif
|
||||||
|
#if SOC_USB_OTG_SUPPORTED && CONFIG_TINYUSB_CDC_ENABLED
|
||||||
|
check(driver_construct_add(&esp32_usb_cdc_device_driver) == ERROR_NONE);
|
||||||
#endif
|
#endif
|
||||||
return ERROR_NONE;
|
return ERROR_NONE;
|
||||||
}
|
}
|
||||||
@@ -89,6 +123,21 @@ static error_t stop() {
|
|||||||
check(driver_remove_destruct(&esp32_wifi_pinned_driver) == ERROR_NONE);
|
check(driver_remove_destruct(&esp32_wifi_pinned_driver) == ERROR_NONE);
|
||||||
check(driver_remove_destruct(&esp32_wifi_driver) == ERROR_NONE);
|
check(driver_remove_destruct(&esp32_wifi_driver) == ERROR_NONE);
|
||||||
#endif
|
#endif
|
||||||
|
#if SOC_USB_OTG_SUPPORTED && CONFIG_TINYUSB_CDC_ENABLED
|
||||||
|
check(driver_remove_destruct(&esp32_usb_cdc_device_driver) == ERROR_NONE);
|
||||||
|
#endif
|
||||||
|
#if SOC_USB_OTG_SUPPORTED && CONFIG_TINYUSB_MIDI_COUNT
|
||||||
|
check(driver_remove_destruct(&esp32_usb_midi_device_driver) == ERROR_NONE);
|
||||||
|
#endif
|
||||||
|
#if SOC_USB_OTG_SUPPORTED && CONFIG_TINYUSB_MSC_ENABLED
|
||||||
|
check(driver_remove_destruct(&esp32_usb_msc_device_driver) == ERROR_NONE);
|
||||||
|
#endif
|
||||||
|
#if SOC_USB_OTG_SUPPORTED && CONFIG_TINYUSB_HID_COUNT
|
||||||
|
check(driver_remove_destruct(&esp32_usb_hid_device_driver) == ERROR_NONE);
|
||||||
|
#endif
|
||||||
|
#if SOC_USB_OTG_SUPPORTED && (CONFIG_TINYUSB_HID_COUNT || CONFIG_TINYUSB_MSC_ENABLED || CONFIG_TINYUSB_MIDI_COUNT || CONFIG_TINYUSB_CDC_ENABLED)
|
||||||
|
check(driver_remove_destruct(&esp32_usb_device_controller_driver) == ERROR_NONE);
|
||||||
|
#endif
|
||||||
#if SOC_USB_OTG_SUPPORTED
|
#if SOC_USB_OTG_SUPPORTED
|
||||||
check(driver_remove_destruct(&esp32_usbhost_msc_driver) == ERROR_NONE);
|
check(driver_remove_destruct(&esp32_usbhost_msc_driver) == ERROR_NONE);
|
||||||
check(driver_remove_destruct(&esp32_usbhost_midi_driver) == ERROR_NONE);
|
check(driver_remove_destruct(&esp32_usbhost_midi_driver) == ERROR_NONE);
|
||||||
|
|||||||
@@ -10,102 +10,43 @@
|
|||||||
#include <esp_system.h>
|
#include <esp_system.h>
|
||||||
#include <freertos/FreeRTOS.h>
|
#include <freertos/FreeRTOS.h>
|
||||||
#include <freertos/task.h>
|
#include <freertos/task.h>
|
||||||
#include <tinyusb.h>
|
|
||||||
#include <tusb_msc_storage.h>
|
|
||||||
#include <wear_levelling.h>
|
#include <wear_levelling.h>
|
||||||
|
|
||||||
|
#include <tactility/device.h>
|
||||||
|
#include <tactility/drivers/esp32_sdcard.h>
|
||||||
|
#include <tactility/drivers/usb_msc_device.h>
|
||||||
#include <tactility/log.h>
|
#include <tactility/log.h>
|
||||||
|
|
||||||
#if CONFIG_IDF_TARGET_ESP32P4
|
|
||||||
#include "hal/usb_wrap_ll.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define EPNUM_MSC 1
|
|
||||||
#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_MSC_DESC_LEN)
|
|
||||||
#define SECTOR_SIZE 512
|
|
||||||
|
|
||||||
constexpr auto* TAG = "USB";
|
constexpr auto* TAG = "USB";
|
||||||
|
|
||||||
namespace tt::hal::usb {
|
namespace tt::hal::usb {
|
||||||
extern sdmmc_card_t* getCard();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set when mass storage was started as part of the dedicated reboot-into-MSC boot flow.
|
// Set when mass storage was started as part of the dedicated reboot-into-MSC boot flow.
|
||||||
// Used to decide whether ejecting the volume should automatically reboot back to normal OS.
|
// Used to decide whether ejecting the volume should automatically reboot back to normal OS.
|
||||||
static bool startedFromBootMode = false;
|
static bool startedFromBootMode = false;
|
||||||
|
|
||||||
enum {
|
static sdmmc_card_t* getCard() {
|
||||||
ITF_NUM_MSC = 0,
|
sdmmc_card_t* card = nullptr;
|
||||||
ITF_NUM_TOTAL
|
|
||||||
};
|
|
||||||
|
|
||||||
enum {
|
device_for_each(&card, [](auto* device, void* context) {
|
||||||
EDPT_CTRL_OUT = 0x00,
|
auto* driver = device_get_driver(device);
|
||||||
EDPT_CTRL_IN = 0x80,
|
if (driver == nullptr) return true;
|
||||||
|
if (!driver_is_compatible(driver, "espressif,esp32-sdspi") &&
|
||||||
|
!driver_is_compatible(driver, "espressif,esp32-sdmmc")) return true;
|
||||||
|
auto** out = static_cast<sdmmc_card_t**>(context);
|
||||||
|
*out = esp32_sdcard_get_card(device);
|
||||||
|
return *out == nullptr;
|
||||||
|
});
|
||||||
|
|
||||||
EDPT_MSC_OUT = 0x01,
|
if (card == nullptr) {
|
||||||
EDPT_MSC_IN = 0x81,
|
LOG_W(TAG, "Couldn't find a mounted SD card");
|
||||||
};
|
}
|
||||||
|
|
||||||
static bool driverInstalled = false;
|
return card;
|
||||||
|
}
|
||||||
|
|
||||||
static tusb_desc_device_t descriptor_config = {
|
static void onMountChanged(bool mounted, void* /*context*/) {
|
||||||
.bLength = sizeof(descriptor_config),
|
if (mounted) {
|
||||||
.bDescriptorType = TUSB_DESC_DEVICE,
|
|
||||||
.bcdUSB = 0x0200,
|
|
||||||
.bDeviceClass = TUSB_CLASS_MISC,
|
|
||||||
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
|
|
||||||
.bDeviceProtocol = MISC_PROTOCOL_IAD,
|
|
||||||
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
|
|
||||||
.idVendor = 0x303A, // TODO: Espressif VID. Do we need to change this?
|
|
||||||
.idProduct = 0x4002,
|
|
||||||
.bcdDevice = 0x100,
|
|
||||||
.iManufacturer = 0x01,
|
|
||||||
.iProduct = 0x02,
|
|
||||||
.iSerialNumber = 0x03,
|
|
||||||
.bNumConfigurations = 0x01
|
|
||||||
};
|
|
||||||
|
|
||||||
static char const* string_desc_arr[] = {
|
|
||||||
(const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409)
|
|
||||||
"Espressif", // 1: Manufacturer
|
|
||||||
"Tactility Device", // 2: Product
|
|
||||||
"42", // 3: Serials
|
|
||||||
"Tactility Mass Storage", // 4. MSC
|
|
||||||
};
|
|
||||||
|
|
||||||
static uint8_t const msc_fs_configuration_desc[] = {
|
|
||||||
// Config number, interface count, string index, total length, attribute, power in mA
|
|
||||||
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
|
|
||||||
|
|
||||||
// Interface number, string index, EP Out & EP In address, EP size
|
|
||||||
TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 0, EDPT_MSC_OUT, EDPT_MSC_IN, 64),
|
|
||||||
};
|
|
||||||
|
|
||||||
#if (TUD_OPT_HIGH_SPEED)
|
|
||||||
static const tusb_desc_device_qualifier_t device_qualifier = {
|
|
||||||
.bLength = sizeof(tusb_desc_device_qualifier_t),
|
|
||||||
.bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER,
|
|
||||||
.bcdUSB = 0x0200,
|
|
||||||
.bDeviceClass = TUSB_CLASS_MISC,
|
|
||||||
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
|
|
||||||
.bDeviceProtocol = MISC_PROTOCOL_IAD,
|
|
||||||
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
|
|
||||||
.bNumConfigurations = 0x01,
|
|
||||||
.bReserved = 0
|
|
||||||
};
|
|
||||||
|
|
||||||
static uint8_t const msc_hs_configuration_desc[] = {
|
|
||||||
// Config number, interface count, string index, total length, attribute, power in mA
|
|
||||||
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
|
|
||||||
|
|
||||||
// Interface number, string index, EP Out & EP In address, EP size
|
|
||||||
TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 0, EDPT_MSC_OUT, EDPT_MSC_IN, 512),
|
|
||||||
};
|
|
||||||
#endif // TUD_OPT_HIGH_SPEED
|
|
||||||
|
|
||||||
static void storage_mount_changed_cb(tinyusb_msc_event_t* event) {
|
|
||||||
if (event->mount_changed_data.is_mounted) {
|
|
||||||
LOG_I(TAG, "MSC Mounted");
|
LOG_I(TAG, "MSC Mounted");
|
||||||
// Storage is only (re)mounted into our own filesystem after the host sends a SCSI
|
// Storage is only (re)mounted into our own filesystem after the host sends a SCSI
|
||||||
// START STOP UNIT eject (see tud_msc_start_stop_cb() in tusb_msc_storage.c). Windows
|
// START STOP UNIT eject (see tud_msc_start_stop_cb() in tusb_msc_storage.c). Windows
|
||||||
@@ -121,130 +62,73 @@ static void storage_mount_changed_cb(tinyusb_msc_event_t* event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool ensureDriverInstalled() {
|
} // namespace tt::hal::usb
|
||||||
if (driverInstalled) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if CONFIG_IDF_TARGET_ESP32P4
|
|
||||||
// Tab5's USB-C port is wired to FSLS PHY0, but the USB_WRAP (FS/FSLS) controller
|
|
||||||
// used by TinyUSB device mode defaults to FSLS PHY1. Route it to PHY0 before
|
|
||||||
// installing the TinyUSB driver.
|
|
||||||
usb_wrap_ll_phy_select(&USB_WRAP, 0);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
const tinyusb_config_t tusb_cfg = {
|
|
||||||
.device_descriptor = &descriptor_config,
|
|
||||||
.string_descriptor = string_desc_arr,
|
|
||||||
.string_descriptor_count = sizeof(string_desc_arr) / sizeof(string_desc_arr[0]),
|
|
||||||
.external_phy = false,
|
|
||||||
#if (TUD_OPT_HIGH_SPEED)
|
|
||||||
.fs_configuration_descriptor = msc_fs_configuration_desc,
|
|
||||||
.hs_configuration_descriptor = msc_hs_configuration_desc,
|
|
||||||
.qualifier_descriptor = &device_qualifier,
|
|
||||||
#else
|
|
||||||
.configuration_descriptor = msc_fs_configuration_desc,
|
|
||||||
#endif // TUD_OPT_HIGH_SPEED
|
|
||||||
.self_powered = false,
|
|
||||||
.vbus_monitor_io = 0
|
|
||||||
};
|
|
||||||
|
|
||||||
if (tinyusb_driver_install(&tusb_cfg) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to install TinyUSB driver");
|
|
||||||
#if CONFIG_IDF_TARGET_ESP32P4
|
|
||||||
// Roll back routing when TinyUSB did not start.
|
|
||||||
usb_wrap_ll_phy_select(&USB_WRAP, 1);
|
|
||||||
#endif
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
driverInstalled = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool tusbIsSupported() { return true; }
|
bool tusbIsSupported() { return true; }
|
||||||
|
|
||||||
bool tusbStartMassStorageWithSdmmc(bool fromBootMode) {
|
bool tusbStartMassStorageWithSdmmc(bool fromBootMode) {
|
||||||
if (!ensureDriverInstalled()) {
|
auto* device = usb_msc_device_get();
|
||||||
|
if (device == nullptr) {
|
||||||
|
LOG_E(TAG, "USB MSC device not available");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
startedFromBootMode = fromBootMode;
|
|
||||||
|
|
||||||
auto* card = tt::hal::usb::getCard();
|
auto* card = tt::hal::usb::getCard();
|
||||||
if (card == nullptr) {
|
if (card == nullptr) {
|
||||||
LOG_E(TAG, "SD card not mounted");
|
LOG_E(TAG, "SD card not mounted");
|
||||||
|
device_put(device);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tinyusb_msc_sdmmc_config_t config_sdmmc = {
|
tt::hal::usb::startedFromBootMode = fromBootMode;
|
||||||
.card = card,
|
|
||||||
.callback_mount_changed = storage_mount_changed_cb,
|
|
||||||
.callback_premount_changed = nullptr,
|
|
||||||
.mount_config = {
|
|
||||||
.format_if_mount_failed = false,
|
|
||||||
.max_files = 5,
|
|
||||||
.allocation_unit_size = 0,
|
|
||||||
.disk_status_check_enable = false,
|
|
||||||
.use_one_fat = false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
auto result = tinyusb_msc_storage_init_sdmmc(&config_sdmmc);
|
auto result = usb_msc_device_start(device, USB_MSC_DEVICE_SOURCE_SDMMC, card,
|
||||||
if (result != ESP_OK) {
|
tt::hal::usb::onMountChanged, nullptr);
|
||||||
LOG_E(TAG, "TinyUSB SDMMC init failed: %s", esp_err_to_name(result));
|
if (result != ERROR_NONE) {
|
||||||
|
LOG_E(TAG, "TinyUSB SDMMC init failed: %s", error_to_string(result));
|
||||||
} else {
|
} else {
|
||||||
LOG_I(TAG, "TinyUSB SDMMC init success");
|
LOG_I(TAG, "TinyUSB SDMMC init success");
|
||||||
}
|
}
|
||||||
|
|
||||||
return result == ESP_OK;
|
device_put(device);
|
||||||
|
return result == ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool tusbStartMassStorageWithFlash(bool fromBootMode) {
|
bool tusbStartMassStorageWithFlash(bool fromBootMode) {
|
||||||
LOG_I(TAG, "Starting flash MSC");
|
LOG_I(TAG, "Starting flash MSC");
|
||||||
if (!ensureDriverInstalled()) {
|
|
||||||
|
auto* device = usb_msc_device_get();
|
||||||
|
if (device == nullptr) {
|
||||||
|
LOG_E(TAG, "USB MSC device not available");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
startedFromBootMode = fromBootMode;
|
|
||||||
|
|
||||||
wl_handle_t handle = tt::getDataPartitionWlHandle();
|
wl_handle_t handle = tt::getDataPartitionWlHandle();
|
||||||
if (handle == WL_INVALID_HANDLE) {
|
if (handle == WL_INVALID_HANDLE) {
|
||||||
LOG_E(TAG, "WL not mounted for /data");
|
LOG_E(TAG, "WL not mounted for /data");
|
||||||
|
device_put(device);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tinyusb_msc_spiflash_config_t config_flash = {
|
tt::hal::usb::startedFromBootMode = fromBootMode;
|
||||||
.wl_handle = handle,
|
|
||||||
.callback_mount_changed = storage_mount_changed_cb,
|
|
||||||
.callback_premount_changed = nullptr,
|
|
||||||
.mount_config = {
|
|
||||||
.format_if_mount_failed = false,
|
|
||||||
.max_files = 5,
|
|
||||||
.allocation_unit_size = 0,
|
|
||||||
.disk_status_check_enable = false,
|
|
||||||
.use_one_fat = false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
esp_err_t result = tinyusb_msc_storage_init_spiflash(&config_flash);
|
auto result = usb_msc_device_start(device, USB_MSC_DEVICE_SOURCE_FLASH,
|
||||||
if (result != ESP_OK) {
|
&handle, tt::hal::usb::onMountChanged, nullptr);
|
||||||
LOG_E(TAG, "TinyUSB flash init failed: %s", esp_err_to_name(result));
|
if (result != ERROR_NONE) {
|
||||||
|
LOG_E(TAG, "TinyUSB flash init failed: %s", error_to_string(result));
|
||||||
} else {
|
} else {
|
||||||
LOG_I(TAG, "TinyUSB flash init success");
|
LOG_I(TAG, "TinyUSB flash init success");
|
||||||
}
|
}
|
||||||
return result == ESP_OK;
|
device_put(device);
|
||||||
|
return result == ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
void tusbStop() {
|
void tusbStop() {
|
||||||
// Actively signal a disconnect to the host before tearing down the peripheral, otherwise
|
auto* device = usb_msc_device_get();
|
||||||
// a subsequent esp_restart() resets the chip too fast for the host to notice the device
|
if (device != nullptr) {
|
||||||
// went away, leaving it stuck showing the old MSC device until the cable is replugged.
|
usb_msc_device_stop(device);
|
||||||
tud_disconnect();
|
device_put(device);
|
||||||
vTaskDelay(pdMS_TO_TICKS(250));
|
}
|
||||||
|
|
||||||
tinyusb_msc_storage_deinit();
|
|
||||||
#if CONFIG_IDF_TARGET_ESP32P4
|
|
||||||
usb_wrap_ll_phy_select(&USB_WRAP, 1);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool tusbCanStartMassStorageWithFlash() {
|
bool tusbCanStartMassStorageWithFlash() {
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ enum BtHidDeviceMode {
|
|||||||
BT_HID_DEVICE_MODE_MOUSE,
|
BT_HID_DEVICE_MODE_MOUSE,
|
||||||
/** Keyboard + Consumer + Mouse (report IDs 1, 2, 3). */
|
/** Keyboard + Consumer + Mouse (report IDs 1, 2, 3). */
|
||||||
BT_HID_DEVICE_MODE_KEYBOARD_MOUSE,
|
BT_HID_DEVICE_MODE_KEYBOARD_MOUSE,
|
||||||
/** Gamepad (report ID 1, 8 bytes: 2-byte buttons + 6-byte axes). */
|
/** Gamepad (report ID 1, 8 bytes: 5-byte axes X/Y/Rx/Ry/Z, 1-byte hat/dpad, 2-byte
|
||||||
|
* buttons[10] padded). */
|
||||||
BT_HID_DEVICE_MODE_GAMEPAD,
|
BT_HID_DEVICE_MODE_GAMEPAD,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -90,7 +91,7 @@ struct BtHidDeviceApi {
|
|||||||
error_t (*send_mouse)(struct Device* device, const uint8_t* report, size_t len);
|
error_t (*send_mouse)(struct Device* device, const uint8_t* report, size_t len);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a gamepad HID report (8 bytes: buttons[2] + axes[6]).
|
* Send a gamepad HID report (8 bytes: axes[5] + hat[1] + buttons[2]).
|
||||||
* @param[in] device the HID device child device
|
* @param[in] device the HID device child device
|
||||||
* @param[in] report pointer to the 8-byte gamepad report
|
* @param[in] report pointer to the 8-byte gamepad report
|
||||||
* @param[in] len number of bytes (up to 8)
|
* @param[in] len number of bytes (up to 8)
|
||||||
|
|||||||
@@ -43,6 +43,20 @@ struct KeyboardKeyData {
|
|||||||
* separately. Drivers whose hardware cannot report Alt leave this false.
|
* separately. Drivers whose hardware cannot report Alt leave this false.
|
||||||
*/
|
*/
|
||||||
bool alt;
|
bool alt;
|
||||||
|
/**
|
||||||
|
* @brief Standard USB HID keyboard usage code for this key (USB HID Usage Tables, page 0x07),
|
||||||
|
* or 0 if this driver doesn't compute one (most don't - `key` is the only field most consumers
|
||||||
|
* need). Populated by drivers whose hardware layout maps cleanly onto HID usage codes, so
|
||||||
|
* consumers that want to mirror physical key presses as real HID reports (e.g. USB HID output)
|
||||||
|
* don't have to reverse-engineer one out of `key`'s LVGL/ASCII encoding - which is lossy for
|
||||||
|
* keys with no ASCII/LVGL representation at all, e.g. F1-F12.
|
||||||
|
*/
|
||||||
|
uint8_t hid_keycode;
|
||||||
|
/**
|
||||||
|
* @brief HID modifier bitmask (report byte 0: bit0=LeftCtrl, bit1=LeftShift, bit2=LeftAlt,
|
||||||
|
* bit3=LeftGui, bit4-7=Right variants) matching hid_keycode, or 0 if hid_keycode is 0.
|
||||||
|
*/
|
||||||
|
uint8_t hid_modifier;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <tactility/error.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct Device;
|
||||||
|
struct DeviceType;
|
||||||
|
struct UsbInterfaceContribution;
|
||||||
|
|
||||||
|
// ---- USB device-mode CDC-ACM addon ----
|
||||||
|
|
||||||
|
/**
|
||||||
|
* USB CDC-ACM addon API (present a serial console over USB device mode). Unlike MSC/HID/MIDI,
|
||||||
|
* CDC is not a primary USB device class and does not go through
|
||||||
|
* usb_device_controller_claim()/release() - it's a devicetree-presence addon the USB device
|
||||||
|
* controller composites into whichever primary class is active (or stands alone with none
|
||||||
|
* active), toggled per-board via the usbdevicecdc0 devicetree child's status. This API is
|
||||||
|
* exposed by that child device and called by the USB device controller itself, not by primary
|
||||||
|
* class drivers.
|
||||||
|
*/
|
||||||
|
struct UsbCdcDeviceApi {
|
||||||
|
/**
|
||||||
|
* @param[in] device the CDC child device
|
||||||
|
* @return true if this board's usbdevicecdc0 child is present and enabled (status != "disabled")
|
||||||
|
*/
|
||||||
|
bool (*is_present)(struct Device* device);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build this CDC instance's descriptor contribution, requesting interface/endpoint numbers
|
||||||
|
* from the controller via usb_device_controller_allocate_interfaces(). Called by the USB
|
||||||
|
* device controller itself during claim(), after the primary class's own contribution has
|
||||||
|
* already been allocated.
|
||||||
|
*
|
||||||
|
* `interface_string_index` is the string-descriptor index CDC's own interface string will
|
||||||
|
* land at once the controller appends it to the primary's string table (always
|
||||||
|
* primary_string_descriptor_count, since CDC's string is always appended last) - CDC bakes
|
||||||
|
* this index into its TUD_CDC_DESCRIPTOR bytes directly since it can't be patched after the
|
||||||
|
* fact. `out_interface_string` is CDC's own interface string text for the controller to
|
||||||
|
* append at that same index.
|
||||||
|
*
|
||||||
|
* @param[in] device the CDC child device
|
||||||
|
* @param[in] controller the USB device controller (to call allocate_interfaces() on)
|
||||||
|
* @param[in] interface_string_index string index this contribution's descriptor bytes must reference
|
||||||
|
* @param[out] out_contribution the built fragment
|
||||||
|
* @param[out] out_interface_string CDC's interface string text, to append at interface_string_index
|
||||||
|
* @retval ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t (*build_contribution)(struct Device* device, struct Device* controller,
|
||||||
|
uint8_t interface_string_index,
|
||||||
|
struct UsbInterfaceContribution* out_contribution,
|
||||||
|
const char** out_interface_string);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install the CDC ACM interface and the log-mirroring vprintf hook. Called by the USB device
|
||||||
|
* controller after claim() successfully installs the composite descriptor.
|
||||||
|
* @param[in] device the CDC child device
|
||||||
|
* @retval ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t (*start_console)(struct Device* device);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uninstall the CDC ACM interface and restore the previous vprintf hook. Called by the USB
|
||||||
|
* device controller before release() uninstalls the TinyUSB driver.
|
||||||
|
* @param[in] device the CDC child device
|
||||||
|
* @retval ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t (*stop_console)(struct Device* device);
|
||||||
|
};
|
||||||
|
|
||||||
|
extern const struct DeviceType USB_CDC_DEVICE_TYPE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the first started USB CDC device and take a reference on it.
|
||||||
|
* @return the device with an outstanding reference, or NULL if none is available - caller must
|
||||||
|
* call device_put() exactly once when done, same as device_get_first_active_by_type().
|
||||||
|
*/
|
||||||
|
struct Device* usb_cdc_device_get(void);
|
||||||
|
|
||||||
|
bool usb_cdc_device_is_present(struct Device* device);
|
||||||
|
error_t usb_cdc_device_build_contribution(struct Device* device, struct Device* controller,
|
||||||
|
uint8_t interface_string_index,
|
||||||
|
struct UsbInterfaceContribution* out_contribution,
|
||||||
|
const char** out_interface_string);
|
||||||
|
error_t usb_cdc_device_start_console(struct Device* device);
|
||||||
|
error_t usb_cdc_device_stop_console(struct Device* device);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <tactility/error.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct Device;
|
||||||
|
struct DeviceType;
|
||||||
|
|
||||||
|
// ---- USB device-mode (peripheral) controller ----
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Which USB device-mode class currently owns the single TinyUSB device-mode slot.
|
||||||
|
* Only one class may be active at a time - see usb_device_controller_claim(). CDC is not part of
|
||||||
|
* this enum: it's an addon that composites into whichever primary class is active (or stands
|
||||||
|
* alone with none active) rather than a primary itself - see usb_cdc_device.h.
|
||||||
|
*/
|
||||||
|
enum UsbDeviceClass {
|
||||||
|
USB_DEVICE_CLASS_NONE,
|
||||||
|
USB_DEVICE_CLASS_MSC,
|
||||||
|
USB_DEVICE_CLASS_HID_KEYBOARD,
|
||||||
|
USB_DEVICE_CLASS_MIDI,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A block of raw USB interface-descriptor bytes contributed by one class (MSC/HID/MIDI/CDC) to
|
||||||
|
* the composite configuration descriptor the controller assembles at claim() time.
|
||||||
|
*
|
||||||
|
* Interface and endpoint numbers inside the descriptor bytes must already be renumbered by the
|
||||||
|
* contributor to the base values the controller handed back via usb_device_controller_allocate_interfaces()
|
||||||
|
* - the controller does not parse or patch the bytes, it only concatenates them behind the
|
||||||
|
* config-descriptor header it writes itself.
|
||||||
|
*
|
||||||
|
* `hs_descriptor_bytes`/`hs_descriptor_bytes_len` are optional (leave both NULL/0 if this
|
||||||
|
* contributor's bytes don't differ between full-speed and high-speed, e.g. HID/MIDI/CDC's
|
||||||
|
* endpoint sizes are already speed-independent for their traffic) - only used when
|
||||||
|
* TUD_OPT_HIGH_SPEED is set, mirroring the fs_/hs_configuration_descriptor split
|
||||||
|
* tinyusb_config_t itself has (see MSC, whose bulk endpoint max-packet-size legitimately differs:
|
||||||
|
* 64 bytes FS vs 512 bytes HS).
|
||||||
|
*/
|
||||||
|
struct UsbInterfaceContribution {
|
||||||
|
const uint8_t* descriptor_bytes; // one or more TUD_*_DESCRIPTOR blocks, back to back (full-speed)
|
||||||
|
size_t descriptor_bytes_len;
|
||||||
|
const uint8_t* hs_descriptor_bytes; // optional high-speed variant; NULL to reuse descriptor_bytes
|
||||||
|
size_t hs_descriptor_bytes_len; // must equal descriptor_bytes_len (same interface/endpoint layout, only sizes differ)
|
||||||
|
uint8_t interface_count; // interfaces consumed (MSC/HID/CDC=1, MIDI=2)
|
||||||
|
uint8_t in_endpoint_count; // IN endpoints consumed (excluding EP0)
|
||||||
|
uint8_t out_endpoint_count; // OUT endpoints consumed
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface/endpoint numbers assigned to one contributor by the controller before it built its
|
||||||
|
* descriptor bytes. Endpoint numbers are direction-local (IN and OUT each number from 1), as is
|
||||||
|
* standard for USB - usb_device_controller_allocate_interfaces() hands out the next free *pair*
|
||||||
|
* per direction, not a single shared counter.
|
||||||
|
*/
|
||||||
|
struct UsbInterfaceAllocation {
|
||||||
|
uint8_t first_interface_number;
|
||||||
|
uint8_t first_in_endpoint; // e.g. 0x81, 0x82, ... or 0 if in_endpoint_count was 0
|
||||||
|
uint8_t first_out_endpoint; // e.g. 0x01, 0x02, ... or 0 if out_endpoint_count was 0
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A fully-described primary class descriptor plus device/string descriptor metadata, submitted
|
||||||
|
* to claim(). CDC (if enabled) is appended by the controller itself - primary contributors never
|
||||||
|
* see or reference CDC's interface numbers.
|
||||||
|
*/
|
||||||
|
struct UsbDeviceClaimConfig {
|
||||||
|
// Actually a `tusb_desc_device_t*` (TinyUSB's device descriptor struct) - void* here so this
|
||||||
|
// kernel header doesn't need to depend on TinyUSB's own headers; the controller and every
|
||||||
|
// contributor already build against TinyUSB directly and cast accordingly. Mutable: the
|
||||||
|
// controller patches the class triad (bDeviceClass/SubClass/Protocol) in place at claim()
|
||||||
|
// time depending on whether CDC is composited in.
|
||||||
|
void* device_descriptor;
|
||||||
|
const char* const* string_descriptor;
|
||||||
|
size_t string_descriptor_count;
|
||||||
|
struct UsbInterfaceContribution primary; // MSC or HID or MIDI's contribution
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared owner of the TinyUSB device-mode (peripheral) stack. Exactly one primary USB device
|
||||||
|
* class (mass storage, HID, MIDI) may be installed at a time - callers claim the slot before use
|
||||||
|
* and release it when done. CDC is a separate, orthogonal addon (see usb_cdc_device.h) that the
|
||||||
|
* controller composites into whichever primary is active, independent of the claim/release cycle.
|
||||||
|
* This exists so multiple independent drivers (MSC, HID, MIDI, CDC) can share the single
|
||||||
|
* underlying `tinyusb_driver_install()` call, composite descriptor assembly, and any
|
||||||
|
* board-specific PHY routing without needing to know about each other.
|
||||||
|
*/
|
||||||
|
struct UsbDeviceControllerApi {
|
||||||
|
/**
|
||||||
|
* Starts a new interface/endpoint allocation session, resetting the numbering counters
|
||||||
|
* allocate_interfaces() hands out. Call once, before building any descriptor bytes, as the
|
||||||
|
* first step of a claim() attempt (i.e. before the primary class's own allocate_interfaces()
|
||||||
|
* call) - claim() itself calls this again internally for CDC's allocation, so primary
|
||||||
|
* contributors only need to call it for their own single call.
|
||||||
|
*
|
||||||
|
* There is no explicit abort/cancel: a session abandoned after begin_claim() (e.g. a
|
||||||
|
* contributor's own allocate_interfaces() call fails and it returns early without calling
|
||||||
|
* claim()) is simply reset by the next begin_claim() call, which unconditionally reinitializes
|
||||||
|
* the allocation state regardless of whether the previous session ever finished.
|
||||||
|
*
|
||||||
|
* @param[in] device the USB device controller device
|
||||||
|
* @retval ERROR_RESOURCE_BUSY if a different class already holds the slot
|
||||||
|
* @retval ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t (*begin_claim)(struct Device* device);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ask the controller for the next free interface number and endpoint pair, before building
|
||||||
|
* descriptor bytes. Call once per contributor (primary class, and CDC internally) per
|
||||||
|
* begin_claim() session, in the order the resulting descriptor should list interfaces:
|
||||||
|
* primary class first, then CDC (the controller enforces this order internally for CDC;
|
||||||
|
* primary contributors just call this once for themselves, after begin_claim()).
|
||||||
|
* @param[in] device the USB device controller device
|
||||||
|
* @param[in] interface_count number of interfaces this contributor needs
|
||||||
|
* @param[in] in_endpoint_count number of IN endpoints needed (0 if none)
|
||||||
|
* @param[in] out_endpoint_count number of OUT endpoints needed (0 if none)
|
||||||
|
* @param[out] out_allocation the assigned numbers
|
||||||
|
* @retval ERROR_INVALID_STATE if called without a preceding begin_claim()
|
||||||
|
* @retval ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t (*allocate_interfaces)(struct Device* device, uint8_t interface_count,
|
||||||
|
uint8_t in_endpoint_count, uint8_t out_endpoint_count,
|
||||||
|
struct UsbInterfaceAllocation* out_allocation);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Claim the USB device-mode slot for the given primary class and install the composite
|
||||||
|
* descriptor (primary + CDC, if the board's usbdevicecdc0 child is enabled). Must be called
|
||||||
|
* after begin_claim() and the primary's own allocate_interfaces() call, using the same
|
||||||
|
* device-mode session (no other claim()/begin_claim() calls in between).
|
||||||
|
* @param[in] device the USB device controller device
|
||||||
|
* @param[in] usb_class the class to claim the slot for
|
||||||
|
* @param[in] config the primary class's descriptor contribution and metadata
|
||||||
|
* @retval ERROR_RESOURCE_BUSY if a different class already holds the slot
|
||||||
|
* @retval ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t (*claim)(struct Device* device, enum UsbDeviceClass usb_class,
|
||||||
|
const struct UsbDeviceClaimConfig* config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release the USB device-mode slot. Stops the CDC console (if it was composited in),
|
||||||
|
* disconnects from the host, uninstalls the TinyUSB driver, and restores any board-specific
|
||||||
|
* PHY routing. Only the current holder may release.
|
||||||
|
* @param[in] device the USB device controller device
|
||||||
|
* @param[in] usb_class the class releasing the slot; must match the current holder
|
||||||
|
* @retval ERROR_INVALID_STATE if usb_class does not hold the slot
|
||||||
|
* @retval ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t (*release)(struct Device* device, enum UsbDeviceClass usb_class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param[in] device the USB device controller device
|
||||||
|
* @return the class currently holding the slot, or USB_DEVICE_CLASS_NONE if free
|
||||||
|
*/
|
||||||
|
enum UsbDeviceClass (*get_active_class)(struct Device* device);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param[in] device the USB device controller device
|
||||||
|
* @return true if a usbdevicecdc0 child device is present and enabled on this board
|
||||||
|
*/
|
||||||
|
bool (*is_cdc_enabled)(struct Device* device);
|
||||||
|
};
|
||||||
|
|
||||||
|
extern const struct DeviceType USB_DEVICE_CONTROLLER_TYPE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the first started USB device controller and take a reference on it.
|
||||||
|
* @return the device with an outstanding reference, or NULL if none is available - caller must
|
||||||
|
* call device_put() exactly once when done, same as device_get_first_active_by_type().
|
||||||
|
*/
|
||||||
|
struct Device* usb_device_controller_get(void);
|
||||||
|
|
||||||
|
error_t usb_device_controller_begin_claim(struct Device* device);
|
||||||
|
error_t usb_device_controller_allocate_interfaces(struct Device* device, uint8_t interface_count,
|
||||||
|
uint8_t in_endpoint_count, uint8_t out_endpoint_count,
|
||||||
|
struct UsbInterfaceAllocation* out_allocation);
|
||||||
|
error_t usb_device_controller_claim(struct Device* device, enum UsbDeviceClass usb_class,
|
||||||
|
const struct UsbDeviceClaimConfig* config);
|
||||||
|
error_t usb_device_controller_release(struct Device* device, enum UsbDeviceClass usb_class);
|
||||||
|
enum UsbDeviceClass usb_device_controller_get_active_class(struct Device* device);
|
||||||
|
bool usb_device_controller_is_cdc_enabled(struct Device* device);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <tactility/error.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct Device;
|
||||||
|
struct DeviceType;
|
||||||
|
|
||||||
|
// ---- USB HID device mode ----
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects the HID report descriptor installed when this device operates as a USB HID
|
||||||
|
* peripheral. Mirrors BtHidDeviceMode's shape (bluetooth_hid_device.h) so callers that want to
|
||||||
|
* support both transports use the same mode taxonomy and send_* call shape.
|
||||||
|
*/
|
||||||
|
enum UsbHidDeviceMode {
|
||||||
|
/** Keyboard (report ID 1, boot-protocol-compatible 8 bytes) + Consumer (report ID 2, 2 bytes). */
|
||||||
|
USB_HID_DEVICE_MODE_KEYBOARD,
|
||||||
|
/** Mouse only (report ID 1, 4 bytes). */
|
||||||
|
USB_HID_DEVICE_MODE_MOUSE,
|
||||||
|
/** Keyboard + Consumer + Mouse (report IDs 1, 2, 3). */
|
||||||
|
USB_HID_DEVICE_MODE_KEYBOARD_MOUSE,
|
||||||
|
/** Gamepad (report ID 1, 8 bytes: 5-byte axes, 1-byte hat/dpad, 2-byte buttons[10] padded). */
|
||||||
|
USB_HID_DEVICE_MODE_GAMEPAD,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* USB HID device profile API (present this device as a USB HID peripheral to a host).
|
||||||
|
* This API is exposed by a child device of the USB device controller.
|
||||||
|
*/
|
||||||
|
struct UsbHidDeviceApi {
|
||||||
|
/**
|
||||||
|
* Claim the USB device-mode slot and start advertising as a USB HID device with the given
|
||||||
|
* mode.
|
||||||
|
* @param[in] device the HID device child device
|
||||||
|
* @param[in] mode the HID device mode (keyboard, mouse, keyboard+mouse, gamepad)
|
||||||
|
* @retval ERROR_RESOURCE_BUSY if another USB device class already holds the slot
|
||||||
|
* @retval ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t (*start)(struct Device* device, enum UsbHidDeviceMode mode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop presenting as a USB HID device and release the USB device-mode slot.
|
||||||
|
* @param[in] device the HID device child device
|
||||||
|
* @return ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t (*stop)(struct Device* device);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override the USB product name string reported to the host (iProduct descriptor). Only
|
||||||
|
* takes effect on the next start() - the descriptor is fixed for the lifetime of a claimed
|
||||||
|
* session, matching bluetooth_set_device_name()'s pattern of being set before starting
|
||||||
|
* advertising. If never called, each mode falls back to its own default name (e.g.
|
||||||
|
* "Tactility Keyboard", "Tactility Mouse").
|
||||||
|
* @param[in] device the HID device child device
|
||||||
|
* @param[in] name the product name (copied; safe to free/reuse the caller's buffer after
|
||||||
|
* this call returns)
|
||||||
|
* @return ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t (*set_name)(struct Device* device, const char* name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a keyboard HID report (report ID 1: modifier, reserved, keycodes[6] - 8 bytes after
|
||||||
|
* the ID byte). Only valid in USB_HID_DEVICE_MODE_KEYBOARD or _KEYBOARD_MOUSE.
|
||||||
|
* @param[in] device the HID device child device
|
||||||
|
* @param[in] report pointer to the 8-byte keyboard report (modifier, reserved, keycode[6])
|
||||||
|
* @param[in] len number of bytes (up to 8)
|
||||||
|
* @return ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t (*send_keyboard)(struct Device* device, const uint8_t* report, size_t len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a consumer control HID report (report ID 2: 16-bit usage code, little-endian).
|
||||||
|
* Only valid in USB_HID_DEVICE_MODE_KEYBOARD or _KEYBOARD_MOUSE.
|
||||||
|
* @param[in] device the HID device child device
|
||||||
|
* @param[in] report pointer to the 2-byte consumer report
|
||||||
|
* @param[in] len number of bytes (up to 2)
|
||||||
|
* @return ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t (*send_consumer)(struct Device* device, const uint8_t* report, size_t len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a mouse HID report (buttons, X, Y, wheel - 4 bytes). Report ID 1 in
|
||||||
|
* USB_HID_DEVICE_MODE_MOUSE, report ID 3 in _KEYBOARD_MOUSE.
|
||||||
|
* @param[in] device the HID device child device
|
||||||
|
* @param[in] report pointer to the 4-byte mouse report
|
||||||
|
* @param[in] len number of bytes (up to 4)
|
||||||
|
* @return ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t (*send_mouse)(struct Device* device, const uint8_t* report, size_t len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a gamepad HID report (report ID 1: axes[5] (X,Y,Rx,Ry,Z), hat/dpad[1] (low nibble),
|
||||||
|
* buttons[2] (10 buttons + 6 padding bits) - 8 bytes). Only valid in
|
||||||
|
* USB_HID_DEVICE_MODE_GAMEPAD. See hid_report_map_gamepad in hid_report_descriptors.cpp for
|
||||||
|
* the full wire layout.
|
||||||
|
* @param[in] device the HID device child device
|
||||||
|
* @param[in] report pointer to the 8-byte gamepad report
|
||||||
|
* @param[in] len number of bytes (up to 8)
|
||||||
|
* @return ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t (*send_gamepad)(struct Device* device, const uint8_t* report, size_t len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param[in] device the HID device child device
|
||||||
|
* @return true when a USB host has mounted the device and the HID interface is ready
|
||||||
|
*/
|
||||||
|
bool (*is_connected)(struct Device* device);
|
||||||
|
};
|
||||||
|
|
||||||
|
extern const struct DeviceType USB_HID_DEVICE_TYPE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the first started USB HID device child device and take a reference on it.
|
||||||
|
* @return the device with an outstanding reference, or NULL if none is available - caller must
|
||||||
|
* call device_put() exactly once when done, same as device_get_first_active_by_type().
|
||||||
|
*/
|
||||||
|
struct Device* usb_hid_device_get(void);
|
||||||
|
|
||||||
|
error_t usb_hid_device_start(struct Device* device, enum UsbHidDeviceMode mode);
|
||||||
|
error_t usb_hid_device_stop(struct Device* device);
|
||||||
|
error_t usb_hid_device_set_name(struct Device* device, const char* name);
|
||||||
|
error_t usb_hid_device_send_keyboard(struct Device* device, const uint8_t* report, size_t len);
|
||||||
|
error_t usb_hid_device_send_consumer(struct Device* device, const uint8_t* report, size_t len);
|
||||||
|
error_t usb_hid_device_send_mouse(struct Device* device, const uint8_t* report, size_t len);
|
||||||
|
error_t usb_hid_device_send_gamepad(struct Device* device, const uint8_t* report, size_t len);
|
||||||
|
bool usb_hid_device_is_connected(struct Device* device);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <tactility/error.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct Device;
|
||||||
|
struct DeviceType;
|
||||||
|
|
||||||
|
// ---- USB MIDI device mode ----
|
||||||
|
|
||||||
|
/**
|
||||||
|
* USB MIDI device profile API (present this device as a USB MIDI peripheral to a host).
|
||||||
|
* This API is exposed by a child device of the USB device controller. Mirrors
|
||||||
|
* bluetooth_midi.h's shape (no mode enum - unlike HID, MIDI has exactly one device profile) so
|
||||||
|
* the two transports stay symmetric for callers that want to support both.
|
||||||
|
*/
|
||||||
|
struct UsbMidiDeviceApi {
|
||||||
|
/**
|
||||||
|
* Claim the USB device-mode slot and start advertising as a USB MIDI device.
|
||||||
|
* @param[in] device the MIDI device child device
|
||||||
|
* @retval ERROR_RESOURCE_BUSY if another USB device class already holds the slot
|
||||||
|
* @retval ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t (*start)(struct Device* device);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop presenting as a USB MIDI device and release the USB device-mode slot.
|
||||||
|
* @param[in] device the MIDI device child device
|
||||||
|
* @return ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t (*stop)(struct Device* device);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override the USB product name string reported to the host (iProduct descriptor). Only
|
||||||
|
* takes effect on the next start() - matches bluetooth_set_device_name()'s pattern of being
|
||||||
|
* set before starting advertising. If never called, falls back to "Tactility MIDI Device".
|
||||||
|
* @param[in] device the MIDI device child device
|
||||||
|
* @param[in] name the product name (copied; safe to free/reuse the caller's buffer after
|
||||||
|
* this call returns)
|
||||||
|
* @return ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t (*set_name)(struct Device* device, const char* name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send raw MIDI message bytes over the USB MIDI connection.
|
||||||
|
* @param[in] device the MIDI device child device
|
||||||
|
* @param[in] msg the raw MIDI bytes
|
||||||
|
* @param[in] len the number of bytes
|
||||||
|
* @return ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t (*send)(struct Device* device, const uint8_t* msg, size_t len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param[in] device the MIDI device child device
|
||||||
|
* @return true when a USB host has mounted the device and the MIDI interface is ready
|
||||||
|
*/
|
||||||
|
bool (*is_connected)(struct Device* device);
|
||||||
|
};
|
||||||
|
|
||||||
|
extern const struct DeviceType USB_MIDI_DEVICE_TYPE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the first started USB MIDI device child device and take a reference on it.
|
||||||
|
* @return the device with an outstanding reference, or NULL if none is available - caller must
|
||||||
|
* call device_put() exactly once when done, same as device_get_first_active_by_type().
|
||||||
|
*/
|
||||||
|
struct Device* usb_midi_device_get(void);
|
||||||
|
|
||||||
|
error_t usb_midi_device_start(struct Device* device);
|
||||||
|
error_t usb_midi_device_stop(struct Device* device);
|
||||||
|
error_t usb_midi_device_set_name(struct Device* device, const char* name);
|
||||||
|
error_t usb_midi_device_send(struct Device* device, const uint8_t* msg, size_t len);
|
||||||
|
bool usb_midi_device_is_connected(struct Device* device);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <tactility/error.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct Device;
|
||||||
|
struct DeviceType;
|
||||||
|
|
||||||
|
// ---- USB mass-storage device mode ----
|
||||||
|
|
||||||
|
/** Which backing storage is exposed to the USB host as a mass-storage volume. */
|
||||||
|
enum UsbMscDeviceSource {
|
||||||
|
USB_MSC_DEVICE_SOURCE_SDMMC,
|
||||||
|
USB_MSC_DEVICE_SOURCE_FLASH,
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Fired when the exposed volume's mount state changes (host mounted/unmounted it). */
|
||||||
|
typedef void (*UsbMscDeviceMountChangedCallback)(bool mounted, void* context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* USB mass-storage device profile API (present the board's SD card or internal flash as a USB
|
||||||
|
* mass-storage device to a host).
|
||||||
|
*/
|
||||||
|
struct UsbMscDeviceApi {
|
||||||
|
/**
|
||||||
|
* Claim the USB device-mode slot and expose the given storage source as a USB mass-storage
|
||||||
|
* volume.
|
||||||
|
* @warning the caller must ensure the backing storage is unmounted/quiesced from local use
|
||||||
|
* before calling this - a source exposed to a USB host while still locally mounted
|
||||||
|
* risks filesystem corruption from two concurrent writers. Tactility's own flash-MSC
|
||||||
|
* path (see UsbTusb.cpp / UsbSettingsApp) enforces this via a dedicated reboot-into-
|
||||||
|
* MSC boot flow rather than unmounting live, so local access never overlaps with USB
|
||||||
|
* exposure; local availability is only restored by rebooting back to normal OS.
|
||||||
|
* @param[in] device the MSC device child device
|
||||||
|
* @param[in] source which backing storage to expose
|
||||||
|
* @param[in] source_handle the backing storage handle: a `sdmmc_card_t*` when source is
|
||||||
|
* USB_MSC_DEVICE_SOURCE_SDMMC, or a pointer to a `wl_handle_t` (i.e. `wl_handle_t*`)
|
||||||
|
* when source is USB_MSC_DEVICE_SOURCE_FLASH - passed by address, not cast through
|
||||||
|
* `void*` by value, since `wl_handle_t` is a plain integer type where 0 is a valid
|
||||||
|
* handle and would collide with the nullptr/"no handle" check otherwise. The caller
|
||||||
|
* resolves this - platform-esp32 has no business knowing which wear-levelling
|
||||||
|
* partition Tactility mounted as /data.
|
||||||
|
* @param[in] mount_changed_cb optional callback fired on host mount/unmount, nullable
|
||||||
|
* @param[in] context passed back to mount_changed_cb, nullable
|
||||||
|
* @retval ERROR_RESOURCE_BUSY if another USB device class already holds the slot
|
||||||
|
* @retval ERROR_INVALID_ARGUMENT if source_handle is invalid for the given source
|
||||||
|
* @retval ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t (*start)(struct Device* device, enum UsbMscDeviceSource source, void* source_handle,
|
||||||
|
UsbMscDeviceMountChangedCallback mount_changed_cb, void* context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop presenting as a USB mass-storage device and release the USB device-mode slot.
|
||||||
|
* @param[in] device the MSC device child device
|
||||||
|
* @return ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t (*stop)(struct Device* device);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param[in] device the MSC device child device
|
||||||
|
* @return true when a USB host currently has the volume mounted
|
||||||
|
*/
|
||||||
|
bool (*is_connected)(struct Device* device);
|
||||||
|
};
|
||||||
|
|
||||||
|
extern const struct DeviceType USB_MSC_DEVICE_TYPE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the first started USB MSC device child device and take a reference on it.
|
||||||
|
* @return the device with an outstanding reference, or NULL if none is available - caller must
|
||||||
|
* call device_put() exactly once when done, same as device_get_first_active_by_type().
|
||||||
|
*/
|
||||||
|
struct Device* usb_msc_device_get(void);
|
||||||
|
|
||||||
|
error_t usb_msc_device_start(struct Device* device, enum UsbMscDeviceSource source, void* source_handle,
|
||||||
|
UsbMscDeviceMountChangedCallback mount_changed_cb, void* context);
|
||||||
|
error_t usb_msc_device_stop(struct Device* device);
|
||||||
|
bool usb_msc_device_is_connected(struct Device* device);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -10,10 +10,12 @@ extern "C" {
|
|||||||
error_t keyboard_read_key(Device* device, KeyboardKeyData* data) {
|
error_t keyboard_read_key(Device* device, KeyboardKeyData* data) {
|
||||||
const auto* driver = device_get_driver(device);
|
const auto* driver = device_get_driver(device);
|
||||||
|
|
||||||
// Default the modifier fields here rather than in each driver: only drivers whose hardware can
|
// Default the modifier/HID fields here rather than in each driver: only drivers whose hardware
|
||||||
// report modifiers set them, and the rest would otherwise leave whatever the caller's stack held.
|
// can report them set them, and the rest would otherwise leave whatever the caller's stack held.
|
||||||
data->ctrl = false;
|
data->ctrl = false;
|
||||||
data->alt = false;
|
data->alt = false;
|
||||||
|
data->hid_keycode = 0;
|
||||||
|
data->hid_modifier = 0;
|
||||||
|
|
||||||
return KEYBOARD_DRIVER_API(driver)->read_key(device, data);
|
return KEYBOARD_DRIVER_API(driver)->read_key(device, data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#include <tactility/drivers/usb_cdc_device.h>
|
||||||
|
#include <tactility/device.h>
|
||||||
|
#include <tactility/driver.h>
|
||||||
|
|
||||||
|
#define USB_CDC_DEVICE_API(device) ((const struct UsbCdcDeviceApi*)device_get_driver(device)->api)
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
const struct DeviceType USB_CDC_DEVICE_TYPE = {
|
||||||
|
.name = "usb-cdc-device",
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Device* usb_cdc_device_get() {
|
||||||
|
struct Device* found = nullptr;
|
||||||
|
device_get_first_active_by_type(&USB_CDC_DEVICE_TYPE, &found);
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool usb_cdc_device_is_present(struct Device* device) {
|
||||||
|
return USB_CDC_DEVICE_API(device)->is_present(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t usb_cdc_device_build_contribution(struct Device* device, struct Device* controller,
|
||||||
|
uint8_t interface_string_index,
|
||||||
|
struct UsbInterfaceContribution* out_contribution,
|
||||||
|
const char** out_interface_string) {
|
||||||
|
return USB_CDC_DEVICE_API(device)->build_contribution(device, controller, interface_string_index, out_contribution, out_interface_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t usb_cdc_device_start_console(struct Device* device) {
|
||||||
|
return USB_CDC_DEVICE_API(device)->start_console(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t usb_cdc_device_stop_console(struct Device* device) {
|
||||||
|
return USB_CDC_DEVICE_API(device)->stop_console(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#include <tactility/drivers/usb_device_controller.h>
|
||||||
|
#include <tactility/device.h>
|
||||||
|
#include <tactility/driver.h>
|
||||||
|
|
||||||
|
#define USB_DEVICE_CONTROLLER_API(device) ((const struct UsbDeviceControllerApi*)device_get_driver(device)->api)
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
const struct DeviceType USB_DEVICE_CONTROLLER_TYPE = {
|
||||||
|
.name = "usb-device-controller",
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Device* usb_device_controller_get() {
|
||||||
|
struct Device* found = nullptr;
|
||||||
|
device_get_first_active_by_type(&USB_DEVICE_CONTROLLER_TYPE, &found);
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t usb_device_controller_begin_claim(struct Device* device) {
|
||||||
|
return USB_DEVICE_CONTROLLER_API(device)->begin_claim(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t usb_device_controller_allocate_interfaces(struct Device* device, uint8_t interface_count,
|
||||||
|
uint8_t in_endpoint_count, uint8_t out_endpoint_count,
|
||||||
|
struct UsbInterfaceAllocation* out_allocation) {
|
||||||
|
return USB_DEVICE_CONTROLLER_API(device)->allocate_interfaces(device, interface_count, in_endpoint_count, out_endpoint_count, out_allocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t usb_device_controller_claim(struct Device* device, enum UsbDeviceClass usb_class, const struct UsbDeviceClaimConfig* config) {
|
||||||
|
return USB_DEVICE_CONTROLLER_API(device)->claim(device, usb_class, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t usb_device_controller_release(struct Device* device, enum UsbDeviceClass usb_class) {
|
||||||
|
return USB_DEVICE_CONTROLLER_API(device)->release(device, usb_class);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum UsbDeviceClass usb_device_controller_get_active_class(struct Device* device) {
|
||||||
|
return USB_DEVICE_CONTROLLER_API(device)->get_active_class(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool usb_device_controller_is_cdc_enabled(struct Device* device) {
|
||||||
|
return USB_DEVICE_CONTROLLER_API(device)->is_cdc_enabled(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
#include <tactility/drivers/usb_hid_device.h>
|
||||||
|
#include <tactility/device.h>
|
||||||
|
#include <tactility/driver.h>
|
||||||
|
|
||||||
|
#define USB_HID_DEVICE_API(device) ((const struct UsbHidDeviceApi*)device_get_driver(device)->api)
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
const struct DeviceType USB_HID_DEVICE_TYPE = {
|
||||||
|
.name = "usb-hid-device",
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Device* usb_hid_device_get() {
|
||||||
|
struct Device* found = nullptr;
|
||||||
|
device_get_first_active_by_type(&USB_HID_DEVICE_TYPE, &found);
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t usb_hid_device_start(struct Device* device, enum UsbHidDeviceMode mode) {
|
||||||
|
return USB_HID_DEVICE_API(device)->start(device, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t usb_hid_device_stop(struct Device* device) {
|
||||||
|
return USB_HID_DEVICE_API(device)->stop(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t usb_hid_device_set_name(struct Device* device, const char* name) {
|
||||||
|
return USB_HID_DEVICE_API(device)->set_name(device, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t usb_hid_device_send_keyboard(struct Device* device, const uint8_t* report, size_t len) {
|
||||||
|
return USB_HID_DEVICE_API(device)->send_keyboard(device, report, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t usb_hid_device_send_consumer(struct Device* device, const uint8_t* report, size_t len) {
|
||||||
|
return USB_HID_DEVICE_API(device)->send_consumer(device, report, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t usb_hid_device_send_mouse(struct Device* device, const uint8_t* report, size_t len) {
|
||||||
|
return USB_HID_DEVICE_API(device)->send_mouse(device, report, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t usb_hid_device_send_gamepad(struct Device* device, const uint8_t* report, size_t len) {
|
||||||
|
return USB_HID_DEVICE_API(device)->send_gamepad(device, report, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool usb_hid_device_is_connected(struct Device* device) {
|
||||||
|
return USB_HID_DEVICE_API(device)->is_connected(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#include <tactility/drivers/usb_midi_device.h>
|
||||||
|
#include <tactility/device.h>
|
||||||
|
#include <tactility/driver.h>
|
||||||
|
|
||||||
|
#define USB_MIDI_DEVICE_API(device) ((const struct UsbMidiDeviceApi*)device_get_driver(device)->api)
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
const struct DeviceType USB_MIDI_DEVICE_TYPE = {
|
||||||
|
.name = "usb-midi-device",
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Device* usb_midi_device_get() {
|
||||||
|
struct Device* found = nullptr;
|
||||||
|
device_get_first_active_by_type(&USB_MIDI_DEVICE_TYPE, &found);
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t usb_midi_device_start(struct Device* device) {
|
||||||
|
return USB_MIDI_DEVICE_API(device)->start(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t usb_midi_device_stop(struct Device* device) {
|
||||||
|
return USB_MIDI_DEVICE_API(device)->stop(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t usb_midi_device_set_name(struct Device* device, const char* name) {
|
||||||
|
return USB_MIDI_DEVICE_API(device)->set_name(device, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t usb_midi_device_send(struct Device* device, const uint8_t* msg, size_t len) {
|
||||||
|
return USB_MIDI_DEVICE_API(device)->send(device, msg, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool usb_midi_device_is_connected(struct Device* device) {
|
||||||
|
return USB_MIDI_DEVICE_API(device)->is_connected(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#include <tactility/drivers/usb_msc_device.h>
|
||||||
|
#include <tactility/device.h>
|
||||||
|
#include <tactility/driver.h>
|
||||||
|
|
||||||
|
#define USB_MSC_DEVICE_API(device) ((const struct UsbMscDeviceApi*)device_get_driver(device)->api)
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
const struct DeviceType USB_MSC_DEVICE_TYPE = {
|
||||||
|
.name = "usb-msc-device",
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Device* usb_msc_device_get() {
|
||||||
|
struct Device* found = nullptr;
|
||||||
|
device_get_first_active_by_type(&USB_MSC_DEVICE_TYPE, &found);
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t usb_msc_device_start(struct Device* device, enum UsbMscDeviceSource source, void* source_handle,
|
||||||
|
UsbMscDeviceMountChangedCallback mount_changed_cb, void* context) {
|
||||||
|
return USB_MSC_DEVICE_API(device)->start(device, source, source_handle, mount_changed_cb, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t usb_msc_device_stop(struct Device* device) {
|
||||||
|
return USB_MSC_DEVICE_API(device)->stop(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool usb_msc_device_is_connected(struct Device* device) {
|
||||||
|
return USB_MSC_DEVICE_API(device)->is_connected(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
@@ -33,9 +33,13 @@
|
|||||||
#include <tactility/drivers/spi_controller.h>
|
#include <tactility/drivers/spi_controller.h>
|
||||||
#include <tactility/drivers/trackball.h>
|
#include <tactility/drivers/trackball.h>
|
||||||
#include <tactility/drivers/uart_controller.h>
|
#include <tactility/drivers/uart_controller.h>
|
||||||
|
#include <tactility/drivers/usb_device_controller.h>
|
||||||
|
#include <tactility/drivers/usb_hid_device.h>
|
||||||
#include <tactility/drivers/usb_host_hid.h>
|
#include <tactility/drivers/usb_host_hid.h>
|
||||||
#include <tactility/drivers/usb_host_midi.h>
|
#include <tactility/drivers/usb_host_midi.h>
|
||||||
#include <tactility/drivers/usb_host_msc.h>
|
#include <tactility/drivers/usb_host_msc.h>
|
||||||
|
#include <tactility/drivers/usb_midi_device.h>
|
||||||
|
#include <tactility/drivers/usb_msc_device.h>
|
||||||
#include <tactility/drivers/wifi.h>
|
#include <tactility/drivers/wifi.h>
|
||||||
#include <tactility/error.h>
|
#include <tactility/error.h>
|
||||||
#include <tactility/filesystem/file_mutex.h>
|
#include <tactility/filesystem/file_mutex.h>
|
||||||
@@ -422,6 +426,40 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = {
|
|||||||
// drivers/usb_host_msc
|
// drivers/usb_host_msc
|
||||||
DEFINE_MODULE_SYMBOL(usb_msc_eject),
|
DEFINE_MODULE_SYMBOL(usb_msc_eject),
|
||||||
DEFINE_MODULE_SYMBOL(USB_HOST_MSC_TYPE),
|
DEFINE_MODULE_SYMBOL(USB_HOST_MSC_TYPE),
|
||||||
|
// drivers/usb_device_controller
|
||||||
|
DEFINE_MODULE_SYMBOL(usb_device_controller_get),
|
||||||
|
DEFINE_MODULE_SYMBOL(usb_device_controller_begin_claim),
|
||||||
|
DEFINE_MODULE_SYMBOL(usb_device_controller_allocate_interfaces),
|
||||||
|
DEFINE_MODULE_SYMBOL(usb_device_controller_claim),
|
||||||
|
DEFINE_MODULE_SYMBOL(usb_device_controller_release),
|
||||||
|
DEFINE_MODULE_SYMBOL(usb_device_controller_get_active_class),
|
||||||
|
DEFINE_MODULE_SYMBOL(usb_device_controller_is_cdc_enabled),
|
||||||
|
DEFINE_MODULE_SYMBOL(USB_DEVICE_CONTROLLER_TYPE),
|
||||||
|
// drivers/usb_hid_device
|
||||||
|
DEFINE_MODULE_SYMBOL(usb_hid_device_get),
|
||||||
|
DEFINE_MODULE_SYMBOL(usb_hid_device_start),
|
||||||
|
DEFINE_MODULE_SYMBOL(usb_hid_device_stop),
|
||||||
|
DEFINE_MODULE_SYMBOL(usb_hid_device_set_name),
|
||||||
|
DEFINE_MODULE_SYMBOL(usb_hid_device_send_keyboard),
|
||||||
|
DEFINE_MODULE_SYMBOL(usb_hid_device_send_consumer),
|
||||||
|
DEFINE_MODULE_SYMBOL(usb_hid_device_send_mouse),
|
||||||
|
DEFINE_MODULE_SYMBOL(usb_hid_device_send_gamepad),
|
||||||
|
DEFINE_MODULE_SYMBOL(usb_hid_device_is_connected),
|
||||||
|
DEFINE_MODULE_SYMBOL(USB_HID_DEVICE_TYPE),
|
||||||
|
// drivers/usb_msc_device
|
||||||
|
DEFINE_MODULE_SYMBOL(usb_msc_device_get),
|
||||||
|
DEFINE_MODULE_SYMBOL(usb_msc_device_start),
|
||||||
|
DEFINE_MODULE_SYMBOL(usb_msc_device_stop),
|
||||||
|
DEFINE_MODULE_SYMBOL(usb_msc_device_is_connected),
|
||||||
|
DEFINE_MODULE_SYMBOL(USB_MSC_DEVICE_TYPE),
|
||||||
|
// drivers/usb_midi_device
|
||||||
|
DEFINE_MODULE_SYMBOL(usb_midi_device_get),
|
||||||
|
DEFINE_MODULE_SYMBOL(usb_midi_device_start),
|
||||||
|
DEFINE_MODULE_SYMBOL(usb_midi_device_stop),
|
||||||
|
DEFINE_MODULE_SYMBOL(usb_midi_device_set_name),
|
||||||
|
DEFINE_MODULE_SYMBOL(usb_midi_device_send),
|
||||||
|
DEFINE_MODULE_SYMBOL(usb_midi_device_is_connected),
|
||||||
|
DEFINE_MODULE_SYMBOL(USB_MIDI_DEVICE_TYPE),
|
||||||
// concurrent/dispatcher
|
// concurrent/dispatcher
|
||||||
DEFINE_MODULE_SYMBOL(dispatcher_alloc),
|
DEFINE_MODULE_SYMBOL(dispatcher_alloc),
|
||||||
DEFINE_MODULE_SYMBOL(dispatcher_free),
|
DEFINE_MODULE_SYMBOL(dispatcher_free),
|
||||||
|
|||||||
@@ -341,11 +341,34 @@ def write_touch_calibration_variables(output_file, device_properties: dict):
|
|||||||
|
|
||||||
|
|
||||||
def write_usb_variables(output_file, device_properties: dict):
|
def write_usb_variables(output_file, device_properties: dict):
|
||||||
has_tiny_usb = get_boolean_property_or_false(device_properties, "hardware.tinyUsb")
|
has_tiny_usb_msc = get_boolean_property_or_false(device_properties, "hardware.tinyUsbMsc")
|
||||||
if has_tiny_usb:
|
has_tiny_usb_hid = get_boolean_property_or_false(device_properties, "hardware.tinyUsbHid")
|
||||||
|
has_tiny_usb_midi = get_boolean_property_or_false(device_properties, "hardware.tinyUsbMidi")
|
||||||
|
has_tiny_usb_cdc = get_boolean_property_or_false(device_properties, "hardware.tinyUsbCdc")
|
||||||
|
if has_tiny_usb_msc or has_tiny_usb_hid or has_tiny_usb_midi or has_tiny_usb_cdc:
|
||||||
output_file.write("# TinyUSB\n")
|
output_file.write("# TinyUSB\n")
|
||||||
output_file.write("CONFIG_TINYUSB_MSC_ENABLED=y\n")
|
if has_tiny_usb_msc:
|
||||||
output_file.write("CONFIG_TINYUSB_MSC_MOUNT_PATH=\"/sdcard\"\n")
|
output_file.write("CONFIG_TINYUSB_MSC_ENABLED=y\n")
|
||||||
|
output_file.write("CONFIG_TINYUSB_MSC_MOUNT_PATH=\"/sdcard\"\n")
|
||||||
|
if has_tiny_usb_hid:
|
||||||
|
# TinyUSB HID is gated by an interface count, not a plain enable flag - 1 interface
|
||||||
|
# is enough for the boot-protocol keyboard the USB HID device driver installs.
|
||||||
|
output_file.write("CONFIG_TINYUSB_HID_COUNT=1\n")
|
||||||
|
if has_tiny_usb_midi:
|
||||||
|
# Same "count > 0 enables it" shape as HID. MIDI is its own claim()-able
|
||||||
|
# USB_DEVICE_CLASS_MIDI (mutually exclusive with HID/MSC at runtime, like MSC
|
||||||
|
# already is) - a board acting as a MIDI controller has no reason to also be a
|
||||||
|
# keyboard, and vice versa.
|
||||||
|
output_file.write("CONFIG_TINYUSB_MIDI_COUNT=1\n")
|
||||||
|
if has_tiny_usb_cdc:
|
||||||
|
# CDC is an independent devicetree-toggleable addon (usbdevicecdc0), not tied to any
|
||||||
|
# one primary class - the USB device controller composites it into whichever of
|
||||||
|
# HID/MIDI is active (or none) so a serial console stays reachable even while the
|
||||||
|
# port is also presenting as a HID/MIDI device. MSC is deliberately excluded (a MSC
|
||||||
|
# device should look like plain mass storage, no console). See
|
||||||
|
# esp32_usb_cdc_device.cpp / esp32_usb_device_controller.cpp. This flag only needs to
|
||||||
|
# be true if the board's .dts actually has an enabled usbdevicecdc0 node.
|
||||||
|
output_file.write("CONFIG_TINYUSB_CDC_ENABLED=y\n")
|
||||||
idf_target = get_property_or_exit(device_properties, "hardware.target").lower()
|
idf_target = get_property_or_exit(device_properties, "hardware.target").lower()
|
||||||
if idf_target == "esp32p4":
|
if idf_target == "esp32p4":
|
||||||
# P4 has two USB-DWC controllers (HS/UTMI and FS/FSLS). esp_tinyusb defaults to
|
# P4 has two USB-DWC controllers (HS/UTMI and FS/FSLS). esp_tinyusb defaults to
|
||||||
|
|||||||
Reference in New Issue
Block a user