Introduction: PCB BOT (Line Following, Obstacle Avoiding & Bluetooth Controlled Robot in a Single PCB)

About: I like to learn, like to make, like to share.

Lots of line followers are available on the internet. Most of them are made using separate modules. Several single PCB designs are available but all three functionality like Bluetooth control, obstacle avoiding and line following is not available in a single PCB design. In this instructable, I am going to share the recipe of my single PCB line follower with Bluetooth control and obstacle avoiding functionality. The bot is completely opensource and you can replicate it easily. I will provide all the required materials in the following steps including schematic, PCB design file and Arduino source code. The bot is designed as a single PCB and all the required components and sensors are placed directly to PCB without using any breadboard or any separate module. So, it is much more reliable than other line followers where separate sensor module is used.

For line follower, I used PID controller and you can tune all three PID parameters from an Android phone which can reduce lots of tuning hassle as well as it will help to save lots of tuning time. Before going further watch the demo of line follower where PID controller is used.

Line Following:

To power-up the BOT I used two 18650 lithium cells. Arduino NANO is used as the main controller and I placed the Nano using the pin header so that I can remove it easily from the bot. If you feel interested and plan to make one just keep reading.

Bluetooth Control:

Step 1: Bill of Materials

Components:

  1. Arduino NANO (1 pc) (buy from gearbest.com)
  2. HC-05 Bluetooth Module (buy from gearbest.com)
  3. HC-SR04 Ultrasonic Sensor (buy from gearbest.com)
  4. L293D Motor Driver IC (buy from gearbest.com)
  5. LTH1550-01 IR Sensor (7 pcs) (buy from aliexpress.com)
  6. LM324N Quad OPAMP (2 pcs) (buy from aliexpress.com)
  7. LED 5mm (5 pcs)
  8. LM2940, 5V, 1A Low Dropout Regulator (buy from aliexpress.com)
  9. 18650 Rechargeable Lithium Cell (2 pcs) (buy from gearbest.com)
  10. 18650 double-cell battery holder (buy from gearbest.com)
  11. 10K Multiturn Pot (buy from aliexpress.com)
  12. 470R X 12, 22K X 7 Resistor
  13. SPDT Switch
  14. N20 Micro metal gear motor 6V, 400 RPM (2 pcs) (buy from gearbest.com)
  15. Rubber wheel 43mm (2 pcs) (buy from aliexpress.com)
  16. Mounting Bracket for N20 Motor with M3 nuts and screws (2 pcs) (buy from aliexpress.com)
  17. Ball Caster (buy from aliexpress.com)
  18. 4" X 5" Double Sided Copper Clad Board (buy from aliexpress.com)

Tools:

  1. Soldering Iron (you can buy an anti-static Digital Display Constant Temperature Soldering Station from gearbest.com).
  2. Hand Drill (you can buy a Hand Drill Electric Grinding Micro Tool Suit from gearbest.com). You can also choose this nice PCB drill.
  3. Wire Cutter (you can buy this Multifunction Electronic Wire Cutter from gearbest.com).
  4. Screw Driver (you can buy this Magnetic Screwdriver Set 45 In 1 Precision Screw Driver Tools from gearbest.com).

Step 2: Circuit & PCB Designing on Eagle

Power Supply - In the circuit, Arduino Nano board, Operational Amplifier, Bluetooth Module, Ultrasonic Sensor and IR sensor array need a 5V regulated DC for their operation while the motor driver IC needs additional 6V DC for powering the motor. Two 3.7V Lithium cell is used as the primary source of power. The supply from the battery is regulated to 5V for Arduino and other sensors and ICs. The pin 1 of both the voltage regulator ICs is connected to the anode of the battery and pin 2 of both ICs is connected to ground. The DC motors cannot be directly connected to the battery as they can only be controlled by the motor driver IC and the motor driver IC itself need a regulated power input.

Arduino NANO - Arduino NANO is one of the most popular prototyping boards. It is used frequently in robotic applications as it is small in size and packed with rich features. The board comes with built-in Arduino boot loader. It is an Atmega 328 based controller board which has 14 GPIO pins, 6 PWM pins, 6 Analog inputs and onboard UART, SPI and TWI interfaces. In this project, 7 GPIO pins of the board are utilized to connect the IR sensors and 6 GPIO pins are used to interface L293D motor driver IC, two GPIO pins are used for ultrasonic sensor and two GPIO pins are used for Bluetooth module.

L293D DC Motor Driver IC - The L293D is a dual H-bridge motor driver integrated circuit (IC). The Motor drivers act as current amplifiers since they take a low-current control signal from Arduino and provide a higher-current and higher voltage signal. This higher current signal is used to drive the motors.

