Chantilly Academy Robotics Team 612 has developed code that slows the wear on the kit-of-parts planetary gearbox.
The code gradually changes the speed based on the joystick value.
NOTE: in this code, pwm01 is the left side, pwm02 is the right. This also assumes that you are using 2 joystick drive, with the left joystick Port 1 on the Operator Interface, and with the right joystick on Port 2 on the Operator Interface.
Here it is:
/***** Put these lines of code into user_routines.c at the top *****/ #define aRate 3 //This is the rate at which the motor speeds up #define dRate 8 //This is the rate at which the motor slows down
void accelR(int); //method explained below
void accelL(int); //method explained below
/***** This goes with the above
*FUNCTION NAME: accelL
*PURPOSE: to gradually increase or decrease motor output on pwm01
*CALLED FROM: user_routines.c
*ARGUMENTS: num, the input target value for the motor output
*RETURNS: void
*****/
void accelL(int num)
{
static int leftMotorTemp = 127;
if(pwm01 >= 127)
{
if(num > leftMotorTemp)
leftMotorTemp += aRate;
else if(num < leftMotorTemp)
leftMotorTemp -= dRate;
}
else if(pwm01 < 127)
{
if(num > leftMotorTemp)
leftMotorTemp += dRate;
else if(num < leftMotorTemp)
leftMotorTemp -= aRate;
}
if(leftMotorTemp > 255)
leftMotorTemp = 255;
else if(leftMotorTemp < 0)
leftMotorTemp = 0;
pwm01 = leftMotorTemp;
}
/***** This goes with the above
*FUNCTION NAME: accelR
*PURPOSE: to gradually increase or decrease motor output on pwm02
*CALLED FROM: user_routines.c
*ARGUMENTS: num, the input target value for the motor output
*RETURNS: void
*****/
void accelR(int num)
{
static int rightMotorTemp = 127;
if(pwm02 >= 127)
{
if(num > rightMotorTemp)
rightMotorTemp += aRate;
else if(num < rightMotorTemp)
rightMotorTemp -= dRate;
}
else if(pwm02 < 127)
{
if(num > rightMotorTemp)
rightMotorTemp += dRate;
else if(num < rightMotorTemp)
rightMotorTemp -= aRate;
}
if(rightMotorTemp > 255)
rightMotorTemp = 255;
else if(rightMotorTemp < 0)
rightMotorTemp = 0;
pwm02 = rightMotorTemp;
}
/***** Put this into Process_Data_From_Master_uP
accelL(p1_y); Changes left side based on left joystick y-value
accelR(p2_y); Changes right side bases on right joystick y-value
//End of code
What this code does is gradually change the speed. This will do several things:
Make driving easier by decreasing sensitivity
Prevent circuit breaker pops
Decrease gearbox wear
Prevent or slow chain and/or belt wear
Our driver really likes the code, so we will keep it after FIRST sends out the fix.
Sorry about the edit. We have several versions of the same code floating around.
I believe that the problem with the carrier plate will come about from shock loads. Software will not help with that. Properly engineered materials will.
You could also write this as 1 function by passing the joystick & pwm by reference to your acceleration function. Also, there is nothing wrong with modifying the pwm output variable directly, since the actual pwm output value isn’t changed until the Putdata() in Process_Data_From_Master_uP().
What happens if the joystick input is 5, your current pwm output is 8, and it decrements the pwm output by 12? At first glance it looks like your temporary value would go negative. I could be wrong though :ahh:
Thanks for posting this code. I think this is a very simple fix that will be very helpful for teams using the BaneBots gearboxes. With everyone working on materials fixes, it’s easy to forget the simpler ways to alleviate the problem.
Another suggestion: Keep your chains in tension. Any backlash will increase impulsive loading a lot.
Depending on your gearing, you can change the aRate and dRate. 8 and 12 worked well for us. Feel free to change it to suit your needs.
To get more responive braking, put the Victor jumper on “B”, the inner 2 pins. This will cause the robot to actually stop after you tell it to.
About the negative or >255 pwm outputs, you can put a limiter on the temporary pwm. something along the lines of (* is wildcard):
if (pwmtemp > 255)
pwmtemp = 255;
else if (pwm** < 0)
pwm** = 0;
If the bad pwm’s give you a problem, put these lines of code before the pwm assignment, but after the if/else block, replacing th *'s with the approprate number.
Ask, and ye shall receive. Below is the code we’ve been using for 2 years now, you pass in the speed you want, your current speed, and the limit for how much your speed can change by, it takes care of the rest from there.
This goes in a .c file:
/*******************************************************************************
*
* FUNCTION: motor ramping()
*
* PURPOSE: Makes the motor gradually change speed
*
* PARAMETERS: desired_speed, current_speed,
* change_speed //small interval to change the motor speed
*
* RETURNS: mew_motor_speed the small change from the current motor speed
*
* COMMENTS:
*
*******************************************************************************/
unsigned char ramp_motor_speed(unsigned char requested_speed,
unsigned char present_speed,
unsigned char modified_speed)
{
unsigned char new_motor_speed;
// comparing the desired speed to the current speed,
// if they are the same if moves on, if not it goes to the else
if (requested_speed == present_speed)
{
new_motor_speed = requested_speed;
}
else
{
if (requested_speed < present_speed)
{
// if the requested speed is less then the current speed
// it will subtract the desired speed from the current speed
// to get the interval to change the speed
if ((present_speed - requested_speed) < modified_speed)
{
modified_speed = present_speed - requested_speed;
new_motor_speed = present_speed - modified_speed;
}
else
{
new_motor_speed = present_speed - modified_speed;
}
}
else if (requested_speed > present_speed)
{
// if the requested speed is greater then the current speed
// it will subtract the current speed from the desired speed
// to get the interval to change the speed
if ((requested_speed - present_speed) < modified_speed)
{
modified_speed = requested_speed - present_speed;
new_motor_speed = present_speed + modified_speed;
}
else
{
new_motor_speed = present_speed + modified_speed;
}
}
}
return (new_motor_speed);
}
This goes in a .h file:
//example to call function
// current_speed = pwm
// // change the motor speed by the change_speed value
// pwm = ramp_motor_speed(desired_speed, current_speed, drive_interval)
//
//variables for above function
#define DRIVE_INTERVAL 6 //limits drive motors to changing 6 pwm values per loop
unsigned char ramp_motor_speed(unsigned char requested_speed, unsigned char present_speed, unsigned char modified_speed);
On a side note, if you’re using the drive setup 612 is, their code looks easier to setup and use than ours. Ours is designed open ended since we use it for both arm motors and drive motors.
We at MARS have our own acceleration code, written by your’s truly. It’s very simple, and it didn’t take too long to type up. We use different acceleration rates for autonomous mode and human control. Here it is:
//these two variables are declared at the top of user_routines_fast.c
int AUTON_RATE_LIMIT = 1;//wheels accelerate at about 40 per sec (3 secs to max)
int RATE_LIMIT = 5; //faster accel. during manual control
//The function was added in at the bottom of the file.
//Make sure to make a prototype for this in user_routines.h!!
/*******************************************************************************
* FUNCTION NAME: Motor_Accel
* PURPOSE: Motors accelerate at certain rate (prevents wearing of transmission)
* CALLED FROM: user_routines_fast.c
* ARGUMENTS:
* Argument Type IO Description
* -------- ---- -- -----------
* pwm_out int O Value being sent to pwm
* pwm_in int I Pwm that value is being sent to
* RETURNS: int
*******************************************************************************/
int Motor_Accel(int pwm_out, int pwm_in)
{
if (autonomous_mode) //while autonomous is running...
{
printf("Motor_Accel running...
");
if (pwm_out > (pwm_in + AUTON_RATE_LIMIT)) //If the input is more than your pwm, + 1...
return (pwm_in + AUTON_RATE_LIMIT); //Make it your pwm, + 1
else if (pwm_out < (pwm_in - AUTON_RATE_LIMIT)) //If it is less than your pwm, - 1...
return (pwm_in - AUTON_RATE_LIMIT); //Make it your pwm, - 1
else //And if it is within 1 either way (or the same)...
return pwm_out; //Send it as is
} //end autonomous acceleration code
else //While under human control
{
if (pwm_out > (pwm_in + RATE_LIMIT)) //If the input is more than your pwm, + 5...
return (pwm_in + RATE_LIMIT); //Make it your pwm, + 5
else if (pwm_out < (pwm_in - RATE_LIMIT)) //If it is less than your pwm, - 5...
return (pwm_in - RATE_LIMIT); //Make it your pwm, - 5
else //If it is within 5 either way...
return pwm_out; //Send it as is
} //end human control acceleration code
} //end Motor_Accel
And there it is! As I said, very simple. The only complication is having to put the pwm name in twice…
pwm03 = Motor_Accel(p1_y, pwm03);
EDIT: I forgot to mention, this isn’t final. We’re still taking a look at exactly how it will work. We know it works, but we don’t know how well it works…
EDIT (#2): Turning seems to be a problem, since the acceleration rates make it hard to shift from one side of 127 to the other…
2 robots collide head on with each going 6 FPS. The shock and stress to the drive train will happen and software will not protect against this. Too many shocks and some thing fails. The baines bot carrier plate is 1 point of failure. The gears are a concern of mine. Yes, they are hard, but can they endure the stress that some teams inflict on their drive trains. Planetary gear boxes have some pluses. They also need higher strength and precision than the plate and gear sandwich gear boxes like the Andy Mark’s. Our team is playing it safe with the AM solution and will watch the BB story unfold. Good luck to the teams that are going with the BB’s. Train your drivers not to smash into objects at full speed. As a side note, The power tools and equipment that I have at work and has survived my constant abuse for years and years do not have planetary transmissions.
First off, great work 612. Our neighbors down the street keep continuously trying to make life easier for other teams, and helping out FIRST.
This is a great way to try and prevent your own robot from damaging your Banebots transmissions, but, as mentioned, it does not protect from outside forces, so be wary. It may or may not impede impulses enough to prevent damage when using 2 CIMs, but it will certainly mitigate the damage. Basically, a word of caution, this is a great start on saving your transmissions, but it isn’t perfect (no solution will be), and there will still be certain risks imposed upon your transmissions.
We put a low-pass filter on the motor speed command almost every year. A more compact way to accomplish this follows. Note that you can (and I always do) use integers for the fractional part, but it’s quicker to show you what’s going on with floats:
// Put this at the top of "user_routines.c"
#define K_LP .2
// Put these at the top of Default_Routine()
static unsigned char pwm01_old = 127;
// Put these in your drive code
pwm01 = (your drive code here)
pwm01 = (unsigned char)(K_LP*pwm01) + (unsigned char)((1.0-K_LP)*pwm01_old);
pwm01_old = pwm01;
Adjust the “K_LP” parameter to change the response of the filter. Higher numbers make the system more responsive, but with more noise. Lower numbers make the system smoother, but with more lag.
Actually, that’s going to make the motors go full reverse, since you’re handling everything as an 8-bit integer value calculation, anything between 1 and 0 as a decimal defaults to zero, and you’re multiplying both sides of the plus by 0, so you’d end with 0 + 0 = 0.
The concept is right but might I suggest a better execution?
// Put this at the top of "user_routines.c"
#define K_LP 2
unsigned int temp_pwm01;
// Put these in your drive code
pwm01 = (your drive code here)
temp_pwm01 = (unsigned int)((K_LP*pwm01)/10) + (unsigned int)(((10-K_LP)*pwm01_old)/10);
if(temp_pwm01 > 255)
{
temp_pwm01 = 255;
}
pwm01_old = pwm01 = (unsigned char)temp_pwm01;
This scales everything up for the calculations to avoid floating point (MUCH FASTER, and the extra int we used is still less memory than a float), then divides it back down to minimize rounding errors, then checks to make sure its not too large for a char and sets pwm01 to it.
Actually, that’s not true. The literal value .2 in the #define statement is actually of type double. (You could keep it to just type float by specifying .2f) If the compiler follows the ISO/ANSI standard, it will promote the pwm values to double before doing the arithmetic and then be cast back to unsigned char. However, it would probably be better to do the cast only once as in:
You are right that avoiding floating point math completely would be a good idea, but I think there’s a problem with your example.
The epression:
(unsigned int)((K_LP*pwm01)/10)
the part to the right of the cast is still, I believe, going to be evaluated as an unsigned char and then the unsigned int cast applied. This is because the expression in the parentheses is evaluated first. You should write it as:
(unsigned int) K_LP * pwm01 / 10
this will cast K_LP to unsigned int and then the rest of the expression will be promoted to unsigned int and then arithmetic done.
is a bit inefficient if the compiler isn’t smart enough to optimize the division by 10. It would be better to help the compiler and be sure to get efficient code by doing the addition once and then the division.
This is quite true. However, the difference in shock load imposed by an impact by another robot, vs. that imposed by a sudden direction reversal of a motor, seems to me to be about an order of magnitude different.
Let’s say your robot is driving forward, and another robot hits yours head on. There is no direction reversal at the DD joint. Now let’s say your robot is driving forward slowly, and it is hit from behind by another robot. There is a direction reversal at the DD joint, but also the impact at the DD joint is softened considerably by some things. These include the fact that the other robot will share it’s momentum with yours, so that it must accelerate the mass of your robot, and this takes time and absorbs energy. Also the load will be distributed among more than one wheel/transmission, so it is not all concentrated at one point. And there is some give in the chain drive system, assuming you are using a chain drive.
In a sudden motor reversal situation, the inertia of what’s connected to the transmission output shaft is higher than the inertia of the transmission gears/motor armature, and there is also a large torque multiplication and speed reduction supplied by the transmission. From the instant the motor changes direction, it can turn quite a ways at full torque as it takes up the slack in the transmission gears (and DD joint), and it can gain a considerable amount of rotational inertia before it finally slams the DD joint against the other side. From the pictures of damaged plates, it looks to me like this is what is causing the problem.
I really would like to see the results of some driving tests on DD joint wear with software motor acceleration control. I think it might be more helpful than some folks realize.
The more important issue in my mind is how the shock is applied. If the robot sustains a shock to the chassis, that shock is going to be applied pretty much uniformly to the transmission, assuming it’s mounted solidly, shaft supported, etc. As I understand it, the failure that’s being seen is a torsion problem. It’s specifically failing at the joint where the shaft is attached to the planet gear carrier plate. Very much apples and oranges in making any comparisons.
This is almost exactly the code our team has used as well. We try to err on the side of responsiveness, however, so after two competitions all the mechanisms have definitely picked up some slop.