"ello World" - Interfacing a venerable LCD |
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 MicroprocessorThe 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.
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.
Data an commands are "strobed" on to the device by making the enable pin go high, then low. 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 Header file Finally, the implementation #defineSpammers 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 ArduinoSome 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 :
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: 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.
If you are the proud owner of a 1980s DMC-16433, add the following to the very end of the MyLiquidCrystal::begin(.. function. 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 CodeTake 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 { 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. SummaryIf 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++. ExperimentsRemove 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
|