New kernel drivers and device implementation updates (#561)

- Added modular device support and devicetree bindings for ILI9341, ILI9488, CST816S, XPT2046, and GPIO button input, updating several board configurations for display/touch/backlight/keyboard/battery.
- Added a setting to control deprecated HAL usage (device property + Kconfig).
This commit is contained in:
Ken Van Hoeylandt
2026-07-12 23:54:55 +02:00
committed by GitHub
parent 50c0a14a93
commit fa4a6e255c
148 changed files with 5837 additions and 2145 deletions
+4
View File
@@ -69,6 +69,10 @@ if (DEFINED ENV{ESP_IDF_VERSION})
else ()
message("Building for sim target")
# Devices/simulator/Source/Simulator.cpp always defines its own hardwareConfiguration; without
# this, Firmware/Source/Main.cpp's #else branch also defines an empty one (its ESP32-only
# fallback for devices migrated off the deprecated HAL), causing a duplicate-definition link error.
add_compile_definitions(CONFIG_TT_USE_DEPRECATED_HAL)
add_compile_definitions(CONFIG_TT_DEVICE_ID="simulator")
add_compile_definitions(CONFIG_TT_DEVICE_NAME="Simulator")
add_compile_definitions(CONFIG_TT_DEVICE_VENDOR="")
+2 -3
View File
@@ -1,7 +1,6 @@
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
file(GLOB_RECURSE SOURCE_FILES source/*.c*)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source"
REQUIRES Tactility esp_lvgl_port ILI934x CST816S PwmBacklight driver vfs fatfs
REQUIRES TactilityKernel driver
)
@@ -1,30 +0,0 @@
#include "devices/Display.h"
#include <driver/gpio.h>
#include <Tactility/hal/Configuration.h>
#include <PwmBacklight.h>
static bool initBoot() {
// Set the RGB LED Pins to output and turn them off
ESP_ERROR_CHECK(gpio_set_direction(GPIO_NUM_4, GPIO_MODE_OUTPUT)); // Red
ESP_ERROR_CHECK(gpio_set_direction(GPIO_NUM_16, GPIO_MODE_OUTPUT)); // Green
ESP_ERROR_CHECK(gpio_set_direction(GPIO_NUM_17, GPIO_MODE_OUTPUT)); // Blue
// 0 on, 1 off
ESP_ERROR_CHECK(gpio_set_level(GPIO_NUM_4, 1)); // Red
ESP_ERROR_CHECK(gpio_set_level(GPIO_NUM_16, 1)); // Green
ESP_ERROR_CHECK(gpio_set_level(GPIO_NUM_17, 1)); // Blue
return driver::pwmbacklight::init(LCD_PIN_BACKLIGHT);
}
static tt::hal::DeviceVector createDevices() {
return {
createDisplay(),
};
}
extern const tt::hal::Configuration hardwareConfiguration = {
.initBoot = initBoot,
.createDevices = createDevices
};
@@ -1,45 +0,0 @@
#include "Display.h"
#include "Cst816Touch.h"
#include <Ili934xDisplay.h>
#include <PwmBacklight.h>
static std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
// Note: GPIO 25 for reset and GPIO 21 for interrupt?
auto configuration = std::make_unique<Cst816sTouch::Configuration>(
I2C_NUM_0,
240,
320
);
return std::make_shared<Cst816sTouch>(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 = true,
.mirrorY = false,
.invertColor = false,
.swapBytes = true,
.bufferSize = LCD_BUFFER_SIZE,
.touch = createTouch(),
.backlightDutyFunction = driver::pwmbacklight::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,18 +0,0 @@
#pragma once
#include <Tactility/hal/display/DisplayDevice.h>
#include <driver/gpio.h>
#include <driver/spi_common.h>
#include <memory>
// Display
constexpr auto LCD_PIN_BACKLIGHT = GPIO_NUM_27;
constexpr auto LCD_SPI_HOST = SPI2_HOST;
constexpr auto LCD_PIN_CS = GPIO_NUM_15;
constexpr auto LCD_PIN_DC = GPIO_NUM_2;
constexpr auto LCD_HORIZONTAL_RESOLUTION = 240;
constexpr auto LCD_VERTICAL_RESOLUTION = 320;
constexpr auto LCD_BUFFER_HEIGHT = LCD_VERTICAL_RESOLUTION / 10;
constexpr auto LCD_BUFFER_SIZE = LCD_HORIZONTAL_RESOLUTION * LCD_BUFFER_HEIGHT;
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
-23
View File
@@ -1,23 +0,0 @@
#include <tactility/module.h>
extern "C" {
static error_t start() {
// Empty for now
return ERROR_NONE;
}
static error_t stop() {
// Empty for now
return ERROR_NONE;
}
struct Module cyd_2432s024c_module = {
.name = "cyd-2432s024c",
.start = start,
.stop = stop,
.symbols = nullptr,
.internal = nullptr
};
}
+28 -3
View File
@@ -6,7 +6,9 @@
#include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h>
#include <tactility/bindings/esp32_sdspi.h>
#include <tactility/bindings/display_placeholder.h>
#include <tactility/bindings/esp32_ledc_backlight.h>
#include <bindings/ili9341.h>
#include <bindings/cst816s.h>
/ {
compatible = "root";
@@ -28,6 +30,22 @@
clock-frequency = <400000>;
pin-sda = <&gpio0 33 GPIO_FLAG_NONE>;
pin-scl = <&gpio0 32 GPIO_FLAG_NONE>;
touch {
compatible = "hynitron,cst816s";
reg = <0x15>;
x-max = <240>;
y-max = <320>;
};
};
display_backlight {
compatible = "espressif,esp32-ledc-backlight";
// Off by default so display power-on won't show the screen from before the last power loss.
// The display backlight is turned on during the boot process.
status = "disabled";
pin-backlight = <&gpio0 27 GPIO_FLAG_NONE>;
frequency-hz = <512>;
};
spi0 {
@@ -37,8 +55,15 @@
pin-mosi = <&gpio0 13 GPIO_FLAG_NONE>;
pin-sclk = <&gpio0 14 GPIO_FLAG_NONE>;
display {
compatible = "display-placeholder";
display@0 {
compatible = "ilitek,ili9341";
horizontal-resolution = <240>;
vertical-resolution = <320>;
mirror-x;
bgr-order;
pixel-clock-hz = <40000000>;
pin-dc = <&gpio0 2 GPIO_FLAG_NONE>;
backlight = <&display_backlight>;
};
};
+2
View File
@@ -7,6 +7,8 @@ hardware.target=ESP32
hardware.flashSize=4MB
hardware.spiRam=false
dependencies.useDeprecatedHal=false
storage.userDataLocation=SD
display.size=2.4"
+2
View File
@@ -1,3 +1,5 @@
dependencies:
- Platforms/platform-esp32
- Drivers/ili9341-module
- Drivers/cst816s-module
dts: cyd,2432s024c.dts
+33
View File
@@ -0,0 +1,33 @@
#include <tactility/module.h>
#include <tactility/error.h>
#include <driver/gpio.h>
extern "C" {
static error_t start() {
// Set the RGB LED pins to output and turn them off (0 on, 1 off)
gpio_set_direction(GPIO_NUM_4, GPIO_MODE_OUTPUT); // Red
gpio_set_direction(GPIO_NUM_16, GPIO_MODE_OUTPUT); // Green
gpio_set_direction(GPIO_NUM_17, GPIO_MODE_OUTPUT); // Blue
gpio_set_level(GPIO_NUM_4, 1); // Red
gpio_set_level(GPIO_NUM_16, 1); // Green
gpio_set_level(GPIO_NUM_17, 1); // Blue
return ERROR_NONE;
}
static error_t stop() {
return ERROR_NONE;
}
Module cyd_2432s024c_module = {
.name = "cyd-2432s024c",
.start = start,
.stop = stop,
.symbols = nullptr,
.internal = nullptr
};
}
@@ -1,7 +1,6 @@
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
file(GLOB_RECURSE SOURCE_FILES source/*.c*)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source"
REQUIRES Tactility esp_lvgl_port ILI9488 GT911 PwmBacklight driver
REQUIRES TactilityKernel driver
)
@@ -1,21 +0,0 @@
#include "devices/Display.h"
#include <Tactility/hal/Configuration.h>
#include <PwmBacklight.h>
using namespace tt::hal;
static bool initBoot() {
return driver::pwmbacklight::init(GPIO_NUM_38);
}
static DeviceVector createDevices() {
return {
createDisplay(),
};
}
extern const Configuration hardwareConfiguration = {
.initBoot = initBoot,
.createDevices = createDevices
};
@@ -1,41 +0,0 @@
#include "Display.h"
#include <Gt911Touch.h>
#include <PwmBacklight.h>
#include <Ili9488Display.h>
#include <tactility/check.h>
#include <tactility/device.h>
static std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
auto* i2c = device_find_by_name("i2c0");
check(i2c);
auto configuration = std::make_unique<Gt911Touch::Configuration>(
i2c,
320,
480
);
return std::make_shared<Gt911Touch>(std::move(configuration));
}
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
auto touch = createTouch();
auto configuration = std::make_unique<Ili9488Display::Configuration>(
CROWPANEL_LCD_SPI_HOST,
CROWPANEL_LCD_PIN_CS,
CROWPANEL_LCD_PIN_DC,
CROWPANEL_LCD_HORIZONTAL_RESOLUTION,
CROWPANEL_LCD_VERTICAL_RESOLUTION,
touch,
false,
false,
false,
true
);
configuration->backlightDutyFunction = driver::pwmbacklight::setBacklightDuty;
auto display = std::make_shared<Ili9488Display>(std::move(configuration));
return std::reinterpret_pointer_cast<tt::hal::display::DisplayDevice>(display);
}
@@ -1,12 +0,0 @@
#pragma once
#include <Tactility/hal/display/DisplayDevice.h>
#define CROWPANEL_LCD_SPI_HOST SPI2_HOST
#define CROWPANEL_LCD_PIN_CS GPIO_NUM_40
#define CROWPANEL_LCD_PIN_DC GPIO_NUM_41 // RS
#define CROWPANEL_LCD_HORIZONTAL_RESOLUTION 320
#define CROWPANEL_LCD_VERTICAL_RESOLUTION 480
#define CROWPANEL_LCD_SPI_TRANSFER_HEIGHT (CROWPANEL_LCD_VERTICAL_RESOLUTION / 10)
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
@@ -12,6 +12,8 @@ hardware.tinyUsb=true
hardware.esptoolFlashFreq=120M
hardware.bluetooth=true
dependencies.useDeprecatedHal=false
storage.userDataLocation=SD
display.size=3.5"
@@ -1,3 +1,5 @@
dependencies:
- Platforms/platform-esp32
- Drivers/ili9488-module
- Drivers/gt911-module
dts: elecrow,crowpanel-advance-35.dts
@@ -8,7 +8,9 @@
#include <tactility/bindings/esp32_spi.h>
#include <tactility/bindings/esp32_uart.h>
#include <tactility/bindings/esp32_sdspi.h>
#include <tactility/bindings/display_placeholder.h>
#include <tactility/bindings/esp32_ledc_backlight.h>
#include <bindings/ili9488.h>
#include <bindings/gt911.h>
/ {
compatible = "root";
@@ -35,6 +37,22 @@
clock-frequency = <400000>;
pin-sda = <&gpio0 15 GPIO_FLAG_NONE>;
pin-scl = <&gpio0 16 GPIO_FLAG_NONE>;
touch {
compatible = "goodix,gt911";
reg = <0x5D>;
x-max = <320>;
y-max = <480>;
};
};
display_backlight {
compatible = "espressif,esp32-ledc-backlight";
// Off by default so display power-on won't show the screen from before the last power loss.
// The display backlight is turned on during the boot process.
status = "disabled";
pin-backlight = <&gpio0 38 GPIO_FLAG_NONE>;
frequency-hz = <512>;
};
spi0 {
@@ -44,8 +62,15 @@
pin-mosi = <&gpio0 39 GPIO_FLAG_NONE>;
pin-sclk = <&gpio0 42 GPIO_FLAG_NONE>;
display {
compatible = "display-placeholder";
display@0 {
compatible = "ilitek,ili9488";
horizontal-resolution = <320>;
vertical-resolution = <480>;
invert-color;
color-conversion-buffer-size = <15360>;
pixel-clock-hz = <40000000>;
pin-dc = <&gpio0 41 GPIO_FLAG_NONE>;
backlight = <&display_backlight>;
};
};
@@ -3,16 +3,14 @@
extern "C" {
static error_t start() {
// Empty for now
return ERROR_NONE;
}
static error_t stop() {
// Empty for now
return ERROR_NONE;
}
struct Module elecrow_crowpanel_advance_35_module = {
Module elecrow_crowpanel_advance_35_module = {
.name = "elecrow-crowpanel-advance-35",
.start = start,
.stop = stop,
@@ -1,7 +1,6 @@
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
file(GLOB_RECURSE SOURCE_FILES source/*.c*)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source"
REQUIRES Tactility esp_lvgl_port ILI9488 XPT2046 PwmBacklight driver
REQUIRES TactilityKernel driver
)
@@ -1,25 +0,0 @@
#include "PwmBacklight.h"
#include "devices/Display.h"
#include <Tactility/hal/Configuration.h>
using namespace tt::hal;
static bool initBoot() {
driver::pwmbacklight::init(GPIO_NUM_27);
// Delay is required, or GUI will lock up.
// It's possibly related to the increased power draw when turning on the backlight.
vTaskDelay(100);
return true;
}
static DeviceVector createDevices() {
return {
createDisplay(),
};
}
extern const Configuration hardwareConfiguration = {
.initBoot = initBoot,
.createDevices = createDevices
};
@@ -1,40 +0,0 @@
#include "Display.h"
#include <Ili9488Display.h>
#include <PwmBacklight.h>
#include <Xpt2046Touch.h>
std::shared_ptr<Xpt2046Touch> createTouch() {
auto configuration = std::make_unique<Xpt2046Touch::Configuration>(
CROWPANEL_LCD_SPI_HOST,
CROWPANEL_TOUCH_PIN_CS,
320,
480,
false,
false,
true
);
return std::make_shared<Xpt2046Touch>(std::move(configuration));
}
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
auto touch = createTouch();
auto configuration = std::make_unique<Ili9488Display::Configuration>(
CROWPANEL_LCD_SPI_HOST,
CROWPANEL_LCD_PIN_CS,
CROWPANEL_LCD_PIN_DC,
320,
480,
touch,
false,
false,
false
);
configuration->backlightDutyFunction = driver::pwmbacklight::setBacklightDuty;
auto display = std::make_shared<Ili9488Display>(std::move(configuration));
return std::reinterpret_pointer_cast<tt::hal::display::DisplayDevice>(display);
}
@@ -1,13 +0,0 @@
#pragma once
#include <Tactility/hal/display/DisplayDevice.h>
#define CROWPANEL_LCD_SPI_HOST SPI2_HOST
#define CROWPANEL_LCD_PIN_CS GPIO_NUM_15
#define CROWPANEL_TOUCH_PIN_CS GPIO_NUM_12
#define CROWPANEL_LCD_PIN_DC GPIO_NUM_2 // RS
#define CROWPANEL_LCD_HORIZONTAL_RESOLUTION 320
#define CROWPANEL_LCD_VERTICAL_RESOLUTION 240
#define CROWPANEL_LCD_SPI_TRANSFER_HEIGHT (CROWPANEL_LCD_VERTICAL_RESOLUTION / 10)
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
@@ -7,6 +7,8 @@ hardware.target=ESP32
hardware.flashSize=4MB
hardware.spiRam=false
dependencies.useDeprecatedHal=false
storage.userDataLocation=SD
display.size=3.5"
@@ -1,3 +1,5 @@
dependencies:
- Platforms/platform-esp32
- Drivers/ili9488-module
- Drivers/xpt2046-module
dts: elecrow,crowpanel-basic-35.dts
@@ -7,8 +7,9 @@
#include <tactility/bindings/esp32_spi.h>
#include <tactility/bindings/esp32_uart.h>
#include <tactility/bindings/esp32_sdspi.h>
#include <tactility/bindings/display_placeholder.h>
#include <tactility/bindings/pointer_placeholder.h>
#include <tactility/bindings/esp32_ledc_backlight.h>
#include <bindings/ili9488.h>
#include <bindings/xpt2046.h>
/ {
compatible = "root";
@@ -32,6 +33,15 @@
pin-scl = <&gpio0 21 GPIO_FLAG_NONE>;
};
display_backlight {
compatible = "espressif,esp32-ledc-backlight";
// Off by default so display power-on won't show the screen from before the last power loss.
// The display backlight is turned on during the boot process.
status = "disabled";
pin-backlight = <&gpio0 27 GPIO_FLAG_NONE>;
frequency-hz = <512>;
};
spi0 {
compatible = "espressif,esp32-spi";
host = <SPI2_HOST>;
@@ -43,11 +53,20 @@
max-transfer-size = <65536>;
display@0 {
compatible = "display-placeholder";
compatible = "ilitek,ili9488";
horizontal-resolution = <320>;
vertical-resolution = <480>;
color-conversion-buffer-size = <15360>;
pixel-clock-hz = <40000000>;
pin-dc = <&gpio0 2 GPIO_FLAG_NONE>;
backlight = <&display_backlight>;
};
touch@1 {
compatible = "pointer-placeholder";
compatible = "xptek,xpt2046";
x-max = <320>;
y-max = <480>;
mirror-y;
};
};
@@ -3,16 +3,14 @@
extern "C" {
static error_t start() {
// Empty for now
return ERROR_NONE;
}
static error_t stop() {
// Empty for now
return ERROR_NONE;
}
struct Module elecrow_crowpanel_basic_35_module = {
Module elecrow_crowpanel_basic_35_module = {
.name = "elecrow-crowpanel-basic-35",
.start = start,
.stop = stop,
+3 -3
View File
@@ -1,7 +1,7 @@
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
file(GLOB_RECURSE SOURCE_FILES source/*.c*)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source"
REQUIRES Tactility esp_lvgl_port esp_lcd ST7789 PwmBacklight driver esp_adc EstimatedPower vfs fatfs TCA8418
INCLUDE_DIRS "source"
REQUIRES TactilityKernel
)
@@ -1,30 +0,0 @@
#include "devices/Display.h"
#include "devices/CardputerKeyboard.h"
#include "devices/CardputerPower.h"
#include <tactility/device.h>
#include <Tactility/hal/Configuration.h>
#include <PwmBacklight.h>
#include <Tca8418.h>
using namespace tt::hal;
static bool initBoot() {
return driver::pwmbacklight::init(LCD_PIN_BACKLIGHT, 512);
}
static DeviceVector createDevices() {
auto tca8418 = std::make_shared<Tca8418>(device_find_by_name("i2c_internal"));
return {
createDisplay(),
tca8418,
std::make_shared<CardputerKeyboard>(tca8418),
std::make_shared<CardputerPower>()
};
}
extern const Configuration hardwareConfiguration = {
.initBoot = initBoot,
.createDevices = createDevices
};
@@ -1,153 +0,0 @@
#include "CardputerKeyboard.h"
#include <tactility/drivers/i2c_controller.h>
constexpr auto* TAG = "CardputerKeyb";
constexpr auto KB_ROWS = 14;
constexpr auto KB_COLS = 4;
// Lowercase Keymap
static constexpr char keymap_lc[KB_COLS][KB_ROWS] = {
{'`', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '_', '=', LV_KEY_BACKSPACE},
{'\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\\'},
{'\0', '\0', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', LV_KEY_ENTER},
{'\0', '\0', '\0', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', ' '}
};
// Uppercase Keymap
static constexpr char keymap_uc[KB_COLS][KB_ROWS] = {
{'~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '-', '+', LV_KEY_DEL},
{'\t', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '|'},
{'\0', '\0', 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', LV_KEY_ENTER},
{'\0', '\0', '\0', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', ' '}
};
// Symbol Keymap
static constexpr char keymap_sy[KB_COLS][KB_ROWS] = {
{LV_KEY_ESC, '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0'},
{'\t', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0'},
{'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', LV_KEY_PREV, '\0', LV_KEY_ENTER},
{'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', LV_KEY_LEFT, LV_KEY_NEXT, LV_KEY_RIGHT, '\0'}
};
void CardputerKeyboard::readCallback(lv_indev_t* indev, lv_indev_data_t* data) {
auto keyboard = static_cast<CardputerKeyboard*>(lv_indev_get_user_data(indev));
char keypress = 0;
if (xQueueReceive(keyboard->queue, &keypress, 0) == pdPASS) {
data->key = keypress;
data->state = LV_INDEV_STATE_PRESSED;
} else {
data->key = 0;
data->state = LV_INDEV_STATE_RELEASED;
}
}
void CardputerKeyboard::remap(uint8_t& row, uint8_t& col) {
// Col
uint8_t coltemp = row * 2;
if (col > 3) coltemp++;
// Row
uint8_t rowtemp = (col + 4) % 4;
row = rowtemp;
col = coltemp;
}
void CardputerKeyboard::processKeyboard() {
static bool shift_pressed = false;
static bool sym_pressed = false;
static bool cap_toggle = false;
static bool cap_toggle_armed = true;
if (keypad->update()) {
// Check if symbol or shift is pressed
for (int i = 0; i < keypad->pressed_key_count; i++) {
// Swap rows and columns
uint8_t row = keypad->pressed_list[i].row;
uint8_t column = keypad->pressed_list[i].col;
remap(row, column);
if ((row == 2) && (column == 0)) {
sym_pressed = true;
}
if ((row == 2) && (column == 1)) {
shift_pressed = true;
}
}
// Toggle caps lock
if ((sym_pressed && shift_pressed) && cap_toggle_armed) {
cap_toggle = !cap_toggle;
cap_toggle_armed = false;
}
// Process regular key input given the processed modifiers
for (int i = 0; i < keypad->pressed_key_count; i++) {
auto row = keypad->pressed_list[i].row;
auto column = keypad->pressed_list[i].col;
remap(row, column);
char chr = '\0';
if (sym_pressed) {
chr = keymap_sy[row][column];
} else if (shift_pressed || cap_toggle) {
chr = keymap_uc[row][column];
} else {
chr = keymap_lc[row][column];
}
if (chr != '\0') xQueueSend(queue, &chr, 50 / portTICK_PERIOD_MS);
}
for (int i = 0; i < keypad->released_key_count; i++) {
auto row = keypad->released_list[i].row;
auto column = keypad->released_list[i].col;
remap(row, column);
if ((row == 2) && (column == 0)) {
sym_pressed = false;
}
if ((row == 2) && (column == 1)) {
shift_pressed = false;
}
}
if ((!sym_pressed && !shift_pressed) && !cap_toggle_armed) {
cap_toggle_armed = true;
}
}
}
bool CardputerKeyboard::startLvgl(lv_display_t* display) {
keypad->init(7, 8);
assert(inputTimer == nullptr);
inputTimer = std::make_unique<tt::Timer>(tt::Timer::Type::Periodic, pdMS_TO_TICKS(20), [this] {
processKeyboard();
});
kbHandle = lv_indev_create();
lv_indev_set_type(kbHandle, LV_INDEV_TYPE_KEYPAD);
lv_indev_set_read_cb(kbHandle, &readCallback);
lv_indev_set_display(kbHandle, display);
lv_indev_set_user_data(kbHandle, this);
inputTimer->start();
return true;
}
bool CardputerKeyboard::stopLvgl() {
assert(inputTimer);
inputTimer->stop();
inputTimer = nullptr;
lv_indev_delete(kbHandle);
kbHandle = nullptr;
return true;
}
bool CardputerKeyboard::isAttached() const {
return i2c_controller_has_device_at_address(keypad->getController(), keypad->getAddress(), 100) == ERROR_NONE;
}
@@ -1,46 +0,0 @@
#pragma once
#include <Tactility/hal/keyboard/KeyboardDevice.h>
#include <Tca8418.h>
#include <Tactility/Timer.h>
#include <freertos/queue.h>
class CardputerKeyboard final : public tt::hal::keyboard::KeyboardDevice {
lv_indev_t* kbHandle = nullptr;
QueueHandle_t queue = nullptr;
std::shared_ptr<Tca8418> keypad;
std::unique_ptr<tt::Timer> inputTimer;
void processKeyboard();
static void readCallback(lv_indev_t* indev, lv_indev_data_t* data);
/**
* Remaps wiring coordinates to keyboard mapping coordinates.
* Wiring is 7x8 (rows & colums), but our keyboard definition is 4x14)
*/
static void remap(uint8_t& row, uint8_t& column);
public:
explicit CardputerKeyboard(const std::shared_ptr<Tca8418>& tca) : keypad(tca) {
queue = xQueueCreate(20, sizeof(char));
}
~CardputerKeyboard() override {
vQueueDelete(queue);
}
std::string getName() const override { return "TCA8418"; }
std::string getDescription() const override { return "TCA8418 I2C keyboard"; }
bool startLvgl(lv_display_t* display) override;
bool stopLvgl() override;
bool isAttached() const override;
lv_indev_t* getLvglIndev() override { return kbHandle; }
};
@@ -1,85 +0,0 @@
#include "CardputerPower.h"
#include <driver/adc.h>
#include <tactility/log.h>
constexpr auto* TAG = "CardputerPower";
bool CardputerPower::adcInitCalibration() {
bool calibrated = false;
esp_err_t efuse_read_result = esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP_FIT);
if (efuse_read_result == ESP_ERR_NOT_SUPPORTED) {
LOG_W(TAG, "Calibration scheme not supported, skip software calibration");
} else if (efuse_read_result == ESP_ERR_INVALID_VERSION) {
LOG_W(TAG, "eFuse not burnt, skip software calibration");
} else if (efuse_read_result == ESP_OK) {
calibrated = true;
LOG_I(TAG, "Calibration success");
esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, static_cast<adc_bits_width_t>(ADC_WIDTH_BIT_DEFAULT), 0, &adcCharacteristics);
} else {
LOG_W(TAG, "eFuse read failed, skipping calibration");
}
return calibrated;
}
uint32_t CardputerPower::adcReadValue() const {
int adc_raw = adc1_get_raw(ADC1_CHANNEL_9);
LOG_D(TAG, "Raw data: %d", adc_raw);
float voltage;
if (calibrated) {
voltage = esp_adc_cal_raw_to_voltage(adc_raw, &adcCharacteristics);
LOG_D(TAG, "Calibrated data: %f mV", voltage);
} else {
voltage = 0.0f;
}
return voltage;
}
bool CardputerPower::ensureInitialized() {
if (!initialized) {
calibrated = adcInitCalibration();
if (adc1_config_width(static_cast<adc_bits_width_t>(ADC_WIDTH_BIT_DEFAULT)) != ESP_OK) {
LOG_E(TAG, "ADC1 config width failed");
return false;
}
if (adc1_config_channel_atten(ADC1_CHANNEL_9, ADC_ATTEN_DB_11) != ESP_OK) {
LOG_E(TAG, "ADC1 config attenuation failed");
return false;
}
initialized = true;
}
return true;
}
bool CardputerPower::supportsMetric(MetricType type) const {
switch (type) {
using enum MetricType;
case BatteryVoltage:
case ChargeLevel:
return true;
default:
return false;
}
}
bool CardputerPower::getMetric(MetricType type, MetricData& data) {
if (!ensureInitialized()) {
return false;
}
switch (type) {
case MetricType::BatteryVoltage:
data.valueAsUint32 = adcReadValue() * 2;
return true;
case MetricType::ChargeLevel:
data.valueAsUint8 = chargeFromAdcVoltage.estimateCharge(adcReadValue() * 2);
return true;
default:
return false;
}
}
@@ -1,29 +0,0 @@
#pragma once
#include <Tactility/hal/power/PowerDevice.h>
#include <ChargeFromVoltage.h>
#include <esp_adc_cal.h>
#include <string>
using tt::hal::power::PowerDevice;
class CardputerPower final : public PowerDevice {
ChargeFromVoltage chargeFromAdcVoltage = ChargeFromVoltage(3.3f, 4.2f);
bool initialized = false;
esp_adc_cal_characteristics_t adcCharacteristics;
bool calibrated = false;
bool adcInitCalibration();
uint32_t adcReadValue() const;
bool ensureInitialized();
public:
std::string getName() const override { return "Cardputer Power"; }
std::string getDescription() const override { return "Power measurement via ADC"; }
bool supportsMetric(MetricType type) const override;
bool getMetric(MetricType type, MetricData& data) override;
};
@@ -1,32 +0,0 @@
#include "Display.h"
#include <PwmBacklight.h>
#include <St7789Display.h>
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
St7789Display::Configuration panel_configuration = {
.horizontalResolution = LCD_HORIZONTAL_RESOLUTION,
.verticalResolution = LCD_VERTICAL_RESOLUTION,
.gapX = 53, // Should be 52 according to https://github.com/m5stack/M5GFX/blob/master/src/M5GFX.cpp but this leaves a gap at the bottom
.gapY = 40,
.swapXY = true,
.mirrorX = true,
.mirrorY = false,
.invertColor = true,
.bufferSize = LCD_BUFFER_SIZE,
.touch = nullptr,
.backlightDutyFunction = driver::pwmbacklight::setBacklightDuty,
.resetPin = LCD_PIN_RESET,
.lvglSwapBytes = false
};
auto spi_configuration = std::make_shared<St7789Display::SpiConfiguration>(St7789Display::SpiConfiguration {
.spiHostDevice = LCD_SPI_HOST,
.csPin = LCD_PIN_CS,
.dcPin = LCD_PIN_DC,
.pixelClockFrequency = 62'500'000,
.transactionQueueDepth = 10
});
return std::make_shared<St7789Display>(panel_configuration, spi_configuration);
}
@@ -1,19 +0,0 @@
#pragma once
#include <Tactility/hal/display/DisplayDevice.h>
#include <memory>
#include <driver/gpio.h>
#include <driver/spi_common.h>
constexpr auto LCD_SPI_HOST = SPI2_HOST;
constexpr auto LCD_PIN_CS = GPIO_NUM_37;
constexpr auto LCD_PIN_DC = GPIO_NUM_34; // RS
constexpr auto LCD_PIN_RESET = GPIO_NUM_33;
constexpr auto LCD_PIN_BACKLIGHT = GPIO_NUM_38;
constexpr auto LCD_HORIZONTAL_RESOLUTION = 240;
constexpr auto LCD_VERTICAL_RESOLUTION = 135;
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();
@@ -10,6 +10,8 @@ hardware.tinyUsb=true
hardware.esptoolFlashFreq=120M
hardware.bluetooth=true
dependencies.useDeprecatedHal=false
storage.userDataLocation=SD
display.size=1.14"
@@ -1,4 +1,6 @@
dependencies:
- Platforms/platform-esp32
- Drivers/bmi270-module
- Drivers/st7789-module
- Drivers/m5stack-module
dts: m5stack,cardputer-adv.dts
@@ -1,6 +1,8 @@
/dts-v1/;
#include <tactility/bindings/root.h>
#include <tactility/bindings/battery_sense.h>
#include <tactility/bindings/esp32_adc_oneshot.h>
#include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h>
@@ -11,7 +13,10 @@
#include <tactility/bindings/esp32_uart.h>
#include <bindings/bmi270.h>
#include <tactility/bindings/esp32_sdspi.h>
#include <tactility/bindings/display_placeholder.h>
#include <tactility/bindings/esp32_ledc_backlight.h>
#include <bindings/st7789.h>
#include <bindings/cardputer_adv_keyboard.h>
// Reference: https://docs.m5stack.com/en/core/Cardputer-Adv
/ {
@@ -33,6 +38,21 @@
gpio-count = <49>;
};
adc0 {
compatible = "espressif,esp32-adc-oneshot";
unit-id = <ADC_UNIT_1>;
clk-src = <ADC_RTC_CLK_SRC_DEFAULT>;
// GPIO10 (ADC1_CHANNEL_9 on ESP32-S3): battery voltage sense, behind a 1:2 divider.
channels = <ADC_CHANNEL_9 ADC_ATTEN_DB_12 ADC_BITWIDTH_DEFAULT>;
};
battery-sense {
compatible = "battery-sense";
io-channel = <&adc0 0>;
reference-voltage-mv = <3300>;
multiplier = <2000>;
};
i2c_internal {
compatible = "espressif,esp32-i2c";
port = <I2C_NUM_0>;
@@ -44,6 +64,11 @@
compatible = "bosch,bmi270";
reg = <0x69>;
};
keyboard {
compatible = "m5stack,cardputer-adv-keyboard";
reg = <0x34>;
};
};
port_a: grove0 {
@@ -56,6 +81,15 @@
i2cClockFrequency = <400000>;
};
display_backlight {
compatible = "espressif,esp32-ledc-backlight";
// Off by default so display power-on won't show the screen from before the last power loss.
// The display backlight is turned on during the boot process.
status = "disabled";
pin-backlight = <&gpio0 38 GPIO_FLAG_NONE>;
frequency-hz = <512>;
};
spi0 {
compatible = "espressif,esp32-spi";
host = <SPI2_HOST>;
@@ -63,8 +97,20 @@
pin-mosi = <&gpio0 35 GPIO_FLAG_NONE>;
pin-sclk = <&gpio0 36 GPIO_FLAG_NONE>;
display {
compatible = "display-placeholder";
display@0 {
compatible = "sitronix,st7789";
horizontal-resolution = <240>;
vertical-resolution = <135>;
// Should be 52 according to https://github.com/m5stack/M5GFX/blob/master/src/M5GFX.cpp but this leaves a gap at the bottom
gap-x = <53>;
gap-y = <40>;
swap-xy;
mirror-x;
invert-color;
pixel-clock-hz = <62500000>;
pin-dc = <&gpio0 34 GPIO_FLAG_NONE>;
pin-reset = <&gpio0 33 GPIO_FLAG_NONE>;
backlight = <&display_backlight>;
};
};
+3 -3
View File
@@ -1,7 +1,7 @@
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
file(GLOB_RECURSE SOURCE_FILES source/*.c*)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source"
REQUIRES Tactility esp_lvgl_port esp_lcd ST7789 PwmBacklight driver esp_adc EstimatedPower vfs fatfs
INCLUDE_DIRS "source"
REQUIRES TactilityKernel
)
@@ -1,27 +0,0 @@
#include "devices/Display.h"
#include "devices/CardputerEncoder.h"
#include "devices/CardputerKeyboard.h"
#include "devices/CardputerPower.h"
#include <PwmBacklight.h>
#include <Tactility/hal/Configuration.h>
using namespace tt::hal;
static bool initBoot() {
return driver::pwmbacklight::init(LCD_PIN_BACKLIGHT, 512);
}
static DeviceVector createDevices() {
return {
createDisplay(),
std::make_shared<CardputerKeyboard>(),
std::make_shared<CardputerEncoder>(),
std::make_shared<CardputerPower>()
};
}
extern const Configuration hardwareConfiguration = {
.initBoot = initBoot,
.createDevices = createDevices
};
@@ -1,54 +0,0 @@
#include "CardputerEncoder.h"
void CardputerEncoder::readCallback(lv_indev_t* indev, lv_indev_data_t* data) {
CardputerEncoder* self = static_cast<CardputerEncoder*>(lv_indev_get_user_data(indev));
self->keyboard.updateKeyList();
data->state = LV_INDEV_STATE_RELEASED;
if (self->keyboard.keyList().size() != self->lastKeyNum) {
// If key pressed
if (self->keyboard.keyList().size() != 0) {
// Update states and values
self->keyboard.updateKeysState();
if (self->keyboard.keysState().fn) {
if (self->keyboard.keysState().enter) {
data->key = LV_KEY_ENTER;
data->state = LV_INDEV_STATE_PRESSED;
} else {
for (auto& i : self->keyboard.keysState().values) {
if (i == ';') { // Up
data->enc_diff = -1;
} else if (i == '.') { // Down
data->enc_diff = 1;
}
break; // We only care about the first value
}
}
}
self->lastKeyNum = self->keyboard.keyList().size();
} else {
self->lastKeyNum = 0;
}
}
}
bool CardputerEncoder::startLvgl(lv_display_t* display) {
keyboard.init();
lvglDevice = lv_indev_create();
lv_indev_set_type(lvglDevice, LV_INDEV_TYPE_ENCODER);
lv_indev_set_read_cb(lvglDevice, &readCallback);
lv_indev_set_display(lvglDevice, display);
lv_indev_set_user_data(lvglDevice, this);
return true;
}
bool CardputerEncoder::stopLvgl() {
lv_indev_delete(lvglDevice);
lvglDevice = nullptr;
return true;
}
@@ -1,29 +0,0 @@
#pragma once
#include <Tactility/hal/encoder/EncoderDevice.h>
#include "keyboard/keyboard.h"
/**
* Wrapper around the keyboard that uses the following buttons to simulate an encoder:
* - Up
* - Down
* - ok (fn + enter)
*/
class CardputerEncoder final : public tt::hal::encoder::EncoderDevice {
KEYBOARD::Keyboard keyboard;
int lastKeyNum = 0;
lv_indev_t* lvglDevice = nullptr;
static void readCallback(lv_indev_t* indev, lv_indev_data_t* data);
public:
std::string getName() const override { return "Cardputer Encoder"; }
std::string getDescription() const override { return "Cardputer keyboard up/down acting as encoder"; }
bool startLvgl(lv_display_t* display) override;
bool stopLvgl() override;
lv_indev_t* getLvglIndev() override { return lvglDevice; }
};
@@ -1,102 +0,0 @@
#include "CardputerKeyboard.h"
#include <tactility/log.h>
constexpr auto* TAG = "Keyboard";
bool CardputerKeyboard::startLvgl(lv_display_t* display) {
keyboard.init();
lvglDevice = lv_indev_create();
lv_indev_set_type(lvglDevice, LV_INDEV_TYPE_KEYPAD);
lv_indev_set_read_cb(lvglDevice, &readCallback);
lv_indev_set_display(lvglDevice, display);
lv_indev_set_user_data(lvglDevice, this);
return true;
}
bool CardputerKeyboard::stopLvgl() {
lv_indev_delete(lvglDevice);
lvglDevice = nullptr;
return true;
}
void CardputerKeyboard::readCallback(lv_indev_t* indev, lv_indev_data_t* data) {
CardputerKeyboard* self = static_cast<CardputerKeyboard*>(lv_indev_get_user_data(indev));
data->key = 0;
data->state = LV_INDEV_STATE_RELEASED;
self->keyboard.updateKeyList();
if (self->keyboard.keyList().size() != self->lastKeyNum) {
// If key pressed
if (self->keyboard.keyList().size() != 0) {
// Update states and values
self->keyboard.updateKeysState();
if (!self->keyboard.keysState().fn) {
if (self->keyboard.keysState().enter) {
data->key = LV_KEY_ENTER;
data->state = LV_INDEV_STATE_PRESSED;
} else if (self->keyboard.keysState().space) {
data->key = ' ';
data->state = LV_INDEV_STATE_PRESSED;
} else if (self->keyboard.keysState().del) {
data->key = LV_KEY_BACKSPACE;
data->state = LV_INDEV_STATE_PRESSED;
} else {
// Normal chars
for (auto& i : self->keyboard.keysState().values) {
data->key = i;
data->state = LV_INDEV_STATE_PRESSED;
break; // We only support 1 keypress for now
}
}
} else {
if (self->keyboard.keysState().del) {
LOG_I(TAG, "del");
data->key = LV_KEY_DEL;
data->state = LV_INDEV_STATE_PRESSED;
} else {
for (auto& i : self->keyboard.keysState().values) {
if (i == ';') { // Up
/*
* WARNING:
* lv_switch picks up on this and toggles it, while the CardputerEncoder uses it for scrolling.
* We disable the keypress so the encoder can work properly.
*/
// TODO: Can we detect the active widget and ignore it in case it's a switch?
// data->key = LV_KEY_UP;
// data->state = LV_INDEV_STATE_PRESSED;
} else if (i == '.') { // Down
/*
* WARNING:
* lv_switch picks up on this and toggles it, while the CardputerEncoder uses it for scrolling.
* We disable the keypress so the encoder can work properly.
*/
// TODO: Can we detect the active widget and ignore it in case it's a switch?
// data->key = LV_KEY_DOWN;
// data->state = LV_INDEV_STATE_PRESSED;
} else if (i == ',') { // Left
data->key = LV_KEY_LEFT;
data->state = LV_INDEV_STATE_PRESSED;
} else if (i == '/') { // Right
data->key = LV_KEY_RIGHT;
data->state = LV_INDEV_STATE_PRESSED;
} else if (i == '`') { // Escape
data->key = LV_KEY_ESC;
data->state = LV_INDEV_STATE_PRESSED;
}
break; // We only support 1 keypress for now
}
}
}
self->lastKeyNum = self->keyboard.keyList().size();
} else {
self->lastKeyNum = 0;
}
}
}
@@ -1,26 +0,0 @@
#pragma once
#include <Tactility/Thread.h>
#include <Tactility/hal/keyboard/KeyboardDevice.h>
#include "keyboard/keyboard.h"
class CardputerKeyboard : public tt::hal::keyboard::KeyboardDevice {
KEYBOARD::Keyboard keyboard;
int lastKeyNum = 0;
lv_indev_t* lvglDevice = nullptr;
static void readCallback(lv_indev_t* indev, lv_indev_data_t* data);
public:
std::string getName() const override { return "Cardputer Keyboard"; }
std::string getDescription() const override { return "Cardputer internal keyboard"; }
bool startLvgl(lv_display_t* display) override;
bool stopLvgl() override;
bool isAttached() const override { return true; }
lv_indev_t* getLvglIndev() override { return lvglDevice; }
};
@@ -1,85 +0,0 @@
#include "CardputerPower.h"
#include <driver/adc.h>
#include <tactility/log.h>
constexpr auto* TAG = "CardputerPower";
bool CardputerPower::adcInitCalibration() {
bool calibrated = false;
esp_err_t efuse_read_result = esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP_FIT);
if (efuse_read_result == ESP_ERR_NOT_SUPPORTED) {
LOG_W(TAG, "Calibration scheme not supported, skip software calibration");
} else if (efuse_read_result == ESP_ERR_INVALID_VERSION) {
LOG_W(TAG, "eFuse not burnt, skip software calibration");
} else if (efuse_read_result == ESP_OK) {
calibrated = true;
LOG_I(TAG, "Calibration success");
esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, static_cast<adc_bits_width_t>(ADC_WIDTH_BIT_DEFAULT), 0, &adcCharacteristics);
} else {
LOG_W(TAG, "eFuse read failed, skipping calibration");
}
return calibrated;
}
uint32_t CardputerPower::adcReadValue() const {
int adc_raw = adc1_get_raw(ADC1_CHANNEL_9);
LOG_D(TAG, "Raw data: %d", adc_raw);
float voltage;
if (calibrated) {
voltage = esp_adc_cal_raw_to_voltage(adc_raw, &adcCharacteristics);
LOG_D(TAG, "Calibrated data: %f mV", voltage);
} else {
voltage = 0.0f;
}
return voltage;
}
bool CardputerPower::ensureInitialized() {
if (!initialized) {
calibrated = adcInitCalibration();
if (adc1_config_width(static_cast<adc_bits_width_t>(ADC_WIDTH_BIT_DEFAULT)) != ESP_OK) {
LOG_E(TAG, "ADC1 config width failed");
return false;
}
if (adc1_config_channel_atten(ADC1_CHANNEL_9, ADC_ATTEN_DB_11) != ESP_OK) {
LOG_E(TAG, "ADC1 config attenuation failed");
return false;
}
initialized = true;
}
return true;
}
bool CardputerPower::supportsMetric(MetricType type) const {
switch (type) {
using enum MetricType;
case BatteryVoltage:
case ChargeLevel:
return true;
default:
return false;
}
}
bool CardputerPower::getMetric(MetricType type, MetricData& data) {
if (!ensureInitialized()) {
return false;
}
switch (type) {
case MetricType::BatteryVoltage:
data.valueAsUint32 = adcReadValue() * 2;
return true;
case MetricType::ChargeLevel:
data.valueAsUint8 = chargeFromAdcVoltage.estimateCharge(adcReadValue() * 2);
return true;
default:
return false;
}
}
@@ -1,29 +0,0 @@
#pragma once
#include <Tactility/hal/power/PowerDevice.h>
#include <ChargeFromVoltage.h>
#include <esp_adc_cal.h>
#include <string>
using tt::hal::power::PowerDevice;
class CardputerPower final : public PowerDevice {
ChargeFromVoltage chargeFromAdcVoltage = ChargeFromVoltage(3.3f, 4.2f);
bool initialized = false;
esp_adc_cal_characteristics_t adcCharacteristics;
bool calibrated = false;
bool adcInitCalibration();
uint32_t adcReadValue() const;
bool ensureInitialized();
public:
std::string getName() const override { return "Cardputer Power"; }
std::string getDescription() const override { return "Power measurement via ADC"; }
bool supportsMetric(MetricType type) const override;
bool getMetric(MetricType type, MetricData& data) override;
};
@@ -1,32 +0,0 @@
#include "Display.h"
#include <PwmBacklight.h>
#include <St7789Display.h>
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
St7789Display::Configuration panel_configuration = {
.horizontalResolution = LCD_HORIZONTAL_RESOLUTION,
.verticalResolution = LCD_VERTICAL_RESOLUTION,
.gapX = 53, // Should be 52 according to https://github.com/m5stack/M5GFX/blob/master/src/M5GFX.cpp but this leaves a gap at the bottom
.gapY = 40,
.swapXY = true,
.mirrorX = true,
.mirrorY = false,
.invertColor = true,
.bufferSize = LCD_BUFFER_SIZE,
.touch = nullptr,
.backlightDutyFunction = driver::pwmbacklight::setBacklightDuty,
.resetPin = LCD_PIN_RESET,
.lvglSwapBytes = false
};
auto spi_configuration = std::make_shared<St7789Display::SpiConfiguration>(St7789Display::SpiConfiguration {
.spiHostDevice = LCD_SPI_HOST,
.csPin = LCD_PIN_CS,
.dcPin = LCD_PIN_DC,
.pixelClockFrequency = 62'500'000,
.transactionQueueDepth = 10
});
return std::make_shared<St7789Display>(panel_configuration, spi_configuration);
}
@@ -1,19 +0,0 @@
#pragma once
#include <Tactility/hal/display/DisplayDevice.h>
#include <memory>
#include <driver/gpio.h>
#include <driver/spi_common.h>
constexpr auto LCD_SPI_HOST = SPI2_HOST;
constexpr auto LCD_PIN_CS = GPIO_NUM_37;
constexpr auto LCD_PIN_DC = GPIO_NUM_34; // RS
constexpr auto LCD_PIN_RESET = GPIO_NUM_33;
constexpr auto LCD_PIN_BACKLIGHT = GPIO_NUM_38;
constexpr auto LCD_HORIZONTAL_RESOLUTION = 240;
constexpr auto LCD_VERTICAL_RESOLUTION = 135;
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,31 +0,0 @@
# keyboard folder
The keyboard code in this folder is from https://github.com/m5stack/M5Cardputer-UserDemo/tree/main/main/hal/keyboard
# License
The original license is an MIT license:
```
MIT License
Copyright (c) 2023 M5Stack
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
@@ -1,266 +0,0 @@
/**
* @file keyboard.cpp
* @author Forairaaaaa
* @brief
* @version 0.1
* @date 2023-09-22
*
* @copyright Copyright (c) 2023
*
*/
#include "keyboard.h"
#include <driver/gpio.h>
#define digitalWrite(pin, level) gpio_set_level((gpio_num_t)pin, level)
#define digitalRead(pin) gpio_get_level((gpio_num_t)pin)
using namespace KEYBOARD;
void Keyboard::_set_output(const std::vector<int>& pinList, uint8_t output)
{
output = output & 0B00000111;
digitalWrite(pinList[0], (output & 0B00000001));
digitalWrite(pinList[1], (output & 0B00000010));
digitalWrite(pinList[2], (output & 0B00000100));
}
uint8_t Keyboard::_get_input(const std::vector<int>& pinList)
{
uint8_t buffer = 0x00;
uint8_t pin_value = 0x00;
for (int i = 0; i < 7; i++)
{
pin_value = (digitalRead(pinList[i]) == 1) ? 0x00 : 0x01;
pin_value = pin_value << i;
buffer = buffer | pin_value;
}
return buffer;
}
void Keyboard::init()
{
for (auto i : output_list)
{
gpio_reset_pin((gpio_num_t)i);
gpio_set_direction((gpio_num_t)i, GPIO_MODE_OUTPUT);
gpio_set_pull_mode((gpio_num_t)i, GPIO_PULLUP_PULLDOWN);
digitalWrite(i, 0);
}
for (auto i : input_list)
{
gpio_reset_pin((gpio_num_t)i);
gpio_set_direction((gpio_num_t)i, GPIO_MODE_INPUT);
gpio_set_pull_mode((gpio_num_t)i, GPIO_PULLUP_ONLY);
}
_set_output(output_list, 0);
}
Point2D_t Keyboard::getKey()
{
Point2D_t coor;
coor.x = -1;
coor.y = -1;
uint8_t input_value = 0;
for (int i = 0; i < 8; i++) {
_set_output(output_list, i);
// printf("% 3d,\t", get_input(inputList));
input_value = _get_input(input_list);
/* If key pressed */
if (input_value) {
/* Get X */
for (int j = 0; j < 7; j++) {
if (input_value == X_map_chart[j].value) {
coor.x = (i > 3) ? X_map_chart[j].x_1 : X_map_chart[j].x_2;
break;
}
}
/* Get Y */
coor.y = (i > 3) ? (i - 4) : i;
/* Keep the same as picture */
coor.y = -coor.y;
coor.y = coor.y + 3;
break;
}
}
// printf("%d,%d\n", x, y);
return coor;
}
uint8_t Keyboard::getKeyNum(Point2D_t keyCoor)
{
uint8_t ret = 0;
if ((keyCoor.x < 0) || (keyCoor.y < 0)) {
return 0;
}
ret = (keyCoor.y * 14) + (keyCoor.x + 1);
return ret;
}
void Keyboard::updateKeyList()
{
_key_list_buffer.clear();
Point2D_t coor;
uint8_t input_value = 0;
for (int i = 0; i < 8; i++) {
_set_output(output_list, i);
input_value = _get_input(input_list);
/* If key pressed */
if (input_value) {
/* Get X */
for (int j = 0; j < 7; j++) {
if (input_value & (0x01 << j)) {
coor.x = (i > 3) ? X_map_chart[j].x_1 : X_map_chart[j].x_2;
/* Get Y */
coor.y = (i > 3) ? (i - 4) : i;
// printf("%d,%d\t", coor.x, coor.y);
/* Keep the same as picture */
coor.y = -coor.y;
coor.y = coor.y + 3;
_key_list_buffer.push_back(coor);
}
}
}
}
}
bool Keyboard::isKeyPressing(int keyNum)
{
if (_key_list_buffer.size())
{
for (const auto& i : _key_list_buffer)
{
if (getKeyNum(i) == keyNum)
return true;
}
}
return false;
}
#include <cstring>
void Keyboard::updateKeysState()
{
_keys_state_buffer.reset();
_key_values_without_special_keys.clear();
// Get special keys
for (auto& i : _key_list_buffer)
{
if (strcmp(getKeyValue(i).value_first, "tab") == 0)
{
_keys_state_buffer.tab = true;
continue;
}
if (strcmp(getKeyValue(i).value_first, "fn") == 0)
{
_keys_state_buffer.fn = true;
continue;
}
if (strcmp(getKeyValue(i).value_first, "shift") == 0)
{
_keys_state_buffer.shift = true;
continue;
}
if (strcmp(getKeyValue(i).value_first, "ctrl") == 0)
{
_keys_state_buffer.ctrl = true;
continue;
}
if (strcmp(getKeyValue(i).value_first, "opt") == 0)
{
_keys_state_buffer.opt = true;
continue;
}
if (strcmp(getKeyValue(i).value_first, "alt") == 0)
{
_keys_state_buffer.alt = true;
continue;
}
if (strcmp(getKeyValue(i).value_first, "del") == 0)
{
_keys_state_buffer.del = true;
_keys_state_buffer.hidKey.push_back(KEY_BACKSPACE);
continue;
}
if (strcmp(getKeyValue(i).value_first, "enter") == 0)
{
_keys_state_buffer.enter = true;
_keys_state_buffer.hidKey.push_back(KEY_ENTER);
continue;
}
if (strcmp(getKeyValue(i).value_first, "space") == 0)
{
_keys_state_buffer.space = true;
_keys_state_buffer.hidKey.push_back(KEY_SPACE);
continue;
}
_key_values_without_special_keys.push_back(i);
}
// Deal what left
for (auto& i : _key_values_without_special_keys)
{
if (_keys_state_buffer.ctrl || _keys_state_buffer.shift || _is_caps_locked)
{
_keys_state_buffer.values.push_back(*getKeyValue(i).value_second);
_keys_state_buffer.hidKey.push_back(getKeyValue(i).value_num_second);
}
else
{
_keys_state_buffer.values.push_back(*getKeyValue(i).value_first);
_keys_state_buffer.hidKey.push_back(getKeyValue(i).value_num_first);
}
}
}
bool Keyboard::isChanged()
{
if (_last_key_size != _key_list_buffer.size())
{
_last_key_size = _key_list_buffer.size();
return true;
}
return false;
}
@@ -1,192 +0,0 @@
/**
* @file keyboard.h
* @author Forairaaaaa
* @brief
* @version 0.1
* @date 2023-09-22
*
* @copyright Copyright (c) 2023
*
*/
#pragma once
#include <iostream>
#include <vector>
#include "keymap.h"
namespace KEYBOARD
{
struct Chart_t
{
uint8_t value;
uint8_t x_1;
uint8_t x_2;
};
struct Point2D_t
{
int x;
int y;
};
const std::vector<int> output_list = {8, 9, 11};
const std::vector<int> input_list = {13, 15, 3, 4, 5, 6, 7};
// const std::vector<int> input_list = {1, 2, 3, 4, 5, 6, 7};
const Chart_t X_map_chart[7] =
{
{1, 0, 1},
{2, 2, 3},
{4, 4, 5},
{8, 6, 7},
{16, 8, 9},
{32, 10, 11},
{64, 12, 13}
};
struct KeyValue_t
{
const char* value_first;
const int value_num_first;
const char* value_second;
const int value_num_second;
};
const KeyValue_t _key_value_map[4][14] = {
{{"`", KEY_GRAVE, "~", KEY_GRAVE},
{"1", KEY_1, "!", KEY_1},
{"2", KEY_2, "@", KEY_2},
{"3", KEY_3, "#", KEY_3},
{"4", KEY_4, "$", KEY_4},
{"5", KEY_5, "%", KEY_5},
{"6", KEY_6, "^", KEY_6},
{"7", KEY_7, "&", KEY_7},
{"8", KEY_8, "*", KEY_KPASTERISK},
{"9", KEY_9, "(", KEY_KPLEFTPAREN},
{"0", KEY_0, ")", KEY_KPRIGHTPAREN},
{"-", KEY_MINUS, "_", KEY_KPMINUS},
{"=", KEY_EQUAL, "+", KEY_KPPLUS},
{"del", KEY_BACKSPACE, "del", KEY_BACKSPACE}},
{{"tab", KEY_TAB, "tab", KEY_TAB},
{"q", KEY_Q, "Q", KEY_Q},
{"w", KEY_W, "W", KEY_W},
{"e", KEY_E, "E", KEY_E},
{"r", KEY_R, "R", KEY_R},
{"t", KEY_T, "T", KEY_T},
{"y", KEY_Y, "Y", KEY_Y},
{"u", KEY_U, "U", KEY_U},
{"i", KEY_I, "I", KEY_I},
{"o", KEY_O, "O", KEY_O},
{"p", KEY_P, "P", KEY_P},
{"[", KEY_LEFTBRACE, "{", KEY_LEFTBRACE},
{"]", KEY_RIGHTBRACE, "}", KEY_RIGHTBRACE},
{"\\", KEY_BACKSLASH, "|", KEY_BACKSLASH}},
{{"fn", 0, "fn", 0},
{"shift", 0, "shift", 0},
{"a", KEY_A, "A", KEY_A},
{"s", KEY_S, "S", KEY_S},
{"d", KEY_D, "D", KEY_D},
{"f", KEY_F, "F", KEY_F},
{"g", KEY_G, "G", KEY_G},
{"h", KEY_H, "H", KEY_H},
{"j", KEY_J, "J", KEY_J},
{"k", KEY_K, "K", KEY_K},
{"l", KEY_L, "L", KEY_L},
{";", KEY_SEMICOLON, ":", KEY_SEMICOLON},
{"'", KEY_APOSTROPHE, "\"", KEY_APOSTROPHE},
{"enter", KEY_ENTER, "enter", KEY_ENTER}},
{{"ctrl", KEY_LEFTCTRL, "ctrl", KEY_LEFTCTRL},
{"opt", 0, "opt", 0},
{"alt", KEY_LEFTALT, "alt", KEY_LEFTALT},
{"z", KEY_Z, "Z", KEY_Z},
{"x", KEY_X, "X", KEY_X},
{"c", KEY_C, "C", KEY_C},
{"v", KEY_V, "V", KEY_V},
{"b", KEY_B, "B", KEY_B},
{"n", KEY_N, "N", KEY_N},
{"m", KEY_M, "M", KEY_M},
{",", KEY_COMMA, "<", KEY_COMMA},
{".", KEY_DOT, ">", KEY_DOT},
{"/", KEY_KPSLASH, "?", KEY_KPSLASH},
{"space", KEY_SPACE, "space", KEY_SPACE}}};
class Keyboard
{
public:
struct KeysState
{
bool tab = false;
bool fn = false;
bool shift = false;
bool ctrl = false;
bool opt = false;
bool alt = false;
bool del = false;
bool enter = false;
bool space = false;
std::vector<char> values;
std::vector<int> hidKey;
void reset()
{
tab = false;
fn = false;
shift = false;
ctrl = false;
opt = false;
alt = false;
del = false;
enter = false;
space = false;
values.clear();
hidKey.clear();
}
};
private:
std::vector<Point2D_t> _key_list_buffer;
std::vector<Point2D_t> _key_values_without_special_keys;
KeysState _keys_state_buffer;
bool _is_caps_locked;
uint8_t _last_key_size;
void _set_output(const std::vector<int>& pinList, uint8_t output);
uint8_t _get_input(const std::vector<int>& pinList);
public:
Keyboard() :
_is_caps_locked(false),
_last_key_size(0)
{}
void init();
Point2D_t getKey();
uint8_t getKeyNum(Point2D_t keyCoor);
void updateKeyList();
inline std::vector<KEYBOARD::Point2D_t>& keyList() { return _key_list_buffer; }
inline KeyValue_t getKeyValue(const Point2D_t& keyCoor) { return _key_value_map[keyCoor.y][keyCoor.x]; }
bool isKeyPressing(int keyNum);
void updateKeysState();
inline KeysState& keysState() { return _keys_state_buffer; }
inline bool capslocked(void) { return _is_caps_locked; }
inline void setCapsLocked(bool isLocked) { _is_caps_locked = isLocked; }
bool isChanged();
inline uint8_t isPressed() { return _key_list_buffer.size(); }
};
}
@@ -1,366 +0,0 @@
/**
* This file is part of esp32s3-keyboard.
*
* Copyright (C) 2020-2021 Yuquan He <heyuquan20b at ict dot ac dot cn>
* (Institute of Computing Technology, Chinese Academy of Sciences)
*
* esp32s3-keyboard is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* esp32s3-keyboard is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with esp32s3-keyboard. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* USB HID format: 8 bytes
* Byte 0: Keyboard modifier bits (SHIFT, ALT, CTRL etc)
* Byte 1: reserved
* Byte 2-7: Up to six keyboard usage indexes representing the keys that are
* currently "pressed". Order is not important, a key is either
* pressed (present in the buffer) or not pressed.
*/
#ifndef MY_KEYMAP_H
#define MY_KEYMAP_H
#include <stdint.h>
/**
* Modifier masks - used for the first byte in the HID report.
* NOTE: The second byte in the report is reserved, 0x00
*/
#define KEY_MOD_LCTRL 0x01
#define KEY_MOD_LSHIFT 0x02
#define KEY_MOD_LALT 0x04
#define KEY_MOD_LMETA 0x08
#define KEY_MOD_RCTRL 0x10
#define KEY_MOD_RSHIFT 0x20
#define KEY_MOD_RALT 0x40
#define KEY_MOD_RMETA 0x80
/**
* Scan codes - last N slots in the HID report (usually 6).
* 0x00 if no key pressed.
*
* If more than N keys are pressed, the HID reports
* KEY_ERR_OVF in all slots to indicate this condition.
*/
#define KEY_NONE 0x00 // No key pressed
#define KEY_ERR_OVF \
0x01 // Keyboard Error Roll Over - used for all slots if too many keys are
// pressed ("Phantom key")
// 0x02 // Keyboard POST Fail
// 0x03 // Keyboard Error Undefined
#define KEY_A 0x04 // Keyboard a and A
#define KEY_B 0x05 // Keyboard b and B
#define KEY_C 0x06 // Keyboard c and C
#define KEY_D 0x07 // Keyboard d and D
#define KEY_E 0x08 // Keyboard e and E
#define KEY_F 0x09 // Keyboard f and F
#define KEY_G 0x0a // Keyboard g and G
#define KEY_H 0x0b // Keyboard h and H
#define KEY_I 0x0c // Keyboard i and I
#define KEY_J 0x0d // Keyboard j and J
#define KEY_K 0x0e // Keyboard k and K
#define KEY_L 0x0f // Keyboard l and L
#define KEY_M 0x10 // Keyboard m and M
#define KEY_N 0x11 // Keyboard n and N
#define KEY_O 0x12 // Keyboard o and O
#define KEY_P 0x13 // Keyboard p and P
#define KEY_Q 0x14 // Keyboard q and Q
#define KEY_R 0x15 // Keyboard r and R
#define KEY_S 0x16 // Keyboard s and S
#define KEY_T 0x17 // Keyboard t and T
#define KEY_U 0x18 // Keyboard u and U
#define KEY_V 0x19 // Keyboard v and V
#define KEY_W 0x1a // Keyboard w and W
#define KEY_X 0x1b // Keyboard x and X
#define KEY_Y 0x1c // Keyboard y and Y
#define KEY_Z 0x1d // Keyboard z and Z
#define KEY_1 0x1e // Keyboard 1 and !
#define KEY_2 0x1f // Keyboard 2 and @
#define KEY_3 0x20 // Keyboard 3 and #
#define KEY_4 0x21 // Keyboard 4 and $
#define KEY_5 0x22 // Keyboard 5 and %
#define KEY_6 0x23 // Keyboard 6 and ^
#define KEY_7 0x24 // Keyboard 7 and &
#define KEY_8 0x25 // Keyboard 8 and *
#define KEY_9 0x26 // Keyboard 9 and (
#define KEY_0 0x27 // Keyboard 0 and )
#define KEY_ENTER 0x28 // Keyboard Return (ENTER)
#define KEY_ESC 0x29 // Keyboard ESCAPE
#define KEY_BACKSPACE 0x2a // Keyboard DELETE (Backspace)
#define KEY_TAB 0x2b // Keyboard Tab
#define KEY_SPACE 0x2c // Keyboard Spacebar
#define KEY_MINUS 0x2d // Keyboard - and _
#define KEY_EQUAL 0x2e // Keyboard = and +
#define KEY_LEFTBRACE 0x2f // Keyboard [ and {
#define KEY_RIGHTBRACE 0x30 // Keyboard ] and }
#define KEY_BACKSLASH 0x31 // Keyboard \ and |
#define KEY_HASHTILDE 0x32 // Keyboard Non-US # and ~
#define KEY_SEMICOLON 0x33 // Keyboard ; and :
#define KEY_APOSTROPHE 0x34 // Keyboard ' and "
#define KEY_GRAVE 0x35 // Keyboard ` and ~
#define KEY_COMMA 0x36 // Keyboard , and <
#define KEY_DOT 0x37 // Keyboard . and >
#define KEY_SLASH 0x38 // Keyboard / and ?
#define KEY_CAPSLOCK 0x39 // Keyboard Caps Lock
#define KEY_F1 0x3a // Keyboard F1
#define KEY_F2 0x3b // Keyboard F2
#define KEY_F3 0x3c // Keyboard F3
#define KEY_F4 0x3d // Keyboard F4
#define KEY_F5 0x3e // Keyboard F5
#define KEY_F6 0x3f // Keyboard F6
#define KEY_F7 0x40 // Keyboard F7
#define KEY_F8 0x41 // Keyboard F8
#define KEY_F9 0x42 // Keyboard F9
#define KEY_F10 0x43 // Keyboard F10
#define KEY_F11 0x44 // Keyboard F11
#define KEY_F12 0x45 // Keyboard F12
#define KEY_PRTSC 0x46 // Keyboard Print Screen
#define KEY_SCROLLLOCK 0x47 // Keyboard Scroll Lock
#define KEY_PAUSE 0x48 // Keyboard Pause
#define KEY_INSERT 0x49 // Keyboard Insert
#define KEY_HOME 0x4a // Keyboard Home
#define KEY_PAGEUP 0x4b // Keyboard Page Up
// #define KEY_DELETE 0x4c // Keyboard Delete Forward
#define KEY_DELETE 0xD4 // Keyboard Delete Forward
#define KEY_END 0x4d // Keyboard End
#define KEY_PAGEDOWN 0x4e // Keyboard Page Down
#define KEY_RIGHT 0x4f // Keyboard Right Arrow
#define KEY_LEFT 0x50 // Keyboard Left Arrow
#define KEY_DOWN 0x51 // Keyboard Down Arrow
#define KEY_UP 0x52 // Keyboard Up Arrow
#define KEY_NUMLOCK 0x53 // Keyboard Num Lock and Clear
#define KEY_KPSLASH 0x54 // Keypad /
#define KEY_KPASTERISK 0x55 // Keypad *
#define KEY_KPMINUS 0x56 // Keypad -
#define KEY_KPPLUS 0x57 // Keypad +
#define KEY_KPENTER 0x58 // Keypad ENTER
#define KEY_KP1 0x59 // Keypad 1 and End
#define KEY_KP2 0x5a // Keypad 2 and Down Arrow
#define KEY_KP3 0x5b // Keypad 3 and PageDn
#define KEY_KP4 0x5c // Keypad 4 and Left Arrow
#define KEY_KP5 0x5d // Keypad 5
#define KEY_KP6 0x5e // Keypad 6 and Right Arrow
#define KEY_KP7 0x5f // Keypad 7 and Home
#define KEY_KP8 0x60 // Keypad 8 and Up Arrow
#define KEY_KP9 0x61 // Keypad 9 and Page Up
#define KEY_KP0 0x62 // Keypad 0 and Insert
#define KEY_KPDOT 0x63 // Keypad . and Delete
#define KEY_102ND 0x64 // Keyboard Non-US \ and |
#define KEY_COMPOSE 0x65 // Keyboard Application
#define KEY_POWER 0x66 // Keyboard Power
#define KEY_KPEQUAL 0x67 // Keypad =
#define KEY_F13 0x68 // Keyboard F13
#define KEY_F14 0x69 // Keyboard F14
#define KEY_F15 0x6a // Keyboard F15
#define KEY_F16 0x6b // Keyboard F16
#define KEY_F17 0x6c // Keyboard F17
#define KEY_F18 0x6d // Keyboard F18
#define KEY_F19 0x6e // Keyboard F19
#define KEY_F20 0x6f // Keyboard F20
#define KEY_F21 0x70 // Keyboard F21
#define KEY_F22 0x71 // Keyboard F22
#define KEY_F23 0x72 // Keyboard F23
#define KEY_F24 0x73 // Keyboard F24
#define KEY_OPEN 0x74 // Keyboard Execute
#define KEY_HELP 0x75 // Keyboard Help
#define KEY_PROPS 0x76 // Keyboard Menu
#define KEY_FRONT 0x77 // Keyboard Select
#define KEY_STOP 0x78 // Keyboard Stop
#define KEY_AGAIN 0x79 // Keyboard Again
#define KEY_UNDO 0x7a // Keyboard Undo
#define KEY_CUT 0x7b // Keyboard Cut
#define KEY_COPY 0x7c // Keyboard Copy
#define KEY_PASTE 0x7d // Keyboard Paste
#define KEY_FIND 0x7e // Keyboard Find
#define KEY_MUTE 0x7f // Keyboard Mute
#define KEY_VOLUMEUP 0x80 // Keyboard Volume Up
#define KEY_VOLUMEDOWN 0x81 // Keyboard Volume Down
// 0x82 Keyboard Locking Caps Lock
// 0x83 Keyboard Locking Num Lock
// 0x84 Keyboard Locking Scroll Lock
#define KEY_KPCOMMA 0x85 // Keypad Comma
// 0x86 Keypad Equal Sign
#define KEY_RO 0x87 // Keyboard International1
#define KEY_KATAKANAHIRAGANA 0x88 // Keyboard International2
#define KEY_YEN 0x89 // Keyboard International3
#define KEY_HENKAN 0x8a // Keyboard International4
#define KEY_MUHENKAN 0x8b // Keyboard International5
#define KEY_KPJPCOMMA 0x8c // Keyboard International6
// 0x8d Keyboard International7
// 0x8e Keyboard International8
// 0x8f Keyboard International9
#define KEY_HANGEUL 0x90 // Keyboard LANG1
#define KEY_HANJA 0x91 // Keyboard LANG2
#define KEY_KATAKANA 0x92 // Keyboard LANG3
#define KEY_HIRAGANA 0x93 // Keyboard LANG4
#define KEY_ZENKAKUHANKAKU 0x94 // Keyboard LANG5
// 0x95 Keyboard LANG6
// 0x96 Keyboard LANG7
// 0x97 Keyboard LANG8
// 0x98 Keyboard LANG9
// 0x99 Keyboard Alternate Erase
// 0x9a Keyboard SysReq/Attention
// 0x9b Keyboard Cancel
// 0x9c Keyboard Clear
// 0x9d Keyboard Prior
// 0x9e Keyboard Return
// 0x9f Keyboard Separator
// 0xa0 Keyboard Out
// 0xa1 Keyboard Oper
// 0xa2 Keyboard Clear/Again
// 0xa3 Keyboard CrSel/Props
// 0xa4 Keyboard ExSel
// 0xb0 Keypad 00
// 0xb1 Keypad 000
// 0xb2 Thousands Separator
// 0xb3 Decimal Separator
// 0xb4 Currency Unit
// 0xb5 Currency Sub-unit
#define KEY_KPLEFTPAREN 0xb6 // Keypad (
#define KEY_KPRIGHTPAREN 0xb7 // Keypad )
// 0xb8 Keypad {
// 0xb9 Keypad }
// 0xba Keypad Tab
#define KEY_KPBACKSPACE 0xbb // Keypad Backspace
// 0xbc Keypad A
// 0xbd Keypad B
// 0xbe Keypad C
// 0xbf Keypad D
// 0xc0 Keypad E
// 0xc1 Keypad F
// 0xc2 Keypad XOR
// 0xc3 Keypad ^
// 0xc4 Keypad %
// 0xc5 Keypad <
// 0xc6 Keypad >
// 0xc7 Keypad &
// 0xc8 Keypad &&
// 0xc9 Keypad |
// 0xca Keypad ||
// 0xcb Keypad :
// 0xcc Keypad #
// 0xcd Keypad Space
// 0xce Keypad @
// 0xcf Keypad !
// 0xd0 Keypad Memory Store
// 0xd1 Keypad Memory Recall
// 0xd2 Keypad Memory Clear
// 0xd3 Keypad Memory Add
// 0xd4 Keypad Memory Subtract
// 0xd5 Keypad Memory Multiply
// 0xd6 Keypad Memory Divide
// 0xd7 Keypad +/-
// 0xd8 Keypad Clear
// 0xd9 Keypad Clear Entry
// 0xda Keypad Binary
// 0xdb Keypad Octal
// 0xdc Keypad Decimal
// 0xdd Keypad Hexadecimal
#define KEY_LEFTCTRL 0xe0 // Keyboard Left Control
#define KEY_LEFTSHIFT 0xe1 // Keyboard Left Shift
#define KEY_LEFTALT 0xe2 // Keyboard Left Alt
#define KEY_LEFTMETA 0xe3 // Keyboard Left GUI
#define KEY_RIGHTCTRL 0xe4 // Keyboard Right Control
#define KEY_RIGHTSHIFT 0xe5 // Keyboard Right Shift
#define KEY_RIGHTALT 0xe6 // Keyboard Right Alt
#define KEY_RIGHTMETA 0xe7 // Keyboard Right GUI
#define KEY_MEDIA_PLAYPAUSE 0xe8
#define KEY_MEDIA_STOPCD 0xe9
#define KEY_MEDIA_PREVIOUSSONG 0xea
#define KEY_MEDIA_NEXTSONG 0xeb
#define KEY_MEDIA_EJECTCD 0xec
#define KEY_MEDIA_VOLUMEUP 0xed
#define KEY_MEDIA_VOLUMEDOWN 0xee
#define KEY_MEDIA_MUTE 0xef
#define KEY_MEDIA_WWW 0xf0
#define KEY_MEDIA_BACK 0xf1
#define KEY_MEDIA_FORWARD 0xf2
#define KEY_MEDIA_STOP 0xf3
#define KEY_MEDIA_FIND 0xf4
#define KEY_MEDIA_SCROLLUP 0xf5
#define KEY_MEDIA_SCROLLDOWN 0xf6
#define KEY_MEDIA_EDIT 0xf7
#define KEY_MEDIA_SLEEP 0xf8
#define KEY_MEDIA_COFFEE 0xf9
#define KEY_MEDIA_REFRESH 0xfa
#define KEY_MEDIA_CALC 0xfb
enum {
// Generic Control
KEY_CONSUMER_CONTROL = 0x0001,
// Power Control
KEY_CONSUMER_POWER = 0x0030,
KEY_CONSUMER_RESET = 0x0031,
KEY_CONSUMER_SLEEP = 0x0032,
// Screen Brightness
KEY_CONSUMER_BRIGHTNESS_INCREMENT = 0x006F,
KEY_CONSUMER_BRIGHTNESS_DECREMENT = 0x0070,
// These HID usages operate only on mobile systems (battery powered) and
// require Windows 8 (build 8302 or greater).
KEY_CONSUMER_WIRELESS_RADIO_CONTROLS = 0x000C,
KEY_CONSUMER_WIRELESS_RADIO_BUTTONS = 0x00C6,
KEY_CONSUMER_WIRELESS_RADIO_LED = 0x00C7,
KEY_CONSUMER_WIRELESS_RADIO_SLIDER_SWITCH = 0x00C8,
// Media Control
KEY_CONSUMER_PLAY_PAUSE = 0x00CD,
KEY_CONSUMER_SCAN_NEXT = 0x00B5,
KEY_CONSUMER_SCAN_PREVIOUS = 0x00B6,
KEY_CONSUMER_STOP = 0x00B7,
KEY_CONSUMER_VOLUME = 0x00E0,
KEY_CONSUMER_MUTE = 0x00E2,
KEY_CONSUMER_BASS = 0x00E3,
KEY_CONSUMER_TREBLE = 0x00E4,
KEY_CONSUMER_BASS_BOOST = 0x00E5,
KEY_CONSUMER_VOLUME_INCREMENT = 0x00E9,
KEY_CONSUMER_VOLUME_DECREMENT = 0x00EA,
KEY_CONSUMER_BASS_INCREMENT = 0x0152,
KEY_CONSUMER_BASS_DECREMENT = 0x0153,
KEY_CONSUMER_TREBLE_INCREMENT = 0x0154,
KEY_CONSUMER_TREBLE_DECREMENT = 0x0155,
// Application Launcher
KEY_CONSUMER_AL_CONSUMER_CONTROL_CONFIGURATION = 0x0183,
KEY_CONSUMER_AL_EMAIL_READER = 0x018A,
KEY_CONSUMER_AL_CALCULATOR = 0x0192,
KEY_CONSUMER_AL_LOCAL_BROWSER = 0x0194,
// Browser/Explorer Specific
KEY_CONSUMER_AC_SEARCH = 0x0221,
KEY_CONSUMER_AC_HOME = 0x0223,
KEY_CONSUMER_AC_BACK = 0x0224,
KEY_CONSUMER_AC_FORWARD = 0x0225,
KEY_CONSUMER_AC_STOP = 0x0226,
KEY_CONSUMER_AC_REFRESH = 0x0227,
KEY_CONSUMER_AC_BOOKMARKS = 0x022A,
// Mouse Horizontal scroll
KEY_CONSUMER_AC_PAN = 0x0238,
};
#endif
@@ -10,6 +10,8 @@ hardware.tinyUsb=true
hardware.esptoolFlashFreq=120M
hardware.bluetooth=true
dependencies.useDeprecatedHal=false
storage.userDataLocation=SD
display.size=1.14"
@@ -1,3 +1,5 @@
dependencies:
- Platforms/platform-esp32
- Drivers/st7789-module
- Drivers/m5stack-module
dts: m5stack,cardputer.dts
@@ -1,6 +1,8 @@
/dts-v1/;
#include <tactility/bindings/root.h>
#include <tactility/bindings/battery_sense.h>
#include <tactility/bindings/esp32_adc_oneshot.h>
#include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h>
@@ -10,7 +12,10 @@
#include <tactility/bindings/esp32_spi.h>
#include <tactility/bindings/esp32_uart.h>
#include <tactility/bindings/esp32_sdspi.h>
#include <tactility/bindings/display_placeholder.h>
#include <tactility/bindings/esp32_ledc_backlight.h>
#include <bindings/st7789.h>
#include <bindings/cardputer_keyboard.h>
// Reference: https://docs.m5stack.com/en/core/Cardputer
/ {
@@ -32,6 +37,21 @@
gpio-count = <49>;
};
adc0 {
compatible = "espressif,esp32-adc-oneshot";
unit-id = <ADC_UNIT_1>;
clk-src = <ADC_RTC_CLK_SRC_DEFAULT>;
// GPIO10 (ADC1_CHANNEL_9 on ESP32-S3): battery voltage sense, behind a 1:2 divider.
channels = <ADC_CHANNEL_9 ADC_ATTEN_DB_12 ADC_BITWIDTH_DEFAULT>;
};
battery-sense {
compatible = "battery-sense";
io-channel = <&adc0 0>;
reference-voltage-mv = <3300>;
multiplier = <2000>;
};
port_a: grove0 {
compatible = "espressif,esp32-grove";
defaultMode = <GROVE_MODE_I2C>;
@@ -42,6 +62,15 @@
i2cClockFrequency = <400000>;
};
display_backlight {
compatible = "espressif,esp32-ledc-backlight";
// Off by default so display power-on won't show the screen from before the last power loss.
// The display backlight is turned on during the boot process.
status = "disabled";
pin-backlight = <&gpio0 38 GPIO_FLAG_NONE>;
frequency-hz = <512>;
};
spi0 {
compatible = "espressif,esp32-spi";
host = <SPI2_HOST>;
@@ -49,8 +78,20 @@
pin-mosi = <&gpio0 35 GPIO_FLAG_NONE>;
pin-sclk = <&gpio0 36 GPIO_FLAG_NONE>;
display {
compatible = "display-placeholder";
display@0 {
compatible = "sitronix,st7789";
horizontal-resolution = <240>;
vertical-resolution = <135>;
// Should be 52 according to https://github.com/m5stack/M5GFX/blob/master/src/M5GFX.cpp but this leaves a gap at the bottom
gap-x = <53>;
gap-y = <40>;
swap-xy;
mirror-x;
invert-color;
pixel-clock-hz = <62500000>;
pin-dc = <&gpio0 34 GPIO_FLAG_NONE>;
pin-reset = <&gpio0 33 GPIO_FLAG_NONE>;
backlight = <&display_backlight>;
};
};
@@ -68,6 +109,20 @@
};
};
keyboard {
compatible = "m5stack,cardputer-keyboard";
pins-output = <&gpio0 8 GPIO_FLAG_NONE>,
<&gpio0 9 GPIO_FLAG_NONE>,
<&gpio0 11 GPIO_FLAG_NONE>;
pins-input = <&gpio0 13 GPIO_FLAG_NONE>,
<&gpio0 15 GPIO_FLAG_NONE>,
<&gpio0 3 GPIO_FLAG_NONE>,
<&gpio0 4 GPIO_FLAG_NONE>,
<&gpio0 5 GPIO_FLAG_NONE>,
<&gpio0 6 GPIO_FLAG_NONE>,
<&gpio0 7 GPIO_FLAG_NONE>;
};
// Speaker and microphone
// TODO: Microphone I2C init code from https://github.com/m5stack/M5Unified/blob/a6256725481f1bc366655fa48cf03b6095e30ad1/src/M5Unified.cpp#L936
i2s0 {
@@ -12,7 +12,7 @@ static error_t stop() {
return ERROR_NONE;
}
struct Module m5stack_cardputer_module = {
Module m5stack_cardputer_module = {
.name = "m5stack-cardputer",
.start = start,
.stop = stop,
+3 -3
View File
@@ -1,7 +1,7 @@
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
file(GLOB_RECURSE SOURCE_FILES source/*.c*)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source"
REQUIRES Tactility esp_lvgl_port esp_lcd ST7789 PwmBacklight ButtonControl
INCLUDE_DIRS "source"
REQUIRES TactilityKernel driver
)
@@ -1,31 +0,0 @@
#include "devices/Display.h"
#include <Tactility/hal/Configuration.h>
#include <ButtonControl.h>
#include <PwmBacklight.h>
using namespace tt::hal;
bool initBoot() {
// CH552 applies 4 V to GPIO 0, which reduces Wi-Fi sensitivity
// Setting output to high adds a bias of 3.3 V and suppresses over-voltage:
gpio::configure(0, gpio::Mode::Output, false, false);
gpio::setLevel(0, true);
// "Hold power" pin: must be set to high to keep the device powered on:
gpio::configure(4, gpio::Mode::Output, false, false);
gpio::setLevel(4, true);
return driver::pwmbacklight::init(GPIO_NUM_27, 512);
}
static DeviceVector createDevices() {
return {
ButtonControl::createTwoButtonControl(37, 39),
createDisplay()
};
}
extern const Configuration hardwareConfiguration = {
.initBoot = initBoot,
.createDevices = createDevices
};
@@ -1,32 +0,0 @@
#include "Display.h"
#include <PwmBacklight.h>
#include <St7789Display.h>
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
St7789Display::Configuration panel_configuration = {
.horizontalResolution = LCD_HORIZONTAL_RESOLUTION,
.verticalResolution = LCD_VERTICAL_RESOLUTION,
.gapX = 52,
.gapY = 40,
.swapXY = false,
.mirrorX = false,
.mirrorY = false,
.invertColor = true,
.bufferSize = LCD_BUFFER_SIZE,
.touch = nullptr,
.backlightDutyFunction = driver::pwmbacklight::setBacklightDuty,
.resetPin = LCD_PIN_RESET,
.lvglSwapBytes = false
};
auto spi_configuration = std::make_shared<St7789Display::SpiConfiguration>(St7789Display::SpiConfiguration {
.spiHostDevice = LCD_SPI_HOST,
.csPin = LCD_PIN_CS,
.dcPin = LCD_PIN_DC,
.pixelClockFrequency = 40'000'000,
.transactionQueueDepth = 10
});
return std::make_shared<St7789Display>(panel_configuration, spi_configuration);
}
@@ -1,18 +0,0 @@
#pragma once
#include "Tactility/hal/display/DisplayDevice.h"
#include <memory>
#include <driver/gpio.h>
#include <driver/spi_common.h>
constexpr auto LCD_SPI_HOST = SPI2_HOST;
constexpr auto LCD_PIN_CS = GPIO_NUM_5;
constexpr auto LCD_PIN_DC = GPIO_NUM_14;
constexpr auto LCD_PIN_RESET = GPIO_NUM_12;
constexpr auto LCD_HORIZONTAL_RESOLUTION = 135;
constexpr auto LCD_VERTICAL_RESOLUTION = 240;
constexpr auto LCD_BUFFER_HEIGHT = LCD_VERTICAL_RESOLUTION / 3;
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,23 +0,0 @@
#include <tactility/module.h>
extern "C" {
static error_t start() {
// Empty for now
return ERROR_NONE;
}
static error_t stop() {
// Empty for now
return ERROR_NONE;
}
struct Module m5stack_stickc_plus2_module = {
.name = "m5stack-stickc-plus2",
.start = start,
.stop = stop,
.symbols = nullptr,
.internal = nullptr
};
}
@@ -10,6 +10,8 @@ hardware.spiRamMode=QUAD
hardware.spiRamSpeed=80M
hardware.esptoolFlashFreq=80M
dependencies.useDeprecatedHal=false
storage.userDataLocation=Internal
display.size=1.14"
@@ -2,4 +2,6 @@ dependencies:
- Platforms/platform-esp32
- Drivers/mpu6886-module
- Drivers/bm8563-module
- Drivers/st7789-module
- Drivers/button-control-module
dts: m5stack,stickc-plus2.dts
@@ -7,7 +7,10 @@
#include <tactility/bindings/esp32_uart.h>
#include <bindings/mpu6886.h>
#include <bindings/bm8563.h>
#include <tactility/bindings/display_placeholder.h>
#include <tactility/bindings/esp32_ledc_backlight.h>
#include <bindings/st7789.h>
#include <bindings/button_control.h>
/ {
compatible = "root";
@@ -49,6 +52,15 @@
pin-scl = <&gpio0 33 GPIO_FLAG_NONE>;
};
display_backlight {
compatible = "espressif,esp32-ledc-backlight";
// Off by default so display power-on won't show the screen from before the last power loss.
// The display backlight is turned on during the boot process.
status = "disabled";
pin-backlight = <&gpio0 27 GPIO_FLAG_NONE>;
frequency-hz = <512>;
};
spi0 {
compatible = "espressif,esp32-spi";
host = <SPI2_HOST>;
@@ -56,11 +68,26 @@
pin-mosi = <&gpio0 15 GPIO_FLAG_NONE>;
pin-sclk = <&gpio0 13 GPIO_FLAG_NONE>;
display {
compatible = "display-placeholder";
display@0 {
compatible = "sitronix,st7789";
horizontal-resolution = <135>;
vertical-resolution = <240>;
gap-x = <52>;
gap-y = <40>;
invert-color;
pixel-clock-hz = <40000000>;
pin-dc = <&gpio0 14 GPIO_FLAG_NONE>;
pin-reset = <&gpio0 12 GPIO_FLAG_NONE>;
backlight = <&display_backlight>;
};
};
buttons {
compatible = "tactility,button-control";
pin-primary = <&gpio0 37 GPIO_FLAG_NONE>;
pin-secondary = <&gpio0 39 GPIO_FLAG_NONE>;
};
uart_grove: uart1 {
compatible = "espressif,esp32-uart";
status = "disabled";
@@ -0,0 +1,41 @@
#include <tactility/module.h>
#include <tactility/error.h>
#include <driver/gpio.h>
constexpr auto* TAG = "StickCPlus2";
extern "C" {
static bool init_power() {
// CH552 applies 4 V to GPIO 0, which reduces Wi-Fi sensitivity.
// Setting output to high adds a bias of 3.3 V and suppresses over-voltage:
gpio_set_direction(GPIO_NUM_0, GPIO_MODE_OUTPUT);
gpio_set_level(GPIO_NUM_0, 1);
// "Hold power" pin: must be set to high to keep the device powered on:
gpio_set_direction(GPIO_NUM_4, GPIO_MODE_OUTPUT);
gpio_set_level(GPIO_NUM_4, 1);
return true;
}
static error_t start() {
init_power();
return ERROR_NONE;
}
static error_t stop() {
// Empty for now
return ERROR_NONE;
}
Module m5stack_stickc_plus2_module = {
.name = "m5stack-stickc-plus2",
.start = start,
.stop = stop,
.symbols = nullptr,
.internal = nullptr
};
}
+1
View File
@@ -37,6 +37,7 @@
## Medium Priority
- Implement a LED kernel driver (single colour and RGB, plain GPIO and PWM)
- Make USB host driver disabled by default, so it doesn't consume memory
- Filtering for apps in App Hub:
- apps that only work on a specific device
@@ -3,7 +3,6 @@
#include <Tactility/hal/encoder/EncoderDevice.h>
#include <Tactility/hal/gpio/Gpio.h>
#include <Tactility/MessageQueue.h>
#include <Tactility/TactilityCore.h>
#include <Tactility/Thread.h>
#include <driver/gpio.h>
@@ -0,0 +1,11 @@
cmake_minimum_required(VERSION 3.20)
include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake")
file(GLOB_RECURSE SOURCE_FILES "source/*.c*")
tactility_add_module(button-control-module
SRCS ${SOURCE_FILES}
INCLUDE_DIRS include/
REQUIRES TactilityKernel platform-esp32 lvgl driver
)
@@ -0,0 +1,195 @@
Apache License
==============
_Version 2.0, January 2004_
_&lt;<http://www.apache.org/licenses/>&gt;_
### Terms and Conditions for use, reproduction, and distribution
#### 1. Definitions
“License” shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
“Licensor” shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
“Legal Entity” shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, “control” means **(i)** the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
outstanding shares, or **(iii)** beneficial ownership of such entity.
“You” (or “Your”) shall mean an individual or Legal Entity exercising
permissions granted by this License.
“Source” form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
“Object” form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
“Work” shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
“Derivative Works” shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
“Contribution” shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
“submitted” means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as “Not a Contribution.”
“Contributor” shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
#### 2. Grant of Copyright License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
#### 3. Grant of Patent License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
#### 4. Redistribution
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
* **(a)** You must give any other recipients of the Work or Derivative Works a copy of
this License; and
* **(b)** You must cause any modified files to carry prominent notices stating that You
changed the files; and
* **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
#### 5. Submission of Contributions
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
#### 6. Trademarks
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
#### 7. Disclaimer of Warranty
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
#### 8. Limitation of Liability
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
#### 9. Accepting Warranty or Additional Liability
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
_END OF TERMS AND CONDITIONS_
### APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets `[]` replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same “printed page” as the copyright notice for easier identification within
third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+30
View File
@@ -0,0 +1,30 @@
# Button Control
Kernel driver for navigating apps with 1 or 2 GPIO buttons. Kernel-driver counterpart to
`Drivers/ButtonControl`, exposing a `KEYBOARD_TYPE` device (`tactility/drivers/keyboard.h`)
instead of an LVGL encoder indev.
Devicetree binding: `tactility,button-control` (see `bindings/tactility,button-control.yaml`).
## Modes
- **One-button** (`pin-secondary` absent): short press = `LV_KEY_NEXT`, long press = `LV_KEY_ENTER`.
- **Two-button** (`pin-secondary` present): primary short = `LV_KEY_ENTER`, primary long =
`LV_KEY_ESC`, secondary short = `LV_KEY_NEXT`, secondary long = `LV_KEY_PREV`.
Short vs. long press is only decided when the button is released, by comparing the hold
duration against `long-press-ms` - not at the moment it's pressed.
## Example
```dts
buttons {
compatible = "tactility,button-control";
pin-primary = <&gpio0 37 GPIO_FLAG_NONE>;
pin-secondary = <&gpio0 39 GPIO_FLAG_NONE>;
};
```
## License
[Apache License Version 2.0](LICENSE-Apache-2.0.md)
@@ -0,0 +1,36 @@
description: >
GPIO button(s) that emulate UI navigation keys, for boards with only 1 or 2 physical
buttons instead of a full keyboard or touchscreen. Exposes a KEYBOARD_TYPE device: each
button's short/long press is translated to an LVGL navigation key, decided once the
button is released (so a long press is only recognized after release, matching how long
a button was actually held).
compatible: "tactility,button-control"
properties:
pin-primary:
type: phandles
required: true
description: >
Primary button GPIO pin. One-button mode (pin-secondary absent): short press = select
next, long press = press/enter. Two-button mode (pin-secondary present): short press =
press/enter, long press = close app.
pin-secondary:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: >
Optional secondary button GPIO pin. When present, enables two-button mode: short press
= select next, long press = select previous. When absent, the device runs in
one-button mode (see pin-primary).
active-low:
type: boolean
default: true
description: Whether a pressed button reads as a logic low level
debounce-ms:
type: int
default: 20
description: Minimum time between recognized level changes, for software debouncing
long-press-ms:
type: int
default: 500
description: Minimum hold duration (from press to release) recognized as a long press
@@ -0,0 +1,3 @@
dependencies:
- TactilityKernel
bindings: bindings
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/bindings/bindings.h>
#include <drivers/button_control.h>
DEFINE_DEVICETREE(button_control, struct ButtonControlConfig)
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/module.h>
#ifdef __cplusplus
extern "C" {
#endif
extern struct Module button_control_module;
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdbool.h>
#include <tactility/drivers/gpio.h>
struct ButtonControlConfig {
struct GpioPinSpec pin_primary;
// GPIO_PIN_SPEC_NONE selects one-button mode; see the binding description for the
// resulting short/long press key mapping in each mode.
struct GpioPinSpec pin_secondary;
bool active_low;
uint32_t debounce_ms;
uint32_t long_press_ms;
};
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,226 @@
// SPDX-License-Identifier: Apache-2.0
#include <drivers/button_control.h>
#include <button_control_module.h>
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/drivers/gpio_controller.h>
#include <tactility/drivers/keyboard.h>
#include <tactility/error.h>
#include <tactility/log.h>
#include <tactility/time.h>
#include <lvgl.h>
#include <cstdlib>
#define TAG "ButtonControl"
#define GET_CONFIG(device) (static_cast<const ButtonControlConfig*>((device)->config))
// Worst case: both buttons complete a short/long press gesture (press + release, 2 events each)
// within the same read_key() polling interval.
constexpr auto BUTTON_CONTROL_PENDING_CAPACITY = 4;
struct ButtonControlPendingEvent {
uint32_t key;
bool pressed;
};
struct ButtonControlButtonState {
GpioDescriptor* descriptor;
bool in_use;
bool debounced_pressed;
uint32_t press_start_time;
uint32_t last_change_time;
uint32_t short_press_key;
uint32_t long_press_key;
};
struct ButtonControlInternal {
ButtonControlButtonState primary;
ButtonControlButtonState secondary;
ButtonControlPendingEvent pending[BUTTON_CONTROL_PENDING_CAPACITY];
uint8_t pending_head;
uint8_t pending_count;
};
static void push_pending(ButtonControlInternal* internal, uint32_t key, bool pressed) {
if (internal->pending_count >= BUTTON_CONTROL_PENDING_CAPACITY) {
LOG_W(TAG, "Pending event queue full, dropping event");
return;
}
uint8_t tail = (internal->pending_head + internal->pending_count) % BUTTON_CONTROL_PENDING_CAPACITY;
internal->pending[tail] = { .key = key, .pressed = pressed };
internal->pending_count++;
}
static bool pop_pending(ButtonControlInternal* internal, ButtonControlPendingEvent* out_event) {
if (internal->pending_count == 0) {
return false;
}
*out_event = internal->pending[internal->pending_head];
internal->pending_head = (internal->pending_head + 1) % BUTTON_CONTROL_PENDING_CAPACITY;
internal->pending_count--;
return true;
}
// region Driver lifecycle
static error_t acquire_button(const GpioPinSpec& pin, uint32_t short_press_key, uint32_t long_press_key, ButtonControlButtonState* out_state) {
if (pin.gpio_controller == nullptr) {
*out_state = {};
return ERROR_NONE;
}
auto* descriptor = gpio_descriptor_acquire(pin.gpio_controller, pin.pin, GPIO_OWNER_GPIO);
if (descriptor == nullptr) {
LOG_E(TAG, "Failed to acquire GPIO descriptor");
return ERROR_RESOURCE;
}
if (gpio_descriptor_set_flags(descriptor, pin.flags | GPIO_FLAG_DIRECTION_INPUT) != ERROR_NONE) {
LOG_E(TAG, "Failed to configure GPIO as input");
gpio_descriptor_release(descriptor);
return ERROR_RESOURCE;
}
*out_state = {
.descriptor = descriptor,
.in_use = true,
.debounced_pressed = false,
.press_start_time = 0,
.last_change_time = 0,
.short_press_key = short_press_key,
.long_press_key = long_press_key,
};
return ERROR_NONE;
}
static error_t start(Device* device) {
const auto* config = GET_CONFIG(device);
auto* internal = static_cast<ButtonControlInternal*>(malloc(sizeof(ButtonControlInternal)));
if (internal == nullptr) {
return ERROR_OUT_OF_MEMORY;
}
*internal = {};
bool two_button_mode = config->pin_secondary.gpio_controller != nullptr;
error_t error = acquire_button(
config->pin_primary,
two_button_mode ? LV_KEY_ENTER : LV_KEY_NEXT,
two_button_mode ? LV_KEY_ESC : LV_KEY_ENTER,
&internal->primary
);
if (error != ERROR_NONE) {
free(internal);
return error;
}
error = acquire_button(config->pin_secondary, LV_KEY_NEXT, LV_KEY_PREV, &internal->secondary);
if (error != ERROR_NONE) {
if (internal->primary.in_use) {
gpio_descriptor_release(internal->primary.descriptor);
}
free(internal);
return error;
}
device_set_driver_data(device, internal);
return ERROR_NONE;
}
static error_t stop(Device* device) {
auto* internal = static_cast<ButtonControlInternal*>(device_get_driver_data(device));
if (internal->primary.in_use) {
gpio_descriptor_release(internal->primary.descriptor);
}
if (internal->secondary.in_use) {
gpio_descriptor_release(internal->secondary.descriptor);
}
free(internal);
return ERROR_NONE;
}
// endregion
// region KeyboardApi
// Long press is only distinguished from short press at release time, by how long the button
// was held - not at the moment it's first pressed. This matches physical button behavior:
// you can't know a press is "long" until it either ends or the threshold is reached.
static void poll_button(const ButtonControlConfig* config, ButtonControlInternal* internal, ButtonControlButtonState* state) {
if (!state->in_use) {
return;
}
bool high = false;
if (gpio_descriptor_get_level(state->descriptor, &high) != ERROR_NONE) {
return;
}
bool raw_pressed = config->active_low ? !high : high;
uint32_t now = get_millis();
if ((now - state->last_change_time) < config->debounce_ms) {
return;
}
if (raw_pressed == state->debounced_pressed) {
return;
}
state->last_change_time = now;
state->debounced_pressed = raw_pressed;
if (raw_pressed) {
state->press_start_time = now;
return;
}
// Release: decide short vs. long press by elapsed hold duration, then queue a synthetic
// key tap (press followed by release) for the LVGL key this gesture maps to.
uint32_t held_ms = now - state->press_start_time;
uint32_t key = held_ms < config->long_press_ms ? state->short_press_key : state->long_press_key;
push_pending(internal, key, true);
push_pending(internal, key, false);
}
static error_t button_control_read_key(Device* device, KeyboardKeyData* data) {
const auto* config = GET_CONFIG(device);
auto* internal = static_cast<ButtonControlInternal*>(device_get_driver_data(device));
poll_button(config, internal, &internal->primary);
poll_button(config, internal, &internal->secondary);
ButtonControlPendingEvent event;
if (pop_pending(internal, &event)) {
data->key = event.key;
data->pressed = event.pressed;
data->continue_reading = internal->pending_count > 0;
} else {
data->key = 0;
data->pressed = false;
data->continue_reading = false;
}
return ERROR_NONE;
}
// endregion
static constexpr KeyboardApi button_control_api = {
.read_key = button_control_read_key,
};
Driver button_control_driver = {
.name = "button_control",
.compatible = (const char*[]) { "tactility,button-control", nullptr },
.start_device = start,
.stop_device = stop,
.api = &button_control_api,
.device_type = &KEYBOARD_TYPE,
.owner = &button_control_module,
.internal = nullptr
};
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/check.h>
#include <tactility/driver.h>
#include <tactility/module.h>
extern "C" {
extern Driver button_control_driver;
static error_t start() {
/* We crash when construct fails, because if a single driver fails to construct,
* there is no guarantee that the previously constructed drivers can be destroyed */
check(driver_construct_add(&button_control_driver) == ERROR_NONE);
return ERROR_NONE;
}
static error_t stop() {
/* We crash when destruct fails, because if a single driver fails to destruct,
* there is no guarantee that the previously destroyed drivers can be recovered */
check(driver_remove_destruct(&button_control_driver) == ERROR_NONE);
return ERROR_NONE;
}
Module button_control_module = {
.name = "button-control",
.start = start,
.stop = stop,
.symbols = nullptr,
.internal = nullptr
};
} // extern "C"
+11
View File
@@ -0,0 +1,11 @@
cmake_minimum_required(VERSION 3.20)
include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake")
file(GLOB_RECURSE SOURCE_FILES "source/*.c*")
tactility_add_module(cst816s-module
SRCS ${SOURCE_FILES}
INCLUDE_DIRS include/
REQUIRES TactilityKernel platform-esp32 esp_lcd_touch_cst816s esp_lcd driver
)
@@ -0,0 +1,45 @@
description: Hynitron CST816S capacitive touch controller
include: ["i2c-device.yaml"]
compatible: "hynitron,cst816s"
bus: i2c
properties:
x-max:
type: int
required: true
description: Maximum X coordinate reported by the controller (typically the panel's horizontal resolution)
y-max:
type: int
required: true
description: Maximum Y coordinate reported by the controller (typically the panel's vertical resolution)
swap-xy:
type: boolean
default: false
description: Swap the X and Y axes
mirror-x:
type: boolean
default: false
description: Mirror the X axis
mirror-y:
type: boolean
default: false
description: Mirror the Y axis
pin-reset:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Reset GPIO pin
pin-interrupt:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Interrupt GPIO pin
reset-active-high:
type: boolean
default: false
description: Whether the reset pin is active high
interrupt-active-high:
type: boolean
default: false
description: Whether the interrupt pin is active high
+3
View File
@@ -0,0 +1,3 @@
dependencies:
- TactilityKernel
bindings: bindings
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/bindings/bindings.h>
#include <drivers/cst816s.h>
DEFINE_DEVICETREE(cst816s, struct Cst816sConfig)
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/module.h>
#ifdef __cplusplus
extern "C" {
#endif
extern struct Module cst816s_module;
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdbool.h>
#include <tactility/drivers/gpio.h>
struct Cst816sConfig {
// Devicetree address hint. Unused by the driver: the CST816S always sits at a fixed
// I2C address (0x15, see ESP_LCD_TOUCH_IO_I2C_CST816S_ADDRESS), unlike GT911's
// strapping-dependent address.
uint8_t address;
uint16_t x_max;
uint16_t y_max;
bool swap_xy;
bool mirror_x;
bool mirror_y;
struct GpioPinSpec pin_reset;
struct GpioPinSpec pin_interrupt;
bool reset_active_high;
bool interrupt_active_high;
};
#ifdef __cplusplus
}
#endif
+205
View File
@@ -0,0 +1,205 @@
// SPDX-License-Identifier: Apache-2.0
#include <drivers/cst816s.h>
#include <cst816s_module.h>
#include <tactility/check.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>
#include <tactility/drivers/pointer.h>
#include <tactility/log.h>
#include <esp_err.h>
#include <esp_lcd_io_i2c.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_touch.h>
#include <esp_lcd_touch_cst816s.h>
#include <cstdlib>
#define TAG "CST816S"
#define GET_CONFIG(device) (static_cast<const Cst816sConfig*>((device)->config))
struct Cst816sInternal {
esp_lcd_panel_io_handle_t io_handle;
esp_lcd_touch_handle_t touch_handle;
};
static inline gpio_num_t pin_or_nc(const struct GpioPinSpec& pin) {
return pin.gpio_controller == nullptr ? GPIO_NUM_NC : static_cast<gpio_num_t>(pin.pin);
}
// region Driver lifecycle
// CST816S always sits at a fixed I2C address, unlike GT911's strapping-dependent address,
// so no bus probing is needed here.
static esp_err_t create_io_handle(Device* parent, esp_lcd_panel_io_handle_t* out_handle) {
esp_lcd_panel_io_i2c_config_t io_config = ESP_LCD_TOUCH_IO_I2C_CST816S_CONFIG();
auto* parent_driver = device_get_driver(parent);
if (driver_is_compatible(parent_driver, "espressif,esp32-i2c")) {
auto port = static_cast<const Esp32I2cConfig*>(parent->config)->port;
return esp_lcd_new_panel_io_i2c_v1(port, &io_config, out_handle);
}
if (driver_is_compatible(parent_driver, "espressif,esp32-i2c-master")) {
auto bus = esp32_i2c_master_get_bus_handle(parent);
io_config.scl_speed_hz = esp32_i2c_master_get_clock_frequency(parent);
return esp_lcd_new_panel_io_i2c_v2(bus, &io_config, out_handle);
}
LOG_E(TAG, "Unsupported I2C driver");
return ESP_ERR_NOT_SUPPORTED;
}
static error_t start(Device* device) {
auto* parent = device_get_parent(device);
check(device_get_type(parent) == &I2C_CONTROLLER_TYPE);
const auto* config = GET_CONFIG(device);
auto* internal = static_cast<Cst816sInternal*>(malloc(sizeof(Cst816sInternal)));
if (internal == nullptr) {
return ERROR_OUT_OF_MEMORY;
}
esp_err_t ret = create_io_handle(parent, &internal->io_handle);
if (ret != ESP_OK) {
free(internal);
return ERROR_RESOURCE;
}
esp_lcd_touch_config_t touch_config = {
.x_max = config->x_max,
.y_max = config->y_max,
.rst_gpio_num = pin_or_nc(config->pin_reset),
.int_gpio_num = pin_or_nc(config->pin_interrupt),
.levels = {
.reset = config->reset_active_high ? 1u : 0u,
.interrupt = config->interrupt_active_high ? 1u : 0u,
},
.flags = {
.swap_xy = config->swap_xy ? 1u : 0u,
.mirror_x = config->mirror_x ? 1u : 0u,
.mirror_y = config->mirror_y ? 1u : 0u,
},
.process_coordinates = nullptr,
.interrupt_callback = nullptr,
.user_data = nullptr,
.driver_data = nullptr,
};
ret = esp_lcd_touch_new_i2c_cst816s(internal->io_handle, &touch_config, &internal->touch_handle);
if (ret != ESP_OK) {
LOG_E(TAG, "Failed to create touch handle: %s", esp_err_to_name(ret));
esp_lcd_panel_io_del(internal->io_handle);
free(internal);
return ERROR_RESOURCE;
}
device_set_driver_data(device, internal);
return ERROR_NONE;
}
static error_t stop(Device* device) {
auto* internal = static_cast<Cst816sInternal*>(device_get_driver_data(device));
bool ok = true;
// esp_lcd_touch_del() only releases the touch-side resources; the panel IO handle is owned
// separately and needs its own deletion.
if (esp_lcd_touch_del(internal->touch_handle) != ESP_OK) {
LOG_E(TAG, "Failed to delete touch handle");
ok = false;
}
if (esp_lcd_panel_io_del(internal->io_handle) != ESP_OK) {
LOG_E(TAG, "Failed to delete panel IO handle");
ok = false;
}
free(internal);
return ok ? ERROR_NONE : ERROR_RESOURCE;
}
// endregion
// region PointerApi
static error_t cst816s_enter_sleep(Device* device) {
auto* internal = static_cast<Cst816sInternal*>(device_get_driver_data(device));
return esp_lcd_touch_enter_sleep(internal->touch_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t cst816s_exit_sleep(Device* device) {
auto* internal = static_cast<Cst816sInternal*>(device_get_driver_data(device));
return esp_lcd_touch_exit_sleep(internal->touch_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t cst816s_read_data(Device* device, TickType_t timeout) {
(void)timeout; // esp_lcd_touch_read_data() has no timeout parameter
auto* internal = static_cast<Cst816sInternal*>(device_get_driver_data(device));
return esp_lcd_touch_read_data(internal->touch_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static bool cst816s_get_touched_points(Device* device, uint16_t* x, uint16_t* y, uint16_t* strength, uint8_t* point_count, uint8_t max_point_count) {
auto* internal = static_cast<Cst816sInternal*>(device_get_driver_data(device));
return esp_lcd_touch_get_coordinates(internal->touch_handle, x, y, strength, point_count, max_point_count);
}
static error_t cst816s_set_swap_xy(Device* device, bool swap) {
auto* internal = static_cast<Cst816sInternal*>(device_get_driver_data(device));
return esp_lcd_touch_set_swap_xy(internal->touch_handle, swap) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t cst816s_get_swap_xy(Device* device, bool* swap) {
auto* internal = static_cast<Cst816sInternal*>(device_get_driver_data(device));
return esp_lcd_touch_get_swap_xy(internal->touch_handle, swap) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t cst816s_set_mirror_x(Device* device, bool mirror) {
auto* internal = static_cast<Cst816sInternal*>(device_get_driver_data(device));
return esp_lcd_touch_set_mirror_x(internal->touch_handle, mirror) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t cst816s_get_mirror_x(Device* device, bool* mirror) {
auto* internal = static_cast<Cst816sInternal*>(device_get_driver_data(device));
return esp_lcd_touch_get_mirror_x(internal->touch_handle, mirror) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t cst816s_set_mirror_y(Device* device, bool mirror) {
auto* internal = static_cast<Cst816sInternal*>(device_get_driver_data(device));
return esp_lcd_touch_set_mirror_y(internal->touch_handle, mirror) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t cst816s_get_mirror_y(Device* device, bool* mirror) {
auto* internal = static_cast<Cst816sInternal*>(device_get_driver_data(device));
return esp_lcd_touch_get_mirror_y(internal->touch_handle, mirror) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
// endregion
static const PointerApi cst816s_pointer_api = {
.enter_sleep = cst816s_enter_sleep,
.exit_sleep = cst816s_exit_sleep,
.read_data = cst816s_read_data,
.get_touched_points = cst816s_get_touched_points,
.set_swap_xy = cst816s_set_swap_xy,
.get_swap_xy = cst816s_get_swap_xy,
.set_mirror_x = cst816s_set_mirror_x,
.get_mirror_x = cst816s_get_mirror_x,
.set_mirror_y = cst816s_set_mirror_y,
.get_mirror_y = cst816s_get_mirror_y,
};
Driver cst816s_driver = {
.name = "cst816s",
.compatible = (const char*[]) { "hynitron,cst816s", nullptr },
.start_device = start,
.stop_device = stop,
.api = &cst816s_pointer_api,
.device_type = &POINTER_TYPE,
.owner = &cst816s_module,
.internal = nullptr
};
+32
View File
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/check.h>
#include <tactility/driver.h>
#include <tactility/module.h>
extern "C" {
extern Driver cst816s_driver;
static error_t start() {
/* We crash when construct fails, because if a single driver fails to construct,
* there is no guarantee that the previously constructed drivers can be destroyed */
check(driver_construct_add(&cst816s_driver) == ERROR_NONE);
return ERROR_NONE;
}
static error_t stop() {
/* We crash when destruct fails, because if a single driver fails to destruct,
* there is no guarantee that the previously destroyed drivers can be recovered */
check(driver_remove_destruct(&cst816s_driver) == ERROR_NONE);
return ERROR_NONE;
}
Module cst816s_module = {
.name = "cst816s",
.start = start,
.stop = stop,
.symbols = nullptr,
.internal = nullptr
};
} // extern "C"
+195
View File
@@ -0,0 +1,195 @@
Apache License
==============
_Version 2.0, January 2004_
_&lt;<http://www.apache.org/licenses/>&gt;_
### Terms and Conditions for use, reproduction, and distribution
#### 1. Definitions
“License” shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
“Licensor” shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
“Legal Entity” shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, “control” means **(i)** the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
outstanding shares, or **(iii)** beneficial ownership of such entity.
“You” (or “Your”) shall mean an individual or Legal Entity exercising
permissions granted by this License.
“Source” form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
“Object” form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
“Work” shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
“Derivative Works” shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
“Contribution” shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
“submitted” means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as “Not a Contribution.”
“Contributor” shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
#### 2. Grant of Copyright License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
#### 3. Grant of Patent License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
#### 4. Redistribution
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
* **(a)** You must give any other recipients of the Work or Derivative Works a copy of
this License; and
* **(b)** You must cause any modified files to carry prominent notices stating that You
changed the files; and
* **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
#### 5. Submission of Contributions
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
#### 6. Trademarks
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
#### 7. Disclaimer of Warranty
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
#### 8. Limitation of Liability
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
#### 9. Accepting Warranty or Additional Liability
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
_END OF TERMS AND CONDITIONS_
### APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets `[]` replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same “printed page” as the copyright notice for easier identification within
third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+11
View File
@@ -0,0 +1,11 @@
cmake_minimum_required(VERSION 3.20)
include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake")
file(GLOB_RECURSE SOURCE_FILES "source/*.c*")
tactility_add_module(ili9341-module
SRCS ${SOURCE_FILES}
INCLUDE_DIRS include/
REQUIRES TactilityKernel platform-esp32 esp_lcd_ili9341 driver
)
@@ -0,0 +1,195 @@
Apache License
==============
_Version 2.0, January 2004_
_&lt;<http://www.apache.org/licenses/>&gt;_
### Terms and Conditions for use, reproduction, and distribution
#### 1. Definitions
“License” shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
“Licensor” shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
“Legal Entity” shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, “control” means **(i)** the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
outstanding shares, or **(iii)** beneficial ownership of such entity.
“You” (or “Your”) shall mean an individual or Legal Entity exercising
permissions granted by this License.
“Source” form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
“Object” form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
“Work” shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
“Derivative Works” shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
“Contribution” shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
“submitted” means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as “Not a Contribution.”
“Contributor” shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
#### 2. Grant of Copyright License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
#### 3. Grant of Patent License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
#### 4. Redistribution
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
* **(a)** You must give any other recipients of the Work or Derivative Works a copy of
this License; and
* **(b)** You must cause any modified files to carry prominent notices stating that You
changed the files; and
* **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
#### 5. Submission of Contributions
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
#### 6. Trademarks
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
#### 7. Disclaimer of Warranty
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
#### 8. Limitation of Liability
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
#### 9. Accepting Warranty or Additional Liability
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
_END OF TERMS AND CONDITIONS_
### APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets `[]` replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same “printed page” as the copyright notice for easier identification within
third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,71 @@
description: Ilitek ILI9341 display panel
compatible: "ilitek,ili9341"
bus: spi
properties:
horizontal-resolution:
type: int
required: true
description: Horizontal resolution in pixels
vertical-resolution:
type: int
required: true
description: Vertical resolution in pixels
gap-x:
type: int
default: 0
description: X offset applied to all draw operations
gap-y:
type: int
default: 0
description: Y offset applied to all draw operations
swap-xy:
type: boolean
default: false
description: Swap the X and Y axes
mirror-x:
type: boolean
default: false
description: Mirror the X axis
mirror-y:
type: boolean
default: false
description: Mirror the Y axis
invert-color:
type: boolean
default: false
description: Invert the panel's color output
bgr-order:
type: boolean
default: false
description: Use BGR element order instead of RGB
bits-per-pixel:
type: int
default: 16
description: Color depth in bits per pixel
pixel-clock-hz:
type: int
default: 40000000
description: SPI pixel clock frequency in Hz
transaction-queue-depth:
type: int
default: 10
description: Size of the internal SPI transaction queue
pin-dc:
type: phandles
required: true
description: Data/Command GPIO pin
pin-reset:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Reset GPIO pin
reset-active-high:
type: boolean
default: false
description: Whether the reset pin is active high
backlight:
type: phandle
default: NULL
description: Optional reference to this display's backlight device
+3
View File
@@ -0,0 +1,3 @@
dependencies:
- TactilityKernel
bindings: bindings
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/bindings/bindings.h>
#include <drivers/ili9341.h>
DEFINE_DEVICETREE(ili9341, struct Ili9341Config)
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdbool.h>
#include <tactility/device.h>
#include <tactility/drivers/gpio.h>
struct Ili9341Config {
uint16_t horizontal_resolution;
uint16_t vertical_resolution;
int32_t gap_x;
int32_t gap_y;
bool swap_xy;
bool mirror_x;
bool mirror_y;
bool invert_color;
bool bgr_order;
uint32_t bits_per_pixel;
uint32_t pixel_clock_hz;
uint8_t transaction_queue_depth;
struct GpioPinSpec pin_dc;
struct GpioPinSpec pin_reset;
bool reset_active_high;
// Optional reference to this display's backlight device, NULL if none.
struct Device* backlight;
};
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/module.h>
#ifdef __cplusplus
extern "C" {
#endif
extern struct Module ili9341_module;
#ifdef __cplusplus
}
#endif
+325
View File
@@ -0,0 +1,325 @@
// SPDX-License-Identifier: Apache-2.0
#include <drivers/ili9341.h>
#include <ili9341_module.h>
#include <tactility/check.h>
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/drivers/display.h>
#include <tactility/drivers/esp32_spi.h>
#include <tactility/drivers/spi_controller.h>
#include <tactility/error.h>
#include <tactility/log.h>
#include <esp_err.h>
#include <esp_lcd_io_spi.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include <esp_lcd_ili9341.h>
#include <freertos/semphr.h>
#include <cstdlib>
#define TAG "ILI9341"
#define GET_CONFIG(device) (static_cast<const Ili9341Config*>((device)->config))
struct Ili9341Internal {
esp_lcd_panel_io_handle_t io_handle;
esp_lcd_panel_handle_t panel_handle;
// Given from ISR context by on_color_trans_done() once a queued SPI transfer physically
// completes. draw_bitmap() blocks on this so it can honor DisplayApi's synchronous contract
// (see lvgl_display.c: the caller reuses/overwrites the color buffer as soon as draw_bitmap
// returns) - esp_lcd_panel_draw_bitmap() itself only queues the transfer and returns early.
SemaphoreHandle_t draw_done_semaphore;
};
// Fires for every completed SPI transaction on this IO (not just draw_bitmap's color transfers -
// bring-up commands like reset/init/gap go through the same IO), called from ISR context.
static bool IRAM_ATTR on_color_trans_done(esp_lcd_panel_io_handle_t, esp_lcd_panel_io_event_data_t*, void* user_ctx) {
auto* internal = static_cast<Ili9341Internal*>(user_ctx);
BaseType_t high_task_woken = pdFALSE;
xSemaphoreGiveFromISR(internal->draw_done_semaphore, &high_task_woken);
return high_task_woken == pdTRUE;
}
static int pin_or_unused(const struct GpioPinSpec& pin) {
return pin.gpio_controller == nullptr ? -1 : static_cast<int>(pin.pin);
}
// region Driver lifecycle
static error_t start(Device* device) {
auto* parent = device_get_parent(device);
check(device_get_type(parent) == &SPI_CONTROLLER_TYPE);
const auto* spi_config = static_cast<const Esp32SpiConfig*>(parent->config);
const auto* config = GET_CONFIG(device);
struct GpioPinSpec cs_pin;
if (esp32_spi_get_cs_pin(device, &cs_pin) != ERROR_NONE) {
LOG_E(TAG, "Failed to resolve CS pin");
return ERROR_RESOURCE;
}
auto* internal = static_cast<Ili9341Internal*>(malloc(sizeof(Ili9341Internal)));
if (internal == nullptr) {
return ERROR_OUT_OF_MEMORY;
}
internal->draw_done_semaphore = xSemaphoreCreateBinary();
if (internal->draw_done_semaphore == nullptr) {
free(internal);
return ERROR_OUT_OF_MEMORY;
}
esp_lcd_panel_io_spi_config_t io_config = {
.cs_gpio_num = pin_or_unused(cs_pin),
.dc_gpio_num = pin_or_unused(config->pin_dc),
.spi_mode = 0,
.pclk_hz = config->pixel_clock_hz,
.trans_queue_depth = config->transaction_queue_depth,
.on_color_trans_done = on_color_trans_done,
.user_ctx = internal,
.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 = 1,
.lsb_first = 0,
.cs_high_active = 0,
},
};
esp_err_t ret = esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)spi_config->host, &io_config, &internal->io_handle);
if (ret != ESP_OK) {
LOG_E(TAG, "Failed to create panel IO: %s", esp_err_to_name(ret));
vSemaphoreDelete(internal->draw_done_semaphore);
free(internal);
return ERROR_RESOURCE;
}
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = pin_or_unused(config->pin_reset),
.rgb_ele_order = config->bgr_order ? LCD_RGB_ELEMENT_ORDER_BGR : LCD_RGB_ELEMENT_ORDER_RGB,
.data_endian = LCD_RGB_DATA_ENDIAN_LITTLE,
.bits_per_pixel = config->bits_per_pixel,
.flags = { .reset_active_high = config->reset_active_high },
.vendor_config = nullptr,
};
ret = esp_lcd_new_panel_ili9341(internal->io_handle, &panel_config, &internal->panel_handle);
if (ret != ESP_OK) {
LOG_E(TAG, "Failed to create panel: %s", esp_err_to_name(ret));
esp_lcd_panel_io_del(internal->io_handle);
vSemaphoreDelete(internal->draw_done_semaphore);
free(internal);
return ERROR_RESOURCE;
}
// Bring-up sequence, order matches EspLcdDisplayV2::applyConfiguration (proven correct on real ILI9341 panels).
// Every failure path below must clean up fully: unlike stop_device, this is never retried by the kernel
// if start_device fails (see device_start() in TactilityKernel), so a partial failure here would leak.
bool ok =
esp_lcd_panel_reset(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);
if (ok) {
int gap_x = config->swap_xy ? config->gap_y : config->gap_x;
int gap_y = config->swap_xy ? config->gap_x : config->gap_y;
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->mirror_x && !config->mirror_y) || esp_lcd_panel_mirror(internal->panel_handle, config->mirror_x, config->mirror_y) == ESP_OK);
ok = ok && (!config->invert_color || esp_lcd_panel_invert_color(internal->panel_handle, true) == ESP_OK);
ok = ok && esp_lcd_panel_disp_on_off(internal->panel_handle, true) == ESP_OK;
if (!ok) {
LOG_E(TAG, "Failed to bring up panel");
esp_lcd_panel_del(internal->panel_handle);
esp_lcd_panel_io_del(internal->io_handle);
vSemaphoreDelete(internal->draw_done_semaphore);
free(internal);
return ERROR_RESOURCE;
}
device_set_driver_data(device, internal);
return ERROR_NONE;
}
static error_t stop(Device* device) {
auto* internal = static_cast<Ili9341Internal*>(device_get_driver_data(device));
error_t result = ERROR_NONE;
if (esp_lcd_panel_del(internal->panel_handle) != ESP_OK) {
LOG_E(TAG, "Failed to delete panel");
result = ERROR_RESOURCE;
}
if (esp_lcd_panel_io_del(internal->io_handle) != ESP_OK) {
LOG_E(TAG, "Failed to delete panel IO");
result = ERROR_RESOURCE;
}
vSemaphoreDelete(internal->draw_done_semaphore);
free(internal);
return result;
}
// endregion
// region DisplayApi
static error_t ili9341_reset(Device* device) {
auto* internal = static_cast<Ili9341Internal*>(device_get_driver_data(device));
return esp_lcd_panel_reset(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t ili9341_init(Device* device) {
auto* internal = static_cast<Ili9341Internal*>(device_get_driver_data(device));
return esp_lcd_panel_init(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t ili9341_draw_bitmap(Device* device, int32_t x_start, int32_t y_start, int32_t x_end, int32_t y_end, const void* color_data) {
auto* internal = static_cast<Ili9341Internal*>(device_get_driver_data(device));
// Drain any stale signal left over from a prior non-draw transaction (bring-up commands like
// reset/gap also complete through on_color_trans_done), so the take() below can only be
// satisfied by this draw's own transfer completing.
xSemaphoreTake(internal->draw_done_semaphore, 0);
if (esp_lcd_panel_draw_bitmap(internal->panel_handle, x_start, y_start, x_end, y_end, color_data) != ESP_OK) {
return ERROR_RESOURCE;
}
// Block until the SPI transfer physically completes: DisplayApi's draw_bitmap is a synchronous
// contract (see lvgl_display.c), so the caller must be able to safely reuse/overwrite
// color_data as soon as this call returns. esp_lcd_panel_draw_bitmap() only queues the
// transfer and returns once it's handed to the SPI peripheral, not once it's finished.
xSemaphoreTake(internal->draw_done_semaphore, portMAX_DELAY);
return ERROR_NONE;
}
static error_t ili9341_mirror(Device* device, bool x_axis, bool y_axis) {
auto* internal = static_cast<Ili9341Internal*>(device_get_driver_data(device));
return esp_lcd_panel_mirror(internal->panel_handle, x_axis, y_axis) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t ili9341_swap_xy(Device* device, bool swap_axes) {
auto* internal = static_cast<Ili9341Internal*>(device_get_driver_data(device));
return esp_lcd_panel_swap_xy(internal->panel_handle, swap_axes) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
// Reads the devicetree-configured baseline, not live hardware state: swap_xy()/mirror() calls made after
// start_device() (e.g. by an LVGL rotation binding) intentionally don't change what "rotation 0" means here.
static bool ili9341_get_swap_xy(Device* device) {
return GET_CONFIG(device)->swap_xy;
}
static bool ili9341_get_mirror_x(Device* device) {
return GET_CONFIG(device)->mirror_x;
}
static bool ili9341_get_mirror_y(Device* device) {
return GET_CONFIG(device)->mirror_y;
}
static error_t ili9341_set_gap(Device* device, int32_t x_gap, int32_t y_gap) {
auto* internal = static_cast<Ili9341Internal*>(device_get_driver_data(device));
return esp_lcd_panel_set_gap(internal->panel_handle, x_gap, y_gap) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t ili9341_invert_color(Device* device, bool invert_color_data) {
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;
}
static error_t ili9341_disp_on_off(Device* device, bool on_off) {
auto* internal = static_cast<Ili9341Internal*>(device_get_driver_data(device));
return esp_lcd_panel_disp_on_off(internal->panel_handle, on_off) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t ili9341_disp_sleep(Device* device, bool sleep) {
auto* internal = static_cast<Ili9341Internal*>(device_get_driver_data(device));
return esp_lcd_panel_disp_sleep(internal->panel_handle, sleep) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
// bgr_order only selects the panel controller's rgb_ele_order (applied in start(), below) so the
// R/B swap happens in hardware over SPI. LVGL always fills RGB565 buffers either way - it has no
// BGR565 format (lvgl_display.c rejects it, "no LVGL equivalent"), and none is needed here.
//
// _SWAPPED (not plain RGB565): the panel expects each 16-bit pixel high-byte-first over SPI, but
// this CPU is little-endian, so a plain RGB565 buffer arrives byte-swapped per pixel - confirmed
// on real hardware: black/white rendered correctly (0x0000/0xFFFF are swap-invariant) while every
// other color was wrong. The old deprecated-HAL driver had the same requirement (its equivalent
// knob was esp_lvgl_port's `swapBytes = true` in Devices/*/Source/devices/Display.cpp).
static enum DisplayColorFormat ili9341_get_color_format(Device*) {
return DISPLAY_COLOR_FORMAT_RGB565_SWAPPED;
}
static uint16_t ili9341_get_resolution_x(Device* device) {
return GET_CONFIG(device)->horizontal_resolution;
}
static uint16_t ili9341_get_resolution_y(Device* device) {
return GET_CONFIG(device)->vertical_resolution;
}
static void ili9341_get_frame_buffer(Device*, uint8_t, void** out_buffer) {
*out_buffer = nullptr;
}
static uint8_t ili9341_get_frame_buffer_count(Device*) {
return 0;
}
static error_t ili9341_get_backlight(Device* device, Device** backlight) {
auto* configured_backlight = GET_CONFIG(device)->backlight;
if (configured_backlight == nullptr) {
return ERROR_NOT_SUPPORTED;
}
*backlight = configured_backlight;
return ERROR_NONE;
}
// endregion
static const DisplayApi ili9341_display_api = {
.reset = ili9341_reset,
.init = ili9341_init,
.draw_bitmap = ili9341_draw_bitmap,
.mirror = ili9341_mirror,
.swap_xy = ili9341_swap_xy,
.get_swap_xy = ili9341_get_swap_xy,
.get_mirror_x = ili9341_get_mirror_x,
.get_mirror_y = ili9341_get_mirror_y,
.set_gap = ili9341_set_gap,
.invert_color = ili9341_invert_color,
.disp_on_off = ili9341_disp_on_off,
.disp_sleep = ili9341_disp_sleep,
.get_color_format = ili9341_get_color_format,
.get_resolution_x = ili9341_get_resolution_x,
.get_resolution_y = ili9341_get_resolution_y,
.get_frame_buffer = ili9341_get_frame_buffer,
.get_frame_buffer_count = ili9341_get_frame_buffer_count,
.get_backlight = ili9341_get_backlight,
};
Driver ili9341_driver = {
.name = "ili9341",
.compatible = (const char*[]) { "ilitek,ili9341", nullptr },
.start_device = start,
.stop_device = stop,
.api = &ili9341_display_api,
.device_type = &DISPLAY_TYPE,
.owner = &ili9341_module,
.internal = nullptr
};
+32
View File
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/check.h>
#include <tactility/driver.h>
#include <tactility/module.h>
extern "C" {
extern Driver ili9341_driver;
static error_t start() {
/* We crash when construct fails, because if a single driver fails to construct,
* there is no guarantee that the previously constructed drivers can be destroyed */
check(driver_construct_add(&ili9341_driver) == ERROR_NONE);
return ERROR_NONE;
}
static error_t stop() {
/* We crash when destruct fails, because if a single driver fails to destruct,
* there is no guarantee that the previously destroyed drivers can be recovered */
check(driver_remove_destruct(&ili9341_driver) == ERROR_NONE);
return ERROR_NONE;
}
Module ili9341_module = {
.name = "ili9341",
.start = start,
.stop = stop,
.symbols = nullptr,
.internal = nullptr
};
} // extern "C"
+11
View File
@@ -0,0 +1,11 @@
cmake_minimum_required(VERSION 3.20)
include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake")
file(GLOB_RECURSE SOURCE_FILES "source/*.c*")
tactility_add_module(ili9488-module
SRCS ${SOURCE_FILES}
INCLUDE_DIRS include/
REQUIRES TactilityKernel platform-esp32 esp_lcd_ili9488 driver
)
@@ -0,0 +1,195 @@
Apache License
==============
_Version 2.0, January 2004_
_&lt;<http://www.apache.org/licenses/>&gt;_
### Terms and Conditions for use, reproduction, and distribution
#### 1. Definitions
“License” shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
“Licensor” shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
“Legal Entity” shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, “control” means **(i)** the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
outstanding shares, or **(iii)** beneficial ownership of such entity.
“You” (or “Your”) shall mean an individual or Legal Entity exercising
permissions granted by this License.
“Source” form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
“Object” form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
“Work” shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
“Derivative Works” shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
“Contribution” shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
“submitted” means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as “Not a Contribution.”
“Contributor” shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
#### 2. Grant of Copyright License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
#### 3. Grant of Patent License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
#### 4. Redistribution
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
* **(a)** You must give any other recipients of the Work or Derivative Works a copy of
this License; and
* **(b)** You must cause any modified files to carry prominent notices stating that You
changed the files; and
* **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
#### 5. Submission of Contributions
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
#### 6. Trademarks
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
#### 7. Disclaimer of Warranty
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
#### 8. Limitation of Liability
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
#### 9. Accepting Warranty or Additional Liability
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
_END OF TERMS AND CONDITIONS_
### APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets `[]` replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same “printed page” as the copyright notice for easier identification within
third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,75 @@
description: Ilitek ILI9488 display panel
compatible: "ilitek,ili9488"
bus: spi
properties:
horizontal-resolution:
type: int
required: true
description: Horizontal resolution in pixels
vertical-resolution:
type: int
required: true
description: Vertical resolution in pixels
gap-x:
type: int
default: 0
description: X offset applied to all draw operations
gap-y:
type: int
default: 0
description: Y offset applied to all draw operations
swap-xy:
type: boolean
default: false
description: Swap the X and Y axes
mirror-x:
type: boolean
default: false
description: Mirror the X axis
mirror-y:
type: boolean
default: false
description: Mirror the Y axis
invert-color:
type: boolean
default: false
description: Invert the panel's color output
bgr-order:
type: boolean
default: true
description: Use BGR element order instead of RGB
bits-per-pixel:
type: int
default: 18
description: Color depth in bits per pixel. The panel requires 18 over SPI
color-conversion-buffer-size:
type: int
required: true
description: Size (in pixels) of the internal buffer used to convert 16bpp draw data to the panel's 18bpp format
pixel-clock-hz:
type: int
default: 40000000
description: SPI pixel clock frequency in Hz
transaction-queue-depth:
type: int
default: 10
description: Size of the internal SPI transaction queue
pin-dc:
type: phandles
required: true
description: Data/Command GPIO pin
pin-reset:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Reset GPIO pin
reset-active-high:
type: boolean
default: false
description: Whether the reset pin is active high
backlight:
type: phandle
default: NULL
description: Optional reference to this display's backlight device
+3
View File
@@ -0,0 +1,3 @@
dependencies:
- TactilityKernel
bindings: bindings
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/bindings/bindings.h>
#include <drivers/ili9488.h>
DEFINE_DEVICETREE(ili9488, struct Ili9488Config)

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