10 September 2011

Tutorial 14a: The Alphanumeric LCD

We're almost at a point where we have built a complete scientific instrument. The one thing the capacitance meter lacks is a way to provide the measurement outside of the debugging environment-- not very convenient for working in the field. There are a number of ways we can transfer the data somewhere usable. One way that is useful for single measurements is by displaying the result on an LCD display, much as consumer meters do. We're now going to look at using a standard alphanumeric LCD module with the LaunchPad. This project will also introduce the concept of building a custom library; by the end of this tutorial, you will have a library that you can import into future projects to add an LCD display without having to re-code the entire thing.

The LCD Module (LCM)

One disadvantage to this tutorial is that it require having an LCD display, which you might not have. If not, and are willing to purchase one, I would recommend doing so. These displays can be very useful, and are not very expensive. They come in a variety of sizes and styles-- for this tutorial we will use a standard 16x2 character alphanumeric display modules, such as those found at SparkFun.com. SparkFun carries a variety of these modules; feel free to pick one to your liking, but be certain of a few things: it needs to be configured to run on 3.3 V and should not have been modified to accept serial input. If you'd like a larger module (such as a 20x4 display), it's up to you, as they all work the same way. Get whatever color you'd like.

The SparkFun LCM's utilize an ST7066 chip as the interface to the display, which is based on the ubiquitous HD44780 interface. (If the names make this sound technical, don't worry too much about them-- the important thing here is that we have an interface that translates data from the MSP430 into the commands and characters needed to control the LCM.) This interface uses an 8-bit parallel transmission for sending data to/from the display. As you can imagine, with 8 bits for data, plus another 3 bits for control, you can very quickly run out of GIO pins on your MSP430. In fact, even if we use the G2211 device without a crystal so that P2.5 and P2.6 are available, we only have 10 GIO pins available in total, so we're short 3 pins to control the LCM (needing 11 pins) and the comparator interface (needing 2 pins) for our meter. Fortunately, the HD44780 interface (and thus the ST7066) provides a means of sending data in two 4-bit chunks, and as long as we have no need for reading data from the LCM, we can get by with 6 pins for LCM control, allowing us to just fit the comparator and the LCM into the 8 GIO pins for P1 on our G2211. We're stretching the limits of this chip at this point, which is excellent motivation for expanding to other MSP430 devices in the future!

Connecting to the LCM

The standard LCM module has 16 pin connections (14 without a backlight). The first few connections are for power (possibly in two places, if the LCD is also backlit) and contrast adjustment. Pin 1 (labeled as Vss) should be connected to your LaunchPad ground. Pin 2 (Vdd) is connected to the LaunchPad Vcc. If you are using the backlight, pin 15 (LED+) is also connected to Vcc and pin 16 (LED-) to ground. (This is most easily done on a breadboard. If you find yourself losing track of this description, a good guide to follow is one written by LadyAda.) Pin 3 (Vo) controls the contrast of the screen. If you have a 10k potentiometer available, tie it to the wiper and tie the two ends to Vcc and ground for an adjustable contrast. If not, you can just ground this pin; it probably won't look as good as it could, but it will work.

