Membangun Robot Line Follower 5-Channel dengan Kendali PID & Rute Kustom

Pelajari cara merakit, memprogram, dan men-tuning robot pengikut garis cerdas yang mampu melewati persimpangan rumit serta mendeteksi garis Start dan Finish secara otomatis.

1. Konsep & Arsitektur

Robot Line Follower (Pengikut Garis) ini menggunakan Arduino Nano sebagai otak pemroses. Berbeda dengan robot dasar yang hanya berbelok patah-patah, robot ini menggunakan algoritma PID (Proportional, Integral, Derivative).

P (Proportional)

Bereaksi terhadap error saat ini. Semakin jauh robot dari garis tengah, semakin kuat ia akan berbelok kembali.

I (Integral)

Mengumpulkan histori error. Berguna untuk mengatasi lintasan miring/tidak rata (Sering diset 0 untuk line follower).

D (Derivative)

Memprediksi error masa depan. Berfungsi sebagai "rem" untuk mencegah robot bergetar (osilasi) berlebihan saat kembali ke tengah.

2. Alat, Bahan & Wiring

Daftar Komponen

  • 1x Arduino Nano & Kabel USB
  • 1x Modul Driver Motor L298N
  • 1x Modul Sensor IR 5-Channel (TCRT5000/BFD-1000)
  • 2x Motor DC + Gearbox + Roda
  • 1x Roda Bebas (Caster Wheel) & Sasis Robot
  • 1x Baterai LiPo 2S (7.4V) atau 2x 18650
  • Kabel Jumper secukupnya & Saklar On/Off

Tabel Koneksi (Wiring)

Komponen APinKomponen BPin
Baterai (+)MerahL298N12V
Baterai (-)HitamL298N & ArduinoGND (Common)
L298N5V OutArduino5V In
Sensor 5-ChS1, S2, S3, S4, S5ArduinoA0, A1, A2, A3, A4
L298N (Kendali)ENA, IN1..IN4, ENBArduinoD5, D6, D7, D8, D9, D10

*Pastikan jumper 5V enable pada L298N terpasang jika menggunakan L298N untuk memberi daya ke Arduino.*

3. Simulator Logika Sensor

Pahami bagaimana letak garis di bawah robot memengaruhi nilai error. Klik kotak sensor di bawah ini untuk mensimulasikan garis hitam (warna gelap) dan latar putih (warna terang).

Hasil Pembacaan CPU

Error: 0
Lurus Sempurna

4. Script Arduino Master

Script ini mengintegrasikan PID, manajemen rute persimpangan, dan deteksi Start/Finish otomatis. Copy kode ini dan paste di Arduino IDE.

Penjelasan Bagian Fitur Cerdas:

  • Pengaturan Rute: rute[] menyimpan instruksi arah (1=Kiri, 2=Kanan, 0=Lurus). totalHitam melacak posisi garis Start/Cabang/Finish.
  • Auto-Kalibrasi: Karena sensor tidak memiliki potensiometer, script menggunakan analogRead() untuk mencari batas nilai (threshold) warna hitam dan putih secara otomatis setiap kali robot dinyalakan.
LineFollower_Pro.ino
// ======================================================
// LINE FOLLOWER 5 CH - PID, MULTI-BRANCH, START & FINISH
// ======================================================

// --- Pin Driver Motor L298N ---
#define ENA 5
#define IN1 6
#define IN2 7
#define IN3 8
#define IN4 9
#define ENB 10

// --- Pin Sensor IR 5-Channel ---
#define S1 A0 
#define S2 A1 
#define S3 A2 
#define S4 A3 
#define S5 A4 

// --- Pengaturan Rute & Logika Jalur ---
// 1 = Belok Kiri, 2 = Belok Kanan, 0 = Lurus
int rute[] = {1, 2}; 
int indexCabang = 0;         // Penunjuk rute saat ini
int jumlahCabangDiTengah = 2; // Total persimpangan di array rute
int totalHitam = 0;          // Penghitung garis hitam tebal

// --- Variabel Kalibrasi Otomatis (Analog) ---
int sensorPins[5] = {S1, S2, S3, S4, S5};
int sensorMin[5] = {1023, 1023, 1023, 1023, 1023};
int sensorMax[5] = {0, 0, 0, 0, 0};
int threshold[5];

// --- Variabel Kecepatan & PID ---
int baseSpeed = 100;
int maxSpeed = 200;
float Kp = 15.0, Ki = 0.0, Kd = 10.0;
float error = 0, previous_error = 0, PID_value = 0;

bool robotBerhenti = false;

