ここまでプロトタイプ(試作品)製作のためにブレッドボードを使ってきましたが、これはそのままサイトに持っていくには向いていません。ちょっと触っただけでジャンパーワイヤ―は外れてしまいますし、何より各種部品の固定ができません(現在の作品では、各モジュールが浮いた状態になっていると思います)。モジュールの多くには基板の隅に小さな穴が開いているので、これをつかって木ねじで木版に固定するということもできますが、最終的にはやはりすべてはんだ付けをすることが望ましいです。このときの基板となるのが、ユニバーサル基板というものです。

 

アメブロの仕様が変わり、Amazonのページを埋め込めなくなったので、直リンクを貼ります。

あるいは「ユニバーサル基板」で検索。

https://www.amazon.co.jp/Quimat-ユニバーサル基板-PCB回路基板-実験プレート-はんだ付け/dp/B07TTMGK9K/ref=sr_1_1_sspa?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&keywords=%E3%83%A6%E3%83%8B%E3%83%90%E3%83%BC%E3%82%B5%E3%83%AB%E5%9F%BA%E6%9D%BF&qid=1585813954&sr=8-1-spons&psc=1&spLa=ZW5jcnlwdGVkUXVhbGlmaWVyPUExU1lXOE5LNDFSWUlUJmVuY3J5cHRlZElkPUEwNDI1NDQyMjBUVVNJR0c3WE5KWSZlbmNyeXB0ZWRBZElkPUFUVzBMSENLMVVEOVAmd2lkZ2V0TmFtZT1zcF9hdGYmYWN0aW9uPWNsaWNrUmVkaXJlY3QmZG9Ob3RMb2dDbGljaz10cnVl

 

6cm×8cmくらいのものが一番よく使います(5cm×7cmのほうがたくさん売っていますが、この1cmの違いで、モジュールが乗り切らない、ということが多いです)。

 

はんだ付けのやり方まではこのブログで説明はしません(うちの学生が、何もつけずに電線同士を熱してくっつけようとしていたことがありますが・・・それははんだ付けじゃなくて溶接)。基板にモジュールをはんだ付けし、裏で端子同士を電線で(やはりはんだで)つなぐというのが最も直接的で、かつ確実で外れづらいです。こんな感じです(図(a))。

 

 

ただし、このやり方だと、一つのモジュールが故障した際に交換が大変です。多数の端子ではんだ付けされたモジュールを外すには、基本的にペンチやニッパーでもぎとるしかありません。この問題を避けるためには、メスのヘッダー(ソケット)を使って、モジュールがちょうどはまるようなソケットをつくります。メスのヘッダーはこちら。

 

https://www.amazon.co.jp/HiLetgo-単一の行のストレート-ヘッダーストリップ-ピッチ2-54mm-10個セット/dp/B01LWN9UU1/ref=sr_1_5?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&keywords=%E3%83%A1%E3%82%B9+%E3%83%98%E3%83%83%E3%83%80+%E3%82%BD%E3%82%B1%E3%83%83%E3%83%88&qid=1585814088&sr=8-5

 

図(b)のようにすると、例えば個々のモジュールが故障しても、適宜交換することができます。メスのヘッダーを裏で配線することもできますし、あるいは隣にオスのピンヘッダ―を並べ、裏でそれらを接続し、オス同士を表でメス―メスジャンパーワイヤ―で結線することもできます(意味不明?下の写真を見て下さい)。オスのピンヘッダ―とメスのジャンパーワイヤ―は、ブレッドボードとオスのジャンパーワイヤ―よりもずっとしっかりはまるので、本接続にも向いています。このやり方だと、プロジェクトが終わった時に、モジュールを再利用することができます。

 

 

図(b)と同じようなやり方ですが、(c)のようにすると、裏面がやたらと煩雑になるのを防げます(その分、表面が煩雑になりますが)。(c)のやり方でつくった例の写真を下に示します。

 

 

 表はごちゃごちゃします。

 

 取り外せます。

 

  裏はこんな感じ。けっこう技術いります。

 

これを木板に固定するとロガーボックスに入れやすくなります。下の写真の例では、8cm×12cmと大きめなユニバーサル基板を使って、マスターとスレーブをどちらも同じ基板上に配置しています。下にある4つの端子台は、センサーへの5V印加・GNDと、センサーからの信号線(12チャネル)とのインターフェイスになっています(写真ではまだ端子台とロガーをつなげていませんが)。

 

 

 最初からこれを使えって話ではあります。太陽光パネルで充電池を充電しながら使えば、地下でもない限り恒久的に給電を行うことができます。ただ、露出部品が増えますので、盗難・いたずら・風雨への注意が必要になります(タイで観測をしたときは、金属部品が見えたら1日で盗まれると現地の人に言われました)。

 

 太陽光パネルの使用については、あまり詳しくないのですが、ある程度常識的なことをまとめると

・太陽光パネルの出力は常にふらつくので、バッテリーを介してロガーに給電する必要がある。

・バッテリー(充電池)には、リチウムイオン(ポリマー)電池・ニッケル水素電池(エネループ・充電式エボルタなど)・鉛電池(車やバイクに使うもの)などが使える

・過充電や過放電を防ぐために、バッテリーチャージャー(チャージコントローラー)が必要。

・特に、リチウムイオン電池は爆発の可能性があり、ヘタな充電をしないように注意が必要

このあたりのことはどこにでも書いてあると思うので、ここでは具体的な製品とつなぎ方を紹介します。

 

鉛電池を用いる場合

 鉛電池は基本的に12Vです。小型のもので7Ahくらい(写真左)、やや大型で12Ahくらい(写真右)の容量があります。これだけでも相当な期間の稼働が可能なわけですが、サイズが大きいのが難点です。ただ、電圧が高いため、大きな印加電圧が必要なセンサーに直接用いることができるなどのメリットもあります。寒冷地仕様などいろいろありますが、通常のものは5千~1万円くらいで買えます。

 

 

チャージコントローラーは違う種類のものがいくらでも売っていますが、例えばこれ。

 

 

この製品はType A USB端子(この前まで全てのPCについていた、いわゆる普通のUSB端子)がついており、ここから5Vを引き出すこともできます。ちなみに太陽光パネル・鉛電池と合わせてもっていると、災害のときにスマホの充電に役に立ちます。2018年胆振東部地震のときにも、部屋にあったスペアを使いました。

 

 太陽光パネルは、ちょうどよい大きさのものを見つけるのが難しいですが、例えばこれ。

 

 

電線をテスターにつないで太陽に向けると、20Vくらい出力されるのがわかります。

 

 これらを、以下のようにつなぎます。

 

  

これで、太陽光パネルを南上方に向けておけばよいです。こんな感じに。

 

 

