Čtení a zápis do EEPROM pomocí I²C

V tomto článku si ukážeme, jak pomocí Arduino Uno číst a zapisovat paměti typu EEPROM, které komunikují přes sběrnici I²C. Velmi stručně tuto sběrnici představíme, popíšeme jak se EEPROM adresuje a nakonec si ukážeme jak zapsat a přečíst několik bajtů.

Arduino Uno obsahuje vnitřní EEPROM paměť, se kterou se dá pracovat pomocí knihovny EEPROM. Pokud ale chceme pracovat s pamětí větší, nebo chceme číst z více pamětí, můžeme použít následující postup.

Uno má na 4. a 5. analogovém pinu vyvedenu sběrnici I2C, která se dá ovládat pomocí knihovny Wire. Tato knihovna obsahuje množství funkcí, díky kterým lze Arduino používat jak v režimu master, tak režimu slave a je napsána dostatečně obecně, aby se dalo komunikovat s jakýmkoliv zařízením, nejenom s EEPROM. Při používání se ale najde pár problémů, o kterých se dokumentace nezmiňuje.

I²C

Jedná se o sériovou synchronní sběrnici, která vyžaduje 2 vodiče — jeden hodinový, označovaný jako SCL a druhý datový SDA. Na jedné sběrnici může být jedno zařízení typu master, které řídí hodinový vodič, a několik zařízení typu slave. Každý slave má adresu, pomocí které jej může master identifikovat.
Více informací o této sběrnici lze nalézt například na anglické Wikipedii — I²C.

EEPROM

Sériová, elektricky mazatelná paměť, označovaná jako EEPROM využívá již zmíněné sběrnice pro komunikaci s okolím. Typ, který budeme dále používat nese označení 24C01, což znamená, že se jedná o 1kb paměť.

Jednoduchým výpočtem zjistíme, že paměť má 128B (1024/8) a každý bajt se tedy adresuje pomocí 7 bitů (když master zařízení odesílá adresu, odešle 8 bitů, MSB se pak ignoruje).

V datasheetu je uvedeno několik možných způsobů zápisu a čtení — čtení/zápis jednoho bajtu, zápis celé stránky (pro 1kb a 2kb paměti má stránka velikost 8B, větší paměti pak mají 16B) a sekvenční čtení. Dále si ukážeme zápis a čtení jedinohé bajtu, další zmiňované režimy jsou podobné, viz datasheet.

Adresa zařízení

Jak již bylo uvedeno, každé slave zařízení na I²C sběrnici má unikátní adresu. EEPROM 24C01 je v pouzdru DIP8 a má pro tuto adresu vyhrazeny 3 piny označené jako A0-A2.
Pokud chceme z paměti cokoliv číst/zapisovat, musíme jako první tuto adresu odeslat. Ta je ve tvaru (platí pouze pro 1kb a 2kb verzi):

1 0 1 0 A2 A1 A0 R/W

LSB bit určuje, jestli chceme data číst nebo zapisovat. Pro zápis se nastaví na log. nulu, pro čtení na log. jedničku.

Adresa paměťové buňky

Paměť EEPROM obsahuje registr nazvaný address counter, který si drží adresu paměťové buňky, se kterou se pracovalo naposledy (pouze dokud paměť neodpojíme od napájení, potom se registr smaže). Pokud tedy chceme číst po sobě jdoucí bajty, nemusíme posílat jejich adresu.

Pokud ale chceme číst bajty, které nejdou uloženy za sebou, musíme provést tzv. dummy write, tedy zahájíme komunikaci jak by se jednalo o zápis dat (adresa zařízení, R/W bit nastaven na nulu, nakonec adresa buňky a konec) a poté opět odešleme adresu zařízení a začneme číst.

Knihovna Wire

Pokud chceme s Arduinem pracovat s I²C, musíme v kódu ve funkci setup() toto rozhraní aktivovat:

Wire.begin();

