RaspberryPiとサーボモータで5軸ロボットアームを動かしてみた

RaspberryPiとサーボモータで5軸ロボットアームを動かしてみた

今回はRaspberryPiとサーボモータでサインスマートの5軸ロボットアームを動かしてみましたので、そのときの情報を書いていきたいと思います。作ったロボットアームでボトルキャップチャレンジをやってみたときの様子がこちらです↓

イメージ通りに動いてくれてとても嬉しかったです。可能なら他のことにもチャレンジしてみたいですね。
では、目次の流れで書いていきたいと思います。

 

必要な材料

今回使用したものを以下に列挙していきます。

***************************************************
・5自由度ロボット•アーム・キット
・RaspberryPi 3 Model B
・SDカード(32GB)
・ブレッドボード
・ジャンパーワイヤ
・L字ピンヘッダ(サーボモータを回路に接続する用)
・ACアダプター(6V、サーボモータ駆動用)
・DCジャック(ACアダプターを回路に接続する用)
・HDMIケーブルが接続できるモニター
・HDMIケーブル
・サーボモータMG996R(ハンド用、追加で購入)
***************************************************

メインとなるロボットアームはサインスマートの5軸ロボットアームを使用しています。今回は5軸のロボットアームを購入しましたが、他にも3軸や6軸のものもあるようです。

サーボモータはMG996Rが4個、DS3218が1個付属されています。DS3218はハンド用として付属されていますが、電圧などをこれのためだけに別途用意したくはなかったのでMG996Rをもう1個追加で購入しました。フレームはアルミ製かと思っていたのですが、鉄製でした。持ってみるとかなりズッシリとしていて重いです。その分強度は強いと思います。

MG996Rのデータシートはこちら↓

MG996Rのデータシート

サーボのトルクがかなり強いので土台部分をガッチリと固定してから動かさないと振り回してあぶないです。

組み立てにはプラスドライバーがあれば十分です。あとラジオペンチなどあればナットの取り付けのときに役に立つかもしれません。

参考にさせていただいたサイト

こちらのサイトがとても参考になりました。

Raspberry pi でロボットアームを動かす その1

ロボットアームの組み立て

ネットから注文しましたので、ロボットアームはバラされた状態で届きます。この状態から写真を見ながら組み立てていきます。組み立て方法などが記載された説明書などがあればよかったのですが、そういったものは同封されていませんでした。

箱から出したときはこのようになっています。

サーボとフレームをネジで組み立てていきます。一つのフレームとサーボはこのようになっています。

まずは土台部分とサーボモータの一段目のフレームを固定します。

一段目のサーボモータを固定します。

フランジの上に二段目のフレームを固定します。

二段目のサーボを固定します。

ロボットアームの長いフレームの部分を取り付けます。

サーボモータの三段目の部分を取り付けます。四段目のフレームは先に固定しておいたほうがいいです。

四段目のサーボを固定します。ここで、付属されているネジでは四段目のフレームを固定しているネジと四段目のサーボが干渉してしまうことが分かります。。なので、可能ならば付属されているネジより少し短いネジが用意できれば理想的です。自分は干渉させたまま、サーボを固定するネジのほうを長くして対応しています。

最後に掴み部をフランジに取り付けます。サーボモータは先に固定しておきます。

全部組み立てるとこのようになります。

全部組み立てるのに2時間かからないぐらいだと思います。

初期版ロボットアーム

まずは初期版ロボットアームについて書いていきます。こちらのほうは結果的に操作が微妙でしたので、読み飛ばしていただいても大丈夫です。

ロボットアームのブラウザ操作画面

今回はPythonでロボットアームを操作していきたいと思います。ブラウザの操作画面はこのようになります。

前回は一個のサーボモータをブラウザから操作してみましたので、こちらをもとに今回のプログラムを作成します。

ブラウザのスライダーでRaspberryPiのサーボモータを動かす(WebIOPI利用)

上記の記事と同様にして5個全部のサーボモータを動かしていきたいと思います。

index.htmlを作成する

ブラウザに表示するindex.htmlは以下のようにしました。上から順にロボットアームのサーボを操作するスライダーを設置しています。

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 <meta name="viewport" content="width=device-width">
 <title>Controller</title>
 <script type="text/javascript" src="/webiopi.js"></script>
 <script type="text/javascript">
 </script>

 <style type="text/css">
 </style>
