Programozási alapismeretek kezdőknek

Tartalom:

  • A programozásról abszolút kezdőknek!
  • Program tárolása és futtatása operációs rendszer nélkül
  • Program végrehajtás, változók, változó típusok
  • Program fő részei, setup() és loop()
  • Kivezetések beállítása, állapot beolvasása bemenetről, kimenet állapotának megváltoztatása
  • Sokszor végrehajtott programrészek kialakítása, ciklusok
  • Változók értékének összehasonlítása if(), logikai vizsgálatok, logikai kapcsolatok
  • Többféle programvégrehajtás változó értéktől függően switch()
  • Sokszor felhasznált programrészek, függvények létrehozása és meghívása, programkönyvtárak

—————————————————————

Ezt a néhány oldalas ismertetőt azért írtam, mert egy Arduino iránt érdeklődő kezdő nem biztos, hogy programozási tudással rendelkezik, és segíteni szeretnék az alapok megértésében. Ha már készült egy elektronikai alapokat ismertető leírás, valóban kell egy programozási ismertető is. Nem lesz részletes és bonyolult.

Kedves olvasó! Ha most kezded a programozást tanulni, és a jövőben játékprogramokat, grafikus rendszereket, vagy éppen ügyviteli rendszereket szeretnél programozni, akkor is jó helyen jársz. Az alapok megértése feltételül szükséges, noha egyes fejlesztő rendszerek elrejtik előlünk az egyszerű programsorokat. Lehetséges, hogy ennyire alacsony szintű feladatokkal, mikrovezérlőkkel nem is fogsz találkozni későbbi feladataidban, de a tudás soha nem árt. Úgy gondolom, hogy a profi autóversenyzők is a lábpedállal hajtott játékutóban kezdik a pályafutásukat. Hajrá! ismerd meg a világ legizgalmasabb hobbiját. Tanulj meg logikusan gondolkodni és élni!

Mi a program? Ennek megértése nagyon egyszerű annak, akinek az anyukája, felesége esetleg a főnöke átadott már egy listát az aznapi teendőkről. Egy ilyen listán megtaláljuk a feladataink felsorolását. Lássunk egy példát, mit írna egy anyuka a gyerekének:

  1. Felkelés után moss fogat
  2. Öltözz fel
  3. Edd meg a reggelidet
  4. Indulj el az iskolába
  5. Zárd be az ajtót
  6. Ha pénzt raktam az asztalra, álj meg a közértnél, ha tízórait, akkor egyenest menj az iskolába.
  7. A közértben vegyél tejet és kiflit.
  8. Ha nincs tej, vegyél kakaót zsömlével.
  9. Menj be az iskolába

Láthatóan anyuka utasításkat adott a gyerekének, amit ha jól viselkedik, akkor végre is hajt. Ez a különbség köztünk emberek és a gépek között. A gép pontosan azt csinálja, amit mondunk neki, a gyerek nem biztos, hogy tejet fog venni tízóraira, lehet, hogy csokit. A listában vannak egyértelmű parancsok, melyeket feltétel nélkül végre kell hajtani, és vannak olyan utasítások, melyeknek ez elvégzéséhez tartoznak feltételek. Pl. ha a anyuka rakott pénzt az asztalra, akkor közértbe is menni kell.
Kedves programozni tanuló olvasó, képzeld azt, hogy Te vagy az anyuka, és listát írsz a gyerekednek, aki most konkrétan az Arduino nevű számítógép. Persze Arduino nem ért magyarul, egy bizonyos nyelven ért csak. Ezt a nyelvet azonban most nem fogjuk részleteiben megismerni, mert nagyon nehezen olvasható az ember számára, és nehéz benne programot írni. Neve gépikódú programozás, nem biztos, hogy érdemes megtanulni. Egy kicsivel magasabb szintű nyelvezettel kezdünk aminek neve „C++”. Ennek a nyelvnek az utasításait már könnyedén el tudjuk olvasni és jól érthető számunkra, ha megismertük a szerkezetét. Ha leírtunk egy utasításokból álló programot, van egy segédprogram, ami lefordítja az Arduino számára érthető gépikódú programnak. Ennek a “fordító” programnak a neve Arduino IDE. Mivel ez az ingyen használható program lehetővé teszi, hogy többé kevésbé érthető szavakkal és rövidítésekkel írjuk le mit szeretnénk, nem kell feltétlenül a gépikódú programozást megtanulni. Persze ha érdekel, akkor ez sem felesleges. De most maradjunk a C++ nyelvnél.

Az Arduino nem tud felkelni felöltözni, tejet venni, mindössze egyik-másik kivezetésére tud elektromos jelzéseket kapcsolni. Azt is meg tudja nézni, hogy egyik-másik kivezetésére milyen elektromos jelet kapcsoltunk. Tud még mást is, olyat amit egy kisiskolás például még nem tud: összead, kivon, szoroz, oszt gyököt von, és semmit nem felejt abból amit megjegyzett. Az a lista, amit leírhatunk neki, az a program. Tehát a programunk egy felsorolás, amit Arduino szép sorjában végrehajt. Teszi ezt igen gyorsan, egy-egy programsorból másodpercenként több tizezret, vagy akár százezret is végrehajt.

Remek dolog számunkra az, hogy van egy gépünk ami pontosan azt csinálja amit mondunk neki! Nagyon gyors, soha nem fárad el, és annyiszor ismétli amit leírtunk ahányszor csak akarjuk. Pontosabban addig ismétli amíg ki nem kapcsoltuk. Van amikor az ismétlés a lényeg, mert egyszerűen csak sokszor kell valamit megcsinálni, gondoljunk egy hőmérőre, ami mér és kijelez, nincs más dolga. Vannak azonban olyan gépek, amik feladataik végrehajtásakor minden alkalommal változtatnak valamin. Ha valamin változtatni kell a gépnek, akkor a különbséget meg kell mondani számára. A gép bamba módon ismétli a műveleteket. pl a robot legyárt egy autót. Azonban az autó festésekor valahonnan tudnia kell, hogy milyen színű legyen az éppen gyártott autó. Nekünk meg kell mondanunk a színt. A színt tárolnunk kell valahol, ahonnan a gép kiolvassa, és a megfelelő színű festéket veszi elő. Az ilyen adatok tárolására szolgálnak a változók. Pl az autó festését végző robotnak van egy „autószín” nevű változója, aminek a tartalma lehet zöld, kék, fehér stb. Minden autó legyártása előtt valaki beállítja ebbe a változóba a következő autó színét. A gép kiolvassa ennek a változónak a tartalmát, és döntést hoz. Eldönti, melyik festékes flakont kell levenni a polcról. Változókat más okból is használnia kell a gépnek. Pl. az autó gyártó robotunk minden autó legyártásakor megnöveli egy változó értékét, hogy a gépkezelő tudja hány autót gyártottak eddig. De mióta? Hát a változó utolsó törlése óta, ami lehet minden nap éjfélkor, de lehet újév napján is. Ezt a program dönti el. Pontosabban az, aki a programot megírta. Még pontosabban az, aki a program megírására megbízást adta a programozónak, és meghatározta a feladatot.  Tehát a gép kizárólag azt tudja megcsinálni, amit mit mi kitaláltunk számára. Ha valamilyen esetre nem gondol a programozó, vagy a megbízó, akkor az nem lesz végrehajtva. Ha pl. senki nem gondolt arra, hogy a legyártott autók számlálóját időnként törölni kell, akkor nem lesz törölve.

Látható a fenti példákból, hogy a programunk egy előre megírt és letárolt lista, de amikor a gép végrehajtja, a változók tartalmától függően más-más részét fogja végrehajtani a programnak, vagy más eredménye lesz a program végrehajtásának. Épp azért, hogy különféle tevékenységeket tudjon a gép végrehajtani, lehetőséget kell adnunk arra a programban, hogy egy-egy változó tartalmától függően „elágazzon” a program, és mást csináljon.

Az is jól látható a fenti példából, hogy egy számunkra ideális számítógépnek vagy mikrovezérlőnek tartalmaznia kell egy eszközt ami megjegyzi az általunk felírt listát. Ráadásul úgy kell megjegyeznie, hogy ne felejtse el akkor sem, ha közben lemerültek az elemek, vagy áramszünet volt. Az Arduino-ban van ilyen memória, flash-nek hívják, működési elve azonos az SD kártyával, amit fényképezőgépekben, mp3 lejátszókban már sokszor használtunk. A laptopok, asztali számítógépek úgynevezett merevlemezt használnak erre a feladatra. A telefonok amik szintén számítógépek szintén SD kártyát, vagy flash memóriát.

