RaspberryPiでサーボモーターを動作させてみる

フェリーハッカソン2019(以降ハッカソン)にてスマートロックっぽいものを作ったので、メモっておきます。

基本的にはサーボモーターをRaspberryPiで駆動させて鍵っぽいものを作る形になります。 ハッカソンではトリガーとなる部分はフロントエンド側の担当の方におまかせしたので、 このエントリーではHTTP経由でアクセスされたらCGIpythonのプログラムを動作させてサーボモータを駆動する形としています。 トリガーになる動作がないので部分の処理がないのですが、その当たりはすいませんです。

f:id:ueponx:20190130165017j:plain

サーボモーターを駆動するのは、検索すると結構いっぱいヒットしますが、自分は以下の方の情報を見ながら作業しました。 ありがとうございます。

【参考】 bufferoverruns.blogspot.com

サーボモーターとしてしようしたのはSG90というモーターになります。

eleshop.jp

割と電子工作界隈では有名なサーボモーターのようです。自分は今回はじめてサーボモーターを使用したので、知りませんでした。 モーター駆動はドライバーICなどが必要になると思っていたので躊躇していたのですが、このSG90はGPIO経由(制御3.3V)でも問題なく動作します。 ということで、参考にした情報をもとに動かしてみます。

GPIOのピンからPWM(Pulse Width Modulation)という信号制御を使用して、特定のパルス幅の信号をモーターに与えて制御を行います。 パルスのHI区間とLOW区間の時間の比率であるDuty Cycleを使っているそうです。 今回はGPIOの4pin目を使用してサーボモーターを制御し、17pin目でLEDの点灯制御を行っています。 RaspberryPiのGPIOをpythonから使用するRPiGPIOモジュールを使用します。(多分、Raspbianにはデフォルトでインストールされているかなと思います)

SG90とRaspberryPiの接続は以下のようになります。

  • オレンジ線:PWM信号
  • 赤線:電源
  • ブラウン線:GND

【open.py】

import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BCM)

gp_out = 4
gp_led = 17

GPIO.setup(gp_out, GPIO.OUT)
GPIO.setup(gp_led, GPIO.OUT)

GPIO.output(gp_led,GPIO.HIGH)

servo = GPIO.PWM(gp_out, 50) 

servo.start(0.0)

servo.ChangeDutyCycle(2.5)
time.sleep(0.5)

servo.stop()

GPIO.output(gp_led, GPIO.LOW)
time.sleep(5.0)

GPIO.cleanup()

こんな感じで問題ないと思います。

ちょっとだけ説明すると

GPIO.setmode(GPIO.BCM)

ここでGPIOのpin指定のモードを設定します。BCMでGPIOのGPIOの番号で指定するモードになります。 他にもボードのピン番号で指定するモードもあります。

GPIO.setmode(GPIO.BCM)   # GPIO番号指定
GPIO.setmode(GPIO.BOARD)   # ボードピン番号指定

以下の部分でGPIOのピンを出力モードに設定しています。

GPIO.setup(gp_out, GPIO.OUT)
GPIO.setup(gp_led, GPIO.OUT)

これで事前設定は終わりです。 あとは、GPIOのピンにPWMの制御をかけることになります。

servo = GPIO.PWM(gp_out, 50) 

servo.start(0.0)

servo.ChangeDutyCycle(2.5)
time.sleep(0.5)

servo.stop()

GPIO.PWM()の設定のところで引数に50とあるのは50Hzを意味します。この周波数の信号にデューティーサイクルを設定して制御信号を出力します。 SG90は2.5から12.5の値のにデューティーサイクルを与えることで0度~180度の角度で回す事ができるようです。 今回はサーボモータが0度の状態を空いた状態とし、90度の状態を閉まった状態として扱うことにします。 サーボモータは駆動する時間が必要があるのでtime.sleep()でウエイトを与えています。

以上のような感じで制御ができます。

鍵を閉める場合にはサーボモータの角度を90度回転すればいいのでservo.ChangeDutyCycle(7.5)とすれば良い感じです。

