Adding an LCD display

9 May

Adding an LCD display
A liquid crystal display (LCD) is a simple and cheap device to use for your TINI to provide an output for the user. There are many parallel interface LCDs on the market these days that adhere to a data standard of sorts. This standard seems to follow the data formats and pinouts of displays based on the Hitachi HD4478010 controller chip. You can find LCDs in a variety of sizes, all based on the number of characters displayed and the size of the array: 1×16, 1×20, 1×40, 2×20, 2×40, and 4×20 are common but a number of other sizes can be found as well. We will connect an LCD to the TINI stick as a memory-mapped device. This involves connecting the data bus lines from TINI to the LCD and implementing some sort of enable for the LCD. This enable will let the LCD know when data should be read from the data bus, and whether that data is a command or information to be displayed. We will be using the address decoder shown in Figure 8-10 for this, although there are many ways this address decoding can be implemented. Simply purchasing a serial LCD and connecting that to a serial port is also an option (but we won’t be discussing that here). The TINI class LCDPort can be used to communicate with a HD44780-based display, but it is somewhat limited in its implementation. You can use the address decoder shown in Figure 8-9 and this will work nicely with the LCDport that is part of the TINI API. 11 12 The LCDport class uses the address that is decoded by the decoder shown in Figure 8-9 by default, but the base address can be changed as needed if you choose to implement a different address decoder (like we do, as shown in Figure 8-10). The TINI LCDport class will not let you change which address line you use to select the LCD mode (in this case it’s fixed on A3). The sample program below will display “Hello World” if you have connected your LCD using the address decoder from either of the two listed references. Note that this uses address line A19 and chip select /CE3 to put the LCD at address 0×00380000 in memory, using A3 to select between command mode and data mode. Listing 8-3 is written to work with the address decoder shown in Figure 8-9 and the default settings of the LCDport class.

Listing 8-3: dsLCDporttest.java
import com.dalsemi.comm.*;
import com.dalsemi.system.*;
// Class dsLDPporttest tests out the dalsemi LCDport class
public class dsLCDporttest
{
public static void main(String[] args)
{
// Configure display to 8bits, 2 lines
LCDPort.sendControl(0×38);
// Turn display on, cursor off
LCDPort.sendControl(0×0C);
// Clear the display
LCDPort.sendControl(1);
// Start at 00
LCDPort.setAddress(0×00);
String message = “Hello World”;
byte[] databytes = message.getBytes();
for (int i = 0; i < databytes.length; i++)
{
LCDPort.sendData(databytes[i]);
}
}
}
Compile this program:
C:\> javac -bootclasspath %TINI_HOME%\bin\tiniclasses.jar
-d bin src\dsLCDporttest.java
C:\> java-classpath %TINI_HOME%\bin\tini.jar;. BuildDependency
-p%TINI_HOME%\bin\owapi_dependencies_TINI.jar
-f bin
-x%TINI_HOME%\bin\owapi_dep.txt
-o bin\dsLCDporttest.tini
-d %TINI_HOME%\bin\tini.db

You can place this LCD elsewhere in memory by changing the LCD port address using the com.dalsemi.system.TINIOS.setLCDAddress() method but, as we mentioned already, there is no method that allows us to select a different address line to select the LCD data/command mode and the Dallas Semiconductor classes make no provision for enabling the LCD mode that would allow us to read data back from it. To add flexibility and take advantage of all of the LCD’s features, we offer an alternative schematic for connecting the LCD using the address decoder shown in Figure 8-10. We will connect the TINI address bus lines A0 and A1 to the LCD to control the LCD’s modes. This allows us to read from the display (things like cursor position or status) as well as write to it and also to control whether we are writing text for display or sending commands (like “clear screen” and “home cursor”). We will use address line A0 to control the LCD R/W pin, which puts the LCD in either read (high signal) or write (low signal) mode. Read mode is the mode in which the LCD will send data back to us, and write mode is the mode in which we will send data to it. Address line A1 is used to control the LCD RS (register select) pin to control whether the LCD interprets data on the data bus as instructions (signal is low for select mode) or as text data to display (signal is high for register mode). Figure 8-12 shows how we will connect our address decoder to an LCD.

