ROS Noetic Robot Motor Control & Teleoperation (Keyboard + Xbox Controller)
This guide documents the complete real-world process I followed to drive a 4WD robot using ROS Noetic on Raspberry Pi. It starts after installing Ubuntu 20.04 and ROS Noetic and ends with smooth motor control using both keyboard and Xbox Wireless Controller.
1. Hardware & Software Used
Hardware
- Raspberry Pi (Ubuntu 20.04)
- 4WD Robot Chassis
- L298N Motor Driver
- Xbox Wireless Controller (Bluetooth)
Software
- Ubuntu 20.04 (Focal)
- ROS Noetic
- Python 3
- pygame
- BlueZ (Bluetooth)
2. ROS Workspace Setup
mkdir -p ~/catkin_ws/src
cd ~/catkin_ws
catkin_make
source devel/setup.bash
Add this to ~/.bashrc so ROS always loads:
source /opt/ros/noetic/setup.bash
source ~/catkin_ws/devel/setup.bash
3. Motor Driver ROS Node (Forward / Backward / Left / Right)
This node converts /cmd_vel into GPIO signals for an L298N motor driver.
#!/usr/bin/env python3
import rospy
from geometry_msgs.msg import Twist
import RPi.GPIO as GPIO
ENA = 18
IN1 = 17
IN2 = 27
ENB = 13
IN3 = 22
IN4 = 23
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
for pin in [ENA, IN1, IN2, ENB, IN3, IN4]:
GPIO.setup(pin, GPIO.OUT)
pwmA = GPIO.PWM(ENA, 100)
pwmB = GPIO.PWM(ENB, 100)
pwmA.start(0)
pwmB.start(0)
def stop():
for pin in [IN1, IN2, IN3, IN4]:
GPIO.output(pin, GPIO.LOW)
pwmA.ChangeDutyCycle(0)
pwmB.ChangeDutyCycle(0)
def callback(msg):
linear = msg.linear.x
angular = msg.angular.z
left = linear - angular
right = linear + angular
GPIO.output(IN1, GPIO.HIGH if left > 0 else GPIO.LOW)
GPIO.output(IN2, GPIO.LOW if left > 0 else GPIO.HIGH)
GPIO.output(IN3, GPIO.HIGH if right > 0 else GPIO.LOW)
GPIO.output(IN4, GPIO.LOW if right > 0 else GPIO.HIGH)
pwmA.ChangeDutyCycle(min(abs(left) * 100, 100))
pwmB.ChangeDutyCycle(min(abs(right) * 100, 100))
rospy.init_node("motor_driver_node")
rospy.Subscriber("/cmd_vel", Twist, callback)
rospy.on_shutdown(stop)
rospy.spin()
4. Keyboard Control Node (Day 4)
This node allows robot control using keyboard keys:
- W → Forward
- S → Backward
- A → Left
- D → Right
#!/usr/bin/env python3
import rospy
from geometry_msgs.msg import Twist
import sys, select, termios, tty
move = {
'w': (1, 0),
's': (-1, 0),
'a': (0, 1),
'd': (0, -1)
}
def getKey():
tty.setraw(sys.stdin.fileno())
r, _, _ = select.select([sys.stdin], [], [], 0.1)
key = sys.stdin.read(1) if r else ''
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, settings)
return key
settings = termios.tcgetattr(sys.stdin)
rospy.init_node("keyboard_teleop")
pub = rospy.Publisher("/cmd_vel", Twist, queue_size=1)
twist = Twist()
try:
while not rospy.is_shutdown():
key = getKey()
if key in move:
twist.linear.x = move[key][0]
twist.angular.z = move[key][1]
else:
twist.linear.x = 0
twist.angular.z = 0
pub.publish(twist)
finally:
twist.linear.x = 0
twist.angular.z = 0
pub.publish(twist)
5. Xbox Wireless Controller (Bluetooth Pairing)
bluetoothctl
power on
agent on
default-agent
scan on
Put controller into pairing mode (Xbox + Pair button):
pair XX:XX:XX:XX:XX:XX
trust XX:XX:XX:XX:XX:XX
connect XX:XX:XX:XX:XX:XX
LED becomes solid → paired.
6. Xbox Teleop (Headless Fix)
Because Raspberry Pi runs headless, pygame needs a dummy display.
import os
os.environ["SDL_VIDEODRIVER"] = "dummy"
Initialize pygame safely:
pygame.init()
pygame.display.init()
pygame.display.set_mode((1,1))
pygame.joystick.init()
7. Running the System
Terminal 1
roscore
Terminal 2
rosrun my_4wd_robot motor_driver_node.py
Terminal 3 (Keyboard)
rosrun keyboard_teleop keyboard_teleop.py
Terminal 3 (Xbox)
rosrun xbox_teleop xbox_teleop_node.py
8. Validation
rostopic echo /cmd_vel
Joystick or keyboard movement should reflect in velocity values.
9. Final Result
- Stable Bluetooth
- Smooth motor control
- Keyboard + Xbox working
- Headless-safe ROS setup
The robot drives like butter.
10. Next Upgrades
- Deadman safety switch
- Speed modes
- Launch files
- Auto-start on boot
This guide documents a real debugging journey, not theory.
Comments
Post a Comment