今回の講義の目的は、マイコンを使って電圧を制御することです。電圧を上げたり下げたりできると、LEDの光る強さを変えたり、モータの回転速度を変えたりすることができます。
目次
- 6.1 PWM
- 6.2 モータの基礎
- 6.3 外部電源を使う時の注意
- 6.4 DCモータを回す
- 6.5 ギア
- 6.6 サーボモータを回す
- 6.7 実習
6.1 PWM
マイコンの世界はデジタルなので、プログラムで電圧を制御するのは、ちょっと難しいことです。そこで、PWMという手法が登場します。
これはデジタル出力を使って擬似的にアナログ出力を実現する方法だと思ってください。
仕組み
マイコンのデジタルピンからの出力は、HIGH(例えば3V)かLOW(例えば0V)のどちらかだけです。しかし、0Vから3Vまでの間で電圧を自由に変えたいという場合があります。例えば、LEDの明るさを調整したい場合などです。こういう時には、PWM(Pulse Width Modulation)という方法を用います。
PWMの仕組みを一言で説明すると、HIGHとLOWを素早く切り替えることによって、HIGHとLOWの間の電圧を擬似的に作り出す方法です。以下の図をみてください。

このようにHIGHとLOWを規則的に繰り返す信号を、パルス波と言います。上記の図で示しているパルス波は、ある一定時間で同じパターンを繰り返していますね。このような繰り返しの間隔を周期と呼び、1秒間に何周期あるかを周波数と呼びます。周期の始まりは、LOWからHIGHに変わる瞬間で、周期の終わりは、次にLOWからHIGHに変わる瞬間です。周波数1kHzと言ったら、1秒間に1000回の周期が起こるような波形を意味します。
図で示したパルス波は、1周期におけるHIGH(3V)とLOW(0V)の間隔が、ちょうど1/2ずつになっています。周波数が十分に高い場合,このデジタルピンにかかる電圧は、3V / 2 = 1.5Vとなります。これがPWMです。実際に電圧を変えるのではなくて、擬似的に電圧を変えているのです。
PWMを使った場合にデジタルピンにかかる電圧は,デューティ比(duty ratio) によって決まります。デューティ比とは,1周期の間にHIGHになっている割合のことです。HIGHが3Vの場合、デューティ比50%ならば1.5V,10%ならば0.3Vとなります。