Figure 8-12: LCD display schematic
Examining the schematic, there are a few noteworthy things. Some displays may have fewer than 16 pins. If your display does not have a backlight LED, then you may have a 14-pin connector. Some LCDs only accept data in nibbles (4 bits) so you will have fewer data lines. We found that this circuit didn’t need a contrast adjustment so we simply tied pin 3 on the LCD to Vcc. In all cases, consult the data sheet for your LCD to determine the function and order of the connector pins. Listing 8-4 is a simple program, LCDhello, that demonstrates accessing the LCD directly using the DataPort class to give us full control over the LCD. Note that we can change the chip select from /PCE0 to any other unused chip select and change the address lines to put the LCD elsewhere in memory as needed. In contrast to our previous example, the LCD now resides in a completely different place in memory. The LCD now lives at address 0×00800008 as decoded by the address decoder (Figure 8-10) and is in command mode and ready for writing when A0 and A1 are low. When A1 is high, then the address for the LCD for writing data is 0×0080000A.

Listing 8-4: LCDhello.java

// Class LCDhello demonstrates how to simply communicate with an LCD
import com.dalsemi.system.*;
public class LCDhello
{
public static void main(String[] args)
{
DataPort data                                                                            = new DataPort( 0×0080000A );
DataPort command = new DataPort( 0×00800008 );
data.setStretchCycles(DataPort.STRETCH10);
command.setStretchCycles(DataPort.STRETCH10);
String message=”The Quick Brown Fox Jumps Over The Lazy Sleeping Dog.”;
try {
// set display mode
command.write(0×38);
TINIOS.sleepProcess( 150 );
// clear display
command.write(0×01);
TINIOS.sleepProcess( 5 );
// turn on display, cursor
command.write(0×0F);
TINIOS.sleepProcess( 10 );
byte[] databytes = message.getBytes();
for (int i = 0; i < databytes.length; i++)
{
data.write(databytes[i]);
}
}
catch(IllegalAddressException e) {
System.out.println( e );
}
}
}

Taking apart the above Java program a bit, we can see that two DataPorts are created, one for sending data and one for sending commands. These are the addresses that are defined by the address lines used by the address decoder and the data/command enable for the LCD (with this simple program we have not yet used the read mode).

DataPort data = new DataPort( 0×0080000A );
DataPort command = new DataPort( 0×00800008 );

Also notice that we have set the StretchCycles to STRETCH10. You can try changing the values of this to see what happens. With lower values we didn’t get consistent operation and at the lowest values the LCD does not seem to respond at all, since the commands to the LCD are changing too fast for it to process them.

data.setStretchCycles(DataPort.STRETCH10);
command.setStretchCycles(DataPort.STRETCH10);

After each of the writes to the command dataport, you will notice a sleepProcess. This is because these commands require a little longer time than the StretchCycles provides for. Consult the Hitachi data sheets and previously mentioned references for detailed timing information and the commands that these displays understand.

command.write(0×38);
TINIOS.sleepProcess( 150 );

Finally, we send our text to the display. You will notice that the dataport write() method takes bytes so we first need to convert our message into an array of bytes, and then each byte is written to the dataport sequentially.

byte[] databytes = message.getBytes();
for (int i = 0; i < databytes.length; i++)
{
data.write(databytes[i]);
}

Compile this program the usual way:

C:\> javac -bootclasspath %TINI_HOME%\bin\tiniclasses.jar
-d bin src\LCDhello.java
C:\> java-classpath %TINI_HOME%\bin\tini.jar;. BuildDependency
-p%TINI_HOME%\bin\owapi_dependencies_TINI.jar
-f bin
-x%TINI_HOME%\bin\owapi_dep.txt
-o bin\LCDhello.tini
-d %TINI_HOME%\bin\tini.db

There are two other features of the LCD that we can take advantage of: The LCD allows us to define eight custom characters for displaying, and we can also read the cursor position, the LCD display contents, and the LCD display status. To read the LCD status, we set the R/W line (A0) for read (high) and the R/S line (A1) for instructions (low). To read the LCD contents we need to set the R/W line (A0) for read (high) and the R/S line (A1) for data (high). So the dataport address for reading the status is 0×00800009 and the dataport address for reading data is 0×0080000B. You will see this in the program (Listing 8-7: myLCD.java). Each custom character is defined as a sequence of bytes, each bit representing horizontal lines of pixels that are on (1) or off (0). The figure shows two examples of how these custom characters are defined. This data is sent to the LCD by first sending the LCD a command to select the internal LCD memory address and then writing a sequence of data to the LCD for it to interpret as a custom font. Figure 8-13 shows an example character definition.

