ゆるく楽しむプログラミング

サーボーモータで迷路をリモコン操作

micro:bitでやったサーボモーターで動かすリモコン迷路をArduinoでやってみます。
サーボモータの操作はライブラリを使えば簡単ですが、それではサーボモータの仕組みが分からないので、ライブラリなしで直接操作してみます。

使用しているのは
ArduinoUNO互換機
Seeed studio Grove Beginner Kit for Arduino
Grove接続ユニットが使えて便利

操作は、I2C接続のジョイスティック
M5Stack Joystick Unit


サーボモータ・ライブラリを使う場合

Servoライブラリを使う場合は特に悩む必要もなく簡単に使えます。
ArduinoIDEのスケッチ例にあるサーボモータのサンプルをチョット手直し。

-- LIST : Servoライブラリを使用 --
※ <>を全角<>に置き換えています。コピペの際は注意してください。
#include <Servo.h>
#include <Wire.h>
#define STICK_ADDRESS 0x52

int posX = 0;
int posY = 0;
byte x = 0 ;
byte y = 0 ;
byte b = 0 ;
int xp = 0 ;
int yp = 0 ;

Servo servoX;  // servo object to control servo-X
Servo servoY;  // servo object to control servo-Y

void setup() {
  Wire.begin(); // マスタとしてI2Cバスに接続する
  servoX.attach(9); 	// X軸用のサーボを9番ピンに割り当て
  servoY.attach(10);	// Y軸用のサーボを10番ピンに割り当て
}

void loop() {
  // Joystickの読み取り
  // 3Byteでx軸(0-255),y軸(0-255),ボタン(0-1)を返す
  Wire.requestFrom(STICK_ADDRESS,3);
  while(Wire.available()) {
    x = Wire.read() ;
    y = Wire.read() ;
    b = Wire.read() ;
  }

  // 入力に変化があった時だけサーボの回転位置を再計算
  // 細かくやるとプルプル震えるので大雑把に16段階にする
  if (xp != x / 16 || yp != y / 16) {
    xp = x / 16 ;
    yp = y / 16 ;
    posX = 11.25 * xp ; // 180度を16段階で表現 
    posY = 11.25 * yp ; // 精密操作する訳じゃないので誤差は気にしない
  }

  servoX.write(posX); X軸用サーボ操作
  servoY.write(posY); Y軸用サーボ操作
  delay(15);  // waits 15ms for the servo to reach the position
}


サーボモータの仕様

サーボモータ (工作用としてメジャーな) SG90の仕様を見てみると

20ms秒間隔(周波数50Hz)で
1m秒~2m秒(デューティー比 5~10)
のHIGE信号の長さで
サーボモータの角度を
制御しています。


PWM出力を使えば良いような気がしますが、
ArduinoUNOのPWMは実測で542.299Hzありました。

これでは使えないので周波数を落とす必要があります。
ATMega328のタイマーレジスタを操作すれば
PWMの周波数を変更する事は可能ですが…

・タイマーの使用には制限がある
・タイマー(割り込み)の実装はマイコンチップ依存
・ServoライブラリはPWM非対応のデジタルピンも操作できる
のでPWMで操作しているのではないようです。

マイコンチップ依存だとArduinoIDEを使ていても
SeeeduinoXIAOやRaspberryPI Pico、M5STACKはもちろん
ATTiny85やArduinoLeonaldでも個別に作らないとダメで
ゆるくない手段です。

ちょっと面倒なので、PWM(タイマー)を使う方法はナシかな。


タイマーを操作する方法は面倒なのでmicros()での経過時間計測と
delayMicrosecond()を使ったLチカの ゆるい方法で操作してみます。

20ミリ秒経過毎に
サーボ1をHIGHにしてマイクロ秒まってLOWに
サーボ2をHIGHにしてマイクロ秒まってLOWに
の繰り返しで、なんちゃってPWMが出来上がります。
大雑把ですがCPUの違いによる影響をほぼ受けないので、ArduinoIDEが使えるマイコンボードで共通に使えます。
※ArduinoUNOとRaspberryPi Picoでは同じコードで同じように動作しました。

-- LIST :ゆるく行うサーボモーター操作 --
※ <>を全角<>に置き換えています。コピペの際は注意してください。
#include <Wire.h>
#define STICK_ADDRESS 0x52
#define ServoX 9
#define ServoY 10
int rX = 0;
int rY = 0;
byte x = 0 ;
byte y = 0 ;
byte b = 0 ;
int xp = 0 ;
int yp = 0 ;
long mts = 0 ;
long mtp = 0 ;

void setup() {
  Wire.begin(); // マスタとしてI2Cバスに接続する

  pinMode(ServoX, OUTPUT);
  pinMode(ServoY, OUTPUT);

  mts = micros() ;  // 経過時間計測基準点
}

void loop() {
  // Joystickの読み取り
  // 3Byteでx軸(0-255),y軸(0-255),ボタン(0-1)を返す
  Wire.requestFrom(STICK_ADDRESS,3);
  while(Wire.available()) {
    x = Wire.read() ;
    y = Wire.read() ;
    b = Wire.read() ;
  }

  // 入力に変化があった時だけサーボの回転位置を再計算
  // 細かくやるとプルプル震えるので大雑把に16段階にする
  if (xp != x / 16 || yp != y / 16) {
    xp = x / 16 ;
    yp = y / 16 ;
    rX = 125 * xp + 500 ;
    rY = 125 * yp + 500 ;
  }

  mtp = micros() ;  // 経過時間
  if (mtp - mts >= 20000) {
    // 20m秒経過毎にLチカ
    mts = mtp ; // 経過時間再設定

    // X軸サーボのポジション設定
    digitalWrite(ServoX, HIGH );
    delayMicroseconds(rX) ;
    digitalWrite(ServoX, LOW );
  
    // Y軸サーボのポジション設定
    digitalWrite(ServoY, HIGH );
    delayMicroseconds(rY) ;
    digitalWrite(ServoY, LOW );
  }
}

オシロスコープで見ると、サーボX、サーボYともに50Hzで、HIGH信号の長さだけズレて動作しているのが分かります。
Servoライブラリを使用した場合も同じ波形になるので、同じような方法で実現しているのだと思います。


解説動画