"ello World" - Interfacing a venerable LCD
User Rating: / 2
PoorBest 
Written by Bryce Ringwood   

Every electronic experimenter has a junk box. (According to my wife, I have two - my study and my workshop.) Among my junk was a vintage 1980's DMC 16433 4-line LCD display. Great - I could interface it to my microprocessor projects and make something that would display something. I duly read the instructions that came with the PIC TRIX kit, and wired it up to the LCD header pin on the board using a bit of ribbon cable clamped in the connector. I split the other end of the cable and wired it to a Molex connector. I soldered a row of pins on the LCD so I could unplug it and use it for something else - e.g. the Arduino later on.

I had previously used these displays with an 8052AHBASIC chip, so wasn't expecting any problem. On programming the PIC16F877A with the test program provided, I duly got the message "MICROCHIP IS TOPS!" - so I thought everything was well.

But "unbeknownst" to me all was not well and this was perhaps a good thing.

In order to get the display to do what I wanted, I needed it to work in 'C', not assembly language. Fortunately, there was a program in the PIC samples directory, but it hadn't been designed for the pin assignment on the PIC TRIX board, so some changes would have to be made to the program. After carrying these out, I ran the program

"ello world" it said. I was slightly amused by this dropping of the aitches and wondered what could be the problem. Did the assember program do this too ? I re-ran the assembly program and it too dropped the first character - I hadn't noticed because the first character was a space. I figured out that the display hadn't finished initialsing before I asked it to print, so I reasoned a delay after initialisation was required. In the case of my display, a delay of 2 milliseconds was sufficient to allow it to finish initiallising, but some displays may need quite a lot longer.

Interfacing the Display to the Microprocessor

The DMC16433 interfaces the same way as a 2-line display, but there's a slight catch. The 3rd line is a continuation of the first line and the fourth line is a continuation of the second. Note that other 4-line displays have a second pin (EN2) which allows addressing of each line individually.

Here is a table of connections between the DMC 16433 and the PIC and Arduino processrs. The lcd can operate in 8 pin or 4 pin mode for data - we will be using 4-pin mode.

Pin No. Description PIC  Pin no. Aduino Pin No. Comment
1 Vss   gnd Pot end
2 Vcc   +5v Pot end
3 Vee     Pot Wiper
4 RS RB5 12  
5 R/W 0   I grounded this pin
6 E RB4 11  
7 DB0      
8 DB1      
9 DB2      
10 DB3      
11 DB4 RB0 5 Only high nybble is used
12 DB5 RB1 4  
13 DB6 RB2 3  
14 DB7 RB3 2  

The way the program operates is to simply write to the LCD and allow sufficient time for the  display to react. This is simpler than reading the display to find out when its finished doing what its doing. The R/W line of the display is held low.

When the program begins, we allow 15 to 30 milliseconds for the display to wake up, then we begin the initialisation sequence.

First, the display is cleared.
Next the display is set to 8 bits or 4 bits and 1 or 2 line mode.
The display is then then set to "on" and the cursor mode is set.
Finally, the screen is cleared and the data entry mode is set.

Data an commands are "strobed" on to the device by making the enable pin go high, then low.dmc16433

At this point you should be able to follow the code for the PIC. If you are struggling, then you need to go back to the previous article on number systems and maybe ask someone to help you, or read other articles on the web and try a few examples until you really understand what's going on.

What's really going on is that we have to control the display by making some pins go high and others go low. We do this by putting  a binary number on the microprocessor port (port D in the case of the PIC program.) To write a character, we first write the high nybble, then the low nybble - bearing in mind that the low nybble of port D is connected to data bits 4-7 of the display (don't blame me - I didn't design the PIC TRIX board.) The control pins are connected to the low bits of port A.

Even if you are an Arduino fan, its a good idea to look at the following code for driving the display, because its a little bit easier to follow than the Arduino code presented later.

Here is the code for the PIC:-

Main Routine

#include <htc.h>
#include "lcd.h"
__CONFIG(XT & WDTDIS & PWRTDIS & BORDIS & LVPDIS &
 DEBUGDIS & DUNPROT & UNPROTECT);

