Asembler mikrokontrolerów AVR     Wyświetlacz LCD - duże cyfry         Kompilacja




Powyższy schemat przedstawia sposób podłączenia alfanumerycznego wyświetlacza LCD (ze ste-
rownikiem HD44780 lub kompatybilnym), który może wyświetlić 2 linie tekstu po 16 znaków (2x16).
Dokładny opis tego schematu, większości fragmentów kodu programu oraz prawidłowej konfiguracji
mikrokontrolera, znajduje się na tej stronie.

Poniżej znajduje się kodu programu przeznaczonego dla mikrokontrolerów serii ATmega48/88/168/328,
który inicjalizuje wyświetlacz LCD i pokazuje na nim duże cyfry o rozmiarze 3x2 znaki.
.include  "m48def.inc"    ;definicje dla ATmega48
;.include  "m88def.inc"    ;definicje dla ATmega88
;.include  "m168def.inc"   ;definicje dla ATmega168
;.include  "m328def.inc"   ;definicje dla ATmega328

.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

;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

;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]

;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
Jeśli po włączeniu/resecie systemu zworka J1 jest zwarta (ON), to na pinie PC5 panuje stan niski i pro-
gram wyświetli na LCD szesnastkowe wartości fuse/lock bitów, aktualnie ustawionych w mikrokontro-
lerze (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 nastąpi wykonywanie
dalszego kodu programu.


Rysunek 1
;Zapisywanie 8 znaków użytkownika do pamięci CGRAM wyświetlacza LCD
          ldi     ZL,LOW(Chrs3<<1)  ;wybór kroju dużych cyfr (3x2 znaki): Chrs0/Chrs1/Chrs2/Chrs3
          ldi     ZH,HIGH(Chrs3<<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
Zapis 8 znaków użytkownika (nr 0-7), o wymiarach 5x8 pikseli do pamięci CGRAM wyświetlacza LCD,
które służą do budowy dużych cyfr o rozmiarze 3x2 znaki. Do wyboru jest 4 zestawy znaków użytko-
wnika, od których zależy krój (wygląd) dużych cyfr. Aby zmienić krój dużych cyfr, należy zmienić etykiety
"Chrs3" na "Chrs0", "Chrs1" lub "Chrs2". Poniżej (rysunek 2) jest przedstawiony wygląd wszystkich
krojów dużych cyfr (począwszy od "Chrs0" do "Chrs3"):


Rysunek 2


Duże cyfry składają się maksymalnie z 8 znaków użytkownika oraz pustego znaku spacji ($20). Dwa
pierwsze kroje ("Chrs0" i "Chrs1"), używają też całkowicie zapełnionego znaku ($FF), który może nie być
dostępny w każdym wyświetlaczu. Duże cyfry "Chrs0" i "Chrs1", powinny być wyświetlane obok siebie
z przerwą (spacją) o szerokości 1 znaku, aby zwiększyć ich czytelność.
;Inicjalizacja rejestrów
          ser     XL
          ser     XH
          movw    R4,ZL     ;adres danych opisujących budowę dużych cyfr (3x2 znaki) [60-bajtów]

;Główna pętla programu
Loop:     adiw    XL,$01    ;zwiększenie 16-bitowej wartości o 1
          movw    R0,XL
          rcall   Con16D    ;konwersja 16-bitowej wartości do postaci dziesiętnej
          movw    XL,R0
          ldi     R23,$80   ;adres znaku 1 w 1 linii LCD
          ldi     R22,$04
Loop0:    ld      R16,Y+    ;odczyt kolejnej cyfry ($00-$09) z pamięci SRAM
          ldi     R17,$06
          mul     R16,R17   ;mnożenie odczytanej cyfry przez 6
          movw    ZL,R4
          add     ZL,R0
          adc     ZH,R1     ;dodanie wyniku mnożenia do wartości adresu, wskazującego na początek struktury
                            ;w pamięci FLASH, która opisuje budowę poszczególnych cyfr o rozmiarze 3x2 znaki.
          mov     R18,R23   ;ustawienie kursora na początku kolejnej cyfry w 1 linii LCD
          rcall   LCDwi     ;zapis instrukcji do LCD
          lpm     R18,Z+    ;odczyt znaku z pamięci FLASH
          rcall   LCDwc     ;zapis znaku do LCD
          lpm     R18,Z+    ;odczyt znaku z pamięci FLASH
          rcall   LCDwc     ;zapis znaku do LCD
          lpm     R18,Z+    ;odczyt znaku z pamięci FLASH
          rcall   LCDwc     ;zapis znaku do LCD
          mov     R18,R23
          ori     R18,$40   ;ustawienie kursora na początku kolejnej cyfry w 2 linii LCD
          rcall   LCDwi     ;zapis instrukcji do LCD
          lpm     R18,Z+    ;odczyt znaku z pamięci FLASH
          rcall   LCDwc     ;zapis znaku do LCD
          lpm     R18,Z+    ;odczyt znaku z pamięci FLASH
          rcall   LCDwc     ;zapis znaku do LCD
          lpm     R18,Z+    ;odczyt znaku z pamięci FLASH
          rcall   LCDwc     ;zapis znaku do LCD
          subi    R23,$FC   ;zwiększenie adresu o 4 - przerwa między cyframi
          ;subi    R23,$FD   ;zwiększenie adresu o 3 - brak przerwy między cyframi
          dec     R22
          brne    Loop0
          ldi     R16,$FF   ;opóźnienie 25.5 ms
          rcall   Wait      ;pętla opóźniająca
          rcall   Wait      ;pętla opóźniająca
          rcall   Wait      ;pętla opóźniająca
          rcall   Wait      ;pętla opóźniająca
          rjmp    Loop
Ustawienie początkowych wartości rejestrów i główna pętla programu, która jest wykonywana bez
przerwy. Z każdym obiegiem pętli, 16-bitowa wartość zapisana w rejestrach [XH:XL] jest zwiększana
o 1. Następnie zwiększona wartość jest zamieniana na 4-bajtowy ciąg dziesiętny (każdy bajt to jedna
cyfra $00-$09), zapisywany do bufora w pamięci SRAM mikrokontrolera. Tak uzyskany ciąg jest
wyświetlany w postaci dużych cyfr o rozmiarze 3x2 znaki.
Instrukcja "subi R23,$FC" zwiększając adres o 4 powoduje, że między kolejnymi cyframi znajduje się
przerwa (spacja) o szerokości 1 znaku. Aby wyświetlać cyfry bez tej przerwy, należy użyć instrukcji
"subi R23,$FD", która zwiększa adres o 3.
Przed końcem pętli znajduje się kod generujący opóźnienie ok. 102ms, aby spowolnić wyświetlanie
nowych wartości na LCD. Instrukcja skoku RJMP zamyka pętlę.
Dzięki temu podczas pracy programu na wyświetlaczu LCD można zaobserwować, szybko zwiększającą
się 4-cyfrową wartość dziesiętną. Przykładowa zawartość ekranu wyświetlacza LCD, podczas wykony-
wania głównej pętli programu jest przedstawiona poniżej: duże cyfry "Chrs3" z przerwą na rysunku 3
oraz bez przerwy na rysunku 4.


Rysunek 3

Rysunek 4
;Konwersja 16-bitów do systemu dziesiętnego: [XH:XL] => [B4:B3:B2:B1], gdzie:
;[B1] - bajt z najmniej znaczącą cyfrą wyniku (B1-B4 = $00-$09).
Con16D:   ldi     YL,LOW(sbuf)
          ldi     YH,HIGH(sbuf) ;adres wyjścia danych
          adiw    YL,$04
          ldi     R17,$04       ;maksymalna liczba cyfr
Con16D0:  ldi     R18,$10       ;liczba bitów do konwersji
          sub     R16,R16
Con16D1:  rol     R16
          cpi     R16,$0A
          brcs    Con16D2
          subi    R16,$0A
          sec
          rjmp    Con16D3
Con16D2:  clc
Con16D3:  rol     XL            ;młodszy bajt do konwersji
          rol     XH            ;starszy bajt do konwersji
          dec     R18
          brpl    Con16D1
          st      -Y,R16
          dec     R17
          brne    Con16D0
          ret
Procedura konwertuje 16-bitowe słowo z rejestrów [XH:XL] do postaci 4-bajtowego ciągu dziesiętnego
(każdy bajt to jedna cyfra $00-$09), 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(Text<<1)
          ldi     ZH,HIGH(Text<<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

;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

;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

;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

;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

;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

;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    R24,R0
Wait1:    sbiw    R24,$01
          brne    Wait1
          dec     R2
          brne    Wait0
          ret

;Napisy dla wyświetlacza LCD
Text:     .db     "Fuse/lock bits:",0

;Znaki użytkownika dla wyświetlacza LCD, służące do wyświetlania dużych cyfr o rozmiarze 3x2 znaki

;Duże cyfry proste z przerwą (4 znaki użytkownika + całkowicie zapełniony znak $FF)
Chrs0:    .db     31,31,31,0,0,0,0,0   ;0
          .db     0,0,0,0,0,31,31,31   ;1
          .db     31,31,31,0,0,0,31,31 ;2
          .db     31,0,0,0,0,31,31,31  ;3
          .db     0,0,0,0,0,0,0,0      ;4
          .db     0,0,0,0,0,0,0,0      ;5
          .db     0,0,0,0,0,0,0,0      ;6
          .db     0,0,0,0,0,0,0,0      ;7
;Budowa poszczególnych cyfr
          .db     255,0,255,255,1,255  ;0
          .db     0,255,32,1,255,1     ;1
          .db     2,2,255,255,3,3      ;2
          .db     0,2,255,1,3,255      ;3
          .db     255,1,255,32,32,255  ;4
          .db     255,2,2,3,3,255      ;5
          .db     255,2,2,255,3,255    ;6
          .db     255,0,255,32,32,255  ;7
          .db     255,2,255,255,3,255  ;8
          .db     255,2,255,3,3,255    ;9

;Duże cyfry zaokrąglone z przerwą (8 znaków użytkownika + całkowicie zapełniony znak $FF)
Chrs1:    .db     31,31,31,0,0,0,0,0      ;0
          .db     0,0,0,0,0,31,31,31      ;1
          .db     31,31,31,0,0,0,31,31    ;2
          .db     31,0,0,0,0,31,31,31     ;3
          .db     7,15,31,31,31,31,31,31  ;4
          .db     28,30,31,31,31,31,31,31 ;5
          .db     31,31,31,31,31,31,15,7  ;6
          .db     31,31,31,31,31,31,30,28 ;7
;Budowa poszczególnych cyfr
          .db     4,0,5,6,1,7             ;0
          .db     0,5,32,1,255,1          ;1
          .db     2,2,5,255,3,3           ;2
          .db     0,2,5,1,3,7             ;3
          .db     6,1,255,32,32,255       ;4
          .db     255,2,2,3,3,7           ;5
          .db     4,2,2,6,3,7             ;6
          .db     4,0,5,32,32,255         ;7
          .db     4,2,5,6,3,7             ;8
          .db     4,2,5,3,3,7             ;9

;Duże cyfry proste bez przerwy (8 znaków użytkownika)
Chrs2:    .db     30,30,30,30,30,30,30,30 ;0
          .db     15,15,15,15,15,15,15,15 ;1
          .db     31,31,0,0,0,0,0,0       ;2
          .db     0,0,0,0,0,0,31,31       ;3
          .db     0,0,0,0,0,0,15,15       ;4
          .db     31,31,0,0,0,0,31,31     ;5
          .db     30,30,0,0,0,0,30,30     ;6
          .db     15,15,0,0,0,0,15,15     ;7
;Budowa poszczególnych cyfr
          .db     1,2,0,1,3,0             ;0
          .db     32,1,32,32,1,32         ;1
          .db     7,5,0,1,3,3             ;2
          .db     7,5,0,4,3,0             ;3
          .db     1,3,0,32,32,0           ;4
          .db     1,5,6,4,3,0             ;5
          .db     1,5,6,1,3,0             ;6
          .db     1,2,0,32,32,0           ;7
          .db     1,5,0,1,3,0             ;8
          .db     1,5,0,4,3,0             ;9

;Duże cyfry zaokrąglone bez przerwy (8 znaków użytkownika)
Chrs3:    .db     28,30,30,30,30,30,30,28 ;0
          .db     7,15,15,15,15,15,15,7   ;1
          .db     31,31,0,0,0,0,0,0       ;2
          .db     0,0,0,0,0,0,31,31       ;3
          .db     0,0,0,0,0,0,3,7         ;4
          .db     31,31,0,0,0,0,31,31     ;5
          .db     28,24,0,0,0,0,24,28     ;6
          .db     7,3,0,0,0,0,3,7         ;7
;Budowa poszczególnych cyfr
          .db     1,2,0,1,3,0             ;0
          .db     32,1,32,32,1,32         ;1
          .db     7,5,0,1,3,3             ;2
          .db     7,5,0,4,3,0             ;3
          .db     1,3,0,32,32,0           ;4
          .db     1,5,6,4,3,0             ;5
          .db     1,5,6,1,3,0             ;6
          .db     1,2,0,32,32,0           ;7
          .db     1,5,0,1,3,0             ;8
          .db     1,5,0,4,3,0             ;9

.dseg                ;pamięć danych (SRAM)
.org      $0100
sbuf:     .byte   12 ;rezerwuje N-bajtów w pamięci SRAM