Első programunk létrehozása előtt, még egy kicsit beszélnünk kell a programot futtató számítógépek felépítéséről. Mindennapjainkban asztali számítógépeket laptopokat és mobiltelefonokat használunk. Ezeknek a számítógépeknek a használatával kapcsolatban fontos igény, hogy tetszés szerint cserélhessük a programokat, applikációkat. Épp ezért jó nagy memóriával rendelkeznek, és a sok sok előre megírt program közül mindig azt töltik be a merevlemezről vagy az SD kártyáról amire éppen szükség van. Azok az eszközök, melyek gépek, robotok, mérőberendezések vezérlésére készülnek nem cserélgetik a programot. Egyetlen programra van szükségük, ami működteti a teljes szerkezetet és semmi mással nem foglalkozik. Ezt a feladatot egyetlen picike számítógép, a mikrovezérlő végzi. Ez a mikrovezérlő sokszor nem nagyobb 2×2 mm-nél. Azért tud ilyen kicsi lenni, mert egyetlen cél feladatra használják, egyetlen programot kell futtatnia. Ennek az egyetlen programnak a tárolásra szolgáló memóriát beleépítik a mikrovezérlőbe, így futtatás előtt nem kell minden alkalommal betölteni valamilyen külső memóriából. Így minden sokkal egyszerűbb, a mikrovezérlő önmagában tartalmaz mindent ami a működéséhez szükséges. A mikrovezérlőknek a mobiltelefonokhoz képest nevetségesen kicsi memóriájuk van, de azokra a feladatokra amikre használjuk, az a kevés is elég szokott lenni. Fontos azonban tudni, hogy a program alapvető utasításai alig különböznek egymástól a számítógépeken és a mikrovezérlőkön. Ezért is mindegy, hogy melyiken kezded el a programozás tanulni.

Találjunk ki egy értelmes, de nagyon egyszerű feladatot, amin végig követhetjük egy program elkészítését a gondolat megszületésétől a megvalósításig. Természetesen mikrovezérlőt fogunk programozni (egy Arduino-t), mert azzal sokkal egyszerűbb dolgunk van, és tanuláshoz fontos, hogy egyszerre ne kelljen túl sok dologra figyelni.

1. feladat: Tolvajriasztó

Valaha nagyon régen (80-as években) egy barátom megkért, hogy készítsek egy LED villogó kapcsolást az autójába. Akkoriban még nem volt riasztó az autókban, örültünk, ha működő motor volt benne. Az autótolvajok válogatás nélkül mindent loptak amit az utcán találtak, Zsigulit, Skodát, Trabantot csupa menő márkát. Ha egy tucat Trabant közül az egyikben láttak egy villogó LED-et, azt hitték riasztó van az autóban és inkább a másikat lopták el. Így született az értelmes feladat, amit akkor nem Arduino-val, hanem egy tranzisztoros kapcsolással oldottam meg. Itt a lehetőség, hogy korszerű, 21. századi megoldást készítsünk. A feladat egyszerű, a mikrovezérlő egyik kivezetésére kötött LED-nek feszültséget kell adni, vári egy másodpercet, aztán lekapcsolni a feszültséget, aztán megint várni egy másodpercet és ezt ismételgetni. Régebben még rajzolgattunk folyamatábrát, talán még most is hasznos:

Azt láthatjuk, hogy a program végrehajtása során négy műveletet kell elvégeznünk, és ezt követően már csak ezeket ismételgetjük. Ez azt jelenti, hogy végtelen ideig (feltétel nélkül) ismételgetjük az utasításokat. A mikrovezérlők programjai mind így épülnek fel, végtelen ideig fut egy program, még akkor is ha egy adott időszakban semmit nem kell csinálnia mert épp várakozik valamire. Egy PC-n értelmes dolog, hogy egy programnak van eleje és vége, mert ott van úgynevezett operációs rendszer (Windows, Linux, mobilokon Android), ez fut végtelen ideig, és ha kell elindít egy programot, ami csak egyszer fut le. Egy mikrovezérlőnek nincs operációs rendszere, a program végrehajtás elindul bekapcsoláskor és akkor fejeződik be, amikor lekapcsoljuk róla a tápfeszt. Így aztán ne csodálkozzunk, hogy a programozási nyelv szerkezetét is ennek a tulajdonságnak megfelelően építették fel a C++ Arduino változatának tervezői. Ha C++ olyan változatával kezdesz dolgozni, amivel PC-re írsz programot, minden utasítás ugyanúgy fog kinézni, de egy kicsit másként fog felépülni a programod.

Lássuk, hogyan is épül fel egy program Arduino környezetben?! Mielőtt elkezdenénk utasításokkal ki és bekapcsolgatni az egyik kivezetés feszültségét, meg kell mondanunk, melyik legyen az a kivezetés. Vagyis be kell állítani a mikrovezérlőt a feladatai végrehajtásához. Azt érdemes tudni egy Arduino-ban található mikrovezérlőről, hogy 14 digitális kivezetése van (0-13-ig számozva), melyek tetszőlegesen kimenetnek és bemenetnek is beállíthatók a program speciális, erre kitalált utasításaival. Ezt ugyebár csak egyszer kell megmondani egy programban, így nem csodálkozunk azon, hogy a végtelen ciklus előtt van egy olyan programrész, amit csak egyszer futtatunk le. Most még ne foglalkozzunk azzal, hová írjuk be a programot, és mi a program Arduino-ba töltésének technikája, ezzel ráérünk később foglalkozni. Nézzük először ezt a beállító szakaszt.:

void setup() 
{
  pinMode(13,OUTPUT);
  digitalWrite(13,LOW);
}

Ebben a programrészletben a void setup() felirat azt jelzi, hogy a “{” és “}” zárójelek közé írt utasításokat egyszer fogja végrehajtani a programunk. Most csak két utasítás található itt. Az első megmondja, hogy a kivezetés kimenet legyen. A pinMode(13,OUTPUT);  utasítással a 13. kivezetés állítja kimenetnek. Ezzel készen is van a beállítás, a 13-as számú kivezetés kimenet lett, rá is kötöttünk képzeletben egy LED-et. Erre a kivezetésre a mikrovezérlő 0V feszültséget (LED nem világít) vagy 5V feszültséget (LED világit) tud kapcsolgatni majd. A második utasításunk be is állítja a kimenetet LOW állapotra, ami a 0V kimenő feszültséget jelenti. Tehát a program elindulásakor a LED nem fog világítani. Figyeljük meg, hogy az utasításokat pontosvesszővel zárjuk le. Ez jelenti egy utasítás végét. Az utasításokat akár közvetlenül egymás után is írhattuk volna, de általában áttekinthetőbb a program, ha külön sorba írjuk.

Nézzük is meg rögtön azt a programrészt, ami a led-et villogtatni fogja a világ végéig, vagy legalábbis addig, amig a tápfeszültséget le nem kapcsoljuk a mikrovezérlőről.

void loop() 
{
  digitalWrite(13,HIGH);
  delay(1000);
  digitalWrite(13,LOW);
  delay(1000);
}

