Arduinoで水位センサー:キャリブレーション、あふれ警報、ポンプ制御
Arduinoで水位センサーを使う方法を解説します。空・半分・満水のキャリブレーション、腐食を防ぐピンからの給電、ブザーのあふれ警報、ポンプ制御、ESP32/Picoのコードまで紹介します。
水位センサーは、コップや水槽、植木鉢の受け皿、小さなタンクの水がどこまで来ているかをArduinoで検知する、もっとも安く簡単な方法です。基板にむき出しになっている導体パターンが水に沈むと、アナログ出力の電圧が上がります。Arduinoはその電圧をanalogRead()で読み取り、水が「空」「半分」「満水」のどれなのかを判断します。配線は3本、コードも数行で動き、あふれ警報から自動ポンプ制御まで、多くのプロジェクトの出発点になります。
このガイドでは、モジュールの中身、読み取り値が1023に届かない理由、空・半分・満水のキャリブレーションの方法、そして数週間でセンサーをむしばむ腐食の防ぎ方を学びます。続いて、ブザーで知らせるあふれ警報と、タンクに自動で水を補充するポンプ制御を作り、フロートスイッチや非接触型のXKC-Y25と簡単に比較します。最後に、同じことをESP32とRaspberry Pi Picoで行う方法を紹介します。

