Arduino連携 高機能てこ式雨量計
部品と材料
| 部品名 | 数量 | 備考 |
|---|---|---|
| Arduino Nano R3 | 1 | メイン制御 |
| Tiny RTC I2C DS1307 | 1 | リアルタイムクロック |
| SDカードモジュール (LC Studios) | 1 | データログ記録用 |
| 7-12 VDC 電源アダプター | 1 | 壁面コンセント用 |
| Adafruit 標準 LCD 16x2 (白文字/青背景) | 1 | 室内表示ユニット用 |
| Texas Instruments 74HC595N シフトレジスタ | 1 | LCD 制御用 (3線式) |
| 抵抗 1kΩ | 2 | |
| 抵抗 330Ω | 2 | LED 用等 |
| コンデンサ 100nF | 1 | デカップリング用 |
| ポテンショメーター 10kΩ (単回転) | 1 | LCD コントラスト調整用 |
| PCB モメンタリ プッシュボタンスイッチ | 1 | メニュー切替用 |
| リードスイッチ SPST-NO | 1 | てこバケット検知用 (屋外ユニット) |
| マグネット (外径 7mm × 長さ 7mm) | 1 | バケットに取り付け、リードスイッチ検知用 |
| 5x10cm 片面ユニバーサル基板 | 1 | 制御基板用 |
必要な工具と機器
- 3Dプリンター (汎用)
- はんだごて (汎用)
プロジェクト概要
この装置はプロの気象観測で使われる「てこ式雨量計」をベースにしています。雨水を漏斗で集め、てこ式バケット (てこの原理で、両端に約 2ml の雨水を保持するバケットを持つ) へ導きます。片側が満杯になるとバケットが傾き、雨水が反対側のバケットへ集まり始めます。バケットが傾くたびにマグネットがリードスイッチを一時的に ON にし、Arduino のハードウェア割り込みに信号を送ります。Arduino は各「ティップ (傾き)」をカウントし、バケット容量と集水面積 (16.605 平方インチ) に基づいて降雨量 (インチ) に換算します。
室内表示ユニットにはメニュー選択機能があり、「現在の時間」「前の時間」「現在の日」「前の日」の降雨量合計を表示できます。
各バケットティップごとに日時スタンプ付きイベントが SD カードファイルに書き込まれます。このファイルは後で Excel にインポートして分析・グラフ化可能です。
実際の「バケットティップ」動作を撮影した動画はありませんが、実際の降雨イベントを記録する様子を動画で確認できます。
組み立て手順
ステップ 1: 3Dプリントパーツ出力
リストアップされたすべてのコンポーネントを出力します。屋外使用で紫外線と温度変化に耐える PETG フィラメントを使用し、インフィル 30% で出力しました。上部フィルターについては、水平シェル (トップ/ボトムレイヤー) をすべてオフにして多孔質構造にしてください。

ステップ 2: フロントパネル基板準備
LCD を半田付けせずに PY-5×10cm ユニバーサル基板に載せ、LCD のピン 1 が基板の C-1 穴位置に来るようにします。LCD 基板の 4 つの取り付け穴位置でユニバーサル基板にドリルで穴を開けます。その後 LCD を一旦取り外しておきます。
ステップ 3: フロントパネル基板組み立て
74HC595N シフトレジスタ IC、スイッチ、抵抗を配置し、配線図を参照して必要な配線を行います。LCD 接続用の配線と、後でメイン基板に接続する 6 インチ (約 15cm) の配線を伸ばしておきます。

ステップ 4: LCD 取り付け
3Dプリントしたスペーサーを使って LCD をフロントパネル基板に取り付け、IC とのクリアランスを確保します。M3×15mm キャップスクリューとナットで固定。(ファスナーはファスネルやマスターカール等で入手可能)

ステップ 5: メイン基板組み立て
Arduino Nano をピン D12 が基板の R27 穴位置に来るよう挿入。コンデンサ、抵抗を配置し、SD カードモジュールと RTC モジュールを挿入する前にすべての配線を所定位置まで引き回します。配線詳細は回路図を参照してください。

