Translating encoder changes into (x, y, θ) updates with a tiny bit of trig.
Every 10 ms:
d = (ΔL + ΔR) / 2.Δθ = (ΔL − ΔR) / trackWidth.θ += Δθ.x += d * cos(θ - Δθ/2)y += d * sin(θ - Δθ/2)#include "vex.h"
#include <cmath>
// using namespace vex; (auto-included by VEXcode IQ)
// --- Configure for your robot ---
const double WHEEL_DIAM_IN = 2.75; // your wheel diameter
const double TRACK_WIDTH = 11.0; // distance between left and right wheels
const double TICKS_PER_REV = 360.0; // motor encoder ticks per revolution
// --- Pose ---
double poseX = 0.0;
double poseY = 0.0;
double poseT = 0.0; // theta in radians
// --- Previous encoder readings ---
double prevL = 0;
double prevR = 0;
double ticksToInches(double ticks) {
double revs = ticks / TICKS_PER_REV;
return revs * (3.14159 * WHEEL_DIAM_IN);
}
void odomUpdate() {
double curL = LeftMotor.position(degrees);
double curR = RightMotor.position(degrees);
double dL = ticksToInches(curL - prevL);
double dR = ticksToInches(curR - prevR);
prevL = curL;
prevR = curR;
double d = (dL + dR) / 2.0; // forward distance moved
double dT = (dL - dR) / TRACK_WIDTH; // change in heading (radians)
double thetaMid = poseT + dT / 2.0; // midpoint heading
poseX += d * cos(thetaMid);
poseY += d * sin(thetaMid);
poseT += dT;
}
// Run this in its own task so the main code is free
int odomTask() {
while (true) {
odomUpdate();
wait(10, msec);
}
return 0;
}
int main() {
// start the tracking task
task tracker(odomTask);
// ... your autonomous code, which can read poseX, poseY, poseT anytime
}
// Drive to a point on the field
void driveToPoint(double targetX, double targetY) {
while (true) {
double dx = targetX - poseX;
double dy = targetY - poseY;
double dist = sqrt(dx*dx + dy*dy);
if (dist < 1.0) break; // within 1 inch
// P-control: aim at the target, then move toward it
double targetHeading = atan2(dy, dx);
double headingError = targetHeading - poseT;
// ... apply PID to turn while moving
wait(20, msec);
}
}
Two-wheel odometry using only drive encoders is the simplest setup, but it has weaknesses:
Using only the tracked pose (poseX, poseY, poseT) from this week's odometry task, drive the robot in a 24-inch square and return to the starting position with the same heading.
Two odometry implementations with subtle bugs. Find them.
void odomUpdate() {
double curL = LeftMotor.position(degrees);
double curR = RightMotor.position(degrees);
double dL = ticksToInches(curL - prevL);
double dR = ticksToInches(curR - prevR);
// (no update of prevL / prevR here)
double d = (dL + dR) / 2.0;
double dT = (dL - dR) / TRACK_WIDTH;
poseX += d * cos(poseT + dT/2.0);
poseY += d * sin(poseT + dT/2.0);
poseT += dT;
}
void odomUpdate() {
// ... encoders read and delta-ed correctly ...
double d = (dL + dR) / 2.0;
double dT = (dL - dR) / TRACK_WIDTH;
poseX += d * cos(poseT); // using start heading
poseY += d * sin(poseT); // using start heading
poseT += dT;
}