433Mhz-es rádiós adatátviteli modul

Tartalom:

  • Amplitúdómoduláció működése
  • RF433-LC adó-vevő modul technikai adatai, összekötése Arduino-val
  • RadioHead programkönyvtár működésének részletei
  • Példa program adó és vevő oldalon, integer szám átvitele
  • Adatvesztéssel kapcsolatos kísérletek, adat ismétlési igény becslése a biztos átvitelhez
  • Arduino kivezetések átállítása 11 illetve 12-es kivezetésről másra
  • Tápfeszültség hatása az adás teljesítményére
  • Adás és vétel egyszerre
  • Antenna méretének hatása, egyéb befolyásoló tényezők

———————————————————————————–

 

Már sikerült olyan bonyolultságú szerkezetet építenem (időjárás állomás), amit nem lehetett egyetlen Arduino nano-val kiszolgálni. Megosztottam a feladatokat a két nano között, melyeknek kommunikálnia kellett egymással. Ez remekül sikerült pár centiméterre egymástól az I2C segítségével. Elérkezett azonban az a pillanat, amikor az adatokat 10-20 méter távolságra szeretném továbbítani, de az I2C max. néhány méter átvitelére használható. A soros port alkalmas lehetne, hiszen kis sebességnél akár többszáz méterre vihető az információ, de ki szeret falat fúrni és kábel csatornázni. Feleségem egyébként sem engedné, hogy a falat vezetékekkel rondítsam el, tehát marad a rádiós átvitel. Megjegyzem, ez sokkal olcsóbb, mint a vezeték. 10-20 méterre megbízható átvitelt lehet csinálni a fenti képen látható RF433-LC adó-vevő párossal, ami ráadásul 800 Ft körül kapható Magyarországon. Ennyiből még a szükséges vezetéket és csatlakozókat sem lehet megvenni.

Ismerkedjünk hát meg ezzel a modullal! Az adó rész roppant egyszerű. Az a nagy kerek valami egy 433,92Mhz-s rezonátor, amit egy tranzisztorral lehet be és kikapcsolni. Van még néhány apróság, de lényegében ebből áll az adóegység. A teljesítménye 10mW, ami nagyon kicsi teljesítmény. Ezért nem kell hozzá engedély! Az adó teljesítménye függ a tápfeszültségtől, mivel 3-12V közötti tartományban működik. Értelemszerűen 5V-on használtam, ahol talán még 10mW-nál is kisebb lehet a kisugárzott teljesítmény. Nyitott terepen csak párszor tíz méter átvitelt ígér a gyártó, azt is csak antennával. Az antenna egy egyszerű vékony fémdrót. Vastagsága közömbös, lehet merev és hajlékony is. Egy megfelelő hosszúságú fémdrót körkörös irányba sugároz, ezért lehetőleg függőlegesen helyezzük el, illetve a vevő és az adó rész antennája álljon párhuzamosan. Az antennákról további hasznos infók találhatók a hőmérséklet távadó című írásban! A legideálisabb antenna hosszúság az egy periódus hullámhossza, ami 433Mhz-nél pontosan 69,24cm. Ez azonban elég hosszú, megfelelő lehet ennek a negyed része, ami kb. 17cm. Az én esetemben a szerszámos sufniból kellett adni, ami kb. 10m-re van a háztól. A vevő és az adó között 3 téglafal is volt, ennek ellenére remekül működött olyan antennával amit le sem mértem, csak ráforrasztottam ami éppen volt a hulladékban.

Az adó működésének megértéséhez találtam egy nagyon jó ábrát:

Ehhez azt hiszem nem is kell magyarázat. Ha a bemenetre magas szintet kapcsolunk, megjelenik az éterben egy 433,92Mhz frekvenciájú jel, amit egy megfelelően hangolt vevőegységgel lehet érzékelni. Persze ha sok adót helyezünk el egymáshoz közel, akkor nem lehet elkülöníteni, hogy melyiktől is származik az adott jel. Azonban ez nem feltétlenül okoz problémát, mivel a távolabb elhelyezett adóegység jelei el sem jutnak a mi vevőnkbe a kicsi teljesítmény miatt. A szomszéd rádiós külső hőmérője más problémát fog okozni: időnként zavarjeleket generál a vevőegységünkben, vagy ha elég közel van, a kisugárzott adatai “ütközni” fognak a mi adónk adataival, és egyik vevő sem kap értelmezhető jelsorozatot!

Az adó a bemenetére adott digitális jelekkel jelsorozatot tud kisugározni magából. Ehhez is találtam egy nagyon jó ábrát:

Ezt a módszert amplitúdómodulációnak hívják. Bár esetünkben a „vivő jel” (433,92 Mhz) amplitúdóját nem is igazán változtatjuk, egyszerűen csak ki és bekapcsoljuk. Lehetne olyan átvitelt is konstruálni, ahol a jelszintet szabályozzuk a bemenő jel nagyságával, pl. egy mikrofon jelével. Így működtek az első rádió adók. A fiatalok ezzel már nemigen találkoznak. A rádiózás őskorában, amikor még hallgatható műsorok voltak a rádióban, csak olyan középhullámú rádiókat hallgattunk, melyektől amplitúdómodulációval jutott el fülünkbe a hang. Pl. a solti nagy adó 540Khz-es vivő frekvenciájávan lefedte az egész országot. Az alacsony frekvencia miatt a kisugárzott jel hullámhossza nagy, és ezért a középhullám kikerüli az akadályokat. Még a pincében is szólt a remek kis „Szokol” rádió:

Nekem is volt egy, sok zenét hallgattam rajta. Sajnos a minőség gyatra volt! No nem csak a zsebrádió miatt, a vételt sok körülmény zavarja! Nem voltak mély és magas hangok, sistergett, ropogott, ha apukám beindította az 1200-es Ladát, futva menekültem a közelből a zsebrádióval a kezemben, hogy halhassam a kedvenc ABBA számomat, amit csak kéthetente sugárzott Komjáthy György „Vasárnapi koktél”, vagy „Csak fiataloknak” című műsora.

Aztán jött a frekvenciamoduláció, ahol a bemenő hangfrekvenciás jel kicsit eltolja a vivő jel frekvenciáját! A rádió vevőkészülék az alapfrekvencia és az antennából kapott és felerősített jel frekvenciájának a különbségét alakítja hanggá! A minőség sokkal jobb, vannak basszusok, nincs sistergés a zene alatt. Ezt már ismerős lehet, ezt hívják URH adásnak. Ilyen bonyolult és drága rádiót már csak egyetemista koromban tudtam venni magamnak.

De térjünk vissza a digitális jellel amplitúdó modulált rádióhullámú adatátvitelhez. Nyilván a vevőkészülék is roppant egyszerű. Az antennában keletkező jelekből kiszűri a 433,92Mhz frekvenciájú jeleket. Erre azért van szükség, mert az antenna kimenetén a Kossuth rádió 540Khz-s jele, illetve az URH tartományban használt 107,8Mhz-s jele is megjelenik. Nomeg a világ összes közelben lévő rádióadója, valamint a közelben éppen használt mobiltelefonok 1800Mhz frekvenciája, és még a szomszéd kisbabája mellett bekapcsolt bébicsősz adójának jelei is. Az említett 433,92Mhz frekvenciasáv szűrését egy rezgőkör végzi el, ami csak annyit csinál, hogy ebben a sávban az antennán keletkező jelet átengedi a többi frekvenciát meg nem. A szűrő kimenetén keletkező jelet erősíteni kell, aztán már csak vezérelni kell a jellel egy kapcsolót.

Ha ábrát is mutatok, attól nagyon tudományosnak érzem magam, ezért íme a vevő blokk vázlata:

A kimenet előtt található PLL azért kerül beépítésre, mert az antennából jövő és felerősített jel nagyon zajos. Ha csak az amplitúdót figyelnénk, akkor egyes pillanatokban akkor is jelet kapnánk a kimeneten, ha éppen nem működik az adó (nincs bekapcsolva a vivő frekvencia). A PLL (fáziszárt hurok) nevű áramkör egyik felhasználása, hogy nagyon pontosan képes a vivő frekvenciát szétválasztani a zajoktól, és így a kimeneten csak akkor lesz magas szint, ha az adót bekapcsoltuk. A fáziszárt hurok működéséről szóló leírásokban azt említik, hogy egy frekvenciát tud lemásolni vagy többszörözni stb. Azonban arra is jó, amire itt használják. Akit pontosabban érdekel a működése, nézzen utána!

Az adó és a vevő lábkiosztása:

A tökéletességre törekvés jegyében az Arduino Uno bekötésről is bemásoltam egy-egy ábrát:

Két adóvevő párt vettem magamnak. Azt tapasztaltam, hogy szabadon cserélgethettem az adókat, és mindkét adó jelét bármelyik vevő képes volt venni. Igaz, nem túl nagy távolságokkal próbálkoztam, a szerszámos sufni kb. 10 méter távolságban volt, plusz a három téglafal. Az antennák hosszára egyáltalán nem volt érzékeny ebben a távolságban. Azonban antenna nélkül (mert így is működik) már csak pár méter a hatótáv az is csak ugyanabban a szobában.

Úgy képzeltem, hogy akár egy morze adó-vevő készüléket is készíthetek vele. Tudja valaki egyáltalán mi az a morze? A Titanik kért vele segítséget ti-ti-ti–tá-tá-tá–ti-ti-ti, ami éppen az SOS betűk morze megfelelője. Hát el is süllyedt! Azonban én katonaként rádiós voltam, és tanultam morzézni, ezért a szívemhez nőtt a dolog, vagyis morze gépet akartam elsőként csinálni. Az adó bementére szereltem nyomógombot, ami 0 vagy 5V feszültséget kapcsol az adó bemenetére, a vevő kimenetére pedig egy led-et tettem, és megnéztem mi történik. Persze nem közvetlenül tettem rá a kimenetre a led-et, mert valószínűleg nem tudta volna meghajtani a vevő kimenete, hanem végtelen ciklusban kérdezgettem le egy Arduino programmal a kimenetet, és az Arduino kapcsolgatta a led-et. Meglepő módon a morze készülék nem működött. Azaz valami működési jelenség történt. Ha nem nyomtam meg az adón a gombot, akkor a led-nek nem lett volna szabad világítani, ennek ellenére néha felvillant. Ha azonban nyomtam a gombot, akkor a led-nek folyamatosan világítania kellett volna, de néha elaludt. Eben a formában még ajtócsengőnek sem használható.  Ekkor még nem volt antenna az adón és a vevő, csak pár centire voltak egymástól. Tettem rá antennát, de a helyzet nem változott. Elővettem Római István már említett hőmérséklet távadójának programját, és megnéztem hogyan oldotta meg az átvitelt. Kimásoltam a szükséges programrészeket, és nem működött! Nem értem, hogy mi a probléma, mert bár az ott alkalmazott adóvevő egy H12 típusú oda vissza irányú modul, de az elv és a vivő frekvencia is hasonló. Abban a programban simán egy 1200boud-al működő sorosport kimenettel vezérelte az adót, és működik?! Utána olvasgattam kicsit a dolognak, és több homályos utalást is találtam arra, hogy a jelzavarok nagyon befolyásolják egy ilyen megoldásban az átvitelt. Nyilván a H12 modul egy jobban megkonstruált zavarérzéketlenebb modul. Erre utal, hogy jóval többe is kerül. Jobb ötlet híján elkezdtem előre készített programkönyvtárakat keresni. Természetesen elsőre a RadioHead programkönyvtárba botlottam bele. http://www.airspayce.com/mikem/arduino/RadioHead/