水位センサーとは?どのように動作するのか
モジュールの下部には、互いに接触せず、2つのくしの歯のようにかみ合った10本の導体パターンがあります。そのうち5本は+電源につながった電源パターン、残りの5本は基板上部にあるNPNトランジスタS8050のベースにつながる検知パターンです。基板が乾いているときはパターンの間に空気しかなく、トランジスタはオフで、Sピンは0 V近くにとどまります。基板が水に入ると、水が電源パターンと検知パターンの間をつなぎます。トランジスタのベースに小さな電流が流れてトランジスタが導通し始め、エミッタから取り出しているS出力の電圧が上がります。水位が高いほど濡れるパターンが長くなり、パターン間の抵抗が下がって、出力電圧が高くなります。
この仕組みから、重要なことが3つわかります。1つ目に、このセンサーが実際に測っているのは水そのものではなく水の導電率です。水道水に溶けているミネラルは読み取り値を押し上げますが、純水(蒸留水)ではほとんど反応しません。2つ目に、出力はエミッタフォロワとして動作するトランジスタから取り出されるため、電圧が電源電圧に達することはありません。5 Vで給電して完全に濡らしたセンサーは、Arduino Unoでたいてい500〜650程度を示し、1023にはまったく近づきません。3つ目に、関係は直線的ではありません。最初の数ミリでは値が急に上がり、その後はどんどん上がり方が鈍くなります。つまりこのセンサーは、ミリ単位の精度を持つ定規ではなく、いくつかのレベルを確実に見分ける実用的なインジケーターです。
| 項目 | 値 |
|---|---|
| 動作電圧 | 3.3〜5 V DC |
| 動作電流 | 20 mA未満(完全に濡れたときに最大) |
| 出力 | アナログ電圧(Sピン)、水位が上がると上昇 |
| 検知範囲 | 約40 × 16 mm、つまり水の高さは最大で約4 cm |
| 基板サイズ | 約62 × 20 mm |
| 動作温度 | 10〜30 °C(メーカーの値) |
| ピン | S(信号)、+(VCC)、−(GND) |
水位センサーの配線:Arduino Uno、ESP32、Pico
| センサーのピン | Arduino Uno | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| S(信号) | A0 | GPIO 34(ADC1) | GP26(ADC0) |
| +(VCC) | D7(測定中だけ給電) | GPIO 25 | GP15 |
| −(GND) | GND | GND | GND |
腐食を防ぐもっとも簡単な方法は、表のとおりセンサーをデジタルピンから給電することです。コードは測定の直前にピンをHIGHにし、10 ms待ってから値を読み取り、再びピンをLOWにします。1秒に1回測定するシステムなら、パターンに電圧がかかっているのは時間全体の1%だけなので、劣化はおよそ100分の1の速さになります。完全に濡れたとき、モジュールの消費電流は最大で約20 mAです。Arduino Unoのピンの推奨上限は20 mA、絶対最大定格は40 mAで、しかもピンがオンになるのは数ミリ秒だけなので、この接続は安全です。ESP32のピンもこの程度の電流なら問題なく供給できます。一方、Picoのピンは初期設定の駆動能力(ドライブ強度)が低めなので、センサーが完全に濡れるとピンの電圧が少し下がり、読み取り値が小さくなります。同じ配線でキャリブレーションしていれば問題ありませんが、より安定した値がほしい場合は、BC557のような小型のPNPトランジスタでセンサーの+をスイッチングできます。エミッタをボードの3V3ピンに、コレクタをセンサーの+に、ベースを1 kΩの抵抗を通してGPIOピンにつなぐと、ピンがLOWのときにセンサーがオンになります。適したトランジスタを選ぶには、「トランジスタのピン配置」ツールを見てください。
ステップごとに:Arduinoで最初の値を読み取る
- センサーのSピンをA0に、+ピンをD7に、−ピンをGNDにつなぎます。パターンを下に向けて、基板をまっすぐ立てて持ちます。
- 下のコードをArduino IDEで開き、ボードに「Arduino Uno」を選び、正しいポートを選んで書き込みます。
- シリアルモニタを開き、9600ボーに設定します。センサーが乾いている間、値が0に近いことを確認します。
- 水の入ったコップにセンサーをゆっくり沈めます。最初は先端だけ、次に半分、最後にパターンが終わる線まで沈めます。各段階で数秒待ち、値を書き留めます。
1const int SENSOR_PIN = A0; // S → A0
2const int POWER_PIN = 7; // + → D7 (yalnızca ölçerken besle / power only while measuring)
3
4int readSensor() {
5 digitalWrite(POWER_PIN, HIGH); // sensörü aç / power the sensor
6 delay(10); // değerin oturmasını bekle / let the reading settle
7 long sum = 0;
8 for (int i = 0; i < 8; i++) { // 8 ölçümün ortalaması / average of 8 readings
9 sum += analogRead(SENSOR_PIN);
10 }
11 digitalWrite(POWER_PIN, LOW); // sensörü kapat / power off
12 return sum / 8;
13}
14
15void setup() {
16 pinMode(POWER_PIN, OUTPUT);
17 digitalWrite(POWER_PIN, LOW); // kapalı başla / start powered off
18 Serial.begin(9600);
19}
20
21void loop() {
22 int raw = readSensor();
23 Serial.print("Ham deger / Raw value: ");
24 Serial.println(raw);
25 delay(1000); // saniyede bir ölç / measure once a second
26}readSensor()関数は、1回の測定でセンサーを約11 msだけオンにします。10 msの待ち時間で、トランジスタとパターン間の水が安定した値に落ち着きます。そのうえで8回の読み取りの平均を取り、1回ごとの小さな揺れをならします。Unoでは1回のanalogRead()に約0.1 msかかるので、8回読んでも1 ms未満です。水をかき混ぜたりコップを揺らしたりすると、シリアルモニタの値が跳ね回るのがわかります。これは正常です。水面が動くと、パターンの濡れている長さが変わり続けるからです。
空・半分・満水:キャリブレーションの方法
ネットで見つけたコードのしきい値をそのまま使わないでください。センサーごと、水の種類ごと、電源電圧ごとに値は変わります。キャリブレーションは、センサーを実際に使う容器で、同じ水を使って行います。水槽なら水槽の水、液肥を入れた灌水タンクなら液肥入りの水です。マーカーでセンサーの0、10、20、30、40 mmの位置に線を引き、それぞれの線まで基板を沈め、値が落ち着くまで3〜4秒待ってから、シリアルモニタの数値を書き留めます。下の表は水道水と5 V給電での一般的な範囲です。自分で測った値を横に書き込んでください。
| レベル | 沈める深さ | Unoでの一般的な値(5 V、水道水) |
|---|---|---|
| 空 | 0 mm(乾いた状態) | 0〜20 |
| 低 | 約5〜10 mm | 250〜400 |
| 半分 | 約20 mm | 400〜480 |
| 満水 | 約40 mm(パターンが終わる線) | 480〜650 |
表で目立つのは、最初の1 cmで値が急に上がり、そのあと間隔が詰まってくることです。0〜10 mmでは数百の差が出ることもありますが、20〜40 mmではわずか数十しか変わりません。そのため、map()で1本の直線を引くよりも、測定点の各区間ごとに別々の比率を使う区分線形補間のほうが、はるかに正確な結果になります。下のコードは、測定した5つの点を2つの配列に持ち、生の値をミリメートルに変換して、レベルを名前で表示します。自分の測定値をRAW配列に小さい順に書き込むだけです。
1const int SENSOR_PIN = A0;
2const int POWER_PIN = 7;
3
4// Kendi kalibrasyon ölçümlerin / your own calibration readings
5const int N = 5;
6const int RAW[N] = {0, 330, 440, 490, 520}; // ham değer, küçükten büyüğe / raw value, ascending
7const int MM[N] = {0, 10, 20, 30, 40}; // batma derinliği (mm) / immersion depth (mm)
8
9int readSensor() {
10 digitalWrite(POWER_PIN, HIGH);
11 delay(10);
12 long sum = 0;
13 for (int i = 0; i < 8; i++) sum += analogRead(SENSOR_PIN);
14 digitalWrite(POWER_PIN, LOW);
15 return sum / 8;
16}
17
18int rawToMm(int raw) {
19 if (raw <= RAW[0]) return MM[0];
20 for (int i = 1; i < N; i++) {
21 if (raw <= RAW[i]) { // iki nokta arasında oran kur / interpolate between two points
22 return map(raw, RAW[i - 1], RAW[i], MM[i - 1], MM[i]);
23 }
24 }
25 return MM[N - 1]; // en üst noktanın üstü / above the top point
26}
27
28const char* levelName(int mm) {
29 if (mm < 3) return "Bos / Empty";
30 if (mm < 15) return "Az / Low";
31 if (mm < 30) return "Yarim / Half";
32 return "Dolu / Full";
33}
34
35void setup() {
36 pinMode(POWER_PIN, OUTPUT);
37 digitalWrite(POWER_PIN, LOW);
38 Serial.begin(9600);
39}
40
41void loop() {
42 int raw = readSensor();
43 int mm = rawToMm(raw);
44 Serial.print(raw);
45 Serial.print(" -> ");
46 Serial.print(mm);
47 Serial.print(" mm, ");
48 Serial.println(levelName(mm));
49 delay(1000);
50}ブザーで知らせるあふれ警報
水位センサーのもっとも便利な使い道の1つが、あふれの警告です。洗濯機や給湯器の下、植木鉢の受け皿、エアコンのドレンパン、水がたまっていくバケツの縁などに置いておけば、水が先端に触れた瞬間に警報が鳴ります。これにはキャリブレーション表の「低」レベルでも十分です。下のコードはアクティブブザーとUnoの内蔵LEDを使います。値がALARM_ONを超えるとブザーが0.25秒ごとに鳴り、ALARM_OFFを下回ると止まります。タイミングにdelay()ではなくmillis()を使っているので、測定とブザーが互いを妨げません。ブザーの種類は「ブザーとは?」を、millis()の考え方は「millis()とdelay()の違い」を参照してください。
1const int SENSOR_PIN = A0; // S → A0
2const int POWER_PIN = 7; // + → D7
3const int BUZZER_PIN = 8; // aktif buzzer + ucu / active buzzer +
4const int LED_PIN = 13; // Uno'nun dahili LED'i / Uno's built-in LED
5
6const int ALARM_ON = 200; // bu değer ve üstü taşma / at or above: overflow (kendin ölç / measure yours)
7const int ALARM_OFF = 100; // bunun altına inince alarm biter / alarm clears below this
8
9bool alarmActive = false;
10bool buzzerOn = false;
11unsigned long lastRead = 0;
12unsigned long lastBeep = 0;
13
14int readSensor() {
15 digitalWrite(POWER_PIN, HIGH);
16 delay(10);
17 long sum = 0;
18 for (int i = 0; i < 8; i++) sum += analogRead(SENSOR_PIN);
19 digitalWrite(POWER_PIN, LOW);
20 return sum / 8;
21}
22
23void setup() {
24 pinMode(POWER_PIN, OUTPUT);
25 pinMode(BUZZER_PIN, OUTPUT);
26 pinMode(LED_PIN, OUTPUT);
27 digitalWrite(POWER_PIN, LOW);
28 Serial.begin(9600);
29}
30
31void loop() {
32 unsigned long now = millis();
33
34 if (now - lastRead >= 1000) { // saniyede bir ölç / measure once a second
35 lastRead = now;
36 int level = readSensor();
37 if (!alarmActive && level >= ALARM_ON) alarmActive = true; // taşma başladı / overflow started
38 if (alarmActive && level < ALARM_OFF) alarmActive = false; // su çekildi / water receded
39 Serial.print("Seviye / Level: ");
40 Serial.println(level);
41 }
42
43 if (alarmActive) {
44 if (now - lastBeep >= 250) { // 250 ms öt, 250 ms sus / 250 ms on, 250 ms off
45 lastBeep = now;
46 buzzerOn = !buzzerOn;
47 digitalWrite(BUZZER_PIN, buzzerOn ? HIGH : LOW);
48 }
49 } else if (buzzerOn) {
50 buzzerOn = false;
51 digitalWrite(BUZZER_PIN, LOW);
52 }
53 digitalWrite(LED_PIN, alarmActive ? HIGH : LOW);
54}ポンプ制御:タンクに自動で水を補充する
2つ目の定番の使い方は、水位を一定の範囲に保つ自動補充です。水槽から蒸発した水を予備のバケツから補ったり、ペットの水入れをいつも満たしておいたり、温室のタンクに水を送ったりできます。仕組みは単純で、水位がSTART_BELOWを下回るとポンプが動き、STOP_ABOVEに達すると止まります。この2つのしきい値の差がヒステリシスで、ポンプが頻繁にオン・オフを繰り返すのを防ぎます。センサーの検知範囲は約4 cmしかないことを忘れないでください。保ちたい水位の範囲がこの4 cmの中に収まる高さに、基板を取り付けます。

