PROGRAMOWANIE SYSTEMÓW OSADZONYCH
Marek Klimowicz
Robot reagujący na światło – „zrób
to sam” (specjalnie dla wykop.pl)
Wśród elektroników amatorów panuje przekonanie, że budowa własnego robota
wymaga ogromnej wiedzy i doświadczenia oraz że jest to niewykonalne bez zaplecza
technicznego. Ten artykuł ma na celu przedstawienie, że nawet bez dużej wiedzy i doświadczenia, stosując się do kilku reguł, można stworzyć dobrą i prostą konstrukcję.
A
tmel AVR jest rodziną mikrokontrolerów stworzonych przez firmę Atmel. W jej skład wchodzą jednostki zarówno ośmio, jak i 32-bitowe
oparte na rdzeniu i zestawie instrukcji AVR. Szeroka gama wyposażenia oraz serie dedykowane do specyficznych zastosowań, jak zarządzanie
ogniwami litowo-jonowymi oraz układy uniwersalne, pozwalają dobrać odpowiedni model mikrokontrolera do wymagań danego projektu.
Zarys architektury Atmel AVR
Architektura rdzenia AVR oparta jest na architekturze harwardzkiej. Pamięć wykonywanego programu oraz danych są rozdzielone. W nowoczesnych układach pamięć programu realizowana jest przez pamięć FLASH. Jest
to bardzo wygodne rozwiązanie, gdyż pozwala wielokrotnie (nawet do 100
tys. razy na komórkę pamięci) nadpisać zawartość, na przykład w przypadku
błędu programistycznego lub chęci zmiany programu na inny. Kod programu
może być w warunkach ciągłego braku zasilania przechowywany do 100 lat
bez zaniku zawartości.
Mikrokontrolerem, który zostanie użyty do wykonania projektu, jest ATmega328P w obudowie DIP. Na nim oprę opis cech układów Atmel AVR oraz
rozwiązań i kodu.
Jest niewielki (28 wyprowadzeń), a przy tym bardzo dobrze wyposażony.
Posiada 3 sprzętowe timery/liczniki, obsługę przerwań i komunikacji w różnych standardach cyfrowych.
Układ posiada 32kB pamięci programu. Może wydawać się, że jest to
bardzo niewiele, jednakże architektura AVR oraz zestaw instrukcji typu RISC
zapewniają wysoką gęstość kodu oraz szybkość wykonania. Maksymalna
częstotliwość taktowania 20MHz pozwala osiągnąć do 20MIPS (milionów instrukcji na sekundę).
Warto zwrócić uwagę na sposób obsługi liczb zmiennoprzecinkowych.
Rdzeń AVR nie został wyposażony w jednostkę zmiennoprzecinkową. Wszystkie operacje zmiennoprzecinkowe są emulowane programowo, a funkcji operujących na tych liczbach dostarcza kompilator.
Układ posiada 2kB statycznej pamięci RAM, która nie wymaga odświeżania w celu podtrzymania zawartości, jednakże z racji tego, że jest to pamięć
ulotna, po odłączeniu zasilania oraz resecie zawartość jest kasowana. Chip
posiada też niewielki obszar pamięci nieulotnej ogólnego przeznaczenia
typu EEPROM o pojemności 1kB.
Układ jest wyposażony w 32 ośmiobitowe rejestry ogólnego przeznaczenia. Wszystkie rejestry, zarówno ogólne, jak i specyficzne dla funkcji sprzętowych są zmapowane na początku przestrzeni adresowej RAM, do której
możemy się odwoływać w programie.
Napięcie, jakim należy zasilić mikrokontroler, jest zależne m.in. od modelu
układu, wymagań projektowych i częstotliwości taktowania. Zgodnie z wykresem częstotliwości zegara od napięcia zasilania (nota katalogowa: wykres
29-1), dla zegara 20MHz bezpiecznym zakresem jest 4.5-5.5V. Dla napięć poniżej bezpiecznego progu producent nie gwarantuje stabilnej pracy układu.
Atmel oferuje układy mogące pracować już od 0.7V, jednakże należy spełnić
kilka warunków specyficznych dla danego modelu.
86
/ 11 . 2013 . (18) /
Układy peryferyjne wewnętrzne
Mianem tym możemy określić wszystkie elementy układu niepołączone
bezpośrednio z rdzeniem AVR, a poprzez szynę danych.
Dla redukcji liczby wyprowadzeń mikrokontrolera stosuje się zwielokrotnianie funkcjonalności na danych portach i pinach. Do każdego portu są wewnętrznie podpięte, oprócz linii wejścia/wyjścia, różne układy funkcji alternatywnych, np. sprzętowa obsługa protokołów komunikacji, tj. TWI czy SPI,
wyjścia zegarowe, wejścia konwertera ADC czy komparatora analogowego.
Dokładny opis wszystkich elementów procesora, przykłady zastosowania
funkcji oraz wiele innych przydatnych rzeczy są dostępne w nocie katalogowej1 do pobrania za darmo ze strony producenta. Dostępna jest jedynie wersja anglojęzyczna.
ROBOT MOBILNY. PROJEKTOWANIE
I BUDOWA
Wielu elektroników amatorów zapewne rozmyślało o budowie własnego robota.Taka konstrukcja wymaga trochę zdolności manualnych, ale przede wszystkim przemyślanego projektu. Roboty mobilne są dosyć wymagające. Parametry
projektowe są od siebie zależne i często w znacznym stopniu na siebie wpływają.
Na początku należy zdecydować, w jakich warunkach robot będzie się
poruszał. Warunki wysokiej wilgotności lub narażenie na kontakt z wodą, na
przykład przejazd przez kałużę, z pewnością zaszkodzą niezabezpieczonemu urządzeniu. Taki sam niepożądany wpływ na różne elementy robota ma
temperatura. Granice zalecanych temperatur określa producent dla każdego
układu. Często pomimo operowania w tych granicach zdarzają się błędy lub
usterki. Niskie temperatury niekorzystnie wpływają na źródło zasilania, które traci swoją pojemność. Następnie należy zadecydować, jakich wymiarów
będzie robot i jakie ma być jego przeznaczenie. Robot do przewozu paczek
będzie musiał być odpowiednio większy i wytrzymalszy konstrukcyjnie od
małej zabawki. Jest to jeden z czynników wpływających na wybór silników
oraz sposobu ich zasilania.
Napęd robotów realizowany jest zwykle przez silniki elektryczne lub
serwa. Do lekkich robotów można zastosować serwa modelarskie lub silniki
małych mocy. Robot cięższy, przewożący ładunki, musi zostać wyposażony w
odpowiednio mocniejsze silniki, które sprostają wygenerowaniu odpowiedniej siły, aby wprawić całość w ruch. Masa całkowita robota również będzie rosnąć. Drugim aspektem w doborze silników jest szybkość obrotowa, od której
zależy szybkość całego robota. Regulacja obrotów jest ciekawym i godnym
większej uwagi tematem. Istnieje wiele strategii, od mechanicznych, elektromechanicznych i czysto elektronicznych. Często stosuje się przekładnie do
redukcji wysokich obrotów silnika. Poza zmniejszeniem szybkości rośnie siła,
jaką generuje silnik na wyjściu. Wpływ na szybkość ruchu robota, poza silnikiem i przekładnią redukcyjną, w przypadku robota kołowego mają właśnie
koła, przyczepność i ich średnica.
1
http://tinyurl.com/ds-328p
ROBOT REAGUJĄCY NA ŚWIATŁO – „ZRÓB TO SAM”
Zasilanie jest niezbędne do uruchomienia robota. Jest to niezmiernie ważna kwestia, gdyż wpływa zarówno na masę robota, jak i na wydajność silników.
Nawet przy prostych i lekkich konstrukcjach mało wydajne ogniwa przełożą
się na spadek efektywności całego robota. Źródło zasilania należy dobierać do
parametrów silników, nie odwrotnie. Zawsze uwzględnia się pewien margines
bezpieczeństwa, aby uniknąć niespodzianek w postaci spadków napięcia z
powodu zbyt dużego oporu wewnętrznego lub niewystarczającej wydajności
prądowej w stresie. Zazwyczaj stosowane są akumulatory żelowe, polimerowe
lub ładowalne pakiety bateryjne złożone z wysokiej klasy ogniw.
Ostatnim z aspektów mechanicznych jest konstrukcja nośna robota spinająca pozostałe części w jednego robota. Od decyzji podjętych w poprzednich etapach projektowania będą zależeć użyte materiały, finalne rozmiary
oraz masa, a także różne zdolności terenowe. Należy tu także uwzględnić
miejsce na układy elektroniczne.
Posiadając już podstawowe założenia projektu, przeznaczenie robota,
warunki pracy i napęd, trzeba jeszcze stworzyć system sterujący. Można go
podzielić na 4 części:
• układ sterowania silnikami
• zasilanie części logicznej
• system czujników
• centralny element sterujący – mikrokontroler
Do sterowania silnikami, kierunkiem ich obrotów oraz szybkością są
stosowane układy mostka H, np. zintegrowany L293, lub skonstruowane samodzielnie z elementów podstawowych i układów scalonych przy silnikach
wysokich mocy lub nietypowych zastosowaniach. Tak samo jak zasilanie silników, parametry pracy sterownika należy dobrać pod konkretne silniki.
Zasilanie części logicznej robota, a więc mikrokontrolera oraz czujników
jest bardzo ważne. Stabilne zasilanie jest jednym z gwarantów stabilnej pracy
całego robota. Jest tu spora dowolność w metodyce regulacji, czy to poprzez
przetwornicę impulsową czy stabilizator liniowy. Ogromnie ważną kwestią
jest filtrowanie zasilania. Nie należy tu przesadnie oszczędzać na kondensatorach czy innych filtrach. Silniki podczas pracy, a szczególnie w momentach startu i zatrzymania, pobierają duże ilości prądu, co powoduje spadek
napięcia, oraz generują zakłócenia mogące zdestabilizować układ, jeżeli nie
są należycie odfiltrowane. Jak każde zagrożenie, również zakłócenia należy
zwalczać u źródła. Łatwiej jest zniwelować zakłócenia generowane przez silnik tuż przy jego wyjściach, niż blisko sterownika, gdy kable połączeniowe
wpływają na wielkość zakłócenia oraz rozsiewają je na inne blisko położone
elementy i układy.
W kwestii systemu czujników panuje całkowita dowolność wyboru sposobu, w jaki robot będzie odbierał otoczenie. Można zastosować wszystko. Od
najprostszych mikroprzełączników reagujących przy kontakcie z obiektem,
poprzez czujniki odległości na podczerwień do zaawansowanych czujników
ultradźwiękowych i nacisku. Zwykle ograniczeniem jest budżet i dostępność
specyficznych typów czujników. Jednak nie zawsze koszt czujnika musi stanowić znaczącą pozycję w budżecie projektu. Dobrym przykładem są rezystory
wrażliwe na światło czy temperaturę. Prosty, ale przede wszystkim bardzo
tani układ dzielnika napięciowego złożonego z takiego rezystora oraz rezystora stałego może zostać użyty na wiele różnych sposobów, zależnych jedynie od wyobraźni konstruktora.
Sterowanie robota zazwyczaj oparte jest o mikrokontrolery. Szeroki wachlarz oferowanych przez producentów układów pozwala łatwo dobrać konkretny produkt spełniający założenia i wymagania projektowe. Wielokrotna
programowalność pamięci FLASH daje możliwość poprawienia programu
sterującego bez konieczności zmiany fizycznego układu.
Inną metodą na wykonanie sterowania jest zastosowanie mniej złożonych układów scalonych, tj. bramek logicznych, wzmacniaczy operacyjnych, komparatorów analogowych czy nawet tranzystorów. Takie podejście wymaga dużo więcej cierpliwości, ostrożności, a przede wszystkim
wiedzy i obycia z budową układów opartych na tych elementach. Drastycznie zwiększa to stopień skomplikowania projektu, a także możliwość
popełnienia błędu.
Przykładowy robot mobilny – konstrukcja
mechanicza
Głównym założeniem tego projektu jest prostota i minimalizm. Zarówno
podstawa robota, jak i koła jezdne zostały wykonane ze sklejki. Jest to lekki,
sztywny i prosty w obróbce materiał. Wystarczą podstawowe narzędzia, jak
mała piła do drewna, pilnik i wiertarka. Przydatnym może się okazać także nóż
do tapet z wymiennymi ostrzami.
Rysunek 1. Projekt platformy robota
Ilustracja jest jedynie propozycją, która przedstawia wymiary i kształt
robota wykonanego na potrzeby artykułu. Użyty do budowy materiał także
może być inny, na przykład tworzywo sztuczne. Jednakże drewno tudzież
sklejka jest wygodniejsza w obróbce oraz bardziej dostępna jako surowy materiał do obróbki.
Przy wykonywaniu własnego egzemplarza należy uwzględnić wielkość
posiadanych silników, płytki stykowej dla elektroniki oraz koszyków bateryjnych czy innego wybrango źródła zasilania. Przedstawiony dalej układ sterowania silnikami nie jest przystosowany do silników wysokiej mocy. Maksymalny prąd ciągły pracy wynosi około 1A, lecz przy takim obciążeniu układ
wydziela dużo ciepła i nie jest zalecana praca blisko warunków granicznych.
Podstawa
Jako że zasilacz został umiejscowiony blisko tylnej krawędzi robota, w odpowiadającym miejscu powinny być zamocowane koszyki bateryjne. Jeden
koszyk ośmiobateryjny znacznie ułatwia montaż, jednakże i dwa poczwórne
nie stanowią problemu. Sposobów przytwierdzenia elementów do podstawy
może być wiele, od taśm dwustronnych i kleju, poprzez opaski zaciskowe, po
połączenia śrubowe. Aby nie komplikować zbędnie projektu dodatkowymi
otworami montażowymi, wykorzystano piankową taśmę dwustronnie klejącą oraz opaski zaciskowe do mocowania silników.
Koszyki bateryjne zostały przytwierdzone do podstawy właśnie za pomocą taśmy. Może wydawać się, że jest to niedorzeczne rozwiązanie, jednak jest
wystarczająco wytrzymałe i spełnia swoje zadanie. Grubość taśmy niweluje
niedoskonałości obu łączonych powierzchni.
Niezmiernie istotna jest tu jakość i siła łączenia kleju. Częśc taśm nie będzie w stanie sprostać utrzymaniu obciążonych bateriami koszyków i wibracjom i zwyczajnie części rozkleją się.
W ten sam sposób mocowana jest płyta stykowa z układem sterowania.
Ważnym elementem podstawy jest przedni ślizgacz. Dzięki temu elementowi robot może bez przeszkód poruszać się po prawie płaskich powierzchniach,
a także przez niektóre, niewielkie przeszkody, jak progi drzwiowe czy krawędzie
dywanów. W przykładowej konstrukcji została użyta tekturowa tuba z metalowym wieczkiem, również przytwierdzona poprzez dwustronną taśmę.
Silniki jako elementy wymagające pewnego mocowania zostały przyczepione za pomocą opasek zaciskowych. Ważne jest równe wiercenie otworów
mocujących, większe róznice w położeniu będą widoczne w równoległości
obrotów kół robota.
Koła
Koło jest dość niefortunną figurą geometryczną do wycięcia, szczególnie
z twardszego materiału, jakim jest sklejka. Należy się spodziewać, że koło nie
/ www.programistamag.pl /
87
PROGRAMOWANIE SYSTEMÓW OSADZONYCH
jest prawdziwie okrągłe, a jedynie „koliste”. Dobrym sposobem zarówno na
ułatwienie wykonania, jak i zwiększenie pola kontaktu koła z podłożem, a
przez to przyczepności jest podział obwodu na kilkanaście prostoliniowych
odcinków. Dzięki temu możliwe jest wykorzystanie piły do wykonania prostego cięcia. Metoda ta jednak wymaga większego wkładu w zaznaczanie
segmentów na obwodzie koła. Jest to jedynie drobna niedogodność w porównaniu ze zmęczeniem dłoni od wycinania prawdziwych, niesegmentowanych kół.
Niezależnie od obranego sposobu wykonania koła oraz materiału dobrym
pomysłem jest naklejenie paska gumy na obwodzie koła w charakterze bieżnika. Robot może przez to zwolnić, jednakże zmniejszy się także generowany
przez drewniane koła hałas, a także poślizg podczas ruchu.
Mocowanie kół do osi silników można przeprowadzić na kilka sposobów,
w zależności od tego, jaki kształt ma wałek użytego w projekcie silnika. Wałki
D-kształtne pozwalają na łatwy i bezpieczny montaż za pomocą klina i wycięcia w kole. Silniki z odzysku często posiadają osadzone na wałkach koła zębate, metalowe lub plastikowe, dające stabilne oparcie i pole do zamocowania
koła. Najbardziej problematyczne są silniki z cienkim okrągłym wałkiem. Nie
zapewnia to wystarczającej powierzchni do osadzenia metodą „na wcisk”. Dla
niezawodności połączenia może być konieczne użycie bardzo silnie wiążącego kleju i/lub dodatkowych środków mocujących.
Przykładowy robot mobilny – elektronika
sterująca
Sekcja zasilania
Pierwszym elementem układu elektrycznego jest zasilacz części logicznej.
Układ wykonawczy sterowania silnikami
Schemat 2. Sterownik silników
Wykorzystany układ L293 lub odpowiednik funkcjonalny SN754410 znajduje zastosowanie głównie w rozwiązaniach małej mocy. Mostki wyjściowe
są złożone z tranzystorów bipolarnych. Naturalny spadek napięcia na półprzewodniku powoduje grzanie układu, co prowadzi do ograniczenia maksymalnej sprawności. Kolejnym z ograniczeń jest maksymalna częstotliwość
przełączania wynosząca 5kHz. Położenie w zakresie słyszalnym dla człowieka
powoduje w pewnych warunkach piszczenie silników. Dla bezpieczeństwa i
bezproblemowej pracy stosuje się niższe częstotliwości.
Układ pozwala na kontrolę prędkości obrotowej dwóch silników w obu
kierunkach lub czterech w jednym kierunku. Interfejs dla mikrokontrolera jest
bardzo prosty. Składa się z wejścia włączającego (1-2 i 3-4 EN) przyjmującego
sygnał PWM oraz linii kierunku obrotów (piny xA). Rozdzielenie linii kierunku
pozwala na fizyczne odłączenie wyjść silnika, pozwalając na swobodne obroty poprzez wystawienie stanu niskiego na obu pinach.
Schemat 1. Układ zasilania sekcji logicznej
Proponowany na schemacie zasilacz oparty jest na dobrze znanym i bardzo popularnym stabilizatorze liniowym 7805 (nazwa jest zależna od producenta). Zasadniczą wadą tego typu układów stabilizacyjnych jest moc tracona, która jest iloczynem różnicy napięcia wejściowego i wyjściowego oraz
pobieranego przez układ prądu. W tym projekcie nie stanowi to problemu,
gdyż część logiczna nie pobiera znaczącego prądu, a stabilizator będzie tylko
lekko ciepły.
Rysunek 3. Gotowy układ sterowania silnikami (fot. D. Sadowski / sadowskifoto.pl)
Czujniki natężenia światła
Schemat 3. Czujnik natężenia światła
Rysunek 2. Układ zasilania na płytce uniwersalnej (fot. D. Sadowski / sadowskifoto.pl)
88
/ 11 . 2013 . (18) /
Elementem reagującym na natężenie światła jest rezystor światłoczuły
(LDR). Jego opór zmniejsza się logarytmicznie wraz ze wzrostem natężenia
światła nań padającego. Przy całkowitym zaciemnieniu rezystancja dochodzi
do 2Mohm, natomiast przy dużej intensywności światła spada nawet do 2k.
Czujnik jest zbudowany na podstawowym układzie dwóch rezystorów,
jakim jest dzielnik napięciowy. LDR jest wpięty między wejście analogowe a
masę sygnałową układu (GND). Jako że rezystancja spada wraz ze wzrostem
natężenia światła, w tym ustawieniu napięcie na wyjściu sensora będzie spadać. Sensor ten jest wrażliwy na zmiany warunków oświetlenia otoczenia.
Program korzystający z wartości zmierzonych w porze dużego nasłonecznienia może dawać niepoprawne wyniki przy pracy w warunkach słabego oświetlenia lub na odwrót.
ROBOT REAGUJĄCY NA ŚWIATŁO – „ZRÓB TO SAM”
Efekt końcowy
Wykonanie proponowanego robota wymagało włożenia nieco wysiłku, trochę umiejętności manualnych i wiedzy o elektronice, dając zaskakująco dobry
efekt widoczny poniżej. Otrzymana platforma mobilna, pomimo nieco prowizorycznego podejścia, jest solidna, prosta w wykonaniu i nie wymaga zaawansowanych narzędzi. W rezultacie powstał podstawowy układ przeznaczony do
rozwoju i rozbudowy głównie od strony elektronicznej oraz programowej.
Rysunek 4. Czujniki natężenia światła, lewa i prawa strona (fot. D. Sadowski /
sadowskifoto.pl)
Główny układ sterujący
Rysunek 6. Efekt końcowy trudu włożonego w budowę. (fot. D. Sadowski /
sadowskifoto.pl)
PROGRAMOWANIE AVR
Kod programu dla mikrokontrolerów AVR możemy stworzyć, wykorzystując
kilka różnych języków/środowisk programistycznych. Oprócz podstawowego
języka asemblera AVR, program można stworzyć w popularnym ze względu na
prostotę środowisku BASCOM (BASIC dla AVR), językach C czy C++. Możliwe
jest także wykorzystanie innych języków używanych na komputerach klasy PC,
lecz są to często projekty eksperymentalne, niestabilne i niewydajne. Najwięcej
swobody w bezpośrednim dostępie do warstwy sprzętowej, elastyczności oraz
najlepszą wydajność poza językiem asemblera daje C.
Do uruchomienia skompilowanego programu na mikrokontrolerze potrzebny jest jeszcze element pośredniczący w komunikacji między komputerem a układem docelowym, który załaduje ów program do pamięci FLASH.
Schemat 4. Główny kontroler
Układ spinający pozostałe części w jedną funkcjonalną całość. Serce i mózg
robota. Schemat przedstawia standardowe podłączenie procesora ATmega328P z zewnętrznym zegarem kwarcowym, przyciskiem resetu oraz minimalnym filtrowaniem zasilania. Linia PC6 służąca za sygnał resetowania mikrokontrolera jest podłączona poprzez rezystor podciągający (pull-up) ustalający
napięcie na tym pinie. Jest to wymagane, aby procesor pracował bezpiecznie i
poprawnie przy większym wachlarzu zakłóceń i wahań napięcia zasilania.
Generator kwarcowy, zgodnie z zaleceniami producenta, jest podłączony
poprez kondensatory 18-27pF do GND. Jest to kolejny element zapewniający
bezproblemową pracę, gdyż bez tych kondensatorów układ może nie generować impulsu zegarowego, przez co mikrokontroler będzie w stanie zawieszenia.
Wgrywanie kodu do pamięci mikrokontrolera
– programatory
Zazwyczaj układy oferowane przez sklepy elektroniczne są fabrycznie
czyste, dostępna jest cała pamięć programu, a bity ustawień są w stanach
domyślnych. Aby przesłać skompilowany program do mikrokontrolera, potrzebne jest urządzenie pośredniczące, zwane programatorem, lub program
ładujący oraz konwerter komunikacji, np USB do TTL.
Popularność platformy AVR sprawiła, że powstało wiele konstrukcji i projektów
programatorów. Zwykle ich podział jest dokonywany ze względu na interfejs komunikacyjny z komputerem, np USB, szeregowy, równoległy. Podłączenie do mikrokontrolera przy programowaniu poprzez ISP w układzie jest zawsze takie samo.
Programy ładujące (ang. bootloader) rezydują w oddzielnej sekcji w pamięci programu, zmniejszając tym dostępny obszar dla kodu głównego. Ich
zaletą jest prostota użycia, gdyż nie jest wymagany programator, a jedynie
konweter sygnałów do komunikacji. Zazwyczaj używany jest port UART mikrokontrolera jako medium transmisji kodu programu.
Środowisko programistyczne
Rysunek 5. Zmontowany układ ATmega328P (fot. D. Sadowski / sadowskifoto.pl)
Najpopularniejszym kompilatorem języka C/C++ dla platformy AVR jest
GNU GCC. Zestaw GCC, binutils oraz avrdude jest dostępny dla większości systemów operacyjnych i platform sprzętowych.
Dla systemu Windows sprawdzonym i często polecanym pakietem jest
WinAVR zawierający wszystkie niezbędne narzędzia do rozpoczęcia przygody
z mikrokontrolerami Atmel. Jest to jedynie zbiór aplikacji do użytku konsolowego. Środowisko graficzne należy wybrać i skonfigurować do współpracy
z kompilatorem samemu. Niektóre posiadają szablony i wtyczki ułatwiające
integrację, np Code::Blocks czy Eclipse.
/ www.programistamag.pl /
89
PROGRAMOWANIE SYSTEMÓW OSADZONYCH
Alternatywą dla WinAVR jest dostarczane przez producenta, bazowane na
Visual Studio, Atmel Studio. Zaletą tego środowiska jest wbudowany symulator umożliwiający debugowanie kodu w pełni kontrolowanych warunkach
bez udziału fizycznego układu.
Symulator posiada jednak pewne ograniczenia, np. nie ma możliwości odbioru danych z zewnątrz dla żadnego protokołu komunikacji.
Mając na przykład podpiętą zewnętrzną pamięć, używając któregoś z realizowanych sprzętowo protokołów komunikacji, możemy zasymulować wysłanie
danych. Zostaną one stracone. Odczyt z pamięci, mimo że poprawny, nie jest
możliwy. Dołączony do Atmel Studio symulator nie oferuje możliwości symulacji innych układów poza mikrokontrolerami stworzonymi przez producenta.
Dla systemów opartych na Linuksie najlepszym i często jedynym wyborem jest GCC. Sposób instalacji i nazwy poszczególnych pakietów są zależne
od używanej dystrybucji, jednakże nie różnią się znacząco. Należy więc zainstalować: gcc-avr, binutils-avr, avr-libc, avrdude, make.
Dodatkowe, wymagane pakiety powinny zostać zainstalowane automatycznie. Zaś sposób tworzenia kodu pozostaje w gestii użytkownika.
Na uwagę zasługuje tutaj biblioteka standardowa języka C. Jest znacznie
okrojona, lecz dostarcza bardzo pomocnych i niemal niezbędnych makr i definicji
używanych w kodzie programu. Wszystkie nagłówki specyficzne dla AVR znajdują
się w podkatalogu avr. Plik avr/io.h dostarcza w znormalizowany sposób definicji
i makr specyficznych dla wybranego mikrokontrolera poprzez opcję kompilatora.
Struktura programu dla AVR
Zasadniczą różnicą w budowie programów na AVR jest konstrukcja funkcji
main. Program musi się wykonywać do czasu odłączenia zasilania/resetu. Main
nie może wrócić, gdyż nie ma gdzie. W przypadku wyjścia poza main zachowanie mikrokontrolera zależy od kompilatora i kodu epilogu. W jednym przypadku wykonanie zakończy się bezpiecznie, zawiesi się na pętli nieskończonej.
Drugą możliwością jest wykonanie niewłaściwego kodu, co jest zachowaniem
niezdefiniowanym. Zwykle w takich sytuacjach nastąpi reset procesora, dlatego należy świadomie zabezpieczyć koniec main pętlą nieskończoną i/lub ująć
cały kod programu w taką pętlę, by uniknąć niejednoznacznych sytuacji i błędów. Nie możemy także przekazać argumentów funkcji głównej.
Listing 1. Szkielet programu
//Dołączenie nagłówków
int main(void) {
//Inicjalizacja wstępna
while(1) {
//Kod programu
}
}
KOMUNIKACJA Z OTOCZENIEM.
OPERACJE WEJŚCIA/WYJŚCIA
Mikrokontroler komunikuje się ze światem zewnętrznym za pomocą portów wejścia/wyjścia. Porty oraz przynależące do nich wyprowadzenia (piny)
są od siebie niezależne. Możemy więc dowolnie skonfigurować kierunek pracy oraz nadać stan każdemu pinowi z osobna. Porty są dwukierunkowe, lecz
piny nie mogą być jednocześnie wejściem i wyjściem.
Piny przyjmują stany logiczne (binarne) 1 lub 0, wysoki lub niski, VCC (napięcie zasilania) lub 0V (GND). Nie oznacza to jednak, że są to dokładnie takie wartości. Dla kierunku wyjściowego, wartości napięć są niemal równe VCC i GND. W
przypadku wejścia mikrokontroler interpretuje jako dany stan zakres napięcia.
Wartości graniczne dla poszczególnych zakresów różnią się w zależności od
napięcia zasilania. Dokładne dane przedstawia tabela 29-1 noty katalogowej.
Domyślnie po uruchomieniu czy resecie mikrokontrolera porty są skonfigurowane jako ogólnego przeznaczenia, wejściowe oraz występuje na pinach stan niski.
Przy manipulacji pinami i portami używamy identycznej składni jak przy
operacjach na zwykłych zmiennych.
90
/ 11 . 2013 . (18) /
Cyfrowy zapis i odczyt
Porty realizujące czysto cyfrowe funkcje zostały nazwane B i D. Port C oprócz
operacji cyfrowych może być wykorzystywany do pomiarów analogowych. Piny
numerowane są od 0 do N-1, gdzie N to szerokość portu w bitach. Porty B i D
są ośmiobitowe, natomiast C jest siedmiobitowy, przy czym PC6 jest linią RESET.
W przykładach zostały użyte schematyczne nazwy rejestrów. Literą x została oznaczona litera portu: B, C lub D.
Każdy port ma przypisane rejestry realizujące odczyt, zapis oraz zmianę
kierunku pinów.
Nie zawsze wystarcza domyślny, wejściowy kierunek portu. Jego zmiana
wykonywana jest poprzez zapis 1 do rejestru DDRx na pozycji odpowiadającej danemu pinowi. Można w tym celu posłużyć się zarówno numerem pinu
w porcie, jak i makrem nazwy.
Listing 2. Ustalenie kierunku pracy pinu
//Równoważne zapisy
DDRx |= (1 << Px5);
DDRx |= (1 << 5);
Za zapis i odczyt odpowiadają rejestry PORTx oraz PINx. Rejestr wejściowy
jest tylko do odczytu, a niezdefiniowane bity, czyli dla nieobecnych pinów,
mają zawsze wartość 0.
Wartość, jaką wystawia port, można modyfikować poprzez operacje bitowe, jak i zwykłe przypisanie stałej lub zmiennej.
Listing 3. Modyfikacja stanu wyjściowego portu
//Zmiana wartości wyjściowej portu
PORTx = 0xFF;
PORTx ^= value;
Stan portu możemy odczytać do zmiennej i wykorzystać w programie, np.
do obliczeń.
Listing 4. Odczyt wartości wejściowej portu
[unsigned] char val = PINx;
Warto zauważyć, że jeśli piny portu pracują zarówno jako wejścia i wyjścia,
to rejestr PINx odzwierciedla całkowity stan na porcie. Piny wyjściowe w stanie
wysokim są także widoczne w rejestrze wejściowym. Aby otrzymać odczyt tylko z
pinów wejściowych, należy wyzerować bity odpowiadające pinom wyjściowym.
Odczyt analogowy
Odczyt analogowy jest bardzo przydatny i szeroko stosowany, np. przy obsłudze różnego rodzaju czujników. Funkcja ta jest realizowana poprzez konwerter analogowo-cyfrowy dostępny jako funkcja alternatywna portu C. Piny tego
portu PC0 do PC5 są podłączone do pojedynczego układu ADC poprzez multiplekser. W praktyce oznacza to przetwarzanie wejścia z jednego pinu naraz.
Rozdzielczość ADC dochodzi do 10 bitów, czyli 1024 wartości pośrednich
pomiędzy napięciem 0V a napięciem odniesienia. Wpływ na błąd pomiaru mają
stabilność napięcia zasilania, odniesienia, napięcia wejściowego, zakłócenia zewnętrzne i wewnętrzne mikrokontrolera, a także zegar układu konwertera.
Maksymalną precyzję, czyli, w najlepszym wypadku, 10 bitów, możemy
uzyskać dla częstotliwości taktowania ADC 50-200kHz. Taktowanie zegara
głównego wynosi tyle, ile wartość rezonatora zewnętrznego lub wewnętrznego RC. W tym przypadku jest to rezonator kwarcowy 20MHz. Do ustalenia
odpowiedniej częstotliwości zegara ADC służy wbudowany w układ siedmiobitowy dzielnik. Wartość podziału ustalają bity 0-2 rejestru ADCSRA – rejestru
kontroli i statusu konwertera ADC. Zgodnie z tabelą 24-4 w nocie katalogowej
ATmega328P, dzielnikiem, który zapewni pracę w optymalnej częstotliwości
zegara, a zarazem maksymalnym, jest wartość 128.
ROBOT REAGUJĄCY NA ŚWIATŁO – „ZRÓB TO SAM”
Konwerter analogowy posiada możliwość programowego wyboru napięcia odniesienia. Zastosujemy opcję napięcia odniesienia VREF równego AVCC.
Za ten wybór odpowiada rejestr ADMUX oraz ustawienie REFS0 (bit nr 6).
Należy pamiętać o stosowaniu kondensatorów filtrujących na pinach
AVCC i AREF. Zapewnią one stabilną i bezproblemową pracę.
Poza dzielnikiem zegara, rejestr ADCSRA kontroluje uruchomienie układu
(ADEN, bit 7), start konwersji (ADSC, bit 6) oraz przerwania ADC.
Zalecane jest także, dla poprawy precyzji oraz zmniejszenia niepotrzebnych
strat wewnętrznych, wyłączenie bufora cyfrowego dla pinów pełniących funkcje
analogowe. Odpowiedzialnym za to zadaniem jest rejestr DIDR0. Zapis jedynki
na danej pozycji wyłączy funkcje cyfrowe odpowiadającego pinu portu C.
Wynik jest rozdzielony na dwa rejestry ADCL i ADCH, domyślnie 8 i 2 bity.
Istnieje możliwość przesunięcia wyniku w lewo poprzez ustawienie bitu
ADLAR (bit nr 5) w rejestrze ADMUX.
Dzięki temu rejestr ADCH będzie przechowywał 8 bitów wynikowych, a
ADCL pozostałe 2 bity. Jest to szybki sposób na usunięcie dolnych bitów, które
zwykle zawierają zakłócenia i błędy konwersji oraz redukcję wartości wynikowej do 8 bitów, np. do bezpośredniej pracy z mniejszymi timerami.
Listing 5. Inicjalizacja układu ADC
void adc_init(void) {
//Ustawienie napięcia odniesienia dla konwertera analogowego (AVCC)
ADMUX = (1 << REFS0);
//Przesunięcie wyniku do wyższego bajtu
ADMUX |= (1 << ADLAR);
//Ustawienie dzielnika zegara na 128
ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
//Wyłączenie cyfrowej części pinów
// ...
//Uruchomienie układu ADC
ADCSRA |= (1 << ADEN);
}
Aby wykonać odczyt analogowo-cyfrowy, najpierw należy wybrać kanał,
z którego zostanie dokonana konwersja, a następnie ustawić bit startu ADSC.
Konwersja napięcia na wartość liczbową nie jest natychmiastowa. Po ustawieniu flagi ADSC (bitu 6) w rejestrze ADCSRA układ ADC rozpoczyna konwersję
z wybranego kanału. Bit startu konwersji może być użyty do oczekiwania na
rezultat w pętli, gdyż jest odczytywany jako 1 podczas konwersji. Po zakończeniu pracy flaga jest sprzętowo ustawiana na 0.
Listing 6. Blokująca funkcja odczytu analogowego
unsigned char adc_read(unsigned char channel) {
//Wyczyszczenie poprzednich bitów wyboru kanału
ADMUX &= 0xF0;
//Ustawienie kanału konwersji
ADMUX |= channel;
//Uruchomienie konwersji z danego kanału
ADCSRA |= (1 << ADSC);
//Oczekiwanie na konwersję
while (ADCSRA & (1 << ADSC));
//Zwrócenie wyższego bajtu wyniku
return ADCH;
}
Zapis analogowy falą prostokątną – PWM
Mikrokontrolery Atmel z rodzin ATtiny, ATmega i kilku innych nie posiadają wbudowanego układu pozwalającego uzyskać czysto analogowy sygnał, np. sinusoidalny. Zamiast tego stosowana jest technika modulacji szerokości impulsu (PWM).
Polega ona na szybkim naprzemiennym wystawianiu stanu wysokiego i niskiego
na wyjściu. Uzyskany sygnał ma przebieg zbliżony do prostokątnego. Więc dlaczego
możemy tu mówić o ‘zapisie analogowym’? Generowany sygnał może przybierać
tylko dwa stany: wysoki i niski. Jednakże forma, w jakiej ten sygnał zostanie zarejestrowany, zależy od odbiornika. Odbiorniki o wystarczająco wysokiej częstotliwości pracy i czasie reakcji są w stanie odebrać sygnał w postaci niezmienionej. Do tej
grupy zaliczamy m.in. tranzystory. Z drugiej strony są układy o wolniejszym czasie
reakcji. Dobrym przykładem są głośniki. Wejściowa fala prostokątna zostanie zniekształcona, wygładzona, dając odpowiedni dźwięk, a nie serię krótkich impulsów.
ATmega328P posiada 6 pinów oferujących sprzętową realizację PWM
poprzez wyjścia OCxn timerów/liczników. Istnieje możliwość rozszerzenia tej
przydatnej funkcji na inne piny programowo.
Timery/liczniki mogą pracować w wielu różnych trybach. Ośmiobitowe
timery 0 i 2 udostępniają 6, a szesnastobitowy aż 15 trybów pracy. Nie wszystkie jednak są znacząco inne od pozostałych. Część trybów jest wariacją, posiadają wspólne zasady działania, ale różne, np. zależne od rejestru wartości
maksymalnej licznika czy wielkość/dokładność licznika.
Literą x oznaczono numer timera/licznika, zaś n oznacza kanał A, B lub
numer bitu z danej grupy.
Każdy z timerów udostępnia dwa typy PWM: szybki (fast) i phase correct.
Tryb fast PWM liczy jedynie w górę, a zmiany stanu pinów wyjściowych zachodzą przy równości wartości zadanej w rejestrach OCRxn z aktualną wartością licznika timera TCNTx. W rezultacie otrzymujemy sygnał prostokątny
niesymetryczny względem jego okresu.
Tryb phase correct generuje symetryczny sygnał poprzez liczenie w obie
strony. Licznik najpierw zlicza w górę, dochodząc do wartości zadanej, gdzie zachodzi zmiana stanu wyjścia, i kontynuuje liczenie do wartości maksymalnej, lecz
zamiast przekroczenia zakresu (zmiany na 0) licznik liczy w dół do 0. Przy czym dla
równości rejestru OCRxn i licznika timera zachodzi kolejna zmiana. Dzięki temu
sygnał jest bardziej symetryczny względem okresu pracy. Zachowanie timera/
licznika oraz tryb należy dobrać do wymagań konkretnego projektu.
Kontrolę silników prądu stałego można zrealizować zarówno w trybie
szybkim, jak i phase correct. Dostępne są także timery w dwóch rozdzielczościach: ośmiu i szesnastu bitów. Wysokie rozdrobnienie wartości pośrednich
wyjścia nie jest potrzebne, jednostkowe różnice, np. pomiędzy wartością OC­
R1n 30000 i 30001, będą niezauważalne w pracy silnika.
Konfiguracja timera/licznika sprowadza się do ustawienia bitów trybu pracy (WGMxn) w rejestrach TCCRxn, podłączenie i sposób działania wyjść OCxn
oraz dzielnik częstotliwości pracy licznika. Domyślnie wszystkie wyjśia mikroprocesora ustawione są w kierunku wejściowym, Aby kanały OCxn realizowały swoje funkcje, należy ustawić kierunek pracy tych pinów na wyjściowy.
Za sposób działania wyjść odpowiadają bity COMxn. Są dostępne dwie główne opcje działające w identyczny sposób dla obu kanałów wyjściowych. Dla trybu
fast PWM, pin wyjściowy zostanie ustawiony w stan wysoki na początku zliczania
(dla wartości 0 licznika) i pozostanie w tym stanie, aż do osiągnięcia wartości zadanej rejestrem OCRxn. Sposób ten został nazwany nieodwracającym (non-inverting). Działanie odwracające jest funkcjonalnie przeciwne do nieodwracającego.
Także tryb phase correct został wyposażony w kilka sposobów generowania sygnału wyjściowego. Non-inverting polega na ustawieniu danego kanału w stan wysoki przy zrównaniu wartości zadanej i licznika przy zliczaniu w
górę, natomiast ustawienia stanu niskiego przy tym samym zdarzeniu przy
zliczaniu w dół. Tryb odwracający jest komplementarny do przedstawionego.
Pin OCxA może być potraktowany w sposób specjalny i przy ustawieniu
bitu WGMx2 stan tego wyjścia zmieni się na przeciwny przy zrównaniu licznika
i wartości zadanej. Funkcjonalność ta nie jest dostępna dla kanału B.
Dzielnik ustawiany jest za pomocą bitów CSx0-2. Wszystkie kombinacje oraz ich wpływ na dzielnik i zachowanie timera opisuje tabela 15-9 noty
katalogowej. W przypadku, gdy wszystkie bity wyboru dzielnika są równe 0,
timer/licznik nie pracuje.
W przykładzie dzielnik zegara dla timera/licznika 0 wynosi 64.
Listing 7. Przykładowa konfiguracja timera 0, fast PWM, non-inverting
void timer0_init(void) {
//Wybranie trybu (fast PWM)
TCCR0A |= (1 << WGM01) | (1 << WGM00);
//Działanie kanałów wyjściowych (non-inverting)
TCCR0A |= (1 << COM0A1) | (1 << COM0B1);
//Ustawienie dzielnika zegara, uruchomienie timera
TCCR0B |= (1 << CS01) | (1 << CS00);
//Przestawienie kanałów w tryb wyjściowy
DDRD |= (1 << PD5) | (1 << PD6);
}
Przedstawiony kod jest wystarczającym, żeby uruchomić timer/licznik 0
przystosowany do kontroli silników.
/ www.programistamag.pl /
91
PROGRAMOWANIE SYSTEMÓW OSADZONYCH
STRATEGIE ZACHOWAŃ ROBOTA
CZUŁEGO NA ŚWIATŁO
Do kompletności projektu brakuje jeszcze jednej cegiełki: algorytmu zachowania spinającego przedstawione wcześniej elementy w jedne sprzętowo-programowe rozwiązanie.
Pomimo bardzo prostej budowy czujnika, jest on niezwykle uniwersalny.
Położenie, kierunek i separacja LDR pozwala na wykrywanie przeszkód, linii,
ruch zależny od kierunku światła oraz wiele innych. Jedną z najmniej skomplikowanych strategii sterowania jest ruch względem światła. Zostaną przedstawione dwa sposoby reakcji na światło.
Przygotowanie kodu
Przed przystąpieniem do właściwego programowania zachowań, należy
jeszcze przygotować kod inicjalizujący elementy, które będą wykorzystane.
Jedynym wymaganym nagłówkiem dla całego kodu jest pochodzący z biblioteki standardowej <avr/io.h>.
Listing 8. Inicjalizacja układów i pinów
//Ustawienie pinów kontroli kierunku silników w stan wyjściowy
oraz kierunek “do przodu”
//Silnik lewy
DDRB |= (1 << PB1) | (1 << PB2);
PORTB |= (1 << PB1);
//Silnik prawy
DDRB |= (1 << PB3) | (1 << PB4);
PORTB |= (1 << PB3);
//Inicjalizacja ADC i timera/licznika 0
adc_init();
timer0_init();
Robot „Światłolub”
Pierwszym z nich jest podążanie za światłem. Metoda bardzo prosta, wymagająca poza kodem uruchamiającym elementy sprzętowe, tj. ADC i timer/
licznik 0, bardzo niewiele. Najpierw trzeba zastanowić się, jak będą reagować
poszczególne silniki zależnie od natężnia światła na danym czujniku oraz
przypomnieć sposób ich działania.
Aby robot kierował się w stronę źródła światła szybciej, musi obracać się
silnik po przeciwnej stronie do czujnika odczytującego wyższe natężenie
światła. Zgodnie z tym, sensor po drugiej stronie będzie mniej oświetlony, a
odpowiadający mu silnik zwolni.
Zastosowane czujniki działają w sposób odwracający, im wyższe natężenie padającego światła zostanie zarejestrowane, tym niższa wartość zwrócona przez odczyt analogowo-cyfrowy. Dzięki temu, przedstawione zachowanie silników możemy osiągnąć na kilka sposobów.
Pierwsza i druga możliwość wiążą się z modyfikacją układu na płytce stykowej. Pierwszą opcją jest zamiana prawego i lewego czujnika na porcie C.
Dzięki temu program czytając z kanału 0, czyli prawego czujnika, przeprowadzi odczyt z lewego. Analogicznie dla kanału 1.
Drugą zmianą, jaką można poczynić, jest zamiana wyjść OC0A i OC0B. Da
to taki sam efekt jak zamiana połączeń ADC. Należy wykonać tylko jedną taką
modyfikację, dwie jednocześnie wzajemnie się zniosą.
Marek Klimowicz
Trzecim sposobem na uzyskanie krzyżowego sterowania jest rozwiązanie
programowe. Tu także można wykonać zamiany identyczne jak dla sprzętu,
czyli zmienić numer kanału ADC dla danego czujnika lub zamianę kanałów
PWM. W przeciwieństwie do rozwiązań w układzie sprzętowym, edycja kodu
wymaga rekompilacji i ponownego wgrania do pamięci mikrokontrolera, co
może być czasem niewygodne.
Listing 9. Pętla główna podstawowego programu sterującego
unsinged char sensor_left, sensor_right;
while (1) {
//Odczyt z prawego i lewego sensora
sensor_right = adc_read(0);
sensor_left = adc_read(1);
//Ustawienie wypełnienia timera 0 (szybkości silników,
prawego i lewego)
OCR0A = 255 - sensor_left;
OCR0B = 255 - sensor_right;
}
Powyższy fragment kodu prezentuje główną logikę sterującą prostego robota “światłoluba”. Zakres wartości zwracanych przez funkcję odczytu
analogowego jest równy wielkości rejestru licznika, więc można jej użyć bezpośrednio w obliczeniach. Odejmowanie, które pojawia się przy ustawianiu
wartości wypełnienia, jest skutkiem sposobu, w jaki działają czujniki. Bez
odjęcia wartości odczytanej od maksymalnej robot hamowałby przy wzroście natężenia światła, a nie podążał za nim. Sterowanie krzyżowe uzyskano
poprzez użycie odczytu z lewego sensora przy ustawianiu wypełnienia PWM
dla prawego silnika i vice versa.
Robot uciekający od światła, czyli „syndrom dnia
poprzedniego” w wersji elektromechanicznej
Zapewne wielu czytelników wie, czym objawia się ten syndrom. Jednak
w przeciwieństwie do robota układ elektroniczny tylko zasymuluje jeden z
efektów – światłowstręt.
W przeciwieństwie do “Światłoluba”, przy tym zachowaniu, robot będzie
poruszać się w stronę przeciwną do światła. Należy zatem zmienić kierunek
obrotów silników. Działanie te jest realizowane poprzez wystawienie stanów
odwrotnych do użytych przy podążaniu za światłem na liniach kierunku.
Zmianę tę należy przeprowadzić w kodzie inicjalizującym. Pętla główna może
pozostać taka jak dla “Światłoluba” – wówczas silniki będą sterowane krzyżowo, lub zamienić sterowanie na proste, w zależności od tego, jakie zachowanie chcemy uzyskać.
PODSUMOWANIE
Przedstawiony projekt robota mobilnego daje ogromną swobodę dalszego rozwoju, ze względu na zastosowane proste materiały i uniwersalną bazę
układu elektronicznego. Jest to doskonała podstawa zarówno dla amatorów,
jak i bardziej doświadczonych osób. Zaproponowane rozwiązanie jest proste
w budowie i uruchomieniu oraz niezbyt kosztowne, ze względu na zastosowane ogólnodostępne materiały.
[email protected]
Student Politechniki Białostockiej na kierunku Automatyka i Robotyka. Elektronik amator
z zacięciem do mechaniki. Programuje hobbystycznie od wielu lat w różnych językach. W wolnym czasie tworzy oprogramowanie, układy elektroniczne oraz projekty mobilnych platform
robotycznych.
92
/ 11 . 2013 . (18) /
Download

Robot reagujący na światło – „zrób to sam” (specjalnie dla wykop.pl)