LowTech.com



 同じアドレスを持つ複数のI2Cデバイスから、ポート単位でデータを読み込む実験


 同じアドレスのI2Cデバイスを複数個使いたい場合、アナログスイッチや専用ICを使う方法が普通ですが、I2C通信プログラムを自作さえすれば本実験のような8ビットポートを丸ごと読み込むことで8個(Arduino-UNOの場合は6個)のデバイスから、同一タイミングでデータを収集するなどということも可能になります。ということでI2C通信の勉強を兼ねて並列化I2Cシリアル通信プログラムなるものを作ってみました。

(1)回路図

Arduino and 6ch Color Sensor(S11059-02)

 カラーセンサーのSDAは、ArduinoのPortB(Pin8~13)にMOS-FETを介して接続します。SCLは基本的には一方通行ですので抵抗分圧したものを各センサーへ並列に供給し、電気的保護とレベル変換(5V-3.3V)を行います。また、6つのカラーセンサーの近傍に6種類のLEDを設置して発光させ、どのようなデータが得られるか確認しました。これらの回路は下の写真のようにブレッドボード上に作っています。






(2)ソフトウェア

 プログラムは、メインとなる「Color_sens_6ch.iso」とI2C通信用のサブプログラム「i2c_wr_1port.iso」の2つで構成されます。
メインとなるプログラムの詳細は次のとおりです。

① Color_sens_6ch.iso

#define integ_multi_val   10  //マニュアルモード時、積分時間を決定する乗算値。

extern void i2c_start_1port();
extern void i2c_read_1port(unsigned char);
extern void i2c_write_1port(unsigned char);
extern void i2c_stop_1port();
void get_color();

typedef struct{
 unsigned int R;
 unsigned int G;
 unsigned int B;
 unsigned int IR;
}DAT;

unsigned char pin_dt[8]; //PortBから8ch分丸ごと読み込んだデータを格納する配列。
unsigned char read_portBit[8][8];//PortBから読み込んだデータをセンサー毎に整理して格納する配列。
                                 //read_portBit[センサー番号][データ番号(0=Rh,1=RL,,,6=IRh,7=IRL)]
DAT RGBI[8]; //16ビットデータに変換後の色データを格納する構造体配列。RGBI[センサー番号].R、.G、.B、.IR
unsigned char ack_pb; //各センサー(ch0~7)からのアクノリッジ。


void setup() {
  DDRB = B00111111; //PortB 入出力方向設定レジスタ 0=入力、1=出力
  DDRC = B00111111; //PortC
  DDRD = B11111100; //PortD
  // serial com. 9600 bps
  Serial.begin(9600);
  i2c_start_1port();  //I2Cスタート
  i2c_write_1port(0x54); // I2C アドレスコールコマンド
  i2c_write_1port(0x00); // アクセスするアドレスをセット。コントロールレジスタ(0x00)
  i2c_write_1port(0b11101101); //セットしたアドレス(0x00)へ書き込み。
                               //ADCreset(b7),sleep(b6),high gain(b3),manual mode(b2),01(b1,b0)=2.8ms
  i2c_write_1port(0x00); //積分時間をマニュアル設定にした場合の乗算値(上位バイト)
  i2c_write_1port(integ_multi_val); //同上(下位バイト),積分時間=2.8ms*integ_multi_val(10)=28ms
  i2c_stop_1port();  //I2Cストップ
}


