Interfacing 16×2 LCD Display with Raspberry Pi Pico

Interfacing 16×2 LCD Display with Raspberry Pi Pico


Overview

In this tutorial, we will learn Interfacing of 16×2 LCD Display with Raspberry Pi Pico. An LCD is an electronic display module that uses liquid crystal technology to produce a visible image. The 16×2 LCD display is a very basic module commonly used in electronic circuit applications. The 16×2 translates 16 characters per line in 2 such lines. In this LCD each character is displayed in a 5×8 pixel matrix.

Raspberry Pi Pico which was released a few months ago requires some LCD Display in engineering applications. Here we will go through the LCD Display details and features. The LCD Display is based on HD44780 Driver. There are I2C versions and non-I2C versions of LCD Display. We will take both of these LCDs as an example and interface with Raspberry Pi Pico. We will write MicroPython Code for Interfacing 16×2 LCD Display with Raspberry Pi Pico.


Bill of Materials

You need to purchase following components for building this project.

S.N.COMPONENTSDESCRIPTIONQUANTITY
1Raspberry Pi PicoRPI Pico Board RP20401
2LCD Display16x2 I2C LCD Display1

Brief Description of 16X2 LCD Display

16×2 LCD is named so because it has 16 Columns and 2 Rows. So, it will have (16×2=32) 32 characters in total and each character will be made of 5×8 Pixel Dots. A Single character with all its Pixels is shown in the below picture.

Each character has (5×8=40) 40 Pixels and for 32 Characters we will have (32×40) 1280 Pixels. Further, the LCD should also be instructed about the Position of the Pixels. Hence it will be a complicated task to handle everything with the help of a microcontroller. Hence the LCD uses an interface IC like HD44780. This IC is mounted on the backside of the LCD Module. You can check the HD44780 Datasheet for more information.

The function of this IC is to get the Commands and Data from the MCU and process them to display meaningful information on LCD Screen. The LCD operating Voltage is 4.7V to 5.3V & the Current consumption is 1mA without a backlight. It can work on both 8-bit and 4-bit mode
It can also display any custom-generated characters. These LCDs are available in Green and Blue Backlight.

There are two section pins on the whole 16×2 LCD module. Some of them are data pins and some are command pins. Somehow, every pin has a role in controlling a single pixel on the display.

16X2 I2C LCD Display

This display incorporates an I2C interface that requires only 2 pins on a microcontroller to the interface. The I2C interface is a daughter board attached to the back of the LCD module. The I2C address for these displays is either 0x3F or 0x27.

The adapter has an 8-Bit I/O Expander chip PCF8574. This chip converts the I2C data from a microcontroller into the parallel data required by the LCD display. There is a small trimpot to make fine adjustments to the contrast of the display. In addition, there is a jumper on the board that supplies power to the backlight.


Interfacing 16×2 LCD Display with Raspberry Pi Pico

Now, let us interface the 16×2 LCD Display with Raspberry Pi Pico. You can use Fritzing Software to draw the Schematic. You can assemble the circuit on breadboard as shown in the image below.

Connect the pin 1, 5 & 16 of LCD to GND of Raspberry Pi Pico. Similarly, connect the Pin 2 & 15 of LCD to 5V Pin, i.e Vbus of Raspberry Pi Pico. Connect the Pin 4, 6, 11, 12, 13, 14 of LCD Display to Raspberry Pi Pico GP16, GP17, GP18, GP19, GP20, GP21 Pin.

To adjust the LCD Contrast, connect a 10K Potentiometer at Pin 3 of LCD Display.

Source Code/Program

Raspberry Pi Pico supports MicroPython Program for interfacing 16×2 LCD Display. You can either use Thonny IDE or uPyCraft IDE for running the MicroPython Code.

Here is a complete code for interfacing the 16X2 HD44780 LCD with Raspberry Pi Pico. Copy the code to the IDE and save it with the name main.py.

import machine

import utime

 

rs = machine.Pin(16,machine.Pin.OUT)

e = machine.Pin(17,machine.Pin.OUT)

d4 = machine.Pin(18,machine.Pin.OUT)

d5 = machine.Pin(19,machine.Pin.OUT)

d6 = machine.Pin(20,machine.Pin.OUT)

d7 = machine.Pin(21,machine.Pin.OUT)

 

def pulseE():

    e.value(1)

    utime.sleep_us(40)

    e.value(0)

    utime.sleep_us(40)