Miután elolvastam, rá kellett jönnöm, hogy ez a nekem való cucc! Mondanom sem kell, hogy a letöltött példaprogramokkal azonnal működött minden. Pedig a hardverhez hozzá sem nyúltam, még az Arduino ki és bemenetek is ugyanott maradtak. Nincsenek csodák, a RadioHead könyvtár átviteli módszere alaposan át van gondolva. Én magamtól ezt nem tudtam volna megcsinálni! Az átvinni kívánt információt megfelelő bevezető jelsorozatokkal és hiba detektálására alkalmas adatokkal látja el. Ha a fogadott adat hibás, akkor észreveszi és eldobja az adatot. Egyirányú kapcsolatnál ilyenkor elveszítünk egy adatot, de annyira olcsó az adóvevő pár, hogy betehetünk kettőt, és visszajelezhetünk az adónak, ha sikerült a vétel. Ha az adó nem kap visszaigazolást, akkor ismétel. Ez lehetne a tökéletes átvitel elve, azonban nekem erre nincs szükségem. Inkább majd a valós alkalmazásban kicsit sűrűbben küldöm az adatokat mint szükséges. Pl. a hőmérsékletet elég lenne negyedóránkén frissíteni a kijelzőt, mert nagyon lassú a változás. Ha azonban percenként küldök adatot, időnként egy-egy csak sikeres lesz! Mindenkit megnyugtatok, hibamentesen megy az átvitel a szerszámos sufniból! Próbaként a RadioHead-hoz mellékelt demó programmal másodpercenként küldtem a „Hello Word!”-öt (lásd RadioHead RF_ASK send és receive példaprogramja), és egy adás sem veszett el az alatt a 10 perc alatt, amíg figyeltem. Persze nem tudom mi lenne, ha még létezne apukám 1200-es Lada autója és éppen beindítaná! Sajnos a Lada legalább 20 éve nincs már meg, néhány napja, hogy már apukám sem indítja be az autóját! Nem figyeltem meg azt sem, hogy nagy vihar, villámlás milyen hatással van, de igyekezni fogok valamilyen rafinált megoldással “megmérni” az információ vesztés mértékét. Pl számolom az adóban, hogy hányszor adtam, és számolom a vevőben a vételek számát. Ha megegyeznek, akkor nem volt veszteség!

Talán érdemes megnézni, hogyan is megy át az adat a RadioHead könyvtárral megvalósított átvitelben. Arduino környezetben az RF433-LC modulhoz az RF_ASK.h programot kell használni. Itt egy értékes byte átviteléhez a következő kiegészítő adatok kellenek:

36 bit bevezető jelsorozat egymást követő 0-1 bitpárokból
– 12 bit startjel 0xb38
– 1 byte-on az üzenet értékes byte-jainak darabszáma. Ez a szám max. 30 lehet a lezáró FCS byte-okkal együtt
– n üzenet byte
– 2 bytes FCS

Az értékes byte-okat valójában 2×6 biten küldi el, vagyis egy byte nem 8 egymást követő bittel kerül átvitelre. A leírásban a jó DC egyensúllyal indokolják ezt a technikát. Nem tudom, ez mit jelenthet, de sejtésem szerint az adó-vevő fizikai tulajdonságaihoz lehet köze. Ha így történik az átvitel, akkor a 0 átvitele közben is lesznek 1-es bitek az adatfolyamban, ami valószínűleg jól jön a jelek dekódolásakor. Az FCS-ről úgy sejtem, hogy az átvitt adatok ellenőrző összege, de ebben nem vagyok biztos, a leírás hi és low byte-okat emleget. Az ellenőrző paritás biteket elhelyezhették az értékes adatok 2×6 bites átvitelében is.

Az átviteli sebesség fixen 2000bps, ami azért alakult ki, mert az Arduino 16Mhz órajelfrekvenciájához ez adta ki a legpontosabb ciklusidőt, ami nagyon fontos az adó és vevő szinkronitásához. Az átvitel során pontos időközönként kell mintát venni a jelből és ez az órajel osztásával oldható meg adó és vevő oldalon is a legpontosabban. Érdemes esetleg megnézni a soros átvitel elvét! Így itt nem a szabványosság a szempont, hanem a megbízhatóság! Az adás illetve a vétel is megszakítások igénybevételével történik, és a timer1-et használja. Tehát ha a RadioHead könyvtárat használjuk, akkor a programban ne használjuk a 9-es vagy 10-es kimenetet PWM módban. Szerencsére ezzel a konfliktussal még nem akadt dolgom! Érdemes azonban figyelni, mert hardver függő, hogy ez mit érint. Pl. ATtiny85 esetén a 2-es időzítőt használja. Tehát ennek utána kell olvasni, ha felmerül a kérdés a programban.

A RadioHead programkód a megszakítások segítségével folyamatosan figyeli a vevő kimenetét, és ha jelsorozat érkezik, azt egy előre definiált memória területre írja ki. Ha nem olvassuk ki időben, és egy újabb jelsorozat érkezik, akkor az előző elveszik. Bevallom, nem vagyok egy kiművelt C++ programozó, a változók címeinek mutatóival nem szeretek dolgozni. Így egy kicsit szokatlan és nehezen értelmezhető volt elsőre a programkönyvtár send() illetve recv() függvénye. Ezeknek ugyanis a változók kezdőcímét kell átadni. A függvények meghívása egyébként automatikusan küldés, illetve fogadás módot állít be. A fogadáskor a programkódunk fut tovább, a függvény csak akkor ad vissza 0-nál nagyobb értéket, ha érkezett érvényes adat. Ez nagyon jó, mert miközben adatokat várunk, a programunk mást is csinálhat. Azt azonban érdemes figyelemebe venni, hogy a program futása az adatok vételekor megszakításra kerül, és az időkritikus műveletek sérülhetnek. Küldés esetén érdemes külön figyelni, hogy megtörtént-e már az adatok elküldése, és csak akkor tovább engedni a programot. Erre szolgál waitPacketSent() függvény!

A demó programokat kicsit átalakítottam és felkommenteztem. Számomra nem a „Hello Word!” átvitel volt a szükséges. Számadatokat szeretnék átvinni, ezért egy integer értékkel próbálkoztam. Az integer változó kezdő memóriacímét kell átadni. A függvény hívásba a változó típushoz kizárólag az ott látható (uint8_t *) kerülhet. Ahhoz, hogy az átvitt integer értékem előjelet is tartalmazzon (-32768-tól 32767-ig), egy kicsit küzdenem kellett, mert a vételi oldalon a két byte-ból vissza kellett állítani az integer értéket. Valószínűleg hiányos C++ tudásom miatt erre nem találtam kész függvényt, így magam csináltam meg egy if()-el. Bocs!

Ebben az esetben 2 értékes byte-ot továbbítottunk az adóvevővel. Az adatátvitel idejét is megmértem a programmal, ami kb 70-80msec-re adódott. Az adó áramfelvételét még nem mértem meg, de várhatóan 10-20mA-es tartományba eshet. Elemes táplálás esetén néhány percenként egy-egy adással hónapokig működhet a kütyü, ha ATmega vezérlőt sleep módon használjuk. Római István leírta tapasztalatait a lítium akkumulátoros táplálásról, bár Arduino nano-val kísérletezett, ami nem tud hatékonyan alvó módba menni az USB soros átalakító chip miatt. Azért ezek az adatok elég bíztatóak.

Az adó programja (alig különbözik a “gyárilag” adott példaprogramtól, csak nem karaktereket, henem integer értéket továbbít):

#include <RH_ASK.h>
//#include <SPI.h>  //nincs rá szükség, de a példa programban benne volt, nem tudom mikor kellhet?

RH_ASK driver;
uint8_t adat[2];    //ebbe a tömbbe fogjuk elhelyezni az átvinni kívánt byte-okat
int ertek=-1259;    //a példa programban ezt az integer értéket visszük át 2 byte-on
                    //negatív érték is lehet!!
long ido;           //az átviteli idő méréséhez segédváltozó
void setup()        //RH_ASK objektum létrehozása
{
  Serial.begin(9600);
    if (!driver.init())        //RF inicializálása. Vételt és adást is megvalósit egyszerre, itt csak az adást fogjuk használni
                               //Adás 12-es láb, vétel 11-es láb (nincs bekötve), sebesség 2000bps
                               //a sebességet nem lehet megváltoztatni
                               //a készítő valamiért az Arduino lábakat is bevasalta. Az RH_ASK.h állományban elvileg átírható
      Serial.println("RF inicializálás nem sikerült...");
}

void loop()
{
  //az int értéket felbontom két byte-ra, amiket sorban egymás után visz át az RF
  adat[0]=ertek&255;                  //első 8 bit kimaszkolása
  adat[1]=(ertek>>8)&255;             //felső 8 bit eltolása jobbra és kimaszkolás
  //itt jön a tényleges átvitel. A két byte érték kezdőcímét kell átadni a függvénynek
  //mert az átvitel megszakításokkal dolgozik a háttérben, nem ez a függvény hajtja végre ténylegesen
  ido=millis();                       //megjegyezzük az átvitel kezdetének időpontját
  driver.send((uint8_t *)&adat, 2);   //első paraméter az átvinni kíván adatbájtok kezdőcíme a memóriában
                                      //második paraméter az átvinni kívánt byte-ok száma
  driver.waitPacketSent();            //megvárjuk míg megtörténik az átvitel
  ido=millis()-ido;
  Serial.print("Átviteli idő (msec):");Serial.println(ido);
  delay(3000);
}

A vevő programja:

#include <RH_ASK.h>
//#include <SPI.h> //nincs rá szükség, de a példa programban benne volt, nem tudom mikor kellhet?
uint8_t adat[2];   //ebbe a tömbbe helyezi a vételkor az áthozott byte-okat
                   //a tömb típusa kizárólag "uint8_t" lehet, mert a recv() függvény azt várja az első paraméterben
int ertek;         //ebbe a változóba fogjuk tenni az áthozott int értéket
RH_ASK driver;     //RH_ASK objektum létrehozása

void setup()
{
    Serial.begin(9600);  
    if (!driver.init())        //RF inicializálása. Vételt és adást is megvalósit egyszerre, itt csak az adást fogjuk használni
                               //Adás 12-es láb (nincs bekötve), vétel 11-es láb, sebesség 2000bps
                               //a sebességet nem lehet megváltoztatni
                               //a készítő valamiért az Arduino lábakat is bevasalta. Az RH_ASK.h állományban elvileg átírható
     Serial.println("RF inicializálás nem sikerült...");
}

void loop()
{
    if (driver.recv(adat, 2))                   //két byte kiolvasása az adat nevű pufferből, akkor ad vissza értéket, 
                                                //ha megtörtént az adatok vétele, és a paritás is rendben volt
                                                //első paraméter az adatpufferként használt változó, második a várt adatok hossza
    {
      ertek=(unsigned int)adat[1]*256+adat[0];  //a két byte-ból előállítjuk az integer értéket
      if (ertek>32768) ertek=ertek-65536;       //mivel unsigned int értéket hozunk át, a negatív 
                                                //számokról külön kell gondoskodni (lehet, hogy erre van külön függvény)
      Serial.print("Ertek:");
      Serial.println(ertek);         
    }
}

—————————————————————————

