CCS-C,C18,C32によるMCP23017のコントロール

手軽にI/Oを増設するデバイスとしてMCP23017があります。このデバイスをI2Cでコントロールする方法についてのPICでのサンプルは、WEBではあまり見つかりません(簡単すぎるからかな..HAHA)。他のMPUでの記述は結構あります。それで手始めに、PIC18F2420 + MCP23017の構成で CCS-C と C18 を使用して、それぞれ動作させてみたものを掲載します。
また、この回路を少しだけ改造すれば、32ビットの PIC32MX220F032B などに差し替えられますので、これで C32 を使って動作させてみましたので、そのサンプルも掲載します。

[] CCS-C と C18 によるサンプル

 (1)  回路図
    PIC18F2420を使用しています。また、PICkit2を接続したままディバッグできるようにしています。
   RS232Cは、I/O拡張の評価には必要はないのですが、デバッグ用としてCN5を通して出しています。RS232Cの
   レベル変換はいつも使用するので、別のミニ基板に入れて、その基板にまた、PICkit2(または3)を接続するため
   のコネクタを付けています(写真参照)。
   
リセットICを使っていますが、特に理由はないので、適当に変更してください。
     

 (2)  CCS-Cでのソース
   C18 および C32 でも同様ですが、whileループで、OutputTest(InputTest()); としています。
   これは、入力に設定している GPIOB の8ビットを入力し、出力に設定している GPIOA に出力し続ける動作
   です。

  ( TOP部とメインのInitialize+Loop部 )
   #include <18F2420.h>
   #device PASS_STRINGS = IN_RAM //for the pointer of Strings in ROM
   #include <string.h>

   //configure a 10MHz crystal to operate at 40MHz
   #fuses HS,H4,WDT256,NOPROTECT,PUT,NOLVP,NODEBUG // H4: 10->40MHz
   #use delay(clock=40000000)
   #use rs232(baud=19200, parity=N, xmit=PIN_C6, rcv=PIN_C7, bits=8)
   #use I2C(MASTER,SDA=PIN_C4,SCL=PIN_C3,RESTART_WDT,FAST)
   #use fast_io(A)
   #use fast_io(B)
   #use fast_io(C)
     ・省略
   typedef unsigned int Uint; // 8 bits
   typedef unsigned long Ulng; // 16 bits
   typedef signed int Sint; // 8 bits
   typedef signed long Slng; // 16 bits
   typedef unsigned char Uchar;
   const char verStr[] = "IoExpander2_131018";
     ・省略
   void main(void)
   {
     set_tris_a(0x0F); // Port A
     ・省略
     output_float(PIN_C3); //SCL(I2C)
     output_float(PIN_C4); //SDA(I2C)
     ・省略
     ConfigMCP23017(); // Configuration of MCP23017

      while(1) {
       OutputTest(InputTest()); // GPIOB(In) -> GPIOA
       restart_wdt();
     }
   }


  ( MCP23017のコントロール部 )
   void ConfigMCP23017(void) {
      // Set OUTPUT All bits of GPA
      i2c_start();
      DataTrans(0x40); // CB(W) adrs=0x20, (adrs<<1 + W(0))
      DataTrans(0x00); // reg-adrs IODIRA
      DataTrans(0x00); // Set data:0x00, All OUT
      i2c_stop();

      // Set INPUT All bits of GPB
      i2c_start();
      DataTrans(0x40); // CB(W) adrs=0x20, (adrs<<1 + W(0))
      DataTrans(0x01); // reg-adrs IODIRB
      DataTrans(0xFF); // Set data:0xFF, All IN
      i2c_stop();

      // Set Reverse INPUT All bits of GPB
      i2c_start();
      DataTrans(0x40); // CB(W) adrs=0x20, (adrs<<1 + W(0))
      DataTrans(0x03); // reg-adrs IPOLB
      DataTrans(0xFF); // Set data:0xFF, All reverse
      i2c_stop();
   }

   /****************************************
   /* Input(Read) from MCP23017 Port *
   ****************************************/
   unsigned char InputTest(void){
      unsigned char data;

      i2c_start();
      DataTrans(0x40); // CB(W) adrs=0x20, (adrs<<1 + W(0))
      DataTrans(0x13); // reg-adrs GPIOB
      i2c_start(); // Restart
      DataTrans(0x40 | 0x01); // Chip Adrs & Rcmd, (adrs<<1 + R(1))
      data = i2c_read(0);
      i2c_stop();
      return(data); // Return input data
   }

   /****************************************
   * Output data to MCP23017 Port *
   ****************************************/
   void OutputTest(unsigned char dat) {
      // Set data to GPA
      i2c_start();
      DataTrans(0x40); // CB(W) adrs=0x20, (adrs<<1 + W(0))
      DataTrans(0x12); // reg-adrs GPIOA
      DataTrans(dat); // Set data
      i2c_stop();
   }

   // Common write processer of I2C
   void DataTrans(unsigned char data){
      i2c_write(data);
   }


 (3)  C18のソース
   入力に設定している GPIOB の8ビットを、出力に設定している GPIOA に出力し続ける動作です。

  ( TOP部とメインのInitialize+Loop部 )
   #include <p18f2420.h>
   #include <i2c.h>
   #include <usart.h>
   #include <stdio.h>

   /* コンフィギュレーションの設定   2320/2420 */
   #pragma config OSC=HSPLL, FCMEN=OFF, IESO=OFF, PWRT=ON // FSCM/FCMEN
   #pragma config BOREN=ON, BORV=2, WDT=OFF, WDTPS=1024 // BOR/BOREN, BORV
   #pragma config MCLRE=ON, PBADEN=OFF, CCP2MX=PORTC // PBAD/PBADEN, CCP2MX
   #pragma config STVREN=ON, LVP=OFF, DEBUG=OFF // STVR/STVREN
   #pragma config CP0=OFF, CP1=OFF, CPB=OFF // 2420:CP2,CP3なし
   #pragma config CPD=OFF, WRT0=OFF, WRT1=OFF // 2420:WRT2~3なし
   #pragma config WRTB=OFF, WRTC=OFF, WRTD=OFF, EBTR0=OFF
   #pragma config EBTR1=OFF, EBTRB=OFF // 2420:EBTR2~3なし
      ・省略
   const char verStr[] = "IoExpander_131017\r\n";
      ・省略

   void main(void){
      ・省略
     InitializeI2C(); //I2C の初期化(SSPの初期設定I2Cモード)
     ConfigMCP23017(); // MCP23017のコンフィグ

      while(1) {
       OutputTest(InputTest());   // GPIOBの8ビットを GPIOA に連続出力する
     }
   }


  ( MCP23017のコントロール部 )
   void InitializeI2C(void) // I2C の初期化(SSPの初期設定I2Cモード)
   {
      OpenI2C(MASTER, SLEW_ON); //マスターモードに設定
      SSPADD = 24; //400kbpsに設定(40M:24, 41.67M:25.04)
   }

   void ConfigMCP23017(void) {
      // GPA0〜GPA7の全ビットを出力ポートに設定
      StartI2C(); // スタート条件出力
      while(SSPCON2bits.SEN); // 出力完了待ち
      DataTrans(0x40); // CB(W) adrs=0x20
      DataTrans(0x00); // reg-adrs IODIRA選択
      DataTrans(0x00); // 設定値 0x00
      StopI2C(); // ストップ条件出力
      while(SSPCON2bits.PEN); //出力完了待ち

      // GPB0〜GPB7の全ビットを逆入力に設定
      StartI2C(); // スタート条件出力
      while(SSPCON2bits.SEN); // 出力完了待ち
      DataTrans(0x40); // CB(W) adrs=0x20
      DataTrans(0x03); // reg-adrs IPOLB選択
      DataTrans(0xFF); // 設定値 0xFF
      StopI2C(); // ストップ条件出力
      while(SSPCON2bits.PEN); //出力完了待ち
   }

   /****************************************
   /* MCP23017ポート読み出し関数 *
   ****************************************/
   unsigned char InputTest(void){
      unsigned char data;
      StartI2C(); //スタート条件出力
      while(SSPCON2bits.SEN); //出力完了待ち
      DataTrans(0x40); // CB(W) adrs=0x20
      DataTrans(0x13); // reg-adrs GPIOB選択
      RestartI2C(); //リスタート条件出力
      while(SSPCON2bits.RSEN); //出力完了待ち
      DataTrans(0x40 | 0x01); //チップアドレスと制御データ出力
      while(DataRdyI2C()); //データ入力待ち
      data = ReadI2C(); //データ読み込み
      SSPCON2bits.ACKDT = 1; //NACK出力準備
      SSPCON2bits.ACKEN = 1; //NACK出力
      while(SSPCON2bits.ACKEN); //出力完了待ち
      StopI2C(); //ストップ条件出力
      while(SSPCON2bits.PEN); //出力完了待ち
      return(data); //データを戻り値にする
   }

   /****************************************
   * MCP23017ポート書き込み関数 *
   ****************************************/
   void OutputTest(unsigned char dat) {
      // GPA0〜GPA7の全ポートに設定値を出力
      StartI2C(); // スタート条件出力
      while(SSPCON2bits.SEN); // 出力完了待ち
      DataTrans(0x40); // CB(W) adrs=0x20
      DataTrans(0x12); // reg-adrs GPIOA選択
      DataTrans(dat); // 設定値 dat
      StopI2C(); // ストップ条件出力
      while(SSPCON2bits.PEN); //出力完了待ち
   }

   // I2Cデータ出力共通関数
   void DataTrans(unsigned char data){
      WriteI2C(data); //データ出力実行
      IdleI2C(); //バスアイドル待ち
   }


