MKR1000マルチモード環境センサーデッキ - 落雷・ガス・UV・温湿度・気圧を総合監視
必要な部品と用品
| Arduino MKR1000 | ×1 |
| MOD-1016 落雷センサー(AS3935搭載) | ×1 |
| DFRobot MQ-9 ガスセンサー | ×1 |
| Adafruit 湿度・温度センサー(HTU21D-F) | ×1 |
| Adafruit 気圧/高度/温度センサー(BMP085/180) | ×1 |
| SparkFun ML8511 UVセンサー | ×1 |
| Chronodot RTC(DS3231搭載、電池バックアップ付き) | ×1 |
| SparkFun Photon Weather Shield | ×1 |
| Adafruit NeoPixel | ×1 |
| ブレッドボード | ×1 |
| ジャンパーワイヤ | ×1 |
プロジェクト概要
天気観測が好きで、データを取得・グラフ化したくて始めました。市販のホーム気象ステーションは「今」の詳細や過去1時間・1日の集計程度ですが、もっと多くのことをArduinoでやりたいと考えました。温湿度、気圧は定番ですが、さらに風速・雨量も欲しい。I2Cを習得したら、AS3935落雷検出器のような面白いセンサーも見つかりました!
しかし、通常のArduinoではハードウェア割り込みピンが足りません。SparkFunのPhoton用ウェザーシールドも同様の制約があります。いくつかのセンサーを諦める必要がありました…。
そこでMKR1000を見つけ、8つのハードウェア割り込みがあることに気づきました。これで全部入ります!
センサーの選び方
センサーは大きく3タイプ:
- アナログ:光(IR、UV含む)、風向、ガス、ロードセル…
- I2C:温度、湿度、気圧、加速度、ジャイロ…
- 割り込み:雨量計(チッピングバケット)、風速計、落雷、タイマー…(シリアル、PWM、クロック、サーボもタイミング割り込みを使用)
MKR1000はこれらすべてに十分なI/Oを備えています。
電圧レベルに注意! MKR1000、Photon、多くのArduinoは3.3V I/Oです。センサーが5V仕様の場合、レベルシフターが必要です。
使用センサーは一般的で、Adafruit、SparkFun、Element14等で入手可能。多くは5-10ドル。ガスセンサーは10-20ドル。落雷検出器(AS3935)はEmbedded Adventures MOD-1016(アンテナ内蔵、26ドル)を選択。Photon Weather Shieldも購入し、将来的に風速・風向計を追加予定。土壌水分センサーも検討中。センサーによっては長い配線を嫌うものがあります。
センサーのピン割り当て
使用センサーが決まったら、各ピンに割り当てます。紙に書き出し、コードのブロックコメントにも残します。
私のMKR1000ピン割り当て:
- A0:DAC用に確保
- A1:ML8511 UVセンサー
- A2:可視光センサー(TEMT6000等)
- A3:TMP36 ローカル温度センサー
- A4:MQ-9 ガスセンサー
- A5:土壌水分センサー
- A6:風速センサー?(アナログ)
- 0 (HW INT):プッシュボタン
- 1 (HW INT):AS落雷検出器
- 2 (HW INT):風速計(回転ごとに割り込み)
- 3 (HW INT):雨量計チッピングバケット
- 4:予備
- 5:予備
- 6:オンボードLEDと共有(ピエゾサウンダー出力)
- 7:NeoPixel出力
- 11:I2C SDA
- 12:I2C SCL
始めるにあたって
初心者の方は、使用予定のセンサーを1つずつ、例題コードから始めることをお勧めします。ライブラリをインストールし、ピン番号を自分の割り当てに合わせて修正し、プロジェクトフォルダにコピーを保存。
シリアルモニタで正常に値が読めたら次のセンサーへ。すべて動作確認できたら、本格的な統合スケッチの作成に入ります。
もともとI2C接続のRTC(Chronodot v2.1)を使ってSDカードにログを取る予定でした。MKR1000にも内蔵RTCと電池がありますが、電池のみで時刻を保持できなかったため、Chronodotも併用しています。
ライブラリについての注意
一部のライブラリはMKR1000やArduino IDE 1.6.7でそのまま動かないことがあります。例:古いATOMIC_*マクロ(AVR libc、IDE 1.6.5で削除)。中級者なら修正可能ですが、初心者にはハードルが高いです。古いライブラリは作者が不在で更新されないことも。
エラーメッセージ(オレンジ色)をよく読み、問題がライブラリ側にあるか確認してください。「arduino」とエラー文で検索し、解決策を探します。見つからなければ作者に連絡を。
個別チップを買って自作しようとしましたが、基板製作(EagleCAD + OSHPark等)ができるまで、AdafruitやSparkFunのモジュールを買うのが賢明です。ライブラリのメンテナンスが行き届いています。
WiFiと暗号化について
WiFiでデータをクラウド送信する構想もあります。MKR1000には暗号化エンジンも搭載されており、IoTでのデータ保護に重要です。今回は時間切れで実装見送りですが、「バージョン2」で取り組みます。SAMDモジュール(ボードの心臓部)のブロック図を参考に掲載。
WiFi開始のヒント: 必ず最新版のWiFi101ライブラリを使用してください! 古いとWiFiモジュールを認識しません。Charif Mahmoudi氏の「MKR1000入門」チュートリアルで指摘されています。当時のWiFi101 GitHubはこちら。
アナログセンサーのコツ
ほとんどのアナログ出力センサーモジュールは単純な電圧出力で、analogRead()で読めます。その後、map()関数や計算で実値に変換。抵抗変化型は追加回路が必要。ポテンショメーターで「微調整」することも。
電圧から実値を求める式が必要な場合も。TMP36は摂氏で出力するよう設計されており、電圧測定後に計算で℃、さらに華氏変換。データシートを読むか、先人のコードを参考にするのが近道です。
微小電圧変化を増幅するアンプ内蔵モジュール(ロードセル、土壌水分、加速度等)は、最初からアンプ・ライブラリ付きモジュールを買うのが無難です。
I2Cデバイス使用のコツ
I2Cは「2線式インターフェース」とも呼ばれ、クロック(SCL)とデータ(SDA)の2線(+電源、GND)で通信。マスターとスレーブがデータ線を共有します。
- 各デバイスに固有の7ビットアドレスあり。同一アドレスの複数使用は避ける(必要ならイネーブルピンで切り替え)
- 注意: 同一アドレスで両方のVCCを切っても、SDA/SCLからパラサイト電流で動作し、誤読み取りの原因になります。イネーブルピンで制御してください。
- 仕様上、最遠端のセンサーにSDA/SCLプルアップ抵抗が必要。モジュールによっては基板上に実装済み。
- 古いハック:SDA/SCLそれぞれに直列抵抗(3.3Vなら220Ω、5Vなら330Ω程度)を入れると通信が安定することがあります。
データの活用
現在はシリアルモニタ出力のみ。LCD追加も可能。SDカードログから、内蔵WiFiでクラウドアップロードへ移行したいところ。複数のセンサーデッキを緯度経度付きで配置すれば、落雷の三角測量まで夢が広がります!
追記(2017-11-29): MKR1000でハードウェア割り込みが動作せず苦戦中。中学3年生(13-14歳)向け教材として、Adalogger M0ボード(SDカードログ + WiFiモジュール余裕)でノウハウ蓄積中。
授業サイト
センサー作業中にIKEAのSODERSVIKランプを見つけ、中の白色LEDを20個のNeoPixelに置き換え、IRリモコン受信を追加したら面白そうだと思いました。風で雲が流れるような演出、色で温度表示、リモコンで過去12時間の温度変化を一時表示…など。
あなたは何を監視・表示したいですか?
コード
統合監視スケッチ (Arduino)
多数のセンサーを統合。NeoPixelと落雷センサーのライブラリは、AVR-libcのクラシックな割り込みロック手法が最近のIDEで削除されたため動作せず、最終スケッチからは除外しています。年内に解決見込み。
/* RTC-Weather-Sensors_v6_MKR1000 by Zonker Harris Spring 2016
* Hooray! 8 hardware interrupts + WiFi + crypto (IoT必須!)
* コメントはアップロード時に無視されるので、たくさん書いてOK。
* ライブラリはプログラムメモリを消費するので注意。
*/
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BMP085_U.h>
#include <Adafruit_HTU21DF.h>
// #include <Adafruit_NeoPixel.h> // 割り込み問題で一時除外
Adafruit_BMP085_Unified bmp = Adafruit_BMP085_Unified(10180);
Adafruit_HTU21DF htu = Adafruit_HTU21DF();
// ピン定義
int UVOUT = A1; // ML8511 UV
int lightOut = A2; // TEMT6000 可視光
int tmp36out = A3; // TMP36 温度
int mq9out = A4; // MQ-9 ガス
int sounderPin = 6; // ピエゾサウンダー(オンボードLED共有)
int neopixelPin = 7; // NeoPixel出力
// Chronodot RTC変数
int addrRTC = 0x68;
int seconds, minutes, hours, day, date, month, years;
int temprtc, tempfrtc;
void setup() {
Serial.begin(9600);
delay(1000);
Serial.println("i2c Sensor Deck");
pinMode(UVOUT, INPUT);
pinMode(lightOut, INPUT);
pinMode(tmp36out, INPUT);
pinMode(sounderPin, OUTPUT);
pinMode(neopixelPin, OUTPUT);
if (!bmp.begin()) {
Serial.println("BMP085/180 not detected!");
while(1);
}
displaySensorDetails();
if (!htu.begin()) {
Serial.println("HTU21-DF not found!");
while(1);
}
// RTC初期設定(初回のみアンコメントしてアップロード)
/* seconds=0; minutes=41; hours=20; day=7; date=3; month=1; years=16; initChrono(); */
}
void loop() {
beep(50, 2);
// Chronodot RTC読み取り
Wire.beginTransmission(0x68);
Wire.write((byte)0);
Wire.endTransmission();
Wire.requestFrom(0x68, 13);
while(Wire.available()) {
seconds = Wire.read();
minutes = Wire.read();
hours = Wire.read();
day = Wire.read();
date = Wire.read();
month = Wire.read();
years = Wire.read();
// アラーム等は省略
temprtc = Wire.read(); // 11h: 温度整数部
tempfrtc = Wire.read(); // 12h: 温度小数部
}
// BCD→10進変換処理...
get_date();
Serial.print("ChronoDot - ");
Serial.print(hours); Serial.print(":");
Serial.print(minutes); Serial.print(":");
Serial.print(seconds); Serial.print(" 20");
Serial.print(years); Serial.print(" ");
Serial.print(month); Serial.print(" ");
Serial.print(date); Serial.print("\t");
Serial.print(temprtc); Serial.println(" C");
delay(100);
// BMP180 気圧・温度
sensors_event_t event;
bmp.getEvent(&event);
float BMPtemperature;
bmp.getTemperature(&BMPtemperature);
Serial.print("Temp: "); Serial.print(BMPtemperature); Serial.print(" C\t");
if (event.pressure) {
Serial.print("BMP180 - Pres: "); Serial.print(event.pressure); Serial.print(" hPa\t");
float seaLevelPressure = SENSORS_PRESSURE_SEALEVELHPA;
Serial.print("Alt: "); Serial.print(bmp.pressureToAltitude(seaLevelPressure, event.pressure)); Serial.println(" m");
} else { Serial.println("Sensor error"); }
delay(100);
// HTU21-DF 湿度・温度
float HTUtemperature = htu.readTemperature();
Serial.print("HTU21-DF - Temp: "); Serial.print(HTUtemperature); Serial.print(" C\tHum: ");
Serial.print(htu.readHumidity()); Serial.println("%");
delay(100);
// TMP36 温度
int reading = averageAnalogRead(tmp36out);
float tmp36voltage = 0.0032258064516129 * reading; // 3.3V/1023
float tmp36temperatureC = (tmp36voltage - 0.5) * 100;
Serial.print("TMP36 - temp: "); Serial.print(tmp36temperatureC); Serial.print(" C\t");
float tmp36temperatureF = (tmp36temperatureC * 9.0 / 5.0) + 32.0;
Serial.print(tmp36temperatureF); Serial.print(" F, out: "); Serial.print(tmp36voltage); Serial.println("v");
delay(100);
// TEMT6000 可視光
int vLevel = averageAnalogRead(lightOut);
float newVOutVolts = 0.0032258064516129 * vLevel;
Serial.print("TEMT6000 out: "); Serial.println(vLevel);
delay(100);
// ML8511 UV
int uvLevel = averageAnalogRead(UVOUT);
float newOutVolts = 0.0032258064516129 * uvLevel;
float uvIntensity = mapfloat(newOutVolts, 0.99, 2.8, 0.0, 15.0);
Serial.print("ML8511 UV out: "); Serial.print(uvLevel);
Serial.print(" / UV Intensity: "); Serial.println(uvIntensity);
delay(100);
// MQ-9 ガス
int MQ9volts = analogRead(mq9out);
Serial.print("MQ-9 Gas: "); Serial.println(MQ9volts, DEC);
delay(100);
Serial.println("");
delay(3500); // ループ約5秒
}
// 補助関数群(平均化アナログ読み取り、float版map、BCD変換、RTC操作、ビープ音等)
// 詳細は元コード参照
アナログ読み取り平均化関数
SparkFunのML8511ライブラリにあった巧妙なハック。あらゆるアナログ読み取りに応用可能。Nathan Seidle氏にビールを奢りたくなるコード(Beerwareライセンス)。
int averageAnalogRead(int pinToRead) {
byte numberOfReadings = 8;
unsigned int runningValue = 0;
for (int x = 0; x < numberOfReadings; x++)
runningValue += analogRead(pinToRead);
runningValue /= numberOfReadings;
return runningValue;
}
I2Cバススキャナー
I2Cデバイスのアドレスが不明な場合にスキャン。既知センサーの説明も表示。
#include <Wire.h>
void setup() { Wire.begin(); Serial.begin(9600); Serial.println("\nI2C Scanner"); }
void loop() {
byte error, address; int nDevices = 0;
Serial.println("Scanning...");
for (address = 1; address < 127; address++) {
Wire.beginTransmission(address);
error = Wire.endTransmission();
if (error == 0) {
Serial.print("I2C device at 0x"); if (address<16) Serial.print("0");
Serial.print(address, HEX);
if (address == 3) Serial.print(" - AS3935 Lightning");
if (address == 64) Serial.print(" - HTU21D Humidity/Temp");
if (address == 104) Serial.print(" - DS1307 RTC (Chronodot)");
if (address == 119) Serial.print(" - BMP180 Pressure/Temp");
Serial.println(); nDevices++;
} else if (error == 4) {
Serial.print("Unknown error at 0x"); if (address<16) Serial.print("0");
Serial.println(address, HEX);
}
}
if (nDevices == 0) Serial.println("No I2C devices found\n");
else Serial.println("done\n");
delay(5000);
}
ML8511 デモコード(3.3V CPU対応版)
int UVOUT = A1;
void setup() { Serial.begin(9600); pinMode(UVOUT, INPUT); Serial.println("ML8511 example"); }
void loop() {
int uvLevel = averageAnalogRead(UVOUT);
float newOutVolts = 0.0032258064516129 * uvLevel;
float uvIntensity = mapfloat(newOutVolts, 0.99, 2.8, 0.0, 15.0);
Serial.print("ML8511 out: "); Serial.print(uvLevel);
Serial.print(" / UV Intensity: "); Serial.println(uvIntensity);
delay(100);
}
回路図
Fritzing初心者でパーツが見つからなかったため、モックアップ図を作成。
製造プロセス
- センサーデータをRaspberry Piに統合:実践ガイド
- Raspberry Piでライントラッキングセンサー – シンプルなKY-033ドア/ライン検出器
- GY-91モジュールでRaspberry Pi上に堅牢な温度・センサーAPIを構築
- Raspberry Piと湿度センサーで作る自動エアロポニックスシステム
- Raspberry Pi GPIOマスター:PIRモーションセンサーをB+/Model 2でインターフェース
- Acconeer A111パルスレーダーをRaspberry Piと統合:実践ガイド
- HC-SR501 PIRモーションセンサーをRaspberry Piで接続・調整・プログラミングする方法
- Python/MicroPythonセンサーロガー:センサーデータをGoogleスプレッドシートにシームレス保存
- Raspberry Pi 2からAzure Event HubsへAdafruit 10DOF IMUデータを送信(Windows 10 IoT Core)
- Raspberry Pi のグレースフルシャットダウンとスリープモード:DIY 電源管理回路
- センサーフューションが自律システムの信頼性、セキュリティ、効率を高める仕組み