I2Cの実験(3/6)

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


《通信実験3:複数バイトの送受信》

 前の頁(1/6)(2/6)では1バイト単位の送受信でしたが、ここでは複数バイトの送受信にて実験を行って見ました。 尚、I2Cの概要や接続配線などは前の頁(1/6)を参照して下さい。

 通常I2Cの使い方として、スレーブデバイスにリアルタイムクロックやEEPROM等を接続し、 そのデバイスへコマンドを送信してコマンドに対応したデータを受信すると言う使い方が 一般的だと思いますので、その意味では以下の記事が参考になるのではないかいとおもふぅ

ArduinoをマスターにPICをスレーブ(アドレスは8)にします。
Arduinoの動作としては、
PICへ1秒毎に2バイトのコマンドデータ"01","23","45"の3パターンを繰り返し送信します。
すると、PICから7バイトのデータが返されます、これをArduinoIDEのシリアルモニター画面に表示させます。
PIC側の動作としては、
コマンドデータ"01","23","45"を受信すると、"01"なら"Arduino"、"23"なら"12F1822"、
"45"なら"16F1827"の文字列を送信します。

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( )に書き換え変更しました。 *2)

COM3  IDEに下記のスケッチプログラムをコピーペーストして貼り付けて下さい。
 次にIDEツールバーの「Upload」ボタンをクリックしてコンパイルとarduinoボード
 に書込みを行います。
 シリアルモニター画面を起動させましょう、左の様に表示されたら成功です。
 "Arduino"、"12F1822"、"16F1827"の文字が繰り返し表示されます。

---------------------------------------------------------------------
#include <ctype.h>
#include <string.h>
#include <Wire.h>

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

     for (i=1 ; i<=3 ; i++) {
          if (i==1) memcpy(dt,"01",2) ;
          if (i==2) memcpy(dt,"23",2) ;
          if (i==3) memcpy(dt,"45",2) ;
          Wire.beginTransmission(8) ;   // 通信の開始処理
          Wire.write((byte *)dt,2) ;    // 通信データを準備する
          ans = Wire.endTransmission() ;// データの送信と終了処理
          delay(100) ;                  // スレーブがデータを準備するまで待つ
          ans = Wire.requestFrom(8,7) ; // スレーブ(8)にデータ送信要求(7バイト受信)をだす
          for (j=0 ; j<ans ; j++) {  // 受信したデータの個数だけ繰り返す
               dt[j] = Wire.read()  ;   // 1バイト x 7回のデータを取り出す
          }
          dt[j] = 0x00 ;
          Serial.println(dt) ;          // IDEにシリアル通信で表示する
          delay(1000) ;                 // 1秒ウエイト
     }
}
---------------------------------------------------------------------
PIC(12F1822)側スレーブのプログラム例

下記がプログラムソースです、
HI-TECH C Compiler for PIC10/12/16 MCUs(Lite Mode)V9.80コンパイラを使用しています。
プロジェクトを作成して新規ファイルにコピーペーストして貼り付けて下さい。

このプログラムには「skI2Cslave.c」「skI2Cslave.h」のライブラリが必要です。
ライブラリは前の頁(1/6)のプログラム例からダウンロードして下さい。
(尚、ダウンロードしたファイルslave.cは必要有りません下記プログラムを使用します。)
また、マスターからの受信データはLCDモニターには表示させていません。
(なのでぇ、「skMonitorLCD.c」「skMonitorLCD.h」は使用しません。)
---------------------------------------------------------------------
#include <pic.h>
#include <htc.h>                        // delay用に必要
#include <string.h>
#include "skI2Cslave.h"                 // I2C関数ライブラリー用

#define _XTAL_FREQ   8000000            // delay用に必要(クロック8MHzを指定)

