LCD Control with an MCU
Controlling a liquid crystal display (LCD) to indicate a few ASCII characters should not be a big challenge as a project. That’s exactly what I thought when I decided to include a 2 line by 16 character display in my current project. My initial thought was. “How difficult could it be with all the resources on the internet and my embedded development expertise primarily in telecoms?” Let me tell you it is not as straightforward as I thought it would be and therefore I decided to write about some of my key findings that I think may be helpful to other developers who follow EmbeddedRelated.com.
I decided to use the JHD162A as the display mainly because it is cheap, widely available and its size is suitable for my project. Here is the link to one of the very few useful datasheets on this LCD. There are a number of other alternative datasheets to this part and I have reviewed most of them while working on this project. However, the one I provided above makes most sense albeit the poorly presented and partially missing information in it.
After reviewing many LCDs and related datasheets, I came to realise that the fundamental problem with most LCDs is the technical documentation is rather poor. While developing your code, you find yourself guessing what the technical descriptions actually mean as well as doing quite a bit of trial and error. This obviously can be time consuming and frustrating. I hope that this article will help you in avoiding the hassle I had to go through to get my LCD working as required.
Interfacing the JHD162A to an MCU is rather straightforward. Besides the power pins, you need to connect 8 data pins and 3 control pins to your microcontroller. Here is the link to the connection diagram I used in my project. Note that R1 resistor between the ground and Vee is a variable resistor used for adjusting the contrast level of the display. Once you are happy with the contrast level of your display, you can use a fixed resistor instead (which I have done and 1K seems to work well).
If you would like to see the visual effect of changing the contrast level, you can have a look at my short video here. In the video, 10K variable resistor has been connected between ground and the Vee pin. Note that as the resistance increases, the contrast decreases. Therefore, when you first connect your LCD to the MCU with no resistor across ground and Vee, the display will look blank as the contrast will be set to minimum due to this open circuit. Before you are mislead to think that your LCD is faulty, what I suggest is you short the Vee to ground to start with in order to see some signs of life on your display upon establishing the first connection!
Once you have connected your display to your MCU and saw some signs of life, you are ready to tackle the software side of things.
Initialising the display
Following is the command sequence I have used to initialise the display which has been working very reliably. You will find the details of the instructions in section 13 of the datasheet I have provided above. The instruction content is easy to follow, however, interpreting the initialisation sequence and when to invoke particular commands is not straightforward and that’s where I hope the information I have provided below will come in handy.
Step 1 – Put the display into command mode (see below for more details on this).
Step 2 – Clear the display and wait for 1 millisecond.
Step 3 – Turn the display ON and wait for 1 millisecond.
Step 4 – Set cursor to be visible and wait for 1 millisecond.
Step 5 – Turn cursor blink OFF and wait for 1 millisecond.
Step 6 – Send cursor home on line 1 and wait for 3 milliseconds.
Step 7 – Invoke Function Set command with 2 lines, 8 bit bus and 5x7 font size and wait for 3 milliseconds.
Note that the delays I have specified above are a bit longer than what has been specified in the datasheet. However, these delays were very easy to achieve using the timers on Atmel AVR in my existing project and I simply decided not to change my reliable timer set up.
One very strange behaviour I discovered while trying to get my initialisation working properly was that that “Function Set” command MUST be invoked after cursor and display settings. Otherwise, two line setting does not get activated and you are restricted to using single line. I do not know why this happens and it has not been listed anywhere in the datasheet but I know this problem exists so be aware!
Another important point to remember here is that when the RS pin is at logic ‘0’, the LCD is said to be in “command mode” and when RS = logic 1, the display is in “data mode”. The rule of thumb is when you are not specifying a character to be displayed on the LCD, you need to be in command mode. Not ensuring that the LCD is in the correct mode will cause you to get unexpected responses (or no response) from the LCD and can be very frustrating. So if things don’t look right, one of the first things to check is to make sure the sequence of instructions are provided in the required mode.
Just to give you an example, following is the sequence you will need in order to clear the display.
1 – Pull E pin high.
2 - Pull RS pin low. (command mode set here)
3 – Pull RW pin low to enable write.
4 – Write 0x01 to data pins (i.e. DB7-DB0)
5 – Pull E pin low. (this creates the falling edge on the E pin like a clock which causes the change on the display).
6 – Pause for 3 milliseconds.
7 – Pull E pin high again.
After clearing the display, if you want to print the ‘A’ character to the current cursor position, you simply need to add the following to the above sequence.
8 - Pull RS pin high. (entering data mode here)
9 - Write 0x41 to data pins ( 0x41 is the ASCII code for ‘A’)
10 – Pull E pin low. (don’t forget to clock the LCD with falling edge on E to visualise each character on display!).
Useful tips on JHD162A display & a demo
Normally, the characters we display on the LCD are read from ROM and are transferred to the CGRAM (Character Generator RAM) when the display is powered up. The characters stored on the CGRAM are transferred to Data Display RAM (DDRAM) when they need to be visualised.
The display of the JHD162A can show 16 characters at a time on each of the two lines available. However, each line can store up to 40 characters in DDRAM. Those characters can be shown on the display by shifting the display to right or left though only 16 of them are visible on the screen at a time. If you need to display more than 40 characters per line, you can simply define a character buffer in your application and constantly update the screen with the characters you need to display. That way the limit to the number of characters you can display becomes the available buffer size in your software application.
One important point to keep in mind in the case of JHD162A is that the two lines cannot be shifted independently. Therefore, if the user wants to freeze one line while shifting the character on the other, the frozen line needs to be printed again after shifting is completed. This operation is so fast that the user cannot notice the re-printing while characters on the shifting line are moving smoothly across the screen. Following video demonstrates how this feature has been implemented in my project.
In the above video, the MCU scrolls each character on line 1 every 400 milliseconds, which is the reason why the line does not shift as smoothly. This delay has been intentionally inserted to give the user enough time to read a message. The important thing to note in this demo is that every time line scrolls by one character to the left, all the characters on the second line are re-printed. This gives the impression that the characters on the second line are standing still! This little trick allows you to dedicate the second line to a static menu, like I have done in my project. You can clearly see the three small yellow buttons that I have allocated to the “DELete”, “DoWN” and “UP” commands in the video. This concept can easily be extended to implement more menu pages that can be controlled by the yellow “soft keys”. This will allow you to use a simple LCD as a sophisticated user interface.
I must add a note of caution here. If you are planning to use the display shift feature, make sure you invoke the “return home” command ONCE before you shift the display. Otherwise, the scroll feature you have seen in the above video will not work. This is something I had to find out for myself as the datasheet does not provide any useful guidance on this.
Note that CGRAM can also be used to implement user defined characters to be displayed on the LCD. In the datasheet, the address range 0x00 to 0x20 in CGRAM do not have any pre-defined characters. Therefore, these addresses can be programmed by any pixel pattern (of size 5x7) to create new custom characters. One thing you need to remember about this feature is since the custom pixel patterns are not stored on ROM, you need to hardcode these patterns into your application to initialise the CGRAM at every power cycle as needed. Once the CGRAM has stored the custom characters, those can be routed to the DDRAM to be displayed as an ordinary ASCII character.
Unfortunately, since I have not played with this feature in my project yet, I am unable to provide illustrations of custom characters in this post. I am hoping to write a new post on this feature after I have implemented it on the bench. In the meantime, I hope you will find this post useful in your applications.
You must design your firmware to work correctly with the inherent slowness of LCDs. You need to de-couple your firmware application's writing to the device from this slowness. Your firmware should only buffer writes to a structure which has a logical image of the LCD. A separate LCD update function which runs at its own refresh frequency is used to render the actual LCD screen. The refresh function should be designed to only render changed data (through the use of flags).
This technique gives you lag free firmware.
Thanks for the design suggestion. ? think using the port expander is a great method if you require multiple ?2C devices to access the same LCD as and when needed.
? cannot see how using this device will take the load off the main firmware though. ?n a periodic scrolling scenario, the characters that need to be displayed on the LCD still needs to be sent to the LCD by the firmware when needed. The port expander does not have an internal memory to store data. For static text, the LCD controller displays the data without requiring any regular updates from the firmware.
The latency you see on the scrolling video is not due to the latencies in the system. ?t is part of the firmware design to give the user enough time to read a moving text. Without that delay, the display updates so fast that you cannot follow the characters at all.
?n my design, the scroll updates are done on a timer interrupt and in the lowest priority task. So the firmware has plenty of time to deal with other more high priority tasks before updating the LCD and as far as ? have observed, the system works beautifully.
Also, from a hardware perspective, adding a port expander will increase the system cost and the battery power consumption whilst complicating the pcb design. ? cannot see sufficient benefit to justify these disadvantages when ? consider the system as a whole.
Another trick I use with the LCD in 4 bit mode is using the other 4 inputs to the LCD to set some "plug n' play" config bits to help the system identify the LCD size. These bits can be read by switching to 8 bit mode, writing a byte to LCD memory, switching back to 4 bit mode, then reading it. The bits in 8 bit mode that determine line doubling and font size should be wired to make the 8 bit "switch to 4 bit mode" command correct or else the controller will glitch out and corrupt the display when you perform the acrobatics to read D0-D3. D2 and D3 can be connected to two additional switches if desired, but they are complicated to read. I chose to use them for additional PNP bits.
However, JHD162A also supports 4-bit data bus whereby you can invoke each command in 2 clock cycles. The data bus width can be set using the "DL" bit of the command "feature set" in the datasheet.
Google 'embedded controller forth for the 8051 family'
Thank you for the tip. I guess you are the author of the following book:
To post reply to a comment, click on the 'reply' button attached to each comment. To post a new comment (not a reply to a comment) check out the 'Write a Comment' tab at the top of the comments.
Please login (on the right) if you already have an account on this platform.
Otherwise, please use this form to register (free) an join one of the largest online community for Electrical/Embedded/DSP/FPGA/ML engineers: