diff --git a/lib/lib_display/UDisplay/uDisplay.cpp b/lib/lib_display/UDisplay/uDisplay.cpp index 142eecfcd..dfe7287d7 100644 --- a/lib/lib_display/UDisplay/uDisplay.cpp +++ b/lib/lib_display/UDisplay/uDisplay.cpp @@ -20,15 +20,11 @@ #include #include "uDisplay.h" #include "uDisplay_config.h" -#include "uDisplay_spi.h" -#ifdef ESP32 -#include "esp8266toEsp32.h" -#endif #include "tasmota_options.h" -//#define UDSP_DEBUG +// #define UDSP_DEBUG #ifndef UDSP_LBSIZE #define UDSP_LBSIZE 256 @@ -42,18 +38,10 @@ uDisplay::~uDisplay(void) { free(frame_buffer); } - if (lut_full) { - free(lut_full); - } - - if (lut_partial) { - free(lut_partial); - } - - for (uint16_t cnt = 0; cnt < MAX_LUTS; cnt++ ) { - if (lut_array[cnt]) { - free(lut_array[cnt]); - } + // Free panel config union + if (panel_config) { + free(panel_config); + panel_config = nullptr; } #ifdef USE_UNIVERSAL_TOUCH @@ -70,17 +58,6 @@ uDisplay::~uDisplay(void) { free(ut_gety_code); } #endif // USE_UNIVERSAL_TOUCH - -#ifdef USE_ESP32_S3 - if (_dmadesc) { - heap_caps_free(_dmadesc); - _dmadesc = nullptr; - _dmadesc_size = 0; - } - if (_i80_bus) { - esp_lcd_del_i80_bus(_i80_bus); - } -#endif // USE_ESP32_S3 } uDisplay::uDisplay(char *lp) : Renderer(800, 600) { @@ -95,12 +72,7 @@ uDisplay::uDisplay(char *lp) : Renderer(800, 600) { bpmode = 0; dsp_off = 0xff; dsp_on = 0xff; - lutpsize = 0; - lutfsize = 0; - lutptime = 35; - lutftime = 350; - lut3time = 10; - busy_pin = -1; + // busy_pin = -1; // MOVED to EPDPanelConfig.busy_pin spec_init = -1; ep_mode = 0; fg_col = 1; @@ -112,8 +84,6 @@ uDisplay::uDisplay(char *lp) : Renderer(800, 600) { startline = 0xA1; uint8_t section = 0; dsp_ncmds = 0; - epc_part_cnt = 0; - epc_full_cnt = 0; lut_num = 0; lvgl_param.data = 0; lvgl_param.flushlines = 40; @@ -121,18 +91,15 @@ uDisplay::uDisplay(char *lp) : Renderer(800, 600) { rot_t[1] = 1; rot_t[2] = 2; rot_t[3] = 3; - epcoffs_full = 0; - epcoffs_part = 0; interface = 0; - - for (uint32_t cnt = 0; cnt < MAX_LUTS; cnt++) { - lut_cnt[cnt] = 0; - lut_cmd[cnt] = 0xff; - lut_array[cnt] = 0; - } - - lut_partial = 0; - lut_full = 0; + + // Allocate panel_config once at the beginning + panel_config = (PanelConfigUnion*)calloc(1, sizeof(PanelConfigUnion)); + // Set EPD timing defaults (will be used if EPD mode is detected) + panel_config->epd.lut_full_time = 350; + panel_config->epd.lut_partial_time = 35; + panel_config->epd.update_time = 10; + char linebuff[UDSP_LBSIZE]; while (*lp) { @@ -160,28 +127,26 @@ uDisplay::uDisplay(char *lp) : Renderer(800, 600) { } if (*lp1 == 'S') { - // pecial case RGB with software SPI init clk,mosi,cs,reset + // special case RGB with software SPI init clk,mosi,cs,reset lp1++; if (interface == _UDSP_RGB) { - // collect line and send directly - lp1++; - spi_nr = 4; - spec_init = _UDSP_SPI; - spi_dc = -1; - spi_miso = -1; - spi_clk = next_val(&lp1); - spi_mosi = next_val(&lp1); - spi_cs = next_val(&lp1); - reset = next_val(&lp1); + lp1++; + SPIControllerConfig spi_cfg = { + .bus_nr = 4, + .cs = -1, + .clk = (int8_t)next_val(&lp1), + .mosi = (int8_t)next_val(&lp1), + .dc = -1, + .miso = -1, + .speed = spi_speed + }; + spi_cfg.cs = (int8_t)next_val(&lp1); + spec_init = _UDSP_SPI; + reset = next_val(&lp1); - pinMode(spi_cs, OUTPUT); - digitalWrite(spi_cs, HIGH); - - pinMode(spi_clk, OUTPUT); - digitalWrite(spi_clk, LOW); - - pinMode(spi_mosi, OUTPUT); - digitalWrite(spi_mosi, LOW); + spiController = new SPIController(spi_cfg); + // spiSettings = spiController->getSPISettings(); + // busy_pin = spi_cfg.miso; // update for timing if (reset >= 0) { pinMode(reset, OUTPUT); @@ -190,7 +155,7 @@ uDisplay::uDisplay(char *lp) : Renderer(800, 600) { reset_pin(50, 200); } #ifdef UDSP_DEBUG - AddLog(LOG_LEVEL_DEBUG, "UDisplay: SSPI_MOSI:%d SSPI_SCLK:%d SSPI_CS:%d DSP_RESET:%d", spi_mosi, spi_clk, spi_cs, reset); + AddLog(LOG_LEVEL_DEBUG, "UDisplay: SSPI_MOSI:%d SSPI_SCLK:%d SSPI_CS:%d DSP_RESET:%d", spiController->spi_config.mosi, spiController->spi_config.clk, spiController->spi_config.dc, reset); #endif } } else if (*lp1 == 'I') { @@ -220,21 +185,33 @@ uDisplay::uDisplay(char *lp) : Renderer(800, 600) { if (*lp1 >= '1' && *lp1 <= '5') { lut_num = (*lp1 & 0x07); lp1 += 2; - lut_siz[lut_num - 1] = next_val(&lp1); - lut_array[lut_num - 1] = (uint8_t*)malloc(lut_siz[lut_num - 1]); - lut_cmd[lut_num - 1] = next_hex(&lp1); + uint8_t lut_size = next_val(&lp1); + uint8_t lut_cmd_val = next_hex(&lp1); + + // Store directly in EPD config + panel_config->epd.lut_siz[lut_num - 1] = lut_size; + panel_config->epd.lut_array_data[lut_num - 1] = (uint8_t*)malloc(lut_size); + panel_config->epd.lut_cmd[lut_num - 1] = lut_cmd_val; } else { lut_num = 0; lp1++; - lut_siz_full = next_val(&lp1); - lut_full = (uint8_t*)malloc(lut_siz_full); - lut_cmd[0] = next_hex(&lp1); + uint16_t lut_size = next_val(&lp1); + uint8_t lut_cmd_val = next_hex(&lp1); + + // Store directly in EPD config + panel_config->epd.lut_full_data = (uint8_t*)malloc(lut_size); + panel_config->epd.lutfsize = 0; // Will be filled during :L data parsing + panel_config->epd.lut_cmd[0] = lut_cmd_val; } } else if (section == 'l') { lp1++; - lut_siz_partial = next_val(&lp1); - lut_partial = (uint8_t*)malloc(lut_siz_partial); - lut_cmd[0] = next_hex(&lp1); + uint16_t lut_size = next_val(&lp1); + uint8_t lut_cmd_val = next_hex(&lp1); + + // Store directly in EPD config + panel_config->epd.lut_partial_data = (uint8_t*)malloc(lut_size); + panel_config->epd.lutpsize = 0; // Will be filled during :l data parsing + panel_config->epd.lut_cmd[0] = lut_cmd_val; } if (*lp1 == ',') lp1++; @@ -275,18 +252,21 @@ uDisplay::uDisplay(char *lp) : Renderer(800, 600) { section = 0; } else if (!strncmp(ibuff, "SPI", 3)) { interface = _UDSP_SPI; - spi_nr = next_val(&lp1); - spi_cs = next_val(&lp1); - spi_clk = next_val(&lp1); - spi_mosi = next_val(&lp1); - spi_dc = next_val(&lp1); + SPIControllerConfig spi_cfg = { + .bus_nr = (uint8_t)next_val(&lp1), + .cs = (int8_t)next_val(&lp1), + .clk = (int8_t)next_val(&lp1), + .mosi = (int8_t)next_val(&lp1), + .dc = (int8_t)next_val(&lp1) + }; bpanel = next_val(&lp1); reset = next_val(&lp1); - spi_miso = next_val(&lp1); - spi_speed = next_val(&lp1); + spi_cfg.miso = (int8_t)next_val(&lp1); + spi_cfg.speed = next_val(&lp1); + spiController = new SPIController(spi_cfg); section = 0; } else if (!strncmp(ibuff, "PAR", 3)) { -#ifdef USE_ESP32_S3 +#if defined(UDISPLAY_I80) uint8_t bus = next_val(&lp1); if (bus == 8) { interface = _UDSP_PAR8; @@ -294,45 +274,79 @@ uDisplay::uDisplay(char *lp) : Renderer(800, 600) { interface = _UDSP_PAR16; } reset = next_val(&lp1); - par_cs = next_val(&lp1); - par_rs = next_val(&lp1); - par_wr = next_val(&lp1); - par_rd = next_val(&lp1); + + // Parse control pins directly into I80 config + panel_config->i80.cs_pin = next_val(&lp1); + panel_config->i80.dc_pin = next_val(&lp1); + panel_config->i80.wr_pin = next_val(&lp1); + panel_config->i80.rd_pin = next_val(&lp1); bpanel = next_val(&lp1); + // Parse data pins directly into I80 config for (uint32_t cnt = 0; cnt < 8; cnt ++) { - par_dbl[cnt] = next_val(&lp1); + panel_config->i80.data_pins_low[cnt] = next_val(&lp1); } if (interface == _UDSP_PAR16) { for (uint32_t cnt = 0; cnt < 8; cnt ++) { - par_dbh[cnt] = next_val(&lp1); + panel_config->i80.data_pins_high[cnt] = next_val(&lp1); } } spi_speed = next_val(&lp1); -#endif // USE_ESP32_S3 +#endif // UDISPLAY_I80 section = 0; } else if (!strncmp(ibuff, "RGB", 3)) { -#ifdef USE_ESP32_S3 +#ifdef SOC_LCD_RGB_SUPPORTED interface = _UDSP_RGB; - - de = next_val(&lp1); - vsync = next_val(&lp1); - hsync = next_val(&lp1); - pclk = next_val(&lp1); + // RGB needs DMA-capable memory - reallocate panel_config + free(panel_config); + panel_config = (PanelConfigUnion*)heap_caps_calloc(1, sizeof(PanelConfigUnion), MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); + // Parse pin configuration directly into union + panel_config->rgb.de_gpio_num = (gpio_num_t)next_val(&lp1); + panel_config->rgb.vsync_gpio_num = (gpio_num_t)next_val(&lp1); + panel_config->rgb.hsync_gpio_num = (gpio_num_t)next_val(&lp1); + panel_config->rgb.pclk_gpio_num = (gpio_num_t)next_val(&lp1); bpanel = next_val(&lp1); - - for (uint32_t cnt = 0; cnt < 8; cnt ++) { - par_dbl[cnt] = next_val(&lp1); + // Parse data pins directly into RGB config + // Note: byte order may be swapped later based on lvgl_param.swap_color + for (uint32_t cnt = 0; cnt < 8; cnt++) { + panel_config->rgb.data_gpio_nums[cnt + 8] = next_val(&lp1); } - - for (uint32_t cnt = 0; cnt < 8; cnt ++) { - par_dbh[cnt] = next_val(&lp1); + for (uint32_t cnt = 0; cnt < 8; cnt++) { + panel_config->rgb.data_gpio_nums[cnt] = next_val(&lp1); } spi_speed = next_val(&lp1); -#endif // USE_ESP32_S3 +#endif //SOC_LCD_RGB_SUPPORTED + } else if (!strncmp(ibuff, "DSI", 3)) { +#ifdef SOC_MIPI_DSI_SUPPORTED + interface = _UDSP_DSI; + // Parse DSI-specific parameters directly into union + panel_config->dsi.dsi_lanes = next_val(&lp1); + panel_config->dsi.te_pin = next_val(&lp1); + bpanel = next_val(&lp1); + panel_config->dsi.reset_pin = next_val(&lp1); + panel_config->dsi.ldo_channel = next_val(&lp1); + panel_config->dsi.ldo_voltage_mv = next_val(&lp1); + panel_config->dsi.pixel_clock_hz = next_val(&lp1); + panel_config->dsi.lane_speed_mbps = next_val(&lp1); + panel_config->dsi.rgb_order = next_val(&lp1); + panel_config->dsi.data_endian = next_val(&lp1); + + // Set display dimensions + panel_config->dsi.width = gxs; + panel_config->dsi.height = gys; + panel_config->dsi.bpp = bpp; + section = 0; - } +#ifdef UDSP_DEBUG + AddLog(LOG_LEVEL_DEBUG, "UDisplay: DSI interface - Lanes:%d TE:%d BL:%d LDO:%d@%dmV Clock:%dHz Speed:%dMbps RGB_Order:%d Endian:%d", + panel_config->dsi.dsi_lanes, panel_config->dsi.te_pin, bpanel, + panel_config->dsi.ldo_channel, panel_config->dsi.ldo_voltage_mv, + panel_config->dsi.pixel_clock_hz, panel_config->dsi.lane_speed_mbps, + panel_config->dsi.rgb_order, panel_config->dsi.data_endian); +#endif +#endif //SOC_MIPI_DSI_SUPPORTED +} break; case 'S': splash_font = next_val(&lp1); @@ -380,6 +394,22 @@ uDisplay::uDisplay(char *lp) : Renderer(800, 600) { } } interface = _UDSP_RGB; + } else if (interface == _UDSP_DSI) { + // DSI - parse current line and accumulate bytes + // Don't reset dsp_ncmds - accumulate across all :I lines + uint16_t line_bytes = 0; + while (1) { + if (dsp_ncmds >= sizeof(dsp_cmds)) { + AddLog(LOG_LEVEL_ERROR, "DSI: Init command buffer full at %d bytes", dsp_ncmds); + break; + } + if (!str2c(&lp1, ibuff, sizeof(ibuff))) { + dsp_cmds[dsp_ncmds++] = strtol(ibuff, 0, 16); + line_bytes++; + } else { + break; + } + } } else { if (interface == _UDSP_I2C) { dsp_cmds[dsp_ncmds++] = next_hex(&lp1); @@ -400,14 +430,14 @@ uDisplay::uDisplay(char *lp) : Renderer(800, 600) { break; case 'f': // epaper full update cmds - if (!epcoffs_full) { - epcoffs_full = dsp_ncmds; - epc_full_cnt = 0; + if (!panel_config->epd.epcoffs_full) { + panel_config->epd.epcoffs_full = dsp_ncmds; + panel_config->epd.epc_full_cnt = 0; } while (1) { - if (epc_full_cnt >= sizeof(dsp_cmds)) break; + if (panel_config->epd.epc_full_cnt >= sizeof(dsp_cmds)) break; if (!str2c(&lp1, ibuff, sizeof(ibuff))) { - dsp_cmds[epcoffs_full + epc_full_cnt++] = strtol(ibuff, 0, 16); + dsp_cmds[panel_config->epd.epcoffs_full + panel_config->epd.epc_full_cnt++] = strtol(ibuff, 0, 16); } else { break; } @@ -415,32 +445,54 @@ uDisplay::uDisplay(char *lp) : Renderer(800, 600) { break; case 'p': // epaper partial update cmds - if (!epcoffs_part) { - epcoffs_part = dsp_ncmds + epc_full_cnt; - epc_part_cnt = 0; + if (!panel_config->epd.epcoffs_part) { + panel_config->epd.epcoffs_part = dsp_ncmds + panel_config->epd.epc_full_cnt; + panel_config->epd.epc_part_cnt = 0; } while (1) { - if (epc_part_cnt >= sizeof(dsp_cmds)) break; + if (panel_config->epd.epc_part_cnt >= sizeof(dsp_cmds)) break; if (!str2c(&lp1, ibuff, sizeof(ibuff))) { - dsp_cmds[epcoffs_part + epc_part_cnt++] = strtol(ibuff, 0, 16); + dsp_cmds[panel_config->epd.epcoffs_part + panel_config->epd.epc_part_cnt++] = strtol(ibuff, 0, 16); } else { break; } } break; -#ifdef USE_ESP32_S3 + case 'V': - hsync_polarity = next_val(&lp1); - hsync_front_porch = next_val(&lp1); - hsync_pulse_width = next_val(&lp1); - hsync_back_porch = next_val(&lp1); - vsync_polarity = next_val(&lp1); - vsync_front_porch = next_val(&lp1); - vsync_pulse_width = next_val(&lp1); - vsync_back_porch = next_val(&lp1); - pclk_active_neg = next_val(&lp1); +#if SOC_LCD_RGB_SUPPORTED + if (interface == _UDSP_RGB){ + // Parse timing directly into union + panel_config->rgb.timings.flags.hsync_idle_low = (next_val(&lp1) == 0) ? 1 : 0; + panel_config->rgb.timings.hsync_front_porch = next_val(&lp1); + panel_config->rgb.timings.hsync_pulse_width = next_val(&lp1); + panel_config->rgb.timings.hsync_back_porch = next_val(&lp1); + panel_config->rgb.timings.flags.vsync_idle_low = (next_val(&lp1) == 0) ? 1 : 0; + panel_config->rgb.timings.vsync_front_porch = next_val(&lp1); + panel_config->rgb.timings.vsync_pulse_width = next_val(&lp1); + panel_config->rgb.timings.vsync_back_porch = next_val(&lp1); + panel_config->rgb.timings.flags.pclk_active_neg = next_val(&lp1); + // Set fixed flags (not in descriptor) + panel_config->rgb.timings.flags.de_idle_high = 0; + panel_config->rgb.timings.flags.pclk_idle_high = 0; + } +#endif // SOC_LCD_RGB_SUPPORTED +#if SOC_MIPI_DSI_SUPPORTED + if (interface == _UDSP_DSI && panel_config->dsi.timing.h_front_porch == 0) { + AddLog(1, "DSI: Parsing :V timing line"); + panel_config->dsi.timing.h_front_porch = next_val(&lp1); + panel_config->dsi.timing.v_front_porch = next_val(&lp1); + panel_config->dsi.timing.h_back_porch = next_val(&lp1); + panel_config->dsi.timing.h_sync_pulse = next_val(&lp1); + panel_config->dsi.timing.v_sync_pulse = next_val(&lp1); + panel_config->dsi.timing.v_back_porch = next_val(&lp1); + AddLog(1, "DSI: Parsed timing - HFP:%d VFP:%d HBP:%d HSW:%d VSW:%d VBP:%d", + panel_config->dsi.timing.h_front_porch, panel_config->dsi.timing.v_front_porch, + panel_config->dsi.timing.h_back_porch, panel_config->dsi.timing.h_sync_pulse, + panel_config->dsi.timing.v_sync_pulse, panel_config->dsi.timing.v_back_porch); + } +#endif //SOC_MIPI_DSI_SUPPORTED break; -#endif // USE_ESP32_S3 case 'o': dsp_off = next_hex(&lp1); break; @@ -449,51 +501,150 @@ uDisplay::uDisplay(char *lp) : Renderer(800, 600) { dsp_on = next_hex(&lp1); break; case 'R': - madctrl = next_hex(&lp1); - startline = next_hex(&lp1); + // Parse directly into SPI config (used by SPI panels only) + if (interface == _UDSP_SPI) { + panel_config->spi.cmd_memory_access = next_hex(&lp1); + panel_config->spi.cmd_startline = next_hex(&lp1); + } +#ifdef UDISPLAY_I80 + else if (interface == _UDSP_PAR8 || interface == _UDSP_PAR16) { + panel_config->i80.cmd_madctl = next_hex(&lp1); + panel_config->i80.cmd_startline = next_hex(&lp1); + } +#endif + else { + madctrl = next_hex(&lp1); + startline = next_hex(&lp1); + } break; case '0': if (interface != _UDSP_RGB) { - rot[0] = next_hex(&lp1); - x_addr_offs[0] = next_hex(&lp1); - y_addr_offs[0] = next_hex(&lp1); + if (interface == _UDSP_SPI) { + // Parse directly into SPI config + panel_config->spi.rot_cmd[0] = next_hex(&lp1); + panel_config->spi.x_addr_offset[0] = next_hex(&lp1); + panel_config->spi.y_addr_offset[0] = next_hex(&lp1); + } +#ifdef UDISPLAY_I80 + else if (interface == _UDSP_PAR8 || interface == _UDSP_PAR16) { + // Parse directly into I80 config + panel_config->i80.rot_cmd[0] = next_hex(&lp1); + panel_config->i80.x_addr_offset[0] = next_hex(&lp1); + panel_config->i80.y_addr_offset[0] = next_hex(&lp1); + } +#endif + else { + rot[0] = next_hex(&lp1); + x_addr_offs[0] = next_hex(&lp1); + y_addr_offs[0] = next_hex(&lp1); + } } rot_t[0] = next_hex(&lp1); break; case '1': if (interface != _UDSP_RGB) { - rot[1] = next_hex(&lp1); - x_addr_offs[1] = next_hex(&lp1); - y_addr_offs[1] = next_hex(&lp1); + if (interface == _UDSP_SPI) { + panel_config->spi.rot_cmd[1] = next_hex(&lp1); + panel_config->spi.x_addr_offset[1] = next_hex(&lp1); + panel_config->spi.y_addr_offset[1] = next_hex(&lp1); + } +#ifdef UDISPLAY_I80 + else if (interface == _UDSP_PAR8 || interface == _UDSP_PAR16) { + // Parse directly into I80 config + panel_config->i80.rot_cmd[1] = next_hex(&lp1); + panel_config->i80.x_addr_offset[1] = next_hex(&lp1); + panel_config->i80.y_addr_offset[1] = next_hex(&lp1); + } +#endif + else { + rot[1] = next_hex(&lp1); + x_addr_offs[1] = next_hex(&lp1); + y_addr_offs[1] = next_hex(&lp1); + } } rot_t[1] = next_hex(&lp1); break; case '2': if (interface != _UDSP_RGB) { - rot[2] = next_hex(&lp1); - x_addr_offs[2] = next_hex(&lp1); - y_addr_offs[2] = next_hex(&lp1); + if (interface == _UDSP_SPI) { + panel_config->spi.rot_cmd[2] = next_hex(&lp1); + panel_config->spi.x_addr_offset[2] = next_hex(&lp1); + panel_config->spi.y_addr_offset[2] = next_hex(&lp1); + } +#ifdef UDISPLAY_I80 + else if (interface == _UDSP_PAR8 || interface == _UDSP_PAR16) { + // Parse directly into I80 config + panel_config->i80.rot_cmd[2] = next_hex(&lp1); + panel_config->i80.x_addr_offset[2] = next_hex(&lp1); + panel_config->i80.y_addr_offset[2] = next_hex(&lp1); + } +#endif + else { + rot[2] = next_hex(&lp1); + x_addr_offs[2] = next_hex(&lp1); + y_addr_offs[2] = next_hex(&lp1); + } } rot_t[2] = next_hex(&lp1); break; case '3': if (interface != _UDSP_RGB) { - rot[3] = next_hex(&lp1); - x_addr_offs[3] = next_hex(&lp1); - y_addr_offs[3] = next_hex(&lp1); + if (interface == _UDSP_SPI) { + panel_config->spi.rot_cmd[3] = next_hex(&lp1); + panel_config->spi.x_addr_offset[3] = next_hex(&lp1); + panel_config->spi.y_addr_offset[3] = next_hex(&lp1); + } +#ifdef UDISPLAY_I80 + else if (interface == _UDSP_PAR8 || interface == _UDSP_PAR16) { + // Parse directly into I80 config + panel_config->i80.rot_cmd[3] = next_hex(&lp1); + panel_config->i80.x_addr_offset[3] = next_hex(&lp1); + panel_config->i80.y_addr_offset[3] = next_hex(&lp1); + } +#endif + else { + rot[3] = next_hex(&lp1); + x_addr_offs[3] = next_hex(&lp1); + y_addr_offs[3] = next_hex(&lp1); + } } rot_t[3] = next_hex(&lp1); break; case 'A': if (interface == _UDSP_I2C || bpp == 1) { - saw_1 = next_hex(&lp1); - i2c_page_start = next_hex(&lp1); - i2c_page_end = next_hex(&lp1); - saw_2 = next_hex(&lp1); - i2c_col_start = next_hex(&lp1); - i2c_col_end = next_hex(&lp1); - saw_3 = next_hex(&lp1); - } else { + // Parse directly into I2C config + panel_config->i2c.cmd_set_addr_x = next_hex(&lp1); + panel_config->i2c.page_start = next_hex(&lp1); + panel_config->i2c.page_end = next_hex(&lp1); + panel_config->i2c.cmd_set_addr_y = next_hex(&lp1); + panel_config->i2c.col_start = next_hex(&lp1); + panel_config->i2c.col_end = next_hex(&lp1); + panel_config->i2c.cmd_write_ram = next_hex(&lp1); + // Also keep in legacy vars for now + saw_1 = panel_config->i2c.cmd_set_addr_x; + i2c_page_start = panel_config->i2c.page_start; + i2c_page_end = panel_config->i2c.page_end; + saw_2 = panel_config->i2c.cmd_set_addr_y; + i2c_col_start = panel_config->i2c.col_start; + i2c_col_end = panel_config->i2c.col_end; + saw_3 = panel_config->i2c.cmd_write_ram; + } else if (interface == _UDSP_SPI) { + // Parse directly into SPI config + panel_config->spi.cmd_set_addr_x = next_hex(&lp1); + panel_config->spi.cmd_set_addr_y = next_hex(&lp1); + panel_config->spi.cmd_write_ram = next_hex(&lp1); + panel_config->spi.address_mode = next_val(&lp1); + } +#ifdef UDISPLAY_I80 + else if (interface == _UDSP_PAR8 || interface == _UDSP_PAR16) { + // Parse directly into I80 config + panel_config->i80.cmd_set_addr_x = next_hex(&lp1); + panel_config->i80.cmd_set_addr_y = next_hex(&lp1); + panel_config->i80.cmd_write_ram = next_hex(&lp1); + panel_config->i80.sa_mode = next_val(&lp1); + } +#endif + else { saw_1 = next_hex(&lp1); saw_2 = next_hex(&lp1); saw_3 = next_hex(&lp1); @@ -501,70 +652,108 @@ uDisplay::uDisplay(char *lp) : Renderer(800, 600) { } break; case 'a': - saw_1 = next_hex(&lp1); - saw_2 = next_hex(&lp1); - saw_3 = next_hex(&lp1); + if (interface == _UDSP_SPI) { + // Parse directly into SPI config + panel_config->spi.cmd_set_addr_x = next_hex(&lp1); + panel_config->spi.cmd_set_addr_y = next_hex(&lp1); + panel_config->spi.cmd_write_ram = next_hex(&lp1); + } else { + saw_1 = next_hex(&lp1); + saw_2 = next_hex(&lp1); + saw_3 = next_hex(&lp1); + } break; case 'P': - col_mode = next_val(&lp1); + col_mode = next_val(&lp1); // Keep for legacy code + if (interface == _UDSP_SPI) { + panel_config->spi.col_mode = col_mode; + } +#ifdef UDISPLAY_I80 + else if (interface == _UDSP_PAR8 || interface == _UDSP_PAR16) { + panel_config->i80.color_mode = col_mode; + } +#endif break; case 'i': inv_off = next_hex(&lp1); inv_on = next_hex(&lp1); + if (interface == _UDSP_SPI) { + panel_config->spi.cmd_invert_off = inv_off; + panel_config->spi.cmd_invert_on = inv_on; + } else if (interface == _UDSP_I2C) { + panel_config->i2c.cmd_invert_off = inv_off; + panel_config->i2c.cmd_invert_on = inv_on; + } break; case 'D': dim_op = next_hex(&lp1); break; case 'L': if (!lut_num) { - if (!lut_full) { + if (!panel_config->epd.lut_full_data) { break; } while (1) { if (!str2c(&lp1, ibuff, sizeof(ibuff))) { - lut_full[lutfsize++] = strtol(ibuff, 0, 16); + panel_config->epd.lut_full_data[panel_config->epd.lutfsize++] = strtol(ibuff, 0, 16); } else { break; } - if (lutfsize >= lut_siz_full) break; } + // Set pointers for compatibility + panel_config->epd.lut_full = panel_config->epd.lut_full_data; + panel_config->epd.lut_full_len = panel_config->epd.lutfsize; } else { uint8_t index = lut_num - 1; - if (!lut_array[index]) { + if (!panel_config->epd.lut_array_data[index]) { break; } while (1) { if (!str2c(&lp1, ibuff, sizeof(ibuff))) { - lut_array[index][lut_cnt[index]++] = strtol(ibuff, 0, 16); + panel_config->epd.lut_array_data[index][panel_config->epd.lut_cnt_data[index]++] = strtol(ibuff, 0, 16); } else { break; } - if (lut_cnt[index] >= lut_siz[index]) break; + if (panel_config->epd.lut_cnt_data[index] >= panel_config->epd.lut_siz[index]) break; } + // Set pointers for compatibility + panel_config->epd.lut_array = (const uint8_t**)panel_config->epd.lut_array_data; + panel_config->epd.lut_cnt = panel_config->epd.lut_cnt_data; } break; case 'l': - if (!lut_partial) { + if (!panel_config->epd.lut_partial_data) { break; } while (1) { if (!str2c(&lp1, ibuff, sizeof(ibuff))) { - lut_partial[lutpsize++] = strtol(ibuff, 0, 16); + panel_config->epd.lut_partial_data[panel_config->epd.lutpsize++] = strtol(ibuff, 0, 16); } else { break; } - if (lutpsize >= lut_siz_partial) break; } + // Set pointers for compatibility + panel_config->epd.lut_partial = panel_config->epd.lut_partial_data; + panel_config->epd.lut_partial_len = panel_config->epd.lutpsize; break; case 'T': - lutftime = next_val(&lp1); - lutptime = next_val(&lp1); - lut3time = next_val(&lp1); + // Parse timing directly into EPD config + if (panel_config) { + panel_config->epd.lut_full_time = next_val(&lp1); + panel_config->epd.lut_partial_time = next_val(&lp1); + panel_config->epd.update_time = next_val(&lp1); + } else { + // Skip values if panel_config not allocated yet + next_val(&lp1); + next_val(&lp1); + next_val(&lp1); + } break; case 'B': lvgl_param.flushlines = next_val(&lp1); lvgl_param.data = next_val(&lp1); #ifdef ESP32 + // if(interface != _UDSP_SPI) // maybe test this later lvgl_param.use_dma = false; // temporary fix to disable DMA due to a problem in esp-idf 5.3 #endif break; @@ -654,14 +843,26 @@ uDisplay::uDisplay(char *lp) : Renderer(800, 600) { } } - if (lutfsize && lutpsize) { - // 2 table mode - ep_mode = 1; - } + // EPD mode detection - only for SPI interface + if (interface == _UDSP_SPI) { + if (panel_config && panel_config->epd.lutfsize && panel_config->epd.lutpsize) { + // 2 table mode + ep_mode = 1; + } - if (lut_cnt[0] > 0 && lut_cnt[1] == lut_cnt[2] && lut_cnt[1] == lut_cnt[3] && lut_cnt[1] == lut_cnt[4]) { - // 5 table mode - ep_mode = 2; + if (panel_config && panel_config->epd.lut_cnt_data[0] > 0 && + panel_config->epd.lut_cnt_data[1] == panel_config->epd.lut_cnt_data[2] && + panel_config->epd.lut_cnt_data[1] == panel_config->epd.lut_cnt_data[3] && + panel_config->epd.lut_cnt_data[1] == panel_config->epd.lut_cnt_data[4]) { + // 5 table mode + ep_mode = 2; + } + + if (panel_config && (panel_config->epd.epcoffs_full || panel_config->epd.epcoffs_part) && + !(panel_config->epd.lutfsize || panel_config->epd.lutpsize)) { + // no lutfsize or lutpsize, but epcoffs_full or epcoffs_part + ep_mode = 3; + } } @@ -677,31 +878,28 @@ void UfsCheckSDCardInit(void); } #endif - if ((epcoffs_full || epcoffs_part) && !(lutfsize || lutpsize)) { - // no lutfsize or lutpsize, but epcoffs_full or epcoffs_part - ep_mode = 3; - } - #ifdef UDSP_DEBUG AddLog(LOG_LEVEL_DEBUG, "UDisplay: Device:%s xs:%d ys:%d bpp:%d", dname, gxs, gys, bpp); if (interface == _UDSP_SPI) { AddLog(LOG_LEVEL_DEBUG, "UDisplay: Nr:%d CS:%d CLK:%d MOSI:%d DC:%d TS_CS:%d TS_RST:%d TS_IRQ:%d", - spi_nr, spi_cs, spi_clk, spi_mosi, spi_dc, ut_spi_cs, ut_reset, ut_irq); + spiController->spi_config.bus_nr, spiController->spi_config.cs, spiController->spi_config.clk, spiController->spi_config.mosi, spiController->spi_config.dc, ut_spi_cs, ut_reset, ut_irq); AddLog(LOG_LEVEL_DEBUG, "UDisplay: BPAN:%d RES:%d MISO:%d SPED:%d Pixels:%d SaMode:%d DMA-Mode:%d opts:%02x,%02x,%02x SetAddr:%x,%x,%x", - bpanel, reset, spi_miso, spi_speed*1000000, col_mode, sa_mode, lvgl_param.use_dma, saw_3, dim_op, startline, saw_1, saw_2, saw_3); + bpanel, reset, spiController->spi_config.miso, spiController->spi_config.speed*1000000, col_mode, sa_mode, lvgl_param.use_dma, saw_3, dim_op, startline, saw_1, saw_2, saw_3); AddLog(LOG_LEVEL_DEBUG, "UDisplay: Rot 0: %x,%x - %d - %d", madctrl, rot[0], x_addr_offs[0], y_addr_offs[0]); - if (ep_mode == 1) { - AddLog(LOG_LEVEL_DEBUG, "UDisplay: LUT_Partial:%d-%d-%x-%d-%d LUT_Full:%d-%d-%x-%d-%d", - lut_siz_partial, lutpsize, lut_cmd[0], epcoffs_part, epc_part_cnt, - lut_siz_full, lutfsize, lut_cmd[0], epcoffs_full, epc_full_cnt); + if (ep_mode == 1 && panel_config) { + AddLog(LOG_LEVEL_DEBUG, "UDisplay: LUT_Partial:%d-%x-%d-%d LUT_Full:%d-%x-%d-%d", + panel_config->epd.lutpsize, panel_config->epd.lut_cmd[0], panel_config->epd.epcoffs_part, panel_config->epd.epc_part_cnt, + panel_config->epd.lutfsize, panel_config->epd.lut_cmd[0], panel_config->epd.epcoffs_full, panel_config->epd.epc_full_cnt); } - if (ep_mode == 2) { + if (ep_mode == 2 && panel_config) { AddLog(LOG_LEVEL_DEBUG, "UDisplay: LUT_SIZE 1:%d 2:%d 3:%d 4:%d 5:%d", - lut_cnt[0], lut_cnt[1], lut_cnt[2], lut_cnt[3], lut_cnt[4]); + panel_config->epd.lut_cnt_data[0], panel_config->epd.lut_cnt_data[1], panel_config->epd.lut_cnt_data[2], + panel_config->epd.lut_cnt_data[3], panel_config->epd.lut_cnt_data[4]); AddLog(LOG_LEVEL_DEBUG, "UDisplay: LUT_CMDS %02x-%02x-%02x-%02x-%02x", - lut_cmd[0], lut_cmd[1], lut_cmd[2], lut_cmd[3], lut_cmd[4]); + panel_config->epd.lut_cmd[0], panel_config->epd.lut_cmd[1], panel_config->epd.lut_cmd[2], + panel_config->epd.lut_cmd[3], panel_config->epd.lut_cmd[4]); } } if (interface == _UDSP_I2C) { @@ -712,40 +910,42 @@ void UfsCheckSDCardInit(void); } if (interface == _UDSP_PAR8 || interface == _UDSP_PAR16) { -#ifdef USE_ESP32_S3 +#if defined(UDISPLAY_I80) AddLog(LOG_LEVEL_DEBUG, "UDisplay: par mode:%d res:%d cs:%d rs:%d wr:%d rd:%d bp:%d", - interface, reset, par_cs, par_rs, par_wr, par_rd, bpanel); + interface, reset, panel_config->i80.cs_pin, panel_config->i80.dc_pin, + panel_config->i80.wr_pin, panel_config->i80.rd_pin, bpanel); for (uint32_t cnt = 0; cnt < 8; cnt ++) { - AddLog(LOG_LEVEL_DEBUG, "UDisplay: par d%d:%d", cnt, par_dbl[cnt]); + AddLog(LOG_LEVEL_DEBUG, "UDisplay: par d%d:%d", cnt, panel_config->i80.data_pins_low[cnt]); } if (interface == _UDSP_PAR16) { for (uint32_t cnt = 0; cnt < 8; cnt ++) { - AddLog(LOG_LEVEL_DEBUG, "UDisplay: par d%d:%d", cnt + 8, par_dbh[cnt]); + AddLog(LOG_LEVEL_DEBUG, "UDisplay: par d%d:%d", cnt + 8, panel_config->i80.data_pins_high[cnt]); } } AddLog(LOG_LEVEL_DEBUG, "UDisplay: par freq:%d", spi_speed); -#endif // USE_ESP32_S3 +#endif // UDISPLAY_I80 } if (interface == _UDSP_RGB) { -#ifdef USE_ESP32_S3 +#ifdef SOC_LCD_RGB_SUPPORTED - AddLog(LOG_LEVEL_DEBUG, "UDisplay: rgb de:%d vsync:%d hsync:%d pclk:%d bp:%d", de, vsync, hsync, pclk, bpanel); + AddLog(LOG_LEVEL_DEBUG, "UDisplay: rgb de:%d vsync:%d hsync:%d pclk:%d bp:%d", panel_config->rgb.de_gpio_num, panel_config->rgb.vsync_gpio_num, panel_config->rgb.hsync_gpio_num, panel_config->rgb.pclk_gpio_num, bpanel); for (uint32_t cnt = 0; cnt < 8; cnt ++) { - AddLog(LOG_LEVEL_DEBUG, "UDisplay: rgb d%d:%d", cnt, par_dbl[cnt]); + AddLog(LOG_LEVEL_DEBUG, "UDisplay: rgb d%d:%d", cnt, panel_config->rgb.data_gpio_nums[cnt]); } for (uint32_t cnt = 0; cnt < 8; cnt ++) { - AddLog(LOG_LEVEL_DEBUG, "UDisplay: rgb d%d:%d", cnt + 8, par_dbh[cnt]); + AddLog(LOG_LEVEL_DEBUG, "UDisplay: rgb d%d:%d", cnt + 8, panel_config->rgb.data_gpio_nums[cnt + 8]); } - AddLog(LOG_LEVEL_DEBUG, "UDisplay: rgb freq:%d hsync_pol:%d hsync_fp:%d hsync_pw:%d hsync_bp:%d vsync_pol:%d vsync_fp:%d vsync_pw:%d vsync_bp:%d pclk_neg:%d", - spi_speed, hsync_polarity, hsync_front_porch, hsync_pulse_width, hsync_back_porch, - vsync_polarity, vsync_front_porch, vsync_pulse_width, vsync_back_porch, pclk_active_neg); + AddLog(LOG_LEVEL_DEBUG, "UDisplay: rgb freq:%d hsync_idle_low:%d hsync_fp:%d hsync_pw:%d hsync_bp:%d vsync_idle_low:%d vsync_fp:%d vsync_pw:%d vsync_bp:%d pclk_neg:%d", + spiController->spi_config.speed, panel_config->rgb.timings.flags.hsync_idle_low, panel_config->rgb.timings.hsync_front_porch, panel_config->rgb.timings.hsync_pulse_width, + panel_config->rgb.timings.hsync_back_porch, panel_config->rgb.timings.flags.vsync_idle_low, panel_config->rgb.timings.vsync_front_porch, + panel_config->rgb.timings.vsync_pulse_width, panel_config->rgb.timings.vsync_back_porch, panel_config->rgb.timings.flags.pclk_active_neg); -#endif // USE_ESP32_S3 +#endif // SOC_LCD_RGB_SUPPORTED } #endif @@ -765,10 +965,10 @@ uint16_t cmd_offset = 0; #endif while (1) { uint8_t iob; - SPI_CS_LOW + spiController->csLow(); iob = dsp_cmds[cmd_offset++]; index++; - ulcd_command(iob); + spiController->writeCommand(iob); uint8_t args = dsp_cmds[cmd_offset++]; index++; #ifdef UDSP_DEBUG @@ -780,9 +980,9 @@ uint16_t cmd_offset = 0; #ifdef UDSP_DEBUG AddLog(LOG_LEVEL_DEBUG, "UDisplay: %02x", iob); #endif - ulcd_data8(iob); + spiController->writeData8(iob); } - SPI_CS_HIGH + spiController->csHigh(); if (args & 0x80) { // delay after the command delay_arg(args); } @@ -802,11 +1002,14 @@ uint16_t index = 0; #endif while (1) { uint8_t iob; - SPI_CS_LOW + spiController->csLow(); iob = dsp_cmds[cmd_offset++]; index++; if ((ep_mode == 1 || ep_mode == 3) && iob >= EP_RESET) { // epaper pseudo opcodes + if (!universal_panel) return; + EPDPanel* epd = static_cast(universal_panel); + uint8_t args = dsp_cmds[cmd_offset++]; index++; #ifdef UDSP_DEBUG @@ -821,42 +1024,41 @@ uint16_t index = 0; reset_pin(iob, iob); break; case EP_LUT_FULL: - SetLut(lut_full); - ep_update_mode = DISPLAY_INIT_FULL; + epd->setLut(epd->cfg.lut_full_data, epd->cfg.lutfsize); + epd->setUpdateMode(DISPLAY_INIT_FULL); break; case EP_LUT_PARTIAL: - SetLut(lut_partial); - ep_update_mode = DISPLAY_INIT_PARTIAL; + epd->setLut(epd->cfg.lut_partial_data, epd->cfg.lutpsize); + epd->setUpdateMode(DISPLAY_INIT_PARTIAL); break; case EP_WAITIDLE: if (args & 1) { iob = dsp_cmds[cmd_offset++]; index++; } - //delay(iob * 10); - delay_sync(iob * 10); + epd->delay_sync(iob * 10); break; case EP_SET_MEM_AREA: - SetMemoryArea(0, 0, gxs - 1, gys - 1); + epd->setMemoryArea(0, 0, gxs - 1, gys - 1); break; case EP_SET_MEM_PTR: - SetMemoryPointer(0, 0); + epd->setMemoryPointer(0, 0); break; case EP_SEND_DATA: - Send_EP_Data(); + epd->sendEPData(); break; case EP_CLR_FRAME: - ClearFrameMemory(0xFF); + epd->clearFrameMemory(0xFF); break; case EP_SEND_FRAME: - SetFrameMemory(framebuffer); + epd->setFrameMemory(framebuffer); break; case EP_BREAK_RR_EQU: if (args & 1) { iob = dsp_cmds[cmd_offset++]; index++; if (iob == ESP_ResetInfoReason()) { - ep_update_mode = DISPLAY_INIT_PARTIAL; + epd->setUpdateMode(DISPLAY_INIT_PARTIAL); goto exit; } } @@ -866,7 +1068,7 @@ uint16_t index = 0; iob = dsp_cmds[cmd_offset++]; index++; if (iob != ESP_ResetInfoReason()) { - ep_update_mode = DISPLAY_INIT_PARTIAL; + epd->setUpdateMode(DISPLAY_INIT_PARTIAL); goto exit; } } @@ -881,7 +1083,7 @@ uint16_t index = 0; delay_arg(args); } } else { - if (spi_dc == -2) { + if (spiController->spi_config.dc == -2) { // pseudo opcodes switch (iob) { case UDSP_WRITE_16: @@ -892,7 +1094,7 @@ uint16_t index = 0; break; } } - ulcd_command(iob); + spiController->writeCommand(iob); uint8_t args = dsp_cmds[cmd_offset++]; index++; #ifdef UDSP_DEBUG @@ -905,12 +1107,12 @@ uint16_t index = 0; AddLog(LOG_LEVEL_DEBUG, "%02x ", iob ); #endif if (!allcmd_mode) { - ulcd_data8(iob); + spiController->writeData8(iob); } else { - ulcd_command(iob); + spiController->writeCommand(iob); } } - SPI_CS_HIGH + spiController->csHigh(); if (args & 0x80) { // delay after the command delay_arg(args); } @@ -961,24 +1163,24 @@ Renderer *uDisplay::Init(void) { wire = &Wire1; } #endif // ESP32 -/* - if (i2c_sda != i2c_scl) { - wire->begin(i2c_sda, i2c_scl); // TODO: aren't I2C buses already initialized? Shouldn't this be moved to display driver? - } -*/ -#ifdef UDSP_DEBUG - AddLog(LOG_LEVEL_DEBUG, "UDisplay: I2C cmds:%d", dsp_ncmds); -#endif - for (uint32_t cnt = 0; cnt < dsp_ncmds; cnt++) { - i2c_command(dsp_cmds[cnt]); -#ifdef UDSP_DEBUG - AddLog(LOG_LEVEL_DEBUG, "UDisplay: cmd=%x", dsp_cmds[cnt]); -#endif - } + if (wire) { + // Populate remaining I2C config fields (most already parsed directly into union) + panel_config->i2c.width = gxs; + panel_config->i2c.height = gys; + panel_config->i2c.bpp = bpp; + panel_config->i2c.i2c_address = i2caddr; + panel_config->i2c.wire = wire; + panel_config->i2c.cmd_display_on = dsp_on; + panel_config->i2c.cmd_display_off = dsp_off; + panel_config->i2c.init_commands = dsp_cmds; + panel_config->i2c.init_commands_count = dsp_ncmds; + + universal_panel = new i2c_panel(panel_config->i2c, frame_buffer); + } } - if (interface == _UDSP_SPI) { +if (interface == _UDSP_SPI) { if (bpanel >= 0) { #ifdef ESP32 @@ -988,65 +1190,7 @@ Renderer *uDisplay::Init(void) { digitalWrite(bpanel, HIGH); #endif // ESP32 } - if (spi_dc >= 0) { - pinMode(spi_dc, OUTPUT); - digitalWrite(spi_dc, HIGH); - } - if (spi_cs >= 0) { - pinMode(spi_cs, OUTPUT); - digitalWrite(spi_cs, HIGH); - } - -#ifdef ESP8266 - if (spi_nr <= 1) { - SPI.begin(); - uspi = &SPI; - } else { - pinMode(spi_clk, OUTPUT); - digitalWrite(spi_clk, LOW); - pinMode(spi_mosi, OUTPUT); - digitalWrite(spi_mosi, LOW); - if (spi_miso >= 0) { - pinMode(spi_miso, INPUT_PULLUP); - busy_pin = spi_miso; - } - } -#endif // ESP8266 - -#ifdef ESP32 - if (spi_nr == 1) { - uspi = &SPI; - uspi->begin(spi_clk, spi_miso, spi_mosi, -1); - if (lvgl_param.use_dma) { - spi_host = VSPI_HOST; - initDMA(lvgl_param.async_dma ? spi_cs : -1); // disable DMA CS if sync, we control it directly - } - - } else if (spi_nr == 2) { - uspi = new SPIClass(HSPI); - uspi->begin(spi_clk, spi_miso, spi_mosi, -1); - if (lvgl_param.use_dma) { - spi_host = HSPI_HOST; - initDMA(lvgl_param.async_dma ? spi_cs : -1); // disable DMA CS if sync, we control it directly - } - } else { - pinMode(spi_clk, OUTPUT); - digitalWrite(spi_clk, LOW); - pinMode(spi_mosi, OUTPUT); - digitalWrite(spi_mosi, LOW); - if (spi_miso >= 0) { - busy_pin = spi_miso; - pinMode(spi_miso, INPUT_PULLUP); -#ifdef UDSP_DEBUG - AddLog(LOG_LEVEL_DEBUG, "UDisplay: Dsp busy pin:%d", busy_pin); -#endif - } - } -#endif // ESP32 - - - spiSettings = SPISettings((uint32_t)spi_speed*1000000, MSBFIRST, SPI_MODE3); - SPI_BEGIN_TRANSACTION + // spiController->beginTransaction(); if (reset >= 0) { pinMode(reset, OUTPUT); @@ -1054,15 +1198,74 @@ Renderer *uDisplay::Init(void) { delay(50); reset_pin(50, 200); } + + if (ep_mode) { + // Populate remaining EPD config fields (LUT data already parsed into union) + panel_config->epd.width = gxs; + panel_config->epd.height = gys; + panel_config->epd.bpp = bpp; + panel_config->epd.ep_mode = ep_mode; + // Timing values already set in panel_config->epd (either defaults or from :T section) + panel_config->epd.reset_pin = reset; + panel_config->epd.busy_pin = spiController->spi_config.miso; + panel_config->epd.invert_colors = true; // IF_INVERT_COLOR was hardcoded to 1 + panel_config->epd.invert_framebuffer = true; // TODO: maybe use lvgl_param.invert_bw for per-display config? + panel_config->epd.busy_invert = (bool)lvgl_param.busy_invert; + + // Set callback for sending command sequences + panel_config->epd.send_cmds_callback = [this](uint16_t offset, uint16_t count) { + this->send_spi_cmds(offset, count); + }; + + // Create EPD panel BEFORE sending init commands (send_spi_cmds needs universal_panel) + universal_panel = new EPDPanel(panel_config->epd, spiController, frame_buffer); + send_spi_cmds(0, dsp_ncmds); + + // After descriptor init commands, do initial EPD setup + EPDPanel* epd = static_cast(universal_panel); + epd->resetDisplay(); + if (epd->cfg.lut_full && epd->cfg.lut_full_len > 0) { + epd->setLut(epd->cfg.lut_full, epd->cfg.lut_full_len); + } + epd->clearFrameMemory(0xFF); + epd->displayFrame(); + + // Send full update command sequence if available + if (epd->cfg.epc_full_cnt) { + send_spi_cmds(epd->cfg.epcoffs_full, epd->cfg.epc_full_cnt); + } + + // Set update mode to partial for subsequent updates + epd->setUpdateMode(DISPLAY_INIT_PARTIAL); + } else { + AddLog(2,"SPI Panel!"); + // Populate remaining SPI config fields (most already parsed directly into union) + panel_config->spi.width = gxs; + panel_config->spi.height = gys; + panel_config->spi.bpp = bpp; + panel_config->spi.cmd_display_on = dsp_on; + panel_config->spi.cmd_display_off = dsp_off; + panel_config->spi.reset_pin = reset; + panel_config->spi.bpanel = bpanel; + panel_config->spi.all_commands_mode = allcmd_mode; + + send_spi_cmds(0, dsp_ncmds); // Send init commands for regular SPI + universal_panel = new SPIPanel(panel_config->spi, spiController, frame_buffer); +#ifdef ESP32 + spiController->initDMA(panel_config->spi.width, lvgl_param.flushlines, lvgl_param.data); +#endif + universal_panel->fillRect(0, 0, 100, 100, 0xFF00); // Yellow + delay(2000); // Hold for 2 seconds before anything else runs + } - send_spi_cmds(0, dsp_ncmds); + // spiController->endTransaction(); - SPI_END_TRANSACTION - - } + // EPD LUT initialization is now handled inside EPDPanel constructor + // so we don't need to call Init_EPD here anymore +} +#if SOC_LCD_RGB_SUPPORTED if (interface == _UDSP_RGB) { -#ifdef USE_ESP32_S3 if (!UsePSRAM()) { // RGB is not supported on S3 without PSRAM #ifdef UDSP_DEBUG AddLog(LOG_LEVEL_INFO, "UDisplay: Dsp RGB requires PSRAM, abort"); @@ -1073,209 +1276,92 @@ Renderer *uDisplay::Init(void) { if (bpanel >= 0) { analogWrite(bpanel, 32); } - esp_lcd_rgb_panel_config_t *_panel_config = (esp_lcd_rgb_panel_config_t *)heap_caps_calloc(1, sizeof(esp_lcd_rgb_panel_config_t), MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); - _panel_config->clk_src = LCD_CLK_SRC_PLL160M; + panel_config->rgb.clk_src = LCD_CLK_SRC_PLL160M; + panel_config->rgb.timings.pclk_hz = spi_speed*1000000; + panel_config->rgb.timings.h_res = gxs; + panel_config->rgb.timings.v_res = gys; + panel_config->rgb.data_width = 16; // RGB565 in parallel mode, thus 16bit in width + panel_config->rgb.sram_trans_align = 8; + panel_config->rgb.psram_trans_align = 64; - //if (spi_speed > 14) { - //spi_speed = 14; - //} - _panel_config->timings.pclk_hz = spi_speed*1000000; - _panel_config->timings.h_res = gxs; - _panel_config->timings.v_res = gys; - - _panel_config->timings.hsync_pulse_width = hsync_pulse_width; - _panel_config->timings.hsync_back_porch = hsync_back_porch; - _panel_config->timings.hsync_front_porch = hsync_front_porch; - _panel_config->timings.vsync_pulse_width = vsync_pulse_width; - _panel_config->timings.vsync_back_porch = vsync_back_porch; - _panel_config->timings.vsync_front_porch = vsync_front_porch; - _panel_config->timings.flags.hsync_idle_low = (hsync_polarity == 0) ? 1 : 0; - _panel_config->timings.flags.vsync_idle_low = (vsync_polarity == 0) ? 1 : 0; - _panel_config->timings.flags.de_idle_high = 0; - _panel_config->timings.flags.pclk_active_neg = pclk_active_neg; - _panel_config->timings.flags.pclk_idle_high = 0; - - _panel_config->data_width = 16; // RGB565 in parallel mode, thus 16bit in width - _panel_config->sram_trans_align = 8; - _panel_config->psram_trans_align = 64; - _panel_config->hsync_gpio_num = hsync; - _panel_config->vsync_gpio_num = vsync; - _panel_config->de_gpio_num = de; - _panel_config->pclk_gpio_num = pclk; - - // assume that byte swapping of 16-bit color is done only upon request - // via display.ini and not by callers of pushColor() - // -> swap bytes by swapping GPIO numbers - int8_t *par_db8 = lvgl_param.swap_color ? par_dbl : par_dbh; - for (uint32_t cnt = 0; cnt < 8; cnt ++) { - _panel_config->data_gpio_nums[cnt] = par_db8[cnt]; + // Handle byte swapping by swapping the low and high byte pin assignments + if (lvgl_param.swap_color) { + for (uint32_t cnt = 0; cnt < 8; cnt++) { + int8_t temp = panel_config->rgb.data_gpio_nums[cnt]; + panel_config->rgb.data_gpio_nums[cnt] = panel_config->rgb.data_gpio_nums[cnt + 8]; + panel_config->rgb.data_gpio_nums[cnt + 8] = temp; + } + lvgl_param.swap_color = 0; } - par_db8 = lvgl_param.swap_color ? par_dbh : par_dbl; - for (uint32_t cnt = 0; cnt < 8; cnt ++) { - _panel_config->data_gpio_nums[cnt + 8] = par_db8[cnt]; - } - lvgl_param.swap_color = 0; - _panel_config->disp_gpio_num = GPIO_NUM_NC; + panel_config->rgb.disp_gpio_num = GPIO_NUM_NC; - _panel_config->flags.disp_active_low = 0; - _panel_config->flags.refresh_on_demand = 0; - _panel_config->flags.fb_in_psram = 1; // allocate frame buffer in PSRAM + panel_config->rgb.flags.disp_active_low = 0; + panel_config->rgb.flags.refresh_on_demand = 0; + panel_config->rgb.flags.fb_in_psram = 1; // allocate frame buffer in PSRAM - ESP_ERROR_CHECK(esp_lcd_new_rgb_panel(_panel_config, &_panel_handle)); - ESP_ERROR_CHECK(esp_lcd_panel_reset(_panel_handle)); - ESP_ERROR_CHECK(esp_lcd_panel_init(_panel_handle)); + universal_panel = new RGBPanel(&panel_config->rgb); + rgb_fb = universal_panel->framebuffer; + // super->setDrawMode(); - uint16_t color = random(0xffff); - ESP_ERROR_CHECK(_panel_handle->draw_bitmap(_panel_handle, 0, 0, 1, 1, &color)); - - void * buf = NULL; - esp_lcd_rgb_panel_get_frame_buffer(_panel_handle, 1, &buf); - rgb_fb = (uint16_t *)buf; - -#endif // USE_ESP32_S3 } +#endif // SOC_LCD_RGB_SUPPORTED +#if SOC_MIPI_DSI_SUPPORTED + if (interface == _UDSP_DSI) { + // Pass init commands to DSI panel config + panel_config->dsi.init_commands = dsp_cmds; + panel_config->dsi.init_commands_count = dsp_ncmds; + + // Pass display on/off commands from descriptor + panel_config->dsi.cmd_display_on = dsp_on; + panel_config->dsi.cmd_display_off = dsp_off; + + universal_panel = new DSIPanel(panel_config->dsi); + rgb_fb = universal_panel->framebuffer; + + if (bpanel >= 0) { + analogWrite(bpanel, 32); + } + } +#endif if (interface == _UDSP_PAR8 || interface == _UDSP_PAR16) { - -#ifdef USE_ESP32_S3 - - if (bpanel >= 0) { - analogWrite(bpanel, 32); - } - - pinMode(par_cs, OUTPUT); - digitalWrite(par_cs, HIGH); - - pinMode(par_rs, OUTPUT); - digitalWrite(par_rs, HIGH); - - pinMode(par_wr, OUTPUT); - digitalWrite(par_wr, HIGH); - - if (par_rd >= 0) { - pinMode(par_rd, OUTPUT); - digitalWrite(par_rd, HIGH); - } - - for (uint32_t cnt = 0; cnt < 8; cnt ++) { - pinMode(par_dbl[cnt], OUTPUT); - } - - uint8_t bus_width = 8; - - if (interface == _UDSP_PAR16) { - for (uint32_t cnt = 0; cnt < 8; cnt ++) { - pinMode(par_dbh[cnt], OUTPUT); + #ifdef UDISPLAY_I80 + // Reset handling + if (reset >= 0) { + pinMode(reset, OUTPUT); + digitalWrite(reset, HIGH); + delay(50); + reset_pin(50, 200); } - bus_width = 16; - } - - if (reset >= 0) { - pinMode(reset, OUTPUT); - digitalWrite(reset, HIGH); - delay(50); - reset_pin(50, 200); - } - - esp_lcd_i80_bus_config_t bus_config = { - .dc_gpio_num = par_rs, - .wr_gpio_num = par_wr, - .clk_src = LCD_CLK_SRC_DEFAULT, - .bus_width = bus_width, - .max_transfer_bytes = 32768 - }; - - if (interface == _UDSP_PAR8) { - for (uint32_t cnt = 0; cnt < 8; cnt ++) { - bus_config.data_gpio_nums[cnt] = par_dbl[cnt]; + + // Populate remaining I80 config fields (most already parsed directly into union) + // Control and data pins already parsed directly into config during INI parsing + panel_config->i80.width = gxs; + panel_config->i80.height = gys; + panel_config->i80.bpp = bpp; + panel_config->i80.color_mode = col_mode; + panel_config->i80.bus_width = (uint8_t)((interface == _UDSP_PAR16) ? 16 : 8); + panel_config->i80.clock_speed_hz = (uint32_t)spi_speed * 1000000; + panel_config->i80.allcmd_mode = allcmd_mode; + // Set sa_mode default if not parsed (old descriptors may not have it) + if (panel_config->i80.sa_mode == 0) { + panel_config->i80.sa_mode = sa_mode; // Use global default (16) } - } else { - for (uint32_t cnt = 0; cnt < 8; cnt ++) { - bus_config.data_gpio_nums[cnt] = par_dbl[cnt]; + panel_config->i80.init_commands = dsp_cmds; + panel_config->i80.init_commands_count = dsp_ncmds; + + universal_panel = new I80Panel(panel_config->i80); + + if (bpanel >= 0) { + analogWrite(bpanel, 32); } - for (uint32_t cnt = 0; cnt < 8; cnt ++) { - bus_config.data_gpio_nums[cnt + 8] = par_dbh[cnt]; - } - } - - // to disable SPI TRANSACTION - spi_nr = 3; - spi_cs = par_cs; - - _i80_bus = nullptr; - - esp_lcd_new_i80_bus(&bus_config, &_i80_bus); - - uint32_t div_a, div_b, div_n, clkcnt; - calcClockDiv(&div_a, &div_b, &div_n, &clkcnt, 240*1000*1000, spi_speed*1000000); - lcd_cam_lcd_clock_reg_t lcd_clock; - lcd_clock.lcd_clkcnt_n = std::max((uint32_t)1u, clkcnt - 1); // ESP_IDF_VERSION_MAJOR >= 5 - lcd_clock.lcd_clk_equ_sysclk = (clkcnt == 1); - lcd_clock.lcd_ck_idle_edge = true; - lcd_clock.lcd_ck_out_edge = false; - lcd_clock.lcd_clkm_div_num = div_n; - lcd_clock.lcd_clkm_div_b = div_b; - lcd_clock.lcd_clkm_div_a = div_a; - lcd_clock.lcd_clk_sel = 2; // clock_select: 1=XTAL CLOCK / 2=240MHz / 3=160MHz - lcd_clock.clk_en = true; - _clock_reg_value = lcd_clock.val; - - _alloc_dmadesc(1); - - _dev = &LCD_CAM; - - pb_beginTransaction(); - uint16_t index = 0; - while (1) { - uint8_t iob; - cs_control(0); - - iob = dsp_cmds[index++]; - pb_writeCommand(iob, 8); - - uint8_t args = dsp_cmds[index++]; - #ifdef UDSP_DEBUG - AddLog(LOG_LEVEL_DEBUG, "UDisplay: cmd, args %02x, %d", iob, args & 0x1f); - #endif - for (uint32_t cnt = 0; cnt < (args & 0x1f); cnt++) { - iob = dsp_cmds[index++]; - #ifdef UDSP_DEBUG - AddLog(LOG_LEVEL_DEBUG, "UDisplay: %02x", iob); - #endif - pb_writeData(iob, 8); - } - cs_control(1); - if (args & 0x80) { // delay after the command - uint32_t delay_ms = 0; - switch (args & 0xE0) { - case 0x80: delay_ms = 150; break; - case 0xA0: delay_ms = 10; break; - case 0xE0: delay_ms = 500; break; - } - if (delay_ms > 0) { - delay(delay_ms); - #ifdef UDSP_DEBUG - AddLog(LOG_LEVEL_DEBUG, "UDisplay: delay %d ms", delay_ms); - #endif - } - - } - if (index >= dsp_ncmds) break; - } - - pb_endTransaction(); - - -#endif // USE_ESP32_S3 - + #endif } - - // must init luts on epaper - if (ep_mode) { - if (ep_mode == 2) Init_EPD(DISPLAY_INIT_FULL); - //if (ep_mode == 1) Init_EPD(DISPLAY_INIT_PARTIAL); + + if(!universal_panel){ + return NULL; } #ifdef UDSP_DEBUG @@ -1286,30 +1372,37 @@ Renderer *uDisplay::Init(void) { void uDisplay::DisplayInit(int8_t p, int8_t size, int8_t rot, int8_t font) { if (p != DISPLAY_INIT_MODE && ep_mode) { - ep_update_mode = p; if (p == DISPLAY_INIT_PARTIAL) { - if (lutpsize) { + if (universal_panel) { + EPDPanel* epd = static_cast(universal_panel); + epd->setUpdateMode(DISPLAY_INIT_PARTIAL); + if (epd->cfg.lutpsize) { #ifdef UDSP_DEBUG - AddLog(LOG_LEVEL_DEBUG, "init partial epaper mode"); + AddLog(LOG_LEVEL_DEBUG, "init partial epaper mode"); #endif - SetLut(lut_partial); - Updateframe_EPD(); - delay_sync(lutptime * 10); + epd->setLut(epd->cfg.lut_partial_data, epd->cfg.lutpsize); + epd->updateFrame(); + epd->delay_sync(epd->cfg.lut_partial_time * 10); + } } return; } else if (p == DISPLAY_INIT_FULL) { #ifdef UDSP_DEBUG AddLog(LOG_LEVEL_DEBUG, "init full epaper mode"); #endif - if (lutfsize) { - SetLut(lut_full); - Updateframe_EPD(); + if (universal_panel) { + EPDPanel* epd = static_cast(universal_panel); + epd->setUpdateMode(DISPLAY_INIT_FULL); + if (epd->cfg.lutfsize) { + epd->setLut(epd->cfg.lut_full_data, epd->cfg.lutfsize); + epd->updateFrame(); + } + if (ep_mode == 2) { + epd->clearFrame_42(); + epd->displayFrame_42(); + } + epd->delay_sync(epd->cfg.lut_full_time * 10); } - if (ep_mode == 2) { - ClearFrame_42(); - DisplayFrame_42(); - } - delay_sync(lutftime * 10); return; } } else { @@ -1332,127 +1425,4 @@ void uDisplay::DisplayInit(int8_t p, int8_t size, int8_t rot, int8_t font) { } } -void uDisplay::i2c_command(uint8_t val) { - //AddLog(LOG_LEVEL_DEBUG, "%02x\n",val ); - wire->beginTransmission(i2caddr); - wire->write(0); - wire->write(val); - wire->endTransmission(); -} - - -#define WIRE_MAX 32 - -void uDisplay::Updateframe(void) { - - if (interface == _UDSP_RGB) { - return; - } - - if (ep_mode) { - Updateframe_EPD(); - return; - } - - if (interface == _UDSP_I2C) { - - #if 0 - i2c_command(saw_1); - i2c_command(i2c_page_start); - i2c_command(i2c_page_end); - i2c_command(saw_2); - i2c_command(i2c_col_start); - i2c_command(i2c_col_end); - - uint16_t count = gxs * ((gys + 7) / 8); - uint8_t *ptr = framebuffer; - wire->beginTransmission(i2caddr); - i2c_command(saw_3); - uint8_t bytesOut = 1; - while (count--) { - if (bytesOut >= WIRE_MAX) { - wire->endTransmission(); - wire->beginTransmission(i2caddr); - i2c_command(saw_3); - bytesOut = 1; - } - i2c_command(*ptr++); - bytesOut++; - } - wire->endTransmission(); -#else - - i2c_command(saw_1 | 0x0); // set low col = 0, 0x00 - i2c_command(i2c_page_start | 0x0); // set hi col = 0, 0x10 - i2c_command(i2c_page_end | 0x0); // set startline line #0, 0x40 - - uint8_t ys = gys >> 3; - uint8_t xs = gxs >> 3; - //uint8_t xs = 132 >> 3; - uint8_t m_row = saw_2; - uint8_t m_col = i2c_col_start; - - uint16_t p = 0; - - uint8_t i, j, k = 0; - - for ( i = 0; i < ys; i++) { - // send a bunch of data in one xmission - i2c_command(0xB0 + i + m_row); //set page address - i2c_command(m_col & 0xf); //set lower column address - i2c_command(0x10 | (m_col >> 4)); //set higher column address - - for ( j = 0; j < 8; j++) { - wire->beginTransmission(i2caddr); - wire->write(0x40); - for ( k = 0; k < xs; k++, p++) { - wire->write(framebuffer[p]); - } - wire->endTransmission(); - } - } -#endif - - } - - - if (interface == _UDSP_SPI) { - if (framebuffer == nullptr) { return; } - - SPI_BEGIN_TRANSACTION - SPI_CS_LOW - - // below commands are not needed for SH1107 - // ulcd_command(saw_1 | 0x0); // set low col = 0, 0x00 - // ulcd_command(i2c_page_start | 0x0); // set hi col = 0, 0x10 - // ulcd_command(i2c_page_end | 0x0); // set startline line #0, 0x40 - - uint8_t ys = gys >> 3; - uint8_t xs = gxs >> 3; - //uint8_t xs = 132 >> 3; - uint8_t m_row = saw_2; - uint8_t m_col = i2c_col_start; - // AddLog(LOG_LEVEL_DEBUG, "m_row=%d m_col=%d xs=%d ys=%d\n", m_row, m_col, xs, ys); - - uint16_t p = 0; - - uint8_t i, j, k = 0; - for ( i = 0; i < ys; i++) { // i = line from 0 to ys - // send a bunch of data in one xmission - ulcd_command(0xB0 + i + m_row); //set page address - ulcd_command(m_col & 0xf); //set lower column address - ulcd_command(0x10 | (m_col >> 4)); //set higher column address - - for ( j = 0; j < 8; j++) { - for ( k = 0; k < xs; k++, p++) { - ulcd_data8(framebuffer[p]); - } - } - } - - SPI_CS_HIGH - SPI_END_TRANSACTION - - } - -} +#define WIRE_MAX 32 \ No newline at end of file diff --git a/lib/lib_display/UDisplay/uDisplay.h b/lib/lib_display/UDisplay/uDisplay.h index 53d565c26..cd7e2d55f 100644 --- a/lib/lib_display/UDisplay/uDisplay.h +++ b/lib/lib_display/UDisplay/uDisplay.h @@ -7,14 +7,54 @@ #include #ifdef ESP32 +#if __has_include("soc/soc_caps.h") +# include "soc/soc_caps.h" +#else +# error "No ESP capability header found" +#endif +#if (SOC_LCDCAM_I80_NUM_BUSES && !SOC_PARLIO_GROUPS) + #define UDISPLAY_I80 + #include "uDisplay_I80_panel.h" +#endif + +#if defined(SOC_LCD_RGB_SUPPORTED) + #include "uDisplay_RGB_panel.h" +#endif +#if SOC_MIPI_DSI_SUPPORTED + #include "uDisplay_DSI_panel.h" +#endif + #ifdef CONFIG_IDF_TARGET_ESP32S3 #define USE_ESP32_S3 #endif #include "soc/gpio_periph.h" #include -#include "driver/spi_master.h" +// #include "driver/spi_master.h" #endif +#include "uDisplay_SPI_controller.h" +#include "uDisplay_I2C_panel.h" +#include "uDisplay_EPD_panel.h" +#include "uDisplay_SPI_panel.h" + +// ===== Panel Config Union ===== +// Union to hold any panel configuration type +// Only one config is active at a time based on interface type +union PanelConfigUnion { + SPIPanelConfig spi; + I2CPanelConfig i2c; + EPDPanelConfig epd; +#ifdef UDISPLAY_I80 + I80PanelConfig i80; +#endif +#if SOC_LCD_RGB_SUPPORTED + esp_lcd_rgb_panel_config_t rgb; // ESP-IDF native config +#endif +#if SOC_MIPI_DSI_SUPPORTED + DSIPanelConfig dsi; +#endif +}; + enum { UT_RD,UT_RDM,UT_CP,UT_RTF,UT_MV,UT_MVB,UT_RT,UT_RTT,UT_RDW,UT_RDWM,UT_WR,UT_WRW,UT_CPR,UT_AND,UT_SCALE,UT_LIM,UT_DBG,UT_GSRT,UT_XPT,UT_CPM,UT_END }; @@ -24,37 +64,20 @@ enum { #define UDSP_READ_STATUS 0xf2 -#define SIMPLERS_XP par_dbl[1] -#define SIMPLERS_XM par_cs -#define SIMPLERS_YP par_rs -#define SIMPLERS_YM par_dbl[0] - -#ifdef USE_ESP32_S3 -#include -#include "esp_private/gdma.h" -#include -#include -#include -#include -#include "esp_lcd_panel_interface.h" -#include "esp_lcd_panel_rgb.h" -#include "esp_pm.h" -#include "esp_lcd_panel_ops.h" -#include -#include -#include "esp_rom_lldesc.h" -#endif // USE_ESP32_S3 +// Simple resistive touch pin mapping (I80 only): +// XP = data_pins_low[1], XM = cs_pin, YP = dc_pin, YM = data_pins_low[0] #define _UDSP_I2C 1 #define _UDSP_SPI 2 #define _UDSP_PAR8 3 #define _UDSP_PAR16 4 #define _UDSP_RGB 5 +#define _UDSP_DSI 6 #define UDISP1_WHITE 1 #define UDISP1_BLACK 0 -#define MAX_LUTS 5 +// #define MAX_LUTS 5 #define DISPLAY_INIT_MODE 0 #define DISPLAY_INIT_PARTIAL 1 @@ -95,14 +118,25 @@ class uDisplay : public Renderer { private: uint8_t *frame_buffer; - uint8_t *lut_full; - uint8_t *lut_partial; - uint8_t *lut_array[MAX_LUTS]; + // uint8_t *lut_full; // MOVED to EPDPanelConfig.lut_full_data + // uint8_t *lut_partial; // MOVED to EPDPanelConfig.lut_partial_data + // uint8_t *lut_array[MAX_LUTS]; // MOVED to EPDPanelConfig.lut_array_data +#if SOC_MIPI_DSI_SUPPORTED + uint8_t dsp_cmds[1024]; // for DSI, does not hurt for ESP32 +#else uint8_t dsp_cmds[256]; +#endif char dname[16]; - SPIClass *uspi; + + SPIController *spiController; TwoWire *wire; - SPISettings spiSettings; + UniversalPanel* universal_panel = nullptr; + + // ===== Panel Configuration Union ===== + // Heap-allocated union holding the active panel config + // Allocated after parsing :H line, populated during INI parsing + PanelConfigUnion* panel_config = nullptr; + uint16_t x_addr_offs[4]; uint16_t y_addr_offs[4]; @@ -117,8 +151,8 @@ private: uint16_t seta_xp2; uint16_t seta_yp1; uint16_t seta_yp2; - uint16_t lutptime; - uint16_t lut3time; + // uint16_t lutptime; // MOVED to EPDPanelConfig.lut_partial_time + // uint16_t lut3time; // MOVED to EPDPanelConfig.update_time uint16_t lut_num; uint8_t bpp; @@ -129,14 +163,14 @@ private: uint8_t i2c_col_end; uint8_t i2c_page_start; uint8_t i2c_page_end; - uint8_t dsp_ncmds; + uint16_t dsp_ncmds; uint8_t dsp_on; uint8_t dsp_off; uint8_t allcmd_mode; uint8_t splash_size; uint8_t dimmer8; uint8_t spi_speed; - uint8_t spi_nr; + // uint8_t spi_nr; uint8_t rot[4]; uint8_t rot_t[4]; uint8_t madctrl; @@ -150,19 +184,19 @@ private: uint8_t inv_off; uint8_t sa_mode; uint8_t dim_op; - uint8_t lutfsize; - uint8_t lutpsize; - uint8_t lut_siz_full; - uint8_t lut_siz_partial; - uint8_t epcoffs_full; - uint8_t epc_full_cnt; - uint8_t epcoffs_part; - uint8_t epc_part_cnt; - uint8_t lut_cnt[MAX_LUTS]; - uint8_t lut_cmd[MAX_LUTS]; - uint8_t lut_siz[MAX_LUTS]; + // uint8_t lutfsize; // MOVED to EPDPanelConfig.lutfsize + // uint8_t lutpsize; // MOVED to EPDPanelConfig.lutpsize + // uint8_t lut_siz_full; // Local variable only + // uint8_t lut_siz_partial; // Local variable only + // uint8_t epcoffs_full; // MOVED to EPDPanelConfig.epcoffs_full + // uint8_t epc_full_cnt; // MOVED to EPDPanelConfig.epc_full_cnt + // uint8_t epcoffs_part; // MOVED to EPDPanelConfig.epcoffs_part + // uint8_t epc_part_cnt; // MOVED to EPDPanelConfig.epc_part_cnt + // uint8_t lut_cnt[MAX_LUTS]; // MOVED to EPDPanelConfig.lut_cnt_data + // uint8_t lut_cmd[MAX_LUTS]; // MOVED to EPDPanelConfig.lut_cmd + // uint8_t lut_siz[MAX_LUTS]; // MOVED to EPDPanelConfig.lut_siz uint8_t ep_mode; - uint8_t ep_update_mode; + // uint8_t ep_update_mode; // MOVED to EPDPanel.update_mode uint8_t sspi; int8_t spec_init; @@ -172,64 +206,29 @@ private: int8_t reset; int8_t splash_font; int8_t bpmode; - int8_t spi_cs; - int8_t spi_clk; - int8_t spi_mosi; - int8_t spi_dc; + // int8_t spi_cs; + // int8_t spi_clk; + // int8_t spi_mosi; + // int8_t spi_dc; int8_t bpanel; - int8_t spi_miso; - int8_t busy_pin; + // int8_t spi_miso; + // int8_t busy_pin; // MOVED to EPDPanelConfig.busy_pin (EPD-only) - int16_t lutftime; + // int16_t lutftime; // MOVED to EPDPanelConfig.lut_full_time int16_t rotmap_xmin; int16_t rotmap_xmax; int16_t rotmap_ymin; int16_t rotmap_ymax; - void beginTransaction(SPISettings s); - void endTransaction(void); void setAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1); void drawPixel(int16_t x, int16_t y, uint16_t color); void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color); void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color); uint32_t str2c(char **sp, char *vp, uint32_t len); + void i2c_command(uint8_t val); - void ulcd_command_one(uint8_t val); - void ulcd_command(uint8_t val); - void ulcd_data8(uint8_t val); - void ulcd_data16(uint16_t val); - void ulcd_data32(uint32_t val); - void write8(uint8_t val); - void write8_slow(uint8_t val); - void write9(uint8_t val, uint8_t dc); - void write9_slow(uint8_t val, uint8_t dc); - void hw_write9(uint8_t val, uint8_t dc); - void write16(uint16_t val); - void write32(uint32_t val); - void spi_data9(uint8_t d, uint8_t dc); - uint8_t readData(void); - uint8_t readStatus(void); - uint8_t writeReg16(uint8_t reg, uint16_t wval); - void WriteColor(uint16_t color); - void SetLut(const unsigned char* lut); - void SetLuts(void); - void DisplayFrame_29(void); - void Updateframe_EPD(); - void SetFrameMemory(const unsigned char* image_buffer); - void SetFrameMemory(const unsigned char* image_buffer, uint16_t x, uint16_t y, uint16_t image_width, uint16_t image_height); - void SetMemoryArea(int x_start, int y_start, int x_end, int y_end); - void SetMemoryPointer(int x, int y); - void DrawAbsolutePixel(int x, int y, int16_t color); - void drawPixel_EPD(int16_t x, int16_t y, uint16_t color); - void fillRect_EPD(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color); - void drawFastVLine_EPD(int16_t x, int16_t y, int16_t h, uint16_t color); - void drawFastHLine_EPD(int16_t x, int16_t y, int16_t w, uint16_t color); - void Init_EPD(int8_t p); - void spi_command_EPD(uint8_t val); - void spi_data8_EPD(uint8_t val); - void ClearFrameMemory(unsigned char color); - void ClearFrame_42(void); - void DisplayFrame_42(void); + + uint8_t strlen_ln(char *str); int32_t next_val(char **sp); uint32_t next_hex(char **sp); @@ -238,82 +237,11 @@ private: void delay_sync(int32_t time); void reset_pin(int32_t delayl, int32_t delayh); void delay_arg(uint32_t arg); - void Send_EP_Data(void); + void send_spi_cmds(uint16_t cmd_offset, uint16_t cmd_size); void send_spi_icmds(uint16_t cmd_size); -#ifdef USE_ESP32_S3 - int8_t par_cs; - int8_t par_rs; - int8_t par_wr; - int8_t par_rd; - - int8_t par_dbl[8]; - int8_t par_dbh[8]; - - int8_t de; - int8_t vsync; - int8_t hsync; - int8_t pclk; - - uint16_t hsync_polarity; - uint16_t hsync_front_porch; - uint16_t hsync_pulse_width; - uint16_t hsync_back_porch; - uint16_t vsync_polarity; - uint16_t vsync_front_porch; - uint16_t vsync_pulse_width; - uint16_t vsync_back_porch; - uint16_t pclk_active_neg; - - esp_lcd_panel_handle_t _panel_handle = NULL; - - esp_lcd_i80_bus_handle_t _i80_bus = nullptr; - gdma_channel_handle_t _dma_chan; - lldesc_t *_dmadesc = nullptr; - uint32_t _dmadesc_size = 0; - uint32_t _clock_reg_value; - void calcClockDiv(uint32_t* div_a, uint32_t* div_b, uint32_t* div_n, uint32_t* clkcnt, uint32_t baseClock, uint32_t targetFreq); - void _alloc_dmadesc(size_t len); - void _setup_dma_desc_links(const uint8_t *data, int32_t len); - void pb_beginTransaction(void); - void pb_endTransaction(void); - void pb_wait(void); - bool pb_busy(void); - void _pb_init_pin(bool); - bool pb_writeCommand(uint32_t data, uint_fast8_t bit_length); - void pb_writeData(uint32_t data, uint_fast8_t bit_length); - void pb_pushPixels(uint16_t* data, uint32_t length, bool swap_bytes, bool use_dma); - void pb_writeBytes(const uint8_t* data, uint32_t length, bool use_dma); - void _send_align_data(void); - volatile lcd_cam_dev_t* _dev; - uint32_t* _cache_flip; - static constexpr size_t CACHE_SIZE = 256; - uint32_t _cache[2][CACHE_SIZE / sizeof(uint32_t)]; - bool _has_align_data; - uint8_t _align_data; - void cs_control(bool level); - uint32_t get_sr_touch(uint32_t xp, uint32_t xm, uint32_t yp, uint32_t ym); - void drawPixel_RGB(int16_t x, int16_t y, uint16_t color); -#endif - -#ifdef ESP32 - // dma section - bool DMA_Enabled = false; - uint8_t spiBusyCheck = 0; - spi_transaction_t trans; - spi_device_handle_t dmaHAL; - spi_host_device_t spi_host = VSPI_HOST; - // spi_host_device_t spi_host = VSPI_HOST; - bool initDMA(int32_t ctrl_cs); - void deInitDMA(void); - bool dmaBusy(void); - void dmaWait(void); - void pushPixelsDMA(uint16_t* image, uint32_t len); - void pushPixels3DMA(uint8_t* image, uint32_t len); -#endif // ESP32 - #ifdef USE_UNIVERSAL_TOUCH // universal touch driver void ut_trans(char **sp, uint8_t **ut_code); diff --git a/lib/lib_display/UDisplay/uDisplay_DSI_panel.cpp b/lib/lib_display/UDisplay/uDisplay_DSI_panel.cpp new file mode 100644 index 000000000..c67a7b482 --- /dev/null +++ b/lib/lib_display/UDisplay/uDisplay_DSI_panel.cpp @@ -0,0 +1,289 @@ +// ====================================================== +// 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 diff --git a/lib/lib_display/UDisplay/uDisplay_DSI_panel.h b/lib/lib_display/UDisplay/uDisplay_DSI_panel.h new file mode 100644 index 000000000..ad5bae5e3 --- /dev/null +++ b/lib/lib_display/UDisplay/uDisplay_DSI_panel.h @@ -0,0 +1,103 @@ +// WIP - NOT REALLY IMPLEMENTED!!! +// ====================================================== +// uDisplay_DSI_panel.h - MIPI-DSI Display Panel Implementation +// ====================================================== + +#pragma once +#ifdef ESP32 +#if __has_include("soc/soc_caps.h") +# include "soc/soc_caps.h" +#else +# error "No ESP capability header found" +#endif + +#if SOC_MIPI_DSI_SUPPORTED + +#include "uDisplay_panel.h" +#include "esp_lcd_panel_interface.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_panel_ops.h" +#include "esp_lcd_mipi_dsi.h" +#include "esp_ldo_regulator.h" + +struct DSIPanelConfig { + // Basic display info + uint16_t width; + uint16_t height; + uint8_t bpp; // 24 + + // DSI parameters (from :H line) + uint8_t dsi_lanes; // 2 + int8_t te_pin; // -1 (no TE) + int8_t reset_pin; // -1 (no reset control) + int ldo_channel; // 3 + int ldo_voltage_mv; // 2500 + uint32_t pixel_clock_hz; // 54000000 + uint32_t lane_speed_mbps; // 750 + uint8_t rgb_order; // 0=RGB, 1=BGR + uint8_t data_endian; // 0=Big, 1=Little + + // Video timing (from :V line) + struct { + uint16_t h_front_porch; // 160 + uint16_t v_front_porch; // 40 + uint16_t h_back_porch; // 160 + uint16_t h_sync_pulse; // 12 + uint16_t v_sync_pulse; // 10 + uint16_t v_back_porch; // 23 + } timing; + + // Init commands (from :I section) + uint8_t* init_commands; + uint16_t init_commands_count; + + // Display on/off commands (from :O and :o lines) + uint8_t cmd_display_on; // 0x29 + uint8_t cmd_display_off; // 0x28 +}; + +class DSIPanel : public UniversalPanel { +public: + // Constructor - takes ESP-IDF panel handle (already initialized) + DSIPanel(const DSIPanelConfig& config); + ~DSIPanel(); + + // Core graphics API (must return bool) + bool drawPixel(int16_t x, int16_t y, uint16_t color) override; + bool fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) override; + bool pushColors(uint16_t *data, uint16_t len, bool not_swapped) override; + bool setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) override; + bool drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) override; + bool drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) override; + + // Control API + bool displayOnff(int8_t on) override; + bool invertDisplay(bool invert) override; + bool setRotation(uint8_t rotation) override; + bool updateFrame() override; + + // Get direct framebuffer access (for DPI mode) + uint16_t* framebuffer = nullptr; + +private: + // ESP-IDF panel handle + esp_lcd_panel_handle_t panel_handle = nullptr; + esp_lcd_panel_io_handle_t io_handle = nullptr; + esp_ldo_channel_handle_t ldo_handle = nullptr; + DSIPanelConfig cfg; + void sendInitCommandsDBI(); + + // Display parameters + uint8_t rotation = 0; + + // Address window tracking + int16_t window_x0 = 0; + int16_t window_y0 = 0; + int16_t window_x1 = 0; + int16_t window_y1 = 0; + size_t framebuffer_size = 0; + uint32_t framebuffer_dirty = false; +}; + +#endif // SOC_MIPI_DSI_SUPPORTED +#endif // ESP32 \ No newline at end of file diff --git a/lib/lib_display/UDisplay/uDisplay_EPD_panel.cpp b/lib/lib_display/UDisplay/uDisplay_EPD_panel.cpp new file mode 100644 index 000000000..3ec15c75b --- /dev/null +++ b/lib/lib_display/UDisplay/uDisplay_EPD_panel.cpp @@ -0,0 +1,514 @@ +// ====================================================== +// uDisplay_epd_panel.cpp - E-Paper Display Panel Implementation +// ====================================================== + +#include "uDisplay_EPD_panel.h" +#include + +// EPD Command Definitions +static constexpr uint8_t DRIVER_OUTPUT_CONTROL = 0x01; +static constexpr uint8_t BOOSTER_SOFT_START_CONTROL = 0x0C; +static constexpr uint8_t GATE_SCAN_START_POSITION = 0x0F; +static constexpr uint8_t DEEP_SLEEP_MODE = 0x10; +static constexpr uint8_t DATA_ENTRY_MODE_SETTING = 0x11; +static constexpr uint8_t SW_RESET = 0x12; +static constexpr uint8_t TEMPERATURE_SENSOR_CONTROL = 0x1A; +static constexpr uint8_t MASTER_ACTIVATION = 0x20; +static constexpr uint8_t DISPLAY_UPDATE_CONTROL_1 = 0x21; +static constexpr uint8_t DISPLAY_UPDATE_CONTROL_2 = 0x22; +static constexpr uint8_t WRITE_RAM = 0x24; +static constexpr uint8_t WRITE_VCOM_REGISTER = 0x2C; +static constexpr uint8_t WRITE_LUT_REGISTER = 0x32; +static constexpr uint8_t SET_DUMMY_LINE_PERIOD = 0x3A; +static constexpr uint8_t SET_GATE_TIME = 0x3B; +static constexpr uint8_t BORDER_WAVEFORM_CONTROL = 0x3C; +static constexpr uint8_t SET_RAM_X_ADDRESS_START_END_POSITION = 0x44; +static constexpr uint8_t SET_RAM_Y_ADDRESS_START_END_POSITION = 0x45; +static constexpr uint8_t SET_RAM_X_ADDRESS_COUNTER = 0x4E; +static constexpr uint8_t SET_RAM_Y_ADDRESS_COUNTER = 0x4F; +static constexpr uint8_t TERMINATE_FRAME_READ_WRITE = 0xFF; + +EPDPanel::EPDPanel(const EPDPanelConfig& config, + SPIController* spi_ctrl, + uint8_t* framebuffer) + : spi(spi_ctrl), cfg(config), fb_buffer(framebuffer), update_mode(0), rotation(0) +{ + // Don't do automatic initialization here - let the descriptor init commands handle it + // The uDisplay framework will call send_spi_cmds() after panel creation + // which will handle reset, LUT setup, and initial display state +} + +EPDPanel::~EPDPanel() { + // Panel doesn't own framebuffer or SPI controller + + // Free owned LUT data + if (cfg.lut_full_data) { + free(cfg.lut_full_data); + } + + if (cfg.lut_partial_data) { + free(cfg.lut_partial_data); + } + + for (uint8_t i = 0; i < 5; i++) { + if (cfg.lut_array_data[i]) { + free(cfg.lut_array_data[i]); + } + } +} + +void EPDPanel::delay_sync(int32_t ms) { + uint8_t busy_level = cfg.busy_invert ? LOW : HIGH; + uint32_t time = millis(); + if (cfg.busy_pin >= 0) { + while (digitalRead(cfg.busy_pin) == busy_level) { + delay(1); + if ((millis() - time) > cfg.busy_timeout) { + break; + } + } + } else { + delay(ms); + } +} + +void EPDPanel::resetDisplay() { + if (cfg.reset_pin < 0) return; + + pinMode(cfg.reset_pin, OUTPUT); + digitalWrite(cfg.reset_pin, HIGH); + delay(10); + digitalWrite(cfg.reset_pin, LOW); + delay(10); + digitalWrite(cfg.reset_pin, HIGH); + delay(10); + delay_sync(100); // Use delay_sync instead of waitBusy +} + +void EPDPanel::waitBusy() { + // Deprecated - use delay_sync instead + delay_sync(cfg.update_time); +} + +void EPDPanel::setLut(const uint8_t* lut, uint16_t len) { + if (!lut || len == 0) return; + + spi->beginTransaction(); + spi->csLow(); + spi->writeCommand(WRITE_LUT_REGISTER); + for (uint16_t i = 0; i < len; i++) { + spi->writeData8(lut[i]); + } + spi->csHigh(); + spi->endTransaction(); +} + +void EPDPanel::setMemoryArea(int x_start, int y_start, int x_end, int y_end) { + int x_start1 = (x_start >> 3) & 0xFF; + int x_end1 = (x_end >> 3) & 0xFF; + int y_start1 = y_start & 0xFF; + int y_start2 = (y_start >> 8) & 0xFF; + int y_end1 = y_end & 0xFF; + int y_end2 = (y_end >> 8) & 0xFF; + + spi->beginTransaction(); + spi->csLow(); + spi->writeCommand(SET_RAM_X_ADDRESS_START_END_POSITION); + spi->writeData8(x_start1); + spi->writeData8(x_end1); + + spi->writeCommand(SET_RAM_Y_ADDRESS_START_END_POSITION); + if (cfg.ep_mode == 3) { + // ep_mode 3: reversed Y order + spi->writeData8(y_end1); + spi->writeData8(y_end2); + spi->writeData8(y_start1); + spi->writeData8(y_start2); + } else { + spi->writeData8(y_start1); + spi->writeData8(y_start2); + spi->writeData8(y_end1); + spi->writeData8(y_end2); + } + spi->csHigh(); + spi->endTransaction(); +} + +void EPDPanel::setMemoryPointer(int x, int y) { + int x1, y1, y2; + + if (cfg.ep_mode == 3) { + x1 = (x >> 3) & 0xFF; + y--; + y1 = y & 0xFF; + y2 = (y >> 8) & 0xFF; + } else { + x1 = (x >> 3) & 0xFF; + y1 = y & 0xFF; + y2 = (y >> 8) & 0xFF; + } + + spi->beginTransaction(); + spi->csLow(); + spi->writeCommand(SET_RAM_X_ADDRESS_COUNTER); + spi->writeData8(x1); + spi->writeCommand(SET_RAM_Y_ADDRESS_COUNTER); + spi->writeData8(y1); + spi->writeData8(y2); + spi->csHigh(); + spi->endTransaction(); +} + +void EPDPanel::clearFrameMemory(uint8_t color) { + setMemoryArea(0, 0, cfg.width - 1, cfg.height - 1); + setMemoryPointer(0, 0); + + spi->beginTransaction(); + spi->csLow(); + spi->writeCommand(WRITE_RAM); + + uint32_t pixel_count = (cfg.width * cfg.height) / 8; + for (uint32_t i = 0; i < pixel_count; i++) { + spi->writeData8(color); + } + + spi->csHigh(); + spi->endTransaction(); +} + +void EPDPanel::displayFrame() { + spi->beginTransaction(); + spi->csLow(); + spi->writeCommand(DISPLAY_UPDATE_CONTROL_2); + spi->writeData8(0xC4); + spi->writeCommand(MASTER_ACTIVATION); + spi->writeData8(TERMINATE_FRAME_READ_WRITE); + spi->csHigh(); + spi->endTransaction(); + + delay_sync(cfg.update_time); // Use delay_sync with proper timing +} + +void EPDPanel::drawAbsolutePixel(int x, int y, uint16_t color) { + // Bounds check using physical dimensions + if (x < 0 || x >= cfg.width || y < 0 || y >= cfg.height) { + return; + } + + // CRITICAL: Must match Renderer::drawPixel() layout! + // + // Two rendering systems write to the SAME framebuffer: + // 1. Renderer::drawPixel() - used by DrawStringAt() for text (Splash Screen) + // 2. EPDPanel::drawPixel() - used by Adafruit_GFX for graphics (circles, lines) + // + // Both MUST use the same framebuffer layout: Y-column-wise + // Layout: fb[x + (y/8)*width] with bit position (y&7) + // This means 8 vertical pixels are stored in one byte. + // + // setFrameMemory() will convert Y-column to X-row format when sending to hardware. + + if (color) { + fb_buffer[x + (y / 8) * cfg.width] |= (1 << (y & 7)); + } else { + fb_buffer[x + (y / 8) * cfg.width] &= ~(1 << (y & 7)); + } +} + +// ===== UniversalPanel Interface Implementation ===== + +bool EPDPanel::drawPixel(int16_t x, int16_t y, uint16_t color) { + if (!fb_buffer) return false; + + // Get rotated dimensions for bounds check + int16_t w = cfg.width, h = cfg.height; + if (rotation == 1 || rotation == 3) { + std::swap(w, h); + } + + if ((x < 0) || (x >= w) || (y < 0) || (y >= h)) { + return false; // Out of bounds + } + + // Apply rotation transformation using PHYSICAL dimensions (gxs/gys) + switch (rotation) { + case 1: + std::swap(x, y); + x = cfg.width - x - 1; // gxs + break; + case 2: + x = cfg.width - x - 1; // gxs + y = cfg.height - y - 1; // gys + break; + case 3: + std::swap(x, y); + y = cfg.height - y - 1; // gys + break; + } + + // Convert color to monochrome and draw + drawAbsolutePixel(x, y, (color != 0) ? 1 : 0); + return true; +} + +bool EPDPanel::fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) { + // Use drawPixel to handle rotation properly + for (int16_t yp = y; yp < y + h; yp++) { + for (int16_t xp = x; xp < x + w; xp++) { + drawPixel(xp, yp, color); + } + } + return true; +} + +bool EPDPanel::pushColors(uint16_t *data, uint16_t len, bool first) { + // Convert RGB565 to monochrome and write to framebuffer + // Pixel is white if at least one of the 3 RGB components is above 50% + static constexpr uint16_t RGB16_TO_MONO = 0x8410; + + if (!fb_buffer) return false; + + // Write pixels to framebuffer based on window coordinates + // IMPORTANT: window coordinates are in LOGICAL (rotated) space, + // so we must use drawPixel (not drawAbsolutePixel) to apply rotation! + for (int16_t y = window_y1; y < window_y2 && len > 0; y++) { + for (int16_t x = window_x1; x < window_x2 && len > 0; x++, len--) { + uint16_t color = *data++; + // Convert to mono: white if any component > 50% + bool pixel = (color & RGB16_TO_MONO) ? true : false; + if (cfg.invert_colors) pixel = !pixel; + drawPixel(x, y, pixel ? 1 : 0); + } + } + + return true; // Handled by EPD panel +} + +bool EPDPanel::setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) { + // Save window coordinates for pushColors + window_x1 = x0; + window_y1 = y0; + window_x2 = x1; + window_y2 = y1; + return true; +} + +bool EPDPanel::drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) { + while (w--) { + drawPixel(x, y, color); + x++; + } + return true; +} + +bool EPDPanel::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) { + while (h--) { + drawPixel(x, y, color); + y++; + } + return true; +} + +bool EPDPanel::displayOnff(int8_t on) { + // EPD doesn't have on/off in traditional sense + return true; +} + +bool EPDPanel::invertDisplay(bool invert) { + // Toggle color inversion logic + cfg.invert_colors = invert; + + // For EPD, we need to redraw the entire display when inversion changes + if (fb_buffer) { + // Invert the entire framebuffer + uint32_t byte_count = (cfg.width * cfg.height) / 8; + for (uint32_t i = 0; i < byte_count; i++) { + fb_buffer[i] = ~fb_buffer[i]; + } + updateFrame(); + } + return true; +} + +bool EPDPanel::setRotation(uint8_t rot) { + rotation = rot & 3; // Store rotation (0-3) + return true; +} + +bool EPDPanel::updateFrame() { + if (!fb_buffer) return false; + + // Handle different EPD modes + if (cfg.ep_mode == 1 || cfg.ep_mode == 3) { + // Mode 1 (2-LUT) or Mode 3 (command-based): Use descriptor command sequences + switch (update_mode) { + case 1: // DISPLAY_INIT_PARTIAL + if (cfg.epc_part_cnt && cfg.send_cmds_callback) { + cfg.send_cmds_callback(cfg.epcoffs_part, cfg.epc_part_cnt); + } + break; + case 2: // DISPLAY_INIT_FULL + if (cfg.epc_full_cnt && cfg.send_cmds_callback) { + cfg.send_cmds_callback(cfg.epcoffs_full, cfg.epc_full_cnt); + } + break; + default: // DISPLAY_INIT_MODE (0) + // Default: write framebuffer and display + setFrameMemory(fb_buffer, 0, 0, cfg.width, cfg.height); + displayFrame(); + } + } else if (cfg.ep_mode == 2) { + // Mode 2 (5-LUT / 4.2" displays): Use internal displayFrame_42 + displayFrame_42(); + } + + return true; +} + +// ===== ep_mode 2 Support (5-LUT mode) ===== + +void EPDPanel::setLuts() { + if (!cfg.lut_array || !cfg.lut_cnt) return; + + for (uint8_t index = 0; index < 5; index++) { + if (cfg.lut_cmd[index] == 0 || !cfg.lut_array[index]) continue; + + spi->beginTransaction(); + spi->csLow(); + spi->writeCommand(cfg.lut_cmd[index]); + for (uint8_t count = 0; count < cfg.lut_cnt[index]; count++) { + spi->writeData8(cfg.lut_array[index][count]); + } + spi->csHigh(); + spi->endTransaction(); + } +} + +void EPDPanel::clearFrame_42() { + spi->beginTransaction(); + spi->csLow(); + + spi->writeCommand(cfg.saw_1); + for (uint16_t j = 0; j < cfg.height; j++) { + for (uint16_t i = 0; i < cfg.width; i++) { + spi->writeData8(0xFF); + } + } + + spi->writeCommand(cfg.saw_2); + for (uint16_t j = 0; j < cfg.height; j++) { + for (uint16_t i = 0; i < cfg.width; i++) { + spi->writeData8(0xFF); + } + } + + spi->writeCommand(cfg.saw_3); + spi->csHigh(); + spi->endTransaction(); + + delay_sync(100); +} + +void EPDPanel::displayFrame_42() { + spi->beginTransaction(); + spi->csLow(); + + spi->writeCommand(cfg.saw_1); + for(int i = 0; i < cfg.width / 8 * cfg.height; i++) { + spi->writeData8(0xFF); + } + + spi->csHigh(); + spi->endTransaction(); + delay(2); + + spi->beginTransaction(); + spi->csLow(); + spi->writeCommand(cfg.saw_2); + for(int i = 0; i < cfg.width / 8 * cfg.height; i++) { + spi->writeData8(fb_buffer[i] ^ 0xff); + } + spi->csHigh(); + spi->endTransaction(); + delay(2); + + setLuts(); + + spi->beginTransaction(); + spi->csLow(); + spi->writeCommand(cfg.saw_3); + spi->csHigh(); + spi->endTransaction(); + + delay_sync(100); +} + +// ===== Frame Memory Management ===== + +// Helper: Convert Y-column framebuffer to X-row format and send via SPI +// Y-column: fb[x + (y/8)*width] with bit (y&7) - 8 vertical pixels per byte +// X-row: 8 horizontal pixels per byte, MSB = leftmost pixel +void EPDPanel::sendYColumnAsXRow(const uint8_t* y_column_buffer, uint16_t buffer_width, + uint16_t rows, uint16_t cols_bytes) { + for (uint16_t row = 0; row < rows; row++) { + for (uint16_t x_byte = 0; x_byte < cols_bytes; x_byte++) { + uint8_t byte_out = 0; + for (uint8_t bit = 0; bit < 8; bit++) { + uint16_t x = x_byte * 8 + bit; + uint8_t pixel = (y_column_buffer[x + (row / 8) * buffer_width] >> (row & 7)) & 1; + if (pixel) byte_out |= (0x80 >> bit); + } + spi->writeData8(byte_out ^ 0xff); + } + } +} + +void EPDPanel::setFrameMemory(const uint8_t* image_buffer) { + setMemoryArea(0, 0, cfg.width - 1, cfg.height - 1); + setMemoryPointer(0, 0); + + spi->beginTransaction(); + spi->csLow(); + spi->writeCommand(WRITE_RAM); + sendYColumnAsXRow(image_buffer, cfg.width, cfg.height, cfg.width / 8); + spi->csHigh(); + spi->endTransaction(); +} + +void EPDPanel::setFrameMemory(const uint8_t* image_buffer, uint16_t x, uint16_t y, uint16_t image_width, uint16_t image_height) { + if (!image_buffer) return; + + // Align to 8-pixel boundary + x &= 0xFFF8; + image_width &= 0xFFF8; + + uint16_t x_end = (x + image_width >= cfg.width) ? cfg.width - 1 : x + image_width - 1; + uint16_t y_end = (y + image_height >= cfg.height) ? cfg.height - 1 : y + image_height - 1; + + // Full screen optimization + if (!x && !y && image_width == cfg.width && image_height == cfg.height) { + setFrameMemory(image_buffer); + return; + } + + setMemoryArea(x, y, x_end, y_end); + setMemoryPointer(x, y); + + spi->beginTransaction(); + spi->csLow(); + spi->writeCommand(WRITE_RAM); + sendYColumnAsXRow(image_buffer, image_width, y_end - y + 1, (x_end - x + 1) / 8); + spi->csHigh(); + spi->endTransaction(); +} + +void EPDPanel::sendEPData() { + // EP_SEND_DATA (0x66) - used by some display.ini files (e.g. v2) + // Must also convert Y-column to X-row format like setFrameMemory() + sendYColumnAsXRow(fb_buffer, cfg.width, cfg.height, cfg.width / 8); +} + +// ===== Update Mode Control ===== + +void EPDPanel::setUpdateMode(uint8_t mode) { + update_mode = mode; +} diff --git a/lib/lib_display/UDisplay/uDisplay_EPD_panel.h b/lib/lib_display/UDisplay/uDisplay_EPD_panel.h new file mode 100644 index 000000000..9d7309d82 --- /dev/null +++ b/lib/lib_display/UDisplay/uDisplay_EPD_panel.h @@ -0,0 +1,134 @@ +// ====================================================== +// uDisplay_epd_panel.h - E-Paper Display Panel Implementation +// ====================================================== + +#pragma once + +#include +#include "uDisplay_panel.h" +#include "uDisplay_SPI_controller.h" + +/** + * Configuration for E-Paper displays + */ +struct EPDPanelConfig { + uint16_t width; + uint16_t height; + uint8_t bpp; // Always 1 for EPD + uint8_t ep_mode; // 1=2-LUT, 2=5-LUT, 3=command-based + + // Timing + int16_t lut_full_time; + uint16_t lut_partial_time; + uint16_t update_time; + + // Pins + int8_t reset_pin; + int8_t busy_pin; + + // EPD-specific flags + bool invert_colors; // If true, invert color logic + bool invert_framebuffer; // If true, invert when sending to display + bool busy_invert; // If true, busy pin is active low + + // Busy timeout + uint16_t busy_timeout = 3000; // UDSP_BUSY_TIMEOUT + + // Command bytes for ep_mode 2 (4.2" displays) + uint8_t saw_1 = 0; // First command for frame update + uint8_t saw_2 = 0; // Second command for frame update + uint8_t saw_3 = 0; // Third command for frame update + + // LUT data (for ep_mode 1 - 2-LUT mode) + const uint8_t* lut_full = nullptr; + uint16_t lut_full_len = 0; + const uint8_t* lut_partial = nullptr; + uint16_t lut_partial_len = 0; + + // LUT data (for ep_mode 2 - 5-LUT mode) + const uint8_t** lut_array = nullptr; // Array of 5 LUTs + const uint8_t* lut_cnt = nullptr; // Size of each LUT + uint8_t lut_cmd[5] = {0}; // Commands for each LUT + + // Additional LUT management (owned by EPD panel) + uint8_t* lut_full_data = nullptr; // Owned pointer to full LUT data + uint8_t* lut_partial_data = nullptr; // Owned pointer to partial LUT data + uint8_t* lut_array_data[5] = {nullptr, nullptr, nullptr, nullptr, nullptr}; // Owned pointers to LUT array data + uint16_t lutfsize = 0; // Filled size of lut_full + uint16_t lutpsize = 0; // Filled size of lut_partial + uint8_t lut_cnt_data[5] = {0}; // Filled sizes of each LUT in array + uint8_t lut_siz[5] = {0}; // Allocated sizes of each LUT in array + + // Command offsets for ep_mode 1 and 3 + uint16_t epcoffs_full = 0; // Offset to full update commands + uint16_t epcoffs_part = 0; // Offset to partial update commands + uint8_t epc_full_cnt = 0; // Count of full update commands + uint8_t epc_part_cnt = 0; // Count of partial update commands + + // Callback to send command sequences from descriptor + std::function send_cmds_callback; +}; + +class EPDPanel : public UniversalPanel { +public: + EPDPanel(const EPDPanelConfig& config, + SPIController* spi_ctrl, + uint8_t* framebuffer); // REQUIRED for EPD + + ~EPDPanel(); + + // UniversalPanel interface + bool drawPixel(int16_t x, int16_t y, uint16_t color) override; + bool fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) override; + bool pushColors(uint16_t *data, uint16_t len, bool first = false) override; + bool setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) override; + bool drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) override; + bool drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) override; + + bool displayOnff(int8_t on) override; + bool invertDisplay(bool invert) override; + bool setRotation(uint8_t rotation) override; + bool updateFrame() override; + + // EPD-specific public methods (for uDisplay wrapper compatibility) + void resetDisplay(); + void setLut(const uint8_t* lut, uint16_t len); + void setLuts(); // For ep_mode 2 (5-LUT mode) + void setMemoryArea(int x_start, int y_start, int x_end, int y_end); + void setMemoryPointer(int x, int y); + void clearFrameMemory(uint8_t color); + void displayFrame(); + void delay_sync(int32_t ms); + + // ep_mode 2 specific (4.2" displays) + void clearFrame_42(); + void displayFrame_42(); + + // Frame memory management + void setFrameMemory(const uint8_t* image_buffer); + void setFrameMemory(const uint8_t* image_buffer, uint16_t x, uint16_t y, uint16_t w, uint16_t h); + void sendEPData(); + + // Update mode control (for ep_mode 1 and 3) + void setUpdateMode(uint8_t mode); // 0=DISPLAY_INIT_MODE, 1=DISPLAY_INIT_PARTIAL, 2=DISPLAY_INIT_FULL + + EPDPanelConfig cfg; + +private: + SPIController* spi; + uint8_t* fb_buffer; // Framebuffer (always used) + uint8_t update_mode; // 0=DISPLAY_INIT_MODE, 1=DISPLAY_INIT_PARTIAL, 2=DISPLAY_INIT_FULL + uint8_t rotation; // Current rotation (0-3) + + // Address window for pushColors + int16_t window_x1 = 0; + int16_t window_y1 = 0; + int16_t window_x2 = 0; + int16_t window_y2 = 0; + + // Private helpers + void waitBusy(); + void drawAbsolutePixel(int x, int y, uint16_t color); + void sendYColumnAsXRow(const uint8_t* y_column_buffer, uint16_t buffer_width, + uint16_t rows, uint16_t cols_bytes); +}; \ No newline at end of file diff --git a/lib/lib_display/UDisplay/uDisplay_I2C_panel.cpp b/lib/lib_display/UDisplay/uDisplay_I2C_panel.cpp new file mode 100644 index 000000000..a3e3b9e9e --- /dev/null +++ b/lib/lib_display/UDisplay/uDisplay_I2C_panel.cpp @@ -0,0 +1,61 @@ +#include "uDisplay_I2C_panel.h" + +i2c_panel::i2c_panel(const I2CPanelConfig& config, uint8_t* framebuffer) + : cfg(config), framebuffer(framebuffer) { + + // Execute initialization commands + if (cfg.init_commands && cfg.init_commands_count > 0) { + for (uint16_t i = 0; i < cfg.init_commands_count; i++) { + i2c_command(cfg.init_commands[i]); + } + } +} + +bool i2c_panel::updateFrame() { + if (!framebuffer) return false; + + i2c_command(cfg.cmd_set_addr_x | 0x0); + i2c_command(cfg.page_start | 0x0); + i2c_command(cfg.page_end | 0x0); + + uint8_t ys = cfg.height >> 3; + uint8_t xs = cfg.width >> 3; + uint8_t m_row = cfg.cmd_set_addr_y; + uint8_t m_col = cfg.col_start; + + uint16_t p = 0; + uint8_t i, j, k = 0; + + for (i = 0; i < ys; i++) { + i2c_command(0xB0 + i + m_row); + i2c_command(m_col & 0xf); + i2c_command(0x10 | (m_col >> 4)); + + for (j = 0; j < 8; j++) { + cfg.wire->beginTransmission(cfg.i2c_address); + cfg.wire->write(0x40); + for (k = 0; k < xs; k++, p++) { + cfg.wire->write(framebuffer[p]); + } + cfg.wire->endTransmission(); + } + } + return true; +} + +bool i2c_panel::displayOnff(int8_t on) { + i2c_command(on ? cfg.cmd_display_on : cfg.cmd_display_off); + return true; +} + +bool i2c_panel::invertDisplay(bool invert) { + i2c_command(invert ? cfg.cmd_invert_on : cfg.cmd_invert_off); + return true; +} + +void i2c_panel::i2c_command(uint8_t val) { + cfg.wire->beginTransmission(cfg.i2c_address); + cfg.wire->write(0); + cfg.wire->write(val); + cfg.wire->endTransmission(); +} \ No newline at end of file diff --git a/lib/lib_display/UDisplay/uDisplay_I2C_panel.h b/lib/lib_display/UDisplay/uDisplay_I2C_panel.h new file mode 100644 index 000000000..4c5d94655 --- /dev/null +++ b/lib/lib_display/UDisplay/uDisplay_I2C_panel.h @@ -0,0 +1,73 @@ +#ifndef _UDISPLAY_I2C_PANEL_H_ +#define _UDISPLAY_I2C_PANEL_H_ + +#include +#include +#include "uDisplay_panel.h" + +/** + * Configuration for I2C displays (typically OLED like SSD1306) + */ +struct I2CPanelConfig { + // ===== Display Dimensions ===== + uint16_t width; + uint16_t height; + uint8_t bpp; // bits per pixel (typically 1 for OLED) + + // ===== I2C Configuration ===== + uint8_t i2c_address; // I2C device address + TwoWire* wire; // I2C bus instance + + // ===== Display Commands ===== + uint8_t cmd_set_addr_x; // Set column address command + uint8_t cmd_set_addr_y; // Set page address command + uint8_t cmd_write_ram; // Write data command + + // ===== Display Control Commands ===== + uint8_t cmd_display_on; + uint8_t cmd_display_off; + uint8_t cmd_invert_on; + uint8_t cmd_invert_off; + + // ===== Address Range ===== + uint8_t page_start; // Starting page + uint8_t page_end; // Ending page + uint8_t col_start; // Starting column + uint8_t col_end; // Ending column + + // ===== Initialization ===== + uint8_t* init_commands; + uint16_t init_commands_count; +}; + +class i2c_panel : public UniversalPanel { + +public: + /** + * Constructor - receives configuration struct and framebuffer + */ + i2c_panel(const I2CPanelConfig& config, uint8_t* framebuffer); + + bool updateFrame() override; + bool displayOnff(int8_t on) override; + bool invertDisplay(bool invert) override; + bool setRotation(uint8_t rotation) override { return true; } + + bool drawPixel(int16_t x, int16_t y, uint16_t color) override { return false; } + bool fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) override { return false; } + bool pushColors(uint16_t *data, uint16_t len, bool first = false) override { return false; } + bool setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) override { return false; } + bool drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) override { return false; } + bool drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) override { return false; } + + uint8_t* framebuffer = nullptr; + +private: + // ===== Hardware & Configuration ===== + I2CPanelConfig cfg; // Copy of config + + // ===== Internal Helpers ===== + void i2c_command(uint8_t val); +}; + +#endif // _UDISPLAY_I2C_PANEL_H_ \ No newline at end of file diff --git a/lib/lib_display/UDisplay/uDisplay_I80_panel.cpp b/lib/lib_display/UDisplay/uDisplay_I80_panel.cpp new file mode 100644 index 000000000..15d7a5ba8 --- /dev/null +++ b/lib/lib_display/UDisplay/uDisplay_I80_panel.cpp @@ -0,0 +1,733 @@ +#include "uDisplay_I80_panel.h" + +#if (SOC_LCD_I80_SUPPORTED && SOC_LCDCAM_I80_NUM_BUSES && !SOC_PARLIO_GROUPS ) + +#ifdef UDSP_DEBUG +extern void AddLog(uint32_t loglevel, const char *formatP, ...); +#define LOG_LEVEL_DEBUG 3 +#endif + +// Pin control helpers +static inline volatile uint32_t* get_gpio_hi_reg(int_fast8_t pin) { return (pin & 32) ? &GPIO.out1_w1ts.val : &GPIO.out_w1ts; } +static inline volatile uint32_t* get_gpio_lo_reg(int_fast8_t pin) { return (pin & 32) ? &GPIO.out1_w1tc.val : &GPIO.out_w1tc; } +static inline void gpio_hi(int_fast8_t pin) { if (pin >= 0) *get_gpio_hi_reg(pin) = 1 << (pin & 31); } +static inline void gpio_lo(int_fast8_t pin) { if (pin >= 0) *get_gpio_lo_reg(pin) = 1 << (pin & 31); } + +I80Panel::I80Panel(const I80PanelConfig& config) + : cfg(config), + _width(config.width), _height(config.height), _rotation(0), + _i80_bus(nullptr), _dev(nullptr), _dmadesc(nullptr), + _DMA_Enabled(false), _dma_chan(nullptr), _dmadesc_size(0), + _addr_x0(0), _addr_y0(0), _addr_x1(0), _addr_y1(0) { + + framebuffer = nullptr; + + // Initialize pins manually FIRST (matching old code order) + if (cfg.cs_pin >= 0) { + pinMode(cfg.cs_pin, OUTPUT); + digitalWrite(cfg.cs_pin, HIGH); + } + pinMode(cfg.dc_pin, OUTPUT); + digitalWrite(cfg.dc_pin, HIGH); + pinMode(cfg.wr_pin, OUTPUT); + digitalWrite(cfg.wr_pin, HIGH); + if (cfg.rd_pin >= 0) { + pinMode(cfg.rd_pin, OUTPUT); + digitalWrite(cfg.rd_pin, HIGH); + } + + for (int i = 0; i < 8; i++) { + pinMode(cfg.data_pins_low[i], OUTPUT); + } + if (cfg.bus_width == 16) { + for (int i = 0; i < 8; i++) { + pinMode(cfg.data_pins_high[i], OUTPUT); + } + } + + // Now create I80 bus config + esp_lcd_i80_bus_config_t bus_config = { + .dc_gpio_num = cfg.dc_pin, + .wr_gpio_num = cfg.wr_pin, + .clk_src = LCD_CLK_SRC_DEFAULT, + .bus_width = cfg.bus_width, + .max_transfer_bytes = (size_t)cfg.width * cfg.height * 2 + }; + + // Set data pins + if (cfg.bus_width == 8) { + for (int i = 0; i < 8; i++) { + bus_config.data_gpio_nums[i] = cfg.data_pins_low[i]; + } + } else { + for (int i = 0; i < 8; i++) { + bus_config.data_gpio_nums[i] = cfg.data_pins_low[i]; + bus_config.data_gpio_nums[i + 8] = cfg.data_pins_high[i]; + } + } + + // Create I80 bus (this will take over GPIO matrix for DC, WR, and data pins) + esp_lcd_new_i80_bus(&bus_config, &_i80_bus); + + // Calculate clock using original algorithm + uint32_t div_a, div_b, div_n, clkcnt; + calcClockDiv(&div_a, &div_b, &div_n, &clkcnt, 240*1000*1000, cfg.clock_speed_hz); + + lcd_cam_lcd_clock_reg_t lcd_clock; + lcd_clock.lcd_clkcnt_n = std::max((uint32_t)1u, clkcnt - 1); + lcd_clock.lcd_clk_equ_sysclk = (clkcnt == 1); + lcd_clock.lcd_ck_idle_edge = true; + lcd_clock.lcd_ck_out_edge = false; + lcd_clock.lcd_clkm_div_num = div_n; + lcd_clock.lcd_clkm_div_b = div_b; + lcd_clock.lcd_clkm_div_a = div_a; + lcd_clock.lcd_clk_sel = 2; // 240MHz + lcd_clock.clk_en = true; + _clock_reg_value = lcd_clock.val; + + _alloc_dmadesc(1); + _dev = &LCD_CAM; + + // EXECUTE INITIALIZATION COMMANDS (from original uDisplay code) + if (cfg.init_commands && cfg.init_commands_count > 0) { + uint16_t index = 0; + pb_beginTransaction(); + + while (index < cfg.init_commands_count) { + cs_control(false); + + uint8_t cmd = cfg.init_commands[index++]; + pb_writeCommand(cmd, 8); + + if (index < cfg.init_commands_count) { + uint8_t args = cfg.init_commands[index++]; + uint8_t arg_count = args & 0x1f; + +#ifdef UDSP_DEBUG + AddLog(LOG_LEVEL_DEBUG, "UDisplay: cmd, args %02x, %d", cmd, arg_count); +#endif + + for (uint32_t cnt = 0; cnt < arg_count && index < cfg.init_commands_count; cnt++) { + uint8_t arg_data = cfg.init_commands[index++]; +#ifdef UDSP_DEBUG + AddLog(LOG_LEVEL_DEBUG, "%02x ", arg_data); +#endif + pb_writeData(arg_data, 8); + } + + cs_control(true); + + // Handle delay after command + if (args & 0x80) { + uint32_t delay_ms = 0; + switch (args & 0xE0) { + case 0x80: delay_ms = 150; break; + case 0xA0: delay_ms = 10; break; + case 0xE0: delay_ms = 500; break; + } + if (delay_ms > 0) { +#ifdef UDSP_DEBUG + AddLog(LOG_LEVEL_DEBUG, "UDisplay: delay %d ms", delay_ms); +#endif + delay(delay_ms); + } + } + } else { + cs_control(true); + } + } + + pb_endTransaction(); + } +} + +I80Panel::~I80Panel() { + deInitDMA(); + if (_dmadesc) { + heap_caps_free(_dmadesc); + _dmadesc = nullptr; + } + if (_i80_bus) { + esp_lcd_del_i80_bus(_i80_bus); + } +} + +// DMA Implementation +bool I80Panel::initDMA() { + if (_DMA_Enabled) return true; + + gdma_channel_alloc_config_t dma_chan_config = { + .direction = GDMA_CHANNEL_DIRECTION_TX + }; + + if (gdma_new_channel(&dma_chan_config, &_dma_chan) == ESP_OK) { + gdma_connect(_dma_chan, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_LCD, 0)); + _alloc_dmadesc(16); + _DMA_Enabled = true; + return true; + } + + return false; +} + +void I80Panel::deInitDMA() { + if (_dma_chan) { + gdma_disconnect(_dma_chan); + gdma_del_channel(_dma_chan); + _dma_chan = nullptr; + } + _DMA_Enabled = false; +} + +bool I80Panel::dmaBusy() { + if (!_DMA_Enabled) return false; + return (_dev->lcd_user.val & LCD_CAM_LCD_START); +} + +void I80Panel::dmaWait() { + if (!_DMA_Enabled) return; + while (dmaBusy()) { + delay(1); + } +} + +// Graphics implementation +bool I80Panel::drawPixel(int16_t x, int16_t y, uint16_t color) { + if ((x < 0) || (x >= _width) || (y < 0) || (y >= _height)) return true; + + pb_beginTransaction(); + cs_control(false); + setAddrWindow_int(x, y, 1, 1); + writeColor(color); + cs_control(true); + pb_endTransaction(); + return true; +} + +bool I80Panel::fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) { + if((x >= _width) || (y >= _height)) return true; + if((x + w - 1) >= _width) w = _width - x; + if((y + h - 1) >= _height) h = _height - y; + + pb_beginTransaction(); + cs_control(false); + setAddrWindow_int(x, y, w, h); + + for (int16_t yp = h; yp > 0; yp--) { + for (int16_t xp = w; xp > 0; xp--) { + writeColor(color); + } + } + + cs_control(true); + pb_endTransaction(); + return true; +} + +bool I80Panel::drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) { + if((x >= _width) || (y >= _height)) return true; + if((x + w - 1) >= _width) w = _width - x; + + pb_beginTransaction(); + cs_control(false); + setAddrWindow_int(x, y, w, 1); + + while (w--) { + writeColor(color); + } + + cs_control(true); + pb_endTransaction(); + return true; +} + +bool I80Panel::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) { + if ((x >= _width) || (y >= _height)) return true; + if ((y + h - 1) >= _height) h = _height - y; + + pb_beginTransaction(); + cs_control(false); + setAddrWindow_int(x, y, 1, h); + + while (h--) { + writeColor(color); + } + + cs_control(true); + pb_endTransaction(); + return true; +} + +bool I80Panel::pushColors(uint16_t *data, uint16_t len, bool first) { + // Match old code: just push pixels, no transaction management + // Transaction is managed by setAddrWindow() + pb_pushPixels(data, len, true, false); // swap_bytes=true to match old driver + return true; +} + +bool I80Panel::setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) { + // Match old code behavior exactly + if (!x0 && !y0 && !x1 && !y1) { + // End transaction signal + cs_control(true); + pb_endTransaction(); + } else { + // Begin transaction and send address window commands + pb_beginTransaction(); + cs_control(false); + setAddrWindow_int(x0, y0, x1 - x0, y1 - y0); + // Leave transaction open for pushColors + } + return true; +} + +bool I80Panel::displayOnff(int8_t on) { + return false; // Let uDisplay handle display commands +} + +bool I80Panel::invertDisplay(bool invert) { + return false; // Let uDisplay handle inversion commands +} + +bool I80Panel::setRotation(uint8_t rotation) { + _rotation = rotation & 3; + + // Calculate new dimensions based on rotation FIRST + uint16_t new_width, new_height; + switch (_rotation) { + case 0: + case 2: + new_width = cfg.width; + new_height = cfg.height; + break; + case 1: + case 3: + new_width = cfg.height; + new_height = cfg.width; + break; + } + + // Send MADCTL rotation command to display + pb_beginTransaction(); + cs_control(false); + + // Send rotation command (matches old code behavior) + pb_writeCommand(cfg.cmd_madctl, 8); + if (!cfg.allcmd_mode) { + pb_writeData(cfg.rot_cmd[_rotation], 8); + } else { + pb_writeCommand(cfg.rot_cmd[_rotation], 8); + } + + // For sa_mode == 8, also send startline command + if (cfg.sa_mode == 8 && !cfg.allcmd_mode) { + pb_writeCommand(cfg.cmd_startline, 8); + pb_writeData((_rotation < 2) ? new_height : 0, 8); + } + + cs_control(true); + pb_endTransaction(); + + // Update dimensions + _width = new_width; + _height = new_height; + + return true; +} + +bool I80Panel::updateFrame() { + return true; // I80 updates are immediate +} + +uint32_t I80Panel::getSimpleResistiveTouch(uint32_t threshold) { + uint32_t aval = 0; + uint16_t xp, yp; + if (pb_busy()) return 0; + + // Disable GPIO matrix routing to use pins as GPIOs + _pb_init_pin(true); + + // Temporarily reconfigure I80 pins as GPIOs for analog touch + gpio_matrix_out(cfg.dc_pin, 0x100, 0, 0); + + pinMode(cfg.data_pins_low[0], INPUT_PULLUP); + pinMode(cfg.dc_pin, INPUT_PULLUP); + + pinMode(cfg.cs_pin, OUTPUT); + pinMode(cfg.data_pins_low[1], OUTPUT); + digitalWrite(cfg.cs_pin, HIGH); + digitalWrite(cfg.data_pins_low[1], LOW); + + xp = 4096 - analogRead(cfg.data_pins_low[0]); + + pinMode(cfg.cs_pin, INPUT_PULLUP); + pinMode(cfg.data_pins_low[1], INPUT_PULLUP); + + pinMode(cfg.data_pins_low[0], OUTPUT); + pinMode(cfg.dc_pin, OUTPUT); + digitalWrite(cfg.data_pins_low[0], HIGH); + digitalWrite(cfg.dc_pin, LOW); + + yp = 4096 - analogRead(cfg.data_pins_low[1]); + + aval = (xp << 16) | yp; + + // Restore pins to I80 function + pinMode(cfg.dc_pin, OUTPUT); + pinMode(cfg.cs_pin, OUTPUT); + pinMode(cfg.data_pins_low[0], OUTPUT); + pinMode(cfg.data_pins_low[1], OUTPUT); + digitalWrite(cfg.dc_pin, HIGH); + digitalWrite(cfg.cs_pin, HIGH); + + // Re-enable GPIO matrix routing for I80 + _pb_init_pin(false); + gpio_matrix_out(cfg.dc_pin, LCD_DC_IDX, 0, 0); + + return aval; +} + +// Color mode helper +void I80Panel::writeColor(uint16_t color) { + if (cfg.color_mode == 18) { + uint8_t r = (color & 0xF800) >> 11; + uint8_t g = (color & 0x07E0) >> 5; + uint8_t b = color & 0x001F; + r = (r * 255) / 31; + g = (g * 255) / 63; + b = (b * 255) / 31; + + pb_writeData(r, 8); + pb_writeData(g, 8); + pb_writeData(b, 8); + } else { + pb_writeData(color, 16); + } +} + +void I80Panel::setAddrWindow_int(uint16_t x, uint16_t y, uint16_t w, uint16_t h) { + // Apply rotation-specific offsets (matches old code logic) + x += cfg.x_addr_offset[_rotation]; + y += cfg.y_addr_offset[_rotation]; + + uint16_t x2 = x + w - 1; + uint16_t y2 = y + h - 1; + + if (cfg.sa_mode != 8) { + // Normal mode: send 32-bit packed coordinates + uint32_t xa = ((uint32_t)x << 16) | x2; + uint32_t ya = ((uint32_t)y << 16) | y2; + + pb_writeCommand(cfg.cmd_set_addr_x, 8); + pb_writeData(xa, 32); + pb_writeCommand(cfg.cmd_set_addr_y, 8); + pb_writeData(ya, 32); + + if (cfg.cmd_write_ram != 0xff) { + pb_writeCommand(cfg.cmd_write_ram, 8); + } + } else { + // Special mode 8: swap coordinates if rotation is odd + if (_rotation & 1) { + uint16_t tmp; + tmp = x; x = y; y = tmp; + tmp = x2; x2 = y2; y2 = tmp; + } + + pb_writeCommand(cfg.cmd_set_addr_x, 8); + if (cfg.allcmd_mode) { + pb_writeCommand(x, 8); + pb_writeCommand(x2, 8); + } else { + pb_writeData(x, 8); + pb_writeData(x2, 8); + } + + pb_writeCommand(cfg.cmd_set_addr_y, 8); + if (cfg.allcmd_mode) { + pb_writeCommand(y, 8); + pb_writeCommand(y2, 8); + } else { + pb_writeData(y, 8); + pb_writeData(y2, 8); + } + + if (cfg.cmd_write_ram != 0xff) { + pb_writeCommand(cfg.cmd_write_ram, 8); + } + } + + // Store for push operations + _addr_x0 = x; + _addr_y0 = y; + _addr_x1 = x2; + _addr_y1 = y2; +} + +// Low-level I80 implementation +void I80Panel::calcClockDiv(uint32_t* div_a, uint32_t* div_b, uint32_t* div_n, uint32_t* clkcnt, uint32_t baseClock, uint32_t targetFreq) { + uint32_t diff = INT32_MAX; + *div_n = 256; + *div_a = 63; + *div_b = 62; + *clkcnt = 64; + + uint32_t start_cnt = std::min(64u, (baseClock / (targetFreq * 2) + 1)); + uint32_t end_cnt = std::max(2u, baseClock / 256u / targetFreq); + if (start_cnt <= 2) { end_cnt = 1; } + + for (uint32_t cnt = start_cnt; diff && cnt >= end_cnt; --cnt) { + float fdiv = (float)baseClock / cnt / targetFreq; + uint32_t n = std::max(2u, (uint32_t)fdiv); + fdiv -= n; + + for (uint32_t a = 63; diff && a > 0; --a) { + uint32_t b = roundf(fdiv * a); + if (a == b && n == 256) break; + + uint32_t freq = baseClock / ((n * cnt) + (float)(b * cnt) / (float)a); + uint32_t d = abs((int)targetFreq - (int)freq); + if (diff <= d) continue; + + diff = d; + *clkcnt = cnt; + *div_n = n; + *div_b = b; + *div_a = a; + if (b == 0 || a == b) break; + } + } + + if (*div_a == *div_b) { + *div_b = 0; + *div_n += 1; + } +} + +void I80Panel::_alloc_dmadesc(size_t len) { + if (_dmadesc) heap_caps_free(_dmadesc); + _dmadesc_size = len; + _dmadesc = (lldesc_t*)heap_caps_malloc(sizeof(lldesc_t) * len, MALLOC_CAP_DMA); +} + +void I80Panel::_setup_dma_desc_links(const uint8_t *data, int32_t len) { + // ORIGINAL CODE: This function was empty in the original implementation + // DMA descriptor setup is incomplete - transfers larger than pre-allocated + // descriptor count will be silently truncated, causing corrupted data + // This matches the original uDisplay behavior but should be fixed eventually + + static constexpr size_t MAX_DMA_LEN = (4096-4); + // TODO: Implement proper DMA descriptor chain setup + // Currently, if len > MAX_DMA_LEN * _dmadesc_size, data will be truncated + // without any error detection or recovery +} + +void I80Panel::pb_beginTransaction(void) { + auto dev = _dev; + dev->lcd_clock.val = _clock_reg_value; + dev->lcd_misc.val = LCD_CAM_LCD_CD_IDLE_EDGE; + dev->lcd_user.val = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M; +} + +void I80Panel::pb_endTransaction(void) { + auto dev = _dev; + while (dev->lcd_user.val & LCD_CAM_LCD_START) {} +} + +void I80Panel::pb_wait(void) { + auto dev = _dev; + while (dev->lcd_user.val & LCD_CAM_LCD_START) {} +} + +bool I80Panel::pb_busy(void) { + auto dev = _dev; + return (dev->lcd_user.val & LCD_CAM_LCD_START); +} + +void I80Panel::_pb_init_pin(bool read) { + if (read) { + if (cfg.bus_width == 8) { + for (size_t i = 0; i < 8; ++i) { + gpio_ll_output_disable(&GPIO, (gpio_num_t)cfg.data_pins_low[i]); + } + } else { + for (size_t i = 0; i < 8; ++i) { + gpio_ll_output_disable(&GPIO, (gpio_num_t)cfg.data_pins_low[i]); + } + for (size_t i = 0; i < 8; ++i) { + gpio_ll_output_disable(&GPIO, (gpio_num_t)cfg.data_pins_high[i]); + } + } + } else { + auto idx_base = LCD_DATA_OUT0_IDX; + if (cfg.bus_width == 8) { + for (size_t i = 0; i < 8; ++i) { + gpio_matrix_out(cfg.data_pins_low[i], idx_base + i, 0, 0); + } + } else { + for (size_t i = 0; i < 8; ++i) { + gpio_matrix_out(cfg.data_pins_low[i], idx_base + i, 0, 0); + } + for (size_t i = 0; i < 8; ++i) { + gpio_matrix_out(cfg.data_pins_high[i], idx_base + 8 + i, 0, 0); + } + } + } +} + +bool I80Panel::pb_writeCommand(uint32_t data, uint_fast8_t bit_length) { + auto dev = _dev; + auto reg_lcd_user = &(dev->lcd_user.val); + dev->lcd_misc.val = LCD_CAM_LCD_CD_IDLE_EDGE | LCD_CAM_LCD_CD_CMD_SET; + + if (cfg.bus_width == 8) { + auto bytes = bit_length >> 3; + do { + dev->lcd_cmd_val.lcd_cmd_value = data; + data >>= 8; + while (*reg_lcd_user & LCD_CAM_LCD_START) {} + *reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START; + } while (--bytes); + return true; + } else { + dev->lcd_cmd_val.val = data; + while (*reg_lcd_user & LCD_CAM_LCD_START) {} + *reg_lcd_user = LCD_CAM_LCD_2BYTE_EN | LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START; + return true; + } +} + +void I80Panel::pb_writeData(uint32_t data, uint_fast8_t bit_length) { + auto dev = _dev; + auto reg_lcd_user = &(dev->lcd_user.val); + dev->lcd_misc.val = LCD_CAM_LCD_CD_IDLE_EDGE; + auto bytes = bit_length >> 3; + + if (cfg.bus_width == 8) { + uint8_t shift = (bytes - 1) * 8; + for (uint32_t cnt = 0; cnt < bytes; cnt++) { + dev->lcd_cmd_val.lcd_cmd_value = (data >> shift) & 0xff; + shift -= 8; + while (*reg_lcd_user & LCD_CAM_LCD_START) {} + *reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START; + } + return; + } else { + if (bytes == 1 || bytes == 4) { + uint8_t shift = (bytes - 1) * 8; + for (uint32_t cnt = 0; cnt < bytes; cnt++) { + dev->lcd_cmd_val.lcd_cmd_value = (data >> shift) & 0xff; + shift -= 8; + while (*reg_lcd_user & LCD_CAM_LCD_START) {} + *reg_lcd_user = LCD_CAM_LCD_2BYTE_EN | LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START; + } + return; + } + + dev->lcd_cmd_val.val = data; + while (*reg_lcd_user & LCD_CAM_LCD_START) {} + *reg_lcd_user = LCD_CAM_LCD_2BYTE_EN | LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START; + return; + } +} + +void I80Panel::pb_writeBytes(const uint8_t* data, uint32_t length, bool use_dma) { + // original code commented out + /* + uint32_t freq = spi_speed * 1000000; + uint32_t slow = (freq< 4000000) ? 2 : (freq < 8000000) ? 1 : 0; + + auto dev = _dev; + do { + auto reg_lcd_user = &(dev->lcd_user.val); + dev->lcd_misc.lcd_cd_cmd_set = 0; + dev->lcd_cmd_val.lcd_cmd_value = data[0] | data[1] << 16; + uint32_t cmd_val = data[2] | data[3] << 16; + while (*reg_lcd_user & LCD_CAM_LCD_START) {} + *reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_CMD_2_CYCLE_EN | LCD_CAM_LCD_UPDATE_REG | LCD_CAM_LCD_START; + + if (use_dma) { + if (slow) { ets_delay_us(slow); } + _setup_dma_desc_links(&data[4], length - 4); + gdma_start(_dma_chan, (intptr_t)(_dmadesc)); + length = 0; + } else { + size_t len = length; + if (len > CACHE_SIZE) { + len = (((len - 1) % CACHE_SIZE) + 4) & ~3u; + } + memcpy(_cache_flip, &data[4], (len-4+3)&~3); + _setup_dma_desc_links((const uint8_t*)_cache_flip, len-4); + gdma_start(_dma_chan, (intptr_t)(_dmadesc)); + length -= len; + data += len; + _cache_flip = _cache[(_cache_flip == _cache[0])]; + } + dev->lcd_cmd_val.lcd_cmd_value = cmd_val; + dev->lcd_misc.lcd_cd_data_set = 0; + *reg_lcd_user = LCD_CAM_LCD_ALWAYS_OUT_EN | LCD_CAM_LCD_DOUT | LCD_CAM_LCD_CMD | LCD_CAM_LCD_CMD_2_CYCLE_EN | LCD_CAM_LCD_UPDATE_REG; + while (*reg_lcd_user & LCD_CAM_LCD_START) {} + *reg_lcd_user = LCD_CAM_LCD_ALWAYS_OUT_EN | LCD_CAM_LCD_DOUT | LCD_CAM_LCD_CMD | LCD_CAM_LCD_CMD_2_CYCLE_EN | LCD_CAM_LCD_START; + } while (length); +*/ +} + +// FIXED: Byte swap logic was backwards in 8-bit mode +void I80Panel::pb_pushPixels(uint16_t* data, uint32_t length, bool swap_bytes, bool use_dma) { + auto dev = _dev; + auto reg_lcd_user = &(dev->lcd_user.val); + dev->lcd_misc.val = LCD_CAM_LCD_CD_IDLE_EDGE; + + if (cfg.bus_width == 8) { + if (swap_bytes) { + for (uint32_t cnt = 0; cnt < length; cnt++) { + dev->lcd_cmd_val.lcd_cmd_value = *data >> 8; // High byte first + while (*reg_lcd_user & LCD_CAM_LCD_START) {} + *reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START; + dev->lcd_cmd_val.lcd_cmd_value = *data; // Low byte second + while (*reg_lcd_user & LCD_CAM_LCD_START) {} + *reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START; + data++; + } + } else { + for (uint32_t cnt = 0; cnt < length; cnt++) { + dev->lcd_cmd_val.lcd_cmd_value = *data; // Low byte first + while (*reg_lcd_user & LCD_CAM_LCD_START) {} + *reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START; + dev->lcd_cmd_val.lcd_cmd_value = *data >> 8; // High byte second + while (*reg_lcd_user & LCD_CAM_LCD_START) {} + *reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START; + data++; + } + } + } else { + if (swap_bytes) { + uint16_t iob; + for (uint32_t cnt = 0; cnt < length; cnt++) { + iob = *data++; + iob = (iob << 8) | (iob >> 8); + dev->lcd_cmd_val.lcd_cmd_value = iob; + while (*reg_lcd_user & LCD_CAM_LCD_START) {} + *reg_lcd_user = LCD_CAM_LCD_2BYTE_EN | LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START; + } + } else { + for (uint32_t cnt = 0; cnt < length; cnt++) { + dev->lcd_cmd_val.lcd_cmd_value = *data++; + while (*reg_lcd_user & LCD_CAM_LCD_START) {} + *reg_lcd_user = LCD_CAM_LCD_2BYTE_EN | LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START; + } + } + } +} + +void I80Panel::cs_control(bool level) { + auto pin = cfg.cs_pin; + if (pin < 0) return; + if (level) { + gpio_hi(pin); + } else { + gpio_lo(pin); + } +} + +#endif // SOC_LCD_I80_SUPPORTED && SOC_LCDCAM_I80_NUM_BUSES \ No newline at end of file diff --git a/lib/lib_display/UDisplay/uDisplay_I80_panel.h b/lib/lib_display/UDisplay/uDisplay_I80_panel.h new file mode 100644 index 000000000..6ba7629ba --- /dev/null +++ b/lib/lib_display/UDisplay/uDisplay_I80_panel.h @@ -0,0 +1,144 @@ +#pragma once + +#ifdef ESP32 +#if __has_include("soc/soc_caps.h") +# include "soc/soc_caps.h" +#else +# error "No ESP capability header found" +#endif + +#if (SOC_LCD_I80_SUPPORTED && SOC_LCDCAM_I80_NUM_BUSES && !SOC_PARLIO_GROUPS) + +#include "uDisplay_panel.h" + +#include "esp_private/gdma.h" +#include +#include +#include +#include +#include "esp_rom_lldesc.h" +#include "esp_lcd_io_i80.h" +#include "esp_private/gdma.h" +#include +#include +#include +#include +#include "esp_pm.h" +#include +#include +#include "esp_rom_lldesc.h" +#include + +/** + * Configuration for I80 (8080/6800) parallel displays + */ +struct I80PanelConfig { + // ===== Display Dimensions ===== + uint16_t width; + uint16_t height; + uint8_t bpp; // bits per pixel (typically 16) + uint8_t color_mode; // color mode (16, 18, etc.) + + // ===== Bus Configuration ===== + int8_t cs_pin; // Chip select + int8_t dc_pin; // Data/Command + int8_t wr_pin; // Write strobe + int8_t rd_pin; // Read strobe (-1 if not used) + int8_t data_pins_low[8]; // D0-D7 pins + int8_t data_pins_high[8]; // D8-D15 pins (for 16-bit bus) + uint8_t bus_width; // 8 or 16 + uint32_t clock_speed_hz; // Bus clock speed + + // ===== Display Commands ===== + uint8_t cmd_set_addr_x; // Column address command + uint8_t cmd_set_addr_y; // Row/page address command + uint8_t cmd_write_ram; // Write to RAM command + uint8_t cmd_madctl; // Memory access control command (typically 0x36) + uint8_t cmd_startline; // Start line command (for sa_mode == 8) + + // ===== Display Modes ===== + uint8_t sa_mode; // Set address mode (8 = special, 16 = normal) + uint8_t allcmd_mode; // If true, send data as commands + + // ===== Per-Rotation Configuration ===== + uint8_t rot_cmd[4]; // MADCTL rotation command value per rotation + uint16_t x_addr_offset[4]; // Address offset per rotation + uint16_t y_addr_offset[4]; + + // ===== Initialization ===== + uint8_t* init_commands; + uint16_t init_commands_count; +}; + +class I80Panel : public UniversalPanel { +public: + /** + * Constructor - receives configuration struct + */ + I80Panel(const I80PanelConfig& config); + virtual ~I80Panel(); + + // UniversalPanel interface + bool drawPixel(int16_t x, int16_t y, uint16_t color) override; + bool fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) override; + bool pushColors(uint16_t *data, uint16_t len, bool first = false) override; + bool setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) override; + bool drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) override; + bool drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) override; + bool displayOnff(int8_t on) override; + bool invertDisplay(bool invert) override; + bool setRotation(uint8_t rotation) override; + bool updateFrame() override; + + // Simple resistive touch hack + uint32_t getSimpleResistiveTouch(uint32_t threshold); + + // DMA functionality + bool initDMA(); + void deInitDMA(); + bool dmaBusy(); + void dmaWait(); + +private: + // ===== Hardware & Configuration ===== + I80PanelConfig cfg; // Copy of config + + // ===== Display State ===== + int16_t _width, _height; + uint8_t _rotation; + + // I80 hardware handles + esp_lcd_i80_bus_handle_t _i80_bus; + volatile lcd_cam_dev_t* _dev; + uint32_t _clock_reg_value; + + // DMA resources + bool _DMA_Enabled; + gdma_channel_handle_t _dma_chan; + lldesc_t *_dmadesc; + uint32_t _dmadesc_size; + + // Current address window + int16_t _addr_x0, _addr_y0, _addr_x1, _addr_y1; + + // Low-level I80 functions + void calcClockDiv(uint32_t* div_a, uint32_t* div_b, uint32_t* div_n, uint32_t* clkcnt, uint32_t baseClock, uint32_t targetFreq); + void _alloc_dmadesc(size_t len); + void _setup_dma_desc_links(const uint8_t *data, int32_t len); + void pb_beginTransaction(void); + void pb_endTransaction(void); + void pb_wait(void); + bool pb_busy(void); + void _pb_init_pin(bool read); + bool pb_writeCommand(uint32_t data, uint_fast8_t bit_length); + void pb_writeData(uint32_t data, uint_fast8_t bit_length); + void pb_writeBytes(const uint8_t* data, uint32_t length, bool use_dma); + void pb_pushPixels(uint16_t* data, uint32_t length, bool swap_bytes, bool use_dma); + void cs_control(bool level); + + // Color mode helpers + void writeColor(uint16_t color); + void setAddrWindow_int(uint16_t x, uint16_t y, uint16_t w, uint16_t h); +}; +#endif // SOC_LCD_I80_SUPPORTED && SOC_LCDCAM_I80_NUM_BUSES +#endif // ESP32 \ No newline at end of file diff --git a/lib/lib_display/UDisplay/uDisplay_RGB_panel.cpp b/lib/lib_display/UDisplay/uDisplay_RGB_panel.cpp new file mode 100644 index 000000000..9848bfe71 --- /dev/null +++ b/lib/lib_display/UDisplay/uDisplay_RGB_panel.cpp @@ -0,0 +1,118 @@ +// ====================================================== +// panel/uDisplay_rgb_panel.cpp - RGB Panel Implementation +// ====================================================== +#include "uDisplay_RGB_panel.h" + +#if SOC_LCD_RGB_SUPPORTED + +#include +#include +#include + +extern int CACHE_WRITEBACK_ADDR(uint32_t addr, uint32_t size); + +RGBPanel::RGBPanel(const esp_lcd_rgb_panel_config_t *config) { + ESP_ERROR_CHECK(esp_lcd_new_rgb_panel(config, &panel_handle)); + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle)); + ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle)); + width = config->timings.h_res; + height = config->timings.v_res; + framebuffer_size = width * height * 2; // 16 bpp + void* buf = NULL; + esp_lcd_rgb_panel_get_frame_buffer(panel_handle, 1, &buf); + framebuffer = (uint16_t*)buf; + uint16_t color = random(0xffff); + ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, 1, 1, &color)); +} + +RGBPanel::~RGBPanel() { + // TODO: Cleanup panel_handle if needed +} + +bool RGBPanel::drawPixel(int16_t x, int16_t y, uint16_t color) { + int16_t w = width, h = height; + + // Apply rotation + 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; + } + + if ((x < 0) || (x >= w) || (y < 0) || (y >= h)) return true; // Handled (out of bounds) + + framebuffer[y * w + x] = color; + framebuffer_dirty = true; + return true; // Handled by RGB panel +} + +bool RGBPanel::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 * width + x]; + for (int16_t i = 0; i < w; i++) { + line_start[i] = color; + } + } + framebuffer_dirty = true; + return true; // Handled by RGB panel +} + +bool RGBPanel::setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) { + window_x1 = x0; + window_y1 = y0; + window_x2 = x1; + window_y2 = y1; + return true; // Handled by RGB panel +} + +bool RGBPanel::pushColors(uint16_t *data, uint16_t len, bool first) { + esp_lcd_panel_draw_bitmap(panel_handle, window_x1, window_y1, window_x2, window_y2, data); + return true; // Handled by RGB panel +} + +bool RGBPanel::drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) { + uint16_t* line_start = &framebuffer[y * width + x]; + for (int16_t i = 0; i < w; i++) { + line_start[i] = color; + } + CACHE_WRITEBACK_ADDR((uint32_t)line_start, w * 2); + return true; // Handled by RGB panel +} + +bool RGBPanel::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) { + for (int16_t j = 0; j < h; j++) { + framebuffer[(y + j) * width + x] = color; + } + CACHE_WRITEBACK_ADDR((uint32_t)&framebuffer[y * width + x], h * 2); + return true; // Handled by RGB panel +} + +bool RGBPanel::displayOnff(int8_t on) { + esp_lcd_panel_disp_on_off(panel_handle, on != 0); + return false; // bpanel is controlled from display class +} + +bool RGBPanel::invertDisplay(bool invert) { + // TODO: Not supported by RGB panels in ESP-IDF API + return false; // Not handled - let uDisplay handle if possible +} + +bool RGBPanel::setRotation(uint8_t rotation) { + this->rotation = rotation & 3; + esp_lcd_panel_mirror(panel_handle, rotation == 1 || rotation == 2, rotation & 2); + esp_lcd_panel_swap_xy(panel_handle, rotation & 1); + return true; // Handled by RGB panel +} + +bool RGBPanel::updateFrame() { + if (!framebuffer_dirty) { + return true; + } + CACHE_WRITEBACK_ADDR((uint32_t)framebuffer, framebuffer_size); //KISS and fast enough! + framebuffer_dirty = false; + + return true; // Handled (no-op is still handled) +} + + +#endif // #if SOC_LCD_RGB_SUPPORTED diff --git a/lib/lib_display/UDisplay/uDisplay_RGB_panel.h b/lib/lib_display/UDisplay/uDisplay_RGB_panel.h new file mode 100644 index 000000000..635c46a07 --- /dev/null +++ b/lib/lib_display/UDisplay/uDisplay_RGB_panel.h @@ -0,0 +1,55 @@ +// ====================================================== +// uDisplay_rgb_panel.h - RGB Panel Implementation +// ====================================================== + +#pragma once +#ifdef ESP32 +#if __has_include("soc/soc_caps.h") +# include "soc/soc_caps.h" +#else +# error "No ESP capability header found" +#endif +#endif + +#if SOC_LCD_RGB_SUPPORTED + +#include "uDisplay_panel.h" +#include "esp_lcd_panel_interface.h" +#include "esp_lcd_panel_rgb.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_panel_ops.h" + +class RGBPanel : public UniversalPanel { +public: + // Takes only the ESP-IDF config + RGBPanel(const esp_lcd_rgb_panel_config_t *config); + ~RGBPanel(); + + bool drawPixel(int16_t x, int16_t y, uint16_t color) override; + bool fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) override; + bool pushColors(uint16_t *data, uint16_t len, bool first = false) override; + bool setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) override; + bool drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) override; + bool drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) override; + + bool displayOnff(int8_t on) override; + bool invertDisplay(bool invert) override; + bool setRotation(uint8_t rotation) override; + bool updateFrame() override; + uint16_t* framebuffer = nullptr; + +private: + esp_lcd_panel_handle_t panel_handle = nullptr; + uint8_t rotation = 0; + uint16_t width = 0; + uint16_t height = 0; + int16_t window_x1 = 0; + int16_t window_y1 = 0; + int16_t window_x2 = 1; + int16_t window_y2 = 1; + size_t framebuffer_size = 0; + uint32_t framebuffer_dirty = false; + +}; + +#endif //SOC_LCD_RGB_SUPPORTED \ No newline at end of file diff --git a/lib/lib_display/UDisplay/uDisplay_SPI_controller.cpp b/lib/lib_display/UDisplay/uDisplay_SPI_controller.cpp new file mode 100644 index 000000000..8c4e12e29 --- /dev/null +++ b/lib/lib_display/UDisplay/uDisplay_SPI_controller.cpp @@ -0,0 +1,486 @@ +#include "uDisplay_SPI_controller.h" + +// ===== GPIO Macros ===== +#ifdef ESP8266 +#define PIN_OUT_SET 0x60000304 +#define PIN_OUT_CLEAR 0x60000308 +#define GPIO_SET(A) WRITE_PERI_REG(PIN_OUT_SET, 1 << A) +#define GPIO_CLR(A) WRITE_PERI_REG(PIN_OUT_CLEAR, 1 << A) +#define GPIO_SET_SLOW(A) digitalWrite(A, HIGH) +#define GPIO_CLR_SLOW(A) digitalWrite(A, LOW) +#else // ESP32 +#if CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32P4 +#define GPIO_CLR(A) GPIO.out_w1tc.val = (1 << A) +#define GPIO_SET(A) GPIO.out_w1ts.val = (1 << A) +#else // plain ESP32 or S3 +#define GPIO_CLR(A) GPIO.out_w1tc = (1 << A) +#define GPIO_SET(A) GPIO.out_w1ts = (1 << A) +#endif +#define GPIO_SET_SLOW(A) digitalWrite(A, HIGH) +#define GPIO_CLR_SLOW(A) digitalWrite(A, LOW) +#endif + +// ===== RA8876 Constants ===== +static constexpr uint8_t RA8876_DATA_WRITE = 0x80; +static constexpr uint8_t RA8876_DATA_READ = 0xC0; +static constexpr uint8_t RA8876_CMD_WRITE = 0x00; +static constexpr uint8_t RA8876_STATUS_READ = 0x40; + +extern void AddLog(uint32_t loglevel, const char* formatP, ...); + +SPIController::SPIController(const SPIControllerConfig& config) + : spi_config(config) +{ + if (spi_config.dc >= 0) { + pinMode(spi_config.dc, OUTPUT); + digitalWrite(spi_config.dc, HIGH); + } + if (spi_config.cs >= 0) { + pinMode(spi_config.cs, OUTPUT); + digitalWrite(spi_config.cs, HIGH); + } + +#ifdef ESP8266 + if (spi_config.bus_nr <= 1) { + SPI.begin(); + spi = &SPI; + } else { + pinMode(spi_config.clk, OUTPUT); + digitalWrite(spi_config.clk, LOW); + pinMode(spi_config.mosi, OUTPUT); + digitalWrite(spi_config.mosi, LOW); + if (spi_config.miso >= 0) { + pinMode(spi_config.miso, INPUT_PULLUP); + } + } +#endif // ESP8266 + +#ifdef ESP32 + if (spi_config.bus_nr == 1) { + spi = &SPI; + spi->begin(spi_config.clk, spi_config.miso, spi_config.mosi, -1); + } else if (spi_config.bus_nr == 2) { + spi = new SPIClass(HSPI); + spi->begin(spi_config.clk, spi_config.miso, spi_config.mosi, -1); + } else { + pinMode(spi_config.clk, OUTPUT); + digitalWrite(spi_config.clk, LOW); + pinMode(spi_config.mosi, OUTPUT); + digitalWrite(spi_config.mosi, LOW); + if (spi_config.miso >= 0) { + pinMode(spi_config.miso, INPUT_PULLUP); + } + } +#endif // ESP32 + spi_settings = SPISettings((uint32_t)spi_config.speed*1000000, MSBFIRST, SPI_MODE3); +} + +// ===== Pin Control ===== + +void SPIController::csLow() { + if (spi_config.cs >= 0) GPIO_CLR_SLOW(spi_config.cs); +} + +void SPIController::csHigh() { + if (spi_config.cs >= 0) GPIO_SET_SLOW(spi_config.cs); +} + +void SPIController::dcLow() { + if (spi_config.dc >= 0) GPIO_CLR_SLOW(spi_config.dc); +} + +void SPIController::dcHigh() { + if (spi_config.dc >= 0) GPIO_SET_SLOW(spi_config.dc); +} + +// ===== Transaction Control ===== + +void SPIController::beginTransaction() { + if (spi_config.bus_nr <= 2) spi->beginTransaction(spi_settings); +} + +void SPIController::endTransaction() { + if (spi_config.bus_nr <= 2) spi->endTransaction(); +} + +// ===== Low-Level Write Functions ===== +void SPIController::writeCommand(uint8_t cmd) { + if (spi_config.dc < 0) { + // 9-bit mode + if (spi_config.bus_nr > 2) { + if (spi_config.bus_nr == 3) write9(cmd, 0); + else write9_slow(cmd, 0); + } else { + hw_write9(cmd, 0); + } + } else { + // 8-bit mode + dcLow(); + writeData8(cmd); + dcHigh(); + } +} + +void SPIController::writeData8(uint8_t data) { + if (spi_config.dc < 0) { + // 9-bit mode + if (spi_config.bus_nr > 2) { + if (spi_config.bus_nr == 3) write9(data, 1); + else write9_slow(data, 1); + } else { + hw_write9(data, 1); + } + } else { + // 8-bit mode + if (spi_config.bus_nr > 2) { + if (spi_config.bus_nr == 3) write8(data); + else write8_slow(data); + } else { + spi->write(data); + } + } +} + +void SPIController::writeData16(uint16_t data) { + if (spi_config.dc < 0) { + // 9-bit: break into bytes + writeData8(data >> 8); + writeData8(data); + } else { + // 8-bit mode + if (spi_config.bus_nr > 2) { + if (spi_config.bus_nr == 3) write16(data); + else { + // Slow mode: break into bytes + writeData8(data >> 8); + writeData8(data); + } + } else { + spi->write16(data); // Assume SPI has write16 + } + } +} + +void SPIController::writeData32(uint32_t data) { + if (spi_config.dc < 0) { + // 9-bit mode: break into bytes + writeData8(data >> 24); + writeData8(data >> 16); + writeData8(data >> 8); + writeData8(data); + } else { + // 8-bit mode + if (spi_config.bus_nr > 2) { + if (spi_config.bus_nr == 3) { + write32(data); // Fast bit-banging + } else { + // Slow mode: break into bytes + writeData8(data >> 24); + writeData8(data >> 16); + writeData8(data >> 8); + writeData8(data); + } + } else { + // Hardware SPI + spi->write32(data); // Assume SPI has write32 on ESP32 + } + } +} + +// ===== Low-Level Write Functions ===== + +void SPIController::write8(uint8_t val) { + for (uint8_t bit = 0x80; bit; bit >>= 1) { + GPIO_CLR(spi_config.clk); + if (val & bit) GPIO_SET(spi_config.mosi); + else GPIO_CLR(spi_config.mosi); + GPIO_SET(spi_config.clk); + } +} + +void SPIController::write8_slow(uint8_t val) { + for (uint8_t bit = 0x80; bit; bit >>= 1) { + GPIO_CLR_SLOW(spi_config.clk); + if (val & bit) GPIO_SET_SLOW(spi_config.mosi); + else GPIO_CLR_SLOW(spi_config.mosi); + GPIO_SET_SLOW(spi_config.clk); + } +} + +void SPIController::write9(uint8_t val, uint8_t dc) { + GPIO_CLR(spi_config.clk); + if (dc) GPIO_SET(spi_config.mosi); + else GPIO_CLR(spi_config.mosi); + GPIO_SET(spi_config.clk); + + for (uint8_t bit = 0x80; bit; bit >>= 1) { + GPIO_CLR(spi_config.clk); + if (val & bit) GPIO_SET(spi_config.mosi); + else GPIO_CLR(spi_config.mosi); + GPIO_SET(spi_config.clk); + } +} + +void SPIController::write9_slow(uint8_t val, uint8_t dc) { + GPIO_CLR_SLOW(spi_config.clk); + if (dc) GPIO_SET_SLOW(spi_config.mosi); + else GPIO_CLR_SLOW(spi_config.mosi); + GPIO_SET_SLOW(spi_config.clk); + + for (uint8_t bit = 0x80; bit; bit >>= 1) { + GPIO_CLR_SLOW(spi_config.clk); + if (val & bit) GPIO_SET_SLOW(spi_config.mosi); + else GPIO_CLR_SLOW(spi_config.mosi); + GPIO_SET_SLOW(spi_config.clk); + } +} + +void SPIController::write16(uint16_t val) { + for (uint16_t bit = 0x8000; bit; bit >>= 1) { + GPIO_CLR(spi_config.clk); + if (val & bit) GPIO_SET(spi_config.mosi); + else GPIO_CLR(spi_config.mosi); + GPIO_SET(spi_config.clk); + } +} + +void SPIController::write32(uint32_t val) { + for (uint32_t bit = 0x80000000; bit; bit >>= 1) { + GPIO_CLR(spi_config.clk); + if (val & bit) GPIO_SET(spi_config.mosi); + else GPIO_CLR(spi_config.mosi); + GPIO_SET(spi_config.clk); + } +} + +// ===== Hardware 9-bit Mode ===== + +#ifdef ESP32 +void SPIController::hw_write9(uint8_t val, uint8_t dc) { + if (spi_config.dc < -1) { + // RA8876 mode + if (!dc) { + spi->write(RA8876_CMD_WRITE); + spi->write(val); + } else { + spi->write(RA8876_DATA_WRITE); + spi->write(val); + } + } else { + uint32_t regvalue = val >> 1; + if (dc) regvalue |= 0x80; + else regvalue &= 0x7f; + if (val & 1) regvalue |= 0x8000; + + REG_SET_BIT(SPI_USER_REG(3), SPI_USR_MOSI); + REG_WRITE(SPI_MOSI_DLEN_REG(3), 9 - 1); + uint32_t *dp = (uint32_t*)SPI_W0_REG(3); + *dp = regvalue; + REG_SET_BIT(SPI_CMD_REG(3), SPI_USR); + while (REG_GET_FIELD(SPI_CMD_REG(3), SPI_USR)); + } +} +#else +void SPIController::hw_write9(uint8_t val, uint8_t dc) { + if (spi_config.dc < -1) { + // RA8876 mode + if (!dc) { + spi->write(RA8876_CMD_WRITE); + spi->write(val); + } else { + spi->write(RA8876_DATA_WRITE); + spi->write(val); + } + } else { + uint32_t regvalue; + uint8_t bytetemp; + if (!dc) { + bytetemp = (val >> 1) & 0x7f; + } else { + bytetemp = (val >> 1) | 0x80; + } + regvalue = ((8 & SPI_USR_COMMAND_BITLEN) << SPI_USR_COMMAND_BITLEN_S) | ((uint32)bytetemp); + if (val & 0x01) regvalue |= BIT15; + while (READ_PERI_REG(SPI_CMD(1)) & SPI_USR); + WRITE_PERI_REG(SPI_USER2(1), regvalue); + SET_PERI_REG_MASK(SPI_CMD(1), SPI_USR); + } +} +#endif + +// DMA +#ifdef ESP32 +bool SPIController::initDMA(uint16_t width, uint16_t flushlines, uint8_t data) { + AddLog(3,"init dma %u %u %d",flushlines,data, spi_config.cs); + if (!spi && spi_config.cs == -1) return false; + if((data&1) == 0){ + AddLog(3,"no dma selected"); + return false; + } + if (spi_config.bus_nr == 1){ + AddLog(3,"dma spi 1"); + } else if (spi_config.bus_nr == 2){ + AddLog(3,"dma spi 2"); + spi_host = HSPI_HOST; + } else { + return false; + } + + esp_err_t ret; + spi_bus_config_t buscfg = { + .mosi_io_num = spi_config.mosi, + .miso_io_num = spi_config.miso, + .sclk_io_num = spi_config.clk, + .quadwp_io_num = -1, + .quadhd_io_num = -1, + .max_transfer_sz = width * flushlines * 2 + 8, + .flags = 0, + .intr_flags = 0 + }; + + spi_device_interface_config_t devcfg = { + .command_bits = 0, + .address_bits = 0, + .dummy_bits = 0, + .mode = SPI_MODE3, + .duty_cycle_pos = 0, + .cs_ena_pretrans = 0, + .cs_ena_posttrans = 0, + .clock_speed_hz = (int)spi_config.speed, + .input_delay_ns = 0, + .spics_io_num = spi_config.cs, + .flags = SPI_DEVICE_NO_DUMMY, + .queue_size = 1, + .pre_cb = 0, + .post_cb = 0 + }; + + // spi_host_device_t spi_host = (spi_config.bus_nr == 1) ? VSPI_HOST : HSPI_HOST; + + // Try to initialize the bus, but if it's already initialized (by Arduino SPI), that's OK + + ret = spi_bus_initialize(spi_host, &buscfg, SPI_DMA_CH_AUTO); + if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) { + AddLog(3,"init dma bus init failed: %d", ret); + return false; + } + if (ret == ESP_ERR_INVALID_STATE) { + AddLog(3,"init dma bus already initialized (OK)"); + } + + ret = spi_bus_add_device(spi_host, &devcfg, &dmaHAL); + if (ret == ESP_OK) { + DMA_Enabled = true; + async_dma_enabled = ((data&4) != 0); + dma_enabled = true; + spiBusyCheck = 0; + AddLog(3,"init dma succes"); + return true; + } + return false; +} + +// just a placeholder +// void SPIController::deInitDMA(void) { +// if (!DMA_Enabled) return; +// spi_bus_remove_device(dmaHAL); +// spi_bus_free(spi_host); +// DMA_Enabled = false; +// } + +bool SPIController::dmaBusy(void) { + if (!DMA_Enabled || !spiBusyCheck) return false; + + spi_transaction_t *rtrans; + esp_err_t ret; + uint8_t checks = spiBusyCheck; + for (int i = 0; i < checks; ++i) { + ret = spi_device_get_trans_result(dmaHAL, &rtrans, 0); + if (ret == ESP_OK) spiBusyCheck--; + } + if (spiBusyCheck == 0) return false; + return true; +} + +void SPIController::dmaWait(void) { + if (!DMA_Enabled || !spiBusyCheck) return; + spi_transaction_t *rtrans; + esp_err_t ret; + for (int i = 0; i < spiBusyCheck; ++i) { + ret = spi_device_get_trans_result(dmaHAL, &rtrans, portMAX_DELAY); + assert(ret == ESP_OK); + } + spiBusyCheck = 0; +} + +void SPIController::pushPixelsDMA(uint16_t* image, uint32_t len) { + if(!DMA_Enabled){ + getSPI()->writePixels(image, len * 2); + return; + } + if (len == 0) return; + + dmaWait(); + + esp_err_t ret; + + memset(&trans, 0, sizeof(spi_transaction_t)); + + trans.user = (void *)1; + trans.tx_buffer = image; //finally send the line data + trans.length = len * 16; //Data length, in bits + trans.flags = 0; //SPI_TRANS_USE_TXDATA flag + + ret = spi_device_queue_trans(dmaHAL, &trans, portMAX_DELAY); + assert(ret == ESP_OK); + + spiBusyCheck++; + if (!async_dma_enabled) { + dmaWait(); + } +} + +void SPIController::pushPixels3DMA(uint8_t* image, uint32_t len) { + if ((len == 0) || (!DMA_Enabled)) return; + + dmaWait(); + + esp_err_t ret; + + memset(&trans, 0, sizeof(spi_transaction_t)); + + trans.user = (void *)1; + trans.tx_buffer = image; //finally send the line data + trans.length = len * 24; //Data length, in bits + trans.flags = 0; //SPI_TRANS_USE_TXDATA flag + + ret = spi_device_queue_trans(dmaHAL, &trans, portMAX_DELAY); + assert(ret == ESP_OK); + + spiBusyCheck++; + if (!async_dma_enabled) { + dmaWait(); + } +} +#endif // ESP32 +// ===== RA8876 Specific ===== + +uint8_t SPIController::writeReg16(uint8_t reg, uint16_t wval) { + hw_write9(reg, 0); + hw_write9(wval, 1); + hw_write9(reg + 1, 0); + hw_write9(wval >> 8, 1); + return 0; +} + +uint8_t SPIController::readData(void) { + if (!spi) return 0; + spi->write(RA8876_DATA_READ); + return spi->transfer(0); +} + +uint8_t SPIController::readStatus(void) { + if (!spi) return 0; + spi->write(RA8876_STATUS_READ); + return spi->transfer(0); +} diff --git a/lib/lib_display/UDisplay/uDisplay_SPI_controller.h b/lib/lib_display/UDisplay/uDisplay_SPI_controller.h new file mode 100644 index 000000000..e93f34ad9 --- /dev/null +++ b/lib/lib_display/UDisplay/uDisplay_SPI_controller.h @@ -0,0 +1,98 @@ +#ifndef _UDISPLAY_SPI_CONTROLLER_H_ +#define _UDISPLAY_SPI_CONTROLLER_H_ + +#include +#include + +#ifdef ESP32 +#include "soc/spi_reg.h" +#include "soc/spi_struct.h" +#include "esp32-hal-spi.h" +#include "driver/spi_master.h" +#include "soc/gpio_periph.h" +#endif + +#ifndef ESP32 +#include "spi_register.h" +#endif + +struct SPIControllerConfig { + uint8_t bus_nr; + int8_t cs; + int8_t clk; + int8_t mosi; + int8_t dc; + int8_t miso; + uint32_t speed; +}; + +/** + * Minimal SPIController - wraps low-level SPI functions + * Extracted from uDisplay_spi.cpp + */ +class SPIController { +public: + SPIController(const SPIControllerConfig& config); + ~SPIController() = default; + + // ===== Pin Control ===== + void csLow(); + void csHigh(); + void dcLow(); + void dcHigh(); + + // ===== Transaction Control ===== + void beginTransaction(); + void endTransaction(); + + // ===== High-Level Write Functions ===== + void writeCommand(uint8_t cmd); + void writeData8(uint8_t data); + void writeData16(uint16_t data); + void writeData32(uint32_t data); + + // ===== RA8876 Specific ===== + uint8_t writeReg16(uint8_t reg, uint16_t wval); + uint8_t readData(void); + uint8_t readStatus(void); + + // ===== Direct Access ===== + SPIClass* getSPI() { return spi; } + // SPISettings getSPISettings() { return spi_settings; } + + // ===== DMA ===== +#ifdef ESP32 + bool initDMA(uint16_t width, uint16_t height, uint8_t data); + void dmaWait(void); + bool dmaBusy(void); + void pushPixelsDMA(uint16_t* image, uint32_t len); + void pushPixels3DMA(uint8_t* image, uint32_t len); +#endif + SPIControllerConfig spi_config; // make this private in the future again! + +private: + SPIClass* spi; + SPISettings spi_settings; + + // ===== Low-Level Write Functions ===== + void write8(uint8_t val); + void write8_slow(uint8_t val); + void write9(uint8_t val, uint8_t dc); + void write9_slow(uint8_t val, uint8_t dc); + void write16(uint16_t val); + void write32(uint32_t val); + void hw_write9(uint8_t val, uint8_t dc); + +#ifdef ESP32 + bool dma_enabled = false; + bool async_dma_enabled = false; + + spi_host_device_t spi_host = VSPI_HOST; + bool DMA_Enabled = false; + uint8_t spiBusyCheck; + spi_device_handle_t dmaHAL = nullptr; // For DMA + spi_transaction_t trans; +#endif //ESP32 +}; + +#endif // _UDISPLAY_SPI_CONTROLLER_H_ \ No newline at end of file diff --git a/lib/lib_display/UDisplay/uDisplay_SPI_panel.cpp b/lib/lib_display/UDisplay/uDisplay_SPI_panel.cpp new file mode 100644 index 000000000..98ecd80f9 --- /dev/null +++ b/lib/lib_display/UDisplay/uDisplay_SPI_panel.cpp @@ -0,0 +1,367 @@ +// WIP +// ====================================================== +// uDisplay_spi_panel.cpp - SPI LCD Panel Implementation +// ====================================================== + +#include "uDisplay_SPI_panel.h" +#include +extern void AddLog(uint32_t loglevel, const char* formatP, ...); + + +SPIPanel::SPIPanel(const SPIPanelConfig& config, + SPIController* spi_ctrl, + uint8_t* framebuffer) + : spi(spi_ctrl), cfg(config), fb_buffer(framebuffer), + rotation(0), display_on(true), inverted(false) +{ + // Initialize address window state + window_x0 = 0; + window_y0 = 0; + window_x1 = 0; + window_y1 = 0; + use_hw_spi = (spi->spi_config.dc >= 0) && (spi->spi_config.bus_nr <= 2); +} + +SPIPanel::~SPIPanel() { + // Panel doesn't own framebuffer or SPI controller +} + + +// ===== UniversalPanel Interface Implementation ===== + +bool SPIPanel::drawPixel(int16_t x, int16_t y, uint16_t color) { + // From original uDisplay::drawPixel - only handle direct SPI drawing for color TFTs + if ((x < 0) || (x >= cfg.width) || (y < 0) || (y >= cfg.height)) return true; + + // Only handle direct SPI drawing for color displays without framebuffer + if (!fb_buffer && cfg.bpp >= 16) { + spi->beginTransaction(); + spi->csLow(); + setAddrWindow_internal(x, y, 1, 1); + spi->writeCommand(cfg.cmd_write_ram); + + if (cfg.col_mode == 18) { + // From original WriteColor function + uint8_t r = (color & 0xF800) >> 11; + uint8_t g = (color & 0x07E0) >> 5; + uint8_t b = color & 0x001F; + r = (r * 255) / 31; + g = (g * 255) / 63; + b = (b * 255) / 31; + spi->writeData8(r); + spi->writeData8(g); + spi->writeData8(b); + } else { + spi->writeData16(color); + } + spi->csHigh(); + spi->endTransaction(); + return true; + } + + return false; // Let uDisplay handle framebuffer cases (monochrome OLEDs) +} + +bool SPIPanel::fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) { + // From original uDisplay::fillRect + if((x >= cfg.width) || (y >= cfg.height)) return true; + if((x + w - 1) >= cfg.width) w = cfg.width - x; + if((y + h - 1) >= cfg.height) h = cfg.height - y; + + // Only handle direct SPI drawing for color displays without framebuffer + if (!fb_buffer && cfg.bpp >= 16) { + spi->beginTransaction(); + spi->csLow(); + setAddrWindow_internal(x, y, w, h); + spi->writeCommand(cfg.cmd_write_ram); + + if (cfg.col_mode == 18) { + uint8_t r = (color & 0xF800) >> 11; + uint8_t g = (color & 0x07E0) >> 5; + uint8_t b = color & 0x001F; + r = (r * 255) / 31; + g = (g * 255) / 63; + b = (b * 255) / 31; + + for (int16_t yp = h; yp > 0; yp--) { + for (int16_t xp = w; xp > 0; xp--) { + spi->writeData8(r); + spi->writeData8(g); + spi->writeData8(b); + } + } + } else { + for (int16_t yp = h; yp > 0; yp--) { + for (int16_t xp = w; xp > 0; xp--) { + spi->writeData16(color); + } + } + } + spi->csHigh(); + spi->endTransaction(); + return true; + } + + return false; // Let uDisplay handle framebuffer cases (monochrome OLEDs) +} + +bool SPIPanel::pushColors(uint16_t *data, uint16_t len, bool not_swapped) { + // Only handle direct rendering for color displays + if (cfg.bpp < 16) { + return false; + } + + // Handle byte swapping for LVGL (when not_swapped == false) + if (!not_swapped && cfg.col_mode != 18) { + // LVGL data - bytes are already swapped + if (use_hw_spi) { +#ifdef ESP32 + spi->pushPixelsDMA(data, len); +#else + spi->getSPI()->writeBytes((uint8_t*)data, len * 2); +#endif + } else { + // Software SPI - write pixel by pixel + for (uint16_t i = 0; i < len; i++) { + spi->writeData16(data[i]); + } + } + return true; + } + + // Handle 18-bit color mode + if (cfg.col_mode == 18) { +#ifdef ESP32 + if (use_hw_spi) { + uint8_t *line = (uint8_t*)malloc(len * 3); + if (line) { + uint8_t *lp = line; + for (uint32_t cnt = 0; cnt < len; cnt++) { + uint16_t color = data[cnt]; + if (!not_swapped) { + color = (color << 8) | (color >> 8); + } + uint8_t r = (color & 0xF800) >> 11; + uint8_t g = (color & 0x07E0) >> 5; + uint8_t b = color & 0x001F; + r = (r * 255) / 31; + g = (g * 255) / 63; + b = (b * 255) / 31; + *lp++ = r; + *lp++ = g; + *lp++ = b; + } + spi->pushPixels3DMA(line, len); + free(line); + } + } else +#endif + { + // Software SPI or ESP8266 + for (uint16_t i = 0; i < len; i++) { + uint16_t color = data[i]; + if (!not_swapped) { + color = (color << 8) | (color >> 8); + } + uint8_t r = (color & 0xF800) >> 11; + uint8_t g = (color & 0x07E0) >> 5; + uint8_t b = color & 0x001F; + r = (r * 255) / 31; + g = (g * 255) / 63; + b = (b * 255) / 31; + spi->writeData8(r); + spi->writeData8(g); + spi->writeData8(b); + } + } + return true; + } + + // Handle 16-bit color mode with no byte swapping (not_swapped == true) + if (not_swapped) { + if (use_hw_spi) { +#ifdef ESP32 + spi->getSPI()->writePixels(data, len * 2); +#else + // ESP8266: writePixels() doesn't exist, use per-pixel write + for (uint16_t i = 0; i < len; i++) { + spi->writeData16(data[i]); + } +#endif + return true; + } + // Software SPI - write per-pixel + for (uint16_t i = 0; i < len; i++) { + spi->writeData16(data[i]); + } + return true; + } + + return false; +} + +bool SPIPanel::setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) { + // From original uDisplay::setAddrWindow + window_x0 = x0; + window_y0 = y0; + window_x1 = x1; + window_y1 = y1; + if (!x0 && !y0 && !x1 && !y1) { + spi->csHigh(); + spi->endTransaction(); + } else { + spi->beginTransaction(); + spi->csLow(); + setAddrWindow_internal(x0, y0, x1 - x0, y1 - y0); + } + return true; +} + +void SPIPanel::setAddrWindow_internal(uint16_t x, uint16_t y, uint16_t w, uint16_t h) { + // From original uDisplay::setAddrWindow_int + x += cfg.x_addr_offset[rotation]; + y += cfg.y_addr_offset[rotation]; + uint16_t x2 = x + w - 1; + uint16_t y2 = y + h - 1; + + if (cfg.address_mode != 8) { + // 16/32-bit addressing (most TFT displays) + uint32_t xa = ((uint32_t)x << 16) | x2; + uint32_t ya = ((uint32_t)y << 16) | y2; + + spi->writeCommand(cfg.cmd_set_addr_x); + spi->writeData32(xa); + + spi->writeCommand(cfg.cmd_set_addr_y); + spi->writeData32(ya); + + if (cfg.cmd_write_ram != 0xFF) { + spi->writeCommand(cfg.cmd_write_ram); + } + } else { + // 8-bit addressing mode (OLED displays) + if (rotation & 1) { + // Vertical address increment mode + uint16_t temp = x; x = y; y = temp; + temp = x2; x2 = y2; y2 = temp; + } + + spi->writeCommand(cfg.cmd_set_addr_x); + if (cfg.all_commands_mode) { + spi->writeData8(x); + spi->writeData8(x2); + } else { + spi->writeCommand(x); + spi->writeCommand(x2); + } + + spi->writeCommand(cfg.cmd_set_addr_y); + if (cfg.all_commands_mode) { + spi->writeData8(y); + spi->writeData8(y2); + } else { + spi->writeCommand(y); + spi->writeCommand(y2); + } + + if (cfg.cmd_write_ram != 0xFF) { + spi->writeCommand(cfg.cmd_write_ram); + } + } +} + +bool SPIPanel::drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) { + // From original uDisplay::drawFastHLine + return fillRect(x, y, w, 1, color); +} + +bool SPIPanel::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) { + // From original uDisplay::drawFastVLine + return fillRect(x, y, 1, h, color); +} + +bool SPIPanel::displayOnff(int8_t on) { + display_on = (on != 0); + + spi->beginTransaction(); + spi->csLow(); + if (display_on && cfg.cmd_display_on != 0xFF) { + spi->writeCommand(cfg.cmd_display_on); + } else if (!display_on && cfg.cmd_display_off != 0xFF) { + spi->writeCommand(cfg.cmd_display_off); + } + spi->csHigh(); + spi->endTransaction(); + + return true; +} + +bool SPIPanel::invertDisplay(bool invert) { + inverted = invert; + + spi->beginTransaction(); + spi->csLow(); + if (invert && cfg.cmd_invert_on != 0xFF) { + spi->writeCommand(cfg.cmd_invert_on); + } else if (!invert && cfg.cmd_invert_off != 0xFF) { + spi->writeCommand(cfg.cmd_invert_off); + } + spi->csHigh(); + spi->endTransaction(); + + return true; +} + +bool SPIPanel::setRotation(uint8_t rot) { + // From original uDisplay::setRotation + rotation = rot & 3; + spi->beginTransaction(); + spi->csLow(); + + if (cfg.cmd_memory_access != 0xFF && cfg.rot_cmd[rotation] != 0xFF) { + spi->writeCommand(cfg.cmd_memory_access); + if (!cfg.all_commands_mode) { + spi->writeData8(cfg.rot_cmd[rotation]); + } else { + spi->writeCommand(cfg.rot_cmd[rotation]); + } + spi->csHigh(); + spi->endTransaction(); + return true; + } + spi->csHigh(); + spi->endTransaction(); + return false; +} + +bool SPIPanel::updateFrame() { + // From original uDisplay::Updateframe - only for monochrome SPI OLEDs + // Only handle framebuffer updates for monochrome displays + if (!fb_buffer || cfg.bpp != 1) return false; + + // OLED page-based framebuffer update (from original code) + uint8_t ys = cfg.height >> 3; + uint8_t xs = cfg.width >> 3; + uint8_t m_row = cfg.cmd_set_addr_y; // saw_2 in original + uint8_t m_col = 0; // i2c_col_start in original + + uint16_t p = 0; + uint8_t i, j, k = 0; + + spi->beginTransaction(); + spi->csLow(); + for (i = 0; i < ys; i++) { + spi->writeCommand(0xB0 + i + m_row); // set page address + spi->writeCommand(m_col & 0xf); // set lower column address + spi->writeCommand(0x10 | (m_col >> 4)); // set higher column address + + for (j = 0; j < 8; j++) { + for (k = 0; k < xs; k++, p++) { + spi->writeData8(fb_buffer[p]); + } + } + } + spi->csHigh(); + spi->endTransaction(); + return true; +} \ No newline at end of file diff --git a/lib/lib_display/UDisplay/uDisplay_SPI_panel.h b/lib/lib_display/UDisplay/uDisplay_SPI_panel.h new file mode 100644 index 000000000..2ff5976fa --- /dev/null +++ b/lib/lib_display/UDisplay/uDisplay_SPI_panel.h @@ -0,0 +1,116 @@ +// WIP +// ====================================================== +// uDisplay_spi_panel.h - SPI LCD Panel Implementation +// ====================================================== + +#pragma once + +#include "uDisplay_panel.h" +#include "uDisplay_SPI_controller.h" + +typedef struct LVGL_PARAMS_t { + uint16_t flushlines; + union { + uint8_t data; + struct { + uint8_t use_dma : 1; + uint8_t swap_color : 1; + uint8_t async_dma : 1; // force DMA completion before returning, avoid conflict with other devices on same bus. If set you should make sure the display is the only device on the bus + uint8_t busy_invert : 1; + uint8_t invert_bw : 1; + uint8_t resvd_3 : 1; + uint8_t resvd_4 : 1; + uint8_t resvd_5 : 1; + }; + }; +}LVGL_PARAMS_t; + + +/** + * Configuration for SPI-based displays + */ +struct SPIPanelConfig { + // ===== Display Dimensions ===== + uint16_t width; + uint16_t height; + uint8_t bpp; // bits per pixel (1, 8, 16, etc.) + uint8_t col_mode; // color mode (16, 18, etc.) + + // ===== Address Window Protocol ===== + uint8_t cmd_set_addr_x; // Command to set X address range + uint8_t cmd_set_addr_y; // Command to set Y address range + uint8_t cmd_write_ram; // Command to write pixel data + + // ===== Display Control Commands ===== + uint8_t cmd_display_on; + uint8_t cmd_display_off; + uint8_t cmd_invert_on; + uint8_t cmd_invert_off; + uint8_t cmd_memory_access; // For rotation settings + uint8_t cmd_startline; // For vertical scroll offset + + // ===== Per-Rotation Configuration ===== + uint8_t rot_cmd[4]; // Memory access command variant for each rotation + uint16_t x_addr_offset[4]; // Address offset per rotation + uint16_t y_addr_offset[4]; + uint8_t address_mode; // Addressing scheme (8, 16, 32-bit) + + // ===== Flags ===== + bool all_commands_mode; // If true: send data bytes as commands + + // ===== Reset & Power Control ===== + int8_t reset_pin; // GPIO for display reset (-1 if none) + // int8_t busy_pin; // REMOVED - busy_pin is EPD-only, moved to EPDPanelConfig + int8_t bpanel; // Backlight GPIO (-1 if none) +}; + +class SPIPanel : public UniversalPanel { +public: + /** + * Constructor - receives framebuffer from uDisplay if needed + */ + SPIPanel(const SPIPanelConfig& config, + SPIController* spi_ctrl, + uint8_t* framebuffer); + + ~SPIPanel(); + + // ===== UniversalPanel Interface ===== + bool drawPixel(int16_t x, int16_t y, uint16_t color) override; + bool fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) override; + bool pushColors(uint16_t *data, uint16_t len, bool not_swapped = false) override; + bool setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) override; + bool drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) override; + bool drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) override; + + bool displayOnff(int8_t on) override; + bool invertDisplay(bool invert) override; + bool setRotation(uint8_t rotation) override; + bool updateFrame() override; + +private: + // ===== Hardware & Configuration ===== + SPIController* spi; // Not owned by panel + SPIPanelConfig cfg; // Copy of config + + // ===== Framebuffer ===== + uint8_t* fb_buffer; // Framebuffer (if provided by uDisplay) + + // ===== Display State ===== + uint8_t rotation; // Current rotation (0-3) + int16_t window_x0, window_y0, window_x1, window_y1; + bool display_on; + bool inverted; + + bool use_hw_spi = false; + + // ===== Internal Helpers ===== + void setAddrWindow_internal(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1); + void sendAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1); + void sendCommand(uint8_t cmd); + void sendData8(uint8_t data); + void sendData16(uint16_t data); + void writeColor(uint16_t color); + void resetDisplay(); + void waitBusy(); +}; \ No newline at end of file diff --git a/lib/lib_display/UDisplay/uDisplayColors.cpp b/lib/lib_display/UDisplay/uDisplay_colors.cpp similarity index 100% rename from lib/lib_display/UDisplay/uDisplayColors.cpp rename to lib/lib_display/UDisplay/uDisplay_colors.cpp diff --git a/lib/lib_display/UDisplay/uDisplay_config.h b/lib/lib_display/UDisplay/uDisplay_config.h index 5dd8f7d78..e859862bf 100644 --- a/lib/lib_display/UDisplay/uDisplay_config.h +++ b/lib/lib_display/UDisplay/uDisplay_config.h @@ -14,7 +14,7 @@ extern void AddLog(uint32_t loglevel, const char* formatP, ...); extern int32_t ESP_ResetInfoReason(); extern float CharToFloat(const char *str); extern SPIClass *SpiBegin(uint32 bus); -extern int Cache_WriteBack_Addr(uint32_t addr, uint32_t size); +// extern int Cache_WriteBack_Addr(uint32_t addr, uint32_t size); extern bool UsePSRAM(void); enum uColorType { uCOLOR_BW, uCOLOR_COLOR }; diff --git a/lib/lib_display/UDisplay/uDisplay_control.cpp b/lib/lib_display/UDisplay/uDisplay_control.cpp index 29c1d537b..a39a3cb91 100644 --- a/lib/lib_display/UDisplay/uDisplay_control.cpp +++ b/lib/lib_display/UDisplay/uDisplay_control.cpp @@ -1,6 +1,5 @@ #include "uDisplay.h" #include "uDisplay_config.h" -#include "uDisplay_spi.h" void udisp_bpwr(uint8_t on); @@ -21,50 +20,43 @@ void uDisplay::DisplayOnff(int8_t on) { if (pwr_cbp) { pwr_cbp(on); } + if (universal_panel->displayOnff(on)) { + return; + } #define AW_PWMRES 1024 - if (interface == _UDSP_I2C) { - if (on) { - i2c_command(dsp_on); - } else { - i2c_command(dsp_off); + if (on) { + if (bpanel >= 0) { +#ifdef ESP32 + if (!bpmode) { + analogWrite(bpanel, dimmer10_gamma); + } else { + analogWrite(bpanel, AW_PWMRES - dimmer10_gamma); + } +#else + if (!bpmode) { + digitalWrite(bpanel, HIGH); + } else { + digitalWrite(bpanel, LOW); + } +#endif } } else { - if (on) { - if (dsp_on != 0xff) ulcd_command_one(dsp_on); - if (bpanel >= 0) { + if (bpanel >= 0) { #ifdef ESP32 - if (!bpmode) { - analogWrite(bpanel, dimmer10_gamma); - } else { - analogWrite(bpanel, AW_PWMRES - dimmer10_gamma); - } -#else - if (!bpmode) { - digitalWrite(bpanel, HIGH); - } else { - digitalWrite(bpanel, LOW); - } -#endif + if (!bpmode) { + analogWrite(bpanel, 0); + } else { + analogWrite(bpanel, AW_PWMRES - 1); } - } else { - if (dsp_off != 0xff) ulcd_command_one(dsp_off); - if (bpanel >= 0) { -#ifdef ESP32 - if (!bpmode) { - analogWrite(bpanel, 0); - } else { - analogWrite(bpanel, AW_PWMRES - 1); - } #else - if (!bpmode) { - digitalWrite(bpanel, LOW); - } else { - digitalWrite(bpanel, HIGH); - } -#endif + if (!bpmode) { + digitalWrite(bpanel, LOW); + } else { + digitalWrite(bpanel, HIGH); } +#endif } } } @@ -93,12 +85,12 @@ void uDisplay::dim10(uint8_t dim, uint16_t dim_gamma) { if (interface == _UDSP_SPI) { if (dim_op != 0xff) { - SPI_BEGIN_TRANSACTION - SPI_CS_LOW - ulcd_command(dim_op); - ulcd_data8(dimmer8); - SPI_CS_HIGH - SPI_END_TRANSACTION + spiController->beginTransaction(); + spiController->csLow(); + spiController->writeCommand(dim_op); + spiController->writeData8(dimmer8); + spiController->csHigh(); + spiController->endTransaction(); } } } @@ -106,24 +98,8 @@ void uDisplay::dim10(uint8_t dim, uint16_t dim_gamma) { // ===== Display Inversion ===== void uDisplay::invertDisplay(boolean i) { - if (ep_mode) { - return; - } - - if (interface == _UDSP_SPI || interface == _UDSP_PAR8 || interface == _UDSP_PAR16) { - if (i) { - ulcd_command_one(inv_on); - } else { - ulcd_command_one(inv_off); - } - } - - if (interface == _UDSP_I2C) { - if (i) { - i2c_command(inv_on); - } else { - i2c_command(inv_off); - } + if (universal_panel) { + universal_panel->invertDisplay(i); } } @@ -134,7 +110,10 @@ void uDisplay::Splash(void) { if (ep_mode) { Updateframe(); - delay_sync(lut3time * 10); + if (universal_panel) { + EPDPanel* epd = static_cast(universal_panel); + epd->delay_sync(panel_config->epd.update_time * 10); + } } setTextFont(splash_font); diff --git a/lib/lib_display/UDisplay/uDisplay_dma.cpp b/lib/lib_display/UDisplay/uDisplay_dma.cpp deleted file mode 100644 index b8fa6eb63..000000000 --- a/lib/lib_display/UDisplay/uDisplay_dma.cpp +++ /dev/null @@ -1,133 +0,0 @@ -#include "uDisplay.h" -#include "uDisplay_config.h" - -#ifdef ESP32 - -// ===== DMA Initialization and Control ===== -// ESP 32 DMA section , derived from TFT_eSPI - -bool uDisplay::initDMA(int32_t ctrl_cs) { - if (DMA_Enabled) return false; - - esp_err_t ret; - spi_bus_config_t buscfg = { - .mosi_io_num = spi_mosi, - .miso_io_num = -1, - .sclk_io_num = spi_clk, - .quadwp_io_num = -1, - .quadhd_io_num = -1, - .max_transfer_sz = width() * height() * 2 + 8, // TFT screen size - .flags = 0, - .intr_flags = 0 - }; - - spi_device_interface_config_t devcfg = { - .command_bits = 0, - .address_bits = 0, - .dummy_bits = 0, - .mode = SPI_MODE3, - .duty_cycle_pos = 0, - .cs_ena_pretrans = 0, - .cs_ena_posttrans = 0, - .clock_speed_hz = spi_speed*1000000, - .input_delay_ns = 0, - .spics_io_num = ctrl_cs, - .flags = SPI_DEVICE_NO_DUMMY, //0, - .queue_size = 1, - .pre_cb = 0, //dc_callback, //Callback to handle D/C line - .post_cb = 0 - }; - - ret = spi_bus_initialize(spi_host, &buscfg, 1); - ESP_ERROR_CHECK(ret); - ret = spi_bus_add_device(spi_host, &devcfg, &dmaHAL); - ESP_ERROR_CHECK(ret); - - DMA_Enabled = true; - spiBusyCheck = 0; - return true; -} - -void uDisplay::deInitDMA(void) { - if (!DMA_Enabled) return; - spi_bus_remove_device(dmaHAL); - spi_bus_free(spi_host); - DMA_Enabled = false; -} - -bool uDisplay::dmaBusy(void) { - if (!DMA_Enabled || !spiBusyCheck) return false; - - spi_transaction_t *rtrans; - esp_err_t ret; - uint8_t checks = spiBusyCheck; - for (int i = 0; i < checks; ++i) { - ret = spi_device_get_trans_result(dmaHAL, &rtrans, 0); - if (ret == ESP_OK) spiBusyCheck--; - } - - //Serial.print("spiBusyCheck=");Serial.println(spiBusyCheck); - if (spiBusyCheck == 0) return false; - return true; -} - -void uDisplay::dmaWait(void) { - if (!DMA_Enabled || !spiBusyCheck) return; - spi_transaction_t *rtrans; - esp_err_t ret; - for (int i = 0; i < spiBusyCheck; ++i) { - ret = spi_device_get_trans_result(dmaHAL, &rtrans, portMAX_DELAY); - assert(ret == ESP_OK); - } - spiBusyCheck = 0; -} - -// ===== DMA Data Transfer Functions ===== - -void uDisplay::pushPixelsDMA(uint16_t* image, uint32_t len) { - if ((len == 0) || (!DMA_Enabled)) return; - - dmaWait(); - - esp_err_t ret; - - memset(&trans, 0, sizeof(spi_transaction_t)); - - trans.user = (void *)1; - trans.tx_buffer = image; //finally send the line data - trans.length = len * 16; //Data length, in bits - trans.flags = 0; //SPI_TRANS_USE_TXDATA flag - - ret = spi_device_queue_trans(dmaHAL, &trans, portMAX_DELAY); - assert(ret == ESP_OK); - - spiBusyCheck++; - if (!lvgl_param.async_dma) { - dmaWait(); - } -} - -void uDisplay::pushPixels3DMA(uint8_t* image, uint32_t len) { - if ((len == 0) || (!DMA_Enabled)) return; - - dmaWait(); - - esp_err_t ret; - - memset(&trans, 0, sizeof(spi_transaction_t)); - - trans.user = (void *)1; - trans.tx_buffer = image; //finally send the line data - trans.length = len * 24; //Data length, in bits - trans.flags = 0; //SPI_TRANS_USE_TXDATA flag - - ret = spi_device_queue_trans(dmaHAL, &trans, portMAX_DELAY); - assert(ret == ESP_OK); - - spiBusyCheck++; - if (!lvgl_param.async_dma) { - dmaWait(); - } -} - -#endif // ESP32 \ No newline at end of file diff --git a/lib/lib_display/UDisplay/uDisplay_epd.cpp b/lib/lib_display/UDisplay/uDisplay_epd.cpp deleted file mode 100644 index e16122934..000000000 --- a/lib/lib_display/UDisplay/uDisplay_epd.cpp +++ /dev/null @@ -1,362 +0,0 @@ -#include "uDisplay.h" -#include "uDisplay_config.h" -#include "uDisplay_spi.h" - - -// ===== EPD Command Definitions ===== -static constexpr uint8_t DRIVER_OUTPUT_CONTROL = 0x01; -static constexpr uint8_t BOOSTER_SOFT_START_CONTROL = 0x0C; -static constexpr uint8_t GATE_SCAN_START_POSITION = 0x0F; -static constexpr uint8_t DEEP_SLEEP_MODE = 0x10; -static constexpr uint8_t DATA_ENTRY_MODE_SETTING = 0x11; -static constexpr uint8_t SW_RESET = 0x12; -static constexpr uint8_t TEMPERATURE_SENSOR_CONTROL = 0x1A; -static constexpr uint8_t MASTER_ACTIVATION = 0x20; -static constexpr uint8_t DISPLAY_UPDATE_CONTROL_1 = 0x21; -static constexpr uint8_t DISPLAY_UPDATE_CONTROL_2 = 0x22; -static constexpr uint8_t WRITE_RAM = 0x24; -static constexpr uint8_t WRITE_VCOM_REGISTER = 0x2C; -static constexpr uint8_t WRITE_LUT_REGISTER = 0x32; -static constexpr uint8_t SET_DUMMY_LINE_PERIOD = 0x3A; -static constexpr uint8_t SET_GATE_TIME = 0x3B; -static constexpr uint8_t BORDER_WAVEFORM_CONTROL = 0x3C; -static constexpr uint8_t SET_RAM_X_ADDRESS_START_END_POSITION = 0x44; -static constexpr uint8_t SET_RAM_Y_ADDRESS_START_END_POSITION = 0x45; -static constexpr uint8_t SET_RAM_X_ADDRESS_COUNTER = 0x4E; -static constexpr uint8_t SET_RAM_Y_ADDRESS_COUNTER = 0x4F; -static constexpr uint8_t TERMINATE_FRAME_READ_WRITE = 0xFF; - -// ===== EPD Initialization and Control ===== - -void uDisplay::Init_EPD(int8_t p) { - if (p == DISPLAY_INIT_PARTIAL) { - if (lutpsize) { - AddLog(LOG_LEVEL_DEBUG, PSTR("DSP: init partial epaper mode")); - SetLut(lut_partial); - Updateframe_EPD(); - delay_sync(lutptime * 10); - } - return; - } else if (p == DISPLAY_INIT_FULL) { - AddLog(LOG_LEVEL_DEBUG, PSTR("DSP: init full epaper mode")); - if (lutfsize) { - SetLut(lut_full); - Updateframe_EPD(); - } - if (ep_mode == 2) { - ClearFrame_42(); - DisplayFrame_42(); - } - delay_sync(lutftime * 10); - return; - } - - if (ep_mode == 2) Init_EPD(DISPLAY_INIT_FULL); -} - -void uDisplay::SetLut(const unsigned char* lut) { - spi_command_EPD(lut_cmd[0]); - for (int i = 0; i < lutfsize; i++) { - spi_data8_EPD(lut[i]); - } -} - -void uDisplay::SetLuts(void) { - uint8_t index, count; - for (index = 0; index < MAX_LUTS; index++) { - spi_command_EPD(lut_cmd[index]); - for (count = 0; count < lut_cnt[index]; count++) { - spi_data8_EPD(lut_array[index][count]); - } - } -} - -// ===== EPD Frame Memory Management ===== - -void uDisplay::ClearFrameMemory(unsigned char color) { - SetMemoryArea(0, 0, gxs - 1, gys - 1); - SetMemoryPointer(0, 0); - spi_command_EPD(WRITE_RAM); - for (int i = 0; i < gxs / 8 * gys; i++) { - spi_data8_EPD(color); - } -} - -void uDisplay::ClearFrame_42(void) { - spi_command_EPD(saw_1); - for (uint16_t j = 0; j < gys; j++) { - for (uint16_t i = 0; i < gxs; i++) { - spi_data8_EPD(0xFF); - } - } - - spi_command_EPD(saw_2); - for (uint16_t j = 0; j < gys; j++) { - for (uint16_t i = 0; i < gxs; i++) { - spi_data8_EPD(0xFF); - } - } - - spi_command_EPD(saw_3); - delay_sync(100); - AddLog(LOG_LEVEL_DEBUG, PSTR("DSP: EPD Clearframe")); -} - -void uDisplay::DisplayFrame_42(void) { - spi_command_EPD(saw_1); - for(int i = 0; i < gxs / 8 * gys; i++) { - spi_data8_EPD(0xFF); - } - delay(2); - - spi_command_EPD(saw_2); - for(int i = 0; i < gxs / 8 * gys; i++) { - spi_data8_EPD(framebuffer[i]^0xff); - } - delay(2); - - SetLuts(); - - spi_command_EPD(saw_3); - delay_sync(100); - - AddLog(LOG_LEVEL_DEBUG, PSTR("DSP: EPD Displayframe")); -} - -void uDisplay::DisplayFrame_29(void) { - spi_command_EPD(DISPLAY_UPDATE_CONTROL_2); - spi_data8_EPD(0xC4); - spi_command_EPD(MASTER_ACTIVATION); - spi_data8_EPD(TERMINATE_FRAME_READ_WRITE); -} - -// ===== EPD Memory Addressing ===== - -void uDisplay::SetMemoryArea(int x_start, int y_start, int x_end, int y_end) { - int x_start1 = (x_start >> 3) & 0xFF; - int x_end1 = (x_end >> 3) & 0xFF; - int y_start1 = y_start & 0xFF; - int y_start2 = (y_start >> 8) & 0xFF; - int y_end1 = y_end & 0xFF; - int y_end2 = (y_end >> 8) & 0xFF; - - if (ep_mode == 3) { - spi_command_EPD(SET_RAM_X_ADDRESS_START_END_POSITION); - spi_data8_EPD(x_start1); - spi_data8_EPD(x_end1); - spi_command_EPD(SET_RAM_Y_ADDRESS_START_END_POSITION); - spi_data8_EPD(y_end1); - spi_data8_EPD(y_end2); - spi_data8_EPD(y_start1); - spi_data8_EPD(y_start2); - } else { - spi_command_EPD(SET_RAM_X_ADDRESS_START_END_POSITION); - spi_data8_EPD(x_start1); - spi_data8_EPD(x_end1); - spi_command_EPD(SET_RAM_Y_ADDRESS_START_END_POSITION); - spi_data8_EPD(y_start1); - spi_data8_EPD(y_start2); - spi_data8_EPD(y_end1); - spi_data8_EPD(y_end2); - } -} - -void uDisplay::SetMemoryPointer(int x, int y) { - int x1; - int y1; - int y2; - - if (ep_mode == 3) { - x1 = (x >> 3) & 0xFF; - y--; - y1 = y & 0xFF; - y2 = (y >> 8) & 0xFF; - } else { - x1 = (x >> 3) & 0xFF; - y1 = y & 0xFF; - y2 = (y >> 8) & 0xFF; - } - - spi_command_EPD(SET_RAM_X_ADDRESS_COUNTER); - spi_data8_EPD(x1); - spi_command_EPD(SET_RAM_Y_ADDRESS_COUNTER); - spi_data8_EPD(y1); - spi_data8_EPD(y2); -} - -// ===== EPD Frame Updates ===== - -void uDisplay::Updateframe_EPD(void) { - if (ep_mode == 1 || ep_mode == 3) { - switch (ep_update_mode) { - case DISPLAY_INIT_PARTIAL: - if (epc_part_cnt) { - send_spi_cmds(epcoffs_part, epc_part_cnt); - } - break; - case DISPLAY_INIT_FULL: - if (epc_full_cnt) { - send_spi_cmds(epcoffs_full, epc_full_cnt); - } - break; - default: - SetFrameMemory(framebuffer, 0, 0, gxs, gys); - DisplayFrame_29(); - } - } else { - DisplayFrame_42(); - } -} - -void uDisplay::SetFrameMemory(const unsigned char* image_buffer) { - SetMemoryArea(0, 0, gxs - 1, gys - 1); - SetMemoryPointer(0, 0); - spi_command_EPD(WRITE_RAM); - for (int i = 0; i < gxs / 8 * gys; i++) { - spi_data8_EPD(image_buffer[i] ^ 0xff); - } -} - -void uDisplay::SetFrameMemory(const unsigned char* image_buffer, - uint16_t x, uint16_t y, uint16_t image_width, uint16_t image_height) { - - uint16_t x_end; - uint16_t y_end; - - if (image_buffer == NULL || x < 0 || image_width < 0 || y < 0 || image_height < 0) { - return; - } - - x &= 0xFFF8; - image_width &= 0xFFF8; - if (x + image_width >= gxs) { - x_end = gxs - 1; - } else { - x_end = x + image_width - 1; - } - if (y + image_height >= gys) { - y_end = gys - 1; - } else { - y_end = y + image_height - 1; - } - - if (!x && !y && image_width == gxs && image_height == gys) { - SetFrameMemory(image_buffer); - return; - } - - SetMemoryArea(x, y, x_end, y_end); - SetMemoryPointer(x, y); - spi_command_EPD(WRITE_RAM); - for (uint16_t j = 0; j < y_end - y + 1; j++) { - for (uint16_t i = 0; i < (x_end - x + 1) / 8; i++) { - spi_data8_EPD(image_buffer[i + j * (image_width / 8)] ^ 0xff); - } - } -} - -void uDisplay::Send_EP_Data() { - uint16_t image_width = gxs & 0xFFF8; - uint16_t x = 0; - uint16_t y = 0; - uint16_t x_end = gxs - 1; - uint16_t y_end = gys - 1; - - for (uint16_t j = 0; j < y_end - y + 1; j++) { - for (uint16_t i = 0; i < (x_end - x + 1) / 8; i++) { - spi_data8_EPD(framebuffer[i + j * (image_width / 8)] ^ 0xff); - } - } -} - -// ===== EPD Drawing Primitives ===== -#define IF_INVERT_COLOR 1 -#define renderer_swap(a, b) { int16_t t = a; a = b; b = t; } - -void uDisplay::DrawAbsolutePixel(int x, int y, int16_t color) { - int16_t w = width(), h = height(); - if (cur_rot == 1 || cur_rot == 3) { - renderer_swap(w, h); - } - - if (x < 0 || x >= w || y < 0 || y >= h) { - return; - } - - if (IF_INVERT_COLOR) { - if (color) { - framebuffer[(x + y * w) / 8] |= 0x80 >> (x % 8); - } else { - framebuffer[(x + y * w) / 8] &= ~(0x80 >> (x % 8)); - } - } else { - if (color) { - framebuffer[(x + y * w) / 8] &= ~(0x80 >> (x % 8)); - } else { - framebuffer[(x + y * w) / 8] |= 0x80 >> (x % 8); - } - } -} - -void uDisplay::drawPixel_EPD(int16_t x, int16_t y, uint16_t color) { - if (!framebuffer) return; - if ((x < 0) || (x >= width()) || (y < 0) || (y >= height())) - return; - - switch (cur_rot) { - case 1: - renderer_swap(x, y); - x = gxs - x - 1; - break; - case 2: - x = gxs - x - 1; - y = gys - y - 1; - break; - case 3: - renderer_swap(x, y); - y = gys - y - 1; - break; - } - - DrawAbsolutePixel(x, y, color); -} - -void uDisplay::fillRect_EPD(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) { - for (uint32_t yp = y; yp < y + h; yp++) { - for (uint32_t xp = x; xp < x + w; xp++) { - drawPixel_EPD(xp, yp, color); - } - } -} - -void uDisplay::drawFastVLine_EPD(int16_t x, int16_t y, int16_t h, uint16_t color) { - while (h--) { - drawPixel_EPD(x, y, color); - y++; - } -} - -void uDisplay::drawFastHLine_EPD(int16_t x, int16_t y, int16_t w, uint16_t color) { - while (w--) { - drawPixel_EPD(x, y, color); - x++; - } -} - -// ===== EPD SPI Helpers ===== - -void uDisplay::spi_command_EPD(uint8_t val) { - SPI_BEGIN_TRANSACTION - SPI_CS_LOW - ulcd_command(val); - SPI_CS_HIGH - SPI_END_TRANSACTION -} - -void uDisplay::spi_data8_EPD(uint8_t val) { - SPI_BEGIN_TRANSACTION - SPI_CS_LOW - ulcd_data8(val); - SPI_CS_HIGH - SPI_END_TRANSACTION -} \ No newline at end of file diff --git a/lib/lib_display/UDisplay/uDisplay_esp32s3.cpp b/lib/lib_display/UDisplay/uDisplay_esp32s3.cpp deleted file mode 100644 index 4e2409d47..000000000 --- a/lib/lib_display/UDisplay/uDisplay_esp32s3.cpp +++ /dev/null @@ -1,158 +0,0 @@ -#include "uDisplay.h" -#include "uDisplay_config.h" - -#ifdef USE_ESP32_S3 -static inline volatile uint32_t* get_gpio_hi_reg(int_fast8_t pin) { return (pin & 32) ? &GPIO.out1_w1ts.val : &GPIO.out_w1ts; } -//static inline volatile uint32_t* get_gpio_hi_reg(int_fast8_t pin) { return (volatile uint32_t*)((pin & 32) ? 0x60004014 : 0x60004008) ; } // workaround Eratta -static inline volatile uint32_t* get_gpio_lo_reg(int_fast8_t pin) { return (pin & 32) ? &GPIO.out1_w1tc.val : &GPIO.out_w1tc; } -//static inline volatile uint32_t* get_gpio_lo_reg(int_fast8_t pin) { return (volatile uint32_t*)((pin & 32) ? 0x60004018 : 0x6000400C) ; } -static inline bool gpio_in(int_fast8_t pin) { return ((pin & 32) ? GPIO.in1.data : GPIO.in) & (1 << (pin & 31)); } -static inline void gpio_hi(int_fast8_t pin) { if (pin >= 0) *get_gpio_hi_reg(pin) = 1 << (pin & 31); } // ESP_LOGI("LGFX", "gpio_hi: %d", pin); } -static inline void gpio_lo(int_fast8_t pin) { if (pin >= 0) *get_gpio_lo_reg(pin) = 1 << (pin & 31); } // ESP_LOGI("LGFX", "gpio_lo: %d", pin); } - -// ===== ESP32-S3 Clock Calculation ===== - -void uDisplay::calcClockDiv(uint32_t* div_a, uint32_t* div_b, uint32_t* div_n, uint32_t* clkcnt, uint32_t baseClock, uint32_t targetFreq) { - uint32_t diff = INT32_MAX; - *div_n = 256; - *div_a = 63; - *div_b = 62; - *clkcnt = 64; - uint32_t start_cnt = std::min(64u, (baseClock / (targetFreq * 2) + 1)); - uint32_t end_cnt = std::max(2u, baseClock / 256u / targetFreq); - if (start_cnt <= 2) { end_cnt = 1; } - for (uint32_t cnt = start_cnt; diff && cnt >= end_cnt; --cnt) - { - float fdiv = (float)baseClock / cnt / targetFreq; - uint32_t n = std::max(2u, (uint32_t)fdiv); - fdiv -= n; - - for (uint32_t a = 63; diff && a > 0; --a) - { - uint32_t b = roundf(fdiv * a); - if (a == b && n == 256) { - break; - } - uint32_t freq = baseClock / ((n * cnt) + (float)(b * cnt) / (float)a); - uint32_t d = abs((int)targetFreq - (int)freq); - if (diff <= d) { continue; } - diff = d; - *clkcnt = cnt; - *div_n = n; - *div_b = b; - *div_a = a; - if (b == 0 || a == b) { - break; - } - } - } - if (*div_a == *div_b) - { - *div_b = 0; - *div_n += 1; - } -} - -// ===== ESP32-S3 DMA Descriptor Management ===== - -void uDisplay::_alloc_dmadesc(size_t len) { - if (_dmadesc) heap_caps_free(_dmadesc); - _dmadesc_size = len; - _dmadesc = (lldesc_t*)heap_caps_malloc(sizeof(lldesc_t) * len, MALLOC_CAP_DMA); -} - -void uDisplay::_setup_dma_desc_links(const uint8_t *data, int32_t len) { - static constexpr size_t MAX_DMA_LEN = (4096-4); -} - -// ===== ESP32-S3 Pin Control ===== - -void uDisplay::cs_control(bool level) { - auto pin = par_cs; - if (pin < 0) return; - if (level) { - gpio_hi(pin); - } - else { - gpio_lo(pin); - } -} - -void uDisplay::_pb_init_pin(bool read) { - if (read) { - if (interface == _UDSP_PAR8) { - for (size_t i = 0; i < 8; ++i) { - gpio_ll_output_disable(&GPIO, (gpio_num_t)par_dbl[i]); - } - } else { - for (size_t i = 0; i < 8; ++i) { - gpio_ll_output_disable(&GPIO, (gpio_num_t)par_dbl[i]); - } - for (size_t i = 0; i < 8; ++i) { - gpio_ll_output_disable(&GPIO, (gpio_num_t)par_dbh[i]); - } - } - } - else { - auto idx_base = LCD_DATA_OUT0_IDX; - if (interface == _UDSP_PAR8) { - for (size_t i = 0; i < 8; ++i) { - gpio_matrix_out(par_dbl[i], idx_base + i, 0, 0); - } - } else { - for (size_t i = 0; i < 8; ++i) { - gpio_matrix_out(par_dbl[i], idx_base + i, 0, 0); - } - for (size_t i = 0; i < 8; ++i) { - gpio_matrix_out(par_dbh[i], idx_base + 8 + i, 0, 0); - } - } - } -} - -// ===== ESP32-S3 Simple Resistive Touch ===== - -uint32_t uDisplay::get_sr_touch(uint32_t _xp, uint32_t _xm, uint32_t _yp, uint32_t _ym) { - uint32_t aval = 0; - uint16_t xp,yp; - if (pb_busy()) return 0; - - _pb_init_pin(true); - gpio_matrix_out(par_rs, 0x100, 0, 0); - - pinMode(_ym, INPUT_PULLUP); // d0 - pinMode(_yp, INPUT_PULLUP); // rs - - pinMode(_xm, OUTPUT); // cs - pinMode(_xp, OUTPUT); // d1 - digitalWrite(_xm, HIGH); // cs - digitalWrite(_xp, LOW); // d1 - - xp = 4096 - analogRead(_ym); // d0 - - pinMode(_xm, INPUT_PULLUP); // cs - pinMode(_xp, INPUT_PULLUP); // d1 - - pinMode(_ym, OUTPUT); // d0 - pinMode(_yp, OUTPUT); // rs - digitalWrite(_ym, HIGH); // d0 - digitalWrite(_yp, LOW); // rs - - yp = 4096 - analogRead(_xp); // d1 - - aval = (xp << 16) | yp; - - pinMode(_yp, OUTPUT); // rs - pinMode(_xm, OUTPUT); // cs - pinMode(_ym, OUTPUT); // d0 - pinMode(_xp, OUTPUT); // d1 - digitalWrite(_yp, HIGH); // rs - digitalWrite(_xm, HIGH); // cs - - _pb_init_pin(false); - gpio_matrix_out(par_rs, LCD_DC_IDX, 0, 0); - - return aval; -} - -#endif // USE_ESP32_S3 \ No newline at end of file diff --git a/lib/lib_display/UDisplay/uDisplay_graphics.cpp b/lib/lib_display/UDisplay/uDisplay_graphics.cpp index 256facc89..eef1437ec 100644 --- a/lib/lib_display/UDisplay/uDisplay_graphics.cpp +++ b/lib/lib_display/UDisplay/uDisplay_graphics.cpp @@ -1,44 +1,28 @@ #include "uDisplay.h" #include "uDisplay_config.h" -#include "uDisplay_spi.h" // ===== Basic Drawing Primitives ===== static constexpr uint16_t RGB16_TO_MONO = 0x8410; static constexpr uint16_t RGB16_SWAP_TO_MONO = 0x1084; -#define renderer_swap(a, b) { int16_t t = a; a = b; b = t; } void uDisplay::drawPixel(int16_t x, int16_t y, uint16_t color) { -#ifdef USE_ESP32_S3 - if (interface == _UDSP_RGB) { - drawPixel_RGB(x, y, color); - return; - } -#endif - - if (ep_mode) { - drawPixel_EPD(x, y, color); - return; + if (universal_panel->drawPixel(x, y, color)) { + return; // Handled by universal panel } if (framebuffer) { Renderer::drawPixel(x, y, color); return; } - - if ((x < 0) || (x >= _width) || (y < 0) || (y >= _height)) return; - - SPI_BEGIN_TRANSACTION - SPI_CS_LOW - setAddrWindow_int(x, y, 1, 1); - WriteColor(color); - SPI_CS_HIGH - SPI_END_TRANSACTION } void uDisplay::drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) { - if (ep_mode) { - drawFastHLine_EPD(x, y, w, color); + // Rudimentary clipping + if((x >= _width) || (y >= _height)) return; + if((x + w - 1) >= _width) w = _width - x; + + if (universal_panel->drawFastHLine(x, y, w, color)) { return; } @@ -47,65 +31,9 @@ void uDisplay::drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) { return; } - // Rudimentary clipping - if((x >= _width) || (y >= _height)) return; - if((x + w - 1) >= _width) w = _width - x; - -#ifdef USE_ESP32_S3 - if (interface == _UDSP_RGB) { - if (cur_rot > 0) { - while (w--) { - drawPixel_RGB(x , y , color); - x++; - } - } else { - uint16_t *fb = rgb_fb; - fb += (int32_t)y * _width; - fb += x; - while (w--) { - *fb = color; - Cache_WriteBack_Addr((uint32_t)fb, 2); - fb++; - x++; - } - } - return; - } -#endif - - SPI_BEGIN_TRANSACTION - SPI_CS_LOW - setAddrWindow_int(x, y, w, 1); - - if (col_mode == 18) { - uint8_t r = (color & 0xF800) >> 11; - uint8_t g = (color & 0x07E0) >> 5; - uint8_t b = color & 0x001F; - r = (r * 255) / 31; - g = (g * 255) / 63; - b = (b * 255) / 31; - - while (w--) { - ulcd_data8(r); - ulcd_data8(g); - ulcd_data8(b); - } - } else { - while (w--) { - WriteColor(color); - } - } - - SPI_CS_HIGH - SPI_END_TRANSACTION } void uDisplay::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) { - if (ep_mode) { - drawFastVLine_EPD(x, y, h, color); - return; - } - if (framebuffer) { Renderer::drawFastVLine(x, y, h, color); return; @@ -115,67 +43,15 @@ void uDisplay::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) { if ((x >= _width) || (y >= _height)) return; if ((y + h - 1) >= _height) h = _height - y; -#ifdef USE_ESP32_S3 - if (interface == _UDSP_RGB) { - if (cur_rot > 0) { - while (h--) { - drawPixel_RGB(x , y , color); - y++; - } - } else { - uint16_t *fb = rgb_fb; - fb += (int32_t)y * _width; - fb += x; - while (h--) { - *fb = color; - Cache_WriteBack_Addr((uint32_t)fb, 2); - fb+=_width; - y++; - } - } + if (universal_panel->drawFastVLine(x, y, h, color)) { return; } -#endif - SPI_BEGIN_TRANSACTION - SPI_CS_LOW - setAddrWindow_int(x, y, 1, h); - if (col_mode == 18) { - uint8_t r = (color & 0xF800) >> 11; - uint8_t g = (color & 0x07E0) >> 5; - uint8_t b = color & 0x001F; - r = (r * 255) / 31; - g = (g * 255) / 63; - b = (b * 255) / 31; - - while (h--) { - ulcd_data8(r); - ulcd_data8(g); - ulcd_data8(b); - } - } else { - while (h--) { - WriteColor(color); - } - } - - SPI_CS_HIGH - SPI_END_TRANSACTION } void uDisplay::fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) { -#ifdef USE_ESP32_S3 - if (interface == _UDSP_RGB) { - for (uint32_t yp = y; yp < y + h; yp++) { - drawFastHLine(x, yp, w, color); - } - return; - } -#endif - - if (ep_mode) { - fillRect_EPD(x, y, w, h, color); + if (universal_panel->fillRect(x, y, w, h, color)) { return; } @@ -183,40 +59,6 @@ void uDisplay::fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t col Renderer::fillRect(x, y, w, h, color); return; } - - if((x >= _width) || (y >= _height)) return; - if((x + w - 1) >= _width) w = _width - x; - if((y + h - 1) >= _height) h = _height - y; - - SPI_BEGIN_TRANSACTION - SPI_CS_LOW - setAddrWindow_int(x, y, w, h); - - if (col_mode == 18) { - uint8_t r = (color & 0xF800) >> 11; - uint8_t g = (color & 0x07E0) >> 5; - uint8_t b = color & 0x001F; - r = (r * 255) / 31; - g = (g * 255) / 63; - b = (b * 255) / 31; - - for (y = h; y > 0; y--) { - for (x = w; x > 0; x--) { - ulcd_data8(r); - ulcd_data8(g); - ulcd_data8(b); - } - } - } else { - for (y = h; y > 0; y--) { - for (x = w; x > 0; x--) { - WriteColor(color); - } - } - } - - SPI_CS_HIGH - SPI_END_TRANSACTION } void uDisplay::fillScreen(uint16_t color) { @@ -225,140 +67,12 @@ void uDisplay::fillScreen(uint16_t color) { static inline void lvgl_color_swap(uint16_t *data, uint16_t len) { for (uint32_t i = 0; i < len; i++) (data[i] = data[i] << 8 | data[i] >> 8); } -void uDisplay::pushColors(uint16_t *data, uint16_t len, boolean not_swapped) { +void uDisplay::pushColors(uint16_t *data, uint16_t len, boolean not_swapped) { //not_swapped is always true in call form LVGL driver!!!! - if (lvgl_param.swap_color) { - not_swapped = !not_swapped; - } - - //Serial.printf("push %x - %d - %d - %d\n", (uint32_t)data, len, not_swapped, lvgl_param.data); - - // Isolating _UDSP_RGB to increase code sharing - // - // Use ESP-IDF LCD driver to push colors and rely on the following assumptions: - // * bytes swapping is already handled in the driver configuration (see uDisplay::Init()), - // * pushColors() is only called with not_swapped equals true, - // * cache flushing is done by the LCD driver. - if (interface == _UDSP_RGB) { -#ifdef USE_ESP32_S3 - if (!not_swapped) { - // internal error -> write error message but continue (with possibly wrong colors) - AddLog(LOG_LEVEL_ERROR, PSTR("DSP: Unexpected byte-swapping requested in pushColors()")); + if (lvgl_param.swap_color) { + not_swapped = !not_swapped; } - - // check that bytes count matches the size of area, and remove from inner loop - if ((seta_yp2 - seta_yp1) * (seta_xp2 - seta_xp2) > len) { return; } - - esp_lcd_panel_draw_bitmap(_panel_handle, seta_xp1, seta_yp1, seta_xp2, seta_yp2, (void *)data); -#endif - return; - } - - - if (not_swapped == false) { - // called from LVGL bytes are swapped - if (bpp != 16) { - // lvgl_color_swap(data, len); -- no need to swap anymore, we have inverted the mask - pushColorsMono(data, len, true); - return; - } - - if ( (col_mode != 18) && (spi_dc >= 0) && (spi_nr <= 2) ) { - // special version 8 bit spi I or II -#ifdef ESP8266 - lvgl_color_swap(data, len); - while (len--) { - uspi->write(*data++); - } -#else - if (lvgl_param.use_dma) { - pushPixelsDMA(data, len ); - } else { - uspi->writeBytes((uint8_t*)data, len * 2); - } -#endif - } else { - -#ifdef ESP32 - if ( (col_mode == 18) && (spi_dc >= 0) && (spi_nr <= 2) ) { - uint8_t *line = (uint8_t*)malloc(len * 3); - uint8_t *lp = line; - if (line) { - uint16_t color; - for (uint32_t cnt = 0; cnt < len; cnt++) { - color = *data++; - color = (color << 8) | (color >> 8); - uint8_t r = (color & 0xF800) >> 11; - uint8_t g = (color & 0x07E0) >> 5; - uint8_t b = color & 0x001F; - r = (r * 255) / 31; - g = (g * 255) / 63; - b = (b * 255) / 31; - *lp++ = r; - *lp++ = g; - *lp++ = b; - } - - if (lvgl_param.use_dma) { - pushPixels3DMA(line, len ); - } else { - uspi->writeBytes(line, len * 3); - } - free(line); - } - - } else { - // 9 bit and others - if (interface == _UDSP_PAR8 || interface == _UDSP_PAR16) { - #ifdef USE_ESP32_S3 - pb_pushPixels(data, len, true, false); - #endif // USE_ESP32_S3 - } else { - lvgl_color_swap(data, len); - while (len--) { - WriteColor(*data++); - } - } - } -#endif // ESP32 - -#ifdef ESP8266 - lvgl_color_swap(data, len); - while (len--) { - WriteColor(*data++); - } -#endif - } - } else { - // called from displaytext, no byte swap, currently no dma here - - if (bpp != 16) { - pushColorsMono(data, len); - return; - } - if ( (col_mode != 18) && (spi_dc >= 0) && (spi_nr <= 2) ) { - // special version 8 bit spi I or II - #ifdef ESP8266 - while (len--) { - //uspi->write(*data++); - WriteColor(*data++); - } - #else - uspi->writePixels(data, len * 2); - #endif - } else { - // 9 bit and others - if (interface == _UDSP_PAR8 || interface == _UDSP_PAR16) { -#ifdef USE_ESP32_S3 - pb_pushPixels(data, len, false, false); -#endif // USE_ESP32_S3 - } else { - while (len--) { - WriteColor(*data++); - } - } - } - } + universal_panel->pushColors(data, len, not_swapped); } // convert to mono, these are framebuffer based @@ -390,176 +104,34 @@ void uDisplay::pushColorsMono(uint16_t *data, uint16_t len, bool rgb16_swap) { } void uDisplay::setAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { - if (bpp != 16 || interface == _UDSP_RGB) { - // Just save params or update frame - if (!x0 && !y0 && !x1 && !y1) { - if (!ep_mode) { - Updateframe(); - } - } else { - seta_xp1 = x0; - seta_xp2 = x1; - seta_yp1 = y0; - seta_yp2 = y1; - } - return; - } - - if (interface == _UDSP_RGB) { - return; - } - - if (!x0 && !y0 && !x1 && !y1) { - SPI_CS_HIGH - SPI_END_TRANSACTION - } else { - SPI_BEGIN_TRANSACTION - SPI_CS_LOW - setAddrWindow_int(x0, y0, x1 - x0, y1 - y0); - } -} - -void uDisplay::setAddrWindow_int(uint16_t x, uint16_t y, uint16_t w, uint16_t h) { - if (interface == _UDSP_RGB) { - return; - } - - x += x_addr_offs[cur_rot]; - y += y_addr_offs[cur_rot]; - - if (sa_mode != 8) { - uint32_t xa = ((uint32_t)x << 16) | (x + w - 1); - uint32_t ya = ((uint32_t)y << 16) | (y + h - 1); - - ulcd_command(saw_1); - ulcd_data32(xa); - - ulcd_command(saw_2); - ulcd_data32(ya); - - if (saw_3 != 0xff) { - ulcd_command(saw_3); // write to RAM - } - } else { - uint16_t x2 = x + w - 1, - y2 = y + h - 1; - - if (cur_rot & 1) { // Vertical address increment mode - renderer_swap(x,y); - renderer_swap(x2,y2); - } - ulcd_command(saw_1); - if (allcmd_mode) { - ulcd_data8(x); - ulcd_data8(x2); - } else { - ulcd_command(x); - ulcd_command(x2); - } - ulcd_command(saw_2); - if (allcmd_mode) { - ulcd_data8(y); - ulcd_data8(y2); - } else { - ulcd_command(y); - ulcd_command(y2); - } - if (saw_3 != 0xff) { - ulcd_command(saw_3); // write to RAM - } - } + universal_panel->setAddrWindow(x0, y0, x1, y1); } void uDisplay::setRotation(uint8_t rotation) { cur_rot = rotation; + if (universal_panel->setRotation(rotation)) { + // Update Renderer dimensions based on rotation + switch (rotation) { + case 0: + case 2: + _width = gxs; + _height = gys; + break; + case 1: + case 3: + _width = gys; + _height = gxs; + break; + } + return; + } if (framebuffer) { Renderer::setRotation(cur_rot); return; } - - if (interface == _UDSP_SPI || interface == _UDSP_PAR8 || interface == _UDSP_PAR16) { - if (ep_mode) { - Renderer::setRotation(cur_rot); - return; - } - SPI_BEGIN_TRANSACTION - SPI_CS_LOW - ulcd_command(madctrl); - - if (!allcmd_mode) { - ulcd_data8(rot[cur_rot]); - } else { - ulcd_command(rot[cur_rot]); - } - - if ((sa_mode == 8) && !allcmd_mode) { - ulcd_command(startline); - ulcd_data8((cur_rot < 2) ? height() : 0); - } - - SPI_CS_HIGH - SPI_END_TRANSACTION - } - - switch (rotation) { - case 0: - _width = gxs; - _height = gys; - break; - case 1: - _width = gys; - _height = gxs; - break; - case 2: - _width = gxs; - _height = gys; - break; - case 3: - _width = gys; - _height = gxs; - break; - } - -#ifdef USE_ESP32_S3 - if (interface == _UDSP_RGB) { - // Utilize the ESP-IDF LCD driver's support for display rotation - esp_lcd_panel_mirror(_panel_handle, rotation == 1 || rotation == 2, rotation & 2); - esp_lcd_panel_swap_xy(_panel_handle, rotation & 1); - } -#endif } -#ifdef USE_ESP32_S3 -void uDisplay::drawPixel_RGB(int16_t x, int16_t y, uint16_t color) { - int16_t w = _width, h = _height; - - if ((x < 0) || (x >= w) || (y < 0) || (y >= h)) { - return; - } - - // check rotation, move pixel around if necessary - switch (cur_rot) { - case 1: - renderer_swap(w, h); - renderer_swap(x, y); - x = w - x - 1; - break; - case 2: - x = w - x - 1; - y = h - y - 1; - break; - case 3: - renderer_swap(w, h); - renderer_swap(x, y); - y = h - y - 1; - break; - } - - uint16_t *fb = rgb_fb; - fb += (int32_t)y * w; - fb += x; - *fb = color; - Cache_WriteBack_Addr((uint32_t)fb, 2); +void uDisplay::Updateframe(void) { + universal_panel->updateFrame(); } -#endif \ No newline at end of file diff --git a/lib/lib_display/UDisplay/uDisplay_panel.h b/lib/lib_display/UDisplay/uDisplay_panel.h new file mode 100644 index 000000000..d6028e249 --- /dev/null +++ b/lib/lib_display/UDisplay/uDisplay_panel.h @@ -0,0 +1,37 @@ +// ====================================================== +// uDisplay_panel.h - Base Panel Interface +// ====================================================== + +#pragma once + +#if CONFIG_IDF_TARGET_ESP32P4 +#include "esp_cache.h" + #define CACHE_WRITEBACK_ADDR(addr, size) esp_cache_msync((void*)addr, size, ESP_CACHE_MSYNC_FLAG_DIR_C2M) +#else + #define CACHE_WRITEBACK_ADDR(addr, size) Cache_WriteBack_Addr(addr, size) +#endif + +#include + +class UniversalPanel { +public: + virtual ~UniversalPanel() {} + + // Core graphics API - return true if handled, false for uDisplay fallback + virtual bool drawPixel(int16_t x, int16_t y, uint16_t color) = 0; + virtual bool fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) = 0; + virtual bool pushColors(uint16_t *data, uint16_t len, bool first = false) = 0; + virtual bool setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) = 0; + virtual bool drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) = 0; + virtual bool drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) = 0; + + // Control API - return true if handled, false for uDisplay fallback + virtual bool displayOnff(int8_t on) = 0; + virtual bool invertDisplay(bool invert) = 0; + virtual bool setRotation(uint8_t rotation) = 0; + + // Frame update method for displays that need explicit updates + virtual bool updateFrame() = 0; + // Framebuffer - own or external + uint16_t* framebuffer = nullptr; +}; \ No newline at end of file diff --git a/lib/lib_display/UDisplay/uDisplay_parallel.cpp b/lib/lib_display/UDisplay/uDisplay_parallel.cpp deleted file mode 100644 index d3f39f559..000000000 --- a/lib/lib_display/UDisplay/uDisplay_parallel.cpp +++ /dev/null @@ -1,142 +0,0 @@ -#include "uDisplay.h" -#include "uDisplay_config.h" - -#ifdef USE_ESP32_S3 - -#ifdef ESP32 -#include "esp8266toEsp32.h" -#endif - -#define WAIT_LCD_NOT_BUSY while (*reg_lcd_user & LCD_CAM_LCD_START) {} - -// ===== Parallel Bus Control Functions ===== - -void uDisplay::pb_beginTransaction(void) { - auto dev = _dev; - dev->lcd_clock.val = _clock_reg_value; - dev->lcd_misc.val = LCD_CAM_LCD_CD_IDLE_EDGE; - dev->lcd_user.val = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_REG; - _cache_flip = _cache[0]; -} - -void uDisplay::pb_endTransaction(void) { - auto dev = _dev; - while (dev->lcd_user.val & LCD_CAM_LCD_START) {} -} - -void uDisplay::pb_wait(void) { - auto dev = _dev; - while (dev->lcd_user.val & LCD_CAM_LCD_START) {} -} - -bool uDisplay::pb_busy(void) { - auto dev = _dev; - return (dev->lcd_user.val & LCD_CAM_LCD_START); -} - -// ===== Parallel Bus Write Functions ===== - -bool uDisplay::pb_writeCommand(uint32_t data, uint_fast8_t bit_length) { - auto dev = _dev; - auto reg_lcd_user = &(dev->lcd_user.val); - dev->lcd_misc.val = LCD_CAM_LCD_CD_IDLE_EDGE | LCD_CAM_LCD_CD_CMD_SET; - - if (interface == _UDSP_PAR8) { - auto bytes = bit_length >> 3; - do { - dev->lcd_cmd_val.lcd_cmd_value = data; - data >>= 8; - WAIT_LCD_NOT_BUSY - *reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_REG | LCD_CAM_LCD_START; - } while (--bytes); - return true; - } else { - dev->lcd_cmd_val.val = data; - WAIT_LCD_NOT_BUSY - *reg_lcd_user = LCD_CAM_LCD_2BYTE_EN | LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_REG | LCD_CAM_LCD_START; - return true; - } -} - -void uDisplay::pb_writeData(uint32_t data, uint_fast8_t bit_length) { - auto dev = _dev; - auto reg_lcd_user = &(dev->lcd_user.val); - dev->lcd_misc.val = LCD_CAM_LCD_CD_IDLE_EDGE; - auto bytes = bit_length >> 3; - - if (interface == _UDSP_PAR8) { - uint8_t shift = (bytes - 1) * 8; - for (uint32_t cnt = 0; cnt < bytes; cnt++) { - dev->lcd_cmd_val.lcd_cmd_value = (data >> shift) & 0xff; - shift -= 8; - WAIT_LCD_NOT_BUSY - *reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_REG | LCD_CAM_LCD_START; - } - return; - } else { - if (bytes == 1 || bytes == 4) { - uint8_t shift = (bytes - 1) * 8; - for (uint32_t cnt = 0; cnt < bytes; cnt++) { - dev->lcd_cmd_val.lcd_cmd_value = (data >> shift) & 0xff; - shift -= 8; - WAIT_LCD_NOT_BUSY - *reg_lcd_user = LCD_CAM_LCD_2BYTE_EN | LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_REG | LCD_CAM_LCD_START; - } - return; - } - - dev->lcd_cmd_val.val = data; - WAIT_LCD_NOT_BUSY - *reg_lcd_user = LCD_CAM_LCD_2BYTE_EN | LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_REG | LCD_CAM_LCD_START; - return; - } -} - -void uDisplay::pb_pushPixels(uint16_t* data, uint32_t length, bool swap_bytes, bool use_dma) { - auto dev = _dev; - auto reg_lcd_user = &(dev->lcd_user.val); - dev->lcd_misc.val = LCD_CAM_LCD_CD_IDLE_EDGE; - - if (interface == _UDSP_PAR8) { - if (swap_bytes) { - for (uint32_t cnt = 0; cnt < length; cnt++) { - dev->lcd_cmd_val.lcd_cmd_value = *data; - while (*reg_lcd_user & LCD_CAM_LCD_START) {} - *reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_REG | LCD_CAM_LCD_START; - dev->lcd_cmd_val.lcd_cmd_value = *data >> 8; - WAIT_LCD_NOT_BUSY - *reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_REG | LCD_CAM_LCD_START; - data++; - } - } else { - for (uint32_t cnt = 0; cnt < length; cnt++) { - dev->lcd_cmd_val.lcd_cmd_value = *data >> 8; - while (*reg_lcd_user & LCD_CAM_LCD_START) {} - *reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_REG | LCD_CAM_LCD_START; - dev->lcd_cmd_val.lcd_cmd_value = *data; - WAIT_LCD_NOT_BUSY - *reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_REG | LCD_CAM_LCD_START; - data++; - } - } - } else { - if (swap_bytes) { - uint16_t iob; - for (uint32_t cnt = 0; cnt < length; cnt++) { - iob = *data++; - iob = (iob << 8) | (iob >> 8); - dev->lcd_cmd_val.lcd_cmd_value = iob; - WAIT_LCD_NOT_BUSY - *reg_lcd_user = LCD_CAM_LCD_2BYTE_EN | LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_REG | LCD_CAM_LCD_START; - } - } else { - for (uint32_t cnt = 0; cnt < length; cnt++) { - dev->lcd_cmd_val.lcd_cmd_value = *data++; - WAIT_LCD_NOT_BUSY - *reg_lcd_user = LCD_CAM_LCD_2BYTE_EN | LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_REG | LCD_CAM_LCD_START; - } - } - } -} - -#endif \ No newline at end of file diff --git a/lib/lib_display/UDisplay/uDisplay_spi.cpp b/lib/lib_display/UDisplay/uDisplay_spi.cpp deleted file mode 100644 index 2a11abe94..000000000 --- a/lib/lib_display/UDisplay/uDisplay_spi.cpp +++ /dev/null @@ -1,154 +0,0 @@ -#include "uDisplay.h" -#include "uDisplay_config.h" -#include "uDisplay_spi.h" -#ifndef ESP32 -#include "spi_register.h" -#endif - -// ===== Low-Level SPI Write Functions ===== -// ===== RA8876 Controller Commands ===== -static constexpr uint8_t RA8876_DATA_WRITE = 0x80; -static constexpr uint8_t RA8876_DATA_READ = 0xC0; -static constexpr uint8_t RA8876_CMD_WRITE = 0x00; -static constexpr uint8_t RA8876_STATUS_READ = 0x40; - -#ifdef ESP32 -void uDisplay::hw_write9(uint8_t val, uint8_t dc) { - if (spi_dc < -1) { - // RA8876 mode - if (!dc) { - uspi->write(RA8876_CMD_WRITE); - uspi->write(val); - } else { - uspi->write(RA8876_DATA_WRITE); - uspi->write(val); - } - } else { - uint32_t regvalue = val >> 1; - if (dc) regvalue |= 0x80; - else regvalue &= 0x7f; - if (val & 1) regvalue |= 0x8000; - - REG_SET_BIT(SPI_USER_REG(3), SPI_USR_MOSI); - REG_WRITE(SPI_MOSI_DLEN_REG(3), 9 - 1); - uint32_t *dp = (uint32_t*)SPI_W0_REG(3); - *dp = regvalue; - REG_SET_BIT(SPI_CMD_REG(3), SPI_USR); - while (REG_GET_FIELD(SPI_CMD_REG(3), SPI_USR)); - } -} -#else -void uDisplay::hw_write9(uint8_t val, uint8_t dc) { - if (spi_dc < -1) { - // RA8876 mode - if (!dc) { - uspi->write(RA8876_CMD_WRITE); - uspi->write(val); - } else { - uspi->write(RA8876_DATA_WRITE); - uspi->write(val); - } - } else { - uint32_t regvalue; - uint8_t bytetemp; - if (!dc) { - bytetemp = (val>> 1) & 0x7f; - } else { - bytetemp = (val >> 1) | 0x80; - } - regvalue = ((8 & SPI_USR_COMMAND_BITLEN) << SPI_USR_COMMAND_BITLEN_S) | ((uint32)bytetemp); - if (val & 0x01) regvalue |= BIT15; - while (READ_PERI_REG(SPI_CMD(1)) & SPI_USR); - WRITE_PERI_REG(SPI_USER2(1), regvalue); - SET_PERI_REG_MASK(SPI_CMD(1), SPI_USR); - } -} -#endif - -// ===== Software SPI (Slow) Functions ===== - -void uDisplay::write8(uint8_t val) { - for (uint8_t bit = 0x80; bit; bit >>= 1) { - GPIO_CLR(spi_clk); - if (val & bit) GPIO_SET(spi_mosi); - else GPIO_CLR(spi_mosi); - GPIO_SET(spi_clk); - } -} - -void uDisplay::write8_slow(uint8_t val) { - for (uint8_t bit = 0x80; bit; bit >>= 1) { - GPIO_CLR_SLOW(spi_clk); - if (val & bit) GPIO_SET_SLOW(spi_mosi); - else GPIO_CLR_SLOW(spi_mosi); - GPIO_SET_SLOW(spi_clk); - } -} - -void uDisplay::write9(uint8_t val, uint8_t dc) { - GPIO_CLR(spi_clk); - if (dc) GPIO_SET(spi_mosi); - else GPIO_CLR(spi_mosi); - GPIO_SET(spi_clk); - - for (uint8_t bit = 0x80; bit; bit >>= 1) { - GPIO_CLR(spi_clk); - if (val & bit) GPIO_SET(spi_mosi); - else GPIO_CLR(spi_mosi); - GPIO_SET(spi_clk); - } -} - -void uDisplay::write9_slow(uint8_t val, uint8_t dc) { - GPIO_CLR_SLOW(spi_clk); - if (dc) GPIO_SET_SLOW(spi_mosi); - else GPIO_CLR_SLOW(spi_mosi); - GPIO_SET_SLOW(spi_clk); - - for (uint8_t bit = 0x80; bit; bit >>= 1) { - GPIO_CLR_SLOW(spi_clk); - if (val & bit) GPIO_SET_SLOW(spi_mosi); - else GPIO_CLR_SLOW(spi_mosi); - GPIO_SET_SLOW(spi_clk); - } -} - -void uDisplay::write16(uint16_t val) { - for (uint16_t bit = 0x8000; bit; bit >>= 1) { - GPIO_CLR(spi_clk); - if (val & bit) GPIO_SET(spi_mosi); - else GPIO_CLR(spi_mosi); - GPIO_SET(spi_clk); - } -} - -void uDisplay::write32(uint32_t val) { - for (uint32_t bit = 0x80000000; bit; bit >>= 1) { - GPIO_CLR(spi_clk); - if (val & bit) GPIO_SET(spi_mosi); - else GPIO_CLR(spi_mosi); - GPIO_SET(spi_clk); - } -} - -// ===== RA8876 Specific Functions ===== - -uint8_t uDisplay::writeReg16(uint8_t reg, uint16_t wval) { - hw_write9(reg, 0); - hw_write9(wval, 1); - hw_write9(reg + 1, 0); - hw_write9(wval >> 8, 1); - return 0; -} - -uint8_t uDisplay::readData(void) { - uspi->write(RA8876_DATA_READ); - uint8_t val = uspi->transfer(0); - return val; -} - -uint8_t uDisplay::readStatus(void) { - uspi->write(RA8876_STATUS_READ); - uint8_t val = uspi->transfer(0); - return val; -} \ No newline at end of file diff --git a/lib/lib_display/UDisplay/uDisplay_spi.h b/lib/lib_display/UDisplay/uDisplay_spi.h deleted file mode 100644 index 113832a15..000000000 --- a/lib/lib_display/UDisplay/uDisplay_spi.h +++ /dev/null @@ -1,71 +0,0 @@ -#ifndef UDISPLAY_SPI_LOWLEVEL_H -#define UDISPLAY_SPI_LOWLEVEL_H - -#include "uDisplay_config.h" - -// ===== SPI Platform-Specific Includes ===== -#ifdef ESP32 -#include "soc/spi_reg.h" -#include "soc/spi_struct.h" -#include "esp32-hal-spi.h" -#include "esp32-hal.h" -#include "soc/spi_struct.h" -#endif - -// ===== GPIO Control Macros ===== - -#ifdef ESP8266 -#define PIN_OUT_SET 0x60000304 -#define PIN_OUT_CLEAR 0x60000308 -#define GPIO_SET(A) WRITE_PERI_REG( PIN_OUT_SET, 1 << A) -#define GPIO_CLR(A) WRITE_PERI_REG( PIN_OUT_CLEAR, 1 << A) -#define GPIO_CLR_SLOW(A) digitalWrite(A, LOW) -#define GPIO_SET_SLOW(A) digitalWrite(A, HIGH) -#else // ESP32 -#undef GPIO_SET -#undef GPIO_CLR -#undef GPIO_SET_SLOW -#undef GPIO_CLR_SLOW - -#if CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32P4 -#define GPIO_CLR(A) GPIO.out_w1tc.val = (1 << A) -#define GPIO_SET(A) GPIO.out_w1ts.val = (1 << A) -#else // plain ESP32 -#define GPIO_CLR(A) GPIO.out_w1tc = (1 << A) -#define GPIO_SET(A) GPIO.out_w1ts = (1 << A) -#endif - -#define GPIO_CLR_SLOW(A) digitalWrite(A, LOW) -#define GPIO_SET_SLOW(A) digitalWrite(A, HIGH) -#endif - -// ===== SPI Transaction Control Macros ===== -#define SPI_BEGIN_TRANSACTION if (spi_nr <= 2) beginTransaction(spiSettings); -#define SPI_END_TRANSACTION if (spi_nr <= 2) endTransaction(); - -#define SPI_CS_LOW if (spi_cs >= 0) GPIO_CLR_SLOW(spi_cs); -#define SPI_CS_HIGH if (spi_cs >= 0) GPIO_SET_SLOW(spi_cs); -#define SPI_DC_LOW if (spi_dc >= 0) GPIO_CLR_SLOW(spi_dc); -#define SPI_DC_HIGH if (spi_dc >= 0) GPIO_SET_SLOW(spi_dc); - -// ===== Function Declarations ===== -// These would typically be in the class declaration in uDisplay.h -// but we list them here for reference: - -/* -// Low-Level SPI Write Functions -void hw_write9(uint8_t val, uint8_t dc); -void write8(uint8_t val); -void write8_slow(uint8_t val); -void write9(uint8_t val, uint8_t dc); -void write9_slow(uint8_t val, uint8_t dc); -void write16(uint16_t val); -void write32(uint32_t val); - -// RA8876 Specific Functions -uint8_t writeReg16(uint8_t reg, uint16_t wval); -uint8_t readData(void); -uint8_t readStatus(void); -*/ - -#endif // UDISPLAY_SPI_LOWLEVEL_H \ No newline at end of file diff --git a/lib/lib_display/UDisplay/uDisplay_spi_comm.cpp b/lib/lib_display/UDisplay/uDisplay_spi_comm.cpp deleted file mode 100644 index d04197bcf..000000000 --- a/lib/lib_display/UDisplay/uDisplay_spi_comm.cpp +++ /dev/null @@ -1,158 +0,0 @@ -#include "uDisplay.h" -#include "uDisplay_config.h" -#include "uDisplay_spi.h" // Your SPI header - -// ===== High-Level SPI Communication Functions ===== - -void uDisplay::ulcd_command(uint8_t val) { - if (interface == _UDSP_SPI) { - if (spi_dc < 0) { - if (spi_nr > 2) { - if (spi_nr == 3) { - write9(val, 0); - } else { - write9_slow(val, 0); - } - } else { - hw_write9(val, 0); - } - } else { - SPI_DC_LOW - if (spi_nr > 2) { - if (spi_nr == 3) { - write8(val); - } else { - write8_slow(val); - } - } else { - uspi->write(val); - } - SPI_DC_HIGH - } - return; - } - -#ifdef USE_ESP32_S3 - if (interface == _UDSP_PAR8 || interface == _UDSP_PAR16) { - pb_writeCommand(val, 8); - } -#endif -} - -void uDisplay::ulcd_data8(uint8_t val) { - if (interface == _UDSP_SPI) { - if (spi_dc < 0) { - if (spi_nr > 2) { - if (spi_nr == 3) { - write9(val, 1); - } else { - write9_slow(val, 1); - } - } else { - hw_write9(val, 1); - } - } else { - if (spi_nr > 2) { - if (spi_nr == 3) { - write8(val); - } else { - write8_slow(val); - } - } else { - uspi->write(val); - } - } - return; - } - -#ifdef USE_ESP32_S3 - if (interface == _UDSP_PAR8 || interface == _UDSP_PAR16) { - pb_writeData(val, 8); - } -#endif -} - -void uDisplay::ulcd_data16(uint16_t val) { - if (interface == _UDSP_SPI) { - if (spi_dc < 0) { - if (spi_nr > 2) { - write9(val >> 8, 1); - write9(val, 1); - } else { - hw_write9(val >> 8, 1); - hw_write9(val, 1); - } - } else { - if (spi_nr > 2) { - write16(val); - } else { - uspi->write16(val); - } - } - return; - } - -#ifdef USE_ESP32_S3 - if (interface == _UDSP_PAR8 || interface == _UDSP_PAR16) { - pb_writeData(val, 16); - } -#endif -} - -void uDisplay::ulcd_data32(uint32_t val) { - if (interface == _UDSP_SPI) { - if (spi_dc < 0) { - if (spi_nr > 2) { - write9(val >> 24, 1); - write9(val >> 16, 1); - write9(val >> 8, 1); - write9(val, 1); - } else { - hw_write9(val >> 24, 1); - hw_write9(val >> 16, 1); - hw_write9(val >> 8, 1); - hw_write9(val, 1); - } - } else { - if (spi_nr > 2) { - write32(val); - } else { - uspi->write32(val); - } - } - return; - } - -#ifdef USE_ESP32_S3 - if (interface == _UDSP_PAR8 || interface == _UDSP_PAR16) { - pb_writeData(val, 32); - } -#endif -} - -void uDisplay::ulcd_command_one(uint8_t val) { - if (interface == _UDSP_SPI) { - SPI_BEGIN_TRANSACTION - SPI_CS_LOW - ulcd_command(val); - SPI_CS_HIGH - SPI_END_TRANSACTION - } -} - -void uDisplay::WriteColor(uint16_t color) { - if (col_mode == 18) { - uint8_t r = (color & 0xF800) >> 11; - uint8_t g = (color & 0x07E0) >> 5; - uint8_t b = color & 0x001F; - r = (r * 255) / 31; - g = (g * 255) / 63; - b = (b * 255) / 31; - - ulcd_data8(r); - ulcd_data8(g); - ulcd_data8(b); - } else { - ulcd_data16(color); - } -} \ No newline at end of file diff --git a/lib/lib_display/UDisplay/uDisplay_timing.cpp b/lib/lib_display/UDisplay/uDisplay_timing.cpp index f63170326..15e8e551e 100644 --- a/lib/lib_display/UDisplay/uDisplay_timing.cpp +++ b/lib/lib_display/UDisplay/uDisplay_timing.cpp @@ -24,37 +24,3 @@ void uDisplay::reset_pin(int32_t msl, int32_t msh) { delay(msh); } } -#define UDSP_BUSY_TIMEOUT 3000 - -void uDisplay::delay_sync(int32_t ms) { - uint8_t busy_level = HIGH; - if (lvgl_param.busy_invert) { - busy_level = LOW; - } - uint32_t time = millis(); - if (busy_pin > 0) { - while (digitalRead(busy_pin) == busy_level) { - delay(1); - if ((millis() - time) > UDSP_BUSY_TIMEOUT) { - break; - } - } - } else { - delay(ms); - } -} - -// ===== SPI Transaction Control ===== - -void uDisplay::beginTransaction(SPISettings s) { -#ifdef ESP32 - if (lvgl_param.use_dma) { - dmaWait(); - } -#endif - uspi->beginTransaction(s); -} - -void uDisplay::endTransaction(void) { - uspi->endTransaction(); -} \ No newline at end of file diff --git a/lib/lib_display/UDisplay/uDisplay_touch.cpp b/lib/lib_display/UDisplay/uDisplay_touch.cpp index 6afa12637..ae15e5a68 100644 --- a/lib/lib_display/UDisplay/uDisplay_touch.cpp +++ b/lib/lib_display/UDisplay/uDisplay_touch.cpp @@ -32,10 +32,14 @@ bool uDisplay::utouch_Init(char **name) { attachInterrupt(ut_irq, ut_touch_irq, FALLING); } - if (ut_spi_nr == spi_nr) { - // same as display - ut_spi = uspi; + if (ut_wire) { + // I2C touch - no SPI needed + ut_spi = nullptr; + } else if (spiController && ut_spi_nr == spiController->spi_config.bus_nr) { + // SPI touch using same bus as display + ut_spi = spiController->getSPI(); } else { + // SPI touch using different bus or display doesn't use SPI #ifdef ESP32 ut_spi = SpiBegin(ut_spi_nr); #endif @@ -406,9 +410,13 @@ int16_t uDisplay::ut_execute(uint8_t *ut_code) { break; case UT_GSRT: -#ifdef USE_ESP32_S3 +#ifdef UDISPLAY_I80 { - uint32_t val = get_sr_touch(SIMPLERS_XP, SIMPLERS_XM, SIMPLERS_YP, SIMPLERS_YM); + // Simple resistive touch using I80 data pins + uint32_t val = get_sr_touch(panel_config->i80.data_pins_low[1], // XP + panel_config->i80.cs_pin, // XM + panel_config->i80.dc_pin, // YP + panel_config->i80.data_pins_low[0]); // YM if (val == 0) { return false; } @@ -426,7 +434,7 @@ int16_t uDisplay::ut_execute(uint8_t *ut_code) { } return false; } -#endif // USE_ESP32_S3 +#endif // UDISPLAY_I80 break; case UT_XPT: diff --git a/lib/lib_display/UDisplay/uDisplay_utils.cpp b/lib/lib_display/UDisplay/uDisplay_utils.cpp index fff89e886..da3f64055 100644 --- a/lib/lib_display/UDisplay/uDisplay_utils.cpp +++ b/lib/lib_display/UDisplay/uDisplay_utils.cpp @@ -106,6 +106,6 @@ void uDisplay::TS_RotConvert(int16_t *x, int16_t *y) { // ===== Color Conversion Helper ===== -static inline void lvgl_color_swap(uint16_t *data, uint16_t len) { - for (uint32_t i = 0; i < len; i++) (data[i] = data[i] << 8 | data[i] >> 8); -} \ No newline at end of file +// static inline void lvgl_color_swap(uint16_t *data, uint16_t len) { +// for (uint32_t i = 0; i < len; i++) (data[i] = data[i] << 8 | data[i] >> 8); +// } \ No newline at end of file diff --git a/tasmota/displaydesc/EK79007_P4ev_DSI.ini b/tasmota/displaydesc/EK79007_P4ev_DSI.ini new file mode 100644 index 000000000..5c6a98dba --- /dev/null +++ b/tasmota/displaydesc/EK79007_P4ev_DSI.ini @@ -0,0 +1,48 @@ +:H,EK79007,1024,600,16,DSI,2,-1,26,27,3,2500,52000000,900,0,0 ; DisplayName,Width,Height,BPP,Interface,DSI_Lanes,TE_Pin,Backlight_Pin,Reset_Pin,LDO_Chan,LDO_mV,PixelClock_Hz,LaneSpeed_Mbps,RGB_Order,Endian +:V,160,12,160,10,1,23 ; ^H_FP ^V_FP ^H_BP ^H_SW ^V_SW ^V_BP +:B,60,01 +:I +B2,01,10,00 +80,01,8B,00 +81,01,78,00 +82,01,84,00 +83,01,88,00 +84,01,A8,00 +85,01,E3,00 +86,01,88,00 +11,01,00,78 +29,01,00,32 +:o,28 +:O,29 +:i,20,21 +:D,51 +:UTI,GT911,I1,5d,-1,-1 +RDWM 8140 4 +MV 0 1 +CPR 39 +RTF +MV 1 1 +CPR 31 +RTF +MV 2 1 +CPR 31 +RTF +RT +:UTT +RDW 814E +MV 0 1 +AND 80 +CPR 80 +RTF +RDWM 8150 8 +WRW 814E 00 +RT +:UTX +MV 0 3 +SCL 1024 -1 +RT +:UTY +MV 2 3 +SCL 600 -1 +RT +# diff --git a/tasmota/displaydesc/JD9165_DSI.ini b/tasmota/displaydesc/JD9165_DSI.ini new file mode 100644 index 000000000..f8fdfec73 --- /dev/null +++ b/tasmota/displaydesc/JD9165_DSI.ini @@ -0,0 +1,88 @@ +:H,JD9165,1024,600,16,DSI,2,-1,23,27,3,2500,56000000,750,0,0 ; DisplayName,Width,Height,BPP,Interface,DSI_Lanes,TE_Pin,Backlight_Pin,Reset_Pin,LDO_Chan,LDO_mV,PixelClock_Hz,LaneSpeed_Mbps,RGB_Order,Endian +:V,160,12,160,40,10,23 ; ^H_FP ^V_FP ^H_BP ^H_SW ^V_SW ^V_BP +:B,60,01 +:I +30,01,00,00 +F7,04,49,61,02,00,00 +30,01,01,00 +04,01,0C,00 +05,01,00,00 +06,01,00,00 +0B,01,11,00 +17,01,00,00 +20,01,04,00 +1F,01,05,00 +23,01,00,00 +25,01,19,00 +28,01,18,00 +29,01,04,00 +2A,01,01,00 +2B,01,04,00 +2C,01,01,00 +30,01,02,00 +01,01,22,00 +03,01,12,00 +04,01,00,00 +05,01,64,00 +0A,01,08,00 +0B,0B,0A,1A,0B,0D,0D,11,10,06,08,1F,1D,00 +0C,0B,0D,0D,0D,0D,0D,0D,0D,0D,0D,0D,0D,00 +0D,0B,16,1B,0B,0D,0D,11,10,07,09,1E,1C,00 +0E,0B,0D,0D,0D,0D,0D,0D,0D,0D,0D,0D,0D,00 +0F,0B,16,1B,0D,0B,0D,11,10,1C,1E,09,07,00 +10,0B,0D,0D,0D,0D,0D,0D,0D,0D,0D,0D,0D,00 +11,0B,0A,1A,0D,0B,0D,11,10,1D,1F,08,06,00 +12,0B,0D,0D,0D,0D,0D,0D,0D,0D,0D,0D,0D,00 +14,04,00,00,11,11,00 +18,01,99,00 +30,01,06,00 +12,0E,36,2C,2E,3C,38,35,35,32,2E,1D,2B,21,16,29,00 +13,0E,36,2C,2E,3C,38,35,35,32,2E,1D,2B,21,16,29,00 +30,01,0A,00 +02,01,4F,00 +0B,01,40,00 +12,01,3E,00 +13,01,78,00 +30,01,0D,00 +0D,01,04,00 +10,01,0C,00 +11,01,0C,00 +12,01,0C,00 +13,01,0C,00 +30,01,00,00 +11,01,00,78 +29,01,00,32 +:o,28 +:O,29 +:i,20,21 +:D,51 +:UTI,GT911,I1,5d,22,21 +RDWM 8140 4 +MV 0 1 +CPR 39 +RTF +MV 1 1 +CPR 31 +RTF +MV 2 1 +CPR 31 +RTF +RT +:UTT +RDW 814E +MV 0 1 +AND 80 +CPR 80 +RTF +RDWM 8150 8 +WRW 814E 00 +RT +:UTX +MV 0 3 +SCL 0 1.281 +RT +:UTY +MV 2 3 +SCL 0 1.2525 +RT +# \ No newline at end of file