A void loop() jelenti a programban azt a programrészt, amit a mikrovezérlő végtelen ideig ismételgetni fog. Kezdi a “{” zárójel után található utasítással, és végigmegy az összes utasításon, amit “}”-ig talál. Amikor megtalálta a “}” zárójelet, visszaugrik a “{” zárójelre, és kezdi elölről a végrehajtást. Egyszerű mint a pofon: a setup részben beállítjuk amit kell, a loop-meg csinálja amit előírunk a mikrovezérlőnek. A leírt utasítások közül már ismerjük a digitalWrite utasítást, ami LOW (0V), vagy HIGH (5V) feszültséget kapcsolgat a 13. kivezetésre. A delay(1000); kitalálható, hogy mit csinál. Természetesen várakozik 1000 millisecundumot, azaz egy másodpercet. Ha ezt a programot elindítjuk, akkor a led 1 másodpercig világít, egy másodpercig pedig nem. És a tolvaj a másik autót fogja ellopni. Szeretném egyben is megmutatni a teljes programot:

void setup() 
{
  pinMode(13,OUTPUT);
  digitalWrite(13,LOW);
}

void loop() 
{
  digitalWrite(13,HIGH);
  delay(1000);
  digitalWrite(13,LOW);
  delay(1000);
}

Ha ezt bemásolod a később megemlített Arduino IDE nevű fejlesztő program szövegszerkesztőjébe, és egyetlen gombnyomással USB portra kötött Arduino-ra töltöd a programot, már villogni is fog egy LED az Arduino alaplapon, mert pont a 13. kivezetésre gyárilag szereltek egy LED-et. De vigyázz, mert ha ezt pénteki napon csinálod, akkor persze tönkremegy valami a lakásban. Látható, hogy az Arduino nem amerikai termék. Ott ugyanis a lifteken sincs 13. emelet, 12 után a 14 jön babonából nehogy valakit baj érjen pénteken:

Ha most éppen nincs péntek, és máris szeretnél egy ilyen programot beletölteni az Arduino-ba, akkor költened kell kb. 2000 Ft-ot kinai barátainknál, vagy 6000Ft-ot egy elektronikai boltban Arduino Uno alaplapra. Aztán meg kell ismerkedned ennek az alaplapnak a felépítésével, kivezetéseivel, aztán rá kell építened egy led-et (vagy használhatod a 13. kivezetésre gyárilag ráépített LED-et). Ha mindezzel megvagy, akkor le kell töltened a https://www.arduino.cc/en/Main/Software weboldalról egy Arduino IDE nevű fejlesztő eszközt, aminek a szövegszerkesztőjében megírhatod (vagy bemásolhatod) a fenti programot. Ugyanez az Arduino IDE fejlesztő eszköz képes lefordítani az általad írt szöveges parancsokat a mikrovezérlő által is érthető gépikódra. Ez a gépikód nem igazán jól olvasható az ember számára, rengeteg számot tartalmaz. Azonban ezt már közvetlenül be lehet tölteni az Arduino mikrovezérlőjébe (ez egy ATmega328P chip), ami azt képes futtatni. Ehhez csak az Arduino UNO-t kell a számítógép USB portjához csatlakoztatni. Ennyi az egész!
Ha netán most nincs kéznél egy Arduino UNO, akkor se keseredj el. Olvasd el az Arduino szimulátorról szóló írásomat, és abból kiderül, hogyan tudsz 5 perc alatt virtuális Arduino-t programozni. Pl. a fenti programot is kipróbálhatod, csak az Arduino UNO áramkört nem tarthatod a kezedben, de a képernyőn látni fogod, ahogyan villog rajta a LED.
Ha kipróbáltad, ha nem, olvass tovább, mert még nagyon sok dolgot kell megtanulnunk, hogy értelmes feladatokat oldjunk meg!

2. feladat: fejlett tolvajriasztó.

Tételezzük fel, hogy már minden autóba tolvajriasztót építettek. A tolvajok már megszokták az egy másodperces villogást és nem riasztja el őket. Hát tegyük kicsit komolyabbá a látványt, három rövid villanást adjon a led, és ezzel komolyabb szerkezetnek fog tűnni. Talán elhiszik róla, hogy ez tényleg autóriasztó. A feladat csak annyiban különbözik, hogy a LED bekapcsolása rövid, 0.1 másodperces időszakra történik, aztán 0.1 másodperc szünet, és ezt megismételjük háromszor, aztán tartunk 1 teljes másodperc szünetet kikapcsolt LED-el.
Ha egy programban valamit sokszor meg kell ismételni akkor arra kitaláltak a C++ nyelvben nem is egy, hanem háromféle ciklus szervezési lehetőséget, hogy ne kelljen háromszor leírni ugyanazt. A ciklus ugyanazt az utasítás sorozatot meghatározható számban vagy feltétellel megismétli, jelen esetben háromszor fogja ezt megtenni. A ciklus működéséhez már kelleni fog egy változó, amiben azt tároljuk, hogy hányszor hajtottuk már végre a ciklus magban található utasításokat. Nézzük a megoldási lehetőségeket:

A) FOR ciklus. Ez az utasítás arra alkalmas, hogy megadott számú ismétlést állítsunk elő. Kell egy változó, amiben tároljuk a végrehajtások számát. A C++ nyelvben a változókat előre létre kell hozni a programban. Most ezt a változót „i”-nek fogom nevezni. Nem tudom miért, de a programozók a ciklus változóknak i,j,k stb. neveket szoktak adni. Pedig lehetne akár „kiskutya” vagy „bela_bacsi” is. A „változó létrehozást” (deklarálás) több helyen is meg lehet tenni, de egyelőre csak egy helyet fogunk használni, a program legelejét a setup és a loop programrészek előtt. A változóknak van típusa, ami megmondja milyen adatot fogunk benne tárolni. Ez most legyen „byte” típusú. Ez azt jelenti, hogy a változó tartalmát egy byte-on fogjuk tárolni, ami így 0-255 közötti egész értékeket vehet fel. Mivel 3-ig akarunk elszámolni, ez most nekünk elég. Íme a program:

byte i;

void setup() {
  pinMode(13,OUTPUT);	digitalWrite(13,LOW);
}

void loop() {
  for (i=0;i<3;i++)  {
    digitalWrite(13,HIGH);delay(100);
    digitalWrite(13,LOW);delay(100);
  }
  delay(1000);
}

Ez már egy kicsivel hosszabb program. Kicsit változtattam is a formátumon, egy sorba több utasítást írtam, hogy rövidebb legyen. Néha a program áttekinthetőségét ez segíti. Nagyon kell figyelni a { } zárójelekre. Nem csak a setup és loop utasításokat követi egy ilyen zárójel páros. Jelen esetben a for utasítás után is találunk. Később látni fogjuk, hogy minden esetben, ahol egy csoportba fogunk össze utasításokat, azt a { } zárójelek jelölik. Szerencsés úgy megírni a programot, hogy a } zárójelet azonos bekezdés távolságra írjuk azzal az utasítással, amihez tartozik. A köztük lévő utasításokat pedig egy kicsit beljebb kezdjük. Így jól látható, hogy mik az összetartozó utasítások.

A fenti programban a setup előtt létrehoztuk az „i” nevű változót, típusa „byte” aminek az értéke 0-255 közötti szám lehet. Ennél az utasításnál a mikrovezérlő le fog foglalni a memóriában 1 byte helyet a változónak. Ha „i” változóba beleírunk egy értéket, akkor azt a memóriában fogja tárolni. A setup rész nem változott. A loop-ban viszont megjelent egy új elem. A for (i=0;i<3;i++) egy olyan utasítás rész, aminek hatására a „for”-t követő { } zárójelek közötti utasításokat fogja a program ismételni. A for szó utáni ( ) zárójelek közé írt dolgok határozzák meg, hogy hányszor. Az i=0 azt mondja meg, hogy a for legelső végrehajtása előtt az i értékét 0-ra állítjuk. A ciklust addig fogjuk ismételni, amíg i<3. Minden ciklus után növeljük i értékét eggyel, ezt jelenti az „i++”. Lehetne akár csökkenteni is „i- -”-al, de ez most nem lenne jó. Első ciklusban az i értéke 0. A másodikban 1, aztán 2. Ekkor mindvégig kisebb i értéke mint három. Ha azonban i már 3 lesz a harmadik ciklus végrehajtása után, az itt leírt feltétel nem teljesül, és a for befejezi működését A program a for magjában elhelyezett utasításokat összefogó { } zárójeleket követő utasítással folytatja a működést. Tehát a for-hoz tartozó { } zárójelekben leírt utasításokat 3x hajtjuk végre. Úgy is elérhettük volna a háromszori végrehajtást, hogy a következőt írjuk for (i=10;i<13;i++). Jelen esetben ugyanazt csinálná a program, mert mindegy, hogy 0-tól számolunk 3-ig, vagy 10-től 13-ig.

B) DO..WHILE ciklus: A következő ciklus készítő utasítás a „do”. Nagyjából úgy lehetne magyarul mondani, csináld amíg…! A programnak csak a loop() részét írom le lustaságból, mert a setup nem változott (i változót is létre kellett hozni a setup() előtt, ez is marad):