[] C32 によるサンプル

   
 (1)  回路図
   PIC32MX220F032Bを使用しています。また、PICkit3を接続したままディバッグできるようにしています。
    PICkit3とRS232Cの取り扱いは、上と同じです。MCP23017のみ5V動作としています。このような使い方ができ
    るので、どうしても5Vで外部I/Oを扱う時に便利です。もちろん3.3Vでも動作します。

  
 (2)  C32のソース
   入力に設定している GPIOB の8ビットを入力し、出力に設定している GPIOA に出力し続ける動作です。

  ( TOP部とメインのInitialize+Loop部 )
   #include <stdio.h>
   #include <plib.h>
   #include <p32xxxx.h>
   #include <stdlib.h>

   //== 内臓OSC:8MHz SYSCLK:48MHz=(OSC_8*MUL_24)/(PLLI_2*PLLO_2) ====
   #pragma config FNOSC=FRCPLL, POSCMOD=OFF, FPLLIDIV=DIV_2
   #pragma config FPLLMUL=MUL_24, FPBDIV=DIV_1, FPLLODIV=DIV_2
   #pragma config FWDTEN=OFF, ICESEL=RESERVED //ICS_PGx2
   #pragma config FUSBIDIO=OFF, FVBUSONIO=OFF, FSOSCEN=OFF
      ・省略
   const Uchar verStr[] = "ExpIOtest1_131026";
      ・省略

   int main(void) {
       SYSTEMConfigPerformance(48000000); //48MHz 最適化
     mJTAGPortEnable(DEBUG_JTAGPORT_OFF) //JTAGを無効化
       ・省略

     //== I2Cの初期設定 ==
     //400kbps 7bit address ((PBCLK/2/Fsck)-2) 480*10^5/(2*4*10^5)-2=58
     OpenI2C1(I2C_ON | I2C_7BIT_ADD, 58);
     ConfigMCP23017(); // Configuration of MCP23017

      while(1) {
       OutputTest(InputTest());   // GPIOBの8ビットを GPIOA に連続出力する
       ClearWDT();
     }
   }


  ( MCP23017のコントロール部 )
   void ConfigMCP23017(void) {
      // Set OUTPUT All bits of GPA
      StartI2C1(); // Send the Start Bit
      IdleI2C1(); // Wait to complete
      DataTrans(0x40); // CB(W) adrs=0x20, (adrs<<1 + W(0))
      DataTrans(0x00); // reg-adrs IODIRA
      DataTrans(0x00); // Set data:0x00, All OUT
      StopI2C1(); // Send the Stop condition
      IdleI2C1(); // Wait to complete

      // Set INPUT All bits of GPB
      StartI2C1(); // Send the Start Bit
      IdleI2C1(); // Wait to complete
      DataTrans(0x40); // CB(W) adrs=0x20, (adrs<<1 + W(0))
      DataTrans(0x01); // reg-adrs IODIRB
      DataTrans(0xFF); // Set data:0xFF, All IN
      StopI2C1(); // Send the Stop condition
      IdleI2C1(); // Wait to complete

      // Set Reverse INPUT All bits of GPB
      StartI2C1(); // Send the Start Bit
      IdleI2C1(); // Wait to complete
      DataTrans(0x40); // CB(W) adrs=0x20, (adrs<<1 + W(0))
      DataTrans(0x03); // reg-adrs IPOLB
      DataTrans(0xFF); // Set data:0xFF, All reverse
      StopI2C1(); // Send the Stop condition
      IdleI2C1(); // Wait to complete
   }

   /****************************************
   /* Input(Read) from MCP23017 Port *
   ****************************************/
   unsigned char InputTest(void){
      unsigned char data;
      StartI2C1(); // Send the Start Bit
      IdleI2C1(); // Wait to complete
      DataTrans(0x40); // CB(W) adrs=0x20, (adrs<<1 + W(0))
      DataTrans(0x13); // reg-adrs GPIOB

      RestartI2C1(); // Send the Restart condition
      IdleI2C1(); // Wait to complete
      DataTrans(0x40 | 0x01); // Chip Adrs & Rcmd, (adrs<<1 + R(1))
      data = MasterReadI2C1();
      StopI2C1(); // Send the Stop condition
      IdleI2C1(); // Wait to complete
      return(data); // Return input data
   }

   /****************************************
   * Output data to MCP23017 Port *
   ****************************************/
   void OutputTest(unsigned char dat) {
      // Set data to GPA
      StartI2C1(); // Send the Start Bit
      IdleI2C1(); // Wait to complete
      DataTrans(0x40); // CB(W) adrs=0x20, (adrs<<1 + W(0))
      DataTrans(0x12); // reg-adrs GPIOA
      DataTrans(dat); // Set data
      StopI2C1(); // Send the Stop condition
      IdleI2C1(); // Wait to complete
   }

   // Common write processer of I2C
   void DataTrans(unsigned char data){
      MasterWriteI2C1(data);
      IdleI2C1(); // Wait to complete
   }


 (3)  デバッグの状況

   GPIOBの8ビット入力をGPIOAに連続出力している状況です。ディップSWを動かすと入力データに
   応じてLED点灯しデータが変わります。

   


