電圧を読むADC(Analog-to-Digital Converter)の次は、電圧を出すDAC(Digital-to-Analog Converter)についてです。フィールドではデータを読むのが専らの目的なので、制御されたアナログ電圧をロガーから(印加ではなく)信号として出力する機会は少ないですが、例えば土質実験室では、電空変換器(EP)への電圧信号を使って空気圧を制御することもあると思います。また、小細工っぽいですが、アナログ電圧しか読めないデータロガーがあり、これに対してデジタルセンサーがある場合、Arduinoでデジタル信号を読み込み、これをアナログ信号に変換して出力し、既存のデータロガーで読む、といったこともできます。電子工作でDACを使う場合、通常はスピーカーにつないで音声・音楽の再生などに使うのだと思います。

 

 アナログ出力と対比されるものとして、デジタルピンを使ったパルス幅変調(PWM)というものがあります。Arduinoのデジタルピン(「A」がついていない、数字だけ示されたピン)からは電圧を出力できますが、システム電圧(UNOの場合5V)か0Vかしか出力できません。前者をHIGH、後者をLOWといいます。PWMとは、デジタル出力のON/OFFの時間を非常に細かく繰り返すことで見かけの電圧を調整するもので、Arduinoなどが搭載するマイコンが得意とするところですが、これは、たとえばLEDを弱く光らせるといった効果はありますが、真のアナログ電圧制御ではありません。ここまでの経験上、PWMをフィールド計測で使ったことはないので、当面このブログでは扱いません。

 

 Arduino UNOをはじめ、多くのベーシックなマイコンにはDAC端子はありません。ArduinoシリーズでいえばDueについているのみです。そこで、例によって外付けでDACモジュールを使用します。ここではMCP4725というチップを用いたブレークアウトモジュールを使用します。AmazonでMCP4725を検索すれば、同じ製品が様々なブランド(?)名でヒットします。

 

 

データシートはこちら:

https://cdn-shop.adafruit.com/datasheets/mcp4725.pdf

これはI2C通信を用いた12bitのDACです。出力する電圧範囲ですが、これはこのチップに入力する電圧で決まり、仕様書によれば、最大で6.5Vとのことです。Arduinoから電圧をとるとすれば、3.3Vあるいは5.0Vを入力し、これを12bit(4096段階)に分解して出力することになります。

 

 ここでは、ちょっとバカバカしいですが、MCP4725から指定した電圧を出力して、ADS1115で読んでみることにします。下の図のようにつないでください。 第8回で、差動入力でADS1115を使う作品にMCP4725を加えたものです。

 

 

 以下がスケッチです。MCP4725用のライブラリをダウンロードして、第7回にある要領で開発環境に加えてください。

https://github.com/adafruit/Adafruit_MCP4725

第7回の差動入力のときのスケッチに加筆しています(赤字部分)。MCP4725のI2Cアドレス設定には、いくつかケースがあります。データシートを読むと、クライアントからの仕様に従ってアドレスのオプションを変えて出荷しているようです。たとえば、Adafruit社のブレークアウトモジュールでは、MCP4725のA0ピン(Arduino上のA0ではなく)に何もつながないとアドレスは0x62、GNDにつなぐと0x63になるそうです。秋月電気で売っているものは、A0ピンをGNDにつなぐと0x60、Vddにつなぐと0x61(どこにもつながないと不定となる)となるとあります。管理人がAmazonで買ったものはSparkfun社のものに準じているようで、A0をどこにもつながないとアドレスが0x60になるようです。スケッチはこれを反映しています。

 

#include <SPI.h>
#include <SD.h>
#include <DS3232RTC.h>    //http://github.com/JChristensen/DS3232RTC
#include <Time.h>
#include <TimeLib.h>
#include <Wire.h>
#include <Adafruit_ADS1015.h>    //https://github.com/adafruit/Adafruit_ADS1X15
#include <Adafruit_MCP4725.h>    //https://github.com/adafruit/Adafruit_MCP4725

const int chipSelect = 10; // Arduino UNOでは10、Arduino MEGAでは53

#define AVERAGENUM 20 // 取得データの平均化に用いるサンプル数

