2010年11月28日日曜日

パーマカルチャー



Perma Culutre Center Japan :   藤野のパーマカルチャー農園


パーマカルチャーとは、人間にとっての永続的な循環型生活環境を作り出す為のデザイン体系のことである。
環境負荷をなるべく減らして自給自足生活を営むための方法?だという。
こちらの本『パーマカルチャー―農的暮らしの永久デザイン』を読んでみると、農業を始めとして、
家屋のレイアウト、周囲の建築物・自然物の配置、資材の選別方法、エネルギー循環、水の確保、狩猟採集、食料の加工と保存、
有用生物の飼育、排泄物のリサイクル、微生物利用...はては地形の造成など、取り扱う事象は多岐にわたるようである。
たんなる田舎暮らしの指南ではなく、科学的に循環型自給自足を追求しているところが面白そう。

ニワトリの機能分析。

必要とするもの

  • 小屋
  • 砂利
  • 空気
  • 食べ物
  • 仲間のニワトリ

生産性と行動

  • 羽毛
  • 鶏糞
  • メタンガスを出す
  • 炭酸ガスを出す
  • 餌をあさるために地面を引っ掻く
  • 飛ぶ
  • ケンカをする

品種固有の特徴

  • 血統
  • 寒暑耐性
これらの特徴を、人間が楽をできるようにうまく利用する。
  • 後述するチキントラクターで、畑の区画の余計な雑草をすべて除去する。
  • 鶏糞は畑の肥料となる。
  • 育苗用の温室内に放してメタンガスと炭酸ガス、体温で室内を温める。
  • 肉と卵を提供してくれる。
  • ニワトリ小屋は住居と畑の間に設置する。行きは鶏糞を畑に持って行き、帰りは農作物の切れ端を与える。
少しだけ抜粋してみた。
この他にも、

  • 夏と冬の日照角度差を利用して、夏は室内を冷やし、冬は日差しを室内に取り入れる窓枠。
  • 落ち葉などが入らないような屋根の雨樋加工と雨水の集め方。
  • 防風、通気を考えた植林。
  • シャワーの排水、調理の火などからの廃熱を室内に循環させる方法。
など、いろんな事例が載っている。

上記を例として、観察することによって得たモノの構成要素を、他のモノの構成要素とうまく関連づけることが基本である。
そしてあるモノのアウトプットが他のモノのインプットとなるようにものの配置を変え、既存のものを作り替えていくという感じ。
特徴的なのは、人間の生活全般に関わる事すべてのアウトプット〜インプットの流れを循環させようとしていることである。
その中には、当然のこととして廃棄物・排泄物のリサイクル利用が含まれている。
特定の分野の技術や能力を生かせる場所(会社等)で同系統の仕事をやっていくことが都会的な合理性であると定義すると、
パーマカルチャーは逆の意味で合理的な思想を持っているようである。

創立者はタスマニア島出身の漁師であるビル・モリソン氏とデビット・ホルムグレン氏。
オーストラリアでは学校の授業の単位にパーマカルチャーが組まれている。
しかし、もとはといえばビル・モリソン氏のパーマカルチャー発想の原点となったのは、西洋型の非循環型単一農業に疑問を感じて
日本を訪れた時に見た里山文化であったという。




パーマカルチャーセンタージャパン

週末の二日間、パーマカルチャーセンタージャパンの体験講座に参加した。
JR中央線の藤野駅から車で10数分の山の中にある古民家で、ここで年間を通じてパーマカルチャーの講座が催されている。
高度400mにあるので、とても気温が低い。
PCCJ代表理事の設楽清和さんとスタッフのYさん、Gさんが迎えてくれた。
案内していただいた施設には様々な工夫が凝らしてある。
土釜の上に作られた温室、コンポストトイレ、薪で熱する暖炉、廃熱循環の床暖房、など。
農園の散歩と夕食をごちそうになった後、ニュージーランドのレインボーバレーファームのドキュメンタリ映像が上映される。



朝の散歩の時にみかけた神社。
空気が透きとおっていて気持ちがよい。
この神社の舞台は地元のアーティストのライブにも利用されるという。
歌手のUAさんも藤野在住で、たまに出演するとかしないとか。



PCCJのキッチンには保存用に加工された食物を入れたビンが壁一面に並んでいた。


紅葉が美しい。
藤野は湖と里山の街。
この時期には街は紅葉の山々に囲まれる。



PCCJのパーマカルチャー農園。
変った形をした畑が並ぶ。
一般的な直線の形をした畝は見当たらない。
パーマカルチャーの農園では、畝に曲線を多様する。
これにより、畝と通路に細かい境界線が多数生まれ、微気象の変化に寄って様々な植生が生まれる。

すべての通路に段ボールを敷き、近所の木工所でもらってきたオガクズを盛ってある。
歩きやすくなるとともに、段ボールを好んで食べるミミズの働きで通路そのものが堆肥となっていく。