void loop() {
  i=0;
  do  {
    digitalWrite(13,HIGH);delay(100);
	digitalWrite(13,LOW); delay(100);
	i=i+1;
  } while (i<3);
  delay(1000);
}

Igen nagy a különbség. Először is a ciklus kezdete előtt itt is kezdő értéket kellett adni „i”-nek. Azonban ezt a ciklus utasítástól függetlenül kellett megtenni az i=0; programsorral. Ez rögtön példa arra, hogyan lehet egy változó értékét programon belül bárhol megváltoztatni. Azt lehet sejteni, hogy a do után írt { } zárójelek közötti részt fogja ismételgetni a ciklus. A ciklust addig fogja ismételni, amíg a } zárójel mögé írt while (i<3); utasításban leírt feltétel teljesül. A while jelenti azt, hogy a mögé ( ) zárójelbe írt feltételt kiértékeljük, és ha igaz, akkor újra elindítjuk a ciklust a do { zárójelét követő utasítástól. Jelen esetben a ciklusban az i értékét minden ciklus végrehajtáskor 1-el növeltük. Erre szolgál az i=i+1; programsor. Egyébként írhattunk volna helyette „i++”-t is mint a for paramétereiben, tök ugyanazt jelenti. A mostani leírás univerzálisabb, mert így nem csak egyesével lehet a változó értékét növelni, lehet pl. leírni ezt is: i=i+2; és így kettesévvel számolunk felfelé. Ekkor azonban a while feltételeként ezt kellett volna megadni: while (i<6). Jelen esetben egyesével növelünk, ezért az első ciklus végrehajtáskor i értéke 1, a másodiknál 2, a harmadiknál 3. A harmadik végrehajtás után azonban az i<3 feltétel már nem igaz, ezért a while utáni utasítással folytatjuk a program végrehajtást, azaz várakozunk egy másodpercet. Látható, hogy a do..while ciklus sokkal univerzálisabb. Míg a for egyesével történő számlálásra használható kényelmesen, a do..while ciklus feltétele akármi lehet. Pl. lehet egy ilyen ciklussal addig várakozni, amíg meg nem érkezik egy adat valamilyen távoli berendezéstől. Ezt azonban még nem tudjuk, hogyan is kellene programban megírni.

C) WHILE ciklus. Azt láthattuk, hogy a FOR ciklus tutira végig csinálja az előírt mennyiségű ciklusszámot. A DO..WHILE ciklus egyszer biztosan megcsinálja, mert csak a végén értékeli ki a feltétel teljesülését, amivel eldönti, hogy csinálja-e még egyszer. A WHILE ciklus olyan, hogy már az elején megnézi a feltételt, és egyszer sem csinálja meg, ha nem igaz a feltétel.

void loop() {
  i=0;
  while (i<3) {
    digitalWrite(13,HIGH);delay(100);
    digitalWrite(13,LOW); delay(100);
	i=i+1;
  } 
  delay(1000);
}

A program alig különbözik az előzőtől, csak a ciklus elejére írtuk a while utasítást és a feltételt.

Ezzel megismertük a ciklus szervező utasításokat. Itt az ideje, hogy megtanuljuk elágaztatni a programot egy változó értékétől függően.

3. feladat. Világítás kapcsoló.

Tegyük fel, hogy van egy szobánk a szobának van egy ajtaja, és van ott egy villanykapcsoló. Ez úgy működik, hogy ha felkapcsolod, akkor zárja az áramkört (összeér két fémdarab) és világít a lámpa. Ha pedig lekapcsolod, akkor megszakad az áramkör és elalszik a lámpa. Szeretnénk a világítást modernizálni, és a mechanikus kapcsoló helyett egy érintés érzékelővel vezérelni a világítást. Az érintés érzékelő úgy működik, hogy amikor az újaddal hozzáérsz a felületéhez, egy kivezetésén kiad magából egy meghatározott feszültséget, mondjuk pont 5V-ot. Az érintés érzékelő kivezetését hiába kötjük közvetlenül a lámpára nem fog jól működni, mert a lámpa csak akkor fog világítani, amikor épp hozzáérünk a kapcsoló felületéhez. Helyette úgy kellene működnie, hogy egy hozzáérés felkapcsolja a következő pedig lekapcsolja a világítást. A mikrovezérlőnknek nincs ilyen utasítása ezt biztosan állíthatom, ezért ezt a működést nekünk kell megírnunk. Csináljuk!

Először tervezzük meg a működést, találjunk ki egy működtető algoritmust. Az azonnal látszik, hogy kelleni fog egy változó, ami tárolja, hogy éppen világít a lámpa vagy sem. Ha ugyanis megérintjük a kapcsolót és kimenetén megjelenik az érintést jelző feszültség, akkor el kell döntenünk, hogy le kell kapcsolni a lámpát vagy éppen fel. Ehhez meg tudni kell annak pillanatnyi állapotát vagyis azt hogy éppen világít vagy sem. Próbáljunk felrajzolni egy folyamatot. Nevetségesen egyszerű, de mégis:

Egyszerű feladat, biztosan jól fog működni. Azonban a mikrovezérlőnek nincsenek ilyen utasításai. Én már tudom, és hamarosan Te is kedves olvasó, hogy van olyan utasítása, amivel le tudja kérdezni egyik bemenetére kapcsolt feszültséget. Ha ez a feszültség 0V, akkor a lekérdezésének eredménye „hamis”, ha 5V akkor pedig „igaz”. A digitális kétállapotú világban csak két értéket tartunk számon, az igaz és a hamis értéket, mert egy digitális memória cellának (bit-nek) két állapota lehet. A 0 a hamis, az 1 pedig az igaz. Hogy jó zavaros tudománynak látszódjon a programozás, előszeretettel használják hol ezt, hol azt. Így aztán a 0 értékre a következő jelölésekkel fogsz találkozni: „false” (hamis), 0, LOW. Az 1 értékre pedig a következőkkel: „true” (igaz), 1, HIGH. Persze minden jelölésnek megvan az oka. Szerencsére az Arduino rendszerhez használt program fejlesztő eszközünk (programozási C++ nyelv és úgynevezett fordító program), mindegyiket felismeri.

Szóval ott tartottunk, hogy van utasítás ami lekérdezi egy bemenet állapotát, és visszaadja számunkra az eredményt egy 0 vagy 1 számjegy formájában. Ha 1, akkor 5V-van a bemeneten. Próbáljuk most egy kicsit részletesebben felrajzolni a folyamatábrát:

Ez a folyamatábra már láthatóvá teszi számunkra a megoldást. Meg kell várnunk, míg megérinti valaki az érzékelőt. El kell dönteni, hogy fel vagy le kell kapcsolni a lámpát. És ezen a ponton meg kell várnunk, míg leveszi a kezét az illető az érzékelőről, mert a programnak tudnia kell, hogy mikortól várakozzon újra a megérintés jelzésére. A várakozáshoz fel fogjuk használni az előzőekben tanult do..while ciklust, hiszen addig kell majd a programnak várakoznia, amíg a bemeneten meg nem jelenik az érintőkapcsoló megérintésekor az 5V (HIGH szint). Innem már egyszerű lesz, hiszen csak a változók értékét kell figyelni, amire egy új dolog, az IF utasítás szolgál. Lássuk a programot?

bool lampa_vilagit;         //ha értéke 1 (true), az azt jelzi, hogy a lámpa éppen ég
bool erzekelo_allapot;      //ez egy segéd változó, ebbe olvassuk be az érintés érzékelő
                            //állapotát. Ha éppen megérintették, akkor 1 (true)

void setup() {
  pinMode(13,OUTPUT);      //a világítás vezérlő kimenete  
  digitalWrite(13,LOW);    //a kimenetre kötött led nem világít
  pinMode(11,INPUT);       //ez az érintés érzékelő bemenete
  lampa_vilagit=false;     //induláskor úgy állítjuk be, hogy a lámpa nem ég
}

void loop() {
  do {                                        
    erzekelo_allapot=digitalRead(11);
  } while (!erzekelo_allapot);          //várakozunk (ismételjük a ciklust) amíg megérinti az érzékelőt
  if (lampa_vilagit)   {                //ha lampa_vilagit értéke igaz, akkor le kell kapcsolni a lámpát
    digitalWrite(13,LOW);
    lampa_vilagit=false;
  }
  else  {                               //ha lampa_vilagit értéke hamis, akkor fel kell kapcsolni a lámpát
    digitalWrite(13,HIGH);
    lampa_vilagit=true;
  }
  do {                                        
    erzekelo_allapot=digitalRead(11);
  } while (erzekelo_allapot);           //várakozunk (ismételjük a ciklust) amíg elengedi az érzékelőt
}



