LEDの明るさを滑らかに制御してみよう

MicroPython

ラズパイPicoでLチカはできたけど、その先に何をしようか迷っていませんか。

LEDがただ点いたり消えたりするだけだと、少し物足りなく感じるタイミングが来ます。

そこで次の一歩としておすすめなのが、LEDの明るさを滑らかにフェードイン・フェードアウトさせる動きです。

映画のエンディングのように、じわっと明るくなったり、ふわっと暗くなったりする光は、見ているだけで楽しいです。

しかもこのフェード表現は、Raspberry Pi Pico と MicroPython だけで、意外とシンプルなコードで実現できます。

この記事では、すでにLチカとPWMの基礎を一度触ったことがある電子工作初心者を想定しています。

「Lチカはできた」「PWMもなんとなく分かった」「次のステップに行きたい」という人向けの内容です。

完成イメージとして、1つのLEDが暗いところから少しずつ明るくなり、今度はゆっくり暗くなる、という動きを繰り返します。

この記事を読み終わるころには、自分の手でLEDの明るさを滑らかにコントロールできるようになります。

Raspberry Pi Pico でも Pico2 でも、MicroPython の環境さえあれば基本の考え方は同じです。

完成イメージ

LEDが滑らかに点灯消灯を繰り返すよ

この記事でできること(目的とゴール)

この記事では、Raspberry Pi Pico と MicroPython を使って、LED1個の明るさを滑らかに変えるプログラムを作ります。

  • PWM(パルス幅変調)の基本的な考え方
  • Raspberry Pi PicoでLEDの明るさを制御する方法
  • 初心者向けコード → リファクタリング済みコードへの流れ

PWMは「電気のON/OFFを高速で切り替えて、平均電力で明るさを調整する」技術なんだよ。電気を弱めてるわけじゃないの!

ピコさん
ピコさん
PWMは「電気のON/OFFを高速で切り替えて、平均電力で明るさを調整する」技術なんだよ!

準備:必要な道具と環境

パーツ一覧

品名役割代替案
Raspberry Pi Pico Wマイコン本体Pico(無印)でも可
Micro USBケーブルデータ通信・電源供給Type-A to Micro USB(データ通信対応)
ブレッドボード配線用の土台小型でもOK
ジャンパワイヤ(オス-オス)接続ケーブル色違いで用意すると便利
LED(赤・黄・緑など)点灯テスト用単色1つでも可
抵抗(220〜330Ω)電流制限470Ωでも可

動作環境(筆者の検証環境)

  • PC:MacBook Air (M1, macOS 15.5 arm64)
  • エディタ:Thonny 4.1.6(Python 3.10.11, Tk 8.6.13)
  • デバイス:Raspberry Pi Pico W(MicroPython)

もちろん、Windows や Linux 環境でも動作します。MicroPythonを導入していれば同じコードで実行可能です。

配線図

安全上の注意

  • LEDには極性(+と−)があります。長い脚が+側です。
  • 抵抗を必ず直列に入れましょう。入れ忘れるとLEDが焼ける危険があります。
  • 静電気が強い日や金属テーブル上では、手で基板を触る前に放電しておきましょう。

まずは動かすためのコードから

このコードは、「とにかく分かりやすさ優先」で書いています。

多少冗長でも、挙動がイメージしやすいようにコメントをたくさん入れています。

from machine import Pin, PWM
from time import sleep

# ==========================
# 定数定義
# ==========================
LED_PIN = 15               # LED接続ピン
PWM_FREQ_HZ = 1000         # PWM周波数(Hz)

MAX_DUTY = 65535           # 16bitスケールの最大値

FADE_STEP_SECONDS = 0.01   # 1ステップごとの待ち時間(秒)

# ==========================
# 関数定義
# ==========================

# ===================================================
# 関数名:
#   set_brightness
#
# 役割:
#   LEDの明るさをパーセンテージ(0〜100)で指定し、
#   指定した明るさでLEDを点灯(または消灯)させる
#
# 引数:
#   <PWM> pwm / 制御対象となるPWMオブジェクト
#   <int> percent / LEDの明るさを0〜100のパーセンテージで指定
#
# 戻り値:
#   なし / 画面には何も返さず、pwmのデューティ比を変更するだけ
#
# ローカル変数:
#   ratio: percentを0.0〜1.0の比率に変換した値
#   duty_value: duty_u16に渡す0〜MAX_DUTYの整数値
#
# 使用する定数:
#   MAX_DUTY: PWMのデューティ比を16bitスケールで表したときの最大値(65535)
#
# グローバル変数:
#   なし
#
# 注意点:
#   ・percentは0〜100の範囲を想定している(それ以外を渡すと想定外の明るさになる)
#   ・pwmは事前にPWM(Pin(LED_PIN))で初期化され、freqも設定済みであること
#   ・MAX_DUTYなどの定数はモジュール先頭で定義しておくこと
# ===================================================
def set_brightness(pwm, percent):
    ratio = percent / 100.0
    duty_value = int(MAX_DUTY * ratio)
    pwm.duty_u16(duty_value)