[
] 動作させた感想など
ご承知のように、PICのCコンパイラもデバイスに応じて色々出ています。マイクロチップ様の提供の分だけでも C18, C30, C32などがあり、C言語の論理の記述は同じですが、このI2Cの記述やその他のペリフェラルの記述 (その他、割込みの記述などなど)が、この3つのコンパイラで大きく違っているので、慣れるのに時間がかかります。
RS232Cを使ってのディバッグで使用する puts や printf 、そして sprintf の使用は、マイクロチップ様のコンパイラでは、多くのメモリを必要とし、かつコンパイラごとにその動作が少しずつ異なっています。この部分(RS232C)についても、またI2Cでも、CCS-C ではスッキリと使いやすくなっています。私個人の独断と偏見から言うと、この4コンパイラの中では、CCS-C と C32 が一番使い易いと感じています。

18F2420 は、32MX220F032B より安いのではないかと思っていたのですが、デジキーでは 32MX220F032B は、18F2420 の80%以下の価格ですね。秋月さんでは、32MX110F016B などは、なんと190円で供給されているので、これからは32Bitを使う機会も増えるでしょう。
私は、PIC16F84A の前々バージョンの名称が、PIC16C84 と言っていたころに MS-DOS上のアセンブラで動かしていましたが、その頃から見るとまったく隔世の感がありますね。しかし、採算が採れないとなるとスグに生産中止してしまって我々を困らせてしまう企業が多いなかで、価格を上げてでも古い物を供給し続けている姿勢は、賞賛に値すると思います。

