Arduinoのライブラリでサーボモーターを操作する

組み込みエンジニア

こんにちは、ENGかぴです。

Arduino環境では標準ライブラリでサーボモーターを操作することができます。サーボモータはラジコンカーのステアリングやロボットによる弁の開閉部など様々な用途で使用されており0~180度の範囲で動作するものが多くあります。

本記事では標準ライブラリを使用してサーボモーターを回転させる方法をまとめています。サーボモーターはArduinoのスターターキットに付属されていたSG90を使用しています。

Arduino UNO(以下Arduinoとします。)を対象とします。Arduinoのライブラリを使用して動作確認したことをまとめています。

Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方

標準ライブラリでサーボモーターを操作する

SG90の仕様説明
引用:SG90のデータシート SG90の仕様説明

サーボモーターはパルス波形を与えてモータを動作させるものです。SG90のデータシートによるとPWM周期(キャリア周波数)が50Hzの波形でデューティーサイクル(Duty Cycle)が0.5~2.4msになるように調整することで回転する角度が決まります。

データシートにはOperating speed(動作速度)が0.12s/60 degreeと記載されています。60度回転させる場合はキャリア周波数20msでデューティーサイクルが1.13msのパルス波形を120ms経過するまで出力する必要があります。

Servoライブラリはタイマを使用して指定の角度になるようにパルスを調整する仕様になっています。以下では標準ライブラリを使ってサーボモーターの操作を行う手順を説明します。

デューティーサイクルの考え方

ServoのPWMの説明図
ServoのPWMの説明図

Servoライブラリはタイマを使ってPWMを生成します。キャリア周波数が20ms(50Hz)なるようにタイマを管理しますが、指定のタイマカウントより小さい場合はHIGHを出力し指定のタイマカウント以上になるとLOWを出力することでパルス波形を生成します。

デューティーサイクルが0.5ms時のタイマカウントをTCNT1(0度)とし、デューティーサイクルが最大となる2.4ms時のタイマカウントをTCNT3(180度)とします。

0度を指定するとタイマカウントがTCNT1でDO出力を反転させるため赤色のパルスが生成されます。180度を指定するとTCNT1よりも大きなタイマカウントでDO出力を反転させるので緑色のパルスのようにデューティーサイクルが長くなります。

任意のTCNT2(X度)のデューティーサイクルを考えます。0度を基準にすると180度まで回転させる場合のデューティーサイクルの増加分は2.4-0.5=1.9msになります。1度あたりのデューティーサイクルの増加分は1.9ms/180=0.0105msとなり、X度回転させる場合のデューティーサイクルの増加分は0.0105Xになります。

求めるデューティーサイクルは0度時のデューティーサイクルに増加分を加えたものになるため0.5+0.0105Xになります。

例)60度を指定した場合
デューティーサイクルの増加分は0.0105×60=0.63msになります。0度の時のデューティーサイクルに増加分を加えると0.5+0.63=1.13msがデューティーサイクルになります。

後述のライブラリの使用例で紹介しているwrite()関数を使用すると回転角を0~180で指定できるためデューティーサイクルは特に意識しなくても問題ありません。

すき間時間で資格をゲット【STUDYing(スタディング)】

ライブラリの準備と初期化

#include <Servo.h>

Servo sg90; //Servoクラスの型の変数を宣言

void setup() {
  sg90.attach(SERVO_PIN,500,2400); //サーボモーターの初期化
}

ライブラリを使用するためServo.hをインクルードします。サーボモーター専用のクラスの型の変数としてsg90(任意の宣言名でOK)を宣言してインスタンスを作成します。

attach()関数でサーボモーターの初期化を行います。第1引数にサーボモーターを操作するDO出力ピンを指定します。第2引数にサーボモーターの操作の最小値(usの値)を指定します。第3引数にサーボモーターの操作の最大値(usの値)を指定します。

第2引数と第3引数を指定しない場合はそれぞれ最小値が544・最大値が2400となります。sg90のデータシートではデューティーサイクルが0.5ms~2.4msとなっているので例では第2引数に500・第3引数に2400を指定しています。

ライブラリの使用例

#define WAIT_TIM 30 //300msウェイト

void loop() {

  switch (move){
  case 0: //角度指定
    sg90.write(90);
    //sg90.writeMicroseconds(1440);
    timcount = WAIT_TIM;
    ++move;
    break;
  case 1: //動作完了までのウェイト
    if(timcount == TIME_UP ){
      timcount = TIME_OFF;
      ++move;
    }
    break;
  }
}

サーボモーターの回転角度の指定と動作完了待ちをモード管理で行います。delay()関数を使用して動作完了までのウェイトを置く方法もありますが、delay()関数では割り込み処理以外が一時停止してしまいます。動作の遅延が問題となる場合はdelay()関数が使用できないことがあります。

例ではdelay()関数を使用せずにモードによる切り替えとタイマーを使用してウェイトを置く方法でサーボモーターを操作しています。

モード管理とタイマを使用することでdelay()関数によるソフトウェアウェイトを使用しなくてもサーボモーターを操作することができます。

モード0ではサーボモーターの回転角度をwrite()関数で指定します。引数に操作したい角度を指定します。例では90度を指定しています。

writeMicroseconds()関数で引数にus単位で指定する方法もあります。90度を指定する場合はデューティーサイクルが1440us(1.44ms)になるので引数に1440を指定します。

write()関数で指定しても内部でwriteMicroseconds()関数で変換しているためどちらの関数を使用しても結果は同じになります。回転角を0~180で指定できるwrite()関数をお勧めします。

角度を指定した後はサーボモーターが回転し終わるまでのウェイトを管理するタイマをセットしてモード1に進みます。

モード1でモード0で指定した角度になるまで待機します。サーボモーターのデータシートによるとOperating speed(動作速度)が0.12s/60 degreeなので90度回転させる場合は0.18s以上のウェイトを置く必要があります。例では余裕を見て300msのタイマを置いています。タイムアップすると次のモードに遷移させます。

以降は操作する角度に応じてモードを追加していきます。

テックジム-将来のためにプログラミングを学ぶ

動作確認

動作確認用の回路図
動作確認用の回路図

サーボモーターの電源は外部の電源からDC5Vを供給しています。サーボモーターのようにモータ系の負荷になると消費電流が大きいためマイコンのDO出力では過負荷になるためです。外部電源とArduinoのGNDは同一系統にするため接続します。

マイコンのDOの出力電流は最大でも10mA程度であるため過負荷の状態が続くとマイコンが発熱し故障する可能性があるため注意が必要です。

電源をONするとArduinoからのパルス波形によってサーボモーターが回転を始めます。今回は0度(-90度)→90度(0度)→180度(90度)→90度(0度)→0度(-90度)を繰り返すようにしました。

回転角度を指定した後に300ms毎にウェイトを置きましたが、この時間を長くすると一時停止しながら回転するため動作の確認がしやすくなります。逆に90度の時に最低必要時間である180msを下回ると指定する角度が回転が完了しない状態で次の回転指令が上書きされるため不安定な動作になります。

スポンサーリンク

ソースコード全体

以下のソースコードはコンパイルして動作確認をしております。コメントなど細かな部分で間違っていたりやライブラリの更新などにより動作しなくなったりする可能性があります。参考としてお使いいただければと思います。

#include <Servo.h>
#include <MsTimer2.h>

#define SERVO_PIN 7
#define TIME_UP 0
#define TIME_OFF -1
#define BASE_CNT 10 //10msがベースタイマとなる
#define WAIT_TIM 30 //300msウェイト

Servo sg90;
uint8_t move;
int angle;
int8_t cnt10ms;
int8_t timcount;
int8_t ram;
uint8_t chk;

void mainTimer(void);
void TimerCnt(void);

void setup() {
  
  Serial.begin(115200);
  sg90.attach(SERVO_PIN,500,2400);

  MsTimer2::set(1,TimerCnt); //1msごとに関数へ遷移
  MsTimer2::start();
}

void loop() {

  mainTimer();
 
  switch (move){
  case 0: //回転指令
    ram = rand() % 10;
    chk = 80 + ram;
    sg90.write(chk); //回転角度を指定
    Serial.println(chk);
    timcount = WAIT_TIM;
    ++move;
    break;
  case 1: //タイマウェイト
    if(timcount == TIME_UP ){
      //angle = sg90.read(); 現在どの角度が指定されているか
      timcount = TIME_OFF;
      ++move;
    }
    break;
  case 2: //回転指令
    ram = rand() % 10;
    chk = 170 + ram;
    sg90.write(chk); //回転角度を指定
    timcount = WAIT_TIM;
    ++move;
    break;
  case 3: //タイマウェイト
    if(timcount == TIME_UP ){
      timcount = TIME_OFF;
      ++move;
    }
    break;
  case 4: //回転指令
    ram = rand() % 10;
    chk = 80 + ram;
    sg90.write(chk); //回転角度を指定
    timcount = WAIT_TIM;
    ++move;
    break;
  case 5: //タイマウェイト
    if(timcount == TIME_UP ){
      timcount = TIME_OFF;
      ++move;
    }
    break;
  case 6: //回転指令
    ram = rand() % 10;
    chk = 0 + ram;
    sg90.write(chk); //回転角度を指定
    timcount = WAIT_TIM;
    ++move;
    break;
  case 7: //タイマウェイト
    if(timcount == TIME_UP ){
      timcount = TIME_OFF;
      ++move;
    }
    break;
  default:
    break;
  }

  if( move >= 8){
    move = 0;
  }
}
/* callback function add */
void TimerCnt(void){
  ++cnt10ms;
}
/* Timer Management function add */
void mainTimer(void){

  if( cnt10ms >= BASE_CNT ){
    cnt10ms -=BASE_CNT; //10msごとにここに遷移する
    if( timcount > TIME_UP ){
        timcount--;
    }
  }
}

サーボモーターの角度を指定する際に0~9のランダム値を加えた角度を指定しています。毎回同じ角度でなく変化を確認するのが目的です。

MsTimer2.hは標準ライブラリでは実装されていません。ライブラリマネージャで追加する必要があります。

関連リンク

Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。

Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方

Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方

ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方

Code Camp完全オンラインのプログラミング個人レッスン【無料体験】

最後まで、読んでいただきありがとうございました。

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