Arduinoでサーボモータを制御する

Arduino

サーボモータについて

概要

(模型用の)サーボモータとは、位置(回転角)制御のできるモータの一種です。一般的なモータが自由に何回転もできるのと異なり、サーボモータの回転軸は180°程度の範囲でしか回転しません。その代わりに、電気的な信号によって『90°の位置まで回転する』『0°の位置に戻る』のように、位置(角度)を指定して回転させることが可能です。制御信号は簡単なアナログ波形のため、ホビー向けにコンピュータがまったく使われていなかった時代から、ラジコン模型自動車のステアリング操作や玩具ロボットの関節などに使用されてきました。

サーボモータの構造は、一般的な直流モータに較べるとだいぶ複雑です。実際の回転動作を発生するアクチュエータの他、減速ギア、位置(角度)を測定するセンサ、それらを制御する回路が1つのパッケージに封入されています。上の写真の製品ではケースが透明なためギアや制御回路が見えています。

制御方式

回路

多くのサーボモータには3つの端子があります。

端子用途
VCCサーボモータを動作させるための電源を供給します。
ある程度大きな電流を供給できる電源を接続します。
PWMサーボモータの回転角を指定する信号を入力します。
GNDサーボモータの電源および制御信号の信号グラウンドです。

※端子の名称はメーカーによって少し違うことがあります。

基本的な接続は下図のとおりです。VCC端子とGND端子をサーボモータ用の電源に接続し、PWM(制御信号)端子をArduinoなどの制御機器に接続します。

制御信号

多くの模型用小型サーボモータでは、回転角度の制御をPWM信号によって行います。PWMとはPulse Width Modulation(=パルス幅変調)の略で、その名の通りパルスの幅を変えることによって対象を制御するものです。

このようにパルスの発生する周期は一定で、パルスの幅を変化させた信号です。Arduino のアナログ出力では、パルス幅とパルス周期の比=デューティ比によって『実質的な電圧』を変化させています。サーボの場合はデューティ比によって回転角が変化します。

製作・実行

それでは、Arduino で小型サーボモータを制御する実験を行ってみます。

使用部品

今回使用した部品は以下の通りです。

※単価は2023年9月時点

種別型番数量備考参考リンク参考単価
サーボモータ SG901小型サーボモータ秋月電子通商550円
電池ボックス1単3×4本用秋月電子通商80円
電池スナップ1赤黒ジャンパ線つき秋月電子通商40円
電池単3型41.5×4=6.0Vの電源を作ります
ブレッドボード1小型のもので充分
ジャンパ線4
赤×1本、黒×2本、黄×1本
※手持ちのなかでサーボのリード線に近い色

サーボモータ

今回使用したサーボモータは、Tower Pro Pte Ltd のマイクロサーボ SG90です。

PWM周期20ms(50Hz)
パルス幅0.5ms ~ 2.4ms
制御角-90° ~ +90°
トルク1.8kgf・cm(4.8V のとき)
回転速度0.12s/60°(4.8V のとき)
制御信号電圧4.8 ~ 5V

パルス周期が20msなのに対して、角度を指定するパルス幅が0.5ms~2.4ms・デューティ比でいうと2.5%~12%と値域が狭いのがちょっと意外ですが、他メーカの製品でもパルス周期20ms程度/パルス幅最小1ms程度~最大2ms程度のものが多いようです。

データシートには電流量が書かれていないのですが、最大トルクと回転速度からすると機械的な負荷がかかった状態での回転中は平均300mA程度は流れているはずです。無負荷での実験ならもっと少ないとは思いますが、Arduino の5V出力では電流容量に不安がありますので、別に電源を用意した方がいいでしょう。

SG90 には最初から3本のリード線が接続されていて、とりはずすことができません。リード線の先はメス(ソケット)になっているので、Arduinoやブレッドボードとの接続にはジャンパピンやオス-オスのジャンパ線が別に必要です。

各リード線の意味は、

VCC
オレンジPWM
GND

です。

回路

