広告の掲載について

当ブログは、商品やサービスの紹介にアフィリエイト広告を利用しており、収益を得ています。
ただし、読者の皆様にとって有益な情報提供を第一に考えております。

広告 IoT電子工作入門 電子工作

Raspberry Pi Pico 2Wで照度センサーLED調光【第4回IoT電子工作】

こんな方におすすめです

  • Pico 2Wで照度センサーを初めて使う方
  • ADCとPWMのつながりを手を動かして理解したい方
  • 暗くなるとLEDが自動で明るくなる仕組みを作りたい方

「Lチカはできた。次は何を作ればいい?」そう思って来た方にちょうどいい内容です。

前回の第3回では、ボタン入力でLEDを制御しました。

ボタンは「押した」か「押していないか」の2択で、デジタル的な入力です。

今回はその先へ進みます。

Grove光センサーで「明るさ」というアナログな値を読み取ります。

読み取った値をPWMに渡して、LEDをじわっと調光します。

手でセンサーを遮ると、LEDがなめらかに明るくなる仕組みを3ステップで作ります。

Raspberry Pi Pico2Wのシリーズ記事はこちらです。

Raspberry Pi Pico 2Wで照度センサーLED調光【第4回IoT電子工作】

2026/8/30  

こんな方におすすめです Pico 2Wで照度センサーを初めて使う方 ADCとPWMのつながりを手を動かして理解したい方 暗くなるとLEDが自動で明るくなる仕組みを作りたい方 「Lチカはできた。次は何を ...

Raspberry Pi Pico 2Wでボタン入力・LED制御【第3回IoT電子工作】

2026/8/30  

第3回 IoT電子工作入門講座 RaspberryPi Pico 2Wでボタン入力・LED制御動画はこちら https://youtu.be/gZcQWe1X1FU?si=qtHp0hmu1m_js9 ...

Raspberry Pi Pico 2Wの環境構築とLチカ【第2回IoT電子工作】

2026/8/30  

前回の第1回記事では、Raspberry Pi Pico 2Wの基本的な特徴と必要な機材について解説しました。 https://thunderblog.org/electronic_work/rasp ...

Raspberry Pi Pico 2Wを始める準備【第1回IoT電子工作】

そんなあなたに。 私は製造業でエンジニアとして18年以上働いており、実際の現場でIoTシステムの導入やデータ分析を担当してきました。 その経験を活かして、初心者の方でも確実にステップアップできるよう、 ...

この記事でわかること

  • Pico 2Wで照度センサーを読む基本
  • ADC(アナログ-デジタルコンバーター)の役割
  • GL5528(フォトレジスタ、光の強さで抵抗値が変わる素子)の仕組み
  • read_u16()(16bit整数で値を返すADC読み取りメソッド)とduty_u16()(16bit整数でduty比を指定するPWMメソッド)のつながり
  • 暗いほどLEDを明るくするPWM(パルス幅変調)の反転ロジック
  • 実測値に合わせるキャリブレーションとremap()の使い方
  • 明るさの変化をなめらかにするスムージングの入れ方

今回使う部品

今回使う部品は、Pico 2W、Grove Shield for Pi Pico、Grove光センサー、Grove LEDです。

Grove光センサーはGL5528というフォトレジスタを使っています。

光センサーはA0ポート、LEDはD16ポートに接続します。

付属のGroveケーブルを差し込むだけで接続できます。

ケーブルはGroveコネクタ専用の4芯タイプで、Grove Starter Kitに同梱されています。

アナログ信号とデジタル信号の違い

デジタル信号は0か1の世界です。

第2回のLチカや第3回のボタン入力は、このデジタル信号で扱えました。

一方で、照度センサーが扱う光の強さはアナログ信号です。

アナログ信号は連続した値で、自然界の温度、音、光のような変化に向いています。

Pico 2Wは内部でデジタル処理をするため、アナログ電圧を数値へ変換する必要があります。

ADC(アナログ-デジタル変換)とは