void setup() {
  pinMode(13, OUTPUT); // LED bawaan Arduino untuk indikator kalibrasi
  
  pinMode(ENA, OUTPUT); pinMode(IN1, OUTPUT); pinMode(IN2, OUTPUT);
  pinMode(IN3, OUTPUT); pinMode(IN4, OUTPUT); pinMode(ENB, OUTPUT);
  pinMode(S1, INPUT); pinMode(S2, INPUT); pinMode(S3, INPUT);
  pinMode(S4, INPUT); pinMode(S5, INPUT);

  // --- PROSES AUTO KALIBRASI SENSOR (4 DETIK) ---
  digitalWrite(13, HIGH); // LED Menyala: Tanda kalibrasi dimulai
  for (int i = 0; i < 400; i++) {
    for (int j = 0; j < 5; j++) {
      int val = analogRead(sensorPins[j]);
      if (val > sensorMax[j]) sensorMax[j] = val;
      if (val < sensorMin[j]) sensorMin[j] = val;
    }
    delay(10);
  }
  // Menghitung titik tengah antara hitam dan putih
  for (int j = 0; j < 5; j++) {
    threshold[j] = (sensorMax[j] + sensorMin[j]) / 2;
  }
  digitalWrite(13, LOW); // LED Mati: Kalibrasi selesai
  delay(2000); // Jeda 2 detik sebelum robot siap jalan
}

void loop() {
  if (robotBerhenti) {
    stopRobot();
    return;
  }
  bacaSensor();
  hitungPID();
  kontrolMotor();
}

void bacaSensor() {
  // Mengubah sinyal Analog menjadi Digital dengan membandingkan Threshold
  // *Catatan: Jika sensor Anda membaca putih sebagai nilai tinggi, ubah tanda '>' menjadi '<'
  int s1 = analogRead(S1) > threshold[0] ? 1 : 0; 
  int s2 = analogRead(S2) > threshold[1] ? 1 : 0;
  int s3 = analogRead(S3) > threshold[2] ? 1 : 0;
  int s4 = analogRead(S4) > threshold[3] ? 1 : 0;
  int s5 = analogRead(S5) > threshold[4] ? 1 : 0;

  // Menggabungkan nilai 5 sensor menjadi 1 nilai biner menggunakan bitshift
  int binerSensor = (s1 << 4) | (s2 << 3) | (s3 << 2) | (s4 << 1) | s5;

  // KONDISI KHUSUS & LOGIKA PID MENGGUNAKAN PERCABANGAN (SWITCH-CASE)
  switch (binerSensor) {
    // SEMUA HITAM ATAU NOISE LEBAR (Diasumsikan sebagai Garis Start, Cabang, atau Finish)
    case 0b11111: case 0b11110: case 0b01111: case 0b11101: 
    case 0b10111: case 0b11100: case 0b11010: case 0b10100: 
    case 0b00111: case 0b01011: case 0b00101: case 0b11001: 
    case 0b10110: case 0b10010: case 0b10011: case 0b01101: 
    case 0b01001: case 0b01110: case 0b11011: case 0b10101: 
    case 0b10001: case 0b01010:
      totalHitam++; 

      if (totalHitam == 1) { // A. GARIS START
        error = 0; 
        majuDikit(); 
      }
      else if (totalHitam > 1 && indexCabang < jumlahCabangDiTengah) { // B. CABANG
        int arah = rute[indexCabang];
        if (arah == 1) error = -5;      // Kiri
        else if (arah == 2) error = 5;  // Kanan
        else error = 0;                 // Lurus
        indexCabang++;
        majuDikit(); 
      }
      else { // C. FINISH
        robotBerhenti = true;
      }
      break;

    // LOGIKA PID NORMAL (Mengikuti Garis)
    case 0b10000: error = -4; break;
    case 0b11000: error = -3; break;
    case 0b01000: error = -2; break;
    case 0b01100: error = -1; break;
    case 0b00100: error = 0;  break;
    case 0b00110: error = 1;  break;
    case 0b00010: error = 2;  break;
    case 0b00011: error = 3;  break;
    case 0b00001: error = 4;  break;

    // LOGIKA KELUAR JALUR (Semua Putih) -> 00000
    case 0b00000:
      if (previous_error < 0) error = -5; else error = 5;
      break;
      
    // SAFETY CATCH-ALL (Praktik baik, meskipun 32 kemungkinan sudah tertulis)
    default:
      error = previous_error;
      break;
  }
}

void hitungPID() {
  float P = error;
  float I = I + error;
  float D = error - previous_error;
  PID_value = (Kp * P) + (Ki * I) + (Kd * D);
  previous_error = error;
}

