PIC(12F1822) DATA I/O by I2C

PIC(12F1822)を利用して、GVC(Arduino)とI2Cでデータのやり取りをするサンプルソースです。
現在策定中のGVCフォーマットで、I2CのマスターであるArduinoから、スレーブであるPICに対してデータを要求しています。
受けたPICはデータを用意してArduinoにデータを返します。(ここでは’A’,’B’,’C’,’D’の4バイト)

// --------------------------------------------------
// Global Versatile Controler
// --------------------------------------------------
// --------------------------------------------------
// Memo
// --------------------------------------------------
// ------------------------------
// 12F1822 I2C DATA IN/OUT
// ------------------------------
// New BSD License. Copyright (c) 2011-2012, Future Versatile Group
// All rights reserved.
//
// 2011.12.16 T.Kabu
//
// PICをI2Cのスレーブとして、マスターであるGVC(Arduino)と通信をするためのサンプルソースです。
// 他のPIC、またPICそのものの設定により挙動が異なる場合があるので注意すること。
//
// ※このソースはサンプルです。GVCプロトコルは現在も仕様策定中なのでソースが変更になる場合があります。

//---------------------------------------------------
// include
//---------------------------------------------------
#include

// --------------------------------------------------
// Const Define
// --------------------------------------------------
#define I2C_ADDR    0x20    // PICのI2Cアドレス、適時変更すること
#define RED_LED     RA5     // 動作確認用LEDを接続するポート

#define ON_LED      0;
#define OFF_LED     1;

// Delay用の周波数宣言。PICそのものの内部クロックOSCCONを変更したら_XTAL_FREQも変更すること
#define _XTAL_FREQ  4000000    // 4MHz

__CONFIG(
    FOSC_INTOSC & WDTE_OFF & PWRTE_ON & MCLRE_ON & CP_OFF
    & CPD_OFF & BOREN_OFF & CLKOUTEN_ON & IESO_OFF & FCMEN_OFF
);
__CONFIG(
    WRT_OFF & PLLEN_OFF & STVREN_ON &  LVP_OFF
);

#define RX_BUFF_SIZE    8
#define TX_BUFF_SIZE    8

// --------------------------------------------------
// Variable Param
// --------------------------------------------------
unsigned char rx_buffer[RX_BUFF_SIZE];    // 受信バッファ
unsigned char rx_count = 0;               // 受信バッファ位置

unsigned char tx_buffer[TX_BUFF_SIZE];    // 送信バッファ
unsigned char tx_count = 0;               // 送信バッファ位置

static volatile char i2c_flag = 0;        // I2C受信完了フラグ

// --------------------------------------------------
// Sub Routine
// --------------------------------------------------
// ------------------------------
// Delay 10m sec
// ------------------------------
void Delay_10ms(unsigned char time)
{
    // _XTAL_FREQという定数を宣言すること。PICそのものの内部クロックOSCCONを変更したら_XTAL_FREQも変更すること
    // timeを1減らしつつ10ms待つ。例:time=100なら100x10ms=1000ms=1s待つ
    while(time--)
    {
        __delay_ms(10);    // wait 10ms
    }
}

// ------------------------------
// LED Bright
// ------------------------------
void LED_Bright(char param1, char param2)
{
    // 長時間点灯ループ
    while (param1 > 0)
    {
        //モニターLEDを点灯
        RED_LED = ON_LED;
        Delay_10ms(50);
        // モニターLEDを消灯
        RED_LED = OFF_LED;
        Delay_10ms(10);
        // param1を-1
        param1 --;
    }
    // 短時間点灯ループ
    while (param2 > 0)
    {
        //モニターLEDを点灯
        RED_LED = ON_LED;
        Delay_10ms(20);
        // モニターLEDを消灯
        RED_LED = OFF_LED;
        Delay_10ms(10);
        // param2を-1
        param2 --;
    }
    Delay_10ms(20);
}

