Asembler mikrokontrolerów AVR     Obsługa przetwornika ADC         Kompilacja




Powyższy schemat przedstawia prawidłowy sposób podłączenia zewnętrznych elementów, podczas
korzystania z przetwornika analogowo-cyfrowego ADC (Analog to Digital Converter) wbudowanego
w mikrokontroler ATmega48/88/168/328. Elementy te znacznie zmniejszają zakłócenia oraz ich
negatywny wpływ na pracę przetwornika ADC.
Cewka L1 i kondensator C2 tworzy filtr, który eliminuje zakłócenia w napięciu zasilającym część ana-
logową mikrokontrolera (pin AVCC). Kondensator C3 filtruje napięcie odniesienia Vref dla przetwornika
ADC (pin AREF). Rezystor R2 jest opcjonalny - wymusza poziom masy (GND) na wejściu pomiarowym
PC0/ADC0 (Vin). Bez tego rezystora i przy niepodłączonym pinie PC0/ADC0, pojawiałyby się na nim
przypadkowe wartości napięcia.
Na schemacie znajduje się również alfanumeryczny wyświetlacz LCD (sterownik HD44780 lub kom-
patybilny), który służy do prezentacji wyników pomiarów. Dokładny opis jego podłączenia, fragmentów
kodu programu do jego obsługi, pozostałych elementów elektronicznych oraz prawidłowej konfiguracji
mikrokontrolera (fuse/lock bity), znajduje się na tej stronie.