// コンフィギュレーション1の設定
// CLKOUTピンをRA4ピンで使用する(CLKOUTEN_OFF):内部クロック使用する(INTIO)
// 外部クロック監視しない(FCMEN_OFF):外部・内部クロックの切替えでの起動はなし(IESO_OFF)
// 電源電圧降下常時監視機能ON(BOREN_ON):ウオッチドッグタイマー無し(WDTE_OFF)
// 電源ONから64ms後にプログラムを開始する(PWRTEN_ON)
// 外部リセット信号は使用せずにデジタル入力(RA3)ピンとする(MCLRE_OFF)
// プログラムメモリーを保護しない(CP_OFF):データメモリーを保護しない(CPD_OFF)
__CONFIG(CLKOUTEN_OFF & FOSC_INTOSC & FCMEN_OFF & IESO_OFF & BOREN_ON &
         PWRTE_ON & WDTE_OFF & MCLRE_OFF & CP_OFF & CPD_OFF) ;
// コンフィギュレーション2の設定
// 動作クロックを32MHzでは動作させない(PLLEN_OFF)
// スタックがオーバフローやアンダーフローしたらリセットをする(STVREN_ON)
// 低電圧プログラミング機能使用しない(LVP_OFF)
// Flashメモリーを保護しない(WRT_OFF):電源電圧降下常時監視電圧(2.5V)設定(BORV_25)
__CONFIG(PLLEN_OFF & STVREN_ON & WRT_OFF & BORV_25 & LVP_OFF);

// メインの処理
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 >= 2) {
               if ((rcv_data[0]==0x30) && (rcv_data[1]==0x31)) { // "01"コマンド
                    memcpy(snd_data,"Arduino",7) ;
               }
               if ((rcv_data[0]==0x32) && (rcv_data[1]==0x33)) { // "23"コマンド
                    memcpy(snd_data,"12F1822",7) ;
               }
               if ((rcv_data[0]==0x34) && (rcv_data[1]==0x35)) { // "45"コマンド
                    memcpy(snd_data,"16F1827",7) ;
               }
          }
     }
}
---------------------------------------------------------------------

《リピート スタート コンディションの話》

I2C通信の方式は、マスターからの送信で開始され[スタートコンディション][データ][ストップコンディション]といった感じに一連の通信は行われます。

例えば下記の@とAの通信フォーマットのようになります。
 データのフォーマット1

また、数バイト送る場合は下記の様に[スタート][ストップ]の間でデータを繰り返し送る事になります。
 データのフォーマット2

リピート スタート コンディションは[スタート(S)][ストップ(P)]の間に[リピートスタート(R)] を下記の様に送ります、するとぉ、[リピートスタート(R)]の後にアドレスを再指示すればそのスレーブと通信出来る様になる機能です。
それとぉ、マルチマスターを利用する場合はこの方式で行わないと、他のマスターとデータが混戦状態になるでしょう。
(と言うかぁこの方式でなくとも混戦するのですがぁ、マルチマスターの話は次ページを参照下さい)

このフォーマットはリピートスタート機能で同じスレーブに通信する場合
スレーブ(10)への「書込み要求:R/W=0」と「データ送信要求:R/W=1」です。
 データのフォーマット3

このフォーマットはリピートスタート機能で異なるスレーブに通信する場合
スレーブ(10)への「書込み要求:R/W=0」、スレーブ(11)への「書込み要求:R/W=0」です。
 データのフォーマット4

下記のサンプルはPIC(12F1822)でのリピート スタート コンディションを使ったマスター側の例です。
同じスレーブに対して「書込み要求」と「データ送信要求」のリピートスタートを行っています。

スレーブへ1秒毎に"0"〜"9"を送信し、スレーブからのデータを受信したらそのデータをLCDモニターに表示させる サンプルのプログラムです。

なので、このプログラムをコンパイルする際には「skMonitorLCD.c」「skMonitorLCD.h」のライブラリが必要です。 LCDモニターと「skMonitorLCD.c」「skMonitorLCD.h」についてはこちらを参照して下さい。
また、LCDモニターの出力はRA5から行っているので「skMonitorLCD.h」を下記の様に変更します。
#define _XTAL_FREQ 8000000  // 使用するPIC等により動作周波数値を設定する
#define MONITOR_PIN RA5      // モニタ出力するピンの番号を設定する
---------------------------------------------------------------------
#include <pic.h>
#include <htc.h>              // delay用に必要
#include "skMonitorLCD.h"

#define _XTAL_FREQ 8000000    // delay用に必要(クロック8MHzを指定)

