Die Wartezeit aus 1.6.18 hat den Start deutlich verbessert, aber noch nicht zuverlässig gemacht: beim ersten Anstecken nur Beleuchtung, nach Reset zwei von drei Versuchen erfolgreich. Aus dem Fehlerbild folgt, wo es hakt: Die Beleuchtung ging an. Sie hängt an der Bridge und wird über I2C geschaltet, die Kommunikation steht also. Verloren gehen die DCS-Weckbefehle an das Panel selbst - sie wurden genau einmal geschickt, bevor die Videoausgabe lief. War der Panel-Controller dann noch nicht aufnahmebereit, blieb der Bildschirm dunkel, während jeder Schritt Erfolg meldete. - ws7_panel_wake() gebündelt und wird jetzt zweimal aufgerufen: vor und nach esp_lcd_panel_init(), getrennt durch WS7_PANEL_SETTLE_MS. - Jeder Anlauf ist im Start-Log einzeln benannt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P7JsiDJ1QbNFLDz5chf8Jt
711 lines
30 KiB
C
711 lines
30 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_system.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;
|
|
|
|
#if JC_PANEL_TYPE == WS_PANEL_7H
|
|
// ------------------------------------------------------------------------------------
|
|
// Haengenden I2C-Bus befreien
|
|
//
|
|
// Trifft ein Reset des P4 mitten in eine laufende I2C-Uebertragung, bleibt der
|
|
// angesprochene Baustein in seiner Bitausgabe stehen und haelt die Datenleitung dauerhaft
|
|
// auf Masse. Der Bus ist damit tot: Jede weitere Uebertragung schlaegt fehl. Da Bridge und
|
|
// Touch-Controller an Dauerstrom haengen, ueberlebt dieser Zustand jeden Reset und sogar
|
|
// das Flashen - nur echtes Stromabziehen loest ihn.
|
|
//
|
|
// Folge: Die Bridge wird nie initialisiert, das Bild bleibt schwarz, und weil sie ihre
|
|
// Register behaelt, brennt die Hintergrundbeleuchtung weiter. Genau dieses Bild.
|
|
//
|
|
// Abhilfe ist der uebliche Weg aus der I2C-Spezifikation: Vor dem Anlegen des Busses die
|
|
// Taktleitung von Hand takten, bis der Baustein sein letztes Bit ausgegeben hat und die
|
|
// Datenleitung wieder loslaesst, danach eine Stopp-Bedingung erzeugen.
|
|
static void ws7_i2c_bus_recover(void)
|
|
{
|
|
gpio_config_t io = {
|
|
.pin_bit_mask = BIT64(BSP_I2C_SDA) | BIT64(BSP_I2C_SCL),
|
|
.mode = GPIO_MODE_INPUT_OUTPUT_OD, // offener Kollektor, Zustand lesbar
|
|
.pull_up_en = GPIO_PULLUP_ENABLE,
|
|
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
|
.intr_type = GPIO_INTR_DISABLE,
|
|
};
|
|
gpio_config(&io);
|
|
gpio_set_level(BSP_I2C_SDA, 1);
|
|
gpio_set_level(BSP_I2C_SCL, 1);
|
|
esp_rom_delay_us(10);
|
|
|
|
if (gpio_get_level(BSP_I2C_SDA) != 0) {
|
|
return; // Bus frei - nichts zu tun
|
|
}
|
|
|
|
esp_rom_printf("[Panel] I2C-Bus haengt - wird freigetaktet\n");
|
|
|
|
// Bis zu neun Takte: so viele braucht ein Baustein hoechstens, um ein angefangenes
|
|
// Byte samt Bestaetigungsbit zu Ende zu schieben.
|
|
for (int i = 0; i < 9 && gpio_get_level(BSP_I2C_SDA) == 0; i++) {
|
|
gpio_set_level(BSP_I2C_SCL, 0);
|
|
esp_rom_delay_us(5);
|
|
gpio_set_level(BSP_I2C_SCL, 1);
|
|
esp_rom_delay_us(5);
|
|
}
|
|
|
|
// Stopp-Bedingung: Datenleitung geht bei hoher Taktleitung von Masse auf High.
|
|
gpio_set_level(BSP_I2C_SDA, 0);
|
|
esp_rom_delay_us(5);
|
|
gpio_set_level(BSP_I2C_SCL, 1);
|
|
esp_rom_delay_us(5);
|
|
gpio_set_level(BSP_I2C_SDA, 1);
|
|
esp_rom_delay_us(10);
|
|
|
|
esp_rom_printf("[Panel] I2C-Bus nach Freitakten: %s\n",
|
|
gpio_get_level(BSP_I2C_SDA) ? "frei" : "weiterhin belegt");
|
|
|
|
// Pins wieder freigeben, damit der I2C-Treiber sie uebernehmen kann.
|
|
gpio_reset_pin(BSP_I2C_SDA);
|
|
gpio_reset_pin(BSP_I2C_SCL);
|
|
}
|
|
|
|
// Zeigt, welche Bausteine sich am I2C-Bus melden. Erwartet werden 0x45 (Bridge) und
|
|
// 0x14 (GT9271-Touch); 0x18 waere der Audio-Baustein. Fehlt einer, sagt das mehr aus als
|
|
// jede Fehlermeldung des Treibers.
|
|
static void ws7_i2c_scan(const char *wann)
|
|
{
|
|
esp_rom_printf("[Panel] I2C-Bausteine (%s):", wann);
|
|
for (uint8_t addr = 1; addr < 127; addr++) {
|
|
if (i2c_master_probe(s_i2c_handle, addr, 50) == ESP_OK) {
|
|
esp_rom_printf(" 0x%02X", addr);
|
|
}
|
|
}
|
|
esp_rom_printf("\n");
|
|
}
|
|
#endif // JC_PANEL_TYPE == WS_PANEL_7H
|
|
|
|
// 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 };
|
|
esp_err_t err = i2c_master_transmit(s_bridge_handle, buf, sizeof(buf), 100);
|
|
if (err != ESP_OK) {
|
|
// Frueher lief das stillschweigend ins Leere. Schlaegt es fehl, wird die Bridge
|
|
// nie initialisiert und das Bild bleibt schwarz - ohne jeden Hinweis darauf.
|
|
esp_rom_printf("[Panel] Bridge-Register 0x%02X liess sich nicht schreiben: %s\n",
|
|
reg, esp_err_to_name(err));
|
|
}
|
|
}
|
|
|
|
// 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
|
|
|
|
#if JC_PANEL_TYPE == WS_PANEL_7H
|
|
// Panel wecken. Bewusst mehrfach, siehe unten.
|
|
//
|
|
// Ein reines DPI-Panel kennt keine reset-Funktion, esp_lcd_panel_reset() wuerde ins Leere
|
|
// greifen - deshalb nur die DCS-Befehle.
|
|
//
|
|
// Warum mehrfach: Verpasst der Panel-Controller diese Befehle, weil er nach dem
|
|
// Einschalten noch nicht bereit war, bleibt der Bildschirm dunkel, obwohl der P4 alles
|
|
// korrekt gesendet hat und jeder Schritt Erfolg meldet. Die Hintergrundbeleuchtung geht
|
|
// trotzdem an, weil sie an der Bridge haengt und nicht am Panel - genau dieses Bild.
|
|
// Die Befehle sind wiederholbar, ein zweiter Anlauf nach dem Start der Videoausgabe
|
|
// kostet nichts und faengt den Fall ab.
|
|
static void ws7_panel_wake(esp_lcd_panel_io_handle_t io, const char *wann)
|
|
{
|
|
uint8_t zero = 0x00;
|
|
uint8_t madctl = WS7_MADCTL;
|
|
esp_rom_printf("[Panel] Weckbefehle %s\n", wann);
|
|
BSP_STEP(esp_lcd_panel_io_tx_param(io, 0x36, &madctl, 1), "Panel-Befehl MADCTL");
|
|
BSP_STEP(esp_lcd_panel_io_tx_param(io, 0x11, &zero, 1), "Panel-Befehl Sleep Out");
|
|
vTaskDelay(pdMS_TO_TICKS(120));
|
|
BSP_STEP(esp_lcd_panel_io_tx_param(io, 0x29, &zero, 1), "Panel-Befehl Display On");
|
|
vTaskDelay(pdMS_TO_TICKS(20));
|
|
}
|
|
#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)
|
|
{
|
|
#if JC_PANEL_TYPE == WS_PANEL_7H
|
|
// Anlaufzeit abwarten, BEVOR Bridge und Panel angesprochen werden.
|
|
//
|
|
// Das Displaymodul braucht nach dem Anlegen der Versorgung einen Moment, bis Bridge
|
|
// und Panel bereit sind. Faengt der P4 sofort an, laeuft der Hochlauf ins Leere: Die
|
|
// Bridge nimmt ihre Register nicht an oder das Panel zeigt trotz korrekt gesendeter
|
|
// DSI-Daten nichts - beides ohne Fehlermeldung, weil aus Sicht des P4 alles geklappt
|
|
// hat. Das Ergebnis war ein Display, das mal ansprang und mal nicht.
|
|
//
|
|
// Der Werks-Testsketch wartet aus demselben Grund 2 Sekunden am Anfang von setup().
|
|
// Dass es mit geoeffnetem seriellen Monitor fast immer klappte, hatte dieselbe
|
|
// Ursache: Dessen USB-Anmeldung verzoegert den Start und ersetzte damit die Wartezeit.
|
|
vTaskDelay(pdMS_TO_TICKS(WS7_PANEL_WARMUP_MS));
|
|
#endif
|
|
jc_backlight_init();
|
|
|
|
#if JC_PANEL_TYPE == WS_PANEL_7H
|
|
ws7_i2c_bus_recover(); // haengenden Bus vom letzten Reset befreien
|
|
#endif
|
|
|
|
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,
|
|
#if JC_PANEL_TYPE == WS_PANEL_7H
|
|
// Stoerimpulsfilter und interne Abschlusswiderstaende - beides setzt auch das
|
|
// Waveshare-Beispiel. Ohne den Filter brechen Bridge und Touch weg, sobald die
|
|
// DSI-Ausgabe laeuft: Deren schnelle Signale stoeren auf das lange Flachbandkabel
|
|
// ein, und ungefilterte Stoerspitzen bringen die I2C-Uebertragung aus dem Tritt.
|
|
// Der Audio-Baustein auf der Platine bleibt dabei erreichbar, weil er nicht am
|
|
// Displaykabel haengt - genau dieses Muster war im Start-Log zu sehen.
|
|
.glitch_ignore_cnt = 7,
|
|
.flags.enable_internal_pullup = true,
|
|
#endif
|
|
};
|
|
i2c_new_master_bus(&i2c_bus_conf, &s_i2c_handle);
|
|
|
|
#if JC_PANEL_TYPE == WS_PANEL_7H
|
|
ws7_i2c_scan("vor dem Panel-Start");
|
|
// Bridge des Waveshare-Panels vorbereiten (noch ohne Hintergrundbeleuchtung).
|
|
ws7_bridge_pre_init();
|
|
#endif
|
|
|
|
// Reset-Grund mitschreiben: 1 = Kaltstart (Strom weg), alles andere ist ein Warmstart.
|
|
// Nur beim Warmstart behalten die Bausteine ihren alten Zustand - genau dann traten
|
|
// die schwarzen Bilder auf.
|
|
esp_rom_printf("[Panel] Startgrund: %d (1 = Kaltstart)\n", (int)esp_reset_reason());
|
|
|
|
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,
|
|
};
|
|
BSP_STEP(esp_ldo_acquire_channel(&ldo_cfg, &phy_pwr_chan), "Stromversorgung MIPI-D-PHY");
|
|
|
|
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;
|
|
}
|
|
|
|
ws7_panel_wake(io, "vor dem Start der Videoausgabe");
|
|
|
|
BSP_STEP(esp_lcd_panel_init(disp_panel), "DPI-Videoausgabe starten");
|
|
|
|
// Zweiter Anlauf, jetzt bei laufender Videoausgabe: faengt den Fall ab, dass das
|
|
// Panel beim ersten Mal noch nicht aufnahmebereit war.
|
|
vTaskDelay(pdMS_TO_TICKS(WS7_PANEL_SETTLE_MS));
|
|
ws7_panel_wake(io, "nach dem Start der Videoausgabe");
|
|
#if WS7_TEST_PATTERN
|
|
// Prueffarbbalken: Sie entstehen im DSI-Baustein selbst und benutzen weder den
|
|
// Bildspeicher noch LVGL. Erscheinen sie, arbeitet die Anzeigekette bis zum Panel und
|
|
// der Fehler liegt weiter oben. Bleibt es schwarz, liegt es an DSI, Zeitbasis oder
|
|
// Panel.
|
|
//
|
|
// Der Hochlauf endet hier bewusst: Wuerde LVGL danach starten, schriebe es sofort in
|
|
// den Bildspeicher und das Pruefbild waere wieder weg - der Test saehe dann genauso
|
|
// aus wie der Fehler, den er finden soll.
|
|
jc_backlight_set(100); // Beleuchtung an, sonst ist nichts zu sehen
|
|
BSP_STEP(esp_lcd_dpi_panel_set_pattern(disp_panel, MIPI_DSI_PATTERN_BAR_VERTICAL),
|
|
"Prueffarbbalken");
|
|
esp_rom_printf("[Panel] Pruefbild steht - erwartet werden senkrechte Farbbalken.\n");
|
|
esp_rom_printf("[Panel] Hochlauf endet hier (WS7_TEST_PATTERN = 1, keine Oberflaeche).\n");
|
|
return false; // ohne LVGL: Anzeige bleibt beim Pruefbild
|
|
#endif
|
|
|
|
|
|
|
|
#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);
|
|
|
|
#if JC_PANEL_TYPE == WS_PANEL_7H
|
|
// Bridge freigeben und Beleuchtung einschalten, BEVOR der Touch angesprochen wird.
|
|
// Der GT9271 antwortet erst danach: Die Freigabe der Bridge (Register 0xAD) versorgt
|
|
// offenbar auch ihn. In der Werks-Testskizze steht die Touch-Initialisierung aus
|
|
// demselben Grund hinter dem Einschalten der Beleuchtung.
|
|
jc_backlight_set(100);
|
|
vTaskDelay(pdMS_TO_TICKS(WS7_TOUCH_POWER_MS));
|
|
ws7_i2c_scan("nach dem Panel-Start");
|
|
#endif
|
|
|
|
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;
|
|
esp_rom_printf("[Panel] Bringup fertig - LVGL wird gestartet\n");
|
|
lvgl_port_init(disp_panel, tp_handle, interface);
|
|
esp_rom_printf("[Panel] LVGL laeuft\n");
|
|
return true;
|
|
}
|
|
|
|
#endif // JC_USE_REAL_PANEL
|