Kurs Arduino – pierwsze kroki
Oto praktyczny kurs Arduino, który wcześniej ukazał się w klasycznym, drukowanym czasopiśmie (EdW). Kurs Arduino pozwala w łatwy sposób rozpocząć przygodę z nowoczesną elektroniką i programowaniem. Ważnym uzupełnieniem kursu są wspomniane dalej artykuły „Wokół Arduino”, które rozszerzają wiedzę oraz pozwalają świadomie i w pełni wykorzystać potencjał Arduino.
Rozpoczęcie przygody z Arduino jest bardzo proste. Potrzebne są trzy rzeczy:
Płytka (Arduino Uno R3) – mniej więcej taka jak na powyższej fotografii wstępnej.
Kabel USB AB – zwykle kupowany wraz z płytką – fotografia 1.
Pakiet oprogramowania – Arduino Software IDE (darmowy).
Wprawdzie możliwe jest zdalne (online) korzystanie z pakietu IDE, jednak proponuję, żebyś ściągnął wersję „stacjonarną” odpowiednią dla swojego komputera i systemu operacyjnego ze strony:
https://www.arduino.cc/en/Main/Software.
Dla Windows 8.1 i 10 możesz pobrać tę aplikację ze sklepu Microsoft Store (rysunek 2),
co ma dość istotne zalety. Zostaniesz zapytany, czy chcesz finansowo wesprzeć projekt (contribute). Jeśli nie, wybierzesz JUST DOWNLOAD. Potem w sklepie jedno kliknięcie spowoduje zainstalowanie pakietu.
Gdy uruchomisz pakiet Arduino IDE, zobaczysz ekran jak na rysunku 3.
Trzeba też dołączyć płytkę Arduino za pomocą kupionego wraz z płytką kabla USB, co umożliwi przesłanie programu do mikroprocesora na płytce. Kabel zasila też płytkę z portu USB komputera. Na płytce trwale powinna świecić się zielona kontrolka LED.
Tu trzeba wyjaśnić, że komputer sam wykryje sprzęt i zainstaluje sterownik USB (przydzielając mu numer portu COM), konieczny do komunikacji komputera PC z płytką Arduino. Ale komputer nie konfiguruje automatycznie pakietu Arduino IDE, który musi wiedzieć, z jaką płytką współpracuje i który port COM został przydzielony do komunikacji. Aby to uzupełnić, trzeba w Arduino IDE otworzyć menu Narzędzia. W pozycji Płytka zapewne pojawi się domyślna płytka „Arduino/Genuino Uno”, ale niżej, w pozycji Port, trzeba wybrać „podpowiadany” port, który przydzielił wcześniej Menedżer urządzeń, jak pokazuje rysunek 4 (tu jest to COM3, ale u Ciebie zapewne będzie inny).
Konfiguracja według rysunku 5 umożliwia dalsze działania.
Pierwszy program
Gdy mamy wszystko przygotowane, moglibyśmy napisać pierwszy program, tak zwany szkic (sketch) dla Arduino, który ma rozszerzenie .ino. Na rysunku 3 masz szkielet programu – szkicu, a jego nazwa wskazuje na datę utworzenia. Każdy szkic Arduino musi zawierać te dwie funkcje: wykonywaną jednorazowo po uruchomieniu setup ( ) oraz gotową pętlę loop ( ).
Zamiast pisać program, łatwiej jest skorzystać z „gotowca”. W pakiecie Arduino IDE masz ich mnóstwo. Zgodnie z arduinową tradycją, odszukaj i otwórz szkic (program) Blink.ino. W tym celu w menu wybierz kolejno Plik – Przykłady – 01.Basic – Blink, jak pokazuje rysunek 6.
Otworzy się nowe okno ze szkicem Blink.ino (miganie). Jest to plik tekstowy, gdzie najpierw masz komentarz – opis, a właściwy program po usunięciu komentarzy wygląda jak na rysunku 7.
W funkcji setup() konfigurowane jest wyjście, współpracujące z diodą LED wbudowaną w płytkę (LED_BUILTIN). A w pętli loop() dioda ta cyklicznie będzie zaświecana stanem wysokim (HIGH), po odczekaniu 1000 milisekund wyłączana (LOW), po kolejnych 1000ms zaświecana i tak w kółko.
Na razie jest to tylko program na ekranie komputera PC. Aby zaprogramować procesor umieszczony na płytce Arduino, musisz wykonać dwa proste kroki.
Aby sprawdzić, czy program jest poprawny, trzeba kliknąć ikonkę „z ptaszkiem”, opisaną uspokajającym określeniem „Zweryfikuj”. W rzeczywistości, o czym świadczy napis po zakończeniu procesu (rysunek 8),
następuje nie tylko weryfikacja, ale i kompilacja szkicu Blink do kodu, użytecznego dla zastosowanego procesora ATmega328. Co istotne, kompilator wypisuje na dole okna informacje, jaką część zasobów (dostępnej pamięci) procesora zajmie ten program. W naszym przypadku jest to tylko 928 bajtów, czyli 2% dostępnej w tym procesorze pamięci programu Flash i tylko 9 bajtów pamięci RAM.
Gotowy program trzeba jeszcze przesłać przez łącze USB do mikroprocesora, co nastąpi po kliknięciu przycisku ze strzałką opisanego „Wgraj” – rysunek 9.
Po zaprogramowaniu procesora żółta dioda na płytce Arduino zacznie rytmicznie migać: świeci przez 1000ms (1 sekundę) i nie świeci przez 1000ms, zgodnie z programem.
W szkicu Blink zmieniaj teraz dowolnie wyrażony w milisekundach czas świecenia i przerwy:
void loop() {
digitalWrite(LED_BUILTIN, HIGH);
// tu wstaw czas świecenia [ms]
delay(XXXX);
digitalWrite(LED_BUILTIN, LOW);
// tu wstaw czas przerwy [ms]
delay(XXXX); }
Po każdej zmianie musisz kliknąć najpierw ikonkę „Zweryfikuj”, a po skompilowaniu ikonkę „Wgraj” według rysunków 8, 9, by wgrać program do procesora. Możesz odłączyć płytkę od komputera i zasilać ją nie z portu USB, tylko z zasilacza 7…12V przez umieszczone na płytce gniazdo.
Właśnie z powodzeniem zacząłeś programować Arduino!
Jak na razie wykorzystaliśmy wbudowaną diodę LED, podłączoną na stałe do „cyfrowej” nóżki 13. Takich „cyfrowych” nóżek mamy na płytce Arduino czternaście – są numerowane od 0 do 13. Mogą one być dwustanowymi wyjściami o wydajności prądowej do 40mA albo dwustanowymi wejściami. Sześć z nich o numerach 3, 5, 6, 9, 10, 11 oznaczonych jest na płytce znakiem tyldy: (~). Mogą one być tak zwanymi analogowymi wyjściami, co nie jest do końca prawdą – ale może tam występować przebieg PWM o wypełnieniu skokowo regulowanym od zera do 255. Te „wyjścia analogowe” można wykorzystywać do skutecznej pseudoanalogowej regulacji jasności diod LED, regulacji prędkości silników i podobnych celów.
Na płytce Arduino mamy też sześć końcówek oznaczonych A0…A5, które też mogą być „zwykłymi”, cyfrowymi wejściami i wyjściami. Ale mogą również być wejściami analogowymi. Wbudowany w procesor 10-bitowy przetwornik analogowo-cyfrowy pozwala mierzyć występujące na nich napięcia, dając jako wynik liczby z zakresu 0…1023.
Te dwadzieścia wejść i wyjść pozwala zrealizować niezliczone mnóstwo interesujących konstrukcji. Aby zapoznać się z dostępnymi możliwościami, trzeba do płytki dołączyć dodatkowe elementy. Można to zrealizować na wiele sposobów.
Sprzęt
Na rynku można kupić pojedyncze płytki oraz większe i mniejsze zestawy płytek Arduino. Pokazane teraz możliwości pozwolą zdecydować, która wersja najbardziej Ci odpowiada.
Czy wykorzystasz elementy dużego zestawu Arduino według fotografii 10 w sposób powiedzmy, klasyczny?
Tego rodzaju duży zestaw Arduino trochę kosztuje, ale za to od razu masz wiele elementów, płytki stykowe, szyld do prototypów oraz liczne stykowe elementy połączeniowe. To dla wielu jest dużą zaletą, bo w skrajnym przypadku nie trzeba niczego lutować.
A może jako elektronik potrafiący lutować chcesz „kombinować najprościej i najtaniej”. Do tego wystarczy tylko płytka Arduino Uno z kablem USB (A-B) i do tego na początek detale, które można znaleźć w szufladzie każdego elektronika. Jeżeli nie masz jakiegoś elementu, wykorzystywanego w przykładach proponowanych w Arduino IDE, na przykład serwa czy silnika krokowego, nie przejmuj się – i bez nich na początek zrealizujemy mnóstwo interesujących eksperymentów.
Zarówno samą płytkę Arduino Uno R3 z kablem USB, jak i duży zestaw możesz kupić w AVT. Ale trzeba też nadmienić, że oprócz tego rodzaju niedrogich dalekowschodnich klonów Arduino, dostępne są też droższe wersje produkcji europejskiej/USA, reklamowane jako oryginalne lub „w pełni zgodne z oryginałem”. Na fotografii tytułowej z lewej strony masz tańszy chiński klon, a z prawej strony – oryginalną płytkę Arduino. Oryginalna płytka Arduino Uno R3 ze strony https://store.arduino.cc/ kosztuje 20 dolarów netto (rysunek 11), a w kraju około 100zł.
Do wyboru – do koloru. Zdecyduj, co chcesz kupić. A teraz kolejny istotny szczegół.
Schematy
W projektach Arduino do rysowania schematów zazwyczaj wykorzystuje się darmowy program Fritzing, który można ściągnąć ze strony http://fritzing.org.
Rysunek 12 pokazuje przykład schematu „obrazkowego”, a rysunek 13 – wersję ideową tego układu (Fritzing ma też możliwość projektowania płytek).
Schematy ideowe z tego programu są ewidentnie brzydkie, żeby nie powiedzieć obrzydliwe, ale dla niektórych atrakcyjne są schematy „obrazkowe”. My jesteśmy elektronikami, w EdW mamy swój własny sposób i styl rysowania schematów. Będziemy go wykorzystywać także w ramach kursu, ale do zbioru elementów bibliotecznych dodamy płytkę Arduino, która zobaczysz na następnym rysunku.
Pierwsze eksperymenty. Najkrótszy program
Szereg interesujących i pouczających ćwiczeń możemy zrealizować, dołączając do płytki Arduino w jakikolwiek sposób kilka popularnych elementów. Nie jest potrzebny zasilacz – całość będzie zasilana napięciem 5V przez kabel z gniazda USB komputera. Trzeba przy tym pamiętać o ograniczonej wydajności prądowej komputerowego łącza USB (zwykle 500mA, ale w najgorszym przypadku 100mA). W dowolny sposób zbuduj układ według rysunku 14.
Moje dwie realizacje pokazane są na fotografii 15.
Ty na początek nie dołączaj do komputera dwóch płytek jednocześnie. A oto…
najkrótszy program – szkic, składający się tak naprawdę z jednej linii:
analogWrite(11, analogRead(0) / 4);
Aby zobaczyć, jak pracuje, wykorzystamy potencjometr P1 i żółtą diodę LED wbudowaną w płytkę. Przyciski nie są tu wykorzystywane. Konieczne jest jednak dodatkowe połączenie zworą pinów 11 i 13 według rysunku 14. Otwórz Arduino IDE, kliknij ikonkę Nowy (rysunek 16) i dopisz powyższą linię.
W związku z obowiązkowymi dwiema funkcjami setup() i loop(), program (pomijając komentarze) musi wyglądać mniej więcej tak:
void setup() {
}
void loop() {
analogWrite(11, analogRead(0) / 4);
}
Ten szkic dostępny jest tutaj. !!!!! LINK DO PLIKU ZIP. Nazwa szkicu zawiera dużą literę A, numer odcinka kursu i numer szkicu w danym odcinku. Powyższy szkic jest oznaczony A0101. Szkic zawiera też komentarze i omówione dalej modyfikacje. Możesz korzystać z tego „gotowca”, ale gorąco zachęcam, żebyś sam pisał proponowane programy. To bardzo ważne, bo dopiero samodzielnie modyfikując programy i własnoręcznie wklepując kod za pomocą klawiatury, przestaniesz być teoretykiem i nauczysz się unikać powszechnych błędów. Do najczęstszych błędów należy brak średników oraz nawiasów.
Jeśli więc podczas weryfikacji kompilator podświetli na różowo jakąś linię i wypisze słabo zrozumiały komunikat, w pierwszej kolejności sprawdź, czy nie brakuje średnika lub nawiasu w tej albo na końcu poprzedniej linii.
Nasz program może też wyglądać tak:
void setup(){}
void loop(){analogWrite(11,analogRead(0)/4);}
Zauważ, że usunąłem prawie wszystkie spacje. Taka wersja króciutkiego programiku może nawet jest bardziej czytelna. Jednak w przypadku bardziej rozbudowanych programów jest odwrotnie i wtedy zdecydowanie nie zachęcam do takich oszczędności i zwięzłości ze względu na przejrzystość i czytelność kodu. Wiedz, że takie usuwanie spacji i innych tzw. białych znaków robione jest zawsze na początku kompilacji, więc jeżeli polepsza to czytelność szkicu, to nie bój się wstawiać w szkicu dowolnej liczby dodatkowych spacji i przejść do nowej linii (enter). W Arduino IDE masz też możliwość zwiększenia czytelności kodu przez automatyczne „standardowe” formatowanie programu według ogólnych zasad. Aby tak sformatować napisany szkic wybierz: Tools – AutoFormat albo naciśnij Ctrl+T.
Nie szczędź też komentarzy, które powinny możliwie jasno opisywać zarówno działanie programu, jak i poszczególnych instrukcji. Jasne i zwięzłe komentarze są naprawdę bardzo ważne zarówno dla innych, którzy zajrzą do Twojego programu, jak i dla Ciebie, gdy za jakiś czas będziesz próbował przypomnieć sobie jego działanie. Jednoliniowy komentarz zaczyna się dwoma znakami ukośnika //, a dowolnie długi komentarz blokowy zawarty jest między parami znaków ukośnika i gwiazdki:
/* komentarz blokowy */
Teraz skompiluj nasz pierwszy program i wgraj do procesora na płytce. Sukces!
Obracanie suwaka potencjometru zmienia jasność żółtej diody LED na płytce!
Program działa następująco: „arduinowa” funkcja analogRead(0) odczytuje wartość napięcia podawanego na wejście analogowe A0. W rzeczywistości jest to dość skomplikowany odczyt właściwie skonfigurowanego przetwornika A/D (ADC) i Arduino robi to bez naszego udziału. Ponieważ odczytana z ADC wartość jest 10-bitowa (0…1023), dzielimy odczytaną liczbę przez 4 i otrzymujemy liczbę 8-bitową z zakresu 0…255, reprezentującą wartość napięcia (0…5V) z suwaka potencjometru.
Ten sam efekt uzyskamy, pisząc:
analogWrite(11, analogRead(0) >> 2);
Zamiast dzielenia przez 4 (całkowitego, z pominięciem reszty), odczytaną liczbę dziesięciobitową przesuwamy tu bitowo w prawo o dwa miejsca ( >> 2 ), czyli gubimy dwa najmniej znaczące bity.
W każdym razie uzyskana ośmiobitowa liczba (0…255) reprezentująca napięcie zostaje przekazana do funkcji analogWrite(11, napiecie), która na nóżce 11 daje sygnał PWM o wypełnieniu 0…255, odpowiadającym wartości zmierzonego napięcia na nóżce A0. Wbudowana w Arduino funkcja analogWrite(), dająca na wyjściu przebieg impulsowy PWM, też jest dość skomplikowana, a ponieważ wykorzystuje wbudowane timery procesora, działa tylko na pinach oznaczonych tyldą (~). Szczegółów możesz poszukać w dokumentacji mikrokontrolera ATmega328P.
Ponieważ funkcji analogWrite() nie możemy zastosować do pinu 13, do którego dołączona jest dioda LED, wykonaliśmy zworę między pinami 11 i 13. Możemy zrobić takie zwarcie, gdyż (nieużywane) piny procesora domyślnie są konfigurowane i ustawiane jako (niepodciągnięte) wejścia.
Nasz program działa w pętli loop(), więc mierzy napięcie i aktualizuje współczynnik wypełnienia sygnału na pinie 11 tysiące razy na sekundę, ale taka niepotrzebna pracowitość nam nie przeszkadza.
Łącze szeregowe
A teraz zmierzoną wartość napięcia dodatkowo zobrazujemy w postaci liczb. Wykorzystamy fakt, że płytka cały czas połączona jest kablem z komputerem. Łącze USB (właściwie łącze RS-232 skonwertowane na USB) służy nie tylko do programowania i do zasilania, ale jest też wygodnym i łatwo dostępnym połączeniem szeregowym naszej płytki z komputerem. Na komputerze pracuje pakiet Arduino IDE, gdzie mamy możliwość otwarcia konsoli komunikacyjnej przez kliknięcie ikonki Serial Monitor, jak na rysunku 17 (także z menu: Tools – SerialMonitor albo klawiszami Shift+Ctrl+M).
Konsola jest cały czas gotowa do wymiany informacji z płytką Arduino, ale musimy uzupełnić nasz program – szkic. W tym celu skorzystamy z gotowej biblioteki Serial, która jest integralną częścią pakietu Arduino IDE i bez żadnego wysiłku z naszej strony zapewni komunikację z komputerem. Na początku naszego szkicu w funkcji setup() dopiszemy polecenie konfigurujące szybkość łącza szeregowego RS-232, równą 9600 bitów na sekundę, bo tak skonfigurowana jest domyślnie konsola na komputerze. Dalej w programie w pętli loop() dodamy instrukcję wysłania do konsoli na komputerze zmierzonej wartości napięcia oraz wprowadzimy opóźnienie. Program, który w Elportalu ma nazwę A0102, może wyglądać następująco:
// A0102
void setup() {
Serial.begin(9600);
}
void loop() {
analogWrite(11, analogRead(0) / 4);
Serial.println(analogRead(0));
delay(1000); // opóźnienie 1s
}
Właściwy program jest króciutki. Prócz zmiany jasności diody LED, poleceniem Serial.println() w każdym obiegu pętli wysyłamy odczytaną wartość napięcia przez łącze szeregowe COM i wyświetlamy na konsoli. Aby nie następowało to wiele razy na sekundę, dodaliśmy w pętli sekundowe opóźnienie delay(1000). Mniej więcej co sekundę płytka wysyła więc do komputera wartość napięcia z suwaka potencjometru jako liczbę z zakresu 0…1023, co związane jest z mrugnięciem żółtej diody LED oznaczonej TX. Liczby te wyświetlane są na ekranie komputera.
Rysunek 18 pokazuje, jak zmieniały się u mnie przy kręceniu suwakiem potencjometru. Zauważ, że z uwagi na duże opóźnienie delay(1000) pokręcanie suwakiem daje teraz skokowy efekt zmiany jasności LED na płytce, co jest niezbyt pożądaną cechą. Jeśli chcesz, możesz dowolnie zmniejszyć wartość opóźnienia.
Może od razu zasygnalizuję też, że w pakiecie Arduino IDE oprócz konsoli tekstowej wprowadzono drugi monitor graficzno-analogowy.
Zamknij konsolę tekstową i albo w menu wybierz Tools – SerialPlotter, albo naciśnij Shift+Ctrl+L.
Rysunek 19 pokazuje wykres zmian napięcia w czasie pokręcania suwaka potencjometru (przy 10-milisekundowym opóźnieniu w pętli).
Zwróć też uwagę, że w każdej pętli naszego programu dwa razy odczytujemy napięcie z suwaka, co z różnych powodów nie jest eleganckim rozwiązaniem. Bardziej eleganckie i szybsze jest zapisanie raz odczytanej wartości w jakiejś zmiennej. Może to wyglądać następująco:
void setup() {
//jednorazowo skonfiguruj
// i ustaw szybkość portu
Serial.begin(9600);
}
void loop() {
//zmierz napięcie na pinie A0
// i wpisz do zmiennej
int napiecie = analogRead(0);
// teraz ustaw PWM
analogWrite(11, napiecie / 4);
// i wyślij
Serial.println (napiecie);
delay(100); // opóźnienie 0,1s
}
W pętli loop() deklarujemy „w locie” zmienną typu int o nazwie napiecie i od razu wpisujemy do niej 10-bitową liczbę reprezentującą napięcie z pinu A0. Potem korzystamy już tylko ze zmiennej napiecie, a nie tracimy czasu na stosunkowo długie mierzenie napięcia za pomocą przetwornika ADC.
Przyciski
Wykorzystaliśmy bardzo pożyteczne funkcje, jakie oferuje Arduino: analogWrite(), analogRead() i Serial.println(). Skorzystajmy też z poleceń bardziej podstawowych: digitalWrite() i digitalRead(), które działają na wszystkich pinach, także analogowych A0…A5. Najpierw zrealizujemy coś bardzo prostego: przerzutnik RS. Niech przycisk S1 włącza, a S2 niech wyłącza żółtą diodę LED dołączoną do nóżki 13. Potencjometru nie wykorzystujemy.
Zasadniczo przy nauce Arduino proponuje się dołączanie przycisków „od góry”, jak na rysunku 20a.
Z kilku względów bardziej praktyczne jest dołączanie przycisków „od dołu” według rysunku 20b. My stosujemy ten drugi sposób, dodatkowo wykorzystując fakt, że w mikroprocesorze można włączyć kilkudziesięciokiloomowe rezystory podciągające piny do plusa zasilania według rysunku 20c. Stąd na rysunku 14 mamy tylko przyciski dołączone do masy. Program może wyglądać następująco:
// A0103 Wykorzystanie przycisków
void setup() {
pinMode(0, INPUT_PULLUP);
pinMode(1, INPUT_PULLUP);
pinMode(11, OUTPUT);
}
void loop() {
if (digitalRead(0) == LOW) digitalWrite(11, HIGH);
if (digitalRead(1) == LOW) digitalWrite(11, LOW);
}
Domyślnie wszystkie piny procesora, a tym samym Arduino skonfigurowane są jako wejścia, ale „zwykłe” wejścia niepodciągnięte. Trzeba więc włączyć podciąganie na pinach cyfrowych 0 i 1. Musimy też skonfigurować pin 11 jako wyjście. Wcześniej tego nie robiliśmy, bo korzystaliśmy ze znacznie bardziej złożonej „arduinowej” funkcji bibliotecznej analogWrite(), która konfigurację robiła automatycznie, w sposób dla nas niewidoczny. Natomiast funkcja digitalWrite() jest prostsza i nie konfiguruje pinu, tylko wpisuje wartość binarną do jednej z komórek rejestru procesora.
Jednorazowo w funkcji setup() włączamy więc podciąganie, a potem w pętli loop() nieustannie sprawdzamy stan pinów 0, 1. Za pomocą dwóch warunków if sterujemy wyjście cyfrowe 11 oraz połączony z nim pin 13 i diodę LED. Łatwizna! A teraz spróbuj zakomentować, czyli wyłączyć linię konfigurującą wyjście, dodając dwa ukośniki:
// pinMode(11, OUTPUT);
W ten sposób zarówno pin 11, jak i 13 będą wejściami. Spowoduje to ciągłe świecenie diody LED. Nie można jej wyłączyć.
Jeżeli jednak rozłączyłbyś piny 11, 13 i włączyłbyś diodę LED między pin 11 i masę według rysunku 21, układ znów zacznie działać, tylko dołączona dioda LED będzie świecić bardzo słabo.
Wprawdzie pin 11 jest teraz wejściem, ale polecenie digitalWrite(11, HIGH), czyli wpisanie jedynki do wejścia powoduje… włączenie wbudowanego rezystora podciągającego – jest to to samo, co polecenie konfiguracyjne pinMode(11, INPUT_PULLUP). Tak po prostu działają piny portów mikrokontrolera.
Ale dlaczego świeci, i to pełnym blaskiem, dioda LED dołączona do pinu 13? Odpowiedź znajdziesz na schemacie wewnętrznym płytki Arduino UNO R3. Otóż dioda LED nie jest dołączona wprost do pinu 13, tylko za pośrednictwem wzmacniacza LMV358, którego wypływający prąd wejściowy (typowo 11nA, maksymalnie 250nA) okazuje się „czynnikiem podciągającym”. Wystarczy, że pomiędzy pin 13 i masę włączysz rezystor 1MΩ…4,7MΩ, a dioda ta zgaśnie i układ według rysunku 22 będzie jak najbardziej prawidłowo zaświecał diodę także z zakomentowaną linią
// pinMode(11, OUTPUT);
Czy w działaniu zrealizowanego w ten sposób przerzutnika RS zauważamy jeszcze coś dziwnego?
Tak! Podczas naciskania S2 zaświeca się dioda TX.
Aby to wyjaśnić, do programu dodamy polecenia obrazujące stan diody LED na konsoli komputera za pomocą liczb 0 (nie świeci), 1 (świeci). Skompiluj i wgraj do procesora poniższy program:
void setup() {
pinMode(0, INPUT_PULLUP);
pinMode(1, INPUT_PULLUP);
pinMode(11, OUTPUT);
// jednorazowo skonfiguruj i ustaw szybkość portu
Serial.begin(9600);
}
void loop() {
if (digitalRead(0) == LOW) digitalWrite(11, HIGH);
if (digitalRead(1) == LOW) digitalWrite(11, LOW);
Serial.println (digitalRead(11)); // wyślij
delay(300); // opóźnienie 300ms
}
Podczas wgrywania programu żółta lampka TX będzie świecić ciągle, ale gdy otworzysz konsolę na komputerze i gdy płytka Arduino „dogada się” z konsolą, żółta lampka TX będzie migać mniej więcej co 0,3 sekundy. Wprawdzie naciskanie przycisków S1, S2 zaświeca i gasi kontrolkę połączoną z pinem 13, ale długie naciśnięcie S2 gasi lampkę TX i w tym czasie do konsoli nie są przekazywane informacje. Dlaczego?
Wejścia cyfrowe 0, 1 są współdzielone z łączem szeregowym COM (RS-232). Pełną odpowiedź znajdziesz na schemacie płytki Arduino. W każdym razie gdy w procesorze pracuje sprzętowe łącze szeregowe, nie powinniśmy korzystać z cyfrowych pinów 0, 1. Ale warto wiedzieć, że za pomocą odpowiedniej biblioteki łącze szeregowe można też zrealizować programowo na dowolnych pinach procesora, oczywiście licząc się ze znacznym zwiększeniem rozmiarów programu.
Aby zamknąć temat współdzielenia pinów, przełącz przyciski S1, S2 do skrajnych gniazd płytki Arduino według rysunku 23.
W oryginalnej płytce są one wyraźnie oznaczone SCL, SDA (fotografia 24), w klonach oznaczenie jest tylko na spodzie płytki. Program trzeba zmienić następująco:
void setup() {
pinMode(A4, INPUT_PULLUP);
pinMode(A5, INPUT_PULLUP);
pinMode(11, OUTPUT);
// jednorazowo skonfiguruj i ustaw szybkość portu
Serial.begin(9600);
}
void loop() {
if (digitalRead(A4) == LOW) digitalWrite(11, HIGH);
if (digitalRead(A5) == LOW) digitalWrite(11, LOW);
Serial.println (digitalRead(11)); // wyślij
delay(300); // opóźnienie 300ms
}
Teraz wszystko pracuje prawidłowo. Warto tylko zmniejszyć czas opóźnienia z 300ms do na przykład 50ms. Zwróć uwagę, że przyciski S1, S2 dołączyliśmy do nóżek oznaczonych SDA, SCL, a w programie wpisaliśmy numery pinów analogowych A4, A5. W ten sposób przekonaliśmy się, że po pierwsze mamy bezpośrednie połączenie nóżek A5 – SCL oraz A4 – SDA. We wcześniejszych wersjach Arduino „zdublowanych” gniazd oznaczonych SDA, SCL nie było. Zostały dodane dla kompatybilności z przyszłymi wersjami Arduino, z procesorami, gdzie nie ma współdzielenia A4, A5 ze sprzętowym portem I2C (TWI – Two Wire), czyli końcówkami SDA SCL. Jeśli w Arduino Uno R3 będziesz wykorzystywał sprzętowy port TWI/I2C (i bibliotekę Wire), nie powinieneś wykorzystywać pinów A4, A5. Możesz też na dowolnych dwóch pinach zrealizować port I2C programowo.
Przekonaliśmy się też, że tak zwane wejścia czy piny analogowe (A0…A5) mogą bez problemu pełnić funkcję pinów cyfrowych. Zachęcam do analizy schematu płytki Arduino i karty katalogowej procesora ATmega328P.
Na koniec jeszcze jeden dość istotny szczegół. Czy zauważyłeś, że po dołączeniu przycisków do innych pinów musieliśmy przeszukać cały program i zmienić go w kilku miejscach? Gdyby program był bardziej rozbudowany, byłoby to uciążliwe, a przeoczenie spowodowałoby błędy. Dlatego powszechnie stosuje się lepsze sposoby. Takim lepszym sposobem jest wykorzystanie dyrektywy #define. Przykład poniżej:
#define pinWyjsciowy 11
#define pinWeS1 A4
#define pinWeS2 A5
void setup() {
pinMode(pinWeS1, INPUT_PULLUP);
pinMode(pinWeS2, INPUT_PULLUP);
pinMode(pinWyjsciowy, OUTPUT);
//jednorazowo skonfiguruj i ustaw szybkość portu
Serial.begin(9600);
}
void loop() {
if (digitalRead(pinWeS1) == LOW)
digitalWrite(pinWyjsciowy, HIGH);
if (digitalRead(pinWeS2) == LOW)
digitalWrite(pinWyjsciowy, LOW);
Serial.println (digitalRead(pinWyjsciowy));
delay(300); // opóźnienie 300ms
}
Dyrektywa preprocesora (#define) jeszcze przed kompilacją dokona prostego zastąpienia, podstawienia: wszędzie zamiast pinWyjsciowy wpisze 11, zamiast pinWeS1 wpisze po prostu A4 i zamiast pinWeS2 wpisze A5.
Ponieważ dyrektywa #define dokonuje tylko „bezmyślnego” podstawienia przed kompilacją, preferowany jest inny, lepszy sposób pokazany na listingu:
const int pinWyjsciowy = 11;
const int pinWeS1 = A4;
const int pinWeS2 = A5;
void setup() {
pinMode(pinWeS1, INPUT_PULLUP);
pinMode(pinWeS2, INPUT_PULLUP);
pinMode(pinWyjsciowy, OUTPUT);
// jednorazowo skonfiguruj i ustaw szybkość portu
Serial.begin(9600);
}
void loop() {
if (digitalRead(pinWeS1) == LOW) digitalWrite(pinWyjsciowy, HIGH);
if (digitalRead(pinWeS2) == LOW) digitalWrite(pinWyjsciowy, LOW);
Serial.println (digitalRead(pinWyjsciowy)); // wyślij
delay(300); // opóźnienie 300ms
}
Tu na pierwszy rzut oka wydaje się, że na początku, zamiast dyrektywy preprocesora, definiowane są trzy zmienne globalne: pinWyjsciowy, pinWeS1 oraz pinWeS2. Wszystkie są typu int (integer) i od razu przypisywane są im określone wartości, którymi są numery pinów Arduino. Przy definiowaniu i inicjalizowaniu zmiennych, na końcach linii musimy postawić średnik.
Osoby nieco bardziej zorientowane zapewne będą się zastanawiać, dlaczego definiujemy tu zmienne. Jak wiadomo, zdefiniowanie w programie zmiennej określonego typu oznacza zarezerwowanie w pamięci danych procesora odpowiedniej ilości miejsca, stosownie do „wielkości” danego typu. Dla typu int, czyli liczb całkowitych –32768…32767 należy w pamięci RAM zarezerwować dwa bajty (16 bitów).
Ale ściślej biorąc, w tym przypadku nie są to zmienne, tylko stałe, a to dzięki użyciu słowa kluczowego const.
Ogólnie biorąc, stałe zasadniczo też finalnie stają się komórkami pamięci RAM, tylko takimi, których zawartości nie wolno modyfikować w trakcie programu.
Jednak o wszystkim decyduje kompilator. A jest on wystarczająco inteligentny, by „wiedzieć”, że w omawianym teraz przypadku stałe nie mają być komórkami pamięci, tylko „pomocniczymi stałymi”, potrzebnymi jedynie na czas kompilacji. Dlatego kompilator nie zarezerwuje dla tych stałych pamięci RAM. Udowadnia to porównanie objętości zajmowanej pamięci RAM dla skompilowanych obu wersji programu: rysunek 25 pokazuje, że są one jednakowe (po 188 bajtów).
Ten drugi sposób ze stałymi jest lepszy, ponieważ dyrektywa preprocesora #define dokonuje tylko „bezmyślnego” podstawienia, a teraz definiujemy stałe określonego typu, więc kompilator będzie pilnował też pewnych dodatkowych szczegółów i wykryje więcej ewentualnych błędów.
Nie musisz tego do końca rozumieć ani zagłębiać się we wszystkie szczegóły. Ale w treści programów szkiców nie używaj numerów pinów. Na początku programu definiuj stałe. Wtedy zmiana pinów wiązać się będzie ze zmianą stałych tylko na początku programu.
Tyle w pierwszym odcinku cyklu, w którym zapoznaliśmy się z podstawowymi poleceniami wejścia/wyjścia:
digitalRead(),
digitalWrite(),
analogRead() i,
analogWrite().
Jak widzisz, korzystanie z Arduino jest zaskakująco łatwe.
Jestem przekonany, że już teraz napiszesz swoje własne programy. Nie przejmuj się, jeśli coś nie będzie działać według oczekiwań. Stopniowo będziemy poznawać tajniki Arduino oraz języków C i C++. W artykule UR002 zawarty jest drugi odcinek kursu, natomiast artykuły wątku „Wokół Arduino” dotyczące programowania znajdziesz pod numerami zaczynającymi się od PR001, a te dotyczący budowy i działania sprzętu – pod numerami zaczynającymi się od UR030.
Piotr Górecki