Introduction
Mars-RS simulates a differential drive robot, where motion is controlled by independently setting the velocities of left and right wheels. Understanding the physics of differential drive is essential for effective motion control.
Robot Structure
The Robot struct maintains the robot’s state:
pub struct Robot {
pub position : ( f32 , f32 ), // (x, y) coordinates
pub heading : f32 , // Orientation in radians
pub robotSize : f32 // Half of the track width
}
robotSize represents half the distance between the left and right wheels (half the track width). This is crucial for rotation calculations.
Robot Dimensions
The robot size is calculated based on a scale factor:
self . robotSize = ft () / 12.0 * 15.0 / 2.0 ;
Where:
ft() returns a scaling factor for the field
The robot is 15 units wide (in scaled coordinates)
Division by 2 gives the half-width
Differential Drive Kinematics
The Step Function
The step function updates the robot’s position based on left and right wheel velocities:
pub fn step ( & mut self , d : ( f32 , f32 )) {
let deltaRotation = ( d . 1 - d . 0 ) / ( self . robotSize);
self . heading += deltaRotation;
if deltaRotation == 0.0 {
// Straight line motion
self . position . 0 -= ( PI / 2.0 + self . heading) . cos () * d . 1 ;
self . position . 1 -= ( PI / 2.0 + self . heading) . sin () * d . 1 ;
}
else {
// Curved motion
let r = d . 0 / deltaRotation + self . robotSize / 2.0 ;
let relativeY = 2.0 * (deltaRotation / 2.0 ) . sin () * r ;
let rotationOffset = self . heading + (deltaRotation / 2.0 );
let theta = PI / 2.0 + rotationOffset;
let radius = relativeY;
self . position . 0 -= radius * theta . cos ();
self . position . 1 -= radius * theta . sin ();
}
}
Parameters:
d.0: Left wheel velocity
d.1: Right wheel velocity
Rotation Calculation
The change in heading (rotation) is determined by the velocity difference:
Δ θ = v R − v L w \Delta\theta = \frac{v_R - v_L}{w} Δ θ = w v R − v L
Where:
Δ θ \Delta\theta Δ θ = change in heading (deltaRotation)
v R v_R v R = right wheel velocity (d.1)
v L v_L v L = left wheel velocity (d.0)
w w w = track width (2 × robotSize)
let deltaRotation = ( d . 1 - d . 0 ) / ( self . robotSize);
Positive rotation: Right wheel faster than left (robot turns left)Negative rotation: Left wheel faster than right (robot turns right)Zero rotation: Both wheels at same speed (straight line)
Motion Cases
Case 1: Straight Line Motion
When both wheels move at the same speed (v L = v R v_L = v_R v L = v R ):
if deltaRotation == 0.0 {
self . position . 0 -= ( PI / 2.0 + self . heading) . cos () * d . 1 ;
self . position . 1 -= ( PI / 2.0 + self . heading) . sin () * d . 1 ;
}
Mathematics:
The robot moves distance d d d in the direction of its heading:
x n e w = x o l d + d ⋅ cos ( θ + π 2 ) y n e w = y o l d + d ⋅ sin ( θ + π 2 ) \begin{align*}
x_{new} &= x_{old} + d \cdot \cos(\theta + \frac{\pi}{2}) \\
y_{new} &= y_{old} + d \cdot \sin(\theta + \frac{\pi}{2})
\end{align*} x n e w y n e w = x o l d + d ⋅ cos ( θ + 2 π ) = y o l d + d ⋅ sin ( θ + 2 π )
The π 2 \frac{\pi}{2} 2 π (90°) offset converts from the robot’s heading representation (where 0 points up) to standard mathematical convention (where 0 points right). This coordinate system choice is common in robotics where “forward” is considered the positive y-direction.
Case 2: Curved Motion
When wheels move at different speeds, the robot follows a circular arc:
else {
let r = d . 0 / deltaRotation + self . robotSize / 2.0 ;
let relativeY = 2.0 * (deltaRotation / 2.0 ) . sin () * r ;
let rotationOffset = self . heading + (deltaRotation / 2.0 );
let theta = PI / 2.0 + rotationOffset;
let radius = relativeY;
self . position . 0 -= radius * theta . cos ();
self . position . 1 -= radius * theta . sin ();
}
Instantaneous Center of Curvature (ICC)
The robot rotates around a point called the ICC:
R = v L Δ θ + w 2 R = \frac{v_L}{\Delta\theta} + \frac{w}{2} R = Δ θ v L + 2 w
let r = d . 0 / deltaRotation + self . robotSize / 2.0 ;
Where R R R is the radius from the robot’s center to the ICC.
The ICC lies along a line perpendicular to the robot’s heading, offset to one side depending on which wheel is faster.
Arc Length Calculation
The chord length (straight-line distance traveled) is:
c h o r d = 2 R sin ( Δ θ 2 ) chord = 2R \sin\left(\frac{\Delta\theta}{2}\right) c h or d = 2 R sin ( 2 Δ θ )
let relativeY = 2.0 * (deltaRotation / 2.0 ) . sin () * r ;
This represents the displacement of the robot’s center during the rotation.
Position Update
The final position is calculated using the chord length and the average heading:
let rotationOffset = self . heading + (deltaRotation / 2.0 );
let theta = PI / 2.0 + rotationOffset;
self . position . 0 -= radius * theta . cos ();
self . position . 1 -= radius * theta . sin ();
The rotationOffset represents the heading at the midpoint of the motion, giving a more accurate position update.
Differential Drive Properties
Set wheel velocities to equal magnitudes but opposite directions: robot . step (( 50.0 , - 50.0 )); // Spin right
robot . step (( - 50.0 , 50.0 )); // Spin left
The robot rotates around its center point.
The minimum turning radius occurs when one wheel is stopped: R m i n = w 2 R_{min} = \frac{w}{2} R min = 2 w This equals robotSize in the code.
Maximum radius (straightest possible turn) approaches infinity as wheel velocities become equal: lim v L → v R R = ∞ \lim_{v_L \to v_R} R = \infty v L → v R lim R = ∞
Velocity to Wheel Speed Conversion
Movement algorithms calculate linear and angular velocities, then convert to wheel speeds:
let linearVel = lCont . out (linearError);
let angularVel = rCont . out (rotationError);
let rVel = linearVel - angularVel;
let lVel = linearVel + angularVel;
Formulas:
v L = v l i n e a r + v a n g u l a r v R = v l i n e a r − v a n g u l a r \begin{align*}
v_L &= v_{linear} + v_{angular} \\
v_R &= v_{linear} - v_{angular}
\end{align*} v L v R = v l in e a r + v an gu l a r = v l in e a r − v an gu l a r
Where:
v l i n e a r v_{linear} v l in e a r = desired forward velocity
v a n g u l a r v_{angular} v an gu l a r = desired rotational velocity
Positive angular velocity increases left wheel speed and decreases right wheel speed, causing a left turn.Negative angular velocity does the opposite, causing a right turn.
Coordinate System
Mars-RS uses a screen-based coordinate system:
Origin: Top-left corner
X-axis: Increases to the right
Y-axis: Increases downward
Heading: 0 radians points up (negative Y direction)
// Heading = 0: pointing up
// Heading = π/2: pointing right
// Heading = π: pointing down
// Heading = 3π/2: pointing left
The negative signs in the step function (-=) account for the inverted Y-axis where positive motion in standard coordinates corresponds to negative screen coordinates.
Rendering the Robot
The render method draws the robot using transformed vertices:
pub fn render ( & mut self ) {
self . robotSize = ft () / 12.0 * 15.0 / 2.0 ;
let v1 = util :: rotate! [ - self . robotSize, self . robotSize, self . heading, self . position];
let v2 = util :: rotate! [ self . robotSize, self . robotSize, self . heading, self . position];
let v3 = util :: rotate! [ - self . robotSize, - self . robotSize, self . heading, self . position];
let v4 = util :: rotate! [ self . robotSize, - self . robotSize, self . heading, self . position];
draw_triangle ( v1 , v2 , v3 , Color :: from_hex ( 0x6F2232 ));
draw_triangle ( v3 , v4 , v2 , Color :: from_hex ( 0x6F2232 ));
draw_line ( v3 . x, v3 . y, v4 . x, v4 . y, 4.0 , Color :: from_hex ( 0x950740 ));
}
The robot is drawn as a square with a line indicating the front (heading direction).
Rotation Macro
The rotate! macro transforms local coordinates to world coordinates:
macro_rules! rotate {
( $ x : expr , $ y : expr , $ t : expr , $ p : expr ) => {
{
let st = $ t . sin ();
let ct = $ t . cos ();
Vec2 {
x : ( ct * $ x - st * $ y ) + $ p . 0 ,
y : ( st * $ x + ct * $ y ) + $ p . 1
}
}
};
}
This implements the 2D rotation matrix:
[ x ′ y ′ ] = [ cos θ − sin θ sin θ cos θ ] [ x y ] + [ p x p y ] \begin{bmatrix}
x' \\
y'
\end{bmatrix}
=
\begin{bmatrix}
\cos\theta & -\sin\theta \\
\sin\theta & \cos\theta
\end{bmatrix}
\begin{bmatrix}
x \\
y
\end{bmatrix}
+
\begin{bmatrix}
p_x \\
p_y
\end{bmatrix} [ x ′ y ′ ] = [ cos θ sin θ − sin θ cos θ ] [ x y ] + [ p x p y ]
Control Loop Integration
The step function integrates seamlessly with movement algorithms:
while start . elapsed () . as_millis () < timeout . into () {
{
let mut robot = robot . lock () . unwrap ();
let pos = robot . position;
let heading = robot . heading . to_degrees () % 360.0 ;
robot . step ( pidMTPVel ( pos , heading , target , rotationCut,
& mut lCont, & mut rCont, min ));
}
thread :: sleep ( Duration :: from_millis ( 10 ));
}
The 10ms update rate (100Hz) provides smooth motion simulation.
Physical Constraints
Real robots have constraints that the simulation could incorporate:
Velocity Limits
Acceleration Limits
Slippage Model
let maxVel = 127.0 ;
let lVel = lVel . clamp ( - maxVel, maxVel);
let rVel = rVel . clamp ( - maxVel, maxVel);
The current Mars-RS implementation doesn’t enforce velocity limits, but the eulerTurn function shows an example of clamping: vel = if vel . abs () >= 127.0 { 127.0 * vel . signum ()} else { vel };
Practical Applications
Example: Tank Drive
Direct control for teleoperation:
let leftJoystick = getUserInput () . left; // -127 to 127
let rightJoystick = getUserInput () . right; // -127 to 127
robot . step ((leftJoystick, rightJoystick));
Example: Arcade Drive
Single-stick control:
let forward = getJoystick () . y; // Forward/backward
let turn = getJoystick () . x; // Left/right
let leftPower = forward + turn ;
let rightPower = forward - turn ;
robot . step ((leftPower, rightPower));
Movement Algorithms High-level motion control using robot physics
PID Control Convert errors to wheel velocities
Pure Pursuit Path following with differential drive