通信インターフェース I2C をMATLAB的に解析してみた

##１．はじめに
I2C（アイ・スクエアド・シーと読むのが正しいらしい）はフィリップスが提唱した通信プロトコルで、センサとマイコンのインターフェースなどに用いられます。使える経路長は基板内通信よりは長いが2-3mまでが無難な範囲です（ストレッチする技術もあるようですが）。
クロック（SCL）と、データ（SDA）のわずか2本の信号線で双方向にデータのやり取りができるため、多くのデバイスで使われています（こう書くと、本当に2本しか接続しなくて「動かない！」というトラブルがときどき見受けられますが、信号線のリターンとしてＧＮＤの接続は必須です。念のため）。
信号線本数が少なく、お手軽なI2Cですが、マスタ側からのみ出力されるクロック（SCL）はともかく、マスタ、スレーブの両方から出力されるデータ（SDA）は、信号の流れが煩雑に変わるため、解析が難しいという問題があります。マスタorスレーブのどちらが出している信号なのかがわからないので、何が原因かわからないようなことになるわけです。
今回は、MATLABを用いて、信号の解析をおこなってみます。MATLABを使わない方でもわかるように書いてみました。
なお、ここでは、7bitアドレス（スレーブのIDが7bit）、マスタが１つの場合についての説明のみで、10bitアドレスやマルチマスタの場合については説明しません。

##２．I２Cの特徴と制御
最初にI2Cの特徴をまとめました。

１）マスタ（マイコンなど）とスレーブ（センサなど）を接続します。
　　接続信号はSCL（クロック）とSDA（データ）です。
　　スレーブのデバイスは複数用いることができます。
　　マスタは通常１つですが、特殊な使い方としてマルチマスタもあります。

２）データのやり取りをおこなっていないときは、SCL（クロック）、SDA（データ）ともにHighになっています。
　SCL&SDAは10kΩ程度の抵抗でHighに吊って（引っ張り上げられて）います。
　マスタもスレーブもLowに引き下げる機能しか持ちません。ですので、Highに吊る抵抗がないと、信号をHighに戻すことができず、機能しなくなります。
（下図のプルアップ抵抗の値：10kΩは一例であり、条件により変える場合があります）
![I2C接続.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434947/dabc3203-470f-0f95-ffe6-783504f565c6.png)

３）通常のデータ通信では、SCL（クロック）がHighの間（と、その前後の決められた時間）は、SDA（データ）が変化すること（H→L、L→H）は許されていません（次の項目に例外を示します）。
![例外１&２.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434947/a23c1598-bcd2-ab53-a32d-383b3edace6b.png)

４）データシーケンスの開始時（スタートコンディション）、終了時（ストップコンディション）には意図的にSCL（クロック）がHighの期間にSDL（データ）が変化して、開始・終了を示します。

５）SCL（クロック）はマスタ側が常に出力します。
　　これに対しSDA（データ）に関してはマスタ、スレーブの双方が状況に応じて出力します。

６）最小シーケンス単位は1単位が９ビットからなり、前の８ビットは通常のデータ、最後の１ビットは応答（アクノリッジ）に用いられます。

７）スタートコンディションの直後の９ビットは、スレーブデバイスの指定に用いられます。
　９ビットのうち最初の７ビットがアドレス、次の１ビットは、この後のシーケンスでスレーブデバイスに書き込みたいのか（Write:ビットはLow０）、スレーブデバイスから読み出したいのか（Read: ビットはHigh）を指定します。
最後の１ビットはACK（アクノリッジ）となっています。

８）アドレス指定の９ビットの後は、９ビットずつでデータとなります。９ビットのうちの最初の８ビットがデータ、最後の１ビットがACKです。
![アドレス&データ伝送.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434947/8beb983b-291a-1707-6091-4fa77cfe7540.png)


##３．MATLABによるI2Cの信号解析

一例として、温度、湿度、圧力センサを備えたBME280とArduinoをつないで温度情報を取り込む場合を見てみましょう。


まず、オシロスコープから取り込んだSCL（クロック）、SDA（データ）の波形ファイルをMATLABにLoadします。

```matlab:
SCL=load('SCLbme280T.dat');%クロック読み込み
SDA=load('SDAbme280T.dat');%データ読み込み
```
これらのデータを表示すると、
![オシロ波形.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434947/4375c51a-ed5a-ea82-3313-03e5f1dc3360.png)
上図のようになっています。
このままですと、振幅が多値ですので、２値に変換します。電圧が2.5Vあたりを、しきい値とするとよさそうです。
2値化には、logicalを用います。

```matlab:
Vth=2.5;%しきい値
LSCL=logical(SCL>Vth);% ２値化されたSCL
LSDA=logical(SDA>Vth);% ２値化されたSDA
```
![２値化後.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434947/5722d9cc-8a5d-230f-464d-ba0c3b3bd650.png)


