前回はスイッチを使ったLED制御を学びました。
今回はLEDの光る明るさを変える制御について学んでゆきます。

PWMとは
PWM(Pulse Width Modulation)とは、「電気を点けたり消したりする時間の割合を調整することで、出力の強さを変える技術」です。
矩形波(方形波)において周期Tに占めるHの時間tの割合(デューティ比)を変化させることで、疑似的なアナログ出力を実現することができます(実際のアナログ出力ではないので注意してください)。

PWMは、DCモータの回転速度制御やLEDの明るさの制御(調光)などに利用されています。また、人型ロボットの関節などに利用されるRCサーボモータの制御にも利用されています。

「analog」という名称であるが、電圧が変化している訳ではなく、あくまで疑似アナログ出力なので注意してください。
たとえば…
- 点灯:消灯 = 1:1(50%) → 明るさ中くらい
- 点灯:消灯 = 9:1(90%) → 明るさ強め
- 点灯:消灯 = 1:9(10%) → 明るさ弱め
これを**とても速い速度(数千回/秒)**で繰り返すことで、LEDは「チカチカ」せず、明るさが変わったように見えます。
例題1
LEDの明るさを指定して点灯
以下のようなプログラムになります。
from machine import Pin, PWM
import time
led = PWM(Pin(15)) # GPIO15をPWM出力に設定
led.freq(1000) # PWMの周波数を1000Hzに設定
led.duty_u16(32768) # デューティ比50%(最大65535の半分)PWM(Pin(15)):GPIO15ピンにPWMを出力
freq(1000):1秒間に1000回の速さで点滅(チラつき防止)
duty_u16(x):PWMの強さを0〜65535の値で指定
例:65535 = 最大出力(明るさ100%)、0 = OFF
例題2
明るさをだんだん変える
from machine import Pin, PWM
import time
led = PWM(Pin(15))
led.freq(1000)
while True:
# 徐々に明るく
for i in range(0, 65536, 1024):
led.duty_u16(i)
time.sleep(0.01)
# 徐々に暗く
for i in range(65535, -1, -1024):
led.duty_u16(i)
time.sleep(0.01)range(0, 65536, 1024):明るさを0→最大まで増やす
range(65535, -1, -1024):明るさを最大→0まで減らす
time.sleep(0.01):変化の速度(速くするとアニメーションも速くなる)
RCサーボモータ
RCサーボモータはロボット用サーボとも呼ばれ、その名前のとおりヒューマノイドロボットの間接部などで利用されます。もともとは、ラジコンカーのステリアリングをコントロールするために使われていたものであり,Radio Controlの略となっています。

PWM信号により回転軸を任意の角度に回転できるのが特徴であり,一般に10~20msの周期で、パルス幅を1~2ms程度の範囲で変化させることで回転軸の位置を制御します。
PWM信号以外で制御するタイプのサーボモータ、シリアルサーボもあるが高額です。
RCサーボモータの中には駆動源となる小型DCモータ,減速装置,ポテンションメータ,モータ制御用基板が内蔵されており,PWMで制御するタイプのRCサーボモータからは、3本の線が伸びています。
配線の割り当ては下の図に示すように,黒(茶):GND,赤:電源,白(橙):信号となっており,サーボモータには、駆動のために大きな電流が必要なので、制御用マイコンの電源とは別系統で供給する必要があります。

モータ本体に駆動回路が内蔵されているため,PWM信号線はFET等で増幅する必要はなく,マイコン直結で問題ないため,駆動回路は必要ありません。
SG90サーボ → Raspberry Pi Pico W
─────────────────────────────────────────
茶色(GND) → GND(38番ピンなど)
赤色(VCC 5V) → VBUS(40番ピン)または外部5V電源
橙色(信号線) → GP15(20番ピン)選定のポイントは保持トルクと回転速度であり,保持トルクの大きいモータは回転速度が遅いなど,トレードオフ関係にあります。
Raspberry Pi Picoでサーボモータを制御する際には以下のように専用のライブラリを使用すると簡単です。
PWMを出力可能なポートのみ使用することが出来ます。
from machine import Pin, PWM
from time import sleep
# GP15ピンをサーボ用に設定(周波数は50Hz固定)
servo = PWM(Pin(15))
servo.freq(50)
# サーボを動かす関数
def servo_angle(angle):
duty = int(1638 + angle * 34.6) # 角度→duty値に変換
servo.duty_u16(duty)
# 0度 → 90度 → 180度を繰り返す
while True:
servo_angle(0)
sleep(1)
servo_angle(90)
sleep(1)
servo_angle(180)
sleep(1)5行目:PWM(Pin(15)) … GP15ピンをサーボ用に使う宣言です
6行目:servo.freq(50) … サーボは50Hzの信号で動くという「お約束」です
13行目:servo_angle(0) … この数字を変えると角度が変わります(0〜180の間で)
課題6
ボタンを押すごとに明るさを段階的に切り替わる回路を作成せよ。
ボタン(GPIO14)を押すたびに、LEDの明るさが
→「OFF → 弱 → 中 → 強 → OFF…」と繰り返し切り替わる
押しっぱなしでは反応せず、「押された瞬間」だけ反応

