前回はRaspberry Pi Picoに独特なPIOの利用でしたが、今回は一般的なI2Cです。接続するのはEEPROMです。マイクロチップ製の24LC64、64Kbit品です。MicroPythonからメモリの読み書きは「とりあえず」出来たんでありますが、いまだ釈然としない事項が残ります。おまけにI2CバスをSCAN時のロジアナ波形もあり。
※「MicroPython的午睡」投稿順 Indexはこちら
電気的に書き換えできるROMの一種であるEEPROMはフラッシュの「親戚筋」ではありますが、かなり地味なデバイスで馴染みの無い方もいるようです。フラッシュとEEPROMの違いを簡潔に説明してくださっているルネサス様のページがあったので、そこへのURLを貼り付けておきます。
ルネサスエレクトロニクス FAQ 1008915 : EEPROMとFlashメモリ
今回使用いたしますのは、チップ写真を上に掲げましたる米MICROCHIP社の24LC64(秋月通商殿にて購入)であります。以下に製品ページへのリンクを貼らせていただきます。
MICROCHIP 24LC64 64Kb I2C compatible 2-wire Serial EEPROM
64Kbit(8Kバイト)という容量は「普通の」Flashの容量と比べると「吹けば飛ぶような」サイズですが、設定値などの記憶に使われることが多いEEPROMとしてはかなりなサイズ感じゃないかと思います。また、EEPROMらしいのは、
- 100万回書き換え可能
- 200年以上記憶保持
ってなところでしょうか。Flashでは真似できませぬ。同じマイクロチップ社のEEPROMは以前にも以下の投稿で扱ったことがあります。以下投稿では256バイト(2Kビット)品ですが、こちらのサイズの方がEEPROM的にはありがちかもしれません。
鳥なき里のマイコン屋(99) GD32でAT24C02、EEPROM
上の投稿はRISC-V搭載のGD32マイコンでC言語のSDK使っての操作でした。MicroPythonとはコードの見た目が大分違いますが、今回やっていることはほとんど同じです。
今回使用するRaspberry Pi Pico のインタフェース回路は、2個搭載されているI2Cのうち、I2C0の方です。それこそ前回同様PIOのSMをプログラムしてI2Cにすることも可能だと思いますが、I2C0はハードウエアです。RP2040のデータシートを読むと、この回路は「半導体界隈では御馴染み」のSynopsys社のIPであるようです。
RP2040は、端子が非常に柔軟であちこちの端子にI2C0をアサイン可能です。とりあえず使っているブレッドボードで都合が良い位置にI2C0の信号を取りだして24LC64を接続してみました。
制御するソフトウエアの方は例によって、どのような関数が準備されているのか、REPLで処理系に聞いてみました。こんな感じ。
>>> import machine >>> help(machine.I2C) object <class 'I2C'> is of type type init -- <function> scan -- <function> start -- <function> stop -- <function> readinto -- <function> write -- <function> readfrom -- <function> readfrom_into -- <function> writeto -- <function> writevto -- <function> readfrom_mem -- <function> readfrom_mem_into -- <function> writeto_mem -- <function>
これを見る限り、標準的なMicroPythonのI2C実装そのものに見えます。以下にMicroPythonの日本語ドキュメントページへのリンクを貼り付けました。
上記のページと24LC64のデータシートの波形を見ながら、淡々と書けば24LC64の読み書きができる筈です。実際「読み書き」は出来たのです。が、1点、今のところうまく行っていないのが、書き込み処理中のポーリングです。EEPROMである24LC64は、最低1バイトからデータを書き換えることができます。最大は32バイト(同一ページ内、32バイトバウンダリ)まで1度に書き換えできます。ただし、I2Cバスから転送している最中はチップ上のバッファに書きこんでいて、転送完了してから実際にEEPROMの書き換え作業を行うようです。書き換え中は忙しいので、次の操作をしようとしてもアクノリッジしてくれません。つまり書きこんだ直後に次の操作をしたい場合には書き換え作業の終了をポーリングして待たねばなりません。そこの待つシーケンスをちょこっと書いてみたのですが、今のところ上手く行ってません。NAK、ACKの結果を戻してくれる筈なのが期待通りには戻ってきていません。また、次回だな。
とりあえず、操作と操作の間に「隙間をあければ」OKということで書いたコードは以下です。I2Cバスアドレスの0x50番地に置いた24LC64の0x0F00番地にASCII文字”7″、0x0F01番地に”4″を書きこみ、それを読み出すだけのものです。なおI2Cの速度は「標準」の100kHzといたしました。
import time from machine import Pin, I2C i2c = I2C(0, scl=Pin(13), sda=Pin(12), freq=100000) i2c.writeto_mem(0x50,0x0F00,bytes([0x37]), addrsize=16) dF00 = i2c.readfrom_mem(0x50,0x0F00, 1, addrsize=16) time.sleep_ms(5) print(str(dF00)) # i2c.writeto_mem(0x50,0x0F01,bytes([0x34]), addrsize=16) time.sleep_ms(5) dF01 = i2c.readfrom_mem(0x50,0x0F01, 1, addrsize=16) print(str(dF01))
読み書きされるデータはbytes「配列」です。上記では殊更にbytes()で数値リストから変換していますが、可読文字であれば bを頭につけた文字列表現でも書けます。結果をそのまま出力すると期待通りのバイト列が返ってきます。以下のとおり。
b'7' b'4'
釈然としない件もあり、デバッグのため、例によってDigilent Analog Discovery2 のロジアナ機能でI2Cバスの様子の観察を始めました。
とりあえず、沢山のトラフィックが連続して出てくるということで、観察は scan() をターゲットにしてみました。scan()の仕組みは、
- アドレスに「ライト」してみる。
- ACKが返って来ればデバイス存在と判断。
- 返らなければ不在。
です。なお、スキャンするアドレス範囲は0x08から0x77までです。なぜ0x00~0x07と0x78~0x7Fをスキャンしないのかは以前に何かで読んで納得した記憶だけがあるのですが、今や忘却の彼方であります。
スキャンの出だしはこんな感じ。
アドレス0x50にいたり、ようやくACKが返るところが以下です。前後のアドレスではNAKになっていることが分かります。なお、上と波形表現が微妙に違うのは、NAK/ACKが見えるように拡大したからであります。
最後、0x77番地にいたってscan()は完了ですが、ループでscan()させているので即座に0x08番地に戻るところがこちら。
当然ですが、I2Cバスにはデバイス1個きりしか存在しないので、scan()は、ただ1要素 80(16進で0x50)を含むリストを返してきます。
SCANを眺めているだけで疲れてしまって、肝心の問題のところに行きつけませんでした。。。