番外 C言語を用いてI2CでIO拡張する


I2CでIO拡張する

PCF8574はI2Cを用いてデジタル入出力ポートを拡張するための集積回路です。
これのブロック図を下に示します。


ピン配置は下図のようになります。


各ポートの簡単化した回路図は以下のようになりります。
回路図から、ポートを入力に設定するにはそのポートにHigh(1)を出力しておく必要があります。


とに角動作チェックの回路を組んでみます。入出力各2ビットずつチェックするプログラムを作ってみます。

PCF8574の1,2,3ピンはチップのアドレス設定で、ここでは0に設定しています。
従って、アドレスは0x40となります。
P0(4), P1(5)には抵抗を介してLEDを電源に繋いでいます。
SDA(15), SCL(14)は2.2kΩで電源にプルアップしています。
P6(11),P7(12)の入力テストのために1kΩのプルダウン抵抗に接触させます。


main.c
#include "vs-wrc003.h"

// 汎用I2cデジタル入出力拡張用ICであるPCF8574のテスト
// P0(4)にLEDを付ける
// P1(5)にLEDを付ける
// P6(11)とP7(12)をグラウンドに交互に接触させると
// 基板上のLEDが点灯する

void main(void);

void main()
{
  //制御周期の設定[単位:Hz 範囲:30.0~]
  const BYTE MainCycle = 60;
  unsigned char x;
  Init((BYTE)MainCycle);    // CPUの初期設定

  i2c_init();               // I2Cの初期化
  while(1)
  {
    IOwrite(1);             // P0をon P1をoff
    Wait(200);
    IOwrite(2);             // P0をoff P1をon
    Wait(200);
    IOwrite(0xff);          // 入力モードにする
    x = IOread() & 0xc0;    // 入力して2bitのみにする
    x >>= 6;                // 下位の2bitに移動させる
    LED(~x);
  }
}
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
10 
11 
12 
13 
14 
15
16 
17 
18
19 
20
21
22 
23 
24 
25 
26 
27 
28 
29 
30 

ある意味手を抜いて作っており、たまたま動いているという可能性があるので十分プロトコルを理解してから使用してください。
i2c.c
/* ----------------------------------------------------------------------
    i2c.c
    i2c バス処理
---------------------------------------------------------------------- */
#include "iodefine.h"


/* ----------------------------------------------------------------------
    i2c_checkBusCondition
    i2c バスが空くまで待つ
---------------------------------------------------------------------- */
void i2c_waitBusFree() 
{
    while (IIC2.ICCR2.BIT.BBSY == 1);
}

/* ----------------------------------------------------------------------
    i2c_sendStartCondition
    i2c バスに開始要求を送る
---------------------------------------------------------------------- */
void i2c_sendStartCondition() 
{
    IIC2.ICCR2.BYTE = 0xbd;
}

/* ----------------------------------------------------------------------
    i2c_sendStopCondition
    i2c バスに停止要求を送る
---------------------------------------------------------------------- */
void i2c_sendStopCondition() 
{
    IIC2.ICSR.BIT.STOP = 0; // STOP をクリアする
    IIC2.ICCR2.BYTE = 0x3D; // 停止条件を発行する(BBSY=0, SCP=0)
    while (IIC2.ICSR.BIT.STOP != 1) ;
}

/* ----------------------------------------------------------------------
    i2c_setTransmitMode
    i2c 送受信モードの設定

    0: スレーブ受信モード
    1: スレーブ送信モード
    2: マスタ受信モード
    3: マスタ送信モード
---------------------------------------------------------------------- */
void i2c_setTransmitMode(unsigned char tmode) 
{
  switch (tmode){
    case 0: tmode = 0x00;   // MST=0, TRS=0
        break;
    case 1: tmode = 0x10;   // MST=0, TRS=1
        break;
    case 2: tmode = 0x20;   // MST=1, TRS=0
        break;
    case 3: tmode = 0x30;   // MST=1, TRS=1
        break;
  }
  // レジスタに送受信モードを設定する
  IIC2.ICCR1.BYTE = ((IIC2.ICCR1.BYTE & 0xcf) | tmode);
}

/* ----------------------------------------------------------------------
    i2c_sendByte
    i2c バスへ1バイト送信
---------------------------------------------------------------------- */
unsigned char i2c_sendByte(unsigned char ch) 
{
    unsigned char tend;

    // データを送信する
    IIC2.ICDRT = ch;
    while(1) {
        tend = IIC2.ICSR.BIT.TEND;
        if(tend == 1) break;
    }
    // ACK を返す
    return(IIC2.ICIER.BIT.ACKBR);
}

