はじめてのESP32。(Hello WorldしたりLチカしたりミラーボールチカしたり土壌の水分計測する)

はじめてのESP32。(Hello WorldしたりLチカしたりミラーボールチカしたり土壌の水分計測する)

初心者がESP32を触り始め、Hello WorldやLチカからミラーボールチカ、土壌水分計測まで試してみた記録をお届けします。実際のセットアップから実装まで紹介していますので、電子工作初挑戦の方の参考になれば幸いです。
2026.07.05

はじめに

皆様こんにちは、あかいけです。

マイコンで電子工作をしたことはありますか?私はありません。
というのも普段はクラウドまわりを触ることが多く、「実体のあるモノ」を自分の手で動かす経験はほぼゼロでした。

そんな私ですが、過去にRaspberry Piで電子工作をやってみたくなった時期があり、購入時に高すぎて代わりにESP32を買っていました。
そして、気づけば半年ほど部屋の隅で眠っていることに先週気づきました。

というわけで今回は、完全な初心者がESP32を触りはじめてから、定番の「Hello World」「Lチカ」を経て、「ミラーボールチカ」「土壌の水分計測」までをやってみた記録としてまとめます。

本ブログで使うもの

マイコン周りは大体Amazonで買いました。

  • PC
    • macOS 26.2
    • Arduino IDE 2.3.10
  • マイコン
    • ESP32(FNK0090B)
    • USBケーブル(ESP32についてたやつ)
    • ブレッドボード(ESP32ブレイクアウトボード)
    • ジャンパーワイヤセット(arduino用ワイヤ—ゲ—ジ28AWG)
    • 容量性土壌水分センサー(電圧3.3〜5.5VArduino用)
  • その他
    • ミラーボール

ESP32について

ESP32は、Wi-FiとBluetoothを標準搭載した安価なマイコンです。
1台で無線通信までこなせるため、IoT工作の定番として広く使われています。

なお、私が使っているのはFreenoveのボードを買いました。
公式チュートリアルとドキュメントが充実しているので、詰まったときはこちらを参照するのが良さそうです。

https://freenove.com/tutorial
https://docs.freenove.com/projects/fnk0090/en/latest/

Arduino IDE

まずは開発環境が必要なので用意します。

ESP32はArduino IDEが使えるようなので、これを使っていきます。
Arduino IDEは、コードを書いてマイコンに書き込むための統合開発環境で、無料で使えます。

ダウンロード

以下の公式サイトから、インストーラーをダウンロードしてインストールします。

https://www.arduino.cc/en/software/

言語設定

デフォルトだと英語表示になっているので、日本語のほうがわかりやすい方は設定から言語を変更しておきます。
Preferences(設定)から言語を「日本語」に切り替えられます。

スクリーンショット 2026-07-05 17.21.26

スクリーンショット 2026-07-05 17.22.00

ボードマネージャ

Arduino IDEはインストール直後の状態ではESP32を認識できません。

ESP32向けの開発ができるように、ボードマネージャからESP32のボード定義を追加します。
ボードマネージャで「esp32」を検索して「esp32 by Espressif Systems」をインストールします。

スクリーンショット 2026-07-05 17.23.20

以下のように表示されればインストール完了です。

Platform esp32:esp32@3.3.10 installed

スクリーンショット 2026-07-05 17.25.05

ボード選択

インストールが終わったら、実際に使うボードと接続先のポートを選択します。
まずはESP32をUSBケーブルでPCに接続してから、ボードの種類とシリアルポートを指定します。

1000005655

どれがESP32のシリアルポートかわからない時は、コマンドで確認するとわかりやすいです。
私の環境では/dev/cu.usbserial-1110がESP32でした(usbserialという名前が目印です)。

% ls -l /dev/tty.* /dev/cu.*