ポンプはArduinoのピンが供給できるよりはるかに大きな電流を消費するので、リレーモジュール、またはロジックレベルMOSFETとフライバックダイオードで駆動し、ポンプは別の電源から給電して、両方の電源のGNDを共通にします。詳しくは「リレーモジュールとは?」、「ArduinoでMOSFETを使う方法」、「ミニウォーターポンプ」を参照してください。コードでいちばん大切なのは安全対策です。ポンプがMAX_RUN_MSの時間いっぱい動いても水位が目標に届かなければ、システムはポンプを動作禁止にし、短いビープ音で知らせます。ホースが抜けた、予備のバケツが空になった、センサーがずれた、といった可能性があるからです。この動作禁止がなければ、ポンプが何時間も空運転したり、あたり一面を水浸しにしたりするおそれがあります。
1const int SENSOR_PIN = A0;
2const int POWER_PIN = 7;
3const int PUMP_PIN = 8; // röle IN / relay IN
4const int BUZZER_PIN = 9; // aktif buzzer / active buzzer
5const bool RELAY_ACTIVE_LOW = true; // çoğu röle modülü LOW ile çeker / most relay modules switch on LOW
6
7const int START_BELOW = 300; // bunun altında doldur / start filling below this (kendin ölç / measure yours)
8const int STOP_ABOVE = 480; // bu değerde dur / stop at this value
9const unsigned long MAX_RUN_MS = 30000; // en uzun çalışma 30 sn / max run time 30 s
10
11bool pumpOn = false;
12bool fault = false;
13unsigned long pumpStart = 0;
14
15int readSensor() {
16 digitalWrite(POWER_PIN, HIGH);
17 delay(10);
18 long sum = 0;
19 for (int i = 0; i < 8; i++) sum += analogRead(SENSOR_PIN);
20 digitalWrite(POWER_PIN, LOW);
21 return sum / 8;
22}
23
24void setPump(bool on) {
25 pumpOn = on;
26 bool pinHigh = RELAY_ACTIVE_LOW ? !on : on;
27 digitalWrite(PUMP_PIN, pinHigh ? HIGH : LOW);
28}
29
30void setup() {
31 setPump(false); // pin çıkış olmadan önce kapalı konuma al / set off before the pin becomes an output
32 pinMode(PUMP_PIN, OUTPUT);
33 pinMode(POWER_PIN, OUTPUT);
34 pinMode(BUZZER_PIN, OUTPUT);
35 digitalWrite(POWER_PIN, LOW);
36 Serial.begin(9600);
37}
38
39void loop() {
40 if (fault) { // arıza: pompa kilitli, kesik kesik öt / fault: pump locked out, beep
41 digitalWrite(BUZZER_PIN, HIGH);
42 delay(200);
43 digitalWrite(BUZZER_PIN, LOW);
44 delay(1800);
45 return; // yeniden başlatmak için reset / press reset to restart
46 }
47
48 int level = readSensor();
49
50 if (!pumpOn && level < START_BELOW) {
51 setPump(true); // seviye düştü, doldur / level low, start filling
52 pumpStart = millis();
53 } else if (pumpOn && level >= STOP_ABOVE) {
54 setPump(false); // istenen seviyeye ulaşıldı / target level reached
55 } else if (pumpOn && millis() - pumpStart > MAX_RUN_MS) {
56 setPump(false); // süre doldu, seviye yükselmedi / timed out, level did not rise
57 fault = true;
58 }
59
60 Serial.print("Seviye / Level: ");
61 Serial.print(level);
62 Serial.println(pumpOn ? " pompa / pump ON" : " pompa / pump OFF");
63 delay(500);
64}水位センサー、フロートスイッチ、XKC-Y25の比較
| 項目 | 水位センサー(導体パターン) | フロートスイッチ | XKC-Y25(非接触) |
|---|---|---|---|
| 検知方式 | 水の導電率 | 浮かぶ磁石とリード接点 | 容器の壁越しの静電容量の変化 |
| 出力 | アナログ(数段階のレベル) | 接点の開閉 | デジタル(液体の有無) |
| 測定範囲 | 約0〜4 cm | 1点(取り付け高さ) | 1点(取り付け高さ) |
| 水との接触 | あり、パターンが水中 | あり、本体が水中 | なし、容器の外側に貼り付け |
| 腐食 | あり。ピンからの給電が必須 | ほぼなし | なし |
| 電源 | 3.3〜5 V | 不要(受動接点) | 5〜24 V、約5 mA |
| 向いている用途 | 実験、あふれ警報、小さな容器 | 常設のタンク、ポンプの保護 | 密閉されたプラスチックやガラスのタンク、腐食性の液体 |
| 価格 | いちばん安い | 安い | ほかより高い |
フロートスイッチは、中にリード接点が入った軸と、その軸に沿って上下する磁石入りのフロートでできています。水位が上がるとフロートが持ち上がり、磁石がリード接点の位置に来て、接点が閉じるか開きます。フロートを軸に上下逆に取り付けると、常時開(NO)と常時閉(NC)の動作を入れ替えられます。Arduinoにつなぐには、一方の線をデジタルピンに、もう一方をGNDにつなぎ、ピンをINPUT_PULLUPに設定します。電気分解による劣化がないので、常設の設備ではずっと長持ちします。小型モデルの接点はたいてい0.5 A、10 W程度までなので、ポンプは直接ではなくリレーを通して駆動してください。リード接点の仕組みは「リードスイッチ」の記事を参照してください。

