Hey folks,
Our robot curves slightly to the left when we drive full speed straight ahead (we’re using 4WD tank mode. Any suggestions on adjusting wheels, trim, or other things to get a straight driving line?
Thanks!
Hey folks,
Our robot curves slightly to the left when we drive full speed straight ahead (we’re using 4WD tank mode. Any suggestions on adjusting wheels, trim, or other things to get a straight driving line?
Thanks!
Drifting to the side while trying to drive straight has some usual causes:
wheels going slower on one side, due to extra friction on that side
misaligned frame or wheels
less traction on one side, or other issues related with center of gravity
motor scaling trim issues:
Does robot drive straight during autonomous?
in a simple code like that, its most likely the build thats the problem as jgraber said
see if you can take a gear off the wheel and free spin it, it should spin with barley any friction
There is no possible way to make everything perfectly equal on both sides, or even on each wheel of the same side. Even if it is an issue with the trim, you will always have some difference due to the reasons that jgraber posted, and many more. for this reason, instead of driving yourself crazy and getting nowhere, use encoders on your wheels. Put an enoder on either side of the bot. It will save you a lot of struggle, and make for a much more dynamic robot.
I agree… we always use an encoder derived heading psi_deg. It is used in a heading_hold_function(ref). While the manual turn cmd is outside its joystick dead zone, we synchronize the ref to the current heading. When the manual turn cmd is in the dead zone (0), the heading_hold_function(ref) is engaged to hold heading while you drive fwd. Takes care of a lot of sins in the drive system. Below is a code snippet from the Mentor Bobcat RobotC code:
// in main while loop
//pwm_turn , pwm_turn_hh, psi_deg are globals
if(bheading_hold_on && (pwm_turn!=0 )) // If turning then reset heading ref at current heading
{
heading_ref = psi_deg ;
}
heading_hold_function(heading_ref); //outputs pwm_turn_hh command
// in motor routine
motor[lt] = pwm_fwd + pwm_turn + pwm_turn_hh;
motor[rt] = pwm_fwd -(pwm_turn + pwm_turn_hh);
//note: positive pwm cmds always move a motor forward and heading is always positive to the right.
//heading_hold_function() holds heading relative to the input psi_deg_ref
//To hold existing heading ... call function with psi_deg_ref = psi_deg at time of call
void heading_hold_function(int psi_deg_ref)
{
float k_hh = 5. ;//pwm_cmd units per deg of heading error
int hh_dead_band = 1; // creats a +_ deadband (nominally 1 deg)
if(bheading_hold_on)
{
psi_error = (psi_deg_ref - psi_deg);
if(psi_error > hh_dead_band || psi_error < -hh_dead_band)
pwm_turn_hh = k_hh*(float)psi_error;
else
{
pwm_turn_hh = 0 ;
}
}
}
Thanks for the input, everyone. Vamfum, we’re going to study your code and try to get our heads around it - hopefully one day we’ll be able to emulate that level of code sophistication!
We haven’t yet determined if friction or scale issues are playing a part in our robot curving to the left in both manual and autonomous modes. However we wrote some code using encoders that helps to correct the curvature (modeled on the RobotC curriculum). It’s an improvement, but the robot doesn’t quite go in a straight line. Instead it slightly wiggles - curving to the left for a second, then right, then back to left, then right, etc. It’s good enough for now, but I’d like to hear any input y’all might have to improve our code - both to minimize the wiggle, but also just to improve our code writing form.
#include "Main.h"
void DriveStraightUsingShaftEncodersAutonomous ( void )
{
long FrontRightSE;
long FrontLeftSE;
int Ch2;
int Ch3;
unsigned long TimerValue;
// Front right SE; Top Cable to Interrupt 1, Bottom Cable to Digital Input 5
// Front Left SE: Top Cable to Interrupt 2, Bottom Cable to Digital Input 6
// SE has 90 counts per revolution
PresetQuadEncoder ( 1 , 5 , 0) ; // Reset SEs to 0
PresetQuadEncoder ( 2 , 6 , 0) ;
StartQuadEncoder ( 1 , 5 , 1 ) ; // Start SEs; RightFront inverted
StartQuadEncoder ( 2 , 6 , 0 ) ;
TimerValue =0 ; // Resets timer
StartTimer ( 1 ) ;
while ( TimerValue < 10000 ) // Runs loop for 10 seconds
{
TimerValue = GetTimer ( 1 ) ;
FrontRightSE = GetQuadEncoder ( 1 , 5 ) ; // Gets values of SEs for variables
FrontLeftSE = GetQuadEncoder ( 2 , 6 ) ;
if ( FrontRightSE > FrontLeftSE ) // If robot turns left...
{
SetMotor ( 2 , 220 ) ; // ...slow down front right motor
SetMotor ( 3 , 0 ) ;
}
if ( FrontRightSE < FrontLeftSE ) // If robot turns left...
{
SetMotor ( 2 , 255 ) ;
SetMotor ( 3 , 35 ) ; // ...slow down front left motor
}
if ( FrontRightSE == FrontLeftSE ) // If robot is going straight
{
DriveForward100 ( ) ;
}
PrintToScreen ( "Right Front Encoder = %d\n" , (int)FrontRightSE ) ;
PrintToScreen ( "Right Left Encoder = %d\n" , (int)FrontLeftSE ) ;
}
}
even thought encoder pid loops help and everything, remember
it can only SLOW DOWN the faster side (not the other way around)
if you really want straight driving, i really recommend taking apart the slow drive and see whats wrong (it could be too tight, bent axle, misaligned bearing block, ect)
(you may have to dissect it entirely, but it is worth the time investment; one class, maybe two?)
remember that whatever the reason is, you are also wasting valuable energy and battery life just to keep it going
If you would like to have an example PID in RobotC code instead of easyC, let me know, and I’ll post it.
Yes please!
Back to the original topic, if your motors are healthy and there is no excess friction one side of the drive train, your robot will track straight (for some value of “straight” where “straight” means “straight enough for a field only 12 feet wide”). Check everything jgraber and Murdomeek and others suggest before resorting to shaft encoders and software.
That said, for autonomous operation, using shaft encoders and software is your best bet to make sure your robot runs straight, especially if you are using dead reckoning.
Good luck and let us know what happens.
Hi,
What you have is a bang-bang controller which puts maximum correction for even the slightest error. It is ok on average but its only stable point is where the two encoders are equal…a point that is almost impossible to maintain. So you get the wiggle and your motors are always active.
Here are a few steps I would do.
1)check the friction. I use the EasyC on_line utility and add motor command to each installed motor or side separately until it moves. I do this with and without weight on wheels. This is the command required to exceed the static friction. I do this for the right and left and see how much the sides are unbalanced. Typically, an installed motor requires 20 to 40 pwm counts to get moving. The high torq motors maybe half that. Do what you can to lower the pwm counts for the worst side.
2)The next step is to add a dead zone on the encoder error = lt_encoder-rt_encoder. Typically if you drive straight within say 3 in out of 12 ft this is an error of about 1.2 degs. So a dead zone of 1 or 2 degrees would be good enough. When the |encoder error| > Deadz then correct otherwise drive straight. This would be an easy modification to your code.
Edit: However, since you have a mismatch in left and right drive trains, setting the same power to each motor train does not cause a drive straight condition and you are destined to continuous corrections. If you give up a little on the turn correction power, you can minimize the overshoots and stay within the dead zone longer. This will smooth out your correction and take a little longer to get back to driving straight but it is desirable from the standpoint of motor wear. This is always the trade off with control systems… response time /accuracy vs stability.
3)A further improvement is to make the correction proportional to the error. The code I posted is a PID loop without the ID and is usually good enough for heading controllers.
The heading ,psi_deg, is computed from left and right encoder difference:
psi_deg = deg_per_cnt*(encoder_lt-encoder_rt)
The multiplier constant is computed as:
deg_per_cnt =(180/pi)*circum/dist_axle/90cnts
where dist_axle is the spacing between left and right wheels in inches and the circum = wheel circumference in inches.
Edit: with a 16 in wheel base and a 4 in dia wheel, deg_per_cnt = .5 with EasyC. With RobotC, there are 360 ticks per rev since they are using full quadrature so deg_per_cnt = .125 .
The P loop is closed in the Heading_Hold_function(ref) with proportional gain K_hh = 5. This is the standard Kp in PID loops.
pwm_turn and pwm_fwd are outputs of an Arcade function that includes a dead zone. When within the stick turning dead zone, pwm_turn = 0 and heading is held and pwm_fwd is the symmetric command that sets the robot speed.
So there is really nothing fancy, its just encoder difference causing a differential motor command. The only thing I do is use it during manual driving with automatic engagement when the joystick is within it turning deadband.
I will provide the general outline of the RobotC code one would use for simple PID. This is very simplified and not the most advanced way to do this, but in most cases, it will get the job done. This code will be for two wheels, but you can modify it for more if you wish.
I will use these variables:
leftEnc for the encoder on the left wheel.
rightEnc for the encoder on the right wheel.
leftW for the motor on the left wheel.
rightW for the motor on the right wheel.
int encBuffer for the error allowance on the encoders.
When I write PID, I make a function to handle it instead of doing the same thing over and over. So, above your task main(){, write this function.
void pIDMove(int power){
if(power > 0){
if(SensorValue(leftEnc) > (sensorValue(rightEnc) + encBuffer)){
motor[leftW] = 0;
motor[rightW] = power;
}else if(SensorValue(leftEnc) < (sensorValue(rightEnc) - encBuffer)){
motor[leftW] = power;
motor[rightW] = 0;
}
}else if(power < 0){
if(SensorValue(leftEnc) > (sensorValue(rightEnc) + encBuffer)){
motor[leftW] = power;
motor[rightW] = 0;
}else if(SensorValue(leftEnc) < (sensorValue(rightEnc) - encBuffer)){
motor[leftW] = 0;
motor[rightW] = power;
}
}
}
When writing code, you would use this function instead of the motor assignment. for example, instead of
motor[leftW] = 80;
motor[rightW] = 80;
you would write
pIDMove(80);
This assignment does have to be put in a while loop or a timer loop instead of a wait statement though. so instead of
motor[leftW] = 80;
motor[rightW] = 80;
Wait1MSec(4000);
you would write
Time1[T1] = 0;
while(time1[T1] < 4000){
pIDMove(80);
}
This is a little different than the way it is done the RobotC sample code, but it will autocorrect faster. If my code isn’t clear or has errors in it, please let me know.
Other mechanical solutions that might help you: Do you have four non-onmi wheels driven? Try lengthening the wheel base by moving them a little farther out. They’ll resist turning a bit more and generally resist slight deflections to one direction.
Most Vex robots for whatever reason arc a little bit, but not a noticeable amount over 12 feet (something like 1-4 inches)
I notice evryone is saying that the robot should travel straight without encoders, but that’s not the only reason to put them on. They are necesarry in autonomous mode. Even though you may get the robot to travel straight, you cant time how far it goes accuratly. The motor power depends on battery life, traction among other things. There is no way to accuratly send a robot a certain distance without some kind of sensor. Encoders are one option, but ultrasonics, alellerometers, and line sensors can also be used. Encoders are also great for resetting a robot if it gets into a collision.
Where do you calculate the encBuffer? Also could you call this as a task rather than a void or no?
A task allows you to run code simultaneously. You can start a task, but the rest of your code can keep running as well.
Oh ok thanks. Do you have any idea what determines the encBuffer?
This thread is over 4 years old ! Probably not the best place to ask questions on PID.
encBuffer was presumably a constant or variable that was set somewhere else in the code.
int encBuffer for the error allowance on the encoders.
Haven’t seen magicode around here for a while and I doub’t would remember anyway.
tasks and functions (please don’t call them “voids”) are not interchangeable, this function wouldn’t make much sense as a task. void is used to indicate that a function does not return a variable (and other purposes) you can read all about them here.
http://en.wikipedia.org/wiki/Void_type
Oh ok sorry! I just had a question about this specific code so this is where i asked. Thanks for the help.