AVR i ARM7. Programowanie mikrokontrolerów dla każdego [PDF]

  • 0 0 0
  • Gefällt Ihnen dieses papier und der download? Sie können Ihre eigene PDF-Datei in wenigen Minuten kostenlos online veröffentlichen! Anmelden
Datei wird geladen, bitte warten...
Zitiervorschau

Wszelkie prawa zastrzeżone. Nieautoryzowane rozpowszechnianie całości lub fragmentu niniejszej publikacji w jakiejkolwiek postaci jest zabronione. Wykonywanie kopii metodą kserograficzną, fotograficzną, a także kopiowanie książki na nośniku filmowym, magnetycznym lub innym powoduje naruszenie praw autorskich niniejszej publikacji. Wszystkie znaki występujące w tekście są zastrzeżonymi znakami firmowymi bądź towarowymi ich właścicieli. Autor oraz Wydawnictwo HELION dołożyli wszelkich starań, by zawarte w tej książce informacje były kompletne i rzetelne. Nie biorą jednak żadnej odpowiedzialności ani za ich wykorzystanie, ani za związane z tym ewentualne naruszenie praw patentowych lub autorskich. Autor oraz Wydawnictwo HELION nie ponoszą również żadnej odpowiedzialności za ewentualne szkody wynikłe z wykorzystania informacji zawartych w książce. Redakcja: Michał Mrowiec Projekt okładki: Mateusz Obarek, Maciej Pokoński Fotografia na okładce została wykorzystana za zgodą iStockPhoto Inc. Wydawnictwo HELION ul. Kościuszki 1c, 44-100 GLIWICE tel. 32 231 22 19, 32 230 98 63 e-mail: [email protected] WWW: http://helion.pl (księgarnia internetowa, katalog książek) Drogi Czytelniku! Jeżeli chcesz ocenić tę książkę, zajrzyj pod adres http://helion.pl/user/opinie?avrar7_ebook Możesz tam wpisać swoje uwagi, spostrzeżenia, recenzję. Pliki z przykładami omawianymi w książce można znaleźć pod adresem: ftp://ftp.helion.pl/przyklady/avrar7_ebook.zip ISBN: 978-83-246-4504-6 Copyright © Helion 2010 Printed in Poland.

• Poleć książkę na Facebook.com • Kup w wersji papierowej • Oceń książkę

• Księgarnia internetowa • Lubię to! » Nasza społeczność

Spis treści

Wstęp ...............................................................................................................................

Poszukiwacze zaginionych portów, czyli jak zacząć przygodę z mikrokontrolerami ....

Część I Programowanie mikrokontrolerów z rodziny AVR ....................

Lekcja 1. Instalacja oprogramowania ............................................................................. 1.1. Kompilatory ....................................................................................................................15 1.1.1. AVR Studio ......................................................................................................................15 1.1.2. WinAVR ..........................................................................................................................17 1.1.3. Bascom .............................................................................................................................18 1.1.4. MikroPascal for AVR .......................................................................................................20 1.2. Programy ładujące ...........................................................................................................21 1.2.1. PonyProg2000 ..................................................................................................................21 1.2.2. AVRdude ..........................................................................................................................23

Lekcja 2. Cztery i pół metody zdobycia programatora ................................................... 2.1. Sample Electronics cable programmer — programator podłączany do portu LPT ............27 2.2. SI Prog — programator podłączany do portu COM ..........................................................28 2.2.1. Montaż programatora .......................................................................................................28 2.2.2. Montaż adaptera ...............................................................................................................34 2.2.3. Konfiguracja PonyProg2000 ............................................................................................37 2.3. USBasp — programator podłączany do portu USB ..........................................................37 2.3.1. Montaż programatora .......................................................................................................37 2.3.2. Podłączanie USBasp do komputera (system Windows) ...................................................44 2.3.3. Praca USBasp z AVRdude ...............................................................................................46 2.3.4. Praca USBasp z AVR Studio ...........................................................................................46 2.3.5. Praca USBasp ze środowiskiem Bascom ..........................................................................47 2.3.6. Praca USBasp z pakietem WinAVR .................................................................................48 2.4. USBasp — zakup kontrolowany ......................................................................................49 2.5. Pół metody zdobycia programatora ..................................................................................50 2.6. Jak zaprogramować pozostałe układy AVR? ....................................................................50

Lekcja 3. Zaświecenie diody LED ................................................................................... 3.1. Asembler .........................................................................................................................55 3.2. Język C ............................................................................................................................62 3.3. Bascom ................................................................................................................... .........65 3.4. Pascal ..............................................................................................................................68 3.5. Ćwiczenia ........................................................................................................................71

4

Spis treści

Lekcja 4. Mruganie diody LED ......................................................................................... 4.1. Asembler .........................................................................................................................73 4.2. Język C ............................................................................................................................79 4.3. Bascom ............................................................................................................................83 4.4. Pascal ..............................................................................................................................85 4.5. Ćwiczenia ........................................................................................................................86

Lekcja 5. Obsługa wyświetlacza LED ............................................................................. 5.1. Asembler .........................................................................................................................91 5.2. Język C ..........................................................................................................................106 5.3. Bascom ..........................................................................................................................111 5.4. Pascal ............................................................................................................................114 5.5. Ćwiczenia ......................................................................................................................118

Lekcja 6. Obsługa przycisku ...........................................................................................1 6.1. Asembler .......................................................................................................................127 6.2. Język C ..........................................................................................................................132 6.3. Bascom ..........................................................................................................................135 6.4. Pascal ............................................................................................................................138 6.5. Ćwiczenia ......................................................................................................................141

Lekcja 7. Obsługa klawiatury .........................................................................................1 7.1. Asembler .......................................................................................................................146 7.2. Język C ..........................................................................................................................159 7.3. Bascom ..........................................................................................................................165 7.4. Pascal ............................................................................................................................170 7.5. Ćwiczenia ......................................................................................................................176

Lekcja 8. Obsługa przerwań, a przy tym o bitach konfiguracyjnych i śpiochach słów p 8.1. Asembler .......................................................................................................................191 8.2. Język C ..........................................................................................................................204 8.3. Bascom ..........................................................................................................................210 8.4. Pascal ............................................................................................................................217 8.5. Ćwiczenia ......................................................................................................................223

Lekcja 9. Obsługa wyświetlacza alfanumerycznego LCD .............................................. 9.1. Asembler ................................................................................................................. ......229 9.2. Język C ..........................................................................................................................251 9.3. Bascom ..........................................................................................................................264 9.4. Pascal ............................................................................................................................269 9.5. Ćwiczenia ......................................................................................................................275

Lekcja 10. …a zakończą część pierwszą dwa słowa: USART, EEPROM… ..................... 10.1. Asembler ................................................................................................................................279 10.2. Język C ...................................................................................................................................293 10.3. Bascom ...................................................................................................................................298 10.4. Pascal .....................................................................................................................................304 10.5. Ćwiczenia ...............................................................................................................................309

Część II Programowanie mikrokontrolerów z rdzeniem ARM7 .............

Lekcja 11. Instalacja oprogramowania, przygotowanie oprzyrządowania .................... 11.1. Instalacja środowisk programistycznych Keil uVision3 i WinARM oraz programu ładującego Flash Magic .................................................................................314 11.2. Opis zestawu uruchomieniowego ARE0068 ..........................................................................317

Spis treści

5

Lekcja 12. Igraszki z diodami LED .................................................................................. 12.1. Język C ........................................................................................................................324 12.2. Asembler .....................................................................................................................337 12.3. Ćwiczenia ....................................................................................................................358

Lekcja 13. Obsługa przycisków ......................................................................................3 13.1. Język C ........................................................................................................................361 13.2. Asembler .....................................................................................................................369 13.3. Ćwiczenia ....................................................................................................................385

Lekcja 14. Przerwania sprzętowe ................................................................................... 14.1. Język C ........................................................................................................................392 14.2. Asembler .....................................................................................................................398 14.3. Ćwiczenia ....................................................................................................................408

Lekcja 15. Obsługa wyświetlacza graficznego z telefonu Siemens S65. Część 1. ........ 15.1. Język C ........................................................................................................................415 15.2. Asembler .....................................................................................................................431 15.3. Ćwiczenia ....................................................................................................................439

Lekcja 16. Obsługa wyświetlacza graficznego z telefonu Siemens S65. Część 2. ........ 16.1. Język C ........................................................................................................................443 16.2. Asembler .....................................................................................................................457 16.3. Ćwiczenia ....................................................................................................................464

Lekcja 17. Serwomechanizmy w lewo zwrot, czyli jak zaprogramować ruch robota ... 17.1. Język C ........................................................................................................................471 17.2. Asembler .....................................................................................................................482 17.3. Ćwiczenia ....................................................................................................................488

Lekcja 18. Mały krok w kierunku systemów czasu rzeczywistego — FreeRTOS ..........

Skorowidz ......................................................................................................................51

6

Spis treści

Wstęp Po opublikowaniu mojej książki o programowaniu Windows przyszedł do mnie na egzamin student i już w drzwiach oznajmił: — Panie doktorze, uczyłem się z pańskiego podręcznika z przyjemnością, bo czułem się tak, jakbym był u pana na zajęciach. Odparłem mu ze śmiertelną powagą w głosie: — Panie studencie, przed chwilą próbował pan uzyskać moją przychylność tanim komplementem. Otóż oświadczam panu, że próba się udała i jest pan teraz bardzo blisko upragnionej piątki z egzaminu. Szanowny Czytelniku, przytoczyłem tę anegdotę, by już na wstępie wytłumaczyć się z nie do końca poważnej formy tej książki. Prowadząc zajęcia ze studentami, staram się, by nikt, łącznie ze mną, nie był nimi znudzony. Podobna idea przyś wiecała mi podczas tworzenia niniejszego podręcznika: odczuwałem radość z jego pisania i chciałem, by jego lektura była dla Czytelnika fascynującą, zaprawioną odrobiną humoru przygodą. A jest to książka — o czym informuje tytuł — dla każdego. I dla tych, którzy chcą się z niej czegoś nauczyć — ci niewątpliwie czegoś się nauczą. I dla tych, którzy nie chcą się z niej niczego nauczyć — ci niewątpliwie niczego się z niej nie nauczą. Jeśli chodzi o umiejętności, które przydadzą się podczas lektury: tylko kiedy przedstawiam kod w języku C, zakładam, że Czytelnik posiada podstawową znajomość jego składni (choć i tę staram się tłumaczyć, o ile to możliwe). Pozostałe języki programowania, szczególnie preferowany przeze mnie asembler, wprowadzam od podstaw. Moim zamierzeniem było, by podręcznik mógł służyć jak najszerszemu kręgowi odbiorców — od gimnazjalisty po profesora zwyczajnego. Przy okazji chcę obalić mit, jakoby programowanie mikrokontrolerów było trudnym działem informatyki. Akurat jest odwrotnie. Programowanie mikrokontrolerów należy nie tylko do łatwych, ale jednocześnie do piękniejszych dziedzin ludzkiej wiedzy. I jako takie, moim zdaniem, powinno na stałe zagościć w programach nauczania informatyki w szkołach średnich, najlepiej wraz z możliwością konstruowania i programowania robotów. Rozmarzyłem się nieco, a tu trzeba jeszcze powiedzieć kilka słów o układzie podręcznika. Podzieliłem go na dwie części i uznałem, że to dobry pomysł. Części podzieliłem na lekcje i ten pomysł wprawił mnie w zachwyt. Lekcje podzieliłem na paragrafy dotyczące poszczególnych języków programowania i moja radość z tego powodu nie miała końca. W paragrafach umieściłem nieco treści i tak powsta ła ta książka. Część pierwszą poświęciłem omówieniu programowania mikrokontrolerów rodziny AVR. Jeśli chodzi bowiem o naukę podstaw programowania mikrokontrolerów, układy AVR — tak myślę — nadają się do tego najlepiej. W ramach każdej lekcji omawiam cztery języki programowania AVR, przy czym asembler traktuję jako język podstawowy (większość zagadnień technicznych i programistycznych analizuję właśnie w paragrafie dotyczącym asemblera). Jak korzystać z tych lekcji? Otóż proponuję, aby Czytelnik wybrał preferowany przez siebie język programowania wyższego poziomu i czytał paragrafy dotyczące asemblera plus wybrany przez siebie język. Po każdej lekcji Czytelnik może sprawdzić swoje

8

AVR i ARM7. Programowanie mikrokontrolerów dla każde

umiejętności, wykonując zaproponowane ćwiczenia. Część drugą podręcznika poświęciłem omówieniu potężnej rodziny mikrokontrolerów z rdzeniem ARM7. Omawiam tu dwa języki programowania: C i asembler, lecz jako podstawowy, odmiennie niż w części pierwszej, traktuję język C. Dlaczego podstawowymi jednostkami w tej książce są lekcje, a nie — jak wymaga tradycja — rozdziały? Otóż chciałem, aby podręcznik kojarzył się Czytelnikowi z czymś przyjemnym. A nie ma nic przyjemniejszego nad szkołę i naukę w niej! Niech ilustracją tej tezy — myśli przewodniej podręcznika — będzie opowieść o małej Hani, którą przedstawia rysunek 1. Opowieść dydaktyczna z cyklu Mała Hania bawi się ze swoimi lalkami w szkołę

Aha, jeszcze jedna rzecz wymaga wyjaśnienia. Na zajęciach ze studentami lubię od czasu do czasu coś zabawnego narysować na tablicy, aby dać odpocząć oczom od gęsto zapisanego kodu. Także i w niniejszym podręczniku znajdzie się kilka zabawnych, a jednocześnie edukacyjnych historyjek. Mam nadzieję, że szanowny Czytelnik zaakceptuje ten drobny zabieg dydaktyczny. Życzę przyjemnej lektury. Na pytania i sugestie czekam pod adresem [email protected].

1

Tak ma być: nie rysunek 1.5, nie rysunek A, lecz po prostu rysunek. Taki mały żart na początek. Nie jest śmieszny? A to przepraszam.

Poszukiwacze zaginionych portów, czyli jak zacząć przygodę z mikrokontrolerami

9

Poszukiwacze zaginionych portów, czyli jak zacząć przygodę z mikrokontrolerami Istnieje w literaturze naukowej dziwny zwyczaj, według którego treść książki powinna odpowiadać temu, co zostało zawarte w tytule. Pół biedy z tym obyczajem — ostatecznie można go jakoś obejść. Gorzej, że istnieje zwyczaj drugi, stokroć okropniejszy od poprzedniego. Otóż wymaga się, by pojęcia występujące w tytule zostały w pracy zdefiniowane. Jako autor muszę poddać się tej tradycji i co nieco powiedzieć o mikrokontrolerach. Czymże jest mikrokontroler? Pierwszą próbę definicji prezentuje rysunek W.1. Rysunek W.1. Opowiastka dydaktyczna pod tytułem Co to jest mikrokontroler?

Tak, przyznaję, definicja zilustrowana na rysunku W.1 jest dość ogólna. Więc może inaczej: wyobraźmy sobie mikroprocesor. Właściwie nie musimy go sobie wyobrażać, przedstawia go rysunek W.2.

10

AVR i ARM7. Programowanie mikrokontrolerów dla każde

Rysunek W.2. Mikroprocesor, czyli jednostka arytmetyczno-logiczna

Mikroprocesor, czyli jednostkę arytmetyczno-logiczną, symbolizuje Zuzanna. Umieszczenie w tej roli kobiety jest z mojej strony postępkiem słusznym i roztropnym. Mikroprocesor jaki jest, każdy widzi. Wiemy, że taki mikroprocesor, jaki przywykliśmy spotykać w komputerze, jest poza nim bezużyteczny, gdyż do działania potrzebuje odpowiedniego interfejsu: pamięci RAM, karty graficznej, oprzyrządowania płyty głównej itd. Wszystko, co z zewnątrz dołączamy do procesora, nazywamy peryferiami. A teraz wyobraźmy sobie, że do takiej jednostki arytmetyczno-logicznej dołączamy urządzenia peryferyjne i wszystko umieszczamy w jednej obudowie. Tak powstaje mikrokontroler (patrz rysunek W.3). Rysunek W.3. Mikrokontroler, czyli jednostka arytmetyczno-logiczna plus peryferia

Mikrokontroler, w odróżnieniu od mikroprocesora, od razu po podłączeniu zasilania jest gotowy do użycia. Do podanej definicji należy także dodać pojęcie tak zwanej architektury harwardzkiej (szczególnie często występującej w mikrokontrolerach), w której przestrzeń pamięci programu i danych jest rozdzielona. Nasze zainteresowanie mikrokontrolerami nie byłoby tak duże, gdyby nie możliwość ich programowania. Stało się to możliwe dzięki obecności pamięci programowalnej, na przykład pamięci flash. Ponieważ można ją programować co najmniej kilka tysięcy razy, otwiera się przed nami wielkie pole do działania (ściślej: prób). W programowaniu mikrokontrolerów możemy wyróżnić trzy etapy pracy: 1. Pisanie programu na komputerze. Producenci mikrokontrolerów często nieodpłatnie udostępniają na swoich stronach internetowych kompilatory przeznaczone do pracy z konkretną rodziną mikrokontrolerów. Za pomocą tych lub innych kompilatorów definiujemy w programie sposób działania mikrokontrolera. (Ten etap pracy został przedstawiony na rysunku W.4).

Poszukiwacze zaginionych portów, czyli jak zacząć przygodę z mikrokontrolerami

11

Rysunek W.4. Pierwszy etap pracy programowania mikrokontrolerów — pisanie programu na komputerze

2. Kompilacja zbudowanego programu. W wyniku poprawnej kompilacji powinniśmy otrzymać plik wynikowy, w którym zawarty będzie stworzony program, lecz przedstawiony w języku maszynowym. My najczęściej będziemy używać pliku szesnastkowego z rozszerzeniem *.hex (patrz rysunek W.5). Rysunek W.5. Drugi etap pracy programowania mikrokontrolerów — kompilacja

3. Programowanie mikrokontrolera. Na tym etapie zbudowany przez nas kod wgrywamy do pamięci flash. Aby proces był możliwy, potrzebne są trzy elementy: program ładujący, programator i adapter, w którym zostanie umieszczony mikrokontroler. Program ładujący wysyła dane z komputera do mikrokontrolera. W tym procesie najczęściej pośredniczy urządzenie zwane programatorem. Do programatora zaś powinien być podłączony mikrokontroler (patrz rysunek W.6). Jak zatem zacząć przygodę z mikrokontrolerami? Po pierwsze trzeba mieć tę książkę, zgodnie ze słowami popularnej piosenki: Już nie chodź ty struty jak strucla bez masła, Bo nie wiesz, dlaczego znów dioda ci zgasła, Bank pełen pieniędzy bezczelnie ty złup, Tę książkę w księgarni najbliższej wnet kup.

12

AVR i ARM7. Programowanie mikrokontrolerów dla każde

Rysunek W.6. Oprzyrządowanie potrzebne do programowania mikrokontrolera

Jak wspomniałem, w podręczniku zawarłem omówienie bardzo popularnych układów AVR i ARM7. Na początku proponuję zainteresować się układami AVR. Potrzebny będzie mikrokontroler ATmega8, który za kilka złotych można kupić w każdym sklepie elektronicznym. Następnie należy zbudować lub kupić programator wraz z adapterem, co pozwoli programować mikrokontroler (jak zbudować najprostsze programatory, pokażę w lekcji 2.). Niezbędne oprogramowanie znajduje się na dołączonym do książki nośniku CD. No i trzeba programować, jak najwięcej programować. A jeśli przeczytasz już całą książkę, to co dalej? Pragnę Cię zapewnić, szanowny Czytelniku, że po przestudiowaniu niniejszego podręcznika staniesz się prawdziwym ekspertem w dziedzinie programowania mikrokontrolerów. Przyznaję jednak, że ta pozycja nie stanowi pełnego kompendium wiedzy o mikrokontrolerach. Jej rolą jest próba zapełnienia luki w polskiej literaturze przedmiotu, brak bowiem podręcznika wprowadzaj ącego w zagadnienie programowania mikrokontrolerów. Poszerzenie wiedzy zdobytej dzięki tej książce umożliwią Czytelnikowi coraz liczniejsze podręczniki dotyczące mikrokontrolerów AVR i ARM7. Nieocenionym źródłem wiedzy i pomocy są fora internetowe — gorąco namawiam do korzystania z nich. Oto niektóre fora polskojęzyczne: http://www.elektroda.pl/rtvforum/forum12.html — forum, na którym można znaleźć wskazówki dotyczące każdej rodziny mikrokontrolerów. http://forum.mikrokontrolery.net — forum poświęcone różnym rodzinom mikrokontrolerów jednoukładowych. http://www.ekiert.com/cgi-bin/YaBB/YaBB.pl — Polskie Forum Microchipa zawierające informacje o nieomówionej w podręczniku rodzinie mikrokontrolerów PIC. Niektóre fora anglojęzyczne: http://www.avrfreaks.net, http://www.embeddedrelated.com, http://www.keil.com/forum. A więc… Do startu! Gotowi? Start!

Część I

Programowanie mikrokontrolerów z rodziny AVR

14

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

♦ Instalacja oprogramowania Lekcja 1

15

Lekcja 1

Instalacja oprogramowania 1.1. Kompilatory W przygodzie z programowaniem mikrokontrolerów z rodziny AVR towarzyszyć nam będą cztery kompilatory: AVR Studio — kompilator języka asembler dla AVR. WinAVR — pakiet programów dla AVR z kompilatorem języka C. Bascom — kompilator języka BASIC dla AVR. MikroPascal for AVR — kompilator języka Pascal dla AVR. Dwa pierwsze są darmowe, natomiast kompilatory Bascom i mikroPascal for AVR dostępne są w wersjach demo, z czego wynikają oczywiście ograniczenia dotyczące wielkości kompilowanego kodu. Słowo „kompilator” nie oddaje w pełni możliwości wymienionych programów. Są to właściwie środowiska programistyczne, składające się z kompilatora danego języka, programu ładującego, często także symulatora. Niektóre kompilatory wspomagają nawzajem swoje działanie. Na przykład możliwe jest programowanie w języku C w środowisku AVR Studio, o ile mamy zainstalowany kompilator WinAVR. Możliwe jest także takie skonfigurowanie AVR Studio i Bascom, by współpracował y z programem ładującym AVRdude. To jednak zostanie pokazane w następnej lekcji.

