Files
Dual-PID/P4_Display_Firmware/board_bringup.c
T
raw-designsandClaude Opus 5 328dc06983 fix(P4): Tipp auf einen Knopf löst nicht mehr mehrfach aus (1.6.9)
Der GT9271 lässt gelegentlich einen Messzyklus aus oder liefert ein
unplausibles Paket. Der Hersteller-Treiber kann "gerade nichts Neues" nicht
von "Finger weg" unterscheiden und meldet beides als losgelassen. Seit die
Abtastrate mit 1.6.8 von 33 auf 16 ms gestiegen ist, wurde daraus sichtbar
Drücken-Loslassen-Drücken-Loslassen.

Vor den Treiber ist jetzt ein eigener get_xy-Aufsatz gesetzt: Er verwirft
unplausible Rohwerte und hält den letzten gültigen Punkt noch
WS7_TOUCH_HOLD_MS lang (Vorgabe 40 ms). Der Aufsatz wird zur Laufzeit in das
Touch-Handle eingehängt, die Herstellerdateien bleiben unverändert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018QoLDQeUh1dCQz7yYZVb4Y
2026-09-01 14:18:28 +02:00

543 lines
22 KiB
C

// =====================================================================================
// board_bringup.c - ESP32-P4 Hardware-Bringup
// JC_PANEL_43 = JC4880P443C-I-W (ST7701, 480x800, GT911)
// JC_PANEL_70 = JC1060P470C-I-W (JD9165, 1024x600, GT911)
// WS_PANEL_7H = Waveshare ESP32-P4-Pico + 7inch DSI LCD (H) (1280x720, GT9271)
// =====================================================================================
// BEWUSST eine .c-Datei (wird als C kompiliert): die esp_lcd-Init-Makros nutzen
// Compound-Literals und Out-of-Order-Designated-Initializer, die in C++ (Arduino .cpp)
// zu Fehlern fuehren. Der Ablauf ist 1:1 aus dem Hersteller-Beispiel
// arduino_examples/lvgl_v9_sw_rotation/lvgl_sw_rotation.c uebernommen - lediglich der
// Demo-Aufruf (lv_demo_widgets) entfaellt; das UI wird ueber display_hal.cpp aufgebaut.
//
// Nur aktiv bei JC_USE_REAL_PANEL == 1. Erwartet die Vendor-BSP-Dateien im Sketch:
// pins_config.h, lvgl_port_v9.h/.c, src/lcd/*, src/touch/*
// =====================================================================================
#include "config.h" // liefert JC_USE_REAL_PANEL (C-sicher)
#if JC_USE_REAL_PANEL
#include <string.h>
#include <stdbool.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/ledc.h"
#include "driver/i2c_master.h"
#include "esp_log.h"
#include "esp_cache.h"
#include "esp_heap_caps.h"
#include "esp_rom_sys.h"
#include "esp_timer.h"
#include "esp_ldo_regulator.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_mipi_dsi.h"
#include "src/touch/esp_lcd_touch_gt911.h"
#if JC_PANEL_TYPE == WS_PANEL_7H
// Das Waveshare-Panel braucht keinen Hersteller-Panel-Treiber: es wird direkt als
// DPI-Panel angelegt und mit den drei Standard-DCS-Befehlen geweckt.
#include "hal/mipi_dsi_types.h"
#elif JC_PANEL_TYPE == JC_PANEL_70
#include "src/lcd/esp_lcd_jd9165.h"
#else
#include "src/lcd/esp_lcd_st7701.h"
#endif
#include "pins_config.h"
#include "lvgl_port_v9.h"
#define BSP_MIPI_DSI_PHY_PWR_LDO_CHAN (3)
#define BSP_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV (2500)
#define BSP_LCD_H_RES (LCD_H_RES) // native Panel-Aufloesung
#define BSP_LCD_V_RES (LCD_V_RES)
#define BSP_I2C_NUM (I2C_NUM_1)
#define BSP_I2C_SDA (GPIO_NUM_7)
#define BSP_I2C_SCL (GPIO_NUM_8)
#define BSP_LCD_TOUCH_RST (GPIO_NUM_NC)
#define BSP_LCD_TOUCH_INT (GPIO_NUM_NC)
#define BSP_LCD_RST ((gpio_num_t)LCD_RST)
#define BSP_LCD_BACKLIGHT ((gpio_num_t)LCD_LED)
#define LCD_LEDC_CH LEDC_CHANNEL_0
// Bytes je Pixel im Framebuffer. Beim Waveshare-Panel waehlbar ueber WS7_COLOR_BITS
// (pins_config.h); LVGL rechnet mit derselben Tiefe (lv_conf.h). JC-Panels: RGB565.
#if JC_PANEL_TYPE == WS_PANEL_7H
#define BSP_LCD_FB_BYTES_PER_PX (WS7_COLOR_BITS / 8)
#else
#define BSP_LCD_FB_BYTES_PER_PX (2)
#endif
static i2c_master_bus_handle_t s_i2c_handle = NULL;
// Klartext-Meldung auf der seriellen Konsole. Bewusst esp_rom_printf: das laeuft auch bei
// abgeschaltetem Core-Debug-Level und ohne Heap, also genau dann, wenn es darauf ankommt.
#define BSP_STEP(call, what) \
do { \
esp_err_t _err = (call); \
if (_err != ESP_OK) { \
esp_rom_printf("[Panel] FEHLER: %s -> %s\n", what, esp_err_to_name(_err)); \
} \
} while (0)
// Ohne PSRAM passt kein Framebuffer. Bei 1280x720 in RGB888 werden 3 x 2,7 MB gebraucht -
// bleibt der Panel-Treiber daran haengen, sieht man hier sofort, woran es liegt.
static void bsp_report_psram(size_t needed_bytes)
{
esp_rom_printf("[Panel] PSRAM gesamt %u KB, frei %u KB, groesster Block %u KB, benoetigt %u KB\n",
(unsigned)(heap_caps_get_total_size(MALLOC_CAP_SPIRAM) / 1024),
(unsigned)(heap_caps_get_free_size(MALLOC_CAP_SPIRAM) / 1024),
(unsigned)(heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM) / 1024),
(unsigned)(needed_bytes / 1024));
}
// =====================================================================================
#if JC_PANEL_TYPE == WS_PANEL_7H
// =====================================================================================
// Waveshare 7inch DSI LCD (H): Panel-Freigabe und Helligkeit haengen an einer Bridge
// auf I2C-Adresse 0x45 - es gibt weder einen Reset- noch einen Backlight-GPIO.
// Registerfolge wie im Linux-Treiber:
// Grundinitialisierung: C0=01, C2=01, AC=01 (vor dem DSI-Bringup)
// Freigabe/Helligkeit: AD=01, AB=255-Helligkeit, AA=01 (ERST wenn DSI laeuft,
// sonst bleibt das Bild dunkel bzw. flackert beim Start)
// =====================================================================================
static i2c_master_dev_handle_t s_bridge_handle = NULL;
static void ws7_bridge_write(uint8_t reg, uint8_t value)
{
if (s_bridge_handle == NULL) return;
uint8_t buf[2] = { reg, value };
i2c_master_transmit(s_bridge_handle, buf, sizeof(buf), 100);
}
// Bridge am I2C-Bus anmelden und Grundinitialisierung schreiben. Muss VOR dem
// DSI-Bringup laufen; die Hintergrundbeleuchtung bleibt dabei noch aus.
static void ws7_bridge_pre_init(void)
{
i2c_device_config_t dev_cfg = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = WS7_BRIDGE_I2C_ADDR,
.scl_speed_hz = 100000,
};
if (i2c_master_bus_add_device(s_i2c_handle, &dev_cfg, &s_bridge_handle) != ESP_OK) {
s_bridge_handle = NULL;
return;
}
ws7_bridge_write(0xC0, 0x01);
ws7_bridge_write(0xC2, 0x01);
ws7_bridge_write(0xAC, 0x01);
}
void jc_backlight_init(void)
{
// Nichts zu tun - die Bridge uebernimmt das, siehe ws7_bridge_pre_init().
}
void jc_backlight_set(int percent)
{
if (percent > 100) percent = 100;
if (percent < 0) percent = 0;
uint8_t brightness = (uint8_t)((255 * percent) / 100);
ws7_bridge_write(0xAD, 0x01); // Panel-/Backlight-Freigabe
ws7_bridge_write(0xAB, (uint8_t)(255 - brightness));
ws7_bridge_write(0xAA, 0x01); // Helligkeit uebernehmen
}
// =====================================================================================
#else // JC-Panels: Hintergrundbeleuchtung als LEDC-PWM an einem GPIO
// =====================================================================================
void jc_backlight_init(void)
{
const ledc_channel_config_t ch = {
.gpio_num = BSP_LCD_BACKLIGHT,
.speed_mode = LEDC_LOW_SPEED_MODE,
.channel = LCD_LEDC_CH,
.intr_type = LEDC_INTR_DISABLE,
.timer_sel = 1,
.duty = 0,
.hpoint = 0
};
const ledc_timer_config_t tm = {
.speed_mode = LEDC_LOW_SPEED_MODE,
.duty_resolution = LEDC_TIMER_10_BIT,
.timer_num = 1,
.freq_hz = 5000,
.clk_cfg = LEDC_AUTO_CLK
};
ledc_timer_config(&tm);
ledc_channel_config(&ch);
}
void jc_backlight_set(int percent)
{
if (percent > 100) percent = 100;
if (percent < 0) percent = 0;
uint32_t duty = (1023 * percent) / 100;
ledc_set_duty(LEDC_LOW_SPEED_MODE, LCD_LEDC_CH, duty);
ledc_update_duty(LEDC_LOW_SPEED_MODE, LCD_LEDC_CH);
}
#endif // JC_PANEL_TYPE == WS_PANEL_7H
IRAM_ATTR static bool on_vsync(esp_lcd_panel_handle_t panel,
esp_lcd_dpi_panel_event_data_t *edata, void *ctx)
{
return lvgl_port_notify_lcd_vsync();
}
#if JC_PANEL_TYPE == WS_PANEL_7H
// ------------------------------------------------------------------------------------
// Aussetzer des GT9271 ueberbruecken
//
// Der Controller laesst gelegentlich einen Messzyklus aus und liefert ab und zu ein
// unplausibles Paket - die Werks-Testskizze hat solche Pakete ebenfalls verworfen. Der
// Hersteller-Treiber kann "gerade nichts Neues" nicht von "Finger weg" unterscheiden und
// meldet beides als losgelassen. Seit die Oberflaeche doppelt so oft abtastet, faellt das
// auf: Aus einem Tipp wird Druecken-Loslassen-Druecken-Loslassen, der Knopf loest also
// mehrfach aus.
//
// Deshalb liegt hier ein eigener get_xy-Aufsatz vor dem Treiber: Er verwirft unplausible
// Rohwerte und haelt den letzten gueltigen Punkt kurz fest. Losgelassen wird erst
// gemeldet, wenn WS7_TOUCH_HOLD_MS lang wirklich nichts mehr kam.
// ------------------------------------------------------------------------------------
static bool (*s_touch_get_xy_orig)(esp_lcd_touch_handle_t, uint16_t *, uint16_t *,
uint16_t *, uint8_t *, uint8_t) = NULL;
static uint16_t s_touch_last_x = 0;
static uint16_t s_touch_last_y = 0;
static int64_t s_touch_last_us = 0;
static bool s_touch_down = false;
static bool ws7_touch_get_xy(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y,
uint16_t *strength, uint8_t *point_num, uint8_t max_point_num)
{
bool got = s_touch_get_xy_orig(tp, x, y, strength, point_num, max_point_num);
int64_t now = esp_timer_get_time();
// Unplausible Rohwerte gelten als Aussetzer, nicht als Beruehrung.
if (got && *point_num > 0 &&
(x[0] > WS7_TOUCH_RAW_X_MAX || y[0] > WS7_TOUCH_RAW_Y_MAX)) {
got = false;
*point_num = 0;
}
if (got && *point_num > 0) {
s_touch_last_x = x[0];
s_touch_last_y = y[0];
s_touch_last_us = now;
s_touch_down = true;
return true;
}
#if WS7_TOUCH_HOLD_MS > 0
if (s_touch_down && (now - s_touch_last_us) < ((int64_t)WS7_TOUCH_HOLD_MS * 1000)) {
x[0] = s_touch_last_x;
y[0] = s_touch_last_y;
if (strength) strength[0] = 0;
*point_num = 1;
return true;
}
#endif
s_touch_down = false;
return false;
}
// Der GT9271 des Waveshare-Panels meldet im Hochformat (Roh-X 0..720, Roh-Y 0..1280),
// das Bild laeuft aber im Querformat 1280x720. Achsentausch (und bei Bedarf Spiegelung)
// deshalb ueber den process_coordinates-Hook, der VOR den swap/mirror-Flags von
// esp_lcd_touch greift - deren Flags rechnen mit x_max/y_max des BILDES und wuerden auf
// den Rohwerten falsche Ergebnisse liefern.
static void ws7_touch_map(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y,
uint16_t *strength, uint8_t *point_num, uint8_t max_point_num)
{
(void)tp; (void)strength; (void)max_point_num;
if (point_num == NULL) return;
for (uint8_t i = 0; i < *point_num; i++) {
uint16_t raw_x = x[i];
uint16_t raw_y = y[i];
#if WS7_TOUCH_MIRROR_RAW_X
raw_x = (raw_x > WS7_TOUCH_RAW_X_MAX) ? 0 : (uint16_t)(WS7_TOUCH_RAW_X_MAX - raw_x);
#endif
#if WS7_TOUCH_MIRROR_RAW_Y
raw_y = (raw_y > WS7_TOUCH_RAW_Y_MAX) ? 0 : (uint16_t)(WS7_TOUCH_RAW_Y_MAX - raw_y);
#endif
// Achsentausch + Begrenzung auf die Bildflaeche (der GT9271 liefert vereinzelt
// Ausreisser, die sonst als Klick am Rand ankaemen).
uint16_t px = (raw_y >= LCD_H_RES) ? (LCD_H_RES - 1) : raw_y;
uint16_t py = (raw_x >= LCD_V_RES) ? (LCD_V_RES - 1) : raw_x;
#if WS7_ROTATE_180
// Bild wird um 180 Grad gedreht ausgegeben -> Touch muss mitdrehen.
px = (uint16_t)(LCD_H_RES - 1 - px);
py = (uint16_t)(LCD_V_RES - 1 - py);
#endif
#if WS7_TOUCH_DEBUG
esp_rom_printf("[Touch] roh X=%u Y=%u -> Bild X=%u Y=%u\n",
(unsigned)x[i], (unsigned)y[i], (unsigned)px, (unsigned)py);
#endif
x[i] = px;
y[i] = py;
}
}
#endif
#if JC_PANEL_TYPE == JC_PANEL_70
// Der GT911 des 7,0"-Panels liefert Rohkoordinaten im Raster 800x480 (nicht 1024x600).
// Ohne Umrechnung waere nur das linke obere Rechteck erreichbar. Das Hersteller-Beispiel
// skaliert dafuer im Touch-Treiber; hier ueber den vorgesehenen process_coordinates-Hook,
// damit die Vendor-Datei esp_lcd_touch_gt911.c unveraendert bleibt.
#define JC_TOUCH_RAW_H_RES (800)
#define JC_TOUCH_RAW_V_RES (480)
static void jc_touch_scale(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y,
uint16_t *strength, uint8_t *point_num, uint8_t max_point_num)
{
(void)tp; (void)strength; (void)max_point_num;
if (point_num == NULL) return;
for (uint8_t i = 0; i < *point_num; i++) {
x[i] = (uint16_t)(((uint32_t)x[i] * LCD_H_RES) / JC_TOUCH_RAW_H_RES);
y[i] = (uint16_t)(((uint32_t)y[i] * LCD_V_RES) / JC_TOUCH_RAW_V_RES);
}
}
#endif
// Bringt Panel + Touch + LVGL-Port hoch (LVGL laeuft danach in eigenem Task).
// Rueckgabe false: Panel kam nicht hoch - der Aufrufer darf dann KEINE LVGL-Funktion
// benutzen, sonst folgt ein zweiter Absturz, der die eigentliche Ursache ueberdeckt.
bool jc_board_bringup(void)
{
jc_backlight_init();
i2c_master_bus_config_t i2c_bus_conf = {
.clk_source = I2C_CLK_SRC_DEFAULT,
.sda_io_num = BSP_I2C_SDA,
.scl_io_num = BSP_I2C_SCL,
.i2c_port = BSP_I2C_NUM,
};
i2c_new_master_bus(&i2c_bus_conf, &s_i2c_handle);
#if JC_PANEL_TYPE == WS_PANEL_7H
// Bridge des Waveshare-Panels vorbereiten (noch ohne Hintergrundbeleuchtung).
ws7_bridge_pre_init();
#endif
static esp_ldo_channel_handle_t phy_pwr_chan = NULL;
esp_ldo_channel_config_t ldo_cfg = {
.chan_id = BSP_MIPI_DSI_PHY_PWR_LDO_CHAN,
.voltage_mv = BSP_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV,
};
esp_ldo_acquire_channel(&ldo_cfg, &phy_pwr_chan);
esp_lcd_dsi_bus_handle_t mipi_dsi_bus;
esp_lcd_panel_io_handle_t io = NULL;
esp_lcd_panel_handle_t disp_panel = NULL;
#if JC_PANEL_TYPE == WS_PANEL_7H
// ---------------- Waveshare 7inch DSI LCD (H) (1280x720) ----------------
// Kein Hersteller-Panel-Treiber: DSI-Bus, DBI-Kommandokanal und DPI-Panel werden
// direkt angelegt. Entscheidend ist die Referenztaktquelle PLL_F20M - mit der
// Standardquelle bleibt das Bild auf ESP32-P4 Rev. 1.3 / ECO2 schwarz.
bsp_report_psram((size_t)BSP_LCD_H_RES * BSP_LCD_V_RES * BSP_LCD_FB_BYTES_PER_PX
* LVGL_PORT_LCD_BUFFER_NUMS);
esp_lcd_dsi_bus_config_t bus_config = {
.bus_id = 0,
.num_data_lanes = WS7_DSI_LANES,
.phy_clk_src = MIPI_DSI_PHY_PLLREF_CLK_SRC_PLL_F20M,
.lane_bit_rate_mbps = WS7_DSI_LANE_MBPS,
};
BSP_STEP(esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus), "MIPI-DSI-Bus");
esp_lcd_dbi_io_config_t dbi_config = {
.virtual_channel = 0,
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
};
BSP_STEP(esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &io), "DBI-Kommandokanal");
esp_lcd_dpi_panel_config_t dpi_config = {
.virtual_channel = 0,
.dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT,
.dpi_clock_freq_mhz = WS7_DPI_CLOCK_MHZ,
#if WS7_COLOR_BITS == 24
.pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB888,
#else
.pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565,
#endif
.num_fbs = LVGL_PORT_LCD_BUFFER_NUMS,
.video_timing = {
.h_size = BSP_LCD_H_RES,
.v_size = BSP_LCD_V_RES,
.hsync_back_porch = WS7_HSYNC_BACK,
.hsync_pulse_width = WS7_HSYNC_PULSE,
.hsync_front_porch = WS7_HSYNC_FRONT,
.vsync_back_porch = WS7_VSYNC_BACK,
.vsync_pulse_width = WS7_VSYNC_PULSE,
.vsync_front_porch = WS7_VSYNC_FRONT,
},
.flags = {
.use_dma2d = true,
},
};
BSP_STEP(esp_lcd_new_panel_dpi(mipi_dsi_bus, &dpi_config, &disp_panel),
"DPI-Panel (Framebuffer im PSRAM)");
if (disp_panel == NULL) {
esp_rom_printf("[Panel] Abbruch: Panel wurde nicht angelegt. Haeufigste Ursache ist zu "
"wenig PSRAM - PSRAM in den Board-Einstellungen aktivieren oder in "
"pins_config.h weniger Framebuffer waehlen.\n");
return false;
}
// Panel wecken (MADCTL / Sleep Out / Display On). Ein Reset gibt es hier nicht:
// ein reines DPI-Panel kennt keine reset-Funktion, esp_lcd_panel_reset() wuerde
// ins Leere greifen.
{
uint8_t zero = 0x00;
uint8_t madctl = WS7_MADCTL;
esp_lcd_panel_io_tx_param(io, 0x36, &madctl, 1); // MADCTL
esp_lcd_panel_io_tx_param(io, 0x11, &zero, 1); // Sleep Out
vTaskDelay(pdMS_TO_TICKS(120));
esp_lcd_panel_io_tx_param(io, 0x29, &zero, 1); // Display On
vTaskDelay(pdMS_TO_TICKS(20));
}
BSP_STEP(esp_lcd_panel_init(disp_panel), "DPI-Videoausgabe starten");
#elif JC_PANEL_TYPE == JC_PANEL_70
// ---------------- 7,0" JD9165 (1024x600) ----------------
esp_lcd_dsi_bus_config_t bus_config = JD9165_PANEL_BUS_DSI_2CH_CONFIG();
esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus);
esp_lcd_dbi_io_config_t dbi_config = JD9165_PANEL_IO_DBI_CONFIG();
esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &io);
esp_lcd_dpi_panel_config_t dpi_config = JD9165_1024_600_PANEL_60HZ_DPI_CONFIG(LCD_COLOR_PIXEL_FORMAT_RGB565);
dpi_config.num_fbs = LVGL_PORT_LCD_BUFFER_NUMS;
jd9165_vendor_config_t vendor_config = {
.mipi_config = {
.dsi_bus = mipi_dsi_bus,
.dpi_config = &dpi_config,
},
};
esp_lcd_panel_dev_config_t lcd_dev_config = {
.bits_per_pixel = 16,
.rgb_ele_order = ESP_LCD_COLOR_SPACE_RGB,
.reset_gpio_num = BSP_LCD_RST,
.vendor_config = &vendor_config,
};
esp_lcd_new_panel_jd9165(io, &lcd_dev_config, &disp_panel);
#else
// ---------------- 4,3" ST7701 (480x800) ----------------
esp_lcd_dsi_bus_config_t bus_config = ST7701_PANEL_BUS_DSI_2CH_CONFIG();
esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus);
esp_lcd_dbi_io_config_t dbi_config = ST7701_PANEL_IO_DBI_CONFIG();
esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &io);
esp_lcd_dpi_panel_config_t dpi_config = ST7701_480_360_PANEL_60HZ_DPI_CONFIG(LCD_COLOR_PIXEL_FORMAT_RGB565);
dpi_config.num_fbs = LVGL_PORT_LCD_BUFFER_NUMS;
st7701_vendor_config_t vendor_config = {
.mipi_config = {
.dsi_bus = mipi_dsi_bus,
.dpi_config = &dpi_config,
},
.flags = {
.use_mipi_interface = 1,
},
};
esp_lcd_panel_dev_config_t lcd_dev_config = {
.bits_per_pixel = 16,
.rgb_ele_order = ESP_LCD_COLOR_SPACE_RGB,
.reset_gpio_num = BSP_LCD_RST,
.vendor_config = &vendor_config,
};
esp_lcd_new_panel_st7701(io, &lcd_dev_config, &disp_panel);
#endif
#if JC_PANEL_TYPE != WS_PANEL_7H
esp_lcd_panel_reset(disp_panel);
esp_lcd_panel_init(disp_panel);
#endif
// Alle Framebuffer einmal auf Schwarz loeschen. Im Avoid-Tear-Direct-Mode (mehrere
// Buffer) kopiert LVGL pro Frame nur die geaenderten Bereiche in den naechsten Buffer;
// bis alle Buffer einmal vollstaendig bemalt sind, wuerde sonst uninitialisierter
// Speicher (weisses Rauschen) durchblitzen. Schwarz faellt gegen das dunkle Theme
// nicht auf. Cache-Writeback noetig, da die Buffer im PSRAM liegen und der DMA liest.
{
void *fbs[3] = { NULL, NULL, NULL };
size_t fb_size = (size_t)BSP_LCD_H_RES * BSP_LCD_V_RES * BSP_LCD_FB_BYTES_PER_PX;
#if LVGL_PORT_LCD_BUFFER_NUMS >= 3
esp_lcd_dpi_panel_get_frame_buffer(disp_panel, 3, &fbs[0], &fbs[1], &fbs[2]);
#elif LVGL_PORT_LCD_BUFFER_NUMS == 2
esp_lcd_dpi_panel_get_frame_buffer(disp_panel, 2, &fbs[0], &fbs[1]);
#else
esp_lcd_dpi_panel_get_frame_buffer(disp_panel, 1, &fbs[0]);
#endif
for (int i = 0; i < 3; i++) {
if (fbs[i]) {
memset(fbs[i], 0, fb_size);
esp_cache_msync(fbs[i], fb_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M);
}
}
}
esp_lcd_dpi_panel_event_callbacks_t cbs = {
#if LVGL_PORT_AVOID_TEAR_MODE
.on_refresh_done = on_vsync,
#else
.on_color_trans_done = on_vsync,
#endif
};
esp_lcd_dpi_panel_register_event_callbacks(disp_panel, &cbs, NULL);
esp_lcd_panel_io_handle_t tp_io_handle = NULL;
// MUSS vorbelegt sein: schlaegt die Touch-Initialisierung fehl, gibt der Treiber einen
// unbrauchbaren Zeiger zurueck. Ohne Vorbelegung landet Muell im LVGL-Port.
esp_lcd_touch_handle_t tp_handle = NULL;
esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG();
tp_io_config.scl_speed_hz = 100000;
#if JC_PANEL_TYPE == WS_PANEL_7H
// GT9271 des Waveshare-Panels: gleiche Registerbelegung wie der GT911, aber
// andere I2C-Adresse.
tp_io_config.dev_addr = WS7_TOUCH_I2C_ADDR;
#endif
esp_lcd_new_panel_io_i2c(s_i2c_handle, &tp_io_config, &tp_io_handle);
const esp_lcd_touch_config_t tp_cfg = {
.x_max = BSP_LCD_H_RES,
.y_max = BSP_LCD_V_RES,
.rst_gpio_num = BSP_LCD_TOUCH_RST,
.int_gpio_num = BSP_LCD_TOUCH_INT,
.levels = {
.reset = 0,
.interrupt = 0,
},
.flags = {
.swap_xy = 0,
.mirror_x = 0,
.mirror_y = 0,
},
#if JC_PANEL_TYPE == WS_PANEL_7H
.process_coordinates = ws7_touch_map,
#elif JC_PANEL_TYPE == JC_PANEL_70
.process_coordinates = jc_touch_scale,
#endif
};
if (esp_lcd_touch_new_i2c_gt911(tp_io_handle, &tp_cfg, &tp_handle) != ESP_OK) {
esp_rom_printf("[Panel] Touch-Controller nicht gefunden - Anzeige laeuft ohne Bedienung.\n");
tp_handle = NULL;
}
#if JC_PANEL_TYPE == WS_PANEL_7H
if (tp_handle) { // Aussetzer-Filter vor den Treiber haengen
s_touch_get_xy_orig = tp_handle->get_xy;
tp_handle->get_xy = ws7_touch_get_xy;
}
#endif
lvgl_port_interface_t interface =
(dpi_config.flags.use_dma2d) ? LVGL_PORT_INTERFACE_MIPI_DSI_DMA
: LVGL_PORT_INTERFACE_MIPI_DSI_NO_DMA;
lvgl_port_init(disp_panel, tp_handle, interface);
return true;
}
#endif // JC_USE_REAL_PANEL