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