2010年12月27日月曜日

定点観測データロガー -7 : 観測用シールド




フィールド設置型の定点観測ロガーを作成する。

< 定点観測器の仕様 >

  1. 計測内容は、計測時刻・気温・湿度・温度・照度とする。
  2. Arduino Pro 328 3.3V 8MHz をスタンドアロンで動作させる。
  3. 単三乾電池4個を電源とし、最低一週間は稼働する省電力設計とする。
  4. 設定した間隔で持続的にデータを記録できるものとする。
  5. 定期的に記録データ媒体と乾電池を交換できる設計とする。
  6. 計測データの保存はSDカードを使用する。


< 定点観測データロガーシールド >

定点観測用: Honeypotシールド + Arduino Pro 3.3V 8MHz
前回の回路をシールド化する。
材料リスト



シールドの回路図

シールド裏側の配線
シールド裏側は画像の通りかなり複雑な配線になる。
最初に配線した時、耐熱ワイヤを使わなかったためにハンダごての熱でビニールが融けて接触不良になり、
結局すべて剥ぎ取ってもう一度配線し直すことになった。
それでも接触不良が不安だったら、エポキシ系樹脂で配線をガチガチに固定する。
コネクタの接続方法はこちらを参考にさせて頂きました。
GNDへはなるべく一点アースにし、回路どうしが干渉しないようにする。
SDソケットは剥がれやすいのでワイヤで固定してある。
多回転半固定ボリューム抵抗はハンダ付けする前にピンを回転させて21KΩにセットしておく。
それと、Arduinoとシールドをつなぐピンのうち、RESETピンは不要なので取り付けないようにする。
取り付けたままでスケッチをアップロードしようとすると、下記のようなエラーが出てアップできない。

avrdude: stk500_getsync(): not in sync: resp=0x00
avrdude: stk500_disable(): protocol error, expect=0x14, resp=0x00

最終的なスケッチは以下のようになった。


Source code: Honeypot ▼

5分間隔で計測し、SDカードにデータを記録する。
温度と照度は一度の計測につき5回センシングしてその平均値を採用する。
1時間に一度、タイムスタンプのブレを修正する。
SDカード記録時のエラーはLEDの点滅回数で表す。
Watchdogとスリープ制御処理も記述する。


上記スケッチをアップロードする前に外部EEPROMをクリアしなければならない。
以下のスケッチをアップロードすると、D13のLEDが点灯する。
そうしたらクリア完了。

Source code: eeprom_I2C_clear ▼


20分後にSDカードを取り出してデータを見てみた。
","(カンマ)区切りで、左からタイムスタンプ、温度1、温度2、温度3、照度、気温、湿度、露点のデータが記録されている。
次回は、屋外に設置して計測をしてみたいと思います。



2010年12月26日日曜日

定点観測データロガー -6 : 省電力テスト


フィールド設置型の定点観測ロガーを作成する。

< 定点観測器の仕様 >
  1. 計測内容は、計測時刻・気温・湿度・温度・照度とする。
  2. Arduino Pro 328 3.3V 8MHz をスタンドアロンで動作させる。
  3. 単三乾電池4個を電源とし、最低一週間は稼働する省電力設計とする。
  4. 設定した間隔で持続的にデータを記録できるものとする。
  5. 定期的に記録データ媒体と乾電池を交換できる設計とする。
  6. 計測データの保存はSDカードを使用する。

< Watchdogとスリープ機能を省いて省電力テストをする >


  • 入力系:温度・湿度・照度
  • 出力系:SDカード・外部EEPROM
ブレッドボード上に回路を設置し、電池ボックスにセットしたエネループ単3電池4個を接続する。
とりあえずWatchdogとスリープを使用しないでどれくらい稼働するのかを試してみた。
外部電源はArduinoボードのVinとGNDに繋ぐ。
エネループ単三電池はフル充電で1.4V 130mAなので4個直列に繋ぐと5.6V 520mAとなるが、Vinから供給された電圧は3.3Vに降圧される。
スケッチからもわかる通り、計測と記録の開始と終了時に、処理確認用のLEDを一度光らせるようにしてある。