もちろん、防水対策を忘れずに(1年以上動いていたものが突然止まったときがあり、現場に行ってみると、太陽光パネルからの電線に雨水が入ったようで、ボロボロになっていました)。少し計算してみたことがありますが、平均して1日に1時間でも晴れれば、前回までに作ったシステムを恒久稼働するのに十分な電力が得られます。鉛電池のバファーがあるので、仮に1か月くらい太陽が全く出なくても、その後快晴が数日続けば大丈夫です。秋田で2年ほど運転していますが、数日ごとに鉛電池は満タンに戻っています。

 

リチウムイオン(ポリマー)電池を用いる場合

 リチウムイオン(ポリマー)電池用のチャージコントローラーもたくさん、しかも安く売られています。しかし、管理人はこれらを使ったことがありません。というのも、裏ワザがあって、ホームセンターで売っている防犯LEDライトを買えば、1000円もせずに太陽光パネル・チャージコントローラー・リチウムイオンポリマー電池と、これらを入れるケースが全てついてくるのです(人が通ったときだけ人感センサーもついていることが多いです)。

 

 

買ったら、まずケースを開けます。製品によって違うでしょうが(管理人が試したのは、上の製品ではなく、Viva Homeで1個980円買ったオーム電機社製のものです)、この例では、人感センサーが反応したときだけLEDに電力供給するような制御チップと回路が付いています。これを外して(下図のように切断して)、下図のようにつなぐと使えます。

 

リチウムイオンポリマー電池なので出力は3.7Vです。よって、5V系であるArduino UNOには使えません(直列つなぎで2つ使わない限り)。Pro Mini 3.3Vを使ってください。フル充電すると4Vを超えるので(天気が悪い日がよほど続かない限り、4.0-4.2Vの間で推移します)、RAW – GND間に印加して給電するのがよいです。

 

 サイズとしては鉛電池をつかったシステムよりも格段にコンパクトですが、前回までのシステムを恒久的に給電する能力があります。家のベランダで1か月ほど稼働させていますが(下の写真)、リチウムイオンポリマー電池の電圧は全く下がっていきません。エネループだとたかだか数か月しか持たないとはいえ、せいぜい1~2mAの消費なので、小さい太陽光パネルでも十分まかなえます。

 

 

 これで、省電力・長期稼働のテーマは終わりです。スリープや電源管理モジュールを使ってできる限り省電力化を行うとともに、近場で観測するならエネループを数か月おきに交換、遠隔地なら市販の防犯LEDライトをハッキングしたものを使う、という方針で、長期観測ができます。

 

その他

 ニッケル水素電池用のチャージコントローラーも存在しますが、メモリ効果(使い切らないのに充電すると実質容量が低くなってしまうこと)が大きい電池であることを考えると、このようなつぎ足し充電によって長期間使うのにはあまり向いていないかもしれません。その他、エナジーハーヴェスティングといって、例えば振動や温度差など自然エネルギーを利用した給電方法が考案されていますが、実用上、なかなか太陽光に替わるものはないでしょうね。ちなみに、スマホのモバイルバッテリーに太陽光パネルがついた製品もありますが、第6回で説明したように、過充電防止機能が勝手にはたらくため、そのまま使うのは難しいようです。

省電力化:マスターとスレーブの分離

 ここで、フィールドにて1時間に1回、あるいは2時間に1回程度起動し、センサーの値を記録する装置を長期間駆動することを考えます。大部分の時間は寝ているだけなのですが、第13回で学んだようなスリープを使っても、Arduinoに接続したモジュールは電力を消費し続けます。これを完全に切断できればよいのですが、なかなか難しいようです。これを克服する手っ取り早いやり方としては、ロギングを行う本体と、それのスイッチをON/OFFするだけの電源管理モジュールを分離することです。前者にはいろいろなモジュールがついているわけですが、そもそも全体の電源が切られるわけなので、起動時以外は全く電力を消費しません。後者はスリープ状態で待機するわけですが、アラーム用のRTCモジュールだけ接続しているので、消費電力は抑えられます。電源をつかさどる後者をマスター(主)、それに命令されて起動・切断を繰り返す前者をスレーブ(従)とよぶことにします。ここでは、例として、スレーブには第8回の差動入力版電圧ロガーを使うことにします。マスターのほうは、第13回ですでに完成しています。リレーを1時間に一度、10秒だけONするシステムをつくったので、このリレーをスイッチにしてスレーブと電池をつなげばよいのです(10秒あれば、だいたいのロギングは終わります)。これらをまとめて絵にすると、

 

 

ここではスレーブにUNO(もちろんPro Miniなどでもいいです)、マスターにPro Mini(こちらがUNOだと省電力化できません)を使用しています。スケッチは、スレーブには第8回(差動入力版)、マスターには第13回のものを入れます。

 

 ここで、システムの稼働時間について計算してみます。消費電流は、バッテリー電圧やつくるものによって異なるのですが、ざっと

マスター:起動時(3600秒のうち10秒)約100mA、スリープ時(3600秒のうち3590秒)約1mA

スレーブ:起動時(3600秒のうち10秒)約50mA、切断時(3600秒のうち3590秒)0mA

マスターはリレーを駆動するので、第11回に書いた通り、短時間ながらかなりの電流を消費します。スレーブは完全切断されている間は、当然消費電力は0です。1時間あたりの平均電流に直すと、

 マスター:(10×100+3590×1)/3600 = 1.3mA

 スレーブ:(10×100+3590×1)/3600 = 0.14mA

 合わせて約1.5mA

よって、2500mAhの公称容量をもつエネループプロでは、駆動時間は2500mAh/1.5mA=1667h=70日(約2か月)となります。微妙?同じ市内での観測なら悪くないですが、山間部まで2か月に1度の電池交換をしに行っているヒマはありませんね。

 

TPL5110という秘密兵器

 ここまでは、管理人も電子工作を始めて数か月でたどり着きました。しかし、この「駆動時間2か月」の壁をなかなか破れないでいました。もちろん、太陽光電池を使うなどの手段もあるのですが、だんだんと機器が大がかりになっていきます。このとき見つけたのが、TPL5110という電源管理用チップでした。これこそまさに欲していたものでした。

 

 

この製品は、Amazonに出ていないことも多いです。スイッチサイエンス(Amazonに出ているときは、この会社が出しているのですが)かDigikey、あるいはChip1Stopなどで買えます。ここではTPL5110のモジュール版を扱いますが、基板に実装されていないチップそのものとして売っていることもあるので注意してください。この製品は、一言でいえば、上で自作したマスターがそのまま製品になったものです。しかし、スリープ時の待機電力が非常に小さく、5V電源に対して公称20μAですので、桁違いの省電力です。この製品は、スケッチを書き込むわけではなく、以下の要領で動きます。

・Delay - GND間の抵抗に応じて起動時間間隔が変化する。この抵抗は、可変抵抗(ポテンショメーター)のつまみで変えられる

・Vcc – GNDに対して電力供給する。時間がくると、この電圧がDRV端子から出力される。

