Arduinoで長期間に渡る屋外用距離測定器を作った話

# なぜ作ったか
　私は普段鉄道の保全員として仕事をしていますが、よく異常が発生する ~~問題児~~ 設備を取り上げて原因、対策を研究する業務があります。
　今年のテーマのなかで、野外フィールド(レール)の状況を長期間に渡って連続で測定する必要が出てきたので、ロボ研時代を思い起こして作ってみました。という話です。

# 前提条件
 データロガーを作成するにあたり、決定した仕様は以下のとおりです。

1. 長期間(1ヶ月程度)の連続使用に耐えうる
2. ある程度の防水、防塵、耐振性がある(鉄道沿線に設置するため)
3. データは15分に1回採取する
4. 採取したデータはSDカードに保管する(保存した時刻が必要)

 本来ならば、鉄道のレールには帰線電流やら様々なものが流れているため誘導対策やらの異常電流への対策が必要なのですが、下図のように枕木に並行して取り付けることで帰線電流の向きと直行させ、誘導電流を回避します。

※実際の装置にはもう一つセンサーが付いていますが、図面に載せると何の研究かがひと目でわかってしまうため表記していません。

![イメージ図.png](https://qiita-image-store.s3.amazonaws.com/0/68948/83590f0a-ee37-a161-16e6-b16c9cb26604.png "装置イメージ")

# センサーの検討
センサーは一般的なSONYの赤外線センサー[GP2Y0A21YK](http://akizukidenshi.com/catalog/g/gI-02551/)を採用しました。

また、異常値を軽減するため、10回測定の算術平均を取ることにします。

比較対象として[超音波測距センサー](http://akizukidenshi.com/catalog/g/gM-12185/)がありますが、
赤外線測距センサーに比べて高価であること、超音波測距センサーはI2C通信のため長距離、ノイズ箇所での伝送に不安があることの2点から見送りました。

赤外線測距センサーでも7mも伝送すればある程度の減衰がありますが、それは電圧から距離への変換時にカバーすることにします。

# データの記録
## SDカード
常時PCに繋ぐわけにはいかないので(測定器は屋外のうえにケーブルトラフ内に設置するのでスペースがありません)、microSDを利用してそこへ書き込むことにします。
フォーマットは以下の通りです。スペース区切りにしてあとで処理しやすいようにしておきます。

*DATE_TIME SENSOR1  SENSOR2 SENSOR3 ...*

1ヶ月という長期間に渡る測定であるため、時刻と共に記録を行いたいのですが、Arduinoは内部に時刻を保持することが出来ません。また、現地で整時する事もできません。
そこで、Real Time Clock(RTC)を利用して時刻を取得することを検討します。

## 時刻の取得
RTCはAdafruit社の[PCF8523Kit](https://www.adafruit.com/product/3295)を使用します。
これをあらかじめ整時しておくことで、正確な測定時刻を取得することが出来ます。
[Arduino用のライブラリ](https://learn.adafruit.com/adafruit-pcf8523-real-time-clock/rtc-with-arduino)も公開されており、便利なキットです。
I2C通信で時刻を取得するサンプルを以下に示しておきます。

```CPP
#include <Wire.h>
#include "RTClib.h"

// RTCのインスタンス
RTC_PCF8523 rtc;

// DateTime.dayOnTheWeek()の戻り値はintですので、こちらで読み替えてやる必要があります
char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

bool rtcSetup(){
  Serial.println("rtc setup");
  if(! rtc.begin()){
    Serial.println("Could not find RTC module!");
    return false;
  }
  Serial.println("begin ok.");
  
  if(! rtc.initialized()){
    Serial.println("RTC is not Running!");

    // 時刻を調整する関数。最初の一回だけ動作させればOKです。
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }
  
  return true;
}

void setup () {
    Serial.begin(9600);
    Serial.println("Setup Start");
  if(!rtcSetup()){
    Serial.println("RTC false loop");
    while(1);
  }
  Serial.println("rtc ok.");  
}
void loop () {
  // 時刻の読み出し
  DateTime time = rtc.now();
  printSerialTime(time);
  
  delay(1000);
}

// シリアルモニターに打刻する
void printSerialTime(DateTime time){
    Serial.print(time.year(), DEC);
    Serial.print("/");
    Serial.print(time.month(), DEC);
    Serial.print("/");
    Serial.print(time.day(), DEC);
    Serial.print(" (");
    Serial.print(daysOfTheWeek[time.dayOfTheWeek()]);
    Serial.print(") ");
    Serial.print(time.hour(), DEC);
    Serial.print(":");
    Serial.print(time.minute(), DEC);
    Serial.print(":");
    Serial.print(time.second(), DEC);
    Serial.println(); 
}
```

# 回路図
![Data_Logger_回路図.png](https://qiita-image-store.s3.amazonaws.com/0/68948/c77f402d-8bff-dc6c-40f7-dcdbb8389cf6.png)

実装は[Arduino Wireless SD Shield](https://store.arduino.cc/usa/arduino-wirelss-sd-shield)を購入し、その上にコネクタまでの全回路を実装しています。(画像の赤い線はArduinoのSDA,SCLへ飛ばすケーブルです。)

![実装図.JPG](https://qiita-image-store.s3.amazonaws.com/0/68948/fe0457ed-80c5-8c61-54e3-61e853e0c63f.jpeg)

# 電源問題の解決
仕様上から、何らかの外部電源が必要になります。
候補としては9V電池またはモバイルバッテリーがあげられますが、省スペース化の観点から9V電池を採用したいです。
しかし、9V電池で赤外線測距センサー×4とArduinoを常時起動させたらあっという間に電池がなくなってしまうので、sleepによる省電力化を実装します。
本当は時計モジュールに起動パルスを出してくれる機能があれば良かったのですがPCF8523には無いので内部のWDTを利用します。[*参考文献*](http://radiopench.blog96.fc2.com/blog-entry-486.html)

ArduinoのWDTの最長時間は9秒なので15分に1回測定したい場合、$\frac{15[min] \times 60[sec/1min]}{9[sec]} = 100[回]$となりWDTが上がるたびにカウントアップして100を超えたタイミングで測定を行えば良いことになります。

```CPP
// Date and time functions using a DS1307 RTC connected via I2C and Wire lib
#include <Wire.h>
#include <stdint.h>
#include <avr/sleep.h>
#include <avr/wdt.h>
#include "RTClib.h"

RTC_PCF8523 rtc;

char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

bool rtcSetup(){
  Serial.println("rtc setup");
  if(! rtc.begin()){
    Serial.println("Could not find RTC module!");
    return false;
  }
  Serial.println("begin ok.");
  
  if(! rtc.initialized()){
    Serial.println("RTC is not Running!");
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }
  
  return true;
}

void setup () {
    Serial.begin(9600);
    Serial.println("Setup Start");
  if(!rtcSetup()){
    Serial.println("RTC false loop");
    while(1);
  }
  Serial.println("rtc ok.");  
}

void loop () {
  float length[3] = {0, 0, 0};
  DateTime time = rtc.now();
  printSerialTime(time);

  for(int i = 0; i < 3; i++){
    length[i]  = lengeAverage(i);
    Serial.print(length[i]);
    Serial.print(" ");
  }
  Serial.println("");

  // SDカード、シリアル通信に必要な時間を確保
  delay(100);
  for(int i = 0; i < 100; i++){
    delayWDT(9);
  }
}

// 10回平均をとる
float lengeAverage(int pin){
  int sum = 0;
  for(int i = 0; i < 10; i++){
    sum += analogRead(pin);
  }

  float ave = (float)sum / 10;
  return ave * 5.0/1024.0;
}

// シリアルモニターに打刻する
void printSerialTime(DateTime time){
    Serial.print(time.year(), DEC);
    Serial.print('/');
    Serial.print(time.month(), DEC);
    Serial.print('/');
    Serial.print(time.day(), DEC);
    Serial.print(" (");
    Serial.print(daysOfTheWeek[time.dayOfTheWeek()]);
    Serial.print(") ");
    Serial.print(time.hour(), DEC);
    Serial.print(':');
    Serial.print(time.minute(), DEC);
    Serial.print(':');
    Serial.print(time.second(), DEC);
    Serial.println(); 
}

void delayWDT(unsigned long t) {        // パワーダウンモードでdelayを実行
  delayWDT_setup(t);                    // ウォッチドッグタイマー割り込み条件設定
  ADCSRA &= ~(1 << ADEN);               // ADENビットをクリアしてADCを停止（120μA節約）
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);  // パワーダウンモード
  sleep_enable();

  sleep_mode();                         // ここでスリープに入る

  sleep_disable();                      // WDTがタイムアップでここから動作再開 
  ADCSRA |= (1 << ADEN);                // ADCの電源をON (|=が!=になっていたバグを修正2014/11/17)

}

void delayWDT_setup(unsigned int ii) {  // ウォッチドッグタイマーをセット。
  // 引数はWDTCSRにセットするWDP0-WDP3の値。設定値と動作時間は概略下記
  // 0=16ms, 1=32ms, 2=64ms, 3=128ms, 4=250ms, 5=500ms
  // 6=1sec, 7=2sec, 8=4sec, 9=8sec
  byte bb;
  if (ii > 9 ){                         // 変な値を排除
    ii = 9;
  }
  bb =ii & 7;                           // 下位3ビットをbbに
  if (ii > 7){                          // 7以上（7.8,9）なら
    bb |= (1 << 5);                     // bbの5ビット目(WDP3)を1にする
  }
  bb |= ( 1 << WDCE );

  MCUSR &= ~(1 << WDRF);                // MCU Status Reg. Watchdog Reset Flag ->0
  // start timed sequence
  WDTCSR |= (1 << WDCE) | (1<<WDE);     // ウォッチドッグ変更許可（WDCEは4サイクルで自動リセット）
  // set new watchdog timeout value
  WDTCSR = bb;                          // 制御レジスタを設定
  WDTCSR |= _BV(WDIE);
} 

ISR(WDT_vect) {                         // WDTがタイムアップした時に実行される処理
  //  wdt_cycle++;                        // 必要ならコメントアウトを外す
}
```
# 防水、振動対策
本体の防水対策は、筐体を用意しそこにArduinoと電池を収めることにしました。ケーブルを出すために一部切り欠きましたが設置時に防水パテで目止めを行い、内部に水が侵入しないようにします。
![本体完成](https://qiita-image-store.s3.amazonaws.com/0/68948/85d1a95f-f675-69a0-950e-f6d8af8f225f.jpeg)

また、赤外線測距センサーは裏面の基盤が露出した~~貧弱~~構成ですので、ホットボンドで基盤の目止めを行いました。(汚いとか言ってはいけません)
![センサー目止め](https://qiita-image-store.s3.amazonaws.com/0/68948/0a1b3aac-a25a-adc6-1b04-51951c407870.jpeg)

列車の走行で発生する振動に対してはArduinoへの接続をすべてXHコネクタにすることでおいそれと抜けないようにしています。

# 最後に
このセンサーは近日中に取り付けを行い、稼働させますが期待するデータが取れることを祈ります。

また、約3年ぶりの電子工作でしたが室内用ロボットと違い、屋外に長期間設置するというのは検討するべきことが圧倒的に多く悩みました。

熱中した製作期間(実装2日)でしたが、ブランクの重さを感じたので精進していきます...

# 完成ソース(Gist)
https://gist.github.com/Nectcom/9c5e85323db2902e160f51fd6b034de0


# 参考文献
## SD Card
http://arms22.blog91.fc2.com/blog-entry-502.html

## WDT and Sleep
http://radiopench.blog96.fc2.com/blog-entry-486.html
http://deviceplus.jp/hobby/entry_056/