Doslej smo se na Arduina priklapljali preproste stvari, v glavnem diode, tipke in limone. Zares zabavno postane, ko priključimo kaj bolj zahtevnega, na primer različne senzorje, motorje, zaslone... Vse to lahko za drobiž kupimo pri Kitajcih (obeta pa se zakonodaja, katere namen bo "zaščita domačih trgovcev" in bo podeseterila ceno).

Nakup opreme in nameščanje knjižnice

To gre tako. Recimo, da želim kupiti zaslon za prikazovanje številk. Ne čisto pravi LCD, temveč le takšnega, kot v starih kalkulatorjih. Grem v kitajsko trgovino, na primer AliExpress in vtipkam kaj v slogu LED display Arduino. Dobim kupe zadetkov, tipična cena je okrog 70 centov.

Naslednji korak je preveriti, ali bo to res delovalo z Arduinom. Naprave, kot so zasloni, bodo imeli običajno določen čip, ki jih nadzira. In teh čipov je navadno le nekaj različnih vrst. Pri zaslonih, ki sem jih kupoval, je to TM1637. Zasloni, ki uporabljamo TM1637, so sicer lahko različni, ker gre za isti čip pa bo zanje skoraj gotovo uporabna ista knjižnica. Poleg teh so pogosto takšni, ki imajo MAX7219; ti so še bolj imenitni, vendar nismo delali z njimi.

Arduino se bo moral na nek način pogovarjati s tem čipom. Ker je odkrivanje "tega načina" lahko kar hackersko delo in ker se nam tega verjetno ne programira (prebrati in razumeti bi morali dokumentacijo čipa, naletel pa sem že na takšne, kjer bi moral prebrati in razumeti takšno dokumentacijo), moramo preveriti, ali je to kdo že prijazno naredil namesto nas. Ugotoviti moramo torej, ali obstaja že pripravljena knjižnica funkcij za Arduino, s katero lahko upravljamo zaslone, ki temeljijo na tem čipu.

Če je čip dovolj znan, standarden, popularen, smo ga morda že dobili z okoljem za programiranje Arduina. V okolju gremo v Sketch / Include Library / Manage Libraries in v iskalno polje vpišemo TM1637. Morda se bo prikazalo kaj, kar bo zvenelo kot prava stvar. Morda pa ne.

V tem primeru pride na vrsto Google. Pri TM1637 bo najbrž že prvi zadetek http://playground.arduino.cc/Main/TM1637. Na tem spletnem mestu najdemo knjižnice za vse mogoče, kar se da priključiti na Arduino (kazalo). Če najdemo čip tu, je to dober znak.

O TM1637 tu sicer ni veliko napisanega, pomembno pa je, da je na koncu povezava na knjižnico. Najzadnejšo različico dobimo na Githubu (kjer tudi sicer običajno domujejo takšne knjižnice) in jo tam poberemo z zeleno tipko Clone or Download. V vsakem primeru bomo prišli do datoteke .zip.

Ko imamo zip, v okolju za Arduino izberemo Sketch / Include Library / Add .ZIP Library ... in izberemo pravkar pobrano datoteko. S tem namestimo knjižnico.

Ob pripravi teh zapiskov sem naletel na -- na kot je videti, boljšo -- knjižnico https://github.com/bremme/arduino-tm1637. Ima več funkcij, bolj logična imena, boljšo dokumentacijo. Zapiski vseeno temeljijo na tisti, ki smo jo uporabljali na delavnici. Če se boste z zasloni igrali v razredu, pa si le oglejte tudi to knjižnico.

Uporaba knjižnice

Da bomo v svojem programu lahko uporabljali funkcije iz knjižnice, moramo na njegov začetek dodati #include <ime-datoteke-s-funkcijami>. Da ne brskamo, kakšno bi to ime utegnilo biti, v okolju izbereko Sketch / Include Library in v menuju poiščemo knjižnico, ki smo jo pravkar namestili, pa se bo na začetku programa pojavil ustrezni #include.

Zdaj pa moramo izvedeti imena funkcij in kako jih klicati.

