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) /