void main(void)
{
 lcd_init();
 lcd_goto(0); // select first line
 lcd_puts("Hello World on 1");
 lcd_goto(0x40); // Select second line
 lcd_puts(" 12345678 line 2");
 lcd_goto(0x10); // Select tird line
 lcd_puts(" This is line 3");
 lcd_goto(0x50); // Select 4th line
 lcd_puts(" This is line 4");
 for(;;);
}

Header file

/*
 * LCD interface header file
 * See lcd.c for more info
 */

/* write a byte to the LCD in 4 bit mode */

extern void lcd_write(unsigned char);

/* Clear and home the LCD */

extern void lcd_clear(void);

/* write a string of characters to the LCD */

extern void lcd_puts(const char * s);

/* Go to the specified position */

extern void lcd_goto(unsigned char pos);
 
/* intialize the LCD - call before anything else */

extern void lcd_init(void);

extern void lcd_putch(char);

/* Set the cursor position */

#define lcd_cursor(x) lcd_write(((x)&0x7F)|0x80)

Finally, the implementation

/*
 * LCD interface example
 * Uses routines from delay.c
 * This code will interface to a standard LCD controller
 * like the Hitachi HD44780. It uses it in 4 bit mode, with
 * the hardware connected as follows (the standard 14 pin
 * LCD connector is used):
 * 
 * PORTB bits 0-3 are connected to the LCD data bits 4-7 (high nibble)
 * PORTB bit 4 is connected to the LCD EN input (register select)
 * PORTB bit 5 is connected to the LCD RS bit (enable)
 *  R/W is not connected
 * 
 * To use these routines, set up the port I/O (TRISB) then
 * call lcd_init(), then other routines as required.
 * 
 */

#ifndef _XTAL_FREQ
 // Unless specified elsewhere, 4MHz system frequency is assumed
 #define _XTAL_FREQ 4000000
#endif


#include <htc.h>
#include "lcd.h"

#define LCD_RS RB5
#define LCD_RW RB6 // Not connected
#define LCD_EN RB4

#define LCD_DATA PORTB
#define LCD_STROBE() (LCD_EN = 1);(LCD_EN=0)

/* write a byte to the LCD in 4 bit mode */

void lcd_write_cmd(unsigned char c)
{
 __delay_us(40);
 LCD_DATA = ( ( c >> 4 ) & 0x0F );
 LCD_STROBE();
 LCD_DATA = ( c & 0x0F );
 LCD_STROBE();
}
void lcd_write_data(unsigned char c)
{
 __delay_us(40);
 LCD_DATA = ( ( c >> 4 ) & 0x0F ) | 0x20 ;
    LCD_STROBE();
 LCD_DATA = ( c & 0x0F ) | 0x20 ;
 LCD_STROBE();
 LCD_RS = 0;
}
/*
 *  Clear and home the LCD
 */

void lcd_clear(void)
{

 lcd_write_cmd(0x1);
 __delay_ms(2);
}

/* write a string of chars to the LCD */

void lcd_puts(const char * s)
{
 while(*s)
  lcd_write_data(*s++);
}

/* write one character to the LCD */

void lcd_putch(char c)

 lcd_write_data( c );
}


/*
 * Go to the specified position
 */

void lcd_goto(unsigned char pos)

 lcd_write_cmd(0x80+pos);

}
 
/* initialise the LCD - put into 4 bit mode */
void lcd_init()
{
 char init_value=0x3;
 TRISB=0;
 LCD_RS = 0;
 LCD_EN = 0;
 LCD_RW = 0;  // has no effect in this impplementation
 
 __delay_ms(30); // wait 30mSec after power applied,

 LCD_DATA  = init_value; 
 LCD_STROBE();
 __delay_ms(5);
 LCD_STROBE();
 __delay_us(200);
 LCD_STROBE();
 __delay_us(200);
 LCD_DATA = 2; // Four bit mode 
 LCD_STROBE();

 lcd_write_cmd(0x28); // Set interface length
 lcd_write_cmd(0xF);  // Display On, Cursor On, Cursor Blink
 lcd_clear();      // Clear screen
 lcd_write_cmd(0x6);  // Set entry Mode
 __delay_ms(2);
}