・これによって起こされたスレーブからDoneに信号を送る(スレーブのデジタルピンからHIGHの信号を送る)と再びスリープに入る

・上の黒いボタンを押すと、起動時間間隔に関わらず起動する

 

非常にシンプルです。より詳細は以下のAdafruit社のウェブサイトに書いてあります(英語)。

https://learn.adafruit.com/adafruit-tpl5110-power-timer-breakout

このページの表にもとづくと、起動時間間隔と可変抵抗の関係については、例えば、

10 sec: 11.2 kΩ

1 min: 22.02 kΩ

30 min: 92.43 kΩ

1 hour: 124.91 kΩ

2 hours: 170 kΩ

です。この極小のポテンショメーターを回してこれらの抵抗値をぴったり得るのは不可能なので、起動間隔は「だいたい1 hour」くらいの設定で諦めるしかありません。もちろん、ポテンショメーターをはずして、固定抵抗をつけてもよいようです。

 

 TPL5110を使う際の接続とスケッチ例を示します。第8回の差動入力版のスケッチを原型に、TPL5110に関係した部分だけ赤字で示してあります。

 

 

#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
const int donepin = 9; // TPL5110のDoneにつなぐピン

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

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

    pinMode(donepin, OUTPUT);
    digitalWrite(donepin, LOW);
}

void loop(void)
{
    /* データの読み取り */
    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から差動(Differential)入力
      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();
    
    digitalWrite(donepin, HIGH); //ここでTPL5110に信号HIGHを送ると電源が切れる
    delay(100);
    digitalWrite(donepin, LOW); //これは不要かもしれないが、切れなかったときに再試行するためLOWに戻しておく
    delay(100);    
}

 

 

スリープ中は20μAとして先ほどの計算をすると、エネループプロで8か月くらい稼働するという計算になります。実績として、同じ装置を2台、冬期の間走らせたことがありますが、11月に開始し、翌年5月に見たら、1台は動き続け、もう1台は5か月くらいで止まっていました。北海道の話ですので、冬期の外気温のせいで電池容量が下がったのかもしれませんし、もしかしたらうまくスリープに入れないことがあったのかもしれません。実際の環境で信頼性をもって8か月稼働はなかなか難しいです。これが、現在のところ、「普通の」充電池でできる限界かと思います。

 前回からの続きです。ここでは省電力化のためスリープを使うとして、そこから復帰する方法を説明します。Arduinoのスリープモードにはいくつかありますが、簡単なのは前回のようにEnerlibライブラリを使ってスリープすることです。以下からダウンロードできます。

https://github.com/Arduino-IoT/libraries/tree/master/Enerlib

前回のスケッチでは、最後にenergy.PowerDown()としてスリープに入ってしまうので、ここから起きることなく、動作が止まってしまいます。ある時間に起きるためには、外部からInterruptという信号を送り、起こさなくてはなりません。そのためには、第7回で使用したRTCモジュールが使えます。まだ使っていないSQWという端子がありますが、ここからスリープ中のArduinoを起こす信号を送ることができます。まさしくアラーム時計そのものです。ArduinoにはWatchdog timerといって、自身で自身を叩き起こす機能もあるのですが、最大8秒までしか待ってくれないので、8秒ごとに起こされてすぐに眠らなくてはなりませんので、フィールドデータロガーのように1時間単位でスリープさせたいものには不便です。

 

 ここでは例として、毎時00分に起き、10秒だけリレー(第11回参照)をONにし、またすぐにスリープに入るシステムをつくります。Arduino UNO版とArduino Pro Mini版の接続を示しますが、スケッチはどちらも同じです。なお、第7回の手順に沿って、RTCの時刻合わせは済んでいるものとします。

 

UNO版(電源はUSBからとるものとして省略しています。VinとGNDに電池をつないでもいいです)

 

 

 

Pro Mini版

 

 

 

 

#include <Enerlib.h>
#include <DS3232RTC.h>
#include <Time.h>
#include <TimeLib.h>
#include <Wire.h>
#include <avr/sleep.h>
#include <avr/power.h>

Energy energy;
bool rtcint = true; // RTC interruptを作動させるフラグ
int RelayPin=9; // リレーに信号を送るピン

void setup(void)
{
    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 up RTC alarm ----- */
    RTC.alarmInterrupt(1, false); //秒に関するアラームをクリア
    RTC.alarmInterrupt(2, false); //分に関するアラームをクリア
    RTC.oscStopped(true); //このへんはまあ気にしない
    //RTC.alarm(2); //今回は2系統のアラームは使わない
    RTC.alarm(1); //アラームのフラグをクリア
    attachInterrupt(0, alcall, FALLING); //アラームが作動するとピンの電圧が下がる(FALLING)ので、その際は関数alcallを実行
    //RTC.setAlarm(ALM2_EVERY_MINUTE , 0, 0, 0); //今回は使わないが、例えばこれでもよい
    RTC.setAlarm(ALM1_MATCH_MINUTES, 0, 0, 0, 0);
    //RTC.alarmInterrupt(2, true); //今回は2系統のアラームは使わない
    RTC.alarmInterrupt(1, true); //アラームを有効化

    /* ----- Preparation of relay operation ----- */
    pinMode(RelayPin, OUTPUT);
    digitalWrite(RelayPin, LOW);
}

void loop(void)
{
    if (rtcint)
    {     
      rtcint = false;
      //RTC.alarm(2);
      RTC.alarm(1); //次にもう一度アラームが効くように、ここで再びフラグをクリア

      /* Turn on the relay */
      digitalWrite(RelayPin, HIGH); //リレーをON
      delay(10000); //10秒待つ
      digitalWrite(RelayPin, LOW); //リレーをOFF
    }

  /* Enter sleep */
  energy.PowerDown();
}

void alcall() {
  rtcint = true; 
}

 

これで、上記の通り動作するはずです。なお、フラグrtcintはtrueから始まるため、時刻に関わらず、立ち上げのときは一度だけリレーがONします。ここで、スケッチについて説明を加えておきます。第7回で紹介したJack ChristensenさんのライブラリにあるRTC.setAlarm()というメソッドで、アラームを設定するわけですが、アラームにはいろいろ種類があって、1の系統と2の系統があるようです。ライブラリのDS3232RCT.hを覗いてみると、以下のようなコードがあります。

 

    ALM1_EVERY_SECOND = 0x0F,

    ALM1_MATCH_SECONDS = 0x0E,

    ALM1_MATCH_MINUTES = 0x0C,     //match minutes *and* seconds

    ALM1_MATCH_HOURS = 0x08,       //match hours *and* minutes, seconds

    ALM1_MATCH_DATE = 0x00,        //match date *and* hours, minutes, seconds

    ALM1_MATCH_DAY = 0x10,         //match day *and* hours, minutes, seconds

    ALM2_EVERY_MINUTE = 0x8E,

    ALM2_MATCH_MINUTES = 0x8C,     //match minutes

    ALM2_MATCH_HOURS = 0x88,       //match hours *and* minutes

    ALM2_MATCH_DATE = 0x80,        //match date *and* hours, minutes

    ALM2_MATCH_DAY = 0x90,         //match day *and* hours, minutes

 

