I2Cの実験(1/6)

2/6 )( 3/6 )( 4/6 )( 5/6 )( 6/6 )  〔マイコンのトップに戻る〕


【目次】
 RTC-8564NB(リアルタイムクロック)を使用した記事はこちら(Arduino編)(PIC編)を参照下さい。
 EEPROM(24LC256)を使用した記事はこちら(Arduino編)(PIC編)を参照下さい。
 大気圧センサー(MPL115A2)を使用した記事はこちら(Arduino編)(PIC編)を参照下さい。


【概要】

このページでI2Cに関する事を記載して行きます。
I2C(Inter-Integrated Circuit):実際はICと書き、アイ・スクエアド・シーと呼ぶみたいですね。
I2Cはフィリップス社で開発されたシリアルバスです。フィリップス社のI2C日本語仕様書はこちらを参考
にして下さい。

 RS232C/USARTは1対1の非同期式シリアル通信方式です、
RS232Cは伝送距離も比較的長く(15m程)取れますが、USARTの伝送距離は1〜2m程位でしょう。
SPIやI2Cインターフェースは1対nの同期式のシリアル通信で伝送距離は基板と基板間などの
装置内での短い距離です、SPIやI2Cは1対nですが、1がマスターでnがスレーブとなっています、
もちろん1対1もOK。
RS232Cは接続されたお互いの機器から個々にそれぞれのタイミングで送受信が可能ですが、
SPIやI2Cはマスターからアクションを起こさないとスレーブとは送受信が出来ない所が異なります。

SPIとI2Cの違いとしては
 SPIは通信速度がI2Cより速く、送受信に使う配線はSPIが2本(SO/SI)、I2Cは1本(SDA)です、
それと、マスターが同期取る為に送信するクロック信号がSPIもI2Cも1本づつ配線されます。
それにSPIは通信する相手(スレーブ)を指定するのにSSの選択信号を相手の個数分配線しますが、
I2Cは通信する相手のアドレスをデータとして送り通信を確立します、なので配線はI2Cが2本で
SPIが4本+αです
、この辺りがI2Cを最も使われる大きな理由でしょうね。

 また、SPIは通信規約が単純ですが、I2Cはデータを1バイト送信する毎に受信側からACK信号の
返送をして、互いに確認(ハンドシェイク)を取りながらデータ送信を行っているので結構複雑だったり
します、なのでI2Cの方がプログラム容量は多くなります
その他のI2Cの特徴として接続したスレーブに一斉に同報送信が可能です、
それとマスターが複数設置可能(マルチマスター)です。(ここの記事ではシングルマスターのみです)

マルチマスターの記事を追記しました、こちら(4/6)を参照下さい。 *3)

その他にPICのシリアル通信方式としてSPIとUSART(RS232C)が有ります。
SPIについてはこちらを参照下さい。
USARTについてはこちらを参照下さい。

※今回の実験プログラムでは通信時のエラー処理を行っていないので、
 ちゃんとした機器を製作する場合は考慮する必要が有るかもね!

《ICバスの接続》

接続図1

I2Cの信号線としてはSDA(シリアルデータ)とSCL(シリアルクロック)の2本を配線しますが、この2本は必ず抵抗で+電源へプルアップしないと通信できません。

マスターがSCL信号を送信してスレーブはそれに同期して動作します、 クロック信号は主に100kbpsまたは400kbpsが多いようで、今回は100kbpsで行っています。

尚、ArduinoはI2CのWireライブラリを利用しましたが、クロック設定が無い用なのですが 100kbpsで行っている模様です。
PICピン構成図 接続図2
左がArduinoとPIC12F1822での実験配線です。
Arduino
SCL(アナログ5番ピン)・SDA(アナログ4番ピン)
PIC12F1822
SCL(デジタルRA1ピン)・SDA(デジタルRA2ピン)
PICは必ずデジタル入力ピンに設定を行います。

抵抗値に付いては電子工作室さんのこちらのサイトによれば、抵抗値の決め方が 書いて在りますが、私の手持ちから1KΩを使いました、 また、PICの内部プルアップでも動作しています。