crw-rw-rw-  1 root  wheel  0x9000003  6月 30 10:03 /dev/cu.Bluetooth-Incoming-Port
crw-rw-rw-  1 root  wheel  0x9000001  6月 30 10:03 /dev/cu.debug-console
crw-rw-rw-  1 root  wheel  0x9000007  6月 30 10:03 /dev/cu.OpenComm2byShokz
crw-rw-rw-  1 root  wheel  0x9000009  7月  5 16:49 /dev/cu.usbserial-1110
crw-rw-rw-  1 root  wheel  0x9000005  6月 30 10:03 /dev/cu.V15
crw-rw-rw-  1 root  wheel  0x9000002  6月 30 10:03 /dev/tty.Bluetooth-Incoming-Port
crw-rw-rw-  1 root  wheel  0x9000000  6月 30 10:03 /dev/tty.debug-console
crw-rw-rw-  1 root  wheel  0x9000006  6月 30 10:03 /dev/tty.OpenComm2byShokz
crw-rw-rw-  1 root  wheel  0x9000008  7月  5 16:49 /dev/tty.usbserial-1110
crw-rw-rw-  1 root  wheel  0x9000004  6月 30 10:03 /dev/tty.V15

あとはIDE側から「ボードを選択」をクリックして、

スクリーンショット 2026-07-05 17.27.34

対象となるボード(ESP32 Dev Module)と先ほど確認したシリアルポートを選びます。

スクリーンショット 2026-07-05 17.28.13

スクリーンショット 2026-07-05 17.28.21

シリアルモニタ

ESP32からPCへ文字(ログ)を送るための表示画面が「シリアルモニタ」です。
Serial.println()で出力した内容はここに表示されます。

スクリーンショット 2026-07-05 17.29.47

スクリーンショット 2026-07-05 17.30.15

Hello World

環境が整ったので、プログラミングの定番「Hello World」から始めます。
まずはArduino(ESP32)のプログラムの基本構文を確認しておきましょう。

構文について

Arduinoのプログラムは、大きく2つの関数で構成されます。
setup()は起動時に一度だけ実行される初期化処理、loop()はその後ずっと繰り返し実行される処理です。

void setup() {
  // put your setup code here, to run once:

}

void loop() {
  // put your main code here, to run repeatedly:

}

これを踏まえて、1秒ごとにシリアルモニタへ「Hello World!」と出力するコードを書きます。
setup()でシリアル通信を開始し、loop()の中で文字を出力し続ける、という流れです。

void setup() {
    Serial.begin(9600);
    delay(1000);
}

void loop() {
    Serial.println("Hello World!");
    delay(1000);
}

書いたら、画面左上の書き込みボタンでESP32へアップロードします。

スクリーンショット 2026-07-05 17.50.39

…が、エラーが発生して書き込みに失敗しています。

A fatal error occurred: The chip stopped responding.
Stub flasher running.
Changing baud rate to 921600...
Changed.

Hard resetting via RTS pin...
Failed uploading: uploading error: exit status 2

どうやら書き込み時の通信速度が速すぎて、ESP32が応答しきれていないようです。

なのでアップロード時の通信速度を115200に下げてみます。
通信速度はツールの「Upload Speed」から変更できます。

スクリーンショット 2026-07-05 17.52.13

その後再実行すると書き込みが成功しました。
シリアルモニタにもHello Worldが表示されているのでOKそうです。

スクリーンショット 2026-07-05 17.53.46

また正常に書き込めると、ボードのLEDの光り方が変わりました。
(このLEDは書き込み中と待機中など、タイミングによって光り方が変わります)

1000005656

Lチカ

文字が出せたので、次は電子工作の定番「Lチカ」をやります。

本来であれば個別に外付けのLEDを買って接続して光らせたいところですが、買い忘れたのでESP本体のLEDを光らせます。
今回使っているFreenoveのボードには、フルカラーLEDと単色の青色LEDが搭載されているので、こちらをLチカさせます。

どのGPIOにどのLEDが繋がっているかは、ボードのピン配置図(ピンアウト)で確認できます。
この図は公式ドキュメントに含まれていました。

https://docs.freenove.com/projects/fnk0090/en/latest/

Freenove_ESP32_WROOM_Board_Pinout