Elkezdtem vadul fejleszteni, miután sikeresen kipróbáltam az RF433-LC adó-vevő párost! Átgondoltam, hogy milyen adatokat szeretnék a lakásban, illetve sufniban működő különböző berendezéseim között utaztatni, és arra jutottam, hogy karaktereket biztosan nem akarok átvinni, azonban szükség lehet integer, byte, float és long értékekre is. Sőt, egy vad ötlet alapján még a pontos időt is szeretném átvinni az órák szinkronizálásához. Meg is írtam a dátum és az idő átviteléhez egy programot, amivel 4 byte-on átvihető a pontos idő (dátummal). Így már 4 byte-on mindenféle adat típust átküldhetek, ami az én gyakorlatomban előfordulhat. Ki is szerettem volna próbálni, hogy tényleg képes vagyok-e rá. Alább található az eredmény forráskódja! Amikor kipróbáltam a kész programot, észrevettem, hogy időnként egyes adatokat mintha nem hozna át a vevő. Az adás biztosan megtörténik, de a vételi oldalon az adat nem feltétlenül jelenik meg. Észre sem vettem volna a dolgot, ha nem öt különböző adatot küldök át ciklikusan! A a vételi oldalon öt különböző sornak kellett volna megjelennie egymás alatt, de néha egy-egy sor kimaradt. Ezért kezdtem el foglalkozni az átvitel megbízhatóságával.

A demó programomat gyorsan kiegészítettem néhány változóval. Az adóoldalon beállítottam, hogy minden adás után növekedjen 1-gyel az átvitt adatnak az értéke. Ezzel az adatban benne található, hogy hányszor sugározta ki a vevő. A vételi oldalon a sikeres vételek számát számoltam meg (adat típusonként). Ha egy vétel után az áthozott számból kivonom a sikeres vételek számát, akkor megkapom az elvesztett adatokat. Annak érdekébe, hogy a demó program érthetőségét jól elrontsam, az adatoknak időnként nullától különböző értéket adtam az adás oldalon, illetve pl. a float típusú adatot nem egész értékekkel, hanem csak 0.01-el növeltem ciklusonként. Volt ezzel valamilyen elképzelésem, de nem derült ki semmi értelmes infó az elgondolásból. Viszont a programban már nem javítottam ki. Egyetlen átsugárzott integer értékkel is elvégezhettem volna az összes vizsgálatot, de mint említettem, előbb készült el a program, és utólag jutott eszembe az adatvesztések vizsgálata. Ezzel csak magyarázom, hogy miért lett olyan irdatlanul hosszú és bonyolult ennek az egyszerű feladatnak megoldása.

A vételi oldalon a következő adatokat kaptam a soros monitorban (részlet):

15:34:38.528 -> Pontos idő:2015-2-7  Kiesés:201
15:34:39.130 -> Long:100005150  Kiesés:173
15:34:39.685 -> Int1, int2:5150, -5150  Kiesés:313
5:34:40.311 -> Int, byte1, byte2:5150, 125, 32  Kiesés:179
15:34:40.898 -> Float:151.49  Kiesés:10.75

Ezeket a sorokat ismételgette a vevő. Órákig hagytam futni a programot, és amikor meguntam, kimásoltam a legutolsó néhány sort. Jól látható, hogy az egyes sorokból kiolvasható, hogy hányszor adta az adatot az adó program. A fenti részletben 5150-szer ismételt minden adatot. Az áthozott dátumban ez már nem látható, de mivel az induló értékem a 2001.01.01-nek megfelelő 366-volt, 5150 nappal később a dátum 2015.02.07! Aki nem hiszi, járjon utána. A long típusú változót pl. 100000000-ról indítottam, ezért látszik 100005150 stb.

Meg kell említenem egy igen aggasztó dolgot! A dátum és időpont átadását végző adatok adattartalmának sorosportra írása előtt az integer értékből visszaállítottam a 2000 óta eltelt napokból a dátumot, valamint az éjfél óta eltelt öt másodpercekből az időt, és ezeket szerettem volna kiküldeni a sorosportra. Meglepő dolog történt! Amikor az óra, perc vagy masodperc változók bármelyikét akartam kiírni, a vett adatok folyamatosan 0 értékre álltak be. Mind az öt byte értékére 0-át kaptam. Igazság szerint ennek kiderítése két napig tartott, mert a program egyszer csak elromlott, és kezdetben azt sem tudtam, hogy mi a baj. Visszaállítottam egy régebbi működő verziót, és fokozatosan sorról sorra próbáltam ki a módosítások hatását, így derült ki ennek a három változónak a hatása. Jelenleg még nem tudom az okot, de nagyon valószínű, hogy összetett dologról lehet szó, amihez a RadioHead program működésének is köze van (megszakításokkal dolgozik, memóriába ír stb.). További furcsaság, hogy nem volt elég csak kikommentezni az ora, perc és masodperc változók írását végző sorokat. A program lefordítása és Arduino-ra töltése után, még ki is kellett húzni az USB kábelt a gépből és utána indítani a soros monitort! Ezzel egy napot szívtam, közben eltelt egy éjszaka, leállítottam a gépet (ami a kábel kihúzással egyenértékű) és így derült ki a megoldás! Különben még ma is ott ülnék felette, és bármit csinálok, még a gyári demó program sem működik. Első napon már gép újra telepítésére gondoltam, mivel semmi nem működött Persze csak a RadioHead-al kapcsolatos programok nem mentek, más programok futottak! Mivel a megoldást még nem tudom, kikommenteztem a megdelelő sorokat, és folytattam a munkát!

Három különböző helyen helyeztem el az adót. Először a szerszámos sufniban, itt a vevő és az adó között kb. 10 méter a távolság plusz két vastag téglafal. Közvetlen rálás nincs. A második esetben szobán belül, közvetlen rálátással kb. 5m volt a távolság. A harmadik esetben kb. 50cm volt a távolság, ugyanarra az asztalra tettem az adót és a vevőt. Mint említettem több órát hagytam futni a programokat. Kb. 5 másodperc volt egy teljes ciklus (az öt adatot 500msec szünettel sugároztam, aztán vártam 2.5 másodpercet). A fenti példa 5150 adatának adása így kicsivel több mint 7 órát vett igénybe. Fontos még megjegyezni, hogy a vevő program semmi mással nem töltötte az időt, csak arra várt, hogy adatok érkezzenek. Azzal az esettel, hogy a programnak más feladati is vannak, itt még nem foglalkoztam. Nem is érdekes az adások darabszáma, csak az arányok. Három táblázatot készítettem a három távolságra:

10m:

5m:

0.5m:

A három táblázatból nekem az jön le, hogy a kiesések száma nem a távolságtól függ. A legrosszabb eredményt az adta, amikor egymástól 5m távolságra volt az adó-vevő, és közvetlenül ráláttak egymásra. Érdekes, hogy a nagyobb távolságnál a float adat átvitele kiugróan rossz! Ez nehezen értelmezhető, de úgy látszik az adattartalomnak is lehet köze a kiesések számához. Ez szerintem is reális lehetőség, mert a RadioHead leírása meg is említi, hogy egy byte átvitelét két 6 bites szakaszra bontják, hogy jobb legyen a „DC egyensúly” Bármit is jelent ez, biztosan az adattartalomhoz van köze!

Ha a vevő programnak semmi más dolga nincs, akkor nem tévedünk nagyot, ha feltételezzük, hogy az adások 10%-a nem fog eljutni a vevőbe. Ezek feltehetőleg paritás hibák, és ténylegesen a vevő nem tudta értelmezni az adatokat, ezért meg sem jelentek az recv() függvény kimenetén.

Más problémakör, ha a programunk gyakran el van foglalva valamilyen időigényes feladattal, ezért esetleg adatot veszít. Tételezzük fel, hogy épp akkor kezd bele valamibe, amikor az adat megérkezett! Dolgozik, dolgozik, és a mikor végzett és kiolvasná a RadioHead bufferéből az adatot, már érkezett egy újabb adat. Ekkor nem a rádió zavarok miatt veszítünk adatot, hanem a saját programunk feldolgozási sebessége miatt. Ezt úgy próbáltam szimulálni, hogy a loop()-ban elhelyeztem egy olyan programrészt, ami 1.050 másodpercenként lefut (nem akartam kerek 1 másodpercet, ezért a minimális idő különbség)! Ebben a programrészben delay() függvényt használtam. Ennek különböző értékeket adtam meg.

Az első, legkisebb késleltetési idő 100msec-re lett beállítva. Lássunk is egy részletet a sorosportról (a „dolgozik” felirat pont az amikor letelik a 100msec-es delay() ):

07:54:44.131 -> Float:111.15  Kiesés:0.49
07:54:44.570 -> Dolgozik…
07:54:45.620 -> Dolgozik…
07:54:46.677 -> Dolgozik…
07:54:46.724 -> Pontos idő:2004-1-22  Kiesés:102
07:54:47.327 -> Long:100001116  Kiesés:141
07:54:47.728 -> Dolgozik…
07:54:47.898 -> Int1, int2:1116, -1116  Kiesés:67
07:54:48.531 -> Int, byte1, byte2:1116, 125, 32  Kiesés:86(ciklusszám:1030)
07:54:48.785 -> Dolgozik…
07:54:49.120 -> Float:111.16  Kiesés:0.49
07:54:49.835 -> Dolgozik…
07:54:50.870 -> Dolgozik…

Ennél a programnál az első futtatáskor észrevettem, hogy az egyik kiesés érték negatívnak adódott. Azért raktam be az int,byte,byte adatcsomaghoz a vételi ciklusszámot, mert kíváncsi voltam, hogyan keletkezik negatív érték. Segített is kiszúrni az esetet:

06:22:06.392 -> Dolgozik…
06:22:06.648 -> Long:100000000  Kiesés:0
06:22:07.228 -> Int1, int2:0, 0  Kiesés:0
06:22:07.448 -> Dolgozik…
06:22:07.850 -> Int, byte1, byte2:0, 125, 32  Kiesés:0(ciklusszám:0)
06:22:08.498 -> Dolgozik…
06:22:08.498 -> Int, byte1, byte2:0, 125, 32  Kiesés:-1(ciklusszám:1)
06:22:09.556 -> Dolgozik…
06:22:10.605 -> Dolgozik…

Ez is nagyon érdekes, mert úgy tűnik, mintha kétszer is átadta volna ugyanazt az adatot a RadioHead. Találtam egy durvább hibát is:

08:09:42.179 -> Dolgozik…
08:09:43.229 -> Dolgozik…
08:09:43.930 -> Pontos idő:2004-7-20  Kiesés:124
08:09:44.285 -> Dolgozik…
08:09:44.533 -> Pontos idő:2004-7-20  Kiesés:123
08:09:45.137 -> Pontos idő:2004-7-20  Kiesés:122
08:09:45.335 -> Dolgozik…
08:09:46.373 -> Dolgozik…
08:09:46.373 -> Pontos idő:2004-7-20  Kiesés:121
08:09:47.443 -> Dolgozik…
08:09:48.500 -> Dolgozik…
08:09:48.949 -> Pontos idő:2004-7-21  Kiesés:121
08:09:49.550 -> Dolgozik…

A fenti eset már elgondolkodtató! Nézzük az 5. és 6. sort. Ugyanaz a dátum érkezett meg kb. 600 msec eltéréssel. Tudjuk, hogy két adat között 500msec időt várok az adó programban. Egy adat küldése kb 90msec. Ezt megmértem az adó oldalon. Tehát, ez a két sor két különböző adat, de a RadioHead megismételt egy adatot, vagyis az újabb adat érkezésekor nem írta felül az előzőt! Nyugtalanító hiba, bár megnyugtató, hogy az az egy adat mindkét esetben helyesen érkezett meg, így a feldolgozás során ez talán nem okozhat majd problémát. Minden esetre fel kell készülni a programban erre is. Ha pl. átlagolni akarjuk az érkezett adatokat, akkor ez hamis átlagot eredményez.

