With this Project I am going to talk about controlling an LCD display. Both the 16x2 and 20x4, as they share a common interface. They are the HD44780 chipset, and the 1602 and the 2004 models respectively. They also come in different coloured back lit options.
These are very cheap and east to come by. For about £3 and £6 on eBay you can get these quite easily. They are great if you want to have you Pi start outputting simple things to a screen of some sort and not just the console (If you are running it headless like I am)
This is what you need to get going (Ingredients):
- 1 x Raspberry Pi
- 1 x 10K Pot
- 1 x LCD Screen (In this example I will mainly be using the 16x2)
- A whole lot of Jump Wires
Pi Layout with LCD Screen
The LCD screen pin layout is from left to right (1 to 16). This is the table to explain each pin:
Pin | Label | Value | Explanation |
---|---|---|---|
1 | Vss | 0V | Ground |
2 | Vdd | 5V | Supply voltage for Logic Board - NOT 3.3V |
3 | Vo | 0V | Contrast Adjustment - Can be but on a pot but best to connect direct to Ground for full visibility |
4 | RS | H/L | Register Select, H=Data, Low=Command |
5 | R/W | H/L | Read/Write Mode. H=Read Mode, Low=Write Mode. Connected to ground so that it is always in Write Mode |
6 | E | H,H->Low | Chip Enable Signal / Clock Enable |
7 | DB0 | H/L | Data Bit 0 (Not Used in 4-bit operation) |
8 | DB1 | H/L | Data Bit 1 (Not Used in 4-bit operation) |
9 | DB2 | H/L | Data Bit 2 (Not Used in 4-bit operation) |
10 | DB3 | H/L | Data Bit 3 (Not Used in 4-bit operation) |
11 | DB4 | H/L | Data Bit 4 |
12 | DB5 | H/L | Data Bit 5 |
13 | DB6 | H/L | Data Bit 6 |
14 | DB7 | H/L | Data Bit 7 |
15 | A | +V | Back Light Anode |
16 | K | -V | Back Light Cathode |
As you can see we connected these to the standard GPIO ports on the Pi (Though all of them can be set as GPIO ports - some have more specific uses that others and if you are connecting other things to these type of interfaces while using the LCD screen, then these ports are probably the best to use). All GPIO ports on the Pi can be set in to two states. They can be represented as these:
- True / False
- 1 / 0
- High / Low (Which is what we showed in the table above)
One other thing to make note of, is that there are timings involved in sending commands to the LCD. There needs to be a "pause" between certain commands (Which we will come to next) in order to allow the device to switch from one mode to another. This pause is a fraction of a second, but is needed for the LCD to operate correctly.
As you can see we also do not need to use all the DB pins to control the LCD Screen. We will utilise this ability as we don't want to use every single GPIO Port on the Pi. So using 4 GPIO ports will allow us to control the LCD. But in doing so when we send information to the LCD Screen we need to send the data in 2 separate 4 bit chunks, known as nibbles. A high nibble and a low nibble. This will be explained a little further on.
As you can see we also do not need to use all the DB pins to control the LCD Screen. We will utilise this ability as we don't want to use every single GPIO Port on the Pi. So using 4 GPIO ports will allow us to control the LCD. But in doing so when we send information to the LCD Screen we need to send the data in 2 separate 4 bit chunks, known as nibbles. A high nibble and a low nibble. This will be explained a little further on.
The Python bits
Now comes the fun part of controlling the LCD screen.
To make use of the GPIO ports in Python we need the RPi.GPIO module.
While importing the module we instantiate the RPi.GPIO Class as GPIO. Makes for easier coding.import RPi.GPIO as GPIO
As there are timings involved we also need:
import time
Lets now define some of the GPIO to LCD Mappings:Next lets define some device constants:LCD_RS = 22 LCD_E = 4 LCD_D4 = 25 LCD_D5 = 24 LCD_D6 = 23 LCD_D7 = 18
As you can see I have shown 4 lines, but the 16x2 and the 20x4 share the same chipset and therefore some of the settings cross over. LCD_WIDTH can be set to 20 if you are going to be doing work on the 20x4 LCD screens.LCD_WIDTH = 16 # Maximum characters per line LCD_CHR = True # Register Select to Accept Characters LCD_CMD = False # Register Select to Accept Commands LCD_LINE_1 = 0x80 # LCD RAM address for the 1st line LCD_LINE_2 = 0xC0 # LCD RAM address for the 2nd line LCD_LINE_3 = 0x94 # LCD RAM address for the 3rd line LCD_LINE_4 = 0xD4 # LCD RAM address for the 4th line
These are the Timing Constants I spoke about earlier:
Next are the constants for each position on the display (This comes in useful when you want to place characters directly in certain positions)E_PULSE = 0.00005 E_DELAY = 0.00005
In order to use the GPIO pins we need to initialize them :LINE_1_16x2 = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F] LINE_2_16x2 = [0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F] LINE_1_20x4 = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13] LINE_2_20x4 = [0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53] LINE_3_20x4 = [0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27] LINE_4_20x4 = [0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5b, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67]
Let me explain a couple of things that the above procedure does.def set_gpio(): GPIO.setwarnings(False) GPIO.setmode(GPIO.BCM) # Use BCM GPIO numbers GPIO.setup(LCD_E, GPIO.OUT) # E GPIO.setup(LCD_RS, GPIO.OUT) # RS GPIO.setup(LCD_D4, GPIO.OUT) # DB4 GPIO.setup(LCD_D5, GPIO.OUT) # DB5 GPIO.setup(LCD_D6, GPIO.OUT) # DB6 GPIO.setup(LCD_D7, GPIO.OUT) # DB7
- GPIO.setwarnings(False) makes sure that if there were any previous GPIO settings from a previous running of the code, all warnings will be ignored.
- GPIO.setmode(GPIO.BCM) tells the code how we will talk to the GPIO Pin Layouts. GPIO.BCM means we are going to use the specific labeling of the Pi (Not the exact locations of the pins). Not all the GPIO pin labels are in the actual board pin layout. To have the it the other way and use the specific pin number you need to use GPIO.BOARD
- GPIO.setup(....., GPIO.OUT) sets the GPIO pins into an output mode. Therefore no data can be received by them. Of course you could have IN, but for this project all we are doing is sending data to the LCD display.
Next we can now initialize the LCD display, seeing that the GPIO ports have been set correctly, we can now send data to the LCD Display :
Lets look at the following table to see why we have used the Hex Codes that we see above. Afterwards I will try and explain the ins and out of the code:def lcd_init(): send_data(0x30, LCD_CMD) # Initialize send_data(0x28, LCD_CMD) # Set 4-bit mode, 2 line, 5x7 matrix send_data(0x0F, LCD_CMD) # Display On, set cursor underling and blinking send_data(0x06, LCD_CMD) # Character entry increment, Display shift off send_data(0x01, LCD_CMD) # Clear Screen def send_data(instr, rs_mode): # Send byte to data pins # instr = data set to send # rs_mode = Register Select : # True for character # False for command GPIO.output(LCD_RS, rs_mode) set_data_pins() # Reset pins to False send_data_to_pins('high', instr) commit_data() set_data_pins() # Reset pins to False send_data_to_pins('low', instr) commit_data() def set_data_pins(): GPIO.output(LCD_D4, False) GPIO.output(LCD_D5, False) GPIO.output(LCD_D6, False) GPIO.output(LCD_D7, False) def send_data_to_pins(state, nibble): if state == 'high': if nibble & 0x10 == 0x10: GPIO.output(LCD_D4, True) if nibble & 0x20 == 0x20: GPIO.output(LCD_D5, True) if nibble & 0x40 == 0x40: GPIO.output(LCD_D6, True) if nibble & 0x80 == 0x80: GPIO.output(LCD_D7, True) elif state == 'low': if nibble & 0x01 == 0x01: GPIO.output(LCD_D4, True) if nibble & 0x02 == 0x02: GPIO.output(LCD_D5, True) if nibble & 0x04 == 0x04: GPIO.output(LCD_D6, True) if nibble & 0x08 == 0x08: GPIO.output(LCD_D7, True) def commit_data(): time.sleep(E_DELAY) GPIO.output(LCD_E, True) time.sleep(E_PULSE) GPIO.output(LCD_E, False) time.sleep(E_DELAY)
Command | Binary | Hex | |||||||
---|---|---|---|---|---|---|---|---|---|
D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | ||
Clear Display | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 01 |
Display & Cursor Home | 0 | 0 | 0 | 0 | 0 | 0 | 1 | x | 02 or 03 |
Character Entry Mode | 0 | 0 | 0 | 0 | 0 | 1 | I/D | S | 04 to 07 |
Display On/Off & Cursor | 0 | 0 | 0 | 0 | 1 | D | U | B | 08 to 0F |
Display/Cursor Shift | 0 | 0 | 0 | 1 | D/C | R/L | x | x | 10 to 1F |
Function Set | 0 | 0 | 1 | 8/4 | 2/1 | 10/7 | x | x | 20 to 3F |
Set CGRAM Address | 0 | 1 | A | A | A | A | A | A | 40 to 7F |
Set Display Address | 1 | A | A | A | A | A | A | A | 80 to FF |
I/D: 1 =Increment*, 0=Decrement S: 1=Display shift on, 0=Display shift off* D: 1=Display On, 0=Display off* U: 1=Cursor Underline on, 0=Underline off* B: 1=Cursor blink on, 0=Cursor blink off D/C: 1=Display shift, 0=Cursor move |
R/L: 1=Right shift, 0=Left shift 8/4: 1=8 bit interface*, 0=4 bit interface 2/1: 1=2 line mode, 0=1 line mode* 10/7: 1=5x10 dot format, 0=5x7 dot format* x = Don't care * = Initialisation settings |
In my Python code I have created 5 separate procedures to cater for the different processes needed to talk to the LCD screen:
- lcd_init() - This is used to initialise the LCD screen. Get it into a state where it is ready to accept inputs in the format we are going to send to it.
- send_data(instr, rs_mode) - This basically sends data to the pins. That data can be either commands for the device itself or characters to be displayed.
- By setting rs_mode True, for sending a character, and False, for sending a command, on the RS GPIO pin you can can set the LCD to that specific mode.
- instr is the hex code for the specific commands or characters to be sent
- As you can see in there we reset the pins by calling set_data_pins(). This must be done before each transfer of data
- Because we are running in 4 bit mode we call send_data_to_pins twice for each 4 bit nibble. high for the first 4 bits, and low for the next 4 bits.
- commit_data is called to then commit the data set on the pins
- set_data_pins() - As just previously mentioned, sets all the data pins to a False, or Low state (not to be confussed with the low nibble that gets sent). This makes sure the the GPIO pins are cleared and hold no residual state from a previous execution.
- send_data_to_pins(state, nibble) - This procedure takes the state of the 8 bit (Hex Code) to determine which part of this 8 bit block it should work with (The nibble). The conditional statements use bitwise operators to determine which bits are valid for that specific nibble and therefore set the corresponding GPIO pin to its correct state.
- commit_data() - This is the last part of the code that then tells the LCD display that there the data pins have been set and to take their states as part of the command or character that is being sent. Delays are needed to give the LCD enough time to make sure these states are read correctly. the E pin is set to High, this tells the LCD to read the Pin states, then it is set back to Low so it can wait for the next piece of the command.
Making Text appear
Now all this is out of the way we can now write things to the screen:
def set_line(line_number): if line_number == 1: send_data(LCD_LINE_1, LCD_CMD) elif line_number == 2: send_data(LCD_LINE_2, LCD_CMD) elif line_number == 3: send_data(LCD_LINE_3, LCD_CMD) elif line_number == 4: send_data(LCD_LINE_4, LCD_CMD) def lcd_string(message, line_number): set_line(line_number) message = message.ljust(LCD_WIDTH, " ") for i in range(LCD_WIDTH): send_data(ord(message[i]), LCD_CHR) ################################################################# set_gpio() lcd_init() scroll_text() lcd_string("Hello", 1) lcd_string("World", 2)
- set_line(line_number) procedure takes the line number you wish to write on and send the command to the LCD Screen telling it that what ever we send next must be written to that line using the send_data procedure.
- lcd_string(message, line_number) procedure takes the string to be displayed loops through the string, one character at a time. This is necessary as each character needs to be sent to the display individually. the ord function turns the character into its equivalent hex code, which is what is needed for the LCD display. The table below shows all the characters with their corresponding 4 bit (high / low) nibbles.
Conclusion
I hope this post has been helpful to you. There are more advanced things you can do, like creating your own characters and placing them in the CGRAM section, but that is for another post.
Here is a great reference that I gleaned a lot of information out of :