移動式チキントラクター用の囲い。
といっても電動ではない。
この中にニワトリ数羽を二日間入れておくと、囲いの中のすべての雑草を食べ尽くし、餌をあさって土をほじくり返し、耕してくれる。
糞はそのまま肥料として利用する。



写真では解りにくいが、畝の周りには一定間隔でネギを植えてある。
地中のセンチュウを抑止する働きがある。
マルチとして畝全体に10数センチほどの厚さで落葉が盛ってある。
土は微生物の塊なので、基本的に紫外線にはさらさない方がよいらしい。
また、地中の水分は陽が昇るとともに水蒸気となってすごい勢いで蒸発していく。
落葉マルチは水分蒸発を防ぐ役割もある。

ふかふかの土を手にとって匂いを嗅いでみると、まさしく森の匂いがした。
これは土の中の有効菌が発する匂いである。
もとは粘土質の土だったそうだけど、7年ほど工夫を凝らして土つくりを続けた結果、このような上質の土に変っていったという。



講座の塾生が作った保存室。
材料はとても安価。
土嚢とモルタルでできていて、室内の温度を一定に保つ。
天井には白い屋根板が張ってあり、日中の太陽光を跳ね返す。
入り口は北を向いており、日光が室内に入りにくくなっている。



夕食の後にパーマカルチャーについてのディスカッション。
理事の設楽さんとスタッフのYさんはビル・モリソン氏に師事していたこともあるそうだ。
とてもきさくな方達で、酒を呑みつついろんな事を話した。

パーマカルチャーをどのように生活に取り入れるかはやる人次第で、決まった回答は用意されていない。
地方の山奥に籠って本格的にやる人。
マンションのベランダで完全有機栽培を目指す人。

常にモノを循環させていく生活の中で唯一の永続的なるものは太陽エネルギーであり、
それによって、地球に気象が生まれ、大気が循環し、様々な植生が生まれ、多様性が維持される。

ソ連解体時にロシアがパニックに陥らなかった理由は、首相から一般家庭の家のほとんどに農地があったからだとか。
外部経済に依存した生活形態は安定には遠いことであるとか。

自分の生活を振り返ってみると、なにかしらの外部要素に依存していないものを探す方が困難である。
太陽と空気ぐらいだろうか。

ビル・モリソン氏についての話も出た。
ビル氏いわく、

パーマカルチャーの究極の目標は世界中を森にすることだ。

とのこと。
その言葉はとてもアナーキーである。
ビル氏本人はそれを体現しているような方で、いつも裸足で歩いているので足の裏の表皮が数センチにもなってしまって、
人間の腕くらいの太さの木の棒をひと蹴りで割ってしまうらしい。
野人のようなお方である。

*

人工物に囲まれた生活空間の中では、自給自足などイメージするには余りにも遠い。
農業技術などなにも知らないのに、広い農地の前で立ち尽くしている自分。
畑を荒らすイノシシと奮闘している自分。
コンポストトイレから堆肥化した排泄物をかきだしている自分。
食べるものといえばコンビニ、ではなくて畑からという発想をしている自分。
月10万円で暮らしている自分。

毎日なにかしらの予想できない新しいトラブルが発生し、なんとか乗り切ろうとしていると、
1日があっという間に過ぎてしまうらしい。
しかし必要なこと自分のためだけに使っているので、精神的なストレスはあまりなさそうである。
もし自分がやるとしたら、そのような困難を乗り越える事ができるだろうか。
やる人は、可能かどうかの結論を出す前に踏み出すんだろうな。



2010年11月26日金曜日

定点観測データロガー -5 : タイムスタンプ情報の持続テスト


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

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

< SDカードとEEPROMを使い、タイムスタンプを持続させる >


ArduinoはPCのような持続してカウントする内蔵時計を持たない。
電源を切ると消えてしまう時刻情報を維持させるために、こちらにはGPSを利用した高精度な時計の例がある。
衛星に搭載された原子時計の時刻情報を使うので、通信のために数100msほどの遅れはでるものの、
リアルタイムの時刻情報をその都度取得することができる。
わくわくするような面白いプロジェクトだけど、アウトドアに設置することを考えると電力消費が心配だった。
それに、今後の展開としてこの定点観測ロガーを量産できるようにコストを押さえていきたい。
苦肉の策かもしれないけれど、I2C通信を利用した外部EEPROMを使用し、時刻情報を保存することにした。

以下の回路はこちらこちらを組み合わせたものである。





/*
 * sketch name   : logging_timestamp
 * summary       : 指定のスリープ間隔ごとにタイムスタンプをSDカードに記録する
 *                 SDカードと電池は取り替える事を前提とするため、タイムスタンプ情報は外部EEPROMに保存する
 */

#include <Wire.h>
#include <DateTime.h>
#include <Fat16.h>
#include <Fat16util.h>
#include <Watchdog.h>
#include <Sleep.h>

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

