mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-06-19 04:15:06 +00:00
USB host support (#527)
This commit is contained in:
parent
a59fbf4ed5
commit
3dcc95f6f3
2
.github/actions/build-firmware/action.yml
vendored
2
.github/actions/build-firmware/action.yml
vendored
@ -20,7 +20,7 @@ runs:
|
||||
- name: 'Build'
|
||||
uses: espressif/esp-idf-ci-action@v1
|
||||
with:
|
||||
esp_idf_version: v5.5
|
||||
esp_idf_version: v5.5.2
|
||||
target: ${{ inputs.arch }}
|
||||
path: './'
|
||||
- name: 'Release'
|
||||
|
||||
6
.github/actions/build-sdk/action.yml
vendored
6
.github/actions/build-sdk/action.yml
vendored
@ -21,14 +21,14 @@ runs:
|
||||
uses: espressif/esp-idf-ci-action@v1
|
||||
with:
|
||||
# NOTE: Update with ESP-IDF!
|
||||
esp_idf_version: v5.5
|
||||
esp_idf_version: v5.5.2
|
||||
target: ${{ inputs.arch }}
|
||||
path: './'
|
||||
- name: 'Release'
|
||||
shell: bash
|
||||
env:
|
||||
# NOTE: Update with ESP-IDF!
|
||||
ESP_IDF_VERSION: '5.5'
|
||||
ESP_IDF_VERSION: '5.5.2'
|
||||
run: python Buildscripts/release-sdk.py release/TactilitySDK
|
||||
- name: 'Test Integration Prep'
|
||||
shell: bash
|
||||
@ -41,7 +41,7 @@ runs:
|
||||
- name: 'Test Integration'
|
||||
uses: espressif/esp-idf-ci-action@v1
|
||||
with:
|
||||
esp_idf_version: v5.5
|
||||
esp_idf_version: v5.5.2
|
||||
target: ${{ inputs.arch }}
|
||||
command: export TACTILITY_SDK_PATH=../../test_sdk && cd Tests/SdkIntegration && python tactility.py build ${{ inputs.arch }} --local-sdk
|
||||
- name: 'Upload Artifact'
|
||||
|
||||
@ -2,6 +2,7 @@ idf_component_register(
|
||||
INCLUDE_DIRS
|
||||
"Libraries/TactilityC/include"
|
||||
"Libraries/TactilityKernel/include"
|
||||
"Libraries/TactilityFreeRtos/include"
|
||||
"Libraries/lvgl/include"
|
||||
"Modules/lvgl-module/include"
|
||||
"Drivers/bm8563-module/include"
|
||||
|
||||
@ -11,7 +11,9 @@ macro(tactility_project project_name)
|
||||
project_elf($project_name)
|
||||
|
||||
file(READ ${TACTILITY_SDK_PATH}/idf-version.txt TACTILITY_SDK_IDF_VERSION)
|
||||
if (NOT "$ENV{ESP_IDF_VERSION}" STREQUAL "${TACTILITY_SDK_IDF_VERSION}")
|
||||
string(REGEX REPLACE "^([0-9]+\\.[0-9]+).*" "\\1" TACTILITY_SDK_IDF_MAJOR_MINOR "${TACTILITY_SDK_IDF_VERSION}")
|
||||
string(REGEX REPLACE "^([0-9]+\\.[0-9]+).*" "\\1" CURRENT_IDF_MAJOR_MINOR "$ENV{ESP_IDF_VERSION}")
|
||||
if (NOT "${CURRENT_IDF_MAJOR_MINOR}" STREQUAL "${TACTILITY_SDK_IDF_MAJOR_MINOR}")
|
||||
message(FATAL_ERROR "ESP-IDF version of Tactility SDK (${TACTILITY_SDK_IDF_VERSION}) does not match current ESP-IDF version ($ENV{ESP_IDF_VERSION})")
|
||||
endif()
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
#include <tactility/bindings/esp32_ble.h>
|
||||
#include <tactility/bindings/esp32_gpio.h>
|
||||
#include <tactility/bindings/esp32_i2c.h>
|
||||
#include <tactility/bindings/esp32_usbhost.h>
|
||||
|
||||
// Reference: https://github.com/bigtreetech/PandaTouch_PlatformIO/blob/master/docs/PINOUT.md
|
||||
/ {
|
||||
@ -34,4 +35,23 @@
|
||||
pin-sda = <&gpio0 4 GPIO_FLAG_NONE>;
|
||||
pin-scl = <&gpio0 3 GPIO_FLAG_NONE>;
|
||||
};
|
||||
|
||||
usbhost0 {
|
||||
compatible = "espressif,esp32-usbhost";
|
||||
|
||||
usbhosthid0 {
|
||||
compatible = "espressif,esp32-usbhost-hid";
|
||||
//status = "disabled";
|
||||
};
|
||||
|
||||
usbhostmidi0 {
|
||||
compatible = "espressif,esp32-usbhost-midi";
|
||||
//status = "disabled";
|
||||
};
|
||||
|
||||
usbhostmsc0 {
|
||||
compatible = "espressif,esp32-usbhost-msc";
|
||||
//status = "disabled";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@ -13,6 +13,7 @@ spiRamMode=OCT
|
||||
spiRamSpeed=120M
|
||||
esptoolFlashFreq=120M
|
||||
bluetooth=true
|
||||
usbHostEnabled=true
|
||||
|
||||
[display]
|
||||
size=5"
|
||||
|
||||
@ -77,8 +77,8 @@ std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
|
||||
.mirrorY = false,
|
||||
.invertColor = false,
|
||||
.bufferSize = 0, // 0 = default (1/10 of screen)
|
||||
.swRotate = (variant == Tab5Variant::St7123), // ST7123 MIPI-DSI panel does not support hardware swap_xy
|
||||
.buffSpiram = (variant == Tab5Variant::St7123), // sw_rotate needs a 3rd buffer; use PSRAM to avoid OOM in internal SRAM
|
||||
.swRotate = true,
|
||||
.buffSpiram = true,
|
||||
.touch = touch,
|
||||
.backlightDutyFunction = driver::pwmbacklight::setBacklightDuty,
|
||||
.resetPin = LCD_PIN_RESET,
|
||||
|
||||
@ -13,6 +13,7 @@ spiRamMode=HEX
|
||||
spiRamSpeed=200M
|
||||
esptoolFlashFreq=80M
|
||||
bluetooth=true
|
||||
usbHostEnabled=true
|
||||
|
||||
[display]
|
||||
size=5"
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
#include <tactility/bindings/esp32_i2s.h>
|
||||
#include <tactility/bindings/esp32_spi.h>
|
||||
#include <tactility/bindings/esp32_uart.h>
|
||||
#include <tactility/bindings/esp32_usbhost.h>
|
||||
#include <bindings/bmi270.h>
|
||||
#include <bindings/ina226.h>
|
||||
#include <bindings/pi4ioe5v6408.h>
|
||||
@ -93,4 +94,24 @@
|
||||
pin-tx = <&gpio0 53 GPIO_FLAG_NONE>;
|
||||
pin-rx = <&gpio0 54 GPIO_FLAG_NONE>;
|
||||
};
|
||||
|
||||
usbhost0 {
|
||||
compatible = "espressif,esp32-usbhost";
|
||||
peripheral-map = <0>;
|
||||
|
||||
usbhosthid0 {
|
||||
compatible = "espressif,esp32-usbhost-hid";
|
||||
//status = "disabled";
|
||||
};
|
||||
|
||||
usbhostmidi0 {
|
||||
compatible = "espressif,esp32-usbhost-midi";
|
||||
//status = "disabled";
|
||||
};
|
||||
|
||||
usbhostmsc0 {
|
||||
compatible = "espressif,esp32-usbhost-msc";
|
||||
//status = "disabled";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@ -73,5 +73,13 @@ dependencies:
|
||||
rules:
|
||||
# More hardware might be supported - enable as needed
|
||||
- if: "target in [esp32s3]"
|
||||
idf: '5.5'
|
||||
espressif/usb_host_hid:
|
||||
version: "1.1.0"
|
||||
rules:
|
||||
- if: "target in [esp32s3, esp32p4]"
|
||||
espressif/usb_host_msc:
|
||||
version: "1.1.4"
|
||||
rules:
|
||||
- if: "target in [esp32s3, esp32p4]"
|
||||
idf: '5.5.2'
|
||||
|
||||
|
||||
@ -145,6 +145,7 @@ statusbar_symbol_code_point_names = [
|
||||
"bluetooth_searching",
|
||||
"bluetooth_connected",
|
||||
"bluetooth_disabled",
|
||||
"usb",
|
||||
]
|
||||
|
||||
# Get more from https://fonts.google.com/icons?icon.set=Material+Symbols&icon.style=Rounded
|
||||
|
||||
@ -24,3 +24,4 @@
|
||||
#define LVGL_ICON_STATUSBAR_BLUETOOTH_SEARCHING "\xEE\x98\x8F"
|
||||
#define LVGL_ICON_STATUSBAR_BLUETOOTH_CONNECTED "\xEE\x86\xA8"
|
||||
#define LVGL_ICON_STATUSBAR_BLUETOOTH_DISABLED "\xEE\x86\xA9"
|
||||
#define LVGL_ICON_STATUSBAR_USB "\xEE\x87\xA0"
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*******************************************************************************
|
||||
* Size: 12 px
|
||||
* Bpp: 2
|
||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 12 --font MaterialSymbolsRounded.ttf -r 0xF1DB,0xF15C,0xE322,0xE623,0xF057,0xF0B0,0xEBE4,0xEBD6,0xEBE1,0xF065,0xE1DA,0xF064,0xF257,0xF256,0xF255,0xF254,0xF253,0xF252,0xF24F,0xF250,0xE1A7,0xE60F,0xE1A8,0xE1A9 --format lvgl -o ../source-fonts/material_symbols_statusbar_12.c --force-fast-kern-format
|
||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 12 --font MaterialSymbolsRounded.ttf -r 0xF1DB,0xF15C,0xE322,0xE623,0xF057,0xF0B0,0xEBE4,0xEBD6,0xEBE1,0xF065,0xE1DA,0xF064,0xF257,0xF256,0xF255,0xF254,0xF253,0xF252,0xF24F,0xF250,0xE1A7,0xE60F,0xE1A8,0xE1A9,0xE1E0 --format lvgl -o ../source-fonts/material_symbols_statusbar_12.c --force-fast-kern-format
|
||||
******************************************************************************/
|
||||
|
||||
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
||||
@ -46,6 +46,11 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
|
||||
0x0, 0x3c, 0xe0, 0x0, 0x0, 0x34, 0x0, 0x0,
|
||||
0x0,
|
||||
|
||||
/* U+E1E0 "" */
|
||||
0x2, 0x0, 0x7, 0x80, 0x3, 0x0, 0x73, 0x3c,
|
||||
0xb3, 0x3c, 0x33, 0x18, 0x3f, 0xf4, 0x3, 0x0,
|
||||
0x3, 0x40, 0x3, 0x40,
|
||||
|
||||
/* U+E322 "" */
|
||||
0x0, 0x0, 0x0, 0x77, 0x40, 0x2e, 0xab, 0x3,
|
||||
0x45, 0x34, 0x77, 0xf3, 0x83, 0x77, 0x34, 0x75,
|
||||
@ -166,26 +171,27 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
|
||||
{.bitmap_index = 18, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 43, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = 0},
|
||||
{.bitmap_index = 74, .adv_w = 192, .box_w = 12, .box_h = 11, .ofs_x = 0, .ofs_y = 0},
|
||||
{.bitmap_index = 107, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 132, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 157, .adv_w = 192, .box_w = 8, .box_h = 10, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 177, .adv_w = 192, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 1},
|
||||
{.bitmap_index = 204, .adv_w = 192, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 1},
|
||||
{.bitmap_index = 231, .adv_w = 192, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 1},
|
||||
{.bitmap_index = 258, .adv_w = 192, .box_w = 8, .box_h = 10, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 278, .adv_w = 192, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 1},
|
||||
{.bitmap_index = 305, .adv_w = 192, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 1},
|
||||
{.bitmap_index = 332, .adv_w = 192, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 1},
|
||||
{.bitmap_index = 359, .adv_w = 192, .box_w = 12, .box_h = 8, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 383, .adv_w = 192, .box_w = 8, .box_h = 10, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 403, .adv_w = 192, .box_w = 12, .box_h = 6, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 421, .adv_w = 192, .box_w = 12, .box_h = 6, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 439, .adv_w = 192, .box_w = 12, .box_h = 6, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 457, .adv_w = 192, .box_w = 12, .box_h = 6, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 475, .adv_w = 192, .box_w = 12, .box_h = 6, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 493, .adv_w = 192, .box_w = 12, .box_h = 6, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 511, .adv_w = 192, .box_w = 12, .box_h = 6, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 529, .adv_w = 192, .box_w = 12, .box_h = 6, .ofs_x = 0, .ofs_y = 3}
|
||||
{.bitmap_index = 107, .adv_w = 192, .box_w = 8, .box_h = 10, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 127, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 152, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 177, .adv_w = 192, .box_w = 8, .box_h = 10, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 197, .adv_w = 192, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 1},
|
||||
{.bitmap_index = 224, .adv_w = 192, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 1},
|
||||
{.bitmap_index = 251, .adv_w = 192, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 1},
|
||||
{.bitmap_index = 278, .adv_w = 192, .box_w = 8, .box_h = 10, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 298, .adv_w = 192, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 1},
|
||||
{.bitmap_index = 325, .adv_w = 192, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 1},
|
||||
{.bitmap_index = 352, .adv_w = 192, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 1},
|
||||
{.bitmap_index = 379, .adv_w = 192, .box_w = 12, .box_h = 8, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 403, .adv_w = 192, .box_w = 8, .box_h = 10, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 423, .adv_w = 192, .box_w = 12, .box_h = 6, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 441, .adv_w = 192, .box_w = 12, .box_h = 6, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 459, .adv_w = 192, .box_w = 12, .box_h = 6, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 477, .adv_w = 192, .box_w = 12, .box_h = 6, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 495, .adv_w = 192, .box_w = 12, .box_h = 6, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 513, .adv_w = 192, .box_w = 12, .box_h = 6, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 531, .adv_w = 192, .box_w = 12, .box_h = 6, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 549, .adv_w = 192, .box_w = 12, .box_h = 6, .ofs_x = 0, .ofs_y = 3}
|
||||
};
|
||||
|
||||
/*---------------------
|
||||
@ -193,9 +199,10 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
|
||||
*--------------------*/
|
||||
|
||||
static const uint16_t unicode_list_0[] = {
|
||||
0x0, 0x1, 0x2, 0x33, 0x17b, 0x468, 0x47c, 0xa2f,
|
||||
0xa3a, 0xa3d, 0xeb0, 0xebd, 0xebe, 0xf09, 0xfb5, 0x1034,
|
||||
0x10a8, 0x10a9, 0x10ab, 0x10ac, 0x10ad, 0x10ae, 0x10af, 0x10b0
|
||||
0x0, 0x1, 0x2, 0x33, 0x39, 0x17b, 0x468, 0x47c,
|
||||
0xa2f, 0xa3a, 0xa3d, 0xeb0, 0xebd, 0xebe, 0xf09, 0xfb5,
|
||||
0x1034, 0x10a8, 0x10a9, 0x10ab, 0x10ac, 0x10ad, 0x10ae, 0x10af,
|
||||
0x10b0
|
||||
};
|
||||
|
||||
/*Collect the unicode lists and glyph_id offsets*/
|
||||
@ -203,7 +210,7 @@ static const lv_font_fmt_txt_cmap_t cmaps[] =
|
||||
{
|
||||
{
|
||||
.range_start = 57767, .range_length = 4273, .glyph_id_start = 1,
|
||||
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 24, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
|
||||
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 25, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*******************************************************************************
|
||||
* Size: 16 px
|
||||
* Bpp: 2
|
||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 16 --font MaterialSymbolsRounded.ttf -r 0xF1DB,0xF15C,0xE322,0xE623,0xF057,0xF0B0,0xEBE4,0xEBD6,0xEBE1,0xF065,0xE1DA,0xF064,0xF257,0xF256,0xF255,0xF254,0xF253,0xF252,0xF24F,0xF250,0xE1A7,0xE60F,0xE1A8,0xE1A9 --format lvgl -o ../source-fonts/material_symbols_statusbar_16.c --force-fast-kern-format
|
||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 16 --font MaterialSymbolsRounded.ttf -r 0xF1DB,0xF15C,0xE322,0xE623,0xF057,0xF0B0,0xEBE4,0xEBD6,0xEBE1,0xF065,0xE1DA,0xF064,0xF257,0xF256,0xF255,0xF254,0xF253,0xF252,0xF24F,0xF250,0xE1A7,0xE60F,0xE1A8,0xE1A9,0xE1E0 --format lvgl -o ../source-fonts/material_symbols_statusbar_16.c --force-fast-kern-format
|
||||
******************************************************************************/
|
||||
|
||||
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
||||
@ -54,6 +54,13 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
|
||||
0x0, 0x1, 0x41, 0xd0, 0x0, 0x0, 0x0, 0x70,
|
||||
0x0, 0x0, 0x0, 0x0,
|
||||
|
||||
/* U+E1E0 "" */
|
||||
0x0, 0x0, 0x0, 0xe, 0x0, 0x2, 0xf0, 0x0,
|
||||
0xe, 0x0, 0x10, 0xd1, 0x5b, 0xcd, 0x3e, 0x78,
|
||||
0xd3, 0xe3, 0x4d, 0x1c, 0x39, 0xe6, 0xc2, 0xff,
|
||||
0xf4, 0x0, 0xd0, 0x0, 0xe, 0x0, 0x1, 0xf0,
|
||||
0x0, 0xe, 0x0,
|
||||
|
||||
/* U+E322 "" */
|
||||
0x0, 0x21, 0x40, 0x0, 0x6e, 0xf5, 0x0, 0xff,
|
||||
0xff, 0xd0, 0x34, 0x0, 0x34, 0x3d, 0x3f, 0x8f,
|
||||
@ -210,26 +217,27 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
|
||||
{.bitmap_index = 32, .adv_w = 256, .box_w = 12, .box_h = 14, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 74, .adv_w = 256, .box_w = 13, .box_h = 14, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 120, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = 0},
|
||||
{.bitmap_index = 180, .adv_w = 256, .box_w = 13, .box_h = 13, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 223, .adv_w = 256, .box_w = 12, .box_h = 14, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 265, .adv_w = 256, .box_w = 12, .box_h = 14, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 307, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 355, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 403, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 451, .adv_w = 256, .box_w = 12, .box_h = 14, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 493, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 541, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 589, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 637, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 685, .adv_w = 256, .box_w = 12, .box_h = 14, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 727, .adv_w = 256, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 4},
|
||||
{.bitmap_index = 759, .adv_w = 256, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 4},
|
||||
{.bitmap_index = 791, .adv_w = 256, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 4},
|
||||
{.bitmap_index = 823, .adv_w = 256, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 4},
|
||||
{.bitmap_index = 855, .adv_w = 256, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 4},
|
||||
{.bitmap_index = 887, .adv_w = 256, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 4},
|
||||
{.bitmap_index = 919, .adv_w = 256, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 4},
|
||||
{.bitmap_index = 951, .adv_w = 256, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 4}
|
||||
{.bitmap_index = 180, .adv_w = 256, .box_w = 10, .box_h = 14, .ofs_x = 3, .ofs_y = 1},
|
||||
{.bitmap_index = 215, .adv_w = 256, .box_w = 13, .box_h = 13, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 258, .adv_w = 256, .box_w = 12, .box_h = 14, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 300, .adv_w = 256, .box_w = 12, .box_h = 14, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 342, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 390, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 438, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 486, .adv_w = 256, .box_w = 12, .box_h = 14, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 528, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 576, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 624, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 672, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 720, .adv_w = 256, .box_w = 12, .box_h = 14, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 762, .adv_w = 256, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 4},
|
||||
{.bitmap_index = 794, .adv_w = 256, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 4},
|
||||
{.bitmap_index = 826, .adv_w = 256, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 4},
|
||||
{.bitmap_index = 858, .adv_w = 256, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 4},
|
||||
{.bitmap_index = 890, .adv_w = 256, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 4},
|
||||
{.bitmap_index = 922, .adv_w = 256, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 4},
|
||||
{.bitmap_index = 954, .adv_w = 256, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 4},
|
||||
{.bitmap_index = 986, .adv_w = 256, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 4}
|
||||
};
|
||||
|
||||
/*---------------------
|
||||
@ -237,9 +245,10 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
|
||||
*--------------------*/
|
||||
|
||||
static const uint16_t unicode_list_0[] = {
|
||||
0x0, 0x1, 0x2, 0x33, 0x17b, 0x468, 0x47c, 0xa2f,
|
||||
0xa3a, 0xa3d, 0xeb0, 0xebd, 0xebe, 0xf09, 0xfb5, 0x1034,
|
||||
0x10a8, 0x10a9, 0x10ab, 0x10ac, 0x10ad, 0x10ae, 0x10af, 0x10b0
|
||||
0x0, 0x1, 0x2, 0x33, 0x39, 0x17b, 0x468, 0x47c,
|
||||
0xa2f, 0xa3a, 0xa3d, 0xeb0, 0xebd, 0xebe, 0xf09, 0xfb5,
|
||||
0x1034, 0x10a8, 0x10a9, 0x10ab, 0x10ac, 0x10ad, 0x10ae, 0x10af,
|
||||
0x10b0
|
||||
};
|
||||
|
||||
/*Collect the unicode lists and glyph_id offsets*/
|
||||
@ -247,7 +256,7 @@ static const lv_font_fmt_txt_cmap_t cmaps[] =
|
||||
{
|
||||
{
|
||||
.range_start = 57767, .range_length = 4273, .glyph_id_start = 1,
|
||||
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 24, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
|
||||
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 25, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*******************************************************************************
|
||||
* Size: 20 px
|
||||
* Bpp: 2
|
||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 20 --font MaterialSymbolsRounded.ttf -r 0xF1DB,0xF15C,0xE322,0xE623,0xF057,0xF0B0,0xEBE4,0xEBD6,0xEBE1,0xF065,0xE1DA,0xF064,0xF257,0xF256,0xF255,0xF254,0xF253,0xF252,0xF24F,0xF250,0xE1A7,0xE60F,0xE1A8,0xE1A9 --format lvgl -o ../source-fonts/material_symbols_statusbar_20.c --force-fast-kern-format
|
||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 20 --font MaterialSymbolsRounded.ttf -r 0xF1DB,0xF15C,0xE322,0xE623,0xF057,0xF0B0,0xEBE4,0xEBD6,0xEBE1,0xF065,0xE1DA,0xF064,0xF257,0xF256,0xF255,0xF254,0xF253,0xF252,0xF24F,0xF250,0xE1A7,0xE60F,0xE1A8,0xE1A9,0xE1E0 --format lvgl -o ../source-fonts/material_symbols_statusbar_20.c --force-fast-kern-format
|
||||
******************************************************************************/
|
||||
|
||||
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
||||
@ -66,6 +66,15 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
|
||||
0x0, 0x0, 0x0, 0x0, 0xe0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0,
|
||||
|
||||
/* U+E1E0 "" */
|
||||
0x0, 0x0, 0x0, 0x0, 0x28, 0x0, 0x0, 0x7d,
|
||||
0x0, 0x0, 0xff, 0x0, 0x0, 0x38, 0x0, 0x10,
|
||||
0x38, 0x14, 0xbc, 0x38, 0x7f, 0xfd, 0x38, 0x7f,
|
||||
0x7c, 0x38, 0x7f, 0x38, 0x38, 0x2c, 0x3d, 0x7d,
|
||||
0x6c, 0x2f, 0xff, 0xfc, 0x5, 0x7d, 0x50, 0x0,
|
||||
0x38, 0x0, 0x0, 0x3c, 0x0, 0x0, 0xbe, 0x0,
|
||||
0x0, 0x7d, 0x0, 0x0, 0x0, 0x0,
|
||||
|
||||
/* U+E322 "" */
|
||||
0x0, 0x0, 0x10, 0x0, 0x0, 0x2c, 0x34, 0x0,
|
||||
0x2, 0xbe, 0xfe, 0x80, 0xf, 0xff, 0xff, 0xe0,
|
||||
@ -276,26 +285,27 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
|
||||
{.bitmap_index = 44, .adv_w = 320, .box_w = 16, .box_h = 16, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 108, .adv_w = 320, .box_w = 17, .box_h = 17, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 181, .adv_w = 320, .box_w = 20, .box_h = 18, .ofs_x = 0, .ofs_y = 0},
|
||||
{.bitmap_index = 271, .adv_w = 320, .box_w = 16, .box_h = 16, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 335, .adv_w = 320, .box_w = 16, .box_h = 16, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 399, .adv_w = 320, .box_w = 14, .box_h = 18, .ofs_x = 3, .ofs_y = 1},
|
||||
{.bitmap_index = 462, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 532, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 602, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 672, .adv_w = 320, .box_w = 14, .box_h = 18, .ofs_x = 3, .ofs_y = 1},
|
||||
{.bitmap_index = 735, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 805, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 875, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 945, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 1015, .adv_w = 320, .box_w = 14, .box_h = 17, .ofs_x = 3, .ofs_y = 2},
|
||||
{.bitmap_index = 1075, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 5},
|
||||
{.bitmap_index = 1125, .adv_w = 320, .box_w = 19, .box_h = 10, .ofs_x = 0, .ofs_y = 5},
|
||||
{.bitmap_index = 1173, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 5},
|
||||
{.bitmap_index = 1223, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 5},
|
||||
{.bitmap_index = 1273, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 5},
|
||||
{.bitmap_index = 1323, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 5},
|
||||
{.bitmap_index = 1373, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 5},
|
||||
{.bitmap_index = 1423, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 5}
|
||||
{.bitmap_index = 271, .adv_w = 320, .box_w = 12, .box_h = 18, .ofs_x = 4, .ofs_y = 1},
|
||||
{.bitmap_index = 325, .adv_w = 320, .box_w = 16, .box_h = 16, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 389, .adv_w = 320, .box_w = 16, .box_h = 16, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 453, .adv_w = 320, .box_w = 14, .box_h = 18, .ofs_x = 3, .ofs_y = 1},
|
||||
{.bitmap_index = 516, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 586, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 656, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 726, .adv_w = 320, .box_w = 14, .box_h = 18, .ofs_x = 3, .ofs_y = 1},
|
||||
{.bitmap_index = 789, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 859, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 929, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 999, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 1069, .adv_w = 320, .box_w = 14, .box_h = 17, .ofs_x = 3, .ofs_y = 2},
|
||||
{.bitmap_index = 1129, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 5},
|
||||
{.bitmap_index = 1179, .adv_w = 320, .box_w = 19, .box_h = 10, .ofs_x = 0, .ofs_y = 5},
|
||||
{.bitmap_index = 1227, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 5},
|
||||
{.bitmap_index = 1277, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 5},
|
||||
{.bitmap_index = 1327, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 5},
|
||||
{.bitmap_index = 1377, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 5},
|
||||
{.bitmap_index = 1427, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 5},
|
||||
{.bitmap_index = 1477, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 5}
|
||||
};
|
||||
|
||||
/*---------------------
|
||||
@ -303,9 +313,10 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
|
||||
*--------------------*/
|
||||
|
||||
static const uint16_t unicode_list_0[] = {
|
||||
0x0, 0x1, 0x2, 0x33, 0x17b, 0x468, 0x47c, 0xa2f,
|
||||
0xa3a, 0xa3d, 0xeb0, 0xebd, 0xebe, 0xf09, 0xfb5, 0x1034,
|
||||
0x10a8, 0x10a9, 0x10ab, 0x10ac, 0x10ad, 0x10ae, 0x10af, 0x10b0
|
||||
0x0, 0x1, 0x2, 0x33, 0x39, 0x17b, 0x468, 0x47c,
|
||||
0xa2f, 0xa3a, 0xa3d, 0xeb0, 0xebd, 0xebe, 0xf09, 0xfb5,
|
||||
0x1034, 0x10a8, 0x10a9, 0x10ab, 0x10ac, 0x10ad, 0x10ae, 0x10af,
|
||||
0x10b0
|
||||
};
|
||||
|
||||
/*Collect the unicode lists and glyph_id offsets*/
|
||||
@ -313,7 +324,7 @@ static const lv_font_fmt_txt_cmap_t cmaps[] =
|
||||
{
|
||||
{
|
||||
.range_start = 57767, .range_length = 4273, .glyph_id_start = 1,
|
||||
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 24, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
|
||||
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 25, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*******************************************************************************
|
||||
* Size: 30 px
|
||||
* Bpp: 2
|
||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 30 --font MaterialSymbolsRounded.ttf -r 0xF1DB,0xF15C,0xE322,0xE623,0xF057,0xF0B0,0xEBE4,0xEBD6,0xEBE1,0xF065,0xE1DA,0xF064,0xF257,0xF256,0xF255,0xF254,0xF253,0xF252,0xF24F,0xF250,0xE1A7,0xE60F,0xE1A8,0xE1A9 --format lvgl -o ../source-fonts/material_symbols_statusbar_30.c --force-fast-kern-format
|
||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 30 --font MaterialSymbolsRounded.ttf -r 0xF1DB,0xF15C,0xE322,0xE623,0xF057,0xF0B0,0xEBE4,0xEBD6,0xEBE1,0xF065,0xE1DA,0xF064,0xF257,0xF256,0xF255,0xF254,0xF253,0xF252,0xF24F,0xF250,0xE1A7,0xE60F,0xE1A8,0xE1A9,0xE1E0 --format lvgl -o ../source-fonts/material_symbols_statusbar_30.c --force-fast-kern-format
|
||||
******************************************************************************/
|
||||
|
||||
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
||||
@ -104,6 +104,23 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
|
||||
0x0, 0x0, 0xf8, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x2, 0x0,
|
||||
|
||||
/* U+E1E0 "" */
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe, 0x0,
|
||||
0x0, 0x0, 0x2, 0xf4, 0x0, 0x0, 0x0, 0x7f,
|
||||
0xc0, 0x0, 0x0, 0xf, 0xff, 0x0, 0x0, 0x0,
|
||||
0x6f, 0x90, 0x0, 0x0, 0x1, 0xf0, 0x0, 0x0,
|
||||
0x0, 0x1f, 0x0, 0x0, 0x2f, 0x1, 0xf0, 0x3f,
|
||||
0xfb, 0xfc, 0x1f, 0x3, 0xff, 0xbf, 0xc1, 0xf0,
|
||||
0x3f, 0xfb, 0xfc, 0x1f, 0x3, 0xff, 0x2f, 0x1,
|
||||
0xf0, 0x3f, 0xf2, 0xf0, 0x1f, 0x0, 0xf8, 0x2f,
|
||||
0x1, 0xf0, 0xf, 0x81, 0xfa, 0xaf, 0xaa, 0xf8,
|
||||
0x1f, 0xff, 0xff, 0xff, 0x40, 0x7f, 0xff, 0xff,
|
||||
0xe0, 0x0, 0x1, 0xf0, 0x0, 0x0, 0x0, 0x1f,
|
||||
0x0, 0x0, 0x0, 0x1, 0xf0, 0x0, 0x0, 0x0,
|
||||
0x3f, 0xc0, 0x0, 0x0, 0xb, 0xfc, 0x0, 0x0,
|
||||
0x0, 0xbf, 0xc0, 0x0, 0x0, 0x3, 0xf8, 0x0,
|
||||
0x0, 0x0, 0x5, 0x0, 0x0,
|
||||
|
||||
/* U+E322 "" */
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0xb4, 0x2d, 0x0, 0x0, 0x0, 0x0, 0xf8, 0x3e,
|
||||
@ -486,26 +503,27 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
|
||||
{.bitmap_index = 96, .adv_w = 480, .box_w = 22, .box_h = 24, .ofs_x = 4, .ofs_y = 3},
|
||||
{.bitmap_index = 228, .adv_w = 480, .box_w = 25, .box_h = 25, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 385, .adv_w = 480, .box_w = 30, .box_h = 26, .ofs_x = 0, .ofs_y = 1},
|
||||
{.bitmap_index = 580, .adv_w = 480, .box_w = 24, .box_h = 24, .ofs_x = 3, .ofs_y = 3},
|
||||
{.bitmap_index = 724, .adv_w = 480, .box_w = 23, .box_h = 24, .ofs_x = 4, .ofs_y = 3},
|
||||
{.bitmap_index = 862, .adv_w = 480, .box_w = 20, .box_h = 26, .ofs_x = 5, .ofs_y = 2},
|
||||
{.bitmap_index = 992, .adv_w = 480, .box_w = 28, .box_h = 21, .ofs_x = 1, .ofs_y = 4},
|
||||
{.bitmap_index = 1139, .adv_w = 480, .box_w = 28, .box_h = 21, .ofs_x = 1, .ofs_y = 4},
|
||||
{.bitmap_index = 1286, .adv_w = 480, .box_w = 28, .box_h = 21, .ofs_x = 1, .ofs_y = 4},
|
||||
{.bitmap_index = 1433, .adv_w = 480, .box_w = 20, .box_h = 26, .ofs_x = 5, .ofs_y = 2},
|
||||
{.bitmap_index = 1563, .adv_w = 480, .box_w = 28, .box_h = 21, .ofs_x = 1, .ofs_y = 4},
|
||||
{.bitmap_index = 1710, .adv_w = 480, .box_w = 28, .box_h = 21, .ofs_x = 1, .ofs_y = 4},
|
||||
{.bitmap_index = 1857, .adv_w = 480, .box_w = 28, .box_h = 21, .ofs_x = 1, .ofs_y = 4},
|
||||
{.bitmap_index = 2004, .adv_w = 480, .box_w = 28, .box_h = 20, .ofs_x = 1, .ofs_y = 5},
|
||||
{.bitmap_index = 2144, .adv_w = 480, .box_w = 20, .box_h = 25, .ofs_x = 5, .ofs_y = 3},
|
||||
{.bitmap_index = 2269, .adv_w = 480, .box_w = 28, .box_h = 16, .ofs_x = 1, .ofs_y = 7},
|
||||
{.bitmap_index = 2381, .adv_w = 480, .box_w = 28, .box_h = 16, .ofs_x = 1, .ofs_y = 7},
|
||||
{.bitmap_index = 2493, .adv_w = 480, .box_w = 28, .box_h = 16, .ofs_x = 1, .ofs_y = 7},
|
||||
{.bitmap_index = 2605, .adv_w = 480, .box_w = 28, .box_h = 16, .ofs_x = 1, .ofs_y = 7},
|
||||
{.bitmap_index = 2717, .adv_w = 480, .box_w = 28, .box_h = 16, .ofs_x = 1, .ofs_y = 7},
|
||||
{.bitmap_index = 2829, .adv_w = 480, .box_w = 28, .box_h = 16, .ofs_x = 1, .ofs_y = 7},
|
||||
{.bitmap_index = 2941, .adv_w = 480, .box_w = 28, .box_h = 16, .ofs_x = 1, .ofs_y = 7},
|
||||
{.bitmap_index = 3053, .adv_w = 480, .box_w = 28, .box_h = 16, .ofs_x = 1, .ofs_y = 7}
|
||||
{.bitmap_index = 580, .adv_w = 480, .box_w = 18, .box_h = 26, .ofs_x = 6, .ofs_y = 2},
|
||||
{.bitmap_index = 697, .adv_w = 480, .box_w = 24, .box_h = 24, .ofs_x = 3, .ofs_y = 3},
|
||||
{.bitmap_index = 841, .adv_w = 480, .box_w = 23, .box_h = 24, .ofs_x = 4, .ofs_y = 3},
|
||||
{.bitmap_index = 979, .adv_w = 480, .box_w = 20, .box_h = 26, .ofs_x = 5, .ofs_y = 2},
|
||||
{.bitmap_index = 1109, .adv_w = 480, .box_w = 28, .box_h = 21, .ofs_x = 1, .ofs_y = 4},
|
||||
{.bitmap_index = 1256, .adv_w = 480, .box_w = 28, .box_h = 21, .ofs_x = 1, .ofs_y = 4},
|
||||
{.bitmap_index = 1403, .adv_w = 480, .box_w = 28, .box_h = 21, .ofs_x = 1, .ofs_y = 4},
|
||||
{.bitmap_index = 1550, .adv_w = 480, .box_w = 20, .box_h = 26, .ofs_x = 5, .ofs_y = 2},
|
||||
{.bitmap_index = 1680, .adv_w = 480, .box_w = 28, .box_h = 21, .ofs_x = 1, .ofs_y = 4},
|
||||
{.bitmap_index = 1827, .adv_w = 480, .box_w = 28, .box_h = 21, .ofs_x = 1, .ofs_y = 4},
|
||||
{.bitmap_index = 1974, .adv_w = 480, .box_w = 28, .box_h = 21, .ofs_x = 1, .ofs_y = 4},
|
||||
{.bitmap_index = 2121, .adv_w = 480, .box_w = 28, .box_h = 20, .ofs_x = 1, .ofs_y = 5},
|
||||
{.bitmap_index = 2261, .adv_w = 480, .box_w = 20, .box_h = 25, .ofs_x = 5, .ofs_y = 3},
|
||||
{.bitmap_index = 2386, .adv_w = 480, .box_w = 28, .box_h = 16, .ofs_x = 1, .ofs_y = 7},
|
||||
{.bitmap_index = 2498, .adv_w = 480, .box_w = 28, .box_h = 16, .ofs_x = 1, .ofs_y = 7},
|
||||
{.bitmap_index = 2610, .adv_w = 480, .box_w = 28, .box_h = 16, .ofs_x = 1, .ofs_y = 7},
|
||||
{.bitmap_index = 2722, .adv_w = 480, .box_w = 28, .box_h = 16, .ofs_x = 1, .ofs_y = 7},
|
||||
{.bitmap_index = 2834, .adv_w = 480, .box_w = 28, .box_h = 16, .ofs_x = 1, .ofs_y = 7},
|
||||
{.bitmap_index = 2946, .adv_w = 480, .box_w = 28, .box_h = 16, .ofs_x = 1, .ofs_y = 7},
|
||||
{.bitmap_index = 3058, .adv_w = 480, .box_w = 28, .box_h = 16, .ofs_x = 1, .ofs_y = 7},
|
||||
{.bitmap_index = 3170, .adv_w = 480, .box_w = 28, .box_h = 16, .ofs_x = 1, .ofs_y = 7}
|
||||
};
|
||||
|
||||
/*---------------------
|
||||
@ -513,9 +531,10 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
|
||||
*--------------------*/
|
||||
|
||||
static const uint16_t unicode_list_0[] = {
|
||||
0x0, 0x1, 0x2, 0x33, 0x17b, 0x468, 0x47c, 0xa2f,
|
||||
0xa3a, 0xa3d, 0xeb0, 0xebd, 0xebe, 0xf09, 0xfb5, 0x1034,
|
||||
0x10a8, 0x10a9, 0x10ab, 0x10ac, 0x10ad, 0x10ae, 0x10af, 0x10b0
|
||||
0x0, 0x1, 0x2, 0x33, 0x39, 0x17b, 0x468, 0x47c,
|
||||
0xa2f, 0xa3a, 0xa3d, 0xeb0, 0xebd, 0xebe, 0xf09, 0xfb5,
|
||||
0x1034, 0x10a8, 0x10a9, 0x10ab, 0x10ac, 0x10ad, 0x10ae, 0x10af,
|
||||
0x10b0
|
||||
};
|
||||
|
||||
/*Collect the unicode lists and glyph_id offsets*/
|
||||
@ -523,7 +542,7 @@ static const lv_font_fmt_txt_cmap_t cmaps[] =
|
||||
{
|
||||
{
|
||||
.range_start = 57767, .range_length = 4273, .glyph_id_start = 1,
|
||||
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 24, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
|
||||
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 25, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -197,6 +197,7 @@ const struct ModuleSymbol lvgl_module_symbols[] = {
|
||||
DEFINE_MODULE_SYMBOL(lv_buttonmatrix_set_one_checked),
|
||||
DEFINE_MODULE_SYMBOL(lv_buttonmatrix_set_button_width),
|
||||
DEFINE_MODULE_SYMBOL(lv_buttonmatrix_set_selected_button),
|
||||
DEFINE_MODULE_SYMBOL(lv_buttonmatrix_clear_button_ctrl),
|
||||
// lv_canvas
|
||||
DEFINE_MODULE_SYMBOL(lv_canvas_create),
|
||||
DEFINE_MODULE_SYMBOL(lv_canvas_fill_bg),
|
||||
|
||||
@ -9,4 +9,4 @@ idf_component_register(
|
||||
REQUIRES TactilityKernel driver vfs fatfs
|
||||
)
|
||||
|
||||
idf_component_optional_requires(PRIVATE bt)
|
||||
idf_component_optional_requires(PRIVATE bt usb espressif__usb_host_hid espressif__usb_host_msc)
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
description: ESP32 USB Host HID class driver
|
||||
|
||||
compatible: "espressif,esp32-usbhost-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 Host MIDI class driver
|
||||
|
||||
compatible: "espressif,esp32-usbhost-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 Host MSC class driver (mass storage)
|
||||
|
||||
compatible: "espressif,esp32-usbhost-msc"
|
||||
|
||||
properties:
|
||||
_unused:
|
||||
type: int
|
||||
default: 0
|
||||
description: Placeholder required by the binding schema; this driver has no device-tree configuration.
|
||||
@ -0,0 +1,15 @@
|
||||
description: ESP32 USB Host controller
|
||||
|
||||
compatible: "espressif,esp32-usbhost"
|
||||
|
||||
properties:
|
||||
peripheral-map:
|
||||
type: int
|
||||
default: 0
|
||||
description: |
|
||||
Bitmask of USB OTG controllers to use. Only meaningful on ESP32-P4,
|
||||
which has two independent USB DWC cores. On ESP32-S2/S3 this field
|
||||
is ignored (single controller, always used).
|
||||
Default 0 selects the default peripheral (P4 default: controller 0, HS/UTMI).
|
||||
BIT(0) = controller 0 (HS, UTMI PHY - GPIO 49/50 aka USB2_OTG pins, e.g. USB-A on Tab5)
|
||||
BIT(1) = controller 1 (FS, FSLS PHY - GPIO 24/25 aka USB1 pins, e.g. USB-C OTG on Tab5)
|
||||
@ -0,0 +1,18 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <tactility/bindings/bindings.h>
|
||||
#include <tactility/drivers/esp32_usbhost.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
DEFINE_DEVICETREE(esp32_usbhost, struct Esp32UsbHostConfig)
|
||||
DEFINE_DEVICETREE(esp32_usbhost_hid, struct Esp32UsbHostChildConfig)
|
||||
DEFINE_DEVICETREE(esp32_usbhost_midi, struct Esp32UsbHostChildConfig)
|
||||
DEFINE_DEVICETREE(esp32_usbhost_msc, struct Esp32UsbHostChildConfig)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@ -0,0 +1,20 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct Esp32UsbHostConfig {
|
||||
uint8_t peripheral_map; /**< BIT(0)=controller 0 (HS/UTMI, e.g. USB-A on Tab5), BIT(1)=controller 1 (FS/FSLS, e.g. USB-C OTG on Tab5) */
|
||||
};
|
||||
|
||||
struct Esp32UsbHostChildConfig {
|
||||
int _unused;
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
161
Platforms/platform-esp32/source/drivers/usb/esp32_usbhost.cpp
Normal file
161
Platforms/platform-esp32/source/drivers/usb/esp32_usbhost.cpp
Normal file
@ -0,0 +1,161 @@
|
||||
#include <sdkconfig.h>
|
||||
#ifdef CONFIG_SOC_USB_OTG_SUPPORTED
|
||||
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/drivers/esp32_usbhost.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <usb/usb_host.h>
|
||||
#include <esp_intr_alloc.h>
|
||||
|
||||
#define TAG "esp32_usbhost"
|
||||
|
||||
#define GET_CONFIG(device) ((const Esp32UsbHostConfig*)(device)->config)
|
||||
|
||||
constexpr auto USB_LIB_TASK_STACK = 4096;
|
||||
constexpr auto USB_LIB_TASK_PRIORITY = 10;
|
||||
constexpr auto USB_LIB_EVENT_TIMEOUT_MS = 500;
|
||||
constexpr auto USB_HOST_STOP_TIMEOUT_MS = 3000;
|
||||
constexpr auto USB_HOST_STOP_RETRY_MS = 1000;
|
||||
|
||||
struct UsbHostContext {
|
||||
TaskHandle_t lib_task = nullptr;
|
||||
SemaphoreHandle_t lib_task_done = nullptr;
|
||||
};
|
||||
|
||||
static void usbLibTask(void* arg) {
|
||||
auto* ctx = static_cast<UsbHostContext*>(arg);
|
||||
LOG_I(TAG, "lib task started");
|
||||
|
||||
while (true) {
|
||||
uint32_t flags = 0;
|
||||
esp_err_t err = usb_host_lib_handle_events(pdMS_TO_TICKS(USB_LIB_EVENT_TIMEOUT_MS), &flags);
|
||||
if (err != ESP_OK && err != ESP_ERR_TIMEOUT) {
|
||||
LOG_W(TAG, "usb_host_lib_handle_events: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
if (flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) {
|
||||
LOG_I(TAG, "no more USB clients, freeing all devices");
|
||||
usb_host_device_free_all();
|
||||
}
|
||||
if (flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) {
|
||||
LOG_I(TAG, "all USB devices freed");
|
||||
}
|
||||
|
||||
if (ulTaskNotifyTake(pdFALSE, 0) > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
LOG_I(TAG, "lib task stopping");
|
||||
xSemaphoreGive(ctx->lib_task_done);
|
||||
vTaskDelete(nullptr);
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
static error_t start_device(struct Device* device) {
|
||||
auto* cfg = GET_CONFIG(device);
|
||||
if (!cfg) {
|
||||
LOG_E(TAG, "device config is null");
|
||||
return ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
usb_host_config_t host_cfg = {
|
||||
.skip_phy_setup = false,
|
||||
.root_port_unpowered = false,
|
||||
.intr_flags = ESP_INTR_FLAG_LEVEL1,
|
||||
.enum_filter_cb = nullptr,
|
||||
.fifo_settings_custom = {},
|
||||
#if CONFIG_IDF_TARGET_ESP32P4
|
||||
.peripheral_map = cfg->peripheral_map,
|
||||
#endif
|
||||
};
|
||||
|
||||
esp_err_t ret = usb_host_install(&host_cfg);
|
||||
if (ret != ESP_OK) {
|
||||
LOG_E(TAG, "usb_host_install failed: %s", esp_err_to_name(ret));
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
auto* ctx = new UsbHostContext();
|
||||
|
||||
ctx->lib_task_done = xSemaphoreCreateBinary();
|
||||
if (!ctx->lib_task_done) {
|
||||
LOG_E(TAG, "failed to create lib_task_done semaphore");
|
||||
delete ctx;
|
||||
usb_host_uninstall();
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
BaseType_t result = xTaskCreate(usbLibTask, "usb_lib", USB_LIB_TASK_STACK,
|
||||
ctx, USB_LIB_TASK_PRIORITY, &ctx->lib_task);
|
||||
if (result != pdPASS) {
|
||||
LOG_E(TAG, "failed to create usb_lib task");
|
||||
vSemaphoreDelete(ctx->lib_task_done);
|
||||
delete ctx;
|
||||
usb_host_uninstall();
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
device_set_driver_data(device, ctx);
|
||||
LOG_I(TAG, "started (peripheral_map=0x%02x)", cfg->peripheral_map);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t stop_device(struct Device* device) {
|
||||
auto* ctx = static_cast<UsbHostContext*>(device_get_driver_data(device));
|
||||
if (!ctx) return ERROR_NONE;
|
||||
|
||||
xTaskNotifyGive(ctx->lib_task);
|
||||
|
||||
bool exited = (xSemaphoreTake(ctx->lib_task_done, pdMS_TO_TICKS(USB_HOST_STOP_TIMEOUT_MS)) == pdTRUE);
|
||||
if (!exited) {
|
||||
// Free all devices to unblock the NO_CLIENTS / ALL_FREE flags, then retry.
|
||||
usb_host_device_free_all();
|
||||
exited = (xSemaphoreTake(ctx->lib_task_done, pdMS_TO_TICKS(USB_HOST_STOP_RETRY_MS)) == pdTRUE);
|
||||
}
|
||||
if (!exited) {
|
||||
LOG_E(TAG, "lib task stop timed out after %dms, force terminating — USB host restart required",
|
||||
USB_HOST_STOP_TIMEOUT_MS + USB_HOST_STOP_RETRY_MS);
|
||||
vTaskDelete(ctx->lib_task);
|
||||
vTaskDelay(pdMS_TO_TICKS(50));
|
||||
// Skip usb_host_uninstall — USB stack is in undefined state.
|
||||
ctx->lib_task = nullptr;
|
||||
vSemaphoreDelete(ctx->lib_task_done);
|
||||
device_set_driver_data(device, nullptr);
|
||||
delete ctx;
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
ctx->lib_task = nullptr;
|
||||
vSemaphoreDelete(ctx->lib_task_done);
|
||||
|
||||
esp_err_t uninstall_err = usb_host_uninstall();
|
||||
if (uninstall_err != ESP_OK) {
|
||||
LOG_W(TAG, "usb_host_uninstall: %s", esp_err_to_name(uninstall_err));
|
||||
}
|
||||
|
||||
device_set_driver_data(device, nullptr);
|
||||
delete ctx;
|
||||
LOG_I(TAG, "stopped");
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
Driver esp32_usbhost_driver = {
|
||||
.name = "esp32_usbhost",
|
||||
.compatible = (const char*[]) { "espressif,esp32-usbhost", nullptr },
|
||||
.start_device = start_device,
|
||||
.stop_device = stop_device,
|
||||
.api = nullptr,
|
||||
.device_type = nullptr,
|
||||
.owner = nullptr,
|
||||
.internal = nullptr,
|
||||
};
|
||||
|
||||
} // extern "C"
|
||||
|
||||
#endif // CONFIG_SOC_USB_OTG_SUPPORTED
|
||||
@ -0,0 +1,506 @@
|
||||
#include <sdkconfig.h>
|
||||
#ifdef CONFIG_SOC_USB_OTG_SUPPORTED
|
||||
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/drivers/usb_host_hid.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/queue.h>
|
||||
#include <freertos/task.h>
|
||||
#include <freertos/semphr.h>
|
||||
|
||||
#include <usb/hid_host.h>
|
||||
#include <usb/hid_usage_keyboard.h>
|
||||
#include <usb/hid_usage_mouse.h>
|
||||
#include <esp_timer.h>
|
||||
|
||||
#define TAG "esp32_usbhost_hid"
|
||||
|
||||
constexpr auto HID_EVENT_QUEUE_SIZE = 8;
|
||||
constexpr auto HID_PROC_TASK_STACK = 4096;
|
||||
constexpr auto HID_PROC_TASK_PRIORITY = 5;
|
||||
constexpr auto HID_STOP_TIMEOUT_MS = 2000;
|
||||
constexpr auto MAX_SUBSCRIBERS = 4;
|
||||
|
||||
typedef struct {
|
||||
hid_host_device_handle_t handle;
|
||||
hid_host_driver_event_t event;
|
||||
void* arg;
|
||||
} hid_dev_event_t;
|
||||
|
||||
struct UsbHidContext {
|
||||
std::atomic<bool> mouse_connected{false};
|
||||
std::atomic<bool> device_connected{false};
|
||||
QueueHandle_t hid_event_queue = nullptr;
|
||||
TaskHandle_t hid_proc_task = nullptr;
|
||||
SemaphoreHandle_t hid_proc_task_done = nullptr;
|
||||
std::atomic<bool> hid_proc_running{false};
|
||||
|
||||
uint8_t prev_keys[HID_KEYBOARD_KEY_MAX] = {};
|
||||
uint32_t pressed_lv_keys[256] = {};
|
||||
bool caps_lock_active = false;
|
||||
bool num_lock_active = true;
|
||||
bool scroll_lock_active = false;
|
||||
bool prev_mouse_button2 = false;
|
||||
|
||||
std::atomic<hid_host_device_handle_t> kb_handle{nullptr};
|
||||
std::atomic<bool> kb_led_pending{false};
|
||||
|
||||
QueueHandle_t subscribers[MAX_SUBSCRIBERS] = {};
|
||||
SemaphoreHandle_t sub_mutex = nullptr;
|
||||
};
|
||||
|
||||
static const uint8_t keycode2ascii[57][2] = {
|
||||
{0, 0}, {0, 0}, {0, 0}, {0, 0},
|
||||
{'a', 'A'}, {'b', 'B'}, {'c', 'C'}, {'d', 'D'}, {'e', 'E'},
|
||||
{'f', 'F'}, {'g', 'G'}, {'h', 'H'}, {'i', 'I'}, {'j', 'J'},
|
||||
{'k', 'K'}, {'l', 'L'}, {'m', 'M'}, {'n', 'N'}, {'o', 'O'},
|
||||
{'p', 'P'}, {'q', 'Q'}, {'r', 'R'}, {'s', 'S'}, {'t', 'T'},
|
||||
{'u', 'U'}, {'v', 'V'}, {'w', 'W'}, {'x', 'X'}, {'y', 'Y'},
|
||||
{'z', 'Z'},
|
||||
{'1', '!'}, {'2', '@'}, {'3', '#'}, {'4', '$'}, {'5', '%'},
|
||||
{'6', '^'}, {'7', '&'}, {'8', '*'}, {'9', '('}, {'0', ')'},
|
||||
{'\r', '\r'}, {0, 0}, {'\b', 0}, {'\t', '\t'}, {' ', ' '},
|
||||
{'-', '_'}, {'=', '+'}, {'[', '{'}, {']', '}'},
|
||||
{'\\', '|'}, {'\\', '|'}, {';', ':'}, {'\'', '"'},
|
||||
{'`', '~'}, {',', '<'}, {'.', '>'}, {'/', '?'},
|
||||
};
|
||||
|
||||
static uint32_t hid_keycode_to_key(uint8_t modifier, uint8_t key_code,
|
||||
bool caps_lock, bool num_lock) {
|
||||
bool shift = (modifier & HID_LEFT_SHIFT) || (modifier & HID_RIGHT_SHIFT);
|
||||
bool ctrl = (modifier & HID_LEFT_CONTROL) || (modifier & HID_RIGHT_CONTROL);
|
||||
bool alt = (modifier & HID_LEFT_ALT) || (modifier & HID_RIGHT_ALT);
|
||||
|
||||
switch (key_code) {
|
||||
case HID_KEY_ENTER: return USB_HID_KEY_ENTER;
|
||||
case HID_KEY_ESC: return USB_HID_KEY_ESC;
|
||||
case HID_KEY_DEL: return USB_HID_KEY_BACKSPACE;
|
||||
case HID_KEY_DELETE: return USB_HID_KEY_DEL;
|
||||
case HID_KEY_TAB: return shift ? USB_HID_KEY_PREV : USB_HID_KEY_NEXT;
|
||||
case HID_KEY_UP: return USB_HID_KEY_UP;
|
||||
case HID_KEY_DOWN: return USB_HID_KEY_DOWN;
|
||||
case HID_KEY_LEFT: return USB_HID_KEY_LEFT;
|
||||
case HID_KEY_RIGHT: return USB_HID_KEY_RIGHT;
|
||||
case HID_KEY_HOME: return USB_HID_KEY_HOME;
|
||||
case HID_KEY_END: return USB_HID_KEY_END;
|
||||
case HID_KEY_KEYPAD_ENTER: return USB_HID_KEY_ENTER;
|
||||
case HID_KEY_KEYPAD_ADD: return '+';
|
||||
case HID_KEY_KEYPAD_SUB: return '-';
|
||||
case HID_KEY_KEYPAD_MUL: return '*';
|
||||
case HID_KEY_KEYPAD_DIV: return '/';
|
||||
case HID_KEY_KEYPAD_0: return num_lock ? (uint32_t)'0' : 0u;
|
||||
case HID_KEY_KEYPAD_1: return num_lock ? (uint32_t)'1' : (uint32_t)USB_HID_KEY_END;
|
||||
case HID_KEY_KEYPAD_2: return num_lock ? (uint32_t)'2' : (uint32_t)USB_HID_KEY_DOWN;
|
||||
case HID_KEY_KEYPAD_3: return num_lock ? (uint32_t)'3' : 0u;
|
||||
case HID_KEY_KEYPAD_4: return num_lock ? (uint32_t)'4' : (uint32_t)USB_HID_KEY_LEFT;
|
||||
case HID_KEY_KEYPAD_5: return num_lock ? (uint32_t)'5' : 0u;
|
||||
case HID_KEY_KEYPAD_6: return num_lock ? (uint32_t)'6' : (uint32_t)USB_HID_KEY_RIGHT;
|
||||
case HID_KEY_KEYPAD_7: return num_lock ? (uint32_t)'7' : (uint32_t)USB_HID_KEY_HOME;
|
||||
case HID_KEY_KEYPAD_8: return num_lock ? (uint32_t)'8' : (uint32_t)USB_HID_KEY_UP;
|
||||
case HID_KEY_KEYPAD_9: return num_lock ? (uint32_t)'9' : 0u;
|
||||
case HID_KEY_KEYPAD_DELETE: return num_lock ? (uint32_t)'.' : (uint32_t)USB_HID_KEY_DEL;
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (ctrl || alt) return 0;
|
||||
|
||||
if (key_code < (sizeof(keycode2ascii) / sizeof(keycode2ascii[0]))) {
|
||||
bool is_letter = (key_code >= 0x04 && key_code <= 0x1D);
|
||||
bool effective_shift = is_letter ? (shift ^ caps_lock) : shift;
|
||||
uint8_t ch = keycode2ascii[key_code][effective_shift ? 1 : 0];
|
||||
if (ch != 0) return (uint32_t)ch;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void publish_event(UsbHidContext* ctx, const UsbHidEvent* evt) {
|
||||
if (xSemaphoreTake(ctx->sub_mutex, pdMS_TO_TICKS(10)) != pdTRUE) {
|
||||
LOG_W(TAG, "publish_event: sub_mutex contended, event type=%d dropped", (int)evt->type);
|
||||
return;
|
||||
}
|
||||
bool is_release = (evt->type == USB_HID_EVENT_KEY && !evt->key.pressed);
|
||||
TickType_t send_timeout = is_release ? pdMS_TO_TICKS(10) : 0;
|
||||
for (int i = 0; i < MAX_SUBSCRIBERS; i++) {
|
||||
if (ctx->subscribers[i]) {
|
||||
xQueueSend(ctx->subscribers[i], evt, send_timeout);
|
||||
}
|
||||
}
|
||||
xSemaphoreGive(ctx->sub_mutex);
|
||||
}
|
||||
|
||||
static void publish_scroll(UsbHidContext* ctx, int32_t delta) {
|
||||
UsbHidEvent evt = { .type = USB_HID_EVENT_SCROLL, .scroll = { delta } };
|
||||
publish_event(ctx, &evt);
|
||||
}
|
||||
|
||||
static void hid_interface_callback(hid_host_device_handle_t handle,
|
||||
const hid_host_interface_event_t event,
|
||||
void* arg)
|
||||
{
|
||||
auto* ctx = static_cast<UsbHidContext*>(arg);
|
||||
uint8_t data[64] = {};
|
||||
size_t data_len = 0;
|
||||
hid_host_dev_params_t params;
|
||||
|
||||
if (hid_host_device_get_params(handle, ¶ms) != ESP_OK) return;
|
||||
|
||||
switch (event) {
|
||||
case HID_HOST_INTERFACE_EVENT_INPUT_REPORT:
|
||||
if (hid_host_device_get_raw_input_report_data(handle, data, sizeof(data), &data_len) != ESP_OK) break;
|
||||
|
||||
if (params.proto == HID_PROTOCOL_KEYBOARD) {
|
||||
if (data_len < sizeof(hid_keyboard_input_report_boot_t)) break;
|
||||
auto* kb = reinterpret_cast<const hid_keyboard_input_report_boot_t*>(data);
|
||||
|
||||
for (int i = 0; i < HID_KEYBOARD_KEY_MAX; i++) {
|
||||
uint8_t prev_hid = ctx->prev_keys[i];
|
||||
if (prev_hid > HID_KEY_ERROR_UNDEFINED) {
|
||||
bool still_pressed = false;
|
||||
for (int j = 0; j < HID_KEYBOARD_KEY_MAX; j++) {
|
||||
if (kb->key[j] == prev_hid) { still_pressed = true; break; }
|
||||
}
|
||||
if (!still_pressed) {
|
||||
uint32_t lv_key = ctx->pressed_lv_keys[prev_hid];
|
||||
ctx->pressed_lv_keys[prev_hid] = 0;
|
||||
if (lv_key) {
|
||||
UsbHidEvent evt = { .type = USB_HID_EVENT_KEY, .key = { lv_key, false } };
|
||||
publish_event(ctx, &evt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t hid_code = kb->key[i];
|
||||
if (hid_code > HID_KEY_ERROR_UNDEFINED) {
|
||||
bool was_pressed = false;
|
||||
for (int j = 0; j < HID_KEYBOARD_KEY_MAX; j++) {
|
||||
if (ctx->prev_keys[j] == hid_code) { was_pressed = true; break; }
|
||||
}
|
||||
if (!was_pressed) {
|
||||
if (hid_code == HID_KEY_CAPS_LOCK) {
|
||||
ctx->caps_lock_active = !ctx->caps_lock_active;
|
||||
ctx->kb_led_pending.store(true);
|
||||
continue;
|
||||
}
|
||||
if (hid_code == HID_KEY_NUM_LOCK) {
|
||||
ctx->num_lock_active = !ctx->num_lock_active;
|
||||
ctx->kb_led_pending.store(true);
|
||||
continue;
|
||||
}
|
||||
if (hid_code == HID_KEY_SCROLL_LOCK) {
|
||||
ctx->scroll_lock_active = !ctx->scroll_lock_active;
|
||||
ctx->kb_led_pending.store(true);
|
||||
continue;
|
||||
}
|
||||
bool is_pgup = (hid_code == HID_KEY_PAGEUP) ||
|
||||
(!ctx->num_lock_active && hid_code == HID_KEY_KEYPAD_9);
|
||||
bool is_pgdn = (hid_code == HID_KEY_PAGEDOWN) ||
|
||||
(!ctx->num_lock_active && hid_code == HID_KEY_KEYPAD_3);
|
||||
if (is_pgup || is_pgdn) {
|
||||
publish_scroll(ctx, is_pgup ? -8 : 8);
|
||||
continue;
|
||||
}
|
||||
uint32_t lv_key = hid_keycode_to_key(kb->modifier.val, hid_code,
|
||||
ctx->caps_lock_active, ctx->num_lock_active);
|
||||
if (lv_key) {
|
||||
UsbHidEvent evt = { .type = USB_HID_EVENT_KEY, .key = { lv_key, true } };
|
||||
publish_event(ctx, &evt);
|
||||
ctx->pressed_lv_keys[hid_code] = lv_key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
memcpy(ctx->prev_keys, kb->key, HID_KEYBOARD_KEY_MAX);
|
||||
|
||||
} else if (params.proto == HID_PROTOCOL_MOUSE) {
|
||||
if (data_len < sizeof(hid_mouse_input_report_boot_t)) break;
|
||||
auto* ms = reinterpret_cast<const hid_mouse_input_report_boot_t*>(data);
|
||||
|
||||
if (ms->x_displacement != 0 || ms->y_displacement != 0) {
|
||||
UsbHidEvent evt = { .type = USB_HID_EVENT_MOUSE_MOVE,
|
||||
.mouse_move = { (int32_t)ms->x_displacement, (int32_t)ms->y_displacement } };
|
||||
publish_event(ctx, &evt);
|
||||
}
|
||||
{
|
||||
bool b1 = ms->buttons.button1 != 0;
|
||||
bool b2 = ms->buttons.button2 != 0;
|
||||
UsbHidEvent evt = { .type = USB_HID_EVENT_MOUSE_BTN, .mouse_btn = { b1, b2 } };
|
||||
publish_event(ctx, &evt);
|
||||
|
||||
if (b2 != ctx->prev_mouse_button2) {
|
||||
UsbHidEvent key_evt = { .type = USB_HID_EVENT_KEY, .key = { USB_HID_KEY_ESC, b2 } };
|
||||
publish_event(ctx, &key_evt);
|
||||
ctx->prev_mouse_button2 = b2;
|
||||
}
|
||||
}
|
||||
|
||||
if (data_len > sizeof(hid_mouse_input_report_boot_t)) {
|
||||
int8_t wheel = (int8_t)data[sizeof(hid_mouse_input_report_boot_t)];
|
||||
if (wheel != 0) {
|
||||
int32_t delta = (wheel < -8) ? -8 : (wheel > 8) ? 8 : (int32_t)wheel;
|
||||
publish_scroll(ctx, delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case HID_HOST_INTERFACE_EVENT_DISCONNECTED:
|
||||
if (params.proto == HID_PROTOCOL_KEYBOARD) {
|
||||
memset(ctx->prev_keys, 0, sizeof(ctx->prev_keys));
|
||||
memset(ctx->pressed_lv_keys, 0, sizeof(ctx->pressed_lv_keys));
|
||||
ctx->kb_handle.store(nullptr);
|
||||
ctx->kb_led_pending.store(false);
|
||||
} else if (params.proto == HID_PROTOCOL_MOUSE) {
|
||||
ctx->mouse_connected = false;
|
||||
}
|
||||
hid_host_device_close(handle);
|
||||
if (!ctx->kb_handle.load() && !ctx->mouse_connected) {
|
||||
ctx->device_connected = false;
|
||||
}
|
||||
{
|
||||
UsbHidEventType disc_type = (params.proto == HID_PROTOCOL_KEYBOARD)
|
||||
? USB_HID_EVENT_KEYBOARD_DISCONNECTED
|
||||
: USB_HID_EVENT_MOUSE_DISCONNECTED;
|
||||
UsbHidEvent evt = { .type = disc_type };
|
||||
publish_event(ctx, &evt);
|
||||
}
|
||||
break;
|
||||
|
||||
case HID_HOST_INTERFACE_EVENT_TRANSFER_ERROR:
|
||||
LOG_W(TAG, "HID transfer error (proto=%d)", params.proto);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void hid_driver_callback(hid_host_device_handle_t handle,
|
||||
const hid_host_driver_event_t event,
|
||||
void* arg)
|
||||
{
|
||||
auto* ctx = static_cast<UsbHidContext*>(arg);
|
||||
hid_dev_event_t evt = { handle, event, arg };
|
||||
if (ctx->hid_event_queue) {
|
||||
xQueueSend(ctx->hid_event_queue, &evt, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void hid_proc_task(void* arg) {
|
||||
auto* ctx = static_cast<UsbHidContext*>(arg);
|
||||
LOG_I(TAG, "HID proc task started");
|
||||
|
||||
while (ctx->hid_proc_running) {
|
||||
hid_dev_event_t dev_evt;
|
||||
if (xQueueReceive(ctx->hid_event_queue, &dev_evt, pdMS_TO_TICKS(100)) != pdTRUE) {
|
||||
if (ctx->kb_led_pending.exchange(false) && ctx->kb_handle.load()) {
|
||||
uint8_t leds = (ctx->num_lock_active ? 0x01 : 0)
|
||||
| (ctx->caps_lock_active ? 0x02 : 0)
|
||||
| (ctx->scroll_lock_active ? 0x04 : 0);
|
||||
hid_class_request_set_report(ctx->kb_handle.load(), HID_REPORT_TYPE_OUTPUT, 0, &leds, 1);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (dev_evt.event == HID_HOST_DRIVER_EVENT_CONNECTED) {
|
||||
hid_host_dev_params_t params;
|
||||
if (hid_host_device_get_params(dev_evt.handle, ¶ms) != ESP_OK) continue;
|
||||
|
||||
if (params.proto != HID_PROTOCOL_KEYBOARD && params.proto != HID_PROTOCOL_MOUSE) {
|
||||
LOG_D(TAG, "ignoring HID interface with unhandled proto=%d", params.proto);
|
||||
continue;
|
||||
}
|
||||
LOG_I(TAG, "HID device connected (proto=%d)", params.proto);
|
||||
|
||||
const hid_host_device_config_t dev_cfg = {
|
||||
.callback = hid_interface_callback,
|
||||
.callback_arg = ctx,
|
||||
};
|
||||
if (hid_host_device_open(dev_evt.handle, &dev_cfg) != ESP_OK) {
|
||||
LOG_W(TAG, "hid_host_device_open failed");
|
||||
continue;
|
||||
}
|
||||
hid_class_request_set_protocol(dev_evt.handle, HID_REPORT_PROTOCOL_BOOT);
|
||||
if (params.proto == HID_PROTOCOL_KEYBOARD) {
|
||||
hid_class_request_set_idle(dev_evt.handle, 0, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
ctx->kb_handle.store(dev_evt.handle);
|
||||
uint8_t leds = (ctx->num_lock_active ? 0x01 : 0)
|
||||
| (ctx->caps_lock_active ? 0x02 : 0)
|
||||
| (ctx->scroll_lock_active ? 0x04 : 0);
|
||||
hid_class_request_set_report(dev_evt.handle, HID_REPORT_TYPE_OUTPUT, 0, &leds, 1);
|
||||
} else if (params.proto == HID_PROTOCOL_MOUSE) {
|
||||
ctx->mouse_connected = true;
|
||||
}
|
||||
ctx->device_connected = true;
|
||||
{
|
||||
UsbHidEventType conn_type = (params.proto == HID_PROTOCOL_KEYBOARD)
|
||||
? USB_HID_EVENT_KEYBOARD_CONNECTED
|
||||
: USB_HID_EVENT_MOUSE_CONNECTED;
|
||||
UsbHidEvent evt = { .type = conn_type };
|
||||
publish_event(ctx, &evt);
|
||||
}
|
||||
hid_host_device_start(dev_evt.handle);
|
||||
}
|
||||
}
|
||||
|
||||
LOG_I(TAG, "HID proc task stopped");
|
||||
xSemaphoreGive(ctx->hid_proc_task_done);
|
||||
vTaskDelete(nullptr);
|
||||
}
|
||||
|
||||
static bool api_hid_is_connected(struct Device* device) {
|
||||
auto* ctx = static_cast<UsbHidContext*>(device_get_driver_data(device));
|
||||
return ctx && ctx->device_connected.load();
|
||||
}
|
||||
|
||||
static bool api_hid_subscribe(struct Device* device, UsbHidQueueHandle event_queue) {
|
||||
auto* ctx = static_cast<UsbHidContext*>(device_get_driver_data(device));
|
||||
if (!ctx || !event_queue) return false;
|
||||
if (xSemaphoreTake(ctx->sub_mutex, pdMS_TO_TICKS(100)) != pdTRUE) return false;
|
||||
bool added = false;
|
||||
for (int i = 0; i < MAX_SUBSCRIBERS; i++) {
|
||||
if (ctx->subscribers[i] == static_cast<QueueHandle_t>(event_queue)) {
|
||||
added = true;
|
||||
break;
|
||||
}
|
||||
if (!ctx->subscribers[i]) {
|
||||
ctx->subscribers[i] = static_cast<QueueHandle_t>(event_queue);
|
||||
added = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
xSemaphoreGive(ctx->sub_mutex);
|
||||
if (!added) LOG_W(TAG, "subscriber list full");
|
||||
return added;
|
||||
}
|
||||
|
||||
static void api_hid_unsubscribe(struct Device* device, UsbHidQueueHandle event_queue) {
|
||||
auto* ctx = static_cast<UsbHidContext*>(device_get_driver_data(device));
|
||||
if (!ctx || !event_queue) return;
|
||||
if (xSemaphoreTake(ctx->sub_mutex, pdMS_TO_TICKS(100)) != pdTRUE) {
|
||||
LOG_W(TAG, "unsubscribe: mutex timeout, subscriber slot may remain stale");
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < MAX_SUBSCRIBERS; i++) {
|
||||
if (ctx->subscribers[i] == static_cast<QueueHandle_t>(event_queue)) {
|
||||
ctx->subscribers[i] = nullptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
xSemaphoreGive(ctx->sub_mutex);
|
||||
}
|
||||
|
||||
static const UsbHidApi hid_api = {
|
||||
.is_connected = api_hid_is_connected,
|
||||
.subscribe = api_hid_subscribe,
|
||||
.unsubscribe = api_hid_unsubscribe,
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
|
||||
static error_t start_device(struct Device* device) {
|
||||
auto* ctx = new UsbHidContext();
|
||||
|
||||
ctx->sub_mutex = xSemaphoreCreateMutex();
|
||||
if (!ctx->sub_mutex) {
|
||||
LOG_E(TAG, "failed to create subscriber mutex");
|
||||
delete ctx;
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
ctx->hid_event_queue = xQueueCreate(HID_EVENT_QUEUE_SIZE, sizeof(hid_dev_event_t));
|
||||
if (!ctx->hid_event_queue) {
|
||||
LOG_E(TAG, "failed to create HID event queue");
|
||||
vSemaphoreDelete(ctx->sub_mutex);
|
||||
delete ctx;
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
ctx->hid_proc_task_done = xSemaphoreCreateBinary();
|
||||
if (!ctx->hid_proc_task_done) {
|
||||
LOG_E(TAG, "failed to create task done semaphore");
|
||||
vQueueDelete(ctx->hid_event_queue);
|
||||
vSemaphoreDelete(ctx->sub_mutex);
|
||||
delete ctx;
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
const hid_host_driver_config_t hid_cfg = {
|
||||
.create_background_task = true,
|
||||
.task_priority = HID_PROC_TASK_PRIORITY,
|
||||
.stack_size = HID_PROC_TASK_STACK,
|
||||
.core_id = tskNO_AFFINITY,
|
||||
.callback = hid_driver_callback,
|
||||
.callback_arg = ctx,
|
||||
};
|
||||
if (hid_host_install(&hid_cfg) != ESP_OK) {
|
||||
LOG_E(TAG, "hid_host_install failed");
|
||||
vQueueDelete(ctx->hid_event_queue);
|
||||
vSemaphoreDelete(ctx->hid_proc_task_done);
|
||||
vSemaphoreDelete(ctx->sub_mutex);
|
||||
delete ctx;
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
ctx->hid_proc_running = true;
|
||||
BaseType_t result = xTaskCreate(hid_proc_task, "hid_proc", HID_PROC_TASK_STACK,
|
||||
ctx, HID_PROC_TASK_PRIORITY, &ctx->hid_proc_task);
|
||||
if (result != pdPASS) {
|
||||
LOG_E(TAG, "failed to create hid_proc task");
|
||||
ctx->hid_proc_running = false;
|
||||
hid_host_uninstall();
|
||||
vQueueDelete(ctx->hid_event_queue);
|
||||
vSemaphoreDelete(ctx->hid_proc_task_done);
|
||||
vSemaphoreDelete(ctx->sub_mutex);
|
||||
delete ctx;
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
device_set_driver_data(device, ctx);
|
||||
LOG_I(TAG, "started");
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t stop_device(struct Device* device) {
|
||||
auto* ctx = static_cast<UsbHidContext*>(device_get_driver_data(device));
|
||||
if (!ctx) return ERROR_NONE;
|
||||
|
||||
ctx->hid_proc_running = false;
|
||||
|
||||
if (xSemaphoreTake(ctx->hid_proc_task_done, pdMS_TO_TICKS(HID_STOP_TIMEOUT_MS)) != pdTRUE) {
|
||||
LOG_W(TAG, "HID proc task stop timed out, force terminating");
|
||||
vTaskDelete(ctx->hid_proc_task);
|
||||
}
|
||||
ctx->hid_proc_task = nullptr;
|
||||
vSemaphoreDelete(ctx->hid_proc_task_done);
|
||||
|
||||
hid_host_uninstall();
|
||||
|
||||
if (ctx->hid_event_queue) { vQueueDelete(ctx->hid_event_queue); ctx->hid_event_queue = nullptr; }
|
||||
if (ctx->sub_mutex) { vSemaphoreDelete(ctx->sub_mutex); ctx->sub_mutex = nullptr; }
|
||||
|
||||
device_set_driver_data(device, nullptr);
|
||||
delete ctx;
|
||||
LOG_I(TAG, "stopped");
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
Driver esp32_usbhost_hid_driver = {
|
||||
.name = "esp32_usbhost_hid",
|
||||
.compatible = (const char*[]) { "espressif,esp32-usbhost-hid", nullptr },
|
||||
.start_device = start_device,
|
||||
.stop_device = stop_device,
|
||||
.api = &hid_api,
|
||||
.device_type = &USB_HOST_HID_TYPE,
|
||||
.owner = nullptr,
|
||||
.internal = nullptr,
|
||||
};
|
||||
|
||||
} // extern "C"
|
||||
|
||||
#endif // CONFIG_SOC_USB_OTG_SUPPORTED
|
||||
@ -0,0 +1,314 @@
|
||||
#include <sdkconfig.h>
|
||||
#ifdef CONFIG_SOC_USB_OTG_SUPPORTED
|
||||
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/drivers/usb_host_midi.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <freertos/portmacro.h>
|
||||
|
||||
#include <usb/usb_host.h>
|
||||
#include <usb/usb_helpers.h>
|
||||
#include <usb/usb_types_ch9.h>
|
||||
#include <usb/usb_types_stack.h>
|
||||
|
||||
#define TAG "esp32_usbhost_midi"
|
||||
|
||||
constexpr auto MIDI_TASK_STACK = 4096;
|
||||
constexpr auto MIDI_TASK_PRIORITY = 5;
|
||||
constexpr auto MIDI_STOP_TIMEOUT_MS = 2000;
|
||||
constexpr auto MIDI_TRANSFER_BUF_SIZE = 512;
|
||||
|
||||
constexpr uint8_t MIDI_INTF_CLASS = 0x01;
|
||||
constexpr uint8_t MIDI_INTF_SUBCLASS = 0x03;
|
||||
|
||||
struct UsbMidiContext {
|
||||
usb_host_client_handle_t client_hdl = nullptr;
|
||||
usb_device_handle_t dev_hdl = nullptr;
|
||||
usb_transfer_t* transfer = nullptr;
|
||||
uint8_t ep_addr = 0;
|
||||
uint8_t intf_num = 0;
|
||||
std::atomic<bool> connected{false};
|
||||
std::atomic<bool> running{false};
|
||||
TaskHandle_t task_handle = nullptr;
|
||||
SemaphoreHandle_t task_done = nullptr;
|
||||
|
||||
portMUX_TYPE callback_lock = portMUX_INITIALIZER_UNLOCKED;
|
||||
usb_midi_message_cb_t callback = nullptr;
|
||||
void* callback_arg = nullptr;
|
||||
};
|
||||
|
||||
static bool find_midi_interface(const usb_config_desc_t* cfg, uint8_t* out_intf, uint8_t* out_ep) {
|
||||
int offset = 0;
|
||||
const usb_standard_desc_t* cur = reinterpret_cast<const usb_standard_desc_t*>(cfg);
|
||||
|
||||
while ((cur = usb_parse_next_descriptor_of_type(
|
||||
cur, cfg->wTotalLength, USB_W_VALUE_DT_INTERFACE, &offset)) != nullptr) {
|
||||
const auto* intf = reinterpret_cast<const usb_intf_desc_t*>(cur);
|
||||
if (intf->bInterfaceClass != MIDI_INTF_CLASS || intf->bInterfaceSubClass != MIDI_INTF_SUBCLASS) continue;
|
||||
|
||||
int ep_offset = offset;
|
||||
const usb_standard_desc_t* ep_cur = cur;
|
||||
for (int e = 0; e < intf->bNumEndpoints; e++) {
|
||||
ep_cur = usb_parse_next_descriptor_of_type(ep_cur, cfg->wTotalLength, USB_W_VALUE_DT_ENDPOINT, &ep_offset);
|
||||
if (!ep_cur) break;
|
||||
const auto* ep = reinterpret_cast<const usb_ep_desc_t*>(ep_cur);
|
||||
usb_transfer_type_t xtype = USB_EP_DESC_GET_XFERTYPE(ep);
|
||||
bool is_in = USB_EP_DESC_GET_EP_DIR(ep) == 1;
|
||||
if (is_in && (xtype == USB_TRANSFER_TYPE_BULK || xtype == USB_TRANSFER_TYPE_INTR)) {
|
||||
*out_intf = intf->bInterfaceNumber;
|
||||
*out_ep = ep->bEndpointAddress;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void dispatch_midi_packets(UsbMidiContext* ctx, const uint8_t* buf, int len) {
|
||||
usb_midi_message_cb_t cb;
|
||||
void* arg;
|
||||
taskENTER_CRITICAL(&ctx->callback_lock);
|
||||
cb = ctx->callback;
|
||||
arg = ctx->callback_arg;
|
||||
taskEXIT_CRITICAL(&ctx->callback_lock);
|
||||
if (!cb) return;
|
||||
|
||||
for (int i = 0; i + 3 < len; i += 4) {
|
||||
uint8_t cin = buf[i] & 0x0F;
|
||||
if (cin < 0x02) continue;
|
||||
usb_midi_message_t msg = {
|
||||
.cable = static_cast<uint8_t>(buf[i] >> 4),
|
||||
.status = buf[i + 1],
|
||||
.data1 = buf[i + 2],
|
||||
.data2 = buf[i + 3],
|
||||
};
|
||||
cb(&msg, arg);
|
||||
}
|
||||
}
|
||||
|
||||
static void midi_transfer_cb(usb_transfer_t* transfer) {
|
||||
auto* ctx = static_cast<UsbMidiContext*>(transfer->context);
|
||||
if (transfer->status == USB_TRANSFER_STATUS_COMPLETED && transfer->actual_num_bytes > 0) {
|
||||
dispatch_midi_packets(ctx, transfer->data_buffer, transfer->actual_num_bytes);
|
||||
}
|
||||
if (ctx->running && ctx->connected && transfer->status != USB_TRANSFER_STATUS_NO_DEVICE) {
|
||||
transfer->num_bytes = MIDI_TRANSFER_BUF_SIZE;
|
||||
if (usb_host_transfer_submit(transfer) != ESP_OK) {
|
||||
LOG_E(TAG, "failed to resubmit MIDI transfer");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void client_event_cb(const usb_host_client_event_msg_t* msg, void* arg) {
|
||||
auto* ctx = static_cast<UsbMidiContext*>(arg);
|
||||
|
||||
if (msg->event == USB_HOST_CLIENT_EVENT_NEW_DEV) {
|
||||
if (ctx->dev_hdl != nullptr || ctx->connected.load()) {
|
||||
LOG_W(TAG, "ignoring additional MIDI device while one is already active");
|
||||
return;
|
||||
}
|
||||
uint8_t addr = msg->new_dev.address;
|
||||
usb_device_handle_t dev_hdl = nullptr;
|
||||
if (usb_host_device_open(ctx->client_hdl, addr, &dev_hdl) != ESP_OK) return;
|
||||
|
||||
const usb_config_desc_t* cfg = nullptr;
|
||||
if (usb_host_get_active_config_descriptor(dev_hdl, &cfg) != ESP_OK) {
|
||||
usb_host_device_close(ctx->client_hdl, dev_hdl);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t intf_num = 0, ep_addr = 0;
|
||||
if (!find_midi_interface(cfg, &intf_num, &ep_addr)) {
|
||||
LOG_D(TAG, "USB device addr=%d: no MIDI Streaming interface found", addr);
|
||||
usb_host_device_close(ctx->client_hdl, dev_hdl);
|
||||
return;
|
||||
}
|
||||
|
||||
if (usb_host_interface_claim(ctx->client_hdl, dev_hdl, intf_num, 0) != ESP_OK) {
|
||||
LOG_E(TAG, "failed to claim MIDI interface %d", intf_num);
|
||||
usb_host_device_close(ctx->client_hdl, dev_hdl);
|
||||
return;
|
||||
}
|
||||
|
||||
ctx->dev_hdl = dev_hdl;
|
||||
ctx->intf_num = intf_num;
|
||||
ctx->ep_addr = ep_addr;
|
||||
|
||||
ctx->transfer->device_handle = dev_hdl;
|
||||
ctx->transfer->bEndpointAddress = ep_addr;
|
||||
ctx->transfer->num_bytes = MIDI_TRANSFER_BUF_SIZE;
|
||||
ctx->transfer->callback = midi_transfer_cb;
|
||||
ctx->transfer->context = ctx;
|
||||
ctx->transfer->timeout_ms = 0;
|
||||
|
||||
if (usb_host_transfer_submit(ctx->transfer) == ESP_OK) {
|
||||
ctx->connected = true;
|
||||
LOG_I(TAG, "MIDI device connected (intf=%d ep=0x%02x)", intf_num, ep_addr);
|
||||
} else {
|
||||
LOG_E(TAG, "failed to submit initial MIDI transfer");
|
||||
usb_host_interface_release(ctx->client_hdl, dev_hdl, intf_num);
|
||||
usb_host_device_close(ctx->client_hdl, dev_hdl);
|
||||
ctx->dev_hdl = nullptr;
|
||||
}
|
||||
|
||||
} else if (msg->event == USB_HOST_CLIENT_EVENT_DEV_GONE) {
|
||||
if (ctx->dev_hdl && msg->dev_gone.dev_hdl == ctx->dev_hdl) {
|
||||
LOG_I(TAG, "MIDI device disconnected");
|
||||
ctx->connected = false;
|
||||
usb_host_interface_release(ctx->client_hdl, ctx->dev_hdl, ctx->intf_num);
|
||||
usb_host_device_close(ctx->client_hdl, ctx->dev_hdl);
|
||||
ctx->dev_hdl = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void midi_client_task(void* arg) {
|
||||
auto* ctx = static_cast<UsbMidiContext*>(arg);
|
||||
LOG_I(TAG, "MIDI client task started");
|
||||
|
||||
while (ctx->running) {
|
||||
usb_host_client_handle_events(ctx->client_hdl, pdMS_TO_TICKS(100));
|
||||
}
|
||||
|
||||
if (ctx->dev_hdl) {
|
||||
ctx->connected = false;
|
||||
usb_host_interface_release(ctx->client_hdl, ctx->dev_hdl, ctx->intf_num);
|
||||
usb_host_device_close(ctx->client_hdl, ctx->dev_hdl);
|
||||
ctx->dev_hdl = nullptr;
|
||||
}
|
||||
|
||||
LOG_I(TAG, "MIDI client task stopped");
|
||||
xSemaphoreGive(ctx->task_done);
|
||||
vTaskDelete(nullptr);
|
||||
}
|
||||
|
||||
static void api_set_callback(struct Device* device, usb_midi_message_cb_t callback, void* user_data) {
|
||||
auto* ctx = static_cast<UsbMidiContext*>(device_get_driver_data(device));
|
||||
if (!ctx) return;
|
||||
taskENTER_CRITICAL(&ctx->callback_lock);
|
||||
ctx->callback = callback;
|
||||
ctx->callback_arg = user_data;
|
||||
taskEXIT_CRITICAL(&ctx->callback_lock);
|
||||
}
|
||||
|
||||
static bool api_is_connected(struct Device* device) {
|
||||
auto* ctx = static_cast<UsbMidiContext*>(device_get_driver_data(device));
|
||||
return ctx && ctx->connected.load();
|
||||
}
|
||||
|
||||
static const UsbMidiApi midi_api = {
|
||||
.set_callback = api_set_callback,
|
||||
.is_connected = api_is_connected,
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
|
||||
static error_t start_device(struct Device* device) {
|
||||
auto* ctx = new UsbMidiContext();
|
||||
|
||||
if (usb_host_transfer_alloc(MIDI_TRANSFER_BUF_SIZE, 0, &ctx->transfer) != ESP_OK) {
|
||||
LOG_E(TAG, "failed to allocate MIDI transfer");
|
||||
delete ctx;
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
ctx->task_done = xSemaphoreCreateBinary();
|
||||
if (!ctx->task_done) {
|
||||
LOG_E(TAG, "failed to create task done semaphore");
|
||||
usb_host_transfer_free(ctx->transfer);
|
||||
delete ctx;
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
const usb_host_client_config_t client_cfg = {
|
||||
.is_synchronous = false,
|
||||
.max_num_event_msg = 5,
|
||||
.async = {
|
||||
.client_event_callback = client_event_cb,
|
||||
.callback_arg = ctx,
|
||||
},
|
||||
};
|
||||
if (usb_host_client_register(&client_cfg, &ctx->client_hdl) != ESP_OK) {
|
||||
LOG_E(TAG, "failed to register USB host client");
|
||||
vSemaphoreDelete(ctx->task_done);
|
||||
usb_host_transfer_free(ctx->transfer);
|
||||
delete ctx;
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
ctx->running = true;
|
||||
BaseType_t result = xTaskCreate(midi_client_task, "midi_client", MIDI_TASK_STACK,
|
||||
ctx, MIDI_TASK_PRIORITY, &ctx->task_handle);
|
||||
if (result != pdPASS) {
|
||||
LOG_E(TAG, "failed to create midi_client task");
|
||||
ctx->running = false;
|
||||
usb_host_client_deregister(ctx->client_hdl);
|
||||
vSemaphoreDelete(ctx->task_done);
|
||||
usb_host_transfer_free(ctx->transfer);
|
||||
delete ctx;
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
device_set_driver_data(device, ctx);
|
||||
LOG_I(TAG, "started");
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t stop_device(struct Device* device) {
|
||||
auto* ctx = static_cast<UsbMidiContext*>(device_get_driver_data(device));
|
||||
if (!ctx) return ERROR_NONE;
|
||||
|
||||
ctx->running = false;
|
||||
usb_host_client_unblock(ctx->client_hdl);
|
||||
|
||||
if (xSemaphoreTake(ctx->task_done, pdMS_TO_TICKS(MIDI_STOP_TIMEOUT_MS)) != pdTRUE) {
|
||||
LOG_E(TAG, "MIDI client task stop timed out after %dms — a full USB host restart may be required", MIDI_STOP_TIMEOUT_MS);
|
||||
if (ctx->dev_hdl) {
|
||||
ctx->connected = false;
|
||||
if (usb_host_interface_release(ctx->client_hdl, ctx->dev_hdl, ctx->intf_num) != ESP_OK) {
|
||||
LOG_W(TAG, "failed to release MIDI interface during force-stop");
|
||||
}
|
||||
if (usb_host_device_close(ctx->client_hdl, ctx->dev_hdl) != ESP_OK) {
|
||||
LOG_W(TAG, "failed to close MIDI device during force-stop");
|
||||
}
|
||||
ctx->dev_hdl = nullptr;
|
||||
}
|
||||
vTaskDelete(ctx->task_handle);
|
||||
vTaskDelay(pdMS_TO_TICKS(50));
|
||||
}
|
||||
ctx->task_handle = nullptr;
|
||||
vSemaphoreDelete(ctx->task_done);
|
||||
|
||||
usb_host_client_deregister(ctx->client_hdl);
|
||||
ctx->client_hdl = nullptr;
|
||||
|
||||
usb_host_transfer_free(ctx->transfer);
|
||||
ctx->transfer = nullptr;
|
||||
|
||||
device_set_driver_data(device, nullptr);
|
||||
delete ctx;
|
||||
LOG_I(TAG, "stopped");
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
Driver esp32_usbhost_midi_driver = {
|
||||
.name = "esp32_usbhost_midi",
|
||||
.compatible = (const char*[]) { "espressif,esp32-usbhost-midi", nullptr },
|
||||
.start_device = start_device,
|
||||
.stop_device = stop_device,
|
||||
.api = &midi_api,
|
||||
.device_type = &USB_HOST_MIDI_TYPE,
|
||||
.owner = nullptr,
|
||||
.internal = nullptr,
|
||||
};
|
||||
|
||||
} // extern "C"
|
||||
|
||||
#endif // CONFIG_SOC_USB_OTG_SUPPORTED
|
||||
@ -0,0 +1,377 @@
|
||||
#include <sdkconfig.h>
|
||||
#ifdef CONFIG_SOC_USB_OTG_SUPPORTED
|
||||
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/drivers/usb_host_msc.h>
|
||||
#include <tactility/filesystem/file_system.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/queue.h>
|
||||
#include <freertos/task.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <freertos/portmacro.h>
|
||||
|
||||
#include <usb/msc_host.h>
|
||||
#include <usb/msc_host_vfs.h>
|
||||
#include <esp_vfs_fat.h>
|
||||
|
||||
#define TAG "esp32_usbhost_msc"
|
||||
|
||||
#define USB_MSC_MOUNT_PATH_PREFIX "/usb"
|
||||
|
||||
constexpr auto MAX_MSC_DEVICES = 2;
|
||||
constexpr auto MSC_EVENT_QUEUE_SIZE = 8;
|
||||
constexpr auto MSC_PROC_TASK_STACK = 4096;
|
||||
constexpr auto MSC_PROC_TASK_PRIORITY = 5;
|
||||
constexpr auto MSC_STOP_TIMEOUT_MS = 2000;
|
||||
|
||||
typedef struct {
|
||||
uint8_t usb_addr;
|
||||
msc_host_device_handle_t device;
|
||||
msc_host_vfs_handle_t vfs;
|
||||
char mount_path[16];
|
||||
struct FileSystem* fs_entry;
|
||||
bool mounted;
|
||||
} msc_dev_entry_t;
|
||||
|
||||
// The anonymous enum inside msc_host_event_t is C-only scoped; use a local alias in C++.
|
||||
using msc_event_id_t = decltype(msc_host_event_t{}.event);
|
||||
static constexpr msc_event_id_t kMscDeviceConnected = static_cast<msc_event_id_t>(0);
|
||||
static constexpr msc_event_id_t kMscDeviceDisconnected = static_cast<msc_event_id_t>(1);
|
||||
|
||||
enum class MscMsgId : uint8_t { Connected, Disconnected };
|
||||
|
||||
typedef struct {
|
||||
MscMsgId id;
|
||||
union {
|
||||
uint8_t address;
|
||||
msc_host_device_handle_t handle;
|
||||
};
|
||||
} msc_msg_t;
|
||||
|
||||
struct UsbMscContext {
|
||||
msc_dev_entry_t* devs[MAX_MSC_DEVICES] = {};
|
||||
portMUX_TYPE devs_lock = portMUX_INITIALIZER_UNLOCKED;
|
||||
QueueHandle_t event_queue = nullptr;
|
||||
TaskHandle_t proc_task = nullptr;
|
||||
SemaphoreHandle_t proc_task_done = nullptr;
|
||||
std::atomic<bool> proc_running{false};
|
||||
};
|
||||
|
||||
static error_t usb_fs_mount(void* /*data*/) { return ERROR_NONE; }
|
||||
static error_t usb_fs_unmount(void* /*data*/) { return ERROR_NONE; }
|
||||
static bool usb_fs_is_mounted(void* data) {
|
||||
if (!data) return false;
|
||||
return static_cast<msc_dev_entry_t*>(data)->mounted;
|
||||
}
|
||||
static error_t usb_fs_get_path(void* data, char* out_path, size_t out_path_size) {
|
||||
if (!data) return ERROR_INVALID_ARGUMENT;
|
||||
snprintf(out_path, out_path_size, "%s", static_cast<msc_dev_entry_t*>(data)->mount_path);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static const FileSystemApi usb_fs_api = {
|
||||
.mount = usb_fs_mount,
|
||||
.unmount = usb_fs_unmount,
|
||||
.is_mounted = usb_fs_is_mounted,
|
||||
.get_path = usb_fs_get_path,
|
||||
};
|
||||
|
||||
static int find_free_slot(UsbMscContext* ctx) {
|
||||
for (int i = 0; i < MAX_MSC_DEVICES; i++) {
|
||||
if (ctx->devs[i] == nullptr) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int find_slot_by_handle(UsbMscContext* ctx, msc_host_device_handle_t handle) {
|
||||
for (int i = 0; i < MAX_MSC_DEVICES; i++) {
|
||||
if (ctx->devs[i] && ctx->devs[i]->device == handle) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void free_msc_device(UsbMscContext* ctx, int slot) {
|
||||
if (slot < 0 || slot >= MAX_MSC_DEVICES || !ctx->devs[slot]) return;
|
||||
if (ctx->devs[slot]->fs_entry) {
|
||||
ctx->devs[slot]->mounted = false;
|
||||
file_system_remove(ctx->devs[slot]->fs_entry);
|
||||
ctx->devs[slot]->fs_entry = nullptr;
|
||||
}
|
||||
if (ctx->devs[slot]->vfs) {
|
||||
msc_host_vfs_unregister(ctx->devs[slot]->vfs);
|
||||
}
|
||||
if (ctx->devs[slot]->device) {
|
||||
msc_host_uninstall_device(ctx->devs[slot]->device);
|
||||
}
|
||||
free(ctx->devs[slot]);
|
||||
ctx->devs[slot] = nullptr;
|
||||
}
|
||||
|
||||
static void free_all_msc_devices(UsbMscContext* ctx) {
|
||||
for (int i = 0; i < MAX_MSC_DEVICES; i++) {
|
||||
free_msc_device(ctx, i);
|
||||
}
|
||||
}
|
||||
|
||||
static void msc_event_cb(const msc_host_event_t* event, void* arg) {
|
||||
auto* ctx = static_cast<UsbMscContext*>(arg);
|
||||
if (!ctx->event_queue) return;
|
||||
msc_msg_t msg = {};
|
||||
if (event->event == kMscDeviceConnected) {
|
||||
msg.id = MscMsgId::Connected;
|
||||
msg.address = event->device.address;
|
||||
if (xQueueSend(ctx->event_queue, &msg, 0) != pdTRUE) {
|
||||
LOG_W(TAG, "event queue full, dropped Connected event (addr=%d)", event->device.address);
|
||||
}
|
||||
} else if (event->event == kMscDeviceDisconnected) {
|
||||
msg.id = MscMsgId::Disconnected;
|
||||
msg.handle = event->device.handle;
|
||||
if (xQueueSend(ctx->event_queue, &msg, 0) != pdTRUE) {
|
||||
LOG_W(TAG, "event queue full, dropped Disconnected event");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void msc_proc_task(void* arg) {
|
||||
auto* ctx = static_cast<UsbMscContext*>(arg);
|
||||
LOG_I(TAG, "MSC proc task started");
|
||||
|
||||
while (ctx->proc_running) {
|
||||
msc_msg_t msg;
|
||||
if (xQueueReceive(ctx->event_queue, &msg, pdMS_TO_TICKS(100)) != pdTRUE) continue;
|
||||
|
||||
if (msg.id == MscMsgId::Connected) {
|
||||
LOG_I(TAG, "USB drive connected (addr=%d)", msg.address);
|
||||
|
||||
// Find a free slot under the lock, then allocate outside it.
|
||||
taskENTER_CRITICAL(&ctx->devs_lock);
|
||||
int slot = find_free_slot(ctx);
|
||||
taskEXIT_CRITICAL(&ctx->devs_lock);
|
||||
if (slot < 0) {
|
||||
LOG_W(TAG, "no free slots for USB drive");
|
||||
continue;
|
||||
}
|
||||
auto* entry = static_cast<msc_dev_entry_t*>(calloc(1, sizeof(msc_dev_entry_t)));
|
||||
if (!entry) {
|
||||
LOG_E(TAG, "failed to allocate MSC device entry");
|
||||
continue;
|
||||
}
|
||||
// Re-check the slot is still free before committing.
|
||||
taskENTER_CRITICAL(&ctx->devs_lock);
|
||||
if (ctx->devs[slot] != nullptr) {
|
||||
slot = find_free_slot(ctx); // another path claimed it; find a new one
|
||||
if (slot >= 0) ctx->devs[slot] = entry;
|
||||
} else {
|
||||
ctx->devs[slot] = entry;
|
||||
}
|
||||
taskEXIT_CRITICAL(&ctx->devs_lock);
|
||||
if (slot < 0) {
|
||||
free(entry);
|
||||
LOG_W(TAG, "no free slots for USB drive after allocation");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (msc_host_install_device(msg.address, &ctx->devs[slot]->device) != ESP_OK) {
|
||||
LOG_E(TAG, "msc_host_install_device failed");
|
||||
taskENTER_CRITICAL(&ctx->devs_lock);
|
||||
free(ctx->devs[slot]);
|
||||
ctx->devs[slot] = nullptr;
|
||||
taskEXIT_CRITICAL(&ctx->devs_lock);
|
||||
continue;
|
||||
}
|
||||
ctx->devs[slot]->usb_addr = msg.address;
|
||||
snprintf(ctx->devs[slot]->mount_path, sizeof(ctx->devs[slot]->mount_path),
|
||||
USB_MSC_MOUNT_PATH_PREFIX "%d", slot);
|
||||
const char* mount_path = ctx->devs[slot]->mount_path;
|
||||
|
||||
const esp_vfs_fat_mount_config_t mount_cfg = {
|
||||
.format_if_mount_failed = false,
|
||||
.max_files = 4,
|
||||
.allocation_unit_size = 4096,
|
||||
.disk_status_check_enable = false,
|
||||
.use_one_fat = false,
|
||||
};
|
||||
esp_err_t vfs_err = msc_host_vfs_register(ctx->devs[slot]->device, mount_path, &mount_cfg, &ctx->devs[slot]->vfs);
|
||||
if (vfs_err != ESP_OK) {
|
||||
LOG_E(TAG, "msc_host_vfs_register failed for %s: %s", mount_path, esp_err_to_name(vfs_err));
|
||||
msc_host_uninstall_device(ctx->devs[slot]->device);
|
||||
taskENTER_CRITICAL(&ctx->devs_lock);
|
||||
free(ctx->devs[slot]);
|
||||
ctx->devs[slot] = nullptr;
|
||||
taskEXIT_CRITICAL(&ctx->devs_lock);
|
||||
continue;
|
||||
}
|
||||
LOG_I(TAG, "USB drive mounted at %s", mount_path);
|
||||
ctx->devs[slot]->fs_entry = file_system_add(&usb_fs_api, ctx->devs[slot]);
|
||||
if (!ctx->devs[slot]->fs_entry) {
|
||||
LOG_E(TAG, "failed to register filesystem for %s", mount_path);
|
||||
free_msc_device(ctx, slot);
|
||||
continue;
|
||||
}
|
||||
ctx->devs[slot]->mounted = true;
|
||||
|
||||
} else if (msg.id == MscMsgId::Disconnected) {
|
||||
taskENTER_CRITICAL(&ctx->devs_lock);
|
||||
int slot = find_slot_by_handle(ctx, msg.handle);
|
||||
taskEXIT_CRITICAL(&ctx->devs_lock);
|
||||
if (slot >= 0) {
|
||||
LOG_I(TAG, "USB drive disconnected, unmounting slot %d", slot);
|
||||
free_msc_device(ctx, slot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free_all_msc_devices(ctx);
|
||||
LOG_I(TAG, "MSC proc task stopped");
|
||||
xSemaphoreGive(ctx->proc_task_done);
|
||||
vTaskDelete(nullptr);
|
||||
}
|
||||
|
||||
static bool api_eject(struct Device* device, const char* mount_path) {
|
||||
auto* ctx = static_cast<UsbMscContext*>(device_get_driver_data(device));
|
||||
if (!ctx) return false;
|
||||
|
||||
taskENTER_CRITICAL(&ctx->devs_lock);
|
||||
msc_dev_entry_t* entry = nullptr;
|
||||
int found = -1;
|
||||
for (int i = 0; i < MAX_MSC_DEVICES; i++) {
|
||||
if (ctx->devs[i] && strcmp(ctx->devs[i]->mount_path, mount_path) == 0) {
|
||||
found = i;
|
||||
entry = ctx->devs[i];
|
||||
ctx->devs[i] = nullptr; // claim atomically under the lock
|
||||
break;
|
||||
}
|
||||
}
|
||||
taskEXIT_CRITICAL(&ctx->devs_lock);
|
||||
|
||||
if (found >= 0) {
|
||||
LOG_I(TAG, "ejecting USB drive at %s (slot %d)", mount_path, found);
|
||||
// Free outside the lock; slot is already claimed (nullptr) so msc_proc_task won't touch it.
|
||||
if (entry->fs_entry) {
|
||||
entry->mounted = false;
|
||||
file_system_remove(entry->fs_entry);
|
||||
entry->fs_entry = nullptr;
|
||||
}
|
||||
if (entry->vfs) {
|
||||
msc_host_vfs_unregister(entry->vfs);
|
||||
}
|
||||
if (entry->device) {
|
||||
msc_host_uninstall_device(entry->device);
|
||||
}
|
||||
free(entry);
|
||||
LOG_I(TAG, "USB drive ejected, safe to remove");
|
||||
return true;
|
||||
}
|
||||
LOG_W(TAG, "no drive mounted at %s", mount_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
static const UsbMscApi msc_api = {
|
||||
.eject = api_eject,
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
|
||||
static error_t start_device(struct Device* device) {
|
||||
auto* ctx = new UsbMscContext();
|
||||
|
||||
ctx->event_queue = xQueueCreate(MSC_EVENT_QUEUE_SIZE, sizeof(msc_msg_t));
|
||||
if (!ctx->event_queue) {
|
||||
LOG_E(TAG, "failed to create MSC event queue");
|
||||
delete ctx;
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
ctx->proc_task_done = xSemaphoreCreateBinary();
|
||||
if (!ctx->proc_task_done) {
|
||||
LOG_E(TAG, "failed to create task done semaphore");
|
||||
vQueueDelete(ctx->event_queue);
|
||||
delete ctx;
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
const msc_host_driver_config_t msc_cfg = {
|
||||
.create_backround_task = true,
|
||||
.task_priority = MSC_PROC_TASK_PRIORITY,
|
||||
.stack_size = MSC_PROC_TASK_STACK,
|
||||
.core_id = tskNO_AFFINITY,
|
||||
.callback = msc_event_cb,
|
||||
.callback_arg = ctx,
|
||||
};
|
||||
if (msc_host_install(&msc_cfg) != ESP_OK) {
|
||||
LOG_E(TAG, "msc_host_install failed");
|
||||
vQueueDelete(ctx->event_queue);
|
||||
vSemaphoreDelete(ctx->proc_task_done);
|
||||
delete ctx;
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
ctx->proc_running = true;
|
||||
BaseType_t result = xTaskCreate(msc_proc_task, "msc_proc", MSC_PROC_TASK_STACK,
|
||||
ctx, MSC_PROC_TASK_PRIORITY, &ctx->proc_task);
|
||||
if (result != pdPASS) {
|
||||
LOG_E(TAG, "failed to create msc_proc task");
|
||||
ctx->proc_running = false;
|
||||
msc_host_uninstall();
|
||||
vQueueDelete(ctx->event_queue);
|
||||
vSemaphoreDelete(ctx->proc_task_done);
|
||||
delete ctx;
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
device_set_driver_data(device, ctx);
|
||||
LOG_I(TAG, "started");
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t stop_device(struct Device* device) {
|
||||
auto* ctx = static_cast<UsbMscContext*>(device_get_driver_data(device));
|
||||
if (!ctx) return ERROR_NONE;
|
||||
|
||||
ctx->proc_running = false;
|
||||
|
||||
bool exited = (xSemaphoreTake(ctx->proc_task_done, pdMS_TO_TICKS(MSC_STOP_TIMEOUT_MS)) == pdTRUE);
|
||||
if (!exited) {
|
||||
// Retry once with a short extra wait before resorting to force-delete.
|
||||
exited = (xSemaphoreTake(ctx->proc_task_done, pdMS_TO_TICKS(500)) == pdTRUE);
|
||||
}
|
||||
if (!exited) {
|
||||
LOG_W(TAG, "MSC proc task stop timed out, force terminating");
|
||||
vTaskDelete(ctx->proc_task);
|
||||
vTaskDelay(pdMS_TO_TICKS(50));
|
||||
// Task was killed mid-cleanup — free devices ourselves as best-effort.
|
||||
free_all_msc_devices(ctx);
|
||||
}
|
||||
ctx->proc_task = nullptr;
|
||||
vSemaphoreDelete(ctx->proc_task_done);
|
||||
|
||||
msc_host_uninstall();
|
||||
|
||||
if (ctx->event_queue) { vQueueDelete(ctx->event_queue); ctx->event_queue = nullptr; }
|
||||
|
||||
device_set_driver_data(device, nullptr);
|
||||
delete ctx;
|
||||
LOG_I(TAG, "stopped");
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
Driver esp32_usbhost_msc_driver = {
|
||||
.name = "esp32_usbhost_msc",
|
||||
.compatible = (const char*[]) { "espressif,esp32-usbhost-msc", nullptr },
|
||||
.start_device = start_device,
|
||||
.stop_device = stop_device,
|
||||
.api = &msc_api,
|
||||
.device_type = &USB_HOST_MSC_TYPE,
|
||||
.owner = nullptr,
|
||||
.internal = nullptr,
|
||||
};
|
||||
|
||||
} // extern "C"
|
||||
|
||||
#endif // CONFIG_SOC_USB_OTG_SUPPORTED
|
||||
@ -25,6 +25,12 @@ extern Driver esp32_ble_serial_driver;
|
||||
extern Driver esp32_ble_midi_driver;
|
||||
extern Driver esp32_ble_hid_device_driver;
|
||||
#endif
|
||||
#if SOC_USB_OTG_SUPPORTED
|
||||
extern Driver esp32_usbhost_driver;
|
||||
extern Driver esp32_usbhost_hid_driver;
|
||||
extern Driver esp32_usbhost_midi_driver;
|
||||
extern Driver esp32_usbhost_msc_driver;
|
||||
#endif
|
||||
|
||||
static error_t start() {
|
||||
/* We crash when construct fails, because if a single driver fails to construct,
|
||||
@ -42,6 +48,12 @@ static error_t start() {
|
||||
check(driver_construct_add(&esp32_ble_serial_driver) == ERROR_NONE);
|
||||
check(driver_construct_add(&esp32_ble_midi_driver) == ERROR_NONE);
|
||||
check(driver_construct_add(&esp32_ble_hid_device_driver) == ERROR_NONE);
|
||||
#endif
|
||||
#if SOC_USB_OTG_SUPPORTED
|
||||
check(driver_construct_add(&esp32_usbhost_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_msc_driver) == ERROR_NONE);
|
||||
#endif
|
||||
return ERROR_NONE;
|
||||
}
|
||||
@ -49,6 +61,12 @@ static error_t start() {
|
||||
static error_t stop() {
|
||||
/* We crash when destruct fails, because if a single driver fails to destruct,
|
||||
* there is no guarantee that the previously destroyed drivers can be recovered */
|
||||
#if SOC_USB_OTG_SUPPORTED
|
||||
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_hid_driver) == ERROR_NONE);
|
||||
check(driver_remove_destruct(&esp32_usbhost_driver) == ERROR_NONE);
|
||||
#endif
|
||||
#if defined(CONFIG_BT_NIMBLE_ENABLED)
|
||||
check(driver_remove_destruct(&esp32_ble_hid_device_driver) == ERROR_NONE);
|
||||
check(driver_remove_destruct(&esp32_ble_midi_driver) == ERROR_NONE);
|
||||
|
||||
13
Tactility/Include/Tactility/lvgl/UsbHidInput.h
Normal file
13
Tactility/Include/Tactility/lvgl/UsbHidInput.h
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
namespace tt::lvgl {
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
void startUsbHidInput();
|
||||
void stopUsbHidInput();
|
||||
#else
|
||||
inline void startUsbHidInput() {}
|
||||
inline void stopUsbHidInput() {}
|
||||
#endif
|
||||
|
||||
} // namespace tt::lvgl
|
||||
@ -29,6 +29,7 @@ class View final {
|
||||
void showActions();
|
||||
void showActionsForDirectory();
|
||||
void showActionsForFile();
|
||||
void showActionsForMountPoint();
|
||||
|
||||
void viewFile(const std::string&path, const std::string&filename);
|
||||
void createDirEntryWidget(lv_obj_t* parent, dirent& dir_entry);
|
||||
@ -51,6 +52,7 @@ public:
|
||||
void onCopyPressed();
|
||||
void onCutPressed();
|
||||
void onPastePressed();
|
||||
void onEjectPressed();
|
||||
void onDirEntryListScrollBegin();
|
||||
void onResult(LaunchId launchId, Result result, std::unique_ptr<Bundle> bundle);
|
||||
void deinit(const AppContext& appContext);
|
||||
|
||||
@ -16,6 +16,10 @@
|
||||
#include <Tactility/lvgl/Toolbar.h>
|
||||
#include <tactility/check.h>
|
||||
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/drivers/usb_host_msc.h>
|
||||
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <unistd.h>
|
||||
@ -84,6 +88,11 @@ static void onCutPressedCallback(lv_event_t* event) {
|
||||
view->onCutPressed();
|
||||
}
|
||||
|
||||
static void onEjectPressedCallback(lv_event_t* event) {
|
||||
auto* view = static_cast<View*>(lv_event_get_user_data(event));
|
||||
view->onEjectPressed();
|
||||
}
|
||||
|
||||
static void onPastePressedCallback(lv_event_t* event) {
|
||||
auto* view = static_cast<View*>(lv_event_get_user_data(event));
|
||||
view->onPastePressed();
|
||||
@ -275,18 +284,24 @@ void View::onDirEntryPressed(uint32_t index) {
|
||||
}
|
||||
|
||||
void View::onDirEntryLongPressed(int32_t index) {
|
||||
if (state->getCurrentPath() == "/") {
|
||||
return;
|
||||
}
|
||||
|
||||
dirent dir_entry;
|
||||
if (!resolveDirentFromListIndex(index, dir_entry)) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOGGER.info("Pressed {} {}", dir_entry.d_name, dir_entry.d_type);
|
||||
LOGGER.info("Long-pressed {} {}", dir_entry.d_name, dir_entry.d_type);
|
||||
state->setSelectedChildEntry(dir_entry.d_name);
|
||||
|
||||
if (state->getCurrentPath() == "/") {
|
||||
// At root, only USB mount points support actions (eject).
|
||||
// Other root-level entries intentionally have no context actions.
|
||||
const char* name = dir_entry.d_name;
|
||||
if (strncmp(name, "usb", 3) == 0 && isdigit((unsigned char)name[3])) {
|
||||
showActionsForMountPoint();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
using namespace file;
|
||||
switch (dir_entry.d_type) {
|
||||
case TT_DT_DIR:
|
||||
@ -410,6 +425,30 @@ void View::showActions() {
|
||||
void View::showActionsForDirectory() { showActions(); }
|
||||
void View::showActionsForFile() { showActions(); }
|
||||
|
||||
void View::showActionsForMountPoint() {
|
||||
lv_obj_clean(action_list);
|
||||
|
||||
auto* eject_button = lv_list_add_button(action_list, LV_SYMBOL_EJECT, "Eject");
|
||||
lv_obj_add_event_cb(eject_button, onEjectPressedCallback, LV_EVENT_SHORT_CLICKED, this);
|
||||
|
||||
lv_obj_remove_flag(action_list, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
void View::onEjectPressed() {
|
||||
std::string mount_path = state->getSelectedChildPath();
|
||||
LOGGER.info("Ejecting {}", mount_path);
|
||||
|
||||
struct Device* msc_dev = device_find_first_active_by_type(&USB_HOST_MSC_TYPE);
|
||||
if (!msc_dev || !usb_msc_eject(msc_dev, mount_path.c_str())) {
|
||||
LOGGER.warn("usb_msc_eject: {} not found", mount_path);
|
||||
alertdialog::start("Eject failed", "Could not eject \"" + file::getLastPathSegment(mount_path) + "\".");
|
||||
}
|
||||
|
||||
onNavigate();
|
||||
state->setEntriesForPath(state->getCurrentPath());
|
||||
update();
|
||||
}
|
||||
|
||||
void View::update(size_t start_index) {
|
||||
const bool is_root = (state->getCurrentPath() == "/");
|
||||
|
||||
|
||||
365
Tactility/Source/lvgl/UsbHidInput.cpp
Normal file
365
Tactility/Source/lvgl/UsbHidInput.cpp
Normal file
@ -0,0 +1,365 @@
|
||||
#include <Tactility/lvgl/UsbHidInput.h>
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
|
||||
#include <atomic>
|
||||
#include <Tactility/Assets.h>
|
||||
#include <Tactility/lvgl/Keyboard.h>
|
||||
#include <Tactility/lvgl/LvglSync.h>
|
||||
|
||||
#include <Tactility/Logger.h>
|
||||
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/drivers/usb_host_hid.h>
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/queue.h>
|
||||
#include <freertos/task.h>
|
||||
#include <freertos/semphr.h>
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
namespace tt::lvgl {
|
||||
|
||||
static const auto LOGGER = Logger("UsbHidInput");
|
||||
|
||||
constexpr auto HID_EVENT_QUEUE_SIZE = 64;
|
||||
constexpr auto KEY_EVENT_QUEUE_SIZE = 64;
|
||||
constexpr auto TASK_STACK = 3072;
|
||||
constexpr auto TASK_PRIORITY = 5;
|
||||
constexpr auto STOP_TIMEOUT_MS = 2000;
|
||||
constexpr uint32_t KEY_REPEAT_DELAY_MS = 500;
|
||||
constexpr uint32_t KEY_REPEAT_RATE_MS = 50;
|
||||
constexpr int32_t CURSOR_SIZE = 16;
|
||||
|
||||
typedef struct {
|
||||
uint32_t lv_key;
|
||||
bool pressed;
|
||||
} KeyEvent;
|
||||
|
||||
struct UsbHidInputCtx {
|
||||
// Receives raw UsbHidEvent items from the HID driver
|
||||
QueueHandle_t hid_queue = nullptr;
|
||||
// Key-only events forwarded to the keyboard read callback
|
||||
QueueHandle_t key_queue = nullptr;
|
||||
TaskHandle_t task = nullptr;
|
||||
SemaphoreHandle_t task_done = nullptr;
|
||||
std::atomic<bool> running{false};
|
||||
std::atomic<bool> subscribed{false};
|
||||
|
||||
lv_indev_t* mouse_indev = nullptr;
|
||||
lv_indev_t* kb_indev = nullptr;
|
||||
lv_obj_t* mouse_cursor = nullptr;
|
||||
|
||||
std::atomic<int32_t> mouse_x{0};
|
||||
std::atomic<int32_t> mouse_y{0};
|
||||
std::atomic<bool> mouse_btn1{false};
|
||||
bool mouse_connected = false;
|
||||
|
||||
uint32_t repeat_lv_key = 0;
|
||||
uint32_t repeat_start_ms = 0;
|
||||
uint32_t repeat_last_ms = 0;
|
||||
bool emit_repeat_release = false;
|
||||
uint32_t repeat_release_key = 0;
|
||||
};
|
||||
|
||||
static UsbHidInputCtx* s_ctx = nullptr;
|
||||
|
||||
static void mouse_read_cb(lv_indev_t* indev, lv_indev_data_t* data) {
|
||||
auto* ctx = static_cast<UsbHidInputCtx*>(lv_indev_get_user_data(indev));
|
||||
int32_t cx = ctx->mouse_x.load();
|
||||
int32_t cy = ctx->mouse_y.load();
|
||||
|
||||
lv_display_t* disp = lv_display_get_default();
|
||||
if (disp) {
|
||||
int32_t ow = lv_display_get_original_horizontal_resolution(disp);
|
||||
int32_t oh = lv_display_get_original_vertical_resolution(disp);
|
||||
switch (lv_display_get_rotation(disp)) {
|
||||
case LV_DISPLAY_ROTATION_0:
|
||||
data->point.x = (lv_coord_t)cx;
|
||||
data->point.y = (lv_coord_t)cy;
|
||||
break;
|
||||
case LV_DISPLAY_ROTATION_90:
|
||||
data->point.x = (lv_coord_t)cy;
|
||||
data->point.y = (lv_coord_t)(oh - cx - 1);
|
||||
break;
|
||||
case LV_DISPLAY_ROTATION_180:
|
||||
data->point.x = (lv_coord_t)(ow - cx - 1);
|
||||
data->point.y = (lv_coord_t)(oh - cy - 1);
|
||||
break;
|
||||
case LV_DISPLAY_ROTATION_270:
|
||||
data->point.x = (lv_coord_t)(ow - cy - 1);
|
||||
data->point.y = (lv_coord_t)cx;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
data->point.x = (lv_coord_t)cx;
|
||||
data->point.y = (lv_coord_t)cy;
|
||||
}
|
||||
|
||||
data->state = ctx->mouse_btn1.load() ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED;
|
||||
}
|
||||
|
||||
static void keyboard_read_cb(lv_indev_t* indev, lv_indev_data_t* data) {
|
||||
auto* ctx = static_cast<UsbHidInputCtx*>(lv_indev_get_user_data(indev));
|
||||
|
||||
if (ctx->emit_repeat_release) {
|
||||
ctx->emit_repeat_release = false;
|
||||
data->key = ctx->repeat_release_key;
|
||||
data->state = LV_INDEV_STATE_RELEASED;
|
||||
return;
|
||||
}
|
||||
|
||||
KeyEvent evt;
|
||||
if (ctx->key_queue && xQueueReceive(ctx->key_queue, &evt, 0) == pdTRUE) {
|
||||
data->key = evt.lv_key;
|
||||
data->state = evt.pressed ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED;
|
||||
if (evt.pressed) {
|
||||
ctx->repeat_lv_key = evt.lv_key;
|
||||
ctx->repeat_start_ms = lv_tick_get();
|
||||
ctx->repeat_last_ms = 0;
|
||||
} else if (evt.lv_key == ctx->repeat_lv_key) {
|
||||
ctx->repeat_lv_key = 0;
|
||||
}
|
||||
data->continue_reading = (uxQueueMessagesWaiting(ctx->key_queue) > 0);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t rkey = ctx->repeat_lv_key;
|
||||
if (rkey != 0) {
|
||||
uint32_t now_ms = lv_tick_get();
|
||||
if ((now_ms - ctx->repeat_start_ms) >= KEY_REPEAT_DELAY_MS) {
|
||||
uint32_t last = ctx->repeat_last_ms;
|
||||
if (last == 0 || (now_ms - last) >= KEY_REPEAT_RATE_MS) {
|
||||
ctx->repeat_last_ms = now_ms;
|
||||
ctx->emit_repeat_release = true;
|
||||
ctx->repeat_release_key = rkey;
|
||||
data->key = rkey;
|
||||
data->state = LV_INDEV_STATE_PRESSED;
|
||||
data->continue_reading = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data->state = LV_INDEV_STATE_RELEASED;
|
||||
}
|
||||
|
||||
static void usbHidInputTask(void* arg) {
|
||||
auto* ctx = static_cast<UsbHidInputCtx*>(arg);
|
||||
LOGGER.info("started");
|
||||
|
||||
while (!lv_is_initialized()) {
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
|
||||
if (lock()) {
|
||||
ctx->mouse_cursor = lv_image_create(lv_layer_sys());
|
||||
lv_obj_remove_flag(ctx->mouse_cursor, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_image_set_src(ctx->mouse_cursor, TT_ASSETS_UI_CURSOR);
|
||||
lv_obj_add_flag(ctx->mouse_cursor, LV_OBJ_FLAG_HIDDEN);
|
||||
|
||||
ctx->mouse_indev = lv_indev_create();
|
||||
lv_indev_set_type(ctx->mouse_indev, LV_INDEV_TYPE_POINTER);
|
||||
lv_indev_set_read_cb(ctx->mouse_indev, mouse_read_cb);
|
||||
lv_indev_set_user_data(ctx->mouse_indev, ctx);
|
||||
lv_indev_set_cursor(ctx->mouse_indev, ctx->mouse_cursor);
|
||||
|
||||
ctx->kb_indev = lv_indev_create();
|
||||
lv_indev_set_type(ctx->kb_indev, LV_INDEV_TYPE_KEYPAD);
|
||||
lv_indev_set_read_cb(ctx->kb_indev, keyboard_read_cb);
|
||||
lv_indev_set_user_data(ctx->kb_indev, ctx);
|
||||
lv_indev_set_group(ctx->kb_indev, lv_group_get_default());
|
||||
|
||||
unlock();
|
||||
LOGGER.info("LVGL input devices registered");
|
||||
} else {
|
||||
LOGGER.warn("could not acquire LVGL lock for indev registration");
|
||||
}
|
||||
|
||||
// Drain the HID event queue and route events to the appropriate destinations
|
||||
while (ctx->running) {
|
||||
UsbHidEvent hid_evt;
|
||||
if (xQueueReceive(ctx->hid_queue, &hid_evt, pdMS_TO_TICKS(100)) != pdTRUE) {
|
||||
if (!ctx->subscribed) {
|
||||
struct Device* hid_dev = device_find_first_active_by_type(&USB_HOST_HID_TYPE);
|
||||
if (hid_dev) ctx->subscribed = usb_host_hid_subscribe(hid_dev, ctx->hid_queue);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (hid_evt.type) {
|
||||
case USB_HID_EVENT_KEY: {
|
||||
KeyEvent key_evt = { hid_evt.key.key_code, hid_evt.key.pressed };
|
||||
xQueueSend(ctx->key_queue, &key_evt, 0);
|
||||
break;
|
||||
}
|
||||
case USB_HID_EVENT_MOUSE_MOVE: {
|
||||
lv_display_t* disp = lv_display_get_default();
|
||||
if (!disp) break;
|
||||
// Use logical (post-rotation) resolution so clamping matches LVGL's coordinate space
|
||||
int32_t w = lv_display_get_horizontal_resolution(disp);
|
||||
int32_t h = lv_display_get_vertical_resolution(disp);
|
||||
int32_t nx = ctx->mouse_x.load() + hid_evt.mouse_move.dx;
|
||||
int32_t ny = ctx->mouse_y.load() + hid_evt.mouse_move.dy;
|
||||
if (nx < 0) nx = 0;
|
||||
if (nx > w - CURSOR_SIZE - 1) nx = w - CURSOR_SIZE - 1;
|
||||
if (ny < 0) ny = 0;
|
||||
if (ny > h - CURSOR_SIZE - 1) ny = h - CURSOR_SIZE - 1;
|
||||
ctx->mouse_x.store(nx);
|
||||
ctx->mouse_y.store(ny);
|
||||
break;
|
||||
}
|
||||
case USB_HID_EVENT_MOUSE_BTN:
|
||||
ctx->mouse_btn1.store(hid_evt.mouse_btn.button1);
|
||||
break;
|
||||
case USB_HID_EVENT_SCROLL: {
|
||||
int32_t delta = hid_evt.scroll.delta;
|
||||
uint32_t key = (delta < 0) ? USB_HID_KEY_UP : USB_HID_KEY_DOWN;
|
||||
int ticks = (delta < 0) ? -delta : delta;
|
||||
// Clamp to reasonable maximum to prevent queue overflow
|
||||
constexpr int MAX_SCROLL_TICKS = 10;
|
||||
if (ticks > MAX_SCROLL_TICKS) ticks = MAX_SCROLL_TICKS;
|
||||
for (int t = 0; t < ticks; t++) {
|
||||
KeyEvent press = { key, true };
|
||||
KeyEvent release = { key, false };
|
||||
xQueueSend(ctx->key_queue, &press, 0);
|
||||
xQueueSend(ctx->key_queue, &release, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case USB_HID_EVENT_KEYBOARD_CONNECTED:
|
||||
if (ctx->kb_indev && lock(pdMS_TO_TICKS(200))) {
|
||||
hardware_keyboard_set_indev(ctx->kb_indev);
|
||||
unlock();
|
||||
}
|
||||
break;
|
||||
case USB_HID_EVENT_KEYBOARD_DISCONNECTED:
|
||||
if (lock(pdMS_TO_TICKS(200))) {
|
||||
hardware_keyboard_set_indev(nullptr);
|
||||
unlock();
|
||||
}
|
||||
break;
|
||||
case USB_HID_EVENT_MOUSE_CONNECTED:
|
||||
ctx->mouse_connected = true;
|
||||
if (ctx->mouse_cursor && lock(pdMS_TO_TICKS(200))) {
|
||||
lv_obj_remove_flag(ctx->mouse_cursor, LV_OBJ_FLAG_HIDDEN);
|
||||
unlock();
|
||||
}
|
||||
break;
|
||||
case USB_HID_EVENT_MOUSE_DISCONNECTED:
|
||||
ctx->mouse_connected = false;
|
||||
if (ctx->mouse_cursor && lock(pdMS_TO_TICKS(200))) {
|
||||
lv_obj_add_flag(ctx->mouse_cursor, LV_OBJ_FLAG_HIDDEN);
|
||||
unlock();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (lock()) {
|
||||
if (ctx->mouse_indev) { lv_indev_delete(ctx->mouse_indev); ctx->mouse_indev = nullptr; }
|
||||
if (ctx->mouse_cursor) { lv_obj_delete(ctx->mouse_cursor); ctx->mouse_cursor = nullptr; }
|
||||
if (ctx->kb_indev) {
|
||||
hardware_keyboard_set_indev(nullptr);
|
||||
lv_indev_delete(ctx->kb_indev);
|
||||
ctx->kb_indev = nullptr;
|
||||
}
|
||||
unlock();
|
||||
}
|
||||
|
||||
LOGGER.info("stopped");
|
||||
xSemaphoreGive(ctx->task_done);
|
||||
vTaskDelete(nullptr);
|
||||
}
|
||||
|
||||
void startUsbHidInput() {
|
||||
if (s_ctx != nullptr) return;
|
||||
|
||||
auto* ctx = new UsbHidInputCtx();
|
||||
|
||||
ctx->hid_queue = xQueueCreate(HID_EVENT_QUEUE_SIZE, sizeof(UsbHidEvent));
|
||||
if (!ctx->hid_queue) {
|
||||
LOGGER.error("failed to create HID event queue");
|
||||
delete ctx;
|
||||
return;
|
||||
}
|
||||
|
||||
ctx->key_queue = xQueueCreate(KEY_EVENT_QUEUE_SIZE, sizeof(KeyEvent));
|
||||
if (!ctx->key_queue) {
|
||||
LOGGER.error("failed to create key event queue");
|
||||
vQueueDelete(ctx->hid_queue);
|
||||
delete ctx;
|
||||
return;
|
||||
}
|
||||
|
||||
ctx->task_done = xSemaphoreCreateBinary();
|
||||
if (!ctx->task_done) {
|
||||
LOGGER.error("failed to create task done semaphore");
|
||||
vQueueDelete(ctx->hid_queue);
|
||||
vQueueDelete(ctx->key_queue);
|
||||
delete ctx;
|
||||
return;
|
||||
}
|
||||
|
||||
struct Device* hid_dev = device_find_first_active_by_type(&USB_HOST_HID_TYPE);
|
||||
if (hid_dev) ctx->subscribed = usb_host_hid_subscribe(hid_dev, ctx->hid_queue);
|
||||
|
||||
ctx->running = true;
|
||||
if (xTaskCreate(usbHidInputTask, "usb_hid_inp", TASK_STACK, ctx, TASK_PRIORITY, &ctx->task) != pdPASS) {
|
||||
LOGGER.error("failed to create task");
|
||||
ctx->running = false;
|
||||
if (hid_dev) usb_host_hid_unsubscribe(hid_dev, ctx->hid_queue);
|
||||
vQueueDelete(ctx->hid_queue);
|
||||
vQueueDelete(ctx->key_queue);
|
||||
vSemaphoreDelete(ctx->task_done);
|
||||
delete ctx;
|
||||
return;
|
||||
}
|
||||
|
||||
s_ctx = ctx;
|
||||
LOGGER.info("started");
|
||||
}
|
||||
|
||||
void stopUsbHidInput() {
|
||||
if (!s_ctx) return;
|
||||
auto* ctx = s_ctx;
|
||||
s_ctx = nullptr;
|
||||
|
||||
ctx->running = false;
|
||||
|
||||
if (xSemaphoreTake(ctx->task_done, pdMS_TO_TICKS(STOP_TIMEOUT_MS)) != pdTRUE) {
|
||||
LOGGER.warn("task stop timed out, force terminating");
|
||||
vTaskDelete(ctx->task);
|
||||
// Task was killed before it could clean up LVGL objects; do it here to
|
||||
// prevent mouse_read_cb / keyboard_read_cb from running with a freed ctx.
|
||||
if (lock(pdMS_TO_TICKS(200))) {
|
||||
if (ctx->mouse_indev) { lv_indev_delete(ctx->mouse_indev); ctx->mouse_indev = nullptr; }
|
||||
if (ctx->mouse_cursor) { lv_obj_delete(ctx->mouse_cursor); ctx->mouse_cursor = nullptr; }
|
||||
if (ctx->kb_indev) {
|
||||
hardware_keyboard_set_indev(nullptr);
|
||||
lv_indev_delete(ctx->kb_indev);
|
||||
ctx->kb_indev = nullptr;
|
||||
}
|
||||
unlock();
|
||||
}
|
||||
}
|
||||
ctx->task = nullptr;
|
||||
|
||||
if (ctx->subscribed) {
|
||||
struct Device* hid_dev = device_find_first_active_by_type(&USB_HOST_HID_TYPE);
|
||||
if (hid_dev) usb_host_hid_unsubscribe(hid_dev, ctx->hid_queue);
|
||||
}
|
||||
vQueueDelete(ctx->hid_queue);
|
||||
vQueueDelete(ctx->key_queue);
|
||||
vSemaphoreDelete(ctx->task_done);
|
||||
delete ctx;
|
||||
|
||||
LOGGER.info("stopped");
|
||||
}
|
||||
|
||||
} // namespace tt::lvgl
|
||||
|
||||
#endif // ESP_PLATFORM
|
||||
@ -7,6 +7,7 @@
|
||||
#include <Tactility/LogMessages.h>
|
||||
#include <Tactility/lvgl/LvglSync.h>
|
||||
#include <Tactility/lvgl/Statusbar.h>
|
||||
#include <Tactility/lvgl/UsbHidInput.h>
|
||||
#include <Tactility/service/loader/Loader.h>
|
||||
#include <Tactility/service/ServiceRegistration.h>
|
||||
#include <Tactility/Tactility.h>
|
||||
@ -191,12 +192,16 @@ bool GuiService::onStart(ServiceContext& service) {
|
||||
|
||||
isStarted = true;
|
||||
|
||||
lvgl::startUsbHidInput();
|
||||
|
||||
thread->start();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GuiService::onStop(ServiceContext& service) {
|
||||
lvgl::stopUsbHidInput();
|
||||
|
||||
lock();
|
||||
|
||||
const auto loader = findLoaderService();
|
||||
|
||||
@ -15,12 +15,19 @@
|
||||
#include <tactility/drivers/bluetooth.h>
|
||||
#include <tactility/drivers/bluetooth_serial.h>
|
||||
#include <tactility/drivers/bluetooth_midi.h>
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/drivers/usb_host_hid.h>
|
||||
#include <tactility/drivers/usb_host_midi.h>
|
||||
#include <tactility/drivers/usb_host_msc.h>
|
||||
#include <Tactility/service/gps/GpsService.h>
|
||||
#include <Tactility/service/wifi/Wifi.h>
|
||||
#include <tactility/check.h>
|
||||
#include <tactility/filesystem/file_system.h>
|
||||
|
||||
#include <tactility/lvgl_icon_statusbar.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace tt::service::statusbar {
|
||||
|
||||
static const auto LOGGER = Logger("StatusbarService");
|
||||
@ -134,6 +141,8 @@ class StatusbarService final : public Service {
|
||||
const char* sdcard_last_icon = nullptr;
|
||||
int8_t power_icon_id;
|
||||
const char* power_last_icon = nullptr;
|
||||
int8_t usb_icon_id;
|
||||
bool usb_last_state = false;
|
||||
|
||||
void lock() const {
|
||||
mutex.lock();
|
||||
@ -204,6 +213,31 @@ class StatusbarService final : public Service {
|
||||
}
|
||||
}
|
||||
|
||||
void updateUsbIcon() {
|
||||
struct Device* hid_dev = device_find_first_active_by_type(&USB_HOST_HID_TYPE);
|
||||
struct Device* midi_dev = device_find_first_active_by_type(&USB_HOST_MIDI_TYPE);
|
||||
bool connected = (hid_dev && usb_host_hid_is_connected(hid_dev)) ||
|
||||
(midi_dev && usb_midi_is_connected(midi_dev));
|
||||
if (!connected) {
|
||||
// MSC: scan filesystems for any mounted /usb* path
|
||||
file_system_for_each(&connected, [](struct FileSystem* fs, void* ctx) -> bool {
|
||||
if (!file_system_is_mounted(fs)) return true;
|
||||
char path[64];
|
||||
if (file_system_get_path(fs, path, sizeof(path)) == ERROR_NONE) {
|
||||
if (strncmp(path, USB_MSC_MOUNT_PATH_PREFIX, sizeof(USB_MSC_MOUNT_PATH_PREFIX) - 1) == 0) {
|
||||
*static_cast<bool*>(ctx) = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
if (connected != usb_last_state) {
|
||||
lvgl::statusbar_icon_set_visibility(usb_icon_id, connected);
|
||||
usb_last_state = connected;
|
||||
}
|
||||
}
|
||||
|
||||
void updateSdCardIcon() {
|
||||
auto* sdcard_fs = findSdcardFileSystem(false);
|
||||
// TODO: Support multiple SD cards
|
||||
@ -231,6 +265,7 @@ class StatusbarService final : public Service {
|
||||
updateWifiIcon();
|
||||
updateSdCardIcon();
|
||||
updatePowerStatusIcon();
|
||||
updateUsbIcon();
|
||||
lvgl::unlock();
|
||||
}
|
||||
}
|
||||
@ -241,16 +276,18 @@ public:
|
||||
StatusbarService() {
|
||||
gps_icon_id = lvgl::statusbar_icon_add();
|
||||
bt_icon_id = lvgl::statusbar_icon_add();
|
||||
usb_icon_id = lvgl::statusbar_icon_add();
|
||||
sdcard_icon_id = lvgl::statusbar_icon_add();
|
||||
wifi_icon_id = lvgl::statusbar_icon_add();
|
||||
power_icon_id = lvgl::statusbar_icon_add();
|
||||
}
|
||||
|
||||
~StatusbarService() override {
|
||||
lvgl::statusbar_icon_remove(power_icon_id);
|
||||
lvgl::statusbar_icon_remove(wifi_icon_id);
|
||||
lvgl::statusbar_icon_remove(sdcard_icon_id);
|
||||
lvgl::statusbar_icon_remove(usb_icon_id);
|
||||
lvgl::statusbar_icon_remove(bt_icon_id);
|
||||
lvgl::statusbar_icon_remove(power_icon_id);
|
||||
lvgl::statusbar_icon_remove(gps_icon_id);
|
||||
}
|
||||
|
||||
@ -262,6 +299,8 @@ public:
|
||||
|
||||
// TODO: Make thread-safe for LVGL
|
||||
lvgl::statusbar_icon_set_visibility(wifi_icon_id, true);
|
||||
lvgl::statusbar_icon_set_image(usb_icon_id, LVGL_ICON_STATUSBAR_USB);
|
||||
lvgl::statusbar_icon_set_visibility(usb_icon_id, false);
|
||||
|
||||
auto service = findServiceById<StatusbarService>(manifest.id);
|
||||
assert(service);
|
||||
|
||||
@ -67,15 +67,9 @@ static const char* getChipModelName(esp_chip_model_t model) {
|
||||
case CHIP_ESP32C2: return "ESP32-C2";
|
||||
case CHIP_ESP32C6: return "ESP32-C6";
|
||||
case CHIP_ESP32H2: return "ESP32-H2";
|
||||
#ifdef CHIP_ESP32P4
|
||||
case CHIP_ESP32P4: return "ESP32-P4";
|
||||
#endif
|
||||
#ifdef CHIP_ESP32C5
|
||||
case CHIP_ESP32C5: return "ESP32-C5";
|
||||
#endif
|
||||
#ifdef CHIP_ESP32C61
|
||||
case CHIP_ESP32C61: return "ESP32-C61";
|
||||
#endif
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
@ -273,6 +273,14 @@ bool device_exists_of_type(const struct DeviceType* type);
|
||||
*/
|
||||
struct Device* device_find_by_name(const char* name);
|
||||
|
||||
/**
|
||||
* Find the first started device of the given type.
|
||||
*
|
||||
* @param[in] type non-null device type pointer
|
||||
* @return the first started device of the given type, or NULL if none found
|
||||
*/
|
||||
struct Device* device_find_first_active_by_type(const struct DeviceType* type);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
72
TactilityKernel/include/tactility/drivers/usb_host_hid.h
Normal file
72
TactilityKernel/include/tactility/drivers/usb_host_hid.h
Normal file
@ -0,0 +1,72 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct Device;
|
||||
struct DeviceType;
|
||||
|
||||
typedef void* UsbHidQueueHandle;
|
||||
|
||||
/** Key values — numerically identical to lv_key_t so no translation is needed. */
|
||||
typedef enum {
|
||||
USB_HID_KEY_UP = 17,
|
||||
USB_HID_KEY_DOWN = 18,
|
||||
USB_HID_KEY_RIGHT = 19,
|
||||
USB_HID_KEY_LEFT = 20,
|
||||
USB_HID_KEY_ESC = 27,
|
||||
USB_HID_KEY_DEL = 127,
|
||||
USB_HID_KEY_BACKSPACE = 8,
|
||||
USB_HID_KEY_ENTER = 10,
|
||||
USB_HID_KEY_NEXT = 9,
|
||||
USB_HID_KEY_PREV = 11,
|
||||
USB_HID_KEY_HOME = 2,
|
||||
USB_HID_KEY_END = 3,
|
||||
} UsbHidKey;
|
||||
|
||||
typedef enum {
|
||||
USB_HID_EVENT_KEY,
|
||||
USB_HID_EVENT_MOUSE_MOVE,
|
||||
USB_HID_EVENT_MOUSE_BTN,
|
||||
USB_HID_EVENT_SCROLL,
|
||||
USB_HID_EVENT_KEYBOARD_CONNECTED,
|
||||
USB_HID_EVENT_KEYBOARD_DISCONNECTED,
|
||||
USB_HID_EVENT_MOUSE_CONNECTED,
|
||||
USB_HID_EVENT_MOUSE_DISCONNECTED,
|
||||
} UsbHidEventType;
|
||||
|
||||
typedef struct {
|
||||
UsbHidEventType type;
|
||||
union {
|
||||
struct { uint32_t key_code; bool pressed; } key;
|
||||
struct { int32_t dx; int32_t dy; } mouse_move;
|
||||
struct { bool button1; bool button2; } mouse_btn;
|
||||
struct { int32_t delta; } scroll;
|
||||
};
|
||||
} UsbHidEvent;
|
||||
|
||||
struct UsbHidApi {
|
||||
bool (*is_connected)(struct Device* device);
|
||||
bool (*subscribe)(struct Device* device, UsbHidQueueHandle event_queue);
|
||||
void (*unsubscribe)(struct Device* device, UsbHidQueueHandle event_queue);
|
||||
};
|
||||
|
||||
extern const struct DeviceType USB_HOST_HID_TYPE;
|
||||
|
||||
/** Returns true if any HID device (keyboard or mouse) is currently connected. */
|
||||
bool usb_host_hid_is_connected(struct Device* device);
|
||||
|
||||
/** Subscribe a FreeRTOS queue to receive UsbHidEvent items from the HID driver. */
|
||||
bool usb_host_hid_subscribe(struct Device* device, UsbHidQueueHandle event_queue);
|
||||
|
||||
/** Unsubscribe a previously subscribed queue. */
|
||||
void usb_host_hid_unsubscribe(struct Device* device, UsbHidQueueHandle event_queue);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
53
TactilityKernel/include/tactility/drivers/usb_host_midi.h
Normal file
53
TactilityKernel/include/tactility/drivers/usb_host_midi.h
Normal file
@ -0,0 +1,53 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct Device;
|
||||
struct DeviceType;
|
||||
|
||||
/**
|
||||
* Decoded USB MIDI message.
|
||||
*
|
||||
* `status` encodes both message type and channel:
|
||||
* type = status & 0xF0 (0x80=NoteOff, 0x90=NoteOn, 0xB0=CC, 0xC0=PC, 0xE0=PitchBend, ...)
|
||||
* channel = status & 0x0F (0–15)
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t cable; /**< USB cable number (0–15, almost always 0) */
|
||||
uint8_t status; /**< MIDI status byte */
|
||||
uint8_t data1; /**< First data byte */
|
||||
uint8_t data2; /**< Second data byte */
|
||||
} usb_midi_message_t;
|
||||
|
||||
typedef void (*usb_midi_message_cb_t)(const usb_midi_message_t* msg, void* user_data);
|
||||
|
||||
struct UsbMidiApi {
|
||||
void (*set_callback)(struct Device* device, usb_midi_message_cb_t callback, void* user_data);
|
||||
bool (*is_connected)(struct Device* device);
|
||||
};
|
||||
|
||||
extern const struct DeviceType USB_HOST_MIDI_TYPE;
|
||||
|
||||
/**
|
||||
* Register a callback for incoming MIDI messages.
|
||||
* Replaces any previously registered callback. Pass NULL to disable.
|
||||
* @param device non-null ready USB MIDI device.
|
||||
*/
|
||||
// TODO: Make an interface that takes/releases control
|
||||
void usb_midi_set_callback(struct Device* device, usb_midi_message_cb_t callback, void* user_data);
|
||||
|
||||
/**
|
||||
* Returns true if a MIDI device is currently connected and streaming.
|
||||
* @param device non-null ready USB MIDI device.
|
||||
*/
|
||||
bool usb_midi_is_connected(struct Device* device);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
31
TactilityKernel/include/tactility/drivers/usb_host_msc.h
Normal file
31
TactilityKernel/include/tactility/drivers/usb_host_msc.h
Normal file
@ -0,0 +1,31 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct Device;
|
||||
struct DeviceType;
|
||||
|
||||
#define USB_MSC_MOUNT_PATH_PREFIX "/usb"
|
||||
|
||||
struct UsbMscApi {
|
||||
bool (*eject)(struct Device* device, const char* mount_path);
|
||||
};
|
||||
|
||||
extern const struct DeviceType USB_HOST_MSC_TYPE;
|
||||
|
||||
/**
|
||||
* Safely eject a mounted USB drive.
|
||||
* @param device non-null ready USB MSC device (from device_find_first_active_by_type).
|
||||
* @param mount_path Full mount path (e.g. "/usb0").
|
||||
* @return true if the drive was found and ejected.
|
||||
*/
|
||||
bool usb_msc_eject(struct Device* device, const char* mount_path);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@ -370,4 +370,16 @@ Device* device_find_by_name(const char* name) {
|
||||
return found;
|
||||
}
|
||||
|
||||
Device* device_find_first_active_by_type(const DeviceType* type) {
|
||||
Device* found = nullptr;
|
||||
device_for_each_of_type(type, &found, [](Device* dev, void* ctx) -> bool {
|
||||
if (device_is_ready(dev)) {
|
||||
*static_cast<Device**>(ctx) = dev;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return found;
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
28
TactilityKernel/source/drivers/usb_host_hid.cpp
Normal file
28
TactilityKernel/source/drivers/usb_host_hid.cpp
Normal file
@ -0,0 +1,28 @@
|
||||
#include <tactility/drivers/usb_host_hid.h>
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/driver.h>
|
||||
|
||||
#define USB_HID_API(driver) ((struct UsbHidApi*)(driver)->api)
|
||||
|
||||
extern "C" {
|
||||
|
||||
const struct DeviceType USB_HOST_HID_TYPE = {
|
||||
.name = "usb-host-hid",
|
||||
};
|
||||
|
||||
bool usb_host_hid_is_connected(struct Device* device) {
|
||||
auto* api = USB_HID_API(device_get_driver(device));
|
||||
return api->is_connected(device);
|
||||
}
|
||||
|
||||
bool usb_host_hid_subscribe(struct Device* device, UsbHidQueueHandle event_queue) {
|
||||
auto* api = USB_HID_API(device_get_driver(device));
|
||||
return api->subscribe(device, event_queue);
|
||||
}
|
||||
|
||||
void usb_host_hid_unsubscribe(struct Device* device, UsbHidQueueHandle event_queue) {
|
||||
auto* api = USB_HID_API(device_get_driver(device));
|
||||
api->unsubscribe(device, event_queue);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
23
TactilityKernel/source/drivers/usb_host_midi.cpp
Normal file
23
TactilityKernel/source/drivers/usb_host_midi.cpp
Normal file
@ -0,0 +1,23 @@
|
||||
#include <tactility/drivers/usb_host_midi.h>
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/driver.h>
|
||||
|
||||
#define USB_MIDI_API(driver) ((struct UsbMidiApi*)(driver)->api)
|
||||
|
||||
extern "C" {
|
||||
|
||||
const struct DeviceType USB_HOST_MIDI_TYPE = {
|
||||
.name = "usb-host-midi",
|
||||
};
|
||||
|
||||
void usb_midi_set_callback(struct Device* device, usb_midi_message_cb_t callback, void* user_data) {
|
||||
auto* api = USB_MIDI_API(device_get_driver(device));
|
||||
api->set_callback(device, callback, user_data);
|
||||
}
|
||||
|
||||
bool usb_midi_is_connected(struct Device* device) {
|
||||
auto* api = USB_MIDI_API(device_get_driver(device));
|
||||
return api->is_connected(device);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
18
TactilityKernel/source/drivers/usb_host_msc.cpp
Normal file
18
TactilityKernel/source/drivers/usb_host_msc.cpp
Normal file
@ -0,0 +1,18 @@
|
||||
#include <tactility/drivers/usb_host_msc.h>
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/driver.h>
|
||||
|
||||
#define USB_MSC_API(driver) ((struct UsbMscApi*)(driver)->api)
|
||||
|
||||
extern "C" {
|
||||
|
||||
const struct DeviceType USB_HOST_MSC_TYPE = {
|
||||
.name = "usb-host-msc",
|
||||
};
|
||||
|
||||
bool usb_msc_eject(struct Device* device, const char* mount_path) {
|
||||
auto* api = USB_MSC_API(device_get_driver(device));
|
||||
return api->eject && api->eject(device, mount_path);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@ -8,6 +8,9 @@
|
||||
#include <tactility/drivers/bluetooth_serial.h>
|
||||
#include <tactility/drivers/bluetooth_midi.h>
|
||||
#include <tactility/drivers/bluetooth_hid_device.h>
|
||||
#include <tactility/drivers/usb_host_hid.h>
|
||||
#include <tactility/drivers/usb_host_midi.h>
|
||||
#include <tactility/drivers/usb_host_msc.h>
|
||||
#include <tactility/drivers/gpio_controller.h>
|
||||
#include <tactility/drivers/i2c_controller.h>
|
||||
#include <tactility/drivers/i2s_controller.h>
|
||||
@ -55,6 +58,7 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = {
|
||||
DEFINE_MODULE_SYMBOL(device_for_each_of_type),
|
||||
DEFINE_MODULE_SYMBOL(device_exists_of_type),
|
||||
DEFINE_MODULE_SYMBOL(device_find_by_name),
|
||||
DEFINE_MODULE_SYMBOL(device_find_first_active_by_type),
|
||||
// driver
|
||||
DEFINE_MODULE_SYMBOL(driver_construct),
|
||||
DEFINE_MODULE_SYMBOL(driver_destruct),
|
||||
@ -168,6 +172,16 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = {
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_hid_device_send_gamepad),
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_hid_device_is_connected),
|
||||
DEFINE_MODULE_SYMBOL(BLUETOOTH_HID_DEVICE_TYPE),
|
||||
// drivers/usb_host_hid
|
||||
DEFINE_MODULE_SYMBOL(usb_host_hid_is_connected),
|
||||
DEFINE_MODULE_SYMBOL(USB_HOST_HID_TYPE),
|
||||
// drivers/usb_host_midi
|
||||
DEFINE_MODULE_SYMBOL(usb_midi_set_callback),
|
||||
DEFINE_MODULE_SYMBOL(usb_midi_is_connected),
|
||||
DEFINE_MODULE_SYMBOL(USB_HOST_MIDI_TYPE),
|
||||
// drivers/usb_host_msc
|
||||
DEFINE_MODULE_SYMBOL(usb_msc_eject),
|
||||
DEFINE_MODULE_SYMBOL(USB_HOST_MSC_TYPE),
|
||||
// concurrent/dispatcher
|
||||
DEFINE_MODULE_SYMBOL(dispatcher_alloc),
|
||||
DEFINE_MODULE_SYMBOL(dispatcher_free),
|
||||
|
||||
13
device.py
13
device.py
@ -350,6 +350,18 @@ def write_bluetooth_variables(output_file, device_properties: ConfigParser):
|
||||
# rapid connect/disconnect/re-pair loop.
|
||||
output_file.write("CONFIG_BT_NIMBLE_NVS_PERSIST=y\n")
|
||||
|
||||
def write_usbhost_variables(output_file, device_properties: ConfigParser):
|
||||
has_usbhost = get_boolean_property_or_false(device_properties, "hardware", "usbHostEnabled")
|
||||
if has_usbhost:
|
||||
output_file.write("# USB Host\n")
|
||||
output_file.write("CONFIG_FATFS_VOLUME_COUNT=6\n")
|
||||
output_file.write("CONFIG_VFS_MAX_COUNT=10\n")
|
||||
output_file.write("CONFIG_USB_HOST_HUBS_SUPPORTED=y\n")
|
||||
output_file.write("CONFIG_USB_HOST_DEBOUNCE_DELAY_MS=500\n")
|
||||
output_file.write("CONFIG_USB_HOST_RESET_HOLD_MS=100\n")
|
||||
output_file.write("CONFIG_USB_HOST_RESET_RECOVERY_MS=100\n")
|
||||
output_file.write("CONFIG_USB_HOST_SET_ADDR_RECOVERY_MS=500\n")
|
||||
|
||||
def write_custom_sdkconfig(output_file, device_properties: ConfigParser):
|
||||
if "sdkconfig" in device_properties.sections():
|
||||
output_file.write("# Custom\n")
|
||||
@ -369,6 +381,7 @@ def write_properties(output_file, device_properties: ConfigParser, device_id: st
|
||||
write_performance_improvements(output_file, device_properties)
|
||||
write_usb_variables(output_file, device_properties)
|
||||
write_bluetooth_variables(output_file, device_properties)
|
||||
write_usbhost_variables(output_file, device_properties)
|
||||
write_custom_sdkconfig(output_file, device_properties)
|
||||
write_lvgl_variables(output_file, device_properties)
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user