●I2C EEPROMモジュールを使う

Arduino内部のEEPROMでは容量が少ないため、外部にI2C EEPROMモジュールを取り付けて読み書きしたいと思います。
今回入手したEEPROMモジュール



このEEPROMモジュールをArduinoに対して次のように接続しました。

VCCをArduinoの5V
GNDをArduinoのGND
SDAをArduinoのA4
SCLをArduinoのA5


私の購入したデバイスは、24C256であり、容量は256kビットのサイズがあります。
256/8=32kバイト
8で割ると32kバイトであり、1kは1024のため、1024を掛けることにより、
32*1024=32768
32768個のバイトがあることがわかります。
つまり、0〜32767までのアドレスが使用可能となります。
もし最大容量を超えて、32768に読み書きした場合には、内部でマスクされるためアドレス0と同じ扱いになりループします。

モジュールの左側にジャンパピンが付いており、WPはWrite Protect端子でVCC側にすると書き込み禁止状態になります。
A0、A1、A2というチップのデバイスアドレス指定用に3ビットが用意されており、8個までI2Cバスに接続しても区別がつくようになっています。
A0をVCC、A1とA2をGNDに接続した場合にはデバイスアドレスは0x51になります。
また、A0、A1、A2をすべてGNDに接続した場合には0x50になります。

この、写真のEEPROMモジュールはA0をVCCに接続しているため、デバイスアドレスは0x51になります。


書き込みにはある程度の時間がかかり、デバイスによりかかる時間が変わります。大体書き込み後5msから10ms待機する必要があります。
書き込み回数にも寿命があり、デバイスにより変わりますが、10万回以上は保証されているようです。


▼1バイトの読み込み

デバイスアドレス0x50のEEPROMモジュールのアドレス0x00に対して1バイトの値を読み出しています。

#include <Wire.h>

void setup() {
  Wire.begin();
  Serial.begin(9600);

  int i2c_address = 0x50;
  unsigned int address = 0x00;

  Wire.beginTransmission(i2c_address);
  Wire.write((unsigned int)(address >> 8));
  Wire.write((unsigned int)(address & 0xFF));
  Wire.endTransmission();
  Wire.requestFrom(i2c_address, 1);
  while (Wire.available()) {
    Serial.print(Wire.read(), HEX);
  }
}

void loop() {
}


▼1バイトの書き込み

デバイスアドレス0x50のEEPROMモジュールのアドレス0x00に対して1バイトの値(0xAA)を書き込んでいます。

#include <Wire.h>

void setup() {
  Wire.begin();
  Serial.begin(9600);

  int i2c_address = 0x50;
  unsigned int address = 0x00;

  Wire.beginTransmission(i2c_address);
  Wire.write((unsigned int)(address >> 8));
  Wire.write((unsigned int)(address & 0xFF));
  Wire.write(0xAA);
  Wire.endTransmission();
  delay(5);//書き込み完了まで待機
}

void loop() {
}


▼連続したデータの読み出し

read操作を行うとEEPROM内部の参照アドレスが自動でインクリメントされるため連続して値を読み出す事ができます。
次のプログラムでは1バイトごとにデータを読み出すのを繰り返しています。
read操作はそんなに時間がかからないため1バイト毎に読み出したほうが無難です。

#include <Wire.h>

void setup() {
  Wire.begin();
  Serial.begin(9600);

  int i2c_address = 0x50;
  unsigned int address = 0x00;

  Wire.beginTransmission(i2c_address);
  Wire.write((unsigned int)(address >> 8));
  Wire.write((unsigned int)(address & 0xFF));
  Wire.endTransmission();
  for (int i = address; i < address + 64; i++) {
    Wire.requestFrom(i2c_address, 1);
    while (Wire.available()) {
      Serial.print(i, HEX);
      Serial.print(" : ");
      Serial.println(Wire.read(), HEX);
    }
  }
}

