Powrót

Wokół Arduino. Od języka C nie uciekniesz! 

W serii artykułów „Wokół Arduino” w artykułach począwszy od PR001, poznajemy trudniejsze zagadnienia dotyczące sprzętu i programowania. Jak pisałem w artykule PR001, Arduino to zasłona, która ma przykryć trudniejsze aspekty zagadnienia, by nie straszyły one początkujących.

„Przykryte” są zarówno kwestie sprzętowe, jak też niełatwe aspekty programowe. Dzięki temu nie trzeba wiedzieć, że podstawą pakietu Arduino IDE jest popularny kompilator (AVR-GCC). Nie trzeba wiedzieć, że krótki i zwięzły szkic (plik .ino) najpierw zostanie przekształcony w typowy plik języka C++, czyli w sumie na język C. Nie trzeba też wiedzieć, że króciutki szkic .ino wykorzystuje liczne rozmaite biblioteki, zawierające gotowe fragmenty kodu, przez co wynikowy kod zwykle bardzo się rozrasta w porównaniu ze szkicem .ino. Potem w dość skomplikowanym procesie powstały kod zostaje standardowo skompilowany i zlinkowany do postaci strawnej dla procesora, a wreszcie załadowany do jego pamięci (z pomocą avrdude).

Niebezpieczeństwo

Użytkownikami platformy Arduino bardzo często są ludzie mający znikomą wiedzę zarówno na temat elektroniki i procesorów, jak też na temat programowania w języku C i C++. Osoby takie bardzo często wykorzystują łatwo i legalnie dostępne gotowe programy cudzego autorstwa (szkice z rozszerzeniem .ino) i ewentualnie lekko je tylko modyfikują. A w razie kłopotów szukają pomocy na forach. I zwykle potrzebną pomoc znajdują.

Jest to powszechny sposób działania, który ma niezaprzeczalne zalety. Od początku daje mnóstwo satysfakcji. Kryje jednak w sobie niebezpieczeństwo pozostania na poziomie elementarnym.

Kto nie chce pozostać w przedszkolu, tylko w pełni samodzielnie pisać programy dla Arduino (i nie tylko), albo przynajmniej w pełni świadomie modyfikować znalezione w Internecie szkice .ino (i starsze szkice z rozszerzeniem .pde), powinien nauczyć się przynajmniej podstaw języka C i nabrać wprawy w wykorzystaniu mikroprocesorów. Nie musi, ale powinien.

Nakładka Arduino umożliwia, a wręcz narzuca uproszczony sposób pisania programów. Załatwia też „po cichu” szereg istotnych kroków i wyręcza programistę. Ale od języka C nie uciekniemy, bo programy, czyli szkice Arduino, pisane są w języku C++, a język C++ jest w rzeczywistości językiem C, tylko wzbogaconym o możliwość tzw. programowania obiektowego.

Nie wchodząc w szczegóły, można stwierdzić, że w Arduino IDE można pisać programy w „prawdziwych” językach C i C++, a także w asemblerze. Zapewne już niedługo to docenisz i wykorzystasz. Z drugiej strony, do płytki Arduino można wpisać programy, przygotowane za pomocą kompilatorów innych, (trudniejszych w obsłudze) niż Arduino IDE, np. Atmel Studio. Z trzeciej strony… gotowe płytki są dobre na początek, ale z czasem pakiet programowy Arduino IDE można wykorzystać do programowania także „gołych” procesorów. Nie można bowiem zapomnieć, że finalnie do procesora wpisywany jest standardowy, „zwykły” program w języku maszynowym (pomijając tzw. bootloader).

Na pewno nie da się uciec od języka C! Zwłaszcza w dłuższej perspektywie.

Osoby wykorzystujące Arduino mnóstwo wartościowych, szczegółowych informacji znajdą w dobiegającym właśnie końca kursie C. Niestety, kurs ten dla wielu chętnych okazał się zbyt trudny. Niektórzy „zgubili się” w masie informacji. Jeśli dotyczy to także Ciebie, masz znakomitą okazję, by spróbować jeszcze raz! Arduino praktycznie gwarantuje sukces.

Ale od początku warto interesować się tym, „co jest pod zasłoną?”.

Pod zasłoną Arduino

W komputerze z Windows,10 tworzone szkice Arduino oraz instalowane przeze mnie biblioteki znajdują się w katalogu:

C:\Users\Piotr\Documents\Arduino

Jeżeli przy instalacji ściągałeś pakiet instalacyjny, kluczowe pliki Arduino znajdziesz w katalogu:

C:\Program Files (x86)\Arduino\