Figure 8-13: LCD character definition

The following program defines the class LCDfont. This class will be used in our final LCD class and lets us treat each custom font as an object.

Listing 8-5: LCDFont.java
// Class LCDFont implements a user defined font set for a LCD display
public class LCDfont {
public int fontdata [];
public int fontcode;
// Some interesting font characters
public static int SMILEY []     = { 0×00,0×0A,0×00,0×00,0×11,0×0E,0×00,0×00 };
public static int MAN []       = { 0×0E,0×11,0×0E,0×14,0×1F,0×04,0×0A,0×0A };
public static int WOMAN []   = { 0×0E,0×11,0×0E,0×1F,0×04,0×0E,0×1F,0×0A };
public static int DEGREE []    = { 0×07,0×05,0×07,0×00,0×00,0×00,0×00,0×00 };
public static int FROWNY [] = { 0×00,0×0A,0×00,0×00,0×0E,0×11,0×00,0×00 };
public static int DOWNARROW [] = { 0×00,0×04,0×04,0×15,0×0E,0×04,0×00,0×00 };
public static int UPARROW [] = { 0×00,0×04,0×0E,0×15,0×04,0×04,0×00,0×00 };
public static int INVADER []   = { 0×04,0×0E,0×1F,0×15,0×1F,0×0E,0×0A,0×15 };

public LCDfont( int f1, int f2, int f3, int f4,
int f5, int f6, int f7, int f8 ) {
fontdata = new int [8];
fontcode = 0;
fontdata[0] = f1;
fontdata[1] = f2;
fontdata[2] = f3;
fontdata[3] = f4;
fontdata[4] = f5;
fontdata[5] = f6;
fontdata[6] = f7;
fontdata[7] = f8;
}
public LCDfont () {
// The default is a simple smiley :)
this(0×00,0×0A,0×00,0×00,0×11,0×0E,0×00,0×00);
}
public LCDfont( int data[] ) {
fontdata = new int[8];
fontdata = data;
fontcode = 0;
}
public void setCode( int code ) {
fontcode = code;
}
public String toString() {
return ( “” + (char) fontcode );
}
}

Our final class, LCDport, uses the classes LCDfont and
com.dalsemi.system.dataport to construct a full-featured class for driving your
LCD and taking advantage of all of its features.

Listing 8-6: LCDport.java
import com.dalsemi.system.*;
// Class LCDport implements an alternative class to com.dalsemi.com.LCDport
// with additional funtionality (like reading back data, status, defining

