ま"’s blog

電子工作の部屋?

ArduinoをI/Oエキスパンダとして使う

前置き: キャッチーにArduinoをタイトルに持ってきていますが、正確にはDIP28ピンタイプのATmega8/88/168/328マイコンを指します。Arduinoボードでも再現できると思いますが、私は基本的に内臓クロックモードで実験・製作してますので純正Arduinoボードをお使いの方はご注意ください。

前置きその2: いつのまにかはてなスターを頂きました。とても嬉しいです。どうもありがとう。仕事で疲れて帰ってきましたが、頑張って書くことにしました。

前置きその3: はてなブログヘルプトップから辿り着けなかった、「はてな記法一覧」を検索で見つけたのでリンクを貼ります。ダッシュボードあたりにメモ書きとして置いておきたいのですが、なにかいい方法ないもんでしょうかね?

本題

ラズパイにはADCが載っていないのでアナログ値を測定したい場合には 別途ADCチップを繋げないといけません。また秋月で入手可能なADCにはちょうどいいのが見当たらないです。そんな折、「Arduino / I2C Slave デバイスを作る」
ysin1128.hatenablog.com
という記事を見かけたのでArduinoが I2C経由のADCになるのでは?。と考えました。GPIOも当然使えるし、 PWMのアナログ出力もできるIOエクスパンダにできるのではないか?。と思いつきました。

アクセス方法

ということにしておこうと思います。 インタラプトについては別途必要に応じて考えることにしました。

I2Cデバイスアドレス

0x26とします(7bit表記、8bit表記だと0x4c?)。

ピンのアサイ

UARTのRX/TX(2,3番ピン=デバッグ用), I2CのSCL/SDA(28, 27ピン)はシステムに必須なので除きます。 残るはD2〜D13, A0〜A3ですが、 D10をLOWにすると書き込みモードになるみたいなので、 D10だけ除くことにします。 ピンアサインと内部表現としてはできるだけ一致かつ順番に並べることにします。

名称 別名 ピン番号 備考
D2 INT0 4 0x2
D3 INT1 5 0x3
D4 6 0x4
D5 PWM0 11 0x5
D6 PWM1 12 0x6
D7 13 0x7
D8 14 0x8
D9 PWM2 15 0x9
D11 17 0xB
D12 18 0xC
D13 19 0xD
A0 C0 23
A1 C1 24
A2 C2 25
A3 C3 26

ADCの値を読むのは該当するレジスタを読むことで、そのレジスタへの書き込みはPWM出力設定に割り当てます。PWM3は無いので、A3はread onlyということで。

コントロールレジスタ

とりあえず必要最小限だけ書き出してみました。

Address 名称 意味
00h IODIRL D0〜D7の入出力方向
01h IODIRH D8〜D13の入出力方向
02h OLATL D0〜D7の値
03h OLATH D8〜D13の値
04h ALAT0 A0の値
05h ALAT1 A1の値
06h ALAT2 A2の値
07h ALAT3 A3の値(readのみ)

あとは必要に応じて追加することにしましょう。

IODIR

0がinput, 1がoutput。

IODIRL
7bit 6bit 5bit 4bit 3bit 2bit 1bit 0bit
D7 D6 D5 D4 D3 D2 未使用 未使用
IODIRH
7bit 6bit 5bit 4bit 3bit 2bit 1bit 0bit
未使用 未使用 D13 D12 D11 未使用 D9 D8

OLAT

readでピンの値を入力、writeで出力。
ピンとビットのアサインはIODIRと一緒です。
ピンをOUTPUTモード設定していても、digitalRead()できるので、その値を返します。

ALAT

readでADCの値読み込み、writeでPWM出力。
ArduinoのADCの値は0〜1023(10bit)なのですが、とりあえず今回は上位8bitだけ使用することにします。(つまり0〜255)
Arduinoでは出力ピンを明示的にOUTPUTに指定していなくとも、analogWrite()できてしまうので、ここでもその仕様を踏襲します。こちらの値の範囲は0〜255です。

スケッチ

#include <Wire.h>

#define REG_LENGTH  8

#define IODIRL  0
#define IODIRH  1
#define OLATL   2
#define OLATH   3
#define ALAT0   4
#define ALAT1   5
#define ALAT2   6
#define ALAT3   7

#define PWM_0   5
#define PWM_1   6
#define PWM_2   9

