BASCOM telepítése, és az első program feltöltés Arduino-ba

Az ingyenes, méretkorláttal működő verzió itt tölthető le:
http://www.mcselec.com/index.php?option=com_docman&task=doc_download&gid=139

A letöltött telepítő program telepítése nem bonyolult, íme a telepítés fázisai:

Az újraindítás követően már indíthatjuk is a programot!

Itt most ugyanazt a feladatot fogjuk végrehajtani, mint az Arduino C programozási környezetben. Megírjuk a LED villogtató programot, rátöltjük az Arduino Uno R3-ra.

Keressük meg az ikont az asztalon, és indítsuk a programot:

Nagyjából így néz ki a felület:

Ha kézzel akarod bepötyögni a programot, akkor a File menü New menüpontját kell kiválasztanod, illetve nyomd meg a fenti képen bekarikázott ikont. A LED villogtató programot nem találtam meg az alábbi formában, ezért magam írtam be:

Ez a program a B port ötös kivezetését kimenetnek állítja be, és egy végtelen ciklusban 1-et és egy másodperc után 0-át kapcsol rá. Most még nem foglalkozom a program részletivel, a cél az, hogy rátöltsem az Arduino Uno-ra. Ehhez pár dolgot még be kell állítani. Elsőként azt kell megmondani a programnak, hogy milyen chip-re fog kerülni a program. Ezt az Option menüben a Chip almenüpontban a Compiler fülön tudjuk megtenni:

A megjelenő panelen válasszuk ki az ATmega328p chip-nek megfelelő definicíós állományt:

Tapasztalatom szerint semmi máshoz nem kell nyúlni. Ezt követően a kommunikációs fülön állítsuk be a kommunikációs sebességet 9600 boud-ra és a kristály frekvenciát 16000000 (16Mhz)-re. Bár erre ebben a programban nem lesz szükség, később biztosan zavart okozhatna az esetleges soros kommunikációban. Jobb most túllenni rajta:

Következik a programozó beállítása a programozó fülön. Itt három dolgot kell beállítani. Egyrészt az Arduino programozót az Arduino Uno-hoz, másrészt a kommunikációs portot, harmadrészt a portsebességet.

Ha bármelyik paramétert rosszul állítod be, nem tudsz majd programot beégetni az Arduino Uno-ba, illetve ha később veszel programozót, akkor egy tetszőleges chip-be. A port száma attól függ, hogy hová dugtad a gépen az Arduinodat. Ha nem tudod hol van, akkor a Windows eszközkezelőben lehet megnézni:

Jöhet a program lefordítása. A képen látható program biztosan hibátlan, ezért most nem szükséges leellenőrizni, de ha szeretnéd megtenni, kattints ide:

A program lefordításához ide kell kattintani:

Ezt követi a lefordított kód betöltése a chip-be:

Itt először egy újabb ablak jelenik meg:

Első lépésben le kell kérdezni a chip-et, hogy milyen típusú. Ezt később már el lehet hagyni, ha egymást követő program változatokat akarsz ugyanarra az alaplapra (Pl. Arduino Uno-ra) vagy chip-re beégetni, és nem húzgálod ki a kütyüdet az USB portból. Ezt követően jön a feltöltés a chip-be, és készen is vagyunk. A led villog, boldogság!

A következő részben megismerheted, hogyan lehet az Arduino UNO R3 (és az ATega328 chip) kivezetéseit használni.

GY-302 BH1750 fényérzékelő modul

Egy kis elmélet! Mi a megvilágítás azaz a lux (Wikipédia-ban található infót másoltam ide)?
A megvilágítás az adott területre eső fényáram mértékegysége. A fényáram (mértékegysége a lumen) elképzelhető úgy, mint a jelen lévő látható fény összege, a megvilágítás pedig, mint az adott területre eső fényáram intenzitása. Nekem nagyon kellett koncentrálnom, hogy ezt felfogjam. Sokkal többet fog mondani a lényegről az alábbi táblázat.

Egy lux az a megvilágítás, amelyet 1 lumen fényáram 1 négyzetméteren létrehoz. Egy adott mennyiségű fény nagyobb területen elosztva halványabban fogja azt megvilágítani, azaz a megvilágítás mértéke fordítottan arányos a terület nagyságával. Ezért van az, hogy a zseblámpa fényét mérve a lux mérő közelről többet mutat, mint távolabbról. Példák a lux értékekre, hogy el tudjuk képzelni.

MegvilágításMegvilágított felület:
10−4 luxHoldtalan, borús éjszakai égbolt
0,002 luxHoldtalan, tiszta éjszakai égbolt (airglow)
0,27–1,0 luxTelihold egy tiszta éjszakán
3 luxA színérzékelés határa
3,4 luxszürkület kezdete tiszta égbolt esetén
50 luxLakás nappalijának megvilágítása
80 luxIrodaépület folyosójának/mosdójának világítása
100 luxNagyon sötét, beborult nappali égbolt
320–500 luxIrodai megvilágítás
400 luxNapfelkelte, vagy napnyugta tiszta időben
1000 luxBorult égbolt
10 000–25 000 luxTeljes napsütés (nem közvetlen)
32 000–130 000 luxKözvetlen napfény

A BH1750 egy digitális környezeti fényérzékelő , amelyet általában a mobiltelefonokban használnak a képernyő fényerejének a környezeti megvilágítás alapján történő beállítására. Az adatlap szerint a megvilágítást közvetlenül LUX-ban adja meg. Ha ez igaz, akkor közvetlen napsütésben már nem lesz jól használható, hiszen a fenti táblázat szerint a közvetlen napsütés 32-130ezer lux, míg ennek a szenzornak a felső mérési határa “csak” 64 ezer lux!

A modulba beépített szenzor adatai:

  • Tápfeszültség: 2,4–3,6 V
  • Áramfelvétel: 0,12-0,2mA
  • Mérési tartomány: 1-65535 lux
  • Kommunikáció: I2C busz
  • Pontosság: +/- 20%
  • Az IR (infravörös) sugárzás nagyon kis hatással van a mért értékre
  • A szenzor érzékelési tartománya hasonlít az emberi szem érzékelési tartományához.

Mint az látható a műszaki adatokból, a BH1750 chip nem köthető össze közvetlenül az Arduino-val, mert nem működik 5V-ról. Viszont az általam beszerzett modul igen, mert raktak rá 5V -> 3,3V feszültség átalakítót (tápegységet). Találtam egy kapcsolási rajzot a modulról a neten. A MOSFET-et nem ismertem fel a modul fényképén, ezért gyanítom, hogy ez nem teljesen ugyanaz, ami az én modulom kapcsolási rajza. Az látszik azonban a fényképekről, hogy az SCK és SDA kivezetéseket közvetlenül a BH1750 chip-re kötötték. Vagyis ezek a chip kivezetések elviselik az 5V feszültséget, amit az Arduino-ról kapnak.

Modul Leírása
Típus: GY – 302
Méret: 13,9 mm X 18,5 mm
Az eredeti BH1750FVI ROHM chip-et tartalmazza
Tápfeszültség: 3-5 v

Még néhány egyszerű információ a chip-ről.
Kétféle üzemmód állítható be.
Egyszeri mérés (az érzékelő egy mintát vesz a mérési adatokhoz, és kikapcsolt üzemmódba kerül)
– Alacsony felbontású mód – (4 lux pontosság, 16 ms mérési idő)
– Magas felbontású mód – (1 lux pontosság, 120 ms mérési idő)
– Magas felbontású mód – (0,5 lux pontosság, 120 ms mérési idő)
Folyamatos mérés
– Alacsony felbontású mód – (4 lux pontosság, 16 ms mérési idő)
– Magas felbontású mód – (1 lux pontosság, 120 ms mérési idő)
– Magas felbontású mód – (0,5 lux pontosság, 120 ms mérési idő)
A chip alacsony fogyasztású módba kapcsolható és feléleszthető egy-egy paranccsal.

A példa:

#include <Wire.h>
#include <math.h> 
int BH1750address = 0x23; //i2c address
 
byte buff[2];
void setup()
{
  Wire.begin();
  Serial.begin(9600);
}
 
void loop()
{
  int i;
  uint16_t val=0;
  BH1750_Init(BH1750address);
  delay(200);
 
  if(2==BH1750_Read(BH1750address))
  {
    val=((buff[0]<<8)|buff[1])/1.2;
    Serial.print(val,DEC);
    Serial.println("lux");
  }
  delay(150);
}
 
int BH1750_Read(int address) //
{
  int i=0;
  Wire.beginTransmission(address);
  Wire.requestFrom(address, 2);
  while(Wire.available()) //
  {
    buff[i] = Wire.read();  // receive one byte
    i++;
  }
  Wire.endTransmission();
  return i;
}
 
void BH1750_Init(int address)
{
  Wire.beginTransmission(address);
  Wire.write(0x10);//1lx reolution 120ms
  Wire.endTransmission();
}

Ezt a példa programot a neten találtam. A különféle üzemmódok és a chip elaltatása és felélesztése BH1750_Init függvényben látható módon lehetséges. A Wire.write függvény paramétere a szükséges parancs kódja. A példában megadott kód épp folyamatos mérést állít be alacsony felbontású módban. A lehetséges parancs kódok a program készítője szerint:
0x10 – folyamatos mérés magas felbontás (1lux)
0x11 – folyamatos mérés magas felbontás (0,5lux)
0x13 – folyamatos mérés alacsony felbontás (4lux)
0x20 – egyszeri mérés mérés magas felbontás (1lux)
0x21 – egyszeri mérés mérés magas felbontás (0,5lux)
0x23 – egyszeri mérés mérés alacsony felbontás (4lux)
0x00 – power down mód (0,01 mikroA áramfelvétel)
0x01 – power on mód (140mikroA áramfelvétel)
0x07 – reset

Azt hiszem a publikált magyarázatok nem teljesen pontosak. Megnéztem a chip adatlapját, és ott a magas felbontású üzemmódnál egy automatikus módról beszél, ami a megvilágítás függvényében állítja be a chip felbontását. Mivel nem volt szükségem ezekre a részletekre, nem foglalkoztam vele. Ha azonban valakinek pontosabb mérésekre lenne szüksége, érdemes az adatlapot áttanulmányozni, és nem elhinni amit itt lát leírva.


Forrás: <https://www.aliexpress.com/item/32765542002.html?spm=a2g0s.9042311.0.0.399d4c4dNNJi3w>

MPS20N0040D nyomásmérő vízszint méréshez

Forrás:https://win.adrirobot.it/sensori/MPS20N0040D/MPS20N0040D_pressure_sensor.htm

A feladat az volt, hogy egy kútban található vízszintet mérjem meg. A gyűrűs kutakban a víz összegyűlik a környező területen található vizekből. Ezt a locsolásra hasznát szivattyú kiszívja. Általában lassabban telik meg a kút, mint amilyen gyorsan a szivattyú kiüríti a vizet, és szerettem volna látni, mi is zajlik ott alul. Az elképzelés szerint egy nyitott végű szilikon csövet engedek le a kút legaljára, a cső másik végét pedig a szenzorhoz csatlakoztatom. Amikor a vízszint növekszik a kútban, a szilikon csőben is egyre magasabbra hatol felfelé a víz, és összenyomja a felette lévő levegőt, aminek így nőni fog a nyomása. Az elv egyszerű, és ez az eszköz alkalmasnak látszik a feladatra.

Műszaki adatai:
Méret: 19 * 18mm (2mm rögzítőfurat)
Feszültség: 3,3–5 V
Nyomás: 0-5,8psi (0-40KPa)
Kimenet: 24 bites ADC