解答例はこちら
from machine import Pin, PWM
import time
led = PWM(Pin(15))
led.freq(1000)
button = Pin(14, Pin.IN, Pin.PULL_DOWN)
# 明るさの段階をリストで管理
levels = [0, 20000, 40000, 65535]
level_index = 0
prev = 0 # 前回のボタン状態
while True:
current = button.value()
# 押された瞬間だけ反応(エッジ検出)
if prev == 0 and current == 1:
level_index = (level_index + 1) % len(levels) # インデックスを循環
led.duty_u16(levels[level_index]) # 明るさを設定
prev = current
time.sleep(0.01)課題7
2つのボタンを使ってLEDの明るさを手動調整する回路を作成せよ。
ボタンA(GPIO14)を押すたびに明るさを「+5000」
ボタンB(GPIO13)を押すたびに明るさを「−5000」
明るさは 0〜65535の範囲内に収める
解答例はこちら
from machine import Pin, PWM
import time
led = PWM(Pin(15))
led.freq(1000)
btn_up = Pin(14, Pin.IN, Pin.PULL_DOWN)
btn_down = Pin(13, Pin.IN, Pin.PULL_DOWN)
duty = 0
led.duty_u16(duty)
prev_up = 0
prev_down = 0
while True:
current_up = btn_up.value()
current_down = btn_down.value()
# 明るさを上げる
if prev_up == 0 and current_up == 1:
duty += 5000
if duty > 65535:
duty = 65535
led.duty_u16(duty)
# 明るさを下げる
if prev_down == 0 and current_down == 1:
duty -= 5000
if duty < 0:
duty = 0
led.duty_u16(duty)
prev_up = current_up
prev_down = current_down
time.sleep(0.01)課題8
プッシュスイッチ2つによってサーボモータの角度を変化させ、角度指令に対する出力の変化(PWM)をオシロスコープを使って観察し、報告してください。
オシロスコープは下記の記事を参考にしてください。
解答例はこちら
# ============================================
# 2つのスイッチでサーボの角度を切り替えるプログラム
# スイッチ1を押す → サーボが0度に動く
# スイッチ2を押す → サーボが180度に動く
# ============================================
# 必要な機能を読み込みます
# Pin … GPIOピンを使うため
# PWM … サーボ制御用の信号を作るため
from machine import Pin, PWM
from time import sleep
# --------------------------------------------
# サーボモータの準備
# --------------------------------------------
# GP15ピンをPWM出力として設定します
servo = PWM(Pin(15))
# サーボは50Hzの信号で動くので、周波数を50Hzに設定します
# (これはSG90サーボの「お約束」です)
servo.freq(50)
# --------------------------------------------
# スイッチの準備
# --------------------------------------------
# GP14とGP13を「入力ピン」として設定します
# Pin.PULL_UP は「内部プルアップ抵抗」を有効にする設定です
# → 何もしないときは「1(HIGH)」
# → スイッチを押すと「0(LOW)」になります
sw1 = Pin(14, Pin.IN, Pin.PULL_UP)
sw2 = Pin(13, Pin.IN, Pin.PULL_UP)
# --------------------------------------------
# サーボを指定角度に動かす関数
# --------------------------------------------
def servo_angle(angle):
# 角度(0〜180度)をduty値に変換します
# 1638 = 0度のときのduty値
# 34.6 = 1度あたりのduty値の増加量
duty = int(1638 + angle * 34.6)
# サーボに信号を送ります
servo.duty_u16(duty)
# --------------------------------------------
# メイン処理(ずっと繰り返す)
# --------------------------------------------
while True:
# スイッチ1が押されたか確認します
# value()が 0 のとき = 押されている状態
if sw1.value() == 0:
print("スイッチ1が押されました → 0度")
servo_angle(0) # サーボを0度に動かす
# スイッチ2が押されたか確認します
if sw2.value() == 0:
print("スイッチ2が押されました → 180度")
servo_angle(180) # サーボを180度に動かす
# 少し待機(チャタリング対策と、CPUの負担軽減のため)
sleep(0.05)次回はこちら


