Showing posts with label LCD. Show all posts
Showing posts with label LCD. Show all posts

Wednesday, May 8, 2013

Data Table on a 20x4 LCD using python

Just a quick demo showing off some code I made to handle displaying multiple data points on a 20x4 LCD without having the update the entire screen with each refresh. This keeps the screen clean and a bit easier to read... and a ton easier to code against.



Below is the code for the demo you see in the video. The __main function is running thru some example uses, but you should be able to call this code with no changes. Also this is currently on my fork of the Adafruit python code library as it uses the underlying code from them, See my previous blog post on that. http://crumpspot.blogspot.com/2013/05/using-20x4-lcd-displays-with-mcp23017.html
#!/usr/bin/python
#----------------------------------------------------------------
# Author: Chris Crumpacker                               
# Date: May 2013
#
# Testing a data table on an 20x4 LCD, 
# using a RaspberyPi and an MCP23017 I2C port expander
# 
# Using Adafruit_CharLCD code with the I2C and MCP230xx code as well
#----------------------------------------------------------------

from time import sleep
from Adafruit_CharLCDPlate4line import Adafruit_CharLCDPlate
from LCD_EoL_HandlingAnd4LineSupport import LCD_EoL_Handling

class LCD_DataTable(Adafruit_CharLCDPlate):
    # Limited to 4 characters, 
    # position is left or right, 
    # line can be 0 thru 3
    def updateHalfLabel(self, text, position, line):
        self.clearHalfDataSet(position,line)
        if position == "left":
            lcd.setCursor(0,line)
            eol.message(text[0:4]+':')        
        elif position == "right":
            lcd.setCursor(10,line)
            eol.message('|'+text[0:4]+':')
            
    # Limited to 4 characters, 
    # position is left or right, 
    # line can be 0 thru 3
    def updateHalfValue(self, text, position, line):
        if position == "left":
            lcd.setCursor(5,line)
            eol.message("    ")
            lcd.setCursor(5,line)
            eol.message(text[0:4])        
        elif position == "right":
            lcd.setCursor(16,line)
            eol.message("    ")
            lcd.setCursor(16,line)
            eol.message(text[0:4])
    
    # Writes up to a 9 character label and value to a full line
    def updateWholeLineLabel(self, label, line):
        self.clearWholeLine(line)
        lcd.setCursor(0,line)
        eol.message(label[0:9] + ': ')
    
    # Writes up to a 9 character label and value to a full line
    def updateWholeLineValue(self, value, line):
        lcd.setCursor(10,line)
        eol.message(value[0:10])
    
    #Clears an entire line
    def clearWholeLine(self, line):
        lcd.setCursor(0,line)
        eol.message(" " * columns)
    
    #Clears an entire line
    def clearWholeLineValue(self, line):
        lcd.setCursor(10,line)
        eol.message(" " * 10)
    
    # Clears just a half data set, label and value   
    def clearHalfDataSet(self, position,line):
        if position == "left":
            lcd.setCursor(0,line)
            eol.message(" " * 10)
        elif position == "right":
            lcd.setCursor(10,line)
            eol.message(" " * 10)
    
    # Clears just the value portion for a half data set
    def clearHalfValue(self, position,line):
        if position == "left":
            lcd.setCursor(5,line)
            eol.message("    ")      
        elif position == "right":
            lcd.setCursor(16,line)
            eol.message("    ")