ADC(アナログ-デジタルコンバーター)は、アナログ電圧をデジタルの数値へ変換する機能です。

MicroPythonでは、ADCピンを初期化してからread_u16()を呼ぶと、0〜65535の値として読み取れます。

今回の実測では、明るいほど値が大きく、暗いほど小さくなります。

ここに注意

今回のGrove光センサーでは、明るいとread_u16()の値が大きくなり、暗いと小さくなります。

この向きを間違えると、LEDの明るさ制御が逆になります。

なぜ65535?

65535は、16bitで表せる最大値です。

Pico 2WのADC自体は12bit精度で、素の分解能は0〜4095です。

ただしMicroPythonのread_u16()は、この値を16bitの範囲へスケーリングして返します。

そのため、コード上では0〜65535として扱えます。

この仕様のおかげで、後で使うduty_u16()と同じものさしで考えられます。

GL5528フォトレジスタの仕組み

GL5528は、光の強さで抵抗値が変わるフォトレジスタです。

光が当たると抵抗値が下がり、暗くなると上がります。

この変化を電圧として取り出し、ADCが数値へ変換します。

背景にあるのが分圧回路の考え方です。

分圧回路の考え方はこちらです。

明るさと電圧の関係

今回使うGrove光センサーでは、実測で次の向きになりました。

  • 明るいとread_u16()の値が大きくなります。
  • 暗いとread_u16()の値が小さくなります。

たとえば部屋が明るいと、値は65535に近づきます。

手でセンサーを遮ると、値は0に近づきます。

別のセンサーモジュールでは向きが変わる場合があります。

Grove ShieldのADCポート(A0=GP26)

Grove Shield for Pi Picoでは、A0、A1、A2のようにアナログ入力用のポートが用意されています。

今回使うA0ポートは、Pico 2WのGP26に対応します。

シールド上の表記はA0ですが、MicroPythonのコードではPin(26)と書きます。

この表記の違いが、初心者には紛らわしいポイントです。

配線方法

配線はGroveケーブルを差し込むだけです。

配線の手順

  1. Pico 2WからUSBケーブルを抜きます。
  2. Grove Shield for Pi PicoをPico 2Wに取り付けます。
  3. Grove光センサーをGroveケーブルでA0ポートに接続します。
  4. Grove LEDをGroveケーブルでD16ポートに接続します。

通電したまま抜き差しすると不安定になることがあるので、配線変更時はUSBを抜いてから作業してください。

コード①:ADC値をシリアルモニタで確認する

最初はLEDを制御せず、照度センサーの値だけを確認します。

このコードで、GP26のADC値を読み取り、0〜100%へ換算してシリアルモニタに表示します。

from machine import ADC, Pin
import time

light_sensor = ADC(Pin(26))

while True:
    value = light_sensor.read_u16()
    percent = round(value / 65535 * 100, 2)
    print(f"ADC: {value}  照度: {percent}%")
    time.sleep(0.5)Code language: JavaScript (javascript)

実行すると、ThonnyのシェルにADC値と照度の目安が表示されます。

センサーを手で遮ると値が下がり、光を当てると値が上がります。

実行結果

明るい状態では、ADC値が65535に近い大きな値になります。

暗い状態では、ADC値が小さくなります。

ここで大切なのは、数値そのものより変化の向きです。

コード②:しきい値でLEDをON/OFFする

次は、暗くなったらLEDを点灯するコードです。

ADC値がしきい値を下回ったときにLEDをONにします。

THRESHOLD = 20000が判定の境目です。

from machine import ADC, Pin
import time

light_sensor = ADC(Pin(26))
led = Pin(16, Pin.OUT)
THRESHOLD = 20000  # ※実測で調整

while True:
    value = light_sensor.read_u16()
    led.value(1 if value < THRESHOLD else 0)
    time.sleep(0.1)Code language: PHP (php)

実行すると、センサーを手で遮って暗くしたときにLEDが点灯します。

明るい場所で点きっぱなしになる場合は、コード①で確認した値を見ながらTHRESHOLDを調整してください。

