Lekcja 2, tworzymy pętle opóźniające
Koncepcja programu
W pierwszym ćwiczeniu nauczyliśmy się obsługiwać porty wejścia i wyjścia mikrokontrolera. Znamy również kilka przydatnych instrukcji asemblera pod PIC16. Dzisiaj spróbujemy napisać program, którego celem będzie mruganie diodą podłączoną załóżmy do portu RA.3 (pin 2).
Standardowo jak to bywa w programowaniu możemy to wykonać na kilka zgoła różnych sposobów, które dadzą jednak ten sam efekt. Teoretycznie moglibyśmy po prostu dodać do poprzedniego programu taką komendę:
Program ;główny program
BSF PORTA,3 ;ustaw RA.3 (RA.3 = 1)
BCF PORTA,3 ;zeruj RA.3 (RA.3 = 0)
GOTO Program ;powrót do programu głównego
Częstotliwość pracy procesora
Jak myślisz co się stanie jeżeli skompilujemy taki plik i wyślemy do mikrokontrolera? Dioda będzie mrugać, ale z tak dużą prędkością, że my z naszym niedoskonałym okiem będziemy widzieć ciągłe świecenie. Wynika to oczywiście z tego, że mikrokontroler na wykonanie jednej operacji (np. BSF, czy BCF) potrzebuje bardzo mało czasu. W przypadku pic16f84, którego używamy czas ten wynosi jak podaje katalog 200ns. Dla ścisłości te 200ns to czas pojedynczej instrukcji wykonywanej przez mikroprocesor. Składa się na niego kilka stałych czynności. Dla przykładu polecenie BSF będzie wykonywane mniej więcej tak:
– zaadresuje pamięć
– odczyta rozkaz BSF
– zwiększy licznik programu, o którym później, 0 jeden
– wykona rozkaz, w tym przypadku ustawi dany bit
Dla nas praktycznie nie ma to większego znaczenia. Dioda mruga nam z częstotliwością zależną od użytego kwarcu i nie satysfakcjonuje nas ten stan rzeczy. Musimy obniżyć częstotliwość mrugania, czyli przedłużyć czas wyłączenia diody i jej włączenia. Zrobimy to za pomocą programowego opóźnienia. W innych językach programowania mielibyśmy pewnie gotowe procedury odmierzające czas, ale niestety w asemblerze wszystko musimy robić samodzielnie.
Pętla opóźniająca, czyli odmierzamy czas
Pętle opóźniającą wykonamy w następujący sposób. Zainicjujemy w mikrokontrolerze dwie zmienne np. liczba1 i liczba2. Następnie wpiszemy do nich odpowiednie wartości. Od tych wartości będzie zależała częstotliwość mrugania diody. Pierwszą zmienną z wpisaną wartością będziemy w każdym cyklu zmniejszać, a kiedy osiągnie ona zero przejdziemy do zmniejszania drugiej zmiennej. Po pojedynczym zmniejszeniu tej drugiej program znowu zacznie zmniejszać pierwszą i tak, aż wartość drugiej nie będzie się równać zeru. Jeżeli tak się stanie program opuści pętle opóźniającą i przejdzie do wykonywania dalszych komend.
Opóźnienie: ;tu wskakujemy po przejściu do opóźnienia
MOVLW .200 ;wpisujemy do rejestru w 200 dziesiętnie
MOVWF liczba2 ;wpisujemy 200 do naszej drugiej zmiennej
Petla 1 ;tu wskakujemy, gdy liczba1 = 0 i liczba2 została zmniejszona o jeden
MOVWF liczba1 ;wpisujemy 200 do naszej pierwszej liczby
Petla2 ;tu wskakujemy po każdym zmniejszeniu zmkiennej liczba1
DECFSZ liczba1,f ;zmniejszamy liczba1 o jeden i jeżeli liczba1 = 0 to przeskakuje jedną ;komendę
GOTO Petla2 ;tu wskoczy jeżeli liczba1 nie będzie zerem
DECFSZ liczba2,f ;tu wskoczy, gdy liczba1 będzie zerem i dodatkowo zmniejszy o jeden ;liczba2 i podobnie jak wcześniej, jeżeli jest zerem przeskoczy komendę jeżeli nie przejdzie ;do następnej
GOTO Petla1 ;jeżeli nie jest zerem idzie do Petla1, gdzie wpisuje 200 do licza1 i proces się ;powtarza
RETURN ;powrót do miejsca, z którego został wywołany podprogram opóźnienie, w momencie gdy liczba2 = 0.
Może to się wydać trochę skomplikowane, ale po krótkiej analizie działania dojdziesz do wniosku, że wcale tak nie jest. Wyjaśnijmy teraz działanie nowych komend. Pojawiła się tutaj komenda DECFSZ f,d ,gdzie f to nazwa zmiennej, która jest poddawana operacji, a d to miejsce gdzie zapisywany jest wynik operacji. Dla d = 0 (lub gdy dodaliśmy plik nagłówkowy po prostu w) wynik jest zapisywany w rejestrze w. Natomiast gdy d = 1 (lub f w programie z plikiem nagłówkowym) wynik jest zapisywany w rejestrze f.
DECFSZ to komenda, która jest złożeniem dwóch operacji. Mianowicie zmniejszenia zmiennej o jeden, a następnie sprawdzeniu czy zmienna równa się zeru. W naszym przypadku zmniejszaniu ulegały zmienne liczba1 i liczba2, a ich wartości zmniejszone wpisywane były jak gdyby do ich samych dzięki wstawieniu po zmiennej f (zapis wyniku do rejestru f). Po tej komendzie program ma możliwość wyboru. Jeżeli zmienna nie równa się zeru program wykonuje się dalej normalnie komenda po komendzie. W przypadku, gdy zmienna równa się zeru program przeskakuje, pomija jedną komendę i idzie do następnej
DECFSZ zmienna,f
Wykona komendę wpisaną tutaj, gdy zmienna nie jest równa zeru
Przeskoczy tutaj nie wykonując komendy poprzedniej, gdy zmienna = 0
Licznik programu PC
W celu zrozumienia skąd PIC wie gdzie i o ile ma przeskakiwać należy wprowadzić pojęcie PC (program counter) czyli licznika programu. Przy przejściu do każdej następnej komendy licznik ten jest zwiększany o jeden. Z wartości tego licznika PIC wie, którą komendę ma aktualnie wykonać
1 Opoznienie:
2 MOVLW .200
3 MOVWF liczba1
4 Petla1
5 DECFSZ liczba1,f
6 GOTO Petla1
7 RETURN
W przypadku natrafienia na komendę DECFSZ w PC znajduję się liczba 5. Gdy zmienna liczba1 = 0, do PC jest dodawana liczba 2 i w następnym cyklu instrukcji przechodzi on do lini 7 (5+2=7). Jeżeli liczba1 jest różna od zera do PC jest dodawana standardowo jak przy każdej „zwykłej komendzie” liczba jeden. W tym przypadku PC = 6 i program wykonuje komendę GOTO Petla1.
Trafiła nam się kolejna zagadka. Skąd PIC wie, gdzie jest procedura Petla1, do której ma iść. Znowu jest tu wykorzystany licznik programu. Po natrafieniu na jakąś procedurę (np. Petla1) mikroprocesor zapisuje sobie informacje o tym przy jakim stanie PC (w tym przypadku PC = 4) została ona wywołana i dzięki temu przy instrukcji GOTO ta informacja jest wydobywana i program powraca tam, gdzie ma wrócić.
Praktycznie nie obchodzi nas zupełnie jaką wartość ma PC i jakie liczby są do niego wypisywane. Warto jednak wiedzieć, że takie cudo istnieje i to dzięki niemu PIC wie gdzie w aktualnym momencie ma skoczyć i jaką komendę wykonać.
Przy okazji skróciłem trochę program opóźnienia. Jednak najprawdopodobniej taki krótki będzie wytwarzał za małe opóźnienie i dioda będzie świeciła ciągle. Zależy to od tego jakiego rezonatora używasz.
Ten krótki programie da nam 200 cyklów opóźnienia, natomiast ten pierwszy dłuższy 200*200, czyli 40000. Mam nadzieję, że wiesz z czego wynikają te liczby. W pierwszym programie liczba1 jest zmniejszana w każdym cyklu o jeden, aż nie osiągnie wartości 0. Gdy osiągnie tą wartość liczba2 jest zmniejszana o jeden, program wraca do procedury Petla1, wpisuje do liczba1 kolejne 200 i żeby zmniejszyć liczba2 o kolejną jedynkę liczba1 znowu musi zostać zmniejszona do zera.
Słowo o podprogramach
W powyższych przykładach komendy były pisanie w podprogramie. Podprogram to taka część kodu, która może zostać wywołana z dowolnego miejsca w programie poprzez instrukcję CALL podprogram. Działa to podobnie jak z instrukcją GOTO. Do PC jest wpisywana aktualna wartość, procesor skacze do podprogramu, wykonuje go i gdy natrafi na RETURN dodaje do poprzednio zapisanej wartości PC jeden przez co wraca do komendy bezpośrednio po CALL.
Dzięki podprogramom zmniejszamy wagę programu wynikowego. Zamiast przed i po zapaleniu diody dawać dwie kolumny opóźnień dodajemy tam jedynie CALL opóźnienie.
Zilustrujmy to na przykładzie
Program
BSF PORTA,3 ;włączamy diodę
;Tutaj wklejamy kod opóźnienia czyli 5 linijek z poprzedniego programu
MOVLW .200
MOVWF liczba1
Petla1
DECFSZ liczba1,f
GOTO Petla1
BCF PORTA,3 ;wyłaczamy diodę
;I kolejne dodatkowe linijki zapewniające opóźnienie po wyłączeniu diody
MOVLW .200
MOVWF liczba1
Petla1
DECFSZ liczba1,f
GOTO Petla1
GOTO Program
END
Taki program rozwleka nam się na wiele linijek przez co jest nieczytelny i dodatkowo zwiększ wagę pliku wynikowego. Zamiast takich chałupniczych metod stosujemy po prostu podprogramy na części kodu, które się powtarzają.
Program
BSF PORTA,3 ;włączamy diodę
CALL opóźnienie ;idziemy do podprogramu opóźnienie
BCF PORTA,3 ;Wyłączamy diodę
CALL opóźnienie ;znowu idziemy do podprogramu opóźnienie
GOTO Program
;***PODPROGRAMY***
opóźnienie:
MOVLW .200 ;wpisujemy do rejestru w 200 dziesiętnie
MOVWF liczba2 ;wpisujemy 200 do naszej drugiej zmiennej
Petla 1 ;tu wskakujemy, gdy liczba1 = 0 i liczba2 została zmniejszona o jeden
MOVWF liczba1 ;wpisujemy 200 do naszej pierwszej liczby
Petla2 ;tu wskakujemy po każdym zmniejszeniu zmkiennej liczba1
DECFSZ liczba1,f ;zmniejszamy liczba1 o jeden i jeżeli liczba1 = 0 to przeskakuje jedną ;komendę
GOTO Petla2 ;tu wskoczy jeżeli liczba1 nie będzie zerem
DECFSZ liczba2,f ;tu wskoczy, gdy liczba1 będzie zerem i dodatkowo zmniejszy o jeden ;liczba2 i podobnie jak wcześniej, jeżeli jest zerem przeskoczy komendę jeżeli nie przejdzie ;do następnej
GOTO Petla1 ;jeżeli nie jest zerem idzie do Petla1, gdzie wpisuje 200 do licza1 i proces się ;powtarza
RETURN ;powrót do miejsca, z którego został wywołany podprogram opóźnienie, w momencie gdy liczba2 = 0.
END
W ten sposób otrzymujemy ładny schludny i lżejszy program.
Inicjacja zmiennych
Mniemam, że z napisaniem programu mrugania diody poradzisz sobie sam. Do ostatniego programu wystarczy dopisać konfigurację i ustawienie portów. Jeszcze słowo o inicjacji zmiennych. Wpisujemy je do pamięci SRAM, czyli do adresów od 0C do 4F oczywiście obie liczby podane heksanie. Inicjujemy je tak jak przypisujemy nazwę adresowi rejestru czyli za pomocą EQU. Możemy zrobić to na dwa sposoby. Pojedynczo
Liczba1 equ 0x0c
Liczba2 equ 0x0d
lub jako bok zmiennych
cblock 0x0d ;początek bloku zmiennych
liczba1
liczba2
endc ;koniec bloku zmiennych
Możesz poeksperymentować z różnymi czasami opóźnień, zmieniać wypełnienie (dużej włączać krócej wyłączać), mrugać dwoma, trzema lub dowolną ilością diod na przemian lub równocześnie. Takie eksperymenty są potrzebne, żeby przyswoić sobie podstawy programowania. Spróbuj np. wykonać opóźnienie przy pomocy trzech zmiennych.
Zakończenie, po co nam komentarze?
Na końcu tego artykułu znajdziesz plik w asemblerze z moją propozycją programu do migania diody. Oczywiście Twój może być inny. Ważne, żeby spełnione były założenia i program był możliwie zoptymalizowany. Pamiętaj również o treściwych komentarzach. Tymi, które ja tutaj piszę nie sugeruj się. Są one zdecydowanie zbyt rozwleczone, ze względu na to, że wyjaśniam w nich działanie komendy. W programie pisze np.
DECFSZ liczba1,f ;zmniejsza liczba 1
Pamiętaj, że komentarze mają pomóc Tobie lub ewentualnie osobie, z która piszesz program. Dlatego pisz je w sposób odpowiedni dla Ciebie.
Po tej małej dygresji podsumujmy co powinniśmy umieć po tej lekcji:
– wprowadzać podprogramy’
– inicjować zmienne
– dodawać opóźnienie do programu
– zmniejszać zmienne (przy okazji jeżeli komenda ma tylko zmniejszać, a nie sprawdzać czy jest zerem piszemy DECF)
W następnym artykule poznamy kolejne ciekawe komendy, zrobimy linijkę LED i wprowadzenie do obsługi wyświetlacza LED. Jeżeli dotrwałeś do tego momentu, musze poprosić Cię o pozostawienie komentarza na temat zrozumiałości tekstu. Dziękuje 😉 .
Bardzo podoba mi sie forma tego kursu i sposob pisania oraz tlumaczenia. Mam pytanie czy sa gdzies dostepne nastepne lekcje tego kursu, czy ten projekt umarl smiercia naturalna ;-(
BTW: Nie jestem na Facebooku