部品 · 読了まで17分

リードスイッチとArduinoの使い方:ドアセンサー、アラーム、スピードメーター

リードスイッチのしくみとArduinoへの配線を解説します。NO/NCの種類、磁石の距離、プルアップとチャタリング対策、ドアアラーム、自転車のスピードメーター、ESP32とPicoのコードも紹介します。

リードスイッチ(磁気スイッチ)は、磁石がガラス管の中の2枚の細い金属片を引き寄せて接触させることで回路を閉じる簡単なセンサーで、それ自体は電力をまったく消費しません。Arduinoではボタンと同じように読み取れます。磁石が近づくと接点が閉じ、離れるとまた開きます。この小さなガラス管は、ドアや窓のアラーム、自転車のサイクルコンピューター、フロート式水位スイッチ、風速計の中に隠れています。

このガイドでは、リードスイッチの中身、NOとNCの種類、磁石がいちばんよく働く距離と向き、プルアップ抵抗を使ったArduinoへの配線をステップごとに学びます。続いて、接点のチャタリングをソフトウェアで対策し、ドア開放アラームと自転車のスピードメーターを作り、ESP32でスマホに「ドアが開きました」の通知を送り、同じ回路をRaspberry Pi PicoとMicroPythonで動かします。最後のセクションでは、リードスイッチとホールセンサーを比べ、よくある間違いをまとめます。

リードスイッチのセンサーモジュールとガラス管のリードスイッチ
リードスイッチモジュール:左のガラス管に2枚の金属片が入っています。基板上のLM393コンパレーターとポテンショメーターが、信号をDOピンのきれいなデジタル出力に変えます。

リードスイッチとは?しくみは?

リードスイッチの心臓部は、窒素などの不活性ガスを封入した、または真空にした小さなガラス管です。両端から柔軟なニッケル鉄の金属片が入り、中央でごくわずかなすき間をあけて重なっています。「リード(reed)」とはこの金属片のことです。先端にはロジウムやルテニウムなどの硬い金属がめっきされていて、接触抵抗を低く保ち、摩耗を減らします。磁石が近づくと両方の金属片が磁化し、重なった先端に反対の極ができて、金属片どうしが引き合って接触します。磁石が離れると、金属片のばねの力で接点がまた離れます。ガラスが接点をほこり、湿気、酸化から守るので、リードスイッチは小さな電流なら数億回も動作できます。

この部品を「リードリレー」と呼んで売っている店も多いのですが、厳密にはガラス管だけのものはリードスイッチです。本当のリードリレーは、同じガラス管にコイルを巻いたもので、磁石の代わりにコイルに流れる電流が接点を閉じます。この記事で扱うのは、磁石で動くスイッチと、それを使ったセンサーモジュールです。最大の利点は電力を消費しないことです。流れるのは自分の回路の電流だけなので、電池で何か月も動く機器に最適です。

リードスイッチの仕様

項目一般的な値(小型のガラス管リード)
接点の種類Form A(NO、常時開)が最も一般的
ガラス管直径約2 mm、長さ10〜15 mm
開閉容量(電力)最大10 W
開閉電圧最大100〜200 V DC
開閉電流約0.5 A
接触抵抗0.1 Ω未満
動作時間1 ms未満(チャタリングを含む)
感度10〜40 AT(アンペアターン)
寿命小電流で1億〜10億回
自己消費なし(受動部品)

感度はAT(アンペアターン)で表します。標準の試験用コイルでリードを閉じるのに必要な、電流と巻き数の積です。値が小さいほど弱い磁石でも動作するので、より遠くから反応します。またヒステリシスもあります。接点を閉じるのに必要な磁界(動作、プルイン)は、閉じたまま保つのに必要な磁界より強いのです。たとえば、磁石が15 mmで閉じるリードは、約20 mmまで離さないと開かないことがあります。この差は小さな振動によるばたつきを減らしますが、設計では磁石が復帰(ドロップアウト)距離より遠くまで確実に離れるようにする必要があります。

NO、NC、切り替え型のリード(Form A、B、C)

リードスイッチの接点構成は3種類あります。NO(常時開)とNC(常時閉)という表記は、常に磁石がないときの接点の状態を表します。

種類磁石なし磁石が近い端子数用途
Form A(NO)開閉2ドアセンサー、速度カウンター、ほとんどのモジュール
Form B(NC)閉開2磁石が離れたときに回路を閉じる必要がある用途
Form C(切り替え型)COM–NCが閉COM–NOが閉3開と閉の両方の出力が必要な回路
アラームシステム用に売られているドアセンサー(よく見かけるMC-38タイプなど)には、「NC」と書かれていることがよくあります。この場合のNCは、ドアが閉まっている、つまり磁石がすぐ隣にある間は回路が閉じているという意味で、中身は実はForm A(NO)のリードです。ですから、まずテスターの導通(ブザー)モードで、磁石がある場合とない場合の両方でセンサーをテストしてください。ラベルだけを見てコードを書くと、論理が逆になることがあります。

磁石での動作:距離、向き、感度

