Robotics is the integration of mechanics, electronics, and software into one closed feedback loop: sense, plan, act, repeat. Beginners stall because they treat these as separate subjects. Engineers progress faster by building one small robot early and adding depth in layers. This roadmap gives you that sequence: the math you need, the hardware to buy, the ROS 2 workflow to learn, and a working PID velocity controller you can run today.
Quick Takeaways
- Start with one differential-drive robot. A two-wheel base with an IMU and wheel encoders teaches kinematics, sensing, and control in a single platform.
- Learn math in service of builds. Linear algebra, basic calculus, and probability cover 90% of early work.
- Adopt ROS 2 (Jazzy or later) by month three.
nodes,topics, andlaunch filesare the standard vocabulary of the industry. - Simulate before you wire. Gazebo catches control and URDF errors at zero hardware cost.
| Stage | Duration | Focus | Deliverable |
|---|---|---|---|
| 1. Foundations | Weeks 1-4 | Python, C++, Linux, linear algebra | Python scripts, Ubuntu setup |
| 2. Embedded & Electronics | Weeks 5-10 | Microcontrollers, motor drivers, sensors | Encoder-fed motor loop |
| 3. Control Theory | Weeks 8-14 | PID, kinematics, state estimation | Tuned velocity controller |
| 4. ROS 2 | Weeks 12-22 | Nodes, topics, TF2, Nav2 | Teleoperated robot in simulation |
| 5. Perception & Autonomy | Weeks 20-36 | SLAM, vision, sensor fusion | Autonomous navigation demo |
Stage 1: Build the Foundational Skill Stack
Programming
Learn Python first for rapid prototyping and ROS 2 nodes. Add C++17 when you need deterministic timing, since real-time control loops and high-rate drivers run in C++. Work in Ubuntu 24.04 LTS (native install or dual boot). Virtual machines add latency and break USB device passthrough.
Core Linux skills to practice: bash, git, ssh, udev rules, and systemd services.
Mathematics Checklist
| Topic | Why Robots Need It | Where It Appears |
|---|---|---|
| Vectors & matrices | Pose and transform representation | Kinematics, TF2 |
| Rotation matrices & quaternions | 3D orientation without gimbal lock | IMU, manipulators |
| Derivatives & integrals | Velocity, acceleration, PID terms | Control loops |
| Probability & Gaussians | Noisy sensor models | Kalman filter, SLAM |
| Optimization (least squares) | Calibration, localization | Pose graph SLAM |
A 2D pose uses the homogeneous transform:
T = | cos(θ) -sin(θ) x |
| sin(θ) cos(θ) y |
| 0 0 1 |
Chaining transforms (T_world_base * T_base_sensor) is the single most reused operation in robotics.
Stage 2: Choose Your Hardware
Compute Platform Comparison
| Platform | CPU/GPU | Power | Cost | Best For |
|---|---|---|---|---|
| Arduino/ATmega | 16 MHz, 8-bit | ~0.2W | Low | Learning I/O basics |
| ESP32 | 240 MHz dual-core | ~0.5W | Low | WiFi sensors, micro-ROS |
| Teensy 4.1 | 600 MHz Cortex-M7 | ~1W | Low-Mid | 1kHz+ motor control loops |
| Raspberry Pi 5 | 2.4 GHz quad-core | 5-12W | Mid | ROS 2 on-robot compute |
| Jetson Orin Nano | 6-core + 1024 CUDA | 7-15W | High | Vision and deep learning |
A proven architecture: Teensy 4.1 runs the low-level motor loop at 1kHz, while a Raspberry Pi 5 runs ROS 2 and talks to the microcontroller over USB serial or micro-ROS.
Motor Selection
| Motor Type | Precision | Torque Density | Control Complexity | Typical Use |
|---|---|---|---|---|
| Brushed DC | Medium (with encoder) | Low | Low | Beginner wheeled bases |
| Stepper | 0.9°-1.8° per step, open loop | Medium | Low | 3D printers, light arms |
| Servo (hobby) | ~1° | Low | Very low | Grippers, pan-tilt |
| BLDC + FOC | High | High | High | Drones, quadrupeds, arms |
For a first robot, choose brushed DC gearmotors with quadrature encoders (e.g., 12V, 30:1 gearbox, 64 CPR encoder).
Sensor Selection
| Sensor | Measures | Range/Rate | Strength | Weakness |
|---|---|---|---|---|
| Wheel encoder | Wheel rotation | 1kHz+ | Cheap, precise odometry | Drifts via slip |
| IMU (6/9-DoF) | Accel, gyro, mag | 100-1000Hz | Fast orientation | Gyro bias drift |
| 2D LiDAR | Planar distance | 5-12Hz, 8-12m | Reliable SLAM input | Planar only |
| Depth camera | RGB + depth | 30-90Hz, 0.3-10m | Rich 3D data | Sunlight sensitivity |
| Ultrasonic | Distance | 40Hz, ~4m | Very cheap | Wide beam, noisy |
Wiring Layout for a Starter Differential-Drive Robot
- Battery: 3S LiPo (11.1V nominal) or 12V NiMH feeds the motor driver directly.
- Buck converter: Steps down to 5V/5A for the Raspberry Pi.
- Motor driver: Dual H-bridge (e.g., TB6612FNG), PWM at 20kHz to stay above audible range.
- Encoders: Channels A/B to interrupt-capable pins.
- Common ground: Tie all grounds together. Floating grounds cause erratic readings.
- Flyback protection and fuse: Add a 10A inline fuse on the battery lead.
Stage 3: Kinematics and Control
Differential-Drive Kinematics
With wheel radius r and track width L, wheel angular velocities ω_l and ω_r give body velocities:
v = r * (ω_r + ω_l) / 2 # linear velocity (m/s)
ω = r * (ω_r - ω_l) / L # angular velocity (rad/s)
Inverse kinematics, needed to turn a cmd_vel command into wheel speeds:
ω_l = (v - ω * L / 2) / r
ω_r = (v + ω * L / 2) / r
Odometry integration at time step dt:
x += v * cos(θ) * dt
y += v * sin(θ) * dt
θ += ω * dt
PID Control Formulation
The control law for error e(t) = setpoint - measurement:
u(t) = Kp * e(t) + Ki * ∫e(τ)dτ + Kd * de(t)/dt
Discrete form at sample period Ts:
u[k] = Kp*e[k] + Ki*Ts*Σe[i] + Kd*(e[k] - e[k-1])/Ts
Each term has a job. Kp drives response speed. Ki removes steady-state error. Kd damps overshoot but amplifies encoder noise, so filter it.
PID Controller Implementation (Python)
class PID:
"""Discrete PID with anti-windup clamping and derivative filtering."""
def __init__(self, kp, ki, kd, out_min=-255.0, out_max=255.0, d_alpha=0.1):
self.kp, self.ki, self.kd = kp, ki, kd
self.out_min, self.out_max = out_min, out_max
self.d_alpha = d_alpha # low-pass coefficient for derivative (0-1)
self.integral = 0.0
self.prev_error = 0.0
self.d_filtered = 0.0
def update(self, setpoint, measurement, dt):
error = setpoint - measurement
# Integral with clamping to prevent windup
self.integral += error * dt
i_term = self.ki * self.integral
i_term = max(min(i_term, self.out_max), self.out_min)
# Low-pass filtered derivative to reject encoder quantization noise
raw_d = (error - self.prev_error) / dt
self.d_filtered = self.d_alpha * raw_d + (1 - self.d_alpha) * self.d_filtered
output = self.kp * error + i_term + self.kd * self.d_filtered
self.prev_error = error
# Saturate output to PWM range
return max(min(output, self.out_max), self.out_min)
Tuning Procedure (Ziegler-Nichols Starting Point)
- Set
Ki = Kd = 0. - Raise
Kpuntil the wheel speed oscillates steadily. Record thisKuand oscillation periodTu. - Apply the classic PID rules:
| Gain | Formula |
|---|---|
| Kp | 0.6 * Ku |
| Ki | 1.2 * Ku / Tu |
| Kd | 0.075 * Ku * Tu |
- Reduce
Kpby 20-30% if overshoot exceeds 10%. Ziegler-Nichols tuning is aggressive, so treat it as a starting point only.
Stage 4: Learn ROS 2
ROS 2 is the standard middleware for robot software. It uses DDS for real-time-capable, decentralized messaging. Install ROS 2 Jazzy Jalisco on Ubuntu 24.04, or the newest LTS available for your distribution.
ROS 1 vs. ROS 2
| Feature | ROS 1 | ROS 2 |
|---|---|---|
| Middleware | Custom TCPROS, central master | DDS, no master |
| Real-time support | Limited | Designed for it |
| Security | None | SROS2 (DDS-Security) |
| Launch files | XML | Python, XML, YAML |
| Status | End of life (Noetic, May 2025) | Actively developed |
Start directly with ROS 2. ROS 1 is no longer maintained.
Core Concepts
- Nodes: Single-purpose processes (e.g.,
motor_driver,odom_publisher). - Topics: Publish/subscribe channels with typed messages (
/cmd_vel,/odom,/scan). - Services and actions: Request/response calls and long-running goals with feedback.
- TF2: Maintains the coordinate frame tree (
map→odom→base_link→lidar_link). launchfiles: Start and configure many nodes in one command.
First ROS 2 Node: Velocity Command Subscriber
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist # fields: linear.x (m/s), angular.z (rad/s)
WHEEL_RADIUS = 0.033 # meters
TRACK_WIDTH = 0.160 # meters
class DiffDriveNode(Node):
def __init__(self):
super().__init__('diff_drive_node')
# Subscribe to velocity commands from teleop or Nav2
self.sub = self.create_subscription(Twist, '/cmd_vel', self.cmd_cb, 10)
def cmd_cb(self, msg: Twist):
v = msg.linear.x # forward velocity request
w = msg.angular.z # yaw rate request
# Inverse kinematics: body velocity -> wheel angular velocities
w_left = (v - w * TRACK_WIDTH / 2.0) / WHEEL_RADIUS
w_right = (v + w * TRACK_WIDTH / 2.0) / WHEEL_RADIUS
self.get_logger().info(f'L: {w_left:.2f} rad/s | R: {w_right:.2f} rad/s')
# Next step: forward these to the microcontroller PID loops
def main():
rclpy.init()
rclpy.spin(DiffDriveNode())
rclpy.shutdown()
if __name__ == '__main__':
main()
Test it without hardware:
# Terminal 1: run the node
ros2 run my_robot diff_drive_node
# Terminal 2: publish a 0.2 m/s forward command at 10Hz
ros2 topic pub -r 10 /cmd_vel geometry_msgs/msg/Twist \
"{linear: {x: 0.2}, angular: {z: 0.0}}"
Minimal URDF Snippet
<robot name="starter_bot">
<link name="base_link">
<visual>
<geometry><box size="0.20 0.15 0.06"/></geometry>
</visual>
</link>
<link name="left_wheel">
<visual>
<geometry><cylinder radius="0.033" length="0.02"/></geometry>
</visual>
</link>
<joint name="left_wheel_joint" type="continuous">
<parent link="base_link"/>
<child link="left_wheel"/>
<origin xyz="0 0.08 0" rpy="-1.5708 0 0"/>
<axis xyz="0 0 1"/>
</joint>
</robot>
Stage 5: Perception, State Estimation, and Autonomy
Sensor Fusion with a Complementary Filter
Fuse gyro (fast, drifts) and accelerometer (noisy, drift-free) to estimate pitch:
angle = α * (angle + gyro_rate * dt) + (1 - α) * accel_angle
With α ≈ 0.98 and dt = 0.005s (200Hz), this runs on any microcontroller. When you need full 3D pose or multi-sensor fusion, step up to an Extended Kalman Filter via the robot_localization package, fusing wheel odometry and IMU data.
SLAM and Navigation
| Algorithm | Sensor | Strength | Limitation |
|---|---|---|---|
| SLAM Toolbox | 2D LiDAR | Robust, ROS 2 native, loop closure | Planar environments |
| Cartographer | LiDAR + IMU | Accurate, 2D/3D | Complex tuning |
| RTAB-Map | RGB-D / stereo | Visual loop closure | CPU-heavy |
| ORB-SLAM3 | Camera | Sparse, accurate visual SLAM | Needs calibration |
Pair SLAM Toolbox with Nav2 for a full stack: map building, localization with AMCL, global planning, and local obstacle avoidance.
Real-World Workflow: Your First Autonomous Mapping Run
- Simulate: Launch your URDF in Gazebo with a simulated LiDAR. Confirm
/scanand/odompublish at 10Hz and 50Hz. - Teleoperate: Drive with
ros2 run teleop_twist_keyboard teleop_twist_keyboard. - Map: Run SLAM Toolbox in online async mode:
ros2 launch slam_toolbox online_async_launch.py use_sim_time:=true - Visualize: Open RViz2, add
Map,LaserScan, andTFdisplays. - Save the map:
ros2 run nav2_map_server map_saver_cli -f my_map - Navigate: Launch Nav2 with
my_map.yaml, set a 2D Pose Estimate, then send a Nav2 Goal. - Transfer to hardware: Match topic names and frame IDs, then retune the PID gains on real motors.
Common Failure Modes
| Symptom | Likely Cause | Fix |
|---|---|---|
| Robot drifts in a straight line | Unequal wheel radii or motor mismatch | Calibrate odometry scale factors |
| Map smears or doubles | Bad TF tree or timestamp mismatch | Check use_sim_time, verify odom → base_link |
| Wheel speed oscillates | Kp too high or loop rate too slow |
Lower Kp, run at 100Hz or faster |
| Random microcontroller resets | Motor back-EMF noise on the supply | Add decoupling capacitors, separate power rails |
| Nav2 refuses to plan | Costmap inflation too large | Reduce inflation_radius |
Recommended 12-Month Learning Path
| Months | Goal | Milestone Project |
|---|---|---|
| 1-2 | Python, Linux, math refresh | Plot a 2D robot pose trajectory |
| 3-4 | Electronics and embedded C++ | Encoder-based motor speed PID |
| 5-6 | ROS 2 fundamentals | Teleop robot with /odom and TF |
| 7-8 | Simulation and URDF | Gazebo differential-drive robot |
| 9-10 | SLAM and Nav2 | Map and navigate a room |
| 11-12 | Vision and manipulation | Pick-and-place with a 4-DoF arm |
Frequently Asked Questions
How long does it take to learn robotics from scratch?
Expect 6-12 months of consistent practice to build a mobile robot that maps and navigates autonomously. Mastery of advanced topics such as manipulation, legged locomotion, and reinforcement learning takes years.
Do I need a degree to start robotics?
No. Open tools (ROS 2, Gazebo, open-source datasets) and low-cost hardware allow self-taught learners to build working robots. A degree helps for research roles, but a project portfolio carries significant weight in hiring.
What programming language is best for robotics?
Learn Python and C++. Python handles prototyping, ROS 2 nodes, and vision pipelines. C++ handles real-time control, drivers, and performance-critical perception code.
What is the best first robotics project?
Build a two-wheeled differential-drive robot with encoders and an IMU. It covers motor control, odometry, sensor fusion, and ROS 2 integration, and it scales to SLAM and navigation by adding a 2D LiDAR.




