FM24C04A külső FRAM használata

Tartalom:

  • Mikor lehet szükség tápfesz kikapcsoláskor sem felejtő RAM-ra! Élettartam problémák, és megoldásuk FRAM-al!
  • FRAM felépítése, használata
  • Felhasználási mintapélda letölthető programkönyvtárak nélkül, elemi parancsok használatával.
  • Különböző változó típusok írása és visszaolvasása saját függvényekkel
  • Kapcsolódó leírás (minimum, maximum, átlag, szumma tárolása külső nem felejtő RAM-ban)

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

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

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

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

A chip lábkiosztása (az áramköri lemez számozása ezzel megegyezik):

És az egyes lábak funkciója:

A WP (Write Protect) lábat GND-re kell kötni, ha a RAM-ba írni akarunk. Magas állapotban a RAM nem írható (írásvédelem). A katalógus szerint belülről le van húzva, tehát ha nem kötjük sehová, a RAM mindig írható!

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

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

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

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

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

#include <Wire.h>

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

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

void loop() {
  
}

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

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

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

Mennyire volt hasznos amit olvastál? Értékelés után szövegesen is leírhatod megjegyzéseidet és véleményedet!

Kattints egy csillagra az értékeléshez!

Szövegesen is leírhatod véleményedet! Ha kérdésed van, ne felejtsd el megadni az email címedet!