I2Cの実験(2/6)

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


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

 前の頁(1/6)ではArduinoをマスターにPICをスレーブにした実験でしたが、
こんどはPICをマスターにArduinoをスレーブにした実験です。
尚、I2Cの概要や接続配線などは前の頁(1/6)を参照して下さい。

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

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

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

 データのフォーマット1

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 を利用する人は読み換えやスケッチ変更をお願いします。 *2)
Wire.write( )/Wire.read( )に書き換え変更しました。 *5)

Wireライブラリのスレーブ関連の説明。

@I2Cを行う為の初期化を行います。
 Wire.begin(8) : これでスレーブ設定です、マイアドレスは8とします。

Aマスターからの書き込み要求(R/W=0)を受けた時の割込み関数を登録する
 Wire.onReceive(receiveEvent) : receiveEventは割込みの関数名です。
 例)
 byte dt ;
 void receiveEvent(int howMany) 
 {
      while(Wire.available()) {    // 受信データが0個になるまで繰り返す
           dt = Wire.read()  ;     // 1バイト受信する
           Serial.print(dt) ;      // IDEにシリアル通信で表示する
      }
 }
Bマスターからの読み出し要求(R/W=1)を受けた時の割込み関数を登録する
 Wire.onRequest(requestEvent) : requestEventは割込みの関数名です。
 例)
 byte dt ;
 void requestEvent() 
 {
      dt = dt + 0x11 ;             // 受信データに0x11だけ足して送り返す
      Wire.write(dt) ;             // マスターに1バイト送信する
 }
マスターからの要求により、AとBが割込み処理されます。

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

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

byte dt ;

// データを受信すると処理される割込み関数
void receiveEvent(int howMany) 
{
     while(Wire.available()) {
          dt = Wire.read()  ;           // 1バイト受信する('0'〜'9')
          Serial.print(dt) ;            // IDEにシリアル通信で表示する
     }
}
// データ要求を受信すると処理される割込み関数
void requestEvent() 
{
     dt = dt + 0x11 ;                   // 受信データに0x11だけ足して送り返す
     Wire.write(dt) ;                   // マスターに1バイト送信する('A'〜'I')
}
// 電源起動時とリセットの時だけのみ処理する関数
void setup()
{
     Serial.begin(9600) ;               // シリアル通信の初期化
     Wire.begin(8) ;                    // I2Cの初期化、マイアドレスは8とする
     Wire.onRequest(requestEvent) ;     // 割込み関数の登録
     Wire.onReceive(receiveEvent) ;     // 割込み関数の登録
}
// 繰り返し実行されるメインの処理関数
void loop()
{
}
---------------------------------------------------------------------
COM2
 この左の画面がPICから受信したデータをArduinoIDEの
 シリアルモニター画面に表示させた結果です。
 '0'〜'9'を繰り返し表示します。

PIC(マスター)での操作

HI-TECH C Compiler for PIC10/12/16 MCUs(Lite Mode)V9.80コンパイラでの説明です。

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

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

ビット
機能 SMP D/A R/W BF
Bit7  :SMP  通信速度情報(実際の速度はSSP1ADDレジスタで行う)
          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 ビット情報
          0 : 送信動作中ではない
          1 : 送信動作中
Bit0  :BF    バッファ フル ステータス ビット
          受信時
          0 : 受信は未完了、SSPBUF は空
          1 : 受信完了、SSPBUF フル
          送信時
          0 : データ送信完了 (ACK およびストップ ビットは含まない)、SSPBUF は空
          1 : データ送信中 (ACK およびストップ ビットを含まない)、SSPBUF はフル

SSPCON1:制御レジスタ1
ビット
機能 WCOL SSPOV SSPEN SSPM3 SSPM2 SSPM1 SSPM0
Bit7  :WCOL  書き込み衝突検出ビット(今回このビットは未使用)
           0 : 衝突なし
           1 : 前のデータを処理していないのに次のデータが、
              SSPBUFへ書き込みを実行された(ソフトウエアでクリアする必要が有る)
Bit6   :SSPOV  受信オーバーフローインジケータビット(今回このビットは未使用)
           0:オーバーフローはなし
           1:SSPBUFがデータを保持中に次のデータを受信した
             (ソフトウエアでクリアする必要が有る)