/* 時刻関連 */
// 計測タイムスタンプ初期値(初回セッティング時のみ)
unsigned int year   = 2010;
unsigned int month  = 11;
unsigned int day    = 27;
unsigned int hour   = 0;
unsigned int minute = 0;
unsigned int second = 0;

#define LOGGING_INTERVAL_SEC 10           // 計測間隔(秒)
#define RETOUCH_INTERVAL_SEC 60          // 計測間隔によるタイムスタンプのずれの修正間隔(秒) ※必ず LOGGING_INTERVAL_SEC < RETOUCH_INTERVAL_SEC
#define WATCHDOG_TIMER_SLEEP_INTERVAL 1   // スリープ間隔(1秒固定)
unsigned long log_timestamp;              // 計測時刻タイムスタンプ
unsigned long time1;                      // 計測開始時刻(ms)
unsigned long time2;                      // 計測終了時刻(ms)
unsigned long Integrated_time;            // 計測間隔の集積
unsigned int  cnt_interval;               // スリープカウント
unsigned int  cnt_interval_full;          // 計測までのスリープカウント数
unsigned long retouch_cnt_interval_full;  // タイムスタンプ修正までのカウント(秒)

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

/* 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 LOG_FILE_NAME "log_data.csv"  // 記録ファイル名

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

/* ライブラリのインスタンス化 */
WatchdogClass obj_watchdog = WatchdogClass();
SdCard        obj_card;
Fat16         obj_file;

void setup(void) {
  Serial.begin(9600);
  
  pinMode(LED_PIN, OUTPUT);
  
  // 外部EEPROM(I2Cデバイス)に接続
  Wire.begin(EEPROM_DEV_ADDRESS);
  
  // Watchdogとスリープの設定
  obj_watchdog.systemResetEnable(false);                                             // スリープからの自動復帰時にスケッチがリセットされないように設定
  obj_watchdog.enable(WatchdogClass::TimeOut1s);                                     // スリープ間隔を1秒間にセット
  cnt_interval_full         = LOGGING_INTERVAL_SEC / WATCHDOG_TIMER_SLEEP_INTERVAL;  // 1秒間に設定されたスリープの繰り返し回数
  cnt_interval              = 0;                                                     // 計測間隔カウンタ初期化
  retouch_cnt_interval_full = 0;                                                     // タイムスタンプ修正用カウンタ初期化
  
  // タイムスタンプ初期化
  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();
  }
}


void loop()
{
  // スリープ開始から設定間隔で自動復帰
  
  time1 = micros();
  
  // 計測と記録
  if ((cnt_interval >= cnt_interval_full)) {
    
   /* 処理確認用LED ON */
    digitalWrite(LED_PIN, HIGH);
    
    
   /* 時刻関連処理 */
    // タイムスタンプ追加
    log_timestamp += LOGGING_INTERVAL_SEC;
    
    // タイムスタンプを外部EEPROMに記録
    i2cEeprom4BWrite(EEPROM_DEV_ADDRESS, 0, log_timestamp);
    
    // スリープカウント初期化
    cnt_interval = 0;
    
    // タイムスタンプ修正までのカウントを加える
    retouch_cnt_interval_full += LOGGING_INTERVAL_SEC;
    
    
   /* SDカードに記録 */
    sd_error_no = dataToSdFileWrite (log_timestamp);
    if (sd_error_no != SD_ERROR_OK) {
      Serial.println(sd_error[sd_error_no]);
    }
    
    
   /* 処理確認用LED OFF */
    digitalWrite(LED_PIN, LOW);
    
  // カウントアップ
  } else {
    
    cnt_interval++;
  }
  
  // 計測間隔による時刻カウントのずれの修正
  time2            = micros();
  Integrated_time += (time2 - time1);
  if (retouch_cnt_interval_full >= RETOUCH_INTERVAL_SEC) {
    cnt_interval             += (int)(Integrated_time / 1000000) / WATCHDOG_TIMER_SLEEP_INTERVAL;
    retouch_cnt_interval_full = 0;
    Integrated_time           = 0;
  }
  
  // スリープ開始
  WatchdogClass::timerReset();
  SleepClass::powerDown();
}


/*
 * func name  : dataToSdFileWrite
 * processing : SDカード記録
 * param      : in_sd    / データ格納配列
 *              cnt_data / データ数
 * return     : エラー番号
 */
int dataToSdFileWrite (unsigned long log_timestamp)
{
  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.println();
  
  if (obj_file.writeError) return SD_ERROR_WRITE;
  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;
}