リードスイッチを動作させるのに最適なのは、小さなネオジム磁石です。10×3 mmのネオジムの円板なら、一般的なリードを通常10〜20 mmから動作させられます。同じ大きさの黒いフェライト磁石では、距離はおよそ半分になります。市販のドアセンサーは通常15〜25 mmで動作します。距離は磁石の強さだけでなく向きにも左右されます。磁石のN-S軸をガラス管と平行にして中央に近づけると、1回のきれいな閉路が得られます。磁石が管に対して垂直だと、ちょうど中央では反応せず、両端に近い2か所で反応することがあります。平行な磁石を管に沿って動かすと、主な閉路ゾーンの両側にも小さな閉路ゾーンができます。そのため、磁石がリードの前を通り過ぎるとき、接点が2〜3回開閉することがあります。カウント系のプロジェクトで1回転が2回や3回と数えられてしまう、最も多い原因がこれです。

ネオジム磁石の種類:円板、立方体、角形、皿穴付き
ネオジム磁石はリードスイッチを動作させるのに最適です。皿穴付きのものは、ドアや枠にネジで簡単に取り付けられます。
  • 極性は関係ありません:リードはN極にもS極にも同じように反応します。磁石を裏返しても結果は変わりません。
  • 磁石を管の中央に合わせる:ドアセンサーでは、磁石とリードの本体を平行に並べ、すき間をできるだけ小さくします。
  • 鉄のドアでは距離が短くなる:鉄は磁石の磁界を吸い込んでしまいます。5〜10 mmのプラスチックのスペーサーでセンサーと磁石を浮かせるか、より強い磁石を使ってください。
  • まず距離を測る:シリアルモニタを開いた状態で、磁石をリードにゆっくり近づけたり離したりします。ドアを閉めたときは磁石が動作距離の内側に余裕をもって収まり、ドアが少し開いたら復帰距離の外に出るようにします。
  • 強い磁界から離す:スピーカー、モーター、ほかの磁石は、意図せずリードを閉じることがあります。リードをそれらのすぐ隣に取り付けないでください。

リードスイッチとArduinoの配線:なぜプルアップ抵抗が欠かせないのか

リードスイッチはボタンと同じようにふるまうので、自分で電圧を作ることはなく、2本の端子をつないだり切り離したりするだけです。接点が開いている間、ピンはどこにもつながっていない(フローティング状態)ので、ノイズによるでたらめなHIGHとLOWを読んでしまいます。解決策はプルアップ抵抗です。ピンを抵抗を通して5 V(または3.3 V)に引き上げ、リードをピンとGNDの間に入れます。磁石がなければピンはHIGHを読み、磁石が接点を閉じるとピンはGNDに直接つながってLOWを読みます。Arduino Unoには20〜50 kΩの内部プルアップ抵抗があるので、pinMode(pin, INPUT_PULLUP)と書くだけでよく、外付けの抵抗は不要です。この論理の詳細は「Arduinoに押しボタンをつなぐ方法」のガイドを見てください。

接続Arduino UnoESP32Raspberry Pi Pico
リードの端子1(リード単体)D2GPIO 27GP15(物理ピン番号20)
リードの端子2(リード単体)GNDGNDGND
プルアップ内部(INPUT_PULLUP)内部(INPUT_PULLUP)内部(Pin.PULL_UP)
モジュールのVCC5V3V33V3(OUT)(物理ピン番号36)
モジュールのGNDGNDGNDGND
モジュールのDOD2GPIO 27GP15
状態表示LEDD13(内蔵LED)GPIO 2(内蔵LED)内蔵LED
アクティブブザー(+)D8使用しないGP14
リードスイッチを5VとGNDの間に直接つないではいけません。OUTPUTに設定したピンにつなぐのも同じです。磁石が近づいた瞬間にショートし、細い接点が溶着して閉じたままになったり、ボードのレギュレーターが壊れたりします。ESP32のGPIO 34〜39は入力専用で、内部プルアップ抵抗がありません。これらのピンをリードに使う場合は、10 kΩの外付けプルアップ抵抗を追加してください。

ステップごとに作る最初の回路:磁石を検知する

  1. リードスイッチの一方の端子をArduinoのD2ピンに、もう一方をGNDにつなぎます。リード単体には極性がないので、端子の向きは関係ありません。
  2. モジュールを使う場合は、VCCを5Vに、GNDをGNDに、DO出力をD2につなぎます。
  3. 下のコードを書き込み、シリアルモニタを9600ボーで開きます。
  4. 磁石をガラス管と平行に持ったまま、ゆっくり近づけます。内蔵LEDが点灯し、画面に「magnet detected」(磁石を検知)と表示されるはずです。
  5. 磁石を遠ざけながら、LEDが消える距離を定規で測ります。これが復帰距離です。
C++ (Arduino IDE)
1const byte REED_PIN = 2;             // reed: D2 ile GND arasında / reed between D2 and GND
2const byte LED_PIN = 13;             // karttaki LED / onboard LED
3
4void setup() {
5  Serial.begin(9600);
6  pinMode(REED_PIN, INPUT_PULLUP);   // dahili pull-up: mıknatıs yokken HIGH / internal pull-up: HIGH without a magnet
7  pinMode(LED_PIN, OUTPUT);
8}
9
10void loop() {
11  if (digitalRead(REED_PIN) == LOW) {       // kontak kapalı: mıknatıs yakında / contacts closed: magnet nearby
12    digitalWrite(LED_PIN, HIGH);
13    Serial.println(F("Miknatis var / magnet detected"));
14  } else {                                  // kontak açık: mıknatıs uzakta / contacts open: magnet away
15    digitalWrite(LED_PIN, LOW);
16    Serial.println(F("Miknatis yok / no magnet"));
17  }
18  delay(200);
19}

