In this article, a comprehensive step-by-step guide to interface 16x2 I2C LCD display with Raspberry Pi Pico board using MicroPython. Raspberry Pi Pico has 4 different variants (Pico, Pico 2, Pico W, Pico 2W) supporting micro-python. This articles lays the foundation for more advanced embedded system and IoT projects.
Pre-Request
OS : Windows / Linux / Mac / Chrome
Thonny IDE.
MicroPython firmware in Raspberry Pi Pico / Pico 2 / Pico W / Pico 2W.
frommachineimportPin,SoftI2Cfrompico_i2c_lcdimportI2cLcdfromtimeimportsleep# LCD I2C address and dimensionsI2C_ADDR=0x27I2C_NUM_ROWS=2I2C_NUM_COLS=16# Initialize I2C i2c=SoftI2C(sda=Pin(8),scl=Pin(9),freq=400000)# Initialize 16x2 LCD lcd=I2cLcd(i2c,I2C_ADDR,I2C_NUM_ROWS,I2C_NUM_COLS)sleep(0.25)# Clear the LCDlcd.clear()# By default, it will start at (0,0) if the display is emptylcd.putstr(" Welcome to ")sleep(1)# Starting at the second line (0, 1)lcd.move_to(0,1)lcd.putstr(" Aavishkarah ")sleep(4)# turn off baklightlcd.backlight_off()lcd.display_off()
# library from https://github.com/TexturedPolak/RPI-PICO-I2C-LCDimporttimeclassLcdApi:# Implements the API for talking with HD44780 compatible character LCDs.# This class only knows what commands to send to the LCD, and not how to get# them to the LCD.## It is expected that a derived class will implement the hal_xxx functions.## The following constant names were lifted from the avrlib lcd.h header file,# with bit numbers changed to bit masks.# HD44780 LCD controller command setLCD_CLR=0x01# DB0: clear displayLCD_HOME=0x02# DB1: return to home positionLCD_ENTRY_MODE=0x04# DB2: set entry modeLCD_ENTRY_INC=0x02# DB1: incrementLCD_ENTRY_SHIFT=0x01# DB0: shiftLCD_ON_CTRL=0x08# DB3: turn lcd/cursor onLCD_ON_DISPLAY=0x04# DB2: turn display onLCD_ON_CURSOR=0x02# DB1: turn cursor onLCD_ON_BLINK=0x01# DB0: blinking cursorLCD_MOVE=0x10# DB4: move cursor/displayLCD_MOVE_DISP=0x08# DB3: move display (0-> move cursor)LCD_MOVE_RIGHT=0x04# DB2: move right (0-> left)LCD_FUNCTION=0x20# DB5: function setLCD_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" sectionLCD_CGRAM=0x40# DB6: set CG RAM addressLCD_DDRAM=0x80# DB7: set DD RAM addressLCD_RS_CMD=0LCD_RS_DATA=1LCD_RW_WRITE=0LCD_RW_READ=1def__init__(self,num_lines,num_columns):self.num_lines=num_linesifself.num_lines>4:self.num_lines=4self.num_columns=num_columnsifself.num_columns>40:self.num_columns=40self.cursor_x=0self.cursor_y=0self.implied_newline=Falseself.backlight=Trueself.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()defclear(self):# Clears the LCD display and moves the cursor to the top left cornerself.hal_write_command(self.LCD_CLR)self.hal_write_command(self.LCD_HOME)self.cursor_x=0self.cursor_y=0defshow_cursor(self):# Causes the cursor to be made visibleself.hal_write_command(self.LCD_ON_CTRL|self.LCD_ON_DISPLAY|self.LCD_ON_CURSOR)defhide_cursor(self):# Causes the cursor to be hiddenself.hal_write_command(self.LCD_ON_CTRL|self.LCD_ON_DISPLAY)defblink_cursor_on(self):# Turns on the cursor, and makes it blinkself.hal_write_command(self.LCD_ON_CTRL|self.LCD_ON_DISPLAY|self.LCD_ON_CURSOR|self.LCD_ON_BLINK)defblink_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)defdisplay_on(self):# Turns on (i.e. unblanks) the LCDself.hal_write_command(self.LCD_ON_CTRL|self.LCD_ON_DISPLAY)defdisplay_off(self):# Turns off (i.e. blanks) the LCDself.hal_write_command(self.LCD_ON_CTRL)defbacklight_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=Trueself.hal_backlight_on()defbacklight_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=Falseself.hal_backlight_off()defmove_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_xself.cursor_y=cursor_yaddr=cursor_x&0x3fifcursor_y&1:addr+=0x40# Lines 1 & 3 add 0x40ifcursor_y&2:# Lines 2 & 3 add number of columnsaddr+=self.num_columnsself.hal_write_command(self.LCD_DDRAM|addr)defputchar(self,char):# Writes the indicated character to the LCD at the current cursor# position, and advances the cursor by one position.ifchar=='\n':ifself.implied_newline:# self.implied_newline means we advanced due to a wraparound,# so if we get a newline right after that we ignore it.passelse:self.cursor_x=self.num_columnselse:self.hal_write_data(ord(char))self.cursor_x+=1ifself.cursor_x>=self.num_columns:self.cursor_x=0self.cursor_y+=1self.implied_newline=(char!='\n')ifself.cursor_y>=self.num_lines:self.cursor_y=0self.move_to(self.cursor_x,self.cursor_y)defputstr(self,string):# Write the indicated string to the LCD at the current cursor# position and advances the cursor position appropriately.forcharinstring:self.putchar(char)defcustom_char(self,location,charmap):# Write a character to one of the 8 CGRAM locations, available# as chr(0) through chr(7).location&=0x7self.hal_write_command(self.LCD_CGRAM|(location<<3))self.hal_sleep_us(40)foriinrange(8):self.hal_write_data(charmap[i])self.hal_sleep_us(40)self.move_to(self.cursor_x,self.cursor_y)defhal_backlight_on(self):# Allows the hal layer to turn the backlight on.# If desired, a derived HAL class will implement this function.passdefhal_backlight_off(self):# Allows the hal layer to turn the backlight off.# If desired, a derived HAL class will implement this function.passdefhal_write_command(self,cmd):# Write a command to the LCD.# It is expected that a derived HAL class will implement this function.raiseNotImplementedErrordefhal_write_data(self,data):# Write data to the LCD.# It is expected that a derived HAL class will implement this function.raiseNotImplementedErrordefhal_sleep_us(self,usecs):# Sleep for some time (given in microseconds)time.sleep_us(usecs)
# library from https://github.com/TexturedPolak/RPI-PICO-I2C-LCDimportutimeimportgcfromlcd_apiimportLcdApifrommachineimportI2C# PCF8574 pin definitionsMASK_RS=0x01# P0MASK_RW=0x02# P1MASK_E=0x04# P2SHIFT_BACKLIGHT=3# P3SHIFT_DATA=4# P4-P7classI2cLcd(LcdApi):#Implements a HD44780 character LCD connected via PCF8574 on I2Cdef__init__(self,i2c,i2c_addr,num_lines,num_columns):self.i2c=i2cself.i2c_addr=i2c_addrself.i2c.writeto(self.i2c_addr,bytes([0]))utime.sleep_ms(20)# Allow LCD time to powerup# Send reset 3 timesself.hal_write_init_nibble(self.LCD_FUNCTION_RESET)utime.sleep_ms(5)# Need to delay at least 4.1 msecself.hal_write_init_nibble(self.LCD_FUNCTION_RESET)utime.sleep_ms(1)self.hal_write_init_nibble(self.LCD_FUNCTION_RESET)utime.sleep_ms(1)# Put LCD into 4-bit modeself.hal_write_init_nibble(self.LCD_FUNCTION)utime.sleep_ms(1)LcdApi.__init__(self,num_lines,num_columns)cmd=self.LCD_FUNCTIONifnum_lines>1:cmd|=self.LCD_FUNCTION_2LINESself.hal_write_command(cmd)gc.collect()defhal_write_init_nibble(self,nibble):# Writes an initialization nibble to the LCD.# This particular function is only used during initialization.byte=((nibble>>4)&0x0f)<<SHIFT_DATAself.i2c.writeto(self.i2c_addr,bytes([byte|MASK_E]))self.i2c.writeto(self.i2c_addr,bytes([byte]))gc.collect()defhal_backlight_on(self):# Allows the hal layer to turn the backlight onself.i2c.writeto(self.i2c_addr,bytes([1<<SHIFT_BACKLIGHT]))gc.collect()defhal_backlight_off(self):#Allows the hal layer to turn the backlight offself.i2c.writeto(self.i2c_addr,bytes([0]))gc.collect()defhal_write_command(self,cmd):# Write 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,bytes([byte|MASK_E]))self.i2c.writeto(self.i2c_addr,bytes([byte]))byte=((self.backlight<<SHIFT_BACKLIGHT)|((cmd&0x0f)<<SHIFT_DATA))self.i2c.writeto(self.i2c_addr,bytes([byte|MASK_E]))self.i2c.writeto(self.i2c_addr,bytes([byte]))ifcmd<=3:# The home and clear commands require a worst case delay of 4.1 msecutime.sleep_ms(5)gc.collect()defhal_write_data(self,data):# Write data to the LCD. Data is latched on the falling edge of E.byte=(MASK_RS|(self.backlight<<SHIFT_BACKLIGHT)|(((data>>4)&0x0f)<<SHIFT_DATA))self.i2c.writeto(self.i2c_addr,bytes([byte|MASK_E]))self.i2c.writeto(self.i2c_addr,bytes([byte]))byte=(MASK_RS|(self.backlight<<SHIFT_BACKLIGHT)|((data&0x0f)<<SHIFT_DATA))self.i2c.writeto(self.i2c_addr,bytes([byte|MASK_E]))self.i2c.writeto(self.i2c_addr,bytes([byte]))gc.collect()
# Clear the LCDlcd.clear()# By default, it will start at (0,0) if the display is emptylcd.putstr(" Welcome to ")sleep(1)# Starting at the second line (0, 1)lcd.move_to(0,1)lcd.putstr(" Aavishkarah ")sleep(4)# turn off baklightlcd.backlight_off()lcd.display_off()
lcd.clear() clear the screen and set the cursor position to (Row 1 and Column 1)
lcd.putstr() used to send data to lcd module for display.
lcd.move_to(<column>, <row>)
lcd.move_to(0, 1) place the cursor to second line(row) and first character(column) position.
lcd.backlight_off() turns the backlight led off.
lcd.display_off() turns the display off.
Try It
Alter the output content on the display by passing your data argument to the putstr method.