1const int FLOAT_PIN = 2; // bir kablo D2'ye, diğeri GND'ye / one wire to D2, the other to GND
2
3void setup() {
4 pinMode(FLOAT_PIN, INPUT_PULLUP); // dahili pull-up direnci / internal pull-up resistor
5 Serial.begin(9600);
6}
7
8void loop() {
9 // Kontak kapalıyken pin LOW okunur / the pin reads LOW when the contact is closed
10 if (digitalRead(FLOAT_PIN) == LOW) {
11 Serial.println("Kontak kapali / Contact closed");
12 } else {
13 Serial.println("Kontak acik / Contact open");
14 }
15 delay(500);
16}一方、XKC-Y25は水にまったく触れない静電容量式のセンサーです。プラスチックやガラスのタンクの外側に貼り付けると、壁の向こうに液体があるかどうかを検知します。メーカーによると、5〜24 Vで動作し、消費電流は約5 mA、厚さ13 mmまでの非金属の壁越しに検知でき、応答時間は約500 msです。出力のHIGHレベルは電源電圧と同じなので、5 Vで給電したXKC-Y25をESP32やPicoにつなぐときは分圧回路を使ってください(「分圧計算ツール」で適切な抵抗を求められます)。感度の調整や出力の異なるモデルについては、「XKC-Y25非接触液面センサー」の記事で詳しく説明しています。