今回のスケッチはちょっと長め。
まず、WatchdogとSleepを使用して省電力をはかる。
Sleep間隔は固定の1秒とし、計測間隔(今回はセンシングはなし)を10秒とする。
つまり、単純計算で10回スリープを繰り返した後に計測することになる。
しかし、計測とSDカード・EEPROMへのデータ書き込み処理時間が結構かかることを想定し、
スリープターンごとに処理時間を集積しつつ60秒に一回タイムスタンプの修正処理を行っている。
タイムスタンプ修正後の直近の計測間隔は、集積された計測処理分だけ短くなることになる。
強引だけど、このようにしてタイムスタンプの帳尻を合わせている。
計測とデータ保存期間はLEDを点灯させる。
ある程度うまくいったら一度電源をOFFにし、再び起動させて、EEPROMに保存されたタイムスタンプが
続けて使用されるかどうか確かめる。

以下は一度電源をOFFにして、取り出したSDカードに保存されたタイムスタンプ情報。


SDカード情報を削除し、セットして再び起動させる。



電源をOFFにし、再び取り出したタイムスタンプ。
EEPROMから取り出したデータを引き続き使用することにより、前回からの持続したタイムスタンプが続いている。


この方法の欠点は、電源をOFFにして電池とSDカードを交換し、再び電源を入れるまでの間隔分、
実際の時刻情報がずれてしまうことである。
コストを下げた分、こういうところにしわ寄せがくる。
こればかりは交換時間をなるべく手早くやるしかない。
数週間に数秒のずれなのであまり気にする事もないのかもしれない。

次回は、センサも含めたすべてをセッティングして電力がどれくらい持つのかを実験する。

2010年11月25日木曜日

定点観測データロガー -4 : SDカードにデータを保存する


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

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

< SDカードへの書き込み >



カードスロットは秋月電子で購入。
このほかに、SDカード(2GBで十分)、SDカードリーダーも必要。

SDカードの書き込みについては、そのままずばりやりたいことの記事があったので活用させて頂きました。
下記のリンクを参考にさせていただきました。ありがとうございます。
エレキジャック・フィジカルコンピューティング Arduinoで何でも制御 SDカードを接続するアーカイブ


ログファイルは"LOG_DATA.CSV"という名前で保存している。
今回はタイムスタンプ情報のみだけど、温度や湿度などの各種データは、
","(カンマ)区切りで1列に並べて保存することを想定している。
その上で拡張子を「.csv」で保存すると、カンマ区切りのデータをエクセルやopenOfficeで開いた時に
セルの中に各種データがきれいにおさまる。

SD関連のエラー処理だけが困った。
リンク先のスケッチではエラーが起こったらシリアル出力するようにしているけど、
本番の定点観測データロガーではシリアル出力はしない。
かといって、エラーログを保存しようにもSDカード自体のエラーなのだから保存先がない。
EEPROMはタイムスタンプ保存専用なので論外。
本番ではエラーは起こらないという想定で無理矢理やることにする。
確認なので、以下のスケッチではエラーをシリアル出力している。



/*
 * sketch name   : timestamp_write_sd
 * summary       : SDカードへの書き込み
 */

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

/* 時刻関連 */
unsigned int year   = 2010;
unsigned int month  = 11;
unsigned int day    = 20;
unsigned int hour   = 21;
unsigned int minute = 15;
unsigned int second = 0;
unsigned long log_timestamp;         // 計測時刻タイムスタンプ
#define TIMESTAMP_JST_DEF_SEC 32400  // 日本標準時(JST)への修正秒数(9時間固定)

/* 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 LOG_FILE_NAME "log_data.csv"  // 記録ファイル名

/* ライブラリのインスタンス化 */
SdCard obj_card;
Fat16  obj_file;

void setup()
{
  Serial.begin(9600);
  DateTime.sync(DateTime.makeTime(second, minute, hour, day, month, year));  // DateTime初期化
}

void loop()
{
  log_timestamp = DateTime.now() - TIMESTAMP_JST_DEF_SEC;  // 日本標準時(JST) = グリニッジ標準時(GMT) - 9時間
  
  sd_error_no = dataToSdFileWrite (log_timestamp);
  if (sd_error_no != SD_ERROR_OK) {
    Serial.println(sd_error[sd_error_no]);
  }
  
  delay(5000);
}

/*
 * func name  : dataToSdFileWrite
 * processing : SDカード記録
 * param      : log_timestamp  / タイムスタンプ
 * return     : エラー番号
 */
int dataToSdFileWrite (unsigned long log_timestamp)
{
  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.println();
  if (obj_file.writeError) return SD_ERROR_WRITE;
  if (!obj_file.close()) return SD_ERROR_CLOSE;
  
  return SD_ERROR_OK;
}



適当なタイミングでプログラムを止めて、SDカードの内容をリーダーで見てみる。


delay()関数で5秒おきのタイムスタンプ情報が記録されている。

2010年11月24日水曜日

定点観測データロガー -3 : 気温・湿度・温度・照度計測


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

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


< 温度センサ >



温度センサをセットする時はまず測定温度範囲を決める。
温度センサは計測した温度によって出力する電圧が変化する素子で、
その電圧はAD変換されて0〜1023の値として取得できる。
今回はArduino Pro 328 3.3V を使用するので、0V〜3.3V = 0〜1023となる。
計算しやすいので、100℃までの計測範囲とする。
そうすると、オペアンプのゲインは

