Edited at

ArduinoでA/Dコンバータ(MCP3208)を使ってみた。


はじめに


なぜA/Dコンバータを使うのか

Arduino UNOには10bit(0~1023)で5Vまで計測できる6chのA/Dコンバータ(以下ADCと略記)が内蔵されています。そのため一般的にはこれを利用し、analogRead()するだけでアナログ電圧を取得できます。しかし、1024段階でしか測れないこと・筆者がしばしばマイコンのADCを壊してしまうことなどから外付けの12bit(0~4095)、8chのADCを使うことにしました。


A/Dコンバータとは?

名前の通り、アナログをデジタルに変換するパーツで、アナログ電圧を2進数のデジタルに変換してくれます。デジタル値は主にSPIなどで読み取ることが出来ます。

性能には、何本同時に電圧が測れるか(ch)、変換する際の最小幅(量子化ビット数)、実際の電圧との誤差等があります。

今回利用するMCP3208は8ch、12bitです。誤差はデータシートを見てください。


SPIとは?

主要なADCが利用している通信規格の一つで、Serial Peripheral Interfaceの略称です。データのやりとりには4本のケーブルが必要となっています。

ArduinoにはSPI通信用のライブラリがあるため非常に簡単に実装できます。


使ってみる


回路

だいぶ簡略化するとこんな感じでArduinoはPCのUSBから、デジタル系統はArduinoから、アナログ系統はAC電源にUSB繋げたものから給電しました。また、SPIに使うピンは変更できないためこのままにしてください。

効果あるか分からないですが無いよりはましだろうと適当なセラミックコンデンサを置いてます。

image.png


SPIの規格

SPIはマスターとスレーブに分かれて通信を行います。

以下の2枚の画像に利用させて頂いたMCP3204/3208の日本語データシートはここにあります

image.png

こんな感じで通信すればいいそうです。

ArduinoのSPIライブラリはCS(チップセレクト)には何もしないため、これは自分でdigitalWriteする必要があります。逆に、CSだけは好きなピンに配置できます。たくさんのSPI機器を繋げた時に通信対象を指定するためにこのようになっています。

マスター側は、アドレスをD0-D2において指定した後、B0~B11までの12bitを受け取る必要があります。受け取る際、マスター側はダミーデータをスレーブに対して送信する必要があります。8bitを1単位として送信するため、送信するのは後ろから数えB0-B7まで、B8-D1まで(tsample、null含む)、D2- の3バイトとなります。

また、B0-11以外で受け取ったデータについては一切役に立ちません。

次に、それぞれのビットについて見てみます。

image.png

これは読み取るチャンネルを指定するビットの構成です。これを元にシングルエンドで指定されたビットの値を読み取る関数を作成します。

D2,D1,D0が2進法でチャンネル番号を示しているので計算が楽ですね。


プログラミング

3バイト送信しますが、一番目の1バイト中余ったビットは後ろを合わせるため前にダミービットを並べます。二番目の1バイトはD1,D0にデータを入れ他はダミービットを並べます。三番目の1バイトは全てダミービットで構いません。

そのため、CH0を参照する場合の各bitの構成は以下の通りです。

00000(ダミー)1(開始)1(SGL/DIFF)0(D2)

0(D1)0(D0)000000(ダミー)

00000000(ダミー)


#define cs 10 //csのピン番号
#define vref 5 //vrefに入っている電圧
SPISettings spisettings(1000000, MSBFIRST, SPI_MODE0);

void setup() {
pinMode(cs, OUTPUT);

Serial.begin(115200);
SPI.begin();

digitalWrite(cs,HIGH);
}

void loop(){
//作った関数を任意の場所で呼ぶ。 例えば、
//Serial.println(ADanalogRead(0));
}

double ADanalogRead(char ch) {
double ret = -1;
unsigned char data1, data2;

if (0b111 < ch) {
Serial.println("error");
return (ret);
}

//通信開始
SPI.beginTransaction(spisettings);
digitalWrite(cs, LOW);
//データ送信
SPI.transfer(0b00000110 | (ch >> 2));
data1 = SPI.transfer(ch << 6);
data2 = SPI.transfer(0b00000000);

digitalWrite(cs, HIGH);
SPI.endTransaction();
//通信終了

ret = (((data1&0x0f) << 8) + data2) * vref / 4096.0; //単位の変換、ボルトに直す
return (ret);
}


実装例


MQ-135をCH0に搭載した

ここからほぼコピペし、購入したボードには1kΩのチップ抵抗が載っていたので分圧の逆算に代入しました。

また、なぜか濃度がありえない値になったためキャリブレーションを掛けました。


#include <SPI.h>
#define MQ135_SCALINGFACTOR 116.6020682 //CO2 gas value
#define MQ135_EXPONENT -2.769034857 //CO2 gas value
#define MQ135_MAXRSRO 2.428 //for CO2
#define MQ135_MINRSRO 0.358 //for CO2
#define cs 10

SPISettings spisettings(1000000, MSBFIRST, SPI_MODE0);

void setup()
{
pinMode(cs, OUTPUT);

Serial.begin(115200);
SPI.begin();

digitalWrite(cs, HIGH); //highにしておき、通信時にlowにする。
}

void loop()
{
double mq135_valr = ADanalogRead(0);//電圧
double mq135_val = 1000.0 * (5.0 - mq135_valr) / mq135_valr;//抵抗
double mq135_valAIQ = mq135_getppm(mq135_val, 52000);
Serial.println(mq135_valAIQ);

delay(10000);
}

double mq135_getppm(long resvalue, long ro) {
double ret = 0;
double validinterval = 0;
validinterval = resvalue / (double)ro;
if (validinterval < MQ135_MAXRSRO && validinterval > MQ135_MINRSRO) {
ret = (double)((double)MQ135_SCALINGFACTOR * pow( ((double)resvalue / ro), MQ135_EXPONENT));
}
return ret;
}

double ADanalogRead(char ch) {
double ret = -1;
unsigned char data1, data2;

if (0b111 < ch) {
Serial.println("error");
return (ret);
}

//通信開始
SPI.beginTransaction(spisettings);
digitalWrite(cs, LOW);
//データ送信
SPI.transfer(0b00000110 | ch >> 2);
data1 = SPI.transfer(ch << 6);
data2 = SPI.transfer(0b00000000);

digitalWrite(cs, HIGH);
SPI.endTransaction();
//通信終了

ret = (((data1&0x0f) << 8) + data2) * 5.0 / 4096.0; //単位の変換、ボルトに直す
return (ret);
}