Raspberry Pi Pico
16x2 Character LCD
I had a 3V3 16x2 character LCD display that I had been trying to get running on the Pico. You can find displays like these with 'backpacks' that connect the inputs to port expanders. This isn't one of those. It is just the display itself. If you are doing this, make sure to check that your LCD is for 3V3.
Here is the display connected to my Pimoroni Pico LiPo. Under the jumpers, you might just be able to make out the potentiometer. This is used to set the contrast.

This is a Fritzing diagram showing the connections more clearly.

I find that a list of connections is a little easier to work with. So,
- VSS to GND
- VDD to 3V3
- VO to Potentiometer Wiper
- RS to GP15
- R/W to GND
- E to GP14
- DB4 to GP16
- DB5 to GP17
- DB6 to GP18
- DB7 to GP19
- A to 3V3
- K to GND
After several attempts at getting the display to work, I turned to Adafruit's CircuitPython library.
(https://github.com/adafruit/Adafruit_CircuitPython_CharLCD)
I made the changes needed to convert to MicroPython and left out some of the stuff I didn't need. I left in all of the constants. The library was saved as lcd.py.
from time import sleep_us, sleep_ms
from micropython import const
# Commands
_LCD_CLEARDISPLAY = const(0x01)
_LCD_RETURNHOME = const(0x02)
_LCD_ENTRYMODESET = const(0x04)
_LCD_DISPLAYCONTROL = const(0x08)
_LCD_CURSORSHIFT = const(0x10)
_LCD_FUNCTIONSET = const(0x20)
_LCD_SETCGRAMADDR = const(0x40)
_LCD_SETDDRAMADDR = const(0x80)
# Entry flags
_LCD_ENTRYLEFT = const(0x02)
_LCD_ENTRYSHIFTDECREMENT = const(0x00)
# Control flags
_LCD_DISPLAYON = const(0x04)
_LCD_CURSORON = const(0x02)
_LCD_CURSOROFF = const(0x00)
_LCD_BLINKON = const(0x01)
_LCD_BLINKOFF = const(0x00)
# Move flags
_LCD_DISPLAYMOVE = const(0x08)
_LCD_MOVERIGHT = const(0x04)
_LCD_MOVELEFT = const(0x00)
# Function set flags
_LCD_4BITMODE = const(0x00)
_LCD_2LINE = const(0x08)
_LCD_1LINE = const(0x00)
_LCD_5X8DOTS = const(0x00)
# Offset for up to 4 rows.
_LCD_ROW_OFFSETS = (0x00, 0x40, 0x14, 0x54)
class LCD:
def __init__(self, rs, en, d4, d5, d6, d7, columns, lines):
self.columns = columns
self.lines = lines
self.reset = rs
self.enable= en
self.dl4 = d4
self.dl5 = d5
self.dl6 = d6
self.dl7 = d7
self._write8(0x33)
self._write8(0x32)
# Initialise display control
self.displaycontrol = _LCD_DISPLAYON | _LCD_CURSOROFF | _LCD_BLINKOFF
# Initialise display function
self.displayfunction = _LCD_4BITMODE | _LCD_1LINE | _LCD_2LINE | _LCD_5X8DOTS
# Initialise display mode
self.displaymode = _LCD_ENTRYLEFT | _LCD_ENTRYSHIFTDECREMENT
# Write to displaycontrol
self._write8(_LCD_DISPLAYCONTROL | self.displaycontrol)
# Write to displayfunction
self._write8(_LCD_FUNCTIONSET | self.displayfunction)
# Set entry mode
self._write8(_LCD_ENTRYMODESET | self.displaymode)
self.clear()
self._message = None
self._enable = None
self._direction = None
# track row and column used in cursor_position
# initialize to 0,0
self.row = 0
self.column = 0
self._column_align = False
def home(self):
self._write8(_LCD_RETURNHOME)
sleep_ms(3)
def clear(self):
self._write8(_LCD_CLEARDISPLAY)
sleep_ms(3)
def column_align(self, enable):
if isinstance(enable, bool):
self._column_align = enable
else:
raise ValueError("The column_align value must be either True or False")
def cursor(self, show):
if show:
self.displaycontrol |= _LCD_CURSORON
else:
self.displaycontrol &= ~_LCD_CURSORON
self._write8(_LCD_DISPLAYCONTROL | self.displaycontrol)
def cursor_position(self, column, row):
# Clamp row to the last row of the display
if row >= self.lines:
row = self.lines - 1
# Clamp to last column of display
if column >= self.columns:
column = self.columns - 1
# Set location
self._write8(_LCD_SETDDRAMADDR | (column + _LCD_ROW_OFFSETS[row]))
# Update self.row and self.column to match setter
self.row = row
self.column = column
def blink(self, blink):
if blink:
self.displaycontrol |= _LCD_BLINKON
else:
self.displaycontrol &= ~_LCD_BLINKON
self._write8(_LCD_DISPLAYCONTROL | self.displaycontrol)
def display(self, enable):
if enable:
self.displaycontrol |= _LCD_DISPLAYON
else:
self.displaycontrol &= ~_LCD_DISPLAYON
self._write8(_LCD_DISPLAYCONTROL | self.displaycontrol)
def message(self, message):
self._message = message
line = self.row
initial_character = 0
for character in message:
if initial_character == 0:
if self.displaymode & _LCD_ENTRYLEFT > 0:
col = self.column
else:
col = self.columns - 1 - self.column
self.cursor_position(col, line)
initial_character += 1
if character == "\n":
line += 1
if self.displaymode & _LCD_ENTRYLEFT > 0:
col = self.column * self._column_align
else:
if self._column_align:
col = self.column
else:
col = self.columns - 1
self.cursor_position(col, line)
else:
self._write8(ord(character), True)
self.column, self.row = 0, 0
def create_char(self, location, pattern):
location &= 0x7
self._write8(_LCD_SETCGRAMADDR | (location << 3))
for i in range(8):
self._write8(pattern[i], char_mode=True)
def _write8(self, value, char_mode=False):
sleep_ms(1)
self.reset.value(char_mode)
# set character/data bit. (charmode = False)
self.reset.value(char_mode)
# WRITE upper 4 bits
self.dl4.value(((value >> 4) & 1) > 0)
self.dl5.value(((value >> 5) & 1) > 0)
self.dl6.value(((value >> 6) & 1) > 0)
self.dl7.value(((value >> 7) & 1) > 0)
# send command
self._pulse_enable()
# WRITE lower 4 bits
self.dl4.value((value & 1) > 0)
self.dl5.value(((value >> 1) & 1) > 0)
self.dl6.value(((value >> 2) & 1) > 0)
self.dl7.value(((value >> 3) & 1) > 0)
self._pulse_enable()
def _pulse_enable(self):
self.enable.value(0)
sleep_us(1)
self.enable.value(1)
sleep_us(1)
self.enable.value(0)
sleep_us(0)
Here is some code testing a handful of the features of the library. I have also created a custom character. You can have up to 8 of these, stored at locations 0 to 7. You use a hex escape sequence to insert them into your message. The character is defined as a list of binary integers, each representing the pixel data of a 5 column row. The characters are 5 pixels wide and 8 pixels tall. I did a stickperson but you may need to design characters that otherwise would not display.
from machine import Pin
from time import sleep
from lcd import LCD
rs = Pin(15, Pin.OUT)
en = Pin(14, Pin.OUT)
d4 = Pin(16, Pin.OUT)
d5 = Pin(17, Pin.OUT)
d6 = Pin(18, Pin.OUT)
d7 = Pin(19, Pin.OUT)
# make a display object
display = LCD(rs, en, d4, d5, d6, d7, 16, 2)
# basic message test
display.message("Hello World!")
sleep(3)
# clear the screen
display.clear()
sleep(1)
# show the cursor
display.cursor(True)
sleep(2)
display.message("Hello again!")
sleep(2)
# hide the cursor
display.cursor(False)
sleep(2)
#fill the display
display.home()
display.message("0123456789ABCDEF\n0123456789ABCDEF")
sleep(3)
display.clear()
# create character
stickperson = [0xe,0xe,0x4,0x1f,0x4,0x4,0xa,0x11]
display.create_char(0, stickperson)
display.message("\x00 is a custom\ncharacter.")
sleep(3)
display.clear()
for i in range(100):
display.message(str(i))
sleep(0.25)

