Programowanie mikrokontrolerów PIC

Programowanie mikrokontrolerów PIC

Lekcja 3, programujemy linijke LED

Linijka LED, kolejny program

Dzisiaj zajmiemy się programem, którego celem będzie wyświetlanie linijki LED na wyjściach PORTA. Wiesz chyba co to jest linijka LED? Chodzi o to, że co pewien okres czasu zapala się kolejna dioda podłączona do wyjścia PORTA, a gaśnie poprzednia. Cykl powtarza się, aż do dojścia do ostatniej diody i rozpoczyna powrót dokładnie w ten sam sposób.

Wstępne założenia

Ze względów omówionych później do linijki nie wykorzystałem w swoim programie wyjścia PORT,4, dlatego moja linijka skład się jedynie z czterech diod o kolorze żółtym. Program jest napisany tak by 1 na wyjściu włączała diodę. Oznacza to, że dla uzyskania tego samego efektu dioda powinna być włączona z wyjścia portu do masy. Schemat znajdziesz na końcu artykułu. Oczywiście nic nie stoi na przeszkodzie, aby umieścić diody drugą stroną. To znaczy od plusa do wejścia portu. W takim przypadku diody zapalałyby się, kiedy na wyjściu byłoby zero logiczne, a rotacja 1 z opisywanego programu wyglądałaby tak, że poruszałaby się jak gdyby jedna zgaszona dioda zamiast jednej zapalonej jak w przypadku opisywanej linijki.

Zmiany

Oczywiście Ty jeżeli masz trochę chęci możesz zmienić podany program i podłączyć diody LED pod wyjście PORTB. Będziesz miał wtedy aż 8 diod do testów. Pamiętaj tylko, że linia programatora RB7 nie może być włączona podczas działania programu. Jeżeli pozostawisz ją włączoną nic się nie stanie programatorowi, ani procesorowi. Nie będzie po prostu się zapalać ostatnia dioda z linijki. Pisałem już o tym w jednej z poprzednich części.

Nowe komendy

Poznaliśmy już ostatnimi czasy kilka pożytecznych komend teraz czas na kolejne. Przy realizacji programu przyda nam się komenda, która przesuwa bity rejestru w określona stronę. W asemblerze pod PICa mamy dwie takie komendy. Mianowicie

RRF rejestr,d

Gdzie rejestr to nazwa rejestru, którego bit chcemy przesuwać, a d to miejsce do którego ma zostać przeniesiona liczba powstała po przesunięciu. Dla przykładu w rejestrze PORTA mamy liczbę 00100 i chcemy ta jedynkę przesunąć o jedno miejsce. Możemy oczywiście w najnormalniejszy sposób wpisać do Porta liczbę 00010, ale my tego nie robimy, ponieważ chcemy poznać działanie nowej komendy.

RRF PORTA,f

Po wykonaniu tego działania liczba zapisana w PORTA będzie miała postać 00010, czyli taką jaką oczekiwaliśmy. To f na końcu oznacza, że przesuniętą wartość wpisujemy do PORTA. Gdyby komenda miała postać:

RRF PORTA,w

Liczba, czy mówiąc ściślej wynik nie zostałby zapisany do PORTA tylko do rejestru w. Nasz PORTA miałby wtedy wygląd taki jak na początku czyli 00100.

Najpierw wpisujemy jedynkę

Hasło z tytułu jest chyba najważniejszą rzeczą w tym projekcie. To jest chyba jedyne miejsce, w którym można i popełnia się błąd. Próba przesunięcie zawartości rejestru, w którym znajduję się liczba 00000 spełźnie na niczym. Zapamiętaj, żeby przesunąć jakakolwiek jedynkę w lewo, czy w prawo najpierw musimy ta jedynkę tam wpisać, chyba że już tam jest ;) . W każdym bądź razie rejestr, na którym chcemy dokonać przesunięcia musi zawierać choćby jedną jedynkę.

Program

Po kilku wstępnych uwagach bierzemy się za program. Oczywiście na samym początku ustawiamy fusebity, deklarujemy procesor itp. Kolejny krok to ustawienie portów. Ja w swoim programie jako port wyjściowy dla LED przyjąłem PORTA, ale równie dobrze mógłby to być PORTB. Nawet byłoby lepiej, ponieważ do niego możemy podłączyć większą ilość LED-ów.

Procedury, rotacja w lewo