Az eszköz adatlapján psi-ban megadott nyomásértéket nem értem. Az egyes nyomás mértékegységek átszámítására szolgáló táblázatok szerint az 5,8 psi szinte vákuum. Azonban a 40KPa (kilopascal) sem stimmel, mert a normál légköri nyomás 100KPa nyomásnak felel meg. Lehetséges, hogy a megadott határok nyomás változásra vonatkoznak?? Ezért kicsit utána számoltam, mit is fog ez az eszköz mérni. A mérést nyitott érzékelő csővel szabad levegőn (normál légköri nyomáson) csináltam. A légköri légnyomást inkább a milibar mértékegységben szoktuk használni. A tengerszinten mérhető normál légköri nyomás 1013,25 mbar (1,01325 bar).
A netről sikerült kiderítenem, hogy 1 bar =14504 psi!
Az eszköz adatlapjaiból nem sikerült egyértelműen kiderítenem, hogy milyen értéket lehet  kiolvasni a 24 bites A/D konverter kimenetéből normál légköri nyomás esetén, ezért egyszerűen megmértem. Egy adott pillanatban az általam mért adat 1421158. Ránézésre ez majdnem pont 100-szor akkora, mint az egy bar nyomás psi-ben megadott értéke. Mivel volt nálam egy légnyomásmérő (BMP180), annak értékét is meg tudom adni, éppen 998mbar értéket mutatott.  Ezért feltételezem, hogy a szenzorból kiolvasott adat psi-ben adta meg az aktuális légköri nyomást. Mivel nem tengerszinten voltam, a kapott adatnak kisebbnek kell lennie, mint a tengerszinten mérhető normál légköri nyomásnak, így ez akár igaz is lehet.

Ellenőrizzük le:
14311,58 psi /14504,00 psi =0,986733
1013,25 mbar x 0,986733 = 999,8 mbar

Igen nagy eséllyel valóban az én lakhelyemen éppen aktuális légnyomást mutathatta az eszközből kiolvasott érték (Kistarcsán lakom, ami magasabban van mint a tengerszint, így kisebb a légnyomás). Az is lehet, hogy ez csak véletlen, de szándékos tervezés eredménye is lehet. Ebből adódóan a légnyomást úgy kaphatjuk meg az eszközből kiolvasott értékből, hogy elosztjuk 1431,43-al (1431158/1431,43=999,8 mbar). Ezt a váltószámot nem kiszámoltam, hanem kiolvastam egy a neten fellelt átváltási táblázatból.
Sajnos csak egy szenzorom volt, így nem tudtam kipróbálni, hogy ez a gyári beállítás mennyire azonos két szenzor példányon. Feltételezve, hogy két legyártott szenzor közel azonos értéket mutat, a megadott állandóval lehet dolgozni.

Az érzékelőn belül egy ellenállásokból kialakított Wheatstone-híd található. A belső szerkezetről nincs információm, de feltételezem, hogy az ellenállások egy membrán felületén lettek kialakítva. A membrán a nyomás hatására változtatja az alakját, és így megváltozik az ellenállások értéke is. A Wheatstone híd működési elvéből adódóan nagyon kicsi ellenállás változás hatására is jelentős feszültség változással reagál. Valahol olvastam, hogy ebben a szenzorban 0-25 mV feszültség változás keletkezik a teljes mérési tartományban.

Ez annyira kicsi jel, hogy közvetlenül nem mérhető, erősíteni kell, és még az erősítés is kevés, mert a feszültség változás a légköri nyomás változását figyelembe véve még egy 0-5V tartományra erőstett jel esetén is rendkívül kicsi lesz. Így került a szenzor mellé egy HX710-es jelerősítő és nagy felbontású A/D átalakító. Adatlapja szerint 128-szoros erősítő van benne, és az A/D felbontása 24bites. Nem véletlen a 128-szoros erősítés: 128 x 25mV = 3200mV. Ha 3,3V tápfeszt adunk az ADC-nak, akkor még éppen kisebb a mérendő feszültség, mint a tápfeszültség. Így néz ki a kapcsolás:

Akkor most nézzük meg, hogyan is lehet Arduino-val használni a szenzort. Pofon egyszerű, mert van hozzá több könyvtár is. Írjuk be a könyvtár kezelőbe a HX711 nevet (ez az ADC típusjelzése), és a következő találatokat kapjuk:

Azt, hogy miért nem a HX710 névre kerestünk, az eredeti cikk írójától érdemes megkérdezni, de az biztos, hogy arra chip-re, amit beépítettek az áramkörbe, az Arduino könyvtár kezelője semmit nem dob ki. Viszont a HX711 ugyanazt csinálja, ezért ez bennünket nem zavar. Én rögtön a legelsőt választottam. A könyvtár telepítése után kapunk példa programokat:

Ez itt azonban már az általam kialakított verzió, amivel légnyomást mértem:

const int LOADCELL_DOUT_PIN = 2;
const int LOADCELL_SCK_PIN = 3;
HX711 scale;

void setup()
{
  Serial.begin(9600);
  scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN);
   scale.set_scale(1431.f); 
}

void loop() {
  if (scale.is_ready())
  {
    long meres = scale.read();
    Serial.print("HX711 reading: ");
    Serial.println(meres);
  } else {
    Serial.println("HX711 not found.");
  }
  delay(2000);
}

Láthatóan nem egy bonyolult program. A kiválasztott könyvtár alapvetően egy mérleg számára készült. A mérlegekben nyúlásmérő ellenállásokat alkalmaznak, és feltehetőleg ugyanezt az ADC chipet. Nekem már annyi is bőven elég amit ebben a programban találtam, de a teljesség érdekében a dokumentációból és részben a könyvtár forráskódjából még kiderült néhány függvény, ami hasznos lehet.
A mérlegek többféle mértékegységben is működnek, így a mért értéket skálázni kell, valamint meg kell határozni egy olyan mérési eredményt, amit üresen mérünk (a mechanika súlya üresen), amihez 0 súly kijelzése tartozik. Ezeket az adatokat tapasztalati úton a kész szerkezettel történő mérésekkel lehet legegyszerűbben meghatározni (hitelesítés), és beégetni a programba.

Ehhez találunk néhány függvényt:
scale.set_scale(x)
   beállítja a skála értékét. A megadott számmal osztja a
mérés eredményét, de csak a scale.get_unit() függvény
által visszaadott értékben
scale.get_scale()    Visszaadja a beállított skála értéket
scale.set_offset(x) Beállítja az offset értéket, a scale.get_value() függvény által
visszadott értéket úgy állítja elő, hogy a mért értékből kivonja
az itt beállított értéket.
scale.get_offset()   Visszaadja a beállított offset értéket

Mérési eredmények lekérdezésére szolgáló függvének:
scale.read()
                    Elvégez egy mérést és visszaadja a mért értéket
scale.read_average(x)    Elvégez x db mérést és azok átlagát adja vissza
get_value(x)           Elvégez x db mérést és ezek átalgából levonja
a beállított offset értéket
get_units(x)                    Elvégez x darab mérést ezek átlagából levonja
az offset értékét, majd a kapott adatot elosztja
a skála értékével

Egyéb függvények:
scale.tare(x)
                   Elvégez x db mérést, és az így kiszámított átlagot
beállítja offset értéknek (ez lesz a 0 érték a
mérlegen, TÁRA mérleg funkció)
scale.power_down()  Alacsony fogyasztású módba kapcsolja a szenzort
scale.power_up()         Normál fogyasztású módba kapcsolja a szenzort

Vizszint mérő próbaprogram:

Következzen egy gyakorlati megvalósítás, tényleges vízszint mérés. Vettem egy méteres szilikon csövet. Tudjátok, az az átlátszó cső, amit különböző belső átmérővel lehet kapni. A legkisebb átmérő 2,5mm. Ez sajnos nekem még mindig nagy, mert a szenzoron kialakított csövecske 2mm-es. Ideiglenes megoldásként a szenzor csövére szikszalagot tekertem. Nem vált be, mert magától lecsúszott másnap reggelre, de az alábbi program kipróbálásához elég volt. Előkaptam egy kiürült üdítős üveget (lehetett volna vodkás üveg is, de nem iszom alkoholt). Megtöltöttem vízzel, ami pont 13cm vízmagasságot eredményezett. Azért ennyit, mert a feleségem műanyag vonalzója pont ilyen hosszú. Szóval betöltöttem az Arduino UNO-ra az alábbi program 0. verzióját, csak ott még nem voltak vízszint számítások, csupán a natúr nyomás érték. A működő programnál kaptam egy légnyomás értéket, ezt felírtam, és ledugtam a csövet 13cm mélységbe. A kapott adatot szemrevételeztem, és megállapítottam, hogy a nyomásmérő érzékenysége 10000-es nagyságrendeben változott. Szuper. Aztán már csak osztogatni kellett, és kialakult a program. A program elindításakor a cső szája maradjon szabadon, hogy legyen egy nulla vízszintet jelentő referencia értékünk. Itt már 100-el osztottam, hogy áttekinthetőbb legyen a kapott adat. Integerre is alakítottam, mert a kapott szám már elég stabil lett így, nem változott állandóan az utolsó egy-két számjegyben. Így a 13 cm mélységben pont 800-al nőtt a nyomás, tehát egy cm 57 psi-nek adódik. Nem kell sokat agyalni rajta, ennyi és kész. Arra számítok, hogy a kútba leeresztett kb 12m-es cső esetén ez már nem 57 lesz, mert a cső térfogata a nagyobb hosszúság miatt lényegesen nagyobb és a levegő összenyomható. Így a végleges merőműszeremben egy kisebb számot kell megadni, de ez csak várakozás. Az a bizonyos 57 elég jó kiinduló alap.
Sajnos ennek a tapasztalati számnak a megmagyarázására nem vagyok képes. Az biztos, hogy az 1m-es csőben a levegőt kb 0,9-ed részére nyomtuk össze. Annyit tudunk még, hogy állandó hőmérsékleten a térfogat és a nyomás szorzata állandó. Ebből következik, hogy ha a térfogat a 0,9 szeresére csökken, akkor a nyomásnak ugyanilyen arányban nőnie kell. Így a kb. 14504Psi normál légköri nyomás 14504/0,9=16115Psi-re nőtt volna meg. A növekedés elméletben 1611Psi. A szenzor ennek a változásnak mint legfelül láthattuk a 100-szorosát adja vissza, amit én a programban 1000-el osztottam. Így a változásnak 161-nek kellett volna lenni. Ezzel szemben 800 volt a változás. Szóval az elméletem nem jó. Ha valakinek sikerül igazolni az 57-et, az szóljon!
Még egy fontos megfigyelés: feltehetőleg a szenzor és a szilikoncső illesztése nem sikerült tökéletesen, és szivárgott a levegő, mert a nyomás a cső víz alá dugását követően nagyon lassan csökkenni kezdett. Kb. 5 perc alatt vissza is állt a normál légköri nyomásra. Remélem ez nem a szenzor tulajdonsága, mert akkor nem lehet vele így vízszintet mérni. Később ki fog derülni! Megírom majd az eredményt!

include "HX711.h"
const int LOADCELL_DOUT_PIN = 2;
const int LOADCELL_SCK_PIN = 3;
long nyomas;
int nullaszint;
int vizszint;
HX711 scale;

void setup() {
  Serial.begin(9600);
  scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN);
  nyomas = scale.read_average(20);
  //az eslő mérést úgy végeztem, hogy a szilikoncsövet nem dugtam be a vízbe
  //ekkor gyakorlatilag a légnomást mérem meg. Ha bedugom a csövet a vízbe
  //a légnyomás növekedni fog, ahogyan a víz egyre magasabbra hatol a csőben
  //és összenyomja a levegőt
  nullaszint=(int) (nyomas/1000);
  Serial.print("Elso meres:");
  Serial.println(nullaszint);
}

