
はじめてのESP32。(Hello WorldしたりLチカしたりミラーボールチカしたり土壌の水分計測する)
はじめに
皆様こんにちは、あかいけです。
マイコンで電子工作をしたことはありますか?私はありません。
というのも普段はクラウドまわりを触ることが多く、「実体のあるモノ」を自分の手で動かす経験はほぼゼロでした。
そんな私ですが、過去に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のボードを買いました。
公式チュートリアルとドキュメントが充実しているので、詰まったときはこちらを参照するのが良さそうです。
Arduino IDE
まずは開発環境が必要なので用意します。
ESP32はArduino IDEが使えるようなので、これを使っていきます。
Arduino IDEは、コードを書いてマイコンに書き込むための統合開発環境で、無料で使えます。
ダウンロード
以下の公式サイトから、インストーラーをダウンロードしてインストールします。
言語設定
デフォルトだと英語表示になっているので、日本語のほうがわかりやすい方は設定から言語を変更しておきます。
Preferences(設定)から言語を「日本語」に切り替えられます。


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

以下のように表示されればインストール完了です。
Platform esp32:esp32@3.3.10 installed

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

どれが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側から「ボードを選択」をクリックして、

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


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


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へアップロードします。

…が、エラーが発生して書き込みに失敗しています。
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」から変更できます。

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

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

Lチカ
文字が出せたので、次は電子工作の定番「Lチカ」をやります。
本来であれば個別に外付けのLEDを買って接続して光らせたいところですが、買い忘れたのでESP本体のLEDを光らせます。
今回使っているFreenoveのボードには、フルカラーLEDと単色の青色LEDが搭載されているので、こちらをLチカさせます。
どのGPIOにどのLEDが繋がっているかは、ボードのピン配置図(ピンアウト)で確認できます。
この図は公式ドキュメントに含まれていました。

今回光らせる2つのLEDは、それぞれ以下のGPIOに繋がっています。
| LED | GPIO |
|---|---|
| フルカラーLED(WS2812) | GPIO16 |
| 単色の青色LED | GPIO2 |
またいい感じに光らせるライブラリがあったので、こちらをインストールして使います。
(今回はAdafruit Neopixel by Adafruitを使いました)

次にコードですが、点滅させるだけだと少し寂しいので、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だとわかりづらいですね…)

ミラーボールチカ
Lチカが思ったより地味だったので、もっと派手に光るものをチカさせたくなりました。
というわけでいつものミラーボールをチカらせます。合わせてWi-Fi通信も使ってみます。
事前準備としてミラーボールはSwitchBotのスマートプラグ経由でON/OFFできるようにしてあるので、ESP32からSwitchBotのAPIを叩いてON/OFFを繰り返せば、ミラーボールチカができます。
なお、SwitchBotのスマートプラグやAPIの具体的な設定については、過去にミラーボールを扱ったブログで詳しく紹介しているので、そちらをご参照ください。
コードは以下の通りです。
やっていることをざっくり並べると、こんな感じです。
- Wi-Fiに接続する
- NTPで時刻を同期する(後述の署名に正確な現在時刻が必要なため)
- トークン・タイムスタンプ・nonceからHMAC-SHA256の署名を作る
- 署名をヘッダーに付けてSwitchBotのAPIを叩く
- 3秒ごとに
turnOnとturnOffを交互に送る
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を繰り返し、無事にミラーボールチカができました。
キレイだね…。

土壌の水分計測
最後はにもう少し実用的な工作として、センサー入力に挑戦します。
植物を育てている方なら「土がどれくらい乾いているか」を数値で知りたくなることはありませんか?私はあります。
というわけで、静電容量式の土壌水分センサーをESP32に繋いで、土の水分量を計測してみます。
配線
まずはセンサーとESP32を配線します。
今回のセンサーはアナログ出力タイプなので、電源(VCC・GND)に加えて、出力ピン(AOUT)をESP32のアナログ入力ピンに繋ぎます。
ESP32のGPIO34はADC(アナログ-デジタル変換)に対応した入力専用ピンなので、ここに繋ぎます。
| センサー側 | ESP32側 |
|---|---|
| GND | GND |
| VCC | 3V3 |
| AOUT | GPIO34 |
- センサー側

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

次にコードは以下の通りです。
やっていることをざっくり並べると、こんな感じです。
- センサーのアナログ値を読み取る(ESP32のADCは12bit、0〜4095)
- ノイズ対策のため複数回読んで平均を取る
- 乾いた状態(DRY)と濡れた状態(WET)の基準値をもとに0〜100%に変換する
- 1秒ごとにシリアルモニタへ出力する
DRYとWETはセンサーや個体によって変わるので、まずは仮の値を入れておき、後で実測して書き換えます。
// 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%」の基準値を取るため、センサーを空気中に置いたまま値を読ませてみます。
空気中での計測

センサーを空気中に置いた状態では、値はおおむね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%」の基準値を取ります。
センサーを水に挿して値を読ませてみましょう。
水中での計測

水中では値が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%
乾いた状態と濡れた状態の基準値が取れたので、コードのDRYとWETを実測値に書き換えます。
それぞれ前述の中央値を入れてみます。
// ▼▼▼ キャリブレーション値(まずは仮。あとで実測して書き換える)▼▼▼
// DRY = 空気中(乾いた状態)で表示された値
// WET = 水に挿した(濡れた状態)で表示された値
// 静電容量式は「乾くと値が大きく/濡れると値が小さく」なるのが一般的
int DRY = 3186; // 乾いているときの生の値
int WET = 1089; // 濡れているときの生の値
// ▲▲▲ ここを実測値に合わせると % 表示が正確になる ▲▲▲
実際に計測してみる
キャリブレーションが済んだので、実際に土に挿して計測してみます。
まずは昨日水やりして適度に湿った土に挿してみます。
(こちらはひめの氏より受け継いだハオルチアです)

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%と表示されました。
感覚的にも「そこそこ湿っている」状態と一致していて、いい感じです。
次に、乾いた土でも試してみます。
(こちらはリボベジ中のパイナップルです)

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通信と組み合わせれば「乾いたら通知する」みたいなこともできそうなので、そのうちオフィスグリーンを救おうと思います。
本ブログが「電子工作ちょっと気になるな」という方の、最初の一歩の参考になれば幸いです。







