Arduino UNO

prototypovacia doska Arduino UNO, cyklus Sense-Think-Act, mapa pamäte mikrokontroléra ATmega328P, fragmentácia pamäte, skrytá funkcia main()

Záznam z prednášky

Introduction

  • (slide) Pred pár rokmi som si kúpil túto knižku. Je to taká veselá knižka pre najmenších (vekovú kategóriu som splnil už dávno) o robotoch, pomocou ktorej si môžete postaviť vlastných robotov (rozlične inteligentných a z rozličných komponentov). Kúpil som si ju z viacerých dôvodov, ale dva z nich boli: terminológia a ilustrácie, resp. príklady. A v pár veciach mi bola užitočnou aj pri príprave tejto prednášky.

    Titulná stránka knihy Roboti (zdroj)
  • (slide) Tá knižka je vyznavačom tzv. DIY princípu, čo je skratka od Do It Yourself, teda urob si sám. Čiže je pre takých domácich kutilov a hobbystov.

  • (slide) Táto oblasť sa označuje aj podobným termínom Make a teda každý, kto sa takýmto činnostiam venuje je Maker (makač :-)).

  • (slide) A makači majú tiež svoje vlastné stretnutia, tzv. Maker Faire-s, Maker Day-s alebo u nás Namakaný deň.

    Namakaný deň 2019
  • (slide) V tej knižke sa spomína tzv. sense-think-act cycle (robotiMartinus?), na základe ktorého pracuje každý robot. Jednotlivé fázy tohto cyklu sú:

    • sense - reprezentuje robotovo vnímanie, kedy robot prijíma informácie o dianí okolo seba,
    • think - reprezentuje robotovo myslenie, kedy na základe prijatých informácií z okolia sa robot rozhodne, čo urobí, a
    • act - predstavuje robotovu činnosť, ktorou ovplyvní vonkajší svet.
  • Aby sme boli konkrétni, všetky tieto fázy cyklu je možné reprezentovať konkrétnymi prvkami:

    • (slide) Fáza sense je reprezentovaná prvkami, ktoré sa nazývajú senzory. Senzor je súčiastka, ktorá zisťuje, čo sa deje okolo a prevádza inú formu energie na elektrickú (napr. fotočleny, mikrofón, tlačidlo, …)

    • (slide) Fáza think je reprezentovaná počítačom alebo mikrokontrolérom, ktorý je možné programovať. V programe je následne zadefinované správanie robota.

    • (slide) Fáza act je reprezentovaná tzv. akčným členom (v inej literatúre sa môžete stretnúť s termínmi aktér, efektor, vykonávateľ). Akčný člen je súčiastka, ktorá prevádza elektrickú energiu na inú formu energie (napr. dióda, reproduktor, motor, …)

Airplane Example

  • (slide) Tento cyklus sa však netýka výlučne len robotov, ale je možné sa s ním stretnúť aj v iných oblastiach. My si princíp fungovania tohto cyklu ukážeme na príklade letiaceho lietadla. Lietadlo síce nevyzerá ako robot, ale je to tiež systém pracujúci v reálnom čase.

    Airplane Example (zdroj)
  • Lietadlo obsahuje počítač, ktorý vyhodnocuje údaje z prebiehajúceho letu. Na to ich však potrebuje zbierať. Napr. potrebuje vedieť, ako vysoko letí, ako rýchlo letí, kde sa práve nachádza. Všetky tieto informácie lietadlo potrebuje na to, aby sa vedelo rozhodnúť, čo urobí, resp. kde by sa malo nachádzať v najbližšom momente. Na základe zozbieraných údajov teda upraví výšku pomocou krídiel, spomalí alebo zrýchli pomocou motorov.

  • Ak by sme teda mali vztiahnuť tento systém reálneho času na náš sense-think-act cyklus, tak:

    • zbieranie informácií o aktuálnom stave a polohe lietadla, patrí do fázy sense,

    • vyhodnotenie informácií a naplánovanie ďalšieho kroku lietadla, patrí do fázy think, a

    • zníženie, resp. zvýšenie výkonu motorov lietadla, upravenie polohy krídiel, patrí do fázy act.