Sajnos a fenti hibák miatt már nem teljesen biztos a statisztika, hiszen a fenti ismétlési hibák javíthatták a kiesések számát, mint ez kicsivel feljebb látszik is, a pontos idő ismétléseivel csökken a kiesésszám, egy másik adat típus kiesés száma meg nő! Ettől függetlenül a tapasztalt értékek:

100ms delay():

400ms delay():

700ms delay()

Meg kell jegyeznem, hogy ezek a táblázatok az eredmény szöveges értékelése nélkül nem sokat érnek. A 100msec késleltetés beépítésekor már felfedezhető volt időnként a fentebb részletezett adat ismétlés, és egyben adat vesztés. Látszik is, hogy egyes adat típusok jelentősen megnőttek, mások jelentősen lecsökkentek. Az átlag azonban mintha még javult is volna! Fontos infó, hogy itt a kisérletet rálátással kb. 5m távolságból szobán belül csináltam. Esetleg elmozdítottam az antennát, és jobb volt a vétel, ezért lett jobb az átlag. Még a 400msec késleltetés sem okozott túlzott hibát, de itt már szemmel nézve a soros port kimenetét, elég sok ismétlés látszik. A 700msec azonban már katasztrofális eredményt hozott. Nyilván számítunk is rá, hogy időnként elveszítünk egy-egy adatot, sőt józan ész szerint minden másodikat! Azonban érdemes ránézni a soros port kimenetére:

16:25:54.178 -> Dolgozik…
16:25:55.232 -> Dolgozik…
16:25:56.261 -> Dolgozik…
16:25:56.261 -> Long:100001025  Kiesés:113
16:25:57.322 -> Dolgozik…
16:25:57.322 -> Long:100001025  Kiesés:112
16:25:57.486 -> Int, byte1, byte2:1026, 125, 32  Kiesés:413(ciklusszám:613)
16:25:58.373 -> Dolgozik…
16:25:58.373 -> Int, byte1, byte2:1026, 125, 32  Kiesés:412(ciklusszám:614)
16:25:59.439 -> Dolgozik…
16:26:00.486 -> Dolgozik…
16:26:00.652 -> Pontos idő:2003-10-25  Kiesés:87
16:26:01.515 -> Dolgozik…
16:26:01.515 -> Pontos idő:2003-10-25  Kiesés:86
16:26:01.864 -> Int1, int2:1027, -1027  Kiesés:54
16:26:02.562 -> Dolgozik…
16:26:02.562 -> Int1, int2:1027, -1027  Kiesés:53
16:26:03.622 -> Dolgozik…
16:26:03.622 -> Int1, int2:1027, -1027  Kiesés:52
16:26:04.678 -> Dolgozik…
16:26:05.726 -> Dolgozik…
16:26:05.726 -> Int1, int2:1027, -1027  Kiesés:51
16:26:06.775 -> Dolgozik…
16:26:06.775 -> Int1, int2:1027, -1027  Kiesés:50
16:26:07.815 -> Dolgozik…
16:26:07.815 -> Int1, int2:1027, -1027  Kiesés:49
16:26:08.044 -> Float:110.28  Kiesés:4.07
16:26:08.876 -> Dolgozik…
16:26:09.949 -> Dolgozik…
16:26:10.977 -> Dolgozik…
16:26:10.977 -> Float:110.28  Kiesés:4.06
16:26:11.232 -> Long:100001029  Kiesés:115
16:26:12.048 -> Dolgozik…
16:26:12.048 -> Long:100001029  Kiesés:114
16:26:13.093 -> Dolgozik…
16:26:13.093 -> Long:100001029  Kiesés:11
16:26:14.131 -> Dolgozik…
16:26:15.174 -> Dolgozik…

Már szinte csak ismétlések vannak benne. Meg is lepett az eredmény, hiszen a kieseések arányát előzetesen legalább 40-50%-ra becsültem, viszont jóval kisebb lett. Ezek szerint a RadioHead nem úgy működik ahogyan képzeltem. Az ismétlesekre magyarázat lehet, ha azt csinálja, hogy a buffer tartalmát nem írja felül egy új adat érkezésekor, ha még nem olvastuk ki. Ekkor persze egyértelmű, hogy kiolvasáskor kétszer is ugyanazt kapjuk. Nem teljesen logikus, és sántít a magyarázat, de valami hasonlót tudok elképzelni.

Végső következtetés: szerintem ha jelentősen csökkentjük az adások számát (sűrűségét), és két-három ismétlést is beiktatunk az adó oldalon, valamint a vevő oldalon nincsenek időigényes feladatok, akkor nagyon jól használható eszköz. Terveim szerint az időpontot óránként küldeném, amit háromszor ismétlek véletlenszerű várakozással (5 másodperces kerekítéssel). A hőmérséklet és egyéb adatokat elegendő 5-10 percenként küldeni, ide szerintem elég egy ismétlés 5-10 másodperc után (ezt is véletlenszerű értékkel). Valamint lesznek ad-hock jelek, például ha lekapcsolom a lámpát az egyik szobában egy dupla klikkel, akkor az összes más helyen (ami nincs össze-vezetékezve) szintén lekapcsolódik a világítás. A világítás vezérlésem már most is tudja ezt a funkciót, de csak a lakás szobáira. A borotválkozó tükör világítása nincs rendszerbe kötve, és nem is tudnám csak falbontással vezetékezni. A sufniba sem kell majd kimennem, ha bekapcsolva felejtettem a lámpát! Ennek nagyon fogok örülni, nálam minden szerelgetés egy plusz felesleges út, hóban esőben szélviharban!

A biztonság kedvéért még kipróbálom azt is, hogy egy külön Arduino-val veszem a rádió vevő adatait, amit azonnal átküldök I2C-n egy másik Arduino-nak, ami a tényleges központi vagy egyéb feladatokat elvégzi. Így biztosan nem lesznek problémák a feldolgozási időből. Nagyon aggaszt az a jelenség, hogy a vett adatok helyett 0-ák jelennek meg, ha a programba beírok egyes utasításokat, pl. az ora, perc változók küldését a sorosportra. A példa programban kipróbáltam volna azt az esetet is, hogy csak egy vétel után engedélyezem a loop()-nak egyéb funkciók végrehajtását. Sajnos ha ezt befordítom (forrásban benne van kikommentezve), akkor csupa 0 byte érkezik.

Az adó forráskódja:

#include <RH_ASK.h>

RH_ASK driver;
uint8_t adat[5];              //ebbe a tömbbe fogjuk elhelyezni az átvinni kívánt byte-okat
long long_ertek=100000000;    //ezzel a long értékkel kezdjük az átvitelt, és minden adás után növeljük az értékét 1-el
int int_ertek1=0;             //ezzel az int értékkel kezdjük az átvitelt, és minden adás után növeljük az értékét 1-el
int int_ertek2=0;             //ezzel a int értékkel kezdjük az átvitelt, és minden adás után csökkentjük az értékét 1-el
byte byte_ertek1=125;         //ezt a byte értéket visszük át, értékét nem változtatjuk, mert túl gyakran nullázódna
byte byte_ertek2=32;          //ezt a byte értéket visszük át, értékét nem változtatjuk, mert túl gyakran nullázódna
float float_ertek=100.0;      //ezzel a float értéket kezdjük az átvitelt, minen adás után az értékét növeljük 0.01-el
byte c[]={0,0,0,0};           //segéd változó a float átviteléhez. Egy float értéket 
unsigned int atv_datum=366;   //ezzel a dátummal kezdjük az átvitelt (2001.01.01), minden adás után 1-el növeljük az átvitt értéket, így vételi oldalon minden alkalommal egy nappal nó a dátum
unsigned int atv_ido=0;       //ezzel az időponttal kezdjük az átvitelt, és minden adás után 1-el növeljük az értékét, így a vételi oldalon minden vételnék 5 másodperccel nő az időpont
unsigned int ido5sec;         //segéd változó az időpont átviteléhez
unsigned int datumnap;        //segéd változó a dátum átviteléhez
long ido;                     //az átviteli idő méréséhez segédváltozó

void setup()        //RH_ASK objektum létrehozása
{
  Serial.begin(9600);
    if (!driver.init())        //RF inicializálása. Vételt és adást is megvalósit egyszerre, itt csak az adást fogjuk használni
                               //Adás 11-es láb, vétel 10-es láb (nincs bekötve), sebesség 2000bps
                               //a sebességet nem lehet megváltoztatni
                               //a készítő valamiért az Arduino lábakat is bevasalta. Az RH_ASK.h állományban elvileg átírható
      Serial.println("RF inicializálás nem sikerült...");
}

void loop()
{
  ido=millis();
  //dátum és idő átvitele két int számmal
  datumnap=atv_datum;
  ido5sec=atv_ido;
  adat[0]=0;                           
  adat[1]=datumnap&255;                  //első 8 bit kimaszkolása
  adat[2]=(datumnap>>8)&255;             //felső 8 bit eltolása jobbra és kimaszkolás
  adat[3]=(ido5sec)&255;                 //első 8 bit kimaszkolása
  adat[4]=(ido5sec>>8)&255;              //felső 8 bit eltolása jobbra és kimaszkolás
  driver.send((uint8_t *)&adat, 5);      //első paraméter az átvinni kíván adatbájtok kezdőcíme a memóriában
  driver.waitPacketSent();               //megvárjuk míg megtörténik az átvitel
  ido=millis()-ido;
  Serial.print(atv_datum);
  Serial.print(" ");
  Serial.print(atv_ido);
  Serial.print("  ");
  Serial.print(ido);
  Serial.println("msec");
  atv_datum++;
  atv_ido++;
  delay(500);

  ido=millis();
  adat[0]=1;                             //long adat indexe
  adat[1]=long_ertek&255;                //első 8 bit kimaszkolása
  adat[2]=(long_ertek>>8)&255;           //8 bit eltolása jobbra és kimaszkolás
  adat[3]=(long_ertek>>16)&255;          //8 bit eltolása jobbra és kimaszkolás
  adat[4]=(long_ertek>>24)&255;          //8 bit eltolása jobbra és kimaszkolás
  driver.send((uint8_t *)&adat, 5);      //első paraméter az átvinni kíván adatbájtok kezdőcíme a memóriában
  driver.waitPacketSent();               //megvárjuk míg megtörténik az átvitel
  ido=millis()-ido;
  Serial.print(long_ertek);
  Serial.print("  ");
  Serial.print(ido);
  Serial.println("msec");
  long_ertek++;
  delay(500);
  
  ido=millis();
  adat[0]=2;                             //int,int adat indexe
  adat[1]=int_ertek1&255;                //első 8 bit kimaszkolása
  adat[2]=(int_ertek1>>8)&255;           //felső 8 bit eltolása jobbra és kimaszkolás
  adat[3]=int_ertek2&255;                //első 8 bit kimaszkolása
  adat[4]=(int_ertek2>>8)&255;           //felső 8 bit eltolása jobbra és kimaszkolás
  driver.send((uint8_t *)&adat, 5);      //első paraméter az átvinni kíván adatbájtok kezdőcíme a memóriában
  driver.waitPacketSent();               //megvárjuk míg megtörténik az átvitel
  ido=millis()-ido;
  Serial.print(int_ertek1);
  Serial.print(" ");
  Serial.print(int_ertek2);
  Serial.print("  ");
  Serial.print(ido);
  Serial.println("msec");
  int_ertek2--;
  delay(500);

  ido=millis();
  adat[0]=3;                            //int,byte,byte adat indexe
  adat[1]=int_ertek1&255;               //első 8 bit kimaszkolása
  adat[2]=(int_ertek1>>8)&255;          //felső 8 bit eltolása jobbra és kimaszkolás
  adat[3]=byte_ertek1;                  
  adat[4]=byte_ertek2;                  
  driver.send((uint8_t *)&adat, 5);     //első paraméter az átvinni kíván adatbájtok kezdőcíme a memóriában
  driver.waitPacketSent();              //megvárjuk míg megtörténik az átvitel
  ido=millis()-ido;
  Serial.print(int_ertek1);
  Serial.print(" ");
  Serial.print(byte_ertek1);
  Serial.print(" ");
  Serial.print(byte_ertek2);
  Serial.print("  ");
  Serial.print(ido);
  Serial.println("msec");
  int_ertek1++;
  delay(500);

  ido=millis();
  adat[0]=4;                           //float adat indexe
  memcpy(&c,&float_ertek,4);
  adat[1]=c[0];                        
  adat[2]=c[1];;                       
  adat[3]=c[2];;                       
  adat[4]=c[3];;                       
  driver.send((uint8_t *)&adat, 5);   //első paraméter az átvinni kíván adatbájtok kezdőcíme a memóriában
  driver.waitPacketSent();            //megvárjuk míg megtörténik az átvitel
  ido=millis()-ido;
  Serial.print(float_ertek,2);
  Serial.print("  ");
  Serial.print(ido);
  Serial.println("msec");
  Serial.println();
  float_ertek=float_ertek+0.01;

  delay(2500);
}


