LCD Programming

Small 20x4 or 16x2 LCDs are now so inexpensive that they can be used on many projects, and even if not in the final design, then to aid the process of program development and debugging. Most LCDs with an I2C interface seem to have either an address of 0x27 or 0x3F. Since vendors do not normally state which address their devices use I now have a collection of LCDs with different addresses. Until now, this meant that each program needed to be hardcoded with a specific address.

Most example sketches that you see, whether for Arduino or ESP8266, will have a LiquidCrystal_I2C lcd(0x3F,20,4); statement near the beginning of the sketch to initialise the variable lcd. By defining it outside the setup() and loop() functions, this global variable is available to those functions plus any others you might add. You've probably seen code like this:

#include <LiquidCrystal_I2C.h>

.

.

LiquidCrystal_I2C lcd(0x3F, 20, 4);

.

.

void setup()

{

   lcd.init();

   lcd.backlight();

   lcd.setCursor(0,0);

   lcd.print("Hello world!");

.

.

}

void loop()

{

// lcd also accessible in this function

}

This works fine, but you need to know the LCD address at compile time and if you change the LCD for one with a different address you will need to recompile and reload the sketch. You can't just put LiquidCrystal_I2C lcd(0x3F,20,4); in each function because those names will have a local scope only. You can't re-initialise lcd with a new address in setup() or loop() either. But there is a way to set the LCD address at runtime by using a pointer to lcd. The steps are:

Step 1

At the top of your sketch create a pointer to a LiquidCrystal_I2C object

LiquidCrystal_I2C *plcd;

Step 2

When you have determined your I2C address, initialise the values

plcd=new LiquidCrystal_I2C(LCDaddress,20,4);

Step 3

Where you used lcd.print(...); previously, you must now recode this to use the pointer instead:

plcd->print(...);

The sparse example above now becomes:

#include <LiquidCrystal_I2C.h>

.

.

LiquidCrystal_I2C *plcd;

.

.

void setup()

{

// Presuming you have a valid address in LCDaddress!

// The "new" operator ensures that the storage is within the global scope of the program

// and not just within setup()

   plcd=new LiquidCrystal_I2C(LCDaddress,20,4);

   plcd->init();

   plcd->backlight();

   plcd->setCursor(0,0);

   plcd->print("Hello world!");

.

.

}

void loop()

{

// plcd also accessible in this function

}

HOW TO WORK OUT WHICH ADDRESS TO USE

There are various ways to work out which I2C address to use:

  1. For two addresses use a jumper to set, for example, a pin high for 0x27 or low for 0x3F - use analogRead(); to get its state
  2. For more than two addresses, use two or more jumpers to set a combination of pins high or low as required
  3. Scan the I2C bus for an LCD device at an appropriate address. You can have up to eight LCDs starting at 0x27 and another eight starting at 0x3F so you may need to scan up to 16 addresses