/* ----------------------------------------------------------------------
    i2c_receiveByte
    i2c バスから1バイト受信
    
    ack: データ受信時相手に返すACKビット
    rcvd: 最終バイトをリードするときは1にするこの後停止条件発行可能になる
          連続してデータ受信する場合は0
---------------------------------------------------------------------- */
unsigned char i2c_receiveByte(unsigned char ack, unsigned char rcvd) 
{
    unsigned char rdt;

    // データ受信後に送信するACKフラグを設定する。
    IIC2.ICIER.BIT.ACKBT = ack;

    // 最終バイト読み込み時は1
    IIC2.ICCR1.BIT.RCVD = rcvd;
    
    // i2c バスから送られるデータの受信開始
    rdt = IIC2.ICDRR;

    // データが8bit転送されるまで待つ
    while(IIC2.ICSR.BIT.RDRF != 1) ;

    // 揃ったデータを取り出す    
    rdt = IIC2.ICDRR;

    return(rdt);
}

/* ----------------------------------------------------------------------
    i2c_init   
    i2c初期化
---------------------------------------------------------------------- */
void i2c_init(void) 
{
    // I2C の転送レートをΦ/200(Φ=20MHz, 100kHz)に設定する
    IIC2.ICCR1.BIT.CKS = 0x0d;

  // IIC バスモードをMSB ファースト、ウェイト挿入しない、
  // ビット数: 9 ビットに設定する
    IIC2.ICMR.BIT.MLS  = 0; // MSB First
    IIC2.ICMR.BIT.WAIT = 0; // WAIT 無し
    IIC2.ICMR.BIT.BCWP = 0; // ビットカウンタライトプロテクト解除
    IIC2.ICMR.BIT.BC = 0;   // 9ビット
    IIC2.ICMR.BIT.BCWP = 1; // ビットカウンタライトプロテクト

  // IIC2 I/F モジュールの動作状態を転送動作可能状態
  // (SCL/SDA は、バス駆動状態)に設定する
    IIC2.ICCR1.BIT.ICE = 1;
}

/*----------------------------------------------------------------
  ここからPCF8574専用ルーチン
-----------------------------------------------------------------*/
#define PCF8574_WRITE 0x40

void IOwrite(unsigned char data)
{
    unsigned char rdt;

    i2c_waitBusFree();                      // i2c バス空き待ち
    do {
        i2c_setTransmitMode(3);             // マスタ送信モード
        i2c_sendStartCondition();           // i2c バスを開始条件にする
        rdt = i2c_sendByte(PCF8574_WRITE);  // スレーブアドレスを送信
    } while(rdt == 1);                      // デバイスからACKが返るまでガンバル
    i2c_sendByte(data);                     // レジスタ番号を送信
    IIC2.ICSR.BIT.TEND = 0;
    i2c_sendStopCondition();                // i2c バスを停止条件にする。
    i2c_setTransmitMode(0);                 // スレーブ受信モードにする
}

unsigned char IOread() {

    unsigned char rdt;

    i2c_waitBusFree();                      // i2c バス空き待ち
    do {
        i2c_setTransmitMode(3);             // マスタ送信モード
        i2c_sendStartCondition();           // i2c バスを開始条件にする
        rdt = i2c_sendByte(PCF8574_WRITE);  // スレーブアドレスを送信
    } while(rdt == 1);                      // デバイスからACKが返るまでガンバル
    i2c_setTransmitMode(3);                 // マスタ送信モード
    i2c_sendStartCondition();               // i2c バスを開始条件にする
    i2c_sendByte(PCF8574_WRITE | 1);        // スレーブアドレスを送信
    i2c_setTransmitMode(2);                 // マスタ受信モード
    IIC2.ICSR.BIT.TDRE = 0;
    rdt = i2c_receiveByte(1, 1);            // ACK=1, RCVD = 1で受信する。
    i2c_sendStopCondition();                // i2c バスを停止条件にする。
    IIC2.ICCR1.BIT.RCVD = 0;
    i2c_setTransmitMode(0);                 // スレーブ受信モードにする

    return(rdt);
}
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 
40 
41 
42 
43 
44 
45 
46 
47 
48 
49 
50 
51 
52 
53 
54 
55 
56 
57 
58 
59 
60 
61 
62 
63 
64 
65 
66 
67 
68 
69 
70 
71 
72 
73 
74 
75 
76 
77 
78 
79 
80 
81 
82 
83 
84 
85 
86 
87 
88 
89 
90 
91 
92 
93 
94 
95 
96 
97 
98 
99 
100 
101
102
103 
104 
105 
106 
107 
108 
109 
110 
111 
112 
113 
114 
115 
116 
117 
118 
119 
120 
121 
122 
123 
124 
125 
126 
127 
128 
129 
130 
131 
132 
133 
134 
135 
136 
137 
138 
139 
140 
141 
142 
143 
144 
145 
146 
147 
148 
149 
150 
151 
152 
153 
154 
155 
156 
157 
158 
159 
160 
161 
162 
163 
164 
165 
166 
167 
168 
169 
170 
171 
172 
173 
174 