unsigned int datum_elteltnap(int ev, byte ho, byte nap) {
/******************************************************************************************************************************
 * Ez a függvény egy dátumot alakít át a 2000 év óta eltelt napok számává. Alapvetően a pillanatnyi dátum átadására lett      *
 * készítve, így nem volt cél akármilyen dátum érték átvitele. Mivel a visszaadott érték unsigned int csak * 65535 napot,     *
 * azaz 178 évet tud kezelni. Az egyszerűség kedvéért (szökőévek számolgatása), 2000 év lett a bázis év, így ebben a          *
 * formájában 2178-ig működhet.                                                                                               *
 * Bemnő paraméterek:                                                                                                         *
 *                    ev - évszázadokkal megadva pl. 2021 (csak 2001 és 2178 közötti érték adható meg) A 2000 évet rosszul    *
 *                         számolja, mert az szökőév, de nincs rá szükség, mert az már  múlt, így csak 2001-től működik       *
 *                    ho - 1-12 közötti szám                                                                                  *
 *                    nap - 1-31 közötti szám, de az adott hónaptól függ, szökőéveket is figyelembe kell venni február hóban. *
 * Visszaadott érték:                                                                                                         *
 *                    unsigned int érték 1-65535 közötti szám.                                                                *
 *                    Hibásan megadott bemenő paraméter esetén 0-át ad vissza. Hibás lehet a 2000 előtt és 2178 utáni év,     *
 *                    1-nél kisebb vagy 12-nél nagyobb hónap, illetve ha a nap 1-nél kisebb vagy az adott hónapban            *
 *                    lehetséges maximális (30,31 februárban 28 szökő évben 29) napszámnál.                                   *
 * Késiült egy elteltnap_datum() függvény is, ami ennek a függvények az eredményét visszaalakítja év, hó,nap értékre.         *    
 ******************************************************************************************************************************/
  if (ev<2001 or ev>2178) {return(0);}            //év helyességének ellenőrzése
  if (ho<1 or ho>12) {return 0;}                  //hónap helyességének ellenőrzése
  //hónap napjainak ellenőrzése (szökőév figyelembevételével)
  if ((ho==1 or ho==3 or ho==5 or ho==7 or ho==8 or ho==10 or ho==12) and nap>31) {return 0;};  
  if ((ho==4 or ho==6 or ho==9 or ho==11) and nap>30) {return 0;}  
  if (ho==2) {                                    //februárt vizsgáljuk
    if (szokoev(ev)) {                            //megmondja, hogy az adott év szökőév-e
      if (nap>29) { return 0;}}                   //ez egy szökőév és nagyobbat adtunk meg mint 29
    else {
      if (nap>28) { return 0;}}                   //ez nem szökőév és nagyobbat adtunk meg mint 28
  }
  //eddig csak ellenőriztünk, most jön a 2000 óta elmúlt napok kiszámítása
  int ho_napjai[] = {0,31,59,90,120,151,181,212,243,273,304,334,365};   // az évből eltelet hónapok napjainak száma, nem szökőéveben
  int elmult_szokonap=(ev-2000)/4;                //ebbe számoljuk ki a szökőévekből adódó plusz napok számát
  if (szokoev(ev)) {elmult_szokonap--;}           //ha az aktuális év éppen szökőév, akkor azt nem szabad beleszámolni, mert később a hónapok számlálásakor fogjuk figyelemebe venni
  if (ev>=2100) {elmult_szokonap--;}              //2100 nem szökőév, pedig az előbb a 4-el osztással azt is annak számoltuk
  int elmult_nap = (ev-2000) * 365;               // eddig ennyi napja volt az elmúlt éveknek a szökőévek figyelembevétele nélkül (az induló év 2000)
  elmult_nap=elmult_nap+elmult_szokonap;          //ennyi nap telt el az éppen aktuális évig                                         
  elmult_nap = elmult_nap+ho_napjai[ho-1] + nap;  //kiszámoljuk, mennyi nap telt el az aktuális évben, és azt is hozzáadjuk (itt még nem foglalkozunk azzal, hogy az aktuális év szökőév-e)
  if (ho>2) {                                     //ha már elmúlt február, akkor meg kell állapítani, hogy az aktuális év szökőév-e
    if (szokoev(ev)) {elmult_nap++;}              // ha az aktuális éveben már elmúlt február, és az év szökőév, akkor még egy napot hozzá kell adni
  }
  return (elmult_nap);                            //és ezzel megkaptuk 2000 óta óta eltelt napok számát
}

bool szokoev(int ev)  {
/**************************************************************************************
 * Ez a függvény megállapítja egy évről, hogy szökőév-e. Bemenő paraméter az év       *
 * évszázadokkal megadva pl. 2021. Az eredmény fals, ha nem szökőév, és true ha igen. *
 * Egy adott év akkor szökőév, ha 4-el osztható, de minden 100. év nem szökőév        *
 * minden 400. pedig mégis szökőév. Pl. szökőévek: 4, 8, ...2020, 2024 stb.           *
 * Nem szökőév 100, 200 ... 1900, 2000. Azonban mégis szökőév: 1200, 1600, 2000 stb.) *
 **************************************************************************************/
  if (ev==0) {return false;}             //a 0.évet nem vesszük szökőévnek 
  bool szoko_ev=false;                
  if (ev%4==0) {                         //ha néggyel osztva nem ad maradékot, akkor szökőév
    szoko_ev=true;
    if (ev%100==0) {                     //ha 100-al osztva nem ad maradékot, akkor nem szökőév
      szoko_ev=false;
      if (ev%400==0) {szoko_ev=true;}    //ha 400-al osztva nem ad maradékot, akkor mégis szökőév 
    }
  }
  return szoko_ev;
}

unsigned int ido_eltelt5sec(byte ora, byte perc, byte masodperc) {
/****************************************************************************************************
 * Ez a függvény az éjfél óta eltelt időt alakítja másodpercekké és visszaadja egy unsigned int     *
 * értékben. Mivel egy nap 86400 másodpercből áll, és ez nem fér el az unsigned int-ben (max:65535) *
 * 5 másodperces felbontásban adjuk vissza az időt, tehát csak akkor lesz pontos a visszaadott      *
 * érték, ha a másodpercek száma éppen osztható 5-el. Az időpont átvitele mindig az 5-el osztható   *
 * másodpercekben legyen (pl. egész perckor)                                                        *
 * Bemenő paraméter az óra, perc és másodperc. A másodperc lehet bármilyen érték 0-59 között        *
 * de mindig 5-el osztható másodperceben adja meg, pl. 59-ből 55-öt csinál.                         *
 * Ha az óra nagyobb mint 23, akkor 23-ra állítja be. Ha a perc illetve másodperc nagyobb mint 59,  *
 * akkor 59-re állítja be.                                                                          *
 ****************************************************************************************************/
//bemenő paraméterek ellenőrzése és hibás érték esetén maximum beállítása
if (ora>23) {ora=23;}
if (perc>59) {perc=59;}
if (masodperc>59) {masodperc=59;}
masodperc=masodperc/5;                 //a másodperceket 5-el osztjuk, és csak ennek egész részét vesszük figyelembe
return (ora*720+perc*12+masodperc);    //egy órában 720-sor öt másodperc van, egy percben pedig 12-szer öt másodperc
}

Végül a vevő forráskódja:

#include <RH_ASK.h>
#include <SPI.h> // Not actualy used but needed to compile

uint8_t adat[5];   //ebbe a tömbbe helyezi a vételkor az áthozott byte-okat
                   //a tömb típusa kizárólag "uint8_t" lehet, mert a recv() függvény azt várja az első paraméterben
RH_ASK driver;     //RH_ASK objektum létrehozása

int ev;                           //datum adat esetén ebbe a változóba konvertálja bele az elteltnap_datum() függvény az évet
byte ho,nap,ora,perc,masodperc;   //datum illetve idő adat esetén ezekbe a változókba konvertál az elteltnap_datum() illetve eletelt5sec_ido() függvény
byte a[] = {0,0};                 //a két byte hosszú adatok megfelelő változókba töltéséhez egy segéd tömb
byte b[] = {0,0,0,0};             //a négy byte hosszú adatok megfelelő változókba töltéséhez egy segéd tömb

unsigned int elteltnap;           //segéd változó a dátum adat elteltnap_datum() függvény meghívásához
unsigned int eltelt5sec;          //segéd változó az idő adat eltelt5sec_ido() függvény meghívásához
long long_ertek;                  //ha a küldő oldal long változót küld, ebbe a változóba töltjük bele a kapott adatokat
int int_ertek1;                   //ha a küldő oldal int változót küld, ebbe a változóba töltjük bele a kapott adatokat
int int_ertek2;                   //ha a küldő oldal int változót küld, ebbe a változóba töltjük bele a kapott adatokat
byte byte_ertek1;                 //ha a küldő oldal byte változót küld, ebbe a változóba töltjük bele a kapott adatokat
byte byte_ertek2;                 //ha a küldő oldal byte változót küld, ebbe a változóba töltjük bele a kapott adatokat
float float_ertek;                //ha a küldő oldal float változót küld, ebbe a változóba töltjük bele a kapott adatokat

bool ervenyes_adat0=false;        //ez a változót true-ra billenti az adatfeldolgozó programrész, ha érvényes adat (dátum,idő) érkezett a 0-ás típusú adat érkezett
                                  //a 0-ás típus jelenthet egy külön adóberendezést, vagy ugyanannak az adónak egy elkülönített adat típusát is, ezt a program 
                                  //írójának kell nyilvántartani és tudni!
bool ervenyes_adat1=false;        //ugyanaz mint feljebb csak az 1-es típusú adatra
bool ervenyes_adat2=false;        //ugyanaz mint feljebb csak az 2-es típusú adatra
bool ervenyes_adat3=false;        //ugyanaz mint feljebb csak az 3-es típusú adatra
bool ervenyes_adat4=false;        //ugyanaz mint feljebb csak az 4-es típusú adatra
bool vetel=false;                 //segéd változó egy olyan működés kipróbálásához, amikor csak egy vételt követő adatfeldolgozás után lehet egyéb programrészeket futtatni

