Prispevki Arduino

setup() in loop() – osnovna zanka vsake Arduino skice

Vsak začetek v svetu Arduina se začne z dvema praznima funkcijama, ki ju zagledate takoj, ko odprete razvojno okolje: setup() in loop(). Čeprav na prvi pogled izgledata preprosto, sta ti dve funkciji ogrodje, na katerem stoji celoten program (v svetu Arduina mu pravimo skica). Razumevanje njunega medsebojnega delovanja je prvi in najpomembnejši korak k pisanju stabilne in odzivne kode. Mentalni model, ki si ga morate zapomniti, je preprost: vklopi → pripravi → ponavljaj v neskončnost.

Kaj je setup()

Funkcija setup() je prvi del kode, ki ga mikrokrmilnik izvede po tem, ko dobi napajanje ali ko pritisnete gumb za reset. Njen namen je jasen že iz imena – namenjena je “nastavitvi” oziroma pripravi strojne opreme in programskih parametrov. Ključna lastnost te funkcije je, da se izvede natanko enkrat. Ko krmilnik pride do konca kode znotraj zavihkov funkcije setup(), se vanjo ne vrne več, dokler ne pride do ponovnega zagona sistema.

Tipične stvari v setup(): nastavitve pinov in zagon periferije

V tej funkciji “pripravimo teren”. Brez pravilnih nastavitev v setup() mikrokrmilnik ne bo vedel, kako naj ravna s svojimi fizičnimi priključki. Tipični opravki vključujejo:

  • Nastavitve pinov: S funkcijo pinMode() določimo, ali bo določen pin služil kot vhod (za senzor) ali izhod (za LED, rele).
  • Serijska komunikacija: Z ukazom Serial.begin(9600) odpremo komunikacijski kanal z računalnikom, kar nam omogoča spremljanje delovanja programa.
  • Zagon knjižnic: Če uporabljate zaslon (LCD/OLED), senzorje vlage ali WiFi module, boste tukaj poklicali njihove inicializacijske ukaze (npr. lcd.begin() ali WiFi.begin()).

Kaj pomeni reset in zakaj se setup() zažene znova

Reset je v svetu mikrokrmilnikov kot ponovni zagon računalnika. Do njega lahko pride na več načinov: s fizičnim pritiskom na rdeč gumb na ploščici, s preklopom napajanja, ali pa programsko, ko krmilnik zazna kritično napako (watchdog reset). Ob vsakem takem dogodku se procesor vrne na začetek prve vrstice v setup(). To je varovalo, ki zagotavlja, da se sistem ob vsakem zagonu postavi v točno določeno, znano stanje, ne glede na to, kaj se je dogajalo pred resetom.

Kaj je loop()

Ko je priprava končana, krmilnik vstopi v funkcijo loop(). To je motor vašega projekta. Za razliko od setup(), se ta funkcija izvaja v neskončnost. Ko krmilnik doseže zadnji ukaz v loop(), se takoj in brez premora vrne na prvo vrstico te funkcije in postopek ponovi. Zaradi te narave je loop() idealen kraj za stalno spremljanje okolice (npr. preverjanje, če je kdo pritisnil tipko) in takojšnje odzivanje nanjo.

Neskončnost v praksi: kako hitro se loop() ponavlja

Začetniki so pogosto presenečeni nad hitrostjo delovanja. Če je vaša koda preprosta, se lahko loop() ponovi več deset tisočkrat na sekundo. Ta hitrost je ključna za odzivnost. Če želite, da se luč prižge v istem trenutku, ko pritisnete tipko, mora program to tipko “vprašati” po stanju čim pogosteje. Vsako nepotrebno ustavljanje v loop() funkciji zmanjšuje frekvenco vzorčenja in naredi napravo “leno”.

Minimalen primer skice in kako ga prebrati

Poglejmo si klasiko – utripanje LED diode (Blink):

void setup() {
  pinMode(13, OUTPUT); // Pripravi pin 13 za izhod
}

void loop() {
  digitalWrite(13, HIGH); // Vklopi LED
  delay(1000);            // Počakaj 1 sekundo
  digitalWrite(13, LOW);  // Izklopi LED
  delay(1000);            // Počakaj 1 sekundo
}

Razlaga: Krmilnik v setup() nastavi pin 13 kot izhod. Nato skoči v loop(), prižge luč, čaka, ugasne luč, čaka… in ko pride do konca, se takoj vrne na vrh loop(), kjer ponovno prižge luč. To ustvarja neskončno utripanje.

Zakaj v setup() ne spada glavna logika

Predstavljajte si, da bi ukaz za branje senzorja temperature postavili v setup(). Program bi ob vklopu izmeril temperaturo (npr. 22 °C) in to bi bilo vse. Tudi če bi sobo kasneje segreli na 50 °C, krmilnik tega ne bi vedel, ker kode za merjenje ne bi več izvajal. Glavna logika, ki mora reagirati na spremembe v času, mora biti vedno v loop(). V setup() spadajo le nastavitve, ki se med delovanjem ne spreminjajo.

Kako delay() vpliva na loop() in odzivnost

Ukaz delay() je največji sovražnik odzivnih sistemov. Ko pokličete delay(5000), mikrokrmilnik dobesedno “zamrzne” za 5 sekund. V tem času ne more brati tipk, ne more sprejemati podatkov preko interneta in ne more krmiliti motorjev. Če imate v programu delay(), se vaša neskončna zanka upočasni na hitrost polža. Za učenje je delay() uporaben, za resne projekte (npr. dron ali CNC stroj) pa je popolnoma neprimeren, saj bi povzročil izgubo nadzora.

