Asembler mikrokontrolerów AVR     Obsługa wyświetlacza LCD         Kompilacja




Powyższy schemat przedstawia sposób podłączenia alfanumerycznego wyświetlacza LCD (ze stero-
wnikiem HD44780 lub kompatybilnym), który może wyświetlić 2 linie tekstu po 16 znaków (2x16). Kon-
densator C1 filtruje napięcie zasilania Vcc=4.75-5.25V i powinien być umieszczony, jak najbliżej mikro-
kontrolera. Rezystor R1 utrzymuje wysoki stan logiczny na pinie RESET, przez co zapobiega przypad-
kowemu i samoistnemu resetowaniu się mikrokontrolera (np. podczas pracy w środowisku, gdzie wys-
tępują duże zakłócenia). Działanie zworki J1 jest ściśle związane z programem opisanym niżej. Złącze
CON1 służy do programowania mikrokontrolera w systemie (ISP - In-System Programming) i zawiera
sygnały rozmieszczone w standardowy sposób, zalecany przez firmę Atmel. Programowanie w systemie
oznacza możliwość zaprogramowania mikrokontrolera w układzie elektronicznym, w którym pracuje bez
potrzeby wyjmowania/wylutowywania go z niego. Potencjometrem P1 reguluje się kontrast wyświetlacza
LCD, co ma duży wpływ na jego czytelność. Jeśli wyświetlacz ma podświetlenie w postaci diody LED,
to jego wyprowadzenia nr 15 i 16 służą do jej zasilania. W przeciwnym razie wyprowadzenia te nie są
podłączone (NC).
Na wielu schematach, pokazujących sposób podłączenia wyświetlacza LCD do mikrokontrolera w trybie
4-bitowym, linie danych D0-D3 wyświetlacza są połączone bezpośrednio z masą zasilania (GND). Nie
jest to prawidłowy sposób biorąc pod uwagę fakt, że linie te są dwukierunkowe i mogą pracować jako
wejścia/wyjścia (I/O). Przykładowo, jeśli program sterujący odczytuje dane z wyświetlacza i na którejś
linii D0-D3 pojawia się wysoki stan logiczny, to następuje zwarcie plusa zasilania do masy. Oczywiście
zwarcie to jest krótkotrwałe i nie musi uszkodzić wyświetlacza, czy zaburzać jego pracy, ale mimo to
lepiej unikać takich sytuacji. Dlatego na moim schemacie nieużywane linie D0-D3 wyświetlacza nie są
w ogóle podłączone.
Aby niżej opisany program zadziałał, bardzo ważne jest właściwe skonfigurowanie mikrokontrolera, czyli
zaprogramowanie tzw. fuse/lock bitów. Jeśli mikrokontroler był już używany, to należy się upewnić że ma
odpowiednio ustawione fuse i lock bity: FL (Fuse Low): $62, FH (Fuse High): $DF ($D9 dla ATmega-
328), FE (Fuse Extended): $F9 ($FF dla ATmega48/328), LB (Lock Bits): $FF. Fabrycznie nowe mikro-
kontrolery ATmega48/88/168/328 mają właśnie takie domyślne ustawienia. Powodują one, że mikrokon-
troler korzysta z wewnętrznego oscylatora RC, o częstotliwości nominalnej 8 MHz (bity CKSEL3-0=
0010); włączony jest dzielnik częstotliwości przez 8, co powoduje taktowanie mikrokontrolera zegarem
1 MHz (CKDIV8=0); wydłużony jest czas jego startu do ok. 65ms po włączeniu napięcia zasilania (SUT-
1-0=10), a także włączona jest możliwość jego zaprogramowania przez interfejs szeregowy (SPIEN=0).
Należy bardzo uważać, aby nie dokonać zmian w fuse/lock bitach, które uniemożliwią dalsze programo-
wanie mikrokontrolera.