Introduction to Microcontrollers

  • (slide) Na našich najbližších stretnutiach a rovnako aj vaše posledné zadanie bude venované programovaniu mikrokontrolérov, ktoré sú hlavou sense-think-act cyklu. Mikrokontroléry sú totiž jednoduché počítače v jednom púzdre (v jednom čipe) a je možné ich programovať.

    A microcontroller is a special purpose computer on a single chip.

  • (slide) Ak by sme mali mikrokontrolér s niečim porovnať, tak s mikroprocesorom. Najmä kôli prevedeniu, nakoľko mikroprocesor a mikrokontrolér vyzerajú veľmi podobne - sú v jednom púzdre. Dokonca aj jeden typ mikrokontroléra môžete kúpiť v rozličných púzdrach (viď obrázok).

    Rozličné púzdra ATmega328P (zdroj)
  • Tie najdôležitejšie rozdiely medzi mikrokontrolérom a mikroprocesorom zhrňuje nasledujúca tabuľka:

    mikroprocesor mikrokontrolér
    je zariadenie na všeobecné použitie špecializované zariadenie
    označuje sa ako jednočipový počítač
    neobsahuje I/O porty, pamäť, časovače a pod. obsahuje RAM, ROM, sériové a pralelné rozhranie, časovače, … všetko na jednom čipe
    používajú sa ako CPU v mikropočítačoch používajú sa v jednoduchých/jednoúčelových zariadeniach
    jeho dizajn/návrh je komplexný jeho dizajn/návrh je jednoduchý
    je drahý (desiatky/stovky Eur) je lacný (jednotky Eur)
    energeticky náročný (desiatky watov) low power zariadenia (jednotky Watov)
    majú Von Neumannovskú architektúru majú Harvardskú architektúru
    vysoká rýchlosť (GHz) malá rýchlosť (MHz)
    Raspberry Pi Arduino, ESP32, micro:bit, Rasbperry Pi Pico

Arduino UNO

  • (slide) My sa budeme učiť programovať mikrokontrolér Arduino UNO. Tento mikrokontrolér bol navrhnutý a vyvinutý pre potreby vzdelávania, takže je ideálny kandidát na úvod do tejto problematiky.

    Arduino Uno (zdroj)
  • Pozrime sa bližšie na to, čo všetko sa na doske nachádza:

    • napájací konektor - Arduino je možné napájať z externého zdroja v rozsahu 7-12V
    • stabilizátory napätia
    • USB port - používa sa na napájanie, nahrávanie vášho programu do Arduina (programovanie) a na komunikáciu pomocou sériového portu
    • reset tlačidlo - resetuje mikrokontrolér (reštartne program, ktorý je v ňom nahratý)
    • TX a RX LED diódy - tieto LED-ky indikujú komunikáciu medzi Arduinom a počítačom, čo je možné vidieť napr. pri nahrávaní programu do Arduina alebo pri komunikovaní po sériovej linke
    • mikrokontrolér - samotný mikrokontrolér, ktorý je srdcom Arduina (ATmega)
    • LED-ka napájania - indikuje, že Arduino je napájané
    • digitálne piny - ku ktorým je možné pripojiť senzory a akčné členy, používa sa 5V logika
    • analógové piny - ku ktorým je možné pripojiť senzory a akčné členy
    • špeciálne piny - napr. GND a 5V, PWM a pod
    • LED-ka pripojená k pin-u č. 13 - programovateľná LED-ka pripojená k pin-u č. 13
    • externý oscilátor - 16MHz

Arduino Programming

  • (slide) Na písanie programov budeme používať nástroj Arduino IDE, ktoré je možné stiahnuť z domovskej stránky www.arduino.cc.

    Arduino IDE
  • Samozrejme existuje mnoho iných editorov a prostredí, ktoré je možné použiť na vývoj programov pre Arduino. Najčastejšie sa jedná o rozšírenia existujúceho editora alebo vývojového prostredia o možnosť vyvíjať programy aj pre Arduino, ako Atom, Eclipse, CLion alebo Visual Studio Code. Tým je možné využiť práve silu existujúcich IDE.

  • A samozrejme je možné vyvíjať aj z príkazového riadku alebo rozličnými spôsobmi aj pomocou editora Vim.

  • (slide) Za samostatnú zmienku určite stojí PlatformIO. Obecne sa jedná o rozšírenie pre rozličné editory, ale ponúka výrazne viac možností ako je štandardné Arduino IDE. PlatformIO je ideálny nástroj, ak to myslíte s vývojom pre embedded zariadenia naozaj vážne.

  • (slide) Programy pre Arduino sa píšu v jazyku C++, ktorý je rozšírením jazyka C. A keďže je Arduino IDE založené na platforme Wiring, programy v ňom písané sa volajú sketch-e.

