フィールド設置型の定点観測ロガーを作成する。 < 定点観測器の仕様 >
< 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月26日金曜日
定点観測データロガー -5 : タイムスタンプ情報の持続テスト
2010年11月25日木曜日
定点観測データロガー -4 : 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 : 気温・湿度・温度・照度計測
フィールド設置型の定点観測ロガーを作成する。 < 定点観測器の仕様 >
< 温度センサ > |
温度センサをセットする時はまず測定温度範囲を決める。 温度センサは計測した温度によって出力する電圧が変化する素子で、 その電圧は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にデータを保存する
フィールド設置型の定点観測ロガーを作成する。 < 定点観測器の仕様 >
< 持続した時刻情報の保存 > 前回の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の情報を読み書きできる。 使い方は以下のようになる。
時刻情報はタイムスタンプとして記録する。 タイムスタンプとは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; } |
| 次回は入力するセンサについて実験してみます。 < 参考リンク >
|
定点観測データロガー -1 : Watchdogタイマーとスリープからの自動復帰
アウトドアに設置され、静かにデータを計測し続ける定点観測ロガー。 なぜか「定点観測」という言葉の響きからとてもロマンチックなイメージをしてしまう。 まるで毎晩天体望遠鏡で夜空の星を眺め続ける少年のような(それは天体観測)。 大自然の中に存在する、小さいけれどメカニカルな存在感を美しいと感じているんだと思う。 Arduinoを使い、一ヶ月くらい放置してもきちんと環境データを記録し続けてくれる定点観測ロガーを作成する。 八月から始めた畑は家から結構離れた郊外にある。 往復2時間かかるので、仕事帰りにちょっと野菜達の様子を観るということは無理である。 育成の観察や害虫駆除など本当は毎日でもしたいようなことができない分、農業はまだ身近に感じる事ができない。 だからというわけでもないけど、定点観測ロガーを設置する予定のフィールドは僕の畑である。 畑の面積は約16平方メートルで、これは六畳一間をひとまわり大きくしただけの広さだ。 堆肥の山の発酵熱は成分調整をうまくやれば季節を問わず摂氏80℃にも達する事があり、 また真冬の霜の降りた畝の表土は0℃を下回る。 それにキーホールガーデンや畝、池など、狭い空間なりにできるだけ多様な小環境を造成したので、 ベランダのプランターガーデンよりは複雑な微気象の変化を作り出せるのではないかと思う。 土の水分計測も考えたが、必要ないので止めた。 共同農園には水道の蛇口が一つしかないしから自動水分補給器のようなものをしかけることはできないし、 プランター栽培とは異なり、種まき後の発芽までの期間以外は自然の雨だけで水分補給は事足りてしまうからである。 計測対象となるのは気温と湿度、それに堆肥の山の発酵熱、土の表面温度、池の水温、照度とする。 温度センサと湿度センサ、フォトダイオードをしかける。 ちょっとした環境データを利用して、種まきの時期の把握とか、自分の畑に適する野菜の選別作業の足しになれば?幸いである。 < 定点観測器の仕様 >
まずは電源である。 これはなんとかなりそう。 超最先端ハイテク技術でもない限りこういうことは既に先人達の解決方法があって、そのヒントはMake:Japanの記事の中に眠っていた。 スリープして電池寿命を延ばすArduino小鳥さえずり機 Watchdog Timerによる自動復帰スリープ機能を使って、電池寿命をなんと4日間から3年間に引き延ばしている。 この記事を読んで初めて、Arduinoでも外部からの割り込みなしにスリープからの自動復帰ができるということを知った。 スリープ状態の時には電力消費を限りなく低く抑え、設定したスリープ間隔に達したら自動復帰させて目的の処理を実行させ、 再び設定した間隔でスリープさせる。 畑には毎週、最低でも隔週間隔で通うので、電池4個を使って2週間持てばいい。 < Arduino スケッチ > 以下はテストスケッチである。 ArduinoのWatchdogとSleepのライブラリはなんでも作っちゃう、かも。から賜りました。ありがとうございます。 Watchdogライブラリ リンク先からファイルをコピペし、Watchdog というディレクトリを作成してその中に保存。 Sleepライブラリ リンク先から Sleep003.zip をダウンロード&解凍ファイルする。Sleep というディレクトリができる。 それぞれ、Arduino/libraries/ 以下に保存する。 参考サイトのスケッチをちょっといじった。 デジタル13ピンのLEDを3秒間点灯させた後、スリープさせて1秒後にスリープ復帰...を繰り返す。 Arduino Pro をUSBケーブルで接続して、スケッチをアップロード。 /* * sketch name : watchdog_sleep_led * summary : LEDを点灯させた後に指定の間隔スリープ、自動復帰させる */ #include <Watchdog.h> #include <Sleep.h> // オブジェクト作成 WatchdogClass WD = WatchdogClass(); void setup() { pinMode(13,OUTPUT); // 13ピンを出力に設定 WD.systemResetEnable(false); // スリープ復帰時にスケッチをリセットしない(setup関数を実行しない)ように設定 WD.enable(WatchdogClass::TimeOut1s); // スリープ間隔を1秒に設定 } void loop() { digitalWrite(13,HIGH); // LED点灯開始 delay(3000); // 3秒間 digitalWrite(13,LOW); // LED点灯停止 WatchdogClass::timerReset(); // Watchdogタイマーリセット SleepClass::powerDown(); // パワーダウン } Arduino Pro 328 3.3V 8MHzにスケッチをアップロードするには3.3V動作Arduino製品向け小型USB-シリアルアダプタと USBケーブル(Aオス-miniBタイプ)が必要である。 スケッチの実行は、Arduino Pro 328 3.3V 8MHzとPCをただ繋ぐだけである。 データロギングは、 digitalWrite(13,HIGH); delay(3000); digitalWrite(13,LOW); 次回は、もう一つの問題点である、持続した時刻情報の保存を試してみる。 < 参考リンク >
|
2010年10月17日日曜日
コンパニオンプランツ 秋冬野菜
: 秋冬野菜のコンパニオンプランツ相関図。 現在、畑に植えている作物のみをリストアップした。 コンパニオンプランツの効果は科学的に照明されているわけではないけれど、面白そうなので参考にして植えてみる事にした。 |
登録:
投稿 (Atom)


