【close.py】

import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BCM)

gp_out = 4
GPIO.setup(gp_out, GPIO.OUT)
servo = GPIO.PWM(gp_out, 50) 

servo.start(0.0)

servo.ChangeDutyCycle(7.5)
time.sleep(0.5)

servo.stop()
GPIO.cleanup()

これで開け締めはできるようになりました。これをHTTPトリガーで制御できれば良いことになります。

HTTPサーバーの制御

pythonにはHTTPのモジュールがあるのでそれを使用するだけでHTTPサーバを立てる事ができます。 以下のように実行すれば、簡易的なHTTPのサーバとしてすぐに使用することができます。

python -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

デフォルトではポートが8000番になります。引数でポート番号を指定する事もできます。 また、オプションで--cgiを指定するとCGIを起動することもできます。CGIはデフォルトでは起動パスに存在するcgi-binというディレクトリに 存在するパスがそれに当たります。

【server.sh】

$ python3 -m http.server --cgi

あるディレクトリにserver.shという形でシェルを作った場合には以下のようにcgi-binディレクトリを作成し、 権限を755などにすればいいかと(実行するpythonで作られたCGIプログラムも実行権限とシバンを記載しておいてください)

ディレクトリ構造】

$ ls -l
合計 24
drwxr-xr-x 2 pi pi 4096  127 09:56 cgi-bin
-rw-r--r-- 1 pi pi  222  126 21:58 close.py
-rw-r--r-- 1 pi pi  343  127 09:00 open.py
-rw-r--r-- 1 pi pi 1240  129 08:27 readNFC.py
-rwxr-xr-x 1 pi pi   29  126 15:17 server.sh
-rw-r--r-- 1 pi pi  208  126 13:56 simpleServer.py

$ ls -l cgi-bin/
合計 8
-rwxr-xr-x 1 pi pi 435  127 09:56 servo_close.py
-rwxr-xr-x 1 pi pi 535  127 09:55 servo_open.py

今回は以下のような感じでCGIのプログラムを作成しています。 servo_open.pyにちょっと変わった部分がありますが、CGIでサーボを駆動させているので、ファイル(ファイル名はlockとしている)による動作のロックをかけています。 他にも方法はあるかなと思いますが、何らかの形でロックの機構をいれていないと制御が微妙になることもあるかと思いますので入れておいたほうが無難です。

ドアをOpenする処理は以下のようになります。

【servo_open.py】

#!/usr/bin/env python3

import os

print('Content-type: text/html; charset=UTF-8\r\n')
print('Open the Door!')

if os.path.exists("lock"):
    exit()

f = open('lock','w')
f.close()

import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BCM)
gp_out = 4
gp_led = 17

GPIO.setup(gp_out, GPIO.OUT)
GPIO.setup(gp_led, GPIO.OUT, initial=GPIO.LOW)

servo = GPIO.PWM(gp_out, 50) 

servo.start(0.0)
servo.ChangeDutyCycle(2.5)
time.sleep(0.5)
servo.stop()

GPIO.output(gp_led, GPIO.HIGH)
# time.sleep(3.0)

# GPIO.cleanup()

os.remove('lock')

ドアをCloseするのもほぼ同様で記述できます。

【servo_close.py】

#!/usr/bin/env python3

import os

print('Content-type: text/html; charset=UTF-8\r\n')
print('Close the Door!')

if os.path.exists("lock"):
    exit()

f = open('lock','w')
f.close()

import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BCM)
gp_out = 4
gp_led = 17

GPIO.setup(gp_out, GPIO.OUT)
GPIO.setup(gp_led, GPIO.OUT, initial=GPIO.LOW)

servo = GPIO.PWM(gp_out, 50) 

servo.start(0.0)
servo.ChangeDutyCycle(7.5)
time.sleep(0.5)
servo.stop()

GPIO.output(gp_led, GPIO.LOW)
# time.sleep(3.0)

# GPIO.cleanup()

os.remove('lock')

ここまで来れば、ブラウザからURIを入力してアクセスすれば動作できます。