今回光らせる2つのLEDは、それぞれ以下のGPIOに繋がっています。

LED GPIO
フルカラーLED(WS2812) GPIO16
単色の青色LED GPIO2

またいい感じに光らせるライブラリがあったので、こちらをインストールして使います。
(今回はAdafruit Neopixel by Adafruitを使いました)

スクリーンショット 2026-07-05 18.34.58

次にコードですが、点滅させるだけだと少し寂しいので、2つのLEDがゆったり光る感じにしてみます。
(フルカラーの方は色をじわじわ変えて、青い方はふわっと明るさが上下するイメージ)

// Freenove ESP32-WROOM 内蔵の2つのLEDを両方ゆったり光らせる
// ・フルカラーLED(WS2812 / NeoPixel)… GPIO16
// ・単色の青LED                      … GPIO2
//
// 必要ライブラリ: Adafruit NeoPixel

#include <Adafruit_NeoPixel.h>

#define RGB_PIN   16   // 内蔵WS2812(フルカラー)
#define BLUE_PIN  2    // 内蔵の単色青LED
#define NUM_LEDS  1

Adafruit_NeoPixel pixel(NUM_LEDS, RGB_PIN, NEO_GRB + NEO_KHZ800);

void setup() {
  pixel.begin();
  pixel.setBrightness(80);         // フルカラーの明るさ(0〜255)
  pixel.show();

  ledcAttach(BLUE_PIN, 5000, 8);   // 青LEDをPWM(明るさ調整)で使う
}

void loop() {
  // 経過時間から、ゆっくり動く波(0.0〜1.0)を2つ作る
  float t = millis() / 1000.0;

  // --- フルカラーLED:色相をゆっくり巡らせる ---
  // 色相:約20秒で一周(数字を大きくすると一周が速くなる)
  uint16_t hue = (uint16_t)(millis() * 65536UL / 20000UL);
  // 明るさ:周期 約6秒
  float rgbWave = (sin(t * 2.0 * PI / 6.0) + 1.0) / 2.0;   // 0.0〜1.0
  uint8_t rgbVal = 40 + rgbWave * 215;                     // 40〜255の範囲で揺らす
  pixel.setPixelColor(0, pixel.gamma32(pixel.ColorHSV(hue, 255, rgbVal)));
  pixel.show();

  // --- 青LED:フルカラーとは少しずらしたゆっくりした周期 ---
  float blueWave = (sin(t * 2.0 * PI / 8.0) + 1.0) / 2.0;  // 周期 約8秒
  uint8_t blueVal = blueWave * 255;
  ledcWrite(BLUE_PIN, blueVal);

  delay(20);   // 更新スピード(大きくするとカクカク、小さくすると滑らか)
}

書き込むと、こんな感じでふわふわと色が移り変わりながら光ってくれます。
(実物はもっといい感じに光っていたんですが、GIFだとわかりづらいですね…)

1000005606 (1)

ミラーボールチカ

Lチカが思ったより地味だったので、もっと派手に光るものをチカさせたくなりました。
というわけでいつものミラーボールをチカらせます。合わせてWi-Fi通信も使ってみます。

事前準備としてミラーボールはSwitchBotのスマートプラグ経由でON/OFFできるようにしてあるので、ESP32からSwitchBotのAPIを叩いてON/OFFを繰り返せば、ミラーボールチカができます。

なお、SwitchBotのスマートプラグやAPIの具体的な設定については、過去にミラーボールを扱ったブログで詳しく紹介しているので、そちらをご参照ください。

https://dev.classmethod.jp/articles/cloud-watch-alarm-mirror-ball-tips/
https://dev.classmethod.jp/articles/claude-code-hooks-mirror-ball-tips/
https://dev.classmethod.jp/articles/okta-access-requests-mirror-ball-tips/

コードは以下の通りです。
やっていることをざっくり並べると、こんな感じです。

  • Wi-Fiに接続する
  • NTPで時刻を同期する(後述の署名に正確な現在時刻が必要なため)
  • トークン・タイムスタンプ・nonceからHMAC-SHA256の署名を作る
  • 署名をヘッダーに付けてSwitchBotのAPIを叩く
  • 3秒ごとにturnOnturnOffを交互に送る