Setup your IDE

  • Pozrime sa teda na prostredie, ktoré budeme používať na písanie programov pre Arduino trošku bližšie.

  • Najpodstatnejšia vec, ktorú musíte po spustení urobiť alebo aspoň skontrolovať je, pre akú dosku, resp. pre aký mikrokontrolér je vaše IDE nastavené a ku akému portu je táto doska (mikrokontrolér) pripojený. To si môžete všimnúť vpravo dole v rozhraní IDE.

  • Ak však chcete tieto nastavenia zmeniť, tak:

    • choďte do menu Tools > Board, ak chcete zmeniť prednastavenú dosku, alebo

    • choďte do menu Tools > Port, ak chcete zmeniť prednastavený port.

  • Ak by ste totiž mali vybratý nesprávny port, k nahratiu programu na dosku nedôjde, ale proces nahrávania skončí s chybou. Ak by ste však mali nesprávne vybratú dosku, tak síce k nahratiu dôjde, ale program sa nemusí (ale môže) spustiť (samotné dosky/mikrokontroléry sa navzájom líšia).

  • (slide) Začneme s programom Blink, ktorý je akýmsi Hello world-om v hardvérovom svete.

  • (slide) Projekt Blink rozbliká LED diódu, ktorá sa nachádza priamo na doske, takže netreba nič zapájať navyše.

Blink Animation (zdroj)
  • Projekt Blink sa nachádza v ukážkových príkladoch priamo v Arduino IDE. Nájdete ho v menu File > Examples. Nahráme ho do Arduina. Po nahratí sa program automaticky spustí a LED dióda na doske začne blikať LED dióda.

  • Ak stlačíme tlačidlo RESET, program sa spustí znova od začiatku.

Structure of the Sketch

  • Pozrime sa teraz bližšie na samotný kód:

    // the setup function runs once when you press reset
    // or power the board
    void setup() {
      // initialize digital pin LED_BUILTIN as an output.
      pinMode(LED_BUILTIN, OUTPUT);
    }
    
    // the loop function runs over and over again forever
    void loop() {
        // turn the LED on (HIGH is the voltage level)
        digitalWrite(LED_BUILTIN, HIGH);
        // wait for a second
        delay(1000);
        // turn the LED off by making the voltage LOW
        digitalWrite(LED_BUILTIN, LOW);
        // wait for a second
        delay(1000);
    }
  • (slide) Spomínal som, že sketch-e sú vlastne programy napísané v jazyku C++. To, čo však v sketch-i chýba, je hlavná funkcia main(). Miesto toho má každý program pre Arduino nasledovnú štruktúru:

    void setup(){
        // initialization function
        // runs once
    }
    
    void loop(){
        // main loop
        // runs continuously
    }

    kde význam týchto funkcií je nasledovný:

    • setup() - funkcia slúži na inicializáciu pinov a spúšťa sa iba raz (na začiatku)

    • loop() - funkcia predstavuje hlavnú aplikačnú slučku (tzv. superloop) a po inicializácii funkciou setup() sa táto funkcia spúšťa neustále

  • (slide) Ak by sme sa teda pokúsili prepísať správanie programu napísaného pre Arduino pomocou uvedených dvoch funkcií priamo v jazyku C, program by mohol vyzerať nasledovne:

    int main(){
       // runs once
       setup();
    
       // runs continuosly
       for(;;){
           loop();
       }
    }
  • Samozrejme sa nami vytvorený sketch len pripojí k výslednému programu, ktorý funkciu main() obsahuje. Tá vyzerá v skutočnosti trošku ináč, ale nie veľmi výrazne. Ak si teda necháte vo svojej inštalácii Arduina vyhľadať súbor main.cpp:

    $ cd arduino-x.y.z
    $ find . -name main.cpp
    ./hardware/arduino/avr/cores/arduino/main.cpp

    uvidíte toto:

    int main(void)
    {
        init();
    
        initVariant();
    
    #if defined(USBCON)
        USBDevice.attach();
    #endif
    
        setup();
    
        for (;;) {
            loop();
            if (serialEventRun) serialEventRun();
        }
    
        return 0;
    }
  • Takto organizovaný kód však núti programátora používať globálne premenné, o ktorých sme hovorili, že ich nadmerné používanie vedie k zlým návykom. V prípade mikrokontrolérov je situácia ešte horšia, nakoľko mikrokontroléry sú výrazne obmedzené veľkosťou pamäte. Pozrime sa teda, ako je to s pamäťou v prípade mikrokontroléra ATmega328P.

