31
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Posted at

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

##2.I2Cの特徴と制御
最初にI2Cの特徴をまとめました。

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

2)データのやり取りをおこなっていないときは、SCL(クロック)、SDA(データ)ともにHighになっています。
 SCL&SDAは10kΩ程度の抵抗でHighに吊って(引っ張り上げられて)います。
 マスタもスレーブもLowに引き下げる機能しか持ちません。ですので、Highに吊る抵抗がないと、信号をHighに戻すことができず、機能しなくなります。
(下図のプルアップ抵抗の値:10kΩは一例であり、条件により変える場合があります)
I2C接続.png

3)通常のデータ通信では、SCL(クロック)がHighの間(と、その前後の決められた時間)は、SDA(データ)が変化すること(H→L、L→H)は許されていません(次の項目に例外を示します)。
例外1&2.png

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

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

6)最小シーケンス単位は1単位が9ビットからなり、前の8ビットは通常のデータ、最後の1ビットは応答(アクノリッジ)に用いられます。

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

8)アドレス指定の9ビットの後は、9ビットずつでデータとなります。9ビットのうちの最初の8ビットがデータ、最後の1ビットがACKです。
アドレス&データ伝送.png

##3.MATLABによるI2Cの信号解析

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

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

SCL=load('SCLbme280T.dat');%クロック読み込み
SDA=load('SDAbme280T.dat');%データ読み込み

これらのデータを表示すると、
オシロ波形.png
上図のようになっています。
このままですと、振幅が多値ですので、2値に変換します。電圧が2.5Vあたりを、しきい値とするとよさそうです。
2値化には、logicalを用います。

Vth=2.5;%しきい値
LSCL=logical(SCL>Vth);% 2値化されたSCL
LSDA=logical(SDA>Vth);% 2値化されたSDA

2値化後.png

###3-1.シーケンスを切り出す
I2Cのシーケンスは、スタートコンディションからストップコンディションまで1つの区切りとなります。
これらのコンディションはI2Cのプロトコルの中でも特殊な部分であり、SCLがHighの期間中にSDAが変化します(通常はSCLがHighの間はSDAは変化してはいけません)。
例外1&2.png
まず、スタートコンディションを抽出します。
スタートコンディションはSCLがHighのときに、SDAが立ち下がるところです。これをプログラムに書くと、

SDA_Edge=diff(LSDA);%diff 隣接データ間の差分を取る
%SDA_Edgeが+1のところが立ち上がり、-1のところが立ち下がりとなる。
%0のところはデータが変化しないところ。
%SDA_Edgeは差分なのでLSDAよりデータ数が1つ少なくなる。

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の立ち上がるところです。これをプログラムに書くと、

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

###3-2.SDA(データ)から必要なデータを拾う
I2Cのプロトコルにおいては、SCL(クロック)がHighの間にSDA(データ)が取り込まれます。ここではSCL(クロック)の立ち上がりで取り込まれる、と見ることにします。
取込みタイミング.png

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

SCL_Edge=diff(LSCL);%LSCLの前後に隣接するデータ間の差分を取る
SCL_EdgeP=logical(SCL_Edge>0);%立ち上がり部分はSCL_Edgeの+1
SCL_EdgeP_Cnt=find(SCL_EdgeP>0);%立ち上がり位置

SCLの立ち上がり部分のSDAの値を全てピックアップしてみます。

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
SDAの線上の○の部分が必要なデータとなります(見にくくてすみません)。

###3-3.データの分類をする
上でピックアップしたSDAの値には、いくつかの種類があります。
1)まずスタートコンディションから1つめのシーケンスは、スレーブのアドレスが出力されます。
 1つめのシーケンスの9ビットのデータのうち、最初の7ビットはスレーブデバイスのアドレス(ID)で、マスタが出力します(スレーブデバイスを指名します)。I2Cにおいては、複数のスレーブデバイスが存在し、この7ビットのアドレスに当てはまるスレーブデバイスとの通信を行ないます。
 アドレスの7ビットに続く1ビットもマスタが担当し、スレーブにデータを送りたいのか(Write=Low)、スレーブからデータを受け取りたいのか(Read=High)を出します。
 さらにWrite/Readの1ビットの次の1ビット(9ビットのうちの最終ビット)はアクノリッジ(Ack)で、「了解しました」の意味でスレーブがLowを返します。
(スレーブが動作していない場合は、Ackを返すことができないのでHighのままとなります:No-Ack)。
 以上のアドレス部分の解析図は下のようになります。
アドレス部分解析.png
プログラムは下記のようになり複雑のように見えますが、スタートコンディションを基準に9ビット取り出して、アドレス、Read/Write、アクノリッジに分類しているだけにすぎません。

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

2)スタートコンディションから2つめ以降のシーケンスは、データが出力されます。
 2つめ以降のシーケンスの9ビットのデータのうち、最初の8ビットはデータが出力されます。
 直前のアドレス7ビットの後がWriteの場合は、引き続きマスタからスレーブにデータが送られます(スレーブデバイスの設定など)。Readの場合は、スレーブからマスタにデータが返されます(測定データなど)。
 8ビットの後の1ビット(アクノリッジ)は「データを受信したほう」が出力します。Writeの後の場合はスレーブが、Readの後の場合はマスタがAckをLowにします。
 マスタがAckを出力することになっている場合でも、ストップコンディションの直前では、AckをLowにしません(NoAck)。
 以上のデータ部分の解析図は下のようになります。
データ部分解析.png
プログラムは下記のようになり複雑のように見えますが、スタートコンディションの後、10ビット目から9ビットずつ取り出して、データ、アクノリッジに分類しているだけです。誰が出力したアクノリッジかは、直前のRead/Writeを見て判定しています。

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

##4.まとめ
今回の流れをすべてプロットすると下図のようになります。
final.png
プログラムをまとめますと、

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]');

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

%2値化波形プロット
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

おつかれさまでした。

31
22
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
31
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?