Poniżej znajduje się opis kolejnych fragmentów kodu programu, przeznaczonego dla mikrokontrolerów
serii ATmega48/88/168/328, który służy do inicjalizacji i obsługi wyświetlacza LCD.
.include  "m48def.inc"    ;definicje dla ATmega48
;.include  "m88def.inc"    ;definicje dla ATmega88
;.include  "m168def.inc"   ;definicje dla ATmega168
;.include  "m328def.inc"   ;definicje dla ATmega328
Dołączanie plików z definicjami nazw i adresów rejestrów dla danego mikrokontrolera (w tym przypadku
dla ATmega48). Jeśli kompilujemy program dla ATmega88, 168 lub 328, to należy pierwszą linię zamie-
nić w komentarz (na jej początku dodać znak ";") i uaktywnić jedną z trzech pozostałych linii (usunąć
znak ";").
.cseg             ;pamięć programu (FLASH)
.org      $0000   ;wektory przerwań (dla ATmega48/88: 26 wektorów 2-bajtowych, o adresach $0000-$0019,
                  ;dla ATmega168/328: 26 wektorów 4-bajtowych, o adresach $0000-$0032).

;Jeśli żadne przerwania nie będą używane, to w tym miejscu można bezpośrednio umieścić kod programu

;Wektor nr 1 (Reset)
          ;rjmp    RESET   ;dla ATmega48/88 musi to być instrukcja RJMP o rozmiarze 2 bajtów,
          ;jmp     RESET   ;dla ATmega168/328 musi to być instrukcja JMP o rozmiarze 4 bajtów,
                          ;ponieważ wszystkie wektory w tych mikrokontrolerach mają taki rozmiar.

;RESET:
;Inicjalizacja wskaźnika stosu (zbędna dla ATmega48/88/168/328)
          ;ldi     R16,LOW(RAMEND)
          ;out     SPL,R16
          ;ldi     R16,HIGH(RAMEND)
          ;out     SPH,R16
Dyrektywa ".cseg" oznacza blok pamięci kodu/programu (FLASH) mikrokontrolera, a ".org $0000" okre-
śla jej początek, czyli adres zerowy. Po włączeniu/resecie systemu, to właśnie od tego adresu mikro-
kontroler rozpoczyna wykonywanie instrukcji. Ten program nie używa przerwań dlatego pod adresem
zerowym, znajduje się jego pierwsza instrukcja. Natomiast jeśli przerwania są wykorzystywane, to pod
tym adresem znajduje się tablica 26 wektorów przerwań. Każdy wektor to instrukcja skoku do procedury,
która obsługuje dane przerwanie (2-bajtowa RJMP dla ATmega48/88 lub 4-bajtowa JMP dla ATmega-
168/328). Po wystąpieniu przerwania mikrokontroler przerywa wykonywanie głównego programu, zapa-
miętuje na stosie adres następnej instrukcji, która miałabyć wykonana i wykonuje instrukcję skoku, która
znajduje się pod adresem danego wektora przerwania. Każda procedura obsługująca przerwanie musi
być zakończona instrukcją RETI. Po jej wykonaniu mikrokontroler przywraca ze stosu adres instrukcji,
która nie została wykonana z powodu wystąpienia przerwania, a następnie kontynuuje począwszy od
niej wykonywanie głównego programu.
Kod służący do inicjalizacji wskaźnika stosu, czyli rejestru SP (Stack Pointer) zawierającego adres koń-
ca pamięci SRAM mikrokontrolera, nie jest potrzebny dla ATmega48/88/168/328. W tych modelach war-
tość wskaźnika stosu jest ustawiana automatycznie, zaraz po włączeniu/resecie mikrokontrolera.
;Konfiguracja mikrokontrolera
          ldi     R16,$01   ;częstotliwość pracy mikrokontrolera (CLK): 1-20 [MHz]
          mov     R15,R16
          ldi     R16,$EF   ;zatrzymanie zbędnych modułów (ADC, TWI, SPI, USART, TC0, TC1, TC2)
          sts     $64,R16   ;zapis rejestru PRR
          ldi     R16,$80   ;wyłączenie zasilania komparatora analogowego
          sts     $50,R16   ;zapis rejestru ACSR
