Device migrations and driver improvements (#576)
This commit is contained in:
committed by
GitHub
parent
2fbc44466a
commit
a3fda9ad8f
@@ -2,5 +2,5 @@ file(GLOB_RECURSE SOURCE_FILES source/*.c*)
|
|||||||
|
|
||||||
idf_component_register(
|
idf_component_register(
|
||||||
SRCS ${SOURCE_FILES}
|
SRCS ${SOURCE_FILES}
|
||||||
REQUIRES TactilityKernel axp192-module
|
REQUIRES TactilityKernel
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -21,3 +21,5 @@ display.dpi=200
|
|||||||
cdn.warningMessage=This board implementation concerns the original Core2 hardware and **not** the v1.1 variant
|
cdn.warningMessage=This board implementation concerns the original Core2 hardware and **not** the v1.1 variant
|
||||||
|
|
||||||
lvgl.colorDepth=16
|
lvgl.colorDepth=16
|
||||||
|
|
||||||
|
sdkconfig.CONFIG_CODEC_DUMMY_SUPPORT=y
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
- Platforms/platform-esp32
|
- Platforms/platform-esp32
|
||||||
|
- Drivers/audio-stream-module
|
||||||
|
- Drivers/pdm-mic-module
|
||||||
|
- Drivers/dummy-i2s-amp-module
|
||||||
- Drivers/mpu6886-module
|
- Drivers/mpu6886-module
|
||||||
- Drivers/bm8563-module
|
- Drivers/bm8563-module
|
||||||
- Drivers/axp192-module
|
- Drivers/axp192-module
|
||||||
- Drivers/ft6x36-module
|
- Drivers/ft5x06-module
|
||||||
- Drivers/ili9341-module
|
- Drivers/ili9341-module
|
||||||
dts: m5stack,core2.dts
|
dts: m5stack,core2.dts
|
||||||
|
|||||||
@@ -6,14 +6,16 @@
|
|||||||
#include <tactility/bindings/esp32_grove.h>
|
#include <tactility/bindings/esp32_grove.h>
|
||||||
#include <tactility/bindings/esp32_i2c.h>
|
#include <tactility/bindings/esp32_i2c.h>
|
||||||
#include <tactility/bindings/esp32_i2s.h>
|
#include <tactility/bindings/esp32_i2s.h>
|
||||||
|
#include <tactility/bindings/esp32_sdspi.h>
|
||||||
#include <tactility/bindings/esp32_spi.h>
|
#include <tactility/bindings/esp32_spi.h>
|
||||||
#include <tactility/bindings/esp32_uart.h>
|
#include <tactility/bindings/esp32_uart.h>
|
||||||
#include <bindings/mpu6886.h>
|
#include <bindings/mpu6886.h>
|
||||||
#include <bindings/bm8563.h>
|
#include <bindings/bm8563.h>
|
||||||
#include <bindings/axp192.h>
|
#include <bindings/axp192.h>
|
||||||
#include <bindings/ft6x36.h>
|
#include <bindings/ft5x06.h>
|
||||||
#include <bindings/ili9341.h>
|
#include <bindings/ili9341.h>
|
||||||
#include <tactility/bindings/esp32_sdspi.h>
|
#include <bindings/dummy_i2s_amp.h>
|
||||||
|
#include <bindings/pdm_mic.h>
|
||||||
|
|
||||||
// Reference: https://docs.m5stack.com/en/core/Core2
|
// Reference: https://docs.m5stack.com/en/core/Core2
|
||||||
/ {
|
/ {
|
||||||
@@ -50,10 +52,23 @@
|
|||||||
axp192 {
|
axp192 {
|
||||||
compatible = "x-powers,axp192";
|
compatible = "x-powers,axp192";
|
||||||
reg = <0x34>;
|
reg = <0x34>;
|
||||||
|
dcdc1-enable; // ESP32 + peripherals main 3V3 rail - must stay on
|
||||||
|
dcdc1-voltage = <3300>;
|
||||||
|
dcdc3-enable; // LCD backlight
|
||||||
|
dcdc3-voltage = <3300>;
|
||||||
|
ldo2-enable; // LCD logic + SD card
|
||||||
|
ldo2-voltage = <3300>;
|
||||||
|
// ldo3 (vibration motor) intentionally left disabled
|
||||||
|
exten-enable; // 5V boost (speaker amp etc.)
|
||||||
|
gpio1-pwm; // GPIO1 as PWM1 output, drives the notification LED
|
||||||
|
gpio1-pwm1-duty-cycle = <255>; // 255 = LED off
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// FT6x36 driver doesn't reliably work when power cycling,
|
||||||
|
// or when the driver was powering on with its own power supply (not USB-C).
|
||||||
|
// The driver would not find the device and init would fail.
|
||||||
touch {
|
touch {
|
||||||
compatible = "focaltech,ft6x36";
|
compatible = "focaltech,ft5x06";
|
||||||
reg = <0x38>;
|
reg = <0x38>;
|
||||||
x-max = <320>;
|
x-max = <320>;
|
||||||
y-max = <240>;
|
y-max = <240>;
|
||||||
@@ -96,8 +111,6 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// NS4168: Speaker and microphone
|
|
||||||
// TODO: Init microphone via I2C: https://github.com/m5stack/M5Unified/blob/a6256725481f1bc366655fa48cf03b6095e30ad1/src/M5Unified.cpp#L391C19-L391C44
|
|
||||||
i2s0 {
|
i2s0 {
|
||||||
compatible = "espressif,esp32-i2s";
|
compatible = "espressif,esp32-i2s";
|
||||||
port = <I2S_NUM_0>;
|
port = <I2S_NUM_0>;
|
||||||
@@ -106,4 +119,15 @@
|
|||||||
pin-data-out = <&gpio0 2 GPIO_FLAG_NONE>;
|
pin-data-out = <&gpio0 2 GPIO_FLAG_NONE>;
|
||||||
pin-data-in = <&gpio0 34 GPIO_FLAG_NONE>;
|
pin-data-in = <&gpio0 34 GPIO_FLAG_NONE>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
speaker0 {
|
||||||
|
compatible = "nsiway,ns4168";
|
||||||
|
i2s = <&i2s0>;
|
||||||
|
};
|
||||||
|
|
||||||
|
mic0 {
|
||||||
|
compatible = "generic,spm1423";
|
||||||
|
i2s = <&i2s0>;
|
||||||
|
channels = <1>;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,47 +1,15 @@
|
|||||||
#include <drivers/axp192.h>
|
#include <tactility/error.h>
|
||||||
|
|
||||||
#include <tactility/check.h>
|
|
||||||
#include <tactility/device.h>
|
|
||||||
#include <tactility/device_listener.h>
|
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
static void configure_axp192(Device* axp192) {
|
// AXP192 rail/GPIO1 bring-up is now devicetree-configured (see m5stack,core2.dts's axp192 node
|
||||||
check(axp192_set_rail_voltage(axp192, AXP192_RAIL_LDO2, 3300) == ERROR_NONE); // LCD + SD
|
// and axp192-module's start()), replacing what used to be done by hand here via a
|
||||||
check(axp192_set_rail_voltage(axp192, AXP192_RAIL_DCDC3, 3300) == ERROR_NONE); // LCD backlight
|
// DEVICE_EVENT_STARTED device_listener.
|
||||||
|
Module m5stack_core2_module = {
|
||||||
check(axp192_set_rail_enabled(axp192, AXP192_RAIL_LDO2, true) == ERROR_NONE);
|
|
||||||
check(axp192_set_rail_enabled(axp192, AXP192_RAIL_LDO3, false) == ERROR_NONE); // VIB_MOTOR stop
|
|
||||||
check(axp192_set_rail_enabled(axp192, AXP192_RAIL_DCDC3, true) == ERROR_NONE);
|
|
||||||
|
|
||||||
check(axp192_set_pwm1_duty_cycle(axp192, 255) == ERROR_NONE); // PWM 255 (LED off)
|
|
||||||
check(axp192_set_gpio1_pwm1_output(axp192) == ERROR_NONE); // GPIO1: PWM
|
|
||||||
}
|
|
||||||
|
|
||||||
static void on_device_event(Device* device, DeviceEvent event, void* context) {
|
|
||||||
(void)context;
|
|
||||||
if (event == DEVICE_EVENT_STARTED && strcmp(device->name, "axp192") == 0) {
|
|
||||||
configure_axp192(device);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t start() {
|
|
||||||
device_listener_add(on_device_event, nullptr);
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t stop() {
|
|
||||||
device_listener_remove(on_device_event);
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Module m5stack_core2_module = {
|
|
||||||
.name = "m5stack-core2",
|
.name = "m5stack-core2",
|
||||||
.start = start,
|
.start = [] -> error_t { return ERROR_NONE; },
|
||||||
.stop = stop,
|
.stop = [] -> error_t { return ERROR_NONE; },
|
||||||
.symbols = nullptr,
|
.symbols = nullptr,
|
||||||
.internal = nullptr
|
.internal = nullptr
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
- Platforms/platform-esp32
|
- Platforms/platform-esp32
|
||||||
- Drivers/bmi270-module
|
- Drivers/audio-stream-module
|
||||||
- Drivers/bm8563-module
|
- Drivers/aw88298-module
|
||||||
- Drivers/axp2101-module
|
|
||||||
- Drivers/aw9523b-module
|
- Drivers/aw9523b-module
|
||||||
|
- Drivers/axp2101-module
|
||||||
|
- Drivers/bm8563-module
|
||||||
|
- Drivers/bmi270-module
|
||||||
|
- Drivers/es7210-module
|
||||||
- Drivers/ft5x06-module
|
- Drivers/ft5x06-module
|
||||||
- Drivers/ili9341-module
|
- Drivers/ili9341-module
|
||||||
dts: m5stack,cores3.dts
|
dts: m5stack,cores3.dts
|
||||||
|
|||||||
@@ -9,15 +9,16 @@
|
|||||||
#include <tactility/bindings/esp32_i2s.h>
|
#include <tactility/bindings/esp32_i2s.h>
|
||||||
#include <tactility/bindings/esp32_spi.h>
|
#include <tactility/bindings/esp32_spi.h>
|
||||||
#include <tactility/bindings/esp32_uart.h>
|
#include <tactility/bindings/esp32_uart.h>
|
||||||
#include <bindings/bmi270.h>
|
#include <tactility/bindings/gpio_hog.h>
|
||||||
#include <bindings/bm8563.h>
|
#include <bindings/aw88298.h>
|
||||||
|
#include <bindings/aw9523b.h>
|
||||||
#include <bindings/axp2101.h>
|
#include <bindings/axp2101.h>
|
||||||
#include <bindings/axp2101_backlight.h>
|
#include <bindings/axp2101_backlight.h>
|
||||||
#include <bindings/aw9523b.h>
|
#include <bindings/bmi270.h>
|
||||||
|
#include <bindings/bm8563.h>
|
||||||
|
#include <bindings/es7210.h>
|
||||||
#include <bindings/ft5x06.h>
|
#include <bindings/ft5x06.h>
|
||||||
#include <bindings/ili9341.h>
|
#include <bindings/ili9341.h>
|
||||||
#include <tactility/bindings/esp32_sdspi.h>
|
|
||||||
#include <tactility/bindings/gpio_hog.h>
|
|
||||||
|
|
||||||
// SD card is not supported because of a hardware problem:
|
// SD card is not supported because of a hardware problem:
|
||||||
// The display uses pin 35 as input (MISO), but the SPI bus (and SD card) uses the same pin as DC (output).
|
// The display uses pin 35 as input (MISO), but the SPI bus (and SD card) uses the same pin as DC (output).
|
||||||
@@ -42,6 +43,18 @@
|
|||||||
gpio-count = <49>;
|
gpio-count = <49>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
i2s0 {
|
||||||
|
// Note: M5Unified sets the following for speaker: magnification = 2, oversampling = 1
|
||||||
|
// Note: M5Unified sets the following for microphone: magnification = 4
|
||||||
|
compatible = "espressif,esp32-i2s";
|
||||||
|
port = <I2S_NUM_0>;
|
||||||
|
pin-bclk = <&gpio0 34 GPIO_FLAG_NONE>;
|
||||||
|
pin-ws = <&gpio0 33 GPIO_FLAG_NONE>;
|
||||||
|
pin-data-out = <&gpio0 13 GPIO_FLAG_NONE>;
|
||||||
|
pin-data-in = <&gpio0 14 GPIO_FLAG_NONE>;
|
||||||
|
pin-mclk = <&gpio0 0 GPIO_FLAG_NONE>;
|
||||||
|
};
|
||||||
|
|
||||||
i2c_internal {
|
i2c_internal {
|
||||||
compatible = "espressif,esp32-i2c";
|
compatible = "espressif,esp32-i2c";
|
||||||
port = <I2C_NUM_0>;
|
port = <I2C_NUM_0>;
|
||||||
@@ -61,18 +74,55 @@
|
|||||||
|
|
||||||
// P0_0 = touch reset, P0_1 = bus-out enable, P0_2 = AW88298 reset,
|
// P0_0 = touch reset, P0_1 = bus-out enable, P0_2 = AW88298 reset,
|
||||||
// P0_4 = SD card switch, P1_1 = LCD reset, P1_7 = boost enable (SY7088)
|
// P0_4 = SD card switch, P1_1 = LCD reset, P1_7 = boost enable (SY7088)
|
||||||
aw9523b: aw9523b {
|
aw9523b {
|
||||||
compatible = "awinic,aw9523b";
|
compatible = "awinic,aw9523b";
|
||||||
reg = <0x58>;
|
reg = <0x58>;
|
||||||
|
|
||||||
|
// Bus-out enable, AW88298 (audio amp) reset, SD card power switch and boost enable
|
||||||
|
// (SY7088) are AW9523B pins with no owning peripheral driver yet.
|
||||||
|
aw9523_bus_out_enable {
|
||||||
|
compatible = "gpio-hog";
|
||||||
|
pin = <&aw9523b 1 GPIO_FLAG_NONE>;
|
||||||
|
mode = <GPIO_HOG_MODE_OUTPUT_HIGH>;
|
||||||
|
};
|
||||||
|
|
||||||
|
aw9523_sdcard_switch {
|
||||||
|
compatible = "gpio-hog";
|
||||||
|
pin = <&aw9523b 4 GPIO_FLAG_NONE>;
|
||||||
|
mode = <GPIO_HOG_MODE_INPUT>;
|
||||||
|
};
|
||||||
|
|
||||||
|
aw9523_boost_enable {
|
||||||
|
compatible = "gpio-hog";
|
||||||
|
pin = <&aw9523b 15 GPIO_FLAG_NONE>;
|
||||||
|
mode = <GPIO_HOG_MODE_OUTPUT_HIGH>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
aw88298 {
|
||||||
|
compatible = "awinic,aw88298";
|
||||||
|
reg = <0x36>;
|
||||||
|
i2s = <&i2s0>;
|
||||||
|
pin-reset = <&aw9523b 2 GPIO_FLAG_NONE>;
|
||||||
};
|
};
|
||||||
|
|
||||||
axp2101: axp2101 {
|
axp2101: axp2101 {
|
||||||
compatible = "x-powers,axp2101";
|
compatible = "x-powers,axp2101";
|
||||||
reg = <0x34>;
|
reg = <0x34>;
|
||||||
|
aldo1-millivolt = <1800>;
|
||||||
|
aldo1-enabled;
|
||||||
|
aldo2-millivolt = <3300>;
|
||||||
|
aldo2-enabled;
|
||||||
|
aldo3-millivolt = <3300>;
|
||||||
|
aldo3-enabled;
|
||||||
|
aldo4-millivolt = <3300>;
|
||||||
|
aldo4-enabled;
|
||||||
|
bldo1-enabled;
|
||||||
|
bldo2-enabled;
|
||||||
|
|
||||||
// LCD backlight (DLDO1). Raw register codes [20,28] map to [2500,3300]mV;
|
// LCD backlight (DLDO1). Raw register codes [20,28] map to [2500,3300]mV;
|
||||||
// below 2500mV the backlight was considered "too dark" to be useful.
|
// below 2500mV the backlight was considered "too dark" to be useful.
|
||||||
backlight {
|
display_backlight {
|
||||||
compatible = "axp2101-backlight";
|
compatible = "axp2101-backlight";
|
||||||
ldo = <AXP2101_DLDO1>;
|
ldo = <AXP2101_DLDO1>;
|
||||||
min-millivolt = <2500>;
|
min-millivolt = <2500>;
|
||||||
@@ -87,32 +137,21 @@
|
|||||||
y-max = <240>;
|
y-max = <240>;
|
||||||
pin-reset = <&aw9523b 0 GPIO_FLAG_NONE>;
|
pin-reset = <&aw9523b 0 GPIO_FLAG_NONE>;
|
||||||
};
|
};
|
||||||
};
|
|
||||||
|
|
||||||
// Bus-out enable, AW88298 (audio amp) reset, SD card power switch and boost enable
|
es7210 {
|
||||||
// (SY7088) are AW9523B pins with no owning peripheral driver yet.
|
compatible = "everest,es7210";
|
||||||
aw9523_bus_out_enable {
|
reg = <0x40>;
|
||||||
compatible = "gpio-hog";
|
i2s = <&i2s0>;
|
||||||
pin = <&aw9523b 1 GPIO_FLAG_NONE>;
|
// Only MIC1/MIC2 are wired to real capsules (see schematic); MIC3/MIC4
|
||||||
mode = <GPIO_HOG_MODE_OUTPUT_HIGH>;
|
// slots carry AEC_P/AEC_N reference signal, not microphone audio.
|
||||||
};
|
mic-mask = <3>;
|
||||||
|
// M5Unified applies a 4x post-gain "magnification" for this exact mic
|
||||||
aw9523_aw88298_reset {
|
// wiring (see M5Unified.cpp _mic_data_cb_cores3): the ES7210's own
|
||||||
compatible = "gpio-hog";
|
// hardware ADC gain (already near-max via the default 90% input volume
|
||||||
pin = <&aw9523b 2 GPIO_FLAG_NONE>;
|
// setting, ~34dB of its 0-37.5dB range) still isn't enough for these
|
||||||
mode = <GPIO_HOG_MODE_OUTPUT_HIGH>;
|
// capsules on their own.
|
||||||
};
|
input-gain-percent = <400>;
|
||||||
|
};
|
||||||
aw9523_sdcard_switch {
|
|
||||||
compatible = "gpio-hog";
|
|
||||||
pin = <&aw9523b 4 GPIO_FLAG_NONE>;
|
|
||||||
mode = <GPIO_HOG_MODE_OUTPUT_HIGH>;
|
|
||||||
};
|
|
||||||
|
|
||||||
aw9523_boost_enable {
|
|
||||||
compatible = "gpio-hog";
|
|
||||||
pin = <&aw9523b 15 GPIO_FLAG_NONE>;
|
|
||||||
mode = <GPIO_HOG_MODE_OUTPUT_HIGH>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
port_a: grove0 {
|
port_a: grove0 {
|
||||||
@@ -163,20 +202,8 @@
|
|||||||
pixel-clock-hz = <40000000>;
|
pixel-clock-hz = <40000000>;
|
||||||
pin-dc = <&gpio0 35 GPIO_FLAG_NONE>;
|
pin-dc = <&gpio0 35 GPIO_FLAG_NONE>;
|
||||||
pin-reset = <&aw9523b 9 GPIO_FLAG_NONE>;
|
pin-reset = <&aw9523b 9 GPIO_FLAG_NONE>;
|
||||||
|
backlight = <&display_backlight>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Enable speaker via ES7210 I2C: https://github.com/m5stack/M5Unified/blob/a6256725481f1bc366655fa48cf03b6095e30ad1/src/M5Unified.cpp#L417
|
|
||||||
// TODO: Enable microphone via ES7210 I2C: https://github.com/m5stack/M5Unified/blob/a6256725481f1bc366655fa48cf03b6095e30ad1/src/M5Unified.cpp#L616
|
|
||||||
i2s0 {
|
|
||||||
// Note: M5Unified sets the following for speaker: magnification = 2, oversampling = 1
|
|
||||||
// Note: M5Unified sets the following for microphone: magnification = 4
|
|
||||||
compatible = "espressif,esp32-i2s";
|
|
||||||
port = <I2S_NUM_0>;
|
|
||||||
pin-bclk = <&gpio0 34 GPIO_FLAG_NONE>;
|
|
||||||
pin-ws = <&gpio0 33 GPIO_FLAG_NONE>;
|
|
||||||
pin-data-out = <&gpio0 13 GPIO_FLAG_NONE>;
|
|
||||||
pin-data-in = <&gpio0 14 GPIO_FLAG_NONE>;
|
|
||||||
pin-mclk = <&gpio0 0 GPIO_FLAG_NONE>;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,53 +1,14 @@
|
|||||||
#include <drivers/axp2101.h>
|
#include <tactility/error.h>
|
||||||
|
|
||||||
#include <tactility/check.h>
|
|
||||||
#include <tactility/device.h>
|
|
||||||
#include <tactility/device_listener.h>
|
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
// M5Stack CoreS3 AXP2101 rail setup, ported from the board's old deprecated-HAL
|
|
||||||
// initPowerControl() (source: M5Unified's Power_Class.cpp). ALDO1 powers the AW88298 audio
|
|
||||||
// amp, ALDO2 the ES7210 microphone ADC, ALDO3 the GC0308 camera, ALDO4 the TF/SD card slot.
|
|
||||||
// DLDO1 (LCD backlight) is left to the axp2101-backlight child device.
|
|
||||||
static void configure_axp2101(Device* axp2101) {
|
|
||||||
check(axp2101_set_ldo_voltage(axp2101, AXP2101_ALDO1, 1800) == ERROR_NONE);
|
|
||||||
check(axp2101_set_ldo_voltage(axp2101, AXP2101_ALDO2, 3300) == ERROR_NONE);
|
|
||||||
check(axp2101_set_ldo_voltage(axp2101, AXP2101_ALDO3, 3300) == ERROR_NONE);
|
|
||||||
check(axp2101_set_ldo_voltage(axp2101, AXP2101_ALDO4, 3300) == ERROR_NONE);
|
|
||||||
|
|
||||||
check(axp2101_set_ldo_enabled(axp2101, AXP2101_ALDO1, true) == ERROR_NONE);
|
|
||||||
check(axp2101_set_ldo_enabled(axp2101, AXP2101_ALDO2, true) == ERROR_NONE);
|
|
||||||
check(axp2101_set_ldo_enabled(axp2101, AXP2101_ALDO3, true) == ERROR_NONE);
|
|
||||||
check(axp2101_set_ldo_enabled(axp2101, AXP2101_ALDO4, true) == ERROR_NONE);
|
|
||||||
check(axp2101_set_ldo_enabled(axp2101, AXP2101_BLDO1, true) == ERROR_NONE);
|
|
||||||
check(axp2101_set_ldo_enabled(axp2101, AXP2101_BLDO2, true) == ERROR_NONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void on_device_event(Device* device, DeviceEvent event, void* context) {
|
|
||||||
(void)context;
|
|
||||||
if (event == DEVICE_EVENT_STARTED && strcmp(device->name, "axp2101") == 0) {
|
|
||||||
configure_axp2101(device);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t start() {
|
|
||||||
device_listener_add(on_device_event, nullptr);
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t stop() {
|
|
||||||
device_listener_remove(on_device_event);
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Module m5stack_cores3_module = {
|
struct Module m5stack_cores3_module = {
|
||||||
.name = "m5stack-cores3",
|
.name = "m5stack-cores3",
|
||||||
.start = start,
|
.start = [] -> error_t { return ERROR_NONE; },
|
||||||
.stop = stop,
|
.stop = [] -> error_t { return ERROR_NONE; },
|
||||||
.symbols = nullptr,
|
.symbols = nullptr,
|
||||||
.internal = nullptr
|
.internal = nullptr
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 esp_lvgl_port ILI934x FT6x36 AXP2101 driver vfs fatfs ina226-module py32ioexpander-module
|
REQUIRES TactilityKernel py32ioexpander-module
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,162 +0,0 @@
|
|||||||
#include "devices/Display.h"
|
|
||||||
#include "devices/Power.h"
|
|
||||||
|
|
||||||
#include <tactility/device.h>
|
|
||||||
#include <tactility/drivers/gpio_controller.h>
|
|
||||||
#include <tactility/drivers/i2c_controller.h>
|
|
||||||
#include <tactility/log.h>
|
|
||||||
#include <drivers/py32ioexpander.h>
|
|
||||||
#include <freertos/FreeRTOS.h>
|
|
||||||
#include <freertos/task.h>
|
|
||||||
|
|
||||||
#include <Tactility/hal/Configuration.h>
|
|
||||||
#include <Axp2101Power.h>
|
|
||||||
#include <Axp2101.h>
|
|
||||||
|
|
||||||
using namespace tt::hal;
|
|
||||||
|
|
||||||
static const auto* TAG = "StackChan";
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// I2C addresses
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
static constexpr uint8_t AXP2101_ADDR = 0x34;
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// AW9523B GPIO expander pin map — same wiring as CoreS3.
|
|
||||||
// AW88298 reset (P0_2) is driven directly by the aw88298-module driver itself
|
|
||||||
// (see m5stack,stackchan.dts pin-reset property), not from here.
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
constexpr auto AW9523B_PIN_TOUCH_RESET = 0; // P0_0
|
|
||||||
constexpr auto AW9523B_PIN_BUS_OUT_ENABLE = 1; // P0_1
|
|
||||||
constexpr auto AW9523B_PIN_SD_CARD_SWITCH = 4; // P0_4
|
|
||||||
constexpr auto AW9523B_PIN_LCD_RESET = 8 + 1; // P1_1
|
|
||||||
constexpr auto AW9523B_PIN_BOOST_ENABLE = 8 + 7; // P1_7 (SY7088)
|
|
||||||
|
|
||||||
static bool initGpioExpander(::Device* aw9523b) {
|
|
||||||
struct PinInit { uint8_t pin; bool level; };
|
|
||||||
static constexpr PinInit pins[] = {
|
|
||||||
{ AW9523B_PIN_TOUCH_RESET, true },
|
|
||||||
{ AW9523B_PIN_BUS_OUT_ENABLE, true },
|
|
||||||
{ AW9523B_PIN_SD_CARD_SWITCH, true },
|
|
||||||
{ AW9523B_PIN_LCD_RESET, true },
|
|
||||||
{ AW9523B_PIN_BOOST_ENABLE, true },
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const auto& pinInit : pins) {
|
|
||||||
auto* descriptor = gpio_descriptor_acquire(aw9523b, pinInit.pin, GPIO_OWNER_GPIO);
|
|
||||||
if (descriptor == nullptr) {
|
|
||||||
LOG_E(TAG, "AW9523B: Failed to acquire pin %u", pinInit.pin);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
error_t error = gpio_descriptor_set_flags(descriptor, GPIO_FLAG_DIRECTION_OUTPUT);
|
|
||||||
if (error == ERROR_NONE) {
|
|
||||||
error = gpio_descriptor_set_level(descriptor, pinInit.level);
|
|
||||||
}
|
|
||||||
gpio_descriptor_release(descriptor);
|
|
||||||
if (error != ERROR_NONE) {
|
|
||||||
LOG_E(TAG, "AW9523B: Failed to configure pin %u", pinInit.pin);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// AXP2101 power management — same voltage rails as CoreS3
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
static bool initPowerControl(::Device* i2c) {
|
|
||||||
// Source: https://github.com/m5stack/M5Unified/blob/b8cfec7fed046242da7f7b8024a4e92004a51ff7/src/utility/Power_Class.cpp#L64
|
|
||||||
static constexpr uint8_t reg_data[] = {
|
|
||||||
0x90U, 0xBFU, // LDOS ON/OFF control 0 (backlight)
|
|
||||||
0x92U, 18U - 5U, // ALDO1 = 1.8V (AW88298)
|
|
||||||
0x93U, 33U - 5U, // ALDO2 = 3.3V (ES7210)
|
|
||||||
0x94U, 33U - 5U, // ALDO3 = 3.3V (camera)
|
|
||||||
0x95U, 33U - 5U, // ALDO4 = 3.3V (TF card)
|
|
||||||
0x27U, 0x00U, // PowerKey Hold=1sec / PowerOff=4sec
|
|
||||||
0x69U, 0x11U, // CHGLED setting
|
|
||||||
0x10U, 0x30U, // PMU common config
|
|
||||||
0x30U, 0x0FU, // ADC enabled
|
|
||||||
};
|
|
||||||
|
|
||||||
if (i2c_controller_write_register_array(i2c, AXP2101_ADDR, reg_data, sizeof(reg_data), pdMS_TO_TICKS(1000)) != ERROR_NONE) {
|
|
||||||
LOG_E(TAG, "AXP2101: Failed to set registers");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// initBoot — called after devicetree devices are constructed/started
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
static std::shared_ptr<Axp2101> axp2101;
|
|
||||||
|
|
||||||
bool initBoot() {
|
|
||||||
auto* i2c = device_find_by_name("i2c_internal");
|
|
||||||
if (i2c == nullptr) {
|
|
||||||
LOG_E(TAG, "i2c_internal not found");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Boost enable via AXP2101 before GPIO expander init (same as CoreS3)
|
|
||||||
if (!initPowerControl(i2c)) {
|
|
||||||
LOG_E(TAG, "AXP2101 init failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto* aw9523b = device_find_by_name("aw9523b");
|
|
||||||
if (aw9523b == nullptr) {
|
|
||||||
LOG_E(TAG, "aw9523b not found");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!initGpioExpander(aw9523b)) {
|
|
||||||
LOG_E(TAG, "AW9523B init failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Boot LED pattern — confirms PY32IOExpander is working.
|
|
||||||
// PY32 pin 0 = servo VM_EN (output), pin 13 = WS2812C data line.
|
|
||||||
auto* py32 = device_find_by_name("py32");
|
|
||||||
if (py32 != nullptr) {
|
|
||||||
static constexpr uint8_t LED_COUNT = 12;
|
|
||||||
static constexpr uint8_t COLORS[][3] = {
|
|
||||||
{ 255, 0, 0 }, // red
|
|
||||||
{ 0, 255, 0 }, // green
|
|
||||||
{ 0, 0, 255 }, // blue
|
|
||||||
};
|
|
||||||
py32_led_set_count(py32, LED_COUNT);
|
|
||||||
for (auto& c : COLORS) {
|
|
||||||
for (uint8_t i = 0; i < LED_COUNT; i++) {
|
|
||||||
py32_led_set_color(py32, i, c[0], c[1], c[2]);
|
|
||||||
}
|
|
||||||
py32_led_refresh(py32);
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(150));
|
|
||||||
}
|
|
||||||
py32_led_disable(py32);
|
|
||||||
} else {
|
|
||||||
LOG_W(TAG, "py32 not found — LED boot pattern skipped");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep Axp2101 C++ wrapper alive for Axp2101Power (backlight + battery)
|
|
||||||
axp2101 = std::make_shared<Axp2101>(i2c);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Device list
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
static DeviceVector createDevices() {
|
|
||||||
return {
|
|
||||||
axp2101,
|
|
||||||
std::make_shared<Axp2101Power>(axp2101),
|
|
||||||
createPower(),
|
|
||||||
createDisplay(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
extern const Configuration hardwareConfiguration = {
|
|
||||||
.initBoot = initBoot,
|
|
||||||
.createDevices = createDevices
|
|
||||||
};
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
#include "Display.h"
|
|
||||||
|
|
||||||
#include <Axp2101.h>
|
|
||||||
#include <Ft6x36Touch.h>
|
|
||||||
#include <Ili934xDisplay.h>
|
|
||||||
|
|
||||||
#include <tactility/check.h>
|
|
||||||
#include <tactility/log.h>
|
|
||||||
|
|
||||||
constexpr auto* TAG = "StackChanDisplay";
|
|
||||||
|
|
||||||
static void setBacklightDuty(uint8_t backlightDuty) {
|
|
||||||
const uint8_t voltage = 20 + ((8 * backlightDuty) / 255); // [0b00000, 0b11100]
|
|
||||||
auto controller = device_find_by_name("i2c_internal");
|
|
||||||
check(controller);
|
|
||||||
if (i2c_controller_write_register(controller, AXP2101_ADDRESS, 0x99, &voltage, 1, 1000) != ERROR_NONE) { // Sets DLD01
|
|
||||||
LOG_E(TAG, "Failed to set display backlight voltage");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
|
|
||||||
auto configuration = std::make_unique<Ft6x36Touch::Configuration>(
|
|
||||||
I2C_NUM_0,
|
|
||||||
319,//LCD_HORIZONTAL_RESOLUTION,
|
|
||||||
239,//LCD_VERTICAL_RESOLUTION,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
return std::make_shared<Ft6x36Touch>(std::move(configuration));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
|
|
||||||
Ili934xDisplay::Configuration panel_configuration = {
|
|
||||||
.horizontalResolution = LCD_HORIZONTAL_RESOLUTION,
|
|
||||||
.verticalResolution = LCD_VERTICAL_RESOLUTION,
|
|
||||||
.gapX = 0,
|
|
||||||
.gapY = 0,
|
|
||||||
.swapXY = false,
|
|
||||||
.mirrorX = false,
|
|
||||||
.mirrorY = false,
|
|
||||||
.invertColor = true,
|
|
||||||
.swapBytes = true,
|
|
||||||
.bufferSize = LCD_BUFFER_SIZE,
|
|
||||||
.touch = createTouch(),
|
|
||||||
.backlightDutyFunction = ::setBacklightDuty,
|
|
||||||
.resetPin = GPIO_NUM_NC,
|
|
||||||
.rgbElementOrder = LCD_RGB_ELEMENT_ORDER_BGR
|
|
||||||
};
|
|
||||||
|
|
||||||
auto spi_configuration = std::make_shared<Ili934xDisplay::SpiConfiguration>(Ili934xDisplay::SpiConfiguration {
|
|
||||||
.spiHostDevice = LCD_SPI_HOST,
|
|
||||||
.csPin = LCD_PIN_CS,
|
|
||||||
.dcPin = LCD_PIN_DC,
|
|
||||||
.pixelClockFrequency = 40'000'000,
|
|
||||||
.transactionQueueDepth = 10
|
|
||||||
});
|
|
||||||
|
|
||||||
return std::make_shared<Ili934xDisplay>(panel_configuration, spi_configuration, true);
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <Tactility/hal/display/DisplayDevice.h>
|
|
||||||
#include <driver/gpio.h>
|
|
||||||
#include <driver/spi_common.h>
|
|
||||||
|
|
||||||
// Display
|
|
||||||
constexpr auto LCD_SPI_HOST = SPI2_HOST;
|
|
||||||
constexpr auto LCD_PIN_CS = GPIO_NUM_3;
|
|
||||||
constexpr auto LCD_PIN_DC = GPIO_NUM_35;
|
|
||||||
constexpr auto LCD_HORIZONTAL_RESOLUTION = 320;
|
|
||||||
constexpr auto LCD_VERTICAL_RESOLUTION = 240;
|
|
||||||
constexpr auto LCD_BUFFER_HEIGHT = LCD_VERTICAL_RESOLUTION / 10;
|
|
||||||
constexpr auto LCD_BUFFER_SIZE = LCD_HORIZONTAL_RESOLUTION * LCD_BUFFER_HEIGHT;
|
|
||||||
constexpr auto LCD_SPI_TRANSFER_SIZE_LIMIT = LCD_BUFFER_SIZE * LV_COLOR_DEPTH / 8;
|
|
||||||
|
|
||||||
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
#include "Power.h"
|
|
||||||
|
|
||||||
#include <Tactility/hal/power/PowerDevice.h>
|
|
||||||
#include <drivers/ina226.h>
|
|
||||||
#include <tactility/device.h>
|
|
||||||
#include <tactility/log.h>
|
|
||||||
|
|
||||||
using namespace tt::hal::power;
|
|
||||||
|
|
||||||
static constexpr auto* TAG = "StackChanPower";
|
|
||||||
|
|
||||||
// 1S Li-ion cell (550 mAh): 3.2V (empty) – 4.2V (full)
|
|
||||||
static constexpr float MIN_BATTERY_VOLTAGE_MV = 3200.0f;
|
|
||||||
static constexpr float MAX_BATTERY_VOLTAGE_MV = 4200.0f;
|
|
||||||
|
|
||||||
class StackChanPower final : public PowerDevice {
|
|
||||||
public:
|
|
||||||
explicit StackChanPower(::Device* ina226Device) : ina226(ina226Device) {}
|
|
||||||
|
|
||||||
std::string getName() const override { return "M5Stack StackChan Power"; }
|
|
||||||
std::string getDescription() const override { return "Battery monitoring via INA226 over I2C"; }
|
|
||||||
|
|
||||||
bool supportsMetric(MetricType type) const override {
|
|
||||||
switch (type) {
|
|
||||||
using enum MetricType;
|
|
||||||
case BatteryVoltage:
|
|
||||||
case ChargeLevel:
|
|
||||||
case Current:
|
|
||||||
return ina226 != nullptr;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool getMetric(MetricType type, MetricData& data) override {
|
|
||||||
switch (type) {
|
|
||||||
using enum MetricType;
|
|
||||||
|
|
||||||
case BatteryVoltage: {
|
|
||||||
if (ina226 == nullptr) return false;
|
|
||||||
float volts = 0.0f;
|
|
||||||
if (ina226_read_bus_voltage(ina226, &volts) != ERROR_NONE) return false;
|
|
||||||
data.valueAsUint32 = static_cast<uint32_t>(volts * 1000.0f);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
case ChargeLevel: {
|
|
||||||
if (ina226 == nullptr) return false;
|
|
||||||
float volts = 0.0f;
|
|
||||||
if (ina226_read_bus_voltage(ina226, &volts) != ERROR_NONE) return false;
|
|
||||||
float voltage_mv = volts * 1000.0f;
|
|
||||||
if (voltage_mv >= MAX_BATTERY_VOLTAGE_MV) {
|
|
||||||
data.valueAsUint8 = 100;
|
|
||||||
} else if (voltage_mv <= MIN_BATTERY_VOLTAGE_MV) {
|
|
||||||
data.valueAsUint8 = 0;
|
|
||||||
} else {
|
|
||||||
float factor = (voltage_mv - MIN_BATTERY_VOLTAGE_MV) / (MAX_BATTERY_VOLTAGE_MV - MIN_BATTERY_VOLTAGE_MV);
|
|
||||||
data.valueAsUint8 = static_cast<uint8_t>(factor * 100.0f);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
case Current: {
|
|
||||||
if (ina226 == nullptr) return false;
|
|
||||||
float amps = 0.0f;
|
|
||||||
if (ina226_read_shunt_current(ina226, &s) != ERROR_NONE) return false;
|
|
||||||
data.valueAsInt32 = static_cast<int32_t>(amps * 1000.0f);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
::Device* ina226;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::shared_ptr<PowerDevice> createPower() {
|
|
||||||
auto* ina226 = device_find_by_name("ina226");
|
|
||||||
if (ina226 == nullptr) {
|
|
||||||
LOG_E(TAG, "ina226 device not found");
|
|
||||||
}
|
|
||||||
return std::make_shared<StackChanPower>(ina226);
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <Tactility/hal/power/PowerDevice.h>
|
|
||||||
|
|
||||||
std::shared_ptr<tt::hal::power::PowerDevice> createPower();
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
#include <tactility/error.h>
|
|
||||||
#include <tactility/module.h>
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
|
|
||||||
Module m5stack_stackchan_module = {
|
|
||||||
.name = "m5stack-stackchan",
|
|
||||||
.start = [] -> error_t { return ERROR_NONE; },
|
|
||||||
.stop = [] -> error_t { return ERROR_NONE; },
|
|
||||||
.symbols = nullptr,
|
|
||||||
.internal = nullptr
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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=2"
|
display.size=2"
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
- Platforms/platform-esp32
|
- Platforms/platform-esp32
|
||||||
- Drivers/bmi270-module
|
- Drivers/audio-stream-module
|
||||||
|
- Drivers/aw88298-module
|
||||||
|
- Drivers/aw9523b-module
|
||||||
|
- Drivers/axp2101-module
|
||||||
- Drivers/bm8563-module
|
- Drivers/bm8563-module
|
||||||
|
- Drivers/bmi270-module
|
||||||
|
- Drivers/es7210-module
|
||||||
|
- Drivers/ft6x36-module
|
||||||
|
- Drivers/ili9341-module
|
||||||
- Drivers/ina226-module
|
- Drivers/ina226-module
|
||||||
- Drivers/py32ioexpander-module
|
- Drivers/py32ioexpander-module
|
||||||
- Drivers/aw9523b-module
|
|
||||||
- Drivers/aw88298-module
|
|
||||||
- Drivers/es7210-module
|
|
||||||
- Drivers/audio-stream-module
|
|
||||||
dts: m5stack,stackchan.dts
|
dts: m5stack,stackchan.dts
|
||||||
|
|||||||
@@ -9,15 +9,19 @@
|
|||||||
#include <tactility/bindings/esp32_i2s.h>
|
#include <tactility/bindings/esp32_i2s.h>
|
||||||
#include <tactility/bindings/esp32_spi.h>
|
#include <tactility/bindings/esp32_spi.h>
|
||||||
#include <tactility/bindings/esp32_uart.h>
|
#include <tactility/bindings/esp32_uart.h>
|
||||||
#include <bindings/bmi270.h>
|
#include <bindings/aw88298.h>
|
||||||
|
#include <bindings/aw9523b.h>
|
||||||
|
#include <bindings/axp2101.h>
|
||||||
|
#include <bindings/axp2101_backlight.h>
|
||||||
#include <bindings/bm8563.h>
|
#include <bindings/bm8563.h>
|
||||||
|
#include <bindings/bmi270.h>
|
||||||
|
#include <bindings/es7210.h>
|
||||||
|
#include <bindings/ft6x36.h>
|
||||||
|
#include <bindings/ili9341.h>
|
||||||
#include <bindings/ina226.h>
|
#include <bindings/ina226.h>
|
||||||
#include <bindings/py32ioexpander.h>
|
#include <bindings/py32ioexpander.h>
|
||||||
#include <bindings/aw9523b.h>
|
|
||||||
#include <bindings/aw88298.h>
|
|
||||||
#include <bindings/es7210.h>
|
|
||||||
#include <tactility/bindings/esp32_sdspi.h>
|
#include <tactility/bindings/esp32_sdspi.h>
|
||||||
#include <tactility/bindings/display_placeholder.h>
|
#include <tactility/bindings/gpio_hog.h>
|
||||||
|
|
||||||
// Reference: https://docs.m5stack.com/en/StackChan
|
// Reference: https://docs.m5stack.com/en/StackChan
|
||||||
/ {
|
/ {
|
||||||
@@ -40,7 +44,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
// AW88298 speaker + ES7210 microphone
|
// AW88298 speaker + ES7210 microphone
|
||||||
i2s0: i2s0 {
|
i2s0 {
|
||||||
compatible = "espressif,esp32-i2s";
|
compatible = "espressif,esp32-i2s";
|
||||||
port = <I2S_NUM_0>;
|
port = <I2S_NUM_0>;
|
||||||
pin-bclk = <&gpio0 34 GPIO_FLAG_NONE>;
|
pin-bclk = <&gpio0 34 GPIO_FLAG_NONE>;
|
||||||
@@ -77,14 +81,34 @@
|
|||||||
compatible = "ti,ina226";
|
compatible = "ti,ina226";
|
||||||
reg = <0x41>;
|
reg = <0x41>;
|
||||||
shunt-milliohms = <10>;
|
shunt-milliohms = <10>;
|
||||||
|
power-supply;
|
||||||
};
|
};
|
||||||
|
|
||||||
// AW9523B pin map (same wiring as CoreS3):
|
|
||||||
// P0_0 = touch reset, P0_1 = bus-out enable, P0_2 = AW88298 reset,
|
// P0_0 = touch reset, P0_1 = bus-out enable, P0_2 = AW88298 reset,
|
||||||
// P0_4 = SD card switch, P1_1 = LCD reset, P1_7 = boost enable (SY7088)
|
// P0_4 = SD card switch, P1_1 = LCD reset, P1_7 = boost enable (SY7088)
|
||||||
aw9523b: aw9523b {
|
aw9523b {
|
||||||
compatible = "awinic,aw9523b";
|
compatible = "awinic,aw9523b";
|
||||||
reg = <0x58>;
|
reg = <0x58>;
|
||||||
|
|
||||||
|
// Bus-out enable, AW88298 (audio amp) reset, SD card power switch and boost enable
|
||||||
|
// (SY7088) are AW9523B pins with no owning peripheral driver yet.
|
||||||
|
aw9523_bus_out_enable {
|
||||||
|
compatible = "gpio-hog";
|
||||||
|
pin = <&aw9523b 1 GPIO_FLAG_NONE>;
|
||||||
|
mode = <GPIO_HOG_MODE_OUTPUT_HIGH>;
|
||||||
|
};
|
||||||
|
|
||||||
|
aw9523_sdcard_switch {
|
||||||
|
compatible = "gpio-hog";
|
||||||
|
pin = <&aw9523b 4 GPIO_FLAG_NONE>;
|
||||||
|
mode = <GPIO_HOG_MODE_OUTPUT_HIGH>;
|
||||||
|
};
|
||||||
|
|
||||||
|
aw9523_boost_enable {
|
||||||
|
compatible = "gpio-hog";
|
||||||
|
pin = <&aw9523b 15 GPIO_FLAG_NONE>;
|
||||||
|
mode = <GPIO_HOG_MODE_OUTPUT_HIGH>;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
aw88298 {
|
aw88298 {
|
||||||
@@ -94,6 +118,39 @@
|
|||||||
pin-reset = <&aw9523b 2 GPIO_FLAG_NONE>;
|
pin-reset = <&aw9523b 2 GPIO_FLAG_NONE>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Same rail assignment as CoreS3: ALDO1=AW88298, ALDO2=ES7210, ALDO3=camera
|
||||||
|
// (not yet implemented, but harmless to power), ALDO4=TF/SD card. BLDO1/BLDO2 are
|
||||||
|
// enabled with no specific voltage requirement.
|
||||||
|
axp2101 {
|
||||||
|
compatible = "x-powers,axp2101";
|
||||||
|
reg = <0x34>;
|
||||||
|
aldo1-millivolt = <1800>;
|
||||||
|
aldo1-enabled;
|
||||||
|
aldo2-millivolt = <3300>;
|
||||||
|
aldo2-enabled;
|
||||||
|
aldo3-millivolt = <3300>;
|
||||||
|
aldo3-enabled;
|
||||||
|
aldo4-millivolt = <3300>;
|
||||||
|
aldo4-enabled;
|
||||||
|
bldo1-enabled;
|
||||||
|
bldo2-enabled;
|
||||||
|
|
||||||
|
display_backlight {
|
||||||
|
compatible = "axp2101-backlight";
|
||||||
|
ldo = <AXP2101_DLDO1>;
|
||||||
|
min-millivolt = <2500>;
|
||||||
|
max-millivolt = <3300>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
touch {
|
||||||
|
compatible = "focaltech,ft6x36";
|
||||||
|
reg = <0x38>;
|
||||||
|
x-max = <319>;
|
||||||
|
y-max = <239>;
|
||||||
|
pin-reset = <&aw9523b 0 GPIO_FLAG_NONE>;
|
||||||
|
};
|
||||||
|
|
||||||
es7210 {
|
es7210 {
|
||||||
compatible = "everest,es7210";
|
compatible = "everest,es7210";
|
||||||
reg = <0x40>;
|
reg = <0x40>;
|
||||||
@@ -109,9 +166,6 @@
|
|||||||
input-gain-percent = <400>;
|
input-gain-percent = <400>;
|
||||||
};
|
};
|
||||||
|
|
||||||
// AXP2101 PMIC @ 0x34 — initialized manually in initBoot()
|
|
||||||
// FT6336U capacitive touch @ 0x38 — used by Display driver (FT6x36 library)
|
|
||||||
|
|
||||||
// TODO: Si12T 3-zone head touch @ 0x68 — INT active-low, 10kΩ pull-up to 3.3V; driver not yet implemented
|
// TODO: Si12T 3-zone head touch @ 0x68 — INT active-low, 10kΩ pull-up to 3.3V; driver not yet implemented
|
||||||
// TODO: GC0308 camera @ 0x21 — requires i2c_master driver, not yet available
|
// TODO: GC0308 camera @ 0x21 — requires i2c_master driver, not yet available
|
||||||
// TODO: LTR-553ALS-WA proximity/light @ 0x23 — no driver yet
|
// TODO: LTR-553ALS-WA proximity/light @ 0x23 — no driver yet
|
||||||
@@ -167,7 +221,15 @@
|
|||||||
pin-sclk = <&gpio0 36 GPIO_FLAG_NONE>;
|
pin-sclk = <&gpio0 36 GPIO_FLAG_NONE>;
|
||||||
|
|
||||||
display@0 {
|
display@0 {
|
||||||
compatible = "display-placeholder";
|
compatible = "ilitek,ili9341";
|
||||||
|
horizontal-resolution = <320>;
|
||||||
|
vertical-resolution = <240>;
|
||||||
|
invert-color;
|
||||||
|
bgr-order;
|
||||||
|
pixel-clock-hz = <40000000>;
|
||||||
|
pin-dc = <&gpio0 35 GPIO_FLAG_NONE>;
|
||||||
|
pin-reset = <&aw9523b 9 GPIO_FLAG_NONE>;
|
||||||
|
backlight = <&display_backlight>;
|
||||||
};
|
};
|
||||||
|
|
||||||
sdcard@1 {
|
sdcard@1 {
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
#include <drivers/py32ioexpander.h>
|
||||||
|
|
||||||
|
#include <tactility/check.h>
|
||||||
|
#include <tactility/device.h>
|
||||||
|
#include <tactility/device_listener.h>
|
||||||
|
#include <tactility/module.h>
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
// Boot LED pattern (red/green/blue sweep across the 12-LED WS2812C ring).
|
||||||
|
// Confirms the PY32L020 body IO expander is working.
|
||||||
|
static void run_led_boot_pattern(Device* py32) {
|
||||||
|
static constexpr uint8_t LED_COUNT = 12;
|
||||||
|
static constexpr uint8_t COLORS[][3] = {
|
||||||
|
{ 255, 0, 0 }, // red
|
||||||
|
{ 0, 255, 0 }, // green
|
||||||
|
{ 0, 0, 255 }, // blue
|
||||||
|
};
|
||||||
|
|
||||||
|
py32_led_set_count(py32, LED_COUNT);
|
||||||
|
for (const auto& color : COLORS) {
|
||||||
|
for (uint8_t i = 0; i < LED_COUNT; i++) {
|
||||||
|
py32_led_set_color(py32, i, color[0], color[1], color[2]);
|
||||||
|
}
|
||||||
|
py32_led_refresh(py32);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(150));
|
||||||
|
}
|
||||||
|
py32_led_disable(py32);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_device_event(Device* device, DeviceEvent event, void* context) {
|
||||||
|
(void)context;
|
||||||
|
if (event != DEVICE_EVENT_STARTED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (strcmp(device->name, "py32") == 0) {
|
||||||
|
run_led_boot_pattern(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static error_t start() {
|
||||||
|
device_listener_add(on_device_event, nullptr);
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static error_t stop() {
|
||||||
|
device_listener_remove(on_device_event);
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
Module m5stack_stackchan_module = {
|
||||||
|
.name = "m5stack-stackchan",
|
||||||
|
.start = start,
|
||||||
|
.stop = stop,
|
||||||
|
.symbols = nullptr,
|
||||||
|
.internal = nullptr
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -4,7 +4,11 @@
|
|||||||
|
|
||||||
Tactility is an operating system for the ESP32 microcontroller family. It runs on 40+ supported devices (CYD boards, LilyGO, M5Stack, Elecrow, etc.) and includes a desktop simulator. Built with C++23, ESP-IDF, LVGL, and FreeRTOS.
|
Tactility is an operating system for the ESP32 microcontroller family. It runs on 40+ supported devices (CYD boards, LilyGO, M5Stack, Elecrow, etc.) and includes a desktop simulator. Built with C++23, ESP-IDF, LVGL, and FreeRTOS.
|
||||||
|
|
||||||
## Build Commands
|
## Building
|
||||||
|
|
||||||
|
### Git
|
||||||
|
|
||||||
|
The repository uses git submodules. Make sure to use `--recurse-submodules` on relevant git commands.
|
||||||
|
|
||||||
### Simulator (Linux/macOS, no ESP-IDF needed)
|
### Simulator (Linux/macOS, no ESP-IDF needed)
|
||||||
|
|
||||||
@@ -167,4 +171,4 @@ Pointers are expected to be non-null unless documented otherwise.
|
|||||||
- The `Drivers/` directory contains hardware drivers (display controllers, touch controllers, PMICs, etc.) — each is its own CMake component.
|
- The `Drivers/` directory contains hardware drivers (display controllers, touch controllers, PMICs, etc.) — each is its own CMake component.
|
||||||
- `Modules/` contains cross-cutting modules: `hal-device-module` (device lifecycle) and `lvgl-module` (LVGL task management).
|
- `Modules/` contains cross-cutting modules: `hal-device-module` (device lifecycle) and `lvgl-module` (LVGL task management).
|
||||||
- `Data/system/` and `Data/data/` are flashed as FAT filesystem images on ESP32.
|
- `Data/system/` and `Data/data/` are flashed as FAT filesystem images on ESP32.
|
||||||
- Translations are in `Translations/` as CSV files, generated via `generate.py`.
|
- Translations are in `Translations/` as CSV files, generated via `generate.py`.
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
## Before release
|
## Before release
|
||||||
|
|
||||||
|
- WebServer service shouldn't save webserver.properties at start, it slows the boot process
|
||||||
- Remove incubating flag from various devices
|
- Remove incubating flag from various devices
|
||||||
- Add `// SPDX-License-Identifier: GPL-3.0-only` and `// SPDX-License-Identifier: Apache-2.0` to individual files in the project
|
- Add `// SPDX-License-Identifier: GPL-3.0-only` and `// SPDX-License-Identifier: Apache-2.0` to individual files in the project
|
||||||
- Elecrow Basic & Advance 3.5" memory issue: not enough memory for App Hub
|
- Elecrow Basic & Advance 3.5" memory issue: not enough memory for App Hub
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
idf_component_register(
|
|
||||||
SRC_DIRS "Source"
|
|
||||||
INCLUDE_DIRS "Source"
|
|
||||||
REQUIRES Tactility
|
|
||||||
)
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
# AXP2101
|
|
||||||
|
|
||||||
Power management with I2C interface: a single cell NVDC PMU with E-gauge.
|
|
||||||
|
|
||||||
- [Datasheet](http://file.whycan.com/files/members/6736/AXP2101_Datasheet_V1.0_en_3832.pdf)
|
|
||||||
- [M5Unified implementation](https://github.com/m5stack/M5Unified/blob/master/src/utility/AXP2101_Class.cpp)
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
#include "Axp2101.h"
|
|
||||||
|
|
||||||
bool Axp2101::getBatteryVoltage(float& vbatMillis) const {
|
|
||||||
return readRegister14(0x34, vbatMillis);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Axp2101::getChargeStatus(ChargeStatus& status) const {
|
|
||||||
uint8_t value;
|
|
||||||
if (readRegister8(0x01, value)) {
|
|
||||||
value = (value >> 5) & 0b11;
|
|
||||||
status = (value == 1) ? CHARGE_STATUS_CHARGING : ((value == 2) ? CHARGE_STATUS_DISCHARGING : CHARGE_STATUS_STANDBY);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Axp2101::isChargingEnabled(bool& enabled) const {
|
|
||||||
uint8_t value;
|
|
||||||
if (readRegister8(0x18, value)) {
|
|
||||||
enabled = value & 0b10;
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Axp2101::setChargingEnabled(bool enabled) const {
|
|
||||||
uint8_t value;
|
|
||||||
if (readRegister8(0x18, value)) {
|
|
||||||
return writeRegister8(0x18, (value & 0xFD) | (enabled << 1));
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Axp2101::isVBus() const {
|
|
||||||
uint8_t value;
|
|
||||||
return readRegister8(0x00, value) && (value & 0x20);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Axp2101::getVBusVoltage(float& out) const {
|
|
||||||
if (!isVBus()) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
float vbus;
|
|
||||||
if (readRegister14(0x38, vbus) && vbus < 16375) {
|
|
||||||
out = vbus / 1000.0f;
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Axp2101::setRegisters(uint8_t* bytePairs, size_t bytePairsSize) const {
|
|
||||||
return i2c_controller_write_register_array(controller, address, bytePairs, bytePairsSize, DEFAULT_TIMEOUT) == ERROR_NONE;
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <Tactility/hal/i2c/I2cDevice.h>
|
|
||||||
|
|
||||||
#define AXP2101_ADDRESS 0x34
|
|
||||||
|
|
||||||
/**
|
|
||||||
* References:
|
|
||||||
* - https://github.com/m5stack/M5Unified/blob/master/src/utility/AXP2101_Class.cpp
|
|
||||||
* - http://file.whycan.com/files/members/6736/AXP2101_Datasheet_V1.0_en_3832.pdf
|
|
||||||
*/
|
|
||||||
class Axp2101 final : public tt::hal::i2c::I2cDevice {
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
enum ChargeStatus {
|
|
||||||
CHARGE_STATUS_CHARGING = 0b01,
|
|
||||||
CHARGE_STATUS_DISCHARGING = 0b10,
|
|
||||||
CHARGE_STATUS_STANDBY = 0b00
|
|
||||||
};
|
|
||||||
|
|
||||||
explicit Axp2101(::Device* controller) : I2cDevice(controller, AXP2101_ADDRESS) {}
|
|
||||||
|
|
||||||
std::string getName() const override { return "AXP2101"; }
|
|
||||||
std::string getDescription() const override { return "Power management with I2C interface."; }
|
|
||||||
|
|
||||||
bool setRegisters(uint8_t* bytePairs, size_t bytePairsSize) const;
|
|
||||||
|
|
||||||
bool getBatteryVoltage(float& vbatMillis) const;
|
|
||||||
bool getChargeStatus(ChargeStatus& status) const;
|
|
||||||
bool isChargingEnabled(bool& enabled) const;
|
|
||||||
bool setChargingEnabled(bool enabled) const;
|
|
||||||
|
|
||||||
bool isVBus() const;
|
|
||||||
bool getVBusVoltage(float& out) const;
|
|
||||||
};
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
#include "Axp2101Power.h"
|
|
||||||
|
|
||||||
bool Axp2101Power::supportsMetric(MetricType type) const {
|
|
||||||
switch (type) {
|
|
||||||
using enum MetricType;
|
|
||||||
case BatteryVoltage:
|
|
||||||
case IsCharging:
|
|
||||||
case ChargeLevel:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false; // Safety guard for when new enum values are introduced
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Axp2101Power::getMetric(MetricType type, MetricData& data) {
|
|
||||||
switch (type) {
|
|
||||||
using enum MetricType;
|
|
||||||
case BatteryVoltage: {
|
|
||||||
float milliVolt;
|
|
||||||
if (axpDevice->getBatteryVoltage(milliVolt)) {
|
|
||||||
data.valueAsUint32 = (uint32_t)milliVolt;
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case ChargeLevel: {
|
|
||||||
float vbatMillis;
|
|
||||||
if (axpDevice->getBatteryVoltage(vbatMillis)) {
|
|
||||||
float vbat = vbatMillis / 1000.f;
|
|
||||||
float max_voltage = 4.20f;
|
|
||||||
float min_voltage = 2.69f; // From M5Unified
|
|
||||||
if (vbat > 2.69f) {
|
|
||||||
float charge_factor = (vbat - min_voltage) / (max_voltage - min_voltage);
|
|
||||||
data.valueAsUint8 = (uint8_t)(charge_factor * 100.f);
|
|
||||||
} else {
|
|
||||||
data.valueAsUint8 = 0;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case IsCharging: {
|
|
||||||
Axp2101::ChargeStatus status;
|
|
||||||
if (axpDevice->getChargeStatus(status)) {
|
|
||||||
data.valueAsBool = (status == Axp2101::CHARGE_STATUS_CHARGING);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Axp2101Power::isAllowedToCharge() const {
|
|
||||||
bool enabled;
|
|
||||||
if (axpDevice->isChargingEnabled(enabled)) {
|
|
||||||
return enabled;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Axp2101Power::setAllowedToCharge(bool canCharge) {
|
|
||||||
axpDevice->setChargingEnabled(canCharge);
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Tactility/hal/power/PowerDevice.h"
|
|
||||||
#include <Axp2101.h>
|
|
||||||
#include <memory>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
using tt::hal::power::PowerDevice;
|
|
||||||
|
|
||||||
class Axp2101Power final : public PowerDevice {
|
|
||||||
|
|
||||||
std::shared_ptr<Axp2101> axpDevice;
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
explicit Axp2101Power(std::shared_ptr<Axp2101> axp) : axpDevice(std::move(axp)) {}
|
|
||||||
~Axp2101Power() override = default;
|
|
||||||
|
|
||||||
std::string getName() const override { return "AXP2101 Power"; }
|
|
||||||
std::string getDescription() const override { return "Power management via AXP2101 over I2C"; }
|
|
||||||
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
idf_component_register(SRCS "esp_lcd_touch_axs5106.c"
|
|
||||||
INCLUDE_DIRS "include"
|
|
||||||
REQUIRES "esp_lcd" "driver" "espressif__esp_lcd_touch")
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
# AXS5106
|
|
||||||
|
|
||||||
I2C touch driver.
|
|
||||||
|
|
||||||
Source: https://files.waveshare.com/wiki/1.47inch%20Touch%20LCD/1.47inch_Touch_LCD_Demo_ESP32.zip
|
|
||||||
|
|
||||||
License: Apache 2.0
|
|
||||||
@@ -1,260 +0,0 @@
|
|||||||
#include <stdio.h>
|
|
||||||
#include "esp_lcd_touch_axs5106.h"
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
#include "freertos/FreeRTOS.h"
|
|
||||||
#include "freertos/task.h"
|
|
||||||
#include "esp_system.h"
|
|
||||||
#include "esp_err.h"
|
|
||||||
#include "esp_log.h"
|
|
||||||
#include "esp_check.h"
|
|
||||||
#include "driver/gpio.h"
|
|
||||||
#include "esp_lcd_panel_io.h"
|
|
||||||
#include "esp_lcd_touch.h"
|
|
||||||
|
|
||||||
static const char *TAG = "esp_lcd_touch_axs5106";
|
|
||||||
|
|
||||||
#define TOUCH_AXS5106_TOUCH_POINTS_REG (0X01)
|
|
||||||
#define TOUCH_AXS5106_TOUCH_P1_XH_REG (0x03)
|
|
||||||
#define TOUCH_AXS5106_TOUCH_P1_XL_REG (0x04)
|
|
||||||
#define TOUCH_AXS5106_TOUCH_P1_YH_REG (0x05)
|
|
||||||
#define TOUCH_AXS5106_TOUCH_P1_YL_REG (0x06)
|
|
||||||
|
|
||||||
#define TOUCH_AXS5106_TOUCH_ID_REG (0x08)
|
|
||||||
#define TOUCH_AXS5106_TOUCH_P2_XH_REG (0x09)
|
|
||||||
#define TOUCH_AXS5106_TOUCH_P2_XL_REG (0x0A)
|
|
||||||
#define TOUCH_AXS5106_TOUCH_P2_YH_REG (0x0B)
|
|
||||||
#define TOUCH_AXS5106_TOUCH_P2_YL_REG (0x0C)
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
* Function definitions
|
|
||||||
*******************************************************************************/
|
|
||||||
static esp_err_t esp_lcd_touch_axs5106_read_data(esp_lcd_touch_handle_t tp);
|
|
||||||
static bool esp_lcd_touch_axs5106_get_xy(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, uint8_t *point_num, uint8_t max_point_num);
|
|
||||||
static esp_err_t esp_lcd_touch_axs5106_del(esp_lcd_touch_handle_t tp);
|
|
||||||
|
|
||||||
/* I2C read */
|
|
||||||
static esp_err_t touch_axs5106_i2c_write(esp_lcd_touch_handle_t tp, uint8_t reg, uint8_t *data, uint8_t len);
|
|
||||||
static esp_err_t touch_axs5106_i2c_read(esp_lcd_touch_handle_t tp, uint8_t reg, uint8_t *data, uint8_t len);
|
|
||||||
|
|
||||||
/* AXS5106 init */
|
|
||||||
static esp_err_t touch_axs5106_init(esp_lcd_touch_handle_t tp);
|
|
||||||
/* AXS5106 reset */
|
|
||||||
static esp_err_t touch_axs5106_reset(esp_lcd_touch_handle_t tp);
|
|
||||||
|
|
||||||
esp_err_t esp_lcd_touch_new_i2c_axs5106(const esp_lcd_panel_io_handle_t io, const esp_lcd_touch_config_t *config, esp_lcd_touch_handle_t *out_touch)
|
|
||||||
{
|
|
||||||
esp_err_t ret = ESP_OK;
|
|
||||||
assert(io != NULL);
|
|
||||||
assert(config != NULL);
|
|
||||||
assert(out_touch != NULL);
|
|
||||||
|
|
||||||
/* Prepare main structure */
|
|
||||||
esp_lcd_touch_handle_t esp_lcd_touch_axs5106 = heap_caps_calloc(1, sizeof(esp_lcd_touch_t), MALLOC_CAP_DEFAULT);
|
|
||||||
ESP_GOTO_ON_FALSE(esp_lcd_touch_axs5106, ESP_ERR_NO_MEM, err, TAG, "no mem for AXS5106 controller");
|
|
||||||
|
|
||||||
/* Communication interface */
|
|
||||||
esp_lcd_touch_axs5106->io = io;
|
|
||||||
|
|
||||||
/* Only supported callbacks are set */
|
|
||||||
esp_lcd_touch_axs5106->read_data = esp_lcd_touch_axs5106_read_data;
|
|
||||||
esp_lcd_touch_axs5106->get_xy = esp_lcd_touch_axs5106_get_xy;
|
|
||||||
esp_lcd_touch_axs5106->del = esp_lcd_touch_axs5106_del;
|
|
||||||
|
|
||||||
/* Mutex */
|
|
||||||
esp_lcd_touch_axs5106->data.lock.owner = portMUX_FREE_VAL;
|
|
||||||
|
|
||||||
/* Save config */
|
|
||||||
memcpy(&esp_lcd_touch_axs5106->config, config, sizeof(esp_lcd_touch_config_t));
|
|
||||||
|
|
||||||
/* Prepare pin for touch interrupt */
|
|
||||||
if (esp_lcd_touch_axs5106->config.int_gpio_num != GPIO_NUM_NC)
|
|
||||||
{
|
|
||||||
const gpio_config_t int_gpio_config = {
|
|
||||||
.mode = GPIO_MODE_INPUT,
|
|
||||||
.intr_type = (esp_lcd_touch_axs5106->config.levels.interrupt ? GPIO_INTR_POSEDGE : GPIO_INTR_NEGEDGE),
|
|
||||||
.pin_bit_mask = BIT64(esp_lcd_touch_axs5106->config.int_gpio_num)};
|
|
||||||
ret = gpio_config(&int_gpio_config);
|
|
||||||
ESP_GOTO_ON_ERROR(ret, err, TAG, "GPIO config failed");
|
|
||||||
|
|
||||||
/* Register interrupt callback */
|
|
||||||
if (esp_lcd_touch_axs5106->config.interrupt_callback)
|
|
||||||
{
|
|
||||||
esp_lcd_touch_register_interrupt_callback(esp_lcd_touch_axs5106, esp_lcd_touch_axs5106->config.interrupt_callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Prepare pin for touch controller reset */
|
|
||||||
if (esp_lcd_touch_axs5106->config.rst_gpio_num != GPIO_NUM_NC)
|
|
||||||
{
|
|
||||||
const gpio_config_t rst_gpio_config = {
|
|
||||||
.mode = GPIO_MODE_OUTPUT,
|
|
||||||
.pin_bit_mask = BIT64(esp_lcd_touch_axs5106->config.rst_gpio_num)};
|
|
||||||
ret = gpio_config(&rst_gpio_config);
|
|
||||||
ESP_GOTO_ON_ERROR(ret, err, TAG, "GPIO config failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Reset controller */
|
|
||||||
ret = touch_axs5106_reset(esp_lcd_touch_axs5106);
|
|
||||||
ESP_GOTO_ON_ERROR(ret, err, TAG, "AXS5106 reset failed");
|
|
||||||
|
|
||||||
/* Init controller */
|
|
||||||
ret = touch_axs5106_init(esp_lcd_touch_axs5106);
|
|
||||||
ESP_GOTO_ON_ERROR(ret, err, TAG, "AXS5106 init failed");
|
|
||||||
|
|
||||||
uint8_t data[3] = { 0 };
|
|
||||||
if (touch_axs5106_i2c_read(esp_lcd_touch_axs5106, TOUCH_AXS5106_TOUCH_ID_REG, data, 3) == ESP_OK) {
|
|
||||||
if (data[0] != 0x00) {
|
|
||||||
ESP_LOGI(TAG, "Read: %02x %02x %02x", data[0], data[1], data[2]);
|
|
||||||
} else {
|
|
||||||
ESP_LOGW(TAG, "Failed to read id: received zeroes");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ESP_LOGE(TAG, "Failed to read id: receive failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
err:
|
|
||||||
if (ret != ESP_OK)
|
|
||||||
{
|
|
||||||
ESP_LOGE(TAG, "Error (0x%x)! Touch controller AXS5106 initialization failed!", ret);
|
|
||||||
if (esp_lcd_touch_axs5106)
|
|
||||||
{
|
|
||||||
esp_lcd_touch_axs5106_del(esp_lcd_touch_axs5106);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*out_touch = esp_lcd_touch_axs5106;
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static esp_err_t esp_lcd_touch_axs5106_read_data(esp_lcd_touch_handle_t tp)
|
|
||||||
{
|
|
||||||
uint8_t data[30] = {0};
|
|
||||||
size_t i = 0;
|
|
||||||
|
|
||||||
assert(tp != NULL);
|
|
||||||
|
|
||||||
esp_err_t err = touch_axs5106_i2c_read(tp, TOUCH_AXS5106_TOUCH_POINTS_REG, data, 14);
|
|
||||||
ESP_RETURN_ON_ERROR(err, TAG, "I2C read error: %s", esp_err_to_name(err));
|
|
||||||
|
|
||||||
uint8_t points = data[1];
|
|
||||||
points = points & 0x0F;
|
|
||||||
|
|
||||||
if (points == 0)
|
|
||||||
{
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Number of touched points */
|
|
||||||
points = (points > 2 ? 2 : points);
|
|
||||||
|
|
||||||
// err = touch_axs5106_i2c_read(tp, TOUCH_AXS5106_TOUCH_P1_XH_REG, data, 6 * points);
|
|
||||||
// ESP_RETURN_ON_ERROR(err, TAG, "I2C read error!");
|
|
||||||
|
|
||||||
portENTER_CRITICAL(&tp->data.lock);
|
|
||||||
|
|
||||||
/* Number of touched points */
|
|
||||||
tp->data.points = points;
|
|
||||||
|
|
||||||
/* Fill all coordinates */
|
|
||||||
for (i = 0; i < points; i++)
|
|
||||||
{
|
|
||||||
tp->data.coords[i].y = (((uint16_t)(data[4 + i * 6] & 0x0f)) << 8);
|
|
||||||
tp->data.coords[i].y |= data[5 + i * 6];
|
|
||||||
tp->data.coords[i].x = ((uint16_t)(data[2 + i * 6] & 0x0f)) << 8;
|
|
||||||
tp->data.coords[i].x |= data[3 + i * 6];
|
|
||||||
}
|
|
||||||
portEXIT_CRITICAL(&tp->data.lock);
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool esp_lcd_touch_axs5106_get_xy(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, uint8_t *point_num, uint8_t max_point_num)
|
|
||||||
{
|
|
||||||
assert(tp != NULL);
|
|
||||||
assert(x != NULL);
|
|
||||||
assert(y != NULL);
|
|
||||||
assert(point_num != NULL);
|
|
||||||
assert(max_point_num > 0);
|
|
||||||
|
|
||||||
portENTER_CRITICAL(&tp->data.lock);
|
|
||||||
|
|
||||||
/* Count of points */
|
|
||||||
*point_num = (tp->data.points > max_point_num ? max_point_num : tp->data.points);
|
|
||||||
|
|
||||||
for (size_t i = 0; i < *point_num; i++)
|
|
||||||
{
|
|
||||||
x[i] = tp->data.coords[i].x;
|
|
||||||
y[i] = tp->data.coords[i].y;
|
|
||||||
|
|
||||||
if (strength)
|
|
||||||
{
|
|
||||||
strength[i] = tp->data.coords[i].strength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Invalidate */
|
|
||||||
tp->data.points = 0;
|
|
||||||
|
|
||||||
portEXIT_CRITICAL(&tp->data.lock);
|
|
||||||
|
|
||||||
return (*point_num > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static esp_err_t esp_lcd_touch_axs5106_del(esp_lcd_touch_handle_t tp)
|
|
||||||
{
|
|
||||||
assert(tp != NULL);
|
|
||||||
|
|
||||||
/* Reset GPIO pin settings */
|
|
||||||
if (tp->config.int_gpio_num != GPIO_NUM_NC)
|
|
||||||
{
|
|
||||||
gpio_reset_pin(tp->config.int_gpio_num);
|
|
||||||
if (tp->config.interrupt_callback)
|
|
||||||
{
|
|
||||||
gpio_isr_handler_remove(tp->config.int_gpio_num);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Reset GPIO pin settings */
|
|
||||||
if (tp->config.rst_gpio_num != GPIO_NUM_NC)
|
|
||||||
{
|
|
||||||
gpio_reset_pin(tp->config.rst_gpio_num);
|
|
||||||
}
|
|
||||||
|
|
||||||
free(tp);
|
|
||||||
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static esp_err_t touch_axs5106_i2c_write(esp_lcd_touch_handle_t tp, uint8_t reg, uint8_t *data, uint8_t len)
|
|
||||||
{
|
|
||||||
assert(tp != NULL);
|
|
||||||
return esp_lcd_panel_io_tx_param(tp->io, reg, data, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
static esp_err_t touch_axs5106_i2c_read(esp_lcd_touch_handle_t tp, uint8_t reg, uint8_t *data, uint8_t len)
|
|
||||||
{
|
|
||||||
assert(tp != NULL);
|
|
||||||
assert(data != NULL);
|
|
||||||
return esp_lcd_panel_io_rx_param(tp->io, reg, data, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
static esp_err_t touch_axs5106_init(esp_lcd_touch_handle_t tp)
|
|
||||||
{
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static esp_err_t touch_axs5106_reset(esp_lcd_touch_handle_t tp)
|
|
||||||
{
|
|
||||||
assert(tp != NULL);
|
|
||||||
|
|
||||||
if (tp->config.rst_gpio_num != GPIO_NUM_NC)
|
|
||||||
{
|
|
||||||
ESP_RETURN_ON_ERROR(gpio_set_level(tp->config.rst_gpio_num, tp->config.levels.reset), TAG, "GPIO set level error!");
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(10));
|
|
||||||
ESP_RETURN_ON_ERROR(gpio_set_level(tp->config.rst_gpio_num, !tp->config.levels.reset), TAG, "GPIO set level error!");
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(10));
|
|
||||||
}
|
|
||||||
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @file
|
|
||||||
* @brief ESP LCD touch: AXS5106
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "esp_lcd_touch.h"
|
|
||||||
#include "driver/i2c_master.h"
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Create a new AXS5106 touch driver
|
|
||||||
*
|
|
||||||
* @note The I2C communication should be initialized before use this function.
|
|
||||||
*
|
|
||||||
* @param io LCD/Touch panel IO handle
|
|
||||||
* @param config: Touch configuration
|
|
||||||
* @param out_touch: Touch instance handle
|
|
||||||
* @return
|
|
||||||
* - ESP_OK on success
|
|
||||||
* - ESP_ERR_NO_MEM if there is no memory for allocating main structure
|
|
||||||
*/
|
|
||||||
esp_err_t esp_lcd_touch_new_i2c_axs5106(const esp_lcd_panel_io_handle_t io, const esp_lcd_touch_config_t *config, esp_lcd_touch_handle_t *out_touch);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief I2C address of the AXS5106 controller
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
#define ESP_LCD_TOUCH_IO_I2C_AXS5106_ADDRESS (0x63)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Touch IO configuration structure
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
#define ESP_LCD_TOUCH_IO_I2C_AXS5106_CONFIG() \
|
|
||||||
{ \
|
|
||||||
.dev_addr = ESP_LCD_TOUCH_IO_I2C_AXS5106_ADDRESS, \
|
|
||||||
.control_phase_bytes = 1, \
|
|
||||||
.dc_bit_offset = 0, \
|
|
||||||
.lcd_cmd_bits = 8, \
|
|
||||||
.flags = \
|
|
||||||
{ \
|
|
||||||
.disable_control_phase = 1, \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
|
|
||||||
|
|
||||||
idf_component_register(
|
|
||||||
SRCS ${SOURCE_FILES}
|
|
||||||
INCLUDE_DIRS "Source"
|
|
||||||
REQUIRES Tactility esp_lvgl_port driver
|
|
||||||
)
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# Button Control
|
|
||||||
|
|
||||||
A driver for navigating apps with 1 or more GPIO buttons.
|
|
||||||
@@ -1,222 +0,0 @@
|
|||||||
#include "ButtonControl.h"
|
|
||||||
|
|
||||||
#include <Tactility/app/App.h>
|
|
||||||
#include <tactility/log.h>
|
|
||||||
|
|
||||||
#include <esp_lvgl_port.h>
|
|
||||||
|
|
||||||
constexpr auto* TAG = "ButtonControl";
|
|
||||||
|
|
||||||
ButtonControl::ButtonControl(const std::vector<PinConfiguration>& pinConfigurations)
|
|
||||||
: buttonQueue(20, sizeof(ButtonEvent)),
|
|
||||||
pinConfigurations(pinConfigurations) {
|
|
||||||
|
|
||||||
pinStates.resize(pinConfigurations.size());
|
|
||||||
|
|
||||||
// Build isrArgs with one entry per unique physical pin, then configure GPIO.
|
|
||||||
isrArgs.reserve(pinConfigurations.size());
|
|
||||||
for (size_t i = 0; i < pinConfigurations.size(); i++) {
|
|
||||||
const auto pin = static_cast<gpio_num_t>(pinConfigurations[i].pin);
|
|
||||||
|
|
||||||
// Skip if this physical pin was already seen.
|
|
||||||
bool seen = false;
|
|
||||||
for (const auto& arg : isrArgs) {
|
|
||||||
if (arg.pin == pin) { seen = true; break; }
|
|
||||||
}
|
|
||||||
if (seen) continue;
|
|
||||||
|
|
||||||
gpio_config_t io_conf = {
|
|
||||||
.pin_bit_mask = 1ULL << pin,
|
|
||||||
.mode = GPIO_MODE_INPUT,
|
|
||||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
|
||||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
|
||||||
.intr_type = GPIO_INTR_ANYEDGE,
|
|
||||||
};
|
|
||||||
esp_err_t err = gpio_config(&io_conf);
|
|
||||||
if (err != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to configure GPIO %d: %s", static_cast<int>(pin), esp_err_to_name(err));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// isrArgs is reserved upfront; push_back will not reallocate, keeping addresses stable
|
|
||||||
// for gpio_isr_handler_add() called later in startThread().
|
|
||||||
isrArgs.push_back({ .self = this, .pin = pin });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ButtonControl::~ButtonControl() {
|
|
||||||
if (driverThread != nullptr && driverThread->getState() != tt::Thread::State::Stopped) {
|
|
||||||
stopThread();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ButtonControl::readCallback(lv_indev_t* indev, lv_indev_data_t* data) {
|
|
||||||
// Defaults
|
|
||||||
data->enc_diff = 0;
|
|
||||||
data->state = LV_INDEV_STATE_RELEASED;
|
|
||||||
|
|
||||||
auto* self = static_cast<ButtonControl*>(lv_indev_get_driver_data(indev));
|
|
||||||
|
|
||||||
if (self->mutex.lock(100)) {
|
|
||||||
|
|
||||||
for (int i = 0; i < self->pinConfigurations.size(); i++) {
|
|
||||||
const auto& config = self->pinConfigurations[i];
|
|
||||||
std::vector<PinState>::reference state = self->pinStates[i];
|
|
||||||
const bool trigger = (config.event == Event::ShortPress && state.triggerShortPress) ||
|
|
||||||
(config.event == Event::LongPress && state.triggerLongPress);
|
|
||||||
state.triggerShortPress = false;
|
|
||||||
state.triggerLongPress = false;
|
|
||||||
if (trigger) {
|
|
||||||
switch (config.action) {
|
|
||||||
case Action::UiSelectNext:
|
|
||||||
data->enc_diff = 1;
|
|
||||||
break;
|
|
||||||
case Action::UiSelectPrevious:
|
|
||||||
data->enc_diff = -1;
|
|
||||||
break;
|
|
||||||
case Action::UiPressSelected:
|
|
||||||
data->state = LV_INDEV_STATE_PRESSED;
|
|
||||||
break;
|
|
||||||
case Action::AppClose:
|
|
||||||
tt::app::stop();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self->mutex.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ButtonControl::updatePin(std::vector<PinConfiguration>::const_reference configuration, std::vector<PinState>::reference state, bool pressed) {
|
|
||||||
auto now = tt::kernel::getMillis();
|
|
||||||
|
|
||||||
// Software debounce: ignore edges within 20ms of the last state change.
|
|
||||||
if ((now - state.lastChangeTime) < 20) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
state.lastChangeTime = now;
|
|
||||||
|
|
||||||
if (pressed) {
|
|
||||||
state.pressStartTime = now;
|
|
||||||
state.pressState = true;
|
|
||||||
} else { // released
|
|
||||||
if (state.pressState) {
|
|
||||||
auto time_passed = now - state.pressStartTime;
|
|
||||||
if (time_passed < 500) {
|
|
||||||
LOG_I(TAG, "Short press (%dms)", (int)time_passed);
|
|
||||||
state.triggerShortPress = true;
|
|
||||||
} else {
|
|
||||||
LOG_I(TAG, "Long press (%dms)", (int)time_passed);
|
|
||||||
state.triggerLongPress = true;
|
|
||||||
}
|
|
||||||
state.pressState = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void IRAM_ATTR ButtonControl::gpioIsrHandler(void* arg) {
|
|
||||||
auto* isrArg = static_cast<IsrArg*>(arg);
|
|
||||||
ButtonEvent event {
|
|
||||||
.pin = isrArg->pin,
|
|
||||||
.pressed = gpio_get_level(isrArg->pin) == 0, // active-low: LOW = pressed
|
|
||||||
};
|
|
||||||
// tt::MessageQueue::put() is ISR-safe with timeout=0: it detects ISR context via
|
|
||||||
// xPortInIsrContext() and uses xQueueSendFromISR() + portYIELD_FROM_ISR() internally.
|
|
||||||
isrArg->self->buttonQueue.put(&event, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ButtonControl::driverThreadMain() {
|
|
||||||
ButtonEvent event;
|
|
||||||
while (buttonQueue.get(&event, portMAX_DELAY)) {
|
|
||||||
if (event.pin == GPIO_NUM_NC) {
|
|
||||||
break; // shutdown sentinel
|
|
||||||
}
|
|
||||||
LOG_I(TAG, "Pin %d %s", static_cast<int>(event.pin), event.pressed ? "down" : "up");
|
|
||||||
if (mutex.lock(portMAX_DELAY)) {
|
|
||||||
// Update ALL PinConfiguration entries that share this physical pin.
|
|
||||||
for (size_t i = 0; i < pinConfigurations.size(); i++) {
|
|
||||||
if (static_cast<gpio_num_t>(pinConfigurations[i].pin) == event.pin) {
|
|
||||||
updatePin(pinConfigurations[i], pinStates[i], event.pressed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mutex.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ButtonControl::startThread() {
|
|
||||||
LOG_I(TAG, "Start");
|
|
||||||
|
|
||||||
esp_err_t err = gpio_install_isr_service(ESP_INTR_FLAG_IRAM);
|
|
||||||
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
|
|
||||||
LOG_E(TAG, "Failed to install GPIO ISR service: %s", esp_err_to_name(err));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// isrArgs has one entry per unique physical pin — no duplicate registrations.
|
|
||||||
// Addresses are stable: vector was reserved in constructor and is not modified after that.
|
|
||||||
int handlersAdded = 0;
|
|
||||||
for (auto& arg : isrArgs) {
|
|
||||||
err = gpio_isr_handler_add(arg.pin, gpioIsrHandler, &arg);
|
|
||||||
if (err != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to add ISR for GPIO %d: %s", static_cast<int>(arg.pin), esp_err_to_name(err));
|
|
||||||
for (int i = 0; i < handlersAdded; i++) {
|
|
||||||
gpio_isr_handler_remove(isrArgs[i].pin);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
handlersAdded++;
|
|
||||||
}
|
|
||||||
|
|
||||||
driverThread = std::make_shared<tt::Thread>("ButtonControl", 4096, [this] {
|
|
||||||
driverThreadMain();
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
driverThread->start();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ButtonControl::stopThread() {
|
|
||||||
LOG_I(TAG, "Stop");
|
|
||||||
|
|
||||||
for (const auto& arg : isrArgs) {
|
|
||||||
gpio_isr_handler_remove(arg.pin);
|
|
||||||
}
|
|
||||||
|
|
||||||
ButtonEvent sentinel { .pin = GPIO_NUM_NC, .pressed = false };
|
|
||||||
buttonQueue.put(&sentinel, portMAX_DELAY);
|
|
||||||
|
|
||||||
driverThread->join();
|
|
||||||
driverThread = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ButtonControl::startLvgl(lv_display_t* display) {
|
|
||||||
if (deviceHandle != nullptr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!startThread()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
deviceHandle = lv_indev_create();
|
|
||||||
lv_indev_set_type(deviceHandle, LV_INDEV_TYPE_ENCODER);
|
|
||||||
lv_indev_set_driver_data(deviceHandle, this);
|
|
||||||
lv_indev_set_read_cb(deviceHandle, readCallback);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ButtonControl::stopLvgl() {
|
|
||||||
if (deviceHandle == nullptr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
lv_indev_delete(deviceHandle);
|
|
||||||
deviceHandle = nullptr;
|
|
||||||
|
|
||||||
stopThread();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <Tactility/hal/encoder/EncoderDevice.h>
|
|
||||||
#include <Tactility/hal/gpio/Gpio.h>
|
|
||||||
#include <Tactility/MessageQueue.h>
|
|
||||||
#include <Tactility/Thread.h>
|
|
||||||
|
|
||||||
#include <driver/gpio.h>
|
|
||||||
|
|
||||||
class ButtonControl final : public tt::hal::encoder::EncoderDevice {
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
enum class Event {
|
|
||||||
ShortPress,
|
|
||||||
LongPress
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class Action {
|
|
||||||
UiSelectNext,
|
|
||||||
UiSelectPrevious,
|
|
||||||
UiPressSelected,
|
|
||||||
AppClose,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PinConfiguration {
|
|
||||||
tt::hal::gpio::Pin pin;
|
|
||||||
Event event;
|
|
||||||
Action action;
|
|
||||||
};
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
struct PinState {
|
|
||||||
long pressStartTime = 0;
|
|
||||||
long lastChangeTime = 0;
|
|
||||||
bool pressState = false;
|
|
||||||
bool triggerShortPress = false;
|
|
||||||
bool triggerLongPress = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Queued from ISR to worker thread. pin == GPIO_NUM_NC is a shutdown sentinel. */
|
|
||||||
struct ButtonEvent {
|
|
||||||
gpio_num_t pin;
|
|
||||||
bool pressed;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** One entry per unique physical pin; addresses must remain stable after construction. */
|
|
||||||
struct IsrArg {
|
|
||||||
ButtonControl* self;
|
|
||||||
gpio_num_t pin;
|
|
||||||
};
|
|
||||||
|
|
||||||
lv_indev_t* deviceHandle = nullptr;
|
|
||||||
std::shared_ptr<tt::Thread> driverThread;
|
|
||||||
tt::Mutex mutex;
|
|
||||||
tt::MessageQueue buttonQueue;
|
|
||||||
std::vector<PinConfiguration> pinConfigurations;
|
|
||||||
std::vector<PinState> pinStates;
|
|
||||||
std::vector<IsrArg> isrArgs; // one entry per unique physical pin
|
|
||||||
|
|
||||||
static void updatePin(std::vector<PinConfiguration>::const_reference config, std::vector<PinState>::reference state, bool pressed);
|
|
||||||
|
|
||||||
void driverThreadMain();
|
|
||||||
|
|
||||||
static void readCallback(lv_indev_t* indev, lv_indev_data_t* data);
|
|
||||||
|
|
||||||
static void IRAM_ATTR gpioIsrHandler(void* arg);
|
|
||||||
|
|
||||||
bool startThread();
|
|
||||||
void stopThread();
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
explicit ButtonControl(const std::vector<PinConfiguration>& pinConfigurations);
|
|
||||||
|
|
||||||
~ButtonControl() override;
|
|
||||||
|
|
||||||
std::string getName() const override { return "ButtonControl"; }
|
|
||||||
std::string getDescription() const override { return "ButtonControl input driver"; }
|
|
||||||
|
|
||||||
bool startLvgl(lv_display_t* display) override;
|
|
||||||
bool stopLvgl() override;
|
|
||||||
|
|
||||||
/** Could return nullptr if not started */
|
|
||||||
lv_indev_t* getLvglIndev() override { return deviceHandle; }
|
|
||||||
|
|
||||||
static std::shared_ptr<ButtonControl> createOneButtonControl(tt::hal::gpio::Pin pin) {
|
|
||||||
return std::make_shared<ButtonControl>(std::vector {
|
|
||||||
PinConfiguration {
|
|
||||||
.pin = pin,
|
|
||||||
.event = Event::ShortPress,
|
|
||||||
.action = Action::UiSelectNext
|
|
||||||
},
|
|
||||||
PinConfiguration {
|
|
||||||
.pin = pin,
|
|
||||||
.event = Event::LongPress,
|
|
||||||
.action = Action::UiPressSelected
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::shared_ptr<ButtonControl> createTwoButtonControl(tt::hal::gpio::Pin primaryPin, tt::hal::gpio::Pin secondaryPin) {
|
|
||||||
return std::make_shared<ButtonControl>(std::vector {
|
|
||||||
PinConfiguration {
|
|
||||||
.pin = primaryPin,
|
|
||||||
.event = Event::ShortPress,
|
|
||||||
.action = Action::UiPressSelected
|
|
||||||
},
|
|
||||||
PinConfiguration {
|
|
||||||
.pin = primaryPin,
|
|
||||||
.event = Event::LongPress,
|
|
||||||
.action = Action::AppClose
|
|
||||||
},
|
|
||||||
PinConfiguration {
|
|
||||||
.pin = secondaryPin,
|
|
||||||
.event = Event::ShortPress,
|
|
||||||
.action = Action::UiSelectNext
|
|
||||||
},
|
|
||||||
PinConfiguration {
|
|
||||||
.pin = secondaryPin,
|
|
||||||
.event = Event::LongPress,
|
|
||||||
.action = Action::UiSelectPrevious
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
idf_component_register(
|
|
||||||
SRC_DIRS "Source"
|
|
||||||
INCLUDE_DIRS "Source"
|
|
||||||
REQUIRES Tactility EspLcdCompat esp_lcd_touch_cst816s driver
|
|
||||||
)
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# CST816S
|
|
||||||
|
|
||||||
I2C touch driver for Tactility
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <EspLcdTouch.h>
|
|
||||||
|
|
||||||
class Cst816sTouch 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;
|
|
||||||
esp_lcd_panel_io_handle_t ioHandle = nullptr;
|
|
||||||
esp_lcd_touch_handle_t touchHandle = nullptr;
|
|
||||||
lv_indev_t* deviceHandle = nullptr;
|
|
||||||
|
|
||||||
void cleanup();
|
|
||||||
|
|
||||||
bool createIoHandle(esp_lcd_panel_io_handle_t& ioHandle) override;
|
|
||||||
|
|
||||||
bool createTouchHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_touch_config_t& configuration, esp_lcd_touch_handle_t& touchHandle) override;
|
|
||||||
|
|
||||||
esp_lcd_touch_config_t createEspLcdTouchConfig() override;
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
explicit Cst816sTouch(std::unique_ptr<Configuration> inConfiguration) : configuration(std::move(inConfiguration)) {
|
|
||||||
assert(configuration != nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string getName() const override { return "CST816S"; }
|
|
||||||
std::string getDescription() const override { return "CST816S I2C touch driver"; }
|
|
||||||
};
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
#include "Cst816Touch.h"
|
|
||||||
|
|
||||||
#include <esp_lcd_touch_cst816s.h>
|
|
||||||
|
|
||||||
bool Cst816sTouch::createIoHandle(esp_lcd_panel_io_handle_t& ioHandle) {
|
|
||||||
constexpr esp_lcd_panel_io_i2c_config_t touch_io_config = ESP_LCD_TOUCH_IO_I2C_CST816S_CONFIG();
|
|
||||||
return esp_lcd_new_panel_io_i2c((esp_lcd_i2c_bus_handle_t)configuration->port, &touch_io_config, &ioHandle) == ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Cst816sTouch::createTouchHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_touch_config_t& touchConfiguration, esp_lcd_touch_handle_t& touchHandle) {
|
|
||||||
return esp_lcd_touch_new_i2c_cst816s(ioHandle, &touchConfiguration, &touchHandle) == ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_lcd_touch_config_t Cst816sTouch::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,6 +0,0 @@
|
|||||||
idf_component_register(
|
|
||||||
SRC_DIRS "Source"
|
|
||||||
INCLUDE_DIRS "Source"
|
|
||||||
REQUIRES Tactility epdiy esp_lvgl_port
|
|
||||||
PRIV_REQUIRES esp_timer
|
|
||||||
)
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# EPDiy Display Driver
|
|
||||||
|
|
||||||
A display driver for e-paper/e-ink displays using the EPDiy library. This driver provides LVGL integration and high-level display management for EPD panels.
|
|
||||||
@@ -1,472 +0,0 @@
|
|||||||
#include "EpdiyDisplay.h"
|
|
||||||
|
|
||||||
#include <tactility/check.h>
|
|
||||||
#include <tactility/log.h>
|
|
||||||
|
|
||||||
#include <esp_heap_caps.h>
|
|
||||||
#include <esp_timer.h>
|
|
||||||
|
|
||||||
constexpr const char* TAG = "EpdiyDisplay";
|
|
||||||
|
|
||||||
bool EpdiyDisplay::s_hlInitialized = false;
|
|
||||||
EpdiyHighlevelState EpdiyDisplay::s_hlState = {};
|
|
||||||
|
|
||||||
EpdiyDisplay::EpdiyDisplay(std::unique_ptr<Configuration> inConfiguration)
|
|
||||||
: configuration(std::move(inConfiguration)) {
|
|
||||||
check(configuration != nullptr);
|
|
||||||
check(configuration->board != nullptr);
|
|
||||||
check(configuration->display != nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
EpdiyDisplay::~EpdiyDisplay() {
|
|
||||||
if (lvglDisplay != nullptr) {
|
|
||||||
stopLvgl();
|
|
||||||
}
|
|
||||||
if (initialized) {
|
|
||||||
stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EpdiyDisplay::start() {
|
|
||||||
if (initialized) {
|
|
||||||
LOG_W(TAG, "Already initialized");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize EPDiy low-level hardware
|
|
||||||
epd_init(
|
|
||||||
configuration->board,
|
|
||||||
configuration->display,
|
|
||||||
configuration->initOptions
|
|
||||||
);
|
|
||||||
|
|
||||||
// Set rotation BEFORE initializing highlevel state
|
|
||||||
epd_set_rotation(configuration->rotation);
|
|
||||||
LOG_I(TAG, "Display rotation set to %d", configuration->rotation);
|
|
||||||
|
|
||||||
// Initialize the high-level API only once — epd_hl_init() sets a static flag internally
|
|
||||||
// and there is no matching epd_hl_deinit(). Reuse the existing state on subsequent starts.
|
|
||||||
if (!s_hlInitialized) {
|
|
||||||
s_hlState = epd_hl_init(configuration->waveform);
|
|
||||||
if (s_hlState.front_fb == nullptr) {
|
|
||||||
LOG_E(TAG, "Failed to initialize EPDiy highlevel state");
|
|
||||||
epd_deinit();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
s_hlInitialized = true;
|
|
||||||
LOG_I(TAG, "EPDiy highlevel state initialized");
|
|
||||||
} else {
|
|
||||||
LOG_I(TAG, "Reusing existing EPDiy highlevel state");
|
|
||||||
}
|
|
||||||
|
|
||||||
highlevelState = s_hlState;
|
|
||||||
framebuffer = epd_hl_get_framebuffer(&highlevelState);
|
|
||||||
|
|
||||||
initialized = true;
|
|
||||||
LOG_I(TAG, "EPDiy initialized successfully (%dx%d native, %dx%d rotated)", epd_width(), epd_height(), epd_rotated_display_width(), epd_rotated_display_height());
|
|
||||||
|
|
||||||
// Perform initial clear to ensure clean state
|
|
||||||
LOG_I(TAG, "Performing initial screen clear...");
|
|
||||||
clearScreen();
|
|
||||||
LOG_I(TAG, "Screen cleared");
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EpdiyDisplay::stop() {
|
|
||||||
if (!initialized) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lvglDisplay != nullptr) {
|
|
||||||
stopLvgl();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Power off the display
|
|
||||||
if (powered) {
|
|
||||||
setPowerOn(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deinitialize EPDiy low-level hardware.
|
|
||||||
// The HL framebuffers (s_hlState) are intentionally kept alive: epd_hl_init() has no
|
|
||||||
// matching deinit and sets an internal already_initialized flag, so the HL state must
|
|
||||||
// persist across stop()/start() cycles and be reused on the next start().
|
|
||||||
epd_deinit();
|
|
||||||
|
|
||||||
// Clear instance references to HL state (the static s_hlState still owns the memory)
|
|
||||||
highlevelState = {};
|
|
||||||
framebuffer = nullptr;
|
|
||||||
|
|
||||||
initialized = false;
|
|
||||||
LOG_I(TAG, "EPDiy deinitialized (HL state preserved for restart)");
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void EpdiyDisplay::setPowerOn(bool turnOn) {
|
|
||||||
if (!initialized) {
|
|
||||||
LOG_W(TAG, "Cannot change power state - EPD not initialized");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (powered == turnOn) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (turnOn) {
|
|
||||||
epd_poweron();
|
|
||||||
powered = true;
|
|
||||||
LOG_D(TAG, "EPD power on");
|
|
||||||
} else {
|
|
||||||
epd_poweroff();
|
|
||||||
powered = false;
|
|
||||||
LOG_D(TAG, "EPD power off");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LVGL functions
|
|
||||||
bool EpdiyDisplay::startLvgl() {
|
|
||||||
if (lvglDisplay != nullptr) {
|
|
||||||
LOG_W(TAG, "LVGL already initialized");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!initialized) {
|
|
||||||
LOG_E(TAG, "EPD not initialized, call start() first");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the native display dimensions
|
|
||||||
uint16_t width = epd_width();
|
|
||||||
uint16_t height = epd_height();
|
|
||||||
|
|
||||||
LOG_I(TAG, "Creating LVGL display: %dx%d (EPDiy rotation: %d)", width, height, configuration->rotation);
|
|
||||||
|
|
||||||
// Create LVGL display with native dimensions
|
|
||||||
lvglDisplay = lv_display_create(width, height);
|
|
||||||
if (lvglDisplay == nullptr) {
|
|
||||||
LOG_E(TAG, "Failed to create LVGL display");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// EPD uses 4-bit grayscale (16 levels)
|
|
||||||
// Map to LVGL's L8 format (8-bit grayscale)
|
|
||||||
lv_display_set_color_format(lvglDisplay, LV_COLOR_FORMAT_L8);
|
|
||||||
auto lv_rotation = epdRotationToLvgl(configuration->rotation);
|
|
||||||
lv_display_set_rotation(lvglDisplay, lv_rotation);
|
|
||||||
|
|
||||||
// Allocate LVGL draw buffer (L8 format: 1 byte per pixel)
|
|
||||||
size_t draw_buffer_size = static_cast<size_t>(width) * height;
|
|
||||||
|
|
||||||
lvglDrawBuffer = static_cast<uint8_t*>(heap_caps_malloc(draw_buffer_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT));
|
|
||||||
if (lvglDrawBuffer == nullptr) {
|
|
||||||
LOG_W(TAG, "PSRAM allocation failed for draw buffer, falling back to internal memory");
|
|
||||||
lvglDrawBuffer = static_cast<uint8_t*>(heap_caps_malloc(draw_buffer_size, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lvglDrawBuffer == nullptr) {
|
|
||||||
LOG_E(TAG, "Failed to allocate draw buffer");
|
|
||||||
lv_display_delete(lvglDisplay);
|
|
||||||
lvglDisplay = nullptr;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pre-allocate 4-bit packed pixel buffer used in flushInternal (avoids per-flush heap allocation)
|
|
||||||
// Row stride with odd-width padding: (width + 1) / 2 bytes per row
|
|
||||||
size_t packed_buffer_size = static_cast<size_t>((width + 1) / 2) * static_cast<size_t>(height);
|
|
||||||
packedBuffer = static_cast<uint8_t*>(heap_caps_malloc(packed_buffer_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT));
|
|
||||||
if (packedBuffer == nullptr) {
|
|
||||||
LOG_W(TAG, "PSRAM allocation failed for packed buffer, falling back to internal memory");
|
|
||||||
packedBuffer = static_cast<uint8_t*>(heap_caps_malloc(packed_buffer_size, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (packedBuffer == nullptr) {
|
|
||||||
LOG_E(TAG, "Failed to allocate packed pixel buffer");
|
|
||||||
heap_caps_free(lvglDrawBuffer);
|
|
||||||
lvglDrawBuffer = nullptr;
|
|
||||||
lv_display_delete(lvglDisplay);
|
|
||||||
lvglDisplay = nullptr;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For EPD, we want full refresh mode based on configuration
|
|
||||||
lv_display_render_mode_t render_mode = configuration->fullRefresh
|
|
||||||
? LV_DISPLAY_RENDER_MODE_FULL
|
|
||||||
: LV_DISPLAY_RENDER_MODE_PARTIAL;
|
|
||||||
|
|
||||||
lv_display_set_buffers(lvglDisplay, lvglDrawBuffer, NULL, draw_buffer_size, render_mode);
|
|
||||||
|
|
||||||
// Set flush callback
|
|
||||||
lv_display_set_flush_cb(lvglDisplay, flushCallback);
|
|
||||||
lv_display_set_user_data(lvglDisplay, this);
|
|
||||||
|
|
||||||
// Register rotation change event callback
|
|
||||||
lv_display_add_event_cb(lvglDisplay, rotationEventCallback, LV_EVENT_RESOLUTION_CHANGED, this);
|
|
||||||
LOG_D(TAG, "Registered rotation change event callback");
|
|
||||||
|
|
||||||
// Start touch device if present
|
|
||||||
auto touch_device = getTouchDevice();
|
|
||||||
if (touch_device != nullptr && touch_device->supportsLvgl()) {
|
|
||||||
LOG_D(TAG, "Starting touch device for LVGL");
|
|
||||||
if (!touch_device->startLvgl(lvglDisplay)) {
|
|
||||||
LOG_W(TAG, "Failed to start touch device for LVGL");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_I(TAG, "LVGL display initialized");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EpdiyDisplay::stopLvgl() {
|
|
||||||
if (lvglDisplay == nullptr) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_I(TAG, "Stopping LVGL display");
|
|
||||||
|
|
||||||
// Stop touch device
|
|
||||||
auto touch_device = getTouchDevice();
|
|
||||||
if (touch_device != nullptr) {
|
|
||||||
touch_device->stopLvgl();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lvglDrawBuffer != nullptr) {
|
|
||||||
heap_caps_free(lvglDrawBuffer);
|
|
||||||
lvglDrawBuffer = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (packedBuffer != nullptr) {
|
|
||||||
heap_caps_free(packedBuffer);
|
|
||||||
packedBuffer = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete the LVGL display object
|
|
||||||
lv_display_delete(lvglDisplay);
|
|
||||||
lvglDisplay = nullptr;
|
|
||||||
|
|
||||||
|
|
||||||
LOG_I(TAG, "LVGL display stopped");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void EpdiyDisplay::flushCallback(lv_display_t* display, const lv_area_t* area, uint8_t* pixelMap) {
|
|
||||||
auto* instance = static_cast<EpdiyDisplay*>(lv_display_get_user_data(display));
|
|
||||||
if (instance != nullptr) {
|
|
||||||
uint64_t t0 = esp_timer_get_time();
|
|
||||||
const bool isLast = lv_display_flush_is_last(display);
|
|
||||||
instance->flushInternal(area, pixelMap, isLast);
|
|
||||||
LOG_D(TAG, "flush took %llu us", (unsigned long long)(esp_timer_get_time() - t0));
|
|
||||||
} else {
|
|
||||||
LOG_W(TAG, "flush callback called with null instance");
|
|
||||||
}
|
|
||||||
lv_display_flush_ready(display);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// EPD functions
|
|
||||||
void EpdiyDisplay::clearScreen() {
|
|
||||||
if (!initialized) {
|
|
||||||
LOG_E(TAG, "EPD not initialized");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!powered) {
|
|
||||||
setPowerOn(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
epd_clear();
|
|
||||||
|
|
||||||
// Also clear the framebuffer
|
|
||||||
epd_hl_set_all_white(&highlevelState);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EpdiyDisplay::clearArea(EpdRect area) {
|
|
||||||
if (!initialized) {
|
|
||||||
LOG_E(TAG, "EPD not initialized");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!powered) {
|
|
||||||
setPowerOn(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
epd_clear_area(area);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum EpdDrawError EpdiyDisplay::updateScreen(enum EpdDrawMode mode, int temperature) {
|
|
||||||
if (!initialized) {
|
|
||||||
LOG_E(TAG, "EPD not initialized");
|
|
||||||
return EPD_DRAW_FAILED_ALLOC;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!powered) {
|
|
||||||
setPowerOn(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use defaults if not specified
|
|
||||||
if (mode == MODE_UNKNOWN_WAVEFORM) {
|
|
||||||
mode = configuration->defaultDrawMode;
|
|
||||||
}
|
|
||||||
if (temperature == -1) {
|
|
||||||
temperature = configuration->defaultTemperature;
|
|
||||||
}
|
|
||||||
|
|
||||||
return epd_hl_update_screen(&highlevelState, mode, temperature);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum EpdDrawError EpdiyDisplay::updateArea(EpdRect area, enum EpdDrawMode mode, int temperature) {
|
|
||||||
if (!initialized) {
|
|
||||||
LOG_E(TAG, "EPD not initialized");
|
|
||||||
return EPD_DRAW_FAILED_ALLOC;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!powered) {
|
|
||||||
setPowerOn(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use defaults if not specified
|
|
||||||
if (mode == MODE_UNKNOWN_WAVEFORM) {
|
|
||||||
mode = configuration->defaultDrawMode;
|
|
||||||
}
|
|
||||||
if (temperature == -1) {
|
|
||||||
temperature = configuration->defaultTemperature;
|
|
||||||
}
|
|
||||||
|
|
||||||
return epd_hl_update_area(&highlevelState, mode, temperature, area);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EpdiyDisplay::setAllWhite() {
|
|
||||||
if (!initialized) {
|
|
||||||
LOG_E(TAG, "EPD not initialized");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
epd_hl_set_all_white(&highlevelState);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Internal functions
|
|
||||||
void EpdiyDisplay::flushInternal(const lv_area_t* area, uint8_t* pixelMap, bool isLast) {
|
|
||||||
if (!initialized) {
|
|
||||||
LOG_E(TAG, "Cannot flush - EPD not initialized");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!powered) {
|
|
||||||
setPowerOn(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
const int x = area->x1;
|
|
||||||
const int y = area->y1;
|
|
||||||
const int width = lv_area_get_width(area);
|
|
||||||
const int height = lv_area_get_height(area);
|
|
||||||
|
|
||||||
LOG_D(TAG, "Flushing area: x=%d, y=%d, w=%d, h=%d isLast=%d", x, y, width, height, (int)isLast);
|
|
||||||
|
|
||||||
// Convert L8 (8-bit grayscale, 0=black/255=white) to EPDiy 4-bit (0=black/15=white).
|
|
||||||
// Pack 2 pixels per byte: lower nibble = even column, upper nibble = odd column.
|
|
||||||
// Row stride includes one padding nibble for odd widths to keep rows aligned.
|
|
||||||
// Threshold at 128 (matching FastEPD BB_MODE_1BPP): pixels > 127 → full white (15),
|
|
||||||
// pixels ≤ 127 → full black (0). Maximum contrast for the Mono theme and correct for
|
|
||||||
// MODE_DU which only drives two levels. For greyscale content / MODE_GL16, replace
|
|
||||||
// the threshold with `src[col] >> 4` to preserve intermediate grey levels.
|
|
||||||
const int row_stride = (width + 1) / 2;
|
|
||||||
for (int row = 0; row < height; ++row) {
|
|
||||||
const uint8_t* src = pixelMap + static_cast<size_t>(row) * width;
|
|
||||||
uint8_t* dst = packedBuffer + static_cast<size_t>(row) * row_stride;
|
|
||||||
for (int col = 0; col < width; col += 2) {
|
|
||||||
const uint8_t p0 = (src[col] > 127) ? 15u : 0u;
|
|
||||||
const uint8_t p1 = (col + 1 < width) ? ((src[col + 1] > 127) ? 15u : 0u) : 0u;
|
|
||||||
dst[col / 2] = static_cast<uint8_t>((p1 << 4) | p0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const EpdRect update_area = {
|
|
||||||
.x = x,
|
|
||||||
.y = y,
|
|
||||||
.width = static_cast<uint16_t>(width),
|
|
||||||
.height = static_cast<uint16_t>(height)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Write pixels into EPDiy's framebuffer (no hardware I/O, just memory)
|
|
||||||
epd_draw_rotated_image(update_area, packedBuffer, framebuffer);
|
|
||||||
|
|
||||||
// Only trigger EPD hardware update on the last flush of this render cycle.
|
|
||||||
// EPDiy's epd_prep tasks run at configMAX_PRIORITIES-1 with busy-wait loops; calling
|
|
||||||
// epd_hl_update_area on every partial flush starves IDLE and triggers the task watchdog
|
|
||||||
// during scroll animations. Batching to one hardware update per LVGL render cycle fixes this.
|
|
||||||
if (isLast) {
|
|
||||||
epd_hl_update_screen(
|
|
||||||
&highlevelState,
|
|
||||||
static_cast<EpdDrawMode>(configuration->defaultDrawMode | MODE_PACKING_2PPB),
|
|
||||||
configuration->defaultTemperature
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lv_display_rotation_t EpdiyDisplay::epdRotationToLvgl(enum EpdRotation epdRotation) {
|
|
||||||
// Static lookup table for EPD -> LVGL rotation mapping
|
|
||||||
// EPDiy: LANDSCAPE = 0°, PORTRAIT = 90° CW, INVERTED_LANDSCAPE = 180°, INVERTED_PORTRAIT = 270° CW
|
|
||||||
// LVGL: 0 = 0°, 90 = 90° CW, 180 = 180°, 270 = 270° CW
|
|
||||||
static const lv_display_rotation_t rotationMap[] = {
|
|
||||||
LV_DISPLAY_ROTATION_0, // EPD_ROT_LANDSCAPE (0)
|
|
||||||
LV_DISPLAY_ROTATION_270, // EPD_ROT_PORTRAIT (1) - 90° CW in EPD is 270° in LVGL
|
|
||||||
LV_DISPLAY_ROTATION_180, // EPD_ROT_INVERTED_LANDSCAPE (2)
|
|
||||||
LV_DISPLAY_ROTATION_90 // EPD_ROT_INVERTED_PORTRAIT (3) - 270° CW in EPD is 90° in LVGL
|
|
||||||
};
|
|
||||||
|
|
||||||
// Validate input and return mapped value
|
|
||||||
if (epdRotation >= 0 && epdRotation < 4) {
|
|
||||||
return rotationMap[epdRotation];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default to landscape if invalid
|
|
||||||
return LV_DISPLAY_ROTATION_0;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum EpdRotation EpdiyDisplay::lvglRotationToEpd(lv_display_rotation_t lvglRotation) {
|
|
||||||
// Static lookup table for LVGL -> EPD rotation mapping
|
|
||||||
static const enum EpdRotation rotationMap[] = {
|
|
||||||
EPD_ROT_LANDSCAPE, // LV_DISPLAY_ROTATION_0 (0)
|
|
||||||
EPD_ROT_INVERTED_PORTRAIT, // LV_DISPLAY_ROTATION_90 (1)
|
|
||||||
EPD_ROT_INVERTED_LANDSCAPE, // LV_DISPLAY_ROTATION_180 (2)
|
|
||||||
EPD_ROT_PORTRAIT // LV_DISPLAY_ROTATION_270 (3)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Validate input and return mapped value
|
|
||||||
if (lvglRotation >= LV_DISPLAY_ROTATION_0 && lvglRotation <= LV_DISPLAY_ROTATION_270) {
|
|
||||||
return rotationMap[lvglRotation];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default to landscape if invalid
|
|
||||||
return EPD_ROT_LANDSCAPE;
|
|
||||||
}
|
|
||||||
|
|
||||||
void EpdiyDisplay::rotationEventCallback(lv_event_t* event) {
|
|
||||||
auto* display = static_cast<EpdiyDisplay*>(lv_event_get_user_data(event));
|
|
||||||
if (display == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lv_display_t* lvgl_display = static_cast<lv_display_t*>(lv_event_get_target(event));
|
|
||||||
if (lvgl_display == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lv_display_rotation_t rotation = lv_display_get_rotation(lvgl_display);
|
|
||||||
display->handleRotationChange(rotation);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EpdiyDisplay::handleRotationChange(lv_display_rotation_t lvgl_rotation) {
|
|
||||||
// Map LVGL rotation to EPDiy rotation using lookup table
|
|
||||||
enum EpdRotation epd_rotation = lvglRotationToEpd(lvgl_rotation);
|
|
||||||
|
|
||||||
// Update EPDiy rotation
|
|
||||||
LOG_I(TAG, "LVGL rotation changed to %d, setting EPDiy rotation to %d", lvgl_rotation, epd_rotation);
|
|
||||||
epd_set_rotation(epd_rotation);
|
|
||||||
|
|
||||||
// Update configuration to keep it in sync
|
|
||||||
configuration->rotation = epd_rotation;
|
|
||||||
|
|
||||||
// Log the new dimensions
|
|
||||||
LOG_I(TAG, "Display dimensions after rotation: %dx%d", epd_rotated_display_width(), epd_rotated_display_height());
|
|
||||||
}
|
|
||||||
@@ -1,163 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <Tactility/hal/display/DisplayDevice.h>
|
|
||||||
#include <Tactility/hal/touch/TouchDevice.h>
|
|
||||||
|
|
||||||
#include <epd_highlevel.h>
|
|
||||||
#include <epdiy.h>
|
|
||||||
#include <lvgl.h>
|
|
||||||
#include <memory>
|
|
||||||
#include <cassert>
|
|
||||||
#include <cstdlib>
|
|
||||||
|
|
||||||
class EpdiyDisplay final : public tt::hal::display::DisplayDevice {
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
class Configuration {
|
|
||||||
public:
|
|
||||||
|
|
||||||
Configuration(
|
|
||||||
const EpdBoardDefinition* board,
|
|
||||||
const EpdDisplay_t* display,
|
|
||||||
std::shared_ptr<tt::hal::touch::TouchDevice> touch = nullptr,
|
|
||||||
enum EpdInitOptions initOptions = EPD_OPTIONS_DEFAULT,
|
|
||||||
const EpdWaveform* waveform = EPD_BUILTIN_WAVEFORM,
|
|
||||||
int defaultTemperature = 25,
|
|
||||||
enum EpdDrawMode defaultDrawMode = MODE_GL16,
|
|
||||||
bool fullRefresh = false,
|
|
||||||
enum EpdRotation rotation = EPD_ROT_LANDSCAPE
|
|
||||||
) : board(board),
|
|
||||||
display(display),
|
|
||||||
touch(std::move(touch)),
|
|
||||||
initOptions(initOptions),
|
|
||||||
waveform(waveform),
|
|
||||||
defaultTemperature(defaultTemperature),
|
|
||||||
defaultDrawMode(defaultDrawMode),
|
|
||||||
fullRefresh(fullRefresh),
|
|
||||||
rotation(rotation) {
|
|
||||||
check(board != nullptr);
|
|
||||||
check(display != nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
const EpdBoardDefinition* board;
|
|
||||||
const EpdDisplay_t* display;
|
|
||||||
std::shared_ptr<tt::hal::touch::TouchDevice> touch;
|
|
||||||
enum EpdInitOptions initOptions;
|
|
||||||
const EpdWaveform* waveform;
|
|
||||||
int defaultTemperature;
|
|
||||||
enum EpdDrawMode defaultDrawMode;
|
|
||||||
bool fullRefresh;
|
|
||||||
enum EpdRotation rotation;
|
|
||||||
};
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
std::unique_ptr<Configuration> configuration;
|
|
||||||
lv_display_t* _Nullable lvglDisplay = nullptr;
|
|
||||||
EpdiyHighlevelState highlevelState = {};
|
|
||||||
uint8_t* framebuffer = nullptr;
|
|
||||||
uint8_t* lvglDrawBuffer = nullptr;
|
|
||||||
uint8_t* packedBuffer = nullptr; // Pre-allocated 4-bit packed pixel buffer for flushInternal
|
|
||||||
bool initialized = false;
|
|
||||||
bool powered = false;
|
|
||||||
|
|
||||||
// epd_hl_init() sets an internal already_initialized flag and has no matching deinit.
|
|
||||||
// We track first-time init statically and keep the HL state alive across stop()/start() cycles.
|
|
||||||
static bool s_hlInitialized;
|
|
||||||
static EpdiyHighlevelState s_hlState;
|
|
||||||
|
|
||||||
static void flushCallback(lv_display_t* display, const lv_area_t* area, uint8_t* pixelMap);
|
|
||||||
void flushInternal(const lv_area_t* area, uint8_t* pixelMap, bool isLast);
|
|
||||||
|
|
||||||
static void rotationEventCallback(lv_event_t* event);
|
|
||||||
void handleRotationChange(lv_display_rotation_t rotation);
|
|
||||||
|
|
||||||
// Rotation mapping helpers
|
|
||||||
static lv_display_rotation_t epdRotationToLvgl(enum EpdRotation epdRotation);
|
|
||||||
static enum EpdRotation lvglRotationToEpd(lv_display_rotation_t lvglRotation);
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
explicit EpdiyDisplay(std::unique_ptr<Configuration> inConfiguration);
|
|
||||||
|
|
||||||
~EpdiyDisplay() override;
|
|
||||||
|
|
||||||
std::string getName() const override { return "EPDiy"; }
|
|
||||||
|
|
||||||
std::string getDescription() const override {
|
|
||||||
return "E-Ink display powered by EPDiy library";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Device lifecycle
|
|
||||||
bool start() override;
|
|
||||||
bool stop() override;
|
|
||||||
|
|
||||||
// Power control
|
|
||||||
void setPowerOn(bool turnOn) override;
|
|
||||||
bool isPoweredOn() const override { return powered; }
|
|
||||||
bool supportsPowerControl() const override { return true; }
|
|
||||||
|
|
||||||
// Touch device
|
|
||||||
std::shared_ptr<tt::hal::touch::TouchDevice> _Nullable getTouchDevice() override {
|
|
||||||
return configuration->touch;
|
|
||||||
}
|
|
||||||
|
|
||||||
// LVGL support
|
|
||||||
bool supportsLvgl() const override { return true; }
|
|
||||||
bool startLvgl() override;
|
|
||||||
bool stopLvgl() override;
|
|
||||||
lv_display_t* _Nullable getLvglDisplay() const override { return lvglDisplay; }
|
|
||||||
|
|
||||||
// DisplayDriver (not supported for EPD)
|
|
||||||
bool supportsDisplayDriver() const override { return false; }
|
|
||||||
std::shared_ptr<tt::hal::display::DisplayDriver> _Nullable getDisplayDriver() override {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// EPD specific functions
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a reference to the framebuffer
|
|
||||||
*/
|
|
||||||
uint8_t* getFramebuffer() {
|
|
||||||
return epd_hl_get_framebuffer(&highlevelState);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear the screen by flashing it
|
|
||||||
*/
|
|
||||||
void clearScreen();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear an area by flashing it
|
|
||||||
*/
|
|
||||||
void clearArea(EpdRect area);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Manually trigger a screen update
|
|
||||||
* @param mode The draw mode to use (defaults to configuration default)
|
|
||||||
* @param temperature Temperature in °C (defaults to configuration default)
|
|
||||||
*/
|
|
||||||
enum EpdDrawError updateScreen(
|
|
||||||
enum EpdDrawMode mode = MODE_UNKNOWN_WAVEFORM,
|
|
||||||
int temperature = -1
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update a specific area of the screen
|
|
||||||
* @param area The area to update
|
|
||||||
* @param mode The draw mode to use (defaults to configuration default)
|
|
||||||
* @param temperature Temperature in °C (defaults to configuration default)
|
|
||||||
*/
|
|
||||||
enum EpdDrawError updateArea(
|
|
||||||
EpdRect area,
|
|
||||||
enum EpdDrawMode mode = MODE_UNKNOWN_WAVEFORM,
|
|
||||||
int temperature = -1
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the display to all white
|
|
||||||
*/
|
|
||||||
void setAllWhite();
|
|
||||||
};
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "EpdiyDisplay.h"
|
|
||||||
#include <epd_board.h>
|
|
||||||
#include <epd_display.h>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper class to create EPDiy displays with common configurations
|
|
||||||
*/
|
|
||||||
class EpdiyDisplayHelper {
|
|
||||||
public:
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a display for M5Paper S3
|
|
||||||
* @param touch Optional touch device
|
|
||||||
* @param temperature Display temperature in °C (default: 20)
|
|
||||||
* @param drawMode Default draw mode (default: MODE_DU)
|
|
||||||
* @param fullRefresh Use full refresh mode (default: false for partial updates)
|
|
||||||
* @param rotation Display rotation (default: EPD_ROT_PORTRAIT)
|
|
||||||
*/
|
|
||||||
static std::shared_ptr<EpdiyDisplay> createM5PaperS3Display(
|
|
||||||
std::shared_ptr<tt::hal::touch::TouchDevice> touch = nullptr,
|
|
||||||
int temperature = 20,
|
|
||||||
enum EpdDrawMode drawMode = MODE_DU,
|
|
||||||
bool fullRefresh = false,
|
|
||||||
enum EpdRotation rotation = EPD_ROT_PORTRAIT
|
|
||||||
) {
|
|
||||||
auto config = std::make_unique<EpdiyDisplay::Configuration>(
|
|
||||||
&epd_board_m5papers3,
|
|
||||||
&ED047TC1,
|
|
||||||
touch,
|
|
||||||
static_cast<EpdInitOptions>(EPD_LUT_1K | EPD_FEED_QUEUE_32),
|
|
||||||
static_cast<const EpdWaveform*>(EPD_BUILTIN_WAVEFORM),
|
|
||||||
temperature,
|
|
||||||
drawMode,
|
|
||||||
fullRefresh,
|
|
||||||
rotation
|
|
||||||
);
|
|
||||||
|
|
||||||
return std::make_shared<EpdiyDisplay>(std::move(config));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
idf_component_register(
|
|
||||||
SRC_DIRS "Source"
|
|
||||||
INCLUDE_DIRS "Source"
|
|
||||||
REQUIRES Tactility EspLcdCompat esp_lcd_touch_ft5x06 driver
|
|
||||||
)
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# FT5x06
|
|
||||||
|
|
||||||
A Tactility driver for FT5x06 touch.
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
#include "Ft5x06Touch.h"
|
|
||||||
|
|
||||||
#include <esp_lcd_touch_ft5x06.h>
|
|
||||||
#include <esp_err.h>
|
|
||||||
#include <esp_lvgl_port.h>
|
|
||||||
|
|
||||||
bool Ft5x06Touch::createIoHandle(esp_lcd_panel_io_handle_t& outHandle) {
|
|
||||||
esp_lcd_panel_io_i2c_config_t io_config = ESP_LCD_TOUCH_IO_I2C_FT5x06_CONFIG();
|
|
||||||
return esp_lcd_new_panel_io_i2c(configuration->port, &io_config, &outHandle) == ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Ft5x06Touch::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_ft5x06(ioHandle, &configuration, &panelHandle) == ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_lcd_touch_config_t Ft5x06Touch::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,70 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <Tactility/hal/touch/TouchDevice.h>
|
|
||||||
#include <Tactility/TactilityCore.h>
|
|
||||||
#include <driver/i2c.h>
|
|
||||||
|
|
||||||
#include <EspLcdTouch.h>
|
|
||||||
|
|
||||||
class Ft5x06Touch 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 Ft5x06Touch(std::unique_ptr<Configuration> inConfiguration) : configuration(std::move(inConfiguration)) {
|
|
||||||
assert(configuration != nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string getName() const override { return "FT5x06"; }
|
|
||||||
|
|
||||||
std::string getDescription() const override { return "FT5x06 I2C touch driver"; }
|
|
||||||
};
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
idf_component_register(
|
|
||||||
SRC_DIRS "Source"
|
|
||||||
INCLUDE_DIRS "Source"
|
|
||||||
REQUIRES Tactility EspLcdCompat esp_lcd_touch_ft6336u driver
|
|
||||||
)
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# FT6x36
|
|
||||||
|
|
||||||
A Tactility driver for FT6x36 touch.
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
#include "Ft6x36Touch.h"
|
|
||||||
|
|
||||||
#include <esp_lcd_touch_ft6x36.h>
|
|
||||||
#include <esp_err.h>
|
|
||||||
#include <esp_lvgl_port.h>
|
|
||||||
|
|
||||||
bool Ft6x36Touch::createIoHandle(esp_lcd_panel_io_handle_t& outHandle) {
|
|
||||||
esp_lcd_panel_io_i2c_config_t io_config = ESP_LCD_TOUCH_IO_I2C_FT6x36_CONFIG();
|
|
||||||
return esp_lcd_new_panel_io_i2c(configuration->port, &io_config, &outHandle) == ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Ft6x36Touch::createTouchHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_touch_config_t& configuration, esp_lcd_touch_handle_t& panelHandle) {
|
|
||||||
return esp_lcd_touch_new_i2c_ft6x36(ioHandle, &configuration, &panelHandle) == ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_lcd_touch_config_t Ft6x36Touch::createEspLcdTouchConfig() {
|
|
||||||
return {
|
|
||||||
.x_max = configuration->xMax,
|
|
||||||
.y_max = configuration->yMax,
|
|
||||||
.rst_gpio_num = configuration->pinReset,
|
|
||||||
.int_gpio_num = configuration->pinInterrupt,
|
|
||||||
.levels = {
|
|
||||||
.reset = configuration->pinResetLevel,
|
|
||||||
.interrupt = configuration->pinInterruptLevel,
|
|
||||||
},
|
|
||||||
.flags = {
|
|
||||||
.swap_xy = configuration->swapXy,
|
|
||||||
.mirror_x = configuration->mirrorX,
|
|
||||||
.mirror_y = configuration->mirrorY,
|
|
||||||
},
|
|
||||||
.process_coordinates = nullptr,
|
|
||||||
.interrupt_callback = nullptr,
|
|
||||||
.user_data = nullptr,
|
|
||||||
.driver_data = nullptr
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <Tactility/hal/touch/TouchDevice.h>
|
|
||||||
#include <Tactility/TactilityCore.h>
|
|
||||||
#include <driver/i2c.h>
|
|
||||||
|
|
||||||
#include <EspLcdTouch.h>
|
|
||||||
|
|
||||||
class Ft6x36Touch 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 Ft6x36Touch(std::unique_ptr<Configuration> inConfiguration) : configuration(std::move(inConfiguration)) {
|
|
||||||
assert(configuration != nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string getName() const override { return "FT6x36"; }
|
|
||||||
|
|
||||||
std::string getDescription() const override { return "FT6x36 I2C touch driver"; }
|
|
||||||
};
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
idf_component_register(
|
|
||||||
SRC_DIRS "Source"
|
|
||||||
INCLUDE_DIRS "Source"
|
|
||||||
REQUIRES Tactility driver esp_lcd_gc9a01 EspLcdCompat
|
|
||||||
)
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# GC9A01
|
|
||||||
|
|
||||||
A basic ESP32 LVGL driver for GC9A01 displays.
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
#include "Gc9a01Display.h"
|
|
||||||
|
|
||||||
#include <tactility/log.h>
|
|
||||||
|
|
||||||
#include <esp_lcd_gc9a01.h>
|
|
||||||
#include <esp_lcd_panel_commands.h>
|
|
||||||
#include <esp_lvgl_port.h>
|
|
||||||
|
|
||||||
constexpr auto* TAG = "GC9A01";
|
|
||||||
|
|
||||||
bool Gc9a01Display::createIoHandle(esp_lcd_panel_io_handle_t& outHandle) {
|
|
||||||
LOG_I(TAG, "Starting");
|
|
||||||
|
|
||||||
const esp_lcd_panel_io_spi_config_t panel_io_config = {
|
|
||||||
.cs_gpio_num = configuration->csPin,
|
|
||||||
.dc_gpio_num = configuration->dcPin,
|
|
||||||
.spi_mode = 0,
|
|
||||||
.pclk_hz = configuration->pixelClockFrequency,
|
|
||||||
.trans_queue_depth = configuration->transactionQueueDepth,
|
|
||||||
.on_color_trans_done = nullptr,
|
|
||||||
.user_ctx = nullptr,
|
|
||||||
.lcd_cmd_bits = 8,
|
|
||||||
.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 = 0,
|
|
||||||
.sio_mode = 0,
|
|
||||||
.lsb_first = 0,
|
|
||||||
.cs_high_active = 0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (esp_lcd_new_panel_io_spi(configuration->spiHostDevice, &panel_io_config, &outHandle) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to create panel");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Gc9a01Display::createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t& panelHandle) {
|
|
||||||
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 = nullptr
|
|
||||||
};
|
|
||||||
|
|
||||||
if (esp_lcd_new_panel_gc9a01(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_swap_xy(panelHandle, configuration->swapXY) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to swap XY ");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (esp_lcd_panel_mirror(panelHandle, configuration->mirrorX, configuration->mirrorY) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to set panel to mirror");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (esp_lcd_panel_invert_color(panelHandle, configuration->invertColor) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to set panel to invert");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (esp_lcd_panel_disp_on_off(panelHandle, true) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to turn display on");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
lvgl_port_display_cfg_t Gc9a01Display::getLvglPortDisplayConfig(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t panelHandle) {
|
|
||||||
return lvgl_port_display_cfg_t {
|
|
||||||
.io_handle = ioHandle,
|
|
||||||
.panel_handle = panelHandle,
|
|
||||||
.control_handle = nullptr,
|
|
||||||
.buffer_size = configuration->bufferSize,
|
|
||||||
.double_buffer = true,
|
|
||||||
.trans_size = 0,
|
|
||||||
.hres = configuration->horizontalResolution,
|
|
||||||
.vres = configuration->verticalResolution,
|
|
||||||
.monochrome = false,
|
|
||||||
.rotation = {
|
|
||||||
.swap_xy = configuration->swapXY,
|
|
||||||
.mirror_x = configuration->mirrorX,
|
|
||||||
.mirror_y = configuration->mirrorY,
|
|
||||||
},
|
|
||||||
.color_format = LV_COLOR_FORMAT_RGB565,
|
|
||||||
.flags = {
|
|
||||||
.buff_dma = true,
|
|
||||||
.buff_spiram = false,
|
|
||||||
.sw_rotate = false,
|
|
||||||
.swap_bytes = true,
|
|
||||||
.full_refresh = false,
|
|
||||||
.direct_mode = false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <EspLcdDisplay.h>
|
|
||||||
#include <Tactility/hal/display/DisplayDevice.h>
|
|
||||||
|
|
||||||
#include <driver/gpio.h>
|
|
||||||
#include <driver/spi_common.h>
|
|
||||||
#include <esp_lcd_panel_io.h>
|
|
||||||
#include <esp_lcd_types.h>
|
|
||||||
#include <functional>
|
|
||||||
#include <lvgl.h>
|
|
||||||
|
|
||||||
class Gc9a01Display final : public EspLcdDisplay {
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
class Configuration {
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
Configuration(
|
|
||||||
spi_host_device_t spiHostDevice,
|
|
||||||
gpio_num_t csPin,
|
|
||||||
gpio_num_t dcPin,
|
|
||||||
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),
|
|
||||||
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_NC;
|
|
||||||
unsigned int pixelClockFrequency = 80'000'000; // Hertz
|
|
||||||
size_t transactionQueueDepth = 10;
|
|
||||||
unsigned int horizontalResolution;
|
|
||||||
unsigned int verticalResolution;
|
|
||||||
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;
|
|
||||||
std::shared_ptr<tt::hal::touch::TouchDevice> touch;
|
|
||||||
std::function<void(uint8_t)> _Nullable backlightDutyFunction = nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
std::unique_ptr<Configuration> configuration;
|
|
||||||
|
|
||||||
bool createIoHandle(esp_lcd_panel_io_handle_t& ioHandle) 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;
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
explicit Gc9a01Display(std::unique_ptr<Configuration> inConfiguration) :
|
|
||||||
configuration(std::move(inConfiguration)
|
|
||||||
) {}
|
|
||||||
|
|
||||||
std::string getName() const override { return "GC9A01"; }
|
|
||||||
|
|
||||||
std::string getDescription() const override { return "GC9A01 display"; }
|
|
||||||
|
|
||||||
std::shared_ptr<tt::hal::touch::TouchDevice> _Nullable 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; }
|
|
||||||
};
|
|
||||||
|
|
||||||
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
idf_component_register(
|
|
||||||
SRC_DIRS "Source"
|
|
||||||
INCLUDE_DIRS "Source"
|
|
||||||
REQUIRES Tactility EspLcdCompat esp_lcd_touch_gt911 driver
|
|
||||||
)
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# GT911
|
|
||||||
|
|
||||||
GT911 touch driver for Tactility.
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
#include "Gt911Touch.h"
|
|
||||||
|
|
||||||
#include <tactility/log.h>
|
|
||||||
|
|
||||||
#include <esp_lcd_io_i2c.h>
|
|
||||||
#include <esp_lcd_touch_gt911.h>
|
|
||||||
#include <esp_err.h>
|
|
||||||
#include <tactility/device.h>
|
|
||||||
#include <tactility/driver.h>
|
|
||||||
#include <tactility/drivers/esp32_i2c.h>
|
|
||||||
#include <tactility/drivers/esp32_i2c_master.h>
|
|
||||||
#include <tactility/drivers/i2c_controller.h>
|
|
||||||
|
|
||||||
constexpr auto* TAG = "GT911";
|
|
||||||
|
|
||||||
bool Gt911Touch::createIoHandle(esp_lcd_panel_io_handle_t& outHandle) {
|
|
||||||
esp_lcd_panel_io_i2c_config_t io_config = ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG();
|
|
||||||
|
|
||||||
auto* i2c = configuration->i2cController;
|
|
||||||
|
|
||||||
if (i2c_controller_has_device_at_address(i2c, ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS, pdMS_TO_TICKS(10)) == ERROR_NONE) {
|
|
||||||
io_config.dev_addr = ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS;
|
|
||||||
} else if (i2c_controller_has_device_at_address(i2c, ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP, pdMS_TO_TICKS(10)) == ERROR_NONE) {
|
|
||||||
io_config.dev_addr = ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP;
|
|
||||||
} else {
|
|
||||||
LOG_E(TAG, "No device found on I2C bus");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Legacy I2C implementation
|
|
||||||
auto* driver = device_get_driver(i2c);
|
|
||||||
if (driver_is_compatible(driver, "espressif,esp32-i2c")) {
|
|
||||||
auto port = static_cast<const Esp32I2cConfig*>(i2c->config)->port;
|
|
||||||
return esp_lcd_new_panel_io_i2c_v1(port, &io_config, &outHandle) == ESP_OK;
|
|
||||||
} else if (driver_is_compatible(driver, "espressif,esp32-i2c-master")) {
|
|
||||||
auto bus = esp32_i2c_master_get_bus_handle(i2c);
|
|
||||||
io_config.scl_speed_hz = esp32_i2c_master_get_clock_frequency(configuration->i2cController);
|
|
||||||
return esp_lcd_new_panel_io_i2c_v2(bus, &io_config, &outHandle) == ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_E(TAG, "Unsupported I2C driver");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Gt911Touch::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_gt911(ioHandle, &configuration, &panelHandle) == ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_lcd_touch_config_t Gt911Touch::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 = static_cast<unsigned int>(configuration->swapXy),
|
|
||||||
.mirror_x = static_cast<unsigned int>(configuration->mirrorX),
|
|
||||||
.mirror_y = static_cast<unsigned int>(configuration->mirrorY),
|
|
||||||
},
|
|
||||||
.process_coordinates = nullptr,
|
|
||||||
.interrupt_callback = nullptr,
|
|
||||||
.user_data = nullptr,
|
|
||||||
.driver_data = nullptr
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <Tactility/hal/touch/TouchDevice.h>
|
|
||||||
#include <Tactility/TactilityCore.h>
|
|
||||||
|
|
||||||
#include <EspLcdTouch.h>
|
|
||||||
|
|
||||||
struct Device;
|
|
||||||
|
|
||||||
class Gt911Touch final : public EspLcdTouch {
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
class Configuration {
|
|
||||||
public:
|
|
||||||
|
|
||||||
Configuration(
|
|
||||||
::Device* i2cController,
|
|
||||||
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
|
|
||||||
) : i2cController(i2cController),
|
|
||||||
xMax(xMax),
|
|
||||||
yMax(yMax),
|
|
||||||
swapXy(swapXy),
|
|
||||||
mirrorX(mirrorX),
|
|
||||||
mirrorY(mirrorY),
|
|
||||||
pinReset(pinReset),
|
|
||||||
pinInterrupt(pinInterrupt),
|
|
||||||
pinResetLevel(pinResetLevel),
|
|
||||||
pinInterruptLevel(pinInterruptLevel)
|
|
||||||
{}
|
|
||||||
|
|
||||||
::Device* i2cController;
|
|
||||||
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 Gt911Touch(std::unique_ptr<Configuration> inConfiguration) : configuration(std::move(inConfiguration)) {
|
|
||||||
assert(configuration != nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string getName() const override { return "GT911"; }
|
|
||||||
|
|
||||||
std::string getDescription() const override { return "GT911 I2C touch driver"; }
|
|
||||||
};
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
idf_component_register(
|
|
||||||
SRC_DIRS "Source"
|
|
||||||
INCLUDE_DIRS "Source"
|
|
||||||
REQUIRES Tactility EspLcdCompat esp_lcd_ili9341 driver
|
|
||||||
)
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# ILI934x
|
|
||||||
|
|
||||||
A basic ESP32 LVGL driver for ILI9341 and ILI9342 displays.
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
#include "Ili934xDisplay.h"
|
|
||||||
|
|
||||||
#include <esp_lcd_ili9341.h>
|
|
||||||
#include <esp_lvgl_port.h>
|
|
||||||
|
|
||||||
std::shared_ptr<EspLcdConfiguration> Ili934xDisplay::createEspLcdConfiguration(const Configuration& configuration) {
|
|
||||||
return std::make_shared<EspLcdConfiguration>(EspLcdConfiguration {
|
|
||||||
.horizontalResolution = configuration.horizontalResolution,
|
|
||||||
.verticalResolution = configuration.verticalResolution,
|
|
||||||
.gapX = configuration.gapX,
|
|
||||||
.gapY = configuration.gapY,
|
|
||||||
.monochrome = false,
|
|
||||||
.swapXY = configuration.swapXY,
|
|
||||||
.mirrorX = configuration.mirrorX,
|
|
||||||
.mirrorY = configuration.mirrorY,
|
|
||||||
.invertColor = configuration.invertColor,
|
|
||||||
.bufferSize = configuration.bufferSize,
|
|
||||||
.touch = configuration.touch,
|
|
||||||
.backlightDutyFunction = configuration.backlightDutyFunction,
|
|
||||||
.resetPin = configuration.resetPin,
|
|
||||||
.lvglColorFormat = LV_COLOR_FORMAT_RGB565,
|
|
||||||
.lvglSwapBytes = configuration.swapBytes,
|
|
||||||
.rgbElementOrder = configuration.rgbElementOrder,
|
|
||||||
.bitsPerPixel = 16
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_lcd_panel_dev_config_t Ili934xDisplay::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 = espLcdConfiguration->bitsPerPixel,
|
|
||||||
.flags = {
|
|
||||||
.reset_active_high = 0
|
|
||||||
},
|
|
||||||
.vendor_config = nullptr
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Ili934xDisplay::createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_panel_dev_config_t& panelConfig, esp_lcd_panel_handle_t& panelHandle) {
|
|
||||||
return esp_lcd_new_panel_ili9341(ioHandle, &panelConfig, &panelHandle) == ESP_OK;
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <EspLcdSpiDisplay.h>
|
|
||||||
|
|
||||||
#include <driver/gpio.h>
|
|
||||||
#include <functional>
|
|
||||||
#include <lvgl.h>
|
|
||||||
|
|
||||||
class Ili934xDisplay final : public EspLcdSpiDisplay {
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
/** Minimal set of overrides for EspLcdConfiguration */
|
|
||||||
struct Configuration {
|
|
||||||
unsigned int horizontalResolution;
|
|
||||||
unsigned int verticalResolution;
|
|
||||||
int gapX;
|
|
||||||
int gapY;
|
|
||||||
bool swapXY;
|
|
||||||
bool mirrorX;
|
|
||||||
bool mirrorY;
|
|
||||||
bool invertColor;
|
|
||||||
bool swapBytes;
|
|
||||||
uint32_t bufferSize; // Pixel count, not byte count. Set to 0 for default (1/10th of display size)
|
|
||||||
std::shared_ptr<tt::hal::touch::TouchDevice> touch;
|
|
||||||
std::function<void(uint8_t)> backlightDutyFunction; // Nullable
|
|
||||||
gpio_num_t resetPin;
|
|
||||||
lcd_rgb_element_order_t rgbElementOrder;
|
|
||||||
};
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
static std::shared_ptr<EspLcdConfiguration> createEspLcdConfiguration(const Configuration& configuration);
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
explicit Ili934xDisplay(const Configuration& configuration, const std::shared_ptr<SpiConfiguration>& spiConfiguration, bool hasGammaCurves) :
|
|
||||||
EspLcdSpiDisplay(
|
|
||||||
createEspLcdConfiguration(configuration),
|
|
||||||
spiConfiguration,
|
|
||||||
(hasGammaCurves ? 4 : 0)
|
|
||||||
)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string getName() const override { return "ILI934x"; }
|
|
||||||
|
|
||||||
std::string getDescription() const override { return "ILI934x display"; }
|
|
||||||
};
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
idf_component_register(SRCS "esp_lcd_jd9853.c"
|
|
||||||
INCLUDE_DIRS "include"
|
|
||||||
REQUIRES "esp_lcd" "driver")
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
# JD9853
|
|
||||||
|
|
||||||
SPI display driver.
|
|
||||||
|
|
||||||
Source: https://files.waveshare.com/wiki/1.47inch%20Touch%20LCD/1.47inch_Touch_LCD_Demo_ESP32.zip
|
|
||||||
|
|
||||||
License: Apache 2.0
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
idf_component_register(
|
|
||||||
SRC_DIRS "Source"
|
|
||||||
INCLUDE_DIRS "Source"
|
|
||||||
REQUIRES Tactility driver
|
|
||||||
)
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# PWM Backlight Driver
|
|
||||||
|
|
||||||
A very basic driver to control an LCD panel backlight with a PWM signal.
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
#include "PwmBacklight.h"
|
|
||||||
|
|
||||||
#include <tactility/log.h>
|
|
||||||
|
|
||||||
constexpr auto* TAG = "PwmBacklight";
|
|
||||||
|
|
||||||
namespace driver::pwmbacklight {
|
|
||||||
|
|
||||||
static bool isBacklightInitialized = false;
|
|
||||||
static gpio_num_t backlightPin = GPIO_NUM_NC;
|
|
||||||
static ledc_timer_t backlightTimer;
|
|
||||||
static ledc_channel_t backlightChannel;
|
|
||||||
|
|
||||||
bool init(gpio_num_t pin, uint32_t frequencyHz, ledc_timer_t timer, ledc_channel_t channel) {
|
|
||||||
backlightPin = pin;
|
|
||||||
backlightTimer = timer;
|
|
||||||
backlightChannel = channel;
|
|
||||||
|
|
||||||
LOG_I(TAG, "Init");
|
|
||||||
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, "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, "Channel config failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
isBacklightInitialized = true;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool setBacklightDuty(uint8_t duty) {
|
|
||||||
if (!isBacklightInitialized) {
|
|
||||||
LOG_E(TAG, "Not initialized");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return ledc_set_duty(LEDC_LOW_SPEED_MODE, backlightChannel, duty) == ESP_OK &&
|
|
||||||
ledc_update_duty(LEDC_LOW_SPEED_MODE, backlightChannel) == ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <driver/ledc.h>
|
|
||||||
#include <driver/gpio.h>
|
|
||||||
|
|
||||||
namespace driver::pwmbacklight {
|
|
||||||
|
|
||||||
bool init(gpio_num_t pin, uint32_t frequencyHz = 40000, ledc_timer_t timer = LEDC_TIMER_0, ledc_channel_t channel = LEDC_CHANNEL_0);
|
|
||||||
|
|
||||||
bool setBacklightDuty(uint8_t duty);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
idf_component_register(
|
|
||||||
SRC_DIRS "Source"
|
|
||||||
INCLUDE_DIRS "Source"
|
|
||||||
REQUIRES Tactility EspLcdCompat
|
|
||||||
)
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# RGB Display Driver
|
|
||||||
|
|
||||||
This driver is used for displays that use multiple parallel data lanes.
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
#include "RgbDisplay.h"
|
|
||||||
|
|
||||||
#include <tactility/check.h>
|
|
||||||
#include <Tactility/hal/touch/TouchDevice.h>
|
|
||||||
#include <tactility/log.h>
|
|
||||||
|
|
||||||
#include <esp_err.h>
|
|
||||||
#include <esp_lcd_panel_rgb.h>
|
|
||||||
#include <esp_lcd_panel_ops.h>
|
|
||||||
#include <esp_lvgl_port.h>
|
|
||||||
|
|
||||||
constexpr auto* TAG = "RgbDisplay";
|
|
||||||
|
|
||||||
RgbDisplay::~RgbDisplay() {
|
|
||||||
check(
|
|
||||||
displayDriver == nullptr || displayDriver.use_count() == 0,
|
|
||||||
"DisplayDriver is still in use. This will cause memory access violations."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RgbDisplay::start() {
|
|
||||||
LOG_I(TAG, "Starting");
|
|
||||||
|
|
||||||
if (esp_lcd_new_rgb_panel(&configuration->panelConfig, &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_swap_xy(panelHandle, configuration->swapXY) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to swap XY");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (esp_lcd_panel_mirror(panelHandle, configuration->mirrorX, configuration->mirrorY) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to set panel to mirror");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (esp_lcd_panel_invert_color(panelHandle, configuration->invertColor) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to set panel to invert");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RgbDisplay::stop() {
|
|
||||||
if (lvglDisplay != nullptr) {
|
|
||||||
stopLvgl();
|
|
||||||
lvglDisplay = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (panelHandle != nullptr && esp_lcd_panel_del(panelHandle) != ESP_OK) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (displayDriver != nullptr && displayDriver.use_count() > 1) {
|
|
||||||
LOG_W(TAG, "DisplayDriver is still in use.");
|
|
||||||
}
|
|
||||||
|
|
||||||
auto touch_device = getTouchDevice();
|
|
||||||
if (touch_device != nullptr) {
|
|
||||||
touch_device->startLvgl(lvglDisplay);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool RgbDisplay::startLvgl() {
|
|
||||||
assert(lvglDisplay == nullptr);
|
|
||||||
|
|
||||||
if (displayDriver != nullptr && displayDriver.use_count() > 1) {
|
|
||||||
LOG_W(TAG, "DisplayDriver is still in use.");
|
|
||||||
}
|
|
||||||
|
|
||||||
auto display_config = getLvglPortDisplayConfig();
|
|
||||||
|
|
||||||
const lvgl_port_display_rgb_cfg_t rgb_config = {
|
|
||||||
.flags = {
|
|
||||||
.bb_mode = configuration->bufferConfiguration.bounceBufferMode,
|
|
||||||
.avoid_tearing = configuration->bufferConfiguration.avoidTearing
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
lvglDisplay = lvgl_port_add_disp_rgb(&display_config, &rgb_config);
|
|
||||||
LOG_I(TAG, "Finished");
|
|
||||||
|
|
||||||
auto touch_device = getTouchDevice();
|
|
||||||
if (touch_device != nullptr) {
|
|
||||||
touch_device->startLvgl(lvglDisplay);
|
|
||||||
}
|
|
||||||
|
|
||||||
return lvglDisplay != nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RgbDisplay::stopLvgl() {
|
|
||||||
if (lvglDisplay == nullptr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto touch_device = getTouchDevice();
|
|
||||||
if (touch_device != nullptr) {
|
|
||||||
touch_device->stopLvgl();
|
|
||||||
}
|
|
||||||
|
|
||||||
lvgl_port_remove_disp(lvglDisplay);
|
|
||||||
lvglDisplay = nullptr;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
lvgl_port_display_cfg_t RgbDisplay::getLvglPortDisplayConfig() const {
|
|
||||||
return {
|
|
||||||
.io_handle = nullptr,
|
|
||||||
.panel_handle = panelHandle,
|
|
||||||
.control_handle = nullptr,
|
|
||||||
.buffer_size = configuration->bufferConfiguration.size,
|
|
||||||
.double_buffer = configuration->bufferConfiguration.doubleBuffer,
|
|
||||||
.trans_size = 0,
|
|
||||||
.hres = configuration->panelConfig.timings.h_res,
|
|
||||||
.vres = configuration->panelConfig.timings.v_res,
|
|
||||||
.monochrome = false,
|
|
||||||
.rotation = {
|
|
||||||
.swap_xy = configuration->swapXY,
|
|
||||||
.mirror_x = configuration->mirrorX,
|
|
||||||
.mirror_y = configuration->mirrorY,
|
|
||||||
},
|
|
||||||
.color_format = configuration->colorFormat,
|
|
||||||
.flags = {
|
|
||||||
.buff_dma = !configuration->bufferConfiguration.useSpi,
|
|
||||||
.buff_spiram = configuration->bufferConfiguration.useSpi,
|
|
||||||
.sw_rotate = false,
|
|
||||||
.swap_bytes = false,
|
|
||||||
.full_refresh = false,
|
|
||||||
.direct_mode = false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <Tactility/hal/display/DisplayDevice.h>
|
|
||||||
#include <EspLcdDisplayDriver.h>
|
|
||||||
#include <esp_lcd_panel_rgb.h>
|
|
||||||
#include <esp_lvgl_port_disp.h>
|
|
||||||
|
|
||||||
class RgbDisplay final : public tt::hal::display::DisplayDevice {
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
struct BufferConfiguration final {
|
|
||||||
uint32_t size; // Size in pixel count. 0 means default, which is 1/15 of the screen size
|
|
||||||
bool useSpi;
|
|
||||||
bool doubleBuffer;
|
|
||||||
bool bounceBufferMode;
|
|
||||||
bool avoidTearing;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Configuration final {
|
|
||||||
public:
|
|
||||||
|
|
||||||
esp_lcd_rgb_panel_config_t panelConfig;
|
|
||||||
BufferConfiguration bufferConfiguration;
|
|
||||||
std::shared_ptr<tt::hal::touch::TouchDevice> touch;
|
|
||||||
lv_color_format_t colorFormat;
|
|
||||||
bool swapXY;
|
|
||||||
bool mirrorX;
|
|
||||||
bool mirrorY;
|
|
||||||
bool invertColor;
|
|
||||||
std::function<void(uint8_t)> _Nullable backlightDutyFunction;
|
|
||||||
|
|
||||||
Configuration(
|
|
||||||
esp_lcd_rgb_panel_config_t panelConfig,
|
|
||||||
BufferConfiguration bufferConfiguration,
|
|
||||||
std::shared_ptr<tt::hal::touch::TouchDevice> touch,
|
|
||||||
lv_color_format_t colorFormat,
|
|
||||||
bool swapXY = false,
|
|
||||||
bool mirrorX = false,
|
|
||||||
bool mirrorY = false,
|
|
||||||
bool invertColor = false,
|
|
||||||
std::function<void(uint8_t)> _Nullable backlightDutyFunction = nullptr
|
|
||||||
) : panelConfig(panelConfig),
|
|
||||||
bufferConfiguration(bufferConfiguration),
|
|
||||||
touch(std::move(touch)),
|
|
||||||
colorFormat(colorFormat),
|
|
||||||
swapXY(swapXY),
|
|
||||||
mirrorX(mirrorX),
|
|
||||||
mirrorY(mirrorY),
|
|
||||||
invertColor(invertColor),
|
|
||||||
backlightDutyFunction(std::move(backlightDutyFunction)) {
|
|
||||||
if (this->bufferConfiguration.size == 0) {
|
|
||||||
auto horizontal_resolution = panelConfig.timings.h_res;
|
|
||||||
auto vertical_resolution = panelConfig.timings.v_res;
|
|
||||||
this->bufferConfiguration.size = horizontal_resolution * vertical_resolution / 15;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
std::unique_ptr<Configuration> _Nullable configuration = nullptr;
|
|
||||||
esp_lcd_panel_handle_t _Nullable panelHandle = nullptr;
|
|
||||||
lv_display_t* _Nullable lvglDisplay = nullptr;
|
|
||||||
std::shared_ptr<tt::hal::display::DisplayDriver> _Nullable displayDriver;
|
|
||||||
|
|
||||||
lvgl_port_display_cfg_t getLvglPortDisplayConfig() const;
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
explicit RgbDisplay(std::unique_ptr<Configuration> inConfiguration) : configuration(std::move(inConfiguration)) {
|
|
||||||
assert(configuration != nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
~RgbDisplay() override;
|
|
||||||
|
|
||||||
std::string getName() const override { return "RGB Display"; }
|
|
||||||
std::string getDescription() const override { return "RGB Display"; }
|
|
||||||
|
|
||||||
bool start() override;
|
|
||||||
|
|
||||||
bool stop() override;
|
|
||||||
|
|
||||||
bool supportsLvgl() const override { return true; }
|
|
||||||
|
|
||||||
bool startLvgl() override;
|
|
||||||
|
|
||||||
bool stopLvgl() override;
|
|
||||||
|
|
||||||
std::shared_ptr<tt::hal::touch::TouchDevice> _Nullable 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; }
|
|
||||||
|
|
||||||
lv_display_t* _Nullable getLvglDisplay() const override { return lvglDisplay; }
|
|
||||||
|
|
||||||
// TODO: Fix driver and re-enable
|
|
||||||
bool supportsDisplayDriver() const override { return false; }
|
|
||||||
|
|
||||||
std::shared_ptr<tt::hal::display::DisplayDriver> _Nullable getDisplayDriver() override {
|
|
||||||
if (displayDriver == nullptr) {
|
|
||||||
auto config = getLvglPortDisplayConfig();
|
|
||||||
displayDriver = std::make_shared<EspLcdDisplayDriver>(panelHandle, config.hres, config.vres, tt::hal::display::ColorFormat::RGB888);
|
|
||||||
}
|
|
||||||
return displayDriver;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
idf_component_register(
|
|
||||||
SRC_DIRS "Source"
|
|
||||||
INCLUDE_DIRS "Source"
|
|
||||||
REQUIRES Tactility driver EspLcdCompat
|
|
||||||
)
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# ST7789
|
|
||||||
|
|
||||||
A basic ESP32 LVGL driver for ST7789 displays.
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
#include "St7789Display.h"
|
|
||||||
|
|
||||||
#include <esp_lcd_panel_st7789.h>
|
|
||||||
|
|
||||||
std::shared_ptr<EspLcdConfiguration> St7789Display::createEspLcdConfiguration(const Configuration& configuration) {
|
|
||||||
return std::make_shared<EspLcdConfiguration>(EspLcdConfiguration {
|
|
||||||
.horizontalResolution = configuration.horizontalResolution,
|
|
||||||
.verticalResolution = configuration.verticalResolution,
|
|
||||||
.gapX = configuration.gapX,
|
|
||||||
.gapY = configuration.gapY,
|
|
||||||
.monochrome = false,
|
|
||||||
.swapXY = configuration.swapXY,
|
|
||||||
.mirrorX = configuration.mirrorX,
|
|
||||||
.mirrorY = configuration.mirrorY,
|
|
||||||
.invertColor = configuration.invertColor,
|
|
||||||
.bufferSize = configuration.bufferSize,
|
|
||||||
.buffSpiram = configuration.buffSpiram,
|
|
||||||
.touch = configuration.touch,
|
|
||||||
.backlightDutyFunction = configuration.backlightDutyFunction,
|
|
||||||
.resetPin = configuration.resetPin,
|
|
||||||
.lvglColorFormat = LV_COLOR_FORMAT_RGB565,
|
|
||||||
.lvglSwapBytes = configuration.lvglSwapBytes,
|
|
||||||
.rgbElementOrder = configuration.rgbElementOrder,
|
|
||||||
.bitsPerPixel = 16,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_lcd_panel_dev_config_t St7789Display::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 = espLcdConfiguration->bitsPerPixel,
|
|
||||||
.flags = {
|
|
||||||
.reset_active_high = false
|
|
||||||
},
|
|
||||||
.vendor_config = nullptr
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
bool St7789Display::createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_panel_dev_config_t& panelConfig, esp_lcd_panel_handle_t& panelHandle) {
|
|
||||||
return esp_lcd_new_panel_st7789(ioHandle, &panelConfig, &panelHandle) == ESP_OK;
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <EspLcdSpiDisplay.h>
|
|
||||||
|
|
||||||
#include <driver/gpio.h>
|
|
||||||
#include <functional>
|
|
||||||
#include <lvgl.h>
|
|
||||||
|
|
||||||
class St7789Display final : public EspLcdSpiDisplay {
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
/** Minimal set of overrides for EspLcdConfiguration */
|
|
||||||
struct Configuration {
|
|
||||||
unsigned int horizontalResolution;
|
|
||||||
unsigned int verticalResolution;
|
|
||||||
int gapX;
|
|
||||||
int gapY;
|
|
||||||
bool swapXY;
|
|
||||||
bool mirrorX;
|
|
||||||
bool mirrorY;
|
|
||||||
bool invertColor;
|
|
||||||
uint32_t bufferSize; // Pixel count, not byte count. Set to 0 for default (1/10th of display size)
|
|
||||||
std::shared_ptr<tt::hal::touch::TouchDevice> touch;
|
|
||||||
std::function<void(uint8_t)> _Nullable backlightDutyFunction;
|
|
||||||
gpio_num_t resetPin;
|
|
||||||
bool lvglSwapBytes;
|
|
||||||
bool buffSpiram = false;
|
|
||||||
lcd_rgb_element_order_t rgbElementOrder = LCD_RGB_ELEMENT_ORDER_RGB;
|
|
||||||
};
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
static std::shared_ptr<EspLcdConfiguration> createEspLcdConfiguration(const Configuration& configuration);
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
explicit St7789Display(const Configuration& configuration, const std::shared_ptr<SpiConfiguration>& spiConfiguration) :
|
|
||||||
EspLcdSpiDisplay(
|
|
||||||
createEspLcdConfiguration(configuration),
|
|
||||||
spiConfiguration,
|
|
||||||
4
|
|
||||||
)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string getName() const override { return "ST7789"; }
|
|
||||||
|
|
||||||
std::string getDescription() const override { return "ST7789 display"; }
|
|
||||||
};
|
|
||||||
|
|
||||||
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
idf_component_register(
|
|
||||||
SRC_DIRS "Source"
|
|
||||||
INCLUDE_DIRS "Source"
|
|
||||||
REQUIRES Tactility EspLcdCompat esp_lcd_st7796 driver
|
|
||||||
)
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# ST7796
|
|
||||||
|
|
||||||
A basic ESP32 LVGL driver for ST7796 parallel i8080 displays.
|
|
||||||
@@ -1,275 +0,0 @@
|
|||||||
#include "St7796i8080Display.h"
|
|
||||||
#include <tactility/log.h>
|
|
||||||
#include <driver/gpio.h>
|
|
||||||
#include <esp_lcd_panel_io.h>
|
|
||||||
#include <esp_lcd_panel_ops.h>
|
|
||||||
#include <esp_lvgl_port.h>
|
|
||||||
#include <freertos/FreeRTOS.h>
|
|
||||||
#include <freertos/task.h>
|
|
||||||
#include <lvgl.h>
|
|
||||||
|
|
||||||
constexpr auto* TAG = "St7796i8080Display";
|
|
||||||
static St7796i8080Display* g_display_instance = nullptr;
|
|
||||||
|
|
||||||
St7796i8080Display::St7796i8080Display(const Configuration& config)
|
|
||||||
: configuration(config), lock(std::make_shared<std::mutex>()) {
|
|
||||||
|
|
||||||
// Validate configuration
|
|
||||||
if (!configuration.isValid()) {
|
|
||||||
LOG_E(TAG, "Invalid configuration: resolution must be set");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool St7796i8080Display::createI80Bus() {
|
|
||||||
LOG_I(TAG, "Creating I80 bus");
|
|
||||||
|
|
||||||
// Create I80 bus configuration
|
|
||||||
esp_lcd_i80_bus_config_t bus_cfg = {
|
|
||||||
.dc_gpio_num = configuration.dcPin,
|
|
||||||
.wr_gpio_num = configuration.wrPin,
|
|
||||||
.clk_src = LCD_CLK_SRC_PLL160M,
|
|
||||||
.data_gpio_nums = {
|
|
||||||
configuration.dataPins[0], configuration.dataPins[1],
|
|
||||||
configuration.dataPins[2], configuration.dataPins[3],
|
|
||||||
configuration.dataPins[4], configuration.dataPins[5],
|
|
||||||
configuration.dataPins[6], configuration.dataPins[7],
|
|
||||||
},
|
|
||||||
.bus_width = configuration.busWidth,
|
|
||||||
.max_transfer_bytes = configuration.bufferSize * sizeof(uint16_t),
|
|
||||||
.psram_trans_align = 64,
|
|
||||||
.sram_trans_align = 4
|
|
||||||
};
|
|
||||||
|
|
||||||
if (esp_lcd_new_i80_bus(&bus_cfg, &i80BusHandle) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to create I80 bus");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool St7796i8080Display::createPanelIO() {
|
|
||||||
LOG_I(TAG, "Creating panel IO");
|
|
||||||
|
|
||||||
// Create panel IO
|
|
||||||
esp_lcd_panel_io_i80_config_t io_cfg = {
|
|
||||||
.cs_gpio_num = configuration.csPin,
|
|
||||||
.pclk_hz = configuration.pixelClockFrequency,
|
|
||||||
.trans_queue_depth = configuration.transactionQueueDepth,
|
|
||||||
.on_color_trans_done = nullptr,
|
|
||||||
.user_ctx = nullptr,
|
|
||||||
.lcd_cmd_bits = configuration.lcdCmdBits,
|
|
||||||
.lcd_param_bits = configuration.lcdParamBits,
|
|
||||||
.dc_levels = {
|
|
||||||
.dc_idle_level = configuration.dcLevels.dcIdleLevel,
|
|
||||||
.dc_cmd_level = configuration.dcLevels.dcCmdLevel,
|
|
||||||
.dc_dummy_level = configuration.dcLevels.dcDummyLevel,
|
|
||||||
.dc_data_level = configuration.dcLevels.dcDataLevel,
|
|
||||||
},
|
|
||||||
.flags = {
|
|
||||||
.cs_active_high = configuration.flags.csActiveHigh,
|
|
||||||
.reverse_color_bits = configuration.flags.reverseColorBits,
|
|
||||||
.swap_color_bytes = configuration.flags.swapColorBytes,
|
|
||||||
.pclk_active_neg = configuration.flags.pclkActiveNeg,
|
|
||||||
.pclk_idle_low = configuration.flags.pclkIdleLow
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (esp_lcd_new_panel_io_i80(i80BusHandle, &io_cfg, &ioHandle) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to create panel");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool St7796i8080Display::createPanel() {
|
|
||||||
LOG_I(TAG, "Configuring panel");
|
|
||||||
|
|
||||||
// Create ST7796 panel
|
|
||||||
esp_lcd_panel_dev_config_t panel_config = {
|
|
||||||
.reset_gpio_num = configuration.resetPin,
|
|
||||||
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR,
|
|
||||||
.data_endian = LCD_RGB_DATA_ENDIAN_LITTLE,
|
|
||||||
.bits_per_pixel = 16,
|
|
||||||
.flags = {
|
|
||||||
.reset_active_high = false
|
|
||||||
},
|
|
||||||
.vendor_config = nullptr
|
|
||||||
};
|
|
||||||
|
|
||||||
if (esp_lcd_new_panel_st7796(ioHandle, &panel_config, &panelHandle) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to create panel");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset panel
|
|
||||||
if (esp_lcd_panel_reset(panelHandle) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to reset panel");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize panel
|
|
||||||
if (esp_lcd_panel_init(panelHandle) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to init panel");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set swap XY
|
|
||||||
if (esp_lcd_panel_swap_xy(panelHandle, configuration.swapXY) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to swap XY ");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set mirror
|
|
||||||
if (esp_lcd_panel_mirror(panelHandle, configuration.mirrorX, configuration.mirrorY) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to set panel to mirror");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set inversion
|
|
||||||
if (esp_lcd_panel_invert_color(panelHandle, configuration.invertColor) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to set panel to invert");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Turn on display
|
|
||||||
if (esp_lcd_panel_disp_on_off(panelHandle, true) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to turn display on");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool St7796i8080Display::start() {
|
|
||||||
LOG_I(TAG, "Initializing I8080 ST7796 Display hardware...");
|
|
||||||
|
|
||||||
// Calculate buffer size if needed
|
|
||||||
configuration.calculateBufferSize();
|
|
||||||
|
|
||||||
// Allocate buffer
|
|
||||||
size_t buffer_size = configuration.bufferSize * LV_COLOR_FORMAT_GET_SIZE(LV_COLOR_FORMAT_RGB565);
|
|
||||||
displayBuffer = (uint8_t*)heap_caps_malloc(buffer_size, MALLOC_CAP_DMA);
|
|
||||||
if (!displayBuffer) {
|
|
||||||
LOG_E(TAG, "Failed to allocate display buffer");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create I80 bus
|
|
||||||
if (!createI80Bus()) {
|
|
||||||
stop();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create panel IO
|
|
||||||
if (!createPanelIO()) {
|
|
||||||
stop();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create panel
|
|
||||||
if (!createPanel()) {
|
|
||||||
stop();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_I(TAG, "Display hardware initialized");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool St7796i8080Display::stop() {
|
|
||||||
// Turn off display
|
|
||||||
if (panelHandle) {
|
|
||||||
esp_lcd_panel_disp_on_off(panelHandle, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Destroy in reverse order: panel, IO, bus
|
|
||||||
if (panelHandle) {
|
|
||||||
esp_lcd_panel_del(panelHandle);
|
|
||||||
panelHandle = nullptr;
|
|
||||||
}
|
|
||||||
if (ioHandle) {
|
|
||||||
esp_lcd_panel_io_del(ioHandle);
|
|
||||||
ioHandle = nullptr;
|
|
||||||
}
|
|
||||||
if (i80BusHandle) {
|
|
||||||
esp_lcd_del_i80_bus(i80BusHandle);
|
|
||||||
i80BusHandle = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Free buffer 1
|
|
||||||
if (displayBuffer) {
|
|
||||||
heap_caps_free(displayBuffer);
|
|
||||||
displayBuffer = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Turn off backlight
|
|
||||||
if (configuration.backlightDutyFunction) {
|
|
||||||
configuration.backlightDutyFunction(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool St7796i8080Display::startLvgl() {
|
|
||||||
LOG_I(TAG, "Initializing LVGL for ST7796 display");
|
|
||||||
|
|
||||||
// Don't reinitialize hardware if it's already done
|
|
||||||
if (!ioHandle) {
|
|
||||||
LOG_I(TAG, "Hardware not initialized, calling start()");
|
|
||||||
if (!start()) {
|
|
||||||
LOG_E(TAG, "Hardware initialization failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
LOG_I(TAG, "Hardware already initialized, skipping");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create LVGL display using lvgl_port
|
|
||||||
lvgl_port_display_cfg_t display_cfg = {
|
|
||||||
.io_handle = ioHandle,
|
|
||||||
.panel_handle = panelHandle,
|
|
||||||
.control_handle = nullptr,
|
|
||||||
.buffer_size = configuration.bufferSize,
|
|
||||||
.double_buffer = false,
|
|
||||||
.trans_size = 0,
|
|
||||||
.hres = configuration.horizontalResolution,
|
|
||||||
.vres = configuration.verticalResolution,
|
|
||||||
.monochrome = false,
|
|
||||||
.rotation = {
|
|
||||||
.swap_xy = configuration.swapXY,
|
|
||||||
.mirror_x = configuration.mirrorX,
|
|
||||||
.mirror_y = configuration.mirrorY,
|
|
||||||
},
|
|
||||||
.color_format = LV_COLOR_FORMAT_RGB565,
|
|
||||||
.flags = {
|
|
||||||
.buff_dma = true,
|
|
||||||
.buff_spiram = false,
|
|
||||||
.sw_rotate = false,
|
|
||||||
.swap_bytes = false,
|
|
||||||
.full_refresh = false,
|
|
||||||
.direct_mode = false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create the LVGL display
|
|
||||||
lvglDisplay = lvgl_port_add_disp(&display_cfg);
|
|
||||||
if (!lvglDisplay) {
|
|
||||||
LOG_E(TAG, "Failed to create LVGL display");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_display_instance = this;
|
|
||||||
LOG_I(TAG, "LVGL display created successfully");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool St7796i8080Display::stopLvgl() {
|
|
||||||
if (lvglDisplay) {
|
|
||||||
lvgl_port_remove_disp(lvglDisplay);
|
|
||||||
lvglDisplay = nullptr;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <Tactility/hal/display/DisplayDevice.h>
|
|
||||||
#include <esp_lcd_panel_io.h>
|
|
||||||
#include <esp_lcd_types.h>
|
|
||||||
#include <esp_lcd_st7796.h>
|
|
||||||
#include <driver/gpio.h>
|
|
||||||
#include <array>
|
|
||||||
#include <memory>
|
|
||||||
#include <functional>
|
|
||||||
#include <mutex>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
class St7796i8080Display : public tt::hal::display::DisplayDevice {
|
|
||||||
public:
|
|
||||||
struct Configuration {
|
|
||||||
// Pin configuration
|
|
||||||
gpio_num_t csPin;
|
|
||||||
gpio_num_t dcPin;
|
|
||||||
gpio_num_t wrPin;
|
|
||||||
std::array<gpio_num_t, 8> dataPins;
|
|
||||||
gpio_num_t resetPin;
|
|
||||||
gpio_num_t backlightPin;
|
|
||||||
|
|
||||||
// Display resolution configuration
|
|
||||||
uint16_t horizontalResolution;
|
|
||||||
uint16_t verticalResolution;
|
|
||||||
|
|
||||||
// Bus configuration
|
|
||||||
unsigned int pixelClockFrequency = 20 * 1000 * 1000; // 20MHz default
|
|
||||||
size_t busWidth = 8; // 8-bit bus
|
|
||||||
size_t transactionQueueDepth = 10;
|
|
||||||
size_t bufferSize = 0; // Will be calculated if 0
|
|
||||||
|
|
||||||
// LCD command/parameter configuration
|
|
||||||
int lcdCmdBits = 8;
|
|
||||||
int lcdParamBits = 8;
|
|
||||||
|
|
||||||
// DC line level configuration
|
|
||||||
struct {
|
|
||||||
bool dcIdleLevel = 0;
|
|
||||||
bool dcCmdLevel = 0;
|
|
||||||
bool dcDummyLevel = 0;
|
|
||||||
bool dcDataLevel = 1;
|
|
||||||
} dcLevels;
|
|
||||||
|
|
||||||
// Bus flags
|
|
||||||
struct {
|
|
||||||
bool csActiveHigh = false;
|
|
||||||
bool reverseColorBits = false;
|
|
||||||
bool swapColorBytes = true;
|
|
||||||
bool pclkActiveNeg = false;
|
|
||||||
bool pclkIdleLow = false;
|
|
||||||
} flags;
|
|
||||||
|
|
||||||
// Display configuration
|
|
||||||
bool swapXY = false;
|
|
||||||
bool mirrorX = false;
|
|
||||||
bool mirrorY = false;
|
|
||||||
bool invertColor = true;
|
|
||||||
|
|
||||||
// Additional features
|
|
||||||
std::shared_ptr<tt::hal::touch::TouchDevice> touch = nullptr;
|
|
||||||
std::function<void(uint8_t)> backlightDutyFunction = nullptr;
|
|
||||||
|
|
||||||
// Basic constructor - requires resolution to be set separately
|
|
||||||
Configuration(gpio_num_t cs, gpio_num_t dc, gpio_num_t wr,
|
|
||||||
std::array<gpio_num_t, 8> data, gpio_num_t rst, gpio_num_t bl)
|
|
||||||
: csPin(cs), dcPin(dc), wrPin(wr), dataPins(data), resetPin(rst), backlightPin(bl),
|
|
||||||
horizontalResolution(0), verticalResolution(0) {} // Initialize to 0
|
|
||||||
|
|
||||||
// Method to calculate buffer size after resolution is set
|
|
||||||
void calculateBufferSize() {
|
|
||||||
if (bufferSize == 0 && horizontalResolution > 0 && verticalResolution > 0) {
|
|
||||||
bufferSize = horizontalResolution * verticalResolution / 10;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validation method
|
|
||||||
bool isValid() const {
|
|
||||||
return horizontalResolution > 0 && verticalResolution > 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private:
|
|
||||||
Configuration configuration;
|
|
||||||
esp_lcd_i80_bus_handle_t i80BusHandle = nullptr;
|
|
||||||
esp_lcd_panel_io_handle_t ioHandle = nullptr;
|
|
||||||
esp_lcd_panel_handle_t panelHandle = nullptr;
|
|
||||||
lv_display_t* lvglDisplay = nullptr;
|
|
||||||
std::shared_ptr<std::mutex> lock;
|
|
||||||
uint8_t* displayBuffer = nullptr;
|
|
||||||
|
|
||||||
// Internal initialization methods
|
|
||||||
bool createI80Bus();
|
|
||||||
bool createPanelIO();
|
|
||||||
bool createPanel();
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit St7796i8080Display(const Configuration& config);
|
|
||||||
lv_display_t* getLvglDisplay() const override { return lvglDisplay; }
|
|
||||||
std::string getName() const override { return "I8080 ST7796"; }
|
|
||||||
std::string getDescription() const override { return "I8080-based ST7796 display"; }
|
|
||||||
|
|
||||||
// Lifecycle
|
|
||||||
bool start() override;
|
|
||||||
bool stop() override;
|
|
||||||
bool startLvgl() override;
|
|
||||||
bool stopLvgl() override;
|
|
||||||
|
|
||||||
// Capabilities
|
|
||||||
bool supportsLvgl() const override { return true; }
|
|
||||||
bool supportsDisplayDriver() const override { return false; }
|
|
||||||
bool supportsBacklightDuty() const override { return configuration.backlightDutyFunction != nullptr; }
|
|
||||||
|
|
||||||
// Touch and backlight
|
|
||||||
std::shared_ptr<tt::hal::touch::TouchDevice> getTouchDevice() override { return configuration.touch; }
|
|
||||||
std::shared_ptr<tt::hal::display::DisplayDriver> getDisplayDriver() override { return nullptr; }
|
|
||||||
|
|
||||||
void setBacklightDuty(uint8_t backlightDuty) override {
|
|
||||||
if (configuration.backlightDutyFunction != nullptr) {
|
|
||||||
configuration.backlightDutyFunction(backlightDuty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hardware access methods
|
|
||||||
esp_lcd_panel_io_handle_t getIoHandle() const { return ioHandle; }
|
|
||||||
esp_lcd_panel_handle_t getPanelHandle() const { return panelHandle; }
|
|
||||||
|
|
||||||
// Resolution access methods
|
|
||||||
uint16_t getHorizontalResolution() const { return configuration.horizontalResolution; }
|
|
||||||
uint16_t getVerticalResolution() const { return configuration.verticalResolution; }
|
|
||||||
};
|
|
||||||
|
|
||||||
// Factory function for registration
|
|
||||||
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
|
|
||||||
@@ -3,3 +3,64 @@ description: X-Powers AXP192 power management IC
|
|||||||
include: ["i2c-device.yaml"]
|
include: ["i2c-device.yaml"]
|
||||||
|
|
||||||
compatible: "x-powers,axp192"
|
compatible: "x-powers,axp192"
|
||||||
|
|
||||||
|
properties:
|
||||||
|
dcdc1-voltage:
|
||||||
|
type: int
|
||||||
|
default: 0
|
||||||
|
description: DCDC1 rail voltage in mV (700-3500). 0 leaves the voltage untouched.
|
||||||
|
dcdc1-enable:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
description: >
|
||||||
|
Enable the DCDC1 rail. Unconditionally applied like any other boolean devicetree property -
|
||||||
|
false actively disables it, so a board whose SoC is powered from DCDC1 must set this.
|
||||||
|
dcdc2-voltage:
|
||||||
|
type: int
|
||||||
|
default: 0
|
||||||
|
description: DCDC2 rail voltage in mV (700-2275). 0 leaves the voltage untouched.
|
||||||
|
dcdc2-enable:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
description: Enable the DCDC2 rail. See dcdc1-enable for why false actively disables it.
|
||||||
|
dcdc3-voltage:
|
||||||
|
type: int
|
||||||
|
default: 0
|
||||||
|
description: DCDC3 rail voltage in mV (700-3500). 0 leaves the voltage untouched.
|
||||||
|
dcdc3-enable:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
description: Enable the DCDC3 rail. See dcdc1-enable for why false actively disables it.
|
||||||
|
ldo2-voltage:
|
||||||
|
type: int
|
||||||
|
default: 0
|
||||||
|
description: LDO2 rail voltage in mV (1800-3300). 0 leaves the voltage untouched.
|
||||||
|
ldo2-enable:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
description: Enable the LDO2 rail. See dcdc1-enable for why false actively disables it.
|
||||||
|
ldo3-voltage:
|
||||||
|
type: int
|
||||||
|
default: 0
|
||||||
|
description: LDO3 rail voltage in mV (1800-3300). 0 leaves the voltage untouched.
|
||||||
|
ldo3-enable:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
description: Enable the LDO3 rail. See dcdc1-enable for why false actively disables it.
|
||||||
|
exten-enable:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
description: >
|
||||||
|
Enable the EXTEN output switch. No voltage control (see AXP192_RAIL_EXTEN); see
|
||||||
|
dcdc1-enable for why false actively disables it.
|
||||||
|
gpio1-pwm:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
description: Configure GPIO1 as the PWM1 output instead of GPIO/ADC input/output.
|
||||||
|
gpio1-pwm1-duty-cycle:
|
||||||
|
type: int
|
||||||
|
default: 0
|
||||||
|
min: 0
|
||||||
|
max: 255
|
||||||
|
description: >
|
||||||
|
PWM1 duty cycle (0 = always low, 255 = always high). Only applied when gpio1-pwm is set.
|
||||||
|
|||||||
@@ -15,6 +15,32 @@ extern "C" {
|
|||||||
struct Axp192Config {
|
struct Axp192Config {
|
||||||
/** Address on bus */
|
/** Address on bus */
|
||||||
uint8_t address;
|
uint8_t address;
|
||||||
|
/**
|
||||||
|
* Rail voltage/enable settings, applied once at driver start (see axp192_set_rail_voltage()/
|
||||||
|
* axp192_set_rail_enabled()). Each "enable" flag is unconditionally applied - false (the yaml
|
||||||
|
* default) actively disables the rail, same as every other boolean devicetree property in
|
||||||
|
* this codebase (e.g. reset-active-high, swap-xy: absence means false, not "leave alone").
|
||||||
|
* A board must explicitly set every rail it needs kept on, including ones a board's hardware
|
||||||
|
* happens to power up with by default (e.g. DCDC1, which is the SoC's own supply on some
|
||||||
|
* boards) - see m5stack-core2's devicetree for a worked example. Voltage of 0 means "don't
|
||||||
|
* call axp192_set_rail_voltage() for this rail" (0mV is outside every rail's valid range).
|
||||||
|
*/
|
||||||
|
uint16_t dcdc1_voltage_mv;
|
||||||
|
bool dcdc1_enable;
|
||||||
|
uint16_t dcdc2_voltage_mv;
|
||||||
|
bool dcdc2_enable;
|
||||||
|
uint16_t dcdc3_voltage_mv;
|
||||||
|
bool dcdc3_enable;
|
||||||
|
uint16_t ldo2_voltage_mv;
|
||||||
|
bool ldo2_enable;
|
||||||
|
uint16_t ldo3_voltage_mv;
|
||||||
|
bool ldo3_enable;
|
||||||
|
/** EXTEN has no voltage control (see AXP192_RAIL_EXTEN), hence no exten_voltage_mv field. */
|
||||||
|
bool exten_enable;
|
||||||
|
/** Configures GPIO1 as the PWM1 output (see axp192_set_gpio1_pwm1_output()). */
|
||||||
|
bool gpio1_pwm;
|
||||||
|
/** Applied only when gpio1_pwm is true (see axp192_set_pwm1_duty_cycle()). */
|
||||||
|
uint8_t gpio1_pwm1_duty_cycle;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Switchable/adjustable power rails of the AXP192. */
|
/** Switchable/adjustable power rails of the AXP192. */
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include <new>
|
#include <new>
|
||||||
|
|
||||||
#define GET_CONFIG(device) (static_cast<const Axp192Config*>((device)->config))
|
#define GET_CONFIG(device) (static_cast<const Axp192Config*>((device)->config))
|
||||||
|
constexpr auto* TAG = "AXP192";
|
||||||
|
|
||||||
/** Reference: https://github.com/tuupola/axp192 (register map and ADC/voltage formulas) */
|
/** Reference: https://github.com/tuupola/axp192 (register map and ADC/voltage formulas) */
|
||||||
static constexpr uint8_t REG_MODE_CHGSTATUS = 0x01U; // bit6: battery is charging
|
static constexpr uint8_t REG_MODE_CHGSTATUS = 0x01U; // bit6: battery is charging
|
||||||
@@ -404,10 +405,73 @@ static void destroy_power_supply_child(Device* child) {
|
|||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
|
// region Devicetree-configured bring-up
|
||||||
|
|
||||||
|
static error_t apply_rail_config(Device* device, Axp192Rail rail, uint16_t voltage_mv, bool enable) {
|
||||||
|
if (voltage_mv != 0U) {
|
||||||
|
error_t error = axp192_set_rail_voltage(device, rail, voltage_mv);
|
||||||
|
if (error != ERROR_NONE) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return axp192_set_rail_enabled(device, rail, enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Applies the devicetree's rail voltage/enable and GPIO1 PWM settings, replacing what boards
|
||||||
|
// (e.g. m5stack-core2) used to do by hand via a device_listener after DEVICE_EVENT_STARTED.
|
||||||
|
static error_t apply_devicetree_config(Device* device) {
|
||||||
|
const auto* config = GET_CONFIG(device);
|
||||||
|
|
||||||
|
error_t error = apply_rail_config(device, AXP192_RAIL_DCDC1, config->dcdc1_voltage_mv, config->dcdc1_enable);
|
||||||
|
if (error != ERROR_NONE) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
error = apply_rail_config(device, AXP192_RAIL_DCDC2, config->dcdc2_voltage_mv, config->dcdc2_enable);
|
||||||
|
if (error != ERROR_NONE) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
error = apply_rail_config(device, AXP192_RAIL_DCDC3, config->dcdc3_voltage_mv, config->dcdc3_enable);
|
||||||
|
if (error != ERROR_NONE) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
error = apply_rail_config(device, AXP192_RAIL_LDO2, config->ldo2_voltage_mv, config->ldo2_enable);
|
||||||
|
if (error != ERROR_NONE) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
error = apply_rail_config(device, AXP192_RAIL_LDO3, config->ldo3_voltage_mv, config->ldo3_enable);
|
||||||
|
if (error != ERROR_NONE) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
error = axp192_set_rail_enabled(device, AXP192_RAIL_EXTEN, config->exten_enable);
|
||||||
|
if (error != ERROR_NONE) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config->gpio1_pwm) {
|
||||||
|
error = axp192_set_pwm1_duty_cycle(device, config->gpio1_pwm1_duty_cycle);
|
||||||
|
if (error != ERROR_NONE) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
error = axp192_set_gpio1_pwm1_output(device);
|
||||||
|
if (error != ERROR_NONE) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
static error_t start(Device* device) {
|
static error_t start(Device* device) {
|
||||||
auto* parent = device_get_parent(device);
|
auto* parent = device_get_parent(device);
|
||||||
check(device_get_type(parent) == &I2C_CONTROLLER_TYPE);
|
check(device_get_type(parent) == &I2C_CONTROLLER_TYPE);
|
||||||
|
|
||||||
|
if (apply_devicetree_config(device) != ERROR_NONE) {
|
||||||
|
LOG_E(TAG, "Failed to apply devicetree-configured rail/GPIO1 settings");
|
||||||
|
return ERROR_RESOURCE;
|
||||||
|
}
|
||||||
|
|
||||||
auto* internal = new(std::nothrow) Axp192Internal();
|
auto* internal = new(std::nothrow) Axp192Internal();
|
||||||
if (internal == nullptr) {
|
if (internal == nullptr) {
|
||||||
return ERROR_OUT_OF_MEMORY;
|
return ERROR_OUT_OF_MEMORY;
|
||||||
|
|||||||
@@ -3,3 +3,86 @@ description: X-Powers AXP2101 power management IC
|
|||||||
include: ["i2c-device.yaml"]
|
include: ["i2c-device.yaml"]
|
||||||
|
|
||||||
compatible: "x-powers,axp2101"
|
compatible: "x-powers,axp2101"
|
||||||
|
|
||||||
|
properties:
|
||||||
|
aldo1-millivolt:
|
||||||
|
type: int
|
||||||
|
default: 0
|
||||||
|
description: ALDO1 output voltage in mV (500-3500, step 100), applied at driver start if
|
||||||
|
non-zero. 0 leaves the channel's voltage untouched.
|
||||||
|
aldo1-enabled:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
description: Enable ALDO1 at driver start.
|
||||||
|
aldo2-millivolt:
|
||||||
|
type: int
|
||||||
|
default: 0
|
||||||
|
description: ALDO2 output voltage in mV (500-3500, step 100), applied at driver start if
|
||||||
|
non-zero. 0 leaves the channel's voltage untouched.
|
||||||
|
aldo2-enabled:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
description: Enable ALDO2 at driver start.
|
||||||
|
aldo3-millivolt:
|
||||||
|
type: int
|
||||||
|
default: 0
|
||||||
|
description: ALDO3 output voltage in mV (500-3500, step 100), applied at driver start if
|
||||||
|
non-zero. 0 leaves the channel's voltage untouched.
|
||||||
|
aldo3-enabled:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
description: Enable ALDO3 at driver start.
|
||||||
|
aldo4-millivolt:
|
||||||
|
type: int
|
||||||
|
default: 0
|
||||||
|
description: ALDO4 output voltage in mV (500-3500, step 100), applied at driver start if
|
||||||
|
non-zero. 0 leaves the channel's voltage untouched.
|
||||||
|
aldo4-enabled:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
description: Enable ALDO4 at driver start.
|
||||||
|
bldo1-millivolt:
|
||||||
|
type: int
|
||||||
|
default: 0
|
||||||
|
description: BLDO1 output voltage in mV (500-3500, step 100), applied at driver start if
|
||||||
|
non-zero. 0 leaves the channel's voltage untouched.
|
||||||
|
bldo1-enabled:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
description: Enable BLDO1 at driver start.
|
||||||
|
bldo2-millivolt:
|
||||||
|
type: int
|
||||||
|
default: 0
|
||||||
|
description: BLDO2 output voltage in mV (500-3500, step 100), applied at driver start if
|
||||||
|
non-zero. 0 leaves the channel's voltage untouched.
|
||||||
|
bldo2-enabled:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
description: Enable BLDO2 at driver start.
|
||||||
|
cpusldo-millivolt:
|
||||||
|
type: int
|
||||||
|
default: 0
|
||||||
|
description: CPUSLDO output voltage in mV (500-1400, step 50), applied at driver start if
|
||||||
|
non-zero. 0 leaves the channel's voltage untouched.
|
||||||
|
cpusldo-enabled:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
description: Enable CPUSLDO at driver start.
|
||||||
|
dldo1-millivolt:
|
||||||
|
type: int
|
||||||
|
default: 0
|
||||||
|
description: DLDO1 output voltage in mV (500-3400, step 100), applied at driver start if
|
||||||
|
non-zero. 0 leaves the channel's voltage untouched.
|
||||||
|
dldo1-enabled:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
description: Enable DLDO1 at driver start.
|
||||||
|
dldo2-millivolt:
|
||||||
|
type: int
|
||||||
|
default: 0
|
||||||
|
description: DLDO2 output voltage in mV (500-3400, step 100), applied at driver start if
|
||||||
|
non-zero. 0 leaves the channel's voltage untouched.
|
||||||
|
dldo2-enabled:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
description: Enable DLDO2 at driver start.
|
||||||
|
|||||||
@@ -15,6 +15,29 @@ extern "C" {
|
|||||||
struct Axp2101Config {
|
struct Axp2101Config {
|
||||||
/** Address on bus */
|
/** Address on bus */
|
||||||
uint8_t address;
|
uint8_t address;
|
||||||
|
/** LDOx output voltage in mV, applied at driver start when non-zero, independently of the
|
||||||
|
* matching xEnabled flag. 0 leaves the channel's voltage untouched. Field order here MUST
|
||||||
|
* match the property order in bindings/x-powers,axp2101.yaml: the devicetree compiler
|
||||||
|
* emits positional (non-designated) initializers, so a mismatch silently shifts every
|
||||||
|
* value into the wrong field. */
|
||||||
|
uint16_t aldo1_millivolt;
|
||||||
|
bool aldo1_enabled;
|
||||||
|
uint16_t aldo2_millivolt;
|
||||||
|
bool aldo2_enabled;
|
||||||
|
uint16_t aldo3_millivolt;
|
||||||
|
bool aldo3_enabled;
|
||||||
|
uint16_t aldo4_millivolt;
|
||||||
|
bool aldo4_enabled;
|
||||||
|
uint16_t bldo1_millivolt;
|
||||||
|
bool bldo1_enabled;
|
||||||
|
uint16_t bldo2_millivolt;
|
||||||
|
bool bldo2_enabled;
|
||||||
|
uint16_t cpusldo_millivolt;
|
||||||
|
bool cpusldo_enabled;
|
||||||
|
uint16_t dldo1_millivolt;
|
||||||
|
bool dldo1_enabled;
|
||||||
|
uint16_t dldo2_millivolt;
|
||||||
|
bool dldo2_enabled;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Switchable/adjustable DCDC (buck) converters of the AXP2101. */
|
/** Switchable/adjustable DCDC (buck) converters of the AXP2101. */
|
||||||
|
|||||||
@@ -49,15 +49,24 @@ struct Axp2101VoltRange {
|
|||||||
uint8_t code_base;
|
uint8_t code_base;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Validates millivolts against a single known range and encodes it. No search: caller has
|
||||||
|
* already picked which range applies (e.g. by channel). */
|
||||||
|
static error_t encode_single_range(uint16_t millivolts, const Axp2101VoltRange& range, uint8_t* out_code) {
|
||||||
|
if (millivolts < range.min || millivolts > range.max || (millivolts - range.min) % range.step != 0U) {
|
||||||
|
return ERROR_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
*out_code = static_cast<uint8_t>(range.code_base + (millivolts - range.min) / range.step);
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Finds which of several (possibly non-contiguous) sub-ranges of a single channel contains
|
||||||
|
* millivolts, and encodes it. Only meaningful when ranges all belong to the SAME channel
|
||||||
|
* (e.g. DCDC3's three piecewise sub-ranges) - never pass ranges from different channels,
|
||||||
|
* since their spans can legitimately overlap and this would silently pick the first match. */
|
||||||
static error_t encode_ranged_voltage(uint16_t millivolts, const Axp2101VoltRange* ranges, size_t range_count, uint8_t* out_code) {
|
static error_t encode_ranged_voltage(uint16_t millivolts, const Axp2101VoltRange* ranges, size_t range_count, uint8_t* out_code) {
|
||||||
for (size_t i = 0; i < range_count; i++) {
|
for (size_t i = 0; i < range_count; i++) {
|
||||||
const Axp2101VoltRange& range = ranges[i];
|
if (millivolts >= ranges[i].min && millivolts <= ranges[i].max) {
|
||||||
if (millivolts >= range.min && millivolts <= range.max) {
|
return encode_single_range(millivolts, ranges[i], out_code);
|
||||||
if ((millivolts - range.min) % range.step != 0U) {
|
|
||||||
return ERROR_INVALID_ARGUMENT;
|
|
||||||
}
|
|
||||||
*out_code = static_cast<uint8_t>(range.code_base + (millivolts - range.min) / range.step);
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ERROR_INVALID_ARGUMENT;
|
return ERROR_INVALID_ARGUMENT;
|
||||||
@@ -253,8 +262,9 @@ error_t axp2101_set_ldo_voltage(Device* device, Axp2101Ldo ldo, uint16_t millivo
|
|||||||
};
|
};
|
||||||
|
|
||||||
uint8_t code;
|
uint8_t code;
|
||||||
error_t err = encode_ranged_voltage(millivolts, &LDO_RANGE[ldo], 1, &code);
|
error_t err = encode_single_range(millivolts, LDO_RANGE[ldo], &code);
|
||||||
if (err != ERROR_NONE) {
|
if (err != ERROR_NONE) {
|
||||||
|
LOG_E(TAG, "Failed to encode %u mV", millivolts);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -518,6 +528,45 @@ static error_t start(Device* device) {
|
|||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// All 9 LDO channels: voltage and enable states are applied independently if configured.
|
||||||
|
// Boards that need it enable/voltage-set the channel via config instead of per-board imperative code.
|
||||||
|
// Order matches enum Axp2101Ldo.
|
||||||
|
static constexpr Axp2101Ldo LDO_CHANNELS[9] = {
|
||||||
|
AXP2101_ALDO1, AXP2101_ALDO2, AXP2101_ALDO3, AXP2101_ALDO4,
|
||||||
|
AXP2101_BLDO1, AXP2101_BLDO2, AXP2101_CPUSLDO, AXP2101_DLDO1, AXP2101_DLDO2,
|
||||||
|
};
|
||||||
|
static constexpr const char* LDO_NAMES[9] = {
|
||||||
|
"ALDO1", "ALDO2", "ALDO3", "ALDO4", "BLDO1", "BLDO2", "CPUSLDO", "DLDO1", "DLDO2",
|
||||||
|
};
|
||||||
|
const auto* config = GET_CONFIG(device);
|
||||||
|
const uint16_t ldo_millivolts[9] = {
|
||||||
|
config->aldo1_millivolt, config->aldo2_millivolt, config->aldo3_millivolt, config->aldo4_millivolt,
|
||||||
|
config->bldo1_millivolt, config->bldo2_millivolt, config->cpusldo_millivolt,
|
||||||
|
config->dldo1_millivolt, config->dldo2_millivolt,
|
||||||
|
};
|
||||||
|
const bool ldo_enabled[9] = {
|
||||||
|
config->aldo1_enabled, config->aldo2_enabled, config->aldo3_enabled, config->aldo4_enabled,
|
||||||
|
config->bldo1_enabled, config->bldo2_enabled, config->cpusldo_enabled,
|
||||||
|
config->dldo1_enabled, config->dldo2_enabled,
|
||||||
|
};
|
||||||
|
for (size_t i = 0; i < 9; i++) {
|
||||||
|
if (ldo_millivolts[i] != 0) {
|
||||||
|
error_t err = axp2101_set_ldo_voltage(device, LDO_CHANNELS[i], ldo_millivolts[i]);
|
||||||
|
if (err != ERROR_NONE) {
|
||||||
|
LOG_E(TAG, "Failed to set %s voltage", LDO_NAMES[i]);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ldo_enabled[i]) {
|
||||||
|
error_t err = axp2101_set_ldo_enabled(device, LDO_CHANNELS[i], true);
|
||||||
|
if (err != ERROR_NONE) {
|
||||||
|
LOG_E(TAG, "Failed to enable %s", LDO_NAMES[i]);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto* internal = new(std::nothrow) Axp2101Internal();
|
auto* internal = new(std::nothrow) Axp2101Internal();
|
||||||
if (internal == nullptr) {
|
if (internal == nullptr) {
|
||||||
return ERROR_OUT_OF_MEMORY;
|
return ERROR_OUT_OF_MEMORY;
|
||||||
|
|||||||
@@ -58,12 +58,12 @@ static error_t pulse_reset(GpioDescriptor* descriptor, bool active_high) {
|
|||||||
if (error != ERROR_NONE) {
|
if (error != ERROR_NONE) {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
vTaskDelay(pdMS_TO_TICKS(10));
|
vTaskDelay(pdMS_TO_TICKS(50));
|
||||||
error = gpio_descriptor_set_level(descriptor, !active_high);
|
error = gpio_descriptor_set_level(descriptor, !active_high);
|
||||||
if (error != ERROR_NONE) {
|
if (error != ERROR_NONE) {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
vTaskDelay(pdMS_TO_TICKS(10));
|
vTaskDelay(pdMS_TO_TICKS(300));
|
||||||
return ERROR_NONE;
|
return ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -324,6 +324,21 @@ static error_t ili9341_set_gap(Device* device, int32_t x_gap, int32_t y_gap) {
|
|||||||
return esp_lcd_panel_set_gap(internal->panel_handle, x_gap, y_gap) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
|
return esp_lcd_panel_set_gap(internal->panel_handle, x_gap, y_gap) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reads the devicetree-configured baseline, not live hardware state - see DisplayApi::get_gap_x().
|
||||||
|
// Must match what start() actually programmed into the panel (physical CASET/RASET-space, swapped
|
||||||
|
// when swap_xy is set) - lvgl_display.c treats this as the LV_DISPLAY_ROTATION_0 baseline and
|
||||||
|
// re-applies it verbatim, so returning the raw unswapped config here would silently clobber
|
||||||
|
// start()'s gap back to the wrong axes as soon as a display is bound.
|
||||||
|
static int32_t ili9341_get_gap_x(Device* device) {
|
||||||
|
const auto* config = GET_CONFIG(device);
|
||||||
|
return config->swap_xy ? config->gap_y : config->gap_x;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int32_t ili9341_get_gap_y(Device* device) {
|
||||||
|
const auto* config = GET_CONFIG(device);
|
||||||
|
return config->swap_xy ? config->gap_x : config->gap_y;
|
||||||
|
}
|
||||||
|
|
||||||
static error_t ili9341_invert_color(Device* device, bool invert_color_data) {
|
static error_t ili9341_invert_color(Device* device, bool invert_color_data) {
|
||||||
auto* internal = static_cast<Ili9341Internal*>(device_get_driver_data(device));
|
auto* internal = static_cast<Ili9341Internal*>(device_get_driver_data(device));
|
||||||
return esp_lcd_panel_invert_color(internal->panel_handle, invert_color_data) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
|
return esp_lcd_panel_invert_color(internal->panel_handle, invert_color_data) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
|
||||||
@@ -392,6 +407,8 @@ static const DisplayApi ili9341_display_api = {
|
|||||||
.get_mirror_x = ili9341_get_mirror_x,
|
.get_mirror_x = ili9341_get_mirror_x,
|
||||||
.get_mirror_y = ili9341_get_mirror_y,
|
.get_mirror_y = ili9341_get_mirror_y,
|
||||||
.set_gap = ili9341_set_gap,
|
.set_gap = ili9341_set_gap,
|
||||||
|
.get_gap_x = ili9341_get_gap_x,
|
||||||
|
.get_gap_y = ili9341_get_gap_y,
|
||||||
.invert_color = ili9341_invert_color,
|
.invert_color = ili9341_invert_color,
|
||||||
.disp_on_off = ili9341_disp_on_off,
|
.disp_on_off = ili9341_disp_on_off,
|
||||||
.disp_sleep = ili9341_disp_sleep,
|
.disp_sleep = ili9341_disp_sleep,
|
||||||
|
|||||||
@@ -246,6 +246,21 @@ static error_t ili9488_set_gap(Device* device, int32_t x_gap, int32_t y_gap) {
|
|||||||
return esp_lcd_panel_set_gap(internal->panel_handle, x_gap, y_gap) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
|
return esp_lcd_panel_set_gap(internal->panel_handle, x_gap, y_gap) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reads the devicetree-configured baseline, not live hardware state - see DisplayApi::get_gap_x().
|
||||||
|
// Must match what start() actually programmed into the panel (physical CASET/RASET-space, swapped
|
||||||
|
// when swap_xy is set) - lvgl_display.c treats this as the LV_DISPLAY_ROTATION_0 baseline and
|
||||||
|
// re-applies it verbatim, so returning the raw unswapped config here would silently clobber
|
||||||
|
// start()'s gap back to the wrong axes as soon as a display is bound.
|
||||||
|
static int32_t ili9488_get_gap_x(Device* device) {
|
||||||
|
const auto* config = GET_CONFIG(device);
|
||||||
|
return config->swap_xy ? config->gap_y : config->gap_x;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int32_t ili9488_get_gap_y(Device* device) {
|
||||||
|
const auto* config = GET_CONFIG(device);
|
||||||
|
return config->swap_xy ? config->gap_x : config->gap_y;
|
||||||
|
}
|
||||||
|
|
||||||
static error_t ili9488_invert_color(Device* device, bool invert_color_data) {
|
static error_t ili9488_invert_color(Device* device, bool invert_color_data) {
|
||||||
auto* internal = static_cast<Ili9488Internal*>(device_get_driver_data(device));
|
auto* internal = static_cast<Ili9488Internal*>(device_get_driver_data(device));
|
||||||
return esp_lcd_panel_invert_color(internal->panel_handle, invert_color_data) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
|
return esp_lcd_panel_invert_color(internal->panel_handle, invert_color_data) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
|
||||||
@@ -311,6 +326,8 @@ static const DisplayApi ili9488_display_api = {
|
|||||||
.get_mirror_x = ili9488_get_mirror_x,
|
.get_mirror_x = ili9488_get_mirror_x,
|
||||||
.get_mirror_y = ili9488_get_mirror_y,
|
.get_mirror_y = ili9488_get_mirror_y,
|
||||||
.set_gap = ili9488_set_gap,
|
.set_gap = ili9488_set_gap,
|
||||||
|
.get_gap_x = ili9488_get_gap_x,
|
||||||
|
.get_gap_y = ili9488_get_gap_y,
|
||||||
.invert_color = ili9488_invert_color,
|
.invert_color = ili9488_invert_color,
|
||||||
.disp_on_off = ili9488_disp_on_off,
|
.disp_on_off = ili9488_disp_on_off,
|
||||||
.disp_sleep = ili9488_disp_sleep,
|
.disp_sleep = ili9488_disp_sleep,
|
||||||
|
|||||||
@@ -7,5 +7,5 @@ file(GLOB_RECURSE SOURCE_FILES "source/*.c*")
|
|||||||
tactility_add_module(jd9853-module
|
tactility_add_module(jd9853-module
|
||||||
SRCS ${SOURCE_FILES}
|
SRCS ${SOURCE_FILES}
|
||||||
INCLUDE_DIRS include/
|
INCLUDE_DIRS include/
|
||||||
REQUIRES TactilityKernel platform-esp32 JD9853 esp_lcd driver
|
REQUIRES TactilityKernel platform-esp32 esp_lcd
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
- TactilityKernel
|
- TactilityKernel
|
||||||
|
- Platforms/platform-esp32
|
||||||
bindings: bindings
|
bindings: bindings
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
#include <esp_lcd_jd9853.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include "esp_lcd_jd9853.h"
|
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||||
*
|
*
|
||||||
@@ -8,16 +8,16 @@
|
|||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <sys/cdefs.h>
|
#include <sys/cdefs.h>
|
||||||
#include "freertos/FreeRTOS.h"
|
#include <freertos/FreeRTOS.h>
|
||||||
#include "freertos/task.h"
|
#include <freertos/task.h>
|
||||||
#include "esp_lcd_panel_interface.h"
|
#include <esp_lcd_panel_interface.h>
|
||||||
#include "esp_lcd_panel_io.h"
|
#include <esp_lcd_panel_io.h>
|
||||||
#include "esp_lcd_panel_vendor.h"
|
#include <esp_lcd_panel_vendor.h>
|
||||||
#include "esp_lcd_panel_ops.h"
|
#include <esp_lcd_panel_ops.h>
|
||||||
#include "esp_lcd_panel_commands.h"
|
#include <esp_lcd_panel_commands.h>
|
||||||
#include "driver/gpio.h"
|
#include <driver/gpio.h>
|
||||||
#include "esp_log.h"
|
#include <esp_log.h>
|
||||||
#include "esp_check.h"
|
#include <esp_check.h>
|
||||||
|
|
||||||
static const char *TAG = "JD9853";
|
static const char *TAG = "JD9853";
|
||||||
|
|
||||||
@@ -24,6 +24,7 @@ static constexpr uint8_t REG_FIFO_EN = 0x23; // FIFO enable
|
|||||||
static constexpr uint8_t REG_WHO_AM_I = 0x75; // chip ID — expect 0x19
|
static constexpr uint8_t REG_WHO_AM_I = 0x75; // chip ID — expect 0x19
|
||||||
|
|
||||||
static constexpr uint8_t WHO_AM_I_VALUE = 0x19;
|
static constexpr uint8_t WHO_AM_I_VALUE = 0x19;
|
||||||
|
static constexpr uint8_t WHO_AM_I_ATTEMPTS = 5;
|
||||||
|
|
||||||
// Configuration values
|
// Configuration values
|
||||||
// GYRO_CONFIG: FS_SEL=3 (±2000°/s), FCHOICE_B=00 → 0x18
|
// GYRO_CONFIG: FS_SEL=3 (±2000°/s), FCHOICE_B=00 → 0x18
|
||||||
@@ -54,10 +55,16 @@ static error_t start(Device* device) {
|
|||||||
|
|
||||||
auto address = GET_CONFIG(device)->address;
|
auto address = GET_CONFIG(device)->address;
|
||||||
|
|
||||||
// Verify chip ID
|
// Verify chip ID: it might take more than 1 attempt at power up (on M5Stack Core2)
|
||||||
uint8_t who_am_i = 0;
|
uint8_t who_am_i = 0;
|
||||||
if (i2c_controller_register8_get(i2c_controller, address, REG_WHO_AM_I, &who_am_i, I2C_TIMEOUT_TICKS) != ERROR_NONE
|
for (int i = 0; i < WHO_AM_I_ATTEMPTS; i++) {
|
||||||
|| who_am_i != WHO_AM_I_VALUE) {
|
if (i2c_controller_register8_get(i2c_controller, address, REG_WHO_AM_I, &who_am_i, I2C_TIMEOUT_TICKS) == ERROR_NONE) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
vTaskDelay(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (who_am_i != WHO_AM_I_VALUE) {
|
||||||
LOG_E(TAG, "WHO_AM_I mismatch: got 0x%02X, expected 0x%02X", who_am_i, WHO_AM_I_VALUE);
|
LOG_E(TAG, "WHO_AM_I mismatch: got 0x%02X, expected 0x%02X", who_am_i, WHO_AM_I_VALUE);
|
||||||
return ERROR_RESOURCE;
|
return ERROR_RESOURCE;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,11 +138,13 @@ static error_t start(Device* device) {
|
|||||||
esp_lcd_panel_init(internal->panel_handle) == ESP_OK &&
|
esp_lcd_panel_init(internal->panel_handle) == ESP_OK &&
|
||||||
(!config->invert_color || esp_lcd_panel_invert_color(internal->panel_handle, true) == ESP_OK);
|
(!config->invert_color || esp_lcd_panel_invert_color(internal->panel_handle, true) == ESP_OK);
|
||||||
|
|
||||||
// set_gap() just stores x_gap/y_gap and adds them as raw offsets wherever draw_bitmap()'s
|
// set_gap()'s x/y map straight to the physical CASET/RASET column/row registers, not to
|
||||||
// (already logical, post-swap) x/y land - independent of swap_xy/mirror state (confirmed via
|
// draw_bitmap()'s logical (post-swap) x/y - swap_xy transposes which physical axis those
|
||||||
// ESP-IDF's esp_lcd_panel_st7789.c, same pattern), so gap_x/gap_y are passed through unswapped.
|
// registers address, so the configured gaps must swap with it too
|
||||||
|
int32_t gap_x = config->swap_xy ? config->gap_y : config->gap_x;
|
||||||
|
int32_t gap_y = config->swap_xy ? config->gap_x : config->gap_y;
|
||||||
if (ok) {
|
if (ok) {
|
||||||
ok = (config->gap_x == 0 && config->gap_y == 0) || esp_lcd_panel_set_gap(internal->panel_handle, config->gap_x, config->gap_y) == ESP_OK;
|
ok = (gap_x == 0 && gap_y == 0) || esp_lcd_panel_set_gap(internal->panel_handle, gap_x, gap_y) == ESP_OK;
|
||||||
}
|
}
|
||||||
ok = ok && (!config->swap_xy || esp_lcd_panel_swap_xy(internal->panel_handle, true) == ESP_OK);
|
ok = ok && (!config->swap_xy || esp_lcd_panel_swap_xy(internal->panel_handle, true) == ESP_OK);
|
||||||
ok = ok && ((!config->mirror_x && !config->mirror_y) || esp_lcd_panel_mirror(internal->panel_handle, config->mirror_x, config->mirror_y) == ESP_OK);
|
ok = ok && ((!config->mirror_x && !config->mirror_y) || esp_lcd_panel_mirror(internal->panel_handle, config->mirror_x, config->mirror_y) == ESP_OK);
|
||||||
@@ -252,12 +254,18 @@ static error_t st7735_set_gap(Device* device, int32_t x_gap, int32_t y_gap) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reads the devicetree-configured baseline, not live hardware state - see DisplayApi::get_gap_x().
|
// Reads the devicetree-configured baseline, not live hardware state - see DisplayApi::get_gap_x().
|
||||||
|
// Must match what start() actually programmed into the panel (physical CASET/RASET-space, swapped
|
||||||
|
// when swap_xy is set) - lvgl_display.c treats this as the LV_DISPLAY_ROTATION_0 baseline and
|
||||||
|
// re-applies it verbatim, so returning the raw unswapped config here would silently clobber
|
||||||
|
// start()'s gap back to the wrong axes as soon as a display is bound.
|
||||||
static int32_t st7735_get_gap_x(Device* device) {
|
static int32_t st7735_get_gap_x(Device* device) {
|
||||||
return GET_CONFIG(device)->gap_x;
|
const auto* config = GET_CONFIG(device);
|
||||||
|
return config->swap_xy ? config->gap_y : config->gap_x;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int32_t st7735_get_gap_y(Device* device) {
|
static int32_t st7735_get_gap_y(Device* device) {
|
||||||
return GET_CONFIG(device)->gap_y;
|
const auto* config = GET_CONFIG(device);
|
||||||
|
return config->swap_xy ? config->gap_x : config->gap_y;
|
||||||
}
|
}
|
||||||
|
|
||||||
static error_t st7735_invert_color(Device* device, bool invert_color_data) {
|
static error_t st7735_invert_color(Device* device, bool invert_color_data) {
|
||||||
|
|||||||
@@ -275,6 +275,21 @@ static bool st7789_i8080_get_mirror_y(Device* device) {
|
|||||||
return GET_CONFIG(device)->mirror_y;
|
return GET_CONFIG(device)->mirror_y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reads the devicetree-configured baseline, not live hardware state - see DisplayApi::get_gap_x().
|
||||||
|
// Must match what start() actually programmed into the panel (physical CASET/RASET-space, swapped
|
||||||
|
// when swap_xy is set) - lvgl_display.c treats this as the LV_DISPLAY_ROTATION_0 baseline and
|
||||||
|
// re-applies it verbatim, so returning the raw unswapped config here would silently clobber
|
||||||
|
// start()'s gap back to the wrong axes as soon as a display is bound.
|
||||||
|
static int32_t st7789_i8080_get_gap_x(Device* device) {
|
||||||
|
const auto* config = GET_CONFIG(device);
|
||||||
|
return config->swap_xy ? config->gap_y : config->gap_x;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int32_t st7789_i8080_get_gap_y(Device* device) {
|
||||||
|
const auto* config = GET_CONFIG(device);
|
||||||
|
return config->swap_xy ? config->gap_x : config->gap_y;
|
||||||
|
}
|
||||||
|
|
||||||
static error_t st7789_i8080_set_gap(Device* device, int32_t x_gap, int32_t y_gap) {
|
static error_t st7789_i8080_set_gap(Device* device, int32_t x_gap, int32_t y_gap) {
|
||||||
auto* internal = static_cast<St7789I8080Internal*>(device_get_driver_data(device));
|
auto* internal = static_cast<St7789I8080Internal*>(device_get_driver_data(device));
|
||||||
return esp_lcd_panel_set_gap(internal->panel_handle, x_gap, y_gap) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
|
return esp_lcd_panel_set_gap(internal->panel_handle, x_gap, y_gap) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
|
||||||
@@ -342,6 +357,8 @@ static const DisplayApi st7789_i8080_display_api = {
|
|||||||
.get_mirror_x = st7789_i8080_get_mirror_x,
|
.get_mirror_x = st7789_i8080_get_mirror_x,
|
||||||
.get_mirror_y = st7789_i8080_get_mirror_y,
|
.get_mirror_y = st7789_i8080_get_mirror_y,
|
||||||
.set_gap = st7789_i8080_set_gap,
|
.set_gap = st7789_i8080_set_gap,
|
||||||
|
.get_gap_x = st7789_i8080_get_gap_x,
|
||||||
|
.get_gap_y = st7789_i8080_get_gap_y,
|
||||||
.invert_color = st7789_i8080_invert_color,
|
.invert_color = st7789_i8080_invert_color,
|
||||||
.disp_on_off = st7789_i8080_disp_on_off,
|
.disp_on_off = st7789_i8080_disp_on_off,
|
||||||
.disp_sleep = st7789_i8080_disp_sleep,
|
.disp_sleep = st7789_i8080_disp_sleep,
|
||||||
|
|||||||
@@ -138,11 +138,13 @@ static error_t start(Device* device) {
|
|||||||
esp_lcd_panel_init(internal->panel_handle) == ESP_OK &&
|
esp_lcd_panel_init(internal->panel_handle) == ESP_OK &&
|
||||||
(!config->invert_color || esp_lcd_panel_invert_color(internal->panel_handle, true) == ESP_OK);
|
(!config->invert_color || esp_lcd_panel_invert_color(internal->panel_handle, true) == ESP_OK);
|
||||||
|
|
||||||
// set_gap() just stores x_gap/y_gap and adds them as raw offsets wherever draw_bitmap()'s
|
// set_gap()'s x/y map straight to the physical CASET/RASET column/row registers, not to
|
||||||
// (already logical, post-swap) x/y land - independent of swap_xy/mirror state (confirmed via
|
// draw_bitmap()'s logical (post-swap) x/y - swap_xy transposes which physical axis those
|
||||||
// ESP-IDF's esp_lcd_panel_st7789.c), so gap_x/gap_y are passed through unswapped.
|
// registers address, so the configured gaps must swap with it too.
|
||||||
|
int32_t gap_x = config->swap_xy ? config->gap_y : config->gap_x;
|
||||||
|
int32_t gap_y = config->swap_xy ? config->gap_x : config->gap_y;
|
||||||
if (ok) {
|
if (ok) {
|
||||||
ok = (config->gap_x == 0 && config->gap_y == 0) || esp_lcd_panel_set_gap(internal->panel_handle, config->gap_x, config->gap_y) == ESP_OK;
|
ok = (gap_x == 0 && gap_y == 0) || esp_lcd_panel_set_gap(internal->panel_handle, gap_x, gap_y) == ESP_OK;
|
||||||
}
|
}
|
||||||
ok = ok && (!config->swap_xy || esp_lcd_panel_swap_xy(internal->panel_handle, true) == ESP_OK);
|
ok = ok && (!config->swap_xy || esp_lcd_panel_swap_xy(internal->panel_handle, true) == ESP_OK);
|
||||||
ok = ok && ((!config->mirror_x && !config->mirror_y) || esp_lcd_panel_mirror(internal->panel_handle, config->mirror_x, config->mirror_y) == ESP_OK);
|
ok = ok && ((!config->mirror_x && !config->mirror_y) || esp_lcd_panel_mirror(internal->panel_handle, config->mirror_x, config->mirror_y) == ESP_OK);
|
||||||
@@ -252,12 +254,18 @@ static error_t st7789_set_gap(Device* device, int32_t x_gap, int32_t y_gap) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reads the devicetree-configured baseline, not live hardware state - see DisplayApi::get_gap_x().
|
// Reads the devicetree-configured baseline, not live hardware state - see DisplayApi::get_gap_x().
|
||||||
|
// Must match what start() actually programmed into the panel (physical CASET/RASET-space, swapped
|
||||||
|
// when swap_xy is set) - lvgl_display.c treats this as the LV_DISPLAY_ROTATION_0 baseline and
|
||||||
|
// re-applies it verbatim, so returning the raw unswapped config here would silently clobber
|
||||||
|
// start()'s gap back to the wrong axes as soon as a display is bound.
|
||||||
static int32_t st7789_get_gap_x(Device* device) {
|
static int32_t st7789_get_gap_x(Device* device) {
|
||||||
return GET_CONFIG(device)->gap_x;
|
const auto* config = GET_CONFIG(device);
|
||||||
|
return config->swap_xy ? config->gap_y : config->gap_x;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int32_t st7789_get_gap_y(Device* device) {
|
static int32_t st7789_get_gap_y(Device* device) {
|
||||||
return GET_CONFIG(device)->gap_y;
|
const auto* config = GET_CONFIG(device);
|
||||||
|
return config->swap_xy ? config->gap_x : config->gap_y;
|
||||||
}
|
}
|
||||||
|
|
||||||
static error_t st7789_invert_color(Device* device, bool invert_color_data) {
|
static error_t st7789_invert_color(Device* device, bool invert_color_data) {
|
||||||
|
|||||||
@@ -40,6 +40,13 @@ execute_process(
|
|||||||
# Devicetree dependency collection
|
# Devicetree dependency collection
|
||||||
#
|
#
|
||||||
|
|
||||||
|
# REQUIRES_LIST below is computed once, at configure time. Without this, editing
|
||||||
|
# devicetree.yaml (e.g. adding a driver dependency) doesn't trigger a cmake reconfigure,
|
||||||
|
# so the new component's include dirs never reach the compiler until a fullclean.
|
||||||
|
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
|
||||||
|
"${DEVICETREE_LOCATION}/devicetree.yaml"
|
||||||
|
)
|
||||||
|
|
||||||
execute_process(
|
execute_process(
|
||||||
COMMAND python "${PROJECT_ROOT}/Buildscripts/DevicetreeCompiler/dependencies.py" "${DEVICETREE_LOCATION}"
|
COMMAND python "${PROJECT_ROOT}/Buildscripts/DevicetreeCompiler/dependencies.py" "${DEVICETREE_LOCATION}"
|
||||||
WORKING_DIRECTORY "${PROJECT_ROOT}"
|
WORKING_DIRECTORY "${PROJECT_ROOT}"
|
||||||
|
|||||||
Reference in New Issue
Block a user