Kolejną czynnością do wykonania dla programisty, czyli w tym przypadku Ciebie, jest wpisanie tej jedynki do aktualnie czystego, wyzerowanego rejestru. Jak już wspominałem musimy mieć co przesuwać. Osobiście na wszelki wypadek, gdyby coś w rejestrze przez przypadek znalazło się po zaprogramowaniu, zeruje cały rejestr przed głównym programem komendą:

CLRF PORTA

Wpisanie wspomnianej jedynki może wyglądać tak:

movlw 01h ;wpisanie pierwszej jedynki

movwf PORTA ;wpisanie do PORTA 1 do najmłodszego bitu

Teraz wystarczy już wpisać komendę RLF i wszystko będzie działać. STOP. Zapomnielibyśmy o przymusie dodania opóźnienia pomiędzy włączanie  i wyłączanie kolejnych LED-ów.

Opóźnienie

Podprogram opóźnienia był już ostatnio opisany, więc nie ma co marnować na to objętości kolejnego. Tym razem dla uzyskania odpowiednio długiego opóźnienia użyłem aż trzech zmiennych. Ich inicjalizacja wygląda u mnie tak:

;***ZMIENNE***

cblock 0x0c ;blok zmiennych

liczba1 ;zmienna do opóźnienia

liczba2 ;zmienna do opóźnienia

liczba3 ;zmienna do opóźnienia

endc ;koniec bloku zmiennych

Natomiast sam podprogram opóźnienia tak:

opoznienie ;podprogram opóźnienia (długie)

movlw .4 ;czterokrotne powtórzenie najdłuższej pętli petla1

movwf liczba3

petla1

movlw .200 ;200 razy powtarza petla2

movwf liczba2

petla2

movlw .200 ;200 razy powtarza petle3

movwf liczba1

petla3

decfsz liczba1

goto petla3

decfsz liczba2

goto petla2

decfsz liczba3

goto petla1

return ;powrót do programu

Rotacja w lewo

Po wpisaniu podprogramu opóźnienia możemy już dokonać przesunięcia. Piszemy więc w ciele głównym programu np.

Program

Call opóźnienie

Rlf PORTA,f

Goto program

Kompilujemy odpalamy. Okazuje się, że program działa zgodnie z zamierzeniami. Przemiata jedynką po kolei po wszystkich wyjściach aż do samego końca. My jednak chcemy, żeby po osiągnięciu ostatniego wyjścia program zaczął wracać kolejno do poprzedniego i tak aż do pierwszego, a następnie znowu do drugiego aż do końca i tak dalej.

Sprawdzanie czy liczba osiągnęła pożądany stan

Naszym zadaniem jest teraz wyłapać moment, w którym włączone jest ostatnie wyjście i wymusić rotację w drugą stronę. W moim przypadku dla ostatniego wyświetlanego położenia jedynki w PORTA znajduję się liczba 01000 (PORTA.4 nie biorę pod uwagę ze względu na występowanie tam taimera). Jak widac musimy znaleźć moment, w którym bit PORTA,3 jest ustawiony. Dokonujemy tego znaną już procedurą :

Btfss PORTA,3

Jeżeli PORTA,3 będzie ustawiony to program przeskoczy o dwa miejsca na przykład do goto prawo, a jeżeli będzie wyzerowany powróci do programu przesuwania w lewo.

Btfss PORTA,3

Goto lewo

Goto prawo

Nasz program powinien składać się z dwóch części. Pierwsza to podprogram prawo,  a druga to podprogram lewo. W podprogramie prawo odczekujemy opóźnienie, przesuwamy w prawo, sprawdzamy czy osiągnęliśmy stan końcowy. Jeżeli osiągnęliśmy to idziemy do podprogramu prawo, a jeżeli nie wykonujemy kolejne kroki w podprogramie lewo. Podprogram prawo wygląda identycznie tyle, że przesuwamy w prawo, sprawdzenia dokonujemy dla ustawienie PORTA,0, a jeżeli będzie on ustawiony to idziemy do podprogramu lewo.

PS. W tym przypadku nie chodzi mi o podprogramy wywoływane komendą CALL, tylko zwykłe etykiety prawo, lewo, do których dostajemy się przez komendę GOTO.

Miejsce opóźnienia