</head>

<body>
<div align="center">
<table>
<tr>
<td>
<table>
<tbody>
<tr>
<td>
<div class="slidecontainer">
  <input type="range" min="6" max="20" value="15" class="slider" id="myRange1">
</div>
</td>
</tr>
<tr>
<td>slider value:<p id="out1"></p></td>
</tr>
<tr>
<td>
<div class="slidecontainer">
  <input type="range" min="6" max="20" value="15" class="slider" id="myRange2">
</div>
</td>
</tr>
<tr>
<td>slider value:<p id="out2"></p></td>
</tr>
<tr>
<td>
<div class="slidecontainer">
  <input type="range" min="15" max="20" value="18" class="slider" id="myRange3">
</div>
</td>
</tr>
<tr>
<td>slider value:<p id="out3"></p></td>
</tr>
<tr>
<td>
<div class="slidecontainer">
  <input type="range" min="10" max="16" value="15" class="slider" id="myRange4">
</div>
</td>
</tr>
<tr>
<td>slider value:<p id="out4"></p></td>
</tr>
<tr>
<td>
<div class="slidecontainer">
  <input type="range" min="7" max="19" value="15" class="slider" id="myRange5">
</div>
</td>
</tr>
<tr>
<td>slider value:<p id="out5"></p></td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
</div>

<script>
 var commandID = 0;

 var slider1 = document.getElementById("myRange1");
 var slider2 = document.getElementById("myRange2");
 var slider3 = document.getElementById("myRange3");
 var slider4 = document.getElementById("myRange4");
 var slider5 = document.getElementById("myRange5");
 var output1 = document.getElementById("out1");
 var output2 = document.getElementById("out2");
 var output3 = document.getElementById("out3");
 var output4 = document.getElementById("out4");
 var output5 = document.getElementById("out5");
 output1.innerHTML = slider1.value; // Display the default slider value
 output2.innerHTML = slider2.value; // Display the default slider value
 output3.innerHTML = slider3.value; // Display the default slider value
 output4.innerHTML = slider4.value; // Display the default slider value
 output5.innerHTML = slider5.value; // Display the default slider value

// Update the current slider value (each time you drag the slider handle)
 slider1.oninput = function(){
 output1.innerHTML = slider1.value;
 var slidervalue = slider1.value;
 webiopi().callMacro("setHwPWMforPan1", slidervalue);
 };
 slider2.oninput = function(){
 output2.innerHTML = slider2.value;
 var slidervalue = slider2.value;
 webiopi().callMacro("setHwPWMforPan2", slidervalue);
 };
 slider3.oninput = function(){
 output3.innerHTML = slider3.value;
 var slidervalue = slider3.value;
 webiopi().callMacro("setHwPWMforPan3", slidervalue);
 };
 slider4.oninput = function(){
 output4.innerHTML = slider4.value;
 var slidervalue = slider4.value;
 webiopi().callMacro("setHwPWMforPan4", slidervalue);
 };
 slider5.oninput = function(){
 output5.innerHTML = slider5.value;
 var slidervalue = slider5.value;
 webiopi().callMacro("setHwPWMforPan5", slidervalue);
 };
</script>

</div>
</body>
</html>

簡単に解説していきます。ロボットアームのサーボモータは一つ一つブラウザのスライダーから操作できるようにしています。上から順番に操作できるようにしています。一番上のスライダーはロボット アームの開閉部分です。

スライダーにはレンジを設定してあるので、それぞれ「min」と「max」を設定していきます。レンジを超えてしまうとサーボモータの可動範囲を超えてしまい、壊れる可能性が非常に高くなるため気をつけるポイントとなります。

スライダーのID「id=”myRange1″」などは、ファイル下部のJavaScriptに紐付いています。こちらのほうでスライダーの値などを読み取っていきます。

 slider1.oninput = function(){
 output1.innerHTML = slider1.value;
 var slidervalue = slider1.value;
 webiopi().callMacro("setHwPWMforPan1", slidervalue);
 };