The pin 4, 5, 13 and 12 of the L293D IC are grounded while pins 1, 16 and 9 are connected to 5V DC and pin 8 is directly connected to Battery. The pins 15, 2, 7 and 10 of this motor driver IC are connected to pins 5, 2, 3 and 4 of the Arduino board. The DC motor attached to right wheel is connected to pins 11 and 14 while motor attached to the left wheel is connected to pins 3 and 6 of the motor driver IC.

The pins 15, 2, 7 and 10 are input signal pins of the motor driver IC. These are connected to Arduino pins. On changing digital logic at the Arduino pins, the logic at the input pins of the motor driver IC is also changed. As summarized in the tables above, the direction of rotation of the DC motors depends upon the digital logic at the input pins of the motor driver IC.

Geared DC Motors - In this robot, 6V Micro metal geared DC motors (N20) are attached to the wheels. Geared DC motors are available with wide range of RPM and Torque, which allow a robot to move based on the control signal it receives from the motor driver IC. The motors used here have a maximum speed of 400RPM.

IR sensor array - The line follower is designed to follow black strips of line. For this, a sensor which can detect the color of the underneath surface is required. The IR sensors can detect the color of underneath surface based on reflective/non-reflective indirect incidence. The IR LEDs emit IR radiation which in a normal state gets reflected back from the white surface around the black line.
The reflected radiations are detected by the photodiodes. But when the IR radiation falls on a black line, it gets absorbed completely by the black color and hence there is no reflection of the IR radiation back to the sensor module. This way, an IR sensor module detects the black strips. The IR sensors are available with analog output as well as digital output. In this robot, the sensor module is designed using the IR sensors having digital output. The model number of the sensor is LTH1550-01.

Variable resistor - The multiturn variable resistors are used in the sensor circuit to form a resistive voltage divider. The VCC is applied at the variable resistor side, so the output from the voltage divider is proportional to the resistance of the variable resistor and is inversely proportional to the resistance of the respective photodiode. The use of variable resistor allows calibrating the sensor circuit to properly detect the black strips.

LM324N OPAMP - The LM324N is used to add operational amplifiers in the circuit. The LM324N is a general purpose Quad Operational Amplifier (Op-Amp). The OPAMs are used as voltage comparators in the circuit. The output from the variable resistor is fed as a reference voltage of the comparator. The output from the Ir sensor is given as the input of the comparator. Based on the color the comparator generate digital output for the Arduino.

The complete circuit is designed on Eagle and the schematic and BRD file is attached at the end of the step.

Step 3: Making the PCB (Part One)

To begin with, you need to have your board designed. I use Eagle CAD, but it can be any other PCB design tool. The PCB layout is designed for double layer and I recommend using as few vias as possible as this will reduce the amount of work later on. It's recommended to include mounting holes in the corners. Even if you don't need them, they will make aligning the layers easier.

Once you're done, you can print your design on glossy paper. I recommend using photo paper. Remember to use maximum toner density and resolution and must keep the scale factor 1. For the top layer, print the design as a mirror reflection. Include nets, vias and pads and holes. In Eagle, I add the dimension layer to make cutting the board easier.

When you print the bottom part, make sure you do not select the "Mirror" checkbox. Otherwise, you'll end up with a print that you'll need to transfer through the paper. This may be difficult. For the bottom layer include nets, dimension, pads, and vias.

The size of the design is 4 inch x 5 inch. So, a dual-sided 4 inch X 5 inch copper clad board is required to make the PCB. I choose a dual-sided fiberglass copper clad board. The hardest part of making a dual-sided PCB by hand is perfectly aligning the top and bottom layers together. The toner transfer method is a simple way of making PCBs because all you need is a printer, an iron, some laminate, and etching agent. However, if your design requires two layers, your challenge is to align them. If you fail to do so, your vias will not connect the layers properly. You might also run into a situation in which you drill through a pad on one side but end up on a net on the other.

For knowing details about double sided PCB making by hand take a look at this excellent tutorial: Homemade Double-layer PCB With Toner Transfer Method.

Step 4: Making PCB (Part Two)

I am going to create a double-sided PCB, so I will need to include some reference points that match on both sides. It is a good idea to include the edges of the PCB, but you may also put some extra marks outside the PCB area. This will make it easier to verify that the patterns are still aligned once you have sandwiched the PCB between them. You can slightly reduce the need for perfect alignment by making the ‘via’ solder pads of one of the sides slightly larger and not drawing holes in them.