Bit5  :SSPEN  同期シリアル ポート イネーブル ビット
           0 :SDA/SCLピンは I/O ポートピンとして設定される
            : SDA/SCLピンはI2Cピンとして使用される(ピンは入力に設定する)
Bit0-3:SSPM   同期シリアル ポート モードの選択ビット
           1000 : I2C マスター モード、クロック = FOSC / (4 * (SSPADD+1))
           1011 : I2C ファームウェア制御のマスター モード ( スレーブ アイドル)

SSPCON2:制御レジスタ2
ビット
機能 ACKSTAT ACKDT ACKEN RCEN PEN RSEN SEN
Bit6  :ACKSTAT アクノレッジ ステータス ビット
            0 : アクノレッジ信号(ACK)が受信された
            1 : アクノレッジ信号(ACK)は受信されていない
Bit5  :ACKDT  アクノレッジ データ ビット(受信モード時に使用)
            データ受信の後でアクノレッジ信号を返答すると送信される値です。
            0 : ACK ( OK)
            1 : NACK ( 拒否)
Bit4  :ACKEN  アクノレッジ信号の返答 イネーブル ビット(受信モード時に使用)
            0 : アクノレッジ シーケンスはアイドル状態
            1 : SDA ピンとSCL ピンでアクノレッジ信号の返答を開始しする。
              (ACKDTビット内容を送信)、ハードウェアで自動的にクリアされる
Bit3  :RCEN   受信イネーブル ビット
            0 : 受信はアイドル状態
            1 : I2Cの受信モードを有効にする(スレーブから受信する場合に使用する)
Bit2  :PEN    ストップ コンディションのイネーブル ビット
            SCK リリース制御
            0 : ストップ コンディションはアイドル状態
            1 : SDA ピンと SCL ピンでストップコンディションをの開始を指示する。
               ハードウェアで自動的にクリアされる
Bit1  :RSEN   リピート スタート コンディションのイネーブル ビット
           0 : リピート スタートコンディションはアイドル状態
           1 : SDAピンとSCLピンでリピート スタートコンディションの開始を指示する。
               ハードウェアで自動的にクリアされる
Bit0  :SEN    スタート コンディションのイネーブル ビット
            0 : スタートコンディションはアイドル状態
            1 : SDA ピンと SCL ピンでスタートコンディションの開始を指示する。
               ハードウェアで自動的にクリアされる

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

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

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

BSSP1ADDレジスタの設定
 通信速度クロック値を設定します。
 クロック値=FOSC/((SSPADD + 1)*4)  8MHz/((0x13+1)*4)=0.1(100KHz)
 なので SSP1ADD = 0x13 とします。
 クロック値一覧
 但し、400KHzに設定しても、250KHz程しか実際は出ません、100KHzより高速になりますよ程度です
 (値を小さく設定して行けば、速度も高速になりますが使い物になるかは十分実験しましょう) *3)

C割込み関連レジスタの設定
 SSP1IE = 1 ; SSP(I2C)割り込みを許可する
 BCL1IE = 1 ; MSSP(I2C)バス衝突割り込みを許可する
 PEIE  = 1 ; 周辺装置割り込みを許可する
 GIE   = 1 ; 全割り込み処理を許可する
 SSP1IF = 0 ; SSP(I2C)割り込みフラグをクリアする
 BCL1IF = 0 ; MSSP(I2C)バス衝突割り込みフラグをクリアする
以下がレジスタ設定の例です。
     SSP1STAT= 0b10000000 ;   // 標準速度モードに設定する(100kHz)
     SSP1CON1= 0b00101000 ;   // SDA/SCLピンはI2Cで使用し、マスターモードとする
     SSP1ADD = 0x13       ;   // クロック=FOSC/((SSPADD + 1)*4) 8MHz/((0x13+1)*4)=0.1(100KHz)
     SSP1IE = 1 ;             // SSP(I2C)割り込みを許可する
     BCL1IE = 1 ;             // MSSP(I2C)バス衝突割り込みを許可する
     PEIE   = 1 ;             // 周辺装置割り込みを許可する
     GIE    = 1 ;             // 全割り込み処理を許可する 
     SSP1IF = 0 ;             // SSP(I2C)割り込みフラグをクリアする
     BCL1IF = 0 ;             // MSSP(I2C)バス衝突割り込みフラグをクリアする