コードで注意するのは逆の論理だけです。INPUT_PULLUPを使うので、磁石があるときにLOW、ないときにHIGHを読みます。3ピンのLM393モジュールでも、たいてい磁石が近づくと出力がLOWになり、基板上のDOのLEDが点灯します。これは機種によって異なることがあるので、まず自分のモジュールをシリアルモニタで確認してください。モジュールのポテンショメーターはコンパレーターのしきい値を決めます。リード自体がすでにオン・オフで動作するので、検知距離への影響は小さく、たいていは中央のままで問題ありません。KY-021のようなミニモジュールにはコンパレーターがなく、基板にはリードと10 kΩの抵抗しか載っていません。

チャタリング対策:接点のバウンドと振動

ほかの機械式接点と同じように、リードスイッチも閉じるときに数回跳ねることがあります。リードではこのチャタリング(バウンド)はごく短く、通常1 ms未満ですが、本当の問題はそこではありません。磁石が動作距離の境目でゆっくり動いたり、ドアが風でがたついたりすると、1秒の間に接点が何度も開閉することがあります。そのため、ドア開放の通知を送るシステムが、1回開けただけで5通もメッセージを送ってしまうことがあります。解決策は、すべての変化をすぐに受け入れるのではなく、状態が一定時間変わらないのを待つことです。ドアセンサーなら50〜100 msで十分です。速度カウンターでは、物理的にありうる2つのパルスの最短間隔を不感時間として使うとうまくいきます。

ハードウェアで対策したい場合は、ピンとGNDの間に100 nFのコンデンサを入れます。10 kΩのプルアップと組み合わせると、時定数はτ = R × C = 10 kΩ × 100 nF = 1 msとなり、ごく短いチャタリングをならします。ほかの抵抗とコンデンサの組み合わせは「RC時定数」ツールで試せます。内部プルアップでは抵抗が20〜50 kΩなので、時間は2〜5 msに延びます。数メートルのケーブルがあるドアセンサーでは、4.7〜10 kΩの外付けプルアップとこのコンデンサを組み合わせると、ノイズ対策にもなります。

ドアや窓の開放センサーとアラームのプロジェクト

最も一般的なリードのプロジェクトはドアセンサーです。磁石を動くドア側に、リードを枠側に取り付けます。ドアが閉まっているときは2つが並び、接点が閉じてピンはLOWを読みます。ドアが開くと磁石が離れ、ピンはHIGHになります。この配置にはうれしい副作用があります。誰かがセンサーのケーブルを切っても、ピンはHIGHになり、システムはそれを「ドアが開いた」と判断します。アラームシステムが閉ループの配線を使うのはこのためです。下のコードは、開いた回数を数え、ドアが開いている時間を測り、30秒以上開けっぱなしになるとブザーを断続的に鳴らします。冷蔵庫や冷凍庫のドアにぴったりです。

C++ (Arduino IDE)
1const byte REED_PIN = 2;                  // reed: D2 ile GND arası / reed between D2 and GND
2const byte BUZZER_PIN = 8;                // aktif buzzer artı ucu / active buzzer positive leg
3const byte LED_PIN = 13;                  // karttaki LED / onboard LED
4const unsigned long STABLE_MS = 50;       // debounce: 50 ms değişmeden kalmalı / debounce: must hold for 50 ms
5const unsigned long OPEN_LIMIT = 30000;   // 30 sn'den uzun açık kalırsa alarm / alarm if open longer than 30 s
6
7bool doorOpen = false;                    // onaylanmış durum / confirmed state
8bool lastReading = false;                 // son ham okuma / last raw reading
9unsigned long lastChange = 0;             // ham okumanın son değiştiği an / when the raw reading last changed
10unsigned long openedAt = 0;               // kapının açıldığı an / when the door was opened
11unsigned int openCount = 0;               // toplam açılış / total openings
12
13void setup() {
14  Serial.begin(9600);
15  pinMode(REED_PIN, INPUT_PULLUP);
16  pinMode(BUZZER_PIN, OUTPUT);
17  pinMode(LED_PIN, OUTPUT);
18  doorOpen = (digitalRead(REED_PIN) == HIGH);   // açılıştaki durumu al / take the state at boot
19  lastReading = doorOpen;
20}
21
22void loop() {
23  unsigned long now = millis();
24  bool reading = (digitalRead(REED_PIN) == HIGH);   // HIGH: mıknatıs uzak, kapı açık / HIGH: magnet away, door open
25
26  if (reading != lastReading) {           // ham değer değişti, sayacı yeniden başlat / raw value changed, restart the timer
27    lastReading = reading;
28    lastChange = now;
29  }
30
31  if (reading != doorOpen && now - lastChange >= STABLE_MS) {   // 50 ms kararlı: gerçek değişim / stable for 50 ms: real change
32    doorOpen = reading;
33    if (doorOpen) {
34      openedAt = now;
35      openCount++;
36      Serial.print(F("Kapi acildi / door opened, sayac / count: "));
37      Serial.println(openCount);
38    } else {
39      Serial.print(F("Kapi kapandi / door closed, sure / duration (s): "));
40      Serial.println((now - openedAt) / 1000);
41    }
42  }
43
44  digitalWrite(LED_PIN, doorOpen ? HIGH : LOW);     // LED kapı açıkken yanar / LED is on while the door is open
45
46  // Uzun süre açık kalırsa çeyrek saniyede bir öt / beep every quarter second if left open too long
47  bool alarm = doorOpen && (now - openedAt > OPEN_LIMIT);
48  digitalWrite(BUZZER_PIN, (alarm && (now / 250) % 2) ? HIGH : LOW);
49}