// ------------------------------
// 12F1822 Interrupt Routine
// ------------------------------
// I2Cのマスターとスレーブとのやり取りは、データシートの説明が言葉足らずなために
// 非常に判りづらい。ただし、判ってしまうと何だそれだけか、となる。
//
// 特にスレーブ側であれこれ設定したり判定や処理に必要なのは次のビット
// ・D/A…1=SSP1BUFの中はデータ、0=SSP1BUFの中はアドレス(空っぽ…空の読み出し要求時)
// ・R/W…1=マスターがスレーブから受信、0=マスターがスレーブへ送信
// ・BF…1=バッファに何か入っている(空の読み出し要求時も)、0=バッファは空っぽ
// ・CKP…マスターにデータ送信を許可するとき、スレーブから送信するときに1にしてSCLをリリースする
// ・SSP1IF…割り込みフラグ、なんかしたらクリアする
// ・SEN…マスターからデータを受信する時に、ソフト側でCKPを制御するために1にする。0だとうまく動かないよ!!
//(・S…スタートビット、特に使わなくてもいい気がする)
//
// で、判定に使うビットが多いけど、基本的にはSSP1STATなので、マスクして一括判定すればOK。
static void interrupt int_func()
{
    char status_data;
    char temp_buffer;

    // 全割り込みを禁止(=0) (Global Interrupt Enable bit ... INTCON)
    GIE = 0;

    // MSSP割り込み(=1)なら (Synchronous Serial Port (MSSP) Interrupt Flag bit ... PIR1)
    if (SSP1IF == 1)
    {
        // MSSP割り込み禁止(=0) (Synchronous Serial Port (MSSP) Interrupt Enable bit ... PIE1)
        SSP1IE = 0;
        // MSSP割り込みクリア(=0) (Synchronous Serial Port (MSSP) Interrupt Flag bit ... PIR1)
        SSP1IF = 0;

        // SSP1STATを判定用変数に設定。D/A、R/W、BFビットをマスク
        status_data = SSP1STAT & 0b00100101;

        // アドレス(D/A=0)で、かつマスターがスレーブへ送信(R/W=0)、かつバッファに何かある(BF=1)なら
        if (status_data == 0b00000001)
        {
            // SSP1BUFを空読みして
            temp_buffer = SSP1BUF;
            // 受信バッファ位置を初期化
            rx_count = 0;
            // SCLをリリースしてマスターにデータの送信を許可する
            CKP = 1;
        }
        // データ(D/A=1)で、かつマスターがスレーブへ送信(R/W=0)、かつバッファに何かある(BF=1)なら
        else if (status_data == 0b00100001)
        {
            // SSP1BUFからデータを読み出して、受信バッファの受信バッファ位置に設定
            rx_buffer[rx_count] = SSP1BUF;

            // 受信データが0x00(デリミタ)なら
            if (rx_buffer[rx_count] == 0x00)
            {
                // --------------------------------------------------------------------------------
                // (とりあえず)データ要求なら REQUEST MODULE DATA
                // --------------------------------------------------------------------------------
                if (rx_buffer[1] == 0x11)
                {
                    // とりあえずbufferにダミーデータを入れる。
                    tx_buffer[0] = 'A';
                    tx_buffer[1] = 'B';
                    tx_buffer[2] = 'C';
                    tx_buffer[3] = 'D';
                    tx_buffer[4] = 'E';
                    tx_buffer[5] = 'F';
                    tx_buffer[6] = 'G';
                    tx_buffer[7] = 'H';
                    // 実際に送信されるのはA,B,C,Dだけのはず
                }
                // --------------------------------------------------------------------------------
                // 上記以外の場合には
                // --------------------------------------------------------------------------------
                else
                {
                    // とりあえず命令されたコマンドをそのまま返すようにしよう。:-P
                    tx_buffer[0] = rx_buffer[1];
                }
                // --------------------------------------------------------------------------------
                // 必要ならメッセージ受信したよフラグでも立ててメインループ内で処理してもらう
                // --------------------------------------------------------------------------------
                // I2C受信完了フラグを設定(=受信完了)
                i2c_flag = 1;
            }
            // 受信バッファ位置を+1
            rx_count ++;
            // 受信データがバッファいっぱいになったりしたらフラグでも立ててエラー処理とかする
            if (rx_count >= RX_BUFF_SIZE)
            {
                rx_count = 0;
            }
            // SCLをリリースしてマスターにデータの送信を許可する
            CKP = 1;
        }
        // アドレス(D/A=0)で、かつマスターがスレーブから受信(R/W=1)、かつバッファに何かある(BF=1)なら
        else if (status_data == 0b00000101)
        {
            // SSP1BUFを空読みして
            temp_buffer = SSP1BUF;
            // 送信バッファ位置を初期化
            tx_count = 0;
            // 送信バッファの最初のデータをSSP1BUFに設定
            SSP1BUF = tx_buffer[tx_count];
            // SCLをリリースしてマスターにデータの送信を許可する
            CKP = 1;
            // 送信バッファ位置を+1;
            tx_count ++;
        }
        // データ(D/A=1)で、かつマスターがスレーブから受信(R/W=1)、かつバッファが空(BF=0)なら
        else if (status_data == 0b00100100)
        {
            // 送信バッファの送信バッファ位置のデータをSSP1BUFに設定
            SSP1BUF = tx_buffer[tx_count];
            // SCLをリリースしてマスターにデータの送信を許可する
            CKP = 1;
            // 送信バッファ位置を+1;
            tx_count ++;
            // 送信バッファ位置が送信バッファを越えそうになったりしたらフラグでも立ててエラー処理とかする
            if (tx_count >= TX_BUFF_SIZE)
            {
                tx_count = 0;
            }
        }
        // これら以外は
        else
        {
            // SSP1BUFを空読みして
            temp_buffer = SSP1BUF;
            // SCLをリリースしてマスターに次の命令を促す
            CKP = 1;
        }
        // MSSP割り込み許可(=1) (Synchronous Serial Port (MSSP) Interrupt Enable bit ... PIE1)
        SSP1IE = 1;
    }
    // 全割り込みを許可(=1) (Global Interrupt Enable bit ... INTCON)
    GIE = 1;
}