マスターからの書き込み要求(マスターは送信:スレーブは受信)

次の順番で操作します。

@スタートコンディションの発行
 SSP1CON2bits.SEN = 1 ;

AスレーブへアドレスとR/W=0を送信する
 SSP1BUF = (adrs << 1) ;       // アドレスを送信 R/W=0
 SSP1CON2bits.ACKSTATを見て相手からACK返答を待つ
 (ACK返答は"skI2Cmaster.c"のやり方を参考にして下さい。)  *1)

Bスレーブへデータを送信する
 SSP1BUFにデータをセットする
 SSP1CON2bits.ACKSTATを見て相手からACK返答を待つ
 (複数データを送信するならBを繰り返す)

Cストップコンディションの発行
 SSP1CON2bits.PEN = 1 ;
プログラムのサンプル例
void I2C_Send(unsigned char adrs,int len,char *buf)
{
     int i ;
     
     // スタート(START CONDITION)
     I2C_IdleCheck(0x5) ;
     SSP1CON2bits.SEN = 1 ;
     // [スレーブのアドレス+スレーブは受信を要求]を送信する
     I2C_IdleCheck(0x5) ;
     SSP1BUF = (char)(adrs << 1) ;         // アドレスを送信 R/W=0
     while(SSP1CON2bits.ACKSTAT==1) ;
     // [データ]を送信する
     for (i=0 ; i<len ; i++) {
          I2C_IdleCheck(0x5) ;
          SSP1BUF = (char)*buf ;           // データを送信
          buf++ ;
          while(SSP1CON2bits.ACKSTAT==1) ; // 相手からのACK返答を待つ
     }
     // ストップ(STOP CONDITION)
     I2C_IdleCheck(0x5) ;
     SSP1CON2bits.PEN = 1 ;
}
マスターからの読み出し要求(マスターは受信:スレーブは送信)

次の順番で操作します。

@スタートコンディションの発行
 SSP1CON2bits.SEN = 1 ;

AスレーブへアドレスとR/W=1を送信する
 SSP1BUF = (adrs << 1)+1 ;      // アドレスを送信 R/W=1
 SSP1CON2bits.ACKSTATを見て相手からACK返答を待つ
 (ACK返答は"skI2Cmaster.c"のやり方を参考にして下さい。)  *1)

Bスレーブからのデータを受信する
 SSP1CON2bits.RCEN = 1 ;      // 受信を許可する
 SSP1BUFからデータを取り出す

Cスレーブにデータ受信の返答(アクノレッジ シーケンス)を行う
 SSP1CON2bits.ACKDT = 0(ACK)
 SSP1CON2bits.ACKEN = 1
 複数のデータを受信する場合はBCを繰り返す。
 ただし、受信データの最後は SSP1CON2bits.ACKDT = 1(NACK) で返す。
 マスター受信時のACK/NOACKについての項を参照下さい。

Cストップコンディションの発行
 SSP1CON2bits.PEN = 1 ;
プログラムのサンプル例
void I2C_Receive(unsigned char adrs,int len,char *buf)
{
     unsigned dt ;
     int i ;
     
     // スタート(START CONDITION)
     I2C_IdleCheck(0x5) ;
     SSP1CON2bits.SEN = 1 ;
     // [スレーブのアドレス+スレーブへデータ要求]を送信する
     I2C_IdleCheck(0x5) ;
     SSP1BUF = (char)((adrs << 1)+1) ; // アドレスを送信 R/W=1
     while(SSP1CON2bits.ACKSTAT==1) ;
     for (i=1 ; i<=len ; i++) {
          // [データ]を受信する
          I2C_IdleCheck(0x5) ;
          SSP1CON2bits.RCEN = 1 ;      // 受信を許可する
          I2C_IdleCheck(0x4) ;
          *buf = SSP1BUF ;             // 受信
          buf++ ;
          I2C_IdleCheck(0x5) ;
          if (i==len) SSP1CON2bits.ACKDT = 1 ; // ACKデータはNOACK     *4)
          else        SSP1CON2bits.ACKDT = 0 ; // ACKデータはACK
          SSP1CON2bits.ACKEN = 1 ;             // ACKデータを返す
     }
     // ストップ(STOP CONDITION)
     I2C_IdleCheck(0x5) ;
     SSP1CON2bits.PEN = 1 ;
}
PIC(12F1822)側のプログラム例

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

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

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

