Arduinoベースの信頼性の高いワイヤレス気象観測ステーションを構築する
このチュートリアルでは、Arduinoベースのワイヤレス気象観測ステーションの作り方を学びます。以下の動画をご覧いただくか、書面によるチュートリアルをお読みください。 屋外の温度と湿度はDHT22センサーで測定し、このデータをNRF24L01トランシーバーモジュールで屋内ユニットに無線送信します。屋内ユニットにもDHT22センサーがあり室内の温湿度を測定、さらにDS3231リアルタイムクロックモジュールでArduinoの電源が切れても時刻を保持します。すべてのデータは0.96インチOLEDディスプレイに表示されます。 回路図とプロジェクトの仕組みを見てみましょう。各モジュールの詳細なチュートリアルは既にありますので、詳細は以下をご覧ください:NRF24L01チュートリアル、DHT22チュートリアル、DS3231チュートリアル。 必要なコンポーネントは以下のリンクから入手できます: リアルタイムクロックモジュールとOLEDディスプレイはI2CプロトコルでArduinoと通信するため、Arduino NanoのI2Cピン(アナログピン4と5)に接続します。NRF24L01トランシーバーのすぐ隣にコンデンサを配置して電源を安定化し、DHT22のデータピンにはプルアップ抵抗を接続して正常動作を確保しています。 電源は屋内ユニットに12V DCアダプター、屋外ユニットには2つのリチウムイオンバッテリー(約7.5V)を使用。データを定期的に送信し、その間はArduinoをスリープモード(消費電流約7mA)にするため、屋外ユニットは約10日間動作可能です。 電子部品を整理するため、回路図に基づいてEasyEDA無料オンライン回路設計ソフトでカスタムPCBを設計しました。同じPCBを屋内・屋外両ユニットで使用でき、Arduinoのプログラムのみを変えれば済みます。 設計完了後、製造用のガーバーファイルをエクスポートします。Arduinoワイヤレス気象観測ステーションのEasyEDAプロジェクトファイルはこちらで確認できます。 ガーバーファイルをJLCPCB(この動画のスポンサー)で注文します。 ガーバーファイルをドラッグ&ドロップしてアップロードし、ガーバービューアーでPCBを確認。問題なければ希望の仕様を選んで注文します。JLCPCB初回注文なら最大10枚まで2ドルで入手可能です。 数日後、PCBが到着。品質は素晴らしく、設計通りの仕上がりです。 ピンヘッダーをPCBにはんだ付けして組み立てを開始。これで必要に応じて部品の着脱が容易になります。 コンデンサとプルアップ抵抗も挿入・はんだ付け。これでPCBのピンヘッダーにコンポーネントを取り付けるだけで済みます。 次にケース制作。8mm厚のMDFボードを丸ノコでカットしてサイズ合わせ。 正確な温湿度測定のため、ケース側面に空気が入るスロットをドリルとヤスリで加工(屋内・屋外両ユニット)。 OLEDディスプレイ用のスロットを前面パネルに、装飾用のアルミ板もカット。 木工用ボンドとクランプ、ネジでケースを組み立て。 スプレー塗装で仕上げ(屋外は白、屋内は黒)。乾燥後、PCBをケースに収納。 屋内ユニット背面にDCジャックと電源スイッチ、屋外ユニットはジャンパーワイヤーで簡易スイッチとしています。 ハードウェアは完成。あとはプログラムの仕組みを見ていきましょう。 説明: 屋外ユニットは送信側なので、RF24ライブラリ、DHTライブラリ、スリープモード用のLowPowerライブラリをインクルード。インスタンス、ピン、変数を定義後、setup()で無線通信アドレスを初期化。loop()ではDHT22から温湿度を読み取り、整数値を文字列に結合して文字配列に変換、radio.write()で送信。forループで3回送信し受信側の取りこぼしを防止。最後にLowPowerライブラリでArduinoをスリープさせ消費電力を最小化。 説明: 受信側の屋内ユニットでは、DS3231リアルタイムクロック用ライブラリとOLEDディスプレイ用U8G2ライブラリを追加。インスタンス、ピン、変数を定義し、温度・湿度アイコンをビットマップとして定義します。 温度アイコンビットマップ: ビットマップ作成にはGIMP(オープンソース画像エディタ)を使用。描画後、.xbm形式でエクスポートし、メモ帳で開いてビットマップデータをArduinoコードにコピーします。 PROGMEM修飾子を使って定数として定義すると、ビットマップがSRAMではなくフラッシュメモリに格納され、メモリ節約になります。 setup()では無線通信、OLEDディスプレイ、リアルタイムクロックを初期化。 loop()ではNRF24L01で着信データを常時チェック。データがあればradio.read()で読み取り、最初の2文字を温度、次の2文字を湿度の文字列変数に格納。 millis()関数でinterval変数(3秒)ごとに表示を切り替え。delay()を使わないのは、待機中に屋外ユニットからのデータを取りこぼさないためです。 U8G2ライブラリのfirstPage()とnextPage()で5つの画面を順番に表示。drawDate()で日時、drawInTemperature()で室内温度、drawInHumidity()で室内湿度、drawOutTemperature()で屋外温度、drawOutHumidity()で屋外湿度を表示します。 このArduinoプロジェクトをお楽しみいただけたら幸いです。ご質問はコメント欄でお気軽にお尋ねください。概要

