Submitted by:
Glenn Clark
Protean Logic Inc.
A very common requirement for controller projects is one or more character displays. Displays based on the Hitachi 44780 LCD controller are inexpensive and readily available. These displays require 7 I/O lines to interface to, however, which can be a big problem when using processors like the TICkit or STAMP. A common solution to the I/O shortage is to use an intermediate IC or package like Scott Edward's "Serial Backpack" to reduce the I/O requirement to a single RS232 line. The serial line solution is nice and very flexible, but it too can become a problem either because of speed limitations, cost, or, if multiple displays are used, I/O limitations.
Protean Logic's Xtender IC can address all of these limitations. The Xtender IC is not specifically designed to be an LCD controller interface, but as a general purpose I2C slave, it can be made to control an LCD by issuing multiple commands from the TICkit via the two wire I2C bus. This means up to 8 LCD's can be controlled with a single TICkit and more than that is special order Xtender addresses are used. The Xtender IC's cost is moderate, and the Xtender provides many additional capabilities which might be useful or required by the circuit anyway. Also, the Xtender interface can read back from the LCD's display or character generator RAM which is useful for CRT type scrolls or other similar functions. Also, key scanning and other I/O schemes can easily be integrated with the LCD I/O on each Xtender.
The schematic diagram for connecting a TICkit, Xtender and LCD is shown below:

