前回までで LED の ON/OFF(デジタル出力)とボタン入力を学びました。ただし LED は 「点灯」「消灯」 の二択しか作れていません。今回はその中間、「うっすら光らせる」「だんだん明るくする」を実現する PWM(Pulse Width Modulation) を扱います。
- デューティ比で明るさが決まる仕組み
pico_pwm_write(固定値)とpico_pwm_write_val(変数を差し込める)の使い分けforループでデューティ比を 0→100% に変えるフェードイン と呼吸 LED
PycoBlocks(MicroPythonモード) を開き、左カテゴリ「Pico 電子工作 / 出力」のPWM出力ブロックが今回の主役です。配線は #02 で組んだ「LED + 330Ω」のままで OK なので、新しい部品を追加せずにすぐ試せます。

PWMとは:明るさは「ONの時間割合」で決まる
PWM(Pulse Width Modulation, パルス幅変調)は、GPIO ピンを 非常に速く ON/OFF させて、その ON 時間の割合 を変えることで「中間の明るさ」を作る技術です。Pico 標準では 1 秒間に約 1,900 回(≒ 1.9kHz)の速さで ON/OFF します。目では追えない速さなので、人間には 滑らかな明るさの変化 として見えます。
デューティ比(duty cycle)は、1 周期のうち HIGH の時間が占める割合のことです。0% なら完全消灯、100% なら最大の明るさ、50% ならその中間、というように直感的な指定ができます。下の波形図で、デューティ比と明るさの関係を見てみましょう。

| デューティ比 | 1周期の波形 | LED の見え方 |
|---|---|---|
0% | ずっと LOW | 完全消灯 |
25% | 少しだけ HIGH | かすかに光る |
50% | 半分 HIGH | 中くらいの明るさ |
75% | 大部分 HIGH | 明るい |
100% | ずっと HIGH | 最大の明るさ(点灯と同じ) |
ステップ1:PWMでLEDを薄く光らせよう
配線は #02 と同じです。GP15 → 330Ω 抵抗 → LED → GND の経路で OK。「点灯」ブロックの代わりに 「PWM出力 ピン GP15 デューティ 50%」 ブロックを置きます。



from machine import Pin, PWM
import utime
pwm = PWM(Pin(15))
while True:
pwm.duty_u16(32768) # 50% = 65535 × 50 / 100 ≒ 32768
utime.sleep(0.5)
duty_u16() は MicroPython の PWM API で、0〜65535(16bit 符号なし整数) でデューティを指定します。PycoBlocks のブロックは 0〜100% を内部で 0〜65535 にスケールしてくれるので、人間にわかりやすい % 表記のまま使えます。
💡 試してみよう
0 → 25 → 50 → 75 → 100 と変えて、LED の明るさが段階的に変わることを目で確認しましょう。0% は完全消灯、100% は led.value(1) と同じ最大の明るさです。ステップ2:フェードイン点灯
デューティ値を 0% から 100% まで少しずつ増やす と、LED が「だんだん明るくなる」フェードイン演出が作れます。ここで活躍するのが 「変数を差し込める」 PWM 出力ブロック(pico_pwm_write_val)です。デューティ部分が はめ込み式 になっていて、for ループの繰り返し変数 d をそのまま入れられます。

from machine import Pin, PWM
import utime
pwm = PWM(Pin(15))
while True:
for d in range(0, 100, 5):
pwm.duty_u16(int(max(0, min(100, d)) * 65535 / 100))
utime.sleep(0.05)
range(0, 100, 5) なので d は0, 5, 10, 15, ..., 95 の 20 段階を取ります。0.05 秒 × 20 = 約 1 秒 で 0% から (ほぼ) 100% まで上がりきり、その後 while True で先頭に戻って一気に 0% に落ち、また 0% から明るくなる…を繰り返します。
📝 明るさの上がり方が気になる?──Weber–Fechner の法則
d を 2 乗するなど指数カーブで動かすと改善できます。ステップ3(コラム):なぜちらつかない?──PWM 周波数の話
PWM は「ON / OFF を繰り返している」のに、なぜ LED が ちらつかず連続点灯に見える のでしょうか?答えは 切り替えの速さ(周波数) にあります。
MicroPython の PWM(Pin(...)) 既定周波数は 約 1,907 Hz(≒ 1.9 kHz、1 秒間に約 1,900 回切り替え)です。人間の目が「ちらつき」として認識できる限界は概ね 60Hz 前後 と言われており、それを 30 倍以上上回る速さなので連続点灯に見えるわけです。
周波数を下げてみると?pwm.freq(20) のように 20Hz まで落とすと、目に見えて点滅するようになります(試すと面白いです)。周波数を上げると?数 kHz〜数十 kHz の範囲ではモーターやサーボ、ブザーの制御によく使われます。周波数とデューティ比は 独立に決められる 2 つの軸 だと覚えておきましょう。
演習課題
課題3-4-1:デューティ比からHIGH時間を計算してみよう
PWM 周波数が 1,000 Hz(1 周期 = 1 ms)の信号を出すとき、デューティ比 30% / 50% / 80% のそれぞれで、1 周期のうち HIGH に保たれる時間(ミリ秒)はいくつになるでしょうか?電卓を使わずに計算してみよう。
▶ 模範解答と解説を見る
# 答え:
# 1 周期 = 1 / 1000 秒 = 1 ms
#
# デューティ 30% → HIGH 時間 = 1 ms × 0.30 = 0.30 ms (300 μs)
# デューティ 50% → HIGH 時間 = 1 ms × 0.50 = 0.50 ms (500 μs)
# デューティ 80% → HIGH 時間 = 1 ms × 0.80 = 0.80 ms (800 μs)
#
# 残りの時間が LOW。例えば 30% なら 0.70 ms が LOW。
# 平均電圧(≒ LED の明るさ)は 3.3V × デューティ比 で近似できる。
# 30% → 約 0.99V 相当の明るさ
# 50% → 約 1.65V 相当の明るさ
# 80% → 約 2.64V 相当の明るさ
解説: 計算のコツ:「1 周期の時間 × デューティ比 = HIGH 時間」というシンプルな掛け算です。デューティ比を小数で扱う(30% = 0.30)と式が読みやすくなります。周波数を変えても 「比率」は同じ なので、明るさは保たれます。(例:500Hz でも 1000Hz でも、デューティ 50% なら同じ明るさ)発展:LED に流れる電流も同じ式で平均値を求められます。I = (3.3V − 2.0V) / 330Ω ≒ 約 4 mA。デューティ 50% なら平均電流は約 2 mA。
課題3-4-2:呼吸LEDを作ろう
ステップ2 の「だんだん明るくなる」を改造して、0% → 100% → 0% を行ったり来たり させることで、「ゆっくり呼吸している」ような 演出を作ろう。配線はステップ1 と同じ(GP15 + 330Ω + LED)です。
▶ 模範解答と解説を見る
ブロックの組み合わせ例:

from machine import Pin, PWM
import utime
pwm = PWM(Pin(15))
while True:
for d in range(0, 100, 5): # 明るくしていく
pwm.duty_u16(int(max(0, min(100, d)) * 65535 / 100))
utime.sleep(0.03)
for d in range(100, 0, -5): # 暗くしていく
pwm.duty_u16(int(max(0, min(100, d)) * 65535 / 100))
utime.sleep(0.03)
解説: ポイントは 2 つの for ループを並べる こと。1 本目は step 5 で 0→100、2 本目は step -5 で 100→0 まで戻します。utime.sleep(0.03) なので 20 ステップ × 2 = 40 ステップで約 1.2 秒、つまり 1.2 秒で 1 呼吸 のリズムになります。発展:sleep の値を 0.05、0.1 と変えるとゆっくり呼吸、0.01 にすると速い呼吸になります。深呼吸(5 秒で 1 呼吸)にしたい場合は 0.125 秒 程度が目安です。
課題3-4-3:2つのLEDを交互にフェードさせよう
GP14 と GP15 にそれぞれ LED(330Ω 抵抗付き)を配線し、片方が明るくなるともう片方が暗くなる「シーソー」のような演出を作ろう。

▶ 模範解答と解説を見る
ブロックの組み合わせ例:

from machine import Pin, PWM
import utime
pwm14 = PWM(Pin(14))
pwm15 = PWM(Pin(15))
while True:
for d in range(0, 100, 5): # GP15 が明るく、GP14 が暗く
pwm15.duty_u16(int(max(0, min(100, d)) * 65535 / 100))
pwm14.duty_u16(int(max(0, min(100, 100 - d)) * 65535 / 100))
utime.sleep(0.03)
for d in range(100, 0, -5): # GP15 が暗く、GP14 が明るく
pwm15.duty_u16(int(max(0, min(100, d)) * 65535 / 100))
pwm14.duty_u16(int(max(0, min(100, 100 - d)) * 65535 / 100))
utime.sleep(0.03)
解説: 鍵は 100 - d という計算ブロック です。d が 0 なら GP14 のデューティは 100%、d が 100 なら GP14 のデューティは 0%、というように 足して必ず 100% になる 関係になります。片方が明るくなれば、もう片方は同じ割合だけ暗くなる「シーソー」が成立します。発展:3 つの LED で RGB の色合成にも応用できます。GP13 / GP14 / GP15 にそれぞれ赤・緑・青の LED を繋ぎ、デューティ比を独立に動かすと無限の色を作れます。(実際の RGB LED モジュールは内部で 3 つの LED をまとめたものです)
まとめ
- PWM は GPIO を高速 ON/OFF させ、HIGH 時間の割合(デューティ比) で平均出力を変える技術。
- MicroPython では
PWM(Pin(N)).duty_u16(値)で指定。値は 0〜65535(16bit)だが、PycoBlocks では 0〜100% で書ける。 pico_pwm_writeは固定値、pico_pwm_write_valは 変数や式を差し込める。フェード演出はこちらを使う。- 既定周波数は約 1.9kHz なので人間の目にはちらつかない。
pwm.freq(...)で変更できる。 forループでデューティを動かせば フェードイン・呼吸 LED が作れる。
次の記事ではアナログ 入力(ADC)を扱います。可変抵抗(ボリューム)の回転量を 0〜65535 の数値で読み取り、それをそのまま PWM のデューティ比に渡せば「つまみで明るさを変えられる LED」が完成します。