Hi all,
I currently have a flywheel design and I am running a PID loop on it (without the D). Currently, however, the recovery time is just over 1 second with barely any overshoot (pretty close to no overshoot), and if I try to increase the recovery speed, there is a greater amount of overshoot. Is there a way (both mechanical and software-based) to increase the recovery time without having it overshoot?
If you are talking about overshoot as in the PID function causing the flywheel speed to overachieve its goal RPM by just a little bit, you can lower the cutoff threshold to below the desired speed. For example, our flywheel needs to be at 255 Integrated Motor Encoder ticks per quarter of a second (250ms is the sample rate of our flywheel speed, so roughly 900 ticks a second). The way the code is written, the bot automatically revs the motors up to a flat rate of 127 power until it hits 245 ticks (the cutoff threshold), then it switches over to a PID algorithm designed to precisely micromanage and keep the speed at 255 with little error. We’ve used this method for most of the season, and it’s worked pretty well so far with a reload speed of just under one second.
Are you guys able to make your flywheels work better than the punchers? Are you thinking about changing? My team is using a flywheel and are considering switching, but a flywheel doesn’t rely on elastics (which stretch over time) like the puncher so we’re kind of torn.
Experiment with both honestly. I originally wanted to use elastics, but all my mechanisms failed, and I ran out of time, leaving me stuck with a flywheel since. But I have been able to use the flywheel to its fullest extent minus some special code here and there, and supplanting that with a gamer-child adept at button timing for speed adjusting. Also make sure to document the whole process of designing, building, and testing; it looks good in the notebook.
I like more checks per second. At least 50 but as low as 20ms between every speed check. Maybe tell the program to switch over to a power of 0-40 whenever it is overshooting more than 1 unit. Where is your encoder. Encoder placement is crucial when dealing with fast fire rates.
Actually, initially, we were using the type of system where it boosts the power to 127 and then brings it back down for proportional adjustment, but we switched to a pure PID loop to see if it would simplify the system. I will switch back to the system where the power is boosted to 127 and see it if works better. Thanks!
We are currently using an IME and velocity measurements fluctuate considerable when at a presumed steady-state speed. Do you feel shaft encoders give a more stable reading? If so, would you put them on the motor shaft or another gearing shaft that is turning at a faster RPM? I know connecting to the flywheel shaft doesn’t work well as others have found they have a tendency to burn up at typical flywheel RPMs.
I have attached the data logger output file for our current_velocity variable. The target velocity was set at 114 and the flywheel was ran for about 9 seconds without putting any load on it and data was collected every 20 mSec. We are using the sample code you provided in your Flywheel Velocity Control Revisited thread for velocity calculations as well as the TBH algorithm for velocity control.
Running the data through a standard deviation calculation in Excel gives a deviation of close to 8, which would suggest fluctuations in the range of a little over 7%. No idea if this is “normal” but I am wondering if we would be able to obtain a less jumpy velocity reading using a shaft encoder versus the IME?
When I tried shaft encoders on our flywheels before I realized that the reason our IEMs weren’t working was because they were missing the special gear, it was pretty terrible. Essentially, besides the fact that the shaft encoder is big and bulky, each one we tried would stop counting every so often and just stick to a certain value, maybe plus or minus one. You might try implementing some kind of average in your TBH controller to use an average of the last x RPM values, but I would stick with the IEMs - those have worked very well for us.
That seems a lot more than I’ve experienced in the past. I’ve had no time to play with this for weeks, I will try and find time to repeat some of these experiments soon.
Are you running this measurement in a separate task? Is that task a higher priority than your driver control task? Are you allowing the RTOS scheduler to run? (ie. using wait1Msec or abortTImeSlice in the loops)
We are not familiar with the RTOS scheduler… we do have a wait command at the end of the velocity control program. Below is a copy of our code… please let me know if you see any spots that could be tweaked to improve the stability of the calculated velocity.
/*Set the flywheen motors RIGHT */
void FwMotorSetR( int valueR )
{
motor Motor_FWR ] = valueR;
}
/*Set the flywheen motors LEFT */
void FwMotorSetL( int valueL )
{
motor Motor_FWL ] = valueL;
}
/*Get the flywheen motor encoder count RIGHT*/
long FwMotorEncoderGetR()
{
return( nMotorEncoder Motor_FWR ] );
}
/*Get the flywheen motor encoder count LEFT */
long FwMotorEncoderGetL()
{
return( -(nMotorEncoder Motor_FWL ]) );
}
/*Set the controller position RIGHT */
void FwVelocitySetR( int velocityR, float predicted_driveR )
{
// set target_velocity velocity (motor rpm)
target_velocityR = velocityR;
// Set error so zero crossing is correctly detected
current_errorR = target_velocityR - motor_velocityR;
last_errorR = current_errorR;
// Set predicted open loop drive value
drive_approxR = predicted_driveR;
// Set flag to detect first zero crossing
first_crossR = 1;
// clear tbh variable
drive_at_zeroR = 0;
}
/*Set the controller position LEFT */
void FwVelocitySetL( int velocityL, float predicted_driveL )
{
// set target_velocity velocity (motor rpm)
target_velocityL = velocityL;
// Set error so zero crossing is correctly detected
current_errorL = target_velocityL - motor_velocityL;
last_errorL = current_errorL;
// Set predicted open loop drive value
drive_approxL = predicted_driveL;
// Set flag to detect first zero crossing
first_crossL = 1;
// clear tbh variable
drive_at_zeroL = 0;
}
/*Calculate the current flywheel motor velocity*/
void FwCalculateSpeed()
{
int delta_msR;
int delta_msL;
int delta_encR;
int delta_encL;
// Get current encoder value
encoder_countsR = FwMotorEncoderGetR();
encoder_countsL = FwMotorEncoderGetL();
// This is just used so we don't need to know how often we are called
// how many mS since we were last here
delta_msR = nSysTime - nSysTime_lastR;
nSysTime_lastR = nSysTime;
delta_msL = nSysTime - nSysTime_lastL;
nSysTime_lastL = nSysTime;
// Change in encoder count
delta_encR = (encoder_countsR - encoder_counts_lastR);
delta_encL = (encoder_countsL - encoder_counts_lastL);
// save last position
encoder_counts_lastR = encoder_countsR;
encoder_counts_lastL = encoder_countsL;
// Calculate velocity in rpm
motor_velocityR = (1000.0 / delta_msR) * delta_encR * 60.0 / ticks_per_rev;
motor_velocityL = (1000.0 / delta_msL) * delta_encL * 60.0 / ticks_per_rev;
datalogAddValueWithTimeStamp(0,motor_velocityL);
}
/*Update the velocity tbh controller variables RIGHT*/
void FwControlUpdateVelocityTbhR()
{
// calculate error in velocity
// target_velocity is desired velocity
// current is measured velocity
current_errorR = target_velocityR - motor_velocityR;
// Calculate new control value
driveR = driveR + (current_errorR * gain);
// Clip to the range 0 - 1.
// We are only going forwards
if( driveR > 1 )
driveR = 1;
if( driveR < 0 )
driveR = 0;
// Check for zero crossing
if( sgn(current_errorR) != sgn(last_errorR) ) {
// First zero crossing after a new set velocity command
if( first_crossR ) {
// Set drive to the open loop approximation
driveR = drive_approxR;
first_crossR = 0;
}
else
driveR = 0.5 * ( driveR + drive_at_zeroR );
// Save this drive value in the "tbh" variable
drive_at_zeroR = driveR;
}
// Save last error
last_errorR = current_errorR;
}
/*Update the velocity tbh controller variables */
void FwControlUpdateVelocityTbhL()
{
// calculate error in velocity
// target_velocity is desired velocity
// current is measured velocity
current_errorL = target_velocityL - motor_velocityL;
// Calculate new control value
driveL = driveL + (current_errorL * gain);
// Clip to the range 0 - 1.
// We are only going forwards
if( driveL > 1 )
driveL = 1;
if( driveL < 0 )
driveL = 0;
// Check for zero crossing
if( sgn(current_errorL) != sgn(last_errorL) ) {
// First zero crossing after a new set velocity command
if( first_crossL ) {
// Set drive to the open loop approximation
driveL = drive_approxL;
first_crossL = 0;
}
else
driveL = 0.5 * ( driveL + drive_at_zeroL );
// Save this drive value in the "tbh" variable
drive_at_zeroL = driveL;
}
// Save last error
last_errorL = current_errorL;
}
/*Task to control the velocity of the flywheel */
task FwControlTask()
{
// Set the gain
gain = 0.001;
// We are using Speed geared motors
// Set the encoder ticks per revolution
ticks_per_rev = MOTOR_TPR_393S;
while(1)
{
// Calculate velocity
FwCalculateSpeed();
// Do the velocity TBH calculations
FwControlUpdateVelocityTbhR() ;
FwControlUpdateVelocityTbhL() ;
// Scale drive into the range the motors need
motor_driveR = (driveR * FW_MAX_POWER) + 0.5;
motor_driveL = (driveL * FW_MAX_POWER) + 0.5;
// Final Limit of motor values - don't really need this
if( motor_driveR > 127 ) motor_driveR = 127;
if( motor_driveR < -127 ) motor_driveR = -127;
if( motor_driveL > 127 ) motor_driveL = 127;
if( motor_driveL < -127 ) motor_driveL = -127;
// and finally set the motor control value
FwMotorSetR( motor_driveR );
FwMotorSetL( motor_driveR );
// Run at somewhere between 20 and 50mS
wait1Msec( FW_LOOP_SPEED );
}
}
task usercontrol()
{
// Start the flywheel control task
startTask( FwControlTask );
// Main user control loop
while(1)
{
//Tank control
tankControl( Ch3, Ch2, 10 );
//Hook control
if(vexRT[Btn6DXmtr2] == 1)
{
motor[Hook] = 100;
}
else if(vexRT[Btn6UXmtr2] == 1)
{
motor[Hook] = -40;
}
else
{
motor[Hook] = 0;
}
/////////////
// Different speeds set by buttons
if( vexRT Btn8UXmtr2 ] == 1 )
{
FwVelocitySetR( 115, 0.56 );
FwVelocitySetL( 115, 0.56 );
}
if( vexRT Btn8LXmtr2 ] == 1 )
{
FwVelocitySetR( 112, 0.53 );
FwVelocitySetL( 112, 0.53 );
}
if( vexRT Btn8DXmtr2 ] == 1 )
{
FwVelocitySetR( 100, 0.48 );
FwVelocitySetL( 100, 0.48 );
}
if( vexRT Btn8RXmtr2 ] == 1 )
{
FwVelocitySetR( 00, 0 );
FwVelocitySetL( 00, 0 );
}
if( vexRT Btn7UXmtr2 ] == 1 )
motor[Intake] = 55;
else if( vexRT Btn7DXmtr2 ] == 1 )
motor[Intake] = -127;
else
motor[Intake] = 0;
// Don't hog the cpu :)
wait1Msec(20);
}
}
You have a wait in the while loop for each task, that’s good.
I will explain some potential improvements (I need to do a couple of tests first to see if theory and practice match up) in another thread. Do you just have two IMEs? It’s always useful to have the “#pragma config” statements to go with posted code, it stops guessing where the motors are connected etc.