ここまで、Arduino上にあるアナログピンを用いた電圧計測(A0~A5まで最大6チャネル)、ADS1115を用いた電圧計測(単動で最大4チャネル×4個接続可)、MCP4725を用いた電圧出力(1チャネルのみ)をマスターしました。このチャネルの扱いを劇的に柔軟化するのがマルチプレクサー(multiplexer)モジュール、通称”mux”です。ここでは、CD74HC4067という非常に安価で、非常に役に立つチップを使ったブレークアウトモジュールを使います。これ一つで、出力も入力も16チャネル化できます。

 

 

 原理は簡単です。Arduinoのデジタルピン(A0、A1といったように「A」がついているのがアナログピンで、それ以外の数字がついているのがデジタルピン:電圧が高い(HIGH)・低い(LOW)のどちらかのみを出力あるいは入力できるピンのことです)を4つ使って、それぞれHIGH(電圧を出す)・LOW(GNDレベル、つまり0Vに落とす)を組み合わせれば、CD74HC4067のS0~S3ピンに2^4=16通りの信号を送れます。これによって、C0~C15の端子のうちどれかとSIG端子がつながります。これを使えば、例えば16個のセンサーの出力線をC0~C15につないでおき、Arduinoから順次信号を送れば、順次、接続センサーを切り替えてSIG端子から値を読むことができます。このSIG端子を、例えばADS1115に接続すればいいのです。また、このマルチプレクサーは双方向なので、もう1つ使って、センサーへの印加も順次切り替えることができます。マルチプレクサーとADS1115の複数チャネルを組み合わせたり、マルチプレクサーとマルチプレクサーを組み合わせていけば、100チャネル超えも可能です。

 

 ここでは、5V印加で、出力レンジが0-5Vのセンサーが16個(ch0~15)あるとし、0.2秒ごとにch0から順に印加してその出力を読んでいく例を考えます。センサーは、種類にもよりますが、通常は10mA以下の電流で稼働するので、ここではArduinoの5Vピンから印加します。図のように接続します。なお、センサーは印加・GND・出力の3線式とし、すべてのセンサーのGND線はArduinoと共通のGND(図の黒ワイヤー)につながっているものとします。

 

 

 

 ここで、第8回目の差動入力用スケッチを元にした、以下のものを使って下さい。例によって、変更した部分を赤で示しています。

 

#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

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のインスタンスを作成

const int s0 = 3; // マルチプレクサーを制御するためにC0と接続するピンは3
const int s1 = 4; // マルチプレクサーを制御するためにC1と接続するピンは4
const int s2 = 5; // マルチプレクサーを制御するためにC2と接続するピンは5
const int s3 = 6; // マルチプレクサーを制御するためにC3と接続するピンは6

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();

    /* ----- Preparation of multiplexer signal ----- */
    pinMode(s0, OUTPUT);
    pinMode(s1, OUTPUT);
    pinMode(s2, OUTPUT);
    pinMode(s3, OUTPUT);
}

void loop(void)
{
    /* データの読み取り */
    int cnt1=0;
    int cnt2=0;
    float read_from_ads;
    float averaged_input[16];

    for(cnt2=0;cnt2<16;cnt2++)
    {
      connectMux(cnt2); //ここでチャネルcnt2(=0~15)に接続
      delay(500);
      
      averaged_input[cnt2]=0;
      for(cnt1=0;cnt1<AVERAGENUM;cnt1++) //AVERAGENUMの数だけ繰り返す
      {
        read_from_ads = float(ads.readADC_Differential_0_1()); //チャネル0から差動(Differential)入力
        averaged_input[cnt2]=averaged_input[cnt2]+read_from_ads/AVERAGENUM; //読んだ値を足していく:AVERAGENUMで割ることで平均化している
      }
      averaged_input[cnt2]=averaged_input[cnt2]*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("");
    for(cnt2=0;cnt2<16;cnt2++)
    {    
      Serial.print(averaged_input[cnt2]);
      Serial.print(" mV, ");
    }
    Serial.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("  ");
      for(cnt2=0;cnt2<16;cnt2++)
      {    
        dataFile.print(averaged_input[cnt2]);
        dataFile.print(" mV, ");
      }
      dataFile.println(); //改行
    }    
    dataFile.close();
    
    delay(2000); //2000ミリ秒=2秒の停止
}

/* Function to connect to a particular channel by multiplexer (mux) */
int connectMux(int channel)
{
  int controlPin[] = {s0, s1, s2, s3};

  int muxChannel[16][4]={
    {0,0,0,0}, //channel 0
    {1,0,0,0}, //channel 1
    {0,1,0,0}, //channel 2
    {1,1,0,0}, //channel 3
    {0,0,1,0}, //channel 4
    {1,0,1,0}, //channel 5
    {0,1,1,0}, //channel 6
    {1,1,1,0}, //channel 7
    {0,0,0,1}, //channel 8
    {1,0,0,1}, //channel 9
    {0,1,0,1}, //channel 10
    {1,1,0,1}, //channel 11
    {0,0,1,1}, //channel 12
    {1,0,1,1}, //channel 13
    {0,1,1,1}, //channel 14
    {1,1,1,1}  //channel 15
  };

  //loop through the 4 sig
  for(int i = 0; i < 4; i ++){
    digitalWrite(controlPin[i], muxChannel[channel][i]);
  }
}

 

スケッチの見通しを良くするため、setup()とloop()以外に、はじめて自身で定義する関数(connectMux(int channel))を使います(昔どこかから拾ってきたコードですが、どこからか忘れてしまいました、すみません)。C言語を使ったことのある人なら説明不要と思いますが、最後の部分のint connectMux(int channel) { }で関数と呼ぶ一連の手続きを定義しています。C言語と異なり、Arduino言語では関数を宣言する必要はないようです。カッコ()の中のint channelというのは、整数(integer)を引数として渡すと、それをこの関数内でchannelという変数として使うという意味です。このconnectMux関数は、たとえばconnectMux(3)とすれば、ピンs0、s1、s2、s3に1、1、0、0(つまりHIGH、HIGH、LOW、LOW)が送られ、sigピンとc3ピンがつながるようになります。loop()関数内のconnectMux(cnt2);という文で、cnt2を0から15までforループで増やすことで、順次sigピンとc0~c15ピンをつなげています。以下に留意してください。

 

・接続があまり瞬間的すぎるとセンサーが安定しない可能性があるので、delay(500);で各チャネル500msecずつ待っています。ほとんどのセンサーで、100msecくらいまで落としても問題ないと思います。

・デジタルピンは、出力に使うのか入力に使うのか指定する必要があります。setup()関数の中でpinModeという標準関数(自身で定義する必要がない関数)を使って定義しています。

・読んだ電圧値を格納するaveraged_inputは、16チャネル分の値を格納するためにaveraged_input[16]という配列として定義しました。

 

 後の回で説明したいと思いますが、地盤工学でいえば、たとえばアナログ式土壌水分計(最近ブランドが変わりましたが、昔でいうDecagonのEC5など)やアナログ式圧力センサーなどを16個接続したいという時に便利です。しかも、順次印加するので、消費電流も限定的であり、大容量バッテリーや太いケーブルが不要です。