次は、LEDの明るさをじわっと変えるPWMへ進みます。

PWM(パルス幅変調)とは

PWM(パルス幅変調)は、HIGHとLOWを高速で切り替えて、アナログ的な中間の明るさを作る技術です。

ONの時間が短ければ暗く見え、ONの時間が長ければ明るく見えます。

このON時間の割合をduty比(デューティー比)と呼びます。

duty_u16() の仕組み

MicroPythonでは、PWM出力のduty比をduty_u16()で指定できます。

値は0〜65535です。

0を指定すると消灯し、32767前後でおよそ半分の明るさになり、65535でほぼ全点灯になります。

led.freq(1000)は、PWMの周波数を1000Hzに設定するコードです。

read_u16() と duty_u16() の値域が同じ理由

read_u16()duty_u16()の値域は、どちらも0〜65535です。

read_u16()はADC値を16bitの整数として返します。

duty_u16()はPWMのduty比を16bitの整数として受け取ります。

明るいほどLEDを明るくしたいだけなら、センサー値をそのままduty_u16(value)へ渡せます。

ただし今回は「暗くなるほどLEDを明るくしたい」ので、そのまま代入すると動きが逆になります。

そこで使うのが65535 - valueです。

明るいとvalueは大きいので、65535 - valueは小さくなります。

暗いとvalueは小さいので、65535 - valueは大きくなります。

明るい → value 大 → 65535 - value 小 → LED 暗い
暗い   → value 小 → 65535 - value 大 → LED 明るい

この反転のおかげで、周囲が暗いほどLEDがじわっと明るくなります。

コード③:照度に連動してLEDをじわっと調光する

最後に、ADCとPWMを組み合わせた完成形です。

ここまでの説明どおりなら、led.duty_u16(65535 - value)の1行で完成するはずです。

ところが実際に動かすと、期待したほど明るさが変わりません。

理由は、ADC値が0〜65535の全域を使わないからです。

実測してみると、部屋の明かりの下で27000前後、手で完全に遮って1000前後でした。

つまり動いているのは全体の4割ほどで、残りは一度も通りません。

65535 - 27000 = 38535なので、一番明るい状態でもduty比58%、半分以上点灯したままになります。

そこで完成形では2つの処理を足します。

1つはキャリブレーション(実測値に合わせた範囲の調整)です。

実測範囲を0〜65535へ引き伸ばします。

もう1つはスムージング(値を少しずつ目標へ近づける処理)です。

明るさの変化をなめらかにします。

from machine import ADC, Pin, PWM
import time

# ── ピン設定 ──
LIGHT_SENSOR_PIN = 26   # Grove A0 コネクタ
LED_PIN          = 16   # Grove D16 コネクタ

# ── キャリブレーション値(実測で調整) ──
ADC_MIN = 1000     # 最も暗い時の実測ADC値(完全遮光)
ADC_MAX = 27000    # 最も明るい時の実測ADC値(※必ず自分の環境で測り直す)

# ── PWM設定 ──
PWM_FREQ = 1000    # 1kHz:人の目にちらつきが見えない周波数

# ── スムージング係数(0.0〜1.0) ──
SMOOTH_ALPHA = 0.1  # 小さいほどゆっくり、大きいほど素早く反応する

# ── サンプリング間隔 ──
INTERVAL_SEC = 0.05  # 50ms

light_sensor = ADC(Pin(LIGHT_SENSOR_PIN))
led = PWM(Pin(LED_PIN))
led.freq(PWM_FREQ)
led.duty_u16(0)  # 起動時は消灯

current_duty = 0.0  # スムージング用にfloatで保持


def remap(value, in_min, in_max, out_min, out_max):
    """value を in_min〜in_max から out_min〜out_max へ線形変換する。範囲外はクランプする。"""
    value = max(in_min, min(in_max, value))
    ratio = (value - in_min) / (in_max - in_min)
    return out_min + ratio * (out_max - out_min)