// --------------------------------------------------
// Setup
// --------------------------------------------------
// ------------------------------
// Setup 12F1822
// ------------------------------
void setup(void)
{
    // OSCCON: OSCILLATOR CONTROL REGISTER PICそのものの内部クロック。OSCCONを変更したら_XTAL_FREQも変更すること
    // bit 7 SPLLEN: Software PLL Enable bit
    //     If PLLEN in Configuration Word 1 = 1:
    //         SPLLEN bit is ignored. 4x PLL is always enabled (subject to oscillator requirements)
    //     If PLLEN in Configuration Word 1 = 0:
    //         1 = 4x PLL Is enabled
    //         0 = 4x PLL is disabled
    // bit 6-3 IRCF: Internal Oscillator Frequency Select bits
    //     000x = 31 kHz LF
    //     0010 = 31.25 kHz MF
    //     0011 = 31.25 kHz HF(1)
    //     0100 = 62.5 kHz MF
    //     0101 = 125 kHz MF
    //     0110 = 250 kHz MF
    //     0111 = 500 kHz MF (default upon Reset)
    //     1000 = 125 kHz HF(1)
    //     1001 = 250 kHz HF(1)
    //     1010 = 500 kHz HF(1)
    //     1011 = 1MHz HF
    //     1100 = 2MHz HF
    //     1101 = 4MHz HF
    //     1110 = 8 MHz or 32 MHz HF(see Section 5.2.2.1 “HFINTOSC”)
    //     1111 = 16 MHz HF
    // bit 2 Unimplemented: Read as ‘0’
    // bit 1-0 SCS: System Clock Select bits
    //     1x = Internal oscillator block
    //     01 = Timer1 oscillator
    //     00 = Clock determined by FOSC in Configuration Word 1
    //
    // Note 1: Duplicate frequency derived from HFINTOSC.
    OSCCON = 0b11101011;        // 4MHz

    // ANSELA: PORTA ANALOG SELECT REGISTER ポートのI/Oモードの設定。
    // bit7-5 : none (0)
    // bit4   : ANSA4: Analog Select between Analog or Digital Function on pins RA4, respectively
    //            0 = Digital I/O. Pin is assigned to port or digital special function.
    //            1 = Analog input. Pin is assigned as analog input(1). Digital input buffer disabled.
    // bit3   : none (0)
    // bit2   : ANSA2: Analog Select between Analog or Digital Function on pins RA2, respectively
    // bit1   : ANSA1: Analog Select between Analog or Digital Function on pins RA1, respectively
    // bit0   : ANSA0: Analog Select between Analog or Digital Function on pins RA0, respectively
    ANSELA = 0b00000000;        // ALL digital

    // OPTION_REG: OPTION REGISTER オプション設定
    // bit7   : Weak Pull-up Enable bit 1 = All weak pull-ups are disabled, 0 = Weak pull-ups are enabled by individual WPUx latch values
    // bit6   : Interrupt Edge Select bit 1 = Interrupt on rising edge of RA2/INT pin, 0 = Interrupt on falling edge of RA2/INT pin
    // bit5   : Timer0 Clock Source Select bit 1 = Transition on RA2/T0CKI pin, 0 = Internal instruction cycle clock (FOSC/4)
    // bit4   : Timer0 Source Edge Select bit 1 = Increment on high-to-low transition on RA2/T0CKI pin, 0 = Increment on low-to-high transition on RA2/T0CKI pin
    // bit3   : Prescaler Assignment bit 1 = Prescaler is not assigned to the Timer0 module, 0 = Prescaler is assigned to the Timer0 module
    // bit2-0 : 000 = 1:2
    //        : 001 = 1:4
    //        : 010 = 1:8
    //        : 011 = 1:16
    //        : 100 = 1:32
    //        : 101 = 1:64
    //        : 110 = 1:128
    //        : 111 = 1:256
    OPTION_REG = 0b00000000;    // 特になし

    // WPUA: WEAK PULL-UP PORTA REGISTER プルアップ設定
    // ※アナログリードするときにはプルアップは邪魔なだけなのでdisabledにすること、デフォルトではenabledになっていた H.A. 2012/02/23
    // bit 7-6 Unimplemented: Read as ‘0’
    // bit 5-0 WPUB<5:0>: Weak Pull-up Register bits    ←誤植かな?
    //     1 = Pull-up enabled
    //     0 = Pull-up disabled
    //
    // Note 1: Global WPUEN bit of the OPTION register must be cleared for individual pull-ups to be enabled.
    //      2: The weak pull-up device is automatically disabled if the pin is in configured as an output.
    WPUA = 0b00000000;

    // TRISA: PORTA TRI-STATE REGISTER ポートA設定
    // bit7-6 : none (0)
    // bit5   : TRISA5: PORTA Tri-State Control bits
    //            0 = PORTA pin configured as an output
    //            1 = PORTA pin configured as an input (tri-stated)
    // bit4   : TRISA4: PORTA Tri-State Control bits
    // bit3   : TRISA3: RA3 Port Tri-State Control bit. This bit is always ‘1’ as RA3 is an input only
    // bit2   : TRISA2: PORTA Tri-State Control bits
    // bit1   : TRISA1: PORTA Tri-State Control bits
    // bit0   : TRISA0: PORTA Tri-State Control bits
    TRISA = 0b00000110;        // RA1,RA2だけI2C用にinputモード

    // LATA: PORTA DATA LATCH REGISTER ポートAラッチデータ設定
    // bit 7-6 Unimplemented: Read as ‘0
    // bit 5-4 LATA: RA Output Latch Value bits(1)
    // bit 3 Unimplemented: Read as ‘0
    // bit 2-0 LATA: RA Output Latch Value bits(1)
    //
    // Note 1: Writes to PORTA are actually written to corresponding LATA register. Reads from PORTA register is return
    // of actual I/O pin values.
    LATA = 0b00000000;        // 特になし

    Delay_10ms(10);    // 100ms待つ
}

