Arduinoで障害物を避けるロボット:HC-SR04とサーボのガイド
Arduinoで障害物を避けるロボットを作ります。サーボでHC-SR04を振るスキャン、L298NやL293Dの配線、止まる・下がる・曲がるロジック、赤外線センサー、Bluetoothの手動モードまで解説します。
障害物を避けるロボットは、前方の壁、椅子、箱にぶつかる前に気づき、新しい道を見つけて進む自律走行のロボットカーです。最もよくある構成は、Arduino Uno、HC-SR04超音波距離センサー、センサーを左右に向けるSG90サーボモーター、ギア付きDCモーター2個、そしてL298N(またはL293D)モータードライバです。ロボットはまっすぐ進み、障害物に近づくと止まり、少し下がって、頭を左右に向けて距離を測り、最も開けた方向へ進みます。
このガイドでは、Arduinoのロボットカーの記事で作った2WDの車を、同じピン配置のまま自律ロボットに変えます。配線表、距離のしきい値の計算方法、3段階で育てていくコード、センサーが見えない面とその穴を赤外線センサーで埋める方法、そして最後にスマホからBluetoothで手動モードに切り替える方法を、ステップごとに解説します。同じロボットをESP32やRaspberry Pi Picoで動かすコードは記事の最後にあります。

障害物を避けるロボットはどう動く?
どの自律ロボットも、同じ3段階のループを1秒間に何十回も繰り返しています。感知、判断、行動です。感知を担うのはHC-SR04です。耳には聞こえない40 kHzの短い音を出し、反響が戻るまでの時間を測ります。その時間(マイクロ秒)を58で割ると、距離がセンチメートルで求まります。判断を担うのはArduinoで、測った距離を設定したしきい値と比べます。行動はL298Nとモーターが担当します。センサーがサーボの上にあるので、ロボットは体の向きを変えずに左右を「見る」ことができ、センサーが固定されたロボットよりずっとよい曲がり方を判断できます。センサーの仕組みの詳細はHC-SR04距離センサーの記事にあります。
| 部品 | 役割 | 備考 |
|---|---|---|
| Arduino Uno(またはNano) | 頭脳:測り、判断し、モーターに命令する | ESP32やPicoでも可 |
| HC-SR04 | 目:前方の障害物までの距離を測る | 5 Vで動作、2〜400 cm |
| SG90サーボ | 首:センサーを左右に向ける | センサーはプラスチックのブラケットでサーボのホーンに固定する |
| L298NまたはL293Dシールド | 筋肉:モーターに方向と速度を与える | Arduinoのピンではモーターの電流を供給できない |
| ギア付きTTモーター2個とキャスター | 差動駆動 | 2つの車輪を逆方向に回して曲がる |
| 18650セル × 2(7.4 V) | 電源 | 9 V電池では足りない |
| 赤外線障害物センサー2個(任意) | 角や側面の障害物を捉える | FC-51などLM393を使ったモジュール |
| HC-05(任意) | スマホからの手動操作 | Androidのみ対応 |




電源:サーボでロボットをリセットさせない
障害物を避けるロボットはモーターにサーボが加わるので、普通のロボットカーより電源の配分が重要です。SG90は静止時に約10 mAを消費しますが、動き出した瞬間に100〜250 mAに跳ね上がり、負荷がかかると0.5 A近くになります。この突入電流がモーターの起動電流と重なると、Arduinoの5 Vラインが一瞬落ちてボードが再起動します。解決法は簡単です。モーターは電池からL298Nを通して、ArduinoはVINを通して電池から動かし、サーボ、HC-SR04、赤外線センサー、HC-05はArduinoの5Vピンを共有します。そしてサーボの電源線の近くに470 µFのコンデンサを置きます。サーボが動いている間でも5 Vラインの合計負荷は0.5 A未満にとどまり、一瞬のことなのでUnoのレギュレーターで対応できます。自分の数値で電池の持ちを計算するには電池の持続時間ツールを使ってください。
| 部分 | 電源の供給元 | 一般的な電流 |
|---|---|---|
| TTモーター2個 | L298Nの12V端子 ← 18650 × 2(7.4 V) | 走行中0.3〜0.6 A、起動時は最大2 A |
| Arduino Uno | VIN ← 電池のプラス(スイッチの後) | 約50 mA |
| SG90サーボ | Arduinoの5V + 470 µFコンデンサ | 静止時約10 mA、動作中100〜250 mA |
| HC-SR04 | Arduinoの5V | 約15 mA |
| 赤外線センサー2個 | Arduinoの5V | 1個あたり20〜40 mA |
| HC-05 | Arduinoの5V | ペアリング中30〜40 mA、接続後約10 mA |
配線図:Arduino Uno、ESP32、Pico
モーターのピンはロボットカーのガイドと同じなので、車を分解せずにセンサーヘッドを追加するだけでこのプロジェクトに進めます。速度のピンを5と6にしたのには理由があります。UnoではServoライブラリがTimer1を使うため、9番と10番ピンのPWMが無効になります。モーターの速度をこれらのピンにすると、サーボを接続した途端に速度制御が効かなくなります。HC-SR04と赤外線センサーはA0〜A3につなぎます。Unoのアナログピンは必要に応じてデジタルピン14〜19としても使えます。この選び方なら、L293Dシールドでも同じコードを使いやすくなります。シールドはこれらのピンを使わないからです。ボードのピンはピン配置ツールで確認できます。
| 接続 | Arduino Uno | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| L298N ENA(左モーターの速度) | D5 | GPIO 25 | GP15 |
| L298N IN1、IN2 | D7、D8 | GPIO 27、33 | GP14、GP13 |
| L298N ENB(右モーターの速度) | D6 | GPIO 26 | GP10 |
| L298N IN3、IN4 | D9、D4 | GPIO 32、4 | GP12、GP11 |
| サーボの信号(オレンジ) | D3 | GPIO 13 | GP16 |
| サーボの+(赤)、−(茶) | 5V、GND | L298Nの5V、GND | L298Nの5V、GND |
| HC-SR04 TRIG | A0 | GPIO 18 | GP17 |
| HC-SR04 ECHO | A1(直結) | GPIO 19、1 kΩ / 2 kΩの分圧回路を通す | GP18、1 kΩ / 2 kΩの分圧回路を通す |
| HC-SR04 VCC | 5V | L298Nの5V | L298Nの5V |
| 左の赤外線OUT、右の赤外線OUT | A2、A3 | GPIO 23、22 | GP19、GP20 |
| 赤外線センサーのVCC | 5V | 3V3 | 3V3 |
| HC-05 TXD、RXD | D10、D11(RXDは分圧回路を通す) | 不要(Bluetooth内蔵) | GP1、GP0(UART0) |
| ボードの電源 | VIN ← 電池(スイッチの後) | 5V(VIN) ← L298Nの5V | VSYS ← L298Nの5V |
センサーヘッドの取り付けとテスト