SwitchBotのAPIは署名を付けないと叩けないので、その生成まわりをESP32側で実装するのが少し手間でした。

#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include "mbedtls/md.h"
#include "mbedtls/base64.h"
#include <time.h>
#include <sys/time.h>

// ==== 設定:実際の値に置き換える ====
const char* WIFI_SSID     = "your_wifi_ssid";
const char* WIFI_PASSWORD = "your_wifi_password";

const char* SWITCHBOT_TOKEN  = "your_token";
const char* SWITCHBOT_SECRET = "your_secret";
const char* DEVICE_ID        = "XXXXXXXXXXXX";
// =========================================

const char* API_HOST = "https://api.switch-bot.com/v1.1";

// ---- HMAC-SHA256 → Base64 で署名を作る ----
String makeSign(const String& payload) {
  byte hmacResult[32];
  mbedtls_md_context_t ctx;
  mbedtls_md_init(&ctx);
  mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1);
  mbedtls_md_hmac_starts(&ctx,
    (const unsigned char*)SWITCHBOT_SECRET, strlen(SWITCHBOT_SECRET));
  mbedtls_md_hmac_update(&ctx,
    (const unsigned char*)payload.c_str(), payload.length());
  mbedtls_md_hmac_finish(&ctx, hmacResult);
  mbedtls_md_free(&ctx);

  unsigned char b64[64];
  size_t olen = 0;
  mbedtls_base64_encode(b64, sizeof(b64), &olen, hmacResult, 32);
  b64[olen] = '\0';
  return String((char*)b64);
}

// ---- 13桁のミリ秒タイムスタンプ ----
String makeTimestamp() {
  struct timeval tv;
  gettimeofday(&tv, nullptr);
  uint64_t ms = (uint64_t)tv.tv_sec * 1000ULL + tv.tv_usec / 1000ULL;
  char buf[21];
  snprintf(buf, sizeof(buf), "%llu", (unsigned long long)ms);
  return String(buf);
}

// ---- nonce ----
String makeNonce() {
  const char* hex = "0123456789abcdef";
  char buf[17];
  for (int i = 0; i < 16; i++) buf[i] = hex[esp_random() & 0x0F];
  buf[16] = '\0';
  return String(buf);
}

// ---- デバイスにコマンド送信 ----
void sendCommand(const char* command) {
  String t     = makeTimestamp();
  String nonce = makeNonce();
  String sign  = makeSign(String(SWITCHBOT_TOKEN) + t + nonce);

  WiFiClientSecure client;
  client.setInsecure();  // 証明書検証をスキップ

  HTTPClient https;
  String url = String(API_HOST) + "/devices/" + DEVICE_ID + "/commands";
  https.begin(client, url);
  https.addHeader("Content-Type", "application/json; charset=utf8");
  https.addHeader("Authorization", SWITCHBOT_TOKEN);
  https.addHeader("sign", sign);
  https.addHeader("t", t);
  https.addHeader("nonce", nonce);

  String body = String("{\"command\":\"") + command +
                "\",\"parameter\":\"default\",\"commandType\":\"command\"}";

  int code = https.POST(body);
  Serial.printf("HTTP status: %d\n", code);
  Serial.println(https.getString());  // APIのレスポンス(statusCode:100なら成功)
  https.end();
}

void setup() {
  Serial.begin(115200);
  delay(1000);

  // ① WiFi接続
  Serial.printf("Connecting to %s", WIFI_SSID);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println(" connected");
  Serial.print("IP: ");
  Serial.println(WiFi.localIP());

  // ② NTPで時刻同期(タイムスタンプ生成に利用)
  configTime(0, 0, "pool.ntp.org", "time.nist.gov");
  Serial.print("Syncing time");
  time_t now = time(nullptr);
  while (now < 1700000000) {  // 妥当な現在時刻になるまで待機
    delay(500);
    Serial.print(".");
    now = time(nullptr);
  }
  Serial.println(" done");
}