f:id:ueponx:20190130083253p:plain 【開く場合】

f:id:ueponx:20190130083320p:plain 【閉じる場合】

【動作ログ】

$ ./server.sh 
Serving HTTP on 0.0.0.0 port 8000 ...
192.168.0.12 - - [30/Jan/2019 08:30:46] "GET /cgi-bin/servo_close.py HTTP/1.1" 200 -
192.168.0.12 - - [30/Jan/2019 08:30:49] code 404, message File not found
192.168.0.12 - - [30/Jan/2019 08:30:49] "GET /favicon.ico HTTP/1.1" 404 -
192.168.0.12 - - [30/Jan/2019 08:32:09] "GET /cgi-bin/servo_open.py HTTP/1.1" 200 -
/home/pi/20190126/cgi-bin/servo_open.py:21: RuntimeWarning: This channel is already in use, continuing anyway.  Use GPIO.setwarnings(False) to disable warnings.
  GPIO.setup(gp_out, GPIO.OUT)
/home/pi/20190126/cgi-bin/servo_open.py:22: RuntimeWarning: This channel is already in use, continuing anyway.  Use GPIO.setwarnings(False) to disable warnings.
  GPIO.setup(gp_led, GPIO.OUT, initial=GPIO.LOW)

動作ログをみるとWarningが出ていますが、LEDのをつけたままにしておくなどGPIOのCleanupの処理を行っていないので発生しています。 コード内に

GPIO.setwarnings(False)

と記述すればなくなります。気になるようであればコード内に入れておきましょう。

躓いた点

プログラムを作っているときに躓いた点も記載します。

プログラムが実行の度挙動が異なる

最初の数時間これで悩みました。実行する度に挙動が違ったり、RaspberryPiのコンソール接続が切れるという状況になりました。最初はプログラムの設定がおかしいに違いないと思っていたのですが、 電源を変更するとびっくりするほど安定しました。電流値の高いものを使用したほうがいいようです。2.1Aの供給が行われるACアダプタを使用すると正常に動きました。 サーボモーターを動作させる場合にはACアダプターなどはちゃんと確認して使用したほうがいいかなと思います。

個人的には電源に問題があればRaspberryPiがリブートするのではないかと思っていたのですが、結構意外でした。

LEDのOFFしてもなんかついている(なんとなく点灯してる?)

LED制御のGPIOのピンを3にしていたのですが、LEDのOFFをしてもなんとなくついていました。あれ? よくわからなかったのですが、17に変更すると同じことは発生しなかった。時間がなくて、基本LEDのの直結をしていたのも良くないのですが 3と17の違いは…多分プルアップ、プルダウンの関係ではないかと思います。もう少し調べてみたいです。

LED制御のGPIOのピンをGPIO3にしていたのですが、GPIO.cleanup()をする(初期状態)とLEDがなんとなくついていました。暗く点灯している? 制御を行うと完全に点灯と消灯の状態になります。GPIO17に変更すると同じ減少は発生していませんでした。

以下の情報でGPIO.setup()から初期状態を変えることができるらしいので設定すると

GPIO.setup(gp_led, GPIO.OUT, initial=GPIO.LOW)

GPIO.setup()以降は完全消灯していました。ただGPIO.cleanup()ともとに戻ってしまいます。 原因は以下のページにかかれているのですが、GPIO2とGPIO3は内部に抵抗あり、それが原因かと思います。

tool-lab.com

GPIO2とGPIO3は他のGPIOのpinとは違うというのは覚えて置く必要がありそうです。ちょっとスッキリ。

おわりに

f:id:ueponx:20190130165145j:plain

初めてサーボモーターを触りましたがわりと面白いです。これでアームとかできたら違うアイデアうかぶかも。 今回はQRコードからのトリガー部分はつくっていないのでその部分にチャレンジする?またはFelicaからトリガーを飛ばすのはチャレンジしたいかな~。

QRコードだとカメラの処理…。Felicaだとnfcモジュール…(python2.7系との混在)結構時間かかるかも。