LM35DZ
1個100円(秋月電子)
精度 ±1.0℃(最大)
温度係数 +10mV/℃
オフセット電圧 0V
測定温度範囲0〜100℃
100℃の時、100 × 0.01V = 1V
AD値をフルレンジ(0〜1023)で使用する場合のゲイン : 3.3V / 1V = 3.3

LM60BIZ
1個100円(秋月電子)
精度 ±4.0℃
温度係数 +6.25mV/℃
オフセット電圧 424mV
測定温度範囲-40〜125℃
100℃の時、100 × 0.00625V + 0.424V = 1.049V
AD値をフルレンジ(0〜1023)で使用する場合のゲイン : 3.3V / 1.049V = 約3.1

LM61CIZ
4個200円(秋月電子)
精度 ±4.0℃
温度係数 +10mV/℃
オフセット電圧 600mV
測定温度範囲-30〜100℃
100℃の時、100 × 0.01V + 0.6V = 1.6V
AD値をフルレンジ(0〜1023)で使用する場合のゲイン : 3.3V / 1.6V = 約2

となる。
詳しくはこちら




/*
 * sketch name   : temp_measure
 * summary       : 三種類の温度センサを使用して温度計測する
 */

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

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

/* センサのカウント回数 */
#define CNT_TEMP_PINS sizeof(temp_pins) / sizeof(temp_pins[0])

float temp;

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  Serial.println("**********************");
  
  for (int i = 0; i < CNT_TEMP_PINS; i++) {
    temp = tempCalc(ARDUINO_VCC, temp_sensor_type[i], analogRead(i), temp_gain[i]);
    
    Serial.print("sensor type");
    Serial.print(i);
    Serial.print(" : ");
    Serial.println(temp_sensor_type[i]);
    Serial.print("temperature : ");
    Serial.println(temp);
  }
  
  Serial.println("**********************");
  Serial.println();
  delay(5000);
}

/*
 * func name  : tempCalc
 * processing : 温度センサ処理
 * param      : arduino_vcc / Arduino 基準電圧
 *              sensor_type / 0 : LM35DZ
 *                            1 : LM60BIZ
 *                            2 : LM61CIZ
 *              analog_val  / Analog値(0-1023)
 *              gain        / Op-amp ゲイン
 * return     : temp        / 計測温度(℃)
 */
float tempCalc (float arduino_vcc, int sensor_type, int analog_val, float gain)
{
  float dc_offset;  // DCオフセット電圧(V)
  float factor;     // 温度係数(V/1℃)
  
  switch (sensor_type) {
    
    // 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 (((arduino_vcc * analog_val) / 1024) - (dc_offset * gain)) / (factor * gain);
}



なんだか計測温度が微妙にずれているけれど、これくらいの誤差はしょうがない。
精度はLM35DZが一番いいみたいだけど、氷点下まで計測したいので、LM60BIZを使用することにする。


< 照度センサ >


このフォトダイオードについてはセンサ活用の素 (1)の回路をそのまま使用することにした。
これによると3300ルクスまで計測できることになるが、0〜1023のAD変換値でしか取得できないので精度はかなり落ちる。
照度は暗闇から真夏の直射日光まで、0〜約10000と幅がありすぎる。
1024段階の値で測定しようとするのは仕様的に無理があるけど、
正確なルクスを得ようというよりは日照時間が知りたいだけなので、このくらいアバウトでいい。




/*
 * sketch name   : illmi_measure
 * summary       : フォトダイオード S9648-100 を使用して照度計測する
 */

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

/* 照度センサ  type / 0:S9648-100 */
unsigned int illumi_pin = 3;                  // アナログピン3
#define      ILLMI_MAX_LX ARDUINO_VCC * 1000  // S9648-100使用時の最大計測照度(lx)

float illmi;

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  illmi = (analogRead(illumi_pin) * ILLMI_MAX_LX) / 1024;
  
  Serial.print("Illumination : ");
  Serial.println(illmi);
  Serial.println();
  
  delay(5000);
}






< SHTセンサ >

センサは以前と同じSHT-71を使用する。
Sensirionのライブラリをこちらからダウンロードして解凍後、/Arduino/library/ 以下に設置する。
スケッチはサンプルのものがそのまま使用できるが、CLOCKピンとDATAピンはそれぞれデジタルピン7と8に変えてある。
気温と湿度だけではなく、露点も計測できるようなのでついでに計測しておく。



/*
 * Query a SHT10 temperature and humidity sensor
 *
 * A simple example that queries the sensor every 3 seconds
 * and communicates the result over a serial connection.
 * Error handling is omitted in this example.
 */

#include "Sensirion.h"

#define DATAPIN  8
#define CLOCKPIN 7

float temperature;
float humidity;
float dewpoint;

