2025-04-11 22:23:47 +02:00
|
|
|
#ifdef ST7789
|
|
|
|
|
|
2025-04-10 16:24:17 +02:00
|
|
|
#include "ST7789Display.h"
|
|
|
|
|
|
2026-08-08 01:06:24 +02:00
|
|
|
#ifdef OLED_MISC_FIXED_FONT
|
|
|
|
|
#include "MiscFixedFont.h"
|
|
|
|
|
#include "LemonIcons.h"
|
|
|
|
|
#endif
|
|
|
|
|
|
2025-04-30 11:09:43 +02:00
|
|
|
#ifndef X_OFFSET
|
2025-05-04 21:51:58 -07:00
|
|
|
#define X_OFFSET 0 // No offset needed for landscape
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#ifndef Y_OFFSET
|
|
|
|
|
#define Y_OFFSET 1 // Vertical offset to prevent top row cutoff
|
2025-04-30 11:09:43 +02:00
|
|
|
#endif
|
|
|
|
|
|
2026-01-29 08:02:26 +11:00
|
|
|
#ifdef HELTEC_VISION_MASTER_T190
|
|
|
|
|
#define SCALE_X 2.5f // 320 / 128
|
|
|
|
|
#define SCALE_Y 2.65625f // 170 / 64
|
2026-08-08 01:06:24 +02:00
|
|
|
#elif defined(CARDPUTER_ADV)
|
|
|
|
|
#define SCALE_X 1.875f // 240 / 128
|
|
|
|
|
#define SCALE_Y 2.109375f // 135 / 64
|
2026-01-29 08:02:26 +11:00
|
|
|
#else
|
|
|
|
|
#define SCALE_X 1.875f // 240 / 128
|
|
|
|
|
#define SCALE_Y 2.109375f // 135 / 64
|
|
|
|
|
#endif
|
2025-05-05 15:54:31 +10:00
|
|
|
|
2026-07-13 17:40:39 +12:00
|
|
|
#ifdef DISPLAY_SCALE_X
|
2026-08-11 17:25:35 +02:00
|
|
|
#undef SCALE_X
|
2026-07-13 17:40:39 +12:00
|
|
|
#define SCALE_X DISPLAY_SCALE_X
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#ifdef DISPLAY_SCALE_Y
|
2026-08-11 17:25:35 +02:00
|
|
|
#undef SCALE_Y
|
2026-07-13 17:40:39 +12:00
|
|
|
#define SCALE_Y DISPLAY_SCALE_Y
|
|
|
|
|
#endif
|
|
|
|
|
|
2026-08-08 01:06:24 +02:00
|
|
|
#ifdef OLED_MISC_FIXED_FONT
|
|
|
|
|
// MiscFixedFont.h/LemonIcons.h store each glyph's rows bit-packed
|
|
|
|
|
// *continuously* (Adafruit_GFX's native GFXfont convention -- no per-row byte
|
|
|
|
|
// padding), but drawXbm() below expects classic XBM packing (each row padded
|
|
|
|
|
// to a whole byte). Re-pack into a small stack buffer so the glyph can be
|
|
|
|
|
// blitted through drawXbm() -- which already implements the correct
|
|
|
|
|
// logical->physical scaling for this panel's non-integer SCALE_X/Y (via
|
|
|
|
|
// per-pixel boundary differences), instead of duplicating that math here.
|
|
|
|
|
static bool repackGlyphToXbm(const uint8_t* bitmapProgmem, uint16_t byteOffset, uint8_t w, uint8_t h, uint8_t* out, uint8_t outCap) {
|
|
|
|
|
uint8_t widthInBytes = (w + 7) / 8;
|
|
|
|
|
uint16_t needed = (uint16_t)widthInBytes * h;
|
|
|
|
|
if (needed == 0 || needed > outCap) return false;
|
|
|
|
|
memset(out, 0, needed);
|
|
|
|
|
uint16_t srcBit = 0;
|
|
|
|
|
for (uint8_t row = 0; row < h; row++) {
|
|
|
|
|
for (uint8_t col = 0; col < w; col++) {
|
|
|
|
|
uint16_t byteIdx = byteOffset + (srcBit >> 3);
|
|
|
|
|
uint8_t bitMask = 0x80 >> (srcBit & 7);
|
|
|
|
|
if (pgm_read_byte(bitmapProgmem + byteIdx) & bitMask) {
|
|
|
|
|
out[row * widthInBytes + (col >> 3)] |= (0x80 >> (col & 7));
|
|
|
|
|
}
|
|
|
|
|
srcBit++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t ST7789Display::glyphXAdvance(uint32_t cp) {
|
|
|
|
|
for (uint8_t i = 0; i < lemonIconCount; i++)
|
|
|
|
|
if (pgm_read_dword(&lemonIconCPs[i]) == cp) return pgm_read_byte(&lemonIconGlyphs[i].xAdvance);
|
|
|
|
|
if (cp < MiscFixed.first || cp > MiscFixed.last) return 6;
|
|
|
|
|
return pgm_read_byte(&MiscFixedGlyphs[cp - MiscFixed.first].xAdvance);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2025-04-10 16:24:17 +02:00
|
|
|
bool ST7789Display::begin() {
|
|
|
|
|
if(!_isOn) {
|
|
|
|
|
pinMode(PIN_TFT_VDD_CTL, OUTPUT);
|
|
|
|
|
pinMode(PIN_TFT_LEDA_CTL, OUTPUT);
|
|
|
|
|
digitalWrite(PIN_TFT_VDD_CTL, LOW);
|
2025-08-15 15:54:24 +08:00
|
|
|
#ifdef PIN_TFT_LEDA_CTL_ACTIVE
|
|
|
|
|
digitalWrite(PIN_TFT_LEDA_CTL, PIN_TFT_LEDA_CTL_ACTIVE);
|
|
|
|
|
#else
|
2025-04-10 16:24:17 +02:00
|
|
|
digitalWrite(PIN_TFT_LEDA_CTL, LOW);
|
2025-08-15 15:54:24 +08:00
|
|
|
#endif
|
2025-04-10 16:24:17 +02:00
|
|
|
digitalWrite(PIN_TFT_RST, HIGH);
|
|
|
|
|
|
2025-04-24 22:37:06 +02:00
|
|
|
display.init();
|
2025-04-30 11:09:43 +02:00
|
|
|
display.landscapeScreen();
|
2026-07-13 17:40:39 +12:00
|
|
|
#ifdef DISPLAY_FLIP_VERTICALLY
|
|
|
|
|
display.flipScreenVertically();
|
|
|
|
|
#endif
|
2025-04-24 22:37:06 +02:00
|
|
|
display.displayOn();
|
2025-04-30 11:26:04 +02:00
|
|
|
setCursor(0,0);
|
|
|
|
|
|
2025-04-10 16:24:17 +02:00
|
|
|
_isOn = true;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ST7789Display::turnOn() {
|
2025-06-21 16:13:53 -07:00
|
|
|
if (!_isOn) {
|
|
|
|
|
// Restore power to the display but keep backlight off
|
|
|
|
|
digitalWrite(PIN_TFT_VDD_CTL, LOW);
|
|
|
|
|
digitalWrite(PIN_TFT_RST, HIGH);
|
|
|
|
|
|
|
|
|
|
// Re-initialize the display
|
|
|
|
|
display.init();
|
|
|
|
|
display.displayOn();
|
2026-07-13 17:40:39 +12:00
|
|
|
#ifdef DISPLAY_FLIP_VERTICALLY
|
|
|
|
|
display.flipScreenVertically();
|
|
|
|
|
#endif
|
2025-06-21 18:17:38 -07:00
|
|
|
delay(20);
|
2025-06-21 16:13:53 -07:00
|
|
|
|
|
|
|
|
// Now turn on the backlight
|
2025-08-15 15:54:24 +08:00
|
|
|
#ifdef PIN_TFT_LEDA_CTL_ACTIVE
|
|
|
|
|
digitalWrite(PIN_TFT_LEDA_CTL, PIN_TFT_LEDA_CTL_ACTIVE);
|
|
|
|
|
#else
|
2025-06-21 16:13:53 -07:00
|
|
|
digitalWrite(PIN_TFT_LEDA_CTL, LOW);
|
2025-08-15 15:54:24 +08:00
|
|
|
#endif
|
2025-06-21 16:13:53 -07:00
|
|
|
_isOn = true;
|
|
|
|
|
}
|
2025-04-10 16:24:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ST7789Display::turnOff() {
|
|
|
|
|
digitalWrite(PIN_TFT_VDD_CTL, HIGH);
|
2025-08-15 15:54:24 +08:00
|
|
|
#ifdef PIN_TFT_LEDA_CTL_ACTIVE
|
|
|
|
|
digitalWrite(PIN_TFT_LEDA_CTL, !PIN_TFT_LEDA_CTL_ACTIVE);
|
|
|
|
|
#else
|
2025-04-10 16:24:17 +02:00
|
|
|
digitalWrite(PIN_TFT_LEDA_CTL, HIGH);
|
2025-08-15 15:54:24 +08:00
|
|
|
#endif
|
2025-04-10 16:24:17 +02:00
|
|
|
digitalWrite(PIN_TFT_RST, LOW);
|
|
|
|
|
_isOn = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ST7789Display::clear() {
|
2025-04-24 22:37:06 +02:00
|
|
|
display.clear();
|
2025-04-10 16:24:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ST7789Display::startFrame(Color bkg) {
|
2025-04-24 22:37:06 +02:00
|
|
|
display.clear();
|
2025-08-08 20:01:31 +10:00
|
|
|
_color = ST77XX_WHITE;
|
|
|
|
|
display.setRGB(_color);
|
|
|
|
|
display.setFont(ArialMT_Plain_16);
|
2025-04-10 16:24:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ST7789Display::setTextSize(int sz) {
|
2025-04-24 22:37:06 +02:00
|
|
|
switch(sz) {
|
|
|
|
|
case 1 :
|
2025-05-05 15:54:31 +10:00
|
|
|
display.setFont(ArialMT_Plain_16);
|
2025-04-24 22:37:06 +02:00
|
|
|
break;
|
|
|
|
|
case 2 :
|
|
|
|
|
display.setFont(ArialMT_Plain_24);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
2025-05-05 15:54:31 +10:00
|
|
|
display.setFont(ArialMT_Plain_16);
|
2025-04-24 22:37:06 +02:00
|
|
|
}
|
2025-04-10 16:24:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ST7789Display::setColor(Color c) {
|
2025-04-11 22:23:47 +02:00
|
|
|
switch (c) {
|
|
|
|
|
case DisplayDriver::DARK :
|
|
|
|
|
_color = ST77XX_BLACK;
|
2025-08-08 20:01:31 +10:00
|
|
|
display.setColor(OLEDDISPLAY_COLOR::BLACK);
|
2025-04-11 22:23:47 +02:00
|
|
|
break;
|
2025-08-08 20:01:31 +10:00
|
|
|
#if 0
|
2026-08-11 17:25:35 +02:00
|
|
|
case DisplayDriver::LIGHT :
|
2025-04-11 22:23:47 +02:00
|
|
|
_color = ST77XX_WHITE;
|
|
|
|
|
break;
|
2026-08-11 17:25:35 +02:00
|
|
|
case DisplayDriver::RED :
|
2025-04-11 22:23:47 +02:00
|
|
|
_color = ST77XX_RED;
|
|
|
|
|
break;
|
2026-08-11 17:25:35 +02:00
|
|
|
case DisplayDriver::GREEN :
|
2025-04-11 22:23:47 +02:00
|
|
|
_color = ST77XX_GREEN;
|
|
|
|
|
break;
|
2026-08-11 17:25:35 +02:00
|
|
|
case DisplayDriver::BLUE :
|
2025-04-11 22:23:47 +02:00
|
|
|
_color = ST77XX_BLUE;
|
|
|
|
|
break;
|
2026-08-11 17:25:35 +02:00
|
|
|
case DisplayDriver::YELLOW :
|
2025-04-11 22:23:47 +02:00
|
|
|
_color = ST77XX_YELLOW;
|
|
|
|
|
break;
|
2026-08-11 17:25:35 +02:00
|
|
|
case DisplayDriver::ORANGE :
|
2025-04-11 22:23:47 +02:00
|
|
|
_color = ST77XX_ORANGE;
|
|
|
|
|
break;
|
2025-08-08 20:01:31 +10:00
|
|
|
#endif
|
2025-04-11 22:23:47 +02:00
|
|
|
default:
|
|
|
|
|
_color = ST77XX_WHITE;
|
2025-08-08 20:01:31 +10:00
|
|
|
display.setColor(OLEDDISPLAY_COLOR::WHITE);
|
2025-04-11 22:23:47 +02:00
|
|
|
break;
|
|
|
|
|
}
|
2025-04-24 22:37:06 +02:00
|
|
|
display.setRGB(_color);
|
2025-04-10 16:24:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ST7789Display::setCursor(int x, int y) {
|
2026-08-08 01:06:24 +02:00
|
|
|
#ifdef OLED_MISC_FIXED_FONT
|
|
|
|
|
_lx = x;
|
|
|
|
|
_ly = y;
|
|
|
|
|
#endif
|
2025-05-05 15:54:31 +10:00
|
|
|
_x = x*SCALE_X + X_OFFSET;
|
|
|
|
|
_y = y*SCALE_Y + Y_OFFSET;
|
2025-04-10 16:24:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ST7789Display::print(const char* str) {
|
2026-08-08 01:06:24 +02:00
|
|
|
#ifdef OLED_MISC_FIXED_FONT
|
|
|
|
|
int lx = _lx, ly = _ly;
|
|
|
|
|
uint8_t glyphBuf[16];
|
|
|
|
|
const uint8_t* p = (const uint8_t*)str;
|
|
|
|
|
while (*p) {
|
|
|
|
|
uint32_t cp = DisplayDriver::decodeCodepoint(p);
|
|
|
|
|
if (cp == '\n') { ly += MiscFixed.yAdvance; lx = _lx; continue; }
|
|
|
|
|
|
|
|
|
|
bool drawn = false;
|
|
|
|
|
for (uint8_t i = 0; i < lemonIconCount; i++) {
|
|
|
|
|
if (pgm_read_dword(&lemonIconCPs[i]) != cp) continue;
|
|
|
|
|
const GFXglyph* g = &lemonIconGlyphs[i];
|
|
|
|
|
uint8_t gw = pgm_read_byte(&g->width), gh = pgm_read_byte(&g->height);
|
|
|
|
|
int8_t xo = (int8_t)pgm_read_byte(&g->xOffset), yo = (int8_t)pgm_read_byte(&g->yOffset);
|
|
|
|
|
uint16_t bo = pgm_read_word(&g->bitmapOffset);
|
|
|
|
|
if (repackGlyphToXbm(lemonIconBitmaps, bo, gw, gh, glyphBuf, sizeof(glyphBuf)))
|
|
|
|
|
drawXbm(lx + xo, ly + 6 + yo, glyphBuf, gw, gh);
|
|
|
|
|
lx += pgm_read_byte(&g->xAdvance);
|
|
|
|
|
drawn = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (drawn) continue;
|
|
|
|
|
|
|
|
|
|
if (cp < MiscFixed.first || cp > MiscFixed.last) {
|
|
|
|
|
if (cp >= 0x20) fillRect(lx + 1, ly, 4, 6);
|
|
|
|
|
lx += 6;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const GFXglyph* g = &MiscFixedGlyphs[cp - MiscFixed.first];
|
|
|
|
|
uint8_t gw = pgm_read_byte(&g->width), gh = pgm_read_byte(&g->height);
|
|
|
|
|
int8_t xo = (int8_t)pgm_read_byte(&g->xOffset), yo = (int8_t)pgm_read_byte(&g->yOffset);
|
|
|
|
|
uint16_t bo = pgm_read_word(&g->bitmapOffset);
|
|
|
|
|
if (repackGlyphToXbm(MiscFixedBitmaps, bo, gw, gh, glyphBuf, sizeof(glyphBuf)))
|
|
|
|
|
drawXbm(lx + xo, ly + 7 + yo, glyphBuf, gw, gh);
|
|
|
|
|
lx += pgm_read_byte(&g->xAdvance);
|
|
|
|
|
}
|
|
|
|
|
#else
|
2025-04-24 22:37:06 +02:00
|
|
|
display.drawString(_x, _y, str);
|
2026-08-08 01:06:24 +02:00
|
|
|
#endif
|
2025-04-10 16:24:17 +02:00
|
|
|
}
|
|
|
|
|
|
2025-08-08 20:01:31 +10:00
|
|
|
void ST7789Display::printWordWrap(const char* str, int max_width) {
|
|
|
|
|
display.drawStringMaxWidth(_x, _y, max_width*SCALE_X, str);
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-10 16:24:17 +02:00
|
|
|
void ST7789Display::fillRect(int x, int y, int w, int h) {
|
2025-05-05 15:54:31 +10:00
|
|
|
display.fillRect(x*SCALE_X + X_OFFSET, y*SCALE_Y + Y_OFFSET, w*SCALE_X, h*SCALE_Y);
|
2025-04-10 16:24:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ST7789Display::drawRect(int x, int y, int w, int h) {
|
2025-05-05 15:54:31 +10:00
|
|
|
display.drawRect(x*SCALE_X + X_OFFSET, y*SCALE_Y + Y_OFFSET, w*SCALE_X, h*SCALE_Y);
|
2025-04-10 16:24:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ST7789Display::drawXbm(int x, int y, const uint8_t* bits, int w, int h) {
|
2025-05-06 20:47:14 -07:00
|
|
|
// Calculate the base position in display coordinates
|
|
|
|
|
uint16_t startX = x * SCALE_X + X_OFFSET;
|
|
|
|
|
uint16_t startY = y * SCALE_Y + Y_OFFSET;
|
|
|
|
|
|
|
|
|
|
// Width in bytes for bitmap processing
|
|
|
|
|
uint16_t widthInBytes = (w + 7) / 8;
|
|
|
|
|
|
|
|
|
|
// Process the bitmap row by row
|
|
|
|
|
for (uint16_t by = 0; by < h; by++) {
|
2025-05-07 18:14:56 -07:00
|
|
|
// Calculate the target y-coordinates for this logical row
|
|
|
|
|
int y1 = startY + (int)(by * SCALE_Y);
|
|
|
|
|
int y2 = startY + (int)((by + 1) * SCALE_Y);
|
|
|
|
|
int block_h = y2 - y1;
|
|
|
|
|
|
2025-05-06 20:47:14 -07:00
|
|
|
// Scan across the row bit by bit
|
|
|
|
|
for (uint16_t bx = 0; bx < w; bx++) {
|
2025-05-07 18:14:56 -07:00
|
|
|
// Calculate the target x-coordinates for this logical column
|
|
|
|
|
int x1 = startX + (int)(bx * SCALE_X);
|
|
|
|
|
int x2 = startX + (int)((bx + 1) * SCALE_X);
|
|
|
|
|
int block_w = x2 - x1;
|
|
|
|
|
|
2025-05-06 20:47:14 -07:00
|
|
|
// Get the current bit
|
|
|
|
|
uint16_t byteOffset = (by * widthInBytes) + (bx / 8);
|
|
|
|
|
uint8_t bitMask = 0x80 >> (bx & 7);
|
|
|
|
|
bool bitSet = pgm_read_byte(bits + byteOffset) & bitMask;
|
|
|
|
|
|
2025-05-07 18:14:56 -07:00
|
|
|
// If the bit is set, draw a block of pixels
|
2025-05-06 20:47:14 -07:00
|
|
|
if (bitSet) {
|
2025-05-07 18:14:56 -07:00
|
|
|
// Draw the block as a filled rectangle
|
|
|
|
|
display.fillRect(x1, y1, block_w, block_h);
|
2025-05-06 20:47:14 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-04-10 16:24:17 +02:00
|
|
|
}
|
|
|
|
|
|
2025-05-04 18:17:18 -07:00
|
|
|
uint16_t ST7789Display::getTextWidth(const char* str) {
|
2026-08-08 01:06:24 +02:00
|
|
|
#ifdef OLED_MISC_FIXED_FONT
|
|
|
|
|
uint16_t w = 0;
|
|
|
|
|
const uint8_t* p = (const uint8_t*)str;
|
|
|
|
|
while (*p) w += glyphXAdvance(DisplayDriver::decodeCodepoint(p));
|
|
|
|
|
return w;
|
|
|
|
|
#else
|
2025-05-05 15:54:31 +10:00
|
|
|
return display.getStringWidth(str) / SCALE_X;
|
2026-08-08 01:06:24 +02:00
|
|
|
#endif
|
2025-05-04 18:17:18 -07:00
|
|
|
}
|
|
|
|
|
|
2025-04-10 16:24:17 +02:00
|
|
|
void ST7789Display::endFrame() {
|
2025-04-24 22:37:06 +02:00
|
|
|
display.display();
|
2025-04-10 16:24:17 +02:00
|
|
|
}
|
2025-04-11 22:23:47 +02:00
|
|
|
|
|
|
|
|
#endif
|