// ------------------------------
// Setup MSSP
// ------------------------------
void setup_mssp(void)
{
    // MSSP1制御データ設定
    // bit 7 WCOL: Write Collision Detect bit
    //     Master mode:
    //         1 = A write to the SSP1BUF register was attempted while the I2C conditions were not valid for a transmission to be started
    //         0 = No collision
    //     Slave mode:
    //         1 = The SSP1BUF register is written while it is still transmitting the previous word (must be cleared in software)
    //         0 = No collision
    // bit 6 SSP1OV: Receive Overflow Indicator bit(1)
    //     In SPI mode:
    //         1 = A new byte is received while the SSP1BUF register is still holding the previous data. In case of overflow, the data in SSP1SR
    //             is lost. Overflow can only occur in Slave mode. In Slave mode, the user must read the SSP1BUF, even if only transmitting
    //             data, to avoid setting overflow. In Master mode, the overflow bit is not set since each new reception (and transmission) is
    //             initiated by writing to the SSP1BUF register (must be cleared in software).
    //         0 = No overflow
    //     In I2 C mode:
    //         1 = A byte is received while the SSP1BUF register is still holding the previous byte. SSP1OV is a “don’t care” in Transmit
    //             mode (must be cleared in software).
    //         0 = No overflow
    // bit 5 SSP1EN: Synchronous Serial Port Enable bit
    //     In both modes, when enabled, these pins must be properly configured as input or output
    //     In SPI mode:
    //         1 = Enables serial port and configures SCK, SDO, SDI and SS as the source of the serial port pins(2)
    //         0 = Disables serial port and configures these pins as I/O port pins
    //     In I2 C mode:
    //         1 = Enables the serial port and configures the SDA and SCL pins as the source of the serial port pins(3)
    //         0 = Disables serial port and configures these pins as I/O port pins
    // bit 4 CKP: Clock Polarity Select bit
    //     In SPI mode:
    //         1 = Idle state for clock is a high level
    //         0 = Idle state for clock is a low level
    //     In I2 C Slave mode:
    //         SCL release control
    //         1 = Enable clock
    //         0 = Holds clock low (clock stretch). (Used to ensure data setup time.)
    //     In I2 C Master mode:
    //         Unused in this mode
    // bit 3-0 SSP1M: Synchronous Serial Port Mode Select bits
    //         0000 = SPI Master mode, clock = FOSC/4
    //         0001 = SPI Master mode, clock = FOSC/16
    //         0010 = SPI Master mode, clock = FOSC/64
    //         0011 = SPI Master mode, clock = TMR2 output/2
    //         0100 = SPI Slave mode, clock = SCK pin, SS pin control enabled
    //         0101 = SPI Slave mode, clock = SCK pin, SS pin control disabled, SS can be used as I/O pin
    //         0110 = I2C Slave mode, 7-bit address
    //         0111 = I2C Slave mode, 10-bit address
    //         1000 = I2C Master mode, clock = FOSC / (4 * (SSP1ADD+1))(4)
    //         1001 = Reserved
    //         1010 = SPI Master mode, clock = FOSC/(4 * (SSP1ADD+1))
    //         1011 = I2C firmware controlled Master mode (Slave idle)
    //         1100 = Reserved
    //         1101 = Reserved
    //         1110 = I2C Slave mode, 7-bit address with Start and Stop bit interrupts enabled
    //         1111 = I2C Slave mode, 10-bit address with Start and Stop bit interrupts enabled
    //
    // Note 1: In Master mode, the overflow bit is not set since each new reception (and transmission) is initiated by writing to the SSP1BUF register.
    //      2: When enabled, these pins must be properly configured as input or output.
    //      3: When enabled, the SDA and SCL pins must be configured as inputs.
    //      4: SSP1ADD values of 0, 1 or 2 are not supported for I2C Mode.
    SSP1CON1 = 0b00110110;    // SSP1EN = 1, CKP = 1, SSP1M = Slave mode 7bit

    // bit 0 SEN: Start Condition Enabled bit (in I2C Master mode only)
    //     In Master mode:
    //         1 = Initiate Start condition on SDA and SCL pins. Automatically cleared by hardware.
    //         0 = Start condition Idle
    //     In Slave mode:
    //         1 = Clock stretching is enabled for both slave transmit and slave receive (stretch enabled)
    //         0 = Clock stretching is disabled
    //
    // Note 1: For bits ACKEN, RCEN, PEN, RSEN, SEN: If the I2C module is not in the Idle mode, this bit may not be
    // set (no spooling) and the SSP1BUF may not be written (or writes to the SSP1BUF are disabled).
    SEN = 1;    // Start Condition Enabled bit ... SSP1CON2) ←マスターからデータを受け取るなら設定必要

    // このデバイスのI2Cアドレスを設定
    SSP1ADD = I2C_ADDR << 1;

    // MSSP1割り込み初期化
    // bit 3 SSP1IF: Synchronous Serial Port (MSSP) Interrupt Flag bit
    //     1 = Interrupt is pending
    //     0 = Interrupt is not pending
    SSP1IF = 0;

    Delay_10ms(10);    // 100ms待つ
}

