środa, 20 grudnia 2017

ESP8266 > zapis do pamięci EEPROM kodów routera 433 MHz

Kody urządzeń nadawczych i odbiorczych musimy gdzieś zapisać. Można to zaszyć na stałe w programie naszego routera ale wtedy jakakolwiek zmiana urządzenia wymagać będzie zmiany programu. Lepszym rozwiązaniem jest zapamiętanie kodów w pamięci nieulotnej mikrokontrolera.  To właśnie pamięć EEPROM gwarantuje nam zachowanie wartości zmiennych programu po zaniku zasilania. Ulubiona ATmega328 ma jej 1024 bajty. W porównaniu z kartami SD to naprawdę niedużo.
Ale jeszcze 40 lat temu taka  pojemność byłaby kosmicznie wielka. Pamięci ferrytowe (na zdjęciu obok) odpowiednik współczesnych EEPROMów miały pojemności rzędu kilkudziesięciu BITÓW na płycie o wymiarach kartki A4. A dodatkowo zawartość pamięci kasowała się samoistnie podczas operacji odczytu danych!!! Nośnikiem informacji był namagnesowany rdzeń toroidalny wielkości obrączki. We współczesnych pamięciach jest nim ładunek elektrostatyczny zgromadzony na nanometrowej powierzchni. To obok tranzystora najbardziej spektakularny przykład miniaturyzacji jaka dokonała się w elektronice przez te lata. Skorzystajmy z tego przy budowie naszego routera.

Ale ESP8266 jak raz nie wyposażono w ani jedną komórkę pamięci EEPROM. A jednak nasz ulubiony procesor jest zdolny do pamiętania danych. Jak? Dziś mikrokontrolery za pomocą bootloaderów same sobie wgrywają program do wewnętrznej lub zewnętrznej pamięci typu flash. I tylko od konstruktora procesora zależy czy udostępni nam choćby odrobinę tej pamięci do zapisu bieżących danych z programu tak byśmy mogli je odtworzyć po zaniku zasilania. Chińczycy przyoszczędzili na EEPROMie ale w zamian stworzyli możliwość zmiany zawartości pamięci flash wprost z programu użytkownika.

Technologicznie obie te pamięci są mocno zbliżone do siebie. Główną różnicą między EEPROMem a FLASHem jest sposób kasowania danych. EEPROM ma możliwość skasowania pojedynczych bajtów,  we FLASHu możemy to zrobić jedynie blokiem. Natomiast zapis i odczyt i tu i tu możliwy jest dla pojedynczych bajtów. Różnica wynika z organizacji komórek pamięci. Dla EEPROMa można zaadresować pojedynczy bajt. Dla FLASHa jeden rząd bajtów (blok) który jest następnie odczytywany/zapisywany szeregowo jak w rejestrze przesuwnym. Dużą różnicą jest też gwarantowana ilość możliwych programowań (zapisów lub kasowań).Dla EEPROMa jest to ok 100 000 razy dla FLASHa 10 x mniej. 

O tych wszystkich różnicach praktycznie nie musimy wiedzieć. Cały problem z właściwą obsługą pamięci bierze na siebie biblioteka <EEPROM.h> . Radzi sobie dobrze zarówno ze standardową pamięcią EEPROM np. w ATmega jak i z pamięcią flash w ESP8266. Choć sposób obsługi pamięci obu kości w programie nieco się różni. Dla typowej pamięci procedura  EEPROM.write(adr,dana) powoduje natychmiastowy zapis do pamięci nieulotnej. Dla ESP w całym procesie pośredniczy dodatkowa pamięć RAM i na niej dokonuje się proces zapisu. Dla ostatecznego zapisu całego bloku danych do pamięci flash należy użyć polecenia EEPROM.commit(); (zapis do pamięci flash) lub EEPROM.end(); (zapis do pamięci flash i wyczyszczenie pamięci RAM).

Kompletna procedura zapisu/odczytu danych dla ESP8266 wygląda tak
  EEPROM.begin(512); // deklaracja > ilość bajtów można ustawić od 4 do   4096
  EEPROM.write(adres, dana);
  EEPROM.read(adres);
  EEPROM.commit(); lub   EEPROM.end();

Jako ciekawostkę mamy możliwość potraktowania funkcji EEPROM jako tablicy bajtów
Dzięki tamu zapisu danej do komórki o nr 1 możemy dokonać w ten sposób

EEPROM(1) = 123;

a odczytu

byte dana = EEPROM(1);