Wstęp
Obecnie każdy mikrokontroler posiada wbudowany przetwornik ADC, który zamienia analogową wartość napięcia sta-
łego Vin na odpowiadającą mu wartość cyfrową (binarną/dwójkową). Mikrokontroler ATmega48/88/168/328 posiada
wielokanałowy przetwornik 10-bitowy, co oznacza że może on mierzyć napięcie Vin z kilku wybieranych (multiplekso-
wanych) wejść/źródeł, które jest zamieniane na 10-bitową wartość Aval=$0000-$03FF (0-1023 dziesiętnie).
Od wartości napięcia odniesienia Vref dla przetwornika ADC zależy maksymalne napięcie Vin, które może być zmie-
rzone oraz rozdzielczość pomiaru wynosząca: Mres=Vref/1024 [V]. Jeśli mierzone napięcie Vin będzie wyższe od
napięcia Vref, to wynik konwersji będzie zbliżony do wartości maksymalnej, czyli $03FF. Napięcie Vref nie może być
niższe od 1V, ani wyższe od napięcia zasilania Vcc mikrokontrolera. Wartość zmierzonego napięcia Vin można
obliczyć ze wzoru: Vin=Mres*Aval [V].
Przykład: jeśli Vref=3.072V to przetwornik ADC będzie mierzył napięcie Vin=0-3.072V, które zamieni na wartość cyfro-
wą Aval=$0000-$03FF (10-bitów). Rozdzielczość pomiaru Mres = 3.072V/1024 = 0.003V, czyli każda zmiana napię-
cia Vin o 3mV spowoduje zmianę wartości cyfrowej o 1. Jeśli wynik pomiaru z przetwornika wynosi Aval=$0064=100,
to wartość zmierzonego napięcia Vin = 0.003V*100 = 0.3V.
Oczywiście są to obliczenia teoretyczne dotyczące idealnego przetwornika ADC i źródła napięcia odniesienia Vref.
W praktyce całkowita dokładność przetwornika ADC wbudowanego w mikrokontroler ATmega48/88/168/328, wynosi
w najgorszym wypadku plus/minus 2 LSB (Least Significant Bit), co oznacza że otrzymana wartość Aval może odbiegać
o plus/minus 2 od wartości prawidłowej. Nie istnieje też źródło napięcia odniesienia Vref o idealnie dokładnej i stałej
wartości, a ma ono kluczowy wpływ na wynik konwersji.
Dopuszczalny zakres napięć wejściowych Vin na dowolnym kanale przetwornika ADC, wynosi od GND do Vcc.
Podanie niższego/wyższego napięcia Vin może zaburzyć pracę przetwornika ADC, ale nie uszkodzi mikrokontrolera
pod warunkiem, że płynący prąd nie przekroczy wartości 1mA.
Dzieje się tak dlatego, że każdy pin posiada dwie wewnętrzne diody zabezpieczające (clamping diodes) o spadku
napięcia 0.5V, które chronią mikrokontroler przed za niskim/wysokim napięciem wejściowym Vin w stosunku do jego
napięcia zasilania Vcc. Utrzymują one wszystkie sygnały trafiające do mikrokontrolera w zakresie jego maksymalnych
napięć wejściowych (od GND-0.5V do Vcc+0.5V). Podanie napięcia wejściowego Vin o wartości niższej/wyższej niż
GND-0.5V/Vcc+0.5V, spowoduje przepływ prądu przez dolną/górną diodę zabezpieczającą do masy/plusa zasilania,
a w rezultacie zmniejszenie napięcia Vin na pinie do wartości GND-0.5V/Vcc+0.5V. Jedynym ograniczeniem wewnę-
trznych diod zabezpieczających jest zalecany maksymalny prąd, który może przez nie płynąć wynoszący 1mA. Większy
prąd może spowodować uszkodzenie diody zabezpieczającej, a następnie pinu/portu mikrokontrolera.
Wynika z tego, że stosując odpowiednio dużą wartość rezystancji wejściowej Rin, na pin mikrokontrolera można podać
napięcie wejściowe Vin, o wartości znacznie przekraczającej jego maksymalny zakres (od GND-0.5V do Vcc+0.5V).
Przykład: z rezystorem wejściowym Rin=1M na pin mikrokontrolera można podać napięcie ujemne/dodatnie Vin=1000V,
bez obawy o jego uszkodzenie.
Trzeba jednak pamiętać, że przetwornik ADC działa dobrze jedynie ze źródłami napięcia o impedancji wyjściowej Zout
do 10k (wtedy czas próbkowania jest pomijalnie mały). Zbyt wysoka rezystancja Rin na wejściu przetwornika uniemożliwi
prawidłowy pomiar, ponieważ prąd ze źródła napięcia Vin będzie zbyt niski, aby naładować kondensator próbkujący Csh
(S/H - Sample and Hold) w odpowiednio krótkim czasie (konwersja trwa 13-260 us). Oczywiście ograniczenie to dotyczy
tylko wykonywania konwersji (Free Running mode) z dużą szybkością (15-76.9 kSps). Prawidłowy pomiar źródeł napię-
cia o wyższej impedancji Zout jest możliwy, jeśli pojedyncze konwersje (Single Conversion mode) będą wykonywane
z odpowiednio małą szybkością. Wtedy kondensator próbkujący Csh ma dłuższy czas, aby naładować się niskim prą-
dem ze źródła Vin do wartości równej napięciu tego źródła. Trzeba przy tym brać pod uwagę prądy upływu Iil/Iih wejścia
przetwornika (pin ADCn) w stanie niskim/wysokim, które rosną wraz z temperaturą i maksymalnie mogą wynosić 1uA
(w temperaturze pokojowej zwykle poniżej 50nA). Cyfrowy bufor wejściowy na pinie ADCn musi zostać wyłączony (bit
ADCnD w rejestrze DIDR0), aby wyeliminować wprowadzaną przez niego pojemność oraz zmniejszyć pobór prądu.
Poniższy rysunek przedstawia wewnętrzną budowę przetwornika ADC (podane wartości elementów były zmierzone
doświadczalnie w jednym egzemplarzu mikrokontrolera ATmega328P).


 
Po wybraniu/zmianie wejścia dla przetwornika
ADC w multiplekserze (bity MUXn w rejestrze
ADMUX), do źródła Vin zostaje podłączony kon-
densator Cmux przez rezystor Rmux. Po włącze-
niu przetwornika ADC (bit ADEN w rejestrze
ADCSRA), przełącznik Ssh podłącza do źródła
Vin kondensator Csh przez rezystor Rsh. Po uru-
chomieniu pojedynczej konwersji (bit ADSC w re-
jestrze ADCSRA), kondensator Csh zostaje odłą-
czony od źródła Vin. Po zakończeniu wykonywa-
nia konwersji kondensator Csh zostaje ponownie
podłączony do źródła Vin.
Aby uzyskany wynik każdego pomiaru był prawi-
dłowy, czas Tsh pomiędzy kolejnymi konwersjami
powinien wynosić: Tsh = 6*Rin*Cin, gdzie Rin -
rezystancja wejściowa przetwornika ADC (suma impedancji wyjściowej Zout źródła mierzonego napięcia Vin i rezystan-
cji Rmux+Rsh), Cin - pojemność wejściowa przetwornika ADC (suma pojemności ścieżek Cpcb na płytce drukowanej
i kondensatorów Cmux+Csh). Według not katalogowych suma rezystancji Rmux+Rsh może wynosić 1-100k, a pojemno-
ści Cmux+Csh równe 14pF. Przykład: jeśli Rin = 1M i Cin = 30pF, to kolejna konwersja powinna być wykonana po od-
czekaniu 180us (Tsh = 6*1000000*0.00000000003 = 6*0.00003 = 0.00018). Po tym czasie kondensator próbkujący
Csh podłączony do źródła Vin naładuje się do wartości 99.75% napięcia tego źródła.
Innym sposobem na prawidłowy pomiar źródeł napięcia o wysokiej impedancji Zout jest podłączenie kondensatora 1nF,
pomiędzy masę i wejście przetwornika (pin ADCn). Dzięki temu kondensator próbkujący Csh będzie dużo szybciej ła-
dowany bezpośrednio z kondensatora 1nF (minimalny spadek napięcia), niż przez rezystancję wejściową Rin (np. dzie-
lnika napięcia). Spowoduje to wolniejszą odpowiedź częstotliwościową na szybkie zmiany napięcia źródła Vin, ale nie
wymaga czekania pomiędzy kolejnymi konwersjami. Użyty kondensator 1nF musi mieć niską stratność dielektryczną.


