Project Overview

A line-following robot is one of the most rewarding beginner robotics projects — and it becomes far more powerful when integrated with ROS via rosserial. In this project, the Arduino reads IR sensors and publishes their state to ROS topics, while a ROS node running on a laptop or Raspberry Pi processes the data and sends motor commands back to the Arduino. This architecture keeps your robot's logic in Python or C++ on the ROS host, making it easy to iterate and debug.

Hardware You'll Need

  • Arduino Mega 2560 (recommended for extra memory)
  • 2x DC gear motors with encoders
  • L298N or L293D motor driver module
  • 3x IR reflectance sensors (analog or digital, e.g., TCRT5000)
  • Robot chassis with wheels and caster
  • 7.4V LiPo battery (or 4x AA pack)
  • USB cable (for rosserial connection to laptop or RPi)

System Architecture

The project is split cleanly between the Arduino and the ROS host:

  • Arduino (via rosserial): Reads IR sensor values → Publishes to /ir_sensors topic. Subscribes to /motor_cmd topic → Drives motors accordingly.
  • ROS Host (Python node): Subscribes to /ir_sensors → Applies line-following algorithm → Publishes motor commands to /motor_cmd.

Step 1: Wire the Hardware

Connect your three IR sensors to Arduino analog pins A0, A1, and A2. Connect the L298N motor driver's IN1–IN4 to digital pins 4, 5, 6, 7, and the ENA/ENB PWM pins to 9 and 10. Power the motor driver from your battery, and power the Arduino via USB from your ROS host.

Step 2: Arduino Firmware (rosserial)

#include <ros.h>
#include <std_msgs/Int16MultiArray.h>
#include <std_msgs/Int16.h>

ros::NodeHandle nh;

// Publisher: IR sensor values
std_msgs::Int16MultiArray ir_msg;
ros::Publisher ir_pub("ir_sensors", &ir_msg);

// Motor command state
int leftSpeed = 0, rightSpeed = 0;

void motorCmdCallback(const std_msgs::Int16MultiArray& cmd) {
  leftSpeed  = cmd.data[0];
  rightSpeed = cmd.data[1];
  analogWrite(9,  abs(leftSpeed));
  analogWrite(10, abs(rightSpeed));
  digitalWrite(4, leftSpeed  >= 0 ? HIGH : LOW);
  digitalWrite(5, leftSpeed  >= 0 ? LOW  : HIGH);
  digitalWrite(6, rightSpeed >= 0 ? HIGH : LOW);
  digitalWrite(7, rightSpeed >= 0 ? LOW  : HIGH);
}

ros::Subscriber<std_msgs::Int16MultiArray> motor_sub("motor_cmd", motorCmdCallback);

void setup() {
  pinMode(4,OUTPUT); pinMode(5,OUTPUT);
  pinMode(6,OUTPUT); pinMode(7,OUTPUT);
  ir_msg.data_length = 3;
  ir_msg.data = new int16_t[3];
  nh.initNode();
  nh.advertise(ir_pub);
  nh.subscribe(motor_sub);
}

void loop() {
  ir_msg.data[0] = analogRead(A0);
  ir_msg.data[1] = analogRead(A1);
  ir_msg.data[2] = analogRead(A2);
  ir_pub.publish(&ir_msg);
  nh.spinOnce();
  delay(50);
}

Step 3: ROS Line-Following Node (Python)

#!/usr/bin/env python3
import rospy
from std_msgs.msg import Int16MultiArray

motor_pub = None
THRESHOLD = 500  # IR threshold (adjust for your sensors)

def ir_callback(msg):
    left_ir   = msg.data[0]
    center_ir = msg.data[1]
    right_ir  = msg.data[2]

    cmd = Int16MultiArray()

    if center_ir < THRESHOLD:          # On the line
        cmd.data = [150, 150]          # Go straight
    elif left_ir < THRESHOLD:         # Line to the left
        cmd.data = [80, 150]           # Turn left
    elif right_ir < THRESHOLD:        # Line to the right
        cmd.data = [150, 80]           # Turn right
    else:                              # Lost the line
        cmd.data = [0, 0]              # Stop

    motor_pub.publish(cmd)

if __name__ == '__main__':
    rospy.init_node('line_follower')
    motor_pub = rospy.Publisher('motor_cmd', Int16MultiArray, queue_size=1)
    rospy.Subscriber('ir_sensors', Int16MultiArray, ir_callback)
    rospy.spin()

Step 4: Launch Everything

  1. Upload the Arduino sketch
  2. Start roscore
  3. Launch the serial node: rosrun rosserial_python serial_node.py /dev/ttyUSB0
  4. Run the line-follower node: python3 line_follower.py
  5. Place your robot on a black line on a white surface and watch it go!

Tuning Tips

  • Adjust the THRESHOLD value in the Python node based on your sensor readings (use rostopic echo /ir_sensors to observe values).
  • Modify motor speed values to match your chassis — heavier robots may need higher PWM values.
  • Reduce the Arduino loop delay() for more responsive turns on sharp curves.

What You've Built

This project demonstrates a clean separation of concerns: the Arduino handles low-level hardware I/O while ROS handles the high-level decision logic. This architecture scales beautifully — you can add obstacle avoidance, logging with rosbag, or visualize sensor data in RViz without touching the Arduino firmware at all.