Zápisník experimentátora
Hierarchy: Externá EEPROM
V tomto článku nadviažeme na predchádzajúci, v ktorom sme si vysvetlili základné použitie externej EEPROM. Navrhneme si triedu, ktorá nám umožní pohodlne ukladať do EEPROM ľubovoľný objekt. Využijeme pritom šablóny jazyka C++ a doplníme do triedy dve funkcie, ktoré budú plniť túto úlohu. Príklad bude pre 24LC16B, ale až na drobnosti bude tento návod univerzálny pre ľubovolnú EEPROM.
Na tento experiment stačí iba pár súčiastok:
Arduino je upravené tak, že má na I2C (nachádza sa netradične na extra pinoch uprostred Arduina) prispájkovanú pin lištu s otvormi, aby sa pomocou dvoch prepojovacích vodičov dalo ľahko prepojiť s EEPROM.
Dve tlačidlá sa používajú na spustenie konkrétnych testov a sú pripojené na piny 6 a 8 na Arduine. EEPROM je pripojená na napájanie a adresové piny A0, A1 a A2 sú nezapojené. To platí ale iba pre tento konkrétny typ EEPROM. Ostatné musia obvykle pripojiť tieto piny na GND alebo na VCC, čím si nastavia I2C adresu. Treba si vždy skontrolovať datasheet ku konkrétnemu typu a nastaviť to podľa neho.
Triedy jazyka C++ nám umožňujú používať nielen existujúce triedy, ale vďaka dedičnosti si môžeme z existujúcich tried vytvoriť nové, ktoré doplníme o nové funkcie. V tomto príklade si od triedy extEEPROM
(použitej v predchádzajúcom príklade) odvodíme triedu TemplatedEEPROM
, ktorá bude obsahovať dve šablónové funkcie.
get
- Pomocou funkcie naplníme obsah premennej ľubovoľného typu uloženým obsahom z EEPROM.put
- Pomocou funkcie uložíme obsah premennej ľubovoľného typu do EEPROM.V príklade potom použijeme dve testovacie funkcie na otestovanie našich šablónových funkcii. Poďme rovno na komentovaný príklad, kde uvidíme všetky funkcie. Nebudem komentovať obsah súboru dump.h
. Funkcie, ktoré sa v ňom nachádzajú, sú popísané napríklad v článku Dump obsahu premennej na sériový port.
Týmito dvomi riadkami hovoríme kompilátoru, ktoré ďalšie súbory chceme do kompilácie zahrnúť. V našom prípade to je knižnica pre externú EEPROM a pomocné výpisy na sériový port.
#include <extEEPROM.h>
#include "dump.h"
Definovaním novej triedy v hlavnom programe som zmiatol parser Arduina, ktorý za vás porobí formálne deklarácie funkcií a preto som musel všetky použité funkcie deklarovať ručne. Vo svete Arduina to musíme málokedy robiť, ale toto bol asi ten príklad. Zmysel tejto operácie v C++ je jednoduchý. Ak je funkcia použitá skôr, ako je známa, kompilátor vyhodí chybu. Preto je potrebné funkcie aspoň formálne popísať, čo kompilátoru na preklad stačí a linker si už s vytvorením výsledného programu ľahko poradí.
void dumpEEPROM(uint32_t startAddr, uint32_t nBytes);
void eeErase(uint8_t chunk, uint32_t startAddr, uint32_t endAddr);
void eeTemplateWrite();
void eeTemplateRead();
Takto sa zapisuje odvodená trieda. Prvá funkcia je konštruktor
, ktorý je v tomto pripade iba zopakovaním pôvodného konštruktora z knižnice externej EEPROM. Ale musí tam byť, pretože inak by som nemohol definovať premennú daného typu. A keď vytvárame odvodené typy, skoro vždy pridávame aj vlastný kód. Táto funkcia bez kódu je skôr výnimkou.
Zvyšné dve funkcie sú šablónové. To znamená, že je to len šablóna skutočnej funkcie s konkrétnym typom, ktorý za nás vytvorí kompilátor. Kód nie je zložitý. Iba si pretypujeme typ T
na uint8_t
(adresu v RAM referencie na pointer), vypočítame veľkosť v bajtoch funkciou sizeof
a po jednom znaku ju pošleme do EEPROM. Pointer this
som použil kvôli prehľadnosti, aby ste videli, ktoré funkcie triedy používam na zápis. V C++ to nie je povinné tam dať, ale sú jazyky, ktoré to zase vyžadujú.
class TemplatedEEPROM : public extEEPROM
{
public:
TemplatedEEPROM(eeprom_size_t deviceCapacity, byte nDevice, unsigned int pageSize, byte eepromAddr = 0x50)
: extEEPROM(deviceCapacity, nDevice, pageSize, eepromAddr)
{}
template< typename T > T &get( int idx, T &t ) {
uint8_t *ptr = (uint8_t*) &t;
for ( int count = sizeof(T) ; count ; --count )
*ptr++ = this->read(idx++);
return t;
}
template< typename T > const T &put( int idx, const T &t ) {
const uint8_t *ptr = (const uint8_t*) &t;
for ( int count = sizeof(T) ; count ; --count )
this->write(idx++, *ptr++);
return t;
}
};
A pretože už máme triedu definovanú, môžeme si definovať premennú typu TemplatedEEPROM
, ktorá vie používať aj funkcie pôvodnej triedy, aj naše nové doplnené.
TemplatedEEPROM eep(kbits_16, 1, 16);
Vo funkcii loop iba kontrolujeme stlačenie tlačidla a podľa toho zavoláme funkcie.
void loop(void)
{
if (digitalRead(btnStart) == LOW) {
delay(100);
eeTemplateWrite();
eeTemplateRead();
}
if (digitalRead(btnErase) == LOW) {
delay(100);
eeErase(chunkSize, 0, totalKBytes * 1024);
dumpEEPROM(0, totalKBytes * 1024);
}
}
Aby sme mali nejaké premenné, zadefinoval som jeden textový reťazec a jednu štruktúru a naplnil ich konkrétnymi hodnotami.
char demo_string[] = "0123456789";
struct _demo_struct {
int8_t a;
int16_t b;
int32_t c;
} demo_struct = {1, 2, 3};
Vo funkcii eeTemplateWrite
obsah týchto premenných uložíme na konkrétne miesta do EEPROM.
void eeTemplateWrite()
{
Serial.println(F(""));
Serial.println(F("Write test"));
DUMP(demo_string);
eep.put(0x0000, demo_string);
DUMP(demo_struct);
eep.put(0x0010, demo_struct);
}
Vo funkcii eeTemplateRead
si ich zase načítame do lokálnych premenných, aby sme ich obsah mohli poslať na sériový port a porovnať ich s pôvodnými premennými. Všimnite si, že premennú read_string
som definoval väčšiu, než potrebujeme na pôvodný obsah. To je obvyklá taktika pri načítávaní reťazcov. Vyčleníme si buffer dostatočnej veľkosti a do neho si napríklad postupne čítame jednotlivé riadky z nejakého zdroja. Buffer musí mať toľko miesta, ako je najväčší textový reťazec, ináč by sme si pri naplnení premennej prepísali údaje cez koniec vyhradeného miesta a následky by boli pravdepodobne fatálne na ďalší beh programu.
void eeTemplateRead()
{
Serial.println(F(""));
Serial.println(F("Read test"));
dumpEEPROM(0, 0x0020);
char read_string[0x000f];
eep.get(0x0000, read_string);
DUMP(read_string);
_demo_struct read_struct;
eep.get(0x0010, read_struct);
DUMP(read_struct);
}
A tu už je len výpis z programu. Vidíte, že všetky premenné sa obsahovo rovnajú a to nám hovorí, že som tie šablónové funkcie napísal správne.
Press button '6' to start write test Press button '8' to start erase test Write test Dump: demo_string 0107 - 30 31 32 33 34 35 36 37 38 39 00 0123456789. Dump: demo_struct 0100 - 01 02 00 03 00 00 00 ....... Read test EEPROM DUMP 0x0 0x20 0 32 0x0000 30 31 32 33 34 35 36 37 38 39 00 FF FF FF FF FF 0x0010 01 02 00 03 00 00 00 FF FF FF FF FF FF FF FF FF Dump: read_string 08E5 - 30 31 32 33 34 35 36 37 38 39 00 FF FF FF FF 0123456789..... Dump: read_struct 08C5 - 01 02 00 03 00 00 00 .......
Zdrojový kód sa nachádza na GitHub.
07.04.2017