void loop() {
  // 3秒ごとにON/OFFを交互に送信
  static bool on = true;

  if (on) {
    Serial.println(">> turnOn");
    sendCommand("turnOn");
  } else {
    Serial.println(">> turnOff");
    sendCommand("turnOff");
  }

  on = !on;   // 次回は逆にする
  delay(3000);
}

書き込むと、3秒ごとにミラーボールがON/OFFを繰り返し、無事にミラーボールチカができました。
キレイだね…。

1000005604 (1)

土壌の水分計測

最後はにもう少し実用的な工作として、センサー入力に挑戦します。
植物を育てている方なら「土がどれくらい乾いているか」を数値で知りたくなることはありませんか?私はあります。

というわけで、静電容量式の土壌水分センサーをESP32に繋いで、土の水分量を計測してみます。

配線

まずはセンサーとESP32を配線します。

今回のセンサーはアナログ出力タイプなので、電源(VCC・GND)に加えて、出力ピン(AOUT)をESP32のアナログ入力ピンに繋ぎます。
ESP32のGPIO34はADC(アナログ-デジタル変換)に対応した入力専用ピンなので、ここに繋ぎます。

センサー側 ESP32側
GND GND
VCC 3V3
AOUT GPIO34
  • センサー側

1000005658

そして上記の配線通りに、ブレッドボード、ジャンパーワイヤーを使っていい感じに繋ぎます。

1000005657

次にコードは以下の通りです。
やっていることをざっくり並べると、こんな感じです。

  • センサーのアナログ値を読み取る(ESP32のADCは12bit、0〜4095)
  • ノイズ対策のため複数回読んで平均を取る
  • 乾いた状態(DRY)と濡れた状態(WET)の基準値をもとに0〜100%に変換する
  • 1秒ごとにシリアルモニタへ出力する

DRYWETはセンサーや個体によって変わるので、まずは仮の値を入れておき、後で実測して書き換えます。

// Freenove ESP32-WROOM + 静電容量式 土壌水分センサー(Hailege, アナログ出力)
//
// 配線:
//   センサー VCC  → ESP32 3V3
//   センサー GND  → ESP32 GND
//   センサー AOUT → ESP32 GPIO34 (ADC1_CH6, 入力専用)

#define SOIL_PIN 34   // 土壌センサーのアナログ出力を繋いだピン

// ▼▼▼ キャリブレーション値(まずは仮。あとで実測して書き換える)▼▼▼
// DRY  = 空気中(乾いた状態)で表示された値
// WET  = 水に挿した(濡れた状態)で表示された値
// 静電容量式は「乾くと値が大きく/濡れると値が小さく」なるのが一般的
int DRY = 3000;   // 乾いているときの生の値
int WET = 1200;   // 濡れているときの生の値
// ▲▲▲ ここを実測値に合わせると % 表示が正確になる ▲▲▲

void setup() {
  Serial.begin(115200);
  delay(1000);
  analogReadResolution(12);   // ESP32のADCは12bit(0〜4095)
}

void loop() {
  // 数回読んで平均を取りノイズを減らす
  long sum = 0;
  const int N = 20;
  for (int i = 0; i < N; i++) {
    sum += analogRead(SOIL_PIN);
    delay(5);
  }
  int raw = sum / N;

  // 生の値を DRY〜WET の範囲で 0〜100% に変換
  int percent = map(raw, DRY, WET, 0, 100);
  percent = constrain(percent, 0, 100);   // 0未満/100超えを丸める

  Serial.print("raw: ");
  Serial.print(raw);
  Serial.print("   moisture: ");
  Serial.print(percent);
  Serial.println("%");

  delay(1000);   // 1秒ごとに測定
}

それでは、正確なパーセント表示にするためのキャリブレーション(基準値の実測)を行っていきます。
まずは「完全に乾いた状態=0%」の基準値を取るため、センサーを空気中に置いたまま値を読ませてみます。

空気中での計測

1000005659