Za tiste, ki veste kaj o objektnem programiranju: Arduina programiramo v C++ in ne v C. C++ je objektni jezik. Knjižnice, kot je ta za TM1637 skoraj vedno definirajo razrede. Če hočemo delati z zaslonom, sestavimo ustrezen objekt, ki bo predstavljal zaslon, v konstruktorju pa navedemo njegove lastnosti (npr. ločljivost zaslona, če bi šlo za pravi LCD), na katere pine je priključen, in podobno.

Za tiste, ki ne veste kaj je to objekt: ne vznemirjajte se.

Ko smo namestili knjižnico, so se v menuju File / Examples morda pojavili kaki primeri. Lepo vzgojene knjižnice jih imajo (veliko), manj vzgojene malo ali nič. Tale za TM1637 ima nekaj malega.

Vedno pa bomo potrebovali dokumentacijo. Tu so spet nekateri bolj, drugi manj vzgojeni. Najprej poglejmo na konec strani na Githubu. Tu tokrat ni veliko, še najbolj koristen je namig Please refer to TM1637Display.h for more information. To datoteko najdemo tam, kamor se je namestila knjižnica: vse knjižnice so v istem direktoriju kot direktoriji z našimi projekti (kaj v slogu Documents/Arduino), a v poddirektoriju libraries. Ker smo ravno na Githubu, pa lahko datoteko pogledamo kar na spletu. Premaknemo se na vrh strani in med datotekami poiščemo TM1637Display.h.

Po začetnem pravnem flancu in tehničnih deklaracijah najdemo definicije funkcij, pred katerimi so tokrat zgledno napisani komentarji. Tu je, recimo,

  //! Sets the brightness of the display.
  //!
  //! The setting takes effect when a command is given to change the data being
  //! displayed.
  //!
  //! @param brightness A number from 0 (lowes brightness) to 7 (highest brightness)
  //! @param on Turn display on or off
  void setBrightness(uint8_t brightness, bool on = true);

Neka funkcija se imenuje setBrightness in ima dva argumenta. Argument brightness je številka med 0 in 7, ki predstavlja osvetljenost, argument on pa logična vrednost (true ali false, ki pove, ali naj bo zaslon vključen ali izključen). Drugi argument ima privzeto vrednost, true. To pomeni, da ga lahko izpustimo.

Druga funkcija, ki jo bomo potrebovali, je showNumberDec.

//! Displays a decimal number
//!
//! Dispalys the given argument as a decimal number
//!
//! @param num The number to be shown
//! @param leading_zero When true, leading zeros are displayed. Otherwise unnecessary digits are
//!        blank
//! @param length The number of digits to set. The user must ensure that the number to be shown
//!        fits to the number of digits requested (for example, if two digits are to be displayed,
//!        the number must be between 0 to 99)
//! @param pos The position most significant digit (0 - leftmost, 3 - rightmost)
void showNumberDec(int num, bool leading_zero = false, uint8_t length = 4, uint8_t pos = 0);

Argument num pove število, ki ga želimo pokazati. Ostali argumenti so neobvezni. leading_zero pove, ali želimo pred številko dopisati ničle, recimo 0042 namesto 42; privzeta vrednost je false, torej ne. length pove, na koliko mest želimo izpisati število. pos je mesto, kjer ga želimo začeti izpisovati; 0 je skrajno levo.

Nenazadnje, še več, naprvo, pa potrebujemo tole:

//! Initialize a TM1637Display object, setting the clock and
//! data pins.
//!
//! @param pinClk - The number of the digital pin connected to the clock pin of the module
//! @param pinDIO - The number of the digital pin connected to the DIO pin of the module
TM1637Display(uint8_t pinClk, uint8_t pinDIO);

TM1637Display je "funkcija" (ne čisto, a za zdaj naj zadošča), ki ustvari spremenljivko, ki bo predstavljala zaslon. Dobi dva argumenta, ki povesta, na katere pine smo priključili zaslon. Detajli takoj.

Priključimo zaslon in kaj izpišemo

