LCD1602 with the rpi5 via smbus i2c

I have a question regarding controlling my LCD1602 with my Raspberry Pi 5. To do this I am using the smbus2 library. Here is my code that uses the smbus library to control the i2c system:

try:
    import smbus2
    LCD = smbus2.SMBus(1)
    address = 0x27
    IR = 0
    DR = 1
    ASCII_WORD = "HELLO"

    LCD.write_byte_data(address, IR, 0x80, force=None) #set the cursor at the beginning of line 1

    for char in ASCII_WORD:
        LCD.write_byte_data(address, IR, 0x10, force=None) #set the cursor 1 column left
        for bit in range(8):
            LCD.write_byte_data(address, DR, ord(char) >> (7 - bit), force=None)#write ASCII character
except KeyboardInterrupt:
    LCD.write_byte_data(address, IR, 0x01, force=None)#clear display
    LCD.close()

Here is a great link in which I received most of the data bit values from: LCD 16x2 Pinout, Commands, and Displaying Custom Character

This is what is going on in my code:

  1. Set the cursor position.
  2. Write the ASCII of the character I want.

Issue:

The result from this code was unexpected, the cursor position was on the 5th column of the second row and there was a ≣ sign on the first row second column. I soon realised that the for bit in range(8): was unnecessary because the smbus2 library handles converting the decimal values to binary and sending that bit by bit (Please correct me if I’m wrong). This extra loop is likely causing duplicates of ASCII codes.

Debugged code:

try:
    import time
    import smbus2
    LCD = smbus2.SMBus(1)
    address = 0x27
    IR = 0
    DR = 1
    ASCII_WORD = "HELLO"

    LCD.write_byte_data(address, IR, 0x80, force=None) #set the cursor at the beginning of line 1
    for char in ASCII_WORD:
        LCD.write_byte_data(address, IR, 0x10, force=None) #set the cursor 1 column left
            LCD.write_byte_data(address, DR, ord(char) >> (7 - bit), force=None)#write ASCII character
except KeyboardInterrupt:
    LCD.write_byte_data(address, IR, 0x01, force=None)#clear display
    LCD.close()

New Issue:

This code creates an issue where the whole LCD backlight turns off. It could be that the whole LCD turns off, but the power LED stays on.

Debugging:

I tried removing the except block as I thought maybe it was just clearing the display - the backlight was still turning off when I ran the code. I also tried isolating each LCD.write_byte_data() to see if one singular one was causing the issue - each isolated one was causing the backlight to turn off. I then found the “>> (7-bit)” line was still in my code so I removed it. Here is my revised code:

try:
    import time
    import smbus2
    LCD = smbus2.SMBus(1)
    address = 0x27
    IR = 0
    DR = 1
    ASCII_WORD = "HELLO"

    LCD.write_byte_data(address, IR, 0x80, force=None) #set the cursor at the beginning of line 1
    for char in ASCII_WORD:
        LCD.write_byte_data(address, IR, 0x10, force=None) #set the cursor 1 column left
            LCD.write_byte_data(address, DR, ord(char), force=None)#write ASCII character
except KeyboardInterrupt:
    LCD.write_byte_data(address, IR, 0x01, force=None)#clear display
    LCD.close()

There is a new problem that has come up, when I run the above code, the LCD screen stays the same as it is at startup, a blue background. Next, to see if the issue was happening with my old original code where I had an extra for loop that I removed, and when I ran that code the screen turned right off instead of displaying the cursor sign and ≣ signal. So for some reason the outcome of the first code has changed without me modifying it at all.

Could anyone please help me to debug these issues, specifically why the LCD is turning off/not changing at all? I would really appreciate some help. Thank you in advance.

1 Like

Suggest you use the library provided by the manufacture.
The LCD1602 I have is :-

Follow the Wiki link on that page; in the python area there is a link to the demo file and some examples.

The RGB1602.py library works well with a Pi 3B+ which is set up as the base unit for a weather station. Trying to control the LCD directly as you are will generate the kind of errors you are seeing.

Cheers
Jim

1 Like

Hi Jim, thank you for the links, I know this request might be a bit frustrating, but could you please tell me why “Trying to control the LCD directly as you are will generate the kind of errors you are seeing.” to give me some peace of mind, and so that I know what went wrong and what not to do again.
Thank you,
Matt.

Some I2C LCD backpacks use specific bits to toggle the backlight on or off. If this LCD supports it and backlight control is needed, you could experiment with adding a specific backlight-on command (typically an OR-ed bit with 0x08).
If you want to control the cursor movement separately, commands like 0x14 (shift right) or 0x10 (shift left) could be useful.

