Added digit dimming and support for .clk format (see https://github.c… (#2555)
* Added digit dimming and support for .clk format (see https://github.com/aly-fly/EleksTubeHAX) * Small fixes and improvements, dimming optional Co-authored-by: cschwinne <dev.aircoookie@gmail.com>
This commit is contained in:
parent
9c864c9759
commit
4a0a07f158
@ -529,12 +529,13 @@ board = esp32dev
|
|||||||
platform = espressif32@3.2
|
platform = espressif32@3.2
|
||||||
upload_speed = 921600
|
upload_speed = 921600
|
||||||
build_flags = ${common.build_flags_esp32} -D WLED_DISABLE_BROWNOUT_DET -D WLED_DISABLE_INFRARED
|
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_RTC
|
||||||
-D USERMOD_ELEKSTUBE_IPS
|
-D USERMOD_ELEKSTUBE_IPS
|
||||||
-D LEDPIN=12
|
-D LEDPIN=12
|
||||||
-D RLYPIN=27
|
-D RLYPIN=27
|
||||||
-D BTNPIN=34
|
-D BTNPIN=34
|
||||||
-D WLED_DISABLE_INFRARED
|
-D WLED_DISABLE_BLYNK
|
||||||
-D DEFAULT_LED_COUNT=6
|
-D DEFAULT_LED_COUNT=6
|
||||||
# Display config
|
# Display config
|
||||||
-D ST7789_DRIVER
|
-D ST7789_DRIVER
|
||||||
|
@ -12,6 +12,7 @@ class TFTs : public TFT_eSPI {
|
|||||||
private:
|
private:
|
||||||
uint8_t digits[NUM_DIGITS];
|
uint8_t digits[NUM_DIGITS];
|
||||||
|
|
||||||
|
|
||||||
// These read 16- and 32-bit types from the SD card file.
|
// These read 16- and 32-bit types from the SD card file.
|
||||||
// BMP data is stored little-endian, Arduino is little-endian too.
|
// BMP data is stored little-endian, Arduino is little-endian too.
|
||||||
// May need to reverse subscript order if porting elsewhere.
|
// May need to reverse subscript order if porting elsewhere.
|
||||||
@ -41,41 +42,70 @@ private:
|
|||||||
|
|
||||||
//// BEGIN STOLEN CODE
|
//// 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) {
|
bool drawBin(const char *filename) {
|
||||||
fs::File bmpFS;
|
fs::File bmpFS;
|
||||||
|
|
||||||
|
|
||||||
// Open requested file on SD card
|
// Open requested file on SD card
|
||||||
bmpFS = WLED_FS.open(filename, "r");
|
bmpFS = WLED_FS.open(filename, "r");
|
||||||
|
|
||||||
if (!bmpFS)
|
size_t sz = bmpFS.size();
|
||||||
{
|
if (sz > 64800) {
|
||||||
Serial.print(F("File not found: "));
|
bmpFS.close();
|
||||||
Serial.println(filename);
|
|
||||||
return(false);
|
return(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t sz = bmpFS.size();
|
uint16_t r, g, b, dimming = 255;
|
||||||
if (sz <= 64800)
|
int16_t w, h, y, row, col;
|
||||||
{
|
|
||||||
bool oldSwapBytes = getSwapBytes();
|
|
||||||
setSwapBytes(true);
|
|
||||||
|
|
||||||
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
|
if (!realtimeMode || realtimeOverride) strip.service();
|
||||||
int16_t y = (height() - h) /2;
|
|
||||||
|
|
||||||
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);
|
bmpFS.read(lineBuffer, sizeof(lineBuffer));
|
||||||
|
uint8_t PixM, PixL;
|
||||||
setSwapBytes(oldSwapBytes);
|
|
||||||
|
// 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();
|
bmpFS.close();
|
||||||
|
|
||||||
return(true);
|
return(true);
|
||||||
@ -87,16 +117,9 @@ private:
|
|||||||
// Open requested file on SD card
|
// Open requested file on SD card
|
||||||
bmpFS = WLED_FS.open(filename, "r");
|
bmpFS = WLED_FS.open(filename, "r");
|
||||||
|
|
||||||
if (!bmpFS)
|
|
||||||
{
|
|
||||||
Serial.print(F("File not found: "));
|
|
||||||
Serial.println(filename);
|
|
||||||
return(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t seekOffset;
|
uint32_t seekOffset;
|
||||||
int16_t w, h, row;
|
int16_t w, h, row;
|
||||||
uint8_t r, g, b;
|
uint16_t r, g, b, dimming = 255;
|
||||||
|
|
||||||
uint16_t magic = read16(bmpFS);
|
uint16_t magic = read16(bmpFS);
|
||||||
if (magic == 0xFFFF) {
|
if (magic == 0xFFFF) {
|
||||||
@ -104,13 +127,6 @@ private:
|
|||||||
bmpFS.close();
|
bmpFS.close();
|
||||||
return(false);
|
return(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (magic != 0x4D42) {
|
|
||||||
Serial.print(F("File not a BMP. Magic: "));
|
|
||||||
Serial.println(magic);
|
|
||||||
bmpFS.close();
|
|
||||||
return(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
read32(bmpFS);
|
read32(bmpFS);
|
||||||
read32(bmpFS);
|
read32(bmpFS);
|
||||||
@ -128,8 +144,6 @@ private:
|
|||||||
//draw img that is shorter than 240pix into the center
|
//draw img that is shorter than 240pix into the center
|
||||||
int16_t y = (height() - h) /2;
|
int16_t y = (height() - h) /2;
|
||||||
|
|
||||||
bool oldSwapBytes = getSwapBytes();
|
|
||||||
setSwapBytes(true);
|
|
||||||
bmpFS.seek(seekOffset);
|
bmpFS.seek(seekOffset);
|
||||||
|
|
||||||
uint16_t padding = (4 - ((w * 3) & 3)) & 3;
|
uint16_t padding = (4 - ((w * 3) & 3)) & 3;
|
||||||
@ -142,16 +156,29 @@ private:
|
|||||||
bmpFS.read(lineBuffer, sizeof(lineBuffer));
|
bmpFS.read(lineBuffer, sizeof(lineBuffer));
|
||||||
uint8_t* bptr = lineBuffer;
|
uint8_t* bptr = lineBuffer;
|
||||||
|
|
||||||
|
#ifdef ELEKSTUBE_DIMMING
|
||||||
|
dimming=bri;
|
||||||
|
#endif
|
||||||
// Convert 24 to 16 bit colours while copying to output buffer.
|
// Convert 24 to 16 bit colours while copying to output buffer.
|
||||||
for (uint16_t col = 0; col < w; col++)
|
for (uint16_t col = 0; col < w; col++)
|
||||||
{
|
{
|
||||||
b = *bptr++;
|
b = *bptr++;
|
||||||
g = *bptr++;
|
g = *bptr++;
|
||||||
r = *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);
|
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);
|
pushImage(0, y, w, h, (uint16_t *)output_buffer);
|
||||||
setSwapBytes(oldSwapBytes);
|
setSwapBytes(oldSwapBytes);
|
||||||
|
|
||||||
@ -159,6 +186,83 @@ private:
|
|||||||
return(true);
|
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:
|
public:
|
||||||
TFTs() : TFT_eSPI(), chip_select()
|
TFTs() : TFT_eSPI(), chip_select()
|
||||||
{ for (uint8_t digit=0; digit < NUM_DIGITS; digit++) digits[digit] = 0; }
|
{ for (uint8_t digit=0; digit < NUM_DIGITS; digit++) digits[digit] = 0; }
|
||||||
@ -184,25 +288,30 @@ public:
|
|||||||
chip_select.setDigit(digit);
|
chip_select.setDigit(digit);
|
||||||
|
|
||||||
if (digits[digit] == blanked) {
|
if (digits[digit] == blanked) {
|
||||||
fillScreen(TFT_BLACK);
|
fillScreen(TFT_BLACK); return;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
// Filenames are no bigger than "255.bmp\0"
|
// Filenames are no bigger than "255.bmp\0"
|
||||||
char file_name[10];
|
char file_name[10];
|
||||||
sprintf(file_name, "/%d.bmp", digits[digit]);
|
// Fastest, raw RGB565
|
||||||
if (WLED_FS.exists(file_name)) {
|
sprintf(file_name, "/%d.bin", digits[digit]);
|
||||||
drawBmp(file_name);
|
if (WLED_FS.exists(file_name)) {
|
||||||
} else {
|
drawBin(file_name); return;
|
||||||
sprintf(file_name, "/%d.bin", digits[digit]);
|
|
||||||
drawBin(file_name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
// 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) {
|
void setDigit(uint8_t digit, uint8_t value, show_t show=yes) {
|
||||||
uint8_t old_value = digits[digit];
|
uint8_t old_value = digits[digit];
|
||||||
digits[digit] = value;
|
digits[digit] = value;
|
||||||
|
|
||||||
if (show != no && (old_value != value || show == force)) {
|
if (show != no && (old_value != value || show == force)) {
|
||||||
showDigit(digit);
|
showDigit(digit);
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,8 @@ class ElekstubeIPSUsermod : public Usermod {
|
|||||||
set[i] = false; //display HHMMSS time
|
set[i] = false; //display HHMMSS time
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
uint8_t hr = hour(localTime);
|
uint8_t hr = hour(localTime);
|
||||||
uint8_t hrTens = hr/10;
|
uint8_t hrTens = hr/10;
|
||||||
uint8_t mi = minute(localTime);
|
uint8_t mi = minute(localTime);
|
||||||
@ -37,6 +39,9 @@ class ElekstubeIPSUsermod : public Usermod {
|
|||||||
unsigned long lastTime = 0;
|
unsigned long lastTime = 0;
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
uint8_t lastBri;
|
||||||
|
TFTs::show_t fshow=TFTs::yes;
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
tfts.begin();
|
tfts.begin();
|
||||||
tfts.fillScreen(TFT_BLACK);
|
tfts.fillScreen(TFT_BLACK);
|
||||||
@ -49,7 +54,14 @@ class ElekstubeIPSUsermod : public Usermod {
|
|||||||
void loop() {
|
void loop() {
|
||||||
if (toki.isTick()) {
|
if (toki.isTick()) {
|
||||||
updateLocalTime();
|
updateLocalTime();
|
||||||
updateClockDisplay();
|
#ifdef ELEKSTUBE_DIMMING
|
||||||
|
if (bri != lastBri) {
|
||||||
|
fshow=TFTs::force;
|
||||||
|
lastBri = bri;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
updateClockDisplay(fshow);
|
||||||
|
fshow=TFTs::yes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user