💾

まるでトランジスタ!? 2ピンで制御するEEPROMを試した【AT21CS01】

2024/09/22に公開

先日、非常に興味深い製品が秋月電子通商に入荷しました。

https://x.com/wakwak_akizuki/status/1831485448004223097

Microchip社製のEEPROM, AT21CS01 [1]。1KBit(128Byte)で40円です。
興味深いのはそのパッケージ。なんとSOT23-3です。なんだこれは。

電源とGNDと通信線かなーなんて思っていたら、ピンアサインを見て二度びっくり。

GNDSI/Oの実質2ピンしかありません。お前電源どっから取ってきてるんだ……

なんとプルアップされた通信路から電力をチャージして通信するよう。なんだその非接触ICカードみたいな……

これまた珍妙奇怪、実に面白そうなチップに出会ってしまいました。
ということで今回は、こちらのEEPROMをRaspberry Pi Picoで動かしてみたいと思います。

成果物

はじめに今回の成果物を示します。
汎用的に使いたかったので、Pico C/C++ SDKを使い、CMakeのFetchContent[2]でインポートできるライブラリとして仕上げました。
セキュリティレジスタの読み書きにはまだ非対応ですが、後々作り込んでいく予定です。

https://github.com/Enchan1207/swi_eeprom

チップ概要

チップの概要を整理します。

内部構造

チップはメインメモリアレイセキュリティレジスタと呼ばれるふたつの領域を持っています。

秋月電子の商品説明にあった

偽造防止に役立つ一意のシリアル番号

とは、セキュリティレジスタ先頭に書き込まれているもののはずです。

書き込み可能な領域はゾーン単位でロックできるようですが、データシートを見る限りこれは恒久的なものであり、アンロックするコマンドは用意されていないようです。
あまりにもテストしづらいので、今回はロックについては触れません。

ユースケース

しかしこのチップ需要が謎です。軽く調べた限り2ピンで通信できるという点がミーム的に扱われている情報はいくつかヒットするのですが、肝心のユースケースがイマイチよくわかりません。

公式の実装例[3]によれば

Some application examples include analog sensor calibration data storage, ink and toner printer cartridge identification, and management of after-market consumables.

使用例としては、アナログセンサーの校正データの保存、インクカートリッジの識別、アフターマーケット消耗品の管理などが挙げられます。

とのこと。継続的に電源が供給されない部品について、その識別や認証(?)に使うことを想定しているようです。なるほど。

通信プロトコル

通信プロトコルにはSingle Wire Interface[4] が採用されているようです。物理層?電気信号?のレベルでは1-Wire に似ていますが、データの形式はI²Cの方が近い気がします。

SWIでは以下に示す種類の信号が用いられます:

  • リセット/探索応答
  • 論理「0」(ACK)/論理「1」(NACK)
  • 開始/終了条件

共通

デバイスとマスタとの間で行われるすべての通信は、マスタがバスをLレベルに保持することによって開始されます。デバイスが勝手に応答することはないため、データの読み出しを行う際は マスタがコマンドを送信 → デバイスが応答 → マスタがデータを要求 → デバイスが応答 ……と繰り返す必要があります。ちょっと面倒です。

また、バスはプルアップしておく必要があります。プルアップ抵抗の値域は電源電圧によって異なりますが、今回は1kΩを使いました。

リセット/探索応答

デバイスのリセットは以下のフローで行われます。

  1. バスをt_{RESET} 時間Lに保持します。これによりデバイスが放電され、リセットされます。
  2. バスを解放し、t_{RRT} 時間待機します。このタイミングでデバイスが起動し、復帰します。
  3. 最後にバスを t_{DRR} 時間Lに保持し、解放します。これを受け取ったデバイスは、バスがLになってから t_{DACK} 時間の間、バスをLに保持(=ACK)します。

マスタはバスを解放してから一定時間(t_{DACK}-t_{DRR})のあいだで入力を読み取り、Lであればデバイスが正しく接続・リセットされたと判定できます。

論理「0」(ACK) / 論理「1」(NACK)

1ビットのデータのやり取りは t_{BIT} 時間で行われます。

マスタからデータを送信する際、0または肯定(ACKnowledge)を送る場合は t_{LOW0} 時間、1 または否定(NotACKnowledge)を送る場合は t_{LOW1} 時間、バスをLに保持します。

デバイスからデータを受け取る場合は t_{RD} 時間だけバスをLに保持し、解放します。この信号を受け取ったデバイスは、0を返す場合は最大 t_{MRS} 時間バスをLに保持し、1 を返す場合はなにもしません。

マスタはバスを解放してから一定時間(t_{MRS}-t_{RD})のあいだで入力を読み取り、Lであれば0を、Hであれば1を返したと判定することができます。

開始条件/終了条件

通信は常に 開始条件 で始まり 終了条件 で終了します。これらはそれぞれ t_{HTSS} 時間だけバスを解放することを意味します。つまり通信と通信の間で一定時間待ってね、ということ(……という認識)です。

制御の流れ

先述の通り、マスタとデバイスとの間でやり取りされるデータはI²Cによく似ています。

共通

