Mobile Robot Differential Odometry with 4X Hardware Quadrature Decoding
Track 2D robot position (x, y, theta) in Cartesian space using dual optical wheel encoders, hardware timer quadrature modes, and Runge-Kutta kinematics integration.
2. Prerequisites & Background Theory:
Differential robot kinematics, 4X quadrature edge detection, hardware timer encoder modes, and floating-point math.
Goal & Engineering Objectives
Compute continuous real-time Cartesian pose (x, y, theta) of an autonomous mobile robot across floor navigation courses with less than 2% cumulative drift.
Governing Mathematical Formulation & Physics
System Architecture & Block Flow Diagram
[Left Wheel Optical Encoder] --(Chan A, B)--> [STM32 TIM2 32-Bit Quadrature Mode]
[Right Wheel Optical Encoder] --(Chan A, B)--> [STM32 TIM3 16-Bit Quadrature Mode]
|
(Periodic Timer Interrupt @ 50 Hz)
v
[Delta Tick Calculation]
[Runge-Kutta Pose Integration]
v
[Cartesian Pose (x, y, theta)]
Required Hardware Bill of Materials (BOM)
| Hardware Component | Qty | Engineering Specification |
|---|---|---|
| Optical Quadrature Wheel Encoders (600 PPR) | 2 | Push-pull AB channels, 2400 counts/rev in 4X |
| STM32F401 Development Board | 1 | Hardware 32-bit timers with quadrature decoder blocks |
| Precision Aluminum Robot Wheels with O-Rings | 2 | Radius r = 32.5 mm ± 0.05 mm |
| Chassis Baseplate | 1 | Fixed track wheelbase B = 145.0 mm |
Wiring, Pinout & Schematics Mapping
Firmware Implementation & Algorithms
// C++: Differential Drive Robot Odometry Integrator
#include <cmath>
struct RobotPose {
double x; // Meters
double y; // Meters
double theta; // Radians
};
class OdometryTracker {
public:
OdometryTracker(double wheel_radius, double track_width, int ppr_4x)
: r(wheel_radius), b(track_width), cpr(ppr_4x) {
pose = {0.0, 0.0, 0.0};
prev_left_ticks = 0;
prev_right_ticks = 0;
}
void update(int64_t cur_left_ticks, int64_t cur_right_ticks) {
int64_t delta_l = cur_left_ticks - prev_left_ticks;
int64_t delta_r = cur_right_ticks - prev_right_ticks;
prev_left_ticks = cur_left_ticks;
prev_right_ticks = cur_right_ticks;
double dist_l = (2.0 * M_PI * r * delta_l) / cpr;
double dist_r = (2.0 * M_PI * r * delta_r) / cpr;
double delta_dist = (dist_r + dist_l) / 2.0;
double delta_theta = (dist_r - dist_l) / b;
// Runge-Kutta 2nd Order integration
double mid_theta = pose.theta + (delta_theta / 2.0);
pose.x += delta_dist * cos(mid_theta);
pose.y += delta_dist * sin(mid_theta);
pose.theta += delta_theta;
// Normalize theta to [-PI, +PI]
while (pose.theta > M_PI) pose.theta -= 2.0 * M_PI;
while (pose.theta < -M_PI) pose.theta += 2.0 * M_PI;
}
RobotPose getPose() const { return pose; }
private:
double r, b;
int cpr;
int64_t prev_left_ticks, prev_right_ticks;
RobotPose pose;
};