//Ezeket a változókat a vétel kiesések (adatvesztés) számlálásához, statisztikáihoz használom. 
//a ..ciklusszam változók tartalmát inden vételnél növelem egyel, az ..ertek változóban pedig megjegyzem
//a vett adatot, amit az adó programrészben minden ciklusban egy-el növelek (kivéve float változó, mert ott
//0.01-el növelek, bár erre semmilyen lényeges indok nem volt)!
float f_ciklusszam=99.99 ;        //float adat vétel statisztikához
float f_ertek;
unsigned int ii_ciklusszam=365;   //float adat vétel statisztikához 
unsigned int ii_ertek;
int i1_ciklusszam=-1;             //int, int adat vétel statisztikához
int i2_ciklusszam=-1;             //int, byte, byte adat vétel statisztikához
int i1_ertek,i2_ertek;
long l_ciklusszam=99999999;       //long adat vétel statisztikához
long l_ertek;

long ido_tmp=millis();            //segéd változó az idő méréshez, ami a bemutató programban a rádió vételtől független programrészek futtatását indítja el

void setup()
{
    Serial.begin(115200);  
    if (!driver.init())        //RF inicializálása. Vételt és adást is megvalósit egyszerre, itt csak az adást fogjuk használni
                               //Adás 11-es láb (nincs bekötve), vétel 10-es láb, sebesség 2000bps
                               //a sebességet nem lehet megváltoztatni
                               //a készítő valamiért az Arduino lábakat is bevasalta. Az RH_ASK.h állományban elvileg átírható
     Serial.println("RF inicializálás nem sikerült...");
}


void loop()
{
  if (driver.recv(adat,5))  {                 //öt byte kiolvasása az adat nevű pufferből, akkor ad vissza értéket, 
                                              //ha megtörtént az adatok vétele, és a paritás is rendben volt
                                              //első paraméter az adatpufferként használt változó, második a várt adatok hossza
    //itt következik a kapott adatok feldolgozása. Az első byte adja meg, hogy milyen típusú adat érkezett, illetve a küldőt is be lehet
    //azonosítani. Egy adat-gyűjtő központban minden egyes küldő egyedi címmel ellátott adatának a megfelelő változókba töltését
    //külön meg kell írni. 
    vetel=true;                               //beállítjuk a jelzőt, hogy éppen túlvagyunk egy adat vételen, lehet egyéb programrészeket futtatni
    switch (adat[0]) {                        //az első byte dönti el, hogy az adat honnan érkezett és milyen információt tartalmaz
      case 0:                                 //Ez egy pontos időt tartalmazó adat (egy int-ben a 2000 óta eltelt napok, és egy másik int-ben az éjfél óta eltelt idő) 
        a[0]=adat[1];                         //az második kapott byte a[0]-ba
        a[1]=adat[2];                         //az második kapott byte a[1]-ba
        memcpy(&elteltnap,&a,2);              //az elteltnap változó kezdőcímére másoljuk a kapott két byte-ot a[] ból. Így elteltnap tartalma egy 
                                              //két byte-ból összerakott szabályos integer érték lesz.
        a[0]=adat[3];
        a[1]=adat[4];
        memcpy(&eltelt5sec,&a,2);             //ugyanaz mint feljebb, csak ez az integer az eltelt 5 másodperceket adja meg
        idopont(elteltnap,eltelt5sec);        //az eltel5sec-ből visszakonvertálja az időpontot az ora, perc, masodperc változókba
        ervenyes_adat0=true;
        ii_ertek=elteltnap;                   //elteszem a kapott adtot, ami minden adás esetén 1-el nő
        ii_ciklusszam++;                      //számolom az érvényes kiolvasások számát (az ii_ertek és ii_ciklusszam különbsége)
                                              //megadja az elvesztett adatok számát
        break;  
      case 1:                                 //long érték visszakonvertálása
        b[0]=adat[1];
        b[1]=adat[2];
        b[2]=adat[3];
        b[3]=adat[4];
        memcpy(&long_ertek,&b,4);             //ez egy long értéket másol össze a négy érkezett byte-ból
        ervenyes_adat1=true;
        l_ertek=long_ertek;                   //elteszem a kapott adatot, ami minden adás esetén 1-el nő
        l_ciklusszam++;                       //számolom az érvényes kiolvasások számát (az l_ertek és l_ciklusszam különbsége)
                                              //megadja az elvesztett adatok számát
        break;  
      case 2:                                 //int, int visszakonvertálása (két int érték érkezett)
        a[0]=adat[1];
        a[1]=adat[2];
        memcpy(&int_ertek1,&a,2);             //ez egy int értéket másol össze az első két érkezett byte-ból
        a[0]=adat[3];
        a[1]=adat[4];
        memcpy(&int_ertek2,&a,2);             //ez egy int értéket másol össze a harmadik és negyedik érkezett byte-ból
        ervenyes_adat2=true;
        i1_ertek=int_ertek1;                  //elteszem a kapott adtot, ami minden adás esetén 1-el nő
        i1_ciklusszam++;                      //számolom az érvényes kiolvasások számát (az i1_ertek és i1_ciklusszam különbsége)
                                              //megadja az elvesztett adatok számát
        break;  
      case 3:                                 //int, byte, byte visszakonvertálása (egy int és két byte adat érkezett)
        a[0]=adat[1];
        a[1]=adat[2];
        memcpy(&int_ertek1,&a,2);             //ez egy int értéket másol össze az első két érkezett byte-ból
        byte_ertek1=adat[3];
        byte_ertek2=adat[4];
        ervenyes_adat3=true;
        i2_ertek=int_ertek1;                  //elteszem a kapott adtot, ami minden adás esetén 1-el nő
        i2_ciklusszam++;                      //számolom az érvényes kiolvasások számát (az l_ertek és l_ciklusszam különbsége)
                                              //megadja az elvesztett adatok számát
        break;  
      case 4:                                 //float visszakonvertálása (egy float adat érkezett)
        b[0]=adat[1];
        b[1]=adat[2];
        b[2]=adat[3];
        b[3]=adat[4];
        memcpy(&float_ertek,&b,4);            //ez float értéket másol össze a négy érkezett byte-ból
        ervenyes_adat4=true;
        f_ertek=float_ertek;                  //elteszem a kapott adatot, ami minden adás esetén 1-el nő
        f_ciklusszam=f_ciklusszam+0.01;       //számolom az érvényes kiolvasások számát (az l_ertek és l_ciklusszam különbsége)
                                              //megadja az elvesztett adatok számát
        break;  
    }
  }

  //ezek a programrészek folyamatosan futnak, miközben a vételre vár a RF433-LC vevő
  //Ha adat érkezik, akkor az ervenyes_adaX változó jelzi, hogy valahonnan adat jött, 
  //és itt sok minden mással együtt fel lehet dolgozni a kapott adatot pl. kiírjuka  soros portra
  if (ervenyes_adat0) {                    //ez itt a 0-as adat feldolgozását végző programrész. Itt soros portra írjuk az adatot.
    ervenyes_adat0=false;                  //rögtön fals-ra állítjuk a jelzőt, hogy következő loop-ban ne hajtsuk végre a feldolgozást, csak ha új adat érkezik  
    Serial.print("Pontos idő:");
    Serial.print(ev);
    Serial.print("-");
    Serial.print(ho);
    Serial.print("-");
    Serial.print(nap);
//    Serial.print("  ");                  //Itt nagyon furcsa dolog történt. Ha az ora, perc vagy másodperc változók bármelyikét ki szeretném írni a soros portra
//    Serial.print(ora);                   //a vett adatok mindegyike 0. A RadioHead csak paritással ellenőrzött adatot ad vissza, ezért úgy gondolom, hogy itt már
//    Serial.print(":");                   //az adatok sérülnek valamiképpen. Hosszasan kísérletezgettem! Ha ezeket a sorokat nem kommentezem ki, akkor látszólag
//    Serial.print(perc);                  //jönnek az adatok, de minden kapott byte 0 értéket vesz fel. A sorok kommentezése után nem elég a programot újra
//    Serial.print(":");                   //lefordítani, még a soros portból is ki kell húzni az USB csatlakozót, mert csak akkor jelennek meg újra a valós vett adatok!!
//    Serial.println(masodperc);           //Még nem jöttem rá, hogy itt mi okozza a problémát. Valószínűleg összefügg a RadioHead működésével (megszakításokat használ
                                           //memóriába ír stb.), akár hiba is lehet benne. Ez aggasztó!
    Serial.print("  Kiesés:");
    Serial.println(ii_ertek-ii_ciklusszam);
  }
  if (ervenyes_adat1) {                    //ez itt a 1-s adat feldolgozását végző programrész. Itt soros portra írjuk az adatot.
    ervenyes_adat1=false;                  //rögtön fals-ra állítjuk a jelzőt, hogy következő loop-ban ne hajtsuk végre a feldolgozást, csak ha új adat érkezik   
    Serial.print("Long:");
    Serial.print(long_ertek);
    Serial.print("  Kiesés:");
    Serial.println(l_ertek-l_ciklusszam);
  }
  if (ervenyes_adat2) {                    //ez itt a 2-es adat feldolgozását végző programrész. Itt soros portra írjuk az adatot.
    ervenyes_adat2=false;                  //rögtön fals-ra állítjuk a jelzőt, hogy következő loop-ban ne hajtsuk végre a feldolgozást, csak ha új adat érkezik       
    Serial.print("Int1, int2:");
    Serial.print(int_ertek1);
    Serial.print(", ");
    Serial.print(int_ertek2);
    Serial.print("  Kiesés:");
    Serial.println(i1_ertek-i1_ciklusszam);
  }
  if (ervenyes_adat3) {                    //ez itt a 3-as adat feldolgozását végző programrész. Itt soros portra írjuk az adatot.
    ervenyes_adat3=false;                  //rögtön fals-ra állítjuk a jelzőt, hogy következő loop-ban ne hajtsuk végre a feldolgozást, csak ha új adat érkezik     
    Serial.print("Int, byte1, byte2:");
    Serial.print(int_ertek1);
    Serial.print(", ");
    Serial.print(byte_ertek1);
    Serial.print(", ");
    Serial.print(byte_ertek2);
    Serial.print("  Kiesés:");
    Serial.print(i2_ertek-i2_ciklusszam);
    Serial.print("(ciklusszám:");          //Ez is egy érdekes furcsaság! Miután bekapcsoltam a lejebb található, további feldolgozás időigényét szimuláló
    Serial.print(i2_ciklusszam);           //programrészt /"if (ido_tmp+1050<millis())"/, negatív értékek jelentek a kiesés adatokban. Kiderült, hogy ezek
    Serial.println(")");                   //úgy keletkeznek, hogy egyes vétel után ugyanazt az adatot megismétli a program. Tehát egy adatot vesz, de 
                                           //a RadioHead pufferéből driver.recv(adat,5) feltétel kétszer egymás után is ugyanazt az adatot olvastatja ki
                                           //a programmal. Itt szerintem egyértelműen valamilyen memóriában zajló anomália okoz problémát. Talán a 
                                           //RadioHead a hibás, mert a saját programrészeimben semmi olyat nem látok, ami zavart okozhatna.
  }
  if (ervenyes_adat4) {                    //ez itt a 4-es adat feldolgozását végző programrész. Itt soros portra írjuk az adatot.
    ervenyes_adat4=false;                  //rögtön fals-ra állítjuk a jelzőt, hogy következő loop-ban ne hajtsuk végre a feldolgozást, csak ha új adat érkezik   
    Serial.print("Float:");       
    Serial.print(float_ertek,2);
    Serial.print("  Kiesés:");
    Serial.println(f_ertek-f_ciklusszam);
  }

/*  if (vetel) {                          //ez a programrész csak akkor fut le, amikor éppen megérkezett egy adat
    vetel=false;                          //ekkor biztosan lezajlik a feldolgozás a következő adat érkezéséig (ha nem túl hosszú!
    delay(100);
    Serial.println("Dolgozik...");  
  }*/
  if (ido_tmp+1050<millis()) {            //ez fél másodpercenként fut le a rádió vételtől függetlenül, és azt hivatott bemutatni, hogy nem zavarják egymást 
    ido_tmp=millis();                     //a rádió vétel és az egyéb programrészek mindaddig, amíg a végrehajtási idő alatt nem érkezik újabb adat, miközben
    delay(100);                           //még az előzőt sem olvastuk ki
    Serial.println("Dolgozik...");  
  }
}