ここでは毎時00分で起こしたいので、ALM1_MATCH_MINUTESがよいです。これで、分が0、秒が0のときを指定してアラームが作動してくれます。ALM2の系統では秒を指定できません。それぞれのアラームによって引数の個数が違いますが、RTC.setAlarm()は常に4つの引数をとるようで、1つ目から順に秒、分、時間、日です。ここではALM1を使うので、RTC.alarm()やRTC.alarmInterrupt()の引数は1にしてください。attachInterrupt()関数中にalcallという関数が指定されていますが、これにより、アラームが作動するとalcall関数が実行されます。alcall内でrtcintのフラグがtrueになるので、loop()関数内が実行されることになります。loop()関数の最後にはまたスリープの文があるので、ここで再び眠りに入るわけです。

 

これで、1時間のうち10秒はかなりの電力を消費しますが(第11回の通り、リレーは3.3V×100mAくらい使います)、3590秒は節電状態に入ります。Pro Mini 3.3V 8MHzモデルの場合は数mAで眠り続けます。

 

 実験室で電圧を記録するロガーとしては、ここまでの作品で用が足りますが、電池を使ってこれをフィールドで長期稼働させるとなると電力供給の問題が起こります。後述の通り、Arduino UNOは単体で動かすだけでも、約50mAの電流を消費します。2500mAhのエネループプロ(×4本)で駆動すると、2500mAh/50mA=50hつまり約2日しか稼働しないことになります。これをいかに数か月~数年に延ばすか、が非常に難しい問題になります。本来、マイコンというのは非常に小さい電力で稼働するものですが、Arduino UNOにはUSB-シリアル変換チップその他いろいろ載っており、また接続したセンサーやモジュールにも常時電流が流れます。これを根本的に解決するには、Arduinoのような素人向けマイコンボードではなく、マイコンそのものを単位として一からロガーを構成し、省電設計をする必要があります。ただ、これ以外にもいくつか方法はあります。まず、ここでこれらの方法を概観します。

 

省電力化する

① Arduino UNOではなくArduino Pro Mini (3.3V 8MHz)やESP32など、より省電力化されたボード・マイコンを用いる

② スリープ機能を使う

③ 既往の電源管理モジュール(TPL5110など)を使う、あるいは作る

 

電力供給を解決する

(i) 巨大なバッテリーを用いる

(ii) 太陽光発電などエナジーハーヴェスティングをする

 

①~③と(i)~(ii)を組み合わせることで、より長寿命なシステムを構築できます。結論からいうと、①②だけでは抜本的に省電力化はできないようです。数分の1~1/10程度の省電力化はできるようですが、これでもエネループでは数週間しかもちません。(i)もスマート・抜本的な解決策には思えません。そこで、管理人が辿りついた結論としては、可能な限り①(Pro MiniはUNOに対して特段劣る部分はないので、こちらを用いるに越したことはない)は用いるとして、これと②(ii)の組み合わせ、②③の組み合わせがよさそうということです。

 

Arduino Pro Mini

 ここでは、まずArduino Pro Mini 3.3V 8MHzを紹介します。以下のように、1つあたり500円程度で買えます。ピンヘッダも必要になります。Pro Miniは本家のArduinoは廃盤になったようですが、互換品はまだ買えます。

 

 

ピンヘッダは同梱されていることが多いですが、安いので余分に買っておくとよいです。

 

 

ピンヘッダは下の写真のように下向き・上向きにつけてもどちらでもいいですが、下向きが主流です。

 

 

 

 A4・A5・A6・A7は(下向きには)つけないでおくと、ブレッドボードにそのまま挿してプロトタイプがつくれます。

 

 

基本的にArduino UNOと同じように使えますが、以下が異なります。

・ピンヘッダが付いていないので、自分ではんだ付けする必要がある

・USB-シリアル変換チップが載っていないので、スケッチを書き込む際に、外付けのUSB-シリアル変換モジュールが必要になる

・3.3V・8MHzで稼働する(ただし、5Vを流しても瞬時に壊れることはない)

・各段に小さい

・アナログピンA6・A7がある(UNOはA5まで)

 

 プログラムを書き込むときは、下の写真のようにUSB-シリアル変換モジュールを介してPCとArduino Pro Miniを接続します。

 

 

 例えばこれを使うとよいです。

 

 

6線使い、以下のように接続します。変換モジュールにはPCとUSBケーブルで接続するための端子があります。

 

 

注意が必要なのは、シリアル変換モジュールのRXとPro MiniのTX、シリアル-USB変換モジュールのTXとPro MiniのRX、というように、TXとRXは互い違いに接続することです。TはTransmit(送信)、RはReceive(受信)ですので。Arduino IDE(ソフトウェア)では、「ツール」の「ボード」から「Arduino Pro or Pro Mini」を選んで、さらに「プロセッサ:"Atmega328 (3.3V 8MHz)"」を選び、「シリアルポート」に現れている「COM X」(XはPCが自動的にアサインする数字)を選びます。あとの使い方はArduino UNOと同じです。ピンアサインもUNOと同じで、A4・A5がそれぞれI2C通信のSDA・SCLになります。2つだけちょっとおかしな位置にあるピンなので、わかりやすいです。

 

 Pro Miniの電源関係はややわかりづらいので説明しておきます。シリアル-USB変換モジュールを介して先の写真のようにつないでいるとき、Vccに直接3.3Vちょうど(PCからの5V供給を、このモジュールが3.3Vに制御してくれています)が送られています。Vccはマイコンの動作電圧そのものになるので、ここに例えば4.2Vを送れば、全体が4.2Vで動くことになり、RTCやSDカードなどの周辺装置にもVcc端子を介して4.2Vがかかります。この程度なら問題はないですし、3.3Vモデルに5Vをかけても、少なくとも長い時間でなければ壊れたことはありません。一方、「RAW」という端子がありますが、ここに電圧をかけると、電圧レギュレータを介して3.3Vに調整されてからシステムに電圧がかかります。では常にこのRAW端子に電圧をかければよいということになりそうですが、3.3Vに減圧する以上、入力電圧は3.3Vよりある程度大きくなくてはなりません。3.3Vちょうどくらいの電圧をRAW端子にかけるとうまく動作しないことがあるので注意が必要です。エネループ3本(3.9~4.2V)をRAW端子・GND端子につないで動作させるのがちょうどよいようです(12Vまでかけてよいようなので、もちろん4本つないでも構いません。電圧レギュレータによるエネルギーロスが大きくなるだけです)。

 

 Arduinoシリーズを使ってフィールド観測を行う場合、このPro Miniの出番が多くなります。省電力なうえに、小さなスペースに収めることができるからです。たとえば下の写真のように、ユニバーサル基板の上に載せ、他のモジュールとともに配線を固定(はんだ付け)し、完成品とします。

 