def send2LCD4(BinNum):

    d4.value((BinNum & 0b00000001) >>0)

    d5.value((BinNum & 0b00000010) >>1)

    d6.value((BinNum & 0b00000100) >>2)

    d7.value((BinNum & 0b00001000) >>3)

    pulseE()

def send2LCD8(BinNum):

    d4.value((BinNum & 0b00010000) >>4)

    d5.value((BinNum & 0b00100000) >>5)

    d6.value((BinNum & 0b01000000) >>6)

    d7.value((BinNum & 0b10000000) >>7)

    pulseE()

    d4.value((BinNum & 0b00000001) >>0)

    d5.value((BinNum & 0b00000010) >>1)

    d6.value((BinNum & 0b00000100) >>2)

    d7.value((BinNum & 0b00001000) >>3)

    pulseE()

def setUpLCD():

    rs.value(0)

    send2LCD4(0b0011)#8 bit

    send2LCD4(0b0011)#8 bit

    send2LCD4(0b0011)#8 bit

    send2LCD4(0b0010)#4 bit

    send2LCD8(0b00101000)#4 bit,2 lines?,5*8 bots

    send2LCD8(0b00001100)#lcd on, blink off, cursor off.

    send2LCD8(0b00000110)#increment cursor, no display shift

    send2LCD8(0b00000001)#clear screen

    utime.sleep_ms(2)#clear screen needs a long delay

 

setUpLCD()

rs.value(1)

for x in 'Hello World!':

    send2LCD8(ord(x))

Hence, once you run the code, the LCD Display will start showing the Hello World! message in LCD Display.

Interfacing 16×2 I2C LCD Display with Raspberry Pi Pico

The above code is valid for 16×2 LCD Display without any I2C Module. The PCF8574 I2C or SMBus module simplifies the above connection. Instead of using so many wiring, you can use this IC Module and convert the data output lines just to 2 pins.

Hence the LCD Display becomes an I2C Display with an I2C address of 0x27. The connection between the Raspberry Pi Pico and I2C LCD is very simples as shown below in the schematic.

Connect the LCD VCC & GND Pin to Raspberry Pi Pico 5V & GND Pin respectively. Connect the SDA & SCL pin of LCD to Raspberry Pi Pico GP8 & GP9 respectively.

Source Code/Program

The program for this section is divided into 3 parts. We need to write some driver code as well as I2C protocol code for this part.

Copy all this code and then save all of the 3 codes to Raspberry Pi Pico Board.

lcd_api.py :-


import time