###３－１．シーケンスを切り出す
I2Cのシーケンスは、スタートコンディションからストップコンディションまで１つの区切りとなります。
これらのコンディションはI2Cのプロトコルの中でも特殊な部分であり、SCLがHighの期間中にSDAが変化します（通常はSCLがHighの間はSDAは変化してはいけません）。
![例外１&２.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434947/fcf046bb-5feb-bfc7-e5ce-0b2c11112695.png)
まず、スタートコンディションを抽出します。
スタートコンディションはSCLがHighのときに、SDAが立ち下がるところです。これをプログラムに書くと、

```matlab:
SDA_Edge=diff(LSDA);%diff 隣接データ間の差分を取る
%SDA_Edgeが＋１のところが立ち上がり、－１のところが立ち下がりとなる。
%０のところはデータが変化しないところ。
%SDA_Edgeは差分なのでLSDAよりデータ数が１つ少なくなる。

SDA_EdgeN=logical(SDA_Edge<0);%SDAの立下りエッジ検出
ST_Cond=(SDA_EdgeN & LSCL(2:end));%SDAの立下りで、かつSCLがHighの点　　
ST_Cond_Cnt=find(ST_Cond==1);%スタートコンディションのポイント
```
同様にしてストップコンディションを抽出します。
ストップコンディションはSCLがHighのときに、SDAの立ち上がるところです。これをプログラムに書くと、

```matlab:
SDA_EdgeP=logical(SDA_Edge>0);%SDAの立上りエッジ検出
SP_Cond=(SDA_EdgeP & LSCL(2:end));%SDAの立上り　かつ　SCLがHigh
SP_Cond_Cnt=find(SP_Cond==1);%スタートコンディション位置
```
こうして、抽出したスタートコンディション、ストップコンディションをSCL,SDAのグラフに書き加えます。
![シーケンス抽出01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434947/54e2e1a2-b890-47b0-7680-5ffff7495d30.png)



###３－２．SDA（データ）から必要なデータを拾う
I2Cのプロトコルにおいては、SCL（クロック）がHighの間にSDA（データ）が取り込まれます。ここではSCL（クロック）の立ち上がりで取り込まれる、と見ることにします。
![取込みタイミング.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434947/fb815376-1ea4-4cc8-fa31-821cdeaa8fa2.png)

SCLの立ち上がり検出は次のようになります

```matlab:
SCL_Edge=diff(LSCL);%LSCLの前後に隣接するデータ間の差分を取る
SCL_EdgeP=logical(SCL_Edge>0);%立ち上がり部分はSCL_Edgeの+1
SCL_EdgeP_Cnt=find(SCL_EdgeP>0);%立ち上がり位置
```
SCLの立ち上がり部分のSDAの値を全てピックアップしてみます。

```matlab:
SDA_Value=LSDA(SCL_EdgeP);%SDAのうちの意味のある部分のみを抜き出す

%SCL,SDA,スタートコンディション、ストップコンディションのグラフを描く

%SCLの立ち上がりのSDAにマークをつける
hold on;
plot(SCL_EdgeP_Cnt,SDA_Value*0.5-0.1,'ko');
hold off;
```
![シーケンス抽出02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434947/0550d0e5-b63a-d014-0615-816d613a736b.png)
SDAの線上の○の部分が必要なデータとなります（見にくくてすみません）。

###３－３．データの分類をする
上でピックアップしたSDAの値には、いくつかの種類があります。
１）まずスタートコンディションから１つめのシーケンスは、スレーブのアドレスが出力されます。
　１つめのシーケンスの９ビットのデータのうち、最初の７ビットはスレーブデバイスのアドレス（ＩＤ）で、マスタが出力します（スレーブデバイスを指名します）。I2Cにおいては、複数のスレーブデバイスが存在し、この７ビットのアドレスに当てはまるスレーブデバイスとの通信を行ないます。
　アドレスの７ビットに続く１ビットもマスタが担当し、スレーブにデータを送りたいのか（Write=Low）、スレーブからデータを受け取りたいのか（Read=High）を出します。
　さらにWrite/Readの１ビットの次の１ビット（９ビットのうちの最終ビット）はアクノリッジ（Ack)で、「了解しました」の意味でスレーブがLowを返します。
（スレーブが動作していない場合は、Ackを返すことができないのでHighのままとなります：No-Ack）。
　以上のアドレス部分の解析図は下のようになります。
![アドレス部分解析.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434947/bc17073c-a4b9-2f2a-d6a4-cd498a930361.png)
プログラムは下記のようになり複雑のように見えますが、スタートコンディションを基準に９ビット取り出して、アドレス、Read/Write、アクノリッジに分類しているだけにすぎません。

