Seeeduino XIAOでサーボモーターを操作する

組み込みエンジニア

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

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

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

Seeeduino XIAO用拡張ボード(秋月電子)を使用しています。Seeeduino XIAOを使って動作確認を行ったことを下記リンクにまとめています。

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

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

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

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

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

データシートのOperating voltage(動作電圧)は3.3V~6Vとなっています。パルス波形の例では4.8V~5Vとなっていますが3.3Vのパルスを与えても動作させることができます。

サーボモーターの電源電圧が3.3Vの場合で5Vのパルスを与えるのは入力電圧が電源電圧を超えることになるため注意が必要です。

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

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

#include <Servo.h>

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

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

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

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

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

最小値をデータシートのとおり500(0.5ms)にすると回転角がギリギリな状態になりジリジリ音とを伴って過回転しようとするためデフォルトもしくはギリギリの回転角にならないように調整が必要です。

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

ライブラリの使用例

#define WAIT_TIM 50 //500msウェイト

void loop() {

  switch (move){
  case 0: //回転指令
    if(timcount == TIME_UP ){
      u8x8.setCursor(0, 6); //カーソルを6列に移す
      u8x8.clearLine(6); //6列目をクリア
      chk = 60;
      u8x8.print("  --> ");
      u8x8.print(chk);
      u8x8.print("deg");
      sg90.write(chk); //回転角を指定
      timcount = WAIT_TIM;
      move = 1;
    }
    break;
  case 1:
    if(timcount == TIME_UP ){
      u8x8.setCursor(0, 6);
      u8x8.clearLine(6);
      chk = 180;
      u8x8.print("  --> ");
      u8x8.print(chk);
      u8x8.print("deg");
      sg90.write(chk); //回転角を指定
      timcount = WAIT_TIM;
      move = 2;
    }
    break;
}

サーボモーターの回転角の指定をモード管理で行います。回転角の指定の待機を管理するタイマ(timcount)がタイムアップすると回転角の指定を行いtimcountにウェイトさせる時間を指定します。回転するパターンを応じて追加していきます。

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

サーボモーターが回転し終えるまでの期間以上のウェイトをtimecountをセットしてモード1に遷移します。モード1に遷移した後はタイムアップするまで待機します。以降は追加したモードにおいて同様の処理を行います。

SG90のデータシートによるとOperating speed(動作速度)が0.12s/60 degreeなので60度回転させる場合は120ms以上のウェイトを置く必要があります。例では500msのタイマを置いています。

各モードで指定した回転角を表示するためにu8x8.print()で文字列と指定している回転角を書き込んでOLEDに表示しています。

拡張基板のOLEDに文字を表示

OLEDに文字を表示するためにライブラリを追加して動作確認したことを下記記事にまとめています。

Seeeduino XIAOの拡張ボードのOLEDを使用する

拡張ボードはOLEDように4ピンと5ピンを使用するためサーボモーターを操作するDOに4と5ピンを使用することができません。使用するとOLEDの表示がバグるなど不安定な動作になります。

本記事では動作モードの切り替えと回転角の表示をOLEDで表示するために使用しています。

動作確認

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

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

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

SW1を押すとサーボモーターを操作のパターンを変更します。モード1では0度(-90度)→90度(0度)→180度(90度)→0度(-90度)を繰り返します。モード2では0度(-90度)→60度(-30度)→0度(-90度)を繰り返します。

動作確認(モードの切り替え)
動作確認(モードの切り替え)

SW1を押すとモードが切り替わり回転角が変更されているのを確認しました。回転角を指定した後に500ms毎にウェイトを置きましたが、サーボモーターが回転角に達するまでの時間以上であれば任意で調整することができます。

回転角に至るまでの時間以下で次の回転角を指定すると回転角の指定が上書きされるため動作が不安定になるため注意が必要です。

スポンサーリンク

ソースコード全体

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

#include <Servo.h>
#include <TimerTC3.h>
#include <U8x8lib.h>

#define SERVO_PIN 1
#define PIN_DI1 0
#define DIFILT_MAX 4
#define TIM_DIFILT 1
#define TIME_UP 0
#define TIME_OFF -1
#define BASE_CNT 10 //10msがベースタイマとなる
#define WAIT_TIM 50 //500msウェイト

typedef struct DIFILT{
  uint8_t wp;
  uint8_t buf[DIFILT_MAX];
  uint8_t di;
};

Servo sg90;
uint8_t move;
int angle;
int8_t cnt10ms;
int8_t timcount;
int16_t timDifilter;
int8_t ram;
uint8_t chk;
bool  btnkeep;
U8X8_SSD1306_128X64_NONAME_SW_I2C u8x8( SCL, SDA, U8X8_PIN_NONE);
DIFILT diData;
bool flg;

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

void setup() {
  uint8_t i;

  u8x8.begin();
  u8x8.setFlipMode(3);   // set number from 1 to 3
  u8x8.setFont(u8x8_font_5x8_r);
  u8x8.setCursor(0, 0);
  u8x8.print("Seeeduino OLED");
  u8x8.setCursor(0, 1);
  u8x8.print("       Ver1.00");
  u8x8.setCursor(0, 3);
  u8x8.print("Select Mode");
  u8x8.setCursor(0, 4);
  u8x8.print("  --> Mode1");
  u8x8.setCursor(0, 5);
  u8x8.print("Angle");
  u8x8.setCursor(0, 6);
  u8x8.print("");  

  pinMode(PIN_DI1,INPUT_PULLDOWN);
  sg90.attach(SERVO_PIN,510,2400);
  TimerTc3.initialize(1000);
  TimerTc3.attachInterrupt(TimerCnt);

  i=0;
  while( i < 10){
    mainTimer();
    DiFilter();
    delay(10);
    i++;
  }
}

void loop() {

  mainTimer();
  DiFilter();

  if( diData.di == 1 ){
    if( flg == false ){
      flg = true;
      u8x8.setCursor(0, 4);
      u8x8.clearLine(4);

      if( btnkeep ){
        btnkeep = false;
        u8x8.print("  --> Mode1");
      }
      else{
        btnkeep = true;
        u8x8.print("  --> Mode2");
      }
    }
  }
  else{
    flg = false;
  }
    
  switch (move){
  case 0: //回転指令
    if(timcount == TIME_UP ){
      u8x8.setCursor(0, 6);
      u8x8.clearLine(6);

      if(btnkeep){
        chk = 60;
      }
      else{
        chk = 90;
      }
      u8x8.print("  --> ");
      u8x8.print(chk);
      u8x8.print("deg");
      sg90.write(chk); //回転角を指定
      timcount = WAIT_TIM;
      move = 1;
    }
    break;
  case 1:
    if(timcount == TIME_UP ){
      u8x8.setCursor(0, 6);
      u8x8.clearLine(6);

      if(btnkeep){
        chk = 0;
        move = 0;
      }
      else{
        chk = 180;
        move = 2;
      }
      u8x8.print("  --> ");
      u8x8.print(chk);
      u8x8.print("deg");
      sg90.write(chk); //回転角を指定
      timcount = WAIT_TIM;
    }
    break;
  case 2: //回転指令
    if(timcount == TIME_UP ){
      chk = 0;
      move = 0;
      u8x8.setCursor(0, 6);
      u8x8.clearLine(6);
      u8x8.print("  --> ");
      u8x8.print(chk);
      u8x8.print("deg");
      sg90.write(chk); //回転角を指定
      timcount = WAIT_TIM;
    }
    break;
  }
}
/* callback function add */
void TimerCnt(void){
  ++cnt10ms;
}
/* Timer Management function add */
void mainTimer(){

  if( cnt10ms >= BASE_CNT ){
    cnt10ms -=BASE_CNT;
    //10msごとにここに遷移する
    if( timcount > TIME_UP ){
      timcount--;
    }
    if( timDifilter > TIME_UP ){
      timDifilter--;
    }
  }
}
/* DIフィルタ */
void DiFilter(void){
  bool boo = true;
  uint8_t i;

  if( timDifilter == TIME_UP ){
    timDifilter = TIM_DIFILT;
    diData.buf[diData.wp] = digitalRead(PIN_DI1);

    for( i=1; i < sizeof(diData.buf);i++){
      if( diData.buf[i - 1] != diData.buf[i]){
        boo = false;
      }
    }

    if(boo){ //データがすべて一致なので採用する
      diData.di = diData.buf[0];
    }
    if( ++diData.wp >= sizeof(diData.buf)){
      diData.wp = 0;
    }
  }
}

関連リンク

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

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

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

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

GEEKJOB-未経験からITエンジニアに【オンライン無料体験】

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

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