PID calibration strugling using 3 Digital sensor

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);
  }
}

There are many tutorials on line that cover long ago established, systematic approaches to PID tuning, as well as entire textbooks dedicated to control theory.

Kd is required for responding to the difference (for example) between tracking sharp turns and straight lines, so it would be a good idea to look up those methods, and learn how others have solved the problem. Hint: the search phrase "pid tuning" works.

By the way, can you explain the logic of the following lines of code?

  }
  if (midVal == HIGH) {
    error += 0;
    count++;
  }

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.