1 | ;*******************************************************************************
|
2 | ; File: m8_LCD_4bit.asm
|
3 | ; Title: ATmega8 driver for LCD in 4-bit mode (HD44780)
|
4 | ; Assembler: AVR assembler/AVR Studio
|
5 | ; Version: 1.0
|
6 | ; Created: April 5th, 2004
|
7 | ; Target: ATmega8
|
8 | ; Christoph Redecker, http://www.avrbeginners.net
|
9 | ; BellaD ,www.bellibot.com - some encantements
|
10 | ; change to include file lcd4inc.asm
|
11 | ;*******************************************************************************
|
12 |
|
13 | ; Some notes on the hardware:
|
14 | ;ATmega8 (clock frequency doesn't matter, tested with 1 MHz to 8 MHz)
|
15 | ; PortD.1 -> LCD RS (register select)
|
16 | ; PortD.2 -> LCD RW (read/write)
|
17 | ; PortD.3 -> LCd E (Enable)
|
18 | ; PortD.4 ... PortD.7 -> LCD data.4 ... data.7
|
19 | ; the other LCd data lines can be left open or tied to ground.
|
20 | ; the pin Vo can fit to GND for maximum of contrast
|
21 |
|
22 |
|
23 | ;********* you want this in the main file **********
|
24 | ;.equ LCD_PORT = PORTD
|
25 | ;.equ LCD_DDR = DDRD
|
26 | ;.equ LCDS_PORT = PORTD
|
27 | ;.equ LCDS_DDR = DDRD
|
28 |
|
29 | ;.equ LCD_RS = 1
|
30 | ;.equ LCD_RW = 2
|
31 | ;.equ LCD_E = 3
|
32 |
|
33 |
|
34 | ;.def temp = r16
|
35 | ;.def arg = r17 ;argument for calling subroutines
|
36 | ;.def arg2 = r18 ;argument for mathematics
|
37 | ;.def return = r19 ;return value from subroutines
|
38 |
|
39 | ;***********************************************************************************
|
40 | ; the following source is from AVRbeginners.net .great work- well documented
|
41 | ; the advantage of read the busyflag in 4-bit mode is, no delay's needed
|
42 | ;***********************************************************************************
|
43 | LCD_command8: ;used for init (we need some 8-bit commands to switch to 4-bit mode!)
|
44 | in temp, LCD_DDR ;we need to set the high nibble of DDRD while leaving
|
45 | ;the other bits untouched. Using temp for that.
|
46 | sbr temp, 0b11110000 ;set high nibble in temp
|
47 | out LCD_DDR, temp ;write value to DDRD again
|
48 | in temp, LCD_PORT ;then get the port value
|
49 | cbr temp, 0b11110000 ;and clear the data bits
|
50 | cbr arg, 0b00001111 ;then clear the low nibble of the arg
|
51 | ;so that no control line bits are overwritten
|
52 | or temp, arg ;then set the data bits (from the arg) in the
|
53 | ;Port value
|
54 | out LCD_PORT, temp ;and write the port value.
|
55 | sbi LCDS_PORT, LCD_E ;now strobe E
|
56 | nop
|
57 | nop
|
58 | nop
|
59 | cbi LCDS_PORT, LCD_E
|
60 | in temp, LCD_DDR ;get DDRD to make the data lines input again
|
61 | cbr temp, 0b11110000 ;clear data line direction bits
|
62 | out LCD_DDR, temp ;and write to DDRD
|
63 | ret
|
64 |
|
65 | LCD_putchar:
|
66 | push arg ;save the argmuent (it's destroyed in between)
|
67 | in temp, LCD_DDR ;get data direction bits
|
68 | sbr temp, 0b11110000 ;set the data lines to output
|
69 | out LCD_DDR, temp ;write value to DDRD
|
70 | in temp, LCD_PORT ;then get the data from PortD
|
71 |
|
72 | .if LCDS_DDR == LCD_DDR ;if the same port
|
73 | cbr temp, 0b11111110 ;clear ALL LCD lines (data and control!)
|
74 | .else
|
75 | cbr temp, 0b11110000
|
76 | .endif
|
77 |
|
78 | ;cbr temp, 0b11111110 ;clear ALL LCD lines (data and control!)
|
79 |
|
80 | cbr arg, 0b00001111 ;we have to write the high nibble of our arg first
|
81 | ;so mask off the low nibble
|
82 | or temp, arg ;now set the arg bits in the Port value
|
83 | out LCD_PORT, temp ;and write the port value
|
84 | sbi LCDS_PORT, LCD_RS ;now take RS high for LCD char data register access
|
85 | sbi LCDS_PORT, LCD_E ;strobe Enable
|
86 | nop
|
87 | nop
|
88 | nop
|
89 | cbi LCDS_PORT, LCD_E
|
90 | pop arg ;restore the arg, we need the low nibble now...
|
91 | cbr temp, 0b11110000 ;clear the data bits of our port value
|
92 | swap arg ;we want to write the LOW nibble of the argument to
|
93 | ;the LCD data lines, which are the HIGH port nibble!
|
94 | cbr arg, 0b00001111 ;clear unused bits in argument
|
95 | or temp, arg ;and set the required argument bits in the port value
|
96 | out LCD_PORT, temp ;write data to port
|
97 | sbi LCDS_PORT, LCD_RS ;again, set RS
|
98 | sbi LCDS_PORT, LCD_E ;strobe Enable
|
99 | nop
|
100 | nop
|
101 | nop
|
102 | cbi LCDS_PORT, LCD_E
|
103 | cbi LCDS_PORT, LCD_RS
|
104 | in temp, LCD_DDR
|
105 | cbr temp, 0b11110000 ;data lines are input again
|
106 | out LCD_DDR, temp
|
107 | ret
|
108 |
|
109 | LCD_command: ;same as LCD_putchar, but with RS low!
|
110 | push arg
|
111 | in temp, LCD_DDR
|
112 | sbr temp, 0b11110000
|
113 | out LCD_DDR, temp
|
114 | in temp, LCD_PORT
|
115 | ;cbr temp, 0b11111110
|
116 | .if LCDS_DDR == LCD_DDR ;if the same port then
|
117 | cbr temp, 0b11111110 ;clear ALL LCD lines (data and control!)
|
118 | .else
|
119 | cbr temp, 0b11110000
|
120 | .endif
|
121 | cbr arg, 0b00001111
|
122 | or temp, arg
|
123 |
|
124 | out LCD_PORT, temp
|
125 | sbi LCDS_PORT, LCD_E
|
126 | nop
|
127 | nop
|
128 | nop
|
129 | cbi LCDS_PORT, LCD_E
|
130 | pop arg
|
131 | cbr temp, 0b11110000
|
132 | swap arg
|
133 | cbr arg, 0b00001111
|
134 | or temp, arg
|
135 | out LCD_PORT, temp
|
136 | sbi LCDS_PORT, LCD_E
|
137 | nop
|
138 | nop
|
139 | nop
|
140 | cbi LCDS_PORT, LCD_E
|
141 | in temp, LCD_DDR
|
142 | cbr temp, 0b11110000
|
143 | out LCD_DDR, temp
|
144 | ret
|
145 |
|
146 | LCD_getchar:
|
147 | in temp, LCD_DDR ;make sure the data lines are inputs
|
148 | andi temp, 0b00001111 ;so clear their DDR bits
|
149 | out LCD_DDR, temp
|
150 | sbi LCDS_PORT, LCD_RS ;we want to access the char data register, so RS high
|
151 | sbi LCDS_PORT, LCD_RW ;we also want to read from the LCD -> RW high
|
152 | sbi LCDS_PORT, LCD_E ;while E is high
|
153 | nop
|
154 | in temp, PinD ;we need to fetch the HIGH nibble
|
155 | andi temp, 0b11110000 ;mask off the control line data
|
156 | mov return, temp ;and copy the HIGH nibble to return
|
157 | cbi LCDS_PORT, LCD_E ;now take E low again
|
158 | nop ;wait a bit before strobing E again
|
159 | nop
|
160 | sbi LCDS_PORT, LCD_E ;same as above, now we're reading the low nibble
|
161 | nop
|
162 | in temp, PinD ;get the data
|
163 | andi temp, 0b11110000 ;and again mask off the control line bits
|
164 | swap temp ;temp HIGH nibble contains data LOW nibble! so swap
|
165 | or return, temp ;and combine with previously read high nibble
|
166 | cbi LCDS_PORT, LCD_E ;take all control lines low again
|
167 | cbi LCDS_PORT, LCD_RS
|
168 | cbi LCDS_PORT, LCD_RW
|
169 | ret ;the character read from the LCD is now in return
|
170 |
|
171 | LCD_getaddr: ;works just like LCD_getchar, but with RS low, return.7 is the busy flag
|
172 | in temp, LCD_DDR
|
173 | andi temp, 0b00001111
|
174 | out LCD_DDR, temp
|
175 | cbi LCDS_PORT, LCD_RS
|
176 | sbi LCDS_PORT, LCD_RW
|
177 | sbi LCDS_PORT, LCD_E
|
178 | nop
|
179 | in temp, PinD
|
180 | andi temp, 0b11110000
|
181 | mov return, temp
|
182 | cbi LCDS_PORT, LCD_E
|
183 | nop
|
184 | nop
|
185 | sbi LCDS_PORT, LCD_E
|
186 | nop
|
187 | in temp, PinD
|
188 | andi temp, 0b11110000
|
189 | swap temp
|
190 | or return, temp
|
191 | cbi LCDS_PORT, LCD_E
|
192 | cbi LCDS_PORT, LCD_RW
|
193 | ret
|
194 |
|
195 | LCD_wait: ;read address and busy flag until busy flag cleared
|
196 |
|
197 | rcall LCD_getaddr
|
198 | andi return, 0x80
|
199 | brne LCD_wait
|
200 | ret
|
201 |
|
202 |
|
203 | LCD_delay:
|
204 | clr r2
|
205 | LCD_delay_outer:
|
206 | clr r3
|
207 | LCD_delay_inner:
|
208 | dec r3
|
209 | brne LCD_delay_inner
|
210 | dec r2
|
211 | brne LCD_delay_outer
|
212 | ret
|
213 |
|
214 | LCD_init:
|
215 | ;**** for the most LCD you must write 3times a dummy command before you change to 4Bit***
|
216 | ;this is corrected by www.bellibot.com :-)) - before !you have only one LineLCD ***
|
217 |
|
218 | ;ldi temp, 0b00001110 ;control lines are output, rest is input
|
219 | in temp,LCD_DDR
|
220 | andi temp,0b00001111 ;LCD pins input
|
221 | out LCD_DDR,temp
|
222 | in temp,LCDS_DDR
|
223 | ori temp, (1<<LCD_RS)|(1<<LCD_RW)|(1<<LCD_E) ;controllpins output
|
224 | out LCDS_DDR, temp
|
225 |
|
226 | rcall LCD_delay ;
|
227 | ldi arg, 0x30 ;
|
228 | rcall LCD_command8
|
229 |
|
230 | rcall LCD_delay
|
231 | ldi arg, 0x30 ;NOW: 2 lines, 5*7 font, 4-BIT MODE! ** DUMMY ***
|
232 | rcall LCD_command8 ;
|
233 |
|
234 |
|
235 | rcall LCD_delay
|
236 | ldi arg, 0x30
|
237 | rcall LCD_command8
|
238 |
|
239 | rcall LCD_delay ;first, we'll tell the LCD that we want to use it
|
240 | ldi arg, 0x20 ;in 4-bit mode.
|
241 | rcall LCD_command8 ;LCD is still in 8-BIT MODE while writing this command!!!
|
242 |
|
243 |
|
244 | rcall LCD_delay
|
245 | ldi arg, 0x28 ;NOW: 2 lines, 5*7 font, 4-BIT MODE! ** this change to 4bit 2 lines **
|
246 | rcall LCD_command ;after this point you don't can change anything 'show HD44780 Datasheet'
|
247 |
|
248 | rcall LCD_wait
|
249 | ldi arg, 0x0F ;now proceed as usual: Display on, cursor on, blinking
|
250 | rcall LCD_command
|
251 |
|
252 | rcall LCD_wait
|
253 | ldi arg, 0x01 ;clear display, cursor -> home
|
254 | rcall LCD_command
|
255 |
|
256 | rcall LCD_wait
|
257 | ldi arg, 0x06 ;auto-inc cursor
|
258 | rcall LCD_command
|
259 | ret
|
260 |
|
261 | ;******************************************
|
262 | ; clear Displays
|
263 | LCD_clear:
|
264 | push arg
|
265 | ldi arg, 0b00000001 ; Display clear
|
266 | rcall LCD_command
|
267 | rcall LCD_wait
|
268 | pop arg
|
269 | ret
|
270 |
|
271 | ; Cursor Home
|
272 | LCD_home:
|
273 | push arg
|
274 | ldi arg, 0b00000010 ; Cursor Home
|
275 | rcall LCD_command
|
276 | rcall LCD_wait
|
277 | pop arg
|
278 | ret
|
279 | ;set cursor pos
|
280 | LCD_pos:
|
281 | sbr arg,0b10000000 ; Set DD-RAM-Adress
|
282 | rcall LCD_command
|
283 | rcall LCD_wait
|
284 | ret
|
285 |
|
286 | ;*****************************************************
|
287 | ; Display at the position in arg the string starting at Z (null-term.)
|
288 | ;
|
289 | LCD4ZTxt:
|
290 | sbr arg,0b10000000 ; Set DD-RAM-Adress
|
291 | rcall LCD_command
|
292 | rcall LCD_wait
|
293 | LCD4ZTxt1:
|
294 | lpm ; Get a char
|
295 | tst R0 ; Null-Char?
|
296 | breq LCD4ZTxtR
|
297 | mov arg,R0
|
298 | rcall LCD_putchar ; display the cahr
|
299 | rcall LCD_wait
|
300 | adiw ZL,1 ; next char
|
301 | rjmp LCD4ZTxt1 ; do it again
|
302 | LCD4ZTxtR:
|
303 | ret
|
304 | ;**********************************************************************
|
305 | ;
|
306 | ; Eine 8 Bit Zahl ohne Vorzeichen hexadezimal ausgeben
|
307 | ;
|
308 | ; Übergabe: Zahl im Register arg
|
309 | ; veränderte Register: keine
|
310 | ;
|
311 | lcd_number_hex:
|
312 | swap arg
|
313 | rcall lcd_number_hex_digit
|
314 | swap arg
|
315 |
|
316 | lcd_number_hex_digit:
|
317 | push arg
|
318 |
|
319 | andi arg, $0F
|
320 | cpi arg, 10
|
321 | brlt lcd_number_hex_digit_1
|
322 | subi arg, -( 'A' - '9' - 1 ) ; es wird subi mit negativer Konstante verwendet, weil es kein addi gibt
|
323 | lcd_number_hex_digit_1:
|
324 | subi arg, -'0' ; ditto
|
325 | rcall lcd_putchar
|
326 | rcall LCD_wait
|
327 | pop arg
|
328 | ret
|
329 | ;*********************************************************************
|
330 | ;
|
331 | ; Eine 8 Bit Zahl dezimal ohne Vorzeichen ausgeben
|
332 | ;
|
333 | ; Übergabe: Zahl im Register arg
|
334 | ; veränderte Register: keine
|
335 | ;
|
336 | lcd_number:
|
337 | ;push temp ; die Funktion verändert temp2, also sichern
|
338 | ; wir den Inhalt, um ihn am Ende wieder
|
339 | ; herstellen zu können
|
340 |
|
341 | mov arg2,arg ; das Register temp1 frei machen
|
342 | ; abzählen wieviele Hunderter
|
343 | ; in der Zahl enthalten sind
|
344 | ldi arg, '0'
|
345 | lcd_number_1:
|
346 | subi arg2, 100 ; 100 abziehen
|
347 | brcs lcd_number_2 ; ist dadurch ein Unterlauf entstanden?
|
348 | inc arg ; Nein: 1 Hunderter mehr ...
|
349 | rjmp lcd_number_1 ; ... und ab zur nächsten Runde
|
350 | ;
|
351 | ; die Hunderterstelle ausgeben
|
352 | lcd_number_2:
|
353 | rcall LCD_putchar
|
354 | rcall LCD_wait
|
355 | subi arg2, -100 ; 100 wieder dazuzählen, da die
|
356 | ; vorherhgehende Schleife 100 zuviel
|
357 | ; abgezogen hat
|
358 |
|
359 | ; abzählen wieviele Zehner in
|
360 | ; der Zahl enthalten sind
|
361 | ldi arg, '0'
|
362 | lcd_number_3:
|
363 | subi arg2, 10 ; 10 abziehen
|
364 | brcs lcd_number_4 ; ist dadurch ein Unterlauf enstanden?
|
365 | inc arg ; Nein: 1 Zehner mehr ...
|
366 | rjmp lcd_number_3 ; ... und ab zur nächsten Runde
|
367 |
|
368 | ; die Zehnerstelle ausgeben
|
369 | lcd_number_4:
|
370 | rcall LCD_putchar
|
371 | rcall LCD_wait
|
372 | subi arg2, -10 ; 10 wieder dazuzählen, da die
|
373 | ; vorhergehende Schleife 10 zuviel
|
374 | ; abgezogen hat
|
375 |
|
376 | ; die übrig gebliebenen Einer
|
377 | ; noch ausgeben
|
378 | ldi arg, '0' ; die Zahl in temp2 ist jetzt im Bereich
|
379 | add arg, arg2 ; 0 bis 9. Einfach nur den ASCII Code für
|
380 | rcall LCD_putchar ; '0' dazu addieren und wir erhalten dierekt
|
381 | rcall LCD_wait ; den ASCII Code für die Ziffer
|
382 |
|
383 | ;pop temp ; den gesicherten Inhalt von temp2 wieder herstellen
|
384 | ret ; und zurück
|