Megjelent egy újabb hasznos lehetőség. A programsorokat magyarázó „komment” megjegyzésekkel láthatjuk el.  Ez segíti a program érthetőségét. Kifejezetten hasznos akkor, ha nagy programot írunk. A programsorok így saját magukat magyarázzák. A kommenteket a C++ -ban a „//” jelek mögé írhatjuk. Abban a sorban a „//” mögött már több program utasítás nem lehet, mert a fordító program kommentnek tekinti.

Megjelent egy új változó típus. A „lampa_vilagit” és az „erzekelo_allapot” változónk „bool” típusú lett. A bool típus kétféle értéket vehet fel, igaz vagy hamis. A programban ezt a „true” illetve a „false” szavakkal adhatjuk meg. De akkor sem hibáznánk, ha simán ezt írnánk: lampa_vilagit=0; vagy lampa_vilagit=1;

Néhány változó típus, amit használhatunk (csak a leggyakoribbakat írom le, több is van még):

  • bool – egy bit tárolására, 0, vagy 1 lehet a változó tartalma (illetve true vagy false)
  • byte – egy byte hosszúságú szám (8 bit), 0-255 lehet a tartalma
  • int – két byte hosszúságú szám -32.768-től 32.767-ig tartományban tárolhatunk egész számokat
  • unsigned int – két byte hosszúságú szám 0-tól 65.535-ig tartományban tárolhatunk egész számokat
  • long – négy byte hosszúságú szám -2.147.483.648-tól 2.147438.647-ig tartományban tárolhatunk egész számokat.
  • unsigned long – négy byte hosszúságú szám 0-tól 4.294.967.295-ig tartományban.
  • float – úgynevezett lebegőpontos szám, tizedes értékkel rendelkező számokhoz. A tárolható tartomány -3,4028235E 38-tól 3,4028235E 38-ig, de a tizedes vessző után csak 7 értékes értéket tárolhatunk. Az E 38 az a “tiz a harmincnyolcadikon”-t jelenti. Csak Word-ben tudtam indexet írni, így képként teszem be:

Még egy csomó változó típus létezik, van közöttük szöveg tárolására alkalmas típus is. Részletesebben bármilyen C++ nyelvet ismertető oldalon tájékozódhatsz. Én az https://www.arduino.cc/reference/en/ oldalt használom.

A másik új elem az IF utasítás. Nagyon egyszerű a működése. Az if (lampa_vilagit) sorban megvizsgáljuk a lampa_vilagit változó értékét. Ha ez igaz, akkor az if-et követő { } zárójelek között lévő utasításokat hajtjuk végre. Ha a változó értéke hamis, akkor az else utasítást követő { } zárójelek közötti utasításokat. Vagyis a program elágazott egy feltétel alapján. Az if () feltétel kiértékelésébe bármilyen logikai vizsgálatot beírhatunk. pl.: if (i<3). Ebben az esetben ha i változó értéke 0,1,2 akkor az if-et követő { } zárójelek közötti utasítások lesznek végrehajtva, ha i nagyobb mint három, akkor az else { } zárójelek közötti utasítások. Itt meg kell állnunk egy kicsit. Az „i<3” egy logikai vizsgálat. Elég sokféle lehetőség van a, amivel kiértékelhetjük, összehasonlíthatjuk az adatainkat. Ezeket az általános iskolából jól ismert „<” kacsajeleket összehasonlító operátoroknak nevezzük. Ezeket ismerni kell egy programozónak:

! =  (nem egyenlő) 
<    (kissebb mint) 
<=  (kisebb vagy egyenlő) 
==  (egyenlő) 
>    (nagyobb) 
>= (nagyobb vagy egyenlő) 

Azt hiszem egyiket sem kell magyarázni. Talán a „==” nem világos, miért is kell két egyenlőség jelet írni. Hát azért mert a változók értékadásánál is egyenlőség jelet írunk, és ettől meg kell különböztetni az összehasonlítást. Ha véletlenül ezt írnád: if (i=3) akkor ez hibás. Ezen a ponton a program nem összehasonlítaná i értékét 3-al, hanem i értéke ezen a ponton három lenne (beírná a 3-at i-be). Helyesen: if (i==3), így leírva már jól működik, vagyis ha i értéke éppen három, az if mögötti { } zárójelek közötti utasítások lesznek végrehajtva.

Az összehasonlítások megfogalmazása nem egyszerű. Sok esetben összetett logikai kifejezéseket kell alkalmaznunk. Pl. írhatunk ilyet is: if (i<3 and lampa_vilagit). Sajnos itt már tisztában kell lennünk a logikai kapcsolatokkal a feltétel kiértékelésénél. A logikai és kapcsolat azt jelenti, hogy a kifejezésünk akkor igaz, ha mindkét feltétel igaz. Tehát a példa szerinti kifejezés akkor igaz, ha i<3 és a lámpa is világít. Ez jelen feladatban nem egy értelmes feltétel, csak a példa kedvéért írtam le. Lehet akár három logikai feltételünk is, amit és kapcsolatba hozunk. Pl. if (i<3 and lampa_vilagit and k>100). Néhány logikai kapcsolatot meg kell ismerni, és meg kell érteni, ezek nélkül nem lehet programozni.

ÉS kapcsolat:  C++ programban „and” vagy „&” jelet rajunk a logikai változók vagy kifejezések közé. Pl.: „a & b” ugyanz mint „a and b”. És kapcsolat esetén az eredmény akkor igaz, ha mindkét logikai kifejezés eredménye igaz. Lehetséges esetek: 1 & 1 =1, 0 & 1 =0, 0 & 1=0, 0 & 0=0. A logikai kifejezés alatt valamilyen feltételt értek pl. i<3. Ennek eredménye akkor 1, ha i kisebb mint három.

Vagy kapcsolat: C++ programban „or” vagy „|” jelet rakunk a logikai változók vagy kifejezések közé. Pl.: „a or b”. Én a billentyűzetemen nem találom a függőleges vonalat, ezért mindig „or”-t szoktam írni. Vagy kapcsolat esetén az eredmény akkor igaz, ha bármelyik logikai kifejezés eredménye igaz. Lehetséges esetek: 1 or 1 =1, 0 or 1 =1, 0 or 1=1, 0 or 0=0.

Kizáró vagy kapcsolat: C++ programban „xor” vagy „^” jelet rakunk a logikai változók vagy kifejezések közé. Pl.: „a xor b”. A „^” sem találtam meg a billentyűzetemen, így ha használtam volna valaha, akkor „xor”-t írtam volna. Kizáró vagy kapcsolat esetén az eredmény akkor igaz, ha csak az egyik logikai kifejezés igaz. Ha mindkettő egyszerre hamis vagy igaz, akkor az eredmény hamis. Lehetséges esetk: 1 xor 1 =0, 0 xor 1 =1, 1 xor 0 =1, 0 xor 0 =0.

Tagadás: C++ programban „not” vagy „!” jelet írunk egy logikai kifejezés elé. Ez nem két logikai kifejezés között adja meg a kapcsolatot. Egyszerűen csak megváltoztatja egy logikai kifejezés eredményét. Ha igaz volt, akkor hamis lesz, ha hamis volt, akkor igaz. Pl. !(i<3) kifejezés értéke i=0,1,2 esetén hamis, míg ha i nagyobb mint kettő, akkor igaz. Néha könnyebb így megfogalmazni egy bonyolult összehasonlítást.

Mint látható, az if utasítás keményen megdolgoztatta ismeretanyagunkat. Végre kamatoztathattuk álltalános iskolában megszerzett tudásunkat is. Ez jellemző a programozás tudományára. Nem kell komoly előképzettség az alapokhoz.