// コンフィギュレーション1の設定
// CLKOUTピンをRA4ピンで使用する(CLKOUTEN_OFF):内部クロック使用する(INTIO)
// 外部クロック監視しない(FCMEN_OFF):外部・内部クロックの切替えでの起動はなし(IESO_OFF)
// 電源電圧降下常時監視機能ON(BOREN_ON):電源ONから64ms後にプログラムを開始する(PWRTEN_ON)
// ウオッチドッグタイマー無し(WDTE_OFF):
// 外部リセット信号は使用せずにデジタル入力(RA3)ピンとする(MCLRE_OFF)
// プログラムメモリーを保護しない(CP_OFF):データメモリーを保護しない(CPD_OFF)
__CONFIG(CLKOUTEN_OFF & FOSC_INTOSC & FCMEN_OFF & IESO_OFF & BOREN_ON &
         PWRTE_ON & WDTE_OFF & MCLRE_OFF & CP_OFF & CPD_OFF) ;
// コンフィギュレーション2の設定
// 動作クロックを32MHzでは動作させない(PLLEN_OFF)
// スタックがオーバフローやアンダーフローしたらリセットをする(STVREN_ON)
// 低電圧プログラミング機能使用しない(LVP_OFF)
// Flashメモリーを保護しない(WRT_OFF):電源電圧降下常時監視電圧(2.5V)設定(BORV_25)
__CONFIG(PLLEN_OFF & STVREN_ON & WRT_OFF & BORV_25 & LVP_OFF);

// I2C関連の割り込み処理
void interrupt InterI2C( void )
{
     if (SSP1IF == 1) {       // SSP(I2C)割り込み発生か?
          SSP1IF = 0 ;        // フラグクリア
     }
     if (BCL1IF == 1) {       // MSSP(I2C)バス衝突割り込み発生か?
          BCL1IF = 0 ;        // 今回はフラグのみクリアする(無処理)
     }
}
// アイドル状態のチェック
// ACKEN RCEN PEN RSEN SEN R/W (BF) が全て0ならOK
void I2C_IdleCheck(char mask)
{
     while (( SSP1CON2 & 0x1F ) | (SSP1STAT & mask)) ;
}
// メインの処理
void main()
{
     unsigned char dt ;
     int i ;

     OSCCON  = 0b01110010 ;   // 内部クロックは8MHzとする
     ANSELA  = 0b00000000 ;   // アナログは使用しない(すべてデジタルI/Oに割当てる)
     TRISA   = 0b00001110 ;   // ピンはRA1/RA2のみ入力(RA3は入力専用)
     PORTA   = 0b00000000 ;   // 出力ピンの初期化(全てLOWにする)
     // I2C(マスターモード)の初期化
     SSP1STAT= 0b10000000 ;   // 標準速度モードに設定する(100kHz)
     SSP1CON1= 0b00101000 ;   // SDA(RA2)/SCL(RA1)ピンは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)バス衝突割り込みフラグをクリアする

     __delay_ms(2000) ;
     MonitorPutc(0x11) ;      // モニターの表示位置を設定する
     __delay_ms(3000) ;       // 5秒後に開始する

     while(1) {
          // 英文字の"0"〜"9"を1バイトずつ繰り返し送る
          for (i=0x30 ; i<=0x39 ; i++) {
               // スタート(START CONDITION)
               I2C_IdleCheck(0x5) ;
               SSP1CON2bits.SEN = 1 ;

               // [スレーブのアドレス+スレーブは受信(書込み要求)]を送信する
               I2C_IdleCheck(0x5) ;
               SSP1BUF = (char)(10 << 1) ;      // アドレスは10 R/W=0
               while(SSP1CON2bits.ACKSTAT==1) ;

               // [データ]を送信する
               I2C_IdleCheck(0x5) ;
               SSP1BUF = (char)i ;
               while(SSP1CON2bits.ACKSTAT==1) ;

               // リピート・スタート(REPEATED START CONDITION)
               I2C_IdleCheck(0x5) ;
               SSP1CON2bits.RSEN = 1 ;

               // [スレーブのアドレス+スレーブへデータ送信要求]を送信する
               I2C_IdleCheck(0x5) ;
               SSP1BUF = (char)((10 << 1)+1) ;      // アドレスは10 R/W=1
               while(SSP1CON2bits.ACKSTAT==1) ;

               // [データ]を受信する
               I2C_IdleCheck(0x5) ;
               SSP1CON2bits.RCEN = 1 ;  // 受信を許可する
               I2C_IdleCheck(0x4) ;
               dt = SSP1BUF ;           // 受信
               I2C_IdleCheck(0x5) ;
               SSP1CON2bits.ACKDT = 1 ; // ACKデータはNOACK
               SSP1CON2bits.ACKEN = 1 ; // ACKデータを返す

               // ストップ(STOP CONDITION)
               I2C_IdleCheck(0x5) ;
               SSP1CON2bits.PEN = 1 ;

               // モニターに受信データを送る
               MonitorPutc(dt) ;

               // 1秒ウエイト
               __delay_ms(1000) ;
          }
     }
}
---------------------------------------------------------------------
※リピートスタート機能はPICとPICでの通信はOKでしたが、
 Arduino(スレーブ)(Wireライブラリー)はこの機能に対応していないらしく通信不能でした。
 また、Arduinoをマスター側にしてもリピートスタート機能では送信できません。

 PS. *2)
 Wireライブラリの"Wire.requestFrom"と"Wire.endTransmission"関数に
 Arduino1.0.1以降で3つ目のstopパラメータ(省略可)が追加されています、
 これによりstop送受信の制御が行えるのでリピートスタート送信が出来るかも(未実験)。