#----------------------------------------------------------------
# Main program, just trowing bogus data "against the wall"
#----------------------------------------------------------------
if __name__ == '__main__':
    
    #lcd size reference
    columns = 20
    rows = 4
    
    eol = LCD_EoL_Handling()
    lcd = Adafruit_CharLCDPlate()
    dt = LCD_DataTable()
    
    lcd.begin(columns, rows)
    lcd.backlight(lcd.ON)
    lcd.clear()
    
    lcd.message("20x4 Table Testing")
    sleep(2)
    
    #Filling the table with bogus info
    lcd.clear()
    dt.updateHalfLabel("Temp","left",0)
    dt.updateHalfLabel("Mode","right",0)
    dt.updateHalfLabel("Targ","left",1)
    dt.updateHalfLabel("Fan","right",1)
    dt.updateHalfValue("Cool","right",0)
    dt.updateHalfValue("75.5","left",0)
    dt.updateHalfValue("Auto","right",1)
    dt.updateHalfValue("74.0","left",1)
    dt.updateWholeLineLabel("Tempurature",2)
    dt.updateWholeLineValue("Too Hot!!!",2)
    dt.updateWholeLineLabel("Humidity",3)
    dt.updateWholeLineValue("100%!!!",3)
        
    #Start testing updating and clearing parts
    
    # Clearing entire lines
    sleep(2)
    dt.clearWholeLine(0)
    sleep(1)
    dt.clearWholeLine(3)
    sleep(1)
    
    # Repopulating the lines just cleared
    dt.updateHalfLabel("Temp","left",0)
    dt.updateHalfValue("75.3","left",0)
    dt.updateHalfLabel("Mode","right",0)
    dt.updateHalfValue("Cool","right",0)
    dt.updateWholeLineLabel("Humidity",3)
    dt.updateWholeLineValue("100%!!!",3)
    sleep(2)
    
    # Clearing the entire Data set, both Label and Value
    dt.clearHalfDataSet("left",0)
    sleep(1)
    dt.clearHalfDataSet("right",0)
    sleep(1)
    dt.clearHalfDataSet("left",1)
    sleep(1)
    dt.clearHalfDataSet("right",1)
    sleep(2)
    
    # Repopulating the half labels and values just removed
    dt.updateHalfLabel("Temp","left",0)
    dt.updateHalfLabel("Mode","right",0)
    dt.updateHalfLabel("Targ","left",1)
    dt.updateHalfLabel("Fan","right",1)
    
    dt.updateHalfValue("75.5","left",0)
    dt.updateHalfValue("Cool","right",0)
    dt.updateHalfValue("74.0","left",1)
    dt.updateHalfValue("On","right",1)
    sleep(2)
    
    # Clearing the values in the half data sets
    dt.clearHalfValue("left",0)
    sleep(1)
    dt.clearHalfValue("right",0)
    sleep(1)
    dt.clearHalfValue("left",1)
    sleep(1)
    dt.clearHalfValue("right",1)
    sleep(2)
    
    # Repopulating half data set values
    dt.updateHalfValue("74.7","left",0)
    sleep(1)
    dt.updateHalfValue("Auto","right",0)
    sleep(1)
    dt.updateHalfValue("74.0","left",1)
    sleep(1)
    dt.updateHalfValue("On","right",1)
    sleep(2)
    
    # Clearing the value on a full line entry
    dt.clearWholeLineValue(2)
    dt.clearWholeLineValue(3)
    sleep(2)
    
    # Repopulating the values that was just removed
    dt.updateWholeLineValue("Still Hot",2)
    dt.updateWholeLineValue("90%",3)

Monday, May 6, 2013

Using 20x4 LCD displays with the MCP23017 and Raspberry Pi

Adafruit sells a nice I2C connected 16x2 LCD "plate" to go on top of the RPi that also includes a few buttons. See: http://www.adafruit.com/products/1110 They also include the python library to run it and functions that are pretty easy to use. See: https://github.com/adafruit/Adafruit-Raspberry-Pi-Python-Code/tree/master/Adafruit_CharLCDPlate

Now that's all well and good but as part of an upcoming project I am planning I have bigger needs. First LCD size. I want to run the 20x4 size so I can display outputs on the first 3 lines and then use the fourth line for menu navigation. Second I am going to be using a 4x3 keypad, a "D-Pad" style button setup,a buzzer,  and a few status LEDs. All of this is going to require one 16 pin (28dip)digital I/O expansion chip, MCP23017 to drive the display, the d-pad, buzzers and LEDs. Then an 8pin (18dip) MCP23008 chip to handle the 4x3 keypad. On top of that it needs to be almost 70' away. 

