Search
  • Joe Linogao

How to make a buggy complete an obstacle course

In this blog, I show you how I managed to code my buggy to allow it to traverse my obstacle course! I go through some of the problems I had to overcome, as well as a demo video showing it all working (and maybe a fail or two if you're lucky). This is part two of a two-part special for my blogs, so if you haven't read part one yet, you can read it here!


Have you read it? Alright, I trust you. Let's get right into it!

Coding the Buggy to Traverse the Obstacle Course

So, last we left off. I had built the obstacle course and had everything in place. But, there was one thing missing... the buggy. I had to program the buggy for it to move through the course. How was I going to do that?


Firstly, I considered just guessing all the timings and essentially pre-program the entire path into the buggy. I quickly realised that it would be a pain to do, especially since the buggy tended to veer left/right due to the motors (more on this later). Instead, I came up with a plan to use the ultrasonic sensor as a form of obstacle detection. Originally, I wanted the buggy to act like one of those automatic vacuum cleaners that just spin around in place until there is no object in front of them.


I even managed to make some pseudo-code to try to piece together how I could code it. Essentially, the buggy would rotate left or right depending on a random variable, and the duration it would spin for would also be based on a randomiser.


After some testing, I realised that this was unfeasible for the entire course. While it could have made it past the first turn, it was likely that it would end up backtracking taking a longer time to complete the course. So what does my omega-galaxy brain think to do now? Leave this for about 3 hours, and catch up on "Falcon and the Winter Soldier."


EVENTUALLY, I came up with the idea to combine the two ideas. What if the buggy could use the ultrasonic sensor to determine where it was in the course? If it knew where it was, then it would limit the timings I have to change for the turning and the final zone of the course. Some visual aid of this idea is shown below.

"But Joeeeee, how do you code that???" I'M GETTING THERE. Didn't we talk about you talking over me? Anyways, before coding the main code, I had to create some new functions for the motors that would allow the buggy to spin left or right. These weren't hard to do, as it came down to knowing which direction I should let the wheels spin to get the desired results.

// in Motor.cpp 
// 0 = forward motion, 1 = back motion

void Motor::SpinRight(int speed)
{
  digitalWrite(AIN1, 0);
  digitalWrite(BIN1, 1);
  analogWrite(PWMA_LEFT, speed-leftAdj);
  analogWrite(PWMB_RIGHT, speed-rightAdj);
}

void Motor::SpinLeft(int speed)
{
  digitalWrite(AIN1, 1);
  digitalWrite(BIN1, 0);
  analogWrite(PWMA_LEFT,speed-leftAdj);
  analogWrite(PWMB_RIGHT,speed-rightAdj);
}

You may notice that I have some variables, "leftAdj" and "rightAdj" in my code. These were used to counteract any veering that occurred as the buggy moved. This was caused by the motors not spinning at the same speed, despite giving them the same speed values in the code. By changing those two variables, I could force one motor to spin at a slower speed, relative to the "speed" variable I defined, allowing the motors to move at the same speed overall.


After that was done, in the main code I created a global variable called "courseState."

// set you global variable called courseState
int courseState;

In the setup, I let this value equal to 1. Every time it finds an obstacle is hit, this value would increment up by 1.

  // set courseState to 1
  courseState = 1;

I also set up the ultrasonic sensor using examples found online and made it calculate the distance it was from the incoming wall in centimetres.

// define ultrasonic pins 
#define trigPin 11
#define echoPin A3
  // initalise ultrasonic pins 
  // set pin mode for sensor pins 
  pinMode(trigPin, OUTPUT); // Sets the trigPin as an Output
  pinMode(echoPin, INPUT); // Sets the echoPin as an Input 
// Ultrasonic sensor scanning
  // Clears the trigPin
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  
  // Sets the trigPin on HIGH state for 10 micro seconds
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);

  // Reads the echoPin, returns the sound wave travel time in microseconds
  duration = pulseIn(echoPin, HIGH);

  // Calculating the distance, simple DST calculation
  // Disance is in cm
  distance= duration*0.034/2;

  // Prints the distance on the Serial Monitor
  Serial.print("Distance: ");
  Serial.println(distance);

