longchute

about

ATmega32 Bare Metal HD44780 and LCD

26 Oct 2013

This example initializes a 1x16 LCD with an HD44780 controller and displays dummy text on the screen. The ROM appears to be corrupted on mine, so I'll be exploring how to program the CGRAM (character generator RAM) to display a custom character map in another post.

Joshua Galloway has an excellent page with a schematic and command reference for HD44780-controlled LCDs. In this example, the LCD is wired for 8-bit operation. The data bus D7-0 is connected to PORTD7-0, RS (register select) is on PORTC0, and EN (enable) is on PORTC1. The LCD is powered by its own 5V supply, separate from that powering the ATmega32.

HD44780 LCD display on an ATmega32

The watchdog timer is not used in this example, and the 7-segment LED code has been stripped out. The delay function has been rewritten to take two arguments in r24 and r25. The delay is approximately 250 microseconds * r24 * r25. This gives a reasonable range of values from about 250 microseconds up to 1.5 seconds. Most loops have been converted to PC-relative addressing, removing the need to invent increasingly verbose (and unique) labels for common constructs.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
    ;   Interrupt Handlers
    jmp boot            ;   RESET
    jmp ignore_int      ;   INT0
    jmp ignore_int      ;   INT1
    jmp ignore_int      ;   INT2
    jmp ignore_int      ;   TIMER2 COMP
    jmp ignore_int      ;   TIMER2 OVF
    jmp ignore_int      ;   TIMER1 CAPT
    jmp ignore_int      ;   TIMER1 COMPA
    jmp ignore_int      ;   TIMER1 COMPB
    jmp ignore_int      ;   TIMER1 OVF
    jmp ignore_int      ;   TIMER0 COMP
    jmp ignore_int      ;   TIMER0 OVF
    jmp ignore_int      ;   SPI, STC
    jmp ignore_int      ;   USART, RXC
    jmp ignore_int      ;   USART, UDRE
    jmp ignore_int      ;   USART, TXC
    jmp ignore_int      ;   ADC
    jmp ignore_int      ;   EE_RDY
    jmp ignore_int      ;   ANA_COMP
    jmp ignore_int      ;   TWI
    jmp ignore_int      ;   SPM_RDY
    
    ;   On RESET
    boot:
        enable_stack:
            ldi r16, 0x08
            out 0x3E, r16
            ldi r16, 0x5F
            out 0x3D, r16
        call boot_finish
        call start
    
    ;   Dummy interrupt handler (should be the 1st thing after `boot`.)
    ignore_int:
        reti
    
    ;   Additional boot chores post-stack-initialization, but before `start`
    boot_finish:
        call enable_led
        call enable_hd44780
        call flash_led
        ret
    
    ;   Main program
    start:
        main_loop:
            call toggle_led
            ldi r24, 0x3E   ;   wait 1/4 second 
            ldi r25, 0xFF
            call delay
    
            ldi r16, 0x80   ;   jump to 1st line
            call hd44780_write_command
    
            ldi r16, 0x41   ;   write 'A@?>=<;:'
            call hd44780_write_data
            dec r16
            brne PC - 3
    
            ldi r16, 0xC0   ;   jump to 3rd line
            call hd44780_write_command
    
            ldi r16, 0x42   ;   write 'BBBBBBBB'
            ldi r17, 0x08
            call hd44780_write_data
            dec r17
            brne PC - 3
    
            rjmp main_loop
    
        ;   Returns to `boot`, which drops through to `ignore_int`, calls 
        ;   `reti` and resets the processor. This is never executed.
        ret
    
    ;   HD44780 driver (reserves r23) for PORTD and PORTC
    ;   D0-D7 on PORTD0-7, R/S on PORTC0, EN on PORTC1
    enable_hd44780:
        push r25
        push r24
        push r17
        push r16
    
        ldi r17, 0xFF   ;   set DDRD to output
        out 0x11, r17
    
        ldi r17, 0x03   ;   set DDRC(0:1) to output
        out 0x14, r17
    
        ldi r24, 0x50   ;   wait 20ms
        ldi r25, 0x01
        call delay
        
        ldi r16, 0x30   ;   init
        call hd44780_write_command
        
        ldi r24, 0x14   ;   wait 5ms
        ldi r25, 0x01
        call delay
        
        ldi r16, 0x30   ;   init, round #2
        call hd44780_write_command
        
        ldi r24, 0x01   ;   wait 250us
        ldi r25, 0x01
        call delay
        
        ldi r16, 0x30   ;   init, round #3
        call hd44780_write_command
        
        ldi r24, 0x01   ;   wait 250us
        ldi r25, 0x01
        call delay
    
        ldi r16, 0x3C   ;   set interface (8-bit, 2 lines, 5x10 font)
        call hd44780_write_command
    
        ldi r16, 0x0C   ;   enable cursor/display (display on, cursor off)
        call hd44780_write_command
    
        ldi r16, 0x01   ;   clear and home display
        call hd44780_write_command
    
        ldi r16, 0x06   ;   shift display (off, left)
        call hd44780_write_command
    
        ldi r16, 0x0C   ;   turn on display
        call hd44780_write_command
    
        pop r16
        pop r17
        pop r24
        pop r25
        ret
    
    ;   Pass D0-7 in r16
    hd44780_write_command:
        push r24
        push r25
        clr r23         ;   RS=0, EN=0 on PORTC(0:1)
        out 0x15, r23
        out 0x12, r16   ;   D0-7
        ldi r23, 0x02   ;   RS=0, EN=1
        out 0x15, r23
        clr r23         ;   wait 450ns (< 1 cycle), RS=0, EN=0
        out 0x15, r23
        ldi r24, 0x14   ;   wait 5ms
        ldi r25, 0x01
        call delay
        pop r25
        pop r24
        ret
    
    ;   Pass D0-7 in r16
    hd44780_write_data:
        push r24
        push r25
        ldi r23, 0x01   ;   RS=1, EN=0 on PORTC(0:1)
        out 0x15, r23
        out 0x12, r16   ;   D0-7
        ldi r23, 0x03   ;   RS=1, EN=1
        out 0x15, r23
        ldi r23, 0x01   ;   wait 450ns (< 1 cycle), RS=1, EN=0
        out 0x15, r23
        ldi r24, 0x01   ;   wait 250us
        ldi r25, 0x01
        call delay
        pop r25
        pop r24
        ret
    
    ;  LED driver (reserves r26) for PORTB, pin 0
    enable_led:
        ldi r26, 0x01
        out 0x17, r26
        clr r26
        ret
    
    toggle_led:
        cpi r26, 0x01
        breq PC + 4
        call led_on
        ret
        call led_off
        ret 
    
    led_on:
        ldi r26, 0x01
        out 0x18, r26
        ret
    
    led_off:
        clr r26 
        out 0x18, r26
        ret
       
    ;   Library functions
    flash_led:
        push r16
        push r24
        push r25
        ldi r16, 0x06
            ldi r24, 0x7D
            ldi r25, 0x10
            call delay
            call toggle_led
            dec r16
            brne PC - 5
        pop r25
        pop r24
        pop r16
        ret
    
    ;   Delay is for r24 * r25 * 250 microseconds
    ;   r24 and r25 must be > 0 
    delay:
        push r16
        push r17
        push r24
        push r25
    
        mov r17, r24
            ldi r16, 0x3E
                dec r16
                brne PC - 1
            dec r17
            brne PC - 4
        dec r25
        brne PC - 7
    
        pop r25
        pop r24
        pop r17
        pop r16
        ret