I’ll try the 0x08 command and see what happens. I’ll let you know how it goes. Thanks!!!

1 Like

For some reason whenever I do run the code there is no change in the LCD, the backlight doesn’t turn off even without the 0x08 bits, I haven’t even changed my code or anything, any idea on what is going on?

I’ve updated the original post with the new issue.

@matthew271007 OK. The program I have turns the backlight on and off ok and the characters are correct and in the right locations. I use the python library and the routines as per the links I provided.

Looking at the library (code below) there are a specific number of commands to setup the LCD when the library is started. This ensures the device will operate correctly and is probably what the manufacturer of the control chip recommends. Sending characters the LCD is very similar to what you are doing.

I don’t know if the LCD you have is the same as the one I linked, but most of these LCD1602 devices are very similar.

def __init__(self, col, row): runs when the library is imported and calls
def begin(self,cols,lines): which configures the LCD chip.

I added

def blank(self):
      self.setRGB(0,0,0)

to the library to set the RGB LED to off. Before that, it would not turn off with the other commands in the library.

RGB1602.py Original from Waveshare.

import time
from smbus import SMBus
b = SMBus(1)

#Device I2C Address
LCD_ADDRESS   =  (0x7c>>1)
RGB_ADDRESS   =  (0xc0>>1)

#color define

REG_RED    =     0x04
REG_GREEN  =     0x03
REG_BLUE   =     0x02
REG_MODE1  =     0x00
REG_MODE2  =     0x01
REG_OUTPUT =     0x08
LCD_CLEARDISPLAY = 0x01
LCD_RETURNHOME = 0x02
LCD_ENTRYMODESET = 0x04
LCD_DISPLAYCONTROL = 0x08
LCD_CURSORSHIFT = 0x10
LCD_FUNCTIONSET = 0x20
LCD_SETCGRAMADDR = 0x40
LCD_SETDDRAMADDR = 0x80

#flags for display entry mode
LCD_ENTRYRIGHT = 0x00
LCD_ENTRYLEFT = 0x02
LCD_ENTRYSHIFTINCREMENT = 0x01
LCD_ENTRYSHIFTDECREMENT = 0x00

#flags for display on/off control
LCD_DISPLAYON = 0x04
LCD_DISPLAYOFF = 0x00
LCD_CURSORON = 0x02
LCD_CURSOROFF = 0x00
LCD_BLINKON = 0x01
LCD_BLINKOFF = 0x00

#flags for display/cursor shift
LCD_DISPLAYMOVE = 0x08
LCD_CURSORMOVE = 0x00
LCD_MOVERIGHT = 0x04
LCD_MOVELEFT = 0x00

#flags for function set
LCD_8BITMODE = 0x10
LCD_4BITMODE = 0x00
LCD_2LINE = 0x08
LCD_1LINE = 0x00
LCD_5x8DOTS = 0x00


class RGB1602:
  def __init__(self, col, row):
    self._row = row
    self._col = col
    self._showfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS;
    self.begin(self._row,self._col)

        
  def command(self,cmd):
    b.write_byte_data(LCD_ADDRESS,0x80,cmd)

  def write(self,data):
    b.write_byte_data(LCD_ADDRESS,0x40,data)
    
  def setReg(self,reg,data):
    b.write_byte_data(RGB_ADDRESS,reg,data)


  def setRGB(self,r,g,b):
    self.setReg(REG_RED,r)
    self.setReg(REG_GREEN,g)
    self.setReg(REG_BLUE,b)

  def setCursor(self,col,row):
    if(row == 0):
      col|=0x80
    else:
      col|=0xc0;
    self.command(col)

  def clear(self):
    self.command(LCD_CLEARDISPLAY)
    time.sleep(0.002)
  def printout(self,arg):
    if(isinstance(arg,int)):
      arg=str(arg)

    for x in bytearray(arg,'utf-8'):
      self.write(x)


  def display(self):
    self._showcontrol |= LCD_DISPLAYON 
    self.command(LCD_DISPLAYCONTROL | self._showcontrol)

 
  def begin(self,cols,lines):
    if (lines > 1):
        self._showfunction |= LCD_2LINE 
     
    self._numlines = lines 
    self._currline = 0 
     
    time.sleep(0.05)

    
    # Send function set command sequence
    self.command(LCD_FUNCTIONSET | self._showfunction)
    #delayMicroseconds(4500);  # wait more than 4.1ms
    time.sleep(0.005)
    # second try
    self.command(LCD_FUNCTIONSET | self._showfunction);
    #delayMicroseconds(150);
    time.sleep(0.005)
    # third go
    self.command(LCD_FUNCTIONSET | self._showfunction)
    # finally, set # lines, font size, etc.
    self.command(LCD_FUNCTIONSET | self._showfunction)
    # turn the display on with no cursor or blinking default
    self._showcontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF 
    self.display()
    # clear it off
    self.clear()
    # Initialize to default text direction (for romance languages)
    self._showmode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT 
    # set the entry mode
    self.command(LCD_ENTRYMODESET | self._showmode);

    # backlight init
    self.setReg(REG_MODE1, 0)
    # set LEDs controllable by both PWM and GRPPWM registers
    self.setReg(REG_OUTPUT, 0xFF)
    # set MODE2 values
    # 0010 0000 -> 0x20  (DMBLNK to 1, ie blinky mode)
    self.setReg(REG_MODE2, 0x20)
  
    self.setColorWhite()

  def setColorWhite(self):
    self.setRGB(255, 255, 255)