ESP32でスマホから水位を確認する
ESP32を使えば、Wi-Fi経由で測定値をスマホに表示できます。センサーのSピンは必ずADC1のピン(GPIO 32〜39)につないでください。ADC2のピンはWi-Fiがオンの間は読み取れません。ESP32のADCは12ビットなので値は0〜4095の範囲になり、さらにセンサーを3.3 Vで給電するので、数値はUnoと比較できません。改めてキャリブレーションが必要です。ESP32のADCは0 V付近と測定範囲の上限付近では完全に直線的ではありませんが、ここで大切なのは絶対的な電圧ではなくレベル間の差なので、問題にはなりません。下のコードは2秒ごとに値を読み取り、5秒ごとに自動で更新される小さなWebページを配信します。このページは同じネットワーク上のスマホから開けます。
1#include <WiFi.h>
2#include <WebServer.h>
3
4const char* WIFI_SSID = "WIFI_ADIN"; // ağ adın / your network name
5const char* WIFI_PASS = "WIFI_SIFREN"; // ağ şifren / your network password
6
7const int SENSOR_PIN = 34; // S → GPIO 34 (ADC1, Wi-Fi ile çalışır / works with Wi-Fi)
8const int POWER_PIN = 25; // + → GPIO 25
9const int HALF = 1500; // yarım eşiği / half threshold (kendin ölç / measure yours)
10const int FULL = 2000; // dolu eşiği / full threshold (kendin ölç / measure yours)
11
12WebServer server(80);
13int lastRaw = 0;
14unsigned long lastRead = 0;
15
16int readSensor() {
17 digitalWrite(POWER_PIN, HIGH);
18 delay(10);
19 long sum = 0;
20 for (int i = 0; i < 16; i++) sum += analogRead(SENSOR_PIN); // 0–4095
21 digitalWrite(POWER_PIN, LOW);
22 return sum / 16;
23}
24
25const char* levelName(int raw) {
26 if (raw < 100) return "Bos / Empty";
27 if (raw < HALF) return "Az / Low";
28 if (raw < FULL) return "Yarim / Half";
29 return "Dolu / Full";
30}
31
32void handleRoot() {
33 String html = "<!DOCTYPE html><html><head><meta charset='utf-8'>"
34 "<meta name='viewport' content='width=device-width'>"
35 "<meta http-equiv='refresh' content='5'></head><body>"
36 "<h1>Su seviyesi / Water level</h1><h2>";
37 html += levelName(lastRaw);
38 html += "</h2><p>Ham deger / Raw: ";
39 html += lastRaw;
40 html += "</p></body></html>";
41 server.send(200, "text/html", html);
42}
43
44void setup() {
45 Serial.begin(115200);
46 pinMode(POWER_PIN, OUTPUT);
47 digitalWrite(POWER_PIN, LOW);
48 WiFi.begin(WIFI_SSID, WIFI_PASS);
49 while (WiFi.status() != WL_CONNECTED) { // bağlanana kadar bekle / wait until connected
50 delay(500);
51 Serial.print(".");
52 }
53 Serial.println();
54 Serial.print("Adres / Address: http://");
55 Serial.println(WiFi.localIP());
56 server.on("/", handleRoot);
57 server.begin();
58}
59
60void loop() {
61 server.handleClient();
62 if (millis() - lastRead >= 2000) { // 2 sn'de bir ölç / measure every 2 s
63 lastRead = millis();
64 lastRaw = readSensor();
65 }
66}書き込む前に、WIFI_SSIDとWIFI_PASSを自分のネットワークの情報に書き換えてください。ESP32が接続できるのは2.4 GHzのネットワークだけです。シリアルモニタを115200ボーで開き、表示されたアドレスをスマホのブラウザに入力するだけです。水位が「満水」になったときにスマホで通知を受け取りたい場合は、「ESP32のTelegramボット」の記事の方法で、同じ読み取り値をメッセージに変えられます。
Raspberry Pi PicoとMicroPythonであふれ警報
Raspberry Pi Picoでは、GP26、GP27、GP28ピンをアナログ読み取りに使います。MicroPythonのread_u16()関数は、Picoの12ビットの測定値を0〜65535の範囲に拡大するので、しきい値はUnoよりずっと大きな数値になります。下のコードはGP15からセンサーに給電し、GP26でS出力を読み取り、GP14のアクティブブザーを3回短く鳴らしてあふれを知らせます。最初のスケッチと同じく、しきい値は自分の測定値から決めてください。
1from machine import ADC, Pin
2import time
3
4sensor = ADC(26) # GP26 (ADC0) → S
5power = Pin(15, Pin.OUT, value=0) # GP15 → sensör + / sensor +
6buzzer = Pin(14, Pin.OUT, value=0) # GP14 → aktif buzzer / active buzzer
7
8ALARM_ON = 20000 # bu değer ve üstü taşma / at or above: overflow (kendin ölç / measure yours)
9ALARM_OFF = 10000 # bunun altında alarm biter / alarm clears below this
10
11def read_level(samples=8):
12 power.value(1)
13 time.sleep_ms(10) # değerin oturmasını bekle / let the reading settle
14 total = 0
15 for _ in range(samples):
16 total += sensor.read_u16() # 0–65535
17 power.value(0)
18 return total // samples
19
20alarm = False
21while True:
22 level = read_level()
23 if not alarm and level >= ALARM_ON:
24 alarm = True
25 elif alarm and level < ALARM_OFF:
26 alarm = False
27 print("Seviye / Level:", level, "ALARM" if alarm else "")
28 if alarm:
29 for _ in range(3): # üç kısa bip / three short beeps
30 buzzer.value(1)
31 time.sleep_ms(150)
32 buzzer.value(0)
33 time.sleep_ms(150)
34 else:
35 time.sleep(1)水位センサーを使ったプロジェクトアイデア
- 植木鉢の受け皿のあふれ警告:水やりのあと受け皿に水がたまると鳴るブザーです。土壌水分センサーと組み合わせれば、本格的な水やりシステムになります。
- 水槽の自動給水:蒸発した分の水を予備のバケツからミニポンプで補う、時間制限付きの補充システムです。
- ペットの水入れ:水が少なくなるとLEDを点灯したり、ESP32でスマホに通知を送ったりするリマインダーです。
- 漏水検知器:洗濯機や給湯器の下、地下室の床などにセンサーを平らに置いて使う漏水警報です。
- スマート温室のタンク:灌水タンクの水位を見張り、水がなくなったらポンプを動作禁止にする保護機能です。スマート温室プロジェクトにも簡単に組み込めます。
- 雨水タンクの水位計:水位を空・半分・満水でI2C LCDディスプレイに表示するパネルです。雨センサーを加えれば、雨が降り始めた瞬間もとらえられます。
よくある間違いと対処法
| 症状 | 考えられる原因 | 対処法 |
|---|---|---|
| 水に入れても値が常に0 | +ピンに給電されていない(POWER_PINがHIGHにならない)、またはSが違うピンにつながっている | pinModeとdigitalWriteの行、そしてSがアナログピンにつながっているかを確認 |
| 乾いているのに値がランダムに跳ねる | Sピンが浮いている、またはGNDがつながっていない | 3本の配線をすべて確認。GNDは必ず共通に |
| 完全に沈めても1023に届かない | 正常です。出力がエミッタフォロワから取り出されているため | 自分で測った最大値をもとにしきい値を決める |
| 純水ではまったく反応しない | 蒸留水や浄水は導電率がとても低い | このような水にはXKC-Y25かフロートスイッチを使う |
| 数週間で値が下がる、またはずれていく | パターンの電気分解と付着物 | センサーをピンから給電し、パターンを柔らかいブラシで掃除し、必要ならセンサーを交換 |
| ポンプやブザーがしきい値付近でオン・オフを繰り返す | しきい値が1つだけで、水面の動きで値が揺れている | オンとオフに別々のしきい値(ヒステリシス)を使い、読み取り値を平均する |
| ESP32でWi-Fiに接続すると値が読めなくなる | センサーがADC2のピンにつながっている | SをGPIO 32〜39のADC1ピンに移す |
水位センサーは、安いモジュール1つで、アナログ読み取り、キャリブレーション、ヒステリシス、そして腐食のような現実の問題を教えてくれます。大きなタンクの水位をセンチ単位で測る必要があるなら、HC-SR04距離センサーのような超音波を使う方法に進めます。BlueGraysアプリには、「スマート水やりステーション」やブザーを使った警報のプロジェクトが、ステップごとの回路図とコード付きでそろっていて、そこに水位センサーを簡単に追加できます。
よくある質問
水位センサーはどのように動作しますか?
基板の互い違いに並んだ導体パターンが水に入ると、水がパターンの間をつなぎ、基板上のS8050トランジスタのベースに電流が流れます。トランジスタがよく導通するほどSピンの電圧が高くなり、Arduinoはその電圧をanalogRead()で読み取って水の高さを判断します。
水位センサーが腐食するのはなぜですか?
濡れたパターンに直流が流れ続けると電気分解が始まり、+につながったパターンのめっきが溶けます。センサーを5Vではなくデジタルピンから給電し、1回の読み取りにつき数ミリ秒だけオンにすれば、劣化は大幅に遅くなり、センサーは何か月ももちます。
水位センサーは蒸留水でも使えますか?
ほとんど使えません。このセンサーは水の導電率を測っているため、ミネラルが溶けていない蒸留水や浄水では読み取り値がとても低くなります。このような水には、非接触のXKC-Y25やフロートスイッチなど、導電率に左右されないセンサーのほうが適しています。
水位センサーの値が1023に届かないのはなぜですか?
S出力はトランジスタのエミッタから取り出されるので電圧は電源電圧より低くなり、さらにベース電流は水の抵抗で制限されます。5 VのArduino Unoでは、完全に濡れたセンサーの値はたいてい500〜650程度なので、この最大値をもとにしきい値を決めてください。
水位センサーは全体を水に沈めても大丈夫ですか?
いいえ。水に入れてよいのは導体パターンのある下部だけです。トランジスタ、LED、ピンのある上部が濡れるとショートし、センサーが誤った値を示したり壊れたりします。電子部品の部分をホットボンドやラッカーで保護すると長持ちします。
大きな水タンクの水位はどうやって測りますか?
このセンサーの測定範囲は約4 cmしかないため、大きなタンクには向きません。水位を連続して測るには、タンクのふたに取り付けた防水型の超音波センサー(JSN-SR04Tなど)を使います。いくつかの重要な位置だけを監視するなら、フロートスイッチを異なる高さに取り付けます。
この記事を読んだ後に作れるプロジェクト
全プロジェクトを見る
ブザーの使い方
呼び鈴やアラームがどうやって音を出すのか、気になったことはありませんか?Arduinoが小さなブザーを短い間隔で鳴らし、音を出す、止める、を繰り返します。電気で音が生まれる仕組みを学び、メロディーを演奏するプロジェクトへの第一歩を踏み出せます。