#define

Spammers love to send personalised mail to your in-box. This mail usually looks something like this:

 

Dear Mr [#NAME},

We are delighted to inform you that you have won R100 000 in [#CELLPHONEMAKE]'s Summer promotion. Please phone [#PHONENUMBER] to claim your prize. I'm sure that all the [#SURNAME]s will be pleased at this announcement.

Yours ... etc

I think you can see where this is going.  The 'C' language also has a macro preprocessor that allows you to make substitutions anywhere in your code. When you see the line

#define _XTAL_FREQ 4000000

This simply means that instead of writing 4000000 everywhere, you simply write "_XTAL_FREQ". The nice thing is that if you change the frequency,  you only change one line of code. Macros can also take arguments. e.g.

#define numswap(a,b) (a = a + b); (b = a - b);(a = a - b)

The above works for reals, integers, doubles etc. This would avoid writing 3 separate functions to do the same thing but it breaks 'C' s type-safe checking. You can also get into a horrible situation if you substitute arithmetic expressions for a and b, since you might get the wrong order of operator precedence, with incorrect results that might only be found a long time later.

Some programmers write very confusing macros. Consider the original line in LCD.C

#define LCD_STROBE() ((LCD_EN = 1),(LCD_EN=0))

I could see what was intended, but it didn't make sense. The program compiled and ran correctly though. I searched diligently through the "White Book", but couldn't find any reference to a comma being a statement separator.

Eventually, I tried the following line in Turbo C:-

(printf("%d",0),printf("%d",1));

The program compiled and ran correctly.I suspect this is a "side-effect" of the following allowable statement::-

int a=3,b=5;

At any rate, I altered the LCD_STROBE() definition to a more conventional one. Remember - if you do use "unconventional" programming, there might be side-effects that you didn't intend.

Interfacing the Arduino

Some months elapsed (well, maybe some years) between my connecting the LCD to the PIC and my linking it to the Arduino. In true Arduino fashion, I made up a connector and then stuck the wires in the holes and ran the liquid crystal "HelloWorld" example. When the display said something chatty like "f$?k 0xFF", I connected it correctly. "ello world" it said.....

Of course - nothing could be simpler to correct. Simply put a delay of several milliseconds ahead of the first write statement.

This is not a very satisfying solution - and in any case, we want to address 4 lines neatly. To do this we have to look a little beyond the Arduino IDE.

Introducing The Arduino Samples Library

The first line of the sketch is :
#include <LiquidCrystal.h>

You can find the source code for LiquidCrystal.h in the ...\libraries\LiquidCrystal folder. This folder is probably read-only (it should be). What we want to do is modify the library slightly. To do this:
 Step1 - make a copy of the "LiquidCrystal" folder and rename it to "MyLiquidCrystal" (say). Make the folder have read/write attributes by clicking the properties box and unchecking the readonly box.
Step 2 -Now rename the files inside the folder to "MyLiquidCrystal.c" and "MyLiquidCrystal.h".
Step 3- Using a programmers text editor, such as "Notepad++" edit these two files, so that all occurrences of "LiquidCrystal" are changed to "MyLiquidCrystal".

Now open the "LiquidCrystal" sketch. Select all the text and copy it to the clipboard. (ctrl-a, ctrl-c). Close the sketch and open a new blank one. On the menu, select sketch->import library-MyLiquidCrystal.

The header #include <MyLiquidCrystal.h> will be placed at the top of the file.
Now paste the clipboard to the sketch.
Finally, comment out the line
#include <LiquidCrystal.h>
and save the sketch.
Compile and run the sketch - if you are using a modern LCD, it should just work.

If you are the proud owner of a 1980s DMC-16433, add the following to the very end of the MyLiquidCrystal::begin(.. function.
delayMicroseconds(10000);

Save the file, open the sketch and re-compile. Now it will display correctly.

At this point, it won't do any harm to look at the Arduino tutorial on libraries.

Following the Code

Take a look inside "MyLiquidCrystal.h" to begin with. Note the #ifndef at the beginning of the file and the #endif at the end. Fairly obviously, these  are statements intended to safeguard you from redefining everything in the file. Then follows a mass of #defines - intended to make the code more readable.

Notice the inclusion of a file "Print.h" in quotes rather than angle brackets. The angle brackets are normally used for "standard libraries" and the quotes would be reserved for programmer defined files. Its interesting that "wiring" treats the liquid crystal libraries as standard, once they are imported.

When you look at the class definition for MyLiquidCrystal, you will see a colon then "public Print":-

class MyLiquidCrystal : public Print {
...
}

This means that the class MyLiquidCrystal inherits the methods and properties of the class Print.  There is no "print" function explicitly defined or implemented in the MyLiquidCrystal class, yet we use "lcd.print("something"); in the sketch.

Now, go and grab a strong coffee (and maybe a grand-pa).

In this small series of articles I'm not going to explain the key concepts of Object-Oriented Programming and program design in any great depth. You need to google "inheritance C++", "polymorphism c++" and "encapsulation c++".  These are the key concepts that differentiated C++ from other programming languages at the time it was introduced. Suffice to say, that a class is like a cookie-cutter than can be used to define a number of similar objects. These objects all behave in the same sort of way. It is possible to derive a new class from an existing one, and endow it with additional behaviours, which may or may not be similar to its parent. Polymorphism allows us to redefine the behaviours of the new class in similar terms to those of the parent. The behaviours defined in a class are generally only accessible to the objects of that class, thereby avoiding much grief. This is an example of encapsulation. Keywords are available to override complete encapsulation and allow friend classes to access the behaviours of any particular class.

If you download "print.h", you will see a large number of definitions for the print function - each one catering for a different data type. This explains how you can pass the function a string in the first line of the sketch, and a decimal number in the loop.

The "print" function in turn uses the "write" functions contained in "print.h" - except the one needed to output a character to the display. This is implemented as an inline function in MyLiquidCrystal.cpp. Why inline? Well, in this case, I think it might be to improve performance. Inline functions used this way offer a safer alternative to using #define.  I'm honestly not 100% sure why they used the inline keyword here. I usually use it in the header file for very short member functions of the get/set variety.

Now that we have broached the subject of object inheritance, there are a few more statements to understand. In "Print.h", you will see the following line:-

 virtual void write(uint8_t) = 0;

This statement causes the class Print to be an abstract base class. The function "write" is a pure virtual function - it has no body. Classes derived from Print can have their own "write" function that takes a uint_t data type. Notice however, that there are plenty of "writes' that do have a data type and that brings us to the next confusing statement. In MyLiquidCrystal.h, you wil see:-

 using Print::write;

Without this statement, the compiler would find the "write" function defined two lines above - and would ignore the "write" functions in "Print.h", which would be incorrect.

Summary

If you are new to C++ programming - and I sometimes think we remain new, no matter how long we've been programming using it, this has no doubt been a difficult experiment. Some people are able to grasp the concepts straight away without any problem, but I have known many other really bright people (who I worked with) struggle to pass their exams.

Assuming you're doing this for fun, don't get bogged down on all the detail - just use the minimum you need to know to get  by.

The lcd itself is best understood from the code for the PIC. The important thing is to understand how commands and data are written to the device using the RS line to distinguish between charaters and commands, and that a pulse on the EN (enable)pin causes data or commands to be written.

One way to grasp these principles is to write short example programs (and keep them on your computer - in case you forget!).

At the end of the day, learning to program the Arduino inevitably means learning the basics of C++.

Experiments

Remove the "inline" keyword - what happens?

Modify the code to allow 4 line display for the DMC 16433.

If you have another 4-line display with 2 enable lines - modify the code for 4 lines. (Harder).


Some Books:-

"Object Oriented Design with Applications", Booch,G, Benjamin Cummings,New York,1991

"Liquid Crystal Display", Optrex Corporation, 1990
 

 

 
Joomla template by a4joomla