void loop() {
  nyomas = scale.read_average(20);
  vizszint=((int)(nyomas/1000)-nullaszint)/57;
  //a szilikon csövet egy pohárba dugtam, az 57 osztó egy gyakorlati szám
  //amit egy konkrét méréssel állapítottam meg. A pohárban 14cm víz volt
  //és a nyomás különbség pont 800-ra adódott. Ebből egy cm 57.
  Serial.print("Vizszint:");
  Serial.print(vizszint);
  Serial.println("cm");
  delay(2000);
}

BMP180-M légnyomásmérő

Egy kicsike elmélettel kezdünk. A tengerszinten a légnyomás 1013,25 hPa (hektopaszkal), illetve 1013,25 mbar (milibar), ami egyébként 1 atm (atmoszféra). Sok a mértékegység, és gyakorlatban a légköri nyomásra a mbar-t hallom a legtöbbször. A légnyomás a magassággal nem lineárisan csökken. Pár száz méterig számolhatunk 0,1 mbar/méter csökkenéssel, tehát a Gellért hegy tetején (235m), már csak kb 998 mbar légnyomás mérhető. Lakóhelyemen az átlagos légnyomás kb. 995 mbar, ebből már sejthető, hogy nem a Mátrában lakom.

Ennek a modulnak a lelke egy igen kisméretű 3,6 x 3,8 x 0,9 mm-es szenzor.

Műszaki adatai:

  • 1,8 V – 3,6 V tápfeszültség
  • Alacsony energiafogyasztás – 0,5uA (másodpercenként egy mérés esetén)
  • I2C interfész
  • Maximális I2C sebesség: 3.5Mhz
  • Nagyon alacsony zaj – akár 0.02hPa (17cm)
  • Nyomástartomány: 300hPa – 1100hPa (+ 9000–500 m)
  • Súly: 1,18 g
  • Méret: 21mm x 18mm

Áramköri kapcsolás:

Mint látható, 3,3V-os tápfeszre lett kötve a rajzon. Én ezt nem vettem észre, és 5V tápfeszre kötöttem. Számomra is meglepő módon nem ment tönkre, működött két éven keresztül, amikor szétszedtem a kapcsolást!

Itt találtam hozzá programcsomagot:

A példa programhoz sok magyarázat nem szükséges.

#include <Wire.h>
#include <Adafruit_BMP085.h>

Adafruit_BMP085 bmp;

void setup() {
  Serial.begin(9600);
  if (!bmp.begin()) {
                Serial.println("Nem talalhato ervenyes BMP180 erzekelo!");
                while (1) {}
  }
}

void loop() {
    Serial.print("Homerseklet = ");
    Serial.print(bmp.readTemperature());
    Serial.println(" *C");
    Serial.print("Legnyomas = ");
    Serial.print(bmp.readPressure()/100);
    Serial.println(" mBar");
    /* Magasság kiszámításához feltételezzük, hogy a
    standard légköri nyomás 1013,25 mbar */
    Serial.print("Magassag = ");
    Serial.print(bmp.readAltitude());
    Serial.println(" meter");
    Serial.println();
    delay(5000);
}

Dallas DS18B20 egyvezetékes hőmérő

A Dallas által fejlesztett hőmérő hatalmas előnye, hogy az információ digitálisan áramlik a vezetékeken, ezért a vezeték hossza nem befolyásolja a mért értéket. Ennek természetesen csak akkor van jelentősége, ha a vezeték hosszú. Márpedig a DS18B20 hőmérő chip a gyári adatlap szerint akár 400m távolságból is működhet. Egy vezetékes (onewire) hőmérőnek nevezik, mivel egyetlen adatvezetéken zajlik a kommunikáció, ugyanazon a vezetéken, ahol a tápfeszültséget is kapja a chip. Valójában ez két vezeték, de azért nem nagy hazugság az elnevezés!
Másik nagy előny, hogy ugyanarra a vezetékre sok-sok chip fűzhető fel, én 8db-al próbálkoztam eddig, és működött! Az általam kipróbált legnagyobb vezeték távolság csillagpont topológiával (egy chip, egy vezeték) 25+20+10+5m.

8 chip-et egyszerre csak 20cm vezetékkel próbáltam. Természetesen jól működött. A kísérlet célja az egyes chip-ek hőmérséklet érték szórásainak vizsgálata volt. Úgy 0,5 fok celsius értéken belül voltak az értékek. Ha nem kell preciziós mérés, akkor ez tökéletesen megfelelő.

Technikai adatok:
tápfeszültség: 3,0 – 5,5V
mérési tartomány: -55 – +125 fok celsius
pontosság +/- 0,5 fok celsius
mérési idő: 750ms


Az alábbi ábrákon látható, hogy kell bekötni a chip-et egy arduino-ra:

Több eszközt is használhatunk egyszerre, ezeket párhuzamosan lehet kötni. Azt, hogy egyszerre hányat lehet egyszerre felfűzni, nem sikerült kiderítenem. Jómagam 8 chip-et vásároltam meg, így azt biztosan tudom állítani, hogy 8 chip működik egyszerre normál módban egy vezetéken. Az ábrán látható bekötés három vezetéket használ. Mivel nem kellett nagy távolságra kábeleznem (max 25m), nem volt lényeges a vezeték ára, így 4 eres riasztó kábelt használtam. Működik azonban UTP kábellel is. A vezeték hosszal voltak problémáim, első próbálkozásra 25m kábelre már két chip-et nem tudtam rátenni. Később kiderült, hogy felhúzó ellenállás értékét választottam meg rosszul. Kezdetben 10Kohm akadt a kezembe, azzal működött egy kb. 2m-es vezetékkel. A 25m-es vezetékkel nem ment a 10Kohm-on, de amikor kicseréltem 4,7K-ra, már működött. Jóval később fejtettem meg, hogy miért nem ment egyszerre két chip-el. A felhúzó ellenállást 1,5K-ig csökkentettem, és biztonsággal működött két chip egyszerre. Hosszabb vezetékem nem volt, így a 25m-el van tapasztalatom.

Lehetséges a chipeket úgynevezett parazita módban is használni, amikor is ténylegesen csak 2 vezetékes a rendszer, és az adatvezeték végzi az energia ellátást is. A kommunikációk ideje alatt az adatvezeték természetesen 0 és tápfesz között ugrál, de ezt a chip-ben elhelyezett belső kapacitás elméletileg áthidalja, és amíg a vezetéken 0 a feszültség ellátja energiával. Nem próbáltam ki, mert az én körülményeim között mindegy volt, hogy 3 vagy csak 2 eret kell bekábelezek.

Nézzük meg hogyan is lehet lekérdezni egy chipet:
Mint azt a DHT11-nél is láttuk az 1-wire kommunikáció lényege, hogy valahogyan jelezzük, hogy infót várunk vagy adunk. Itt sincs ez másként. A DATA vezetéket a mikrovezérlő 0-ra húzza le 480 – 960 mikrosec időtartamban (master reset). Ezt követően a chip-ek mindegyike egy úgynevezett „presente” jelenlét jelet ad ki az alábbi ábra szerint:

A probléma csak az, hogy több chip is lehet a vonalon, ezek semmit nem tudnak egymásról, csak szolgai módon lehúzzák a vezetéket 0-ra. Viszont a mikrovezérlő legalább tudja, hogy van ott valaki. Persze ha tudjuk, hogy csak egy eszköz van a vonalon, akkor egyszerűbb a helyzet, de most tegyük fel, hogy többen vannak. Ekkor feltétlenül meg kell tudni, hogy kik vannak a vonalon. Természetesen minden egyes chip-nek van egy gyárilag beégetett egyedi címe, így kizárt hogy két tök azonos chip legyen a vonalon. Már csak meg kell állapítani, hogy mik is ezek címek. Nyilván ha már valahogyan kiderítettük a címet, akkor fogunk tudni beszélgetni az adott chip-el, de most még nem tudjuk.

Így hát a master reset után a mikrovezérlő elkezdi a felderítést. Ehhez a vonal 0-1 megszabott időzítések szerinti „rángatásával” kezd kommunikálni. Sok parancsot adhat, ezek kódját később egy táblázatban megmutatom. Most számunkra a „search ROM” nevű utasítás a fontos, aminek a kódja F0h. Ennek algoritmusát nem fontos pontosan megérteni. A lényeg az, hogy amikor ezt kiadja a mikrovezérlő, minden chip kiteszi a DATA vezetékre a saját címének 0. bitjét, aztán annak invertáltját. A mikrovezérlő ezt követően már tudja, hogy van-e a vezetéken olyan chip aminek 0. bitje 0 vagy 1. Ezt követően a mikrovezérlő közli a chip-ekkel, hogy azzal akar társalogni a továbbiakban, amelyiknek a 0. bitje adott értékű, mondjuk éppen a nullával kezdődő címekkel foglalkozik. Ekkor már csak azok a chip-ek figyelnek, melyek címe nullával kezdődik, és jöhet a cím következő bitje. Ezt a három lépést fogja a mikrovezérlő ismételgetni mindaddig, amíg egyetlen eszközre nem szűkíti a kört. Ha pedig egy eszközt már felderített, kezdi újra a serch ROM parancsot, de már csak a maradék eszközökre „koncentrál” és felderít egy újabb chip-et. A pontos algoritmust nem sikerült megértenem, de talán nem is lényeges, hiszen van kész program könyvtár, ami ezt a bonyolult algoritmust megvalósítja. Az máris látható, hogy ez a felderítési folyamat, nem is olyan gyors. Ha több chip is van a vonalon, akár másodpercekig is eltarthat. Erre érdemes odafigyelni, és ha nem akarunk sok időt vesztegetni a chipek címeinek kiderítésére, akkor a kiderített címeket érdemes tárolni egy „setup” folyamatban és a továbbiakban már csak használni a programban a kiderített adatokat. Akár az Arduino EEPROM-jába be is írhatjuk, hogy kikapcsolást követő újbóli bekapcsoláskor se kelljen ezzel foglalkozni.

Tegyük fel, hogy már tudjuk egy adott chip címét, és azzal az egy szerencsés kiválasztottal akarunk beszélgetni. A folyamat teljesen azonos, kiadunk egy master resetet, megjön a presente jelzés, és már adhatjuk is ki parancsunkat. Ezek a parancsok a következők lehetnek:
Read ROM (ROM olvasás) [33h] : ez az utasítás lehetővé teszi, hogy a mikrovezérlő kiolvassa a DS1820-as 8 bites család-kódját és a  48 bit hosszú egyedi címét és ezen adatokra vonatkozó CRC kódot. Mindezek együtt 64 bit hosszú adatsorozatot eredményeznek a vonalon a parancs kiadását követően. A CRC kód igen fontos, mert a vezetékezés hosszától függően sok zaj lehet a vezetéken, és ez hibás olvasásokat eredményez, ami fel kell fedezni és szükség esetén ismételni kell. Ez az utasítás csak akkor ad „értelmes” eredményt, ha egyetlen chi csatlakozik a buszra!  
Match ROM (ROM kijelölés) [55h] : az utasítás követő 64 bites adatsorozat kijelöli az aktív eszközt, ha több eszköz csatlakozik a buszra. Az az eszköz lesz aktív, amelyiknek a gyárilag beégetett címe megegyezik a kiküldött adatsorozattal. A többi eszköz a következő master reset-ig  inaktív marad.
Skip ROM (ROM „átugrás”) [CCh] : ez az utasítás időt takarít meg, ha egy eszköz csatlakozik a buszra, mert ezt követően rögtön jöhet egy vezérlő utasítás, amiről hamarosan szó lesz.
Search ROM (ROM keresés) [F0h] : ezt az utasítást már ismerjük, nem írnék többet róla
Alarm Search (riasztás keresés) [ECh] : erre az utasításra az eszköz akkor válaszol, ha az utolsó mérésnél a riasztási feltétel igaz. A riasztási feltétel akkor igaz, ha a mért hőmérséklet egy előre beállított értéknél nagyobb (TH regiszter) illetve egy másik értéknél kisebb (TL regiszter). Termosztátok esetén sok kommunikációt lehet ezzel megtakarítani.

