Arduino入門にちょうどいい!センサーで見える化&リレーで水やりする「ミニ施設園芸」づくり

Arduino入門にちょうどいい!センサーで見える化&リレーで水やりする「ミニ施設園芸」づくり

「温度・湿度・光・音を見える化して、必要なら水やりもできる」。
そんな“小さな施設園芸”を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入門としても「センサー+表示+操作+リレー制御」が一通り学べて、完成後もちゃんと役に立つタイプの作品なので、かなりおすすめです。

是非作ってみてください。

About The Author

Hideki
東京大学発AIスタートアップ企業でロボット開発室室長、画像解析室室長、動画解析室室長を務め、AIエンジニアとしても画像認識関連の特許を在籍中に3つ取得。その後、KDDIグループ内でプロダクトリーダーとして自然言語処理パッケージの自社開発を経て、現在はAGRISTのテックリードとして農業の人手不足の解決に向けた収穫ロボットの開発にチャレンジしている。ロボットは技術の総合格闘技との考え方から、AIだけでなく、ハードやエレキ、通信からクラウド、IOTまで幅広く手掛けることができる。最近では人とロボットの共存を目指すべく、性能だけを追い求める開発から「感動やワクワク体験」をデザインできるロボットの研究を進めており、人とロボットがうまく共存できる世界を作り出したいと日々行動している。

LEAVE A REPLY

*
*
* (公開されません)