Hello, Arduino community! I’m working on an exciting line-following robot project and could use your expertise to take it to the next level. Currently, I’ve implemented a PID controller for line following, which helps the robot track the line accurately. While the robot performs well, it takes turns slightly late. I’m seeking advice to fine-tune the PID parameters (Kp, Ki, Kd) or improve the control logic to make the turns smoother and more responsive.
Additionally, I’m working towards enabling the robot to solve a maze. The plan is for the robot to conduct an initial exploratory run, where it remembers all the paths it traverses, identifies dead ends, and records valid routes. Once the maze is fully explored, I want the robot to compute the shortest path to the destination and then solve the maze efficiently during the final run.
To accomplish this, I aim to implement algorithms like depth-first search or similar techniques for path recording, with optimizations for selecting the shortest path. The robot should be capable of differentiating nodes, dead ends, and intersections during its exploration. My code currently handles basic node detection and turning decisions, but I need guidance on enhancing the PID for smoother turns and implementing the maze-solving and path-optimization logic effectively.
I’ve included my current code below for reference. I’d be incredibly grateful for any insights, suggestions, or resources that can help refine my robot’s performance and achieve these advanced capabilities. Thank you so much for your support!
Note : The maze will have only 90 degree turns and no curved path will be there.
#define RMotPin1 5
#define RMotPin2 6
#define LMotPin1 7
#define LMotPin2 8
#define LMotSpeedPin 9
#define RMotSpeedPin 10
int MOTOR_SPEED = 150;
#define IR_MAX 710
#define IR_MIN 15
unsigned long noLineStart = 0;
int leftMotorSpeed = MOTOR_SPEED;
int rightMotorSpeed = MOTOR_SPEED;
// PID constants
float Kp = 10; // Proportional constant
float Ki = 0.5; // Integral constant
float Kd = 1; // Derivative constant
float previousError = 0;
float integral = 0;
struct IRSensor
{
IRSensor(int _pin, bool _flipped = false) : m_pin(_pin), m_flipped(_flipped) {}
int value = 0;
int update() { value = analogRead(m_pin); return value; }
inline bool isBlack() { if(m_flipped) return value < THRESHOLD; else return value > THRESHOLD; }
inline bool isWhite() { return !isBlack(); }
static const int THRESHOLD = (IR_MIN + IR_MAX) * 0.5;
private:
int m_pin;
bool m_flipped;
};
// IR Sensors
IRSensor IR1(A0); // Far left
IRSensor IR2(A1); // Left
IRSensor IR3(A2, true); // Center
IRSensor IR4(A3); // Right
IRSensor IR5(A4); // Far right
void updateIR()
{
IR1.update();
IR2.update();
IR3.update();
IR4.update();
IR5.update();
}
void displayIR()
{
Serial.println(String(IR1.value) + ", " + String(IR2.value) + ", " + String(IR3.value) + ", " + String(IR4.value) + ", " + String(IR5.value));
}
void rotateMotor(int rightMotorSpeed, int leftMotorSpeed)
{
if (rightMotorSpeed < 0)
{
digitalWrite(RMotPin1, LOW);
digitalWrite(RMotPin2, HIGH);
}
else if (rightMotorSpeed > 0)
{
digitalWrite(RMotPin1, HIGH);
digitalWrite(RMotPin2, LOW);
}
else
{
digitalWrite(RMotPin1, LOW);
digitalWrite(RMotPin2, LOW);
}
if (leftMotorSpeed < 0)
{
digitalWrite(LMotPin1, LOW);
digitalWrite(LMotPin2, HIGH);
}
else if (leftMotorSpeed > 0)
{
digitalWrite(LMotPin1, HIGH);
digitalWrite(LMotPin2, LOW);
}
else
{
digitalWrite(LMotPin1, LOW);
digitalWrite(LMotPin2, LOW);
}
analogWrite(RMotSpeedPin, abs(rightMotorSpeed));
analogWrite(LMotSpeedPin, abs(leftMotorSpeed));
}
void PID()
{
int error = 0;
// Error calculation: Weighted contributions for more accuracy
if (IR1.isBlack()) error -= 2; // Far left sensor (higher weight)
if (IR2.isBlack()) error -= 1; // Left sensor
if (IR4.isBlack()) error += 1; // Right sensor
if (IR5.isBlack()) error += 2; // Far right sensor (higher weight)
// Proportional term
float proportional = error * Kp;
// Integral term with clamping to prevent windup
integral += error;
integral = constrain(integral, -50, 50); // Clamping the integral term
float integralTerm = integral * Ki;
// Derivative term
float derivative = (error - previousError) * Kd;
// PID output
float correction = proportional + integralTerm + derivative;
// Adjust motor speeds based on correction
leftMotorSpeed = MOTOR_SPEED - correction;
rightMotorSpeed = MOTOR_SPEED + correction;
// Clamp motor speeds for better control
leftMotorSpeed = constrain(leftMotorSpeed, 0, MOTOR_SPEED);
rightMotorSpeed = constrain(rightMotorSpeed, 0, MOTOR_SPEED);
rotateMotor(rightMotorSpeed, leftMotorSpeed);
previousError = error;
}
void Left()
{
// Improved left turn logic with sensor feedback
rotateMotor(150, 0);
delay(150); // Initial turn
while (!IR3.isBlack()) updateIR(); // Continue turning until the center sensor detects the line
rotateMotor(0, 0); // Stop motors
}
void Right()
{
// Improved right turn logic with sensor feedback
rotateMotor(0, 150);
delay(150); // Initial turn
while (!IR3.isBlack()) updateIR(); // Continue turning until the center sensor detects the line
rotateMotor(0, 0); // Stop motors
}
void StepForward()
{
rotateMotor(MOTOR_SPEED, MOTOR_SPEED); // Move forward
delay(200); // Short step delay for stabilization
rotateMotor(0, 0); // Stop motors
}
void UTurn()
{
// Improved U-turn with feedback
rotateMotor(-MOTOR_SPEED, MOTOR_SPEED);
delay(300); // Start turning
while (!IR3.isBlack()) updateIR(); // Complete U-turn when center sensor detects the line
rotateMotor(0, 0);
Serial.println("UTurn");
}
void detectNode()
{
const bool farLeft = IR1.isBlack();
const bool nearLeft = IR2.isBlack();
const bool center = IR3.isBlack();
const bool nearRight = IR4.isBlack();
const bool farRight = IR5.isBlack();
// Case: No line detected for all sensors
if (!farLeft && !nearLeft && !center && !nearRight && !farRight)
{
static unsigned long noLineStart = 0;
if (noLineStart == 0)
noLineStart = millis();
// If the line is not detected for over 300ms, perform a U-turn
if (millis() - noLineStart > 300)
{
UTurn();
delay(300);
StepForward();
noLineStart = 0; // Reset the timer
return;
}
}
else
{
noLineStart = 0; // Reset timer if any line is detected
}
// Node detection: When multiple sensors detect black
const bool isNode = (farLeft + nearLeft + center + nearRight + farRight) > 2;
if (!isNode)
{
PID(); // Continue normal line following
return;
}
Serial.println("Node detected");
// Handling turn decisions based on detected paths
if (nearLeft){
MOTOR_SPEED = 89;
}
else if (nearRight){
MOTOR_SPEED = 89;
}
else if (farLeft && !farRight)
{
Left(); // Perform left turn
Serial.println("Turning Left");
MOTOR_SPEED = 150;
}
else if (farRight && !farLeft)
{
Right(); // Perform right turn
Serial.println("Turning Right");
MOTOR_SPEED = 150;
}
else
{
StepForward(); // Move forward if neither far left nor far right is strongly detected
Serial.println("Moving Forward");
}
}
void setup()
{
Serial.begin(9600);
Serial.println("Hello");
pinMode(RMotSpeedPin, OUTPUT);
pinMode(RMotPin1, OUTPUT);
pinMode(RMotPin2, OUTPUT);
pinMode(LMotSpeedPin, OUTPUT);
pinMode(LMotPin1, OUTPUT);
pinMode(LMotPin2, OUTPUT);
rotateMotor(MOTOR_SPEED, MOTOR_SPEED);
}
void loop()
{
updateIR();
displayIR();
detectNode();
}
Also for your information upon hardware here are the components i have used :
- Arduino UNO x1
- L298N Motor Driver x1
- 12V LIPO Battery x1
- IR sensors x5
- DC motors x2
- Three Wheel Chasis
And I am also attaching picture of my current robot , the hardware is all ready but not the code
Thanking You in Advance,
Rachit Porwal.