Testing of an Automatic Line-Following Robot Using Raspberry Pi and Infrared Sensors

by unikeyelectronics in Circuits > Electronics

39 Views, 1 Favorites, 0 Comments

Testing of an Automatic Line-Following Robot Using Raspberry Pi and Infrared Sensors

5.png

This project develops an automatic line-following robot with Raspberry Pi 3 B+ and HJ-IR1 modules. It uses infrared reflection differences between black/white surfaces to recognize tracks, with the Pi processing signals to control motors. I've solved key issues: chose HDMI for debugging (real-time audio/video), used screw-type terminals for stable motor power, and applied multithreading to fix curve drift from delayed detection. It also switches between auto line-following and remote control + obstacle avoidance via SA, meeting basic needs.

Supplies

★Raspberry Pi 3 B+ Complete Kit*1

★RuisiKai Frsky XM+ Mini Receiver*1

★Level Inverter Module*1

★Frsky Taranis X9D PLUS SE2019 Transmitter*1

★L298N Expansion Board Module*1

★Smart Car Base Plate Module*1

★Brushed Motor and Wheels*4

★HC-SR04 Ultrasonic Module*1

★HJ-IR2 Infrared Photoelectric Sensor*2

★HJ-IR1 Infrared Line-Following Module*2

★Jumpers (as needed)

Introduction

This experiment utilizes the HJ-IR1 infrared line-following module. The module's infrared-emitting diode continuously emits infrared light. When this light is reflected off an object, it is captured by the infrared receiver, which then outputs a signal for processing by the Raspberry Pi. When connecting the Raspberry Pi to an external display device (such as a debugging monitor), the choice between DVI vs HDMI interfaces often arises: HDMI supports simultaneous transmission of audio and video signals, enabling higher resolutions (e.g., 4K) and refresh rates. This makes it more suitable for debugging scenarios requiring real-time viewing of vehicle operation data and visuals. DVI interfaces transmit video signals only. If basic display is sufficient and the device supports only DVI, it can serve as an alternative solution, though additional audio equipment must be connected.

After signal processing, the Raspberry Pi must control the motor driver module. This requires circuit connections using terminal block types: standard options include screw-type, plug-in, and spring-clamp terminal blocks. Screw-type terminal blocks secure wires with screws, offering a firm connection suitable for scenarios requiring a stable power supply like motor drivers; Plug-in terminal blocks facilitate easy installation and removal, simplifying future maintenance or replacement of motor modules; spring-loaded terminal blocks secure wires with spring tension, offering high wiring efficiency and suitability for compact circuit layouts in vehicles. After ensuring reliable circuit connections through appropriate terminal block selection, the car can be controlled to follow a black line by detecting the line and its position.

Such line-following vehicles are also known as simple line-following robots, including applications like restaurant service robots, farm feeding robots, and fruit-picking robots.

HJ-IR1 Infrared Tracking Module

1.png

There are three standard tracking methods:

Infrared Pair Tracking: Utilizes the differing absorption of infrared light by black and white surfaces.

Camera Tracking Method: Uses a camera to read track information, categorized as analog or digital.

Laser Diode Tracking Method: Similar in principle to infrared Tracking, but with an extended detection range.

This experiment employs the first method, infrared sensor tracking, where infrared signals reflected from white surfaces are stronger than those from black surfaces.

Black Surface Reflection Scenario

2.png

Position both sensors forward on the intelligent vehicle, facing downward. Adjust the potentiometer to ensure distinct signals are generated for white and black surfaces.

White Reflection Scenario

3.png

If neither sensor detects an obstacle, the car proceeds straight. When the left sensor detects an obstacle, the vehicle turns right; when the right sensor detects an obstacle, the vehicle turns left.

Installing the Line-following Module

4.png

During actual testing, it was observed that the car tends to drift off the curved black line while following it. This may be due to either excessive speed or an overly long detection interval. Excessive speed can be mitigated by reducing the car's speed. A long detection interval might result from the detection loop running too infrequently. By the time the deviation from the black line is detected, the car may have already drifted off the line.

