Device migrations, drivers and fixes (#571)

Device migrations:

- cyd-4848S040c
- guition-jc1060p470ciwy
- guition-jc2432w328c
- guition-jc3248w535c (known issue with display and touch, Shadowtrance will look into it)
- guition-jc8048w550c
- heltec-wifi-lora-32-v3
- lilygo-tdeck-max (excluding graphics)
- lilygo-tdisplay
- lilygo-tdisplay-s3
- lilygo-tdongle-s3
- lilygo-tlora-pager

Driver migrations:

- Implemented haptic driver interface in kernel
- AXS15231b
- BQ25896
- BQ27220
- CST328
- CST6xx
- DRV2605
- JD9165
- Custom LilyGO driver for T-Lora Pager
- SSD1306
- ST7701
- ST7735
- SY6970
- TCA8418

Fixes/improvements:

- Boot app: support for multiple power devices, improved UI
- lvgl_devices and related code: fixes for mapping
- Support for arrays in dts parser
This commit is contained in:
Ken Van Hoeylandt
2026-07-18 15:01:42 +02:00
committed by GitHub
parent 3b5a401594
commit d896657bf9
290 changed files with 9113 additions and 6719 deletions
@@ -161,7 +161,7 @@ def resolve_parameters_from_bindings(device: Device, bindings: list[Binding], de
node_name = get_device_node_name_safe(device) node_name = get_device_node_name_safe(device)
result = [] result = []
phandle_arrays = [] array_decls = []
for binding_property in binding_properties: for binding_property in binding_properties:
device_property = find_device_property(device, binding_property.name) device_property = find_device_property(device, binding_property.name)
@@ -172,7 +172,35 @@ def resolve_parameters_from_bindings(device: Device, bindings: list[Binding], de
array_var = f"{node_name}_{prop_safe}" array_var = f"{node_name}_{prop_safe}"
if device_property is not None: if device_property is not None:
entries = resolve_phandle_array_entries(device_property, devices) entries = resolve_phandle_array_entries(device_property, devices)
phandle_arrays.append((array_var, binding_property.element_type, entries)) array_decls.append((array_var, binding_property.element_type, entries))
result.append(f"({binding_property.element_type}*){array_var}")
result.append(str(len(entries)))
elif binding_property.default is not None:
result.append("NULL")
result.append("0")
elif binding_property.required:
raise DevicetreeException(f"device {device.node_name} doesn't have property '{binding_property.name}'")
else:
result.append("NULL")
result.append("0")
continue
if binding_property.type == "array":
# A flat literal array (DTS `[ ... ]` syntax, e.g. a byte blob), as opposed to
# phandle-array's list of resolved device references. Emits the same
# (pointer, length) parameter pair, backed by a plain data array instead of one
# holding phandle-derived initializers.
if binding_property.element_type is None:
raise DevicetreeException(f"array property '{binding_property.name}' requires 'element-type' in binding")
prop_safe = binding_property.name.replace("-", "_")
array_var = f"{node_name}_{prop_safe}"
if device_property is not None:
if device_property.type != "array":
raise DevicetreeException(
f"Device '{device.node_name}' property '{binding_property.name}' must use '[ ... ]' array syntax"
)
entries = [str(value) for value in device_property.value]
array_decls.append((array_var, binding_property.element_type, entries))
result.append(f"({binding_property.element_type}*){array_var}") result.append(f"({binding_property.element_type}*){array_var}")
result.append(str(len(entries))) result.append(str(len(entries)))
elif binding_property.default is not None: elif binding_property.default is not None:
@@ -207,17 +235,17 @@ def resolve_parameters_from_bindings(device: Device, bindings: list[Binding], de
validate_property_range(device, binding_property, device_property.value) validate_property_range(device, binding_property, device_property.value)
result.append(property_to_string(device_property, devices)) result.append(property_to_string(device_property, devices))
return result, phandle_arrays return result, array_decls
def write_config(file, device: Device, bindings: list[Binding], devices: list[Device], type_name: str): def write_config(file, device: Device, bindings: list[Binding], devices: list[Device], type_name: str):
node_name = get_device_node_name_safe(device) node_name = get_device_node_name_safe(device)
config_type = f"{type_name}_config_dt" config_type = f"{type_name}_config_dt"
config_variable_name = f"{node_name}_config" config_variable_name = f"{node_name}_config"
config_params, phandle_arrays = resolve_parameters_from_bindings(device, bindings, devices) config_params, array_decls = resolve_parameters_from_bindings(device, bindings, devices)
# Write phandle-array variables before the config struct # Write phandle-array/array variables before the config struct
for array_var, element_type, entries in phandle_arrays: for array_var, element_type, entries in array_decls:
entries_str = ", ".join(entries) entries_str = ", ".join(entries)
file.write(f"static {element_type} {array_var}[] = {{ {entries_str} }};\n") file.write(f"static {element_type} {array_var}[] = {{ {entries_str} }};\n")
@@ -209,6 +209,92 @@ def test_minmax_symbolic_value_skips_validation():
print("PASSED") print("PASSED")
return True return True
def write_array_config(tmp_dir, device_property_line):
config_dir = os.path.join(tmp_dir, "array_data")
bindings_dir = os.path.join(config_dir, "bindings")
os.makedirs(bindings_dir)
with open(os.path.join(config_dir, "devicetree.yaml"), "w") as f:
f.write("dts: test.dts\nbindings: bindings")
with open(os.path.join(config_dir, "test.dts"), "w") as f:
f.write(f"""/dts-v1/;
/ {{
compatible = "test,root";
model = "Test Model";
test-device@0 {{
compatible = "test,array-device";
{device_property_line}
}};
}};
""")
with open(os.path.join(bindings_dir, "test,root.yaml"), "w") as f:
f.write("description: Test root binding\ncompatible: \"test,root\"\nproperties:\n model:\n type: string\n")
with open(os.path.join(bindings_dir, "test,array-device.yaml"), "w") as f:
f.write("""description: Test array binding
compatible: "test,array-device"
properties:
init-sequence:
type: array
element-type: uint8_t
""")
return config_dir
def test_array_property_generates_static_array_and_length():
print("Running test_array_property_generates_static_array_and_length...")
with tempfile.TemporaryDirectory() as tmp_dir:
config_dir = write_array_config(tmp_dir, "init-sequence = [0xFF 0x01 0x00 0x00 0x10 5 0];")
output_dir = os.path.join(tmp_dir, "output")
os.makedirs(output_dir)
result = run_compiler(config_dir, output_dir)
if result.returncode != 0:
print(f"FAILED: Compilation should have succeeded: {result.stderr} {result.stdout}")
return False
with open(os.path.join(output_dir, "devicetree.c")) as f:
generated = f.read()
if "static uint8_t test_device_init_sequence[] = { 0xFF, 0x01, 0x00, 0x00, 0x10, 5, 0 };" not in generated:
print(f"FAILED: Expected static array declaration not found:\n{generated}")
return False
if "(uint8_t*)test_device_init_sequence" not in generated or "\t7\n" not in generated:
print(f"FAILED: Expected (pointer, length) config params not found:\n{generated}")
return False
print("PASSED")
return True
def test_array_property_defaults_to_null_when_absent():
print("Running test_array_property_defaults_to_null_when_absent...")
with tempfile.TemporaryDirectory() as tmp_dir:
config_dir = write_array_config(tmp_dir, "")
output_dir = os.path.join(tmp_dir, "output")
os.makedirs(output_dir)
result = run_compiler(config_dir, output_dir)
if result.returncode != 0:
print(f"FAILED: Compilation should have succeeded: {result.stderr} {result.stdout}")
return False
with open(os.path.join(output_dir, "devicetree.c")) as f:
generated = f.read()
if "NULL,\n\t0" not in generated:
print(f"FAILED: Expected NULL/0 defaults not found:\n{generated}")
return False
print("PASSED")
return True
def test_compile_missing_config(): def test_compile_missing_config():
print("Running test_compile_missing_config...") print("Running test_compile_missing_config...")
with tempfile.TemporaryDirectory() as output_dir: with tempfile.TemporaryDirectory() as output_dir:
@@ -234,7 +320,9 @@ if __name__ == "__main__":
test_minmax_below_minimum_fails, test_minmax_below_minimum_fails,
test_minmax_above_maximum_fails, test_minmax_above_maximum_fails,
test_minmax_out_of_range_default_fails, test_minmax_out_of_range_default_fails,
test_minmax_symbolic_value_skips_validation test_minmax_symbolic_value_skips_validation,
test_array_property_generates_static_array_and_length,
test_array_property_defaults_to_null_when_absent
] ]
failed = 0 failed = 0
+2 -3
View File
@@ -1,7 +1,6 @@
file(GLOB_RECURSE SOURCE_FILES Source/*.c*) file(GLOB_RECURSE SOURCE_FILES source/*.c*)
idf_component_register( idf_component_register(
SRCS ${SOURCE_FILES} SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source" REQUIRES TactilityKernel driver
REQUIRES Tactility EspLcdCompat esp_lcd_st7701 esp_lcd_panel_io_additions GT911 PwmBacklight driver vfs fatfs
) )
@@ -1,21 +0,0 @@
#include "devices/St7701Display.h"
#include <Tactility/hal/Configuration.h>
#include <PwmBacklight.h>
using namespace tt::hal;
static bool initBoot() {
return driver::pwmbacklight::init(GPIO_NUM_38, 1000);
}
static DeviceVector createDevices() {
return {
std::make_shared<St7701Display>(),
};
}
extern const Configuration hardwareConfiguration = {
.initBoot = initBoot,
.createDevices = createDevices
};
@@ -1,260 +0,0 @@
#include "St7701Display.h"
#include <Gt911Touch.h>
#include <PwmBacklight.h>
#include <tactility/check.h>
#include <tactility/device.h>
#include <tactility/log.h>
#include <driver/gpio.h>
#include <esp_err.h>
#include <esp_lcd_panel_rgb.h>
#include <esp_lcd_panel_ops.h>
#include <esp_lcd_panel_io_additions.h>
#include <esp_lcd_st7701.h>
#include <esp_rom_gpio.h>
#include <soc/spi_periph.h>
#include <driver/spi_master.h>
constexpr auto* TAG = "St7701Display";
// GPIO47/48 are physically shared between this bit-banged 3-wire command bus
// and the SD card's real SPI2 bus (no alternate pins exist on this PCB).
// esp_lcd_new_panel_io_3wire_spi() reconfigures them as plain GPIO via
// gpio_config(), severing their SPI2 matrix routing. Reconnect them here once
// the vendor init sequence is done, so SD card reads/writes keep working.
// Safe only because nothing else calls into the ST7701 IO handle after boot.
static void reclaimSpiPinsForSdCard() {
esp_rom_gpio_connect_out_signal(GPIO_NUM_47, spi_periph_signal[SPI2_HOST].spid_out, false, false);
esp_rom_gpio_connect_out_signal(GPIO_NUM_48, spi_periph_signal[SPI2_HOST].spiclk_out, false, false);
gpio_set_direction(GPIO_NUM_47, GPIO_MODE_OUTPUT);
gpio_set_direction(GPIO_NUM_48, GPIO_MODE_OUTPUT);
}
static const st7701_lcd_init_cmd_t st7701_lcd_init_cmds[] = {
// {cmd, { data }, data_size, delay_ms}
{0xFF, (uint8_t[]) {0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0},
{0xC0, (uint8_t[]) {0x3B, 0x00}, 2, 0},
{0xC1, (uint8_t[]) {0x0D, 0x02}, 2, 0},
{0xC2, (uint8_t[]) {0x31, 0x05}, 2, 0},
{0xCD, (uint8_t[]) {0x00}, 1, 0}, //0x08
//Positive Voltage Gamma Control
{0xB0, (uint8_t[]) {0x00, 0x11, 0x18, 0x0E, 0x11, 0x06, 0x07, 0x08, 0x07, 0x22, 0x04, 0x12, 0x0F, 0xAA, 0x31, 0x18}, 16, 0},
//Negative Voltage Gamma Control
{0xB1, (uint8_t[]) {0x00, 0x11, 0x19, 0x0E, 0x12, 0x07, 0x08, 0x08, 0x08, 0x22, 0x04, 0x11, 0x11, 0xA9, 0x32, 0x18}, 16, 0},
//Page1
{0xFF, (uint8_t[]) {0x77, 0x01, 0x00, 0x00, 0x11}, 5, 0},
{0xB0, (uint8_t[]) {0x60}, 1, 0}, //Vop=4.7375v
{0xB1, (uint8_t[]) {0x32}, 1, 0}, //VCOM=32
{0xB2, (uint8_t[]) {0x07}, 1, 0}, //VGH=15v
{0xB3, (uint8_t[]) {0x80}, 1, 0},
{0xB5, (uint8_t[]) {0x49}, 1, 0}, //VGL=-10.17v
{0xB7, (uint8_t[]) {0x85}, 1, 0},
{0xB8, (uint8_t[]) {0x21}, 1, 0}, //AVDD=6.6 & AVCL=-4.6
{0xC1, (uint8_t[]) {0x78}, 1, 0},
{0xC2, (uint8_t[]) {0x78}, 1, 0},
{0xE0, (uint8_t[]) {0x00, 0x1B, 0x02}, 3, 0},
{0xE1, (uint8_t[]) {0x08, 0xA0, 0x00, 0x00, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x44, 0x44}, 11, 0},
{0xE2, (uint8_t[]) {0x11, 0x11, 0x44, 0x44, 0xED, 0xA0, 0x00, 0x00, 0xEC, 0xA0, 0x00, 0x00}, 12, 0},
{0xE3, (uint8_t[]) {0x00, 0x00, 0x11, 0x11}, 4, 0},
{0xE4, (uint8_t[]) {0x44, 0x44}, 2, 0},
{0xE5, (uint8_t[]) {0x0A, 0xE9, 0xD8, 0xA0, 0x0C, 0xEB, 0xD8, 0xA0, 0x0E, 0xED, 0xD8, 0xA0, 0x10, 0xEF, 0xD8, 0xA0}, 16, 0},
{0xE6, (uint8_t[]) {0x00, 0x00, 0x11, 0x11}, 4, 0},
{0xE7, (uint8_t[]) {0x44, 0x44}, 2, 0},
{0xE8, (uint8_t[]) {0x09, 0xE8, 0xD8, 0xA0, 0x0B, 0xEA, 0xD8, 0xA0, 0x0D, 0xEC, 0xD8, 0xA0, 0x0F, 0xEE, 0xD8, 0xA0}, 16, 0},
{0xEB, (uint8_t[]) {0x02, 0x00, 0xE4, 0xE4, 0x88, 0x00, 0x40}, 7, 0},
{0xEC, (uint8_t[]) {0x3C, 0x00}, 2, 0},
{0xED, (uint8_t[]) {0xAB, 0x89, 0x76, 0x54, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x45, 0x67, 0x98, 0xBA}, 16, 0},
//VAP & VAN
{0xFF, (uint8_t[]) {0x77, 0x01, 0x00, 0x00, 0x13}, 5, 0},
{0xE5, (uint8_t[]) {0xE4}, 1, 0},
{0xFF, (uint8_t[]) {0x77, 0x01, 0x00, 0x00, 0x00}, 5, 0},
{0x3A, (uint8_t[]) {0x60}, 1, 10}, //0x70 RGB888, 0x60 RGB666, 0x50 RGB565
{0x11, (uint8_t[]) {0x00}, 0, 120}, //Sleep Out
{0x29, (uint8_t[]) {0x00}, 0, 0}, //Display On
};
bool St7701Display::createIoHandle(esp_lcd_panel_io_handle_t& outHandle) {
spi_line_config_t line_config = {
.cs_io_type = IO_TYPE_GPIO,
.cs_gpio_num = GPIO_NUM_39,
.scl_io_type = IO_TYPE_GPIO,
.scl_gpio_num = GPIO_NUM_48,
.sda_io_type = IO_TYPE_GPIO,
.sda_gpio_num = GPIO_NUM_47,
.io_expander = nullptr,
};
esp_lcd_panel_io_3wire_spi_config_t panel_io_config = ST7701_PANEL_IO_3WIRE_SPI_CONFIG(line_config, 0);
return esp_lcd_new_panel_io_3wire_spi(&panel_io_config, &outHandle) == ESP_OK;
}
bool St7701Display::createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t& panelHandle) {
const esp_lcd_rgb_panel_config_t rgb_config = {
.clk_src = LCD_CLK_SRC_DEFAULT,
.timings = {
.pclk_hz = 14000000,
.h_res = 480,
.v_res = 480,
.hsync_pulse_width = 10,
.hsync_back_porch = 10,
.hsync_front_porch = 20,
.vsync_pulse_width = 10,
.vsync_back_porch = 10,
.vsync_front_porch = 10,
.flags = {
.hsync_idle_low = false,
.vsync_idle_low = false,
.de_idle_high = false,
.pclk_active_neg = false,
.pclk_idle_high = false
}
},
.data_width = 16,
.bits_per_pixel = 16,
.num_fbs = 2,
.bounce_buffer_size_px = 480 * 10,
.sram_trans_align = 8,
.psram_trans_align = 64,
.hsync_gpio_num = GPIO_NUM_16,
.vsync_gpio_num = GPIO_NUM_17,
.de_gpio_num = GPIO_NUM_18,
.pclk_gpio_num = GPIO_NUM_21,
.disp_gpio_num = GPIO_NUM_NC,
.data_gpio_nums = {
GPIO_NUM_4, // B1
GPIO_NUM_5, // B2
GPIO_NUM_6, // B3
GPIO_NUM_7, // B4
GPIO_NUM_15, // B5
GPIO_NUM_8, // G1
GPIO_NUM_20, // G2
GPIO_NUM_3, // G3
GPIO_NUM_46, // G4
GPIO_NUM_9, // G5
GPIO_NUM_10, // G6
GPIO_NUM_11, // R1
GPIO_NUM_12, // R2
GPIO_NUM_13, // R3
GPIO_NUM_14, // R4
GPIO_NUM_0 // R5
},
.flags = {
.disp_active_low = false,
.refresh_on_demand = false,
.fb_in_psram = true,
.double_fb = true,
.no_fb = false,
.bb_invalidate_cache = true
}
};
st7701_vendor_config_t vendor_config = {
.init_cmds = st7701_lcd_init_cmds,
.init_cmds_size = sizeof(st7701_lcd_init_cmds) / sizeof(st7701_lcd_init_cmd_t),
.rgb_config = &rgb_config,
.flags = {
.use_mipi_interface = 0,
.mirror_by_cmd = 1,
.auto_del_panel_io = 0,
},
};
const esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = GPIO_NUM_NC,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
.data_endian = LCD_RGB_DATA_ENDIAN_LITTLE,
.bits_per_pixel = 16,
.flags = {
.reset_active_high = false,
},
.vendor_config = &vendor_config,
};
if (esp_lcd_new_panel_st7701(ioHandle, &panel_config, &panelHandle) != ESP_OK) {
LOG_E(TAG, "Failed to create panel");
return false;
}
if (esp_lcd_panel_reset(panelHandle) != ESP_OK) {
LOG_E(TAG, "Failed to reset panel");
return false;
}
if (esp_lcd_panel_init(panelHandle) != ESP_OK) {
LOG_E(TAG, "Failed to init panel");
return false;
}
if (esp_lcd_panel_invert_color(panelHandle, false) != ESP_OK) {
LOG_E(TAG, "Failed to invert color");
return false;
}
esp_lcd_panel_set_gap(panelHandle, 0, 0);
if (esp_lcd_panel_disp_on_off(panelHandle, true) != ESP_OK) {
LOG_E(TAG, "Failed to turn display on");
return false;
}
reclaimSpiPinsForSdCard();
return true;
}
lvgl_port_display_cfg_t St7701Display::getLvglPortDisplayConfig(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t panelHandle) {
return {
.io_handle = ioHandle,
.panel_handle = panelHandle,
.control_handle = nullptr,
.buffer_size = (480 * 480),
.double_buffer = true,
.trans_size = 0,
.hres = 480,
.vres = 480,
.monochrome = false,
.rotation = {
.swap_xy = false,
.mirror_x = false,
.mirror_y = false,
},
.color_format = LV_COLOR_FORMAT_RGB565,
.flags = {
.buff_dma = false,
.buff_spiram = true,
.sw_rotate = false,
.swap_bytes = false,
.full_refresh = false,
.direct_mode = false
}
};
}
lvgl_port_display_rgb_cfg_t St7701Display::getLvglPortDisplayRgbConfig(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t panelHandle) {
return {
.flags = {
.bb_mode = true,
.avoid_tearing = false
}
};
}
std::shared_ptr<tt::hal::touch::TouchDevice> St7701Display::getTouchDevice() {
if (touchDevice == nullptr) {
auto* i2c = device_find_by_name("i2c0");
check(i2c);
auto configuration = std::make_unique<Gt911Touch::Configuration>(
i2c,
480,
480
);
touchDevice = std::make_shared<Gt911Touch>(std::move(configuration));
}
return touchDevice;
}
void St7701Display::setBacklightDuty(uint8_t backlightDuty) {
driver::pwmbacklight::setBacklightDuty(backlightDuty);
}
@@ -1,36 +0,0 @@
#pragma once
#include <EspLcdDisplay.h>
#include <lvgl.h>
class St7701Display final : public EspLcdDisplay {
std::shared_ptr<tt::hal::touch::TouchDevice> touchDevice;
bool createIoHandle(esp_lcd_panel_io_handle_t& outHandle) override;
bool createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t& panelHandle) override;
lvgl_port_display_cfg_t getLvglPortDisplayConfig(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t panelHandle) override;
bool isRgbPanel() const override { return true; }
lvgl_port_display_rgb_cfg_t getLvglPortDisplayRgbConfig(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t panelHandle) override;
public:
St7701Display() : EspLcdDisplay() {}
std::string getName() const override { return "ST7701S"; }
std::string getDescription() const override { return "ST7701S RGB display"; }
std::shared_ptr<tt::hal::touch::TouchDevice> getTouchDevice() override;
void setBacklightDuty(uint8_t backlightDuty) override;
bool supportsBacklightDuty() const override { return true; }
// TODO: Find out why it crashes
bool supportsDisplayDriver() const override { return false; }
};
+137 -1
View File
@@ -5,8 +5,12 @@
#include <tactility/bindings/esp32_wifi_pinned.h> #include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h>
#include <tactility/bindings/esp32_sdspi.h> #include <tactility/bindings/esp32_sdspi.h>
#include <tactility/bindings/esp32_spi.h>
#include <tactility/bindings/esp32_pwm_ledc.h>
#include <tactility/bindings/pwm_backlight.h>
#include <bindings/st7701.h>
#include <bindings/gt911.h>
/ { / {
compatible = "root"; compatible = "root";
@@ -33,5 +37,137 @@
clock-frequency = <400000>; clock-frequency = <400000>;
pin-sda = <&gpio0 19 GPIO_FLAG_NONE>; pin-sda = <&gpio0 19 GPIO_FLAG_NONE>;
pin-scl = <&gpio0 45 GPIO_FLAG_NONE>; pin-scl = <&gpio0 45 GPIO_FLAG_NONE>;
touch0 {
// No reset/interrupt pin wired up here, matching the original deprecated-HAL
// config (Gt911Touch::Configuration was constructed with no reset/interrupt pin
// arguments on this board).
compatible = "goodix,gt911";
reg = <0x5D>;
x-max = <480>;
y-max = <480>;
};
};
display_backlight_pwm {
compatible = "espressif,esp32-pwm-ledc";
pin = <&gpio0 38 GPIO_FLAG_NONE>;
period-ns = <1000000>;
ledc-timer = <0>;
ledc-channel = <0>;
};
display_backlight {
compatible = "pwm-backlight";
pwm = <&display_backlight_pwm>;
};
display0 {
compatible = "sitronix,st7701";
horizontal-resolution = <480>;
vertical-resolution = <480>;
pixel-clock-hz = <14000000>;
hsync-pulse-width = <10>;
hsync-back-porch = <10>;
hsync-front-porch = <20>;
vsync-pulse-width = <10>;
vsync-back-porch = <10>;
vsync-front-porch = <10>;
num-fbs = <2>;
double-fb;
bounce-buffer-size-px = <4800>;
bb-invalidate-cache;
pin-hsync = <&gpio0 16 GPIO_FLAG_NONE>;
pin-vsync = <&gpio0 17 GPIO_FLAG_NONE>;
pin-de = <&gpio0 18 GPIO_FLAG_NONE>;
pin-pclk = <&gpio0 21 GPIO_FLAG_NONE>;
pin-data0 = <&gpio0 4 GPIO_FLAG_NONE>; // B1
pin-data1 = <&gpio0 5 GPIO_FLAG_NONE>; // B2
pin-data2 = <&gpio0 6 GPIO_FLAG_NONE>; // B3
pin-data3 = <&gpio0 7 GPIO_FLAG_NONE>; // B4
pin-data4 = <&gpio0 15 GPIO_FLAG_NONE>; // B5
pin-data5 = <&gpio0 8 GPIO_FLAG_NONE>; // G1
pin-data6 = <&gpio0 20 GPIO_FLAG_NONE>; // G2
pin-data7 = <&gpio0 3 GPIO_FLAG_NONE>; // G3
pin-data8 = <&gpio0 46 GPIO_FLAG_NONE>; // G4
pin-data9 = <&gpio0 9 GPIO_FLAG_NONE>; // G5
pin-data10 = <&gpio0 10 GPIO_FLAG_NONE>; // G6
pin-data11 = <&gpio0 11 GPIO_FLAG_NONE>; // R1
pin-data12 = <&gpio0 12 GPIO_FLAG_NONE>; // R2
pin-data13 = <&gpio0 13 GPIO_FLAG_NONE>; // R3
pin-data14 = <&gpio0 14 GPIO_FLAG_NONE>; // R4
pin-data15 = <&gpio0 0 GPIO_FLAG_NONE>; // R5
// 3-wire bit-banged command bus. GPIO47/48 are physically shared with this board's SD
// card SPI2 bus (MOSI/SCLK) - no alternate pins exist on this PCB. No SD card device is
// declared in this devicetree yet (CS/MISO pins are unverified), but if one is added on
// SPI2_HOST later, declare it *after* this node: starting that SPI bus unconditionally
// reprograms the GPIO matrix for its MOSI/SCLK pins (see esp32_spi.cpp's start()),
// which reclaims them from this display's bit-banged use without any extra code.
pin-cs = <&gpio0 39 GPIO_FLAG_NONE>;
pin-scl = <&gpio0 48 GPIO_FLAG_NONE>;
pin-sda = <&gpio0 47 GPIO_FLAG_NONE>;
mirror-by-cmd;
init-sequence = [
// Vendor bring-up sequence for this panel. Framing is
// [cmd, data-len, delay-ms, data-len bytes of data...] - see st7701-module's
// init-sequence binding property for the encoding.
0xFF 5 0 0x77 0x01 0x00 0x00 0x10
0xC0 2 0 0x3B 0x00
0xC1 2 0 0x0D 0x02
0xC2 2 0 0x31 0x05
0xCD 1 0 0x00
// Positive Voltage Gamma Control
0xB0 16 0 0x00 0x11 0x18 0x0E 0x11 0x06 0x07 0x08 0x07 0x22 0x04 0x12 0x0F 0xAA 0x31 0x18
// Negative Voltage Gamma Control
0xB1 16 0 0x00 0x11 0x19 0x0E 0x12 0x07 0x08 0x08 0x08 0x22 0x04 0x11 0x11 0xA9 0x32 0x18
// Page1
0xFF 5 0 0x77 0x01 0x00 0x00 0x11
0xB0 1 0 0x60 // Vop=4.7375v
0xB1 1 0 0x32 // VCOM=32
0xB2 1 0 0x07 // VGH=15v
0xB3 1 0 0x80
0xB5 1 0 0x49 // VGL=-10.17v
0xB7 1 0 0x85
0xB8 1 0 0x21 // AVDD=6.6 & AVCL=-4.6
0xC1 1 0 0x78
0xC2 1 0 0x78
0xE0 3 0 0x00 0x1B 0x02
0xE1 11 0 0x08 0xA0 0x00 0x00 0x07 0xA0 0x00 0x00 0x00 0x44 0x44
0xE2 12 0 0x11 0x11 0x44 0x44 0xED 0xA0 0x00 0x00 0xEC 0xA0 0x00 0x00
0xE3 4 0 0x00 0x00 0x11 0x11
0xE4 2 0 0x44 0x44
0xE5 16 0 0x0A 0xE9 0xD8 0xA0 0x0C 0xEB 0xD8 0xA0 0x0E 0xED 0xD8 0xA0 0x10 0xEF 0xD8 0xA0
0xE6 4 0 0x00 0x00 0x11 0x11
0xE7 2 0 0x44 0x44
0xE8 16 0 0x09 0xE8 0xD8 0xA0 0x0B 0xEA 0xD8 0xA0 0x0D 0xEC 0xD8 0xA0 0x0F 0xEE 0xD8 0xA0
0xEB 7 0 0x02 0x00 0xE4 0xE4 0x88 0x00 0x40
0xEC 2 0 0x3C 0x00
0xED 16 0 0xAB 0x89 0x76 0x54 0x02 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0x20 0x45 0x67 0x98 0xBA
// VAP & VAN
0xFF 5 0 0x77 0x01 0x00 0x00 0x13
0xE5 1 0 0xE4
0xFF 5 0 0x77 0x01 0x00 0x00 0x00
0x3A 1 10 0x60 // 0x70 RGB888, 0x60 RGB666, 0x50 RGB565
0x11 0 120 // Sleep Out
0x29 0 0 // Display On
];
backlight = <&display_backlight>;
};
spi0 {
compatible = "espressif,esp32-spi";
host = <SPI2_HOST>;
cs-gpios = <&gpio0 42 GPIO_FLAG_NONE>;
pin-mosi = <&gpio0 47 GPIO_FLAG_NONE>;
pin-miso = <&gpio0 41 GPIO_FLAG_NONE>;
pin-sclk = <&gpio0 48 GPIO_FLAG_NONE>;
sdcard@0 {
compatible = "espressif,esp32-sdspi";
frequency-khz = <20000>;
};
}; };
}; };
+2
View File
@@ -10,6 +10,8 @@ hardware.spiRamMode=OCT
hardware.spiRamSpeed=80M hardware.spiRamSpeed=80M
hardware.bluetooth=true hardware.bluetooth=true
dependencies.useDeprecatedHal=false
storage.userDataLocation=SD storage.userDataLocation=SD
display.size=4" display.size=4"
+2
View File
@@ -1,3 +1,5 @@
dependencies: dependencies:
- Platforms/platform-esp32 - Platforms/platform-esp32
- Drivers/st7701-module
- Drivers/gt911-module
dts: cyd,4848s040c.dts dts: cyd,4848s040c.dts
@@ -3,16 +3,14 @@
extern "C" { extern "C" {
static error_t start() { static error_t start() {
// Empty for now
return ERROR_NONE; return ERROR_NONE;
} }
static error_t stop() { static error_t stop() {
// Empty for now
return ERROR_NONE; return ERROR_NONE;
} }
Module cyd_4848s040c_module = { struct Module cyd_4848s040c_module = {
.name = "cyd-4848s040c", .name = "cyd-4848s040c",
.start = start, .start = start,
.stop = stop, .stop = stop,
@@ -1,8 +1,6 @@
file(GLOB_RECURSE SOURCE_FILES Source/*.c*) file(GLOB_RECURSE SOURCE_FILES source/*.c*)
idf_component_register( idf_component_register(
SRCS ${SOURCE_FILES} SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source" REQUIRES TactilityKernel
REQUIRES Tactility esp_lvgl_port esp_lcd EspLcdCompat esp_lcd_jd9165 GT911 PwmBacklight driver vfs fatfs
PRIV_REQUIRES esp_adc EstimatedPower
) )
@@ -1,17 +0,0 @@
#include "devices/Display.h"
#include "devices/Power.h"
#include <Tactility/hal/Configuration.h>
using namespace tt::hal;
static DeviceVector createDevices() {
return {
createDisplay(),
createPower()
};
}
extern const Configuration hardwareConfiguration = {
.createDevices = createDevices,
};
@@ -1,66 +0,0 @@
#include "Display.h"
#include "Jd9165Display.h"
#include <Gt911Touch.h>
#include <PwmBacklight.h>
#include <Tactility/Mutex.h>
#include <tactility/check.h>
#include <tactility/device.h>
#include <tactility/log.h>
constexpr auto LCD_PIN_RESET = GPIO_NUM_0; // Match P4 EV board reset line
constexpr auto LCD_PIN_BACKLIGHT = GPIO_NUM_23;
constexpr auto LCD_HORIZONTAL_RESOLUTION = 1024;
constexpr auto LCD_VERTICAL_RESOLUTION = 600;
constexpr auto TOUCH_PIN_RESET = GPIO_NUM_NC;
constexpr auto TOUCH_PIN_INTERRUPT = GPIO_NUM_NC;
static std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
auto* i2c = device_find_by_name("i2c_internal");
check(i2c);
auto configuration = std::make_unique<Gt911Touch::Configuration>(
i2c,
LCD_HORIZONTAL_RESOLUTION,
LCD_VERTICAL_RESOLUTION,
false, // swapXY
false, // mirrorX
false, // mirrorY
TOUCH_PIN_RESET,
TOUCH_PIN_INTERRUPT
);
return std::make_shared<Gt911Touch>(std::move(configuration));
}
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
// Initialize PWM backlight
if (!driver::pwmbacklight::init(LCD_PIN_BACKLIGHT, 20000, LEDC_TIMER_1, LEDC_CHANNEL_0)) {
LOG_W("jc1060p470ciwy", "Failed to initialize backlight");
}
auto touch = createTouch();
auto configuration = std::make_shared<EspLcdConfiguration>(EspLcdConfiguration {
.horizontalResolution = LCD_HORIZONTAL_RESOLUTION,
.verticalResolution = LCD_VERTICAL_RESOLUTION,
.gapX = 0,
.gapY = 0,
.monochrome = false,
.swapXY = false,
.mirrorX = false,
.mirrorY = false,
.invertColor = false,
.bufferSize = 0, // 0 = default (1/10 of screen)
.touch = touch,
.backlightDutyFunction = driver::pwmbacklight::setBacklightDuty,
.resetPin = LCD_PIN_RESET,
.lvglColorFormat = LV_COLOR_FORMAT_RGB565,
.lvglSwapBytes = false,
.rgbElementOrder = LCD_RGB_ELEMENT_ORDER_RGB,
.bitsPerPixel = 16
});
const auto display = std::make_shared<Jd9165Display>(configuration);
return std::static_pointer_cast<tt::hal::display::DisplayDevice>(display);
}
@@ -1,203 +0,0 @@
#include "Jd9165Display.h"
#include <tactility/log.h>
#include <esp_lcd_jd9165.h>
constexpr auto* TAG = "JD9165";
// MIPI DSI PHY power configuration
#define MIPI_DSI_PHY_PWR_LDO_CHAN 3 // LDO_VO3 connects to VDD_MIPI_DPHY
#define MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV 2500
// JD9165 initialization commands from ESP32-P4 Function EV Board
// Delays set to match the reference sequence exactly.
static const jd9165_lcd_init_cmd_t jd9165_init_cmds[] = {
{0x30, (uint8_t[]){0x00}, 1, 0},
{0xF7, (uint8_t[]){0x49,0x61,0x02,0x00}, 4, 0},
{0x30, (uint8_t[]){0x01}, 1, 0},
{0x04, (uint8_t[]){0x0C}, 1, 0},
{0x05, (uint8_t[]){0x00}, 1, 0},
{0x06, (uint8_t[]){0x00}, 1, 0},
{0x0B, (uint8_t[]){0x11}, 1, 0},
{0x17, (uint8_t[]){0x00}, 1, 0},
{0x20, (uint8_t[]){0x04}, 1, 0},
{0x1F, (uint8_t[]){0x05}, 1, 0},
{0x23, (uint8_t[]){0x00}, 1, 0},
{0x25, (uint8_t[]){0x19}, 1, 0},
{0x28, (uint8_t[]){0x18}, 1, 0},
{0x29, (uint8_t[]){0x04}, 1, 0},
{0x2A, (uint8_t[]){0x01}, 1, 0},
{0x2B, (uint8_t[]){0x04}, 1, 0},
{0x2C, (uint8_t[]){0x01}, 1, 0},
{0x30, (uint8_t[]){0x02}, 1, 0},
{0x01, (uint8_t[]){0x22}, 1, 0},
{0x03, (uint8_t[]){0x12}, 1, 0},
{0x04, (uint8_t[]){0x00}, 1, 0},
{0x05, (uint8_t[]){0x64}, 1, 0},
{0x0A, (uint8_t[]){0x08}, 1, 0},
{0x0B, (uint8_t[]){0x0A,0x1A,0x0B,0x0D,0x0D,0x11,0x10,0x06,0x08,0x1F,0x1D}, 11, 0},
{0x0C, (uint8_t[]){0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D}, 11, 0},
{0x0D, (uint8_t[]){0x16,0x1B,0x0B,0x0D,0x0D,0x11,0x10,0x07,0x09,0x1E,0x1C}, 11, 0},
{0x0E, (uint8_t[]){0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D}, 11, 0},
{0x0F, (uint8_t[]){0x16,0x1B,0x0D,0x0B,0x0D,0x11,0x10,0x1C,0x1E,0x09,0x07}, 11, 0},
{0x10, (uint8_t[]){0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D}, 11, 0},
{0x11, (uint8_t[]){0x0A,0x1A,0x0D,0x0B,0x0D,0x11,0x10,0x1D,0x1F,0x08,0x06}, 11, 0},
{0x12, (uint8_t[]){0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D}, 11, 0},
{0x14, (uint8_t[]){0x00,0x00,0x11,0x11}, 4, 0},
{0x18, (uint8_t[]){0x99}, 1, 0},
{0x30, (uint8_t[]){0x06}, 1, 0},
{0x12, (uint8_t[]){0x36,0x2C,0x2E,0x3C,0x38,0x35,0x35,0x32,0x2E,0x1D,0x2B,0x21,0x16,0x29}, 14, 0},
{0x13, (uint8_t[]){0x36,0x2C,0x2E,0x3C,0x38,0x35,0x35,0x32,0x2E,0x1D,0x2B,0x21,0x16,0x29}, 14, 0},
{0x30, (uint8_t[]){0x0A}, 1, 0},
{0x02, (uint8_t[]){0x4F}, 1, 0},
{0x0B, (uint8_t[]){0x40}, 1, 0},
{0x12, (uint8_t[]){0x3E}, 1, 0},
{0x13, (uint8_t[]){0x78}, 1, 0},
{0x30, (uint8_t[]){0x0D}, 1, 0},
{0x0D, (uint8_t[]){0x04}, 1, 0},
{0x10, (uint8_t[]){0x0C}, 1, 0},
{0x11, (uint8_t[]){0x0C}, 1, 0},
{0x12, (uint8_t[]){0x0C}, 1, 0},
{0x13, (uint8_t[]){0x0C}, 1, 0},
{0x30, (uint8_t[]){0x00}, 1, 0},
{0X3A, (uint8_t[]){0x55}, 1, 0},
{0x11, (uint8_t[]){0x00}, 1, 120},
{0x29, (uint8_t[]){0x00}, 1, 20},
};
Jd9165Display::~Jd9165Display() {
// TODO: This should happen during ::stop(), but this isn't currently exposed
if (mipiDsiBus != nullptr) {
esp_lcd_del_dsi_bus(mipiDsiBus);
mipiDsiBus = nullptr;
}
if (ldoChannel != nullptr) {
esp_ldo_release_channel(ldoChannel);
ldoChannel = nullptr;
}
}
bool Jd9165Display::createMipiDsiBus() {
// Enable MIPI DSI PHY power (transition from "no power" to "shutdown" state)
esp_ldo_channel_config_t ldo_mipi_phy_config = {
.chan_id = MIPI_DSI_PHY_PWR_LDO_CHAN,
.voltage_mv = MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV,
.flags = {}
};
if (esp_ldo_acquire_channel(&ldo_mipi_phy_config, &ldoChannel) != ESP_OK) {
LOG_E(TAG, "Failed to acquire LDO channel for MIPI DSI PHY");
return false;
}
LOG_I(TAG, "MIPI DSI PHY powered on");
// Create MIPI DSI bus
// TODO: use MIPI_DSI_PHY_CLK_SRC_DEFAULT() in future ESP-IDF 6.0.0 update with esp_lcd_jd9165 library version 2.x
const esp_lcd_dsi_bus_config_t bus_config = {
.bus_id = 0,
.num_data_lanes = 2,
.phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT,
.lane_bit_rate_mbps = 750
};
if (esp_lcd_new_dsi_bus(&bus_config, &mipiDsiBus) != ESP_OK) {
LOG_E(TAG, "Failed to create MIPI DSI bus");
return false;
}
LOG_I(TAG, "MIPI DSI bus created");
return true;
}
bool Jd9165Display::createIoHandle(esp_lcd_panel_io_handle_t& ioHandle) {
// Initialize MIPI DSI bus if not already done
if (mipiDsiBus == nullptr) {
if (!createMipiDsiBus()) {
return false;
}
}
// Use DBI interface to send LCD commands and parameters
esp_lcd_dbi_io_config_t dbi_config = JD9165_PANEL_IO_DBI_CONFIG();
if (esp_lcd_new_panel_io_dbi(mipiDsiBus, &dbi_config, &ioHandle) != ESP_OK) {
LOG_E(TAG, "Failed to create panel IO");
return false;
}
return true;
}
esp_lcd_panel_dev_config_t Jd9165Display::createPanelConfig(std::shared_ptr<EspLcdConfiguration> espLcdConfiguration, gpio_num_t resetPin) {
return {
.reset_gpio_num = resetPin,
.rgb_ele_order = espLcdConfiguration->rgbElementOrder,
.data_endian = LCD_RGB_DATA_ENDIAN_LITTLE,
.bits_per_pixel = static_cast<uint8_t>(espLcdConfiguration->bitsPerPixel),
.flags = {
.reset_active_high = 0
},
.vendor_config = nullptr // Will be set in createPanelHandle
};
}
bool Jd9165Display::createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_panel_dev_config_t& panelConfig, esp_lcd_panel_handle_t& panelHandle) {
// Create DPI panel configuration
// Override default timings
const esp_lcd_dpi_panel_config_t dpi_config = {
.virtual_channel = 0,
.dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT,
.dpi_clock_freq_mhz = 50,
.pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565,
.in_color_format = LCD_COLOR_FMT_RGB565,
.out_color_format = LCD_COLOR_FMT_RGB565,
.num_fbs = 1,
.video_timing = {
.h_size = 1024,
.v_size = 600,
.hsync_pulse_width = 20,
.hsync_back_porch = 160,
.hsync_front_porch = 160,
.vsync_pulse_width = 2,
.vsync_back_porch = 21,
.vsync_front_porch = 12,
},
.flags = {
.use_dma2d = 1,
.disable_lp = 0
}
};
jd9165_vendor_config_t vendor_config = {
.init_cmds = jd9165_init_cmds,
.init_cmds_size = sizeof(jd9165_init_cmds) / sizeof(jd9165_lcd_init_cmd_t),
.mipi_config = {
.dsi_bus = mipiDsiBus,
.dpi_config = &dpi_config,
},
};
// Create a mutable copy of panelConfig to set vendor_config
esp_lcd_panel_dev_config_t mutable_panel_config = panelConfig;
mutable_panel_config.vendor_config = &vendor_config;
if (esp_lcd_new_panel_jd9165(ioHandle, &mutable_panel_config, &panelHandle) != ESP_OK) {
LOG_E(TAG, "Failed to create panel");
return false;
}
LOG_I(TAG, "JD9165 panel created successfully");
// Defer reset/init to base class applyConfiguration to avoid double initialization
return true;
}
lvgl_port_display_dsi_cfg_t Jd9165Display::getLvglPortDisplayDsiConfig(esp_lcd_panel_io_handle_t /*ioHandle*/, esp_lcd_panel_handle_t /*panelHandle*/) {
// Disable avoid_tearing to prevent stalls/blank flashes when other tasks (e.g. flash writes) block timing
return lvgl_port_display_dsi_cfg_t{
.flags = {
.avoid_tearing = 0,
},
};
}
@@ -1,39 +0,0 @@
#pragma once
#include <EspLcdDisplayV2.h>
#include <Tactility/RecursiveMutex.h>
#include <esp_lcd_mipi_dsi.h>
#include <esp_ldo_regulator.h>
class Jd9165Display final : public EspLcdDisplayV2 {
esp_lcd_dsi_bus_handle_t mipiDsiBus = nullptr;
esp_ldo_channel_handle_t ldoChannel = nullptr;
bool createMipiDsiBus();
protected:
bool createIoHandle(esp_lcd_panel_io_handle_t& ioHandle) override;
esp_lcd_panel_dev_config_t createPanelConfig(std::shared_ptr<EspLcdConfiguration> espLcdConfiguration, gpio_num_t resetPin) override;
bool createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_panel_dev_config_t& panelConfig, esp_lcd_panel_handle_t& panelHandle) override;
bool useDsiPanel() const override { return true; }
lvgl_port_display_dsi_cfg_t getLvglPortDisplayDsiConfig(esp_lcd_panel_io_handle_t /*ioHandle*/, esp_lcd_panel_handle_t /*panelHandle*/) override;
public:
Jd9165Display(
const std::shared_ptr<EspLcdConfiguration>& configuration
) : EspLcdDisplayV2(configuration) {}
~Jd9165Display() override;
std::string getName() const override { return "JD9165"; }
std::string getDescription() const override { return "JD9165 MIPI-DSI 1024x600 display"; }
};
@@ -1,180 +0,0 @@
#include "Power.h"
#include <ChargeFromVoltage.h>
#include <tactility/log.h>
#include <esp_adc/adc_oneshot.h>
#include <esp_adc/adc_cali.h>
#include <esp_adc/adc_cali_scheme.h>
using tt::hal::power::PowerDevice;
constexpr auto* TAG = "JcPower";
namespace {
constexpr adc_unit_t ADC_UNIT = ADC_UNIT_2;
constexpr adc_channel_t ADC_CHANNEL = ADC_CHANNEL_4; // matches ADC2 CH4 used in brookesia config
constexpr adc_atten_t ADC_ATTEN = ADC_ATTEN_DB_12;
constexpr int32_t UPPER_RESISTOR_OHM = 85'000; // per brookesia settings
constexpr int32_t LOWER_RESISTOR_OHM = 100'000; // per brookesia settings
class JcPower final : public PowerDevice {
public:
JcPower() : chargeEstimator(3.3f, 4.2f) {}
~JcPower() override { deinit(); }
std::string getName() const override { return "JC Power"; }
std::string getDescription() const override { return "Battery voltage via ADC"; }
bool supportsMetric(MetricType type) const override {
switch (type) {
using enum MetricType;
case BatteryVoltage:
case ChargeLevel:
return true;
default:
return false;
}
}
bool getMetric(MetricType type, MetricData& data) override {
if (!ensureInit()) {
return false;
}
uint32_t batteryMv = 0;
if (!readBatteryMilliVolt(batteryMv)) {
return false;
}
switch (type) {
case MetricType::BatteryVoltage:
data.valueAsUint32 = batteryMv;
return true;
case MetricType::ChargeLevel:
data.valueAsUint8 = chargeEstimator.estimateCharge(batteryMv);
return true;
default:
return false;
}
}
private:
bool ensureInit() {
if (initialized) {
return true;
}
adc_oneshot_unit_init_cfg_t init_cfg = {
.unit_id = ADC_UNIT,
.clk_src = ADC_RTC_CLK_SRC_DEFAULT,
.ulp_mode = ADC_ULP_MODE_DISABLE,
};
if (adc_oneshot_new_unit(&init_cfg, &adcHandle) != ESP_OK) {
LOG_E(TAG, "ADC unit init failed");
return false;
}
adc_oneshot_chan_cfg_t chan_cfg = {
.atten = ADC_ATTEN,
.bitwidth = ADC_BITWIDTH_DEFAULT,
};
if (adc_oneshot_config_channel(adcHandle, ADC_CHANNEL, &chan_cfg) != ESP_OK) {
LOG_E(TAG, "ADC channel config failed");
adc_oneshot_del_unit(adcHandle);
adcHandle = nullptr;
return false;
}
calibrated = tryInitCalibration();
initialized = true;
return true;
}
bool tryInitCalibration() {
#if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED
adc_cali_line_fitting_config_t cali_config = {
.unit_id = ADC_UNIT,
.atten = ADC_ATTEN,
.bitwidth = ADC_BITWIDTH_DEFAULT,
};
if (adc_cali_create_scheme_line_fitting(&cali_config, &caliHandle) == ESP_OK) {
calScheme = CaliScheme::Line;
LOG_I(TAG, "ADC calibration (line fitting) enabled");
return true;
}
#endif
#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
adc_cali_curve_fitting_config_t curve_cfg = {
.unit_id = ADC_UNIT,
.chan = ADC_CHANNEL,
.atten = ADC_ATTEN,
.bitwidth = ADC_BITWIDTH_DEFAULT,
};
if (adc_cali_create_scheme_curve_fitting(&curve_cfg, &caliHandle) == ESP_OK) {
calScheme = CaliScheme::Curve;
LOG_I(TAG, "ADC calibration (curve fitting) enabled");
return true;
}
#endif
LOG_W(TAG, "ADC calibration not available, using raw scaling");
return false;
}
bool readBatteryMilliVolt(uint32_t& outMv) {
int raw = 0;
if (adc_oneshot_read(adcHandle, ADC_CHANNEL, &raw) != ESP_OK) {
LOG_E(TAG, "ADC read failed");
return false;
}
int mv = 0;
if (calibrated && adc_cali_raw_to_voltage(caliHandle, raw, &mv) == ESP_OK) {
// ok
} else {
// Fallback: approximate assuming 12-bit full scale 3.3V
mv = (raw * 3300) / 4095;
}
const int64_t numerator = static_cast<int64_t>(UPPER_RESISTOR_OHM + LOWER_RESISTOR_OHM) * mv;
const int64_t denominator = LOWER_RESISTOR_OHM;
outMv = static_cast<uint32_t>(numerator / denominator);
return true;
}
void deinit() {
if (adcHandle) {
adc_oneshot_del_unit(adcHandle);
adcHandle = nullptr;
}
if (caliHandle) {
if (calScheme == CaliScheme::Line) {
#if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED
adc_cali_delete_scheme_line_fitting(caliHandle);
#endif
#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
} else if (calScheme == CaliScheme::Curve) {
adc_cali_delete_scheme_curve_fitting(caliHandle);
#endif
}
caliHandle = nullptr;
calibrated = false;
}
}
enum class CaliScheme { None, Line, Curve };
bool initialized = false;
bool calibrated = false;
CaliScheme calScheme = CaliScheme::None;
adc_oneshot_unit_handle_t adcHandle = nullptr;
adc_cali_handle_t caliHandle = nullptr;
ChargeFromVoltage chargeEstimator;
};
} // namespace
std::shared_ptr<PowerDevice> createPower() {
return std::make_shared<JcPower>();
}
@@ -1,7 +0,0 @@
#pragma once
#include <memory>
#include <Tactility/hal/power/PowerDevice.h>
// Battery measurement via ADC2 channel 4 with 85k/100k divider
std::shared_ptr<tt::hal::power::PowerDevice> createPower();
@@ -11,6 +11,8 @@ hardware.spiRamSpeed=200M
hardware.esptoolFlashFreq=80M hardware.esptoolFlashFreq=80M
hardware.bluetooth=true hardware.bluetooth=true
dependencies.useDeprecatedHal=false
storage.userDataLocation=SD storage.userDataLocation=SD
display.size=7" display.size=7"
@@ -1,3 +1,5 @@
dependencies: dependencies:
- Platforms/platform-esp32 - Platforms/platform-esp32
- Drivers/jd9165-module
- Drivers/gt911-module
dts: guition,jc1060p470ciwy.dts dts: guition,jc1060p470ciwy.dts
@@ -7,13 +7,20 @@
#include <tactility/bindings/esp32_i2s.h> #include <tactility/bindings/esp32_i2s.h>
#include <tactility/bindings/esp32_sdmmc.h> #include <tactility/bindings/esp32_sdmmc.h>
#include <tactility/bindings/esp32_wifi.h> #include <tactility/bindings/esp32_wifi.h>
#include <tactility/bindings/esp32_adc_oneshot.h>
#include <tactility/bindings/battery_sense.h>
#include <tactility/bindings/esp32_pwm_ledc.h>
#include <tactility/bindings/pwm_backlight.h>
#include <bindings/jd9165.h>
#include <bindings/gt911.h>
/** /**
* For future reference: * For future reference:
* - ES8311 on I2C with PA PIN at GPIO 11 * - ES8311 on I2C with PA PIN at GPIO 11
* - Built-in led at GPIO 26 * - Built-in led at GPIO 26
* - Boot button at GPIO 21 * - Boot button at GPIO 21
* - LCD reset: GPIO 27 * - LCD reset: GPIO 0 (matches P4 EV board reset line - deprecated HAL's old Display.cpp
* comment claimed GPIO 27 here, but its actual LCD_PIN_RESET constant was GPIO_NUM_0)
* - LCD backlight: GPIO 23 * - LCD backlight: GPIO 23
*/ */
/ { / {
@@ -41,6 +48,13 @@
clock-frequency = <400000>; clock-frequency = <400000>;
pin-sda = <&gpio0 7 GPIO_FLAG_NONE>; pin-sda = <&gpio0 7 GPIO_FLAG_NONE>;
pin-scl = <&gpio0 8 GPIO_FLAG_NONE>; pin-scl = <&gpio0 8 GPIO_FLAG_NONE>;
touch0 {
compatible = "goodix,gt911";
reg = <0x5D>;
x-max = <1024>;
y-max = <600>;
};
}; };
i2s0 { i2s0 {
@@ -65,4 +79,116 @@
bus-width = <4>; bus-width = <4>;
on-chip-ldo-chan = <4>; on-chip-ldo-chan = <4>;
}; };
adc0 {
compatible = "espressif,esp32-adc-oneshot";
unit-id = <ADC_UNIT_2>;
channels = <ADC_CHANNEL_4 ADC_ATTEN_DB_12 ADC_BITWIDTH_DEFAULT>;
};
// Matches the deprecated HAL's old Power.cpp: ADC2 CH4 behind an 85k/100k divider
// (multiplier = (85000+100000)/100000 = 1.850), reference-voltage-mv is the nominal
// full-scale value at ADC_ATTEN_DB_12 (battery-sense has no calibration path, unlike the
// old driver's adc_cali fallback).
battery-sense {
compatible = "battery-sense";
io-channel = <&adc0 0>;
reference-voltage-mv = <3300>;
multiplier = <1850>;
};
display_backlight_pwm {
compatible = "espressif,esp32-pwm-ledc";
pin = <&gpio0 23 GPIO_FLAG_NONE>;
period-ns = <50000>;
ledc-timer = <1>;
ledc-channel = <0>;
};
display_backlight {
compatible = "pwm-backlight";
pwm = <&display_backlight_pwm>;
};
display0 {
compatible = "jdi,jd9165";
horizontal-resolution = <1024>;
vertical-resolution = <600>;
pin-reset = <&gpio0 0 GPIO_FLAG_NONE>;
ldo-channel = <3>; // LDO_VO3 connects to VDD_MIPI_DPHY
ldo-voltage-mv = <2500>;
num-data-lanes = <2>;
lane-bit-rate-mbps = <750>;
dpi-clock-freq-mhz = <50>;
hsync-pulse-width = <20>;
hsync-back-porch = <160>;
hsync-front-porch = <160>;
vsync-pulse-width = <2>;
vsync-back-porch = <21>;
vsync-front-porch = <12>;
// Skips the wait-for-scanout in draw_bitmap(): prevents stalls/blank flashes when
// other tasks (e.g. flash writes) block timing, at the cost of tear-free rendering
// (matches the deprecated HAL's old Jd9165Display::getLvglPortDisplayDsiConfig(),
// which disabled esp_lvgl_port's avoid_tearing for the same reason).
allow-tearing;
backlight = <&display_backlight>;
// Vendor bring-up sequence from the ESP32-P4 Function EV Board reference, carried over
// unchanged from the deprecated HAL's old Jd9165Display.cpp. Framing is
// [cmd, data-len, delay-ms, data-len bytes of data...] - see jd9165-module's
// init-sequence binding property for the encoding.
init-sequence = [
0x30 1 0 0x00
0xF7 4 0 0x49 0x61 0x02 0x00
0x30 1 0 0x01
0x04 1 0 0x0C
0x05 1 0 0x00
0x06 1 0 0x00
0x0B 1 0 0x11
0x17 1 0 0x00
0x20 1 0 0x04
0x1F 1 0 0x05
0x23 1 0 0x00
0x25 1 0 0x19
0x28 1 0 0x18
0x29 1 0 0x04
0x2A 1 0 0x01
0x2B 1 0 0x04
0x2C 1 0 0x01
0x30 1 0 0x02
0x01 1 0 0x22
0x03 1 0 0x12
0x04 1 0 0x00
0x05 1 0 0x64
0x0A 1 0 0x08
0x0B 11 0 0x0A 0x1A 0x0B 0x0D 0x0D 0x11 0x10 0x06 0x08 0x1F 0x1D
0x0C 11 0 0x0D 0x0D 0x0D 0x0D 0x0D 0x0D 0x0D 0x0D 0x0D 0x0D 0x0D
0x0D 11 0 0x16 0x1B 0x0B 0x0D 0x0D 0x11 0x10 0x07 0x09 0x1E 0x1C
0x0E 11 0 0x0D 0x0D 0x0D 0x0D 0x0D 0x0D 0x0D 0x0D 0x0D 0x0D 0x0D
0x0F 11 0 0x16 0x1B 0x0D 0x0B 0x0D 0x11 0x10 0x1C 0x1E 0x09 0x07
0x10 11 0 0x0D 0x0D 0x0D 0x0D 0x0D 0x0D 0x0D 0x0D 0x0D 0x0D 0x0D
0x11 11 0 0x0A 0x1A 0x0D 0x0B 0x0D 0x11 0x10 0x1D 0x1F 0x08 0x06
0x12 11 0 0x0D 0x0D 0x0D 0x0D 0x0D 0x0D 0x0D 0x0D 0x0D 0x0D 0x0D
0x14 4 0 0x00 0x00 0x11 0x11
0x18 1 0 0x99
0x30 1 0 0x06
0x12 14 0 0x36 0x2C 0x2E 0x3C 0x38 0x35 0x35 0x32 0x2E 0x1D 0x2B 0x21 0x16 0x29
0x13 14 0 0x36 0x2C 0x2E 0x3C 0x38 0x35 0x35 0x32 0x2E 0x1D 0x2B 0x21 0x16 0x29
0x30 1 0 0x0A
0x02 1 0 0x4F
0x0B 1 0 0x40
0x12 1 0 0x3E
0x13 1 0 0x78
0x30 1 0 0x0D
0x0D 1 0 0x04
0x10 1 0 0x0C
0x11 1 0 0x0C
0x12 1 0 0x0C
0x13 1 0 0x0C
0x30 1 0 0x00
0x3A 1 0 0x55
0x11 1 120 0x00
0x29 1 20 0x00
];
};
}; };
@@ -3,16 +3,14 @@
extern "C" { extern "C" {
static error_t start() { static error_t start() {
// Empty for now
return ERROR_NONE; return ERROR_NONE;
} }
static error_t stop() { static error_t stop() {
// Empty for now
return ERROR_NONE; return ERROR_NONE;
} }
struct Module guition_jc1060p470ciwy_module = { Module guition_jc1060p470ciwy_module = {
.name = "guition-jc1060p470ciwy", .name = "guition-jc1060p470ciwy",
.start = start, .start = start,
.stop = stop, .stop = stop,
+2 -3
View File
@@ -1,7 +1,6 @@
file(GLOB_RECURSE SOURCE_FILES Source/*.c*) file(GLOB_RECURSE SOURCE_FILES source/*.c*)
idf_component_register( idf_component_register(
SRCS ${SOURCE_FILES} SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source" REQUIRES TactilityKernel driver
REQUIRES Tactility esp_lvgl_port ST7789 CST816S PwmBacklight driver vfs fatfs
) )
@@ -1,21 +0,0 @@
#include "devices/Display.h"
#include <PwmBacklight.h>
#include <Tactility/hal/Configuration.h>
using namespace tt::hal;
static bool initBoot() {
return driver::pwmbacklight::init(LCD_PIN_BACKLIGHT);
}
static DeviceVector createDevices() {
return {
createDisplay(),
};
}
extern const Configuration hardwareConfiguration = {
.initBoot = initBoot,
.createDevices = createDevices
};
@@ -1,43 +0,0 @@
#include "Display.h"
#include <Cst816Touch.h>
#include <PwmBacklight.h>
#include <St7789Display.h>
static std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
auto configuration = std::make_unique<Cst816sTouch::Configuration>(
I2C_NUM_0,
LCD_HORIZONTAL_RESOLUTION,
LCD_VERTICAL_RESOLUTION
);
return std::make_shared<Cst816sTouch>(std::move(configuration));
}
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
St7789Display::Configuration panel_configuration = {
.horizontalResolution = LCD_HORIZONTAL_RESOLUTION,
.verticalResolution = LCD_VERTICAL_RESOLUTION,
.gapX = 0,
.gapY = 0,
.swapXY = false,
.mirrorX = false,
.mirrorY = false,
.invertColor = false,
.bufferSize = LCD_BUFFER_SIZE,
.touch = createTouch(),
.backlightDutyFunction = driver::pwmbacklight::setBacklightDuty,
.resetPin = GPIO_NUM_NC,
.lvglSwapBytes = false
};
auto spi_configuration = std::make_shared<St7789Display::SpiConfiguration>(St7789Display::SpiConfiguration {
.spiHostDevice = LCD_SPI_HOST,
.csPin = LCD_PIN_CS,
.dcPin = LCD_PIN_DC,
.pixelClockFrequency = 62'500'000,
.transactionQueueDepth = 10
});
return std::make_shared<St7789Display>(panel_configuration, spi_configuration);
}
@@ -1,22 +0,0 @@
#pragma once
#include "Display.h"
#include <Tactility/hal/display/DisplayDevice.h>
#include <memory>
#include <driver/gpio.h>
#include <driver/spi_common.h>
// Display backlight (PWM)
constexpr auto LCD_PIN_BACKLIGHT = GPIO_NUM_27;
// Display
constexpr auto LCD_SPI_HOST = SPI2_HOST;
constexpr auto LCD_PIN_CS = GPIO_NUM_15;
constexpr auto LCD_PIN_DC = GPIO_NUM_2;
constexpr auto LCD_HORIZONTAL_RESOLUTION = 240;
constexpr auto LCD_VERTICAL_RESOLUTION = 320;
constexpr auto LCD_BUFFER_HEIGHT = LCD_VERTICAL_RESOLUTION / 10;
constexpr auto LCD_BUFFER_SIZE = LCD_HORIZONTAL_RESOLUTION * LCD_BUFFER_HEIGHT;
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
@@ -7,6 +7,8 @@ hardware.target=ESP32
hardware.flashSize=4MB hardware.flashSize=4MB
hardware.spiRam=false hardware.spiRam=false
dependencies.useDeprecatedHal=false
storage.userDataLocation=SD storage.userDataLocation=SD
display.size=2.8" display.size=2.8"
@@ -1,3 +1,5 @@
dependencies: dependencies:
- Platforms/platform-esp32 - Platforms/platform-esp32
- Drivers/st7789-module
- Drivers/cst816s-module
dts: guition,jc2432w328c.dts dts: guition,jc2432w328c.dts
@@ -8,8 +8,10 @@
#include <tactility/bindings/esp32_uart.h> #include <tactility/bindings/esp32_uart.h>
#include <tactility/bindings/esp32_sdspi.h> #include <tactility/bindings/esp32_sdspi.h>
#include <tactility/bindings/esp32_wifi_pinned.h> #include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/display_placeholder.h> #include <tactility/bindings/pwm_backlight.h>
#include <tactility/bindings/rgb_led_pwm.h> #include <tactility/bindings/rgb_led_pwm.h>
#include <bindings/st7789.h>
#include <bindings/cst816s.h>
/ { / {
compatible = "root"; compatible = "root";
@@ -31,6 +33,13 @@
clock-frequency = <400000>; clock-frequency = <400000>;
pin-sda = <&gpio0 33 GPIO_FLAG_NONE>; pin-sda = <&gpio0 33 GPIO_FLAG_NONE>;
pin-scl = <&gpio0 32 GPIO_FLAG_NONE>; pin-scl = <&gpio0 32 GPIO_FLAG_NONE>;
touch {
compatible = "hynitron,cst816s";
reg = <0x15>;
x-max = <240>;
y-max = <320>;
};
}; };
// CN1 header // CN1 header
@@ -42,6 +51,22 @@
pin-scl = <&gpio0 22 GPIO_FLAG_NONE>; pin-scl = <&gpio0 22 GPIO_FLAG_NONE>;
}; };
display_backlight_pwm {
compatible = "espressif,esp32-pwm-ledc";
pin = <&gpio0 27 GPIO_FLAG_NONE>;
period-ns = <25000>;
ledc-timer = <1>;
ledc-channel = <3>;
};
display_backlight {
compatible = "pwm-backlight";
// Off by default so display power-on won't show the screen from before the last power loss.
// The display backlight is turned on during the boot process.
status = "disabled";
pwm = <&display_backlight_pwm>;
};
spi0 { spi0 {
compatible = "espressif,esp32-spi"; compatible = "espressif,esp32-spi";
host = <SPI2_HOST>; host = <SPI2_HOST>;
@@ -49,8 +74,13 @@
pin-mosi = <&gpio0 13 GPIO_FLAG_NONE>; pin-mosi = <&gpio0 13 GPIO_FLAG_NONE>;
pin-sclk = <&gpio0 14 GPIO_FLAG_NONE>; pin-sclk = <&gpio0 14 GPIO_FLAG_NONE>;
display { display@0 {
compatible = "display-placeholder"; compatible = "sitronix,st7789";
horizontal-resolution = <240>;
vertical-resolution = <320>;
pixel-clock-hz = <62500000>;
pin-dc = <&gpio0 2 GPIO_FLAG_NONE>;
backlight = <&display_backlight>;
}; };
}; };
+2 -3
View File
@@ -1,7 +1,6 @@
file(GLOB_RECURSE SOURCE_FILES Source/*.c*) file(GLOB_RECURSE SOURCE_FILES source/*.c*)
idf_component_register( idf_component_register(
SRCS ${SOURCE_FILES} SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source" REQUIRES TactilityKernel driver
REQUIRES Tactility EspLcdCompat esp_lcd_axs15231b PwmBacklight driver vfs fatfs
) )
@@ -1,493 +0,0 @@
#include "Axs15231bDisplay.h"
#include <Tactility/hal/touch/TouchDevice.h>
#include <Axs15231b/Axs15231bTouch.h>
#include <EspLcdDisplayDriver.h>
#include <esp_lcd_axs15231b.h>
#include <esp_lcd_panel_commands.h>
#include <esp_lcd_panel_ops.h>
#include <esp_heap_caps.h>
#include <tactility/log.h>
constexpr auto* TAG = "AXS15231B";
static const axs15231b_lcd_init_cmd_t lcd_init_cmds[] = {
{0xBB, (uint8_t[]){0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A, 0xA5}, 8, 0},
{0xA0, (uint8_t[]){0xC0, 0x10, 0x00, 0x02, 0x00, 0x00, 0x04, 0x3F, 0x20, 0x05, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00}, 17, 0},
{0xA2, (uint8_t[]){0x30, 0x3C, 0x24, 0x14, 0xD0, 0x20, 0xFF, 0xE0, 0x40, 0x19, 0x80, 0x80, 0x80, 0x20, 0xf9, 0x10, 0x02, 0xff, 0xff, 0xF0, 0x90, 0x01, 0x32, 0xA0, 0x91, 0xE0, 0x20, 0x7F, 0xFF, 0x00, 0x5A}, 31, 0},
{0xD0, (uint8_t[]){0xE0, 0x40, 0x51, 0x24, 0x08, 0x05, 0x10, 0x01, 0x20, 0x15, 0x42, 0xC2, 0x22, 0x22, 0xAA, 0x03, 0x10, 0x12, 0x60, 0x14, 0x1E, 0x51, 0x15, 0x00, 0x8A, 0x20, 0x00, 0x03, 0x3A, 0x12}, 30, 0},
{0xA3, (uint8_t[]){0xA0, 0x06, 0xAa, 0x00, 0x08, 0x02, 0x0A, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x55, 0x55}, 22, 0},
{0xC1, (uint8_t[]){0x31, 0x04, 0x02, 0x02, 0x71, 0x05, 0x24, 0x55, 0x02, 0x00, 0x41, 0x00, 0x53, 0xFF, 0xFF, 0xFF, 0x4F, 0x52, 0x00, 0x4F, 0x52, 0x00, 0x45, 0x3B, 0x0B, 0x02, 0x0d, 0x00, 0xFF, 0x40}, 30, 0},
{0xC3, (uint8_t[]){0x00, 0x00, 0x00, 0x50, 0x03, 0x00, 0x00, 0x00, 0x01, 0x80, 0x01}, 11, 0},
{0xC4, (uint8_t[]){0x00, 0x24, 0x33, 0x80, 0x00, 0xea, 0x64, 0x32, 0xC8, 0x64, 0xC8, 0x32, 0x90, 0x90, 0x11, 0x06, 0xDC, 0xFA, 0x00, 0x00, 0x80, 0xFE, 0x10, 0x10, 0x00, 0x0A, 0x0A, 0x44, 0x50}, 29, 0},
{0xC5, (uint8_t[]){0x18, 0x00, 0x00, 0x03, 0xFE, 0x3A, 0x4A, 0x20, 0x30, 0x10, 0x88, 0xDE, 0x0D, 0x08, 0x0F, 0x0F, 0x01, 0x3A, 0x4A, 0x20, 0x10, 0x10, 0x00}, 23, 0},
{0xC6, (uint8_t[]){0x05, 0x0A, 0x05, 0x0A, 0x00, 0xE0, 0x2E, 0x0B, 0x12, 0x22, 0x12, 0x22, 0x01, 0x03, 0x00, 0x3F, 0x6A, 0x18, 0xC8, 0x22}, 20, 0},
{0xC7, (uint8_t[]){0x50, 0x32, 0x28, 0x00, 0xa2, 0x80, 0x8f, 0x00, 0x80, 0xff, 0x07, 0x11, 0x9c, 0x67, 0xff, 0x24, 0x0c, 0x0d, 0x0e, 0x0f}, 20, 0},
{0xC9, (uint8_t[]){0x33, 0x44, 0x44, 0x01}, 4, 0},
{0xCF, (uint8_t[]){0x2C, 0x1E, 0x88, 0x58, 0x13, 0x18, 0x56, 0x18, 0x1E, 0x68, 0x88, 0x00, 0x65, 0x09, 0x22, 0xC4, 0x0C, 0x77, 0x22, 0x44, 0xAA, 0x55, 0x08, 0x08, 0x12, 0xA0, 0x08}, 27, 0},
{0xD5, (uint8_t[]){0x40, 0x8E, 0x8D, 0x01, 0x35, 0x04, 0x92, 0x74, 0x04, 0x92, 0x74, 0x04, 0x08, 0x6A, 0x04, 0x46, 0x03, 0x03, 0x03, 0x03, 0x82, 0x01, 0x03, 0x00, 0xE0, 0x51, 0xA1, 0x00, 0x00, 0x00}, 30, 0},
{0xD6, (uint8_t[]){0x10, 0x32, 0x54, 0x76, 0x98, 0xBA, 0xDC, 0xFE, 0x93, 0x00, 0x01, 0x83, 0x07, 0x07, 0x00, 0x07, 0x07, 0x00, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x00, 0x84, 0x00, 0x20, 0x01, 0x00}, 30, 0},
{0xD7, (uint8_t[]){0x03, 0x01, 0x0b, 0x09, 0x0f, 0x0d, 0x1E, 0x1F, 0x18, 0x1d, 0x1f, 0x19, 0x40, 0x8E, 0x04, 0x00, 0x20, 0xA0, 0x1F}, 19, 0},
{0xD8, (uint8_t[]){0x02, 0x00, 0x0a, 0x08, 0x0e, 0x0c, 0x1E, 0x1F, 0x18, 0x1d, 0x1f, 0x19}, 12, 0},
{0xD9, (uint8_t[]){0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F}, 12, 0},
{0xDD, (uint8_t[]){0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F}, 12, 0},
{0xDF, (uint8_t[]){0x44, 0x73, 0x4B, 0x69, 0x00, 0x0A, 0x02, 0x90}, 8, 0},
{0xE0, (uint8_t[]){0x3B, 0x28, 0x10, 0x16, 0x0c, 0x06, 0x11, 0x28, 0x5c, 0x21, 0x0D, 0x35, 0x13, 0x2C, 0x33, 0x28, 0x0D}, 17, 0},
{0xE1, (uint8_t[]){0x37, 0x28, 0x10, 0x16, 0x0b, 0x06, 0x11, 0x28, 0x5C, 0x21, 0x0D, 0x35, 0x14, 0x2C, 0x33, 0x28, 0x0F}, 17, 0},
{0xE2, (uint8_t[]){0x3B, 0x07, 0x12, 0x18, 0x0E, 0x0D, 0x17, 0x35, 0x44, 0x32, 0x0C, 0x14, 0x14, 0x36, 0x3A, 0x2F, 0x0D}, 17, 0},
{0xE3, (uint8_t[]){0x37, 0x07, 0x12, 0x18, 0x0E, 0x0D, 0x17, 0x35, 0x44, 0x32, 0x0C, 0x14, 0x14, 0x36, 0x32, 0x2F, 0x0F}, 17, 0},
{0xE4, (uint8_t[]){0x3B, 0x07, 0x12, 0x18, 0x0E, 0x0D, 0x17, 0x39, 0x44, 0x2E, 0x0C, 0x14, 0x14, 0x36, 0x3A, 0x2F, 0x0D}, 17, 0},
{0xE5, (uint8_t[]){0x37, 0x07, 0x12, 0x18, 0x0E, 0x0D, 0x17, 0x39, 0x44, 0x2E, 0x0C, 0x14, 0x14, 0x36, 0x3A, 0x2F, 0x0F}, 17, 0},
{0xA4, (uint8_t[]){0x85, 0x85, 0x95, 0x82, 0xAF, 0xAA, 0xAA, 0x80, 0x10, 0x30, 0x40, 0x40, 0x20, 0xFF, 0x60, 0x30}, 16, 0},
{0xA4, (uint8_t[]){0x85, 0x85, 0x95, 0x85}, 4, 0},
{0xBB, (uint8_t[]){0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 8, 0},
{0x13, (uint8_t[]){0x00}, 0, 0},
{0x35, (uint8_t[]){0x00}, 1, 0}, // Enable Tearing Effect output (V-blank sync)
{0x11, (uint8_t[]){0x00}, 0, 120},
{0x2C, (uint8_t[]){0x00, 0x00, 0x00, 0x00}, 4, 0}
};
void Axs15231bDisplay::lvgl_port_flush_callback(lv_display_t *drv, const lv_area_t *area, uint8_t *color_map) {
Axs15231bDisplay* self = (Axs15231bDisplay*)lv_display_get_user_data(drv);
assert(self != nullptr);
assert(drv != nullptr);
esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t)lv_display_get_driver_data(drv);
assert(panel_handle != nullptr);
// Swap RGB565 byte order for SPI transmission (big-endian wire format)
lv_draw_sw_rgb565_swap(color_map, lv_area_get_size(area));
lv_display_rotation_t rotation = lv_display_get_rotation(drv);
int logical_width = lv_display_get_horizontal_resolution(drv);
int logical_height = lv_display_get_vertical_resolution(drv);
uint16_t* draw_buf = (uint16_t*)color_map;
if (rotation != LV_DISPLAY_ROTATION_0) {
// Lazy-allocate tempBuf on first rotated frame
static bool allocationErrorLogged = false;
if (self->tempBuf == nullptr) {
self->tempBuf = (uint16_t *)heap_caps_malloc(
self->configuration->horizontalResolution * self->configuration->verticalResolution * sizeof(uint16_t),
MALLOC_CAP_SPIRAM);
if (self->tempBuf == nullptr) {
if (!allocationErrorLogged) {
LOG_E(TAG, "Failed to allocate rotation buffer, drawing unrotated");
allocationErrorLogged = true;
}
if (esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, self->configuration->horizontalResolution, self->configuration->verticalResolution, draw_buf) != ESP_OK) {
lv_display_flush_ready(drv);
}
return;
}
}
// Rotate to tempBuf using tile-based approach for cache efficiency.
// SPIRAM random access is very slow due to cache thrashing. By processing
// in small tiles (TILE x TILE pixels), the working set stays within the
// ESP32-S3's 32KB PSRAM cache, dramatically reducing cache misses.
constexpr int TILE = 32;
uint16_t* src = (uint16_t*)color_map;
uint16_t* dst = self->tempBuf;
const int hw = self->configuration->horizontalResolution;
switch (rotation) {
case LV_DISPLAY_ROTATION_90:
for (int ty = 0; ty < logical_height; ty += TILE) {
for (int tx = 0; tx < logical_width; tx += TILE) {
int y_end = (ty + TILE < logical_height) ? ty + TILE : logical_height;
int x_end = (tx + TILE < logical_width) ? tx + TILE : logical_width;
for (int y = ty; y < y_end; y++) {
for (int x = tx; x < x_end; x++) {
dst[(logical_width - 1 - x) * hw + y] = src[y * logical_width + x];
}
}
}
}
break;
case LV_DISPLAY_ROTATION_180: {
// 180° is a simple reverse - sequential reads and writes
const int total = logical_width * logical_height;
for (int i = 0; i < total; i++) {
dst[total - 1 - i] = src[i];
}
break;
}
case LV_DISPLAY_ROTATION_270:
for (int ty = 0; ty < logical_height; ty += TILE) {
for (int tx = 0; tx < logical_width; tx += TILE) {
int y_end = (ty + TILE < logical_height) ? ty + TILE : logical_height;
int x_end = (tx + TILE < logical_width) ? tx + TILE : logical_width;
for (int y = ty; y < y_end; y++) {
for (int x = tx; x < x_end; x++) {
dst[x * hw + (logical_height - 1 - y)] = src[y * logical_width + x];
}
}
}
}
break;
default:
break;
}
draw_buf = self->tempBuf;
}
// Wait for TE (tearing effect) signal before starting SPI transfer.
// This synchronizes frame output with the display's V-blank to reduce tearing.
if (self->teSyncSemaphore != nullptr) {
xSemaphoreTake(self->teSyncSemaphore, 0); // Drain any pending signal
xSemaphoreTake(self->teSyncSemaphore, pdMS_TO_TICKS(20)); // Wait for next V-blank
}
esp_err_t ret = esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, self->configuration->horizontalResolution, self->configuration->verticalResolution, draw_buf);
if (ret != ESP_OK) {
// If SPI transfer failed, on_color_trans_done won't fire.
// Manually signal flush ready to prevent LVGL from hanging.
LOG_E(TAG, "draw_bitmap failed: %s", esp_err_to_name(ret));
lv_display_flush_ready(drv);
}
}
bool Axs15231bDisplay::onColorTransDone(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx) {
lv_display_t *disp = (lv_display_t *)user_ctx;
if (disp != nullptr) {
lv_display_flush_ready(disp);
}
return false;
}
void IRAM_ATTR Axs15231bDisplay::teIsrHandler(void* arg) {
SemaphoreHandle_t sem = (SemaphoreHandle_t)arg;
BaseType_t needYield = pdFALSE;
xSemaphoreGiveFromISR(sem, &needYield);
if (needYield == pdTRUE) {
portYIELD_FROM_ISR();
}
}
bool Axs15231bDisplay::setupTeSync() {
if (configuration->tePin == GPIO_NUM_NC) {
LOG_I(TAG, "TE pin not configured, skipping TE sync");
return true;
}
teSyncSemaphore = xSemaphoreCreateBinary();
if (teSyncSemaphore == nullptr) {
LOG_E(TAG, "Failed to create TE sync semaphore");
return false;
}
gpio_config_t io_conf = {};
io_conf.intr_type = GPIO_INTR_POSEDGE;
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
io_conf.pin_bit_mask = (1ULL << configuration->tePin);
if (gpio_config(&io_conf) != ESP_OK) {
LOG_E(TAG, "Failed to configure TE GPIO");
vSemaphoreDelete(teSyncSemaphore);
teSyncSemaphore = nullptr;
return false;
}
esp_err_t err = gpio_install_isr_service(ESP_INTR_FLAG_IRAM);
if (err == ESP_OK) {
isrServiceInstalledByUs = true;
} else if (err != ESP_ERR_INVALID_STATE) {
LOG_E(TAG, "Failed to install GPIO ISR service");
vSemaphoreDelete(teSyncSemaphore);
teSyncSemaphore = nullptr;
return false;
}
if (gpio_isr_handler_add(configuration->tePin, teIsrHandler, (void*)teSyncSemaphore) != ESP_OK) {
LOG_E(TAG, "Failed to add TE ISR handler");
gpio_intr_disable(configuration->tePin);
if (isrServiceInstalledByUs) {
gpio_uninstall_isr_service();
isrServiceInstalledByUs = false;
}
vSemaphoreDelete(teSyncSemaphore);
teSyncSemaphore = nullptr;
return false;
}
teIsrInstalled = true;
LOG_I(TAG, "TE sync enabled on GPIO %d", (int)configuration->tePin);
return true;
}
void Axs15231bDisplay::teardownTeSync() {
if (teIsrInstalled && configuration->tePin != GPIO_NUM_NC) {
gpio_isr_handler_remove(configuration->tePin);
gpio_intr_disable(configuration->tePin);
teIsrInstalled = false;
}
if (isrServiceInstalledByUs) {
gpio_uninstall_isr_service();
isrServiceInstalledByUs = false;
}
if (teSyncSemaphore != nullptr) {
vSemaphoreDelete(teSyncSemaphore);
teSyncSemaphore = nullptr;
}
}
bool Axs15231bDisplay::createIoHandle() {
const esp_lcd_panel_io_spi_config_t panel_io_config = {
.cs_gpio_num = configuration->csPin,
.dc_gpio_num = configuration->dcPin,
.spi_mode = 3,
.pclk_hz = configuration->pixelClockFrequency,
.trans_queue_depth = configuration->transactionQueueDepth,
.on_color_trans_done = nullptr,
.user_ctx = nullptr,
.lcd_cmd_bits = 32,
.lcd_param_bits = 8,
.cs_ena_pretrans = 0,
.cs_ena_posttrans = 0,
.flags = {
.dc_high_on_cmd = 0,
.dc_low_on_data = 0,
.dc_low_on_param = 0,
.octal_mode = 0,
.quad_mode = 1,
.sio_mode = 0,
.lsb_first = 0,
.cs_high_active = 0
}
};
return esp_lcd_new_panel_io_spi(configuration->spiHostDevice, &panel_io_config, &ioHandle) == ESP_OK;
}
bool Axs15231bDisplay::createPanelHandle() {
const axs15231b_vendor_config_t vendor_config = {
.init_cmds = lcd_init_cmds,
.init_cmds_size = sizeof(lcd_init_cmds) / sizeof(lcd_init_cmds[0]),
.flags = {
.use_qspi_interface = 1,
},
};
const esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = configuration->resetPin,
.rgb_ele_order = configuration->rgbElementOrder,
.data_endian = LCD_RGB_DATA_ENDIAN_LITTLE,
.bits_per_pixel = 16,
.flags = {
.reset_active_high = false
},
.vendor_config = (void *)&vendor_config
};
if (esp_lcd_new_panel_axs15231b(ioHandle, &panel_config, &panelHandle) != ESP_OK) {
LOG_E(TAG, "Failed to create axs15231b");
return false;
}
if (esp_lcd_panel_reset(panelHandle) != ESP_OK) {
LOG_E(TAG, "Failed to reset panel");
esp_lcd_panel_del(panelHandle);
panelHandle = nullptr;
return false;
}
if (esp_lcd_panel_init(panelHandle) != ESP_OK) {
LOG_E(TAG, "Failed to init panel");
esp_lcd_panel_del(panelHandle);
panelHandle = nullptr;
return false;
}
//SWAPXY Doesn't work with the JC3248W535... Left in for future compatibility.
if (esp_lcd_panel_swap_xy(panelHandle, configuration->swapXY) != ESP_OK) {
LOG_E(TAG, "Failed to swap XY");
esp_lcd_panel_del(panelHandle);
panelHandle = nullptr;
return false;
}
if (esp_lcd_panel_mirror(panelHandle, configuration->mirrorX, configuration->mirrorY) != ESP_OK) {
LOG_E(TAG, "Failed to mirror panel");
esp_lcd_panel_del(panelHandle);
panelHandle = nullptr;
return false;
}
if (esp_lcd_panel_invert_color(panelHandle, configuration->invertColor) != ESP_OK) {
LOG_E(TAG, "Failed to invert color");
esp_lcd_panel_del(panelHandle);
panelHandle = nullptr;
return false;
}
if (esp_lcd_panel_disp_on_off(panelHandle, false) != ESP_OK) {
LOG_E(TAG, "Failed to turn off panel");
esp_lcd_panel_del(panelHandle);
panelHandle = nullptr;
return false;
}
return true;
}
Axs15231bDisplay::Axs15231bDisplay(std::unique_ptr<Configuration> inConfiguration) :
configuration(std::move(inConfiguration))
{
assert(configuration != nullptr);
}
bool Axs15231bDisplay::start() {
if (!createIoHandle()) {
LOG_E(TAG, "Failed to create IO handle");
return false;
}
if (!createPanelHandle()) {
LOG_E(TAG, "Failed to create panel handle");
esp_lcd_panel_io_del(ioHandle);
ioHandle = nullptr;
return false;
}
if (!setupTeSync()) {
LOG_W(TAG, "TE sync setup failed, continuing without TE synchronization");
}
return true;
}
bool Axs15231bDisplay::stop() {
if (lvglDisplay != nullptr) {
stopLvgl();
}
teardownTeSync();
// Invalidate cached DisplayDriver - it holds a raw panelHandle that is about to be deleted
displayDriver.reset();
if (panelHandle != nullptr && esp_lcd_panel_del(panelHandle) != ESP_OK) {
LOG_E(TAG, "Failed to delete panel");
}
panelHandle = nullptr;
if (ioHandle != nullptr && esp_lcd_panel_io_del(ioHandle) != ESP_OK) {
LOG_E(TAG, "Failed to delete IO");
}
ioHandle = nullptr;
return true;
}
bool Axs15231bDisplay::startLvgl() {
if (lvglDisplay != nullptr) {
LOG_E(TAG, "LVGL already started");
return false;
}
lvglDisplay = lv_display_create(configuration->horizontalResolution, configuration->verticalResolution);
lv_display_set_user_data(lvglDisplay, this);
lv_display_set_color_format(lvglDisplay, LV_COLOR_FORMAT_RGB565);
uint32_t buffer_pixels = configuration->bufferSize;
uint32_t bufferSize = buffer_pixels * sizeof(uint16_t);
buffer1 = (uint16_t*)heap_caps_malloc(bufferSize, MALLOC_CAP_SPIRAM);
buffer2 = (uint16_t*)heap_caps_malloc(bufferSize, MALLOC_CAP_SPIRAM);
if (buffer1 == nullptr || buffer2 == nullptr) {
LOG_E(TAG, "Failed to allocate buffers");
heap_caps_free(buffer1);
heap_caps_free(buffer2);
buffer1 = nullptr;
buffer2 = nullptr;
lv_display_delete(lvglDisplay);
lvglDisplay = nullptr;
return false;
}
lv_display_set_buffers(lvglDisplay, buffer1, buffer2, bufferSize, LV_DISPLAY_RENDER_MODE_FULL);
lv_display_set_flush_cb(lvglDisplay, lvgl_port_flush_callback);
lv_display_set_driver_data(lvglDisplay, panelHandle);
const esp_lcd_panel_io_callbacks_t cbs = {
.on_color_trans_done = onColorTransDone,
};
if (esp_lcd_panel_io_register_event_callbacks(ioHandle, &cbs, lvglDisplay) != ESP_OK) {
LOG_E(TAG, "Failed to register panel IO callbacks");
heap_caps_free(buffer1);
heap_caps_free(buffer2);
buffer1 = nullptr;
buffer2 = nullptr;
lv_display_delete(lvglDisplay);
lvglDisplay = nullptr;
return false;
}
auto touch_device = getTouchDevice();
if (touch_device != nullptr && touch_device->supportsLvgl()) {
touch_device->startLvgl(lvglDisplay);
}
return true;
}
bool Axs15231bDisplay::stopLvgl() {
if (lvglDisplay == nullptr) {
return false;
}
auto touch_device = getTouchDevice();
if (touch_device != nullptr) {
touch_device->stopLvgl();
}
esp_lcd_panel_disp_on_off(panelHandle, false);
// Unregister IO callbacks before deleting the display to prevent use-after-free.
// The on_color_trans_done callback captures lvglDisplay as user_ctx; if a DMA
// transfer completes after lv_display_delete(), it would call
// lv_display_flush_ready() on a freed pointer.
const esp_lcd_panel_io_callbacks_t nullCbs = {
.on_color_trans_done = nullptr,
};
esp_lcd_panel_io_register_event_callbacks(ioHandle, &nullCbs, nullptr);
lv_display_delete(lvglDisplay);
lvglDisplay = nullptr;
heap_caps_free(buffer1);
heap_caps_free(buffer2);
buffer1 = nullptr;
buffer2 = nullptr;
heap_caps_free(tempBuf);
tempBuf = nullptr;
return true;
}
std::shared_ptr<tt::hal::display::DisplayDriver> Axs15231bDisplay::getDisplayDriver() {
if (lvglDisplay != nullptr) {
LOG_E(TAG, "Cannot get DisplayDriver while LVGL is active - call stopLvgl() first");
return nullptr;
}
if (panelHandle == nullptr) {
LOG_E(TAG, "Cannot get DisplayDriver - display is not started");
return nullptr;
}
if (displayDriver == nullptr) {
displayDriver = std::make_shared<EspLcdDisplayDriver>(
panelHandle,
configuration->horizontalResolution,
configuration->verticalResolution,
tt::hal::display::ColorFormat::RGB565Swapped
);
}
return displayDriver;
}
@@ -1,139 +0,0 @@
#pragma once
#include <Tactility/hal/display/DisplayDevice.h>
#include <Tactility/hal/display/DisplayDriver.h>
#include <driver/gpio.h>
#include <driver/spi_common.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_types.h>
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#include <functional>
#include <lvgl.h>
class Axs15231bDisplay final : public tt::hal::display::DisplayDevice {
public:
class Configuration {
public:
Configuration(
spi_host_device_t spiHostDevice,
gpio_num_t csPin,
gpio_num_t dcPin,
gpio_num_t resetPin,
gpio_num_t tePin,
unsigned int horizontalResolution,
unsigned int verticalResolution,
std::shared_ptr<tt::hal::touch::TouchDevice> touch,
bool swapXY = false,
bool mirrorX = false,
bool mirrorY = false,
bool invertColor = false,
uint32_t bufferSize = 0, // Size in pixel count. 0 means default, which is 1/10 of the screen size,
lcd_rgb_element_order_t rgbElementOrder = LCD_RGB_ELEMENT_ORDER_RGB
) : spiHostDevice(spiHostDevice),
csPin(csPin),
dcPin(dcPin),
resetPin(resetPin),
tePin(tePin),
horizontalResolution(horizontalResolution),
verticalResolution(verticalResolution),
swapXY(swapXY),
mirrorX(mirrorX),
mirrorY(mirrorY),
invertColor(invertColor),
bufferSize(bufferSize),
rgbElementOrder(rgbElementOrder),
touch(std::move(touch))
{
if (this->bufferSize == 0) {
this->bufferSize = horizontalResolution * verticalResolution / 10;
}
}
spi_host_device_t spiHostDevice;
gpio_num_t csPin;
gpio_num_t dcPin;
gpio_num_t resetPin;
gpio_num_t tePin;
unsigned int pixelClockFrequency = 40'000'000; // Hertz
size_t transactionQueueDepth = 10;
unsigned int horizontalResolution;
unsigned int verticalResolution;
bool swapXY;
bool mirrorX;
bool mirrorY;
bool invertColor;
uint32_t bufferSize; // Size in pixel count. 0 means default, which is 1/10 of the screen size
lcd_rgb_element_order_t rgbElementOrder;
std::shared_ptr<tt::hal::touch::TouchDevice> touch;
std::function<void(uint8_t)> backlightDutyFunction = nullptr;
};
private:
esp_lcd_panel_io_handle_t ioHandle = nullptr;
esp_lcd_panel_handle_t panelHandle = nullptr;
lv_display_t* lvglDisplay = nullptr;
uint16_t* buffer1 = nullptr;
uint16_t* buffer2 = nullptr;
uint16_t* tempBuf = nullptr;
SemaphoreHandle_t teSyncSemaphore = nullptr;
bool teIsrInstalled = false;
bool isrServiceInstalledByUs = false;
std::unique_ptr<Configuration> configuration;
std::shared_ptr<tt::hal::display::DisplayDriver> displayDriver;
bool createIoHandle();
bool createPanelHandle();
bool setupTeSync();
void teardownTeSync();
static void IRAM_ATTR teIsrHandler(void* arg);
public:
explicit Axs15231bDisplay(std::unique_ptr<Configuration> inConfiguration);
std::string getName() const override { return "AXS15231B Display"; }
std::string getDescription() const override { return "AXS15231B display"; }
bool start() override;
bool stop() override;
bool supportsLvgl() const override { return true; }
bool startLvgl() override;
bool stopLvgl() override;
lv_display_t* getLvglDisplay() const override { return lvglDisplay; }
std::shared_ptr<tt::hal::touch::TouchDevice> getTouchDevice() override { return configuration->touch; }
void setBacklightDuty(uint8_t backlightDuty) override {
if (configuration->backlightDutyFunction != nullptr) {
configuration->backlightDutyFunction(backlightDuty);
}
}
bool supportsBacklightDuty() const override { return configuration->backlightDutyFunction != nullptr; }
bool supportsDisplayDriver() const override { return true; }
std::shared_ptr<tt::hal::display::DisplayDriver> getDisplayDriver() override;
static void lvgl_port_flush_callback(lv_display_t *drv, const lv_area_t *area, uint8_t *color_map);
static bool onColorTransDone(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx);
};
@@ -1,38 +0,0 @@
#include "Axs15231bTouch.h"
#include <esp_lcd_axs15231b.h>
#include <esp_err.h>
bool Axs15231bTouch::createIoHandle(esp_lcd_panel_io_handle_t& outHandle) {
if (configuration == nullptr) {
return false;
}
esp_lcd_panel_io_i2c_config_t io_config = ESP_LCD_TOUCH_IO_I2C_AXS15231B_CONFIG();
return esp_lcd_new_panel_io_i2c(configuration->port, &io_config, &outHandle) == ESP_OK;
}
bool Axs15231bTouch::createTouchHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_touch_config_t& configuration, esp_lcd_touch_handle_t& panelHandle) {
return esp_lcd_touch_new_i2c_axs15231b(ioHandle, &configuration, &panelHandle) == ESP_OK;
}
esp_lcd_touch_config_t Axs15231bTouch::createEspLcdTouchConfig() {
return {
.x_max = configuration->xMax,
.y_max = configuration->yMax,
.rst_gpio_num = configuration->pinReset,
.int_gpio_num = configuration->pinInterrupt,
.levels = {
.reset = configuration->pinResetLevel,
.interrupt = configuration->pinInterruptLevel,
},
.flags = {
.swap_xy = configuration->swapXy,
.mirror_x = configuration->mirrorX,
.mirror_y = configuration->mirrorY,
},
.process_coordinates = nullptr,
.interrupt_callback = nullptr,
.user_data = nullptr,
.driver_data = nullptr
};
}
@@ -1,72 +0,0 @@
#pragma once
#include <Tactility/hal/touch/TouchDevice.h>
#include <Tactility/TactilityCore.h>
#include <driver/i2c.h>
#include <memory>
#include <string>
#include <EspLcdTouch.h>
class Axs15231bTouch final : public EspLcdTouch {
public:
class Configuration {
public:
Configuration(
i2c_port_t port,
uint16_t xMax,
uint16_t yMax,
bool swapXy = false,
bool mirrorX = false,
bool mirrorY = false,
gpio_num_t pinReset = GPIO_NUM_NC,
gpio_num_t pinInterrupt = GPIO_NUM_NC,
unsigned int pinResetLevel = 0,
unsigned int pinInterruptLevel = 0
) : port(port),
xMax(xMax),
yMax(yMax),
swapXy(swapXy),
mirrorX(mirrorX),
mirrorY(mirrorY),
pinReset(pinReset),
pinInterrupt(pinInterrupt),
pinResetLevel(pinResetLevel),
pinInterruptLevel(pinInterruptLevel)
{}
i2c_port_t port;
uint16_t xMax;
uint16_t yMax;
bool swapXy;
bool mirrorX;
bool mirrorY;
gpio_num_t pinReset;
gpio_num_t pinInterrupt;
unsigned int pinResetLevel;
unsigned int pinInterruptLevel;
};
private:
std::unique_ptr<Configuration> configuration;
bool createIoHandle(esp_lcd_panel_io_handle_t& outHandle) override;
bool createTouchHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_touch_config_t& configuration, esp_lcd_touch_handle_t& panelHandle) override;
esp_lcd_touch_config_t createEspLcdTouchConfig() override;
public:
explicit Axs15231bTouch(std::unique_ptr<Configuration> inConfiguration) : configuration(std::move(inConfiguration)) {
assert(configuration != nullptr);
}
std::string getName() const override { return "AXS15231B Touch"; }
std::string getDescription() const override { return "AXS15231B I2C touch driver"; }
};
@@ -1,21 +0,0 @@
#include "devices/Display.h"
#include <Tactility/hal/Configuration.h>
#include <PwmBacklight.h>
using namespace tt::hal;
static DeviceVector createDevices() {
return {
createDisplay(),
};
}
static bool initBoot() {
return driver::pwmbacklight::init(GPIO_NUM_1);
}
extern const Configuration hardwareConfiguration = {
.initBoot = initBoot,
.createDevices = createDevices
};
@@ -1,44 +0,0 @@
#include "Display.h"
#include <PwmBacklight.h>
#include <Axs15231b/Axs15231bDisplay.h>
#include <Axs15231b/Axs15231bTouch.h>
static constexpr size_t JC3248W535C_LCD_DRAW_BUFFER_SIZE = 320 * 480;
static std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
auto configuration = std::make_unique<Axs15231bTouch::Configuration>(
I2C_NUM_0,
320, // width
480, // height
false, // mirror_x
false, // mirror_y
false // swap_xy
);
return std::make_shared<Axs15231bTouch>(std::move(configuration));
}
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
auto touch = createTouch();
auto configuration = std::make_unique<Axs15231bDisplay::Configuration>(
SPI2_HOST,
GPIO_NUM_45, //CS
GPIO_NUM_NC, //DC
GPIO_NUM_NC, //RST
GPIO_NUM_38, //TE
320, // width
480, // height
touch,
false, // swap_xy
false, // mirror_x
false, // mirror_y
false, // invert color
JC3248W535C_LCD_DRAW_BUFFER_SIZE
);
configuration->backlightDutyFunction = driver::pwmbacklight::setBacklightDuty;
return std::make_shared<Axs15231bDisplay>(std::move(configuration));
}
@@ -1,6 +0,0 @@
#pragma once
#include <memory>
#include <Tactility/hal/display/DisplayDevice.h>
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
@@ -12,6 +12,8 @@ hardware.tinyUsb=true
hardware.esptoolFlashFreq=120M hardware.esptoolFlashFreq=120M
hardware.bluetooth=true hardware.bluetooth=true
dependencies.useDeprecatedHal=false
storage.userDataLocation=SD storage.userDataLocation=SD
display.size=3.5" display.size=3.5"
@@ -2,4 +2,5 @@ dependencies:
- Platforms/platform-esp32 - Platforms/platform-esp32
- Drivers/dummy-i2s-amp-module - Drivers/dummy-i2s-amp-module
- Drivers/audio-stream-module - Drivers/audio-stream-module
- Drivers/axs15231b-module
dts: guition,jc3248w535c.dts dts: guition,jc3248w535c.dts
@@ -9,8 +9,11 @@
#include <tactility/bindings/esp32_spi.h> #include <tactility/bindings/esp32_spi.h>
#include <tactility/bindings/esp32_uart.h> #include <tactility/bindings/esp32_uart.h>
#include <tactility/bindings/esp32_sdspi.h> #include <tactility/bindings/esp32_sdspi.h>
#include <tactility/bindings/display_placeholder.h> #include <tactility/bindings/esp32_pwm_ledc.h>
#include <tactility/bindings/pwm_backlight.h>
#include <bindings/dummy_i2s_amp.h> #include <bindings/dummy_i2s_amp.h>
#include <bindings/axs15231b_display.h>
#include <bindings/axs15231b_touch.h>
/ { / {
compatible = "root"; compatible = "root";
@@ -31,14 +34,6 @@
gpio-count = <49>; gpio-count = <49>;
}; };
i2c_internal: i2c0 {
compatible = "espressif,esp32-i2c";
port = <I2C_NUM_0>;
clock-frequency = <400000>;
pin-sda = <&gpio0 4 GPIO_FLAG_NONE>;
pin-scl = <&gpio0 8 GPIO_FLAG_NONE>;
};
i2c_external: i2c1 { i2c_external: i2c1 {
compatible = "espressif,esp32-i2c"; compatible = "espressif,esp32-i2c";
port = <I2C_NUM_1>; port = <I2C_NUM_1>;
@@ -60,6 +55,25 @@
i2s = <&i2s0>; i2s = <&i2s0>;
}; };
display_backlight_pwm {
compatible = "espressif,esp32-pwm-ledc";
pin = <&gpio0 1 GPIO_FLAG_NONE>;
period-ns = <25000>;
ledc-timer = <0>;
ledc-channel = <0>;
// Matches the deprecated HAL's old PwmBacklight driver, which hardcoded 8-bit duty
// resolution (the binding's own default is 10-bit).
duty-resolution = <8>;
};
display_backlight {
compatible = "pwm-backlight";
// Off by default so display power-on won't show the screen from before the last power loss.
// The display backlight is turned on during the boot process.
status = "disabled";
pwm = <&display_backlight_pwm>;
};
spi0 { spi0 {
compatible = "espressif,esp32-spi"; compatible = "espressif,esp32-spi";
host = <SPI2_HOST>; host = <SPI2_HOST>;
@@ -70,8 +84,77 @@
pin-wp = <&gpio0 40 GPIO_FLAG_NONE>; pin-wp = <&gpio0 40 GPIO_FLAG_NONE>;
pin-hd = <&gpio0 39 GPIO_FLAG_NONE>; pin-hd = <&gpio0 39 GPIO_FLAG_NONE>;
display { display@0 {
compatible = "display-placeholder"; compatible = "axs,axs15231b";
horizontal-resolution = <320>;
vertical-resolution = <480>;
pin-te = <&gpio0 38 GPIO_FLAG_NONE>;
backlight = <&display_backlight>;
// Confirmed on real hardware: a sub-region draw desyncs this panel's row
// auto-increment counter (no QSPI row-address command exists to resync it),
// producing a sheared/doubled image - see requires-full-frame's binding doc.
requires-full-frame;
// Vendor bring-up sequence, carried over unchanged from the deprecated HAL's old
// Axs15231bDisplay.cpp. Framing is [cmd, data-length, delay-ms, data-length bytes of
// data...] - see axs15231b-module's init-sequence binding property for the encoding.
init-sequence = [
0xBB 8 0 0x00 0x00 0x00 0x00 0x00 0x00 0x5A 0xA5
0xA0 17 0 0xC0 0x10 0x00 0x02 0x00 0x00 0x04 0x3F 0x20 0x05 0x3F 0x3F 0x00 0x00 0x00 0x00 0x00
0xA2 31 0 0x30 0x3C 0x24 0x14 0xD0 0x20 0xFF 0xE0 0x40 0x19 0x80 0x80 0x80 0x20 0xf9 0x10 0x02 0xff 0xff 0xF0 0x90 0x01 0x32 0xA0 0x91 0xE0 0x20 0x7F 0xFF 0x00 0x5A
0xD0 30 0 0xE0 0x40 0x51 0x24 0x08 0x05 0x10 0x01 0x20 0x15 0x42 0xC2 0x22 0x22 0xAA 0x03 0x10 0x12 0x60 0x14 0x1E 0x51 0x15 0x00 0x8A 0x20 0x00 0x03 0x3A 0x12
0xA3 22 0 0xA0 0x06 0xAa 0x00 0x08 0x02 0x0A 0x04 0x04 0x04 0x04 0x04 0x04 0x04 0x04 0x04 0x04 0x04 0x04 0x00 0x55 0x55
0xC1 30 0 0x31 0x04 0x02 0x02 0x71 0x05 0x24 0x55 0x02 0x00 0x41 0x00 0x53 0xFF 0xFF 0xFF 0x4F 0x52 0x00 0x4F 0x52 0x00 0x45 0x3B 0x0B 0x02 0x0d 0x00 0xFF 0x40
0xC3 11 0 0x00 0x00 0x00 0x50 0x03 0x00 0x00 0x00 0x01 0x80 0x01
0xC4 29 0 0x00 0x24 0x33 0x80 0x00 0xea 0x64 0x32 0xC8 0x64 0xC8 0x32 0x90 0x90 0x11 0x06 0xDC 0xFA 0x00 0x00 0x80 0xFE 0x10 0x10 0x00 0x0A 0x0A 0x44 0x50
0xC5 23 0 0x18 0x00 0x00 0x03 0xFE 0x3A 0x4A 0x20 0x30 0x10 0x88 0xDE 0x0D 0x08 0x0F 0x0F 0x01 0x3A 0x4A 0x20 0x10 0x10 0x00
0xC6 20 0 0x05 0x0A 0x05 0x0A 0x00 0xE0 0x2E 0x0B 0x12 0x22 0x12 0x22 0x01 0x03 0x00 0x3F 0x6A 0x18 0xC8 0x22
0xC7 20 0 0x50 0x32 0x28 0x00 0xa2 0x80 0x8f 0x00 0x80 0xff 0x07 0x11 0x9c 0x67 0xff 0x24 0x0c 0x0d 0x0e 0x0f
0xC9 4 0 0x33 0x44 0x44 0x01
0xCF 27 0 0x2C 0x1E 0x88 0x58 0x13 0x18 0x56 0x18 0x1E 0x68 0x88 0x00 0x65 0x09 0x22 0xC4 0x0C 0x77 0x22 0x44 0xAA 0x55 0x08 0x08 0x12 0xA0 0x08
0xD5 30 0 0x40 0x8E 0x8D 0x01 0x35 0x04 0x92 0x74 0x04 0x92 0x74 0x04 0x08 0x6A 0x04 0x46 0x03 0x03 0x03 0x03 0x82 0x01 0x03 0x00 0xE0 0x51 0xA1 0x00 0x00 0x00
0xD6 30 0 0x10 0x32 0x54 0x76 0x98 0xBA 0xDC 0xFE 0x93 0x00 0x01 0x83 0x07 0x07 0x00 0x07 0x07 0x00 0x03 0x03 0x03 0x03 0x03 0x03 0x00 0x84 0x00 0x20 0x01 0x00
0xD7 19 0 0x03 0x01 0x0b 0x09 0x0f 0x0d 0x1E 0x1F 0x18 0x1d 0x1f 0x19 0x40 0x8E 0x04 0x00 0x20 0xA0 0x1F
0xD8 12 0 0x02 0x00 0x0a 0x08 0x0e 0x0c 0x1E 0x1F 0x18 0x1d 0x1f 0x19
0xD9 12 0 0x1F 0x1F 0x1F 0x1F 0x1F 0x1F 0x1F 0x1F 0x1F 0x1F 0x1F 0x1F
0xDD 12 0 0x1F 0x1F 0x1F 0x1F 0x1F 0x1F 0x1F 0x1F 0x1F 0x1F 0x1F 0x1F
0xDF 8 0 0x44 0x73 0x4B 0x69 0x00 0x0A 0x02 0x90
0xE0 17 0 0x3B 0x28 0x10 0x16 0x0c 0x06 0x11 0x28 0x5c 0x21 0x0D 0x35 0x13 0x2C 0x33 0x28 0x0D
0xE1 17 0 0x37 0x28 0x10 0x16 0x0b 0x06 0x11 0x28 0x5C 0x21 0x0D 0x35 0x14 0x2C 0x33 0x28 0x0F
0xE2 17 0 0x3B 0x07 0x12 0x18 0x0E 0x0D 0x17 0x35 0x44 0x32 0x0C 0x14 0x14 0x36 0x3A 0x2F 0x0D
0xE3 17 0 0x37 0x07 0x12 0x18 0x0E 0x0D 0x17 0x35 0x44 0x32 0x0C 0x14 0x14 0x36 0x32 0x2F 0x0F
0xE4 17 0 0x3B 0x07 0x12 0x18 0x0E 0x0D 0x17 0x39 0x44 0x2E 0x0C 0x14 0x14 0x36 0x3A 0x2F 0x0D
0xE5 17 0 0x37 0x07 0x12 0x18 0x0E 0x0D 0x17 0x39 0x44 0x2E 0x0C 0x14 0x14 0x36 0x3A 0x2F 0x0F
0xA4 16 0 0x85 0x85 0x95 0x82 0xAF 0xAA 0xAA 0x80 0x10 0x30 0x40 0x40 0x20 0xFF 0x60 0x30
0xA4 4 0 0x85 0x85 0x95 0x85
0xBB 8 0 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x13 0 0
0x35 1 0 0x00
0x11 0 120
0x2C 4 0 0x00 0x00 0x00 0x00
];
};
};
// Declared (and thus started) after display@0 above, not before: the display and touch
// sides of this chip share the same silicon, and the deprecated HAL's old Hal.cpp always
// started the display first, then the touch device second. Starting touch first (as an
// earlier version of this devicetree did) let touch's I2C handshake succeed initially, but
// the display's own reset/init sequence running afterward then reset the whole chip and
// left the touch interface unresponsive - confirmed on real hardware (I2C writes started
// failing consistently right after display bring-up completed).
i2c_internal: i2c0 {
compatible = "espressif,esp32-i2c";
port = <I2C_NUM_0>;
clock-frequency = <400000>;
pin-sda = <&gpio0 4 GPIO_FLAG_NONE>;
pin-scl = <&gpio0 8 GPIO_FLAG_NONE>;
touch {
compatible = "axs,axs15231b-touch";
reg = <0x3B>;
x-max = <320>;
y-max = <480>;
}; };
}; };
@@ -3,12 +3,10 @@
extern "C" { extern "C" {
static error_t start() { static error_t start() {
// Empty for now
return ERROR_NONE; return ERROR_NONE;
} }
static error_t stop() { static error_t stop() {
// Empty for now
return ERROR_NONE; return ERROR_NONE;
} }
+2 -3
View File
@@ -1,7 +1,6 @@
file(GLOB_RECURSE SOURCE_FILES Source/*.c*) file(GLOB_RECURSE SOURCE_FILES source/*.c*)
idf_component_register( idf_component_register(
SRCS ${SOURCE_FILES} SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source" REQUIRES TactilityKernel driver
REQUIRES Tactility esp_lvgl_port esp_lcd RgbDisplay GT911 PwmBacklight driver vfs fatfs
) )
@@ -1,21 +0,0 @@
#include "PwmBacklight.h"
#include "devices/Display.h"
#include <Tactility/hal/Configuration.h>
using namespace tt::hal;
static bool initBoot() {
return driver::pwmbacklight::init(GPIO_NUM_2);
}
static DeviceVector createDevices() {
return {
createDisplay(),
};
}
extern const Configuration hardwareConfiguration = {
.initBoot = initBoot,
.createDevices = createDevices
};
@@ -1,108 +0,0 @@
#include "Display.h"
#include <Gt911Touch.h>
#include <PwmBacklight.h>
#include <RgbDisplay.h>
#include <tactility/check.h>
#include <tactility/device.h>
std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
// Note for future changes: Reset pin is 38 and interrupt pin is 18
// or INT = NC, schematic and other info floating around is kinda conflicting...
auto* i2c = device_find_by_name("i2c0");
check(i2c);
auto configuration = std::make_unique<Gt911Touch::Configuration>(
i2c,
800,
480
);
return std::make_shared<Gt911Touch>(std::move(configuration));
}
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
auto touch = createTouch();
constexpr uint32_t bufferPixels = 800 * 10;
esp_lcd_rgb_panel_config_t rgb_panel_config = {
.clk_src = LCD_CLK_SRC_DEFAULT,
.timings = {
.pclk_hz = 16000000,
.h_res = 800,
.v_res = 480,
.hsync_pulse_width = 4,
.hsync_back_porch = 8,
.hsync_front_porch = 8,
.vsync_pulse_width = 4,
.vsync_back_porch = 8,
.vsync_front_porch = 8,
.flags = {
.hsync_idle_low = false,
.vsync_idle_low = false,
.de_idle_high = false,
.pclk_active_neg = true,
.pclk_idle_high = false
}
},
.data_width = 16,
.bits_per_pixel = 0,
.num_fbs = 2,
.bounce_buffer_size_px = bufferPixels,
.sram_trans_align = 8,
.psram_trans_align = 64,
.hsync_gpio_num = GPIO_NUM_39,
.vsync_gpio_num = GPIO_NUM_41,
.de_gpio_num = GPIO_NUM_40 ,
.pclk_gpio_num = GPIO_NUM_42,
.disp_gpio_num = GPIO_NUM_NC,
.data_gpio_nums = {
GPIO_NUM_8, // B3
GPIO_NUM_3, // B4
GPIO_NUM_46, // B5
GPIO_NUM_9, // B6
GPIO_NUM_1, // B7
GPIO_NUM_5, // G2
GPIO_NUM_6, // G3
GPIO_NUM_7, // G4
GPIO_NUM_15, // G5
GPIO_NUM_16, // G6
GPIO_NUM_4, // G7
GPIO_NUM_45, // R3
GPIO_NUM_48, // R4
GPIO_NUM_47, // R5
GPIO_NUM_21, // R6
GPIO_NUM_14, // R7
},
.flags = {
.disp_active_low = false,
.refresh_on_demand = false,
.fb_in_psram = true,
.double_fb = true,
.no_fb = false,
.bb_invalidate_cache = false
}
};
RgbDisplay::BufferConfiguration buffer_config = {
.size = (800 * 480),
.useSpi = true,
.doubleBuffer = true,
.bounceBufferMode = true,
.avoidTearing = false
};
auto configuration = std::make_unique<RgbDisplay::Configuration>(
rgb_panel_config,
buffer_config,
touch,
LV_COLOR_FORMAT_RGB565,
false,
false,
false,
false,
driver::pwmbacklight::setBacklightDuty
);
return std::make_shared<RgbDisplay>(std::move(configuration));
}
@@ -1,5 +0,0 @@
#pragma once
#include <Tactility/hal/display/DisplayDevice.h>
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
@@ -11,6 +11,8 @@ hardware.spiRamSpeed=80M
hardware.esptoolFlashFreq=80M hardware.esptoolFlashFreq=80M
hardware.bluetooth=true hardware.bluetooth=true
dependencies.useDeprecatedHal=false
storage.userDataLocation=SD storage.userDataLocation=SD
display.size=5" display.size=5"
@@ -2,4 +2,6 @@ dependencies:
- Platforms/platform-esp32 - Platforms/platform-esp32
- Drivers/dummy-i2s-amp-module - Drivers/dummy-i2s-amp-module
- Drivers/audio-stream-module - Drivers/audio-stream-module
- Drivers/rgb-display-module
- Drivers/gt911-module
dts: guition,jc8048w550c.dts dts: guition,jc8048w550c.dts
@@ -9,7 +9,11 @@
#include <tactility/bindings/esp32_spi.h> #include <tactility/bindings/esp32_spi.h>
#include <tactility/bindings/esp32_uart.h> #include <tactility/bindings/esp32_uart.h>
#include <tactility/bindings/esp32_sdspi.h> #include <tactility/bindings/esp32_sdspi.h>
#include <tactility/bindings/esp32_pwm_ledc.h>
#include <tactility/bindings/pwm_backlight.h>
#include <bindings/dummy_i2s_amp.h> #include <bindings/dummy_i2s_amp.h>
#include <bindings/rgb_display.h>
#include <bindings/gt911.h>
/ { / {
compatible = "root"; compatible = "root";
@@ -36,6 +40,16 @@
clock-frequency = <400000>; clock-frequency = <400000>;
pin-sda = <&gpio0 19 GPIO_FLAG_NONE>; pin-sda = <&gpio0 19 GPIO_FLAG_NONE>;
pin-scl = <&gpio0 20 GPIO_FLAG_NONE>; pin-scl = <&gpio0 20 GPIO_FLAG_NONE>;
touch0 {
// Reset pin 38 and interrupt pin 18 (or INT = NC) exist on the board but are not
// wired up here - conflicting schematic info, unverified (matches the original
// deprecated-HAL config).
compatible = "goodix,gt911";
reg = <0x5D>;
x-max = <800>;
y-max = <480>;
};
}; };
i2c_external: i2c1 { i2c_external: i2c1 {
@@ -81,4 +95,58 @@
pin-tx = <&gpio0 18 GPIO_FLAG_NONE>; pin-tx = <&gpio0 18 GPIO_FLAG_NONE>;
pin-rx = <&gpio0 17 GPIO_FLAG_NONE>; pin-rx = <&gpio0 17 GPIO_FLAG_NONE>;
}; };
display_backlight_pwm {
compatible = "espressif,esp32-pwm-ledc";
pin = <&gpio0 2 GPIO_FLAG_NONE>;
period-ns = <25000>;
ledc-timer = <0>;
ledc-channel = <0>;
};
display_backlight {
compatible = "pwm-backlight";
// Off by default so display power-on won't show the screen from before the last power loss.
// The display backlight is turned on during the boot process.
status = "disabled";
pwm = <&display_backlight_pwm>;
};
display0 {
compatible = "espressif,esp32-rgb-display";
horizontal-resolution = <800>;
vertical-resolution = <480>;
pixel-clock-hz = <16000000>;
hsync-pulse-width = <4>;
hsync-back-porch = <8>;
hsync-front-porch = <8>;
vsync-pulse-width = <4>;
vsync-back-porch = <8>;
vsync-front-porch = <8>;
pclk-active-neg;
num-fbs = <2>;
double-fb;
bounce-buffer-size-px = <8000>;
pin-hsync = <&gpio0 39 GPIO_FLAG_NONE>;
pin-vsync = <&gpio0 41 GPIO_FLAG_NONE>;
pin-de = <&gpio0 40 GPIO_FLAG_NONE>;
pin-pclk = <&gpio0 42 GPIO_FLAG_NONE>;
pin-data0 = <&gpio0 8 GPIO_FLAG_NONE>; // B3
pin-data1 = <&gpio0 3 GPIO_FLAG_NONE>; // B4
pin-data2 = <&gpio0 46 GPIO_FLAG_NONE>; // B5
pin-data3 = <&gpio0 9 GPIO_FLAG_NONE>; // B6
pin-data4 = <&gpio0 1 GPIO_FLAG_NONE>; // B7
pin-data5 = <&gpio0 5 GPIO_FLAG_NONE>; // G2
pin-data6 = <&gpio0 6 GPIO_FLAG_NONE>; // G3
pin-data7 = <&gpio0 7 GPIO_FLAG_NONE>; // G4
pin-data8 = <&gpio0 15 GPIO_FLAG_NONE>; // G5
pin-data9 = <&gpio0 16 GPIO_FLAG_NONE>; // G6
pin-data10 = <&gpio0 4 GPIO_FLAG_NONE>; // G7
pin-data11 = <&gpio0 45 GPIO_FLAG_NONE>; // R3
pin-data12 = <&gpio0 48 GPIO_FLAG_NONE>; // R4
pin-data13 = <&gpio0 47 GPIO_FLAG_NONE>; // R5
pin-data14 = <&gpio0 21 GPIO_FLAG_NONE>; // R6
pin-data15 = <&gpio0 14 GPIO_FLAG_NONE>; // R7
backlight = <&display_backlight>;
};
}; };
@@ -3,12 +3,10 @@
extern "C" { extern "C" {
static error_t start() { static error_t start() {
// Empty for now
return ERROR_NONE; return ERROR_NONE;
} }
static error_t stop() { static error_t stop() {
// Empty for now
return ERROR_NONE; return ERROR_NONE;
} }
@@ -1,7 +1,6 @@
file(GLOB_RECURSE SOURCE_FILES Source/*.c*) file(GLOB_RECURSE SOURCE_FILES source/*.c*)
idf_component_register( idf_component_register(
SRCS ${SOURCE_FILES} SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source" REQUIRES TactilityKernel
REQUIRES Tactility EspLcdCompat SSD1306 ButtonControl EstimatedPower driver
) )
@@ -1,48 +0,0 @@
#include "devices/Display.h"
#include "devices/Power.h"
#include "devices/Constants.h"
#include <tactility/log.h>
#include <tactility/freertos/task.h>
#include <Tactility/hal/Configuration.h>
#include <ButtonControl.h>
#include <driver/gpio.h>
static void enableOledPower() {
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << DISPLAY_PIN_POWER),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE, // The board has an external pull-up
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&io_conf);
gpio_set_level(DISPLAY_PIN_POWER, 0); // Active low
vTaskDelay(pdMS_TO_TICKS(500)); // Add a small delay for power to stabilize
LOG_I("HeltecV3", "OLED power enabled");
}
static bool initBoot() {
// Enable power to the OLED before doing anything else
enableOledPower();
return true;
}
using namespace tt::hal;
static std::vector<std::shared_ptr<tt::hal::Device>> createDevices() {
return {
createPower(),
ButtonControl::createOneButtonControl(0),
createDisplay()
};
}
extern const Configuration hardwareConfiguration = {
.initBoot = initBoot,
.createDevices = createDevices
};
@@ -1,16 +0,0 @@
#pragma once
#include <driver/gpio.h>
#include <driver/i2c.h>
#include <driver/spi_common.h>
// Display
constexpr auto DISPLAY_I2C_ADDRESS = 0x3C;
constexpr auto DISPLAY_I2C_SPEED = 200000;
constexpr auto DISPLAY_I2C_PORT = I2C_NUM_0;
constexpr auto DISPLAY_PIN_SDA = GPIO_NUM_17;
constexpr auto DISPLAY_PIN_SCL = GPIO_NUM_18;
constexpr auto DISPLAY_PIN_RST = GPIO_NUM_21;
constexpr auto DISPLAY_HORIZONTAL_RESOLUTION = 128;
constexpr auto DISPLAY_VERTICAL_RESOLUTION = 64;
constexpr auto DISPLAY_PIN_POWER = GPIO_NUM_36;
@@ -1,19 +0,0 @@
#include "Display.h"
#include "Constants.h"
#include <Ssd1306Display.h>
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
auto configuration = std::make_unique<Ssd1306Display::Configuration>(
DISPLAY_I2C_PORT,
DISPLAY_I2C_ADDRESS,
DISPLAY_PIN_RST,
DISPLAY_HORIZONTAL_RESOLUTION,
DISPLAY_VERTICAL_RESOLUTION,
nullptr, // no touch
false // invert
);
auto display = std::make_shared<Ssd1306Display>(std::move(configuration));
return std::static_pointer_cast<tt::hal::display::DisplayDevice>(display);
}
@@ -1,5 +0,0 @@
#pragma once
#include <Tactility/hal/display/DisplayDevice.h>
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
@@ -1,20 +0,0 @@
#include "Power.h"
#include <ChargeFromAdcVoltage.h>
#include <EstimatedPower.h>
#include <Tactility/hal/gpio/Gpio.h>
#include <memory>
// ADC enable pin on GPIO37
constexpr auto ADC_CTRL = 37;
std::shared_ptr<tt::hal::power::PowerDevice> createPower() {
ChargeFromAdcVoltage::Configuration configuration;
// 2.0 ratio, but +0.11 added as display voltage sag compensation.
configuration.adcMultiplier = 2.11;
// Configure the ADC enable pin as an output
tt::hal::gpio::configure(ADC_CTRL, tt::hal::gpio::Mode::Output, false, false);
return std::make_shared<EstimatedPower>(configuration);
}
@@ -1,7 +0,0 @@
#pragma once
#include <memory>
#include <Tactility/hal/power/PowerDevice.h>
#include <driver/gpio.h>
std::shared_ptr<tt::hal::power::PowerDevice> createPower();
@@ -12,6 +12,8 @@ hardware.tinyUsb=true
hardware.esptoolFlashFreq=120M hardware.esptoolFlashFreq=120M
hardware.bluetooth=true hardware.bluetooth=true
dependencies.useDeprecatedHal=false
storage.userDataLocation=Internal storage.userDataLocation=Internal
display.size=0.96" display.size=0.96"
@@ -1,3 +1,5 @@
dependencies: dependencies:
- Platforms/platform-esp32 - Platforms/platform-esp32
- Drivers/ssd1306-module
- Drivers/button-control-module
dts: heltec,wifi-lora-32-v3.dts dts: heltec,wifi-lora-32-v3.dts
@@ -5,6 +5,11 @@
#include <tactility/bindings/esp32_wifi_pinned.h> #include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_adc_oneshot.h>
#include <tactility/bindings/battery_sense.h>
#include <tactility/bindings/gpio_hog.h>
#include <bindings/button_control.h>
#include <bindings/ssd1306.h>
/ { / {
compatible = "root"; compatible = "root";
@@ -25,11 +30,87 @@
gpio-count = <49>; gpio-count = <49>;
}; };
// Board power-enable pins. Must be asserted before the dependent devices below start, since
// devicetree devices are constructed and started during kernel_init(), earlier than the
// deprecated HAL's initBoot() used to run. gpio-hog nodes run in declaration order, so they
// must stay before their dependents.
oled_power_en {
// Active low (external pull-up on the board).
compatible = "gpio-hog";
pin = <&gpio0 36 GPIO_FLAG_NONE>;
mode = <GPIO_HOG_MODE_OUTPUT_LOW>;
};
adc_ctrl_en {
compatible = "gpio-hog";
pin = <&gpio0 37 GPIO_FLAG_NONE>;
mode = <GPIO_HOG_MODE_OUTPUT_LOW>;
};
i2c_internal { i2c_internal {
compatible = "espressif,esp32-i2c"; compatible = "espressif,esp32-i2c";
port = <I2C_NUM_0>; port = <I2C_NUM_0>;
clock-frequency = <200000>; clock-frequency = <200000>;
pin-sda = <&gpio0 17 GPIO_FLAG_NONE>; pin-sda = <&gpio0 17 GPIO_FLAG_NONE>;
pin-scl = <&gpio0 18 GPIO_FLAG_NONE>; pin-scl = <&gpio0 18 GPIO_FLAG_NONE>;
display0 {
compatible = "solomon,ssd1306";
reg = <0x3C>;
horizontal-resolution = <128>;
vertical-resolution = <64>;
pin-reset = <&gpio0 21 GPIO_FLAG_NONE>;
// oled_power_en above only just asserted the OLED's power rail moments earlier in
// the same kernel_init() pass - matches the deprecated HAL's old
// enableOledPower()'s explicit 500ms settle delay before touching the chip.
power-on-delay-ms = <500>;
// Vendor bring-up sequence, carried over unchanged from the deprecated HAL's old
// Ssd1306Display.cpp (its own comment: "esp_lcd_panel_init() ... doesn't configure
// correctly for Heltec V3!"). Each byte is its own SSD1306 command - see
// ssd1306-module's init-sequence binding property for the encoding.
init-sequence = [
0xAE // Display off while configuring
0xD5 0xF0 // Set oscillator frequency (~96 Hz) - must come early in the sequence
0xA8 0x3F // Set multiplex ratio (vertical-resolution - 1 = 64 - 1)
0xD3 0x00 // Set display offset
0x40 // Set display start line = 0
0x8D 0x14 // Enable charge pump - must be before memory mode
0x20 0x00 // Horizontal addressing mode
0xA1 // Segment remap (Heltec V3 orientation)
0xC8 // Scan direction reversed
0xDA 0x12 // COM pin config (alternate, for 64-row displays)
0x81 0xCF // Contrast
0xD9 0xF1 // Pre-charge period
0xDB 0x40 // VCOMH deselect level
0x21 0x00 0x7F // Column address range: 0 to (horizontal-resolution - 1)
0x22 0x00 0x07 // Page address range: 0 to 7 (for a 64-row display)
0xA7 // Display invert - required for this panel's wiring
0x2E // Stop scroll
0xAF // Display on
];
};
};
adc0 {
compatible = "espressif,esp32-adc-oneshot";
unit-id = <ADC_UNIT_1>;
channels = <ADC_CHANNEL_3 ADC_ATTEN_DB_12 ADC_BITWIDTH_DEFAULT>;
};
// Matches the deprecated HAL's old Power.cpp: ADC1 CH3 behind a 2:1 divider, with +0.11
// added as display voltage sag compensation (adcMultiplier = 2.11, so multiplier = 2110).
// reference-voltage-mv is the nominal full-scale value at ADC_ATTEN_DB_12 (battery-sense has
// no calibration path, unlike the old driver's raw-scaling formula).
battery-sense {
compatible = "battery-sense";
io-channel = <&adc0 0>;
reference-voltage-mv = <3300>;
multiplier = <2110>;
};
buttons {
compatible = "tactility,button-control";
pin-primary = <&gpio0 0 GPIO_FLAG_NONE>;
}; };
}; };
@@ -3,12 +3,10 @@
extern "C" { extern "C" {
static error_t start() { static error_t start() {
// Empty for now
return ERROR_NONE; return ERROR_NONE;
} }
static error_t stop() { static error_t stop() {
// Empty for now
return ERROR_NONE; return ERROR_NONE;
} }
+3 -3
View File
@@ -1,7 +1,7 @@
file(GLOB_RECURSE SOURCE_FILES Source/*.c*) file(GLOB_RECURSE SOURCE_FILES source/*.c*)
idf_component_register( idf_component_register(
SRCS ${SOURCE_FILES} SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source" INCLUDE_DIRS "source"
REQUIRES Tactility GDEQ031T10 TCA8418 BQ27220 driver REQUIRES Tactility GDEQ031T10 driver
) )
@@ -1,100 +0,0 @@
#include "devices/Display.h"
#include "devices/TdeckmaxKeyboard.h"
#include "devices/TdeckmaxPower.h"
#include <Bq27220.h>
#include <Tactility/hal/Configuration.h>
#include <tactility/check.h>
#include <tactility/delay.h>
#include <tactility/device.h>
#include <tactility/drivers/esp32_spi.h>
#include <tactility/drivers/gpio_controller.h>
using namespace tt::hal;
// Reset lines routed through the XL9555 IO expander (P-numbers from the vendor
// lib/TDeckMaxBoard/src/TDeckMaxBoard.h). Both are active low.
constexpr auto XL9555_PIN_TOUCH_RST = 7; // P07
constexpr auto XL9555_PIN_KEY_RST = 9; // P11
// Release the touch and keyboard reset lines held by the XL9555. Without this
// the touch controller may stay in a half-powered state and the keyboard's
// TCA8418 is held in reset, so neither responds. Mirrors the vendor factory
// firmware's XL9555 init (examples/factory/factory.ino).
static void initIoExpander() {
auto* xl9555 = device_find_by_name("xl9555");
if (xl9555 == nullptr) {
// Boot anyway; display works without the expander, but touch/keyboard won't.
return;
}
auto* touch_rst = gpio_descriptor_acquire(xl9555, XL9555_PIN_TOUCH_RST, GPIO_OWNER_GPIO);
auto* key_rst = gpio_descriptor_acquire(xl9555, XL9555_PIN_KEY_RST, GPIO_OWNER_GPIO);
if (touch_rst != nullptr) {
gpio_descriptor_set_flags(touch_rst, GPIO_FLAG_DIRECTION_OUTPUT);
gpio_descriptor_set_level(touch_rst, false);
}
if (key_rst != nullptr) {
gpio_descriptor_set_flags(key_rst, GPIO_FLAG_DIRECTION_OUTPUT);
gpio_descriptor_set_level(key_rst, false);
}
delay_millis(20);
// Release both resets and give the controllers time to boot before they're probed.
if (touch_rst != nullptr) {
gpio_descriptor_set_level(touch_rst, true);
gpio_descriptor_release(touch_rst);
}
if (key_rst != nullptr) {
gpio_descriptor_set_level(key_rst, true);
gpio_descriptor_release(key_rst);
}
delay_millis(60);
}
static bool initBoot() {
// The LoRa and SD chip-selects share the EPD's SPI bus but aren't wired up
// yet. spi0's start() already drives every cs-gpio high during kernel init;
// re-assert deselection before the EPD driver first transacts, as a cheap
// guard against either chip latching stray EPD command bytes (same pattern
// as the esp32_sdspi mount path).
auto* spi0 = device_find_by_name("spi0");
check(spi0 != nullptr);
esp32_spi_deselect_all_cs(spi0);
initIoExpander();
return true;
}
static DeviceVector createDevices() {
auto* i2c = device_find_by_name("i2c0");
check(i2c != nullptr);
// SD card is not created here: it's an espressif,esp32-sdspi node in the
// devicetree (sdcard@1 under spi0), started by Hal::init's mountAll().
DeviceVector devices = {
createDisplay()
};
auto keypad = std::make_shared<Tca8418>(i2c);
devices.push_back(keypad);
devices.push_back(std::make_shared<TdeckmaxKeyboard>(keypad));
// Battery metrics from the BQ27220 fuel gauge (0x55); power-off via the
// SY6970 charger (0x6A). Enables the launcher's power-off button.
auto gauge = std::make_shared<Bq27220>(i2c);
devices.push_back(gauge);
devices.push_back(std::make_shared<TdeckmaxPower>(gauge, i2c));
return devices;
}
extern const Configuration hardwareConfiguration = {
.initBoot = initBoot,
.createDevices = createDevices
};
@@ -1,225 +0,0 @@
#include "Cst66xxTouch.h"
#include <tactility/log.h>
#include <Tactility/app/App.h>
#include <tactility/drivers/gpio_controller.h>
#include <tactility/drivers/i2c_controller.h>
#include <freertos/FreeRTOS.h>
// Touch reset is wired to XL9555 P07 (active low).
static constexpr uint32_t XL9555_PIN_TOUCH_RST = 7;
constexpr auto* TAG = "CST66xx";
static constexpr TickType_t I2C_TIMEOUT = pdMS_TO_TICKS(20);
// The protocol uses 4-byte register addresses, taken from the vendor's
// lib/HynTouch/src/hyn_cst66xx.c.
// Normal-mode setup sequence (cst66xx_set_workmode, NOMAL_MODE).
static constexpr uint8_t CMD_DISABLE_LP_I2C[4] = {0xD0, 0x00, 0x04, 0x00};
static constexpr uint8_t CMD_NORMAL_A[4] = {0xD0, 0x00, 0x00, 0x00};
static constexpr uint8_t CMD_NORMAL_B[4] = {0xD0, 0x00, 0x0C, 0x00};
static constexpr uint8_t CMD_NORMAL_C[4] = {0xD0, 0x00, 0x01, 0x00};
// Read-point register: write these 4 bytes, then read the touch frame.
static constexpr uint8_t REG_READ_POINT[4] = {0xD0, 0x07, 0x00, 0x00};
// Acknowledge/clear after each read.
static constexpr uint8_t CMD_ACK[4] = {0xD0, 0x00, 0x02, 0xAB};
// Identity/config register (cst66xx_updata_tpinfo): read 50 bytes; buf[2]==0xCA
// && buf[3]==0xCA confirms a CST66xx.
static constexpr uint8_t REG_INFO[4] = {0xD0, 0x03, 0x00, 0x00};
static void setNormalMode(::Device* i2c, uint8_t address) {
i2c_controller_write(i2c, address, CMD_DISABLE_LP_I2C, sizeof(CMD_DISABLE_LP_I2C), I2C_TIMEOUT);
vTaskDelay(pdMS_TO_TICKS(1));
i2c_controller_write(i2c, address, CMD_DISABLE_LP_I2C, sizeof(CMD_DISABLE_LP_I2C), I2C_TIMEOUT);
i2c_controller_write(i2c, address, CMD_NORMAL_A, sizeof(CMD_NORMAL_A), I2C_TIMEOUT);
i2c_controller_write(i2c, address, CMD_NORMAL_B, sizeof(CMD_NORMAL_B), I2C_TIMEOUT);
i2c_controller_write(i2c, address, CMD_NORMAL_C, sizeof(CMD_NORMAL_C), I2C_TIMEOUT);
}
bool Cst66xxTouch::start() {
// Reset the controller (XL9555 P07, active low). The chip only re-initialises
// into normal reporting mode after a clean reset pulse.
auto* xl9555 = device_find_by_name("xl9555");
if (xl9555 != nullptr) {
auto* rst = gpio_descriptor_acquire(xl9555, XL9555_PIN_TOUCH_RST, GPIO_OWNER_GPIO);
if (rst != nullptr) {
gpio_descriptor_set_flags(rst, GPIO_FLAG_DIRECTION_OUTPUT);
gpio_descriptor_set_level(rst, false);
vTaskDelay(pdMS_TO_TICKS(10));
gpio_descriptor_set_level(rst, true);
gpio_descriptor_release(rst);
}
}
// The reset pulse exits boot mode; give the controller time to re-init.
vTaskDelay(pdMS_TO_TICKS(80));
// Put the controller into normal reporting mode and verify the identity. The
// first read after reset can miss, so retry like the vendor's
// cst66xx_updata_tpinfo (read 0xD0030000, expect buf[2]==buf[3]==0xCA).
auto* i2c = configuration.i2cController;
const uint8_t address = configuration.address;
bool confirmed = false;
for (int attempt = 0; attempt < 5 && !confirmed; ++attempt) {
setNormalMode(i2c, address);
uint8_t info[50] = {};
if (i2c_controller_write(i2c, address, REG_INFO, sizeof(REG_INFO), I2C_TIMEOUT) == ERROR_NONE &&
i2c_controller_read(i2c, address, info, sizeof(info), I2C_TIMEOUT) == ERROR_NONE &&
info[2] == 0xCA && info[3] == 0xCA) {
confirmed = true;
} else {
vTaskDelay(pdMS_TO_TICKS(10));
}
}
if (confirmed) {
LOG_I(TAG, "CST66xx initialised");
} else {
LOG_W(TAG, "CST66xx identity not confirmed (continuing)");
}
return true;
}
bool Cst66xxTouch::stop() {
return true;
}
bool Cst66xxTouch::readPoint(int16_t& x, int16_t& y) {
// CST66xx frame (cst66xx_report): write the read-point register, then read 9
// bytes. buf[2]=report type (0xFF=position/key), buf[3] low nibble = finger
// count, high nibble = key count. The first 5-byte slot (buf[4..8]) is a key
// slot when key count > 0, otherwise the first finger; further fingers, if
// any, follow at buf[index+...].
uint8_t buf[9] = {};
error_t err = i2c_controller_write(configuration.i2cController, configuration.address, REG_READ_POINT, sizeof(REG_READ_POINT), I2C_TIMEOUT);
if (err == ERROR_NONE) {
err = i2c_controller_read(configuration.i2cController, configuration.address, buf, sizeof(buf), I2C_TIMEOUT);
}
if (err != ERROR_NONE) {
return false;
}
// Acknowledge/clear the frame after every read.
i2c_controller_write(configuration.i2cController, configuration.address, CMD_ACK, sizeof(CMD_ACK), I2C_TIMEOUT);
const uint8_t reportType = buf[2];
const uint8_t fingerCount = buf[3] & 0x0F;
const uint8_t keyCount = (buf[3] & 0xF0) >> 4;
// Bezel touch-keys are reported in the first slot (buf[8]): low nibble = key
// id, high nibble = state (non-zero = pressed). The controller reports keys
// mutually exclusively with finger coordinates, so a key frame never carries
// a touch point.
if (reportType == 0xFF && keyCount > 0) {
const uint8_t keyByte = buf[8];
handleBezelKey(keyByte & 0x0F, (keyByte >> 4) != 0);
return false;
}
// No key in this frame: clear the latch so the next press fires once.
bezelKeyDown = false;
if (reportType != 0xFF || fingerCount < 1) {
return false;
}
// First finger's slot follows any key slots.
const uint8_t index = keyCount * 5;
const uint8_t event = buf[index + 8] >> 4; // 0 = up
if (event == 0) {
return false;
}
int16_t rawX = static_cast<int16_t>(buf[index + 4] | (static_cast<uint16_t>(buf[index + 7] & 0x0F) << 8));
int16_t rawY = static_cast<int16_t>(buf[index + 5] | (static_cast<uint16_t>(buf[index + 7] & 0xF0) << 4));
if (configuration.swapXy) {
std::swap(rawX, rawY);
}
if (configuration.mirrorX) {
rawX = static_cast<int16_t>(configuration.width - 1 - rawX);
}
if (configuration.mirrorY) {
rawY = static_cast<int16_t>(configuration.height - 1 - rawY);
}
x = rawX;
y = rawY;
return true;
}
// Bezel touch-key ids as reported by the CST66xx, left to right (confirmed on
// hardware). To re-map a button, change the action in the switch below — the
// mapping is also documented in NOTES.md.
static constexpr uint8_t BEZEL_KEY_HEART = 0; // left
static constexpr uint8_t BEZEL_KEY_SPEECH = 1; // centre
static constexpr uint8_t BEZEL_KEY_AIRPLANE = 2; // right
void Cst66xxTouch::handleBezelKey(uint8_t keyId, bool pressed) {
if (!pressed) {
bezelKeyDown = false;
return;
}
if (bezelKeyDown) {
return; // still held; the press edge was already handled
}
bezelKeyDown = true;
LOG_D(TAG, "bezel key %u", keyId);
// Bezel button -> navigation action. Edit these cases to customise:
switch (keyId) {
case BEZEL_KEY_HEART: // Back: stop the current app (no-op at the launcher root)
tt::app::stop();
break;
// Home (BEZEL_KEY_SPEECH) and Recents (BEZEL_KEY_AIRPLANE) are intentionally
// unbound: tt::app::start() always pushes a new instance onto the app
// stack, so repeated presses would keep growing it (Launcher/AppList
// instances never get cleaned up) and leak memory over time. The app
// stack has no API yet to collapse back to an existing instance instead
// of pushing a new one; wire these up once that exists.
default:
break;
}
}
void Cst66xxTouch::readCallback(lv_indev_t* indev, lv_indev_data_t* data) {
auto* self = static_cast<Cst66xxTouch*>(lv_indev_get_user_data(indev));
int16_t x;
int16_t y;
if (self->readPoint(x, y)) {
self->lastX = x;
self->lastY = y;
data->point.x = x;
data->point.y = y;
data->state = LV_INDEV_STATE_PRESSED;
} else {
// Report the release at the last known position.
data->point.x = self->lastX;
data->point.y = self->lastY;
data->state = LV_INDEV_STATE_RELEASED;
}
}
bool Cst66xxTouch::startLvgl(lv_display_t* display) {
if (indev != nullptr) {
return true;
}
indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
lv_indev_set_read_cb(indev, &readCallback);
lv_indev_set_display(indev, display);
lv_indev_set_user_data(indev, this);
return true;
}
bool Cst66xxTouch::stopLvgl() {
if (indev == nullptr) {
return true;
}
lv_indev_delete(indev);
indev = nullptr;
return true;
}
@@ -1,54 +0,0 @@
#pragma once
#include <Tactility/hal/touch/TouchDevice.h>
#include <tactility/device.h>
// Capacitive touch for the LilyGO T-Deck Max's Hynitron CST66xx controller at
// I2C 0x1A. (The vendor docs label it "CST328", but the vendor HynTouch driver
// probes cst66xx first and that is what answers here.) This is a minimal,
// single-touch port of the vendor's hyn_cst66xx.c protocol for LVGL pointer input.
class Cst66xxTouch final : public tt::hal::touch::TouchDevice {
public:
struct Configuration {
::Device* i2cController;
uint8_t address;
uint16_t width;
uint16_t height;
bool swapXy;
bool mirrorX;
bool mirrorY;
};
explicit Cst66xxTouch(const Configuration& configuration) : configuration(configuration) {}
std::string getName() const override { return "CST66xx"; }
std::string getDescription() const override { return "CST66xx I2C capacitive touch"; }
bool start() override;
bool stop() override;
bool supportsLvgl() const override { return true; }
bool startLvgl(lv_display_t* display) override;
bool stopLvgl() override;
lv_indev_t* getLvglIndev() override { return indev; }
bool supportsTouchDriver() override { return false; }
std::shared_ptr<tt::hal::touch::TouchDriver> getTouchDriver() override { return nullptr; }
private:
Configuration configuration;
lv_indev_t* indev = nullptr;
int16_t lastX = 0;
int16_t lastY = 0;
// The CST66xx also reports the three bezel touch-keys (heart / speech-bubble /
// paper-airplane). bezelKeyDown latches a press so we act once on the rising
// edge rather than every poll while the key is held.
bool bezelKeyDown = false;
bool readPoint(int16_t& x, int16_t& y);
void handleBezelKey(uint8_t keyId, bool pressed);
static void readCallback(lv_indev_t* indev, lv_indev_data_t* data);
};
@@ -1,57 +0,0 @@
#include "Display.h"
#include "Cst66xxTouch.h"
#include <Gdeq031t10Display.h>
#include <tactility/log.h>
#include <tactility/device.h>
constexpr auto* TAG = "TdeckmaxDisplay";
// Pins and I2C address from Xinyuan-LilyGO/T-Deck-MAX's
// lib/TDeckMaxBoard/src/TDeckMaxBoard.h and docs/pinmap.md.
constexpr auto EPD_SPI_HOST = SPI2_HOST;
constexpr auto EPD_PIN_CS = GPIO_NUM_34;
constexpr auto EPD_PIN_DC = GPIO_NUM_35;
constexpr auto EPD_PIN_RST = GPIO_NUM_9;
constexpr auto EPD_PIN_BUSY = GPIO_NUM_37;
constexpr uint16_t TOUCH_I2C_ADDRESS = 0x1A;
static std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
auto* i2c = device_find_by_name("i2c0");
if (i2c == nullptr) {
LOG_E(TAG, "i2c0 not found, booting without touch");
return nullptr;
}
// The touch reset line is released earlier via the XL9555 expander (see
// Configuration.cpp). Orientation flags are starting guesses; adjust after
// checking on hardware.
const Cst66xxTouch::Configuration configuration = {
.i2cController = i2c,
.address = TOUCH_I2C_ADDRESS,
.width = Gdeq031t10Display::WIDTH,
.height = Gdeq031t10Display::HEIGHT,
.swapXy = false,
.mirrorX = false,
.mirrorY = false,
};
return std::make_shared<Cst66xxTouch>(configuration);
}
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
auto configuration = std::make_unique<Gdeq031t10Display::Configuration>(
EPD_SPI_HOST,
EPD_PIN_CS,
EPD_PIN_DC,
EPD_PIN_RST,
EPD_PIN_BUSY,
createTouch(),
10'000'000,
// Default to a fast (~1s) refresh for the automatic ghost-clears so the
// full-screen flash they cause is as short as possible.
Gdeq031t10Display::RefreshMode::Fast
);
return std::make_shared<Gdeq031t10Display>(std::move(configuration));
}
@@ -1,5 +0,0 @@
#pragma once
#include <Tactility/hal/display/DisplayDevice.h>
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
@@ -1,249 +0,0 @@
#include "TdeckmaxKeyboard.h"
#include <tactility/log.h>
#include <driver/i2c.h>
#include <driver/gpio.h>
constexpr auto* TAG = "TdeckmaxKeyboard";
// Keyboard backlight (LED_PWM), GPIO42 on the T-Deck Max.
constexpr auto BACKLIGHT = GPIO_NUM_42;
constexpr auto KB_ROWS = 4;
constexpr auto KB_COLS = 10;
// Keymaps are written in the vendor's row/column orientation
// (Xinyuan-LilyGO/T-Deck-MAX examples/factory/peri_keypad.cpp). The TCA8418
// columns are wired in reverse, so the vendor reads keymap[row][9 - col]. We do
// the same reversal in processKeyboard(), which keeps these tables a direct,
// verifiable copy of the vendor layout.
//
// Modifier keys (ALT and SYM) are blanked here ('\0') and handled by position:
// ALT -> shift/uppercase, at vendor column 0 of row 2
// SYM -> symbol layer, at vendor column 8 of row 3
// Lowercase (base) layer
static constexpr char keymap_lc[KB_ROWS][KB_COLS] = {
{'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'},
{'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', LV_KEY_BACKSPACE},
{'\0', 'z', 'x', 'c', 'v', 'b', 'n', 'm', '$', LV_KEY_ENTER},
{'\0', '\0', '\0', '\0', '\0', LV_KEY_PREV, '0', ' ', '\0', LV_KEY_NEXT}
};
// Uppercase layer (ALT held or caps toggled)
static constexpr char keymap_uc[KB_ROWS][KB_COLS] = {
{'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P'},
{'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', LV_KEY_BACKSPACE},
{'\0', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '$', LV_KEY_ENTER},
{'\0', '\0', '\0', '\0', '\0', LV_KEY_PREV, '0', ' ', '\0', LV_KEY_NEXT}
};
// Symbol layer (SYM held). The T-Deck Max silkscreen for the symbol layer is
// not documented in the vendor sources, so these are sensible defaults; adjust
// to match the printed keys once verified on hardware.
static constexpr char keymap_sy[KB_ROWS][KB_COLS] = {
{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'},
{'@', '#', '+', '-', '*', '/', '(', ')', '_', LV_KEY_BACKSPACE},
{'\0', '!', '?', ';', ':', '\'', '"', ',', '.', LV_KEY_ENTER},
{'\0', '\0', '\0', '\0', '\0', LV_KEY_PREV, '0', ' ', '\0', LV_KEY_NEXT}
};
void TdeckmaxKeyboard::readCallback(lv_indev_t* indev, lv_indev_data_t* data) {
auto keyboard = static_cast<TdeckmaxKeyboard*>(lv_indev_get_user_data(indev));
// Emit the release edge for the previous key first: LVGL's keypad handling
// only delivers a key on a RELEASED->PRESSED transition, so two different
// keys reported PRESSED back-to-back would swallow the second one.
if (keyboard->lastKeyNeedsRelease) {
keyboard->lastKeyNeedsRelease = false;
data->key = keyboard->lastKey;
data->state = LV_INDEV_STATE_RELEASED;
data->continue_reading = uxQueueMessagesWaiting(keyboard->queue) > 0;
return;
}
char keypress = 0;
if (xQueueReceive(keyboard->queue, &keypress, 0) == pdPASS) {
keyboard->lastKey = static_cast<uint32_t>(keypress);
keyboard->lastKeyNeedsRelease = true;
data->key = keyboard->lastKey;
data->state = LV_INDEV_STATE_PRESSED;
// Drain the whole queue in this read cycle so a typing burst lands in
// one render pass (and thus a single e-paper refresh).
data->continue_reading = true;
} else {
data->key = 0;
data->state = LV_INDEV_STATE_RELEASED;
}
}
void TdeckmaxKeyboard::processKeyboard() {
bool anykey_pressed = false;
// Each update() pops one event from the TCA8418's FIFO. Drain it fully per
// poll (bounded, in case of a misbehaving bus) so a fast typing burst isn't
// throttled to one event per poll period. Handling each event as a discrete
// press/release edge also means a key held across another key's press (fast
// typing rollover) no longer re-sends its character.
for (int drained = 0; drained < 16 && keypad->update(); drained++) {
if (keypad->released_key_count > 0) {
// Release event: only modifier state cares about releases.
auto row = keypad->released_list[0].row;
auto vcol = (KB_COLS - 1) - keypad->released_list[0].col;
if ((row == 2) && (vcol == 0)) {
shiftPressed = false; // ALT key
}
if ((row == 3) && (vcol == 8)) {
symPressed = false; // SYM key
}
} else if (keypad->pressed_key_count > 0) {
// Press event: the key that caused it is the newest list entry.
auto row = keypad->pressed_list[keypad->pressed_key_count - 1].row;
auto vcol = (KB_COLS - 1) - keypad->pressed_list[keypad->pressed_key_count - 1].col;
anykey_pressed = true;
if ((row == 2) && (vcol == 0)) {
shiftPressed = true; // ALT key
} else if ((row == 3) && (vcol == 8)) {
symPressed = true; // SYM key
} else {
char chr;
if (symPressed) {
chr = keymap_sy[row][vcol];
} else if (shiftPressed || capToggle) {
chr = keymap_uc[row][vcol];
} else {
chr = keymap_lc[row][vcol];
}
// Non-blocking: this runs on the periodic input timer, so a full
// queue (reader stalled) must drop the keystroke rather than
// stall this callback and the timer's other periodic work.
if (chr != '\0' && xQueueSend(queue, &chr, 0) != pdPASS) {
LOG_W(TAG, "Keyboard queue full, dropping keystroke");
}
}
if ((symPressed && shiftPressed) && capToggleArmed) {
capToggle = !capToggle;
capToggleArmed = false;
}
}
if ((!symPressed && !shiftPressed) && !capToggleArmed) {
capToggleArmed = true;
}
}
if (anykey_pressed) {
makeBacklightImpulse();
}
}
bool TdeckmaxKeyboard::startLvgl(lv_display_t* display) {
backlightOkay = initBacklight(BACKLIGHT, 30000, LEDC_TIMER_0, LEDC_CHANNEL_1);
keypad->init(KB_ROWS, KB_COLS);
assert(inputTimer == nullptr);
inputTimer = std::make_unique<tt::Timer>(tt::Timer::Type::Periodic, tt::kernel::millisToTicks(20), [this] {
processKeyboard();
});
assert(backlightImpulseTimer == nullptr);
backlightImpulseTimer = std::make_unique<tt::Timer>(tt::Timer::Type::Periodic, tt::kernel::millisToTicks(50), [this] {
processBacklightImpulse();
});
kbHandle = lv_indev_create();
lv_indev_set_type(kbHandle, LV_INDEV_TYPE_KEYPAD);
lv_indev_set_read_cb(kbHandle, &readCallback);
lv_indev_set_display(kbHandle, display);
lv_indev_set_user_data(kbHandle, this);
inputTimer->start();
backlightImpulseTimer->start();
return true;
}
bool TdeckmaxKeyboard::stopLvgl() {
assert(inputTimer);
inputTimer->stop();
inputTimer = nullptr;
assert(backlightImpulseTimer);
backlightImpulseTimer->stop();
backlightImpulseTimer = nullptr;
lv_indev_delete(kbHandle);
kbHandle = nullptr;
return true;
}
bool TdeckmaxKeyboard::isAttached() const {
return i2c_controller_has_device_at_address(keypad->getController(), keypad->getAddress(), 100) == ERROR_NONE;
}
bool TdeckmaxKeyboard::initBacklight(gpio_num_t pin, uint32_t frequencyHz, ledc_timer_t timer, ledc_channel_t channel) {
backlightPin = pin;
backlightTimer = timer;
backlightChannel = channel;
ledc_timer_config_t ledc_timer = {
.speed_mode = LEDC_LOW_SPEED_MODE,
.duty_resolution = LEDC_TIMER_8_BIT,
.timer_num = backlightTimer,
.freq_hz = frequencyHz,
.clk_cfg = LEDC_AUTO_CLK,
.deconfigure = false
};
if (ledc_timer_config(&ledc_timer) != ESP_OK) {
LOG_E(TAG, "Backlight timer config failed");
return false;
}
ledc_channel_config_t ledc_channel = {
.gpio_num = backlightPin,
.speed_mode = LEDC_LOW_SPEED_MODE,
.channel = backlightChannel,
.intr_type = LEDC_INTR_DISABLE,
.timer_sel = backlightTimer,
.duty = 0,
.hpoint = 0,
.sleep_mode = LEDC_SLEEP_MODE_NO_ALIVE_NO_PD,
.flags = {
.output_invert = 0
}
};
if (ledc_channel_config(&ledc_channel) != ESP_OK) {
LOG_E(TAG, "Backlight channel config failed");
return false;
}
return true;
}
bool TdeckmaxKeyboard::setBacklightDuty(uint8_t duty) {
if (!backlightOkay) {
LOG_E(TAG, "Backlight not ready");
return false;
}
return (ledc_set_duty(LEDC_LOW_SPEED_MODE, backlightChannel, duty) == ESP_OK) &&
(ledc_update_duty(LEDC_LOW_SPEED_MODE, backlightChannel) == ESP_OK);
}
void TdeckmaxKeyboard::makeBacklightImpulse() {
backlightImpulseDuty = 255;
setBacklightDuty(backlightImpulseDuty);
}
void TdeckmaxKeyboard::processBacklightImpulse() {
if (backlightImpulseDuty > 0) {
backlightImpulseDuty--;
setBacklightDuty(backlightImpulseDuty);
}
}
@@ -1,69 +0,0 @@
#pragma once
#include <Tactility/hal/keyboard/KeyboardDevice.h>
#include <Tactility/Timer.h>
#include <Tca8418.h>
#include <driver/gpio.h>
#include <driver/ledc.h>
#include <freertos/queue.h>
// QWERTY keyboard for the LilyGO T-Deck (Pro) Max.
//
// The keyboard is a TCA8418 4x10 matrix scanner on the shared I2C bus at 0x34.
// The keyboard reset line is wired through the XL9555 IO expander (P11), so it
// must be released before this device is started (see Configuration.cpp).
//
// Pins, address and keymap are taken from Xinyuan-LilyGO/T-Deck-MAX:
// examples/factory/peri_keypad.cpp and lib/TDeckMaxBoard/src/TDeckMaxBoard.h.
class TdeckmaxKeyboard final : public tt::hal::keyboard::KeyboardDevice {
lv_indev_t* kbHandle = nullptr;
gpio_num_t backlightPin = GPIO_NUM_NC;
ledc_timer_t backlightTimer;
ledc_channel_t backlightChannel;
bool backlightOkay = false;
int backlightImpulseDuty = 0;
QueueHandle_t queue = nullptr;
// LVGL only registers a key on a RELEASED->PRESSED edge, so readCallback
// alternates press/release per queued key; this tracks the pending release.
uint32_t lastKey = 0;
bool lastKeyNeedsRelease = false;
std::shared_ptr<Tca8418> keypad;
std::unique_ptr<tt::Timer> inputTimer;
std::unique_ptr<tt::Timer> backlightImpulseTimer;
bool shiftPressed = false;
bool symPressed = false;
bool capToggle = false;
bool capToggleArmed = true;
bool initBacklight(gpio_num_t pin, uint32_t frequencyHz, ledc_timer_t timer, ledc_channel_t channel);
void processKeyboard();
void processBacklightImpulse();
static void readCallback(lv_indev_t* indev, lv_indev_data_t* data);
public:
explicit TdeckmaxKeyboard(const std::shared_ptr<Tca8418>& tca) : keypad(tca) {
queue = xQueueCreate(20, sizeof(char));
}
~TdeckmaxKeyboard() override {
vQueueDelete(queue);
}
std::string getName() const override { return "T-Deck Max Keyboard"; }
std::string getDescription() const override { return "T-Deck Max TCA8418 I2C matrix keyboard"; }
bool startLvgl(lv_display_t* display) override;
bool stopLvgl() override;
bool isAttached() const override;
lv_indev_t* getLvglIndev() override { return kbHandle; }
bool setBacklightDuty(uint8_t duty);
void makeBacklightImpulse();
};
@@ -1,139 +0,0 @@
#include "TdeckmaxPower.h"
#include <tactility/log.h>
#include <tactility/drivers/i2c_controller.h>
constexpr auto* TAG = "TdeckmaxPower";
// SY6970 charger (newer T-Deck Max revisions; older boards use BQ25896 @ 0x6B).
// Register map is BQ25896-compatible for everything used here (see NOTES.md).
static constexpr uint8_t SY6970_ADDRESS = 0x6A;
static constexpr uint8_t SY6970_REG_03 = 0x03; // bit4 CHG_CONFIG: charge enable
static constexpr uint8_t SY6970_REG_04 = 0x04; // bits[6:0] ICHG: charge current, 64 mA/LSB
static constexpr uint8_t SY6970_REG_06 = 0x06; // bits[7:2] VREG: charge voltage, 3840 mV + n*16 mV
static constexpr uint8_t SY6970_REG_07 = 0x07; // bits[5:4] WATCHDOG: I2C watchdog timer
static constexpr uint8_t SY6970_REG_09 = 0x09;
static constexpr uint8_t SY6970_REG_0B = 0x0B; // read-only status
static constexpr uint8_t SY6970_CHG_CONFIG = 1 << 4;
static constexpr uint8_t SY6970_BATFET_DIS = 1 << 5; // Force BATFET off = ship mode
static constexpr TickType_t I2C_TIMEOUT = pdMS_TO_TICKS(50);
// Charge parameters from the vendor factory firmware (XPowersLib SY6970 init):
// watchdog off, 4288 mV target, 1024 mA fast-charge current. The I2C watchdog
// must be disabled first, otherwise the chip reverts custom register values to
// defaults after the watchdog expires. Input current limit (REG00) is left at
// the hardware PSEL/ILIM default. Non-fatal: charging still works on chip
// defaults if any write fails.
bool TdeckmaxPower::configureCharger() const {
if (i2c_controller_register8_reset_bits(i2c, SY6970_ADDRESS, SY6970_REG_07, 0x30, I2C_TIMEOUT) != ERROR_NONE) {
LOG_E(TAG, "Failed to disable SY6970 watchdog");
return false;
}
uint8_t reg06 = 0;
if (i2c_controller_register8_get(i2c, SY6970_ADDRESS, SY6970_REG_06, &reg06, I2C_TIMEOUT) != ERROR_NONE ||
i2c_controller_register8_set(i2c, SY6970_ADDRESS, SY6970_REG_06, (reg06 & 0x03) | (28 << 2), I2C_TIMEOUT) != ERROR_NONE) { // 3840 + 28*16 = 4288 mV
LOG_E(TAG, "Failed to set SY6970 charge voltage");
return false;
}
uint8_t reg04 = 0;
if (i2c_controller_register8_get(i2c, SY6970_ADDRESS, SY6970_REG_04, &reg04, I2C_TIMEOUT) != ERROR_NONE ||
i2c_controller_register8_set(i2c, SY6970_ADDRESS, SY6970_REG_04, (reg04 & 0x80) | 0x10, I2C_TIMEOUT) != ERROR_NONE) { // 16 * 64 = 1024 mA
LOG_E(TAG, "Failed to set SY6970 charge current");
return false;
}
LOG_I(TAG, "SY6970 configured: 4288 mV / 1024 mA, watchdog off");
return true;
}
bool TdeckmaxPower::isAllowedToCharge() const {
uint8_t reg03 = 0;
if (i2c_controller_register8_get(i2c, SY6970_ADDRESS, SY6970_REG_03, &reg03, I2C_TIMEOUT) != ERROR_NONE) {
return false;
}
return (reg03 & SY6970_CHG_CONFIG) != 0;
}
void TdeckmaxPower::setAllowedToCharge(bool canCharge) {
error_t result;
if (canCharge) {
result = i2c_controller_register8_set_bits(i2c, SY6970_ADDRESS, SY6970_REG_03, SY6970_CHG_CONFIG, I2C_TIMEOUT);
} else {
result = i2c_controller_register8_reset_bits(i2c, SY6970_ADDRESS, SY6970_REG_03, SY6970_CHG_CONFIG, I2C_TIMEOUT);
}
if (result != ERROR_NONE) {
LOG_E(TAG, "Failed to set SY6970 charge enable");
}
}
bool TdeckmaxPower::supportsMetric(MetricType type) const {
switch (type) {
using enum MetricType;
case IsCharging:
case Current:
case BatteryVoltage:
case ChargeLevel:
return true;
default:
return false;
}
}
bool TdeckmaxPower::getMetric(MetricType type, MetricData& data) {
uint16_t u16 = 0;
int16_t s16 = 0;
switch (type) {
using enum MetricType;
case IsCharging: {
// REG0B CHRG_STAT bits[4:3]: 0 = not charging, 1 = pre-charge,
// 2 = fast charge, 3 = charge done. Only 1|2 count as charging —
// "done" must not (XPowersLib had this inverted once). Fall back to
// the fuel gauge's discharge flag if the charger doesn't answer.
uint8_t reg0b = 0;
if (i2c_controller_register8_get(i2c, SY6970_ADDRESS, SY6970_REG_0B, &reg0b, I2C_TIMEOUT) == ERROR_NONE) {
uint8_t charge_status = (reg0b >> 3) & 0x03;
data.valueAsBool = charge_status == 1 || charge_status == 2;
return true;
}
Bq27220::BatteryStatus status;
if (gauge->getBatteryStatus(status)) {
data.valueAsBool = !status.reg.DSG;
return true;
}
return false;
}
case Current:
if (gauge->getCurrent(s16)) {
data.valueAsInt32 = s16;
return true;
}
return false;
case BatteryVoltage:
if (gauge->getVoltage(u16)) {
data.valueAsUint32 = u16;
return true;
}
return false;
case ChargeLevel:
if (gauge->getStateOfCharge(u16)) {
data.valueAsUint8 = u16;
return true;
}
return false;
default:
return false;
}
}
void TdeckmaxPower::powerOff() {
// Ship mode: force the charger's BATFET off (REG09 bit5). Mirrors the vendor
// XPowersLib PowersSY6970::shutdown(). Note: this only fully powers the board
// down when running on battery with USB unplugged.
LOG_I(TAG, "Power off (SY6970 BATFET_DIS)");
if (i2c_controller_register8_set_bits(i2c, SY6970_ADDRESS, SY6970_REG_09, SY6970_BATFET_DIS, pdMS_TO_TICKS(50)) != ERROR_NONE) {
LOG_E(TAG, "Failed to write SY6970 shutdown register");
}
}
@@ -1,42 +0,0 @@
#pragma once
#include "Tactility/hal/power/PowerDevice.h"
#include <Bq27220.h>
#include <tactility/device.h>
#include <memory>
using tt::hal::power::PowerDevice;
// Power device for the LilyGO T-Deck Max. Battery metrics come from the BQ27220
// fuel gauge (I2C 0x55); charging status, charge control and power-off (ship
// mode, REG09 BATFET_DIS) use the SY6970 charger. Newer T-Deck Max revisions
// ship the SY6970 (0x6A) instead of the older BQ25896 (0x6B); this board has
// the SY6970 (confirmed by I2C scan). The two are register-compatible for
// everything used here.
class TdeckmaxPower final : public PowerDevice {
std::shared_ptr<Bq27220> gauge;
::Device* i2c; // i2c0 controller, used to reach the SY6970 charger
bool configureCharger() const;
public:
TdeckmaxPower(const std::shared_ptr<Bq27220>& gauge, ::Device* i2c) : gauge(gauge), i2c(i2c) {
configureCharger();
}
std::string getName() const final { return "T-Deck Max Power"; }
std::string getDescription() const final { return "Battery metrics (BQ27220), SY6970 charging and power-off"; }
bool supportsMetric(MetricType type) const override;
bool getMetric(MetricType type, MetricData& data) override;
bool supportsChargeControl() const override { return true; }
bool isAllowedToCharge() const override;
void setAllowedToCharge(bool canCharge) override;
bool supportsPowerOff() const override { return true; }
void powerOff() override;
};
+4
View File
@@ -1,4 +1,8 @@
dependencies: dependencies:
- Platforms/platform-esp32 - Platforms/platform-esp32
- Drivers/xl9555-module - Drivers/xl9555-module
- Drivers/cst66xx-module
- Drivers/tca8418-module
- Drivers/sy6970-module
- Drivers/bq27220-module
dts: lilygo,tdeck-max.dts dts: lilygo,tdeck-max.dts
@@ -7,7 +7,13 @@
#include <tactility/bindings/esp32_i2c_master.h> #include <tactility/bindings/esp32_i2c_master.h>
#include <tactility/bindings/esp32_spi.h> #include <tactility/bindings/esp32_spi.h>
#include <tactility/bindings/esp32_sdspi.h> #include <tactility/bindings/esp32_sdspi.h>
#include <tactility/bindings/esp32_pwm_ledc.h>
#include <tactility/bindings/pwm_backlight.h>
#include <bindings/xl9555.h> #include <bindings/xl9555.h>
#include <bindings/cst66xx.h>
#include <bindings/tca8418.h>
#include <bindings/sy6970.h>
#include <bindings/bq27220.h>
// Reference: https://github.com/Xinyuan-LilyGO/T-Deck-MAX // Reference: https://github.com/Xinyuan-LilyGO/T-Deck-MAX
// lib/TDeckMaxBoard/src/TDeckMaxBoard.h, docs/pinmap.md // lib/TDeckMaxBoard/src/TDeckMaxBoard.h, docs/pinmap.md
@@ -40,6 +46,88 @@
compatible = "xlsemi,xl9555"; compatible = "xlsemi,xl9555";
reg = <0x20>; reg = <0x20>;
}; };
// Vendor docs label this chip "CST328", but the vendor HynTouch driver probes
// cst66xx first and that is what answers on this board.
touch {
compatible = "hynitron,cst66xx";
reg = <0x1A>;
x-max = <240>;
y-max = <320>;
// Reset routed through the XL9555 IO expander (P07, active low).
pin-reset = <&xl9555 7 GPIO_FLAG_NONE>;
};
// TCA8418 4x10 matrix scanner. Keymaps are written in the vendor's row/column
// orientation (Xinyuan-LilyGO/T-Deck-MAX examples/factory/peri_keypad.cpp); the
// TCA8418 columns are wired in reverse of that order, hence reverse-columns.
// ALT (shift/uppercase) is row 2 col 0; SYM (symbol layer) is row 3 col 8.
keyboard {
compatible = "ti,tca8418";
reg = <0x34>;
rows = <4>;
columns = <10>;
reverse-columns;
keymap-lc = [
113 119 101 114 116 121 117 105 111 112 // q w e r t y u i o p
97 115 100 102 103 104 106 107 108 8 // a s d f g h j k l BACKSPACE
0 122 120 99 118 98 110 109 36 10 // z x c v b n m $ ENTER
0 0 0 0 0 11 48 32 0 9 // PREV 0 SPC NEXT
];
keymap-uc = [
81 87 69 82 84 89 85 73 79 80 // Q W E R T Y U I O P
65 83 68 70 71 72 74 75 76 8 // A S D F G H J K L BACKSPACE
0 90 88 67 86 66 78 77 36 10 // Z X C V B N M $ ENTER
0 0 0 0 0 11 48 32 0 9 // PREV 0 SPC NEXT
];
keymap-sy = [
49 50 51 52 53 54 55 56 57 48 // 1 2 3 4 5 6 7 8 9 0
64 35 43 45 42 47 40 41 95 8 // @ # + - * / ( ) _ BACKSPACE
0 33 63 59 58 39 34 44 46 10 // ! ? ; : ' " , . ENTER
0 0 0 0 0 11 48 32 0 9 // PREV 0 SPC NEXT
];
shift-row = <2>;
shift-col = <0>;
sym-row = <3>;
sym-col = <8>;
// Reset routed through the XL9555 IO expander (P11, active low).
pin-reset = <&xl9555 9 GPIO_FLAG_NONE>;
};
// Battery metrics from the BQ27220 fuel gauge; power-off via the SY6970 charger.
// Newer T-Deck Max revisions ship the SY6970 (0x6A) instead of the older BQ25896
// (0x6B); this board has the SY6970 (confirmed by I2C scan). The two are
// register-compatible for everything used here.
sy6970 {
compatible = "silergy,sy6970";
reg = <0x6A>;
charge-voltage-mv = <4288>;
charge-current-ma = <1024>;
};
bq27220 {
compatible = "ti,bq27220";
reg = <0x55>;
};
};
// Keyboard backlight (LED_PWM), GPIO42. The keypress-triggered flash/decay effect
// from the pre-migration driver isn't reproduced here - kernel drivers don't have a
// home for that kind of UI policy (same reasoning as the touch driver's dropped bezel
// keys). Off by default; app code can drive brightness via the generic pwm-backlight
// API if desired.
keyboard_backlight_pwm {
compatible = "espressif,esp32-pwm-ledc";
pin = <&gpio0 42 GPIO_FLAG_NONE>;
period-ns = <33333>;
ledc-timer = <0>;
ledc-channel = <0>;
};
keyboard_backlight {
compatible = "pwm-backlight";
status = "disabled";
pwm = <&keyboard_backlight_pwm>;
}; };
spi0 { spi0 {
@@ -0,0 +1,35 @@
#include "devices/Display.h"
#include <Tactility/hal/Configuration.h>
#include <tactility/check.h>
#include <tactility/device.h>
#include <tactility/drivers/esp32_spi.h>
using namespace tt::hal;
static bool initBoot() {
// The LoRa and SD chip-selects share the EPD's SPI bus but aren't wired up
// yet. spi0's start() already drives every cs-gpio high during kernel init;
// re-assert deselection before the EPD driver first transacts, as a cheap
// guard against either chip latching stray EPD command bytes (same pattern
// as the esp32_sdspi mount path).
auto* spi0 = device_find_by_name("spi0");
check(spi0 != nullptr);
esp32_spi_deselect_all_cs(spi0);
return true;
}
static DeviceVector createDevices() {
// Touch, keyboard and battery/charging are kernel drivers (cst66xx, tca8418, sy6970,
// bq27220 - see the .dts), started independently of this HAL layer. Only the EPD
// display remains on the deprecated HAL.
return DeviceVector {
createDisplay()
};
}
extern const Configuration hardwareConfiguration = {
.initBoot = initBoot,
.createDevices = createDevices
};
@@ -0,0 +1,29 @@
#include "Display.h"
#include <Gdeq031t10Display.h>
// Pins from Xinyuan-LilyGO/T-Deck-MAX's lib/TDeckMaxBoard/src/TDeckMaxBoard.h and docs/pinmap.md.
constexpr auto EPD_SPI_HOST = SPI2_HOST;
constexpr auto EPD_PIN_CS = GPIO_NUM_34;
constexpr auto EPD_PIN_DC = GPIO_NUM_35;
constexpr auto EPD_PIN_RST = GPIO_NUM_9;
constexpr auto EPD_PIN_BUSY = GPIO_NUM_37;
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
// Touch is a kernel driver (hynitron,cst66xx - see the .dts), discovered and fed to LVGL
// independently of this display, so no touch device is passed into the configuration here.
auto configuration = std::make_unique<Gdeq031t10Display::Configuration>(
EPD_SPI_HOST,
EPD_PIN_CS,
EPD_PIN_DC,
EPD_PIN_RST,
EPD_PIN_BUSY,
nullptr,
10'000'000,
// Default to a fast (~1s) refresh for the automatic ghost-clears so the
// full-screen flash they cause is as short as possible.
Gdeq031t10Display::RefreshMode::Fast
);
return std::make_shared<Gdeq031t10Display>(std::move(configuration));
}
+2 -3
View File
@@ -1,7 +1,6 @@
file(GLOB_RECURSE SOURCE_FILES Source/*.c*) file(GLOB_RECURSE SOURCE_FILES source/*.c*)
idf_component_register( idf_component_register(
SRCS ${SOURCE_FILES} SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source" REQUIRES TactilityKernel driver
REQUIRES Tactility ButtonControl PwmBacklight EstimatedPower ST7789-i8080
) )
@@ -1,22 +0,0 @@
#include "devices/Display.h"
#include "devices/Power.h"
#include <Tactility/hal/Configuration.h>
#include <ButtonControl.h>
bool initBoot();
using namespace tt::hal;
static std::vector<std::shared_ptr<tt::hal::Device>> createDevices() {
return {
createPower(),
createDisplay(),
ButtonControl::createTwoButtonControl(0, 14),
};
}
extern const Configuration hardwareConfiguration = {
.initBoot = initBoot,
.createDevices = createDevices
};
@@ -1,44 +0,0 @@
#include "devices/Power.h"
#include "devices/Display.h"
#include "PwmBacklight.h"
#include <Tactility/SystemEvents.h>
#include <Tactility/TactilityCore.h>
#include <tactility/log.h>
#define TAG "tdisplay-s3"
static bool powerOn() {
gpio_config_t power_signal_config = {
.pin_bit_mask = BIT64(TDISPLAY_S3_POWERON_GPIO),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
if (gpio_config(&power_signal_config) != ESP_OK) {
return false;
}
if (gpio_set_level(TDISPLAY_S3_POWERON_GPIO, 1) != ESP_OK) {
return false;
}
return true;
}
bool initBoot() {
LOG_I(TAG, "Powering on");
if (!powerOn()) {
LOG_E(TAG, "Power on failed");
return false;
}
if (!driver::pwmbacklight::init(DISPLAY_BL, 30000)) {
LOG_E(TAG, "Backlight init failed");
return false;
}
return true;
}
@@ -1,30 +0,0 @@
#include "Display.h"
#include "St7789i8080Display.h"
#include "PwmBacklight.h"
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
// Create configuration
auto config = St7789i8080Display::Configuration(
DISPLAY_CS, // CS
DISPLAY_DC, // DC
DISPLAY_WR, // WR
DISPLAY_RD, // RD
{ DISPLAY_I80_D0, DISPLAY_I80_D1, DISPLAY_I80_D2, DISPLAY_I80_D3,
DISPLAY_I80_D4, DISPLAY_I80_D5, DISPLAY_I80_D6, DISPLAY_I80_D7 }, // D0..D7
DISPLAY_RST, // RST
DISPLAY_BL // BL
);
// Set resolution explicitly
config.horizontalResolution = DISPLAY_HORIZONTAL_RESOLUTION;
config.verticalResolution = DISPLAY_VERTICAL_RESOLUTION;
config.backlightDutyFunction = driver::pwmbacklight::setBacklightDuty;
// Adjust other settings as needed
config.gapX = 35; // ST7789 has a 35 pixel gap on X axis
config.pixelClockFrequency = 10 * 1000 * 1000; // 10MHz for better stability
auto display = std::make_shared<St7789i8080Display>(config);
return display;
}
@@ -1,26 +0,0 @@
#pragma once
#include <Tactility/hal/display/DisplayDevice.h>
#include <driver/gpio.h>
class St7789i8080Display;
constexpr auto DISPLAY_CS = GPIO_NUM_6;
constexpr auto DISPLAY_DC = GPIO_NUM_7;
constexpr auto DISPLAY_WR = GPIO_NUM_8;
constexpr auto DISPLAY_RD = GPIO_NUM_9;
constexpr auto DISPLAY_RST = GPIO_NUM_5;
constexpr auto DISPLAY_BL = GPIO_NUM_38;
constexpr auto DISPLAY_I80_D0 = GPIO_NUM_39;
constexpr auto DISPLAY_I80_D1 = GPIO_NUM_40;
constexpr auto DISPLAY_I80_D2 = GPIO_NUM_41;
constexpr auto DISPLAY_I80_D3 = GPIO_NUM_42;
constexpr auto DISPLAY_I80_D4 = GPIO_NUM_45;
constexpr auto DISPLAY_I80_D5 = GPIO_NUM_46;
constexpr auto DISPLAY_I80_D6 = GPIO_NUM_47;
constexpr auto DISPLAY_I80_D7 = GPIO_NUM_48;
constexpr auto DISPLAY_HORIZONTAL_RESOLUTION = 170;
constexpr auto DISPLAY_VERTICAL_RESOLUTION = 320;
// Factory function for registration
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
@@ -1,12 +0,0 @@
#include "Power.h"
#include <ChargeFromAdcVoltage.h>
#include <EstimatedPower.h>
std::shared_ptr<tt::hal::power::PowerDevice> createPower() {
ChargeFromAdcVoltage::Configuration configuration;
// 2.0 ratio, but +.11 added as display voltage sag compensation.
configuration.adcMultiplier = 2.11;
return std::make_shared<EstimatedPower>(configuration);
}
@@ -1,9 +0,0 @@
#pragma once
#include <memory>
#include <Tactility/hal/power/PowerDevice.h>
#include <driver/gpio.h>
constexpr auto TDISPLAY_S3_POWERON_GPIO = GPIO_NUM_15;
std::shared_ptr<tt::hal::power::PowerDevice> createPower();
@@ -1,23 +0,0 @@
#include <tactility/module.h>
extern "C" {
static error_t start() {
// Empty for now
return ERROR_NONE;
}
static error_t stop() {
// Empty for now
return ERROR_NONE;
}
struct Module lilygo_tdisplay_s3_module = {
.name = "lilygo-tdisplay-s3",
.start = start,
.stop = stop,
.symbols = nullptr,
.internal = nullptr
};
}
@@ -13,6 +13,8 @@ hardware.tinyUsb=true
hardware.esptoolFlashFreq=120M hardware.esptoolFlashFreq=120M
hardware.bluetooth=true hardware.bluetooth=true
dependencies.useDeprecatedHal=false
storage.userDataLocation=Internal storage.userDataLocation=Internal
display.size=1.9" display.size=1.9"
@@ -1,3 +1,5 @@
dependencies: dependencies:
- Platforms/platform-esp32 - Platforms/platform-esp32
- Drivers/st7789-i8080-module
- Drivers/button-control-module
dts: lilygo,tdisplay-s3.dts dts: lilygo,tdisplay-s3.dts
@@ -4,6 +4,13 @@
#include <tactility/bindings/esp32_ble.h> #include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_wifi_pinned.h> #include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_adc_oneshot.h>
#include <tactility/bindings/battery_sense.h>
#include <tactility/bindings/esp32_i8080.h>
#include <tactility/bindings/esp32_pwm_ledc.h>
#include <tactility/bindings/pwm_backlight.h>
#include <bindings/st7789_i8080.h>
#include <bindings/button_control.h>
/ { / {
compatible = "root"; compatible = "root";
@@ -23,4 +30,72 @@
compatible = "espressif,esp32-gpio"; compatible = "espressif,esp32-gpio";
gpio-count = <49>; gpio-count = <49>;
}; };
// Board peripheral power rail (GPIO15). The kernel_init() module-start pass (source/module.cpp)
// asserts it before any devicetree device is constructed/started - see that file's start().
adc0 {
compatible = "espressif,esp32-adc-oneshot";
unit-id = <ADC_UNIT_1>;
channels = <ADC_CHANNEL_3 ADC_ATTEN_DB_12 ADC_BITWIDTH_DEFAULT>;
};
// Matches the deprecated HAL's old Power.cpp (EstimatedPower/ChargeFromAdcVoltage default
// config): ADC1 CH3, 2:1 divider with +0.11 display voltage sag compensation
// (adcMultiplier = 2.11, so multiplier = 2110).
battery-sense {
compatible = "battery-sense";
io-channel = <&adc0 0>;
reference-voltage-mv = <3300>;
multiplier = <2110>;
};
display_backlight_pwm {
compatible = "espressif,esp32-pwm-ledc";
pin = <&gpio0 38 GPIO_FLAG_NONE>;
period-ns = <33333>;
ledc-timer = <0>;
ledc-channel = <0>;
};
display_backlight {
compatible = "pwm-backlight";
// Off by default so display power-on won't show the screen from before the last power loss.
// The display backlight is turned on during the boot process.
status = "disabled";
pwm = <&display_backlight_pwm>;
};
i8080_0 {
compatible = "espressif,esp32-i8080";
pin-dc = <&gpio0 7 GPIO_FLAG_NONE>;
pin-wr = <&gpio0 8 GPIO_FLAG_NONE>;
pin-rd = <&gpio0 9 GPIO_FLAG_NONE>;
pin-d0 = <&gpio0 39 GPIO_FLAG_NONE>;
pin-d1 = <&gpio0 40 GPIO_FLAG_NONE>;
pin-d2 = <&gpio0 41 GPIO_FLAG_NONE>;
pin-d3 = <&gpio0 42 GPIO_FLAG_NONE>;
pin-d4 = <&gpio0 45 GPIO_FLAG_NONE>;
pin-d5 = <&gpio0 46 GPIO_FLAG_NONE>;
pin-d6 = <&gpio0 47 GPIO_FLAG_NONE>;
pin-d7 = <&gpio0 48 GPIO_FLAG_NONE>;
// horizontal-resolution * (vertical-resolution / 10 partial buffer) * 2 bytes/pixel
max-transfer-bytes = <10880>;
cs-gpios = <&gpio0 6 GPIO_FLAG_NONE>;
display@0 {
compatible = "sitronix,st7789-i8080";
horizontal-resolution = <170>;
vertical-resolution = <320>;
gap-x = <35>;
pixel-clock-hz = <10000000>;
pin-reset = <&gpio0 5 GPIO_FLAG_NONE>;
backlight = <&display_backlight>;
};
};
buttons {
compatible = "tactility,button-control";
pin-primary = <&gpio0 0 GPIO_FLAG_NONE>;
pin-secondary = <&gpio0 14 GPIO_FLAG_NONE>;
};
}; };
@@ -0,0 +1,45 @@
#include <tactility/module.h>
#include <driver/gpio.h>
// Board peripheral power-enable pin (display, backlight, etc). Must be asserted before the
// devicetree devices below start - kernel_init() starts all dts_modules[] (this one included)
// before constructing any dts_devices[], so doing it here in start() runs early enough.
constexpr auto POWER_ON_PIN = GPIO_NUM_15;
extern "C" {
static error_t start() {
gpio_config_t power_signal_config = {
.pin_bit_mask = BIT64(POWER_ON_PIN),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
if (gpio_config(&power_signal_config) != ESP_OK) {
return ERROR_RESOURCE;
}
if (gpio_set_level(POWER_ON_PIN, 1) != ESP_OK) {
return ERROR_RESOURCE;
}
return ERROR_NONE;
}
static error_t stop() {
// Empty for now
return ERROR_NONE;
}
struct Module lilygo_tdisplay_s3_module = {
.name = "lilygo-tdisplay-s3",
.start = start,
.stop = stop,
.symbols = nullptr,
.internal = nullptr
};
}
+2 -3
View File
@@ -1,7 +1,6 @@
file(GLOB_RECURSE SOURCE_FILES Source/*.c*) file(GLOB_RECURSE SOURCE_FILES source/*.c*)
idf_component_register( idf_component_register(
SRCS ${SOURCE_FILES} SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source" REQUIRES TactilityKernel driver
REQUIRES Tactility ButtonControl esp_lvgl_port ST7789 PwmBacklight driver
) )
@@ -1,23 +0,0 @@
#include "devices/Display.h"
#include <PwmBacklight.h>
#include <Tactility/hal/Configuration.h>
#include <ButtonControl.h>
using namespace tt::hal;
static bool initBoot() {
return driver::pwmbacklight::init(LCD_PIN_BACKLIGHT);
}
static std::vector<std::shared_ptr<tt::hal::Device>> createDevices() {
return {
createDisplay(),
ButtonControl::createTwoButtonControl(35, 0)
};
}
extern const Configuration hardwareConfiguration = {
.initBoot = initBoot,
.createDevices = createDevices
};
@@ -1,32 +0,0 @@
#include "Display.h"
#include <PwmBacklight.h>
#include <St7789Display.h>
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
St7789Display::Configuration panel_configuration = {
.horizontalResolution = LCD_HORIZONTAL_RESOLUTION,
.verticalResolution = LCD_VERTICAL_RESOLUTION,
.gapX = 52,
.gapY = 40,
.swapXY = false,
.mirrorX = false,
.mirrorY = false,
.invertColor = true,
.bufferSize = LCD_BUFFER_SIZE,
.touch = nullptr,
.backlightDutyFunction = driver::pwmbacklight::setBacklightDuty,
.resetPin = LCD_PIN_RESET,
.lvglSwapBytes = true
};
auto spi_configuration = std::make_shared<St7789Display::SpiConfiguration>(St7789Display::SpiConfiguration {
.spiHostDevice = LCD_SPI_HOST,
.csPin = LCD_PIN_CS,
.dcPin = LCD_PIN_DC,
.pixelClockFrequency = 62'500'000,
.transactionQueueDepth = 10
});
return std::make_shared<St7789Display>(panel_configuration, spi_configuration);
}
@@ -1,18 +0,0 @@
#pragma once
#include <Tactility/hal/display/DisplayDevice.h>
#include <memory>
#include <driver/gpio.h>
#include <driver/spi_common.h>
constexpr auto LCD_SPI_HOST = SPI2_HOST;
constexpr auto LCD_PIN_CS = GPIO_NUM_5;
constexpr auto LCD_PIN_DC = GPIO_NUM_16;
constexpr auto LCD_PIN_RESET = GPIO_NUM_23;
constexpr auto LCD_PIN_BACKLIGHT = GPIO_NUM_4;
constexpr auto LCD_HORIZONTAL_RESOLUTION = 135;
constexpr auto LCD_VERTICAL_RESOLUTION = 240;
constexpr auto LCD_BUFFER_HEIGHT = LCD_VERTICAL_RESOLUTION / 6;
constexpr auto LCD_BUFFER_SIZE = LCD_HORIZONTAL_RESOLUTION * LCD_BUFFER_HEIGHT;
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
@@ -10,6 +10,8 @@ hardware.flashSize=16MB
hardware.spiRam=false hardware.spiRam=false
hardware.esptoolFlashFreq=80M hardware.esptoolFlashFreq=80M
dependencies.useDeprecatedHal=false
storage.userDataLocation=Internal storage.userDataLocation=Internal
display.size=1.14" display.size=1.14"
+2
View File
@@ -1,3 +1,5 @@
dependencies: dependencies:
- Platforms/platform-esp32 - Platforms/platform-esp32
- Drivers/st7789-module
- Drivers/button-control-module
dts: lilygo,tdisplay.dts dts: lilygo,tdisplay.dts
+37 -4
View File
@@ -3,9 +3,11 @@
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_wifi_pinned.h> #include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h> #include <tactility/bindings/esp32_spi.h>
#include <tactility/bindings/display_placeholder.h> #include <tactility/bindings/esp32_pwm_ledc.h>
#include <tactility/bindings/pwm_backlight.h>
#include <bindings/st7789.h>
#include <bindings/button_control.h>
/ { / {
compatible = "root"; compatible = "root";
@@ -21,6 +23,22 @@
gpio-count = <49>; gpio-count = <49>;
}; };
display_backlight_pwm {
compatible = "espressif,esp32-pwm-ledc";
pin = <&gpio0 4 GPIO_FLAG_NONE>;
period-ns = <1953125>;
ledc-timer = <0>;
ledc-channel = <0>;
};
display_backlight {
compatible = "pwm-backlight";
// Off by default so display power-on won't show the screen from before the last power loss.
// The display backlight is turned on during the boot process.
status = "disabled";
pwm = <&display_backlight_pwm>;
};
spi0 { spi0 {
compatible = "espressif,esp32-spi"; compatible = "espressif,esp32-spi";
host = <SPI2_HOST>; host = <SPI2_HOST>;
@@ -28,8 +46,23 @@
pin-mosi = <&gpio0 19 GPIO_FLAG_NONE>; pin-mosi = <&gpio0 19 GPIO_FLAG_NONE>;
pin-sclk = <&gpio0 18 GPIO_FLAG_NONE>; pin-sclk = <&gpio0 18 GPIO_FLAG_NONE>;
display { display@0 {
compatible = "display-placeholder"; compatible = "sitronix,st7789";
horizontal-resolution = <135>;
vertical-resolution = <240>;
gap-x = <52>;
gap-y = <40>;
invert-color;
pixel-clock-hz = <62500000>;
pin-dc = <&gpio0 16 GPIO_FLAG_NONE>;
pin-reset = <&gpio0 23 GPIO_FLAG_NONE>;
backlight = <&display_backlight>;
}; };
}; };
buttons {
compatible = "tactility,button-control";
pin-primary = <&gpio0 35 GPIO_FLAG_NONE>;
pin-secondary = <&gpio0 0 GPIO_FLAG_NONE>;
};
}; };
+2 -3
View File
@@ -1,7 +1,6 @@
file(GLOB_RECURSE SOURCE_FILES Source/*.c*) file(GLOB_RECURSE SOURCE_FILES source/*.c*)
idf_component_register( idf_component_register(
SRCS ${SOURCE_FILES} SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source" REQUIRES TactilityKernel driver
REQUIRES Tactility ST7735 PwmBacklight driver
) )
@@ -1,18 +0,0 @@
#include "devices/Display.h"
#include <Tactility/hal/Configuration.h>
bool initBoot();
using namespace tt::hal;
static std::vector<std::shared_ptr<tt::hal::Device>> createDevices() {
return {
createDisplay()
};
}
extern const Configuration hardwareConfiguration = {
.initBoot = initBoot,
.createDevices = createDevices
};
-16
View File
@@ -1,16 +0,0 @@
#include "PwmBacklight.h"
#include <Tactility/service/gps/GpsService.h>
#include <Tactility/TactilityCore.h>
#include <tactility/log.h>
constexpr auto* TAG = "T-Dongle S3";
bool initBoot() {
if (!driver::pwmbacklight::init(GPIO_NUM_38, 12000)) {
LOG_E(TAG, "Backlight init failed");
return false;
}
return true;
}
@@ -1,36 +0,0 @@
#include "Display.h"
#include <PwmBacklight.h>
#include <St7735Display.h>
#define LCD_SPI_HOST SPI2_HOST
#define LCD_PIN_CS GPIO_NUM_4
#define LCD_PIN_DC GPIO_NUM_2
#define LCD_PIN_RESET GPIO_NUM_1
#define LCD_HORIZONTAL_RESOLUTION 80
#define LCD_VERTICAL_RESOLUTION 160
#define LCD_SPI_TRANSFER_HEIGHT (LCD_VERTICAL_RESOLUTION / 4)
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
auto configuration = std::make_unique<St7735Display::Configuration>(
LCD_SPI_HOST,
LCD_PIN_CS,
LCD_PIN_DC,
LCD_PIN_RESET,
80,
160,
nullptr,
false,
false,
false,
true,
0,
26,
1
);
configuration->backlightDutyFunction = driver::pwmbacklight::setBacklightDuty;
auto display = std::make_shared<St7735Display>(std::move(configuration));
return std::reinterpret_pointer_cast<tt::hal::display::DisplayDevice>(display);
}

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