Code
Autonomous and Bluetooth Control (Arduino C)
Enrolled students will be able to copy or download code.
autonomous_bluetooth_car.ino
// This is the sketch to drive a car using in an autonomous mode using sensors to follow lanes and detect obstacles.
// And also to drive it manually using control signals from an app over Bluetooth. The car can switch between modes (either one at a time).
// The car uses battery operated motors controlled by an L293D shield for the Arduino UNO.
// The left and right turns are managed by varying the speeds of the wheels or by changing their direction of rotation.
// For example, rotating right wheels backwards and left wheels forward will make the car turn right.
// The sketch reads data from sensors - two IR sensors for line detection and an ultrasound sensor for obstacle and distance detection.
// The ultrasound sensor is mounted on a servo to check for obstacles straight ahead, on the left, and on the right.
/* LIBRARIES */
// Motor driver
#include <AFMotor.h> //<https://github.com/adafruit/Adafruit-Motor-Shield-library>
// Servo
#include <Servo.h> //<https://www.arduino.cc/reference/en/libraries/servo/>
// PIN DEFINITIONS
// Note: Analog pins can be used as digital pins but digital pins CANNOT be used as analog pins.
// Analog pins go A1, A2 and so on, while digital pins go 1, 2, and so on, equivalent to D1, D2, and so on.
// byte is an alias for uint8_t, either can be used.
const uint8_t IR_SENSOR_LEFT = A3; //left side IR sensor
const uint8_t IR_SENSOR_RIGHT = A2; //right side IR sensor
const uint8_t ECHO_PIN = A1; //echo pin of determineDistanceFromUltrasound sensor
const uint8_t TRIGGER_PIN = A0; //trigger pin of determineDistanceFromUltrasound sensor
// The L293D shield drives the servos and the motors.
const uint8_t SERVO_PIN = 9; // The ultrasound sensor servo is connected to the motor driver shield pin which is connected to pin 9 of the Arduino UNO.
// CONTROL SIGNAL DEFINITIONS
// The car takes multiple single character inputs from an app over Classic Bluetooth in the manual control mode.
// The characters and actions are defined as constants below:
// const is preferred to #define
// But if you do use #define note that there is no semicolon at the end of the line.
const char signal_mode_rc = 'C';
const char signal_mode_auto = 'A';
const char signal_mode_neutral = 'N';
const char signal_move_forward = 'F';
const char signal_move_backward = 'B';
const char signal_turn_left = 'L'; // Capital L, this keeps on turning in place.
const char signal_turn_right = 'R'; // Capital R, this keeps on turning in place.
const char signal_nudge_left = 'l'; // Small l, a nudge is better for a regular path.
const char signal_nudge_right = 'r'; // Small r, a nudge is better for a regular path.
const char signal_stop = '0';
const char signal_low_speed = '1';
const char signal_medium_speed = '2';
const char signal_high_speed = '3';
// OTHER CONSTANTS
// Speed values can go from 0 to 255.
// The speed values have been determined after testing on a smooth floor and with constant weight and for optimum functioning.
// The speed will vary accorinding to floor and weight on the car.
// The turning radius can be changed by changing the speed of the respective wheels in the left and right functions.
const uint8_t low_speed = 75;
const uint8_t medium_speed = 95;
const uint8_t high_speed = 115;
const uint8_t obstacle_threshold = 30; // In centimeters.
// This is to calculate the distance of an obstacle using the ultrasound sensor.
const float speed_of_sound = 0.034;
Servo myservo; // Create a servo object to control a servo; twelve servo objects can be created on most boards.
// Initialising variables for programming logic and data.
bool mode_set = false;
int mode; // Default to control mode.
int M = 1 ; // control variable - WHAT IS THIS FOR?
int bt_data; // Data received over Bluetooth. Convert to char().
int ir_sensor_left_value; // Left IR sensor value
int ir_sensor_right_value; // Right IR sensor value
// Keep speeds the same for RC or Auto mode.
int speed = low_speed; // default to low_speed on start.
// int speeed = 255; //speed varaiable for rc_mode
// int speed = 0;
// For ultrasound and distance measurement, moved to the function as they are local variables.
// long duration;
// float distanceCm;
// All distances are in centimeters.
float distance_forward, distance_left, distance_right;
// int distance_left, distance_right;
// Create motor objects.
// 1, 2, 3, 4 are the LD293 shield pins to which the motors are connected.
AF_DCMotor motor1(1);
AF_DCMotor motor2(2);
AF_DCMotor motor3(3);
AF_DCMotor motor4(4);
// Setup code
void setup() {
Serial.begin(9600); // Serial comm at 9600 bps
// Note that Serial.printlm must enclose text in "", single '' do not work.
Serial.println("Autonomous And Bluetooth Controlled Car setup...");
myservo.attach(SERVO_PIN); // Initialise servos
pinMode(IR_SENSOR_LEFT, INPUT);
pinMode(IR_SENSOR_RIGHT, INPUT);
pinMode(ECHO_PIN, INPUT);
pinMode(TRIGGER_PIN, INPUT);
}
/***** FUNCTIONS HERE *****/
// Function to calculate distance of objects using sensor, returns distance in cm.
float determineDistanceFromUltrasound(){
long duration = 0;
float distance_cm = 0;
digitalWrite(TRIGGER_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIGGER_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIGGER_PIN, LOW);
duration = pulseIn(ECHO_PIN, HIGH);
distance_cm = duration * speed_of_sound/2;
return distance_cm;
}
/* Functions to move the car in autonomous mode */
// Break up the motor speed and direction in two generic functions.
// Takes values from 0 to 255, one for each of 4 motors for now.
void setMotorSpeeds(uint8_t speed1, uint8_t speed2, uint8_t speed3, uint8_t speed4){
motor1.setSpeed(speed1);
motor2.setSpeed(speed2);
motor3.setSpeed(speed3);
motor4.setSpeed(speed4);
}
// Takes values FORWARD or BACKWARD, one for each of 4 motors for now.
void setMotorDirections(uint8_t direction1, uint8_t direction2, uint8_t direction3, uint8_t direction4){
motor1.run(direction1);
motor2.run(direction2);
motor3.run(direction3);
motor4.run(direction4);
}
// Move forward at the selected speed.
void moveForward(){
setMotorSpeeds(speed, speed, speed, speed);
setMotorDirections(FORWARD, FORWARD, FORWARD, FORWARD);
// DEBUG
Serial.println("moving forward");
}
// Move backward at the selected speed.
void moveBackward(){
setMotorSpeeds(speed, speed, speed, speed);
setMotorDirections(BACKWARD, BACKWARD, BACKWARD, BACKWARD);
// DEBUG
Serial.println("moving backward");
}
// Nudge left
void nudgeLeft(){
setMotorSpeeds(20, 170, 170, 20);
setMotorDirections(BACKWARD, FORWARD, FORWARD, BACKWARD);
// DEBUG
Serial.println("nudging left");
}
// Nudge right
void nudgeRight(){
setMotorSpeeds(170, 20, 20, 170);
setMotorDirections(FORWARD, BACKWARD, BACKWARD, FORWARD);
// DEBUG
Serial.println("nudging right");
}
// to turn left
void turnLeft(){
setMotorSpeeds(0, 200, 200, 0);
setMotorDirections(FORWARD, FORWARD, FORWARD, FORWARD);
// DEBUG
Serial.println("turning left");
}
// to turn right
void turnRight(){
setMotorSpeeds(200, 0, 0, 200);
setMotorDirections(FORWARD, FORWARD, FORWARD, FORWARD);
// DEBUG
Serial.println("turning right");
}
// To rotate 180 degrees in place.
void rotate(){
setMotorSpeeds(255, 255, 255, 255);
setMotorDirections(FORWARD, BACKWARD, BACKWARD, FORWARD);
// DEBUG
Serial.println("rotating in place");
}
// Function to stop all wheels and motors (commmon for both modes)
void stopAll(){
setMotorSpeeds(0, 0, 0, 0);
setMotorDirections(RELEASE, RELEASE, RELEASE, RELEASE);
// DEBUG
Serial.println("stopping");
}
// --------------- functions to maneuver the car in RC mode --------------- //
// WHY ARE THE TWO DIFFERENT?
// void forward(){
// motor.setSpeed(speed);
// motor3.setSpeed(speed);
// motor2.setSpeed(speed);
// motor1.setSpeed(speed);
// motor.run(FORWARD);
// motor3.run(FORWARD);
// motor2.run(FORWARD);
// motor1.run(FORWARD);
// Serial.println("moving forward");
// }
// void backward(){
// motor.setSpeed(speed);
// motor3.setSpeed(speed);
// motor2.setSpeed(speed);
// motor1.setSpeed(speed);
// motor.run(BACKWARD);
// motor3.run(BACKWARD);
// motor2.run(BACKWARD);
// motor1.run(BACKWARD);
// Serial.println("moving backward");
// }
// Function to turn left on the spot - NOT USED
void hardLeft(){
// // Set motor speeds
// motor.setSpeed(speed);
// motor3.setSpeed(speed);
// motor2.setSpeed(speed);
// motor1.setSpeed(speed);
// // Set motor directions
// motor.run(BACKWARD);
// motor3.run(FORWARD);
// motor2.run(FORWARD);
// motor1.run(BACKWARD);
setMotorSpeeds(speed, speed, speed, speed);
setMotorDirections(BACKWARD, FORWARD, FORWARD, BACKWARD);
// DEBUG
Serial.println("hard left");
}
void left(){
setMotorSpeeds(0, speed, speed, 0);
setMotorDirections(RELEASE, FORWARD, FORWARD, RELEASE);
// DEBUG
Serial.println("moving left");
}
// Function to turn right on the spot - NOT USED
void hardRight(){
// Set motor speeds
motor4.setSpeed(speed);
motor3.setSpeed(speed);
motor2.setSpeed(speed);
motor1.setSpeed(speed);
// Set motor directions
motor3.run(BACKWARD);
motor4.run(FORWARD);
motor1.run(FORWARD);
motor2.run(BACKWARD);
}
void right(){
setMotorSpeeds(speed, 0, 0, speed);
setMotorDirections(FORWARD, RELEASE, RELEASE, FORWARD);
// DEBUG
Serial.println("moving right");
}
// Function to compare distance between left and right and taking turn in the direction which has object at a greater distance.
void compareDistanceAndMove(){
stopAll();
Serial.println("comparing left and right distances");
if (distance_left < obstacle_threshold && distance_right < obstacle_threshold ){ // if there is an obstacle on both sides then go back and rotate
moveBackward();
delay(1000);
rotate();
delay(1000);
stopAll();
}
else if(distance_left > distance_right){ // if obstacle is on the right side then turn left
moveForward();
delay(400);
turnLeft();
delay(1000);
stopAll();
delay(100);
}
else{ // if obstacle is on the left side then turn right
moveForward();
delay(400);
turnRight();
delay(1000);
stopAll();
delay(100);
}
}
// This function is to detect distance of objects on left and right side of the car.
// The sensor is mounted on top of the servo and the servo rotated to point the ultrasound sensor in the desired direction.
void checkSideObstacles(){
stopAll();
delay(100);
myservo.write(30); // turn sensor to the right.
delay(500);
distance_right = determineDistanceFromUltrasound();
Serial.print("distance right: ");
Serial.println(distance_right);
delay(100);
myservo.write(150); // turn sensor to the left.
delay(500);
distance_left = determineDistanceFromUltrasound();
Serial.print("distance left: ");
Serial.println(distance_left);
delay(100);
myservo.write(95);
delay(500);
// Decide which side has no obstacle and turn and move forward in that direction.
compareDistanceAndMove();
}
// This is the function for autonomous mode.
// Once the car is in this mode it will start moving forward.
// If black colored lane lines are set up the car will stay within those lines.
// The IR sensors on both front sides of the car detect the black line when the car reaches the edge and the car nudges itself back inside.
// The car will stop if it detects an object with the ultrasound sensor in front of it at less than 30cm.
// The servo will be trigerred to move the ultrasound sensor right and left to check for obstacles on either side.
// The car will turn to the side which there are no obstacles.
// If both sides have obstacles the car will rotate in place and go back where it came from.
void startAutonomousMode() {
// DEBUG
// Serial.println("autonomous mode");
// Before starting, check if there is an obstacle.
distance_forward = determineDistanceFromUltrasound(); //to check distance of object in front
// DEBUG
Serial.print("distance forward: " );
Serial.println(distance_forward);
if(distance_forward <= obstacle_threshold){
Serial.print("obstacle detected");
// Send indicator data to app when transmit circuit is ready.
stopAll();
delay(3000); // wait for 3 seconds to measure again, in case the obstacle has moved.
distance_forward = determineDistanceFromUltrasound();
if (distance_forward <= obstacle_threshold){ //if it detects an object at a distance less than the threshold again it will call the check side function to turn accordingly
checkSideObstacles();
}
}
//Logic to keep inside lanes.
ir_sensor_left_value = digitalRead(IR_SENSOR_LEFT); //left IR value
ir_sensor_right_value = digitalRead(IR_SENSOR_RIGHT); // right IR value
Serial.print("Left IR : " );
Serial.println(ir_sensor_left_value);
Serial.print("Right IR : " );
Serial.println(ir_sensor_right_value);
//
if (ir_sensor_left_value == 1 && ir_sensor_right_value == 1){ // if both values are 0 then it means neither sensor is picking up black line , means the car is inside the lane and can move forward
Serial.println("within lane moving forward");
moveForward();
}
else if (ir_sensor_left_value == 0 && ir_sensor_right_value == 1){ //left sensor value is 1 and right sensor value is 0 , the car is on the left side border of the lane and should nudge towards the right
stopAll(); //stopAll and delay is required in order to slow down the car so that it does not go off track and is able to take turn
delay(50);
Serial.println("nudging right");
nudgeRight();
}
else if (ir_sensor_left_value == 1 && ir_sensor_right_value == 0) { //left sensor value is 0 and right sensor value is 1 , the car is on the left side border of the lane and should nudge towards the left
stopAll();
delay(50);
Serial.println("nudging left");
nudgeLeft();
}
else if (ir_sensor_left_value == 0 && ir_sensor_right_value == 0) { // optional condition if both detect black line
stopAll();
Serial.println("unknown terrain, stopping.");
delay(100);
}
// Check for control signals, the only ones to respond to are speed change, stop, and mode change.
if(Serial.available() > 0){
bt_data = Serial.read();
switch(char(bt_data)){
case signal_low_speed:
speed = low_speed;
setMotorSpeeds(speed, speed, speed, speed); // This should change the speed while maintaining the direction.
Serial.println("low speed");
break;
case signal_medium_speed:
speed = medium_speed;
setMotorSpeeds(speed, speed, speed, speed); // This should change the speed while maintaining the direction.
Serial.println("medium speed");
break;
case signal_high_speed:
speed = high_speed;
setMotorSpeeds(speed, speed, speed, speed); // This should change the speed while maintaining the direction.
Serial.println("high speed");
break;
case signal_stop:
stopAll();
Serial.println("stopped");
break;
case signal_mode_neutral:
Serial.println("exiting autonomous mode");
stopAll();
mode = signal_mode_neutral;
mode_set = false;
break;
}
// if (bt_data == signal_low_speed){ //low speed , the speed variable will be set to 75 so that the motors run at that speed
// speed = low_speed;
// Serial.println("low speed");
// }
// if (bt_data == signal_medium_speed){ //medium speed , the speed variable will be set to 95 so that the motors run at that speed
// speed = medium_speed;
// Serial.println("medium speed");
// }
// if (bt_data == signal_high_speed){//high speed , the speed variable will be set to 115 so that the motors run at that speed
// speed = high_speed;
// Serial.println("high speed");
// }
// these values of speed have been chosen since for any higher value the car will not be able to follow the lane and go off-track
// higher speed can be made possible by increasing the number of IR sensors and increasing lane line width.
// if (bt_data == 'E'){ //to exit the autonomous mode
// Serial.println("exiting mode auto");
// stopAll();
// M = 1; //set control to 1
// }
}
}
// In the RC mode the car will move based on control signals received from an app over Classic Bluetooth.
void startRCMode(){
// DEBUG
// Serial.println("starting RC mode");
// Listen continuously for control signals.
if(Serial.available() > 0){
bt_data = Serial.read(); // Will only read one char, that's all that is expected.
// DEBUG
Serial.println(bt_data);
// Always stop everything between control signals. WHY?
// stopAll();
// Decode the control signal received and call the required function.
// Use switch instead of if-else
switch(char(bt_data)){
case signal_move_forward:
stopAll();
moveForward();
break;
case signal_move_backward:
stopAll();
moveBackward();
break;
case signal_turn_right:
turnRight();
break;
case signal_turn_left:
turnLeft();
break;
case signal_nudge_right:
nudgeRight();
break;
case signal_nudge_left:
nudgeLeft();
break;
case signal_stop:
stopAll();
break;
case signal_low_speed:
speed = low_speed;
break;
case signal_medium_speed:
speed = medium_speed;
break;
case signal_high_speed:
speed = high_speed;
break;
case signal_mode_neutral:
Serial.println("exiting RC mode");
stopAll();
mode = signal_mode_neutral;
mode_set = false;
break;
}
// if (bt_data == 'F'){
// moveForward();
// }
// if (bt_data == 'B'){ //backward movement
// backward();
// }
// if (bt_data == 'L'){ //left
// left();
// }
// if (bt_data == 'R'){ //right
// right();
// }
// if (bt_data == 'S'){ //stop
// stopAll();
// }
// if (bt_data == 'l'){ //low speed , the speed variable will be set to 100 so that the motors run at that speed
// speed = 100;
// Serial.println("low speed");
// }
// if (bt_data == 'm'){ //medium speed , the speed variable will be set to 180 so that the motors run at that speed
// speed = 180;
// Serial.println("medium speed");
// }
// if (bt_data == 'h'){//high speed , the speed variable will be set to 255 so that the motors run at that speed
// speed = 255;
// Serial.println("high speed");
// }
// if (bt_data == 'E'){ //exit mode
// Serial.println("exiting mode rc");
// stopAll();
// M = 1; // set control to 1
// Serial.println(M);
// }
}
}
/* The loop function */
// Listen for a control signal over BT in the loop function to switch to a mode only when mode is not set.
// Once in a mode listen there to switch to another mode or to neutral.
void loop(){
// M = 1 (control is 1) , means that the car is not in any mode right now and is open for an input for modes
// if( M == 1 && Serial.available() > 0 ){ // this if loop listens (only when control is 1) for the message coming via bluetooth to set the mode. it will only run when no mode is selected
// mode = Serial.read();
// Serial.println(mode);
// if (mode == '1' || mode == '2'){
// M = 0; // sets control to 0 indicating it has been shifted to some mode.
// }
// }
// while (mode == '1' && M == 0){ // if mode 1 has been selected the its the rc mode
// startRCMode();
// }
// while (mode == '2' && M == 0) { // if mode 2 has been selected then its the autonomous mode
// startAutonomousMode();
// }
if(!mode_set){
Serial.println("In neutral mode, send A or C to move to auto or remote control mode.");
delay(15000);
}
if(!mode_set && Serial.available() > 0){
bt_data = Serial.read(); // Will only read one char, that's all that is expected.
// DEBUG
Serial.println(char(bt_data)); // Char value
// Serial.println(bt_data); // ASCII value
if(char(bt_data) == signal_mode_rc || char(bt_data) == signal_mode_auto){
mode = char(bt_data); // Set mode
mode_set = true;
}
}
// Mode is set so run the code for that mode where it will listen to and process control signals.
// Both modes will have an exit option that will move to mode_set = false.
if (mode == signal_mode_auto){
startAutonomousMode();
} else if (mode == signal_mode_rc){
startRCMode();
}
}