Mobile Robot resembling NASA's Mars Perseverance Rover. Everything from scratch and open-source including mechanical design, electronics, and software. The end goal of this project is to gain experience with mobile robotics concepts including SLAM, state estimation, and dynamic environment navigation.
This project is currently in progress and I'm currently focusing on the drive-base with the following tasks:
- Building a Kalman Filter focusing on the kinematic model of a 6-wheel Mars Rover
- I have also implemented kinematic models for diff-drive, mecanum, and skid-steering for the educational value.
- Creating a data-driven layout for the physical robot, focusing on the important features such as: rocker-bogie assembly, rocker-bogie differential system, and the 4 independently steered wheels.
- Architecting the ROS2 package to easily fit sensors and modularize the filter.
Creating a PPO RL training pipeline for a simplified mobile robot and goal pose navigation- Working on firmware for motor board (RoverMotorControl)
- Designing Battery Management System (BMS) and working on firmware required for battery monitoring
This plot was generated with draw_rocker_bogie.py
A lot of the math expressions aren't rendering correctly and can best be visualized by viewing the markdown render locally in an IDE (VS Code). It's a work in progress to create SVG images for each equation and display them once they are finalized...
Simulated Teleoperation in pygame with ackerman steering, try it out by running prototyping/rover_teleop.py!
Approximating the robot as a diff-drive robot makes it easier to compute the state transition matrix and derive the system dynamics/kinematics. Kinematic models for mecanum wheeled drive and rocker bogie suspension system are more complex, but better represent the robot's motion, and I've implemented motion models for all 3.
Note: Each motion model still needs to be tested rigorously
The control input for the diff-drive robot is very simple both wheel velocities:
The function that describes the state's evolution is the process model:
The state transition matrix
The mecanum wheel robot is omni-directional and each wheel contributes to the robot's overall velocity vector:
Wheel angular speeds (ordered: front-left, front-right, rear-left, rear-right) map to body-frame velocities via a linear transform (forward kinematics):
Where:
-
$R$ is wheel radius -
$W_b$ is the Wheel base (longitudinal length) -
$T$ is the Track width (lateral length) $L = \tfrac{W_b}{2}$ $W = \tfrac{T}{2}$
Body-frame velocities are converted to global-frame motion using the heading
With the extended state vector (including velocities)
Where
Jacobian (state transition matrix)
The lower rows are zero because the new body velocities are set directly from the control (wheel speeds) rather than previous velocities.
The rocker-bogie suspension has 6 powered wheels, 4 of which are able to rotate independently for ackerman-style steering. The control vector includes all wheel velocities and the 4 (outer) wheel steering angles:
Each wheel
Stacking all 6 wheels yields a linear system
Body velocities are transformed to global frame and integrated:
Discrete process model with state
Because
Note that the new velocities and angular rate do not depend on the previous velocities or angular rate, making their corresponding derivatives zero. The new velocities,
This model treats measured linear accelerations (e.g., IMU) as control inputs producing a constant-acceleration (CA) motion assumption over each small interval
State (extended to include velocities & yaw rate):
Control Vector (global-frame linear accelerations):
Continuous dynamics (assuming measured
Discretized (first-order) process model: $$\begin{bmatrix} x_{k+1} \ y_{k+1} \ \theta_{k+1} \ v_{x,k+1} \ v_{y,k+1} \ \omega_{k+1} \end{bmatrix} = \begin{bmatrix} x_k + v_{x,k}\Delta t + \tfrac{1}{2} a_{x,k}\Delta t^2 \ y_k + v_{y,k}\Delta t + \tfrac{1}{2} a_{y,k}\Delta t^2 \ heta_k + \omega_k \Delta t \ v_{x,k} + a_{x,k}\Delta t \ v_{y,k} + a_{y,k}\Delta t \ \omega_k \end{bmatrix}$$
This can be written in linear form
Yaw rate
The goal of the prediction step in a Kalman filter is to use the prediction model to calculate how the robot's state evolves over a time step
The initial state covariance
During each prediction step, we learn more about the system's dynamics and need to update our state-covariance matrix to reflect this new knowledge:
During the update step, we also need to update the state covariance matrix to reflect the new information gained from the sensor measurements. We do this with the kalman gain
The correction step of the filter uses sensor measurements to refine the predicted state. For each sensor, we need a measurement model
The IMU measures the angular rate around the Z-axis
The measurement model only needs to use the angular velocity
Odometry uses wheel encoders coupled with knowledge about the robot's physical layout to obtain an estimate of the robot's pose:
The measurement model selects the pose elements from the state:
Collect data with the robot sitting absolutely still for a specified length of time and calculate the variance of the data set for each sensor's respective values in the measurement model.
The datasheet for each sensor could prove to be useful if they've documented the sensor's noise density and/or standard deviation.
Now that we're equipped with jargon and the individual components of the EKF, we can outline the state estimation pipeline with some pseudocode:
# 1. Initialize state and covariance
X = np.zeros((6, 1)) # [x, y, theta, v_x, v_y, omega]
P = np.eye(6) * 1e-3 # State Covariance Matrix
Q = np.eye(6) * 1e-3 # Process Noise Covariance Matrix
prev_estimate_time = 0 # Timestamp for the previous update
# Loop through prediction and update step
# This typically happens at a fixed time interval e.g. every 100ms or 10Hz
while True:
# Calculate timestep between estimations
current_time = now()
dt = current_time - prev_estimate_time
if (dt <= 0):
continue;
# 2. Prediction Step
# 2.1 Predict the next state using the process model
X_new = process_model(X, dt)
# 2.2 Calculate the Jacobian F
F = calculate_jacobian(X, dt)
# 2.3. Update the state covariance
P = F @ P @ F.T + Q
# 3. Update Step
for [z, H, R] in sensor_readings:
# z is the measurement vector from the sensor
# H is the measurement model matrix for the sensor
# R is the measurement noise covariance for this sensor
# 3.1 Calculate the innovation
y = z - H @ X_new
# 3.2 Calculate the innovation covariance [S]
S = H @ P @ H.T + R
# 3.3 Calculate the kalman gain [K]
K = P @ H.T @ np.linalg.inv(S)
# 3.4 Update the state estimate
X = X_new + K @ y
# 3.5 Update the state covariance
P = (np.eye(6) - K @ H) @ P
# Update estimate timestamp before next loop execution
prev_estimate_timestamp = current_time- Path Following using Visual Odometry for a Mars Rover in High-Slip Environments
- Feature-Based Scanning LiDAR-Inertial Odometry Using Factor Graph Optimization
- Autonomous robotics is driving Perseverance rover’s progress on Mars
- A Friction-Based Kinematic Model for Skid-Steer Wheeled Mobile Robots
- Kalman Filter Overview
- Ghost IV — Sensor Fusion: Encoders + IMU
- Design of Rocker-Bogie Mechanism
- The Challenges of Designing the Rocker-Bogie Suspension for the Mars Exploration Rover
- Design and Fabrication of six wheels Rocker Bogie Mechanisms
- Understanding and Making Rocker-Bogie System