# ==========================
# メイン処理
# ==========================
def main():
    pwm = PWM(Pin(LED_PIN))
    pwm.freq(PWM_FREQ_HZ)

    while True:
# ------------------------------
# フェードイン(0% → 100%)
# ------------------------------
# 下の for 文は、ざっくり書くと
#   set_brightness(pwm, 0)
#   set_brightness(pwm, 1)
#   set_brightness(pwm, 2)
#   ...
#   set_brightness(pwm, 100)
# を順番に実行しているイメージです。
        for percent in range(0, 101):  # 0,1,2,...,100
            set_brightness(pwm, percent)
            sleep(FADE_STEP_SECONDS)

# ------------------------------
# フェードアウト(100% → 0%)
# ------------------------------
# こちらは逆向きに
#   100, 99, 98, ..., 0
# という順番で set_brightness を呼び出しています。
       for percent in range(100, -1, -1):  # 100,99,98,...,0
            set_brightness(pwm, percent)
            sleep(FADE_STEP_SECONDS)


# ==========================
# 実行部
# ==========================
if __name__ == "__main__":
    main()

このコードを保存し、Picoに書き込んで実行すると、LEDがゆっくり明るくなり、またゆっくり暗くなる様子が見られるはずです。

「あ、ちゃんと滑らかに変わってる!」と感じられたら成功です。

ピコさん
ピコさん
関数「set_brightness」部分の説明について 関数詳細をコード内にいれてみました。
こうやってコード内に書くと後で見返したときにわかりやすいよ

リファクタリングコード

こちらが、定数や関数を整理したリファクタリング済みのコードです。

中身は同じ「フェードイン・フェードアウト」ですが、設定を変えやすく、読みやすい構造になっています。

from machine import Pin, PWM
from time import sleep

# ==========================
# 定数定義
# ==========================
LED_PIN = 15               # LED接続ピン
PWM_FREQ_HZ = 1000         # PWM周波数(Hz)

MAX_DUTY = 65535           # 16bitスケールの最大値

MIN_DUTY_PERCENT = 0       # 最小の明るさ(0%)
MAX_DUTY_PERCENT = 100     # 最大の明るさ(100%)

FADE_STEPS = 100           # フェードの分割数(多いほどなめらか)
STEP_SECONDS = 0.01        # 1ステップごとの待ち時間(秒)

HOLD_TOP_SECONDS = 0.5     # 最大輝度でキープする時間(秒)
HOLD_BOTTOM_SECONDS = 0.5  # 最小輝度でキープする時間(秒)


# ==========================
# 関数定義
# ==========================
def set_brightness(pwm, percent):
    ratio = percent / 100.0
    duty_value = int(MAX_DUTY * ratio)
    pwm.duty_u16(duty_value)

# 関数名:
#   fade
#
# 役割:
#   LED の明るさを「スタート → ゴール」へ向けて
#   少しずつ変化(フェード)させる
#
# 引数:
#  <PWM> pwm / 制御対象となる PWM オブジェクト
#  <int> start_percent / フェード開始時の明るさ(0〜100%)
#  <int> end_percent   / フェード終了時の明るさ(0〜100%)
#  <int> steps         / 何段階に分けて変化させるか(多いほど滑らか)
#  <float> step_delay  / 各ステップごとの待ち時間(秒)
#
# 戻り値:
#   なし / 明るさを変化させるだけで、値は返さない
#
# ローカル変数:
#   step_size: 1ステップごとに変化させる明るさの量(%)
#   current: 現時点の明るさ(ループ中で更新される)
#
# 使用する定数:
#   なし(ただし set_brightness 内で MAX_DUTY を使用)
#
# グローバル変数:
#   なし
#
# 注意点:
#   ・start_percent と end_percent の値は 0〜100 の範囲を想定している
#   ・steps が小さすぎると変化がカクカクになる
#   ・pwm は事前に PWM(Pin(x)) で初期化し、freq が設定されている必要がある
# ===================================================
def fade(pwm, start_percent, end_percent, steps, step_delay):
    step_size = (end_percent - start_percent) / steps
    current = start_percent

    for _ in range(steps + 1):
        set_brightness(pwm, current)  # ← percentのまま呼べる
        sleep(step_delay)
        current += step_size

# ==========================
# メイン処理
# ==========================
def main():
    pwm = PWM(Pin(LED_PIN))
    pwm.freq(PWM_FREQ_HZ)

    while True:
        # フェードイン(暗い → 明るい)        
        fade(
            pwm,
            MIN_DUTY_PERCENT,
            MAX_DUTY_PERCENT,
            FADE_STEPS,
            STEP_SECONDS,
        )
        sleep(HOLD_TOP_SECONDS)

        # フェードアウト(明るい → 暗い)
        fade(
            pwm,
            MAX_DUTY_PERCENT,
            MIN_DUTY_PERCENT,
            FADE_STEPS,
            STEP_SECONDS,
        )
        sleep(HOLD_BOTTOM_SECONDS)