マスター受信時のACK/NOACKについて

マスターはデータを受信したらACKを返しますが、最後のデータであれば下記の様にNOACKを返す必要があります。

1バイト受信時のフォーマットです。
 データのフォーマット51
複数バイト受信時のフォーマットです。
 データのフォーマット52

《一斉に同報送信の話》

通常、マスターは送信する場合は相手スレーブのアドレスを指定し1対1で通信を確定して送信を行いますが、 アドレスを0(ゼロ)で送信すると接続されているすべてのスレーブに一斉に通知する事が出来ます。 尚、一斉通知は「マスターからの書込み要求:R/W=0」のみです。
また、PICが一斉通知を受信する為にはSSP1CON2レジスターのGCENビットを1にします。

※Arduino(スレーブ)(Wireライブラリー)はこの機能に対応していないらしく通信不能でしたが、
 Arduinoをマスターでアドレス0を送ることは可能でした。

《実験の風景》

 実験の風景
上のブレッドボードがLCDモニターです。
下のブレッドボードの右が12F1822で、 左が16F1827です。
Arduinoと16F1827がスレーブ
12F1822をマスターでの実験です。


16F1827のSCL/SDAピンについて
このHPで紹介した[skI2Cmaster.c][skI2Cslave.c]のライブラリプログラムはそのまま 16F1827で利用可能ですがピンのアサインが異なるので下記に記載します。

16F1827
このPICはI2C機能(MSSP)が2個有ります、ってことわぁ、SPIとI2Cが同時に使用可能ってことですねぇ
(MSSPが1個だとI2CかSPIのどちらかしか利用できません。)

I2C1(MSSP1)は
SCL1:10番ピン(RB4) SDA1:7番ピン(RB1)を使用します。
ANSELB = 0b00000000 ; // AN5-AN11は使用しない全てデジタルI/Oとする
TRISB    = 0b00010010 ; // ピン(RB1/4)を入力に割当てる

I2C2(MSSP2)は
SCL2:11番ピン(RB5) SDA2:8番ピン(RB2)を使用します。
ANSELB = 0b00000000 ; // AN5-AN11は使用しない全てデジタルI/Oとする
TRISB    = 0b00100100 ; // ピン(RB2/5)を入力に割当てる
※I2C2を利用する場合は[skI2Cmaster.c][skI2Cslave.c]を変更する必要が有ります。
 (レジスター名のSSP1の文字をSSP2に変更すればOKとおもふぅ、実験してませ〜ん)


[前ページ"PIC(マスター)-Arduino(スレーブ"へ] [次ページ"PICでのマルチマスターの通信"へ]



追記(*2) 2020/03/05
追記(*1) 2012/09/14


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