A-Dコンバータ その2 10ビットSPI MCP3008 -(1)

 DIPパッケージのICは電子工作で扱いやすいです。スマホのように、高密度で部品を実装する時代になって、3mm角のような小さいパッケージが主流になってきましたが、顕微鏡がないと人の手では実装もできないです。低倍率の実体顕微鏡は、倒立した像にはならないので、はんだ付けに利用しやすいです。

 MCP3008は8チャネルのA-Dコンバータで、2個使えば16チャネルの入力を扱えます。SPIバスでは、I2Cバスのようにアドレスがデバイス固有ではないため、SS信号をデバイスごとに用意します。

MCP3008のおもなスペック

  • 分解能 10ビット
  • 入力数 シングルエンド;8チャネル、疑似差動;4チャネル
  • 動作電圧 2.7~5.5V
  • サンプリング速度 200ksps
  • クロック 最大3.6MHz
  • 変換方式 逐次変換SAR(Successive Approximation Register)
  • 動作時の電流 500uA(5V時)
  • 動作温度範囲 -40~85℃
  • インターフェース SPI
  • パッケージ DIP、SOIC、TSSOP

 基準電圧Vref入力、アナログ・グラウンドAGNDとディジタル・グラウンドDGNDが独立した端子で用意されています。

MCP3008のデータ出力のタイミング

 次の図は、MSBからデータをマスタへ送るときのフォーマットです。マスタであるArduinoからスレーブのMCP3008のDinへ、Startビット=1、SGL/DIFFでシングル入力か疑似差動入力かを選択し、D2、D1、D0でチャネルを選択するコマンドが送られてきます。8ビット単位になるように先頭に0を追加します。

 マスタからのD0が届いたのちA-D変換を開始し、MCP3008はデータをDoutから送り出します。スレーブがデータを送っている最中も、マスタはクロックを出し続けなければなりません。スレーブ側は、マスタが送ってくるD0以降のデータはDon't Care=無視するよと言っているので、0でもよいし1でもかまいません。

 入力の選択ビットです。

SGL/
DIFF
D2 D1 D0  入力方法  チャネル
0 0 0 0 疑似差動入力 CH0→IN+
CH1→IN-
0 0 0 1 疑似差動入力 CH0→ IN-
CH1→IN+
0 0 1 0 疑似差動入力 CH2→IN+
CH3→IN-
0 0 1 1 疑似差動入力 CH2→ IN-
CH3→IN+
0 1 0 0 疑似差動入力 CH4→IN+
CH5→IN-
0 1 0 1 疑似差動入力 CH4→ IN-
CH5→IN+
0 1 1 0 疑似差動入力 CH6→IN+
CH7→IN-
0 1 1 1 疑似差動入力 CH6→ IN-
CH7→IN+
1 0 0 シングルエンド CH0-GND
1 0 シングルエンド CH1-GND
1 0 1 0 シングルエンド CH2-GND
1 0 1 1 シングルエンド CH3-GND
1 1 0 0 シングルエンド CH4-GND
1 1 0 1 シングルエンド CH5-GND
1 1 1 0 シングルエンド CH6-GND
1 1 1 1 シングルエンド CH7-GND

接続

 MCP3008を1個接続します。入力CH0~CH1に対応するグラウンドはAGNDです。AGNDはDGNDと接続して使うので、電気的にはCH0~CH1に対応するグラウンドはAGNDとDGNDの両方です。回路図上ではAGNDとDGNDは同じ電位です。しかし、配線材には抵抗とインピーダンス成分があるので、接続点が離れていると、数uから数十uVの電位差が生じます。A-D変換は各チャネル入力端子とAGND間の電圧をディジタル値に変換します。

 とはいえ、10ビットA-Dコンバータの量子化誤差は大きいので、グラウンド・ノイズ、電位差などの影響はほとんど影響がないと思われます。だからこそ10ビットが電子工作では使われます。

 12ビット以上では、不適切なグラウンド処理を行うとデータに影響を与えると思われます。

スケッチ

 前回のMCP3002を修正します。

 SPIライブラリを使います。チップ・セレクト信号SSは任意のポートに割り付けられます。10番にしました。11番はMOSI、12番はMISO、13番はSCKです。GNDと5Vの電源も配線します。

 SPIのデータ転送速度は1MHz=1000000に、MSBから送ってくるようにMSBFIRST、データ・モードはMCP3008のデータシートから、正論理のクロック立ち上がりなのでSPI_MODE0を指定します。

 データのフォーマットです。

  • ダミー7ビットのあと、Startビット=1を送るので、0x00000001
  • シングルエンド、チャネル0の設定は1000です。残りの4ビットはダミーなので0000にし、0b10000000
  • 最後の1バイトはダミーなので、0x00
#include <SPI.h>
#define SS 10
float Vref = 5.0 ;

SPISettings settings(1000000,MSBFIRST,SPI_MODE0);
 
void setup() {
    pinMode(SS, OUTPUT);
    Serial.begin(9600);
    SPI.begin();
}
  
void loop(){
    SPI.beginTransaction(settings);
        digitalWrite(SS, LOW);
            SPI.transfer(0b00000001); // Start bit 1
            byte highByte = SPI.transfer(0b10000000); // CH0 singleEnd
            byte lowByte = SPI.transfer(0x00); // dummy
        digitalWrite(SS, HIGH);
    SPI.endTransaction();
    unsigned int dataCh0 = ((highByte & 0x03) << 8) + lowByte;
    float volts = dataCh0*Vref /1024;
    Serial.println("highByte= " + String(highByte,DEC) + "lowByte= " + String(lowByte,DEC) );
    Serial.println("CH0 " + String(volts,3) + "V");
}

 このときのシリアル出力です。


 クロックとMISO(Dout)です。入力CH0は5Vの電源につないでいます。

 次に、チャネル0とチャネル1を続けて読むようにスケッチを修正しました。

