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