Edited at

I2C通信を試してみる

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


オープンドレイン接続について


予備知識

仕様書はこちらI2Cバス仕様およびユーザーマニュアル(NXP Semiconductors)

(バス接続)

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

(ワイヤードアンド)

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

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

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


FPGAとI2Cバスの接続

I2Cバスは、オープンドレイン接続かつ双方向接続であるため、トライステート制御が必要です。

ただし、vivadoを使う場合はこの処理を自動的にやってくれます。SDAをinout port宣言して、SDAから入力したいときだけ、SDA<-'Z'とし、出力するときは'1'や'0'を書き込むようにソースをかけば、 vivadoごIOBUFを追加して制御回路を付加 してくれます。 synthesized designをみるとIOBUFが入っていることが確認できます。(2019/9)

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


IO回路

-- inSDA 信号線の状態

-- sda 出力したい信号(1でハイインピーダンス)
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)が使えます。

前項のようにotSDA_T,otSCL_Tでトライステート制御する場合は、以下のように修正します。


パッチ

--  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アドレスの拡張機能など、いろいろ素晴らしい機能があるのですが、スター型に接続するだけなら基本のプロトコルだけ覚えればそれなりに使えるかと。

ポイント1)連続書き込みはストップコンディションで終了を伝え、連続読み込みはNACKで終了を伝える。

ポイント2)アドレスを指定した読み出しや、引数を指定た関数の呼び出し(Remote Procedure Call)では、リピートスタートコンディションを使う。

ポイント3)I2C通信では上位プロトコルは規定せず。アドレス長や通信シーケンスなどは、アプリケーションやデバイス仕様で規定。


1バイト書き込み

S
SLAVE
R/W
Ack
DATA
Ack
P

MASTER
S
アドレス指定
0

書き込みデータ

P

SLAVE

0

0


1バイト読み出し

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を返す。スレーブが1バイトしか伝送しないことがあらかじめ決まっているならばACKを返す。


2バイト以上の通信

2バイト以上のデータを連続書き込み、読み出しする場合。

S
SLAVE
R/W
Ack
DATA
Ack
DATA
Ack
P

MASTER
S
アドレス指定
0

書き込みデータ

書き込みデータ

P

SLAVE

0

0

0

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

S
SLAVE
R/W
Ack
DATA
ACK
DATA
ACK
P

MASTER
S
アドレス指定
1

0

1
P

SLAVE

0
読み出しデータ

読み出しデータ

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


具体例


レジスタアクセスプロトコルの例

デバイスに複数のレジスタがある場合やROMは、デバイス指定だけでなく、レジスタやメモリのアドレス(1 or 2 byte)を指定する。

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

S
SLAVE
W
A
DATA
A
DATA
A
DATA
A
DATA
A
P

MASTER
S
デバイス
アドレス
0

アドレス
上位バイト

アドレス
下位バイト

書き込み
データ1

書き込み
データ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
デバイス
アドレス
0

アドレス
上位バイト

アドレス
下位バイト

Sr
デバイス
アドレス
1

0

1
P

SLAVE

0

0

0

0
読み出し
データ

読み出し
データ