( 出力の実動作速度 )
I2Cを含めた詳しい解析は別として、上記の2つの回路で 10,000回 GPIOAに出力したときの1出力当たりの時間を測定しました。ただし、この時間には、I2C通信以外に、ループ処理やカウントアップ処理などが含まれています。
PIC18F2420(ただしクロック40M)     -> 108us
PIC32MX220F032B(ただしクロック48M) -> 87us
ミリセカンドクラスの入出力の精度が要求される場合の1つの目安となると思います。
MCP23017(28ピンDIP)の価格は、デジキーにて25個の単価が108円(2013/10)であり、28ピンDIPのMPUに多くのI/Oを必要とする場合などには、最大で128(16x8個)のI/Oまでアドレス(A0,A1,A2)を設定して、簡単に拡張できますので大変便利ですね。

コントロールするための資料は、マイクロチップ様より仕様書をダウンロードして下さい。これでは割り込みなどは使っていません。
参考にレジスタマップのみ置いておきます。デフォルトでは、IOCON.BANK = 0 です。
     

【 追記 】
2015,1,5
昨年(2014)の10月ころに MPLAB X をインストールして使用しました。エディタなどは大きく改良されており、さらに XC8、XC16やXC32などのとの間に統一された記述ができるようになり、扱い易くなっていると思います。ただ、同時にインストールした「PICkit 3 v3.10」ではPIC32MX220F032B の書き込みができなくなり、かつ御存知かと思いますが正常に終了しません(この現象は以前から出ていたが、書き込みはできていた)。しかし、このバージョンでは最新のデバイスがサポートされてなく、それらに書き込むには、IPE(同時にインストールされる書き込みツール)を使用するようにという記述が下記のリンクにありました。
 http://www.microchip.co.jp/support/faq_debugger.html (Question 6)