That leaves 11 pins for the control and data lines. The three control lines are pins 4 (RS), 5 (R/W), and 6 (E). The read/write (R/W) pin is not necessary here, and by tying it to ground we keep the LCM always in write mode. We won't be able to read anything from the LCM (such as the address of the cursor, the busy state flag, etc.), but it saves us a pin on the MSP430. The Register Select (RS) and Enable (E) pins are what we'll use to control the LCM. Finally, pins 7-14 are the data lines D0-D7 respectively. You can consider these pins much like the P1 pins on the MSP430-- D0 is the first bit, D1 the second, and so on. If we used an entire GIO port on an MSP430 to control the data lines, we could connect Px.n to Dn, and by writing a value to Px, we can write the same value to D (conveniently saving us from any strange coding to accommodate changing the order). Since the G2211 doesn't provide enough ports to do this, we'll use the 4-bit mode instead. This mode uses only D4-D7. Leave D0-D3 unconnected for now. (Doing so prevents accidentally writing commands we don't intend.)

For the capacitance meter, we're going to change some of the pin arrangements to accommodate the LCM. We'll use P1.1 as TA0.0 rather than CA1, and use P1.2/CA2 as the input to V+ on the comparator. P1.0 will control RS, P1.3 will control E, and P1.4-7 will control D4-7.

Note: P1.3 is also connected to the button-- this shouldn't affect the code, but since there is a pull-up resistor on P1.3, there will be excess current whenever we drive E low. Unfortunately, the LaunchPad is not designed with a jumper like on P1.0 and P1.6 for the LEDs, so we'll just have to live with this. While we're on the topic, be sure to remove the jumpers for the two LEDs and on the TXD/RXD pins.

Sending Commands to the LCM

Once we're wired up, sending commands or characters to the LCM is a straightforward task. In fact, it can even be done by hand, without a microcontroller at all! (If you're interested in this, or would like to know more about what's going on, see the Reader Exercise below.) The basic idea is that we write an instruction to the data pins and pulse E. The instruction is sent on the falling edge of E, which is why it's pulsed. The instruction issues a command if RS is low, and sends a character if RS is high.

As an example, let's look at the commands we need to set the LCM in 4-bit mode. The 8-bit binary instruction 0b001nnnxx is the "Function Set" instruction. (Here, the n's represent values we choose for the configuration, and the x's imply an unused bit-- these can have either 0's or 1's and not affect the instruction.) Bit 4 in this instruction sets the interface mode: a 1 sets the LCM to accept 8-bit instructions, and a 0 sets it to accept 4-bit instructions. So by sending the instruction 0b00100000 (or 0x20), we configure the LCM to accept commands and characters in two 4-bit chunks instead of one 8-bit chunk. This command must be issued first in order to do anything with our 6-wire setup. We first set the data lines with P1OUT |= 0x20; (which also sets RS low (command mode) and E low in this wiring scheme) and then send the command by pulsing E.

The LCM does not respond instantly to the command, and there are some strict timing requirements in order for it to work correctly. Specifically, RS must be set low a certain amount of time before beginning the pulse on E. The data lines must be set a certain amount of time before the falling edge on the pulse, and must be held at that value a certain amount of time after the pulse. Then a certain amount of time must elapse before we can pulse E again. Fortunately for us, the only timing value of major concern is the time between command pulses. The other times are on the order of a few hundred nanoseconds, and at the processing speeds of the MSP430 there is enough latency to accommodate them. The amount of time needed to complete a command before accepting another can be on the order of 150 milliseconds, however, and so delays must be incorporated to handle them.

So, to recap, here's the set of instructions needed to set the LCM in 4-bit instruction mode:

__delay_cycles(10000);   // wait for the LCM to settle on power-up
P1OUT |= 0x20;   // set to 4-bit instructions
P1OUT |= BIT3;   // E high
__delay_cycles(200);
P1OUT &= ~BIT3;  // E low
__delay_cycles(200);
P1OUT &= 0x0F;   // clear the upper 4 bits

Though E can be set high before setting the data lines, it's convenient to switch the order to prevent any timing mismatches. Note that if you use different pin connections, or especially if you use multiple GIO ports, this code won't work exactly as is; it's convenient to use P1.4-7 for D4-7 to be able to assign directly to P1OUT, but this is not general. If we were to swap the order, for example, to P1.4-7 as D7-4, then we would be writing 0b0100 instead of 0b0010 on the data line with this code. So be careful; either use the pin connections I've suggested here, or assign the data line bits individually as needed. The final line clears the data line bits to make it easier to set the next command properly.

Other Initializations: Sending Commands in 4-bit Mode

Now we have our LCM ready to accept 4-bit commands. This mode works by sending the upper 4-bits (or nibble) with a pulse on E, and then sending the lower nibble with a second pulse. With our wiring scheme, we can do this easily by the following code:

P1OUT |= ( & 0xF0);  // send upper nibble
pulse();
P1OUT &= 0x0F;   // clear
P1OUT |= (( & 0x0F;) << 4);  // send lower nibble
pulse();
P1OUT &= 0x0F;   // clear

I'm assuming here that I've lumped the commands to pulse E with the incorporated delays into a function void pulse(void).   refers, of course, to whatever 8-bit command or character we're sending to the LCM. If we encapsulate this set of commands into a function void SendByte(char),  then we can issue the next initialization commands as follows:

SendByte(0x28);   // Function Set 4-bit, 2-line mode (for 2-line displays, of course)
SendByte(0x0E);   // Display on, underline cursor on, non-blinking
SendByte(0x06);   // Character entry mode: increment address, no display shift

After sending these commands, our LCM is ready to display whatever characters/text we want to send it. Note that to send characters, the commands are similar to above, but P1OUT must also set BIT0 (RS) to tell the LCM to receive character instructions rather than commands. In lcddemoG2211.c, I demonstrate this with a more generalized version of SendByte that lets you send commands and characters. It also demonstrates other commands, such as clearing the display and moving the cursor. If you have an LCM, try out the code yourself. I wouldn't use a DCO faster than about 2 MHz with the selected delays, so if you play around with the code keep that in mind. In the next tutorial, we'll look at how to encapsulate all of this into a custom library that we can keep on hand and how to import it into our capacitance meter project.

Reader Exercise: Not really an exercise-- more suggested reading. If you'd like to know more about how the modules work and the specific commands and characters that can be sent, I suggest reading the following two articles from Everyday Practical Electronics:
        How To Use Intelligent LCDs: Part One
        How To Use Intelligent LCDs: Part Two
These articles are very easy to understand, and do a great job of explaining how to use the LCM.

8 comments:

Jon said...

I've followed your tutorial and when I run the program I get 6 of the Japanese characters 11001111. I've removed the four jumpers and wired the LCD per your specs. Any idea why I might have this issue?

Unknown said...

It's hard to say.. is there any chance your LCD has a different character set than the one I use? You might try running an experiment where it cycles through the characters you think it should be displaying (as per the How To Use Intelligent LCDs Part One article), and compare to what actually shows up on your display.

Jon said...

David,

I went out and bought a 10K potentiometer today and rewired everything from scratch. It's now working. I'm not quite sure what I got wrong but I've got it figured out now. Thanks for the help.

Take care,

Jon

Jon said...

David,

Would you mind explaining this line from the MoveCursor function.

SendByte(0x80|address, FALSE);

I can't understand why you are using 0x80|address and not just address. It seems as if we've already created the right location with address by the time we get to the SendByte function.

If we wanted to move the cursor on line 1, wouldn't 0x80 automatically bump us to the second line?

Thanks,

Jon

Unknown said...

It comes from a subtle characteristic of these types of display-- the electronics of it allow for 40 characters per line. (They're actually identical electronics for the 20x4 lcd, so "line 1" is split in the first two lines and "line 2" is split for the last two lines.) On the 16x2 display, you can see the first 16 characters of each line.

There is a command that will let you scroll across and see the remainder of the line; see the documents I've referred to above about that.

So to answer the question, the addresses for line 1 start at 0x00. The addresses for line 2 start at 0x40. Hence the code that checks the row number before adding the column to the base value.

To write the address command, we send the binary value 0b1AAAAAAA where the A's represent the address we're moving the cursor to. 0b10000000 is 0x80, so 0x80+address (or 0x80|address) is the proper command to move to address 0bAAAAAAA.

Hope that helps-- the links above are really very useful for understanding how to use these great little displays.

Jon said...

Oh!!!! I did read the two articles but I missed that 0x80 was in Table 2. Now it totally makes sense. Thanks for the help.

Take care,

Jon

Anonymous said...

Hello David,

First of all I would like to thank you for the great work you have done with this blog. It helped me a lot.

I've been trying to edit your code for the LCD by using the following definitions:

#define RS BIT0
#define EN BIT1//instead of BIT3
#define D4 BIT5//instead of BIT4
#define D5 BIT2//instead of BIT5
#define D6 BIT4//instead of BIT6
#define D7 BIT3//instead of BIT7

This way would be easier to develop an LCM shield for my launchpad.

Could you help me and explain where should I modify the code.
I've been trying to modify the void LCM_init function. I'm always getting either some strange characters or nothing at all.

I'm a beginner with micro controllers.

Thanks!

Unknown said...

Anon: If you use different pins, you should only need to change the definitions in the file header. The code should generalize. Be sure you've wired it up the way you've specified; a misplaced wire can cause all kinds of grief for these modules.