まず、スライダー1が操作されると上記が動き、output1にスライダー1の値が表示されます。またslidervalueにスライダーの値が代入され、「webiopi().callMacro(・・・)」の部分で引数としてPythonに渡されます。

script.py(wiringpi用)を作成する

次にPythonのほうはこのようになっています。GPIOはwiringpiで操作しています。インストール方法については上記記事をご参照ください。

import webiopi
import time
import wiringpi as GPIO

SV_PIN1 = 19
SV_PIN2 = 26
SV_PIN3 = 21
SV_PIN4 = 20
SV_PIN5 = 16

# SV_PAN  (Left)90 ... 0 ... -90(Right)

#val1 = [28,25,20,16,12,6,0]
val2 = [28,27,26,25,24,23,22,21,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0]

def getServoPanPWMvalue(val):
 return val2[val]
 
webiopi.setDebug()

def setup():
 webiopi.debug("Script with macros - Setup")
 GPIO.wiringPiSetupGpio()
 GPIO.pinMode(SV_PIN1,GPIO.OUTPUT)
 GPIO.softPwmCreate(SV_PIN1,0,50)
 GPIO.pinMode(SV_PIN2,GPIO.OUTPUT)
 GPIO.softPwmCreate(SV_PIN2,0,50)
 GPIO.pinMode(SV_PIN3,GPIO.OUTPUT)
 GPIO.softPwmCreate(SV_PIN3,0,50)
 GPIO.pinMode(SV_PIN4,GPIO.OUTPUT)
 GPIO.softPwmCreate(SV_PIN4,0,50)
 GPIO.pinMode(SV_PIN5,GPIO.OUTPUT)
 GPIO.softPwmCreate(SV_PIN5,0,50)

def loop():
 webiopi.sleep(5)

def destroy():
 webiopi.debug("Script with macros - Destroy")

@webiopi.macro
def setHwPWMforPan1(slidervalue):
 GPIO.softPwmWrite(SV_PIN1, getServoPanPWMvalue(int(slidervalue)))

@webiopi.macro
def setHwPWMforPan2(slidervalue):
 GPIO.softPwmWrite(SV_PIN2, getServoPanPWMvalue(int(slidervalue)))

@webiopi.macro
def setHwPWMforPan3(slidervalue):
 GPIO.softPwmWrite(SV_PIN3, getServoPanPWMvalue(int(slidervalue)))

@webiopi.macro
def setHwPWMforPan4(slidervalue):
 GPIO.softPwmWrite(SV_PIN4, getServoPanPWMvalue(int(slidervalue)))

@webiopi.macro
def setHwPWMforPan5(slidervalue):
 GPIO.softPwmWrite(SV_PIN5, getServoPanPWMvalue(int(slidervalue)))

こちらのほうも簡単に解説していきます。HTMLのほうのスライダーが操作されるとマクロが呼ばれます。

@webiopi.macro
def setHwPWMforPan1(slidervalue):
 GPIO.softPwmWrite(SV_PAN1, getServoPanPWMvalue(int(slidervalue)))

引数として渡されたスライダーの値は「getServoPanPWMvalue(int(slidervalue))」で関数に渡されます。

def getServoPanPWMvalue(val):
 return val2[val]

関数の中では配列「val2」からインデックスの場所の値がリターンされてきます。配列の中身はPWMで出力したい値をそのまま格納しています。

リターンされてきたら「GPIO.softPwmWrite」でサーボモータのほうに出力されていきます。スライダーを操作するとサーボモータも動くようになっていると思います。

もしPythonの中身を変更したときはWebIOPIを再起動してみてください。

回路を作成する

ページ下部の回路図を参照ください。ピンはPythonファイルで定義してあるものを使う必要があります。

初期版を動かしてみたときの様子

以上ですべての準備ができました。実際に動かしてみた様子がこちらです。

ガタガタしてしまうのはどうしようもありませんでした。いろいろと調べてみるとwiringpiソフトウェアPWMでは安定したPWMを出力できないのが原因らしいです。よって、これを解決するためにはハードウェアPWMサーボモータドライバを使うと安定したPWMを出力できるらしいです。

ハードウェアPWMを試してみたときの記事はこちらです。こちらのほうでは情報通り安定したPWMを出すことができました。

ブラウザのスライダーでRaspberryPiのサーボモータを動かす(ハードウェアPWM)

