Files
Dual-PID/JC_Display_Firmware/board_bringup.c
T
raw-designsandClaude Opus 5 72b47f6dd6 fix(P4): Waveshare-Panel um 180 Grad gedreht ausgeben (1.6.1)
Das Panel ist auf dem Kopf eingebaut. WS7_ROTATE_180 in pins_config.h dreht
Bild und Touch gemeinsam; Vorgabe ist gedreht.

Die Drehung übernimmt der PPA-Grafikbeschleuniger (EXAMPLE_LVGL_PORT_PPA_
ROTATION_ENABLE), weil die Software-Rotation des LVGL-Ports nur RGB565
verarbeitet und für dieses RGB888-Panel nicht in Frage kommt. Der Touch wird
im process_coordinates-Hook mitgedreht, sonst läge die Bedienung
spiegelbildlich zum Bild.

MADCTL ist als WS7_MADCTL herausgezogen, falls die Bridge es doch auswertet.

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

429 lines
17 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 "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_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. Das Waveshare-Panel laeuft in RGB888 (LVGL ebenfalls,
// siehe lv_conf.h), die JC-Panels in RGB565.
#if JC_PANEL_TYPE == WS_PANEL_7H
#define BSP_LCD_FB_BYTES_PER_PX (3)
#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
// =====================================================================================
// 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
// 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
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).
void 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.
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,
};
esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus);
esp_lcd_dbi_io_config_t dbi_config = {
.virtual_channel = 0,
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
};
esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &io);
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,
.pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB888,
.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,
},
};
esp_lcd_new_panel_dpi(mipi_dsi_bus, &dpi_config, &disp_panel);
// 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));
}
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;
esp_lcd_touch_handle_t tp_handle;
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
};
esp_lcd_touch_new_i2c_gt911(tp_io_handle, &tp_cfg, &tp_handle);
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);
}
#endif // JC_USE_REAL_PANEL