Zapisywanie do rejestru R15 wartości, która odpowiada częstotliwości taktowania mikrokontrolera. Mo-
dele ATmega48/88/168/328 mogą pracować z częstotliwością 1-20 MHz, której odpowiada wartość $01-
$14. W tym przypadku mikrokontroler pracuje z zegarem 1 MHz, dlatego do rejestru R15 jest wpisywana
wartość $01. Jeśli mikrokontroler byłby taktowany inną częstotliwością, to należy wpisać jej właściwą
wartość. Jest to niezbędne do generowania prawidłowych opóźnień, podczas komunikacji mikrokontro-
lera z wyświetlaczem LCD.
Zostaje też zatrzymane taktowanie nieużywanych modułów mikrokontrolera: przetwornika analogowo-
cyfrowego (ADC), interfejsów do komunikacji szeregowej (TWI, SPI, USART) oraz liczników (TC0, TC1,
TC2), aby zmniejszyć pobór prądu. Dzieje się to po zapisaniu wartości $EF w rejestrze PRR (Power
Reduction Register). Dodatkowo jest również wyłączany komparator analogowy, po zapisaniu wartości
$80 w rejestrze ACSR (Analog Comparator control and Status Register).
;Obsługa wyświetlacza LCD 2x16 znaków (HD44780), z wyprowadzeniami podłączonymi w następujący sposób:
;R/W=GND, E=PD7, RS=PD6, D7=PD3, D6=PD2, D5=PD1, D4=PD0, D0-D3=NC (nie podłączone).
;Po włączeniu zasilania wyświetlacz LCD: zostaje wyczyszczony (kursor na pozycji 1 w 1 linii), działa
;w trybie 8-bitowym, może wyświetlać 1 linię znaków o rozmiarze 5x8 pikseli, wyświetlacz, kursor
;i jego miganie są wyłączone, kursor porusza się do przodu, przesuwanie (shift) jest wyłączone.
          rcall   LCDi      ;inicjalizacja LCD (włączenie trybu 4-bitowego)
          ldi     R18,$28   ;ustawienie opcji: 2 linie, znaki 5x8 pikseli, interfejs 4-bit
          rcall   LCDwi     ;zapis instrukcji do LCD
          ldi     R18,$0C   ;włączenie LCD bez kursora
          rcall   LCDwi     ;zapis instrukcji do LCD
;Instrukcje wyświetlacza LCD [czas ich wykonywania]:
; $01 - czyszczenie LCD i powrót kursora na pozycję 1 w 1 linii [1.52 ms]
; $02 - ustawienie kursora na pozycji 1 w 1 linii [1.52 ms]
; $04 - poruszanie kursora do tył, przesuwanie (shift) wyłączone [37 us]
; $05 - poruszanie kursora do tył, przesuwanie (shift) włączone [37 us]
; $06 - poruszanie kursora do przodu, przesuwanie (shift) wyłączone [37 us]
; $07 - poruszanie kursora do przodu, przesuwanie (shift) włączone [37 us]
; $08 - wyłączenie LCD [37 us]
; $0C - włączenie LCD bez kursora [37 us]
; $0E - włączenie LCD z kursorem [37 us]
; $0F - włączenie LCD z migającym kursorem [37 us]
; $10 - przesunięcie kursora w lewo [37 us]
; $14 - przesunięcie kursora w prawo [37 us]
; $18 - przesunięcie ekranu w lewo [37 us]
; $1C - przesunięcie ekranu w prawo [37 us]
; $80 - ustawienie adresu $00 dla DDRAM (kursor na pozycji 1 w 1 linii) [37 us]
; $C0 - ustawienie adresu $40 dla DDRAM (kursor na pozycji 1 w 2 linii) [37 us]
Inicjalizacja, ustawienie parametrów pracy i włączenie wyświetlacza LCD. Ponadto spis podstawowych
instrukcji, które są obsługiwane przez wyświetlacz. Poniższa tabela przedstawia rozmieszczenie znaków
na ekranie oraz ich adresy w pamięci DDRAM wyświetlacza LCD.

