Arduinoで障害物回避!超音波センサー(HC-SR04)とサーボで滑らかな自動操舵システムを作る

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)D6PWM制御 ( ~ が付いたピン)

【補足】サーボモーターの配線色について

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) と命令しても、厳密に中央を向いていないかもしれません。

基準位置の確認方法:

  1. 上記のコードの loop() 内を空にして、setup()myServo.write(90); だけを実行します。
  2. サーボが停止した位置が、そのモーターの「90度」です。
  3. もし車のタイヤが真っ直ぐになっていなければ、write(88);write(92); のように数値を微調整し、真っ直ぐになる角度(それが真のニュートラル)を見つけてください。

この記事では、便宜上 90° = 直進70° = 右110° = 左 として進めます。


ステップ3: 【応用】左右のセンサーで障害物回避

次に、超音波センサーをもう1つ追加し、左側も検知できるようにシステムを拡張します。

1. ピン配置(左センサー追加)

左側用の超音波センサーを追加します。ピンが重複しないように D7D8 を使用します。

接続先Arduino ピン備考
右 超音波センサー
Trig (トリガー)D9デジタル出力
Echo (エコー)D10デジタル入力
左 超音波センサー
Trig (トリガー)D7デジタル出力
Echo (エコー)D8デジタル入力
サーボモーターD6PWM制御
(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. 最初は1つのセンサーで単純なON/OFF制御(30cm未満なら舵を切る)から始めました。
  2. 次に、2つのセンサーで左右の状況を判断できるようにしました。
  3. 最後に、map() 関数などを用いて、距離に応じて舵角を滑らかに変化させる制御に改善しました。

この基本システムにDCモーターとモータードライバーを追加すれば、壁にぶつからない自動走行車が完成します。ぜひ、挑戦してみてください。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次