mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-08-22 01:45:06 +00:00
Compare commits
No commits in common. "d2c69ee7e859843537410562ea68f8f3e5c87c73" and "d72d5a0ef2cbb5fd958bb8cb0791849d39c1b9c0" have entirely different histories.
d2c69ee7e8
...
d72d5a0ef2
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,8 +1,10 @@
|
|||||||
.idea/
|
.idea/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
build*/
|
build/
|
||||||
cmake*/
|
buildsim/
|
||||||
|
build-*/
|
||||||
|
cmake-*/
|
||||||
CMakeCache.txt
|
CMakeCache.txt
|
||||||
*.cbp
|
*.cbp
|
||||||
CMakeFiles
|
CMakeFiles
|
||||||
|
|||||||
@ -200,7 +200,6 @@ def main():
|
|||||||
add_module(target_path, "lvgl-module")
|
add_module(target_path, "lvgl-module")
|
||||||
add_module(target_path, "crypt-module")
|
add_module(target_path, "crypt-module")
|
||||||
add_module(target_path, "gps-module")
|
add_module(target_path, "gps-module")
|
||||||
add_module(target_path, "service-module")
|
|
||||||
|
|
||||||
# Drivers - only ones actually built for this target (chip-restricted drivers like
|
# Drivers - only ones actually built for this target (chip-restricted drivers like
|
||||||
# sc2356-module won't have a .a outside ESP32-P4)
|
# sc2356-module won't have a .a outside ESP32-P4)
|
||||||
|
|||||||
@ -98,7 +98,6 @@ if (NOT DEFINED ENV{ESP_IDF_VERSION})
|
|||||||
add_subdirectory(Modules/lvgl-module)
|
add_subdirectory(Modules/lvgl-module)
|
||||||
add_subdirectory(Modules/crypt-module)
|
add_subdirectory(Modules/crypt-module)
|
||||||
add_subdirectory(Modules/gps-module)
|
add_subdirectory(Modules/gps-module)
|
||||||
add_subdirectory(Modules/service-module)
|
|
||||||
add_subdirectory(Drivers/gps-generic-module)
|
add_subdirectory(Drivers/gps-generic-module)
|
||||||
add_subdirectory(Drivers/gps-meshtastic-module)
|
add_subdirectory(Drivers/gps-meshtastic-module)
|
||||||
|
|
||||||
|
|||||||
@ -3,5 +3,5 @@ file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
|
|||||||
idf_component_register(
|
idf_component_register(
|
||||||
SRCS ${SOURCE_FILES}
|
SRCS ${SOURCE_FILES}
|
||||||
INCLUDE_DIRS "Source"
|
INCLUDE_DIRS "Source"
|
||||||
REQUIRES TactilityKernel lvgl-module ina226-module ili9881c-module st7121-module st7123-module gt911-module
|
REQUIRES TactilityKernel lvgl-module ina226-module ili9881c-module st7123-module gt911-module
|
||||||
)
|
)
|
||||||
|
|||||||
@ -21,34 +21,6 @@ void tab5_set_variant(Tab5Variant variant) {
|
|||||||
detected_variant = variant;
|
detected_variant = variant;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The ST7123 (V2) and ST7121 (V3) touch controllers share the same fixed I2C address, so presence
|
|
||||||
// alone doesn't distinguish them. Both expose a firmware-version byte at register 0x0000 (a 16-bit
|
|
||||||
// register address, per ESP_LCD_TOUCH_IO_I2C_ST7123_CONFIG's lcd_cmd_bits=16 - see the M5Tab5
|
|
||||||
// UserDemo's bsp_detect_display_type()): fw_version 1 means ST7121/V3, fw_version 3 means
|
|
||||||
// ST7123/V2. Returns false (leaving *out_variant untouched) if the register read fails or reports
|
|
||||||
// an unrecognized value, so the caller's outer attempt loop can retry rather than the touch IC's
|
|
||||||
// transient not-finished-booting state permanently misdetecting V3 hardware as V2.
|
|
||||||
static bool probe_st7123_or_st7121_variant(Device* i2c0, TickType_t timeout, Tab5Variant* out_variant) {
|
|
||||||
const uint8_t reg_addr[2] = {0x00, 0x00};
|
|
||||||
uint8_t fw_version = 0;
|
|
||||||
if (i2c_controller_write_read(i2c0, ESP_LCD_TOUCH_IO_I2C_ST7123_ADDRESS, reg_addr, sizeof(reg_addr), &fw_version, 1, timeout) != ERROR_NONE) {
|
|
||||||
LOG_W(TAG, "display_detect: failed to read touch FW version, retrying");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (fw_version == 1) {
|
|
||||||
LOG_I(TAG, "display_detect: detected ST7121 touch (FW version 1) — using variant V3");
|
|
||||||
*out_variant = Tab5Variant::V3;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (fw_version == 3) {
|
|
||||||
LOG_I(TAG, "display_detect: detected ST7123 touch (FW version 3) — using variant V2");
|
|
||||||
*out_variant = Tab5Variant::V2;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
LOG_W(TAG, "display_detect: touch at ST7123 address reported unknown FW version %u, retrying", fw_version);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Tab5Variant tab5_probe_variant(Device* i2c0) {
|
Tab5Variant tab5_probe_variant(Device* i2c0) {
|
||||||
// Allow time for the touch IC to fully boot after the reset pulse above: 100ms is enough for
|
// Allow time for the touch IC to fully boot after the reset pulse above: 100ms is enough for
|
||||||
// I2C ACK (probe) but cold power-on needs ~300ms before register reads succeed reliably.
|
// I2C ACK (probe) but cold power-on needs ~300ms before register reads succeed reliably.
|
||||||
@ -64,12 +36,8 @@ Tab5Variant tab5_probe_variant(Device* i2c0) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (i2c_controller_has_device_at_address(i2c0, ESP_LCD_TOUCH_IO_I2C_ST7123_ADDRESS, PROBE_TIMEOUT) == ERROR_NONE) {
|
if (i2c_controller_has_device_at_address(i2c0, ESP_LCD_TOUCH_IO_I2C_ST7123_ADDRESS, PROBE_TIMEOUT) == ERROR_NONE) {
|
||||||
Tab5Variant variant;
|
LOG_I(TAG, "display_detect: detected ST7123 touch — using variant V2");
|
||||||
if (probe_st7123_or_st7121_variant(i2c0, PROBE_TIMEOUT, &variant)) {
|
return Tab5Variant::V2;
|
||||||
return variant;
|
|
||||||
}
|
|
||||||
// FW-version read failed/unrecognized - fall through to the retry delay below instead
|
|
||||||
// of giving up immediately, same as an address-probe miss.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(100));
|
vTaskDelay(pdMS_TO_TICKS(100));
|
||||||
|
|||||||
@ -6,7 +6,6 @@ enum class Tab5Variant {
|
|||||||
Unknown,
|
Unknown,
|
||||||
V1, // Older variant: ILI9881C display + GT911 touch (see devices_v1.cpp)
|
V1, // Older variant: ILI9881C display + GT911 touch (see devices_v1.cpp)
|
||||||
V2, // Newer variant (default): ST7123 display + in-cell touch (see devices_v2.cpp)
|
V2, // Newer variant (default): ST7123 display + in-cell touch (see devices_v2.cpp)
|
||||||
V3, // Newest variant: ST7121 display + in-cell touch (see devices_v3.cpp)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Populated once the device_listener callback in display_detect.cpp has detected which
|
// Populated once the device_listener callback in display_detect.cpp has detected which
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
#include "devices_v2.h"
|
#include "devices_v2.h"
|
||||||
|
|
||||||
#include "devices_common.h"
|
#include "devices_common.h"
|
||||||
#include "devices_v2_v3_touch.h"
|
|
||||||
#include "st7123_init_data.h"
|
#include "st7123_init_data.h"
|
||||||
|
|
||||||
#include <tactility/device.h>
|
#include <tactility/device.h>
|
||||||
@ -9,6 +8,7 @@
|
|||||||
#include <tactility/log.h>
|
#include <tactility/log.h>
|
||||||
|
|
||||||
#include <drivers/st7123.h>
|
#include <drivers/st7123.h>
|
||||||
|
#include <drivers/st7123_touch.h>
|
||||||
|
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@ -19,6 +19,43 @@ static std::vector<uint8_t> display_init_bytes;
|
|||||||
static St7123Config st7123_config {};
|
static St7123Config st7123_config {};
|
||||||
static Device display_device {};
|
static Device display_device {};
|
||||||
|
|
||||||
|
static St7123TouchConfig st7123_touch_config {};
|
||||||
|
static Device st7123_touch_device {};
|
||||||
|
|
||||||
|
static void create_st7123_touch(Device* i2c0) {
|
||||||
|
st7123_touch_device = Device {
|
||||||
|
.address = 0,
|
||||||
|
.name = "touch0",
|
||||||
|
.config = nullptr,
|
||||||
|
.parent = nullptr,
|
||||||
|
.internal = nullptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
GpioPinSpec pin_interrupt = GPIO_PIN_SPEC_NONE;
|
||||||
|
Device* gpio0 = nullptr;
|
||||||
|
if (device_get_by_name("gpio0", &gpio0) == ERROR_NONE) {
|
||||||
|
pin_interrupt = GpioPinSpec { gpio0, 23, GPIO_FLAG_NONE };
|
||||||
|
device_put(gpio0);
|
||||||
|
} else {
|
||||||
|
LOG_W(TAG, "display_detect: gpio0 not found, touch interrupt pin will not be wired");
|
||||||
|
}
|
||||||
|
|
||||||
|
st7123_touch_config = St7123TouchConfig {
|
||||||
|
.address = 0x55, // fixed - see ESP_LCD_TOUCH_IO_I2C_ST7123_ADDRESS
|
||||||
|
.x_max = 720,
|
||||||
|
.y_max = 1280,
|
||||||
|
.swap_xy = false,
|
||||||
|
.mirror_x = false,
|
||||||
|
.mirror_y = false,
|
||||||
|
// Reset is pulsed via io_expander0 (detect.cpp's pulse_display_reset_pins), not a direct SoC GPIO.
|
||||||
|
.pin_reset = GPIO_PIN_SPEC_NONE,
|
||||||
|
.pin_interrupt = pin_interrupt,
|
||||||
|
};
|
||||||
|
st7123_touch_device.config = &st7123_touch_config;
|
||||||
|
|
||||||
|
construct_add_start(&st7123_touch_device, i2c0, "sitronix,st7123-touch");
|
||||||
|
}
|
||||||
|
|
||||||
void tab5_create_devices_v2(Device* i2c0) {
|
void tab5_create_devices_v2(Device* i2c0) {
|
||||||
display_device = Device {
|
display_device = Device {
|
||||||
.address = 0,
|
.address = 0,
|
||||||
|
|||||||
@ -1,48 +0,0 @@
|
|||||||
#include "devices_v2_v3_touch.h"
|
|
||||||
|
|
||||||
#include "devices_common.h"
|
|
||||||
|
|
||||||
#include <tactility/device.h>
|
|
||||||
#include <tactility/drivers/gpio.h>
|
|
||||||
#include <tactility/log.h>
|
|
||||||
|
|
||||||
#include <drivers/st7123_touch.h>
|
|
||||||
|
|
||||||
constexpr auto* TAG = "Tab5";
|
|
||||||
|
|
||||||
static St7123TouchConfig st7123_touch_config {};
|
|
||||||
static Device st7123_touch_device {};
|
|
||||||
|
|
||||||
void create_st7123_touch(Device* i2c0) {
|
|
||||||
st7123_touch_device = Device {
|
|
||||||
.address = 0,
|
|
||||||
.name = "touch0",
|
|
||||||
.config = nullptr,
|
|
||||||
.parent = nullptr,
|
|
||||||
.internal = nullptr,
|
|
||||||
};
|
|
||||||
|
|
||||||
GpioPinSpec pin_interrupt = GPIO_PIN_SPEC_NONE;
|
|
||||||
Device* gpio0 = nullptr;
|
|
||||||
if (device_get_by_name("gpio0", &gpio0) == ERROR_NONE) {
|
|
||||||
pin_interrupt = GpioPinSpec { gpio0, 23, GPIO_FLAG_NONE };
|
|
||||||
device_put(gpio0);
|
|
||||||
} else {
|
|
||||||
LOG_W(TAG, "display_detect: gpio0 not found, touch interrupt pin will not be wired");
|
|
||||||
}
|
|
||||||
|
|
||||||
st7123_touch_config = St7123TouchConfig {
|
|
||||||
.address = 0x55, // fixed - see ESP_LCD_TOUCH_IO_I2C_ST7123_ADDRESS
|
|
||||||
.x_max = 720,
|
|
||||||
.y_max = 1280,
|
|
||||||
.swap_xy = false,
|
|
||||||
.mirror_x = false,
|
|
||||||
.mirror_y = false,
|
|
||||||
// Reset is pulsed via io_expander0 (detect.cpp's pulse_display_reset_pins), not a direct SoC GPIO.
|
|
||||||
.pin_reset = GPIO_PIN_SPEC_NONE,
|
|
||||||
.pin_interrupt = pin_interrupt,
|
|
||||||
};
|
|
||||||
st7123_touch_device.config = &st7123_touch_config;
|
|
||||||
|
|
||||||
construct_add_start(&st7123_touch_device, i2c0, "sitronix,st7123-touch");
|
|
||||||
}
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
struct Device;
|
|
||||||
|
|
||||||
// Constructs, parents, binds and starts the ST7123 in-cell touch device on i2c0. Shared by the V2
|
|
||||||
// (ST7123 display) and V3 (ST7121 display) variants - both boards use the exact same touch
|
|
||||||
// controller/address/wiring, only the display panel differs. Not used by V1 (ILI9881C + GT911).
|
|
||||||
void create_st7123_touch(Device* i2c0);
|
|
||||||
@ -1,85 +0,0 @@
|
|||||||
#include "devices_v3.h"
|
|
||||||
|
|
||||||
#include "devices_common.h"
|
|
||||||
#include "devices_v2_v3_touch.h"
|
|
||||||
|
|
||||||
#include <tactility/device.h>
|
|
||||||
#include <tactility/drivers/gpio.h>
|
|
||||||
#include <tactility/log.h>
|
|
||||||
|
|
||||||
#include <drivers/st7121.h>
|
|
||||||
|
|
||||||
#define TAG "Tab5"
|
|
||||||
|
|
||||||
static St7121Config st7121_config {};
|
|
||||||
static Device display_device {};
|
|
||||||
|
|
||||||
// Newest Tab5 variant: ST7121 display + the same in-cell ST7123 touch controller as V2 (see
|
|
||||||
// devices_common.cpp's create_st7123_touch). No custom init-sequence is supplied - unlike ST7123,
|
|
||||||
// the M5Tab5 UserDemo runs the ST7121 panel with the esp_lcd_st7121 component's own built-in
|
|
||||||
// default bring-up sequence (see esp_lcd_st7121.c's vendor_specific_init_default), so
|
|
||||||
// init_sequence stays null here too. Timing values (vsync_pulse_width/back_porch/front_porch) are
|
|
||||||
// per the UserDemo's is_st7121 branch in bsp_display_new_with_handles_to_st7123() - the only
|
|
||||||
// values that differ from V2/ST7123.
|
|
||||||
void tab5_create_devices_v3(Device* i2c0) {
|
|
||||||
display_device = Device {
|
|
||||||
.address = 0,
|
|
||||||
.name = "display0",
|
|
||||||
.config = nullptr,
|
|
||||||
.parent = nullptr,
|
|
||||||
.internal = nullptr,
|
|
||||||
};
|
|
||||||
|
|
||||||
Device* backlight = nullptr;
|
|
||||||
if (device_get_by_name("display_backlight", &backlight) != ERROR_NONE) {
|
|
||||||
LOG_W(TAG, "display_detect: display_backlight not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
st7121_config = St7121Config {
|
|
||||||
.horizontal_resolution = 720,
|
|
||||||
.vertical_resolution = 1280,
|
|
||||||
.bits_per_pixel = 16,
|
|
||||||
.bgr_order = false,
|
|
||||||
.invert_color = false,
|
|
||||||
.mirror_x = false,
|
|
||||||
.mirror_y = false,
|
|
||||||
.pin_reset = GPIO_PIN_SPEC_NONE,
|
|
||||||
.ldo_channel = 3,
|
|
||||||
.ldo_voltage_mv = 2500,
|
|
||||||
.dsi_bus_id = 0,
|
|
||||||
.num_data_lanes = 2,
|
|
||||||
.lane_bit_rate_mbps = 965, // ST7121 lane bitrate per M5Stack BSP (same as ST7123)
|
|
||||||
.dpi_clock_freq_mhz = 70,
|
|
||||||
.hsync_pulse_width = 2,
|
|
||||||
.hsync_back_porch = 40,
|
|
||||||
.hsync_front_porch = 40,
|
|
||||||
.vsync_pulse_width = 20,
|
|
||||||
.vsync_back_porch = 24,
|
|
||||||
.vsync_front_porch = 200,
|
|
||||||
.num_fbs = 2,
|
|
||||||
.use_dma2d = true,
|
|
||||||
.disable_lp = false,
|
|
||||||
.allow_tearing = true, // matches old lvgl_port_display_dsi_cfg_t.avoid_tearing = 0 (disabled = don't wait)
|
|
||||||
.init_sequence = nullptr, // use esp_lcd_st7121's built-in default sequence, per the UserDemo
|
|
||||||
.init_sequence_length = 0,
|
|
||||||
.backlight = backlight,
|
|
||||||
};
|
|
||||||
display_device.config = &st7121_config;
|
|
||||||
|
|
||||||
if (backlight != nullptr) {
|
|
||||||
device_put(backlight);
|
|
||||||
}
|
|
||||||
|
|
||||||
Device* root = nullptr;
|
|
||||||
if (device_get_by_name("/", &root) != ERROR_NONE) {
|
|
||||||
LOG_E(TAG, "display_detect: root device not found");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
bool started = construct_add_start(&display_device, root, "sitronix,st7121");
|
|
||||||
device_put(root);
|
|
||||||
if (!started) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
create_st7123_touch(i2c0);
|
|
||||||
}
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
struct Device;
|
|
||||||
|
|
||||||
void tab5_create_devices_v3(Device* i2c0);
|
|
||||||
@ -3,7 +3,6 @@
|
|||||||
#include "devices_common.h"
|
#include "devices_common.h"
|
||||||
#include "devices_v1.h"
|
#include "devices_v1.h"
|
||||||
#include "devices_v2.h"
|
#include "devices_v2.h"
|
||||||
#include "devices_v3.h"
|
|
||||||
#include "tab5_keyboard.h"
|
#include "tab5_keyboard.h"
|
||||||
|
|
||||||
#include <tactility/device.h>
|
#include <tactility/device.h>
|
||||||
@ -92,18 +91,16 @@ static void on_display_detect_event(Device* device, DeviceEvent event, void* con
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need i2c0 and io_expander0 to pulse the LCD/touch reset pins, probe the variant, and
|
if (io_expander0 != nullptr && tab5_get_variant() == Tab5Variant::Unknown) {
|
||||||
// create the display and touch devices - all gated on the same pair, so do them together in
|
Tab5Variant variant = tab5_probe_variant(i2c0);
|
||||||
// one pass. Order matters: the reset pulse must run *before* tab5_probe_variant(), otherwise
|
tab5_set_variant(variant);
|
||||||
// the touch IC's response depends on whatever power-on state it happened to be in rather than
|
}
|
||||||
// a deterministic post-reset state.
|
|
||||||
|
// We need i2c0 and io_expander0 to create the display and touch devices
|
||||||
if (!did_create_display && i2c0 != nullptr && io_expander0 != nullptr) {
|
if (!did_create_display && i2c0 != nullptr && io_expander0 != nullptr) {
|
||||||
did_create_display = true;
|
did_create_display = true;
|
||||||
if (pulse_display_reset_pins(io_expander0)) {
|
if (pulse_display_reset_pins(io_expander0)) {
|
||||||
Tab5Variant variant = tab5_probe_variant(i2c0);
|
switch (tab5_get_variant()) {
|
||||||
tab5_set_variant(variant);
|
|
||||||
|
|
||||||
switch (variant) {
|
|
||||||
case Tab5Variant::Unknown:
|
case Tab5Variant::Unknown:
|
||||||
LOG_E(TAG, "Variant not detected yet");
|
LOG_E(TAG, "Variant not detected yet");
|
||||||
break;
|
break;
|
||||||
@ -113,9 +110,6 @@ static void on_display_detect_event(Device* device, DeviceEvent event, void* con
|
|||||||
case Tab5Variant::V2:
|
case Tab5Variant::V2:
|
||||||
tab5_create_devices_v2(i2c0);
|
tab5_create_devices_v2(i2c0);
|
||||||
break;
|
break;
|
||||||
case Tab5Variant::V3:
|
|
||||||
tab5_create_devices_v3(i2c0);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LOG_E(TAG, "display_detect: skipping display creation, failed to pulse reset pins");
|
LOG_E(TAG, "display_detect: skipping display creation, failed to pulse reset pins");
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
#include <tactility/drivers/i2c_controller.h>
|
#include <tactility/drivers/i2c_controller.h>
|
||||||
#include <tactility/drivers/keyboard.h>
|
#include <tactility/drivers/keyboard.h>
|
||||||
#include <tactility/log.h>
|
#include <tactility/log.h>
|
||||||
|
#include <lvgl/lvgl.h>
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
|
||||||
#include <driver/gpio.h>
|
#include <driver/gpio.h>
|
||||||
@ -35,6 +36,11 @@ 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;
|
||||||
|
|
||||||
|
// Hot-plug attach-state check interval. I2C probes can false-positive on a floating/half-connected
|
||||||
|
// bus (e.g. mid-unplug), so a state change is only acted on once it's seen on two consecutive
|
||||||
|
// checks in a row (see check_attach_state()).
|
||||||
|
static constexpr uint32_t ATTACH_CHECK_INTERVAL_MS = 1000;
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Register addresses
|
// Register addresses
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@ -187,10 +193,14 @@ struct Tab5KeyboardInternal {
|
|||||||
bool irq_configured;
|
bool irq_configured;
|
||||||
gpio_num_t irq_pin;
|
gpio_num_t irq_pin;
|
||||||
|
|
||||||
// Poll throttling (real-time based, since read_key() is called at whatever rate LVGL's indev
|
// Poll/attach-check throttling (real-time based, since read_key() is called at whatever rate
|
||||||
// timer and its own drain-loop - via continue_reading - happen to run at, unlike the old
|
// LVGL's indev timer and its own drain-loop - via continue_reading - happen to run at, unlike
|
||||||
// deprecated-HAL's fixed 20ms Timer)
|
// the old deprecated-HAL's fixed 20ms Timer)
|
||||||
uint32_t last_poll_ms;
|
uint32_t last_poll_ms;
|
||||||
|
uint32_t last_attach_check_ms;
|
||||||
|
bool was_attached;
|
||||||
|
bool pending_attach_state;
|
||||||
|
uint8_t pending_attach_confirm_count;
|
||||||
|
|
||||||
// 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;
|
uint32_t repeat_key;
|
||||||
@ -198,6 +208,9 @@ struct Tab5KeyboardInternal {
|
|||||||
uint8_t repeat_col;
|
uint8_t repeat_col;
|
||||||
uint32_t repeat_start_ms;
|
uint32_t repeat_start_ms;
|
||||||
uint32_t repeat_last_ms;
|
uint32_t repeat_last_ms;
|
||||||
|
|
||||||
|
Tab5KeyboardAttachListener attach_listener;
|
||||||
|
void* attach_listener_context;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@ -213,20 +226,9 @@ static bool write_reg(Device* device, uint8_t reg, uint8_t value) {
|
|||||||
return i2c_controller_write_register(parent, I2C_ADDRESS, reg, &value, 1, pdMS_TO_TICKS(50)) == ERROR_NONE;
|
return i2c_controller_write_register(parent, I2C_ADDRESS, reg, &value, 1, pdMS_TO_TICKS(50)) == ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Short-timeout variant used only by tab5_keyboard_reinit(), which runs on the FreeRTOS timer
|
static bool is_attached_raw(Device* device) {
|
||||||
// daemon task (via tab5_keyboard_attach_detect.cpp) - a slow/absent device there blocks every
|
|
||||||
// other software timer in the system, not just this one, so it can't afford write_reg()'s 50ms
|
|
||||||
// per-call budget. read_reg()/write_reg() themselves stay at 50ms since they're also used from the
|
|
||||||
// hot IRQ/poll path (drain_events()), where a too-short timeout would cause missed key events
|
|
||||||
// under normal bus contention.
|
|
||||||
static bool write_reg_fast(Device* device, uint8_t reg, uint8_t value) {
|
|
||||||
auto* parent = device_get_parent(device);
|
auto* parent = device_get_parent(device);
|
||||||
return i2c_controller_write_register(parent, I2C_ADDRESS, reg, &value, 1, pdMS_TO_TICKS(2)) == ERROR_NONE;
|
return i2c_controller_has_device_at_address(parent, I2C_ADDRESS, pdMS_TO_TICKS(100)) == ERROR_NONE;
|
||||||
}
|
|
||||||
|
|
||||||
bool tab5_keyboard_is_attached(Device* device) {
|
|
||||||
auto* parent = device_get_parent(device);
|
|
||||||
return i2c_controller_has_device_at_address(parent, I2C_ADDRESS, pdMS_TO_TICKS(5)) == ERROR_NONE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@ -383,32 +385,69 @@ static void drain_events(Device* device, Tab5KeyboardInternal* internal) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// tab5_keyboard_reinit - (re)applies the device register configuration. Called from start() and
|
// reinit_device - (re)applies the device register configuration. Used at start() and again on
|
||||||
// again by tab5_keyboard_attach_detect.cpp on confirmed hot-plug reattach, since the device's RGB
|
// hot-plug reattach, since the device's RGB mode and interrupt configuration are volatile and
|
||||||
// mode and interrupt configuration are volatile and reset to power-on defaults when the keyboard
|
// reset to power-on defaults when the keyboard is unplugged and reconnected.
|
||||||
// is unplugged and reconnected.
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
void tab5_keyboard_reinit(Device* device) {
|
static void reinit_device(Device* device, Tab5KeyboardInternal* internal) {
|
||||||
auto* internal = static_cast<Tab5KeyboardInternal*>(device_get_driver_data(device));
|
write_reg(device, REG_KEYBOARD_MODE, 0x00); // Normal mode
|
||||||
write_reg_fast(device, REG_KEYBOARD_MODE, 0x00); // Normal mode
|
write_reg(device, REG_EVENT_NUM, 0x00); // flush event queue
|
||||||
write_reg_fast(device, REG_EVENT_NUM, 0x00); // flush event queue
|
write_reg(device, REG_INT_STAT, 0x00); // clear pending INT
|
||||||
write_reg_fast(device, REG_INT_STAT, 0x00); // clear pending INT
|
write_reg(device, REG_RGB_MODE, 0x01); // Custom RGB mode (manual LED control)
|
||||||
write_reg_fast(device, REG_RGB_MODE, 0x01); // Custom RGB mode (manual LED control)
|
write_reg(device, REG_BRIGHTNESS, 50); // 50% brightness
|
||||||
write_reg_fast(device, REG_BRIGHTNESS, 50); // 50% brightness
|
|
||||||
update_leds(device, internal); // restore current LED state
|
update_leds(device, internal); // restore current LED state
|
||||||
|
|
||||||
if (internal->irq_configured) {
|
if (internal->irq_configured) {
|
||||||
write_reg_fast(device, REG_INT_CFG, 0x01); // re-enable Normal-mode interrupt (bit 0)
|
write_reg(device, REG_INT_CFG, 0x01); // re-enable Normal-mode interrupt (bit 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// check_attach_state - throttled (~1s) hot-plug detection. Reapplies device register
|
||||||
|
// configuration on reattach, and notifies the registered attach listener (if any) of confirmed
|
||||||
|
// transitions - see Tab5KeyboardAttachListener's doc comment for the retry contract.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
static void check_attach_state(Device* device, Tab5KeyboardInternal* internal) {
|
||||||
|
uint32_t now = now_ms();
|
||||||
|
if (now - internal->last_attach_check_ms < ATTACH_CHECK_INTERVAL_MS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
internal->last_attach_check_ms = now;
|
||||||
|
|
||||||
|
const bool attached = is_attached_raw(device);
|
||||||
|
if (attached == internal->was_attached) {
|
||||||
|
internal->pending_attach_confirm_count = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Require the new state to be confirmed on a second consecutive check before acting - a
|
||||||
|
// single probe on a floating/half-connected bus (e.g. mid-unplug) can false-positive.
|
||||||
|
if (attached != internal->pending_attach_state || internal->pending_attach_confirm_count == 0) {
|
||||||
|
internal->pending_attach_state = attached;
|
||||||
|
internal->pending_attach_confirm_count = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
internal->pending_attach_confirm_count = 0;
|
||||||
|
|
||||||
|
if (attached) {
|
||||||
|
reinit_device(device, internal);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (internal->attach_listener != nullptr) {
|
||||||
|
if (!internal->attach_listener(device, attached, internal->attach_listener_context)) {
|
||||||
|
return; // not handled yet (e.g. LVGL lock busy) - retry on the next confirmed check
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal->was_attached = attached;
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// poll_if_due - the closest equivalent to the old deprecated-HAL's 20ms-Timer-driven
|
// poll_if_due - the closest equivalent to the old deprecated-HAL's 20ms-Timer-driven
|
||||||
// processKeyboard(): drains new key events (IRQ-gated or polled) and ticks software key-repeat.
|
// processKeyboard(): drains new key events (IRQ-gated or polled), ticks software key-repeat, and
|
||||||
// Called from read_key(), throttled to real elapsed time rather than call count, since read_key()
|
// checks hot-plug attach state. Called from read_key(), throttled to real elapsed time rather
|
||||||
// can be called back-to-back multiple times per LVGL indev timer tick while draining an
|
// than call count, since read_key() can be called back-to-back multiple times per LVGL indev
|
||||||
// already-queued burst (continue_reading). Hot-plug attach detection lives outside the driver -
|
// timer tick while draining an already-queued burst (continue_reading).
|
||||||
// see tab5_keyboard_attach_detect.cpp.
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
static void poll_if_due(Device* device, Tab5KeyboardInternal* internal) {
|
static void poll_if_due(Device* device, Tab5KeyboardInternal* internal) {
|
||||||
uint32_t now = now_ms();
|
uint32_t now = now_ms();
|
||||||
@ -444,6 +483,8 @@ static void poll_if_due(Device* device, Tab5KeyboardInternal* internal) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
check_attach_state(device, internal);
|
||||||
}
|
}
|
||||||
|
|
||||||
static gpio_num_t pin_or_nc(const GpioPinSpec& pin) {
|
static gpio_num_t pin_or_nc(const GpioPinSpec& pin) {
|
||||||
@ -476,24 +517,17 @@ static error_t start(Device* device) {
|
|||||||
internal->irq_pin = pin_or_nc(config->pin_interrupt);
|
internal->irq_pin = pin_or_nc(config->pin_interrupt);
|
||||||
if (internal->irq_pin != GPIO_NUM_NC) {
|
if (internal->irq_pin != GPIO_NUM_NC) {
|
||||||
configure_irq_pin(internal); // best-effort; falls back to polling if it fails. Must
|
configure_irq_pin(internal); // best-effort; falls back to polling if it fails. Must
|
||||||
// happen before tab5_keyboard_reinit() so REG_INT_CFG is
|
// happen before reinit_device() so REG_INT_CFG is written
|
||||||
// written if IRQ setup succeeded.
|
// if IRQ setup succeeded.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Driver data must be set before tab5_keyboard_reinit() - it looks internal back up via
|
// Best-effort: if the keyboard isn't attached yet (e.g. this device is constructed
|
||||||
// device_get_driver_data().
|
// speculatively at boot so it can be hot-plug-detected later), these I2C writes fail
|
||||||
|
// silently and reinit_device() runs again once attach is detected.
|
||||||
|
reinit_device(device, internal);
|
||||||
|
internal->was_attached = is_attached_raw(device);
|
||||||
|
|
||||||
device_set_driver_data(device, internal);
|
device_set_driver_data(device, internal);
|
||||||
|
|
||||||
// This device is constructed speculatively at boot so it can be hot-plug-detected later - if
|
|
||||||
// the keyboard isn't physically attached yet, skip reinit here (tab5_keyboard_attach_detect.cpp
|
|
||||||
// calls it again once attach is confirmed) rather than issuing register writes that are certain
|
|
||||||
// to fail: unlike tab5_keyboard_is_attached()'s plain probe, write_register() logs at error
|
|
||||||
// level on failure (see esp32_i2c_master.cpp), which would be misleading noise for what's just
|
|
||||||
// "not plugged in yet".
|
|
||||||
if (tab5_keyboard_is_attached(device)) {
|
|
||||||
tab5_keyboard_reinit(device);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ERROR_NONE;
|
return ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -557,17 +591,76 @@ Driver tab5_keyboard_driver = {
|
|||||||
.internal = nullptr
|
.internal = nullptr
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// region Attach listener
|
||||||
|
|
||||||
|
void tab5_keyboard_add_attach_listener(Device* device, Tab5KeyboardAttachListener callback, void* context) {
|
||||||
|
auto* internal = static_cast<Tab5KeyboardInternal*>(device_get_driver_data(device));
|
||||||
|
if (internal->attach_listener != nullptr) {
|
||||||
|
LOG_W(TAG, "Replacing existing attach listener without it being removed first");
|
||||||
|
}
|
||||||
|
internal->attach_listener = callback;
|
||||||
|
internal->attach_listener_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tab5_keyboard_remove_attach_listener(Device* device, Tab5KeyboardAttachListener callback) {
|
||||||
|
auto* internal = static_cast<Tab5KeyboardInternal*>(device_get_driver_data(device));
|
||||||
|
if (internal->attach_listener != callback) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
internal->attach_listener = nullptr;
|
||||||
|
internal->attach_listener_context = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
// region Dynamic construction
|
// region Dynamic construction
|
||||||
|
|
||||||
|
// Reacts to the Tab5 keyboard accessory's hot-plug attach state (see Tab5KeyboardAttachListener's
|
||||||
|
// doc comment above for the retry contract). This is UI-layer behavior the driver itself can't do
|
||||||
|
// (it has no LVGL dependency): switch to landscape while the keyboard is attached, restoring
|
||||||
|
// whatever rotation was active before once it's removed - but only if the user hasn't manually
|
||||||
|
// changed it since attaching, in which case their choice is respected. Ported as-is from the
|
||||||
|
// deprecated HAL's Tab5Keyboard::applyAutoRotation().
|
||||||
|
static bool on_keyboard_attach_changed(Device* /*device*/, bool attached, void* /*context*/) {
|
||||||
|
static lv_display_rotation_t saved_rotation = LV_DISPLAY_ROTATION_0;
|
||||||
|
static bool rotation_override_active = false;
|
||||||
|
|
||||||
|
auto* display = lv_display_get_default();
|
||||||
|
if (display == nullptr) {
|
||||||
|
return false; // LVGL not ready yet - retry on the next confirmed check
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lvgl_try_lock(pdMS_TO_TICKS(1000))) {
|
||||||
|
return false; // retry next check
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attached) {
|
||||||
|
if (lv_display_get_rotation(display) != LV_DISPLAY_ROTATION_90) {
|
||||||
|
saved_rotation = lv_display_get_rotation(display);
|
||||||
|
rotation_override_active = true;
|
||||||
|
lv_display_set_rotation(display, LV_DISPLAY_ROTATION_90);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Only restore if rotation is still what we set it to - if the user manually changed it
|
||||||
|
// since attaching, respect their choice instead.
|
||||||
|
if (rotation_override_active && lv_display_get_rotation(display) == LV_DISPLAY_ROTATION_90) {
|
||||||
|
lv_display_set_rotation(display, saved_rotation);
|
||||||
|
}
|
||||||
|
rotation_override_active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
lvgl_unlock();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static Tab5KeyboardConfig tab5_keyboard_config {};
|
static Tab5KeyboardConfig tab5_keyboard_config {};
|
||||||
static Device tab5_keyboard_device {};
|
static Device tab5_keyboard_device {};
|
||||||
|
|
||||||
// The keyboard accessory is a kernel driver device (m5stack,tab5-keyboard, defined directly in
|
// The keyboard accessory is a kernel driver device (m5stack,tab5-keyboard, defined directly in
|
||||||
// this project). Unlike the display/touch, it isn't gated on the display-variant detection at
|
// this project). Unlike the display/touch, it isn't gated on the display-variant detection at
|
||||||
// all (it lives on i2c2, a separate bus) - lvgl-module binds its indev unconditionally at boot
|
// all (it lives on i2c2, a separate bus) - lvgl-module binds its indev unconditionally at boot
|
||||||
// regardless of physical attach state. Hot-plug attach/detach handling (register reinit, LVGL
|
// regardless of physical attach state, and the driver's own read_key() polling handles hot-plug
|
||||||
// rotation) lives in tab5_keyboard_attach_detect.cpp, not this driver - see module.cpp for where
|
// internally.
|
||||||
// that gets started.
|
|
||||||
void tab5_create_keyboard(Device* i2c2) {
|
void tab5_create_keyboard(Device* i2c2) {
|
||||||
tab5_keyboard_device = Device {
|
tab5_keyboard_device = Device {
|
||||||
.address = 0,
|
.address = 0,
|
||||||
@ -594,7 +687,9 @@ void tab5_create_keyboard(Device* i2c2) {
|
|||||||
|
|
||||||
// Parented to i2c2 itself (not root, unlike the display): the keyboard driver's start() uses
|
// Parented to i2c2 itself (not root, unlike the display): the keyboard driver's start() uses
|
||||||
// device_get_parent() as its I2C bus controller.
|
// device_get_parent() as its I2C bus controller.
|
||||||
construct_add_start(&tab5_keyboard_device, i2c2, "m5stack,tab5-keyboard");
|
if (construct_add_start(&tab5_keyboard_device, i2c2, "m5stack,tab5-keyboard")) {
|
||||||
|
tab5_keyboard_add_attach_listener(&tab5_keyboard_device, on_keyboard_attach_changed, nullptr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|||||||
@ -20,23 +20,27 @@ struct Tab5KeyboardConfig {
|
|||||||
struct GpioPinSpec pin_interrupt;
|
struct GpioPinSpec pin_interrupt;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Called when the keyboard accessory's hot-plug attach state changes (confirmed over two
|
||||||
|
// consecutive ~1s checks - see the driver source). Orientation changes and other LVGL-aware
|
||||||
|
// reactions to attach state live outside the driver (it has no LVGL dependency) - module.cpp
|
||||||
|
// registers a listener for this instead.
|
||||||
|
// @return true once handled; false to be called again on the next confirmed check with the same
|
||||||
|
// `attached` value (e.g. if the LVGL lock couldn't be acquired) - mirrors this driver's own
|
||||||
|
// internal retry-until-handled pattern for reinitializing the device on reattach.
|
||||||
|
typedef bool (*Tab5KeyboardAttachListener)(struct Device* device, bool attached, void* context);
|
||||||
|
|
||||||
|
// Only one listener is supported (this board only ever has one caller - module.cpp). Registering
|
||||||
|
// a new one before removing the previous replaces it with a warning logged.
|
||||||
|
void tab5_keyboard_add_attach_listener(struct Device* device, Tab5KeyboardAttachListener callback, void* context);
|
||||||
|
void tab5_keyboard_remove_attach_listener(struct Device* device, Tab5KeyboardAttachListener callback);
|
||||||
|
|
||||||
extern struct Driver tab5_keyboard_driver;
|
extern struct Driver tab5_keyboard_driver;
|
||||||
|
|
||||||
// Constructs and starts the keyboard accessory device on i2c2. Called from display_detect.cpp's
|
// Constructs and starts the keyboard accessory device on i2c2, then registers this project's own
|
||||||
|
// hot-plug rotation handler as its attach listener. Called from display_detect.cpp's
|
||||||
// on_display_detect_event() once i2c2 is up.
|
// on_display_detect_event() once i2c2 is up.
|
||||||
void tab5_create_keyboard(struct Device* i2c2);
|
void tab5_create_keyboard(struct Device* i2c2);
|
||||||
|
|
||||||
// Returns true if the keyboard accessory currently ACKs on the I2C bus. Cheap bus probe, no
|
|
||||||
// debouncing - callers wanting hot-plug-stable state (e.g. tab5_keyboard_attach_detect.cpp)
|
|
||||||
// should debounce across their own polling interval.
|
|
||||||
bool tab5_keyboard_is_attached(struct Device* device);
|
|
||||||
|
|
||||||
// (Re)applies the device's register configuration - RGB mode, brightness, interrupt config, LED
|
|
||||||
// state. Volatile on this chip: reset to power-on defaults whenever the keyboard is unplugged and
|
|
||||||
// reconnected, so callers must call this again after confirming a reattach (see
|
|
||||||
// tab5_keyboard_attach_detect.cpp).
|
|
||||||
void tab5_keyboard_reinit(struct Device* device);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -1,157 +0,0 @@
|
|||||||
#include "tab5_keyboard_attach_detect.h"
|
|
||||||
|
|
||||||
#include "tab5_keyboard.h"
|
|
||||||
|
|
||||||
#include <tactility/device.h>
|
|
||||||
#include <tactility/log.h>
|
|
||||||
|
|
||||||
#include <lvgl/lvgl.h>
|
|
||||||
#include <lvgl.h>
|
|
||||||
|
|
||||||
#include <freertos/FreeRTOS.h>
|
|
||||||
#include <freertos/timers.h>
|
|
||||||
|
|
||||||
constexpr auto* TAG = "Tab5";
|
|
||||||
|
|
||||||
// Hot-plug attach-state check interval. I2C probes can false-positive on a floating/half-connected
|
|
||||||
// bus (e.g. mid-unplug), so a state change is only acted on once it's seen on two consecutive
|
|
||||||
// checks in a row.
|
|
||||||
constexpr auto ATTACH_CHECK_INTERVAL_MS = 1000;
|
|
||||||
|
|
||||||
static TimerHandle_t attach_detect_timer = nullptr;
|
|
||||||
|
|
||||||
static bool was_attached = false;
|
|
||||||
static bool pending_attach_state = false;
|
|
||||||
static uint8_t pending_attach_confirm_count = 0;
|
|
||||||
|
|
||||||
// Tracks LVGL's own readiness so a restart (lvgl_is_running() going from false back to true -
|
|
||||||
// e.g. an app that took over the display for direct rendering, stopping and letting LVGL rebind)
|
|
||||||
// can be told apart from the keyboard itself attaching/detaching. See apply_state()'s comment for
|
|
||||||
// why that distinction matters.
|
|
||||||
static bool was_lvgl_ready = false;
|
|
||||||
|
|
||||||
static lv_display_rotation_t saved_rotation = LV_DISPLAY_ROTATION_0;
|
|
||||||
static bool rotation_override_active = false;
|
|
||||||
|
|
||||||
// Applies the current attach state to LVGL (landscape rotation while attached, restoring the
|
|
||||||
// prior rotation on detach unless the user changed it manually since attaching) and to the
|
|
||||||
// keyboard device's own register state (reinit on attach - RGB mode/interrupt config are volatile
|
|
||||||
// across an unplug/replug on this chip). Ported as-is from the deprecated HAL's
|
|
||||||
// Tab5Keyboard::applyAutoRotation() / the pre-refactor tab5_keyboard.cpp driver logic.
|
|
||||||
// @return true once handled; false to be retried on the next tick (e.g. LVGL lock busy).
|
|
||||||
static bool apply_state(Device* keyboard_device, bool attached) {
|
|
||||||
if (!lvgl_try_lock(pdMS_TO_TICKS(100))) {
|
|
||||||
return false; // retry next tick
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolved inside the lock, not before: the default display can start/stop between an
|
|
||||||
// unlocked probe and actually taking the lock, and lv_display_get_default() itself isn't
|
|
||||||
// safe to call without holding it (unlike lvgl_is_running(), used for the readiness check in
|
|
||||||
// attach_detect_callback()).
|
|
||||||
auto* display = lv_display_get_default();
|
|
||||||
if (display == nullptr) {
|
|
||||||
lvgl_unlock();
|
|
||||||
return false; // LVGL not ready yet - retry next tick
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attached) {
|
|
||||||
tab5_keyboard_reinit(keyboard_device);
|
|
||||||
|
|
||||||
if (lv_display_get_rotation(display) != LV_DISPLAY_ROTATION_90) {
|
|
||||||
saved_rotation = lv_display_get_rotation(display);
|
|
||||||
rotation_override_active = true;
|
|
||||||
lv_display_set_rotation(display, LV_DISPLAY_ROTATION_90);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Only restore if rotation is still what we set it to - if the user manually changed it
|
|
||||||
// since attaching, respect their choice instead.
|
|
||||||
if (rotation_override_active && lv_display_get_rotation(display) == LV_DISPLAY_ROTATION_90) {
|
|
||||||
lv_display_set_rotation(display, saved_rotation);
|
|
||||||
}
|
|
||||||
rotation_override_active = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
lvgl_unlock();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void attach_detect_callback(TimerHandle_t /*timer*/) {
|
|
||||||
Device* keyboard_device = nullptr;
|
|
||||||
if (device_get_by_name("keyboard0", &keyboard_device) != ERROR_NONE) {
|
|
||||||
return; // Not constructed yet - will retry on next tick
|
|
||||||
}
|
|
||||||
|
|
||||||
// LVGL restarting is a distinct event from the keyboard physically attaching/detaching: the
|
|
||||||
// accessory may never have moved, but whatever apply_state() last set (rotation) may have
|
|
||||||
// been reset in the meantime by the restart. Forcing was_attached false makes the block below
|
|
||||||
// see a fresh "attached" transition (still going through the normal 2-check debounce) so
|
|
||||||
// apply_state() re-announces the current state instead of staying silent forever, waiting for
|
|
||||||
// an edge that will never come because the keyboard was never actually unplugged.
|
|
||||||
// lvgl_is_running() is safe to call unlocked (unlike lv_display_get_default(), resolved inside
|
|
||||||
// the lock in apply_state() instead).
|
|
||||||
const bool lvgl_ready = lvgl_is_running();
|
|
||||||
if (lvgl_ready && !was_lvgl_ready) {
|
|
||||||
was_attached = false;
|
|
||||||
pending_attach_confirm_count = 0;
|
|
||||||
}
|
|
||||||
was_lvgl_ready = lvgl_ready;
|
|
||||||
|
|
||||||
const bool attached = tab5_keyboard_is_attached(keyboard_device);
|
|
||||||
if (attached != was_attached) {
|
|
||||||
// Require the new state to be confirmed on a second consecutive check before acting - a
|
|
||||||
// single probe on a floating/half-connected bus (e.g. mid-unplug) can false-positive.
|
|
||||||
if (attached != pending_attach_state || pending_attach_confirm_count == 0) {
|
|
||||||
pending_attach_state = attached;
|
|
||||||
pending_attach_confirm_count = 1;
|
|
||||||
} else {
|
|
||||||
pending_attach_confirm_count = 0;
|
|
||||||
if (apply_state(keyboard_device, attached)) {
|
|
||||||
was_attached = attached;
|
|
||||||
}
|
|
||||||
// else: not handled yet (e.g. LVGL lock busy) - retry on the next confirmed check
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
pending_attach_confirm_count = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
device_put(keyboard_device);
|
|
||||||
}
|
|
||||||
|
|
||||||
void tab5_keyboard_attach_detect_start() {
|
|
||||||
if (attach_detect_timer != nullptr) {
|
|
||||||
LOG_W(TAG, "keyboard attach-detect timer already running");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
was_attached = false;
|
|
||||||
pending_attach_confirm_count = 0;
|
|
||||||
was_lvgl_ready = false;
|
|
||||||
rotation_override_active = false;
|
|
||||||
|
|
||||||
attach_detect_timer = xTimerCreate("kb_attach_detect", pdMS_TO_TICKS(ATTACH_CHECK_INTERVAL_MS), pdTRUE, nullptr, attach_detect_callback);
|
|
||||||
if (!attach_detect_timer) {
|
|
||||||
LOG_E(TAG, "Failed to create keyboard attach-detect timer");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (xTimerStart(attach_detect_timer, pdMS_TO_TICKS(100)) != pdPASS) {
|
|
||||||
LOG_E(TAG, "Failed to start keyboard attach-detect timer");
|
|
||||||
xTimerDelete(attach_detect_timer, pdMS_TO_TICKS(100));
|
|
||||||
attach_detect_timer = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void tab5_keyboard_attach_detect_stop() {
|
|
||||||
if (attach_detect_timer == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (xTimerStop(attach_detect_timer, pdMS_TO_TICKS(100)) != pdPASS) {
|
|
||||||
LOG_W(TAG, "Failed to stop keyboard attach-detect timer");
|
|
||||||
}
|
|
||||||
if (xTimerDelete(attach_detect_timer, pdMS_TO_TICKS(100)) != pdPASS) {
|
|
||||||
LOG_E(TAG, "Failed to delete keyboard attach-detect timer");
|
|
||||||
}
|
|
||||||
// Always clear the handle - stale non-null handle is worse than a resource leak, as it would
|
|
||||||
// cause tab5_keyboard_attach_detect_start() to silently skip re-creating the timer.
|
|
||||||
attach_detect_timer = nullptr;
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
// Starts/stops the periodic keyboard-accessory hot-plug poll: reapplies register configuration on
|
|
||||||
// reattach (see tab5_keyboard_reinit()) and switches LVGL to landscape while attached, restoring
|
|
||||||
// the prior rotation on detach - unless the user changed it manually since attaching, in which
|
|
||||||
// case their choice is respected. Also re-announces the current attach state after LVGL itself
|
|
||||||
// restarts (e.g. an app that took over the display for direct rendering, stopping and letting
|
|
||||||
// LVGL rebind), since that's a distinct event from the keyboard physically attaching/detaching -
|
|
||||||
// the accessory may never have moved, but the rotation override it applied may have been reset in
|
|
||||||
// the meantime. Called from module.cpp's start()/stop().
|
|
||||||
void tab5_keyboard_attach_detect_start();
|
|
||||||
void tab5_keyboard_attach_detect_stop();
|
|
||||||
@ -13,7 +13,6 @@
|
|||||||
#include "devices/detect.h"
|
#include "devices/detect.h"
|
||||||
#include "devices/tab5_headphone_detect.h"
|
#include "devices/tab5_headphone_detect.h"
|
||||||
#include "devices/tab5_keyboard.h"
|
#include "devices/tab5_keyboard.h"
|
||||||
#include "devices/tab5_keyboard_attach_detect.h"
|
|
||||||
#include "devices/tab5_power_control.h"
|
#include "devices/tab5_power_control.h"
|
||||||
#include "devices/tab_5_camera.h"
|
#include "devices/tab_5_camera.h"
|
||||||
|
|
||||||
@ -70,12 +69,10 @@ static error_t start() {
|
|||||||
tab5_camera_init();
|
tab5_camera_init();
|
||||||
device_listener_add(on_io_expander0_started, nullptr);
|
device_listener_add(on_io_expander0_started, nullptr);
|
||||||
tab5_headphone_detect_start();
|
tab5_headphone_detect_start();
|
||||||
tab5_keyboard_attach_detect_start();
|
|
||||||
return ERROR_NONE;
|
return ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static error_t stop() {
|
static error_t stop() {
|
||||||
tab5_keyboard_attach_detect_stop();
|
|
||||||
tab5_headphone_detect_stop();
|
tab5_headphone_detect_stop();
|
||||||
device_listener_remove(on_io_expander0_started);
|
device_listener_remove(on_io_expander0_started);
|
||||||
tab5_detect_stop();
|
tab5_detect_stop();
|
||||||
|
|||||||
@ -10,6 +10,5 @@ dependencies:
|
|||||||
- Drivers/sc2356-module
|
- Drivers/sc2356-module
|
||||||
- Drivers/ili9881c-module
|
- Drivers/ili9881c-module
|
||||||
- Drivers/st7123-module
|
- Drivers/st7123-module
|
||||||
- Drivers/st7121-module
|
|
||||||
- Drivers/gt911-module
|
- Drivers/gt911-module
|
||||||
dts: m5stack,tab5.dts
|
dts: m5stack,tab5.dts
|
||||||
|
|||||||
@ -71,12 +71,12 @@
|
|||||||
pin-sda = <&gpio0 31 GPIO_FLAG_PULL_UP>;
|
pin-sda = <&gpio0 31 GPIO_FLAG_PULL_UP>;
|
||||||
pin-scl = <&gpio0 32 GPIO_FLAG_PULL_UP>;
|
pin-scl = <&gpio0 32 GPIO_FLAG_PULL_UP>;
|
||||||
|
|
||||||
io_expander0 {
|
io_expander0: io_expander0 {
|
||||||
compatible = "diodes,pi4ioe5v6408";
|
compatible = "diodes,pi4ioe5v6408";
|
||||||
reg = <0x43>;
|
reg = <0x43>;
|
||||||
};
|
};
|
||||||
|
|
||||||
io_expander1 {
|
io_expander1: io_expander1 {
|
||||||
compatible = "diodes,pi4ioe5v6408";
|
compatible = "diodes,pi4ioe5v6408";
|
||||||
reg = <0x44>;
|
reg = <0x44>;
|
||||||
};
|
};
|
||||||
@ -123,11 +123,6 @@
|
|||||||
sc2356 {
|
sc2356 {
|
||||||
compatible = "smartsens,sc2356";
|
compatible = "smartsens,sc2356";
|
||||||
reg = <0x36>;
|
reg = <0x36>;
|
||||||
// Pulsed by the sc2356 driver itself in start_device, not a gpio-hog: the driver's
|
|
||||||
// I2C probe otherwise races a bare hog's pin release with no settle delay (the hog
|
|
||||||
// also only starts after this node, since it's a root-level sibling declared later
|
|
||||||
// in this file - always too late).
|
|
||||||
pin-reset = <&io_expander0 6 GPIO_FLAG_ACTIVE_LOW>;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -143,6 +138,12 @@
|
|||||||
mode = <GPIO_HOG_MODE_OUTPUT_HIGH>;
|
mode = <GPIO_HOG_MODE_OUTPUT_HIGH>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exp0_camera_reset {
|
||||||
|
compatible = "gpio-hog";
|
||||||
|
pin = <&io_expander0 6 GPIO_FLAG_NONE>;
|
||||||
|
mode = <GPIO_HOG_MODE_OUTPUT_HIGH>;
|
||||||
|
};
|
||||||
|
|
||||||
exp1_c6_wlan_enable {
|
exp1_c6_wlan_enable {
|
||||||
compatible = "gpio-hog";
|
compatible = "gpio-hog";
|
||||||
pin = <&io_expander1 0 GPIO_FLAG_NONE>;
|
pin = <&io_expander1 0 GPIO_FLAG_NONE>;
|
||||||
|
|||||||
@ -3,13 +3,3 @@ description: Smartsens SC2356 2MP MIPI CSI camera sensor
|
|||||||
include: [ "i2c-device.yaml" ]
|
include: [ "i2c-device.yaml" ]
|
||||||
|
|
||||||
compatible: "smartsens,sc2356"
|
compatible: "smartsens,sc2356"
|
||||||
|
|
||||||
properties:
|
|
||||||
pin-reset:
|
|
||||||
type: phandles
|
|
||||||
default: GPIO_PIN_SPEC_NONE
|
|
||||||
description: >
|
|
||||||
Reset GPIO pin. Pulsed (asserted then released, with a settle delay) before the sensor is
|
|
||||||
probed in start_device. Typically wired through a board's IO expander rather than a direct
|
|
||||||
SoC GPIO, so it's handled here via the generic gpio_descriptor API rather than
|
|
||||||
esp_video_init's csi_config.reset_pin (which only accepts native SoC GPIOs).
|
|
||||||
|
|||||||
@ -5,7 +5,6 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <tactility/drivers/camera.h>
|
#include <tactility/drivers/camera.h>
|
||||||
#include <tactility/drivers/gpio.h>
|
|
||||||
#include <tactility/error.h>
|
#include <tactility/error.h>
|
||||||
|
|
||||||
struct Device;
|
struct Device;
|
||||||
@ -17,11 +16,6 @@ extern "C" {
|
|||||||
struct Sc2356Config {
|
struct Sc2356Config {
|
||||||
/** SCCB I2C address (0x36) */
|
/** SCCB I2C address (0x36) */
|
||||||
uint8_t address;
|
uint8_t address;
|
||||||
|
|
||||||
// Reset pin. GPIO_PIN_SPEC_NONE if the sensor's reset is tied high on the board (or otherwise
|
|
||||||
// not under our control), in which case start_device skips the reset pulse entirely and goes
|
|
||||||
// straight to probing.
|
|
||||||
struct GpioPinSpec pin_reset;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
#include <tactility/device.h>
|
#include <tactility/device.h>
|
||||||
#include <tactility/drivers/esp32_i2c_master.h>
|
#include <tactility/drivers/esp32_i2c_master.h>
|
||||||
#include <tactility/drivers/gpio_controller.h>
|
|
||||||
#include <tactility/drivers/i2c_controller.h>
|
#include <tactility/drivers/i2c_controller.h>
|
||||||
#include <tactility/log.h>
|
#include <tactility/log.h>
|
||||||
|
|
||||||
@ -64,19 +63,6 @@ struct Sc2356State : CameraHandleData {
|
|||||||
|
|
||||||
#define GET_CONFIG(device) (static_cast<const Sc2356Config*>((device)->config))
|
#define GET_CONFIG(device) (static_cast<const Sc2356Config*>((device)->config))
|
||||||
|
|
||||||
// Reset timings per the SC2356/SC202CS datasheet's power-up sequence: hold reset asserted for at
|
|
||||||
// least 1ms, then wait for the sensor's internal power-on/clock startup (datasheet specifies a
|
|
||||||
// minimum before the first SCCB transaction - 10ms gives comfortable margin) before probing.
|
|
||||||
static error_t reset_pulse(GpioDescriptor* descriptor) {
|
|
||||||
// Release is attempted unconditionally, even if assert failed - leaving the expander output
|
|
||||||
// asserted on an assert failure would hold the sensor in reset for the rest of boot.
|
|
||||||
const bool assert_ok = gpio_descriptor_set_level(descriptor, true) == ERROR_NONE; // assert
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(10));
|
|
||||||
const bool release_ok = gpio_descriptor_set_level(descriptor, false) == ERROR_NONE; // release
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(10));
|
|
||||||
return (assert_ok && release_ok) ? ERROR_NONE : ERROR_RESOURCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t start(Device* device) {
|
static error_t start(Device* device) {
|
||||||
auto* i2c = device_get_parent(device);
|
auto* i2c = device_get_parent(device);
|
||||||
if (device_get_type(i2c) != &I2C_CONTROLLER_TYPE) {
|
if (device_get_type(i2c) != &I2C_CONTROLLER_TYPE) {
|
||||||
@ -84,28 +70,7 @@ static error_t start(Device* device) {
|
|||||||
return ERROR_RESOURCE;
|
return ERROR_RESOURCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto* config = GET_CONFIG(device);
|
auto address = GET_CONFIG(device)->address;
|
||||||
auto address = config->address;
|
|
||||||
|
|
||||||
// Reset is pulsed (not just held released) so the sensor reaches a known state regardless of
|
|
||||||
// whatever it inherited from a previous boot - see the equivalent reasoning for the tab5
|
|
||||||
// display/touch reset pulse in Devices/m5stack-tab5/Source/devices/display_detect.cpp.
|
|
||||||
if (config->pin_reset.gpio_controller != nullptr) {
|
|
||||||
// Merge with the devicetree-supplied flags (e.g. GPIO_FLAG_ACTIVE_LOW) rather than
|
|
||||||
// overwriting them, so a board that wires reset active-high isn't silently forced to
|
|
||||||
// active-low polarity.
|
|
||||||
auto* reset_descriptor = gpio_descriptor_acquire(config->pin_reset.gpio_controller, config->pin_reset.pin, config->pin_reset.flags | GPIO_FLAG_DIRECTION_OUTPUT, GPIO_OWNER_GPIO);
|
|
||||||
if (reset_descriptor == nullptr) {
|
|
||||||
LOG_E(TAG, "Failed to acquire reset pin");
|
|
||||||
return ERROR_RESOURCE;
|
|
||||||
}
|
|
||||||
error_t reset_error = reset_pulse(reset_descriptor);
|
|
||||||
gpio_descriptor_release(reset_descriptor);
|
|
||||||
if (reset_error != ERROR_NONE) {
|
|
||||||
LOG_E(TAG, "Failed to pulse reset pin");
|
|
||||||
return reset_error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t chip_id_h = 0, chip_id_l = 0;
|
uint8_t chip_id_h = 0, chip_id_l = 0;
|
||||||
error_t err = i2c_controller_write_read(i2c, address, SC2356_REG_CHIP_ID_H, sizeof(SC2356_REG_CHIP_ID_H), &chip_id_h, 1, I2C_TIMEOUT);
|
error_t err = i2c_controller_write_read(i2c, address, SC2356_REG_CHIP_ID_H, sizeof(SC2356_REG_CHIP_ID_H), &chip_id_h, 1, I2C_TIMEOUT);
|
||||||
|
|||||||
@ -1,11 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.20)
|
|
||||||
|
|
||||||
include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake")
|
|
||||||
|
|
||||||
file(GLOB_RECURSE SOURCE_FILES "source/*.c*")
|
|
||||||
|
|
||||||
tactility_add_module(st7121-module
|
|
||||||
SRCS ${SOURCE_FILES}
|
|
||||||
INCLUDE_DIRS include/
|
|
||||||
REQUIRES TactilityKernel platform-esp32 esp_lcd_st7121 esp_lcd driver esp_hw_support
|
|
||||||
)
|
|
||||||
@ -1,195 +0,0 @@
|
|||||||
Apache License
|
|
||||||
==============
|
|
||||||
|
|
||||||
_Version 2.0, January 2004_
|
|
||||||
_<<http://www.apache.org/licenses/>>_
|
|
||||||
|
|
||||||
### 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.
|
|
||||||
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
# ST7121 display controller
|
|
||||||
|
|
||||||
Driver for the Sitronix `ST7121` MIPI-DSI display panel (`sitronix,st7121`), driven over ESP-IDF's
|
|
||||||
`esp_lcd_st7121` component (DPI/DBI interface, ESP32-P4 and other `SOC_MIPI_DSI_SUPPORTED` targets
|
|
||||||
only). Owns the MIPI DSI PHY LDO channel and DSI bus directly, so unlike SPI/RGB panels it has no
|
|
||||||
parent bus controller device.
|
|
||||||
|
|
||||||
The ST7121 panel's in-cell touch controller is the same chip/protocol as the ST7123's (see
|
|
||||||
`st7123-module`'s `sitronix,st7123-touch` driver) - boards using this display reuse that driver
|
|
||||||
for touch rather than duplicating it here.
|
|
||||||
|
|
||||||
License: [Apache v2.0](LICENSE-Apache-2.0.md)
|
|
||||||
@ -1,111 +0,0 @@
|
|||||||
description: >
|
|
||||||
Sitronix ST7121 MIPI-DSI display panel, driven over ESP-IDF's esp_lcd_st7121 component (DPI/DBI interface, ESP32-P4 and other SOC_MIPI_DSI_SUPPORTED targets only). Owns the MIPI DSI PHY LDO channel and DSI bus directly, so unlike SPI/RGB panels it has no parent bus controller device.
|
|
||||||
|
|
||||||
compatible: "sitronix,st7121"
|
|
||||||
|
|
||||||
properties:
|
|
||||||
horizontal-resolution:
|
|
||||||
type: int
|
|
||||||
required: true
|
|
||||||
description: Horizontal resolution in pixels
|
|
||||||
vertical-resolution:
|
|
||||||
type: int
|
|
||||||
required: true
|
|
||||||
description: Vertical resolution in pixels
|
|
||||||
bits-per-pixel:
|
|
||||||
type: int
|
|
||||||
default: 16
|
|
||||||
description: Color depth in bits per pixel (16, 18 or 24)
|
|
||||||
bgr-order:
|
|
||||||
type: boolean
|
|
||||||
default: false
|
|
||||||
description: Use BGR element order instead of RGB
|
|
||||||
invert-color:
|
|
||||||
type: boolean
|
|
||||||
default: false
|
|
||||||
description: Invert the panel's color output
|
|
||||||
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. Falls back to a software reset (sent over the DBI command interface) if not set.
|
|
||||||
ldo-channel:
|
|
||||||
type: int
|
|
||||||
required: true
|
|
||||||
description: LDO channel index powering the MIPI DSI PHY (chan_id in esp_ldo_channel_config_t)
|
|
||||||
ldo-voltage-mv:
|
|
||||||
type: int
|
|
||||||
required: true
|
|
||||||
description: Voltage to supply to the MIPI DSI PHY LDO channel, in mV
|
|
||||||
dsi-bus-id:
|
|
||||||
type: int
|
|
||||||
default: 0
|
|
||||||
description: Which DSI controller to use, index from 0
|
|
||||||
num-data-lanes:
|
|
||||||
type: int
|
|
||||||
default: 2
|
|
||||||
description: Number of MIPI DSI data lanes. 0 falls back to the maximum available.
|
|
||||||
lane-bit-rate-mbps:
|
|
||||||
type: int
|
|
||||||
required: true
|
|
||||||
description: MIPI DSI lane bit rate, in Mbps
|
|
||||||
dpi-clock-freq-mhz:
|
|
||||||
type: int
|
|
||||||
required: true
|
|
||||||
description: MIPI DPI clock frequency, in MHz
|
|
||||||
hsync-pulse-width:
|
|
||||||
type: int
|
|
||||||
required: true
|
|
||||||
description: Horizontal sync width, in PCLK periods
|
|
||||||
hsync-back-porch:
|
|
||||||
type: int
|
|
||||||
required: true
|
|
||||||
description: Number of PCLK periods between hsync and the start of line active data
|
|
||||||
hsync-front-porch:
|
|
||||||
type: int
|
|
||||||
required: true
|
|
||||||
description: Number of PCLK periods between the end of active data and the next hsync
|
|
||||||
vsync-pulse-width:
|
|
||||||
type: int
|
|
||||||
required: true
|
|
||||||
description: Vertical sync width, in lines
|
|
||||||
vsync-back-porch:
|
|
||||||
type: int
|
|
||||||
required: true
|
|
||||||
description: Number of invalid lines between vsync and the start of the frame
|
|
||||||
vsync-front-porch:
|
|
||||||
type: int
|
|
||||||
required: true
|
|
||||||
description: Number of invalid lines between the end of the frame and the next vsync
|
|
||||||
num-fbs:
|
|
||||||
type: int
|
|
||||||
default: 1
|
|
||||||
description: Number of screen-sized frame buffers to allocate (0 or 1 = single-buffered)
|
|
||||||
use-dma2d:
|
|
||||||
type: boolean
|
|
||||||
default: true
|
|
||||||
description: Use DMA2D to copy user buffers into the frame buffer when necessary (only meaningful on SOC_DMA2D_SUPPORTED targets - must be false otherwise)
|
|
||||||
disable-lp:
|
|
||||||
type: boolean
|
|
||||||
default: false
|
|
||||||
description: Disable MIPI DSI low-power mode
|
|
||||||
allow-tearing:
|
|
||||||
type: boolean
|
|
||||||
default: false
|
|
||||||
description: By default, when draw_bitmap's color_data is one of the panel's own frame buffers (i.e. LVGL is bound directly onto them), draw_bitmap waits for a full scan-out to complete before returning so the caller can't start overwriting a buffer still being displayed. Set this to trade away that tear-free guarantee for lower latency (e.g. when other tasks occasionally block timing for long enough that waiting causes visible stalls).
|
|
||||||
init-sequence:
|
|
||||||
type: array
|
|
||||||
element-type: uint8_t
|
|
||||||
description: >
|
|
||||||
Custom vendor bring-up sequence, flattened into bytes as a run of [cmd, data-length, delay-ms, data-length bytes of data...] entries, e.g. `init-sequence = [0x11 0 120 0x29 0 20];`. Omit to use the ST7121 component's own built-in default sequence.
|
|
||||||
backlight:
|
|
||||||
type: phandle
|
|
||||||
default: "NULL"
|
|
||||||
description: Optional reference to this display's backlight device
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
dependencies:
|
|
||||||
- TactilityKernel
|
|
||||||
bindings: bindings
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <tactility/bindings/bindings.h>
|
|
||||||
#include <drivers/st7121.h>
|
|
||||||
|
|
||||||
// The devicetree compiler derives the expected config typedef name from the compatible
|
|
||||||
// string's suffix (e.g. "sitronix,st7121" -> st7121_config_dt), not from the node name or
|
|
||||||
// driver name, so the tag here must match that exactly.
|
|
||||||
DEFINE_DEVICETREE(st7121, struct St7121Config)
|
|
||||||
@ -1,68 +0,0 @@
|
|||||||
// 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 St7121Config {
|
|
||||||
uint16_t horizontal_resolution;
|
|
||||||
uint16_t vertical_resolution;
|
|
||||||
uint8_t bits_per_pixel;
|
|
||||||
bool bgr_order;
|
|
||||||
bool invert_color;
|
|
||||||
bool mirror_x;
|
|
||||||
bool mirror_y;
|
|
||||||
|
|
||||||
// Reset pin for the panel. GPIO_PIN_SPEC_NONE falls back to a software reset sent over the
|
|
||||||
// DBI command interface (see esp_lcd_st7121's panel_st7121_reset()).
|
|
||||||
struct GpioPinSpec pin_reset;
|
|
||||||
|
|
||||||
// LDO channel powering the MIPI DSI PHY - the PHY has no power of its own until this is
|
|
||||||
// acquired, so it must happen before the DSI bus is created. Both fields are int32_t (not
|
|
||||||
// uint32_t) to exactly match esp_ldo_channel_config_t's plain `int` fields - assigning a
|
|
||||||
// uint32_t into that struct's brace-init would be a narrowing conversion (-Werror=narrowing).
|
|
||||||
int32_t ldo_channel;
|
|
||||||
int32_t ldo_voltage_mv;
|
|
||||||
|
|
||||||
uint8_t dsi_bus_id;
|
|
||||||
// Number of MIPI DSI data lanes. 0 falls back to the maximum available.
|
|
||||||
uint8_t num_data_lanes;
|
|
||||||
uint32_t lane_bit_rate_mbps;
|
|
||||||
|
|
||||||
uint32_t dpi_clock_freq_mhz;
|
|
||||||
uint32_t hsync_pulse_width;
|
|
||||||
uint32_t hsync_back_porch;
|
|
||||||
uint32_t hsync_front_porch;
|
|
||||||
uint32_t vsync_pulse_width;
|
|
||||||
uint32_t vsync_back_porch;
|
|
||||||
uint32_t vsync_front_porch;
|
|
||||||
// Number of screen-sized frame buffers the driver allocates (0 or 1 = single-buffered).
|
|
||||||
uint8_t num_fbs;
|
|
||||||
bool use_dma2d;
|
|
||||||
bool disable_lp;
|
|
||||||
|
|
||||||
// See the 'allow-tearing' binding property.
|
|
||||||
bool allow_tearing;
|
|
||||||
|
|
||||||
// Custom vendor init sequence, flattened as bytes: a run of
|
|
||||||
// [cmd, data_len, delay_ms, data_len bytes of data...] entries. NULL/0 falls back to the
|
|
||||||
// ST7121 component's own built-in default sequence (see vendor_specific_init_default in
|
|
||||||
// esp_lcd_st7121.c) - not guaranteed to match any particular panel's actual bring-up
|
|
||||||
// requirements.
|
|
||||||
const uint8_t* init_sequence;
|
|
||||||
uint32_t init_sequence_length;
|
|
||||||
|
|
||||||
// Optional reference to this display's backlight device, NULL if none.
|
|
||||||
struct Device* backlight;
|
|
||||||
};
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <tactility/module.h>
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
extern struct Module st7121_module;
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
#include <tactility/driver.h>
|
|
||||||
#include <tactility/module.h>
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
|
|
||||||
extern Driver st7121_driver;
|
|
||||||
|
|
||||||
static Driver* const st7121_drivers[] = {
|
|
||||||
&st7121_driver,
|
|
||||||
nullptr
|
|
||||||
};
|
|
||||||
|
|
||||||
Module st7121_module = {
|
|
||||||
.name = "st7121",
|
|
||||||
.drivers = st7121_drivers
|
|
||||||
};
|
|
||||||
|
|
||||||
} // extern "C"
|
|
||||||
@ -1,590 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
#include <soc/soc_caps.h>
|
|
||||||
#if SOC_MIPI_DSI_SUPPORTED
|
|
||||||
|
|
||||||
#include <drivers/st7121.h>
|
|
||||||
#include <st7121_module.h>
|
|
||||||
|
|
||||||
#include <tactility/device.h>
|
|
||||||
#include <tactility/driver.h>
|
|
||||||
#include <tactility/drivers/display.h>
|
|
||||||
#include <tactility/error.h>
|
|
||||||
#include <tactility/log.h>
|
|
||||||
|
|
||||||
#include <esp_err.h>
|
|
||||||
#include <esp_ldo_regulator.h>
|
|
||||||
#include <esp_lcd_st7121.h>
|
|
||||||
#include <esp_lcd_mipi_dsi.h>
|
|
||||||
#include <esp_lcd_panel_io.h>
|
|
||||||
#include <esp_lcd_panel_ops.h>
|
|
||||||
|
|
||||||
#include <freertos/FreeRTOS.h>
|
|
||||||
#include <freertos/semphr.h>
|
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
|
|
||||||
constexpr auto* TAG = "ST7121";
|
|
||||||
#define GET_CONFIG(device) (static_cast<const St7121Config*>((device)->config))
|
|
||||||
|
|
||||||
// Generic lvgl-module display glue (Modules/lvgl-module/source/lvgl_display.c) only ever asks
|
|
||||||
// for frame buffer index 0 and 1, so caching more than that would be dead weight.
|
|
||||||
constexpr size_t MAX_CACHED_FRAME_BUFFERS = 2;
|
|
||||||
|
|
||||||
struct St7121Internal {
|
|
||||||
esp_ldo_channel_handle_t ldo_handle;
|
|
||||||
esp_lcd_dsi_bus_handle_t dsi_bus_handle;
|
|
||||||
esp_lcd_panel_io_handle_t io_handle;
|
|
||||||
esp_lcd_panel_handle_t panel_handle;
|
|
||||||
void* frame_buffers[MAX_CACHED_FRAME_BUFFERS];
|
|
||||||
uint8_t frame_buffer_count;
|
|
||||||
// Size of each buffer in frame_buffers, in bytes - used to range-check whether a given
|
|
||||||
// draw_bitmap() color_data pointer is actually one of them (see draw_bitmap() below).
|
|
||||||
size_t frame_buffer_size_bytes;
|
|
||||||
// Signaled by on_refresh_done once per real scan-out of a whole frame. Only waited on in
|
|
||||||
// draw_bitmap() when color_data is one of frame_buffers and avoid_tearing is set - see the
|
|
||||||
// comment there for why.
|
|
||||||
SemaphoreHandle_t frame_complete_semaphore;
|
|
||||||
// Signaled by on_color_trans_done once the panel driver's own copy/DMA2D transfer of a
|
|
||||||
// draw_bitmap() call's pixels into the target frame buffer completes. Unlike
|
|
||||||
// frame_complete_semaphore this is always waited on (regardless of allow_tearing or whether
|
|
||||||
// color_data is a real frame buffer) - see the comment on draw_bitmap() for why.
|
|
||||||
SemaphoreHandle_t color_trans_done_semaphore;
|
|
||||||
// Heap-allocated only when the devicetree supplies a custom init_sequence (see
|
|
||||||
// parse_init_sequence()) - nullptr otherwise, since the vendor's built-in default sequence
|
|
||||||
// needs no parsing. Its .data pointers alias directly into the devicetree's static const
|
|
||||||
// byte buffer, so only this struct array itself needs freeing in stop().
|
|
||||||
st7121_lcd_init_cmd_t* parsed_init_cmds;
|
|
||||||
};
|
|
||||||
|
|
||||||
// esp_lcd_dpi_panel's draw_bitmap() has a zero-copy path when color_data is one of the panel's
|
|
||||||
// own frame buffers (as returned by esp_lcd_dpi_panel_get_frame_buffer()): it just repoints which
|
|
||||||
// buffer is scanned out and returns almost instantly - well before the DSI peripheral has
|
|
||||||
// actually finished scanning out the *previous* buffer, let alone started on this one. Callers in
|
|
||||||
// full/direct LVGL render mode render straight into these real frame buffers, so if draw_bitmap()
|
|
||||||
// returned that quickly, LVGL would be free to start overwriting the *other* buffer - which may
|
|
||||||
// still be mid-scanout - producing visible tearing/flashing. on_refresh_done fires once per actual
|
|
||||||
// whole-frame scan-out completion (continuously, at the panel's refresh rate, independent of
|
|
||||||
// draw_bitmap calls), so waiting for the next occurrence after each draw_bitmap() genuinely blocks
|
|
||||||
// until it's safe to start writing into the frame buffers again - unless avoid_tearing is false,
|
|
||||||
// in which case the caller has opted out of this wait (see the binding property).
|
|
||||||
static bool on_refresh_done(esp_lcd_panel_handle_t, esp_lcd_dpi_panel_event_data_t*, void* user_ctx) {
|
|
||||||
auto* internal = static_cast<St7121Internal*>(user_ctx);
|
|
||||||
BaseType_t high_task_woken = pdFALSE;
|
|
||||||
xSemaphoreGiveFromISR(internal->frame_complete_semaphore, &high_task_woken);
|
|
||||||
return high_task_woken == pdTRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// esp_lcd_dpi_panel_draw_bitmap() copies (memcpy, or DMA2D when use_dma2d is set) the caller's
|
|
||||||
// pixels into the target frame buffer and returns immediately - the copy itself finishes
|
|
||||||
// asynchronously. Calling draw_bitmap() again before that copy completes hits the panel driver's
|
|
||||||
// own re-entrancy guard and logs "previous draw operation is not finished" (lcd.dsi). This is
|
|
||||||
// unconditional, unlike frame_complete_semaphore's tearing-avoidance wait: it protects the copy
|
|
||||||
// itself, not scan-out, so it must be waited on regardless of allow_tearing or which buffer
|
|
||||||
// color_data is.
|
|
||||||
//
|
|
||||||
// Unlike on_refresh_done (always ISR, from the VSYNC interrupt), esp_lcd_panel_dpi.c invokes this
|
|
||||||
// callback from two different contexts depending on the copy path: synchronously from the calling
|
|
||||||
// task for the CPU-memcpy and fb-direct (zero-copy) paths, but from an ISR (async_fbcpy_done_cb,
|
|
||||||
// the DMA2D completion interrupt) when use_dma2d is set. Giving a semaphore with the ISR-only API
|
|
||||||
// from task context is undefined behavior on FreeRTOS, so the context must be checked at runtime.
|
|
||||||
static bool on_color_trans_done(esp_lcd_panel_handle_t, esp_lcd_dpi_panel_event_data_t*, void* user_ctx) {
|
|
||||||
auto* internal = static_cast<St7121Internal*>(user_ctx);
|
|
||||||
if (xPortInIsrContext()) {
|
|
||||||
BaseType_t high_task_woken = pdFALSE;
|
|
||||||
xSemaphoreGiveFromISR(internal->color_trans_done_semaphore, &high_task_woken);
|
|
||||||
return high_task_woken == pdTRUE;
|
|
||||||
}
|
|
||||||
xSemaphoreGive(internal->color_trans_done_semaphore);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int pin_or_unused(const GpioPinSpec& pin) {
|
|
||||||
return pin.gpio_controller == nullptr ? -1 : static_cast<int>(pin.pin);
|
|
||||||
}
|
|
||||||
|
|
||||||
static lcd_color_format_t color_format_from_bits_per_pixel(uint8_t bits_per_pixel) {
|
|
||||||
switch (bits_per_pixel) {
|
|
||||||
case 18: return LCD_COLOR_FMT_RGB666;
|
|
||||||
case 24: return LCD_COLOR_FMT_RGB888;
|
|
||||||
default: return LCD_COLOR_FMT_RGB565;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unpacks the devicetree's flat [cmd, data_len, delay_ms, data_len bytes...] encoding (produced
|
|
||||||
// by the devicetree compiler's "array" property type - see init-sequence in
|
|
||||||
// bindings/sitronix,st7121.yaml) into a heap-allocated st7121_lcd_init_cmd_t array. Each entry's
|
|
||||||
// .data points directly into `bytes`, which is the devicetree's static const buffer and outlives
|
|
||||||
// the device, so no per-entry copy is needed.
|
|
||||||
static bool parse_init_sequence(const uint8_t* bytes, uint32_t length, st7121_lcd_init_cmd_t** out_cmds, uint16_t* out_count) {
|
|
||||||
uint32_t count = 0;
|
|
||||||
for (uint32_t offset = 0; offset < length; count++) {
|
|
||||||
if (offset + 3 > length) {
|
|
||||||
LOG_E(TAG, "init-sequence truncated: entry header runs past the end of the array");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
offset += 3 + bytes[offset + 1];
|
|
||||||
if (offset > length) {
|
|
||||||
LOG_E(TAG, "init-sequence truncated: entry data runs past the end of the array");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto* cmds = static_cast<st7121_lcd_init_cmd_t*>(malloc(count * sizeof(st7121_lcd_init_cmd_t)));
|
|
||||||
if (cmds == nullptr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t offset = 0;
|
|
||||||
for (uint32_t i = 0; i < count; i++) {
|
|
||||||
uint8_t data_len = bytes[offset + 1];
|
|
||||||
cmds[i] = {
|
|
||||||
.cmd = bytes[offset],
|
|
||||||
.data = data_len > 0 ? &bytes[offset + 3] : nullptr,
|
|
||||||
.data_bytes = data_len,
|
|
||||||
.delay_ms = bytes[offset + 2],
|
|
||||||
};
|
|
||||||
offset += 3 + data_len;
|
|
||||||
}
|
|
||||||
|
|
||||||
*out_cmds = cmds;
|
|
||||||
*out_count = (uint16_t)count;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// region Driver lifecycle
|
|
||||||
|
|
||||||
static error_t start(Device* device) {
|
|
||||||
const auto* config = GET_CONFIG(device);
|
|
||||||
|
|
||||||
auto* internal = static_cast<St7121Internal*>(malloc(sizeof(St7121Internal)));
|
|
||||||
if (internal == nullptr) {
|
|
||||||
return ERROR_OUT_OF_MEMORY;
|
|
||||||
}
|
|
||||||
internal->parsed_init_cmds = nullptr;
|
|
||||||
|
|
||||||
const st7121_lcd_init_cmd_t* init_cmds = nullptr;
|
|
||||||
uint16_t init_cmds_size = 0;
|
|
||||||
if (config->init_sequence != nullptr && config->init_sequence_length > 0) {
|
|
||||||
if (!parse_init_sequence(config->init_sequence, config->init_sequence_length, &internal->parsed_init_cmds, &init_cmds_size)) {
|
|
||||||
LOG_E(TAG, "Failed to parse init-sequence property");
|
|
||||||
free(internal);
|
|
||||||
return ERROR_INVALID_ARGUMENT;
|
|
||||||
}
|
|
||||||
init_cmds = internal->parsed_init_cmds;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The MIPI DSI PHY has no power of its own until this LDO channel is enabled - must happen
|
|
||||||
// before the DSI bus is created.
|
|
||||||
esp_ldo_channel_config_t ldo_config = {
|
|
||||||
.chan_id = config->ldo_channel,
|
|
||||||
.voltage_mv = config->ldo_voltage_mv,
|
|
||||||
.flags = {},
|
|
||||||
};
|
|
||||||
if (esp_ldo_acquire_channel(&ldo_config, &internal->ldo_handle) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to acquire LDO channel for MIPI DSI PHY");
|
|
||||||
free(internal->parsed_init_cmds);
|
|
||||||
free(internal);
|
|
||||||
return ERROR_RESOURCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
const esp_lcd_dsi_bus_config_t bus_config = {
|
|
||||||
.bus_id = config->dsi_bus_id,
|
|
||||||
.num_data_lanes = config->num_data_lanes,
|
|
||||||
.phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT,
|
|
||||||
.lane_bit_rate_mbps = config->lane_bit_rate_mbps,
|
|
||||||
};
|
|
||||||
if (esp_lcd_new_dsi_bus(&bus_config, &internal->dsi_bus_handle) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to create MIPI DSI bus");
|
|
||||||
esp_ldo_release_channel(internal->ldo_handle);
|
|
||||||
free(internal->parsed_init_cmds);
|
|
||||||
free(internal);
|
|
||||||
return ERROR_RESOURCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
const esp_lcd_dbi_io_config_t dbi_config = {
|
|
||||||
.virtual_channel = 0,
|
|
||||||
.lcd_cmd_bits = 8,
|
|
||||||
.lcd_param_bits = 8,
|
|
||||||
};
|
|
||||||
if (esp_lcd_new_panel_io_dbi(internal->dsi_bus_handle, &dbi_config, &internal->io_handle) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to create panel IO");
|
|
||||||
esp_lcd_del_dsi_bus(internal->dsi_bus_handle);
|
|
||||||
esp_ldo_release_channel(internal->ldo_handle);
|
|
||||||
free(internal->parsed_init_cmds);
|
|
||||||
free(internal);
|
|
||||||
return ERROR_RESOURCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lcd_color_format_t color_format = color_format_from_bits_per_pixel(config->bits_per_pixel);
|
|
||||||
const esp_lcd_dpi_panel_config_t dpi_config = {
|
|
||||||
.virtual_channel = 0,
|
|
||||||
.dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT,
|
|
||||||
.dpi_clock_freq_mhz = config->dpi_clock_freq_mhz,
|
|
||||||
.pixel_format = (lcd_color_rgb_pixel_format_t)0, // deprecated field - in/out_color_format below take precedence
|
|
||||||
.in_color_format = color_format,
|
|
||||||
.out_color_format = color_format,
|
|
||||||
.num_fbs = config->num_fbs,
|
|
||||||
.video_timing = {
|
|
||||||
.h_size = config->horizontal_resolution,
|
|
||||||
.v_size = config->vertical_resolution,
|
|
||||||
.hsync_pulse_width = config->hsync_pulse_width,
|
|
||||||
.hsync_back_porch = config->hsync_back_porch,
|
|
||||||
.hsync_front_porch = config->hsync_front_porch,
|
|
||||||
.vsync_pulse_width = config->vsync_pulse_width,
|
|
||||||
.vsync_back_porch = config->vsync_back_porch,
|
|
||||||
.vsync_front_porch = config->vsync_front_porch,
|
|
||||||
},
|
|
||||||
.flags = {
|
|
||||||
.use_dma2d = config->use_dma2d,
|
|
||||||
.disable_lp = config->disable_lp,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
st7121_vendor_config_t vendor_config = {
|
|
||||||
.init_cmds = init_cmds,
|
|
||||||
.init_cmds_size = init_cmds_size,
|
|
||||||
.mipi_config = {
|
|
||||||
.dsi_bus = internal->dsi_bus_handle,
|
|
||||||
.dpi_config = &dpi_config,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const 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,
|
|
||||||
// ST7121's reset line is fixed active-low in hardware.
|
|
||||||
.flags = { .reset_active_high = false },
|
|
||||||
.vendor_config = &vendor_config,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (esp_lcd_new_panel_st7121(internal->io_handle, &panel_config, &internal->panel_handle) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to create panel");
|
|
||||||
esp_lcd_panel_io_del(internal->io_handle);
|
|
||||||
esp_lcd_del_dsi_bus(internal->dsi_bus_handle);
|
|
||||||
esp_ldo_release_channel(internal->ldo_handle);
|
|
||||||
free(internal->parsed_init_cmds);
|
|
||||||
free(internal);
|
|
||||||
return ERROR_RESOURCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bring-up sequence: reset() pulses (or software-resets) the panel and init() pushes
|
|
||||||
// init_cmds over the DBI command interface before bringing up the underlying DPI peripheral.
|
|
||||||
// swap_xy/set_gap are intentionally not called: the ST7121 driver doesn't override them and
|
|
||||||
// the underlying raw DPI panel doesn't implement them either, so both would just fail with
|
|
||||||
// ESP_ERR_NOT_SUPPORTED (see DisplayApi below). Every failure path here 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 &&
|
|
||||||
esp_lcd_panel_invert_color(internal->panel_handle, config->invert_color) == ESP_OK &&
|
|
||||||
esp_lcd_panel_mirror(internal->panel_handle, config->mirror_x, config->mirror_y) == ESP_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);
|
|
||||||
esp_lcd_del_dsi_bus(internal->dsi_bus_handle);
|
|
||||||
esp_ldo_release_channel(internal->ldo_handle);
|
|
||||||
free(internal->parsed_init_cmds);
|
|
||||||
free(internal);
|
|
||||||
return ERROR_RESOURCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal->frame_buffer_count = 0;
|
|
||||||
internal->frame_buffer_size_bytes = (size_t)config->horizontal_resolution * config->vertical_resolution *
|
|
||||||
((config->bits_per_pixel + 7) / 8);
|
|
||||||
if (config->num_fbs > 0) {
|
|
||||||
// esp_lcd_dpi_panel_get_frame_buffer() is variadic: the number of out-pointer arguments
|
|
||||||
// passed must match fb_num exactly, so this can't be a loop.
|
|
||||||
size_t fb_num = config->num_fbs < MAX_CACHED_FRAME_BUFFERS ? config->num_fbs : MAX_CACHED_FRAME_BUFFERS;
|
|
||||||
esp_err_t ret;
|
|
||||||
switch (fb_num) {
|
|
||||||
case 1:
|
|
||||||
ret = esp_lcd_dpi_panel_get_frame_buffer(internal->panel_handle, 1, &internal->frame_buffers[0]);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
ret = esp_lcd_dpi_panel_get_frame_buffer(internal->panel_handle, 2, &internal->frame_buffers[0], &internal->frame_buffers[1]);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
ret = ESP_OK;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ret != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to get frame buffer(s): %s", esp_err_to_name(ret));
|
|
||||||
esp_lcd_panel_del(internal->panel_handle);
|
|
||||||
esp_lcd_panel_io_del(internal->io_handle);
|
|
||||||
esp_lcd_del_dsi_bus(internal->dsi_bus_handle);
|
|
||||||
esp_ldo_release_channel(internal->ldo_handle);
|
|
||||||
free(internal->parsed_init_cmds);
|
|
||||||
free(internal);
|
|
||||||
return ERROR_RESOURCE;
|
|
||||||
}
|
|
||||||
internal->frame_buffer_count = (uint8_t)fb_num;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal->frame_complete_semaphore = xSemaphoreCreateBinary();
|
|
||||||
if (internal->frame_complete_semaphore == nullptr) {
|
|
||||||
esp_lcd_panel_del(internal->panel_handle);
|
|
||||||
esp_lcd_panel_io_del(internal->io_handle);
|
|
||||||
esp_lcd_del_dsi_bus(internal->dsi_bus_handle);
|
|
||||||
esp_ldo_release_channel(internal->ldo_handle);
|
|
||||||
free(internal->parsed_init_cmds);
|
|
||||||
free(internal);
|
|
||||||
return ERROR_OUT_OF_MEMORY;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal->color_trans_done_semaphore = xSemaphoreCreateBinary();
|
|
||||||
if (internal->color_trans_done_semaphore == nullptr) {
|
|
||||||
vSemaphoreDelete(internal->frame_complete_semaphore);
|
|
||||||
esp_lcd_panel_del(internal->panel_handle);
|
|
||||||
esp_lcd_panel_io_del(internal->io_handle);
|
|
||||||
esp_lcd_del_dsi_bus(internal->dsi_bus_handle);
|
|
||||||
esp_ldo_release_channel(internal->ldo_handle);
|
|
||||||
free(internal->parsed_init_cmds);
|
|
||||||
free(internal);
|
|
||||||
return ERROR_OUT_OF_MEMORY;
|
|
||||||
}
|
|
||||||
// The panel starts out idle (no draw_bitmap() call in flight), so the first draw_bitmap()
|
|
||||||
// must not block waiting for a completion event that will never come.
|
|
||||||
xSemaphoreGive(internal->color_trans_done_semaphore);
|
|
||||||
|
|
||||||
esp_lcd_dpi_panel_event_callbacks_t callbacks = {};
|
|
||||||
callbacks.on_refresh_done = on_refresh_done;
|
|
||||||
callbacks.on_color_trans_done = on_color_trans_done;
|
|
||||||
if (esp_lcd_dpi_panel_register_event_callbacks(internal->panel_handle, &callbacks, internal) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to register panel event callbacks");
|
|
||||||
vSemaphoreDelete(internal->color_trans_done_semaphore);
|
|
||||||
vSemaphoreDelete(internal->frame_complete_semaphore);
|
|
||||||
esp_lcd_panel_del(internal->panel_handle);
|
|
||||||
esp_lcd_panel_io_del(internal->io_handle);
|
|
||||||
esp_lcd_del_dsi_bus(internal->dsi_bus_handle);
|
|
||||||
esp_ldo_release_channel(internal->ldo_handle);
|
|
||||||
free(internal->parsed_init_cmds);
|
|
||||||
free(internal);
|
|
||||||
return ERROR_RESOURCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
device_set_driver_data(device, internal);
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t stop(Device* device) {
|
|
||||||
auto* internal = static_cast<St7121Internal*>(device_get_driver_data(device));
|
|
||||||
|
|
||||||
if (internal->panel_handle != nullptr) {
|
|
||||||
if (esp_lcd_panel_del(internal->panel_handle) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to delete panel");
|
|
||||||
return ERROR_RESOURCE;
|
|
||||||
}
|
|
||||||
internal->panel_handle = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (internal->io_handle != nullptr) {
|
|
||||||
if (esp_lcd_panel_io_del(internal->io_handle) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to delete panel IO");
|
|
||||||
return ERROR_RESOURCE;
|
|
||||||
}
|
|
||||||
internal->io_handle = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (internal->dsi_bus_handle != nullptr) {
|
|
||||||
if (esp_lcd_del_dsi_bus(internal->dsi_bus_handle) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to delete DSI bus");
|
|
||||||
return ERROR_RESOURCE;
|
|
||||||
}
|
|
||||||
internal->dsi_bus_handle = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (internal->ldo_handle != nullptr) {
|
|
||||||
if (esp_ldo_release_channel(internal->ldo_handle) != ESP_OK) {
|
|
||||||
LOG_E(TAG, "Failed to release LDO channel");
|
|
||||||
return ERROR_RESOURCE;
|
|
||||||
}
|
|
||||||
internal->ldo_handle = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
vSemaphoreDelete(internal->frame_complete_semaphore);
|
|
||||||
vSemaphoreDelete(internal->color_trans_done_semaphore);
|
|
||||||
free(internal->parsed_init_cmds);
|
|
||||||
free(internal);
|
|
||||||
device_set_driver_data(device, nullptr);
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region DisplayApi
|
|
||||||
|
|
||||||
static error_t st7121_reset(Device* device) {
|
|
||||||
auto* internal = static_cast<St7121Internal*>(device_get_driver_data(device));
|
|
||||||
return esp_lcd_panel_reset(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t st7121_init(Device* device) {
|
|
||||||
auto* internal = static_cast<St7121Internal*>(device_get_driver_data(device));
|
|
||||||
return esp_lcd_panel_init(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only block for scan-out completion when color_data is actually one of the panel's own frame
|
|
||||||
// buffers (see on_refresh_done's comment above for why that matters) and allow_tearing is not
|
|
||||||
// set - i.e. this specific call is a zero-copy flip, not a plain CPU copy into the panel's buffer
|
|
||||||
// from a caller-owned one (e.g. LVGL bound in owned-buffer mode), which has no reuse race to
|
|
||||||
// guard against and shouldn't pay the up-to-one-frame latency cost for every partial update.
|
|
||||||
static bool st7121_color_data_is_frame_buffer(const St7121Internal* internal, const void* color_data) {
|
|
||||||
const auto* ptr = static_cast<const uint8_t*>(color_data);
|
|
||||||
for (uint8_t i = 0; i < internal->frame_buffer_count; i++) {
|
|
||||||
const auto* base = static_cast<const uint8_t*>(internal->frame_buffers[i]);
|
|
||||||
if (ptr >= base && ptr < base + internal->frame_buffer_size_bytes) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t st7121_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<St7121Internal*>(device_get_driver_data(device));
|
|
||||||
|
|
||||||
// Wait for the previous draw_bitmap()'s copy/DMA2D transfer to finish before issuing a new
|
|
||||||
// one - see on_color_trans_done's comment for why this is unconditional (unlike the
|
|
||||||
// tearing-avoidance wait below). The semaphore starts pre-given (see start()), so the first
|
|
||||||
// call doesn't block here.
|
|
||||||
xSemaphoreTake(internal->color_trans_done_semaphore, portMAX_DELAY);
|
|
||||||
|
|
||||||
bool wait_for_scanout = !GET_CONFIG(device)->allow_tearing && st7121_color_data_is_frame_buffer(internal, color_data);
|
|
||||||
if (wait_for_scanout) {
|
|
||||||
xSemaphoreTake(internal->frame_complete_semaphore, 0); // clear any already-pending signal
|
|
||||||
}
|
|
||||||
|
|
||||||
if (esp_lcd_panel_draw_bitmap(internal->panel_handle, x_start, y_start, x_end, y_end, color_data) != ESP_OK) {
|
|
||||||
xSemaphoreGive(internal->color_trans_done_semaphore);
|
|
||||||
return ERROR_RESOURCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also wait for *this* call's own copy to finish before returning: color_data may be a
|
|
||||||
// caller-owned buffer (e.g. LVGL's draw buffer in owned-buffer/PARTIAL mode), and the
|
|
||||||
// CPU-memcpy/DMA2D copy paths both read directly from it asynchronously - draw_bitmap()
|
|
||||||
// returning early would let the caller start overwriting color_data (DisplayApi's contract
|
|
||||||
// treats draw_bitmap as synchronous - see lvgl_display_flush_cb()'s lv_display_flush_ready()
|
|
||||||
// call right after) while that read is still in flight. on_color_trans_done() re-gives the
|
|
||||||
// semaphore here, restoring the pre-given/idle state for the next call.
|
|
||||||
xSemaphoreTake(internal->color_trans_done_semaphore, portMAX_DELAY);
|
|
||||||
xSemaphoreGive(internal->color_trans_done_semaphore);
|
|
||||||
|
|
||||||
if (wait_for_scanout) {
|
|
||||||
xSemaphoreTake(internal->frame_complete_semaphore, portMAX_DELAY);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mirror is always implemented by LCD command (MADCTL), unconditionally, by esp_lcd_st7121 - see
|
|
||||||
// panel_st7121_mirror() in esp_lcd_st7121.c. Unlike esp_lcd_rgb_panel's software rotate_mask
|
|
||||||
// trick, this isn't tied to draw_bitmap's copy path, so it stays available even when LVGL is
|
|
||||||
// bound directly onto the panel's own frame buffers.
|
|
||||||
static error_t st7121_mirror(Device* device, bool x_axis, bool y_axis) {
|
|
||||||
auto* internal = static_cast<St7121Internal*>(device_get_driver_data(device));
|
|
||||||
return esp_lcd_panel_mirror(internal->panel_handle, x_axis, y_axis) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool st7121_get_mirror_x(Device* device) {
|
|
||||||
return GET_CONFIG(device)->mirror_x;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool st7121_get_mirror_y(Device* device) {
|
|
||||||
return GET_CONFIG(device)->mirror_y;
|
|
||||||
}
|
|
||||||
|
|
||||||
// swap_xy/set_gap are not exposed: esp_lcd_st7121 doesn't override them and the underlying raw
|
|
||||||
// MIPI DPI panel doesn't implement them either (esp_lcd_panel_dpi.c never assigns those function
|
|
||||||
// pointers), so esp_lcd_panel_swap_xy()/set_gap() would just return ESP_ERR_NOT_SUPPORTED.
|
|
||||||
|
|
||||||
static error_t st7121_invert_color(Device* device, bool invert_color_data) {
|
|
||||||
auto* internal = static_cast<St7121Internal*>(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 st7121_disp_on_off(Device* device, bool on_off) {
|
|
||||||
auto* internal = static_cast<St7121Internal*>(device_get_driver_data(device));
|
|
||||||
return esp_lcd_panel_disp_on_off(internal->panel_handle, on_off) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// disp_sleep is not exposed: esp_lcd_st7121 doesn't override it either (only
|
|
||||||
// del/init/reset/mirror/invert_color/disp_on_off - see esp_lcd_st7121.c).
|
|
||||||
|
|
||||||
// bgr_order only selects the panel controller's rgb_ele_order (applied in start(), above) so the
|
|
||||||
// R/B swap happens on-chip. LVGL always fills the same-layout buffer either way - there's no
|
|
||||||
// separate "BGR" memory layout to produce, unlike the SPI byte-order swap some other panels need.
|
|
||||||
static enum DisplayColorFormat st7121_get_color_format(Device* device) {
|
|
||||||
return GET_CONFIG(device)->bits_per_pixel == 24 ? DISPLAY_COLOR_FORMAT_RGB888 : DISPLAY_COLOR_FORMAT_RGB565;
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint16_t st7121_get_resolution_x(Device* device) {
|
|
||||||
return GET_CONFIG(device)->horizontal_resolution;
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint16_t st7121_get_resolution_y(Device* device) {
|
|
||||||
return GET_CONFIG(device)->vertical_resolution;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void st7121_get_frame_buffer(Device* device, uint8_t index, void** out_buffer) {
|
|
||||||
auto* internal = static_cast<St7121Internal*>(device_get_driver_data(device));
|
|
||||||
*out_buffer = index < internal->frame_buffer_count ? internal->frame_buffers[index] : nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint8_t st7121_get_frame_buffer_count(Device* device) {
|
|
||||||
auto* internal = static_cast<St7121Internal*>(device_get_driver_data(device));
|
|
||||||
return internal->frame_buffer_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
static error_t st7121_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 st7121_display_api = {
|
|
||||||
.capabilities = DISPLAY_CAPABILITY_CAP_MIRROR | DISPLAY_CAPABILITY_INVERT_COLOR |
|
|
||||||
DISPLAY_CAPABILITY_ON_OFF | DISPLAY_CAPABILITY_BACKLIGHT,
|
|
||||||
.reset = st7121_reset,
|
|
||||||
.init = st7121_init,
|
|
||||||
.draw_bitmap = st7121_draw_bitmap,
|
|
||||||
.mirror = st7121_mirror,
|
|
||||||
.swap_xy = nullptr,
|
|
||||||
.get_swap_xy = nullptr,
|
|
||||||
.get_mirror_x = st7121_get_mirror_x,
|
|
||||||
.get_mirror_y = st7121_get_mirror_y,
|
|
||||||
.set_gap = nullptr,
|
|
||||||
.invert_color = st7121_invert_color,
|
|
||||||
.disp_on_off = st7121_disp_on_off,
|
|
||||||
.disp_sleep = nullptr,
|
|
||||||
.get_color_format = st7121_get_color_format,
|
|
||||||
.get_resolution_x = st7121_get_resolution_x,
|
|
||||||
.get_resolution_y = st7121_get_resolution_y,
|
|
||||||
.get_frame_buffer = st7121_get_frame_buffer,
|
|
||||||
.get_frame_buffer_count = st7121_get_frame_buffer_count,
|
|
||||||
.get_backlight = st7121_get_backlight,
|
|
||||||
.has_capability = nullptr,
|
|
||||||
};
|
|
||||||
|
|
||||||
Driver st7121_driver = {
|
|
||||||
.name = "st7121",
|
|
||||||
.compatible = (const char*[]) { "sitronix,st7121", nullptr },
|
|
||||||
.start_device = start,
|
|
||||||
.stop_device = stop,
|
|
||||||
.api = &st7121_display_api,
|
|
||||||
.device_type = &DISPLAY_TYPE,
|
|
||||||
.owner = &st7121_module,
|
|
||||||
.internal = nullptr
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // SOC_MIPI_DSI_SUPPORTED
|
|
||||||
@ -100,7 +100,6 @@ else ()
|
|||||||
gps-module
|
gps-module
|
||||||
gps-generic-module
|
gps-generic-module
|
||||||
gps-meshtastic-module
|
gps-meshtastic-module
|
||||||
service-module
|
|
||||||
SDL2::SDL2-static
|
SDL2::SDL2-static
|
||||||
SDL2-static
|
SDL2-static
|
||||||
)
|
)
|
||||||
|
|||||||
@ -58,11 +58,6 @@ dependencies:
|
|||||||
rules:
|
rules:
|
||||||
# More hardware seems to be supported - enable as needed
|
# More hardware seems to be supported - enable as needed
|
||||||
- if: "target in [esp32p4]"
|
- if: "target in [esp32p4]"
|
||||||
espressif/esp_lcd_st7121:
|
|
||||||
version: "1.0.1"
|
|
||||||
rules:
|
|
||||||
# More hardware seems to be supported - enable as needed
|
|
||||||
- if: "target in [esp32p4]"
|
|
||||||
espressif/esp_lcd_touch_st7123:
|
espressif/esp_lcd_touch_st7123:
|
||||||
version: "1.0.1"
|
version: "1.0.1"
|
||||||
rules:
|
rules:
|
||||||
|
|||||||
@ -8,5 +8,5 @@ tactility_add_module(gps-module
|
|||||||
SRCS ${SOURCE_FILES}
|
SRCS ${SOURCE_FILES}
|
||||||
PRIV_INCLUDE_DIRS private/
|
PRIV_INCLUDE_DIRS private/
|
||||||
INCLUDE_DIRS include/
|
INCLUDE_DIRS include/
|
||||||
REQUIRES TactilityKernel service-module minmea
|
REQUIRES TactilityKernel minmea
|
||||||
)
|
)
|
||||||
|
|||||||
@ -2,10 +2,9 @@
|
|||||||
#include <gps/gps_settings.h>
|
#include <gps/gps_settings.h>
|
||||||
#include <gps/private/gps_ledger.h>
|
#include <gps/private/gps_ledger.h>
|
||||||
|
|
||||||
#include <service/paths.h>
|
|
||||||
|
|
||||||
#include <tactility/filesystem/file_mutex.h>
|
#include <tactility/filesystem/file_mutex.h>
|
||||||
#include <tactility/log.h>
|
#include <tactility/log.h>
|
||||||
|
#include <tactility/service/service_paths.h>
|
||||||
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.20)
|
|
||||||
|
|
||||||
include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake")
|
|
||||||
|
|
||||||
file(GLOB_RECURSE SOURCE_FILES "source/*.c*")
|
|
||||||
|
|
||||||
tactility_add_module(service-module
|
|
||||||
SRCS ${SOURCE_FILES}
|
|
||||||
INCLUDE_DIRS include/
|
|
||||||
REQUIRES TactilityKernel
|
|
||||||
)
|
|
||||||
@ -1,195 +0,0 @@
|
|||||||
Apache License
|
|
||||||
==============
|
|
||||||
|
|
||||||
_Version 2.0, January 2004_
|
|
||||||
_<<http://www.apache.org/licenses/>>_
|
|
||||||
|
|
||||||
### 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.
|
|
||||||
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
dependencies:
|
|
||||||
- TactilityKernel
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
#include <tactility/module.h>
|
|
||||||
#include <service/instance.h>
|
|
||||||
#include <service/manager.h>
|
|
||||||
#include <service/paths.h>
|
|
||||||
|
|
||||||
const ModuleSymbol service_module_symbols[] = {
|
|
||||||
// service/service_instance
|
|
||||||
DEFINE_MODULE_SYMBOL(service_instance_construct),
|
|
||||||
DEFINE_MODULE_SYMBOL(service_instance_destruct),
|
|
||||||
DEFINE_MODULE_SYMBOL(service_instance_get_manifest),
|
|
||||||
DEFINE_MODULE_SYMBOL(service_instance_get_data),
|
|
||||||
DEFINE_MODULE_SYMBOL(service_instance_get_state),
|
|
||||||
// service/service_manager
|
|
||||||
DEFINE_MODULE_SYMBOL(service_manager_add),
|
|
||||||
DEFINE_MODULE_SYMBOL(service_manager_remove),
|
|
||||||
DEFINE_MODULE_SYMBOL(service_manager_find_manifest),
|
|
||||||
DEFINE_MODULE_SYMBOL(service_manager_start),
|
|
||||||
DEFINE_MODULE_SYMBOL(service_manager_stop),
|
|
||||||
DEFINE_MODULE_SYMBOL(service_manager_get_state),
|
|
||||||
DEFINE_MODULE_SYMBOL(service_manager_find_instance),
|
|
||||||
// service/service_paths
|
|
||||||
DEFINE_MODULE_SYMBOL(service_paths_get_user_data_directory),
|
|
||||||
DEFINE_MODULE_SYMBOL(service_paths_get_user_data_path),
|
|
||||||
DEFINE_MODULE_SYMBOL(service_paths_get_assets_directory),
|
|
||||||
DEFINE_MODULE_SYMBOL(service_paths_get_assets_path),
|
|
||||||
// terminator
|
|
||||||
MODULE_SYMBOL_TERMINATOR
|
|
||||||
};
|
|
||||||
|
|
||||||
Module service_module = {
|
|
||||||
.name = "service",
|
|
||||||
.drivers = nullptr,
|
|
||||||
.symbols = service_module_symbols,
|
|
||||||
.internal = nullptr
|
|
||||||
};
|
|
||||||
@ -12,7 +12,6 @@ list(APPEND REQUIRES_LIST
|
|||||||
gps-module
|
gps-module
|
||||||
gps-generic-module
|
gps-generic-module
|
||||||
gps-meshtastic-module
|
gps-meshtastic-module
|
||||||
service-module
|
|
||||||
lv_screenshot
|
lv_screenshot
|
||||||
minitar
|
minitar
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <service/instance.h>
|
#include <tactility/service/service_instance.h>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <service/manifest.h>
|
#include <tactility/service/service_manifest.h>
|
||||||
|
|
||||||
struct ServiceInstance;
|
struct ServiceInstance;
|
||||||
|
|
||||||
|
|||||||
@ -2,9 +2,8 @@
|
|||||||
|
|
||||||
#include <tactility/check.h>
|
#include <tactility/check.h>
|
||||||
#include <tactility/error.h>
|
#include <tactility/error.h>
|
||||||
|
#include <tactility/service/service_manifest.h>
|
||||||
#include <service/manifest.h>
|
#include <tactility/service/service_paths.h>
|
||||||
#include <service/paths.h>
|
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
#include <Tactility/service/ServiceContext.h>
|
#include <Tactility/service/ServiceContext.h>
|
||||||
|
|
||||||
#include <Tactility/service/ServicePaths.h>
|
#include <Tactility/service/ServicePaths.h>
|
||||||
#include <service/instance.h>
|
|
||||||
|
#include <tactility/service/service_instance.h>
|
||||||
|
|
||||||
namespace tt::service {
|
namespace tt::service {
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
#include <service/manager.h>
|
|
||||||
|
|
||||||
#include <Tactility/service/ServiceRegistration.h>
|
#include <Tactility/service/ServiceRegistration.h>
|
||||||
|
|
||||||
#include <Tactility/Mutex.h>
|
#include <Tactility/Mutex.h>
|
||||||
#include <Tactility/service/ServiceContext.h>
|
#include <Tactility/service/ServiceContext.h>
|
||||||
#include <Tactility/service/ServiceManifest.h>
|
#include <Tactility/service/ServiceManifest.h>
|
||||||
|
|
||||||
#include <tactility/error.h>
|
#include <tactility/error.h>
|
||||||
#include <tactility/log.h>
|
#include <tactility/log.h>
|
||||||
|
#include <tactility/service/service_manager.h>
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|||||||
@ -64,6 +64,7 @@ error_t keyboard_read_key(struct Device* device, struct KeyboardKeyData* data);
|
|||||||
*/
|
*/
|
||||||
error_t keyboard_get_backlight(struct Device* device, struct Device** backlight_device);
|
error_t keyboard_get_backlight(struct Device* device, struct Device** backlight_device);
|
||||||
|
|
||||||
|
|
||||||
extern const struct DeviceType KEYBOARD_TYPE;
|
extern const struct DeviceType KEYBOARD_TYPE;
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|||||||
@ -2,9 +2,8 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <service/manifest.h>
|
|
||||||
|
|
||||||
#include <tactility/error.h>
|
#include <tactility/error.h>
|
||||||
|
#include <tactility/service/service_manifest.h>
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@ -3,9 +3,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <service/instance.h>
|
|
||||||
#include <service/manifest.h>
|
|
||||||
#include <tactility/error.h>
|
#include <tactility/error.h>
|
||||||
|
#include <tactility/service/service_instance.h>
|
||||||
|
#include <tactility/service/service_manifest.h>
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@ -1,7 +1,6 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
#include <service/instance.h>
|
#include <tactility/service/service_instance.h>
|
||||||
|
|
||||||
#include <tactility/concurrent/mutex.h>
|
#include <tactility/concurrent/mutex.h>
|
||||||
#include <tactility/log.h>
|
#include <tactility/log.h>
|
||||||
|
|
||||||
@ -1,10 +1,11 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
#include <service/manager.h>
|
#include <tactility/service/service_manager.h>
|
||||||
|
|
||||||
|
#include "tactility/system_event.h"
|
||||||
|
|
||||||
#include <tactility/concurrent/mutex.h>
|
#include <tactility/concurrent/mutex.h>
|
||||||
#include <tactility/log.h>
|
#include <tactility/log.h>
|
||||||
#include <tactility/system_event.h>
|
|
||||||
|
|
||||||
#include <new>
|
#include <new>
|
||||||
#include <string>
|
#include <string>
|
||||||
@ -1,6 +1,6 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
#include <service/paths.h>
|
#include <tactility/service/service_paths.h>
|
||||||
#include <tactility/paths.h>
|
#include <tactility/paths.h>
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
@ -41,6 +41,9 @@
|
|||||||
#include <tactility/filesystem/file_system.h>
|
#include <tactility/filesystem/file_system.h>
|
||||||
#include <tactility/memory.h>
|
#include <tactility/memory.h>
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
#include <tactility/service/service_instance.h>
|
||||||
|
#include <tactility/service/service_manager.h>
|
||||||
|
#include <tactility/service/service_paths.h>
|
||||||
#include <tactility/wifi_auto_scan.h>
|
#include <tactility/wifi_auto_scan.h>
|
||||||
|
|
||||||
#ifndef ESP_PLATFORM
|
#ifndef ESP_PLATFORM
|
||||||
@ -470,6 +473,25 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = {
|
|||||||
DEFINE_MODULE_SYMBOL(module_is_started),
|
DEFINE_MODULE_SYMBOL(module_is_started),
|
||||||
DEFINE_MODULE_SYMBOL(module_resolve_symbol),
|
DEFINE_MODULE_SYMBOL(module_resolve_symbol),
|
||||||
DEFINE_MODULE_SYMBOL(module_resolve_symbol_global),
|
DEFINE_MODULE_SYMBOL(module_resolve_symbol_global),
|
||||||
|
// service/service_instance
|
||||||
|
DEFINE_MODULE_SYMBOL(service_instance_construct),
|
||||||
|
DEFINE_MODULE_SYMBOL(service_instance_destruct),
|
||||||
|
DEFINE_MODULE_SYMBOL(service_instance_get_manifest),
|
||||||
|
DEFINE_MODULE_SYMBOL(service_instance_get_data),
|
||||||
|
DEFINE_MODULE_SYMBOL(service_instance_get_state),
|
||||||
|
// service/service_manager
|
||||||
|
DEFINE_MODULE_SYMBOL(service_manager_add),
|
||||||
|
DEFINE_MODULE_SYMBOL(service_manager_remove),
|
||||||
|
DEFINE_MODULE_SYMBOL(service_manager_find_manifest),
|
||||||
|
DEFINE_MODULE_SYMBOL(service_manager_start),
|
||||||
|
DEFINE_MODULE_SYMBOL(service_manager_stop),
|
||||||
|
DEFINE_MODULE_SYMBOL(service_manager_get_state),
|
||||||
|
DEFINE_MODULE_SYMBOL(service_manager_find_instance),
|
||||||
|
// service/service_paths
|
||||||
|
DEFINE_MODULE_SYMBOL(service_paths_get_user_data_directory),
|
||||||
|
DEFINE_MODULE_SYMBOL(service_paths_get_user_data_path),
|
||||||
|
DEFINE_MODULE_SYMBOL(service_paths_get_assets_directory),
|
||||||
|
DEFINE_MODULE_SYMBOL(service_paths_get_assets_path),
|
||||||
// terminator
|
// terminator
|
||||||
MODULE_SYMBOL_TERMINATOR
|
MODULE_SYMBOL_TERMINATOR
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,14 +3,12 @@ project(tests)
|
|||||||
set(DOCTESTINC ${PROJECT_SOURCE_DIR}/Doctest/Include)
|
set(DOCTESTINC ${PROJECT_SOURCE_DIR}/Doctest/Include)
|
||||||
|
|
||||||
enable_testing()
|
enable_testing()
|
||||||
add_subdirectory(service-module)
|
|
||||||
add_subdirectory(TactilityFreeRtos)
|
add_subdirectory(TactilityFreeRtos)
|
||||||
add_subdirectory(TactilityKernel)
|
add_subdirectory(TactilityKernel)
|
||||||
add_subdirectory(Tactility)
|
add_subdirectory(Tactility)
|
||||||
add_subdirectory(crypt-module)
|
add_subdirectory(crypt-module)
|
||||||
|
|
||||||
add_custom_target(build-tests)
|
add_custom_target(build-tests)
|
||||||
add_dependencies(build-tests ServiceModuleTests)
|
|
||||||
add_dependencies(build-tests TactilityFreeRtosTests)
|
add_dependencies(build-tests TactilityFreeRtosTests)
|
||||||
add_dependencies(build-tests TactilityTests)
|
add_dependencies(build-tests TactilityTests)
|
||||||
add_dependencies(build-tests TactilityKernelTests)
|
add_dependencies(build-tests TactilityKernelTests)
|
||||||
|
|||||||
@ -19,7 +19,6 @@ target_link_libraries(TactilityTests PRIVATE
|
|||||||
gps-module
|
gps-module
|
||||||
gps-generic-module
|
gps-generic-module
|
||||||
gps-meshtastic-module
|
gps-meshtastic-module
|
||||||
service-module
|
|
||||||
lvgl
|
lvgl
|
||||||
SDL2::SDL2-static SDL2-static
|
SDL2::SDL2-static SDL2-static
|
||||||
)
|
)
|
||||||
|
|||||||
@ -13,5 +13,4 @@ add_test(NAME TactilityKernelTests COMMAND TactilityKernelTests)
|
|||||||
target_link_libraries(TactilityKernelTests PUBLIC
|
target_link_libraries(TactilityKernelTests PUBLIC
|
||||||
TactilityKernel
|
TactilityKernel
|
||||||
platform-posix
|
platform-posix
|
||||||
service-module
|
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
#include "doctest.h"
|
#include "doctest.h"
|
||||||
|
|
||||||
#include <service/paths.h>
|
|
||||||
|
|
||||||
#include <tactility/paths.h>
|
#include <tactility/paths.h>
|
||||||
|
#include <tactility/service/service_paths.h>
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <string>
|
#include <string>
|
||||||
@ -1,6 +1,5 @@
|
|||||||
#include "doctest.h"
|
#include "doctest.h"
|
||||||
|
#include <tactility/service/service_manager.h>
|
||||||
#include <service/manager.h>
|
|
||||||
|
|
||||||
// Defined in service_instance.cpp. Internal-only, exposed here to test try_get/put gating.
|
// Defined in service_instance.cpp. Internal-only, exposed here to test try_get/put gating.
|
||||||
extern "C" void service_instance_set_state(ServiceInstance* instance, ServiceState state);
|
extern "C" void service_instance_set_state(ServiceInstance* instance, ServiceState state);
|
||||||
@ -1,18 +0,0 @@
|
|||||||
project(ServiceModuleTests)
|
|
||||||
|
|
||||||
enable_language(C CXX ASM)
|
|
||||||
|
|
||||||
|
|
||||||
file(GLOB_RECURSE TEST_SOURCES ${PROJECT_SOURCE_DIR}/Source/*.cpp)
|
|
||||||
add_executable(ServiceModuleTests EXCLUDE_FROM_ALL ${TEST_SOURCES})
|
|
||||||
|
|
||||||
target_include_directories(ServiceModuleTests PRIVATE ${DOCTESTINC})
|
|
||||||
|
|
||||||
add_test(NAME ServiceModuleTests COMMAND ServiceModuleTests)
|
|
||||||
|
|
||||||
target_link_libraries(ServiceModuleTests PUBLIC
|
|
||||||
TactilityKernel
|
|
||||||
service-module
|
|
||||||
platform-posix
|
|
||||||
freertos_kernel
|
|
||||||
)
|
|
||||||
@ -1,195 +0,0 @@
|
|||||||
Apache License
|
|
||||||
==============
|
|
||||||
|
|
||||||
_Version 2.0, January 2004_
|
|
||||||
_<<http://www.apache.org/licenses/>>_
|
|
||||||
|
|
||||||
### 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.
|
|
||||||
|
|
||||||
@ -1,51 +0,0 @@
|
|||||||
#define DOCTEST_CONFIG_IMPLEMENT
|
|
||||||
#include "doctest.h"
|
|
||||||
#include <cassert>
|
|
||||||
|
|
||||||
#include "FreeRTOS.h"
|
|
||||||
#include "task.h"
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
int argc;
|
|
||||||
char** argv;
|
|
||||||
int result;
|
|
||||||
} TestTaskData;
|
|
||||||
|
|
||||||
void test_task(void* parameter) {
|
|
||||||
auto* data = (TestTaskData*)parameter;
|
|
||||||
|
|
||||||
doctest::Context context;
|
|
||||||
|
|
||||||
context.applyCommandLine(data->argc, data->argv);
|
|
||||||
|
|
||||||
// overrides
|
|
||||||
context.setOption("no-breaks", true); // don't break in the debugger when assertions fail
|
|
||||||
|
|
||||||
data->result = context.run();
|
|
||||||
|
|
||||||
vTaskEndScheduler();
|
|
||||||
|
|
||||||
vTaskDelete(nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
|
||||||
TestTaskData data = {
|
|
||||||
.argc = argc,
|
|
||||||
.argv = argv,
|
|
||||||
.result = 0
|
|
||||||
};
|
|
||||||
|
|
||||||
BaseType_t task_result = xTaskCreate(
|
|
||||||
test_task,
|
|
||||||
"test_task",
|
|
||||||
8192,
|
|
||||||
&data,
|
|
||||||
1,
|
|
||||||
nullptr
|
|
||||||
);
|
|
||||||
assert(task_result == pdPASS);
|
|
||||||
|
|
||||||
vTaskStartScheduler();
|
|
||||||
|
|
||||||
return data.result;
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user