消費電力について

 

 ここで、Arduino UNOとPro Miniの消費電力の比較をしてみます。ここでは以下の2つのスケッチを試します。1つ目は、「何もしない」スケッチです。2つ目はスリープを導入しています。これにより、Arduinoは省電力モードに入ります。avrのライブラリは標準としてすでに入っていると思いますが、ここではEnerlibライブラリを以下よりダウンロードしてインストール(やり方については第7回参照)してください。

https://github.com/Arduino-IoT/libraries/tree/master/Enerlib

 

void setup() {

}

void loop() {

}

 

#include <Enerlib.h>
#include <avr/sleep.h>
#include <avr/power.h>

Energy energy;

void setup(void)
{
  
}

void loop(void)
{
  /* Enter sleep */
  energy.PowerDown();
}

 

これらをUNOとPro Miniで試してみました。UNO互換品にはいくつか種類があり、ここではUNO Aとしたもの(マイコンがATMEGA328P-PU、USB-シリアル変換チップがatmega16u2)とUNO Bとしたもの(マイコンがATMEGA328P-AU、USB-シリアル変換チップがCH340)、およびPro Miniを試しています。消費電力は以下のようになりました(どうやって計るか?テスターを使って直列つなぎにします。小学校で習った通りです)。

 

 

 何もしない

 スリープ

 UNO A

 48mA

 33mA

 UNO B

 63mA

 48mA

 Pro Mini

  3.3V 8 MHz

 5mA

 1.5mA

 

他のボードも含めた比較は第31回を見て下さい。

 

 さて、Pro Mini 3.3V 8MHzの省電力性能がよくわかりました。しかし、この電流はあくまで単体での消費電力であり、実際にはこれにRTCだのSDカードだのセンサーだのつけることになります。これらのモジュールは、それぞれおよそ1mAほど消費し、マイコンがスリープしても消費は続くので、4つつければスリープ時でも5~6mA消費することになってしまいます(2500mAhのエネループプロで約400時間、2~3週間程度)。少しでも省電力化するためにできることとしては、Arduinoそのものや、RTCなどモジュールについているLEDをもぎとることです。これにより、それぞれ0.5mAくらいずつ消費を減らすことができます(誰も見ていないロガーボックスの中で光り続けるほど無駄なことはない)。

 

 ここで、素朴な疑問。スリープからはどうやって起きる?これについては次回説明します。

 ここで、データ記録から一度話が外れますが、リレーの駆動について説明します。リレー(継電器)とは、電気で操作するスイッチのことです。例えばArduinoで100V ACの家電を動かすことはできませんが(電圧・電力ともに足りないし、そもそも交流が出せない)、100V ACにつないである電線のスイッチを開閉することはできます。このスイッチがリレーです。これを動かせれば、たとえば三軸試験装置で、変位がある程度大きくなったら(つまり、変位計からの電圧が一定値を超えたら)、モーターのスイッチを切る、などすることが可能です。データロガーという観点でも、後の回で説明しますが、これを使って電源管理モジュール(間欠動作を管理するモジュール)をつくることができます。

 

 リレーにはメカニカルリレーとソリッドステートリレーがあります。前者はソレノイドを用いて、電磁石として機械的にスイッチを開閉するものです。動くたびにカチッと音がします。機械的に絶縁・接続するので、有無を言わせず機能します。ただし、機械的動作を伴うため、消費電力が大きいうえに、寿命があります(数万回・数十万回といった動作回数)。ソリッドステートリレーについては詳しくありませんが、フォトカプラを使って絶縁・接続するため、機械的動作を伴いません。ただ(この認識が正しいのかわかりませんが)、交流専用のものが多いように感じます(直流に対して使うと、一度リレーを開くと、閉じようとしても電流が流れっぱなしになる)。

 

 電子工作でリレーを買おうとすると、以下の製品(あるいは似たもの)がよく出てきます。2つ、4つ、6つと連なった多連型も多く売っています。

 

 

 これはこれで使えますが、5V専用で、3.3Vではパワーが足りないようです。後に3.3Vのシステムに移行することも考えると、以下のものがよいです。こちらは3.3V用ですが(推奨されているかはわかりませんが)5Vでも使えます。

 

GROVE - リレー GROVE - リレー
484円
楽天

 

消費電力はいずれも100mAほどです。電子工作的にはかなり大きな消費電力です。室内試験などで用いる場合はArduinoに家庭用電源から電力を得られるので問題ありませんが、電池駆動を考えると、あまり長い時間は駆動させたくないものです。リレーには、信号を送るとONとなるもの、信号を送るとOFFになるものがありますが、上記の製品は、信号を送るときのみON(接続される)となります。以下のように接続してみてください。

 

 

実際に届いたリレーと、上のFritzingの図で端子の並びが違うことがあるのでご注意ください。Vcc同士、GND同士をつなげばよいだけです。リレーの信号(SIG)ピンはArduinoの任意の端子につなぎ、スケッチ上で指定します。NCという端子もありますが、non-connectedなので使いません。この製品はGroveというシリーズで、下のようなコネクターが付いてきます。Arduinoに対して使うときは、片側は処理が必要です。ここでは単に切って剥きました(右図)。

 

 

 

そして以下のスケッチを試してみて下さい。5秒に一度、リレーがカチッといって開いたり閉じたりします。そして端子台の2極がつながったり絶縁されたりすることが、テスターを使えば(抵抗がゼロになるかどうか)でわかります。

 

#include <DS3232RTC.h>    //http://github.com/JChristensen/DS3232RTC
#include <Time.h>
#include <TimeLib.h>

const int RelayPin = 9; // リレーに信号を送るデジタルピン番号

void setup(void)
{
  pinMode(RelayPin, OUTPUT); // リレーに信号を送るデジタルピンを出力モードに設定
}

void loop(void)
{
  digitalWrite(RelayPin, HIGH);
  delay(10000);
  digitalWrite(RelayPin, LOW);
  delay(10000);
}

 

こんな感じ(左:リレーがOFFのとき、右:リレーがONのとき)。

 

 

 

ここでは応用として、第7回で説明したRTC(時計)を使えば、次のようにして、例えば毎時0分になったら10秒間だけリレーを開くということもできます(下のスケッチ、毎時リレーをながめているのが面倒だったので実際に試していないのですが、うまくいく、のではと思います)。ここで、RTCの時間設定は第7回のスケッチによりすでにされているものとします。何かの家電を1時間に10秒だけ電源を入れる、といったことに使えますね(家電はだいたい100V ACでしょうから、上記のソリッドステートリレーのほうがよいかもしれません)。

 

 

#include <DS3232RTC.h>    //http://github.com/JChristensen/DS3232RTC
#include <Time.h>
#include <TimeLib.h>

const int RelayPin = 9; // リレーに信号を送るデジタルピン番号
bool flg = false; // リレーがこの時間(hour)にすでに動作したかどうかのフラグ

void setup(void)
{
    pinMode(RelayPin, OUTPUT); // リレーに信号を送るデジタルピンを出力モードに設定
    
    /* ----- 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);
}

void loop(void)
{
    if((minute()==0)&&(!flg)) //分(minute)が0、かつまだこの分で動作していないなら
    {
      digitalWrite(RelayPin, HIGH);
      delay(10000);
      digitalWrite(RelayPin, LOW);
      flg=true; //おなじ分のうちにまた動作しないよう、フラグをtrueとしておく
    }

    if(minute()!=0) // 分(minute)が0でないときに、フラグを再びfalseとしておく
    {
      flg=false;        
    }
}

 

 なぜここでリレーの回を設けたかというと、Arduinoを使ったロガーそのもの(スレーブ)を、もう一台のArduino(マスター)でリレーを使って電源ON/OFFすれば、省電力化できるため、それを後の回で説明するためです。

 

 ここまで、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個接続したいという時に便利です。しかも、順次印加するので、消費電流も限定的であり、大容量バッテリーや太いケーブルが不要です。

 電圧を読む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)には多少のオフセットのズレがありますが、だいたい意図した通りに出力されているのがわかります。

ADCモジュール ADS1115

 ここまでで一応、電圧は計測することができるようになりました。ただし、Arduinoのアナログ入力分解能は10bit(Arduino Dueは12bit)なので、1024段階でしかデジタル化できません。この分解能の問題は、別途ADC(Analong-to-Digital Converter:アナログ-デジタル変換器)モジュールを接続すれば、改善できます。ここではADS1115という16bit(2^16=65536段階)のADCチップを使います。これも例によって、ブレークアウトモジュールの形で製品になっています。1個あたり500円くらいで買えます。

 

 

ADS1115の特徴は、

・16bit 分解能

・単動4チャネル、差動2チャネルの入力端子

詳しいデータシートはこちら

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

 

単動と差動の違いについて、なじみがなければ、例えば以下を読んで下さい。

https://www.fa.omron.co.jp/guide/faq/detail/faq05826.html

一言でいえば、差動のほうがノイズに強く、安定して電圧を読めますが、2つの入力端子を1つのチャネルに使うので、入力チャネル数が少なくなります。

 

 つなぎ方の説明に入る前に、分解能の必要性について考えるとよいと思います。例えば、計測範囲が0~1000kPaの圧力センサーを読もうとすると、10bit(1024段階)での分解能は約1kPaになります。水位にすると10cmです。これは、いまいちですね。しかし、例えば計測範囲が-100~+100kPaの絶対圧センサーの場合、分解能は0.2kPaになります。多くの場合、フィールド計測で(実験室でもそうですが)重要なのは、分解能(resolution)ではなく精度(precision)・正確性(accuracy)です。これらの違いは下の図の通りです。

 

 

分解能が低くても、計測回数を増やして平均すれば、実質の分解能が高くなります(いくつの計測回数で真値に近くなるか、が精度の言いかえといってもいいでしょう)ので、やはり最も重要なのは正確性です。分解能が必要なのは、わずかな変化をセンシングする必要があるときです。しかし、それには高い精度が伴わなければ、実際の物理量のわずかな変化なのか、センサーのブレなのかの区別はつきません。自身の用途に対し、10bitより高い分解能が必要か、まず考えてみて下さい。

 

単動入力をする

 では、ADS1115の説明をします。これはI2Cセンサーなので、前回(第7回)のRTCモジュール同様、Vcc・GND・SDA・SCLの4本で接続します。前回までの作品にADS1115を加えたものを下図に示します。なお、VccではなくVddと書いてある場合もありますが、このレベルでは特に気にしなくてよいです。どちらも入力電圧の+側と思えばよいです。

 

 

 

上のfritzing図と少し違いますが(電池ではなくPCからUSBで給電している、SDカードモジュールに3.3Vのものを用いている、ブレッドボードの大きさや使っている穴の位置が違う、など)、参考までに写真を載せると

 

 

RTCと合わせると、I2Cセンサーを2つつなぐことになるので、SDA・SCL端子もブレッドボード上に拡張しましょう。ADS1115のI2Cアドレスですが、「ADDR」端子をどこにつなぐかによって、4つの異なるアドレスを設定することができます。ここの例では、ADDRをGNDにつないでいるので(白い線)、ADS1115のアドレスは0x48になります。この場合も含めて、アドレスは

 

ADDR -> GND: 0x48

ADDR -> VDD: 0x49

ADDR -> SDA: 0x4A

ADDR -> SCL: 0x4B

 

と設定できます。ということは、ADS1115は最大4つまでArduinoに同時接続できるということです。これにより、(ADS1115を4つ買えば)単動16チャネル、差動8チャネルまで読み込めます。ただ、後の回で紹介するように、マルチプレクサーという切り替え装置を使えば、実際にはADS1115を4つ買う必要はありません。上の図は、単動入力の場合で、電圧を計測したいもの(ここでは電池)の+をA0端子につなぎ、チャネル0から読んでいます。GNDは他と共通とします。

 

 次に、スケッチです。ADS1115のライブラリをダウンロードして、自身の環境に加えて下さい(やり方は第7回のRTCモジュールDS3231のときと同じ)。

https://github.com/adafruit/Adafruit_ADS1X15

なお、上記のページに行くとADS1015(12bit版の製品)用であるかのように書かれていますが、このライブラリにはADS1015とADS1115どちらのクラスも含まれていますので安心して下さい。では、上図のように接続できたら下のスケッチを書き込んで下さい。第7回の作品に加筆しています(赤字部分)。ここではADS1115は一つしか接続していないので、スケッチのほうでアドレスを指定する必要はありません。

 

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

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

void loop(void)
{
    /* データの読み取り */
    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_SingleEnded(0)); //チャネル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秒の停止
}

 

