four-line-display: support for SSD1309 and SSD1327 (SPI only)

* driver for SSD1309 4-Wire SPI (128x64) - untested
* driver for SSD1327 4-Wire SPI (128x128) - tested with waveshare 1.5in grayscale OLED
* driver Info to "clock mode" (WLED_DEBUG only)
* correct some misleading comments
* fix some typos
This commit is contained in:
Frank
2023-03-26 14:38:14 +02:00
parent 361a3b1cb3
commit 049d0f901d

View File

@@ -4,36 +4,35 @@
#include <Wire.h>
#include <SPI.h>
#undef U8X8_NO_HW_I2C // WLEDMM: we do want I2C hardware drivers - if possible
//#define WIRE_INTERFACES_COUNT 2 // experimental - tell U8x8Lib that there is a second Wire unit
//#define WIRE_INTERFACES_COUNT 2 // experimental - tell U8x8Lib that there is a second Wire unit
#include "wled.h"
#include <U8x8lib.h> // from https://github.com/olikraus/u8g2/
#include "4LD_wled_fonts.c"
#ifndef FLD_ESP32_NO_THREADS
#define FLD_ESP32_USE_THREADS // comment out to use 0.13.x behviour without parallel update task - slower, but more robust. May delay other tasks like LEDs or audioreactive!!
#define FLD_ESP32_USE_THREADS // comment out to use 0.13.x behaviour without parallel update task - slower, but more robust. May delay other tasks like LEDs or audioreactive!!
#endif
//#define OLD_4LD_FONTS // comment out if you prefer the "classic" look with blocky fonts (saves 1K flash)
//
// Insired by the usermod_v2_four_line_display
// Inspired by the usermod_v2_four_line_display
//
// v2 usermod for using 128x32 or 128x64 i2c
// OLED displays to provide a four line display
// for WLED.
//
// Dependencies
// * This usermod REQURES the ModeSortUsermod
// * This Usermod works best, by far, when coupled
// with RotaryEncoderUIUsermod.
// * This usermod does not REQUIRE the ModeSortUsermod any more
// * This usermod works best, by far, when coupled
// with RotaryEncoderUI_ALT usermod.
//
// Make sure to enable NTP and set your time zone in WLED Config | Time.
//
// REQUIREMENT: You must add the following requirements to
// REQUIREMENT: "lib_deps" within platformio.ini / platformio_override.ini
// REQUIREMENT: * U8g2 (the version already in platformio.ini is fine)
// REQUIREMENT: * Wire
// REQUIREMENT: olikraus/U8g2@ ^2.34.15 (the version already in platformio.ini is fine)
//
//The SCL and SDA pins are defined here.
@@ -95,7 +94,9 @@ typedef enum {
SSD1305, // U8X8_SSD1305_128X32_ADAFRUIT_HW_I2C
SSD1305_64, // U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C
SSD1306_SPI, // U8X8_SSD1306_128X32_NONAME_HW_SPI
SSD1306_SPI64 // U8X8_SSD1306_128X64_NONAME_HW_SPI
SSD1306_SPI64=7, // U8X8_SSD1306_128X64_NONAME_HW_SPI
SSD1309_SPI64=8, // U8X8_SSD1309_128X64_NONAME0_4W_HW_SPI
SSD1327_SPI128=9 // U8X8_SSD1327_WS_128X128_4W_SW_SPI
} DisplayType;
@@ -163,6 +164,8 @@ class FourLineDisplayUsermod : public Usermod {
bool enabled = true;
#endif
bool contrastFix = false;
bool driverHW = false;
bool driverSPI = false;
// Next variables hold the previous known values to determine if redraw is
// required.
@@ -240,8 +243,8 @@ class FourLineDisplayUsermod : public Usermod {
if (u8x8 == nullptr) return;
if (FLD_SemaphoreTake(drawMux, maxWait) != pdTRUE) return; // WLEDMM acquire draw mutex
#if defined(ARDUINO_ARCH_ESP32) && !defined(OLD_4LD_FONTS) // WLEDMM use nicer 2x2 font on ESP32
if (!ignoreLH && lineHeight==2) {
#if defined(ARDUINO_ARCH_ESP32) && !defined(OLD_4LD_FONTS) // WLEDMM use nicer 2x2 font on ESP32
if (!ignoreLH && lineHeight>1) {
if(strlen(string) > 3) // WLEDMM little hack - less than 3 chars -> show in bold
u8x8->setFont(u8x8_font_7x14_1x2_r); // normal
else
@@ -254,8 +257,8 @@ class FourLineDisplayUsermod : public Usermod {
}
#else
u8x8->setFont(u8x8_font_chroma48medium8_r);
if (!ignoreLH && lineHeight==2) u8x8->draw1x2String(col, row, string);
else u8x8->drawString(col, row, string);
if (!ignoreLH && lineHeight>1) u8x8->draw1x2String(col, row, string);
else u8x8->drawString(col, row, string);
#endif
FLD_SemaphoreGive(drawMux); // WLEDMM release draw mutex
}
@@ -263,8 +266,8 @@ class FourLineDisplayUsermod : public Usermod {
if (!typeOK || !enabled) return;
if (u8x8 == nullptr) return;
if (FLD_SemaphoreTake(drawMux, maxWait) != pdTRUE) return; // WLEDMM acquire draw mutex
#if defined(ARDUINO_ARCH_ESP32) && !defined(OLD_4LD_FONTS) // WLEDMM use nicer 2x2 font on ESP32
if (lineHeight==2) { // WLEDMM use 2x3 on 128x64 displays
#if defined(ARDUINO_ARCH_ESP32) && !defined(OLD_4LD_FONTS) // WLEDMM use nicer 2x2 font on ESP32
if (lineHeight>1) { // WLEDMM use 2x3 on 128x64 displays
//u8x8->setFont(u8x8_font_profont29_2x3_r); // sans serif 2x3
u8x8->setFont(u8x8_font_courB18_2x3_r); // courier bold 2x3
u8x8->drawString(col, row + (row >3? 1:0), string);
@@ -276,7 +279,7 @@ class FourLineDisplayUsermod : public Usermod {
}
#else
u8x8->setFont(u8x8_font_chroma48medium8_r);
if (lineHeight==2) { // WLEDMM use 2x3 on 128x64 displays
if (lineHeight>1) { // WLEDMM use 2x3 on 128x64 displays
u8x8->draw2x2String(col, row + (row >3? 1:0), string);
} else {
u8x8->draw2x2String(col, row, string);
@@ -288,7 +291,7 @@ class FourLineDisplayUsermod : public Usermod {
if (!typeOK || !enabled) return;
if (FLD_SemaphoreTake(drawMux, maxWait) != pdTRUE) return; // WLEDMM acquire draw mutex
u8x8->setFont(font);
if (!ignoreLH && lineHeight==2) u8x8->draw1x2Glyph(col, row, glyph);
if (!ignoreLH && lineHeight>1) u8x8->draw1x2Glyph(col, row, glyph);
else u8x8->drawGlyph(col, row, glyph);
FLD_SemaphoreGive(drawMux); // WLEDMM release draw mutex
}
@@ -325,7 +328,7 @@ class FourLineDisplayUsermod : public Usermod {
void draw2x2GlyphIcons() {
if (!typeOK || !enabled) return; // WLEDMM make sure the display is initialized before we try to draw on it
if (FLD_SemaphoreTake(drawMuxBig, maxWaitLong) != pdTRUE) return; // WLEDMM acquire BIG draw mutex
if (lineHeight == 2) {
if (lineHeight > 1) {
drawGlyph( 1, 0, 1, u8x8_4LineDisplay_WLED_icons_2x2, true); //brightness icon
drawGlyph( 5, 0, 2, u8x8_4LineDisplay_WLED_icons_2x2, true); //speed icon
drawGlyph( 9, 0, 3, u8x8_4LineDisplay_WLED_icons_2x2, true); //intensity icon
@@ -379,7 +382,11 @@ class FourLineDisplayUsermod : public Usermod {
drawStatusIcons(); //icons power, wifi, timer, etc
if (lineHeight > 1) { // WLEDMM use extra space for useful information
strncpy_P(lineBuffer, PSTR(" "), LINE_BUFFER_SIZE);
#if defined(WLED_DEBUG) || defined(SR_DEBUG) || defined(SR_STATS)
snprintf_P(lineBuffer, LINE_BUFFER_SIZE, PSTR(" %-3.3s %-2.2s "), driverSPI? "SPI" : "I2C", driverHW? "hw" : "sw"); // WLEDMM driver info
#else
strncpy_P(lineBuffer, PSTR(" "), LINE_BUFFER_SIZE);
#endif
if (apActive) strncpy_P(lineBuffer, PSTR(" AP mode "), LINE_BUFFER_SIZE);
else if (!WLED_CONNECTED) strncpy_P(lineBuffer, PSTR(" NO NET "), LINE_BUFFER_SIZE);
if (WLED_MQTT_CONNECTED) lineBuffer[9] = 'M'; // "MQTT"
@@ -411,7 +418,7 @@ class FourLineDisplayUsermod : public Usermod {
void setup() {
if (!enabled) return; // typeOK = true will be set after successful setup
bool isHW, isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64);
bool isHW, isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64 || type > 7);
PinOwner po = PinOwner::UM_FourLineDisplay;
if (isSPI) {
if (ioPin[0] < 0 || ioPin[1] < 0) {
@@ -467,6 +474,8 @@ class FourLineDisplayUsermod : public Usermod {
}
}
driverHW = isHW;
driverSPI= isSPI;
DEBUG_PRINTLN(F("Allocating display."));
/*
// At some point it may be good to not new/delete U8X8 object but use this instead
@@ -538,6 +547,16 @@ class FourLineDisplayUsermod : public Usermod {
if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_4W_SW_SPI(ioPin[0], ioPin[1], ioPin[2], ioPin[3], ioPin[4]);
else u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_4W_HW_SPI(ioPin[2], ioPin[3], ioPin[4]); // Pins are cs, dc, reset
break;
case SSD1309_SPI64:
// u8x8 uses global SPI variable that is attached to VSPI bus on ESP32
if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1309_128X64_NONAME0_4W_SW_SPI(ioPin[0], ioPin[1], ioPin[2], ioPin[3], ioPin[4]);
else u8x8 = (U8X8 *) new U8X8_SSD1309_128X64_NONAME0_4W_HW_SPI(ioPin[2], ioPin[3], ioPin[4]); // Pins are cs, dc, reset
break;
case SSD1327_SPI128:
// u8x8 uses global SPI variable that is attached to VSPI bus on ESP32
if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1327_WS_128X128_4W_SW_SPI(ioPin[0], ioPin[1], ioPin[2], ioPin[3], ioPin[4]);
else u8x8 = (U8X8 *) new U8X8_SSD1327_WS_128X128_4W_HW_SPI(ioPin[2], ioPin[3], ioPin[4]); // Pins are cs, dc, reset
break;
default:
u8x8 = nullptr;
}
@@ -551,6 +570,8 @@ class FourLineDisplayUsermod : public Usermod {
}
lineHeight = u8x8->getRows() > 4 ? 2 : 1;
if (u8x8->getRows() > 8) lineHeight =3;
//if (u8x8->getRows() > 12) lineHeight =4;
if (isSPI) {
USER_PRINTLN(isHW ? F("Starting display (SPI HW).") : F("Starting display (SPI Soft)."));
} else {
@@ -568,7 +589,7 @@ class FourLineDisplayUsermod : public Usermod {
setPowerSave(0);
drawing = false;
// init semaphores to enable drawing
// init semaphores to allow drawing
FLD_SemaphoreGive(drawMux);
FLD_SemaphoreGive(drawMuxBig);
@@ -798,9 +819,9 @@ class FourLineDisplayUsermod : public Usermod {
uint8_t col = 15;
uint8_t row = 0;
drawGlyph(col, row, (wificonnected ? 20 : 0), u8x8_4LineDisplay_WLED_icons_1x1, true); // wifi icon
if (lineHeight==2) { col--; } else { row++; }
if (lineHeight>1) { col--; } else { row++; }
drawGlyph(col, row, (bri > 0 ? 9 : 0), u8x8_4LineDisplay_WLED_icons_1x1, true); // power icon
if (lineHeight==2) { col--; } else { col = row = 0; }
if (lineHeight>1) { col--; } else { col = row = 0; }
drawGlyph(col, row, (nightlightActive ? 6 : 0), u8x8_4LineDisplay_WLED_icons_1x1, true); // moon icon for nightlight mode
}
@@ -839,7 +860,7 @@ class FourLineDisplayUsermod : public Usermod {
for (byte i=5; i<=printedChars; i++) lineBuffer[i-5] = lineBuffer[i]; //include '\0'
printedChars -= 5;
}
if (lineHeight == 2) { // use this code for 8 line display
if (lineHeight > 1) { // use this code for 8 line display
char smallBuffer1[MAX_MODE_LINE_SPACE+1] = { '\0' };
char smallBuffer2[MAX_MODE_LINE_SPACE+1] = { '\0' };
uint8_t smallChars1 = 0;
@@ -868,11 +889,11 @@ class FourLineDisplayUsermod : public Usermod {
}
while (smallChars1 < (MAX_MODE_LINE_SPACE-1)) smallBuffer1[smallChars1++]=' ';
smallBuffer1[smallChars1] = 0;
smallBuffer1[MAX_MODE_LINE_SPACE -1] = '\0'; // ensure the string ends where it should (while loop avove can overshoot by 1)
smallBuffer1[MAX_MODE_LINE_SPACE -1] = '\0'; // ensure the string ends where it should (while loop above can overshoot by 1)
drawString(1, row*lineHeight, smallBuffer1, true);
while (smallChars2 < (MAX_MODE_LINE_SPACE-1)) smallBuffer2[smallChars2++]=' ';
smallBuffer2[smallChars2] = 0;
smallBuffer2[MAX_MODE_LINE_SPACE -1] = '\0'; // ensure the string ends where it should (while loop avove can overshoot by 1)
smallBuffer2[MAX_MODE_LINE_SPACE -1] = '\0'; // ensure the string ends where it should (while loop above can overshoot by 1)
drawString(1, row*lineHeight+1, smallBuffer2, true);
}
} else { // use this code for 4 ling displays
@@ -926,7 +947,7 @@ class FourLineDisplayUsermod : public Usermod {
if (!wakeDisplay()) clear();
// Print the overlay
if (glyphType>0 && glyphType<255) {
if (lineHeight == 2) drawGlyph(5, 0, glyphType, u8x8_4LineDisplay_WLED_icons_6x6, true); // use 3x3 font with draw2x2Glyph() if flash runs short and comment out 6x6 font
if (lineHeight > 1) drawGlyph(5, 0, glyphType, u8x8_4LineDisplay_WLED_icons_6x6, true); // use 3x3 font with draw2x2Glyph() if flash runs short and comment out 6x6 font
else drawGlyph(6, 0, glyphType, u8x8_4LineDisplay_WLED_icons_3x3, true);
}
if (line1) {
@@ -950,7 +971,7 @@ class FourLineDisplayUsermod : public Usermod {
// Turn the display back on
if (!wakeDisplay()) clear();
// Print the overlay
if (lineHeight == 2) {
if (lineHeight > 1) {
//add a bit of randomness
switch (millis()%3) {
case 0:
@@ -1242,7 +1263,9 @@ class FourLineDisplayUsermod : public Usermod {
oappend(SET_F("addOption(dd,'SSD1305 128x64',5);"));
oappend(SET_F("addOption(dd,'SSD1306 SPI',6);"));
oappend(SET_F("addOption(dd,'SSD1306 SPI 128x64',7);"));
bool isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64);
oappend(SET_F("addOption(dd,'SSD1309 SPI 128x64',8);"));
oappend(SET_F("addOption(dd,'SSD1327 SPI 128x128',9);"));
bool isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64 || type > 7);
// WLEDMM add defaults
oappend(SET_F("addInfo('4LineDisplay:pin[]',0,'','I2C/SPI CLK');"));
oappend(SET_F("dRO('4LineDisplay:pin[]',0);")); // disable read only pins
@@ -1305,7 +1328,7 @@ class FourLineDisplayUsermod : public Usermod {
void addToConfig(JsonObject& root) {
// determine if we are using global HW pins (data & clock)
int8_t hw_dta, hw_clk;
if ((type == SSD1306_SPI || type == SSD1306_SPI64)) {
if ((type == SSD1306_SPI || type == SSD1306_SPI64) || (type > 7)) {
hw_clk = spi_sclk;
hw_dta = spi_mosi;
} else {
@@ -1371,7 +1394,7 @@ class FourLineDisplayUsermod : public Usermod {
clockMode = top[FPSTR(_clockMode)] | clockMode;
showSeconds = top[FPSTR(_showSeconds)] | showSeconds;
contrastFix = top[FPSTR(_contrastFix)] | contrastFix;
if (newType == SSD1306_SPI || newType == SSD1306_SPI64)
if (newType == SSD1306_SPI || newType == SSD1306_SPI64 || newType > 7)
ioFrequency = min(20000, max(500, (int)(top[FPSTR(_busClkFrequency)] | ioFrequency/1000))) * 1000; // limit frequency
else
ioFrequency = min(3400, max(100, (int)(top[FPSTR(_busClkFrequency)] | ioFrequency/1000))) * 1000; // limit frequency
@@ -1401,7 +1424,7 @@ class FourLineDisplayUsermod : public Usermod {
USER_PRINTLN(F("Display terminated."));
}
PinOwner po = PinOwner::UM_FourLineDisplay;
bool isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64);
bool isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64 || type > 7);
if (isSPI) {
pinManager.deallocateMultiplePins((const uint8_t *)(&oldPin[2]), 3, po);
bool isHW = (oldPin[0]==spi_sclk && oldPin[1]==spi_mosi);