Arduino(アルディーノ)を使った電子工作の醍醐味の一つに、「自動制御」があります。今回は、安価で入手しやすい「超音波距離センサー(HC-SR04)」と「サーボモーター(GeekServo 9Gなど)」を使い、障害物を検知して自動で舵を切る(操舵する)システムの構築方法を、ステップバイステップで詳しく解説します。
最初は基本的な制御から始め、最終的には障害物との距離に応じて滑らかにハンドル操作を行う、より高度な制御を目指します。
ステップ1: 【基本】片側のセンサーで障害物を検知する
まずは、超音波センサーを1つだけ使い、「右側に障害物があったら、右に舵を切る」という基本的な動作を作ります。
1. 使用する部品とピン配置
使用する部品は以下の通りです。
- Arduino (Uno, Nano など)
- 超音波センサー (HC-SR04)
- サーボモーター (今回は GeekServo 9G Servo-Gray を想定)
- ブレッドボードとジャンパーワイヤー
ピンの接続は以下の表のように行います。
接続先 | Arduino ピン | 備考 |
超音波センサー (HC-SR04) | ||
VCC (電源) | 5V | 電源 |
GND (接地) | GND | グランド |
Trig (トリガー) | D9 | デジタル出力 (OUTPUT) |
Echo (エコー) | D10 | デジタル入力 (INPUT) |
サーボモーター (GeekServo 9G) | ||
VCC (電源) | 5V | 電源 |
GND (接地) | GND | グランド |
信号 (PWM) | D6 | PWM制御 ( ~ が付いたピン) |
【補足】サーボモーターの配線色について
GeekServo 9G など、一般的なサーボモーターのケーブルは「黄・赤・茶」の3色(または「オレンジ・赤・茶」)になっています。
- 黄 (または オレンジ): 信号線です。Arduinoの
D6
(PWMピン) に接続します。 - 赤: 電源 (VCC) です。Arduino の
5V
に接続します。 - 茶 (または 黒): GND です。Arduino の
GND
に接続します。
2. 基本の制御コード(右検知→右操舵)
以下のコードは、超音波センサーが30cm未満の距離に障害物を検知すると、サーボモーター(ハンドル)を右(70°)に切り、障害物がなければ中央(90°)に戻すというプログラムです。
#include <Servo.h> // Servoライブラリをインクルード
// --- ピン定義 ---
const int trigPin = 9; // 超音波センサーのTrigピン
const int echoPin = 10; // 超音波センサーのEchoピン
Servo myServo; // サーボオブジェクト作成
void setup() {
Serial.begin(9600); // シリアル通信を開始 (デバッグ用)
pinMode(trigPin, OUTPUT);
pinMode(echoPin, INPUT);
myServo.attach(6); // サーボをD6ピンに接続
myServo.write(90); // 初期位置(直進)に設定
}
void loop() {
// --- 超音波センサーで距離測定 ---
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
// EchoピンがHIGHだった時間(μs)を取得
long duration = pulseIn(echoPin, HIGH);
// 時間を距離(cm)に変換 (音速 約343m/s で計算)
float distance = (duration * 0.0343) / 2;
// シリアルモニタに距離を表示
Serial.print("Distance: ");
Serial.print(distance);
Serial.println(" cm");
// --- サーボ制御 ---
// 距離が0より大きく、30cm未満なら右に舵を切る
if (distance > 0 && distance < 30) {
myServo.write(70); // 右に舵を切る (70度)
Serial.println("Turning Right (70 degrees)");
} else {
myServo.write(90); // 直進 (90度)
Serial.println("Going Straight (90 degrees)");
}
delay(500); // 500ms待機 (動作を安定させるため)
}
ステップ2: サーボモーターの「90度」とは?
ここで、myServo.write(90)
と記述している「角度」について理解を深めておきましょう。
サーボモーターの90度は、一般的に「中央位置(ニュートラルポジション)」を指します。
ただし、これは製造時の目安であり、製品によっては個体差があります。write(90)
と命令しても、厳密に中央を向いていないかもしれません。
基準位置の確認方法:
- 上記のコードの
loop()
内を空にして、setup()
でmyServo.write(90);
だけを実行します。 - サーボが停止した位置が、そのモーターの「90度」です。
- もし車のタイヤが真っ直ぐになっていなければ、
write(88);
やwrite(92);
のように数値を微調整し、真っ直ぐになる角度(それが真のニュートラル)を見つけてください。
この記事では、便宜上 90° = 直進
、70° = 右
、110° = 左
として進めます。
ステップ3: 【応用】左右のセンサーで障害物回避
次に、超音波センサーをもう1つ追加し、左側も検知できるようにシステムを拡張します。
1. ピン配置(左センサー追加)
左側用の超音波センサーを追加します。ピンが重複しないように D7
と D8
を使用します。
接続先 | Arduino ピン | 備考 |
右 超音波センサー | ||
Trig (トリガー) | D9 | デジタル出力 |
Echo (エコー) | D10 | デジタル入力 |
左 超音波センサー | ||
Trig (トリガー) | D7 | デジタル出力 |
Echo (エコー) | D8 | デジタル入力 |
サーボモーター | D6 | PWM制御 |
(VCC / GND) | 5V / GND | 共通の電源とグランドに接続 |
2. 左右回避コード
左右のセンサーからの距離をそれぞれ測定し、「右が近ければ左へ」「左が近ければ右へ」回避するロジックに変更します。
#include <Servo.h>
// --- ピン定義 ---
const int rightTrigPin = 9; // 右センサー Trig
const int rightEchoPin = 10; // 右センサー Echo
const int leftTrigPin = 7; // 左センサー Trig
const int leftEchoPin = 8; // 左センサー Echo
Servo myServo; // サーボオブジェクト
void setup() {
Serial.begin(9600);
pinMode(rightTrigPin, OUTPUT);
pinMode(rightEchoPin, INPUT);
pinMode(leftTrigPin, OUTPUT);
pinMode(leftEchoPin, INPUT);
myServo.attach(6); // サーボをD6ピンに接続
myServo.write(90); // 初期位置(直進)
}
// 距離を測定する関数 (コードを共通化)
float getDistance(int trigPin, int echoPin) {
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
long duration = pulseIn(echoPin, HIGH);
float distance = (duration * 0.0343) / 2;
return distance;
}
void loop() {
// 左右の距離をそれぞれ測定
float rightDistance = getDistance(rightTrigPin, rightEchoPin);
float leftDistance = getDistance(leftTrigPin, leftEchoPin);
// シリアルモニタに表示
Serial.print("Right: ");
Serial.print(rightDistance);
Serial.print(" cm, Left: ");
Serial.print(leftDistance);
Serial.println(" cm");
// --- 操舵ロジック ---
// 右に障害物が近い (30cm未満)
if (rightDistance > 0 && rightDistance < 30) {
myServo.write(110); // 左に舵を切る
Serial.println("Turning Left (110)");
}
// 左に障害物が近い (30cm未満)
else if (leftDistance > 0 && leftDistance < 30) {
myServo.write(70); // 右に舵を切る
Serial.println("Turning Right (70)");
}
// 障害物がない場合
else {
myServo.write(90); // 直進
Serial.println("Going Straight (90)");
}
delay(300); // 少し待機
}
このコードで左右の回避は可能になりますが、障害物を見つけた瞬間に「ギュン!」と最大角度まで舵が切れてしまい、動きが非常にぎこちなくなります。
ステップ4: 【改善】距離に応じて「滑らかに」操舵する
ステップ3の「ギュンギュン動く」問題を解決するため、障害物との距離に応じて舵角を連続的に(滑らかに)変化させるロジックを導入します。
Arduinoの map()
関数を使うと、この処理を簡単に実装できます。
- アイデア:
50cm
以上離れていれば90°
(直進)30cm
まで近づいたら最大舵角(70°
または110°
)30cm
から50cm
の間は、距離に応じて舵角をリニアに変化させる。
改善後のコード(滑らかな操舵)
loop()
関数部分を以下のように変更します。(setup()
と getDistance()
関数はステップ3と同じです)
void loop() {
float rightDistance = getDistance(rightTrigPin, rightEchoPin);
float leftDistance = getDistance(leftTrigPin, leftEchoPin);
Serial.print("Right: ");
Serial.print(rightDistance);
Serial.print(" cm, Left: ");
Serial.print(leftDistance);
Serial.println(" cm");
int finalAngle = 90; // 基本は直進(90度)
// --- 操舵ロジック (滑らかバージョン) ---
// 1. 右センサーの角度を計算
// 距離を30cm~50cmの範囲に制限
int rightConstrained = constrain(rightDistance, 30, 50);
// 距離(30~50)を角度(110~90)にマッピング
// (右が近いので左に切る = 90より大きい角度)
int rightAngle = map(rightConstrained, 30, 50, 110, 90);
// 2. 左センサーの角度を計算
// 距離を30cm~50cmの範囲に制限
int leftConstrained = constrain(leftDistance, 30, 50);
// 距離(30~50)を角度(70~90)にマッピング
// (左が近いので右に切る = 90より小さい角度)
int leftAngle = map(leftConstrained, 30, 50, 70, 90);
// 3. 最終的な角度を決定
// 両方とも50cm以上なら、mapの結果が90になるので直進
// 右の方が左より近い場合 (右に障害物)
if (rightDistance < leftDistance) {
finalAngle = rightAngle; // 右センサーが計算した角度(左舵)を採用
}
// 左の方が右より近い場合 (左に障害物)
else {
finalAngle = leftAngle; // 左センサーが計算した角度(右舵)を採用
}
myServo.write(finalAngle); // 最終的な角度をサーボに書き込む
Serial.print("Servo Angle: ");
Serial.println(finalAngle);
delay(100); // 制御周期を少し早める
}
constrain(value, min, max) は、value を min と max の範囲内に収める関数です。
map(value, fromLow, fromHigh, toLow, toHigh) は、value を from の範囲から to の範囲へ線形に変換(マッピング)する関数です。
これにより、障害物が 40cm
の位置にあれば中程度の舵角、30cm
に近づくにつれて最大の舵角、というように滑らかな動作が実現できます。
ステップ5: センサーの特性と「床の反射」について
超音波センサーを使う上で、その特性を知っておくことは重要です。
超音波は扇状(円錐状)に広がる
HC-SR04から発射される超音波は、レーザー光線のように真っ直ぐ飛ぶわけではなく、約15°〜30°程度の角度で扇状(円錐状)に広がって進みます。
このため、センサーの真正面にない物体(少し横にある壁など)も検知することがあります。
床からの反射は影響するか?
「ブレッドボードにセンサーを載せていると、広がった超音波が床に反射して誤検知しないか?」という疑問が生じることがあります。
結論から言うと、**「設置状況によっては、床の反射が影響する可能性はある」**です。
影響を受けにくいケース:
- センサーが床と完全に水平に取り付けられている。
- ブレッドボード(センサー)の高さが十分にある(例: 10cm以上)。
- 床がカーペットなど、音波を吸収しやすい素材である。
影響を受けやすいケース:
- センサーがわずかに下向きになっている。(これが最も多い原因です)
- センサーの設置位置が非常に低い(例: 5cm以下)。
- 床がフローリングやタイルのように、硬く反射しやすい素材である。
もし、目の前に何もないのにシリアルモニタに短い距離(例: 15cmなど)が表示され続ける場合は、床からの反射を拾っている可能性が高いです。センサーの取り付け角度を少し上向きに調整してみてください。
まとめ
今回は、Arduinoと超音波センサー、サーボモーターを使い、障害物を回避するシステムを構築しました。
- 最初は1つのセンサーで単純なON/OFF制御(30cm未満なら舵を切る)から始めました。
- 次に、2つのセンサーで左右の状況を判断できるようにしました。
- 最後に、
map()
関数などを用いて、距離に応じて舵角を滑らかに変化させる制御に改善しました。
この基本システムにDCモーターとモータードライバーを追加すれば、壁にぶつからない自動走行車が完成します。ぜひ、挑戦してみてください。