Introduction: EAL - RC Car With Ultrasound, Magnetic Compass and Bluetooth Control

Picture of EAL - RC Car With Ultrasound, Magnetic Compass and Bluetooth Control

Hello. This instrucable will show how we made a RC-car that can be controlled over your phone using bluetooth and is using an Arduino UNO aswell as sensors. We had acces to a RC car (Motors, frame and battery), that we wanted to work on. Our plan was to control it remotely via a device (PS3 controller, mobile phone or simular), and have Ultrasound sensors to stop the car in case it was about to hit something. The controls for manuel control can be done from an App (Android) that we had help making. We also tried to add a magnetic compass, so it could drive on its own. That did unfortunately not work out, because of electrical noise from the other components. We will look at how we attemped to program it though. To help make the car look better we 3D printed a board to mount the components onto. Made at eal.

We have used various components to make this car. Full list:

Came with car:

DC-motor

Servo motor

Battery

We added:

Arduino UNO

Magnetic Compass: CMPS93

2x Ultrasound sensor: HC-SR04

RN42 Bluetooth Module

HC-Bridge DC Motor Control Shield

Step 1: Video of the Car in Action

Here is a little video showing our car in action being manually controlled.

"Pulse" Kevin MacLeod (incompetech.com)Licensed under Creative Commons: By Attribution 3.0 Licensehttp://creativecommons.org/licenses/by/3.0/

Step 2: The Circuit

Picture of The Circuit

Heres an image of the circuit and how everything is connected together. It is made in Fritzing. We were unable to find suitable icon for 2 components - Motor Control Shield H-bridge and CMPS03 magnetic compass. Therefore we decided to use small boards and connected the pins to those. We have pictures explaining how these components are connected to the Arduino. The action on the breadboard is done to reduce the current from 5v to 3,3v for the bluetooth module.

Step 3: Setup of the Car

Picture of Setup of the Car

In this step we will take a look at how we build the car. First of we have a picture of the car as we got it. We got the important stuff - Underbody, wheels, motors and battery. Then we needed to 3D print a platform that we could mount the components to (We chose green because it is magnificient). As you can see on the pictures. The car looked very messy and primitive during testing before we got the board made.

Step 4: Libraries for the Arduino

We have made custom libraries for our distance sensor and for the magnetic compass to make the program more clean and compact.

The distance library is used to control an ultrasonic sensor and the outcome of this function is a distance in centimeter's. Fist include the library, after that we need to make a setup for example: "DistanceSensor forwardSensor(trigPin, pulsePin);" where trigPin could be 2 and pulsePin 4 on the arduino. When you need the distance you simply use the following: "forwardSensor.getHeading();".

The magnetic compass library is used to get a heading from 0-359 where 0 is north, like the distance library, we need to include it first. Thereafter the setup needs to be made, for example: "MagneticCompass compass(pwmPin);" where pwmPin could be 6 on the arduino. When the heading is needed simply use the following: "compass.getHeading();".

Step 5: Code (Servo)

Picture of Code (Servo)

The car we used had a built-in servo attached already for the steering. But it was a 5 pin one and we had no idea how to connect it, so we did some research and found out that the old 5-pin servo were missing a controller. The car had a small controllerbox that it had used to control the servo. In there it had the controller, but it was covered in glue and we deemed it not worth the hassle to try and take it apart. We wanted to use the original servo, because we knew that it would fit well and work with the steering. But without a way to control the servo we needed to add something. What we did was that we used another newer servo that we took apart and took the controller from that instead and connected it to the servo used by the car. In the datasheet for the servo we learned what pins was what and how we should connect them to the controller board. From there we simply connected it to our Arduino and we could control it, as you would control a normal servo.

/* Include */
#include <Servo.h>                  //Library that contains servo funktions

/* Servo setup */
Servo myServo;                      //create servo object to control a servo
int servoPosRight = 0;              //Servo position right
int servoPosLeft = 0;               //Servo position left
int servoPosInit = 89;              //Initialising servo position
int servoControl = 100;             //Servo control int 200-100=right 100-0=left
int servoControlBluetooth = 100;    //Servo control bluetooth same attributes as servoControl