1.1.1. AVR Studio Środowisko AVR Studio to darmowe oprogramowanie rozpowszechniane przez firmę Atmel, twórcę rodziny mikrokontrolerów AVR (strona internetowa http://www.atmel.com). Instalacja środowiska AVR Studio jest bardzo prosta. Na płycie CD w folderze C1_AVR\Programy\Kompilatory\asm znajduje się plik aStudio4b589.exe. Po uruchomieniu tej aplikacji zobaczymy okno informujące o przygotowywaniu ustawień procesu instalacji (patrz rysunek 1.1). Klikamy przycisk Next, po czym akceptujemy umowę licencyjną (patrz rysunek 1.2). Następnie dwukrotnie klikamy przycisk Next, aż wreszcie kliknięciem przycisku Install rozpoczynamy proces instalowania środowiska AVR Studio na dysku twardym (patrz rysunek 1.3). Proces instalacji przebiega automatycznie. Po jego zakończeniu pozostaje nam zamknąć okno programu instalacyjnego — wystarczy kliknąć przycisk Finish (patrz rysunek 1.4).

16 Rysunek 1.1. Pierwsze okno procesu instalacji

Rysunek 1.2. Okno umowy licencyjnej

Rysunek 1.3. Okno wizualizujące postęp instalacji

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

♦ Instalacja oprogramowania Lekcja 1

17

Rysunek 1.4.

Okno kończące proces instalacji środowiska AVR Studio

1.1.2. WinAVR Plik instalacyjny środowiska WinAVR znajdziemy na nośniku CD w folderze C1_AVR\Programy\ Kompilatory\C. WinAVR stanowi pakiet darmowego oprogramowania, rozpowszechnianego na zasadach licencji GNU General Public Licence (jego najnowszą wersję można pobrać ze strony http://winavr.sourceforge.net/). Plik znajdujący się na nośniku CD nosi nazwę WinAVR-20080411-install.exe. Po jego uruchomieniu otrzymamy możliwość wyboru języka instalacji (patrz rysunek 1.5). Rysunek 1.5. Okno umożliwiające wybór języka instalacji

Następne okno poinformuje nas o rozpoczęciu procesu instalacji. Należy wybrać przycisk Dalej, po czym ukaże się umowa licencyjna. Nie ma innej możliwości, jak tylko zgodzić się na przedstawione warunki, inaczej instalacja nie będzie kontynuowana. W końcu zobaczymy okno wyboru docelowej lokalizacji instalowanego środowiska (patrz rysunek 1.6). Rysunek 1.6. Okno wyboru lokalizacji instalacji

18

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

Jeżeli akceptujemy wybrany folder, klikamy przycisk Dalej. W następnym oknie będziemy mieć możliwość wyboru komponentów do zainstalowania. Dobrym krokiem będzie wybranie wszystkich elementów. Po kliknięciu przycisku Zainstaluj rozpocznie się właściwa część procesu instalacji, czyli przenoszenie danych na dysk. O postępie instalacji będzie nas informował pasek postępu (patrz rysunek 1.7). Rysunek 1.7. Okno wizualizujące postęp instalacji

Proces przebiega automatycznie. Po jego zakończeniu pozostaje nam tylko zamknąć program instalacyjny (patrz rysunek 1.8). Rysunek 1.8. Okno informujące o zakończeniu procesu instalacji

1.1.3. Bascom Kompilator Bascom to oprogramowanie komercyjne, rozwijane przez firmę MCS Electronics (strona internetowa http://www.mcselec.com). Udostępniana jest darmowa wersja demo programu umożliwiającego kompilację kodu o rozmiarze nie większym niż 4 KB. Znajdujący się na dołączonym do książki nośniku CD plik setupdemo.exe zainstaluje na komputerze właśnie wersję demo kompilatora. W celu uruchomienia programu instalacyjnego należy przejść do folderu C1_AVR\Programy\Kompilatory\Bascom. Pierwsze okno instalatora powita nas i poprosi o zamknięcie innych działających w tym czasie aplikacji (patrz rysunek 1.9). Klikamy przycisk Next i przechodzimy do okna przedstawiającego licencję produktu. Po zaakceptowaniu przedstawionych zasad użytkowania produktu i po kliknięciu przycisku Next zobaczymy okno informujące o ograniczeniach wersji demo oprogramowania (patrz rysunek 1.10).

♦ Instalacja oprogramowania Lekcja 1

19

Rysunek 1.9. Okno informujące o rozpoczęciu procesu instalacji programu Bascom w wersji demo

Rysunek 1.10. Okno informacji o wersji demo

Klikamy przycisk Next. W następnym oknie otrzymamy możliwość wyboru folderu docelowego dla kompilatora Bascom. Klikamy dwukrotnie przycisk Next, by rozpocząć etap automatycznej instalacji oprogramowania. Zakończenie procesu instalacji zostanie zasygnalizowane oknem, w którym jednocześnie znajdzie się informacja o konieczności zrestartowania komputera (patrz rysunek 1.11). Rysunek 1.11. Okno kończące proces instalacji programu Bascom

20

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

1.1.4. MikroPascal for AVR Kompilator mikroPascal for AVR to środowisko składające się głównie z kompilatora języka Pascal dla AVR, disasemblera i programu ładującego. Oprogramowanie jest rozwijane przez firmę mikroElektronika Associates (strona internetowa http://www.mikroe.com). Zawarty na dołączonym do książki nośniku CD plik mikroPascal_pro_AVR_2008_Build.1.25 zainstaluje na dysku wersję demo środowiska, które zawiera ograniczenie wielkości kompilowanego kodu do 2 KB. Program instalacyjny znajdziemy w folderze C1_AVR\Programy\Kompilatory\Pascal. Po jego uruchomieniu zobaczymy okno, takie jak na rysunku 1.12. Rysunek 1.12. Okno powitalne programu instalacyjnego mikroPascal

Po kliknięciu przycisku Next zapoznamy się z umową licencyjną, którą musimy zaakceptować. Następnie zostaną nam zaproponowane komponenty do zainstalowania. Jest wielce wskazane, by wraz z kompilatorem zainstalować też przykłady (patrz rysunek 1.13). Rysunek 1.13. Okno wyboru komponentów do zainstalowania

Następne okno umożliwi wybór folderu, w którym zostaną zainstalowane komponenty oprogramowania (patrz rysunek 1.14). Po kliknięciu przycisku Install rozpocznie się automatyczny proces instalacji oprogramowania. O jego zakończeniu poinformuje nas okno w postaci podobnej do tej z rysunku 1.15. Na tym jednak nie kończy się instalacja środowiska mikroPascal for AVR. W następnym oknie zostanie nam zaproponowana instalacja programu ładującego AVRFLASH. Jeśli nie mamy układu EasyAVR5, instalacja tego komponentu nie jest konieczna.

♦ Instalacja oprogramowania Lekcja 1

21

Rysunek 1.14. Okno wyboru folderu do instalacji

Rysunek 1.15. Okno informujące o zakończeniu procesu instalacji

1.2. Programy ładujące 1.2.1. PonyProg2000 PonyProg2000 jest darmowym programem ładującym, którego najnowszą wersję można pobrać ze strony http://www.lancos.com. Oprogramowanie współpracuje między innymi z bardzo dobrym programatorem SI Prog. Plik instalacyjny wersji 2.06f znalazł się na dołączonym do książki nośniku CD w folderze C1_AVR\Programy\Program_ładujace\PonyProg2000. Po jego uruchomieniu i kliknięciu przycisku Tak zostaniemy poinformowani o rozpoczęciu procesu instalacyjnego (patrz rysunek 1.16). W następnym kroku musimy zgodzić się na warunki licencji, po czym wybieramy folder do instalacji oprogramowania. Klikamy dwukrotnie przycisk Next, po czym za pomocą przycisku Install rozpoczynamy automatyczny etap procesu instalacji (patrz rysunek 1.17). Ponieważ program jest niewielki, proces powinien szybko się skończyć, o czym poinformuje stosowne okno (patrz rysunek 1.18). Podczas pierwszego uruchomienia programu zostaniemy poproszeni o skalibrowanie oprogramowania z prędkością działania procesora (patrz rysunek 1.19).

22 Rysunek 1.16. Okno informujące o instalacji programu PonyProg2000

Rysunek 1.17. Rozpoczęcie etapu automatycznej instalacji

Rysunek 1.18. Okno informujące o zakończeniu procesu instalacji

Rysunek 1.19. Okno kalibracji programu

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

♦ Instalacja oprogramowania Lekcja 1

23

Zanim zgodzimy się na przeprowadzenie tej operacji, sprawdźmy, czy nie mamy zbyt wielu programów otworzonych w tle, co może zafałszować wyniki. Po kilku sekundach zobaczymy okno informujące o zakończeniu procesu kalibracji (patrz rysunek 1.20). Rysunek 1.20. Okno informujące o zakończeniu procesu kalibracji

Ostatnią czynnością przygotowującą program PonyProg2000 do pracy jest wybranie właściwego modelu mikrokontrolera. Ponieważ będziemy programować układ ATmega8, należy go wybrać w sposób pokazany na rysunku 1.21. Rysunek 1.21. Wybór układu do programowania

1.2.2. AVRdude AVRdude jest darmowym programem ładującym, którego najnowszą wersję można pobrać ze strony http:// savannah.nongnu.org/projects/avrdude. Oprogramowania AVRdude nie trzeba instalować. Na dołączonym do książki nośniku CD w folderze C1_AVR\Programy\Programy_ladujace\AVRdude znajduje się plik avrdude-5.5.tar, który należy rozpakować w dowolnym miejscu na dysku. Ponieważ AVRdude jest oprogramowaniem przeznaczonym do pracy w systemie Linux, w utworzonym folderze avrdude-5.5 nie znajdziemy pliku typu *.exe. Można co prawda skompilować program AVRdude w środowisku Cygwin, ale nie będziemy tego robić. Zainstalowane komponenty posłużą nam do umieszczenia w folderze systemowym biblioteki giveio.sys, niezbędnej do obsługi portów szeregowych i równoległych. W tym celu przechodzimy do folderu avrdude-5.5\windows, w którym uruchamiamy plik wsadowy install_giveio.bat. Transfer biblioteki giveio.sys wykonany zostanie automatycznie (patrz rysunek 1.22). Rysunek 1.22. Instalacja biblioteki giveio.sys za pomocą pliku install_giveio.bat

24

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

Pracę w systemie Windows umożliwią komponenty, które zainstalujemy za pomocą pliku avr-dude___ graphic_interface_1154.exe. Jest to samorozpakowujące się archiwum rar. Plik należy umieścić na przykład w folderze avrdude-5.5 i przy uruchomieniu go rozpakować w tej lokalizacji (patrz rysunek 1.23). Rysunek 1.23. Samorozpakowujące się archiwum z programem AVRdude

W folderze avr-dude + graphic interface znajduje się plik avrdude.exe, którym oprogramowujemy mikrokontrolery, wydając rozkazy z linii komend. Pracę z programem wspomaga avrdude-gui.exe, który stanowi nakładkę na avrdude.exe. Jednak o wiele lepszą nakładką jest aplikacja AVR8_Burn-O-Mat, której najnowszą wersję można pobrać ze strony http://avr8-burn-o-mat.aaabbb.de/avr8_burn_o_mat_avrdude_gui_en.html. Warunkiem działania tej aplikacji jest zainstalowana Java. Aby korzystać z aplikacji AVR8_Burn-O-Mat, należy w dowolnym miejscu rozpakować znajdujący się na nośniku CD plik AVR8_Burn-O-Mat.zip. Program uruchamiamy za pomocą pliku wsadowego start.bat. AVR8_Burn-O-Mat korzysta z programu AVRdude, którego komponentów domyślnie szuka w lokalizacji C:\WinAVR\bin. Jeżeli ich tam nie znajdzie, podczas pierwszego uruchomienia zobaczymy informację systemową o braku określonej ścieżki. Konieczna będzie zmiana konfiguracji AVR8_Burn-O-Mat. W tym celu wybieramy Settings/AVRDUDE (patrz rysunek 1.24). Rysunek 1.24. Uruchomienie okna ustawień aplikacji AVR8_Burn-O-Mat

Zobaczymy okno ustawień aplikacji, w którym przede wszystkim należy podać ścieżki do plików avrdude.exe i avrdude.conf. Ustawienia okna Programmer będą zależały od używanego programatora (patrz rysunek 1.25).

♦ Instalacja oprogramowania Lekcja 1

25

Rysunek 1.25. Właściwe ustawienia aplikacji AVR8_Burn-O-Mat

Ponieważ podczas kolejnych lekcji będziemy pracować z mikrokontrolerem ATmega8, ostatnim ustawieniem będzie wybranie właśnie tego układu. Robimy to w oknie głównym aplikacji AVR8_Burn-O-Mat (patrz rysunek 1.26). Rysunek 1.26. Wybór typu mikrokontrolera

26

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

Lekcja 2

Cztery i pół metody zdobycia programatora Nie da się programować mikrokontrolerów bez odpowiedniego programatora. W niniejszej lekcji zaprezentowane zostaną cztery i pół metody zdobycia programatora, przy czym trzy pierwsze metody są związane z koniecznością samodzielnego konstruowania. Jeśli to zadanie wydaje się zbyt trudne, proponuję czwartą metodę zdobycia programatora — jego zakup. Aby praca z tą częścią podręcznika była efektywna, wykorzystamy programatory SI Prog oraz USBasp, z adapterem będącym jednocześnie płytką uruchomieniową, o konstrukcji opisanej w punkcie 2.2.2.

2.1.Sample Electronics cable programmer — programator podłączany do portu LP Niewątpliwie programatorem o najprostszej konstrukcji jest Sample Electronics cable programmer, wspierany przez program ładujący środowiska Bascom. Ten programator składa się z wtyczki portu LPT (tak zwanej wtyczki męskiej) oraz kilku kabli łączących wtyczkę z mikrokontrolerem (patrz rysunek 2.1). Rysunek 2.1. Schemat programatora Sample Electronics cable programmer

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

28

Szczegółowy opis połączeń znajduje się w tabeli 2.1. Opis połączeń programatora Sample Electronics cable programmer Tabela 2.1. Pin LPT

Pin układu ATmega8

2

PB3 (MOSI)

4PC6

(RESET)

5

PB5 (SCK)

11

PB4 (MISO)

18

GND

W celu zabezpieczenia portu LPT przed uszkodzeniem można między połączeniami 2-MOSI, 4-RESET oraz 5-SCK umieścić rezystory 330 Ω. Nie jest to jednak konieczne. Należy za to pamiętać, by podczas programowania do mikrokontrolera podłączone było zasilanie około 5 V, z wspólną masą idącą do pinu 18. portu LPT. W celu skonfigurowania środowiska Bascom do pracy z programatorem należy wybrać Options/Programmer, następnie w oknie opcji programatora wskazać typ używanego urządzenia (patrz rysunek 2.2). Rysunek 2.2. Okno opcji programatora środowiska Bascom

Środowisko jest już gotowe do pracy z programatorem. Program ładujący środowiska Bascom uruchamiamy za pomocą klawisza F4 lub wybierając ikonę programatora (patrz rysunek 2.3). Rysunek 2.3. Ikona programatora

Podczas pierwszego uruchomienia programu ładującego należy wybrać typ programowanego układu. W naszym przypadku jest to ATmega8 (patrz rysunek 2.4).

2.2.SI Prog — programator podłączany do portu CO 2.2.1. Montaż programatora Programator SI Prog jest jednym z najbardziej niezawodnych układów służących programowaniu mikrokontrolerów AVR. Jest też stosunkowo łatwy do zrobienia. Jednym z atutów SI Prog jest możliwość jego użytkowania bez konieczności dołączania zewnętrznego zasilania — w większości przypadków wystarczy

♦ Cztery i pół metody zdobycia programatora Lekcja 2

29

Rysunek 2.4. Okno programu ładującego

prąd pobierany z gniazda COM komputera. Pomysłodawcą programatora jest Claudio Lanconelli, który na stronie internetowej http://www.lancos.com rozpowszechnia za darmo program ładujący PonyProg2000, obsługujący programator SI Prog. Sam programator jest zbudowany na schemacie dostępnym także na tej stronie (patrz rysunek 2.5). Rysunek 2.5. Schemat programatora SI Prog

Proponuję zbudować programator samodzielnie. Do przedstawionego schematu wprowadzimy dwie modyfikacje. Po pierwsze pominiemy obwód oznaczony jako zewnętrzny obwód zasilania (external power circuit). Z tego wynika, że diody D1, D2 i D3 połączymy bezpośrednio z wejściem Vin układu U3. Po drugie

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

30

zamiast proponowanego stabilizatora LM2936Z-5 użyjemy LM7805. Układ ten dla naszych zastosowań jest wystarczająco dobry, tani i (w przeciwieństwie do LM2936Z-5) łatwy do zdobycia. Całość oprzemy na popularnej płytce uniwersalnej o układzie ścieżek przedstawionym na rysunku 2.6. Rysunek 2.6. Układ ścieżek płytki uniwersalnej

Wiele firm produkuje płytki o podobnym układzie ścieżek i każda z nich nadaje płytce własny numer. My będziemy płytkę oznaczać symbolem U-08. Szczegółowy spis części potrzebnych do zbudowania programatora SI Prog zawiera tabela 2.2. Spis elementów programatora SI Prog Tabela 2.2. Lp.

Oznaczenie na schemacieCzęść

1. 2. 3. 4. 5. 6. 7.

U-08 U3 C12 C3, C4 D1, D2, D3 Z1, Z2, Z3 R1

Płytka uniwersalna Stabilizator 78L05 Kondensator 100nF 2 kondensatory 47uF 3 diody 1N4148 3 diody Zenera 5V1 Rezystor 15k

8. 9. 10. 11. 12. 13. 14. 15. 16.

R3 R4, R5, R6 Q1 -

Rezystor 10k 3 rezystory 4k Tranzystor BC547 Gniazdo DS-09 Obudowa do gniazda DS-09 Złącze WF-02S Listwa kołkowa pojedyncza 5 styków do obudowy Obudowa ML05

17. 18.

-

Przewód wielożyłowy FLAT10 Dwie wtyczki IDC-10

W tabeli wymieniono także dwie wtyczki, które wraz z częścią kabla wielożyłowego posłużą do skonstruowania połączenia programatora z adapterem. Co to jest adapter? Jest to układ, w którym znajduje się mikrokontroler przeznaczony do programowania. Nasz adapter będzie jednocześnie płytką uruchomieniową dla mikrokontrolera ATmega8. Gorąco namawiam do jego zbudowania, gdyż będzie nam służył także w przypadku używania programatora USBasp. W tabeli 2.3 wyszczególniono części konieczne do skonstruowania adaptera.

♦ Cztery i pół metody zdobycia programatora Lekcja 2

31

Części do budowy adaptera Tabela 2.3. Lp.

Oznaczenie na schemacieCzęść

1.

U-08

Płytka uniwersalna

2. 3. 4. 5.

C1, C2, C3 R1 -

3 kondensatory 100nF Rezystor 10k Podstawka zwykła Złącze WF-02S

Dodatkowo może się przydać cewka indukcyjna 10uH. W przypadku jej braku wystarczy rezystor około 100 Ω. Nie są to artykuły pierwszej potrzeby, dlatego nie zostały uwzględnione w tabeli. Zaczynamy od montażu programatora. Proponuję w pierwszej kolejności zająć się destrukcją, czyli przerwaniem ścieżek. Odwracamy płytkę i ostrym narzędziem przecinamy dwie ścieżki w miejscu oznaczonym na rysunku 2.7 czarną kreską. Rysunek 2.7. Miejsce przerwania ścieżek

Drugim etapem pracy jest przylutowanie części do przedniej strony płytki uniwersalnej zgodnie ze schematem przedstawionym na rysunku 2.8. Rysunek 2.8. Schemat rozmieszczenia elementów programatora na przedniej stronie płytki uniwersalnej

32

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

Łączenie ścieżek najlepiej wykonać za pomocą nieizolowanego drutu. Na rysunku 2.9 widzimy płytkę U-08 z połączonymi ścieżkami. Rysunek 2.9. Płytka ze ścieżkami połączonymi nieizolowanym drutem

W następnym kroku można wlutować listwy kołkowe (patrz rysunek 2.10). Rysunek 2.10. Widok płytki uniwersalnej z wlutowanymi listwami kołkowymi

Teraz wlutujemy części elektroniczne. Po wykonaniu tej czynności powinniśmy zobaczyć widok mniej więcej podobny do tego z rysunku 2.11. Rysunek 2.11. Gotowa przednia część płytki

♦ Cztery i pół metody zdobycia programatora Lekcja 2

33

Najważniejsze (i najtrudniejsze) za nami. Przekręcamy płytkę. Teraz połączymy ścieżki zgodnie ze schematem przedstawionym na rysunku 2.12 (czarne linie oznaczają miejsca łączeń). Rysunek 2.12. Schemat połączeń na spodniej stronie płytki

Po wykonaniu tej czynności możemy sobie pogratulować, gdyż właśnie skonstruowaliśmy programator uniwersalny SI Prog. Teraz zajmiemy się komunikacją z komputerem. Schemat połączenia programatora z wtyczką portu COM został zaprezentowany na rysunku 2.13. Rysunek 2.13. Schemat połączenia programatora z portem COM

Połączenie można zrobić za pomocą przewodu wielożyłowego, który nie powinien mieć więcej niż około metr długości. Od strony komputera kabel lutujemy do gniazda DS-09, a od strony programatora zakładamy styki i obudowę. Jeśli wszystko przebiegło prawidłowo, programator powinien już móc komunikować się z komputerem. Został nam do zbudowania adapter, za pomocą którego możliwe będzie programowanie układu ATmega8. Zanim go jednak zbudujemy, w pierwszej kolejności skonstruujemy połączenie między programatorem a adapterem. Rozkład wyprowadzeń zbudowanego przez nas programatora przedstawia rysunek 2.14. Jak widać, jedno wyprowadzenie (LED) jest przez nas nieużywane. Służy ono do podłączenia diody, która swoim światłem sygnalizuje proces programowania. Przedstawiony rozkład wyprowadzeń jest zgodny ze standardem programatorów AVR firmy Atmel. Identyczny rozkład w adapterze sprawia, że połączenie programatora z adapterem może zostać zrealizowane za pomoc ą kabla wielożyłowego i dwóch wtyczek IDC-10 (patrz rysunek 2.15).

34

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

Rysunek 2.14. Rozkład wyprowadzeń programatora SI Prog

Rysunek 2.15. Przewód z dwiema wtyczkami służący połączeniu programatora z adapterem

2.2.2. Montaż adaptera Zgodnie z zapowiedzią budowany przez nas adapter będzie jednocześnie płytką uruchomieniową. Dodatkowo może się przydać cewka indukcyjna 10uH. W przypadku jej braku wystarczy rezystor około 100 Ω. Zadanie konstruowania adaptera podzielimy na trzy łatwe etapy. Etap pierwszy: przerwanie ścieżki na płytce uniwersalnej w miejscu oznaczonym na schemacie czarną linią (patrz rysunek 2.16). Rysunek 2.16. Schemat pierwszego etapu budowy adaptera

Etap drugi: umieszczenie części adaptera na przedniej stronie płytki uniwersalnej. Zadanie to realizujemy według schematu przedstawionego na rysunku 2.17.

♦ Cztery i pół metody zdobycia programatora Lekcja 2

35

Rysunek 2.17. Schemat drugiego etapu budowy adaptera

Zacznijmy od umieszczenia części listwy kołkowej w miejscach do tego przygotowanych. Następnie wykonamy połączenia za pomocą nieizolowanego przewodnika (patrz rysunek 2.18). Rysunek 2.18. Płytka z wmontowanymi listwami kołkowymi i zrealizowanymi połączeniami

Czas na kondensatory 100nF. Umieszczamy je w celu odkłócenia napięcia zasilającego. Dokumentacja mikrokontrolera ATmega8 podaje, że należy to zrobić także z końcówką AREF, nawet jeśli nie będziemy z niej korzystać, a to ze względu na wewnętrzne połączenie ze źródłem napięcia odniesienia. Ważne jest także, aby kondensatory zostały umieszczone jak najbliżej wyprowadzeń mikrokontrolera. Połączymy wyprowadzenie RESET z napięciem zasilającym za pomocą rezystora 10k i wlutujemy podstawkę pod mikrokontroler. Przednią część płytki mamy już gotową (patrz rysunek 2.19). Etap trzeci: dodanie łączeń na spodniej stronie płytki uniwersalnej. Zadanie zrealizujemy według schematu przedstawionego na rysunku 2.20. L1 oznacza cewkę indukcyjną 10uH. Jej umieszczenie między AVCC a napięciem zasilającym ma sprawić, by wartości odbierane przez przetwornik ADC były maksymalnie pozbawione szumu. Zgodnie z tym, co napisałem wcześniej, można ją zastąpić rezystorem 100 Ω. Ostatecznie można zlutować linię AVCC z napięciem zasilania i wielkiej tragedii nie będzie. Co najwyżej pierwsze trzy bity odczytu przetwornika ADC będą nieco zwariowane. Wykonujemy jeszcze siedem razy niezbyt forsowne lutowanie. Ostatni etap to wykonanie trzech połączeń za pomocą izolowanego przewodnika, oznaczonych na schemacie liniami przerywanymi. To wszystko. Zbudowaliśmy układ do pracy z mikrokontrolerami AVR. Rysunek 2.21 przedstawia programator SI Prog z adapterem.

36 Rysunek 2.19. Widok gotowej przedniej części płytki

Rysunek 2.20. Schemat trzeciego etapu konstrukcji adaptera

Rysunek 2.21. Gotowy zestaw w czasie pracy

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

♦ Cztery i pół metody zdobycia programatora Lekcja 2

37

2.2.3. Konfiguracja PonyProg2000 Skonstruowany przez nas programator obsługuje program ładujący PonyProg2000, który należy właściwie skonfigurować. Uruchamiamy Setup (patrz rysunek 2.22). Rysunek 2.22. Setup PonyProg2000

W oknie ustawień programu PonyProg2000 należy wybrać programator SI Prog, a następnie ustawić port, pod który podłączyliśmy programator (patrz rysunek 2.23). Rysunek 2.23. Konfiguracja PonyProg2000

2.3.USBasp — programator podłączany do portu US 2.3.1. Montaż programatora Przedstawione w poprzednich paragrafach konstrukcje programatorów powinny zaspokoić wymagania większości programistów. Coraz częściej jednak pracujemy na komputerach przenośnych, które nie posiadają ani portu LPT, ani portu COM, a jedynie gniazda USB. Z tego wynika potrzeba zbudowania programatorów podłączanych do portu USB. Przedstawicielem rodziny programatorów podłączanych do portu USB jest USBasp. Może początkowo brzmieć jak żart informacja, że do jego budowy konieczny będzie zaprogramowany mikrokontroler ATmega8. Wpadamy w klasyczną antynomię: do budowy programatora potrzebny nam będzie mikrokontroler, który możemy zaprogramować jedynie za pomocą programatora. Aby rozwiązać ten problem, proponuję wykonać jedną z poniższych czynności: W pierwszej kolejności zbudować programator Sample Electronics cable programmer i za jego pomocą zaprogramować mikrokontroler. W pierwszej kolejności zbudować programator SI Prog i za jego pomocą zaprogramować mikrokontroler. Dorwać programator koleżanki/kolegi i za jego pomocą zaprogramować mikrokontroler.

38

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

Podsumujmy: zanim zaczniemy budować programator, musimy zaprogramować mikrokontroler ATmega8. Mikrokontroler programujemy plikiem usbasp.atmega8.2007-07-23.hex, który znajduje się na dołączonym do książki nośniku CD, w katalogu C1_AVR\Pliki. Wskazówki dotyczące sposobu programowania mikrokontrolerów początkujący programiści mikrokontrolerów znajdą w lekcji 3. Następnie, ze względu na konieczność taktowania mikrokontrolera w układzie za pomocą zewnętrznego rezonatora, należy odpowiednio ustawić fuse bity. Oto jakie powinny mieć wartości: RSTDISBL = 1 WDTON = 1 SPIEN = 0 CKOPT = 0 EESAVE = 1 BOOTSZ1 = 0 BOOTSZ0 = 0 BOOTRST = 1 BODLEVEL = 1 BODEN = 1 SUT1 = 1 SUT0 = 0 CKSEL3 = 1 CKSEL2 = 1 CKSEL1 = 1 CKSEL0 = 1 Znowu początkujący programiści mają kłopot: co to są fuse bity i jak się je ustawia? Szerzej odpowiem na te pytania w lekcji 8. Na razie powinna wystarczyć informacja, że fuse bity są częścią pamięci trwałej mikrokontrolera i za ich pomocą ustawiamy sposób pracy układu. Jeżeli programujemy mikrokontroler za pomocą programu PonyProg, okno ustawień fuse bitów otwieramy za pomocą kombinacji klawiszy Ctrl+S lub wybierając Command/Security and Configuration Bits (patrz rysunek 2.24). Rysunek 2.24. Uruchomienie okna ustawień fuse bitów w programie PonyProg2000

♦ Cztery i pół metody zdobycia programatora Lekcja 2

39

Aby zabezpieczyć się przed uszkodzeniem mikrokontrolera, dobrze jest w pierwszej kolejności odczytać fuse bity programowanego układu. W tym celu wybieramy przycisk Read. Następnie zmieniamy wartości bitów zgodnie z zamieszczoną listą, pamiętając jednak, że ustawienie bitu oznacza jego wyzerowanie. Innymi słowy, obok nazw bitów powinny być puste kwadraciki w tych miejscach, w których bity mają mieć wartość 1. Dla pewności warto porównać wynik swojej pracy z rysunkiem 2.25 — pokazano na nim poprawnie ustawione okno fuse bitów. Rysunek 2.25. Okno ustawień fuse bitów w PonyProg2000 skonfigurowanych w celu zaprogramowania układu programatora USBasp

Klikamy Write i oto ustawiliśmy fuse bity mikrokontrolera. Warto wiedzieć, że zgodnie z ustawioną właśnie przez nas konfiguracją mikrokontroler nie będzie pracował w układzie bez zewnętrznego rezonatora. Dlatego nie wpadajmy w panikę, jeżeli PonyProg2000 nagle zgłosi błąd mikrokontrolera. Najprawdopodobniej będzie to wynik braku rezonatora kwarcowego podpiętego do układu. Budowany przez nas programator USBasp ma taki rezonator, więc mikrokontroler będzie w nim efektywnie pracował. Jeśli natomiast używamy nakładki AVR8_Burn-O-Mat programu AVRdude, okno ustawień fuse bitów powinno wyglądać tak jak na rysunku 2.26. Rysunek 2.26. Okno ustawień fuse bitów w AVR8_Burn-O-Mat

Zakładam, że operacja przebiegła pomyślnie. Co dalej? Teraz będzie już bardzo łatwo. Śmiało można stwierdzić, że USBasp posiada jedną z prostszych konstrukcji wśród programatorów. Wszystko, co dotyczy tego programatora, można znaleźć na stronie http://www.fischl.de/usbasp/. Zrealizujemy interpretację programatora podaną przez J.A. de Groota. Schemat układu zaprezentowano na rysunku 2.27.

Rysunek 2.27. Programator USBasp (schemat zbudowany za pomocą programu EAGLE 5.0.0 Light)

40 Część ♦ I Programowanie mikrokontrolerów z rodziny AV

♦ Cztery i pół metody zdobycia programatora Lekcja 2

Zgodnie z zasadą, że małe jest piękne, uprościmy sobie pracę i zignorujemy połączenia RXD i TXD. Całość oprzemy na popularnej płytce uniwersalnej U-08, z którą zetknęliśmy się już w paragrafie poprzednim. Szczegółowy spis elementów wraz z ich oznaczeniami przedstawia tabela 2.4. Spis elementów potrzebnych do budowy programatora USBasp Tabela 2.4. Lp.

Oznaczenie na schemacie Część

1.

U-08

Płytka uniwersalna

2.

MEGA8-P

Układ ATmega8 w obudowie PDIP

3.

-

Podstawka pod układ ATmega8

4.

D1, D2

2 diody świecące (czerwona i zielona)

5.

C1, C2

2 kondensatory 18p

6.

C3

Kondensator 100n

7.

C5

Kondensator 10u

8.

R1

Rezystor 10k

9.

R2, R6

2 rezystory 68

10.

R4, R5

2 rezystory 390

11.

R7

Rezystor 1,5K

12.

Q2

Rezonator kwarcowy 12MHz

13.

-

Listwa kołkowa (pojedyncza lub podwójna)

14.

-

Kabel USB do drukarki (typu A-B)

15.

USB-B

Gniazdo USB typu B

Bardzo łatwe zadanie konstruowania programatora podzielimy na trzy jeszcze łatwiejsze etapy: 1. Przerwanie ścieżek dolnej części płytki uniwersalnej. 2. Przylutowanie elementów do górnej części płytki uniwersalnej. 3. Zlutowanie ścieżek dolnej części płytki uniwersalnej. Etap pierwszy, bardzo łatwy. Odwracamy płytkę uniwersalną. Przerywamy ścieżki w trzech miejscach oznaczonych na schemacie czarną linią (patrz rysunek 2.28). Rysunek 2.28. Schemat pierwszego etapu konstrukcji programatora USBasp

Etap drugi, niesamowicie łatwy. Naszym zadaniem będzie przylutowanie elementów programatora do przedniej strony płytki uniwersalnej w sposób pokazany na schemacie (patrz rysunek 2.29).

41

42

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

Rysunek 2.29. Schemat drugiego etapu konstrukcji programatora USBasp

Proponuję zacząć od łączenia ścieżek za pomocą nieizolowanego drutu. Po pierwszym etapie pracy płytka wygląda tak jak na rysunku 2.30. Rysunek 2.30. Widok płytki po połączeniu ścieżek

Na płytce można zauważyć miejsca przygotowane dla listew kołkowych i nieco większy otwór, który posłuży do przytwierdzenia gniazda USB. Teraz czas na przylutowanie części elektronicznych i listew kołkowych (patrz rysunek 2.31). Rysunek 2.31. Widok po wlutowaniu elementów elektronicznych i listew kołkowych

♦ Cztery i pół metody zdobycia programatora Lekcja 2

43

W końcu dodajemy podstawkę pod układ ATmega8 i gniazdo USB. W ten sposób powstała przednia część płytki uniwersalnej programatora (patrz rysunek 2.32). Rysunek 2.32. Gotowa przednia część płytki programatora

Tak dotarliśmy do końca etapu drugiego. Czas na etap trzeci, nieprawdopodobnie łatwy. Odwracamy płytkę uniwersalną. Łączymy ścieżki za pomocą cyny w miejscach oznaczonych na schemacie czarną linią (patrz rysunek 2.33). Rysunek 2.33. Schemat trzeciego etapu konstrukcji programatora USBasp

Dodatkowo w trzech miejscach musimy wlutować rezystory, bo nie miałem pomysłu, gdzie je umieścić na przedniej stronie płytki. Ostatnią czynnością, którą nawet trudno nazwać konkretnym etapem naszej pracy, jest włożenie zaprogramowanego wcześniej mikrokontrolera do podstawki. W gniazdo USB wtykamy końcówkę kabla i mamy gotowy programator USBasp! Czas na opisanie funkcji wlutowanych przez nas zworek. Ustalmy przy tym kolejność taką jak na rysunku 2.34. 1. Ustawienie zworki 1 (zwarcie pinów) powoduje, że programator USBasp staje się adapterem i możliwe jest załadowanie nowego oprogramowania do mikrokontrolera znajdującego się w programatorze bez konieczności jego wyjmowania. W trybie normalnej pracy zworka nie powinna być włączona. 2. Za pomocą tej zworki ustawiamy sposób zasilania adaptera. Jeśli zworka jest ustawiona (piny zwarte), zasilanie pobierane jest z portu USB. Zworka otworzona powoduje konieczność podłączenia zasilania do adaptera w czasie programowania mikrokontrolera.

44

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

Rysunek 2.34. Rozkład zworek programatora USBasp

3. Za pomocą tej zworki wybieramy prędkość pracy programowanego mikrokontrolera. Jeśli układ jest taktowany zegarem poniżej 1.5 MHz, zworka musi zostać ustawiona (piny zwarte). Mikrokontrolery ATmega8 domyślnie pracują z prędkością 1 MHz (jeśli nie zmieniliśmy ustawień fuse bitów), dlatego w tym przypadku zworka powinna być ustawiona. Do programowania mikrokontrolerów potrzebny będzie adapter. Proponuję skonstruować adapter będący jednocześnie płytką uruchomieniową w sposób opisany w paragrafie dotyczącym programatora SI Prog. Rozkład wyprowadzeń zbudowanego programatora USBasp jest identyczny z tym z programatora SI Prog, dlatego kabel łączeniowy i sam adapter będzie pasował znakomicie. Obraz programatora USBasp z adapterem został przedstawiony na rysunku 2.35. Rysunek 2.35. Gotowy zestaw w czasie pracy

2.3.2. Podłączanie USBasp do komputera (system Wind Do poprawnej pracy programatora potrzebne są sterowniki, które znajdziemy na dołączonym do książki nośniku CD, w folderze C1_AVR\Pliki. Plik win-driver.zip należy rozpakować w dowolnym miejscu na dysku komputera i zapamiętać jego lokalizację. Po podłączeniu programatora do komputera (najlepiej do tego celu użyć jednego z portów USB nieznajdującego się na przednim panelu komputera) system wykryje nowe urządzenie i otworzy się kreator znajdowania nowego sprzętu (patrz rysunek 2.36).

♦ Cztery i pół metody zdobycia programatora Lekcja 2

45

Rysunek 2.36. Okno kreatora znajdowania nowego sprzętu

Wybieramy opcję Zainstaluj z listy lub określonej lokalizacji, po czym zostaniemy poproszeni o wskazanie lokalizacji sterowników (patrz rysunek 2.37). Rysunek 2.37. Wybranie opcji Przeglądaj w celu wskazania lokalizacji sterowników

Wskazujemy lokalizację rozpakowanego folderu ze sterownikami (patrz rysunek 2.38). Rysunek 2.38. Wybór lokalizacji sterowników

Klikamy przycisk Dalej. Kolejne etapy instalacji będą się wykonywały automatycznie. System rozpocznie proces wyszukiwania sterowników, a następnie umieści pobrane dane w folderze systemowym. Pozostało nam już tylko zakończyć proces dodawania nowego sprzętu. Programator został zainstalowany i od tej pory będzie widziany jako USBasp. Należy pamiętać, by programator podłączać do tego samego gniazda USB, w przeciwnym wypadku będziemy zmuszeni instalować sterowniki od nowa.

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

46

2.3.3. Praca USBasp z AVRdude Programator USBasp współpracuje z programem AVRdude. Najprostszym sposobem pracy z AVRdude jest wydawanie komend z wiersza polecenia. Należy przejść do folderu avr-dude + graphic interface. W celu odczytania pamięci flash mikrokontrolera i umieszczenia jego zawartości w pliku o nazwie program.hex (zakładam, że pracujemy z mikrokontrolerem ATmega8) wydajemy polecenie: avrdude.exe -p m8 -c usbasp -U flash:r:"program.hex":i

Aby załadować pamięć flash mikrokontrolera zawartością pliku program.hex, wydajemy polecenie: avrdude.exe -p m8 -c usbasp -U flash:w:"program.hex":i

Można także pracować w interfejsie graficznym dzięki nakładce avrdude-gui.exe lub AVR8_Burn-O-Mat. Podczas pracy z AVR8_Burn-O-Mat należy w ustawieniach programu wybrać typ używanego programatora USBasp (patrz rysunek 2.39). Rysunek 2.39. Fragment okna ustawień aplikacji AVR8_Burn-O-Mat przedstawiający konfigurację do współpracy z programatorem USBasp

1

2.3.4. Praca USBasp z AVR Studio Współpraca AVR Studio z programatorem USBasp sprowadza się właściwie do umożliwienia uruchomienia programu AVRdude z interfejsu AVR Studio. W AVR Studio istnieje bowiem możliwość dodania zewnętrznego urządzenia. Aby tego dokonać, należy z menu wybrać Tools/Customize… (patrz rysunek 2.40). Rysunek 2.40. Rozkaz dodawania nowych narzędzi

1

W opracowaniu technik opisanych w punktach 2.3.4, 2.3.5 oraz 2.3.6 pomógł mi Jakub Malewicz (http://are.net.pl), za co składam mu serdeczne podziękowania.

♦ Cztery i pół metody zdobycia programatora Lekcja 2

47

W otworzonym oknie wybieramy zakładkę Tools i klikamy ikonę dodawania nowego urządzenia (patrz rysunek 2.41). Rysunek 2.41. Dodawanie nowego urządzenia

W oknie Menu contens wpisujemy nazwę, która będzie się kojarzyła z naszym programatorem, na przykład USBasp. Następnie w linii Command podajemy ścieżkę do programu avrdude.exe. W linii Arguments podajemy argumenty dla programu ładującego AVRdude. -p m8 -c usbasp -U flash:w:"lekcja3_1.hex":i

Dużą niedogodnością jest brak możliwości skorzystania ze zmiennych środowiskowych, co sprawia, że przy tworzeniu nowego projektu będziemy zmuszeni zmieniać nazwę pliku *.hex. W polu Initial directory nie wpisujemy nic. Dzięki temu folder projektu będzie folderem domyślnym. Poprawnie skonfigurowane okno Customize przedstawiono na rysunku 2.42. Rysunek 2.42. Okno dodawania urządzenia zewnętrznego skonfigurowane w celu współpracy z programem AVRdude

Zamykamy okno przyciskiem Close. Po dodaniu nowego urządzenia w menu Tools pojawi się rozkaz jego uruchomienia (patrz rysunek 2.43).

2.3.5. Praca USBasp ze środowiskiem Bascom

Środowisko Bascom umożliwia dodanie zewnętrznego programu ładującego. W celu jego dodania z menu wybieramy Options/Programmer (patrz rysunek 2.44).

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

48 Rysunek 2.43. Menu Tools z dodanym nowym urządzeniem

Rysunek 2.44. Otwieranie opcji programu ładującego

Otworzy się okno opcji programu ładującego. Z listy Programmer wybieramy External Programmer. Następnie w linii Program podajemy ścieżkę do pliku avrdude.exe. Jako parametry podajemy te same argumenty, które podaliśmy podczas konfiguracji środowiska AVR Studio. Jednak w przypadku środowiska Bascom możemy podać zmienną środowiskową{FILE} , którą wpisujemy w miejsce nazwy pliku. Dzięki temu nazwa pliku wysyłanego do AVRdude będzie za każdym razem odpowiadała nazwie aktualnie aktywnego projektu, a my nie będziemy musieli zmieniać podanej właśnie konfiguracji. Okno opcji programu ładującego z parametrami nakierowanymi na współpracę z AVRdude pokazuje rysunek 2.45. Rysunek 2.45. Okno opcji programu ładującego skonfigurowane do współpracy z AVRdude

2.3.6. Praca USBasp z pakietem WinAVR Środowisko WinAVR domyślnie jest skonfigurowane do pracy z AVRdude. Trudność polega na tym, że sterowniki nowych edycji WinAVR nie są kompatybilne ze starszymi sterownikami programatora USBasp. Brak zgodności objawia się następującym komunikatem: avrdude: error: could not find USB device "USBasp" with vid=0x16c0 pid=0x5dc

♦ Cztery i pół metody zdobycia programatora Lekcja 2

49

Co w takim przypadku? Wtedy należy zastąpić nowe sterowniki WinAVR tymi, których użyliśmy do instalacji USBasp. Ale po kolei. Do pracy w środowisku WinAVR potrzebny jest plik Makefile. Za jego pomocą możemy program skompilować, a następnie załadować go do pamięci flash mikrokontrolera. Więcej o tym pliku dowiemy się z lekcji 4. Natomiast w celu skonfigurowania WinAVR do pracy z programatorem USBasp należy edytować plik Makefile, a następnie znaleźć w nim wyrażenie postaci: AVRDUDE_PROGRAMMER =

stk500v2 Z prawej strony znaku= znajduje się nazwa używanego programatora (na przykład ). Należy tę nazwę zastąpić nazwą usbasp . Otrzymamy następujący wiersz: AVRDUDE_PROGRAMMER = usbasp

To wszystko. Jeśli natomiast występuje konflikt sterowników, o którym napisałem na początku, należy przejść do folderu bin pakietu WinAVR i zastąpić plik libusb0.dll biblioteką o tej samej nazwie, która występuje w folderze sterowników programatora USBasp, czyli w folderze win-driver (patrz punkt 2.3.2). Program AVRdude uruchamiamy z poziomu Programmer’s Notepad — wystarczy wybrać Tools/[WinAVR] Program (patrz rysunek 2.46). Rysunek 2.46. Uruchomienie programu ładującego z aplikacji Programmers Notepad [WinAVR]

2.4. USBasp — zakup kontrolowany Czwarta proponowana przeze mnie metoda zdobycia programatora jest adresowana do osób, które obawiają się, że brak im umiejętności koniecznych do samodzielnego jego skonstruowania. W internecie można znaleźć wiele ofert programatorów mikrokontrolerów AVR. Jedną z ciekawszych propozycji jest programator zgodny z USBasp, konstruowany przez studentów Politechniki Wrocławskiej, który można kupić za pośrednictwem strony internetowej http://are.net.pl (patrz rysunek 2.47). Rysunek 2.47. Programatory USBasp konstruowane przez studentów Politechniki Wrocławskiej (zdjęcie pochodzi z serwisu http://are.net.pl, wykorzystane za zgodą serwisu)

50

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

Programator jest dostępny w dwóch wersjach: w wersji podstawowej, oznaczonej ARE0014, oraz w wersji dwunapięciowej, oznaczonej ARE0045. Programator jest sprzętowo kompatybilny z programatorem USBasp, którego konstrukcję omówiłem w paragrafie 2.3. Stąd instalacja programatora jest identyczna z tą przedstawioną w punkcie 2.3.2. Najnowsze sterowniki oraz dokumentacja programatora są do pobrania ze strony http://are.net.pl. Do programatora można podłączyć płytkę uruchomieniową, której budowa została opisana w punkcie 2.2.2. Na przywołanej stronie internetowej można także kupić odpowiednie moduły, współpracujące z programatorem.

2.5. Pół metody zdobycia programatora Osoby nie w pełni usatysfakcjonowane przedstawionymi programatorami zachęcam do zajrzenia w otchłanie internetu. Słowa-klucze to na przykład AVR ISP, SI Prog, STK200, STK300, STK500. Można znaleźć ciekawe konstrukcje, można też co nieco kupić. Warto zwrócić uwagę na: Programator pana Adama Dybkowskiego. Szczegółowe informacje, schemat i program ładujący można znaleźć na stronie internetowej http://dybkowski.net. Programator komunikuje się z komputerem za pomocą portu LPT. Zestaw uruchomieniowy STK500 firmy Atmel (http://www.atmel.com), z programatorem równoległym.

2.6.Jak zaprogramować pozostałe układy AVR? Wiemy już, jak zaprogramować układ ATmega8. Ale może chcemy pracować z układem ATmega16, ATmega32, ATTiny2313… Co wtedy? Jak te układy podłączyć do programatora? Na szczęście jest to zadanie niezwykle proste2. Spójrzmy jeszcze raz na standardowy rozkład wyprowadzeń zastosowany w programatorze SI Prog (patrz rysunek 2.14). Wynika z niego, że w celu zaprogramowania mikrokontrolera z rodziny AVR należy połączyć programator z mikrokontrolerem za pomocą sześciu linii: MOSI, SCK, RST, MISO, VCC i GND, przy czym RST to skrótowy zapis linii RESET. Wystarczy w dokumentacji interesującego nas układu odnaleźć położenie wspomnianych linii, połączyć je z programatorem i gotowe. Dla przykładu podłączymy do programatora SI Prog układ ATmega16. Na rysunku 2.48 widać rozmieszczenie wyprowadzeń tego mikrokontrolera w popularnej obudowie PDIP. Rysunek 2.48. Rozmieszczenie wyprowadzeń mikrokontrolera ATmega16

2

Nawiasem mówiąc, ponieważ lubię siebie, będę w tej książce zadawał sobie wyłącznie łatwe pytania, abym bez trudu mógł na nie odpowiedzieć.

♦ Cztery i pół metody zdobycia programatora Lekcja 2

51

Połączenie układu z programatorem, jak już wspomniano, będzie polegało na podłączeniu do mikrokontrolera sześciu linii: MOSI, SCK, RST, MISO, VCC i GND (patrz rysunek 2.49).

Schemat podłączenia programatora SI Prog do mikrokontrolera ATmega16 Rysunek 2.49.

52

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

Lekcja 3

Zaświecenie diody LED Czas na pierwszy program. Co za wspaniała chwila, świat jest piękny, życie zachwyca pełnią swych barw! Będzie jeszcze piękniej, gdy postawimy pierwszy krok w nauce programowania mikrokontrolerów. Nie czekajmy dłużej! Start! W naszym pierwszym programie obsłużymy układ z diodą LED, tak by dioda świeciła. Ambitny plan, lecz poradzimy sobie doskonale. Anodę diody LED podłączymy do linii PB0, katodę zaś do linii PB1. Co mam na myśli, gdy piszę o anodzie i katodzie? Dioda, w tym przypadku dioda świecąca, ma ciekawą właściwość: przewodzi prąd tylko w jednym kierunku. Popatrzmy na rysunek 3.1. Rysunek 3.1. Schemat prawidłowego podłączenia diody LED do źródła zasilania

Z przedstawionego schematu wynika, że w celu zaświecenia diody LED anoda powinna być podłączona do końcówki „plus” zasilania, katoda do końcówki „minus”. Dzięki tej własności łatwo możemy odnaleźć odpowiednie wyprowadzenia diody. Dodatkowym ułatwieniem okazuje się to, że w diodach świecących nóżka anody jest najczęściej dłuższa od nóżki katody. A co to za tajemnicze linie PB0 i PB1? Proponuję teraz przyjrzeć się świeżo kupionemu mikrokontrolerowi — prócz wspaniałego korpusu ma jeszcze nóżki (jeżeli do tej pory nie wyrwaliśmy żadnej, ATmega8 powinna mieć ich 28). Nóżki, inaczej wyprowadzenia, służą do komunikacji mikrokontrolera ze światem. Dla lepszej identyfikacji każde wyprowadzenie ma swoją nazwę, która wskazuje na jego funkcję (na przykład VCC i GND służą do doprowadzenia zasilania). Korzystając ze schematu umieszczonego na rysunku 3.2, łatwo odnajdziemy też wyprowadzenia oznaczone PB0 i PB1 — oto i cała tajemnica. Wróćmy do zadania. Schemat obsługiwanego układu został przedstawiony na rysunku 3.3 1.

1

Należy wyraźnie podkreślić, że bezpośrednie podłączenie diody LED do układu umożliwia architektura mikrokontrolerów AVR. W przypadku podłączania diod do innych chipów koniecznie sprawdźmy w dokumentacji, czy taki zabieg jest dopuszczalny, czy raczej należy zabezpieczyć linię rezystorem.

54

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

Rysunek 3.2. Opis wyprowadzeń mikrokontrolera ATmega8 w obudowie PDIP

Rysunek 3.3. Schemat podłączenia diody LED do mikrokontrolera

Bardzo prawdopodobne, że na początku procesu nauki schemat tego typu nie będzie zbyt pomocny w prawidłowym podłączaniu urządzeń do mikrokontrolera. Ze schematu wynika bowiem, że do wyprowadzenia numer 14 układu ATmega8 należy podłączyć (przylutować?) anodę diody LED, a do wyprowadzenia numer 15 — katodę tej diody. Drogi Czytelniku, jeśli skonstruowałeś zaproponowaną przeze mnie płytkę uruchomieniową, łatwiejszy do odczytania będzie dla Ciebie taki schemat, jak ten pokazany na rysunku 3.4 (zakładam, że płytka jest zasilana bateryjką o napięciu około +5 V). Rysunek 3.4. Schemat podłączenia diody LED do płytki uruchomieniowej

♦ Zaświecenie diody LED Lekcja 3

55

3.1. Asembler Zagadnienia: Utworzenie nowego projektu. Dyrektywy kompilatora. Obsługa portów wejścia-wyjścia. Ładowanie programu do pamięci mikrokontrolera. Rejestry robocze. Spróbujmy dla omawianego układu napisać program w języku asemblera. W tym celu uruchamiamy program AVR Studio. W oknie powitania wybieramy przycisk New Project, po czym otworzy się okno tworzenia nowego projektu. W nim zaznaczamy typ projektu Atmel AVR Assembler, wpisujemy nazwę projektu (uwaga, AVR Studio nie lubi w nazwach plików polskich znaków!), wybieramy jego lokalizację i przechodzimy do następnego okna, klikając przycisk Next (patrz rysunek 3.5). Rysunek 3.5. Okno tworzenia projektu

Zobaczymy okno wyboru platformy uruchomieniowej i typu mikrokontrolera. W pierwszym oknie opcji Debug platform: zaznaczamy AVR Simulator, w oknie drugim Device: odszukujemy i zaznaczamy typ mikrokontrolera, czyli ATmega8 (patrz rysunek 3.6). Rysunek 3.6. Okno platformy uruchomieniowej i typu mikrokontrolera

Klikamy przycisk Finish i jesteśmy gotowi do pisania programu. Okno główne programu AVR Studio powinno wyglądać tak jak na rysunku 3.7 (widok okna głównego może się nieznacznie różnić w zależności od rozdzielczości ekranu monitora).

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

56

Okno główne programu AVR Studio 4 po uruchomieniu Rysunek 3.7.

Teraz zajmiemy się budowaniem odpowiedniego kodu. W pierwszej kolejności wpisujemy pięć dyrektyw dla kompilatora: .nolist .include "m8def.inc" .list .cseg .org 0

Dyrektywy kompilatora nie są instrukcjami. Są to informacje dla oprogramowania kompilującego program. Co oznaczają wpisane przez nas dyrektywy? Dowiemy się tego niebawem. Na razie najważniejsza dla nas informacja jest taka: wszystkie programy będziemy zaczynać od tych pięciu linijek. Przy okazji drobna uwaga: wielkość liter nie ma znaczenia. Domyślamy się, że dioda LED będzie świecić, jeśli przez linie PB0 i PB1 popłynie prąd. Dodatkowo linią PB0 musi płynąć prąd oznaczony plus (mówimy, że linia PB0 będzie miała wysoki stan logiczny), linia PB1 ma się zachowywać jak minus bateryjki (mówimy, że linia PB1 będzie w stanie niskim). Dla zrozumienia istoty programowania mikrokontrolerów bardzo ważne jest to, że PB0 i PB1 są częścią większej całości, którą nazywamy portem. Większość portów mikrokontrolerów rodziny AVR ma 8 linii. Mikrokontroler ATmega8 posiada 3 porty oznaczane B, C i D. Dlatego mamy linie PB0, PB1, …, PB7, siedem linii portu C — od PC0 do PC6 — i osiem linii portu D: PD0, PD1, …, PD7. Podstawowa, a jednocze śnie najłatwiejsza część programowania sprowadza się do odpowiedniego ustawienia linii portów, tak by były wejściowe lub wyjściowe, w stanie niskim lub wysokim. Otóż do obsługi każdego z portów mikrokontrolerów AVR przeznaczono po trzy specjalne rejestry (tak zwane rejestry funkcyjne) oznaczone PORTx, DDRx i PINx, gdzie x oznacza nazwę portu: A, B, C, D itd. Na proces konfiguracji linii portu składają się dwa etapy: 1. Wybranie kierunku pracy linii portu (wejście lub wyjście). Do realizacji tego zadania służy rejestr DDRx. 2. Ustawienie poziomu napięcia na linii. Ten etap pracy realizujemy za pomocą rejestru PORTx.

♦ Zaświecenie diody LED Lekcja 3

57

Relację zachodzącą między portem B mikrokontrolera ATmega8 a wyprowadzeniami układu w sposób schematyczny przedstawia rysunek 3.8. Rysunek 3.8. Schemat sposobu sterowania wyprowadzeniami portu B

Podsumujmy: po pierwsze linie PB0 i PB1 muszą być wyjściowe. Będzie tak wtedy, gdy ustawimy bity zerowy i pierwszy rejestru DDRB (inaczej mówiąc, nadamy tym bitom wartość 1). Do ustawienia bitów posłuży nam instrukcja sbio następującej składni: sbi rejestr_we_wy, numer_bitu

W miejscu oznaczonymrejestr_we_wy powinniśmy umieścić nazwę rejestru funkcyjnego DDRx lub PORTx. Ostatecznie bity zerowy i pierwszy ustawimy za pomocą następujących dwóch instrukcji: sbi DDRB, 0 sbi DDRB, 1

Z obu linii będzie płynął prąd. Na linii PB0 napięcie musi być wysokie, co osiągniemy w efekcie ustawienia bitu zerowego rejestru PORTB. sbi PORTB, 0

Na linii PB1 napięcie będzie niskie, jeśli wyzerujemy bit pierwszy rejestru PORTB. Posłużymy się instrukcją cbio następującej składni: cbi rejestr_we_wy, numer_bitu

Rozkaz jest podobny do instrukcjisbi, tylko zamiast ustawiać, zeruje bit o numerze numer_bitu w rejestrze DDRx lub PORTx. cbi PORTB, 1

Już prawie koniec programu. Brakuje nam jeszcze nieskończonej pętli, która zatrzyma proces czytania dalszych fragmentów pamięci mikrokontrolera. petla: rjmp petla

Rozkaz rjmpwymusza skok bezwarunkowy pod adres oznaczony etykietąpetla . Etykietą może być dowolny ciąg znaków (bez spacji i znaków polskich) zakończony dwukropkiem. W środowisku AVR Studio 4 etykieta nie może mieć więcej niż 31 znaków. Oto nasz pierwszy program w całości. Listing lekcja3_1.asm .nolist .include "m8def.inc" .list .cseg .org 0

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

58 sbi DDRB, 0 sbi DDRB, 1 sbi PORTB, 0 cbi PORTB, 1 petla: rjmp petla

Po poprawnym napisaniu programu należy go skompilować. Z menu wybieramy polecenie Build/Build lub naciskamy klawisz F7 (patrz rysunek 3.9). Rysunek 3.9. Uruchomienie procesu kompilacji

Właściwie przeprowadzony proces kompilacji zostanie zasygnalizowany w oknie Build następującym komentarzem: ATmega8 memory use summary [bytes]: Segment Begin End Code Data Used Size Use% --------------------------------------------------------------[.cseg] 0x000000 0x00000a 10 0 10 8192 0.1% [.dseg] 0x000060 0x000060 0 0 0 1024 0.0% [.eseg] 0x000000 0x000000 0 0 0 512 0.0% Assembly complete, 0 errors. 0 warnings

Na tym etapie pracy najczęściej przydarzającymi się błędami mogą być literówki. Na przykład brak przecinka w linii dziewiątej: sbi DDRB 1

kompilator zasygnalizuje następującym komentarzem: D:\...\lekcja3_1.asm(9): error: syntax error, unexpected INTEGER

Po poprawieniu błędów (o ile były) i skompilowaniu programu w katalogu roboczym powinniśmy zobaczyć plik o nazwie naszego projektu i rozszerzeniu *.hex. Jest to kod gotowy do załadowania do pamięci mikrokontrolera. Jeżeli pracujemy z programatorem SI Prog, w celu zapisania pamięci flash mikrokontrolera uruchamiamy program PonyProg2000, a następnie otwieramy plik typu *.hex (patrz rysunek 3.10). Rysunek 3.10. Ikona otwierania pliku w programie PonyProg2000

Na razie nasz program nie jest przesadnie duży. Pięć zastosowanych przez nas instrukcji daje 10 bajtów (patrz rysunek 3.11). Klikamy ikonę zapisu, a następnie potwierdzamy chęć zastąpienia poprzedniej zawartości mikrokontrolera (patrz rysunki 3.12 i 3.13).

♦ Zaświecenie diody LED Lekcja 3

59

Rysunek 3.11. Okno programu PonyProg2000 z załadowanym plikiem *.hex

Rysunek 3.12. Ikona zapisu pamięci flash mikrokontrolera

Rysunek 3.13. Okno potwierdzenia zapisu danych do mikrokontrolera

Jeżeli programator został właściwie podłączony, powinniśmy zobaczyć okno statusu zapisu danych oraz potwierdzenie poprawnie wykonanej operacji (patrz rysunki 3.14 i 3.15). Rysunek 3.14. Okno statusu zapisu danych do mikrokontrolera

Rysunek 3.15. Okno potwierdzenia poprawnie zapisanych danych

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

60

Może się jednak zdarzyć, że w trakcie procesu zapisywania pamięci mikrokontrolera wystąpi błąd. Wtedy zobaczymy komunikat pokazany na rysunku 3.16. Rysunek 3.16. Okno komunikatu błędu w zapisie danych

Komunikat ten oznacza, że weryfikacja zawartości pamięci flash nie potwierdziła zgodności z kodem, który miał być tam zapisany. Najprościej jest powtórzyć proces zapisywania, a gdy to nie pomoże, warto do programatora dodać zewnętrzne zasilanie około +5 V. Jeżeli mimo zastosowania zaproponowanych rozwiązań błąd nadal występuje, może to oznaczać, że uszkodzony został programator lub port komputera. Błąd programatora może być także zgłoszony wcześniej w postaci okienka przedstawionego na rysunku 3.17. Rysunek 3.17. Okno komunikatu błędu programatora

Jeśli natomiast pracujemy z programatorem USBasp, w folderze z programem avrdude.exe wydajemy polecenie: avrdude.exe -p m8 -c usbasp -U flash:w:"lekcja3_1.hex":i

Pracę niewątpliwie usprawni umieszczenie powyższego ciągu poleceń w pliku wsadowym, który będzie można uruchamiać podwójnym kliknięciem myszy. Oczywiście w tym samym katalogu musimy mieć plik lekcja3_1.hex. Jeśli jednak tego typu praca z programem nas nie satysfakcjonuje, możemy użyć nakładek na AVRdude, czyli programu avrdude-gui.exe lub AVR8_Burn-O-Mat. Po załadowaniu programu do mikrokontrolera i podłączeniu zasilania do płytki uruchomieniowej (zasilanie może być podłączone do płytki przez cały czas) zobaczymy świecącą się diodę LED. Zanim przejdziemy do następnych zadań, obsłużymy układ z rysunku 3.3 jeszcze raz. Napisałem wcześniej, że zrozumienie mechanizmu ustawiania linii portów należy do podstaw programowania mikrokontrolerów. Aby uczynić Cię, drogi Czytelniku, biegłym w tym temacie, pokażę, jak zaświecić diod ę LED przy użyciu innych rozkazów niż zaproponowane poprzednio. Tym razem w kręgu naszych zainteresowań będą instrukcje ldii out. Zapoznamy się także z pojęciem rejestru. Tworzenie programu rozpoczynamy od wpisania pięciu znanych już dyrektyw: .nolist .include "m8def.inc" .list .cseg .org 0

Teraz zastosujemy instrukcję ldi, której używamy w celu załadowania rejestru wartością bezpośrednią. W naszym programie ldiposłuży do umieszczenia w rejestrze R16 liczby binarnej 0b00000011 . ldi R16, 0b00000011

Już używaliśmy pojęcia rejestru w kontekście rejestru funkcyjnego. Co to właściwie jest rejestr? Najogólniej mówiąc, jest to miejsce na dane, fragment dostępnej pamięci mikrokontrolera. Mikrokontrolery rodziny AVR, do której należy także używany przez nas układ ATmega8, posiadają 32 ośmiobitowe rejestry ogólnego przeznaczenia numerowane od 0 do 31 (nazywamy je także rejestrami roboczymi). Czyli mamy rejestry o nazwach R0, R1, R2 itd., aż do R31. Pierwszych szesnaście rejestrów R0 – R15 nie może być używanych do operacji z liczbami ładowanymi bezpośrednio, dlatego najczęściej używanym przez nas rejestrem będzie pierwszy z drugiej szesnastki, czyli R16.

♦ Zaświecenie diody LED Lekcja 3

61

Teraz załadujemy zawartość R16do rejestru DDRB. out DDRB, R16

Może wydawać się nieco dziwne, że nie ładujemy wartości liczbowej bezpośrednio do rejestru funkcyjnego, lecz posługujemy się rejestrem roboczym. Wygodne byłoby dla nas takie ładowanie, jakie pokażę poniżej. Ale uwaga, poniższy rozkaz jest błędny! out DDRB, 0b00000011 ;Uwaga! Instrukcja błędna!

Pokazana instrukcja jest niepoprawna, a wynika to z architektury mikrokontrolerów AVR. Zapisanie wartości do rejestru funkcyjnego jest możliwe tylko z wykorzystaniem pośrednictwa rejestru roboczego. Teraz ustawiamy linie PORTB. ldi R16, 0b00000001 out PORTB, R16

Często będę używać liczb binarnych — dla ich oznaczenia zastosuję zapis z pełnymi ośmioma bitami. Dzięki temu od razu widać, jaka wartość jest przypisywana do każdego z ośmiu bitów portu. Być może na początku trudno sobie wyobrazić, w jaki sposób liczba binarna zapełnia bity rejestru. Mam nadzieję, że wyjaśni to rysunek 3.18. Rysunek 3.18. Schemat przypisywania wartości binarnej do rejestrów

Na końcu umieszczamy nieskończoną pętlę: petla: rjmp petla

i mamy gotowy cały program. Listing lekcja3_2.asm .nolist .include "m8def.inc" .list .cseg .org 0 ldi R16, 0b00000011 out DDRB, R16 ldi R16, 0b00000001 out PORTB, R16 petla: rjmp petla

Na koniec nieco przydługiego — przyznaję — paragrafu wytłumaczę znaczenie używanych przez nas dyrektyw. Dyrektywa .nolistwyłącza następujące po niej wiersze z procesu sporządzania raportu z kompilacji. Łatwo możemy wywnioskować, że.listnakazuje uwzględniać w raporcie z kompilacji wiersze następujące po tej dyrektywie. Po co zastosowaliśmy.nolist ? Otóż w wierszu drugim nakazujemy dołączyć do naszego .include pliku zawartość pliku nagłówkowego m8def.inc. Czynimy to za pomocą dyrektywy . Plik m8def.inc zawiera opisy adresów pamięci mikrokontrolera ATmega8. Dzięki jego definicjom zamiast: sbi 0x17, 0

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

62

możemy napisać: sbi DDRB, 0

Dodajmy do tego, że m8def.inc jest plikiem dużym i sporządzanie raportu z jego zawartości spowalniałoby każdą kompilację. Użycie pliku nagłówkowego czyni nasz program bardziej uniwersalnym — mam tu na myśli możliwość zastosowania kodu do zaprogramowania innego mikrokontrolera. By na przykład zaprogramować napisanym kodem układ ATmega16, należy: Upewnić się, że ATmega16 posiada takie same porty, jakich używamy w programie. Zmienić plik nagłówkowy na następujący: .include "m16def.inc"

Dyrektywa .csegoznacza, że następujący po niej kod będzie umieszczany w segmencie programu. Dyrektywa .org 0nakazuje zacząć proces umieszczania kodu od adresu 0 segmentu pamięci. Zestawienie nowych instrukcji w kolejności ich wystąpienia w paragrafie: sbi P, b — ustaw bit o numerze b w porcie wejścia-wyjścia P. cbi P, b — wyzeruj bit o numerze b w porcie wejścia-wyjścia P. rjmp k— wykonaj skok bezwarunkowy pod adres oznaczony etykietą k. ldi Rd, K8 załaduj — do rejestru ogólnego przeznaczenia Rd wartość ośmiobitową K8. out P, Rr — załaduj do portu wejścia-wyjścia P wielkość znajdującą się w rejestrze Rr.

3.2. Język C Zagadnienia: Utworzenie nowego projektu. Obsługa portów wejścia-wyjścia. Pętla nieskończona. Praca z plikiem Makefile. Po przeczytaniu pasjonującego paragrafu 3.1 spróbujemy układ przedstawiony na rysunku 3.3 obsłużyć w języku C. Ponieważ niezbędna teoria pojawiła się w poprzednim paragrafie, zajmiemy się wyłącznie omówieniem rozkazów używanych w języku C. Uruchamiamy WinAVR Programmers Notepad (patrz rysunek 3.19). Wpisujemy od razu interesujący nas kod, który wygląda tak, jak pokazano poniżej. Listing lekcja3.c #include int main(void) { DDRB = 0x03; PORTB = 0x01; for(;;){} return 0; }

♦ Zaświecenie diody LED Lekcja 3

63

Rysunek 3.19. Okno główne programu WinAVR Programmers Notepad

Omówmy kolejne wiersze programu. Użycie dyrektywy: #include

ma na celu dodanie do naszego kodu pliku io.h, który w procesie kompilacji połączy plik z definicjami adresów portów programowanego przez nas mikrokontrolera. Jak każdy kod języka C, także i nasz zawiera funkcję główną main. Dodatkowo, dla pełnej zgodności ze standardem tego języka, jest to funkcja typuint. Dlatego ostatnim rozkazem w funkcji mainjest: return 0;

Oczywiście zwrócenie jakiejkolwiek wartości przez mikrokontroler nie ma sensu. Kompilator zamienia tę linijkę w pętlę nieskończoną. Właściwie nasz program mógłby wyglądać tak: #include void main() { DDRB = 0x03; PORTB = 0x01; for(;;){} }

Jednak podczas kompilacji funkcjimaintypu voidWinAVR ostrzega nas o braku zgodności ze standardem ję0 powyższych rozważań zyka C. Aby uniknąć uciążliwych ostrzeżeń, warto dodać tę jedną linijkęreturn (z wynika, że ta instrukcja zabezpieczy nas także wtedy, gdy zapomnimy dodać na końcu programu pętlę nieskończoną)2. Ustawienie kierunków wyjściowych dwu najmłodszych linii portu B wykonujemy za pomocą następującego przypisania: DDRB = 0x03;

Po zapisaniu liczby szesnastkowej 0x03w postaci binarnej: 0x03 = 0b00000011

łatwo zauważymy, że ustawione zostaną bity zerowy i pierwszy rejestru funkcyjnego DDRB. Podobnie ustawiamy PORTB— pamiętajmy jednak, że linia zerowa (PB0) powinna znajdować się w stanie wysokim, pierwsza (PB1) w niskim stanie logicznym. PORTB = 0x01;

2

Kompilator środowiska WinAVR, począwszy od wersji 20080411, nie traktuje braku instrukcji return 0 w funkcji mainjako błędu.

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

64

Program kończymy pętlą nieskończoną. for(;;){}

Zapisujemy plik, klikając ikonę Save lub używając kombinacji klawiszy Ctrl+S. W tym momencie zostaniemy poproszeni o wpisanie nazwy pliku. Pamiętajmy, że kompilator automatycznie nie nadaje rozszerzenia *.c, musimy je wpisać samodzielnie (patrz rysunek 3.20). Rysunek 3.20. Okno zapisywania programu

Uruchomienie procesu kompilacji wymaga użycia pliku o nazwie Makefile. Plik ten powinien znajdować się w katalogu, w którym zapisaliśmy nasz program. Nie ma potrzeby samodzielnego tworzenia pliku Makefile. Wystarczy go przegrać z katalogu WinAVR\doc\avr-libc\examples\demo. Następnie, po otworzeniu dowolnym edytorem, zobaczymy dwie pierwsze linijki takiej postaci: PRG OBJ

= demo = demo.o

które należy zmienić poprzez wpisanie nazwy zbudowanego przez nas programu. Dwie pierwsze linijki Makefile zmodyfikowanego przeze mnie wyglądają następująco: PRG OBJ

= lekcja3 = lekcja3.o

Kompilujemy program za pomocą zakładki Tools/[WinAVR] Make All (patrz rysunek 3.21). Rysunek 3.21. Zakładka kompilacji programu

Jeśli w programie nie wykryto błędów, w oknie Output zobaczymy następujące komunikaty: > "make.exe" all avr-gcc -g -Wall -O2 -mmcu=atmega8 -c -o lekcja3.o lekcja3.c avr-gcc -g -Wall -O2 -mmcu=atmega8 -Wl,-Map,lekcja3.map -o lekcja3.elf lekcja3.o avr-objdump -h -S lekcja3.elf > lekcja3.lst avr-objcopy -j .text -j .data -O ihex lekcja3.elf lekcja3.hex avr-objcopy -j .text -j .data -O binary lekcja3.elf lekcja3.bin avr-objcopy -j .text -j .data -O srec lekcja3.elf lekcja3.srec

♦ Zaświecenie diody LED Lekcja 3

65

avr-objcopy -j .eeprom --change-section-lma .eeprom=0 -O ihex lekcja3.elf lekcja3_eeprom.hex \ || { echo empty lekcja3_eeprom.hex not generated; exit 0; } c:\WinAVR-20080411\bin\avr-objcopy.exe: --change-section-lma .eeprom=0x00000000 never used avr-objcopy -j .eeprom --change-section-lma .eeprom=0 -O binary lekcja3.elf lekcja3_eeprom.bin \ || { echo empty lekcja3_eeprom.bin not generated; exit 0; } c:\WinAVR-20080411\bin\avr-objcopy.exe: --change-section-lma .eeprom=0x00000000 never used avr-objcopy -j .eeprom --change-section-lma .eeprom=0 -O srec lekcja3.elf lekcja3_eeprom.srec \ || { echo empty lekcja3_eeprom.srec not generated; exit 0; } c:\WinAVR-20080411\bin\avr-objcopy.exe: --change-section-lma .eeprom=0x00000000 never used > Process Exit Code: 0 > Time Taken: 00:03

W katalogu naszego projektu znajdziemy dwa pliki w formacie szesnastkowym: z rozszerzeniem *.hex. i *.eep. U mnie są to lekcja3.hex oraz lekcja3.eep. Pierwszy z wymienionych plików zawiera kod, którym ładujemy pamięć mikrokontrolera w sposób opisany w paragrafie 3.1. Plik drugi, z rozszerzeniem *.eep, na razie nie będzie nam potrzebny. Osoby, które w tym momencie grzeją dłonie przy ciepłym blasku zaświeconej właśnie diody LED, mogą zakończyć czytanie tego paragrafu i przejść do zadań następnych. Osobom, którym teraz smutek wykrzywia twarz, bo program nie chce się skompilować, należą się dodatkowe informacje. Najczęściej spotykane błędy to brak średnika i literówka. Brak średnika kompilator zasygnalizuje podobnym komunikatem: > "make.exe" all avr-gcc -g -Wall -O2 -mmcu=atmega8 -c -o lekcja3.o lekcja3.c lekcja3.c: In function 'main': lekcja3.c:8: error: expected ';' before 'for' make.exe: *** [lekcja3.o] Error 1 > Process Exit Code: 2 > Time Taken: 00:01

Literówką może być brak litery w rozkazie lub nazwie portu. Należy też pamiętać, że język C rozróżnia wielkość liter. Dlatego zapis portbjest dla kompilatora zupełnie niezrozumiały. > "make.exe" all avr-gcc -g -Wall -O2 -mmcu=atmega8 -c -o lekcja3.o lekcja3.c lekcja3.c: In function `main': lekcja3.c:6: error: `portb' undeclared (first use in this function) lekcja3.c:6: error: (Each undeclared identifier is reported only once lekcja3.c:6: error: for each function it appears in.) make.exe: *** [lekcja3.o] Error 1 > Process Exit Code: 2 > Time Taken: 00:00

3.3. Bascom Zagadnienia: Utworzenie nowego projektu. Obsługa portów wejścia-wyjścia. Pętla nieskończona. Kompilacja programu. Bascom to język szczególnie przyjazny użytkownikom wyświetlaczy LCD. Na razie chcemy za jego pomocą zaświecić diodę LED podłączoną do płytki uruchomieniowej zgodnie ze schematem z rysunku 3.4. Daję słowo, że nie będzie to zadanie szczególnie wyczerpujące. Uruchamiamy program BASCOM-AVR. Zauważ, Czytelniku, że zgodnie z obietnicą na razie nie jest ciężko. Aktywny Bascom uśmiecha się do nas oknem o wyglądzie podobnym do tego z rysunku 3.22.

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

66 Rysunek 3.22. Okno główne programu Bascom AVR

Otwieramy edytor nowego pliku. W tym celu klikamy pierwszą z lewej strony ikonę oznaczoną Create a new file (patrz rysunek 3.23). Rysunek 3.23. Ikona otwierania edytora nowego pliku

Wpiszemy od razu interesujący nas kod. Listing lekcja3.bas $regfile "m8def.dat" Config Portb = &B00000011 Portb = &B00000001 Do Loop

Program jest krótki, ponieważ język Bascom jest prosty. Używany edytor ma ciekawą właściwość: zmienia wielkość wpisywanych liter. Dlatego możemy pisać dowolnie, efekt i tak nas zaskoczy. Popatrzmy na wpisany kod od strony programistycznej. Pierwsza linijka programu to dyrektywa: $regfile "m8def.dat"

Plik m8def.dat jest odpowiednikiem znanej z programowania w asemblerze biblioteki m8def.inc. Dzięki dyrektywie $regfile kompilator będzie używał adresów portów specyficznych dla mikrokontrolera zdefiniowanego w bibliotece dat. Ustawienie kierunków transmisji linii portu B odbywa się za pomocą przypisania poprzedzonego wyrażeniem Config . Config Portb = &B00000011

Zwróćmy uwagę na sposób kodowania liczby binarnej. Ciąg zer i jedynek poprzedzamy przedrostkiem&B. &H. Liczbę szesnastkową zaś, o ile będziemy mieli nieodpartą chęć jej użycia, poprzedzimy przedrostkiem

♦ Zaświecenie diody LED Lekcja 3

67

Ustawienie stanów logicznych realizujemy za pomocą zwykłego przypisania. Portb = &B00000001

Najmłodszy bit wartości binarnej&B00000001 ustawia wysoki stan logiczny na linii PB0. Linia PB1 znajdzie się w stanie logicznym niskim. Właściwie idealnie trafiliśmy, bowiem taka konfiguracja gwarantuje, że dioda LED się zaświeci. Pozostała nam do omówienia nieskończona pętla. Do Loop

Zapętlenie na końcu programu musi nastąpić. Inaczej mikrokontroler będzie czytał pozbawiony kodu fragment End. Co więcej, rywalizację międzyDo pamięci flash. W języku Bascom rolę takiej pętli spełnia także instrukcja Loopi Endwygrywa Endstosunkiem 2:1. Pierwszy punkt za zmniejszenie kodu, drugi za wyłączenie przerwań. Jednak na początku przygody z językiem Bascom wolę używaćDo Loop , gdyż wyraźnie wskazuje obecność zapętlenia na końcu programu. Kompilujemy program. W tym celu klikamy ikonę kompilacji programu lub naciskamy klawisz F7 (patrz rysunek 3.24). Rysunek 3.24. Ikona kompilacji programu

W czasie przeprowadzania procesu kompilacji Bascom informuje nas o jego przebiegu w specjalnym oknie (patrz rysunek 3.25). Rysunek 3.25. Okno informacyjne procesu kompilacji

Jeżeli nie było błędów, w katalogu roboczym naszego projektu znajdziemy plik *.hex, którym ładujemy pamięć mikrokontrolera w sposób opisany w paragrafie 3.1. Jeżeli natomiast zdarzyła się jakaś usterka, próbujemy ją naprawić. Bascom szczegółowo informuje o rodzaju i miejscu popełnionego błędu. Przeanalizujmy następujący przykład — błędnie zapisany format liczby binarnej: Portb = B00000001

Bascom poinformuje o błędzie w następujący sposób: Error: 242 Line: 4 Source variable does not match the target variable [Portb = B00000001]

Wystarczy dwukrotnie kliknąć pasek opisujący błąd, by Bascom przeniósł nas automatycznie w miejsce wykrytego błędu. A jeśli już wiadomo, gdzie znajduje się błąd, mile widziane jest jego naprawienie. Zestawienie nowych instrukcji języka Bascom: $regfile „K” — dyrektywa nakazująca użycie adresu portów z pliku K. Config Port — rozkaz konfiguracji portu Port. Do Loop— pętla nieskończona.

68

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

3.4. Pascal Zagadnienia: Utworzenie nowego projektu. Obsługa portów wejścia-wyjścia. Pętla nieskończona. Kompilacja programu. Do języka Pascal mam szczególny sentyment. Był pierwszym poważnym językiem programowania, jakiego się nauczyłem. Dzięki przyjaznej składni Pascal od lat sprawdza się w edukacji. Twórcy kompilatora mikroPascal docenili zalety języka i dodali do tego naprawdę świetny kompilator. Oto dlaczego postanowiłem umieścić w podręczniku paragraf dotyczący programowania mikrokontrolerów w języku Pascal. Uruchamiamy program mikroPascal for AVR. Zobaczymy okno o wyglądzie zaprezentowanym na rysunku 3.26. Rysunek 3.26. Okno główne programu mikroPascal AVR

Kompilator domyślnie ładuje się z przykładowym projektem. My chcemy utworzyć nowy projekt, w tym celu wybieramy zakładkę Project/New Project (patrz rysunek 3.27). Rysunek 3.27. Zakładka tworzenia nowego projektu

♦ Zaświecenie diody LED Lekcja 3

69

Wyświetli się okno — kreator nowego projektu — w którym w pięciu krokach podajemy podstawowe informacje o tworzonym projekcie: jego nazwę, katalog roboczy, typ mikrokontrolera oraz częstotliwość oscylatora taktującego układ (patrz rysunek 3.28). Rysunek 3.28. Okno powitalne kreatora nowego projektu

Ponieważ pracujemy z mikrokontrolerem ATmega8, w pierwszym oknie kreatora Device Name musimy wybrać właśnie ten typ układu. Zwróćmy także uwagę na szybkość taktowania urządzenia. Jeśli wciąż nasza ATmega8 pracuje z częstotliwością 1 MHz, w oknie numer 2 (Device Clock) powinniśmy mieć ustawioną wartość 1 000 000. W oknie numer 3 wybieramy nazwę i folder roboczy dla projektu. Następne okno pozostawiamy puste (na razie nic nie dodajemy do naszego projektu), w piątym kroku klikamy przycisk Finish i wracamy do okna głównego kompilatora mikroPascal. Zobaczymy w nim wpisane już podstawowe elementy programu — nazwę programu oraz słowa kluczowe begini endoznaczające początek i koniec programu. program lekcja3; { Declarations section } begin { Main program } end.

Właśnie między begini endbędziemy wpisywać rozkazy dla mikrokontrolera. Przypominam, że zajmujemy się obsługą układu z rysunku 3.3. Anoda diody LED jest podłączona do PB0, zaś katoda do PB1. Dioda LED będzie świecić, jeśli linia PB0 będzie w stanie logicznym wysokim, linia PB1 musi się wtedy znajdować w stanie logicznym niskim. Jednak w pierwszej kolejności ustalamy wyjściowe kierunki transmisji linii PB0 i PB1. Czynimy to, ustawiając bity zerowy i pierwszy rejestru funkcyjnego DDRB. %00000011 Dlatego pierwszą instrukcją będzie przypisanie do rejestru funkcyjnego DDRB wartości binarnej . DDRB := %00000011;

Warto zwrócić uwagę na sposób pisania liczby binarnej w języku Pascal — ciąg zer i jedynek poprzedzamy znakiem %. Przypisanie realizujemy za pomocą operatora :=. Do rejestru funkcyjnego PORTBładujemy liczbę binarną %00000001 . PORTB := %00000001;

whiledo, która się wykonuje, dopóki prawdziwy Program musi zakończyć nieskończona pętla. Użyjemy pętli jest warunek pętli. while TRUE do begin end;

TRUE W miejsce warunku pętli wstawiliśmy stałą , co powoduje, że warunek jest zawsze prawdziwy. Pętla będzie się wykonywała w nieskończoność.

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

70

Oto cały program. Listing lekcja3.mpas program Lekcja3; begin DDRB := %00000011; PORTB := %00000001; while TRUE do begin end; end.

Projekt kompilujemy za pomocą zakładki Project/Build lub za pomocą kombinacji klawiszy Ctrl+F9 (patrz rysunek 3.29). Rysunek 3.29. Zakładka kompilacji projektu

Śledzenie poprawności procesu kompilacji ułatwiają nam informacje pojawiające się w oknie komunikatów (patrz rysunek 3.30). Rysunek 3.30. Okno komunikatów

Dodatkowo dowiadujemy się, jaki procent pamięci mikrokontrolera zajmuje nasz program. 0 1139 Static RAM (bytes): 0 Dynamic RAM (bytes): 1024 0 1139 Used ROM (bytes): 104 (1%) Free ROM (bytes): 14231 (99%)

W katalogu roboczym naszego projektu odnajdujemy plik *.hex, którego zawartością ładujemy pamięć mikrokontrolera w sposób opisany w paragrafie 3.1. Jeżeli jednak się nieco postaramy, uda nam się zrobić błąd. Co wtedy? Bez paniki. Kompilator grzecznie i taktownie zwróci nam uwagę, to znaczy poinformuje, jakiego typu błąd został popełniony oraz gdzie należy go szukać. Dla przykładu zmieńmy w piątym wierszu programu operator przypisania na operator warunkowy: PORTB = %00000001;

Otrzymamy taką oto uprzejmą informację: Syntax error: Expected ':=' but '=' found

♦ Zaświecenie diody LED Lekcja 3

71

Poprawiamy błąd, kompilujemy program i ładujemy go do pamięci mikrokontrolera. Zestawienie nowych instrukcji i procedur języka Pascal: program— słowo kluczowe podające nazwę programu (użycie opcjonalne). begin— słowo kluczowe oznaczające początek programu, procedury, instrukcji lub bloku instrukcji. begin end— słowo kluczowe wskazujące koniec bloku instrukcji otworzonego słowem kluczowym . W szczególności wystąpienie z kropką ( end.) oznacza koniec programu. while [warunek] — dopętla wykonująca się do czasu, gdy fałszywy będzie warunek .

3.5. Ćwiczenia 1. Zaświeć diodę LED podłączoną do mikrokontrolera w sposób pokazany na rysunku 3.31. Rysunek 3.31. Schemat układu do ćwiczenia 1.

2. Zaświeć trzy diody LED podłączone do mikrokontrolera w sposób pokazany na rysunku 3.32. Rysunek 3.32. Schemat układu do ćwiczenia 2.

72

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

Lekcja 4

Mruganie diody LED Cóż, w życiu każdego programisty zdarza się taka chwila, w której pragnie on, by dioda LED zamrugała do niego filuternie. Właśnie nadszedł taki moment. Zadanie, którego rozwiązanie ma doprowadzić do mrugania diody, posłuży nam także do zapoznania się z nowymi instrukcjami programowania mikrokontrolerów. Będziemy obsługiwać układ znany z poprzedniej lekcji. Na rysunku 4.1 możemy go zobaczyć jeszcze raz. Rysunek 4.1. Schemat podłączenia diody LED do mikrokontrolera

Oczywiście szczęśliwi posiadacze płytki uruchomieniowej pamiętają, że diodę LED należy podłączyć do płytki w sposób pokazany na rysunku 3.4.

4.1. Asembler Zagadnienia: Podprogram. Obsługa stosu mikrokontrolera. Komentarze języka asembler. Wywołanie opóźnienia działania programu.

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

74

Uruchamiamy AVR Studio, tworzymy nowy projekt. W ramach rozgrzewki wpisujemy pięć dyrektyw kompilatora: .nolist .include "m8def.inc" .list .cseg .org 0

Odpoczywamy, rzecz normalna po rozgrzewce. A następnie intensywnie myślimy: jak usprawnić działanie naszych programów? Otóż często się zdarza, że w różnych częściach programu wykorzystujemy ten sam fragment kodu. Oczywiście dobrze jest wtedy interesujący nas kod umieścić w jednym miejscu programu, a następnie wywoływać ten fragment zgodnie z potrzebą. Z technicznego punktu widzenia chodzi o wyodrębnienie fragmentu kodu, do którego następnie będą wykonywane skoki. Taki fragment kodu będziemy nazywać podprogramem. Brzmi obiecująco, lecz od razu napotykamy dużą trudność: jeśli do podprogramu będą wykonywane skoki z różnych części programu, to w jaki sposób zaimplementować powrót pod odpowiedni adres? Spójrzmy na przykładowy szkic kodu. W początkowej części kodu wykonywane są pewne instrukcje, po czym następuje skok do podprogramu. ;różne dziwne instrukcje … ;skok do podprogramu rjmp procedurka ;adres powrotny powrot: ;inne dziwne instrukcje … ;podprogram procedurka: ;różne niesamowicie dziwne instrukcje … ;powrót do kodu głównego rjmp powrot

Na razie nie widzimy żadnej trudności. Skok do podprogramu wykonywany jest bowiem tylko z jednego miejsca kodu. A teraz przypadek z dwoma skokami do podprogramu: ;różne dziwne instrukcje … ;pierwszy skok do podprogramu rjmp procedurka ;pierwszy adres powrotny powrot1: ;inne dziwne instrukcje … ;drugi skok do podprogramu rjmp procedurka ;drugi adres powrotny powrot2: ;inne dziwne instrukcje … ;podprogram procedurka: ;różne niesamowicie dziwne instrukcje … ;powrót do kodu głównego ;gdzie wracać: powrot1 czy powrot2? rjmp powrot?

Trudność jest widoczna jak na dłoni: nie wiadomo, pod który adres wrócić po skoku do podprogramu. Najwygodniej byłoby, gdybyśmy podczas każdego skoku zapamiętywali gdzieś adres, pod który należy wrócić z podprogramu. Otóż jest taki mechanizm, realizowany za pomocą instrukcjircalli ret. Podczas skoku do podprogramu wykonywanego za pomocą instrukcji rcalladres powrotny zapamiętywany jest na stosie,

♦ Mruganie diody LED Lekcja 4

75

czyli specjalnym fragmencie pamięci. Wywołanie rozkazu retpowoduje zdjęcie ze szczytu stosu adresu, pod który wykonywany jest skok powrotny. Ze względu na uzupełniające się operacje na stosie instrukcje rcalli retmuszą zawsze występować w parze. Podsumujmy: w naszym programie będziemy wykonywać skoki do podprogramów. Do tego będą nam służyły instrukcjercalli ret, które korzystają ze stosu. Wniosek: musimy zaimplementować stos. Proponuję, by początek stosu znajdował się na końcu pamięci SRAM (o rodzajach pamięci powiem więcej w następnym paragrafie). Adres końca pamięci SRAM przechowuje stałaRAMEND . Projektanci mikrokontrolerów AVR do obsługi stosu przeznaczyli parę rejestrów SPH i SPL. Oto co zrobimy: załadujemy do nich adres pobrany z RAMEND . Robimy to za pomocą pięciu linijek kodu: cli ldi out ldi out

R16, SPH, R16, SPL,

HIGH(RAMEND) R16 LOW(RAMEND) R16

Skąd taki dziwny zapis? W pliku m8def.inc możemy odczytać, że pod stałąRAMENDskrywa się wartość szesnastkowa 0x045f . Jest to liczba zbyt duża, by zmieścić się w ośmiobajtowym rejestrze, dlatego będzie przechowywana w parze rejestrów SPH i SPL. Ładowanie odbywa się w częściach: starszy bajt, młodszy bajt (patrz rysunek 4.2). Rysunek 4.2. Schemat sposobu ładowania rejestrów SPH i SPL

Instrukcja cliwyłącza przerwania. Nie wiemy jeszcze, co to są przerwania, nie powinno nas to jednak niepokoić. Musimy tylko zapamiętać, że ładowanie rejestrów SPH i SPL trzeba wykonać przy wyłączonej obsłudze przerwań. Podobno najlepszą metodą ujarzmienia nieznanego zjawiska jest nadanie mu nazwy. Nic nie stoi na przeszkocli, za to możemy jego zastodzie, byśmy właśnie tak postąpili. Nie znamy jeszcze pełnego działania rozkazu sowanie opisać w formie komentarza. ;wyłączenie przerwań cli ;załadowanie adresu końca pamięci ldi R16, HIGH(RAMEND) out SPH, R16 ldi R16, LOW(RAMEND) out SPL, R16

Komentarze w programowaniu są niezwykle pożyteczne — nie pozwalają się zgubić w pisanym kodzie. W asemblerze komentarz wstawiamy za pomocą średnika. Wszystko, co następuje po znaku średnika, aż do końca wiersza, będzie przez kompilator pomijane. AVR Studio 4.0 pozwala także na użycie komentarzy w formie znanej z języka C: 1. //— symbol komentarza o działaniu identycznym z działaniem średnika, to jest wyłączający z procesu kompilacji kod następujący po znaku komentarza aż do końca wiersza. 2. /* — symbol komentarza wyłączający z procesu kompilacji kod następujący po znaku komentarza aż do jego odwołania symbolem */.

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

76

Powracamy do głównego tematu lekcji, czyli do wymuszenia mrugania diody LED. W naszym programie mamy już zaimplementowany stos. Teraz ustawimy kierunek transmisji linii PB0 i PB1. ;ustawienie kierunków transmisji sbi DDRB, 0 sbi DDRB, 1

Zapalaniem i gaszeniem diody LED będziemy sterować poprzez ustawienie wysokiego i niskiego stanu logicznego na linii PB0. Linia PB1 powinna znajdować się w niskim stanie logicznym, co możemy od razu zaimplementować. ;ustawienie PB1 w niski stan logiczny cbi PORTB, 1

Prawdę mówiąc, w mikrokontrolerze ATmega8 zaraz po inicjalizacji domyślnie wszystkie porty są wejściowe w niskim stanie logicznym. Możemy z tego wnioskować, że zerowanie linii PB1 nie jest konieczne. Oto moja rada: nie ufajmy wartościom domyślnym! Jako ilustrację tego gorącego apelu przeanalizujmy następujący przykład. Zapalamy diodę LED, ustawiając PB0 w wysoki stan logiczny, a PB1 pozostawiając w stanie niskim. Przyjmijmy także, że gdzieś w dalszej części kodu programu linia PB1 przyjmuje wysoki stan logiczny. Przez nasz programistyczny błąd (brak pętli nieskończonej) mikrokontroler czyta niezapisane fragmenty pamięci flash, po czym zaczyna wykonywać kod programu od nowa. Jeżeli ufając wartościom domyślnym, nie umieściliśmy rozkazu cbi PORTB, , 1dioda LED już więcej nie zaświeci, gdyż linia PB1 pozostanie w stanie wysokim. W nieskończonej pętli umieścimy cztery instrukcje: 1. Ustawienie linii PB0 w wysoki stan logiczny (dioda LED świeci). 2. Wywołanie podprogramu opóźniającego wykonanie programu o 0,25 sekundy. 3. Ustawienie linii PB0 w niski stan logiczny (zgaszenie diody LED). 4. Wywołanie podprogramu opóźniającego wykonanie programu o 0,25 sekundy. Kod fragmentu programu wygląda następująco: petla: ;ustawienie PB0 w wysoki stan logiczny sbi PORTB, 0 ;wywołanie opóźnienia rcall Czekaj250ms ;ustawienie PB0 w niski stan logiczny cbi PORTB, 0 ;wywołanie opóźnienia rcall Czekaj250ms ;powrót do etykiety petla rjmp petla

Użyliśmy rozkazu rcall, który powoduje wywołanie podprogramu. Pozostał nam do zdefiniowania podprogram, który zrealizuje opóźnienie 0,25 sekundy. Wybrałem właśnie taki czas, by mruganie było widoczne. Stałą wartość opóźnienia w programowaniu mikrokontrolerów realizujemy najczęściej poprzez umieszczenie w kodzie instrukcji, których wykonanie zajmie określony czas. Na potrzeby dalszej części lekcji zakładam, że nasz mikrokontroler działa z prędkością 1 000 000 operacji na sekundę (1 MHz). Spróbujmy napisać podprogram, którego wykonanie zajmie 250 000 cykli zegarowych. Nie ma innej rady, jak tylko oprzeć procedurę na pętlach. Pętla pierwsza (wewnętrzna). Zaczynamy od wyzerowania rejestru. Aby nie być posądzonym o faworyzowanie R16, użyję rejestru R21. ldi R21, 0

Na początku każdej przyzwoitej pętli musimy wskazać miejsce adresowania skoków. Oczywiście użyjemy etykiety. ldi R21, 0 czekaj250ms_1:

♦ Mruganie diody LED Lekcja 4

77

W ciele pętli inkrementujemy zawartość rejestru. Do tego szlachetnego celu służy instrukcja inc. ldi R21, 0 czekaj250ms_1: inc R21

R21z liczbą250. Teraz sprawdzimy, czy pętlę należy kontynuować. W tym celu porównujemy zawartość rejestru ldi R21, 0 czekaj250ms_1: inc R21 cpi R21, 250

Powrót do etykiety czekaj250ms_1 powinien nastąpić, gdy wartość rejestru R21 będzie mniejsza od 250. Posłużymy się rozkazem — skokiem warunkowym brlo, który możemy odczytać: skocz, jeśli mniejsze: ldi R21, 0 czekaj250ms_1: inc R21 cpi R21, 250 brlo czekaj250ms_1

Instrukcja brlo249 razy wywoła powrót do etykiety czekaj250ms_1 , aż za 250 razem skok nie zostanie wykonany i nastąpi wyjście z pętli. Zasadne jest pytanie o to, ile cykli zegarowych zajmie mikrokontrolerowi jej wykonanie. Zaraz to policzymy — otwieramy listę rozkazów układu ATmega8 i klikamy zakładkę Help/AVR Tools User Guide (patrz rysunek 4.3). Rysunek 4.3. Uruchamianie pomocy środowiska AVR Studio

Ze spisu treści wybieramy podpunkt AVR Assembler/Parts, a następnie odszukujemy zbiór rozkazów układu ATmega8 (patrz rysunek 4.4). Rysunek 4.4. Lista rozkazów układu ATmega8

Otworzone zestawienie rozkazów wyświetla nie tylko opis instrukcji dostępnych dla mikrokontrolera ATmega8, ale także informację o tym, ile cykli zegarowych zajmuje ich wykonanie. Odczytane dane umieścimy w pętli w formie komentarza:

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

78 ldi R21, 0 ;1 czekaj250ms_1: inc R21 ;1 cpi R21, 250 ;1 brlo czekaj250ms_1 ;2

Wykonanie instrukcjibrlozajmuje 2 cykle zegarowe, gdy warunek nie jest spełniony (w naszej pętli w 249 przypadkach). Jeśli warunek jest prawdziwy, wykonanie instrukcji zajmuje 1 cykl zegarowy. Gdy uwzględnimy występujący przed pętlą rozkaz: ldi R21, 0

;1

mamy dokładnie 4×250 = 1000 cykli zegarowych dla pętli wewnętrznej. Wykonujemy drugą pętlę (zewnętrzną). Nad pętlą wewnętrzną budujemy kod pętli zewnętrznej przy użyciu rejestru R20. Czekaj250ms: ldi R20, 0 ;1 czekaj250ms_0: ldi R21, 0 ;1 czekaj250ms_1: inc R21 ;1 cpi R21, 250 ;1 brlo czekaj250ms_1 ;1 inc R20 ;1 cpi R20, 249 ;1 brlo czekaj250ms_0;2

Pętlę zewnętrzną wykonujemy 249 razy, gdyż musimy pamiętać o uwzględnieniu także czasu jej obsługi. Obliczmy: dzięki pętli zewnętrznej pętla wewnętrzna wykonuje się 249 razy, co daje 1000×249 = 249 000 cykli zegarowych. Do tego należy dodać 4×249 = 996 cykli zegarowych dla obsługi pętli zewnętrznej. Razem mamy 249 000+996 = 249 996 cykli zegarowych. ret. Jej zadaJak wiemy, na końcu każdego podprogramu musi wystąpić instrukcja powrotu z podprogramu niem jest wykonanie skoku bezwarunkowego pod adres, który wcześniej został zdjęty ze szczytu stosu. Obsługa instrukcji retzajmuje 4 cykle zegarowe, co w sumie nam daje 249 996+4 = 250 000 cykli zegarowych, czyli dokładnie ¼ sekundy zrealizowanego opóźnienia. Czekaj250ms: ldi R20, 0 ;1 czekaj250ms_0: ldi R21, 0 ;1 czekaj250ms_1: inc R21 ;1 cpi R21, 250 ;1 brlo czekaj250ms_1 ;1 inc R20 ;1 cpi R20, 249 ;1 brlo czekaj250ms_0;2 ret ;4

Oto cały kod programu realizującego mruganie diody LED. Listing lekcja4.asm ;;; lekcja4.asm ;;; .nolist .include "m8def.inc" .list .cseg .org 0 ;wyłączenie przerwań cli ;załadowanie adresu końca pamięci ldi R16, HIGH(RAMEND)

♦ Mruganie diody LED Lekcja 4

79

out SPH, R16 ldi R16, LOW(RAMEND) out SPL, R16 ;ustawienie kierunków transmisji sbi DDRB, 0 sbi DDRB, 1 ;ustawienie PB1 w niski stan logiczny cbi PORTB, 1 petla: ;ustawienie PB0 w wysoki stan logiczny sbi PORTB, 0 ;wywołanie opóźnienia rcall Czekaj250ms ;ustawienie PB0 w niski stan logiczny cbi PORTB, 0 ;wywołanie opóźnienia rcall Czekaj250ms ;powrót do etykiety petla rjmp petla Czekaj250ms: ldi R20, 0 czekaj250ms_0: ldi R21, 0 czekaj250ms_1: inc R21 cpi R21, 250 brlo czekaj250ms_1 inc R20 cpi R20, 249 brlo czekaj250ms_0 ret

Zestawienie nowych instrukcji w kolejności ich wystąpienia w paragrafie: rcall K— skocz do podprogramu o adresie (etykiecie) K. ret— rozkaz powrotu z podprogramu. cli— wyłączenie obsługi przerwań. Rd (Rd = R0, R1, R2, inc Rd— zwiększ o jeden (inkrementuj) wartość przechowywaną w rejestrze …, R29, R30, R31). cpi Rd, — K porównaj wartość przechowywaną w rejestrze Rd (Rd = R16, R17, R18, …, R29, R30, R31) z wielkością K (0 – 255). brlo K— skok warunkowy „skocz, jeśli mniejsze”. Etykieta K nie może być dalej niż 63×2 bajty od miejsca wystąpienia skoku.

4.2. Język C Zagadnienia: Komentarze języka C. Funkcje opóźniające działanie programu. Generowanie pliku Makefile za pomocą programu MFile. W porównaniu do programu asemblerowego praca nad kodem języka C będzie bardzo prostym, wręcz relaksującym zadaniem. Uruchamiamy WinAVR Programmers Notepad. Wpisujemy pierwszy wiersz, który dołączy do programu zasoby pliku nagłówkowego io.h: #include

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

80

Z paragrafu 4.1 dowiedzieliśmy się, że nie należy lekceważyć komentarzy. Z tą świadomością umieścimy komentarz w kodzie języka C. /* lekcja4.c */ #include

Przypominam, że znak /* otwiera komentarz, zamyka go znak */. Naszym zadaniem jest napisanie programu, który załadowany do pamięci mikrokontrolera wywoła mruganie diody LED. Kluczową pozycją w kodzie asemblerowym była procedura opóźniająca. W naszym programie skorzystamy z zasobów oferowanych przez kompilator i do wywołania opóźnienia użyjemy gotowej funkcji _delay_ms opisanej w bibliotece delay.h. Przed dodaniem biblioteki musimy określić częstotliwość pracy F_CPU naszego mikrokontrolera poprzez zdefiniowanie stałej . Zakładam, że korzystamy z wewnętrznego rezonatora o częstotliwości pracy 1 MHz. #define F_CPU 1000000

Dopiero teraz dodajemy odpowiednią bibliotekę. Wraz z komentarzem mamy już 4 linijki kodu — a to już powód do dumy. /* lekcja4.c */ #include #define F_CPU 1000000 #include

Nadszedł czas na umieszczenie funkcji głównej ściowe kierunki transmisji linii PB0 i PB1.

main. W niej w pierwszej kolejności ustawiamy wyj-

//linie PB0 i PB1 wyjściowe DDRB = 0x03;

Instrukcja została opisana za pomocą komentarza wstawianego symbolem //, który obowiązuje do końca wiersza. Naprzemienne zapalanie i gaszenie diody LED umieszczamy w pętli nieskończonej for(;;) . //naprzemienne zapalanie i gaszenie diody LED for(;;) { PORTB = 0x01; _delay_ms(250); PORTB = 0; _delay_ms(250); }

Funkcja _delay_ms wywołuje opóźnienie liczone w milisekundach. Jej argumentem może być liczba zmiennoprzecinkowa z zakresu od 0 do 262 (dokładnie 262,14). Podanie argumentu większego niż 262 zostanie _delay_ms potraktowane jak podanie zera. Odpowiednikiem funkcji dla krótszego okresu jest_delay_us , która wywołuje opóźnienie liczone w mikrosekundach. Argumentem tej funkcji może być dowolna liczba zmiennoprzecinkowa z zakresu od 0 do 768. Jesteśmy już gotowi, by zobaczyć program w całości. Listing lekcja4.c /* lekcja4.c */ #include #define F_CPU 1000000 #include int main() { //linie PB0 i PB1 wyjściowe DDRB = 0x03; //naprzemienne zapalanie i gaszenie diody LED for(;;) {

♦ Mruganie diody LED Lekcja 4

81

PORTB = 0x01; _delay_ms(250); PORTB = 0; _delay_ms(250); } return 0; }

Program należy skompilować i załadować do pamięci flash mikrokontrolera. Zanim to jednak zrobimy, skorzystam ze sprzyjającej okoliczności, jaką jest niezwykle łatwa lekcja, i wzbogacę podany materiał o umiejętność generowania własnego pliku Makefile. Nawet najprostszy plik Makefile zawiera wiele zupełnie niezrozumiałych rozkazów. Na szczęście nie musimy uczyć się ich na pamięć. Pakiet WinAVR zawiera kreator plików Makefile. Jest nim program o nazwie MFile. Po jego uruchomieniu zobaczymy okno o wyglądzie przedstawionym na rysunku 4.5. Rysunek 4.5. Okno programu MFile

Dostęp do opcji pliku Makefile uzyskujemy za pomocą wybieranego z menu rozkazu Makefile. Kolejne pozycje rozwiniętego menu umożliwiają modyfikację prawie wszystkich opcji pliku Makefile. Zaczynamy od modyfikacji nazwy projektu. W tym celu wybieramy pozycję Main file name… (patrz rysunek 4.6). Rysunek 4.6. Pozycje menu Makefile z zaznaczoną opcją modyfikacji nazwy projektu

Zobaczymy małe okno, w którym wpisujemy nazwę naszego pliku źródłowego bez rozszerzenia (patrz rysunek 4.7).

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

82 Rysunek 4.7. Okno zmiany nazwy projektu

Po zatwierdzeniu zmiany przyciskiem OK od razu zobaczymy wprowadzoną zmianę w kodzie wyświetlanym w oknie programu MFile. Odpowiedni ustęp zostanie wyróżniony kolorem. Następnie za pomocą pozycji MCU type wybieramy typ mikrokontrolera. Pozostałe opcje powinniśmy pozostawić w postaci domyślnej. Zwróćmy uwagę na cztery z nich. I tak w pozycji Output format wybieramy format pliku wynikowego. Domyślnie zaznaczony jest format szesnastkowy (ihex). Za pomocą opcji Optmization level wybieramy poziom optymalizacji kodu. Znacznik s oznacza optymalizację rozmiaru kodu. Jeśli pracujemy z programem ładującym AVRdude, istotne dla nas będą jeszcze opcje z grupy AVRdude. Za pomocą pozycji Programmer wybieramy typ używanego programatora. Niestety nie ma w nim pozycji usbasp. Mamy dwie możliwości: albo w każdym nowo wygenerowanym pliku Makefile wpisywać w odpowiednim miejscu nazwę usbasp, albo dodać pozycję usbasp do programu MFile. Proponuję wykonać tę drugą czynność , czyli przystosować program MFile do naszych potrzeb. W tym celu należy edytować plik mfile.tcl znajdujący się w folderze WinAVR-20080411\mfile. Znajdujemy fragment z listą dostępnych typów programatora — fragment ten ma następującą postać: foreach p [lsort {dasa3 dasa ponyser dapa xil futurlec \ abcmini picoweb sp12 alf bascom dt006 \ pony-stk200 stk200 pavr jtag2 jtag2fast \ jtag2slow jtagmkII jtagmkI avr911 avr109 \ butterfly avr910 stk500v2 stk500 avrisp2 \ avrispv2 avrisp bsd}]

Wystarczy dopisać na końcu listy nazwę programatora usbasp. Po tej operacji lista wygląda następująco: foreach p [lsort {dasa3 dasa ponyser dapa xil futurlec \ abcmini picoweb sp12 alf bascom dt006 \ pony-stk200 stk200 pavr jtag2 jtag2fast \ jtag2slow jtagmkII jtagmkI avr911 avr109 \ butterfly avr910 stk500v2 stk500 avrisp2 \ avrispv2 avrisp bsd usbasp}]

Zamykamy plik. Aby wprowadzone zmiany zostały załadowane do programu Mfile, należy go zamknąć i ponownie otworzyć. Tym razem w pozycji Programmer pojawi się pozycja usbasp. Zaznaczamy ją, dzięki temu będziemy mogli wywołać program ładujący AVRdude z aplikacji Programmer’s Notepad. W przypadku programatora typu USBasp wybór portu nie jest konieczny. Pewne opcje pliku Makefile nie są dostępne z menu programu MFile. Aby edytować plik, należy zaznaczyć Makefile->Enable Editing of Makefile. Najczęściej będziemy korzystać z tej możliwość, aby zmienić wartość przypisywaną nazwie F_CPU. Domyślnie w Makefile ma ona wartość 8000000 . F_CPU = 8000000

Modyfikacja w Makefile nie jest konieczna, w przypadku gdy w kodzie programu występuje dyrektywa preprocesora z właściwą prędkością mikrokontrolera. #define F_CPU 1000000

Wygenerowany plik Makefile zapisujemy w katalogu naszego projektu. O poprawnie wykonanym procesie kompilacji powinniśmy być poinformowani za pomocą następujących komunikatów: > "make.exe" all -------- begin -------avr-gcc (WinAVR 20080411) 4.3.0 Copyright (C) 2008 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Size before: AVR Memory Usage ---------------Device: atmega8

♦ Mruganie diody LED Lekcja 4

83

Program: 132 bytes (1.6% Full) (.text + .data + .bootloader) Data: 0 bytes (0.0% Full) (.data + .bss + .noinit) Creating load file for EEPROM: lekcja4.eep avr-objcopy -j .eeprom --set-section-flags=.eeprom="alloc,load" \ --change-section-lma .eeprom=0 --no-change-warnings -O ihex lekcja4.elf lekcja4.eep || exit 0 Creating Extended Listing: lekcja4.lss avr-objdump -h -S -z lekcja4.elf > lekcja4.lss Creating Symbol Table: lekcja4.sym avr-nm -n lekcja4.elf > lekcja4.sym Size after: AVR Memory Usage ---------------Device: atmega8 Program: 132 bytes (1.6% Full) (.text + .data + .bootloader) Data: 0 bytes (0.0% Full) (.data + .bss + .noinit) -------- end -------> Process Exit Code: 0 > Time Taken: 00:03

Zaprogramowanie mikrokontrolera jest możliwe za pomocą rozkazu Tools/[WinAVR] Program. Zestawienie nowych funkcji i instrukcji języka C: _delay_ms(K) — funkcja wywołująca opóźnienie działania programu liczone w milisekundach. K może być liczbą zmiennoprzecinkową z zakresu (0 – 262). _delay_us(K) — funkcja wywołująca opóźnienie działania programu liczone w mikrosekundach. K może być liczbą zmiennoprzecinkową z zakresu (0 – 768).

4.3. Bascom Zagadnienia: Komentarze języka Bascom. Instrukcje opóźniające działanie programu. Program w języku Bascom zaczniemy od komentarza. Najczęściej używanymi znakami specjalnymi dla wyodrębnienia opisu kodu są: 1. ' — symbol komentarza o działaniu identycznym z działaniem średnika w asemblerze, to jest wyłączający z procesu kompilacji kod następujący po znaku komentarza aż do końca wiersza. 2. '(— symbol komentarza wyłączający z procesu kompilacji kod następujący po znaku komentarza aż do jego odwołania symbolem '). Praktyka pokazuje, że te dwa znaki komentarza nie mogą znajdować się w linii instrukcji, gdyż kompilator zgłasza wtedy błąd. W drugim wierszu programu umieścimy dyrektywę dołączenia do kodu programu zawartości pliku m8def.dat. ' Lekcja4.bas $regfile "m8def.dat"

W wierszu trzecim umieścimy dyrektywę$crystal , za pomocą której definiujemy częstotliwość pracy mikrokontrolera. Zakładam, że nasz mikrokontroler wykonuje 1000000operacji na sekundę. ' Lekcja4.bas $regfile "m8def.dat" $crystal = 1000000

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

84

Zdefiniowanie częstotliwości pracy mikrokontrolera jest ważne dla instrukcji opóźniającychWait. Od razu mamy wspaniałą wiadomość: w języku Bascom nie musimy pisać procedury opóźniającej samodzielnie. Odpowiedni kod należy do standardów tego języka. Zanim jednak przejdziemy do pętli głównej programu, ustawiamy wyjściowy kierunek transmisji linii PB0 i PB1. 'linie PB0 i PB1 wyjściowe Config Portb = &B00000011

Nie pozostało nam nic innego, jak zająć się obsługą diody LED. Odpowiedni kod umieszczamy w pętli nieskończonej. 'naprzemienne zapalanie i gaszenie diody LED Do Portb = &B00000001 Waitms 250 Portb = 0 Waitms 250 Loop

Waitms W celu opóźnienia działania programu użyliśmy instrukcji , która wywołuje opóźnienie liczone w milisekundach. W języku Bascom zaimplementowano aż cztery instrukcje opóźniające: 1. Wait— oczekiwanie liczone w sekundach. 2. Waitms— oczekiwanie liczone w milisekundach. 3. Waitus— oczekiwanie liczone w mikrosekundach. 4. Delay— instrukcja bezargumentowa, wywołuje opóźnienie około 1 milisekundy.

Argumentem instrukcjiWait, Waitmsi Waitusmoże być liczba całkowita z przedziału od 1 do 65 335. W starszych wersjach kompilatora Bascom w miejsce argumentu nie mogła być podstawiona zmienna. Na szczęście nowe wersje kompilatora pozwalają na użycie zmiennych. Poniżej napisany przez nas program w całości. Listing lekcja4.bas ' Lekcja4.bas $regfile "m8def.dat" $crystal = 1000000 'linie PB0 i PB1 wyjściowe Config Portb = &B00000011 'naprzemienne zapalanie i gaszenie diody LED Do Portb = &B00000001 Waitms 250 Portb = 0 Waitms 250 Loop

Zestawienie nowych instrukcji języka Bascom: Wait K— instrukcja wywołująca opóźnienie działania programu liczone w sekundach. K może być liczbą całkowitą z przedziału (1 – 65 535). Waitms K— instrukcja wywołująca opóźnienie działania programu liczone w milisekundach. K może być liczbą całkowitą z przedziału (1 – 65 535). Waitus K — instrukcja wywołująca opóźnienie działania programu liczone w mikrosekundach. K może być liczbą całkowitą z przedziału (1 – 65 535).

♦ Mruganie diody LED Lekcja 4

85

4.4. Pascal Zagadnienia: Komentarze języka Pascal. Procedury opóźniające działanie programu. Czas na programowanie w języku Pascal. Przypominam, że obsługujemy diodę LED podłączoną tak, jak pokazano na rysunku 4.1. Oto nasze ambitne zadanie: zmusić diodę LED do mrugania. Uruchamiamy środowisko mikroPascal for AVR. Domyślnie otworzy się ostatnio opracowywany projekt. Wybieramy zakładkę Project/New Project…, w oknie konfiguracji nowego projektu wybieramy nazwę projektu i typ mikrokontrolera wraz z częstotliwością jego pracy. Zatwierdzamy ustawienia, klikając OK. Zobaczymy gotowy szkielet programu, w którym umieszczone są już dwa komentarze. program Lekcja4; { Declarations section } begin { Main program } end.

Pascal oferuje możliwość wstawiania komentarzy dwojakiego rodzaju: 1. Komentarz obowiązujący od miejsca umieszczenia znaku // aż do końca wiersza. 2. Komentarz blokowy zaczynający się znakiem { i kończący się znakiem }. W procedurze głównej ustawiamy wyjściowe kierunki transmisji linii PB0 i PB1. //linie PB0 i PB1 wyjściowe DDRB := %00000011;

Kod programu sterujący zapalaniem i gaszeniem diody umieszczamy w pętli nieskończonej. //naprzemienne zapalanie i gaszenie diody LED while TRUE do begin PORTB := %00000001; Delay_ms(250); PORTB := 0; Delay_ms(250); end;

Delay_ms W celu spowodowania opóźnienia działania programu użyliśmy procedury . Pascal oferuje trzy procedury, które z entuzjazmem zajmują się spowalnianiem czasu działania kodu: 1. Delay_us— powoduje opóźnienie liczone w mikrosekundach. 2. Delay_ms— powoduje opóźnienie liczone w milisekundach. — powoduje opóźnienie liczone w cyklach działania mikrokontrolera, przy czym 3. Delay_Cyc argumentem tej procedury jest stała dziesięciokrotnie mniejsza od oczekiwanej wartości opóźnienia.

Argumentem każdej z powyższych procedur może być liczba całkowita, której maksymalna wielkość jest związana z częstotliwością pracy mikrokontrolera. Przy założeniu, że praca układu przebiega z częstotliwością 1 MHz, największymi wartościami akceptowanymi przez procedury są: dla procedury Delay_us . 1. 0xFFFFFFFF 0xFF Delay_Cyc dla . 2. 0xC51F3B Delay_ms dla procedury . 3.

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

86

Przekroczenie dopuszczalnego zakresu powoduje wyświetlenie informacji o błędzie. Umieśćmy dla przykładu nieprawidłowy argument w procedurze Delay_ms . Delay_ms(0xC51f3C);

Kompilator poinformuje nas o błędzie za pomocą następującej informacji: Argument is out of range “-1”

Dużym ułatwieniem w porównaniu do poprzedniej wersji kompilatora mikroPascal jest wprowadzenie procedury opóźniającej, której argumentem może być zmienna. Vdelay_ms(zmienna_16_bitowa: Word);

Zgodnie z opisem procedury dostarczonym w dokumentacji generowany przez procedurę czas opóźnienia nie jest czasem dokładnym. Aby uzyskać dokładny czas opóźnienia, należy użyć jednej z trzech procedur przedstawionych wcześniej. Poniżej widnieje kod gotowego programu. Listing lekcja4.mpas {lekcja4.mpas} program Lekcja4; begin //linie PB0 i PB1 wyjściowe DDRB := %00000011; //naprzemienne zapalanie i gaszenie diody LED while TRUE do begin PORTB := %00000001; Delay_ms(250); PORTB := 0; Delay_ms(250); end; end.

Zestawienie nowych instrukcji i procedur języka Pascal: Delay_Cyc(K) — procedura wywołująca opóźnienie działania programu liczone w cyklach mikrokontrolera. K może być liczbą całkowitą z przedziału (0 – 0xFF). Delay_ms(K) — procedura wywołująca opóźnienie działania programu liczone w milisekundach. K może być liczbą całkowitą z przedziału (0 – 0xC51F3B). Delay_us(K) — procedura wywołująca opóźnienie działania programu liczone w mikrosekundach. K może być liczbą całkowitą z przedziału (0 – 0xFFFFFFFF). Vdelay_ms(Z) — procedura wywołująca opóźnienie działania programu liczone w milisekundach. Z może być zmienną typu Word.

4.5. Ćwiczenia 1. W tym ćwiczeniu — manipulując czasem opóźnienia — wyślij za pomocą diody LED sekwencję sygnału SOS. Przyjmijmy podłączenie diody LED pokazane na rysunku 4.1. 2. Napisz program naprzemiennie zapalający na czas ½ sekundy dwie diody podłączone do mikrokontrolera w sposób pokazany na rysunku 4.8 (kiedy jedna dioda świeci, druga gaśnie).

♦ Mruganie diody LED Lekcja 4 Rysunek 4.8. Schemat do ćwiczenia 2.

87

88

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

Lekcja 5

Obsługa wyświetlacza LED Nie rozstajemy się jeszcze z diodami LED. Powód naszej sympatii do nich jest łatwy do rozszyfrowania: świetnie nadają się do celów dydaktycznych. Wyświetlacz LED to także zbiór diod LED, tyle że ułożonych w takiej konfiguracji, by można było na nich wyświetlać cyfry (patrz rysunek 5.1). Rysunek 5.1. Poglądowy schemat połączeń diod LED w wyświetlaczu LED ze wspólną anodą

Pokazany na rysunku 5.1 wyświetlacz LED ma wspólną dla wszystkich diod anodę i osobno wyprowadzoną dla każdej diody katodę. Spróbujmy odpowiedzieć na niebagatelne pytanie: w jakich okolicznościach zaświeci się któraś z diod, powiedzmy dioda D4? Pierwsza odpowiedź: gdy do anody podłączymy zasilanie około +5 V. Brawo! A drugi warunek? Dioda D4 się zaświeci, gdy na linii o numerze 4 zostanie podłączona masa. Opisana właśnie zasada stanowi klucz sterowania wyświetlaczem LED za pomocą mikrokontrolera. Osiem linii sterujących ośmioma diodami podłączamy do ośmiu linii jednego z portów mikrokontrolera. Zapalamy diodę LED, ustawiając odpowiednią linię portu w niski stan logiczny. Oczywiście w przypadku wyświetlaczy ze wspólną katodą sterowanie polega na ustawieniu odpowiedniej linii portu mikrokontrolera w stan wysoki. Przedmiotem naszego zainteresowania będzie podwójny wyświetlacz LED (patrz rysunek 5.2). Rysunek 5.2. Schemat podwójnego wyświetlacza LED

90

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

Warto zauważyć, że przedstawiony wyświetlacz składa się z dwóch części, W0 i W1, z których każda jest układem ośmiu diod LED. Wyświetlacz W0 ma wspólną anodę oznaczoną A0, wspólna anoda wyświetlacza W1 została oznaczona A1. Jak sterujemy takim wyświetlaczem LED? Bardzo łatwo. Załóżmy, że chcemy zaświecić diodę D4 wyświetlacza W0. W tym celu wykonujemy dwie czynności: 1. Podłączamy zasilanie do wyprowadzenia (anody) A0. 2. Podłączamy masę do wyprowadzenia (katody) 4. Złączenie katod ma na celu zmniejszenie liczby linii koniecznych do obsługi wyświetlacza LED. Oszczędność stanie się wyraźniejsza, gdy uświadomimy sobie, ilu linii portów mikrokontrolera wymagałaby obsługa pięciu wyświetlaczy LED z osobnymi katodami. Wtedy potrzebowalibyśmy 5×8 = 40 linii mikrokontrolera. Układ ATmega8 nie dysponuje tak ą liczbą wyprowadzeń. Natomiast obsługa pięciu wyświetlaczy LED ze złączonymi katodami wymagałaby jedynie 13 linii mikrokontrolera. Jeżeli nie mamy pod ręką wyświetlacza LED z połączonymi katodami, możemy go skonstruować ze zwykłego podwójnego wyświetlacza, na przykład z popularnego LDD056BSR-20. Być może pomocny w tym arcytrudnym zadaniu okaże się rysunek 5.3. Rysunek 5.3. Schemat połączenia katod w wyświetlaczu LDD056BSR-20

Przystępujemy do opracowywania tematu lekcji. Podłączmy podwójny wyświetlacz LED do płytki uruchomieniowej w sposób pokazany na rysunku 5.4. Rysunek 5.4. Schemat podłączenia podwójnego wyświetlacza LED do płytki uruchomieniowej

♦ Obsługa wyświetlacza LED Lekcja 5

91

Wyświetlacz LED powinien być podłączony do mikrokontrolera tak, by wyjścia wyświetlacza były podłączone do linii portu B o takich samych numerach. Czyli wyprowadzenie o numerze 0 wyświetlacza LED łączymy z linią PB0 mikrokontrolera, wyprowadzenie 1 z linią PB1, wyprowadzenie 2 z linią PB2 itd. Wyprowadzenia A0 i A1 łączymy z emiterami tranzystorów — odpowiednio — T0 i T1, które pośredniczą w doprowadzaniu napięcia do wyświetlacza. Bazy tranzystorów podłączamy do linii PC0 i PC1 mikrokontrolera. Aby uchronić tranzystory przed nadmiernym prądem, wszystkie linie dodatkowo zabezpieczamy rezystorami. Rozważmy proces zasilania części W0 wyświetlacza LED. Z własności tranzystora npn wynika, że do wyprowadzenia A0 napięcie zostanie podłączone wtedy, gdy na bazę tranzystora T0 wysłany zostanie impuls wysoki. Szybki proces myślenia i utwierdzamy się w przekonaniu, że włączaniem napięcia na A0 będziemy sterować poprzez wysyłanie z linii PC0 impulsu o odpowiedniej wysokości. Analogiczną sytuację mamy w przypadku wyprowadzenia A1 wyświetlacza LED i linii PC1 mikrokontrolera. Zrealizujemy dwa zadania dotyczące zbudowanego układu: 1. Wyświetlenie na wyświetlaczu LED cyfr w kolejności rosnącej od 0 do 9, a następnie w kolejności malejącej od 9 do 0 (zmiana cyfr co ¼ sekundy). 2. Wyświetlenie na wyświetlaczu LED liczb od 00 do 99, z prędkością zmiany cyfry na wyświetlaczu W0 wynoszącą 1/10 sekundy oraz prędkością zmiany cyfry na wyświetlaczu W1 wynoszącą 1 sekundę.

5.1. Asembler Zagadnienia: Rodzaje pamięci mikrokontrolerów AVR. Przechowywanie danych w pamięci flash. Przechowywanie danych w pamięci SRAM. Obsługa wyświetlacza LED. I znów ruszamy na szlak przygody. Na końcu tego paragrafu czeka nas wielka satysfakcja z przyswojonej porcji wiedzy. Zanim to jednak nastąpi, poznamy kilka nowych technik programistycznych oraz pewne nowe informacje o architekturze mikrokontrolerów z rodziny AVR. Być może nie wszystko wyda się od razu zrozumiałe, najlepiej zatem zignorować rodzące się po pierwszym czytaniu wątpliwości. Pożyteczną rzeczą będzie przetestowanie występujących w paragrafie programów, być może nawet uda nam się coś w nich zepsuć. Dopiero po tak brzemiennych w skutkach próbach warto przeczytać paragraf jeszcze raz, a wtedy — to gwarantuję — zawarta w nim porcja wiedzy okaże się łatwa i zrozumiała.

Zadanie 1. Zaczynamy standardowo: uruchamiamy AVR Studio, otwieramy nowy projekt. Z rozpędu wpisujemy dyrektywy kompilatora i kod obsługi stosu. .nolist .include "m8def.inc" .list .cseg .org 0 ;wyłączenie przerwań cli ;załadowanie adresu końca pamięci ldi R16, HIGH(RAMEND) out SPH, R16 ldi R16, LOW(RAMEND) out SPL, R16

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

92

Przed przystąpieniem do dalszej pracy rzeczą pożyteczną będzie uświadomienie sobie, co program ma robić. Otóż zajmiemy się wyświetleniem cyfr od 0 do 9 i od 9 do 0. Szybkość zmiany cyfr ma wynosić ¼ sekundy. By usprawnić program (i zaimponować przyjaciołom), kodowanie cyfr umieścimy w tablicy. Tablicę umieszczamy w pamięci mikrokontrolera… i tu dylemat: w której pamięci? Rozterka jest poważna, pamięć mikrokontrolerów z rodziny AVR składa się bowiem z trzech oddzielnych modułów: pamięci kodu programu flash, ulotnej pamięci danych i trwałej pamięci danych EEPROM (patrz rysunek 5.5). Rysunek 5.5. Rodzaje pamięci i ich organizacja w układzie ATmega8

Oto krótka charakterystyka organizacji pamięci mikrokontrolerów AVR: 1. Pamięć flash to pamięć trwała, w której zapisywany jest kod programu. W mikrokontrolerze ATmega8 pamięć ta może zostać nadpisana 10 000 razy. 2. Pamięć danych jest pamięcią ulotną, co oznacza kasowanie zawartości pamięci w chwili odłączenia zasilania. Pamięć ta może być zapisywana dowolną liczbę razy, z tego względu świetnie nadaje się do przechowywania danych, które w trakcie działania programu będą modyfikowane. W jej skład wchodzą trzy oddzielne przestrzenie adresowe o różnych możliwościach dostępu. Poznaliśmy już technikę operowania na rejestrach roboczych i rejestrach funkcyjnych. Korzystaliśmy też z pamięci SRAM, gdyż w niej implementujemy stos. Umieszczenie tablicy danych w tym obszarze pamięci jest dobrym pomysłem. 3. Pamięć EEPROM jest pamięcią trwałą służącą przechowywaniu danych, które nie mogą ulec zniszczeniu nawet po wyłączeniu zasilania. W mikrokontrolerze ATmega8 pamięć ta może zostać nadpisana 100 000 razy. Przedstawione na początku paragrafu zadanie rozwiążemy na dwa sposoby: w pierwszym programie tablicę danych umieścimy w pamięci flash, w programie drugim dane będą pobierane z pamięci SRAM. Wracamy do wpisanego kodu. Schemat przedstawiony na rysunku 5.4 przekonuje nas, że wszystkie linie portu B powinny być wyjściowe, podobnie jak dwie pierwsze linie portu C. ;ustawienie kierunków transmisji ldi R16, 0xFF ;wszystkie linie portu B wyjściowe out DDRB, R16 ;wszystkie linie portu C wyjściowe out DDRC, R16

Wszystkie linie portu C zostały skonfigurowane w wyjściowym kierunku transmisji. Nie jest to złośliwość, lecz oszczędność kodu. Ponieważ linie od PC2 do PC6 nie obsługują żadnych urządzeń, możemy je ustawić dowolnie. Przy okazji dowiemy się, że warto mieć pewne pożyteczne przyzwyczajenie: lepiej nie podłączać urządzeń do linii PC6, gdyż zgodnie z domyślnymi ustawieniami jest to linia RESET (PC6 może się stać

♦ Obsługa wyświetlacza LED Lekcja 5

93

prawie normalną linią sygnałową po zaprogramowaniu bitu RSTDISBL, a o bitach konfiguracyjnych będzie mowa dopiero w lekcji 8.). RESET jest uaktywniany stanem niskim na linii, więc pośrednie podłączenie (przez jakieś urządzenie) linii PC6 do masy może ciągle stopować pracę mikrokontrolera. Przy braku podpiętych urządzeń do linii RESET takiego niebezpieczeństwa nie ma, a to ze względu na wewnętrzne podciągnięcie do VCC, a nawet — często stosowane — podciągnięcie zewnętrzne (o podciąganiu — dziwne słowo — będzie mowa w lekcji 6.) 1. Cyfry będą wyświetlane na wyświetlaczu W0. Dlatego już teraz możemy podłączyć napięcie do tej części wyświetlacza LED. Robimy to, wysyłając impuls wysoki z linii PC0. ;uruchom pierwszą część wyświetlacza ldi R16, 0b00000001 out PORTC, R16

Program musi zawierać pętlę nieskończoną. Umieśćmy tę pętlę, na razie bez dodatkowego kodu. petla: rjmp petla

Zanim zajmiemy się psuciem tak ładnie wyglądającej pętli, dodajmy do programu podprogram opóźniający działanie programu o ¼ sekundy. Czekaj250ms: ldi R20, 0 czekaj250ms_0: ldi R21, 0 czekaj250ms_1: inc R21 cpi R21, 250 brlo czekaj250ms_1 inc R20 cpi R20, 249 brlo czekaj250ms_0 ret

Na koniec umieszczamy tablicę zakodowanych cyfr. Dziesięć bajtów umieszczonych w pamięci flash poprzedza etykieta, która wskazuje adres początku danych. Każdy wiersz tablicy rozpoczyna dyrektywa informująca o typie jej elementów. Dane naszej tablicy są 8-bitowe, stąd dyrektywa .db. liczba_LED: .db 0b11000000, .db 0b10100100, .db 0b10011001, .db 0b10000010, .db 0b10000000,

0b11111001 ;0, 1 0b10110000 ;2, 3 0b10010010 ;4, 5 0b11111000 ;6, 7 0b10010000 ;8, 9

Liczba bajtów w wierszu definicji danych musi być określona liczbą parzystą, w przeciwnym razie kompilator uzupełni wiersz danych zerem. Wynika to z 16-bitowej organizacji pamięci flash mikrokontrolera. Gdy zdefiniujemy tylko jeden bajt, jak w następującym przykładzie: liczba_LED: .db 0b11000000

otrzymamy ostrzeżenie kompilatora postaci: lekcja5_1.asm(81): warning: .cseg .db misalignment — padding zero byte

Czas omówić związek umieszczonych w pamięci flash wartości z cyframi, które będziemy wizualizować na 0b10110000 wyświetlaczu LED. Na przykład załadowanie do portu B liczby ma wywołać wyświetlenie cyfry 3. W jaki sposób? Otóż wiemy już, że ustawienie linii portu B w niski stan logiczny oznacza zapalenie diody o odpowiadającym linii mikrokontrolera numerze. Widać zatem, że dane tablicy przedstawiają odpowiednie dla wyświetlenia cyfr kombinacje zer i jedynek (patrz rysunek 5.6). 1

Być może pewne elementy tego ustępu są niejasne, a pojęcia — zupełnie już ciemne. Niech się jednak Czytelnik nie martwi, wszystkiego dowie się z dalszej części podręcznika. W owym niewielkim ustępie autor chciał jedynie zaimponować szanownemu Czytelnikowi swoją wiedzą.

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

94 Rysunek 5.6. Schemat obrazujący związek między wysyłaną wartością binarną 0b10110000 a cyfrą zapalaną przez tę wartość na wyświetlaczu LED

Wracamy do pętli głównej programu. Aby móc korzystać z danych umieszczonych w pamięci flash, należy pobrać adres ich pierwszego bajtu. Robimy to za pomocą następującego kodu: ;załaduj adres danych w pamięci flash ldi ZL, LOW(liczba_LED*2) ldi ZH, HIGH(liczba_LED*2)

Dwie rzeczy wymagają wyjaśnienia: 1. Co to są rejestry ZL i ZH? ? 2. Dlaczego adres obliczamy za pomocą wzoru liczba_LED*2 Odpowiedź na pierwsze pytanie: ZL to inna nazwa rejestru R30, ZH to inna nazwa rejestru R31. W niektórych sytuacjach, a do takich należy przechowywanie adresu pamięci, pojemność rejestrów 8-bitowych okazuje się być niewystarczająca. Dlatego w mikrokontrolerach AVR sześć ostatnich rejestrów zostało skonstruowanych specjalnie tak, by mogły być traktowane jako trzy rejestry 16-bitowe (patrz rysunek 5.7). Rysunek 5.7. Schemat organizacji przestrzeni rejestrów roboczych

Owe trzy rejestry 16-bitowe noszą nazwę X, Y i Z. Można wyszczególnić ich część górną i dolną, co oznacza się dodatkowymi literami H i L. Odpowiedź na drugie pytanie (dlaczego adres obliczamy za pomocą wzoru liczba_LED*2 ?): pamięć flash mikrokontrolerów AVR zorganizowana jest w sposób 16-bitowy, dlatego adresowanie także jest podpo0xFFF rządkowane tej architekturze. Na rysunku 5.5 ostatnia komórka pamięci flash ma adres szesnastkowy , co dziesiętnie odpowiada liczbie 4095. Producenci mikrokontrolera ATmega8 zapewniają natomiast, że wspomniany układ posiada 8 kB pamięci flash. Czyżby blefowali? Nic podobnego. Po prostu adresowanie uwzględnia komórki dwubajtowe, a nie jednobajtowe. Dlatego aby obliczyć rzeczywisty adres danych umieszczonych w pamięci flash, należy pomnożyć wartość związaną z etykietą przez liczbę 2. W celu wyświetlenia dziesięciu kolejnych cyfr na wyświetlaczu LED posłużymy się pętlą, która wykona się 10 razy. ldi R22, 0 petla1: inc R22 cpi R22, 10 brlo petla1

♦ Obsługa wyświetlacza LED Lekcja 5

95

Właściwie najtrudniejsze za nami. Teraz wystarczy zapełnić zaprezentowaną pętlę odpowiednimi instrukcjami. Będą to trzy wiersze kodu: lpm: 1. Załadowanie danych z pamięci flash do rejestru (łatwe zadanie). W tym celu wykorzystamy rozkaz lpm R0, Z+

Bajt pamięci znajdujący się pod adresem przechowywanym przez rejestr Z jest ładowany do rejestru R0. R0 to domyślny rejestr dla tego typu operacji (możemy użyć dowolnego innego rejestru, pod warunkiem że nie przechowuje adresu pobieranych danych, czyli nie jest częścią Z). Po wykonaniu zadania zawartość rejestru Z jest zwiększana o 1, dzięki czemu Z wskazuje następną komórkę pamięci flash. 2. Załadowanie portu B odczytaną wartością (bardzo łatwe zadanie): out PORTB, R0

3. Wywołanie podprogramu opóźniającego (niesamowicie łatwe zadanie): rcall Czekaj250ms

Zrealizowana przez nas pierwsza pętla w całości wygląda następująco: ;załaduj adres danych w pamięci flash ldi ZL, LOW(liczba_LED*2) ldi ZH, HIGH(liczba_LED*2) ;wyświetl cyfry od 0 do 9 ldi R22, 0 petla1: ;załaduj cyfrę z pamięci do R0 ;i inkrementuj adres w rejestrze Z lpm R0, Z+ ;załaduj PORTB out PORTB, R0 ;czekaj ¼ sekundy rcall Czekaj250ms ;zwiększ licznik i sprawdź, czy kontynuować pętlę inc R22 cpi R22, 10 brlo petla1

Teraz zajmiemy się realizacją drugiej części pętli głównej, to jest wyświetlaniem cyfr w kolejności od 9 do 0. Aby dodatkowo zaznaczyć tę część pracy mikrokontrolera, zapalimy kropkę, czyli diodę o numerze 7 wyświetlacza LED. Zaczynamy od załadowania adresu ostatniego bajtu tablicy danych. ldi ZL, LOW((liczba_LED*2)+9) ldi ZH, HIGH((liczba_LED*2)+9)

Operacje związane z obliczaniem adresu wykona kompilator w czasie konsolidacji programu. Wyrażenie LOW((liczba_LED*2)+9) będzie zamienione na liczbę, która zostanie załadowana z programem do pamięci mikrokontrolera. Cyfry będą wyświetlane w pętli, która wykona się 10 razy. ldi R22, 0 petla2: inc R22 cpi R22, 10 brlo petla2

Zawartość pętli nie powinna nas zaskoczyć. W pierwszej kolejności ładujemy dane z pamięci flash doR0 rejestru . lpm R0, Z

Niestety nie ma odmiany instrukcjilpm, w której dokonywana by była jednoczesna dekrementacja zawartości rejestru Z. Tym, czego nam potrzeba, jest rozkaz odejmujący wartości od rejestrów 16-bitowych. Traf chce, że istnieje taki rozkaz. sbiw R31:R30, 1

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

96

R25:R24 Pierwszym argumentem instrukcji sbiwmogą być następujące pary rejestrów 8-bitowych: , R27:R26 , R29:R28 , R31:R30 . Drugim argumentem może być liczba całkowita z zakresu od 1 do 63.

Następny krok to załadowanie wartości do rejestru PORTB. out PORTB, R0

Obiecaliśmy także, że zapalimy diodę-kropkę wyświetlacza LED. cbi PORTB, 7

Na końcu wywołujemy podprogram opóźniający działanie programu o ¼ sekundy. rcall Czekaj250ms

Pętla druga jest gotowa. Oto ona od początku do końca: ;załaduj adres danych w pamięci flash ldi ZL, LOW((liczba_LED*2)+9) ldi ZH, HIGH((liczba_LED*2)+9) ;wyświetl cyfry od 9 do 0 ldi R22, 0 petla2: ;załaduj cyfrę z pamięci do R0 lpm R0, Z ;dekrementuj adres w rejestrze Z sbiw R31:R30, 1 ;załaduj PORTB out PORTB, R0 ;zapal kropkę cbi PORTB, 7 ;czekaj ¼ sekundy rcall Czekaj250ms ;zwiększ licznik i sprawdź, czy kontynuować pętlę inc R22 cpi R22, 10 brlo petla2

Zebraliśmy wszystkie elementy potrzebne do napisania programu realizującego pojawianie się cyfr na wyświetlaczu LED. Dane programu przechowywane są w pamięci flash. Listing lekcja5_1.asm ;;; lekcja5_1.asm — dane w pamięci flash ;;; .nolist .include "m8def.inc" .list .cseg .org 0 ;wyłączenie przerwań cli ;załadowanie adresu końca pamięci ldi R16, HIGH(RAMEND) out SPH, R16 ldi R16, LOW(RAMEND) out SPL, R16 ;ustawienie kierunków transmisji ldi R16, 0xFF ;wszystkie linie portu B wyjściowe out DDRB, R16 ;wszystkie linie portu C wyjściowe out DDRC, R16 ;uruchom pierwszą część wyświetlacza ldi R16, 0b00000001 out PORTC, R16

♦ Obsługa wyświetlacza LED Lekcja 5

97

petla: ;załaduj adres danych w pamięci flash ldi ZL, LOW(liczba_LED*2) ldi ZH, HIGH(liczba_LED*2) ;wyświetl cyfry od 0 do 9 ldi R22, 0 petla1: ;załaduj cyfrę z pamięci do R0 ;i inkrementuj adres w rejestrze Z lpm R0, Z+ ;załaduj PORTB out PORTB, R0 ;czekaj ¼ sekundy rcall Czekaj250ms ;zwiększ licznik i sprawdź, czy kontynuować pętlę inc R22 cpi R22, 10 brlo petla1 ;załaduj adres danych w pamięci flash ldi ZL, LOW((liczba_LED*2)+9) ldi ZH, HIGH((liczba_LED*2)+9) ;wyświetl cyfry od 9 do 0 ldi R22, 0 petla2: ;załaduj cyfrę z pamięci do R0 lpm R0, Z ;dekrementuj adres w rejestrze Z sbiw R31:R30, 1 ;załaduj PORTB out PORTB, R0 ;zapal kropkę cbi PORTB, 7 ;czekaj ¼ sekundy rcall Czekaj250ms ;zwiększ licznik i sprawdź, czy kontynuować pętlę inc R22 cpi R22, 10 brlo petla2 rjmp petla Czekaj250ms: ldi R20, 0 czekaj250ms_0: ldi R21, 0 czekaj250ms_1: inc R21 cpi R21, 250 brlo czekaj250ms_1 inc R20 cpi R20, 249 brlo czekaj250ms_0 ret liczba_LED: .db 0b11000000, .db 0b10100100, .db 0b10011001, .db 0b10000010, .db 0b10000000,

0b11111001 0b10110000 0b10010010 0b11111000 0b10010000

;0, ;2, ;4, ;6, ;8,

1 3 5 7 9

Osoby zmęczone powinny w tym momencie nieco odpocząć, na przykład się zdrzemnąć. Osobom pełnym energii polecam zapoznanie się z drugą wersją programu, w której tablica danych będzie przechowywana w pamięci SRAM.

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

98

Program tradycyjnie zaczynamy od pięciu dyrektyw kompilatora: .nolist .include "m8def.inc" .list .cseg .org 0

Teraz nastąpi obsługa stosu. Popatrzmy na rysunek 5.8. Rysunek 5.8. Organizacja pamięci SRAM ze wspólną przestrzenią danych i stosu

Widać na nim typową organizację pamięci SRAM, w której dane znajdują się we wspólnej przestrzeni adresowej ze stosem. Strzałką zaznaczono kierunek rozrastania się stosu. Z rysunku wynika, że istnieje niebezpieczeństwo nadpisania danych przez stos. Inny sposób organizacji pamięci został przedstawiony na rysunku 5.9. Rysunek 5.9. Organizacja pamięci SRAM z przestrzenią danych oddzieloną od przestrzeni stosu

Taka organizacja pamięci SRAM wyklucza możliwość nadpisania danych przez nadmiernie rozrastający się stos. Dlatego proponuję początek stosu umieścić pod adresem (RAMEND-10) . ;wyłączenie przerwań cli ;załadowanie adresu końca pamięci ldi R16, HIGH(RAMEND-10) out SPH, R16 ldi R16, LOW(RAMEND-10) out SPL, R16

Następnie ustawiamy wyjściowe kierunki transmisji portów B i C. Włączamy też pierwszą część wyświetlacza LED. ldi out out ldi out

R16, 0xFF DDRB, R16 DDRC, R16 R16, 0b00000001 PORTC, R16

♦ Obsługa wyświetlacza LED Lekcja 5

99

Kluczowy moment programu to załadowanie danych do pamięci SRAM. Właśnie nadszedł odpowiedni czas na tę czynność. W rejestrze Z umieszczamy adres pierwszego elementu tablicy danych. ldi ZL, LOW(RAMEND-9) ldi ZH, HIGH(RAMEND-9)

Następnie ładujemy pierwszy bajt danych do rejestru R16. ldi R16, 0b11000000

Do umieszczania zawartości rejestru R16 w pamięci SRAM służy rozkazst. Wykorzystamy tę jego wersję, w której inkrementowany jest jednocześnie adres znajdujący się w rejestrze Z. st Z+, R16

W ten sposób ładujemy wszystkie dziesięć bajtów danych. ldi R16, 0b11000000 st Z+, R16 ldi R16, 0b11111001 st Z+, R16 ldi R16, 0b10100100 st Z+, R16 ldi R16, 0b10110000 st Z+, R16 ldi R16, 0b10011001 st Z+, R16 ldi R16, 0b10010010 st Z+, R16 ldi R16, 0b10000010 st Z+, R16 ldi R16, 0b11111000 st Z+, R16 ldi R16, 0b10000000 st Z+, R16 ldi R16, 0b10010000 st Z+, R16

;0 ;1 ;2 ;3 ;4 ;5 ;6 ;7 ;8 ;9

Dociekliwy Czytelnik zapewne łamie sobie głowę, po co umieszczamy dane w pamięci SRAM, skoro i tak muszą one zostać załadowane z pamięci flash. Słuszne pytanie. Otóż tablica umieszczona w pamięci flash nie mogłaby podlegać modyfikacjom, co jest możliwe w przypadku pamięci SRAM. Zawartość pętli głównej programu będzie bardzo przypominała kod pętli głównej poprzedniego przykładu. Jedyna różnica wystąpi w sposobie ładowania danych z pamięci. Do tego zadania użyjemy rozkazu ld. ld R0, Z+

Domyślamy się, że zapis Z+ oznacza wymuszenie inkrementacji adresu przechowywanego w rejestrze Z. Czynność ta nastąpi po załadowaniu bajtu znajdującego się pod adresem Z do rejestru R0. W pętli drugiej, w której realizowany jest proces wyświetlania cyfr w kolejności od 9 do 0, dane pobierzemy za pomocą innej wersji rozkazu ld. ld R0, -Z

Należy pamiętać, że zapis-Z oznacza dekrementację adresu znajdującego się w Z przed załadowaniem danych do rejestru R0. Właśnie dzięki tej własności nie jest konieczne ładowanie adresu danych przed drugą pętlą. Oto cały program obsługujący wyświetlacz LED, w którym dane są pobierane z pamięci SRAM. Listing lekcja5_1a.asm ;;; lekcja5_1a.asm — dane w pamięci SRAM ;;; .nolist .include "m8def.inc" .list .cseg .org 0

100 ;wyłączenie przerwań cli ;załadowanie adresu końca pamięci ldi R16, HIGH(RAMEND-10) out SPH, R16 ldi R16, LOW(RAMEND-10) out SPL, R16 ;ustawienie kierunków transmisji ldi R16, 0xFF ;wszystkie linie portu B wyjściowe out DDRB, R16 ;wszystkie linie portu C wyjściowe out DDRC, R16 ;uruchom pierwszą część wyświetlacza ldi R16, 0b00000001 out PORTC, R16 ;załaduj adres danych w pamięci SRAM ldi ZL, LOW(RAMEND-9) ldi ZH, HIGH(RAMEND-9) ;zachowaj dane cyfr od 0 do 9 ;w pamięci SRAM ldi R16, 0b11000000 ;0 st Z+, R16 ldi R16, 0b11111001 ;1 st Z+, R16 ldi R16, 0b10100100 ;2 st Z+, R16 ldi R16, 0b10110000 ;3 st Z+, R16 ldi R16, 0b10011001 ;4 st Z+, R16 ldi R16, 0b10010010 ;5 st Z+, R16 ldi R16, 0b10000010 ;6 st Z+, R16 ldi R16, 0b11111000 ;7 st Z+, R16 ldi R16, 0b10000000 ;8 st Z+, R16 ldi R16, 0b10010000 ;9 st Z+, R16 petla: ;pobierz adres pamięci danych ldi ZL, LOW(RAMEND-9) ldi ZH, HIGH(RAMEND-9) ;wyświetl cyfry od 0 do 9 ldi R22, 0 petla1: ;załaduj cyfrę z pamięci do R0 ;i inkrementuj adres w rejestrze Z ld R0, Z+ ;załaduj PORTB out PORTB, R0 ;czekaj ¼ sekundy rcall Czekaj250ms ;zwiększ licznik i sprawdź, czy kontynuować pętlę inc R22 cpi R22, 10 brlo petla1 ;wyświetl cyfry od 9 do 0 ldi R22, 0 petla2:

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

♦ Obsługa wyświetlacza LED Lekcja 5

101

;dekrementuj adres w rejestrze Z ;i załaduj cyfrę z pamięci do R0 ld R0, -Z ;załaduj PORTB out PORTB, R0 ;zapal kropkę cbi PORTB, 7 ;czekaj ¼ sekundy rcall Czekaj250ms ;zwiększ licznik i sprawdź, czy kontynuować pętlę inc R22 cpi R22, 10 brlo petla2 rjmp petla Czekaj250ms: ldi R20, 0 czekaj250ms_0: ldi R21, 0 czekaj250ms_1: inc R21 cpi R21, 250 brlo czekaj250ms_1 inc R20 cpi R20, 249 brlo czekaj250ms_0 ret

Zadanie 2. Przypomnijmy, co jest treścią drugiego zadania: wyświetlenie na wyświetlaczu LED liczb od 00 do 99, z prędkością zmiany cyfry na wyświetlaczu W0 wynoszącą 1/10 sekundy oraz prędkością zmiany cyfry na wyświetlaczu W1 wynoszącą 1 sekundę. Tablica danych zostanie umieszczona w pamięci SRAM. Wszystkie elementy potrzebne do zbudowania programu zostały omówione w poprzednim zadaniu. Odpowiednia organizacja pamięci SRAM nakazuje nam przemieścić początek stosu do adresu młodszego o 10 bajtów od wartości RAMEND . cli ldi out ldi out

R16, SPH, R16, SPL,

HIGH(RAMEND-10) R16 LOW(RAMEND-10) R16

Ustawiamy wyjściowe kierunki transmisji portów B i C. ldi R16, 0xFF out DDRB, R16 out DDRC, R16

Teraz załadujemy dane do pamięci SRAM. W celu zoptymalizowania kodu zrobimy to w pętli. Zakładamy, że w pamięci flash mamy umieszczoną tablicę danych. liczba_LED: .db 0b11000000, .db 0b10100100, .db 0b10011001, .db 0b10000010, .db 0b10000000,

0b11111001 ;0, 1 0b10110000 ;2, 3 0b10010010 ;4, 5 0b11111000 ;6, 7 0b10010000 ;8, 9

Aby zdefiniowane dziesięć bajtów danych załadować do pamięci SRAM, potrzebujemy dwóch rejestrów adresowych: Z i Y. W rejestrze Z umieścimy adres danych pamięci flash. ldi ZL, LOW(liczba_LED*2) ldi ZH, HIGH(liczba_LED*2)

102

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

W rejestrze Y umieścimy adres pierwszego z wygospodarowanych dziesięciu bajtów pamięci SRAM. ldi YL, LOW(RAMEND-9) ldi YH, HIGH(RAMEND-9)

Teraz możemy za pomocą pętli umieścić dane w pamięci SRAM. ldi R22, 0 petla_p: ;załaduj cyfrę z pamięci do R0 ;i inkrementuj adres w rejestrze Z lpm R0, Z+ ;załaduj do SRAM st Y+, R0 ;zwiększ licznik i sprawdź, czy kontynuować pętlę inc R22 cpi R22, 10 brlo petla_p

Obsługa dwóch części wyświetlacza LED o wspólnych katodach wymaga pewnego triku. W chwili, gdy wyświetlana jest cyfra na wyświetlaczu W0, wyświetlacz W1 musi być zgaszony. Analogicznie wyświetlanie cyfry na wyświetlacza W1 odbywa się przy wyłączonym wyświetlaczu W0. Czas przełączania części wyświetlacza musi być na tyle krótki, by uzyskać złudzenie jednoczesnej pracy obu części wyświetlacza LED. W celu lepszego zrozumienia zagadnienia posłużymy się przykładem. Załóżmy, że kod pierwszej cyfry służącej do wyświetlenia znajduje się w rejestrze R1, kod drugiej cyfry przechowywany jest w rejestrze R0. Wtedy jednoczesne wyświetlenie obu cyfr możemy zrealizować za pomocą następującego kodu: petla: ;uruchom pierwszą część wyświetlacza ldi R16, 0b00000001 out PORTC, R16 ;załaduj PORTB out PORTB, R1 ;czekaj 1/200 sekundy rcall Czekaj5ms ;uruchom drugą część wyświetlacza ldi R16, 0b00000010 out PORTC, R16 ;załaduj PORTB out PORTB, R0 ;czekaj 1/200 sekundy rcall Czekaj5ms rjmp petla

Każda z części wyświetlacza LED jest aktywna przez 1/200 sekundy. Tyle samo trwa okres zgaszenia diod. Jest to czas na tyle krótki, że nie zauważamy przeskoków cyfr. Sprawa się nieco komplikuje, gdy chcemy zmieniać cyfry pojawiające się na wyświetlaczu LED. Na pierwszej jego części cyfry powinny się zmieniać co1/10 sekundy, na części drugiej okres zmiany będzie wynosił 1 sekundę. Nie ma innego wyjścia, jak tylko zagnieździć pętlę wyświetlającą cyfry w trzech pętlach. Ich organizację przedstawia rysunek 5.10. Poszczególne pętle pełnią następujące role: 1. Pętla nieskończona. Jest pierwszą zewnętrzną pętlą. W niej następuje załadowanie adresu danych do rejestru Z. 2. Pętla obsługująca wyświetlacz W1. Dwie najważniejsze funkcje tej pętli to załadowanie adresu danych do rejestru Y oraz umieszczenie w R0 jednego bajtu pobieranego spod adresu przechowywanego w Z połączone z inkrementacją adresu. Dziesięciokrotne wykonanie tej pętli oznacza wyświetlenie liczb od 00 do 99. 3. Pętla obsługująca wyświetlacz W0. W tej pętli następuje umieszczenie w R1 bajtu danych pobranego spod adresu przechowywanego w Y. 4. Pętla wyświetlająca cyfry na obu częściach wyświetlacza LED.

♦ Obsługa wyświetlacza LED Lekcja 5 Rysunek 5.10. Schemat organizacji pętli w programie obsługującym dwie części wyświetlacza LED

Oto cały program wyświetlający na wyświetlaczu LED liczby od 00 do 99. Listing lekcja5_2.asm ;;; lekcja5_2.asm — dane w pamięci SRAM ;;; .nolist .include "m8def.inc" .list .cseg .org 0 ;wyłączenie przerwań cli ;załadowanie adresu końca pamięci ldi R16, HIGH(RAMEND-10) out SPH, R16 ldi R16, LOW(RAMEND-10) out SPL, R16 ;ustawienie kierunków transmisji ldi R16, 0xFF ;wszystkie linie portu B wyjściowe out DDRB, R16 ;wszystkie linie portu C wyjściowe out DDRC, R16 ;załaduj adres danych w pamięci flash ldi ZL, LOW(liczba_LED*2) ldi ZH, HIGH(liczba_LED*2) ;załaduj adres danych w pamięci SRAM ldi YL, LOW(RAMEND-9) ldi YH, HIGH(RAMEND-9) ;zachowaj dane cyfr od 0 do 9

103

104 ;do pamięci SRAM ldi R22, 0 petla_p: ;załaduj cyfrę z pamięci do R0 ;i inkrementuj adres w rejestrze Z lpm R0, Z+ ;załaduj do SRAM st Y+, R0 ;zwiększ licznik i sprawdź, czy kontynuować pętlę inc R22 cpi R22, 10 brlo petla_p petla: ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; pętla zewnętrzna ;;;;;;;;;;;;;;;;;;;;;;;; ;pobierz adres pamięci danych ldi ZL, LOW(RAMEND-9) ldi ZH, HIGH(RAMEND-9) ;wyzeruj licznik ldi R22, 0 petla0: ;załaduj cyfrę z pamięci do R0 ;i inkrementuj adres w rejestrze Z ld R0, Z+ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; pętla wewnętrzna ;;;;;;;;;;;;;;;;;;;;;;;; ;pobierz adres pamięci danych ldi YL, LOW(RAMEND-9) ldi YH, HIGH(RAMEND-9) ;wyzeruj licznik ldi R23, 0 petla1: ;załaduj cyfrę z pamięci do R1 ;i inkrementuj adres w rejestrze Y ld R1, Y+ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; pętla wyświetlania ;;;;;;;;;;;;;;;;;;;;;; ldi R24, 0 petla2: ;uruchom pierwszą część wyświetlacza ldi R16, 0b00000001 out PORTC, R16 ;załaduj PORTB out PORTB, R1 ;czekaj 1/200 sekundy rcall Czekaj5ms ;uruchom drugą część wyświetlacza ldi R16, 0b00000010 out PORTC, R16 ;załaduj PORTB out PORTB, R0 ;czekaj 1/200 sekundy rcall Czekaj5ms inc R24 cpi R24, 10 brlo petla2 ;;;;;;;;;;;;;;;;;;;;;;petla2 inc R23 cpi R23, 10 brlo petla1 ;;;;;;;;;;;;;;;;;;;;;;petla1 ;zwiększ licznik i sprawdź, czy kontynuować pętlę inc R22 cpi R22, 10 brlo petla0 ;;;;;;;;;;;;;;;;;;;;;;petla0

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

♦ Obsługa wyświetlacza LED Lekcja 5

105

rjmp petla Czekaj5ms: ldi R20, 0 czekaj5ms_0: ldi R21, 0 czekaj5ms_1: inc R21 cpi R21, 250 brlo czekaj5ms_1 inc R20 cpi R20, 5 brlo czekaj5ms_0 ret liczba_LED: .db 0b11000000, .db 0b10100100, .db 0b10011001, .db 0b10000010, .db 0b10000000,

0b11111001 ;0, 1 0b10110000 ;2, 3 0b10010010 ;4, 5 0b11111000 ;6, 7 0b10010000 ;8, 9

Zestawienie nowych instrukcji w kolejności ich wystąpienia w paragrafie: lpm Rd, Z+ — załadowanie bajtu danych spod adresu Z z pamięci flash do rejestru Rd (Rd = R0, R1, R2, …, R27, R28, R29), połączone z inkrementacją adresu przechowywanego w rejestrze Z. Pozostałe postacie instrukcji: lpm— załadowanie bajtu danych spod adresu Z z pamięci flash do rejestru R0. lpm Rd, — Z załadowanie bajtu danych spod adresu Z z pamięci flash do rejestru Rd. sbiw Rd+1:Rd, — K odejmij wartość całkowitą K (0 – 63) od rejestru 16-bitowego Rd+1:Rd (Rd = R24, R26, R28, R30). st Z+, Rr— załadowanie bajtu danych z rejestru Rr (Rr = R0, R1, R2, …, R29, R30, R31) pod adres Z pamięci SRAM, połączone z inkrementacją adresu przechowywanego w rejestrze Z. Pozostałe postacie instrukcji: st X, Rr — załadowanie bajtu danych z rejestru Rr pod adres X pamięci SRAM. st X+, Rr — załadowanie bajtu danych z rejestru Rr pod adres X pamięci SRAM, połączone z inkrementacją adresu przechowywanego w rejestrze X. st -X, Rr — dekrementacja adresu przechowywanego w rejestrze X, a następnie załadowanie bajtu danych z rejestru Rr pod adres X pamięci SRAM. st Y, Rr — załadowanie bajtu danych z rejestru Rr pod adres Y pamięci SRAM. st Y+, Rr — załadowanie bajtu danych z rejestru Rr pod adres Y pamięci SRAM, połączone z inkrementacją adresu przechowywanego w rejestrze Y. st -Y, Rr — dekrementacja adresu przechowywanego w rejestrze Y, a następnie załadowanie bajtu danych z rejestru Rr pod adres Y pamięci SRAM. st Z, Rr — załadowanie bajtu danych z rejestru Rr pod adres Z pamięci SRAM. st -Z, Rr — dekrementacja adresu przechowywanego w rejestrze Z, a następnie załadowanie bajtu danych z rejestru Rr pod adres Z pamięci SRAM. ld Rd, Z+ — załadowanie bajtu danych z pamięci SRAM o adresie Z do rejestru Rd (Rd = R0, R1, R2, …, R23, R24, R25), połączone z inkrementacją rejestru Z. Pozostałe postacie instrukcji: ld Rd, X — załadowanie bajtu danych z pamięci SRAM o adresie X do rejestru Rd. ld Rd, X+ — załadowanie bajtu danych z pamięci SRAM o adresie X do rejestru Rd, połączone z inkrementacją adresu przechowywanego w rejestrze X.

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

106

ld Rd, -X — dekrementacja adresu przechowywanego w rejestrze X, a następnie załadowanie

bajtu danych z pamięci SRAM o adresie X do rejestru Rd. ld Rd, Y — załadowanie bajtu danych z pamięci SRAM o adresie Y do rejestru Rd. ld Rd, Y+ — załadowanie bajtu danych z pamięci SRAM o adresie Y do rejestru Rd, połączone z inkrementacją adresu przechowywanego w rejestrze Y. ld Rd, -Y — dekrementacja adresu przechowywanego w rejestrze Y, a następnie załadowanie bajtu danych z pamięci SRAM o adresie Y do rejestru Rd. ld Rd, Z — załadowanie bajtu danych z pamięci SRAM o adresie Z do rejestru Rd. ld Rd, -Z — dekrementacja adresu przechowywanego w rejestrze Z, a następnie załadowanie bajtu danych z pamięci SRAM o adresie Z do rejestru Rd.

5.2. Język C Zagadnienia: Podstawowe typy danych liczbowych języka C. Jednowymiarowa tablica danych. Pętla for. Ustawianie i zerowanie bitu w języku C. Obsługa wyświetlacza LED. Wszystko, co w asemblerze było trudne, w językach wyższego poziomu będzie łatwe. Przygotujmy się więc na relaks przy lekturze tego paragrafu. Pierwsze zadanie polega na wyświetleniu na pierwszej części wyświetlacza LED cyfr w kolejności od 0 do 9 i od 9 do 0. Przy kolejności wyświetlania cyfr od 9 do 0 dodatkowo powinna się palić dioda siódma (kropka) wyświetlacza LED. Zaczynamy od wpisania dyrektyw kompilatora. #include #define F_CPU 1000000 #include

Będziemy wysyłać do wyświetlacza LED zakodowane cyfry przy użyciu tablicy. Przed jej zdefiniowaniem przypomnijmy sobie podstawowe typy liczbowe języka C. Pewną ciekawostką jest to, że prócz standardowych typów danych języka C istnieją także typy specyficzne dla programowania mikrokontrolerów (patrz tabela 5.1). Podstawowe typy danych liczbowych języka C Tabela 5.1. Typ standardowy języka C

Odpowiednik typu w programowaniu mikrokontrolerów

Rozmiar typu (w bitach)

signed char

int8_t

8Typ

unsigned char

uint8_t

8Typ

signed int

int16_t

16

Typ całkowity znakowy

unsigned int

uint16_t

16

Typ całkowity bezznakowy

signed long int

int32_t

32

Typ całkowity znakowy

unsigned long int

uint32_t

Uwagi całkowity znakowy całkowity bezznakowy

32

Typ całkowity bezznakowy

signed long long int int64_t

64

Typ całkowity znakowy

unsigned long long intuint64_t

64

Typ całkowity bezznakowy

float

-

32

Typ zmiennoprzecinkowy

double

-

32

Typ zmiennoprzecinkowy

♦ Obsługa wyświetlacza LED Lekcja 5

107

Typy danych występujące w jednym wierszu są całkowicie zamienne i przez środowisko WinAVR nieodróżnialne. Dlatego możemy pisać na przykład uint8_tzamiast unsignedchari odwrotnie. Pierwszą zadeklarowaną przez nas zmienną będzie zmienna indeksowa i. Posłuży nam ona do pobrania elementu tablicy. Ponieważ tablica danych będzie miała dziesięć elementów, wystarczy, by i była zmienną 8-bitową. int8_t i;

Teraz deklarujemy dziesięcioelementową tablicę danych. Robimy to, podając typ tablicy oraz umieszczając po nazwie tablicy jej rozmiar zapisany w nawiasach kwadratowych. unsigned char liczba_LED[10];

W języku C deklarację tablicy możemy połączyć z definicją jej zawartości. Zakodowane cyfry umieścimy w formacie szesnastkowym. unsigned char liczba_LED[10] = {0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90};

Przypominam, że obsługujemy układ połączony tak, jak pokazano na rysunku 5.4. Dlatego musimy ustawić wyjściowe kierunki transmisji portów B i C. Dodatkowo włączymy wyświetlacz W0. DDRB = 0xFF; DDRC = 0xFF; PORTC = 0x01;

for. Do tej pory pisaliśmy inPrzerwijmy na chwilę pisanie programu, by omówić zagadnienie tworzenia pętli strukcję: for(;;);

for. A może jednak nie? mówiąc, że jest to pętla nieskończona. Zakładam, że Czytelnik wie, co to jest pętla Spróbuję więc wyjaśnić to pojęcie w dwóch słowach.

Ogólnie pojęcie pętli jest raczej intuicyjne. Zaczynamy jakąś czynność dla pewnych warunków początkowych, kontynuujemy aż do spełnienia warunku zakończenia pętli. Na przykład w asemblerze pętla wykonująca się 10 razy wyglądała tak: ldi R22, 0 petla: ;różne ciekawe instrukcje ;… ;inkrementacja zmiennej (rejestru) pętli inc R22 ;jak się ma wartość w R22 do liczby 10 cpi R22, 10 ;jeśli wartość w R22 jest mniejsza od 10, wróć do etykiety petla brlo petla2

Przeanalizowaliśmy ten temat w ramach lekcji 4. A jak to jest z pętlą języka wyższego poziomu, na przykład z pętlą for? Bardzo podobnie. Pętla forwykonująca się 10 razy może wyglądać tak: int8_t i; for(i=0; i 0 Then Portc = Portc Xor 1 Licznik = Licznik — 1 Else Portc.0 = 0 End If Return

Do zrealizowania pozostało nam drugie zadanie, w którym chcemy mikrokontroler grzecznie poprosić, aby poszedł spać. W języku Bascom funkcjonują trzy procedury usypiające:Idle, Powerdown oraz Powersave . Zakresem wyłączanych modułów odpowiadają trybom wstrzymania o takiej samej nazwie opisanym w dokumentacji. Nas zainteresuje proceduraPowerdown . Zacznijmy jednak od pętli głównej. Tu będziemy sprawdzać stan przycisku na linii PD0, a stwierdziwszy, że ten jest nienaruszony, będziemy na przemian zaświecać i gasić diody D1 i D2. Po odebraniu sygnału o wciśnięciu przycisku nastąpi wywołanie procedury Powerdown . 'pętla nieskończona Do 'sprawdź, czy został naciśnięty przycisk S2 If Pind.0 = 0 Then Powerdown 'diody świecą na przemian

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

216 Portc.0 = 1 Portc.2 = 0 'zaczekaj 50 ms Waitms 50 'diody świecą na przemian Portc.0 = 0 Portc.2 = 1 'zaczekaj 50 ms Waitms 50 Loop

Procedura Powerdown , prócz asemblerowej instrukcji sleep, zawiera kod odpowiednio ustawiający bity rejestru MCUCR. Dlatego nie ma potrzeby samodzielnego ich ładowania. Natomiast w podprogramie obsługi przerwania INT0 warto wyzerować 4 najstarsze bity MCUCR. Co prawda, można potraktować ów podprogram jedynie jako budzik przerywający czas wstrzymania systemu, dokumentacja mikrokontrolerów AVR zaleca jednak, aby w mikrokontrolerze niebędącym w stanie uśpienia przede wszystkim nie pozostawiać ustawionego bitu SE. 'podprogram obsługi przerwania Int0 Int0_int: 'wyzeruj bity dotyczące stanu uśpienia w MCUCR Mcucr = Mcucr And &B00001111 Return

Pozostało nam jeszcze odpowiednio skonfigurować przerwanie INT0 oraz ustawić linie dla diod i przycisków. Oto zrealizowaliśmy drugie zadanie w ramach tej lekcji — dla kompilatora Bascom. Listing lekcja8_2.bas $regfile = "m8def.dat" $crystal = 1000000 'diody na liniach PC0 i PC2 Config Portc.0 = Output Config Portc.2 = Output 'na linii PD0 przycisk S2 wprowadzania w stan uśpienia Config Portd.0 = Input Portd.0 = 1 'ustawienia linii Int0 (PD2) Config Portd.2 = Input 'włącz rezystor podciągający Portd.2 = 1 'ustawienia dla przerwania INT0 'a) wybierz reakcję na poziom niski Config Int0 = Low Level 'b) podprogram obsługi przerwania Int0 o nazwie Int0_int On Int0 Int0_int 'c) włącz obsługę przerwania INT0 Enable Int0 'włącz globalną obsługę przerwań Enable Interrupts 'pętla nieskończona Do 'sprawdź, czy został naciśnięty przycisk S2 If Pind.0 = 0 Then Powerdown 'diody świecą na przemian Portc.0 = 1 Portc.2 = 0 'zaczekaj 50 ms

♦ Obsługa przerwań, a przy tym o bitach konfiguracyjnych i śpiochach słów217 Lekcja 8 parę Waitms 50 'diody świecą na przemian Portc.0 = 0 Portc.2 = 1 'zaczekaj 50 ms Waitms 50 Loop 'podprogram obsługi przerwania Int0 Int0_int: 'wyzeruj bity dotyczące stanu uśpienia w MCUCR Mcucr = Mcucr And &B00001111 Return

Zestawienie nowych instrukcji języka Bascom: On — instrukcja tworząca powiązanie między przerwaniem a podprogramem jego obsługi. Idle, Powerdown, Powersave — procedury wprowadzające mikrokontroler w stan uśpienia Load TIMERx, — W instrukcja ładująca do rejestru TCNTx wartość W.

8.4. Pascal Zagadnienia: Mądre rady pana Pickwicka. Sposoby konfigurowania rejestrów funkcyjnych. Procedury obsługi przerwań i ich adresy. O dwóch konwencjach adresowania pamięci flash. Zadanie pierwsze. Zadanie drugie. W tej części lekcji 8., w której programujemy przy użyciu języka Pascal, towarzyszyć nam będą mądre rady pana Pickwicka. Słońce, ten punktualny sługa pracy, zaledwie wstało i rzuciło światło na poranek trzynastego maja 1827 roku, gdy pan Pickwick, podobny do drugiego słońca, wyrwał się z objęć snu; otworzył okno i spojrzał na świat. Ulica Goswell leżała u jego stóp, ulica Goswell po prawej ręce — jak daleko wzrok sięgał — ulica Goswell po lewej ręce; a naprzeciwko niego druga strona ulicy Goswell. „Takie to są — pomyślał pan Pickwick — ciasne poglądy filozofów poprzestających na badaniu powierzchni rzeczy i nie usiłujących zbadać ukrytych tajemnic. I ja mógłbym tak jak oni poprzestać na badaniu ulicy Goswell, nie robiąc żadnych usiłowań, by dostać się do otaczających ją nieznanych okolic!”3 Także i my nie możemy poprzestać na badaniu powierzchni rzeczy. A chodzi o rzecz niebagatelną: w jaki sposób odwoływać się do bitów rejestrów funkcyjnych? Konfiguracja przerwań w kompilatorze mikroPascal PRO for AVR przypomina mechanizm znany z asemblera i języka C. O odpowiednie ustawienie rejestrów dedykowanych przerwaniu musimy zadbać sami. Na przykład ustawiając w przerwaniu INT0 reakcję na opadające zbocze, musimy ustawić bit 1. Można to zrobić tak: MCUCR.1 := 1;

Można zastosować też konwencję, w której numer x bitu jest podawany za pomocą stałej Bx. MCUCR.B1 := 1; 3

Karol Dickens, Klub Pickwicka, tłum. W. Górski, Warszawa 1991, s. 12.

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

218

I już rodzi się pierwsze pytanie: dlaczego można tak i tak? Odpowiedź znajdziemy w plikach definicji, czyli pascalowych modułach. Oto fragment pliku ATmega8.mpas: // Individual bit access constants const B0 = 0; const B1 = 1; const B2 = 2; const B3 = 3; const B4 = 4; const B5 = 5; const B6 = 6; const B7 = 7;

Wyrażenie constjest słowem kluczowym języka Pascal, oznaczającym utworzenie stałych obiektów posiadających pewną przypisaną im w czasie definiowania wartość, której nie można już później w programie B1 przypisano liczbę 1. Od tej pory każde zmienić. Na przykład w zaprezentowanym fragmencie kodu nazwie użycie B1 będzie równoważne użyciu liczby 1. Rozumiemy teraz, że nazwy B0, B1, …, B7 odpowiadają odpowiednim liczbom od 0 do 7. Pierwszy bit rejestruMCUCRnosi nazwę ISC01(patrz rysunek 8.21). Okazuje się, że dozwolone jest użycie także takiego zapisu: MCUCR.ISC01 := 1;

Ten fakt nas zupełnie nie dziwi. Spodziewamy się, że ISC01, jak i wiele podobnych nazw bitów rejestrów funkcyjnych, pełni rolę stałych odpowiadających pewnym wartościom. Tak jest istotnie. W pliku ATmega8.mpas łatwo możemy odnaleźć świadczący o tym fragment: const ISC01 = 1;

Czyli właściwie zamiast stałejISC01możemy użyć dowolnej innej nazwy, o przypisanej wartości 1. Na przykład poprawny jest zapis: MCUCR.EXTRF := 1;

co wynika z następującej definicji: const EXTRF = 1;

Ostatnia, czwarta konwencja konfiguracji bitów rejestrów umożliwia bezpośrednie odwoływanie się do nazw bitów. ISC01_bit := 1;

_bit. Znowu zajrzyjmy do pliku Zapis nazwy nie jest przypadkowy: zawiera oznaczenie bitu, a po nim słowo definicji. Znajdziemy taki fragment: var MCUCR var const var const var const var const var const var const var const var const

: byte; absolute 0x55; io; volatile; sfr; ISC00_bit : sbit at MCUCR.B0; ISC00 = 0; register; ISC01_bit : sbit at MCUCR.B1; ISC01 = 1; register; ISC10_bit : sbit at MCUCR.B2; ISC10 = 2; register; ISC11_bit : sbit at MCUCR.B3; ISC11 = 3; register; SM0_bit : sbit at MCUCR.B4; SM0 = 4; register; SM1_bit : sbit at MCUCR.B5; SM1 = 5; register; SM2_bit : sbit at MCUCR.B6; SM2 = 6; register; SE_bit : sbit at MCUCR.B7; SE = 7; register;

♦ Obsługa przerwań, a przy tym o bitach konfiguracyjnych i śpiochach słów219 Lekcja 8 parę

W pliku pomocy kompilatora mikroPascal PRO można wyczytać, żesbitjest specjalnym typem danych, za pomocą którego możemy bezpośrednio odwoływać się do bitów rejestrów. Na przykład gdy mamy podłączoną diodę na linii PC0, po zdefiniowaniu odpowiedniej zmiennej: var dioda: sbit at PORTC.B0;

ustawianie wartości logicznej na linii PC0 może odbywać się następująco: dioda := 0;

I tak oto, nie poprzestając na badaniu powierzchni rzeczy, stwierdziliśmy, że możliwe jest użycie aż czterech konwencji zapisu ładowania bitów. Przejdźmy teraz do głównego tematu naszej lekcji, to jest do przerwań. Wiemy już, że ich użycie wiąże się z koniecznością ustawiania bitów rejestrów dedykowanych przerwaniu. Na przykład tak skonfigurujemy obsługę przerwania INT0: //ustawienia dla przerwania INT0 //a) włącz obsługę przerwania INT0 GICR.INT0 := 1; //b) wybierz reakcję na opadające zbocze MCUCR.ISC01 := 1; //c) ustaw linię INT0 (PD2) w kierunku wejściowym DDRD.2 := 0; //d) włącz na linii rezystor podciągający PORTD.2 := 1;

Wszystkie nazwy rejestrów są standardowe, skopiowane z dokumentacji mikrokontrolerów AVR. Kod obsługi przerwań umieszczamy w procedurach, które mogą mieć dowolne nazwy, a o ich specjalnym znaczeniu świadczy przypisany im adres. Jako przykład zdefiniujemy procedurę obsługi przerwania INT0: procedure Przerwanie_Int0(); org IVT_ADDR_INT0; begin //kod end;

INT0. Zestawienie wszystkich Wyrażenie IVT_ADDR_INT0 jest stałą, której przypisano wartość adresu przerwania nazw wektorów przerwań dla mikrokontrolera ATmega8 zawiera tabela 8.14. Zestawienie nazw wektorów przerwań w języku Pascal Tabela 8.14. Adres przerwania (dla ustawień domyślnych) Nazwa przerwania 0x000

IVT_ADDR_RESET

0x002

IVT_ADDR_INT0

0x004

IVT_ADDR_INT1

0x006

IVT_ADDR_TIMER2_COMP

0x008

IVT_ADDR_TIMER2_OVF

0x00A

IVT_ADDR_TIMER1_CAPT

0x00C

IVT_ADDR_TIMER1_COMPA

0x00E

IVT_ADDR_TIMER1_COMPB

0x010

IVT_ADDR_TIMER1_OVF

0x012

IVT_ADDR_TIMER0_OVF

0x014

IVT_ADDR_SPI__STC

0x016

IVT_ADDR_USART__RXC

0x018

IVT_ADDR_USART__UDRE

0x01A

IVT_ADDR_USART__TXC

0x01C

IVT_ADDR_ADC

0x01E

IVT_ADDR_EE_RDY

0x020

IVT_ADDR_ANA_COMP

0x022

IVT_ADDR_TWI

0x024

IVT_ADDR_SPM_RDY

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

220

Przypisanie adresu do procedury nie powoduje, że jej kod zostanie umieszczony w miejscu wskazanym adresem. Jak pamiętamy, na każdy z wektorów przerwań zarezerwowano jedynie 2 bajty. W miejscu wskazanym adresem orgzostanie umieszczony rozkaz skoku bezwarunkowego do procedury obsługi przerwania. Wyjście z przerwania jest wywoływane za pomocą rozkazureti. Niestety, kod języka Pascal jest tak skonstruowany, że wywołanie przerwania o niezdefiniowanej procedurze obsługi wywoła RESET mikrokontrolera. Jeszcze jedna rzecz wymaga omówienia: jak to jest właściwie z adresami przerwań? W tabelach zamieszczonych w ramach niniejszej lekcji konsekwentnie zapisywane były adresy zwiększające się o 2 jednostki. A zatem IVT_ADDR_RESET ma adres 0, ale następny wektorIVT_ADDR_INT0 ma już adres 0x0002. Zdarzy się nam pewnie także oglądać tabele, w których adresy zwiększają się o 1. Które wartości są więc prawdziwe? Jedne i drugie. Pierwszy sposób pisania adresów, stosowany w tej książce, odnosi się do adresów fizycznych, wyrażonych w bajtach. Drugi sposób pisania adresów odnosi się do komórek rozkazowych, które — jak wiadomo — są dwubajtowe. Mamy już wystarczająco dużo informacji, by napisać pierwszy program, w którym obsługujemy układ z rysunku 8.17. Przypominam, że naciśnięcie przycisku S1, które wyzwala przerwanie INT0, powinno wywołać pięciokrotne mrugnięcie diody LED. Kolejne zaświecanie i gaszenie diody zrealizujemy za pomocą znanego operatora xor. Listing lekcja8_1.mpas program lekcja8_1; var licznik: Byte; procedure Przerwanie_Int0(); org IVT_ADDR_INT0; begin licznik := 10; end; procedure Przerwanie_Timer1(); org IVT_ADDR_TIMER1_OVF; begin if(licznik > 0) Then begin PORTC.0 := PORTC.0 xor 0x01; Dec(licznik); end else PORTC.0 := 0; end; begin //dioda na linii PC0 DDRC.0 := 1; //dioda na razie nie świeci PORTC.0 := 0; //ustawienia dla przerwania INT0 //a) włącz obsługę przerwania INT0 GICR.INT0 := 1; //b) wybierz reakcję na opadające zbocze MCUCR.ISC01 := 1; //c) ustaw linię INT0 (PD2) w kierunku wejściowym DDRD.2 := 0; //d) włącz na linii rezystor podciągający PORTD.2 := 1; //ustawienia Timer1 //a) wartość preskalera = 8 // (65535 * 8 = 524280, czyli nieco ponad ½ sekundy) TCCR1B.CS11 := 1; //b) włącz obsługę przerwania przepełnienia Timer1 TIMSK.TOIE1 := 1;

♦ Obsługa przerwań, a przy tym o bitach konfiguracyjnych i śpiochach słów221 Lekcja 8 parę //włącz globalną obsługę przerwań SREG.SREG_I := 1; //pętla nieskończona while TRUE do begin end; end.

Pozostało nam do wykonania drugie zadanie. Naszym celem będzie przełączenie mikrokontrolera w tryb zmniejszonego poboru energii. W tym fragmencie pracy z układami AVR kompilator mikroPascal PRO for AVR nie oferuje absolutnie żadnych udogodnień. Wszystkie ustawienia dotyczące trybu uśpienia należy wykonać samodzielnie w rejestrze MCUCR. Nawet nie istnieje procedura wyższego poziomu odpowiadająca asemblerowej instrukcji sleep. Aby przełączyć układ w tryb uśpienia, należy rozkaz sleepwywołać we wstawce asemblerowej w kodzie programu. asm sleep end

Rozwiążmy drugie zadanie. Program będzie wyglądał tak, jak pokazano poniżej. Listing lekcja8_2.mpas program lekcja8_2; procedure Przerwanie_Int0(); org IVT_ADDR_INT0; begin //a) wyzeruj bity dotyczące stanu uśpienia w MCUCR MCUCR.SM0 := 0; MCUCR.SM1 := 0; MCUCR.SM2 := 0; MCUCR.SE := 0; end; procedure Synku_spij(); begin //a) ustaw bit SE w MCUCR // (zgodnie z dokumentacją przed użyciem sleep) //b) ustaw bit SM1 (tryb Power-down) MCUCR.SE := 1; MCUCR.SM1 := 1; //uśpij asm sleep end end; begin //diody na liniach PC0 i PC2 DDRC.0 := 1; DDRC.2 := 1; //na linii PD0 przycisk S2 wprowadzania w stan uśpienia DDRD.0 := 0; PORTD.0 := 1; //ustawienia dla przerwania INT0 //a) włącz obsługę przerwania INT0 GICR.INT0 := 1; //b) wybierz reakcję na poziom niski MCUCR := 0;

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

222 //c) ustaw linię INT0 (PD2) w kierunku wejściowym DDRD.2 := 0; //d) włącz na linii rezystor podciągający PORTD.2 := 1; //włącz globalną obsługę przerwań SREG.SREG_I := 1;

//pętla nieskończona while TRUE do begin //sprawdź, czy został naciśnięty przycisk S2 if PIND.0 = 0 then Synku_spij(); //diody świecą na przemian PORTC.0 := 1; PORTC.2 := 0; //zaczekaj 50 ms Delay_ms(50); //diody świecą na przemian PORTC.2 := 1; PORTC.0 := 0; //zaczekaj 50 ms Delay_ms(50); end; end.

Zgodnie z obietnicą program jest bardzo surowy, niemalże asemblerowy. Za te niedogodności kompilator odwdzięcza się nam najmniejszym kodem (194 bajty w pamięci flash) spośród wszystkich języków wyższego poziomu. Jedynie kod asemblerowy, co zrozumiałe, jest mniejszy (124 bajty skompilowanego programu lekcja8_2.asm). Im więcej udogodnień w programie, tym większy obszar pamięci flash on zapełnia. I tak skompilowany program lekcja8_2.c, napisany w języku C, zajmuje 242 bajty, a lekcja8_2.bas języka Bascom, oczywiście w postaci skompilowanej, rezerwuje aż 340 bajtów pamięci flash. Na koniec pan Pickwick uraczy nas anegdotą, którą można streścić słowami poety: „Sięgaj, gdzie wzrok nie sięga”. Niewątpliwie te słowa pasują do ćwiczeń. Więc sięgajmy! […] pan Pickwick wysunął głowę za okno i spojrzał dokoła. Ostry, a zarazem przyjemny zapach siana tylko co skoszonego dochodził do niego. Tysiączne kwiaty w ogrodzie napełniały balsamiczną wonią powietrze; zielona łąka błyszczała od rosy i każde źdźbło połyskiwało, poruszane lekkim wietrzykiem. Wreszcie ptaki śpiewały, jakby każda łza poranna była dla nich źródłem natchnienia. Przypatrując się temu, pan Pickwick wpadł w słodką i tajemniczą zadumę. — Hej! hej! Dźwięki te przywołały go do rzeczywistego życia. Wzrok jego zwrócił się nagle w prawo i nic nie odkrył. Więc skierował się w lewo, ale na próżno błądził w przestrzeni. Śmiałym okiem zmierzył firmament, ale nie stamtąd go wołano. Na koniec uczynił to, co pospolity umysł uczyniłby od razu: spojrzał prosto w ogród i zobaczył pana Wardle’a4. Zestawienie nowych instrukcji i procedur języka Pascal: const— słowo kluczowe tworzące stałą. org— dyrektywa wymuszająca wstawienie kodu pod adresem podanym po słowie org. W przypadku procedur obsługi przerwań dyrektywa tworzy przypisanie procedury do odpowiedniego wektora przerwania.

4

Karol Dickens, op. cit., s. 89 – 90.

♦ Obsługa przerwań, a przy tym o bitach konfiguracyjnych i śpiochach słów223 Lekcja 8 parę

8.5. Ćwiczenia 1. W celu maksymalnego oszczędzania energii w telefonach komórkowych po kilkunastu sekundach bezczynności następuje automatyczne przejście w tryb zmniejszonego poboru energii. Ten fakt zaobserwowaliśmy na pewno niejednokrotnie, gdy spieszyliśmy z naciśnięciem dowolnego klawisza w celu uaktywnienia wyświetlacza LCD. Projektowany program ma zachowywać się podobnie: należy obsłużyć układ z rysunku 8.17, tak aby po dziesięciokrotnym mrugnięciu diody D1 (w odstępach półsekundowych) mikrokontroler przeszedł w tryb uśpienia. Jeśli zostanie wykryte wciśnięcie przycisku S1, liczenie mrugnięć diody należy zacząć od początku. Także wyjście z trybu uśpienia ma nastąpić w wyniku wykrycia wciśnięcia tego przycisku. 2. Ćwiczenie 2. to prawdziwa żonglerka kodem. Należy obsłużyć układ z rysunku 8.25, tak aby: a) dioda D1 świeciła co 170 milisekund (tu należy użyć układu Timer2), b) dioda D2 świeciła co 1,5 sekundy (tu należy użyć układu Timer1), c) dioda D3 świeciła się w chwili zwarcia przycisku S1. Rysunek 8.25. Układ do ćwiczenia 2.

224

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

Lekcja 9

Obsługa wyświetlacza alfanumerycznego LCD Bardzo możliwe, że najdzie nas kiedyś ochota, by za pomocą mikrokontrolera wyświetlić brzydkie słowo1. Do realizacji tak szlachetnego zadania nie wystarczy kilka diod LED, a jeśli epitet należy do szczególnie skomplikowanych, nieprzydatny okazuje się nawet wyświetlacz LED. Z pomocą może nam przyjść wyświetlacz LCD2. A ponieważ jest to zgodne z tematem niniejszej lekcji, pójdźmy tym tropem i dowiedzmy się nieco o obsłudze alfanumerycznego wyświetlacza LCD. Mówiąc o alfanumerycznych (czyli niegraficznych) wyświetlaczach LCD, mamy najczęściej na myśli układy ze sterownikiem HD44780. Tak jest i tym razem. W tego typu wyświetlaczach spotykamy się ze stałą listą instrukcji, jednakowym sposobem ich inicjalizacji i obsługi. Stanowi to duże udogodnienie dla programisty. Zanim jednak zaczniemy wyświetlacz programować, warto go podłączyć, przy czym proponuję najprostsze z możliwych — połączenie realizowane tylko za pomocą 6. linii (RS, EN, DB4..7). Wymienione linie można podłączyć do dowolnego portu, na przykład do portu B (patrz rysunek 9.1). Rysunek 9.1. Schemat podłączenia alfanumerycznego wyświetlacza LCD do płytki uruchomieniowej

1

2

Uściślijmy, aby nie było niejasności: gdy mówię o brzydkich wyrazach, mam na myśli jednym tchem wypowiadany zwrot „Ty struclu niedopieczony”. Trzeba przyznać, że to okrutne słowa! Wizualizacje znaków są realizowane na wyświetlaczu przez ciekłe kryształy, tworzące układy o nazwach tak strasznych, że bledną przy nich wszystkie nasze przekleństwa. Przekonajmy się: mówimy o luźno uporządkowanych układach nematycznych, smektycznych i cholesterycznych!

226

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

Jako się rzekło, zaproponowany schemat podłączenia wyświetlacza LCD należy do najprostszych. Jest to tak zwany jednokierunkowy interfejs 4-bitowy, w którym transmisja 8-bitowych danych odbywa się w dwóch etapach, wysyłających po 4 bity każdy. Skrótowy opis rysunku przedstawia się następująco: DB4..7 — linie służące do przesyłania danych. EN — za pomocą tej linii (Enable) sygnalizowany jest ważny transfer danych. RW — linia określająca kierunek transmisji danych. Stan logicznej 1 na tej linii umożliwia odczytanie zawartości pamięci wyświetlacza LCD. Logiczne 0 daje możliwość zapisania danych do pamięci wyświetlacza. Linia RW na rysunku została podłączona do masy, co jest równoważne przypisaniu jej wartości 0. I na koniec tego detektywistycznego ciągu logicznych powiązań — oznacza to opcję zapisywania danych do pamięci wyświetlacza, bez możliwości odczytania jego pamięci. RS — linia sygnalizująca przesyłanie rozkazu (stan niski) lub danych (wysoki stan logiczny). V0 — linia nastawy kontrastu. VDD — linia podłączenia napięcia +5 V. VSS — linia podłączenia masy. Wróćmy na chwilę do zagadnienia regulacji kontrastu. Na rysunku 9.1 widać, że podłączenie V0 do masy uniemożliwia jakąkolwiek regulację i daje obraz o pikselach maksymalnie nasyconych czernią. Powoduje to zawężenie zakresu kątów, pod którymi spoglądanie na wyświetlacz pozwala odczytać zawarte na nim informacje. Dlatego osobom bardzo wymagającym na pewno spodoba się układ z dodatkowym potencjometrem P1, dzięki któremu możliwa jest płynna regulacja kontrastu (patrz rysunek 9.2). Rysunek 9.2. Schemat podłączenia alfanumerycznego wyświetlacza LCD do płytki uruchomieniowej. Układ umożliwia nastawę kontrastu za pomocą potencjometru P1 podłączonego do linii V0

W niektórych wyświetlaczach LCD istnieją linie 15. i 16., oznaczone na schematach jako A i K. Jeśli nasz wyświetlacz jest w nie zaopatrzony, oznacza to, że możemy włączyć podświetlanie, znacznie ułatwiające odczytanie informacji wyświetlanych na LCD. W tym celu do linii A (anoda) należy podłączyć napięcie około +5 V, a do linii K (katoda) masę. Wspomnieliśmy wcześniej o instrukcjach sterownika HD44780. Przyjrzyjmy się im bliżej. Spis instrukcji sterownika HD44780 zawiera tabela 9.1. Programową obsługą przedstawionych instrukcji zajmiemy się w paragrafach poświęconych poszczególnym językom programowania. Jednak ogólny zarys postępowania z nimi możemy poznać już teraz. Zauważmy przede wszystkim, że dwie instrukcje dotyczące odczytywania danych z wyświetlacza, w których linia R/W powinna mieć wartość 1, nie są dostępne dla naszego sposobu podłączenia wyświetlacza. Ale oczywiście nic nie stoi na przeszkodzie, aby po podłączeniu linii R/W móc je obsługiwać. Dla przykładu zajmijmy się instrukcją Ustaw CGRAM. Oto warunki jej wydania:

♦ Obsługa wyświetlacza alfanumerycznego LCD Lekcja 9

227

Zestawienie instrukcji sterownika HD44780 Tabela 9.1. Instrukcja Czyść wyświetlacz

Kod RSR/WDB7DB6DB5DB4DB3DB2DB1DB0 0000000001

Wróć kursor000000001–

Wprowadzanie 00000001I/DS danych

Włącz funkcje wyświetlacza

0000001DCB

Przesuń okno lub kursor

000001S/CR/L––

Ustawienia wyświetlacza

0

Ustaw CGRAM

0001AAALLL

Ustaw DDRAM

001AAAAAAA

Odczytaj flagę zajętości i pamięć Zapisz dane do CGRAM lub DDRAM

01BFAAAAAAA

1

0

Dane do zapisu

Czytaj dane z CGRAM lub DDRAM

1

1

Odczytane dane

0

0

0

1

DL

N

F





Opis

Maksymalny czas wykonania 1,64 ms

Czyszczenie zawartości pamięci wyświetlacza, kursor wraca do pozycji początkowej 1,64 ms Przywrócenie kursora do pozycji początkowej oraz wyświetlania danych od adresu 0 DDRAM I/D — adres po zapisie 40 µs danych (0: dekrementacja, 1: inkrementacja), S — przesuń po zapisie (0: kursor, 1: okno) D — ustaw wyświetlacz 40 µs (1: włączony), C — ustaw kursor (1: włączony), B — ustaw miganie kursora (1: włączone) 40 µs Przesuń S/C = 0: kursor, S/C = 1: okno, o krok w R/L = 0: lewo, R/L = 1: prawo Konfiguruj ustawienia 40 µs wyświetlacza: DL — liczba linii danych (0: transfer 4. liniami, 1: transfer 8. liniami), N — liczba linii wyświetlacza (0: jedna linia, 1: dwie linie), F — rozmiar matrycy znakowej (0: 5×8 punktów, 1: 5×10 punktów) 40 µs Ustaw jako aktywną pamięć CGRAM, ustaw jej nowy trzybitowy adres (AAA) i numer linii wstawiania (LLL) 40 µs Ustaw jako aktywną pamięć DDRAM, ustaw jej nowy siedmiobitowy adres (AAAAAAA) Odczytaj flagę zajętości 0 µs (BF) i siedmiobitowy adres danych (AAAAAAA) Zapisanie danych do pamięci 40 µs CGRAM (jeśli wcześniej była komenda Ustaw CGRAM) lub DDRAM Odczytanie danych 40 µs z pamięci CGRAM (jeśli wcześniej była komenda Ustaw CGRAM) lub DDRAM

a) wyzerowanie linii RS i DB7; w naszym sposobie podłączenia wyświetlacza LCD linia R/W jest także wyzerowana, tak jak wymaga tego opis instrukcji; b) ustawienie linii DB6;

228

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

c) linie DB5..3 powinny zawierać trzybitowy adres komórki pamięci, w której chcemy wstawić znak; d) linie DB2..0 powinny zawierać trzybitowy adres linii, od której chcemy zacząć wyświetlanie nowego znaku. Za pomocą tej instrukcji możemy zdefiniować własny znak i umieścić go w jednym z pierwszych ośmiu bajtów pamięci CGRAM sterownika HD44780. Pozostałe adresy pamięci zawierają kody znaków, które w wersji standardowej przedstawia rysunek 9.3. Rysunek 9.3. Tablica standardowych znaków sterownika HD44780 (rysunek pochodzi z dokumentacji sterownika)

Wszystkich znaków wymiaru 5×8 może być, jak łatwo obliczyć, 213, czyli 8192. Oczywiście umieszczenie ich w pamięci CGRAM byłoby bezcelowe (nawet jeśli dysponuje się odpowiednim zasobem pamięci). Stąd możliwość zdefiniowania znaków własnych. Rozumiemy już znaczenie instrukcji Ustaw CGRAM. Rozumiemy też znaczenie punktów a), b) i c) jej opisu. Powróćmy na chwilę do zagadnienia trzybitowego adresu opisanego w podpunkcie d). Załóżmy, że definiujemy znak pokazany na rysunku 9.4 w części a). Rysunek 9.4. Nowo zdefiniowany znak wyświetlany a) od linii zerowej, b) od linii trzeciej

♦ Obsługa wyświetlacza alfanumerycznego LCD Lekcja 9

229

W celu zapisania znaku pod adresem 0 należy wywołać instrukcję Ustaw CGRAM następującą konfiguracją linii: RS = 0, DB7 = 0, DB6 = 1, DB5..3 = 000, DB2..0 = 000. Zdefiniowany znak zobaczymy na wyświetlaczu w całości, nakazaliśmy bowiem jego wyświetlanie od linii zerowej (to trudny fragment lekcji, w którym używamy pojęcia linia w dwóch znaczeniach: jako połączenie mikrokontrolera do wyświetlacza i jako wiersz wizualizowanego znaku — postarajmy się w tym nie zgubić!). Możemy też nakazać wyświetlanie znaku, począwszy od trzeciej linii danych. Wtedy instrukcja Ustaw CGRAM powinna być wywołana z następującą konfiguracją bitów: RS = 0, DB7 = 0, DB6 = 1, DB5..3 = 000, DB2..0 = 011. Jedną z możliwych wizualizacji tak zdefiniowanego znaku przedstawiono w części b) na rysunku 9.4. Pierwsze trzy linie będą zawierały przypadkowe dane z pamięci wyświetlacza lub trzy ostatnie linie wcześniej zdefiniowanego znaku. My będziemy definiować znaki od linii ęc 0, wi taką wartość będą zawierały bity DB2..0. Po tych wstępnych wyjaśnieniach przejdźmy do ambitnych zadań, które najpełniej pozwolą zrozumieć tajemnice obsługi alfanumerycznych wyświetlaczy LCD. Zwyczajowo pierwszy program powinien wyświetlić napis „Witaj, świecie”. Będziemy kultywować tę tradycję, a jednocześnie nieco ją zmodyfikujemy — pójdziemy w stronę kulinariów i wyświetlimy napis „Witaj, kotlecie”. W zadaniu 2. po wyświetlaczu będzie się poruszał napis „Bęc” zakończony uśmiechem. Zarówno litera „ę”, jak i uśmiech będą wymagały od nas ich zdefiniowania.

9.1. Asembler Uwagi z dzienniczków uczniowskich: Tymoteusz ściąga haracz z młodszych kolegów, mówiąc, że to na cele dobroczynne. Przywiązał koleżankę do krzesła i żądał okupu. Dłubie w zębach cyrklem, a to, co wydłubał, układa na ławce. Zebrała Maria Janukowicz3

3

Maria Janukowicz, Dzienniczki uczniowskie, „Edukacja i Dialog”, czerwiec 2008, s. 22 – 26. A oto inne uwagi cytowane przez autorkę artykułu, którego lekturę gorąco polecam: Nie chce pisać klasówki, bo twierdzi, że to dla niej za duży stres. Podał nie swoje imię, motywując, że chciałby się tak nazywać. Wysłany po kredę przyniósł ślimaki. Wysłany w celu namoczenia gąbki wrócił z mokrą głową i suchą gąbką. Spóźnił się na pierwszą lekcję, tłumacząc, że na śniadanie było spaghetti i musiał wciągnąć kluskę do końca. Przedstawiam powyższe uwagi w kontekście niekoniecznie zgodnym z intencją pani Janukowicz, osobi ście bowiem uwielbiam u uczniów (studentów) tego typu oryginalne zaskakujące zachowania, które zawsze świadczą — o czym się czasem zapomina — o dużej pomysłowości i inteligencji ich twórców. Oczywiście, o ile przyjmiemy, że będziemy się nawzajem szanować. Uwagi w dzienniczkach bardzo wiele mówią też o nauczycielach. Raczej zgadzamy się, że dłubanie w zębach wielkiej inteligencji nie wymaga (Dłubie w zębach cyrklem, a to, co wydłubał, układa na ławce). Ale — zastanówmy się — jakim trzeba być pedagogiem, by tego typu uwagę zanotować?

230

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

Zagadnienia: O odmierzaniu czasu. Nieszczęsne opadające zbocze EN oraz opowiastka konspiracyjna z czasów II wojny światowej. Wysyłanie danych do wyświetlacza LCD. Inicjalizacja sterownika LCD. Definiowanie własnych znaków. Organizacja pamięci sterownika HD44780. Zamieszczone powyżej uwagi z dzienniczków uczniowskich prócz tego, że są zabawne, odnoszą się niejako do niniejszego paragrafu lekcji. Proponuję bowiem, abyśmy w pierwszej kolejności zajęli się drążeniem tematu — dłubaniem w temacie, który nie leży w głównym nurcie zagadnień obsługi wyświetlacza LCD, ale jest dla tej czynności niezwykle ważny. Chodzi o budowanie podprogramów opóźniających, bardzo precyzyjnie odmierzających czas. Dlatego osoby, którym zależy na jak najszybszym zapoznaniu się z obsługą wyświetlacza LCD, namawiam do pominięcia kilku stron. Zawsze można przecież do przedstawionych tu zagadnie ń wrócić. A tymczasem zapraszam do przeżycia nowej wspaniałej przygody. Dotychczas do odmierzania odcinków czasu używaliśmy podprogramów opóźniających, które wykonywały to zadanie mniej lub bardziej dokładnie. Zdarzało nam się także używać timera. Śpieszę donieść, szanowny Czytelniku, że szybko spotkasz się z zadaniami programistycznymi, w których niezbędne będzie odmierzanie czasu z dokładnością do jednego cyklu zegara4. Na szczęście istnieją łatwe algorytmy implementacji takich podprogramów. Poznamy je dzięki przykładom. Przykład 1. W kodzie programu chcemy umieścić kod realizujący opóźnienie o 200 µs. Mikrokontroler jest taktowany zegarem 1 MHz. To zadanie oczywiście rozwiążemy za pomocą pętli. Należy załadować wartość do jednego z rejestrów, powiedzmy R16, a następnie dekrementować ją aż do otrzymania liczby 0. Pętla, bez wartości stałych, będzie wyglądać tak: ldi R16, wielkość_do_R16 petla: dec R16 brne petla

Jaką wartość powinna zawierać stała wielkość_do_R16 ? Pomyślmy: instrukcja decwykonuje się w jednym cyklu zegara, skok warunkowybrnetrwa dwa cykle we wszystkich tych przypadkach, gdy warunek jest spełniony. W sumie mamy 3 cykle zegara na jednokrotne wykonanie pętli. Jeżeli chcemy, aby opóźnienie wynosiło 200 µs, co dla mikrokontrolera taktowanego zegarem 1 MHz daje 200 cykli zegara, pętlę musimy wykonać 200/3 = 66 razy. Ponieważ 66×3 = 198, dwa pozostałe cykle zapełnimy instrukcją pustą nop. ldi R16, 66 petla: dec R16 brne petla nop nop

Zauważmy, że nasz kod, począwszy od instrukcji ldi R16, 66 , aż po ostatni nop, trwa dokładnie 200 cykli zegara. Bardzo prosty algorytm obliczania wartości ładowanych do rejestru R16 i liczby dodatkowych rozkazów nopprezentuje się następująco: wielkość do R16 = liczba_cykli / 3, liczba dodatkowych rozkazów nop = liczba_cykli % 3.

Omówmy zaprezentowany wzór. Symbol liczba_cykli reprezentuje liczbę cykli wymaganego opóźnienia (w naszym właśnie zrealizowanym programie liczba_cykli = 200). Znak / oznacza dzielenie całkowite. 4

