ESP32+MicroPythonでPWMのdutyが反映されずにハマる


ESP32 + MicroPythonで、PWMオブジェクトを生成した直後にdutyをセットすると、なぜか反映されずにdutyが100% (常時HIGH) になってしまうという問題に遭遇し、ちょっと困った。再現性は不明。毎回ではなく何らかの条件が揃ったときに発生し、同じコードでも発生したりしなかったりする。原因はよくわからないが回避策がわかったので共有する。

なお、今回したMicroPythonのバージョンは1.19.1である。

ESP32+MicroPythonでPWM

まずは基本の話。MicroPythonでPWMを使用するには、machine.PWMクラスを使う。ESP32では、出力に使えるピンならどれでもPWMができる。(GPIO34〜GPIO39は入力専用なので使えないよ! 気をつけてね!) 詳しくはここを読むと良い。

Quick reference for the ESP32 — MicroPython 1.19.1 documentation > class PWM – pulse width modulation

machine.Pinとmachine.PWMを使う。

from machine import Pin, PWM

好きなピンを選んでこんなふうに初期化したら

pin = Pin(25, Pin.OUT)
pin.value(0)
pwm = PWM(pin)

dutyを0 (常時OFF)〜1023 (常時ON)の間の整数でセットする。

pwm.duty(512) # 50%

周波数はこんなふうにセットできる。

pwm12.freq(1000)

コンストラクタでdutyとfreqをセットすることもできる。

PWM(12, freq=500, duty=512)

なかなかシンプルな話のように見えるのだが……。

dutyが反映されず困る

PWMオブジェクトを初期化した直後にdutyをセットすると、何らかの条件下では反映されないという現象に遭遇し、大変つらい思いをした。

最初duty=0%でスタートし、のちのち必要に応じて出力を変えていくつもりだったのだが……

pin = Pin(25, Pin.OUT)
pwm = PWM(pin)
pwm.duty(0)

なぜかpwm.duty(0)が実行された直後、実際の出力ではdutyが100%になる。引数が0でなくても発生する。発生しないこともある。どういうケースで発生するのかがいまいちわからないし、同一のコードを実行しても発生したりしなかったりする。特に、os.reset()を呼び出して再起動した直後だとほぼ確実にこの現象が発生する。なんだこりゃ。

回避策のようなもの

色々試したら、恐らくこれで大丈夫といえる回避策にたどり着いた。

「PWMオブジェクト生成時に確実にdutyをセットしたかったら、コンストラクタを呼んだ直後にduty()を呼ぶのはやめる。コンストラクタにdutyを渡す」

machine.PWMのコンストラクタは、オプション引数としてfreqとdutyを渡すことが出来る。ここで渡したdutyの値は決して無視されることはない。さっきのコードをこんなふうに書き換える。

pin = Pin(25, Pin.OUT)
pwm = PWM(pin, duty=0)

これで、私が試した限り、さきほどの問題は発生しなくなった。