Boljši pristop kot delay(): časovniki z millis()

Namesto da krmilniku ukažemo, naj “ne dela nič”, uporabimo funkcijo millis(). Ta funkcija nam pove, koliko milisekund je preteklo od zagona Arduina. To je kot štoparica. Namesto čakanja v vrsti (delay), krmilnik raje vsak krog v loop() pogleda na štoparico in se vpraša: “Ali je že minilo 1000 ms od zadnjega utripa?”. Če je odgovor ne, takoj nadaljuje z drugim delom kode. Tako lahko loop() dela več stvari hkrati (npr. utripa z LED in hkrati bere senzor vlage).

V praksi to izgleda takole — LED utripa vsako sekundo, loop() pa teče v celoti brez vsakega zaustavljanja:

unsigned long zadnjiUtrip = 0;
const long interval = 1000; // ms

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

void loop() {
  unsigned long zdaj = millis();

  if (zdaj - zadnjiUtrip >= interval) {
    zadnjiUtrip = zdaj;
    int stanje = digitalRead(13);
    digitalWrite(13, !stanje); // preklopi LED
  }
  // preostala koda tece nemoteno naprej
}

Vzorčni vzorec: beri → odloči → ukrepaj v loop()

Da bi vaša koda ostala pregledna, jo v loop() vedno organizirajte v tri korake:
1. Beri (Vhod): Preberite stanje vseh senzorjev in tipk.
2. Odloči (Procesiranje): Izračunajte, kaj se mora zgoditi (npr. temperatura > 30?).
3. Ukrepaj (Izhod): Nastavite izhode (vklopi ventilator, zapiši na zaslon).

Globalne spremenljivke: kako si loop() zapomni stanje

Ker se loop() nenehno začenja znova, se vse lokalne spremenljivke znotraj njega ob koncu zanke pobrišejo. Če želite, da si krmilnik zapomni, kolikokrat je nekdo pritisnil tipko, morate uporabiti globalne spremenljivke, ki jih definirate povsem na vrhu skice (izven obeh funkcij). Te spremenljivke živijo ves čas delovanja programa in omogočajo, da loop() prenaša informacije iz ene ponovitve v drugo.

Funkcije po meri: kako kodo razdeliš na ukaze

Ko projekt raste, postane loop() nepregleden. Rešitev so funkcije po meri. Namesto 100 vrstic kode v loop(), lahko napišete svojo strukturo, kjer kličete funkcije, kot so preberiSenzorje() ali osveziZaslon(). Tako koda postane berljiva kot knjiga, napake pa boste našli bistveno hitreje.

Kaj se dogaja pod pokrovom: main() in Arduino core

Morda se sprašujete, kdo sploh kliče setup() in loop()? V ozadju standardnega jezika C++, na katerem temelji Arduino, vedno obstaja funkcija main(). Arduino okolje to funkcijo skrije pred vami, da vam olajša delo. V resnici se v ozadju zgodi tole: pokliče se setup(), nato pa se vstopi v skrito while(true) zanko, ki neprestano kliče vaš loop().

Pogoste napake začetnikov pri setup() in loop()

Če se vaš projekt obnaša čudno, preverite te tri najpogostejše napake:
1. Pozabljen pinMode v setup(): Če pinu ne poveva, da je OUTPUT, bo LED svetila zelo šibko.
2. Predolg delay(): Tipka se zdi pokvarjena, a v resnici krmilnik le “spi” in ne vidi pritiska.
3. Logika v setup() namesto v loop(): Projekt dela samo prvih nekaj sekund po vklopu, nato pa “umre”.

Primerjava: Arduino loop vs. program na računalniku

Programi na vašem računalniku se običajno izvedejo in končajo. Mikrokrmilnik pa deluje po principu vgrajenega sistema (embedded system). Njegov program se ne sme nikoli končati. Njegov namen je, da živi v napravi in 24 ur na dan čaka na dogodke. Zato je koncept neskončne zanke loop() v elektroniki naravno stanje.

Praktični mini projekt: tipka, LED in debouncing v loop()

Najboljši način učenja je praktični primer. Sestavite kodo, ki bo v loop() pravilno brala tipko in prižigala LED. Uporabite logiko za filtriranje odbojev (debouncing), kjer krmilnik nenehno preverja stanje tipke in si s pomočjo globalne spremenljivke zapomni čas zadnjega pritiska, ne da bi pri tem ustavil izvajanje ostale kode.

Industrijski vidik: zakaj je neblokirajoč loop() pomemben

V industriji (B2B) je zanesljivost na prvem mestu. Če se loop() zaradi slabo napisane kode ali dolgega čakanja na senzor ustavi, se lahko aktivira watchdog timer, ki bo krmilnik resetiral. Zato inženirji nikoli ne uporabljajo delay(). Dobro strukturiran in hiter loop() je znak profesionalno napisane programske opreme, ki bo varno delovala več let brez prekinitev.

Zaključek

setup() in loop() sta srce in duša Arduina. Prva postavi pravila igre, druga pa to igro igra v neskončnost. Če boste svojo kodo gradili okoli hitrega in neblokirajočega loop()-a, boste ustvarili naprave, ki so odzivne, zanesljive in pripravljene na zahtevnejše naloge. Naslednjič, ko boste videli prazni funkciji, ju ne glejte le kot oklepaje, ampak kot priložnost, da svojemu projektu vdihnete pravo življenje.