Project 2

Printing text on an ASCII LCD module


Though there are other types, LCD modules available today usually contain a built-in controller (e.g. Hitachi HD44780). Thus such a module can be connected directly to a microcontroller to print ASCII characters withotu much effort. The 8 bits needed per character are transferred either on 8 parallel lines or sequentially on 4 lines. This way it's  possible to connect the data lines to one port on 8bit controllers as well as on 4bit controllers.
Additionally to the data lines there are 3 control lines (RS, RW, E) and connectors for voltage, ground and a contrast adjustment voltage.
1 ground (0V)
2 supply voltage (+5V)
3 Vc (contrast control voltage)
4 RS (Register Select)
5 R/W (Read Write Select)
6 E (Enable Read/Write)
7 D0 (data bit 0)
8 D1 (data bit 1)
9 D2 (data bit 2)
10 D3 (data bit 3)
11 D4 (data bit 4)
12 D5 (data bit 5)
13 D6 (data bit 6)
14 D7 (data bit 7)

Using the voltage Vc the display contrast is adjustable. The lower Vc, the higher the difference between the supply voltage and Vc and the higher is the contrast.
"RS" decides, whether a sent byte contains data or a command (0: command, 1: data).
"RW" says, whether data/commands should be read or written (0: write, 1: read).
"E" tells, whether data/commands are (to be) transferred (falling edge marks valid data).
Using the 4bit mode, only data lines D4-D7 are used and the higher nibble is transferred before the lower nibble.


I'm using a 16x2 LCD module from Conrad (BN183342-66, DM 38,50), other modules from 16x1 till 40x2 are usable with minimal changes. Different formats (1x8 till 4x40) will need some more modifications; I can't really tell since I don't have docs or samples to test. Anyway, it's important that the module has the 14 connectors described above.
Owners of an STK200/300 just have to build an adaptor cable to connect the 14pin connector on the eva board to the connectors on the lcd; then the display contrast can be easily adjusted with the trimmer.
This connector is however somewhat limited regarding the controller and method of control used. To explain this further, I will comment on the both modes you can drive an lcd display:

1) IO mode

Using the IO mode, the control lines are toggled manually: to send data to the LCD, you have to set R/W high. You have to set R/W low, wenn reading from the LCD (e.g. busy flag). RS must be set to 1 to receive/sent data and 0 to receive/sent commands. Last but not least you have to create a falling edge on the E(nable) line. In the program, this is done the following way:

    sbi(LCD_E_PORT, LCD_E_PIN); asm volatile ("nop" ::)
    cbi(LCD_E_PORT, LCD_E_PIN);

2) Memory mapped mode

To use this mode, the controller has to support external SRAM. Apart from the MegaAVRs this is only true for the 8515 and 4414. With an appropriate external circuit the LCD can be accessed by reading from/writing to the (virtual) external SRAM. The data lines have to be connected to Port A, RS on the LCD to Port C6 (A14) and R/W to Port D6 (WR). The enable signal E ist created using the following circuit:
Now I should mention how data is sent to the LCD (reading works the same way):
        *(char*)(0xC000) = data;       bzw.
        *(char*)(0x8000)  = function;
The (pseudo) memory address is 16bit wide, the low byte is put out on Port A, the high byte on Port C. The both most significant bits are A14 (PC6) and A15 (PC7). For a function/command (0x8000 = 1<<15) A15 is logical 1 and A14 logical 0, for data (0xC000 = (1<<14) + (1<<15)) A14 and A15 are logical 1.
Looking at the circuit you can see, that the output of the 2nd NAND can only be 0, if A15 == 1 and R/W or RD oder both are logical 0. A15 == 1 ist always true since bit 15 is set in 0x8000 as well as in 0xC000. WR resp. RD becomes 0 (both signals are inverted in fact) while reading/writing for short period (increased to two CPU cycles by enabling a waitstate).
One input of the last NAND is always logical 1, so just the 2nd input influences the output. In summary, the output of the last NAND becomes 1, if WR and RD are logical 1 and becomes 0, if WR and/or RD are logical 0 (during read/write cycles).
To be honest, the diode and capacitor puzzled me a bit, because they don't make sense for an "ideal" NAND. For the 74HC00 used in the circuit, the capacitor seems to limit the low period of the last NAND's output to 2ms. What the diode is for, remains uncertain to me.

IO mode on an STK board

On the one side, the memory mapped mode is a nice thing, because on an STK board you can connect and drive an lcd module with minimal effort (in software and hardware). The drawback is however, that this is only possible with AVRs supproting external SRAM (8515, 4414, Megas). Furthermore, using the STK200, the controller has to have an Port A and has to be placed in the "40PIN Digital" socket. Because other 40pin controllers have another pin layout (Vcc, GND), they can't be even used in the digital socket.
But even if you want to use an 8515 or 4414 in IO mode, the external circuit becomes a problem: the lcd's enable line E is not connected directly to an mcu pin, but via the above circuit. Because of this, you have to consider the mcu pins WR and RD, too. Fortunately, the lcd connector can be used only if Port D7 (RD) is set to 0 permanently.
How useful this is, can be questioned, of course.