Tym sposobem wpiszemy lub odczytamy bajt informacji. Dla zmiennych złożonych z większej ilości bajtów mamy dwie drogi.

  • użyć funkcji put / get
  • wpisać / odczytać sekwencyjnie całą daną bajt po bajcie

Funkcje put i get są wewnętrznymi procedurami biblioteki eeprom.h. Umożliwiają jednym poleceniem wykonanie operacji zapisu/ odczytu zmiennych wielobajtowych  ale tylko tych ze zbioru podstawowych typów - int, char, long, float itd.

  EEPROM.begin(1024); // ilość bajtów może zawierać się w przedziale 4 - 4096
  EEPROM.put(adres, dana);  //int dana; char dana; long dana; float dana
  EEPROM.commit(); lub   EEPROM.end();

Do typów podstawowych nie zalicza się niestety popularny String.  Zmienną tego typu musimy zapisać bajt po bajcie lub zamienić na ciąg (tablicę) znaków.

Możemy natomiast tymi funkcjami operować dla innych bardziej złożonych typów np. struktur czy tablic:

struct MojaStruktura {
  float zmienna_f;
  byte zmienna_b;
  char tablica[10];
};
  MojaStruktura struc1; // deklaracja zmiennej
  EEPROM.put(adres, struc1); //zapisz zmiennej bedącej strukturą od komórki adres

Jeśli wiadomo jak zapisywać dane w pamięci spróbujmy połączyć to z grafem odbioru kodów urządzeń opisywanym w poprzednim wpisie.
Da zapamiętania są dwie liczby reprezentujące "adres" konkretnego urządzenia - KOD i IMPULS.
KOD jest zmienną typu long (4 bajty) IMPULS typu int (2 bajty). potrzeba więc 6 bajtów do zapisu parametrów jednego urządzenia. Dla ułatwienia zarezerwuję 10 bajtów/ układ. Kolejne kody będą umieszczane w kolejnych dziesiątkach pamięci co powinno znaczenie ułatwić adresowanie.
Adresy wyznaczam jak poniżej

adres n-tego KODu       =   n x 10
adres n-tego IMPULSu =   n x 10 + 5

Dla maksymalnego rozmiaru pamięci mogę zapamiętać ponad 400 różnych urządzeń.
Testowe procedury zapisu do pamięci wyglądają tak:

int nrpozkod = 0; //nr pozycji do zapisu kodu
long KOD = 0; // zmienna kodu do zapisu w pamięci
int IMPULS = 0; //zmienna długości impulsu kodu do zapisu w pamięci
long mem_KOD = 0; // zmienna kodu odczytana z pamięci
int mem_IMPULS = 0; //zmienna długości impulsu kodu odczytana pamięci

void savekod (int gdzie, long co1, int co2) { // zapis do eeprom
  int  liczadres = gdzie * 10;
  EEPROM.begin(512);
  EEPROM.put(liczadres, co1);
  EEPROM.put((liczadres + 5), co2);
  //  EEPROM.commit();
  EEPROM.end();
}
void readkod(int gdzie) { // odczyt z eeprom
  int  liczadres = gdzie * 10;
  EEPROM.begin(512);
  EEPROM.get(liczadres, mem_KOD);
  EEPROM.get((liczadres + 5), mem_IMPULS);
  EEPROM.commit();
  EEPROM.end();
}

void zapisz_kod() {
  Serial.print(" nr  " + String(nrpozkod)); Serial.print("  zapisuje kod  " + String(KOD));  Serial.println(" impuls " + String(IMPULS));
  savekod(nrpozkod, KOD, IMPULS);
} //procedura wywołania zapisu kodu do pamięci nieulotnej


I efekt działania procedury KOD_do_eeprom()


Klawiszem załączam procedurę (stan automatu z 1 przełącza się na 2) a widgetem MENU ustawiam nr pozycji pamięci do zapamiętania kodu. Dwukrotne powtórzenie nadania tego samego kodu automatycznie zapisuje jego parametry do pamięci flash. Po resecie odczytuję zapamiętane wartości. I o dziwo są identyczne z tymi które zapisywałem.

Automat skończony do dekodowania kodów 
Zapisywanie kodów do EEPROMu 

Czekam ze spokojem na ciąg dalszy......


Wcześniejsze odcinki serialu
http://100-x-arduino.blogspot.com/2017/11/router-433-mhz-aczymy-rozne-systemy.html
http://100-x-arduino.blogspot.com/2017/11/router-433-mhz-nauka-czytania-i-pisania.html
http://100-x-arduino.blogspot.com/2017/12/router433-jak-programowac-schematy.html
106

Brak komentarzy:

Publikowanie komentarza