FPGAでI2C通信を試してみる


I2C通信は、シリアル・データライン (SDA) とシリアル・ク ロックライン (SCL)の２本の線を使って、複数のデバイスが通信する仕組み。FPGAで直接I2C通信を試してみました。

## オープンドレイン接続について
###予備知識
仕様書はこちら[I2Cバス仕様およびユーザーマニュアル(NXP Semiconductors)](https://www.nxp.com/docs/ja/user-guide/UM10204.pdf)

（バス接続）
複数のデバイスが、共通の信号線（バスライン）を使って通信する接続方法。I2CのバスラインはSDAとSCLの2本。共通の信号線を使うため、電気回路や、送信するデバイスを選ぶための手順の規定が必要。

（ワイヤードアンド）
ワイヤードアンド接続は、１本の信号線（ワイヤー）に複数のデバイスの出力を並列に直接接続する回路の構成方法。デバイスの出力は、オープンドレインやオープンコレクタにするのがきまり。抵抗でプルアップした１つの信号線に、各デバイスの出力を直接繋ぐ。

オープンドレインやオープンコレクタ回路は、出力とGNDの間の抵抗値を増減する。信号線はプルアップされているため、この抵抗値が高い状態（ハイインピーダンス）では信号線の電圧は高い状態が維持されるのでプルアップされた信号線の論理値１となる。抵抗値が小さくなる（非ハイインピーダンス、論理０）と、信号線からオープンドレインやオープンコレクタの回路に電流が流れ込む。プルアップ抵抗の電圧降下により電圧が下がり、論理値０になる。

接続するデバイスのうち、１つでも０を出力するデバイスがあると信号が０になる。ワイヤーで接続するだけで、AND回路が構成できることからワイヤードANDと呼ばれる。負論理（低い電圧で１）の信号線でであればワイヤードOR。

<!-- 具体的には、デバイスの出力回路をオープンドレイン（オープンコレクタ）で構成し、信号線は抵抗（プルアップ抵抗）を介してVDDに接続する。オープンドレイン回路は、OFF状態ではハイインピーダンス状態となり信号線に電流が流れない。このため、全てのデバイスの出力がOFFであれば、信号線の電位はプルアップ抵抗によりVDDとなる。逆に、オープンドレイン回路の出力がON状態では、信号線がGNDの間のインピーダンスが小さくなり、電流が流れる。電圧降下により信号線の電位がGNDに近づく。-->

###FPGAとI2Cバスの接続
I2Cバスは、オープンドレイン接続かつ双方向接続であるため、トライステート制御する。
vivadoは、トライステート制御処理は自動的に追加される。SDAをinout port宣言して、SDAから入力したいときだけ、`SDA<-'Z'`とし、出力するときは'1'や'0'を書き込むようにソースをかけば、 ***vivadoがIOBUFを追加して制御回路を付加*** する。 synthesized designをみるとIOBUFが入っていることが確認できます。(2019/9)

```VHDL:vivadoに頼る例
-- SDA inout
-- state st_readで読み出し
-- read_value 読み出しデータ
-- write_value 書き込みデータ
U_STATE:process(state,SDA,write_value) begin
  if state=st_read then
    SDA <='Z';
    read_value <- SDA;
  else
    SDA <= write_value;
  end if;    
end process
```


vivadoに頼らず、自分でトライステート制御する場合は、以下のように(出力をハイインピーダンスにできる）IOBUFを介して接続する。 

```VHDL:IO回路
-- inSDA 信号線の状態
-- sda   出力したい信号（１でハイインピーダンス）
U_SDA:IOBUF Port(
  O=>inSDA,    -- 入力バッファ出力を入力信号に
  IO=>IIC_SDA, -- PIN 
  I=>otSDA,    -- 出力バッファ入力に出力信号に
  T=>otSDA_T   -- トライステート制御
);
U_SDAOUT:process(sda) begin
    if   sda = '0' then
        otSDA <='0';otSDA_T<='0';
    else
        otSDA <='1';otSDA_T<='1';--'1'でハイインピーダンスにする
    end if;
end process;
```

###マスターコントローラへのトライステートパッチ例
Scott Larson,[I2C Master(VHDL)](https://eewiki.net/pages/viewpage.action?pageId=10125324)に、前項のようにotSDA_T,otSCL_Tでトライステート制御する例を示す。

```VHDL:パッチ
--  scl <= '0' WHEN (scl_ena = '1' AND scl_clk = '0') ELSE 'Z';
U8100_SCLOUT:process(scl_ena,scl_clk) begin
    if  (scl_ena = '1' AND scl_clk = '0') then
        otSCL <='0';otSCL_T<='0';
    else
        otSCL <='1';otSCL_T<='1';
    end if;
end process;
--  sda <= '0' WHEN sda_ena_n = '0' ELSE 'Z';
U8200_SDAOUT:process(sda_ena_n) begin
    if   sda_ena_n = '0' then
        otSDA <='0';otSDA_T<='0';
    else
        otSDA <='1';otSDA_T<='1';
    end if;
end process;
```

##基本通信プロトコル
I2Cのプロトコルには、複数のMASTERを接続するための機能や、SLAVEが応答を引き延ばす機能、SLAVEアドレスの拡張機能など、たくさんの機能があるが、スター型に接続するだけなら基本のプロトコルだけ覚えればそれなりに使える。

ポイント１）連続書き込みはストップコンディションで終了を伝え、連続読み込みはNACKで終了を伝える。
ポイント２）アドレスを指定した読み出しや、引数を指定た関数の呼び出し(Remote Procedure Call)では、リピートスタートコンディションを使う。
ポイント３）I2C通信では上位プロトコルは規定せず。アドレス長や通信シーケンスなどは、アプリケーションやデバイス仕様で規定。


###１バイト書き込みのシーケンス
横軸は時間、１行目はタイムスロット、２行目はMASTER装置の送信内容（空欄で受信）、３行目はSLAVE装置の送信内容（空欄で受信）

||S|SLAVE指定|R/W|Ack|DATA|Ack|P|
|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|
|MASTER|S|アドレス（７ビット）|0||書き込みデータ||P|
|SLAVE||||0||0||

###１バイト読み出しのシーケンス

||S|SLAVE指定|R/W|Ack|DATA|ACK|P|
|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|
|MASTER|S|アドレス（７ビット）|1|||1|P|
|SLAVE||||0|読み出しデータ|||

読み出しデータに対するMASTERの確認応答(ACKフィールド)の意味については、上位のプロトコルの設計次第。仕様書によれば、NACKを発生させる条件は以下のとおり。
>1. 転送されたアドレスのレシーバがバスに存在しない.つまりアクノリッジで応答する デバイスがない場合。
>2. レシーバが何らかのリアルタイム機能を実行中でマスタとの通信を行える状態ではなく、送信も受信もできない場合。
>3. 転送の間、 受信したデータやコマンドをレシーバが理解できない場合。
>4. 転送の間、 レシーバがそれ以上データバイトを受信できない場合。
>5. マスタレシーバがスレーブトランスミッタに対し転送の終了を伝える場合。

マスターが、1バイトで転送の終了を明確に伝えるなら(5)の意味でNACKを返す。スレーブが１バイトしか伝送しないことがあらかじめ決まっているならばACKを返す。

###２バイト以上の通信
２バイト以上のデータを連続書き込み／読み出しは、１バイトのシーケンスのデータ伝送とACK部分を繰り返す。

書き込みでは、MASTERが書き込むバイト数だけDATAを繰り返したのち、STOPコンディションを発行する。

||S|SLAVE|R/W|Ack|DATA|Ack|DATA|Ack|P|
|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|
|MASTER|S|アドレス指定|0||書き込みデータ||書き込みデータ||P|
|SLAVE||||0||0||0||


読み出しでは、MASTERが読み出したいバイト数だけDATAを受信すると、ACKの代わりにNACKをSLAVEに送り、送信を終了させる。

||S|SLAVE|R/W|Ack|DATA|ACK|DATA|ACK|P|
|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|
|MASTER|S|アドレス指定|1|||0||1|P|
|SLAVE||||0|読み出しデータ||読み出しデータ|||

##レジスタアクセスプロトコルの例
MASTER装置が、ROMや複数のレジスタをもつデバイスにアクセスするには、デバイスのアドレスを指定するだけでなく、レジスタやメモリのアドレス(1 or 2 byte)を指定する必要がある。このための代表的なプロトコルを示す。

###書き込み
データの先頭にアドレスを追加する。

||S|SLAVE|W|A|DATA|A|DATA|A|DATA|A|DATA|A|P|
|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|
|MASTER|S|デバイス<br>アドレス|0||アドレス<br>上位バイト||アドレス<br>下位バイト||書き込み<br>データ1||書き込み<br>データ2||P|
|SLAVE||||0||0||0||0||0||

###読み出し
アドレスを書き込んだのち、 **STOP**せずに **リピートスタートコンディション**を発行し読み出しを開始する。

||S|SLAVE|W|A|DATA|A|DATA|A|Sr|SLAVE|R|A|DATA|A|DATA|A|P|
|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|
|MASTER|S|デバイス<br>アドレス|0||アドレス<br>上位バイト||アドレス<br>下位バイト||Sr|デバイス<br>アドレス|1|||0||1|P|
|SLAVE||||0||0||0||||0|読み出し<br>データ||読み出し<br>データ|||
Sr:リピートスタートコンディション