void get_color(){ //8ch分の色データを読み込んで、RGBI[0~7].R、.G、.B、.IRへ格納する。
 unsigned char i, j, wk1, d1;

    ack_pb= 0;
    i2c_start_1port();  //I2Cスタート
    i2c_write_1port(0x54); //I2C アドレスコールコマンド
    i2c_write_1port(0x00); //アクセスするアドレスをセット。コントロールレジスタ(0x00)
    i2c_write_1port(0b10001101); //セットしたアドレス(0x00)へ書き込み。
                                 //ADCリセット,スリープ解除,high gain(b3=1),manual mode(b2=1),01=2.8ms*X
    i2c_start_1port(); //I2Cリスタート
    i2c_write_1port(0x54); //I2C address call
    i2c_write_1port(0x00); //set cont-reg-address(0x00)
    i2c_write_1port(0b00001101); //ADCリセット解除,high gain(b3=1),manual mode(b2=1),01=2.8ms*X
    i2c_stop_1port(); //I2Cストップ

    for (i=0; i<integ_multi_val; i++) {delay(18);} //4色分の積分が終了するまで待つ。
                                                   //2.8ms*4=12ms+??=18ms 18ms*10(integ. multiple value)=180ms
    i2c_start_1port(); //I2Cスタート
    i2c_write_1port(0x54); //I2Cアドレスコールコマンド
    i2c_write_1port(0x03); //アクセスするアドレスをセット。データレジスタ(0x03)
    i2c_start_1port();     //リスタート
    i2c_write_1port(0x55); //セットしたアドレスからデータを読み込む。

    for(j=0; j<8; j++){
    //I2Cシーケンシャルリードで8バイト読み込み。7バイト目までは、ACK(0)で、最後のバイトは、NOACK(1)で。
    //i2c_read_1port();では、PortBから丸ごとバイト読みし、pin_dt[]に順に格納する。
      if(j==7){i2c_read_1port(1);}else{i2c_read_1port(0);}

    //pin_dt[]には、bit_Xに、ch_Xセンサーのデータが入っているので、これを抜き取って各chセンサーの
    //バイトデータを作成するため、d1=20、21、22、、、27と変えながら、pin_dt[]とANDをとる。
      d1=1;  // センサーch0=20 d1はセンサーchを示します。
             // センサーch1=21=2、センサーch2=22=4、センサーch3=23=8、、、。
      for(i=0; i<8; i++){ //i:センサー番号(0~7)
        wk1=0;
  //各センサーデータのビット0~7が、pin_dt[0]~[7]に格納されているので、
   //これを各センサーごとのデータとするため、d1=2nとANDをとって確認しwk1にデータを作り、
   //read_portBit[i][j](i=センサー番号、j=データ番号(0=Rh,,7=IRL))に格納する。
    if(pin_dt[0]&d1)wk1=wk1|1; //pin_dt[0]&d1は、d1が示すセンサーの 20ビット
        if(pin_dt[1]&d1)wk1=wk1|2; //pin_dt[1]&d1は、d1が示すセンサーの 21ビット
        if(pin_dt[2]&d1)wk1=wk1|4; //以下同様なので省略。
        if(pin_dt[3]&d1)wk1=wk1|8;
        if(pin_dt[4]&d1)wk1=wk1|0x10;
        if(pin_dt[5]&d1)wk1=wk1|0x20;
        if(pin_dt[6]&d1)wk1=wk1|0x40;
        if(pin_dt[7]&d1)wk1=wk1|0x80; //pin_dt[7]&d1は、d1が示すセンサーの 27ビット
        d1=d1<<1; //左シフトして、次のセンサーへ移動。
        read_portBit[i][j] = wk1; //i=センサー番号、j=データ番号(0=Rh,,,7=IRL)
      }
    }
    i2c_stop_1port();  //8ch分×8バイト=64バイト読み込み終了。

    for(i=0; i<8; i++){
    //16ビットデータへ変換して格納。i=センサーch(0~7)
      RGBI[i].R =read_portBit[i][0]*256+read_portBit[i][1];  //R
      RGBI[i].G =read_portBit[i][2]*256+read_portBit[i][3];  //G
      RGBI[i].B =read_portBit[i][4]*256+read_portBit[i][5];  //B
      RGBI[i].IR =read_portBit[i][6]*256+read_portBit[i][7]; //IR
    }
}


void loop() {
 unsigned char i;

  get_color();	//センサーからデータを読み込む。

// PORTB(Pin8~13)にセンサー(SDA)を接続。センサーch0~ch5
  for(i=0; i<6; i++){
    //シリアルモニターへデータを送信。
    Serial.print("ch"); Serial.print(i); Serial.print("=");
    if((ack_pb&(1<<i))==0){
     //アクノリッジ=0、センサーOK。データ送信。
      Serial.print(RGBI[i].R); Serial.print(","); Serial.print(RGBI[i].G); Serial.print(",");
      Serial.print(RGBI[i].B); Serial.print(","); Serial.print(RGBI[i].IR);
      Serial.println("");
    }else{
     //アクノリッジ=1、センサーNG又はセンサーなし。N/Aとして送信。
      Serial.print(" N/A ");
      Serial.println("");
    }
  }
  Serial.println("");
  delay(1000);
}
 以上がメインのプログラムの詳細となります。



 次は、I2C通信用のプログラムです。これは、I2Cスタート1ポート、I2Cライト1ポート(1バイト)、I2Cリード1ポート(シーケンシャルで8バイト読み)、I2Cストップ1ポートの4つから成ります。いずれも、I2Cの仕様に従ってビットポートに、1、0を立てて行くだけの、かなり冗長なものとなります。詳細は次のとおりです。