// fonts, etc
public class LCDport
{
public                 static   final int         DISPLAY_CLEAR                                  =   0×01;
public                 static   final int         DISPLAY_HOME                                   =   0×02;
public                 static   final int         ENTRY_MODE                                     =   0×04;
public                 static   final int         CURSOR_INC                                     =   0×02;
public                 static   final int         CURSOR_DEC                                     =   0×00;
public                 static   final       int   SHIFT                                          =   0×01;
public                 static   final int         DISPLAY_MODE                                   =   0×08;
public                 static   final int         DISPLAY_ON                                     =   0×04;
public                 static   final int         DISPLAY_OFF                                    =   0×00;
public                 static   final       int   CURSOR_ON                                      =   0×02;
public                 static   final int         CURSOR_OFF                                     =   0×00;
public                 static   final int         CURSOR_BLINK                                   =   0×01;
public                 static                     final int CURSOR_NOBLINK                       =   0×00;
public                 static   final int         SHIFT_MODE                                     =   0×10;
public                 static   final int         CURSOR_MOVE                                    =   0×00;
public                 static   final int         DISPLAY_SHIFT                                  =   0×08;
public                 static   final int         SHIFT_LEFT                                     =   0×00;
public                 static   final int         SHIFT_RIGHT                                    =   0×04;
public                 static   final int         FUNCTION_SET                                   =   0×20;
public                 static   final       int   DATA_4bit                                      =   0×00;
public                 static   final       int   DATA_8bit                                      =   0×10;
public                 static   final       int   LINES_1                                        =   0×00;
public                 static   final       int   LINES_2                                        =   0×08;
public                 static   final       int   FONT_5×7                                       =   0×00;
public                 static   final       int   FONT_5×10                                      =   0×04;
public                 static   final int                                    CHARACTER_ADDRESS       = 0×80;
public                 static   final int         DATA_ADDRESS                                   =   0×40;
public                 static   final       int   BUSY_FLAG                                      =   0×80;
public                 static   final int         ADDRESS_PART                                   =   0×7F;
DataPort dataport;
DataPort cmdport;
DataPort readport;
DataPort statport;

public LCDport(int cmd_addr, int data_addr, int stat_addr, int read_addr) {
cmdport                                                                       = new DataPort( cmd_addr );
cmdport.setStretchCycles(DataPort.STRETCH10);
dataport = new DataPort( data_addr );
dataport.setStretchCycles(DataPort.STRETCH10);

statport = new DataPort( stat_addr );
statport.setStretchCycles(DataPort.STRETCH10);
readport = new DataPort( read_addr );
readport.setStretchCycles(DataPort.STRETCH10);

}
public void init() {
// set display mode
this.set(FUNCTION_SET + DATA_8bit + LINES_2 + FONT_5×7);
// Wait for it. These waits are IMPORTANT, if you don’t
// then you will be sending commands while its not ready
TINIOS.sleepProcess( 150 );
// clear the display
this.set(DISPLAY_CLEAR);
TINIOS.sleepProcess( 5 );
// turn on the display and cursor
this.set(DISPLAY_MODE + DISPLAY_ON + CURSOR_ON +
CURSOR_BLINK);
TINIOS.sleepProcess( 10 );
}
public void set( int value ) {
try {
cmdport.write( value );
}
catch(IllegalAddressException e)                           {
System.out.println( “Error in SET” );
System.out.println( e );
}
}
public void write( String message ) {
byte[] databytes = message.getBytes();
try {
for (int i = 0; i < databytes.length; i++)
{
dataport.write(databytes[i]);
}
}
catch(IllegalAddressException e)                           {
System.out.println( “Error in WRITE” );
System.out.println( e );
}
}
public void write( int address, String message ) {

this.set( CHARACTER_ADDRESS + address );
byte[] databytes = message.getBytes();
try {
for (int i = 0; i < databytes.length; i++)
{
dataport.write(databytes[i]);
}
}
catch(IllegalAddressException e)
System.out.println( “Error in WRITE” );
System.out.println( e );
}
}
public String read(int n)
{
byte[] d = new byte [20*4];
// read n bytes from whatever the current address is
// return as a String
try {
// Wait for display to get into output mode
readport.setStretchCycles(DataPort.STRETCH10);
for(int i = 0; i < n; i++) {
d[i] = (byte)readport.read();
}
}
catch (IllegalAddressException e)
System.out.println( “Error in WRITE” );
System.out.println( e );
}
// This does not translate special characters
String s = new String(d);
return(s);
}
public void defineFont( int code, LCDfont font ) {
// code defines the ASCI code for this character.
int address = (code)*8;
this.set( DATA_ADDRESS + address );
font.setCode( code );
try {
for (int i = 0; i < font.fontdata.length; i++)
{
dataport.write(font.fontdata[i]);
}
}
catch(IllegalAddressException e)
System.out.println( “Error in WRITE” );
System.out.println( e );
}
}
public byte getAddress() {
int i = 0;
try {
i = statport.read();
}
catch(IllegalAddressException e)              {
System.out.println( e );
}
return( (byte)(i & ADDRESS_PART) );
}
public boolean isBusy() {
int i = 0;
try {
i = statport.read();
}
catch(IllegalAddressException e)              {
System.out.println( e );
}
return( (i & BUSY_FLAG)>0 ? true : false );
}
}

Most of the functions of this class are fairly straightforward.

•  LCDport(  int,  int,  int,  int  ),the constructor, takes four parameters: the addresses for each of the different modes (command, sending text, status, read text)
•  init() initializes the display to a known state.
•  set( int ) sends commands to the LCD.
•  write(  string  ) sends a character string to the LCD for display.
•  write(  int,  string  ) sends a character string to the LCD for display, starting at the specified display address.
•  read(  int  ) returns the specified number of  characters from the LCD as a character string.
•  defineFont( int, Font ) sends a Font object to the LCD as the specified character (0..7). The font needs to be defined using the LCDFont( int array ) method.
•  getAddress() returns the current LCD cursor address.
•  isBusy()  returns a boolean indicating  if the LCD is busy or ready (true for busy).