複数のPCF8574が利用できるように改造する。



右側にもう一つのPCF8574を追加します。
PCF8574の1ピンをVddに繋ぎ、2,3ピンはチップはGNDに繋ぎます。
従って、アドレスは0x42となります。
SDA(15), SCL(14)はとなりの同等のピンに繋げます。
P6(11),P7(12)の入力テストのために1kΩのプルダウン抵抗に接触させます。


main.c
#include "vs-wrc003.h"

// 汎用I2cデジタル入出力拡張用ICであるPCF8574のテスト
// 0x40のアドレスのチップを出力に用い、
// 0x42のアドレスのチップを入力に用いる設定
// 0x40側
//  P0(4)にLEDを付ける
//  P1(5)にLEDを付ける
// 0x42側
//  P6(11)をグラウンドに接触させると、基板上緑のLEDが点灯する
//  P7(12)をグラウンドに接触させると、基板上橙のLEDが点灯する

#define PCF8574 0x40

void main(void);

void main()
{
  //制御周期の設定[単位:Hz 範囲:30.0~]
  const BYTE MainCycle = 60;
  unsigned char x;
  int ioAdr = PCF8574;
  Init((BYTE)MainCycle);		    //CPUの初期設定

  i2c_init();                   // I2Cの初期設定
  IOwrite(ioAdr+2, 0xff);       // 入力モードにする  
  
  while(1)
  {
    IOwrite(ioAdr, 1);          // P0をon P1をoff
    Wait(200);
    IOwrite(ioAdr, 2);          // P0をoff P1をon
    Wait(200);
    x = IOread(ioAdr+2) & 0xc0; // 入力して2bitのみにする
    x >>= 6;                    // 下位の2bitに移動させる
    LED(~x);
  }  
}


複数のPCF8574を指定できるようにi2c.cを変更します。この辺はC++などで書いた方がすっきりします。


i2c.c
/* ----------------------------------------------------------------------
    i2c.c
    i2c バス処理
---------------------------------------------------------------------- */
#include "iodefine.h"


/* ----------------------------------------------------------------------
    i2c_checkBusCondition
    i2c バスが空くまで待つ
---------------------------------------------------------------------- */
void i2c_waitBusFree() 
{
    while (IIC2.ICCR2.BIT.BBSY == 1);
}

/* ----------------------------------------------------------------------
    i2c_sendStartCondition
    i2c バスに開始要求を送る
---------------------------------------------------------------------- */
void i2c_sendStartCondition() 
{
    IIC2.ICCR2.BYTE = 0xbd;
}

/* ----------------------------------------------------------------------
    i2c_sendStopCondition
    i2c バスに停止要求を送る
---------------------------------------------------------------------- */
void i2c_sendStopCondition() 
{
    IIC2.ICSR.BIT.STOP = 0; // STOP をクリアする
    IIC2.ICCR2.BYTE = 0x3D; // 停止条件を発行する(BBSY=0, SCP=0)
    while (IIC2.ICSR.BIT.STOP != 1) ;
}

/* ----------------------------------------------------------------------
    i2c_setTransmitMode
    i2c 送受信モードの設定

    0: スレーブ受信モード
    1: スレーブ送信モード
    2: マスタ受信モード
    3: マスタ送信モード
---------------------------------------------------------------------- */
void i2c_setTransmitMode(unsigned char tmode) 
{
  switch (tmode){
    case 0: tmode = 0x00;   // MST=0, TRS=0
        break;
    case 1: tmode = 0x10;   // MST=0, TRS=1
        break;
    case 2: tmode = 0x20;   // MST=1, TRS=0
        break;
    case 3: tmode = 0x30;   // MST=1, TRS=1
        break;
  }
  // レジスタに送受信モードを設定する
  IIC2.ICCR1.BYTE = ((IIC2.ICCR1.BYTE & 0xcf) | tmode);
}

