New kernel drivers and more (#560)
- Added kernel base drivers for: display, pointer (touch, mouse, etc.), keyboard, adc, power supply and backlight - Implement new kernel driver modules: `st7789-module` and `gt911-module` - Implement ESP32 ADC "oneshot" kernel driver - Implement ESP32 backlight kernel driver with ledc API - Implemented `battery-sense` driver that allows for voltage measurement and creates a power supply child device - Updated github actions - Updated flash scripts - Fix for esp32 legacy driver conflict with ADC - Created separate `lilygo-tdeck` and `lilygo-tdeck-plus` devices - Created `lilygo-module` with LilyGO kernel drivers - Fix for intermittent errors in build related to code generation of `devicetree.c` - `lvgl-module` can now map kernel drivers onto LVGL devices - Created `KernelDisplayApp` as a mirror of `HalDisplayApp` (formerly `DisplayApp`) - Removed `struct` and `enum` prefix in a lot of kernel driver cpp source files - `lilygo-tdeck` and `lilygo-tdeck-plus` are now fully relying on kernel drivers and don't use any of the old HAL
This commit is contained in:
committed by
GitHub
parent
c4406b24ba
commit
50c0a14a93
@@ -8,6 +8,8 @@
|
||||
#include <tactility/lvgl_module.h>
|
||||
|
||||
extern struct LvglModuleConfig lvgl_module_config;
|
||||
extern void lvgl_devices_attach();
|
||||
extern void lvgl_devices_detach();
|
||||
|
||||
static bool initialized = false;
|
||||
|
||||
@@ -44,6 +46,8 @@ error_t lvgl_arch_start() {
|
||||
// devices and services. The latter might start adding widgets immediately.
|
||||
initialized = true;
|
||||
|
||||
lvgl_devices_attach();
|
||||
|
||||
if (lvgl_module_config.on_start) lvgl_module_config.on_start();
|
||||
|
||||
return ERROR_NONE;
|
||||
@@ -52,6 +56,8 @@ error_t lvgl_arch_start() {
|
||||
error_t lvgl_arch_stop() {
|
||||
if (lvgl_module_config.on_stop) lvgl_module_config.on_stop();
|
||||
|
||||
lvgl_devices_detach();
|
||||
|
||||
if (lvgl_port_deinit() != ESP_OK) {
|
||||
// Call on_start again to recover
|
||||
if (lvgl_module_config.on_start) lvgl_module_config.on_start();
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
#include "tactility/lvgl_module.h"
|
||||
|
||||
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/drivers/display.h>
|
||||
#include <tactility/drivers/keyboard.h>
|
||||
#include <tactility/drivers/pointer.h>
|
||||
#include <tactility/log.h>
|
||||
#include <tactility/lvgl_display.h>
|
||||
#include <tactility/lvgl_keyboard.h>
|
||||
#include <tactility/lvgl_pointer.h>
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
#define TAG "lvgl"
|
||||
|
||||
void lvgl_devices_attach() {
|
||||
lvgl_lock();
|
||||
|
||||
lv_disp_t* lvgl_display = NULL;
|
||||
struct Device* kernel_display_device = device_find_first_by_type(&DISPLAY_TYPE);
|
||||
// Placeholder drivers (boards not yet migrated to the kernel display driver) register with a
|
||||
// NULL api: they exist so the devicetree node resolves, but have nothing for LVGL to bind to.
|
||||
if (kernel_display_device != NULL && device_get_driver(kernel_display_device)->api == NULL) {
|
||||
kernel_display_device = NULL;
|
||||
}
|
||||
if (kernel_display_device != NULL) {
|
||||
struct LvglDisplayConfig lvgl_display_config = {};
|
||||
if (lvgl_display_add(kernel_display_device, &lvgl_display_config, &lvgl_display) == ERROR_NONE) {
|
||||
LOG_I(TAG, "Bound %s to LVGL", kernel_display_device->name);
|
||||
} else {
|
||||
LOG_E(TAG, "Failed to bind %s to LVGL", kernel_display_device->name);
|
||||
}
|
||||
}
|
||||
|
||||
struct Device* kernel_pointer_device = device_find_first_by_type(&POINTER_TYPE);
|
||||
// Same placeholder situation as the display above.
|
||||
if (kernel_pointer_device != NULL && device_get_driver(kernel_pointer_device)->api == NULL) {
|
||||
kernel_pointer_device = NULL;
|
||||
}
|
||||
lv_indev_t* lvgl_pointer_device;
|
||||
if (kernel_pointer_device != NULL) {
|
||||
if (lvgl_pointer_add(kernel_pointer_device, lvgl_display, &lvgl_pointer_device) == ERROR_NONE) {
|
||||
LOG_I(TAG, "Bound %s to LVGL", kernel_pointer_device->name);
|
||||
} else {
|
||||
LOG_E(TAG, "Failed to bind %s to LVG", kernel_pointer_device->name);
|
||||
}
|
||||
}
|
||||
|
||||
struct Device* kernel_keyboard_device = device_find_first_by_type(&KEYBOARD_TYPE);
|
||||
lv_indev_t* lvgl_keyboard_device;
|
||||
if (kernel_keyboard_device != NULL) {
|
||||
if (lvgl_keyboard_add(kernel_keyboard_device, lvgl_display, &lvgl_keyboard_device) == ERROR_NONE) {
|
||||
LOG_I(TAG, "Bound %s to LVGL", kernel_keyboard_device->name);
|
||||
} else {
|
||||
LOG_E(TAG, "Failed to bind %s to LVGL", kernel_keyboard_device->name);
|
||||
}
|
||||
}
|
||||
|
||||
lvgl_unlock();
|
||||
}
|
||||
|
||||
void lvgl_devices_detach() {
|
||||
lvgl_lock();
|
||||
|
||||
lv_indev_t* device = lv_indev_get_next(NULL);
|
||||
while (device != NULL) {
|
||||
lv_indev_type_t type = lv_indev_get_type(device);
|
||||
if (type == LV_INDEV_TYPE_POINTER) {
|
||||
lvgl_pointer_remove(device);
|
||||
} else if (type == LV_INDEV_TYPE_KEYPAD) {
|
||||
lvgl_keyboard_remove(device);
|
||||
} else {
|
||||
lv_indev_delete(device);
|
||||
}
|
||||
// Always get the first item, because getting the next one doesn't work as the current pointer just became corrupt
|
||||
device = lv_indev_get_next(NULL);
|
||||
}
|
||||
|
||||
lv_disp_t* display = lv_disp_get_next(NULL);
|
||||
while (display != NULL) {
|
||||
lv_display_delete(display);
|
||||
display = lv_disp_get_next(NULL);
|
||||
}
|
||||
|
||||
lvgl_lock();
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <tactility/lvgl_display.h>
|
||||
|
||||
#include <tactility/drivers/display.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#include <esp_heap_caps.h>
|
||||
#endif
|
||||
|
||||
#define TAG "lvgl_display"
|
||||
|
||||
struct LvglDisplayCtx {
|
||||
struct Device* device;
|
||||
void* buf1;
|
||||
void* buf2;
|
||||
bool owns_buffers; // false when buf1/buf2 point at the device's own frame buffer(s)
|
||||
// The device's swap_xy/mirror_x/mirror_y at bind time, queried once and treated as the LV_DISPLAY_ROTATION_0 baseline.
|
||||
bool base_swap_xy;
|
||||
bool base_mirror_x;
|
||||
bool base_mirror_y;
|
||||
};
|
||||
|
||||
static void* lvgl_display_alloc_buffer(size_t size_bytes) {
|
||||
#ifdef ESP_PLATFORM
|
||||
void* buf = heap_caps_aligned_alloc(4, size_bytes, MALLOC_CAP_DMA | MALLOC_CAP_8BIT);
|
||||
if (buf == NULL) {
|
||||
buf = heap_caps_aligned_alloc(4, size_bytes, MALLOC_CAP_DEFAULT);
|
||||
}
|
||||
return buf;
|
||||
#else
|
||||
return malloc(size_bytes);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void lvgl_display_free_buffer(void* buf) {
|
||||
#ifdef ESP_PLATFORM
|
||||
heap_caps_free(buf);
|
||||
#else
|
||||
free(buf);
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool lvgl_display_map_color_format(enum DisplayColorFormat in, lv_color_format_t* out) {
|
||||
switch (in) {
|
||||
case DISPLAY_COLOR_FORMAT_RGB565:
|
||||
*out = LV_COLOR_FORMAT_RGB565;
|
||||
return true;
|
||||
case DISPLAY_COLOR_FORMAT_RGB565_SWAPPED:
|
||||
*out = LV_COLOR_FORMAT_RGB565_SWAPPED;
|
||||
return true;
|
||||
case DISPLAY_COLOR_FORMAT_RGB888:
|
||||
*out = LV_COLOR_FORMAT_RGB888;
|
||||
return true;
|
||||
default:
|
||||
// DISPLAY_COLOR_FORMAT_BGR565/_SWAPPED: no LVGL equivalent (channel order, not byte order).
|
||||
// DISPLAY_COLOR_FORMAT_MONOCHROME: unsupported for now, no 1bpp conversion buffer implemented.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void lvgl_display_apply_rotation(struct LvglDisplayCtx* ctx, lv_display_rotation_t rotation) {
|
||||
bool swap_xy = ctx->base_swap_xy;
|
||||
bool mirror_x = ctx->base_mirror_x;
|
||||
bool mirror_y = ctx->base_mirror_y;
|
||||
|
||||
switch (rotation) {
|
||||
case LV_DISPLAY_ROTATION_0:
|
||||
break;
|
||||
case LV_DISPLAY_ROTATION_90:
|
||||
swap_xy = !ctx->base_swap_xy;
|
||||
if (ctx->base_swap_xy) {
|
||||
mirror_x = !ctx->base_mirror_x;
|
||||
} else {
|
||||
mirror_y = !ctx->base_mirror_y;
|
||||
}
|
||||
break;
|
||||
case LV_DISPLAY_ROTATION_180:
|
||||
mirror_x = !ctx->base_mirror_x;
|
||||
mirror_y = !ctx->base_mirror_y;
|
||||
break;
|
||||
case LV_DISPLAY_ROTATION_270:
|
||||
swap_xy = !ctx->base_swap_xy;
|
||||
if (ctx->base_swap_xy) {
|
||||
mirror_y = !ctx->base_mirror_y;
|
||||
} else {
|
||||
mirror_x = !ctx->base_mirror_x;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
display_swap_xy(ctx->device, swap_xy);
|
||||
display_mirror(ctx->device, mirror_x, mirror_y);
|
||||
}
|
||||
|
||||
static void lvgl_display_rotation_event_cb(lv_event_t* e) {
|
||||
struct LvglDisplayCtx* ctx = (struct LvglDisplayCtx*)lv_event_get_user_data(e);
|
||||
lv_display_t* disp = (lv_display_t*)lv_event_get_current_target(e);
|
||||
lvgl_display_apply_rotation(ctx, lv_display_get_rotation(disp));
|
||||
}
|
||||
|
||||
static void lvgl_display_flush_cb(lv_display_t* disp, const lv_area_t* area, uint8_t* color_map) {
|
||||
struct LvglDisplayCtx* ctx = (struct LvglDisplayCtx*)lv_display_get_driver_data(disp);
|
||||
// LVGL's area is inclusive; DisplayApi's draw_bitmap wants an exclusive end.
|
||||
display_draw_bitmap(ctx->device, area->x1, area->y1, area->x2 + 1, area->y2 + 1, color_map);
|
||||
// DisplayApi has no async completion callback, so draw_bitmap is synchronous.
|
||||
lv_display_flush_ready(disp);
|
||||
}
|
||||
|
||||
error_t lvgl_display_add(struct Device* device, const struct LvglDisplayConfig* config, lv_display_t** out_display) {
|
||||
if (device == NULL || config == NULL || out_display == NULL) {
|
||||
return ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (device_get_type(device) != &DISPLAY_TYPE) {
|
||||
return ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
lv_color_format_t lv_color_format;
|
||||
enum DisplayColorFormat kernel_color_format = display_get_color_format(device);
|
||||
if (!lvgl_display_map_color_format(kernel_color_format, &lv_color_format)) {
|
||||
LOG_E(TAG, "Unsupported color format %d (no LVGL equivalent)", (int)kernel_color_format);
|
||||
return ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
uint16_t hres = display_get_resolution_x(device);
|
||||
uint16_t vres = display_get_resolution_y(device);
|
||||
uint8_t fb_count = display_get_frame_buffer_count(device);
|
||||
uint8_t bpp = lv_color_format_get_size(lv_color_format);
|
||||
|
||||
struct LvglDisplayCtx* ctx = (struct LvglDisplayCtx*)calloc(1, sizeof(struct LvglDisplayCtx));
|
||||
if (ctx == NULL) {
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
ctx->device = device;
|
||||
ctx->base_swap_xy = display_get_swap_xy(device);
|
||||
ctx->base_mirror_x = display_get_mirror_x(device);
|
||||
ctx->base_mirror_y = display_get_mirror_y(device);
|
||||
|
||||
lv_display_render_mode_t render_mode;
|
||||
size_t buf_size_bytes;
|
||||
|
||||
if (fb_count > 0) {
|
||||
display_get_frame_buffer(device, 0, &ctx->buf1);
|
||||
if (fb_count > 1) {
|
||||
display_get_frame_buffer(device, 1, &ctx->buf2);
|
||||
}
|
||||
ctx->owns_buffers = false;
|
||||
render_mode = LV_DISPLAY_RENDER_MODE_FULL;
|
||||
buf_size_bytes = (size_t)hres * vres * bpp;
|
||||
} else {
|
||||
uint16_t buf_height = config->buffer_height > 0 ? config->buffer_height : vres;
|
||||
buf_size_bytes = (size_t)hres * buf_height * bpp;
|
||||
|
||||
ctx->buf1 = lvgl_display_alloc_buffer(buf_size_bytes);
|
||||
if (ctx->buf1 == NULL) {
|
||||
free(ctx);
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
if (config->double_buffer) {
|
||||
ctx->buf2 = lvgl_display_alloc_buffer(buf_size_bytes);
|
||||
if (ctx->buf2 == NULL) {
|
||||
lvgl_display_free_buffer(ctx->buf1);
|
||||
free(ctx);
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
}
|
||||
ctx->owns_buffers = true;
|
||||
render_mode = LV_DISPLAY_RENDER_MODE_PARTIAL;
|
||||
}
|
||||
|
||||
lv_display_t* disp = lv_display_create(hres, vres);
|
||||
if (disp == NULL) {
|
||||
if (ctx->owns_buffers) {
|
||||
lvgl_display_free_buffer(ctx->buf1);
|
||||
lvgl_display_free_buffer(ctx->buf2);
|
||||
}
|
||||
free(ctx);
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
lv_display_set_color_format(disp, lv_color_format);
|
||||
lv_display_set_buffers(disp, ctx->buf1, ctx->buf2, buf_size_bytes, render_mode);
|
||||
lv_display_set_flush_cb(disp, lvgl_display_flush_cb);
|
||||
lv_display_set_driver_data(disp, ctx);
|
||||
lv_display_add_event_cb(disp, lvgl_display_rotation_event_cb, LV_EVENT_RESOLUTION_CHANGED, ctx);
|
||||
|
||||
// Apply once explicitly, independent of whether LV_EVENT_RESOLUTION_CHANGED fires on creation.
|
||||
lvgl_display_apply_rotation(ctx, lv_display_get_rotation(disp));
|
||||
|
||||
*out_display = disp;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
void lvgl_display_remove(lv_display_t* display) {
|
||||
if (display == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct LvglDisplayCtx* ctx = (struct LvglDisplayCtx*)lv_display_get_driver_data(display);
|
||||
lv_display_delete(display);
|
||||
|
||||
if (ctx != NULL) {
|
||||
if (ctx->owns_buffers) {
|
||||
if (ctx->buf1 != NULL) {
|
||||
lvgl_display_free_buffer(ctx->buf1);
|
||||
}
|
||||
if (ctx->buf2 != NULL) {
|
||||
lvgl_display_free_buffer(ctx->buf2);
|
||||
}
|
||||
}
|
||||
free(ctx);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <tactility/lvgl_keyboard.h>
|
||||
|
||||
#include <tactility/drivers/keyboard.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
struct LvglKeyboardCtx {
|
||||
struct Device* device;
|
||||
};
|
||||
|
||||
static void lvgl_keyboard_read_cb(lv_indev_t* indev, lv_indev_data_t* data) {
|
||||
struct LvglKeyboardCtx* ctx = (struct LvglKeyboardCtx*)lv_indev_get_driver_data(indev);
|
||||
|
||||
struct KeyboardKeyData key_data = {0};
|
||||
if (keyboard_read_key(ctx->device, &key_data) != ERROR_NONE) {
|
||||
data->state = LV_INDEV_STATE_RELEASED;
|
||||
data->continue_reading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// KeyboardKeyData deliberately mirrors lv_indev_data_t's key/continue_reading fields, so no translation is needed.
|
||||
data->key = key_data.key;
|
||||
data->state = key_data.pressed ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED;
|
||||
data->continue_reading = key_data.continue_reading;
|
||||
}
|
||||
|
||||
error_t lvgl_keyboard_add(struct Device* device, lv_display_t* display, lv_indev_t** out_indev) {
|
||||
if (device == NULL || out_indev == NULL) {
|
||||
return ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (device_get_type(device) != &KEYBOARD_TYPE) {
|
||||
return ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
struct LvglKeyboardCtx* ctx = (struct LvglKeyboardCtx*)malloc(sizeof(struct LvglKeyboardCtx));
|
||||
if (ctx == NULL) {
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
ctx->device = device;
|
||||
|
||||
lv_indev_t* indev = lv_indev_create();
|
||||
if (indev == NULL) {
|
||||
free(ctx);
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
lv_indev_set_type(indev, LV_INDEV_TYPE_KEYPAD);
|
||||
lv_indev_set_read_cb(indev, lvgl_keyboard_read_cb);
|
||||
lv_indev_set_driver_data(indev, ctx);
|
||||
if (display != NULL) {
|
||||
lv_indev_set_display(indev, display);
|
||||
}
|
||||
|
||||
*out_indev = indev;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
void lvgl_keyboard_remove(lv_indev_t* indev) {
|
||||
if (indev == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct LvglKeyboardCtx* ctx = (struct LvglKeyboardCtx*)lv_indev_get_driver_data(indev);
|
||||
lv_indev_delete(indev);
|
||||
free(ctx);
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <tactility/lvgl_pointer.h>
|
||||
|
||||
#include <tactility/drivers/pointer.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
struct LvglPointerCtx {
|
||||
struct Device* device;
|
||||
};
|
||||
|
||||
// Bus reads are expected to complete quickly; bound the wait so a stalled controller can't block the LVGL indev poll.
|
||||
static const TickType_t LVGL_POINTER_READ_TIMEOUT = pdMS_TO_TICKS(10);
|
||||
|
||||
static void lvgl_pointer_read_cb(lv_indev_t* indev, lv_indev_data_t* data) {
|
||||
struct LvglPointerCtx* ctx = (struct LvglPointerCtx*)lv_indev_get_driver_data(indev);
|
||||
|
||||
if (pointer_read_data(ctx->device, LVGL_POINTER_READ_TIMEOUT) != ERROR_NONE) {
|
||||
data->state = LV_INDEV_STATE_RELEASED;
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t x = 0;
|
||||
uint16_t y = 0;
|
||||
uint8_t point_count = 0;
|
||||
bool touched = pointer_get_touched_points(ctx->device, &x, &y, NULL, &point_count, 1);
|
||||
|
||||
if (touched && point_count > 0) {
|
||||
data->point.x = x;
|
||||
data->point.y = y;
|
||||
data->state = LV_INDEV_STATE_PRESSED;
|
||||
} else {
|
||||
data->state = LV_INDEV_STATE_RELEASED;
|
||||
}
|
||||
}
|
||||
|
||||
error_t lvgl_pointer_add(struct Device* device, lv_display_t* display, lv_indev_t** out_indev) {
|
||||
if (device == NULL || out_indev == NULL) {
|
||||
return ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (device_get_type(device) != &POINTER_TYPE) {
|
||||
return ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
struct LvglPointerCtx* ctx = (struct LvglPointerCtx*)malloc(sizeof(struct LvglPointerCtx));
|
||||
if (ctx == NULL) {
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
ctx->device = device;
|
||||
|
||||
lv_indev_t* indev = lv_indev_create();
|
||||
if (indev == NULL) {
|
||||
free(ctx);
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
|
||||
lv_indev_set_read_cb(indev, lvgl_pointer_read_cb);
|
||||
lv_indev_set_driver_data(indev, ctx);
|
||||
if (display != NULL) {
|
||||
lv_indev_set_display(indev, display);
|
||||
}
|
||||
|
||||
*out_indev = indev;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
void lvgl_pointer_remove(lv_indev_t* indev) {
|
||||
if (indev == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct LvglPointerCtx* ctx = (struct LvglPointerCtx*)lv_indev_get_driver_data(indev);
|
||||
lv_indev_delete(indev);
|
||||
free(ctx);
|
||||
}
|
||||
Reference in New Issue
Block a user