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_sensorstopic. Subscribes to/motor_cmdtopic → 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
- Upload the Arduino sketch
- Start
roscore - Launch the serial node:
rosrun rosserial_python serial_node.py /dev/ttyUSB0 - Run the line-follower node:
python3 line_follower.py - Place your robot on a black line on a white surface and watch it go!
Tuning Tips
- Adjust the
THRESHOLDvalue in the Python node based on your sensor readings (userostopic echo /ir_sensorsto 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.