Tasmota/lib/lib_display/UDisplay/uDisplay_epd.cpp
2025-10-12 17:15:02 +02:00

362 lines
10 KiB
C++

#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
}