byte byteSlaveADR = 0x26; // 7-bit Slave Address
byte byteADR;
byte byteDAT[REG_LENGTH];
byte byteREG[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
bool trig; 
byte maskPortsL = 0b00000011;  // System use these ports
byte maskPortsH = 0b00000100;  // System use these ports

void setup() {
  
  Wire.begin(byteSlaveADR);
  Wire.onReceive(receiveEvent);
  Wire.onRequest(sendEvent);

}

void loop() {
}

void receiveEvent(int val){
  int i = 0;
  int j;
  byte byteTMP;
  
  while(Wire.available()){
    if(i < 32){
      byteDAT[i] = Wire.read();      
    }
    else{
      Wire.read();
    }
    i = i + 1;
  }

  byteADR = byteDAT[0];

  if(i > 1){
    for(j=0;j<(i-1);j++){
      byteTMP = byteADR + j;

      if(byteTMP < REG_LENGTH){
        byteREG[byteTMP] = byteDAT[j+1];

        if(byteTMP == 0){
          trig = true;
        }
        switch (byteTMP) {
          case IODIRL:
            setIoDirL(byteREG[byteTMP]);  break;
          case IODIRH:
            setIoDirH(byteREG[byteTMP]);  break;
          case OLATL:
            setLatchL();  break;
          case OLATH:
            setLatchH();  break;
          case ALAT0:
            analogWrite(PWM_0, byteREG[byteTMP]);  break;
          case ALAT1:
            analogWrite(PWM_1, byteREG[byteTMP]);  break;
          case ALAT2:
            analogWrite(PWM_2, byteREG[byteTMP]);  break;
        }
      }
    }
  }
}

void sendEvent(){
  int i;

  getLatch();
  getAnalogVal();
  for(i=byteADR;i<16;i++){
    Wire.write(byteREG[i]);
  }
}

void  getLatch() {
  byte  btmp = 0;

  for (int i = 7; i >= 0; i--) {
    btmp = (btmp << 1) | (digitalRead(i) & 0x1);
  }
  byteREG[OLATL] = btmp;
  
  for (int i = 13, btmp = 0; i >= 8; i--) {
    btmp = (btmp << 1) | (digitalRead(i) & 0x1);
  }
  byteREG[OLATH] = btmp;
}

void  getAnalogVal() {
  byteREG[ALAT0] = analogRead(A0) >> 2;
  byteREG[ALAT1] = analogRead(A1) >> 2; 
  byteREG[ALAT2] = analogRead(A2) >> 2;
  byteREG[ALAT3] = analogRead(A3) >> 2;
}

void  setIoDirL(byte dir) {
  for(int i = 7; i >= 0; i--) {
    if (!((maskPortsL >> i) & 0x1)) {
      if ((dir >> i) & 0x1) {
        pinMode(i, OUTPUT);
      } else {
        pinMode(i, INPUT);
      }
    }
  }
}

void  setIoDirH(byte dir) {
  for(int i = 13; i >= 8; i--) {
    int   j = i - 8;
    if (!((maskPortsL >> j) & 0x1)) {
      if ((dir >> j) & 0x1) {
        pinMode(i, OUTPUT);
      } else {
        pinMode(i, INPUT);
      }
    }
  }
}

void  setLatchL() {
  byte  btmp;
  for(int i = 7; i >= 0; i--) {
    if (!((maskPortsL >> i) & 0x1)) {
      digitalWrite(i, (byteREG[OLATL] >> i) & 0x1);
    }
  }
}

void  setLatchH() {
  byte  btmp;
  for(int i = 13; i >= 8; i--) {
    int   j = i - 8;
    if (!((maskPortsL >> j) & 0x1)) {
      digitalWrite(i, (byteREG[OLATH] >> j) & 0x1);
    }
  }
}

ラズパイとI2C接続してみました。

ラズパイとI2C通信

GPIOエクステンションボードのフラットケーブルの先にラズパイがあります。ラズパイとArduinoのSCL/SDAをそれぞれ繋ぎます(写真では黄色がSCL, 橙がSDAです)。ポテンショメーターの両端をGND/Vccでつなぎ、中間値の線をArduinoのA0(写真では緑線)。ArduinoのD9(PWM)に赤色LED+220Ω抵抗繋げてGNDに落とします。写真では見づらいですがビルトインLEDに相当するD13ピンに抵抗と緑色LEDがつながっています。
残念ながらBBBBの実験用回路を組むスペースがないという欠点が如実に現れました。隣のラズパイ用のブレッドボードに寄生してます。

テストプログラムは以下の様になります。

#include	<wiringPiI2C.h>
#include	<wiringPi.h>
#include	<stdio.h>

#define 	IODIRL  0
#define 	IODIRH  1
#define 	OLATL   2
#define 	OLATH   3
#define 	ALAT0   4
#define 	ALAT1   5
#define 	ALAT2   6
#define 	ALAT3   7


int 	fd;

int main()
{
	fd = wiringPiI2CSetup (0x26);
	int	i = 1;
	int	aval;

	wiringPiI2CWriteReg8(fd, IODIRH, 0b00100000);	// set LED pin for output

	while(1) {
		wiringPiI2CWriteReg8 (fd, OLATH, i << 5);
		aval = wiringPiI2CReadReg8(fd, ALAT0);
		printf("ALAT0 = %d\n", aval);
		wiringPiI2CWriteReg8 (fd, ALAT2, 255 - aval);
		i = !i;
		delay(1000);
	}
}
  • 動作してるかどうかを見るためにD13番ピンを1秒単位で交互にON/OFFさせてます。
  • A0に繋げたポテンショメーターの値を1秒毎に表示して、その値に応じた値をD9(PWM)にanalogWriteさせてます。
  • ADCの値でそのままLED点灯させても、よくわからないので、反転させて点灯させてます。

今後の展望

汎用化させて色々なモードやら組み合わせやらを追加する予定はありません。それよりもマスター側の処理を楽にするように、マイコン側で拡張デバイスに合わせたロジックを追加してカスタム化するつもりです。
例えば、アナログ値の最上位ビットを符号ビットとして、プラスを前進/マイナスを後退のモーター制御にするとかですね。

記事にラズパイの話も出たことだし、ラズパイのグループにも参加しようかな?