Kategória: Arduino.
Serial monitor
A Serial monitor alkalmas arra, hogy a számítógép kommunikáljon az Arduino UNO lappal. Szinte ez az egyetlen módja a hibakeresésnek is. A Serial osztály függvényei segítségével tudjuk elérni a műveleteket (https://www.arduino.cc/reference/en/language/functions/communication/serial/), az Arduino IDE-ben pedig a Serial Monitort a már leírt módon tudjuk elindítani. Töltsük fel az Arduino-ra a következő kódot:
int ledpin = 13; void setup() { Serial.begin(9600); pinMode(ledpin, OUTPUT); Serial.println("Issue command on or off."); } void loop() { if (Serial.available()) { String command = Serial.readString(); command.trim(); if (command == "on") { digitalWrite(ledpin, HIGH); Serial.println("LED turned on."); } else if (command == "off") { digitalWrite(ledpin, LOW); Serial.println("LED turned off."); } else { Serial.println("Unknown command: " + command + ". Possible commands: on, off."); } } else { delay(100); } }
A Serial Monitor felső sorába írt parancsok (on, off) segítségével tudjuk vezérleni a lapka 13-as lábhoz kapcsolt LED-et. Némi magyarázat:
- A Serial.println() függvény segítségével küld adatot az Arduino a számítógépnek.
- A Serial.read() függvény egyetlen bájtot olvas be. A Serial.readString() a teljes szöveget beolvassa.
- A String osztály segítségével alapvető string műveleteket tudunk végrehajtani. A trim() helyben törli az elejéről a nem látható karaktereket, erre jelen esetben amiatt van szükség, mert alapból az új sor karaktert is tartalmazza a beolvasott string.
- A delay() a megadott századmásodpercig vár.
- A program példa a feltételkezelésre is.
Digitális érzékelő
A digitális érzékelők kétféle értéket tudnak érzékelni. Így ha csak azt szeretnénk látni, hogy az adott esemény bekövetkezett-e, a legegyszerűbb a lapkán található 13-as LED-et be- ill kikapcsolni. Ha feltételezzük, hogy a 2-es lábon jelenik meg a logikai alacsony vagy magas érték, akkor a vonatkozó kód ez:
const int sensorPin = 2; const int ledPin = 13; void setup() { pinMode(ledPin, OUTPUT); pinMode(sensorPin, INPUT); } void loop() { digitalWrite(ledPin, digitalRead(sensorPin)); }
Analóg érzékelő
Az analóg érzékelők végső soron egy 0 és 1023 közötti egész számot adnak. Ha feltesszük, hogy egy ilyen érzékelő az A0-ás lábra van kötve, akkor a kód a következő:
const int sensorPin = A0; void setup() { pinMode(sensorPin, INPUT); Serial.begin(9600); } void loop() { int sensorValue = analogRead(sensorPin); Serial.println(sensorValue); delay(100); }
I2C kapcsolat
Az I2C (I2C, IIC, ami az Inter-Integrated Circuit rövidítése) egy olyan protokoll, melynek segítségével több eszközt tudunk ugyanazon a lábon megcímezni. Két részből áll: az adat (SDA, mely az Arduino UNO-n az A4 lábat jelenti) és az óra (SCL, Arduino UNO A5). Ezen kívül szükség van még egy tápfeszültségre (VCC) és egy földre (GND), így eszközönként 4 vezetékkel megússzuk (ellentétben például az I2C nélküli LCD kijelzővel, melyhez 14 kábel kell, ld. lejjebb), de az igazán nagy hozzáadott értéke nem es ez, hanem az, hogy több eszközt tudunk rádugni ugyanazokra a lábakra egyszerre.
Az I2C a Philips cég specifikációja. Az Azmel (melynek chipje az Arduino-ban van) ezt Two Wire Interface (TWI) néven fejlesztette tovább. A lényege ugyanaz: egy adatkábel és egy órajel segítségével lehessen több mindent megcímezni. Az I2C ill. TWI Arduino specifikációja itt található: https://www.arduino.cc/en/reference/wire.
Az I2cScanner nevű program (https://playground.arduino.cc/Main/I2cScanner) segítségével fel tudjuk deríteni, hogy épp milyen eszközök vannak rákapcsolva, és milyen címen lehet azokat elérni. Pl. ha ráteszünk egy I2C-vel ellátott 2x16 soros karakteres LCD kijelzőt, valamint egy DS3231-es kódjelű órát, akkor azt látjuk, hogy az LCD címe a 0x27 (ez szerepel a kódban is), az órához meg két cím is tartozik: 0x57 és 0x68.
Az eszközök I2C címeit ezen az oldalon találjuk: https://learn.adafruit.com/i2c-addresses/the-list.
SPI
Az SPI a Serial Peripheral Interface (soros külső interfész) rövidítése. leírása itt található: https://www.arduino.cc/en/reference/SPI. A felépítése master-slave (mester-szolga), azaz az egyik eszköz (tipikusan a mikrokontroller) irányítja a kommunikációt. A szükséges lában egyúttal megmagyarázzák a működését is:
- MOSI (Master In Slave Out): ezen a csatornán küld üzenetet a szolga a mesternek. Az Arduino UNO-n ez a 11-es láb.
- MISO (Master Out Slave In): ezen a csatornánk a mester küld üzenetet a szolgának. Az Arduino UNO-n ez a 12-es láb.
- SCL (Serial Clock): a mester ezen keresztül generálja az órajeleket az adattovábbítás szinkronizálása érdekében. Az Arduino UNO-n ez a 13-as láb.
- SS (Slave Select): ezzel tudja a mester engedélyezni vagy letiltani a szolgát, ezáltal lehetővé tenni azt, hogy több eszköz csatlakozzon az SPI-n keresztül. Egyetlen eszköz csatlakozása esetén tipikusan a 10-es lábat szokás használni, mert az a többi mellett van, de tetszőleges láb használható erre a célra.
Ha tehát mondjuk 3 eszközt szeretnénk ezen az interfészen keresztül csatlakoztatni, akkor a MOSI-t, MISO-t és az SCL-t ugyanarra csatlakoztatjuk, az SS pedig lehet mondjuk a 10, a 9 és a 8, és kódból vezéreljük az Arduino-n, hogy az aktuális kommunikáció kinek szól.
Példa az SPI-re a mikro SD kártya olvasó.
Külső megszakítások
Megszakítás során a program szekvenciális futása megszakad, lefut egy művelet, majd folytatódik az eredeti program. Alapvetően megkülönböztetünk külső és belső megszakítást. A külső megszakítás valamilyen külső esemény hatására következik be, míg a belső tipikusan időzítő.
Az Arduino UNO-n a 2-es és a 3-as láb alkalmas külső megszakításra. A vonatkozó függvény ez: attachInterrupt(interrupt, function, mode) (vonatkozó lekapcsolás: detachInterrupt(interrupt)). A paraméterek:
- interrupt: a megszakítás száma. Lehetséges értékek: 0 (2-es láb esetén) és 1 (3-as láb esetén). Jó programozási gyakorlat használni az int digitalPinToInterrupt(pin) függvényt, mellyel a fizikai lába tudjuk megadni, és ezzel a portolhatóságot segítjük elő. Pl. digitalPinToInterrupt(2) azt mondja meg, hogy a 2-es lábra kötött megszakítást szeretnénk hazsnálni.
- function: az a paraméter és visszatérési érték nélküli függvény, mely lefut, amikor a megszakítás bekövetkezik.
- mode: azt mutatja meg, hogy mikor következzen be a megszakítás. lehetséges értékek:
- LOW: mindig bekövetkezik, amikor LOW értéket kap a láb.
- CHANGING: változásra fut le, tehát LOW -> HIGH és HIGH -> LOW esetben is.
- RISING: LOW -> HIGH változásra fut le.
- FALLING: HIGH -> LOW változásra fut le.
A megszakításokat ki lehet kapcsolni a noInterrupts() függvénnyel, visszakapcsolni pedig az interrupts() függvénnyel.
Vonatkozó oldalak:
- https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/
- https://www.arduino.cc/reference/en/language/functions/interrupts/interrupts/
A következő példaprogramban a 2-es lábra kötött megszakítás fut le változásra, és változás esetén a 13-as lábra kötött LED változtatja állapotát.
int led = 13; volatile int state = LOW; void setup() { pinMode(led, OUTPUT); attachInterrupt(digitalPinToInterrupt(2), blink, CHANGE); } void loop() {} void blink() { state = !state; digitalWrite(led, state); }
Látható, hogy a loop() függvény teljesen üres, mégis, állapotváltozás következik be a futás során.
Figyeljük meg a volatile kulcsszót a state változó előtt: ez azt jelenti, hogy bármikor megváltozhat, nemcsak a szekvenciális futás során. Ennek jelentősége a következő: a fordítóprogram a háttérben rengeteget optimalizál(hat), pl. egy int típusú i változó esetén ha beállítjuk az értékét mondjuk 2-re, majd három utasítással később van egy olyan, hogy if (i<5), de közben nem szerepel az i, akkor a fordító alapból igaznak veszi a feltételt, és tkp. bele se teszi. A volatile megakadályozza az ilyen optimalizálást: ez esetben nem feltételezi, hogy közben nem változhat meg az értéke, mert pl. egy megszakítás hatására pont megváltozhat.
Bekötés:
- Nyomógomb A -> Arduino 5V
- Nyomógomb B -> Arduino 2
- Nyomógomb B -> 10 kΩ ellenállás -> Arduino GND
Teszt: nyomjuk meg és engedjük fel a nyomógombot többször egymás után, közben figyeljük a 13-as LED-et.
Időzítő
Az időzítő a belső megszakítások tipikus példája. Ennek segítségével adott időközönként le tudunk futtatni egy programot. A delay() függvény erre nem teljesen tökéletes, mert hozzáadódik a program futásának az ideje, így ha pontos időzítést szeretnénk, akkor érdemes a megszakítást használni.
A megszakításokat legegyszerűbben egy külső, de beépített könyvtár segítségével, a Timer1-gyel tudjuk vezérelni, melynek specifikációja itt található: https://playground.arduino.cc/code/timer1. Példaprogram fél másodperces megszakítással, mely a 13-as LED-et villogtatja:
#include "TimerOne.h" void setup() { pinMode(13, OUTPUT); Timer1.initialize(500000); Timer1.attachInterrupt(callback); } void callback() { digitalWrite(13, 1 - digitalRead(13)); } void loop() {}
Saját könyvtár készítése
Saját könyvtárakat is lehet készíteni, bár a gyakorlatban célszerű inkább már létezőt keresni, mint nekiállni megírni. Szükség esetén lássuk a klasszikus példát: Morze jelek (rövid és hosszú) küldése. A könyvtár C++-ban készül, és a következőket tartalmazza.
Fejléc fájl: praktikusan Morse.h, és a tartalma legyez alábbi:
#ifndef Morse_h #define Morse_h #include "Arduino.h" class Morse { public: Morse(int pin); void dot(); void dash(); private: int _pin; }; #endif
Megvalósítás, jelen esetben Morse.cpp:
#include "Arduino.h" #include "Morse.h" Morse::Morse(int pin) { pinMode(pin, OUTPUT); _pin = pin; } void Morse::dot() { digitalWrite(_pin, HIGH); delay(250); digitalWrite(_pin, LOW); delay(250); } void Morse::dash() { digitalWrite(_pin, HIGH); delay(1000); digitalWrite(_pin, LOW); delay(250); }
Ahhoz, hogy el tudjuk érni az Arduino beépített függvényeit, be kell tölteni az Arduino.h-t (ez az Arduino ino fájlok esetén automatikus).
Kulcsszó fájl: ezt felhasználva segíti a könyvtárat használó fejlesztőt az Arduino IDE, megfelelő színezéssel. Fájlnév: keywords.txt, melynek tartalma (tabulátorokkal):
Morse KEYWORD1
dash KEYWORD2
dot KEYWORD2
Példa program: ha a könyvtár- ill. fájlnév ez: examples\SOS\SOS.ino, akkor a File -> Examples -> Morse alatt fog megjelenni, SOS névvel.
#include <Morse.h> Morse morse(13); void setup() {} void loop() { morse.dot(); morse.dot(); morse.dot(); morse.dash(); morse.dash(); morse.dash(); morse.dot(); morse.dot(); morse.dot(); delay(3000); }
Mindezt tegyük be egy Morse nevű könyvtárba, és ezt csomagoljuk be, Morse.zip néven. tehát a Morse.zip tartalmazni fog egy Morse könyvtárat, melyen belül található a többi. Ezt a szokásos módon telepíthetjük: Sketch -> Include Library -> Add .ZIP Library…, vagy közvetlenül kicsomagolva a c:\Users\[user]\Documents\Arduino\libraries\ könyvtárba.