Manual driving is limited by line-of-sight. To implement autonomous navigation, we integrate dual Infrared Line-Tracking Sensors (TrackingPin_L and TrackingPin_R). These sensors output binary feedback depending on whether they detect a highly reflective light floor or a non-reflective dark path.To allow transitioning between manual piloting and automatic tracking, we establish a State Machine using a RobotMode enumeration. Pressing a designated toggle button (0x1C or "OK") alternates the core operating architecture.
This step implements a Finite State Machine utilizing the remote's OK button (0x1C). You can now toggle mid-flight between manual control and an automated tracking routine monitored by the optical sensors on pins A2 and A3.
#include <Arduino.h>
#include <IRremote.hpp>
const int motorPinA_1A = 5;
const int motorPinA_1B = 6;
const int motorPinB_1A = 9;
const int motorPinB_1B = 10;
const int IR_RECEIVE_PIN = 2;
// Line Tracking Sensors (Assigned to dedicated conflict-free analog pins)
const int TrackingPin_L = A2;
const int TrackingPin_R = A3;
enum RobotMode {
MODE_MANUAL,
MODE_AUTONOMOUS
};
RobotMode currentMode = MODE_MANUAL;
const int motorSpeed = 200;
int trackSpeed = 200; // Optimized track following velocity
unsigned long lastCommandTime = 0;
const unsigned long commandTimeout = 250;
void motor(int A1, int A2, int B1, int B2) {
analogWrite(motorPinA_1A, A1);
analogWrite(motorPinA_1B, A2);
analogWrite(motorPinB_1A, B1);
analogWrite(motorPinB_1B, B2);
}
void exec_cmd(byte key_val) {
switch (key_val) {
case 0x1C: // OK Button - Toggle Manual/Autonomous operations
if (currentMode == MODE_MANUAL) {
currentMode = MODE_AUTONOMOUS;
Serial.println("Mode changed: AUTONOMOUS (Tracking Active)");
} else {
currentMode = MODE_MANUAL;
motor(0, 0, 0, 0);
Serial.println("Mode changed: MANUAL (Remote Override)");
}
break;
case 0x18: if (currentMode == MODE_MANUAL) motor(0, motorSpeed, 0, motorSpeed); break;
case 0x08: if (currentMode == MODE_MANUAL) motor(motorSpeed, 0, 0, motorSpeed); break;
case 0x5A: if (currentMode == MODE_MANUAL) motor(0, motorSpeed, motorSpeed, 0); break;
case 0x52: if (currentMode == MODE_MANUAL) motor(motorSpeed, 0, motorSpeed, 0); break;
default: if (currentMode == MODE_MANUAL) motor(0, 0, 0, 0); break;
}
}
void setup() {
Serial.begin(9600);
// pinMode(motorPinA_1A, OUTPUT);
// pinMode(motorPinA_1B, OUTPUT);
// pinMode(motorPinB_1A, OUTPUT);
// pinMode(motorPinB_1B, OUTPUT);
IrReceiver.begin(IR_RECEIVE_PIN, ENABLE_LED_FEEDBACK);
pinMode(TrackingPin_L, INPUT_PULLUP);
pinMode(TrackingPin_R, INPUT_PULLUP);
}
void loop() {
if (IrReceiver.decode()) {
byte command = IrReceiver.decodedIRData.command;
exec_cmd(command);
if (currentMode == MODE_MANUAL) lastCommandTime = millis();
IrReceiver.resume();
}
if (currentMode == MODE_MANUAL) {
if (millis() - lastCommandTime > commandTimeout) {
motor(0, 0, 0, 0);
}
}
else if (currentMode == MODE_AUTONOMOUS) {
int trackL = digitalRead(TrackingPin_L);
int trackR = digitalRead(TrackingPin_R);
int Track = trackL * 2 + trackR; // Binary encoding mapping sensor states
if (Track == 0) {
motor(0, 0, 0, 0); // Lost line entirely -> Stop
}
else if (Track == 1) {
motor(0, trackSpeed, trackSpeed, 0); // Correct path: Pivot Right
}
else if (Track == 2) {
motor(trackSpeed, 0, 0, trackSpeed); // Correct path: Pivot Left
}
else if (Track == 3) {
motor(0, trackSpeed, 0, trackSpeed); // Center aligned -> Drive Forward
}
delay(10);
}
}