LDRで光の強さを測定
部屋の明るさを数値で見てみませんか?光に反応する小さな部品、LDRで周囲の光を測定し、Arduinoが0.5秒ごとにその値をコンピューターの画面に表示します。手で覆うと数値が変わる様子を確認できます。自動で点灯する常夜灯などのプロジェクトへの第一歩です。

ESP32とLDRモジュールで光の強さを読み取る
LDRモジュールの生のADC値と、暗い状態・明るい状態の基準値から計算した相対的な明るさの割合を、シリアルモニタでリアルタイムに確認します。この割合はルクスの測定値ではありません。モジュールの校正の仕組みを学べます。しきい値の決定に使うため、プロジェクト10の前に取り組むのが適しています。

スマート水やりステーション
旅行中も植物が枯れないよう、自動で水やりをするシステムです。土壌水分センサーで土の湿り具合を測定し、乾燥するとArduinoが給水ポンプを数秒間動かして、青いライトを点灯させます。水分が適切なときは緑のライトが点灯します。土がひどく乾燥すると、赤いライトが点滅し、ブザーが鳴って知らせます。

リードスイッチの状態を読み取る
ドアや窓の警報装置に使われる、磁石で動くスイッチの仕組みが気になったことはありませんか?リードスイッチモジュールに磁石を近づけると、内部の小さなスイッチが閉じます。Arduinoがその状態を1秒ごとに読み取り、コンピューターの画面に0または1を表示します。磁石を近づけたり遠ざけたりして、値が変わる様子を確かめられます。