NUMER
LINII
NUMER ZNAKU (POZYCJA KURSORA)
12345678910111213141516
1$00$01$02$03$04$05$06$07$08$09$0A$0B$0C$0D$0E$0F
2$40$41$42$43$44$45$46$47$48$49$4A$4B$4C$4D$4E$4F
;Obsługa zworki J1
          sbi     PORTC,$05 ;ustawienie pinu PC5 jako wejścia z wysokim stanem (pull-up)
          in      R16,PINC  ;odczyt stanu pinów PC0-PC6
          andi    R16,$20
          brne    Boot1     ;skok, jeśli na pinie PC5 jest stan wysoki (zworka J1=OFF)
;Zworka J1=ON (zwarta)
          rcall   LCDclr    ;czyszczenie LCD
          rcall   FuseBit   ;odczyt i wyświetlenie na LCD wartości fuse/lock bitów
Boot0:    in      R16,PINC  ;odczyt stanu pinów PC0-PC6
          andi    R16,$20
          breq    Boot0     ;skok, jeśli na pinie PC5 jest stan niski (zworka J1=ON)
;Zworka J1=OFF (rozwarta)
Boot1:    rcall   LCDclr    ;czyszczenie LCD
          ldi     ZL,LOW(Text1<<1)
          ldi     ZH,HIGH(Text1<<1)
          rcall   LCDwf     ;wyświetlenie na LCD ciągu ASCIIZ z pamięci FLASH
          ldi     R18,$C0   ;ustawienie kursora na pozycji 1 w 2 linii
          rcall   LCDwi     ;zapis instrukcji do LCD
          rcall   LCDwf     ;wyświetlenie na LCD ciągu ASCIIZ z pamięci FLASH
Pin nr 28 (PC5) mikrokontrolera jest konfigurowany jako wejście, podłączone (podciągnięte) do plusa
napięcia zasilania przez wewnętrzny rezystor (pull-up). Jeśli po włączeniu/resecie systemu zworka J1
jest rozwarta (OFF), to na pinie PC5 panuje stan wysoki i program przechodzi do wyświetlania zwykłych
napisów testu wyświetlacza (rysunek 2). Natomiast jeśli zworka J1 będzie wtedy zwarta (ON), to na pinie
PC5 będzie stan niski i program wyświetli szesnastkowe wartości fuse/lock bitów, aktualnie ustawionych
w mikrokontrolerze (rysunek 1). Będą one pokazane w formacie "FL:LB:FE:FH". Pierwsza wartość (FL)
to młodszy bajt fuse bitów, druga to lock bity, trzecia to dodatkowe fuse bity, a czwarta to starszy bajt
fuse bitów. Wartości te będą widoczne aż do momentu rozwarcia (OFF) zworki J1. Wtedy zostaną już
wyświetlone zwykłe napisy testu wyświetlacza.


Rysunek 1

Rysunek 2
;Zapisywanie 8 znaków użytkownika do pamięci CGRAM wyświetlacza LCD
          ldi     ZL,LOW(Chars<<1)
          ldi     ZH,HIGH(Chars<<1) ;adres danych opisujących budowę 8 znaków użytkownika [64-bajty]
          ldi     R18,$40   ;ustawienie adresu $00 dla CGRAM (pierwszy znak)
          rcall   LCDwi     ;zapis instrukcji do LCD
          ldi     R22,$40
LCDwfch:  lpm     R18,Z+
          rcall   LCDwc     ;zapis znaku do LCD
          dec     R22
          brne    LCDwfch
;Wyświetlanie znaku użytkownika na LCD
          ldi     R18,$CF   ;ustawienie kursora na pozycji 16 w 2 linii
          rcall   LCDwi     ;zapis instrukcji do LCD
          ldi     R18,$05   ;znak użytkownika nr 5
          rcall   LCDwc     ;wyświetlenie znaku na LCD