So I used their good work in the coding department to jump off from. Obviously since the product limits to the use of the 16x2 displays due to physical space the code needed to be optimized to handle differing display sizes. I went a little beyond that and added some support for handling longer text strings that would usually overflow into the buffer, and in the case of the 4 line displays that means any text in the buffer for line one appears on line 3... :(


Running the example code provided below to demonstrate the EoL handling.

Here is the new "message" function from the Adafruit_CharLCDPlate.py script/class.  I just checked in to their Github repo. One thing to note is that this in no way changes the functionality for the normal plate. Any existing scripts will function just like before. But with the addition of handling 4 line displays seamlessly and then all of the EoL features can be added be putting a 1 or 2 in the message function call after your test to print. My github fork


    def message(self, text, limitMode = 0):
            """ Send string to LCD. Newline wraps to next line"""
            lines = str(text).split('\n')       # Split at newline(s)
            for i, line in enumerate(lines):    # For each substring...
                if i == 1:                      # If newline(s),
                    self.write(0xC0)             # set DDRAM address to 2nd line
                elif i == 2:
                    self.write(0x94)
                elif i >= 3:
                    self.write(0xD4)
                """Now depending on the limit mode set by the function call this will handle """
                lineLength = len(line)
                limit = self.numcols
                if limitMode <= 0: 
                    self.write(line, True)     
                elif lineLength >= limit and limitMode == 1:
                    '''With the limit mode set to 1 the line is truncated 
                    at the number of columns available on the display'''
                    limitedLine = line[0:self.numcols]
                    self.write(limitedLine, True)  
                elif lineLength >= limit and limitMode == 2:
                    '''With the limit mode set to 2 the line is truncated 
                    at the number of columns minus 3 to add in an elipse'''
                    limitedLine = line[0:self.numcols-3]+'...'
                    self.write(limitedLine, True)
                elif lineLength >= limit and limitMode >= 3:
                    '''Future todo, add in proper, "line after line" cariage return'''
                else:
                    self.write(line, True)

I also had to add the "self.numlines = lines" to the begin function so I could use the column count.

    def begin(self, cols, lines):
        self.currline = 0
        self.numlines = lines
        self.numcols = cols
        self.clear()

I also spent some time making an example script to leverage this new message function as well as some of the other standard built in functions. Here is my example script on github

#!/usr/bin/python

#----------------------------------------------------------------
# Author: Chris Crumpacker                               
# Date: May 2013
#
# A demo of some of the built in helper functions of 
# the Adafruit_CharLCDPlate.py and Using the EoL_HandlingAnd4LineSupport.py
# 
# Using Adafruit_CharLCD code with the I2C and MCP230xx code as well
#----------------------------------------------------------------

numcolumns = 20
numrows = 4

from time import sleep
from Adafruit_CharLCDPlate import Adafruit_CharLCDPlate

lcd = Adafruit_CharLCDPlate()

lcd.begin(numcolumns, numrows)

lcd.backlight(lcd.ON)
lcd.message("LCD 20x4\nDemonstration")
sleep(2)

while True:
    #Text on each line alone.
    lcd.clear()
    lcd.setCursor(0,0)
    lcd.message("Line 1")
    sleep(1)
    
    lcd.clear()
    lcd.setCursor(0,1)
    lcd.message("Line 2")
    sleep(1)
    
    lcd.clear()
    lcd.setCursor(0,2)
    lcd.message("Line 3")
    sleep(1)
    
    lcd.clear()
    lcd.setCursor(0,3)
    lcd.message("Line 4")
    sleep(1)
    
    # Using the "\n" new line marker
    lcd.clear()
    lcd.setCursor(0,0)
    lcd.message("Line 1")
    sleep(1)
    
    lcd.clear()
    lcd.setCursor(0,0)
    lcd.message("Line 1\nLine 2")
    sleep(1)
    
    lcd.clear()
    lcd.setCursor(0,0)
    lcd.message("Line 1\nLine 2\nLine 3")
    sleep(1)
    
    lcd.clear()
    lcd.setCursor(0,0)
    lcd.message("Line 1\nLine 2\nLine 3\nLine 4")
    sleep(1)
        
    # Auto line limiting by length as to not overflow the display
    # This is line by line and does not to any caraige returns
    lcd.clear()
    lcd.setCursor(0,0)
    lcd.message("This String is 33 Characters long",1)
    sleep(2)    
    
    lcd.clear()
    lcd.setCursor(0,0)
    lcd.message("This String has elpise",2)
    sleep(2)    
    
    #Scroll text to the right
    messageToPrint = "Scrolling Right"
    i=0
    while i<20:
        lcd.clear()
        lcd.setCursor(0,0)
        suffix = " " * i
        lcd.message(suffix + messageToPrint,1)
        sleep(.25)
        i += 1
    
    # Scroll test in from the Left
    messageToPrint = "Scrolling Left"
    i=20
    while i>=0:
        lcd.clear()
        lcd.setCursor(0,0)
        suffix = " " * i
        lcd.message(suffix + messageToPrint,1)
        sleep(.25)
        i -= 1
    sleep(2)  
    
    # Printing text backwards, NOT right justified
    lcd.clear()
    lcd.setCursor(0,0)
    lcd.message("Right to left:")
    lcd.setCursor(10,1)
    lcd.rightToLeft()
    lcd.message("Testing")
    sleep(2)
    
    # Printing normally from the middle of the line
    lcd.clear()
    lcd.setCursor(0,0)
    lcd.message("Left to Right:")
    lcd.setCursor(10,1)
    lcd.message("Testing")
    sleep(2)
    
    # Enabling the cursor and having it blink
    lcd.clear()
    lcd.setCursor(0,0)
    lcd.cursor()
    lcd.blink()
    lcd.message("Cursor is blinking")
    lcd.setCursor(0,1)
    sleep(3)
    lcd.noCursor()
    lcd.noBlink()
    
    # Turning the backlight off and showing a simple count down
    lcd.clear()
    lcd.setCursor(0,0)
    lcd.message("Backlight off in")
    lcd.setCursor(0,3)
    lcd.message("Back on in 3sec")
    lcd.setCursor(17,0)             #Reseting the cursor here keeps us from having to clear the screen, this over writes the previous character
    lcd.message("3")
    sleep(1)
    
    lcd.setCursor(17,0)
    lcd.message("2")
    sleep(1)
    
    lcd.setCursor(17,0)
    lcd.message("1")
    sleep(1)
    
    lcd.backlight(lcd.OFF)
    lcd.clear()
    lcd.setCursor(0,0)
    sleep(3)
    lcd.backlight(lcd.ON)
    lcd.message("Backlight on")