Building our line following robot was at times frustrating, challenging, but overall an informative and fulfilling project.

Sisyphus or Sissy for short.

Sisyphus or Sissy for short.

Fabrication and circuitry:

The circuit we used for the robot was heavily based off of the example from our lab about connecting a motor driver to a DC motor. The main differences with ours vs the lab was that we had a second motor connected to the motor driver, and that we used 4 AA batteries (at peak providing 6.7volts to the circuit) to power all of it. Everything else stayed pretty similar to that lab, otherwise. We kept a 100uf capacitor in the power rails before the DC motor driver as a way to filter and smooth the power flow. Additionally, we used a 5V regulator from the power rail as a way to get a clean line of 5V to the infrared sensor we used. We ended up going with the Grove Line Following IR sensor, which was somewhat easy to use, however, if I were to do it again, I’d use two of them, more on that later. This particular sensor was super simple because it had a physical setting to adjust the sensitivity of its detection, and after that it only sent out a digital ‘high’ or ‘low’ to the Arduino. We used right angle Dagu gearbox motors that required 5V at 300mA for the stall current.

Narrow early chassis, I’d like to revisit if we continue this for the final. This chassis could probably handle more narrow turns.

Narrow early chassis, I’d like to revisit if we continue this for the final. This chassis could probably handle more narrow turns.

Modular attachment mounting holes.

Modular attachment mounting holes.

Final chassis, preferred sensor mounting style we had to ditch.

Final chassis, preferred sensor mounting style we had to ditch.

Building the chassis for the robot was time consuming, but this is also something I am pretty familiar with. I ended up deciding to go with laser cut wood, and I went through 5 chassis designs before we stuck with one. Getting the right angle motors to balance was really challenging with two wheels, since the mass of the motors will never line up with the “axel” line of the robot. I decided to put a kickstand/foot underneath it towards the back to give it balance. Not super elegant but it got the job done. I also designed the chassis to have modular mounting holes on each side of it. These were cut to the width of the material, so we could friction fit attachments without using glue or permanently affixing stuff together. This came in handy for both the kick stand and the shelf we added to hold the battery pack. We originally had the sensor mounted with standoff screws, and I really liked the look of it, however we had a lot of trouble setting the sensitivity for the proper distance with this mounting style. We ended up mounting it with some semi rigid wires so we could adjust the sensor distance more on the fly.

Code

I will describe the basic behaviors we were aiming for before posting blocks of code for the project. The code we used was a big combination of stuff we came up with based on past labs, using services like Chat GPT and Claude to help us understand structure and some functions, and then fellow students like Nasif and Nikolai.

Initially, Sana and I were aiming to have the line following behavior to be centered around staying in the middle of the line. This would occur but having the sensor have both wheels go forward while sensing black, and turn first right, then left after seeing white. Immediately upon seeing black, the wheels would go straight, and this behavior would repeat. We decided the action of looking right and left would be tied to time somehow, and first setup a basic counting system where it would count motor revolutions, however this resulted in the robot spinning left. After which we settled on using millis for time. This is where we found the most success in this behavioral pattern. At some point in testing, we were able to change how long the robot would look left and right, which correlates to how far left or right it would spin. No matter how different the turns were, it would always only be able to turn right or left consistently. The successful turn was based on which way the robot was programmed to turn first. If it was programmed to turn right first, yet the path veered left, it would lose the path too quickly on that initial right turn. This is when I finally believed that we needed a second IR sensor for it to straddle the line. I understand how this would be done now, and I would go about it by locking the line between the two sensors.