Zaslon ima štiri pine. Dva sta VCC in GND, ki gresta na +5V in GND. Preostala dva sta CLK in DIO. Priključimo ju na katerikoli digitalni pin (pri čemer se, kot običajno, izognemo D0 in D1, ker bosta motila prenos programa na Arduino). Vzemimo kar naslednja dva, D2 in D3.

Zdaj pa program.

#include <TM1637Display.h>

TM1637Display zaslon(2, 3);

void setup() {
    zaslon.setBrightness(7);
    zaslon.showNumberDec(42);
}

void loop() {}

Prvo vrstico lahko natipkamo sami, ali pa jo vstavimo z Sketch / Include Library. Z naslednjo vrstico sestavimo spremenljivko, ki predstavlja zaslon. TM1637Display je tip spremenljivke; doslej smo imeli preproste spremenljivke, kot sta int in long, za večja in manjša števila. TM1637Display je pač bolj zapletena spremenljivka, ki ne vsebuje števila temveč, vse, kar je potrebno vedeti o zaslonu, da se lahko Arduino pogovarja z njim. Za razliko od običajnih spremenljivk, ki jim nismo takoj nastavljali vrednosti (napisali smo le int i;) ali pa smo jo nastavili s prirejanjem (int i = 0;), tu nastavimo vrednost (oziroma podamo argumente, ki so potrebni za to, da se spremenljivka pravilno sestavi) v oklepajih. Vrstica TM1637Display zaslon(2, 3); torej pomeni, da imamo zaslon, ki je priključen tako, da je njegov pin CLK povezan z Arduinovim D2, DIO pa z Arduinovim D3.