PWMを使用する場合、どのくらいの周波数が必要かは難しい問題なので、あまり深入りしないことにしますが,1Hzではだめなことは明らかです。1Hzということは、HIGHとLOWが同じ時間であるとすると1周期内で0.5秒ずつです。LEDで実験すると単に点滅するだけです。
PWMの周波数は、デジタルピンの用途によって変わるとされています。例えばLEDを使う際に必要な周波数,モータを使う際に必要な周波数という具合です。モータの場合には10kHzから20kH位が適当でしょう。LEDだと、100Hzくらいでも大丈夫だと思います。
PWMを使って電圧を制御するには、パルス波を作り出す必要があります。パルス波は、タイマを使って作ります。非常に微小な時間をタイマによって作りだし,デジタル出力をHIGHにしたりLOWにしたりします。
例えば、1kHzの周波数でPWM 制御をしたいとしましょう。1秒間に1,000周期ですから,1周期は1ms(ミリ秒)です。1周期を10段階に分けると、1段階100µs(マイクロ秒)になります。この100µsをタイマを使って作り1単位とすれば、10%刻みでデューティ比が設定できるようになります。
試してみよう
GPIO Zeroには、PWMのためのライブラリが用意されています。このライブラリを使えば、プログラムする際にタイマを設定するなどの面倒なことは必要なくなります。
PWMLEDとPWMOutputDeviceという2つのクラスがありますが、ここではPWMLEDの方を使います。この2つのクラスの詳細は、GPIO Zeroのリファランスの”API – Output Devices”を見てください。
https://gpiozero.readthedocs.io/en/stable/api_output.html
LEDを光らせる回路はソース電流を使って作ります。GPIOのピンは特殊用途のピンでなければどこでも良いです。ここではGPIO 17を使います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
from gpiozero import PWMLED from time import sleep led_pin = 17 led_pwm = PWMLED(led_pin) def main_loop(): while True: led_pwm.value = 0.8 # デューティ比80% sleep(0.5) led_pwm.value = 0.1 # デューティ比10% sleep(0.5) if __name__ == '__main__': main_loop() |
このプログラムは、異なるデューティ比でLEDを交互に光らせるものです。デューティ比80%の時には明るく光り、デューティ比10%の時には暗く光るはずです。
PWMLEDはデフォルトでPWMの周波数を100Hzにしています。これを変えるには以下のようにします。
1 |
led_pwm = PWMLED(led_pin, frequency=500) |
先ほどの例は、ソース電流を使ってLEDを光らせましたが、シンク電流でも同じことをすることができます。以下のようにPWMLEDの引数にactive_high=False
を指定するだけです。
1 |
led_pwm = PWMLED(led_pin, active_high=False) |
もう少し複雑な例を上げておきます。これは、デューティ比を徐々に増やしながらLEDを光らせています。LEDの明るさが滑らかに変わることが観測されるはずです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
from gpiozero import PWMLED from time import sleep led_pin = 17 led_pwm = PWMLED(led_pin) def main_loop(): duty_ratio = 0.0 # デューティー比の初期値 while True: led_pwm.value = duty_ratio duty_ratio += 0.05 # 5%ずつ増やしていく sleep(0.1) if duty_ratio >= 1.0: duty_ratio = 0 # 100%以上になったら0に戻す if __name__ == '__main__': main_loop() |
デューティ比を変えてLEDを光らせる時には、デューティ比を切り替える間に少し間隔をおく必要があるということです。5msくらいあれば大丈夫だと思います。上記の例ではsleep(0.1)
で100msの間隔を空けています。これを入れないと、LEDは高速で点滅しているような感じになってしまいます。
6.2 モータの基礎
モータにはいくつか種類があります。
DCモータ
家庭にある電源コンセントを、AC100Vと呼ぶことは知っていると思います。このACとはAlternating Currentの略で、電流の向きが変わることを意味しています。日本語では、交流あるいは交流電流と言います。
一方、乾電池やUSB電源などは、直流あるいは直流電流と呼ばれます。DCというのは、Direct Currentの略で、直流電流を意味します。直流の場合、電流の向きは常に一定です。
DCモータは、直流電流で動くモータです。模型でよく使用するマブチモータは、皆さんも一度は見たことがあると思います。

乾電池でDCモータを動かすのは簡単です。

しかしモータを、マイコンに繋いで制御するとなると、ちょっと難しいです。まず、DCモータに限らずモータというものは、少なくとも数百mA、多ければ数Aの電流を必要とします。Raspberry Piのピンから供給できる電流は最大16mAくらいなので、マイコンのデジタル出力を使い直接DCモータを駆動することはできません。使用するモータによって、別途、適した電源を用意する必要があります。また、正転、逆転、停止、回転速度などの制御を行う必要もあります。
そこで、それらのことを簡単に解決するための、モータドライバというデバイスが存在します。ここでは、TB6612というモータドライバICを使います。このICは表面実装用のICなので、使い易いようにボードに実装されたキットを使います。このキットは秋月電子で販売されています。

TB6612は、最大1.2AまでのDCモータを2つまで制御できます。皆さんに馴染みの深いマブチモータFA-130は、1.5Vから3.0Vの範囲で通常の回し方だと500mAの電流が流れますので、このモータドライバで制御することができます。
サーボモータ
サーボモータは、モータとコントローラを一体化したモータで、回転角度を指定できます。多くの場合、DCモータと角度を計測できるセンサ(エンコーダ)、およびセンサからの値によってDCモータの動きを制御するコントローラから構成されています。つまり、モータの中にマイコンがあるようなものです。

