Subject: Help Tuning PID for Line Follower & Sensor Adequacy
Hello everyone,
I'm having a tough time calibrating the PID for my line follower robot. My main difficulty is finding a KP value that allows the robot to handle both wide curves and straight paths. For example, the robot struggles when it follows a long straight line and then encounters a sharp turn. Interestingly, if I start the turn immediately (without a preceding straight section), the performance seems acceptable.
My current setup is as follows:
- Line Sensors: 3 KY-033 sensors (using digital readings)
- Motors: TT motors controlled with an L298N module
- Speed Settings:
- Maximum Speed: 200
- Base Speed: 70
- The function
line_follower_pid()is called continuously in the main loop.
For calibration, I've found that a KP value between 60 and 70 works best when KD and KI are set to 0.
However, I'm wondering if I can expect a satisfying result using only these sensors or if I should add more sensors to improve performance. Any advice on PID tuning and sensor configuration would be greatly appreciated.
Below is the relevant code with comments in English that highlight the important parts:
float Robot::errorestimation() {
// Global calibration flag (set to true to enable calibration mode)
bool calibration = true;
// Read individual sensor values
int leftVal = digitalRead(LINE_FOLLOWER_LEFT_PIN); // 1 if line is detected, 0 otherwise
int midVal = digitalRead(LINE_FOLLOWER_MID_PIN);
int rightVal = digitalRead(LINE_FOLLOWER_RIGHT_PIN);
// Calculate error based on weighted sensor values
// Assumption: HIGH (1) means the line is detected.
// We assign weights: left = -1, center = 0, right = +1.
float error = 0.0;
int count = 0;
if (leftVal == HIGH) {
error += -1;
count++;
}
if (midVal == HIGH) {
error += 0;
count++;
}
if (rightVal == HIGH) {
error += 1;
count++;
}
Serial.println(count);
if (count > 0) {
error = error / count;
return error;
} else if (!calibration && count == 0) {
changeMovementState(SHARPTURNING);
return 0;
}
return 0;
}
void Robot::line_follower_pid() {
// Obtain the error value from sensor readings
float error = errorestimation();
// Determine the last turn direction based on the sign of the error
lastTurnDirection = (error > 0) ? RIGHT : LEFT;
// Calculate the elapsed time since the last call (in seconds)
unsigned long currentTime = millis();
float dt;
if (pidLastTime == 0) {
dt = 0.00000000000000000000001; // Default dt for the first call
} else {
dt = (currentTime - pidLastTime) / 1000.0; // Convert milliseconds to seconds
}
pidLastTime = currentTime;
// PID calculations
pidIntegral += error * dt;
float derivative = (error - pidLastError) / dt;
float correction = pidKp * error + pidKi * pidIntegral + pidKd * derivative;
pidLastError = error;
// Calculate motor speeds based on the base speed and PID correction.
// A positive correction increases the left motor speed and decreases the right motor speed.
int leftSpeed = robot_speed + correction;
int rightSpeed = robot_speed - correction;
// Ensure the motor speeds remain within the limits (max speed capped at 200)
if (leftSpeed > ROBOT_MAX_SPEED) leftSpeed = ROBOT_MAX_SPEED;
if (rightSpeed > ROBOT_MAX_SPEED) rightSpeed = ROBOT_MAX_SPEED;
if (leftSpeed < 0) leftSpeed = 0;
if (rightSpeed < 0) rightSpeed = 0;
// Apply the calculated speeds to the motors.
// It is assumed that set_speed() correctly handles the direction (positive values move forward).
moteurG.set_speed(leftSpeed);
moteurD.set_speed(rightSpeed);
// Debug output: Uncomment to print error, correction, and motor speeds.
// Serial.print("Error: ");
// Serial.print(error);
// Serial.print(" Correction: ");
// Serial.print(correction);
// Serial.print(" | LeftSpeed: ");
// Serial.print(leftSpeed);
// Serial.print(" RightSpeed: ");
// Serial.println(rightSpeed);
}
void Robot::sharpturn() {
// Check if the center sensor detects the line during a sharp turn
if (digitalRead(LINE_FOLLOWER_MID_PIN) == HIGH) {
// Optionally, verify over multiple cycles for better stability
resetPID();
set_robot_speed(base_speed);
changeMovementState(LINEFOLLOWING);
} else {
// Continue turning in small increments.
// The increment (here 10°) can be adjusted based on the desired responsiveness.
rotate((lastTurnDirection == RIGHT) ? 10 : -10);
}
}