通信方式としてSPIとセットにして解説されるI2C。Arduinoでは必要なハードウェア設定は代わりにやってくれるので、すぐにプログラムが組めますが、PICマイコンではその箇所もきっちりとプログラムしないといけないので、まあまあやっかいです。

個人的な意見ですが、サンプルの制作に当たっては、Arduinoのプログラムコードを参考にする場合に気をつけなければいけない点として、これらが挙げられます。



ではマイクロチップ社のI2C方式I/Oエキスパンダー「MCP23017」のLチカを通して大まかなプログラムの流れを組んでみましょう。
MCP22017の左下にあるGNDの3本線はスレーブアドレスの定義用です。すべてが0だとアドレスは「0x20(0b10000)」ですが、3本すべてに電圧をかけている状態だと「0x27(0b10111)」になります。 その右隣にある電源から引っ張ってきた線はオフになるとリセットされます。プログラムを書き込んだ直後で正しく動かないようであれば、この線を挿し直してみましょう。

PIC32MX220F032Bでは、I2Cの信号線の位置は固定です。この例ではI2C2に当たるRB2とRB3を接続しています。

400kHz以上の高速度でデータをやりとりする場合は「I2C2CONbits.DISSLW = 0」のままでかまいません。この周波数を指示するレジスタが「I2CxBRG」ですが、計算式はメーカーによると「(I2Cクロック) = (周辺クロック)/(2*(I2CxBRG + 2))」とのことなので、以下の数値が目安になります。

周辺クロックI2C周波数I2CxBRGの値
80MHz400kHz0x062
80MHz100kHz0x18E
40MHz400kHz0x030
40MHz100kHz0x0C6
20MHz400kHz0x017
20MHz100kHz0x062
#include <stdio.h>
#include <stdlib.h>
#include <p32xxxx.h>

typedef unsigned char byte;

#pragma config FNOSC = PRIPLL // 発信源 = 主外部発振器
#pragma config POSCMOD = XT // 主発振の方法 = 外部高精度発振器を使う
#pragma config FPLLIDIV = DIV_2 // PLL入力分数 = x1/2
#pragma config FPLLMUL = MUL_20 // PLL逓倍比 = x20
#pragma config FPLLODIV = DIV_2 // PLL出力分数 = x1/2
#pragma config FPBDIV = DIV_2 //周辺モジュールクロック倍数 : x1/2

// レジスタに登録するアドレスは7bitの数値を1bit左にずらしたものになる
#define MCP23017_ADDR (0x20 << 1)

void IdleI2C(void)
{
    // スタート、待機、受信、ACK、送信レジスタがクリアされるまで待機する
    while(I2C2CONbits.SEN || I2C2CONbits.PEN || I2C2CONbits.RSEN || I2C2CONbits.RCEN || I2C2CONbits.ACKEN || I2C2STATbits.TRSTAT);
}

unsigned int WriteI2C(unsigned char data)
{
    I2C2TRN = data;
    
    if(I2C2STATbits.IWCOL) {
        // 通信の衝突が発生したなら0を返す
        return 0;  
    }else{
         while(I2C2STATbits.TBF);      // 送信バッファが空になるのを待機
        IdleI2C();
        return 1;
    }
}

void send(byte reg, byte val)
{
    // 開始フラグ
    I2C2CONbits.SEN = 1;
      
    IdleI2C();    
    
    WriteI2C(MCP23017_ADDR);  
    WriteI2C(reg);    
    WriteI2C(val);
    
    // 停止状態にして送信を完了
    I2C2CONbits.PEN = 1; 
    
    IdleI2C();

    
    /*
    // データ受信のプログラム例(※汎用例です)
    I2C2CONbits.SEN = 1;
    IdleI2C();
    WriteI2C(SLAVE_ADDR);
    WriteI2C(reg);    
    WriteI2C(val);

    // 信号を途切れさせず、連続してデータを送りたいときの例
    I2C2CONbits.RSEN = 1; // リピートスタート
    IdleI2C();
    WriteI2C(SLAVE_ADDR | 0x1);  // 0x1はスレーブから受信を促すために必要
    WriteI2C(val);
    
    // データ読み出し
    I2C2CONbits.RCEN = 1;     // 受信モードに移行
    while(I2C2CONbits.RCEN) ; // 受信が完了すると0になるのでそれまで待機
    I2C2STATbits.I2COV = 0;   // バッファあふれのエラーフラグをリセット
    res = I2C2RCV;            // 受信したデータを格納
     
    I2C2CONbits.PEN = 1; 
    IdleI2C1();
    */
}

int main()   
{   
    int i, toggle = 0xFF;
    ANSELB = 0;
    
    // B2,B3を入力モードにし、プルアップ抵抗を有効にする
    TRISBbits.TRISB2 = 1;
    TRISBbits.TRISB3 = 1;
    CNPUBbits.CNPUB2 = 1;
    CNPUBbits.CNPUB3 = 1;
    
    // 100kHzモード
    I2C2CONbits.DISSLW = 1;
    I2C2BRG = 0x0C5;
    
    // I2C2を有効にする
    I2C2CONbits.ON = 1;
    
    send(0x00, 0x00);  // PortAをすべて出力にする命令
   
    while(1){
        send(0x12, toggle);  // PortAのGPIOをすべてON/OFFにする命令
        toggle = ~toggle;
        for(i=0;i<1000000;i++);
    }
   
    return 0;   
}