サーボモーターを動かす 【Raspberry Pi】

Raspberry Pi

Raspberry Pi でサーボモーターを動かす Python のスクリプトについてまとめておきます。
以下の構成で動作確認をしています。

構成:
・ Raspberry Pi (bullseye, Python 3)
・ サーボモーター FEETECH FS90 (一例)
・ Windows パソコン+Wi-Fi ネットワーク(Raspberry Pi をリモートデスクトップで動かすため。)

背景

Raspberry Pi の特徴として、気軽に GPIO が扱えるという点があります。

一例として、以前に、LED を扱うスクリプトについてまとめています。
そこで引き続き、サーボモーターを動かすスクリプトについてもまとめておくことにします。パルス幅変調(PWM, Pulse Width Modulation)で動かしており、一般の PWM 制御も可能となると思います。

なお、Raspberry Pi 本体については、起動用の SD カードやネットワーク接続など、基本設定はすでに終わっていることを前提としています。設定方法については下記の関連リンクにまとめていますので、関心のある方は参照してみてください。

必要なものの一例 
・ Raspberry Pi (bullseye、電源等を含む一式)
・ サーボモーター FEETECH Micro Servo FS90 (Amazon、一例)
・ 配線用のジャンパワイヤ(オス – メス、赤、黒、青の計3本)

設定手順

サーボモーターの接続

① 上記の写真を参考に、Raspberry Pi にサーボモーターを接続します。
上の写真では、ケーブルの色の対応はつぎのようにしています。

Raspberry Pi ジャンパーワイヤ サーボモーター
2番ピン 5V 赤 (メス – オス) 赤(電源)
32番ピン GPIO12 青 (メス – オス) 橙(PWM の信号線)
39番ピン GND 黒 (メス – オス) 茶(GND)

※ Raspberry Pi のピン配列は、Raspberry Pi でターミナルを起動し、”pinout” +[enter] と入力して確認してください。上の写真では、GPIO12 (32番ピン)にパルス幅変調の信号が出力されるよう、設定しています。
※ なお今回、サーボモーターの電源は、簡単のため、Raspberry Pi の 5V の出力ピンを使っています。
もし実用で使う場合は、サーボモーターの電源を別の 5V 電源等から取って安定化させる等の回路構成についても検討してみてください。

ソフトウェアの設定

② Raspberry Pi を起動して、以下を参考にサーボモーター検討用のフォルダ(”servo1″)を作ります。
例: home/pi/servo1/
③ ②のフォルダの中にテキストファイルを作成し、下記のサンプルスクリプト(2つ)をコピー&ペーストして保存してください。
・ スクリプト1は、サーボモーターを回す動作をオーソドックスに記載したものです。
“servo_motor1.py” 等のファイル名で保存してください。
・ スクリプト2は、サーボモーターの回転角の設定を関数化した事例です。
“servo_motor2.py” 等のファイル名で保存してください。
★ 最初に要素検討をする場合は、スクリプト1が扱いやすいと思います。しかし、モーターを増やしたい/モーターの種類が複数ある、などとなった場合、スクリプト1の書き方ではプログラムが複雑化します。スクリプト2のように関数化したほうが扱いやすいです。

使い方

④ 以下を参考に、③のスクリプトを実行してください。
例:
cd home/pi/servo1
python servo_motor1.py または python servo_motor2.py

→ サーボモーターが動いたら、成功です!

うまく動いたら

・ うまく動いたら、スクリプトをアレンジしてみてください。
たとえば、servo_motor2.py では、サーボモーターの回転角が、±90度、±45度、… となるよう設定しています(上の写真を参照)。角度を変えるなどアレンジしてみてください。
・ モーターを複数に増やすことも可能です。この場合、たとえば、スクリプト servo_motor2.py で、pin1 = 12 のあとに pin2 = 16 等を加え、スクリプトの記載と同様に pwm2 = GPIO1.PWM(pin2, 50) 等 … を加えていきます。複数のモーターが動くことを確認しています。

詳細

スクリプトの説明

