diff --git a/platformio.ini b/platformio.ini index 735cdd45..7d6b4290 100644 --- a/platformio.ini +++ b/platformio.ini @@ -529,12 +529,13 @@ board = esp32dev platform = espressif32@3.2 upload_speed = 921600 build_flags = ${common.build_flags_esp32} -D WLED_DISABLE_BROWNOUT_DET -D WLED_DISABLE_INFRARED + #-D ELEKSTUBE_DIMMING # if enabled, scale display brightness (does not set backlight, only scales contents) -D USERMOD_RTC -D USERMOD_ELEKSTUBE_IPS -D LEDPIN=12 -D RLYPIN=27 -D BTNPIN=34 - -D WLED_DISABLE_INFRARED + -D WLED_DISABLE_BLYNK -D DEFAULT_LED_COUNT=6 # Display config -D ST7789_DRIVER diff --git a/usermods/EleksTube_IPS/TFTs.h b/usermods/EleksTube_IPS/TFTs.h index 675129dc..d0a8318d 100644 --- a/usermods/EleksTube_IPS/TFTs.h +++ b/usermods/EleksTube_IPS/TFTs.h @@ -12,6 +12,7 @@ class TFTs : public TFT_eSPI { private: uint8_t digits[NUM_DIGITS]; + // These read 16- and 32-bit types from the SD card file. // BMP data is stored little-endian, Arduino is little-endian too. // May need to reverse subscript order if porting elsewhere. @@ -41,41 +42,70 @@ private: //// BEGIN STOLEN CODE - // Draw directly from file stored in RGB565 format + // Draw directly from file stored in RGB565 format. Fastest bool drawBin(const char *filename) { fs::File bmpFS; - // Open requested file on SD card bmpFS = WLED_FS.open(filename, "r"); - if (!bmpFS) - { - Serial.print(F("File not found: ")); - Serial.println(filename); + size_t sz = bmpFS.size(); + if (sz > 64800) { + bmpFS.close(); return(false); } - size_t sz = bmpFS.size(); - if (sz <= 64800) - { - bool oldSwapBytes = getSwapBytes(); - setSwapBytes(true); + uint16_t r, g, b, dimming = 255; + int16_t w, h, y, row, col; - int16_t h = sz / (135 * 2); + //draw img that is shorter than 240pix into the center + w = 135; + h = sz / (w * 2); + y = (height() - h) /2; + + uint8_t lineBuffer[w * 2]; - //draw img that is shorter than 240pix into the center - int16_t y = (height() - h) /2; + if (!realtimeMode || realtimeOverride) strip.service(); - bmpFS.read((uint8_t *) output_buffer,sz); + #ifdef ELEKSTUBE_DIMMING + dimming=bri; + #endif - if (!realtimeMode || realtimeOverride) strip.service(); + // 0,0 coordinates are top left + for (row = 0; row < h; row++) { - pushImage(0, y, 135, h, (uint16_t *)output_buffer); - - setSwapBytes(oldSwapBytes); + bmpFS.read(lineBuffer, sizeof(lineBuffer)); + uint8_t PixM, PixL; + + // Colors are already in 16-bit R5, G6, B5 format + for (col = 0; col < w; col++) + { + if (dimming == 255) { // not needed, copy directly + output_buffer[row][col] = (lineBuffer[col*2+1] << 8) | (lineBuffer[col*2]); + } else { + // 16 BPP pixel format: R5, G6, B5 ; bin: RRRR RGGG GGGB BBBB + PixM = lineBuffer[col*2+1]; + PixL = lineBuffer[col*2]; + // align to 8-bit value (MSB left aligned) + r = (PixM) & 0xF8; + g = ((PixM << 5) | (PixL >> 3)) & 0xFC; + b = (PixL << 3) & 0xF8; + r *= dimming; + g *= dimming; + b *= dimming; + r = r >> 8; + g = g >> 8; + b = b >> 8; + output_buffer[row][col] = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3); + } + } } + bool oldSwapBytes = getSwapBytes(); + setSwapBytes(true); + pushImage(0, y, 135, h, (uint16_t *)output_buffer); + setSwapBytes(oldSwapBytes); + bmpFS.close(); return(true); @@ -87,16 +117,9 @@ private: // Open requested file on SD card bmpFS = WLED_FS.open(filename, "r"); - if (!bmpFS) - { - Serial.print(F("File not found: ")); - Serial.println(filename); - return(false); - } - uint32_t seekOffset; int16_t w, h, row; - uint8_t r, g, b; + uint16_t r, g, b, dimming = 255; uint16_t magic = read16(bmpFS); if (magic == 0xFFFF) { @@ -104,13 +127,6 @@ private: bmpFS.close(); return(false); } - - if (magic != 0x4D42) { - Serial.print(F("File not a BMP. Magic: ")); - Serial.println(magic); - bmpFS.close(); - return(false); - } read32(bmpFS); read32(bmpFS); @@ -128,8 +144,6 @@ private: //draw img that is shorter than 240pix into the center int16_t y = (height() - h) /2; - bool oldSwapBytes = getSwapBytes(); - setSwapBytes(true); bmpFS.seek(seekOffset); uint16_t padding = (4 - ((w * 3) & 3)) & 3; @@ -142,16 +156,29 @@ private: bmpFS.read(lineBuffer, sizeof(lineBuffer)); uint8_t* bptr = lineBuffer; + #ifdef ELEKSTUBE_DIMMING + dimming=bri; + #endif // Convert 24 to 16 bit colours while copying to output buffer. for (uint16_t col = 0; col < w; col++) { b = *bptr++; g = *bptr++; r = *bptr++; + if (dimming != 255) { // only dimm when needed + b *= dimming; + g *= dimming; + r *= dimming; + b = b >> 8; + g = g >> 8; + r = r >> 8; + } output_buffer[row][col] = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3); } } + bool oldSwapBytes = getSwapBytes(); + setSwapBytes(true); pushImage(0, y, w, h, (uint16_t *)output_buffer); setSwapBytes(oldSwapBytes); @@ -159,6 +186,83 @@ private: return(true); } + bool drawClk(const char *filename) { + fs::File bmpFS; + + // Open requested file on SD card + bmpFS = WLED_FS.open(filename, "r"); + + if (!bmpFS) + { + Serial.print("File not found: "); + Serial.println(filename); + return(false); + } + + uint16_t r, g, b, dimming = 255, magic; + int16_t w, h, row, col; + + magic = read16(bmpFS); + if (magic != 0x4B43) { // look for "CK" header + Serial.print(F("File not a CLK. Magic: ")); + Serial.println(magic); + bmpFS.close(); + return(false); + } + + w = read16(bmpFS); + h = read16(bmpFS); + int16_t x = (width() - w) / 2; + int16_t y = (height() - h) / 2; + + uint8_t lineBuffer[w * 2]; + + #ifdef ELEKSTUBE_DIMMING + dimming=bri; + #endif + + if (!realtimeMode || realtimeOverride) strip.service(); + + // 0,0 coordinates are top left + for (row = 0; row < h; row++) { + + bmpFS.read(lineBuffer, sizeof(lineBuffer)); + uint8_t PixM, PixL; + + // Colors are already in 16-bit R5, G6, B5 format + for (col = 0; col < w; col++) + { + if (dimming == 255) { // not needed, copy directly + output_buffer[row][col+x] = (lineBuffer[col*2+1] << 8) | (lineBuffer[col*2]); + } else { + // 16 BPP pixel format: R5, G6, B5 ; bin: RRRR RGGG GGGB BBBB + PixM = lineBuffer[col*2+1]; + PixL = lineBuffer[col*2]; + // align to 8-bit value (MSB left aligned) + r = (PixM) & 0xF8; + g = ((PixM << 5) | (PixL >> 3)) & 0xFC; + b = (PixL << 3) & 0xF8; + r *= dimming; + g *= dimming; + b *= dimming; + r = r >> 8; + g = g >> 8; + b = b >> 8; + output_buffer[row][col+x] = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3); + } + } + } + + bool oldSwapBytes = getSwapBytes(); + setSwapBytes(true); + pushImage(0, y, w, h, (uint16_t *)output_buffer); + setSwapBytes(oldSwapBytes); + + bmpFS.close(); + return(true); + } + + public: TFTs() : TFT_eSPI(), chip_select() { for (uint8_t digit=0; digit < NUM_DIGITS; digit++) digits[digit] = 0; } @@ -184,25 +288,30 @@ public: chip_select.setDigit(digit); if (digits[digit] == blanked) { - fillScreen(TFT_BLACK); + fillScreen(TFT_BLACK); return; } - else { - // Filenames are no bigger than "255.bmp\0" - char file_name[10]; - sprintf(file_name, "/%d.bmp", digits[digit]); - if (WLED_FS.exists(file_name)) { - drawBmp(file_name); - } else { - sprintf(file_name, "/%d.bin", digits[digit]); - drawBin(file_name); - } + + // Filenames are no bigger than "255.bmp\0" + char file_name[10]; + // Fastest, raw RGB565 + sprintf(file_name, "/%d.bin", digits[digit]); + if (WLED_FS.exists(file_name)) { + drawBin(file_name); return; } - } + // Fast, see https://github.com/aly-fly/EleksTubeHAX on how to create this clk format + sprintf(file_name, "/%d.clk", digits[digit]); + if (WLED_FS.exists(file_name)) { + drawClk(file_name); return; + } + // Slow, regular RGB888 bmp + sprintf(file_name, "/%d.bmp", digits[digit]); + drawBmp(file_name); + } void setDigit(uint8_t digit, uint8_t value, show_t show=yes) { uint8_t old_value = digits[digit]; - digits[digit] = value; - + digits[digit] = value; + if (show != no && (old_value != value || show == force)) { showDigit(digit); } diff --git a/usermods/EleksTube_IPS/usermod_elekstube_ips.h b/usermods/EleksTube_IPS/usermod_elekstube_ips.h index f2ce8eb0..713c82f8 100644 --- a/usermods/EleksTube_IPS/usermod_elekstube_ips.h +++ b/usermods/EleksTube_IPS/usermod_elekstube_ips.h @@ -21,6 +21,8 @@ class ElekstubeIPSUsermod : public Usermod { set[i] = false; //display HHMMSS time } } + + uint8_t hr = hour(localTime); uint8_t hrTens = hr/10; uint8_t mi = minute(localTime); @@ -37,6 +39,9 @@ class ElekstubeIPSUsermod : public Usermod { unsigned long lastTime = 0; public: + uint8_t lastBri; + TFTs::show_t fshow=TFTs::yes; + void setup() { tfts.begin(); tfts.fillScreen(TFT_BLACK); @@ -49,7 +54,14 @@ class ElekstubeIPSUsermod : public Usermod { void loop() { if (toki.isTick()) { updateLocalTime(); - updateClockDisplay(); + #ifdef ELEKSTUBE_DIMMING + if (bri != lastBri) { + fshow=TFTs::force; + lastBri = bri; + } + #endif + updateClockDisplay(fshow); + fshow=TFTs::yes; } }