この例では、ads.setGain(GAIN_ONE)により、ゲイン(利得)をGAIN_ONEという設定にしています。これは入力レンジを-4.096~+4.096Vにする設定で、これを16bit(2^16=65536段階)で読むため、公正係数(ビットあたりのmV)は0.12500となります。プログラムの冒頭で、CFACTOR(Calibration factor:校正係数)をそのように設定していることに留意してください(0.12500以外のCFACTORは//によりコメントアウトしてあります)。もしセンサーの出力範囲が大きいあるいは小さい場合は、他のゲイン設定にするとよいでしょう。アナログセンサーの多くは3~5Vで信号を返すので、GAIN_ONEを使うことが多いかと思います。これを走らせると、シリアルモニタ―上では:

 

 

 

SDカードにも書き込まれています。 

 

差動入力をする

 差動入力をする場合、電圧信号(ここでは電池からの線)の-をADS1115につなぎます。ピンアサインは、

A0: チャネル0の+

A1: チャネル0の-

A2: チャネル1の+

A3: チャネル1の-

となります。

 

 

スケッチについては、上の単動入力のスケッチのうち、以下を書き変えてください。

read_from_ads = float(ads.readADC_SingleEnded(0));

read_from_ads = float(ads.readADC_Differential_0_1());

に。

ADS1115のA2・A3をつかって差動のチャネル1を読みたいときは、

read_from_ads = float(ads.readADC_Differential_2_3());

を使います。結果については省略します。うまくいきましたか? 

 

複数のADS1115を接続する

 複数のADS1115を接続する場合、それぞれ異なるアドレスを設定しなければなりません。下の例では、2つのADS1115をつないでいます。ADS1115の一つではADDRをGNDにつなぐことでアドレスを0x48に(これをads1としましょう)、もう一つはADDRをVccにつなぐことで0x49にしています(これをads2とします)。ads2への入力(オレンジの線)は省略しています。

 

 

スケッチは以下のように書くことで、どちらがどちらのADS1115か区別できるようにします。オブジェクト志向のプログラム言語を知らないとわかりづらいかもしれませんが、冒頭の

Adafruit_ADS1115 ads1(0x48);

Adafruit_ADS1115 ads2(0x49);

で2つのADS1115のインスタンスを生成しています。

 

#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 ads1(0x48); // ADS1115のインスタンス1を作成
Adafruit_ADS1115 ads2(0x49); // ADS1115のインスタンス2を作成

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 ----- */
    ads1.setGain(GAIN_ONE);
    ads1.begin();
    ads2.setGain(GAIN_ONE);
    ads2.begin();
}

void loop(void)
{
    /* データの読み取り */
    int cnt1=0;
    float read_from_ads;
    float averaged_input1, averaged_input2;

    /* ads1(ADDRピンをGNDピンに接続したほうのADS1115)からの読み込み */
    averaged_input1=0;
    for(cnt1=0;cnt1<AVERAGENUM;cnt1++) //AVERAGENUMの数だけ繰り返す
    {
      read_from_ads = float(ads1.readADC_SingleEnded(0)); //チャネル0から単動(Single End)入力
      averaged_input1=averaged_input1+read_from_ads/AVERAGENUM; //読んだ値を足していく:AVERAGENUMで割ることで平均化している
    }
    averaged_input1=averaged_input1*CFACTOR; //校正係数を掛けて電圧とする

    /* ads2(ADDRピンをGNDピンに接続したほうのADS1115)からの読み込み */
    averaged_input2=0;
    for(cnt1=0;cnt1<AVERAGENUM;cnt1++) //AVERAGENUMの数だけ繰り返す
    {
      read_from_ads = float(ads2.readADC_SingleEnded(0)); //チャネル0から単動(Single End)入力
      averaged_input2=averaged_input2+read_from_ads/AVERAGENUM; //読んだ値を足していく:AVERAGENUMで割ることで平均化している
    }
    averaged_input2=averaged_input2*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_input1);Serial.print(" mV, ");
    Serial.print(averaged_input2);Serial.println(" mV");
    
    /* 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_input1);dataFile.print(" mV, ");
      dataFile.print(averaged_input2);dataFile.println(" mV");
    }    
    dataFile.close();
    
    delay(2000); //2000ミリ秒=2秒の停止
}
 

 

ここまでで、単動最大16チャネル(あるいは差動最大8チャネル:異なるADS1115を使って単動と差動の組み合わせもできます)16bitのアナログデータロガーができました。

 

パフォーマンスの確認

 冒頭で分解能・精度・正確性の話をしましたが、ここで、ADS1115による計測とArduinoのアナログピン(ここではA0:第7回のブログの作品を使いました)による電池の電圧計測結果を見てみます。2秒間隔でアルカリ電池の電圧を調べました。20回計測の結果は以下の通り。

 

ADS1115          Arduino A0ピン 

1623.36 mV         1606.45 mV

1623.37 mV         1606.45 mV

1623.36 mV         1606.45 mV

1623.36 mV         1606.45 mV

1623.37 mV         1606.45 mV

1623.36 mV         1606.45 mV

1623.37 mV         1606.45 mV

1623.36 mV         1606.45 mV

1623.36 mV         1606.45 mV

1623.36 mV         1606.45 mV

1623.37 mV         1606.45 mV

1623.37 mV         1606.45 mV

1623.37 mV         1606.45 mV

1623.37 mV         1606.45 mV

1623.37 mV         1606.45 mV

1623.37 mV         1606.45 mV

1623.36 mV         1606.45 mV

1623.36 mV         1606.45 mV

1623.37 mV         1606.45 mV

1623.37 mV         1606.45 mV

 

まず、電圧値そのもの(真の0Vに対するオフセット)が17mVほど異なります。実際の計測では校正(キャリブレーション)を行っておいたほうがよいでしょう。値のばらつきについて見てみて下さい。ADS1115のほうは、0.01mVのうちにおさまっています。一方、Arduinoアナログピン(10bit = 1024段階)のほうは、分解能が5V/1024=5mV(この「5V」というレンジはAREFピンなどを使って変えることができますが)なので、電池のように安定した電圧を持つものに対してはピクリとも動きません。

RTCとは

データロガーには、データそのものとともに、データを取得した時刻を記録する機能が必要です。Arduinoには時計が内蔵されていますが、電源が切れるたびにリセットされます(1970年1月1日などにタイムスリップします)。これでは不便なので、時計(RTC: Real Time Clock)モジュールを取り付けます。このRTCモジュールにはコイン電池をつなぎ、常に給電させておきます。RTCモジュール自体は非常に省電力なので、コイン電池で数年持ちます。

 

 

 

RTCにはいろいろなモデルがありますが、DS1307やDS3231がよく使われるようです。前者はArduino IDEに最初から入っているRTC.hライブラリで動かせます。後者には、管理人は別のライブラリを使っています。以下からダウンロードしてください。

https://github.com/JChristensen/DS3232RTC

DS3232とありますが、DS3231にも使えます。このGitHubというサイトは、いろいろなライブラリやソフトのレポジトリーサイトです。”Clone or download”をクリックすると(Internet Explorerには非対応になったようです。ChromeやEdgeを使ってください)、zipファイルとしてダウンロードできるので解凍してください。DS3232RTC-masterというフォルダになるので、これを("-master”をフォルダ名からとるのが通常のようですが、残しておいても動作には問題ありません)Arduinoのlibrariesフォルダに移します。このフォルダの場所ですが、Windows 10で言われるがままArduino IDEをインストールした場合、以下にあると思います。

C:\Program Files (x86)\Arduino\libraries

これでこのライブラリが使えるようになります。

 

ここからはDS3231を使って説明します。このモジュールはI2C通信で動きます。第5回で説明したように、I2C通信用のモジュールは、すべて固有のアドレスをもっています。このRTC(DS3231)のアドレスは0x68です。これをプログラム(スケッチ)に書く必要はないのですが(先ほどダウンロードしたライブラリのプログラムの中にすでに書いてあります。” DS3232RTC.h”の49行目を見てみてください)、他にI2Cモジュールをつなぐ場合、競合しないように注意が必要です。

 

さて、前回までの作品に、以下のようにDS3231をつないでください。ここで、ブレッドボードを使います。いろいろ部品をつないでいくと、Arduino上のVcc(5V)やGNDの端子が足りなくなることに気付くと思います。このようにつなぐと、第4回で説明したように、一番上の列が全てGND、上から二番目の列が全て5Vとつながります。

 

 

 

 ここでSDA・SCLという端子がありますが、このSDAがデータ送受信、SCLがクロックに使われます。Arduino UNOの場合、SDA・SCLと書いた端子があるので、そこに繋げばいいですし、あるいはそれぞれA4・A5につないでも構いません。UNOではA4・A5がI2C通信用端子を兼ねています。

 

DS3231(を実装したモジュール)にはコイン電池をはめる部分があるので、ここにCR2032を入れれば計時が始まります。CR2032はTOSHIBAなど一流ブランドなら1個200円くらい、100円ショップなら2個で100円です。

 

 

参考1:

電子工作を始めたころに他のサイトで知ったのですが、実はこのモジュール、VCC端子がコイン電池の端子につながっているそうです。つまり、Arduinoへの給電から電気をおすそ分けしてもらって、コイン電池を充電しようという設計になっているそうです。ただし、CR2032は充電池ではないので、この状態が続くと電池が「重くなる」そうで、液漏れなどの危険もあるとのこと(つないですぐにどうこうなるというわけではなさそうですし、公式には充電池ではない電池も、実は充電が全くできないわけではないのですよね)。実は、コイン充電池であるLIR2032を使うことを想定しているようです。このLIR2032、数年前はなぜか非常に手に入れづらく、Amazonにすらありませんでした。しかしこのブログを書いたのをきっかけに検索したら、しこたま売っているようです。どういう事情があったのでしょう?とにかく、LIR2032を買えばよいのですが、CR2032を使う場合(100円ショップで買えるのは便利)、以下のようにパターンカット(カッターでガリガリと基盤上の結線を断線させる)すればよいらしいです。

 

 

いずれにしても、このブログの終盤でつくるロガーでは、電源は1時間に数秒しか入らなくなるので、このようなことをしなくてもコイン電池を過充電する心配はありませんが。

 

参考2:

ZS-042と名前のついたこのDS3231のブレークアウトモジュール、両端にVcc・GND・SCL・SDAが1組ずつありますが、それぞれ基板上でつながっているようで、どちら側にあるものを使っても問題ありません。

 

RTCの時間合わせをする

ここまでで物理的な作業は終わりで、ここからはスケッチの話になります。まずはRTCの時間合わせをしないといけないので、時間を合わせる以下のプログラムを書き込みます。ここでは2020年2月9日23:08:00に合わせています。

 

#include <ds3232rtc.h>    //http://github.com/JChristensen/DS3232RTC
#include <time.h>         //http://www.arduino.cc/playground/Code/Time  
#include <timelib.h>
#include <wire.h>         //http://arduino.cc/en/Reference/Wire (included with Arduino IDE)

void setup(void)
{
    Serial.begin(9600);
    while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
    }
    
    /* ----- Initialisation of time ----- */
    setTime(23, 8, 00, 9, 2, 2020);   // set the system time in Arduino [Hour, Min, Sec, Day, Month, Year]
    RTC.set(now());   // set the RTC time according to Arduino (Need to activate only when the RTC module has to be re-set)
     
    if(timeStatus() != timeSet) 
       Serial.println("Unable to sync with the RTC");
    else
       Serial.println("RTC has set the system time");
    
    delay(1000);
}

