Memória



Nem rég szembesültem egy számomra új hibajelenséggel, amit több napos próbálkozás és keresgélés után sikerült csak úgy ahogy megoldanom. Most ezt osztom meg hátha más ebből gyorsabban felismeri vagy sikeresebben elkerüli a hiba forrását.


Tehát kezdjük az elején: építettem egy több szenzoros oled kijelzős mérőszerkezetet, ez így már egy összetettebb dolog. A programot több részben írtam meg, a részeket teszteltem, működtek. A progi összefűzése után jöttek a problémák, még az alap eljárások is egész mást csináltak mint amit logikusan elvárható lett volna tőlük. Egy pár jelenség: elcsúszott kijelzés, más helyen megjelenő karakterek, teljes lefagyás, indításnál többszöri reset a normális kezdéshez, alapművelet továbbléptetése utáni lefagyás. Röviden teljes káosz.


Progi átírása, eljárások áthelyezése, végül programrészek felfüggesztése sem segített, vagyis mégis
mert ha megadott nagyságú részt elraktam megjegyzésként akkor a maradék program hibátlanul működött. Ekkor álltam neki keresgélni a neten, mi okozhatja az én új keletű problémámat (amiről sejtem hogy már sokan ismerik). A hibát egy Arduino memóriáról szóló cikk segítségével értettem meg, ebből ültetek át egy két dolgot magyarra. A cikk megtekinthető: https://learn.adafruit.com/memories-of-an-arduino?view=all


Milyen memória kiosztással dolgozik az Uno?

Az Atmel328 32kB Flash memóriát használ, ez a program memória, itt tárolódik a lefordított programunk. Az Flash akkor is megőrzi az adatokat ha nincs áramellátása( de ezt már gondolom mindenki észrevette). A Flash memória lefoglalt területét az Arduino IDE mindig tudatja velünk fordítás után az alsó üzenet ablakban. Itt mindig csak arra kell figyelnünk, hogy ne telítsük teljesen a memóriát.



Az Uno EEPROM-ot is használ tárolásra, ami szintén megőrzi a tárolandó adatokat, viszont csak nagyon korlátozott méretben használható 1kB. Külön könyvtár foglalkozik az olvasásával illetve írásával. Ezt akkor használhatjuk ki jól ha egy változó értéket fixen elakarjuk tárolni későbbi felhasználásra. Ennek a használatáról majd később.


Most nézzük azt ami a problémát okozta nálam, ez a mikrovezérlőnk harmadik memória típusa az
SRAM. 2KB áll rendelkezésünkre, ami nem túl sok, sőt rendkívül kevés ha figyelembe vesszük, hogy mire is használja az Unonk. Az SRAM végzi a statikus és globális változók kezelését ezt az
SRAM Statikus adat blokkjában tárolja el a program indításakor.

A dinamikusan lefoglalt adatterületek a a Heap-ben(kupac) helyezkednek el, ez az SRAM statikus
blokkja felett található.

Az SRAM harmadik egysége a Stack (verem), ez kezeli a helyi változókat, a függvény hívásokat,
a megszakításokat. Mindig csak az aktív változóknak, függvényeknek, megszakításoknak foglalja a
memóriát, a használat után felszabadítja azt. Ez a memória felső részén helyezkedik el.


A problémák ott kezdődnek amikor a elhasználódik az SRAM, a Heap és a Stack memória területfoglalása ütközik. Az eredmény mindig kiszámíthatatlan, vagy teljes összeomlást eredményez vagy későbbi működési zavart.

Mi a teendő? Több optimalizáló megoldás létezik, de még így is fennállhat a lehetősége annak, hogy túl sokat kívánunk a kis teljesítményű Arduinonkból kipréselni.
Először nézzük a memória egységek szabad területének megállapítását, erre a programunkban beépíthető függvények alkalmasak.

Flash memória: a fordító a progi minden fordításánál kiírja az elhasznált flash nagyságát.

EEPROM: ezt csak akkor mérjük ha használjuk is. A mérést a következő függvény segíti:

// ************************************************
// Write floating point values to EEPROM
// ************************************************
void EEPROM_writeDouble(int address, double value)
{
byte* p = (byte*)(void*)&value;
for (int i = 0; i < sizeof(value); i++)
{
EEPROM.write(address++, *p++);
}
}
// ************************************************
// Read floating point values from EEPROM
// ************************************************
double EEPROM_readDouble(int address)
{
double value = 0.0;
byte* p = (byte*)(void*)&value;
for (int i = 0; i < sizeof(value); i++)
{
*p++ = EEPROM.read(address++);
}
return value;
}


SRAM: ami minket igazán érdekel, a következő módon tudjuk ellenőrizni:

int freeRam ()
{
extern int __heap_start, *__brkval;
int v;
return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}


az adott függvényt hozzácsatoljuk a programunkhoz, és ezt a kérdéses helyekről meghívjuk a
következő módon: Serial.println(freeRAM());


Mindig a szabad SRAM méretét írja ki. Itt azért tudni kell az SRAM-ban eltárolt adatok között rések keletkeznek a memória tagoltságából eredően(töredezettség), a függvény ezeket a réseket is beleszámolja a szabad helybe így valóságba kevesebb használható helyünk lesz mint amennyit a függvény kiad. Ez nálam 100-150 bájtot jelentet.


Most nézzük mivel optimalizálhatjuk a programunkat:

A program memória alapból véges a maga 32kB-ával, itt program írásnál kell minél egyszerűbb
hatékonyabb kódrészekre törekedni.
Meg kell szüntetni a felesleges könyvtár deklarációkat, a nem használt változókat, függvényeket
töröljük. Az ismétlődő részek helyett használjunk függvényeket.


SRAM optimalizálása: Változók, ne használjunk felesleges változókat, csak a típushoz megfelelőt deklaráljunk. A változó tömbök méretét korlátozzuk.
Minél kevesebb változót és állandót deklaráljunk globális ként mert ezek statikusan foglalják a helyet az SRAM-ban. Amit csak lehet helyi változókkal oldjunk meg egy-egy cikluson vagy függvényen belül, ezek mindig dinamikus helyfoglalást végeznek, az adott programrész lefutása után a helyük felszabadul.

Az állandókat PROGMEM utasítás segítségével át mozgathatjuk a Flash ram-ba evvel is csökkentve a az SRAM terhelését.

Pl: const int szam PROGMEM = 123;
const double szamok[] PROGMEM = {12.21,34.43,56.65,78.87};
const char uzenet[] PROGMEM = {”Memoria optimalizalas lehetosegei”};
cons char* sorrend[] PROGMEM = {”elso”,”masodik”,”harmadik”};

Ezek mindig állandók, a programból már más értéket nem vehetnek fel!

Nekem az állandó mérés segített, azt hiszem így a legkönnyebb megtalálni a program ideális felépítését. Zárásként még egy két kiemelkedő sram fogyasztó:

OLED kijelző – felbontástól függ, minden képpontra 1 bit lefoglalt SRAM jut. 128X64 -es felbontás 1kB SRAM-ot foglal le, 128x32 512 bájtot.

STC7565 LCD – majdnem ugyan az mint az OLED 1kB SRAM.

SD-kártyák – 512bájt lefoglalt puffer memória kezdésként.

RGB-mátrix – 32x32 pixeles több mint 1600 bájt SRAM.

LED-szalag – minden pixel 3bájtot lefoglal.

Minden szenzor, minden eszköz és sok könyvtár is súlyos memória helyeket foglal el az SRAM-ból, ezért mindig nagyon meggondoltan, hogy mit akarunk használni és hogyan.