PIC16F87xでI2Cスレーブ

 概要
PIC16F87xのMSSPモジュールを用いてI2Cスレーブを実装するためのコード片です。 こちらも合わせてご覧ください。

 キーワード
PIC16F87x, I2C, IIC

 注記
きっちりとしたライブラリ化はあえてしていません。
動作実績のある1コード片として参考にしていただけたら幸いです。
著者への質問・つっこみはこちらまで。

 コンパイル・動作確認済み環境
ハードウェア:PIC16F873A(秋月電子)
開発環境: windows XP + MPASM + PICWW
動作実績:第11回川崎ロボット競技大会 リミットカズラ で使用。

 コード
 前置き

        radix   dec
        #include <p16f873.inc>
        #include "Macros.h"

    #define I2C_ADDRESS 3       ;I2Cのスレーブアドレス
    #define I2C_DATA_LENGTH 10  ;送受信データ長

        org     h'20'
    ;変数割り当て
    tmpW            res 1                   ;割り込み時退避用
    tmpStatus       res 1                   ;
    i2c_ctrl        res 1                   ;I2Cコントロールバイト
    i2c_data        res I2C_DATA_LENGTH     ;I2Cデータバッファ
    usr_data        res I2C_DATA_LENGTH     ;ユーザ定義データ

        org     h'00'
        goto    Init
        org     h'04'
        goto    Intr

    ;-----------------------------------------------------------
    ;割り込み時の変数退避/復帰
    push    macro
        movwf   tmpW
        movff   STATUS, tmpStatus
        endm

    pop macro
        movff   tmpStatus, STATUS
        movf    tmpW, W
        endm
				
Macros.hはこちら。 I2C_ADDRESSでI2Cスレーブアドレスを設定します。ここでは3とします。 I2C_DATA_LENGTHはマスタと送受信するデータのバイト数です。本コードでは固定とします。
tmpWとtmpStatusは割り込み時にWレジスタとSTATUSレジスタの値の退避用です。
i2c_ctrlはI2Cセッションの最初にマスタから送られてくるコントロールバイトを格納する変数です。 i2c_dataは送受信データを格納するバッファです。 usr_dataは本サンプルのために用意したユーザ定義変数です。 マスタとやりとりする情報の格納先だと思ってください。

 初期化処理

    ;-----------------------------------------------------------
    ;初期化処理
    Init:
        bank1
        movlf   b'00011000', TRISC      ;PORTCはSCL, SDAは入力、それ以外は出力
        bsf     PIE1, SSPIE             ;I2C割り込み許可
        movlf   I2C_ADDRESS, SSPADD     ;I2Cスレーブアドレス設定
        bcf     STATUS, C               ;アドレスは上位7ビットなので、左に1ビットシフト
        rlf     SSPADD, f

        bank0
        movlf   b'00001111', OPTION_REG ;
        movlf   b'00110110', SSPCON     ;I2Cを7bitアドレススレーブモードでイネーブル

        movlf   b'11000000', INTCON     ;割り込み許可

        goto    Main

				
初期化処理です。ここではI2C関連の処理のみを抜粋しています。
*SSPADDの設定部分ですが、今見るとわざわざ rlf を使わなくても
movlf I2C_ADDRESS << 1, SSPADD
とすれば良いような気がするのですが、何分半年前に書いたコードなので 何か特別な理由があったのかも知れません(なんてテキトーな・・

 割り込みハンドラ

    ;-----------------------------------------------------------
    ;割り込みハンドラ
    Intr:
    ;{
        push
    ;コントロールバイトを受信した場合
        bank1
        btfsc   SSPSTAT, D_A
        goto    Intr_Data
                                            ;//コントロールバイトを受信した場合
    Intr_Ctrl:                              ;if(SSPSTAT == 0){
        bank0
        movff   SSPBUF, i2c_ctrl            ;   i2c_ctrl = SSPBUF;
        movlf   i2c_data, FSR               ;   FSR = i2c_data;
        btfss   i2c_ctrl, 0
        goto    Intr_End
                                            ;   //受信(スレーブ->マスタ)指令の場合,バッファにコピーして最初の1バイトを送信
    Intr_Ctrl_SendMSB:                      ;   if(i2c_ctrl<0> == 1){
        ;!ここでマスタに送りたいデータをバッファにコピー
        memcpy  usr_data, i2c_data, I2C_DATA_LENGTH
        movff   INDF, SSPBUF                ;       SSPBUF = *FSR;
        bsf     SSPCON, CKP                 ;       SSPCON = true; //クロック引き延ばし解除
        incf    FSR, f                      ;       FSR++;
                                            ;   }
        goto    Intr_End                    ;}

    Intr_Data:
        bank0
        btfss   i2c_ctrl, 0
        goto    Intr_Data_Write
                                            ;//スレーブ->マスタ:次のバイトを送信
    Intr_Data_Read:                         ;if(i2c_ctrl<0> == 1){
        movff   INDF, SSPBUF                ;   SSPBUF = *FSR;
        bsf     SSPCON, CKP                 ;   SSPCON = true; //クロック引き延ばし解除
        goto    Intr_Data_End               ;}
                                            ;//スレーブ<-マスタ:データをバッファにコピー
    Intr_Data_Write:                        ;else{
        movff   SSPBUF, INDF                ;   *FSR = SSPBUF;
    Intr_Data_End:                          ;}
        incf    FSR, f                      ;FSR++;

        movlw   i2c_data + I2C_DATA_LENGTH;
        xorwf   FSR, W
        btfss   STATUS, Z
        goto    Intr_End
                                            ;//送受信完了時
    Intr_Complete:                          ;if(FSR == i2c_data + I2C_DATA_LENGTH){
        btfsc   i2c_ctrl, 0                 ;   if(i2c_ctrl<0> == 0)
        goto    Intr_End
        ;!ここで受信したデータをバッファからユーザ変数へ
        memcpy  i2c_data, usr_data, I2C_DATA_LENGTH
                                            ;}
    Intr_End:
        bank0
        bcf     PIR1, SSPIF                 ;PIR1 = false;   //フラグクリア
        pop
        retfie
    ;}
				
コアの処理です。コメントにほぼ等価なC言語を書いてあるのでこちらを参考にしてください。 ただし<i>は変数のi番目のビットの意味です。
FSRとINDFはPICで間接アドレッシングを行うためのレジスタです。 INDFを読み書きすると、FSRに格納してあるアドレスにある変数が読み書きされます。

 メインループ

    ;-----------------------------------------------------------
    ;メインループ
    Main:
        ;
        ;ここでループ処理
        ;
    Main_End:
        clrwdt  ;ウォッチドッグタイマをクリア
        goto    Main

    ;-----------------------------------------------------

        end

				
お決まりのメインループです。