でも、このハードウェアPWMはRaspberryPiのGPIOでは2個までしか利用することができないので、5軸あるロボットアームでは足りません。残りの3個はソフトウェアPWMとなってしまいます。

そこで何かほかに良い方法はないか探してみたところ、pigpioというライブラリを利用するとハードウェアPWMを使わなくても高精度PWMを出力できることが分かりました。

次はpigpioを利用して改良版ロボットアームを作りましたのでこちらの説明をしていきます。

改良版ロボットアーム

上記まででロボットアームを動かせるまでにはなっていましたが、サーボがどうしてもガタガタしてしまいました。そこでpigpioというライブラリが高精度なPWM制御ができるということだったのでそちらのほうでプログラムを書き直してみました。

こちらのライブラリは評判通りの性能を発揮してくれました。ハードウェアPWMを使っているわけでもないのにピタッとサーボが止まってくれます。そう、これを目指していた。

index.htmlを作成する

改良版のindex.htmlはこちらです。上記まででは角度指定で動かしていなかったので細かく操作することができませんでしたが、今回は0~180度で操作できるようにしてみました。minとmaxはロボットアームの可動範囲に合わせて変更しています。

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta name="viewport" content="width=device-width">
    <title>Controller</title>
    <script type="text/javascript" src="/webiopi.js"></script>
    <script type="text/javascript">
    </script>
    <style type="text/css">
    </style>
  </head>
  <body>
    <div align="center">
      <table>
        <tr>
          <td>
            <table>
              <tbody>
                <tr>
                  <td>
                    <div class="slidecontainer">
                      <input type="range" min="100" max="160" value="130" class="slider" id="myRange1">
                    </div>
                  </td>
                  <td>slider value :</td>
                  <td id="out1"></td>
                </tr>
                <tr>
                  <td>
                    <div class="slidecontainer">
                      <input type="range" min="0" max="180" value="90" class="slider" id="myRange2">
                    </div>
                  </td>
                  <td>slider value :</td>
                  <td id="out2"></td>
                </tr>
                <tr>
                  <td>
                    <div class="slidecontainer">
                      <input type="range" min="0" max="70" value="50" class="slider" id="myRange3">
                    </div>
                  </td>
                  <td>slider value :</td>
                  <td id="out3"></td>
                </tr>
                <tr>
                  <td>
                    <div class="slidecontainer">
                      <input type="range" min="50" max="120" value="100" class="slider" id="myRange4">
                    </div>
                  </td>
                  <td>slider value :</td>
                  <td id="out4"></td>
                </tr>
                <tr>
                  <td>
                    <div class="slidecontainer">
                      <input type="range" min="0" max="180" value="90" class="slider" id="myRange5">
                    </div>
                  </td>
                  <td>slider value :</td>
                  <td id="out5"></td>
                </tr>
              </tbody>
            </table>
          </td>
        </tr>
      </table>
    </div>
<script>
 var slider1 = document.getElementById("myRange1");
 var slider2 = document.getElementById("myRange2");
 var slider3 = document.getElementById("myRange3");
 var slider4 = document.getElementById("myRange4");
 var slider5 = document.getElementById("myRange5");
 var output1 = document.getElementById("out1");
 var output2 = document.getElementById("out2");
 var output3 = document.getElementById("out3");
 var output4 = document.getElementById("out4");
 var output5 = document.getElementById("out5");

// Display the default slider value
 output1.innerHTML = slider1.value; 
 output2.innerHTML = slider2.value;
 output3.innerHTML = slider3.value;
 output4.innerHTML = slider4.value;
 output5.innerHTML = slider5.value;

// Update the current slider value (each time you drag the slider handle)
 slider1.oninput = function(){
 output1.innerHTML = slider1.value;
 var slidervalue = slider1.value;
 webiopi().callMacro("setServoPWM1", slidervalue);
 };
 slider2.oninput = function(){
 output2.innerHTML = slider2.value;
 var slidervalue = slider2.value;
 webiopi().callMacro("setServoPWM2", slidervalue);
 };
 slider3.oninput = function(){
 output3.innerHTML = slider3.value;
 var slidervalue = slider3.value;
 webiopi().callMacro("setServoPWM3", slidervalue);
 };
 slider4.oninput = function(){
 output4.innerHTML = slider4.value;
 var slidervalue = slider4.value;
 webiopi().callMacro("setServoPWM4", slidervalue);
 };
 slider5.oninput = function(){
 output5.innerHTML = slider5.value;
 var slidervalue = slider5.value;
 webiopi().callMacro("setServoPWM5", slidervalue);
 };

