パラレルI/O(MCP23S17編)


RaspberryでのパラレルI/Oのやり方はいくつかあります。
今回はSPI接続のIOエクスパンダであるMCP23S17を使ったパラレル I/Oを紹介します。
前回紹介したMCP23017と非常に良く似たICです。
Raspberryとの結線は以下の様になります。



初めてSPIを使う場合、こちらなどを参考 にSPIを有効にする必要があります。
最初はwiringPiライブラリを使った8BitのパラレルI/Oの例です。

//
// test program for MCP23S17 with wiringPi
// cc -o mcp23s17w mcp23s17w.c -l wiringPi
//
#include <stdio.h>
#include <time.h>
#include <wiringPi.h>
#include <wiringPiSPI.h>

#define IODIRA          0x00            // MCP23S17 address of I/O direction
#define IODIRB          0x01            // MCP23S17 address of I/O direction
#define GPIOA           0x12            // MCP23S17 address of GP Value
#define GPIOB           0x13            // MCP23S17 address of GP Value
#define ADDRESS         0b01000000      // MCP23S17 SPI Address
#define CHANNEL         0               // MCP23S17 SPI Port

// MCP23S17 SPI Data Transfer
void SPI_TX(unsigned char device, unsigned char regadd, unsigned char tx_data)
{
  char tbuf[3];
//  char rbuf[3];
  device = device & 0b11111110; // Clear last bit for a write
  tbuf[0]=device;
  tbuf[1]=regadd;
  tbuf[2]=tx_data;
  wiringPiSPIDataRW(CHANNEL,tbuf,sizeof(tbuf));
}

int main(int argc, char **argv) {
 int i;
 int loop,loopm;
 int fd;
 time_t      start_time;
 time_t      end_time;
 int byte;

 loopm=30000;
 if (argc == 2) loopm=1;

 if (wiringPiSPISetup(CHANNEL, 32000000) < 0) {
   printf("Setup Fail\n");
   return 1;
 }

 SPI_TX(ADDRESS,IODIRB,0b00000000);   // MCP23S17 port B = OUTPUT
 SPI_TX(ADDRESS,IODIRA,0b00000000);   // MCP23S17 port A = OUTPUT
 SPI_TX(ADDRESS,GPIOB,0b00000000);    // MCP23S17 Clear port B
 SPI_TX(ADDRESS,GPIOA,0b00000000);    // MCP23S17 Clear port A

 start_time = time(NULL);
 for(loop=0;loop<loopm;loop++) {
  byte=0;
  for(i=0;i<8;i++) {
   byte=(byte<<1) + 1;
   SPI_TX(ADDRESS,GPIOA,byte);
   if (argc == 2) delay(500);
  }

  byte=0xff;
  for(i=0;i<8;i++) {
   byte=byte-(1<<i);
   SPI_TX(ADDRESS,GPIOA,byte);
   if (argc == 2) delay(500);
  }
 }
 end_time = time(NULL);
 printf("time:%.1fSec\n",difftime(end_time,start_time));
}


適当な引数を指定して実行すると1回だけ処理を行います。
MCP23S17のGPA0からGPA7にLEDを付けると動きが良くわかります。
引数無しで実行すると30000回の処理を行います。

$ sudo ./mcp23s17w
time:35.0Sec

MCP23017よりも高速に処理できることが分かりました。

次にbcm2835ライブラリを使った8BitのパラレルI/Oの例です。
bcm2835ライブラリのインストールは以下の手順で行います。

$ wget http://www.open.com.au/mikem/bcm2835/bcm2835-1.49.tar.gz
$ tar xvfz bcm2835-1.49.tar.gz
$ cd bcm2835-1.49
$ ./configure
$ make
$ sudo make install

//
// test program for MCP23S17 with bcm2835
// cc -o mcp23s17b mcp23s17b.c -l bcm2835
//
#include <stdio.h>
#include <time.h>
#include <bcm2835.h>

#define IODIRA          0x00            // MCP23S17 address of I/O direction
#define IODIRB          0x01            // MCP23S17 address of I/O direction
#define GPIOA           0x12            // MCP23S17 address of GP Value
#define GPIOB           0x13            // MCP23S17 address of GP Value
#define ADDRESS         0b01000000      // MCP23S17 SPI Address

// MCP23S17 SPI Data Transfer
void SPI_TX(unsigned char device, unsigned char regadd, unsigned char tx_data)
{
  char tbuf[3];
  char rbuf[3];
  device = device & 0b11111110; // Clear last bit for a write
  tbuf[0]=device;
  tbuf[1]=regadd;
  tbuf[2]=tx_data;
  bcm2835_spi_transfernb(tbuf,rbuf,3);
}

int main(int argc, char **argv) {
 int i;
 int loop,loopm;
 time_t      start_time;
 time_t      end_time;
 int byte;

 loopm=30000;
 if (argc == 2) loopm=1;

 if (bcm2835_init() == -1) {
   printf("bmc2835_init Error\n");
   return 1;
 }
 bcm2835_spi_begin();
 bcm2835_spi_setBitOrder(BCM2835_SPI_BIT_ORDER_MSBFIRST); // The default MSBFIRST
 bcm2835_spi_setDataMode(BCM2835_SPI_MODE0); // The default MODE0
 bcm2835_spi_setClockDivider(BCM2835_SPI_CLOCK_DIVIDER_8); // The default 65536
 bcm2835_spi_chipSelect(BCM2835_SPI_CS0); // The default CS0
 bcm2835_spi_setChipSelectPolarity(BCM2835_SPI_CS0, LOW); // the default LOW

 SPI_TX(ADDRESS,IODIRB,0b00000000);   // MCP23S17 port B = OUTPUT
 SPI_TX(ADDRESS,IODIRA,0b00000000);   // MCP23S17 port A = OUTPUT
 SPI_TX(ADDRESS,GPIOB,0b00000000);    // MCP23S17 Clear port B
 SPI_TX(ADDRESS,GPIOA,0b00000000);    // MCP23S17 Clear port A

 start_time = time(NULL);
 for(loop=0;loop<loopm;loop++) {
  byte=0;
  for(i=0;i<8;i++) {
   byte=(byte<<1) + 1;
   SPI_TX(ADDRESS,GPIOA,byte);
   if (argc == 2) delay(500);
  }

  byte=0xff;
  for(i=0;i<8;i++) {
   byte=byte-(1<<i);
   SPI_TX(ADDRESS,GPIOA,byte);
   if (argc == 2) delay(500);
  }
 }
 end_time = time(NULL);
 printf("time:%.1fSec\n",difftime(end_time,start_time));

//  bcm2835_i2c_end();
  bcm2835_close();
}

適当な引数を指定して実行すると1回だけ処理を行います。
引数無しで実行すると30000回の処理を行います。

$ sudo ./mcp23s17b
time:1.0Sec

ものすごく高速に処理できることが分かります。

パラレルI/Oを行う場合、74HC595を使うか、MCP23S17+BCM2835がお勧めです。
前から気になっていたんですが、wiringPiライブラリはbcm2835ライブラリに比べて、少しパフォーマンスが悪いみたいです。
特にSPIの場合の差が顕著です。
30000回のパラレルI/Oの時間比較。

wiringPiライブラリ bcm2835ライブラリ
74HC595
2秒
1秒
MCP23017(i2c)
209秒
145秒
MCP23S17(SPI)
35秒
1秒