Arduinoでサーボモーターを動かす|パルス幅での角度指定、電源の分け方と震えるときの直し方
attach・write・writeMicrosecondsの役割を分ける
attachで端子と変換範囲を決めます
Servoライブラリは、サーボごとのオブジェクトを作り、attachで信号端子を登録して使います。通常の角度指定はwrite、パルス幅を直接指定する場合はwriteMicrosecondsです。後者の単位はマイクロ秒で、1500なら1.5msを表します。角度の値と時間の値を同じ変数に混ぜない方が、後から読み直すときに迷いません。
| 書き方の例 | 指定していること | 確かめる点 |
|---|---|---|
servo.attach(9) |
9番端子をライブラリの標準範囲で使用 | 標準範囲が製品に合うか |
servo.attach(9, 1000, 2000) |
角度の0〜180を指定の時間幅へ対応付ける | 時間幅の単位はµs |
servo.write(90) |
設定した変換範囲の中央付近を指令 | 実測の90度とは限らない |
servo.writeMicroseconds(1500) |
1500µsのパルスを指令 | 製品の許容範囲内にする |
servo.detach() |
ライブラリからのパルス出力を止める | 電源遮断や保持の保証ではない |
引数が端子だけのattachでは、ライブラリが持つ初期の最小・最大パルス幅が使われます。この初期値を、製品が保証する可動域だと考えてはいけません。公式Servoライブラリの定義では、パルス幅の初期値や更新周期、各関数の役割を確認できます。使用するバージョンやボードでの対応も併せて確認します。
三つのコード例は、1000〜2000µsへの対応を製品の資料で確認できた場合の設定です。送る指令は中央付近に絞りますが、機構との干渉には注意が必要です。サーボ単体を固定し、動く範囲を空けて試します。
指令の更新と、パルスの繰り返しは別です
writeを一度呼ぶと、ライブラリは最後に指定された値でパルスを繰り返します。loopの中で毎回同じwriteを呼ぶ必要はありません。delayでプログラムの主な処理が待っている間も、通常はタイマーと割り込みによって信号が出ます。反対に、長時間割り込みを止める処理などがあると、パルスの時間が乱れる場合があります。
ライブラリはタイマーという時間を数える回路を使うため、同じタイマーを使う機能と競合することがあります。別の端子のLED調光が変になった場合も、信号線を挿し直す前にタイマーの使用条件を調べます。使えなくなるPWM端子などはボードで異なるため、ここでは一律の端子番号を決めず、利用環境の資料を確認してください。
まずは中央付近の三つの角度へ順に動かす
最初のスケッチは、位置と待ち時間を固定します
最初から全可動域を往復させず、指令値75、90、105の順に切り替えます。この数値はライブラリ上の角度で、実際のホーンの角度を測った値ではありません。端子9を使うのはコード内の選択で、PWM端子であることを利用しているわけではありません。外部電源と共通GNDは前のページの構成を使います。

#include <Servo.h>
Servo servo;
const int SERVO_PIN = 9;
const int PULSE_MIN_US = 1000;
const int PULSE_MAX_US = 2000;
void setup() {
servo.attach(SERVO_PIN, PULSE_MIN_US, PULSE_MAX_US);
servo.write(90);
delay(1000);
}
void loop() {
const int targets[] = {75, 90, 105};
for (int i = 0; i < 3; ++i) {
servo.write(targets[i]);
delay(1000);
}
}
setupは起動時に一度だけ、loopは繰り返し実行されます。writeは到着を待たずに処理を返すため、直後に待ち時間を置き、動きと停止後の様子を観察します。すぐ次の値を書くと、到着前に指令が変わります。delayを増やしても移動中の速さは変わりません。
この設定で時間幅を明示したい場合は、write(90)をwriteMicroseconds(1500)へ置き換えます。二つを続けて書くと後の値へ更新されるため、比較には一方だけを使います。
通電から観察までを同じ順番にします
スケッチを書き込むあいだは、サーボの電源を切っておきます。サーボの電源を入れないまま信号だけを加えてよいかは、製品の資料に従います。ホーンが回り始めても机や配線に当たらない配置を作ってから、確認を進めます。
- Servoライブラリを利用できる状態にし、使用ボードと端子の設定を確認します。
- 電源を切ったまま、サーボの極性とGNDの共通接続を照合します。
- サーボ本体を適切な固定具で留め、出力軸の周囲を空けます。
- スケッチを書き込み、製品に合う手順で給電して、短時間だけ動きを見ます。
- 三つの位置を行き来するか、止まった後に強い音や震えがないかを記録します。
- 観察を終えたら電源を切り、配線や取り付けを変えます。
回転方向が思った向きと逆でも、電源線を逆にしてはいけません。指令の並びや機構の向きを見直します。最初の位置だけ急に動くこともあるので、起動前の軸位置を手で合わせようとせず、どの位置から動いても当たらない空間を用意してください。

ゆっくり動かすには、目標位置を少しずつ変える
待つ時間だけでは移動の速さは変わりません
90から120へ一度に指令を変えると、サーボは内部の制御に従って目標へ向かいます。ゆっくり動かしたいときは、91、92というように途中の目標を順に渡します。ただし、指令の刻みを細かくしても、モーターやギアの特性を超えた滑らかさや位置精度が得られるわけではありません。
次の例は、指令値60〜120の範囲を1ずつ、20ms以上の間隔で更新します。millisは起動後の経過時間をミリ秒で返す関数です。前の更新から一定時間が過ぎたかを調べるため、長いdelayでloop全体を止めずに、ほかの入力処理も追加できます。
#include <Servo.h>
Servo servo;
const int SERVO_PIN = 9;
const int MIN_ANGLE = 60;
const int MAX_ANGLE = 120;
const unsigned long STEP_INTERVAL_MS = 20;
int angle = 90;
int direction = 1;
unsigned long lastStepMs = 0;
void setup() {
servo.attach(SERVO_PIN, 1000, 2000);
servo.write(angle);
lastStepMs = millis();
}
void loop() {
const unsigned long now = millis();
if (now - lastStepMs < STEP_INTERVAL_MS) {
return;
}
lastStepMs = now;
angle += direction;
if (angle >= MAX_ANGLE) {
angle = MAX_ANGLE;
direction = -1;
} else if (angle <= MIN_ANGLE) {
angle = MIN_ANGLE;
direction = 1;
}
servo.write(angle);
}
この20msは、プログラムが目標を変更する間隔です。ライブラリが作るパルスの周期とは別に管理されています。間隔を長くすると目標の移り変わりは遅くなりますが、一段ずつ動く感じが目立つ場合もあります。短くしても、サーボが指令に追い付けなければ、意図した経路を再現できません。
時間の変数をunsigned longにし、前回の時刻との差で比較する形は、millisの値が一周する場合にも使えます。この例では処理が遅れても、不足分を一気に送らず次の一段へ進みます。
端で折り返すときの負荷も見ます
一定の刻みで往復させる例では、折り返しで方向が急に変わります。軽いホーンでは気にならなくても、長い腕では慣性による負荷が増えます。まず動かす範囲を狭くし、腕を短くして、機構が引っ掛からないことを確かめます。より滑らかにするなら、始めと終わりで目標の変化量を小さくする工夫を加えます。
ボタンや表示を追加する場合も長い待ち時間を避けます。ただし、millisを使うだけで信号の乱れは解消しません。ライブラリ追加後に震えが出たなら、機能を一つずつ外してタイマーや割り込みの競合を確かめます。