Poté již můžeme kdykoliv zahájit komunikaci pomocí funkce:

Wire.beginTransmission(adresa);

kde adresa je adresa zařízení se kterým chceme komunikovat. Zde je ale nutné upozornit na fakt, o kterém se oficiální dokumentace nezmiňuje — knihovna si sama automaticky nastaví LSB bit označený jako R/W (viz text výše). Bajt s adresou zařízení, který předáme funkci beginTransmission() je nejdříve posunut o jeden bit vlevo a LSB je nastaven pomocí OR:

address = (address << 1) | READ_WRITE;

To znamená, že pokud chceme komunikovat s EEPROM, která má například adresu 010, funkci beginTransmission() předáme B01010010, nebo 0x52.

Poté provedeme čtení/zápis pomocí funkcí Wire.receive(), respektive Wire.send(data) a komunikaci ukončíme Wire.endTransmission().

Nutno poznamenat, že samotná komunikace nezačne, dokud nezavoláme Wire.endTransmission(). Do té doby se data ukládají do paměti.

Opět další důležitá informace, která není v oficiální dokumentaci uvedena: Tato paměť má velikost pouze 32B. Pokud chcete odesílat/přijímat více dat, musíte je rozdělit na bloky o této délce. V opačném případě se nic neodešle a knihovna bez jakéhokoliv upozornění ukončí přenos.

Ukázkový kód a zapojení

/**
 * i2c-eeprom
 * Zapisuje a cte data z EEPROM pres seriove rozhrani I2C,
 * s pomoci knihovny WIRE.
 *
 * Arduino Uno ma:
 *  - SDA na analog pinu 4,
 *  - SCL na analog pinu 5.
 */

#include <Wire.h>

/**
 * Funkce slouzi pro zapis jednoho bajtu na danou adresu.
 */
void eeprom_i2c_write(byte address, byte from_addr, byte data) {
  Wire.beginTransmission(address); // Zahajime prenos
  Wire.send(from_addr); // Adresa pametove bunky
  Wire.send(data);
  Wire.endTransmission();
}

/**
 * Funkce precte jeden bajt na dano adrese.
 */
byte eeprom_i2c_read(int address, int from_addr) {
  Wire.beginTransmission(address); // Zahajime prenos
  Wire.send(from_addr);
  Wire.endTransmission();

  Wire.requestFrom(address, 1); // Cteme jediny bajt.
  if(Wire.available())
    return Wire.receive();
  else
    // Toto neni idealni zpusob, jak oznamit chybu.
    return 0xFF;
}

void setup() {
  // Iniciuje I2C bus jako master.
  // Pro Atmel aktivuje interni pull-upy,
  // takze neni nutne v obvodu mit dalsi.
  Wire.begin();

  // Seriove spojeni s PC, prectena
  // data budeme odesilat sem.
  Serial.begin(9600);

  for(int i = 0; i < 10; i++) {
    eeprom_i2c_write(B01010000, i, 'a'+i);
    delay(100);
  }

  Serial.println("Writen to memory!");
}

void loop() {
  for(int i = 0; i < 10; i++) {
    byte r = eeprom_i2c_read(B01010000, i);
    Serial.print(i);
    Serial.print(" - ");
    Serial.print(r);
    Serial.print("\n");
    delay(1000);
  }
}

Zapojení obvodu je následující:

Schéma zapojení EEPROM a Arduino Uno.
Schéma zapojení EEPROM a Arduino Uno.

Grafické znázornění přímo na Arduinu a PDF verze schématu jsem uložil na server Fritzing.org.

Další informace

Při práci s EEPROM jsem čerpal informace z článku Using Arduino with an I2C EEPROM (popisuje stejné téma, ale pracuje s pamětí 24C256, takže k adresaci paměťové buňky používá 2 bajty), ze zdrojových kódů knihovny Wire a nakonec z datasheetu obvodu AT24C01A.

Články v sérii