void setup() {
  /* Servo */
  myServo.attach(9);                //attaches the servo on pin 9 to the servo object
  myServo.write(servoPosInit);      //tell servo to go to position, left = 110, middel = 89, right = 65

void run() {
  /* Servo control*/
  if (servoControl < 100) {
    servoPosRight = map(servoControl, 101, 200, 89, 65);        //Maps int from 100-200 to 89-65
    myServo.write(servoPosRight);                               //Writes mapped pos to servo
  } else if (servoControl > 100) {
    servoPosLeft = map(servoControl, 99, 0, 89, 113);           //Maps int from 100-0 to 89-113
    myServo.write(servoPosLeft);                                //Writes mapped pos to servo
  } else {
    myServo.write(servoPosInit);                                //Writes initial pos to servo
  }

Step 6: Code (DC-motor)

Picture of Code (DC-motor)

The DC-motor is controlled by two PWM signal's, one for forward movement and one for backward.
A PWM signal goes from 0-255. The H-bridge we use in this project has two inhabit inputs that needs to be HIGH/TRUE in order for the bridge to drive the motor. We went a little overkill with the H-bridge because it can take up to 250W, we did this to make sure that we could drive at full throttle (60km/t). An little outtake from the code with some of the functions we use to control the DC-motor:

/* H-bridge setup */
#define  IN_1  3                    //Defining IN_1 as pin 3, PWM signal for forward movement(bridge 1)
#define  IN_2  11                   //Defining IN_2 as pin 11, PWM signal for backward movement(bridge 2)
#define  INH_1 12                   //Defining INH_1 as pin 12, inhibit signal for bridge 1 
#define  INH_2 13                   //Defining INH_1 as pin 13, inhibit signal for bridge 2
int motorSpeedForward = 0;          //Motor speed forward PWM
int motorSpeedBackward = 0;         //Motor speed backward PWM
int motorControl = 100;             //Motor control int 200-100=forward speed 100-0=backward speed
int motorControlBluetooth = 100;    //Motor bluetooth control same attributes as motorControl
int motorControlIntern = 100;       //Motor intern control same attributes as motorControl
int pwmMax = 255;                   //Max PWM signal to DCmotor

void setup() {
  /* H-Bridge */
  pinMode(IN_1, OUTPUT);            //H-brige pinmode for IN_1
  pinMode(IN_2, OUTPUT);            //H-brige pinmode for IN_2
  pinMode(INH_1, OUTPUT);           //H-brige pinmode for INH_1
  pinMode(INH_2, OUTPUT);           //H-brige pinmode for INH_2
  resetPorts();                     //H-brige reset input ports on bridge 1 and 2
  digitalWrite(INH_1, 1);           //H-brige sets sleep mode to off on bridge 1
  digitalWrite(INH_2, 1);           //H-brige sets sleep mode to off on bridge 2
}

void resetPorts() {
  /* Reset input on H-bridge 1 and 2 */
  digitalWrite(IN_1, 0);
  digitalWrite(IN_2, 0);
  }
void run() {
  /* H-bridge/DCmotor control*/
  motorSpeedBackward = map(motorControl, 100, 0, 0, pwmMax);    //Maps int motorControl from 100-0 to 0-pwmMax(0-255)
  motorSpeedForward = map(motorControl, 100, 200, 0, pwmMax);   //Maps int motorControl from 100-200 to 0-pwmMax(0-255)
  
  /*Speed control*/
  if (!autoManual) {
    if (motorControl < 100 && backwardSensor.getDistance() > brakeDistanceBackward) {      //Controls backward movement in automode
      analogWrite(IN_1, motorSpeedBackward);                                               //Writes mapped speed to DCmotor
    } else if (motorControl > 100 && forwardSensor.getDistance() > brakeDistanceForward) { //Controls forward movement in automode
      analogWrite(IN_2, motorSpeedForward);                                                //Writes mapped speed to DCmotor
    } else {
      resetPorts();                                                                        //Stops the car
    }
  } else {
    if (motorControl < 100) {                 //Controls backward movement in manual mode
      analogWrite(IN_1, motorSpeedBackward);  //Writes mapped speed to DCmotor
    } else if (motorControl > 100) {          //Controls forward movement in manual mode
      analogWrite(IN_2, motorSpeedForward);   //Writes mapped speed to DCmotor
    } else {
      resetPorts();                           //Stops the car
    }
  }
}

Step 7: Code (Ultrasound)

Picture of Code (Ultrasound)

There is mounted two ultra sound sensors on the car, these are used to measure the distance to an object. The sensors are only used in the different auto modes, to make sure the car doesn't hit anything. The following code example shows where/how we use the sensors.

/* Include */
#include <distancesensor.h>         //Library that contains distance sensor funktions

/* Distance sensors setup*/
DistanceSensor forwardSensor(4, 2); //Setup for forward sensor with trigPin 4, echoPin 2
DistanceSensor backwardSensor(8, 7);//Setup for backward sensor with trigPin 8, echoPin 7


void run(){

if (motorControl < 100 && backwardSensor.getDistance() > brakeDistanceBackward) { //Controls backward movement in automode
analogWrite(IN_1, motorSpeedBackward); //Writes mapped speed to DCmotor
} else if (motorControl > 100 && forwardSensor.getDistance() > brakeDistanceForward) { //Controls forward movement in automode
analogWrite(IN_2, motorSpeedForward); //Writes mapped speed to DCmotor
} else {
resetPorts(); //Stops the car
}

}

Step 8: Code (Magnetic Compass)

Picture of Code (Magnetic Compass)

In this project we do also use a magnetic compass, from this we can get an heading that goes from 0 to 359, where 0 is north. The magnetic compass have given us some troubles, in the sens that there is a lot of magnetic/electrical noise, so the readings from the compass can go from 100 to 200 in a second without a change of direction. The following code shows the different functions and what not, that is needed to utilize the magnetic compass.

/* Include */
#include <magneticcompass.h>        //Library that contains magnetic compass funktions

/* Mangnetic compass setup*/
MagneticCompass compass(6);         //Setup for magnetic compass with pwm pin 6

void squareMode() {
/* Squaremode */
  motorControl = patternSpeed;            //Assigns speed to motor control
  if (resetTime == 0) {
    resetTime = millis();
  }
  if (millis() - resetTime > 5500) {
    resetTime = 0;
    startDegrees = compass.getHeading();
    targetDegrees = startDegrees + 90;    //How many degrees in each corner
    if (targetDegrees > 359) {
      targetDegrees -= 360;
    }
  }
  turn(targetDegrees);
}

Step 9: Code (Bluetooth)

Picture of Code (Bluetooth)

We use a Bluetooth connection to control the different automatic modes and a manual mode. The Bluetooth module communicates with the arduino over a serial connection, which sends three different variables. The three variables is the following: a speed (motor control), a direction(servo control) and a control(drivemode) variable. If we take a look at the code underneath we see a function with the name serialEvent(), this only runs when there is new data from the Bluetooth module. This function is used to minimize the cycle-time of the program.

/* Include */
#include <arduinojson.h>            //Library that contains json funktions

void setup() {
  Serial.begin(115200);             //Starts bluetooth serial comunication
}

void serialEvent() {
  /*Bluetooth, takes data from the serial and assings them to their variables */
  StaticJsonBuffer<200> jsonBuffer;
  JsonObject& json = jsonBuffer.parseObject(Serial);
  if (json.success()) {
    motorControlBluetooth = json["speed"];
    servoControlBluetooth = json["direction"];
    driveMode = json["control"];
  }
}

Step 10: Android App

Picture of Android App

We got a friend of ours to make an app for android, with this we can control the different modes. First you have to connect to the Bluetooth module on the car, in order to do this you need the mac address from the Bluetooth module. When you have that, click connect and simply type in the address when the prompt appear, then click OK. When connected, try the manual mode, it's a nice way to test if it works and if the connection is stable.

Step 11: Full Code

The code and everything else can be found on Github.

/*Include*/
#include <MagneticCompass.h> //Library that contains magnetic compass functions
#include <DistanceSensor.h> //Library that contains distance sensor functions #include <Servo.h> //Library that contains servo functions #include <ArduinoJson.h> //Library that contains json functions

/* Drive mode setup*/ int driveMode = 0; //Controls the different drive modes int startDegrees = 0; //Used to control when to turn in the different cases int targetDegrees = 0; //Used to define the next turn in the different cases long resetTime = 0; //Used in auto functions #define patternSpeed 120; //Speed in auto functions 0-100-200 (works like motor control) #define patternTurnValue 100; //Turning amount in auto function 0-100

bool autoManual; //Used to define if in auto or manual mode (manual = True) int delayTime = 1; //Used to delay (1 in manual and 100 in auto)

int distanceForward = 0; //Distance it takes to brake without hitting something, forward direction int distanceBackward = 0; //Distance it takes to brake without hitting something, backward direction

/* Servo setup */ Servo myServo; //create servo object to control a servo int servoPosRight = 0; //Servo position right int servoPosLeft = 0; //Servo position left int servoPosInit = 89; //Initialising servo position int servoControl = 100; //Servo control int 200-100=right 100-0=left int servoControlBluetooth = 100; //Servo control bluetooth same attributes as servoControl

/* H-bridge setup */ #define IN_1 3 //Defining IN_1 as pin 3, PWM signal for forward movement(bridge 1) #define IN_2 11 //Defining IN_2 as pin 11, PWM signal for backward movement(bridge 2) #define INH_1 12 //Defining INH_1 as pin 12, inhibit signal for bridge 1 #define INH_2 13 //Defining INH_1 as pin 13, inhibit signal for bridge 2 long brakeDistanceBackward = 100; //Brake distance in CM long brakeDistanceForward = 100; //Brake distance in CM int motorSpeedForward = 0; //Motor speed forward PWM int motorSpeedBackward = 0; //Motor speed backward PWM int motorControl = 100; //Motor control int 200-100=forward speed 100-0=backward speed int motorControlBluetooth = 100; //Motor bluetooth control same attributes as motorControl int motorControlIntern = 100; //Motor intern control same attributes as motorControl int pwmMax = 255; //Max PWM signal to DCmotor

/* Distance sensors setup*/ DistanceSensor forwardSensor(4, 2); //Setup for forward sensor with trigPin 4, echoPin 2 DistanceSensor backwardSensor(8, 7);//Setup for backward sensor with trigPin 8, echoPin 7

/* Mangnetic compass setup*/ MagneticCompass compass(6); //Setup for magnetic compass with pwm pin 6

void setup() { Serial.begin(115200); //Starts bluetooth serial comunication /* Servo */ myServo.attach(9); //attaches the servo on pin 9 to the servo object myServo.write(servoPosInit); //tell servo to go to position, left = 110, middel = 89, right = 65 /* H-Bridge */ pinMode(IN_1, OUTPUT); //H-brige pinmode for IN_1 pinMode(IN_2, OUTPUT); //H-brige pinmode for IN_2 pinMode(INH_1, OUTPUT); //H-brige pinmode for INH_1 pinMode(INH_2, OUTPUT); //H-brige pinmode for INH_2 resetPorts(); //H-brige reset input ports on bridge 1 and 2 digitalWrite(INH_1, 1); //H-brige sets sleep mode to off on bridge 1 digitalWrite(INH_2, 1); //H-brige sets sleep mode to off on bridge 2 }

void resetPorts() { /* Reset input on H-bridge 1 and 2 */ digitalWrite(IN_1, 0); digitalWrite(IN_2, 0); }

void turn(int target) { /* Turn'S the car the fastes route to the target*/ int current = compass.getHeading(); bool result = true; int diff = 0; if (target > current) { diff = target - current; } else { diff = current - target; } int dist = 0; if (diff > 180) { dist = 360 - diff; } else { dist = diff; } if (diff != dist) { result = current > 180; } else { result = target - current > 0; }

if (result) { servoControl = 100 + patternTurnValue; } else { servoControl = 100 - patternTurnValue; } }

void serialEvent() { /*Bluetooth, takes data from the serial and assings them to their variables */ StaticJsonBuffer<200> jsonBuffer; JsonObject& json = jsonBuffer.parseObject(Serial); if (json.success()) { motorControlBluetooth = json["speed"]; servoControlBluetooth = json["direction"]; driveMode = json["control"]; } }

void loop() { /*Drive mode*/ switch (driveMode) { case 0: //STOP motorControl = 100; servoControl = 100; break; case 1: //Drive in square squareMode(); break; case 2: //Drive in rectangel rectangelMode(); break; case 3: //Drive in figure eight pattern eightMode(); break; case 4: //Drive in manuel mode manualMode(); break; } run(); //Run is used to control the servo and DC-motor autoManual = driveMode == 4; //Auto or manual if (!autoManual) { //If auto is true delay = 100, if auto is false delay = 1 delayTime = 100; } else { delayTime = 1; } delay(delayTime); //Delay for stability }

void squareMode() { /* Squaremode */ motorControl = patternSpeed; //Assigns speed to motor control

if (resetTime == 0) { resetTime = millis(); } if (millis() - resetTime > 5500) { resetTime = 0; startDegrees = compass.getHeading(); targetDegrees = startDegrees + 90; //How many degrees in each corner if (targetDegrees > 359) { targetDegrees -= 360; } } turn(targetDegrees); }

void rectangelMode() { /* Rectanglemode */ motorControl = patternSpeed; //Assigns speed to motor control

if (resetTime == 0) { resetTime = millis(); } if (millis() - resetTime > 5500) { resetTime = 0; startDegrees = compass.getHeading(); targetDegrees = startDegrees + 180; //How many degrees in each corner if (targetDegrees > 359) { targetDegrees -= 360; } } turn(targetDegrees); }

void eightMode() { /* Eightmode */ motorControl = patternSpeed; //Assigns speed to motor control

if (resetTime == 0) { resetTime = millis(); } if (millis() - resetTime > 5500) { resetTime = 0; startDegrees = compass.getHeading(); targetDegrees = startDegrees + 270; //How many degrees in each corner if (targetDegrees > 359) { targetDegrees -= 360; } } turn(targetDegrees); }

void manualMode() { servoControl = servoControlBluetooth; //Assigns servo pos to servo control from bluetooth

motorControl = motorControlBluetooth; //Assigns speed to motor control from blutooth }

void run() { /* Servo control*/ if (servoControl < 100) { servoPosRight = map(servoControl, 101, 200, 89, 65); //Maps int from 100-200 to 89-65 myServo.write(servoPosRight); //Writes mapped pos to servo } else if (servoControl > 100) { servoPosLeft = map(servoControl, 99, 0, 89, 113); //Maps int from 100-0 to 89-113 myServo.write(servoPosLeft); //Writes mapped pos to servo } else { myServo.write(servoPosInit); //Writes initial pos to servo }

/* H-bridge/DCmotor control*/ /*Backward*/ if (!autoManual) { brakeDistanceBackward = (motorSpeedBackward * 10) / 10 + 8; //Brake distance in cm, only used in auto brakeDistanceForward = (motorSpeedForward * 10) / 10 + 8; //Brake distance in cm, only used in auto }

motorSpeedBackward = map(motorControl, 100, 0, 0, pwmMax); //Maps int motorControl from 100-0 to 0-pwmMax(0-255) motorSpeedForward = map(motorControl, 100, 200, 0, pwmMax); //Maps int motorControl from 100-200 to 0-pwmMax(0-255)

/*Speed control*/ if (!autoManual) { if (motorControl < 100 && backwardSensor.getDistance() > brakeDistanceBackward) { //Controls backward movement in automode analogWrite(IN_1, motorSpeedBackward); //Writes mapped speed to DCmotor } else if (motorControl > 100 && forwardSensor.getDistance() > brakeDistanceForward) { //Controls forward movement in automode analogWrite(IN_2, motorSpeedForward); //Writes mapped speed to DCmotor } else { resetPorts(); //Stops the car } } else { if (motorControl < 100) { //Controls backward movement in manual mode analogWrite(IN_1, motorSpeedBackward); //Writes mapped speed to DCmotor } else if (motorControl > 100) { //Controls forward movement in manual mode analogWrite(IN_2, motorSpeedForward); //Writes mapped speed to DCmotor } else { resetPorts(); //Stops the car } } }

Step 12: Issues

Picture of Issues

Underways the project, there has been several issues unfortunately. Most of them we have been able to fix relatively easy, but the biggest one we have had has been with the ultrasound sensors. When we apply the code for the ultrasound sensors, the car starts being unresponsive, and we are unable to control it properly over bluetooth. It has proven to be a huge problem that we have not been able to fix completely. However when run the code together without controlling the car over bluetooth the ultrasound sensors work perfectly.

Furthermore we have been working with the CMPS03 magnetic compass, and we have run into problems here aswell. After thorough trial and error, we have found that there is too much electric/magnetic noise on our car for the compass to work. It is giving out bad signals (It sometimes gives wrong values, leading to the code doing the wrong thing), and therefore we have found that it does not work with our build. We even tried addind capacitors all over the car to help reduce said noise, but without any difference. This is most likely because the interference is hitting the magnetic signal.

Step 13: Fun :)

Picture of Fun :)

To finish of this instrucable we have some progress pictures that found funny and shows just how much a mess the car has been during the project. It got alot better once we got our platform, even though it is still kinda messy.

You might have noticed that we have what looks like a mobile phone strapped underneath the powerbank and you might think we are using it for something usefull - We are not. The only reason it is there is because the powerbank we use is unable to provide a steady current of electricity to the arduino because of how little energy it is consuming. Therefore we decided to strap a mobile phone on there to help the issue. So with a better solution, you would not need a mobile phone strapped into it :)

Comments

the_3d6 (author)2017-05-29

You are using distance library that in turn is based on standard pulseIn function. This function blocks code execution for the whole time when it waits for response - so every time you run distance measurement, your code is blocked for 20-30 milliseconds. If you are running it in a loop cycle each time, then atmega will be waiting for pulse, doing nothing, for about 99% of time - you can't accept RC commands like this.

To solve this, you need to change your distance measurement code and wiring in a way to utilize hardware pin interrupts. Arduino is able to catch pin state interrupts on D2 and D3 - so 2 sensors can be implemented. You need to connect "echo" pin of each sensor to D2 and D3 pins correspondingly (well, you actually can use other pins too, but it gets even more complicated then - not sure you want it).

In order to turn on interrupts on D2 and D3, you need following code in setup():
sei();
EIMSK |= (1 << INT0) | (1 << INT1);

EICRA |= (1 << ISC01) | (1 << ISC11);

INT0 and INT1 are pin interrupts, ISC01 and ISC11 means that interrupts 0 and 1 will be called when pin state changes its state in any direction.
And you need to add interrupt handlers:
ISR(INT0_vect) {

if(digitalRead(2) == 1) {

trig_micros1 = micros();

}
else
{

float d = micros() - trig_micros1;

sensor1_distance = d * 0.01728;//57.87 us/cm

}
}
and the same ISR function for INT1_vector, but with trig_micros2 and sensor2_distance variables, and digitalRead(3). trig_micros variables should be declared as long before both ISR functions, sensor_distance variables should be declared as float, in the same place.
These interrupts will update sensor_distance variables soon after you'll send trigger pulse on sonars without loading MCU (but you need to make sure that you are not sending them more often than once in 50 milliseconds).

frederikdt (author)the_3d62017-05-30

Hey the_3d6. Thanks alot for your comment. We will look into it and try to implement and test your suggestion. Hopefully we can fix the issues :)

Swansong (author)2017-05-23

That's a neat build, I like that it's app controlled :)

About This Instructable

556views

5favorites

License:

More by frederikdt:EAL - RC Car With Ultrasound, Magnetic Compass and Bluetooth Control
Add instructable to: