Zapraszam na drugą część przygody z płytką Wemos D1 ESP8266 i czujnikiem temperatury/wilgotności DHT11. W tej części rozwinę swój program o zapis wyników w pamięci Flash ESP8226, wykorzystam do tego bibliotekę o wdzięcznej nazwie EEPROM. Dlaczego klasa do zapisu we Flash nazywa się tutaj EEPROM? Przekonajmy się!
Poprzedni temat z serii:
Wemos D1 "Arduino" i DHT11 - prosta stacja pogody z wykresami na stronie WWW
Zaraz zaczynamy, ale najpierw...
Przestroga przed zużyciem pamięci Flash
W tym temacie prezentuję zapis wyników w pamięci Flash. Jest to proste i dostępne, więc jak najbardziej kuszące, ale może być też zwodnicze. Pamięć flash się dość szybko zużywa na skutek wykonywanych cyklów kasowania.
Załóżmy, że nasz moduł z ESP8266 ma na pokładzie pamięć W25Q32, jest to pamięć podłączona do niego przez SPI, w tej pamięci przechowywany jest nasz program, ale możemy też zapisywać tam pomiary.... jednakże ta pamięć ma pewne ograniczenia. W nocie katalogowej mówi o nich jedna linijka, zaszyta w wielu innych informacjach:
100 000 cykli kasowania na sektor... zakładając najgorszy scenariusz, że zapisujemy pomiary do jednego sektoru, oraz zakładając że każdy zapis poprzedzony jest kasowaniem, to mamy 100 000 zdarzeń zapisu pomiarów.
100 000 pomiarów, ile to jest? Załóżmy jeden pomiar na godzinę.
100000/24 to około 4166 dni. Czyli około 11 lat...
Nie jest to raczej zbyt dobry wynik jeśli chcemy stworzyć coś więcej niż prosty program do zabawy. A przy np. 10-krotnie większej częstotliwości zapisów już spadamy do 1 roku... (minimum).
Jednakże ten problem rozwiążę w trzeciej części, na razie spróbujmy to pominąć i po prostu poznać najprostszy sposób zapisu do Flash.
Zaczynamy. W ramach przypomnienia zamieszczam kod z poprzedniego tematu:
Kod: C / C++
Pamięć "EEPROM"
ESP8266 nie posiada pamięci EEPROM, ale ktoś pomysłu wpadł na pomysł emulacji jej poprzez pamięć Flash. Na początek możemy spróbować tego użyć. Nie jest to zbyt dobry pomysł, bo częsty zapis będzie nam zużywać cenne cykle erase flash, więc musimy unikać zbyt częstego zapisu, ale w ramach ćwiczeń i tak można spróbować to zrobić.
Zatem załączamy nagłówek:
Kod: C / C++
Jakby co, cała dokumentacja jest tutaj:
https://arduino-esp8266.readthedocs.io/en/latest/libraries.html
W dokumentacji też jest przestroga przed szybkim zużyciem flash:
Cytat:
Note that the sector needs to be re-flashed every time the changed EEPROM data needs to be saved, thus will wear out the flash memory very quickly even if small amounts of data are written. Consider using one of the EEPROM libraries mentioned down below.
Warto też zobaczyć kod źródłowy użytej biblioteki:
https://github.com/esp8266/Arduino/blob/master/libraries/EEPROM/EEPROM.cpp
Do zapisu i odczytu mamy szablonowe funkcje put i get, które robią wszystko za nas. Dodatkowo trzeba uruchomić EEPROM funkcją begin:
Kod: C / C++
W miejsce x wstawiamy rozmiar EEPROM do użycia.
EEPROM może mieć tu rozmiar do 4096, zatem trzeba policzyć ile co zajmie miejsca. Nasza struktura to, przypominam:
Kod: C / C++
Ma to u nas 16 bajtów, zresztą sprawdźmy, sizeof prawdę nam powie:
Kod: C / C++
16 bajtów:
Skoro tak, to w 4096 zmieści się ta struktura całe 256 razy, ale chcemy jeszcze móc zapisać indeks ostatniego pomiaru, więc załóżmy, że zapisywać będziemy 255 próbek:
Kod: C / C++
Zatem implementujemy, najpierw - odczyt:
Kod: C / C++
Na pozycji 0 w pamięci będzie integer - indeks ostatniej próbki z bufora kołowego, a potem za nim będą kolejno próbki.
Teraz zapis:
Kod: C / C++
Zapis wymaga jeszcze wywołania commit aby dokonać właściwego zapisu zmian.
Trzeba jeszcze sprawdzić czy to działa. W tym celu dałem dodatkowo wyświetlanie do konsoli indeksu ostatniej próbki:
Kod: C / C++
Wgrywamy i obserwujemy zmiany w konsoli:
Czy po restarcie płytki odliczanie próbek wznowi się od zapisanego indeksu?
Tak, wygląda na to, że zapis działa.
Ale chwila...
Wykres się nie wyświetla!
W konsoli mamy błąd:
Wykonujemy "Zbadaj źródło" na stronie by zobaczyć co poszło nie tak:
Mamy w pamięci mnóstwo wartości nan, czyli "not a number". Trzeba to naprawić.
Ochrona przed śmieciami w pamięci
Nasz problem wynika stąd, że bieżąca wersja kodu naiwnie wczytuje z pamięci to co tam jest, bez jakiegokolwiek weryfikowania tych danych. A przecież ten system dopiero co opracowaliśmy i nie wiemy co w tej pamięci jest, wartości tam mogą być całkiem niezdefiniowane.
Można by po prostu ręcznie wyczyścić tą pamięć raz i ustawić tam same zera, ale można też poradzić sobie nieco sprytniej. Wystarczy użyć sumy kontrolnej.
Suma kontrolna to specjalna wartość która jest obliczana na podstawie większej ilości danych z pamięci. Funkcja sumy kontrolnej zaprojektowana jest tak, by mała zmiana danych skutkowała od razu zmianą wyniku tej sumy. A więc jeśli policzymy sumę kontrolną dla danej tablicy i przerzucimy chociażby jeden bit w tej tablicy, to suma kontrolna powinna się już zmienić. Oczywiście nie jest to idealne rozwiązanie, bo istnieją tzw. kolizje, ale przy tym zastosowaniu pozwolę to sobie pominąć...
Popularnym systemem sum kontrolnych jest CRC32. W sam raz na 4 bajty, 32-bity, mamy też do niego gotową bibliotekę:
Przykład od autora biblioteki:
Kod: C / C++
My musimy liczyć sumę kontrolną całego bloku danych przy każdym zapisie oraz zapisywać ją wtedy do EEPROM:
Kod: C / C++
Dla uproszczenie nie liczę sumy kontrolnej też dla indeksu ostatniej próbki, ale generalnie to też powinienem robić.
Od teraz moja struktura pamięci jest następująca:
- offset 0 - ostatnia próbka (integer, 4 bajty)
- offset 4 - suma kontrolna crc32 (integer, 4 bajty)
- offset 8 - dane pomiarów
W momencie startu programu, przy odczycie próbek, musimy odczytywać "starą" sumę kontrolną i liczyć to samo dla wczytanych danych. Potem porównujemy te wartości i jeśli są takie same, to zakładamy, że nie nastąpiła żadna niepożądana zmiana. Jeśli są różne, to na pewno coś poszło nie tak bądź w pamięci nie było żadnych próbek:
Kod: C / C++
Jeśli już wiemy, że w pamięci nie ma poprawnych danych, to musimy też tę pamięć ręcznie wyczyścić, policzyć poprawną sumę kontrolną i zapisać do niej te dane:
Kod: C / C++
Pora przetestować opracowany mechanizm. Na ten moment w pamięci są nieoczekiwane wartości, więc spodziewać się będziemy najpierw raz komunikatu o złej sumie kontrolnej, a potem z każdym rebootem płytki ta suma powinna być już w porządku.
Po wciśnięciu RESET:
Rzeczywiście, za drugim odczytem suma kontrolna była już poprawna. System działa:
Wartości pomiarów są odpowiednio pamiętane, nawet po utracie zasilania urządzenia.
Podsumowanie
W tej części poznaliśmy klasę EEPROM która w tym wydaniu zapewnia dostęp do... pamięci Flash oraz zorganizowaliśmy działający, ale niezbyt wydajny zapis danych do tej klasy. Przy okazji poćwiczyliśmy tematy takie jak suma kontrolna, itd.
Opracowany system działa, ale nie nadaje się do częstego zapisu wyników. Jeszcze gdybyśmy korzystali z EEPROM do zapisu jakiś ustawień (np. hasła WiFi, tylko gdy zmienia je użytkownik) to byłoby lepiej, ale do częstych zapisów to się nie nadaje.
Z tego też powodu w trzeciej części przepiszę ten program i zoptymalizuję go pod kątem redukcji zużycia pamięci Flash. Zobaczymy (i policzymy) ilukrotnie uda mi się wydłużyć szacowany czas życia naszych biednych sektorów.
Oczywiście, można by też po prostu użyć zewnętrznej pamięci EEPROM... kod byłoby łatwo na to przerzucić. Zobaczmy do noty katalogowej jakiegoś EEPROM ile on wytrzyma cykli. Przykładowo 24LC256:
Jak na razie - to tyle. Zapraszam do podzielenia się własnymi doświadczeniami z omawianą tematyką, jak byście Wy zorganizowali przechowywanie tych pomiarów?
Fajne? Ranking DIY Pomogłem? Kup mi kawę.