void loop(void)
{

}

 

このあと、本来入れたいプログラムを迅速に入れます。「迅速に」というのは以下の理由です。Arduinoは、電源が入ると、メモリに書き込んであるスケッチを実行します。ここまでの時点で、メモリには「今、何月何日何時何分何秒に設定しろ」という上記のスケッチが入っています。これに、本来入れたいプログラムを入れようとしてPCにつなぐと、その瞬間に給電され、時間設定のプログラムがまた走り、上記の時間にリセットされてしまうからです。ですので、本来入れたいプログラムを入れる時間をあらかじめ見計らって時間を先に設定しておかなければなりません。言いたいことわかります?

 

前回からの続き:時間をRTCから読み、電池の電圧を、測った時間とともにSDカードに記録

上記のスケッチに続き、以下のスケッチを入れて下さい。前回までの電圧を読んでSDカードに記録する機能(第5回:1チャネルだけ電圧を読み込むもの)に、時間の書き込みも加えたものです。今回書き加えた部分を赤く示しています。また、青字の部分も、前回のスケッチを修正することで、SDカードに書き込まれるデータの改行を減らしています。

 

#include <SPI.h>
#include <SD.h>
#include <DS3232RTC.h>    //http://github.com/JChristensen/DS3232RTC
#include <Time.h>
#include <TimeLib.h>
#include <Wire.h>

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

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

void loop(void)
{
    int ainput; //読み取ったbit数:intは整数
    float vinput; //bit数を電圧に変換したもの:floatは浮動小数点数

    /* データの読み取り */
    ainput=analogRead(A0); //ピンA0から電圧をbitとして読む
    vinput=5000.0*ainput/1024; //上記を電圧mVに変換
    //ここで5000.0でなく5000とすると、整数としてvinputにキャストされてしまう)
    //粗い値になったり、おかしな値になったりする

    /* 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(ainput);
    Serial.println(" bit"); //" bit"と表示、printではなくprintlnとすることで改行する
    Serial.print(vinput);
    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(ainput);
      dataFile.print(" bit, "); //" bit"と表示
      dataFile.print(vinput);
      dataFile.println(" mV"); //" mV"と表示、printではなくprintlnとすることで改行する
    }    
    dataFile.close();
    
    delay(2000); //2000ミリ秒=2秒の停止
}

 

この結果、シリアルモニタ―を開くと

 

SDカードには

 

今回はここで終わりにしましょう。このDS3232には、SQW端子を通してAlarm interruptということをできる機能があります。これは、Arduinoをスリープさせて省電力モードに入れたときに、決まった時間に叩き起こす、目覚まし時計の機能で、非常に便利なものです。Arduinoのスリープの話もしないといけないので、次回以降にします。

 

1回を更新するのがだいぶ大変になってきました。正直、この季節にやるものではないです。