/*
 * sketch name   : Honeypot_test
 * summary       : フィールド設置型 省電力定点計測データロガー スリープなしテスト
 * Sensors       : 温度 & 照度 & 気温 & 湿度 & 露点
 * releases      : 2010/11/6
 */

#include <Wire.h>
#include <DateTime.h>
#include <Fat16.h>
#include <Fat16util.h>
#include <Sensirion.h>


////////////////////////////////////////////////////
// default values //////////////////////////////////
////////////////////////////////////////////////////

/* Arduino 基準電圧 */
#define ARDUINO_VCC 3.3

/* 時刻関連 */
// 計測タイムスタンプ初期値(初回セッティング時のみ)
unsigned int year   = 2010;
unsigned int month  = 12;
unsigned int day    = 3;
unsigned int hour   = 0;
unsigned int minute = 0;
unsigned int second = 0;
#define LOGGING_INTERVAL_SEC  300                   // 計測間隔(秒)
#define TIMESTAMP_JST_DEF_SEC 32400                 // 日本標準時(JST)への修正秒数(9時間固定)
unsigned long log_timestamp;                        // 計測時刻タイムスタンプ

/* 外部EEPROMアドレス */
#define EEPROM_DEV_ADDRESS 0x50

/* 温度センサ  type / 0:LM35DZ  1:LM60BIZ  2:LM61CIZ */
unsigned int temp_pins[]          = {0, 1, 2};        // アナログピン0,1,2
unsigned int temp_sensor_type[]   = {2, 2, 2};        // 使用センサタイプ(temp_pins の値に対応)
float        temp_gain[]          = {2, 2, 2};        // Op-amp ゲイン(temp_pins の値に対応)

/* 照度センサ  type / 0:S9648-100 */
unsigned int illumi_pins[]        = {3};              // アナログピン3
unsigned int illumi_sensor_type[] = {0};              // 使用センサタイプ(illumi_pins の値に対応)
#define  ILLMI_MAX_LX ARDUINO_VCC * 1000              // S9648-100使用時の最大計測照度(lx)

/* 気温・湿度・露点  type / SHT71 */
#define SHT_CLOCK_PIN 7                               // CLOCK(デジタルピン)
#define SHT_DATA_PIN  8                               // DATA(デジタルピン)

/* センサのカウント回数 */
#define CNT_TEMP_PINS  sizeof(temp_pins) / sizeof(temp_pins[0])      // 温度センサ
#define CNT_ILLMI_PINS sizeof(illumi_pins) / sizeof(illumi_pins[0])  // 照度センサ
#define CNT_SHT        3                                             // SHTセンサ(センサ数)

/* 計測値格納用 */
float temp_data[CNT_TEMP_PINS];    // 温度データ格納
int   illmi_data[CNT_ILLMI_PINS];  // 照度データ格納
float sht_data[CNT_SHT];           // SHTデータ格納

/* SDカード関連 */
#define SD_ERROR_OK         0
#define SD_ERROR_CARD_INIT  1
#define SD_ERROR_FAT16_INIT 2
#define SD_ERROR_OPEN       3
#define SD_ERROR_WRITE      4
#define SD_ERROR_CLOSE      5
unsigned int sd_error_no;  // SDエラー番号格納
// エラー文字列
char *sd_error[] = {
       "no error.",
       "error about sd card init.",
       "error about sd fat16 init.",
       "error about sd open.",
       "error about sd write.",
       "error about sd close.",
     };
#define SD_LOG_DELIMITER ','             // 記録時のデータ区切り文字 ','(カンマ)
#define LOG_FILE_NAME    "log_data.csv"  // 記録ファイル名