If you intend to place the Xtender a long distance from the controlling TICkit, you will need to use pull up resistors (10K) on the I2CCLK and I2CDAT lines. You may even want to use shielded wire for these runs as they will contain 400KHz signals.
The program for controlling the LCD is a bit more involved than the circuit. The sample below consists primarily of three routines. The xtn_lcd_init() function will initialize the Xtender and display at the specified I2C address for operation. The xtn_lcd_write() function writes to the specified Xtender and display. Depending on the i2c_address bit 8, either the LCD's data register or control register will be written. The xtn_lcd_read() function reads from the LCD connected to the specified Xtender. Again, bit 8 of the i2c_address determines if the data or control register is read.
; Program to use Xtender as a multi-drop serial interface to character
; LCD modules. Requires one Xtender per LCD
DEF tic62_c
LIB fbasic.lib
LIB xtn73h.lib
DEF lcd_rs 0y00001000b
DEF lcd_rw 0y00000100b
DEF lcd_e 0y00000010b
FUNC none xtn_buss_init
PARAM word i2c_addr ; bit 0 is ignored and must be low
BEGIN
i2c_write( +( i2c_addr, xtn_pins_in ), 0y11110000b )
i2c_write( +( i2c_addr, xtn_pins_low ), 0y11111110b )
i2c_write( +( i2c_addr, xtn_pins_out ), 0y00001110b )
ENDFUN
FUNC none xtn_buss_write
PARAM word i2c_addr ; bit 8 is used as address RS of LCD
PARAM byte data
LOCAL byte temp
BEGIN
IF b_test( i2c_addr, 0x0100w )
b_clear( i2c_addr, 0x0100w )
i2c_write( +( i2c_addr, xtn_pins_low ), 0y11111110b )
i2c_write( +( i2c_addr, xtn_pins_out ), 0y11111110b )
=( temp, b_and( data, 0y11110000b ))
i2c_write( +( i2c_addr, xtn_pins_high ), temp )
i2c_write( +( i2c_addr, xtn_pins_high ), 0y00000010b ) ; pulse enable
i2c_write( +( i2c_addr, xtn_pins_low ), 0y00000010b ) ; enable back low
=( temp, *( 16b, b_and( data, 0y00001111b )))
ELSE
i2c_write( +( i2c_addr, xtn_pins_low ), 0y11111110b )
i2c_write( +( i2c_addr, xtn_pins_out ), 0y11111110b )
=( temp, b_and( data, 0y11110000b ))
=( temp, b_or( temp, 0y00001000b ))
i2c_write( +( i2c_addr, xtn_pins_high ), temp )
i2c_write( +( i2c_addr, xtn_pins_high ), 0y00000010b ) ; pulse enable
i2c_write( +( i2c_addr, xtn_pins_low ), 0y00000010b ) ; enable back low
=( temp, *( 16b, b_and( data, 0y00001111b )))
ENDIF
i2c_write( +( i2c_addr, xtn_pins_low ), 0y11110110b )
i2c_write( +( i2c_addr, xtn_pins_high ), temp )
i2c_write( +( i2c_addr, xtn_pins_high ), 0y00000010b ) ; pulse enable
i2c_write( +( i2c_addr, xtn_pins_low ), 0y00000010b ) ; enable back low
ENDFUN
FUNC byte xtn_buss_read
PARAM word i2c_addr ; bit 8 is used as address of RS of LCD
LOCAL byte temp
BEGIN
IF b_test( i2c_addr, 0x0100w )
b_clear( i2c_addr, 0x0100w )
i2c_write( +( i2c_addr, xtn_pins_high ), 0y00000100b )
i2c_write( +( i2c_addr, xtn_pins_low ), 0y00001010b )
ELSE
i2c_write( +( i2c_addr, xtn_pins_high ), 0y00001100b )
ENDIF
i2c_write( +( i2c_addr, xtn_pins_in ), 0y11110000b )
i2c_write( +( i2c_addr, xtn_pins_out ), 0y00001110b )
i2c_write( +( i2c_addr, xtn_pins_high ), 0y00000010b ) ; pulse enable
=( exit_value, b_and( i2c_read( +( i2c_addr, xtn_pins )), 0y11110000b ))
i2c_write( +( i2c_addr, xtn_pins_low ), 0y00000010b ) ; enable back low
i2c_write( +( i2c_addr, xtn_pins_high ), 0y00000010b ) ; pulse enable
=( temp, b_and( i2c_read( +( i2c_addr, xtn_pins )), 0y11110000b ))
i2c_write( +( i2c_addr, xtn_pins_low ), 0y00000010b ) ; enable back low
=( exit_value, b_or( exit_value, /( temp, 16b )))
ENDFUN
FUNC none xtn_lcd_init
PARAM word i2c_addr
BEGIN
xtn_buss_init( i2c_addr )
delay( 15 ) ; wait 15ms
i2c_write( +( i2c_addr, xtn_pins_low ), 0y11111110b )
i2c_write( +( i2c_addr, xtn_pins_out ), 0y11111110b )
i2c_write( +( i2c_addr, xtn_pins_high ), 0y00110000b )
i2c_write( +( i2c_addr, xtn_pins_high ), 0y00000010b ) ; pulse enable
i2c_write( +( i2c_addr, xtn_pins_low ), 0y00000010b ) ; enable back low
delay( 5 )
i2c_write( +( i2c_addr, xtn_pins_high ), 0y00000010b ) ; pulse enable
i2c_write( +( i2c_addr, xtn_pins_low ), 0y00000010b ) ; enable back low
delay( 1 )
i2c_write( +( i2c_addr, xtn_pins_high ), 0y00000010b ) ; pulse enable
i2c_write( +( i2c_addr, xtn_pins_low ), 0y00000010b ) ; enable back low
i2c_write( +( i2c_addr, xtn_pins_low ), 0y00010000b ) ; make it a 2 nibble
i2c_write( +( i2c_addr, xtn_pins_high ), 0y00000010b ) ; pulse enable
i2c_write( +( i2c_addr, xtn_pins_low ), 0y00000010b ) ; enable back low
xtn_buss_write( +( i2c_addr, 0x0100w ), 0y00101000b ) ; assumes 2 line 5x7 font
xtn_buss_write( +( i2c_addr, 0x0100w ), 0y00001111b )
xtn_buss_write( +( i2c_addr, 0x0100w ), 0y00000001b )
xtn_buss_write( +( i2c_addr, 0x0100w ), 0y00000110b )
ENDFUN
FUNC none xtn_lcd_string
PARAM word i2c_addr
PARAM word str_pntr
LOCAL word pointer
LOCAL byte data
BEGIN
=( pointer, str_pntr )
=( data, ee_read( pointer ))
WHILE data
xtn_buss_write( i2c_addr, data )
++( pointer )
=( data, ee_read( pointer ))
LOOP
ENDFUN
FUNC none main
BEGIN
delay( 10 )
rs_param_set( debug_pin )
con_out( i2c_read( xtn_dev0 | xtn_reset ))
xtn_lcd_init( xtn_dev0 )
xtn_lcd_string( xtn_dev0, "Hello There" )
REP
debug_on()
LOOP
ENDFUN
You can use the functions above and create a simple library of functions for displaying strings, scrolling, displaying numbers, etc. on LCD's in this manner.
Protean Logic Inc. Copyright 05/06/04 Top of Page