Arduinoワイヤレス気象観測ステーションの回路図

カスタム設計PCB












Arduinoワイヤレス気象観測ステーションのコード
屋外ユニット(送信側)のコード
/* Arduino Wireless Communication Tutorial
Outdoor unit - Transmitter
by Dejan Nedelkovski, www.HowToMechatronics.com
Libraries: NRF24L01 - TMRh20/RF24, https://github.com/tmrh20/RF24/
DHT22 - DHTlib, https://github.com/RobTillaart/Arduino/tree/master/libraries/DHTlib
LowPower - https://github.com/rocketscream/Low-Power */
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <dht.h>
#include <LowPower.h>
#define dataPin 8 // DHT22データピン
dht DHT; // DHTオブジェクト作成
RF24 radio(10, 9); // CE, CSN
const byte address[6] = "00001";
char thChar[32] = "";
String thString = "";
void setup() {
radio.begin();
radio.openWritingPipe(address);
radio.setPALevel(RF24_PA_MIN);
radio.stopListening();
}
void loop() {
int readData = DHT.read22(dataPin); // センサーからデータ読取
int t = DHT.temperature; // 温度取得
int h = DHT.humidity; // 湿度取得
thString = String(t) + String(h);
thString.toCharArray(thChar, 12); // 文字配列に変換
// 屋内ユニットへ無線送信
for (int i = 0; i <= 3; i++) { // 確実に届くよう3回送信
radio.write(&thChar, sizeof(thChar));
delay(50);
}
// 2分間スリープ(15×8秒=120秒)
for (int sleepCounter = 15; sleepCounter > 0; sleepCounter--) {
LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
}
}屋内ユニット(受信側)のコード
/* Arduino Wireless Communication Tutorial
Indoor unit - Receiver
by Dejan Nedelkovski, www.HowToMechatronics.com
Libraries: DS3231 - https://www.rinkydinkelectronics.com/library.php?id=73
U8G2 - https://github.com/olikraus/u8g2 */
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <dht.h>
#include <DS3231.h>
#include <U8g2lib.h>
#include <Wire.h>
#define dataPin 8 // DHT22センサー
dht DHT;
DS3231 rtc(SDA, SCL);
U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
RF24 radio(10, 9);
const byte address[6] = "00001";
char text[6] = "";
int readDHT22, t, h;
String inTemp, inHum, outTemp, outHum;
String rtcTime, rtcDate;
int draw_state = 0;
unsigned long previousMillis = 0;
long interval = 3000;
// 温度アイコンビットマップ
#define Temperature_20Icon_width 27
#define Temperature_20Icon_height 47
static const unsigned char Temperature_20Icon_bits[] U8X8_PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x80, 0x7f, 0x00, 0x00,
0xc0, 0xe1, 0x00, 0x00, 0xe0, 0xc0, 0x01, 0x00, 0x60, 0x80, 0xf9, 0x03,
0x60, 0x80, 0x01, 0x00, 0x60, 0x80, 0x01, 0x00, 0x60, 0x80, 0x79, 0x00,
0x60, 0x80, 0x01, 0x00, 0x60, 0x80, 0x01, 0x00, 0x60, 0x80, 0xf9, 0x03,
0x60, 0x80, 0x01, 0x00, 0x60, 0x80, 0x01, 0x00, 0x60, 0x8c, 0x79, 0x00,
0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0xf9, 0x03,
0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0x79, 0x00,
0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0xf9, 0x03,
0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0x01, 0x00,
0x70, 0x9e, 0x03, 0x00, 0x38, 0x1e, 0x07, 0x00, 0x18, 0x3e, 0x0e, 0x00,
0x1c, 0x3f, 0x0c, 0x00, 0x0c, 0x7f, 0x18, 0x00, 0x8c, 0xff, 0x18, 0x00,
0x8e, 0xff, 0x38, 0x00, 0xc6, 0xff, 0x31, 0x00, 0xc6, 0xff, 0x31, 0x00,
0xc6, 0xff, 0x31, 0x00, 0x8e, 0xff, 0x38, 0x00, 0x8c, 0xff, 0x18, 0x00,
0x0c, 0x7f, 0x1c, 0x00, 0x3c, 0x1c, 0x0e, 0x00, 0x78, 0x00, 0x06, 0x00,
0xe0, 0x80, 0x07, 0x00, 0xe0, 0xff, 0x03, 0x00, 0x80, 0xff, 0x00, 0x00,
0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// 湿度アイコンビットマップ
#define Humidity_20Icon_width 27
#define Humidity_20Icon_height 47
static const unsigned char Humidity_20Icon_bits[] U8X8_PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00,
0x00, 0x70, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0xdc, 0x00, 0x00,
0x00, 0xdc, 0x01, 0x00, 0x00, 0x8e, 0x01, 0x00, 0x00, 0x86, 0x03, 0x00,
0x00, 0x06, 0x03, 0x00, 0x00, 0x03, 0x07, 0x00, 0x80, 0x03, 0x06, 0x00,
0x80, 0x01, 0x0c, 0x00, 0xc0, 0x01, 0x1c, 0x00, 0xc0, 0x00, 0x18, 0x00,
0xe0, 0x00, 0x38, 0x00, 0x60, 0x00, 0x30, 0x00, 0x70, 0x00, 0x70, 0x00,
0x30, 0x00, 0xe0, 0x00, 0x38, 0x00, 0xc0, 0x00, 0x18, 0x00, 0xc0, 0x01,
0x1c, 0x00, 0x80, 0x01, 0x0c, 0x00, 0x80, 0x03, 0x0e, 0x00, 0x80, 0x03,
0x06, 0x00, 0x00, 0x03, 0x06, 0x00, 0x00, 0x03, 0x07, 0x00, 0x00, 0x07,
0x03, 0x00, 0x00, 0x06, 0x03, 0x00, 0x00, 0x06, 0x03, 0x00, 0x00, 0x06,
0x63, 0x00, 0x00, 0x06, 0x63, 0x00, 0x00, 0x06, 0x63, 0x00, 0x00, 0x06,
0xe3, 0x00, 0x00, 0x06, 0xc7, 0x00, 0x00, 0x06, 0xc6, 0x01, 0x00, 0x07,
0x86, 0x03, 0x00, 0x03, 0x0e, 0x1f, 0x00, 0x03, 0x0e, 0x1e, 0x80, 0x01,
0x1c, 0x00, 0xc0, 0x01, 0x38, 0x00, 0xe0, 0x00, 0x78, 0x00, 0x70, 0x00,
0xf0, 0x00, 0x38, 0x00, 0xe0, 0x07, 0x1f, 0x00, 0x80, 0xff, 0x0f, 0x00,
0x00, 0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00
};
void setup() {
radio.begin();
radio.openReadingPipe(0, address);
radio.setPALevel(RF24_PA_MIN);
radio.startListening();
u8g2.begin();
rtc.begin();
}
void loop() {
if (radio.available()) {
radio.read(&text, sizeof(text));
outTemp = String(text[0]) + String(text[1]) + char(176) + "C";
outHum = String(text[2]) + String(text[3]) + "%";
}
unsigned long currentMillis = millis();
if (currentMillis - previousMillis > interval) {
previousMillis = currentMillis;
u8g2.firstPage();
do {
switch (draw_state) {
case 0: drawDate(); break;
case 1: drawInTemperature(); break;
case 2: drawInHumidity(); break;
case 3: drawOutTemperature(); break;
case 4: drawOutHumidity(); break;
}
} while (u8g2.nextPage());
draw_state++;
if (draw_state > 4) draw_state = 0;
}
}
void drawDate() {
String dowa = rtc.getDOWStr();
dowa.remove(3);
rtcDate = dowa + " " + rtc.getDateStr();
u8g2.setFont(u8g2_font_timB14_tr);
u8g2.setCursor(0, 15);
rtcTime = rtc.getTimeStr();
rtcTime.remove(5);
u8g2.print(rtcDate);
u8g2.setFont(u8g2_font_fub30_tf);
u8g2.setCursor(8, 58);
u8g2.print(rtcTime);
}
void drawInTemperature() {
readDHT22 = DHT.read22(dataPin);
t = DHT.temperature;
inTemp = String(t) + char(176) + "C";
u8g2.setFont(u8g2_font_helvR14_tr);
u8g2.setCursor(24, 15);
u8g2.print("INDOOR");
u8g2.setFont(u8g2_font_fub30_tf);
u8g2.setCursor(36, 58);
u8g2.print(inTemp);
u8g2.drawXBMP(0, 17, Temperature_20Icon_width, Temperature_20Icon_height, Temperature_20Icon_bits);
}
void drawInHumidity() {
h = DHT.humidity;
inHum = String(h) + "%";
u8g2.setFont(u8g2_font_helvR14_tr);
u8g2.setCursor(24, 15);
u8g2.print("INDOOR");
u8g2.setFont(u8g2_font_fub30_tf);
u8g2.setCursor(36, 58);
u8g2.print(inHum);
u8g2.drawXBMP(0, 17, Humidity_20Icon_width, Humidity_20Icon_height, Humidity_20Icon_bits);
}
void drawOutTemperature() {
u8g2.setFont(u8g2_font_helvR14_tr);
u8g2.setCursor(12, 15);
u8g2.print("OUTDOOR");
u8g2.setFont(u8g2_font_fub30_tf);
u8g2.setCursor(36, 58);
u8g2.print(outTemp);
u8g2.drawXBMP(0, 17, Temperature_20Icon_width, Temperature_20Icon_height, Temperature_20Icon_bits);
}
void drawOutHumidity() {
u8g2.setFont(u8g2_font_helvR14_tr);
u8g2.setCursor(12, 15);
u8g2.print("OUTDOOR");
u8g2.setFont(u8g2_font_fub30_tf);
u8g2.setCursor(36, 58);
u8g2.print(outHum);
u8g2.drawXBMP(0, 17, Humidity_20Icon_width, Humidity_20Icon_height, Humidity_20Icon_bits);
}#define Temperature_20Icon_width 27
#define Temperature_20Icon_height 47
static const unsigned char Temperature_20Icon_bits[] U8X8_PROGMEM = { ... }

static const unsigned char Temperature_20Icon_bits[] U8X8_PROGMEM // フラッシュメモリに保存
static unsigned char Temperature_20Icon_bits[] // SRAMに保存
製造プロセス
- Raspberry Pi搭載DIYソーラー駆動気象ステーション
- Raspberry Pi 2とADS-WS1で屋外気象観測ステーションを構築
- Raspberry Piで気象観測ステーションを構築し、日次天気データをメール送信
- 高度気象ステーション v2.0:温度・気圧・湿度・高度のリアルタイム監視
- Arduinoオーディオ周波数検出器 – 高感度サウンドモジュールで最大音量ピークの周波数を測定
- スマートワイヤレス照明スイッチコントローラー:Arduinoで物理スイッチを遠隔操作
- Arduino三目並べ:MAX7219 LEDマトリクスと段ボールケースで自作
- SparkFun 7セグメントディスプレイで作る信頼性の高いArduinoカウントダウンタイマー
- 10ドルで作る携帯型Arduino気象観測ステーション – Nano、DHT22、BMP180、LCDで簡単DIY
- eDOT:高精度Arduino時計・気象ステーション(IRリモコン・自動輝度調整内蔵)
- ArduinoとThingSpeakで作るリアルタイム気象モニタリングシステム