Összetett Arduino eszközök

Kategória: Arduino.

Több kimenet egy lábon: 74HC595

Amint láttuk a megjelenítő eszközöknél, ha egyesével próbáljuk megcímezni pl. a LED-eket, igen hamar kifogyunk a lábakból. Pl. egy 8x8-as LAD mátrix 16 lábat vesz el a 20-ból, ráadásul ott már trükköt kellett alkalmazni, hogy ne legyen szükség 64 lábra.

Egy ötlet lábak spórolására: ugyanazon a lábon írjuk ki sorban egyesével a logikai értékeket, és a fogadó is ennek alapján értelmezze, majd "ossza szét" belül. Az ötlet jó, viszont problémát okoz az időzítés, melynél valójában két problémát kell megoldani:

  • Nem garantálható, hogy a két eszköz belső időzítője teljesen tökéletesen szinkronban van, pl. függ a küldő processzorának sebességétől. Akármennyire is tökéletes oszcillátorokat használunk, könnyen "szétcsúszik" az adat. Emiatt szükség van egy órajelre is: az adó nemcsak az adatot küldi, hanem egy másik szálon az ütemezést is. Így a fogadó tudja, hogy eddig tartott az előző adat, most jön a következő.
  • Azt is tudatni kell a fogadóval, hogy mikortól figyelje az adatokat. Ezt egy újabb szálon tudjuk átadni. Ezzel azt is meg tudjuk mondani, hogy meddig tart, és a fogadó miután megkapta a vége jelet, állítja csak be az állapotát, tehát nem sorban, ahogy jön a jel, hanem egyszerre. (Emlékezzünk csak: a mátrix kijelzőnél a képpontokat sorban villantottuk fel.)

Adja magát tehát a megoldás. Szükség van 3 lábra: egy adatra (data), egy órajelre (clock) és egy zárra (latch vagy strobe). Pont ezt valósítja meg a 74HC595 kódú IC, melynek segítségével 8 kimenetet tudunk megcímezni 3 bemenet segítségével. Ráadásul a fentiekből az is következik, hogy akármelyik 3-at használhatjuk a 20-ból (ellentétben pl. az I2C kommunikációval, melyre kizárólag csak az A4 és A5 alkalmas).

