Jak si usnadnit ovládání externí EEPROM pomocí šablon

Zápisník experimentátora

Hierarchy: Externá EEPROM

V tomto článku navážeme na předchozí, ve kterém jsme si vysvětlili základní použití externí EEPROM. Navrhneme si třídu, která nám umožní pohodlně ukládat do EEPROM libovolný objekt. Využijeme přitom šablony jazyka C++ a doplníme do třídy dvě funkce, které budou plnit tento úkol. Příklad bude pro 24LC16B, ale až na drobnosti bude tento návod univerzální pro libovolnou EEPROM.

Použité součástky

Na tento experiment stačí pouze pár součástek:

  • Arduino Pro Mini (link) - Použil jsem ho proto, že se vejde i na nejmenší nepájivé pole.
  • Breadboard 400 Holes (link) - Na něm máme dost místa na dva mikrospínače.
  • Převodník CP2102 USB to Serial (link) - Převodník slouží k naprogramování Arduina.
  • Mikrospínače (link, link) - Potřebujeme dva kusy. Do zkušebního pole se dají použít i čtyřpinový i dvojpinové. Někdy jsou piny dlouhé, nebo mají podivné výstupky. Nebojte se ty konce odstřihnout, aby se lépe daly zastrčit.
  • EEPROM 24LC16B

Arduino je upraveno tak, že má na I2C (nachází se netradičně na extra pinech uprostřed Arduino) připájenou pin lištu s otvory, aby se pomocí dvou propojovacích vodičů dalo snadno propojit s EEPROM.

Dvě tlačítka se používají ke spuštění konkrétních testů a jsou připojeny na piny 6 a 8 na Arduinu. EEPROM je připojena k napájení a adresové piny A0, A1 a A2 jsou nezapojené. To platí ale pouze pro tento konkrétní typ EEPROM. Ostatní musí obvykle připojit tyto piny na GND nebo na VCC, čímž si nastaví I2C adresu. Je třeba si vždy zkontrolovat datasheet ke konkrétnímu typu a nastavit to podle něj.

Příklad

Třídy jazyka C++ nám umožňují používat nejen stávající třídy, ale díky dědičnosti si můžeme z existujících tříd vytvořit nové, které doplníme o nové funkce. V tomto příkladu si od třídy extEEPROM (použité v předchozím příkladu) odvodíme třídu TemplatedEEPROM, která bude obsahovat dvě šablonové funkce.

  • get - Pomocí funkce naplníme obsah proměnné libovolného typu uloženým obsahem z EEPROM.
  • put - Pomocí funkce uložíme obsah proměnné libovolného typu do EEPROM.

V příkladu potom použijeme dvě testovací funkce na otestování našich šablonových funkci. Pojďme rovnou na komentovaný příklad, kde uvidíme všechny funkce. Nebudu komentovat obsah souboru dump.h. Funkce, které se v něm nacházejí, jsou popsány například v článku Dump obsahu proměnné na sériový port.

Těmito dvěma řádky říkáme kompilátoru, které další soubory chceme do kompilace zahrnout. V našem případě to je knihovna pro externí EEPROM a pomocné výpisy na sériový port.

#include <extEEPROM.h>
#include "dump.h"

Definováním nové třídy v hlavním programu jsem zmátl parser Arduina, který za vás udelá formální deklarace funkcí a proto jsem musel všechny použité funkce deklarovat ručně. Ve světě Arduina to musíme málokdy dělat, ale toto byl asi ten příklad. Smysl této operace v C++ je jednoduchý. Pokud je funkce použita dříve, než je známa, kompilátor vyhodí chybu. Proto je potřebné funkce alespoň formálně popsat, co kompilátoru na překlad stačí a linker si už s vytvořením výsledného programu snadno pořadí.

void dumpEEPROM(uint32_t startAddr, uint32_t nBytes);
void eeErase(uint8_t chunk, uint32_t startAddr, uint32_t endAddr);
void eeTemplateWrite();
void eeTemplateRead();

Takto se zapisuje odvozená třída. První funkce je konstruktor, který je v tomto případě pouze zopakováním původního konstruktora z knihovny externí EEPROM. Ale musí tam být, protože jinak bych nemohl definovat proměnnou daného typu. A když vytváříme odvozené typy, skoro vždy přidáváme i vlastní kód. Tato funkce bez kódu je spíše výjimkou.

Zbývající dvě funkce jsou šablonové. To znamená, že je to jen šablona skutečné funkce s konkrétním typem, který za nás vytvoří kompilátor. Kód není složitý. Pouze si přetypujeme typ T na uint8_t (adresu v RAM reference na pointer), vypočítáme velikost v bajtech funkcí sizeof a po jednom znaku ji pošleme do EEPROM. Pointer this jsem použil kvůli přehlednosti, abyste viděli, které funkce třídy používám na zápis. V C++ to není povinné tam dát, ale jsou jazyky, které 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 protože už máme třídu definovanou, můžeme si definovat proměnnou typu TemplatedEEPROM, která umí používat i funkce původní třídy, i naše nové doplněné.

TemplatedEEPROM eep(kbits_16, 1, 16);

Ve funkci loop pouze kontrolujeme stisk tlačítka a podle toho zavoláme funkce.

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);
  }
}

Abychom měli nějaké proměnné, definoval jsem jeden textový řetězec a jednu strukturu a naplnil jejich konkrétními hodnotami.

char demo_string[] = "0123456789";
struct _demo_struct {
  int8_t a;
  int16_t b;
  int32_t c;
} demo_struct = {1, 2, 3};

Ve funkci eeTemplateWrite obsah těchto proměnných uložíme na konkrétní místa 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);
}

Ve funkci eeTemplateRead si je zase načteme do lokálních proměnných, abychom jejich obsah mohli poslat na sériový port a porovnat je s původními proměnnými. Všimněte si, že proměnnou read_string jsem definoval větší, než potřebujeme na původní obsah. To je obvyklá taktika při načítání řetězců. Vyčleníme si buffer dostatečné velikosti a do něj si například postupně čteme jednotlivé řádky z nějakého zdroje. Buffer musí mít tolik místa, jako je největší textový řetězec, jinak bychom si při naplnění proměnné přepsali údaje přes konec vyhrazeného místa a následky by byly pravděpodobně fatální na další běh 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 tady už je jen výpis z programu. Vidíte, že všechny proměnné se obsahově rovnají a to nám říká, že jsem ty šablonové funkce napsal správně.

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

Zdrojový kód sa nachází na GitHub.


22.10.2017


Menu