This may occur because the loop detecting the line-following module's signal includes checks for other sensors, or because the loop only runs at specific intervals to detect other sensors. Therefore, I employed multithreading programming techniques. I created the function for detecting the line-following module's signal as a separate thread. This ensures it does not interfere with other sensors or loops, or runs in parallel, making the line-following control highly responsive.

Experimental Procedure

5.png

Step 1: Connect the circuit.

When connecting the circuit, link the Raspberry Pi's GPIO.23 (corresponding to BOARD number 33) to the output terminal of the left tracking module, and GPIO.24 (corresponding to BOARD number 35) to the output terminal of the right tracking module. Simultaneously, the Raspberry Pi's 5V port supplies power to the VCC terminals of both line-following modules, while the Raspberry Pi's GND terminal connects to the GND terminals of both line-following modules.

Mounting distance for the bottom of the car with added line-following modules.

Step 2: The coding for the files motor_4w.py, moving_control.py, sbus_receiver_pi.py, ultrasonic.py, and infrared.py is not repeated here.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This module serves as the main program for the Raspberry Pi car.

from motor_4w import Motor_4w
from sbus_receiver_pi import SBUSReceiver
from moving_control import MovingControl
from ultrasonic import Ultrasonic
from infrared import Infrared
from tracking import Tracking
import time
import threading

# Initialize SBUS signal receiver instance
sbus_receiver = SBUSReceiver('/dev/ttyAMA0')
# Initialize motor control instance
smp_car = Motor_4w()
# Initialize pins
smp_car.setGPIO()

# Initialize PWM enable signal; A is left wheel
ENA_pwm = smp_car.pwm(smp_car.ENA)
# Initialize PWM enable signal for right wheel
ENB_pwm = smp_car.pwm(smp_car.ENB)

# Initialize vehicle motion control instance
smartcar = MovingControl(smp_car, ENA_pwm, ENB_pwm)

# Initialize ultrasonic instance
ultr = Ultrasonic()
# Initialize ultrasonic pins
ultr.setup()

# Initialize infrared obstacle avoidance instance
infrared = Infrared()
# Initialize infrared obstacle avoidance pins
infrared.setup()

# Initialize tracking module
tracking = Tracking()
tracking.setup()


def ultra_control():
"""Ultrasonic Sensor Control"""
# If distance is less than 10cm, stop first then reverse for 0.3 seconds
smartcar.brake()
smartcar.reverse()
time.sleep(0.3)


def infra_control(infra_left_value, infra_right_value):
"""Infrared Obstacle Avoidance Sensor Control"""
if infra_left_value == 0: # Value 0 indicates obstacle detected on left
# Steer the car to the right
smartcar.accelerator(1, 0.5)
# Maintain right turn for 0.5 seconds
time.sleep(0.5)
if infra_right_value == 0: # Value 0 indicates obstacle detected on right
# Steer the car to the left
smartcar.accelerator(0.5, 1)
# Maintain left turn for 0.5 seconds
time.sleep(0.5)
print(f'infra_left_value={infra_left_value} infra_right_value={infra_right_value}')


# Flag for tracking loop: 0 = do not exit, 1 = exit
exitFlag_tracking = 1

def tracking_control():
"""Infrared line-following sensor controls the car to follow black line automatically"""
while True:
# Conserve computational resources by limiting loop speed
time.sleep(0.5)
while not exitFlag_tracking:
smartcar.acc_value = 40
tracking_left_value, tracking_right_value = tracking.tracking_detect()
# Both values 0 = infrared reflection detected on both sides (white surface) - go straight
if tracking_left_value == 0 and tracking_right_value == 0:
# Balance throttle left and right
smartcar.accelerator()
# Move straight forward
smartcar.forward()
# Left = 1, Right = 0 = no reflection on left (black line) - turn left
elif tracking_left_value == 1 and tracking_right_value == 0:
smartcar.accelerator(0, 1)
smartcar.forward()
# Right = 1, Left = 0 = no reflection on right (black line) - turn right
elif tracking_right_value == 1 and tracking_left_value == 0:
smartcar.accelerator(1, 0)
smartcar.forward()
# Both values 1 = no infrared reflection detected - stop
elif tracking_left_value == 1 and tracking_right_value == 1:
# Stop the car
smartcar.brake()