② i2c_wr_1port.iso


#define pb_out PORTB //ポートB出力レジスタ
#define pb_in PINB   //ポートB入力レジスタ
#define pb_dir DDRB  //ポートB方向レジスタ

#define pd_out PORTD //ポートD出力レジスタ
#define pd_dir DDRD  //ポートD方向レジスタ

#define pb_dat1  B00111111 //PORTB又はDDRBの、ビット0~ビット5をORをとって1にするための定数
#define pb_dat0  B11000000 //PORTB又はDDRBの、ビット0~ビット5をANDをとって0にするための定数

#define scl_1  B00000100  //PORTD又はDDRDの、ビット2をORをとって1にするための定数
#define scl_0  B11111011  //PORTD又はDDRDの、ビット2をANDをとって0にするための定数

extern unsigned char ack_pb;
extern unsigned char pin_dt[8];
extern unsigned char read_portBit[8][8];


void  i2c_start_1port(){

  pb_dir |= pb_dat1; //PB0~PB5を出力ポートに設定(SDA)
  pd_dir |= scl_1; // PD2を出力ポートに設定(SCL)
  //I2Cは、SCLが1の期間にはSDAを変更できない仕様なので、まずは、SCLを0にする。
  pd_out &= scl_0;  //SCL=0
  pd_out &= scl_0;  //SCL=0 (dummy)
  //SDA=1にする。
  pb_out |=pb_dat1;  //SDA=1
  delayMicroseconds(10);
  //SCL=1にする。
  pd_out |= scl_1;  //SCL=1
  delayMicroseconds(5);
  //SCL=1、SDA=1の状態から、SDAを0にするとI2Cスタートコンディションとなる。
  pb_out &= pb_dat0;  //SDA=0
  delayMicroseconds(5);
  pd_out &= scl_0;  //SCL=0、SDA=0としてスタート完了。
  delayMicroseconds(2);
}


void i2c_write_1port(unsigned char dat){
// ポートBに接続されている全てのI2Cデバイスに、同じ1バイトのデータ(dat)を書き込む。
// I2Cは上位ビットから送信するので、datのbit7から順に、1又は0を送る。
  pb_out &= pb_dat0; //SDAを0にする。(SCL=0)
  if ((dat & 0x80) == 0x80)pb_out |= pb_dat1; //datのbit7をチェック、1なら、SDA=1。
  pd_out |= scl_1; //SCLを1に。
  delayMicroseconds(5);	//クロック周波数を、約100kHzに。
  pd_out &= scl_0; //SCL=0

  pb_out &= pb_dat0; //SCL=0としたので、SDAの変更が可能。
  delayMicroseconds(5); //クロック周波数を、約100kHzに。
  if ((dat & 0x40) == 0x40)pb_out |= pb_dat1; //datのbit6をチェック、1なら、SDA=1。
  pd_out |= scl_1; //SCLを1に。
  delayMicroseconds(5);	//クロック周波数を、約100kHzに。
  pd_out &= scl_0;  // SCL=0

// 以降、同じなので説明を省略。
  pb_out &= pb_dat0;
  delayMicroseconds(5);
  if ((dat & 0x20) == 0x20)pb_out |= pb_dat1;
  pd_out |= scl_1;
  delayMicroseconds(5);
  pd_out &= scl_0;

  pb_out &= pb_dat0;
  delayMicroseconds(5);
  if ((dat & 0x10) == 0x10)pb_out |= pb_dat1;
  pd_out |= scl_1;
  delayMicroseconds(5);
  pd_out &= scl_0;

  pb_out &= pb_dat0;
  delayMicroseconds(5);
  if ((dat & 0x08) == 0x08)pb_out |= pb_dat1;
  pd_out |= scl_1;
  delayMicroseconds(5);
  pd_out &= scl_0;

  pb_out &= pb_dat0;
  delayMicroseconds(5);
  if ((dat & 0x04) == 0x04)pb_out |= pb_dat1;
  pd_out |= scl_1;
  delayMicroseconds(5);
  pd_out &= scl_0;

  pb_out &= pb_dat0;
  delayMicroseconds(5);
  if ((dat & 0x02) == 0x02)pb_out |= pb_dat1;
  pd_out |= scl_1;
  delayMicroseconds(5);
  pd_out &= scl_0;

  pb_out &= pb_dat0;
  delayMicroseconds(5);
  if ((dat & 0x01) == 0x01)pb_out |= pb_dat1;
  pd_out |= scl_1;
  delayMicroseconds(5);
  pd_out &= scl_0;
//以上で、8ビット長のデータ送出完了。

//次の第9クロック目で、デバイスからの返答を受信する。(アクノリッジビット)
//受信側がバイト受信に成功した場合は、受信側がSDA=0(ACK)とする。
//受信に失敗したとき、又は、デバイスが接続されていない場合は、SDA=1(NOACK)。
  pb_out &= pb_dat0;
  pb_dir &= pb_dat0; //ポートBを入力ポートに変更。
  pb_out |= pb_dat1; //CPU内臓のプルアップ抵抗(約20~50kΩ)を使用。(*適正値の約10倍ですが、実験ということで。)
  delayMicroseconds(5);
  pd_out |= scl_1; //クロック(SCL)を1に。
  ack_pb |= (pb_in & pb_dat1); //SCLが1の期間は、SDAは一定値を保つので、
                                 //ポートBの状態(ACK又はNOACK)を読み込み、以前のデータにORで加えていく。
  delayMicroseconds(5);
  pd_out &= scl_0; //第9クロック終了。SCL=0
  pb_dir |= pb_dat1; // アクノリッジビットが終了したので、ポートBを元の出力ポートに戻す。
  pb_out &= pb_dat0; //最後に、SDAを(確実に)0にして終了。
  delayMicroseconds(5);
}