```matlab:
for s=1:length(ST_Cond_Cnt)
    u=find(SCL_EdgeP_Cnt > ST_Cond_Cnt(s) & SCL_EdgeP_Cnt < SP_Cond_Cnt(s));
    Num_byte=(length(u)-1)/9;
    for w=0:Num_byte-1
        ww=w*9;
        S=0;
        if w==0
            for x=1:7
                S=S+SDA_Value(u(ww+x))*2^(7-x);
                text(double(SCL_EdgeP_Cnt(u(ww+x))),double(SDA_Value(u(ww+x)))*0.05-0.3,'A','Color','k');
            end
            Adrs=dec2hex(S,2);
            text(double(SCL_EdgeP_Cnt(u(ww+3))),-0.4,Adrs);
            RW(s)=SDA_Value(u(ww+x+1));
            if RW(s)==0
                text(double(SCL_EdgeP_Cnt(u(ww+x+1))),0.05-0.3,'W','Color','r');
            else
                text(double(SCL_EdgeP_Cnt(u(ww+x+1))),0.05-0.3,'R','Color','r');
            end
            ACK=SDA_Value(u(ww+x+2));
            if ACK==false
                text(double(SCL_EdgeP_Cnt(u(ww+x+2))),0.05-0.2,'S','Color','c');
            else
                text(double(SCL_EdgeP_Cnt(u(ww+x+2))),0.05-0.2,'NA','Color','c');
            end
        end
    end
end
```
２）スタートコンディションから２つめ以降のシーケンスは、データが出力されます。
　２つめ以降のシーケンスの９ビットのデータのうち、最初の８ビットはデータが出力されます。
　直前のアドレス７ビットの後がWriteの場合は、引き続きマスタからスレーブにデータが送られます（スレーブデバイスの設定など）。Readの場合は、スレーブからマスタにデータが返されます（測定データなど）。
　８ビットの後の１ビット（アクノリッジ）は「データを受信したほう」が出力します。Writeの後の場合はスレーブが、Readの後の場合はマスタがAckをLowにします。
　マスタがAckを出力することになっている場合でも、ストップコンディションの直前では、AckをLowにしません（NoAck）。
　以上のデータ部分の解析図は下のようになります。
![データ部分解析.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434947/0f7faae2-2317-eb81-8964-b6489db12c28.png)
プログラムは下記のようになり複雑のように見えますが、スタートコンディションの後、10ビット目から９ビットずつ取り出して、データ、アクノリッジに分類しているだけです。誰が出力したアクノリッジかは、直前のRead/Writeを見て判定しています。

```matlab:
for s=1:length(ST_Cond_Cnt)
    u=find(SCL_EdgeP_Cnt > ST_Cond_Cnt(s) & SCL_EdgeP_Cnt < SP_Cond_Cnt(s));
    Num_byte=(length(u)-1)/9;
    for w=0:Num_byte-1
        ww=w*9;
        S=0;
        if w>0
            for x=1:8
                S=S+SDA_Value(u(ww+x))*2^(8-x);
                text(double(SCL_EdgeP_Cnt(u(ww+x))),double(SDA_Value(u(ww+x)))*0.05-0.3,'D','Color','k');
            end
            Dat=dec2hex(S,2);
            text(double(SCL_EdgeP_Cnt(u(ww+4))),-0.4,Dat);
            ACK=SDA_Value(u(ww+x+1));
            if ACK==false
                if RW(s)==0
                    text(double(SCL_EdgeP_Cnt(u(ww+x+1))),0.05-0.2,'S','Color','r');
                else
                    text(double(SCL_EdgeP_Cnt(u(ww+x+1))),0.05-0.2,'M','Color','b');
                end
            else
                text(double(SCL_EdgeP_Cnt(u(ww+x+1))),0.05-0.2,'NA','Color','g');  
            end
        end
    end
end
```
##４．まとめ
今回の流れをすべてプロットすると下図のようになります。
![final.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434947/c461ddb9-d6a6-e3ed-723e-8c481cab8072.png)
プログラムをまとめますと、