Arduino and Memory

  • (slide) Keď sa program nahrá do mikrokontroléra, v spodnej časti IDE sa zobrazia informácie o spotrebe pamäte aktuálnym sketch-om:

    Sketch uses 924 bytes (2%) of program storage space. Maximum
    is 32256 bytes.
    Global variables use 9 bytes (0%) of dynamic memory, leaving
    2039 bytes for local variables. Maximum is 2048 bytes.
  • Všimnite si, koľko výsledný kód zaberá - 924B z 32256B. A všimnite si koľko pamäte máme k dispozícii - 9B z 2047B. Prečo sú tam dve maximá? Nemali by sme mať k dispozícii len jednu pamäť pre program a dáta?

  • (slide) Väčšina moderných mikrokontrolérov je založených na Harvadskej architektúre. To znamená, že časť pre program (označovaná ako Flash) je oddelená od pamäte pre dáta (označovaná ako SRAM). Pamäť mikrokontrolérov je obyčajne malá a ľahko sa môže stať, že aj malý program dokáže naplniť pamäť dát veľmi rýchlo.

    System Architecture (zdroj)
  • (slide) Pozrime sa teda na to, ako je na tom s pamäťou mikrokontrolér ATmega328P. Tento mikrokontrolér má tri typy pamäte:

    The Memory Map of an ATmega328P (zdroj)
    • Flash - Pamäť pre uloženíe preloženého programu. Má veľkosť 32k a používa 16b adresnú zbernicu. Je rozdelená na dve časti:

      1. Boot Loader Section, ktorá zaberá 512B, a
      2. Application Program Section, kde sa nachádza samotný program

      Adresná zbernica Flash pamäte je iná ako pamäť SRAM (má iný adresný priestor). Jej životnosť predstavuje minimálne 10k cyklov.

    • SRAM (Static Random Access Memory) - Dočasná pamäť, ktorá bude vymazaná, pri reštartovaní mikrokontroléra. Okrem samotnej pamäte pre bežiaci program o veľkosti 2k obsahuje aj všeobecné a špeciálne registre.

    • EEPROM - Elektricky prepisovateľná trvalá pamäť, ktorej údaje sú dostupné aj po reštartovaní mikrokontroléra. Môžu byť do nej zapisované len 8b hodnoty. Jej veľkosť je 1k. Jej životnosť je minimálne 100k cyklov.

      Používa sa pomocou “štandardnej” knižnice EEPROM.h alebo pomocou externej knižnice EEPROMEx, pomocou ktorej je možné zapisovať aj iné dátové typy ako byty.

  • (slide) Pre náš bežiaci program máme teda k dispozícii 2k dát v adresnom rozsahu od adresy 0x100 až po adresu 0x8ff. Aby to nebolo všetko, aj táto pamäť má konkrétnu štruktúru:

    SRAM Memory Usage (zdroj)
    • .data - Inicializovaný dátový segment, nazývaný aj jednoducho Dátový segment. Obsahuje inicializované globálne a statické premenné v kóde.

    • .bss - Neinicializované globálne a statické premenné v programe.

    • heap - Časť pamäte, kde sa ukladajú dynamicky alokované údaje pomocou funkcií ako malloc() alebo calloc() a je ich možné uvolniť pomocou volania funkcie free().

    • stack - Časť pamäte známa ako zásobník. Ukladá sa do nej aktuálny stav registrov pri volaní funkcií, aby procesor vedel, do akého stavu sa vrátiť, keď sa funkcia ukončí. Rovnako sa tu nachádzajú lokálne premenné funkcií.

  • Obecný problém je, že pamäť heap a stack sa podľa potreby a v rámci veľkosti dynamicky zväčšujú a zmenšujú. To, čo sa však dostane do pamätí .data a .bss, je k dispozícii po celý čas behu programu. Je teda veľmi jednoduché zapratať pamäť napr. dostatočne veľkým poľom prvkov, ktoré bude existovať ako globálna premenná.

Code Example

  • Použitie zásobníku a hromady je viacmenej jasné. Zaujímavé je ale to, kedy sa využívajú segmenty .data a .bss. Modifikujem teda ukážku Blink, aby sme ho videli:

    // uninitialized (global = 0) global variable, 2B
    int global;
    // initialized global variable, 2B
    int counter = -1;
    
    void setup() {
        pinMode(LED_BUILTIN, OUTPUT);
        counter = 4;
    }
    
    void loop() {
        for(global = 0; global < counter; global++){
            digitalWrite(LED_BUILTIN, HIGH);
            delay(1000);
            digitalWrite(LED_BUILTIN, LOW);
            delay(1000);
        }
    }
  • Obsadené miesto v príslušnom segmente je možné vidieť postupne pomocou príkazu avr-size:

    $ avr-size /path/to/Blink.ino.elf
       text    data     bss     dec     hex filename
       1002       2      11    1015     3f7 /path/to/Blink.ino.elf

