367 lines
11 KiB
C++
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;
|
|
} |