Zapis 8 znaków użytkownika (nr 0-7), o wymiarach 5x8 pikseli do pamięci CGRAM wyświetlacza LCD,
a następnie wyświetlenie znaku nr 5 na ostatniej pozycji w 2 linii. Po wykonaniu tego kodu zawartość
ekranu wyświetlacza LCD powinna wyglądać tak, jak na rysunku 3.
Każdy znak jest opisany za pomocą 8-bajtów, w których tylko młodszych 5-bitów opisuje mapę bitową
znaku, a najstarsze 3-bity nie są używane (wartość 0). Tak więc do opisania 8 znaków użytkownika są
potrzebne 64-bajty, które znajdują się pod etykietą "Chars" w pamięci FLASH mikrokontrolera.
Sposób zapisu mapy bitowej przykładowego znaku użytkownika nr 5, przedstawia poniższa tabela.
Zwykle ostatnią (dolną) linię znaku pozostawia się pustą, bo na niej może być wyświetlany migający
kursor, jeśli jest włączony.

ZNAKBAJTY MAPY BITOWEJ
DWÓJKOWODZIESIĘTNIE
00010001
00001110
00001000
00001110
00000010
00001110
00010001
00000000
17
14
8
14
2
14
17
0

Rysunek 3
;Inicjalizacja rejestrów
          clr     XL
          clr     XH
          movw    R22,XL
          movw    R24,XL
          inc     XL
Ustawienie początkowych wartości rejestrów, które będą używane w głównej pętli programu.
;Główna pętla programu
Loop:     rcall   Add3216       ;dodawanie słowa do wartości 32-bitowej
          ldi     ZL,LOW(sbuf)
          ldi     ZH,HIGH(sbuf) ;adres bufora wyjściowego w pamięci SRAM
          mov     R16,R22       ;najstarszy bajt 32-bitowej wartości
          rcall   ConBH         ;konwersja bajtu do systemu HEX-ASCIIZ
          mov     R16,R23
          rcall   ConBH         ;konwersja bajtu do systemu HEX-ASCIIZ
          mov     R16,R24
          rcall   ConBH         ;konwersja bajtu do systemu HEX-ASCIIZ
          mov     R16,R25       ;najmłodszy bajt 32-bitowej wartości
          rcall   ConBH         ;konwersja bajtu do systemu HEX-ASCIIZ
          sbiw    ZL,$08
          ldi     R18,$C6       ;ustawienie kursora na pozycji 7 w 2 linii
          rcall   LCDwi         ;zapis instrukcji do LCD
          rcall   LCDws         ;wyświetlenie na LCD ciągu ASCIIZ z pamięci SRAM
          rjmp    Loop
Główna pętla programu, która jest wykonywana bez przerwy. Z każdym obiegiem pętli, 32-bitowa war-
tość zapisana w rejestrach [R22:R23:R24:R25] jest zwiększana o 16-bitową wartość $0001 z rejestrów
[XH:XL]. Następnie każdy bajt tej zwiększonej wartości jest zamieniany na szesnastkowy ciąg ASCIIZ
i zapisywany do bufora w pamięci SRAM mikrokontrolera. Uzyskany w ten sposób 8-znakowy ciąg
ASCIIZ jest wyświetlany, począwszy od pozycji nr 7 w 2 linii. Instrukcja skoku RJMP zamyka pętlę.
Dzięki temu podczas pracy programu na wyświetlaczu LCD można zaobserwować, szybko zwiększającą
się 32-bitową wartość szesnastkową. Przykładowa zawartość ekranu wyświetlacza LCD, podczas wyko-
nywania głównej pętli programu jest przedstawiona na rysunku 4.


