すでに「Arduino のマルチタスク」でサーボモータを動かしているのですが、ちょっと基本的なことを試してみたいと思います。
どうすれば動く?
えーと、ググってください m(_ _;)m
まぁ簡単に言うと、スケッチ例「servo」の中の「knob」とか「sweep」とかです。ググると解説されているサイトがたくさん出てきます。
でも、いまひとつ肝心なことがわからない meyon さんでありました。
実際に動かしてみよう
わからないなら、実際に動かして試してみましょう。
回路図です。
スケッチ例の「knob」も「sweep」も、これで動きます。
knob では、ボリューム VR1 を回すと、それに応じてサーボモータの角度が制御されます。
sweep は、0~180 度を行ったり来たりします。
ボリュームまわりですが、電源側は Arduino の 5V 出力を利用し、GND 側も Arduino の GND につないでいます。
これは、制御用電源をサーボモータの駆動用電源と分離することで、ノイズの影響を軽減するためです。
もちろん、サーボモータの 5V 電源は別の電源回路から供給します。1 台あたり 300~500mA は見込んでおきましょう。
ただし、この程度ではまだノイズの影響を受けます。実際に試してみるとわかると思いますが、制御用の電源も信号もふらつき、サーボモータがジリジリと小さく動いてしまいます。
対策についてはもっと調べてみるか、研究してみる必要がありますね。ロボットとか作っている人はどうしているんでしょう? 今後の課題。
パルスを 1 回だけ与えてみる
さて、どうすればサーボモータが動くのか、やってみましょう。
SG90 のデータシートによれば、周期 20ms で、デューティーサイクル 0.5~2.4ms のパルスを与えれば良いとなっています。
そこで、こんなスケッチを試してみました。
- void setup() {
- pinMode(9, OUTPUT);
- int pos = 2400;
- digitalWrite(9, HIGH);
- delayMicroseconds(pos);
- digitalWrite(9, LOW);
- }
- void loop() {
- }
4 行目の値がデューティーサイクル。単位は μs ですので、2400 ならば 2.4ms です。その長さのパルスを 1 回だけ出力させます。
結果、サーボモータは 180 度の位置へ動きました。
次にデューティーサイクルを 1450μs に変更してみます。するとサーボモータは 90 度の位置へ戻ります。さらにデューティーサイクルを 500μs にすると、サーボモータは 0 度の位置となりました。
つまり、「サーボモータに 0.5~2.4ms 幅のパルスを 1 回与えてやると、そのパルス幅に応じた位置まで動く」ってこと。
俺が知りたかったことは、これです。
たったこれだけのことですが、ググってみた限りでは知り得なかった。
周期 20ms ってなに?
もっとも、パルスを 1 回与えるだけでは使い物にはなりません。
止まったサーボモータに荷重をかけると、サーボモータは回転してしまいます。ブレーキもありませんし、ウォームギヤを使っているわけでもありませんから当然ですね。
そこで、周期的にパルスを与えることで、その位置に止めておくことができるようになります。もし負荷によってサーボモータが回転したら、パルスによって元に戻され、あたかもその位置に止まっているようになるわけです。
では、パルス周期はなぜ 20ms なのでしょう?
こんなスケッチを試してみました。
- void setup() {
- pinMode(9, OUTPUT);
- }
- void loop() {
- static int pos = 500;
- static int increment = 20;
- digitalWrite(9, HIGH);
- delayMicroseconds(pos);
- digitalWrite(9, LOW);
- delayMicroseconds(10000 - pos);
- delay(10);
- // delay(20);
- pos += increment;
- if(500 >= pos || 2400 <= pos) increment = -increment;
- }
難しくはないと思いますが。
09〜13 行はデューティサイクル pos のパルスを 20ms 周期で発生させています。
16~17 行目は、180 度動いたら反転して元へ戻る処理。
さて、ここで 14 行のコメントアウトを外したらどうなるでしょう。周期が 40ms に延びますね。サーボモータの動きが半分の速さになりますが、特に問題なく動きます。速度が半分になったのは、1 周期ごとの増分が見かけ上半分になったためですから、increment を倍の 40 にすれば元の速さに戻ります。
では、13~14 行をコメントアウトして、increment を 10 にするとどうでしょうか。周期は 10ms になりますが、サーボモータは変わりなく動きます。
つまり、周期 20ms はかなりラフでいいってことのようです。俺にはわからんのですが、諸々の性能上の理由で、デューティー比はこれぐらいにするのがいいですよ、って値。たぶん、そーゆーこと。
ボリュームを追いかけるサーボモータ
サーボモータを動かす基本がわかりました。基本をわかった上で、でも実際に利用するときはライブラリ Servo.h を使うのが簡単で、便利です (^_^;)
ボリュームを動かすとその位置に応じてサーボモータが動くスケッチを作りましょう。基本はスケッチ例の knob ですが、knob は delay() を使っています。俺たちは Ditch the delay() でいきますよ。
- #include <Servo.h>
- Servo myservo;
- byte potentiometerPin = 0;
- byte servoPin = 9;
- void setup() {
- myservo.attach(servoPin);
- }
- void loop() {
- static int value = 0;
- static byte interval = 15;
- static unsigned long previousMillis = millis();
- if(interval < millis() - previousMillis) {
- value = analogRead(potentiometerPin);
- value = map(value, 0, 1023, 0, 180);
- myservo.write(value);
- previousMillis = millis();
- }
- }
更新時刻が過ぎたら (17行) 、ボリュームの位置を読み取り (18行) 、サーボモータへ出力 (20行) 、更新時刻を記憶 (21行) する。ステートマシンです (^_^;)
クラスの勉強したんだからね
自信ないけど、インスタンス化してみましょう。
- #include <Servo.h>
- class Follow {
- Servo myservo;
- byte potentiometerPin;
- int value;
- byte interval;
- unsigned long previousMillis;
- public:
- Follow(byte pin) {
- potentiometerPin = pin;
- value = 0;
- interval = 15;
- previousMillis = millis();
- }
- void Attach(byte servoPin) {
- myservo.attach(servoPin);
- }
- void Update() {
- if(interval < millis() - previousMillis) {
- value = analogRead(potentiometerPin);
- value = map(value, 0, 1023, 0, 180);
- myservo.write(value);
- previousMillis = millis();
- }
- }
- };
- Follow follow1(0);
- void setup() {
- follow1.Attach(9);
- }
- void loop() {
- follow1.Update();
- }
どうだ。これでボリュームとサーボモータのセットをいくつでも増やせますぞ。
あ、そろそろサーボモータを追加注文しないといかんな (^_^;)