Ważne jest, żeby procedura opóźnienia była na samym początku podprogramu. Mam nadzieję, że powód wydaję Ci się oczywisty. Po prostu dzięki niej możemy zobaczyć jak gdyby liczbę wpisaną do PORTA. Jako, że chcemy oglądać kolejne stany 00001, 00010, 00100, 01000, 00100 itd. Opóźnienie musi wystąpić już na samym początku gdy mamy wpisaną do rejestru PORTA jedynkę do najmłodszego bitu. Jeżeli najpierw dokonalibyśmy przesunięcia, a następnie dodali opóźnienie ta jedynka na najmłodszym bicie trwałaby tak krótko, że zamiast niej zobaczylibyśmy od razu stan 00010. Tak samo, gdy wykryjemy stan skrajny. W moim przypadku jest to PORTA,3 = 1 lub PORTA,0 = 1, musimy poczekać na wyświetlenie. Z tego względu opóźnienie umieszczamy przed, a nie po rotacji. Pewnie było to dla Ciebie oczywiste, ale zawsze warto się upewniać ;) .

Zakończenie i przykładowy program

Dobrze by było gdybyś sam powalczył z napisaniem programu linijki LED. Najważniejsze fragment masz tutaj wklejone, ale wcale nie musisz z nich korzystać. W razie problemów dodaje program, który wykonałem. Następna lekcja to będzie omówienie przycisków. Zajmiemy się eliminacją drgań styków i dostosowaniem microswitchy do swoich potrzeb. Możliwe, że zdążymy również wzbogacić zaprogramowaną linijkę LED o obsługę przycisków. Do zobaczenia. Pamiętaj o komentarzu ;) .

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 ;) .


Lekcja1, piszemy pierwszy program

Czas na programowanie

Ostatnio udało nam się uruchomić programator, przetestować jego działanie i w końcu zaprogramować PIC-a. Teraz nadszedł czas na poznanie tajników działania tego złożonego tworu jakim jest mikroprocesor. Zacznijmy jednak od opisu działania podstawowych komend asemblera użytych w poprzednim programie testowy.

Architektura RISC

Należy wspomnieć, że rodzina PIC-ów, którą jak mniemam postanowiłeś się zająć posiada jedynie 35 komend w języku asembler. Znakomicie ułatwia to rozpoczęcie programowania. Nie musimy uczyć się wielu komend, których zadaniem jest wykonanie pojedynczego działania mikroprocesora. Mamy za to 35 komend, które zostały uznane za najpraktyczniejsze. Część z nich powoduje wykonanie jednej operacji, a część jest złożeniem kilku. Tak małą ilość komend zawdzięczamy architekturze RISC w jakiej został wykonany mikroprocesor. Charakteryzuje się ona właśnie zniwelowaniem ilość komend. Zostało to osiągnięte poprzez połączenie kilku podstawowych komend w jedną.

Opis programu

Teorią dotyczącą działania mikroprocesora i subtelnościami PIC-ów zajmiemy się możliwie w najbliższym czasie. Przy nauce programowania w asemblerze i tak siłą rzeczy poznamy jego działanie, a czytanie suchej niezrozumiałej teorii na pewno dla nikogo nie jest przyjemnością.

Blok konfiguracji

Nazwałem tak część programu, która musi wystąpić przy każdym tworzonym projekcie. Składa się na nią:

- Zdeklarowanie używanego mikroprocesora. Realizuje tą funkcję komenda, która jest wysyłana tylko do kompilatora. Ma to na celu zapoznanie kompilator z procesorem, który programujemy. Dzięki temu wie on niejako jakie adresy mogą wystąpić w danym programie. Oznacza to tyle, że dzięki temu kompilator wyrzuci nam błąd, kiedy odwołamy się do rejestru, który nie istnieje w programowanym mikrokontrolerze.

Procesor 16f84 ;to jest właśnie ta komenda

- Powiązanie pliku nagłówkowego nagłówkowego z pisanym programem. Jak już doszliśmy do wniosku w poprzednim artykule, (a raczej ja napisałem) plik nagłówkowy znakomicie ułatwia pisanie programu i skraca i tak już długi czas poświęcony na programowanie. Taki plik nagłówkowy, dany nam przez twórców kompilatora asemblera, składa się praktycznie w całości z bloku procedur mających następujący wygląd, zasadę działania:

nazwa rejestru equ adres rejestru np.

PORTA equ xxx

Tłumacząc, rejestr o adresie xxx nazwij PORTA. Dzięki temu zamiast przy każdym kolejnym odwołaniu do adresu rejestru PORTA, zamiast pisać niezrozumiały i mylący adres piszemy po prostu PORTA.

W pliku nagłówkowym znajdują się również adresy procedur bitów konfiguracyjnych. Chodzi mi o bity odpowiedzialne za wyłączenie/włączenie watchdoga, typ kwarcu z jakim pracujemy itp.

- Ustawienie bitów konfiguracyjnych, fusebitów. Wspominałem już wcześniej o swoim problemie z watchdogiem. Takich problemów unikniemy świadomie ustawiając bity konfiguracyjne. Najważniejsze z nich to właśnie watchdog, oscylator z jakim pracujemy i kilka innych, które na moim etapie nauki po prostu zostawiam w spokoju.

_config – mówimy procesorowi o tym, że zaraz nastąpi przesłanie danych dotyczących ustawienia bitów konfiguracyjnych.

_XT_OSC – oscylatorem jest kwarc o normalnej prędkości (do 4 MHz)

_WTD_OFF – wyłączamy watchdoga

Wszystkie procedury konfiguracji łączymy ze sobą spójnikiem &.

Mamy już za sobą konfigurację. Jeszcze raz zwracam uwagę na to, że są to bardzo ważne ustawienia. Często rozpoczynając programowanie  chcemy jak najszybciej skompilować program i przetestować czy wszystkie komponenty działają. Jeżeli mamy trochę szczęścia to pierwszy prosty program zadziała (np. ze względu na to, że użyjemy oscylatora RC, który jest automatycznie ustawiany). Niestety po wymianie przytoczonego oscylatora na kwarc wszystko zacznie się sypać, my stracimy chęci i skończymy z nauką uważając, że jest to jakieś magiczne. W końcu skoro nie działa wpisanie 1 na wyjście RB.0, które wcześniej zostało zainicjowane jako wyjście, to albo coś jest nie tak ze sprzętem, albo z człowiekiem. Zamiast skazywać się na takie rozmyślania sumiennie przestudiujmy działanie bitów konfiguracyjnych i dobierzmy je wedle swoich potrzeb.

Program właściwy, ustawiamy porty

PIC, którym się zajmujemy czyli pic16f84 posiada dwa porty wyjściowe. PORTA o adresie 05h i PORTB o adresie 06h. PORTA posiada cztery wyjścia RA.0 (nóżka 17), RA.1 (nóżka 18), RA.2 (nóżka 1), RA.3 (nóżka 2) i RA.4 (nóżka3). Dla nas oznacza to możliwość zaadresowania takiego portu przy użyciu 5 bitów (kod dwójkowy). Tłumacząc to prościej wpisanie liczby 00001 do PORTA spowoduje pojawienie się jedynki na wyjściu RA.0. Wpisanie 11111 spowoduje pojawienie się jedynek na wszystkich wyjściach tego portu. Oczywiście wcześniej musimy ustalić czy mają to być wejścia czy wyjścia, ale o tym za chwilę. PORTB ustawiamy analogicznie jak PORTA. Mamy tutaj do dyspozycji aż 8 wyprowadzeń. Należy wziąć pod uwagę to, że dwa z nich wykorzystujemy do programowania (RB.6 i RB.7). Nie oznacza to, że nie możemy ich używać, ale wpisanie jedynki na RB.7 w momencie gdy mamy tam podłączoną linie programatora nie spowoduje pojawienia się tam 5V. Dopiero po odłączeniu tej linii mikroprocesor zachowa się tak jak chcieliśmy.

Podsumowując: PORTA umożliwia obsługę 5 wyjść, PORTB natomiast 8. W tym drugim uważamy na wyjścia RB.7 i RB.6. Programujemy je normalnie, ale żeby działały tak jak chcemy odłączamy po prostu linie programatora. Na razie nie zajmujemy się specyfiką wyjść tych portów. W niedalekim czasie zajmiemy się tym, a na razie nie zaprzątaj sobie głowy tym co jest tam  w środku i co oznacza TOCKI przy RA.4.

Omówienie programu

Zajmijmy się teraz opisem poszczególnych procedur użytych w programie. Na pierwszy ogień idzie ustawianie portów jako wejściowe i wyjściowe. Wejściowe oznacza, że będą one odczytywały sygnał zewnętrzny, natomiast wyjściowe będą przekazywać sygnał na zewnątrz.

Jak już, jako wielbiciel teorii się dowiedziałeś, bohater tego cyklu (pic16f84) ma pamięć podzieloną na dwa banki. Czemu i w jakim celu spróbujemy dojść później. Sygnały na podawane na porty, czy procedury odczytywania znajdują się w BANK0, a bity odpowiedzialne za ustawienie portów jako wyjścia i wejścia w BANK1. Dla programisty, czyli nas oznacza to przymus przełączenia się między tymi bankami w celu ustawienia portów.

Za to, który bank jest aktualnie włączony, odpowiada bit piąty rejestru STATUS, który znajduję się pod adresem 03h. Jeżeli bit ten jest wyzerowany to jesteśmy w obszarze BANK0 jeżeli ustawiony (1) to w obszarze BANK1. Zerowanie czyli ustawienie bitu w stan zera logicznego wykonywany jest za pomocą funkcji bcf rejestr,numer bitu .

BCF czyli bit clear, wyczyść bit.

Natomiast jedynkę logiczną ustawiamy komendą bsf rejestr,numer bitu.

BSF czyli bit set, ustaw bit.

Przechodzimy więc do przykładu. Jako, że na początku programu zawsze znajdujemy się w BANK0, przejdziemy teraz do BANK1. Dokonamy tego przy pomocy następującej procedury:

BCF STATUS,5 ;bit 5 rejestru STATUS jest teraz zerem, co oznacza, że jesteśmy jesteśmy obszarze BANK1.

Kiedy już znajdujemy się w pożądanym banku, musimy dokonać ustawienia portów. Wpisując do portu jedynkę ustawiamy go jako wyjście, natomiast wpisując zero jako wejście. W celu wpisania jakiejkolwiek liczby do rejestru musimy wykonać dwie czynności. Wpisać żądaną liczbę do rejestru w, a następnie przepisać zawartość rejestru w do docelowego rejestru (w naszym przypadku TRISA lub TRISB). W to główny rejestr mikroprocesora PIC, w którym przechowywane są dane liczbowe, które mogą być potem przesuwane do innych rejestrów, dodawane itp. Generalizując w to rejestr zapewniający przestrzeń do wykonywania działań na liczbach.

W celu wpisania czegokolwiek do rejestru w wykonujemy komendę movlw liczba. Gdzie wpisywana liczba musi mieć wyraźnie zaznaczony kod np.

MOVLW d’10’ – wpisuje do rejestru w liczbę 10 dziesięteni

MOVLW 10h    – wpisuje do rejestru w liczbę 10 heksalnie

MOVLW b’1010’ – wpisuje do rejestru w liczbę 10 binarnie

Jest to ważne, bo w przypadku nie określenia kodu w jakim zapisana jest dana liczba kompilator wyrzuci błąd.

Kolejny krok to wpisanie liczby z rejestru w do innego rejestru. Jest to zadanie komendy movwf adres rejestru (przesuń z rejestru w do f). Jak widać dla znających język angielski chociażby w najmniejszym stopniu komendy asemblera są same w sobie zrozumiałe i czytelne. Jedyne co jest tu do zapamiętania to składnia jaka występuję po komendzie.

Podajmy dalszy przykład dla naszej liczby dziesięć. Załóżmy, że jesteśmy w BANK0 i chcemy tą dziesiątkę wpisać do PORTA, który wcześniej został zdefiniowany jako wyjściowy.

MOVLW 10h ;wpisujemy dziesięć do rejestru ogólnego przeznaczenia w

MOVWF PORTA ;przepisujemy zawartość z rejestru w do PORTA

Po wykonaniu tych dwóch komend na wyjściach PORTA panują następujące stany 01010. Oczywiście jest to włożona do rejestru w liczba dziesięć tyle, że zapisana dwójkowo.

Wiemy już jak ustawiać i zerować poszczególne bit. Oczywiście nie odnosi się to nie tylko do rejestru STATUS i bitu odpowiedzialnego za wybór banku. Poznanymi poleceniami jesteśmy w stanie wyzerować i ustawić każdy bit dowolnego rejestru. Jak wyzerować wszystkie bity danego rejestru? Mamy kilka możliwości. Mianowicie możemy zerować każdy bit po kolei poznaną już komendą, ale zużyjemy tym samym masę pamięci na powtarzanie tej samej komendy. Możemy wpisać zero do rejestru w, a następnie do zerowanego rejestru (przypuśćmy, że zerujemy PORTA).

MOVLW 00h ;wpisujemy zero do rejestru w