The following commands to the lcd are defined:
Befehl RS RW DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0 Description t
Clear 0 0 0 0 0 0 0 0 0 1 Clear Display, return cursor
Home 0 0 0 0 0 0 0 0 1 * Return Cursor Home <1,64ms
Entry Mode 0 0 0 0 0 0 0 1 I/D S Set Cursor move direction (I/D) and display shift (S) 40us
Display On/Off 0 0 0 0 0 0 1 D C B Display on/off (D), Cursor on/off (C), Cursor blink (B) 40us
Cursor/Disp shift 0 0 0 0 0 1 S/C R/L * * Move Cursor (C), shift Display (S) 40us
Function 0 0 0 0 1 DL N F * * Set data length (DL), number of lines (N) and font (F) 40us
Set CG RAM  0 0 0 1 A5 A4 A3 A2 A1 A0 Set CG RAM address 40us
Set DD RAM 0 0 1 A6 A5 A4 A3 A2 A1 A0 Set DD RAM address 40us
Read busy flag 0 1 BF A6 A5 A4 A3 A2 A1 A0 Read busy flag (BF) and address counter. 40us
Write Data 1 0 D7 D6 D5 D4 D3 D2 D1 D0 Write data into RAM 40us
Read Data 1 1 D7 D6 D5 D4 D3 D2 D1 D0 Read data from RAM 40us

I/D  = 1:  Increment (+), I/D = 0:  decrement (-)
S     = 1:  Display shift
S/C = 1:  Display shift,    S/C = 0:  cursor move
R/L = 1:  Shift right,        R/L = 0:  shift left
DL  = 1:  8bits,               DL = 0:  4bits
N    = 1:  2 lines,             N   = 0: 1 line
F     = 1:  5x10dots,        F    = 0: 5x7dots
BF  = 1:  busy,                BF = 1: ready


The program is split into different modules again:
main.c Initializes modules and prints text on the lcd.
timer.c, timer.h Defines delay function
lcd.c, lcd.h Defines LCD related functions/definitions
global.h Global definitions

All LCD related functions are defined in module "lcd". The following functions are exported:

void lcd_init(u08 cursor, u08 fnc) Initializes the display.
void lcd_clrscr(void) Clears display.
void lcd_home(void) Sets cursor back to home position.
void lcd_gotoxy(u08 x, u08 y) Sets cursor to position (x,y).
void lcd_putchar(u08 data) Prints character at the current position.
void lcd_puts(char s[]) Prints string at current position (no auto newline).
void lcd_command(u08 cmd) Sends command to LCD

1) Initialization

When initializing using lcd_init(), the cursor mode and lcd function have to be given.

For cursor mode, the following bits are defined:
To turn the cursor on, you have to pass the follwoing to lcd_init():
To turn a blinking cursor on, you have to use:
    (1<<LCD_ON_CURSOR) | (1<<LCD_ON_BLINK)
or 0 to hide the cursor.

For the lcd function, the following bits are defined:
Same procedure as for the cursor, e.g. to use 2 lines, 8bit mode and 5x7dots font, you have to pass:
BTW: 4bit mode is not (yet) supported, since this is quite useless in memory mapped mode and complicates everything in IO mode.

2) Sending commands

Using lcd_command(), you can send any command from the above table to the lcd. The command bits are defined in "lcd.h" which have to be combined as for lcd_init(). In contrast to the parameters in lcd_init the command byte has to include the appropriate command bit. To switch the cursor off for example, you have to use the following command:
    lcd_command((1<<LCD_ON) | (1<<LCD_ON_DISPLAY));
This corresponds with  "00001100" (only data bits) from the above table and turn the display on, but the cursor off.

3) Adapting to your own needs

By definitions at the start of "lcd.h" you can adapt the module to your own needs:

    #define LCD_LINES           2     /* visible lines */
Defines whether to use one or two lines.

    #define LCD_LINE_LENGTH  0x40     /* internal line length */
Sets internal line length (should be ok for most displays, also if only 8 or 16 characters displayed).

    #define LCD_8BIT_MODE       1     /* 0: 4bit, 1: 8bit */
Don't change this, since 4bit mode is not (yet) supported.

    #define LCD_IO_MODE         1     /* 0: memory mapped mode, 1: IO port mode */
The memory mapped mode (0) only works under the conditions described above.
For IO mode, the ports/pins used can be changed shortly after this definition.

    #define LCD_HW_ENABLE       0     /* 0: normal IO, 1: IO mode on STK200/300 */
To use IO mode on an STK board, set this to 1.

4) Output

If everthing works as expected, the lcd shows the following text after startup:
loop: 00000

The number after "loop" is increased permanently; if 65535 is reached the counter restarts at 00000.


AVR program

back to homepage