I2C接続でLED点灯

  1. I2C接続


    1. I2C接続の特徴

      I2C接続は、SCL(クロック信号)とSDA(双方向のデータ信号)の2本の信号で接続できる汎用のインタフェースです。この接続の基本形として、LEDを点灯するシステムを紹介します。

    2. システム構成

       システムは、I2Cマスタデバイスと複数のI2Cスレーブデバイスから構成されます。ここでは、LEDを点灯するだけですから、マスターとスレーブはほぼ同じ回路で構成できます。


    3. 16F873マスタ回路

       下図は、16F873をマスタとした例です。ICSPはPICの B7、B6 端子で16F88と同じですが、I2Cの端子は、SCLがPICのC3、SDAがC4の端子に接続します。こちらは、LEDをBポートに接続します(特に意味はありません)。



    4. スレーブ回路とI2C接続回路

       スレーブ回路は、SCL、SDA、に接続している抵抗(1k)を省くだけで、同じ回路が利用できます。マスタ回路とスレーブ回路(ledSlave)を作成し、SCLとSDAを接続します。マスタ回路では、SCLとSDAの信号を抵抗を通して電源5Vに接続します。下図の抵抗はマスタ回路に内臓されている抵抗を意味します。

  2. プログラム


    1. I2Cの設定

       CSCコンパイラで 16F88に対し、I2C のマスタを機能を設定するには以下のようにします。
      #use i2c(master,sda=PIN_B1,scl=PIN_B4)
       先頭の master が、マスタとなる設定、sda=PIN_B1, scl=PIN_B4 はsda 信号とscl 信号の端子を指定します。スレーブの場合は以下のように設定します。
      #use I2c(slave,sda=PIN_B1,scl=PIN_B4,address=0x62,force_hw)
       先頭の slave でスレーブ機能を設定します。異なるのは、address=0x62 でアドレスを設定することと、最後の force_hw です。これは、i2c の機能をハードウエアで実行することを指示します。実は、16F88 の場合、スレーブの機能はハードウエアで実装されていますが、マスタの機能は実装されていないため、ソフトウエアで行う必要があります。16F88 に対し、
       #use i2c(master,sda=PIN_B1,scl=PIN_B4,force_hw)
      を指定すると、期待通りの動作はしませんから、注意してください。ただし、16F873の場合、マスタ機能もハードウエアで実装されていますから、上記の設定が可能です。
       
    2. I2Cのマスタ側アクセス関数

       マスタとして、スレーブにデータを送るには、以下のような関数呼び出しをします。
          i2c_start();       //スタートコンディション
          i2c_write(Leddva);
          //delay_us(20);
          i2c_write(val %12 ); 
          i2c_stop();
       i2c_start(); で開始し、スタート状態を設定します。次に、i2c_write(Leddva); で、スレーブのアドレスを指定します。自分のアドレスを受け取ったスレーブのみが、以下のデータを受け取ります。i2c_write(val %12 );  でLEDの点灯パタンを送ります。val %12  は特に意味がありません。スレーブのLED が4個なので、15以内のデータであれば表示ができます。続くデータがあれば、i2c_write(val %12 ); で次のデータを送りますが、ここでは不要なので、i2c_stop(); で終了します。
       この呼び出しは、16F88でも16F873でも同じです。

    3. I2Cのスレーブ側のアクセス関数(ハード処理の場合)

       I2Cをハードウエアで処理できる場合、スレーブ側は割り込み機能を利用できます。マスタが指定したアドレス以外のスレーブは割り込みがありませんから、殆ど負荷はかかりません。
      #INT_SSP 
      void ssp_interupt (){   }
      で、I2C の割込み関数を宣言します。state = i2c_isr_state(); で割込みの状態を知ります。state が 0 より大きく 0x7F 以下なら、データを受信したことを意味します。val2=i2c_read() でデータを受け取ることができます。
       データが 0x80 以上の場合は、マスタからのデータ要求を意味します。i2c_write( ) で直ちにマスタにデータを送り必要があります。
      スレーブはマスタの要求に対して応答すればよいので、ハードウエアの支援がある場合割り込み処理で I2C の処理を行うことができます。

    4. 16F873のマスタ側プログラム

      16F873によるマスター側のプログラムを紹介します。valの値を増やしながら、I2Cのスレーブに送ります。#use i2C()は FORCE_HW でハードウエア機能を利用します。
      //////////////////////////////////////////////////
      // I2Cマスターテストプログラム      //  
      //I2CでLED点灯
      //I2Cデバイスアドレスは0x64
      //コマンド (val)、valは4bit
      
      //マスタの回路は、C4,C3をI2C接続
      //A0 をLED表示(動作確認用)
      
      #include <16f873.h>
      #fuses HS,NOWDT,NOPROTECT,NOLVP,NOBROWNOUT
      #use delay(clock = 20000000)      
      #use i2c(MASTER,sda=PIN_C4,scl=PIN_C3,FORCE_HW)
      
      //メイン関数/////////////////////////////////////////
      #define Leddva 0x64
      int val;
      //int tm,num;
      
      void main(){
          val=0;
          //tm=0;
          //set_tris_c(0x99);          //RB 7-4:IN 3-0:OUT    
          output_float(PIN_C3);       //I2C pin float
          output_float(PIN_C4);       //I2C pin float 
          //Cポートに出力する場合、set_tris_c()が必要  
          
          delay_ms(100); //wait for Client up  
             
          while(1){
               output_toggle(PIN_A0);//動作確認                       
                  i2c_start();       //スタートコンディション
                      i2c_write(Leddva);
                      //delay_us(20);
                      i2c_write(val  %12 ); 
                      i2c_stop();
                     val++;
                                                         
               delay_ms(20);
      
              }        
      }

    5. 16F873のスレーブ側プログラム

       マスタがデバイスアドレス 0x64 にデータを送信した場合、マスタからのデータを受け取り LED に表示します。ハードウエアの機能でI2C割り込みを利用して受信します。
      //i2cLEDSlv873.c
      //割り込みでi2cからデータを受け取りLEDを点灯
      //
      
      #include <16F873A.h>
      
      #fuses HS,NOWDT,NOPROTECT,NOLVP,NOBROWNOUT
      #use delay(clock = 20000000) 
      #use I2c(slave,sda=PIN_C4,scl=PIN_C3,address=0x64,force_hw)
      
      #use fast_io(C)
      #use fast_io(B)
      
      int val2;
      int state;
      
      #INT_SSP
      void ssp_interupt (){    
         state=i2c_isr_state();
         //output_toggle(PIN_A0);
         
         if(state>=0x80){//addrs match with master read         
              //i2c_write(0);//return  value
          }
          else if(state >0){//adrs match with master write
              val2=i2c_read();//1:ack,default 0:nack
              output_b(val2);
          }    
      }
      
      void main (){
              
          set_tris_b(0xF0);
          output_float(PIN_C4);  //I2C pin float
          output_float(PIN_C3);  //I2C pin float
          set_tris_c(0xFE);  
          
          val2=0;
          enable_interrupts(INT_SSP);     //SSP割り込みを許可
          enable_interrupts(GLOBAL);      //全ての割り込みを許可
         
           while (1) {
            //output_a(val2);
          }              
      }
       
       このプログラムの流れは、16F88 の場合も同様です。データ受信後に表示するLEDのポートが異なる(AポートにLEDを接続)のみです。

  3. 16F88による構成

    1. 16F88マスタ回路

      16F88を用いたI2Cのマスタ回路です。PICKIT2 でプログラムするためのICSPコネクタと 電源付の I2C を接続しています。ICSP の VPP 端子は PICのCLR 端子に接続します。プログラム時に12Vがかかります。VCCとGNDは電源とグランドを接続します。PGDとPGCはプログラム用のデータとクロック端子で、PICのB7とB6端子に接続します、ポートA端子のLEDは、動作のモニタ用で、マスタ動作の場合なくてもかまいません。
       I2C 端子は電源を含めた4端子になっています。電源が別途供給可能なら、電源端子は省略可能です。I2CはSCL(クロック信号)をPICのB4に、SDA(データ信号)をPICのB3に接続します。ここでは、PICのクロックは、内臓のRCを利用します。





    2. 16F88マスタのプログラム

       16F88でI2Cで接続したスレーブにデータを送るプログラムです。16F88は内臓の4Mクロックを利用します。16F88は i2Cのマスタ機能は実装していませんから
      #use i2c(MASTER,slow,sda=PIN_B1,scl=PIN_B4)
      では、force_hw を指定できません。#define Leddva 0x64 はスレーブのデバイスアドレスを定義しています。
      この回路の場合、ポートBは I2C しか利用していませんが、#use fast_io(B) で、Bポートの入出力を、プログラムで管理することとし、I2Cで利用する B4,B1 は出力に設定後、output_float(PIN_B1); でフロート(0でも1でもない) 状態に設定しておきます。
       繰り返しループの中で、一つづつ値を増しながら、0x64 のスレーブに val%12 のデータを送ります。

      //I2CLedMst88.c
      // I2Cマスターテストプログラム      //  
      //I2CでLED点灯
      //ポートA にLEDを接続(動作確認用)
      
      #include <16f88.h>
      
      #fuses INTRC_IO,NOWDT,NOPROTECT,NOLVP,NOBROWNOUT
      #use delay(clock = 4000000)      
      #use i2c(MASTER,slow,sda=PIN_B1,scl=PIN_B4)
      
      #use fast_io(B)
      #use fast_io(A)
      
      //メイン関数/////////////////////////////////////////
      #define Leddva 0x64
      
      int val;
      int tm,num;
      
      void main(){
      
          val=0;
          tm=0;
          set_tris_a(0xF0);          //RB 7-4:IN 3-0:OUT
          set_tris_b(0xED);
           
          output_float(PIN_B1);       //I2C pin float
          output_float(PIN_B4);       //I2C pin float 
          
          delay_ms(100); //wait for Client up  
             
          while(1){
      
          i2c_start();       //スタートコンディション
          i2c_write(Leddva);
          //delay_us(20);
          i2c_write(val %12 ); 
          i2c_stop();
      
          output_a(val%12);
          val++;
          delay_ms(200);
      
          }
      }

    3. スレーブの回路

      マスターの回路と同じです。ただし、I2Cのプルアップ抵抗(1kΩ)は省略します。

    4. スレーブのプログラム

      88もスレーブのハード機能は備えていますから、873と同様のプログラムができます。ポートAの出力をビット単位で行っていますが、output_a(val2) でもたぶん大丈夫です。
      //I2C Slave
      //マスタからのデータでAポートのLEDを点灯
      //I2Cアドレス 0x62
      #include <16F88.h>
      
      //#fuses HS,NOWDT,NOLVP,NOBROWNOUT
      
      #fuses INTRC_IO,NOWDT,NOLVP,NOBROWNOUT
      #use delay(clock = 4000000)  //設定を間違えるとI2Cの受信を失敗する 
         
      #use I2c(slave,sda=PIN_B1,scl=PIN_B4,address=0x64,force_hw)
      
      #use fast_io(B)
      #use fast_io(A)
      
      int val2;
      int state;
      
      #INT_SSP
      void ssp_interupt (){
          
         state=i2c_isr_state();
        
         if(state>=0x80){//addrs match with master read    
          //i2c_write(0);//return  value
          }
          else if(state >0){//adrs match with master write
           val2=i2c_read();//1:ack,default 0:nack
           output_bit(PIN_A0,val2&0x1);
           output_bit(PIN_A1,val2&0x2);
           output_bit(PIN_A2,val2&0x4);
           output_bit(PIN_A3,val2&0x8);
           
           output_toggle(PIN_A4);//受信で点滅
          }    
      }
      
      void main (){
          set_tris_a(0x20);
                         
          output_float(PIN_B1);  //I2C pin float
          output_float(PIN_B4);  //I2C pin float
          set_tris_b(0x36); //I2Cの端子は入力に設定    
          val2=0;              
           enable_interrupts(INT_SSP);     //SSP割り込みを許可
           enable_interrupts(GLOBAL);      //全ての割り込みを許可
         
           while (1) {
            sleep();
          }       
      }