Most everyone struggles with accurate base control in auton. At the highest level, 5225 developed a 3 tracking wheel solution that allowed them to set the world record in programming skills last year, and a handful of teams including 8059 and 139 have replicated their “odometry” based on a document they released last year. Team 5225 Introduction to Position Tracking Document
But at a less extreme level, for people just looking to take a step up from move.relative you may like the base control code I developed this year. Before I go any farther, I’d like to openly admit that I’m not a good coder in any capacity and my conventions and whatnot may be sloppy, but I nevertheless won 8 of my 11 autonomous periods at worlds and was overall very happy with my chassis control in auton. And when my robot did miss, it was entirely because I didn’t spend enough time tuning my constants. The results I got on the competition field were perfectly replicable on all practice fields, regardless of antistatic.
The basic philosophy of my code is that the only possible reason an encoder value would not be representative of the true position of the robot is if the wheels slip or lift off the ground. As such, code that prevents the robot from accelerating or decelerating too fast and thus has no wheel slippage results in encoder values that are perfectly representative of the true position of the robot. To prevent too high an acceleration value, I used slew rate control when speeding up and a P loop when slowing down. 9605 and 169 use a similar concept (though I haven’t seen their code) and PID with slew rate has been a concept forever, so I’m not trying to take credit for anyone else’s work. But here is mine.
int velCap; //velCap limits the change in velocity and must be global
int targetLeft;
int targetRight;
void drivePIDFn(void*){
leftFrontDrive.tare_position(); //reset base encoders
rightFrontDrive.tare_position();
int errorLeft;
int errorRight;
float kp = 0.075;
float kpTurn = 0.2;
int acc = 5;
int voltageLeft = 0;
int voltageRight = 0;
int signLeft;
int signRight;
delay(20);
while(isAuton){
errorLeft = targetLeft - leftFrontDrive.get_position(); //error is target minus actual value
errorRight = targetRight - rightFrontDrive.get_position();
signLeft = errorLeft / abs(errorLeft); // + or - 1
signRight = errorRight / abs(errorRight);
if(signLeft == signRight){
voltageLeft = errorLeft * kp; //intended voltage is error times constant
voltageRight = errorRight * kp;
}
else{
voltageLeft = errorLeft * kpTurn; //same logic with different turn value
voltageRight = errorRight * kpTurn;
}
velCap = velCap + acc; //slew rate
if(velCap > 115){
velCap = 115; //velCap cannot exceed 115
}
if(abs(voltageLeft) > velCap){ //limit the voltage
voltageLeft = velCap * signLeft;
}
if(abs(voltageRight) > velCap){ //ditto
voltageRight = velCap * signRight;
}
leftBackDrive.move(voltageLeft); //set the motors to the intended speed
leftFrontDrive.move(voltageLeft);
rightBackDrive.move(voltageRight);
rightFrontDrive.move(voltageRight);
delay(20);
}
}
void drive(int left, int right){
targetLeft = targetLeft + left;
targetRight = targetRight + right;
velCap = 0; //reset velocity cap for slew rate
}
In auton, to drive 100 ticks forward, I would write drive(100, 100); and then put in an appropriate delay. To turn 100 ticks, I would write drive(100, -100);.
How does this code work? Let me break it down.
If one of my auton commands is drive(100, 100); the drive function will set the targetLeft and targetRight globals to their current values plus 100 and set the velCap global to 0. Then, in the PID loop (really just a P loop in this case), the error will be the target value minus the real value (100 if the robot has not moved) and will try set the voltage on either side of the base to 100 * kp. Meanwhile, the velCap increments by a little bit each time through the loop and limits the maximum voltage the motor can receive. This means the robot will accelerate smoothly with a slowly increasing voltage because of the gradual increase in the value of velCap and will decelerate smoothly as the robot approaches the target and the error decreases.
This is a lot of information to take in at once, so be sure to read through the above paragraph and the code a few times and ask any questions below if you’re still confused. ![]()
To tune the loop for your, you will need to sit down with your robot on a practice field and adjust the kp, kpTurn, and acc values. The acc value needs to be tuned for smooth acceleration, and the kp and kpTurn values need to be tuned so that the error value is very close to 0 after the motion is complete. In my worlds code, I also used a constant I value that I had to tune, and that I omitted for simplicity. If you want to use an I or D value as well, obviously that will need to be tuned too.
And that’s it! I won’t have much need for drive PID since I’m graduating this year, but hopefully some of you can benefit from my trials and failures. And if you have any questions, be sure to ask!
Good luck!