Sensirion tempSensor = Sensirion(DATAPIN, CLOCKPIN);

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  tempSensor.measure(&temperature, &humidity, &dewpoint);

  Serial.print("Temperature: ");
  serialPrintFloat(temperature);
  Serial.print(" C, Humidity: ");
  serialPrintFloat(humidity);
  Serial.print(" %, Dewpoint: ");
  serialPrintFloat(dewpoint);
  Serial.println(" C");
  
  delay(3000);  
}

void serialPrintFloat(float f){
  Serial.print((int)f);
  Serial.print(".");
  int decplace = (f - (int)f) * 100;
  Serial.print(abs(decplace));
}

2010年11月23日火曜日

定点観測データロガー -2 : EEPROMにデータを保存する


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

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


< 持続した時刻情報の保存 >

前回のWatchdogとスリープ制御によって、定点観測ロガーの電力補給問題はなんとかなりそうである。
まだ実験途中だけど、スリープと乾電池4個を使えば最低でも1週間は持つだろう。

もう一つの問題は持続した正確な時刻情報の記録だ。
電池とデータ保存用のSDカードを交換する時には当然電源を切らなくてはならないけれど、
Arduinoには電源を切ってもカウントを続けるPCのような内蔵時計の機能はない。
しかしデータの計測にはある程度正確な時刻情報がともなっていないと意味がないのである。
例えばDatetimeライブラリを使用するとして、setup()関数で日付をセットしても、
電源を切った途端に日付情報は消えてしまう。
電池とSDカードを交換した後に、現在の時刻情報をもう一度スケッチにセットしてArduinoにアップロードしなくてはならない。
フィールドにPCを持ち歩くのは面倒なのでやりたくない。

解決方法として、ArduinoのEEPROMメモリ領域に時刻情報を記録することにした。
EEPROMはArduinoの電源を切っても消えないメモリ領域である(Arduino 日本語リファレンス EEPROMの項を参照)。
Arduino Pro 328 3.3V 8MHzの場合はATmega328チップなので、1024byteの情報を読み書きできる。
使い方は以下のようになる。
  1. SDカードに環境情報を記録するタイミングでEEPROMにも時刻情報のみを記録する。
  2. 電源を切る(電池とSDカード交換)。
  3. 起動。
  4. EEPROM領域に時刻情報が記録されていればそれを使い、記録されていなければDatetimeライブラリで初期化した時刻情報を使う(初めて畑にロガーをセットする時のみ)。setup()関数内に処理を書いておけばいい。

時刻情報はタイムスタンプとして記録する。
タイムスタンプとは1970年1月1日00:00:00からの経過秒数のことで、たいていの言語で簡単に日付時刻に変換できるので、
コンピュータの世界では時刻情報として利用されている。
例えば2010年11月23日14時8分40秒(日本標準時)のタイムスタンプは「1290456520」といった感じ。

UNIXタイムスタンプ変換ツール

タイムスタンプはlong型のデータ情報で、EEPROM領域を4byte使用する。
データ書き換え時に古いタイムスタンプ情報を上書きしてしまえばいいので、1024byteのメモリ容量でも十分である。


< タイムスタンプ情報(4 Byte値)をEEPROMに書き込み、読み込む >



/*
 * sketch name   : eeprom_r_w_4B
 * summary       : タイムスタンプ情報(4 Byte値)をEEPROMに書き込み、読み込む
 */

#include <EEPROM.h>
#include <DateTime.h>

// log start date
int year   = 2010;
int month  = 11;
int day    = 20;
int hour   = 18;
int minute = 53;
int second = 0;

long log_timestamp;

void setup(void) {
  Serial.begin(9600);
  DateTime.sync(DateTime.makeTime(second, minute, hour, day, month, year));
}

void loop() {
  log_timestamp = DateTime.now();
  Serial.print(" write timestamp / ");
  Serial.println(log_timestamp, DEC);
  
  eep4Bwrite(0, log_timestamp);  // 書き込み
  log_timestamp = eep4Bread(0);  // 読み込み
  
  Serial.print(" read timestamp / ");
  Serial.println(log_timestamp, DEC);
  Serial.println();
  
  delay(3000);
}

/*
 * func name  : eep4Bwrite
 * processing : 4byte値をEEPROMに書き出す
 * param      : int  address / 書き出し開始EEPROMアドレス
 *              long param   / 書き出す値
 * return     : 
 */
void eep4Bwrite(int address, long param)
{
  int  l_shift;     // 左シフトbit数
  long inleft;
  byte in_bytes;    // EEPROM格納用byte値
  int  eep_address; // EEPROM上のアドレス
  int  i;
  for (i = 0; i <= 3; i++) {
    l_shift     = 8 * (3 - i);
    inleft      = param  << l_shift;     // 左にシフト
    in_bytes    = inleft >> 24;          // 右にシフト
    eep_address = address + i;
    EEPROM.write(eep_address, in_bytes);  // 書き込み
    
    //デバック用
//    Serial.print(" eeprom address : ");
//    Serial.print(eep_address,DEC);
//    Serial.print(" / byte : ");
//    Serial.println(in_bytes,BIN);
//    Serial.println(" ");
  }
}