servo_motor1.py 
・ Raspberry Pi の汎用入出力 GPIO を使うので、冒頭で import Rpi.GPIO …とします。
・ パルス幅制御のパルスを出力するピン pin1 を 12 としています。
使用するピンを変更したい場合は修正してください。
・ duty1_min = 2.5、duty1_max = 12.5 は、サーボモーターの仕様から決まるパルス幅です。それぞれ最小デューティ幅、最大デューティ幅に対応し、回転角では -90度、+90度に対応しています。単位は % で設定します。
・ GPIO01.setmode、setup、pwm1 = … とした行で GPIO の初期設定をします。
・ 最初に pwm1.start( duty1_mid ) で、モーターの回転角を中央の0度(上の写真で右向き)にしています。
・ 以後、for ループで回転角を順次、変更しています。180ステップとし、-90度から +90 度まで(duty の最小値から最大値となるよう)設定しています。
・ 最後に回転角を中心値 duty1_mid に戻し、使用した GIPO を開放しています。

servo_motor2.py 
・ servo_motor1.py と同様の処理をしていますが、サーボモーターを動かす(回転角を与える)部分を関数化しています。
・ 関数 set_duty0() は、PWM 制御の信号を出力するための汎用な関数です。
・ 関数 set_duty1() は、サーボモーター FS90 の設定値を入れた関数です。FS90 の仕様に基づき、固有なパラメータをこの関数内で定義し、汎用関数 set_duty0() を呼び出しています。異なるサーボモーターを使う場合はこの中を修正してください。
また、種類の異なるサーボモーターを複数使う場合は、同様の要領で set_duty2()、set_duty3() 等を追加していけば動くと思います。
・ 以後は servo_motor1.py と同様に、角度を設定して動かしています。
具体的には、-90度 → +90度 → -45度 → +45度 → -30度 → +30度 → 0 度となるよう動かしています。上の写真(gif 画像)の動きとなります。

FEETECHサーボ FS90 の仕様の確認

サーボモーターの仕様(下記の外部リンク参照)を確認しておきます。
仕様から要部を転記すると、以下のようになっています。
・ 電源電圧: DC 4.8 ~ 6 V
・ 配線: 茶 = GND、赤 = 電源[+]、橙 = 制御信号
・ 制御パルス: 0.5ms ~ 2.5ms (180 度で使用するとき)
この仕様に基づいて配線をつなぎ、ソフトウェア側での設定値を決めることになります。
(デューティの幅、レンジに関する記載は仕様・ホームページにありますが、周期に関する記載が見つからないようです。以下、周期は 20ms とみなして記載します。)

・ Raspberry Pi 側からのパルス幅制御(PWM)で、周波数を 50Hz と設定とすると周期は、1 sec / 50Hz = 20 ms となります。そこでプログラム側で 50Hz に設定します。
・ 制御パルスは、0.5ms ~ 2.5ms とあります。一方、Python の PWM 制御のスクリプト側では、制御パルス幅を % 単位で指定するようになっています。
そこで、この範囲を周期 20ms で割って % 表示にすると、0.5ms/20ms ~ 2.5ms/20ms = 2.5% ~ 12.5% となります。
2.5、12.5 の値が、サーボモーターの-90度回転、+90度回転に対応しています。
・ サーボモーターの回転の中央値は、(2.5 + 12.5)/2.0 = 7.5 となります。
パルス幅の設定を 7.5 にすると、モーターの軸の回転角が0度となる(上記の写真でモーターに取り付けた部材が右向きとなる)ことになります。

その他、印象など

・ 比較のため、類似した製品であるサーボモーター Tower Pro SG-90 の仕様を確認すると、制御パルス幅は 0.5ms ~ 2.4ms となっています。最後の数値が FS90 とは少し異なっていますが、ほぼ同じプログラムで動くと思います。
・ FS90 は、内部のギアが樹脂製です。比較的低価格であるためこの製品を購入しましたが、トルクが必要な用途、長時間使用するような用途では、ギアが金属である製品を使ったほうがよさそうです。
・ 配線が3本で済むのはサーボモーターのメリットだと思います。多自由度の制御を行う場合、回路がシンプルになります。ステッピングモーターですと、ユニポーラ型の2相励磁式で、制御信号4本+2本となり、回路が複雑化しやすいです。
・ また、FS90 を動かしてみた印象として、サーボモーターは指定角度に合わせ込こもうとする動きとなるため、動作中、微小な往復動作(振動、ハンチング)が入ります。
そのため、高精度なカメラ制御の用途というよりは、Web カメラと組み合わせて小型のパンカメラを構成する、ペンプロッタのペンの動作用に使う、小型のロボットアームに使うなどの用途が向いているかな、といった印象です。精密な制御が必要であれば、ステッピングモーターや超音波モーターが向いていると思います。
・ 実測はしておらず目視ですが、180度の最大角度(-90度~+90度)も数度、ずれていそう、また、指定した角度も1、2度のばらつきはあるのでは、といった印象です。
角度の精度が要求されるような用途では、原点出しやキャリブレーションの処理が必要となりそう。また、角度設定の繰り返し再現性も評価しておいたほうがよいという印象です。