De maradjunk a fenti programnál, mert még nem beszéltünk mindenről. A do..while ciklus utasítást már ismerjük, de mit is jelent a digitalRead(11);? Benne van a nevében, hogy beolvassa a 11. kivezetés állapotát. Ha ott 0V feszültséget talál, akkor a visszaadott érték false azaz 0. Ha a bemeneten 5V feszültséget talál, akkor a visszaadott érték true azaz 1. A programban két ciklust is találunk. Az első addig ismétlődik amíg a bemenet állapota false, azaz nincs megérintve az érzékelő. Ezt úgy lehet elérni, hogy a beolvasott érzékelő állapotot tagadjuk, ezért írtuk a while feltételébe, hogy !erzekelo_allapot. Ha épp nincs megérintve az érzékelő, akkor a beolvasott változó érték false, amit ha tagadunk az true, vagyis a ciklus újra lefut. Amint megérintjük az érzékelőt, a változó értéke true, amit ha tagadunk az false, tehát a ciklus nem ismétlődik, és megyünk a következő utasításra. A második ciklusban arra várakozunk, hogy elengedje az érzékelőt, ezért ott nem tettük a változó neve elé a „!” jelet!

A valóságban nem érintő kapcsolót találunk a falon. Létezik, de a villanyszerelő valószínűleg egy egyszerű ajtócsengőnak használt nyomógombot tenne a falra. Azért írtam érintő kapcsolót, mert ha ilyen működéssel létezne, valóban logikai 1-et (5V) adna ki magából ha megérinted. Sajna nem így működnek, rögtön beleépítik azt a programot is amit most megírtunk. Tehát a belsejében van egy mikrovezérlő, de nem dolgoztunk hiába, megértettük, hogyan működik, és mit csinál egy érintőgombos villanykapcsoló.
Mi lenne, ha hagyományos nyomógomb lenne a falon? Ez a program egy hagyományos nyomógombbal a jelen állapotában nem biztos, hogy működni fog. Ennek oka, hogy a nyomógombok nem adnak határozott jelet. Mivel két fémfelület ér egymáshoz, sokszor pattognak egymáson egy kicsit a fémdarabok. Ezért egy nyomógomb lenyomás és elengedés sok jelet ad. A mikrovezérlőnk olyan gyors, hogy ezeket a jelsorozatokat külön külön mind észreveszi és gombnyomásnak fogja érzékelni. Segíthetünk a problémán, ha az első kontaktus után várakozunk egy kicsikét, és csak azután megyünk tovább a programban. A szükséges várakozási idő olyan rövid, hogy az emberi érzékszervek fel sem fogják. Én 50msec (50 ezred másodperc) időt javaslok. Még egy gyakorlati eltérés szokott lenni. A gyakorlati megvalósításokban a nyomógombokat úgy kötik be, hogy nem 5V feszültséget állítanak be a vezérlő bemenetére, hanem pont fordítva, azaz 0V feszültséget, amikor lenyomják a gombot. Itt olvashatsz erről részletesebben, de ahhoz, hogy programozni tanulj, ez most nem igazán fontos. Így a programban a gyakorlatban pont fordítva kell a do .. while() ciklus feltételeket megfogalmazni. Íme egy gyakorlatban is működő program, amit csak azért írtam le külön, mert a valaki veszi a fáradságot és tényleg megírja ezt a programot és kipróbálja, akkor az előző nem működne jól:

bool lampa_vilagit;         //ha értéke 1 (true), az azt jelzi, hogy a lámpa éppen ég
bool erzekelo_allapot;      //ez egy segéd változó, ebbe olvassuk be az érintés érzékelő
                                                //állapotát. Ha éppen megérintették, akkor 1 (true)

void setup() {
  pinMode(13,OUTPUT);      //a világítás vezérlő kimenete  
  digitalWrite(13,LOW);    //a kimenetre kötött led nem világít
  pinMode(11,INPUT);       //ez a nyomógomb bemenete
  lampa_vilagit=false;     //induláskor úgy állítjuk be, hogy a lámpa nem ég
}

void loop() {
  do {                                        
    erzekelo_allapot=digitalRead(11);
  } while (erzekelo_allapot);          //várakozunk (ismételjük a ciklust) amíg megérinti az érzékelőt
  delay(50);                           //50msec időt várunk, ezalatt beállnak a nyomógomb kontaktusai
  if (lampa_vilagit)   {               //ha lampa_vilagit értéke igaz, akkor le kell kapcsolni a lámpát
    digitalWrite(13,LOW);
    lampa_vilagit=false;
  }
  else  {                              //ha lampa_vilagit értéke hamis, akkor fel kell kapcsolni a lámpát
    digitalWrite(13,HIGH);
    lampa_vilagit=true;
  }
  do {                                        
    erzekelo_allapot=digitalRead(11);
  } while (!erzekelo_allapot);         //várakozunk (ismételjük a ciklust) amíg elengedi az érzékelőt
  delay(50);                           //50msec időt várunk, ezalatt beállnak a nyomógomb kontaktusai
}

Ebben a programban megjelent egy új utasítás a delay(). Ez a nevében is jelzi, hogy késleltetni fog. Azaz várakozik egy megadott ideig. Jelen esetben a zárójelek közé írt 50 az pont 50 milisec időt jelent. Ez “emberi” időmértékben 0,05 másodperc.

4. Továbbfejlesztett világítás kapcsoló

Kis szobácskánkban korszerűsítettük a világítást. Lett egy falon perem mögött körben futó „hangulat világítás” kis fényerejű LED szalagból, és lett egy plafonon középen felszerelt szuper fényt adó csillárunk. Szeretnénk megoldani, hogy a felre szerelt kapcsolóval (már nem érintőkapcsolóban gondolkodunk) mindkét világítást bekapcsolhassuk. Azt szeretnénk, ha első megnyomásra bekapcsolna a hangulatvilágítás. Másodikra kikapcsol a hangulatvilágítás, és bekapcsol a csillár, harmadikra pedig kikapcsol a csillár, azaz lekapcsoltuk a világítást. Sejthetjük, hogy az if-el mindhárom állapotot le tudnánk vizsgálni, de itt már nem két állapotunk lesz a világításra (világít, nem világít), hanem három is. Legyen a 0, ha nem világit semmi. Legyen 1, a hangulat világítás működik, és legyen 2 ha a csillár. Két kimenetet is fel fogunk használni, mert két világítást is kell vezérelnünk. Legyen a csillár kimenete a 13., a hangulatvilágításé a 12. kivezetés. A kapcsolót a 11. kivezetésre kötöttük. A programot úgy alakítjuk át, hogy amikor megnyomjuk a kapcsolót, akkor egy változó értékét fogjuk minden megnyomáskor változtatni. Használni fogunk egy új utasítást, ami a háromféle állapot tennivalóit segít elkülöníteni. Ez a SWITCH utasítás. Folyamatábrát most nem rajzolok, már nagyon meg ez nekünk, nincs rá szükség. Ebben a programban már felhasználtam azt a tudást, amit az előbb a nyomógombokról megtanultunk. A nyomógomb megnyomása 0V jelszintet kapcsol a bemenetre, aminek hatására a digitalRead(11); 0-át ad vissza, valamint beépítettem a késleltetést is a nyomógomb kontaktusainak tökéletlen viselkedése miatt!

byte lampa;              //ha értéke 0 nem világít semmi, ha értéke egy akkor a hangulat
                         //világítás működik, ha 2 akkor a csillár világít
bool erzekelo_allapot ;  //ez egy segéd változó, ebbe olvassuk be az érintés érzékelő
                         //állapotát. Ha éppen megérintették, akkor 1 (true)

void setup() {
  pinMode(13,OUTPUT);   //csillár vezérlő kimenete  
  digitalWrite(13,LOW); //a csillár nem világít
  pinMode(12,OUTPUT);   //hangulatvilágítás vezérlő kimenete
  digitalWrite(12,LOW); //a kimenetre kötött hangulatfény nem világít
  pinMode(11,INPUT);    //erre a bemenetre lett kötve a nyomógom
  lampa=0;              //induláskor úgy állítjuk be, hogy a lámpa nem ég
}