Arduinoは受信したらArduinoIDEのシリアルモニター(COM)画面に表示させていますが、 PICは別途自作のLCDモニターに表示させています。

スレーブのアドレスについては7ビットと10ビットが有りますが7ビットで実験しています。 また、アドレス番号として将来用に予約された番号や10ビット用等で使用できない番号が有ったりしますので、 注意が必要ですが、まあ、8〜119までを利用すればOKでしょう。

実験風景の写真はこちらです。

《通信実験1:Arduino(マスター)-PIC(スレーブ)》

マスターは1秒毎に'0'〜'9'を送信して、スレーブはその受信データに0x11(17)を足してマスターに 送信(返送)します、尚、'0'〜'9'に0x11を足すと'A'〜'I'のキャラクタ文字で返す事になります。

通信データのフォーマット仕様(1バイトデータの送受信)

I2Cは以下の様に2パターンの通信フォーマットが有ります。
R/W=0:マスターからの書き込み要求、マスターが送信モードでスレーブが受信モード
R/W=1:マスターからの読み出し要求、マスターが受信モードでスレーブが送信モード

 データのフォーマット1

下図はロジックアナライザで、実際にデータを送受信した内容の波形を表示させた物です。
赤の波形(A1)がSCLで、緑の波形(A4)がSDAです。
アドレス0x8のスレーブ(PIC)にデータ(0x35:"5")を送信しています。
尚、この波形、R/Wは内容0なのでマスター送信モード(Write)上記図@となります。 *2)

 データの波形図
SCLのクロック信号に同期してSDAライン一本で送信も受信もこなしているのが見て取れると思います。
また、SCLの1サイクルは10usの様なのでクロックはやはり100kbpsの様ですね。

Arduino(マスター)での操作

arduino IDE 0022 (ArduinoのWireライブラリ使用)での説明です。

注意)
arduino IDE 1.0.1 ではArduinoのWireライブラリ名が変更になっています、
Wire.send( ) が Wire.write( ) へ
Wire.receive( ) が Wire.read( ) へ
arduino IDE 1.0.1 を利用する人は読み換えやスケッチ変更をお願いします。 *1)
Wire.write( )/Wire.read( )に書き換え変更しました。 *6)

次の順番で行って行きます。

@I2Cを行う為の初期化を行います。
 Wire.begin( ) : これでマスター設定です

Aスレーブにデータ('0'〜'9')を送信します。[マスターからの書き込み要求]
 Wire.beginTransmission(アドレス)   : スレーブにスタートコンディションの発行とアドレスの送信を行う
 Wire.write(送信データ)        : データの準備を行う
 Wire.endTransmission( )       : データの送信とストップコンディションの発行を行う 

Bスレーブからデータ('A'〜'I')を受信します。[マスターからの読み出し要求]
 ans = Wire.requestFrom(アドレス,受信するバイト数) : スレーブへのデータ送信要求を行う
 ans : 受信したデータの個数が返される。
 この処理でスタート・アドレス送信・データの受信・ストップまでの一連の処理が実行されます。
 Wire.read( ) : この関数で受信したデータを取り出します。

C以降はA〜Bを1秒毎に繰り返します。
(尚、送受信バッファは32バイトがMAXの様です、これ以上はエラーになります。)

Arduino側のスケッチプログラム例

IDEに下記のスケッチプログラムをコピーペーストして貼り付けて下さい。
次にIDEツールバーの「Upload」ボタンをクリックしてコンパイルとarduinoボードに書込みを行います。
---------------------------------------------------------------------
#include <Wire.h>