IPE(v2.26)を起動すると PIC32MX220F032B はサポートされています。しかし、'Connect' ができません。色々悩みましたが、結局のところ「PICkit 3 v3.10」では、単独のモードと'MPLAB mode'があり、MPLAB mode にしないと IPE が動作しないことが分かりました。
それで一旦、「PICkit 3 v3.10」を起動し、「Tools」-「Revert to MPLAB mode」で MPLAB mode に切り替えて終了し、IPEを起動すると書き込めるようになりました。
確かに、「MPLAB X IDE」はとても素晴らしい開発環境を提供しつつあるとは思いますが、まだまだ今のところは一部不具合なども見受けられ改良中のようですね(現在 v2.26)。
「PICkit 3 v3.10」が最新のデバイスをサポートしていないため、余計な作業を強いられてしまいました。つまり今後は、PICkit3の単独の動作モードは、切り捨てて IPE に移行して行くと言うことのようですね。
まとめますと、「MPLAB X 」をインストールすると、PICkit3 からの最新のデバイスへの書き込みは IPE からでないとできないです。言い換えれば、「PICkit 3 v3.10」は最終版であり、バージョンアップは無いということです。
(追記)「MPLAB X 」をインストールしてしばらく後に、PICkit 2 を起動してあるデバイスの書き込みをしようとしましたが、それはサポート外になっていました。やむを得ず IPE から書き込みました。どうも、「MPLAB X 」をインストールすると PICkit 2,3 はダウングレードされるみたいですね?
2015,1,14
Harmony(v1_02)を少し扱ってみました。これは、PIC32MX, MZ 専用の開発環境なんですね。取扱説明書は、なんと英文で5349ページもあり驚きです。また、PIC32MZ2048ECHのプログラムサイズは2MB、データサイズは512K、速度は200MHz と驚きのスペックです。取説のサイズと言いデバイスのスペックと言い、従来のPICとは一線を画しています。
私達ユーザーにとっては、MPLAB、MPLAB XのどちらのIDEを使うのか、またコンパイラは従来のものかXCシリーズかの選択をしなければなりません。MPLAB Xは、MPLABが持っていた機能の一部をサポートしていないところもあり、単純なものは別として Importしても素直にコンパイルできない事も多々あるようです。どう使い分けるか悩ましいことではありますが 、いずれにしろPIC32については Harmonyが主流になって行くことは間違いないですね。現在、PIC32MXシリーズのDIP28pinタイプをHarmony環境で動作させるテストをしています。この規模のもので Harmonyを適用するのが良いのか、それとも単純に Harmony を組み込まないMPLAB X+C32の方が良いのかの検討のためでもあります。
Harmonyをインストールした時のディレクトリー構成を見ると、従来の「Microchip Solutions」に比較すると、私にとってはとても分かり易くなっていると感じます。どちらにしろ、開発規模に応じて最適な選択をしていけば良いのではないでしょうか。
2015,1,16
PIC32MX で plib.h にある組み込み関数を使ったソースを XC32でコンパイルすると大量のワーニングがでます。最初は少しビックリしてしまいます。ワーニングの内容は・・・
The PLIB functions and macros in this file will be removed from the MPLAB XC32 C/C++ Compiler in future releases.
・・・というもので、XC32では、PLIBは将来は削除しますよということらしい。対応方法としては、組込関数を使わないで直接レジスタ設定の記述に変更するか、もしくは、コンバイラのみは、C30を使用し続けるかのどちらかを選ぶしかないようですね。しかし本当は、C32からXC32への移行を容易にするための努力はしてほしいものです。

Reported by TokioYamada@ADK


汎用製品通販のページへ        高速フーリエ変換(dsPIC-FFT)へ         USB-IOのページに戻 る