Arduino v příkladech - V.díl - Jak pracovat s pamětí do třetice
Opakování je matka moudrosti – EEPROM je paměť mikrokontroléru, kde zapsaná data zůstanou i po vypnutí napájení. Má omezený počet zápisů, výrobcem deklarovaných min. 100 tis. bylo v praxi více než 12 mil. Je to paměť, kde vy určujete co a kam se zapíše.
Výborně, no a co že nevymaže. Koupíte impulsní vodoměr, napíšete program, který vám zavře ventil pokud bude puštěná voda déle jak 30 minut. Zabráníte tak vytopení domu v případě že praskne voda. Pak půjdete napouštět bazén a každých 30 minut se Vám zavře voda. Řešení je jednoduché. Vytrhnete kabel od ventilu, nebo rozeberete krabičku ve které to máte umístěné a nahrajete nový program. Nebo stačí na to při programování myslet a požadovanou dobu uložíte do EEPROM. Změnit tuho hodnotu můžete za pomoci potenciometru, tlačítka, displeje, wifi a vlastně všeho co umožňuje pracovat s Arduinem.
Paměť EEPROM je tedy výhodné použít vždy, kdy potřebujete za běhu programu měnit jeho parametry, jako je časování čtení čidel a měnit jejich rozlišení, módy čtení, ukládat hesla, mazat a nahrávat adresy RFID klíčenek, uložit heslo, atd. a po každém restartu Arduino to nechcete absolvovat znovu. Také lze napsat funkci, která v případě že program totálně rozladíte, vrátí vše do výchozího nastavení. Jen příklad, když jsem psal program pro zabezpečovací ústřednu, obsadil jsem takto 720 polí a z krabičky Arduino vyndám až shoří.
V každém Arduino může být paměť jinak veliká, je to dáno použitým čipem. Např. Arduino UNO používá čip ATmega328P, který má 1 kB, tedy 1024 byte paměti EEPROM. Paměťové pole má rozsah 8 bit, tedy lze zadat hodnotu v rozsahu 0-255.
Pro naše příklady využijeme právě UNO a výpisy na Serial. Jediné co budeme potřebovat navíc, je knihovna EEPROM, která je součástí standardní instalace Arduino IDE.
Každá buňka EEPROM má svoji adresu, v případě UNO ji voláme v rozsahu 0-1023.
Podíváme se jak zobrazit velikost EEPROM
#include <EEPROM.h> // knihovna pro zapis do pameti EEPROM
void setup() {
Serial.begin(9600);
}
void loop() {
// uz vyuzijeme znalosti z SRAM - nevytvarime promennou
Serial.print(EEPROM.length()); // zobrazime velikost EEPROM
EEPROM.write(0, 1);
while (1) delay(10000);
}
ANO je to hodnota 1024. Stále na to zapomínám, začínáme 0, tedy končíme EEPROM.lenght()-1, tj. 1023. Nepokoušejte se zapsat do buňky 1024 – jde to, zapíšete do buňky 0, 1025 do buňky 1 atd.
Přehled funkcí knihovny EEPROM
Ukážeme si jen ty nejčastěji používané:
EEPROM.write(x, y) – zapíše do buňky x, hodnotu y. Pozor na přetečení, zapisujeme vždy hodnotu byte, nebo unsigned char (jsou stejné)
EEPROM.read(x) – přečte hodnotu z buňky x
EEPROM.update(x, y) – porovná hodnotu z buňky x s y a pokud nejsou stejné, zapíše do buňky x, hodnotu y. Tato funkce vykonává v podstatě stejné jako EEPROM.write s tím, že omezuje počet zápisů a je výrazně pomalejší.
Funkce se dá vyjádřit následujícím zápisem
if (EEPROM.read(x) != y) EEPROM.write(x,y);
EEPROM.clear() – přepíše celou paměť hodnotou 0. Osobně preferuji při vyčištění paměti zápis hodnoty 255. Hodnota 0 může přinést neočekávané výsledky třeba v případě, že kontrolujete zda bylo do buňky něco zapsáno. U hodnoty 255 je pravděpodobnost mnohem menší.
Funkční část programu pro mazání paměti
for (int i = 0; i < EEPROM.length(); i++) {
EEPROM.write(i, 255);
}
Existují ještě funkce pro zápis řetězců, desetinných čísel a jejich čtení, ověření zda se hodnoty zapsaly správně, ale to si můžeme ukázat jindy.
Z funkcí je ještě zajímavá ta, která vám zobrazí, co máte v EEPROM uložené. Výborná věc až budete hledat, proč něco nefunguje.
#include <EEPROM.h>
byte count = 0;
void setup() {
Serial.begin(9600);
}
void loop() {
for (long i = 0; i < EEPROM.length(); i++) {
if (count == 0) {
Serial.print("EEPROM ");
Serial.print(i);
Serial.print(": ");
}
Serial.print(EEPROM.read(i));
count ++;
if (count < 10) Serial.print(", ");
if (count == 10) {
Serial.println();
count = 0;
}
}
while (1) delay(10000);
}
Praktické příklady
Příklad 1
Následující sketch nám ukáže, jak za pomoci zápisu EEPROM nastavit počet bliknutí diody na Arduinu v zadaném intervalu 5 s. Nahrajte sketch, zapněte Serial monitor, přepněte do režimu "Chybný konec řádky" a zadejte libovolné číslo v rozsahu 0-9. To je počet rychlých bliknutí každých 5 s.
#include <EEPROM.h> // knihovna pro zapis do pameti EEPROM
#define LED 13 // pojmenujeme vystup
unsigned long posledniSpusteni; // pripravime prommenou pro cyklus
int prodleva = 5000; // nastavime prodlevu mezi blikanim
void setup() {
Serial.begin(9600); // inicializuje serial
pinMode(LED, OUTPUT); // nastavime pin jako vystupni
digitalWrite(LED, LOW); // pro jistotu zhasneme diodu
}
void loop() {
// kdyz odesleme data, precteme je a ulozime do EEPROMM
if (Serial.available() > 0) { // kdyz prisla data
char cteni = Serial.read(); // precti je
String text = (String)cteni; // preved ctenou hodnotu na String
int pocet = text.toInt(); // preved ulozenou hodnotu na cislo
EEPROM.update(0, pocet); // uloz cislo do pameti EEPROM
}
// a blikame
if ((unsigned long)(millis() - posledniSpusteni) >= prodleva) { // nastavime podminku pro uplynuti casu
posledniSpusteni = millis(); // nastavime novy cas spusteni cyklu
// zablikame diodou tokikrat, kolikrat je ulozeno v EEPROM
for (byte i = 0; i < EEPROM.read(0); i++) {
digitalWrite(LED, HIGH);
delay (100);
digitalWrite(LED, LOW);
delay (100);
}
}
}
Pokud Vám to nefunguje a dioda nebliká, tak jste zapomněli přepnout do režimu "Chybný konec řádky".
Až budete pracovat s daty ze Serial monitoru zjistíte, že ve skutečnosti NULA odeslaná ze Serial monitoru není NULOU jako číslo, ale číslo 48. Více informací najdete na https://cs.wikipedia.org/wiki/ASCII. Pokud vezmu jazyk Arduino, tak jste dostali hodnotu '0', tedy text. Protože je to seriál především pro začátečníky, zvolil jsem převod na číslo jak ho vidíte, zkušení jistě prominou. Ale to zase odbočuji.
Teď odpojte Arduino od napájení a znovu připojte, dioda začne opět blikat frekvencí kterou jste nastavili. A to je důvod proč používat paměť EEPROM.
Příklad 2
Nyní si zkusíme složitější příklad. Do EEPROM si budeme ukládat telefonní čísla 4 uživatelů a po zadání si je vždy pošleme na Serial monitor pro ověření.
Zadání čísla probíhá tak, že písmena A, B, C a D mám určí kterému uživateli budeme zapisovat číslo a pak zadáme devíti místné číslo. Písmenem V získáme výpis uložených čísel. Tento příklad je vhodný třeba pro změnu čísla, za pomoci displeje na které bude volat GSM modul. Takto si můžete číslo změnit i za pomoci SMS. Jak používat GSM modul, jsme si na našich stránkách již ukázali. Program je napsaný tak, aby vyloučil neplatné znaky. Můžete tedy odeslat "C 800800800" a stejně dostanete správný výsledek.
#include <EEPROM.h> // knihovna pro zapis do pameti EEPROM
void setup() {
Serial.begin(9600); // inicializuje serial
}
void loop() {
if (Serial.available() > 0) { // kdyz prisla data
zapisCisla(); // spustime funkci
}
}
// funkce pro zapis modu, telefonniho cisla a cteni zapsanych dat
void zapisCisla() {
// vytvorime prommene
const int adresaEEPROM[] = {1, 10, 19, 28}; // adresy EEPROM pro zapis tel. cisel
byte mod = 0;
byte pocitadlo = 0;
unsigned long casSpusteni;
const byte konecBehu = 100;
char cteni;
String text;
// cyklus pro cteni dat ze Serial portu
do { // cekani na spravnou odpoved
if (Serial.available() > 0) { // kdyz jsou k dispozici data, precti data
casSpusteni = millis(); // nastav cas pro ukonceni
cteni = Serial.read(); // precti data
if (cteni == 'V') { // kdyz je V nastav mod na vypis cisel
mod = 3;
}
else if (cteni == 'A' || cteni == 'B' || cteni == 'C' || cteni == 'D') { // kdyz je znak ABCD, nastav mod pro zapis na uzivatele ABCD
mod = 1;
}
else if (cteni >= '0' && cteni <= '9') { // kdyz je cislo
text += cteni; // dopln promennou o dalsi nacteny znam
if (text.length() == 9) { // kdyz se dosahne 9 mistneho cisla nastav mod na zapis
mod = 2;
}
}
}
}
while (!mod && !((unsigned long)(millis() - casSpusteni) >= konecBehu)); // ceka na spravnou hodnotu, nebo uplynuti casu
if (mod == 1) { // kdyz je mod 1
EEPROM.update(0, cteni - 'A'); // zapis do EEPROM 0, ciselnou hodnotu uzivatele ktereho budeme zapisovat
}
else if (mod == 2) { // kdyz je mod 2
for (int i = 0; i < 9; i++) { // zapis do EEPROM tel cislo uzivatele
EEPROM.update(adresaEEPROM[EEPROM.read(0)] + i, text[i]);
}
}
String text1 = "Telefonni cislo uzivatele "; // promenna pro zobrazeni textu
if (mod == 2) { // kdyz je mod 2
Serial.print(text1 + (String)(EEPROM.read(0) + 1) + " : "); // zobraz cislo zapisovaneho uzivatele
for (int i = 0; i < 9; i++) { // zobraz ulozene telefonni cislo
Serial.print((char)EEPROM.read(adresaEEPROM[EEPROM.read(0)] + i));
}
Serial.println();
Serial.println();
}
if (mod == 3) { // kdyz je mod 3
for (int i = 0; i < 4; i++) { // zobraz postupne uzivatele
Serial.print(text1 + (String)(i + 1) + " : ");
for (int count = 0; count < 9; count++) { // zobraz telefoni cislo uzivatele
Serial.print((char)EEPROM.read(adresaEEPROM[i] + count));
}
Serial.println();
}
Serial.println();
}
}
A hned opět odbočím. Využili jsme techniky, které jsme si popsali v minulých seriálech. Pro nezkušené oko se to může zdát těžko čitelné, ale výrazně jsme si tak zkrátili celý program. Vytvořili jsme si funkci zapisCisla(). Důvodem je zkrátit funkci loop. Až budete psát delší program, je dobré si udržovat krátký loop, jinak se z těch závorek zblázníte. Také ani jednu proměnnou jsme nedefinovali jako globální. Jednak ušetříme místo v SRAM a pak je pokaždé nemusíme nulovat. Pro čtení dat ze Serial portu využíváme cyklus do..while. Ten používám prakticky neustále. Většina periférií posílá ukončovací znak, podle kterého i můžeme ukončit cyklus. Pro zápisy a čtení EEPROM je využit cyklus for, důvodem je opět zkrácení zápisu. Kód je velmi podrobně popsaný, ale rád zodpovím na případné dotazy.
Shrnutí
Pokud budete tvořit projekt, kde se mění základní nastavení a nechcete připojovat počítač k vašemu Arduino, bez práce s EEPROM se neobejdete.
Omezte počet zápisů do EEPROM, neodkládejte tam tzv. pracovní proměnné, které se mění několikrát za vteřinu.
Dávejte pozor kam co zapisujete. Tuto paměť ovládáte vy.
Příště si ukážeme praktický příklad, jak používat periférie právě pro nastavení za pomoci displeje. A vrátíme se k analogovému čtení.
JB