Rysunek 4
;Dodawanie słowa do wartości 32-bitowej: [R22:R23:R24:R25] + [XH:XL] = [R22:R23:R24:R25]
Add3216:  clr     R2
          add     R25,XL  ;najmłodszy bajt wartości/wyniku, młodszy bajt słowa
          adc     R24,XH  ;starszy bajt słowa
          adc     R23,R2
          adc     R22,R2  ;najstarszy bajt wartości/wyniku
          ret             ;C=1 jeśli wynik >4294967295
Procedura dodaje 16-bitowe słowo z rejestrów [XH:XL] do 32-bitowej wartości, zapisanej w rejestrach
[R22:R23:R24:R25].
;Zapis szesnastkowej wartości bajtu do pamięci SRAM w postaci ciągu ASCIIZ: "NN"
ConBH:    ldi     R17,$02
ConBH0:   swap    R16     ;bajt do konwersji
          mov     R18,R16
          andi    R18,$0F
          subi    R18,$D0
          cpi     R18,$3A
          brlt    ConBH1
          subi    R18,$F9 ;subi R18,$D9 - małe litery (a-f)
ConBH1:   st      Z+,R18
          dec     R17
          brne    ConBH0
          st      Z,R17   ;wyjście danych
          ret
Procedura konwertuje bajt z rejestru R16 do postaci szesnastkowego 2-znakowego ciągu ASCIIZ,
zapisanego do bufora w pamięci SRAM mikrokontrolera.
;Zapis szesnastkowych wartości fuse/lock bitów do pamięci SRAM w postaci
;ciągu ASCIIZ: "FL:LB:FE:FH" i wyświetlenie go na wyświetlaczu LCD.
FuseBit:  ldi     YL,LOW(sbuf)
          ldi     YH,HIGH(sbuf) ;adres wyjścia danych
          clr     ZL
          clr     ZH
          ldi     R18,$3A
FuseBit0: lds     R16,$57       ;odczyt rejestru SPMCSR
          ori     R16,$09       ;ustawienie bitów BLBSET i SELFPRGEN
          sts     $57,R16       ;zapis rejestru SPMCSR
          lpm     R2,Z+         ;odczyt fuse/lock bitów
;W zależności od wartości adresu [ZH:ZL] są odczytywane:
; $0000 - Fuse bits Low byte (FL)
; $0001 - Lock Bits (LB)
; $0002 - Fuse bits Extended byte (FE)
; $0003 - Fuse bits High byte (FH)
          ldi     R16,$02
FuseBit1: swap    R2
          mov     R17,R2
          andi    R17,$0F
          subi    R17,$D0
          cpi     R17,$3A
          brlt    FuseBit2
          subi    R17,$F9
FuseBit2: st      Y+,R17
          dec     R16
          brne    FuseBit1
          st      Y+,R18
          cpi     ZL,$04
          brne    FuseBit0
          st      -Y,R16
;Wyświetlanie wartości fuse/lock bitów na LCD
          ldi     ZL,LOW(Text0<<1)
          ldi     ZH,HIGH(Text0<<1)
          rcall   LCDwf         ;wyświetlenie na LCD ciągu ASCIIZ z pamięci FLASH
          ldi     R18,$C0       ;ustawienie kursora na pozycji 1 w 2 linii
          rcall   LCDwi         ;zapis instrukcji do LCD
          ldi     ZL,LOW(sbuf)
          ldi     ZH,HIGH(sbuf)
          rcall   LCDws         ;wyświetlenie na LCD ciągu ASCIIZ z pamięci SRAM
          ret
Procedura odczytuje aktualnie ustawione wartości fuse/lock bitów mikrokontrolera, zapisuje je do
pamięci SRAM w postaci 11-znakowego ciągu ASCIIZ, zawierającego cztery szesnastkowe wartości
oddzielone dwukropkami, a następnie wyświetla ten ciąg na LCD.
;Inicjalizacja wyświetlacza LCD
LCDi:     ldi     R16,$CF
          out     DDRD,R16  ;ustawienie pinów PD0-PD3/PD6-PD7 jako wyjść, PD4-PD5 jako wejść
          clr     R16       ;E=0, RS=0
          out     PORTD,R16 ;wyzerowanie wyjść PD0-PD3/PD6-PD7