void idopont(unsigned int elmult_nap,unsigned int sec5) {
/*********************************************************************************************************
 * Ez a függvény a 2000 óta eltelt napok számából visszaadja a pontos dátumot az ev, ho, nap változókba. *
 * A bázisnak a 2000 évet tekinti, de a 2000 évebn nem működik jól, mert a datum_elteletnap() függvény   *
 * írásakor elrontottam, és nem vettem figyelembe, hogy a 2000 év szökőév. Mivel a függvényt az aktuális * 
 * dátum átvitelére terveztem, nem volt cél, hogy 2000-ban is jól működjön. Elegendő, hogy 2001-től      *
 * 2178-ig képes a dátum átvitelére a függvénypáros.                                                     *
 * Bemenő paraméter egy unsigned int érték. Kimenő paraméterek (globális változóként deklarálni kell     *
 * a programban:                                                                                         *
 *  ev - int                                                                                             *
 *  ho - byte                                                                                            *
 *  nap -byte                                                                                            *
 *********************************************************************************************************/
  ev=2000;                                    //200-től indulunk a napokat évente csökkentő ciklussal
  int evnapja=365;                            //ez a változó az év aktuális napjainak számát adja meg
  while (elmult_nap>evnapja) {                //addig megy a ciklus, amíg kisebb a hátralévő napok száma, mint az aktuális év napjainak száma
    elmult_nap=elmult_nap-evnapja;            //kivonjuk az adott év napjainak számát
    ev++;                                     //növeljük az évet! Így a ciklus befejezésekor az éppen aktuális évnél fogunk járni
    if (elmult_nap==0) {elmult_nap=1;}        //ha pont megegyezik, akkor sem lehet 0 az eredmény, mert csak 1.-e lehet
    if (szokoev(ev)) {evnapja=366;}           //a következő évben szökőév esetén 366-ot vonunk le 
    else {evnapja=365;}                       //ha a következő év nem szökőév, akkor 365-öt vonunk le
  }
  //megállapítottuk, hogy hány évet adtunk meg a bemenő számadatban, most jön a maradék alapján, hogy az éven belül hányadik hónapban vagyunk
  int ho_napjai[] = {31,28,31,30,31,30,31,31,30,31,30,31};     //tudnunk kell, hogy egy adott hónap hány napos
  if (szokoev(ev)) {ho_napjai[1]=29;}                          // szökőévben a február 29 napos 
  ho=1;
  while (elmult_nap>ho_napjai[ho-1]) {                         //addig megy a ciklus, amíg a napok csökkentésével egy hónap alá nem kerül a maradék
    elmult_nap=elmult_nap-ho_napjai[ho-1];                     //csökkentjük a maradék napokat az adott hónap napjaival
    ho++;                                                      //növeljük a hónapot
  } 
  nap=elmult_nap;                                              //megkaptuk az adott hónap napját is a maradékban
/********************************************************************************************
 * Ez a programrész az éjfél óta öt másodperces felbontásban megadott számból visszaállítja *
 * a pontos időpontot.                                                                      *
 * Globális változókat használ, tehát a program setup része előtt deklarálni kell az        *
 * ora, perc, masodperc byte típusú változókat.                                             *
 *******************************************************************************************/
  ora=sec5/720;                        //ennyi óra van a megadott számban
  perc=(sec5-ora*720)/12;              //ennyi perc van az órák által megadott öt másodpercek kivonása után
  masodperc=(sec5-ora*720-perc*12)*5;  //ennyimásodperc marad az órák és percek öt másodperces számának kivonása után.

}


bool szokoev(int ev)  {
/**************************************************************************************
 * Ez a függvény megállapítja egy évről, hogy szökőév-e. Bemenő paraméter az év       *
 * évszázadokkal megadva pl. 2021. Az eredmény fals, ha nem szökőév, és true ha igen. *
 * Egy adott év akkor szökőév, ha 4-el osztható, de minden 100. év nem szökőév        *
 * minden 400. pedig mégis szökőév. Pl. szökőévek: 4, 8, ...2020, 2024 stb.           *
 * Nem szökőév 100, 200 ... 1900, 2000. Azonban mégis szökőév: 1200, 1600, 2000 stb.) *
 **************************************************************************************/
  if (ev==0) {return false;}             //a 0.évet nem vesszük szökőévnek 
  bool szoko_ev=false;                
  if (ev%4==0) {                         //ha néggyel osztva nem ad maradékot, akkor szökőév
    szoko_ev=true;
    if (ev%100==0) {                     //ha 100-al osztva nem ad maradékot, akkor nem szökőév
      szoko_ev=false;
      if (ev%400==0) {szoko_ev=true;}    //ha 400-al osztva nem ad maradékot, akkor mégis szökőév 
    }
  }
  return szoko_ev;
}

A felhasznált Arduino kivezetések átállítása

Erős kötöttség, hogy a RadioHead könyvtár adáshoz az a 12-es, vételhez pedig a 11-es kivezetést használja. Ezt sajnos az inicializálás során nem lehet megváltoztatni. Azonban mégis van erre lehetőség, csak meg kell néznünk a könyvtár által adott forráskódokat, és ott átírhatjuk. Azt kell tudnunk, hogy a letöltött RadioHead könyvtár Windows esetén a C:\Users\Zoli\Documents\Arduino\libraries\RadioHead könyvtárba kerül. Itt találjuk az RH_ASK.h állományt. Ha ezt megnyitjuk szövegszerkesztővel (pl. notepad), akkor a 236. sorban ezt találjuk:
RH_ASK(uint16_t speed = 2000, uint8_t rxPin = 11, uint8_t txPin = 12, uint8_t pttPin = 10, bool pttInverted = false);
Nem kell mást tennünk, mint a txPin = 12-t kicserélni pl. txPin = 2-re, és újra fordítani a programot. Ekkor az Arduino-ra töltött program már a 2-es kivezetésen keresi az adót. Figyelem! Az átírt kivezetés általános érvényű, tehát ettől kezdve minden programban a 2-es lábra költözik az adás. Régebbi források újra fordításakor meglepetés érhet, ha nem írjuk vissza a régi értékre. Azóta, hogy az első RadioHead könyvtárat letöltöttem, találtam egy frissebb verziót. Ebben már nem a 236. sorban lehet átírni a kivezetést. Ezért a forrásban az érintett sor sorszámát nem is érdemes megjegyezni, az verzió függő. Rá kell keresni az rxPin szóra, és gyorsan meg lesz!

Adó tápfeszültségének hatásai

Nagyon boldogan tettem hírré, hogy 5V-ról 10m (2-3 téglafal) távolságból tökéletes a vétel. Kiderült, hogy nem ennyire ideális a helyzet. Azt történt, hogy a szerszámos sufniban az első kísérletek a háztól legtávolabbi ponton végeztem. Aztán bekötöttem a szélsebességmérőmre az adót. Valami zűr volt a programmal (forrás hamarosan érkezik), és kénytelen voltam a szerszámos ablakába helyezni a szélkerék mérő berendezésem elektronikáját, mert hiba volt a programban, amit meg kellett javítani, és ehhez látnom kellett mi történik. Utólag kiderült, hogy nem a programban van a hiba, hanem a szélkerék hall érzékelője ad időnként fals jeleket, ami a beltéri vevőben időnként 160km-h sebességértékeket eredményez. A hibakeresésre azt találtam ki, hogy az ablakba raktam az egész cuccot, és LED-eket villogtattam. Nem akartam ugyanis a tetőről leszerelni a szélkereket, kint a hidegben (épp tél van) pedig nem kellemes egy laptopon nézni a képernyőt. Jobb ezt a meleg szobában csinálni. Ezt a módszert rögtön el is neveztem “távdebug”-nak. A dolog jól működött, a LED-ek villogtak csak éppen alig jött jel a vevőbe. A vevőt szintén a lakás ablakába kellett költöztetni, hogy legyen vétel. A távolság immár csak 6m mégis csak néha jön jel?! Néhány nap kellett, hogy leessen mi a baj. A szerszámos ablaka dróthálós üveg. A panel lakók erkély üvegként ismerhetik. Az üvegbe egy dróthálót öntenek, hogy erősítsék. Ha ráversz kalapáccsal, az üveg összevissza reped, de a tábla egyben marad. Jó is ez nagyon, de a drótháló kiváló árnyékoló eszköz a rádió adások számára. A dolgon úgy segítettem, hogy beszereltem egy step-up tápegységet az Arduino nano 5V-jára, és azzal csináltam 12V-ot az adónak. A vétel immár tökéletes! A vevőt a lakásban akárhová vihetem, már nem kell az ablakban lennie. Sőt, bevihetem a hálóba is, ami még 5m, plusz egy téglafal. Megfigyeltem, hogy régebben el-el veszett egy-egy adás. Azaz szemmel észrevettem a soros monitoron, ahol a folyamatot néztem, hogy néhány percenként kimarad egy-egy sor, amit vételkor írtam ki. Bekapcsoltam az időbélyegzőt, így lehetett észre venni a hiányt! Ez azonban nem zavart, mert elég sűrűn történik adás, majd a következő pótolja a hiányt. Teljesen megszűnt ez a jelenség is! Percekig néztem a soros monitort, és semmi nem hiányzott! Köszönjük 12V!

Belekeveredtem egy olyan fejlesztésbe, amiben adó és vevő modul is került ugyanarra az Arduino nano-ra. A feladat az volt, hogy az időjárás állomásom mérési eredményeit egy adó beküldje a lakásban található vevőre. Már majdnem készen vagyok, hamarosan ennek a megoldásnak az ismertetője is felkerül a projektek közé. Azonban az építés közben arra következtetésre jutottam, hogy szerencsés lenne, ha az időjárás állomás óráját szinkronizálnám egy központi órával. Mivel a rádió adásokban felhasznált adatszerkezetem lehetővé tette (4 byte-ot viszek át és ebbe a pontos idő is belefér), a pontos időt óránként sugározni fogom egy központi helyről. Amikor a vevő veszi az adatot szinkronizálja az órát. Ehhez azonban kell az adó modul mellé egy vevő is. Aggódtam, hogy nem fogják-e egymást megzavarni valamilyen módon. Örömmel tehetem közzé, hogy nem. Amikor az adó épp ad, akkor nincs vétel. Mindössze ennyi történik. Biztosan le van ez valahol írva a RadioHead leírásaiban, de mivel nem találtam meg, úgy gondoltam ez egy hasznos infó a hozzám hasonló lelkes amatőröknek!


Eltelt pár hónap, és további lényeges tapasztalatokat szereztem, amik a fentebb leírtakat részben cáfolják!

A beltéri egységgel kiegészített időjárás állomásom kezd elkészülni. Van már adó felszerelve, méri a szélsebességet, csapadékot, hőmérsékletet stb. és ezeket az adatokat sugározza is a rászerelt 433Mhz-s adó. Elkészült a beltéri vevőegység első verziója, bár még erőteljesen fejlesztés alatt áll. Az előzetes kísérletek alapján azt hittem minden rendben van, és végleges megoldásokat kezdtem megépíteni és telepíteni. Az első tapasztalat alapján (amikről fentebb írtam) nem foglalkoztam igazán az antennával és annak elhelyezésével, mert úgy tűnt nem érzékeny rá az adóvevő páros 10m körüli távolságban, még akkor sem, ha nincs közvetlen rálátás!