while True:
    adc_value = light_sensor.read_u16()

    # 実測範囲を0〜65535へ引き伸ばしてから反転する
    mapped = remap(adc_value, ADC_MIN, ADC_MAX, 0, 65535)
    target_duty = 65535 - mapped

    # 目標値へ少しずつ近づける(じわっと変化させる)
    current_duty += SMOOTH_ALPHA * (target_duty - current_duty)

    led.duty_u16(int(current_duty))  # duty_u16()は整数のみ受け付ける

    percent = round(adc_value / 65535 * 100, 1)
    print(f"ADC: {adc_value:5d}  照度: {percent:5.1f}%  duty: {int(current_duty):5d}")
    time.sleep(INTERVAL_SEC)Code language: Python (python)

実行すると、手でセンサーを遮ったときにLEDがじわっと明るくなります。

手を離すとADC値が大きくなり、duty比が小さくなるのでLEDは暗くなります。

ON/OFFだけだったコード②と違い、コード③では明るさが段階的に変化します。

ADC_MINとADC_MAXの決め方(キャリブレーションが必要な理由)

ここに注意

ADC_MIN = 1000ADC_MAX = 27000は、筆者の部屋で測った値です。

そのままコピーしても、あなたの環境では同じように動きません。

この2つだけは必ず測り直して入れ替えてください。

ADC_MINADC_MAXは、環境によって変わる値です。

窓際か部屋の奥か、昼か夜か、デスクライトを点けているかどうかで、ADC値は簡単に1万以上動きます。

記事の値は「筆者の環境ではこうだった」という一例にすぎません。

測り方は簡単です。

コード①をもう一度実行して、2つの状態のADC値を読み取ります。

  • センサーを指で完全に覆った状態の値 → ADC_MIN
  • いつも作業している明るさの値 → ADC_MAX

この2つを入れると、その部屋の明暗の幅がLEDの明暗の幅いっぱいに割り当てられます。

remap()が範囲外の値をクランプ(範囲の端に切り詰める処理)するので、想定より明るくても暗くても壊れません。

値がズレていると、次のような見え方になります。

値がズレているときのLEDの見え方

  • ADC_MAXが実際より大きい → 明るくしてもLEDが完全に消えません
  • ADC_MAXが実際より小さい → 少し暗くしただけでLEDが最大まで点いて、そこから変化しません
  • ADC_MINが実際より大きい → 完全に遮ってもLEDが最大まで届きません

どれも壊れているわけではありません。

上の症状が出たら、その値を実測へ近づけてください。

ここは「実測で決める」以外に正解がない部分です。

1回で決まらなくても問題ありません。

動かしながら数値を変えて、変化の幅がちょうどよく見えるところを探してください。

remap()でADCの実測範囲を0〜65535へ広げる

remap()は、ある範囲の値を別の範囲へ置き換える関数です。

今回は「1000〜27000」を「0〜65535」へ引き伸ばしています。

ADC 1000(真っ暗) → mapped 0     → 反転して 65535 → LED 全点灯
ADC 14000(中間)  → mapped 32768 → 反転して 32768 → LED 半分
ADC 27000(明るい)→ mapped 65535 → 反転して 0     → LED 消灯

この対応表の左端と右端が、さきほど測ったADC_MINADC_MAXです。

値を入れ替えると、この表ごと自分の部屋に合わせて動きます。

引き伸ばす前は38535〜65535の狭い範囲しか使えませんでした。

引き伸ばした後は0〜65535の全域が使えます。

同じセンサーでも、明暗の差がはっきり見えるようになります。

スムージングでPWM調光をじわっとさせる

センサーの値は、手の影や蛍光灯のちらつきで細かく揺れます。

読んだ値をそのままLEDへ渡すと、明るさがカタカタと震えて見えます。

SMOOTH_ALPHAは、その揺れをならすための係数です。

目標値へ一気に飛ばさず、差の10%ずつ近づけます。

current_duty += SMOOTH_ALPHA * (target_duty - current_duty)

0.1なら、目標へ届くまでに何回かループを回ります。