/* 計測時の確認用点滅LED */
#define LED_PIN 9

/* ライブラリのインスタンス化 */
Sensirion     obj_sht      = Sensirion(SHT_DATA_PIN, SHT_CLOCK_PIN);
SdCard        obj_card;
Fat16         obj_file;

// 初回フラグ
boolean setup_flg = true;

////////////////////////////////////////////////////
// setup ///////////////////////////////////////////
////////////////////////////////////////////////////

void setup()
{
  pinMode(LED_PIN, OUTPUT);
  
  // 外部EEPROM(I2Cデバイス)に接続
  Wire.begin(EEPROM_DEV_ADDRESS);
  
  // タイムスタンプ初期化
  log_timestamp = i2cEeprom4BRead(EEPROM_DEV_ADDRESS, 0);                            // 外部EEPROMにタイムスタンプが記録されていたら読み込む
  if (log_timestamp == 0) {                                                          // そうでないならばDatetime設定値(最初のセッティング時のみ)
    DateTime.sync(DateTime.makeTime(second, minute, hour, day, month, year));        // DateTime初期化
    log_timestamp = DateTime.now() - TIMESTAMP_JST_DEF_SEC;                          // 日本標準時(JST) = グリニッジ標準時(GMT) - 9時間
    setup_flg = false;
  }
}


////////////////////////////////////////////////////
// loop ////////////////////////////////////////////
////////////////////////////////////////////////////

void loop()
{
 /* 処理確認用LED ON */
  digitalWrite(LED_PIN, HIGH);
  delay(100);
  digitalWrite(LED_PIN, LOW);
  
  
 /* 時刻関連処理 */
  // タイムスタンプ追加
  if (setup_flg) {
    setup_flg = false;
  } else {
    log_timestamp += LOGGING_INTERVAL_SEC;
  }
  
  // タイムスタンプを外部EEPROMに記録
  i2cEeprom4BWrite(EEPROM_DEV_ADDRESS, 0, log_timestamp);
  
  
 /* 計測処理 */
  // 温度
  for (int i = 0; i < CNT_TEMP_PINS; i++) {
    temp_data[i] = tempCalc(ARDUINO_VCC, temp_sensor_type[i], analogRead(i), temp_gain[i]);
    //Serial.println(temp_data[i]);
  }
  
  // 照度
  for (int j = 0; j < CNT_ILLMI_PINS; j++) {
    int k = CNT_TEMP_PINS + j;
    illmi_data[j] = (analogRead(k) * ILLMI_MAX_LX) / 1024;
    //Serial.println(illmi_data[j]);
  }
  
  // 気温 & 湿度 & 露点
  obj_sht.measure(&sht_data[0], &sht_data[1], &sht_data[2]);
  //Serial.println(sht_data[0]);
  //Serial.println(sht_data[1]);
  //Serial.println(sht_data[2]);
  //Serial.println();
  
 /* SDカードに記録 */
  sd_error_no = dataToSdFileWrite (log_timestamp, temp_data, illmi_data, sht_data, CNT_TEMP_PINS, CNT_ILLMI_PINS, CNT_SHT);
  // エラー時はエラーNo.分だけ点滅
  if (sd_error_no != SD_ERROR_OK) {
    for (int l = 1; l <= sd_error_no; l++) {
      digitalWrite(LED_PIN, HIGH);
      delay(100);
      digitalWrite(LED_PIN, LOW);
    }
    //Serial.println(sd_error[sd_error_no]);
  }
    
    
 /* 処理確認用LED OFF */
  digitalWrite(LED_PIN, HIGH);
  delay(100);
  digitalWrite(LED_PIN, LOW);
  
  
  delay(300000UL);  // 五分間隔
}


////////////////////////////////////////////////////
// function ////////////////////////////////////////
////////////////////////////////////////////////////