def sbus_control():
"""Radio Control"""
# Channel 1 (aileron) controls in-place turning
aileron_value = sbus_receiver.get_rx_channel(0)
# Channel 2 (elevator) controls forward/backward movement
elevator_value = sbus_receiver.get_rx_channel(1)
# Channel 4 (rudder) controls lateral movement
rudder_value = sbus_receiver.get_rx_channel(3)

# Channel 3 (throttle) minimum value is 172
acc_value_sbus = 172
if sbus_receiver.get_rx_channel(2):
# Channel 3 controls vehicle speed
acc_value_sbus = sbus_receiver.get_rx_channel(2)
# Convert throttle values (172-1811) to duty cycle (0-100)
smartcar.acc_value = int(100 * (acc_value_sbus - 172) / (1811 - 172))
print(f'original.acc_value={smartcar.acc_value}')
smartcar.accelerator()
print(f'processed.acc_value={smartcar.acc_value}')

# Handle lateral movement (rudder control)
# Center value is 992 with possible minor fluctuations
if 970 < rudder_value < 1100:
# No lateral offset
pass
elif rudder_value >= 1100: # Right offset
rate_right = (1811.0 - rudder_value) / (1811 - 1100)
smartcar.accelerator(1, rate_right)
elif rudder_value <= 970: # Left offset
rate_left = (rudder_value - 172.0) / (970 - 172)
smartcar.accelerator(rate_left, 1)
print(f'elevator_value={elevator_value}, aileron_value={aileron_value}, rudder_value={rudder_value}')

# Handle in-place turning (aileron control)
if aileron_value >= 1100: # Left turn in place
smartcar.leftTurn()
smartcar.accelerator()
elif aileron_value <= 970: # Right turn in place
smartcar.rightTurn()
smartcar.accelerator()
else:
# Stop the car
smartcar.brake()

# Handle forward/backward movement (elevator control)
if elevator_value >= 1100: # Move forward
smartcar.forward()
elif elevator_value <= 970: # Move backward
smartcar.reverse()


try:
# Create and start tracking control thread to keep it running continuously
myThread_tracking = threading.Thread(target=tracking_control)
myThread_tracking.start()
while True:
# Retrieve complete frame of data
sbus_receiver.update()
# Channel 5 is the tracking switch
tracking_value = sbus_receiver.get_rx_channel(4)
# Switch to line-following mode when SA switch is in down position
if tracking_value > 1800:
# Start tracking loop
exitFlag_tracking = 0
else:
# Exit tracking loop - switch to remote control with sensors
exitFlag_tracking = 1
# Ultrasonic obstacle avoidance
dis = ultr.distance()
if dis < 10:
ultra_control()
# Infrared obstacle avoidance
infra_left_value, infra_right_value = infrared.infra_detect()
if infra_left_value == 0 or infra_right_value == 0:
infra_control(infra_left_value, infra_right_value)
# Remote control vehicle movement
sbus_control()

except KeyboardInterrupt:
smp_car.destroy(ENA_pwm, ENB_pwm)
finally:
smp_car.destroy(ENA_pwm, ENB_pwm)

Summary

This small project concludes here. Based on actual test results, the current line-following system built with the HJ-IR1 infrared tracking module and Raspberry Pi has achieved its core functionality: on a flat black-and-white track, the car can stably recognize the black line using two infrared sensors. Leveraging multithreading programming, the line-following control responds sensitively with no noticeable instances of veering off the black line. The SA switch allows seamless switching between "automatic line-following mode" and "remote control + ultrasonic/infrared obstacle avoidance mode." Both modes function normally for motor drive and sensor data reading, meeting the basic requirements for a line-following robot. I originally planned to integrate servos, cameras, image recognition, and network programming control. However, during a previous experiment, I accidentally burned out the L298N expansion board. Although it functioned again after my amateur repair, the vehicle's compact size and limited space prompted me to select a new car project kit. This also provides an opportunity to examine how other experts write code.