[075] Układamy wiedzę - cz. 1
Minęło już trochę czasu, samych artykułów pojawiło się siedemdziesiąt cztery. Pora wrócić do korzeni, układając po kolei całą dotąd zdobytą wiedzę, a przy okazji dowiedzieć się o rzeczach, które umykały, choćby z powodu braku potrzeby użycia danego rozwiązania w przykładach. W najbliższych artykułach nie będziemy budować niczego nowego, co jednak nie znaczy, że będzie nudo, gdyż – jak zawsze – będziemy bazować na konkretach. Purystów od razu przepraszam – będę starał się ograniczać tak zwaną wiedzę nieużyteczną do minimum. Daruję sobie zatem takie pojęcia jak wiring, C, także z dwoma plusami, kompilator i wiele innych. To wszystko można znaleźć w internecie i zachęcam do zgłębienia tych pojęć. My jednak pozostaniemy przy tak zwanym minimum nieodstraszającym, w zupełności wystarczającym do prawie wszystkiego.
Zacznijmy zatem od pojęcia programu, czyli jak to mówią w „arduinolandzie” – szkicu. Każdy program składa się z dwóch obowiązkowych części: drugiej i trzeciej, nieobowiązkowej pierwszej i czasem części kolejnych. Pierwszą nie jest właściwy program, a spis różnych deklaracji, nazw, których będziemy używać, bibliotek i definicji. W niektórych przypadkach może tam nic nie być, w innych – cała litania.
Druga część zaczyna się określeniem void setup() i tu już zaczyna się program właściwy. I właściwie to byłoby wszystko, gdyby nie nieco dziwny pomysł stworzenia trzeciej części, w której następuje ciąg dalszy tego, co było w drugiej. Po co tak? Część druga wykonuje się jednorazowo po włączeniu prądu albo po resecie, trzecia – zaczynająca się słowami void loop() – w kółko bez końca. Taka jest zasada, ale… Najlepiej wyjaśnijmy sobie to na przykładzie najnudniejszego programu do migania diodą.
void setup() {
pinMode(13, OUTPUT); // Zadeklaruj port diody jako wyjście.
}
void loop() {
digitalWrite(13, HIGH); // Włącz diodę.
delay(250); // Zaczekaj 250 milisekund.
digitalWrite(13, LOW); // Wyłącz diodę.
delay(250); // Zaczekaj 250 milisekund.
}
Tak wygląda modelowa struktura szkicu migania diodą świecącą, zwanego Blink. Możemy jednak zignorować zupełnie część drugą, przerzucając wszystko do trzeciej.
void setup() {
}
void loop() {
pinMode(13, OUTPUT); // Zadeklaruj port diody jako wyjście.
poczatekProgramu:
digitalWrite(13, HIGH); // Włącz diodę.
delay(250); // Zaczekaj 250 milisekund.
digitalWrite(13, LOW); // Wyłącz diodę.
delay(250); // Zaczekaj 250 milisekund.
goto poczatekProgramu;
}
Jednakże wówczas bez przerwy deklarowałby się port diody, co nie jest potrzebne i zajmuje czas.
void setup() {
}
void loop() {
pinMode(13, OUTPUT); // Zadeklaruj port diody jako wyjście.
poczatekProgramu:
digitalWrite(13, HIGH); // Włącz diodę.
delay(250); // Zaczekaj 250 milisekund.
digitalWrite(13, LOW); // Wyłącz diodę.
delay(250); // Zaczekaj 250 milisekund.
goto poczatekProgramu;
}
Użyłem więc nieużywanej raczej w Arduino komendy goto, o której jeszcze później rzeknę słówko, by w nieskończonej pętli tylko migać, bez ciągłego deklarowania pinu, do którego podłączona jest dioda, jako port wyjściowy. Program wygląda teraz bardziej jak w BASIC-u ze Spectrum i generalnie instrukcji goto unika się, bo tworzy zbędne byty i chaos. Ale program działa.
void setup() {
pinMode(13, OUTPUT); // Zadeklaruj port diody jako wyjście.
poczatekProgramu:
digitalWrite(13, HIGH); // Włącz diodę.
delay(250); // Zaczekaj 250 milisekund.
digitalWrite(13, LOW); // Wyłącz diodę.
delay(250); // Zaczekaj 250 milisekund.
goto poczatekProgramu;
}
void loop() {
}
Działać będzie także, gdy zignorujemy część trzecią, wrzucając cały program do części drugiej. Czasem tak się pisze bardzo proste programy, ale generalnie trzymajmy się zaleceń: w części drugiej (setup) fragment programu do wykonania jednorazowego, a w trzeciej (loop) – pozostała część programu, która ma pracować bez końca.
Wrócę jeszcze do części pierwszej (setup). W tym prostym programie wystąpiła liczba 13. Wtajemniczeni wiedzą, że każda płytka Arduino ma tam podłączoną diodę świecącą, więc program będzie pracował od razu, bez jakiegokolwiek podłączania czegokolwiek. Ale odwoływanie się do peryferiów po numerkach jest niemiłe i niezalecane. Co będzie, gdy podłączymy diodę gdzie indziej? Trzeba będzie odnaleźć w całym programie wszystkie odwoływania i pozamieniać adresy. Głupie to i niebezpieczne.
byte diodaSwiecaca = 13; // Numer pinu, do której podłączona jest dioda świecąca.
void setup() {
pinMode(diodaSwiecaca, OUTPUT); // Zadeklaruj port diody jako wyjście.
}
void loop() {
digitalWrite(diodaSwiecaca, HIGH); // Włącz diodę.
delay(250); // Zaczekaj 250 milisekund.
digitalWrite(diodaSwiecaca, LOW); // Wyłącz diodę.
delay(250); // Zaczekaj 250 milisekund.
}
I tutaj właśnie mamy pierwszą część, w której możemy nadać jakąś ludzką nazwę pinowi trzynastemu, w naszym przypadku – diodaSwiecaca, a gdybyśmy diodę przesiedlili, to tylko w tym miejscu będziemy musieli wprowadzić zmianę.
byte diodaSwiecaca = 13; // Numer pinu, do której podłączona jest dioda świecąca.
void setup() {
pinMode(diodaSwiecaca, OUTPUT); // Zadeklaruj port diody jako wyjście.
}
void loop() {
digitalWrite(diodaSwiecaca, HIGH); // Włącz diodę.
opoznienie(); // Skocz do procedury opóźniającej.
digitalWrite(diodaSwiecaca, LOW); // Wyłącz diodę.
opoznienie(); // Skocz do procedury opóźniającej.
}
void opoznienie() { // Procedura opóźniająca.
delay(250); // Zaczekaj 250 milisekund.
}
Dla porządku – wprowadźmy sobie jeszcze część czwartą i kolejne. Blok setup i loop są obowiązkowe i już wiemy co robią. Ale do nich możemy dobudowywać dowolną liczbę kolejnych, rozpoczynających się słowem void i przydaje się to, by wyprowadzać z programu elementy powtarzające się, a czasem rozbijać duże programy na części zajmujące się konkretnymi zadaniami, by zyskać czytelność i wygodę do analiz. Jak widać, u nas powtarzające się elementy do funkcja opóźniająca. Wyrzućmy ją z trzeciej części do czwartej, którą nazwę opoznienie. W miejscach po komendzie delay siedzi teraz odwołanie do owej czwartej części, czyli podprogramu, a sam delay trafił tamże. Należy pamiętać o zasadach: muszą tkwić nawiasy i średniki. Nota bene, te nawiasy są używane do przekazywania parametrów. Spójrzmy teraz jak ich użyć.
byte diodaSwiecaca = 13; // Numer pinu, do której podłączona jest dioda świecąca.
void setup() {
pinMode(diodaSwiecaca, OUTPUT); // Zadeklaruj port diody jako wyjście.
}
void loop() {
digitalWrite(diodaSwiecaca, HIGH); // Włącz diodę.
opoznienie(250); // Skocz do procedury opóźniającej.
digitalWrite(diodaSwiecaca, LOW); // Wyłącz diodę.
opoznienie(500); // Skocz do procedury opóźniającej.
}
void opoznienie(int milisekundy) { // Procedura opóźniająca.
delay(milisekundy); // Zaczekaj 250 milisekund.
}
Zróbmy tak: po włączeniu diody zaczekamy ćwierć sekundy, a po wyłączeniu – pół. Owe wartości wstawimy do pustych dotychczas nawiasów, a w podprogramie odzyskamy je, przekazując wartości do zmiennej milisekundy, którą zaraz przejmie funkcja delay. Spójrzmy na wielką zaletę takiego rozwiązania: możemy sobie używać jednego podprogramu, ale parametry przekazywane mogą być różne, w zależności od potrzeby. Oczywiście w tym przypadku zysk ze stworzenia tego konkretnego podprogramu, czyli czwartej części, jest żaden, bo tam siedzi tylko jedna linijka kodu. Ale nie do takich rozwiązań stworzono podprogramy, a bardziej skomplikowanych.
Podsumowując: programy dla Arduino piszemy w pewnym porządku bloków. Pierwszy – zawiera różne deklaracje, definicje i biblioteki związane z programem, ale nie ma tu programu. Drugi – tę część programu, która wykonuje się po resecie, trzeci – tę, która będzie wykonywać się w kółko, a wszystkie kolejne będą wydzielonymi fragmentami, tworzonymi wyłącznie dla zwiększenia czytelności i ograniczenia powtarzalnych fragmentów. W kolejnym artykule zajmiemy się liczbami, bez których w zasadzie żaden program nie mógłby zaistnieć.