//#define CFACTOR 0.18750 // GAIN_TWOTHIRDS: +/-6.144V range: Calibration factor (mV/bit) for ADS1115 
#define CFACTOR 0.12500 // GAIN_ONE: +/-4.096V range: Calibration factor (mV/bit) for ADS1115
//#define CFACTOR 0.06250 // GAIN_TWO: +/-2.048V range: Calibration factor (mV/bit) for ADS1115
//#define CFACTOR 0.03125 // GAIN_FOUR: +/-1.024V range: Calibration factor (mV/bit) for ADS1115
//#define CFACTOR 0.01563 // GAIN_EIGHT: +/-/0.512V range: Calibration factor (mV/bit) for ADS1115
//#define CFACTOR 0.00781 // GAIN_SIXTEEN: +/-0.256V range: Calibration factor (mV/bit) for ADS1115

Adafruit_ADS1115 ads; // ADS1115のインスタンスを作成
Adafruit_MCP4725 dac; // MCP4725のインスタンスを作成

void setup(void)
{
    /* ----- Setting up serial communication with PC ------ */
    /* ここでUSBを介してPCとシリアル通信を始める。9600はシリアル通信のボーレート */
    Serial.begin(9600);
    while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
    //何らかの問題があってシリアルポートに接続できないときは、このループにトラップされる    
    }

    /* ----- Initialisation of SD card ------ */
    Serial.print("Initializing SD card...");
     //see if the card is present and can be initialized:
    if (!SD.begin(chipSelect)) {
       Serial.println("Card failed, or not present");
       // don't do anything more:
      return;
    }
    Serial.println("card initialized.");

    /* ----- Setting time based on RTC ----- */
    setSyncProvider(RTC.get);   // ここでRTCの時刻をもとにArduinoの時刻を合わせる
    if(timeStatus() != timeSet) 
        Serial.println("Unable to sync with the RTC");
    else
        Serial.println("RTC has set the system time");
    delay(1000);

    /* ----- Setting of ADS1115 AD converter ----- */
    ads.setGain(GAIN_ONE);
    ads.begin();
    
    /* ----- Setting of ADS1115 AD converter ----- */
    dac.begin(0x60);
}

void loop(void)
{
    /* DACによるアナログ電圧出力 */
    dac.setVoltage(1200, false);
    delay(100);
      
    /* データの読み取り */
    int cnt1=0;
    float read_from_ads;
    float averaged_input;
      
    averaged_input=0;
    for(cnt1=0;cnt1<AVERAGENUM;cnt1++) //AVERAGENUMの数だけ繰り返す
    {
      read_from_ads = float(ads.readADC_Differential_0_1()); //チャネル0から単動(Single End)入力
      averaged_input=averaged_input+read_from_ads/AVERAGENUM; //読んだ値を足していく:AVERAGENUMで割ることで平均化している
    }
    averaged_input=averaged_input*CFACTOR; //校正係数を掛けて電圧とする

    /* PCのシリアルモニタに表示 */
    Serial.print(year());Serial.print("/");Serial.print(month());Serial.print("/");Serial.print(day());Serial.print(" ");
    Serial.print(hour());Serial.print(":");Serial.print(minute());Serial.print(":");Serial.print(second());Serial.println("");
    Serial.print(averaged_input);
    Serial.println(" mV"); //" mV"と表示、printではなくprintlnとすることで改行する

    /* SDカードに書き込み */
    File dataFile = SD.open("datalog.csv", FILE_WRITE);
    if (dataFile)
    {
      dataFile.print(year());dataFile.print("/");dataFile.print(month());dataFile.print("/");dataFile.print(day());dataFile.print(" ");
      dataFile.print(hour());dataFile.print(":");dataFile.print(minute());dataFile.print(":");dataFile.print(second());dataFile.print("  ");
      dataFile.print(averaged_input);
      dataFile.println(" mV"); //" mV"と表示、printではなくprintlnとすることで改行する
    }    
    dataFile.close();
    
    delay(2000); //2000ミリ秒=2秒の停止
}

 

以下がシリアルモニタ―に現れる結果です。

 

 

ここで、出力した電圧について説明します。ここでDAC(MCP4725)はVddに5.0V入力しているのでsetVoltage(1200, false)とすると、5000mV×1200/4096=1465mVが出力値になります。前回(第8回)に説明した通り、ADCで読んだ値(1476mV)には多少のオフセットのズレがありますが、だいたい意図した通りに出力されているのがわかります。