コードはdelay()を使わないので、ループは常にピンを確認し、開放を見逃しません。この手法は「millis()とdelay()の違い」の記事で詳しく解説しています。生の読み取り値が変わるたびにlastChangeの時刻がリセットされ、値が50 ms変わらなかった場合にだけdoorOpenの状態が変わります。ブザーの部分はmillis()を250で割り、1/4秒ごとにオン・オフを切り替えます。本格的なアラームにするなら、パスワード入力用の「キーパッド」で警戒・解除を切り替え、室内に「PIRセンサー」を追加できます。より完成度の高い構成は「Arduinoのアラームシステム」の記事を見てください。

自転車のスピードメーター:リードスイッチで回転数を数える

ほとんどのサイクルコンピューターは、リードスイッチとスポークに取り付ける小さな磁石でできています。車輪が1回転するたびに、磁石がフォークのリードの前を1回通り、パルスを1つ出します。2つのパルスの間隔を測れば速度が、パルスを数えれば距離が分かります。速度(km/h) = 周長(mm) / 時間(ms) × 3.6です。ここでmm/msはちょうどm/sなので、3.6を掛けるとkm/hに換算できます。たとえば周長2105 mmの700x25Cの車輪で、パルスの間隔が253 msなら、2105 / 253 × 3.6 ≈ 30 km/hです。周長を最も正確に求めるには、バルブを下にして車輪をちょうど1回転転がし、地面につけた2つの印の間の距離を測ります。

タイヤサイズおおよその周長30 km/hでの1回転の時間
20x1.75(BMX、子ども用自転車)1515 mm182 ms
26x2.1(マウンテンバイク)2068 mm248 ms
29x2.1(マウンテンバイク)2288 mm275 ms
700x25C(ロードバイク)2105 mm253 ms
700x28C(街乗り、グラベル)2136 mm256 ms
C++ (Arduino IDE)
1const byte REED_PIN = 2;                  // kesme pini D2 (INT0) / interrupt pin D2 (INT0)
2const float WHEEL_MM = 2105.0;            // 700x25C tekerlek çevresi (mm) / 700x25C wheel circumference (mm)
3const unsigned long LOCKOUT_MS = 80;      // daha sık darbe imkânsız (~95 km/sa) / faster pulses are impossible (~95 km/h)
4const unsigned long TIMEOUT_MS = 3000;    // 3 sn darbe yoksa hız 0 / no pulse for 3 s means speed 0
5
6volatile unsigned long lastPulseMs = 0;   // son geçerli darbe zamanı / time of the last valid pulse
7volatile unsigned long periodMs = 0;      // son iki darbe arası süre / time between the last two pulses
8volatile unsigned long revCount = 0;      // toplam tur / total revolutions
9
10void onMagnet() {                         // ISR: kısa ve hızlı / ISR: short and fast
11  unsigned long now = millis();
12  unsigned long dt = now - lastPulseMs;
13  if (dt < LOCKOUT_MS) return;            // sıçrama ya da çift darbe: yok say / bounce or double pulse: ignore
14  periodMs = (dt < TIMEOUT_MS) ? dt : 0;  // uzun duraklamadan sonraki ilk darbe hız vermez / first pulse after a stop gives no speed
15  lastPulseMs = now;
16  revCount++;
17}
18
19void setup() {
20  Serial.begin(9600);
21  pinMode(REED_PIN, INPUT_PULLUP);
22  attachInterrupt(digitalPinToInterrupt(REED_PIN), onMagnet, FALLING);   // mıknatıs gelince HIGH->LOW / HIGH->LOW when the magnet arrives
23}
24
25void loop() {
26  static unsigned long lastPrint = 0;
27  if (millis() - lastPrint < 500) return; // saniyede 2 kez yaz / print twice per second
28  lastPrint = millis();
29
30  noInterrupts();                         // çok baytlı değerleri güvenle kopyala / copy multi-byte values safely
31  unsigned long period = periodMs;
32  unsigned long last = lastPulseMs;
33  unsigned long revs = revCount;
34  interrupts();
35
36  float speedKmh = 0;
37  if (period > 0 && millis() - last < TIMEOUT_MS) {
38    speedKmh = WHEEL_MM / period * 3.6;   // mm/ms = m/s; 3,6 ile çarp = km/sa / multiply by 3.6 = km/h
39  }
40  float distanceKm = revs * WHEEL_MM / 1000000.0;   // tur x çevre / revolutions x circumference
41
42  Serial.print(F("Hiz / speed: "));
43  Serial.print(speedKmh, 1);
44  Serial.print(F(" km/h   Mesafe / distance: "));
45  Serial.print(distanceKm, 3);
46  Serial.println(F(" km"));
47}