/*
 * func name  : eep4Bread
 * processing : 4byte値をEEPROMから読み出す
 * param      : int  address   / 読み出し開始EEPROMアドレス
 * return     : long ret_param / 4byte値
 */
long eep4Bread(int address)
{
  int  eep_address;   // EEPROM上のアドレス
  long inleft;        // EEPROM格納用byte値
  int  l_shift;       // 左シフトbit数
  long in_bytes;      // byte値格納用
  long ret_param = 0; // リターン値初期化
  int  i;
  for (i = 0; i <= 3; i++) {
    eep_address = address + i;
    inleft      = EEPROM.read(eep_address);  // 読み込み
    l_shift     = 8 * i;
    in_bytes    = inleft << l_shift;
    ret_param  |= in_bytes;
    
    //デバック用
//    Serial.print(" read test eeprom address : ");
//    Serial.print(eep_address,DEC);
//    Serial.print(" / byte : ");
//    Serial.println(ret_param,BIN);
//    Serial.println(" ");
  }
  return ret_param;
}


Arduino Pro 328 3.3V 8MHzにスケッチをアップロードするには3.3V動作Arduino製品向け小型USB-シリアルアダプタとUSBケーブル(Aオス-miniBタイプ)が必要である。
スケッチの実行は、Arduino Pro 328 3.3V 8MHzとPCをただ繋ぐだけである。

シリアルモニタで確認



スケッチは、以下のリンク先を参考にさせて頂きました。ありがとうございます。
Reflection of my mind arduinoのEEPROMテスト

リンク先のスケッチを理解してEEPROM書き込み関数を作成するにあたってビット演算を少し勉強する必要があった。
タイムスタンプを格納しているunsigned long型というのは符号なし整数型の4byteのデータ型だけど、
まずは"4byte"とはなんだ?というレベルからである。
およばずながら、理解できた範囲で確認してみる。

正の整数を格納するデータ型は

byte型(1byte)
unsigned int型(2byte)
unsigned long型(4byte)

がある。
これは2進数であらわすと、

■ byte型(10進数では255 〜 0、つまり2の8乗〜2の0乗。1byte。8bit。)
11111111 〜 00000000

■ unsigned int型(10進数では65,535 〜 0、つまり2の16乗〜2の0乗。2byte。16bit。)
11111111 11111111 〜 00000000 00000000

■ unsigned long型(10進数では4,294,967,2952 〜 0、つまり2の32乗〜2の0乗。4byte。32bit。)
11111111 11111111 11111111 11111111 〜 00000000 00000000 00000000 00000000

となる。

EEPROM.write()メソッドは1byteづつしか書き込めない。
ということは、タイムスタンプ型のunsigned long型は4回に分けて書き込む必要があるということになる。

eep4Bwrite()関数の

  for (i = 0; i <= 3; i++) {
    l_shift     = 8 * (3 - i);
    inleft      = param  << l_shift;     // 左にシフト
    in_bytes    = inleft >> 24;          // 右にシフト
    eep_address = address + i;
    EEPROM.write(eep_address, in_bytes);  // 書き込み
  }
の箇所でやっていることがそれである。

引数 "param"(4byteのタイムスタンプ情報)の値を仮に「1290456520」とすると、
2進数では「01001100 11101010 11001101 11001000」となる。

2進数、8進数、10進数、16進数相互変換

これを、「01001100」と「11101010」と「11001101」と「11001000」の4つの値(1byte)に分解して
EEPROM.write()メソッドで書き込む。

for文ループの0回目で、
「1290456520」を左に24bitシフトし、long型変数 "inleft" に格納する。
右に24bitシフトして、byte型変数 "in_bytes" に格納する。
ということをやっている。

←に24bitシフト
01001100 11101010 11001101 11001000
                                 ↓
11001000 00000000 00000000 00000000

→に24bitシフト
11001000 00000000 00000000 00000000
                                 ↓
00000000 00000000 00000000 11001000

できあがった値「00000000 00000000 00000000 11001000」は、
byte型変数 "in_bytes" に2進数で「11001000」という値として格納される。
それをすかさずEEPROMメモリアドレスの0番地に保存する。

同様に、

for文の1回目 : in_bytes = 11101010、EEPROMメモリアドレスの1番地に保存
for文の2回目 : in_bytes = 11001101、EEPROMメモリアドレスの2番地に保存
for文の3回目 : in_bytes = 11001000、EEPROMメモリアドレスの3番地に保存

と繰り返せば、long型のタイムスタンプ情報がEEPROMメモリアドレスの0〜3番地に保存できる。

読み込みの eep4Bread()関数ではこの逆の処理をやっている。
for文ループの0回目で、
EEPROMアドレス0番地の値「01001100」を読み込み、左に0bitシフトし、long型(4byte格納)変数 "in_bytes" に格納する。