A ROM parancsok után a chip-nek vezérlőutasítást kell küldeni. Ezek a vezérlő utasítások a következők lehetnek:
Write Scratchpad („regiszterek írása”) [4Eh] : az utasítást követő két byte az chip TH és TL regiszterének tartalmát írja felül. Ezzel beállíthatók a riasztási hőmérséklet értékek. Sajnos ezek 8 bites regiszterek, ezért csak egész hőmérsékleti értékek adhatók meg riasztási értekként.
Read Scratchpad („regiszterek olvasása”) [BEh] : a regiszterek tartalmának beolvasása. A 18B20-nak 8db regisztere van, ezeket tudjuk kiolvasni. Az alábbi táblázatban láthatók a regiszterek. Túl sok magyarázat azt hiszem nem kell hozzájuk.

A Temperature LSB/MSB regiszterekben van az utolsó mért hőmérséklet, A TH/TL regiszterekben a riszatási értékek. A config regiszterben pedig a hőmérséklet mérés felbontása, ami a hőmérséklet mérés pontosságát is meghatározza. Az alábbi táblázatokban ezt látjuk:

A gyári alapbeállítás a 12 bites felbontás. Minél kisebb a felbontás, annál gyorsabb a mérési idő. A fenti táblázatban látható, hogy a maximális 12 bites felbontás esetén már csaknem 1 másodperc kell a méréshez. Ez idő alatt a chip áramot is fogyaszt, ezért parazita táplálási módban nem szerencsés egyszerre több chipben is elindítani a mérést. Ha biztosak vagyunk abban, hogy a gyári alapbeállítás van érvényben, akkor a programban nem igazán érdemes figyelni ezeknek a biteknek az állapotát. Én a letöltött példa programban  (lásd később) készen kaptam ennek a regiszternek a figyelését, ezért benne hagytam okulásul. A gyári alapbeállítás megtartásával a a mért hőmérsékletet meghatározó (konverziós) program rész jelentősen lerövidíthető, ami a program áttekinthetőségét javítja. A felbontás csökkentésének más haszna szerintem nincs, hacsak nem irányítunk repülőgépet a mikrovezérlőnkkel, és kell a számítási kapacitás valami nagyon fontos más dologra J
Valójában a fenti adatregiszterek kiolvasása 9 byte-os eredményt ad, mivel a chip a 8 adatregiszter tartalmat kiegészíti egy CRC byte-al. Ez nagyon fontos, mert a kiolvasott értékek a vezetéken a zajok és zavarok miatt sérülhetnek.
Copy Scratchpad („regiszterek másolása”) [48h] : a TH és TL regiszterek bemásolása az E2RAM regiszterekbe, így ez az érték a tápfeszültség kikapcsolása után is megmarad.
Convert T („hőmérséklet mérés indítása”) [44h] : ez a parancs indítja el a hőmérséklet mérést. Ha ezalatt olvassuk a buszt 0-t kapunk, ha kész a konverzió, akkor a buszról 1 olvasható. A konverzió ideje függ attól, hogy milyen felbontásban várjuk a hőmérséklet adatot, de maximum 750 ms.
Recall E2 („E2RAM előhívása”) [B8h]: ez az utasítás bemásolja a scratchpad TH és TL regiszterébe az E2RAM-ban eltárolt riasztási hőmérséklet értéket. Ez a művelet a tápfeszültség bekapcsolásakor automatikusan lezajlik, így nemigazán kell foglalkozni ezzel.
Read Power Suply (tápfesz kiolvasás) [B4h] :  ha a chip a az utasítást követően 1-t helyez a buszra akkor külső tápfeszültségről működik, ha 0-t akkor az adatvonalról parazita módban.
Ebből számunkra a legfontosabb (alap esetben mással nem is kell foglalkoznunk) a Read Scratchpad [BEh] parancs, amivel a chip adat regisztereit tudjuk kiolvasni. Ha nem akarjuk használni a riasztás funkciót illetve elegendő a gyárilag alapértelmezetten 12 bites felbontás, akkor semmi másra nincs szükségünk. Ha ezt a parancsot kiadjuk, akkor 9 byte-ot kapunk eredményül.

A Dallas 18B20 chip egyébként -55 és 125 között mér. Felbontása 12bit!

Itt találtam hozzá könyvtárat: http://www.tweaking4all.com/hardware/arduino/arduino-ds18b20-temperature-sensor/