パルスをattachInterruptでとらえるので、loop()がどれだけ忙しくても回転を見逃しません。Arduino Unoでこの種類の割り込みが使えるのはD2ピンとD3ピンだけです。LOCKOUT_MSの値はとても重要です。2105 mmの車輪では、80 msが最高速度約95 km/hに相当します(2105 / 80 × 3.6)。それより間隔の短いパルスは物理的にありえないので、チャタリング、磁石の側方の閉路ゾーン、開くときの接点の跳ね返りとして無視します。3秒間パルスが来なければ、速度はゼロとみなします。同じ構造で、モーターや車輪の速度を1分あたりの回転数で測れます。RPM = 60000 / 時間(ms)です。割り込みについては「Arduinoの割り込み」のガイドを、ハンドルに付けたディスプレイに速度を表示するには、コードを「OLEDディスプレイ」と組み合わせてください。

リードスイッチとホールセンサー、どちらを使う?

磁石を検知するもう1つの方法は「ホール効果センサー」です。ホールセンサーは半導体チップの中で磁界を電気信号に変えます。動く部品はありませんが、動作には電源が必要です。簡単なルールはこうです。電池で長く動き、たまにしか反応しないドアや窓のセンサーならリードを選びます。モーター、ファン、1秒に何十回も回る「水流センサー」のように、速く連続して数える用途ならホールセンサーのほうが向いています。

項目リードスイッチホールセンサー(A3144、US1881、49E)
しくみ機械式の接点、磁石で閉じる半導体、磁界を電気信号に変える
電源不要、それ自体は電力を消費しない必要(数mA)
開閉の速さ1秒に数百回最大で数万Hz
接点のチャタリングあり(1 ms未満)なし
寿命機械的に摩耗する、1億〜10億回動く部品がなく、摩耗しない
極の判別不可、両方の極に反応する機種による:ユニポーラ、バイポーラ、ラッチ型
アナログ測定不可、開か閉のみリニア型(49E)は磁界の強さを測れる
負荷の開閉小さなAC/DC負荷なら直接開閉できる信号出力のみ
丈夫さガラス管は衝撃や曲げに弱いプラスチックのパッケージで、衝撃に強い

ESP32でドア開放を通知する(Telegram)

ESP32のWi-Fiを使えば、ドアが開いた瞬間にドアセンサーからスマホにメッセージを送れます。そのためには、TelegramのBotFatherでボットを作ってトークンと自分のチャットIDを取得し、Arduino IDEにUniversalTelegramBotとArduinoJsonのライブラリをインストールする必要があります。この手順は「ESP32のTelegramボット」の記事で詳しく解説しています。リードの一方の端子をGPIO 27に、もう一方をGNDにつなぎます。コードはドアが開いたときと閉まったときにメッセージを送り、ドアが5分以上開いたままなら知らせ、その時点でWi-Fiが切れていれば10秒後にメッセージを再送します。