回転角度の指定は、パルス波によって行います。パルス波は、PWMで説明したように、電圧が周期的にON・OFFするようなデジタル信号です。ON(HIGH)の長さによって回転する角度を指定します。安価に手に入れられるサーボーモータの場合、回転角は最大で180度のものが多いです。xミリ秒ONにすると0度の位置、yミリ秒ONにすると180度の位置、というようにパルス波のHIGHの長さで角度が決まります。
サーボモータはピンキリで、物によってはあまり正確に指定した角度になりません。0度から180度まで指定できるとは言っても、実際にはそれよりも狭い範囲しか動かないものもあります。実際に使用する際には、そのことも考慮に入れてプログラムを調整してください。
6.3 外部電源を使う時の注意
外部電源を使うときの注意点を、幾つか説明しておきます。
外部電源を使っている時に回路をショートさせてしまうことがよくあります。外部電源のプラス側は、回路が完成してからよく確認をして最後につなげるようにしましょう。
FA-130を外部電源で駆動する場合について説明します。TB6612を使用することを前提とします。TB6612は、モータを駆動するための電源に13.5Vまで入力することができます。実際に出力されるのは、PWMで与えた電圧以下になりますから、Raspberry Piの場合、最大でも3.3Vまでにしかなりません。ということは、外部電源として単3電池2本を使っても良いし、4本使っても良いし、9Vの電池も使えます。
モバイルバッテリも、条件によってはモータの駆動に使えます。モバイルバッテリは5V出力なのですが、物によっては500mA程度の電流しか供給できません。モータを駆動する際に使う場合には、1A以上のものが良いでしょう。
また、モバイルバッテリは、通常、USBケーブルで電源供給しますから、ブレッドボードで使用できるUSBコネクタを用意して、そこから5.0Vを取ってこなければいけません。
外部電源のグラウンドとRaspberry Piのグラウンドは必ずつなげます。プラス側は絶対につなげてはいけません。
6.4 DCモータを回す
それでは、TB6612を使ってDCモータを回してみましょう。
電池ボックスは単3電池を2本使います。これは単4でも同じです。

TB6612キットとRapberry Piおよび電池ボックスの接続を以下の図を参考にしてください。

それぞれのピンの役割は以下の通りです。
- AO1 → モータの片側の線へ
- AO2 → モータの片側の線へ
- PGND → Raspberry Piのグラウンドへ
- VM → 電池ボックスのプラス(3V)
- PWMA → GPIO 17(PWM制御してモータへの電圧を決める)
- AIN2 → GPIO 27
- AIN1 → GPIO 22
- VCC → Raspberry Piの3.3Vピン(TB6612に電源を供給)
- STBY → Raspberry Piの3.3Vピン(スタンバイ)
- GND → Raspberry Piのグラウンドへ
このように、外部電源を使う場合には、必ず双方の電源のグラウンドをつなげます。一方、双方の電源のプラス側は絶対につないではいけません。
制御は、3つのピンで行います。ここではGPIO 17、GPIO 22、GPIO 27を使っています。GPIO 17はPWMで電圧を操作します。ここで入力された電圧と同じ電圧がモータにかかります。GPIO 17の電圧が直接モータにかかるわけではなく、外部電源から電圧が調節されてかかります。GPIO 22とGPIO 27の2つのピンで、モータの正転、逆転、ストップなどの制御を行います。

ショートブレーキとストップはあまり変わらないと思って良いです。正転と逆転は、モータの回転をどちらを正転と思い、どちらを逆転と思うかで変わります。GPIO 22とGPIO27のどちらかをonにして、どちらかをoffにすればモータは回ります。
以下のような回路を組みます。

モータは以下の記号で示されています。