MOVWF PORTA ;wpisujemy zawartość w (czyli 0) do rejestru PORTA

Jak widać tracimy na to, tylko dwie komendy. My jednak znając wszystkie dostępne komendy skorzystamy z procedur clrf adres rejestru, która w jednej komendzie wyczyści nam całą zawartość wskazanego rejestru.

CLRF PORTA ;zerujemy cały PORTA

Piszemy program

Na początek obrazek ze schematem układu, pod który napisany był program i pod którym na pewno on zadziała:

Mamy już wystarczająco dużo wiadomości by napisać program, który zapali nam diodę dajmy na to na wyjściu RA.3 , a pozostawi zgaszoną na wyjściu RA.4.

Zaczynamy oczywiście od najważniejszego, czyli procedur konfiguracyjnych:

;***PLIKI KONFIGURACYJNE***

processor 16f84

;wpisujemy typ naszego PIC-a

__config _XT_OSC & _PWRTE_ON & _CP_OFF & _WDT_OFF

;konfigurujemy oscylator kwarcowy i wyłączamy watchdoga

include <p16f84a.inc>

;dołączamy plik p16f84a.inc , w którym znajdują się nazwy wszystkich rejestrów PIC-a z ;przypisanymi adresami

Dodam, że po znaku ; umieszczamy komentarze, które później naprawdę się przydają. Nawet te najprostsze operacje warto obarczyć krótkim opisem. Najlepiej krótkim i zrozumiałym dla każdego, a najbardziej dla nas samych.

Procedura konfiguracyjna została już omówiona wcześniej, więc przejdźmy już do głównego programu. Naszym celem jest podanie 01000 na PORTA. Czwarty bit ma być jedynka co znaczy, że spowoduje włączenie odpowiednio dołączonej diody. 01000 binarnie to liczba 8 heksalnie. Tyle wprowadzenie teraz zajmijmy się programem. Cały PORTA ma tutaj pracować jako wyjścia danych czyli musimy go ustawić jako wyjście. Na ustawianie portów mamy dwa sposobu. Możemy to zrobić tak jak już wcześniej zaczęliśmy czyli:

;***USTAWIENIE PORTÓW I/O***

BSF STATUS,5 ;idziemy do BANK1

MOVLW 00h ;wpisujemy zero do rejestru w

MOVWF TRISA ;przepisujemy zawartość w do rejestru TRISA odpowiedzialnego za ;ustawienie portów (PORTA)

BCF STATUS,5 ;powracamy do BANK0, gdzie wykonuje się większość operacji

Drugi sposób jest krótszy i zachęcam Ci do jego stosowania:

;***USTAWIENIE PORTÓW I/O***

MOVLW 00h ;wpisujemy zero do w

TRIS PORTA ;procedura wpisująca zawartość w do rejestrów PORTA odpowiedzialnych za ;ustawienie portów, czyli do TRISA

Polecenie TRIS wykonuje trzy czynności. Najpierw zmienia obszar na BANK1. Następnie wpisuje zawartość w do rejestru TRIS podanego portu, a na końcu znowu wraca do BANK0. Znakomite ułatwienie, nie sądzisz?

Główna część programu jest w tym przypadku najkrótsza. Oczywiście musimy wpisać tą wspomnianą wcześniej ósemkę heksanie lub 01000 binarnie do PORTA. Mam nadzieję, że sam poradzisz sobie z tym zadaniem. Ja wspomnę tylko o przymusie zapętlenia programu. Procesor jeżeli jest włączony to ciągle pracują (na razie pomijamy różne tryby wstrzymania czy watchdogi), dlatego musimy go czymś zająć w większości przypadków wystarczy zakończyć program komendą end, a kompilator się wszystkim zajmie. My jednak lubimy być zabezpieczeni przed nie lubianymi wpadkami i sami zadbamy oto by procesor miał co robić. Skorzystamy tutaj z funkcji goto, czyli idź do.

Twój kod wpisujący 8 do PORTA

Program

GOTO Program ;idź do procedury Program

END ;kompilator tego wymaga

Procesor natrafia na procedurę (podprogram, jak zwał tak zwał) o nazwie Program, przechodzi dalej i trafia na komendę wysyłającą go do wcześniej zdefiniowanej procedury Program. Procesor posłusznie wykonuje rozkaz i jest zapętlony w nieskończoność. Oczywiście często zdarzy się, że będziemy musieli wyprowadzać program z takich pętli. Tym jednak zajmiemy się później. Twój program równie dobrze mógłby wyglądać tak:

Program

Twój kod wpisujący 8 do PORTA

GOTO Program ;idź do program i kolejny raz wpis 8 do PORTA

END ;kompilator tego wymaga

Jak widać programy wykonujące te samą funkcję możemy napisać na kilka różnych sposobów. Najczęściej jest tak, że wybieramy ten najoptymalniejszy lub ten, który najbardziej nam odpowiada. W przyszłości jeszcze nie raz przekonasz się o wielorakich możliwościach rozwiązania tego samego problemu.

Co już umiesz, powinieneś umieć?

Zapoznaliśmy się ze sposobem ustawiania konfiguracji nieodłącznym dla każdego programu. Umiemy wpisywać liczby do dowolnych rejestrów, a także zerować i ustawiać zarówno całe rejestry jaki i ich pojedyncze bity. Potrafimy zapętlić program i tworzyć własne procedury. Wiedza ta doprowadziła Cię mam nadzieję do stworzenia pierwszego programu. Nic wielkiego ledwie potrafisz zapalić diodę. Ona nawet nie mruga. Na większe projekty przyjdzie jednak czas. Te pierwsze programy, które wykonujemy samodzielnie są bardzo ważne i wymagają dużo cierpliwości, dlatego gratuluję Ci pierwszego własnoręcznie napisanego programu i do zobaczenia w kolejnym wpisie. Jeżeli masz chwilę czasu zostaw tutaj jakiś komentarz odnośnie jakości tekstu, problemów, czy chociażby pochwal się pierwszym programem.

Uruchamianie programatora

Łączenie kabla

Po tym jak złożymy już cały programator, musimy zmierzyć się z kolejnym etapem jakim jest odpowiednie połączenie dwóch wtyczek. Potrzebne nam będą dwie wtyczki w obudowie i jedno złącze do montażu na płytce. Mamy tutaj pewną dowolność. Jako złącze do montażu na płytce możemy użyć wtyku męskiego lub żeńskiego, oczywiście wtyk, który będziemy wpinać w to złącze musi być analogicznie żeńskie lub męskie. Chodzi generalnie o to, żeby jakoś to wyglądało i zapewniło komunikacje z komputerem.

Numer pinu, który znajduję się na schemacie odpowiada danemu pinowi w porcie komputera. Do komputera możemy wpiąć jedynie wtyczkę męską. Na tym złączu męskim mamy wygraberowanie małe numerki pinów. Nasze zadanie polega na podłączeniu pinu nr.2 ze schematu z pinem nr.2 w tej właśnie wtyczce męskiej, nr.3 z nr.3 itd. Piszę o tym, ponieważ numeracja pinów pomiędzy złączem na płytce, a wtykiem do niej pasującym może być niezgodna. Nie przejmujemy się tym po prostu numer 3 ma być numer 3 w kończącej (tej podłączanej do komputera) wtyczce męskiej.Wszystkie piny we wtyczce, które mają być podłączone do masy mostkujemy i przesyłamy jednym przewodem. Akurat są to kolejne piny, więc sprawa jest zdecydowanie ułatwiona.

Jak już zarobimy wszystkie wtyczki możemy wstępnie miernikiem sprawdzić przejścia między odpowiednim pinem ostatniej wtyczki, a odpowiednimi padami na płytce. Jeżeli wszystko jest gotowe przechodzimy do etapu testowania.

Test napięciowy

Nie ma tu o czym pisać. Podłączamy programator do zasilania. Na razie nie przejmujemy się kablem LPT i zostawiamy go w spokoju.  Bierzemy miernik i sprawdzamy napięcia w dwóch punktach układu. Na nóżce LM7812, gdzie powinno być ok. 13,2V w zależności od egzemplarzu użytych diod oraz na nóżce LM7805, gdzie musi panować napięcie równe 5V. Jeżeli nie mamy napięcia 13,2V, które nawiasem mówiąc jest bardzo ważne, to sprawdzamy napięcie na jego nóżce o nazwie GND. Powinno tam być napięcie równe dwóm spadkom napięć na przewodzących diodach. Jeżeli jest inaczej sprawdzamy dokładnie kierunek wlutowania. Diody powinny być wlutowane czarnym paskiem w stronę masy, czyli mówiąc fachowo w kierunku przewodzenia. W przypadku, gdy diody są poprawnie wlutowane, a napięcia nie ma musimy sprawdzić czy dobre są diody, ewentualnie układ LM7812. Przypominam o przymusie podania napięcia ok. 17V. Ze zbyt małym napięciem monolityczne stabilizatory wykorzystane w układzie mogą nie działać.