Nagy meglepetések értek! Arra már rájöttem, hogy az „erkélyüveg” erősen árnyékol. Bár az adó tápfeszültségének megemelése látszólag megoldotta a problémát. Ráadásul az adó is kikerült az árnyékoló erkélyüveg mögül, így nem gondoltam, hogy probléma lehet. A konyhaasztalon elhelyezett vevővel minden rendben volt, jöttek az adatok.

Amikor a vevőt a véglegesnek szánt helyre telepítettem, nem volt vétel. Ez ugyan csak 2m-el volt távolabb mint a vevőegység fejlesztési helyszínéül szolgáló konyhaasztal, mégsem jöttek az adatok. Nem vettem a fáradságot, hogy pontosan kiderítsem mi is a baj, így hiábavaló munkák tömegét végeztem el, bár igen sok tapasztalatot szereztem ezzel. Az első hiba részemről az volt, hogy nem vettem észre, ha 60 cm-rel távolabb viszem a vevőt az antennával, máris megint van vétel. Ehelyett kivezettem az adót a kisházon kívülre, hiszen így legalább 2m-el közelebb került az adó és a vevőhöz, valamint eltűnt az egyik téglafal. Biztos voltam az eredményben! Éptettem egy műanyag dobozt, és abba éptettem be az adót és a vevőt egymás mellé. Így nézett ki a remekmű:

Ez a doboz nem a szépségipar terméke. Az antenna simán kilóg lefelé a dobozból. Belül, mint a képen látható, semmi nem tartja a 433Mhz-s adót és vevőt, csak lifegnek a vezetékek végén (némi szikszalaggal rögzítve), és az antennát is csak simán kihúztam a műanyaglemezre fúrt lukakon. Az egész szerkezet az eresz alatt kapott helyet, és kb. 3 méter riasztó kábellel csatlakozik az Arduino-hoz. A vevőt épp nem használtam, mert ennek csak később lesz szerepe (központi idő szinkron), de voltak pillanatok, amikor úgy véltem, hogy a két antenna közel van egymáshoz és zavarják egymást. Ekkor a vevőantennát felhajtottam és felakasztottam, hogy ne lógjon az adóantenna előtt. Ez az állapot látszik a képen. Ettől egyébként helyre is ált a kapcsolat abban pillanatban. Aztán egy óra múlva megint nem volt vétel! Kb. 2 hónapom ment rá arra, hogy megtaláljam a megoldást erre a teljesen bizonytalan működésre.

Kezdetben a vétel mindenhol tökéletes volt, kivéve ott, ahová szerelni szerettem volna a vevőt. Nevezetesen ez egy könyvespolc egyik függőleges éle lett volna. A könyvespolc alsó részén tervezem elhelyezni a központi egységet, innen egy kábel megy fel a polc tetejére, ahol egy Arduino nano vezérli a 433Mhz-s vevőt.

Minek a külön Arduino nano? Azért mert küzdök azzal, hogy a vevő csupa 0 adatot ad vissza időnként. Ez a feltöltött programtól függ, és úgy tapasztaltam, hogy a központi egységemben már nem tudok olyan programot fordítani, bármit is változtatok rajta, hogy ez a probléma ne jöjjön elő. Az Arduino nano csak a vétellel foglalkozik, és I2C buszon keresztül adja az adatokat a központi egységnek. Ez a program vélhetőleg olyan kicsi még, hogy nincs gond a megérkezett adatokkal. Kicsivel feljebb is írtam már erről a problémakörről! A könyvespolc tetejére terveztem a vevőt, és a polc oldalsó élén lógott volna le az antenna (zölddel karikázott hely). Itt azonban 0 vételt tapasztaltam. Első körben átakasztottam az egész szerkezetet egy virágtartó kampóra, kb. 50-60cm-el közelebb az adóhoz. Kékkel karikáztam be! Itt már időnként volt vétel, és kezdődhetett a kísérletezgetés az antenna hosszával, és az antenna rakosgatásával. A képen látható a nevezetes polc és a virágtartó kampó:

A zölddel karikázott rész lett volna az ideális. Innen akasztottam át a cuccot a kékkel karikázott kampóra, ahol most egy virágcserép lóg egy kötélen. Ez feleségem birodalma, így itt nem maradhatott tartósan, de itt minden működött. Az a kampó, amire akasztottam az antennát, egy áthidaló gerendából lóg ki. Bár az egész lakás tégla, ez az egyetlen pont, ahol vasak is vannak a falban. Méghozzá nem is kevés, mert itt egy 4 m-es vasbeton gerenda található. Ha jól gondolom, a gerenda alatt nm zavarják a vasak a vételt. Az adás jobbról érkezik, és a zölddel karikázott hely már kissé a gerenda mögött található, és ezért nincs ott vétel. Megpróbáltam a polc élén lejjebb elhelyezni az antannál kb. 50cm-el, illetve teljesen a polc alján. Az eredmény nem volt meggyőző, egyik pozícióban sem javult számottevően a vétel. Ebből arra következtetek, hogy ezen a ponton az adó és a vevő távolsága a hullámhossz egésszámú többszöröse környékén lehet, és így sehol nincs jó vétel. De nem értek hozzá, csak következtetek. A pirossal karikázott részen éppen a jelenleg is működő vevő látható a 17cm-es antennával. Egy ruhaszárító csipesz feszíti ki az antenna vezetéket, mert egy nagyon vékony drótot forrasztottam rá, és így 17cm- már nem feszül ki a saját súlyától. Végül ez lett az a hely, amit elfogadtam magamnak kompromisszumként, ide fog kerülni a végleges szerkezet antennája, ha egyszer készen lesz. Jelenleg a több hetes próbaüzem zajlik.

Lássuk a kísérleti eredményeket! Az első antenna csak úgy saccra kb. 20-30cm-es darab volt adó és vevőoldalon is. Már ezzel is volt időnként vétel, de nem sok. A vevő ekkor a virágtartó kampón lógott, mint említettem a tervezett helyén semmi vétel nem volt. Tudományosan közelítettem a problémához és első lépésben 69cm hosszósággal próbálkoztam, aztán ezt megfeleztem, és végül 17cm hosszúság maradt a kábelvagdosás eredménye. Közben rakosgattam a vevőt, mert ugye az adót már végleges helyre építettem, ott csak az antenna hosszával lehetett kísérletezgetni.

Minden hosszúságnál legalább egy napig számoltam a vett adatok számát. Írtam egy programrészt a központi egységbe, ami a vett adatok típusa szerint jelenítette meg a vételek számát. Azt kell még tudni, hogy többféle adatot is továbbított az időjárás állomásom, és pl. a szélsebességmérő csak akkor ad 10 másodperces időközönként, ha fúj a szél. Szélcsendben csak 10 percenként ismétel egyet.

Tehát az adások száma nem mondható meg pontosan, de 24 óra alatt kb. 50.000 adás történt. Tudom, hogy ezt lehetett volna tudományosabban is, pl. megszámolom az adások számát, de ennyi időt már nem akartam tölteni vele. Végleges kialakításban természetesen jóval kevesebb lesz az adások száma, de most ideiglenesen jó sűrűre állítottam, hogy lássam a változtatásaim eredményét. Ehhez nem kellet lényegesen átírni a programot. A fenti képen a 10190 a szélsebességmérő adata.  A 134 pedig a szennyvíz átemelő szivattyú monitorozó programjától érkezik. Időközben már ez is közvetíti egy 433Mhz-s adóval óránként a szivattyú indítások számát, és tudna riasztást küldeni, ha a szennyvíz szint túl magas, mert esetleg elromlott a szivattyú, és nem ürít. Ez az adó egész máshol van, és soha sehol nem volt probléma a vételével. Sokat segített, mert láttam, hogy ettől a berendezéstől jönnek az adatok, tehát nem a központi egységem vevője működésképtelen, csak az időjárás állomás adójától nem jutnak el hozzá a jelek. Meg kell jegyeznem, hogy ez az adó pont az emlegetett vasbeton gerenda azon oldalán van, ahol a vevőt szerettem volna elhelyezni! Vette is az adatait a vevő, még akkor is, amikor a tervezett helyére akasztottam! Ez látható a későbbi statisztikában is!

A központi állomás vevőjének különböző elhelyezéséből, és egyéb változtatásokból készült egy statisztika. Igyekeztem leírni pontosan az adás és a vétel elhelyezés körülményeit, mert esetleg másnak is hasznos lehet, hogy minek van igazán hatása. Már csak az elkészült statisztikai adatokat adom közre! A két vétel között eltelt átlagos időt, valamint az óránként vett adatok számát, tartottam a leghasznosabbnak. Azt ezért tudni kell, hogy ez így nem igaz, mert voltak olyan esetek, amikor órákig nem jött egyetlen adat sem, aztán újraindult anélkül, hogy bármit tettem volna. Kedvezőtlen feltételek mellett úgy tűnt semmi vétel nincs, de egy nap alatt ilyenkor is hébe-hóba beütött egy-egy vétel.

Íme a beszédes statisztika:

Zölddel jelöltem a legjobb eredményt, és kékkel azt, ami a végleges elhelyezés lesz!

A fentiekből talán látható, hogy az adó tápfesznek alig volt hatása. Az adó antenna mellett pár cm-re elhelyezett vevőantenna szintén nem befolyásolta a fogadott adatok számát, sőt, látszólag még javított is, de ez valószínűleg véletlen, esetleg fújt a szél, és több adás történt. Az eredetileg vevőantennának szánt hely el van átkozva, rajta már nem segít az sem ha feláldozom a falu legszebb kecskéjét.

Tanulságok:

Az antenna hossza nagyon lényeges. Bár a leírásokban arra utaltak, hogy az antenna hossza jó ha megegyezik a hullámhosszal, vagy annak felével, negyedével stb., valójában a 17cm hossz az ideális. Próbáltam a neten ennek alaposabban utána nézni, de a szakanyagok megértéséhez nincs elegendő képzettségem. Az egyik fórumon olvastam egy arra utaló mondatot valakitől, hogy a negyed hullámhossz a legjobb, és minimális magyarázat is volt, de azt nem értettem. Viszont bizonyítottam!

Az adó tápfeszültsége nem javította az átvitel minőségét. Sőt még rontott is azon. Pedig kiélezett helyzetben próbálkoztam, ahol tényleg csak nagyon kevés adat érkezett meg, mégsem növekedett az átvitt adatok száma a tápfesz növelésével, hanem tovább csökkent. Elképzelhető, hogy itt más tényezők is befolyásolták az eredményt. Mint említettem, időnként hosszabb ideig egyetlen adat sem érkezett, aztán magától megjavult. Ki tudja, mi okozta a leállást? A szomszéd bekapcsolt valamit a lakásában?

A 433Mhz-es olcsó rádió adó-vevő páros nem megbízható. Kisérletezgetéssel található olyan hely az antennáknak ahol általában van vétel, és ezért mégis használható valamire. Arra feltétlenül alkalmas, hogy olyan adatokat vigyünk át, ami nem kritikus, nem kell feldolgozni, nem kell vele beavatkozni, mert csak tájékoztatásra használjuk. Pl. az Apolló űrhajón nem használnám a fékező rakéta begyújtására. Nagy égés lenne!

Mennyire volt hasznos amit olvastál?

Kattints egy csillagra az értékeléshez!

Szövegesen is leírhatod véleményedet!