まとめ

Raspberry Pi でサーボモーターを扱うスクリプトについてまとめました。

ネット検索をしたところ、モーターの仕様を確認してプログラムの設定値を求めているサイトが見つかりませんでした。そこでオーソドックスに、仕様の確認から設定値の導出についてもまとめました。
これで、Raspberry Pi でパルス幅変調 PWM も扱えることになります。
モーターをいくつか買ったので、ロボットアームが作れるかなとも思ったのですが、トルク・耐久性が弱いか。。

関連リンク
・ 起動用SDカードを設定する 【Raspberry Pi】
・ LED を ON/OFF する 【Raspberry Pi & Python】
・ リンク機構をシミュレーションしてみる 【Python】

外部リンク [PR] 
・ FEETECH Micro Servo 9g FS90 (Amazon)
・ FEETECH サーボ FS90 (秋月電子)
・ TOWER PRO SG90 (Amazon)
・ Tower Pro Pte Ltd マイクロサーボ9g SG-90  (秋月電子)
★ ご参考までにサーボモーターのリンクを挙げておきます。上記の写真は FS90 ですが、別製品 SG-90 でも duty1_max = 12.0 とする程度で同様に動くと思います。

サンプルスクリプト

サンプル1:servo_motor1.py  スクリプトを平易に記載した場合

import RPi.GPIO as GPIO1
import time as tm1 

pin1 = 12 

duty1_min = 2.5 
duty1_max = 12.5 
duty1_mid = (duty1_min + duty1_max)/2.0 

GPIO1.setmode(GPIO1.BCM)
GPIO1.setup(pin1, GPIO1.OUT)

pwm1 = GPIO1.PWM(pin1, 50)  # 50Hz, 20ms 
pwm1.start( duty1_mid )
tm1.sleep( 1.0 ) 

for i1 in range( 181 ):
    duty1 = duty1_min + (duty1_max-duty1_min)*i1/180 
    print( i1, duty1 ) 
    pwm1.ChangeDutyCycle( duty1 )
    tm1.sleep( 0.50 ) 

pwm1.ChangeDutyCycle( duty1_mid ) 
tm1.sleep( 1.0 ) 

GPIO1.cleanup() 

サンプル2: servo_motor2.py PWM 制御を関数化した場合

import RPi.GPIO as GPIO1
import time as tm1 

def set_duty0( pwm1, n1, n1_max, duty1_min, duty1_max ): 
    duty1 = duty1_min + (duty1_max-duty1_min) * n1/n1_max 
    print( "n1:" + str(n1) + " duty1: " + str(duty1) + " ms " ) 
    pwm1.ChangeDutyCycle( duty1 ) 

def set_duty1( pwm1, n1 ): 
    duty1_min = 2.5 
    duty1_max = 12.5 
    n2 = n1 + 90 
    n2_max = 180 
    set_duty0( pwm1, n2, n2_max, duty1_min, duty1_max ) 

pin1 = 12 

GPIO1.setmode(GPIO1.BCM)
GPIO1.setup(pin1, GPIO1.OUT)

pwm1 = GPIO1.PWM(pin1, 50)  # 50Hz, 20ms 
pwm1.start( 0.0 )
tm1.sleep( 4.0 ) 

set_duty1( pwm1, -90 ) 
tm1.sleep( 4.0 ) 

set_duty1( pwm1, 90 ) 
tm1.sleep( 4.0 ) 

set_duty1( pwm1, -45 ) 
tm1.sleep( 4.0 ) 

set_duty1( pwm1, 45 ) 
tm1.sleep( 4.0 ) 

set_duty1( pwm1, -30 ) 
tm1.sleep( 4.0 ) 

set_duty1( pwm1, 30 ) 
tm1.sleep( 4.0 ) 

set_duty1( pwm1, 0 ) 
tm1.sleep( 4.0 ) 

GPIO1.cleanup() 
タイトルとURLをコピーしました