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