Playing simple music by “pwmio” of CircuitPython.
前回は、CircuitPython をインストールした SAMD21 マイコンボードに、Mu エディタで接続することで、プログラムを編集したり、REPL で Python とお話ししたりしてみました。
今回は CircuptPython に備わる pwmio というコアモジュール(ライブラリ)を使って PWM(パルス幅変調)出力を試してみましょう。また、PWM と圧電スピーカ(ピエゾブザー)を使って、簡単な音楽(?)を演奏してみましょう。
pwmio コアモジュール
CircuitPython には、pwmio という名前の(ソフトウェア)モジュールがあります。これは、PWM(パルス幅変調)の出力を得るためのライブラリです。PWM は、GPIO 出力ピンをある一定の間隔でオン・オフすれば良いので、C 言語や Arduino Sketch ではそのようなライブラリがなくても、なんとか PWM 出力をすることはできます。しかし、CircuitPython はインタプリタ言語であり、そのままでは高速な信号オン・オフがうまくできませんし、あまり正確な時間制御をするのも得意ではありません。そのため、このようなライブラリが重要になってきます。
PWM(パルス幅変調)
ところで、PWM とはなんでしょう? 以下にイメージ図を示します。横軸は時間で、縦軸は出力(ここでは電圧)です。左から右に向かって時間が進むにつれて、電圧が low に(低く)なったり、high に(高く)なったりします。(以下、オン・オフすると呼びましょう。)
図には上下、2つの波形が示されています。いずれの波形も、オン・オフの「周期(period)」は同じです。例えば、ここでは周期が 1ミリ秒(1/1000秒)だとしましょう。周期の逆数は「周波数(frequency)」です。周期が 1ミリ秒のとき、逆数は 1000 となり、単位は Hz(ヘルツ)で表現します。つまり、周波数が 1000(あるいは 1キロ)Hz ということになります。
一方で、オンになっている部分を灰色で表現していますが、その灰色になっている割合をデューティ比(duty ratio、あるいは duty cycle)と呼びます。もし、上の波形のように、周期の 17% の時間がオンになるのであれば、デューティ比 17% と呼びます。下の波形はデューティ比が 50% です。
PWM の応用
さて、このような PWM は何の役に立つのでしょうか?
皆さんは、パソコンやスマートフォンに付いている LED が、眠たげ(?)にフワフワとゆっくりした間隔で明滅しているのを見たことはありませんか? あるいは LED 照明器具などで、明るさを調整する機能を見たことはありませんか?
このように LED の明るさを調整する際、LED にかける電圧を変えていると思っていませんでしたでしょうか? 例えば、2V(ボルト)をかけると明るく、1V だったら暗く、という具合です。小学校の理科の時間に、豆電球に 1.5V の電池を繋ぐと暗く、3V だと明るく、ということを実験したことがある方もあるでしょう。
実は、LED の明るさ制御には、通常(人の目に見せるための用途では)、可変電圧制御や可変電流制御は用いません。それには、次のように、いくつかの理由があります。
- LED の明るさを変えるには可変電流制御で可能だが、一般のマイコンで可変電流制御を実現するには、複雑な電子回路を必要とする。
- 簡単な可変電圧・電圧制御では、エネルギーが熱などに変わって無駄になってしまうことがある。
そのため、現在、LED の明るさを制御するには、可変電圧や可変電流の制御はしません。皆さんが、あっと驚くような方法を使っています。それは、
LED を高速に点滅させて、点灯している時間と消灯している時間の割合を変化させる
というものです。私が昔、そのやり方を知ったときには、本当に驚きました。このような制御をパルス幅制御、あるいはパルス幅変調(PWM)制御などと呼ぶのです。なぜそのようなことができるかというと、人間の目には、あまりに高速な点滅は視認することができず(つまり、点滅に気付かず)、明るさが変わっているようにしか見えない、という性質があるからです。
PWM 制御には、次のような利点があります。
- 電圧のオン・オフは、ディジタル回路が非常に得意とするものである。特に、タイマ回路が同時に使えれば、PWM 制御は簡単に実現できる。
- 現代の安いマイコン(マイクロコンピュータ)はディジタル回路であり、もちろんタイマ回路も持っており、さらに、動作をプログラムで自由に変更することができる。
- オン・オフ制御では、エネルギーが熱などに変わって無駄になることが、ほとんどない。
CircuitPython で PWM を試してみよう
さて、理屈は分かりました。それでは実際に、CircuitPython で LED を PWM 制御してみましょう。今回も、前回と同様に SparkFun SAMD21 Mini ボードを使います。他のマイコンボードでも、CircuitPython が動けば、まず問題なく動作するはずです。
CircuitPython のコードを以下に示します。今回は、ボードに搭載の LED を PWM 制御します。ここでは、前回に説明した REPL で対話的に CircuitPython を使う(CircuitPython とおしゃべりをする)のが使いやすいでしょう。
import board
import pwmio
led = pwmio.PWMOut(board.LED, frequency=1000, duty_cycle=0)
この状態では LED は消灯しているはずです。
プログラムを簡単に説明しましょう。最初の 2行は、4行目で必要になるライブラリ(コアモジュール)をインポート(取り込み)しています。4行目では、pwmio.PWMOut
という関数を使って、board.LED
のピンを PWM 出力モードに設定しています。周波数は 1000Hz(ヘルツ)、デューティ比は 0% です。いま、習ったのですぐに分かりますね。
なお、ここで一つだけ注意が必要です。pwmio モジュールでのデューティはパーセントではなく、ある整数値で指定します。0% に対応する値は 0 で良いのですが、100% に対応する値は 65535 という変な(?)値になります。実は、65535というのはコンピュータ屋さんにとって極めてなじみ深い数字で、これは 2の 16乗から 1を引いた値なのです。16進法を御存知の方には、
0xffff
と言えば分かりやすいかも知れません。つまり、デューティ比 50% は、その半分の 32768、25% は 16384、12.5% は 8192、といった具合の値になりますので、その値を
PWMOut
関数の引数で指定します。
さて、実際にデューティ比を変えてみましょう。REPL で、プロンプトの >>>
に続けて次のように(半角文字で)入力し、リターンキーを押しましょう。
led.duty_cycle = 65535
LED が明るく光りましたね? 次に、
led.duty_cycle = 16384
としてみましょう。やや暗くなりましたね? ここで、0〜65535 の間で好きな値を試して、LED の明るさを見てみましょう。
注意深い方はお気づきかも知れませんが、デューティ比の値を半分にしても、明るさが半分になったような感じがしませんね? 私は、1/4 か 1/8 程度にしたときに、元の値の半分くらいの明るさに感じます。(例えば、65535 の 1/4 →(約)16384 など)
これは人間の視覚(感覚)の面白いところで、人間は、明るさ(光)でも音量でも、エネルギーが半分になっても、半分になったようには感じません。もっとずっと小さなエネルギーで、半分くらいに感じるのが普通です。電気工学に基づくエネルギー(電力)の観点からは、デューティ比が半分になると、単位時間あたりに流れる電流が半分になり、電圧はそのままなので、電力が半分になるはずなのですけどね!
もっと注意深い方は、さらにお考えかと思います。
「そうか、LED 照明器具で明るさを半分くらいに調整すると、必要な電力(電気代)は半分以下になるんだ!」
その通りです。エコな考え方ですね。面白いですよね!
本当に点滅しているの?
上で実験した方は、「これって本当? 本当に点滅しているの?」とお疑いの方もあるでしょう。
それを確かめるにはどうしたら良いでしょう? はい、そうです。PWM の周波数をずっと下げれば良いのですね。
テレビや LED 信号機(交通信号機)の話で御存知の方もあるかと思いますが、人間の視覚には、数十 Hz より高い点滅を認識できないという性質があります。テレビは 30、ないしは 60Hz で画面を切り替えていますし、交通信号機は(東日本と西日本で違うそうですが)100〜120Hz で点滅していると言われています。
「じゃあ、周波数をもっと下げてみよう!」 そうですね、やってみましょう。
一度マイコンボードをリセットしても良いのですが、以下のようにすると、PWMOut
を再設定できます。なお、#
以降はコメントですので、入力しなくて結構です。(入力しても大丈夫です。)
led.deinit() # 一度 led を無効にする
led = pwmio.PWMOut(board.LED, frequency=20, duty_cycle=0)
これで、周波数が 20Hz になりました。さて、デューティ比 50% で点灯(点滅)させてみましょう。
led.duty_cycle = 32768
点滅が見えますね? duty_cycle
をいろいろ変えて実験してみてください!
音楽を演奏してみよう!
ここからは応用編です。PWM を使うと、圧電スピーカ(ピエゾブザー, 圧電素子などとも呼びます)で音を鳴らすことができるんですね。
そもそも PWM というのは、デューティ比を制御するのが本来の目的で、以下の使い方は、PWM の本質からは外れますけど、面白いので試してみてください。 😉
まず回路ですが、先ほどの LED と同じ端子に圧電スピーカを繋ぎます。
SparkFun SAMD21 Mini ボードでは、LED をトランジスタ(MOSFET)で駆動しているので、同じピン(D13)に並列に圧電スピーカを繋いでも大丈夫です。うまくいかない方は、違う GPIO ピンを使ってください。その際、プログラム中のピン名は
board.LED
ではなくて、board.D12
とか、board.D3
とかのように変更してください。
圧電スピーカには極性があります。マイコンの GPIO 端子側に、圧電スピーカのプラス端子(側)を繋ぎます。そして、できたら数十から数百オーム(Ω)程度の抵抗器(なくてもたぶん大丈夫)を直列に繋ぎ、抵抗器の反対側をグラウンド(GND)に繋ぎます。
以下に写真を示します。この写真では、圧電スピーカの下側がプラス端子です。抵抗器は、ここでは 43オームを使っています。(心配な方はもっと大きな抵抗値が良いかも知れませんが、音量は小さくなります。)
以下に Python のプログラムを示します。(スクロールすると全体が見られます。)
import board
import pwmio
import time
def freq(note):
"""
音符名に対応する周波数を返す
パラメタ
--------
note : str
音符名
返り値
------
freq : float
対応する周波数。不明な場合は 0.0。
"""
# 音階表
REL_KEYS = {
'do': +3,
're': +5,
'mi': +7,
'fa': +8,
'so': +10,
'la': +12,
'ti': +14,
'Do': +15,
}
# ラ(A440) の周波数
FREQ_LA = 440.0
if note in REL_KEYS:
rel_key = REL_KEYS[note]
return FREQ_LA * (2 ** (rel_key / 12))
else:
return 0.0
# 演奏したい楽譜
SCORE = ['do', 're', 'mi', 'fa', 'mi', 're', 'do']
# PWM の初期化
led = pwmio.PWMOut(board.LED,
frequency=int(freq('la')),
duty_cycle=32768,
variable_frequency=True)
# 楽譜を演奏する
for note in SCORE:
led.frequency = int(freq(note))
time.sleep(0.5)
variable_frequency
というのは、PWM の初期化後に周波数を変えられるようにするために必要な設定です。詳しくは、以下、参考と書いたリンク(下のほうのリンク)を御参考ください。
音が鳴らない場合は、ボードのリセットボタンを押してみましょう。あるいは、REPL で接続している場合には Ctrl-D を何度か送ってみてください。以下に、YouTube に投稿したビデオを示しておきます。
参考
以下に、参考ウェブサイトを挙げておきます。
- CircuitPython PWM | CircuitPython Essentials | Adafruit Learning System
- pwmio – Support for PWM based protocols — Adafruit CircuitPython 7.0.0-beta.0 documentation
今日はここまで!
宿題
- [初級編] フワフワと、1秒周期(あるいは 2秒周期)で LED を少しずつ明るくしたり暗くしたりしてみましょう。できるだけ、見ていて眠くなるように工夫しましょう。 🙂
- [中級編] Python に詳しい方は、上記プログラムを改良して、音符の長さ(4分音符なら 4、8分音符なら 8など)や、休符などを指定できるようにしてみましょう!