1秒間正転し、1秒間停止し、1秒間逆転し、1秒間停止するということを繰り返すプログラムを書いてみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
from gpiozero import PWMOutputDevice from gpiozero import OutputDevice from time import sleep pwm_pin = 17 motor1_pin = 22 motor2_pin = 27 pwm = PWMOutputDevice(pwm_pin, frequency=10000) # 10kHz motor1 = OutputDevice(motor1_pin) motor2 = OutputDevice(motor2_pin) def motor_forward(in1, in2): in1.on() in2.off() def motor_backward(in1, in2): in1.off() in2.on() def motor_stop(in1, in2): in1.off() in2.off() def main_loop(): motor_stop(motor1, motor2) pwm.value = 0.3 # デューティ比30% while True: sleep(1) motor_forward(motor1, motor2) sleep(1) motor_stop(motor1, motor2) sleep(1) motor_backward(motor1, motor2) sleep(1) motor_stop(motor1, motor2) if __name__ == '__main__': main_loop() |
DCモータの制御をするときには、以下のことを守りましょう。
- 回転方向を変えるときには、必ず一旦ストップさせる。
- モータの軸を手で回さない。
6.5 ギア
DCモータを使う時には、通常ギアも必要となります。ギアは、歯車という意味ですが、モータやエンジンの回転数とトルクを調整するための装置の名称としても使われます。ここではギアの原理については説明しません。
DCモータは、そのまま使うと回転数が高過ぎて色々と厄介です。例えば、ラジコンのようなものを作ろうと思う時には、回転数をギアを使ってトルクに変える必要があります。トルクというのは回転させる力のことです。
以下のタミヤのページを見ると、色々なギアボックスというものがあることが分かります。
https://www.tamiya.com/japan/products/list.html?genre_item=401020
また、元々ギアが付いたモータというものも売られています。
6.6 サーボモータを回す
サーボモータの多くは、5Vで動くようになっています。多くの場合、説明書に4.8Vと書いてあれば、単3電池4本(6V)で動きます。2本では動かないので注意しましょう。
サーボモータには3本線があります。電源のプラスとグラウンド、そして信号線です。この3本の線のどれがプラスで、どれがマイナスかに注意してください。説明書が入っている場合にはそれをきちんと確認しましょう。
信号線にパルス波を入力すればサーボモータが回転します。パルス波はPWMと同じように考えれば良いです。ほとんどのサーボモータでは、パルス波の1周期の長さは20msになっています。デューティ比によって角度が指定できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
from gpiozero import Servo from time import sleep servo = Servo(17) def main_loop(): while True: servo.min() sleep(1) servo.mid() sleep(1) servo.max() sleep(1) if __name__ == '__main__': main_loop() |
上記のプログラムはGPIO ZeroのServoクラスを使っています。min()
は最小角度への移動、mid()
は真ん中の角度への移動、max()
は最大角度への移動です。servo.value = 0
のように数字で角度を指定することもできます。その際には-1が最小角度、0が真ん中、1が最大角度です。
6.7 実習
今回の実験では、モータドライバ、DCモータ、電池ボックスを配ります。演習終了時に全て回収します。
実験1. PWMで電圧を制御する
以下のような回路を作ります。

プログラムは以下のようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
from gpiozero import PWMOutputDevice pwm_pin = 17 pwm = PWMOutputDevice(pwm_pin) def main_loop(): pwm.value = 0.5 while True: pass if __name__ == '__main__': main_loop() |
pwm.value
の値を0.1にした時、0.5の時、0.8の時、それぞれの10KΩ抵抗にかかる電圧をマルチーメータで測ります。測る前に、計算によって予想を立ててから実験を行ってください。
実験2. PWMの周期を変えてみる1
この資料で説明したLEDの明るさをPWMで制御する例題を修正して、PWMの周期を100msにしてみましょう。もともとは10ms(100Hz)なので、10倍の長さにするということですね。観測された結果の考察を書いてください。ここでいう考察とは、なぜ結果がそうなったのかを自分なりに推測するということです。
以下のプログラムを修正します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
from gpiozero import PWMLED from time import sleep led_pin = 17 led_pwm = PWMLED(led_pin) def main_loop(): while True: led_pwm.value = 0.8 # デューティ比80% sleep(0.5) led_pwm.value = 0.1 # デューティ比10% sleep(0.5) if __name__ == '__main__': main_loop() |
実験3. PWMの周期を変えてみる2
実験1と同じ回路を用いて、PWMでLEDの調光を行うために必要となるPWMの周期を調べましょう。100msから10msくらいの間隔でだんだん周期を短くしていき、正しく調光できる周期の下限を見つけましょう。
プログラムを修正しては実行する、ということを繰り返しておおよその値を調べます。
実験4. タクトスイッチで調光する
タクトスイッチ2個(AとBという名前にします)とLED1個を使います。タクトスイッチAを押すたびに少しLEDの明るさが増し、タクトスイッチBを押すたびに少しLEDの明るさが減るようにします。明るさの上限に達している時にタクトスイッチAを押しても、何も変化しないこととします。下限に達している時も同様です。
— by 石井 健太郎、沼 晃介、飯田 周作 専修大学ネットワーク情報学部