Przeskocz do treści

Lekcja 2, tworzymy pętle opóźniające

5 listopada, 2011

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 😉 .

From → Kurs

One Comment
  1. wojtek39 permalink

    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

Dodaj komentarz