// ====================================================== // 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 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, .phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT, .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