void kontrolMotor() {
  int leftSpeed = baseSpeed + PID_value;
  int rightSpeed = baseSpeed - PID_value;
  leftSpeed = constrain(leftSpeed, -200, maxSpeed);
  rightSpeed = constrain(rightSpeed, -200, maxSpeed);
  moveMotor(leftSpeed, rightSpeed);
}

void moveMotor(int left, int right) {
  if (left >= 0) { digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW); } 
  else { digitalWrite(IN1, LOW); digitalWrite(IN2, HIGH); }
  
  if (right >= 0) { digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW); } 
  else { digitalWrite(IN3, LOW); digitalWrite(IN4, HIGH); }
  
  analogWrite(ENA, abs(left)); analogWrite(ENB, abs(right));
}

void majuDikit() {
  moveMotor(baseSpeed, baseSpeed);
  delay(200); // Waktu lompatan garis tebal
}

void stopRobot() {
  digitalWrite(IN1, LOW); digitalWrite(IN2, LOW);
  digitalWrite(IN3, LOW); digitalWrite(IN4, LOW);
  analogWrite(ENA, 0); analogWrite(ENB, 0);
}

5. Panduan Tuning PID & Kalibrasi

Mengisi kode saja tidak cukup. Kemampuan motor, tingkat pantulan cahaya di ruangan, dan sasis setiap robot berbeda. Anda wajib melakukan langkah-langkah berikut.

0

Kalibrasi Sensor Manual (Penting!)

Saat saklar robot pertama kali dinyalakan, LED pada Arduino (pin 13) akan menyala selama 4 detik. Dalam rentang waktu ini, pegang robot dan geser bagian sensornya ke kiri dan kanan secara menyilang melewati garis hitam dan latar putih berulang-ulang. Arduino sedang belajar merekam kondisi cahaya ruangan Anda. Setelah LED mati, letakkan robot tepat di atas garis Start, ia akan mulai melaju dalam 2 detik.

1

Setel Awal (Ground Zero)

Ubah nilai di kode menjadi Kp = 5.0; Ki = 0.0; Kd = 0.0;. Pastikan robot berjalan di atas lintasan. Jika ia tidak merespon tikungan dan keluar jalur, naikkan nilai Kp (misal 10, 15, 20).

2

Atasi Gemetar (Osilasi)

Jika nilai Kp sudah pas untuk berbelok, robot biasanya akan "menggeleng" keras ke kiri dan kanan. Untuk menghaluskannya (mengerem efek Kp), naikkan nilai Kd sedikit demi sedikit (misal 5, 10, 15).

3

Kalibrasi Delay Persimpangan

Perhatikan variabel delay(200); di dalam fungsi majuDikit(). Jika robot berhenti sebelum mencapai Finish (karena salah mengira garis persimpangan sebagai garis finish), Naikkan delay. Jika robot sering kelewatan persimpangan tanpa belok, Turunkan delay.

Penjelasan Logika Flow

  • Garis Start: Saat robot baru dinyalakan dan berada di atas garis hitam penuh (semua sensor 1), variabel totalHitam menjadi 1. Robot menjalankan fungsi majuDikit(). Ini "memaksa" robot keluar dari garis start selama 0,2 detik sehingga ia masuk ke mode PID biasa.
  • Cabang di Tengah: Jika robot ketemu garis hitam penuh lagi, dan kita masih punya rute di array rute[], robot akan mengambil instruksi (belok kiri/kanan) lalu majuDikit() lagi agar tidak membaca garis yang sama berulang-ulang.
  • Garis Finish: Jika totalHitam sudah melebihi jumlah cabang yang didaftarkan, robot akan menganggap garis hitam penuh berikutnya adalah Finish dan memicu robotBerhenti = true.

Cara Menentukan Rute

Jika lintasan Anda adalah: START → Belok Kiri → Belok Kanan → FINISH.

Maka settingannya adalah:

int rute[] = {1, 2};
int jumlahCabangDiTengah = 2;

Jika lintasan Anda hanya: START → FINISH (tanpa cabang).

Maka settingannya adalah:

int rute[] = {};
int jumlahCabangDiTengah = 0;

Tips Tuning Tambahan

Jika robot berhenti sebelum benar-benar sampai finish (karena membaca garis cabang sebagai finish), naikkan nilai delay(200) pada fungsi majuDikit(). Sebaliknya, jika robot melewatkan cabang, kecilkan nilainya.

Halaman 1 dari 5
✅ Kode berhasil disalin!