/* ----------------------------------------------------------------------
    i2c_sendByte
    i2c バスへ1バイト送信
---------------------------------------------------------------------- */
unsigned char i2c_sendByte(unsigned char ch) 
{
    unsigned char tend;

    // データを送信する
    IIC2.ICDRT = ch;
    while(1) {
        tend = IIC2.ICSR.BIT.TEND;
        if(tend == 1) break;
    }
    // ACK を返す
    return(IIC2.ICIER.BIT.ACKBR);
}

/* ----------------------------------------------------------------------
    i2c_receiveByte
    i2c バスから1バイト受信
    
    ack: データ受信時相手に返すACKビット
    rcvd: 最終バイトをリードするときは1にするこの後停止条件発行可能になる
          連続してデータ受信する場合は0
---------------------------------------------------------------------- */
unsigned char i2c_receiveByte(unsigned char ack, unsigned char rcvd) 
{
    unsigned char rdt;

    // データ受信後に送信するACKフラグを設定する。
    IIC2.ICIER.BIT.ACKBT = ack;

    // 最終バイト読み込み時は1
    IIC2.ICCR1.BIT.RCVD = rcvd;
    
    // i2c バスから送られるデータの受信開始
    rdt = IIC2.ICDRR;

    // データが8bit転送されるまで待つ
    while(IIC2.ICSR.BIT.RDRF != 1) ;

    // 揃ったデータを取り出す    
    rdt = IIC2.ICDRR;

    return(rdt);
}

/* ----------------------------------------------------------------------
    i2c_init   
    i2c初期化
---------------------------------------------------------------------- */
void i2c_init(void) 
{
    // I2C の転送レートをΦ/200(Φ=20MHz, 100kHz)に設定する
    IIC2.ICCR1.BIT.CKS = 0x0d;

  // IIC バスモードをMSB ファースト、ウェイト挿入しない、
  // ビット数: 9 ビットに設定する
    IIC2.ICMR.BIT.MLS  = 0; // MSB First
    IIC2.ICMR.BIT.WAIT = 0; // WAIT 無し
    IIC2.ICMR.BIT.BCWP = 0; // ビットカウンタライトプロテクト解除
    IIC2.ICMR.BIT.BC = 0;   // 9ビット
    IIC2.ICMR.BIT.BCWP = 1; // ビットカウンタライトプロテクト

  // IIC2 I/F モジュールの動作状態を転送動作可能状態
  // (SCL/SDA は、バス駆動状態)に設定する
    IIC2.ICCR1.BIT.ICE = 1;
}

/*----------------------------------------------------------------
  ここからRCF8574専用ルーチン
-----------------------------------------------------------------*/


void IOwrite(int address, int data)
{
    unsigned char rdt;

    i2c_waitBusFree();                      // i2c バス空き待ち
    do {
        i2c_setTransmitMode(3);             // マスタ送信モード
        i2c_sendStartCondition();           // i2c バスを開始条件にする
        rdt = i2c_sendByte(address);        // スレーブアドレスを送信
    } while(rdt == 1);                      // デバイスからACKが返るまでガンバル
    i2c_sendByte(data);                     // レジスタ番号を送信
    IIC2.ICSR.BIT.TEND = 0;
    i2c_sendStopCondition();                // i2c バスを停止条件にする。
    i2c_setTransmitMode(0);                 // スレーブ受信モードにする
}

unsigned char IOread(int address) {

    unsigned char rdt;

    i2c_waitBusFree();                      // i2c バス空き待ち
    do {
        i2c_setTransmitMode(3);             // マスタ送信モード
        i2c_sendStartCondition();           // i2c バスを開始条件にする
        rdt = i2c_sendByte(address);        // スレーブアドレスを送信
    } while(rdt == 1);                      // デバイスからACKが返るまでガンバル
    i2c_setTransmitMode(3);                 // マスタ送信モード
    i2c_sendStartCondition();               // i2c バスを開始条件にする
    i2c_sendByte(address | 1);              // スレーブアドレスを送信
    i2c_setTransmitMode(2);                 // マスタ受信モード
    IIC2.ICSR.BIT.TDRE = 0;
    rdt = i2c_receiveByte(1, 1);            // ACK=1, RCVD = 1で受信する。
    i2c_sendStopCondition();                // i2c バスを停止条件にする。
    IIC2.ICCR1.BIT.RCVD = 0;
    i2c_setTransmitMode(0);                 // スレーブ受信モードにする

    return(rdt);
}