I then created an if statement that would trigger if the incoming obstacle was less than 9 cm away from the buggy. Inside the if statement, I had various switch-case statements which would perform different manoeuvres depending on the value of "courseState." In other words, different functions trigger depending on the buggy's current location.


If the buggy was more than 9cm away from an obstacle, I simply told it to continue moving forward.

  // If more than 9cm away 
  if (distance >= 9){
    Motor.Forward(SPEED);
  }

  // If less than 9cm away 
  else if (distance < 9){
        switch (courseState) {
            case 1:
              rotateLeft();
      
              // add 1 to courseState
              courseState++;
              break;
            case 2:
              rotateRight();
      
              // add 1 to courseState
              courseState++;
              break;
              
            case 3:
              rotateRight2();
      
              // add 1 to courseState
              courseState++;
              break;
              
             case 4:
              finalState();
      
              // add 1 to courseState
              courseState++;
              break;
              
             default:
             // if courseState is not 1 to 4
               rotateLeft();
               courseState++;
               break;   
    }
  }

Note that once a case function is triggered, it increments the "courseState" value, allowing the buggy to progress to the next case function. I created separate void functions to keep the code neat, with the "finalState" function being pre-programmed to manoeuvre through the final zone without using sensors.

///////// VOID FUNCTIONS FOR MANOEUVRES ///////

// Causes buggy to rotate right 90 degrees
void rotateRight(){
  // Stop buggy from moving
  Motor.Stop();
  delay(1000);
  
  // Spin right at specified speed 
  Motor.SpinRight(SPEED);
  delay(660);

  Motor.Stop();
  delay(1000);
}

void rotateRight2(){
  // Stop buggy from moving
  Motor.Stop();
  delay(1000);
  
  // Spin right at specified speed 
  Motor.SpinRight(SPEED);
  delay(610);

  Motor.Stop();
  delay(1000);
}

/////////////////////////

// Causes buggy to rotate left 90 degerees
void rotateLeft(){
    // Stop buggy from moving
  Motor.Stop();
  delay(1000);
  
  // Spin left at specified speed
  Motor.SpinLeft(SPEED);
  // make sure buggy doesn't veer on the straights!
  delay(690);


  Motor.Stop();
  delay(1000);
}

/////////////////////////
// Performs final move to move buggy into position for launcher then 
// leaves aftewards

void finalState(){
    // Stop buggy from moving
  Motor.Stop();
  delay(1000);
  
  // Spin left at specified speed
  Motor.SpinLeft(SPEED);
  // make sure buggy doesn't veer on the straights!
  delay(750);

  // go forward
  Motor.Stop();
  delay(500);

  Motor.Forward(SPEED);
  delay(2300);

  // stop and spin left
  Motor.Stop();
  delay(500);


  // Spin left at specified speed
  Motor.SpinLeft(SPEED);
  // make sure buggy doesn't veer on the straights!
  delay(620);
  // go forward
  
  // stop for 20 seconds
  Motor.Stop();
  delay(20000);
  
  // go out 
    Motor.Forward(SPEED);
  delay(1500);

  Motor.Stop();
  delay(1000);
}

Here's a photo of me testing out the switch case functions and the ultrasonic sensor values.


Buggy Final Demonstration

Well, did it all work? To my surprise, yes. Yes, it did. There were some mishaps, as you can see in the video below.


But after some further tweaks and a bit of luck, I achieved a successful run!

So that's the end of the obstacle course saga! I hope you enjoyed reading along as much as I did doing the whole thing! There's still the Rube Goldberg machine to do, but I have somewhat of an idea for that. So stayed tuned!


Remember to follow my Instagram @joeceengineering to keep up to date with all my engineering antics! As always, stay safe and I hope to see you next time!




28 views0 comments

Recent Posts

See All