class LcdApi:

 

    LCD_CLR = 0x01              # DB0: clear display

    LCD_HOME = 0x02             # DB1: return to home position

 

    LCD_ENTRY_MODE = 0x04       # DB2: set entry mode

    LCD_ENTRY_INC = 0x02        # --DB1: increment

    LCD_ENTRY_SHIFT = 0x01      # --DB0: shift

 

    LCD_ON_CTRL = 0x08          # DB3: turn lcd/cursor on

    LCD_ON_DISPLAY = 0x04       # --DB2: turn display on

    LCD_ON_CURSOR = 0x02        # --DB1: turn cursor on

    LCD_ON_BLINK = 0x01         # --DB0: blinking cursor

 

    LCD_MOVE = 0x10             # DB4: move cursor/display

    LCD_MOVE_DISP = 0x08        # --DB3: move display (0-> move cursor)

    LCD_MOVE_RIGHT = 0x04       # --DB2: move right (0-> left)

 

    LCD_FUNCTION = 0x20         # DB5: function set

    LCD_FUNCTION_8BIT = 0x10    # --DB4: set 8BIT mode (0->4BIT mode)

    LCD_FUNCTION_2LINES = 0x08  # --DB3: two lines (0->one line)

    LCD_FUNCTION_10DOTS = 0x04  # --DB2: 5x10 font (0->5x7 font)

    LCD_FUNCTION_RESET = 0x30   # See "Initializing by Instruction" section

 

    LCD_CGRAM = 0x40            # DB6: set CG RAM address

    LCD_DDRAM = 0x80            # DB7: set DD RAM address

 

    LCD_RS_CMD = 0

    LCD_RS_DATA = 1

 

    LCD_RW_WRITE = 0

    LCD_RW_READ = 1

 

    def __init__(self, num_lines, num_columns):

        self.num_lines = num_lines

        if self.num_lines > 4:

            self.num_lines = 4

        self.num_columns = num_columns

        if self.num_columns > 40:

            self.num_columns = 40

        self.cursor_x = 0

        self.cursor_y = 0

        self.implied_newline = False

        self.backlight = True

        self.display_off()

        self.backlight_on()

        self.clear()

        self.hal_write_command(self.LCD_ENTRY_MODE | self.LCD_ENTRY_INC)

        self.hide_cursor()

        self.display_on()

 

    def clear(self):

        """Clears the LCD display and moves the cursor to the top left

        corner.

        """

        self.hal_write_command(self.LCD_CLR)

        self.hal_write_command(self.LCD_HOME)

        self.cursor_x = 0

        self.cursor_y = 0

 

    def show_cursor(self):

        """Causes the cursor to be made visible."""

        self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY |

                               self.LCD_ON_CURSOR)

 

    def hide_cursor(self):

        """Causes the cursor to be hidden."""

        self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY)

 

    def blink_cursor_on(self):

        """Turns on the cursor, and makes it blink."""

        self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY |

                               self.LCD_ON_CURSOR | self.LCD_ON_BLINK)

 

    def blink_cursor_off(self):

        """Turns on the cursor, and makes it no blink (i.e. be solid)."""

        self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY |

                               self.LCD_ON_CURSOR)

 

    def display_on(self):

        """Turns on (i.e. unblanks) the LCD."""

        self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY)

 

    def display_off(self):

        """Turns off (i.e. blanks) the LCD."""

        self.hal_write_command(self.LCD_ON_CTRL)

 

    def backlight_on(self):

        """Turns the backlight on.

        This isn't really an LCD command, but some modules have backlight

        controls, so this allows the hal to pass through the command.

        """

        self.backlight = True

        self.hal_backlight_on()

 

    def backlight_off(self):

        """Turns the backlight off.

        This isn't really an LCD command, but some modules have backlight

        controls, so this allows the hal to pass through the command.

        """

        self.backlight = False

        self.hal_backlight_off()

 

    def move_to(self, cursor_x, cursor_y):

        """Moves the cursor position to the indicated position. The cursor

        position is zero based (i.e. cursor_x == 0 indicates first column).

        """

        self.cursor_x = cursor_x

        self.cursor_y = cursor_y

        addr = cursor_x & 0x3f

        if cursor_y & 1:

            addr += 0x40    # Lines 1 & 3 add 0x40

        if cursor_y & 2:    # Lines 2 & 3 add number of columns

            addr += self.num_columns

        self.hal_write_command(self.LCD_DDRAM | addr)

 

    def putchar(self, char):

        """Writes the indicated character to the LCD at the current cursor

        position, and advances the cursor by one position.

        """

        if char == '\n':

            if self.implied_newline:

                # self.implied_newline means we advanced due to a wraparound,

                # so if we get a newline right after that we ignore it.

                pass

            else:

                self.cursor_x = self.num_columns

        else:

            self.hal_write_data(ord(char))

            self.cursor_x += 1

        if self.cursor_x >= self.num_columns:

            self.cursor_x = 0

            self.cursor_y += 1

            self.implied_newline = (char != '\n')

        if self.cursor_y >= self.num_lines:

            self.cursor_y = 0

        self.move_to(self.cursor_x, self.cursor_y)

 

    def putstr(self, string):

        """Write the indicated string to the LCD at the current cursor

        position and advances the cursor position appropriately.

        """

        for char in string:

            self.putchar(char)

 

    def custom_char(self, location, charmap):

        """Write a character to one of the 8 CGRAM locations, available

        as chr(0) through chr(7).

        """

        location &= 0x7

        self.hal_write_command(self.LCD_CGRAM | (location << 3))

        self.hal_sleep_us(40)

        for i in range(8):

            self.hal_write_data(charmap[i])

            self.hal_sleep_us(40)

        self.move_to(self.cursor_x, self.cursor_y)

 

    def hal_backlight_on(self):

        """Allows the hal layer to turn the backlight on.

        If desired, a derived HAL class will implement this function.

        """

        pass

 

    def hal_backlight_off(self):

        """Allows the hal layer to turn the backlight off.

        If desired, a derived HAL class will implement this function.

        """

        pass

 

    def hal_write_command(self, cmd):

        """Write a command to the LCD.

        It is expected that a derived HAL class will implement this

        function.

        """

        raise NotImplementedError

 

    def hal_write_data(self, data):

        """Write data to the LCD.

        It is expected that a derived HAL class will implement this

        function.

        """

        raise NotImplementedError

 

    def hal_sleep_us(self, usecs):

        """Sleep for some time (given in microseconds)."""

        time.sleep_us(usecs)