←に0bitシフト
00000000 00000000 00000000 11001000
                                 ↓
00000000 00000000 00000000 11001000

そして、値0が格納されたlong型変数 "ret_param"(値は2進数で「00000000 00000000 00000000 00000000」)と
"|"(OR演算子)でbit演算した値を"ret_param"に格納する。

    ret_param  |= in_bytes;

は、わかりやすく書くと

    ret_param  = ret_param | in_bytes;

という処理をしている。
"|"(OR演算子)をかけると、

00000000 00000000 00000000 00000000 : ret_param
00000000 00000000 00000000 11001000 : in_bytes
----------------------------------------------------------------------
00000000 00000000 00000000 11001000 : ret_paramに格納

つまり、"ret_param" には「00000000 00000000 00000000 11001000」が格納される。
同様に、

for文の1回目 :
00000000 00000000 00000000 11001000 : ret_param
00000000 00000000 11001101 00000000 : in_bytes
----------------------------------------------------------------------
00000000 00000000 11001101 11001000 : ret_paramに格納

for文の2回目 :
00000000 00000000 11001101 11001000 : ret_param
00000000 11101010 00000000 00000000 : in_bytes
----------------------------------------------------------------------
00000000 11101010 11001101 11001000 : ret_paramに格納

for文の3回目 :
00000000 11101010 11001101 11001000 : ret_param
01001100 00000000 00000000 00000000 : in_bytes
----------------------------------------------------------------------
01001100 11101010 11001101 11001000 : ret_paramに格納

となり、long型変数 "ret_param" には最終的に「01001100 11101010 11001101 11001000」、つまりもとのタイムスタンプ「1290456520」が格納される。


< I2C通信を使い、タイムスタンプ情報(4 Byte値)を外部EEPROMに書き込み、読み込む >




ArduinoのEEPROMに値を格納できたけれど、ひとつ問題がある。
ArduinoのEEPROMの読み書き回数は、公称100000回だそうだ。
タイムスタンプ情報を一回書き込むためには4回の書き込みが必要である。
畑に設置する定点観測器で計測間隔を5分にするとして、1時間で48回、
1日で1152回もの書き込み処理が発生することになる。
つまり、たった86日間で書き込み限度回数に達してしまうのである。
三ヶ月弱でArduinoを買い替えるのは経済的に無駄である。
そこで、内部EEPROMの代わりに、Arduinoに外部接続できる非常に安価なEEPROM、24LC64を使用する事にする。
24LC64は公称書き込み限度回数1000000回、秋月だと60円で購入できる。
上記の計算だと28ヶ月持つ。
コストが安くて設置も簡単だけど、あえて欠点を挙げるならばアナログピンを二つ使用しなければならないところ。

回路は、以下のリンク先を参考にさせて頂きました。ありがとうございます。
なんでも作っちゃう、かも。 Arduino で遊ぼう - 大容量EEPROMに毎日の温度変化を保存する





/*
 * sketch name   : eeprom_I2C_r_w_4B
 * summary       : タイムスタンプ情報(4 Byte値)を外部EEPROMに書き込み、読み込む
 */

#include <Wire.h>
#include <DateTime.h>

// log start date
int year   = 2010;
int month  = 11;
int day    = 20;
int hour   = 20;
int minute = 45;
int second = 0;

#define EEPROM_DEV_ADDRESS 0x50  // I2CEEPROM 24LC64

long log_timestamp;

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

void setup(void) {
  Serial.begin(9600);
  pinMode(LED_PIN, OUTPUT);
  Wire.begin(EEPROM_DEV_ADDRESS);
  DateTime.sync(DateTime.makeTime(second, minute, hour, day, month, year));
}

void loop() {
  digitalWrite(LED_PIN, HIGH);
  
  log_timestamp = DateTime.now();
  
  Serial.println("Timestamp is written to the EEPROM...");
  Serial.print("  DEC : ");
  Serial.println(log_timestamp, DEC);
  Serial.print("  BIN : ");
  Serial.println(log_timestamp, BIN);
  Serial.println(" ");
    
  i2cEeprom4BWrite(EEPROM_DEV_ADDRESS, 0, log_timestamp);  // 外部EEPROMにタイムスタンプ情報(4byte値)を書き込む
  log_timestamp = i2cEeprom4BRead(EEPROM_DEV_ADDRESS, 0);  // 外部EEPROMからタイムスタンプ情報(4byte値)を読み込む
  
  Serial.println("Time stamp read from the EEPROM...");
  Serial.print("  DEC : ");
  Serial.println(log_timestamp, DEC);
  Serial.print("  BIN : ");
  Serial.println(log_timestamp, BIN);
  Serial.println(" ");
  
  digitalWrite(LED_PIN, LOW);
  
  delay(5000);
}

/*
 * 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;
}


次回は入力するセンサについて実験してみます。


< 参考リンク >