Poniżej znajduje się kodu programu przeznaczonego dla mikrokontrolerów serii ATmega48/88/168/328,
który konfiguruje przetwornik ADC, wykonuje za jego pomocą ciągłe pomiary i prezentuje ich wyniki na
wyświetlaczu 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

.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,$EE   ;zatrzymanie zbędnych modułów (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
          ldi     R16,$FF
          out     PORTB,R16 ;ustawienie pinów PB0-PB7 jako wejść z wysokim stanem (pull-up)
          out     DDRD,R16  ;ustawienie pinów PD0-PD7 jako wyjść z niskim stanem
          ldi     R16,$30
          out     PORTD,R16 ;ustawienie wysokiego stanu na wyjściach PD4-PD5
                            ;(PD0-PD3/PD6-PD7 stan niski).
          ldi     R16,$3E
          out     PORTC,R16 ;ustawienie pinów PC1/ADC1-PC5/ADC5 jako wejść z wysokim stanem (pull-up)
          ldi     R16,$01   ;wyłączenie cyfrowego bufora wejściowego na pinie PC0/ADC0
          sts     $7E,R16   ;zapis rejestru DIDR0
Aby zmniejszyć zakłócenia i pobór prądu, zostaje wyłączony komparator analogowy oraz zatrzymane
taktowanie nieużywanych modułów mikrokontrolera (TWI, SPI, USART, TC0, TC1, TC2). Na wszy-
stkich pinach mikrokontrolera panuje ustalony stan logiczny (cyfrowe wyjścia PC0/ADC0-PC3/ADC3
nie powinny się przełączać podczas trwania konwersji), a na wejściu pomiarowym PC0/ADC0 (Vin)
jest wyłączony cyfrowy bufor wejściowy.
Mikrokontrolery ATmega48/88/168/328 nie mają oddzielonej masy cyfrowej (pin 8) od analogowej
(pin 22). Muszą się one ze sobą łączyć na płytce drukowanej, ale tylko w jednym punkcie - znacznie
zmniejsza to wpływ zakłóceń, generowanych przez część cyfrową mikrokontrolera na jego część
analogową.
Ponadto istnieje też możliwość wykonywania pomiarów przez przetwornik ADC tylko wtedy, gdy mikro-
kontroler jest "uśpiony" (tryb "ADC Noise Reduction"), co dodatkowo niweluje wpływ wewnętrznych
zakłóceń na wynik konwersji. Wymaga to jednak użycia przerwań mikrokontrolera, które nie zostały
wykorzystane w tym przykładzie.
;Konfiguracja wyświetlacza LCD 2x16 znaków (HD44780)
          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
          rcall   LCDclr    ;czyszczenie 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
          rcall   LCDwf     ;wyświetlenie na LCD ciągu ASCIIZ z pamięci FLASH
Inicjalizacja i włączenie wyświetlacza LCD oraz wyświetlenie na nim początkowych napisów.
;Główna pętla programu
Loop:
;Obsługa zworki J1 - wybieranie wejścia dla przetwornika ADC
          sbic    PINC,$05  ;odczyt stanu pinu PC5/ADC5
          rjmp    Loop0     ;skok, jeśli na pinie PC5/ADC5 jest stan wysoki (zworka J1=OFF)
;Zworka J1=ON (zwarta): wybranie wewnętrznego źródła napięcia odniesienia (1.1V) jako wejścia dla ADC
          ldi     R16,$4E   ;wybranie napięcia odniesienia z pinu AVCC i wejścia dla ADC
          sts     $7C,R16   ;zapis rejestru ADMUX
          rjmp    Loop1
;Zworka J1=OFF (rozwarta): wybranie pinu PC0/ADC0 jako wejścia dla ADC
Loop0:    ldi     R16,$40   ;wybranie napięcia odniesienia z pinu AVCC i wejścia dla ADC
          sts     $7C,R16   ;zapis rejestru ADMUX
;Uruchomienie konwersji ADC
Loop1:    ldi     R16,$C3   ;włączenie ADC i wykonanie pojedynczej konwersji z f=CLK/8 (125 kHz)
          sts     $7A,R16   ;zapis rejestru ADCSRA
;Czekanie na zakończenie konwersji przez ADC
Loop2:    lds     R16,$7A   ;odczyt rejestru ADCSRA
          andi    R16,$40
          brne    Loop2     ;skok, jeśli bit ADSC=1 (konwersja trwa)
;Odczyt wyniku konwersji ADC
          lds     R23,$78   ;odczyt rejestru ADCL (młodszy bajt wyniku)
          lds     R16,$79   ;odczyt rejestru ADCH (starszy bajt wyniku)
;Konwersja 16-bitów wyniku do systemu HEX-ASCIIZ: [R16:R23] => "NNNN"
          ldi     ZL,LOW(sbuf)
          ldi     ZH,HIGH(sbuf) ;adres bufora wyjściowego w pamięci SRAM
          rcall   ConBH     ;konwersja bajtu do systemu HEX-ASCIIZ
          mov     R16,R23   ;młodszy bajt wyniku
          rcall   ConBH     ;konwersja bajtu do systemu HEX-ASCIIZ
;Wyświetlenie szesnastkowej wartości wyniku na LCD
          sbiw    ZL,$04
          ldi     R18,$C7   ;ustawienie kursora na pozycji 8 w 2 linii
          rcall   LCDwi     ;zapis instrukcji do LCD
          rcall   LCDws     ;wyświetlenie na LCD ciągu ASCIIZ z pamięci SRAM
          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
Główna pętla programu, która jest wykonywana bez przerwy. Na początku program sprawdza stan zwo-
rki J1 (pin PC5/ADC5). Jeśli zworka J1 jest rozwarta (OFF), to za pomocą rejestru ADMUX dla przetwo-
rnika ADC jest wybierane napięcie odniesienia Vref z pinu AVCC (bity REFS1-0, Vref=Vcc) oraz wejście
na pinie PC0/ADC0 (bity MUX3-0). Jeśli zworka J1 jest zwarta (ON), to dla przetwornika ADC wybierane
jest napięcie odniesienia Vref również z pinu AVCC, ale wejście z wewnętrznego źródła napięcia odnie-
sienia (1.1V). Następnie przy użyciu rejestru ADCSRA włączany jest przetwornik ADC (bit ADEN) i uru-
chamiana pojedyncza konwersja (bit ADSC) z wybraną częstotliwością (bity ADPS2-0). Aby uzyskać
wynik konwersji o maksymalnej rozdzielczości 10-bitów, przetwornik musi pracować z częstotliwością
Fadc=50-200 kHz (wyższa obniży rozdzielczość pomiaru). Częstotliwość Fadc jest uzyskiwana z głównej
częstotliwości CLK taktującej mikrokontroler, podzielonej w 7-bitowym prescalerze przetwornika ADC
(podział przez 2/4/8/16/32/64/128). Poniższa tabela zawiera wszystkie możliwe do uzyskania często-
tliwości Fadc w zależności od częstotliwości taktowania CLK mikrokontrolera.

CLK
[MHz]
Fadc [Hz]
248163264128
1500k250k125k62.5k31.25k15.62k7.81k
21M500k250k125k62.5k31.25k15.62k
31.5M750k375k187.5k93.75k46.88k23.44k
42M1M500k250k125k62.5k31.25k
52.5M1.25M625k312.5k156.25k78.12k39.06k
63M1.5M750k375k187.5k93.75k46.88k
73.5M1.75M875k437.5k218.75k109.38k54.69k
84M2M1M500k250k125k62.5k
94.5M2.25M1.12M562.5k281.25k140.62k70.31k
105M2.5M1.25M625k312.5k156.25k78.12k
115.5M2.75M1.37M687.5k343.75k171.88k85.94k
126M3M1.5M750k375k187.5k93.75k
136.5M3.25M1.62M812.5k406.25k203.12k101.56k
147M3.5M1.75M875k437.5k218.75k109.38k
157.5M3.75M1.87M937.5k468.75k234.38k117.19k
168M4M2M1M500k250k125k
178.5M4.25M2.12M1.06M531.25k265.62k132.81k
189M4.5M2.25M1.12M562.5k281.25k140.62k
199.5M4.75M2.37M1.18M593.75k296.88k148.44k
2010M5M2.5M1.25M625k312.5k156.25k
Po wykryciu zakończenia konwersji (bit ADSC=
0) przez pętlę warunkową, 16-bitowy wynik jest
odczytywany z rejestrów [ADCH:ADCL] (najsta-
rsze 6-bitów ma zawsze wartość 0). Każdy bajt
tego wyniku jest zamieniany na szesnastkowy
ciąg ASCII i zapisywany do bufora w pamięci
SRAM mikrokontrolera. Uzyskany w ten sposób
4-znakowy ciąg ASCIIZ jest wyświetlany na
LCD, począwszy od pozycji nr 8 w 2 linii.
Przed końcem pętli znajduje się kod generujący
opóźnienie ok. 102ms, aby spowolnić wyświe-
tlanie nowych wartości na LCD. Instrukcja skoku
RJMP zamyka pętlę.
Dzięki temu podczas pracy programu na wyś-
wietlaczu LCD można obserwować aktualną
4-cyfrową wartość szesnastkową, która odpo-
wiada napięciu na pinie PC0/ADC0 (J1=OFF)
lub napięciu wewnętrznego źródła odniesienia
(J1=ON) mikrokontrolera.
Otrzymana wartość zależy bezpośrednio od na-
pięcia zasilania Vcc mikrokontrolera (pin AVCC),
ponieważ jest ono jednocześnie napięciem od-
niesienia Vref dla przetwornika ADC. Aby wyniki
pomiarów nie były zależne od napięcia zasilania
Vcc, należy na pin AREF podać napięcie odnie-
sienia Vref z innego źródła (bity REFS1-0 w rejestrze ADMUX). Może to być wewnętrzne źródło napięcia
odniesienia (1.1V) mikrokontrolera, ale lepszym rozwiązaniem jest użycie zewnętrznego układu scalo-
nego, który dostarczy stabilniejszego napięcia Vref (mniejsza zależność od zmian napięcia zasilania
i temperatury).

Przykładowa zawartość ekranu wyświetlacza LCD, podczas wykonywania głównej pętli programu jest
przedstawiona poniżej: rysunek 1 - wejście pomiarowe PC0/ADC0 (Vin) niepodłączone (J1:OFF);
rysunek 2 - pomiar napięcia zasilania Vcc (J1:OFF); rysunek 3 - pomiar napięcia wewnętrznego źródła
odniesienia 1.1V (J1:ON).


Rysunek 1


Rysunek 3

Rysunek 2
;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

;Inicjalizacja wyświetlacza LCD
LCDi:
;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,$B3
          out     PORTD,R16 ;E=1, RS=0 (PD4-PD5=1)
          ;(wait 4 CL)
          cbi     PORTD,$07 ;E=0, RS=0 (PD4-PD5=1)
          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,$B2
          out     PORTD,R16 ;E=1, RS=0 (PD4-PD5=1)
          ;(wait 4 CL)
          cbi     PORTD,$07 ;E=0, RS=0 (PD4-PD5=1)
          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,$B0   ;E=1, RS=0 (PD4-PD5=1)
          rjmp    LCDwc0
;Zapis znaku do wyświetlacza LCD (tryb 4-bitowy)
LCDwc:    ldi     R20,$F0   ;E=1, RS=1 (PD4-PD5=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ść (PD4-PD5=1)
          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ść (PD4-PD5=1)
          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    YL,R0
Wait1:    sbiw    YL,$01
          brne    Wait1
          dec     R2
          brne    Wait0
          ret

;Napisy dla wyświetlacza LCD
Text0:    .db     "*** ADC test ***",0,"Value:",0

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