```matlab:I2Cdetect.m
%SCL（クロック）、SDA（データ）読み込み
SCL=load('SCLbme280T.dat');
SDA=load('SDAbme280T.dat');

%元波形プロット
figure(2);
t=1:length(SCL);
subplot(2,1,1);
plot(t,SCL,'r');
title('SCL（クロック）');
xlabel('point');
ylabel('[Volt]');
subplot(2,1,2);
plot(t,SDA,'b');
title('SDA（データ）');
xlabel('point');
ylabel('[Volt]');

%２値化
Vth=2.5;%しきい値
LSCL=logical(SCL>Vth);
LSDA=logical(SDA>Vth);

%２値化波形プロット
figure(3);
t=1:length(LSCL);
subplot(2,1,1);
plot(t,LSCL,'r');
ylim([-0.2 1.2]);
title('SCL（クロック）');
xlabel('point');
yticks([0 1]);
subplot(2,1,2);
plot(t,LSDA,'b');
ylim([-0.2 1.2]);
title('SDA（データ）');
xlabel('point');
yticks([0 1]);


%解析

t=1:length(LSCL);
%Start condition 抽出
SDA_Edge=diff(LSDA);
SDA_EdgeN=logical(SDA_Edge<0);%SDAの立下りエッジ検出
ST_Bit=(LSCL(2:end)&SDA_EdgeN);%SCLがHighで、かつ、SDAの立下り　
ST_Cond_Cnt=find(ST_Bit==1);%スタートコンディション位置

%Stop Condition 抽出
SDA_EdgeP=logical(SDA_Edge>0);%SDAの立上りエッジ検出
SP_Bit=(LSCL(2:end)&SDA_EdgeP);%SCLがHighで、かつ、SDAの立上り　
SP_Cond_Cnt=find(SP_Bit==1);%スタートコンディション位置

%Start & Stop Condition 表示
figure(4);
plot(t,LSCL*0.5+0.45,'r',t,LSDA*0.5-0.1,'b');
lgd = legend('SCL','SDA');
lgd.AutoUpdate = 'off';
hold on;
for s=1:length(ST_Cond_Cnt)
    plot([ST_Cond_Cnt(s) ST_Cond_Cnt(s)],[-0.5 1.1],'g--');
end

for s=1:length(SP_Cond_Cnt)
    plot([SP_Cond_Cnt(s) SP_Cond_Cnt(s)],[-0.5 1.1],'m--');
end

%SCLの立ち上がりエッジ 抽出
SCL_Edge=diff(LSCL);
SCL_EdgeP=logical(SCL_Edge>0);
SCL_EdgeP_Cnt=find(SCL_EdgeP>0);

%SDAの値を抽出
SDA_Value=LSDA(SCL_EdgeP_Cnt);

ylim([-0.5 1.1]);
yticks([]);

%アドレス＆データ部分解析
for s=1:length(ST_Cond_Cnt)
    u=find(SCL_EdgeP_Cnt > ST_Cond_Cnt(s) & SCL_EdgeP_Cnt < SP_Cond_Cnt(s));
    Num_byte=(length(u)-1)/9;
    for w=0:Num_byte-1
        ww=w*9;
        S=0;
        if w==0
            for x=1:7
                %アドレス解析
                S=S+SDA_Value(u(ww+x))*2^(7-x);
                text(double(SCL_EdgeP_Cnt(u(ww+x))),double(SDA_Value(u(ww+x)))*0.05-0.3,'A','Color','k');
            end
            Adrs=['0x',dec2hex(S,2)];
            text(double(SCL_EdgeP_Cnt(u(ww+2))),-0.4,Adrs);
            RW(s)=SDA_Value(u(ww+x+1));
            if RW(s)==0
                text(double(SCL_EdgeP_Cnt(u(ww+x+1))),0.05-0.3,'W','Color','r');
            else
                text(double(SCL_EdgeP_Cnt(u(ww+x+1))),0.05-0.3,'R','Color','r');
            end
            ACK=SDA_Value(u(ww+x+2));
            if ACK==false
                text(double(SCL_EdgeP_Cnt(u(ww+x+2))),0.05-0.2,'S','Color','r');
            else
                text(double(SCL_EdgeP_Cnt(u(ww+x+2))),0.05-0.2,'NA','Color','g');
            end
        else
            for x=1:8
                %データ解析
                S=S+SDA_Value(u(ww+x))*2^(8-x);
                text(double(SCL_EdgeP_Cnt(u(ww+x))),double(SDA_Value(u(ww+x)))*0.05-0.3,'D','Color','k');
            end
            Dat=['0x',dec2hex(S,2)];
            text(double(SCL_EdgeP_Cnt(u(ww+3))),-0.4,Dat);
            ACK=SDA_Value(u(ww+x+1));
            if ACK==false
                if RW(s)==0
                    text(double(SCL_EdgeP_Cnt(u(ww+x+1))),0.05-0.2,'S','Color','r');
                else
                    text(double(SCL_EdgeP_Cnt(u(ww+x+1))),0.05-0.2,'M','Color','b');
                end
            else
                text(double(SCL_EdgeP_Cnt(u(ww+x+1))),0.05-0.2,'NA','Color','g');  
            end
        end
    end
end
xlabel('point');
yticks([]);
```
となります。

ちなみに、ZeroplusのロジックアナライザLAP-CのI2Cプロトコルでデータ取込み＆表示した結果は下記のようになりました。
![ZeroplusI2C.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434947/00c2d0b3-1c55-d2f2-4e85-0d432aea94b0.png)


おつかれさまでした。