Tartalom:
- Hogyan lehet kb. 250 eseményt rögzíteni 1 Kbyte-ban EEPROM-ban dátummal, időponttal, és két adattal?
- Dátum és időpont tárolás 14 bitben.
- Számítások az EEPROM élettartamának meghatározására
- Alapvető adatbáziskezelő függvények, írás, kiválasztott rekord olvasása, törlés, méret lkérdezés
- Teljes forráskód példa programmal
—————————————————————————————————-
Szükségem lenne egy olyan programra, amivel hetente két-három esemény adatait naplózom. Azonban az is előfordulhat, hogy valamilyen méréssorozatot akarok végezni, ami hosszú ideig tart. Ez a hosszú idő lehet pl. egy akkumulátor töltési vagy merítési karakterisztikája. Ehhez elegendő pl. óránként regisztrálni adatokat. Számtalan példát el tudok képzelni.
A konkrét feladat, ami miatt gondolkodni kezdtem, egy plug-in hybrid autó töltésének a naplózása lenne. Jelenleg még csak törtöm a fejem, hogyan lehetne a naplózási részt a legegyszerűbben és legolcsóbban megoldani. Ehhez a feladathoz felesleges SD kártyára rögzíteni az adatokat. Még a keletkezett adatok letöltése, vagy továbbítása is felesleges, ha valamire szükségem lenne, odamegyek, toll, papír és leírom. Ezért jutott eszembe, hogy az esemény naplózásra esetleg alkalmas lehet az ATmega vezérlők szerény méretű belső EEPROM-ja is. 1024 byte áll rendelkezésre, mire is elég annyi memória? Ha például hetente három alkalommal kell tölteni az autót, akkor 52×3=156 eseményt kellene regisztrálni valahogyan évente. Ez egyébként bőven elegendő, mert maximum néhány hónapot tartok reálisnak, hogy vissza kelljen nézni az adatokat. A régebbi eseményeket el lehet felejteni. Legyünk nagyvonalúak, és az elvárást állítsuk 256-ra. Ekkor már akár naponta tölthetjük az autót, és ekkor is 9 hónap adatait tárolhatjuk visszamenőleg. Viszont így már csak 4 byte áll rendelkezésre minden egyes adatrögzítéhez. Hívjuk az egy naplózás adatait adatrekordnak! Négy byte-nak kell elégnek lennie a töltés elkezdésének dátuma, időpontja, a töltési idő, és a felhasznált energia tárolására. Karakteresen nem elég a hely, tömöríteni kell, illetve trükkökhöz kell folyamodnunk. Pl. nem kell nekem másodperc pontossággal, hogy mikor kezdtem el a töltést. Pl. 5-10 perc pontosság bőven elegendő. Ugyanígy a töltési időtartam sem kell másodperc pontossággal, néhány perc felbontás itt is elegendő. Az egy töltésben felhasznált energia sem végtelen, jelen esetben maximum 11 KWh körüli lehet, ami bőven elfér egy byte-on egy tizedesjegy pontossággal. További takarékoskodást lehet elérni, ha pl. az évet és a hónapot nem tárolom minden egyes adatrekordban, hanem a rekordokat sorrendben olvasom el, és ha változik az év és hónap, akkor tárolom az új értéket, és a tudomásul veszem, hogy a további rekordok erre a hónapra vonatkoznak. Ekkor az adatrekordban már csak a hónap napját, és a kezdési időpontot kell tárolni. A kezdési időpontot sem fontos óra perc-ben megadni, megadhatom például úgy is, hogy az éjfél óta eltelt perceket tárolom. Egy nap 1440 perc. Ha elfogadom, hogy három perces felbontással tudom tárolni a kezdési időpontot, akkor ehhez elegendő 1440/3=480 maximális érték, ami viszont 9 biten elfér. A töltési idő 1 és 10 óra között lehet attól függően, hogy milyen nagy árammal töltünk. Legyünk ebben a kérdésben is nagyvonalúak, tételezzük fel, hogy maximum 24 órát is tölthetünk. Ha percekben adjuk meg a töltési időt, akkor itt is 1440 perc a maximum. Ha két perces pontosság elegendő, akkor az 10 biten fér el.
Így a következő adatszerkezetet találtam ki:
- 5 bit a hónap napjának (1-31-ig ez pont kijön, a 0-át nem kell tárolni, mert nincs 0. nap)
- 9 bit a kezdési időpontnak (éjfél óta eltelt 3 percek száma, max. 480)
- 10 bit a töltési időtartamnak (töltési idő 2 percekben, max. 720)
- 8 bit a felhasznált energiának (ennek maximuma 255, amit 10-el osztva 25,5 KWh-t kapok)
Ez mindösszesen 32 bit, azaz 4 byte! Győztünk!!
Egyetlen hátrány, hogy amikor a hónap változik, akkor kell egy másik típusú adatrekord, ami az onnan érvényes év és hónap értéket adja meg. Itt nem kell spórolni, 2 byte az év, 1 byte a hónap, és már csak jelölni kell valahogyan, hogy melyik rekord tárolja az évet és a hónapokat és melyik a konkrét adat rekordokat. Ebben nagy szerencsém van, mert 0. nap nincs a hónapban, ezért ha az első 5 bit 0, akkor az „év-hónap” rekord. Így természetesen nem fogunk tudni 256 értékes adatot tárolni. Ha minden nap töltjük az autót, akkor kb. 9 db év-hónap rekordra is szükség lesz, vagyis csak 247 adatrekordunk lehet. Még ez is több mint 8 hónap.
További hátrány, hogy egy adott adatrekord értelmezéséhez meg kell keresni a sorrendben előtte található legelső év-hónap rekordot. Ezt nem tartom nagy hátránynak egy 256 rekordos adatbázisban. Századmásodpercek alatt sikerülni fog!
Lehet még egy korlát, mert az EEPROM 100.000x írható. Olvasni korlátlanul lehet. Okoz-e ez valamilyen nehézséget? Igen okozhat problémát, foglalkozzunk ezzel is! Ha a programot nagyon kényelmesre akarom megírni, akkor úgy fog működni, hogy amikor az EEPROM megtelik, akkor automatikusan felülírok egy régi adatot. Ez könnyen megvalósítható, ha minden adatrekordnak külön sorszámot adok, de erre most nincs elegendő hely. A sorrendiség miatt egyetlen megoldást találtam, hogy fizikailag újraírom az eeprom-ot, kihagyok egy rekordot, és feljebb írom a következőket. Ekkor viszont minden egyes új rekord beírása előtt végig kell írni minden egyes memória cellát. Ez azt jelenti, hogy az első 256 adatrekord beírása csupán egy-egy memóriacella írását jelenti, de ezt követően már egy adatrekord beírása a teljes eeprom minden egyes memória celláját végig írja egyszer. Azonban még így is 99.999-szer adhatunk hozzá új rekordot az adatbázisunkhoz, igaz, hogy ezzel a módszerrel a teljes eeprom összes tároló celláját egyszerre tesszük tönkre. Ha naponta rögzítünk egy adatot, akkor 99999/365=273 évig fog működni adatrögzítő masinánk. Ha pl. óránként akarnánk egy rekordot írni, akkor az még mindig 10 év működés, de a rögzítés maximum 256/24=10 napig tart meg egy adatot. A 10 év működési idő szintén elfogadható, ha cserélhető foglalatba tesszük az Arduino alaplapot, és 10 évente költünk kb.1000Ft-ot egy új alaplapra. Bár azt nem tudhatjuk lesz-e még 10 év múlva Arduino (legalább is azonos típus)?
A fenti megfontolással készítettem az alábbi programot. Semmi különöset nem tud. Négy függvényt tartalmaz:
adat_ir() Beír az eepromba egy új adatrekordot. A dátum alapján
automatikusa elkészíti az év-hónap rekordot
log_utolso_bejegyzes() Lekérdezhető ezzel a függvénnyel az utolsó bejegyzés sorszáma,
ami megegyezik a rekordszámmal. Paramétertől függően az értékes
adatrekordok számát, vagy a teljes, év-hónap rekordokkal együtt
adja meg az adatok mennyiségét
toltes_adat() Ez a függvény kiolvassa a paraméterként megkapott sorszámú
adatrekordot. A sorszám 0 és a maximális értékes adatszám között
lehet.
honap_torles() Egyenlőre manuális adat törlésben gondolkodom, és úgy
döntöttem, hogy egy teljes hónapot fogok törölni az adatbázisból.
Ez a függvény egy hónapot töröl, tehát kezeli az év-hónap
rekordokat is a törlés során.
A függvényeket jó sok kommenttel láttam el, részletesen leírta a bemenő paraméterek jelentését, és a visszaadott paramétereket is. Készítettem a program setup() és loop() részében egy demó programot, amivel a függvények működését próbáltam ki. Nyilván ezeket a fejlesztés során hoztam létre, de rögtön jó lett demónak is. A bemutató program a setup() részben jó sok adat rekordot berögzít az eeprom-ba, a loop() ban pedig nyomógombok segítségével lehet az adatokat léptetni egy 2×16-os LCD kijelzőn, illetve egy harmadik nyomógombbal lehet törölni egy teljes hónap adatait. A fejlesztést az UnoArduSim szimulátorral kezdtem el, mert nem akartam egy Arduino UNO eeprom-ját tönkre tenni egy-egy jól megfogalmazott végtelen ciklussal. Jártam már így, mire észrevettem és felfogtam mi történik mehetett egy nano a kukába. A szimulátor azonban nem teljesen valós környezet, csak 512byte eeprom-ot szimulál. Ez pont kapóra jött ahhoz, hogy ne 1024 byte-ra írjam meg a kezelt eeprom méretét, így a program véletlenül olyan lett, hogy tetszőleges méretű eeprom területet be lehet állítani az adatrögzítéshez, hogy maradjon valamennyi más feladatra is. A felhasznált terület a konstansok között van a program elején EEPROM_MERET néven. A szimulátor a régebben készített multi_buttom() prellmenetsítő, nyomógomb kezelő függvényemet sem szerette, azt a valós Arduino UNO-ra töltéskor barkácsoltam bele, mert az elképzelt kütyüben biztosan lesznek nyomógombok. Az adatok kódolásában és dekódolásában jó sok bitművelet található, ezért most nem úsztam meg ezek használatának a megtanulását sem.
Íme egy kép a futási eredményekről, egy adat, és a törlést követő pillanatok:
Sok sikert a használathoz!
/******************************************************************************************************************************** * Ez a mintaprogram vezérlő belső eepromjában valósít meg esemény és adat logolást. Kifejezetten ritka (napi egy-két) esemény * * regisztrálására használható, mert az ATmega328 vezérlőben található 1K eepromban maximum 256 eseményt képes regisztrálni. * * Egy eseményről tárolja a dátumot, az óra percet, és két jellemző adatot. A konkrét adat rekordban csak a hónap napját tárolja,* * Ha az év illetve a hónap változik, annak tárolásához elhasznál egy év-hónap rekordot. Ezért az adatok visszaolvasásakor * * mindig sorban az összes rekordot sorrendben vissza kell olvasni, hogy kiderüljön egy konkrét adat időpontjáról az év és a * * hónap is. A két jellemző adat az esetemben egy energia mérő két adata. Az energiamérővel egy elektromos autó töltésére * * felhasznált energiát méri, valamint regisztrálja az akkumulátorok feltöltésének idejét. Egy adat rekord hossza 4 byte, * * így a tárolt adatok nem teljesen pontosak. Az esemény időpontjának (az akkumulátor töltés időpontjának) időpontja csak három * * perc pontosságú, a töltés időtartamának tárolása pedig két perc pontosságú és maximum 24 óra lehet. A töltésre felhasznált * * energia maximum 25,5Kwh, ami az én esetemben elegendő, hiszen maximum 11Kwh energia kell a teljes feltöltéshez. * * További információk az adatok szerkezetéről az adat_ir() függvényben. * * Írtam egy honap_torles() függvényt is, amivel egy teljes hónap adatait lehet kitörölni, ha kezd megtelni az eeprom. * *********************************************************************************************************************************/ #include <EEPROM.h> //eeprom kezelő programkönyvtár #include <Wire.h> //I2C kezelő programkönyvtár #include <LiquidCrystal_I2C.h> //I2C LCD kezelő könyvtár //LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); //LCD paraméterek megadása, ha 2x16-os LCD kijelződ van LiquidCrystal_I2C lcd(0x3F, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); //LCD paraméterek megadása, a 4 soros LCD-m címe 3F //#include <Adafruit_LiquidCrystal.h> //a fejlesztéshez az UnoArduSim szimulátort használtam, abban volt ez a programkönyvtár az LCD kijelző kezeléséhez //Adafruit_LiquidCrystal lcd(0); //UnoArduSim programban így kell létrehozni az LCD kijelzőt /*********************************************************************************************************************** * UnoArduSim programban nem tudtam felhasználni a multibuttom() függvényemet, ami a nyomógombokat kezeli, azonban * Arduino-ra töltött program esetén ez használható. Ha szimulátorban futtatod a programot, akkor a loop()-ban ki kell * venni a kommenteket néhány sor elől, és kikommentelni a multi_buttom() függvényt használó programsorokat. * A multi_buttom() függvény a programban maradhat akkor is ha szimulátort használsz, csak éppen akkor nem használjuk! ***********************************************************************************************************************/ byte bemenet_allapot[]={0,0,0}; //bemenet pillanatnyi állapota (prellmentes állapotában kerül beállításra), int kimenet_allapot[]={0,0,0}; //kimenet állapota, ez a függvény által visszaadott érték, ami 0,1,1-max:32767 értékeket vehet fel //valaszmód=0 és valaszmód=1 esetén:0-kikapcsolt állapotot ad vissza, 1-bekapcsolt állapotot ad vissza, //valaszmód=2 esetén 1-től beállított maximumig számlált állapotot ad vissza byte bemenet_elozo_allapot[]={0,0,0}; //bemenet előző állapota segéd változó, amivel felderíthetjük a //fel vagy lefutó álét a prellmentes nyomógomb állapotváltozásnak byte prell_tmp[]={0,0,0}; //bemenet prell figyeléshez segéd változó long prell_time[]={0,0,0}; //bemenet prellmentesítéshez az első kontaktus időpontja, segéd változó //eddig tartottak a multibuttom() függvény változói int recordszam; //a minta programban az adatok kiolvasásakor ebben tárolom az aktuális rekordszámot, ami a kijelzőn látható. Értéke //nulla és az utolsó rekord között léptethető fel és le két nyomógombbal int max_recordszam; //ebben a változóban található az eepromban található összes adatrekord száma. Nincs beleszámolva az év-hónap rekordok //száma, amiket csak a pontos esemény dátum visszaállítása miatt tárolunk int max_ossz_recordszam; //ugyanaz mint előbbi változó, de az év-hónap rekordok is bele lettek számolva, tehát a teljes adatbázis rekordszámot //adja meg. Mivel minden rekord (adat és év-hónap is) 4 byte-ot foglal, ennek a változónak a méret szorozva 4-el //megadja a teljes helyfoglalást az eeprom-ban. bool allapot_sw1; //Az sw1 nyomógomb pillanatnyi állapotát olvasom ebbe, amivel a kijelzőn visszafelé lehet léptetni az adatokat az első esemény felé bool allapot_sw2; //Az sw2 nyomógomb pillanatnyi állapotát olvasom ebbe, amivel a kijelzőn előre lehet léptetni az adatokat az utolsó esemény felé bool allapot_sw3; //Az sw3 nyomógomb pillanatnyi állapotát olvasom ebbe, amivel az adatok közül egy hónapot lehet kitörölni bool sw1_elozo_allapot=1; //ahhoz kell, hogy tudjam, mikor nyomtam meg az sw1 nyomógombot bool sw2_elozo_allapot=1; //ahhoz kell, hogy tudjam, mikor nyomtam meg az sw2 nyomógombot bool sw3_elozo_allapot=1; //ahhoz kell, hogy tudjam, mikor nyomtam meg az sw3 nyomógombot bool kiiras=1; //értékét 1-re állítom, ha valamelyik nyomógombot megnyomtam, és ki kell írni a kijelzőre egy új adatot. A kiírás után visszaállítom 0-ra, hogy a //következő ciklusban ne legyen kiírás (induláskor azonnal lesz egy kiírás) String str_tmp; //ebbe a változóba gyűjtöm össze formázottan a dátum és időpont értékeket byte tmp_ora; //a töltési idő órájának kiszámításához használom (mivel a töltési idő percben van megadva) byte tmp_perc; //a töltési idő percének kiszámításához használom (mivel a töltési idő percben van megadva) int ossz_rec_szam; //Egy adat kiírás után ebbe a változóba kaphatom vissza az összes elhasznált rekord számát (teljes rekordszam) int torolve; //Egy hónap törlés után ebbe kaphatom vissza a kitörölt rekordok számát const byte SW1=8; //az SW1 nyomógombhoz tartozó bemenet száma const byte SW2=9; //az SW2 nyomógombhoz tartozó bemenet száma const byte SW3=10; //az SW3 nyomógombhoz tartozó bemenet száma const int EEPROM_MERET=1024; //Ez a tárolásra felhasználható eeprom méretet tárolja. 0 kezdőcímtől használjuk az eeprom-ot és a megadott számnak 4-el oszthatónak kell lennie //Az UnoArduSim szimulátor csak 512 byte eeprom-ot kezel. struct toltes { //ebben a struktúrában adja vissza az adatokat a toltes_adat() függvény int start_ev; //a töltés elindításának éve byte start_ho; //a töltés elindításának hónapja byte start_nap; //a töltés elindításának napja byte start_ora; //a töltés elindításának órája byte start_perc; //a töltés elindításának perce, három perc pontosságú (pl. ha beírok 15-öt, lehet, hogy 13-at kapok vissza) int tolt_perc; //a töltés időtartama percben, két perc pontossággal (pl. ha a töltés időtartama 31 perc, 30-at kapok vissza) float kwh; //a töltésre használt energia Kwh-ban. maximális értéke 25,5 Kwh }; toltes adat; //rögtön létre is hozunk egy toltes struktúrájú változót, ebbe fogjuk visszakapni a kiolvasott értékeket az adatok léptetésekor void setup() { lcd.begin(20,4); //LCD inicializálása (nekem egy 4x20-as kijelzőm van, de a programot 2x16-ra írtam meg) //lcd.begin(16,2); //ha 2x16-os kijelződ van lcd.backlight(); //háttérvilágítás bekapcsolása //nyomógombok bemeneteinek beállítása, felhúzó ellenállások bekapcsolása pinMode(SW1,INPUT);digitalWrite(SW1,HIGH); pinMode(SW2,INPUT);digitalWrite(SW2,HIGH); pinMode(SW3,INPUT);digitalWrite(SW3,HIGH); for (int i=0;i<EEPROM_MERET;i++) { EEPROM.write(i,0);} //töröljük az eeprom-ot, ez nyilván csak a fejlesztéshez kellett //ezek a sorok csak a demó-hoz kellettek. Elegendő egyszer beírni az eeprom-ba az adatokat, aztán lehet nézegetni a kiolvasást és a törlést //UnoArduSim szimulátorban futtatva azonban kellenek ezek a sorok, mert minden futtatáskor újra meg kell csinálni az eeprom tartalmat //mert a szimulátor nem jegyzi meg az epromba írt adatokat, minden futtatáskor üres benne az eeprom adat_ir(2021,4,2,0,0,2,0.1); //beírunk egy adatrekordot, az év-hónap rekordot a függvény automatikusan kezeli, nem kell vele foglalkozni adat_ir(2021,4,11,2,3,0,5.6); adat_ir(2021,4,23,3,7,123,11.4); adat_ir(2021,4,24,4,8,542,5.5); adat_ir(2021,4,24,9,9,31,6.3); adat_ir(2021,4,29,12,10,222,2.7); adat_ir(2021,5,3,16,11,1440,5.9); adat_ir(2021,5,5,17,15,500,11.4); adat_ir(2021,5,17,18,21,470,9.7); adat_ir(2021,5,22,19,27,350,8.3); adat_ir(2021,5,31,20,30,310,7.3); adat_ir(2021,6,10,21,38,680,5.6); adat_ir(2021,6,20,22,40,430,11.4); adat_ir(2021,6,29,23,45,200,9.9); ossz_rec_szam=adat_ir(2021,6,30,23,59,15,8.1); //példa arra, hogy az elfoglalt rekordok számát vissza is kaphatjuk //itt vége az eeprom adatokkal történő feltöltésének, innen már csak nézegetjük a működést max_recordszam=log_utolso_bejegyzes(0); //A log_utolso_bejegyzes(0) függvény vissza tudja adni a rögzített értékes rekordok számát max_ossz_recordszam=log_utolso_bejegyzes(1); //A log_utolso_bejegyzes(1) függvény vissza tudja adni az elhasznált összes rekord számát (év-hónap rekordokkal együtt) recordszam=max_recordszam; //a kijelzőn kezdetben a legutolsó rekord lesz látható adat=toltes_adat(recordszam); //hogy mi legyen a kijelzőn látható, ahhoz az adat nevű struktúrába ki kell olvasni egy rekord tartalmát } void loop() { //az alábbi három sor elé kommentet kell tenni, ha UnoArduSim szimulátorban akarjuk futtatni a programot, mert az valamiért nem tudja helyesen futtatni a multi_buttom() függvényt allapot_sw1=multi_button(0,0,SW1,0,0,0); //prellmentesít, ha le van nyomva a gomb, akkor allapot_sw1=0, ha nincs akkor allapot_sw1=1 allapot_sw2=multi_button(1,0,SW2,0,0,0); //prellmentesít, ha le van nyomva a gomb, akkor allapot_sw2=0, ha nincs akkor allapot_sw2=1 allapot_sw3=multi_button(2,0,SW3,0,0,0); //prellmentesít, ha le van nyomva a gomb, akkor allapot_sw3=0, ha nincs akkor allapot_sw3=1 //allapot_sw1=digitalRead(SW1); //Az sw1 nyomógomb lekérdezése, UnoArduSim szimulátorban futtatáskor kell csak if (allapot_sw1==0 & sw1_elozo_allapot==1) { //léptetés visszafelé if (recordszam>1) { recordszam--;} kiiras=1; //delay(100); //ez a sor csak UnoArduSim szimulátorban futtatáskor kell, mert úgy tűnik, mintha prellezést is szimulálna } sw1_elozo_allapot=allapot_sw1; //allapot_sw2=digitalRead(SW2); //Az sw2 nyomógomb lekérdezése, UnoArduSim szimulátorban futtatáskor kell csak if (allapot_sw2==0 & sw2_elozo_allapot==1) { //léptetés előre if (recordszam<max_recordszam) { recordszam++;} kiiras=1; //delay(100); //ez a sor csak UnoArduSim szimulátorban futtatáskor kell, mert úgy tűnik, mintha prellezést is szimulálna } sw2_elozo_allapot=allapot_sw2; //allapot_sw3=digitalRead(SW3); //Az sw3 nyomógomb lekérdezése, UnoArduSim szimulátorban futtatáskor kell csak if (allapot_sw3==0 & sw3_elozo_allapot==1) { //adat törlés torolve=honap_torles(); //visszakapjuk a torolt rekordok számát max_recordszam=log_utolso_bejegyzes(0); //visszakapjuk a jelenleg eepromban található értékes adat rekordok számát recordszam=max_recordszam; //az aktuális rekord most is az utolsó lesz max_ossz_recordszam=log_utolso_bejegyzes(1); //visszakapjuk az elhasznált összes rekord számát (év-hónap rekordokkal együtt lcd.clear(); //kiírjuk a kijelzőre a törlés eredményét, zárójelben az összes eepromban lévő rekord száma (év-hónap-al együtt) lcd.setCursor(0,0);lcd.print("Torolve:");lcd.print(torolve); lcd.print(" (");lcd.print(max_ossz_recordszam);lcd.print(")"); lcd.setCursor(0,1);lcd.print("Szabad:");lcd.print(EEPROM_MERET/4-max_ossz_recordszam); //ennyi helyünk van az eeprom-ban még delay(3000); kiiras=1; } sw3_elozo_allapot=allapot_sw3; if (kiiras==1) { //ki kell írni a kijelzőre egy rekord adatait adat=toltes_adat(recordszam); //beolvasunk egy adat rekordot az adat nevű struktúrába str_tmp=String(adat.start_ev)+"."; //innentől kezdve összeszerkesztjük a formázott stringet a kiíráshoz if (adat.start_ho<10) { str_tmp=str_tmp+"0"+String(adat.start_ho);} else { str_tmp=str_tmp+String(adat.start_ho);} if (adat.start_nap<10) { str_tmp=str_tmp+".0"+String(adat.start_nap);} else { str_tmp=str_tmp+"."+String(adat.start_nap);} if (adat.start_ora<10) { str_tmp=str_tmp+" 0"+String(adat.start_ora);} else { str_tmp=str_tmp+" "+String(adat.start_ora);} if (adat.start_perc<10) { str_tmp=str_tmp+":0"+String(adat.start_perc);} else { str_tmp=str_tmp+":"+String(adat.start_perc);} lcd.setCursor(0,0);lcd.print(str_tmp); tmp_ora=adat.tolt_perc/60; tmp_perc=adat.tolt_perc-(tmp_ora*60); if (tmp_ora<10) { str_tmp="0"+String(tmp_ora);} else { str_tmp=String(tmp_ora);} if (tmp_perc<10) { str_tmp=str_tmp+":0"+String(tmp_perc);} else { str_tmp=str_tmp+":"+String(tmp_perc);} lcd.setCursor(0,1); lcd.print(str_tmp);lcd.print(" ");lcd.print(adat.kwh,1); lcd.print("KWh "); kiiras=0; //azért, hogy a következő ciklusban ne írjuk ki újra } } int adat_ir(int start_ev, byte start_ho, byte start_nap, byte start_ora, byte start_perc, int tolt_perc, float kwh) { /************************************************************************************************* * Ez a függvény eeprom.ba írja 4 byte-ra a hónap napját, a start időpontot, a töltési időt * * és a töltésre felhasznáét energiát. Ebben az esetben, h egy előző rekordhoz képest változott * * a hónap, készít egy év-hónap rekordot is, amiben elhelyezi a konkrét mérési adat évét és * * hónapját. Feltételezzük, hogy egy hónapon belül több adat lesz, ezért ez spórolást jelent * * a memóriával. Ha minden hónapban csak egy töltés lenne, akkor ez több helyet fogaszt, de * * jelen felhasználásban számítunk rá hogy, naponta, illetve egy héten többször is töltjük az * * autót. * * * * Bemenő paraméterek: * * start_ev - A töltés indítás éve. Ez nem kerül tárolásra, csak akkor, ha az előző * * rekordhoz képest változott az év. Külön rekordban tároljuk, ha változott * * A teljes évszámot várja (pl. 2021), ezért int a típus. * * start_ho - A töltés indítás hónapja. Nem kerül tárolásra, csak akkor, ha az előző * * rekordhoz képest változott a hónap. Külön rekordban tároljuk, ha változott * * start_nap - a töltési időpont hónapjának napja. Egy megelőző rekordban tárolásra kerül * * az aktuális rekordra vonatkozó év és hónap, ezért elegendő csak a dátum * * szerinti napot megadni. * * start_ora - A töltési időpont órája * * start_perc - A töltési időpont perce. A tárolás 3 perc pontossággal történik, tehát * * nem feltétlenül a konkrét óra percet tároljuk, hanem lehetséges, hogy * * egy-két perccel előbbi időpontot. Jelen alkalmazásban lényegtelen a pontos * * időpont, így megengedhető a hely spórolás miatti kicsi pontatlanság * * tolt_perc - A töltési idő percekben. Hely spórolás miatt, csak két perces pontossággal * * tároljuk a töltési időt, tehát lehetséges, hogy egy perccel rövidebb időt * * tárolunk, mint a tényleges idő, de ez nem lényes, inkább tájékoztató adat * * semmilyen számításban nem vesz részt, így megfelelő. A maximális idő így * * 1024*2=2048 perc. Mivel egy teljes nap 1440 percből áll, ezt sem fogjuk * * teljesen kihasználni. Egy teljes autó feltöltés maximum 10-11 óra a legkisebb * * töltési sebességgel, ami 600-700 perc lehet. 9 bit (512) is elég lehetne * * de volt még 1 bit, és hátha valamiért mégis lassabban tölt. * * kwh - A töltésre felhasznált energia mennyisége Kwh-ban. egy tizedes pontossággal * * tároljuk egy byte-on, ami azért elegendő, mert jelen esetben a töltési * * mennyiség maximuma 11Kwh-nál nem lehet több. Egy byte-on max 25,5 Kwh * * töltést lehetne tárolni, de ezt sem használjuk ki. * * * * Rekord felépítés töltési adat esetén: * * * * 4 byte hossza 32 bit, azt a következő képen osztjuk fel az egyes adatok számára: * * 5 bit hónap napja 1-31 (0 hónap jelző rekord kezdő értéke) * * 9 bit éjfél óta eltelt percek száma /3 (3 perces pontosság) * * 10 bit töltési idő 2 perc pontosság * * 8 bit energia max 25,5 kwh (10-el osztva az itt tárolt egész számot) * * * * Rekord felépítés év és hónap rekord esetén * * 1. byte az év-hónap rekord jelzésére. Az öt bit mindegyike 0, ez töltési rekordnál nem * * fordulhat elő, mert a hónap napja 1-31 lehet * * 2-3. byte az évszám, egy integer értéken a teljes év pl. 2021 * * 4. byte a hónap (1-12) * * * * A függvény először megkeresi az EEPROM-ban az első szabd helyet. Ezt onnan ismeri fel, * * hogy a beolvasott adatrekor első byte-ja 0. Nem olvassa ki mind a 4 byte-ot, mert ehhez * * elegendő 4 byte-onként csak egyet beolvasni. Ha egy beolvasott adat 0, akkor megnézi, hogy * * a következő 2 byte-on és a 4. byte-on milyen adat van, és ezt tárolja mint utolsó kiolvasott * * év-hónap adatot. Mindig az utoljára megtaláltat jegyzi meg. Ha a 2-4. byte-on 0-át talál, * * akkor megtalálta az első szabd helyet. Ha ez rögtön az első rekord, akkor tárolni kell * * egy év-hónap rekordot. Ha ez nem az első rekord volt, akkor össze kell hasonlítani az * * aktuális töltési rekordhoz magadott év és hónappal, és ha egyezik akkor írható a töltési * * rekord, ha nem egyezik, akkor előbb egy év-hónap rekordot kell csinálni, és aztán írható * * a töltési rekord. * **************************************************************************************************/ // elkészítjük a töltési rekord adatát írásra int nap_perce=(start_ora*60+start_perc)/3; //kiszámoljuk, hogy az adott napnak hányadik perce a megadott időpont, azért osztjuk 3-al, mert nem //tudunk ilyen nagy számot ábrázolni 9 biten (512 miozben egy nap 1440 perc is lehet) unsigned long xadat=((unsigned long)start_nap<<27) | ((unsigned long)nap_perce<<18) | ((unsigned long)(tolt_perc/2)<<8) | ((unsigned long)(kwh*10)); //elkészítjük az adatrekordot int akt_cim=0; //ebben található az a cím, ahonnan éppen olvasunk, vagy ahová éppen írunk. 4-el növeljük minden ciklusban byte akt_byte; //ebbe egy byte értéket olvasunk az eepromból int akt_int; //ebbe egy int értékezt olvasok ki az eepromból, amikor az év-hónap rekordot olvasom int uts_ev=0; //az utolsó megtalált év byte uts_ho=0; //az utolsó megtalált hónap do { akt_byte=EEPROM.read(akt_cim); //kiolvasunk egy byte értéket a következő címről (kezdetben 0, és 4-el növekszik if (akt_byte==0) { //ez vagy üres rekord, vagy év-hónap rekord EEPROM.get(akt_cim+1,akt_int); if (akt_int==0) { //ez egy üres rekord, csinálni kell egy év-hónap rekordot //és lehet írni a töltési rekordot if (uts_ev==0) { //teljesen üres az adatbázis, mert még nem volt tárolt év-hónap rekord //megcsináljuk az első év-hónap rekordot. Ezt csak egyszer fogjuk megtenni, az első függvényhíváskor EEPROM.put(akt_cim+1,start_ev); EEPROM.put(akt_cim+3,start_ho); akt_cim=akt_cim+4; EEPROM.put(akt_cim,xadat); break; //végeztünk, kilépünk a ciklusból } else { //üres rekor volt, de már van utolsó év adatunk, lehet összehasonlítani az évet és a hónapot if ((uts_ev==start_ev) & (uts_ho==start_ho)) { //stimmel az év és a hónap, lehet írni a töltési rekordot EEPROM.put(akt_cim,xadat); break; //beírtuk az adat rekordot, végeztünk } else { //nem stimmel az év és a hónap, először írunk egy év-hónap rekordot, aztán az adatrekordot EEPROM.put(akt_cim+1,start_ev); EEPROM.put(akt_cim+3,start_ho); akt_cim=akt_cim+4; EEPROM.put(akt_cim,xadat); break; //beírtuk az adat rekordot, végeztünk } } } else { //nem üres rekord, kiolvassuk még a hónapot is és tároljuk mint utolsó //év és hónao adatot uts_ev=akt_int; uts_ho=EEPROM.read(akt_cim+3); //mehet tovább a következő rekord olvasása } } akt_cim=akt_cim+4; } while (akt_cim<1023); //addig olvasunk maximum, amíg végig nem érünk az eeprom-on return (akt_cim/4); //visszatérünk az elhasznált terület mennyiségével (adatrekordok száma, ami max. EEPROM_MERET (UNO esetén 1023) lehet) } int log_utolso_bejegyzes(bool teljes_recordszam) { /********************************************************************************* * Ez a függvény visszadja egy int értékben az utolsó log bejegyzés sorszámát * * A sorszámot a log rekordok megszámlálásával állítja elő, és nem számolja bele * * az év-hónap rekordokat, csak a töltés rekordokat * **********************************************************************************/ int uts_rec=0; //az utoljára olvasott rekord sorszáma, minden adat rekordnál növeljük 1-el int akt_cim=0; //ebben található az a cím, ahonnan éppen olvasunk, vagy ahová éppen írunk. 4-el növeljük minden ciklusban byte akt_byte; //ebbe egy byte értéket olvasunk az eepromból int akt_int; //ebbe egy int értékezt olvasok ki az eepromból, amikor az év-hónap rekordot olvasom do { akt_byte=EEPROM.read(akt_cim); //kiolvasunk egy byte értéket a következő címről (kezdetben 0, és 4-el növekszik if (akt_byte==0) { //ez vagy üres rekord, vagy év-hónap rekord EEPROM.get(akt_cim+1,akt_int); if (akt_int==0) { break;} //ez egy üres rekord, vége a számlálásnak } else { uts_rec++;} //nem üres rekord, növeljük a rekordszámot akt_cim=akt_cim+4; //vesszük a következő rekord kezdőcímét } while (akt_cim<EEPROM_MERET); //ha végig értünk az eeprom-on, akkor is vége ciklusnak if (teljes_recordszam==1) { return(akt_cim/4);} else { return(uts_rec);} } toltes toltes_adat(int recordszam) { /*********************************************************************************** * Ez a függvény kiolvassa egy paraméterben megadott számú adatrekord tartalmát és * * visszaadja egy toltes struktúrában az adott adatrekord tartalmát. * * Az eeprom tartalmat az elejétől az utolsó rekordig végig olvassa azért, hogy * * az év-hónap rekordokból kiderítse a kért rekord évét és hónapját * ************************************************************************************/ int uts_rec=0; //az utoljára olvasott rekord sorszáma, minden adat rekordnál növeljük 1-el int akt_cim=0; //ebben található az a cím, ahonnan éppen olvasunk, vagy ahová éppen írunk. 4-el növeljük minden ciklusban byte akt_byte; //ebbe egy byte értéket olvasunk az eepromból unsigned long akt_long; //ebbe olvassuk ki a megtalált rekord tartalmát toltes adat2; //ebben a struktúrában foguk visszaadni az adatokat adat2.start_ev=0; adat2.start_ho=0; adat2.start_nap=0; adat2.start_ora=0; adat2.start_perc=0; adat2.tolt_perc=0; adat2.kwh=0; int akt_int; //ebbe egy int értékezt olvasok ki az eepromból, amikor az év-hónap rekordot olvasom do { akt_byte=EEPROM.read(akt_cim); //kiolvasunk egy byte értéket a következő címről (kezdetben 0, és 4-el növekszik if (akt_byte==0) { //ez vagy üres rekord, vagy év-hónap rekord EEPROM.get(akt_cim+1,adat2.start_ev); if (adat2.start_ev != 0) { //ez egy év-hónap rekord, meg kell jegyezni, mert ez az eddigi legutolsó EEPROM.get(akt_cim+3,adat2.start_ho); } else { break;} //ez egy üres rekord, vége a keresésnek, a legutolsó rekordot adjuk vissza } else { //nem üres rekord, növeljük a rekordszámot, és megjegyezzük az adatokat, hátha ez az utolsó rekord EEPROM.get(akt_cim,akt_long); adat2.start_nap=(akt_long & 4160749568)>>27; adat2.start_ora=(((akt_long & 133955584)>>18)*3)/60; adat2.start_perc=(((akt_long & 133955584)>>18)*3)-(adat2.start_ora*60); adat2.tolt_perc=((akt_long & 261888)>>8)*2; adat2.kwh=((float)(akt_long & 255))/10; uts_rec++; if (recordszam==uts_rec) { break;} //pont ez a rekord kell, nem keresünk tovább } akt_cim=akt_cim+4; //vesszük a következő rekord kezdőcímét } while (akt_cim<EEPROM_MERET); //ha végig értünk az eeprom-on, akkor is vége ciklusnak return(adat2); } int honap_torles() { /***************************************************************************************** * Ez a függvény a legrégebbi hónap adatait törli ki az eepromból, és a régebbi rekordokat* * előre lépteti az eeprom-ban. Valójában nem törli a régi rekordokat, hanem felülírja * * a régebbi rekordokkal, és az utolsó hónap adatait törli, miután azok már másolásra * * kerültek. A legelső év-hónap rekordot kihagyja. * * Ha csak egyetlen hónap adatai vannak logolva (pl. mert naponta többször is logolunk) * * és így telt meg az eeprom, akkor a rekordok negyedét törli. Ehhez azonban olyan * * állapotban kell meghívni a függvényt, amikor már megtelt az eeprom, ezt a programban * * figyelni kell. * * Visszaadja egy int értékben az összes törölt rekordszámot. Ha ez 0, akkor nem törölt * * semmit. Minimum 2 rekordot törölnie kell hiszen legalább egy év-hónap rekord és egy * * értékes adat rekord törlésre kerül. * * Ha csak egyetlen hónap adatai vannak az adatbázisban, akkor semmit nem töröl, és 0-át * * ad vissza * ******************************************************************************************/ int ossz_rec=log_utolso_bejegyzes(1); int cel_kezdopont=0; //ebben fogjuk megadni a másolás kezdőpontját rekordszámban (ennek négyszerese lesz a kezdőcím) int forras_kezdopont=0; //ebbe fogjuk kiszámolni a törlés kezdőpontját rekordszámban (ennek négyszerese lesz a kezdőcím) if (ossz_rec==0) { return(0);} //üres volt az adatbázis nem töröltünk semmit int akt_cim=4; //Az első rekord egy év-hónap rekord, ezt már nem kell kiolvasni, rögtön a következővel kezdünk byte akt_byte; //ebbe egy byte értéket olvasok be az eepromból int akt_int; //ebbe egy int értékezt olvasok ki az eepromból, amikor az év-hónap rekordot olvasom unsigned long akt_long; //ebbe a változóba olvasom be a másolandó adatot do { //megkeressük az első másolandó rekordot, ami egy év-hónap rekord akt_byte=EEPROM.read(akt_cim); //kiolvasunk egy byte értéket a következő címről (kezdetben 4, és 4-el növekszik if (akt_byte==0) { //ez a következő év-hónap rekord, vagy egy üres rekord EEPROM.get(akt_cim+1,akt_int); if (akt_int==0) { return(0);} //ez egy üres rekord, nem kell semmit másolni, mert csak egy hónap adatai vannak rögzítve else { break;} //ez egy év hónap rekord, megtaláltuk a másolás kezdőpontját } akt_cim=akt_cim+4; //vesszük a következő rekord kezdőcímét } while (akt_cim<EEPROM_MERET); //ha végig értünk az eeprom-on, akkor is vége ciklusnak if (akt_cim==EEPROM_MERET) { //egyetlen hónap töltötte meg az adatbázist, nem lehet egy teljes hónapot törölni cel_kezdopont=4; //az első rekordot nem bántjuk, csak a másodiktól kezdünk felülírni illetve törölni forras_kezdopont=((EEPROM_MERET/4)/4)*4; //meghatározzuk a teljes eeprom terület nagyságának negyedét önkényesen, ennyit fogunk törölni //Az így meghatározott rekordtól olvassuk majd az adatokat és felülírjuk a régi adatokat } else { //van egy következő év-hónap rekord, kiszámolhatjuk a másolás kezdő és cel_kezdopont=0; //az első rekordtól felülírunk majd forras_kezdopont=akt_cim; //A kiderített második év-hónap rekordtól olvassuk majd az adatokat és felülírjuk a régi adatokat } for (int i=0;i<EEPROM_MERET;i=i+4) { if (i+forras_kezdopont>EEPROM_MERET-1) { akt_long=0;} //a végére értünk az eeprom-nak, itt már 0-val töltjük a maradék területet else { EEPROM.get(i+forras_kezdopont,akt_long);} //még az eeprom-ból olvasunk if (i+cel_kezdopont>EEPROM_MERET-1) { break;} //mindent lemásoltunk, vége else { EEPROM.put(i+cel_kezdopont,akt_long);} //felülírjuk a régi adatot az újjal } return(akt_cim/4); //megadjuk, hogy hány rekordot sikerült törölni } byte multi_button(byte csatorna,byte valasz_mod,byte bemenet,byte felfutoel, byte novekedes, byte max_ertek) /**************************************************************************************************************************** * Bemenő paraméterek sorrendben: * * csatorna: ez egy globális változó deklarációban megadott tömbmérettől függő szám. Jelen példa programban 0-2 közötti szám* * mert három elemű tömböket deklaráltunk. * * * * valasz mod: ezzel állítjuk be, hogy milyen eseményt eredményez a nyomógomb lenyomása * * * * bemenet: Az arduino bemenet száma, amire a nyomógombot kötöttük * * * * felfutoel: A kimenet változása a nyomógomb megnyomásakor (0-lefutó él), vagy elengedésekor következzen be (1-felfutó él) * * Nyilván függ attól, hogy fizikailag hogyan lett bekötve a nyomógomb, itt most feltételezem, hogy szokványos * * bekötésű, tehát lenyomva földre húzza a bemenetet * * * * novekedes: valasz_mod=2 esetén gombnyomásonként ennyivel növeli a visszaadott értéket * * * * max_ertek: valasz_mod=2 esetén ezt az értéket követő nyomógomb megnyomás nullázza a visszaadott értéket * * * * Visszaadott érték lehetséges állapotai (ha a bemenet_allapot alapértelmezett értéke 0): * * valasz_mod=0 esetén és felfutoel=0: 1-nincs lenyomva a nyomógomb, 0-le van nyomva a nyomógomb * * valasz_mod=0 esetén és felfutoel=1: 0-nincs lenyomva a nyomógomb, 1-le van nyomva a nyomógomb * * * * valasz_mod=1 esetén: 0,1 minden lenyomáskor változik * * valasz_mod=2 esetén: 0-255 gombnyomásonként felfelé számol, novekedes változóban beállított érték a növekmény, max_ertek * * változóban beállított érték elérésekor visszaesik a kimenet értéke 0-ra. * ****************************************************************************************************************************/ { //bemenet lekövetése a paraméterek alapján if (felfutoel==0) //felfutó élre fognak bekövetkezni a visszaadott érték változásai { if (bemenet_allapot[csatorna]==0 & digitalRead(bemenet)==LOW & prell_tmp[csatorna]==0) //első lenyomás érzékelése { prell_tmp[csatorna]=1;prell_time[csatorna]=millis();} if (bemenet_allapot[csatorna]==0 & digitalRead(bemenet)==LOW & prell_tmp[csatorna]==1 & millis()>prell_time[csatorna]+50) // már 50msecv óta nyomva van, most már biztos, hogy lenyomták és nem prellezik { bemenet_allapot[csatorna]=1;prell_tmp[csatorna]=0;} if (bemenet_allapot[csatorna]==1 & digitalRead(bemenet)==HIGH & prell_tmp[csatorna]==0) //első elengedés érzékelése { prell_tmp[csatorna]=1;prell_time[csatorna]=millis();} if (bemenet_allapot[csatorna]==1 & digitalRead(bemenet)==HIGH & prell_tmp[csatorna]==1 & millis()>prell_time[csatorna]+50) // már 50msecv óta elengedve, most már biztos, hogy elengedték és nem prellezik { bemenet_allapot[csatorna]=0;prell_tmp[csatorna]=0;} } else //lefutó élre fognak bekövetkezni a visszaadott érték változásai { if (bemenet_allapot[csatorna]==0 & digitalRead(bemenet)==HIGH & prell_tmp[csatorna]==0) //első elengedés érzékelése { prell_tmp[csatorna]=1;prell_time[csatorna]=millis();} if (bemenet_allapot[csatorna]==0 & digitalRead(bemenet)==HIGH & prell_tmp[csatorna]==1 & millis()>prell_time[csatorna]+50) // már 50msecv óta elengedve, most már biztos, hogy elengedték és nem prellezik { bemenet_allapot[csatorna]=1;prell_tmp[csatorna]=0;} if (bemenet_allapot[csatorna]==1 & digitalRead(bemenet)==LOW & prell_tmp[csatorna]==0) //első lenyomás érzékelése { prell_tmp[csatorna]=1;prell_time[csatorna]=millis();} if (bemenet_allapot[csatorna]==1 & digitalRead(bemenet)==LOW & prell_tmp[csatorna]==1 & millis()>prell_time[csatorna]+50) // már 50msecv óta lenyomták, most már biztos, hogy elengedték és nem prellezik { bemenet_allapot[csatorna]=0;prell_tmp[csatorna]=0;} } switch (valasz_mod) { //kimenet vezérlése a paraméterek alapján case 0: //csak visszaadjuk a nyomógomb állapotát if (bemenet_allapot[csatorna]==0) { kimenet_allapot[csatorna]=1;} else { kimenet_allapot[csatorna]=0;} break; case 1: //változtatni kell a kimenet_alapot értékét 0->1, 1->0 if (bemenet_allapot[csatorna]==1 & bemenet_elozo_allapot[csatorna]==0) { if (kimenet_allapot[csatorna]==0) { kimenet_allapot[csatorna]=1;} else { kimenet_allapot[csatorna]=0;}} bemenet_elozo_allapot[csatorna]=bemenet_allapot[csatorna]; break; case 2: //növelni kell a kimenet_allapot értékét, és ha elérte a maximumot, akkor újra 0-át beállítani if (novekedes==0) { novekedes=1;} if (bemenet_allapot[csatorna]==1 & bemenet_elozo_allapot[csatorna]==0){ kimenet_allapot[csatorna]=kimenet_allapot[csatorna]+novekedes; if (kimenet_allapot[csatorna]>max_ertek) { kimenet_allapot[csatorna]=0;} } bemenet_elozo_allapot[csatorna]=bemenet_allapot[csatorna]; break; } return kimenet_allapot[csatorna]; }