Analogicznie sprawdzamy czy jest obecne 5V. Dla spokoju sumienia czy nasz scalak z wyjściami OC jest poprawnie zasilony (5V na VCC)  i możemy przejść do podłączenia kabla.

Test programatora

Jak już wspomniałem programator, który najprawdopodobniej właśnie zrobiłeś, posiada bardzo dobre, lekkie i darmowe oprogramowanie Oshon Parallere Port PIC Programing, który można poprać ze strony producenta. Oprócz tych niewątpliwych zalet posiada dodatkowo możliwość testu naszego programatora. W celu wykonania takiego sprawdzenia otwieramy program, wchodzimy w zakładkę Hardwere i dalej Check. Pojawia nam się okno z pięcioma możliwościami do zaznaczenia.

-Enable VDD line, po włączeniu powinna zaświecić się dioda LED2
-Enable VPP line, po włączeniu powinna zaświecić się dioda LED1
-Enable CLOCK line, musimy miernikiem sprawdzić czy na linii CLOCK (RB7) pojawia się jedynka logiczna (5V) po włączeniu
-Enable DATA OUT line, analogicznie jak w poprzednim

Jeżeli wszystko działa poprawnie możemy już przejść do uruchomienia pierwszego programu testowego. To jednak zostanie opisane w kolejnym artykule. W przypadku, gdy włączenie któreś funkcji nie daję żadnego rezultatu lub nie działa poprawnie mechanizm postępowania jest następujący. Sprawdzamy czy na odpowiednik pinie na wejściu LPT pojawia się jedynka logiczna. Jeżeli nie to znaczy, że popełniliśmy jakiś błąd podczas zarabiania kabla. Następnie sprawdzamy czy sygnał pojawia się na wyjściu inwerterów. Jego brak może być spowodowany nie podłączanym zasilaniem do układu, nie zwarciem nie wykorzystanych wejść do masy czy do plusa zasilania lub zniszczonym układem scalonym. w Przypadku, gdy na wyjściu układu pojawiają się odpowiednie stany logiczne, a programator nie działa tak jak powinien sprawdzenie zaczynamy od napięć zasilania w poszczególnych punktach układu. Na wyjściach stabilizatorów, na emiterach tranzystorów i na rezystorach podciągających na wyjściach bramek.

Mam nadzieje, że ten opis chociażby w małym stopniu pomoże niektórym uruchomić pierwszy programator i zaprogramować pierwszy mikrokontroler, ponieważ jest to naprawdę świetna zabawa. Niestety wymaga ona poświęcenia trochę czasu i nerwów na pierwsze uruchomienie ;). Podsumowując, jeżeli nie działa nie poddajemy się tylko szukamy błędu w układzie, próbujemy wymyślanych rozwiązań i programujemy.


Wyszukiwarka

Podobne podstrony:
Programowalny zegar cyfrowy przełącznik za pomocą mikrokontrolera PIC, opis
Programowalny zegar cyfrowy przełącznik za pomocą mikrokontrolera PIC opis
ZL5PRG Programator mikrokontrol Nieznany
AVR i ARM7 Programowanie mikrokontrolerow dla kazdego avrar7
Podstawy Programowania Mikrokontrolera 8051
Programowanie mikrokontrolerow 8051 w jezyku C
Galka Galka Podstawy Programowania Mikrokontrolera 8051
Podstawy programowania mikrokontrolera 8051
Mikrokontrolery PIC w praktycznych zastosowaniach mipicp
Podstawy programowania mikrokontrolerów AVR8 w środowisku AVR Studio 4
Podstawy programowania mikrokontrolera 8051(300dpi)
programowanie mikrokontrolerów
kurs programowania w języku ms basic, Programowanie mikrokontrolerów
Podstawy programowania mikrokontrolera 8051
Programowanie mikrokontrolerów za pomocą programatora USBasp
programator mikrokomputerow 89C Nieznany
proj, Edukacja, studia, Semestr V, Programowanie Mikrokomputerów, Ćwiczenia
ZL5PRG Programator mikrokontrol Nieznany

więcej podobnych podstron