Jeżeli jednak przy instalacji skorzystałeś ze sklepu Microsoft (Windows) Store, katalog ten zawiera praktycznie tylko jakieś dziwne pliki Javy, a kluczowych plików… nie ma. Aby odkryć tajemnicę, trzeba otworzyć program Arduino IDE i wybrać File – Preferences (Plik – Preferewncje), co otworzy okno z ustawieniami – rysunek 1. Na górze jest ścieżka do szkiców Arduino. Można tu także zmienić język edytora na „standardowy”, czyli angielski.

Trzeba natomiast zaznaczyć okienko wskazane czerwoną strzałką, by podczas kompilacji były wyświetlane szczegółowe informacje.

Rysunek 1

Rysunek 2 pokazuje, że w czarnym oknie po kompilacji (Verify) rzeczywiście pojawi się więcej informacji niż standardowe dwie linijki. Zobrazowany jest tam przebieg kompilacji z wykorzystaniem AVR-GCC: kolejno realizowane kroki – programy. Okaże się, iż przy instalacji za pomocą Microsoft Store wszystkie wykorzystywane programy znajdują się w katalogu C:\Program Files\WindowsApps\

Rysunek 2

U mnie na jednym z komputerów jest to: C:\Program Files\WindowsApps\ArduinoLLC.ArduinoIDE_1.8.10.0_x86__mdqgnx93n4wtt\hardware\tools\avr/bin

Jest kłopot z folderem C:\Program Files\WindowsApps\. Może być zupełnie niewidoczny w oknie eksploratora. A gdy jest widoczny, nie ma do niego dostępu nawet z uprawnieniami administratora. Aby ten dostęp uzyskać, trzeba trochę „pomajstrować” w zabezpieczeniach.

Natomiast przy instalacji ze ściągniętego pakietu, analogiczna ścieżka robocza jest następująca: C:\Program Files (x86)\Arduino\hardware\tools\avr/bin

i nie ma problemu z dostępem. W każdym razie w katalogu …\hardware\tools\avr\bin

znajduje się szereg programów, w tym avr-objcopy.exe, który pod koniec kompilacji uruchomiony z różnymi opcjami (przełącznikami) tworzy dwa pliki: Blink.ino.elf  oraz Blink.ino.hex.

Tworzony przez Ciebie plik .ino zostaje trwale zapisany w katalogu C:\Users\Piotr\Documents\Arduino, natomiast wspomniane pliki .elf i .hex zostaną zapisane do katalogu tymczasowego, który zostanie skasowany po zamknięciu Arduino IDE. U mnie ten sam przykładowy plik Blink.ino na dwóch komputerach  został skompilowany do katalogów tymczasowych:

C:\Users\PIOTR_~1\AppData\Local\Temp\arduino_build_564143\

C:\Users\Piotr\AppData\Local\Temp\arduino_build_630736\

U Ciebie ścieżka będzie podobna, tylko inna będzie zaznaczona na czerwono nazwa użytkownika i inny numer zaznaczonego na niebiesko folderu tymczasowego. Warto tam zajrzeć, bo znajdziemy jeszcze inne stworzone podczas kompilacji pliki.

Kompilacja

Podczas kompilacji szkic .ino jest traktowany jak program napisany w C++.  Ale jak zapewne wiesz, każdy program C (i C++) musi zawierać funkcję main(). W Arduino jej nie widzimy, ale na początku kompilacji użyty zostanie plik main.cpp (który możesz odnaleźć) i którego treść pokazuje poniższy listing:

/* main.cpp Main loop for Arduino sketches */
#include <Arduino.h>
int atexit(void (* /*func*/ )()) { return 0; }
void initVariant() __attribute__((weak));
void initVariant() { }
void setupUSB() __attribute__((weak));
void setupUSB() { }
int main(void) {
init();
initVariant();
#if defined(USBCON) USBDevice.attach(); #endif
setup();

for (;;) { //pętla nieskończona for
loop();
if (serialEventRun) serialEventRun();
} return 0; }

Podstawą jest więc plik main.cpp z jego podstawową funkcją main(), zawierający także szereg innych instrukcji. Kompilator właśnie do tego pliku dołączy obowiązkowe arduinowe funkcje setup() i loop(), które muszą znaleźć się w każdym szkicu .ino:

void setup() { /* tu jednorazowo określisz

       potrzebną Ci konfigurację systemu*/   }

void loop() { /* tu w gotowej pętli umieścisz

          swój główny program */        }

Co ważne, bez Twojej wiedzy zostanie też wykorzystany, dołączony za pomocą dyrektywy preprocesora plik nagłówkowy: #include <Arduino.h>. Poniżej masz drobny fragment tego pliku Arduino.h:

#include "compiler.h"
#include "led.h"
/*! \name Oscillator Definitions  */

#define FOSC32          32768
#define OSC32_STARTUP   AVR32_PM_OSCCTRL32

