最終更新日: 2011年2月20日

I2C EEPROMを使う

I2C方式のEEPROMには比較的大容量なものがあり、何かと使えそう。ということでArduinoにつないでみました。使ったのはMicrochipの24FC1025です。

ポイント

Arduinoには標準のI2C通信のライブラリ(Wireクラス)があり、これを使えば簡単にアクセスできます。しかしこのライブラリを使う上で若干のデメリットがあります。
まずこのライブラリ、通信はbeginTransmission()で始まりendTransmission()で終わりますが、実際の通信はendTransmission()にて実行されます。つまりその間にsend()で送信したデータはバッファされるだけです。 さらに実際の送信時、さらに別のバッファにコピーされます。送信する量の2倍のRAMを消費し、なんだか無駄です。ちなみにこれらのバッファは標準では32バイトですが、24FC1025のページ書き込みは一度に128バイトまで書き込めます。これに合わせてライブラリのバッファ容量を広げると、合計256バイトもRAMを消費してしまうことになってしまいます。
そして、このEEPROMは書き込みがビジーかどうか、デバイスアドレスを送信した直後のACK/NOACKにて判断します。しかしこのライブラリを使うと、この結果がendTransmission()を実行した後でしかわかりません。もしここでビジーという事がわかれば再度beginTransmission()から実行しなおさなければなりません。これまたちょっと無駄です。
ArduinoサイトのplaygroundにもI2C EEPROM用のライブラリが掲載されていますが、これもWireクラス利用しているので同じです。
という事で、これらのライブラリを使わずにアクセスしてみました。

接続

基本的な接続はArduinoとは2本の信号線と電源だけです。SDA,SCLは1kΩでプルアップします。A0, A1は共に0(GND)に接続しました。A2は"1"にする必要があるので5Vに接続します。

pin 機能 Arduino端子
1 A0 GND
2 A1 GND
3 A2 5V
4 Vss GND
5 SDA Analog 4
6 SCL Analog 5
7 WP(Write Protect) GND
8 Vcc 5V

基本関数

まずはI2Cにて通信するための基本的な関数を作成しました。
i2c_getStatus()は単にステータスを受信するだけですが、デバッグを容易にするためにひとつの関数にしました。結果をグローバル変数に保存するのはちょっと美しくないですが…。
i2c_init()は初期設定。ライブラリの初期設定部分をパクってますが、割り込みは利用していません。転送速度は400kHzにしました。
i2c_sendStartCondition()はスタートコンディションを送信する関数です。このあたりはATMega328のデータシートに記載されている例を利用しています。
i2c_sendData(), i2c_receiveData()はそれぞれ1バイト送信・受信する関数です。受信側は連続して読み出す時にfContinueをtrueにすると受信後にACKを発行するようにしています。これによってシーケンシャルリードが可能になります。
i2c_start()はスタートコンディションを送信後、コントロールバイト(スレーブアドレスとR/Wフラグ)を送信します。この結果がNOACKだった場合は書き込みビジーなので、スタートコンディションから再送します。

sketch

byte i2c_status;

void i2c_getStatus() {
  i2c_status = TWSR & 0xF8;
  return ;
}

void i2c_init() {
  // pull up
  sbi(PORTC, 4);
  sbi(PORTC, 5);
  // initialize twi prescaler and bit rate
  cbi(TWSR, TWPS0);
  cbi(TWSR, TWPS1);
  //  TWBR = ((CPU_FREQ / TWI_FREQ) - 16) / 2;
  TWBR = ((16000000 / 400000) - 16) / 2;
  // enable twi module, acks
  TWCR = _BV(TWEN) | _BV(TWEA);
}

byte i2c_sendStartCondition() {
  // send Start Condituin
  TWCR = (1<<TWINT)|(1<<TWSTA)| (1<<TWEN) | (1<<TWEA);
  // wait for TWINT Flag set. 
  while (!(TWCR & (1<<TWINT))) ;
  i2c_getStatus();  
  if ((i2c_status == 0x08) || (i2c_status == 0x10)) {
     // receive ACK
     return true;
  }
  return false;
}

byte i2c_sendData(byte data) {
  // send 1 byte
  TWDR = data;
  TWCR = (1<<TWINT) | (1<<TWEN);
  // wait for send
  while (!(TWCR & (1<<TWINT))) ;
  
  i2c_getStatus();
  // check status
  if (i2c_status != 0x28) {
    // receive ACK
    return false;
  }
  return true;
}

byte i2c_receiveData(byte *pData, bool fContinue = false) {
  // wait for receive

  if(fContinue) {
    TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWEA);    
  } else {
    TWCR = (1<<TWINT) | (1<<TWEN) ;        
  }
  while (!(TWCR & (1<<TWINT))) ;
  *pData = TWDR;
  i2c_getStatus();
  if(i2c_status == 0x50) return true;
  return false;
}

byte i2c_start(byte sla, byte mode) {
  byte data;
  data = sla << 1 | mode;
  
  do {
    if(!i2c_sendStartCondition()) return false;
    i2c_sendData(data);
  } while ((i2c_status == 0x20) || (i2c_status == 0x48)); // repeat when receive not ack
  return true;
}

byte i2c_stop() {
  // send Stop condition
  TWCR = (1<<TWINT)|(1<<TWSTO)| (1<<TWEN);
}

データの書き込み

書き込み時には、まずコントロールバイトを送信後、アクセスするアドレスを上位8ビット、下位8ビットの順に送信、さらに書き込むデータを送信します。ページ書き込みの場合はi2c_sendData()を書き込みたいバイト数だけ繰り返すだけです()。最後にi2c_stop()を呼び出してストップコンディションを送信します。
ここではバンクを0固定にしているので、1024kbit(128kByte)のメモリなのに64kByteしかアクセスしていません。

Sketch
#define MEM0 0x50
#define I2C_WRITE 0
#define I2C_READ 1

void sendAddress(word address) {
  i2c_sendData(address >> 8);
  i2c_sendData(address & 0xff);
}

void eeprom_writeData(word address,byte data)
{
  i2c_start(MEM0,I2C_WRITE);
  sendAddress(address);
  i2c_sendData(data);
  i2c_stop();
}

データの読み出し

読み出し時は、書き込み時と同様にコントロールバイトとアクセスするアドレスを送信し、それから再度コントロールバイトをR/WフラグをRで送信し、データを受信します。シーケンシャルリードの場合は、最後のデータ以外では i2c_receiveData(&data,true) と呼び出して読み出します。

Sketch
byte eeprom_readData(word address) 
{
  byte data;
  i2c_start(MEM0,I2C_WRITE);
  sendAddress(address);
  i2c_start(MEM0,I2C_READ);
  i2c_receiveData(&data);
  i2c_stop();
  return(data);
}

ということでライブラリを使わずにも簡単にアクセスできました。

 



home | Elec top

inserted by FC2 system