Arduino-programozás mesterfokon
Már hosszú ideje készítesz alkalmazásokat mikrovezérlőre? A következő tippekkel hatékonyan optimalizálhatod kódodat.
Eleinte bizonyára te is úgy készítetted el a mikrovezérlőre szánt kódokat, hogy egyetlen cél vezérelte az alkotási folyamatot, mégpedig a végeredmény. Nem számított, mennyire átláthatatlan a program (sőt könnyen lehet, hogy azt sem tudtad, egy-egy specifikus sor miért került bele, de a működéshez elengedhetetlennek tűnt), a lényeg az volt, hogy a fordító elfogadja, és az Arduinóra feltöltve pontosan azt a feladatot hajtsa végre, amit szerettél volna.
Aztán ahogy egyre több időt töltöttél a különféle projektekkel, valószínűleg elkezdett zavarni, ha az egyes szekciók nem a megfelelő behúzásokkal jelentek meg a képernyőn, és a változókat is beszédes nevekre cserélted, így aztán amikor később előástál egy néhány száz sorból álló programot, nem kellett azon tűnődnöd, hogy mire is használtad a kód elején definiált, "a" betűvel jelölt változót. Idővel a kommentek is megjelentek a programjaidban és arra is ráeszméltél, hogy a funkciók alkalmazása mennyi felesleges kódolástól megkímél.
Tehát a kezdetekhez képest rengeteg optimalizálási irányelvet alkalmazol már most is az Arduino-fejlesztés során, ám még mindig akadhatnak olyan módszerek, amelyek révén még hatékonyabb kódot varázsolhatsz limitált erőforrásokkal gazdálkodó mikrovezérlődre. Ezek közül mutatunk be többet is a cikkünkben.
Optimalizált adattípus
Egy-egy komolyabb projektben gyakorta jelentős számú változót használsz a különféle információk tárolására, éppen ezért kiemelten fontos, hogy mindig a megfelelő adattípust válaszd ki, ez pedig nem is olyan egyszerű feladat. Ha ugyanis a szükségesnél nagyobb lefedettséggel rendelkező változóval dolgozol, akkor extra memóriát használsz feleslegesen, ha azonban sikerül alálőni a kiválasztott típussal, a túlcsordulás miatt jelentkezhetnek problémák.
Méretek tekintetében a bool, a char, és a byte 8 biten, az int és a short 16 biten, a long, a float, valamint a double pedig 32 biten kap helyet. Ezek ismeretében, valamint a maximálisan és minimálisan felvehető értékeik alapján egy kis keresést követően pontosan belőheted, hogy épp melyik lehet az a típus, amelyik minden szempontból tökéletesen passzol majd az alkalmazásod kiválasztott eleméhez.
Helyi változók használata
Azzal valószínűleg már tisztában vagy, hogy a kódodban található setup() függvény előtt létrehozott, úgynevezett globális változókat a kód teljes területén elérheted, de nem biztos, hogy minden esetben szükséges ezeket a folyamatosan megtartott értékkel rendelkező elemeket használnod. Ahol teheted, érdemesebb mindent lokális változóra cserélned, hiszen az elérhetőség és az élettartam leszűkítésével jelentősen csökkentheted az egyes elemek "kezelési költségét".
Ráadásul a kizárólag lokálisan - például egy függvényen belül - elérhető, úgynevezett STACK-en tárolt komponensek sokkal gyorsabban hozzáférhetőek, mint a teljes kódra kiterjedő társaik. Szóval javasolt ezzel a megoldással élni, és csakis akkor globális változókat bevetni, amikor feltétlenül szükséges.
Szabályozott szövegek
Nemcsak a tiszteletedet róhatod le a mémmé vált F használatával, hanem az Arduinóra történő fejlesztés során is jól jöhet, különösen akkor, ha komolyabb mennyiségű szöveget szeretnél programod futtatása közben a soros monitorra küldeni. Ez a folyamat ugyanis jelentős memóriát emészt fel, amiből ugyebár az Arduino csak erősen limitált mennyiséggel rendelkezik.
Viszonylag egyszerűen véget vethetsz a féktelen RAM-zabálásnak, ha a flashmemóriát alkalmazod a kiíratások során. Ehhez eszközt is kapsz a környezettől az F() makró képében, amely arra kényszeríti a fordítót, hogy a program a flashmemóriát használja. Tehát a Serial.print("Ez itt a minta!"); helyett a Serial.print(F("Ez itt a minta!")); segítségével némileg tehermentesíted a rendszermemóriát, így pedig egy optimalizált végeredmény vár rád. Az egyetlen komolyabb hátulütője az F() makrónak, hogy kizárólag karaktersorozatok esetén jöhet szóba.
Természetesen nem ez az egyetlen opciód a szöveges információk szabályozására. Szintén jelentősen optimalizálhatja kódod működését, ha kezeled az egyébként dinamikusan változó karaktersorozatok méretét, ehhez pedig - komolyabb extra kódolás nélkül - előre lefoglalhatsz memóriát a számukra. Ezzel a lépéssel lényegében megelőzhetsz olyan memóriatöredezettség által előidézett kellemetlenségeket, amelyek hatására csökkenhet a programod teljesítménye. Alkalmazásához nincs más teendőd, mint a reserve() funkciót a létrehozott, string típusú változó mögé gépelni, illetve egy byte-ban megadott memóriamennyiséget a zárójelei közé írni, és már végre is hajtottad az előfoglalást (String valtozo; valtozo.reserve(50);).
Felturbózott fordítás
Belepiszkálhatsz a fordító működésébe is, ami nélkül az eszköz képtelen lenne értelmezni a kódot. Az ehhez szükséges módosításokat azonban a fejlesztői környezeten kívül találod. Navigálj az Arduino IDE telepítési könyvtárába (ha az asztalon jobb egérrel a szoftver ikonjára kattintasz, és A fájlt tartalmazó mappa megnyitása lehetőséget választod, akkor az intéző odavisz), majd itt indulj el a hardware/arduino/avr útvonalon, és keresd meg a platform.txt állományt.
Ha megtaláltad, egy szimpla szövegszerkesztő segítségével nyisd is meg, és jöhet a módosítás. Itt rengeteg hasznos pluszkapcsolót élesíthetsz a gcc fordító által felkínáltak közül, de most koncentrálj kizárólag az optimalizációt érintő paraméterekre. Alapértelmezetten a -Os szint az, amit a fordító használ, így azt a három helyet kell megkeresned a szövegben, ahol ezek elhelyezésre kerültek.
Összesen hatféle kapcsoló (-O0, -O1,-O2,-O3, -Os, -Ofast) áll a rendelkezésedre, mind más és más eredménnyel kecsegtet, érdemes lehet kipróbálni őket. Természetesen a hatékonyságuk a kódodtól is függ, illetve egész pontosan attól, hogy milyen metódusokat alkalmazol. Az alapértelmezett -Os kapcsolóval egészen kompakt méretű kódokat generálhatsz, de memóriaelérésben a -Ofast képes lekörözni, míg például a delay() funkciók késleltetése a -O1 esetén a legalacsonyabb (a -O0 az egyetlen optimalizáció, amelyet a fordító sem ajánl).
Kizárólag profiknak
Rengeteg, komolyabb hozzáértést igénylő optimalizációs lehetőség is létezik, amelyek közül az egyik legizgalmasabb a mikrokontroller portjainak közvetlen manipulálása. A C-s alapnyelv szépségeinek köszönhetően módodban áll közvetlenül parancsokat küldeni a csatlakozóknak, ezzel megnövelve az írási/olvasási sebességet és jó néhány mikroszekundummal felpörgetve a szoftver futási idejét (esetenként még memóriát is spórolhatsz).
Persze nem véletlenül rejtették el az IDE fejlesztői ezeket a funkciókat, mivel használatuk könnyen instabilitáshoz vezethet, de ha a C nyelv nem ismeretlen számodra, akkor ezt a tudásodat akár a portok közvetlen elérésénél is kamatoztathatod. Amennyiben foglalkoztat ez a lehetőség, az Arduino hivatalos oldalán olvashatsz róla részletesebben. Szintén csak a profiknak javasoljuk a bootloader kikerülésének/módosításának opcióját, amely akkor jöhet jól, ha az eszközöd flashmemóriájának nemcsak elméleti, hanem ténylegesen elérhető maximumára van szükséged, esetleg gyorsabb kódfeltöltésre vágysz.
Természetesen ez már lényegesen bonyolultabb folyamat, komoly kockázatokkal is jár, ezért érdemes erre ténylegesen egyfajta végső megoldásként tekinteni. Amennyiben mégis foglalkoztat a bootloader hackelésének gondolata, ezen az oldalon találhatsz néhány hasznos bemutatót ezzel kapcsolatban.