Tasmota/lib/lib_display/UDisplay/src/uDisplay_SPI_panel.cpp
gemu 9c3588256a
fix udisplay bpanel with spi displays (#24278)
Co-authored-by: Gerhard Mutz <gerhardmutz1@imac.local>
2025-12-29 17:37:59 +01:00

367 lines
11 KiB
C++

// WIP
// ======================================================
// uDisplay_spi_panel.cpp - SPI LCD Panel Implementation
// ======================================================
#include "uDisplay_SPI_panel.h"
#include <Arduino.h>
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 false; //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;
}