Da bi res razumeli, kaj se dogaja v ozadju, bi morali vedeti več o razredih v jeziku C++. Na srečo to niti ni zares potrebno, saj se ista štorija kot zgoraj ponovi z vsako rečjo, ki bi jo priklapljali na Arduino. Ko, recimo, priključimo prav LCD, ki zna kazati tudi črke, bo vsa razlika v tem, da bomo potrebovali drugo knjižnico, vključili drugo datoteko (#include <LiquidCrystal.h>), namesto spremenljivke tipa TM1637Display sestavili spremenljivko tipa LiquidCrystal in namesto dveh argumentov s številkami pinov podali šest argumentov s številkami pinov (LiquidCrystal lcd(12, 11, 5, 4, 3, 2);). Skratka, ne vznemirjajte se: vedno bo potrebno odkriti le nekaj magičnih besed, vse ostalo bo enako.

Še ena novost je, kako kličemo funkcije. Vse funkcije, ki nam jih priskrbi knjižnica (kot recimo setBrightness in showNumberDec, ki smo ju videli zgoraj), niso funkcije kar tako, kot naša stara prijatelja digitalWrite ali sleep, temveč so funkcije, ki "pripadajo" spremenljivki zaslon. Če želimo nastaviti osvetljenost zaslona, ki ga opisuje spremenljivka zaslon, napišemo zaslon.setBrightness(7). Če želimo prikazati število 42, napišemo zaslon.showNumberDec(42).

In to je to. Zdaj znamo narediti (približno) vse, kar se da narediti s tem zaslonom. Preostane nam le še "običajno" programiranje.

Števec

Najprej napišimo program, ki šteje sekunde.

#include <TM1637Display.h>

TM1637Display zaslon(2, 3);

void setup() {
    zaslon.setBrightness(7);
    int i = 0;
    while(i < 9999) {
        zaslon.showNumberDec(i);
        i++;
        delay(1000);
    }
}

void loop() {}

Ravno dan pred delavnico sem bral blog, v katerem se nekdo pritožuje nad Arduinom in ena od (ne prav zelo utemeljenih) reči, na katere se obeša, je delay: pravi, da je to zgled funkcije, ki je ne bi smelo biti. Z delay ni nič narobe, le čisto vedno ni primeren. Tule, recimo, se mu izognimo, ker bomo program počasi razširili do štoparice in če obdržimo delay, Arduino ne bo pravilno zaznaval pritiskov tipk.

Uporabili bomo trik, ki ga že poznamo: ko izpišemo število, bomo zabeležili, kdaj je potrebno izpisati naslednje število. Vse delo preselimo v loop. Tako je boljše, v splošnem. Program se vedno splača zastaviti tako, da setup le "vzpostavi" reči, nastavi začetne vrednosti in podobno. Resnično delo pa opravi loop, pri čemer ga zastavimo tako, da vedno le opravimo, kar je potrebno opraviti v tistem trenutku (recimo prikazati naslednje število).

void setup() {
    zaslon.setBrightness(7);
    i = 0;
    zaslon.showNumberDec(i);
    naprej = millis() + 1000;
}

void loop() {
    if (millis() >= naprej) {
        i++;
        zaslon.showNumberDec(i);
        naprej += 1000;
    }
}

Število, ki ga prikazujemo, i, ne bo več lokalna spremenljivka, znotraj setup, temveč globalna, tako da bo obdržala vrednost med različnimi klici loop-a. Pridružila se ji je še ena spremenljivka, naprej, ki pove, kdaj je potrebno povečati števec.

setup le vključi zaslov, nato pa nastavi števec na -1 in čas, ko ga je treba povečati, na takoj.

loop preveri, ali je napočil čas, da je potrebno povečati števec. Če je tako, da ga poveča, pokaže in poveča trenutek, ko je potrebno iti naprej, za 1000.

Zakaj je naprej += 1000 boljše od naprej = millis() + 1000? Zato, da se zamude ne povečujejo. Arduino se malo zamudi tudi v if-u, pa še kje, in te zamude bi se, če bi imeli naprej = millis() + 1000, seštevale. V tem programu se to res ne bo poznalo, kadar delamo kaj bolj natančnega pa nas utegnejo zamude zafrkavati.

Štoparica brez tipke

Spremenimo števec tako, da kaže čas v sekundah od vklopa Arduina. Na dva načina.

#include <TM1637Display.h>

TM1637Display zaslon(2, 3);

int i;
long naprej;

void setup() {
    zaslon.setBrightness(7);
    i = 0;
    zaslon.showNumberDec(i / 60, true, 2, 0);
    zaslon.showNumberDec(i % 60, true, 2, 2);
    naprej = millis() + 1000;
}

void loop() {
    if (millis() >= naprej) {
        i++;
        zaslon.showNumberDec(i / 60, true, 2, 0);
        zaslon.showNumberDec(i % 60, true, 2, 2);
        naprej += 1000;
    }
}

Štejemo tako kot prej, le da na prvi dve mesti izpišemo i / 60 (čas v minutah), na zadnji dve pa ostanek po deljenju i s 60 (preostale sekunde).

Na zaslonu je med prvima in zadnjima števkama dvopičje. Če ga želimo prižgati, pokličemo namesto funkcije showNumberDec funkcijo showNumberDecEx, ki ji kot dodaten argument podamo, kateri vmesni znaki naj bodo prižgani. Dodatni argument je številka, ki jo funkcija prebere po bitih - bit 0 pomeni najdesnejšo piko, bit 1 drugo in tako naprej. Ali pa tudi ne; pravzaprav mi je vseeno: prižgati hočem vse pike (točneje obe), torej kot dodatni argument podam 255.

    zaslon.showNumberDec(i / 60, true, 2, 0);
    zaslon.showNumberDecEx(i % 60, 255, true, 2, 2);

Drugi način: namesto enega števca (i) ločeno štejemo minute in sekunde.

#include <TM1637Display.h>

TM1637Display zaslon(2, 3);

int min, sek;
long naprej;

void setup() {
    zaslon.setBrightness(7);
    min = 0;
    sek = 0;
    zaslon.showNumberDec(min, true, 2, 0);
    zaslon.showNumberDec(sek, true, 2, 2);
    naprej = millis() + 1000;
}

void loop() {
    if (millis() >= naprej) {
        sek++;
        if (sek == 60) {
            sek = 0;
            min++;
        }
        zaslon.showNumberDec(min, true, 2, 0);
        zaslon.showNumberDec(sek, true, 2, 2);
        naprej += 1000;
    }
}

V loop povečamo števec sekund. Če doseže 60, ga postavimo na 0 in povečamo števec minut. Nato pokažemo obe števili.

Različica z enim samim števcem je nekoliko preprostejša, zato se je bomo držali tudi v nadaljevanju.

Štoparica z dvema tipkama

Recimo, da imamo dve tipki, priključeni na D4 in D5. Prva požene štoparico, druga jo ustavi.

Nalogo najprejprosteje rešimo z dodatno spremenljivko, ki pove, ali naj štoparica teče ali ne.

#include <TM1637Display.h>

TM1637Display zaslon(2, 3);

int i;
long naprej;
int stej;

void setup() {
    pinMode(4, INPUT_PULLUP);
    pinMode(5, INPUT_PULLUP);

    zaslon.setBrightness(7);
    i = 0;
    zaslon.showNumberDec(i / 60, true, 2, 0);
    zaslon.showNumberDec(i % 60, true, 2, 2);
    stej = 0;
}

void loop() {
    if (digitalRead(4) == LOW) {
        stej = 1;
        naprej = millis() + 1000;
    }
    if (digitalRead(5) == LOW) {
        stej = 0;
    }

    if ((millis() >= naprej) && (stej != 0)) {
        i++;
        zaslon.showNumberDec(i / 60, true, 2, 0);
        zaslon.showNumberDec(i % 60, true, 2, 2);
        naprej += 1000;
    }
}

(Najprej opomba za tiste, ki so bolj vešči programiranja: da, stej bi lahko bila bool namesto int. A ne zapletajmo razlage z nepotrebnim dodatnim podatkovnim tipom.)

V setup zdaj le nastavimo stej na 0, kar nam bo pomenilo, da trenutno ne štejemo. Spremenljivko naprej pustimo.

V loop preverimo, ali je prisnjena prva tipka. Če, potem sprožimo štetje tako, da nastavimo stej na 1 in naprej na trenutni čas +1000. Če je pritisnjena druga tipka, ustavimo štetje (stej = 0).

V pogoj, ki preverja, ali je čas za povečanje števca, k (millis() >= naprej) dodamo še && (stej != 0). To je vse.

Štoparica z eno tipko

Zdaj pa spremenimo program tako, da bomo štoparico zagnali in ustavili z isto tipko. Poučno bo.

#include <TM1637Display.h>

TM1637Display zaslon(2, 3);

int i;
long naprej;
int stej;

void setup() {
    pinMode(4, INPUT_PULLUP);

    zaslon.setBrightness(7);
    i = 0;
    zaslon.showNumberDec(i / 60, true, 2, 0);
    zaslon.showNumberDec(i % 60, true, 2, 2);
    stej = 0;
}

void loop() {
    if (digitalRead(4) == LOW) {
        stej = 1 - stej;
        naprej = millis() + 1000;
    }

    if ((millis() >= naprej) && (stej != 0)) {
        i++;
        zaslon.showNumberDec(i / 60, true, 2, 0);
        zaslon.showNumberDec(i % 60, true, 2, 2);
        naprej += 1000;
    }
}

Program je pravzaprav nekoliko krajši. Pač ena tipka manj. Le stej = 1 se spremeni v stej = 1 - stej: če je bil 1, postane 0, če je bil 0, pa postane 1. V vsakem primeru nastavimo še naprej, čeprav ga takrat, ko štoparico ustavljamo, v resnici ne potrebujemo.

Le ... da tole ne deluje dobro. Tipka včasih "prime" včasih ne.

Takrat, ko pritisnemo tipko, signal "zaniha". Tipka je mehanska zadeva in v prvih nekaj trenutkih stik ni popoln, tako da Arduino zazna več pritiskov tipke. Najenostavnejši ukrep je kratka pavza po pritisku:

    if (digitalRead(4) == LOW) {
        stej = 1 - stej;
        naprej = millis() + 1000;
        delay(100);
    }

No, pa imamo spet delay. :) Tule nas ne bo motil. (Kdor hoče, pa se mu lahko z nekaj truda tudi izogne.)

Kdor potrebuje kak dodaten izziv, si lahko izdela odštevalnik. Ob pritisku na tipko šteje od 3:00 do 0:00. Ko se čas izteče, začne piskati in čaj je nared.

Last modified: Saturday, 7 January 2017, 1:10 PM