// 電源起動時とリセットの時だけのみ処理する関数
void setup()
{
     Serial.begin(9600) ;               // シリアル通信の初期化
     Wire.begin() ;                     // I2Cの初期化、マスターとする
     delay(5000) ;                      // 5秒後に開始
}
// 繰り返し実行されるメインの処理関数
void loop()
{
     int ans , i , j ;
     char c ;

     for (i=0x30 ; i<=0x39 ; i++) {
          Wire.beginTransmission(8) ;   // 通信の開始処理、スレーブのアドレスは8とする
          Wire.write((byte)i) ;         // 通信データを準備する('0'〜'9')
          ans = Wire.endTransmission() ;// データの送信と終了処理
          delay(100) ;                  // スレーブがデータを準備するまで待つ
          ans = Wire.requestFrom(8,1) ; // スレーブにデータ送信要求をだし、1バイト受信する
          for (j=0 ; j<ans ; j++) {  // 受信したデータの個数(今回はans=1)だけ繰り返す
               c = Wire.read()  ;       // 1バイトデータを読み取る('A'〜'I')
               Serial.print(c) ;        // IDEにシリアル通信で表示する
          }
          delay(1000) ;                 // 1秒ウエイト
     }
}
---------------------------------------------------------------------
COM1
 この左の画面がPICから受信したデータをArduinoIDEの
 シリアルモニター画面に表示させた結果です。
 'A'〜'I'を繰り返し表示します。

PIC(スレーブ)での操作

HI-TECH C Compiler for PIC10/12/16 MCUs(Lite Mode)V9.80コンパイラでの説明です。
スレーブのマイアドレスは8とします、

スレーブで使用する関連レジスタの説明

SSPSTAT:SSPステータスレジスタ

ビット
機能 SMP D/A R/W BF
Bit7  :SMP  通信速度情報
          0 : 高速モード (400 kHz) のスルー レート制御が有効
           : 標準速度モード (100 kHz および 1 MHz) のスルー レート制御が無効
Bit5  :D/A  Data/Address ビット情報
          0 : 最後の受信したバイトがアドレスであった事を示す
          1 : 最後の受信したバイトがデータであった事を示す
Bit4  :P     ストップ ビット
          0 : ストップ ビットが最後に検出されなかった事を示す
          1 : ストップ ビットが最後に検出された事を示す
Bit3  :S     スタート ビット
          0 : スタート ビットが最後に検出されなかった事を示す
          1 : スタートビットが最後に検出された事を示す
Bit2  :R/W  Read/Write ビット情報
          このビットは、最後のアドレス一致後の R/W ビット情報を含みます。
          有効期間は、アドレス一致後から次のスタート、ストップまたは NOT ACK を
          受信するまでです。
          0 : マスターからの書き込み要求(データを受信する)
          1 : マスターからの読み出し要求(データを送信する)
Bit0  :BF    バッファ フル ステータス ビット
          受信時
          0 : 受信は未完了、SSPBUF は空
          1 : 受信完了、SSPBUF フル
          送信時
          0 : データ送信完了 (ACK およびストップ ビットは含まない)、SSPBUF は空
          1 : データ送信中 (ACK およびストップ ビットを含まない)、SSPBUF はフル

SSPCON1:制御レジスタ1
ビット
機能 WCOL SSPOV SSPEN CKP SSPM3 SSPM2 SSPM1 SSPM0
Bit7  :WCOL  書き込み衝突検出ビット(今回このビットは未使用)
            0 : 衝突なし
            1 : 前のワードが送信中であるのに SSPBUF レジスタに書き込みされた
              (ソフトウエアでクリアする必要が有る)
Bit6   :SSPOV  受信オーバーフローインジケータビット(今回このビットは未使用)
           0:オーバーフローはなし
           1:SSPBUFがデータを保持中に次のデータを受信した
             (ソフトウエアでクリアする必要が有る)
Bit5  :SSPEN  同期シリアル ポート イネーブル ビット
            0 :SDA/SCLピンは I/O ポートピンとして設定される
            : SDA/SCLピンはI2Cピンとして使用される(ピンは入力に設定する)
Bit4   :CKP    クロック極性選択ビット
           SCKリリース制御
           0:データのセットアップ時間を確保する為にクロックをLOWに保持する
             (クロックストレッチ処理:通信の一時中断)
           1:クロックを有効にしてSCLラインを開放する(通信の再開)
Bit0-3:SSPM   同期シリアル ポート モードの選択ビット
         0110 : I2C スレーブモード、7ビットアドレス
         1110 : I2C スレーブモード、7ビットアドレス(スタート/ストップの割込みが有効)

