LiquidCrystal I2C with multiple LCDs : LCDs stagnate after custom caracters creation

Question for displaying customs caracters (large characters (3x2) with multiple I2C LCD 20x4 on a Wemos D1 mini. I have 4 LCD 20x04 where I display information in large characters. At initialization (setup() ), after having generated the 8 special characters, the LCDs stagnate and no longer respond to any command. The rest of my program work just fine.
Well I got around the problem by immediately passing a special character to print in the initialization just after special caracters creation, and everything works normally, but I would like to understand why LCDs stop responding if I don't do that!
There is no compiling error message except the warning that LiquidCrystal_I2C may not be compatible with ESP8266...
Ideas ?

#include <Wire.h>
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd[4] = {
  { 0x27, 20, 4 },  //No jumper
  { 0x26, 20, 4 },  //A0 jumper
  { 0x25, 20, 4 },  //A1 jumper
  { 0x23, 20, 4 }   //A2 jumper
};

void setup() {
  Serial.begin(115200);
  delay(1000);

  // I2C communication setup
  Wire.begin();
  // LCDs setup
  for (int i = 0; i < 4; i++) {
    lcd[i].init();
    lcd[i].backlight();
    // Create big custom caracters
    BigNumber_SendCustomChars(i);
    
    //**************************
    // LCDs stagnate id I don't print at least one special caracter !!! Why ???
    DrawBigChar(i, 0, 0, 'A');
    //**************************
    
    lcd[i].clear();
  }
}

void loop() {
  lcd[0].print("hello");
  lcd[1].print("blue");
  lcd[2].print("meow");
  lcd[3].print("poo");

  delay(1000);
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//************************************************************************
//*	A set of custom made large numbers for a 16x2 LCD using the
//*	LiquidCrystal librabry. Works with displays compatible with the
//*	Hitachi HD44780 driver.
//*
//*	orginal developed  by Michael Pilcher  2/9/2010
//*	there are 8 entries, 8 bytes per entry
//*	these are the building blocks to make the numbers
//*
//*	http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1265696343
//************************************************************************

//*	The LL, LT, UB, ect... are rust abreviations to help me designate which segment was which when referencing the large '0'.
//*	LT= left top
//*	UB= upper bar
//*	RT= right top
//*	LL= lower left
//*	LB= lower bar
//*	LR= lower right
//*	UMB= upper middle bars(upper middle section of the '8')
//*	LMB= lower middle bars(lower middle section of the '8')
byte LT[8] = {
  B00111,
  B01111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111
};
byte UB[8] = {
  B11111,
  B11111,
  B11111,
  B00000,
  B00000,
  B00000,
  B00000,
  B00000
};
byte RT[8] = {
  B11100,
  B11110,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111
};
byte LL[8] = {
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B01111,
  B00111
};
byte LB[8] = {
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  B11111,
  B11111,
  B11111
};
byte LR[8] = {
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11110,
  B11100,
};
byte UMB[8] = {
  B11111,
  B11111,
  B11111,
  B00000,
  B00000,
  B00000,
  B11111,
  B11111
};
byte LMB[8] = {
  B11111,
  B00000,
  B00000,
  B00000,
  B00000,
  B11111,
  B11111,
  B11111
};

//************************************************************************
void BigNumber_SendCustomChars(int lcdindex) {
  lcd[lcdindex].createChar(0, LT);
  lcd[lcdindex].createChar(1, UB);
  lcd[lcdindex].createChar(2, RT);
  lcd[lcdindex].createChar(3, LL);
  lcd[lcdindex].createChar(4, LB);
  lcd[lcdindex].createChar(5, LR);
  lcd[lcdindex].createChar(6, UMB);
  lcd[lcdindex].createChar(7, LMB);
}

uint8_t gBigFontShapeTable[] PROGMEM = {
  //*	LT[8] =
  B00111,
  B01111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  //*	UB[8] =
  B11111,
  B11111,
  B11111,
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  //*	RT[8] =
  B11100,
  B11110,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  //*	LL[8] =
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B01111,
  B00111,
  //*	LB[8] =
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  B11111,
  B11111,
  B11111,
  //*	LR[8] =
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11110,
  B11100,
  //*	UMB[8] =
  B11111,
  B11111,
  B11111,
  B00000,
  B00000,
  B00000,
  B11111,
  B11111,
  //*	LMB[8] =
  B11111,
  B00000,
  B00000,
  B00000,
  B00000,
  B11111,
  B11111,
  B11111


};

//************************************************************************
//*	6 numbers for each character
//*	9 means BLANK
uint8_t gBigFontAsciiTable[] PROGMEM = {

  32, 32, 32, 32, 32, 32,  //	0x20	space
  32, 0, 32, 32, 4, 32,    //	0x21	!
  32, 32, 32, 32, 32, 32,  //	0x22
  32, 32, 32, 32, 32, 32,  //	0x23
  32, 32, 32, 32, 32, 32,  //	0x24
  32, 32, 32, 32, 32, 32,  //	0x25
  32, 32, 32, 32, 32, 32,  //	0x26
  32, 32, 32, 32, 32, 32,  //	0x27
  32, 32, 32, 32, 32, 32,  //	0x28
  32, 32, 32, 32, 32, 32,  //	0x29
  32, 32, 32, 32, 32, 32,  //	0x2A
  32, 32, 32, 32, 32, 32,  //	0x2B
  32, 32, 32, 32, 32, 32,  //	0x2C
  32, 32, 32, 32, 32, 32,  //	0x2D
  32, 32, 32, 32, 4, 32,   //	0x2E	. (period) place holder
  32, 32, 32, 32, 32, 32,  //	0x2F


  0, 1, 2, 3, 4, 5,        //	0x30	0
  1, 2, 32, 32, 5, 32,     //	0x31	1
  6, 6, 2, 3, 7, 7,        //	0x32	2
  6, 6, 2, 7, 7, 5,        //	0x33	3
  3, 4, 2, 32, 32, 5,      //	0x34	4
  255, 6, 6, 7, 7, 5,      //	0x35	5
  //	0,	6,	6,	7,	7,	5,		//	0x35	5
  0, 6, 6, 3, 7, 5,        //	0x36	6
  1, 1, 2, 32, 0, 32,      //	0x37	7
  0, 6, 2, 3, 7, 5,        //	0x38	8
  0, 6, 2, 32, 32, 5,      //	0x39	9
  32, 32, 32, 32, 32, 32,  //	0x3A  : place holder
  32, 32, 32, 32, 32, 32,  //	0x3B
  32, 32, 32, 32, 32, 32,  //	0x3C
  32, 32, 32, 32, 32, 32,  //	0x3D
  32, 32, 32, 32, 32, 32,  //	0x3E
  1, 6, 2, 254, 7, 32,     //	0x3F	?

  32, 32, 32, 32, 32, 32,  //	0x40	@
  0, 6, 2, 255, 254, 255,  //	0x41	A
  255, 6, 5, 255, 7, 2,    //	0x42	B
  0, 1, 1, 3, 4, 4,        //	0x43	C
  255, 1, 2, 255, 4, 5,    //	0x44	D
  255, 6, 6, 255, 7, 7,    //	0x45	E
  255, 6, 6, 255, 32, 32,  //	0x46	F

  0, 1, 1, 3, 4, 2,            //	0x47	G
  255, 4, 255, 255, 254, 255,  //	0x48	H
  1, 255, 1, 4, 255, 4,        //	0x49	I
  32, 32, 255, 4, 4, 5,        //	0x4A	J
  255, 4, 5, 255, 254, 2,      //	0x4B	K
  255, 32, 32, 255, 4, 4,      //	0x4C	L
  32, 32, 32, 32, 32, 32,      //	0x4D	M	place holder
  32, 32, 32, 32, 32, 32,      //	0x4E	N	place holder
  0, 1, 2, 3, 4, 5,            //	0x4F	O	(same as zero)

  0, 6, 2, 3, 32, 32,      //	0x50	P
  32, 32, 32, 32, 32, 32,  //	0x51	Q
  0, 6, 5, 3, 32, 2,       //	0x52	R
  0, 6, 6, 7, 7, 5,        //	0x35	S	(same as 5)
  1, 2, 1, 32, 5, 32,      //	0x54	T
  2, 32, 2, 3, 4, 5,       //	0x55	U
  32, 32, 32, 32, 32, 32,  //	0x56	V	place holder
  32, 32, 32, 32, 32, 32,  //	0x57	W	place holder
  3, 4, 5, 0, 32, 2,       //	0x58	X
  3, 4, 5, 32, 5, 32,      //	0x59	Y
  1, 6, 5, 0, 7, 4,        //	0x5A	Z
  0

};

//*	we have a seperate table for the wide routines
uint8_t gBigFontAsciiTableWide[] PROGMEM = {
  //*	this table is 10 bytes, 2 rows of 5
  //	---top------------|
  32, 32, 32, 32, 32, 4, 32, 32, 32, 32,  //	0x2E	.		1-wide
  4, 32, 32, 32, 32, 4, 32, 32, 32, 32,   //	0x3A	:		1-wide
  0, 1, 3, 1, 2, 3, 32, 32, 32, 5,        //	0x4D	M		5-wide
  0, 3, 32, 2, 32, 3, 32, 2, 5, 32,       //	0x4E	N		4-wide
  0, 1, 2, 32, 32, 3, 4, 3, 4, 32,        //	0x51	Q		4-wide
  3, 32, 32, 5, 32, 32, 3, 5, 32, 32,     //	0x56	V		4-wide
  0, 32, 32, 32, 2, 3, 4, 0, 4, 5,        //	0x57	W		5-wide
  0
};

//************************************************************************
//*	returns the width of the character
static int DrawBigChar(int lcdindex, int xLocation, int yLocation, char theChar) {
  int offset;
  int ii;
  char theByte;
  boolean specialCase;
  int specialIndex;
  int charWidth;

  if (theChar == 0x20) {
    return (2);
  } else if (theChar < 0x20) {
    return (0);
  }

  if (theChar >= 'A') {
    theChar = theChar & 0x5F;  //*	force to upper case
  }
  specialCase = true;
  switch (theChar) {
    case '.':
      charWidth = 1;
      specialIndex = 0;
      break;
    case ':':
      charWidth = 1;
      specialIndex = 1;
      break;
    case 'M':
      charWidth = 5;
      specialIndex = 2;
      break;
    case 'N':
      charWidth = 4;
      specialIndex = 3;
      break;
    case 'Q':
      charWidth = 4;
      specialIndex = 4;
      break;
    case 'V':
      charWidth = 4;
      specialIndex = 5;
      break;
    case 'W':
      charWidth = 5;
      specialIndex = 6;
      break;

    default:
      charWidth = 3;
      specialCase = false;
      offset = 6 * (theChar - 0x20);
      lcd[lcdindex].setCursor(xLocation, yLocation);
      for (ii = 0; ii < 3; ii++) {
        if (ii == charWidth) {
          theByte = 32;
        }  // overwrite with space the space in between caracthers
        else {
          theByte = pgm_read_byte_near(gBigFontAsciiTable + offset + ii);
        }
        lcd[lcdindex].write(theByte);
      }

      lcd[lcdindex].setCursor(xLocation, yLocation + 1);
      offset += 3;
      for (ii = 0; ii < 3; ii++) {
        if (ii == charWidth) {
          theByte = 32;
        }  // overwrite with space the space in between caracthers
        else {
          theByte = pgm_read_byte_near(gBigFontAsciiTable + offset + ii);
        }
        lcd[lcdindex].write(theByte);
      }
      break;
  }
  if (specialCase) {
    //*
    offset = 10 * specialIndex;
    lcd[lcdindex].setCursor(xLocation, yLocation);
    for (ii = 0; ii < charWidth + 1; ii++) {
      if (ii == charWidth) {
        theByte = 32;
      }  // overwrite with space the space in between caracthers
      else {
        theByte = pgm_read_byte_near(gBigFontAsciiTableWide + offset + ii);
      }
      lcd[lcdindex].write(theByte);
    }

    lcd[lcdindex].setCursor(xLocation, yLocation + 1);
    offset += 5;
    for (ii = 0; ii < charWidth + 1; ii++) {
      if (ii == charWidth) {
        theByte = 32;
      }  // overwrite with space the space in between caracthers
      else {
        theByte = pgm_read_byte_near(gBigFontAsciiTableWide + offset + ii);
      }
      lcd[lcdindex].write(theByte);
    }
  }
  return (charWidth + 1);
}

can you check if a delay(100) instead your DrawBigChar(i, 0, 0, 'A'); would have the same "positive" result?

P.S.: When you edit your entry post you can move your topic to Using Arduino / Display which is better suited than the generic "Programming Questions".

1 Like

This is a not a timing issue nor an issue related to display size or number of displays.
It is an issue in how the library works.
You are falling victim to a library custom character issue (a bug IMO) in every single "LiquidCrystal" type library except the hd44780 library.
I believe that this is because the library authors don't fully grasp how the hd44780 chipset and instruction set really works.

The issue is that the hd44780 chip set has two modes when sending data.
Display Data RAM or DDRAM mode and Character Generator RAM or CGRAM mode.
When sending data that is to be displayed on the display the chipset must be in DDRAM mode which causes data writes to go into the memory that is mapped to the LCD display.
When sending data that is be used for custom characters the chipset must be in CGRAM mode which causes data writes to go into the memory used for custom characters.

When you call createChar() it sends a set CGRAM address instruction to the chipset to put the chipset into CGRAM mode. This causes ALL subsequent data writes to go to CGRAM. This is how you write to CGRAM for custom characters.
The chipset remains in CGRAM mode until a DDRAM address is set.

There are 3 ways to get back to DDRAM mode when in CGRAM mode, all of which involve sending an instruction to set the DDRAM address.

  • Clear Display instruction ( clear() in the API) which sets the DDRAM address to zero
  • Return Home instruction ( home() in the API) which sets the DDRAM address to zero
  • set DDRAM address ( setCursor() in the API) which sets the DDRAM address based on the row,col

The library you are using (as well as every other "LiquidCrystal" library other than hd44780) leaves the chipset in CGRAM when createChar() returns.
That means that if you do data writes, by say trying to print to the display immediately after calling createChar() the data will not go to DDRAM to be displayed on the LCD but will go to CGRAM, which can and will clobber custom character data.
No data sent using print(), write(), etc... will be displayed on the display until the chipset is put back into DDRAM mode.

The hd44780 library does not have this issue. It ensures that the chipset is put back into DDRAM mode when createChar() returns so you can immediately print something after calling createChar().
On a device like the one you have the hd44780_I2Cexp i/o class will read the cursor position and restore it back to the same position using a set DDRAM address which will put the display back into DDRAM mode with the cursor in the same position it was prior to calling createChar()
i.e. the display is put back to the way it was prior to calling createChar()
No other library does this.

You can either switch to the hd44780 library hd44780_I2Cexp i/o class for your device, or add a clear(), home() or setCursor() after you do a createChar() before you want to print anything to the display.

--- bill

3 Likes

Hi,

I'll move the post to the appropriate category...

No delay does nothing to it...

Have a nice day !

Samuel

Thanks for the really good answer !

I've made a few tests and all of your solutions work : writing a custom caracter to screen, set the cursor, clear and/or home.

I had moved de clear function after creating the caracters, but did not try it without printing a custom caracater I had put there...

I'll look in the hd44780. Is it more supported then the LiquidCrystal_I2C ?

Regards !

Samuel

Yes.
I created & support the hd44780 library.
It is at least 2x the speed, and has many additional features and lots of documentation that comes with it.
There is even a wiki.

There is nobody supporting the LiquidCrystal_I2C library, not to mention that there are multiple "LiquidCrystal_I2C" libraries floating around out there and they are not all the same.

The LiquidCrystal_I2C library in the IDE library manager, was taken over by a guy name John Rickman who made a wreck of it.
Here is more about that:

--- bill

Hi,

I've tried the HD44780 library seems to work great ! Thanks!

I've read a fews exemple, but I'm still noob about MCU and coding. So for ESP8266 and custom characters, should I keep the PROGMEM characters definitions or change it to other way ?

Regards !

Samuel

Here is more on the use of the proprietary avr-gcc PROGMEM on the ESP8266
https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html

You can decide what you want/need.

For me, for most stuff particularly if it is not small or I want to have direct access to to it without having to mess with the indirect access routines, I don't mess with PROGMEM except on the AVRs which are so resource limited.
(I don't use AVRs much anymore for my own projects)

For portability, if you intend the code to run on all platforms AVR included, using PROGMEM would be recommended.

--- bill

1 Like

Thanks again !

Since you seem (to my knowledges) to have made such a great job on the HD447800, I've switch to this library on all my in progress projects. The Liquid_Crystal is used in nearly all tutorials, but I had troubles finding consistency : multiples versions... Your libray is so much more define and with lot of explanations, and no just the "put it that way and it will work" kind of explanations. I'm actually learning something reading your library.

I was working with Arduino a lot, but I'm gradually switching toward ESP for the connectivity and memory.

One last question : you've included at multiple place warnings about 5v LCD module on 3.3V MCU, just like in my project. It seems to work just fine without shifting logic... Am I really at risk of busting something here ?

I've read that ESP8266 are 5V tolerant on I/O pins (not on the supply, just the I/O pins), but it's like a never ending story posts between believers and detractors... What is your opinion about it ?

Regards,

Sam

I've seen the same, but according their latest datasheet which was updated in Feb 2023, it makes not mention of 5v tolerance.

See page 19

Even if the pins were 5v tolerant, it is better to run two busses using a level shifter when doing something like mixing 5v slaves with a 3v master.
I always use level shifters when using 3v MCUs and 5v slaves.

now I have cheated and used a 5v i2c slave with the esp8266 without a level shifter but when I did that, I changed the pullups to go to 3v instead of 5v.
This runs the bus out of spec for the 5v slave but it ensures that there is never 5v on the i2c signals. It usually works.
IMO, best is to use a level shifter.

--- bill

Hi Bill !

Sorry to bother you again. After a while, I've notice my LCDs are not responding anymore to commands. I don't know if there is a max update rate, or if it's I2C related... Any Idea ?

I'm now using the HD44780 libray with the hd44780_I2Cexp class.

I've put in my main loop() an LCD update request at each 500ms. Like this :

// Update lcd screens at 500 ms least
  if ((currentTime - lastLCDsupdate) > 500)
  {
    lastLCDsupdate = currentTime;
    for (int i = 0; i < 4; i++)
    {
      displayToLCD(i, i);
    }
  }

The fucntions called are those, they imply the custom big caracters from my main question. They print a kid's name on first 2 rows, then they print some points stored in an array on the last 2 rows. All in big caracters...

void displayToLCD(int lcdindex, int displaymode)
{

  switch (displaymode)
  {

  case 0: // Display kid #1 points
    displayPointsToLCD(lcdindex, displaymode, kidsData[15 * displaymode]);
    break;

  case 1: // Display kid #2 points
    displayPointsToLCD(lcdindex, displaymode, kidsData[15 * displaymode]);
    break;

  case 2: // Display kid #3 points
    displayPointsToLCD(lcdindex, displaymode, kidsData[15 * displaymode]);
    break;

  case 3: // Display kid #4 points
    displayPointsToLCD(lcdindex, displaymode, kidsData[15 * displaymode]);
    break;

  case 4: // Show time and day in big characters
    displayTimeToLCD(lcdindex);
  }
}

void displayPointsToLCD(int lcdindex, int kidindex, int kidpoints)
{
  // Create names characters array
  char kidNameArray[][6] = {"LIEM:", "TUAN:", "VAN :", "LUAN:"};
  // Write name in big characters on first 2 LCD's lines
  int curXpos = 0; // set cursor to start of the line
  for (int i = 0; i < 6; i++)
  {
    curXpos += DrawBigChar(lcdindex, curXpos, 0, kidNameArray[kidindex][i]);
  }
  // Determine points digits and size
  static char pointsCharBuff[6];
  itoa(kidpoints, pointsCharBuff, 10);
  int pointsCharSize = 0;
  if (kidpoints < 10)
  {
    pointsCharSize = 2;
  }
  else if (kidpoints < 100)
  {
    pointsCharSize = 3;
  }
  else if (kidpoints < 100)
  {
    pointsCharSize = 4;
  }
  else if (kidpoints < 10000)
  {
    pointsCharSize = 5;
  }
  else if (kidpoints < 100000)
  {
    pointsCharSize = 6;
  }
  // Write big digits to last two LCD's lines
  curXpos = 0; // reset cursor to start of line
  for (int i = 0; i < pointsCharSize; i++)
  {
    curXpos += DrawBigChar(lcdindex, curXpos, 2, pointsCharBuff[i]);
  }
  // Fill with spaces after digits
  if (curXpos < 20)
  {
    for (int j = 2; j < 4; j++)
    {
      for (int k = curXpos; k < 20; k++)
      {
        lcd[lcdindex].setCursor(k, j);
        lcd[lcdindex].write(32); // write a space
      }
    }
  }
  return;
}

What do you mean by "not responding"
Is the processor still running or has the processor crashed?
If the sketch runs for too long in loop() without calling yield() or delay() or it can crash a ESP8266 as the watchdog will fire and trigger a crash.
If you are worried about this you can insert a yield() or delay(0) in your code to release the processor to other internal ESP8266 OS threads to clear the watchdog timer.

If the processor is still running, are the displays no longer updating? Just one or all of them? Are any of the displays garbled?

typically when things work for a while and then the display updates have issues there are a few common things that can cause this.
(But many times when this happens the display(s) get garbled)

  • i2c issues.
    This can be noise on the i2c signal lines, or i2c issues that occur due to power fluctuations or power noise that induces noise on the i2c signals.
    Or when signal wires are long or near some electrical signals.

  • power issues.
    Sometimes power can have noise or glitch that can cause issues.

  • timing
    this is more rare, but sometimes an LCD is slower than the timing in the library.
    The timing in the library can be adjust if needed. Typically this is not necessary unless really old LCDs are being used.

  • library issue
    While this can happen, This is very rare.

The library does return a status from the API calls like write() and setCursor()
It would be interesting to see if the library is reporting any errors.

--- bill

Hello Bill,

Thank you again for your support !

MCU still running and all the program except the LDCs is working : LCDs hang on that last printed data. It's a program linked with a GUI (RemoteXY) where I award points to my kids for different things : the kids points should be displayed on the LCDs, one LCD per kid, so they stop asking me how much point they have :sweat_smile: ! Anyway, the points are collecting ok and the interraction between MCU and GUI is ok and they keep running when LCDs hang.

When it hang, all the LCDs hang and do not display the actual points. Serial print of the points and GUI indicators are ok and updated.

The MCU (WeMos D1 min) is powered by USB from the computer, and LCDs are powered from the 5V board pins. I've added a small capacitor to the power circuit btween 5v and GND pins as a buffer, 47uF. There is no logic shifter between the MCU and LCD. There is 4 ribbon wires going from the board to the LCDs : GND, 5V, SDA, SDC.

I've run the diagnostic sketch from the library, everything seems fine adn uptime is now printing, I will let it run to see if it hang overnight. Here is the results from the diagnostic run and a picture of my installation. There is a broken LCDs (the second one), but I don't think it's the issue because it behave like the others.

13:39:45.175 -> SCL digital pin: 5 (GPIO5) D1
13:39:45.175 -> --------------------------------------------------------------------
13:39:45.175 -> Checking for required external I2C pull-up on SDA - YES
13:39:45.221 -> Checking for required external I2C pull-up on SCL - YES
13:39:45.267 -> Checking for I2C pins shorted together - Not Shorted
13:39:45.397 -> --------------------------------------------------------------------
13:39:45.397 -> Scanning i2c bus for devices..
13:39:45.431 ->  i2c device found at address 0x23
13:39:45.431 ->  i2c device found at address 0x25
13:39:45.431 ->  i2c device found at address 0x26
13:39:45.431 ->  i2c device found at address 0x27
13:39:45.563 -> Total I2C devices found: 4
13:39:45.563 -> --------------------------------------------------------------------
13:39:45.563 -> Scanning i2c bus for all lcd displays (4 max)
13:39:45.673 ->  LCD at address: 0x23 | config: P01245673H | R/W control: Yes
13:39:46.854 ->  LCD at address: 0x25 | config: P01245673H | R/W control: Yes
13:39:48.032 ->  LCD at address: 0x26 | config: P01245673H | R/W control: Yes
13:39:49.184 ->  LCD at address: 0x27 | config: P01245673H | R/W control: Yes
13:39:50.271 -> Total LCD devices found: 4
13:39:50.271 -> --------------------------------------------------------------------
13:39:50.271 -> LCD Display Memory Test
13:39:50.271 -> Display: 0
13:39:50.271 ->  Walking 1s data test:	PASSED
13:39:50.396 ->  Address line test:	PASSED
13:39:50.736 -> Display: 1
13:39:50.736 ->  Walking 1s data test:	PASSED
13:39:50.862 ->  Address line test:	PASSED
13:39:51.189 -> Display: 2
13:39:51.189 ->  Walking 1s data test:	PASSED
13:39:51.328 ->  Address line test:	PASSED
13:39:51.639 -> Display: 3
13:39:51.639 ->  Walking 1s data test:	PASSED
13:39:51.777 ->  Address line test:	PASSED
13:39:52.087 -> --------------------------------------------------------------------
13:39:52.165 -> Each working display should have its backlight on
13:39:52.165 -> and be displaying its #, address, and config information
13:39:52.165 -> If all pixels are on, or no pixels are showing, but backlight is on, try adjusting contrast pot
13:39:52.165 -> If backlight is off, wait for next test
13:40:02.146 -> --------------------------------------------------------------------
13:40:02.191 -> Blinking backlight test: to verify BL level autodetection
13:40:02.191 -> If backlight is mostly off but
13:40:02.191 -> you briefly see "BL Off" on display with backlight on,
13:40:02.191 -> then the library autodetected incorrect BL level
13:40:02.191 -> and the library cannot autoconfigure the device
13:40:12.619 -> --------------------------------------------------------------------
13:40:12.698 -> Displaying 'uptime' on all displays
13:40:12.698 -> --------------------------------------------------------------------

I've read from the net, but I can't find it in the actual spec, that WeMos D1 mini could provide 500mA max from 5V pin and that is also the power from an USB port on a computer...

I can't find the proper spec shit for the LCDs, but it seems to have a power consumption of about 43mA per LCD:

  • Current consumption LCD: about 2mA
  • Current consumption backlight: 40mA
  • I2C backpacl consumption : 40-100uA

I should be ok to power 4 LCDs from the MCU 5V pin : about 215 mA.

Now thinking of it, if the computer goes in standby (sleeping mode), does the USB are still powered the same way ? Maybe there is a drop then and thing hangs...

Or maybe when the ESP8266 is emitting to WiFi there is a power drop ? That's why I've included a small capacitor as a buffer between the 5V and GND pins...

Or there is noise in the ribbon cables ?

Maybe I'm just babling about and it's in the code...

I've let it run on the diagnostic sketch for a few hours and the LCDs didn't freeze after a while.

So I think it's either noises on the I2C cables/power issues, or something in my sketch about timing on the I2C, like to much request in short time? I mean writing big charaacters require a command for each character on 4 screens, so it makes a lots of writing commands one after the other on the I2C bus?

So last evening I've create a LCDs update timing loop in which I update one LCDs at a time each 250ms. So far so good, LCDs did not freeze over night and are still responding.

If no LCDs freezing in the next few days, I'll will modify the sketch to update LCDs only when data change and only one I2C device at time, with a short delay between them... that may be overkill, but it doesn't mean a lot in that application !

Is there a panic button somewhere that I can hit ? :scream:

Some chips are made more-or-less 5V tolerant, without specifying that in the datasheet. The ESP8266 can deal with 5V on its digital pins.

If you connect the ESP8266 with 3.3V signal levels to a 5V display with 5V signal levels, then your noise margin on the I2C bus is gone. It will work for a test, but you should not do that for a real project that you are going to use. Use a I2C level shifter :exclamation:

:arrow_right: The worst for a I2C bus is when SDA is next to SCL in a flat ribbon cable :arrow_left:
Don't do that :exclamation:
The official standard (UM10204.pdf at Pololu) shows at chapter 7.5, page 54, that you have 10cm for the I2C bus. Beyond that, you have to know what you are doing by keeping SDA away from SCL.
With your reduced noise level, you don't have 10cm. The reliable length for your I2C bus is a negative length.

The I2C bus should work 100% all the time, it is not a fault tolerant bus.

1 Like

Ouch ! So I guess I'm lucky it's working at all ! Well I'll be damn because I've struggled building this cable... I wonder why they put the GND, VCC, SDA and SCL pins in that order on the module since it would be logic to have a pins arrangement like the standard ???

Thanks for the I2C bus standard. This is something handy to have around.

If I get it right, the best thing I should use is something like Ethernet cable with twisted pairs GND twisted with SDA and SDC, another wire alone for the 5V ?

The second best, if I want to keep my ribbon wires, is to switch the wire around to obtain SDA, Vdd, Vss, SCL...

For this short distance, you don't need a Ethernet cable.

If you use a Ethernet cable, then use a twisted pair for SDA + GND, a pair for SCL + GND and a pair for 5V + GND.
Also lowering the clock speed might be needed, to overcome the capacitance of the line.

When two wires are next to each other, if you push a pulse of 1A in one wire, then the 1A comes back in the other wire. That is because of the inductive/capacitive coupling.
Therefor you need a strong coupling between 5V and GND. The advantage is that they will eliminate the overall noise because of the strong coupling.

If you move the wires around in your cable, then it is okay. Or split the wires. But keep 5V and GND not split if possible.

You still need that I2C level shifter.

1 Like

Thanks for sharing your knowledge !

For curiosity, on a pcb, how would you arrange the traces to keep liability on the I2C bus ?

I'm talking about a 2 layers PCB that can easily be created for a small project...

Regards,

Samuel

A pcb is often small, that should be no problem.

When a motor is used and when the ground current from the motor travels through the whole pcb board, then a problem is introduced.

1 Like