# ==========================
# 実行部
# ==========================
if __name__ == "__main__":
    main()

比率やステップ数、キープ時間を定数で管理しているので、好みに合わせた光り方を試しやすくなっています。

ここまで来ると、他のGPIOピンや複数のLEDにも応用しやすくなります。

仕組みを理解する(技術解説)

ここからは、コードの裏側で何が起きているのかを、初心者向けに整理してみます。

なんとなく動かすだけでなく、仕組みをイメージできると、次のステップに進みやすくなります。

PWMで明るさが変わる理由

PWM(Pulse Width Modulation)は、電圧をアナログ的に変えているわけではありません。

実際には、高速にONとOFFを繰り返して、平均のON時間を変えることで明るさを調整しています。

たとえば1kHzのPWMなら、1秒間に1000回ON/OFFを切り替えています。

そのうち80%をONにすれば、LEDは「8割の明るさ」に感じられます。

duty_u16とデューティ比

MicroPythonのPWMでは、duty_u16 という16bitスケールの値でON時間の割合を指定します。

0〜65535の整数で、0なら完全OFF、65535なら常にONというイメージです。

リファクタリング前のコードでは、パーセントから比率を計算して、MAX_DUTYを掛けていました。

リファクタリング後のコードでは、明るさを 0〜100% のパーセント表記に統一しています。
これにより、初心者でも直感的に理解しやすくなり、コード内の計算も比較的シンプルになります。

for文で滑らかさを作る

LEDが滑らかに変化して見えるのは、for文で少しずつ明るさの値を変えているからです。

ステップ数を増やすと、より細かく変化するので、目にはよりなめらかに映ります。

逆にステップ数を減らすと、「カクカク」した変化になり、フェードというより段階的な点灯になります。

FADE_STEPS と STEP_SECONDS の組み合わせを変えることで、雰囲気の違う光り方を作れます。

関数化のメリット

set_brightness や fade のように、処理を関数にまとめると、コードの見通しが良くなります。

同じ処理を何度もコピペする必要がなくなるので、バグも入りにくくなります。

将来的にRaspberry Pi Pico で複数のLEDや、Pico2で別のPWM出力を扱うときにも、この考え方はそのまま使えます。

ピコさん
ピコさん
PWMは「ON時間の割合」で明るさが決まるよ!

トラブルシューティング(チェックリスト)

うまく動かないときは、落ち着いて1つずつ確認していくのがコツです。

よくある症状を表にまとめたので、チェックリストとして使ってください。

症状考えられる原因対処方法
LEDがまったく光らない配線ミス/LEDの極性逆/GPIO番号の不一致LEDの足の向きと抵抗の位置、LED_PINの値を見直す
点くがフェードしないPWMではなくPin出力を使っている必ずPWM(Pin(LED_PIN)) を使っているか確認する
チカチカ点滅して見えるPWMの周波数が低すぎるpwm.freq(PWM_FREQ_HZ) で1000Hz程度に設定する
明るさの変化がカクカクしているFADE_STEPS が少なすぎるFADE_STEPS を増やして、STEP_SECONDSも調整する
すごくゆっくり変化してしまうSTEP_SECONDS が大きすぎるSTEP_SECONDS を0.01など小さめに設定する
エラーで止まってしまうインデントやコロンの打ち間違いエラーメッセージの行番号を見て、該当行を慎重に見直す

配線写真が無くても、上の表を上から順に確認していけば、多くのトラブルは解消できます。

特にGPIO番号とGNDの接続は、落ち着いて見直してみてください。

まとめと次のステップ

ここまで、Raspberry Pi Pico と MicroPython を使って、LEDの明るさを滑らかに変える方法を見てきました。

Lチカから一歩進んで、PWMを使ったフェードイン・フェードアウトが自分で書けるようになっていればバッチリです。

この記事のポイントを3行で振り返ってみます。

  • PWMは「ON時間の割合」で明るさを調整する仕組み
  • for文でデューティ比を少しずつ変えるとフェードが作れる
  • 定数と関数で整理すると、調整・再利用がぐっと楽になる

次のステップとしては、例えばこんな応用が考えられます。

  • 3色LEDを使って、信号機のように順番にフェードさせる
  • ボタン入力と組み合わせて、明るさを手動で変える
  • 複数のPWMチャンネルで、複数のLEDを別々に制御する

Raspberry Pi Pico でも Pico2 でも、今回のような基本的なPWM制御はほぼ共通です。

この先、センサーやモーター制御に進むときも、今回の「比率で考える」「関数でまとめる」という考え方はそのまま活かせます。

もし上記が難しいと感じたら

以下の内容を見てみてください。

コメント