センサーを空気中に置いた状態では、値はおおむね3186前後で安定していました。
これを「乾いているとき(0%)」の基準値にします。

乾き(空気中)

平均 3186.6 / 中央値 3186 / 範囲 3160〜3217

100回分の計測データ
raw: 3188   moisture: 0%
raw: 3194   moisture: 0%
raw: 3183   moisture: 0%
raw: 3188   moisture: 0%
raw: 3183   moisture: 0%
raw: 3182   moisture: 0%
raw: 3184   moisture: 0%
raw: 3201   moisture: 0%
raw: 3188   moisture: 0%
raw: 3182   moisture: 0%
raw: 3186   moisture: 0%
raw: 3179   moisture: 0%
raw: 3185   moisture: 0%
raw: 3199   moisture: 0%
raw: 3185   moisture: 0%
raw: 3195   moisture: 0%
raw: 3189   moisture: 0%
raw: 3182   moisture: 0%
raw: 3186   moisture: 0%
raw: 3188   moisture: 0%
raw: 3192   moisture: 0%
raw: 3186   moisture: 0%
raw: 3186   moisture: 0%
raw: 3187   moisture: 0%
raw: 3194   moisture: 0%
raw: 3184   moisture: 0%
raw: 3160   moisture: 0%
raw: 3186   moisture: 0%
raw: 3178   moisture: 0%
raw: 3179   moisture: 0%
raw: 3188   moisture: 0%
raw: 3203   moisture: 0%
raw: 3186   moisture: 0%
raw: 3178   moisture: 0%
raw: 3181   moisture: 0%
raw: 3185   moisture: 0%
raw: 3171   moisture: 0%
raw: 3189   moisture: 0%
raw: 3186   moisture: 0%
raw: 3186   moisture: 0%
raw: 3169   moisture: 0%
raw: 3192   moisture: 0%
raw: 3186   moisture: 0%
raw: 3184   moisture: 0%
raw: 3189   moisture: 0%
raw: 3186   moisture: 0%
raw: 3191   moisture: 0%
raw: 3187   moisture: 0%
raw: 3186   moisture: 0%
raw: 3185   moisture: 0%
raw: 3185   moisture: 0%
raw: 3195   moisture: 0%
raw: 3192   moisture: 0%
raw: 3191   moisture: 0%
raw: 3191   moisture: 0%
raw: 3200   moisture: 0%
raw: 3190   moisture: 0%
raw: 3193   moisture: 0%
raw: 3186   moisture: 0%
raw: 3185   moisture: 0%
raw: 3182   moisture: 0%
raw: 3186   moisture: 0%
raw: 3187   moisture: 0%
raw: 3186   moisture: 0%
raw: 3187   moisture: 0%
raw: 3186   moisture: 0%
raw: 3186   moisture: 0%
raw: 3187   moisture: 0%
raw: 3187   moisture: 0%
raw: 3187   moisture: 0%
raw: 3192   moisture: 0%
raw: 3185   moisture: 0%
raw: 3187   moisture: 0%
raw: 3185   moisture: 0%
raw: 3189   moisture: 0%
raw: 3191   moisture: 0%
raw: 3186   moisture: 0%
raw: 3178   moisture: 0%
raw: 3189   moisture: 0%
raw: 3190   moisture: 0%
raw: 3196   moisture: 0%
raw: 3184   moisture: 0%
raw: 3182   moisture: 0%
raw: 3190   moisture: 0%
raw: 3193   moisture: 0%
raw: 3180   moisture: 0%
raw: 3178   moisture: 0%
raw: 3172   moisture: 0%
raw: 3191   moisture: 0%
raw: 3184   moisture: 0%
raw: 3179   moisture: 0%
raw: 3180   moisture: 0%
raw: 3201   moisture: 0%
raw: 3185   moisture: 0%
raw: 3184   moisture: 0%
raw: 3177   moisture: 0%
raw: 3183   moisture: 0%
raw: 3190   moisture: 0%
raw: 3183   moisture: 0%
raw: 3217   moisture: 0%