/*
 * func name  : dataToSdFileWrite
 * processing : SDカード記録
 * param      : log_timestamp  / タイムスタンプ
 *              temp_data      / 温度データ格納配列
 *              illmi_data     / 照度データ格納配列
 *              sht_data       / SHTデータ格納配列
 *              cnt_temp_pins  / 温度データ数
 *              cnt_illmi_pins / 照度データ数
 *              cnt_sht        / SHTデータ数
 * return     : エラー番号
 */
int dataToSdFileWrite (unsigned long log_timestamp, float *temp_data, int *illmi_data, float *sht_data, int cnt_temp_pins, int cnt_illmi_pins, int cnt_sht)
{
  if (!obj_card.init()) return SD_ERROR_CARD_INIT;
  if (!Fat16::init(&obj_card)) return SD_ERROR_FAT16_INIT;
  // O_CREAT  : ファイルが存在しなかったら作成する
  // O_APPEND : 書き込み前に、ファイル内のデータの最後尾を探す(追記)
  // O_WRITE  : 書き込みのためにファイルを開く
  if (!obj_file.open(LOG_FILE_NAME, O_CREAT | O_APPEND | O_WRITE)) return SD_ERROR_OPEN;
  obj_file.writeError = false;
  
  // タイムスタンプ記録
  obj_file.print(log_timestamp);
  obj_file.print(SD_LOG_DELIMITER);
  //Serial.print("log timestamp : ");
  //Serial.println(log_timestamp);
  
  // 温度記録
  for (int i = 0; i < cnt_temp_pins; i++) {
    obj_file.print(temp_data[i]);
    obj_file.print(SD_LOG_DELIMITER);  // 区切り文字
    if (obj_file.writeError) return SD_ERROR_WRITE;
    //Serial.print("temp");
    //Serial.print(i);
    //Serial.print(" : ");
    //Serial.println(temp_data[i]);
  }
  
  // 照度記録
  for (int j = 0; j < cnt_illmi_pins; j++) {
    obj_file.print(illmi_data[j]);
    obj_file.print(SD_LOG_DELIMITER);  // 区切り文字
    if (obj_file.writeError) return SD_ERROR_WRITE;
    //Serial.print("illmi");
    //Serial.print(j);
    //Serial.print(" : ");
    //Serial.println(illmi_data[j]);
  }
  
  // SHTデータ記録
  for (int k = 0; k < cnt_sht; k++) {
    obj_file.print(sht_data[k]);
    // 最後のデータだったら改行
    if (k == (cnt_sht - 1)) {
      obj_file.println();
    // それ以外は区切り文字
    } else {
      obj_file.print(SD_LOG_DELIMITER);
    }
    if (obj_file.writeError) return SD_ERROR_WRITE;
    //Serial.print("sht");
    //Serial.print(k);
    //Serial.print(" : ");
    //Serial.println(sht_data[k]);
  }
  
  //Serial.println();
  
  if (!obj_file.close()) return SD_ERROR_CLOSE;
  
  return SD_ERROR_OK;
}

/*
 * func name  : i2cEeprom4BWrite
 * processing : 4byte値をI2C外部EEPROMに書き出す
 * param      : deviceAddress      / I2C外部EEPROMのデバイスアドレス
 *              startMemoryAddress / EEPROM上の書き込み開始アドレス
 *              longParam          / 書き込む4byteのデータ
 * summary    : デバイスの0番目の番地 / 32〜25bit
 *                        1番目の番地 / 24〜17bit
 *                        2番目の番地 / 16〜9bit
 *                        3番目の番地 / 8〜1bit
 * return     : 
*/
void i2cEeprom4BWrite(int deviceAddress, unsigned long startMemoryAddress, long longParam)
{
  Wire.beginTransmission(deviceAddress);
  Wire.send((int)(startMemoryAddress >> 8));
  Wire.send((int)(startMemoryAddress & 0xFF));
  
  byte byteArray[sizeof(long)] = {
         (byte)(longParam >> 24),
         (byte)(longParam >> 16),
         (byte)(longParam >> 8),
         (byte)(longParam >> 0)
       };
  
  for (int i = 0; i < sizeof(long); i++) {
    Wire.send(byteArray[i]);
  }
  
  Wire.endTransmission();
  delay(5);
}