void i2c_read_1port(unsigned char acknlg){ //acknlg=0=ACK. 1=NOACK.
//ポートBから、ポート丸ごと読みで、受信データ最上位のbit7から順にbit0までを読み込む。ポートB×8=8バイト
//通常は、1Pinから1ビットずつ8ビット(1バイト)の読み込み。
  pb_dir &= pb_dat0; //ポートBを入力ポートに設定。
  pb_out |= pb_dat1; //CPU内臓のプルアップ抵抗(約20~50kΩ)を使用。
  pd_out |= scl_1; //SCL=1
  delayMicroseconds(5);
  pin_dt[7] = pb_in; //SCLが1の期間に、ポートB(SDA)を読む。
                     //ここでは、受信データの最上位ビット(bit7)が並列に8センサー分読み込まれる。
  pd_out &= scl_0; //SCL=0
  delayMicroseconds(5);
  pd_out |= scl_1;  //SCL=1
  delayMicroseconds(5);
  pin_dt[6] = pb_in; //SCLが1の期間に、ポートB(SDA)を読む。
                     //ここでは、受信データのbit6が並列に8センサー分読み込まれる。
  pd_out &= scl_0; //SCL=0
  delayMicroseconds(5);
//以降、同じなので説明文省略。
  pd_out |= scl_1;
  delayMicroseconds(5);
  pin_dt[5] = pb_in;
  pd_out &= scl_0;
  delayMicroseconds(5);
  pd_out |= scl_1;
  delayMicroseconds(5);
  pin_dt[4] = pb_in;
  pd_out &= scl_0;
  delayMicroseconds(5);
  pd_out |= scl_1;
  delayMicroseconds(5);
  pin_dt[3] = pb_in;
  pd_out &= scl_0;
  delayMicroseconds(5);
  pd_out |= scl_1;
  delayMicroseconds(5);
  pin_dt[2] = pb_in;
  pd_out &= scl_0;
  delayMicroseconds(5);
  pd_out |= scl_1;
  delayMicroseconds(5);
  pin_dt[1] = pb_in;
  pd_out &= scl_0;
  delayMicroseconds(5);
  pd_out |= scl_1;
  delayMicroseconds(5);
  pin_dt[0] = pb_in;
  pd_out &= scl_0;
  delayMicroseconds(5);
// 以上で、8ビット長のデータ受信完了。

// 次の第9クロック目で、デバイスへACK(0)、又は、データ受信の最後ならば、NOACK(1)を、返信する。
  if(acknlg==0){ // ACK(0)の送信
    pb_dir |= pb_dat1; //ポートBを出力ポートに設定。
    pb_out &= pb_dat0; //SDAを0に。(ACK)
    delayMicroseconds(5);
    pd_out |= scl_1; //第9パルス送出 SCL=1
    delayMicroseconds(5);
    pd_out &= scl_0; //SCL=0
  }else{ //NOACK(1)の送出(受信データの最後を知らせる。)
    pb_dir |= pb_dat1; //ポートBを出力ポートに設定。
    pb_out |= pb_dat1; //SDAを1に。(NOACK)
    delayMicroseconds(5);
    pd_out |= scl_1; //第9パルス送出 SCL=1
    delayMicroseconds(5);
    pd_out &= scl_0; //SCL=0
    pb_out &= pb_dat0; //SDA=0にして終了。
  }
}