// --------------------------------------------------
// Main loop
// --------------------------------------------------
void main(void)
{
    // 12F1822 初期設定
    setup();
    // MSSP 初期設定
    setup_mssp();
    // モニターLEDを点灯
    LED_Bright(3, 0);

    // MSSP割り込み許可(=1) (Synchronous Serial Port (MSSP) Interrupt Enable bit ... PIE1)
    SSP1IE = 1;
    // 周辺割り込み許可(=1) (Peripheral Interrupt Enable bit ... INTCON)
    PEIE = 1;
    // 全割り込みを許可(=1) (Global Interrupt Enable bit ... INTCON)
    GIE = 1;
    // I2C同期許可(=1) (Synchronous Serial Port Enable bit ... SSP1CON1)
    SSPEN = 1;

    // メインループ
    while(1)
    {
        // I2C受信完了フラグがセットされるまでループ
        while(i2c_flag == 0)
        {
            // ループし続ける
        }

        // ここで何らかの処理をする場合には、I2Cとかの割り込みについて注意しつつ、
        // どちらかと言うと、割り込み関数内で処理するには長い処理や、命令を受けて
        // やりっぱですむ処理を記述すること。(例:スイッチONとかリモコン送信とか)
        //
        // センサーデータを要求された場合には、できるだけ簡潔に、短い処理時間ですむ
        // ようにして、割り込み関数内にて送信バッファに設定してしまうこと。

        // 受信バッファ位置を先頭に戻す
        rx_count = 0;
        // I2C受信完了フラグを
        i2c_flag = 0;

        // モニターLEDを点灯
        LED_Bright(0,1);
    }
}