
「温度・湿度・光・音を見える化して、必要なら水やりもできる」。
そんな“小さな施設園芸”をArduinoで作ってみると、電子工作の基礎が一気に身につきます。
こちらがミニ施設園芸装置の解説動画です。
今回は、Grove系のセンサーを中心に組んだ ミニ施設園芸モニター+リレー水やり装置のコードを読み解きながら、装置の全体像とポイントを紹介します。Arduino入門にもかなりおすすめです。
コードはこちらです。配線を同じにすればコピペで動くと思います。
#include <Wire.h>
#include "rgb_lcd.h"
#include <DHT.h>
#include <math.h>
// ====== ピン割り当て ======
// LCD: I2C
// DHT11 DATA -> D2
// 温度センサー(サーミスタ) -> A0
// 光センサー -> A1
// Rotary Angle Sensor(明るさ) -> A2
// Sound Sensor -> A3
// Grove Button(色切替) -> D3
// Grove Touch Sensor(ページ切替) -> D5
// Grove Buzzer -> D6
// Grove-Relay -> D4(※D4のLEDを置き換え)
// Grove-LED Button -> D7ポート(SIG1=D7:LED / SIG2=D8:ボタン)
// ----- LCD -----
rgb_lcd lcd;
const int LCD_COLS = 16;
// ----- DHT11 -----
#define DHTPIN 2
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);
// ----- Thermistor -----
const int PIN_THERM = A0;
int B_THERM = 3975;
const float R0 = 10000.0;
// ----- Light / Knob / Sound -----
const int PIN_LIGHT = A1;
const int PIN_KNOB = A2;
const int PIN_SOUND = A3;
// ----- Button / Touch / Buzzer -----
const int BTN_PIN = 3;
const int TOUCH_PIN = 5;
const int BUZZ_PIN = 6;
// ----- Relay(D4)-----
const int RELAY_PIN = 4;
// ----- Grove LED Button(D7ポート:D7=LED, D8=ボタン)-----
const int LEDBTN_LED_PIN = 7; // SIG1
const int LEDBTN_BTN_PIN = 8; // SIG2(押すとLOW)
// ----- 半角カタカナ(文字コード)-----
const uint8_t K_ONDO[] = {0xB5, 0xDD, 0xC4, 0xDE}; // オンド
const uint8_t K_SHITUDO[] = {0xBC, 0xC2, 0xC4, 0xDE}; // シツド
const uint8_t K_AKARUSA[] = {0xB1, 0xB6, 0xD9, 0xBB}; // アカルサ
const uint8_t K_OTO[] = {0xB5, 0xC4}; // オト
void lcdWriteBytes(const uint8_t *bytes, size_t len) { for (size_t i=0;i<len;i++) lcd.write(bytes[i]); }
void lcdClearRow(int row) { lcd.setCursor(0,row); for (int i=0;i<LCD_COLS;i++) lcd.print(' '); }
float readThermistorC(int pin) {
int a = analogRead(pin);
if (a <= 0 || a >= 1023) return NAN;
float R = (1023.0 - a) * R0 / a;
float tempK = 1.0 / (log(R / R0) / B_THERM + (1.0 / 298.15));
return tempK - 273.15;
}
int readSoundAvg(int pin) {
long sum = 0;
for (int i = 0; i < 32; i++) sum += analogRead(pin);
return (int)(sum >> 5);
}
void setBacklightScaled(uint8_t baseR, uint8_t baseG, uint8_t baseB, uint8_t brightness) {
uint8_t r = (uint16_t)baseR * brightness / 255;
uint8_t g = (uint16_t)baseG * brightness / 255;
uint8_t b = (uint16_t)baseB * brightness / 255;
lcd.setRGB(r, g, b);
}
// ===== ブザー =====
void beep(int freqHz, int ms) {
tone(BUZZ_PIN, freqHz, ms);
delay(ms);
noTone(BUZZ_PIN);
}
void tripleBeep() { for (int i=0;i<3;i++){ beep(1200,80); delay(80);} }
// LEDボタン(=リレー操作)専用の「他と違う音」
void relayToggleBeep() {
beep(1800, 50);
delay(30);
beep(1200, 120);
}
// 6色(赤以外)
struct RGB { uint8_t r,g,b; };
const RGB COLORS[6] = {
{0,255,0}, {0,0,255}, {0,255,255}, {255,0,255}, {255,255,0}, {255,255,255}
};
int colorIndex = 0;
// ===== 状態・タイマー =====
unsigned long lastDhtRead=0, lastLcd=0, lastAutoPage=0;
unsigned long manualHoldUntil=0;
int page=0; // 0:DHT 1:A0/A1 2:Sound
float dhtT=NAN, dhtH=NAN;
bool dhtOk=false, dhtOkPrev=true;
// リレー状態(LEDボタンで切替)
bool relayOn = false;
// デバウンス
const unsigned long DEBOUNCE_MS = 30;
// D3ボタン(押すとHIGH想定)
int btnStable=LOW, btnLastRead=LOW; unsigned long btnLastChange=0;
// タッチ(HIGHで反応想定)
int touchStable=LOW, touchLastRead=LOW; unsigned long touchLastChange=0;
// LEDボタン(通常HIGH、押すとLOW)
int lbtnStable=HIGH, lbtnLastRead=HIGH; unsigned long lbtnLastChange=0;
void setup() {
Serial.begin(9600);
dht.begin();
pinMode(BTN_PIN, INPUT);
pinMode(TOUCH_PIN, INPUT);
pinMode(BUZZ_PIN, OUTPUT);
digitalWrite(BUZZ_PIN, LOW);
pinMode(RELAY_PIN, OUTPUT);
digitalWrite(RELAY_PIN, LOW); // 起動時はOFF
pinMode(LEDBTN_LED_PIN, OUTPUT);
pinMode(LEDBTN_BTN_PIN, INPUT);
digitalWrite(LEDBTN_LED_PIN, LOW); // リレーOFFなので消灯
lcd.begin(16,2);
lcd.setRGB(0,255,0);
lcdClearRow(0); lcdClearRow(1);
lcd.setCursor(0,0); lcd.print("START");
lcd.setCursor(0,1); lcd.print("RELAY=D4 BTN=D7");
delay(1200);
lcd.clear();
beep(1500, 80);
}
void loop() {
unsigned long now = millis();
// --- DHT(2秒ごと) ---
if (now - lastDhtRead >= 2000) {
lastDhtRead = now;
dhtH = dht.readHumidity();
dhtT = dht.readTemperature();
dhtOkPrev = dhtOk;
dhtOk = !(isnan(dhtH) || isnan(dhtT));
if (dhtOkPrev == true && dhtOk == false) {
tripleBeep(); // エラーに入った瞬間
}
}
// --- アナログ ---
float thermC = readThermistorC(PIN_THERM);
int lightRaw = analogRead(PIN_LIGHT);
int soundRaw = readSoundAvg(PIN_SOUND);
// つまみ(明るさ)
static int knobSmooth=0;
int knobRaw = analogRead(PIN_KNOB);
knobSmooth = (knobSmooth*7 + knobRaw)/8;
uint8_t brightness = (uint8_t)map(knobSmooth, 0, 1023, 0, 255);
// --- D3ボタン:色切替 ---
int btnRead = digitalRead(BTN_PIN);
if (btnRead != btnLastRead) { btnLastRead=btnRead; btnLastChange=now; }
if (now - btnLastChange >= DEBOUNCE_MS) {
if (btnRead != btnStable) {
btnStable = btnRead;
if (btnStable == HIGH) {
colorIndex = (colorIndex + 1) % 6;
beep(1200, 60);
}
}
}
// --- タッチ:ページ切替 ---
int touchRead = digitalRead(TOUCH_PIN);
if (touchRead != touchLastRead) { touchLastRead=touchRead; touchLastChange=now; }
if (now - touchLastChange >= DEBOUNCE_MS) {
if (touchRead != touchStable) {
touchStable = touchRead;
if (touchStable == HIGH) {
page = (page + 1) % 3;
manualHoldUntil = now + 10000UL; // 10秒固定
beep(2000, 60);
}
}
}
// --- LEDボタン(D7/D8):リレーON/OFF ---
int lbtnRead = digitalRead(LEDBTN_BTN_PIN); // 押すとLOW
if (lbtnRead != lbtnLastRead) { lbtnLastRead=lbtnRead; lbtnLastChange=now; }
if (now - lbtnLastChange >= DEBOUNCE_MS) {
if (lbtnRead != lbtnStable) {
lbtnStable = lbtnRead;
// HIGH→LOW が「押した」
if (lbtnStable == LOW) {
relayOn = !relayOn;
// リレー制御(HIGH=ON, LOW=OFF)
digitalWrite(RELAY_PIN, relayOn ? HIGH : LOW);
// LEDボタン内蔵LED(HIGHで点灯)
digitalWrite(LEDBTN_LED_PIN, relayOn ? HIGH : LOW);
relayToggleBeep(); // 他と違う音
}
}
}
// --- 自動ページ切替(3秒ごと、タッチ後は一時停止) ---
if (now > manualHoldUntil) {
if (now - lastAutoPage >= 3000) {
lastAutoPage = now;
page = (page + 1) % 3;
}
}
// --- LCD更新(250msごと) ---
if (now - lastLcd >= 250) {
lastLcd = now;
// バックライト:DHTエラー時は赤固定、それ以外は6色(明るさはつまみ)
if (!dhtOk) {
setBacklightScaled(255,0,0, brightness);
} else {
RGB c = COLORS[colorIndex];
setBacklightScaled(c.r,c.g,c.b, brightness);
}
lcdClearRow(0); lcdClearRow(1);
// 右上:ページ / リレー状態
lcd.setCursor(10,0);
lcd.print("P"); lcd.print(page+1); lcd.print("/3");
lcd.setCursor(10,1);
lcd.print("RY:");
lcd.print(relayOn ? "ON " : "OFF");
if (page == 0) {
// DHT
if (dhtOk) {
lcd.setCursor(0,0); lcdWriteBytes(K_ONDO, sizeof(K_ONDO));
lcd.print(":"); lcd.print(dhtT,1); lcd.print("C");
lcd.setCursor(0,1); lcdWriteBytes(K_SHITUDO, sizeof(K_SHITUDO));
lcd.print(":"); lcd.print(dhtH,0); lcd.print("%");
} else {
lcd.setCursor(0,0); lcd.print("DHT ERROR");
lcd.setCursor(0,1); lcd.print("RED BACKLIGHT");
}
} else if (page == 1) {
// A0温度 / 光
lcd.setCursor(0,0);
lcd.print("A0"); lcdWriteBytes(K_ONDO, sizeof(K_ONDO));
lcd.print(":");
if (isnan(thermC)) lcd.print("----"); else lcd.print(thermC,1);
lcd.print("C");
lcd.setCursor(0,1);
lcdWriteBytes(K_AKARUSA, sizeof(K_AKARUSA));
lcd.print(":"); lcd.print(lightRaw);
} else {
// 音
lcd.setCursor(0,0);
lcdWriteBytes(K_OTO, sizeof(K_OTO));
lcd.print(":"); lcd.print(soundRaw); lcd.print(" A3");
lcd.setCursor(0,1);
lcd.print("C:"); lcd.print(colorIndex+1); lcd.print("/6");
}
}
}
どんな装置?:ミニ施設園芸の「環境モニター+手動水やりスイッチ」
このコードが作っているのは、ざっくり言うと次の機能を持つ装置です。
LCD(16×2)に環境データを表示
- DHT11:温度・湿度
- サーミスタ(A0):別系統の温度(地温/ケース内温度などの想定)
- 光センサー(A1):明るさ
- 音センサー(A3):周囲の音量(平均値)
バックライト色をボタンで変更(6色を循環)
つまみ(A2)でバックライト輝度調整
タッチセンサーで表示ページ切替(全3ページ)
- 通常は3秒ごとに自動ページ切替
- タッチすると10秒間そのページ固定
LEDボタンでリレーON/OFF(水やりポンプなどの電源を切替)
- リレー状態はLCDに「RY:ON / OFF」で表示
- 切替時は他と違う“専用のビープ音”で分かる
つまり、
「環境を測って表示し、必要なら水やりをスイッチで実行できる」
ミニ施設園芸のコントローラです。
パーツ構成(コードのピン割り当てから読み取れるもの)
- LCD:I2C(rgb_lcd)
- DHT11:D2
- サーミスタ:A0
- 光センサー:A1
- 回転角センサー(つまみ):A2(バックライト輝度)
- 音センサー:A3
- ボタン(色切替):D3
- タッチセンサー(ページ切替):D5
- ブザー:D6
- リレー:D4
- Grove LED Button:D7(LED) / D8(ボタン)
※押すとLOWになるタイプで、ここが“水やりスイッチ”担当
使い勝手が良いポイント:エラー通知とUIが丁寧
1) DHTエラー時は「赤バックライト+トリプルビープ」
DHT11はたまに読み取り失敗することがあるんですが、このコードは
- 読み取りが失敗した瞬間に tripleBeep()
- LCDバックライトは 赤固定
- LCD表示も「DHT ERROR」「RED BACKLIGHT」
と、かなり親切な作りを目指しました。「センサーが壊れた?配線ミス?電源不足?」が見てすぐ分かるので、入門者に優しいです。
2) 操作ごとに音が違う
- 色切替(D3):短いビープ
- ページ切替(タッチ):別の高音ビープ
- リレー切替(LEDボタン):専用の“2音”ビープ
液晶を見なくても操作が分かるの、地味に良いんですよね。
水やり(リレー)の動き:LEDボタンでトグル切替
水やりに相当するのがここ。
- LEDボタンを押す(D8がHIGH→LOW)
relayOn = !relayOn;で状態反転digitalWrite(RELAY_PIN, relayOn ? HIGH : LOW);
→ リレーONでポンプ(または電磁弁)に通電
- LEDボタン内蔵LED(D7)も連動して点灯
LCD右下にも常に
RY:ON / OFF
が出るので、水やりがONのまま放置…も防げます。
※注意:このコードは「手動でON/OFFする」タイプです。
自動水やり(湿度や時間でON)にしたい場合は、ここを拡張すると楽しいです。
LCD表示の構成:3ページ+自動切替
ページは page = 0..2 の3種類。
(推測:オト:xxxx のような表示になりそう)
ページ1(page==0):DHT11 温度/湿度
- オンド:xx.xC / シツド:xx%
ページ2(page==1):A0温度(サーミスタ)+光
- A0オンド:xx.xC / アカルサ:xxxx
ページ3(page==2):音センサー(平均値)
- (推測:オト:xxxx のような表示になりそう)
LCDに半角カタカナを出すために、文字コードをバイト列で持って lcd.write() しているのも面白いポイントです(「オンド」「シツド」「アカルサ」「オト」)。
入門としておすすめな理由:学べる要素が全部盛り
この作品、Arduinoの基礎トピックが本当にバランスよく入っています。
- デジタル入力(ボタン、タッチ)
- アナログ入力(温度、光、つまみ、音)
- I2Cデバイス(LCD)
- 出力(ブザー、LED)
- リレー制御(外部装置のON/OFF)
- デバウンス処理(チャタリング対策)
- タイマ(millisで周期処理:2秒、250ms、3秒など)
- エラー処理(DHT失敗時の分岐)
「Lチカだけ卒業して、次に何作ろう?」の題材としてかなり強いです。
実際に“ミニ施設園芸”にするなら:構成アイデア
リレーで制御する先は、例えばこういうのが現実的です。
- **小型水中ポンプ(5V/12V)**で点滴チューブに送水
- 電磁弁で給水ラインを開閉
- ミスト発生器(湿度上げ)
- ファン(換気・温度下げ)
まずは安全に、低電圧(5V/12V)で完結する構成がおすすめです。
もう一歩の改造案(次の記事ネタにも)
せっかく“施設園芸”なので、次の拡張がめちゃくちゃ楽しいです。
自動水やり
- 土壌水分センサー追加(または重量センサー)
- 「一定以下なら○秒だけON」
水やりタイマー
- RTC(DS3231)で時刻管理 → 朝だけ水やり
安全装置
- リレーONの最大時間を制限(例:30秒で強制OFF)
ログ取り
- SDカード、またはシリアルでPCへ送信
表示の見やすさ改善
ページ名を左上に出す、単位を統一する、など
まとめ
このコードが作っているのは、
- 環境(温湿度/温度/光/音)をLCDで見える化し
- 操作しやすいUI(色・輝度・ページ・音フィードバック)を持ち
- リレーで水やり(外部装置のON/OFF)までできる
という、まさに ミニ施設園芸のコントローラです。
Arduino入門としても「センサー+表示+操作+リレー制御」が一通り学べて、完成後もちゃんと役に立つタイプの作品なので、かなりおすすめです。
是非作ってみてください。






LEAVE A REPLY