void  i2c_stop_1port(){ //ストップコンディション
  pd_out &= scl_0; // SCL=0
  pb_out &= pb_dat0; // SDA=0
  pb_out &= pb_dat0; // SDA=0(dummy)
  delayMicroseconds(5);
  pd_out |= scl_1;  //クロックを1にする。SCL=1
  pd_out |= scl_1;  //dummy delay
//SCL=1、SDA=0の状態から、SDAを1にするとI2Cストップコンディションとなる。
  pb_out |= pb_dat1; //SDA=1として完了。
  delayMicroseconds(5);
}
 プログラムの解説はここまでです。プログラムファイル(.iso)は、次のリンクからダウンロードできます。

D/L_files: Color_sens_6ch.zip



(3)6種類のLEDを使用して実験

 


 回路図に示したように、Pin8~13にカラーセンサーch0~ch5を繋ぎ、上の写真のようにそれぞれのセンサーにLED光を照射し、その時の測定値をシリアルモニターで確認したものが図1です。 LEDの色は、ch0から順に、赤、緑、青、赤外、黄、白です。データは、左から、R、G、B、IRの順です。それぞれの色とデータを確認してみると、

 
     R, G, B, IR
  ch0=13004,3500,2399,2184   LED:赤
  ch1=3548,4861,1622,1170   LED:緑
  ch2=5453,23685,44222,5307  LED:青
  ch3=5496,4871,4591,4380   LED:赤外 ←???
  ch4=6319,5197,1822,1385   LED:黄
  ch5=10906,19200,15160,3141  LED:白

 赤外LED以外は、まずまず想定内の結果となりました。しかし、赤外LEDの結果は信じられないほど意外で、何か間違っているのか、或いはセンサーの故障か、LEDがおかしいのか?とセンサーや赤外LEDを交換したりと思いつくことは試したのですが、結果に大きな変化はありませんでした。普通、赤外LEDを照射すれば、IRのデータが最も大きくなるのが当たり前だと思うのですが、何度やっても、4つの成分とも同じくらいの値となり、且つ、R>G>(≒)B>IRとなるのです。なぜこうなるのか?、確かなことは判らないのですが、カラーセンサーの分光特性(図2)からある程度推察できるかと思います。図2から判るように、赤外LED(OSI5FU5113A-40)のピーク波長:940nmでは、このカラーセンサーは殆ど感度を持っていません。従って、IRの値は小さくなってしまうということ、さらに、R,G,Bの感度は、R>G>Bとなっているため、赤外LEDから僅かながら出ているR、G、Bの成分を検出しているため、感度順に測定されるのではないか、と第一感で推察しました。しかしもしそうであれば、透明ではなく白っぽい光が見えてしまう筈ですが何も見えません。ということは、やはり赤外LEDからは、有色光は出ていないということになります。
 結論として(これも推察ですが)、図2のスペクトラムレスポンスのグラフからでは、読み取れないのですが、赤外LEDの発する940nm付近の感度がゼロではなく、R、G、B、IRとも同じくらいの感度があるということです。そのなかで、Rの感度がやや大きく、IRの感度はやや小さいということではないでしょうか。これなら、すべて説明ができます。しかししかし、最後に素朴な疑問が沸いてきます。じゃあ、IR成分を測定している意味はなんなの?と。何だか、ちょっと後味がすっきりしない実験でしたが、ローテク、ローコストではこれ以上のことは判らないということで今回の実験は終了とします。


図1 測定結果
 


図2 S11059-02のスペクトラムレスポンス
 



図3 夜間に実験
 



図4 夜間の結果
 






Copyright 2019 © LowTech.com

inserted by FC2 system