次は反対に「完全に濡れた状態=100%」の基準値を取ります。
センサーを水に挿して値を読ませてみましょう。

水中での計測

1000005660

水中では値が1089前後まで下がりました。
コメントにも書いた通り、静電容量式センサーは「乾くと値が大きく/濡れると値が小さく」なるので、想定通りの挙動です。これが「濡れているとき(100%)」の基準値になります。

濡れ(水中)

平均 1089.4 / 中央値 1089 / 範囲 1074〜1112

100回分の計測データ
raw: 1110   moisture: 100%
raw: 1088   moisture: 100%
raw: 1096   moisture: 100%
raw: 1089   moisture: 100%
raw: 1089   moisture: 100%
raw: 1089   moisture: 100%
raw: 1091   moisture: 100%
raw: 1089   moisture: 100%
raw: 1089   moisture: 100%
raw: 1089   moisture: 100%
raw: 1090   moisture: 100%
raw: 1090   moisture: 100%
raw: 1089   moisture: 100%
raw: 1086   moisture: 100%
raw: 1089   moisture: 100%
raw: 1094   moisture: 100%
raw: 1093   moisture: 100%
raw: 1095   moisture: 100%
raw: 1098   moisture: 100%
raw: 1080   moisture: 100%
raw: 1082   moisture: 100%
raw: 1086   moisture: 100%
raw: 1104   moisture: 100%
raw: 1091   moisture: 100%
raw: 1087   moisture: 100%
raw: 1077   moisture: 100%
raw: 1080   moisture: 100%
raw: 1076   moisture: 100%
raw: 1087   moisture: 100%
raw: 1089   moisture: 100%
raw: 1091   moisture: 100%
raw: 1087   moisture: 100%
raw: 1093   moisture: 100%
raw: 1082   moisture: 100%
raw: 1080   moisture: 100%
raw: 1088   moisture: 100%
raw: 1092   moisture: 100%
raw: 1093   moisture: 100%
raw: 1089   moisture: 100%
raw: 1095   moisture: 100%
raw: 1089   moisture: 100%
raw: 1089   moisture: 100%
raw: 1085   moisture: 100%
raw: 1099   moisture: 100%
raw: 1094   moisture: 100%
raw: 1091   moisture: 100%
raw: 1089   moisture: 100%
raw: 1081   moisture: 100%
raw: 1098   moisture: 100%
raw: 1074   moisture: 100%
raw: 1086   moisture: 100%
raw: 1097   moisture: 100%
raw: 1084   moisture: 100%
raw: 1088   moisture: 100%
raw: 1086   moisture: 100%
raw: 1082   moisture: 100%
raw: 1112   moisture: 100%
raw: 1092   moisture: 100%
raw: 1091   moisture: 100%
raw: 1078   moisture: 100%
raw: 1100   moisture: 100%
raw: 1084   moisture: 100%
raw: 1088   moisture: 100%
raw: 1092   moisture: 100%
raw: 1085   moisture: 100%
raw: 1095   moisture: 100%
raw: 1092   moisture: 100%
raw: 1088   moisture: 100%
raw: 1090   moisture: 100%
raw: 1082   moisture: 100%
raw: 1093   moisture: 100%
raw: 1075   moisture: 100%
raw: 1089   moisture: 100%
raw: 1095   moisture: 100%
raw: 1100   moisture: 100%
raw: 1089   moisture: 100%
raw: 1080   moisture: 100%
raw: 1090   moisture: 100%
raw: 1089   moisture: 100%
raw: 1095   moisture: 100%
raw: 1099   moisture: 100%
raw: 1088   moisture: 100%
raw: 1088   moisture: 100%
raw: 1088   moisture: 100%
raw: 1088   moisture: 100%
raw: 1088   moisture: 100%
raw: 1096   moisture: 100%
raw: 1090   moisture: 100%
raw: 1084   moisture: 100%
raw: 1092   moisture: 100%
raw: 1087   moisture: 100%
raw: 1090   moisture: 100%
raw: 1088   moisture: 100%
raw: 1083   moisture: 100%
raw: 1100   moisture: 100%
raw: 1086   moisture: 100%
raw: 1083   moisture: 100%
raw: 1092   moisture: 100%
raw: 1091   moisture: 100%
raw: 1089   moisture: 100%