</script>
  </body>
</html>

script.py(pigpio用)作成する

pigpioで書き換えたPythonファイルはこちらです。

import webiopi
import time
import pigpio

GPIO = webiopi.GPIO
pi = pigpio.pi()

SV_1 = 4
SV_2 = 13
SV_3 = 26
SV_4 = 12
SV_5 = 16

def getServoPWMvalue(val):
 deg = int(1900/180*val) + 500
 return deg  # Return 500 ~ 2400

def setup():
 pi.set_mode(SV_1, pigpio.OUTPUT)
 pi.set_mode(SV_2, pigpio.OUTPUT)
 pi.set_mode(SV_3, pigpio.OUTPUT)
 pi.set_mode(SV_4, pigpio.OUTPUT)
 pi.set_mode(SV_5, pigpio.OUTPUT)

def loop():
 webiopi.sleep(5)

def destroy():
 webiopi.debug("Script with macros - Destroy")

@webiopi.macro
def setServoPWM1(val):
 pi.set_servo_pulsewidth(SV_1, getServoPWMvalue(int(val)))

@webiopi.macro
def setServoPWM2(val):
 pi.set_servo_pulsewidth(SV_2, getServoPWMvalue(int(val)))

@webiopi.macro
def setServoPWM3(val):
 pi.set_servo_pulsewidth(SV_3, getServoPWMvalue(int(val)))

@webiopi.macro
def setServoPWM4(val):
 pi.set_servo_pulsewidth(SV_4, getServoPWMvalue(int(val)))

@webiopi.macro
def setServoPWM5(val):
 pi.set_servo_pulsewidth(SV_5, getServoPWMvalue(int(val)))

こちらのほうもブラウザのスライダーが操作されたら値がPythonファイルに渡され、それをgetServoPWMvalueに引数として渡しています。この中ではスライダーの値が500~2400に変換されて返却されてきます。

注意事項として、ファイル名を「pigpio.py」というファイル名にしないこと。pigpioは外部からインポートしているため、ファイル名をライブラリ名と同じにしてしまうと動かなくなってしまいます。ファイル名は「script.py」などのようにして保存しておいたほうがよいです。

また、pigpioを使う場合は先にデーモンを起動しておく必要があります。こちらを先に実行しておきます。

sudo pigpiod

回路を変更する(GPIOの接続を変更)

サーボモータから出ている配線は3本あり、それぞれ以下のように接続します。

・黄色い線:RaspberryPiのGPIOへ

・赤い線:乾電池の+へ

・茶色い線:乾電池の-へ

改良版のHTMLではGPIOのピンを変更していますので、HTMLのファイルの内容に合わせて接続を変更しておきます。サーボモータの電源には単三乾電池4本(6V)を使用します。6VのACアダプタがあればそちらでもいいかもしれません。

電圧を測定する

サーボモータの電圧を測定してみました。約6Vぐらいの電圧となっており、ちゃんと動作してくれました。電圧が低すぎたり高すぎたりするとサーボモータが動いてくれない可能性があります。

ロボットアームのブラウザ操作画面

これでまたwebiopiを再起動してブラウザにアクセスするとこのような画面が表示されます。

改良版を動かしてみたときの様子

改良版のロボットアームを動かしてみたときのようすがこちらです。初期版と比較して振動が少なくなっていることが分かります。こちらのほうが断然良い感じになりました。

ロボットアームで何をするの?

今は以下のような感じでラジコンボートにロボットアームを取り付けてゴミ回収ができるようなものが作れればいいかなと思っています。

興味がありましたらこちらの記事も読んでみてください。

Raspberry Piで水面ゴミ収集ラジコンボートを作ってみる

 

以上、今回はRaspberryPiとサーボモータで5軸ロボットアームを動かす方法についてご紹介させていただきました。

IoTカテゴリの最新記事