SSPCON2:制御レジスタ2
ビット
機能 GCEN ACKSTAT SEN
Bit7  :GCEN   一括呼び出し(同報)イネーブルビット
             : 一括呼び出しは無効
            1 : 一括呼び出しアドレス(0x00)を受信した時に、割り込みが有効になる
Bit6  :ACKSTAT アクノレッジ ステータス ビット
            0 : アクノレッジ信号(ACK)が受信された
            1 : アクノレッジ信号(ACK)は受信されていない
Bit0  :SEN    SCL制御(クロックストレッチ)の有無
            0 : 制御はしない
             : 制御は行う
             (通信の一時中断処理が行われるのでCKPビットで再開させる必要が有ります)

レジスタの設定は次の順番で行って行きます。

@SSP1STATレジスタの設定
 SSP1STAT = 0b10000000 ; 標準速度モード(100kHz)で利用する

ASSP1CON1レジスタの設定
 SSP1CON1= 0b00100110 ; SDA/SCLピンはI2Cで使用し、スレーブモードとする

BSSP1CON2レジスタの設定
 SSP1CON2bits.SEN = 1 ;  SCL制御(クロックストレッチ)を行う
 もし、一括呼び出しの同報通信を受信する場合は、SSP1CON2bits.GCEN = 1 ;と追記する。

Cスレーブのアドレス設定
 アドレスはSSP1ADDレジスタに設定しますが、レジスターのビット1-6にセットし、ビット0はR/Wを
 セットします。(但し、R/Wのセットはマスターが行います、スレーブは0のままです)

アドレス設定  アドレス8なら、SSP1ADD = 8 << 1 ; とすれば1ビット左へシフトされる

 マスターであれば、
 R/W=0 なら左シフトで0ビット目は0になるのでこのままでOKです。
 R/W=1 ならSSP1ADD = (8 << 1) + 1 ; とします。

 マスターからアドレスを受信した時に、自分のアドレスかチェックを行う際にSSP1MSKレジスタとも
 比較されますが、このレジスタはマスクデータでONしているビットのみ受信データと比較されます、
 通常はSSP1MSK = 0b11111110 ; で問題ないでしょう。

D割込み関連レジスタの設定
 SSP1IE = 1 ; SSP(I2C)割り込みを許可する
 BCL1IE = 1 ; MSSP(I2C)バス衝突割り込みを許可する
 PEIE  = 1 ; 周辺装置割り込みを許可する
 GIE   = 1 ; 全割り込み処理を許可する
 SSP1IF = 0 ; SSP(I2C)割り込みフラグをクリアする
 BCL1IF = 0 ; MSSP(I2C)バス衝突割り込みフラグをクリアする

E以降はマスタからアドレスデータや8バイトデータを受信すると割込みが発生します。
以下がレジスタ設定の例です。
     SSP1STAT= 0b10000000 ;   // 標準速度モードに設定する(100kHz)
     SSP1CON1= 0b00100110 ;   // SDA/SCLピンはI2Cで使用し、スレーブモードとする
     SSP1CON2bits.SEN = 1 ;   // SCL制御(クロックストレッチ)を行う
     SSP1ADD = 8 << 1     ;   // マイアドレス(8)の設定
     SSP1MSK = 0b11111110 ;   // アドレス比較用マスクデータ
     SSP1IE = 1 ;             // SSP(I2C)割り込みを許可する
     BCL1IE = 1 ;             // MSSP(I2C)バス衝突割り込みを許可する
     PEIE   = 1 ;             // 周辺装置割り込みを許可する
     GIE    = 1 ;             // 全割り込み処理を許可する 
     SSP1IF = 0 ;             // SSP(I2C)割り込みフラグをクリアする
     BCL1IF = 0 ;             // MSSP(I2C)バス衝突割り込みフラグをクリアする
割込みイベント時の処理

今回のこのスレーブ設定において下記の割込みが発生します。
尚、スタートやストップコンディション発行時にも割り込みを発生させる事も出来ます、
また、ACK返答はPICが自動的に行いますが、設定を変更するとソフト側で返す事も出来ます。