const int LINE_FINDER_PIN = 2; // Grove line finder input
const int AIN1 = 7;
const int AIN2 = 6;
const int PWMA = 4;
const int BIN1 = 8;
const int BIN2 = 9;
const int PWMB = 10;
bool here = true; // Flag for when the robot loses the line
int lostLineCounter = 0; // Counter to track how long the line is lost
void setup() {
Serial.begin(9600); // Initialize serial communication
pinMode(LINE_FINDER_PIN, INPUT);
pinMode(AIN1, OUTPUT);
pinMode(AIN2, OUTPUT);
pinMode(PWMA, OUTPUT);
pinMode(BIN1, OUTPUT);
pinMode(BIN2, OUTPUT);
pinMode(PWMB, OUTPUT);
}
void loop() {
int lineValue = digitalRead(LINE_FINDER_PIN); // Read the value from the line finder
if (lineValue == HIGH) { // Black line detected
Serial.println("Black");
// Move forward
digitalWrite(AIN1, HIGH); // Motor A forward
digitalWrite(AIN2, LOW);
analogWrite(PWMA, 100); // Speed for motor A
digitalWrite(BIN1, HIGH); // Motor B forward
digitalWrite(BIN2, LOW);
analogWrite(PWMB, 100); // Speed for motor B
// Reset lost line flag and counter
here = true;
lostLineCounter = 0;
delay(250); // Pause for stability
} else if (lineValue == LOW) { // White line detected
Serial.println("White");
// Set lost line flag and reset counter
here = false;
lostLineCounter = 0;
delay(250); // Pause for stability
}
// If we've lost the line for a while, enter search mode
if (here == false) {
lostLineCounter++;
if (lostLineCounter > 10) { // Threshold for how long the line has been lost
Serial.println("LEFT");
// Turn left to search for the line
digitalWrite(AIN1, LOW);
digitalWrite(AIN2, LOW); // Stop motor A
digitalWrite(BIN1, HIGH); // Motor B forward (left turn)
digitalWrite(BIN2, LOW);
analogWrite(PWMB, 100);
delay(250); // Pause for stability
}
}
delay(100); // Short delay for stability between loops
}

Eventually, we realized we might be able to find success by having it stay on one side of the line, meaning it would only really need to worry about making right or left turns, but not both. This behavior looked like if the sensor sees white, it turns right (if it’s on the left side of the line) and once the sensor sees black, it would turn left away from the line. In both cases the right and left turns would be accompanied by some forward motion. This isn’t super quick, but it follows lines!

const int LINE_FINDER_PIN = 2;  // Grove line finder input
const int AIN1 = 7;
const int AIN2 = 6;
const int PWMA = 4;    // Motor driver PWM pin
const int BIN1 = 8;
const int BIN2 = 9;
const int PWMB = 10;

// Speed settings for different movements
const int FORWARD_SPEED = 65;     // Speed for forward motion
const int TURN_SPEED = 30;        // Speed for turning component

void setup() {
  Serial.begin(9600);
  pinMode(LINE_FINDER_PIN, INPUT);
  pinMode(AIN1, OUTPUT);
  pinMode(AIN2, OUTPUT);
  pinMode(PWMA, OUTPUT);
  pinMode(BIN1, OUTPUT);
  pinMode(BIN2, OUTPUT);
  pinMode(PWMB, OUTPUT);
}

int readLineValue() {
  return digitalRead(LINE_FINDER_PIN);
}

// Combined right turn + forward motion
void turnRightWithForward() {
  // Right motor slower than left for turning
  digitalWrite(AIN1, HIGH);  // Left motor forward
  digitalWrite(AIN2, LOW);
  analogWrite(PWMA, FORWARD_SPEED);  // Left motor full speed
  
  digitalWrite(BIN1, HIGH);  // Right motor forward
  digitalWrite(BIN2, LOW);
  analogWrite(PWMB, TURN_SPEED);  // Right motor slower
  
  Serial.println("Turning right + forward");
}

// Combined left turn + forward motion
void turnLeftWithForward() {
  // Left motor slower than right for turning
  digitalWrite(AIN1, HIGH);  // Left motor forward
  digitalWrite(AIN2, LOW);
  analogWrite(PWMA, TURN_SPEED);  // Left motor slower
  
  digitalWrite(BIN1, HIGH);  // Right motor forward
  digitalWrite(BIN2, LOW);
  analogWrite(PWMB, FORWARD_SPEED);  // Right motor full speed
  
  Serial.println("Turning left + forward");
}

void loop() {
  int lineValue = readLineValue();
  
  if (lineValue == HIGH) {  // On black line
    // Turn left + forward until we lose the line
    turnLeftWithForward();
    Serial.println("Found black - turning left+forward");
  } 
  else {  // On white surface
    // Turn right + forward until we find the line
    turnRightWithForward();
    Serial.println("Found white - turning right+forward");
  }
  
  delay(250);  
}

IMG_0240.gif