mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-08-17 23:55:04 +00:00
Papers3 improvements (#611)
- And Tab5 keyboard improvements - Usb host keyboard improvements - Symbols - Requires the device.py change from CL-32 PR but that only affects visuals and not build breaking
This commit is contained in:
parent
564d8af64c
commit
dc3f6104b8
@ -11,46 +11,27 @@
|
|||||||
#include <epd_board.h>
|
#include <epd_board.h>
|
||||||
#include <epdiy.h>
|
#include <epdiy.h>
|
||||||
|
|
||||||
|
#include <esp_heap_caps.h>
|
||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
#define TAG "Papers3Display"
|
#define TAG "Papers3Display"
|
||||||
#define GET_CONFIG(device) (static_cast<const Papers3DisplayConfig*>((device)->config))
|
#define GET_CONFIG(device) (static_cast<const Papers3DisplayConfig*>((device)->config))
|
||||||
|
|
||||||
// Maps each src byte (8px, MSB-first, bit=1 -> white/0x0F) to the 4 packed dst bytes
|
|
||||||
// (2px/byte, EPDiy MODE_PACKING_2PPB nibble order) it produces, replacing a per-pixel
|
|
||||||
// branch loop with a table lookup.
|
|
||||||
static uint32_t s_unpack_lut[256];
|
|
||||||
|
|
||||||
static void init_unpack_lut() {
|
|
||||||
for (uint32_t byte = 0; byte < 256; byte++) {
|
|
||||||
uint8_t dst[4];
|
|
||||||
for (int32_t pair = 0; pair < 4; pair++) {
|
|
||||||
const uint8_t bit0 = (byte >> (7 - pair * 2)) & 0x01U;
|
|
||||||
const uint8_t bit1 = (byte >> (7 - pair * 2 - 1)) & 0x01U;
|
|
||||||
const uint8_t p0 = bit0 ? 0x0FU : 0x00U;
|
|
||||||
const uint8_t p1 = bit1 ? 0x0FU : 0x00U;
|
|
||||||
dst[pair] = static_cast<uint8_t>((p1 << 4U) | p0);
|
|
||||||
}
|
|
||||||
memcpy(&s_unpack_lut[byte], dst, sizeof(dst));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
extern Module m5stack_papers3_module;
|
extern Module m5stack_papers3_module;
|
||||||
|
|
||||||
// epd_hl_init() sets an internal already_initialized flag and has no matching deinit, so the
|
// epd_hl_init() has no matching deinit and sets an internal already_initialized flag, so the
|
||||||
// highlevel state (and the framebuffer it owns) must persist across stop()/start() cycles and be
|
// highlevel state must persist across stop()/start() cycles and be reused rather than recreated.
|
||||||
// reused rather than recreated - ported from the old deprecated-HAL EpdiyDisplay's identical
|
|
||||||
// s_hlInitialized/s_hlState statics.
|
|
||||||
static bool s_hl_initialized = false;
|
static bool s_hl_initialized = false;
|
||||||
static EpdiyHighlevelState s_hl_state = {};
|
static EpdiyHighlevelState s_hl_state = {};
|
||||||
|
|
||||||
struct Papers3DisplayInternal {
|
struct Papers3DisplayInternal {
|
||||||
EpdiyHighlevelState hl_state;
|
EpdiyHighlevelState hl_state;
|
||||||
uint8_t* framebuffer;
|
uint8_t* framebuffer;
|
||||||
// Scratch buffer for the I1(1bpp)->EPDiy(4bpp packed, 2px/byte) conversion in draw_bitmap().
|
// Scratch buffer for the grayscale8->EPDiy(4bpp packed, 2px/byte) conversion in draw_bitmap().
|
||||||
uint8_t* packed_buffer;
|
uint8_t* packed_buffer;
|
||||||
bool powered;
|
bool powered;
|
||||||
};
|
};
|
||||||
@ -76,16 +57,19 @@ static error_t papers3_display_reset(Device* device) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static error_t papers3_display_init(Device* device) {
|
static error_t papers3_display_init(Device* device) {
|
||||||
|
const auto* config = GET_CONFIG(device);
|
||||||
auto* internal = static_cast<Papers3DisplayInternal*>(device_get_driver_data(device));
|
auto* internal = static_cast<Papers3DisplayInternal*>(device_get_driver_data(device));
|
||||||
power_on(internal);
|
power_on(internal);
|
||||||
epd_clear();
|
// The bootloader/boot-logo splash draws via partial refreshes that never get a real quality
|
||||||
epd_hl_set_all_white(&internal->hl_state);
|
// pass, leaving a faint ghost. Run a full clear now, before LVGL's first flush ever reaches
|
||||||
|
// draw_bitmap(), so it never has to undo content LVGL already put on screen.
|
||||||
|
epd_fullclear(&internal->hl_state, config->temperature_celsius);
|
||||||
return ERROR_NONE;
|
return ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// LVGL only ever calls this with the full frame: DISPLAY_COLOR_FORMAT_MONOCHROME forces
|
// Reports GRAYSCALE8 (not MONOCHROME) so LVGL uses partial/tile updates instead of forcing
|
||||||
// LV_DISPLAY_RENDER_MODE_FULL in the generic kernel LVGL bridge (lvgl_display.c), and FULL mode
|
// full-frame - the bridge hardcodes full-frame for MONOCHROME/I1 regardless of capability flags.
|
||||||
// only presents (calls draw_bitmap) once per render cycle, with the complete 0,0..hres,vres rect.
|
// So draw_bitmap is called once per changed tile, not necessarily the whole panel.
|
||||||
static error_t papers3_display_draw_bitmap(Device* device, int32_t x_start, int32_t y_start, int32_t x_end, int32_t y_end, const void* color_data) {
|
static error_t papers3_display_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<Papers3DisplayInternal*>(device_get_driver_data(device));
|
auto* internal = static_cast<Papers3DisplayInternal*>(device_get_driver_data(device));
|
||||||
const auto* config = GET_CONFIG(device);
|
const auto* config = GET_CONFIG(device);
|
||||||
@ -93,32 +77,26 @@ static error_t papers3_display_draw_bitmap(Device* device, int32_t x_start, int3
|
|||||||
const int32_t width = x_end - x_start;
|
const int32_t width = x_end - x_start;
|
||||||
const int32_t height = y_end - y_start;
|
const int32_t height = y_end - y_start;
|
||||||
|
|
||||||
// color_data is DISPLAY_COLOR_FORMAT_MONOCHROME: row-major, MSB-first 1bpp (LVGL's LV_COLOR_FORMAT_I1
|
// color_data is DISPLAY_COLOR_FORMAT_GRAYSCALE8: row-major, 1 byte/pixel luminance
|
||||||
// with the palette header already stripped by the caller). Bit 1 = white/lit (LVGL's I1 blend
|
// (0x00=black..0xFF=white, matching LVGL's L8). EPDiy wants 4bpp packed (2px/byte, 0x0=black,
|
||||||
// sets a bit when the source luminance is above its threshold), bit 0 = black.
|
// 0xF=white) - a plain >>4 truncation preserves all 16 real gray levels the panel supports
|
||||||
|
// (this panel is not B/W-only; see MODE_GC16/GL16 in papers3-display.yaml's draw-mode doc).
|
||||||
const auto* src = static_cast<const uint8_t*>(color_data);
|
const auto* src = static_cast<const uint8_t*>(color_data);
|
||||||
const size_t src_stride = static_cast<size_t>(width + 7) / 8;
|
const size_t src_stride = static_cast<size_t>(width);
|
||||||
const size_t packed_stride = static_cast<size_t>(width + 1) / 2;
|
const size_t packed_stride = static_cast<size_t>(width + 1) / 2;
|
||||||
|
|
||||||
for (int32_t row = 0; row < height; row++) {
|
for (int32_t row = 0; row < height; row++) {
|
||||||
const uint8_t* src_row = src + static_cast<size_t>(row) * src_stride;
|
const uint8_t* src_row = src + static_cast<size_t>(row) * src_stride;
|
||||||
uint8_t* dst_row = internal->packed_buffer + static_cast<size_t>(row) * packed_stride;
|
uint8_t* dst_row = internal->packed_buffer + static_cast<size_t>(row) * packed_stride;
|
||||||
int32_t col = 0;
|
int32_t col = 0;
|
||||||
// Bulk path: one LUT lookup + 4-byte copy per 8 source pixels.
|
for (; col + 2 <= width; col += 2) {
|
||||||
for (; col + 8 <= width; col += 8) {
|
const uint8_t p0 = src_row[col] >> 4U;
|
||||||
memcpy(dst_row + col / 2, &s_unpack_lut[src_row[col / 8]], 4);
|
const uint8_t p1 = src_row[col + 1] >> 4U;
|
||||||
}
|
|
||||||
// Tail: fewer than 8 pixels left (width not a multiple of 8).
|
|
||||||
for (; col < width; col += 2) {
|
|
||||||
const uint8_t bit0 = (src_row[col / 8] >> (7 - (col % 8))) & 0x01U;
|
|
||||||
const uint8_t p0 = bit0 ? 0x0FU : 0x00U;
|
|
||||||
uint8_t p1 = 0;
|
|
||||||
if (col + 1 < width) {
|
|
||||||
const uint8_t bit1 = (src_row[(col + 1) / 8] >> (7 - ((col + 1) % 8))) & 0x01U;
|
|
||||||
p1 = bit1 ? 0x0FU : 0x00U;
|
|
||||||
}
|
|
||||||
dst_row[col / 2] = static_cast<uint8_t>((p1 << 4U) | p0);
|
dst_row[col / 2] = static_cast<uint8_t>((p1 << 4U) | p0);
|
||||||
}
|
}
|
||||||
|
if (col < width) { // odd width: last column has no pair, low nibble unused
|
||||||
|
dst_row[col / 2] = static_cast<uint8_t>(src_row[col] >> 4U);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const EpdRect update_area = {
|
const EpdRect update_area = {
|
||||||
@ -152,7 +130,7 @@ static error_t papers3_display_disp_on_off(Device* device, bool on_off) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static DisplayColorFormat papers3_display_get_color_format(Device*) {
|
static DisplayColorFormat papers3_display_get_color_format(Device*) {
|
||||||
return DISPLAY_COLOR_FORMAT_MONOCHROME;
|
return DISPLAY_COLOR_FORMAT_GRAYSCALE8;
|
||||||
}
|
}
|
||||||
|
|
||||||
// epd_width()/epd_height() are the panel's native, unrotated dimensions (display->width/height in
|
// epd_width()/epd_height() are the panel's native, unrotated dimensions (display->width/height in
|
||||||
@ -172,7 +150,7 @@ static uint16_t papers3_display_get_resolution_y(Device*) {
|
|||||||
|
|
||||||
static void papers3_display_get_frame_buffer(Device*, uint8_t, void** out_buffer) {
|
static void papers3_display_get_frame_buffer(Device*, uint8_t, void** out_buffer) {
|
||||||
// Not exposed via the generic fb-direct path: EPDiy's framebuffer is its own 4bpp packed
|
// Not exposed via the generic fb-direct path: EPDiy's framebuffer is its own 4bpp packed
|
||||||
// format, not the DISPLAY_COLOR_FORMAT_MONOCHROME (1bpp) this driver reports - see
|
// format, not the DISPLAY_COLOR_FORMAT_GRAYSCALE8 (1 byte/pixel) this driver reports - see
|
||||||
// get_frame_buffer_count() and draw_bitmap()'s conversion.
|
// get_frame_buffer_count() and draw_bitmap()'s conversion.
|
||||||
*out_buffer = nullptr;
|
*out_buffer = nullptr;
|
||||||
}
|
}
|
||||||
@ -184,7 +162,9 @@ static uint8_t papers3_display_get_frame_buffer_count(Device*) {
|
|||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
static const DisplayApi papers3_display_api = {
|
static const DisplayApi papers3_display_api = {
|
||||||
.capabilities = DISPLAY_CAPABILITY_ON_OFF | DISPLAY_CAPABILITY_REQUIRES_FULL_FRAME | DISPLAY_CAPABILITY_SLOW_REFRESH,
|
// PREFER_EXTERNAL_RAM: draw_bitmap() converts into packed_buffer before touching hardware,
|
||||||
|
// never DMAs from LVGL's pointer directly - frees LVGL's draw buffers from forced internal RAM.
|
||||||
|
.capabilities = DISPLAY_CAPABILITY_ON_OFF | DISPLAY_CAPABILITY_SLOW_REFRESH | DISPLAY_CAPABILITY_PREFER_EXTERNAL_RAM,
|
||||||
.reset = papers3_display_reset,
|
.reset = papers3_display_reset,
|
||||||
.init = papers3_display_init,
|
.init = papers3_display_init,
|
||||||
.draw_bitmap = papers3_display_draw_bitmap,
|
.draw_bitmap = papers3_display_draw_bitmap,
|
||||||
@ -213,12 +193,6 @@ static const DisplayApi papers3_display_api = {
|
|||||||
static error_t start(Device* device) {
|
static error_t start(Device* device) {
|
||||||
const auto* config = GET_CONFIG(device);
|
const auto* config = GET_CONFIG(device);
|
||||||
|
|
||||||
static bool s_lut_initialized = false;
|
|
||||||
if (!s_lut_initialized) {
|
|
||||||
init_unpack_lut();
|
|
||||||
s_lut_initialized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto* internal = static_cast<Papers3DisplayInternal*>(malloc(sizeof(Papers3DisplayInternal)));
|
auto* internal = static_cast<Papers3DisplayInternal*>(malloc(sizeof(Papers3DisplayInternal)));
|
||||||
if (internal == nullptr) {
|
if (internal == nullptr) {
|
||||||
return ERROR_OUT_OF_MEMORY;
|
return ERROR_OUT_OF_MEMORY;
|
||||||
@ -245,8 +219,12 @@ static error_t start(Device* device) {
|
|||||||
internal->framebuffer = epd_hl_get_framebuffer(&internal->hl_state);
|
internal->framebuffer = epd_hl_get_framebuffer(&internal->hl_state);
|
||||||
|
|
||||||
// Sized for the rotated (LVGL-facing) resolution - see get_resolution_x()/y()'s comment.
|
// Sized for the rotated (LVGL-facing) resolution - see get_resolution_x()/y()'s comment.
|
||||||
|
// ~260KB for this panel - a plain malloc() would land in scarce internal RAM. This buffer is
|
||||||
|
// only ever read once per draw_bitmap() call by epd_draw_rotated_image() (into epdiy's own
|
||||||
|
// SPIRAM-backed framebuffers, see highlevel.c), so it has no internal-RAM/DMA requirement and
|
||||||
|
// belongs in PSRAM instead, matching epdiy's own front_fb/back_fb/difference_fb allocations.
|
||||||
const size_t packed_buffer_size = static_cast<size_t>((epd_rotated_display_width() + 1) / 2) * static_cast<size_t>(epd_rotated_display_height());
|
const size_t packed_buffer_size = static_cast<size_t>((epd_rotated_display_width() + 1) / 2) * static_cast<size_t>(epd_rotated_display_height());
|
||||||
internal->packed_buffer = static_cast<uint8_t*>(malloc(packed_buffer_size));
|
internal->packed_buffer = static_cast<uint8_t*>(heap_caps_malloc(packed_buffer_size, MALLOC_CAP_SPIRAM));
|
||||||
if (internal->packed_buffer == nullptr) {
|
if (internal->packed_buffer == nullptr) {
|
||||||
LOG_E(TAG, "Failed to allocate packed pixel buffer");
|
LOG_E(TAG, "Failed to allocate packed pixel buffer");
|
||||||
epd_deinit();
|
epd_deinit();
|
||||||
|
|||||||
@ -35,6 +35,12 @@ static constexpr uint32_t REPEAT_RATE_MS = 80;
|
|||||||
// REG_INT_STAT polling (when no IRQ pin) and software key-repeat ticking.
|
// REG_INT_STAT polling (when no IRQ pin) and software key-repeat ticking.
|
||||||
static constexpr uint32_t POLL_INTERVAL_MS = 20;
|
static constexpr uint32_t POLL_INTERVAL_MS = 20;
|
||||||
|
|
||||||
|
// Upper bound on events consumed per drain_events() call. Since the loop re-reads REG_EVENT_NUM
|
||||||
|
// each iteration rather than counting down a latched value, this caps the damage if the device
|
||||||
|
// ever reports a non-zero count that never drains - without it, that would spin forever holding
|
||||||
|
// the I2C bus. The device's own queue is far smaller than this, so it never limits normal bursts.
|
||||||
|
static constexpr uint8_t MAX_EVENTS_PER_DRAIN = 32;
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Register addresses
|
// Register addresses
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@ -117,6 +123,12 @@ static constexpr HidMapping KEY_MATRIX_HID_SYM[70] = {
|
|||||||
// Covers all codes present in the Tab5 matrix tables above. LV_KEY_* are plain uint32_t
|
// Covers all codes present in the Tab5 matrix tables above. LV_KEY_* are plain uint32_t
|
||||||
// constants - matching KeyboardKeyData::key's driver-defined contract and the same convention
|
// constants - matching KeyboardKeyData::key's driver-defined contract and the same convention
|
||||||
// m5stack-module's cardputer_keyboard.cpp kernel driver already uses.
|
// m5stack-module's cardputer_keyboard.cpp kernel driver already uses.
|
||||||
|
//
|
||||||
|
// `ctrl` only selects the LVGL focus-navigation aliases for the arrow keys. Ctrl chords on
|
||||||
|
// ordinary keys are NOT folded into the returned value - the C0 control codes a terminal wants
|
||||||
|
// (Ctrl+C = 0x03, Ctrl+K = 0x0B, ...) collide with the LVGL constants returned here (LV_KEY_END = 3,
|
||||||
|
// LV_KEY_PREV = 11, ...), so Ctrl is reported out-of-band via KeyboardKeyData::ctrl instead and
|
||||||
|
// consumers that want control codes derive them themselves.
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
static uint32_t tab5_translate_key(uint8_t keycode, uint8_t modifier, bool ctrl) {
|
static uint32_t tab5_translate_key(uint8_t keycode, uint8_t modifier, bool ctrl) {
|
||||||
const bool shift = (modifier & 0x22U) != 0U;
|
const bool shift = (modifier & 0x22U) != 0U;
|
||||||
@ -172,6 +184,15 @@ static uint32_t now_ms() {
|
|||||||
return static_cast<uint32_t>(esp_timer_get_time() / 1000);
|
return static_cast<uint32_t>(esp_timer_get_time() / 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Queued key event. Modifier state is captured here at enqueue time rather than read back from
|
||||||
|
// Tab5KeyboardInternal at dequeue time, since the user can release Ctrl before read_key() drains
|
||||||
|
// the event - and software key-repeat replays this same struct, so a held chord keeps its modifiers.
|
||||||
|
struct Tab5KeyEvent {
|
||||||
|
uint32_t key;
|
||||||
|
bool ctrl;
|
||||||
|
bool alt;
|
||||||
|
};
|
||||||
|
|
||||||
struct Tab5KeyboardInternal {
|
struct Tab5KeyboardInternal {
|
||||||
QueueHandle_t queue;
|
QueueHandle_t queue;
|
||||||
|
|
||||||
@ -181,6 +202,7 @@ struct Tab5KeyboardInternal {
|
|||||||
bool aa_held;
|
bool aa_held;
|
||||||
bool aa_tapped;
|
bool aa_tapped;
|
||||||
bool ctrl_held;
|
bool ctrl_held;
|
||||||
|
bool alt_held;
|
||||||
|
|
||||||
// IRQ-driven event gating
|
// IRQ-driven event gating
|
||||||
volatile bool irq_pending;
|
volatile bool irq_pending;
|
||||||
@ -193,7 +215,7 @@ struct Tab5KeyboardInternal {
|
|||||||
uint32_t last_poll_ms;
|
uint32_t last_poll_ms;
|
||||||
|
|
||||||
// Software key-repeat state (tracked by position to survive modifier changes)
|
// Software key-repeat state (tracked by position to survive modifier changes)
|
||||||
uint32_t repeat_key;
|
Tab5KeyEvent repeat_event;
|
||||||
uint8_t repeat_row;
|
uint8_t repeat_row;
|
||||||
uint8_t repeat_col;
|
uint8_t repeat_col;
|
||||||
uint32_t repeat_start_ms;
|
uint32_t repeat_start_ms;
|
||||||
@ -289,16 +311,22 @@ static void remove_irq_pin(Tab5KeyboardInternal* internal) {
|
|||||||
// drain_events - reads all pending events from the device queue
|
// drain_events - reads all pending events from the device queue
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
static void drain_events(Device* device, Tab5KeyboardInternal* internal) {
|
static void drain_events(Device* device, Tab5KeyboardInternal* internal) {
|
||||||
|
// REG_EVENT_NUM is re-read every iteration rather than latched once and counted down, matching
|
||||||
|
// M5's own UnitTab5Keyboard::drain_events(). Each REG_KEY_EVENT read consumes one event from the
|
||||||
|
// device queue, so a count latched up front can go stale mid-drain; re-reading makes the loop
|
||||||
|
// self-correcting and lets it stop as soon as the device says the queue is actually empty.
|
||||||
|
uint8_t drained = 0;
|
||||||
|
while (drained < MAX_EVENTS_PER_DRAIN) {
|
||||||
uint8_t count = 0;
|
uint8_t count = 0;
|
||||||
if (!read_reg(device, REG_EVENT_NUM, &count) || count == 0) {
|
if (!read_reg(device, REG_EVENT_NUM, &count) || count == 0) {
|
||||||
return;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (count > 0) {
|
|
||||||
uint8_t raw = 0;
|
uint8_t raw = 0;
|
||||||
if (!read_reg(device, REG_KEY_EVENT, &raw) || raw == KEY_EVENT_EMPTY) {
|
if (!read_reg(device, REG_KEY_EVENT, &raw) || raw == KEY_EVENT_EMPTY) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
drained++;
|
||||||
|
|
||||||
const bool pressed = (raw & 0x80U) != 0U;
|
const bool pressed = (raw & 0x80U) != 0U;
|
||||||
const uint8_t row = (raw >> 4U) & 0x07U;
|
const uint8_t row = (raw >> 4U) & 0x07U;
|
||||||
@ -308,7 +336,6 @@ static void drain_events(Device* device, Tab5KeyboardInternal* internal) {
|
|||||||
if (row == MOD_ROW_SYM && col == MOD_COL_SYM) {
|
if (row == MOD_ROW_SYM && col == MOD_COL_SYM) {
|
||||||
internal->sym_active = pressed;
|
internal->sym_active = pressed;
|
||||||
update_leds(device, internal);
|
update_leds(device, internal);
|
||||||
count--;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (row == MOD_ROW_AA && col == MOD_COL_AA) {
|
if (row == MOD_ROW_AA && col == MOD_COL_AA) {
|
||||||
@ -324,16 +351,14 @@ static void drain_events(Device* device, Tab5KeyboardInternal* internal) {
|
|||||||
internal->aa_tapped = false;
|
internal->aa_tapped = false;
|
||||||
}
|
}
|
||||||
update_leds(device, internal);
|
update_leds(device, internal);
|
||||||
count--;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (row == MOD_ROW_CTRL && col == MOD_COL_CTRL) {
|
if (row == MOD_ROW_CTRL && col == MOD_COL_CTRL) {
|
||||||
internal->ctrl_held = pressed;
|
internal->ctrl_held = pressed;
|
||||||
count--;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (row == MOD_ROW_ALT && col == MOD_COL_ALT) {
|
if (row == MOD_ROW_ALT && col == MOD_COL_ALT) {
|
||||||
count--;
|
internal->alt_held = pressed;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -354,10 +379,11 @@ static void drain_events(Device* device, Tab5KeyboardInternal* internal) {
|
|||||||
// no business reaching into, so ESC is now just queued as a normal key
|
// no business reaching into, so ESC is now just queued as a normal key
|
||||||
// like everything else (LVGL/app code already handles ESC via focus/group
|
// like everything else (LVGL/app code already handles ESC via focus/group
|
||||||
// navigation the same way a dedicated ESC key on any other keyboard would).
|
// navigation the same way a dedicated ESC key on any other keyboard would).
|
||||||
xQueueSend(internal->queue, &lv_key, 0);
|
const Tab5KeyEvent event = { lv_key, internal->ctrl_held, internal->alt_held };
|
||||||
|
xQueueSend(internal->queue, &event, 0);
|
||||||
// Arm software repeat tracking by row/col to survive modifier changes
|
// Arm software repeat tracking by row/col to survive modifier changes
|
||||||
const uint32_t now = now_ms();
|
const uint32_t now = now_ms();
|
||||||
internal->repeat_key = lv_key;
|
internal->repeat_event = event;
|
||||||
internal->repeat_row = row;
|
internal->repeat_row = row;
|
||||||
internal->repeat_col = col;
|
internal->repeat_col = col;
|
||||||
internal->repeat_start_ms = now;
|
internal->repeat_start_ms = now;
|
||||||
@ -370,12 +396,11 @@ static void drain_events(Device* device, Tab5KeyboardInternal* internal) {
|
|||||||
}
|
}
|
||||||
} else if (row == internal->repeat_row && col == internal->repeat_col) {
|
} else if (row == internal->repeat_row && col == internal->repeat_col) {
|
||||||
// Match release by position, not translated value — survives sticky Aa clear
|
// Match release by position, not translated value — survives sticky Aa clear
|
||||||
internal->repeat_key = 0;
|
internal->repeat_event.key = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
count--;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear INT status after draining so the line de-asserts
|
// Clear INT status after draining so the line de-asserts
|
||||||
@ -434,13 +459,19 @@ static void poll_if_due(Device* device, Tab5KeyboardInternal* internal) {
|
|||||||
drain_events(device, internal);
|
drain_events(device, internal);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Software key-repeat (runs every tick regardless of IRQ)
|
// Software key-repeat (runs every tick regardless of IRQ).
|
||||||
if (internal->repeat_key != 0U) {
|
//
|
||||||
if ((now - internal->repeat_start_ms) >= REPEAT_INITIAL_MS) {
|
// The clock is re-read here rather than reusing `now` from the top of the function: a press
|
||||||
|
// handled by the drain above sets repeat_start_ms to a timestamp taken *during* the drain, which
|
||||||
|
// is later than `now`. The unsigned subtraction below would then wrap to a huge value and clear
|
||||||
|
// the REPEAT_INITIAL_MS gate immediately, emitting one spurious repeat ~1ms after every press.
|
||||||
|
const uint32_t repeat_now = now_ms();
|
||||||
|
if (internal->repeat_event.key != 0U) {
|
||||||
|
if ((repeat_now - internal->repeat_start_ms) >= REPEAT_INITIAL_MS) {
|
||||||
const uint32_t last = internal->repeat_last_ms;
|
const uint32_t last = internal->repeat_last_ms;
|
||||||
if (last == 0 || (now - last) >= REPEAT_RATE_MS) {
|
if (last == 0 || (repeat_now - last) >= REPEAT_RATE_MS) {
|
||||||
internal->repeat_last_ms = now;
|
internal->repeat_last_ms = repeat_now;
|
||||||
xQueueSend(internal->queue, &internal->repeat_key, 0);
|
xQueueSend(internal->queue, &internal->repeat_event, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -464,7 +495,7 @@ static error_t start(Device* device) {
|
|||||||
}
|
}
|
||||||
memset(internal, 0, sizeof(Tab5KeyboardInternal));
|
memset(internal, 0, sizeof(Tab5KeyboardInternal));
|
||||||
|
|
||||||
internal->queue = xQueueCreate(20, sizeof(uint32_t));
|
internal->queue = xQueueCreate(20, sizeof(Tab5KeyEvent));
|
||||||
if (internal->queue == nullptr) {
|
if (internal->queue == nullptr) {
|
||||||
free(internal);
|
free(internal);
|
||||||
return ERROR_OUT_OF_MEMORY;
|
return ERROR_OUT_OF_MEMORY;
|
||||||
@ -522,15 +553,19 @@ static error_t tab5_keyboard_read_key(Device* device, KeyboardKeyData* data) {
|
|||||||
|
|
||||||
poll_if_due(device, internal);
|
poll_if_due(device, internal);
|
||||||
|
|
||||||
uint32_t lv_key = 0;
|
Tab5KeyEvent event = {};
|
||||||
if (xQueueReceive(internal->queue, &lv_key, 0) == pdTRUE) {
|
if (xQueueReceive(internal->queue, &event, 0) == pdTRUE) {
|
||||||
data->key = lv_key;
|
data->key = event.key;
|
||||||
data->pressed = true;
|
data->pressed = true;
|
||||||
data->continue_reading = uxQueueMessagesWaiting(internal->queue) > 0;
|
data->continue_reading = uxQueueMessagesWaiting(internal->queue) > 0;
|
||||||
|
data->ctrl = event.ctrl;
|
||||||
|
data->alt = event.alt;
|
||||||
} else {
|
} else {
|
||||||
data->key = 0;
|
data->key = 0;
|
||||||
data->pressed = false;
|
data->pressed = false;
|
||||||
data->continue_reading = false;
|
data->continue_reading = false;
|
||||||
|
data->ctrl = false;
|
||||||
|
data->alt = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ERROR_NONE;
|
return ERROR_NONE;
|
||||||
|
|||||||
@ -53,6 +53,13 @@ struct LvglDisplayConfig {
|
|||||||
* the LV_COLOR_FORMAT_I1 path (already always-full-frame).
|
* the LV_COLOR_FORMAT_I1 path (already always-full-frame).
|
||||||
*/
|
*/
|
||||||
bool force_full_frame;
|
bool force_full_frame;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opts owned draw buffer(s) OUT of DMA-capable memory, falling back to PSRAM instead of
|
||||||
|
* scarce internal RAM. Default false keeps existing behavior. Only set true if the driver
|
||||||
|
* never DMAs directly from the buffer pointer LVGL hands it in the flush callback.
|
||||||
|
*/
|
||||||
|
bool prefer_external_ram;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -67,11 +67,13 @@ void lvgl_devices_attach() {
|
|||||||
// itself is never asked to do something it can't.
|
// itself is never asked to do something it can't.
|
||||||
bool can_hw_rotate = display_has_capability(kernel_display_device, DISPLAY_CAPABILITY_CAP_SWAP_XY) &&
|
bool can_hw_rotate = display_has_capability(kernel_display_device, DISPLAY_CAPABILITY_CAP_SWAP_XY) &&
|
||||||
display_has_capability(kernel_display_device, DISPLAY_CAPABILITY_CAP_MIRROR);
|
display_has_capability(kernel_display_device, DISPLAY_CAPABILITY_CAP_MIRROR);
|
||||||
|
bool prefer_external_ram_buffer = display_has_capability(kernel_display_device, DISPLAY_CAPABILITY_PREFER_EXTERNAL_RAM);
|
||||||
struct LvglDisplayConfig lvgl_display_config = {
|
struct LvglDisplayConfig lvgl_display_config = {
|
||||||
.buffer_height = (uint16_t)(vres > 10 ? vres / 10 : vres),
|
.buffer_height = (uint16_t)(vres > 10 ? vres / 10 : vres),
|
||||||
.sw_rotate = !can_hw_rotate,
|
.sw_rotate = !can_hw_rotate,
|
||||||
.swap_bytes = swap_bytes,
|
.swap_bytes = swap_bytes,
|
||||||
.force_full_frame = display_requires_full_frame
|
.force_full_frame = display_requires_full_frame,
|
||||||
|
.prefer_external_ram = prefer_external_ram_buffer
|
||||||
};
|
};
|
||||||
lv_disp_t* added_display = NULL;
|
lv_disp_t* added_display = NULL;
|
||||||
if (lvgl_display_add(kernel_display_device, &lvgl_display_config, &added_display) == ERROR_NONE) {
|
if (lvgl_display_add(kernel_display_device, &lvgl_display_config, &added_display) == ERROR_NONE) {
|
||||||
|
|||||||
@ -59,13 +59,18 @@ struct LvglDisplayCtx {
|
|||||||
bool byte_swap;
|
bool byte_swap;
|
||||||
};
|
};
|
||||||
|
|
||||||
static void* lvgl_display_alloc_buffer(size_t size_bytes) {
|
static void* lvgl_display_alloc_buffer(size_t size_bytes, bool prefer_external_ram) {
|
||||||
#ifdef ESP_PLATFORM
|
#ifdef ESP_PLATFORM
|
||||||
// Must match LV_DRAW_BUF_ALIGN (can be > 4 - e.g. 64, tied to the cache line size for
|
// Must match LV_DRAW_BUF_ALIGN (can be > 4 - e.g. 64, tied to the cache line size for
|
||||||
// DMA2D/PPA coherency on some targets - see sdkconfig's CONFIG_LV_DRAW_BUF_ALIGN). A buffer
|
// DMA2D/PPA coherency on some targets - see sdkconfig's CONFIG_LV_DRAW_BUF_ALIGN). A buffer
|
||||||
// allocated less strictly than that fails lv_display_set_buffers()'s alignment assert, which
|
// allocated less strictly than that fails lv_display_set_buffers()'s alignment assert, which
|
||||||
// is configured to LV_ASSERT_HANDLER (while(1);) rather than a clean abort - i.e. a silent hang.
|
// is configured to LV_ASSERT_HANDLER (while(1);) rather than a clean abort - i.e. a silent hang.
|
||||||
void* buf = heap_caps_aligned_alloc(LV_DRAW_BUF_ALIGN, size_bytes, MALLOC_CAP_DMA | MALLOC_CAP_8BIT);
|
// MALLOC_CAP_DMA is scarce internal RAM - skip it for displays that don't DMA directly from
|
||||||
|
// this buffer (see prefer_external_ram_buffer). Dropping MALLOC_CAP_DMA alone isn't enough to
|
||||||
|
// land in PSRAM though: MALLOC_CAP_8BIT alone is still satisfied by internal RAM, so
|
||||||
|
// MALLOC_CAP_SPIRAM must be requested explicitly (confirmed on real hardware).
|
||||||
|
uint32_t caps = prefer_external_ram ? (MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT) : (MALLOC_CAP_DMA | MALLOC_CAP_8BIT);
|
||||||
|
void* buf = heap_caps_aligned_alloc(LV_DRAW_BUF_ALIGN, size_bytes, caps);
|
||||||
if (buf == NULL) {
|
if (buf == NULL) {
|
||||||
buf = heap_caps_aligned_alloc(LV_DRAW_BUF_ALIGN, size_bytes, MALLOC_CAP_DEFAULT);
|
buf = heap_caps_aligned_alloc(LV_DRAW_BUF_ALIGN, size_bytes, MALLOC_CAP_DEFAULT);
|
||||||
}
|
}
|
||||||
@ -104,6 +109,13 @@ static bool lvgl_display_map_color_format(enum DisplayColorFormat in, lv_color_f
|
|||||||
// (e.g. ssd1306_draw_bitmap()'s row-to-page transpose).
|
// (e.g. ssd1306_draw_bitmap()'s row-to-page transpose).
|
||||||
*out = LV_COLOR_FORMAT_I1;
|
*out = LV_COLOR_FORMAT_I1;
|
||||||
return true;
|
return true;
|
||||||
|
case DISPLAY_COLOR_FORMAT_GRAYSCALE8:
|
||||||
|
// Row-major, 1 byte/pixel luminance (0x00=black, 0xFF=white) - matches LV_COLOR_FORMAT_L8
|
||||||
|
// directly, no repacking needed. Deliberately NOT routed through the I1 branch below in
|
||||||
|
// lvgl_display_add(): I1 is hardcoded to LV_DISPLAY_RENDER_MODE_FULL there, which is what
|
||||||
|
// this format exists to avoid for panels that want real partial/tile updates.
|
||||||
|
*out = LV_COLOR_FORMAT_L8;
|
||||||
|
return true;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -396,7 +408,7 @@ error_t lvgl_display_add(struct Device* device, const struct LvglDisplayConfig*
|
|||||||
// buffer's start (see lvgl_display_flush_cb()). Always redraw the whole frame in one
|
// buffer's start (see lvgl_display_flush_cb()). Always redraw the whole frame in one
|
||||||
// owned buffer instead of computing partial-region byte offsets against that packing.
|
// owned buffer instead of computing partial-region byte offsets against that packing.
|
||||||
buf_size_bytes = (size_t)((hres + 7) / 8) * vres + 8;
|
buf_size_bytes = (size_t)((hres + 7) / 8) * vres + 8;
|
||||||
ctx->buf1 = lvgl_display_alloc_buffer(buf_size_bytes);
|
ctx->buf1 = lvgl_display_alloc_buffer(buf_size_bytes, config->prefer_external_ram);
|
||||||
if (ctx->buf1 == NULL) {
|
if (ctx->buf1 == NULL) {
|
||||||
delete wrapper;
|
delete wrapper;
|
||||||
return ERROR_OUT_OF_MEMORY;
|
return ERROR_OUT_OF_MEMORY;
|
||||||
@ -416,13 +428,13 @@ error_t lvgl_display_add(struct Device* device, const struct LvglDisplayConfig*
|
|||||||
? vres : config->buffer_height;
|
? vres : config->buffer_height;
|
||||||
buf_size_bytes = (size_t)hres * buf_height * bpp;
|
buf_size_bytes = (size_t)hres * buf_height * bpp;
|
||||||
|
|
||||||
ctx->buf1 = lvgl_display_alloc_buffer(buf_size_bytes);
|
ctx->buf1 = lvgl_display_alloc_buffer(buf_size_bytes, config->prefer_external_ram);
|
||||||
if (ctx->buf1 == NULL) {
|
if (ctx->buf1 == NULL) {
|
||||||
delete wrapper;
|
delete wrapper;
|
||||||
return ERROR_OUT_OF_MEMORY;
|
return ERROR_OUT_OF_MEMORY;
|
||||||
}
|
}
|
||||||
if (config->double_buffer) {
|
if (config->double_buffer) {
|
||||||
ctx->buf2 = lvgl_display_alloc_buffer(buf_size_bytes);
|
ctx->buf2 = lvgl_display_alloc_buffer(buf_size_bytes, config->prefer_external_ram);
|
||||||
if (ctx->buf2 == NULL) {
|
if (ctx->buf2 == NULL) {
|
||||||
lvgl_display_free_buffer(ctx->buf1);
|
lvgl_display_free_buffer(ctx->buf1);
|
||||||
delete wrapper;
|
delete wrapper;
|
||||||
@ -436,7 +448,7 @@ error_t lvgl_display_add(struct Device* device, const struct LvglDisplayConfig*
|
|||||||
ctx->buf_size_bytes = buf_size_bytes;
|
ctx->buf_size_bytes = buf_size_bytes;
|
||||||
|
|
||||||
if (ctx->sw_rotate) {
|
if (ctx->sw_rotate) {
|
||||||
ctx->rotate_buf = lvgl_display_alloc_buffer(buf_size_bytes);
|
ctx->rotate_buf = lvgl_display_alloc_buffer(buf_size_bytes, config->prefer_external_ram);
|
||||||
if (ctx->rotate_buf == NULL) {
|
if (ctx->rotate_buf == NULL) {
|
||||||
if (ctx->owns_buffers) {
|
if (ctx->owns_buffers) {
|
||||||
lvgl_display_free_buffer(ctx->buf1);
|
lvgl_display_free_buffer(ctx->buf1);
|
||||||
|
|||||||
@ -108,7 +108,16 @@ static uint32_t hid_keycode_to_key(uint8_t modifier, uint8_t key_code,
|
|||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctrl || alt) return 0;
|
/*
|
||||||
|
* Ctrl and Alt no longer suppress the key.
|
||||||
|
*
|
||||||
|
* They used to return 0 here, which meant a chord like Ctrl+C produced nothing at all and a
|
||||||
|
* terminal application could never see it. The modifiers are now reported alongside the key in
|
||||||
|
* UsbHidEvent instead, so the plain character still comes through and a consumer that wants a
|
||||||
|
* control code derives it. Alt is passed through on the same basis.
|
||||||
|
*/
|
||||||
|
(void)ctrl;
|
||||||
|
(void)alt;
|
||||||
|
|
||||||
if (key_code < (sizeof(keycode2ascii) / sizeof(keycode2ascii[0]))) {
|
if (key_code < (sizeof(keycode2ascii) / sizeof(keycode2ascii[0]))) {
|
||||||
bool is_letter = (key_code >= 0x04 && key_code <= 0x1D);
|
bool is_letter = (key_code >= 0x04 && key_code <= 0x1D);
|
||||||
@ -157,6 +166,10 @@ static void hid_interface_callback(hid_host_device_handle_t handle,
|
|||||||
if (params.proto == HID_PROTOCOL_KEYBOARD) {
|
if (params.proto == HID_PROTOCOL_KEYBOARD) {
|
||||||
if (data_len < sizeof(hid_keyboard_input_report_boot_t)) break;
|
if (data_len < sizeof(hid_keyboard_input_report_boot_t)) break;
|
||||||
auto* kb = reinterpret_cast<const hid_keyboard_input_report_boot_t*>(data);
|
auto* kb = reinterpret_cast<const hid_keyboard_input_report_boot_t*>(data);
|
||||||
|
const bool with_ctrl = (kb->modifier.val & HID_LEFT_CONTROL) ||
|
||||||
|
(kb->modifier.val & HID_RIGHT_CONTROL);
|
||||||
|
const bool with_alt = (kb->modifier.val & HID_LEFT_ALT) ||
|
||||||
|
(kb->modifier.val & HID_RIGHT_ALT);
|
||||||
|
|
||||||
for (int i = 0; i < HID_KEYBOARD_KEY_MAX; i++) {
|
for (int i = 0; i < HID_KEYBOARD_KEY_MAX; i++) {
|
||||||
uint8_t prev_hid = ctx->prev_keys[i];
|
uint8_t prev_hid = ctx->prev_keys[i];
|
||||||
@ -169,7 +182,7 @@ static void hid_interface_callback(hid_host_device_handle_t handle,
|
|||||||
uint32_t lv_key = ctx->pressed_lv_keys[prev_hid];
|
uint32_t lv_key = ctx->pressed_lv_keys[prev_hid];
|
||||||
ctx->pressed_lv_keys[prev_hid] = 0;
|
ctx->pressed_lv_keys[prev_hid] = 0;
|
||||||
if (lv_key) {
|
if (lv_key) {
|
||||||
UsbHidEvent evt = { .type = USB_HID_EVENT_KEY, .key = { lv_key, false } };
|
UsbHidEvent evt = { .type = USB_HID_EVENT_KEY, .key = { lv_key, false, with_ctrl, with_alt } };
|
||||||
publish_event(ctx, &evt);
|
publish_event(ctx, &evt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -208,7 +221,10 @@ static void hid_interface_callback(hid_host_device_handle_t handle,
|
|||||||
uint32_t lv_key = hid_keycode_to_key(kb->modifier.val, hid_code,
|
uint32_t lv_key = hid_keycode_to_key(kb->modifier.val, hid_code,
|
||||||
ctx->caps_lock_active, ctx->num_lock_active);
|
ctx->caps_lock_active, ctx->num_lock_active);
|
||||||
if (lv_key) {
|
if (lv_key) {
|
||||||
UsbHidEvent evt = { .type = USB_HID_EVENT_KEY, .key = { lv_key, true } };
|
UsbHidEvent evt = {
|
||||||
|
.type = USB_HID_EVENT_KEY,
|
||||||
|
.key = { lv_key, true, with_ctrl, with_alt }
|
||||||
|
};
|
||||||
publish_event(ctx, &evt);
|
publish_event(ctx, &evt);
|
||||||
ctx->pressed_lv_keys[hid_code] = lv_key;
|
ctx->pressed_lv_keys[hid_code] = lv_key;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,9 +3,18 @@
|
|||||||
|
|
||||||
#include <symbols/esp_http_client.h>
|
#include <symbols/esp_http_client.h>
|
||||||
|
|
||||||
|
#include <sdkconfig.h>
|
||||||
#include <esp_http_client.h>
|
#include <esp_http_client.h>
|
||||||
|
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
|
||||||
|
#include <esp_crt_bundle.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
const esp_elfsym esp_http_client_symbols[] = {
|
const esp_elfsym esp_http_client_symbols[] = {
|
||||||
|
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
|
||||||
|
// Needed for HTTPS: an app passes this as crt_bundle_attach to validate certificates against
|
||||||
|
// the bundle already compiled into the firmware (CONFIG_MBEDTLS_CERTIFICATE_BUNDLE).
|
||||||
|
ESP_ELFSYM_EXPORT(esp_crt_bundle_attach),
|
||||||
|
#endif
|
||||||
ESP_ELFSYM_EXPORT(esp_http_client_init),
|
ESP_ELFSYM_EXPORT(esp_http_client_init),
|
||||||
ESP_ELFSYM_EXPORT(esp_http_client_perform),
|
ESP_ELFSYM_EXPORT(esp_http_client_perform),
|
||||||
ESP_ELFSYM_EXPORT(esp_http_client_cancel_request),
|
ESP_ELFSYM_EXPORT(esp_http_client_cancel_request),
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
#include <sdkconfig.h>
|
||||||
#include <private/elf_symbol.h>
|
#include <private/elf_symbol.h>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
|
||||||
@ -40,6 +41,7 @@ const esp_elfsym freertos_symbols[] = {
|
|||||||
ESP_ELFSYM_EXPORT(xTaskGenericNotify),
|
ESP_ELFSYM_EXPORT(xTaskGenericNotify),
|
||||||
ESP_ELFSYM_EXPORT(xTaskGenericNotifyFromISR),
|
ESP_ELFSYM_EXPORT(xTaskGenericNotifyFromISR),
|
||||||
ESP_ELFSYM_EXPORT(ulTaskGenericNotifyTake),
|
ESP_ELFSYM_EXPORT(ulTaskGenericNotifyTake),
|
||||||
|
ESP_ELFSYM_EXPORT(xTaskGetCurrentTaskHandle),
|
||||||
ESP_ELFSYM_EXPORT(xTaskGetTickCount),
|
ESP_ELFSYM_EXPORT(xTaskGetTickCount),
|
||||||
ESP_ELFSYM_EXPORT(xTaskGetTickCountFromISR),
|
ESP_ELFSYM_EXPORT(xTaskGetTickCountFromISR),
|
||||||
ESP_ELFSYM_EXPORT(pvTaskGetThreadLocalStoragePointer),
|
ESP_ELFSYM_EXPORT(pvTaskGetThreadLocalStoragePointer),
|
||||||
@ -101,6 +103,12 @@ const esp_elfsym freertos_symbols[] = {
|
|||||||
ESP_ELFSYM_EXPORT(vPortYield),
|
ESP_ELFSYM_EXPORT(vPortYield),
|
||||||
ESP_ELFSYM_EXPORT(vPortEnterCritical),
|
ESP_ELFSYM_EXPORT(vPortEnterCritical),
|
||||||
ESP_ELFSYM_EXPORT(vPortExitCritical),
|
ESP_ELFSYM_EXPORT(vPortExitCritical),
|
||||||
|
#if defined(CONFIG_IDF_TARGET_ESP32P4) || defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||||
|
ESP_ELFSYM_EXPORT(xPortEnterCriticalTimeout),
|
||||||
|
#endif
|
||||||
|
#if defined(CONFIG_IDF_TARGET_ESP32P4)
|
||||||
|
ESP_ELFSYM_EXPORT(vPortExitCriticalMultiCore),
|
||||||
|
#endif
|
||||||
ESP_ELFSYM_EXPORT(xPortInIsrContext),
|
ESP_ELFSYM_EXPORT(xPortInIsrContext),
|
||||||
ESP_ELFSYM_EXPORT(xPortCanYield),
|
ESP_ELFSYM_EXPORT(xPortCanYield),
|
||||||
ESP_ELFSYM_EXPORT(xPortGetCoreID),
|
ESP_ELFSYM_EXPORT(xPortGetCoreID),
|
||||||
|
|||||||
@ -33,6 +33,7 @@
|
|||||||
#include <esp_heap_caps.h>
|
#include <esp_heap_caps.h>
|
||||||
#include <esp_timer.h>
|
#include <esp_timer.h>
|
||||||
#include <esp_system.h>
|
#include <esp_system.h>
|
||||||
|
#include <esp_vfs.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <lwip/sockets.h>
|
#include <lwip/sockets.h>
|
||||||
#include <lwip/netdb.h>
|
#include <lwip/netdb.h>
|
||||||
@ -89,6 +90,7 @@ const esp_elfsym main_symbols[] {
|
|||||||
ESP_ELFSYM_EXPORT(close),
|
ESP_ELFSYM_EXPORT(close),
|
||||||
ESP_ELFSYM_EXPORT(rmdir),
|
ESP_ELFSYM_EXPORT(rmdir),
|
||||||
ESP_ELFSYM_EXPORT(unlink),
|
ESP_ELFSYM_EXPORT(unlink),
|
||||||
|
ESP_ELFSYM_EXPORT(open),
|
||||||
// strings.h
|
// strings.h
|
||||||
ESP_ELFSYM_EXPORT(explicit_bzero),
|
ESP_ELFSYM_EXPORT(explicit_bzero),
|
||||||
ESP_ELFSYM_EXPORT(strcasecmp),
|
ESP_ELFSYM_EXPORT(strcasecmp),
|
||||||
@ -194,6 +196,10 @@ const esp_elfsym main_symbols[] {
|
|||||||
ESP_ELFSYM_EXPORT(fgets),
|
ESP_ELFSYM_EXPORT(fgets),
|
||||||
ESP_ELFSYM_EXPORT(fopen),
|
ESP_ELFSYM_EXPORT(fopen),
|
||||||
ESP_ELFSYM_EXPORT(freopen),
|
ESP_ELFSYM_EXPORT(freopen),
|
||||||
|
// Lets an app find the descriptor behind a stream. Needed when stdin/stdout have been pointed
|
||||||
|
// somewhere other than descriptors 0 and 1, which is the case for an app that owns a terminal.
|
||||||
|
ESP_ELFSYM_EXPORT(fileno),
|
||||||
|
ESP_ELFSYM_EXPORT(setvbuf),
|
||||||
ESP_ELFSYM_EXPORT(fputc),
|
ESP_ELFSYM_EXPORT(fputc),
|
||||||
ESP_ELFSYM_EXPORT(fputs),
|
ESP_ELFSYM_EXPORT(fputs),
|
||||||
ESP_ELFSYM_EXPORT(fprintf),
|
ESP_ELFSYM_EXPORT(fprintf),
|
||||||
@ -239,6 +245,7 @@ const esp_elfsym main_symbols[] {
|
|||||||
ESP_ELFSYM_EXPORT(memchr),
|
ESP_ELFSYM_EXPORT(memchr),
|
||||||
ESP_ELFSYM_EXPORT(memmove),
|
ESP_ELFSYM_EXPORT(memmove),
|
||||||
ESP_ELFSYM_EXPORT(strdup),
|
ESP_ELFSYM_EXPORT(strdup),
|
||||||
|
ESP_ELFSYM_EXPORT(stpcpy),
|
||||||
|
|
||||||
// ctype
|
// ctype
|
||||||
ESP_ELFSYM_EXPORT(isalnum),
|
ESP_ELFSYM_EXPORT(isalnum),
|
||||||
@ -256,6 +263,13 @@ const esp_elfsym main_symbols[] {
|
|||||||
ESP_ELFSYM_EXPORT(toupper),
|
ESP_ELFSYM_EXPORT(toupper),
|
||||||
// ESP-IDF
|
// ESP-IDF
|
||||||
ESP_ELFSYM_EXPORT(esp_log),
|
ESP_ELFSYM_EXPORT(esp_log),
|
||||||
|
// Lets an app that has taken over the display quieten firmware logging: with stdout redirected
|
||||||
|
// to a terminal the app owns, anything logged appears on screen as if the app had printed it.
|
||||||
|
ESP_ELFSYM_EXPORT(esp_log_level_set),
|
||||||
|
// Lets an app redirect firmware logging somewhere other than stdout. An app that has pointed
|
||||||
|
// stdout at its own terminal needs this: without it, every ESP_LOG from any component in the
|
||||||
|
// system - NTP, RTC, TLS - paints over the app's display.
|
||||||
|
ESP_ELFSYM_EXPORT(esp_log_set_vprintf),
|
||||||
ESP_ELFSYM_EXPORT(esp_log_write),
|
ESP_ELFSYM_EXPORT(esp_log_write),
|
||||||
ESP_ELFSYM_EXPORT(esp_log_timestamp),
|
ESP_ELFSYM_EXPORT(esp_log_timestamp),
|
||||||
ESP_ELFSYM_EXPORT(esp_err_to_name),
|
ESP_ELFSYM_EXPORT(esp_err_to_name),
|
||||||
@ -327,9 +341,26 @@ const esp_elfsym main_symbols[] {
|
|||||||
ESP_ELFSYM_EXPORT(ipaddr_addr),
|
ESP_ELFSYM_EXPORT(ipaddr_addr),
|
||||||
// POSIX socket names (VFS wrappers used when apps include <sys/socket.h>)
|
// POSIX socket names (VFS wrappers used when apps include <sys/socket.h>)
|
||||||
ESP_ELFSYM_EXPORT(select),
|
ESP_ELFSYM_EXPORT(select),
|
||||||
|
// stdlib.h - environment and sorting
|
||||||
|
ESP_ELFSYM_EXPORT(getenv),
|
||||||
|
ESP_ELFSYM_EXPORT(setenv),
|
||||||
|
ESP_ELFSYM_EXPORT(unsetenv),
|
||||||
|
ESP_ELFSYM_EXPORT(qsort),
|
||||||
|
// unistd.h
|
||||||
|
ESP_ELFSYM_EXPORT(access),
|
||||||
|
ESP_ELFSYM_EXPORT(isatty),
|
||||||
|
ESP_ELFSYM_EXPORT(read),
|
||||||
|
ESP_ELFSYM_EXPORT(write),
|
||||||
|
ESP_ELFSYM_EXPORT(lseek),
|
||||||
// sys/stat.h
|
// sys/stat.h
|
||||||
ESP_ELFSYM_EXPORT(stat),
|
ESP_ELFSYM_EXPORT(stat),
|
||||||
ESP_ELFSYM_EXPORT(mkdir),
|
ESP_ELFSYM_EXPORT(mkdir),
|
||||||
|
// esp_vfs.h - lets an app register a device node (e.g. a terminal at /dev/...) so that plain
|
||||||
|
// printf/stdout reaches it. Note the registration is global and outlives the app unless it
|
||||||
|
// unregisters: an app that takes this must release it on shutdown, or the next one inherits a
|
||||||
|
// path whose callbacks point into unloaded memory.
|
||||||
|
ESP_ELFSYM_EXPORT(esp_vfs_register),
|
||||||
|
ESP_ELFSYM_EXPORT(esp_vfs_unregister),
|
||||||
// esp_netif.h
|
// esp_netif.h
|
||||||
ESP_ELFSYM_EXPORT(esp_netif_get_ip_info),
|
ESP_ELFSYM_EXPORT(esp_netif_get_ip_info),
|
||||||
ESP_ELFSYM_EXPORT(esp_netif_get_handle_from_ifkey),
|
ESP_ELFSYM_EXPORT(esp_netif_get_handle_from_ifkey),
|
||||||
@ -360,6 +391,19 @@ const esp_elfsym main_symbols[] {
|
|||||||
ESP_ELFSYM_EXPORT(tinfl_decompress),
|
ESP_ELFSYM_EXPORT(tinfl_decompress),
|
||||||
ESP_ELFSYM_EXPORT(tinfl_decompress_mem_to_callback),
|
ESP_ELFSYM_EXPORT(tinfl_decompress_mem_to_callback),
|
||||||
ESP_ELFSYM_EXPORT(tinfl_decompress_mem_to_mem),
|
ESP_ELFSYM_EXPORT(tinfl_decompress_mem_to_mem),
|
||||||
|
// Compression, the counterpart to the tinfl_* decompression above. Like those, these live in
|
||||||
|
// the chip's ROM rather than in flash, so exporting them costs nothing.
|
||||||
|
//
|
||||||
|
// Note this is miniz, not zlib: ESP-IDF builds it with MINIZ_NO_ZLIB_APIS, so deflate/inflate,
|
||||||
|
// crc32 and the gz* file API do not exist anywhere in the firmware. An app wanting gzip files
|
||||||
|
// has to use the tdefl_/tinfl_ interfaces directly, or vendor zlib itself.
|
||||||
|
ESP_ELFSYM_EXPORT(tdefl_init),
|
||||||
|
ESP_ELFSYM_EXPORT(tdefl_compress),
|
||||||
|
ESP_ELFSYM_EXPORT(tdefl_compress_buffer),
|
||||||
|
ESP_ELFSYM_EXPORT(tdefl_compress_mem_to_mem),
|
||||||
|
ESP_ELFSYM_EXPORT(tdefl_compress_mem_to_output),
|
||||||
|
ESP_ELFSYM_EXPORT(tdefl_get_adler32),
|
||||||
|
ESP_ELFSYM_EXPORT(tdefl_get_prev_return_status),
|
||||||
// ledc
|
// ledc
|
||||||
ESP_ELFSYM_EXPORT(ledc_update_duty),
|
ESP_ELFSYM_EXPORT(ledc_update_duty),
|
||||||
ESP_ELFSYM_EXPORT(ledc_set_freq),
|
ESP_ELFSYM_EXPORT(ledc_set_freq),
|
||||||
|
|||||||
@ -23,7 +23,13 @@ enum DisplayCapability {
|
|||||||
DISPLAY_CAPABILITY_SLEEP = 1 << 6,
|
DISPLAY_CAPABILITY_SLEEP = 1 << 6,
|
||||||
DISPLAY_CAPABILITY_REQUIRES_FULL_FRAME = 1 << 7,
|
DISPLAY_CAPABILITY_REQUIRES_FULL_FRAME = 1 << 7,
|
||||||
/** Can be used by e-paper with pointer devices for long click timing changes. */
|
/** Can be used by e-paper with pointer devices for long click timing changes. */
|
||||||
DISPLAY_CAPABILITY_SLOW_REFRESH = 1 << 8
|
DISPLAY_CAPABILITY_SLOW_REFRESH = 1 << 8,
|
||||||
|
/**
|
||||||
|
* Promise that draw_bitmap() never DMAs directly from the color_data pointer it's given (e.g.
|
||||||
|
* it copies/converts into its own buffer first). Lets the LVGL bridge allocate this display's
|
||||||
|
* draw buffer(s) from non-DMA-capable memory instead of forcing scarce internal RAM.
|
||||||
|
*/
|
||||||
|
DISPLAY_CAPABILITY_PREFER_EXTERNAL_RAM = 1 << 9
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -36,6 +42,11 @@ enum DisplayColorFormat {
|
|||||||
DISPLAY_COLOR_FORMAT_RGB565 = 0x3,
|
DISPLAY_COLOR_FORMAT_RGB565 = 0x3,
|
||||||
DISPLAY_COLOR_FORMAT_RGB565_SWAPPED = 0x4,
|
DISPLAY_COLOR_FORMAT_RGB565_SWAPPED = 0x4,
|
||||||
DISPLAY_COLOR_FORMAT_RGB888 = 0x5,
|
DISPLAY_COLOR_FORMAT_RGB888 = 0x5,
|
||||||
|
// 8 bpp luminance, 0x00 = black, 0xFF = white (matches LVGL's LV_COLOR_FORMAT_L8). Unlike
|
||||||
|
// MONOCHROME, the LVGL bridge does not force full-frame rendering for this format, so drivers
|
||||||
|
// that want real partial/tile updates (e.g. grayscale e-paper panels) should report this
|
||||||
|
// instead of MONOCHROME even if they intend to threshold down to black/white themselves.
|
||||||
|
DISPLAY_COLOR_FORMAT_GRAYSCALE8 = 0x6,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -24,6 +24,25 @@ struct KeyboardKeyData {
|
|||||||
* immediately to drain it. False if this was the last pending event.
|
* immediately to drain it. False if this was the last pending event.
|
||||||
*/
|
*/
|
||||||
bool continue_reading;
|
bool continue_reading;
|
||||||
|
/**
|
||||||
|
* @brief True if Ctrl was held when this key was pressed.
|
||||||
|
*
|
||||||
|
* Reported separately rather than folded into `key` because the two encodings collide: the C0
|
||||||
|
* control codes a terminal expects for Ctrl chords (Ctrl+C is 0x03, Ctrl+K is 0x0B, ...) overlap
|
||||||
|
* the LVGL key constants drivers emit in the same field (LV_KEY_END is 3, LV_KEY_PREV is 11,
|
||||||
|
* LV_KEY_UP is 17, ...), so a single uint32_t cannot express both. Consumers that want control
|
||||||
|
* codes derive them here, e.g.
|
||||||
|
* `((key >= 'a' && key <= 'z') || (key >= 'A' && key <= 'Z')) ? (key & 0x1F) : key`
|
||||||
|
* when ctrl is set.
|
||||||
|
*
|
||||||
|
* Drivers whose hardware cannot report Ctrl leave this false.
|
||||||
|
*/
|
||||||
|
bool ctrl;
|
||||||
|
/**
|
||||||
|
* @brief True if Alt was held when this key was pressed. See ctrl for why modifiers are reported
|
||||||
|
* separately. Drivers whose hardware cannot report Alt leave this false.
|
||||||
|
*/
|
||||||
|
bool alt;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -43,7 +43,18 @@ typedef enum {
|
|||||||
typedef struct {
|
typedef struct {
|
||||||
UsbHidEventType type;
|
UsbHidEventType type;
|
||||||
union {
|
union {
|
||||||
struct { uint32_t key_code; bool pressed; } key;
|
/**
|
||||||
|
* @brief A key press or release.
|
||||||
|
*
|
||||||
|
* `ctrl` and `alt` report the modifiers separately rather than folding them into
|
||||||
|
* `key_code`, because the two encodings collide: the C0 control codes a terminal expects
|
||||||
|
* for Ctrl chords (Ctrl+C is 0x03, Ctrl+K is 0x0B) overlap the UsbHidKey constants above
|
||||||
|
* (USB_HID_KEY_END is 3, USB_HID_KEY_PREV is 11). A consumer wanting control codes derives
|
||||||
|
* them here, e.g.
|
||||||
|
* `((key_code >= 'a' && key_code <= 'z') || (key_code >= 'A' && key_code <= 'Z')) ?
|
||||||
|
* (key_code & 0x1F) : key_code`.
|
||||||
|
*/
|
||||||
|
struct { uint32_t key_code; bool pressed; bool ctrl; bool alt; } key;
|
||||||
struct { int32_t dx; int32_t dy; } mouse_move;
|
struct { int32_t dx; int32_t dy; } mouse_move;
|
||||||
struct { bool button1; bool button2; } mouse_btn;
|
struct { bool button1; bool button2; } mouse_btn;
|
||||||
struct { int32_t delta; } scroll;
|
struct { int32_t delta; } scroll;
|
||||||
|
|||||||
@ -9,6 +9,12 @@ extern "C" {
|
|||||||
|
|
||||||
error_t keyboard_read_key(Device* device, KeyboardKeyData* data) {
|
error_t keyboard_read_key(Device* device, KeyboardKeyData* data) {
|
||||||
const auto* driver = device_get_driver(device);
|
const auto* driver = device_get_driver(device);
|
||||||
|
|
||||||
|
// Default the modifier fields here rather than in each driver: only drivers whose hardware can
|
||||||
|
// report modifiers set them, and the rest would otherwise leave whatever the caller's stack held.
|
||||||
|
data->ctrl = false;
|
||||||
|
data->alt = false;
|
||||||
|
|
||||||
return KEYBOARD_DRIVER_API(driver)->read_key(device, data);
|
return KEYBOARD_DRIVER_API(driver)->read_key(device, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -171,6 +171,7 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = {
|
|||||||
DEFINE_MODULE_SYMBOL(file_system_unmount),
|
DEFINE_MODULE_SYMBOL(file_system_unmount),
|
||||||
DEFINE_MODULE_SYMBOL(file_system_is_mounted),
|
DEFINE_MODULE_SYMBOL(file_system_is_mounted),
|
||||||
DEFINE_MODULE_SYMBOL(file_system_get_path),
|
DEFINE_MODULE_SYMBOL(file_system_get_path),
|
||||||
|
DEFINE_MODULE_SYMBOL(file_system_for_each),
|
||||||
// memory
|
// memory
|
||||||
DEFINE_MODULE_SYMBOL(MEMORY_POLICY_DEFAULT),
|
DEFINE_MODULE_SYMBOL(MEMORY_POLICY_DEFAULT),
|
||||||
DEFINE_MODULE_SYMBOL(memory_print_stats),
|
DEFINE_MODULE_SYMBOL(memory_print_stats),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user