Autonomous Obstacle Avoiding Robot

Autonomous Obstacle Avoiding Robot

HARDWARE REQUIRED:

  • PICUNO Microcontroller board
  • 1 × 4-Wheel Chassis (with wheels, 4 DC motors, etc.) full set
  • 1 × 298N Motor Driver
  • 1 × SG90 Servo Motor
  • 1 × IR obstacle avoidance sensor
  • 1 × 4xAA Battery pack (with fresh or rechargeable batteries)
  • 1 × 9V Battery (optional for external power supply to PICUNO)
  • Jumper wires
  • USB cable

DESCRIPTION:

This project transforms the 4-wheel chassis into a true autonomous robot that can navigate its environment. The robot drives forward on its own, using an infrared "Avoid" sensor to watch for obstacles. When an object is detected, the robot stops, backs up, and then uses a servo motor as a "neck" to turn the sensor and look left and right. Based on which direction is clearer, the robot makes a decision, turns, and continues on its new path, successfully avoiding the obstacle.

ASSEMBLY OF 4-WHEEL CHASIS KIT:
1.) Prepare the Chassis: If your chassis parts are acrylic, they often come with a protective paper or plastic film. Peel this film off all the pieces.
2.) Mount the Motors: Attach the four yellow DC motors to the lower chassis plate. Use the brackets and long screws/nuts that came with the kit to secure them firmly in place.
3.) Attach the Wheels: Push the four main wheels onto the plastic shafts of the motors. They should be a snug fit.
4.) Attach the Upper Chassis: Use the Screws/nuts to attach the upper chassis firmly. This will work as a holder for other components to be placed.
5.) Mount the Battery Holder: Use screws to attach the 4xAA battery pack at the top of the upper chassis.
6.) Mount the Electronics: Finally, mount the PICUNO, the L298N motor driver board and IR obstacle avoid sensor (placed on a servo motor's arm) onto the upper chassis, usually using screws/double tape. Position them such that to easily run wires to the motors.

CIRCUIT DIAGRAM:

Autonomous Obstacle Avoiding Robot
  • L298N Motor Driver:
    • OUT1 & OUT2: Connect these two screw terminals to the outputs for the first motor and second motor (e.g., two wheels from left side). Connect the two wires (black) from both DC motors to OUT1 and other two wires (red) to OUT2.
    • OUT3 & OUT4: Connect these two screw terminals to the outputs for the third and fourth motor (e.g., two wheels from right side). Connect the two wires (red) from both DC motors to OUT3 and other two wires (black) to OUT4.
    • Connect the positive terminal (+) of the 4xAA battery pack to the 12V screw terminal.
    • Connect the negative terminal (-) of the 4xAA battery pack to the GND screw terminal.
    • Also connect the GND terminal on the L298N to a GND pin on the microcontroller to create a common ground.
    • Connect the IN1 pin (Left side wheels) to GPIO 8.
    • Connect the IN2 pin (Left side wheels) to GPIO 9.
    • Connect the ENA pin (Left motors speed) to GPIO 10.
    • Connect the IN1 pin (Right side wheels) to GPIO 11.
    • Connect the IN2 pin (Right side wheels) to GPIO 12.
    • Connect the ENB pin (Right motors speed) to GPIO 13.

    NOTE: Remove the ENA and ENB jumpers on the L298N motor driver.

  • Servo Motor:
    • Connect the Servo's VCC (Red) pin to the Positive (+) terminal of the 4xAA Battery Pack.
    • Connect the Servo's GND (Brown) pin to GND.
    • Connect the Servo's Signal (Yellow) pin to GPIO 15.
  • IR Obstacle Avoidance Sensor:
    • Connect the Sensor's VCC (+) pin to 5V on board.
    • Connect the Sensor's GND (-) pin to GND.
    • Connect the Sensor's OUT pin to GPIO 16.

    OPTIONAL: You can power the PICUNO using an external power supply (9V Battery) through DC Jack, once you have uploaded the code to the microcontroller.

    SCHEMATIC:

    L298N Motor Driver:

    OUT1 & OUT2 → Outputs for the Left side Motors.

    OUT3 & OUT4 → Outputs for the Right side Motors.

    12V → positive terminal (+) of 4xAA battery pack.

    GND → negative terminal (-) of 4xAA battery pack → GND on PICUNO.

    IN1 (Left side Motors) → GPIO 8

    IN2 (Left side Motors) → GPIO 9

    ENA (Left Motors speed) → GPIO 10

    IN3 (Right side Motors) → GPIO 11

    IN4 (Right side Motors) → GPIO 12

    ENB (Right Motors speed) → GPIO 13

    Servo Motor:

    VCC (Red Wire) → 4xAA Battery Pack (+)

    GND (Brown Wire) → GND

    Signal (Yellow Wire) → GPIO 15

    IR Obstacle Avoidance Sensor:

    VCC / (+) → 5V

    GND / (-) → GND

    OUT → GPIO 16

    CODE -- C:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    #include <Servo.h>
    
    const int L_IN1=8, L_IN2=9, L_ENA=10;
    const int R_IN3=11, R_IN4=12, R_ENB=13;
    const int SERVO_PIN = 15;
    const int AVOID_SENSOR_PIN = 16;
    
    Servo myServo;
    
    // --- Motor Control Functions ---
    void setMotorSpeed(char motor, int speed) {
        int duty = map(abs(speed), 0, 100, 0, 255);
        if (motor == 'L') {
            if (speed > 0) { digitalWrite(L_IN1, HIGH); digitalWrite(L_IN2, LOW); }
            else if (speed < 0) { digitalWrite(L_IN1, LOW); digitalWrite(L_IN2, HIGH); }
            else { digitalWrite(L_IN1, LOW); digitalWrite(L_IN2, LOW); }
            analogWrite(L_ENA, duty);
        } else if (motor == 'R') {
            if (speed > 0) { digitalWrite(R_IN3, HIGH); digitalWrite(R_IN4, LOW); }
            else if (speed < 0) { digitalWrite(R_IN3, LOW); digitalWrite(R_IN4, HIGH); }
            else { digitalWrite(R_IN3, LOW); digitalWrite(R_IN4, LOW); }
            analogWrite(R_ENB, duty);
        }
    }
    
    void forward(int speed = 50) { setMotorSpeed('L', speed); setMotorSpeed('R', speed); }
    void backward(int speed = 50) { setMotorSpeed('L', -speed); setMotorSpeed('R', -speed); }
    void turn_left(int speed = 60) { setMotorSpeed('L', -speed); setMotorSpeed('R', speed); }
    void turn_right(int speed = 60) { setMotorSpeed('L', speed); setMotorSpeed('R', -speed); }
    void stopMotors() { setMotorSpeed('L', 0); setMotorSpeed('R', 0); }
    
    // --- Obstacle Avoidance Logic ---
    String lookAround() {
        Serial.println("Looking for a clear path...");
        myServo.write(30); // Look right
        delay(500);
        bool rightIsClear = (digitalRead(AVOID_SENSOR_PIN) == HIGH);
    
        myServo.write(150); // Look left
        delay(700);
        bool leftIsClear = (digitalRead(AVOID_SENSOR_PIN) == HIGH);
    
        myServo.write(90); // Return to center
        delay(400);
    
        if (leftIsClear && !rightIsClear) { Serial.println("Path is clear to the left."); return "left"; }
        else if (rightIsClear && !leftIsClear) { Serial.println("Path is clear to the right."); return "right"; }
        else if (leftIsClear && rightIsClear) { Serial.println("Both paths clear, choosing left."); return "left"; }
        else { Serial.println("Both paths blocked, turning around."); return "blocked"; }
    }
    
    void setup() {
        pinMode(L_IN1, OUTPUT); pinMode(L_IN2, OUTPUT); pinMode(L_ENA, OUTPUT);
        pinMode(R_IN3, OUTPUT); pinMode(R_IN4, OUTPUT); pinMode(R_ENB, OUTPUT);
        pinMode(AVOID_SENSOR_PIN, INPUT);
        myServo.attach(SERVO_PIN);
        Serial.begin(9600);
        
        Serial.println("Starting Autonomous Robot...");
        myServo.write(90); // Look straight ahead
        delay(1000);
    }
    
    void loop() {
        // The avoid sensor's pin is LOW when it detects an obstacle
        if (digitalRead(AVOID_SENSOR_PIN) == LOW) {
            Serial.println("Obstacle detected! Stopping.");
            stopMotors();
            backward(40);
            delay(400);
            stopMotors();
            
            String directionToTurn = lookAround();
            
            if (directionToTurn == "left") {
                turn_left();
                delay(600); // Adjust time for a good 90-degree turn
                stopMotors();
            } else if (directionToTurn == "right") {
                turn_right();
                delay(600);
                stopMotors();
            } else { // Blocked
                turn_right(80); // Turn with more speed
                delay(1000);    // Adjust time for a 180-degree turn
                stopMotors();
            }
        } else {
            forward(60); // Path is clear, drive forward
        }
        delay(50);
    }
    
    setMotorSpeed(motor, speed) - This is the core motor control function. It takes a side ('L' or 'R') and a speed (from -100 to 100) as input. It then sets the correct direction pins (IN1/IN2) for forward or backward motion and uses analogWrite() to set the speed on the enable pin (ENA/ENB).

    High-Level Movement Functions - Functions like forward(), backward(), and turn_left() make the main code easy to read. They act as simple commands that call the setMotorSpeed() function with the correct parameters for each wheel to achieve a specific movement.

    myServo.write(angle) - This command from the Servo library tells the servo motor to move to a specific angle (e.g., 30 degrees to look right, 150 to look left).

    lookAround() Function - This is the robot's "decision-making brain." It commands the servo to turn the AVOID_SENSOR_PIN to the right and left, reading the sensor at each position to see if the path is clear (HIGH) or blocked (LOW). Based on these readings, it returns a simple command: "left", "right", or "blocked".

    Main loop() - This is the part of the code that runs forever. It continuously checks the AVOID_SENSOR_PIN using digitalRead(). If the path is clear, it calls forward(60) to drive ahead. If it detects an obstacle (digitalRead(...) == LOW), it stops, reverses briefly, and then calls the lookAround() function to decide which way to turn.

    CODE -- PYTHON:

      1
      2
      3
      4
      5
      6
      7
      8
      9
     10
     11
     12
     13
     14
     15
     16
     17
     18
     19
     20
     21
     22
     23
     24
     25
     26
     27
     28
     29
     30
     31
     32
     33
     34
     35
     36
     37
     38
     39
     40
     41
     42
     43
     44
     45
     46
     47
     48
     49
     50
     51
     52
     53
     54
     55
     56
     57
     58
     59
     60
     61
     62
     63
     64
     65
     66
     67
     68
     69
     70
     71
     72
     73
     74
     75
     76
     77
     78
     79
     80
     81
     82
     83
     84
     85
     86
     87
     88
     89
     90
     91
     92
     93
     94
     95
     96
     97
     98
     99
    100
    101
    from machine import Pin, PWM
    from time import sleep_ms
    
    L_IN1 = Pin(8, Pin.OUT); L_IN2 = Pin(9, Pin.OUT); L_ENA = PWM(Pin(10))
    R_IN3 = Pin(11, Pin.OUT); R_IN4 = Pin(12, Pin.OUT); R_ENB = PWM(Pin(13))
    L_ENA.freq(1000); R_ENB.freq(1000)
    
    servo = PWM(Pin(15))
    servo.freq(50)
    
    avoid_sensor = Pin(16, Pin.IN)
    
    # --- Motor Control Functions ---
    def set_motor_speed(motor, speed):
        duty = int(abs(speed) / 100 * 65535)
        if motor == 'left':
            if speed > 0: L_IN1.high(); L_IN2.low()
            elif speed < 0: L_IN1.low(); L_IN2.high()
            else: L_IN1.low(); L_IN2.low()
            L_ENA.duty_u16(duty)
        elif motor == 'right':
            if speed > 0: R_IN3.high(); R_IN4.low()
            elif speed < 0: R_IN3.low(); R_IN4.high()
            else: R_IN3.low(); R_IN4.low()
            R_ENB.duty_u16(duty)
    
    def forward(speed=50): set_motor_speed('left', speed); set_motor_speed('right', speed)
    def backward(speed=50): set_motor_speed('left', -speed); set_motor_speed('right', -speed)
    def turn_left(speed=60): set_motor_speed('left', -speed); set_motor_speed('right', speed)
    def turn_right(speed=60): set_motor_speed('left', speed); set_motor_speed('right', -speed)
    def stop(): set_motor_speed('left', 0); set_motor_speed('right', 0)
    
    # --- Servo Control Function ---
    def set_servo_angle(angle):
        duty = int(((angle / 180) * (8350 - 1350)) + 1350)
        servo.duty_u16(duty)
    
    # --- Obstacle Avoidance Logic ---
    def look_around():
        """Looks left and right and returns which direction is clearer."""
        print("Looking for a clear path...")
        set_servo_angle(30) 
        sleep_ms(500)
        
        right_is_clear = avoid_sensor.value() == 1
    
        # Look left (high angle)
        set_servo_angle(150)
        sleep_ms(700)
        left_is_clear = avoid_sensor.value() == 1
        
        # Return to center
        set_servo_angle(90)
        sleep_ms(400)
    
        if left_is_clear and not right_is_clear:
            print("Path is clear to the left.")
            return 'left'
        elif right_is_clear and not left_is_clear:
            print("Path is clear to the right.")
            return 'right'
        elif left_is_clear and right_is_clear:
            print("Both paths clear, choosing left.")
            return 'left' # Default to left if both are clear
        else:
            print("Both paths blocked, turning around.")
            return 'blocked'
    
    # --- Main Program Loop ---
    print("Starting Autonomous Robot...")
    set_servo_angle(90) 
    sleep_ms(1000)
    
    while True:
        if avoid_sensor.value() == 0:
            print("Obstacle detected! Stopping.")
            stop()
            backward(40) # Back up a little to give turning room
            sleep_ms(400)
            stop()
            
            direction_to_turn = look_around()
            
            if direction_to_turn == 'left':
                turn_left()
                sleep_ms(600) # 90-degree turn
                stop()
            elif direction_to_turn == 'right':
                turn_right()
                sleep_ms(600) # 90-degree turn
                stop()
            else: # Blocked, so turn around
                turn_right(80) # Turn with more speed
                sleep_ms(1000) # 180-degree turn
                stop()
                
        else:
            # Path is clear, so drive forward
            forward(60) 
    
        sleep_ms(50)
    
    set_motor_speed(motor, speed) - This is the core motor control function. It takes a side ('left' or 'right') and a speed (from -100 to 100) as input. It then sets the correct direction pins (IN1/IN2) for forward or backward motion and uses PWM to set the speed on the enable pin (ENA/ENB).

    High-Level Movement Functions - Functions like forward(), backward(), and turn_left() make the main code easy to read. They act as simple commands that call the set_motor_speed() function with the correct parameters for each wheel to achieve a specific movement.

    set_servo_angle(angle) - This function takes a desired angle (e.g., 90 degrees) and converts it into the specific PWM signal (duty) that the servo motor needs to move to that exact position.

    look_around() Function - This is the robot's "decision-making brain." It commands the servo to turn the avoid_sensor to the right and left, reading the sensor at each position to see if the path is clear. Based on these readings, it returns a simple command: 'left', 'right', or 'blocked'.

    Main while Loop - This is the part of the code that runs forever. It continuously checks the avoid_sensor. If the path is clear, it calls forward(60) to drive ahead. If it detects an obstacle (if avoid_sensor.value() == 0:), it stops, reverses briefly, and then calls the look_around() function to decide which way to turn.