void loop() {
  do {                                        
    erzekelo_allapot=digitalRead(11);
  } while (erzekelo_allapot);          //várakozunk (ismételjük a ciklust) amíg megérinti az érzékelőt
  delay(50);                           //50msec időt várunk, ezalatt beállnak a nyomógomb kontaktusai
  lampa=lampa+1;                       //növeljük a lampa változót 1-el
  if (lampa>2) {lampa=0;}              //ha lampa értéke 2 volt, akkor újra 0-ra kell állítani
  switch (lampa) {
    case 0:
      digitalWrite(13,LOW);            //lekapcsoljuk a csillárt
      break;                           //a switch utáni utasításra ugrunk
    case 1:
      digitalWrite(12,HIGH);           //felkapcsoljuk a hangulatvilágítást
      break;                           //a switch utáni utasításra ugrunk
    case 2:
      digitalWrite(12,LOW);            //lekapcsoljuk a hangulatvilágítást
      digitalWrite(13,HIGH);           //felkapcsoljuk a csillárt
      break;                           //a switch utáni utasításra ugrunk
  }
  do {                                        
    erzekelo_allapot=digitalRead(11);
  } while (!erzekelo_allapot);         //várakozunk (ismételjük a ciklust) amíg elengedi az érzékelőt
  delay(50);                           //50msec időt várunk, ezalatt beállnak a nyomógomb kontaktusai
}

Ha jól sejtem már a program kommentjeiből is kideríthető, mit is csinál a switch. Megadjuk a switch () zárójeleiben azt a változót, aminek a lehetséges értékeit figyelni szeretnénk, majd a case utasítással kezdődő sorokban felsoroljuk az összes lehetséges értéket, és megírjuk azt a programrészt, amit az adott érték esetén kell végrehajtani. Ha pl. a lampa változó értéke 1, az azt jelenti, hogy fel kell kapcsolnunk a hangulatvilágítást. Mivel az előző állapot a lampa változó 0 értéke volt, ezért tudjuk, hogy a csillár nem világít, tehát annak lekapcsolásáról nem kell gondoskodni. Annak érdekében, hogy a program első elindításakor is igaz legyen a setup-ban be is állítottuk a lampa változót 0-ra, és mindkét kimenetet LOW-ra.

5. feladat: Használjunk függvényt.

Az előző program már elég nehezen átlátható, ezért szeretnénk olvashatóbbá, könnyebben érthetővé tenni. Ez nagyon fontos akkor, ha a programjaidat barátaidnak is szeretnéd odaadni. Jellemző is a programozó közösségekre, hogy azokat a programokat, melyeket megírtak és jól működnek, megosztják egymás között. Egyik ilyen nagy megosztó webhely a GITHUB. Rengeteg előre megírt hasznos programot találhatsz itt. Ahhoz azonban, hogy egy program könnyen újra felhasználható legyen, nem szerencsés, ha a loop-ba kell minden alkalommal „belebarkácsolni” a másoktól kapott kódokat. Nyilván ez igaz akkor is, ha a saját programjaidat akarod felhasználni különböző programokban. És még valami, amire praktikus megoldást kell találnunk. Ha egy feladatot sok ponton kell végrehajtani egy programban, akkor nagyon nem jó megoldás, ha minden egyes programrészhez bemásolod a program utasítások sorozatát. Növeli ugyanis a program méretét. Arduino világban ez nagyon fontos szempont, mert csak 32kbyte memória áll rendelkezésre. Az asztali PC-k világában ez kevésbé lényeges szempont, de ott is számít valamit. Ráadásul, ha változtatni akarsz egy programrészen és több helyre is bemásoltad, akkor minden részt meg kell keresned és egyenként javítanod.

A problémára a megoldás a függvény. Jelen esetben a hangulatvilágítás és csillár kapcsolgatását végző programrészt szeretnénk barátunknak odaadni, hogy ne kelljen vesződnie a program megírásával. Az univerzálisan használható világítás kapcsoló függvényünknek egyetlen bemenő paramétere lesz, a lampa vezérlés állapot értéke, ami 0,1,2 értéket vehet fel.

Függvényünkkel megírt új programunk így néz ki (a setup részt és a változó deklarációs programrészek nem változtak):

byte lampa;              //ha értéke 0 nem világít semmi, ha értéke egy akkor a hangulat
                         //világítás működik, ha 2 akkor a csillár világít
bool erzekelo_allapot ;  //ez egy segéd változó, ebbe olvassuk be az érintés érzékelő
                         //állapotát. Ha éppen megérintették, akkor 1 (true)

void setup() {
  pinMode(13,OUTPUT);   //csillár vezérlő kimenete  
  digitalWrite(13,LOW); //a csillár nem világít
  pinMode(12,OUTPUT);   //hangulatvilágítás vezérlő kimenete
  digitalWrite(12,LOW); //a kimenetre kötött hangulatfény nem világít
  pinMode(11,INPUT);    //ez az érintés érzékelő erre a bemenetre lett kötve
  lampa=0;              //induláskor úgy állítjuk be, hogy a lámpa nem ég
}

void loop() {
  do {                                        
    erzekelo_allapot=digitalRead(11);
  } while (erzekelo_allapot);          //várakozunk (ismételjük a ciklust) amíg megérinti az érzékelőt
  delay(50);                           //50msec időt várunk, ezalatt beállnak a nyomógomb kontaktusai
  lampa=lampa+1;                       //növeljük a lampa változót 1-el
  if (lampa>2) {lampa=0;}              //ha lampa értéke 2 volt, akkor újra 0-ra kell állítani
  vilagitas_vezerles(lampa);           //meghívjuk a vilagitas_vezerles függvényt, és átadjuk neki 
                                       //a lampa változó értékét
  do {                                        
    erzekelo_allapot=digitalRead(11);
  } while (!erzekelo_allapot);         //várakozunk (ismételjük a ciklust) amíg elengedi az érzékelőt
  delay(50);                           //50msec időt várunk, ezalatt beállnak a nyomógomb kontaktusai
}

/* Ez a függvény a meghívásakor átvesz egy byte típusú változóba egy paramétert
A paraméter értékétűl függően bekapcsolja a hangulat világítást, vagy a csillárt */
void vilagitas_vezerles(byte lampa_allapot) {
  switch (lampa_allapot) {
    case 0:
      digitalWrite(13,LOW);   //lekapcsoljuk a csillárt
      break;                  //a switch utáni utasításra ugrunk
    case 1:
      digitalWrite(12,HIGH);  //felkapcsoljuk a hangulatvilágítást
      break;                  //a switch utáni utasításra ugrunk
    case 2:
      digitalWrite(12,LOW);   //lekapcsoljuk a hangulatvilágítást
      digitalWrite(13,HIGH);  //felkapcsoljuk a csillárt
      break;                  //a switch utáni utasításra ugrunk
  }
}

A függvényünk elé egy több soros kommentet írtam, látható ebből, hogy a /* és */ karakterek közé írt szöveg is lehet komment. Ez jól jön akkor, ha hosszú magyarázatokat kell írni egy egy funkcióhoz.

A loop programban annyi változott, hogy a switch utasítás programsorait egyetlen sorra cseréltük: vilagitas_vezerles(lampa); Ez a függvényhívás! A függvény tovább fog dolgozni a lampa változó értékével, ezért meghíváskor a zárójelekbe írjuk a változó nevét, amit átadunk neki. Egy függvény tetszőleges mennyiségű paramétert át tudna venni, ezeket vesszővel kell elválasztani. Fontos, hogy az átadott paraméter típusával megegyezzen az a változó típusa, amibe átolvassuk a paraméter értékét. Jelen példában a byte típusú „lampa_allapot” változóba olvastuk be az értéket. Ez a változó nem lett a setup rész előtt létrehozva. A program futása közben a függvény fogja létrehozni magának. Amikor a függvény végrehajtotta feladatát, és visszakerül a program futás a függvény hívás utáni sorra. Ez a változó nem is fog tovább létezni. A függvényben a neve akár meg is egyezhet más függvényekben használt változó nevekkel, nem fognak az azonos változónevek egymásra hatással lenni, nem fognak az értékeik összekeveredni.

Ha tehát írsz egy függvényt, annak működését önállóan kipróbálhatod, tesztelheted, tetszőleges helyi, csak abban a függvényben érvényes változókat hozhatsz létre. Ettől lesz egy függvény kényelmesen többször felhasználható. Te magad is felhasználgatod többször is ugyanabban a programban is, és oda is adhatod valakinek. Nem kell azzal foglalkozni, hogy mi van a függvényben csak meg kell hívni és felhasználni a tudását.

Még egy fontos dologról nem beszéltünk. A függvény paramétereket kaphat, amit futásakor felhasznál, és vissza is tud adni egy paramétert. A példában szereplő függvénynek nem kellett visszaadnia semmit, ezért írtuk a neve elé, hogy „void”. Tételezzük fel, hogy egy olyan függvényt kell írnunk, ami kiszámol valamit, és az eredményt adja vissza. Olyan változó típust kell a neve elé írnunk, amit szeretnénk visszakapni. Itt egy egyszerű példa:

eredmeny=szorzas(15);      //ezzel a programsorral hívjuk meg a függvényt

long szorzas(int szam) {   //a függvény átvesz egy int típusú paramétert
    return szam*2;         //a függvény kiszámolja az átvett paraméter kétszeresét, 
                           //és visszaadja az értékét, futása itt befejeződik
}

A függvényt meghívó programrészt nem részleteztem, képzeld el, hogy egyszer csak az utasítások közé leírod a látható programsort. A példában a feladat az, hogy a függvény híváskor átadtunk egy számot (ez lehetne egy változó is), és a függvény a kétszeresét adja vissza. A függvényben az eredmény visszaadását a return utasítás végzi. Több return is lehet egy függvényben, azaz többféle ponton is befejezhetjük a függvény futtatását.

Egy nagyon fontos dolgot érdemes észrevennünk. A setup és a loop is egy függvény. Értéket nem adnak vissza, ezért írunk eléjük „void”-ot. Valójában ezek olyan függvények, amiket a rendszer elinduláskor automatikusan meghív, előbb a setup-ot aztán a loop-ot. Aztán ezek további általunk írt függvényeket hívogathatnak, ha így írtuk meg a programunkat. Az általunk programban leírt utasítások is valójában többnyire függvények. Biztosan nem vagyok szakszerű, amikor utasításokat írok, de ez csak szóhasználat. Egy hozzám hasonló amatőr, ezt is megengedheti magának, lényeg, hogy működjön a program!

6. feladat. Használjunk programkönyvtárakat.

Tegyük fel, hogy egy ébresztőóra programot készítünk. Az ébresztőóra programot úgy írtuk meg, hogy nyomógombok segítségével be lehet állítani egy ébresztési időpontot. Most ezt a programrészt csak elképzeljük, nem fogjuk megíni. Két változóba beírjuk a beállított értéket, legyen az egyik neve „ebresztes_ora”, a masik „ebresztes_perc”. Óra programunk akkor fog ébreszteni, amikor az aktuális idő megegyezik ezekkel az értékekkel. Ezt a programrészt is csak elképzeljük, most nem írjuk meg. Azt már tudjuk, hogy programunk az Arduino mikrovezérlőjében addig fut, amíg van áram a alkásban. Tehát ébresztőóránk csörögni fog ha nem volt éjszaka váratlan áramszünet. Reggel 7-kor megszólal egy csúnya berregő hang, ami azt üzeni, ideje felkelni és elindulni a programozó iskolába, mert még meg kell tanulni az eeprom használatát is. Ha azonban áramszünet volt éjszaka, elveszett az ebresztes_ora és a ebresztes_perc változó tartalma, mert a mikrovezérlő újra indulásakor a program legelején létrehoztuk, de nem állítottuk be újra, mivel épp az igazak álmát aludtuk. Ha pechünk van, ezek értéke akár 0 is lehet, így pont éjfélkor fog felkelteni a rosszul megírt ébresztőóra programunk. Ezt el kell kerülni, így valamit ki kell találni. Tudjuk, hogy az Arduino mikrovezérlőjében nem csak felejtő memória van, olyat is találunk, ami a tápfesz kikapcsolásakor is megőrzi tartalmát. Ez a memória típus az eeprom. Nagyon lassú, ezért tényleg csak arra használható, hogy néha, kis mennyiségű adatot beleírjunk. Olyan programot szeretnénk írni, ami ezt végrehajtja. Azonban a C++ nyelvet nem arra találták ki, hogy mikrovezérlő eeprom-ba lehessen írni vele, ez a feladat nem része a szabvány C++ nyelvnek. De szerencsénk van. Valaki már ezt megcsinálta helyettünk, és felhasználható a programja. Azt, hogy ezt a programot hogyan is lehet megszerezni, most nem lényeges, hiszen minden környezetben és minden mikrovezérlő vagy számítógép típusnál más és más lehet ennek módszere. Egyébként nem túl bonyolult, az Arduino IDE fejlesztő eszköz telepítésekor ezt is automatikusan megkapjuk, csak használni kell. De hogyan? Íme egy példa program, ami eeprom-ban tárol egy értéket. Ugyan ez nem egy óra program, még csak nem is annak részlete, de itt most az elv megértése a lényeg:

#include <EEPROM.H>;    //ezzel a programsorral betöltjük az eeprom programkönyvtárat
byte ertek=0;           //létrehozzuk az ertek nevű változót, és tartalmát rögtön beállítjuk 0-ra

void setup() {
   EEPROM.write(0,123);   //az eeprom memória 0. tárolórekeszébe beírjuk a 123-at
   ertek=EEPROM.read(0);  //kiolvassuk az eeprom 0. rekeszét, és így ertek változóba 
                          //beírjuk a 123-at
}

Kis programocskánk meglehetősen értelmetlen működést valósít meg, de csak egy egyszerű példa. Az eeprom memória 0. rekeszébe, ami egy byte tárolására alkalmas, beleírja a 123-at. Ez csak egy szám, 0 és 255 között bármi lehetett volna. Az eeprom memóriából a következő programsorban rögvest ki is olvassuk a tartalmát, ezt csak azért csináltuk, hogy lássunk példát az adatok kiolvasására is. Az eeprom megőrzi a tartalmát tápfesz lekapcsolása után is, ezért egy értelmes programban valahol beleírunk, és valahol a program teljesen más részén kiolvasunk belőle. Ha közben kikapcsoltuk a tápfeszt és újraindul a mikrovezérlő, az adatot akkor is ki tudjuk olvasni és értéke nem veszett el.

A lényeg a program legeslegelső sora. Az #include <EEPROM.H> programsor azt mondta meg az Arduino IDE nevű fejlesztő eszközünknek, hogy keresse meg a számítógépen telepített programkönyvtárak közül  az EEPROM.H nevű programfilet, és adja hozzá a programunkhoz. Az EEPROM.H egy ugyanolyan jellegű program, mint amit példáinkban írogatunk, csak mások készítették, hogy könnyen és gyorsan tudjunk programozni, ne kelljen mindent magunknak megcsinálni. Bár az általunk írt programban az EEPROM.H tartalma soha nem fog látszani, az abban megírt függvényeket használhatjuk. Mikor a szövegesen megírt forrásprogramunkat lefordítjuk az Arduino által is érthető gépikódra, a fordító program hozzászerkeszti az EEPROM.H tartalmát is, és így egyben tölti rá a mikrovezérlő program memóriájára. Mellesleg jegyzem meg a mikrovezérlő program tároló memóriája is egy eeprom szerű memória. A tápfeszültség lekapcsolása után benne marad a programunk, és fut a következő bekapcsoláskor.

Számtalan program könyvtárat találhatunk. Mielőtt valamilyen programot megírunk érdemes szétnézni az interneten a github-on, mert szinte biztos, hogy valaki már megcsinálta, vagy legalább is csinált hasonlót, amit részben felhasználhatunk és átalakíthatunk.

Kedves olvasó! Örömmel közölhetem veled, ha a fentieket megértetted, akkor már tudsz programozni, csak még sokat kell gyakorolnod. Ne hidd, hogy “csak” ennyi a programozás. A C++ referencia kézikönyve több mit 1000 nyomtatott oldal. Rengeteg tanulnivalód van még. Azonban egyszerű Arduino eszközök barkácsolásához szinte elég, amit eddig tanultál. Menet közben sokszor el fogsz akadni, mert csak a legegyszerűbb eseteket néztük át. Érdemes olvasgatni az arduino.cc weboldalt. Itt mindent megtalálsz, amire szükséged lehet.

És végül egy megjegyzés. Azért szeretek programozni, mert megtanított az élet minden más területén is logikusan gondolkodni, felkészülni az összes eshetőségre. Az életünk pont olyan, mint egy program. Ha nem gondolsz valamire előre, nem gondolod át, hogy mit lehet elrontani, akkor hibásan fog működni! A kulcsmondat: Tervezz előre!!!!

Mennyire volt hasznos amit olvastál?

Kattints egy csillagra az értékeléshez!

Szövegesen is leírhatod véleményedet!