From a442287c9b27054720e242d5016ec81fc1a3a018 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sun, 19 Nov 2023 20:23:33 +0100 Subject: [PATCH 1/9] [P012] Add support for ST7032 (LCD 2x16) --- lib/LiquidCrystal_I2C/LiquidCrystal_I2C.cpp | 79 ++++++++++----- lib/LiquidCrystal_I2C/LiquidCrystal_I2C.h | 26 +++++ src/_P012_LCD.ino | 49 ++++++---- src/src/PluginStructs/P012_data_struct.cpp | 103 ++++++++++++-------- src/src/PluginStructs/P012_data_struct.h | 31 ++++-- 5 files changed, 194 insertions(+), 94 deletions(-) diff --git a/lib/LiquidCrystal_I2C/LiquidCrystal_I2C.cpp b/lib/LiquidCrystal_I2C/LiquidCrystal_I2C.cpp index 37143d30a4..ad1079f145 100755 --- a/lib/LiquidCrystal_I2C/LiquidCrystal_I2C.cpp +++ b/lib/LiquidCrystal_I2C/LiquidCrystal_I2C.cpp @@ -84,32 +84,41 @@ void LiquidCrystal_I2C::begin(uint8_t cols, uint8_t lines, uint8_t dotsize) { // before sending commands. Arduino can turn on way befer 4.5V so we'll wait 50 delay(50); - // Now we pull both RS and R/W low to begin commands - expanderWrite(_backlightval); // reset expanderand turn backlight off (Bit 8 =1) - delay(1000); - - //put the LCD into 4 bit mode - // this is according to the hitachi HD44780 datasheet - // figure 24, pg 46 - - // we start in 8bit mode, try to set 4 bit mode - write4bits(0x03 << 4); - delayMicroseconds(4500); // wait min 4.1ms - - // second try - write4bits(0x03 << 4); - delayMicroseconds(4500); // wait min 4.1ms - - // third go! - write4bits(0x03 << 4); - delayMicroseconds(150); - - // finally, set to 4-bit interface - write4bits(0x02 << 4); - - - // set # lines, font size, etc. - command(LCD_FUNCTIONSET | _displayfunction); + if (LCD_AltMode::None == _altMode) { + // Now we pull both RS and R/W low to begin commands + expanderWrite(_backlightval); // reset expander and turn backlight off (Bit 8 =1) + delay(1000); // FIXME tonhuisman Remove this huge, historic, delay + + //put the LCD into 4 bit mode + // this is according to the hitachi HD44780 datasheet + // figure 24, pg 46 + + // we start in 8bit mode, try to set 4 bit mode + write4bits(0x03 << 4); + delayMicroseconds(4500); // wait min 4.1ms + + // second try + write4bits(0x03 << 4); + delayMicroseconds(4500); // wait min 4.1ms + + // third go! + write4bits(0x03 << 4); + delayMicroseconds(150); + + // finally, set to 4-bit interface + write4bits(0x02 << 4); + + // set # lines, font size, etc. + command(LCD_FUNCTIONSET | _displayfunction); + + } else + if (LCD_AltMode::ST7032 == _altMode) { + extendFunctionSet(); + command(LCD_EX_SETBIASOSC | LCD_BIAS_1_5 | LCD_OSC_183HZ); // 1/5bias, OSC=183Hz@3.0V + command(LCD_EX_FOLLOWERCONTROL | LCD_FOLLOWER_ON | LCD_RAB_2_00); // internal follower circuit is turn on + delay(200); // Wait time >200ms (for power stable) + normalFunctionSet(); + } // turn the display on with no cursor or blinking default _displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF; @@ -245,9 +254,25 @@ void LiquidCrystal_I2C::backlight(void) { /*********** mid level commands, for sending data/cmds */ inline void LiquidCrystal_I2C::command(uint8_t value) { - send(value, 0); + if (LCD_AltMode::None == _altMode) { + send(value, 0); + } else + if (LCD_AltMode::ST7032 == _altMode) { + Wire.beginTransmission(_Addr); + Wire.write((uint8_t)0x00); + Wire.write(value); + Wire.endTransmission(); + delayMicroseconds(27); // >26.3us + } } +void LiquidCrystal_I2C::normalFunctionSet() { + command(LCD_FUNCTIONSET | _displayfunction); +} + +void LiquidCrystal_I2C::extendFunctionSet() { + command(LCD_FUNCTIONSET | _displayfunction | LCD_EX_INSTRUCTION); +} /************ low level data pushing commands **********/ diff --git a/lib/LiquidCrystal_I2C/LiquidCrystal_I2C.h b/lib/LiquidCrystal_I2C/LiquidCrystal_I2C.h index f7c7d132c3..b36b4df0f1 100755 --- a/lib/LiquidCrystal_I2C/LiquidCrystal_I2C.h +++ b/lib/LiquidCrystal_I2C/LiquidCrystal_I2C.h @@ -52,9 +52,29 @@ #define Rw B00000010 // Read/Write bit #define Rs B00000001 // Register select bit +// flags for _altMode: +enum class LCD_AltMode : uint8_t { + None = 0u, // Regular Hitachi controller + ST7032, // ST7032 controller + }; + +// extra defines for ST7032 controller +#define LCD_EX_INSTRUCTION 0x01 // IS: instruction table select +#define LCD_EX_SETBIASOSC 0x10 // Bias selection / Internal OSC frequency adjust +#define LCD_EX_SETICONRAMADDR 0x40 // Set ICON RAM address +#define LCD_EX_POWICONCONTRASTH 0x50 // Power / ICON control / Contrast set(high byte) +#define LCD_EX_FOLLOWERCONTROL 0x60 // Follower control +#define LCD_EX_CONTRASTSETL 0x70 // Contrast set(low byte) +#define LCD_OSC_183HZ 0x04 // 183Hz@3.0V +#define LCD_FOLLOWER_ON 0x08 // internal follower circuit is turn on +#define LCD_FOLLOWER_OFF 0x00 // internal follower circuit is turn off +#define LCD_RAB_2_00 0x04 // 1+(Rb/Ra)=2.00 +#define LCD_BIAS_1_5 0x00 // bias will be 1/5 + class LiquidCrystal_I2C : public Print { public: LiquidCrystal_I2C(uint8_t lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows); + virtual ~LiquidCrystal_I2C() {} void begin(uint8_t cols, uint8_t rows, uint8_t charsize = LCD_5x8DOTS ); void clear(); void home(); @@ -89,6 +109,9 @@ class LiquidCrystal_I2C : public Print { void command(uint8_t); void init(); void oled_init(); + void setAltMode(LCD_AltMode altMode) { + _altMode = altMode; + } ////compatibility API function aliases void blink_on(); // alias for blink() @@ -117,6 +140,8 @@ void draw_vertical_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixe void write4bits(uint8_t); void expanderWrite(uint8_t); void pulseEnable(uint8_t); + void normalFunctionSet(); + void extendFunctionSet(); uint8_t _Addr; uint8_t _displayfunction; uint8_t _displaycontrol; @@ -126,6 +151,7 @@ void draw_vertical_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixe uint8_t _cols; uint8_t _rows; uint8_t _backlightval; + LCD_AltMode _altMode = LCD_AltMode::None; }; #endif diff --git a/src/_P012_LCD.ino b/src/_P012_LCD.ino index 5ad557ddc8..e20bdf114c 100644 --- a/src/_P012_LCD.ino +++ b/src/_P012_LCD.ino @@ -10,6 +10,7 @@ // ####################################################################################################### /** Changelog: + * 2023-11-18 tonhuisman: Trying to include support for Midas displays MD21605B6W-FTPLWI3 ST7032 (16x2 at I2C 0x3E) * 2023-03-07 tonhuisman: Parse text to display without trimming off leading and trailing spaces * 2023-03: First changelog added, older changes not logged */ @@ -95,11 +96,17 @@ boolean Plugin_012(uint8_t function, struct EventStruct *event, String& string) { { const __FlashStringHelper *options2[] = { - F("2 x 16"), - F("4 x 20"), + toString(P012_DisplaySize_e::LCD_2x16), + toString(P012_DisplaySize_e::LCD_4x20), + toString(P012_DisplaySize_e::LCD_2x16_ST7032), }; - const int optionValues2[2] = { 1, 2 }; - addFormSelector(F("Display Size"), F("psize"), 2, options2, optionValues2, P012_SIZE); + const int optionValues2[] = { + static_cast(P012_DisplaySize_e::LCD_2x16), + static_cast(P012_DisplaySize_e::LCD_4x20), + static_cast(P012_DisplaySize_e::LCD_2x16_ST7032), + }; + constexpr int optionCount2 = NR_ELEMENTS(optionValues2); + addFormSelector(F("Display Size"), F("psize"), optionCount2, options2, optionValues2, P012_SIZE); } { @@ -125,8 +132,9 @@ boolean Plugin_012(uint8_t function, struct EventStruct *event, String& string) F("Truncate exceeding message"), F("Clear then truncate exceeding message"), }; - const int optionValues3[] = { 0, 1, 2 }; - addFormSelector(F("LCD command Mode"), F("pmode"), 3, options3, optionValues3, P012_MODE); + const int optionValues3[] = { 0, 1, 2 }; + constexpr int optionCount3 = NR_ELEMENTS(optionValues3); + addFormSelector(F("LCD command Mode"), F("pmode"), optionCount3, options3, optionValues3, P012_MODE); } success = true; @@ -162,7 +170,10 @@ boolean Plugin_012(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_INIT: { - initPluginTaskData(event->TaskIndex, new (std::nothrow) P012_data_struct(P012_I2C_ADDR, P012_SIZE, P012_MODE, P012_TIMER)); + initPluginTaskData(event->TaskIndex, new (std::nothrow) P012_data_struct(P012_I2C_ADDR, + static_cast(P012_SIZE), + P012_MODE, + P012_TIMER)); P012_data_struct *P012_data = static_cast(getPluginTaskData(event->TaskIndex)); @@ -236,25 +247,27 @@ boolean Plugin_012(uint8_t function, struct EventStruct *event, String& string) P012_data_struct *P012_data = static_cast(getPluginTaskData(event->TaskIndex)); - if (nullptr != P012_data) { + if ((nullptr != P012_data) && P012_data->isValid()) { String cmd = parseString(string, 1); - if (cmd.equalsIgnoreCase(F("LCDCMD"))) + if (equals(cmd, F("lcdcmd"))) { - success = true; String arg1 = parseString(string, 2); - if (arg1.equalsIgnoreCase(F("Off"))) { - P012_data->lcd.noBacklight(); + if (equals(arg1, F("off"))) { + P012_data->lcd->noBacklight(); + success = true; } - else if (arg1.equalsIgnoreCase(F("On"))) { - P012_data->lcd.backlight(); + else if (equals(arg1, F("on"))) { + P012_data->lcd->backlight(); + success = true; } - else if (arg1.equalsIgnoreCase(F("Clear"))) { - P012_data->lcd.clear(); + else if (equals(arg1, F("clear"))) { + P012_data->lcd->clear(); + success = true; } } - else if (cmd.equalsIgnoreCase(F("LCD"))) + else if (equals(cmd, F("lcd"))) { success = true; int colPos = event->Par2 - 1; @@ -264,8 +277,8 @@ boolean Plugin_012(uint8_t function, struct EventStruct *event, String& string) P012_data->lcdWrite(text, colPos, rowPos); } - break; } + break; } } return success; diff --git a/src/src/PluginStructs/P012_data_struct.cpp b/src/src/PluginStructs/P012_data_struct.cpp index 0d72e4f670..519c1e6c0b 100644 --- a/src/src/PluginStructs/P012_data_struct.cpp +++ b/src/src/PluginStructs/P012_data_struct.cpp @@ -6,75 +6,99 @@ // is in a directory which is excluded in the src_filter # include +const __FlashStringHelper* toString(P012_DisplaySize_e display) { + switch (display) { + case P012_DisplaySize_e::LCD_2x16: return F("2 x 16"); + case P012_DisplaySize_e::LCD_4x20: return F("4 x 20"); + case P012_DisplaySize_e::LCD_2x16_ST7032: return F("2 x 16 (ST7032)"); + } + return F(""); +} -P012_data_struct::P012_data_struct(uint8_t addr, - uint8_t lcd_size, - uint8_t mode, - uint8_t timer) : - lcd(addr, 20, 4), +P012_data_struct::P012_data_struct(uint8_t addr, + P012_DisplaySize_e lcd_size, + uint8_t mode, + uint8_t timer) : Plugin_012_mode(mode), displayTimer(timer) { - switch (lcd_size) - { - case 1: + // Get display parameters right + switch (lcd_size) { + case P012_DisplaySize_e::LCD_2x16: + case P012_DisplaySize_e::LCD_2x16_ST7032: Plugin_012_rows = 2; Plugin_012_cols = 16; break; - case 2: + case P012_DisplaySize_e::LCD_4x20: Plugin_012_rows = 4; Plugin_012_cols = 20; break; + } - default: - Plugin_012_rows = 2; - Plugin_012_cols = 16; - break; + // Create the display object + lcd = new (std::nothrow) LiquidCrystal_I2C(addr, Plugin_012_cols, Plugin_012_rows); + + if (isValid()) { + // If all is fine, set any additional parameters + switch (lcd_size) { + case P012_DisplaySize_e::LCD_2x16: + case P012_DisplaySize_e::LCD_4x20: + break; + case P012_DisplaySize_e::LCD_2x16_ST7032: + lcd->setAltMode(LCD_AltMode::ST7032); // Set alternative ST7032 mode, with adjusted initialization + break; + } } } void P012_data_struct::init() { - // Setup LCD display - lcd.init(); // initialize the lcd - lcd.backlight(); - lcd.print(F("ESP Easy")); - createCustomChars(); + if (isValid()) { + // Setup LCD display + lcd->init(); // initialize the lcd + lcd->backlight(); + lcd->print(F("ESP Easy")); + createCustomChars(); + } } void P012_data_struct::setBacklightTimer(uint8_t timer) { - displayTimer = timer; - lcd.backlight(); + if (isValid()) { + displayTimer = timer; + lcd->backlight(); + } } void P012_data_struct::checkTimer() { - if (displayTimer > 0) + if (isValid() && (displayTimer > 0)) { displayTimer--; if (displayTimer == 0) { - lcd.noBacklight(); + lcd->noBacklight(); } } } void P012_data_struct::lcdWrite(const String& text, uint8_t col, uint8_t row) { + if (!isValid()) { return; } + // clear line before writing new string if (Plugin_012_mode == 2) { - lcd.setCursor(col, row); + lcd->setCursor(col, row); for (uint8_t i = col; i < Plugin_012_cols; i++) { - lcd.print(' '); + lcd->print(' '); } } - lcd.setCursor(col, row); + lcd->setCursor(col, row); if ((Plugin_012_mode == 1) || (Plugin_012_mode == 2)) { - lcd.setCursor(col, row); + lcd->setCursor(col, row); for (uint8_t i = 0; i < Plugin_012_cols - col; i++) { if (text[i]) { - lcd.print(text[i]); + lcd->print(text[i]); } } } @@ -88,13 +112,13 @@ void P012_data_struct::lcdWrite(const String& text, uint8_t col, uint8_t row) { while (stillProcessing) { if (++col > Plugin_012_cols) { // have we printed 20 characters yet (+1 for the logic) row += 1; - lcd.setCursor(0, row); // move cursor down + lcd->setCursor(0, row); // move cursor down col = 1; } // dont print if "lower" than the lcd if (row < Plugin_012_rows) { - lcd.print(text[charCount - 1]); + lcd->print(text[charCount - 1]); } if (!text[charCount]) { // no more chars to process? @@ -103,7 +127,7 @@ void P012_data_struct::lcdWrite(const String& text, uint8_t col, uint8_t row) { charCount += 1; } - // lcd.print(text.c_str()); + // lcd->print(text.c_str()); // end fix } } @@ -229,6 +253,7 @@ String P012_data_struct::P012_parseTemplate(String& tmpString, uint8_t lineSize) } void P012_data_struct::createCustomChars() { + if (!isValid()) { return; } # ifdef USES_P012_POLISH_CHARS /* @@ -266,15 +291,15 @@ void P012_data_struct::createCustomChars() { static const char LETTER_z2[8] PROGMEM = { // z z kropka 0b00100, 0b00000, 0b11111, 0b00010, 0b00100, 0b01000, 0b11111, 0b00000 }; - lcd.createChar(0, LETTER_o); // probably defected memory cell - lcd.createChar(1, LETTER_l); - lcd.createChar(2, LETTER_e); - lcd.createChar(3, LETTER_c); - lcd.createChar(4, LETTER_n); - lcd.createChar(5, LETTER_a); - lcd.createChar(6, LETTER_s); - lcd.createChar(7, LETTER_z2); - lcd.createChar(8, LETTER_o); + lcd->createChar(0, LETTER_o); // probably defected memory cell + lcd->createChar(1, LETTER_l); + lcd->createChar(2, LETTER_e); + lcd->createChar(3, LETTER_c); + lcd->createChar(4, LETTER_n); + lcd->createChar(5, LETTER_a); + lcd->createChar(6, LETTER_s); + lcd->createChar(7, LETTER_z2); + lcd->createChar(8, LETTER_o); # endif // ifdef USES_P012_POLISH_CHARS } diff --git a/src/src/PluginStructs/P012_data_struct.h b/src/src/PluginStructs/P012_data_struct.h index ef4a77b1a9..6f24c1af6d 100644 --- a/src/src/PluginStructs/P012_data_struct.h +++ b/src/src/PluginStructs/P012_data_struct.h @@ -7,12 +7,20 @@ # include +enum class P012_DisplaySize_e : uint8_t { + LCD_2x16 = 1u, // Value is stored in user settings, so don't change! + LCD_4x20, + LCD_2x16_ST7032, +}; + +const __FlashStringHelper* toString(P012_DisplaySize_e display); + struct P012_data_struct : public PluginTaskData_base { - P012_data_struct(uint8_t addr, - uint8_t lcd_size, - uint8_t mode, - uint8_t timer); - P012_data_struct() = delete; + P012_data_struct(uint8_t addr, + P012_DisplaySize_e lcd_size, + uint8_t mode, + uint8_t timer); + P012_data_struct() = delete; virtual ~P012_data_struct() = default; void init(); @@ -30,12 +38,15 @@ struct P012_data_struct : public PluginTaskData_base { void createCustomChars(); + bool isValid() { + return nullptr != lcd; + } - LiquidCrystal_I2C lcd; - int Plugin_012_cols = 16; - int Plugin_012_rows = 2; - int Plugin_012_mode = 1; - uint8_t displayTimer = 0; + LiquidCrystal_I2C *lcd = nullptr; + int Plugin_012_cols = 16; + int Plugin_012_rows = 2; + int Plugin_012_mode = 1; + uint8_t displayTimer = 0; }; #endif // ifdef USES_P012 From d2e3a030efa05d46e13d089983866615dded6280 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sun, 19 Nov 2023 20:25:42 +0100 Subject: [PATCH 2/9] [P012] Update documentation --- docs/source/Plugin/P012.rst | 2 ++ docs/source/Plugin/P012_DisplaySize.png | Bin 4783 -> 10715 bytes 2 files changed, 2 insertions(+) diff --git a/docs/source/Plugin/P012.rst b/docs/source/Plugin/P012.rst index 0ef6a14e5a..b2eebc7147 100644 --- a/docs/source/Plugin/P012.rst +++ b/docs/source/Plugin/P012.rst @@ -51,6 +51,8 @@ Device Settings .. image:: P012_DisplaySize.png +To support an alternative LCD Controller, using the ST7032 chip, an extra selection has been added. This controller uses the (fixed) I2C address ``0x3E``, so that has to be configured as well. + **Line 1..4** Up to 4 lines can be pre-configured with text to display. For the 2x16 device size, only Line 1 and 2 will be used. Variables can be used, f.e. to display values provided by a sensor. **Display button** A button can be configured to enable the display on demand. This function is enabled by selecting a GPIO pin to monitor. Combined with Inversed login and Display Timeout the display will be automatically turned off after the timeout has expired. diff --git a/docs/source/Plugin/P012_DisplaySize.png b/docs/source/Plugin/P012_DisplaySize.png index f6f3c5efe606df1310e25846104606e81ea45207..6b995305bce3f32bccdfcb4532dc248ef07741e3 100644 GIT binary patch literal 10715 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 From 0ef2d0098b3157cbb7d6d3a49d6515b8788ef5b3 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sun, 19 Nov 2023 20:43:51 +0100 Subject: [PATCH 3/9] [P012] Fix destructor --- src/src/PluginStructs/P012_data_struct.cpp | 5 +++++ src/src/PluginStructs/P012_data_struct.h | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/src/PluginStructs/P012_data_struct.cpp b/src/src/PluginStructs/P012_data_struct.cpp index 519c1e6c0b..b0c0aa480d 100644 --- a/src/src/PluginStructs/P012_data_struct.cpp +++ b/src/src/PluginStructs/P012_data_struct.cpp @@ -51,6 +51,11 @@ P012_data_struct::P012_data_struct(uint8_t addr, } } +P012_data_struct::~P012_data_struct() { + delete lcd; + lcd = nullptr; +} + void P012_data_struct::init() { if (isValid()) { // Setup LCD display diff --git a/src/src/PluginStructs/P012_data_struct.h b/src/src/PluginStructs/P012_data_struct.h index 6f24c1af6d..5564b8ae7f 100644 --- a/src/src/PluginStructs/P012_data_struct.h +++ b/src/src/PluginStructs/P012_data_struct.h @@ -21,7 +21,7 @@ struct P012_data_struct : public PluginTaskData_base { uint8_t mode, uint8_t timer); P012_data_struct() = delete; - virtual ~P012_data_struct() = default; + virtual ~P012_data_struct(); void init(); From 91531db94bcfe3929e710915012fbe66c5769d81 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Mon, 20 Nov 2023 20:18:50 +0100 Subject: [PATCH 4/9] [P012] Fix some missing ST7032 code --- lib/LiquidCrystal_I2C/LiquidCrystal_I2C.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/LiquidCrystal_I2C/LiquidCrystal_I2C.cpp b/lib/LiquidCrystal_I2C/LiquidCrystal_I2C.cpp index ad1079f145..a1fd508801 100755 --- a/lib/LiquidCrystal_I2C/LiquidCrystal_I2C.cpp +++ b/lib/LiquidCrystal_I2C/LiquidCrystal_I2C.cpp @@ -278,10 +278,19 @@ void LiquidCrystal_I2C::extendFunctionSet() { // write either command or data void LiquidCrystal_I2C::send(uint8_t value, uint8_t mode) { - uint8_t highnib=value&0xf0; - uint8_t lownib=(value<<4)&0xf0; - write4bits((highnib)|mode); - write4bits((lownib)|mode); + if (LCD_AltMode::None == _altMode) { + uint8_t highnib=value&0xf0; + uint8_t lownib=(value<<4)&0xf0; + write4bits((highnib)|mode); + write4bits((lownib)|mode); + } else + if (LCD_AltMode::ST7032 == _altMode) { + Wire.beginTransmission(_Addr); + Wire.write((uint8_t)0x40); + Wire.write(value); + Wire.endTransmission(); + delayMicroseconds(27); // >26.3us + } } void LiquidCrystal_I2C::write4bits(uint8_t value) { From 9f8f25bffca7693dec18f03c0cc15231de952ded Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Mon, 20 Nov 2023 23:18:05 +0100 Subject: [PATCH 5/9] [P012] Use alternative ST7032 initialization code --- lib/LiquidCrystal_I2C/LiquidCrystal_I2C.cpp | 33 ++++++++++++++++----- lib/LiquidCrystal_I2C/LiquidCrystal_I2C.h | 5 ++++ 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/lib/LiquidCrystal_I2C/LiquidCrystal_I2C.cpp b/lib/LiquidCrystal_I2C/LiquidCrystal_I2C.cpp index a1fd508801..84be02594c 100755 --- a/lib/LiquidCrystal_I2C/LiquidCrystal_I2C.cpp +++ b/lib/LiquidCrystal_I2C/LiquidCrystal_I2C.cpp @@ -113,11 +113,23 @@ void LiquidCrystal_I2C::begin(uint8_t cols, uint8_t lines, uint8_t dotsize) { } else if (LCD_AltMode::ST7032 == _altMode) { - extendFunctionSet(); - command(LCD_EX_SETBIASOSC | LCD_BIAS_1_5 | LCD_OSC_183HZ); // 1/5bias, OSC=183Hz@3.0V - command(LCD_EX_FOLLOWERCONTROL | LCD_FOLLOWER_ON | LCD_RAB_2_00); // internal follower circuit is turn on - delay(200); // Wait time >200ms (for power stable) - normalFunctionSet(); + /*** + * Using BlackBrix library: + */ + // extendFunctionSet(); + // command(LCD_EX_SETBIASOSC | LCD_BIAS_1_5 | LCD_OSC_183HZ); // 1/5bias, OSC=183Hz@3.0V + // command(LCD_EX_FOLLOWERCONTROL | LCD_FOLLOWER_ON | LCD_RAB_2_00); // internal follower circuit is turn on + // delay(200); // Wait time >200ms (for power stable) + // normalFunctionSet(); + /*** + * Using olkal library: + */ + command(LCD_FUNCTIONSET | LCD_8BITMODE | LCD_2LINE); + command(LCD_EX_SETBIASOSC | LCD_ICON_ON | LCD_OSC_183HZ); + command(LCD_EX_POWICONCONTRASTH | LCD_ICON_ON); + setContrast(_contrast); + command(LCD_EX_FOLLOWERCONTROL | LCD_FOLLOWER_ON | LCD_RAB_2_00); + delay(300); // FIXME tonhuisman: Fix delays } // turn the display on with no cursor or blinking default @@ -259,7 +271,7 @@ inline void LiquidCrystal_I2C::command(uint8_t value) { } else if (LCD_AltMode::ST7032 == _altMode) { Wire.beginTransmission(_Addr); - Wire.write((uint8_t)0x00); + Wire.write((uint8_t)0x80); // Single byte command Wire.write(value); Wire.endTransmission(); delayMicroseconds(27); // >26.3us @@ -349,6 +361,14 @@ void LiquidCrystal_I2C::printstr(const char c[]){ print(c); } +void LiquidCrystal_I2C::setContrast(uint8_t val) { + if (val > LCD_CONTRAST_MAX) val = LCD_CONTRAST_MIN; + else if (val < LCD_CONTRAST_MIN) val = LCD_CONTRAST_MAX; + command(LCD_EX_CONTRASTSETL | (val & B00001111)); + command((val >> 4) | LCD_EX_POWICONCONTRASTH | POWER_ICON_BOST_CONTR_Bon); + _contrast = val; +} + // unsupported API functions #pragma GCC diagnostic push @@ -361,5 +381,4 @@ uint8_t LiquidCrystal_I2C::keypad (){return 0;} uint8_t LiquidCrystal_I2C::init_bargraph(uint8_t graphtype){return 0;} void LiquidCrystal_I2C::draw_horizontal_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_col_end){} void LiquidCrystal_I2C::draw_vertical_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_row_end){} -void LiquidCrystal_I2C::setContrast(uint8_t new_val){} #pragma GCC diagnostic pop diff --git a/lib/LiquidCrystal_I2C/LiquidCrystal_I2C.h b/lib/LiquidCrystal_I2C/LiquidCrystal_I2C.h index b36b4df0f1..0d1b69b97d 100755 --- a/lib/LiquidCrystal_I2C/LiquidCrystal_I2C.h +++ b/lib/LiquidCrystal_I2C/LiquidCrystal_I2C.h @@ -65,11 +65,15 @@ enum class LCD_AltMode : uint8_t { #define LCD_EX_POWICONCONTRASTH 0x50 // Power / ICON control / Contrast set(high byte) #define LCD_EX_FOLLOWERCONTROL 0x60 // Follower control #define LCD_EX_CONTRASTSETL 0x70 // Contrast set(low byte) +#define LCD_ICON_ON 0x08 // ICON display on #define LCD_OSC_183HZ 0x04 // 183Hz@3.0V #define LCD_FOLLOWER_ON 0x08 // internal follower circuit is turn on #define LCD_FOLLOWER_OFF 0x00 // internal follower circuit is turn off #define LCD_RAB_2_00 0x04 // 1+(Rb/Ra)=2.00 #define LCD_BIAS_1_5 0x00 // bias will be 1/5 +#define LCD_CONTRAST_MAX 0x3F // limit range max value (0x00 - 0x3F) +#define LCD_CONTRAST_MIN 0x00 // limit range min value (0x00 - 0x3F) +#define POWER_ICON_BOST_CONTR_Bon 0x04 //Ion: ICON display on/off class LiquidCrystal_I2C : public Print { public: @@ -151,6 +155,7 @@ void draw_vertical_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixe uint8_t _cols; uint8_t _rows; uint8_t _backlightval; + uint8_t _contrast = 0x18; // Default for ST7032 LCD_AltMode _altMode = LCD_AltMode::None; }; From 6598b2d5f6eaef07b413d705ffd7c9808a07264a Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Tue, 21 Nov 2023 21:28:25 +0100 Subject: [PATCH 6/9] [P012] Enable changing contrast for ST7032 displays, update docs --- docs/source/Plugin/P012_commands.repl | 1 + lib/LiquidCrystal_I2C/LiquidCrystal_I2C.cpp | 12 +++++++----- src/_P012_LCD.ino | 8 ++++++++ 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/docs/source/Plugin/P012_commands.repl b/docs/source/Plugin/P012_commands.repl index 4300fb2fad..f5e6be4518 100644 --- a/docs/source/Plugin/P012_commands.repl +++ b/docs/source/Plugin/P012_commands.repl @@ -10,6 +10,7 @@ * ``on`` will turn the display ON. * ``off`` will turn the display OFF. * ``clear`` will clear any information from the display. + * ``contrast,`` will set the contrast of the display, valid range: 0..63 "," Using these commands, either from rules, via http or mqtt, the state of the display can be controlled. diff --git a/lib/LiquidCrystal_I2C/LiquidCrystal_I2C.cpp b/lib/LiquidCrystal_I2C/LiquidCrystal_I2C.cpp index 84be02594c..752f97a5a5 100755 --- a/lib/LiquidCrystal_I2C/LiquidCrystal_I2C.cpp +++ b/lib/LiquidCrystal_I2C/LiquidCrystal_I2C.cpp @@ -362,11 +362,13 @@ void LiquidCrystal_I2C::printstr(const char c[]){ } void LiquidCrystal_I2C::setContrast(uint8_t val) { - if (val > LCD_CONTRAST_MAX) val = LCD_CONTRAST_MIN; - else if (val < LCD_CONTRAST_MIN) val = LCD_CONTRAST_MAX; - command(LCD_EX_CONTRASTSETL | (val & B00001111)); - command((val >> 4) | LCD_EX_POWICONCONTRASTH | POWER_ICON_BOST_CONTR_Bon); - _contrast = val; + if (LCD_AltMode::ST7032 == _altMode) { + if (val > LCD_CONTRAST_MAX) val = LCD_CONTRAST_MIN; + else if (val < LCD_CONTRAST_MIN) val = LCD_CONTRAST_MAX; + command(LCD_EX_CONTRASTSETL | (val & B00001111)); + command((val >> 4) | LCD_EX_POWICONCONTRASTH | POWER_ICON_BOST_CONTR_Bon); + _contrast = val; + } } diff --git a/src/_P012_LCD.ino b/src/_P012_LCD.ino index e20bdf114c..32fb474a55 100644 --- a/src/_P012_LCD.ino +++ b/src/_P012_LCD.ino @@ -10,6 +10,7 @@ // ####################################################################################################### /** Changelog: + * 2023-11-21 tonhuisman: Add support for contrast for ST7032 based displays * 2023-11-18 tonhuisman: Trying to include support for Midas displays MD21605B6W-FTPLWI3 ST7032 (16x2 at I2C 0x3E) * 2023-03-07 tonhuisman: Parse text to display without trimming off leading and trailing spaces * 2023-03: First changelog added, older changes not logged @@ -266,6 +267,13 @@ boolean Plugin_012(uint8_t function, struct EventStruct *event, String& string) P012_data->lcd->clear(); success = true; } + else if (equals(arg1, F("contrast")) && + (P012_DisplaySize_e::LCD_2x16_ST7032 == static_cast(P012_SIZE)) && + (event->Par2 >= LCD_CONTRAST_MIN) && + (event->Par2 <= LCD_CONTRAST_MAX)) { + P012_data->lcd->setContrast(event->Par2); + success = true; + } } else if (equals(cmd, F("lcd"))) { From bc60afc51317e0277529586f4a02dbbc6b5686a8 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Tue, 26 Dec 2023 23:40:44 +0100 Subject: [PATCH 7/9] [P012] Clear splash after 5 seconds if not yet overwritten --- src/_P012_LCD.ino | 13 ++++++++++++- src/src/PluginStructs/P012_data_struct.cpp | 3 +-- src/src/PluginStructs/P012_data_struct.h | 3 ++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/_P012_LCD.ino b/src/_P012_LCD.ino index 32fb474a55..ccb5cdc24b 100644 --- a/src/_P012_LCD.ino +++ b/src/_P012_LCD.ino @@ -10,6 +10,7 @@ // ####################################################################################################### /** Changelog: + * 2023-12-26 tonhuisman: Clear the splash from the display after 5 seconds if not already overwritten * 2023-11-21 tonhuisman: Add support for contrast for ST7032 based displays * 2023-11-18 tonhuisman: Trying to include support for Midas displays MD21605B6W-FTPLWI3 ST7032 (16x2 at I2C 0x3E) * 2023-03-07 tonhuisman: Parse text to display without trimming off leading and trailing spaces @@ -228,6 +229,15 @@ boolean Plugin_012(uint8_t function, struct EventStruct *event, String& string) char deviceTemplate[P12_Nlines][P12_Nchars]; LoadCustomTaskSettings(event->TaskIndex, reinterpret_cast(&deviceTemplate), sizeof(deviceTemplate)); + if (P012_data->firstLineState == 0) { + // Most common route + } else if (P012_data->firstLineState == 2) { + P012_data->firstLineState = 1; + Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + 5000); + } else if (P012_data->firstLineState == 1) { + P012_data->lcdWrite(F(" "), 0, 0); // Wipe 'ESP Easy' splash text, will reset firstLineState + } + for (uint8_t x = 0; x < P012_data->Plugin_012_rows; x++) { String tmpString = deviceTemplate[x]; @@ -265,7 +275,8 @@ boolean Plugin_012(uint8_t function, struct EventStruct *event, String& string) } else if (equals(arg1, F("clear"))) { P012_data->lcd->clear(); - success = true; + P012_data->firstLineState = 0; + success = true; } else if (equals(arg1, F("contrast")) && (P012_DisplaySize_e::LCD_2x16_ST7032 == static_cast(P012_SIZE)) && diff --git a/src/src/PluginStructs/P012_data_struct.cpp b/src/src/PluginStructs/P012_data_struct.cpp index b0c0aa480d..321533466b 100644 --- a/src/src/PluginStructs/P012_data_struct.cpp +++ b/src/src/PluginStructs/P012_data_struct.cpp @@ -96,11 +96,10 @@ void P012_data_struct::lcdWrite(const String& text, uint8_t col, uint8_t row) { } } + if (row == 0) { firstLineState = 0; } // Reset firstLineState lcd->setCursor(col, row); if ((Plugin_012_mode == 1) || (Plugin_012_mode == 2)) { - lcd->setCursor(col, row); - for (uint8_t i = 0; i < Plugin_012_cols - col; i++) { if (text[i]) { lcd->print(text[i]); diff --git a/src/src/PluginStructs/P012_data_struct.h b/src/src/PluginStructs/P012_data_struct.h index 5564b8ae7f..e4c98c52c3 100644 --- a/src/src/PluginStructs/P012_data_struct.h +++ b/src/src/PluginStructs/P012_data_struct.h @@ -20,7 +20,7 @@ struct P012_data_struct : public PluginTaskData_base { P012_DisplaySize_e lcd_size, uint8_t mode, uint8_t timer); - P012_data_struct() = delete; + P012_data_struct() = delete; virtual ~P012_data_struct(); void init(); @@ -47,6 +47,7 @@ struct P012_data_struct : public PluginTaskData_base { int Plugin_012_rows = 2; int Plugin_012_mode = 1; uint8_t displayTimer = 0; + uint8_t firstLineState = 2; }; #endif // ifdef USES_P012 From d52a050d5b9ab1cd68d91245b9510bba63d88742 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Wed, 27 Dec 2023 13:32:24 +0100 Subject: [PATCH 8/9] [P012] Replace splashState values by enum --- src/_P012_LCD.ino | 22 +++++++++++++--------- src/src/PluginStructs/P012_data_struct.cpp | 2 +- src/src/PluginStructs/P012_data_struct.h | 8 +++++++- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/_P012_LCD.ino b/src/_P012_LCD.ino index ccb5cdc24b..fc407841a9 100644 --- a/src/_P012_LCD.ino +++ b/src/_P012_LCD.ino @@ -229,13 +229,17 @@ boolean Plugin_012(uint8_t function, struct EventStruct *event, String& string) char deviceTemplate[P12_Nlines][P12_Nchars]; LoadCustomTaskSettings(event->TaskIndex, reinterpret_cast(&deviceTemplate), sizeof(deviceTemplate)); - if (P012_data->firstLineState == 0) { - // Most common route - } else if (P012_data->firstLineState == 2) { - P012_data->firstLineState = 1; - Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + 5000); - } else if (P012_data->firstLineState == 1) { - P012_data->lcdWrite(F(" "), 0, 0); // Wipe 'ESP Easy' splash text, will reset firstLineState + switch (P012_data->splashState) { + case P012_splashState_e::SplashCleared: + // Most common route + break; + case P012_splashState_e::SplashInitial: + P012_data->splashState = P012_splashState_e::SplashTimerRunning; + Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + 5000); + break; + case P012_splashState_e::SplashTimerRunning: + P012_data->lcdWrite(F(" "), 0, 0); // Wipe 'ESP Easy' splash text, will reset splashState + break; } for (uint8_t x = 0; x < P012_data->Plugin_012_rows; x++) @@ -275,8 +279,8 @@ boolean Plugin_012(uint8_t function, struct EventStruct *event, String& string) } else if (equals(arg1, F("clear"))) { P012_data->lcd->clear(); - P012_data->firstLineState = 0; - success = true; + P012_data->splashState = P012_splashState_e::SplashCleared; + success = true; } else if (equals(arg1, F("contrast")) && (P012_DisplaySize_e::LCD_2x16_ST7032 == static_cast(P012_SIZE)) && diff --git a/src/src/PluginStructs/P012_data_struct.cpp b/src/src/PluginStructs/P012_data_struct.cpp index 321533466b..6acded07e4 100644 --- a/src/src/PluginStructs/P012_data_struct.cpp +++ b/src/src/PluginStructs/P012_data_struct.cpp @@ -96,7 +96,7 @@ void P012_data_struct::lcdWrite(const String& text, uint8_t col, uint8_t row) { } } - if (row == 0) { firstLineState = 0; } // Reset firstLineState + if (row == 0) { splashState = P012_splashState_e::SplashCleared; } // Reset splashState lcd->setCursor(col, row); if ((Plugin_012_mode == 1) || (Plugin_012_mode == 2)) { diff --git a/src/src/PluginStructs/P012_data_struct.h b/src/src/PluginStructs/P012_data_struct.h index e4c98c52c3..4955d732b4 100644 --- a/src/src/PluginStructs/P012_data_struct.h +++ b/src/src/PluginStructs/P012_data_struct.h @@ -13,6 +13,12 @@ enum class P012_DisplaySize_e : uint8_t { LCD_2x16_ST7032, }; +enum class P012_splashState_e : uint8_t { + SplashCleared = 0u, + SplashTimerRunning = 1u, + SplashInitial = 2u +}; + const __FlashStringHelper* toString(P012_DisplaySize_e display); struct P012_data_struct : public PluginTaskData_base { @@ -47,7 +53,7 @@ struct P012_data_struct : public PluginTaskData_base { int Plugin_012_rows = 2; int Plugin_012_mode = 1; uint8_t displayTimer = 0; - uint8_t firstLineState = 2; + P012_splashState_e splashState = P012_splashState_e::SplashInitial; }; #endif // ifdef USES_P012 From 3b29a5e7bbc51f97dbea240c55839fe665c8f53e Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Thu, 30 May 2024 22:53:35 +0200 Subject: [PATCH 9/9] [P012] Fix merge issue, minor improvements --- src/_P012_LCD.ino | 40 ++++++++-------------- src/src/PluginStructs/P012_data_struct.cpp | 7 ++-- 2 files changed, 17 insertions(+), 30 deletions(-) diff --git a/src/_P012_LCD.ino b/src/_P012_LCD.ino index 35dbf5b903..a31df2c8f2 100644 --- a/src/_P012_LCD.ino +++ b/src/_P012_LCD.ino @@ -115,8 +115,7 @@ boolean Plugin_012(uint8_t function, struct EventStruct *event, String& string) String strings[P12_Nlines]; LoadCustomTaskSettings(event->TaskIndex, strings, P12_Nlines, P12_Nchars); - for (int varNr = 0; varNr < P12_Nlines; varNr++) - { + for (int varNr = 0; varNr < P12_Nlines; ++varNr) { addFormTextBox(concat(F("Line "), varNr + 1), getPluginCustomArgName(varNr), strings[varNr], P12_Nchars); } } @@ -155,8 +154,7 @@ boolean Plugin_012(uint8_t function, struct EventStruct *event, String& string) char deviceTemplate[P12_Nlines][P12_Nchars] = {}; String error; - for (uint8_t varNr = 0; varNr < P12_Nlines; varNr++) - { + for (uint8_t varNr = 0; varNr < P12_Nlines; ++varNr) { if (!safe_strncpy(deviceTemplate[varNr], webArg(getPluginCustomArgName(varNr)), P12_Nchars)) { error += getCustomTaskSettingsError(varNr); } @@ -193,10 +191,8 @@ boolean Plugin_012(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_TEN_PER_SECOND: { - if (validGpio(CONFIG_PIN3)) - { - if (digitalRead(CONFIG_PIN3) == P012_INVERSE_BTN) - { + if (validGpio(CONFIG_PIN3)) { + if (digitalRead(CONFIG_PIN3) == P012_INVERSE_BTN) { P012_data_struct *P012_data = static_cast(getPluginTaskData(event->TaskIndex)); @@ -242,13 +238,11 @@ boolean Plugin_012(uint8_t function, struct EventStruct *event, String& string) break; } - for (uint8_t x = 0; x < P012_data->Plugin_012_rows; x++) - { + for (uint8_t x = 0; x < P012_data->Plugin_012_rows; ++x) { String tmpString = deviceTemplate[x]; - if (tmpString.length()) - { - String newString = P012_data->P012_parseTemplate(tmpString, P012_data->Plugin_012_cols); + if (!tmpString.isEmpty()) { + const String newString = P012_data->P012_parseTemplate(tmpString, P012_data->Plugin_012_cols); P012_data->lcdWrite(newString, 0, x); } } @@ -263,11 +257,10 @@ boolean Plugin_012(uint8_t function, struct EventStruct *event, String& string) static_cast(getPluginTaskData(event->TaskIndex)); if ((nullptr != P012_data) && P012_data->isValid()) { - String cmd = parseString(string, 1); + const String cmd = parseString(string, 1); - if (equals(cmd, F("lcdcmd"))) - { - String arg1 = parseString(string, 2); + if (equals(cmd, F("lcdcmd"))) { + const String arg1 = parseString(string, 2); if (equals(arg1, F("off"))) { P012_data->lcd->noBacklight(); @@ -289,17 +282,12 @@ boolean Plugin_012(uint8_t function, struct EventStruct *event, String& string) P012_data->lcd->setContrast(event->Par2); success = true; } - else if (arg1.equalsIgnoreCase(F("Clear"))) { - P012_data->lcd.clear(); - P012_data->splashState = P012_splashState_e::SplashCleared; - } } - else if (equals(cmd, F("lcd"))) - { + else if (equals(cmd, F("lcd"))) { success = true; - int colPos = event->Par2 - 1; - int rowPos = event->Par1 - 1; - String text = parseStringKeepCaseNoTrim(string, 4); + const int colPos = event->Par2 - 1; + const int rowPos = event->Par1 - 1; + String text = parseStringKeepCaseNoTrim(string, 4); text = P012_data->P012_parseTemplate(text, P012_data->Plugin_012_cols); P012_data->lcdWrite(text, colPos, rowPos); diff --git a/src/src/PluginStructs/P012_data_struct.cpp b/src/src/PluginStructs/P012_data_struct.cpp index 6acded07e4..c95c40daa1 100644 --- a/src/src/PluginStructs/P012_data_struct.cpp +++ b/src/src/PluginStructs/P012_data_struct.cpp @@ -74,8 +74,7 @@ void P012_data_struct::setBacklightTimer(uint8_t timer) { } void P012_data_struct::checkTimer() { - if (isValid() && (displayTimer > 0)) - { + if (isValid() && (displayTimer > 0)) { displayTimer--; if (displayTimer == 0) { @@ -91,7 +90,7 @@ void P012_data_struct::lcdWrite(const String& text, uint8_t col, uint8_t row) { if (Plugin_012_mode == 2) { lcd->setCursor(col, row); - for (uint8_t i = col; i < Plugin_012_cols; i++) { + for (uint8_t i = col; i < Plugin_012_cols; ++i) { lcd->print(' '); } } @@ -100,7 +99,7 @@ void P012_data_struct::lcdWrite(const String& text, uint8_t col, uint8_t row) { lcd->setCursor(col, row); if ((Plugin_012_mode == 1) || (Plugin_012_mode == 2)) { - for (uint8_t i = 0; i < Plugin_012_cols - col; i++) { + for (uint8_t i = 0; i < Plugin_012_cols - col; ++i) { if (text[i]) { lcd->print(text[i]); }