先述の通り、サーボモータはArduinoの5V 端子(外部機器へ電源を供給する端子)には接続せず、別に電池を用意しました。サーボの動作電圧は3.3~6Vということなので、一般的な乾電池(1本1.5V)なら3~4本(4.5~6.0V)、リチウムイオン電池(1本1.2V)なら 3~5本(3.6~6.0V)を接続すればよいと思います。今回は単3型4本の電池ボックスを使用したので、乾電池なら6.0V、リチウムイオン電池なら4.8Vとなり、いずれの場合も動作します。

サーボの制御信号はPWMですが、Arduino のPWM出力とは周期などが違うため、analogWrite()関数は使えません。その代わり、出力端子にはPWM用ではない端子でも使えそうです。今回の例ではD3(IO3)端子を使います。

なお、GNDはArduinoとサーボモータ電源の電池とサーボモータのGND端子すべて共通です。Arduino のGND端子を接続するのを忘れないで下さい。

配線図

非常に簡単な回路ですが、電源の接続を間違えないようにして下さい。

プログラム1 パルス1つだけ

まず、以下のようなプログラムで試してみます。サーボモータに対して、パルスを1つだけ送るプログラムです。

#define PWM_PIN 3

void setup() {
  pinMode(PWM_PIN, OUTPUT);
}

void loop() {
  unsigned long start;
  start=micros();
  digitalWrite(PWM_PIN, HIGH);
  for(;;){ if(micros()-start>=500)break; }  // 0.5ms経過するまで待つ
  digitalWrite(PWM_PIN, LOW);
  delay(2000);

  start=micros();
  digitalWrite(PWM_PIN, HIGH);
  for(;;){ if(micros()-start>=2400)break; }  // 2.4ms経過するまで待つ
  digitalWrite(PWM_PIN, LOW);
  delay(2000);
}

プログラムの解説

初期設定
#define PWM_PIN 3

void setup() {
  pinMode(PWM_PIN, OUTPUT);
}

#define でステッピングモータを接続した端子を定数PWM_PINとして設定します。この例では3=D3端子です。

setup()関数内で、PWM_PINを出力に設定します。

時間を計測し、正確な幅と周期のパルスを作る

今回は、パルスの幅と周期をできるだけ正確に出力するため、プログラムは一工夫しました。

  unsigned long start;
  start=micros();
  digitalWrite(PWM_PIN, HIGH);
  for(;;){ if(micros()-start>=500)break; }  // 0.5ms経過するまで待つ
  digitalWrite(PWM_PIN, LOW);
  delay(2000);

micros()関数は、Arduinoが起動してから経過した時間をμs単位で返します。書式は以下の通りです。

unsigned long micros(void)

引数:

なし

返却値:

意味
unsigned longプログラムがきどうしてからの経過時間(単位はμs)

返却値の分解能は4μsで、返ってくる値は必ず4の倍数となります。この関数を使用してできるだけ正確に時間を計ります。手順は以下の通りです。

出力端子にHIGHを出力する直前の時間(μs)を変数startに記録します。

digitalWrite()関数で、PWM_PINにHIGHを出力します。

for(;;)(無限ループ)の中で繰り返しmicros()関数と変数startとの差を求め、その差が500以上となる=startを記録してから500μsが経過するのを待ちます。500以上となったらbreakで無限ループを終了します。

digitalWrite()関数で、PWM_PINにLOWを出力します。

これでパルス幅が500μs=0.5msのパルスが出力されます。delayMicroseconds()(引数で指定された時間だけ待つ、引数はμs単位)という関数もあるのですが、上記のプログラムの方が時間を正確に測れるようです。

このプログラムでは、パルス出力後にdelay()関数で2秒待っています。

  start=micros();
  digitalWrite(PWM_PIN, HIGH);
  for(;;){ if(micros()-start>=2400)break; }  // 2.4ms経過するまで待つ
  digitalWrite(PWM_PIN, LOW);
  delay(2000);

後半は、PWM_PINにHIGHを出力してからLOWを出力するまでの時間が2400μs=2.4msとなっている以外は前半と同じです。

つまりこれで、

パルス幅最小(0.5ms)を出力(0°を指定)

2秒待つ

パルス幅最大(2.4ms)を出力(180°を指定)

2秒待つ

という動作を繰り返すことになります。

なお、micors()の返値の型は、int型ではなく unsigned long型です。変数startもunsigned long で宣言しています。

Arduinoのint型は16ビットなので最大値は215-1=32,767です。unsigned intなら最大値は216-1=65,535です。1μs=10-6秒なので、μs単位でカウントするとint型ならわずか0.033秒、unsigned intでも0.066秒ほどでオーバーフローしてしまいます。一方、unsigned long型は符号なし32ビットなので4,294,967,295までカウントできます。

unsigned longでも4,294,967,295÷1,000,000≒4,294秒≒71.6分でオーバーフローしてしまいますが、今回の実験には充分でしょう。

※実はこのプログラムには『動作させたまま71.6分放置し、もしfor(;;)で時間待ちを続けている間にオーバーフローすると、そのままずっとHIGHが出続ける』というバグがあります。…どなたか試してみますか?

プログラム1の実行結果

実はこのプログラムではうまく動きませんでした。

2秒ごとに右へ左へと動いているのは間違いないのですが、あきらかに角度が足りません。どうやらパルス1個だけで指定の角度まで動いてくれるほど甘くは無いようです。

プログラム2 複数のパルスを連続で出力

どうも、サーボモータを動かすには指定の位置(角度)に移動(回転)しきるまで連続してパルスを与え続ける必要があるような気がしてきました。

データシートによると、このサーボモータの回転速度は0.12s/60°です。最大で180°回転しますので、その場合にかかる時間は0.12s×(180°÷60°)=0.36s=360msです。

パルスの周期は20msですから、端から端まで回転させるには360ms÷20ms=18個のパルスを連続して送る必要がありそうです。ちょっと余裕を見て、パルス幅最小(0°)の場合とパルス幅最大(180°)の場合、それぞれ20個のパルスを連続して送信するようにプログラムを改造します。

#define PWM_PIN 3

void setup() {
  pinMode(PWM_PIN, OUTPUT);
}

void loop() {
  int i;
  unsigned long start;

  for(i=0; i<20; i++){
    start=micros();
    digitalWrite(PWM_PIN, HIGH);
    for(;;){ if(micros()-start>=500)break; }
    digitalWrite(PWM_PIN, LOW);
    for(;;){ if(micros()-start>=20000)break; }
  }
  delay(2000);

  for(i=0; i<20; i++){
    start=micros();
    digitalWrite(PWM_PIN, HIGH);
    for(;;){ if(micros()-start>=2400)break; }
    digitalWrite(PWM_PIN, LOW);
    for(;;){ if(micros()-start>=20000)break; }
  }
  delay(2000);
}

プログラムの解説

20回繰り返す

同じ幅のパルスを20回連続して出力するため、for文を使用しています。これはC、C++、Java、PHPなど非常に多くの言語で共通の構文なので、いずれかの言語を学習した人なら説明不要でしょう。

ここでは、

for(i=0; i<20; i++){
  ループ内容
}

として20回繰り返しています。

パルス周期を20msにする

プログラム1との違いは、出力端子にLOWを出力した後に

for(;;){ if(micros()-start>=20000)break; }

としていることです。startはHIGHを出力する直前に設定したものですから、そこから測って20000μs=20msになるまで待っています。これでパルス周期が20msになります。

※厳密にはfor文の繰り返し処理にも時間がかかるので、周期は20msより少し長くなっていると思いますが、せいぜい数十μs程度の誤差だと思われますので気にしないことにします

プログラム2の実行結果

今度は上手く行きました。回転が180°にはちょっと足りないようですが、パルス幅をもう少し長くしても『カチカチ』という異音がサーボモータから聞こえてきたのでこれが回転の限界なのかもしれません。

プログラム3 ライブラリServo.hを使用する

実は自分で時間を計ってパルスを出力するような面倒なことをしなくても、簡単にサーボモータを制御できる Servo.h というライブラリがあります。Arduino IDEに標準で付いてくるライブラリなので、#include すればすぐに利用できます。

このライブラリを使用してプログラムを書き換えます。

#include <Servo.h>
#define PWM_PIN 3
Servo servo;

void setup() {
  servo.attach(PWM_PIN, 500, 2400);
}

void loop() {
  servo.write(0);
  delay(2000);
  servo.write(180);
  delay(2000);
}

プログラム2と同じ動作をするはずですが…

なんて簡単なプログラムで済むんだ!
ライブラリって便利!!

いや、動作原理を学ぶためにプログラム1、プログラム2を作ったことは無駄ではないですよ、うん。

プログラムの解説

サーボ制御ライブラリServo.h
#include <Servo.h>

サーボモータの制御にはPWMを使用します。しかしArduinoの analogWrite() によるPWM出力は周期が約2ms(490Hz)、サーボモータのPWMの周期は20ms(50Hz)とまったく違うため、信号の出力には少々工夫が必要です。

そこで用意されたのが、Servo.h に定義された関数群です。Arduino IDEをインストールするときに標準で一緒にインストールされているので、このように #include 文で指定するだけで使用することができます。

Servoクラス
Servo servo;

サーボモータの制御には、Servoクラスを使用します。ここではServoクラスの変数servoを定義しています。

PWM端子の設定
servo.attach(PWM_PIN, 500, 2400);

サーボモータの制御に使用する出力端子を設定するには、Servo::attach()関数を使用します。書式は以下の通りです。

uint8_t attach(int pin [, int min = 544, int max = 2400] )

引数:

名前意味
pinintサーボモータにPWMを出力するための端子番号
minintPWMのパルス幅の最小値(0°のときのパルス幅)。単位はμs
省略可で、デフォルト値は544μs=0.544ms
maxintPWMのパルス幅の最大値(180°のときのパルス幅)。単位はμs
省略可で、デフォルト値は2400μs=2.4ms

返却値:

意味
uint8_tサーボ番号(オブジェクト生成時に自動付番されている)

SG90のデータシートにはパルス幅が0.5ms~2.4msとあったので、このプログラムではminに500、maxに2400を与えました。

min、maxの指定が必要ない場合は、

servo.attach(PWMPIN);

と端子番号だけを引数とする書式もあります。

サーボモータに制御信号を出力するServo::write()

サーボモータに出力するPWM制御信号の値を設定するには、Servo::write()関数を使用します。書式は以下の通りです。

void write(int value)

引数:

名前意味
valueintサーボモータに指定する回転角。単位は度。指定可能な値は0~180
0より小さな値を指定すると0、180より大きな値を指定すると180として処理される

返却値:

なし

Servo.write()関数は引数としてサーボモータの位置(角度)を指定します。値が度単位なので直感的に判りやすいです。このプログラムでは

  servo.write(0);
  delay(2000);
  servo.write(180);
  delay(2000);

として、

0°を指定

2秒待つ

180°を指定

2秒待つ

という動作を繰り返すことになります。つまりプログラム2とまったく同じです。『必要数のPWMパルスを出す』部分が1行で済んでいるため、プログラムが簡潔になり、かつ可読性もよくなっています。

プログラム3の実行

プログラム2とまったく同じ動作です。回転角が180°に少し足りないのも同じなので、やはりこのサーボではこれが限界なのでしょう。

プログラム4 もっと細かく角度を設定する

Servo.hを利用して、もっと細かく角度を調節してみましょう。

#include <Servo.h>
#define PWM_PIN 3
Servo servo;

void setup() {
  servo.attach(PWM_PIN, 500,2400);
}

void loop() {
  int arg;
  for(arg=0; arg<=180; arg+=10){
    servo.write(arg);
    delay(500);
  }
  delay(2000);
  for(arg=180; arg>=0; arg-=10){
    servo.write(arg);
    delay(500);
  }
  delay(2000);
}

for文を利用して、10度刻みにサーボの位置(角度を)刻んでいくプログラムです。

プログラム4の実行

正確に一定の角度ずつ動いていく様子は小気味よいものです。

まとめ

・サーボはPWMのパルス幅によって回転角(位置)を制御する
・Arduinoでサーボを制御するには、標準のServo.hを利用すると簡単

コメント

タイトルとURLをコピーしました