void loop() {
}

1バイトごとに繰り返しで複数のデータを読み出せました。
それでも、一度にデータを読み出すのをあきらめられないので、
次のように一度に複数のデータを読み出すように改造してみます。
#include <Wire.h>

void setup() {
  Wire.begin();
  Serial.begin(9600);

  int i2c_address = 0x50;
  unsigned int address = 0;

  Wire.beginTransmission(i2c_address);
  Wire.write((unsigned int)(address >> 8));
  Wire.write((unsigned int)(address & 0xFF));
  Wire.endTransmission();
  //for (int i = address; i < address + 64; i++) {
  int i=address;
    Wire.requestFrom(i2c_address, 32);
    while (Wire.available()) {
      Serial.print(i++, HEX);
      Serial.print(" : ");
      Serial.println(Wire.read(), HEX);
    }
  //}
}

void loop() {
}

このようにすると、ある種の問題が発生します。
赤文字の部分の指定で一度に32個の値を要求していますが、この数をもっと増やしても、たぶん一度に32個しか読み出せないと思います。
理由は、I2cを扱うWireクラスのバッファサイズが32バイトで作られている為で、バッファを超えたデータは捨てられているためです。
これは、I2cの問題ではなくてWireクラスが原因です。

良い点としては、readにはwriteみたいにページ単位の問題はないのでページ境界をまたいでも問題なく読み出せます。


▼連続したデータの書き込み

write操作を行うとEEPROM内部の参照アドレスが自動でインクリメントされるため連続して値を書き込む事ができます。
次のプログラムでは1バイトごとにデータを書き込んで、それを繰り返しています。
1バイト書き込む毎に書き込み完了まで待機しているために少々時間がかかりますが、予想外のバグが入り込む余地が少なく確実です。

#include <Wire.h>

void setup() {
  Wire.begin();
  Serial.begin(9600);

  int i2c_address = 0x50;
  unsigned int address = 0x00;
  for (int address = 0; address < 128; address++) {
    Wire.beginTransmission(i2c_address);
    Wire.write((unsigned int)(address >> 8));
    Wire.write((unsigned int)(address & 0xFF));

    Wire.write(0x00);  // 0を書き込む

    Wire.endTransmission();
    delay(5);//書き込み完了まで待機
  }
  Serial.println("end");
}

void loop() {
}

次に一度に沢山のデータを書き込むようにしてみます。
一度に書き込む場合にはページ単位でアクセスする必要があり、 書き込み中に現在のページの一番後ろを超えて書き込むと、現在のページの先頭に書き込んでしまうので注意が必要です。
ページサイズは64バイトであり、アドレスで考えると 0〜63 64〜127 などの64の区切りで書き込む必要があります。

#include <Wire.h>

void setup() {
  Wire.begin();
  Serial.begin(9600);

  int i2c_address = 0x50;
  unsigned int address = 0x00;

  Wire.beginTransmission(i2c_address);
  Wire.write((unsigned int)(address >> 8));
  Wire.write((unsigned int)(address & 0xFF));


  for (int i = 0; i < 16; i++) {
    Wire.write(0x00);  // 0を書き込む
  }

  Wire.endTransmission();
  delay(5);//書き込み完了まで待機
  Serial.println("end");
}

void loop() {
}

このプログラムでは16個の値を一度に書き込んでいます。
EEPROMのページは64個のため一度に64個の値を書き込みたくなりますが、 一度に64個の値は書き込めません、書き込めるのは30個の値までです。
なぜなのか、プログラム中のWire.writeを数えてみると、アドレス指定で2個、値の書き込みで30個の全部で32個です。
つまり、I2cを扱うWireクラスのバッファサイズの32バイトを全部使っているからだろうと推測できます。
ここでも、readと同じバッファの問題が出てきましたが、とりあえずは30個の値は一度に書き込める事がわかりました。


▲トップページ > マイコンなど