mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-02-18 10:53:17 +00:00
M5Stack PaperS3 implementation (#478)
Selectively imported code from @juicecultus [fork](https://github.com/juicecultus/Tactility) with some changes/additions from me.
This commit is contained in:
parent
a935410f82
commit
9cc96fd32b
1
.github/workflows/build.yml
vendored
1
.github/workflows/build.yml
vendored
@ -61,6 +61,7 @@ jobs:
|
|||||||
{ id: m5stack-cardputer-adv, arch: esp32s3 },
|
{ id: m5stack-cardputer-adv, arch: esp32s3 },
|
||||||
{ id: m5stack-core2, arch: esp32 },
|
{ id: m5stack-core2, arch: esp32 },
|
||||||
{ id: m5stack-cores3, arch: esp32s3 },
|
{ id: m5stack-cores3, arch: esp32s3 },
|
||||||
|
{ id: m5stack-papers3, arch: esp32s3 },
|
||||||
{ id: m5stack-stickc-plus, arch: esp32 },
|
{ id: m5stack-stickc-plus, arch: esp32 },
|
||||||
{ id: m5stack-stickc-plus2, arch: esp32 },
|
{ id: m5stack-stickc-plus2, arch: esp32 },
|
||||||
{ id: m5stack-tab5, arch: esp32p4 },
|
{ id: m5stack-tab5, arch: esp32p4 },
|
||||||
|
|||||||
6
Devices/m5stack-papers3/CMakeLists.txt
Normal file
6
Devices/m5stack-papers3/CMakeLists.txt
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
|
||||||
|
|
||||||
|
idf_component_register(SRCS ${SOURCE_FILES}
|
||||||
|
INCLUDE_DIRS "Source"
|
||||||
|
REQUIRES FastEpdDisplay GT911 TactilityCore
|
||||||
|
)
|
||||||
45
Devices/m5stack-papers3/Source/Configuration.cpp
Normal file
45
Devices/m5stack-papers3/Source/Configuration.cpp
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
#include "devices/Display.h"
|
||||||
|
#include "devices/SdCard.h"
|
||||||
|
|
||||||
|
#include <Tactility/hal/Configuration.h>
|
||||||
|
#include <Tactility/lvgl/LvglSync.h>
|
||||||
|
|
||||||
|
using namespace tt::hal;
|
||||||
|
|
||||||
|
static DeviceVector createDevices() {
|
||||||
|
auto touch = createTouch();
|
||||||
|
return {
|
||||||
|
createSdCard(),
|
||||||
|
createDisplay(touch)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
extern const Configuration hardwareConfiguration = {
|
||||||
|
.initBoot = nullptr,
|
||||||
|
.createDevices = createDevices,
|
||||||
|
.spi {
|
||||||
|
spi::Configuration {
|
||||||
|
.device = SPI2_HOST,
|
||||||
|
.dma = SPI_DMA_CH_AUTO,
|
||||||
|
.config = {
|
||||||
|
.mosi_io_num = GPIO_NUM_38,
|
||||||
|
.miso_io_num = GPIO_NUM_40,
|
||||||
|
.sclk_io_num = GPIO_NUM_39,
|
||||||
|
.quadwp_io_num = GPIO_NUM_NC,
|
||||||
|
.quadhd_io_num = GPIO_NUM_NC,
|
||||||
|
.data4_io_num = GPIO_NUM_NC,
|
||||||
|
.data5_io_num = GPIO_NUM_NC,
|
||||||
|
.data6_io_num = GPIO_NUM_NC,
|
||||||
|
.data7_io_num = GPIO_NUM_NC,
|
||||||
|
.data_io_default_level = false,
|
||||||
|
.max_transfer_sz = 4096,
|
||||||
|
.flags = 0,
|
||||||
|
.isr_cpu_id = ESP_INTR_CPU_AFFINITY_AUTO,
|
||||||
|
.intr_flags = 0
|
||||||
|
},
|
||||||
|
.initMode = spi::InitMode::ByTactility,
|
||||||
|
.isMutable = false,
|
||||||
|
.lock = tt::lvgl::getSyncLock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
34
Devices/m5stack-papers3/Source/devices/Display.cpp
Normal file
34
Devices/m5stack-papers3/Source/devices/Display.cpp
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#include "Display.h"
|
||||||
|
#include <Gt911Touch.h>
|
||||||
|
#include <FastEpdDisplay.h>
|
||||||
|
#include <Tactility/lvgl/LvglSync.h>
|
||||||
|
|
||||||
|
std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
|
||||||
|
auto configuration = std::make_unique<Gt911Touch::Configuration>(
|
||||||
|
I2C_NUM_0,
|
||||||
|
540,
|
||||||
|
960,
|
||||||
|
false, // swapXy
|
||||||
|
false, // mirrorX
|
||||||
|
false, // mirrorY
|
||||||
|
GPIO_NUM_NC, // pinReset
|
||||||
|
GPIO_NUM_NC // pinInterrupt
|
||||||
|
);
|
||||||
|
|
||||||
|
auto touch = std::make_shared<Gt911Touch>(std::move(configuration));
|
||||||
|
return std::static_pointer_cast<tt::hal::touch::TouchDevice>(touch);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay(std::shared_ptr<tt::hal::touch::TouchDevice> touch) {
|
||||||
|
FastEpdDisplay::Configuration configuration = {
|
||||||
|
.horizontalResolution = 540,
|
||||||
|
.verticalResolution = 960,
|
||||||
|
.touch = std::move(touch),
|
||||||
|
.busSpeedHz = 20000000,
|
||||||
|
.rotationDegrees = 90,
|
||||||
|
.use4bppGrayscale = false,
|
||||||
|
.fullRefreshEveryNFlushes = 40,
|
||||||
|
};
|
||||||
|
|
||||||
|
return std::make_shared<FastEpdDisplay>(configuration, tt::lvgl::getSyncLock());
|
||||||
|
}
|
||||||
7
Devices/m5stack-papers3/Source/devices/Display.h
Normal file
7
Devices/m5stack-papers3/Source/devices/Display.h
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Tactility/hal/display/DisplayDevice.h>
|
||||||
|
#include <Tactility/hal/touch/TouchDevice.h>
|
||||||
|
|
||||||
|
std::shared_ptr<tt::hal::touch::TouchDevice> createTouch();
|
||||||
|
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay(std::shared_ptr<tt::hal::touch::TouchDevice> touch);
|
||||||
25
Devices/m5stack-papers3/Source/devices/SdCard.cpp
Normal file
25
Devices/m5stack-papers3/Source/devices/SdCard.cpp
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#include "SdCard.h"
|
||||||
|
|
||||||
|
#include <Tactility/lvgl/LvglSync.h>
|
||||||
|
#include <Tactility/hal/sdcard/SpiSdCardDevice.h>
|
||||||
|
|
||||||
|
constexpr auto PAPERS3_SDCARD_PIN_CS = GPIO_NUM_47;
|
||||||
|
|
||||||
|
using tt::hal::sdcard::SpiSdCardDevice;
|
||||||
|
|
||||||
|
std::shared_ptr<SdCardDevice> createSdCard() {
|
||||||
|
auto configuration = std::make_unique<SpiSdCardDevice::Config>(
|
||||||
|
PAPERS3_SDCARD_PIN_CS,
|
||||||
|
GPIO_NUM_NC,
|
||||||
|
GPIO_NUM_NC,
|
||||||
|
GPIO_NUM_NC,
|
||||||
|
SdCardDevice::MountBehaviour::AtBoot,
|
||||||
|
tt::lvgl::getSyncLock(),
|
||||||
|
std::vector<gpio_num_t>{},
|
||||||
|
SPI2_HOST
|
||||||
|
);
|
||||||
|
|
||||||
|
return std::make_shared<SpiSdCardDevice>(
|
||||||
|
std::move(configuration)
|
||||||
|
);
|
||||||
|
}
|
||||||
7
Devices/m5stack-papers3/Source/devices/SdCard.h
Normal file
7
Devices/m5stack-papers3/Source/devices/SdCard.h
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Tactility/hal/sdcard/SdCardDevice.h"
|
||||||
|
|
||||||
|
using tt::hal::sdcard::SdCardDevice;
|
||||||
|
|
||||||
|
std::shared_ptr<SdCardDevice> createSdCard();
|
||||||
23
Devices/m5stack-papers3/Source/module.cpp
Normal file
23
Devices/m5stack-papers3/Source/module.cpp
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#include <tactility/module.h>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
static error_t start() {
|
||||||
|
// Empty for now
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static error_t stop() {
|
||||||
|
// Empty for now
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @warning The variable name must be exactly "device_module" */
|
||||||
|
struct Module device_module = {
|
||||||
|
.name = "m5stack-papers3",
|
||||||
|
.start = start,
|
||||||
|
.stop = stop,
|
||||||
|
.symbols = nullptr
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
27
Devices/m5stack-papers3/device.properties
Normal file
27
Devices/m5stack-papers3/device.properties
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
[general]
|
||||||
|
vendor=M5Stack
|
||||||
|
name=Paper S3
|
||||||
|
incubating=true
|
||||||
|
|
||||||
|
[hardware]
|
||||||
|
target=esp32s3
|
||||||
|
flashSize=16MB
|
||||||
|
spiRam=true
|
||||||
|
spiRamMode=OPI
|
||||||
|
spiRamSpeed=80M
|
||||||
|
esptoolFlashFreq=80M
|
||||||
|
tinyUsb=true
|
||||||
|
|
||||||
|
[display]
|
||||||
|
size=4.7"
|
||||||
|
shape=rectangle
|
||||||
|
dpi=235
|
||||||
|
|
||||||
|
[lvgl]
|
||||||
|
colorDepth=8
|
||||||
|
theme=Mono
|
||||||
|
|
||||||
|
[sdkconfig]
|
||||||
|
CONFIG_EPD_DISPLAY_TYPE_ED047TC2=y
|
||||||
|
|
||||||
|
|
||||||
3
Devices/m5stack-papers3/devicetree.yaml
Normal file
3
Devices/m5stack-papers3/devicetree.yaml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
dependencies:
|
||||||
|
- Platforms/PlatformEsp32
|
||||||
|
dts: m5stack,papers3.dts
|
||||||
25
Devices/m5stack-papers3/m5stack,papers3.dts
Normal file
25
Devices/m5stack-papers3/m5stack,papers3.dts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/dts-v1/;
|
||||||
|
|
||||||
|
#include <tactility/bindings/root.h>
|
||||||
|
#include <tactility/bindings/esp32_gpio.h>
|
||||||
|
#include <tactility/bindings/esp32_i2c.h>
|
||||||
|
|
||||||
|
/ {
|
||||||
|
compatible = "root";
|
||||||
|
model = "M5Stack PaperS3";
|
||||||
|
|
||||||
|
gpio0 {
|
||||||
|
compatible = "espressif,esp32-gpio";
|
||||||
|
gpio-count = <49>;
|
||||||
|
};
|
||||||
|
|
||||||
|
i2c_internal {
|
||||||
|
compatible = "espressif,esp32-i2c";
|
||||||
|
port = <I2C_NUM_0>;
|
||||||
|
clock-frequency = <400000>;
|
||||||
|
pin-sda = <41>;
|
||||||
|
pin-scl = <42>;
|
||||||
|
pin-sda-pullup;
|
||||||
|
pin-scl-pullup;
|
||||||
|
};
|
||||||
|
};
|
||||||
5
Drivers/FastEpdDisplay/CMakeLists.txt
Normal file
5
Drivers/FastEpdDisplay/CMakeLists.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
idf_component_register(
|
||||||
|
SRC_DIRS "Source"
|
||||||
|
INCLUDE_DIRS "Source"
|
||||||
|
REQUIRES FastEPD TactilityCore Tactility
|
||||||
|
)
|
||||||
256
Drivers/FastEpdDisplay/Source/FastEpdDisplay.cpp
Normal file
256
Drivers/FastEpdDisplay/Source/FastEpdDisplay.cpp
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
#include "FastEpdDisplay.h"
|
||||||
|
|
||||||
|
#include <tactility/log.h>
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#ifdef ESP_PLATFORM
|
||||||
|
#include <esp_heap_caps.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define TAG "FastEpdDisplay"
|
||||||
|
|
||||||
|
FastEpdDisplay::~FastEpdDisplay() {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FastEpdDisplay::flush_cb(lv_display_t* disp, const lv_area_t* area, uint8_t* px_map) {
|
||||||
|
auto* self = static_cast<FastEpdDisplay*>(lv_display_get_user_data(disp));
|
||||||
|
|
||||||
|
static uint32_t s_flush_log_counter = 0;
|
||||||
|
|
||||||
|
const int32_t width = area->x2 - area->x1 + 1;
|
||||||
|
const bool grayscale4bpp = self->configuration.use4bppGrayscale;
|
||||||
|
|
||||||
|
// LVGL logical resolution is portrait (540x960). FastEPD PaperS3 native is landscape (960x540).
|
||||||
|
// Keep FastEPD at rotation 0 and do the coordinate transform ourselves.
|
||||||
|
// For a 90° clockwise transform:
|
||||||
|
// x_native = y
|
||||||
|
// y_native = (native_height - 1) - x
|
||||||
|
const int native_width = self->epd.width();
|
||||||
|
const int native_height = self->epd.height();
|
||||||
|
|
||||||
|
// Compute the native line range that will be affected by this flush.
|
||||||
|
// With our mapping y_native = (native_height - 1) - x
|
||||||
|
// So x range maps to y_native range.
|
||||||
|
int start_line = (native_height - 1) - (int)area->x2;
|
||||||
|
int end_line = (native_height - 1) - (int)area->x1;
|
||||||
|
if (start_line > end_line) {
|
||||||
|
const int tmp = start_line;
|
||||||
|
start_line = end_line;
|
||||||
|
end_line = tmp;
|
||||||
|
}
|
||||||
|
if (start_line < 0) start_line = 0;
|
||||||
|
if (end_line >= native_height) end_line = native_height - 1;
|
||||||
|
|
||||||
|
for (int32_t y = area->y1; y <= area->y2; y++) {
|
||||||
|
for (int32_t x = area->x1; x <= area->x2; x++) {
|
||||||
|
const uint8_t gray8 = px_map[(y - area->y1) * width + (x - area->x1)];
|
||||||
|
const uint8_t color = grayscale4bpp ? (uint8_t)(gray8 >> 4) : (uint8_t)((gray8 > 127) ? BBEP_BLACK : BBEP_WHITE);
|
||||||
|
|
||||||
|
const int x_native = y;
|
||||||
|
const int y_native = (native_height - 1) - x;
|
||||||
|
|
||||||
|
// Be defensive: any out-of-range drawPixelFast will corrupt memory.
|
||||||
|
if (x_native < 0 || x_native >= native_width || y_native < 0 || y_native >= native_height) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
self->epd.drawPixelFast(x_native, y_native, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start_line <= end_line) {
|
||||||
|
(void)self->epd.einkPower(1);
|
||||||
|
self->flushCount++;
|
||||||
|
const uint32_t cadence = self->configuration.fullRefreshEveryNFlushes;
|
||||||
|
const bool requested_full = self->forceNextFullRefresh.exchange(false);
|
||||||
|
const bool do_full = requested_full || ((cadence > 0) && (self->flushCount % cadence == 0));
|
||||||
|
|
||||||
|
const bool should_log = ((++s_flush_log_counter % 25U) == 0U);
|
||||||
|
if (should_log) {
|
||||||
|
LOG_I(TAG, "flush #%lu area=(%ld,%ld)-(%ld,%ld) lines=[%d..%d] full=%d",
|
||||||
|
(unsigned long)self->flushCount,
|
||||||
|
(long)area->x1, (long)area->y1, (long)area->x2, (long)area->y2,
|
||||||
|
start_line, end_line, (int)do_full);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (do_full) {
|
||||||
|
const int rc = self->epd.fullUpdate(CLEAR_FAST, true, nullptr);
|
||||||
|
if (should_log) {
|
||||||
|
LOG_I(TAG, "fullUpdate rc=%d", rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// After a full update, keep FastEPD's previous/current buffers in sync so that
|
||||||
|
// subsequent partial updates compute correct diffs.
|
||||||
|
const int w = self->epd.width();
|
||||||
|
const int h = self->epd.height();
|
||||||
|
const size_t bytes_per_row = grayscale4bpp
|
||||||
|
? (size_t)(w + 1) / 2
|
||||||
|
: (size_t)(w + 7) / 8;
|
||||||
|
const size_t plane_size = bytes_per_row * (size_t)h;
|
||||||
|
if (self->epd.currentBuffer() && self->epd.previousBuffer()) {
|
||||||
|
memcpy(self->epd.previousBuffer(), self->epd.currentBuffer(), plane_size);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (grayscale4bpp) {
|
||||||
|
// FastEPD partialUpdate only supports 1bpp mode.
|
||||||
|
// For 4bpp we currently do a fullUpdate. Region-based updates are tricky here because
|
||||||
|
// we also manually rotate/transform pixels in flush_cb; a mismatched rect can refresh
|
||||||
|
// the wrong strip of the panel (seen as "split" buttons on the final refresh).
|
||||||
|
const int rc = self->epd.fullUpdate(CLEAR_FAST, true, nullptr);
|
||||||
|
if (should_log) {
|
||||||
|
LOG_I(TAG, "fullUpdate(4bpp) rc=%d", rc);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const int rc = self->epd.partialUpdate(true, start_line, end_line);
|
||||||
|
|
||||||
|
// Keep FastEPD's previous/current buffers in sync after partial updates as well.
|
||||||
|
// This avoids stale diffs where subsequent updates don't visibly apply.
|
||||||
|
const int w = self->epd.width();
|
||||||
|
const int h = self->epd.height();
|
||||||
|
const size_t bytes_per_row = (size_t)(w + 7) / 8;
|
||||||
|
const size_t plane_size = bytes_per_row * (size_t)h;
|
||||||
|
if (rc == BBEP_SUCCESS && self->epd.currentBuffer() && self->epd.previousBuffer()) {
|
||||||
|
memcpy(self->epd.previousBuffer(), self->epd.currentBuffer(), plane_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (should_log) {
|
||||||
|
LOG_I(TAG, "partialUpdate rc=%d", rc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lv_display_flush_ready(disp);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FastEpdDisplay::start() {
|
||||||
|
if (initialized) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int rc = epd.initPanel(BB_PANEL_M5PAPERS3, configuration.busSpeedHz);
|
||||||
|
if (rc != BBEP_SUCCESS) {
|
||||||
|
LOG_E(TAG, "FastEPD initPanel failed rc=%d", rc);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_I(TAG, "FastEPD native size %dx%d", epd.width(), epd.height());
|
||||||
|
|
||||||
|
const int desired_mode = configuration.use4bppGrayscale ? BB_MODE_4BPP : BB_MODE_1BPP;
|
||||||
|
if (epd.setMode(desired_mode) != BBEP_SUCCESS) {
|
||||||
|
LOG_E(TAG, "FastEPD setMode(%d) failed", desired_mode);
|
||||||
|
epd.deInit();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep FastEPD at rotation 0. LVGL-to-native mapping is handled in flush_cb.
|
||||||
|
|
||||||
|
// Ensure previous/current buffers are in sync and the panel starts from a known state.
|
||||||
|
if (epd.einkPower(1) != BBEP_SUCCESS) {
|
||||||
|
LOG_W(TAG, "FastEPD einkPower(1) failed");
|
||||||
|
} else {
|
||||||
|
epd.fillScreen(configuration.use4bppGrayscale ? 0x0F : BBEP_WHITE);
|
||||||
|
|
||||||
|
const int native_width = epd.width();
|
||||||
|
const int native_height = epd.height();
|
||||||
|
const size_t bytes_per_row = configuration.use4bppGrayscale
|
||||||
|
? (size_t)(native_width + 1) / 2
|
||||||
|
: (size_t)(native_width + 7) / 8;
|
||||||
|
const size_t plane_size = bytes_per_row * (size_t)native_height;
|
||||||
|
|
||||||
|
if (epd.currentBuffer() && epd.previousBuffer()) {
|
||||||
|
memcpy(epd.previousBuffer(), epd.currentBuffer(), plane_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (epd.fullUpdate(CLEAR_FAST, true, nullptr) != BBEP_SUCCESS) {
|
||||||
|
LOG_W(TAG, "FastEPD fullUpdate failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initialized = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FastEpdDisplay::stop() {
|
||||||
|
if (lvglDisplay) {
|
||||||
|
stopLvgl();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (initialized) {
|
||||||
|
epd.deInit();
|
||||||
|
initialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FastEpdDisplay::startLvgl() {
|
||||||
|
if (lvglDisplay != nullptr) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
lvglDisplay = lv_display_create(configuration.horizontalResolution, configuration.verticalResolution);
|
||||||
|
if (lvglDisplay == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
lv_display_set_color_format(lvglDisplay, LV_COLOR_FORMAT_L8);
|
||||||
|
|
||||||
|
if (lv_display_get_rotation(lvglDisplay) != LV_DISPLAY_ROTATION_0) {
|
||||||
|
lv_display_set_rotation(lvglDisplay, LV_DISPLAY_ROTATION_0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t pixel_count = (uint32_t)(configuration.horizontalResolution * configuration.verticalResolution / 10);
|
||||||
|
const uint32_t buf_size = pixel_count * (uint32_t)lv_color_format_get_size(LV_COLOR_FORMAT_L8);
|
||||||
|
lvglBufSize = buf_size;
|
||||||
|
|
||||||
|
#ifdef ESP_PLATFORM
|
||||||
|
lvglBuf1 = heap_caps_malloc(buf_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
||||||
|
#else
|
||||||
|
lvglBuf1 = malloc(buf_size);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (lvglBuf1 == nullptr) {
|
||||||
|
lv_display_delete(lvglDisplay);
|
||||||
|
lvglDisplay = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
lvglBuf2 = nullptr;
|
||||||
|
lv_display_set_buffers(lvglDisplay, lvglBuf1, lvglBuf2, buf_size, LV_DISPLAY_RENDER_MODE_PARTIAL);
|
||||||
|
|
||||||
|
lv_display_set_user_data(lvglDisplay, this);
|
||||||
|
lv_display_set_flush_cb(lvglDisplay, FastEpdDisplay::flush_cb);
|
||||||
|
|
||||||
|
if (configuration.touch && configuration.touch->supportsLvgl()) {
|
||||||
|
configuration.touch->startLvgl(lvglDisplay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FastEpdDisplay::stopLvgl() {
|
||||||
|
if (lvglDisplay) {
|
||||||
|
if (configuration.touch) {
|
||||||
|
configuration.touch->stopLvgl();
|
||||||
|
}
|
||||||
|
|
||||||
|
lv_display_delete(lvglDisplay);
|
||||||
|
lvglDisplay = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lvglBuf1 != nullptr) {
|
||||||
|
free(lvglBuf1);
|
||||||
|
lvglBuf1 = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lvglBuf2 != nullptr) {
|
||||||
|
free(lvglBuf2);
|
||||||
|
lvglBuf2 = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
lvglBufSize = 0;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
65
Drivers/FastEpdDisplay/Source/FastEpdDisplay.h
Normal file
65
Drivers/FastEpdDisplay/Source/FastEpdDisplay.h
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Tactility/Lock.h>
|
||||||
|
#include <Tactility/hal/display/DisplayDevice.h>
|
||||||
|
#include <Tactility/hal/touch/TouchDevice.h>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <FastEPD.h>
|
||||||
|
#include <lvgl.h>
|
||||||
|
|
||||||
|
class FastEpdDisplay final : public tt::hal::display::DisplayDevice {
|
||||||
|
public:
|
||||||
|
struct Configuration final {
|
||||||
|
int horizontalResolution;
|
||||||
|
int verticalResolution;
|
||||||
|
std::shared_ptr<tt::hal::touch::TouchDevice> touch;
|
||||||
|
uint32_t busSpeedHz = 20000000;
|
||||||
|
int rotationDegrees = 90;
|
||||||
|
bool use4bppGrayscale = false;
|
||||||
|
uint32_t fullRefreshEveryNFlushes = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
Configuration configuration;
|
||||||
|
std::shared_ptr<tt::Lock> lock;
|
||||||
|
|
||||||
|
lv_display_t* lvglDisplay = nullptr;
|
||||||
|
void* lvglBuf1 = nullptr;
|
||||||
|
void* lvglBuf2 = nullptr;
|
||||||
|
uint32_t lvglBufSize = 0;
|
||||||
|
|
||||||
|
FASTEPD epd;
|
||||||
|
bool initialized = false;
|
||||||
|
uint32_t flushCount = 0;
|
||||||
|
std::atomic_bool forceNextFullRefresh{false};
|
||||||
|
|
||||||
|
static void flush_cb(lv_display_t* disp, const lv_area_t* area, uint8_t* px_map);
|
||||||
|
|
||||||
|
public:
|
||||||
|
FastEpdDisplay(Configuration configuration, std::shared_ptr<tt::Lock> lock)
|
||||||
|
: configuration(std::move(configuration)), lock(std::move(lock)) {}
|
||||||
|
|
||||||
|
~FastEpdDisplay() override;
|
||||||
|
|
||||||
|
void requestFullRefresh() override { forceNextFullRefresh.store(true); }
|
||||||
|
|
||||||
|
std::string getName() const override { return "FastEpdDisplay"; }
|
||||||
|
std::string getDescription() const override { return "FastEPD (bitbank2) E-Ink display driver"; }
|
||||||
|
|
||||||
|
bool start() override;
|
||||||
|
bool stop() override;
|
||||||
|
|
||||||
|
bool supportsLvgl() const override { return true; }
|
||||||
|
bool startLvgl() override;
|
||||||
|
bool stopLvgl() override;
|
||||||
|
|
||||||
|
lv_display_t* getLvglDisplay() const override { return lvglDisplay; }
|
||||||
|
|
||||||
|
bool supportsDisplayDriver() const override { return false; }
|
||||||
|
std::shared_ptr<tt::hal::display::DisplayDriver> getDisplayDriver() override { return nullptr; }
|
||||||
|
|
||||||
|
std::shared_ptr<tt::hal::touch::TouchDevice> getTouchDevice() override {
|
||||||
|
return configuration.touch;
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -6,93 +6,6 @@ menu "Tactility App"
|
|||||||
config TT_DEVICE_ID
|
config TT_DEVICE_ID
|
||||||
string "Device Identifier"
|
string "Device Identifier"
|
||||||
default ""
|
default ""
|
||||||
choice
|
|
||||||
prompt "Device"
|
|
||||||
default TT_DEVICE_CUSTOM
|
|
||||||
config TT_DEVICE_CUSTOM
|
|
||||||
bool "Custom"
|
|
||||||
config TT_DEVICE_BTT_PANDA_TOUCH
|
|
||||||
bool "BigTreeTech Panda Touch"
|
|
||||||
config TT_DEVICE_CYD_2432S024C
|
|
||||||
bool "CYD 2432S024C"
|
|
||||||
config TT_DEVICE_CYD_2432S028R
|
|
||||||
bool "CYD 2432S028R"
|
|
||||||
config TT_DEVICE_CYD_2432S028RV3
|
|
||||||
bool "CYD 2432S028RV3"
|
|
||||||
config TT_DEVICE_CYD_E32R28T
|
|
||||||
bool "CYD E32R28T"
|
|
||||||
config TT_DEVICE_CYD_E32R32P
|
|
||||||
bool "CYD E32R32P"
|
|
||||||
config TT_DEVICE_CYD_2432S032C
|
|
||||||
bool "CYD 2432S032C"
|
|
||||||
config TT_DEVICE_CYD_8048S043C
|
|
||||||
bool "CYD 8048S043C"
|
|
||||||
config TT_DEVICE_CYD_4848S040C
|
|
||||||
bool "CYD 4848S040C"
|
|
||||||
config TT_DEVICE_ELECROW_CROWPANEL_ADVANCE_28
|
|
||||||
bool "Elecrow CrowPanel Advance 2.8"
|
|
||||||
config TT_DEVICE_ELECROW_CROWPANEL_ADVANCE_35
|
|
||||||
bool "Elecrow CrowPanel Advance 3.5"
|
|
||||||
config TT_DEVICE_ELECROW_CROWPANEL_ADVANCE_50
|
|
||||||
bool "Elecrow CrowPanel Advance 5.0"
|
|
||||||
config TT_DEVICE_ELECROW_CROWPANEL_BASIC_28
|
|
||||||
bool "Elecrow CrowPanel Basic 2.8"
|
|
||||||
config TT_DEVICE_ELECROW_CROWPANEL_BASIC_35
|
|
||||||
bool "Elecrow CrowPanel Basic 3.5"
|
|
||||||
config TT_DEVICE_ELECROW_CROWPANEL_BASIC_50
|
|
||||||
bool "Elecrow CrowPanel Basic 5.0"
|
|
||||||
config TT_DEVICE_GUITION_JC1060P470CIWY
|
|
||||||
bool "Guition JC1060P470CIWY"
|
|
||||||
config TT_DEVICE_GUITION_JC2432W328C
|
|
||||||
bool "Guition JC2432W328C"
|
|
||||||
config TT_DEVICE_GUITION_JC3248W535C
|
|
||||||
bool "Guition JC3248W535C"
|
|
||||||
config TT_DEVICE_GUITION_JC8048W550C
|
|
||||||
bool "Guition JC8048W550C"
|
|
||||||
config TT_DEVICE_HELTEC_V3
|
|
||||||
bool "Heltec v3"
|
|
||||||
config TT_DEVICE_LILYGO_TDECK
|
|
||||||
bool "LilyGo T-Deck"
|
|
||||||
config TT_DEVICE_LILYGO_TDONGLE_S3
|
|
||||||
bool "LilyGo T-Dongle S3"
|
|
||||||
config TT_DEVICE_LILYGO_TLORA_PAGER
|
|
||||||
bool "LilyGo T-Lora Pager"
|
|
||||||
config TT_DEVICE_LILYGO_TDISPLAY
|
|
||||||
bool "LilyGo T-Display"
|
|
||||||
config TT_DEVICE_M5STACK_CARDPUTER
|
|
||||||
bool "M5Stack Cardputer"
|
|
||||||
config TT_DEVICE_M5STACK_CARDPUTER_ADV
|
|
||||||
bool "M5Stack Cardputer Adv"
|
|
||||||
config TT_DEVICE_M5STACK_CORE2
|
|
||||||
bool "M5Stack Core2"
|
|
||||||
config TT_DEVICE_M5STACK_CORES3
|
|
||||||
bool "M5Stack CoreS3"
|
|
||||||
config TT_DEVICE_M5STACK_STICKC_PLUS
|
|
||||||
bool "M5Stack StickC Plus"
|
|
||||||
config TT_DEVICE_M5STACK_STICKC_PLUS2
|
|
||||||
bool "M5Stack StickC Plus2"
|
|
||||||
config TT_DEVICE_M5STACK_TAB5
|
|
||||||
bool "M5Stack Tab5"
|
|
||||||
config TT_DEVICE_UNPHONE
|
|
||||||
bool "unPhone"
|
|
||||||
config TT_DEVICE_WAVESHARE_ESP32_S3_GEEK
|
|
||||||
bool "Waveshare ESP32 S3 GEEK"
|
|
||||||
config TT_DEVICE_WAVESHARE_S3_TOUCH_43
|
|
||||||
bool "Waveshare ESP32 S3 Touch LCD 4.3"
|
|
||||||
config TT_DEVICE_WAVESHARE_S3_TOUCH_LCD_147
|
|
||||||
bool "Waveshare ESP32 S3 Touch LCD 1.47"
|
|
||||||
config TT_DEVICE_WAVESHARE_S3_TOUCH_LCD_128
|
|
||||||
bool "Waveshare ESP32 S3 Touch LCD 1.28"
|
|
||||||
config TT_DEVICE_WAVESHARE_S3_LCD_13
|
|
||||||
bool "Waveshare ESP32 S3 LCD 1.3"
|
|
||||||
config TT_DEVICE_WIRELESS_TAG_WT32_SC01_PLUS
|
|
||||||
bool "Wireless Tag WT32-SC01 Plus"
|
|
||||||
|
|
||||||
help
|
|
||||||
Select a device.
|
|
||||||
Use TT_DEVICE_CUSTOM if you will manually configure the device in your project.
|
|
||||||
endchoice
|
|
||||||
|
|
||||||
config TT_SPLASH_DURATION
|
config TT_SPLASH_DURATION
|
||||||
int "Splash Duration (ms)"
|
int "Splash Duration (ms)"
|
||||||
default 1000
|
default 1000
|
||||||
|
|||||||
@ -54,4 +54,11 @@ dependencies:
|
|||||||
version: "1.7.6~1"
|
version: "1.7.6~1"
|
||||||
rules:
|
rules:
|
||||||
- if: "target == esp32s3"
|
- if: "target == esp32s3"
|
||||||
|
FastEPD:
|
||||||
|
git: https://github.com/bitbank2/FastEPD.git
|
||||||
|
version: 1.4.2
|
||||||
|
rules:
|
||||||
|
# More hardware might be supported - enable as needed
|
||||||
|
- if: "target in [esp32s3]"
|
||||||
idf: '5.5'
|
idf: '5.5'
|
||||||
|
|
||||||
|
|||||||
@ -26,6 +26,9 @@ public:
|
|||||||
virtual bool isPoweredOn() const { return true; }
|
virtual bool isPoweredOn() const { return true; }
|
||||||
virtual bool supportsPowerControl() const { return false; }
|
virtual bool supportsPowerControl() const { return false; }
|
||||||
|
|
||||||
|
/** For e-paper screens */
|
||||||
|
virtual void requestFullRefresh() {}
|
||||||
|
|
||||||
/** Could return nullptr if not started */
|
/** Could return nullptr if not started */
|
||||||
virtual std::shared_ptr<touch::TouchDevice> getTouchDevice() = 0;
|
virtual std::shared_ptr<touch::TouchDevice> getTouchDevice() = 0;
|
||||||
|
|
||||||
|
|||||||
@ -148,6 +148,8 @@ def write_spiram_variables(output_file, device_properties: ConfigParser):
|
|||||||
output_file.write("CONFIG_SPIRAM=y\n")
|
output_file.write("CONFIG_SPIRAM=y\n")
|
||||||
output_file.write(f"CONFIG_{idf_target.upper()}_SPIRAM_SUPPORT=y\n")
|
output_file.write(f"CONFIG_{idf_target.upper()}_SPIRAM_SUPPORT=y\n")
|
||||||
mode = get_property_or_exit(device_properties, "hardware", "spiRamMode")
|
mode = get_property_or_exit(device_properties, "hardware", "spiRamMode")
|
||||||
|
if mode == "OPI":
|
||||||
|
mode = "OCT"
|
||||||
# Mode
|
# Mode
|
||||||
if mode != "AUTO":
|
if mode != "AUTO":
|
||||||
output_file.write(f"CONFIG_SPIRAM_MODE_{mode}=y\n")
|
output_file.write(f"CONFIG_SPIRAM_MODE_{mode}=y\n")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user