pico_i2c_lcd.py :-


from lcd_api import LcdApi

from machine import I2C

from time import sleep_ms

 

DEFAULT_I2C_ADDR = 0x27

 

# Defines shifts or masks for the various LCD line attached to the PCF8574

 

MASK_RS = 0x01

MASK_RW = 0x02

MASK_E = 0x04

SHIFT_BACKLIGHT = 3

SHIFT_DATA = 4

 

 

class I2cLcd(LcdApi):

    """Implements a character based lcd connected via PCF8574 on i2c."""

 

    def __init__(self, i2c, i2c_addr, num_lines, num_columns):

        self.i2c = i2c

        self.i2c_addr = i2c_addr

        self.i2c.writeto(self.i2c_addr, bytearray([0]))

        sleep_ms(20)   # Allow LCD time to powerup

        # Send reset 3 times

        self.hal_write_init_nibble(self.LCD_FUNCTION_RESET)

        sleep_ms(5)    # need to delay at least 4.1 msec

        self.hal_write_init_nibble(self.LCD_FUNCTION_RESET)

        sleep_ms(1)

        self.hal_write_init_nibble(self.LCD_FUNCTION_RESET)

        sleep_ms(1)

        # Put LCD into 4 bit mode

        self.hal_write_init_nibble(self.LCD_FUNCTION)

        sleep_ms(1)

        LcdApi.__init__(self, num_lines, num_columns)

        cmd = self.LCD_FUNCTION

        if num_lines > 1:

            cmd |= self.LCD_FUNCTION_2LINES

        self.hal_write_command(cmd)

 

    def hal_write_init_nibble(self, nibble):

        """Writes an initialization nibble to the LCD.

        This particular function is only used during intiialization.

        """

        byte = ((nibble >> 4) & 0x0f) << SHIFT_DATA

        self.i2c.writeto(self.i2c_addr, bytearray([byte | MASK_E]))

        self.i2c.writeto(self.i2c_addr, bytearray([byte]))

 

    def hal_backlight_on(self):

        """Allows the hal layer to turn the backlight on."""

        self.i2c.writeto(self.i2c_addr, bytearray([1 << SHIFT_BACKLIGHT]))

 

    def hal_backlight_off(self):

        """Allows the hal layer to turn the backlight off."""

        self.i2c.writeto(self.i2c_addr, bytearray([0]))

 

    def hal_write_command(self, cmd):

        """Writes a command to the LCD.

        Data is latched on the falling edge of E.

        """

        byte = ((self.backlight << SHIFT_BACKLIGHT) | (((cmd >> 4) & 0x0f) << SHIFT_DATA))

        self.i2c.writeto(self.i2c_addr, bytearray([byte | MASK_E]))

        self.i2c.writeto(self.i2c_addr, bytearray([byte]))

        byte = ((self.backlight << SHIFT_BACKLIGHT) | ((cmd & 0x0f) << SHIFT_DATA))

        self.i2c.writeto(self.i2c_addr, bytearray([byte | MASK_E]))

        self.i2c.writeto(self.i2c_addr, bytearray([byte]))

        if cmd <= 3:

            # The home and clear commands require a worst case delay of 4.1 msec

            sleep_ms(5)

 

    def hal_write_data(self, data):

        """Write data to the LCD."""

        byte = (MASK_RS | (self.backlight << SHIFT_BACKLIGHT) | (((data >> 4) & 0x0f) << SHIFT_DATA))

        self.i2c.writeto(self.i2c_addr, bytearray([byte | MASK_E]))

        self.i2c.writeto(self.i2c_addr, bytearray([byte]))

        byte = (MASK_RS | (self.backlight << SHIFT_BACKLIGHT) | ((data & 0x0f) << SHIFT_DATA))

        self.i2c.writeto(self.i2c_addr, bytearray([byte | MASK_E]))

        self.i2c.writeto(self.i2c_addr, bytearray([byte]))

main.py :-

from pico_i2c_lcd import I2cLcd

from machine import I2C

from machine import Pin

import utime as time

 

 

i2c = I2C(id=1,scl=Pin(9),sda=Pin(8),freq=100000)

lcd = I2cLcd(i2c, 0x27, 2, 16)

 

while True:

      lcd.move_to(2,0)

      lcd.putstr('Hello world')

 Once, you run all these codes on Raspberry Pi Pico, the LCD will start displaying the Hello World Message.

Post a Comment (0)
Previous Post Next Post