Nie zapominajmy przy tym, że na dokładność odmierzanego czasu decydujący wpływ ma stabilność urządzenia taktującego mikrokontroler.

♦ Obsługa wyświetlacza alfanumerycznego LCD Lekcja 9

231

Oznacza to, że jeśli w wyniku dzielenia otrzymaliśmy liczby z wielkością ułamkową, należy wziąć wyłącznie część całkowitą liczby (na przykład 200 podzielone przez 3 daje liczbę 66,66666, z czego do wyniku bierzemy % 3 = 2). A co zrobić, jeśli chcemy uzyskać opóźnienie 66). Znak % oznacza resztę z dzielenia (na przykład 200 200 µs na mikrokontrolerze taktowanym zegarem 2 MHz? Oczywiście należy wtedy liczbę cykli pomnożyć nopobliczymy za przez 2. Pętla powinna wykonać się 400 razy, wartość ładowaną do R16 i liczbę rozkazów pomocą poznanego wzoru. wielkość do R16 = 400 / 3 = 133, liczba dodatkowych rozkazów nop = 400 % 3 = 1.

W rezultacie otrzymujemy następujący kod: ldi R16, 133 petla: dec R16 brne petla nop

Opóźnienia większe niż 770 cykli należy realizować za pomocą rozbudowanej wersji algorytmu, która zostanie pokazana w przykładzie 2. Przykład 2. W kodzie programu chcemy umieścić kod realizujący opóźnienie o 1000 µs. Mikrokontroler jest taktowany zegarem 4 MHz. Opóźnienie o 1000 µs = 1 ms w układzie taktowanym zegarem 4 MHz zrealizujemy za pomocą kodu, którego działanie zajmie 4000 cykli zegara. Schemat kodu wygląda teraz tak: ldi R17, wielkość_do_R17 ldi R16, wielkość_do_R16 petla: dec R16 brne petla dec R17 brne petla

(wielkosc_do_R16 * 3) Ile trwa wykonanie pętli z R16? W pierwszym przebiegu trwa, co łatwo obliczyć, . — 1 (256 Przy każdym następnym przebiegu rejestr R16 liczony jest od wartości 0, czyli wykona się * 3) — 1 = . 767 Ponieważ dekrementacja i skok warunkowy pętli z R17 trwają 3 cykle, w sumie stwierdzamy, że jednokrotne pełne wykonanie pętli z R17 trwa 770 cykli zegara. Dlatego łatwo znajdujemy wzór na obliczenie wielkości ładowanych do rejestrów R16 i R17. wielkość do R17 = (liczba_cykli / 770) + 1, wielkość do R16 = [(liczba_cykli % 770) / 3] — 1, liczba dodatkowych rozkazów nop = (liczba_cykli % 770) % 3.

(liczba_cykli / 770) Przyjrzyjmy się pierwszemu wyrażeniu. Człon jest zrozumiały, ale co tu robi suma+1 ( )? Otóż jej obecność wynika z konieczności uwzględnienia dodatkowo pierwszej pętli, w której dekrementowana jest wartość początkowo przypisana do R16. A tę wielkość obliczymy za pomocą wyrażenia drugiego. Tutaj obecność członu -1 wynika z konieczności uwzględnienia dwóch rozkazów ldi. Więc obliczajmy. wielkość do R17 = (4000 / 770) + 1 = 5 + 1 = 6, wielkość do R16 = [(liczba_cykli % 770) / 3] — 1 = (150 / 3) — 1 = 50 — 1 = 49, liczba dodatkowych rozkazów nop = (liczba_cykli % 770) % 3 = 150 % 3 = 0.

Ostatecznie kod wykonujący się w 4000 cyklach zegara wygląda tak: ldi R17, 6 ldi R16, 49 petla: dec R16 brne petla dec R17 brne petla

Maksymalna wartość dla tej postaci algorytmu to około 256×770 = 197 120 cykli (197 ms przy taktowaniu 1 MHz). Bardziej rozbudowany algorytm został przedstawiony w przykładzie 3.

232

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

Przykład 3. W kodzie programu chcemy umieścić kod realizujący opóźnienie o 1 s = 1000 ms = 1 000 000 µs. Mikrokontroler jest taktowany zegarem 1 MHz. W celu realizacji zadania należy utworzyć kod, którego wykonanie zajmie 1 000 000 cykli zegara. Posłużymy się następującym schematem kodu: ldi R18, wielkość_do_R18 ldi R17, wielkość_do_R17 ldi R16, wielkość_do_R16 petla: dec R16 brne petla dec R17 brne petla dec R18 brne petla

Zanim obliczymy jakąkolwiek wielkość, musimy od liczby początkowej odjąć wartość 3, uwzględniającą wykonanie trzech instrukcji ldi. liczba_cykli = liczba_cykli — 3

Tym razem pełne jednokrotne wykonanie pętli z R18 zajmie 256×770 = 197 120 cykli zegara. Po dodaniu 2 cykli uwzględniających niespełnienie warunków w rozkazach brneotrzymujemy wielkość 197 122. Stąd znajdujemy następujące wzory 5: Liczba_cykli = liczba_cykli — 3 wielkość do R18 = (liczba_cykli / 197122) + 1, wielkość do R17 = [(liczba_cykli % 197122) / 770] + 1, wielkość do R16 = [(liczba_cykli % 197122) % 770] / 3 — 1, liczba dodatkowych rozkazów nop = [(liczba_cykli % 197122) % 770] % 3.

Wypróbujmy ten fenomenalny algorytm na liczbie 1 000 000. Liczba_cykli = 1000000 — 3 = 999997 wielkość do R18 = (999997 / 197122) + 1 = 5 + 1 = 6, wielkość do R17 = [(999997 % 197122) / 770] + 1 = 18 + 1 = 19, wielkość do R16 = [(999997 % 197122) % 770] / 3 — 1 = 175 — 1 = 174, liczba dodatkowych rozkazów nop = [(999997 % 197122) % 770] % 3 = 2.

Czyli opóźnienie o 1 000 000 cykli realizować może kod: ldi R18, 6 ldi R17, 19 ldi R16, 174 petla: dec R16 brne petla dec R17 brne petla dec R18 brne petla nop nop

Przedstawione algorytmy są stosowane, i to z powodzeniem, w kompilatorze mikroPascal for AVR. Niestety ich użycie zakłada podawanie liczb stałych jako argumentów. Problem pojawia się w chwili, gdy chcemy mieć procedurę opóźniającą, której argumentem będzie zmienna. Aby utrudnić sobie życie, możemy dodatkowo zażądać, aby procedura uwzględniała także taktowanie zegara mikrokontrolera. Problem jest rzeczywiście niełatwy, ale nie beznadziejny. Kiedy buduję ze studentami roboty o sterowaniu rozproszonym, synchronizacja

5

Żartobliwi matematycy lubią pisać: „Dowód twierdzenia jest trywialny, więc się go nie podaje”, gdy tymczasem dowód twierdzenia zajmuje kilka stron maszynopisu i wcale do trywialnych nie należy. Łatwo jest napisać liczbę 197 122, a następnie zdanie: „Stąd znajdujemy następujące wzory”. Nie podaję pełnego ciągu obliczeń prowadzących do przedstawionych wzorów, nie o tym bowiem jest ta książka. A kto będzie chciał szukać, ten niewątpliwie znajdzie…

♦ Obsługa wyświetlacza alfanumerycznego LCD Lekcja 9

233

pracy kilku mikrokontrolerów na raz wymusza na nas stosowanie procedur odmierzających czas bardzo precyzyjnie. Zbudowałem więc odpowiednie podprogramy, które służą dobrze i dotychczas nas nie zawiodły. Czekaj_ms Ich działanie opiera się na użyciu szesnastobitowego rejestru Y. Spójrzmy na kod podprogramu : ;Czekaj_ms (argument w YL = R28) ;minimalna wartość do YL = 1 ;korzysta z: R0, R1, R16, R28, R29 Czekaj_ms: ;wywołanie podprogramu trwa 4 cykle ldi YH, AVR_zegar ;1 mul YL, YH ;1 movw YH:YL, R1:R0 ;1 ;kalibrowanie zegara trwa 3 cykle Czekaj_ms1: ;------\ sbiw Y, 1 ;2 \ breq Czekaj_ms3 ;1 \ ;sprawdzenie końca trwa 3 cykle \ ldi R16, 0 ;1 \ Czekaj_ms2: ; >1000 cykli inc R16 ;1 / cpi R16, 248 ;1 / brne Czekaj_ms2 ;2 / nop ;1 / nop ;1 / nop ;1 / rjmp Czekaj_ms1 ;2--/ Czekaj_ms3: ldi R16, 0 ;1 Czekaj_ms4: ; inc R16 ;1 cpi R16, 246 ;1 brne Czekaj_ms4 ;2 ret ;4 ;powrót z podprogramu trwa 4 cykle

W pierwszych trzech instrukcjach następuje skalibrowanie podprogramu do prędkości taktowania zegara. Wartość otrzymana w rejestrze YL zostaje pomnożona przez stałą prędkości zegara mikrokontrolera (nazwaną AVR_zegar ). Mnożenie realizowane jest za pomocą rozkazu mul. Zapis: mul YL, YH

oznacza pomnożenie wartości rejestru YL przez wartość rejestru YH. Ponieważ w mikrokontrolerach AVR wynik mnożenia przechowuje para rejestrów R1:R0, stamtąd jest ładowany do rejestru Y. ldi YH, AVR_zegar mul YL, YH movw YH:YL, R1:R0

Zauważmy, że do ładowania rejestru zawartością innego rejestru używamy rozkazumov. Aby przekopiować wartość 16-bitowego rejestru do drugiego o tym samym rozmiarze, używamy rozkazu movw. Następnie widzimy pętlę, której jednokrotne wykonanie zajmuje 1000 cykli zegara. Pętla wykona się o 1 raz mniej, niż wynosi wartość zapamiętana w rejestrze Y. Wynika to z konieczności sprawdzania warunku Czekaj_ms3 kontynuacji pętli na jej początku. Dodatkowe cykle zostaną wykonane w części oznaczonej etykietą . Procedura Czekaj_ms może być wywołana w dowolnym miejscu programu. Należy przy tym zaznaczyć, że podprogram stanowi całość z jego wywołaniem. Oznacza to, że liczbę cykli, jaką zajmie wykonanie podprogramu, należy liczyć od instrukcji przypisującej wartość dla procedury. Popatrzmy na przykład, w którym stała AVR_zegar wynosi 1: ldi YL, 100 ;od tego miejsca zaczyna się wykonanie 100 000 cykli zegara rcall Czekaj_ms ;wywołanie podprogramu ;po powrocie z podprogramu licznik cykli zegara ;zwiększy się o liczbę 100 000

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

234

Nieco trudniejsza w implementacji jest procedura wykonująca opóźnienie liczone w mikrosekundach. Popatrzmy na gotowy kod: ;Czekaj_us (argument w Y = YH:YL = R29:R28) ;minimalna wartość do Y = 36 ;maksymalna wartość Y = 0xFFFF, czyli 65,535 ms ;korzysta z: R0, R1, R16, R17, R28, R29 Czekaj_us: ;wywołanie podprogramu trwa 5 cykli ;zapamiętaj 2 najmłodsze bity YL (2 cykle) mov R16, YL ;1 andi R16, 0b00000011 ;1 ;podziel przez 4 (4 cykle) ;a) przesuń bity YH o krok w prawo lsr YH ;1 ;b) przesuń bity YL o krok w prawo (najstarszy bit z C) ror YL ;1 ;c) przesuń bity YH o krok w prawo lsr YH ;1 ;d) przesuń bity YL o krok w prawo (najstarszy bit z C) ror YL ;1 ;odejmij liczbę cykli obsługi podprogramu/4 sbiw Y, 3 ;2 ;zapamiętaj liczbę µs movw R1:R0, YH:YL ;1 sbiw Y, 5 ;2 ;załaduj do R17 częstotliwość mikrokontrolera ldi R17, AVR_zegar ;1 Czekaj_us1: nop ;1 nop ;1 nop ;1 Czekaj_us2: sbiw Y, 1 ;2 brne Czekaj_us2 ;2 ;najmłodsze bity YL sbrs R16, 1 ;1/2 rjmp Czekaj_us3 ;2 nop ;1 nop ;1 nop ;1 Czekaj_us3: sbrs R16, 0 ;1/2 rjmp Czekaj_us4 ;2 nop ;1 nop ;1 ;obsługa najmłodszych bitów trwa 6 do 9 cykli Czekaj_us4: ;przywróć zapamiętane dane movw YH:YL, R1:R0 ;1 dec R17 ;1 brne Czekaj_us1 ;2 ret ;4 ;powrót z podprogramu trwa 4 cykle

Główna pętla odmierzająca czas zaczyna się etykietąCzekaj_us2 . Ponieważ jej jednokrotne wywołanie trwa 4 cykle zegara mikrokontrolera, argument dla pętli został podzielony przez liczbę 4. ;podziel przez 4 (4 cykle) ;a) przesuń bity YH o krok w prawo lsr YH ;1

♦ Obsługa wyświetlacza alfanumerycznego LCD Lekcja 9

235

;b) przesuń bity YL o krok w prawo (najstarszy bit z C) ror YL ;1 ;c) przesuń bity YH o krok w prawo lsr YH ;1 ;d) przesuń bity YL o krok w prawo (najstarszy bit z C) ror YL ;1

Jak wiadomo, dzielenie przez potęgę liczby 2 można zrealizować za pomocą operacji przesunięcia bitów. I tak przesunięcie bitów rejestru o dwa kroki w prawo jest równoważne dzieleniu całkowitemu przez 4. Bystre oko szanownego Czytelnika na pewno wypatrzyło, że przesunięcie bitów rejestru w prawo wykonują rozkazy lsri ror. Plik pomocy mikrokontrolerów AVR opisuje rozkaz lsrw następujący sposób: lsr Rd: Rd(n)=Rd(n+1), Rd(7)=0, C=Rd(0)

Z opisu odczytujemy, że bit o numerze n otrzymuje wartość bitu o numer starszego n+1. Bit siódmy zostaje wyzerowany, natomiast wartość bitu zerowego zostaje skopiowana do bitu przeniesienia C. Popatrzmy teraz na opis rozkazu ror: ror Rd: Rd(7)=C, Rd(n)=Rd(n+1), C=Rd(0)

Działanie bardzo przypomina rozkazror, z wyjątkiem bitu siódmego, któremu zostaje przypisana wartość bitu przeniesienia C. Użycie rozkazów lsri rorw parze umożliwia przesuwanie bitów nie tylko rejestrów dwubajtowych, ale i zmiennych wielobajtowych. Odpowiednikiem rozkazówlsri rorna przypadek przesuwania bitów w lewo sąlsli rol. Oto ich działanie opisane w pliku pomocy mikrokontrolerów AVR: lsl Rd: Rd(n+1)=Rd(n), Rd(0)=0, C=Rd(7), rol Rd: Rd(0)=C, Rd(n+1)=Rd(n), C=Rd(7)

Użycie tych rozkazów w parze umożliwia przesuwanie w prawo bitów zmiennych wielobajtowych. Kończymy rozważania nad procedurą Czekaj_µs . Widzimy, że uwzględnienie czasu zgubionych w wyniku dzielenia bitów odbywa się w części oznaczonej komentarzem najmłodsze bity YL. Jeśli chodzi o prędkość taktowania mikrokontrolera, stałaAVR_zegar została załadowana do rejestru R17. W ten sposób część kodu, począwszy od etykietyCzekaj_us1 , jest powtarzana tyle razy, ile wynosi wartość załadowana do rejestru R17. Zaopatrzeni w precyzyjne podprogramy odmierzające czas jesteśmy gotowi zająć się obsługą alfanumerycznych wyświetlaczy LCD. Zaczniemy od deklaracji stałych, konfigurujących podłączenie wyświetlacza do mikrokontrolera (patrz rysunek 9.1). .equ .equ .equ .equ .equ .equ .equ .equ

DDR_LCD PORT_LCD RS_LCD EN_LCD DB4_LCD DB5_LCD DB6_LCD DB7_LCD

= = = = = = = =

DDRB PORTB 2 3 4 5 6 7

Jeśli przyjdzie nam ochota na zmianę portu podłączenia wyświetlacza — bądź też zmusi nas do tego smutna konieczność — jedyną modyfikacją programu powinno być wprowadzenie adekwatnych zmian w przedstawionych deklaracjach stałych. Jednak w całym programie będziemy zakładać, że 4 linie wyświetlacza będą podłączone do 4 ostatnich linii portu. Czyli DB4 będzie podłączone do linii czwartej portu, DB5 do linii piątej itd. Dodatkowo przyjmujemy, że wszystkie linie łączące mikrokontroler z wyświetlaczem będą liniami jednego portu. Nadszedł czas, by zbudować procedurę wysyłającą dane do wyświetlacza. Zasada transferu danych jest bardzo prosta: 1. Wysyłany bajt dzielimy na dwie części po 4 bity. 2. W pierwszej kolejności wysyłamy 4 starsze bity, w tym celu odpowiednio ustawiamy linie DB4_LCD, DB5_LCD, DB6_LCD, DB7_LCD i potwierdzamy wysyłanie wa żnych danych opadającym zboczem EN_LCD.

236

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

3. Wysyłamy 4 młodsze bity w sposób opisany w punkcie 2. 4. Czekamy co najmniej 40 µs. Zwróćmy uwagę na sposób informowania sterownika wyświetlacza o transferze ważnych danych: opadającym zboczem EN. O co chodzi? Otóż komunikacja mikrokontrolera z jakimkolwiek urządzeniem może odbywać się w sposób synchroniczny i asynchroniczny. W pierwszym z wymienionych sposobów między urządzeniami przebiega linia taktująca, a transfer danych odbywa się zgodnie z generowanym przez nią impulsem. Natomiast w transferze asynchronicznym musi istnieć sposób poinformowania sterownika wyświetlacza LCD, że ta oto konkretna konfiguracja stanów logicznych na liniach łączących urządzenia jest częścią transferu danych i trzeba ją odebrać. Tym sposobem jest sygnalizacja opadającym zboczem EN. Aby lepiej uzmysłowić sobie przedstawiony mechanizm, spójrzmy na opowiastkę konspiracyjną z czasów II wojny światowej (patrz rysunek 9.5). Rysunek 9.5. Opowiastka konspiracyjna z czasów II wojny światowej ilustrująca zasadę potwierdzania danych opadającym zboczem na linii

Zanalizujmy przykład. Widzimy na nim kanał komunikacyjny, w którym rolę źródła informacji pełnią wywieszone przed oknem skarpetki. Pewne układy kolorów skarpetek mają znaczenie i mogą zostać odczytane przez ciało kompetentne. Ale nie wszystkie konfiguracje kolorów są znaczące. Przecież Hilda od czasu do czasu pierze swoje skarpetki o kolorach nieistotnych, trzeba także zmylić czujność wroga. Informacja o znaczącym komunikacie jest potwierdzana opadającą donicą. Właśnie w tym momencie układ kolorów skarpetek jest ważny. Drugi rysunek ilustrujący omawianą zasadę jest już bardziej techniczny. Widzimy na nim 4 linie danych oraz linię EN, która opadającym zboczem potwierdza przesłanie ważnych danych (patrz rysunek 9.6). Rysunek 9.6. Ilustracja zasady potwierdzania transmisji danych opadającym zboczem EN

♦ Obsługa wyświetlacza alfanumerycznego LCD Lekcja 9

237

W chwili zmiany stanu EN z logicznej 1 na logiczne 0 obserwujemy następujący stan linii: DB4_LCD = 1, DB5_LCD = 0, DB6_LCD = 0, DB7_LCD = 1. Programowa realizacja przedstawionej zasady będzie polegała na: 1. ustawieniu linii EN: sbi PORT_LCD, EN_LCD

2. wysłaniu danych liniami DB4_LCD, DB5_LCD, DB6_LCD i DB7_LCD: ;wysyłanie 4 najstarszych bitów danych mov R17, R16 ;przekopiuj do R17 dane andi R17, 0b11110000;pozostaw 4 najstarsze bity in R18, PORT_LCD ;pobierz stan portu z LCD andi R18, 0b00001111;wyczyść 4 najstarsze bity or R18, R17 ;ustaw bity zgodnie z układem danych out PORT_LCD, R18;wyślij 4 najstarsze bity do wyświetlacza LCD

3. potwierdzeniu wysłania ważnych danych opadającym zboczem EN: cbi PORT_LCD, EN_LCD

Zwróćmy uwagę na sposób wysyłania danych w punkcie 2. Zakładamy, że bajt do wysłania został umieszczony w rejestrze R16. Bajt jest dzielony na dwie 4-bitowe części poprzez umieszczenie w rejestrze R17. Jednak nie jest wykonywane bezpośrednie ładowanie zawartości R17 do PORT_LCD. Wartość jest sumowana z zawartością PORT_LCD, a to nie tylko przez wzgląd na linie EN_LCD i RS_LCD. Po prostu do dwóch pozostałych linii PORT_LCD możemy mieć podłączone urządzenia, i nie chcielibyśmy zakłócać wysyłanego do nich imor. pulsu. Sumowanie bitowe, jak zdążyliśmy się zapewne zorientować, w asemblerze AVR realizuje rozkaz Popatrzmy na gotową procedurę: ;Wyslij_do_LCD (argument w R16) ;procedura wysyła do LCD bajt z R16 ;w procedurze zakłada się, że linie DB4, DB5, DB6 i DB7 ;podłączono do 4 najstarszych linii portu PORT_LCD Wyslij_do_LCD: sbi PORT_LCD, EN_LCD ;ustawienie EN ;wysyłanie 4 najstarszych bitów danych mov R17, R16 ;przekopiuj do R17 dane andi R17, 0b11110000 ;pozostaw 4 najstarsze bity in R18, PORT_LCD;pobierz stan portu z LCD andi R18, 0b00001111 ;wyczyść 4 najstarsze bity or R18, R17 ;ustaw bity zgodnie z układem danych out PORT_LCD, R18;wyślij 4 najstarsze bity do wyświetlacza LCD nop ;zaczekaj 1 takt cbi PORT_LCD, EN_LCD ;potwierdzenie wysłania danych (opadającym zboczem) nop ;zaczekaj 1 takt sbi PORT_LCD, EN_LCD ;ustawienie EN ;wysyłanie 4 najmłodszych bitów danych swap R16 ;zamień 4 najmłodsze bity z najstarszymi andi R16, 0b11110000 ;pozostaw 4 najstarsze bity in R18, PORT_LCD;pobierz stan portu z LCD andi R18, 0b00001111 ;wyczyść 4 najstarsze bity or R18, R16 ;ustaw bity zgodnie z układem danych out PORT_LCD, R18;wyślij 4 najmłodsze bity do wyświetlacza LCD nop ;zaczekaj 1 takt cbi PORT_LCD, EN_LCD ;potwierdzenie wysłania danych (opadającym zboczem)

238

Część ♦ I Programowanie mikrokontrolerów z rodziny AV

;zaczekaj co najmniej 37 µs ldi YH, 0 ldi YL, 40 rcall Czekaj_us ret

Wywołanie procedury Czekaj_µs z argumentem 40 wynika z konieczności uwzględnienia czasu trwania operacji odbioru danych przez sterownik LCD, która minimalnie zajmuje okres 37 µs. Tego typu wartości najczęściej zaokrągla się do najbliższej wielokrotności liczby 10, w takiej też postaci zostały pokazane w tabeli 9.1. Czas na programową inicjalizację wyświetlacza. Dokumentacja sterownika HD44780 podaje algorytmy postępowania w przypadku inicjalizacji z interfejsem 8- i 4-bitowym (patrz rysunek 9.7). Rysunek 9.7. Algorytm inicjalizacji sterownika HD44780 w przypadku interfejsu 4-bitowego (rysunek pochodzi z dokumentacji sterownika)

Przedstawiony algorytm z mniejszymi lub większymi modyfikacjami funkcjonuje w literaturze. My zastosujemy algorytm z dwiema modyfikacjami. Po pierwsze czas przeznaczony na ustabilizowanie się poziomu 6 napięcia wydłużymy do 45 mikrosekund. Po drugie dwukrotne wysłanie sekwencji 0011 dwukrotnie zakończymy oczekiwaniem 100 mikrosekund. Moje doświadczenia pokazują, że jednokrotne wywołanie procedury oczekującej nie zawsze poprawnie inicjalizuje wyświetlacz. Użyjemy zatem następującego algorytmu: 1. Ustaw linie RS_LCD, EN_LCD, DB4_LCD, DB5_LCD, DB6_LCD, DB7_LCD w kierunku wyjściowym, wyzeruj linie. 2. Zaczekaj co najmniej 45 ms na ustabilizowanie napięcia. 3. Wyślij sekwencję 0011. 6

Mam nadzieję, że szanowny Czytelnik zauważył i docenił, że prócz programowania mikrokontrolerów nieobca jest mi również matematyka, i poprawnie liczę przynajmniej do dwóch (po pierwsze…, po drugie…).

♦ Obsługa wyświetlacza alfanumerycznego LCD Lekcja 9

239

4. Zaczekaj co najmniej 4,1 ms. 5. Powtórnie wyślij sekwencję 0011. 6. Zaczekaj co najmniej 100 mikrosekund. 7. Po raz trzeci wyślij sekwencję 0011. 8. Zaczekaj co najmniej 100 mikrosekund. 9. Ustaw interfejs 4-bitowy, czyli wyślij sekwencję 0010. 10. Ustaw parametry wyświetlacza. 11. Ustaw tryb pracy wyświetlacza. 12. Włącz wyświetlacz. 13. Wyczyść pamięć wyświetlacza. Wysyłanie sekwencji 0011 będzie miało bardzo prostą postać, w której początkowo nie będziemy korzystać z podprogramu Wyslij_do_LCD . sbi PORT_LCD, EN_LCD ;załaduj sekwencję 0011 in R16, PORT_LCD ori R16, (1