Example Program
Display Hello World for 5 seconds then blank display

import RGB1602
import time
lcd=RGB1602.RGB1602(16,2)

lcd.setCursor(0, 0)
lcd.printout("Hello World")
sleep(5)

# Blank Display
lcd.setCursor(0, 0)
lcd.printout("                ")
lcd.setCursor(0, 1)
lcd.printout("                ")
lcd.blank()
lcd.clear

Regards
Jim

2 Likes

When I run your suggested code with the library, an error comes up:

Traceback (most recent call last):
  File "/home/matthew/python/SMBUS_LCD1602_TEST.py", line 4, in <module>
    lcd=RGB1602.RGB1602(16,2)
  File "/home/matthew/python/RGB1602.py", line 59, in __init__
    self.begin(self._row,self._col)
  File "/home/matthew/python/RGB1602.py", line 111, in begin
    self.command(LCD_FUNCTIONSET | self._showfunction)
  File "/home/matthew/python/RGB1602.py", line 63, in command
    b.write_byte_data(LCD_ADDRESS,0x80,cmd)
OSError: [Errno 121] Remote I/O error

Could you please help me troubleshoot this, Thanks!!!

Hi @matthew271007 The error occurs the first time the library tries to talk to the LCD.
My guess is the address is wrong.
The Waveshare library has:

#Device I2C Address
LCD_ADDRESS   =  (0x7c>>1)
RGB_ADDRESS   =  (0xc0>>1)

Your original post is using only one address: address = 0x27

0x7c & 0xc0 are bit shifted right to become 0x3e & 0x60.
Both Waveshare and DFRobot displays sold by Core Electronics use the same address and bit shift them. One is to access the LCD and the other the RGB Backlight.

image

As the display you are using responds to a different address for the LCD it possible it is using different control chips. Can you provide a link to the display you are using, or a pic of it ??

The commands seem to be very similar, it might just be a case of using 0x27 as the address without bit shifting. But how the backlight is controlled is another matter.

Anyway … a link pr pic would help identify the display and might give a clue how to use it.
Cheers
Jim

2 Likes

I’m using this one made by sunfounder:
I2C LCD1602 Module

It seems that the products from that supplier can be either 0x27 or 0x37so it is worth checking both. What is the configuration of the A0, A1 and A2 jumpers for your board? If your board has headers at the end of the I2C module then the jumper will determine whether or not you can control the LED.

2 Likes

The error is now gone when I replaced the address of 0x7c>>1 with 0x27 but now the LCD doesn’t make any change - the screen stays the same as what it is at startup. I wonder if this is something to do with the library, maybe it isn’t made for my specific model, or maybe it is something to do with the RGB address. Thank you for all the help so far.

All my A0, A1 and A2 are unconnected from each other, I think this means 0x27 is my address.

@matthew271007 Thanks for providing the link. The Waveshare library will not work with that LCD, the two devices use different methods to control the LCD.

The daughter board has a PCF8574T chip. Remote 8-Bit I/O Expander for I2C Bus.
Search for pcf8574t datasheet links to a Texas Instruments pdf for the chip.
This does not show how the chip is used on the daughter board.

From the LCD1602 link you provided the pins for the LCD are :

The image from Sunfounder shows a jumper near the A & K pins and LED as the label. Possibly the jumper is for backlight on or off, not controllable by software. (unsure)