データを受け取った側は応答(ACK/NACK)を返す必要があります。
マスタがデータを送信した場合はデバイスが、マスタの指示に従いデバイスがデータを返した場合はマスタが、それぞれ応答を返します。

デバイスアドレスの送信

通信開始時、マスタはデバイスアドレスバイトを送信します。これには、オペコード、スレーブアドレス、そして読み書きを示すフラグが含まれます。

オペコードはデバイスに対して行う操作を示します。メインメモリにアクセスする場合は0x0A、メーカIDを取得する場合は 0x0C を指定します。

「スレーブアドレス」は発注時に指定でき、工場出荷時に書き込まれる値のようです。商品説明を見る限り、秋月から購入した場合は0b000です。

スレーブアドレス0のデバイスに対してメインメモリへの読み出しアクセスを行う場合のデバイスアドレスバイトは以下のようになります。

自身に対するデバイスアドレスバイトを正常に受け取ったデバイスはACKを返します。

ややこしいですが、これはデバイスが勝手に行うわけではありません。マスタはデバイスアドレスを送信したのち、デバイスに対して1bitの受信を要求する必要があります(これで2時間溶けた)。

メモリアドレスの送信

次にアクセス先のメモリアドレスを送信します。1Kbitなのでアドレス空間は7bitですが、横着せず8bit送信する必要があります(最上位ビットは読み捨てられます)。
メモリアドレスを受け取ったデバイスはACKを返します。

書き込み

単純に1byte送信するだけです。

AT21CS01は最大8byteのデータを一つのサイクル内で書き込むことができます(ページ書き込み)。書き込みサイクル終了後は t_{WR} 時間待機する必要があるため、多量のデータを一度に書き込む場合はページ書き込みを使うと効率的です。

読み出し

デバイスに対し8回データの受信を要求します。これで対象のアドレスに格納された1byteのデータを読み出すことができます。読み出しが完了したら、マスタから要求停止を意味するNACKを送信します。

こちらも一つのサイクル内で複数バイトのデータを読み出すことができます。書き込みと異なり、一度に読み出せるデータ量に制限はありません(たぶん)。
複数のデータを一度に読み出す場合は1byteごとにACKを送信し、最後のデータを読み出したところでNACKを送信します。

開始条件・終了条件

ひとつの処理の前後には開始条件及び終了条件を挟む必要があります。

実装

これら仕様をRaspberry Pi Picoに落とし込んでいきます。

PIOアセンブリ

マイコン側が結構速い(125MHz)ので sleep_usgpio_set でなんとかできないかと思っていたのですが、デバイスの出力を読み出す時間 t_{MRS}最大2usとまあまあシビアだったため、PIO(Programmable I/O)を採用しました。

公式サンプルに1-Wireの実装例が載っていた[5]ので、これを参考にしています。

https://github.com/Enchan1207/swi_eeprom/blob/v0.1.0/src/driver.pio

(PIOに不慣れなこともあり、かなり無駄なことをしている気がします……)

CPUとのインタフェース

実装にあたり大きな課題の一つとなったのがPIOとCPUとのI/Fです。PIOの命令メモリは非常に小さく、複雑な動作を任せることはできません。しかしCPUリソースを占有するような設計ではPIOのメリットが薄くなりますし、割込み等で通信が不安定になるリスクも出てきます。

最終的に、以下のような分業体制を取りました。

  1. CPUが開始条件を送信する
  2. CPUが32bitのコマンドをTX FIFOに送る
  3. PIOがそれを解釈し、リセット/1byte読出し(ACK or NACK)/1byte書込み のいずれかを行う
    • バス解放後にI/Oを読み、ISRにシフトインする
    • 1byteのやりとりが終わったら、ISRの内容をRX FIFOに送る
  4. CPUがRX FIFOから処理結果を受け取る
  5. CPUが終了条件を送信する

これにより、最低限1byteのデータを確実に(CPUリソースに依存せず)送受信できます。

動作の様子

ライブラリ同梱のサンプル[6]を動かしている様子です(あらかじめ適当なデータを書き込んでいます)。

おわりに

ここまで読んでいただきありがとうございました。

AT21CS01、SOT23という見た目のインパクトもあり、通信路を電力供給に使うという仕様のインパクトもあり、それなりに話題になったような気がします(少なくとも私のTLは沸きました)。

通信プロトコルも知らないまま衝動買いしてデータシートとにらめっこしながら遊ぶのはなかなか良い経験になったかなと思います。
買ってそのままにしてしまったRaspberry Pi Picoもたっぷり活用できたので大満足です。

皆さんも、2ピンのEEPROM、試してみてはいかがでしょうか。

それでは。

push noblock

脚注
  1. AT21CS01 | Microchip Technology ↩︎

  2. FetchContent — CMake 3.30.3 Documentation ↩︎

  3. MicrochipTech/SWI-Connector-Demo: Hardware authentication demo using Microchip’s single-wire AT21CS01 EEPROM. ↩︎

  4. What is Single Wire Interface (SWI) Protocol? - Developer Help ↩︎

  5. pico-examples/pio/onewire/onewire_library/onewire_library.pio at master · raspberrypi/pico-examples ↩︎

  6. swi_eeprom/samples/dump_eeprom.c at v0.1.0 · Enchan1207/swi_eeprom ↩︎

Discussion