If the copper of the PCB appears to have oxidized a bit, you may want to first wash it with a bit of vinegar (or a stronger acid like HCl), which will return the metal to an un-oxidized state.Scrub the copper side with the abrasive pad, until the copper is shiny and clean.Next, scrub the copper side with a paper towel soaked in acetone. This step is essential! The acetone will remove any remaining dirt and grease. If there's anything left on the copper, the toner won't stick well at those places. You should rub very hard on every square millimeter of the copper, switching to clean parts of the towel regularly. Repeat until no more dirt shows up on the towel. Needless to say, you must avoid touching the copper after this step.

Double-layered boards are tricky. The safest method would be to first transfer one side, and etch it with the other side protected by duct tape or foil. Also drill at least a few via holes to make alignment easier. Then transfer and etch the other side (again, covering the already etched side). Quicker but more difficult is to do both transfers at once, by sandwiching the pcb between the two sheets. Alignment could be problematic in this case. What could work, is to align the two sheets in advance. Hold them against a bright light and once aligned, stick or staple one or two edges together. Then you can insert the board in between and iron both sides.

Set the iron to its highest setting. Place the PCB, copper side up, on a heat-resistant surface. Wood will do, if you don't mind it getting a bit darker at one spot. Place the design on the copper (obviously with the toner facing down), aligning it properly. Then, while ensuring that the paper doesn't shift, place the hot iron on top of it. Press hard and move the iron around every 5 seconds, to ensure that the entire board is heated evenly. Next, you should rub the entire board with the sides and tip of the iron. Hold the iron slightly tilted so most of the pressure is concentrated on the tip. The paper should start to look shiny at those places you've already rubbed, so you can see which areas you still need to cover.It's OK if the paper gets a little brownish during this whole process, this just means you're really heating it, which is good. It requires some experience to find the right amount of pressure that is required: too little pressure will cause a bad transfer, too much pressure will cause the toner to smear out. Don't expect your first PCB to be perfect!

Details: https://www.dr-lex.be/hardware/tonertransfer.html

Step 5: Making the PCB (Part Three)

After etching the bottom copper it is the high time to align the top layer to the board. To do this I made three holes to the board and placed the top copper print according to the hole to make it perfectly match. Then I transferred the toner. Before etching the top layer I again musk the etched bottom layer using duct tape to protect the layer from etching again.

After rinsing and drying the PCB, you can opt to remove the toner immediately, or wait until all other steps. It's somewhat easier to remove the toner before the holes are drilled, but leaving the toner on will protect the copper during the following steps. Whenever you want to do it, use a towel drenched in acetone to wipe away the toner.

Step 6: Making the PCB (Part Four)

After musking the bottom side etched earlier I sink the board the ferric chloride solution to etch the top layer. It takes around 15 minutes to completely etch the PCB. After etching both sides you need to make hole for pads and vias.

Whatever etchant you use, you should wear safety gloves, because you don't want to get any substance that dissolves metal on your skin. Safety goggles are also a good idea to prevent accidental splashes from getting in your eyes. It's obvious that you can't use a metal container to perform the etching. Especially aluminium with a HCl-based etchant is a very, very bad idea unless you're aching to get into massive trouble. If you want to know why, pour a little HCl in a glass jar in a well-ventilated area and drop a tiny ball of aluminium foil in it. Enjoy!

Step 7: Cutting the PCB

To place the wheel you need to cut two sides of the PCB according to the marking. The marking was made for 43mm standard wheel for Micro metal gearmotor. You may use a hack saw or mini Dremel tool to cut the PCB.

Lots of hassle! Is not it? Don't like to take this hassle? JLCPCB can remove all the hassle at a very low cost. JLCPCB (Shenzhen JIALICHUANG Electronic Technology Development Co., Ltd.), is the largest PCB prototype enterprise in China and a high-tech manufacturer specializing in quick PCB prototype and small-batch PCB production. With over 10 years of experience in PCB manufacturing, JLCPCB has more than 200,000 customers at home and abroad, with over 8,000 online orders of PCB prototyping and small quantity PCB production per day.

JLCPCB have incredibly quick turnaround times. Just submit your grabber and let them verify your design. I hope within 24 hours you will get the feedback. Make the payment and wait for a week to get the PCB at your hand.

Step 8: Making Holes and Vias