/*
 * func name  : i2cEeprom4BRead
 * processing : 4byte値をI2C外部EEPROMから読み出す
 * param      : deviceAddress      / I2C外部EEPROMのデバイスアドレス
 *              startMemoryAddress / EEPROM上の読み込み開始アドレス
 * return     : return_long        / 読み込んだ4byteデータ 
*/
long i2cEeprom4BRead(int deviceAddress, unsigned long startMemoryAddress)
{
  Wire.beginTransmission(deviceAddress);
  Wire.send((int)(startMemoryAddress >> 8));
  Wire.send((int)(startMemoryAddress & 0xFF));
  Wire.endTransmission();
  Wire.requestFrom(deviceAddress, sizeof(long));
  delay(5);
  
  long received_long;
  long return_long = 0;
  int  cnt = sizeof(long) - 1;
  
  for (int i = 0; i <= cnt; i++) {
    if (Wire.available()) {
      received_long = Wire.receive();
      return_long  |= (received_long << (8 * (cnt - i)));
    }
  }

  return return_long;
}

/*
 * func name  : tempCalc
 * processing : 温度センサ処理
 * param      : arduinoVcc / Arduino 基準電圧
 *              sensor     / 0 : LM35DZ
 *                           1 : LM60BIZ
 *                           2 : LM61CIZ
 *              analogVal  / Analog値(0-1023)
 *              gain       / Op-amp ゲイン
 * return     : temp       / 計測温度(℃)
 */
float tempCalc (float arduinoVcc, int sensor, int analogVal, float gain)
{
  float dc_offset;  // DCオフセット電圧(V)
  float factor;     // 温度係数(V/1℃)
  
  switch (sensor) {
    
    // LM35DZ
    // DCオフセット 0V
    // +10mV/℃
    case 0:
      dc_offset = 0;
      factor    = 0.01;
      break;
      
    // LM60BIZ
    // DCオフセット 424mV
    // +6.25mV/℃
    case 1:
      dc_offset = 0.424;
      factor    = 0.00625;
      break;
      
    // LM61CIZ
    // DCオフセット 600mV
    // +10mV/℃
    case 2:
      dc_offset = 0.6;
      factor    = 0.01;
      break;
      
    define:
      return 0;
      break;
  }
  
  return (((arduinoVcc * analogVal) / 1023) - (dc_offset * gain)) / (factor * gain);
}



スケッチをアップロードし電池ボックスの電源をONにすると、稼働し始める。
うっかりしていて外部EEPROMにミス入力したままのタイムスタンプ情報が残っていたのか、
10日後に取り出したSDカードのデータを見てみると1行目のタイムスタンプが「1293359672(2010/12/26 19:34:32)」となっていた。
スケッチをアップする前に外部EEPROMを空にすることを忘れていたためだが、特に問題はない。
データは一回の計測につき","(カンマ)区切り1行で記録される。
左から「タイムスタンプ」「温度1」「温度2」「温度3」「照度」「気温」「湿度」「露点」となっている。


こちらはログファイルの末尾である。
「1294213772(2011/01/05 16:49:32)」となっている。
5分で一度、つまり1日288行のデータが記録されるので、2851行分のデータは妥当な量である。


待つのに飽きたし目的の1週間を過ぎたので10日で取り出したけれど、まだまだいけそうである。
100円ショップで購入した電池残量チェッカーによると、まだ3/4ほど電力が残っている。


10日は確実に稼働することがわかったけれど、
Watchdogとスリープ制御をすることによってどれだけの期間稼働するのかは想像がつかないし実験などしていられない。

次回は回路をシールド化してみます。