C++ (Arduino IDE)
1#include <WiFi.h>
2#include <WiFiClientSecure.h>
3#include <UniversalTelegramBot.h>
4#include <ArduinoJson.h>
5
6const char* WIFI_SSID = "AgAdi";          // Wi-Fi adın (yalnız 2,4 GHz) / your Wi-Fi name (2.4 GHz only)
7const char* WIFI_PASS = "Sifre123";       // Wi-Fi şifren / your Wi-Fi password
8#define BOT_TOKEN "1234567890:AAExxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"   // BotFather'ın verdiği token / token from BotFather
9#define CHAT_ID   "123456789"             // bildirimin gideceği ID / the ID that receives alerts
10
11const int REED_PIN = 27;                  // reed: GPIO 27 ile GND arası / reed between GPIO 27 and GND
12const int LED_PIN  = 2;                   // çoğu DevKit'te dahili LED / onboard LED on most DevKits
13const unsigned long STABLE_MS = 100;      // değişim 100 ms sürmeli / a change must last 100 ms
14const unsigned long REMIND_MS = 300000;   // 5 dk açık kalırsa hatırlat / remind after 5 min open
15const unsigned long RETRY_MS  = 10000;    // gönderilemeyen mesajı 10 sn sonra dene / retry a failed message after 10 s
16
17WiFiClientSecure client;                  // şifreli (HTTPS) bağlantı / encrypted (HTTPS) connection
18UniversalTelegramBot bot(BOT_TOKEN, client);
19
20bool doorOpen = false;                    // onaylanmış kapı durumu / confirmed door state
21bool lastRaw = false;                     // son ham okuma / last raw reading
22unsigned long lastChange = 0;
23unsigned long openedAt = 0;
24bool reminded = false;
25String pending = "";                      // gönderilmeyi bekleyen mesaj / message waiting to be sent
26unsigned long lastTry = 0;
27
28void notify(const String& text) {
29  Serial.println(text);
30  if (WiFi.status() == WL_CONNECTED && bot.sendMessage(CHAT_ID, text)) {
31    pending = "";                         // gönderildi / sent
32  } else {
33    pending = text;                       // Wi-Fi yoksa sonra dene / no Wi-Fi: try again later
34    lastTry = millis();
35  }
36}
37
38void setup() {
39  Serial.begin(115200);
40  pinMode(REED_PIN, INPUT_PULLUP);        // dahili pull-up; GPIO 34-39'da yoktur / internal pull-up; GPIO 34-39 have none
41  pinMode(LED_PIN, OUTPUT);
42
43  WiFi.mode(WIFI_STA);
44  WiFi.begin(WIFI_SSID, WIFI_PASS);
45  Serial.print("Wi-Fi'ye baglaniyor / connecting to Wi-Fi");
46  while (WiFi.status() != WL_CONNECTED) {
47    delay(500);
48    Serial.print(".");
49  }
50  Serial.println();
51  Serial.println(WiFi.localIP());
52  client.setCACert(TELEGRAM_CERTIFICATE_ROOT);   // api.telegram.org kök sertifikası / root certificate of api.telegram.org
53
54  doorOpen = (digitalRead(REED_PIN) == HIGH);    // HIGH = mıknatıs uzak = kapı açık / HIGH = magnet away = door open
55  lastRaw = doorOpen;
56  openedAt = millis();
57  digitalWrite(LED_PIN, doorOpen ? HIGH : LOW);
58  notify(String("Kapı sensörü hazır, kapı ") + (doorOpen ? "AÇIK" : "KAPALI") + " / door sensor ready");
59}
60
61void loop() {
62  unsigned long now = millis();
63  bool raw = (digitalRead(REED_PIN) == HIGH);
64
65  if (raw != lastRaw) {                   // ham değer değişti / raw value changed
66    lastRaw = raw;
67    lastChange = now;
68  }
69
70  if (raw != doorOpen && now - lastChange >= STABLE_MS) {   // kararlı değişim / stable change
71    doorOpen = raw;
72    digitalWrite(LED_PIN, doorOpen ? HIGH : LOW);
73    if (doorOpen) {
74      openedAt = now;
75      reminded = false;
76      notify("Kapı AÇILDI! / door OPENED!");
77    } else {
78      unsigned long sec = (now - openedAt) / 1000;
79      notify("Kapı kapandı / door closed (" + String(sec) + " sn)");
80    }
81  }
82
83  if (doorOpen && !reminded && now - openedAt > REMIND_MS) {   // açık unutuldu / left open
84    reminded = true;
85    notify("Kapı 5 dakikadır açık! / door open for 5 minutes!");
86  }
87
88  if (pending.length() > 0 && now - lastTry > RETRY_MS) {      // bekleyen mesajı yeniden dene / retry the pending message
89    String msg = pending;
90    notify(msg);
91  }
92}

Telegramへのメッセージ送信には1〜2秒かかることがあり、その間ループは待機します。ただし、ステートマシンが送信後にもう一度ピンを読むので、ドアの最後の状態が失われることはありません。センサーを電池で動かしたい場合は、ESP32を「ディープスリープ」にし、esp_sleep_enable_ext0_wakeup(GPIO_NUM_27, 1)を使って、ドアが開いてピンがHIGHになったときに起きるようにします。スリープに入る前に、起動レベルをピンの現在の状態と逆に設定すれば、ボードは開いたときにも閉まったときにも起きます。この場合は、プルアップ抵抗に流れる電流も考慮してください。ドアが閉まっている間はリードが閉じているので、10 kΩの抵抗には3.3 V / 10 kΩ = 330 µAが流れ続けます。100 kΩなら電流は33 µAに下がります。ディープスリープでは、内部プルアップではなく外付けの抵抗を使うのが最も安全です。電池の持ちは「電池寿命計算ツール」で見積もれます。

Raspberry Pi PicoとMicroPythonでリードスイッチを使う

Raspberry Pi Picoでリードスイッチを読むのは、MicroPythonの数行だけでできます。リードをGP15とGNDの間につなぎ、アクティブブザーのプラスをGP14に、マイナスをGNDにつなぎます。Picoは3.3 Vで動くので、モジュールには必ず3V3(OUT)ピンから給電してください。モジュールに5 Vを与えると、DO出力が5 Vまで上がり、Picoの入力を傷めることがあります。Picoの内部プルアップは約50〜80 kΩで、短いケーブルなら十分です。

MicroPython
1from machine import Pin
2import time
3
4reed = Pin(15, Pin.IN, Pin.PULL_UP)   # reed: GP15 ile GND arası / reed between GP15 and GND
5buzzer = Pin(14, Pin.OUT)             # aktif buzzer artı ucu / active buzzer positive leg
6led = Pin("LED", Pin.OUT)             # dahili LED (Pico ve Pico W) / onboard LED (Pico and Pico W)
7
8STABLE_MS = 50                        # debounce süresi / debounce time
9door_open = reed.value() == 1         # 1 = mıknatıs uzak = kapı açık / 1 = magnet away = door open
10last_raw = door_open
11last_change = time.ticks_ms()
12open_count = 0
13led.value(door_open)
14
15while True:
16    raw = reed.value() == 1
17    now = time.ticks_ms()
18    if raw != last_raw:                       # ham değer değişti / raw value changed
19        last_raw = raw
20        last_change = now
21    if raw != door_open and time.ticks_diff(now, last_change) >= STABLE_MS:
22        door_open = raw                       # 50 ms kararlı: gerçek değişim / stable for 50 ms: real change
23        led.value(door_open)
24        if door_open:
25            open_count += 1
26            print("Kapi acildi / door opened:", open_count)
27            buzzer.on()
28            time.sleep_ms(100)                # kısa bip / short beep
29            buzzer.off()
30        else:
31            print("Kapi kapandi / door closed")
32    time.sleep_ms(5)

