LCD 1602

12/17/18

Categories: Electronics Tags: Arduino

~/contents

After using 7-segment displays for sensor data for a bit I was quite happy to get a few of these LCD modules in the mail. These modules come with an 16x2 LCD and the driver board. There is a blue potentiometer on the back of the module that generally needs to be adjusted. My display default setting was 0 brightness. This is standard 4-wire I2C hookup:

GND	> GND
5V	> VCC
SDA (A4) > SDA
SCL (A5) > SCL

To locate the module’s I2C address, plug in the module and load and run I2C Scanner, code reproduced below.

// i2c_scanner
//
// Version 1
//    This program (or code that looks like it)
//    can be found in many places.
//    For example on the Arduino.cc forum.
//    The original author is not know.
// Version 2, Juni 2012, Using Arduino 1.0.1
//     Adapted to be as simple as possible by Arduino.cc user Krodal
// Version 3, Feb 26  2013
//    V3 by louarnold
// Version 4, March 3, 2013, Using Arduino 1.0.3
//    by Arduino.cc user Krodal.
//    Changes by louarnold removed.
//    Scanning addresses changed from 0...127 to 1...119,
//    according to the i2c scanner by Nick Gammon
//    http://www.gammon.com.au/forum/?id=10896
// Version 5, March 28, 2013
//    As version 4, but address scans now to 127.
//    A sensor seems to use address 120.
// Version 6, November 27, 2015.
//    Added waiting for the Leonardo serial communication.
//
// This sketch tests the standard 7-bit addresses
// Devices with higher bit address might not be seen properly.

#include <Wire.h>

void setup(){
  Wire.begin();
  Serial.begin(9600);
  while (!Serial);             // Leonardo: wait for serial monitor
  Serial.println("\nI2C Scanner");
}

void loop(){
  byte error, address;
  int nDevices;
  Serial.println("Scanning...");
  nDevices = 0;
  for(address = 1; address < 127; address++ ) {
    // The i2c_scanner uses the return value of
    // the Write.endTransmisstion to see if
    // a device did acknowledge to the address.
    Wire.beginTransmission(address);
    error = Wire.endTransmission();

    if (error == 0) {
      Serial.print("I2C device found at address 0x");
      if (address<16)
        Serial.print("0");
      Serial.print(address,HEX);
      Serial.println("  !");

      nDevices++;
    }
    else if (error==4){
      Serial.print("Unknown error at address 0x");
      if (address<16)
        Serial.print("0");
      Serial.println(address,HEX);
    }
  }
  if (nDevices == 0)
    Serial.println("No I2C devices found\n");
  else
    Serial.println("done\n");
  delay(5000);           // wait 5 seconds for next scan
}

It will output to the Serial monitor like so:

16:28:00.597 -> I2C Scanner
16:28:00.597 -> Scanning...
16:28:00.597 -> I2C device found at address 0x27  !
16:28:00.631 -> done
16:28:00.631 ->

There are a number of LCD libraries out there, including some interesting variations such as double-height, bar graphs, LCD effects, and LCD menus. Personally, I found these libraries a little confusing at first, but marcoschwart’s LiquidCrystal_I2C library worked without any tweaks and is also available through the Arduino IDE Library Manager. The CustomChars example code demonstrates how to create your own characters or display hex. This basic example uses elements from several sketches.

#include <LiquidCrystal_I2C.h> // https://github.com/marcoschwartz/LiquidCrystal_I2C

const byte lcdAddr = 0x27;  // I2C address
const byte lcdCols = 16;    // character per row
const byte lcdRows = 2; // lines
uint8_t heart[8] = {0x0, 0xa, 0x1f, 0x1f, 0xe, 0x4, 0x0};

LiquidCrystal_I2C lcd(lcdAddr, lcdCols, lcdRows);

void setup(){
  lcd.init();
  lcd.backlight();
  lcd.createChar(0, heart);
}

void loop(){
  lcd.home();
  lcd.print("Hello world!");
  delay(1000);
  lcd.setCursor(0,1);
  lcd.print("Hello world!!");
  lcd.write(0);
  lcd.write(0);
  lcd.write(0);
  delay(1000);
  lcd.clear();
  delay(1000);
}

LCD Button Module

After the fact it occured to me a button shield would be handy. The LCD button shields commonly available use voltage dividers to allow one analog pin read multiple button inputs. I came up with a simple 4-button D-pad configuration that only requires 3 10k resistors and two pins.

The way these types of circuits work, you take an analog reading and the resistance you get back reflects which button is being pushed. For this module, the idle value is 1023 (give or take) and the following button presses return these approximate values:

Left 229 Down 365 Right 458 Up 14

These values may fluxuate slightly, so we check for a value within a range to determine which button was pressed.

#include <LiquidCrystal_I2C.h>  // https://github.com/marcoschwartz/LiquidCrystal_I2C
#include <Bounce2.h> // https://github.com/thomasfredericks/Bounce2

const byte BUTTON_PIN = A1;
const byte UP_VALUE = 14;
const byte LEFT_VALUE = 229;
const byte DOWN_VALUE = 367;
const byte RIGHT_VALUE = 462;
const byte VALUE_RANGE = 10;

Bounce debouncer = Bounce();
LiquidCrystal_I2C lcd(0x27,16,2);  // set the LCD address to 0x27 for a 16 chars and 2 line display

void setup(){
  Serial.begin(9600);
  debouncer.attach(BUTTON_PIN, INPUT_PULLUP);
  debouncer.interval(25);
  lcd.init();
  lcd.backlight();
}

void loop(){
  debouncer.update();
  if(debouncer.fell()){
    int buttonPressed = readButton();
    Serial.println(buttonPressed);
    lcd.clear();
    lcd.home();
    lcd.print("Pressed button ");
    lcd.print(buttonPressed);
    lcd.setCursor(0,1);
    lcd.print("Value is: ");
    int value = analogRead(BUTTON_PIN);
    lcd.print(value);
  }
}

int readButton(){
  byte value = analogRead(BUTTON_PIN);
  if(value >= (RIGHT_VALUE - VALUE_RANGE) && value <= (RIGHT_VALUE + VALUE_RANGE)){
    return 4;
  }
  else if(value >= (DOWN_VALUE - VALUE_RANGE) && value <= (DOWN_VALUE + VALUE_RANGE) ){
    return 3;
  }
  else if(value >= (LEFT_VALUE - VALUE_RANGE) && value <= (LEFT_VALUE + VALUE_RANGE) ){
    return 2;
  }
  else if(value >= (UP_VALUE - VALUE_RANGE) && value <= (UP_VALUE + VALUE_RANGE) ){
    return 1;
  }
  else {
    return 0;
  }
}

Resources: