PID制御とAndroid連携で作るライントレースロボット — Arduino Nano入門
必要なパーツと材料
| パーツ | 数量 | 備考 |
| Arduino Nano R3 | 1 | |
| ライントラッカーセンサーモジュール (SparkFun RedBot / TCRT5000 4ch) | 1+1 | 4chモジュール + 単体センサーで計5センサー構成 |
| HC-06 Bluetoothモジュール | 1 | Androidアプリとの通信用 |
| Android端末 | 1 | PIDパラメータ調整用アプリ実行 |
| 単3電池ボックス (4本用) | 2 | モーター用とロジック用で分離推奨 |
| 連続回転サーボ (SM-S4303R等) | 2 | 左右駆動輪用 |
| ボディ材料: 木板80x80mm×2、バインダークリップ×3、木製車輪50mm径×2、ボールキャスター、ゴムバンド×9、3Mコマンドフック、ブレッドボード、配線、LED、タクトスイッチ | 1式 | 自作シャーシ用 |
使用するアプリとオンラインサービス
- Arduino IDE
- MIT App Inventor 2 (Androidアプリ作成)
プロジェクトの概要
PID制御を実装したライントレースロボットを製作し、AndroidアプリでPIDパラメータ (Kp, Ki, Kd) を無線でリアルタイム調整できるようにします。これは2部構成プロジェクトの第1弾で、第2弾では迷路探索ロボット (簡易AI搭載) に発展させます。
ステップ1: 材料調達 (約75ドルで完成)
上記パーツリストの通り。ボディは木板とバインダークリップで簡易的に組み立て可能。
ステップ2: モーター (連続回転サーボ) の設定
2個の連続回転サーボ (SM-S4303R) を3Mコマンドストリップ等で背中合わせに固定し、一体化させます。このサーボのパルス幅仕様:
- 1.5ms (1500µs): ニュートラル = 停止
- 1.0ms (1000µs): 一方向へ全速 (~70 RPM)
- 2.0ms (2000µs): 逆方向へ全速
- 中間値: 比例速度
取り付け後、まず1500µsパルスを送って完全停止するか確認。ズレがある場合はサーボ底部の黄色調整ネジで微調整 (調整ネジがない場合はコード側で1500µs前後を微調整)。
#include <Servo.h>
Servo leftServo; Servo rightServo;
void setup() {
leftServo.attach(5); // 左サーボ: Pin 5
rightServo.attach(3); // 右サーボ: Pin 3
leftServo.writeMicroseconds(1500);
rightServo.writeMicroseconds(1500);
}
void loop() {}
ステップ3: シャーシ組み立てと走行テスト
木板にサーボ固定、2枚目をクリップで挟んでプラットフォーム化、ボールキャスター装着。モーター用電池 (5V/6V) をブレッドボード横に配置し、GNDをArduino GNDと共通化。サーボ信号線をPin 5 (左)、Pin 3 (右) に接続。LEDをPin 13、タクトスイッチをPin 9に接続 (テスト/スタート用)。
サーボ取り付け向きが対向しているため、前進時のパルス幅範囲は:
- 右サーボ: 1500µs (停止) → 2000µs (全速)
- 左サーボ: 1500µs (停止) → 1000µs (全速)
ステップ4: Bluetoothモジュール HC-06 (任意)
SoftwareSerialライブラリ使用。接続: HC-06 Tx→Arduino Pin 10 (Rx)、Rx→Pin 11 (Tx)、VCC/GND→5V/GND。Bluetoothなしでもデフォルトパラメータで動作するようコードは設計済み。AndroidアプリでPIDチューニングや手動操作をしたい場合のみ導入。
ステップ5: ラインセンサー (5ch) の取り付け
センサー5個をプラスチックバーに等間隔で固定し、フレーム下部に配置。配線はゴムバンドでまとめ車輪やキャスターに干渉しないよう注意。Arduinoピン割り当て:
- Sensor 0 (最左): Pin 12
- Sensor 1: Pin 18 (A4)
- Sensor 2 (中央): Pin 17 (A3)
- Sensor 3: Pin 16 (A2)
- Sensor 4 (最右): Pin 19 (A5)
第2電池セット (5V) をArduino Vinに接続。
ステップ6: IRセンサーロジックの実装
IRセンサーはIR LEDとフォトダイオードで構成。黒/暗色面では反射光が少なく、モジュール上のポテンショメータで閾値調整されたデジタル出力 (HIGH=黒/ライン、LOW=白/背景) を得ます。
5センサー配置では、ライン中央に来たとき中央センサー (Sensor 2) のみHIGH、ライン幅によって隣接2センサーが同時にHIGHになるよう間隔を調整。考えられるセンサー出力パターンと「誤差 (error)」変数の対応:
| センサー出力 (S0-S4) | error | 意味 |
| 0 0 1 0 0 | 0 | 中央 (理想) |
| 0 0 0 0 1 | +4 | 大きく右にズレ |
| 0 0 0 1 1 | +3 | 右にズレ |
| 0 0 0 1 0 | +2 | やや右 |
| 0 0 1 1 0 | +1 | わずかに右 |
| 0 1 1 0 0 | -1 | わずかに左 |
| 0 1 0 0 0 | -2 | やや左 |
| 1 1 0 0 0 | -3 | 左にズレ |
| 1 0 0 0 0 | -4 | 大きく左にズレ |
Arduinoコードでの実装:
const int lineFollowSensor0 = 12;
const int lineFollowSensor1 = 18;
const int lineFollowSensor2 = 17;
const int lineFollowSensor3 = 16;
const int lineFollowSensor4 = 19;
int LFSensor[5] = {0,0,0,0,0};
void readLFSsensors() {
LFSensor[0] = digitalRead(lineFollowSensor0);
LFSensor[1] = digitalRead(lineFollowSensor1);
LFSensor[2] = digitalRead(lineFollowSensor2);
LFSensor[3] = digitalRead(lineFollowSensor3);
LFSensor[4] = digitalRead(lineFollowSensor4);
// 誤差計算 (if-else連鎖またはルックアップテーブルで実装)
if (LFSensor[0]==0 && LFSensor[1]==0 && LFSensor[2]==0 && LFSensor[3]==0 && LFSensor[4]==1) error = 4;
else if (LFSensor[0]==0 && LFSensor[1]==0 && LFSensor[2]==0 && LFSensor[3]==1 && LFSensor[4]==1) error = 3;
else if (LFSensor[0]==0 && LFSensor[1]==0 && LFSensor[2]==0 && LFSensor[3]==1 && LFSensor[4]==0) error = 2;
else if (LFSensor[0]==0 && LFSensor[1]==0 && LFSensor[2]==1 && LFSensor[3]==1 && LFSensor[4]==0) error = 1;
else if (LFSensor[0]==0 && LFSensor[1]==0 && LFSensor[2]==1 && LFSensor[3]==0 && LFSensor[4]==0) error = 0;
else if (LFSensor[0]==0 && LFSensor[1]==1 && LFSensor[2]==1 && LFSensor[3]==0 && LFSensor[4]==0) error = -1;
else if (LFSensor[0]==0 && LFSensor[1]==1 && LFSensor[2]==0 && LFSensor[3]==0 && LFSensor[4]==0) error = -2;
else if (LFSensor[0]==1 && LFSensor[1]==1 && LFSensor[2]==0 && LFSensor[3]==0 && LFSensor[4]==0) error = -3;
else if (LFSensor[0]==1 && LFSensor[1]==0 && LFSensor[2]==0 && LFSensor[3]==0 && LFSensor[4]==0) error = -4;
}
ステップ7: 比例制御 (P制御) での方向制御
基本速度 iniMotorPower = 250 とし、右サーボに 1500 + iniMotorPower、左サーボに 1500 - iniMotorPower のパルスを送ると等速前進。誤差 error が正 (右ズレ) なら右減速/左加速、負 (左ズレ) なら右加速/左減速:
int Kp = 50; // 比例ゲイン
rightServo.writeMicroseconds(1500 + iniMotorPower - Kp * error);
leftServo.writeMicroseconds(1500 - iniMotorPower - Kp * error);
これによりロボットはラインから外れようとすると自動的に修正方向に舵を切ります。これが「比例制御 (P制御)」、PIDの「P」成分です。
ステップ8: PID制御の実装 (任意だが推奨)
P制御だけでも動作しますが、オーバーシュート (行き過ぎ) や定常偏差を改善するため、D項 (微分) とI項 (積分) を追加します。
PIDの数式:
- P項:
P = error (現在の偏差に比例)
- I項:
I = I + error (過去の偏差累積、定常偏差解消)
- D項:
D = error - previousError (偏差の変化率、オーバーシュート抑制)
- 出力:
PIDvalue = Kp*P + Ki*I + Kd*D
実装関数:
void calculatePID() {
P = error;
I = I + error;
D = error - previousError;
PIDvalue = (Kp * P) + (Ki * I) + (Kd * D);
previousError = error;
}
void motorPIDcontrol() {
int leftMotorSpeed = 1500 - iniMotorPower - PIDvalue;
int rightMotorSpeed = 1500 + iniMotorPower - PIDvalue;
leftServo.writeMicroseconds(leftMotorSpeed);
rightServo.writeMicroseconds(rightMotorSpeed);
}
チューニング手順 (試行錯誤法):
Kd = 0, Ki = 0 でKpのみ調整。Kp=25〜50から開始。
- ある程度追従したら、KpとKdを半分ずつにし (例: Kp=25, Kd=25)、Kdを増やしてオーバーシュートを抑制。不安定なら減らす。
- 必要ならKiを微小値から導入 (小型ロボットでは不要なことが多い)。
- ループ周期 (
delay 値) も性能に影響。試行錯誤で最適化。
ステップ9: 完成版メインループと状態管理
単純なループだとライン見失い時や停止ライン検出時に対応できないため、mode 変数で状態管理:
STOPPED (0): 全センサーHIGH (1 1 1 1 1) = 停止ライン検出 → 完全停止
FOLLOWING_LINE (1): 通常追従 → PID制御実行
NO_LINE (2): 全センサーLOW (0 0 0 0 0) = ライン見失い → 停止後その場で180度旋回等で復帰試行
#define STOPPED 0
#define FOLLOWING_LINE 1
#define NO_LINE 2
int mode = STOPPED;
void loop() {
readLFSsensors();
// モード判定
if (LFSensor[0]==1 && LFSensor[1]==1 && LFSensor[2]==1 && LFSensor[3]==1 && LFSensor[4]==1) mode = STOPPED;
else if (LFSensor[0]==0 && LFSensor[1]==0 && LFSensor[2]==0 && LFSensor[3]==0 && LFSensor[4]==0) mode = NO_LINE;
else mode = FOLLOWING_LINE;
switch (mode) {
case STOPPED: motorStop(); break;
case NO_LINE: motorStop(); motorTurn(LEFT, 180); break; // 180度ターン例
case FOLLOWING_LINE: calculatePID(); motorPIDcontrol(); break;
}
}
ステップ10: AndroidアプリでPIDリモートチューニング
毎回コンパイル・書き込みせずにパラメータ調整できるよう、MIT App Inventor 2で専用アプリを作成。機能:
- 手動操作: 前進/後進/左旋回/右旋回/停止 (BT送信: 'f','b','l','r','s')
- PIDスライダー: Kp/Ki/Kd それぞれ 0〜100 で送信 ('p/XXX', 'i/XXX', 'd/XXX')
- スタートボタン: Arduino Pin 9のタクトスイッチと同機能 ('g' 送信でFOLLOWING_LINEモード開始)
アプリファイル: .aia (App Inventor編集用)、.apk (直接インストール用)
ステップ11: リモートチューニング対応コードへの修正
setup() 内でボタン押下待ちループ中にBTコマンドを受信処理:
while (digitalRead(buttonPin) && !mode) {
checkBTcmd(); // BTコマンド受信チェック
manualCmd(); // 手動操作/PID設定反映
command = "";
}
checkPIDvalues();
mode = STOPPED;
void manualCmd() {
switch (command[0]) {
case 'g': mode = FOLLOWING_LINE; break;
case 's': motorStop(); break;
case 'f': motorForward(); break;
case 'r': motorTurn(RIGHT, 30); motorStop(); break;
case 'l': motorTurn(LEFT, 30); motorStop(); break;
case 'b': motorBackward(); break;
case 'p': Kp = command.substring(2).toFloat(); break; // "p/XXX" パース
case 'i': Ki = command.substring(2).toFloat(); break;
case 'd': Kd = command.substring(2).toFloat(); break;
}
}
ステップ12: 結論と次のステップ
このライントレースロボットは、PID制御の基礎を学び、Androidアプリで直感的にチューニングできる優れた教材です。次回はこのベースで迷路探索ロボット (簡易AIで最短経路探索) を製作予定。ファイル一式は GitHub に公開中。詳細はブログ MJRoBot.org もご覧ください。
南半球からこんにちは! — Marcelo
コードリポジトリ
https://github.com/Mjrovai/MJRoBot-Line-Follower
回路図 (Fritzingファイル)
oIDhLcHQ30lDgVBXZvb8.fzz