Znów mamy tu dyrektywy #include, dołączające kolejne pliki z kolejnymi instrukcjami (fragmentami programu).

A oto początek pliku compiler.h:

#include <avr32/io.h>
#include <intrinsics.h>
#include "preprocessor.h"
#include "parts.h"
#include <stddef.h>
#include <stdlib.h>
#define __asm__        asm
#define __inline__     inline

I mamy kolejne dyrektywy #include. Nie ulega wątpliwości, że oprócz Twojego króciutkiego szkicu .ino, do stworzenia finalnego wsadu dla procesora, kompilator  bez Twojego udziału wykorzysta mnóstwo gotowych „półproduktów”.

Arduino to naprawdę skuteczna zasłona tych działań. Ciesz się, że w Arduino IDE cały proces tworzenia kodu jest tak bardzo ułatwiony, w porównaniu z C++ i C.

Ale nie wszystko zostanie dołączone i zdefiniowane automatycznie. Niektóre informacje musi dostarczyć kompilatorowi programista (czyli Ty) na początku pliku .ino. Dlatego zawartość wstępnych linii wielu dostępnych szkiców .ino może Ci się wydać dziwna, a może nawet deprymująca, ale nie przejmuj się, jeśli z początku nie wszystko zrozumiesz. Będziesz wykorzystywał gotowe szkice i je modyfikował. A z czasem będzie coraz lepiej. Także sam główny program,  umieszczony w funkcjach setup() i loop(), musi być napisany zgodnie ze wszystkimi regułami języka C (C++).

Ale nie bój się! Praktyka pokazuje, że nie powinno sprawić Ci problemu korzystanie z większości operatorów. Najprawdopodobniej nie będziesz też mieć problemów ze zmiennymi i z typami zmiennych.

Edytor wbudowany w pakiet Arduino ułatwia pisanie szkiców i chroni przed niektórymi błędami. Niestety, ułatwienia Arduino nie sięgają zbyt głęboko. Kłopoty mogą wystąpić, jeśli słabo znasz język C (C++). Problem w tym, że kompilator  najpierw krótki  i treściwy program zawarty w szkicu .ino „rozwija” w pełnowartościowy program C++, a potem kontroluje wszystko, co potrafi skontrolować. Jak mówiliśmy wcześniej, jest to wyjątkowo upierdliwy służbista i biurokrata, więc sprawdza i „czepia się” nawet najdrobniejszych niedociągnięć. I tylko wtedy, gdy nie znajdzie usterek, przejdzie do następnego etapu: rozpisze kod języka C na elementarne rozkazy kodu maszynowego, zrozumiałe dla procesora.

To, że kompilator „czepia się”, to w sumie bardzo dobrze. Nie jest on Twoim wrogiem, tylko wymagającym i surowym przyjacielem. Zapobiegnie wielu trudnym do wyszukania błędom. Zgłosi też wszelkie literówki, których przeoczenie wywróciłoby działanie programu. Wskaże większość przypadków braku średnika na końcu instrukcji, co jest najczęstszym błędem w programach pisanych przez początkujących.

Ale kompilator nie dopilnuje wszystkiego. On dba o stronę formalną programu. Ale nie wie „co poeta miał na myśli”. Tymczasem reguły języka C i reguły rozpisywania kodu C na elementarne rozkazy kodu maszynowego są inne niż nasze doświadczenia z „normalnego życia”. Problemy często dotyczą logiki działania pętli,  badania warunków, wszelkich kwestii dotyczących czasu i opóźnień i przypadków, gdzie program musi jednocześnie „pilnować kilku rzeczy naraz”. Wbrew pozorom, nie jest łatwo „połączyć” dwa oddzielne, działające programy – szkice Arduino. Niestety, bardzo często samodzielnie tworzone programy albo nie chcą się skompilować, albo zostaną skompilowane, lecz będą działać inaczej, niż sobie wyobrażaliśmy.

Nastaw się, że na początku natkniesz się na niezrozumiałe problemy, wynikające właśnie ze słabej znajomości specyfiki języka C i nieumiejętności wyobrażenia sobie dokładnego działania finalnego, skompilowanego programu.

Na pewno napotkasz takie przeszkody. Ale nie zrażaj się! Ja ze swej strony zachęcam, żebyś nie pisał zaraz na którymś forum prośby o pomoc, tylko żebyś próbował samodzielnie znaleźć przyczynę, w czym mogą znakomicie pomóc informacje z Kursu Arduino, zawartych w artykułach o numerach UR000, UR001, … UR029.

Bardzo dobra wiadomość jest też taka, że w Internecie masz mnóstwo gotowych, działających szkiców, które możesz wykorzystać albo w całości, albo jako przykład i wzór do naśladowania.

Piotr Górecki