;Czekanie na ustabilizowanie się napięcia zasilania
          ldi     R16,$C8   ;opóźnienie 20 ms
          rcall   Wait      ;pętla opóźniająca
          rcall   Wait      ;pętla opóźniająca
;Wysłanie trzech instrukcji $30 do LCD
          ldi     R18,$03
LCDi0:    ldi     R16,$83
          out     PORTD,R16 ;E=1, RS=0
          ;(wait 4 CL)
          cbi     PORTD,$07 ;E=0, RS=0
          ldi     R16,$32   ;opóźnienie 5 ms
          rcall   Wait      ;pętla opóźniająca
          dec     R18
          brne    LCDi0
;Wysłanie instrukcji $20 do LCD (włączenie trybu 4-bitowego)
          ldi     R16,$82
          out     PORTD,R16 ;E=1, RS=0
          ;(wait 4 CL)
          cbi     PORTD,$07 ;E=0, RS=0
          ldi     R16,$0A   ;opóźnienie 1 ms
          rcall   Wait      ;pętla opóźniająca
          ret
Procedura konfiguruje port D mikrokontrolera, inicjalizuje wyświetlacz LCD i włącza jego 4-bitowy tryb
komunikacji. W miejscach, w których znajduje się komentarz ";(wait 4 CL)" powinny być umieszczone
instrukcje, powodujące opóźnienie o 4 cykle zegara taktującego mikrokontroler (np. 4 instrukcje NOP).
W praktyce okazały się one zbędne, przynajmniej w przypadku mojego wyświetlacza.
;Zapis instrukcji do wyświetlacza LCD (tryb 4-bitowy)
LCDwi:    ldi     R20,$80   ;E=1, RS=0
          rjmp    LCDwc0
;Zapis znaku do wyświetlacza LCD (tryb 4-bitowy)
LCDwc:    ldi     R20,$C0   ;E=1, RS=1
LCDwc0:   ldi     R21,$02
LCDwc1:   swap    R18       ;bajt do wysłania
          mov     R19,R18
          andi    R19,$0F
          andi    R20,$F0   ;E=1, RS=początkowa wartość
          add     R20,R19
          out     PORTD,R20 ;wysłanie 4-bitów do LCD (najpierw starsze, później młodsze)
          ;(wait 4 CL)
          cbi     PORTD,$07 ;E=0, RS=początkowa wartość
          ldi     R16,$01   ;opóźnienie 100 us
          rcall   Wait      ;pętla opóźniająca
          dec     R21
          brne    LCDwc1
          ret
Procedura w zależności od wywołania, zapisuje instrukcję lub znak do wyświetlacza LCD. W miejscu,
w którym znajduje się komentarz ";(wait 4 CL)" powinny być umieszczone instrukcje, powodujące opóź-
nienie o 4 cykle zegara taktującego mikrokontroler (np. 4 instrukcje NOP). W praktyce okazały się one
zbędne, przynajmniej w przypadku mojego wyświetlacza.
;Czyszczenie wyświetlacza LCD i powrót kursora na pozycję 1 w 1 linii
LCDclr:   ldi     R18,$01 ;czyszczenie LCD i powrót kursora
          rcall   LCDwi   ;zapis instrukcji do LCD
          ldi     R16,$10 ;opóźnienie 1.6 ms
          rcall   Wait    ;pętla opóźniająca
          ret
Procedura zapisuje instrukcję $01 do wyświetlacza LCD, która powoduje wyczyszczenie zawartości jego
ekranu i powrót kursora na pierwszą pozycję w 1 linii. Następnie procedura czeka przez ok. 1.5 ms czyli
tyle czasu, ile sterownik wyświetlacza potrzebuje na wykonanie tej instrukcji.
;Zapis ciągu ASCIIZ z pamięci SRAM do wyświetlacza LCD (tryb 4-bitowy)
LCDws0:   rcall   LCDwc   ;zapis znaku do LCD
LCDws:    ld      R18,Z+  ;odczyt znaku z pamięci SRAM
          tst     R18
          brne    LCDws0
          ret
