289 lines
9.5 KiB
C++
289 lines
9.5 KiB
C++
// ======================================================
|
|
// uDisplay_DSI_panel.cpp - Hardcoded JD9165 MIPI-DSI Implementation
|
|
// Based on esp_lcd_jd9165.c from Espressif
|
|
// ======================================================
|
|
|
|
#include "uDisplay_DSI_panel.h"
|
|
#if SOC_MIPI_DSI_SUPPORTED
|
|
#include "esp_lcd_panel_ops.h"
|
|
#include "esp_lcd_mipi_dsi.h"
|
|
#include "esp_ldo_regulator.h"
|
|
#include "driver/gpio.h"
|
|
#include <rom/cache.h>
|
|
|
|
extern void AddLog(uint32_t loglevel, const char* formatP, ...);
|
|
|
|
DSIPanel::DSIPanel(const DSIPanelConfig& config)
|
|
: cfg(config), rotation(0)
|
|
{
|
|
|
|
framebuffer_size = cfg.width * cfg.height * 2;
|
|
|
|
esp_err_t ret;
|
|
|
|
// Step 1: Initialize LDO for display power (from config)
|
|
if (cfg.ldo_channel >= 0 && cfg.ldo_voltage_mv > 0) {
|
|
esp_ldo_channel_config_t ldo_config = {
|
|
.chan_id = cfg.ldo_channel,
|
|
.voltage_mv = cfg.ldo_voltage_mv,
|
|
};
|
|
ret = esp_ldo_acquire_channel(&ldo_config, &ldo_handle);
|
|
if (ret != ESP_OK) {
|
|
AddLog(3, "DSI: Failed to acquire LDO: %d", ret);
|
|
return;
|
|
}
|
|
AddLog(3, "DSI: LDO enabled (ch %d @ %dmV)", cfg.ldo_channel, cfg.ldo_voltage_mv);
|
|
delay(10);
|
|
} else {
|
|
AddLog(3, "DSI: No LDO configuration");
|
|
}
|
|
|
|
// Step 2: Create DSI bus (from config)
|
|
esp_lcd_dsi_bus_handle_t dsi_bus = nullptr;
|
|
esp_lcd_dsi_bus_config_t bus_config = {
|
|
.bus_id = 0,
|
|
.num_data_lanes = cfg.dsi_lanes,
|
|
.lane_bit_rate_mbps = cfg.lane_speed_mbps
|
|
};
|
|
ret = esp_lcd_new_dsi_bus(&bus_config, &dsi_bus);
|
|
if (ret != ESP_OK) {
|
|
AddLog(3, "DSI: Failed to create DSI bus: %d", ret);
|
|
return;
|
|
}
|
|
AddLog(3, "DSI: DSI bus created");
|
|
|
|
// Step 3: Create DBI IO for commands
|
|
esp_lcd_dbi_io_config_t io_config = {
|
|
.virtual_channel = 0,
|
|
.lcd_cmd_bits = 8,
|
|
.lcd_param_bits = 8,
|
|
};
|
|
ret = esp_lcd_new_panel_io_dbi(dsi_bus, &io_config, &io_handle);
|
|
if (ret != ESP_OK) {
|
|
AddLog(3, "DSI: Failed to create DBI IO: %d", ret);
|
|
return;
|
|
}
|
|
AddLog(3, "DSI: DBI IO created");
|
|
|
|
// Step 4: Configure DPI panel (from config)
|
|
esp_lcd_dpi_panel_config_t dpi_config = {};
|
|
dpi_config.dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT;
|
|
dpi_config.dpi_clock_freq_mhz = cfg.pixel_clock_hz / 1000000;
|
|
dpi_config.virtual_channel = 0;
|
|
dpi_config.pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565;
|
|
dpi_config.num_fbs = 1;
|
|
dpi_config.video_timing.h_size = cfg.width;
|
|
dpi_config.video_timing.v_size = cfg.height;
|
|
dpi_config.video_timing.hsync_back_porch = cfg.timing.h_back_porch;
|
|
dpi_config.video_timing.hsync_pulse_width = cfg.timing.h_sync_pulse;
|
|
dpi_config.video_timing.hsync_front_porch = cfg.timing.h_front_porch;
|
|
dpi_config.video_timing.vsync_back_porch = cfg.timing.v_back_porch;
|
|
dpi_config.video_timing.vsync_pulse_width = cfg.timing.v_sync_pulse;
|
|
dpi_config.video_timing.vsync_front_porch = cfg.timing.v_front_porch;
|
|
dpi_config.flags.use_dma2d = 1;
|
|
|
|
AddLog(3, "DSI: DPI config: clk=%dMHz res=%dx%d", dpi_config.dpi_clock_freq_mhz, cfg.width, cfg.height);
|
|
AddLog(3, "DSI: H timing: BP=%d PW=%d FP=%d", cfg.timing.h_back_porch, cfg.timing.h_sync_pulse, cfg.timing.h_front_porch);
|
|
AddLog(3, "DSI: V timing: BP=%d PW=%d FP=%d", cfg.timing.v_back_porch, cfg.timing.v_sync_pulse, cfg.timing.v_front_porch);
|
|
AddLog(3, "DSI: Expected: clk=54MHz res=1024x600 H:160/40/160 V:23/10/12");
|
|
|
|
// Step 5: Create DPI panel
|
|
ret = esp_lcd_new_panel_dpi(dsi_bus, &dpi_config, &panel_handle);
|
|
if (ret != ESP_OK) {
|
|
AddLog(3, "DSI: Failed to create DPI panel: %d", ret);
|
|
return;
|
|
}
|
|
AddLog(3, "DSI: DPI panel created");
|
|
|
|
// Step 6: Reset via GPIO (from config)
|
|
if (cfg.reset_pin >= 0) {
|
|
gpio_config_t gpio_conf = {
|
|
.pin_bit_mask = 1ULL << cfg.reset_pin,
|
|
.mode = GPIO_MODE_OUTPUT,
|
|
};
|
|
gpio_config(&gpio_conf);
|
|
gpio_set_level((gpio_num_t)cfg.reset_pin, 1);
|
|
delay(5);
|
|
gpio_set_level((gpio_num_t)cfg.reset_pin, 0);
|
|
delay(10);
|
|
gpio_set_level((gpio_num_t)cfg.reset_pin, 1);
|
|
delay(120);
|
|
AddLog(3, "DSI: GPIO reset completed (pin %d)", cfg.reset_pin);
|
|
} else {
|
|
AddLog(3, "DSI: No reset pin configured");
|
|
}
|
|
|
|
// Step 7: Initialize DPI panel
|
|
ret = esp_lcd_panel_init(panel_handle);
|
|
if (ret != ESP_OK) {
|
|
AddLog(3, "DSI: Panel init failed: %d", ret);
|
|
return;
|
|
}
|
|
AddLog(3, "DSI: DPI panel initialized");
|
|
|
|
// Step 8: Get framebuffer
|
|
void* fb_ptr = nullptr;
|
|
ret = esp_lcd_dpi_panel_get_frame_buffer(panel_handle, 1, &fb_ptr);
|
|
if (ret == ESP_OK && fb_ptr != nullptr) {
|
|
framebuffer = (uint16_t*)fb_ptr;
|
|
AddLog(3, "DSI: Framebuffer at %p", framebuffer);
|
|
} else {
|
|
framebuffer = nullptr;
|
|
AddLog(3, "DSI: No framebuffer, using draw_bitmap");
|
|
}
|
|
|
|
// Step 9: Send init commands from INI file
|
|
if (cfg.init_commands && cfg.init_commands_count > 0) {
|
|
AddLog(3, "DSI: Sending init commands from INI file");
|
|
uint16_t index = 0;
|
|
uint16_t cmd_num = 0;
|
|
|
|
while (index < cfg.init_commands_count) {
|
|
uint8_t cmd = cfg.init_commands[index++];
|
|
uint8_t data_size = cfg.init_commands[index++];
|
|
|
|
if (data_size > 0) {
|
|
ret = esp_lcd_panel_io_tx_param(io_handle, cmd, &cfg.init_commands[index], data_size);
|
|
index += data_size;
|
|
} else {
|
|
ret = esp_lcd_panel_io_tx_param(io_handle, cmd, NULL, 0);
|
|
}
|
|
|
|
if (ret != ESP_OK) {
|
|
AddLog(3, "DSI: Cmd 0x%02x failed: %d", cmd, ret);
|
|
}
|
|
|
|
uint8_t delay_ms = cfg.init_commands[index++];
|
|
if (delay_ms > 0) {
|
|
vTaskDelay(pdMS_TO_TICKS(delay_ms));
|
|
}
|
|
cmd_num++;
|
|
}
|
|
AddLog(3, "DSI: Sent %d commands from INI", cmd_num);
|
|
} else {
|
|
AddLog(3, "DSI: No init commands in config");
|
|
}
|
|
}
|
|
|
|
DSIPanel::~DSIPanel() {
|
|
if (panel_handle) {
|
|
esp_lcd_panel_del(panel_handle);
|
|
}
|
|
if (io_handle) {
|
|
esp_lcd_panel_io_del(io_handle);
|
|
}
|
|
if (ldo_handle) {
|
|
esp_ldo_release_channel(ldo_handle);
|
|
}
|
|
}
|
|
|
|
bool DSIPanel::drawPixel(int16_t x, int16_t y, uint16_t color) {
|
|
if (!framebuffer || x < 0 || y < 0 || x >= cfg.width || y >= cfg.height) return true;
|
|
|
|
int16_t w = cfg.width, h = cfg.height;
|
|
switch (rotation) {
|
|
case 1: std::swap(w, h); std::swap(x, y); x = w - x - 1; break;
|
|
case 2: x = w - x - 1; y = h - y - 1; break;
|
|
case 3: std::swap(w, h); std::swap(x, y); y = h - y - 1; break;
|
|
}
|
|
|
|
uint16_t* p = &framebuffer[y * cfg.width + x];
|
|
*p = color;
|
|
framebuffer_dirty = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DSIPanel::fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) {
|
|
for (int16_t yp = y; yp < y + h; yp++) {
|
|
uint16_t* line_start = &framebuffer[yp * cfg.width + x];
|
|
for (int16_t i = 0; i < w; i++) {
|
|
line_start[i] = color;
|
|
}
|
|
// CACHE_WRITEBACK_ADDR((uint32_t)line_start, w * 2);
|
|
}
|
|
framebuffer_dirty = true;
|
|
return true;
|
|
}
|
|
|
|
bool DSIPanel::drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) {
|
|
return fillRect(x, y, w, 1, color);
|
|
}
|
|
|
|
bool DSIPanel::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) {
|
|
return fillRect(x, y, 1, h, color);
|
|
}
|
|
|
|
bool DSIPanel::pushColors(uint16_t *data, uint16_t len, bool not_swapped) {
|
|
esp_err_t ret = esp_lcd_panel_draw_bitmap(panel_handle, window_x0, window_y0, window_x1, window_y1, data);
|
|
return (ret == ESP_OK);
|
|
}
|
|
|
|
bool DSIPanel::setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) {
|
|
window_x0 = x0;
|
|
window_y0 = y0;
|
|
window_x1 = x1;
|
|
window_y1 = y1;
|
|
return true;
|
|
}
|
|
|
|
bool DSIPanel::displayOnff(int8_t on) {
|
|
if (!io_handle) return false;
|
|
|
|
// Use commands from descriptor
|
|
uint8_t cmd = on ? cfg.cmd_display_on : cfg.cmd_display_off;
|
|
|
|
esp_err_t ret = esp_lcd_panel_io_tx_param(io_handle, cmd, NULL, 0);
|
|
return (ret == ESP_OK);
|
|
}
|
|
|
|
bool DSIPanel::invertDisplay(bool invert) {
|
|
if (!io_handle) return false;
|
|
|
|
// Standard MIPI DCS commands for invert
|
|
uint8_t cmd = invert ? 0x21 : 0x20; // 0x21 = INVON, 0x20 = INVOFF
|
|
esp_err_t ret = esp_lcd_panel_io_tx_param(io_handle, cmd, NULL, 0);
|
|
return (ret == ESP_OK);
|
|
}
|
|
|
|
bool DSIPanel::setRotation(uint8_t rot) {
|
|
if (!io_handle) return false;
|
|
|
|
rotation = rot & 3;
|
|
|
|
// Standard MIPI DCS MADCTL (0x36) values for rotation
|
|
// These are common values but may need adjustment for specific displays
|
|
uint8_t madctl_val = 0;
|
|
|
|
switch (rotation) {
|
|
case 0: // Portrait
|
|
madctl_val = 0x00; // Normal
|
|
break;
|
|
case 1: // Landscape (90° clockwise)
|
|
madctl_val = 0x60; // MX + MV
|
|
break;
|
|
case 2: // Portrait inverted (180°)
|
|
madctl_val = 0xC0; // MX + MY
|
|
break;
|
|
case 3: // Landscape inverted (270° clockwise)
|
|
madctl_val = 0xA0; // MY + MV
|
|
break;
|
|
}
|
|
|
|
// Send MADCTL command (0x36) with rotation value
|
|
esp_err_t ret = esp_lcd_panel_io_tx_param(io_handle, 0x36, &madctl_val, 1);
|
|
return false; // pass job to Renderer
|
|
}
|
|
|
|
bool DSIPanel::updateFrame() {
|
|
if (!framebuffer_dirty) {
|
|
return true;
|
|
}
|
|
CACHE_WRITEBACK_ADDR((uint32_t)framebuffer, framebuffer_size); //KISS and fast enough!
|
|
framebuffer_dirty = false; // ← RESET
|
|
|
|
return true;
|
|
}
|
|
|
|
#endif // SOC_MIPI_DSI_SUPPORTED
|