Az első mintaprogramot később írtam meg, mint a később ismertetésre kerülőt. Sokat küzdöttem azzal, hogy egy bonyolult program sok memóriát foglalt el, és ezért időnként lefagyott, mert ha nagyon kevés a rendelkezésre álló változó memória, ez gyakran előfordul. Így elkezdtem kísérletezgetni, hogyan is lehetne egyszerűsíteni, minimalizálni a programot. A sok közül az egyik ilyen terület a Dallas hőmérő kezelése volt. Azt találtam ki, hogy nem fogom minden egyes bekapcsoláskor (setup()-ban) lekérdezni a chip egyedi címét. Egy programmal kiolvasom, felírom magamnak, és “beleégetem” a programba. A chip csere így körülményesebb, de sebaj. Azt is kihasználhattam a programban, hogy négy Dallas chip-et használtam, de mindet más Arduino kimenetre kötöttem, így egy vezetéken csak egy chip volt. Ekkor vettem észre, hogy utóbbi esetben a chip-et nem is kell megcímezni. Így alakult ki az első mintaprogram. Nincs címzés, hiszen erre nincs is szükség egy hőmérő chip esetén. Egy kicsike hibakezelést is sikerült megvalósítani. Megnéztem a onewire könyvtár függvényeit, és kiderült, hogy a függvények legtöbbjének nincs is visszaadott értéke. Meglepő módon a reset függvény az egyetlen (a read()-en és search()-on kívül) ami visszaad valamit. 0-át ha semmi nincs a vonalon, és 1-et ha van ott Dallas hőmérő. Ezt használtam fel arra, hogy észre vegyem, ha a hőmérő chip adatvezetékét széthúzom. Így a program menet közben felismeri, ha kihúzom, bedugom a chip-et. Észre veszi még a crc hibát (ha sérül az adat a hosszú vezetékeken. Sajnos a hőmérő chip kihúzásakor keletkezik egy fals mérési eredmény, de csak egyetlenegy, aztán jelzi, hogy eltűnt a chip, nincs kivel kommunikálni.
Ennek a programnak van még egy fontos tulajdonsága. Nem használ delay() függvényt, így nem lassítja egy nagyobb program részeként a többi funkció végrehajtását. A program 2 másodpercenként foglalkozik a hőmérővel. Felváltva vagy mérést indít el, vagy kiolvassa az előző mérési eredményt. Ha pl. csak óránként akarnánk mérni, akkor kicsit át kellene alakítani, mert a mérést fél órával a kiolvasás előtt csinálná, ami nem mindig szerencsés.

A forrás:

#include <OneWire.h>          //Dallas DS18b20 hőmérő kezeléshez one-wire busz
OneWire  ds(A0);              // a A0. kivezetéshez kell kapcsolni a chip data kivezetését 
byte ds_data[9];              //kiolvasott adtok tárolására
float homerseklet;            //ebbe a változóba kerül a kiolvasott hőmérséklet
bool meres=true;              //azért, hogy ne kelljen delay()-t használni, az egyik loop() ciklusban utasítjuk a dallas chip-et
                              //hogy mérjen, mig a következőben kiolvassuk a mért értéket. Ez jelzi, amikor csak mérni kell
long ido_tmp=millis();        //segéd változó, ahooz, hogy csak megadott idő után foglakozzon a loop() a dallas hőmérővel 

void setup()
{
  Serial.begin(9600);
  Serial.println("Indul:");
}

void loop() {
  if(millis()>ido_tmp+2000) { //két másodperc után foglalkozunk a hőmérséklet méréss indítással, vagy az eredmény kiolvasással
    if (meres) {              //ha meres=1 akkor arra utasítjuk majd a chip-et, hogy végezzen egy mérést, ha 0 akkor kiolvasunk (else ág)
      if (ds.reset()==1) {    //itt 1-el jelez, ha a reset jelre válaszol egy DS18b20, tehát van a vonalon chip. Ha nincs a vonalon DS18b20, akkor ez 0.
         ds.skip();           //ezzel azt mondjuk a chip-nek, hogy egyedül van a vonalon, nem adunk meg eszköz címet, mindenképpen csinálja amira utasítást kap
         ds.write(0x44,1);    //elinditunk egy mérést a chip-ben
         meres=false;         //ezzel jelezzük, hogy elindítottunk egy mérést, 750msec múlva ki lehet olvasni a mért hőmérsékletet
       }
       else {Serial.println("Nincs DS18b20 a vonalon!");}  //meres változót nem állítjuk false-ra, így várunk amíg lesz chip a vonalon
    }
    else {                    //mivel volt eszköz a vonalon és az előző ciklusban mért is egyet, ki fogjuk olvasni az eredményt
      ds.reset();             //éredekes, de itt már mindenképpen 1 volt a ds.reset() válasza, ha nem volt eszköz a onalon, akkor is,
                              //így itt már nem is vizsgálom. Viszont mivel itt nem vizsgálom, egy fals mérés lehetséges. 
                              //Ennek eredménye 0.00fok, amiről nem lehet megállapítani, hogy nem valós mért érték.
      ds.skip();              //ezzel azt mondjuk a chip-nek, hogy egyedül van a vonalon, nem adunk meg eszköz címet, mindenképpen csinálja amira utasítást kap
      ds.write(0xBE);         // Chip memóriájánbak olvasása következik
      for ( byte i=0;i<9;i++) {ds_data[i]=ds.read();}    // 9 bytot olvasunk ki
      //ds_data[3]=0;         //ez egy mesterséges crc hiba előállítás, hogy meglássuk működik-e a crc vizsgálat. Helyes működéshez ki kell kommentezni ezt a sort
      if ( OneWire::crc8(ds_data,8)!=ds_data[8]) {Serial.println("CRC hiba a kiolvasott adatokban!");}   
      else {
        homerseklet=(float) (((ds_data[1]<<8)|ds_data[0])/16.0);          //mert ertek visszaadása 
        Serial.print("Hőmérséklet:");Serial.println(homerseklet); 
        meres=true;                                                       //ezzel jelezzük, hogy következő ciklusban mérést kell indítani
      }
    }
    ido_tmp=millis();        //pillanatnyi idő tárolása 
  }
}
 

A következő program egy vonalra kötött több érzékelővel zajló kommunikációt valósít meg. Nem én írtam, sajnos már nem emlékszem honnan sikerült letölteni. Abban különbözik az előzőtől, hogy kideríti a chip címét, és a hőmérséklet mérés indításakor és az adatok kiolvasásakor használja is ezt a címet. Ebben is van hibakezelés, de csak a cím felderítéskor ellenőriz, igaz, minden mérés előtt lekérdezi a címet, ami felesleges, ha csak a setup() ban olvassuk ki és tároljuk valahol. Azonban ekkor elveszítjük annak lehetőségét, hogy biztosan tudjuk, hogy ott a chip és működik. Ugyanis csak a reset() függvény ad vissza jelenlét adatot (a search()-on kívül), de az csak azt mondja meg, hogy legalább egy chip van a vonalon.

#include <OneWire.h>
OneWire  ds(A0);            // az A0 kivezetéshez kell kapcsolni a DS18B20 data kivezetést

void setup(void) 
{
  Serial.begin(9600);
  Serial.println("Indul...");
}

void loop() 
{
  byte i;
  byte adat[9];
  byte ds_cim[8];
  float homerseklet;

  if ( !ds.search(ds_cim)) {                           //keresünk egy érzékelőt, ha megtaláljuk, akkor az ds_cim tömbbe kerül az elérési címe
    Serial.println();Serial.println();
    Serial.println("Nincs több érzekelő a vezetéken!");
    Serial.println("Újraindul a keresés!");
    ds.reset_search();                                 //alaphelyzetbe állítjuk a chip keresést
    delay(250);
    return;                                            //újraindul a loop()
  }

   if (OneWire::crc8(ds_cim,7)!=ds_cim[7]) {           //ellenőrizzük az érzékelő címének érvényességét
      Serial.println(" CRC hiba a kiderített címben!");
      return;
  }

  //kiírjuk az érzékelő címét
  Serial.println();
  Serial.print("DS cím:");
  for( i = 0; i < 8; i++) {
    Serial.print(" ");
    Serial.print(ds_cim[i], HEX); 
  }
  //elősször mérünk egyet a címmel kiválasztott chip-en
  ds.reset();                                       // alaphelyzetbe állítjuk a kommunikációt
  ds.select(ds_cim);                                //kiválasztjuk a megadott címmel rendelkező érzékelőt
  ds.write(0x44, 1);                                // Elindítjuk a konverziót
  delay(1000);                                      // legalább 750ms-ot kell várni a mérésre
  //ki fogjuk olvasni a mérési adatokat
  ds.reset();                                       //alaphelyzetbe állítjuk a kommunikációt
  ds.select(ds_cim);                                //kiválasztjuk a megadott címmel rendelkező érzékelőt
  ds.write(0xBE);                                   // elindítjuk a mért értékek kiolvasását (9 byte-ban, amiből az utolsó a CRC)

  Serial.println();
  Serial.print(" Adatok:");
  for ( i = 0; i < 9; i++) {                        //9 byte-ot fogunk kiolvasni 
    adat[i] = ds.read();                            //egy byte kiolvasása
    Serial.print(adat[i], HEX);
    Serial.print(" ");
  }
  //ellenőrizzuk a crc-t és ha jó, kiszámítjuk a hőmérsékletet és kiírjuk
  if ( OneWire::crc8(adat,8)!=adat[8]) 
    {Serial.println("CRC hiba a kiolvasott adatokban!");} 
  else {
    // Aktuális hőmérséklet kiszámítása következik a kiolvasott adatokból, a felbontást is figyeli,
    // bár azt nem állítottuk be, így az a gyári alapértelmezett 12 bit.
    // Az első két byte kell az adatregiszterből a hőmérséklethez
    int16_t raw = (adat[1] << 8) | adat[0];         //összemásoljuk a két byte-ot egy int-be
    byte cfg=(adat[4] & 0x60);                      //a konfigurációs regiszter két felső bitje fogja megmondani a felbontást
    if (cfg==0x00) raw=raw & ~7;                    // 9 bit felbontás (93.75 ms mérési idő kell)
    else if (cfg==0x20) raw=raw & ~3;               // 10 bit felbontás (187.5 ms mérési idő kell)
    else if (cfg==0x40) raw=raw & ~1;               // 11 bit felbontás (75 ms mérési idő kell)
    // default felbontás 12 bit (750 ms mérési idő kell)
    homerseklet=(float)raw / 16.0;
    Serial.println();
    Serial.print(" Homerseklet:");
    Serial.print(homerseklet);
    Serial.print(" C fok");
  }
  delay(2000);
}

Ennek a programnak nagy hibája, hogy delay()-t használ a mérés utáni várakozásra. Ha pl. egy másodpercet is kijelző óra időpontja mögé szeretnénk kiírni a hőmérsékletet, akkor azt tapasztalnánk, hogy a másodperc nem frissül másodpercenként. Miért? Mert egy másodperc a delay() alatt elmegy a semmire. Ilyen esetben jobb megoldás az előző program.
Viszont jó is van ebben a programban: bemutatja, hogyan lehet a felbontást kezelni. Előny még az is, hogy atom biztosan jelzi, ha egy chip eltűnik a vonalról, működés közben nyugodtan lehet ki be dugogatni. Már ha ez fontos valakinek. Pl. egy adatgyűjtőben hasznos lehet, ha pont annyi chip-et mér és jelez ki a program, amennyit rádugtam, és mérések közben is lehet bővíteni a szenzorok számát stb.

Gyorsan megírtam egy olyan változatát a fenti programak, ami nem használ delay() függvényt. Kicsit bonyolult a loop(), mert felváltva mérek vagy olvasok, és váltogatni kell a felderített chip-ek közül, amelyikkel éppen foglalkozok. Ha növelem a chip-ek számát, akkor egyre ritkábban mér egy chi-et.

#include <OneWire.h>          //Dallas DS18b20 hőmérő kezeléshez one-wire busz
OneWire  ds(A0);              // a A0. kivezetéshez kell kapcsolni a chip-ek data kivezetéseit 
byte ds_cim[8];               //ebbe a tömbbe fogja a dallas search() függvénye beolvasni a megtalált chip címét
byte ds_cim_t[5][8];          //ebben a tömbben tároljuk a chip-ek címeit. Azért másolom egy külön tömbbe a címeket
                              //hogy ne kelljen a loop-ban minden mérés előtt felderíteni a chip címet. Így csak
                              //a setup részben csinálok cím felderítést egyszer. Max öt chip lehetséges, de lehet több is.
byte ds_data[9];              //kiolvasott adtok ebbe a tömbba töltődnek kiolvasáskor
float homerseklet;            //ebbe a változóba kerül a kiolvasott hőmérséklet
bool meres=true;              //azért, hogy ne kelljen delay()-t használni, az egyik loop() ciklusban utasítjuk a dallas chip-et
                              //hogy mérjen, mig a következőben kiolvassuk a mért értéket. Ez jelzi, amikor csak mérni kell
long ido_tmp=millis();        //segéd változó, ahooz, hogy csak megadott idő után foglakozzon a loop() a dallas hőmérővel 
byte ds_darab=0;              //segédváltozó, ebben tároljuk a megtalált Dallas chip-ek darabszámát (tömbindexét)
byte ds_tmp_mer=0;            //segédváltozó, ami megmondja melyik chip-et mérjük a federítettek közül
byte ds_tmp_olvas=0;          //segédváltozó, ami megmondja melyik chip-et olvassuk a federítettek közül
void setup()
{
  Serial.begin(9600);
  Serial.println("Indul:");

  while(ds.search(ds_cim))                             //keresünk egy chip-et a vonalon
  {
     Serial.print("Találtunk egy chip-et! Címe:");
     for (byte i=0;i<8;i++) {                          //elmásoljuk a kiderített címet egy másik tömbbe későbbi felhasználásra
       ds_cim_t[ds_darab][i]=ds_cim[i];
       Serial.print(ds_cim[i], HEX);                   //cím byte-ok kiírása 
       Serial.print(",");                              
     }
     if (OneWire::crc8(ds_cim,7)==ds_cim[7])          //ellenőrizzük az érzékelő címének érvényességét
       {Serial.print(" CRC OK!");}                    //cím rendben
     else {Serial.print(" CRC hiba!");}               //crc hiba van a címben
     ds_darab++;
     Serial.println("");                              //új sor a következő chip adataihoz
  }  
}

void loop() {
  if(millis()>ido_tmp+2000) {                         //két másodperc után foglalkozunk a hőmérséklet méréss indítással, vagy az eredmény kiolvasással
    if (meres) {                                      //ha meres=1 akkor arra utasítjuk majd a chip-et, hogy végezzen egy mérést, ha 0 akkor kiolvasunk (else ág)
      for (byte i=0;i<8;i++) {                          //visszamásoljuk a cim tömbből az egyik chip címét a ds_cim tömbe
        ds_cim[i]=ds_cim_t[ds_tmp_mer][i];
      } 
      if (ds.reset()==1) {                            //itt 1-el jelez, ha a reset jelre válaszol legalább egy DS18b20, tehát van a vonalon chip. Ha nincs a vonalon DS18b20, akkor ez 0.
        ds.select(ds_cim);                            //ezzel kiválasztjuk a cím alapján a chip-et
        ds.write(0x44,1);                             //elinditunk egy mérést a chip-ben
        ds_tmp_mer++;                                 //következő ciklusban a következő chip-en indítunk mérést
        if (ds_tmp_mer>ds_darab-1) {ds_tmp_mer=0;}
        meres=false;                                  //ezzel jelezzük, hogy elindítottunk egy mérést, 750msec múlva ki lehet olvasni a mért hőmérsékletet
      }
      else {                                          //nincs egyetlen chip sem a vonalon, meres változó marad false, így nem fogjuk kiolvasni a chip-et akövetkező cklusban
        Serial.println("Nincs egy DS18b20 sem a vonalon!");}  
    }
    else {                                            //mivel volt eszköz a vonalon és az előző ciklusban mért is egyet, ki fogjuk olvasni az eredményt
      for (byte i=0;i<8;i++) {                        //visszamásoljuk a cim tömbből az egyik chip címét a ds_cim tömbe
        ds_cim[i]=ds_cim_t[ds_tmp_olvas][i];
      } 
      ds.reset();                                     //éredekes, de itt már mindenképpen 1 volt a ds.reset() válasza, ha nem volt eszköz a onalon, akkor is,
                                                      //így itt már nem is vizsgálom.  
      ds.select(ds_cim);                              //ezzel kiválasztjuk a cím alapján a chip-et
      ds.write(0xBE);                                 // Chip memóriájánbak olvasása következik
      for ( byte i=0;i<9;i++) {ds_data[i]=ds.read();} // 9 bytot olvasunk ki
      if ( OneWire::crc8(ds_data,8)!=ds_data[8]) 
        {Serial.println("CRC hiba a kiolvasott adatokban!");}  //crc hibát jelez, ha kivesszük a chip-et, itt lehet észrevenni 
                                                               //ha valamilyen hardver hiba van
      else {
        homerseklet=(float) (((ds_data[1]<<8)|ds_data[0])/16.0);  //mért ertek visszaadása 
        Serial.print(ds_tmp_olvas);
        Serial.print(". chip hőmérséklet:");
        Serial.println(homerseklet); 
        meres=true;                                   //ezzel jelezzük, hogy következő ciklusban mérést kell indítani
      }
      ds_tmp_olvas++;                                 //következő ciklusban a következő chip-en indítunk mérést
      if (ds_tmp_olvas>ds_darab-1) {ds_tmp_olvas=0;}
    }
    ido_tmp=millis();        //pillanatnyi idő tárolása 
  }
}
 

Hibás CRC üzenetet soha nem kaptam sem ezzel a programmal sem az előzővel. De gondolom ez vezeték hossz kérdése, és a 15m kábel semmiség a katalógusban olvasott maximális 400m-hez képest. (későbbi tapasztalatom, hogy 25m kábelen már néha előfordul a CRC hiba, 1.5K felhúzó ellenállást használtam, ennek további csökkentése nem hozott eredményt)

HDC1080 hőmérséklet és páratartalom szenzor

Ezt a modult már kipróbáltam, jól is működik, de működési elvének és egyéb tulajdonságainak nem néztem utána. Roppant kényelmes, mert I2C buszon működik. Olcsó is volt, könyvtár is van hozzá, csak a könyvtár kezelőbe be kell írni a nevét. Sokat nem tudok mondani róla.

Néhány infó az adatlapjáról:
Relatív páratartalom pontossága:+/- 2%
Hőmérséklet mérés pontossága:+/- 0.2 fok celsius
AD átalakító felbontása: 14 bit
Áramfelvétel alvó módban: 100nA
Tápfeszültség: 2,7 – 5,5V

Példaprogram:

#include <Wire.h>
#include "ClosedCube_HDC1080.h"
ClosedCube_HDC1080 hdc1080;


void setup()
{
  Serial.begin(9600);    //sorosport indul
  hdc1080.begin(0x40);   //hdc1080 indul
  //eszköz gyári adatainak kiírása
  Serial.print("Gyariszam=0x");
  Serial.println(hdc1080.readManufacturerId(), HEX); 
 // 0x5449 ID of Texas Instruments
  Serial.print("Eszkoz ID=0x");
  Serial.println(hdc1080.readDeviceId(), HEX); // 0x1050 ID of the device
  //felbontás beállítása: első paraméter páratartalom (0-14bit, 1-11bit,2-8bit)
  //második paraméter hőmérséklet (0-14bit, 1-11bit)
  hdc1080.setResolution(0,0);  //14bit hőmérséklet és páratartalom is
  //felbontás kiolvasása eszköz regisztereiből
  HDC1080_Registers reg = hdc1080.readRegister();
  Serial.print("Homerseklet felbontas (0-14bit, 1-11bit):");
  Serial.println(reg.TemperatureMeasurementResolution);
  Serial.print("Paratartalom felbontas (0-14bit, 1-11-bit, 2-8bit):");
  Serial.println(reg.HumidityMeasurementResolution);
}

void loop()
{             
  //hőmérséklet és páratartalom kiolvasása és kiírása soros portra
  Serial.print("T=");
  Serial.print(hdc1080.readTemperature());
  Serial.print("C, RH=");
  Serial.print(hdc1080.readHumidity());
  Serial.println("%");
  delay(5000); 
}

DHT11, DHT22 hőmérséklet és páratartalom szenzor

A DHT11 és DHT22 páratartalom szenzornak négy kivezetése van, ami valójában 3. Az egyiket ugyanis semmire nem lehet használni (NC=No Connection). A DHT11 beltéri mérésekre alkalmas, míg a DHT22 0 fok alatt is mér 100% páratartalomig és pontosabb is. Kicsit drágább mint a DHT11. Azt hiszem 1000Ft/db. 5V tápfeszültségről (Vcc láb) működik.
Egy alkalommal párhuzamosan mértem egy DHT11, DHT22 és 4db DS18B20 hőmérő szenzorokkal. A DHT11 lógott ki legjobban a sorból, kb 1-2 fokkal tért el az általa mért érték a többi öt szenzor által mért értékek átlagától. Ráadásul ez csak egész fokokat ad vissza. Egy fok eltérés nem sok, de nekem úgy tűnt, hogy inkább a DHT22-őt érdemes beszerezni beltérre is. Esetleg valamilyen más típust is érdemes megnézni (pl. BME280 vagy HDC1080)

Mielőtt konkrétan megnézzük a szenzor működését, érdemes megismerni a páratartalom definíciójával és a lehetséges mérési elvekkel.
Abszolút páratartalom: a levegő vízgőz tartalma kg/m3 vagy mol/dm3 koncentráció-egységben. A képlettel adott definíciója: abszolút páratartalom: AH

ahol n a vízmolekulák száma, Mv a víz molekuláris tömege, V pedig a térfogat. A levegő nem tud tetszőleges mennyiségben felvenni vizet – ha telítődik, megindul a kicsapódás. Azt a nyomást, amelyen ez a jelenség megtörténik, telített gőznyomásnak nevezzük.
A tapasztalatok szerint az abszolút páratartalom nincsen összhangban a szubjektív nyirkosság-érzetünkkel, ezért vezették be a relatív páratartalmat, amely a levegőben oldott vízgőz mennyisége a maximálisan oldható vízmennyiség százalékában kifejezve. A pontos definíciója a következő: relatív páratartalom RH

ahol pv a részleges vízgőznyomás, ps pedig az adott hőmérséklethez tartozó telítési nyomás. Ez utóbbi – és így a teljes mennyiség is – hőmérsékletfüggő. Ennek következményeként egy páratartalom-érzékelőnek tartalmaznia kell egy hőmérséklet-érzékelőt is a mért értékek értelmezéséhez. A levegő által oldható vízgőz mennyisége a hőmérséklet emelkedésével nő. Ha egy állandó térfogatban és állandó nyomáson lévő levegőmennyiségnek csökkentjük a hőmérsékletét, a relatív páratartalom nőni kezd. Egy bizonyos hőmérsékletérték alatt a víz elkezd kicsapódni – ez a harmatpont. Ezért harmatos reggel a réten a fű még nyáron is!

A rezisztív mérési elv azon alapul, hogy egy porózus anyag vezetőképessége (ellenállása) megváltozik az anyag felületén kicsapódó páratartalom hatására. Az érzékelő nagyon lassan áll be a pontos értékre, ez akár percekig is tarthat.

Kapacitív mérési elvű érzékelők esetén egy kondenzátor dielektrumának tulajdonságait változtatja meg a páratartalom, vagyis a kondenzátor kapacitása változik meg. Ezek az érzékelők lényegesen drágábbak, de egyben pontosabbak is és az érzékelő is gyorsabban áll be a pontos értékre. Sajnos arról nem találtam konkrét adatot, hogy mennyi a beállási idő. Amatőr gyakorlatban talán nem is érdekes, én pl. félóránként akartam mérni.

A specifikációból a következő adatokat sikerült kihámoznom:
DHT11:
Rezisztív mérési elven alapul
Hőmérséklet mérés 0-50 °C tartományban
Hőmérséklet mérés felbontása:1°C
Hőmérséklet mérés pontossága +/- 2 %
Páratartalom mérés 20-90% RH
Páratartalom mérés felbontása: 1%RH
Páratartalom mérés pontossága +/- 5%

Felbontás hőmérséklet mérés és páratartalom mérésben is 1, vagyis csak egész fokokat és páratartalom értékeket lehet mérni vele. Gyakorlatilag csak szoba hőmérőnek megfelelő.

DHT22:
Kapacitív mérési elven alapul
Hőmérséklet mérés -40 – +80 °C tartományban
Hőmérséklet mérés felbontása:0.1°C
Hőmérséklet mérés pontossága +/- 0.5 %
Páratartalom mérés 0-100% RH
Páratartalom mérés felbontása: 0.1%RH
Páratartalom mérés pontossága +/- 5%

Adatátvitel:
Az adatok átvitelét a mikrovezérlő kezdeményezi azzal, hogy lehúzza a DATA vezetéket 18ms időre a földre. Ekkor a DHT11 (DHT22-is ugyanígy működik) a low-power módból átkapcsol a leírás szerint high-speed üzemmódba. Ez utóbbi szerintem az adatátviteli módot jelenti. Ezt követően a mikrovezérlő a DATA vezetéket figyelni kezdi, azaz írás állapotból olvasás állapotba vált át. A DHT11 pedig egy 40 bites adatsorozatot jelenít meg a DATA vezetéken azzal, hogy azt meghatározott időzítés szerint a ciklikusan földre húzza és elengedi. Az így kialakuló jelsorozat a következő képen néz ki:

A DHT11 adatlapja szerint az adatátvitel során mindig az utoljára mért értéket küldi át. Ha nagyon hosszú ideig nem kérdeztük le a mért értékeket (erre vonatkozóan nem írja az adatlap mi a hosszú), akkor érdemes egy második lekérdezért is kezdeményezni, hogy a pontos mért értékeket kapjuk meg. Feltételezem, hogy ez azért van, mert amikor a 18ms start jellel felébresztjük a DHT11-et, kell számára idő, hogy pontos mérési eredményei legyenek. Vagyis ha pl félóránként kérdezzük le az adatokat, akkor az első kiolvasás az adott kiolvasási pillanat előtt félórával történt mérés adata. Szóval szerintem pár másodperc eltéréssel két mérést kell indítani, és csak a másodikat kell felhasználni. Ha meg folyamatosan kérdezgetjük le a mérési eredményeket, akkor ezzel nem kell foglalkozni!

A DHT11 kiolvasása a következő lépésekből áll:

1. lépés:
Ha a DHT11-re tápfeszt kapcsolunk, legalább legalább 1 másodpercet kell várni, hogy a chip stabil mérési eredményeket produkáljon. Ez idő alatt nem szabad lekérdezni az adatokat. A DHT11 DATA kivezetése ez alatt folyamatosan bemenet
2. lépés:
A mikrovezérlő kivezetése kimenet, amit a kommunikáció indítására minimum 18ms időre 0-ra kell kapcsolni. Ezt követően a mikrokontroller kivezetését bemenetre kell állítani. A felhúzó ellenállás természetesen ekkor a bemeneti feszültséget felhúzza 1-re. A mikrovezérlő ettől kezdve figyeli a vonalat a DHT11 pedig a DATA kivezetés 0-ra húzásával tud jelzéseket adni. Az adatlap ehhez a következő ábrát mutatja:

3. lépés:
A DHT11 a DATA kivezetését átállítja kimenetnek és 0-ra húzza 80mikrosec időtartamra. Ezt követően a mikrovezérlőnek fel kell készülnie az adatok fogadására, amire 80mikrosec idő áll rendelkezésére. Ennyi ideig a DHT11 DATA kivezetése 1-re vált. A következő 0-ra váltás már értékes adatot fog jelenteni. Ez az adatlap szerint így néz ki:

4. lépés:
Minden egyes bit átvitele előtt a DHT11 a DATA vonalat lehúzza 0-ra 50mikrosec-re. Ezt követi egy bit, melynek értéke attól függ, hogy mennyi ideig marad 1-en a DATA kivezetés. Ha az 50mikrosec-es 0 jelzés után 26-28mikrosec magas állapot következik a DATA kivezetésen, akkor az átvitelre kerülő bit értéke 0, ha az állapot 70mikrosec, akkor pedig 1. A két különböző bit érték átvitelét az alábbi ábra mutatja:

Adatátvitel befejezése:
Miután a DHT11 mind a 40 bitet „lejátszotta” elengedi a vonalat és a DATA kivezetés olvasás állapotba kerül. Várja a következő 18ms-es start jelet. Közben megcsinálja a következő hőmérséklet és páratartalom mérést, amit beír a belső regisztereiben. Bár az adatlap erről nem írt, de feltételezem, hogy a mérések végrehajtása után low-power módba kapcsol, hogy kicsi legyen az áramfelvétele.

Első lépésként kerestem hozzá programcsomagot:

A kezelő program roppan egyszerű, mindkét típus kezelésére alkalmas, csak a kommentezett sorokat kell megcserélni a típusnak megfelelően. Az eredményeket a soros monitorra írtam ki!
A katalógusok a DHT22 esetében 100m hosszú vezetékezést említenek. A DHT11 esetében már óvatosabb a gyártó, itt már csak azt mondja, hogy a vezeték hosszabb lehet mint 20m. Nekem egy 15m-es riasztó vezetékem volt otthon, azzal gond nélkül működött.

Példaprogram:

#include "DHT.h"
#define DHTPIN 2     // A 2. kivezetéshez kapcsolódik
#define DHTTYPE DHT11   // DHT 11
//#define DHTTYPE DHT22   // DHT 22  (AM2302), AM2321
//#define DHTTYPE DHT21   // DHT 21 (AM2301)
DHT dht(DHTPIN, DHTTYPE);
void setup() {
  Serial.begin(9600);
  Serial.println("DHTxx test!");
  dht.begin();
}

void loop() {
  delay(2000); //2 másodfperc várakozás a mérésre
  float h = dht.readHumidity();
  float t = dht.readTemperature();
  if (isnan(h) || isnan(t) ) {
    Serial.println("Failed to read from DHT sensor!");
    return;
  }
  
 // Compute heat index in Celsius (isFahreheit = false)
  float hic = dht.computeHeatIndex(t, h, false);

  Serial.print("Paratartalom: ");
  Serial.print(h);
  Serial.print(" % ");
  Serial.print("Homerseklet: ");
  Serial.print(t);
  Serial.print(" Celsius ");
  Serial.print("Heat index: ");
  Serial.print(hic);
  Serial.println(" *C ");
}

FM24C04A külső FRAM használata

Előbb-utóbb kevésnek fog bizonyulni az ATmega328 memória készlete, ezért rákényszerülünk majd külső memóriák használatára. Ha a működési sebesség nem kritikus, akkor az I2C buszon kommunikáló külső memória chip-ek vagy modulok a legkényelmesebbek. Ha nem szeretnéd a tárolt adatokat a tápfesz kikapcsolásakor elveszíteni, akkor eeprom vagy fram memóriát kell használnod. Hogy eldönthesd mikor melyiket célszerű használnod, ismerd meg a memória típusok működését.

Készítettem egy bonyolult programot, ami hőmérséklet mérési eredményeket állított elő 3 másodpercenként. A mérési eredményeket átlagoltam, azaz a memóriába 3 másodpercenként írtam újra az adatokat. Végül az átlag értékeket óránként egy SD kártyára írtam. Elméletben minden tökéletesen működött. Azonban a program időnként (havonta egy-két alakalommal) lefagyott. Nem komoly probléma egy amatőr szerkezet esetén, nyomtam egy reset gombot és minden ment tovább. Azonban nagyon bosszantott, hogy folyamatosan figyelni kellett a szerkezetre. A lefagyást okozó körülményt nem sikerült megtalálni, pedig a programban minden lehetséges részt többször is átalakítottam. Végül feladtam, és elhatároztam, hogy a watcdog áramkörre bízom az újraindítást. Ha a program lefagy, a vatchdog újraindítja, és észre sem veszem, hogy történt valami. Az egy órás intervallumban a mérési eredmények átlagolása rövidebb ideig tart ugyan, de még így is jobb a helyzet, mintha esetleg órákig nem veszem észre a lefagyást, és nem tárolok egyáltalán mérési eredményeket. Gondolkodni kezdtem, hogyan lehetne ezt a problémát megoldani. Az ATmega328 EEPROM memóriája erre alkalmatlan, mert a 100ezer írást nagyon gyorsan elhasználom. Első ötletem, hogy keresni kell egy másik eszközt az adattárolásra. Nézzük csak, mennyi írásról van szó? Három másodpercenként mérek és tárolok, azaz 20 írás percenként. Ez napi 28000 írás, ez évi 10 millió. Ha legalább 10 évig szeretném használni az eszközömet, akkor 100millió írásra alkalmas eszközt kell keresni, vagy tudomásul kell venni, hogy havonta cserélni kell a memória elemet (EEPROM esetén). Esetleg szóba jöhet egy SRAM, amit elemmel vagy akkumulátorral védek az áramszünetek okozta adatvesztéstől.

Melyik az az eszköz, ami elvisel 100 millió írást? A fenti ismertető alapján az FRAM a nyerő. Nyilván elég lusta vagyok hozzá, hogy akkumulátort építsek egy ram-ra, így SRAM-ot nem fogok használni. Az adatlap alapján 100 éves használati időre számíthatok, így rögvest meg is rendeltem 5db-ot 250Ft-ért. Azonban csak felület szerelt kivitelben létezik, ezért kénytelen voltam hozzá rendelni áramköri lemezt (350Ft 10db), amire a rendelkezésemre álló durva eszközökkel is van remény, hogy beforraszthatom. Féltem a forrasztástól, de mind az öt ramot sikerült hibátlanul beépíteni. Az eredmény így néz ki forrasztás után:

Természetesen létezik hozzá könyvtár. Nem is igazán kell kifejezetten FRAM-hoz való könyvtár, hiszen az EEPROM és a statikus ram is ugyanúgy működik. Természetesen most a kicsi kapacitású eszközökről beszélek, azokról melyeknek a memória címe 8 bites. Bár létezik FRAM-ból is sokkal nagyobb (pl. 256kbit), azok címzése már 16 bites, pont úgy mint a hasonló méretű EEPROM-ok esetében. A címzés egyébként csak annyit jelent, hogy az I2C buszon az eszköz megszólítása után, egy vagy két byte-on a címet kell kiírni az eszközbe, és ezt követően lehet írni vagy olvasni a tartalmat. Ennél a típusnál a cím 8 bites, tehát egy I2C busz írási művelettel kiküldjük a címet, és jöhetnek az adatok.

Az I2C busz címet két chip kivezetéssel összesen négyféle címre tudjuk beállítani. Így összesen négy eszközt köthetünk a buszra. Mivel egy FM24C04B FRAM 512byte kapacitású, a két külső cím-vezeték mellett van egy harmadik cím bit is, amivel az FRAM-on belül tudunk a két 256 byte-os lap közül választani. Tehát az eszköz címzése így néz ki:

Ha portscenner programot futtatunk, akkor két címen is válaszol, a decimális 80 és 81 címeken (Ha az A2 és A1 földre van kötve)! Gyakorlatilag olyan, mintha két eszközünk is lenne az I2C buszon, csak ezt egyetlen tokba építették össze. Egy címen 256 byte memóriát tudunk címezni, a két ezközből jön össze az 512 byte (4Kbit). Mivel két hardveresen beállítható címvezeték van, összesen négy FRAM chip-et lehet az I2C buszhoz kapcsolni. Tehát 2Kbyte (16Kbit) memóriát tudunk így kialakítani. Adatgyűjtésre nagyon kevés, tehát marad ilyen esetben az SD kártya, de átmeneti mérési eredmények tárolásához nagyos sok. Ha elveszti a rendszer a tápfeszültséget, akkor minden tárolt adat megmarad, és ez nagy előny lehet. Gyakorlatilag a programban rendelkezésre álló ram területet tudjuk vele kiterjeszteni, de az alérés a belső ram-hoz képest csigalassú!

Találtam egy kifejezetten FM24C04A FRAM-hoz készült könyvtárat, de nem tetszett. Az volt vele a bajom, hogy külön kellett a 256 byte-os lap címeket beállítani, ráadásul a byte, int és long típusú változókra külön függvényeket készített a könyvtár írója. Ez persze nem baj, csak kicsit kényelmesebbnek éreztem, ha egy függvényem lesz az olvasásra, egy pedig az írásra. Nem akartam a lap címzésekkel foglalkozni, egyben szerettem volna kezelni a 0 – 511 címtartományt. Így aztán csináltam magamnak egy kombinált függvényt, ami mindig long típusban adja vissza a ram-ból kiolvasott értéket. A függvényeknek van egy változó típus paramétere, amivel beállítható, hogy hány bájton tárolja az adatot, amit megadtunk, illetve hány bájtról olvassa ki az adatot a megadott kezdőcímtől. A függvény igen buta jelenleg, nem ellenőrzi az I2C buszon zajló dolgokat, hibákat nem kezel. Funkcionálisan jól működik, a példa programban igyekeztem minden értékhatárra egy-egy mintát elhelyezni.

A példa programot még fejlesztgetem. Hamarosan gyakorlatban is használni fogom, és akkor még biztosan javítgatok rajta. Most még lehetnek benne hibák is!

#include <Wire.h>

//Az i2c címet az A2 és A1 kivezetések állapota határozza meg
//Például, ha A2 = VCC és A1 = GND, írjunk: FRAM fram (0b10);
byte A0A1=0b00;
byte _i2cAddress= (0b101000 | A0A1)<<1;
byte Page=0;

              
void setup() {
Wire.begin();  
  Serial.begin(9600);
  Serial.println("indul...");
  //Írás a 0. lapra a ramban
  fram_write(0,0,1);             //byte 0
  fram_write(1,255,1);           //byte 255
  fram_write(10,0,2);            // Int 0
  fram_write(12,-32768,2);       //Int -32768 
  fram_write(14,32767,2);        //Int 32767
  fram_write(20,0,3);            //Long 0
  fram_write(24,-2147483648,3);  //Long -2,147,483,648
  fram_write(28,2147483647,3);   //Long 2,147,483,647
  //Írás a 1. lapra a ramban
  fram_write(256,0,1);           //byte 0
  fram_write(257,255,1);         //byte 255
  fram_write(266,0,2);           // Int 0
  fram_write(268,-32768,2);      //Int -32768 
  fram_write(270,32767,2);       //Int 32767
  fram_write(272,0,3);           //Long 0
  fram_write(276,-2147483648,3); //Long -2,147,483,648
  fram_write(280,2147483647,3);  //Long 2,147,483,647
  //olvasás a 0. lapról 
  Serial.print("Byte visszaolvasva 0. page (0 - 255):");
  Serial.print(fram_read(0,1));
  Serial.print(" - ");Serial.println(fram_read(1,1));
  Serial.print("Int visszaolvasva 0. page (min >> 0 >> max):");
  Serial.print(fram_read(12,2));
  Serial.print(" >> ");Serial.print(fram_read(10,2));
  Serial.print(" >> ");Serial.println(fram_read(14,2));
  Serial.print("Long visszaolvasva 0. page (min >> 0 >> max):");
  Serial.print(fram_read(24,3));
  Serial.print(" >> ");Serial.print(fram_read(20,3));
  Serial.print(" >> ");Serial.println(fram_read(28,3));
  //olvasás a 1. lapról 
  Serial.print("Byte visszaolvasva 1. page (0 - 255):");
  Serial.print(fram_read(256,1));
  Serial.print(" - ");Serial.println(fram_read(257,1));
  Serial.print("Int visszaolvasva 1. page (min >> 0 >> max):");
  Serial.print(fram_read(266,2));
  Serial.print(" >> ");Serial.print(fram_read(268,2));
  Serial.print(" >> ");Serial.println(fram_read(270,2));
  Serial.print("Long visszaolvasva 1. page (min >> 0 >> max):");
  Serial.print(fram_read(276,3));
  Serial.print(" >> ");Serial.print(fram_read(272,3));
  Serial.print(" >> ");Serial.println(fram_read(280,3));
}

void loop() {
  
}

long fram_read(int fram_cim, byte fram_adat_tipus) {
// fram_cim 0-511
// fram_adat_tipus 1-byte,2-int,3-long
byte page=0;
if (fram_cim<256) {page=0;} else {page=1;fram_cim=fram_cim-256;}
    Wire.beginTransmission(_i2cAddress | (page&1));
    Wire.write(fram_cim);
    Wire.endTransmission(); 
    switch (fram_adat_tipus) {
    case 1:
      Wire.requestFrom(_i2cAddress | (page&1),1);
      return Wire.read();
      break;
    case 2:
      Wire.requestFrom(_i2cAddress | (page&1),2);
      return int(Wire.read()) | int(Wire.read())<<8;
      break;
    case 3:
      Wire.requestFrom(_i2cAddress | (page&1),4);
      return long(Wire.read()) | long(Wire.read())<<8 | long(Wire.read())<<16 | long(Wire.read())<<24 ;
      break;
    }
}

void fram_write(int fram_cim, long fram_adat, byte fram_adat_tipus) {
// fram_cim 0-511
// fram_adat long, amit a függvémy szükség szerint byte, vagy int-re alakít
// fram_adat_tipus 1-byte,2-int,3-long
byte page=0;
byte adat0;
byte adat1;
byte adat2;
byte adat3;
if (fram_cim<256) {page=0;} else {page=1;fram_cim=fram_cim-256;}
    Wire.beginTransmission(_i2cAddress | (page&1));
    Wire.write(fram_cim);
    switch (fram_adat_tipus) {
    case 1:
      Wire.write(fram_adat);
      Wire.endTransmission(); 
      break;
    case 2:
      adat0=fram_adat & 0xFF;
      adat1=(fram_adat>>8) & 0xFF;
      Wire.write(adat0);
      Wire.write(adat1);
      Wire.endTransmission(); 
      break;
    case 3:
      adat0=fram_adat & 0xFF;
      adat1=(fram_adat>>8) & 0xFF;
      adat2=(fram_adat>>16) & 0xFF;
      adat3=(fram_adat>>24) & 0xFF;
      Wire.write(adat0);
      Wire.write(adat1);
      Wire.write(adat2);
      Wire.write(adat3);
      Wire.endTransmission(); 
      break;
    }
}

Miközben dolgoztam az FRAM megismerésén, gondolkodtam azon is, hogyan tudnám a legkényelmesebben felhasználni programjaimban. Végül is a külső ram azért van, hogy tápfeszültség kiesés esetén is megőrizze a szükséges adatokat. Még egy bonyolult programban sincs sok ilyen adat (legalább is nekem nem sikerült megfelelően bonyolult problémát kitalálnom). Azonban az adatok amiket nem szeretném ha elfelejtene a rendszer, nem egyszerű számok, hanem valamilyen feldolgozott értékek. Pl. a napi hőmérséklet minimuma, maximuma, átlaga, mérések száma stb. Ezért írtam egy olyan függvényt, illetve függvény gyűjteményt, aminek a használatakor nem kell foglalkoznom az adatok értékelésével, közvetlenül a számomra szükséges feldolgozott adatot kaphatom vissza. Talán mások számára is hasznos lehet, ezért a forráskódok menüpontba beraktam ennek a függvény gyűjteménynek a leírását és programját. Ha érdekel, akkor itt megtalálod!

SD kártya modul

Az SD kártyák SPI buszon kommunikálnak. Ha az SPI kommunikációról többet szeretnél megtudni, kattints ide! Azonban az SD kártyák használatához nem szükséges megismerni a részleteket, nyugodtan haladj tovább, ha csak használni akarod, és nem megérteni. Az SD kártyák csatlakoztatása külön megvásárolt modul nélkül is igen egyszerű, mindössze pár ellenállásra van szükség, amikkel az Arduino 5 V-os rendszerű SPI buszának vonalait a memóriakártyák által elfogadható 3,3 V-os szintűvé alakítjuk. Tulajdonképpen az általam vásárolt modul sem tesz ennél többet.

Az SD kártya az egyik legtökéletesebb eszköz adatok tárolására. Azonban vannak jelentős hátrányai is, mivel a készen elérhető Arduino könyvtárak FAT32 file rendszert valósítanak meg, azaz csak file-okat tudunk létrehozni. Legalább is én csak ilyen szoftver eszközöket találtam. Az SD kártya konkrét kommunikációjának megértésére még nem volt szükségem, így maradtam ezen eszközök felhasználásánál, ami viszont azt jelenti, hogy fájlokat kell kezelni. Ezekkel semmi baj nincs, míg csak írunk. Ha azonban ezekből az állományokból adatokat akarunk visszanyerni a mikrokontrollerben írt programunk számára, az igen nehézkesen fog működni, hiszen elég szerény szoftveres lehetőség áll rendelkezésre. Nem is találtam más függvényt, csak olyat, amivel végig olvashattam az állomány tartalmát addig amíg rá nem találtam arra, amire szükségem volt. Azonban ez nem feltétlenül hátrány. Adatgyűjtés esetén elegendő ha csak írjuk az állományt és soha nem olvasunk belőle. Időnként aztán kivesszük az SD kártyát a foglalatból, bedugjuk a számítógépünkön egy kártyaolvasóra, letöltjük az állományt és feldolgozzuk. Ez is működhet jól. Ráadásul igen sok adat fér a kártyára. A boltban már csak 8Gb-os kártyát kaptam, ha jól emlékszem 2000Ft-ért.
Mint említettem a kártyaolvasó is modul az ISP buszt használja. Nem is lepődünk meg azon, hogy ugyanazokra az Arduino kivezetésekre, illetve chip lábara kell az SD modult bekötni, mint amikor az Arduino Uno R3-at programozónak használjuk.
Így néz ki egy SD modul:

Talán egy kicsit áttekinthetőbb az alábbi kép, ami már csak a kivezetések felsorolását tartalmazza:

Miután bekötöttük, jöhet a program megírása. A kipróbáláshoz egy egyszerű programot töltöttem le. Kicsit megpróbáltam a kommenteket magyarosítani. A program a legtöbb file kezeléssel kapcsolatos parancsot bemutatja. Mielőtt a kártyát használatba vennénk egy kártya olvasóval formázzuk meg FAT32 file rendszerbe.
A keletkező file neve 8.3 formátumú lehet, amit az öregebb tanulók a DOS oprendszerből ismerhetnek. Ékezeteket elfelejteni. A file létrehozási dátuma egységesen 2000.01.01 0:00 időpontot kapott. Nyilván ez egy erősen lekorlátozott file rendszer.

Példa program:

// SD kártya lábait a következő Arduino csatlakozókhoz kell kötni:
//  MOSI - pin 11; ** MISO - pin 12; ** CLK - pin 13; CS - pin 10
#include <SD.h> // ezt az alap programcsomag tartalmazza
File myFile; //File típusú objektum definiálása

void setup()
{
  Serial.begin(9600);
  Serial.print("SD kartya inicializalas...");

  if (!SD.begin(10)) {
    Serial.println("sikertelen inicializalas!");
    return;
  }
  Serial.println("sikeres inicializalas.");

  // nézzük meg, létezik-e az example.txt fájl:
  if (SD.exists("example.txt")) {
    Serial.println("example.txt letezik.");
    } else {
    Serial.println("example.txt nem letezik.");
  }
  myFile = SD.open("example.txt", FILE_WRITE);  // megnyitás vagy létrehozás, egyszerre csak egy fájlt tudunk kezelni
  if (myFile) {                                                          // ha megnyílt a fájl, írjunk bele:
    Serial.print("Írás az example.txt-be…");
    myFile.println("próba 1, 2, 3.");
    myFile.close();                                                     // fájl bezárása / mentése:
    Serial.println("kesz.");
  }
 else {
    Serial.println("nem tudom megnyitni az example.txt-t!");    // ha nem nyílt meg:
  }
  myFile = SD.open("example.txt");      // nyissuk meg újra, most olvasásra
  if (myFile) {
    Serial.println("example.txt:");
    while (myFile.available()) {    // olvassunk belőle, amíg tart a fájl:
     Serial.write(myFile.read());
    }
    myFile.close();    // fájl lezárása:
    } else {
    Serial.println("nem tudom megnyitni az example.txt-t!");      // ha nem nyílik meg:
  }

  Serial.println("example.txt torlése…");  // töröljük le az example.txt-t:

  SD.remove("example.txt");
  if (SD.exists("example.txt")){
    Serial.println("torlés sikertelen.");
  } else {
    Serial.println("torlés sikeres.");
  }
}

void loop()
{
    // itt most nincs semmi
}



A neten található példa programokban az SD kártya tápfeszültségét mindig az Arduino egyik kimenetére kötik. Az SD kártya nem fogyaszt sokat, így a kimenet 40mA-es áram teljesítménye bőven sok az SD kártya tápellátásához. Ennek a megoldásnak az előnye az lenne, hogy csak akkor adunk tápfeszültséget a kártyának, amikor éppen szükség van a használatára. Nekem azonban ez nem működött. A kártyát a at SD.begin() még megnyitotta, de tényelegesen adatokat nem tudtam írni rá, és olvasni sem lehetett. Sajnos az okot máig nem tudom, direktben rákötöttem az a modult a tápfeszültségre, és így működik.

Készítettem egy hőmérséklet adatgyűjtő programot. Érdemes megnézni, hogyan működik. Kattints ide, ha érdekel!

Rotary kapcsoló

A rotary kapcsoló egy igen hasznos adatbeviteli eszköz. Agy forgatógomb, amivel fényerőt (hangerőt) szabályozhatunk, de más hasznos dolgokra is használhatjuk. Én pl. elsőként az ABC betűinek bevitelére használtam. Gyorsabb a forgatógombot tekergetni, mint két különálló gombot nyomogatni. Így néz ki egy rotary gomb:

A tengely forgatás közben érezhetően kis pici pozíciókban áll meg. Az általam használt kapcsoló 30 ilyen pozícióval rendelkezik.

A jeladó belsejében két kapcsoló van (és egy ezektől független harmadik, ami akkor kapcsol, amikor megnyomjuk a tengelyt). Amikor a kapcsoló egy rögzített helyzetben megáll, mindkét kapcsoló nyitott állapotba kerül. Ha valamelyik irányba forgatjuk, akkor az egyik kapcsoló előbb kapcsol, mint a másik. Innen kideríthető a kapcsolás iránya:

Az alábbi ábra szemlélteti a kapcsoló kialakításának módját.

Amint látható, az A és B kontaktus szöghelyzete olyan, hogy:

  • A tengely óramutató járásával megegyező irányú elforgatásával az A és C kivezetések kapcsolnak először.
  • A tengely óramutató járásával ellentétes irányú forgatásakor a B és C kivezetések kapcsolnak először.

Ezt a működést az alábbi ábra szemlélteti:

Lényegében azt kell egy programmal észrevennünk, hogy, hogy melyik kapcsoló vált állapotban először, és ez meghatározza a forgásirányt. Ha A változik először, a kapcsoló az óramutató járásával megegyező irányba forog. Ha B változik először, akkor a kapcsoló az óramutató járásával ellentétes irányban forog.

A rotary kapcsoló kivezetései:

A modul úgy van kialakítva, hogy alacsony a kimenet, amikor a kapcsolók zárt állapotban vannak és magasak, amikor a kapcsolók nyitottak. A zárt állapot a megfelelő kivezetést földre húzza le. Nyitott állapotban a kivezetést egy felhúzó ellenállás a tápfeszültségre húzza fel.

Korábban említettem, hogy van egy harmadik kapcsoló is. Ez akkor záródik, ha a tengelyt megnyomjuk. Így a forgatást követően azonnal jelzést adhatunk a programnak, ha a forgatással kiválasztottuk a megfelelő menüpontot, betűt stb.

Itt egy példaprogram a használatra. A program egy számláló értékét változtatja a rotary tengelyének tekergetésével, fel és le számlál a tekergetés iránya szerint :

int szamlalo = 0;      //szamlalja a pozició váéltások számát 
                                     //előjelesen (előre +, hátra -)
 int rotary_CLK_elozo;     //Utolsó állapota az a kivezetésnek
 int rotary_CLK;                 //A kivezetés aktuális állapota
 int rotary_DT;                    //B kivezetés aktuális állapota
 int rotary_PUSSH;            //nyomógomb kivezetés állapota

 void setup() {
   pinMode (3,INPUT);         //rotary CLK kivezetés
   pinMode (4,INPUT);         //rotary DT kivezetés
   pinMode (5,INPUT);         //rotary nyomógomb kivezetés
   digitalWrite(5,HIGH);      //felhúzó ellenállás bekapcsolása
   pinMode (8,OUTPUT);    //rotary CLK állapotának megjelenítéséhez
   pinMode (9,OUTPUT);    //rotary DT állapotának megjelenítéséhez
   pinMode (10,OUTPUT);  //rotary DT állapotának megjelenítéséhez
   rotary_CLK_elozo = digitalRead(3);  
   Serial.begin (9600);
 }

 void loop() {
   rotary_CLK = digitalRead(3);
   rotary_DT = digitalRead(4);
   rotary_PUSSH = digitalRead(5);
   digitalWrite(8,rotary_CLK);
   digitalWrite(9,rotary_DT);
   digitalWrite(10,rotary_PUSSH);
   if (rotary_CLK and !rotary_CLK_elozo){  //csak a CLK kivezetés 
                                   //felfutó élét figyeljük, 
                                   //mert nyugalmi helyzetben
                                   // CLK=1 felfutó él csak
                                   // tekergetés közben fordul elő
       if (rotary_DT) {         // DT kivezetés 1 - Óramutató járásával 
                                         //megegyezõ irányban forgatunk
         szamlalo--;
       } 
      else {                     // DT kivezetés 0, óramutató játrásával
                                     //ellentétesen forgalunk
         szamlalo++;
       }
      Serial.println(szamlalo);
  }
  rotary_CLK_elozo = rotary_CLK;
}