Procedura wyświetla na LCD ciąg ASCIIZ, zapisany w pamięci SRAM mikrokontrolera.
;Zapis ciągu ASCIIZ z pamięci FLASH do wyświetlacza LCD (tryb 4-bitowy)
LCDwf0:   rcall   LCDwc   ;zapis znaku do LCD
LCDwf:    lpm     R18,Z+  ;odczyt znaku z pamięci FLASH
          tst     R18
          brne    LCDwf0
          ret
Procedura wyświetla na LCD ciąg ASCIIZ, zapisany w pamięci FLASH mikrokontrolera.
;Pętla opóźniająca o zadany czas: 100u - 25.5m [s]
Wait:     mov     R2,R15  ;R15 (CLK) = częstotliwość pracy mikrokontrolera: $01-$14 (1-20 MHz)
          ldi     R17,$19
          mul     R16,R17 ;R16 (DLY) = wartość opóźnienia: $01-$FF (100us-25.5ms)
Wait0:    movw    YL,R0
Wait1:    sbiw    YL,$01
          brne    Wait1
          dec     R2
          brne    Wait0
          ret
Procedura generuje opóźnienie czasowe (100us - 25.5ms), zależne od wartości rejestru R16. Wygene-
rowane opóźnienie będzie miało prawidłowy czas tylko wtedy, gdy rejestr R15 będzie zawierał wartość
częstotliwości, z jaką aktualnie pracuje mikrokontroler.
;Napisy dla wyświetlacza LCD
Text0:    .db     "Fuse/lock bits:",0
Text1:    .db     "*** LCD test ***",0,"Count:",0
Napisy pokazywane na wyświetlaczu LCD, umieszczone w pamięci FLASH mikrokontrolera.
;Znaki użytkownika dla wyświetlacza LCD
Chars:    .db     17,14,10,10,10,14,17,0 ;0
          .db     17,4,12,4,4,14,17,0    ;1
          .db     17,14,2,14,8,14,17,0   ;2
          .db     17,14,2,6,2,14,17,0    ;3
          .db     17,10,10,14,2,2,17,0   ;4
          .db     17,14,8,14,2,14,17,0   ;5
          .db     17,14,8,14,10,14,17,0  ;6
          .db     17,14,10,2,2,2,17,0    ;7
Tablica umieszczona w pamięci FLASH mikrokontrolera, zawierająca 64-bajty opisujące mapy bitowe
8 znaków użytkownika, które są zapisywane do pamięci CGRAM wyświetlacza LCD.
.dseg                ;pamięć danych (SRAM)
.org      $0100
sbuf:     .byte   12 ;rezerwuje N-bajtów w pamięci SRAM
Dyrektywa ".dseg" oznacza blok pamięci danych (SRAM) mikrokontrolera, a ".org $0100" określa adres
jej początku. Następnie znajduje się etykieta z nazwą bufora dla danych wyjściowych/wejściowych oraz
opcjonalna dyrektywa ".byte", która mówi kompilatorowi ile bajtów pamięci SRAM w tym buforze, będzie
używanych (zapisywanych) przez program.

Poniższy rysunek przedstawia budowę dużych cyfr (3x2 znaki), które mogą być pokazane na standar-
dowym wyświetlaczu LCD 2x16. Cyfry te składają się maksymalnie z 8 znaków użytkownika oraz pus-
tego znaku spacji ($20). Dwie pierwsze czcionki wykorzystują też całkowicie zapełniony znak ($FF),
który może nie być dostępny w każdym wyświetlaczu. Pod czcionkami cyfr znajdują się różne znaki
użytkownika, np. stosowane do wyświetlania bargrafów, czy sygnalizowania stanu naładowania baterii
urządzenia. Program do generowania dużych cyfr (3x2 znaki) jest opisany na tej stronie.