このコードはtime.ticks_ms()とtime.ticks_diff()を使って、Arduinoと同じチャタリング対策の論理を作ります。ticks_diffはカウンターが一周しても正しい差を返します。速度カウンターのように見逃せないパルスには、reed.irq(trigger=Pin.IRQ_FALLING, handler=...)で割り込みを使えます。Pico Wがあれば、「Telegramボットの記事」にあるPico Wの例をこのコードに加えて、同じイベントをWi-Fi経由でスマホに送れます。Picoを初めて使うなら、「Raspberry Pi Picoとは?」のガイドから始めてください。

リードスイッチのプロジェクトアイデア

  • ワイヤレスのドア・窓センサー:家のすべての開放をESP32でスマホに知らせるか、「MQTTでHome Assistantに」送ります。
  • 冷蔵庫のドアのリマインダー:ドアが30秒以上開いたままになると、ブザーを断続的に鳴らします。
  • サイクルコンピューター:現在の速度、総距離、平均速度をディスプレイに表示します。
  • 風速計と雨量計:気象ステーションでは、カップ型風速計が1回転ごとに、転倒ます型雨量計が1回の転倒ごとに、リードのパルスを1つ出します。
  • フロート式水位スイッチ:フロートの中の磁石が軸の中のリードを開閉し、タンクが満水になったことを知らせます。詳しくは「水位センサー」の記事で。
  • 秘密の引き出しロック:決まった位置に磁石をかざしたときだけ開く「ソレノイドロック」を作ります。
  • 郵便受けの通知:投入口のふたに付けたリードが、郵便物が届いたときにESP32をディープスリープから起こします。

よくある間違いと直し方

  • ピンがでたらめな値を読む:プルアップがありません。pinModeをINPUT_PULLUPにするか、10 kΩの外付けプルアップ抵抗を追加してください。
  • 論理が逆になっている:プルアップを使うと、磁石があるときピンはLOWを読みます。コードがHIGHを期待しているか、センサーがNCタイプなのかもしれません。テスターで確認してください。
  • 1回の開放で何回も数える:チャタリング対策がないか、磁石が側方の閉路ゾーンを通っています。安定時間を追加し、磁石を管と平行にそろえてください。
  • リードがまったく閉じない:磁石が弱い、距離が遠すぎる、磁石が管の中央に垂直に当たっている、または鉄のドアが磁界を吸収しています。より強いネオジム磁石とプラスチックのスペーサーを使ってください。
  • リードが閉じたままになる:過大な電流や容量性負荷で接点が溶着した可能性があります。モーター、電球、リレーのコイルをリードで直接駆動しないでください。負荷の開閉には「リレーモジュール」か「トランジスタ」を使います。
  • ガラス管が割れた:端子を本体に近すぎる位置で曲げています。端子をペンチで押さえ、ガラスから2〜3 mm以上離れた位置で曲げ、はんだ付けは手早く済ませてください。
  • 長いケーブルで誤報が出る:内部プルアップが弱すぎます。4.7〜10 kΩの外付けプルアップ、ピンに100 nFのコンデンサ、ツイストペアのケーブルを使ってください。
リードスイッチは、回路に組み込む前にテスターの導通(ブザー)モードでテストしてください。テスト棒を両方の端子に当て、磁石が近づくとブザーが鳴り、離れると止まれば正常です。この10秒のテストで、リードが動くかどうかと、NOかNCかの両方が確実に分かります。定規で磁石の反応距離を測るのにも、いちばん簡単な方法です。

リードスイッチは、電力を使わずに磁石を検知する、安価でとても信頼性の高いセンサーです。磁石の向きを正しくし、プルアップ抵抗と数行のチャタリング対策のコードを加えれば、何年もトラブルなく動きます。BlueGraysアプリでは、「リードスイッチの状態を読み取る」と「リードスイッチ式ドアセンサー」のプロジェクトのほか、ESP32の動体検知アラームやTelegramで通知するアラームシステムなどのプロジェクトを、ステップごとの回路図とコード付きで紹介しています。

よくある質問

リードスイッチはどのように動作しますか?

リードスイッチは、ガラス管の中の2枚のニッケル鉄の金属片でできています。磁石が近づくと金属片が磁化して引き合い、接点が閉じます。磁石が離れると、ばねの力で金属片が離れます。それ自体は電力を消費せず、Arduinoではボタンと同じように読み取れます。

リードスイッチは常時開ですか、常時閉ですか?

最も一般的なリードスイッチはNO(常時開、Form A)で、磁石がないと開き、磁石が近づくと閉じます。NC(Form B)や3端子の切り替え型(Form C)もあります。ただし、アラーム用のドアセンサーの「NC」表記は、ドアが閉まって磁石が隣にある間は回路が閉じているという意味なので、テスターでテストするのが最も確実です。