The I/O expander chip only has 8 output pins so these must be connected to D0 to D8 of the LCD. The RS R/W E pins must be tied to whatever will make the LCD work. But there is a problem here as the RS pin determines if it is a command register or a data register. How does the daughter board switch between the two ??

To get your code to work it is necessary to determine how to send commands and data that will cause the daughter board to send the right commands and data to the LCD.

If this was my project, at this stage I might cut my loses and buy one of the Waveshare or DFRobot boards. If you still need this to work on your board you most likely will have to contact Sunfounder or the Manufacturer of the daughter board and ask how to make it work. Probably over time you could figure what is needed but it will be a lot of trial and error before you get it right.

Regards
Jim

2 Likes

@matthew271007
I found the following documentation.

Note the addressing is :-

It also provides a link to a GitHub Arduino library.

Porting the library to python would involve understanding how the C++ code works first and then convert that code to python commands. First I would want to know the Arduino library actually works. So I would try it with a UNO or similar connected to the display. No good trying to convert the library if it does not work.

Understanding how it works and converting that to python is not an easy task. But at least it is a start.

Cheers
Jim

EDIT: Looks like the backlight can be controlled by software.

Setup Function: The setup() function is executed once when the Arduino starts. In this function, the LCD is initialized, cleared, and the backlight is turned on. Then, two messages are displayed on the LCD.

I had a look at the .cpp & .h files for the library, it would take some time for me to understand what the code is doing and why.
They set the LCD to 4 bits mode, the other 4 bits are used for RS, R/W, E, and backlight. I seem to remember the display can be set to 4 bit mode, taking 2 writes to send an 8 bit command or data.

1 Like

I had some trouble getting a 1602 display to work with that piggy back I2C board fitted. I was using Arduino.
None of the libraries wanted to work then I discovered another one hidden in the wealth of info.
This one
#include <LiquidCrystal_PCF8574.h>
To initialise this is the code I used
#include <LiquidCrystal_PCF8574.h>

// initialize the library with I2C address
LiquidCrystal_PCF8574 lcd(0x27);
I think the rows and columns statement in the example was with the address. This did not work too well and I had to move it down to the “set up” section like this.
void setup() {
//Serial.begin(9600);
// initialise and set up the LCD’s number of columns and rows:
lcd.begin(16, 2);
lcd.setBacklight(10);
lcd.clear();
I have been using this in differs places for a long time now without any bother.
Cheers Bob

3 Likes

@Robert93820 Thanks Bob.

@matthew271007 The library Bob mentioned is better than the one I linked. Looks like it has had more development but essentially does the same thing.

If you have a UNO test the display and this library to prove it works then we can look at getting it to work in python.

Cheers
Jim

EDIT: I also found some GitHub attempts at python for the LCD device, yet to investigate them fully. Suffice to say it is looking better at getting this to eventually work now that I have a much better understanding of how it is supposed to work. Just wish I had one then could be absolutely sure.

2 Likes

@matthew271007 Most of my previous posts can be ignored, it was me getting to understand the LCD1602 you have without having one in front of me.

There is a python library available from PyPi as a managed package in Thonny. It looks like it might work and is well written. The upper 4 bits are data and the lower 4 bits control RS, R/W, E, and backlight for a byte sent to the LCD via 12C.
GitHub link.

I don’t know if you know how to install managed packages in Thonny, but its quite easy.

Click Tools / Managed Packages
In the search bar type LCD1602
Click on Search on PyPi

Click on i2clcd
Click Install
Close the window after install is finished.

The following is from the GitHub page as an example to use.

import i2clcd

lcd = i2clcd.i2clcd(i2c_bus=1, i2c_addr=0x27, lcd_width=16)
lcd.init()

# fill a line by the text
lcd.print_line('hello', line=0)
lcd.print_line('world!', line=1, align='RIGHT')

# print text at the current cursor position
lcd.move_cursor(1, 0)
lcd.print('the')

# custom character
char_celsius = (0x10, 0x06, 0x09, 0x08, 0x08, 0x09, 0x06, 0x00)
lcd.write_CGRAM(char_celsius, 0)
lcd.move_cursor(0, 6)
lcd.print(b'CGRAM: ' + i2clcd.CGRAM_CHR[0])

I have no way of knowing if this will work but it might help you out.
Cheers
Jim

1 Like

@James46717 Hello Jim, sorry I couldn’t respond last night I was a bit busy. After reading the posts, I’d just like to appreciate all the help you’ve given me and the time you’ve put in to helping me. I’ll take a look at that last library and let you know how it goes.

1 Like