#include <SPI.h>
#define SS 10
float Vref = 5.0 ;

SPISettings settings( 1000000 , MSBFIRST , SPI_MODE0 );

void setup() {
pinMode(SS, OUTPUT);
Serial.begin(9600);
SPI.begin();
}

void loop(){
SPI.beginTransaction(settings);
digitalWrite(SS, LOW);
SPI.transfer(0b00000001); // Start bit 1
byte highByte0 = SPI.transfer(0b10000000); // CH0 singleEnd
byte lowByte0 = SPI.transfer(0x00); // dummy
SPI.transfer(0b00000001); // Start bit 1
byte highByte1 = SPI.transfer(0b10010000); // CH1 singleEnd
byte lowByte1 = SPI.transfer(0x00); // dummy
digitalWrite(SS, HIGH);
SPI.endTransaction();
unsigned int dataCh0 = ((highByte0 & 0x03) << 8) + lowByte0;
unsigned int dataCh1 = ((highByte1 & 0x03) << 8) + lowByte1;
float volts0 = dataCh0*Vref /1024;
float volts1 = dataCh1*Vref /1024;
Serial.println("CH0 highByte= " + String(highByte0,DEC) + " lowByte= " + String(lowByte0,DEC) );
Serial.println("CH1 highByte= " + String(highByte1,DEC) + " lowByte= " + String(lowByte1,DEC) );
Serial.println("CH0 " + String(volts0,3) + "V");
Serial.println("CH1 " + String(volts1,3) + "V");
}

 シリアル出力です。CH0は電源に、CH1は開放状態です。

 波形です。

 CH0を読み出した後、DoutはHIGH状態のままです。CH1の入力電圧を変化させても、読み取ったデータが対応していません。

 これでよいのかわからないので、CH0を読んだ後いったんSS端子をHIGH-LOWとし、CH1を読みました。

#include <SPI.h>
#define SS 10
float Vref = 5.0 ;

SPISettings settings( 1000000 , MSBFIRST , SPI_MODE0 );

void setup() {
pinMode(SS, OUTPUT);
Serial.begin(9600);
SPI.begin();
}

void loop(){
SPI.beginTransaction(settings);
digitalWrite(SS, LOW);
SPI.transfer(0b00000001); // Start bit 1
byte highByte0 = SPI.transfer(0b10000000); // CH0 singleEnd
byte lowByte0 = SPI.transfer(0x00); // dummy
digitalWrite(SS, HIGH);
digitalWrite(SS, LOW);
SPI.transfer(0b00000001); // Start bit 1
byte highByte1 = SPI.transfer(0b10010000); // CH1 singleEnd
byte lowByte1 = SPI.transfer(0x00); // dummy
digitalWrite(SS, HIGH);
SPI.endTransaction();
unsigned int dataCh0 = ((highByte0 & 0x03) << 8) + lowByte0;
unsigned int dataCh1 = ((highByte1 & 0x03) << 8) + lowByte1;
float volts0 = dataCh0*Vref /1024;
float volts1 = dataCh1*Vref /1024;
Serial.println("CH0 highByte= " + String(highByte0,DEC) + " lowByte= " + String(lowByte0,DEC) );
Serial.println("CH1 highByte= " + String(highByte1,DEC) + " lowByte= " + String(lowByte1,DEC) );
Serial.println("CH0 " + String(volts0,3) + "V");
Serial.println("CH1 " + String(volts1,3) + "V");
}

 シリアル出力です。CH0、CH1の両方とも電源につないでいます。

 波形です。

 NullBitが0になっています。データシートにも0レベルだと書かれています。すべての信号を見ます。

 チャネルを読むごとに、SS端子をHIGH-LOWをするのが正しい読み出し方法のようです。

関数にする

 0~7のchannel番号を入れてadcRead(ch)を呼ぶと、変換データが戻されます。

#include <SPI.h>
#define SS 10

float Vref = 5.0 ;SPISettings settings( 1000000 , MSBFIRST , SPI_MODE0 );

void setup() {
pinMode(SS, OUTPUT);
Serial.begin(9600);
SPI.begin();
}

int adcRead(byte ch) { // 0 .. 7
byte channelData = (ch+8 ) <<4;
// Serial.println(String(channelData,BIN));
SPI.beginTransaction(settings);
digitalWrite(SS, LOW);
SPI.transfer(0b00000001); // Start bit 1
byte highByte = SPI.transfer(channelData); // singleEnd
byte lowByte = SPI.transfer(0x00); // dummy
digitalWrite(SS, HIGH);
SPI.endTransaction();
return ((highByte & 0x03) << 8) + lowByte ;
}
void loop(){
byte channel = 2 ;
unsigned int dataCh = adcRead(channel) ;
float volts = dataCh*Vref /1024 ;
Serial.println("CH" + String(channel) +": " + String(volts,3) + "V") ;
delay(1000);
}

前へ

A-Dコンバータ その1 10ビットSPI MCP3002

次へ

A-Dコンバータ その3 10ビットSPI MCP3008 -(2)