- ホーンを付けずにサーボをつなぎ、下のテスト用スケッチを書き込みます。コードは毎回のスイープの最後にサーボを90度に動かして2秒待ちます。その間にUSBケーブルを抜くと、サーボはその位置で止まります。センサーがまっすぐ前を向くようにホーンを軸に取り付けます。こうすると90度が「正面」になります。
- HC-SR04をプラスチックのブラケットかホットボンドでホーンに固定します。センサーの配線は、サーボが回っても突っ張らない長さが必要です。140度のスイープ中にケーブルがシャーシに引っかかるのはよくある不具合です。
- センサーは床から8〜12 cmの高さで、車体の最も前に近い位置に取り付けます。低すぎると床やカーペットの凹凸からの反響を拾い、高すぎると低い障害物が見えません。
- センサーを3〜5度上向きに傾けます。完全に水平で低い位置のセンサーは、特に凹凸のある床で、床からの反響を障害物と間違えることがあります。
- シリアルモニタを9600ボーで開きます。センサーの左側に手をかざしたときに125度と160度の行で小さな値が出れば、左側が大きい角度です。逆の場合は、発展版のスケッチでSERVO_DIRを-1にし、最初のスケッチではLEFT_ANGLEとRIGHT_ANGLEの値を入れ替えます。
1#include <Servo.h>
2
3const int SERVO_PIN = 3;
4const int TRIG = A0, ECHO = A1; // analog pinler dijital pin olarak / analog pins used as digital pins
5Servo head;
6
7int pingCm() {
8 digitalWrite(TRIG, LOW); delayMicroseconds(2);
9 digitalWrite(TRIG, HIGH); delayMicroseconds(10); // 10 µs tetik darbesi / 10 µs trigger pulse
10 digitalWrite(TRIG, LOW);
11 unsigned long t = pulseIn(ECHO, HIGH, 25000UL); // en fazla 25 ms bekle (~4,3 m) / wait at most 25 ms (~4.3 m)
12 return t / 58; // 0 = yankı yok / 0 = no echo
13}
14
15void setup() {
16 Serial.begin(9600);
17 pinMode(TRIG, OUTPUT);
18 pinMode(ECHO, INPUT);
19 head.attach(SERVO_PIN);
20 head.write(90); // önce ortala / center first
21 delay(1000);
22}
23
24void loop() {
25 const int angles[] = {20, 55, 90, 125, 160};
26 for (int a : angles) {
27 head.write(a);
28 delay(400); // servo yerine otursun / let the servo settle
29 Serial.print(a);
30 Serial.print(F(" derece / degrees: "));
31 Serial.print(pingCm());
32 Serial.println(F(" cm"));
33 }
34 Serial.println();
35 head.write(90);
36 delay(2000);
37}各行には角度とその方向の距離が表示されます。センサーから約4メートル以内に何もなければ0 cmと表示されます。25 ms以内に反響が届かないとpulseIn()は0を返すからです。メインのコードでは0を「道が空いている」として扱いますが、配線が緩んでいたりTRIGとECHOが逆だったりするセンサーもたいてい0を返します。そのため、ロボットを床に置く前に10、30、60 cmの位置に手をかざし、値が本当に変わるか確認してください。このテストをせずに走らせたロボットは、減速せずに壁へ突っ込みます。
判断のアルゴリズム:止まる、下がる、最も開けた方向へ曲がる
ロボットの振る舞いを距離によってゾーンに分けると、コードがシンプルで確実になります。前に障害物がなければ全速で進み、障害物が近づくにつれて減速し、停止のしきい値に達したら止まってスキャンします。スキャン中、センサーは左に70度と35度、右に35度と70度を見ます。各方向で3回測り、その中央値を使います。ロボットは最も距離が長い方向へ、その角度だけ曲がります。どの方向も開いていない場合、たとえば角に入り込んだときは、少し下がって180度回ります。
| 測った距離 | ゾーン | ロボットの動作 |
|---|---|---|
| 60 cm超(または反響なし) | 空き | 全速前進(PWM 200) |
| 25〜60 cm | 接近 | 距離に比例して減速(PWM 200から130まで) |
| 12〜25 cm | 障害物 | 止まり、値を確認し、左右をスキャンし、最も開けた方向へ曲がる |
| 12 cm未満 | 近すぎる | まず350 ms下がってからスキャンする |
| すべての方向が35 cm未満 | 行き止まり | 下がって180度回る |
これらのしきい値は適当に選んだものではありません。停止距離は3つの余裕の合計です。反応の余裕(2回の測定の間に進む距離)、ブレーキの余裕(モーターを止めてから惰性で進む距離)、旋回の余裕(その場で回るときに鼻先が障害物に触れないため)です。6 VのTTモーターの車は約0.5〜0.7 m/sで進み、60 msの測定間隔で3〜4 cm進みます。ブレーキの余裕は約5 cm、その場で回る幅15 cmの車体なら旋回の余裕は約10 cmです。合計すると約18〜20 cmになり、そこに数センチの安全マージンを足すと、STOP_CM = 25がよい出発点です。ロボットが速い場合や床が滑りやすい場合は、しきい値を30〜35 cmに上げてください。逆に狭い廊下では、しきい値が大きすぎるとロボットがしょっちゅう止まってスキャンするので、最終的な調整は自分のコースで行います。
ステップごとのコード1:最初の障害物を避けるロボット
最初のバージョンはアルゴリズムの骨組みです。前に進み、25 cmで止まり、下がり、左右を見て、より開けた側へ曲がります。モーターの関数drive(left, right)はロボットカーのガイドと同じで、-255〜255の値を取ります。プラスは前進、マイナスは後退です。コードを書き込んだら、まず車輪が浮くように車を箱の上に置き、センサーに手を近づけたときにどう止まってどう曲がるかを観察します。問題がなさそうならロボットを床に置きます。
1#include <Servo.h>
2
3const int ENA = 5, IN1 = 7, IN2 = 8; // sol motor / left motor
4const int ENB = 6, IN3 = 9, IN4 = 4; // sağ motor / right motor
5const int SERVO_PIN = 3;
6const int TRIG = A0, ECHO = A1;
7
8const int STOP_CM = 25; // bu mesafede dur / stop at this distance
9const int SPEED = 170; // düz gidiş hızı / cruising speed
10const int TURN = 170; // dönüş hızı / turning speed
11const int LEFT_ANGLE = 160, RIGHT_ANGLE = 20; // ters ise yer değiştir / swap if reversed
12
13Servo head;
14
15void drive(int left, int right) { // her biri -255 … 255 / each -255 … 255
16 digitalWrite(IN1, left > 0); digitalWrite(IN2, left < 0);
17 digitalWrite(IN3, right > 0); digitalWrite(IN4, right < 0);
18 analogWrite(ENA, abs(left));
19 analogWrite(ENB, abs(right));
20}
21
22int pingCm() {
23 digitalWrite(TRIG, LOW); delayMicroseconds(2);
24 digitalWrite(TRIG, HIGH); delayMicroseconds(10);
25 digitalWrite(TRIG, LOW);
26 unsigned long t = pulseIn(ECHO, HIGH, 25000UL);
27 if (t == 0) return 400; // yankı yok: önü açık say / no echo: treat as open
28 return t / 58;
29}
30
31int lookAt(int angle) {
32 head.write(angle);
33 delay(400); // servo dönsün ve dursun / let the servo turn and settle
34 return pingCm();
35}
36
37void setup() {
38 int pins[] = {ENA, IN1, IN2, ENB, IN3, IN4, TRIG};
39 for (int p : pins) pinMode(p, OUTPUT);
40 pinMode(ECHO, INPUT);
41 drive(0, 0);
42 head.attach(SERVO_PIN);
43 head.write(90);
44 delay(3000); // yere koyman için süre / time to put it on the floor
45}
46
47void loop() {
48 int front = pingCm();
49 if (front > STOP_CM) {
50 drive(SPEED, SPEED); // yol açık: ileri / path clear: forward
51 delay(60); // ölçümler arası 60 ms / 60 ms between readings
52 return;
53 }
54
55 drive(0, 0); // 1) dur / 1) stop
56 delay(150);
57 drive(-SPEED, -SPEED); // 2) biraz geri çekil / 2) back off a little
58 delay(300);
59 drive(0, 0);
60
61 int left = lookAt(LEFT_ANGLE); // 3) iki yana bak / 3) look both ways
62 int right = lookAt(RIGHT_ANGLE);
63 head.write(90);
64 delay(300);
65
66 if (left < STOP_CM && right < STOP_CM) { // iki yan kapalı: geri dön / both blocked: turn around
67 drive(-TURN, TURN);
68 delay(700);
69 } else if (left > right) { // 4) daha açık tarafa dön / 4) turn to the more open side
70 drive(-TURN, TURN);
71 delay(350);
72 } else {
73 drive(TURN, -TURN);
74 delay(350);
75 }
76 drive(0, 0);
77 delay(100);
78}このコードでも動きますが、いくつか弱点があります。1回の誤った測定でロボットが無駄に止まる、障害物にいつも同じ速度で近づく、2方向しか見ない、旋回時間が角度によらない、という点です。またdelay()で待つので、スキャン中は何にも反応できません。待ち方の考え方の詳細はmillis()とdelay()の記事にあります。次のバージョンでこうした問題のほとんどを解決します。
ステップごとのコード2:フィルター、段階的な減速、赤外線、Bluetooth
発展版は同じ配線で動き、赤外線センサーやHC-05をまだ付けていなくても問題ありません。赤外線の入力をINPUT_PULLUPで設定しているので、つないでいないセンサーは「障害物なし」(HIGH)を読みます。HC-05がなければ、ロボットはそのまま自動モードで始まります。下の表で、コードの各部分が何をしているかを説明します。
1#include <Servo.h>
2#include <SoftwareSerial.h>
3
4// Pinler / Pins
5const int ENA = 5, IN1 = 7, IN2 = 8; // sol motor / left motor
6const int ENB = 6, IN3 = 9, IN4 = 4; // sağ motor / right motor
7const int SERVO_PIN = 3;
8const int TRIG = A0, ECHO = A1;
9const int IR_LEFT = A2, IR_RIGHT = A3; // LOW = engel (takılı değilse HIGH) / LOW = obstacle (HIGH if not fitted)
10SoftwareSerial bt(10, 11); // RX, TX → HC-05 TXD, RXD (RXD bölücüyle / RXD via divider)
11
12// Ayarlar / Settings
13const int BACK_CM = 12; // daha yakınsa geri çekil / closer: back off first
14const int STOP_CM = 25; // dur ve tara / stop and scan
15const int SLOW_CM = 60; // yavaşlamaya başla / start slowing down
16const int GUARD_CM = 15; // manuel modda çarpışma koruması / collision guard in manual mode
17const int MAX_CM = 400;
18const int CRUISE = 200, SLOW = 130, TURN = 170; // PWM hızları / PWM speeds
19const int SERVO_CENTER = 90; // sensör öne bakmıyorsa düzelt / adjust if not facing ahead
20const int SERVO_DIR = 1; // +70 sola bakmıyorsa -1 yap / set to -1 if +70 doesn't look left
21const float MS_PER_DEG = 4.0; // derece başına ms, kalibre et / ms per degree, calibrate
22const unsigned long BT_TIMEOUT = 600; // manuelde komut kesilirse dur / stop if commands stop
23
24Servo head;
25bool autoMode = true;
26int manualLeft = 0, manualRight = 0, manualSpeed = 200;
27unsigned long lastCmd = 0, lastPing = 0;
28
29void drive(int left, int right) { // her biri -255 … 255 / each -255 … 255
30 left = constrain(left, -255, 255);
31 right = constrain(right, -255, 255);
32 digitalWrite(IN1, left > 0); digitalWrite(IN2, left < 0);
33 digitalWrite(IN3, right > 0); digitalWrite(IN4, right < 0);
34 analogWrite(ENA, abs(left));
35 analogWrite(ENB, abs(right));
36}
37
38int pingCm() {
39 digitalWrite(TRIG, LOW); delayMicroseconds(2);
40 digitalWrite(TRIG, HIGH); delayMicroseconds(10);
41 digitalWrite(TRIG, LOW);
42 unsigned long t = pulseIn(ECHO, HIGH, 25000UL); // ~4,3 m zaman aşımı / ~4.3 m timeout
43 return t == 0 ? MAX_CM : (int)(t / 58); // yankı yok = açık alan / no echo = open space
44}
45
46int distanceCm() { // 3 ölçümün ortancası / median of 3 readings
47 int a = pingCm(); delay(60);
48 int b = pingCm(); delay(60);
49 int c = pingCm();
50 return max(min(a, b), min(max(a, b), c));
51}
52
53bool irBlocked(int pin) {
54 return digitalRead(pin) == LOW;
55}
56
57int lookAt(int offset) { // + sol, - sağ (derece) / + left, - right (degrees)
58 head.write(SERVO_CENTER + SERVO_DIR * offset);
59 delay(300); // servo yerine otursun / let the servo settle
60 return distanceCm();
61}
62
63void turnBy(int deg) { // + sola, - sağa / + left, - right
64 if (deg > 0) drive(-TURN, TURN);
65 else drive(TURN, -TURN);
66 delay((unsigned long)(abs(deg) * MS_PER_DEG));
67 drive(0, 0);
68 delay(100);
69}
70
71void avoid() {
72 drive(0, 0); // 1) dur / 1) stop
73 delay(120);
74 int front = distanceCm(); // ortancayla doğrula / confirm with the median
75 bool sides = irBlocked(IR_LEFT) || irBlocked(IR_RIGHT);
76 if (front >= STOP_CM && !sides) return; // yanlış alarm / false alarm
77
78 if (front < BACK_CM || sides) { // 2) çok yakın: geri çekil / 2) too close: back off
79 drive(-TURN, -TURN);
80 delay(350);
81 drive(0, 0);
82 }
83
84 const int offsets[] = {70, 35, -35, -70}; // 3) tara / 3) scan
85 int bestOffset = 0, bestCm = 0;
86 for (int off : offsets) {
87 int cm = lookAt(off);
88 if (cm > bestCm) { bestCm = cm; bestOffset = off; }
89 }
90 head.write(SERVO_CENTER);
91 delay(200);
92
93 if (bestCm < STOP_CM + 10) { // her yön kapalı: çıkmaz / every way blocked: dead end
94 drive(-TURN, -TURN);
95 delay(400);
96 turnBy(180);
97 } else {
98 turnBy(bestOffset); // 4) en açık yöne dön / 4) turn to the most open direction
99 }
100}
101
102void autoStep() {
103 if (millis() - lastPing < 60) return; // 60 ms'de bir ölç / measure every 60 ms
104 lastPing = millis();
105 int d = pingCm();
106 bool l = irBlocked(IR_LEFT), r = irBlocked(IR_RIGHT);
107
108 if (d < STOP_CM || (l && r)) {
109 avoid();
110 } else if (l) { // sol köşede engel: sağa kaç / front-left: dodge right
111 drive(TURN, -TURN);
112 delay(150);
113 } else if (r) { // sağ köşede engel: sola kaç / front-right: dodge left
114 drive(-TURN, TURN);
115 delay(150);
116 } else if (d < SLOW_CM) {
117 int s = map(d, STOP_CM, SLOW_CM, SLOW, CRUISE); // yaklaştıkça yavaşla / slow down as you get closer
118 drive(s, s);
119 } else {
120 drive(CRUISE, CRUISE);
121 }
122}
123
124void manualStep() {
125 if (millis() - lastCmd > BT_TIMEOUT) { // komut yok: dur / no commands: stop
126 manualLeft = 0;
127 manualRight = 0;
128 }
129 static int front = MAX_CM;
130 if (millis() - lastPing >= 60) {
131 lastPing = millis();
132 front = pingCm();
133 }
134 bool forward = manualLeft > 0 && manualRight > 0;
135 if (forward && front < GUARD_CM) drive(0, 0); // çarpışma koruması / collision guard
136 else drive(manualLeft, manualRight);
137}
138
139void handleBluetooth() {
140 while (bt.available()) {
141 char c = bt.read();
142 Serial.println(c); // gelen harfi göster / show the incoming letter
143 lastCmd = millis();
144 if (c == 'A') { // otomatik mod / auto mode
145 autoMode = true;
146 continue;
147 }
148 if (c == 'M') { // manuel mod / manual mode
149 autoMode = false;
150 manualLeft = manualRight = 0;
151 drive(0, 0);
152 head.write(SERVO_CENTER);
153 continue;
154 }
155 if (autoMode) continue; // otomatikte yön harflerini yok say / ignore in auto mode
156 int s = manualSpeed;
157 switch (c) {
158 case 'F': manualLeft = s; manualRight = s; break; // ileri / forward
159 case 'B': manualLeft = -s; manualRight = -s; break; // geri / backward
160 case 'L': manualLeft = -s; manualRight = s; break; // yerinde sola / spin left
161 case 'R': manualLeft = s; manualRight = -s; break; // yerinde sağa / spin right
162 case 'S': manualLeft = 0; manualRight = 0; break; // dur / stop
163 default:
164 if (c >= '0' && c <= '9') manualSpeed = map(c - '0', 0, 9, 100, 240); // hız kademesi / speed level
165 }
166 }
167}
168
169void setup() {
170 int outs[] = {ENA, IN1, IN2, ENB, IN3, IN4, TRIG};
171 for (int p : outs) pinMode(p, OUTPUT);
172 drive(0, 0);
173 pinMode(ECHO, INPUT);
174 pinMode(IR_LEFT, INPUT_PULLUP);
175 pinMode(IR_RIGHT, INPUT_PULLUP);
176 Serial.begin(9600);
177 bt.begin(9600); // HC-05 veri modu hızı / HC-05 data mode baud rate
178 head.attach(SERVO_PIN);
179 head.write(SERVO_CENTER);
180 delay(3000); // yere koyman için süre / time to put it on the floor
181}
182
183void loop() {
184 handleBluetooth();
185 if (autoMode) autoStep();
186 else manualStep();
187}| 関数または設定 | 役割 |
|---|---|
| pingCm() | 1回測定する。25 ms以内に反響がなければ400 cm(空間が開いている)を返す |
| distanceCm() | 60 ms間隔で3回測り、中央値を返す。外れ値を1つ除外できる |
| autoStep() | 60 msごとに測定し、距離と赤外線センサーに応じて進む、減速する、よける、またはavoid()を呼ぶ |
| avoid() | 止まり、値を確認し、必要なら下がり、4方向を見て最も開けた方向へ曲がる |
| turnBy(degrees) | ロボットをその場で指定の角度だけ回す。時間はMS_PER_DEGで計算する |
| manualStep() | スマホから送られた方向を実行する。前進中は15 cmで止まり、600 msコマンドが来なければ止まる |
| SERVO_CENTER、SERVO_DIR | センサーが正面を向く角度と、どちらが左か |
| STOP_CM、SLOW_CM、BACK_CM | 停止、減速、後退のしきい値 |
障害物を避けるロボットには、平均より中央値のフィルターが向いています。24、26、400 cmのような3つの値の平均は150 cmになってロボットを惑わせますが、中央値なら26 cmを選びます。さらに詳しく知りたい場合はセンサーデータのフィルタリングの記事を参照してください。自動モードと手動モードの切り替えも、実は小さなステートマシンです。この考え方はArduinoのステートマシンのガイドで詳しく解説しています。コードを書き込んだら、次の順番でロボットを自分の部屋に合わせて調整します。
- サーボの中心:平らな壁にロボットを正対させたとき、センサーがまっすぐ壁を向いていなければ、SERVO_CENTERを85や95のように少しずつ変えます。
- 旋回時間:一時的にsetup()の最後にturnBy(360);の行を追加し、ロボットがちょうど1回転するか観察します。360度ではなく300度しか回らなければ、MS_PER_DEGを4.0 × 360 / 300 = 4.8にします。この値は電池や床によって変わるので、満充電の電池で、ロボットが走る床の上で調整してください。
- 停止のしきい値:ロボットを壁に向かって走らせ、止まった位置で鼻先が壁からどれだけ離れているか測ります。10 cm未満なら、STOP_CMを5 cm増やします。
- 低速の速度:SLOWがモーターの起動しきい値より低いと、ロボットは接近ゾーンで止まってしまいます。その場合はSLOWを150に上げます。
HC-SR04の死角:柔らかい面、斜めの壁、細い障害物
超音波センサーは音をよく反射する面は見えますが、すべての障害物が音をセンサーに返すわけではありません。メーカーは、よい結果を得るには0.5 m²以上のできるだけ平らな対象を推奨しています。しかし実際の部屋では、ロボットはカーテン、ソファ、椅子の脚、壁の角に出くわします。音はビリヤードの球のように、当たった角度で跳ね返ります。壁に正対したセンサーは強い反響を受けますが、視線が垂直から約30〜40度以上ずれると、音は別の方向へそれて戻ってきません。その場合pulseIn()は0を返し、コードは道が空いていると判断します。これがロボットが壁をこする最もよくある原因です。
| 障害物または面 | 見えない理由 | 対策 |
|---|---|---|
| カーテン、ソファ、クッション、衣類、ペットの毛 | 柔らかく多孔質な面が音を吸収し、反響がとても弱くなる | 近距離用に赤外線センサーを追加し、ロボットをゆっくり走らせる |
| 壁に斜めに近づく | 音がそれて反射し、反響がセンサーに届かない | 前の角に外向きの赤外線センサーを付け、より細かい角度でスキャンする |
| 椅子や机の脚、細い柱 | 反射する面がとても小さく、反響が弱い | 速度を下げ、中央値のフィルターを使い、側面に赤外線センサーを付ける |
| センサーより低い障害物(ケーブル、敷居、スリッパ) | 音の円錐の下に隠れる | 下向きの赤外線センサーか、前のバンパーにマイクロスイッチを付ける |
| 机の端、階段 | 超音波センサーは前を向いているので、床の切れ目が見えない | 床に向けたTCRT5000で落下を検知する |
| 2 cmより近い物 | センサーの不感帯:音を出し終わる前に反響が戻ってくる | 12 cmで下がるようにロボットを設定する |
| 角や狭い廊下 | 音が2つの壁の間で跳ね返って遅れて戻り、距離が長く見える | 中央値のフィルターと複数の角度からの測定 |
赤外線センサーを追加する
超音波センサーと赤外線センサーは、互いの弱点を補い合います。FC-51のような赤外線障害物センサーは、赤外線LEDと受光部でできています。反射光が十分に強いと、LM393コンパレーターが出力をLOWにし、基板上のLEDが点きます。検知距離は半固定抵抗(トリマー)で約2〜30 cmに調整できますが、距離を測るのではなく「障害物あり」か「なし」かを知らせるだけです。ロボットの前の角に、30〜45度外向きに赤外線センサーを2個付けると、超音波センサーの狭い円錐から外れる椅子の脚や斜めの壁を捉えます。発展版のコードでは、左の赤外線が反応するとロボットは少し右へよけ、右が反応すると左へよけます。両方が同時に反応したら、前に障害物があるときと同じように止まってスキャンします。
| 項目 | HC-SR04(超音波) | FC-51(赤外線障害物) | TCRT5000(赤外線反射) |
|---|---|---|---|
| 出力 | 距離(cm) | あり・なし(デジタル) | アナログ値とデジタル出力 |
| 範囲 | 2〜400 cm | 約2〜30 cm(調整可能) | 約1〜15 mm |
| 検知角度 | 約15° | 約35° | とても狭い |
| 弱点 | 柔らかい面、斜めの面、細い面 | 黒くつやのない面、日光 | 高さの変化、日光 |
| よく見えるもの | ガラス、黒い物、大きく平らな面 | 布、角、近くの障害物 | 床の色と机の端 |
| ロボット上の位置 | メインの目、サーボで振る | 前の角 | 前のバンパーの下、床に向けて |

机の上を走るロボットにとって、最も危険な障害物は実は段差です。前のバンパーの下に床向きのTCRT5000モジュールを付ければ、床がなくなった瞬間がわかります。モジュールのデジタル出力を、Unoでまだ空いているD2につなぎます。まずシリアルモニタで床がないときに出力がどちらのレベルになるかを確認し、autoStep()の一番最初でこのピンを読み、床がなければすぐにモーターを止めてロボットを下がらせます。この確認は距離の測定より先に行う必要があります。落ちるほうがぶつかるより常に被害が大きいからです。
Bluetoothで手動モードに切り替える
自律ロボットを手で運転したいこともあります。部屋から部屋へ移動させたいとき、障害物の前に連れて行って反応を試したいとき、あるいはただ遊びたいときです。発展版のコードのHC-05の接続でそれが可能になります。スマホからAの文字を送るとロボットは自動モードに、Mを送ると手動モードに切り替わります。手動モードではロボットカーのガイドと同じ操作の文字(F、B、L、R、S)を使いますが、違いが1つあります。前進中に15 cmより近い障害物を見つけると、命令に関係なく止まります。この衝突ガードのおかげで、手動運転でも自律ロボットのセンサーの恩恵を受けられます。HC-05のペアリング手順とATコマンドはHC-05 Bluetoothモジュールの記事にあります。
| 文字 | 動作 | どのモードで? |
|---|---|---|
| A | 自動モードに切り替える | 両方のモード |
| M | 手動モードに切り替え、ロボットを止める | 両方のモード |
| F / B | 前進 / 後退 | 手動 |
| L / R | その場で左 / 右に回る | 手動 |
| S | 停止 | 手動 |
| 0〜9 | 速度レベル(PWM 100〜240) | 手動 |
- HC-05のTXDピンをD10に、RXDピンを1 kΩ / 2 kΩの分圧回路を通してD11に、VCCを5Vに、GNDをGNDにつなぎます。
- AndroidスマホのBluetooth設定で、PIN 1234(うまくいかなければ0000)でHC-05とペアリングします。
- PlayストアからBluetoothカーのリモコンアプリをインストールし、HC-05に接続します。ArduinoをUSBにつないだままシリアルモニタを9600ボーで開くと、コードはスマホから届いたすべての文字を表示します。
- 多くのリモコンアプリにはライトやクラクションなどの追加ボタンがあり、それぞれ独自の文字を送ります。シリアルモニタでその文字を調べ、コードの'A'と'M'の代わりに入れてください。ボタンを自分で決めたい場合は、App InventorでArduinoとBluetoothのガイドで自分のリモコンを作れます。
- USBケーブルを抜いてスイッチを入れます。3秒後にロボットは自動モードで走り出します。手動モードに切り替えて、スマホから運転してみましょう。
L293Dモーターシールドで作る

配線がごちゃごちゃするのが嫌なら、L298Nの代わりに、Arduino Unoに直接挿せるL293Dモーターシールド(Adafruit Motor Shield V1とその互換品)を使えます。シールドは4個のDCモーターを駆動でき、サーボ用のピンヘッダーが2つあり、サーボにはArduinoの5 Vラインから給電します。モーターはArduinoのピンに直接ではなく、基板上の74HC595シフトレジスタを通してつながっているので、ライブラリマネージャからインストールしたAdafruit Motor Shield library(V2用ではなくV1用)、つまりAFMotorで動かします。シールドはモーターにD3、D4、D5、D6、D7、D8、D11、D12を、サーボにD9とD10を使い、A0〜A5、D2、D13は空いたままです。HC-SR04と赤外線センサーはすでにA0〜A3にあるので、変更が必要なのはモーターのコード、サーボのピン、HC-05のピンだけです。
| 設定 | L298Nの場合 | L293Dシールドの場合 |
|---|---|---|
| モーターの接続 | OUT1〜OUT4端子 | M1(左)とM2(右)の端子 |
| モーターのコード | drive()内のdigitalWriteとanalogWrite | drive()内のAFMotor |
| SERVO_PIN | 3 | サーボを挿すヘッダーに応じて9か10 |
| HC-05 | D10、D11 | A4(RX)とA5(TX):SoftwareSerial bt(A4, A5) |
| 1チャンネルあたりの電流 | 連続2 A | 連続0.6 A、ピーク1.2 A |
| モーターの電源 | 12V端子 | EXT_PWR端子。PWRジャンパーを付けるとArduinoも同じ電源で動く |
1// L293D shield: ENA–IN4 tanımlarını ve setup()'taki pinMode listesinden bu pinleri sil, drive() fonksiyonunu aşağıdakiyle değiştir
2// L293D shield: delete the ENA–IN4 definitions, remove them from the pinMode list in setup() and replace drive() with the one below
3// Ayrıca / Also: SERVO_PIN = 9 ya da / or 10, SoftwareSerial bt(A4, A5)
4#include <AFMotor.h> // Adafruit Motor Shield V1 kütüphanesi / library
5
6AF_DCMotor leftMotor(1); // M1 klemensi / M1 terminal
7AF_DCMotor rightMotor(2); // M2 klemensi / M2 terminal
8
9void drive(int left, int right) { // her biri -255 … 255 / each -255 … 255
10 leftMotor.setSpeed(min(abs(left), 255));
11 rightMotor.setSpeed(min(abs(right), 255));
12 leftMotor.run(left > 0 ? FORWARD : (left < 0 ? BACKWARD : RELEASE));
13 rightMotor.run(right > 0 ? FORWARD : (right < 0 ? BACKWARD : RELEASE));
14}L293Dの1チャンネルあたり0.6 Aという上限は、TTモーターの2WDロボットの通常走行には十分です。しかし車輪が障害物を押して止まると、モーターは1 A以上を引くことがあり、チップがすぐに熱くなります。そのため、障害物に押し当たる前にロボットを止めるスキャンのアルゴリズムは、シールドの寿命も延ばします。シールドのジャンパー設定と4モーターの車のスケッチはL293Dモーターシールドの記事に、L298Nの内部の仕組みはL298NでDCモーターを制御するガイドにあります。
ESP32で障害物を避けるロボット
ESP32では大きな違いが2つあります。1つ目は、ArduinoのServoライブラリがESP32では動かないので、ボードのPWMユニットであるLEDCで直接サーボの信号を作ることです。50 Hzの信号で0.544〜2.4 msのパルスが0〜180度に対応します。モーターは1 kHz、サーボは50 Hzで動くので、モーターのチャンネルを先に、サーボのチャンネルをあとに設定し、サーボが専用のタイマーを使えるようにします。2つ目は、ESP32はクラシックBluetoothを内蔵しているので、HC-05が不要なことです。ロボットはスマホに「BlueGrays-Robot」として表示され、方向とモードの文字はUno版と同じです。コードはESP32 Arduinoコアのバージョン3.x用(ledcAttachとledcWriteはピン番号で動作)で、クラシックBluetoothが必要なため、ESP32-S3やESP32-C3ではコンパイルできません。
1// ESP32 Arduino çekirdeği 3.x / ESP32 Arduino core 3.x
2#include "BluetoothSerial.h"
3
4const int ENA = 25, IN1 = 27, IN2 = 33; // sol motor / left motor
5const int ENB = 26, IN3 = 32, IN4 = 4; // sağ motor / right motor
6const int SERVO_PIN = 13;
7const int TRIG = 18, ECHO = 19; // ECHO 1 kΩ / 2 kΩ bölücüyle / ECHO via a 1 kΩ / 2 kΩ divider
8const int IR_LEFT = 23, IR_RIGHT = 22; // LOW = engel / LOW = obstacle
9
10const int BACK_CM = 12, STOP_CM = 25, SLOW_CM = 60, GUARD_CM = 15, MAX_CM = 400;
11const int CRUISE = 200, SLOW = 130, TURN = 170;
12const int SERVO_CENTER = 90, SERVO_DIR = 1;
13const float MS_PER_DEG = 4.0; // kalibre et / calibrate
14
15BluetoothSerial bt;
16bool autoMode = true;
17int manualLeft = 0, manualRight = 0;
18unsigned long lastCmd = 0, lastPing = 0;
19
20void drive(int left, int right) { // her biri -255 … 255 / each -255 … 255
21 left = constrain(left, -255, 255);
22 right = constrain(right, -255, 255);
23 digitalWrite(IN1, left > 0); digitalWrite(IN2, left < 0);
24 digitalWrite(IN3, right > 0); digitalWrite(IN4, right < 0);
25 ledcWrite(ENA, abs(left));
26 ledcWrite(ENB, abs(right));
27}
28
29void servoWrite(int angle) { // 0–180° → 544–2400 µs
30 angle = constrain(angle, 0, 180);
31 uint32_t us = map(angle, 0, 180, 544, 2400);
32 ledcWrite(SERVO_PIN, us * 16384 / 20000); // 14 bit, 20 ms periyot / 14-bit, 20 ms period
33}
34
35int pingCm() {
36 digitalWrite(TRIG, LOW); delayMicroseconds(2);
37 digitalWrite(TRIG, HIGH); delayMicroseconds(10);
38 digitalWrite(TRIG, LOW);
39 unsigned long t = pulseIn(ECHO, HIGH, 25000UL);
40 return t == 0 ? MAX_CM : (int)(t / 58);
41}
42
43int distanceCm() { // 3 ölçümün ortancası / median of 3 readings
44 int a = pingCm(); delay(60);
45 int b = pingCm(); delay(60);
46 int c = pingCm();
47 return max(min(a, b), min(max(a, b), c));
48}
49
50bool irBlocked(int pin) { return digitalRead(pin) == LOW; }
51
52void turnBy(int deg) { // + sola, - sağa / + left, - right
53 if (deg > 0) drive(-TURN, TURN);
54 else drive(TURN, -TURN);
55 delay((unsigned long)(abs(deg) * MS_PER_DEG));
56 drive(0, 0);
57 delay(100);
58}
59
60void avoid() {
61 drive(0, 0);
62 delay(120);
63 int front = distanceCm();
64 bool sides = irBlocked(IR_LEFT) || irBlocked(IR_RIGHT);
65 if (front >= STOP_CM && !sides) return; // yanlış alarm / false alarm
66 if (front < BACK_CM || sides) { // geri çekil / back off
67 drive(-TURN, -TURN);
68 delay(350);
69 drive(0, 0);
70 }
71 const int offsets[] = {70, 35, -35, -70};
72 int bestOffset = 0, bestCm = 0;
73 for (int off : offsets) {
74 servoWrite(SERVO_CENTER + SERVO_DIR * off);
75 delay(300);
76 int cm = distanceCm();
77 if (cm > bestCm) { bestCm = cm; bestOffset = off; }
78 }
79 servoWrite(SERVO_CENTER);
80 delay(200);
81 if (bestCm < STOP_CM + 10) { // çıkmaz / dead end
82 drive(-TURN, -TURN);
83 delay(400);
84 turnBy(180);
85 } else {
86 turnBy(bestOffset);
87 }
88}
89
90void setup() {
91 int outs[] = {IN1, IN2, IN3, IN4, TRIG};
92 for (int p : outs) pinMode(p, OUTPUT);
93 pinMode(ECHO, INPUT);
94 pinMode(IR_LEFT, INPUT_PULLUP);
95 pinMode(IR_RIGHT, INPUT_PULLUP);
96 ledcAttach(ENA, 1000, 8); // motor PWM: 1 kHz, 8 bit
97 ledcAttach(ENB, 1000, 8);
98 ledcAttach(SERVO_PIN, 50, 14); // servo: 50 Hz, 14 bit
99 drive(0, 0);
100 servoWrite(SERVO_CENTER);
101 bt.begin("BlueGrays-Robot"); // telefonda görünecek ad / name shown on the phone
102 delay(3000);
103}
104
105void loop() {
106 while (bt.available()) {
107 char c = bt.read();
108 lastCmd = millis();
109 if (c == 'A') autoMode = true; // otomatik / auto
110 else if (c == 'M') { // manuel / manual
111 autoMode = false;
112 manualLeft = manualRight = 0;
113 servoWrite(SERVO_CENTER);
114 }
115 else if (!autoMode) {
116 if (c == 'F') { manualLeft = 200; manualRight = 200; } // ileri / forward
117 else if (c == 'B') { manualLeft = -200; manualRight = -200; } // geri / backward
118 else if (c == 'L') { manualLeft = -200; manualRight = 200; } // sola / left
119 else if (c == 'R') { manualLeft = 200; manualRight = -200; } // sağa / right
120 else if (c == 'S') { manualLeft = 0; manualRight = 0; } // dur / stop
121 }
122 }
123
124 if (millis() - lastPing < 60) return; // 60 ms'de bir / every 60 ms
125 lastPing = millis();
126 int d = pingCm();
127
128 if (!autoMode) {
129 if (millis() - lastCmd > 600) manualLeft = manualRight = 0; // komut yok: dur / no command: stop
130 bool forward = manualLeft > 0 && manualRight > 0;
131 if (forward && d < GUARD_CM) drive(0, 0); // çarpışma koruması / collision guard
132 else drive(manualLeft, manualRight);
133 return;
134 }
135
136 bool l = irBlocked(IR_LEFT), r = irBlocked(IR_RIGHT);
137 if (d < STOP_CM || (l && r)) avoid();
138 else if (l) { drive(TURN, -TURN); delay(150); }
139 else if (r) { drive(-TURN, TURN); delay(150); }
140 else if (d < SLOW_CM) { int s = map(d, STOP_CM, SLOW_CM, SLOW, CRUISE); drive(s, s); }
141 else drive(CRUISE, CRUISE);
142}Raspberry Pi Picoで障害物を避けるロボット(MicroPython)
Raspberry Pi Picoでは、MicroPythonのtime_pulse_us()関数がArduinoのpulseIn()のように働きます。反響が届かないと負の値を返すので、それを400 cm、つまり空間が開いているとして扱います。サーボには50 HzのPWMと、パルス幅をナノ秒で受け取るduty_ns()を使います。GP16のサーボとGP15、GP10のモーターの速度のピンは別々のPWMスライスにあるので、互いの周波数を乱しません。今回は速度をパーセントで指定します。PicoにはBluetoothがないので、手動モードが必要ならロボットカーのガイドと同じようにHC-05をUART0(GP0、GP1)につなげます。
1from machine import Pin, PWM, time_pulse_us
2import time
3
4ena = PWM(Pin(15)); ena.freq(1000) # sol motor hızı / left motor speed
5enb = PWM(Pin(10)); enb.freq(1000) # sağ motor hızı / right motor speed
6in1, in2 = Pin(14, Pin.OUT), Pin(13, Pin.OUT)
7in3, in4 = Pin(12, Pin.OUT), Pin(11, Pin.OUT)
8servo = PWM(Pin(16)); servo.freq(50) # servo sinyali, 50 Hz / servo signal, 50 Hz
9trig = Pin(17, Pin.OUT, value=0)
10echo = Pin(18, Pin.IN) # 1 kΩ / 2 kΩ bölücü üzerinden / via a 1 kΩ / 2 kΩ divider
11ir_left = Pin(19, Pin.IN, Pin.PULL_UP) # 0 = engel / 0 = obstacle
12ir_right = Pin(20, Pin.IN, Pin.PULL_UP)
13
14STOP_CM, BACK_CM = 25, 12
15CRUISE, TURN = 75, 65 # hız yüzdesi / speed in percent
16MS_PER_DEG = 4 # kalibre et / calibrate
17SERVO_CENTER = 90 # öne bakmıyorsa düzelt / adjust if not facing ahead
18
19def drive(left, right): # -100 … 100 (%)
20 in1.value(left > 0); in2.value(left < 0)
21 in3.value(right > 0); in4.value(right < 0)
22 ena.duty_u16(int(min(abs(left), 100) * 655))
23 enb.duty_u16(int(min(abs(right), 100) * 655))
24
25def head(offset): # + sol, - sağ / + left, - right
26 angle = max(0, min(180, SERVO_CENTER + offset))
27 servo.duty_ns((544 + angle * 1856 // 180) * 1000) # 0,544–2,4 ms darbe / 0.544–2.4 ms pulse
28
29def ping_cm():
30 trig.value(1); time.sleep_us(10); trig.value(0) # 10 µs tetik / 10 µs trigger
31 t = time_pulse_us(echo, 1, 25000)
32 return 400 if t < 0 else t // 58 # negatif = yankı yok / negative = no echo
33
34def distance_cm(): # 3 ölçümün ortancası / median of 3 readings
35 r = []
36 for _ in range(3):
37 r.append(ping_cm())
38 time.sleep_ms(60)
39 return sorted(r)[1]
40
41def turn_by(deg): # + sola, - sağa / + left, - right
42 if deg > 0:
43 drive(-TURN, TURN)
44 else:
45 drive(TURN, -TURN)
46 time.sleep_ms(int(abs(deg) * MS_PER_DEG))
47 drive(0, 0)
48 time.sleep_ms(100)
49
50def avoid():
51 drive(0, 0)
52 time.sleep_ms(120)
53 front = distance_cm()
54 sides = not ir_left.value() or not ir_right.value()
55 if front >= STOP_CM and not sides:
56 return # yanlış alarm / false alarm
57 if front < BACK_CM or sides: # geri çekil / back off
58 drive(-TURN, -TURN); time.sleep_ms(350); drive(0, 0)
59 best_off, best_cm = 0, 0
60 for off in (70, 35, -35, -70): # tara / scan
61 head(off)
62 time.sleep_ms(300)
63 cm = distance_cm()
64 if cm > best_cm:
65 best_off, best_cm = off, cm
66 head(0)
67 time.sleep_ms(200)
68 if best_cm < STOP_CM + 10: # çıkmaz / dead end
69 drive(-TURN, -TURN); time.sleep_ms(400)
70 turn_by(180)
71 else:
72 turn_by(best_off)
73
74drive(0, 0)
75head(0)
76time.sleep(3) # yere koyman için süre / time to put it down
77while True:
78 d = ping_cm()
79 left_hit, right_hit = not ir_left.value(), not ir_right.value()
80 if d < STOP_CM or (left_hit and right_hit):
81 avoid()
82 elif left_hit:
83 drive(TURN, -TURN); time.sleep_ms(150) # sağa kaç / dodge right
84 elif right_hit:
85 drive(-TURN, TURN); time.sleep_ms(150) # sola kaç / dodge left
86 else:
87 drive(CRUISE, CRUISE)
88 time.sleep_ms(60) # ölçümler arası / between readings障害物を避けるロボットのプロジェクトアイデア
- レーダー表示:スキャンで得た角度と距離をシリアルポートで送り、パソコンに緑色のレーダー画面を描きます。詳しくはArduinoのレーダープロジェクトの記事にあります。
- ライン + 障害物:ライントレースロボットのようにコースをたどりながら、途中の箱をよけてラインに戻ります。競技会でよくある課題です。
- 壁に沿って走るロボット:センサーを右に60度向けたまま、壁から一定の20 cmを保って走ります。迷路を解くための第一歩です。
- ディスプレイ付きロボット:距離とモードを小さなOLEDディスプレイに表示すると、デバッグがずっと楽になります。
- ロボット掃除機の試作:ランダムな方向選択、バンパースイッチ、落下センサーで家の中を動き回る掃除ロボットの原型を作ります。
- スマート白杖:同じ距離測定のロジックを、視覚障害のある人向けの振動による警告に変えます。Arduinoのスマート白杖を参照してください。
- 学校や競技会のプロジェクト:障害物コース、タイム計測、自動・手動モードのデモと一緒に発表します。アイデアはArduinoの科学コンテスト向けプロジェクトアイデアの記事を見てください。
よくある間違いと解決法
| 問題 | 考えられる原因 | 解決法 |
|---|---|---|
| ロボットが止まらずに壁へぶつかる | センサーが0を読み(断線、TRIGとECHOが逆)、コードがそれを空き空間として扱っている | テスト用スケッチを動かし、手で10〜60 cmを試し、TRIGとECHOのピンを確認する |
| 斜めに近づくと壁をこする | 音が斜めの面で別の方向へ反射している | 前の角に赤外線センサーを付け、速度を下げる |
| スキャンのために止まるとArduinoがリセットされる | サーボとモーターの電流で5 Vラインが落ちている | サーボに470 µFのコンデンサ、満充電の18650セル。必要ならサーボを別の5 V電源から給電する |
| ロボットが理由もなく止まってスキャンする | 1回の誤った測定、または床からの反響 | センサーを8〜12 cmに上げて少し上向きにし、中央値での確認を使う |
| ロボットがふさがった側へ曲がる | サーボが逆向きに付いていて、左右が入れ替わっている | SERVO_DIRを-1にする |
| 旋回が短すぎる、または長すぎる | MS_PER_DEGが調整されていない、または電池が少ない | turnBy(360)で測り、満充電の電池で調整し直す |
| 角で左右に曲がり続ける | 両側ともしきい値に近く、判断が入れ替わり続けている | 行き止まりのしきい値を上げる(STOP_CM + 10)か、最後に曲がった方向を優先させる |
| センサーヘッドがピクピク動く | サーボの電源が弱い、またはSoftwareSerialの割り込み | サーボのケーブルを短くし、コンデンサを追加し、Bluetoothのコマンドを必要以上に送らない |
障害物を避けるロボットは、センサーの読み取り、フィルタリング、判断、モーター制御を1つのプロジェクトにまとめているので、ロボット工学への最適な入口です。まず最初のスケッチで基本の動きを観察し、次に発展版でしきい値を自分の部屋に合わせ、最後に赤外線センサーとBluetoothでロボットを強化しましょう。BlueGraysアプリでは、障害物を避けるロボット、L298NのBluetooth操作カー、HC-SR04での距離測定、駐車センサーのプロジェクトを、回路図とコード付きでステップごとに紹介しています。
よくある質問
障害物を避けるロボットを作るには何が必要?
基本の障害物を避けるロボットには、Arduino Uno、HC-SR04超音波センサー、SG90サーボ、L298NかL293Dのモータードライバ、ギア付きDCモーター2個の2WDシャーシキット、18650セル2本が必要です。角を見るために赤外線障害物センサーを2個、スマホで運転するためにHC-05 Bluetoothモジュールを追加することもできます。
サーボなしで障害物を避けるロボットは作れる?
はい。センサーは正面を向けておきます。障害物を見つけたら、まず一方へ曲がって測り直し、まだふさがっていれば反対へ曲がります。このほうが安上がりですが、試すたびに車体全体を回す必要があるので判断が遅くなります。サーボがあれば、曲がる前に最も開けた方向を見つけられます。
HC-SR04が一部の障害物を見えないのはなぜ?
超音波センサーは反響で測ります。カーテンやソファのような柔らかい面は音を吸収し、斜めの壁は音を別の方向に反射し、椅子の脚のような細い物はとても弱い反響しか返しません。また、約15度の円錐の外や下にある障害物も見えません。こうした穴は赤外線センサーで補えます。
障害物を避けるロボットはどのくらいの距離で止まるべき?
25 cmがよい初期値です。2回の測定の間に進む距離、ブレーキの距離、その場で回るのに必要な余裕を合わせると約20 cmになり、そこに小さな安全マージンを加えた値です。速いロボットや滑りやすい床では30〜35 cmに上げます。障害物が12 cmより近い場合、ロボットはまず下がるべきです。
ロボットにはL298NとL293Dシールドのどちらがいい?
L298Nは1チャンネルあたり2 Aを供給できるので、より頑丈で、ピンを自由に選べます。L293DシールドはUnoに挿せるので配線が減り、4個のモーターを駆動できますが、連続電流は1チャンネルあたり0.6 Aで、多くのピンを使います。TTモーターの小さな2WDロボットなら、どちらでも大丈夫です。
障害物を避けるロボットはスマホから操作できる?
はい。HC-05 Bluetoothモジュールを追加し、Androidスマホのリモコンアプリから文字を送って、自動モードと手動モードを切り替えます。手動モードで超音波センサーを衝突ガードとして使えば、ロボットを壁にぶつけずに済みます。ESP32なら内蔵Bluetoothがあるので、別のモジュールは不要です。
この記事を読んだ後に作れるプロジェクト
全プロジェクトを見る
HC-SR04で距離を測定(シリアルモニタ表示)
HC-SR04センサーは、コウモリが暗闇で道を見つけるのと同じ仕組みで動作します。人には聞こえない音を出し、その反響を捉えて前方の物体までの距離を測ります。Arduinoは0.5秒ごとに距離をセンチメートル単位でコンピューターの画面に表示し、3〜100センチメートルの測定値を示します。駐車センサーや障害物を避けるロボットなどのプロジェクトの基礎になります。

障害物回避ロボット
自分で進む道を見つける、賢い車を作ってみませんか?障害物回避ロボットは、前面の超音波センサーで周囲を常に調べます。障害物を見つけると停止し、サーボモーターで左右を確認して、最も障害物の少ない方向を選んで進みます。自動運転の基礎を学ぶのにぴったりの入門プロジェクトです。

L298Nで作るBluetooth操作の車
スマートフォンをリモコンにして、自作のロボットカーを操作できます。ArduinoはBluetoothモジュールを通じてスマートフォンのリモコンアプリから指令を受け取り、モータードライバーで車輪を回します。前進、後退、左右や斜めへの移動ができ、速度もスマートフォンから調整できます。停止ボタンを押すと、短いブレーキ動作でモーターがすぐに止まります。

サーボモーターの使い方
小型サーボモーターのアームが、一方の端、中央、もう一方の端の3つの位置へ順番に回り、それぞれの位置で2秒間止まります。Arduinoが保持する角度をモーターに指示すると、モーターはその位置まで動いて止まります。ロボットアームや自動開閉するふたが動く仕組みを学べます。

HC-SR04で作る駐車センサー
車の駐車センサーを小さく再現します。物体が近づくほど、ビープ音の間隔が短くなります。距離センサーが前方の物体までの距離を測り、20センチメートルより遠ければ緑のLEDが点灯します。近づくと赤いLEDが点滅してブザーが鳴り、さらに近づくほど警告の間隔が短くなります。車が壁にぶつからずに駐車できる仕組みを、机の上で確かめられます。

TCRT5000で反射(近接)を読み取る
目に見えない光を出し、その反射量を測る小さなセンサーで、前方の表面を検出できます。物体や明るい色の表面にセンサーを近づけるほど戻ってくる光が増え、Arduinoがその値を0.5秒ごとに画面に数値で表示します。ライントレースロボットや非接触式の水栓には、このようなセンサーが使われています。

TCRT5000で床面の白黒を判別
ライントレースロボットの目となる小さなセンサーで、床面が黒か白かを判別できます。TCRT5000センサーは目に見えない光を床面に当て、戻ってくる光の量を調べます。白い面では緑のLED、黒い面では赤のLEDが点灯します。自分でロボットを作る前に学んでおきたい大切なステップです。

ESP32とHC-SR04で作る超音波距離計
HC-SR04超音波センサーは、発した音波が物体に反射して戻るまでの時間(Time of Flight)を測定して距離を計算します。このプロジェクトでは、ESP32からセンサーのTRIGピンに10マイクロ秒のパルスを送り、ECHOピンからの反射波の持続時間をpulseIn()で読み取ります。計算した距離をcm単位で16x2 I2C LCDに表示します。HC-SR04は5Vで動作するため、ECHOピンには分圧回路が必須です。分圧しないとESP32の3.3V GPIOが破損します。
ほかの記事
BlueGraysアプリ
この回路をスマホでステップごとに作る
回路図、コード、部品リストを1つの画面で確認できます。Arduino、ESP32、Picoのプロジェクトを200件以上用意しています。

