サーボーモータで迷路をリモコン操作
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ライブラリを使用した場合も同じ波形になるので、同じような方法で実現しているのだと思います。