pin | 機能 | Arduino端子 |
---|---|---|
1 | CS (Chip Select) | Digital 10 |
2 | SK (Serial Data Clock) | Digital 13 |
3 | DI (Serial Data Input) | Digital 11 |
4 | DO (Serial Data Output) | Ditigal 12 |
5 | GND | GND |
6 | ORG | GND(8bit mode) |
7 | DC (Don't Connect) | - |
8 | Vcc | 5V |
3-wire EEPROMを使う
シリアル接続のEEPROMは大きく分けてI2C, SPI, そして3-wireとがあるようです。この中で結構入手が簡単なのがi2Cと3Wire。そこで3Wire方式のEEPROMをArduinoに接続してみました。使用したのはAtmelの93C86です。
ポイント
3-wireはSPIと良く似た規格ですが、大きな点として以下が異なってます。
・CS(SPIではSS)の極性。3WireではアクティブH。
・コマンドのビット数。SPIは8bit単位ですが、3-wireは半端。
・書き込み後のBusyステータス。3-wireはDO端子のレベルでチェック。
ArduinoのPlaygroundページにはSPIインターフェースのEEPROMを使った解説が出てますが3-wireは見当たりません。もちろんSPI用のプログラムをそのまま使うことはできませんが、若干の工夫で3-wire方式のEEPROMと接続できました。
接続
Arduinoとは、その名のとおり3本の信号線と、CS信号、そして電源を接続します。93C86はデータ長が8bitと16bitをORG端子で選択できますが、今回は8bit(ORG端子をLレベル)にしました。
まず初期設定
まずは使用する端子のI/O方向を設定します。さらにデータ入力端子は書き込み後のReady状態をチェックできるようにするためにプルアップしておきます(Hを書き込んでおく)。
SPCR(SPI Control Register)を設定する際、CPHAを1にセットする必要があります。これをしないと書き込んだデータを読み出す時にビットがずれます。
これで初期設定は完了。書き込みができるよう、EWENコマンドを送信しておきます。
cs_en(), cs_de()関数は、CS端子をH,Lにセットするだけです。
#define DATAOUT 11 //MOSI #define DATAIN 12 //MISO #define SPICLOCK 13 //sck #define SLAVESELECT 10 //ss #define CMD_READ 0x02 #define CMD_WEN 0x00 #define CMD_WRITE 0x01 void setup() { byte clr; pinMode(DATAOUT, OUTPUT); pinMode(DATAIN, INPUT); pinMode(SPICLOCK,OUTPUT); pinMode(SLAVESELECT,OUTPUT); digitalWrite(SLAVESELECT,LOW); //disable device digitalWrite(DATAIN,HIGH); SPCR = (1<<SPE)|(1<<MSTR)|(1<<CPHA); clr=SPSR; clr=SPDR; cs_en(); sendCommand(CMD_WEN,0x600); cs_de(); } void cs_en() { digitalWrite(SLAVESELECT,HIGH); } void cs_de() { digitalWrite(SLAVESELECT,LOW); }
コマンドの送信
この93C86のコマンドは、スタートビット1bit, オペコード2bit、アドレス11bit(8bitモードの場合)、合計14bitです。ArduinoのSPIインターフェースは8bit単位でしか送受信できませんが、3WireはCSがアクティブになった後の最初の“1”入力をスタートビットの見なし、その前のゼロがあれば無視されます。したがってこの14bitの前にゼロを追加して16bitとすればSPIインターフェースを使って簡単にコマンドが送信できます。
オペコードとアドレスを受け取り、コマンドとして組み立てて送信するサブルーチンを作成しました。8bitモードを想定しているのでスタートビットは12ピット目(13ビットシフト)、オペコードは11ビットシフトとなります。オペコードが00の時の使用法がお洒落じゃありませんが、まぁ、妥協。そうして組み立てた16bitのデータを上位8bit、下位8bitの順に送信します。
spi_transfer()関数はPlaygroundページのものと同一、送信するとともに受信します。
void sendCommand(byte cmd, word address) { word sendcmd; sendcmd = (1 << 13) | (cmd << 11) | address; spi_transfer((char)(sendcmd >> 8)); spi_transfer((char)(sendcmd & 0xff)); } char spi_transfer(volatile char data) { SPDR = data; // Start the transmission while (!(SPSR & (1 << SPIF))) // Wait for the end of the transmission { }; return SPDR; // return the received byte }
データの書き込み
書き込み時には、書き込む前にBusy/Readyをチェックするようにします。それがcheckReady()です。CSをイネーブルにした後、DI端子がLの場合はBusyなのでこれがHになるまで待ちます。Busyだった場合は一旦CSをLに落としてからHに戻す必要があります。ifとwhileが冗長ですが、仮に最初からBusyじゃなかった場合でもCSを一旦Lに落とすってのは美しくないので、「H(最初からReady)だったらそのまま、L(Busy)だったらHになるまで待ち、 Readyになったら後にCSを一旦Lに落としてHに戻す」という処理をしています。
Readyであれば、書き込みのコマンド(オペーコードとアドレス)、そして書き込むデータを送信するだけです。書き込みは1バイト(または1ワード)ずつアドレスを指定する必要があるようで、大きなデータを連続して書き込む事はできないようです。
void checkReady() { if(digitalRead(DATAIN) == LOW) { // busy while(digitalRead(DATAIN) == LOW) { ; } cs_de(); cs_en(); } } void writeData(word address,byte data) { cs_en(); checkReady(); sendCommand(CMD_WRITE,address); spi_transfer(data); cs_de(); }
データの読み出し
データの読み出し時、書き込んだ直後のデータを読み出す可能性を考慮し、書き込み時同様にBusy/Readyをチェックするようにしました。
あとは読み出しのオペコードとアドレスを送信し、ダミーのデータ(この例では0xff)を送信することで読み出したデータを受信します。連続するアドレスを読み出す事が可能です。その場合はCS端子をHに保ったまま、連続して次のデータを読み出すだけです(下記のルーチンにはその処理は含まれてません)。
byte readData(word address) { byte data; cs_en(); checkReady(); sendCommand(CMD_READ,address); data = spi_transfer(0xff); cs_de(); return(data); }
あとはこれらを組み合わせるだけです。
3-wireは高速なアクセスが可能だと思いますが、書き込み時の連続書き込みをサポートしていないからでしょうか、大容量のものが無いですね。93C86は16kbit、秋月で80円でした。Arduino用として考えると、ATMega328で1kByteのEEPROM内蔵なので、ちょっと微妙かな。