前編はこちら。
Raspberry Piは、ハードウェアPWMを2系統持っています。クロック源は共有されていますので、同期して動かすことができます。オーディオ出力の左チャネルと右チャネルの出力はこの2系統のPWMが使われています。
前編では、PWMに供給するクロックについて説明しました。後編はPWMの設定を解説します。
PWMの設定
Raspberry PiのPWMはいろいろと細かい設定があるのですが、サーボの制御等で使用するのに必要な項目は以下の3つです。
range:PWMの1周期を構成するクロック個数
data:PWMで「1」を出力するクロック個数
モード:Mark/SpaceまたはPWM
上の図はクロックとrangeとdataの関係を示したものです。
クロックパルスが1つ来るたびにカウントが1ずつ上がっていきます。
カウントがrangeに達するとリセットされて0に戻ります。
従って、rangeはPWM出力波形の1周期の長さを表します。
また同時に、PWMの解像度(段階の数)をも表しています。
PWM出力は、カウントがdata以下ならば1、dataより大きければ0です。
ただし、これはMark/Spaceモードの場合で、PWMモードの場合は1と0をできるだけ高速に切り替えるように動作します。
Mark/SpaceモードでもPWMモードでも、1周期内で出力が1になる時間の総量は同じなのですが、固まって出現するか分散して出現するかが異なります。
デューティ比を連続的に変化させたときのPWM出力信号の違いをオシロで観測した動画を以下に載せておきます。
GPIOの設定
PWMをGPIOピンに出力するためには、設定が別途必要です。
オーディオジャックが付いているRaspberry Piでは、PWM出力はオーディオ出力となっており、GPIO40,45が使われています。
Raspberry Pi Zeroではこのピンは拡張ポートに出力されていませんが、代わりにGPIO18,19またはGPIO12,13が使えます。
これらのピンにRCからなるローパスフィルタを接続すれば、オーディオ出力として使うこともできます。以下のページに解説があります。
Pi Zero PWM Audio | Adding Basic Audio Ouput to Raspberry Pi Zero | Adafruit Learning System
PWMをGPIOピンに出力するには、ピンのモードを切り替える(ピンにAlternate Functionを設定する)必要があります。
このあたりは以前以下の記事で書きました。
Raspberry Pi Zero WでベアメタルLチカ – 楽しくやろう。
マニュアルによると、PWM出力できるGPIOピンと設定するAlternate Functionは以下のようになっています。
PWMは2系統ありますが、ピンによって使用できるPWMは決まっています。
MicroPythonで、PWMに設定したいピン番号を与えると設定を行ってPWMのIDを返す関数は以下のようになります。
設定できた場合はPWMのIDとして0か1、設定できなかった場合は-1を返します。
PWMクラスの実装
マニュアルによると、PWM関連のレジスタは以下のようになっています。
PWMは2系統ありますが、range(RNG1, RNG2)とdata(DAT1, DAT2)以外のレジスタは共用になっています。
FIF1も、1と付いてはいますが共用です。このレジスタに書き込んだデータはFIFOバッファに追加されますが、バッファも共用です。2チャンネルで使用する場合は、各チャンネルは偶数番目と奇数番目のデータを受け取ります。
以前書いたように、ベースアドレスはマニュアルには掲載されていませんが、エラッタによると0x7E20C000です。
ベアメタルでは0x2020C000となります。
また、0xC、0x1Cからの4バイトは使われていないようです。
PWMチャンネルごとの初期化設定には、既に紹介したMark/Spaceの他、
・アクティブ/インアクティブ
・1と0の出力を反転するか(polarity)
・無信号時は0を出力するか1を出力するか(silence bit)
・FIFOの利用有無
・FIFOを利用する場合、データが空なら最後のデータを再度使用するか否か
・PWMとして利用するかシリアライザとして使用するか
といったフラグがあります。
シリアライザとは、データをクロックに合わせて1bitずつ出力する機能で、出力ビットパターンを完全に制御することができます。
silence bitは、PWM使用時は0だと通常、1だとPWMの出力が常に1になるようです。(実際の出力はpolarityの値に依存します。)
PWM出力の指定は前述の通りrangeとdataで行うのが基本です。
また、周波数については前編で紹介したように、クロック源とプリスケーラの設定で制御します。
しかし、MicroPythonの他のプラットフォームでは、周波数やデューティ比を直接指定するAPIがあります。
Quick reference for the pyboard — MicroPython 1.9.4 documentation
7. Pulse Width Modulation — MicroPython 1.9.4 documentation
Quick reference for the WiPy — MicroPython 1.9.4 documentation
そこで、簡易的なAPIとして
・クロック源=OSC
・range=960
を固定し、周波数をプリスケーラの設定で、デューティ比をdataの設定で行うメソッドを用意することにしました。
・freq(f):周波数をf Hzに指定(最大10KHz)
・duty(d):デューティ比を0..960の範囲で指定(480で50%)
rangeが960だと、サーボの制御には若干精度が悪い(range=2000程度が望ましい)ですが、その代わり周波数は最大10KHzまでの矩形波を出力できます。
実装を載せておきます。
使い方はこんな感じです。
動作させると、本記事の冒頭のGIFアニメのような出力が得られます。(このアニメはQGiferで作成しました。)
コメント
ラズパイ(pi3model B)でサーボモータをハードウエアPWMで
動作制御にトライしています。サーボモーターはtower pro のSG92Rです。
下記のプログラムで一動作は回転しますが、連続動作に四苦八苦しています。
例えば90度→180度→0度の動作、さらに無限動作のプログラミングに
苦労しています。
while True:あるいはfor range int
など挑戦していますがなかなか思う通り動きません。
何かヒントお願いいたします。
import wiringpi as GPIO
servo_pin = 18
set_degree =180
print(set_degree)
GPIO.wiringPiSetupGpio()
GPIO.pinMode(servo_pin, 2)
GPIO.pwmSetMode(0)
GPIO.pwmSetRange(1024)
GPIO.pwmSetClock(375)
move_deg = int((9.5*set_degree/180 + 2.5)*(1024/100))
GPIO.pwmWrite(servo_pin, move_deg)
こんにちは、ご質問ありがとうございます。
連続動作ということですが、プログラムはサーボが所定の角度に達するまで待ってはくれませんので、PWMに値を設定した後、
time.sleep(待ち時間)
でサーボが回りきるまで待ってはどうでしょうか。待ち時間の単位は秒です。
また、先頭行に
import time
が必要です。