※ skI2Cmaster.cは2012/01/21にバージョン2.0になりました。  *1)
  スレーブからのACK返答待ち処理を変更しました、I2C_Send/I2C_Receive関数に戻り値を追加。

master.c

LCDmoniter2  スレーブへ1秒毎に"0"〜"9"が繰り返し送信されます。
 スレーブは、その受信したデータに0x11を足して返します。
 マスターはそれを受信してデータをLCDモニターに表示します。

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

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

skI2Cmaster.h
I2C通信のマスター側関数用ライブラリヘッダファイルです。

skI2Cmaster.c
ここでは12F1822での説明ですが、16F1827もこのままこの関数は利用出来ます。
(PS. おそらく、12F18xxや16F18xxも利用可能でしょう?)

skI2Cmaster.cの関数

InitI2C_Master()
 I2C通信のマスターモードで初期化を行う処理
 尚、SSP1ADD = 0x13 で通信速度(100kHz)は初期化しています。
 これはCPUクロックが8MHzですので、他のCPUクロック使用時は変更(上記一覧参照)して下さい。

ans = I2C_Send(adrs,len,*buf)
 スレーブにデータを指定した個数(len)だけ送信する処理(スレーブへの書込み要求)
 adrs : スレーブのアドレスを指定する(8〜119を推奨)
 len  : 送信するデータ(バイト)の個数を指定する
 *buf : 送信するデータを格納した配列を指定する
 ans  : 0は正常 1はアドレス送信に返答がない 2:相手がデータの受信を拒否した  *1)
 例)
  int  ans ;
  char buf[4] ;

  buf[0] = 0x30 ;
  ans = I2C_Send(8,1,buf) ;  // スレーブアドレスは8で1バイト送信する
  if (ans == 0) 正常に送信した
  else          異常
ans = I2C_Receive(adrs,len,*buf)
 スレーブからデータを指定した個数(len)だけ受信する処理(スレーブへのデータ送信要求)
 adrs : スレーブのアドレスを指定する(8〜119を推奨)
 len  : 受信するデータ(バイト)の個数を指定する
 *buf : 受信したデータを格納する配列を指定する
 ans  : 0は正常 1はアドレス送信に返答がない  *1)
 例)
  int  ans ;
  char buf[4] ;

  ans = I2C_Receive(8,1,buf) ;         // スレーブアドレスは8で1バイト受信する
  if (ans == 0) MonitorPutc(buf[0]) ;  // LCDモニターに表示する
  else          異常

skI2Cmaster.cのライブラリを使ったサンプル例です。
本ライブラリを使う事により下記の如くシンプルに送受信が可能です。

void main()
{
     char buf[8] ;
     int i , ans ;

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

     InitI2C_Master() ;       // I2C通信:マスターモードでの初期化処理

     while(1) {
          for (i=0x30 ; i<=0x39 ; i++) {
               buf[0] = i ;
               ans = I2C_Send(8,1,buf) ;     // スレーブに1バイト送信する
               if (ans == 0) {
                    __delay_ms(100) ;        // スレーブがデータを準備するまで待つ
                    I2C_Receive(8,1,buf) ;   // スレーブから1バイト受信する
                    MonitorPutc(buf[0]) ;    // LCDモニターに表示する
               } else MonitorPuts("NG") ;    // スレーブが返答しない
               __delay_ms(1000) ;            // 1秒ウエイト
          }
     }
}
interrupt InterI2C()
 I2C関連の割り込み処理
 割込みが発生すると自動的に処理されるので通常は気にしなくても良いでしょう。
 ちなみに単純に割込みフラグをクリアしているだけです。


[前ページ"Arduino(マスター)-PIC(スレーブ)"へ] [次ページ"複数バイトの送受信"へ]




記事変更(*5) 2020/04/08
NOACK発行処理のバグを変更(*4) 2017/12/20
追記(*3) 2015/07/19
追記(*2) 2012/09/14
追記(*1) 2012/01/21


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