ESP32非同期Webサーバー(LCDにIPアドレスを表示)
このプロジェクトでは、ESP32を同じWi-Fiネットワーク上のスマートフォンやパソコンから操作できる非同期Webサーバーにします。ESPAsyncWebServerライブラリを使うと、従来のWebServer.hのように1つのリクエストで処理がブロックされず、複数のリクエストを同時に処理できます。ブラウザーでESP32のIPアドレスを開き、ON/OFFボタン(/onと/offのルート)を押すと、GPIO 5に接続したLEDを点灯・消灯できます。

ESP32動体検知アラーム(PIR + ブザー + LED + Telegram)
PIRセンサーが動きを検知するとブザーとLEDを作動させ、同時にTelegramで持ち主に即座に通知するスマート防犯アラームです。アラームが繰り返される頻度を抑えるため、30秒のクールダウン(待ち時間)を設けています。この仕組みはPIRの検知が正しいかを確認したり、誤検知をなくしたりするものではありません。家の防犯、ガレージの監視、倉庫や作業部屋などに使えます。ESP32-CAMなら写真付きの通知を追加でき、通常のESP32 DevKit V1ではテキストで通知します。
ほかの記事
BlueGraysアプリ
この回路をスマホでステップごとに作る
回路図、コード、部品リストを1つの画面で確認できます。Arduino、ESP32、Picoのプロジェクトを200件以上用意しています。