Memory Issues

  • (slide) Aj keď sme sa v rámci predmetu (a hlavne zadaní) dostatočne venovali dynamickej alokácii pamäte, jej použitie v prípade embeded zariadení nemusí byť ideálne. Veľmi ľahko sa totiž môže stať, že dôjde k fragmentácii pamäte.

  • (slide) Čo je to fragmentácia pamäte? Jednoducho sa dá fragmentácia opísať ako diery v pamäti, ktoré vznikli uvoľnením už nepotrebných blokov, ale ktoré sa už nedajú vďaka svojej veľkosti znova použiť.

    Memory Fragmentation (zdroj)
  • (slides) Je dobré si uvedomiť, že k dispozícii sú len 2k pamäte, ktorú je možné využiť. Aby sme využili pamäť čo najefektívnejšie a predišli tak problémom s pamäťou, resp. s fragmentáciou pamäte, uvediem niekoľko odporúčaní:

    1. ak je to možné, vždy použite zásobník miesto hromady - pamäť je súvislá, nedochádza k fragmentácii, vždy je pravidelne uvoľňovaná, keď program vyskočí z funkcie, jej správa nás nič nestojí.

    2. vyhnite sa používaniu globálnych a statických premenných - údaje uložené v .bss a .data nie sú nikdy uvoľňované počas životného cyklu programu

    3. snažte sa, aby reťazce boli čo najkratšie - každý znak v pamäti zaberá 1B, čo znamená, že dokopy viete napísať reťazec o dĺžke max. 2048 znakov

    4. snažte sa udržiavať minimálnu veľkosť polí - ak neskôr zistíte, že potrebujete zmeniť veľkosť, proste ju upravte a znovu preložte program

    5. používajte vhodné údajové typy - pri programovaní často zvykneme používať priveľké údajové typy pre hodnoty, ktoré potrebujeme uchovávať; použitím menších typov šetríme pamäť

  • (slide) Štepán Bechynský, jeden z popularizátorov a autorov projektu Arduino 101, na jednom zo svojich workshopov povedal, že “Programovanie pre Arduino môže byť aj celkom dobrým cvičením písania efektívneho kódu pre skúsených programátorov.” A má pravdu ;)

Sketch - The Different Way

  • Pri programovaní mikrokontrolérov sa sám častokrát stretávam s ospravedlňovaním, že “Tak to robia všetci.”, ktorým ľudia ospravedlňujú porušenie niektorých z vyššie uvedených odporúčaní (najčastejšie to o globálnych premenných). Nedajte sa strhnúť davom a najskôr pouvažujte - dobré princípy vám môžu v budúcnosti zachrániť život ;)

  • Aby sme sa týmto problémom vyhli v čo najväčšej miere a aspoň z časti sa im vyhli, budeme v ďalších prednáškach písať sketch-e s pomocou hlavnej funkcie main() a teda nie len pomocou predpísaných funkcií setup() a loop(). Týmto prístupom znížime cenu výsledného programu z pohľadu spotrebovanej pamäte.

  • Do tejto podoby môžeme hneď prepísať aj príklad Blink. Prepíšem teda ten, ktorý zdražel pomocou dvoch globálnych premenných na cenu 13B:

    int main(){
        // needed from original main() function
        init();
    
        // initialization, runs once
        int global;
        int counter = -1;
        pinMode(LED_BUILTIN, OUTPUT);
        counter = 4;
    
        // main loop, runs continuously
        for(;;){
            for(global = 0; global < counter; global++){
                digitalWrite(LED_BUILTIN, HIGH);
                delay(1000);
                digitalWrite(LED_BUILTIN, LOW);
                delay(1000);
            }
        }
    }
  • Ak tento sketch preložíme, uvidíme, že cena poklesla z 13B na pôvodných 9B, čo je cena zrejme Arduino SDK:

    Sketch uses 920 bytes (2%) of program storage space. Maximum
    is 32256 bytes.
    Global variables use 9 bytes (0%) of dynamic memory, leaving
    2039 bytes for local variables. Maximum is 2048 bytes.

.kkrieger

  • (slide) Na záver sa už len pozrieme na to, čo všetko sa dá vopchať do necelých 95kB kódu - pozrieme sa na projekt .kkrieger.

  • .kkrieger (from Krieger, German for warrior) is a first-person shooter video game created by German demogroup .theprodukkt (a former subdivision of Farbrausch) which won first place in the 96k game competition at Breakpoint in April 2004. The game remains a beta version as of 2012

  • stiahnutie na stránke archívu webu (direct download)