Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris
 
PerlMonks  

comment on

( [id://3333]=superdoc: print w/replies, xml ) Need Help??

I was unable to find a Perl script to make my SainSmart LCD character display with i2c 'backpack' work from my Raspberry Pi.

Code for using these devices is available mainly for the Arduino. There is also code in C and python, but I could not find a simple working example in Perl. There is a module for an HTBackpackV2 (HiPi::Interface::HTBackpackV2), but I wasn't able to get this working with the SainSmart LCD.

I found a python script written by Matt Hawkins here: https://bitbucket.org/MattHawkinsUK/rpispy-misc/raw/master/python/lcd_i2c.py and I used his python script as the basis for my Perl Script - thanks Matt.

This script only provides basic display functionality and is run by calling it with two parameters, -s a string of text to display and -l the line number to display the text on. Hopefully it will provide both a way for others to get one of these LCD's working as well as provide the basis for more comprehensive implementations. The subroutines writeByte and sendByte are the key ones, together with the initialization sequence in the init subroutine.

The script must be run as root to set up the i2c object, but drops back to the regular user immediately after - enter your user name and user group in the two variables $user and $group which you will find at lines 39 & 40.

When called with no text and no line parameter the LCD initialization code is run.

After the code I have added some explanation about the way the data is transferred from the backpack to the LCD, including the transition from the power-up 8-bit mode to 4-bit mode. It took me some time to get my head round this!

In addition to -l 1 & -l 2 for the two lines, -l 50 & -l 51 turn the display off and back on. When the display is turned off with -l 50, the data remains in the LCD's DDRAM memory and the previous text will be displayed by -l 51 (a simple flashing display can be implemented)

If no line number is given, the text is printed on line 1 and overflows to line 2 if required. All existing text is cleared.

Examples (run as root/sudo):

lcd_i2c.pl => initialize
lcd_i2c.pl -s "" => initialize
lcd_i2c.pl -s "A string of characters" => prints line 1: 'A string of' & line 2: 'characters' (note: splits on word break)
lcd_i2c.pl -s "A string of characters" -l 1 => prints line 1: 'A string of char' & line 2 unchanged ('characters')
lcd_i2c.pl -l 50 => display turned off
lcd_i2c.pl -l 51 => display turned on. Last data on both lines displayed again
lcd_i2c.pl -s " " => clears display (note: -s "" initializes display)


Code for a Raspberry Pi with SainSmart 2 line, 16 character LCD display with i2c backpack at 0x3f, attached to the Pi's default i2c pins.

#!/usr/bin/perl # # lcd_i2c.pl # anita2R # Version 1.00, 24th May 2016 # # A script to display data on a SainSmart LCD character display # connected via the i2c bus # # The data to be displayed is passed to the routine as parameter -s # The line number is passed as parameter -l # # If -s is empty or null (""), the initialization code is run # If there is a text string and a line number the text is displayed # on that line and is truncated/padded to fit # If there is no line number, the text is displayed on line 1 and # overflowed onto line 2 - existing text on both lines is overwritten # Other line parameters: # -l 50 turns display off # -l 51 turns display on # use Getopt::Std; use HiPi::Utils; use HiPi::BCM2835::I2C qw( :all ); # use strict; # # get the parameters (-s, -l) getopt('sl:'); # pass parameters to meaningful variables our ( $opt_s, $opt_l ); my $lnParam = $opt_l; my $strParam = $opt_s; # # setup bus number, speed and i2c 'backpack' address my $i2cBus = BB_I2C_PERI_1; # bus #1 (vers.1 used bus #0) my $i2cSpeed = BB_I2C_CLOCK_100_KHZ; # said to be reliable on RPi my $i2cAddr = 0x3f; # SainSmart lcd backpack at 0x3f # # setup regular user & group id's - for permission drop-back my $user = <your user name>; my $group = <your user group>; # # setup values specific to the 2 row 16 character SainSmart i2c LCD my $width = 16; # Maximum characters per line my $lines = 2; # LCD lines my $dataMode = 0x01; # Mode - send data my $cmdMode = 0x00; # Mode - send command my $line1 = 0x80; # Address for the 1st line my $line2 = 0xC0; # Address for the 2nd line my $blStatus = 0x08; # Backlight mask On=0x08 - Off=0x00 my $loMask = 0xF0; # Masks off low-order bits in byte my $sEN = 0x04; # 0000 0100 - mask to set enable bit my $cEN = 0x0B; # 0000 1011 - mask to clear enable bit & data # # hold/wait times are in the code but do not seem to be required # perhaps the delay inherent in 12c serial to parallel conversion # is sufficient my $enHold = 0; # hold enable high (microseconds) my $wait = 0; # wait before next write (microseconds) # # Test if initialization required # (no text in -s & line not 50 or 51 (display off / on) my $initFlag = 0; if (( $strParam eq "" && $lnParam != 50 && $lnParam != 51 )) { $initFlag = 1; } # # Change line number (if any) to line address / exit if not valid # line = 50 display off, line = 51 display on, 0 = no -l parameter if ( $lnParam != 0 ) { if ( $lnParam == 1 ) { $lnParam = $line1; } elsif ( $lnParam == 2 ) { $lnParam = $line2; } elsif (( $lnParam != 50 && $lnParam != 51 )) { # non valid -l value exit 1; } } # # create an i2c device object my $objI2c = HiPi::BCM2835::I2C->new( peripheral => $i2cBus, address => $i2cAddr ); HiPi::Utils::drop_permissions_name( $user, $group ); $objI2c->set_baudrate( $i2cSpeed ); # # # ****************************************** # # ************** Main Program ************** # # if ( $initFlag ) { # init flag = 1, initialise display &init; } elsif ( $lnParam == 50 ) { # line = 50, turn display off &sendByte( 0x08, $cmdMode ); } elsif ( $lnParam == 51 ) { # line = 51, turn display on &sendByte( 0x0C, $cmdMode ); } else { # there is text - print it, depending on line number my ( $data1, $data2 ); if ( $lnParam == 0 ) { # no line specified - print on line 1 & overflow to line 2 if ( length($strParam) > $width ) { # overflow onto line 2 # get substring as long as display width $data1 = substr( $strParam, 0, $width ); # cut at last space so as not to split a word ($data1) = split /\s+(?=\S*+$)/, $data1; # remainder of string on second line $data2 = substr( $strParam, length($data1) + 1 ); } else { # text fits on line 1 $data1 = $strParam; $data2 = ""; } # pad / truncate both lines $data1 = &padTrStr( $data1 ); $data2 = &padTrStr( $data2 ); # print both lines &prntStr( $data1, $line1 ); &prntStr( $data2, $line2 ); } else { # pad / truncate then print on one (specified) line $data1 = &padTrStr( $strParam ); &prntStr( $data1, $lnParam ); } } # exit 0; # # # ****************************************** # # ************** Subroutines *************** # # # *************** Initialize *************** # sub init { # 8-bit write (no LCD data in lower nibble) &writeByte( 0x30, $cmdMode ); # 0011 xxxx Sets 8-bit mode &writeByte( 0x30, $cmdMode ); # repeat in case LCD in 4-bit # mode but out of sync &writeByte( 0x20, $cmdMode ); # 0010 xxxx Sets 4-bit mode # *** now in 4-bit mode *** # both nibbles of data sent to LCD using sendByte &sendByte( 0x28, $cmdMode ); # 0010 1000 2 lines & small chars. &sendByte( 0x0C, $cmdMode ); # 0000 1100 Display On, no cursor &sendByte( 0x01, $cmdMode ); # 0000 0001 Clear display } # # *************** Write Byte *************** # sub writeByte { my $byte = $_[0]; # # writes byte to i2c object (LCD) # Enable 'control port' toggled high-low # # write data with enable set $objI2c->bus_write( $byte | $sEN ); $objI2c->delayMicroseconds($enHold); # clear enable, clear data, keep backlight & mode $objI2c->bus_write( $byte | $cEN ); $objI2c->delayMicroseconds( $wait ); } # # **************** Send byte *************** # sub sendByte { my $data = $_[0]; my $mode = $_[1]; # # splits data into high & low-order # puts each nibble into high-order bits # then adds mode & backlight status bits into low-order bits # # mask off 4 low-order bits & 'add' mode and backlight my $data_high = (( $data & $loMask ) | $mode | $blStatus ); # shift 4 low bits to high bits, mask-off low order bits # & 'add' mode and backlight my $data_low = ((( $data << 4 ) & $loMask ) | $mode | $blStatus ); # Send both nibbles of data to write routine &writeByte( $data_high ); &writeByte( $data_low ); } # # ************* Print a String ************* # sub prntStr { my $message = $_[0]; my $line = $_[1]; my $i; # # send address for required line to LCD &sendByte( $line, $cmdMode ); # iterate through message string for ( $i = 0 ; $i < $width ; $i++ ) { # send bytes to be displayed (use character values) &sendByte( ord( substr( $message, $i, 1 )), $dataMode ); } } # # ********* Pad/Truncate a String ********** # sub padTrStr { my $string = $_[0]; # truncate $string = substr( $string, 0, $width ); # pad message with spaces - so it fills the line $string = sprintf( "%-${width}s\n", $string ); return ($string); }

Mode of operation & initialization

The i2c backpack transfers the 8 bits of data sent to it over the i2c bus as follows:
high-order bits go to the LCD high-order data lines
low-order bits go to the LCD control ports

3 2 1 0 Backlight Enable Read/Write Register select

When the LCD is in 8-bit mode it never gets any data on the low-order data lines from the backpack.
The command to switch the LCD to 4-bit mode is binary 0010 xxxx where x is 'don't care', so 4-bit mode can be set even when the low-order data lines are not connected.

Once in 4-bit mode the data presented to the high order data lines is alternately transferred by the LCD's controller to the low-order data lines, so recreating the original 8-bit commands or 8-bit character data.

On power-up, the LCD device is in 8-bit mode, but it could already have been initialized and in 4-bit mode and it could possibly be in 4-bit mode but out of sync so that the first nibble sent goes to the low-order data lines.

To account for these three possible states the initialization sequence uses the writeByte subroutine which sends single bytes to the backpack.

Binary 0011 0000 is sent twice - only 0011 appears on the high-order data lines and the low-order lines are undefined.
If the LCD is in 8-bit mode it will only receive 0011 on its high-order data lines - and this will reset 8-bit mode (twice!)
If the LCD is already in 4-bit mode the binary data 0011 0000 will place 0011 on the high-order data lines and 0011 on the low-order data lines, as the LCD controller moves the second nibble of data to the low-order lines. This will set 8-bit mode.
If the LCD is in 4-bit mode, but out of sync, the first 0011 will be sent to the high-order data lines but transferred by the LCD's controller to the low-order data lines. As the preceding data on the high-order data lines is unknown, the result of this write is undetermined. The second write of 0011 will be to the high-order data lines and will set the device to 8-bit mode.

The third write places 0010 on the high-order data lines and this places the device in 4-bit mode.

From here on the data is sent using sendByte which sends both nibbles of the data in the sequential 4-bit mode with the LCD controller putting alternate nibbles onto the low-order data lines.

Binary 0010 1000 Sets 2 lines & small characters (and 4-bit again)
The last initialization command is binary 0000 1100 which turns the display On as well as setting it to 'no cursor'. Modify as required.
The bits are:

7 6 5 4 3 2 1 0 0 0 0 0 1 C B x x=don't care Bit 3=1 display on, C=1 cursor on, B=1 blink cursor

All data written to the LCD must be set by toggling the enable control port high then low. The minimum high time (as seen on one manufacturer's datasheet) was 300 nanoseconds
The next enable high after enable is set low requires at least a further 200 nanoseconds wait for a combined high-low cycle of 500 nanoseconds.

The code has enable high delays and wait delays after writes, but in practice with the i2c serial transmission, the backpack serial to parallel conversion and other delays inherent in the code and a Raspberry Pi, the program works with the delays set to zero. If implemented in faster environments, delays might become necessary, so the delay code has been left in place.


In reply to i2c attached LCD Character Display for a Raspberry Pi by anita2R

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post; it's "PerlMonks-approved HTML":



  • Are you posting in the right place? Check out Where do I post X? to know for sure.
  • Posts may use any of the Perl Monks Approved HTML tags. Currently these include the following:
    <code> <a> <b> <big> <blockquote> <br /> <dd> <dl> <dt> <em> <font> <h1> <h2> <h3> <h4> <h5> <h6> <hr /> <i> <li> <nbsp> <ol> <p> <small> <strike> <strong> <sub> <sup> <table> <td> <th> <tr> <tt> <u> <ul>
  • Snippets of code should be wrapped in <code> tags not <pre> tags. In fact, <pre> tags should generally be avoided. If they must be used, extreme care should be taken to ensure that their contents do not have long lines (<70 chars), in order to prevent horizontal scrolling (and possible janitor intervention).
  • Want more info? How to link or How to display code and escape characters are good places to start.
Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others about the Monastery: (5)
As of 2024-04-25 13:49 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found