第3回 IoT電子工作入門講座 RaspberryPi Pico 2Wでボタン入力・LED制御動画はこちら
この記事はこんな方におすすめ
- Pico 2Wの第2回(Lチカ)を終えて、次にボタン操作へ進みたい方
- MicroPythonでスイッチ入力を初めて試す方
- チャタリングという言葉は知っているけど、対策のやり方は知らない方
前回の第2回では環境構築から始まり、LEDを光らせる「Lチカ」を体験しました。
前回の解説記事はこちら。
LEDを点けたり消したりするのは、マイコンから外へ信号を出す「出力」の世界でした。
今回はその逆、外から信号を受け取る「入力」に挑戦します。具体的にはGrove Buttonを押すとLEDが光る、という双方向のやり取りを作ります。
入力ができるようになると、IoTの幅が一気に広がります。
センサーの値を読む、スイッチで動作を切り替える、そうした「外の世界の状態を読み取る」処理はすべて入力が土台だからです。
この記事ではデジタル入力の仕組みから、プルアップ・プルダウン抵抗、そして初心者がほぼ必ずハマるチャタリングの除去まで、実際に動くMicroPythonコードで順番に解説します。
まだ環境構築が済んでいない方は、第1回・第2回の記事を先に読んでおくとスムーズです。
第1回の準備編はこちら。
第2回の環境構築・Lチカ編はこちら。
ボタン入力の仕組みを理解しよう(デジタル入力とは)

まず「デジタル入力」とは何かを押さえましょう。
デジタル入力とは、マイコンが外部からの信号を「HIGH(高い電圧)」か「LOW(低い電圧)」の2つの状態として読み取る仕組みのことです。
RaspberryPi Pico 2W(以下、Pico 2Wと略します。)の場合、HIGHはおよそ3.3V、LOWはほぼ0Vを意味します。
マイコンはこのHighかLowの2段階を見ています。
デジタルの世界では、中間の電圧はあいまいで嫌うので、回路側で「きっちりHIGHかLOW」を作ってあげる必要があります。
(ちなみに、アナログは中間電圧を扱います。第4回ではアナログである照度センサーを使います。)
これが後で出てくる抵抗の話につながります。
ボタンを押すという行為は、この電圧を切り替える操作です。
マイコンはGPIO(汎用入出力ピン)でその電圧変化を監視し、「いま押されている」「いま離れている」を判断します。
私はハードウェアの開発を本業にしていますが、入力回路は「マイコンに何を読ませたいか」を電圧で表現する設計だと考えると、一気に理解が進みます。
デジタル出力とデジタル入力の違い
前回のLチカは「デジタル出力」でした。
マイコンがピンに電圧を出して、LEDを光らせる。
主導権はマイコン側にあります。
今回の「デジタル入力」は完全に逆です。
電圧を決めるのは外側(ボタン)で、マイコンはそれを読み取るだけ。
主導権は外の世界にあります。
コードの上でもこの違いははっきり出ます。
出力は Pin.OUT で設定して led.value(1) のように値を書き込みます。
入力は Pin.IN で設定して switch.value() のように値を読み出します。
書くのか読むのか、ここが出力と入力の決定的な差です。
最初のうちは「OUTは命令、INは観測」と覚えておくと混乱しません。
次のステップでは、ボタンを押したとき電圧が実際にどう動くのかを見ていきます。
ボタンを押すと電圧はどう変わる?
ボタンの中身は、ただの金属接点です。
押すと2つの端子がつながり、離すと切れる。電気的にはスイッチのON/OFFそのものです。
問題は「ボタンがつながったときに、マイコンのピンへどんな電圧が届くか」です。
これは配線のしかた次第で変わります。
同じボタンでも、つなぎ方によって「押すとHIGH」にも「押すとLOW」にもなるのです。
ここがデジタル入力で最初につまずくポイントです。
ボタンを押した=1(HIGH)になる、とは限りません。
むしろ一般的な電子工作の入門記事では「押すとLOW(0)になる」配線が主流です。
なぜそうなるのか、そしてGrove Buttonはどちら側なのか。その答えがプルアップ・プルダウン抵抗の話です。
プルアップ・プルダウン抵抗の役割