ステップ 6: SD リーダーと RTC モジュール実装
SD リーダーを A-3 〜 B-10 穴位置に、TinyRTC を C-24 〜 I-24 穴位置に実装し、すべて半田付けします。

ステップ 7: 室内ユニットケース組み立て
室内ユニット制御ケースに、屋外ユニットへの 2 線と外部電源アダプター用の穴を適切な位置に開けます。配線をケース内に通し、適切な位置で半田付け。メイン基板をケースにスライドインし、M3×6mm ネジ 2 本で固定。フロントカバーのスイッチ穴にプッシュボタンを通し、フロント基板を M3×6mm ネジで取り付けます。注: プリント部品ではなく小さな真鍮製パーツをスイッチボタンとして使用しました。手触りが良いためです。

ステップ 8: フロントカバー取り付け
M3-0.5 × 10mm 平頭ネジでフロントカバーを固定します。
ステップ 9: 動作テスト
USB で PC に接続し、まず RTC 時刻設定プログラムを書き込んで時刻を合わせます。時刻設定後、メインプログラムを書き込み。SD カードを挿入。屋外ユニットのリードスイッチ接続リードをショートさせてテスト。各ショート (接点閉) で降雨量合計がインクリメントされることを確認。SD カードを PC でテキストエディタ (メモ帳、WordPad 等) で開き、「Text.txt」ファイルに各「ティップ」イベントの日時スタンプが記録されていることを確認。
ステップ 10: 屋外ユニット組み立て
リードスイッチをサブベースに取り付け、結束バンドで固定。マグネットをバケットに取り付け、バケットを短い真鍮管 (外径 1/8 インチ、約 2 インチ長) でピボットマウントします。組み立て前にバケットの穴を 1/8 インチドリルで通してバリ取り。バケットが摩擦なくスムーズに傾くことを確認。必要なら小さな丸ヤスリで穴を調整。バケット外側に小さな 1/8 インチワッシャーを入れて隙間を少し詰めます。屋外ユニットの取り付け方法を決定 (筆者は 1 インチアルミアングル使用)。ベースをアングル/ブラケットに、内側から 1/4-20 六角ボルトで固定。サブベースを屋外ユニット底部に M3-0.5×10mm ネジで固定。

ステップ 11: 漏斗と鳥よけ設置
漏斗をトップエンクロージャに挿入。約 24 本の 2 インチ長 18 ゲージ亜鉛メッキワイヤーを用意。ペンチでワイヤーを保持し、トーチやライターで熱して慎重にプラスチックトップに押し込みます。これらは鳥がユニットに止まるのを防ぎます。測定したいのは雨量であって鳥の糞ではありません。トップをメインエンクロージャに M3-0.5×6mm ネジ 4 本で固定。フィルターを漏斗トップに取り付け。落ち葉等の詰まりを防ぎます。定期的にチェックして清掃してください。

