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 | 0 | シングルエンド | CH0-GND |
1 | 0 | 0 | 1 | シングルエンド | 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);
}