・アドレスを受信した時
 アドレスを受信した時のACK返送はPICが自動的に返します。
 SSP1STATはD/A=0となっています、SSP1BUFにアドレスデータがセットされているので空読みする。
 [マスターからの書き込み要求:R/W=0]と[マスターからの読み出し要求:R/W=1]で処理を行います。
 R/W=1 であればアドレスデータを空読みした後、SSP1BUFに送信するデータをセットします。
 以上処理したらSSP1IFの割込みフラグをクリアし、
 PICのSCL制御により通信が一時中断されているので、SSP1CON1のCKP=1で通信の再開を行う。

・データを受信した時
 データを受信した時のACK返送はPICが自動的に返します。
 SSP1STATはD/A=1となっています、SSP1BUFからデータを読み出します。
 以上処理したらSSP1IFの割込みフラグをクリアし、
 PICのSCL制御により通信が一時中断されているので、SSP1CON1のCKP=1で通信の再開を行う。

・ACK/NACKを受信した時
 ここの割込みはデータを送信した時にマスターからの返答(ACK/NACK)で発生します。
 SSP1CON2のACKSTAT=0 であれば次の送信データをSSP1BUFにセットする。
 以上処理したらSSP1IFの割込みフラグをクリアし、SSP1CON1のCKP=1で通信の再開を行う。
 SSP1CON2のACKSTAT=1 であれば通信終了です、SSP1IFの割込みフラグをクリアします。
下記はSSP(I2C)割込み発生時のサンプルプルグラム例です。
---------------------------------------------------------------------
void interrupt InterI2C( void )
{
     char x ;

     if (SSP1IF == 1) {  // I2C割込み発生
          if (SSP1STAT.R/W == 0) {
               /******* マスターからの書き込み要求(スレーブは受信)時の処理 *******/
               if (SSP1STAT.D/A == 0) {
                    // 受信バイトはアドレス時の処理
                    Rdtp = (char *)rcv_data ;
                    x = SSP1BUF ;       // アドレスデータを空読みする
               } else {
                    // 受信バイトはデータ時の処理
                    *Rdtp = SSP1BUF ;   // データを読込む(ACKはPICが自動的に返す)
                    Rdtp++ ;
               }
               SSP1IF = 0 ;             // 割込みフラグクリア
               SSP1CON1.CKP = 1 ;       // SCLラインを開放する(通信の再開)
          } else {
               /******* マスターからの読み出し要求(スレーブは送信)時の処理 *******/
               if (SSP1STAT.BF == 1) {
                    // アドレス受信後の割り込みと判断する
                    Sdtp = (char *)snd_data ;
                    x = SSP1BUF ;                 // アドレスデータを空読みする
                    while((SSP1CON1.CKP)|(SSP1STAT.BF)) ;
                    SSP1BUF = *Sdtp ;             // 送信データのセット(1回目のデータ)
                    Sdtp++ ;
                    SSP1IF = 0 ;                  // 割込みフラグクリア
                    SSP1CON1.CKP = 1 ;            // SCLラインを開放する(通信の再開)
               } else {
                    // データの送信後のACK受け取りによる割り込みと判断する
                    if (SSP1CON2.ACKSTAT==0) {
                         // マスターからACK応答なら次のデータを送信する
                         while((SSP1CON1.CKP)|(SSP1STAT.BF)) ;
                         SSP1BUF = *Sdtp ;        // 送信データのセット(2回目以降のデータ)
                         Sdtp++ ;
                         SSP1IF = 0 ;             // 割込みフラグクリア
                         SSP1CON1.CKP = 1 ;       // SCLラインを開放する(通信の再開)
                    } else {
                         // マスターからはNOACKで応答された時(送信終了)
                         SSP1IF = 0 ;             // 割込みフラグクリア
                    }
               }
          }
     }
}
---------------------------------------------------------------------
PIC(12F1822)側のプログラム例

このプログラムにはデバッグモニタープログラム「skMonitorLCD.c」「skMonitorLCD.h」が必要です。
デバッグLCDモニターについてはこちらを参照して下さい。

