1428 lines
51 KiB
C++
1428 lines
51 KiB
C++
/*
|
|
uDisplay.cpp - universal display driver support for Tasmota
|
|
|
|
Copyright (C) 2021 Gerhard Mutz and Theo Arends
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <Arduino.h>
|
|
#include "uDisplay.h"
|
|
#include "uDisplay_config.h"
|
|
|
|
#include "tasmota_options.h"
|
|
|
|
|
|
// #define UDSP_DEBUG
|
|
|
|
#ifndef UDSP_LBSIZE
|
|
#define UDSP_LBSIZE 256
|
|
#endif
|
|
|
|
uDisplay::~uDisplay(void) {
|
|
#ifdef UDSP_DEBUG
|
|
AddLog(LOG_LEVEL_DEBUG, "UDisplay: dealloc");
|
|
#endif
|
|
if (frame_buffer) {
|
|
free(frame_buffer);
|
|
}
|
|
|
|
// Free panel config union
|
|
if (panel_config) {
|
|
free(panel_config);
|
|
panel_config = nullptr;
|
|
}
|
|
|
|
#ifdef USE_UNIVERSAL_TOUCH
|
|
if (ut_init_code) {
|
|
free(ut_init_code);
|
|
}
|
|
if (ut_touch_code) {
|
|
free(ut_touch_code);
|
|
}
|
|
if (ut_getx_code) {
|
|
free(ut_getx_code);
|
|
}
|
|
if (ut_gety_code) {
|
|
free(ut_gety_code);
|
|
}
|
|
#endif // USE_UNIVERSAL_TOUCH
|
|
}
|
|
|
|
uDisplay::uDisplay(char *lp) : Renderer(800, 600) {
|
|
// analyse decriptor
|
|
pwr_cbp = 0;
|
|
dim_cbp = 0;
|
|
framebuffer = 0;
|
|
col_mode = 16;
|
|
sa_mode = 16;
|
|
saw_3 = 0xff;
|
|
dim_op = 0xff;
|
|
bpmode = 0;
|
|
dsp_off = 0xff;
|
|
dsp_on = 0xff;
|
|
// busy_pin = -1; // MOVED to EPDPanelConfig.busy_pin
|
|
spec_init = -1;
|
|
ep_mode = 0;
|
|
fg_col = 1;
|
|
bg_col = 0;
|
|
splash_font = -1;
|
|
rotmap_xmin = -1;
|
|
bpanel = -1;
|
|
allcmd_mode = 0;
|
|
startline = 0xA1;
|
|
uint8_t section = 0;
|
|
dsp_ncmds = 0;
|
|
lut_num = 0;
|
|
lvgl_param.data = 0;
|
|
lvgl_param.flushlines = 40;
|
|
rot_t[0] = 0;
|
|
rot_t[1] = 1;
|
|
rot_t[2] = 2;
|
|
rot_t[3] = 3;
|
|
interface = 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) {
|
|
|
|
uint16_t llen = strlen_ln(lp);
|
|
strncpy(linebuff, lp, llen);
|
|
linebuff[llen] = 0;
|
|
lp += llen;
|
|
char *lp1 = linebuff;
|
|
|
|
if (*lp1 == '#') break;
|
|
if (*lp1 == '\n') lp1++;
|
|
while (*lp1 == ' ') lp1++;
|
|
//AddLog(LOG_LEVEL_DEBUG, ">> %s\n",lp1);
|
|
if (*lp1 != ';') {
|
|
// check ids:
|
|
if (*lp1 == ':') {
|
|
// id line
|
|
lp1++;
|
|
section = *lp1++;
|
|
if (section == 'I') {
|
|
|
|
if (*lp1 == 'C') {
|
|
allcmd_mode = 1;
|
|
lp1++;
|
|
}
|
|
|
|
if (*lp1 == 'S') {
|
|
// special case RGB with software SPI init clk,mosi,cs,reset
|
|
lp1++;
|
|
if (interface == _UDSP_RGB) {
|
|
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);
|
|
|
|
spiController = new SPIController(spi_cfg);
|
|
// spiSettings = spiController->getSPISettings();
|
|
// busy_pin = spi_cfg.miso; // update for timing
|
|
|
|
if (reset >= 0) {
|
|
pinMode(reset, OUTPUT);
|
|
digitalWrite(reset, HIGH);
|
|
delay(50);
|
|
reset_pin(50, 200);
|
|
}
|
|
#ifdef UDSP_DEBUG
|
|
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') {
|
|
// pecial case RGB with i2c init, bus nr, i2c addr
|
|
lp1++;
|
|
if (interface == _UDSP_RGB) {
|
|
// collect line and send directly
|
|
lp1++;
|
|
wire_n = next_val(&lp1);
|
|
i2caddr = next_hex(&lp1);
|
|
#ifdef UDSP_DEBUG
|
|
AddLog(LOG_LEVEL_DEBUG, "UDisplay: I2C_INIT bus:%d addr:%02x", wire_n, i2caddr);
|
|
#endif
|
|
if (wire_n == 1) {
|
|
wire = &Wire;
|
|
} else {
|
|
#if SOC_HP_I2C_NUM > 1
|
|
wire = &Wire1;
|
|
#else
|
|
wire = &Wire;
|
|
#endif
|
|
}
|
|
spec_init = _UDSP_I2C;
|
|
}
|
|
}
|
|
} else if (section == 'L') {
|
|
if (*lp1 >= '1' && *lp1 <= '5') {
|
|
lut_num = (*lp1 & 0x07);
|
|
lp1 += 2;
|
|
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++;
|
|
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++;
|
|
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++;
|
|
|
|
}
|
|
if (*lp1 && *lp1 != ':' && *lp1 != '\n' && *lp1 != ' ') { // Add space char
|
|
switch (section) {
|
|
case 'H':
|
|
// header line
|
|
// SD1306,128,64,1,I2C,5a,*,*,*
|
|
str2c(&lp1, dname, sizeof(dname));
|
|
char ibuff[16];
|
|
gxs = next_val(&lp1);
|
|
setwidth(gxs);
|
|
gys = next_val(&lp1);
|
|
setheight(gys);
|
|
disp_bpp = next_val(&lp1);
|
|
bpp = abs(disp_bpp);
|
|
if (bpp == 1) {
|
|
col_type = uCOLOR_BW;
|
|
} else {
|
|
col_type = uCOLOR_COLOR;
|
|
if (bpp == 16) {
|
|
fg_col = GetColorFromIndex(fg_col);
|
|
bg_col = GetColorFromIndex(bg_col);
|
|
}
|
|
}
|
|
str2c(&lp1, ibuff, sizeof(ibuff));
|
|
if (!strncmp(ibuff, "I2C", 3)) {
|
|
interface = _UDSP_I2C;
|
|
wire_n = 0;
|
|
if (!strncmp(ibuff, "I2C2", 4)) {
|
|
wire_n = 1;
|
|
}
|
|
i2caddr = next_hex(&lp1);
|
|
i2c_scl = next_val(&lp1);
|
|
i2c_sda = next_val(&lp1);
|
|
reset = next_val(&lp1);
|
|
section = 0;
|
|
} else if (!strncmp(ibuff, "SPI", 3)) {
|
|
interface = _UDSP_SPI;
|
|
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_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)) {
|
|
#if defined(UDISPLAY_I80)
|
|
uint8_t bus = next_val(&lp1);
|
|
if (bus == 8) {
|
|
interface = _UDSP_PAR8;
|
|
} else {
|
|
interface = _UDSP_PAR16;
|
|
}
|
|
reset = 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 ++) {
|
|
panel_config->i80.data_pins_low[cnt] = next_val(&lp1);
|
|
}
|
|
|
|
if (interface == _UDSP_PAR16) {
|
|
for (uint32_t cnt = 0; cnt < 8; cnt ++) {
|
|
panel_config->i80.data_pins_high[cnt] = next_val(&lp1);
|
|
}
|
|
}
|
|
spi_speed = next_val(&lp1);
|
|
#endif // UDISPLAY_I80
|
|
section = 0;
|
|
} else if (!strncmp(ibuff, "RGB", 3)) {
|
|
#ifdef SOC_LCD_RGB_SUPPORTED
|
|
interface = _UDSP_RGB;
|
|
// 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);
|
|
// 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++) {
|
|
panel_config->rgb.data_gpio_nums[cnt] = next_val(&lp1);
|
|
}
|
|
spi_speed = next_val(&lp1);
|
|
#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);
|
|
splash_size = next_val(&lp1);
|
|
fg_col = next_val(&lp1);
|
|
bg_col = next_val(&lp1);
|
|
if (bpp == 16) {
|
|
fg_col = GetColorFromIndex(fg_col);
|
|
bg_col = GetColorFromIndex(bg_col);
|
|
}
|
|
splash_xp = next_val(&lp1);
|
|
splash_yp = next_val(&lp1);
|
|
break;
|
|
case 'I':
|
|
// init data
|
|
if (interface == _UDSP_RGB && spec_init > 0) {
|
|
// special case RGB with SPI or I2C init
|
|
// collect line and send directly
|
|
dsp_ncmds = 0;
|
|
while (1) {
|
|
if (dsp_ncmds >= sizeof(dsp_cmds)) break;
|
|
if (!str2c(&lp1, ibuff, sizeof(ibuff))) {
|
|
dsp_cmds[dsp_ncmds++] = strtol(ibuff, 0, 16);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if (spec_init == _UDSP_SPI) {
|
|
interface = spec_init;
|
|
send_spi_icmds(dsp_ncmds);
|
|
} else {
|
|
if (dsp_ncmds == 2) {
|
|
wire->beginTransmission(i2caddr);
|
|
wire->write(dsp_cmds[0]);
|
|
wire->write(dsp_cmds[1]);
|
|
wire->endTransmission();
|
|
#ifdef UDSP_DEBUG
|
|
AddLog(LOG_LEVEL_DEBUG, "UDisplay: reg=%02x val=%02x", dsp_cmds[0], dsp_cmds[1]);
|
|
#endif
|
|
} else {
|
|
delay(dsp_cmds[0]);
|
|
#ifdef UDSP_DEBUG
|
|
AddLog(LOG_LEVEL_DEBUG, "UDisplay: delay=%d ms", dsp_cmds[0]);
|
|
#endif
|
|
}
|
|
}
|
|
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);
|
|
if (!str2c(&lp1, ibuff, sizeof(ibuff))) {
|
|
dsp_cmds[dsp_ncmds++] = strtol(ibuff, 0, 16);
|
|
}
|
|
} else {
|
|
while (1) {
|
|
if (dsp_ncmds >= sizeof(dsp_cmds)) break;
|
|
if (!str2c(&lp1, ibuff, sizeof(ibuff))) {
|
|
dsp_cmds[dsp_ncmds++] = strtol(ibuff, 0, 16);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case 'f':
|
|
// epaper full update cmds
|
|
if (!panel_config->epd.epcoffs_full) {
|
|
panel_config->epd.epcoffs_full = dsp_ncmds;
|
|
panel_config->epd.epc_full_cnt = 0;
|
|
}
|
|
while (1) {
|
|
if (panel_config->epd.epc_full_cnt >= sizeof(dsp_cmds)) break;
|
|
if (!str2c(&lp1, ibuff, sizeof(ibuff))) {
|
|
dsp_cmds[panel_config->epd.epcoffs_full + panel_config->epd.epc_full_cnt++] = strtol(ibuff, 0, 16);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case 'p':
|
|
// epaper partial update cmds
|
|
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 (panel_config->epd.epc_part_cnt >= sizeof(dsp_cmds)) break;
|
|
if (!str2c(&lp1, ibuff, sizeof(ibuff))) {
|
|
dsp_cmds[panel_config->epd.epcoffs_part + panel_config->epd.epc_part_cnt++] = strtol(ibuff, 0, 16);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'V':
|
|
#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;
|
|
case 'o':
|
|
dsp_off = next_hex(&lp1);
|
|
break;
|
|
|
|
case 'O':
|
|
dsp_on = next_hex(&lp1);
|
|
break;
|
|
case 'R':
|
|
// 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) {
|
|
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) {
|
|
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) {
|
|
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) {
|
|
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) {
|
|
// 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);
|
|
sa_mode = next_val(&lp1);
|
|
}
|
|
break;
|
|
case 'a':
|
|
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); // 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 (!panel_config->epd.lut_full_data) {
|
|
break;
|
|
}
|
|
while (1) {
|
|
if (!str2c(&lp1, ibuff, sizeof(ibuff))) {
|
|
panel_config->epd.lut_full_data[panel_config->epd.lutfsize++] = strtol(ibuff, 0, 16);
|
|
} else {
|
|
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 (!panel_config->epd.lut_array_data[index]) {
|
|
break;
|
|
}
|
|
while (1) {
|
|
if (!str2c(&lp1, ibuff, sizeof(ibuff))) {
|
|
panel_config->epd.lut_array_data[index][panel_config->epd.lut_cnt_data[index]++] = strtol(ibuff, 0, 16);
|
|
} else {
|
|
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 (!panel_config->epd.lut_partial_data) {
|
|
break;
|
|
}
|
|
while (1) {
|
|
if (!str2c(&lp1, ibuff, sizeof(ibuff))) {
|
|
panel_config->epd.lut_partial_data[panel_config->epd.lutpsize++] = strtol(ibuff, 0, 16);
|
|
} else {
|
|
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':
|
|
// 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;
|
|
case 'M':
|
|
rotmap_xmin = next_val(&lp1);
|
|
rotmap_xmax = next_val(&lp1);
|
|
rotmap_ymin = next_val(&lp1);
|
|
rotmap_ymax = next_val(&lp1);
|
|
break;
|
|
case 'b':
|
|
bpmode = next_val(&lp1);
|
|
break;
|
|
#ifdef USE_UNIVERSAL_TOUCH
|
|
case 'U':
|
|
if (!strncmp(lp1, "TI", 2)) {
|
|
// init
|
|
ut_wire = 0;
|
|
ut_reset = -1;
|
|
ut_irq = -1;
|
|
lp1 += 3;
|
|
str2c(&lp1, ut_name, sizeof(ut_name));
|
|
if (*lp1 == 'I') {
|
|
// i2c mode
|
|
lp1++;
|
|
uint8_t ut_mode = *lp1 & 0xf;
|
|
lp1 += 2;
|
|
ut_i2caddr = next_hex(&lp1);
|
|
ut_reset = next_val(&lp1);
|
|
ut_irq = next_val(&lp1);
|
|
|
|
if (ut_mode == 1) {
|
|
ut_wire = &Wire;
|
|
} else {
|
|
#if SOC_HP_I2C_NUM > 1
|
|
ut_wire = &Wire1;
|
|
#else
|
|
ut_wire = &Wire;
|
|
#endif
|
|
}
|
|
} else if (*lp1 == 'S') {
|
|
// spi mode
|
|
lp1++;
|
|
ut_spi_nr = *lp1 & 0xf;
|
|
lp1 += 2;
|
|
ut_spi_cs = next_val(&lp1);
|
|
ut_reset = next_val(&lp1);
|
|
ut_irq = next_val(&lp1);
|
|
pinMode(ut_spi_cs, OUTPUT);
|
|
digitalWrite(ut_spi_cs, HIGH);
|
|
ut_spiSettings = SPISettings(2000000, MSBFIRST, SPI_MODE0);
|
|
} else {
|
|
// simple resistive touch
|
|
lp1++;
|
|
}
|
|
ut_trans(&lp, &ut_init_code);
|
|
} else if (!strncmp(lp1, "TT", 2)) {
|
|
lp1 += 2;
|
|
// touch
|
|
ut_trans(&lp, &ut_touch_code);
|
|
} else if (!strncmp(lp1, "TX", 2)) {
|
|
lp1 += 2;
|
|
// get x
|
|
ut_trans(&lp, &ut_getx_code);
|
|
} else if (!strncmp(lp1, "TY", 2)) {
|
|
lp1 += 2;
|
|
// get y
|
|
ut_trans(&lp, &ut_gety_code);
|
|
}
|
|
break;
|
|
#endif // USE_UNIVERSAL_TOUCH
|
|
}
|
|
}
|
|
}
|
|
nextline:
|
|
if (*lp == '\n' || *lp == ' ') { // Add space char
|
|
lp++;
|
|
} else {
|
|
char *lp1;
|
|
lp1 = strchr(lp, '\n');
|
|
if (!lp1) {
|
|
lp1 = strchr(lp, ' ');
|
|
if (!lp1) {
|
|
break;
|
|
}
|
|
}
|
|
lp = lp1 + 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 (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;
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef USE_ESP32_S3
|
|
void UfsCheckSDCardInit(void);
|
|
|
|
if (spec_init == _UDSP_SPI) {
|
|
// special case, assuming sd card and display on same spi bus
|
|
// end spi in case it was running
|
|
SPI.end();
|
|
// reininit SD card
|
|
UfsCheckSDCardInit();
|
|
}
|
|
#endif
|
|
|
|
#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",
|
|
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, 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 && 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 && panel_config) {
|
|
AddLog(LOG_LEVEL_DEBUG, "UDisplay: LUT_SIZE 1:%d 2:%d 3:%d 4:%d 5:%d",
|
|
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",
|
|
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) {
|
|
AddLog(LOG_LEVEL_DEBUG, "UDisplay: Addr:%02x SCL:%d SDA:%d", i2caddr, i2c_scl, i2c_sda);
|
|
|
|
AddLog(LOG_LEVEL_DEBUG, "UDisplay: SPA:%x pa_sta:%x pa_end:%x SCA:%x ca_sta:%x ca_end:%x WRA:%x",
|
|
saw_1, i2c_page_start, i2c_page_end, saw_2, i2c_col_start, i2c_col_end, saw_3);
|
|
}
|
|
|
|
if (interface == _UDSP_PAR8 || interface == _UDSP_PAR16) {
|
|
#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, 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, 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, panel_config->i80.data_pins_high[cnt]);
|
|
}
|
|
}
|
|
AddLog(LOG_LEVEL_DEBUG, "UDisplay: par freq:%d", spi_speed);
|
|
#endif // UDISPLAY_I80
|
|
|
|
}
|
|
if (interface == _UDSP_RGB) {
|
|
#ifdef SOC_LCD_RGB_SUPPORTED
|
|
|
|
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, 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, panel_config->rgb.data_gpio_nums[cnt + 8]);
|
|
}
|
|
|
|
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 // SOC_LCD_RGB_SUPPORTED
|
|
}
|
|
#endif
|
|
|
|
#ifdef UDSP_DEBUG
|
|
AddLog(LOG_LEVEL_DEBUG, "UDisplay: Dsp class init complete");
|
|
#endif
|
|
}
|
|
|
|
// special init for GC displays
|
|
void uDisplay::send_spi_icmds(uint16_t cmd_size) {
|
|
uint16_t index = 0;
|
|
uint16_t cmd_offset = 0;
|
|
|
|
|
|
#ifdef UDSP_DEBUG
|
|
AddLog(LOG_LEVEL_DEBUG, "UDisplay: start send icmd table");
|
|
#endif
|
|
while (1) {
|
|
uint8_t iob;
|
|
spiController->csLow();
|
|
iob = dsp_cmds[cmd_offset++];
|
|
index++;
|
|
spiController->writeCommand(iob);
|
|
uint8_t args = dsp_cmds[cmd_offset++];
|
|
index++;
|
|
#ifdef UDSP_DEBUG
|
|
AddLog(LOG_LEVEL_DEBUG, "UDisplay: cmd, args %02x, %d", iob, args & 0x7f);
|
|
#endif
|
|
for (uint32_t cnt = 0; cnt < (args & 0x7f); cnt++) {
|
|
iob = dsp_cmds[cmd_offset++];
|
|
index++;
|
|
#ifdef UDSP_DEBUG
|
|
AddLog(LOG_LEVEL_DEBUG, "UDisplay: %02x", iob);
|
|
#endif
|
|
spiController->writeData8(iob);
|
|
}
|
|
spiController->csHigh();
|
|
if (args & 0x80) { // delay after the command
|
|
delay_arg(args);
|
|
}
|
|
if (index >= cmd_size) break;
|
|
}
|
|
#ifdef UDSP_DEBUG
|
|
AddLog(LOG_LEVEL_DEBUG, "UDisplay: end send icmd table");
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
|
|
void uDisplay::send_spi_cmds(uint16_t cmd_offset, uint16_t cmd_size) {
|
|
uint16_t index = 0;
|
|
#ifdef UDSP_DEBUG
|
|
AddLog(LOG_LEVEL_DEBUG, "UDisplay: start send cmd table");
|
|
#endif
|
|
while (1) {
|
|
uint8_t iob;
|
|
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<EPDPanel*>(universal_panel);
|
|
|
|
uint8_t args = dsp_cmds[cmd_offset++];
|
|
index++;
|
|
#ifdef UDSP_DEBUG
|
|
AddLog(LOG_LEVEL_DEBUG, "UDisplay: cmd, args %02x, %d", iob, args & 0x1f);
|
|
#endif
|
|
switch (iob) {
|
|
case EP_RESET:
|
|
if (args & 1) {
|
|
iob = dsp_cmds[cmd_offset++];
|
|
index++;
|
|
}
|
|
reset_pin(iob, iob);
|
|
break;
|
|
case EP_LUT_FULL:
|
|
epd->setLut(epd->cfg.lut_full_data, epd->cfg.lutfsize);
|
|
epd->setUpdateMode(DISPLAY_INIT_FULL);
|
|
break;
|
|
case EP_LUT_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++;
|
|
}
|
|
epd->delay_sync(iob * 10);
|
|
break;
|
|
case EP_SET_MEM_AREA:
|
|
epd->setMemoryArea(0, 0, gxs - 1, gys - 1);
|
|
break;
|
|
case EP_SET_MEM_PTR:
|
|
epd->setMemoryPointer(0, 0);
|
|
break;
|
|
case EP_SEND_DATA:
|
|
epd->sendEPData();
|
|
break;
|
|
case EP_CLR_FRAME:
|
|
epd->clearFrameMemory(0xFF);
|
|
break;
|
|
case EP_SEND_FRAME:
|
|
epd->setFrameMemory(framebuffer);
|
|
break;
|
|
case EP_BREAK_RR_EQU:
|
|
if (args & 1) {
|
|
iob = dsp_cmds[cmd_offset++];
|
|
index++;
|
|
if (iob == ESP_ResetInfoReason()) {
|
|
epd->setUpdateMode(DISPLAY_INIT_PARTIAL);
|
|
goto exit;
|
|
}
|
|
}
|
|
break;
|
|
case EP_BREAK_RR_NEQ:
|
|
if (args & 1) {
|
|
iob = dsp_cmds[cmd_offset++];
|
|
index++;
|
|
if (iob != ESP_ResetInfoReason()) {
|
|
epd->setUpdateMode(DISPLAY_INIT_PARTIAL);
|
|
goto exit;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
#ifdef UDSP_DEBUG
|
|
if (args & 1) {
|
|
AddLog(LOG_LEVEL_DEBUG, "UDisplay: %02x", iob);
|
|
}
|
|
#endif
|
|
if (args & 0x80) { // delay after the command
|
|
delay_arg(args);
|
|
}
|
|
} else {
|
|
if (spiController->spi_config.dc == -2) {
|
|
// pseudo opcodes
|
|
switch (iob) {
|
|
case UDSP_WRITE_16:
|
|
break;
|
|
case UDSP_READ_DATA:
|
|
break;
|
|
case UDSP_READ_STATUS:
|
|
break;
|
|
}
|
|
}
|
|
spiController->writeCommand(iob);
|
|
uint8_t args = dsp_cmds[cmd_offset++];
|
|
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[cmd_offset++];
|
|
index++;
|
|
#ifdef UDSP_DEBUG
|
|
AddLog(LOG_LEVEL_DEBUG, "%02x ", iob );
|
|
#endif
|
|
if (!allcmd_mode) {
|
|
spiController->writeData8(iob);
|
|
} else {
|
|
spiController->writeCommand(iob);
|
|
}
|
|
}
|
|
spiController->csHigh();
|
|
if (args & 0x80) { // delay after the command
|
|
delay_arg(args);
|
|
}
|
|
}
|
|
if (index >= cmd_size) break;
|
|
}
|
|
|
|
exit:
|
|
#ifdef UDSP_DEBUG
|
|
AddLog(LOG_LEVEL_DEBUG, "UDisplay: end send cmd table");
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
Renderer *uDisplay::Init(void) {
|
|
if (!interface) { // no valid configuration, abort
|
|
#ifdef UDSP_DEBUG
|
|
AddLog(LOG_LEVEL_INFO, "UDisplay: Dsp Init no valid configuration");
|
|
#endif
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef UDSP_DEBUG
|
|
AddLog(LOG_LEVEL_DEBUG, "UDisplay: Dsp Init 1 start");
|
|
#endif
|
|
|
|
// for any bpp below native 16 bits, we allocate a local framebuffer to copy into
|
|
if (ep_mode || bpp < 16) {
|
|
if (framebuffer) free(framebuffer);
|
|
#ifdef ESP8266
|
|
framebuffer = (uint8_t*)calloc((gxs * gys * bpp) / 8, 1);
|
|
#else
|
|
if (UsePSRAM()) {
|
|
framebuffer = (uint8_t*)heap_caps_malloc((gxs * gys * bpp) / 8, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
|
} else {
|
|
framebuffer = (uint8_t*)calloc((gxs * gys * bpp) / 8, 1);
|
|
}
|
|
#endif // ESP8266
|
|
}
|
|
frame_buffer = framebuffer;
|
|
|
|
if (interface == _UDSP_I2C) {
|
|
if (wire_n == 0) {
|
|
wire = &Wire;
|
|
}
|
|
#if SOC_HP_I2C_NUM > 1
|
|
if (wire_n == 1) {
|
|
wire = &Wire1;
|
|
}
|
|
#endif // ESP32
|
|
|
|
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 (bpanel >= 0) {
|
|
#ifdef ESP32
|
|
analogWrite(bpanel, 32);
|
|
#else
|
|
pinMode(bpanel, OUTPUT);
|
|
digitalWrite(bpanel, HIGH);
|
|
#endif // ESP32
|
|
}
|
|
// spiController->beginTransaction();
|
|
|
|
if (reset >= 0) {
|
|
pinMode(reset, OUTPUT);
|
|
digitalWrite(reset, HIGH);
|
|
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<EPDPanel*>(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
|
|
}
|
|
|
|
// spiController->endTransaction();
|
|
|
|
// 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) {
|
|
if (!UsePSRAM()) { // RGB is not supported on S3 without PSRAM
|
|
#ifdef UDSP_DEBUG
|
|
AddLog(LOG_LEVEL_INFO, "UDisplay: Dsp RGB requires PSRAM, abort");
|
|
#endif
|
|
return NULL;
|
|
}
|
|
|
|
if (bpanel >= 0) {
|
|
analogWrite(bpanel, 32);
|
|
}
|
|
|
|
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;
|
|
|
|
// 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;
|
|
}
|
|
|
|
panel_config->rgb.disp_gpio_num = GPIO_NUM_NC;
|
|
|
|
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
|
|
|
|
universal_panel = new RGBPanel(&panel_config->rgb);
|
|
rgb_fb = universal_panel->framebuffer;
|
|
// super->setDrawMode();
|
|
|
|
}
|
|
#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 UDISPLAY_I80
|
|
// Reset handling
|
|
if (reset >= 0) {
|
|
pinMode(reset, OUTPUT);
|
|
digitalWrite(reset, HIGH);
|
|
delay(50);
|
|
reset_pin(50, 200);
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
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);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if(!universal_panel){
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef UDSP_DEBUG
|
|
AddLog(LOG_LEVEL_DEBUG, "UDisplay: Dsp Init 1 complete");
|
|
#endif
|
|
return this;
|
|
}
|
|
|
|
void uDisplay::DisplayInit(int8_t p, int8_t size, int8_t rot, int8_t font) {
|
|
if (p != DISPLAY_INIT_MODE && ep_mode) {
|
|
if (p == DISPLAY_INIT_PARTIAL) {
|
|
if (universal_panel) {
|
|
EPDPanel* epd = static_cast<EPDPanel*>(universal_panel);
|
|
epd->setUpdateMode(DISPLAY_INIT_PARTIAL);
|
|
if (epd->cfg.lutpsize) {
|
|
#ifdef UDSP_DEBUG
|
|
AddLog(LOG_LEVEL_DEBUG, "init partial epaper mode");
|
|
#endif
|
|
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 (universal_panel) {
|
|
EPDPanel* epd = static_cast<EPDPanel*>(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);
|
|
}
|
|
return;
|
|
}
|
|
} else {
|
|
setRotation(rot);
|
|
invertDisplay(false);
|
|
setTextWrap(false);
|
|
cp437(true);
|
|
setTextFont(font);
|
|
setTextSize(size);
|
|
setTextColor(fg_col, bg_col);
|
|
setCursor(0,0);
|
|
if (splash_font >= 0) {
|
|
fillScreen(bg_col);
|
|
Updateframe();
|
|
}
|
|
|
|
#ifdef UDSP_DEBUG
|
|
AddLog(LOG_LEVEL_DEBUG, "Dsp Init 2 complete");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#define WIRE_MAX 32 |