Overview
The util module provides essential utility functions for robotics calculations, including PID control, distance calculations, angle utilities, and drawing helpers. These are the building blocks for autonomous movement and control systems.
PID Control
PidConstants Struct
pub struct PidConstants {
pub p: f32,
pub i: f32,
pub d: f32,
pub tolerance: f32,
pub integralThreshold: f32,
pub maxIntegral: f32,
}
Configuration parameters for a PID controller.
Proportional gain - responds to current error
Integral gain - responds to accumulated error over time
Derivative gain - responds to rate of error change
Error threshold below which integral is reset to zero
Error threshold below which integral accumulation begins
Maximum allowed integral value (prevents integral windup)
Example:
use mars_rs::util::PidConstants;
let constants = PidConstants {
p: 0.05,
i: 0.001,
d: 0.01,
tolerance: 5.0,
integralThreshold: 50.0,
maxIntegral: 100.0
};
new
pub fn new() -> PidConstants
Creates a PidConstants with all values set to 0.0 (useful as a starting point).
Pid Struct
pub struct Pid {
prevError: f32,
derivative: f32,
integral: f32,
constants: PidConstants
}
PID controller implementation with state tracking.
new
pub fn new(con: PidConstants) -> Pid
Creates a new PID controller with the given constants.
Configuration parameters for the controller
Example:
use mars_rs::util::{Pid, PidConstants};
let constants = PidConstants {
p: 0.05,
i: 0.001,
d: 0.01,
tolerance: 5.0,
integralThreshold: 50.0,
maxIntegral: 100.0
};
let mut controller = Pid::new(constants);
out
pub fn out(&mut self, error: f32) -> f32
Calculates PID output based on current error.
Current error value (target - actual)
Returns: f32 - Control output value
Behavior:
- Resets integral if error is within tolerance
- Only accumulates integral if error exceeds integral threshold
- Clamps integral to maxIntegral
- Calculates derivative from error change
- Returns weighted sum:
P*error + I*integral + D*derivative
Example:
use mars_rs::util::{Pid, PidConstants};
let mut controller = Pid::new(PidConstants {
p: 0.05,
i: 0.001,
d: 0.01,
tolerance: 5.0,
integralThreshold: 50.0,
maxIntegral: 100.0
});
// In control loop
loop {
let target = 100.0;
let actual = get_sensor_value();
let error = target - actual;
let output = controller.out(error);
set_motor_power(output);
}
PID controllers maintain internal state. Create separate controller instances for each control loop (e.g., one for position, one for heading).
Distance Functions
dist
pub fn dist(a: (f32, f32), b: (f32, f32)) -> f32
Calculates Euclidean distance between two points.
First point coordinates (x, y)
Second point coordinates (x, y)
Returns: f32 - Distance between points
Example:
use mars_rs::util::dist;
let robot_pos = (400.0, 300.0);
let target_pos = (600.0, 500.0);
let distance = dist(robot_pos, target_pos);
println!("Distance to target: {}", distance);
if distance < 50.0 {
println!("Reached target!");
}
Angle Utilities
absoluteAngleToPoint
pub fn absoluteAngleToPoint(p1: (f32, f32), p2: (f32, f32)) -> f32
Calculates the absolute angle from p1 to p2 in degrees (0-360).
Returns: f32 - Angle in degrees (0° = up, increasing clockwise)
Example:
use mars_rs::util::absoluteAngleToPoint;
let robot_pos = (400.0, 300.0);
let target_pos = (500.0, 400.0);
let angle = absoluteAngleToPoint(robot_pos, target_pos);
println!("Target is at {} degrees", angle);
The coordinate system has 0° pointing up (negative Y direction) and increases clockwise. This matches typical robotics conventions.
dirToSpin
pub fn dirToSpin(target: f32, current: f32) -> i16
Determines the optimal direction to turn to reach target angle.
Returns: i16 - Turn direction (1 for clockwise, -1 for counterclockwise)
Example:
use mars_rs::util::dirToSpin;
let current_heading = 45.0;
let target_heading = 315.0;
let direction = dirToSpin(target_heading, current_heading);
if direction > 0 {
println!("Turn clockwise");
} else {
println!("Turn counterclockwise");
}
minError
pub fn minError(target: f32, current: f32) -> f32
Calculates the minimum signed angular error between two angles, accounting for 360° wraparound.
Returns: f32 - Signed error in degrees (positive = need to turn clockwise)
Example:
use mars_rs::util::minError;
// Robot at 350°, target at 10°
let error = minError(10.0, 350.0);
println!("Error: {}", error); // 20.0 (turn clockwise)
// Robot at 10°, target at 350°
let error = minError(350.0, 10.0);
println!("Error: {}", error); // -20.0 (turn counterclockwise)
Always use minError for angular calculations to avoid the robot taking the long way around (e.g., turning 350° instead of 10°).
Drawing Utilities
draw_arc
pub fn draw_arc(
center: Vec2,
r: f32,
start: f32,
end: f32,
color: Color
)
Draws a filled circular arc.
Starting angle in radians
Example:
use mars_rs::util::draw_arc;
use macroquad::prelude::*;
let center = vec2(400.0, 300.0);
draw_arc(center, 50.0, 0.0, PI, RED);
draw_arc_lines
pub fn draw_arc_lines(
center: Vec2,
r: f32,
start: f32,
end: f32,
thickness: f32,
color: Color
)
Draws an arc outline with specified thickness.
Starting angle in radians
Example:
use mars_rs::util::draw_arc_lines;
use macroquad::prelude::*;
let center = vec2(400.0, 300.0);
draw_arc_lines(center, 50.0, 0.0, PI, 3.0, BLUE);
Macros
rotate!
rotate![$x: expr, $y: expr, $t: expr, $p: expr]
Rotates a point around another point by a given angle.
X coordinate relative to rotation center
Y coordinate relative to rotation center
Rotation angle in radians
Center point of rotation as (f32, f32)
Returns: Vec2 - Rotated point in absolute coordinates
Example:
use mars_rs::util::rotate;
use std::f32::consts::PI;
let center = (400.0, 300.0);
let angle = PI / 4.0; // 45 degrees
// Rotate point (10, 0) around center
let rotated = rotate![10.0, 0.0, angle, center];
println!("Rotated point: ({}, {})", rotated.x, rotated.y);
hex!
Converts a hexadecimal color code to a Color.
Hexadecimal color value (e.g., 0xFF5964)
Returns: Color - macroquad Color object
Example:
use mars_rs::hex;
use macroquad::prelude::*;
let red = hex!(0xFF5964);
let blue = hex!(0x35A7FF);
let purple = hex!(0xDCABDF);
draw_circle(400.0, 300.0, 50.0, red);
pub_struct!
pub_struct![$name:ident {$($field:ident: $t:ty,)*}]
Utility macro for creating public structs with all public fields and derived traits.
Generated Traits:
- Debug
- Clone
- Copy
- PartialEq
Example:
use mars_rs::pub_struct;
pub_struct!(Point {
x: f32,
y: f32,
});
let p1 = Point { x: 10.0, y: 20.0 };
let p2 = p1; // Copy
println!("{:?}", p1); // Debug
Complete PID Tuning Example
use mars_rs::{robot::Robot, movement, util};
use std::sync::{Arc, Mutex};
let robot = Arc::new(Mutex::new(Robot {
position: (400.0, 300.0),
heading: 0.0,
robotSize: 30.0
}));
// Step 1: Start with P-only control
let mut linear_constants = util::PidConstants {
p: 0.01, // Start low
i: 0.0,
d: 0.0,
tolerance: 5.0,
integralThreshold: 50.0,
maxIntegral: 100.0
};
// Step 2: Increase P until responsive but oscillates
linear_constants.p = 0.05;
// Step 3: Add D to reduce oscillation
linear_constants.d = 0.01;
// Step 4: Add I to eliminate steady-state error
linear_constants.i = 0.001;
// Rotation typically needs less aggressive tuning
let rotation_constants = util::PidConstants {
p: 0.015,
i: 0.0,
d: 0.005,
tolerance: 2.0,
integralThreshold: 30.0,
maxIntegral: 50.0
};
movement::pidMTP(
&robot,
(600.0, 400.0),
90.0,
3000,
linear_constants,
rotation_constants,
0.5
);
Angle System Reference
0° / 360°
|
N
|
270° - W ---+--- E - 90°
|
S
|
180°
- 0° points upward (negative Y)
- Angles increase clockwise
- All angle functions handle 360° wraparound
- Use
minError for all angular PID calculations
See Also
- Movement - Uses PID controllers and angle functions
- Robot - Uses rotation utilities for rendering
- Field - Uses drawing utilities