プログラムソースをダウンロードしたら、MPLABにてプロジェクトを作成します。
以下のファイルをプロジェクトディレクトリにコピーしてプロジェクトに取込んで下さい。
次にコンパイルPIC書き込みを実行して下さい。
HI-TECH C Compiler for PIC10/12/16 MCUs(Lite Mode)V9.80コンパイラを使用しています。

ダウンロードしたら解凍して下さい、以下のファイル構成です。
 slave.c ・・・・・・・・・本体のソースプログラム
 skI2Cslave.c  ・・・・・・I2C通信部分のスレーブ関数を記述したライブラリソースプログラム
 skI2Cslave.h  ・・・・・・ヘッダファイル

slave.c

LCDmoniter1  マスターから1秒毎に"0"〜"9"が繰り返し送信されます。
 その受信データをLCDモニターに表示し、受信データに0x11を
 足して送信バッファ(snd_data[ ])にデータをセットします、
 マスターからの送信要求で割込み処理関数が送信バッファの
データを送信します。

上の写真が受信したデータを表示させた結果です。
'0'〜'9'を繰り返し表示します。

また、LCDモニターの出力はRA5から行っているので「skMonitorLCD.h」を下記の様に変更します。
#define _XTAL_FREQ 8000000  // 使用するPIC等により動作周波数値を設定する
#define MONITOR_PIN RA5      // モニタ出力するピンの番号を設定する

skI2Cslave.h
I2C通信のスレーブ側関数用ライブラリヘッダファイルです。
現在の設定で8バイトまでの複数データ送受信が可能ですが、変更する場合は下記の部分を変更して下さい。
#define SND_DATA_LEN 8                  // 送信データバッファのサイズ
#define RCV_DATA_LEN 8                  // 受信データバッファのサイズ
skI2Cslave.c
ここでは12F1822での説明ですが、16F1827もこのままこの関数は利用出来ます。
(PS. おそらく、12F18xxや16F18xxも利用可能でしょう?)

skI2Cslave.cの関数

InitI2C_Slave(address)
 I2C通信のスレーブモードで初期化を行う処理
 address:自分のアドレスを設定します(8〜119)

 この設定では一括呼び出しの同報通知は無効の設定となっています。
 有効にしたければ、SSP1CON2bits.GCEN = 1 をこの関数に追加して下さい。

ans = I2C_ReceiveCheck()
 マスターからの受信状態をチェックする処理
 ans : 戻り値が0なら未受信で、受信したら受信したデータの個数を返す
    受信されたデータはansの個数分rcv_data[ ]の配列変数にセットされています。
skI2Cslave.cのライブラリを使ったサンプル例です。
本ライブラリを使う事により下記の如くシンプルに送受信が可能です。
データ送信はsnd_data[]の配列変数にデータをセットすればマスターからの要求で自動的に送信されます。

void main()
{
     int ans ;

     OSCCON  = 0b01110010 ;   // 内部クロックは8MHzとする
     ANSELA  = 0b00000000 ;   // アナログ使用しない(すべてデジタルI/Oに割当てる)
     TRISA   = 0b00001110 ;   // ピンはRA1(SCL)/RA2(SDA)のみ入力(RA3は入力専用)
     PORTA   = 0b00000000 ;   // 出力ピンの初期化(全てLOWにする)

     InitI2C_Slave(8) ;       // スレーブモードでの初期化、マイアドレスは8とする

     while(1) {
          ans = I2C_ReceiveCheck() ;              // 受信状態のチェック
          if (ans != 0) {
               MonitorPutc(rcv_data[0]) ;         // モニターに表示する
               snd_data[0] = rcv_data[0] + 0x11 ; // 送信データをセットする
          }
     }
}
interrupt InterI2C()
 I2C関連の割り込み処理
 割込みが発生すると自動的に処理されるので通常は気にしなくても良いでしょう。


[次ページ"PIC(マスター)-Arduino(スレーブ)"へ]




記事変更(*6) 2020/04/08
追記(*5) 2016/01/17
追記(*4) 2015/05/02
追記(*3) 2014/11/01
追記(*2) 2014/05/19
追記(*1) 2012/09/14


【きむ茶工房ガレージハウス】
Copyright (C) 2006-2020 Shigehiro Kimura All Rights Reserved.