ステップ 12: 屋外ユニット設置と配線
屋外ユニットを設置し、室内ユニットへ配線。屋外ユニットのトップが前後左右とも水平であることを確認してください。
ステップ 13: 通電と実動テスト
電源投入し、屋外ユニットに水を注いで動作確認。
ステップ 14: 精度検証 (任意)
精度を確認するには、水 1 カップ (約 237ml = 14.44 立方インチ) を 非常にゆっくり 注ぎ、0.87 インチの降雨として記録されるか確認します (集水面積 16.605 平方インチ ÷ 14.44 立方インチ = 0.87 インチ)。メインプログラムの補正係数で調整可能です。
おめでとうございます! これで降雨量を追跡できるようになりました。お楽しみください。
ソースコード
RTC 時刻設定用 Arduino コード
最初にこのコードを書き込んで RTC の時刻を設定します。コード内の日時を実際の値に書き換えてからアップロードしてください。
#include "Wire.h"
#define DS1307_I2C_ADDRESS 0x68 // Tiny RTC の I2C アドレス
byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
// 10進数 → BCD 変換
byte decToBcd(byte val) {
return ((val / 10 * 16) + (val % 10));
}
// BCD → 10進数 変換
byte bcdToDec(byte val) {
return ((val / 16 * 10) + (val % 16));
}
// 現在時刻設定関数 (秒・分・時を正しい時刻に変更してからアップロード)
void setDateDs1307() {
second = 00;
minute = 11;
hour = 12;
dayOfWeek = 5;
dayOfMonth = 24;
month = 7;
year = 20;
Wire.beginTransmission(DS1307_I2C_ADDRESS);
Wire.write(decToBcd(0));
Wire.write(decToBcd(second)); // bit 7 = 0 でクロック開始
Wire.write(decToBcd(minute));
Wire.write(decToBcd(hour)); // 12時間制 AM/PM の場合は bit 6 も設定 (readDateDs1307 も要変更)
Wire.write(decToBcd(dayOfWeek));
Wire.write(decToBcd(dayOfMonth));
Wire.write(decToBcd(month));
Wire.write(decToBcd(year));
Wire.endTransmission();
}
// DS1307 から日時取得して表示
void getDateDs1307() {
Wire.beginTransmission(DS1307_I2C_ADDRESS);
Wire.write(decToBcd(0));
Wire.endTransmission();
Wire.requestFrom(DS1307_I2C_ADDRESS, 7);
second = bcdToDec(Wire.read() & 0x7f);
minute = bcdToDec(Wire.read());
hour = bcdToDec(Wire.read() & 0x3f); // 12時間制の場合は要変更
dayOfWeek = bcdToDec(Wire.read());
dayOfMonth = bcdToDec(Wire.read());
month = bcdToDec(Wire.read());
year = bcdToDec(Wire.read());
Serial.print(hour, DEC); Serial.print(":");
Serial.print(minute, DEC); Serial.print(":");
Serial.print(second, DEC); Serial.print(" ");
Serial.print(month, DEC); Serial.print("/");
Serial.print(dayOfMonth, DEC); Serial.print("/");
Serial.print(year, DEC); Serial.print(" ");
Serial.println();
}
void setup() {
Wire.begin();
Serial.begin(19200);
setDateDs1307(); // 現在時刻設定
}
void loop() {
delay(2000);
getDateDs1307(); // 2秒ごとに時刻取得表示
}
雨量計メインプログラム (Arduino)
コンパイル前にすべての関連ライブラリをインストールしてください。LiquidCrystal ライブラリ更新手順 を参照し、3線式 LCD 用に更新してください。
/* Tipping Bucket Rain Gauge
Written by Bob Torrence */
#include <SPI.h>
#include <SD.h>
#include <Wire.h>
#include "RTClib.h"
#include <LiquidCrystal_SR.h> // 特殊版 LiquidCrystal ライブラリ (3線式シリアルLCD用)
/// LCD とインターフェースピン定義
LiquidCrystal_SR lcd(6, 5, 9); // Pin 6: Data/SER, Pin 5: Clock/SCL, Pin 9: Latch/SCK
RTC_DS3231 rtc;
int backlight = 7; // LCD バックライト LED 接続ピン (D7)
// 定数: 割り込みピン番号
const byte interruptPin_bucket = 3; // バケットティップ検知
const byte interruptPin_menu = 2; // メニュー切替ボタン
// 変数
volatile int Bucket_Tip_Occurence = 0;
volatile int Menu_Select = 0;
float Bucket_tip_hour_total = 0;
float Bucket_tip_current_hour_total = 0;
float Bucket_tip_previous_hour_total = 0;
float Bucket_tip_current_day_total = 0;
float Bucket_tip_previous_day_total = 0;
int current_minute, loop_minute;
int current_hour, loop_hour;
int current_day, loop_day;
int tip_counter = 0;
float conversion_factor = 0.00854; // インチ/ティップ (バケット容量と集水面積 16.605 sq.in. から算出)
volatile unsigned long backlightOfftime;
volatile unsigned long backlightOnDuration = 30000; // メニュー操作後バックライト点灯持続時間 (ms)
String print_time(DateTime timestamp) {
char message[120];
int Year = timestamp.year();
int Month = timestamp.month();
int Day = timestamp.day();
int Hour = timestamp.hour();
int Minute = timestamp.minute();
int Second = timestamp.second();
sprintf(message, "%02d:%02d:%02d %02d/%02d", Hour, Minute, Second, Month, Day);
return message;
}
File myFile;
void setup() {
lcd.begin(16, 2);
lcd.home();
rtc.begin();
pinMode(10, OUTPUT);
pinMode(backlight, OUTPUT);
digitalWrite(backlight, HIGH); // バックライト ON
backlightOfftime = millis() + backlightOnDuration;
if (!SD.begin(4)) {
lcd.print("insert SD Card");
return;
}
// バケットティップ割り込み設定 (立下りエッジ)
pinMode(interruptPin_bucket, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(interruptPin_bucket), count, FALLING);
// メニューボタン割り込み設定 (立上がりエッジ)
pinMode(interruptPin_menu, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(interruptPin_menu), menu, RISING);
// SD カード初期書き込み
myFile = SD.open("test.txt", FILE_WRITE);
if (myFile) {
myFile.println("Rain Gauge Ready - unit powered up");
myFile.close();
}
DateTime now = rtc.now();
current_minute = loop_minute = now.minute();
current_hour = loop_hour = now.hour();
current_day = loop_day = now.day();
lcd.setCursor(0, 0);
lcd.print(print_time(now));
lcd.setCursor(1, 1);
lcd.print("Rain Gauge");
}
void loop() {
DateTime now = rtc.now();
current_minute = now.minute();
loop_minute = now.minute();
current_hour = now.hour();
loop_hour = now.hour();
current_day = now.day();
loop_day = now.day();
// 当日合計計算ループ
while (loop_day - current_day == 0) {
// 当時間合計計算ループ
while (loop_hour - current_hour == 0) {
// バックライト自動消灯
if (millis() > backlightOfftime) {
digitalWrite(backlight, LOW);
}
// バケットティップ発生処理
if (Bucket_Tip_Occurence == 1) {
Bucket_Tip_Occurence = 0;
tip_counter++;
DateTime now = rtc.now();
myFile = SD.open("test.txt", FILE_WRITE);
myFile.print("Event ");
myFile.print(now.year(), DEC); myFile.print('/');
myFile.print(now.month(), DEC); myFile.print('/');
myFile.print(now.day(), DEC); myFile.print(' ');
myFile.print(now.hour(), DEC); myFile.print(':');
myFile.print(now.minute(), DEC); myFile.print(':');
myFile.print(now.second(), DEC); myFile.print(' ');
myFile.print(conversion_factor, 5);
myFile.println();
myFile.close();
delay(200);
} else {
// 現在時間ステータスチェック
DateTime now = rtc.now();
loop_hour = now.hour();
}
// メニュー表示切替
switch (Menu_Select) {
case 1:
lcd.clear();
lcd.setCursor(0, 0); lcd.print("Current Hour");
lcd.setCursor(0, 1); lcd.print(tip_counter * conversion_factor);
delay(500);
break;
case 2:
lcd.clear();
lcd.setCursor(0, 0); lcd.print("Previous Hour");
lcd.setCursor(0, 1); lcd.print(Bucket_tip_previous_hour_total);
delay(500);
break;
case 3:
lcd.clear();
lcd.setCursor(0, 0); lcd.print("Current Day");
lcd.setCursor(0, 1); lcd.print(Bucket_tip_current_day_total + tip_counter * conversion_factor);
delay(500);
break;
case 4:
lcd.clear();
lcd.setCursor(0, 0); lcd.print("Previous Day");
lcd.setCursor(0, 1); lcd.print(Bucket_tip_previous_day_total);
delay(500);
break;
}
}
// 次の 1 時間ループへ向けたリセット
DateTime now = rtc.now();
current_hour = now.hour();
loop_day = now.day();
loop_hour = now.hour();
// 当時間合計を確定
Bucket_tip_previous_hour_total = tip_counter * conversion_factor;
Bucket_tip_current_day_total += Bucket_tip_previous_hour_total;
tip_counter = 0;
/* 時間ごとのサマリーをファイルに書く場合 (コメントアウト)
myFile = SD.open("test.txt", FILE_WRITE);
myFile.print("Hourly Summary ");
myFile.print(Bucket_tip_previous_hour_total);
myFile.print(now.year(), DEC); myFile.print('/');
myFile.print(now.month(), DEC); myFile.print('/');
myFile.print(now.day(), DEC); myFile.print(' ');
myFile.print(now.hour(), DEC); myFile.print(':');
myFile.print(now.minute(), DEC); myFile.print(':');
myFile.print(now.second(), DEC); myFile.print(' ');
myFile.print(Bucket_tip_previous_hour_total);
myFile.println();
myFile.close();
delay(200);
*/
}
// 日付変更時の処理
Bucket_tip_previous_day_total = Bucket_tip_current_day_total;
Bucket_tip_current_day_total = 0;
Bucket_tip_current_hour_total = 0;
tip_counter = 0;
/* 前日合計のみファイルに書く場合 (コメントアウト)
myFile = SD.open("test.txt", FILE_WRITE);
myFile.print("Day Total ");
myFile.print(Bucket_tip_previous_day_total);
myFile.print(print_time(now));
myFile.close();
*/
}
// バケットリードスイッチ割り込みハンドラー
void count() {
static unsigned long last_interrupt_time_bucket = 0;
unsigned long interrupt_time_bucket = millis();
// 300ms 以内の割り込みはチャタリングとみなし無視
if (interrupt_time_bucket - last_interrupt_time_bucket > 300) {
Bucket_Tip_Occurence = 1;
}
last_interrupt_time_bucket = interrupt_time_bucket;
}
// メニューボタン割り込みハンドラー
void menu() {
static unsigned long last_interrupt_time_menu = 0;
unsigned long interrupt_time_menu = millis();
if (interrupt_time_menu - last_interrupt_time_menu > 300) {
if (digitalRead(backlight) == LOW) {
digitalWrite(backlight, HIGH); // バックライトが消えていたら点灯
} else {
Menu_Select++;
if (Menu_Select > 4) Menu_Select = 1;
}
backlightOfftime = millis() + backlightOnDuration;
}
last_interrupt_time_menu = interrupt_time_menu;
}
3Dプリント用カスタムパーツとエンクロージャ
- 屋外ユニットベース STL (PETG、インフィル 30%)
- 屋外ユニットカバー STL (PETG、インフィル 30%)
- 屋外ユニット漏斗 STL (PETG、インフィル 30%)
- 屋外ユニットフィルター STL (PETG、インフィル 20%、水平トップ/ボトムレイヤーなしで多孔質に出力)
- サブベース STL (バケット取り付け用ピボットロッド接続部、PETG 30%)
- バケット STL (PETG 30%)
- 室内ユニットエンクロージャ STL (PETG 30%)
- 室内ユニットフロントカバー STL (PETG 30%)
- LCD スペーサー STL (シフトレジスタ IC とのクリアランス確保用、PETG 30%)
- Fusion 360 モデル: 屋外ユニット組み立て (
.f3d,.step) - STEP CAD モデル: 室内ユニット (
.step) - Fusion 360 CAD モデル: 室内ユニット (
.f3z) - スイッチ押しボタン STL
回路図
プロジェクト配線図
rain_gauge_NtjqXF6QEw.fzz (Fritzing 形式)
製造プロセス
- 産業・安全用途向け精密圧力計
- スマートブラインド:Arduinoとソーラー電源で実現する自動光制御
- イタリア語ワードクロック自作:Arduino Nano、NeoPixel、LCD、RTCで時刻を文字で表示
- Arduino MKR Fox 1200で作る信頼性の高いSigfox kWhメーター
- デジタルハイトゲージ:精密測定と品質管理ソリューション
- マイクロメータ(マイクロメータネジゲージ):定義・歴史・構造・使い方
- 圧力計の仕組み:種類、機能、メリットを徹底解説
- 歪みゲージ(ストレインゲージ)の原理と応用:機械応力を精密電気信号に変換
- ピラニ真空計の仕組み・用途・メリットを徹底解説
- ダイヤフラム圧力計の仕組み・種類・用途を徹底解説
- ブルドン管圧力計の設計・機能・用途をマスターする