A példa alapján elég nehézkesnek tűnik, és elsőre nem feltétlenül tűnik nagy csodának, a következőkben, a TM1638-as eszköz bemutatásánál viszont látni fogjuk ennek a hatalmas erejét. Egyébként itt érhető el egy részletes leírás: https://www.arduino.cc/en/tutorial/ShiftOut. Először lássuk a bekötést. Az IC-n az egyik szélső lábhoz közel (tehát nem középen!) található egy kis kör alakú bemélyedés: az az első láb, és az óramutató járásával ellentétes irányban haladva növekszik 16-ig. Ahol nincs semmi, azt nem kell bekötni.

  • 74HC595 1 (Q1) -> 220Ω -> LED -> Arduino GND
  • 74HC595 2 (Q2) -> 220Ω -> LED -> Arduino GND
  • 74HC595 3 (Q3) -> 220Ω -> LED -> Arduino GND
  • 74HC595 4 (Q4) -> 220Ω -> LED -> Arduino GND
  • 74HC595 5 (Q5) -> 220Ω -> LED -> Arduino GND
  • 74HC595 6 (Q6) -> 220Ω -> LED -> Arduino GND
  • 74HC595 7 (Q7) -> 220Ω -> LED -> Arduino GND
  • 74HC595 8 (GND) -> Arduino GND
  • 74HC595 9 (Q7')
  • 74HC595 10 (MR) -> Arduino 5V
  • 74HC595 11 (SH_CP; órajel) -> Arduino 4
  • 74HC595 12 (ST_CP; zár) -> Arduino 5
  • 74HC595 13 (OE; output engedélyezés) -> Arduino GND
  • 74HC595 14 (DS; adat) -> Arduino 2
  • 74HC595 15 (Q0) -> 220Ω -> LED -> Arduino GND
  • 74HC595 16 (VCC) -> Arduino 5V

A kód a következő:

int latchPin = 5;
int clockPin = 4;
int dataPin = 2;
 
void setup() {
  pinMode(latchPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(dataPin, OUTPUT);
}
 
void loop() {
  for (int numberToDisplay = 0; numberToDisplay < 256; numberToDisplay++) {
    digitalWrite(latchPin, LOW);
    shiftOut(dataPin, clockPin, MSBFIRST, numberToDisplay);  
    digitalWrite(latchPin, HIGH);
    delay(500);
  }
}

A működése a következő:

  • A zár 0-ra állítása indítja az adatokat, 1-re állítása pedig jelzi a fogadónak az adatok végét. A fogadó tehát nem az adott értéket, hanem az átmenetet figyeli.
  • A shiftOut() utasítás segítségével küldjük ki az adatot. Megadjuk az adatlábat, az óralábat, a kiírandó 8 bites számot, ill. jelezzük a bitek sorrendjét is.

Teszt: a fenti bekötés és példaprogram azt eredményezi, hogy a LED-ek egyesével "elszámolnak" binárisan 0-tól 255-ig.

A példa igen bonyolult, és elsőre jogosan gondolhatjuk azt, hogy igen nehézkes. A bekötéshez én 17 kábelt használtam, az eredeti előtét ellenállásokat ki kellett cserélnem jobbakra, néhány LED-et is, az eredetileg talált bekötés rossz volt, ráadásul a lábkiosztás sem számít szerintem logikusnak (én legalább a 8 outputot egymás mellé tettem volna). Az eredeti példában hibás volt a bekötés, azzal is elment egy csomó időm. Ráadásul így sem teljes: a fenti linken található leírás áramkörében szerepel egy kondenzátor is. Mindenesetre nálam anélkül is működik. Viszont első ránézésre nem tűnik olyan nagy nyereségnek: 5 adatlábbal többel és 7 kábellel kevesebbel ugyanezt meg lehet oldani.

Valamint nem tűnik átütőnek az sem, hogy 3 bemenet segítségével nyerünk 8 kimenetet, mert az nettóban csak 5 nyereség. A problémát nem is a 8 kimenet okozza, hanem az, ha ennek többszörösére van szükség. Erre a megoldás a következő: A zár segítségével egymás után több adatot is kiküldünk. Felfoghatjuk úgy is, hogy parancsokat küldünk, majd paramétereket, és a fogadó csak akkor fogja végrehajtani, miután mindent megkapott. Ehhez persze szüksége van a fogadónak is némi memóriára, így tovább bonyolódik a rendszer…

Ugyanakkor a hatalmas ereje nem abban rejlik, hogy mi magunk elkezdjük élesben használni a 74HC595 IC-t, hanem olyan, kész eszközöket vásárolunk, melyben benne vannak a fentiek, előlünk el vannak rejtve. A következőben látni fogunk egy ilyen eszközt: a TM1638-as kódút!

TM1638

Ez az eszköz tartalmaz 8 darab 7 szegmenses kijelzőt (az összesen 64 LED) + 8 darab nyomógombot + 8 darab különálló LED-et. Mindennek megcímzéséhez alapból 80 lábra lenne szükség, viszont a fent vázolt technikával 3 lábbal (+ GND és VCC) meg tudjuk oldani. Ráadásul az alacsony szintű shiftout() utasításokkal sem kell feltétlenül bajlódnunk, mert létezik olyan könyvtár, ami elrejti előlünk az ilyen részleteket, és elég annyit írnunk, hogy "írd ki azt, hogy 123". De erről később!

A következő leírás alapján értettem meg ennek az eszköznek a működését: https://blog.3d-logic.com/2015/01/10/using-a-tm1638-based-board-with-arduino/. A működésének a logikája a következő: 8 bites parancsokkal lehet vezérelni. Ezek a következők:

  • 1000abbb (0x8?): ezzel lehet aktiválni. Az 'a' bitet 1-re állítva aktiváljuk a rendszert, a 'bbb'-vel pedig a fényerőt állíthatjuk. A 0x8F utasítással aktiváljuk, maximális fényerővel.
  • 01000100 (0x44): adat kiírás. Ennek két paramétere van (1-1- bájt): cím és adat. Ezekről később.
  • 01000000 (0x40): ezzel folyamatosan lehet kiírni adatokat. Paraméterek: kezdő cím, és utána a legfeljebb 16 adat. Ez az utasítás tehát akár 18 bájt hosszú is lehet; elméletileg 144 lábra lenne ehhez szükség!
  • 01000010 (0x42): nyomógombok állapotának beolvasása. Paramétere nincs, viszont az adat lábat INPUT-ra kell állítani, és a shiftin() függvényt kell használni. A beolvasás viszont nem triviális: először az első és ötödik, majd a második és hatodik, utána a harmadik és hetedik, végül a negyedik és nyolcadik nyomógomb értékét olvashatjuk be, mégpedig a jobboldali, valamint jobbról az ötödik biten, tehát 4 darab beolvasásra, majd bit mozgatásra és bitenkénti logikai vagy művelet végrehajtására van szükség.

Címzés: formája 1100aaaa, tehát 0xC0-tól indul, és 16 címet lehet vele megcímezni. A páros számúak (tehát a 0xC0, 0xC2, 0xC4 stb.) a 7 szegmenses kijelzőket jelenti, balról jobbra, míg a páratlanok (tehát 0xC1, 0xC3 stb.) az egyes LED-eket.

Adatok: a 7 szegmenses kijelzőnél a már bemutatott formában tudunk elvileg bármilyen karaktert kirajzoltatni. A LED esetén a mi esetünkben bináris a dolog; az érték 0 vagy 1 lehet. Viszont létezik olyan hasonló eszköz is, ahol a LED nem egy, hanem kétszínű (piros-zöld): ez esetben a jobboldali bit a piros, jobbról a második bit pedig a zöld LED állításáért felelős.

Bekötés mindegyik esetben (amint azt már korábban láttuk):

  • TM1638 VCC -> Arduino 3.3V
  • TM1638 GND -> Arduino GND
  • TM1638 STB -> Arduino 7
  • TM1638 CLK -> Arduino 9
  • TM1638 DIO -> Arduino 8

Először lássuk a 8 számjegyes kijelző kódját!

const int latch = 7;
const int data = 8;
const int clock = 9;
 
void sendCommand(int value) {
  digitalWrite(latch, LOW);
  shiftOut(data, clock, LSBFIRST, value);
  digitalWrite(latch, HIGH);
}
 
void reset() {
  sendCommand(0x40);
  digitalWrite(latch, LOW);
  shiftOut(data, clock, LSBFIRST, 0xc0);
  for (int i = 0; i < 16; i++) {
    shiftOut(data, clock, LSBFIRST, 0x00);
  }
  digitalWrite(latch, HIGH);
}
 
void setup() {
  pinMode(latch, OUTPUT);
  pinMode(clock, OUTPUT);
  pinMode(data, OUTPUT);
  sendCommand(0x8f);
  reset();
}
 
void loop() {
  displayNumber(millis() / 100);
}
 
uint8_t digits[] = {
  B00111111, // 0
  B00000110, // 1
  B01011011, // 2
  B01001111, // 3
  B01100110, // 4
  B01101101, // 5
  B01111101, // 6
  B00000111, // 7
  B01111111, // 8
  B01101111  // 9
};
 
void displayNumber(int numberToDisplay) {
  int queue[8];
  for (int i = 0; i < 8; i++) {
    queue[i] = numberToDisplay % 10;
    numberToDisplay /= 10;
  }
  sendCommand(0x40);
  digitalWrite(latch, LOW);
  shiftOut(data, clock, LSBFIRST, 0xc0);
  for (int position = 7; position >= 0; position--) {
    shiftOut(data, clock, LSBFIRST, digits[queue[position]]);
    shiftOut(data, clock, LSBFIRST, 0x00);
  }
  digitalWrite(latch, HIGH);
}

A fent leírt módon adjuk ki az utasításokat, és végeredményben azt mutatja, hogy hány tizedmásodperc telt el az indulástól számítva.

Most lássuk, hogyan tudjuk használni a nyomógombokat és a LED-eket! A fent leírtak alapján a kód az alábbi:

const int strobe = 7;
const int data = 8;
const int clock = 9;
 
void sendCommand(uint8_t value) {
  digitalWrite(strobe, LOW);
  shiftOut(data, clock, LSBFIRST, value);
  digitalWrite(strobe, HIGH);
}
 
void reset() {
  sendCommand(0x40); // set auto increment mode
  digitalWrite(strobe, LOW);
  shiftOut(data, clock, LSBFIRST, 0xc0);   // set starting address to 0
  for(uint8_t i = 0; i < 16; i++) {
    shiftOut(data, clock, LSBFIRST, 0x00);
  }
  digitalWrite(strobe, HIGH);
}
 
void setup() {
  pinMode(strobe, OUTPUT);
  pinMode(clock, OUTPUT);
  pinMode(data, OUTPUT);
 
  sendCommand(0x8f);  // activate
  reset();
}
 
void loop() {
  buttons();
}
 
void buttons() {
  uint8_t buttons = readButtons();
  for(uint8_t position = 0; position < 8; position++) {
    uint8_t mask = 0x1 << position;
    setLed(buttons & mask ? 1 : 0, position);
  }
}
 
uint8_t readButtons(void) {
  uint8_t buttons = 0;
  digitalWrite(strobe, LOW);
  shiftOut(data, clock, LSBFIRST, 0x42);
  pinMode(data, INPUT);
  for (uint8_t i = 0; i < 4; i++) {
    uint8_t v = shiftIn(data, clock, LSBFIRST) << i;
    buttons |= v;
  }
  pinMode(data, OUTPUT);
  digitalWrite(strobe, HIGH);
  return buttons;
}
 
void setLed(uint8_t value, uint8_t position) {
  pinMode(data, OUTPUT);
  sendCommand(0x44);
  digitalWrite(strobe, LOW);
  shiftOut(data, clock, LSBFIRST, 0xC1 + (position << 1));
  shiftOut(data, clock, LSBFIRST, value);
  digitalWrite(strobe, HIGH);
}

Most ha nyomkodjuk a nyomógombokat, akkor a neki megfelelő LED villan fel.

Ez ebben a formában még mindig nem átütő, bár legalább a kapcsolás egyszerű lett. Viszont most, hogy megismertük, lássuk, milyen lesz a kód, ha használjuk a már említett könyvtárat (https://github.com/rjbatista/tm1638-library). A fenti számláló megvalósítása ennek segítségével:

#include <TM1638.h>
 
TM1638 module(8, 9, 7);
 
void setup() {}
 
void loop() {
  module.setDisplayToDecNumber(millis() / 100, 0);
}

A nyomógombos példáé:

#include <TM1638.h>
 
TM1638 module(8, 9, 7);
 
void setup() {}
 
void loop() {
  module.setLEDs(module.getButtons());
}

Látható, hogy egyetlen include és egyetlen sornyi inicializálás után (ahol az adat, az óra és a zár lábakat kell megadni) mindkettő mindössze egy-egy sorrá zsugorodott. Ez már elfogadhatóan egyszerű úgy a bekötést mind a programozást illetően, és igen komoly lehetőségeket kapunk. A referencia itt található: https://github.com/rjbatista/tm1638-library/wiki/Reference.

Óra

A pontos idő megjegyzéséhez Arduino-n belül nem lehetséges, mert nincs állandó tápellátás. Szerencsére vásárolható viszonylag olcsón olyan eszköz, mely gombelemmel működik, és folyamatosan működik. Ennek jele DS3231. Elképzelhető, hogy magát a 2032-es gombelemet külön kell megvásárolnunk hozzá.

Bekötés: I2C protokollon keresztül csatlakozik az Arduino-ra, így a bekötés az alábbi:

  • DS3231 SCL -> Arduino A5
  • DS3231 SDA -> Arduino A4
  • DS3231 VCC -> Arduino 5V
  • DS3231 GND -> Arduino GND

Kód: először telepítsük fel az alábbi könyvtárakat (mindkettő Michael Mangolis gondozásában) a Library Manager segítségével (vagy GitHub-ról):

Először be kell állítani a pontos időt. Ehhez nyissuk meg a DS1307RTC -> SetTime példaprogramot és töltsük fel. Ez a program az aktuális rendszeridőt tölti fel.

A tapasztalat szerint a valóságban sajnos ez sem jegyzi meg az időt örökre, időnként végre kell hajtani a fenti folyamatot.

A DS1307RTC -> GetTime példaprogram másodpercenként egyszer kiolvassa a pontos időt és megfelelően megformázva a Serial monitorra írja. Ennek egy egyszerűsített változata, mely nem tartalmaz hibakelezést, szép formázást és dátumot sem, az alábbi:

#include <Wire.h>
#include <TimeLib.h>
#include <DS1307RTC.h>
 
void setup() {
  Serial.begin(9600);
}
 
tmElements_t tm;
 
void loop() {
  RTC.read(tm);
  Serial.print(tm.Hour);
  Serial.print(":");
  Serial.print(tm.Minute);
  Serial.print(":");
  Serial.println(tm.Second);
  delay(1000);
}

Mikro SD kártya

A mikro SD kártya olvasó tipikusan nem része a kezdőkészleteknek, de száz Ft-os nagyságrendért vásárolhatunk. Az mikro SD kártyára írhatunk és olvashatunk is, így az kimeneti és bemeneti eszköz egyszerre. A kommunikáció az ún. SPI-n (Serial Peripheral Interface, soros külső interfész) keresztül történik.

Bekötés:

  • Micro SD 3v3 -> Arduino 3.3V
  • Micro SD CS -> Arduino 10
  • Micro SD MOSI -> Arduino 11
  • Micro SD CLK -> Arduino 13
  • Micro SD MISO -> Arduino 12
  • Micro SD GND -> Arduino GND

Kód: a File -> Examples -> SD -> ReadWrite alapján készült:

#include <SPI.h>
#include <SD.h>
 
void setup() {
  Serial.begin(9600);
 
  SD.begin(10);
  File outFile = SD.open("test.txt", FILE_WRITE);
  outFile.println("hello world");
  outFile.close();
 
  File inFile = SD.open("test.txt");
  while (inFile.available()) {
    Serial.write(inFile.read());
  }
  inFile.close();
}
 
void loop() {}

Teszt: helyezzünk be egy mikro SD kártyát, töltsük fel a programot, és figyeljük a Serial Monitort. Utána számítógépen ellenőrizzük a létrehozott fájlt. (Megjegyzés: nehézkesen sikerült csak működésre bírni; két darab 32 GB-os kártyára nem sikerült írni, végül egy 1 GB-osra sikerült, de arra sem egyből.)

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License