RCサーボはマイクロコントローラの電子的な処理結果を、回転という物理的な動きに簡単に変換してくれる便利で使いやすい装置です。
RCサーボは、電源の他は1線のPWM信号で制御されます。最近のマイクロコントローラーにはPWMの発生機能が結構多く組み込まれているので、ある意味RCサーボの制御は簡単です。基本的には、20ms周期(50Hz)の固定周波数の信号を発生させ、信号がHIGHの期間を適当に制御することで、RCサーボは簡単に制御できます。
と、言いたいところですがここからが面倒なところです。RCサーボの制御用のPWM信号の周期は、私が知る限りはどのサーボも20msですが、信号のHIGHの期間と、その長さが、RCサーボの角度とどのような関係になっているのかが、使用するサーボによって異なっているため、いわゆるキャリブレーション:調整、が必要になります。
基本プログラム
CircuitPythonでは以下のモジュールを使用してRCサーボの制御を行うことができます。
- pwmio
- servo (adafruit_mortor内)
pwmioを使用して20ms周期のPWM信号を生成し、servoを使用して指定した角度を対応するPWMのHIGHパルス幅に変換しています。
RP2040-UNOやRP2040-UNO-PLUSでRCサーボの操作実験をしてみましょう。
RP2040-UNOやRP2040-UNO-PLUSは、基板左下の電源ソケットと電源関係のピンソケットの間に、GPIO0, GPIO1をRCサーボに接続しやすいようにそれぞれ3ピンのピンヘッダーでCN2,CN3に引き出されています。ここにRCサーボを接続して、簡単に実験を行うことができます。
RCサーボ用の電源は、USBもしくは電源ソケットから取得される5Vとなっています。開発ボードをUSBのみの接続で使用している場合には、RCサーボが大きめだと、大きな電流を使用してUSBに負荷がかかる場合があるので注意してください。
import board
import time
import pwmio
from adafruit_motor import servo
pwm = pwmio.PWMOut(board.GP0, duty_cycle=2 ** 15, frequency=50)
# actuation_range: int = 180, min_pulse: int = 750, max_pulse: int = 2250
my_servo = servo.Servo(pwm)
while True:
for angle in range(0, 180, 5): # 0 - 180 degrees, 5 degrees at a time.
my_servo.angle = angle
time.sleep(0.05)
for angle in range(180, 0, -5): # 180 - 0 degrees, 5 degrees at a time.
my_servo.angle = angle
time.sleep(0.05)
使用しているRCサーボが上記のデフォルト設定のプログラムで、180度の回転を繰り返してくれればいいのですが、多くの場合、回転角が偏っていたり、回転角が180度に至らないのではないかと思います。
パルス幅のキャリブレーション
指定した値に合わせてRCサーボが適切に回転してくれるようにするには、それぞれのRCサーボに合わせて設定をキャリブレーション:調整してやる必要があります。
このため、RCサーボの回転角がライブラリのデフォルトの180度に合致する場合には、PWMのHIGHのパルス幅の範囲を調整する必要があります。
以下の例は、手元にあったSG90というサーボに合わせたものです。3個のサーボで試してみましたが、同じ設定で問題なく動きました。ただ、SG90という名前で、どうやら複数のメーカーの同等品(信号等の規格が統一された互換品かどうかは不明)が何種類か流通しているようなので、メーカーが異なると、同じ型番でも設定を変更する必要があるかもしれません。
import board
import time
import pwmio
from adafruit_motor import servo
pwm = pwmio.PWMOut(board.GP0, duty_cycle=2 ** 15, frequency=50)
# SG90
my_servo = servo.Servo(pwm, min_pulse = 500, max_pulse = 2150)
while True:
for angle in range(0, 180, 5): # 0 - 180 degrees, 5 degrees at a time.
my_servo.angle = angle
time.sleep(0.05)
for angle in range(180, 0, -5): # 180 - 0 degrees, 5 degrees at a time.
my_servo.angle = angle
time.sleep(0.05)
回転角のキャリブレーション
手元にちょっと大きめのMG996Rというサーボがあったので、それ用のキャリブレーションもやってみました。MG996Rはそもそも回転角が120度となっているので、PWMのパルス幅の他に、指定できる回転角も変更してやる必要があります。
import board
import time
import pwmio
from adafruit_motor import servo
pwm = pwmio.PWMOut(board.GP0, duty_cycle=2 ** 15, frequency=50)
# MG996R
my_servo = servo.Servo(pwm, actuation_range = 120, min_pulse = 500, max_pulse = 2100)
while True:
for angle in range(0, 120, 5): # 0 - 180 degrees, 5 degrees at a time.
my_servo.angle = angle
time.sleep(0.05)
for angle in range(120, 0, -5): # 180 - 0 degrees, 5 degrees at a time.
my_servo.angle = angle
time.sleep(0.05)
スイッチによる操作
RP2040-UNO-PLUSにはスイッチが2つ装備されているので、それを利用してRCサーボを操作できるようにプログラムを少し変更してみましょう。
import board
import digitalio
import time
import pwmio
from adafruit_motor import servo
sw1 = digitalio.DigitalInOut(board.GP22) # 初期状態は入力用
# sw1.pull = digitalio.Pull.UP # RP2040-UNO-PLUSではプルアップされているので必要ない
sw2 = digitalio.DigitalInOut(board.GP23)
# sw2.pull = digitalio.Pull.UP
# create a PWMOut object on Pin A2.
pwm = pwmio.PWMOut(board.GP0, duty_cycle=2 ** 15, frequency=50)
# SG90
my_servo = servo.Servo(pwm, min_pulse = 500, max_pulse = 2100)
angle = 90
while True:
if sw1.value == False:
angle += 5
if angle > 180:
angle = 180
if sw2.value == False:
angle -= 5
if angle < 0:
angle = 0
my_servo.angle = angle
time.sleep(0.05)
センサーによる操作
生?のPWMでの操作
servoモジュールを使用せずに、pwmioモジュールのみで、いわゆる生のPWMで制御した例です。
duty_cycleに、単純に短い側のパルス幅と長い側のパルス幅のHIGHの時間の割合を計算して設定しただけです。
import board
import time
import pwmio
pwm = pwmio.PWMOut(board.GP0, duty_cycle=2 ** 15, frequency=50)
# SG90
# my_servo = servo.Servo(pwm, min_pulse = 450, max_pulse = 2150)
while True:
pwm.duty_cycle = (2 ** 16) // 20000 * 550
time.sleep(1)
pwm.duty_cycle = (2 ** 16) // 20000 * 2200
time.sleep(1)
simpleioモジュールのmap_range()関数を使用すると、角度からパルス幅への返還を簡単に行うことができます。それを使って、サーボの回転量を回転角で指定できる様にした例です。
import board
import time
import pwmio
import simpleio
pwm = pwmio.PWMOut(board.GP0, duty_cycle=2 ** 15, frequency=50)
# SG90
# my_servo = servo.Servo(pwm, min_pulse = 450, max_pulse = 2150)
def servo_duty(degree):
pulse_width = simpleio.map_range(degree, 0, 180, 550, 2200)
return int((2 ** 16) / 20000 * pulse_width)
while True:
pwm.duty_cycle = servo_duty(0)
time.sleep(1)
pwm.duty_cycle = servo_duty(180)
time.sleep(1)