磁石はどのくらいの距離からリードスイッチを動作させられますか?

磁石の強さとリードの感度によります。小さなネオジム磁石なら、一般的なリードを通常1〜2 cmから動作させられ、市販のドアセンサーは15〜25 mmで動作します。フェライト磁石では距離はおよそ半分になります。磁石を管と平行に持ち、鉄の面から離すと距離が伸びます。

リードスイッチに抵抗は必要ですか?

Arduinoの入力にはプルアップ抵抗かプルダウン抵抗が必要です。ないと、接点が開いている間ピンがフローティング状態になり、でたらめな値を読みます。Arduino、ESP32、PicoにはINPUT_PULLUPやPin.PULL_UPで有効にできる内部プルアップがあるので、通常は外付けの抵抗は不要です。長いケーブルでは、4.7〜10 kΩの外付け抵抗を使うと安定します。

リードスイッチとホール効果センサーの違いは何ですか?

リードスイッチは機械式の接点で、電源が不要で小さな負荷を直接開閉できますが、チャタリングがあり、ガラス管は割れやすいです。ホールセンサーは半導体で、電源が必要ですが、チャタリングがなく、ずっと高速です。電池で動くドアセンサーにはリード、高速な回転数のカウントにはホールセンサーが向いています。

リードスイッチで商用電源の電球を開閉できますか?

小型のガラス管リードスイッチの開閉容量は通常最大10 Wなので、商用電源の電球やモーターを直接開閉すると接点が溶着し、危険です。リードはArduinoへの信号としてだけ使い、商用電源の負荷はリレーモジュールで制御してください。商用電源を扱うときは、必ず安全のルールを守ってください。

全プロジェクトを見る
リードスイッチの状態を読み取る
#37初級

リードスイッチの状態を読み取る

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

4.4(21)
27
リードスイッチ式ドアセンサー
#38初級
プレミアム

リードスイッチ式ドアセンサー

ライトを見るだけで、ドアが開いているか閉まっているか分かります。ドアに小さな磁石、ドア枠に磁石を検知するスイッチを取り付けます。ドアが閉まっているときは両者が並び、緑のLEDが点灯します。ドアが開いて磁石が離れると、赤いLEDが点灯します。家庭用警報装置のドアセンサーも、こんなシンプルな仕組みです。

4.7(34)
44
ボタンでLEDを点灯(押している間のみ)
#9初級

ボタンでLEDを点灯(押している間のみ)

呼び鈴のように、押している間は点灯し、離すと消灯します。Arduinoはボタンの状態を常に読み取り、押されている間はLEDを点灯させ、指を離した瞬間に消灯させます。このシンプルな回路で、ボードが外部からの操作を検知する仕組みを学べます。

4.8(113)
229
ESP32でボタンによるLED制御(押している間だけ点灯)
#104初級

ESP32でボタンによるLED制御(押している間だけ点灯)

ボタンを押している間はLEDが点灯し、離すと消灯します。ESP32のデジタル入力ピンと内蔵プルアップ抵抗の使い方を学びます。INPUT_PULLUPモードを使うため、外付け抵抗は不要です。

4.6(36)
55
ESP32動体検知アラーム(PIR + ブザー + LED + Telegram)
#145上級
プレミアム

ESP32動体検知アラーム(PIR + ブザー + LED + Telegram)

PIRセンサーが動きを検知するとブザーとLEDを作動させ、同時にTelegramで持ち主に即座に通知するスマート防犯アラームです。アラームが繰り返される頻度を抑えるため、30秒のクールダウン(待ち時間)を設けています。この仕組みはPIRの検知が正しいかを確認したり、誤検知をなくしたりするものではありません。家の防犯、ガレージの監視、倉庫や作業部屋などに使えます。ESP32-CAMなら写真付きの通知を追加でき、通常のESP32 DevKit V1ではテキストで通知します。

0.0(0)
9
Raspberry Pi Picoのボタンのチャタリング対策:IRQとソフトウェアポーリングの比較
#159中級
プレミアム

Raspberry Pi Picoのボタンのチャタリング対策:IRQとソフトウェアポーリングの比較

ボタンを1回押しただけで、コードでは3~4回と数えられますか?原因はチャタリングです。金属接点がマイクロ秒単位で振動します!シリアルモニタで、2つのカウント方法を並べて比較します。未処理のポーリングは振動も検出して多く数え、IRQとソフトウェアによるチャタリング対策は正確に数えます。接点が振動する仕組み、ソフトウェアによる対策(ticks_diff() > 50ms)、Pin.irq()による割り込みの登録、IRQ_FALLINGトリガー、ISRハンドラー、globalとISRの安全な扱い方を学びます。IRQでLEDを切り替え、動作を目で確認できます。シリアルモニタには押すたびに両方のカウントが表示されます。

0.0(0)
12
レーザー防犯システム
#23上級
プレミアム

レーザー防犯システム

映画のレーザーを使った強盗シーンのように、光センサーにレーザー光を当て、人が間に入って光を遮ると、赤いLEDが点灯してブザーが鳴り始めます。警報は自動では止まらず、ボタンやリモコンの電源ボタンで停止します。異常がないときは緑のLEDが点灯します。ドアや引き出しに、目に見えない見張り役を置くユニークな方法です。

4.8(60)
77