その遅れが「じわっと」した変化に見えます。

反応が鈍く感じるときは0.2へ、もっとゆっくり見せたいときは0.05へ調整してください。

よくあるエラーと解決策

LEDが逆に反応

LEDが逆に反応します。(明るいと点灯・暗いと消灯します。)どうしてですか?

センサーの値の向きが想定と逆になっている可能性があります。

コード①でシリアルモニタの値を確認し、手で遮ったときに値が下がるか上がるかを確かめてください。

下がる場合は65535 - valueの反転は不要なので、duty_u16(value)に書き換えてください。

シリアルモニタに表示されない

シリアルモニタに何も表示されません。何をすれば良いですか?

Thonnyで「実行」ボタンを押した後、シェルタブを選択してください。

また、time.sleep(0.5)をtime.sleep(1)に伸ばすと出力が確認しやすくなります。

LEDがちらつく

LEDがちらつく、不安定な点灯をする。

led.freq(PWM_FREQ)の周波数設定が抜けていないか確認してください。

コード③のINTERVAL_SECが長いと応答が遅くなるので、0.02〜0.05の範囲で調整してください。

それでも明るさが小刻みに震えるときは、SMOOTH_ALPHA0.05まで下げると落ち着きます。

第4回Raspberry Pi Pico 2Wで照度センサーLED調光のまとめ

今回は、Pico 2WとGrove光センサーを使って、照度センサーのADC読み取りとPWM調光を実装しました。

  • アナログ信号は連続した値で、光のような自然界の変化に向いています。
  • ADCはアナログ電圧をデジタルの数値へ変換する機能です。
  • read_u16()duty_u16()は、どちらも0〜65535の値域で扱えます。
  • 暗いほどLEDを明るくするには、65535 - valueで反転します。
  • ADC値は全域を使わないため、remap()で実測範囲を0〜65535へ引き伸ばします。
  • スムージングを入れると、明るさの変化がじわっとなめらかになります。

特に大事なのは、値の向きです。

今回の実測では、明るいほどread_u16()の値が大きく、暗いほど小さくなりました。

そのため、暗いときにLEDを明るくするには反転が必要です。

次回予告

次回は温度湿度センサーに挑戦予定です。

シリーズ一覧

Raspberry Pi Pico 2Wで照度センサーLED調光【第4回IoT電子工作】

2026/8/30  

こんな方におすすめです Pico 2Wで照度センサーを初めて使う方 ADCとPWMのつながりを手を動かして理解したい方 暗くなるとLEDが自動で明るくなる仕組みを作りたい方 「Lチカはできた。次は何を ...

Raspberry Pi Pico 2Wでボタン入力・LED制御【第3回IoT電子工作】

2026/8/30  

第3回 IoT電子工作入門講座 RaspberryPi Pico 2Wでボタン入力・LED制御動画はこちら https://youtu.be/gZcQWe1X1FU?si=qtHp0hmu1m_js9 ...

Raspberry Pi Pico 2Wの環境構築とLチカ【第2回IoT電子工作】

2026/8/30  

前回の第1回記事では、Raspberry Pi Pico 2Wの基本的な特徴と必要な機材について解説しました。 https://thunderblog.org/electronic_work/rasp ...

Raspberry Pi Pico 2Wを始める準備【第1回IoT電子工作】

そんなあなたに。 私は製造業でエンジニアとして18年以上働いており、実際の現場でIoTシステムの導入やデータ分析を担当してきました。 その経験を活かして、初心者の方でも確実にステップアップできるよう、 ...

動画は準備中です。公開したらこの記事からもリンクします。

サンプルコードはこちらです。

GitHubのサンプルコード置き場







  • この記事を書いた人
SNSアイコン

サンダー

電気機器メーカーに就職し、ハードエンジニアとして勤務しています。 ブログでは電気、プログラミング、データサイエンスについて書いています。 電気×プログラミング×データサイエンスの3本柱で日本の製造業を盛り上げたいです!

-IoT電子工作入門, 電子工作