最近お気に入りのArduino DUEですが、UNOと違ってデータ記録用の不揮発メモリEEPROMを搭載していません。
そんなに頻繁に使い道があるわけでは無いのだけど、例えば、「#12-2 DUE+タッチパネル」みたいに、タッチパネルのタッチ位置と画面上のドット位置を校正して使いたい場合、校正データを何かしらの不揮発メモリに記録しておかないと、電源を投入したりリセットする度に一々校正し直さなくてはならず、とても面倒です。
巷には、シリアルEEPROMと呼ばれるチップが出回っていて、これを入手して接続するとDUEでも不揮発メモリを使えるようになります。そこでふと考えました。「このチップ、328duinoでエミュレートできるのでは?」これが、今回テーマのきっかけです。
非同期シリアル通信で接続して、内部の不揮発メモリEEPROMにデータを書き込み/読み出しするメモリチップの総称です。不揮発メモリとして、EEPROMの代わりにFlashメモリを使ったシリアルFlashメモリなんて言うのもあります。
今回フォーカスしたのは、1バイト単位で読み/書き可能なEEPROMの方です。一般的には、EEPROM→小容量、Flash→大容量なので、すごく大きな記憶領域が必要な場合にはFlashを選択せざるを得ません。
ただし、容量が大きくなると、その記憶データや領域の管理が煩雑となるため、大容量の不揮発メモリが必要なのであれば、SDカードの使用を推奨します。SDカードなら、SDライブラリを使ってFAT32のファイルシステムとして扱うことが出来、大容量のデータを比較的容易に管理・操作することが可能だからです。
と、言う事で、今回のテーマはシリアルEEPROMです。数100〜数10K[バイト]程度の記憶領域で、タッチパネルの校正データや、内蔵RC発振器の校正データなど、ちょっとしたデータを不揮発に保存したい用途に使います。
いくつかシリアルEEPROMチップの仕様を調べてみたので、以下表にまとめておきます。
型式 | サイズ | ページ | I/F | 備考 |
23K256 | 32Kバイト | 32バイト | SPI | |
23LC512 | 64Kバイト | 32バイト | SPI | |
24C02 | 256バイト | 8バイト | I2C |
アドレス指定が8ビット。 256バイトを超えるアドレスは、 I2Cスレーブアドレスで指定。 |
24C04 | 512バイト | 16バイト | I2C | |
24C08D | 1Kバイト | 16バイト | I2C | |
24C128 | 16Kバイト | 64バイト | I2C | アドレス指定が16ビット。 |
24C256 | 32Kバイト | 64バイト | I2C | |
ATmega328 | 1Kバイト | 4バイト | − | 対比用 |
シリアルと言ってもUARTでは無く、SPIまたはI2Cでインターフェイスします。最後の行にATmega328(Arduino)のEEPROMについて記載しましたが、専用チップに比べると、容量は少なめの1Kバイトです。サイズ的には24C08D相当となります。
前節のシリアルEEPROMをArduinoでエミュレートするには、Arduino側がSPIスレーブまたはI2Cスレーブとなる必要があります。SPI通信方式には、マスタにウェイトをかける機構が存在しないため、ArduinoをSPIスレーブとして使うのはタイミング的にシビアで難しいので、I2Cを選択します。
※I2Cだと、スレーブ側の準備が整うまでクロックSCLをLOWに保持することで、マスタ側の通信をサスペンドすることができます。
I2C EEPROMの中では小容量の23C08DがATmega328と同じ1KバイトのEEPROMですが、備考欄に書いたように8ビットのアドレス指定では1Kバイトのメモリ空間(10ビット)に対して2ビット足りないため、この2ビット分をI2Cスレーブアドレスで代替しています。つまり、1チップで4つのスレーブアドレスを使っています。
通信トラフィックを最小にして使えるという意味で、アイディアは良いのですが、ArduinoのWireライブラリではI2Cスレーブアドレスを複数指定することが出来ません。このため、インターフェイス(通信仕様)としては、24C128/24C256と同じ、16ビットアドレス指定を選択します。
以下に目標仕様をまとめてみます。
目標仕様:
328duinoと言っても、ただの単体ATmega328です。328duinoの構築には単体のATmega328チップが必要となります。「#9-5 ブレッドボードにArduinoっぽい物」を参照の上、328duinoを構築してください。
「#8-1 Pi ZeroWのI2C通信」で作成した、Pi ZeroWをI2Cマスタとして使い、UNOをI2CスレーブとしたSMBusのテストプログラム(test_smbus.ino)が、ほぼ、ジャストミートです。ちょっと改造したら使えそう。
test_smbus.inoでは、UNOのRAM上に確保した256バイトの配列データにアクセスしたのですが、アクセス先をEEPROMに変更して、サイズを1Kバイトに拡張すればほぼ完成です。今回のI2CアクセスプロトコルはSMBusではないので、その辺りも若干修正が必要となります。
また、デバッグ用にシリアルモニタからコマンドを受け付けているのですが、実使用状態ではシリアルモニタ無しで自立する様にしておきます。シリアルモニタからのコマンド仕様はこちらに準じてあります。
そう言えば、「#8-1 Pi ZeroWのI2C通信」では、UNOの処理能力に律速されて、アクセス速度(SCLの周波数)等に制約がありました。UNO @16MHzでそうなので、328duino @8MHz,3.3Vで使う場合は、更にアクセス速度が制限されるでしょう。
Pi ZeroW&UNOの時には、SCLを75kHzくらいまで落とさないとアクセスできませんでした。あるいは、ダミーライトとリードの間にディレイ(待ち時間)を持たせることで、SCL=400kHzでもアクセス可能なことを確認済みです。
今回は、I2Cマスタ側にDUEを使いますが、恐らく同じ状況でしょうから、後ほど実験で確かめたいと思います。
回路としては、ほぼ、328duino(第二形態)その物、なので、特段新しいことはありません。I2Cスレーブアドレスの指定端子(SA1,SA0)や書き込み禁止端子(WP)が接続されなかった時にデフォルトでLOWと判定されるように、高抵抗接地する回路が追加になるだけです(下図)。
※SA1,SA0,WP端子が未接続な場合にデフォルトLOWとするのは、AT24C256チップの仕様に準じています。既存チップのエミュレートを考えないならば、328duinoの場合、デフォルトHIGHとしてpinMode()関数でINPUT_PULLUP指定しておくのが一番楽(回路追加不要)です。
328duinoにプログラムを書き込む時には、FTDI(USB⇔UART変換モジュール、下図)等を使ってシリアル接続(DTR,RXI,TXO)します。Arduino as ISPなどを使って書き込む場合には、SPI接続(RES,SCK,MISO,MOSI)して使います。
328duinoは第二形態なので、「328duino (RC 8MHz)」のブートプログラム(とヒューズ)を書き込んでおいてください。
なお、最終的にはDUE @3.3Vに接続して使うことを目的としますので、328duinoへ供給する電源電圧Vcc=3.3Vです。そのままFTDIを接続する場合には、I/Oレベルを合わせるため、FTDIの電圧ジャンパ:3.3V側して使ってください。
以下にArduinoをシリアルEEPROMにするプログラムをリンクしておきます。
完成版プログラム:ser_eeprom.ino
328duinoへコンパイル&書き込んでおいてください。
続いて、DUE側で走らせるテスト用プログラムを以下にリンクします。
テストプログラム:test_eeprom.ino
こちらはDUEへコンパイル&書き込んでおいてください。実験はDUEのシリアルモニタを使います。DUEシリアルモニタから受け付けるコマンド仕様はこちらです。
最後にDUEとシリアルEEPROM化328duinoを以下の通り接続してください。
DUE | 接続 | シリアルEEPROM化328duino | |
3.3V | → | Vcc | |
GND | ⇔ | GND | |
D20(SDA) | ⇔ | SDA(PC4_SDA_A4) | |
D21(SCL) | ⇔ | SCL(PC5_SCL_A5) | |
× | SA0(PC0_A0) |
オプション: I2Cスレーブアドレス指定(SA1,SA0) 書き込み禁止(WP) |
|
× | SA1(PC1_A1) | ||
× | WP(PC2_A2) |
※SA1,SA0,WPはオプションです。通常は開放(非接続、N.C.)としてください。SA1,SA0を設定した場合、DUE側のプログラムで定数i2c_slv_adrを変更する必要があります。また、WP=HIGHに設定すると、EEPROMへの書き込みが禁止されます。
328duinoを3.3V駆動するので、DUEと直結できます。5VのUNOやPro Miniなどで実験する場合には、I/Oレベルが合わないので、間にL/Sを挟んでください。
以下、ミニブレッドボード上に構築したシリアルEEPROM化328duinoと、DUEを使った接続実験中の様子です。
以降、DUEのシリアルモニタを開いて実験します。
実験サンプル:【 】内はシリアルモニタからの送信データ
【i2cd】
OK+ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +a +b +c +d +e +f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: 50 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
OK: i2c detect
I2Cバスに接続されたデバイスをスキャンするコマンド【i2cd】を実行しました。I2Cアドレス=0x50に接続されているのが、シリアルEEPROM化328duinoです。
ここで、SA1,SA0端子の機能もテストしておきました。SA1,SA0をVcc=3.3Vに吊ることで、【i2cd】コマンドで表示されるI2Cアドレスが0x50〜0x53で変化するのを確かめました(SA1,SA0の設定を変更したら、328duinoのリセットが必要です)。
※328duinoにはリセットボタンを用意していないので、ジャンパ線などで#1ピンのRESET端子をGNDに接触させてリセットしてください。
実験サンプル:【 】内はシリアルモニタからの送信データ
【wr,10,aa,bb,cc,dd】
OK+ EEPROM[0x10]=0xAA
OK+ EEPROM[0x11]=0xBB
OK+ EEPROM[0x12]=0xCC
OK+ EEPROM[0x13]=0xDD
OK: wrote 4 data
【rd,13】
OK: read [0x0013]=0xdd
【dump,10,16】
OK+ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +a +b +c +d +e +f
0010: aa bb cc dd ff ff ff ff ff ff ff ff ff ff ff ff
OK: dump 0x0010 ~ 16
アドレス0x10〜0x13に0xaa,0xbb,0xcc,0xddを書き込んで、読み出してみました。ちゃんと書き込めています。
ここで、WP端子の機能もテストしておきました。WP端子をVcc=3.3Vに吊って【wr】コマンドを実行しても、データが書き込まれないことを確認しました。
機能的には問題なしです。
「#8-1 Pi ZeroWのI2C通信」では、UNO @16MHzの処理能力に律速されて、アクセス速度(SCLの周波数)等に制約がありました。今回はさらに周波数の遅い328duino @8MHzなので、I2Cの通信速度について確認しておきます。
実験用として、プログラム(test_eeprom.ino、DUE用)には以下の#defineマクロを用意してあります。
以上のマクロを変更しながら動作を確認した結果を以下表にまとめておきます。
I2C_RD_RNDM |
I2C_SPEED [Hz] |
I2C_RS_WAIT [μs] |
読み出し速度 [μs/バイト] |
備考 |
0 | 400,000 | 0 | 143 | OK |
1 | 30,000 | 0 | 1,635 | OK |
1 | 35,000 | 0 | 1,408 | OK |
1 | 40,000 | 0 | 1,232 | 時々、読み出しエラー |
1 | 100,000 | 0 | 419 | 全く読み出しできない |
1 | 100,000 | 10 | 546 | 時々、読み出しエラー |
1 | 100,000 | 20 | 557 | OK |
1 | 400,000 | 0 | 142 | 全く読み出しできない |
1 | 400,000 | 10 | 212 | 時々、読み出しエラー |
1 | 400,000 | 20 | 216 | 時々、読み出しエラー |
1 | 400,000 | 30 | 226 | OK |
※【dump】コマンド実行中にI2Cリードエラーが起きた場合、2桁16進数の代わりに"--"が表示されるのでわかるようになっています。
I2C通信周波数は、I2Cバスに接続される全デバイス中の一番遅いデバイスで律速します。今回のアプリケーション、シリアルEEPROM化328duinoが、その最遅デバイスにならない様、高速モード(400kHz)で動作する設定にしておくことが望ましいです。
以上より、I2C_SPEED=400kHz、I2C_RS_WAIT=30μsがベスト解(推奨値)と考えられます。従って、DUEなどのマスタデバイスからシリアルEEPROM化328duinoへアクセスする場合には、以下の仕様制限がかかります。
その他のトランザクションは目標仕様に挙げた通りで問題ありません。
I2C_RS_WAITの設定値を少しでも小さくして、アクセスを高速化したいケースでは、328duinoのクロックを高速化する必要が生じます。
328duino(第二形態)は、内蔵RC発振器をクロックアップして使用することが可能なのですが、今回のアプリケーションでは、お勧めしません。理由はこちらにまとめてあります。EEPROMの書き込み/消去時間を内蔵RC発振器のカウンタで測っているためです。クロックアップしてしまうと、EEPROMへの書き込み/消去時間が足りなくなってしまいます。
外部発振を使った328duino(第一形態)と言う手もありますが、この程度の小さなアプリケーションで、わざわざ外部発振回路を接続してコストを上げるのもどうなのでしょう?
今回のアプリケーションでは大人しく、328duino @8MHz,3.3Vの定格内使用しておくのが無難です。
EEPROMを持たないDUEのために、何らかのシリアルEEPROMモジュール(またはチップ)を購入しようかと考えたのですが、そんなに頻繁に使う物でもないし、328duinoで済ませてしまいました。若干の制約はあるものの、普通に使う分には何ら問題なく使えるはずです🎃。