Let’s take this LCDport class out for a test drive. We will create a new class, myLCD, that will use both LCDport and LCDfont.

Listing 8-7: myLCD.java

import com.dalsemi.comm.*;
import com.dalsemi.system.*;
// Class myLCD demonstrates the methods in our LCDport class
public class myLCD
{
public static void main(String[] args)
{
LCDport mylcd =
new LCDport(0×00800008, 0×0080000A, 0×00800009, 0×0080000B);
// define some custom fonts
LCDfont man = new LCDfont(LCDfont.MAN);
LCDfont woman = new LCDfont(LCDfont.WOMAN);
LCDfont smiley  = new LCDfont(LCDfont.SMILEY);
LCDfont frowny  = new LCDfont(LCDfont.FROWNY);
LCDfont invader   = new LCDfont(LCDfont.INVADER);
LCDfont degree = new LCDfont(LCDfont.DEGREE);
LCDfont uparrow   = new LCDfont(LCDfont.UPARROW);
LCDfont downarrow  = new LCDfont(LCDfont.DOWNARROW);
// initialize it, check the status
mylcd.init();
System.out.println( “Busy? “ + mylcd.isBusy() );
// home the cursor
mylcd.set(LCDport.DISPLAY_HOME);
// Send the custom fonts to the display memory
mylcd.defineFont( 0, invader );
mylcd.defineFont( 1, man );
mylcd.defineFont( 2, woman );
mylcd.defineFont( 3, smiley );
// Try writibng, assumes a 4×20 display.
// Addresses will be different for other sizes
mylcd.write( 0×00, “Line 1”
// get the current display address, should be 6
System.out.println( “Address is “ + mylcd.getAddress() );
// Send some special characters
mylcd.write( 0×40, “Line 2” + invader + man + woman + smiley);
// write some more.
mylcd.write( 0×14, “Line 3” );
mylcd.write( 0×54, “Line 4” );
mylcd.write( 0×61, “The End” );
// Lets rtead back alll of the text we sent to the display.
String stuff;
System.out.println( “Reading back data”);
mylcd.set( LCDport.CHARACTER_ADDRESS + 0×00 );
stuff = mylcd.read( 20 );
System.out.println( “[“+stuff+”] “ + stuff.length() );
mylcd.set( LCDport.CHARACTER_ADDRESS + 0×40 );
stuff = mylcd.read( 20 );
System.out.println( “[“+stuff+”] “ + stuff.length() );
mylcd.set( LCDport.CHARACTER_ADDRESS + 0×14 );
stuff = mylcd.read( 20 );
System.out.println( “[“+stuff+”] “ + stuff.length() );
mylcd.set( LCDport.CHARACTER_ADDRESS + 0×54 );
stuff = mylcd.read( 20 );
System.out.println( “[“+stuff+”] “ + stuff.length() );
// done!
}
}

Note that writing custom characters to the display requires no special action on our part. Instead we use the toString method (implied) of the LCDfont class to return fontcode, a number that corresponds to the character code for that custom character. If we tried

System.out.println( invader );
Java would call the toString method for the invader object (the toString() method
of the LCDFont class) which would return the character code we assigned to the
invader object in the line:
mylcd.defineFont( 0, invader );
Compile this program:

C:\> cd src
C:\> javac -bootclasspath %TINI_HOME%\bin\tiniclasses.jar
-d ..\bin myLCD.java
C:\> cd ..
C:\> java  -classpath %TINI_HOME%\bin\tini.jar;. BuildDependency
-p  %TINI_HOME%\bin\owapi_dependencies_TINI.jar
-f bin
-x %TINI_HOME%\bin\owapi_dep.txt
-o bin\myLCD.tini
-d %TINI_HOME%\bin\tini.db

This is what a 4×20 LCD looks like after running this program.

Figure 8-14: LCD example
Remember, LCD displays will vary depending on the controller chip and the size of the LCD display. Be sure to get and read the data sheets. Also, be aware of the way your display addresses are arranged (the 4×20 is treated as two 2×40 lines).

Random Posts

Comments are closed.