乾いた状態と濡れた状態の基準値が取れたので、コードのDRYWETを実測値に書き換えます。
それぞれ前述の中央値を入れてみます。

// ▼▼▼ キャリブレーション値(まずは仮。あとで実測して書き換える)▼▼▼
// DRY  = 空気中(乾いた状態)で表示された値
// WET  = 水に挿した(濡れた状態)で表示された値
// 静電容量式は「乾くと値が大きく/濡れると値が小さく」なるのが一般的
int DRY = 3186;   // 乾いているときの生の値
int WET = 1089;   // 濡れているときの生の値
// ▲▲▲ ここを実測値に合わせると % 表示が正確になる ▲▲▲

実際に計測してみる

キャリブレーションが済んだので、実際に土に挿して計測してみます。
まずは昨日水やりして適度に湿った土に挿してみます。
(こちらはひめの氏より受け継いだハオルチアです)

1000005662

19:25:18.054 -> raw: 2159   moisture: 48%
19:25:19.137 -> raw: 2157   moisture: 49%
19:25:20.250 -> raw: 2156   moisture: 49%
19:25:21.364 -> raw: 2154   moisture: 49%
19:25:22.450 -> raw: 2149   moisture: 49%
19:25:23.564 -> raw: 2153   moisture: 49%
19:25:24.646 -> raw: 2156   moisture: 49%
19:25:25.768 -> raw: 2149   moisture: 49%
19:25:26.855 -> raw: 2147   moisture: 49%

湿った土では約49%と表示されました。
感覚的にも「そこそこ湿っている」状態と一致していて、いい感じです。

次に、乾いた土でも試してみます。
(こちらはリボベジ中のパイナップルです)

1000005663

19:27:00.337 -> raw: 3089   moisture: 4%
19:27:01.452 -> raw: 3080   moisture: 5%
19:27:02.538 -> raw: 3118   moisture: 3%
19:27:03.655 -> raw: 3095   moisture: 4%
19:27:04.737 -> raw: 3083   moisture: 4%
19:27:05.853 -> raw: 3098   moisture: 4%
19:27:06.936 -> raw: 3091   moisture: 4%
19:27:08.050 -> raw: 3089   moisture: 4%

乾いた土では約4%と、こちらもきちんと低い値が出ました。
湿り具合によって数値が変化しているので、土壌水分センサーとして問題なく機能していることが確認できました。
(このあとちゃんとお水をあげています、パイナップルは無事なのでご安心ください)

これらを応用すれば、「一定以下の水分量になったら通知する」「数日間水分量何%以下だったら通知する」といった、鉢植えの水やりリマインダーのようなものも作れそうですね。

さいごに

以上、はじめてのESP32でした。

半年ほど眠っていたESP32でしたが、いざ触ってみるとHello WorldからLチカ、ミラーボールチカ、土壌の水分計測まで、なんだかんだで色々遊べました。
自分の書いたコードで実際にモノが光ったり動いたりするのは、思っていた以上に楽しいですね!

特に土壌の水分計測は、Wi-Fi通信と組み合わせれば「乾いたら通知する」みたいなこともできそうなので、そのうちオフィスグリーンを救おうと思います。

本ブログが「電子工作ちょっと気になるな」という方の、最初の一歩の参考になれば幸いです。


製造業のクラウド活用とデジタル化を支援します

クラスメソッドの専門家による包括的なクラウド導入とデジタル化支援で、製造業の業務効率を最大化しましょう。AWSの導入から運用、最適化まで、最新技術と豊富な知見であらゆる課題に対応します。生産ラインのデジタル化やデータ活用、IoTの導入事例もございます。ぜひ、弊社の実績をご覧ください。

製造業界での支援内容を見る

この記事をシェアする

関連記事