The next step is drilling the vias and holes, as we are not using the SMD components all the way. For most ordinary component like resistors, IC and pin headers holes, a 0.8mm (1/32") drill is perfect. Larger holes can be 1mm (1/25") or 1.2mm (3/64"). If you don't need to drill many holes, ordinary HSS bits will do. if you're careful enough, you could drill the holes by hand, but a drill press will make your life a lot easier and reduce the risk of breaking the thin bits. If you need to drill many holes, you may want to look for carbide bits because the glass fiber board will wear out HSS bits quickly. Beware: these are extremely brittle, so a stable drill press is essential here.
To improve the looks of your PCB and make soldering easier, you can print a component diagram on the component side, the same way as you transferred the toner on the copper side! This is much easier because the toner sticks even better on the non-copper side. There's no need to waste the glossy paper on this, ordinary office paper will do. Remember to mirror the image before printing. Scrub the surface with acetone (no need to use the abrasive pad), clear out any dirt from the holes, and align the pattern using the drilled holes, holding the board against a bright light. Then, iron as usual, and soak the board in water. Just peel it off, the toner should stick hard enough. Remove any residual paper with the toothbrush.

After making the holes I use the resistor's lead for vias to connect top and bottom copper trace. I use the resistor lead for all the vias.

Step 9: Placing the Components

After making the vias I soldered IC base for all three IC (one L293D and two LM324N). It is a good practice to use ic base without directly soldering an IC to the board. It can give you several advantages like protection from burning the IC during soldering, debugging of any fault and replacing a faulty ic easily. Then for the same reasons I use female rail for Arduino NANO. Then I soldered all other parts like sensors, resistors, LEDs and voltage regulator. I used female rail for the Bluetooth module and ultrasonic sensor. Finally, I placed the motors at the correct position using mount and screws and soldered the leads of the motors at the correct pads.

Step 10: Completing the Soldering

Unlike the sensors, all the components are placed at the top side. Five line following sensors are placed at the bottom side of the board. Green LEDs are used to observe the sensor output. The battery case is directly soldered to the board and SPDT switch is used for ON/OFF functionality.

Step 11: Uploading the Sketch

A simple line follower robot detects the black strips by using the IR sensor module and moves the robot either left or right in an attempt to keep the robot along the centre of the strip. When using an array of IR sensors, the mean value of the outputs from the IR sensors is used to precisely position the robot along the centre of the strip. A simple line follower without any feedback mechanism is not very stable. A close loop PID controller can increase the stability to a great extent.

The PID algorithm is implemented in the Arduino sketch. The algorithm is implemented to control the duty cycle of the PWM applied at the input pins of the motor driver IC. So, the variable controlled in the PID implementation is duty cycle of PWM which is denoted by power-difference in program code. The duty cycle is changed in each iteration of the controller program according to the variables proportional, derivative and integral multiplied by constants Kp, Kd and Ki. The constants are derived by calibrating the robot to move in the centre of the strip. For deriving the constants, different values of the constants are tried in the program code starting from zero value for each constant. An android program was developed for the tuning purpose of the car.


The value of proportional variable is directly dependent on the current position of the robot derived with reference to the centre of the strip. The position of the robot along the centre of the strip is derived through sensor values as a negative or positive integer. Accordingly, the code decides whether the robot should be turned left or right. The value of derivative variable is the difference of the current value of the proportional variable with the value of the proportional variable in the last iteration of the code. This gives the difference from the desired output. The value of the integral variable is sum of the variable's value in last program code iteration and the current value of the proportional variable. All the three variables multiplied by respective constants is utilized to derive the power difference or the coming difference in the PWM output and according to the duty cycle of the PWM output is corrected.

The PID algorithm is implemented within the Arduino sketch. The PID algorithm uses three constants Kp, Ki and Kd to function. They are shorthand notations for proportionality, integral and differential constants respectively. These three constants have to be set after testing and need to be defined for the desired control application.


For implementing a simple PID control algorithm, first, the digitalRead() function is used in the code to read values from the IR sensors. The error was calculated according to the sensor reading. From the calculated error the PID value is calculated.

#include <SoftwareSerial.h>
SoftwareSerial bluetoothSerial(12, 11); // RX, TX

//driving mode of the car, it depends on tha band of the of the track
# define STOPPED 0
# define FOLLOWING_LINE 1
# define NO_LINE 2
# define BIG_ANGLE_LEFT 3
# define BIG_ANGLE_RIGHT 4
# define SMALL_ANGLE_LEFT 5
# define SMALL_ANGLE_RIGHT 6

//motor pin configuration
int rightMotorEnable = 3;
int leftMotorEnable = 5;
int rightMotorBackward = 6;
int rightMotorForward = 7;
int leftMotorBackward = 8;
int leftMotorForward = 9;

const int lineFollowSensor0 = A0; //righ most sensor
const int lineFollowSensor1 = A1; 
const int lineFollowSensor2 = A3; 
const int lineFollowSensor3 = A4;
const int lineFollowSensor4 = A2; //left most sensor

int leftMotorStartingSpeed = 170;
int rightMotorStartingSpeed = 185;
int manualSpeed = 160;

char btCommand = 'S';
char prevCommand = 'A';
int velocity = 0;   
unsigned long timer0 = 2000;  //Stores the time (in millis since execution started) 
unsigned long timer1 = 0;  //Stores the time when the last command was received from the phone

int mode = 0;

int LFSensor[5]={0, 0, 0, 0, 0};

// PID controller
float Kp=70;
float Ki=0;
float Kd=50;

float pidValue = 0;

float error=0, P=0, I=0, D=0, PIDvalue=0;
float previousError=0, previousI=0;

int wheelCheck=0;
//String command;
String device;

String inputString = "";
String command = "";
String value = "";
boolean stringComplete = false; 

void setup() {
  Serial.begin(9600);  //Set the baud rate to that of your Bluetooth module.
  bluetoothSerial.begin(9600); //set the baud rate for your bluetooth module

  //all motor controller related pin should be output
  pinMode(rightMotorForward, OUTPUT);
  pinMode(rightMotorBackward, OUTPUT);
  pinMode(leftMotorForward, OUTPUT);
  pinMode(leftMotorBackward, OUTPUT);
  pinMode(rightMotorEnable, OUTPUT);
  pinMode(leftMotorEnable, OUTPUT);

  //all sensor pin should be input
  pinMode(lineFollowSensor0, INPUT);
  pinMode(lineFollowSensor1, INPUT);
  pinMode(lineFollowSensor2, INPUT);
  pinMode(lineFollowSensor3, INPUT);
  pinMode(lineFollowSensor4, INPUT);

  //command for bluetooth receiver
  inputString.reserve(50);  // reserve 50 bytes in memory to save for string manipulation 
  command.reserve(50);
  value.reserve(50);
}

void loop() {

 //uncomment the following two lines for testing sensors
 //testLineFollowSensorsAndPIDvalue();
 //delay(1000);

 /****************This snippet is for PID tuning using Bluetooth***************************/
 serialEvent(); //check if bluetooth receiver receives any data
 if (stringComplete) {
    delay(100);
    // identified the posiion of '=' in string and set its index to pos variable
    int pos = inputString.indexOf('=');
    // value of pos variable > or = 0 means '=' present in received string.
    if (pos > -1) {
      // substring(start, stop) function cut a specific portion of string from start to stop
      // here command will be the portion of received string till '='
      // let received string is KP=123.25
      // then the receive value id for KP and actual value is 123.25 
      command = inputString.substring(0, pos);
      // value will be from after = to newline command
      // for the above example Kp value is 123.25
      // we just ignoreing the '=' taking first parameter of substring as 'pos+1'
      // we are using '=' as a separator between command and vale
      // without '=' any other character can be used
      value = inputString.substring(pos+1, inputString.length()-1);  // extract command up to \n exluded
      pidValue = value.toFloat(); //convert the string into float value
      //uncomment following line to check the value in serial monitor
      //Serial.println(pidValue);
      if(command == "KP"){
           Kp = pidValue;
           delay(50);
           }
      else if(command == "KI"){
           Ki = pidValue;
           delay(50);
           }
      else if(command == "KD"){
           Kd = pidValue;
           delay(50);
           } 
        } 
      inputString = "";
      stringComplete = false;//all the data is collected from serial buffer
    }

 /*********************This snippet is for Line Following***************************************/
 readLFSsensorsAndCalculateError(); //read sensor, calculate error and set driving mode
 switch (mode)
   {
    case STOPPED:  //all sensors read black line
      wheelCheck=0;
      motorStop();
      previousError = error;
      break;

    case NO_LINE:  //all sensor are on white plane
      wheelCheck+=1; 
      if(wheelCheck==1){
      forward();
      delay(2);
      }
      else if(wheelCheck==2){
      wheel_rotation(120,-120);
      delay(4);
      }
      previousError = 0;
      break;
      
    case BIG_ANGLE_RIGHT:
      wheelCheck=0;
      wheel_rotation(-120,120); //rotate right for small angle
      delay(5);  //delay determines the degree of angle

    case BIG_ANGLE_LEFT:
      wheelCheck=0;
      wheel_rotation(120,-120); //rotate left for small angle
      delay(5);
      
    case SMALL_ANGLE_RIGHT:
      wheelCheck=0;
      wheel_rotation(-120,120);
      delay(20); //rotate right for big angle
      previousError = 0;
      break;

    case SMALL_ANGLE_LEFT:
      wheelCheck=0;
      wheel_rotation(120,-120);
      delay(20); //rotate left for big angle
      previousError = 0;
      break;

    case FOLLOWING_LINE: //everything is going well
      wheelCheck=0;      
      calculatePID();
      motorPIDcontrol();   
      break;     
  }

  /**************************This snippet is for Bluetooth Remote Driving****************************/
  //Uncomment following line and comment above section for manual driving 
  //bluetoothControl();
}

void forward(){ //drive forward at a preset speed
  analogWrite(rightMotorEnable, manualSpeed);
  digitalWrite(rightMotorForward, HIGH);
  digitalWrite(rightMotorBackward, LOW);
  
  analogWrite(leftMotorEnable, manualSpeed);
  digitalWrite(leftMotorForward, HIGH);
  digitalWrite(leftMotorBackward, LOW);
}

void backward(){
  analogWrite(rightMotorEnable, manualSpeed);
  digitalWrite(rightMotorForward, LOW);
  digitalWrite(rightMotorBackward, HIGH);
  
  analogWrite(leftMotorEnable, manualSpeed);
  digitalWrite(leftMotorForward, LOW);
  digitalWrite(leftMotorBackward, HIGH);
}

void left(){ //left motor rotate CCW and right motor rotate CW
  analogWrite(rightMotorEnable, manualSpeed);
  digitalWrite(rightMotorForward, HIGH);
  digitalWrite(rightMotorBackward, LOW);
  
  analogWrite(leftMotorEnable, manualSpeed);
  digitalWrite(leftMotorForward, LOW);
  digitalWrite(leftMotorBackward, HIGH);
}


void right(){ //left motor rotate CW and left motor rotate CCW
  analogWrite(rightMotorEnable, manualSpeed);
  digitalWrite(rightMotorForward, LOW);
  digitalWrite(rightMotorBackward, HIGH);
  
  analogWrite(leftMotorEnable, manualSpeed);
  digitalWrite(leftMotorForward, HIGH);
  digitalWrite(leftMotorBackward, LOW);
}

void motorStop(){ //speed of both motor is zero
  analogWrite(rightMotorEnable, 0);
  digitalWrite(rightMotorForward, LOW);
  digitalWrite(rightMotorBackward, LOW);
  
  analogWrite(leftMotorEnable, 0);
  digitalWrite(leftMotorForward, LOW);
  digitalWrite(leftMotorBackward, LOW);
}

void readLFSsensorsAndCalculateError()
{
  LFSensor[0] = digitalRead(lineFollowSensor0);
  LFSensor[1] = digitalRead(lineFollowSensor1);
  LFSensor[2] = digitalRead(lineFollowSensor2);
  LFSensor[3] = digitalRead(lineFollowSensor3);
  LFSensor[4] = digitalRead(lineFollowSensor4);
  
  if((     LFSensor[0]== 0 )&&(LFSensor[1]== 0 )&&(LFSensor[2]== 0 )&&(LFSensor[3]== 0 )&&(LFSensor[4]== 1 ))  {mode = FOLLOWING_LINE; error = 4;}
  else if((LFSensor[0]== 0 )&&(LFSensor[1]== 0 )&&(LFSensor[2]== 0 )&&(LFSensor[3]== 1 )&&(LFSensor[4]== 1 ))  {mode = FOLLOWING_LINE; error = 3;}
  else if((LFSensor[0]== 0 )&&(LFSensor[1]== 0 )&&(LFSensor[2]== 0 )&&(LFSensor[3]== 1 )&&(LFSensor[4]== 0 ))  {mode = FOLLOWING_LINE; error = 2;}
  else if((LFSensor[0]== 0 )&&(LFSensor[1]== 0 )&&(LFSensor[2]== 1 )&&(LFSensor[3]== 1 )&&(LFSensor[4]== 0 ))  {mode = FOLLOWING_LINE; error = 1;}
  else if((LFSensor[0]== 0 )&&(LFSensor[1]== 0 )&&(LFSensor[2]== 1 )&&(LFSensor[3]== 0 )&&(LFSensor[4]== 0 ))  {mode = FOLLOWING_LINE; error = 0;}
  else if((LFSensor[0]== 0 )&&(LFSensor[1]== 1 )&&(LFSensor[2]== 1 )&&(LFSensor[3]== 0 )&&(LFSensor[4]== 0 ))  {mode = FOLLOWING_LINE; error =- 1;}
  else if((LFSensor[0]== 0 )&&(LFSensor[1]== 1 )&&(LFSensor[2]== 0 )&&(LFSensor[3]== 0 )&&(LFSensor[4]== 0 ))  {mode = FOLLOWING_LINE; error = -2;}
  else if((LFSensor[0]== 1 )&&(LFSensor[1]== 1 )&&(LFSensor[2]== 0 )&&(LFSensor[3]== 0 )&&(LFSensor[4]== 0 ))  {mode = FOLLOWING_LINE; error = -3;}
  else if((LFSensor[0]== 1 )&&(LFSensor[1]== 0 )&&(LFSensor[2]== 0 )&&(LFSensor[3]== 0 )&&(LFSensor[4]== 0 ))  {mode = FOLLOWING_LINE; error = -4;}
  else if((LFSensor[0]== 1 )&&(LFSensor[1]== 1 )&&(LFSensor[2]== 1 )&&(LFSensor[3]== 1 )&&(LFSensor[4]== 1 ))  {mode = STOPPED; error = 0;}
  else if((LFSensor[0]== 0 )&&(LFSensor[1]== 0 )&&(LFSensor[2]== 0 )&&(LFSensor[3]== 0 )&&(LFSensor[4]== 0 ))  {mode = NO_LINE; error = 0;}
  //track goes right at an angle >90 degree
  else if((LFSensor[0]== 1 )&&(LFSensor[1]== 1 )&&(LFSensor[2]== 1 )&&(LFSensor[3]== 0 )&&(LFSensor[4]== 0 ))  {mode = BIG_ANGLE_RIGHT; error = 0;}
  else if((LFSensor[0]== 1 )&&(LFSensor[1]== 1 )&&(LFSensor[2]== 1 )&&(LFSensor[3]== 1 )&&(LFSensor[4]== 0 ))  {mode = BIG_ANGLE_RIGHT; error = 0;}
  //track goes right at an angle <90 degree
  else if((LFSensor[0]== 1 )&&(LFSensor[1]== 0 )&&(LFSensor[2]== 1 )&&(LFSensor[3]== 0 )&&(LFSensor[4]== 0 ))  {mode = SMALL_ANGLE_RIGHT; error = 0;}
  //track goes left at an angle >90 degree
  else if((LFSensor[0]== 0 )&&(LFSensor[1]== 0 )&&(LFSensor[2]== 1 )&&(LFSensor[3]== 1 )&&(LFSensor[4]== 1 ))  {mode = BIG_ANGLE_LEFT; error = 0;}
  else if((LFSensor[0]== 0 )&&(LFSensor[1]== 1 )&&(LFSensor[2]== 1 )&&(LFSensor[3]== 1 )&&(LFSensor[4]== 1 ))  {mode = BIG_ANGLE_LEFT; error = 0;}
  //track goes left at an angle <90 degree
  else if((LFSensor[0]== 0 )&&(LFSensor[1]== 0 )&&(LFSensor[2]== 1 )&&(LFSensor[3]== 0 )&&(LFSensor[4]== 1 ))  {mode = SMALL_ANGLE_LEFT; error = 0;}

}

void testLineFollowSensorsAndPIDvalue()
{
     int LFS0 = digitalRead(lineFollowSensor0);
     int LFS1 = digitalRead(lineFollowSensor1);
     int LFS2 = digitalRead(lineFollowSensor2);
     int LFS3 = digitalRead(lineFollowSensor3);
     int LFS4 = digitalRead(lineFollowSensor4);
     
     Serial.print ("LFS: L  0 1 2 3 4  R ==> "); 
     Serial.print (LFS0); 
     Serial.print (" ");
     Serial.print (LFS1); 
     Serial.print (" ");
     Serial.print (LFS2); 
     Serial.print (" ");
     Serial.print (LFS3); 
     Serial.print (" ");
     Serial.print (LFS4); 
     Serial.print ("  ==> ");
    
     Serial.print (" P: ");
     Serial.print (P);
     Serial.print (" I: ");
     Serial.print (I);
     Serial.print (" D: ");
     Serial.print (D);
     Serial.print (" PID: ");
     Serial.println (PIDvalue);
}

void bluetoothControl(){
  if(bluetoothSerial.available() > 0){ 
    timer1 = millis();   
    prevCommand = btCommand;
    btCommand = bluetoothSerial.read(); 
    //Change pin mode only if new command is different from previous.   
    if(btCommand!=prevCommand){
      //Serial.println(command);
      switch(btCommand){
      case 'F':  
        forward();
        break;
      case 'B':  
        backward();
        break;
      case 'L':  
        left();
        break;
      case 'R':
        right();
        break;
      case 'S':  
        motorStop();
        break; 
      case 'I':  //FR  
        right();
        break; 
      case 'J':  //BR  
        left();
        break;       
      default:  //Get velocity
        if(btCommand=='q'){
          velocity = 255;  //Full velocity
          manualSpeed = velocity;
        }
        else{ 
          //Chars '0' - '9' have an integer equivalence of 48 - 57, accordingly.
          if((btCommand >= 48) && (btCommand <= 57)){ 
            //Subtracting 48 changes the range from 48-57 to 0-9.
            //Multiplying by 25 changes the range from 0-9 to 0-225.
            velocity = (btCommand - 48)*25;       
            manualSpeed = velocity;
          }
        }
      }
    }
  } 
}


void calculatePID()
{
  P = error; //maximum error value is 4 and minimum is zero
  I = I + error; 
  D = error-previousError; 
  PIDvalue = (Kp*P) + (Ki*I) + (Kd*D);
  previousError = error;
}

void motorPIDcontrol()
{  
  int leftMotorSpeed = leftMotorStartingSpeed + PIDvalue;
  int rightMotorSpeed = rightMotorStartingSpeed - PIDvalue;
  
  // The motor speed should not exceed the max PWM value
  constrain(leftMotorSpeed, 100, 255);
  constrain(rightMotorSpeed, 100, 255);

  //determing the rotation and direction of wheel
  wheel_rotation(leftMotorSpeed,rightMotorSpeed);
}

void wheel_rotation(int left, int right){
  if(left>0){
    analogWrite(leftMotorEnable, left);
    digitalWrite(leftMotorForward, HIGH);
    digitalWrite(leftMotorBackward, LOW);
    }
  else if(left<0){
    analogWrite(leftMotorEnable, abs(left));
    digitalWrite(leftMotorForward, LOW);
    digitalWrite(leftMotorBackward, HIGH);
    }
  if(right>0){
    analogWrite(rightMotorEnable, right);
    digitalWrite(rightMotorForward, HIGH);
    digitalWrite(rightMotorBackward, LOW);
    }
  else if(right<0){
    analogWrite(rightMotorEnable, abs(right));
    digitalWrite(rightMotorForward, LOW);
    digitalWrite(rightMotorBackward, HIGH);
    }
  }

void serialEvent() {
  while (bluetoothSerial.available()) {
    // get the new byte:
    char inChar = (char)bluetoothSerial.read(); 
    //Serial.write(inChar);
    // add it to the inputString:
    inputString += inChar;
    // if the incoming character is a newline or a carriage return, set a flag
    // so the main loop can do something about it:
    if (inChar == '\n' || inChar == '\r') {
      stringComplete = true;
    } 
  }
}

/*
void checkBTcmd()  
 { 
   while (Serial.available())   //Check if there is an available byte to read
   {
     delay(10); //Delay added to make thing stable 
     char c = Serial.read(); //Conduct a serial read
     device += c; //build the string.
   }  
   if (device.length() > 0) 
   {
     //Serial.print("Command received from BT ==> ");
     //Serial.println(device); 
     command = device;
     device ="";  //Reset the variable
     Serial.flush();
    } 
}
*/

Step 12: Android Apps for PID Tuning & Manual Driving

The process of setting the optimal gains for P, I and D to get an ideal response from a control system is called tuning. The goal of tuning a PID loop is to make it stable, responsive and to minimize overshoot. These goals - especially the last two - conflict with each other. You must find a compromise between the goals which acceptably satisfies them all. Process requirements and physical limitations will determine the balance between the amount of acceptable overshoot as well as the demand for responsiveness.

There are different methods of tuning available including the “guess and check” method and the Ziegler Nichols method. The gains of a PID controller can be obtained by trial and error method. Once an engineer understands the significance of each gain parameter, this method becomes relatively easy. In this method, the I and D terms are set to zero first and the proportional gain is increased until the output of the loop oscillates. As one increases the proportional gain, the system becomes faster, but care must be taken not make the system unstable. Once P has been set to obtain a desired fast response, the integral term is increased to stop the oscillations. The integral term reduces the steady state error, but increases overshoot. Some amount of overshoot is always necessary for a fast system so that it could respond to changes immediately. The integral term is tweaked to achieve a minimal steady state error. Once the P and I have been set to get the desired fast control system with minimal steady state error, the derivative term is increased until the loop is acceptably quick to its set point. Increasing derivative term decreases overshoot and yields higher gain with stability but would cause the system to be highly sensitive to noise. Often times, engineers need to tradeoff one characteristic of a control system for another to better meet their requirements.

As explained, the best way to define the correct constant to be used with a PID controller is using the "Try-error" methodology. The bad side of that is that you must re-compile the program each time that you must change it. One way to speed up the process is to use the Android App to send the constants to the car and observing the response and recording the value for a perfect match.

The app is developed using MIT App Inventor and both the source file and .apk file is attached below.

For driving the car I used Arduino Bluetooth RC Car available in Play Store for download.

https://play.google.com/store/apps/details?id=braulio.calle.bluetoothRCcontroller&hl=en

Step 13: Enjoy!!!

If you followed all the previous steps then Congratulation!!!. You successfully made your own robot which has lots of functionality including line following and has a cute look like as the above images.

PCB Contest

Second Prize in the
PCB Contest