プルアップ抵抗・プルダウン抵抗とは、ボタンが押されていないときのピンの電圧を「あらかじめ決めておく」ための抵抗です。
プルアップは電源側(3.3V)へ、プルダウンはグラウンド側(0V)へ、ピンを軽く引っ張っておきます。
「軽く引っ張る」と書いたのは、抵抗を通して弱くつないでいるからです。
ボタンが押されたときは、もっと強い経路で電圧が決まるので、抵抗の力は負けてくれます。
この関係がプルアップ・プルダウンの肝です。
抵抗がないと何が起きる?──フローティング
抵抗を入れず、ボタンとピンだけをつないだ状態を考えてみます。
ボタンを離している間、ピンはどこにもつながっていない「宙ぶらりん」の状態になります。
これをフローティング(浮いた状態)と呼びます。
フローティングのピンは、まわりのノイズを拾ってHIGHになったりLOWになったりフラフラします。
マイコンから見ると「押してないのに押された判定が出る」「値が暴れる」といった不安定な動きになります。
これを防ぐのがプルアップ・プルダウン抵抗です。
ボタンを離している間も、抵抗がピンの電圧をHIGHかLOWのどちらかへ確実に固定してくれます。
だから入力回路には必ずどちらかの抵抗が必要になるわけです。
自分で抵抗を付けるのは少し面倒ですが、安心してください。
今回使うGrove Buttonには、この抵抗が最初から基板に載っています。
次の項目でその「載っている向き」が重要になります。
Grove Buttonはスイッチを押すとHIGHが出る
ここが今回いちばん大事なポイントです。

Grove Buttonはボタンを離している間はLOW(0V)に固定され、押すとHIGH(3.3V)に切り替わります。
一般的なスイッチ接続とは逆の動作です。一般的な使い方は、ボタンを離しているときがHIGH、スイッチを押すとGNDに接続されてLOWとすることが多いです。
このように多くのマイコン入門記事は「プルアップ接続(押すとLOW=0)」を前提にしています。
その感覚のままGrove Buttonのコードを書くと、「押してないのに反応する」「押しても無反応に見える」と混乱します。
私も最初これで首をかしげました。
整理すると、こうなります。
Grove Buttonは後者です。
だから今回のコードでは「switch.value() が 1 なら押された」と判定します。ネット上の多くのサンプルとは判定が逆になる、ここを必ず覚えておいてください。
なお今回のコードはピンを Pin.IN だけで設定します。
Grove Buttonの基板にプルダウン抵抗がすでに載っているため、マイコン内蔵のプル抵抗を使う必要がないからです。
意図をコード上ではっきりさせたい場合は Pin(SWITCH_PIN, Pin.IN, Pin.PULL_DOWN) と書いて内蔵プルダウンを併用しても問題ありません。
配線手順(Grove拡張基板で簡単接続)
配線はGroveケーブルを使うので、とても簡単です。

ハンダ付けもブレッドボードも不要で、ケーブルを挿すだけで完成します。
これで配線は完了です。
ボタンモジュールがD18、LEDモジュールがD16です。
この対応はあとでコードのピン番号と一致させるので、ここで覚えておいてください。
ちなみに今回のGrove LEDモジュールは、基板側に電流制限抵抗が組み込まれているので追加の抵抗は不要です。
もしLEDを単体(生のLED)で使う場合は、電流制限抵抗(LEDに流れる電流を抑えるために直列に入れる抵抗)を必ず入れてください。
抵抗値の求め方の計算ツールを作りました。抵抗値を計算してくれるので便利です。
基本プログラム①ボタンを押している間、LEDが点灯するプログラム
配線ができたら、いよいよプログラムです。
まずは「ボタンを押している間だけLEDが光る」という、いちばんシンプルな基本プログラムを作ります。
このコードは、ボタンの状態を一定間隔で読み取り、押されていればLED点灯・離されていればLED消灯する、という単純な仕組みです。
入力と出力をつなぐ最小の形なので、ここをしっかり理解すると応用が一気に楽になります。
サンプルコード全文
このコードは、スイッチの状態を10ミリ秒ごとに確認して、その結果をそのままLEDに反映するためのものです。
switch.value() が 1(押された)かどうかを見ているのがポイントです。
Grove Buttonはプルダウン接続なので、押されたときが 1 になります。
from machine import Pin
import time
# ピン番号の定義
LED_PIN = 16 # LEDをつなぐピン(GP16 = D16コネクタ)
SWITCH_PIN = 18 # スイッチをつなぐピン(GP18 = D18コネクタ)
# 待機時間の定義
SLEEP_TIME = 0.01 # 10ms ごとにスイッチを確認する
# スイッチ状態の定義
SWITCH_PRESSED = 1 # スイッチが押されている状態
SWITCH_RELEASED = 0 # スイッチが離されている状態
# LED状態の定義
LED_ON = 1 # LED点灯
LED_OFF = 0 # LED消灯
# ピン設定
led = Pin(LED_PIN, Pin.OUT)
switch = Pin(SWITCH_PIN, Pin.IN) # Grove Button基板内蔵のプルダウンを使用
print("スイッチ入力プログラム開始")
# メインループ
while True:
switch_state = switch.value()
if switch_state == SWITCH_PRESSED:
led.value(LED_ON)
else:
led.value(LED_OFF)
time.sleep(SLEEP_TIME)
Code language: Python (python)
実行すると、コンソールに「スイッチ入力プログラム開始」と表示され、あとはボタンを押している間だけLEDが点灯します。
押せば光り、離せば消えます。
もし「押しても消えたまま」「押してないのに光る」という場合は、配線のコネクタ(D18・D16)と、ピン番号の対応を見直してください。
なお、このサンプルプログラムおよび後半の応用プログラムは下記のGitHubで公開しています。
今後公開するプログラムも追加していく予定ですので、是非参考にしてください。
コードの解説
最初の from machine import Pin と import time は、ピン操作と待機処理に必要なモジュールの読み込みです。
MicroPythonでハードを触るときの定番なので、毎回書くものと思ってください。
次に各種の番号や状態を定数として名前付きで定義しています。
SWITCH_PRESSED = 1 のように名前を付けておくと、あとでコードを読んだとき「1って何だっけ?」と悩まずに済みます。
とくに SWITCH_PRESSED = 1 の「1」は、Grove Buttonがプルダウン接続で押すとHIGHになるからこの値です。
ここに意味を込めておくのが大事です。
led = Pin(LED_PIN, Pin.OUT) でLEDのピンを出力に、switch = Pin(SWITCH_PIN, Pin.IN) でスイッチのピンを入力に設定しています。
出力は書き込む、入力は読み取る、という役割の違いがそのまま設定に出ています。
while True: の無限ループの中で、switch.value() でいまのスイッチ状態を読み取り、押されていれば点灯・そうでなければ消灯しています。
最後の time.sleep(SLEEP_TIME) は10ミリ秒の待機で、マイコンに少し休みを与えてループが暴走しないようにしています。
これで「押している間だけ光る」は完成です。ただ実際に試すと、たまに反応が変だと感じることがあります。
その正体が次に説明する「チャタリング」です。
応用:チャタリング除去とトグル動作
基本プログラムは「押している間だけ光る」でした。
次は一段進んで「押すたびにON/OFFが切り替わる(トグル)」を作ります。
トグル動作を正しく作るには、2つの壁を越える必要があります。
1つはチャタリングの除去、もう1つはエッジ検出です。順番に解説します。
チャタリングとは?
金属の接点が物理的にバウンドするために起こります。
人間には一瞬の「カチッ」でも、マイコンの目から見ると、その一瞬の間に何度もON/OFFが起きているように見えます。
結果として、1回しか押していないのに「2回押された」「3回押された」と誤カウントしてしまいます。
トグル動作でこれが起きると最悪です。
1回押したつもりが偶数回カウントされ、ONにしたはずなのにOFFに戻ってしまう、といった「効いたり効かなかったり」する挙動になります。だからトグルを作るならチャタリング対策は必須です。
対策には大きく分けてハードウェアとソフトウェアの2通りがあります。
今回はソフトウェアで対応する「単純遅延方式」を使います。
チャタリングはハードウェアでも対策できます。
代表的なのはコンデンサと抵抗を組み合わせたCR積分回路で、接点のバタつきを電気的になめらかにする方法です。
確実ですが部品の追加が必要になります。
一方ソフトウェア方式は部品ゼロで、プログラムだけで対応できるのが利点です。
学習段階や軽い用途ならソフトウェア方式で十分実用になります。
チャタリング除去例①単純遅延方式で除去する
単純遅延方式とは、ボタンが押されたと検出したあと、一定時間(今回は20ミリ秒)はそれ以降の入力を無視する、という考え方です。
チャタリングはほんの一瞬で収まるので、その間だけ目をつぶってしまえば誤カウントを防げます。
実装では「最後に押された時刻」を記録しておき、新しい押下を検出したときに「前回からどれだけ時間が経ったか」を見ます。
経過時間が20ミリ秒以下なら、それはチャタリングと判断して無視します。
時間の計測には time.ticks_ms() と time.ticks_diff() を使います。
time.ticks_ms() は現在のミリ秒カウントを返す関数、time.ticks_diff() は2つの時刻の差を安全に計算する関数です。単純な引き算ではなく ticks_diff() を使うのには理由があります。
カウンタが一周(オーバーフロー)しても、正しく差を求められるからです。
この20ミリ秒という値(DEBOUNCE_MS)は、短すぎるとチャタリングを取りこぼし、長すぎると素早い連打を拾えなくなります。
20〜50ミリ秒あたりが実用的な目安です。
エッジ検出で「押した瞬間」をとらめる
エッジ検出について説明します。
エッジ検出とは、「押している間ずっと」ではなく「押した瞬間だけ」を捉える技術のことです。
もしエッジ検出をせずに「押されている=1」のたびにトグルしたらどうなるでしょう。
ボタンを1秒押し続けるだけで、状態が何十回も反転してしまいます。これでは使い物になりません。
狙いは「1回の押下=1回の反転」です。
そこで、前回のスイッチ状態と今回のスイッチ状態を比較します。
previous_switch_state が RELEASED(離れていた)で、current_switch_state が PRESSED(押された)になった瞬間、つまり「離れている → 押された」へ変化したその一回だけを処理します。
この変化の瞬間を「立ち上がりエッジ」と呼びます。
応用プログラム:スイッチを押した回数をカウント、LEDはトグル動作
このコードは、チャタリング除去(単純遅延方式)とエッジ検出を組み合わせて、ボタンを押すたびにLEDがON/OFFと切り替わるトグル動作を実現するためのものです。
previous_switch_state と current_switch_state を比較している部分がエッジ検出、time.ticks_diff() で時間差を見ている部分がチャタリング除去にあたります。
from machine import Pin
import time
# ── ピン番号の定義 ─────────────────────────────────────────────
LED_PIN = 16
SWITCH_PIN = 18
# ── 状態の定義(Grove Button はプルダウン接続) ────────────────
SWITCH_PRESSED = 1
SWITCH_RELEASED = 0
LED_ON = 1
LED_OFF = 0
# ── チャタリング除去の待機時間 ─────────────────────────────────
DEBOUNCE_MS = 20 # 20ms 以内の再入力はチャタとして無視
# ── ピン設定 ──────────────────────────────────────────────────
led = Pin(LED_PIN, Pin.OUT)
switch = Pin(SWITCH_PIN, Pin.IN)
# ── 状態変数 ──────────────────────────────────────────────────
led_state = LED_OFF
previous_switch_state = SWITCH_RELEASED
last_press_time = time.ticks_ms()
count = 0
led.value(led_state)
print("応用プログラム 開始")
# ── メインループ ───────────────────────────────────────────────
while True:
current_switch_state = switch.value()
if previous_switch_state == SWITCH_RELEASED and \
current_switch_state == SWITCH_PRESSED:
now = time.ticks_ms()
if time.ticks_diff(now, last_press_time) > DEBOUNCE_MS:
count += 1
if led_state == LED_OFF:
led_state = LED_ON
else:
led_state = LED_OFF
led.value(led_state)
print("押された回数:", count)
last_press_time = now
previous_switch_state = current_switch_state
Code language: Python (python)
実行すると、コンソールに「応用プログラム 開始」と表示され、ボタンを押すたびにLEDがON→OFF→ON…と切り替わります。
同時に「押された回数: 1」「押された回数: 2」とカウントが増えていきます。
もしチャタリングが残って数字が一度に2ずつ増えるようなら、DEBOUNCE_MS の値を30〜50に上げてみてください。
コードの流れを追うと、まずループの先頭でいまの状態を読み取り、「離れている → 押された」へ変化したかを確認します。
変化していたら、前回の押下から20ミリ秒以上経っているかをチェックし、条件を満たしたときだけLEDの状態を反転させて書き込みます。
最後に previous_switch_state = current_switch_state で今回の状態を次回用に保存します。
この保存を忘れるとエッジ検出が成立しないので、ここも重要なポイントです。
まとめ・次回予告
今回はGrove Buttonを使って、デジタル入力の基礎からチャタリング除去・トグル動作までを実際のコードで体験しました。
入力ができるようになったことで、マイコンと外の世界が双方向でやり取りできるようになりました。
とくにGrove Buttonの「押すとHIGH」という挙動は、一般的なプルアップ接続と逆なので、つまずきやすい最重要ポイントです。
ここを押さえておけば、今後どんなスイッチを扱うときも判定で迷わなくなります。
今回の講座で学んだこと
- デジタル入力はHIGH/LOWの2つの状態を読み取る仕組みだと理解した
- プルアップ・プルダウン抵抗がフローティングを防ぐ役割を理解した
- Grove Buttonはプルダウン接続で「押すとHIGH(value=1)」になると分かった
- チャタリングの正体と、単純遅延方式での除去方法を理解した
- エッジ検出を使って、押すたびに切り替わるトグル動作を実装できた
次回(第4回)は照度センサーを使って、明るさを数値として読み取るアナログ入力に挑戦します。
今回のデジタル入力(0か1か)に対して、次回は連続した値を扱う世界です。
入力の世界がさらに広がるので、ぜひ続けて挑戦してみてください。
今回の記事を動画でも解説しています。あわせてご覧ください。

