Log in

View Full Version : High speed encoder with slotted / flat end... can't find one.


Tom Line
21-05-2012, 01:03
We've got fairly tight requirements. In order to improve our shooter, we're increasing the number of counts our encoder returns.

Right now we have a bourns 128 count with 1/4 slotted shaft input.

What we're looking for is a 512 count with 1/4 slotted shaft input at reasonable cost.

With 128 counts per revolution, we see around 7000 counts per second. 512 gets up to 28,000 or so per second. My understanding is that 40,000 per second is the cRio's limit, so I'm happy getting to 3/4 of it. Also note that the encoder will have to be fairly robust to handle the speeds (3000 rpm+) that our shooter runs.

We've looked but can't find something in stock that meets these requirements. Does anyone else have an off-the-shelf idea?

I understand we could gear the encoder we have now upwards to get the appropriate counts, but we direct drive the encoder to minimize noise and would like to stay that way. We don't have the ability for a drop-over or code wheel style encoder due to mounting real-estate.

AdamHeard
21-05-2012, 01:07
Does it have to have a slotted or flat end?

If it is okay, I've used both the S1 (http://www.usdigital.com/products/encoders/incremental/rotary/shaft/s1) and S5 (http://www.usdigital.com/products/encoders/incremental/rotary/shaft/s5) encoders before from us digital.

Your method of calculating and/or filtering velocity could possibly have a much larger impact on control (we ran a 6-tick encoder all season), curious, what are you currently doing? I certainly agree with your logic that more resolution is better though (we're switching to a higher count encoder at IRI for the same reason).

Cory
21-05-2012, 01:25
We are using the S5 encoder at 32 CPR. It has worked fine all year for us. Only problem we had was when testing different wheels/coatings we ended up destroying one after inserting/pulling it out of a tight hole in the shooter shaft about 25 times, which is pretty understandable.

Tom Line
21-05-2012, 02:35
we have our shooter logic in a 10 ms timed loop (not a waited loop).

We calculate the rate based on 3 loops worth of data. Each loop we drop the oldest data point and add a new one.

Then we average those 3 loops of data and send that into our velocity PID as our actual speed.

(I suppose, looking at this now, we really should just be looking at the rate over 9 loops or 90ms, and get rid of the average. It's a bit redundent.

For our 'enable to shoot' logic, we use an 8 sample average of rate fed into an IIR filter with a .5 constant.

Please, feel free to tell us what we can improve =). I'm an mechanical engineer masquerading as a controls engineer, and systems engineering was always one of my most hated classes :lol:

Mark McLeod
21-05-2012, 08:27
Are you using 4x sampling to get the max from your Bourns?
The quoted CPR is based on using only one of the following:1) rising edge A
2) falling edge A
3) rising edge B
4) falling edge B
Counting each and every one of them gives you 512 ticks per revolution with a "128cpr" encoder.

Brandon Holley
21-05-2012, 09:33
We're using a 50CPR US Digital S5 encoder. We switched over to this from the S4's based on recommendation from our friends on the Poofs. We are extremely happy with the S5 (particularly the ball bearing version), and will only be using that encoder for applications similar to our shooter wheel in the future.

-Brando

Ether
21-05-2012, 09:53
If you use 4x sampling and you're not using C++, be aware of this:

http://www.chiefdelphi.com/forums/showthread.php?p=1122730

Alan Anderson
21-05-2012, 09:54
Are you using 4x sampling to get the max from your Bourns?
Counting each and every one of them gives you 512 ticks per revolution with a "128cpr" encoder.

Depending on how much processing/filtering is performed on the encoder results, using anything except the 1x mode could introduce jitter. Picking one transition and sticking with it gives the "cleanest" measurement.

Ether
21-05-2012, 10:27
Given a 128 CPR encoder running at approx 3280 rpm (per post#1), there will be approx 70 counts every 10 ms.

If a 10ms timed loop is used and the rate is computed by reading the raw sample count and dividing by sample time, a 1-count jitter will equal approx 1/70 of 3280, or 47rpm jitter.

Using this processing method and decoding at 4x instead of 1x should reduce the jitter, although perhaps not by a factor of 4 (because of the tolerances of the edge locations).

artdutra04
21-05-2012, 10:55
What kind of ± resolution on the RPM are you looking to achieve? And at what frequency do you want to know the current RPM of the shooter?

I would suggest creating an Excel document to calculate how various changes to the CPR and sampling time interval affect the resolution of the shooter speed output. Depending on what you are looking for, you may be able to achieve your desired results without hardware changes.

For example, with a 128 CPR encoder (and 1x decoding) sampling over a 100ms time interval, you can achieve an RPM resolution of 4.6875rpm per encoder count, while sampling over only a 30ms window yields a resolution of 15.625rpm per encoder count.

Brian Selle
21-05-2012, 11:22
We used a US Digital S5 250CPR, worked great.

-Brian


Qty Mfr Part Number Price Description Usage
1 US Digital S5-250-250-NE-S-B $70.65 S5 series, 250 CPR, 1/4" Shaft, No Index, Single Ended, Ball Bearing Shooter RPM

Tom Line
21-05-2012, 11:56
What kind of ± resolution on the RPM are you looking to achieve? And at what frequency do you want to know the current RPM of the shooter?

I would suggest creating an Excel document to calculate how various changes to the CPR and sampling time interval affect the resolution of the shooter speed output. Depending on what you are looking for, you may be able to achieve your desired results without hardware changes.

For example, with a 128 CPR encoder (and 1x decoding) sampling over a 100ms time interval, you can achieve an RPM resolution of 4.6875rpm per encoder count, while sampling over only a 30ms window yields a resolution of 15.625rpm per encoder count.

Art, excellent questions. Obviously, I'd like the tightest RPM control I can get. Working backwards, I'd like to be no more than +/-6 inches in height, and I still suspect that's more than many top teams are aiming for. That means my shooter velocity can vary from 35 ft/s to 34.3 ft/s.

That means my rpm may vary from about 2674 to 2619 (single wheel)

In retrospect, that means that the 30ms time frame with the 128 encoder we'd been trying to use takes up a huge chunk (30%+) of our tolerance. 5% seems like a more reasonable number. Which means that a 512 count encoder should get us fairly close to having a measurement error of 5% over 30ms.

Obviously I'm trying to minimize calculation (delay) time. The fewer cycles required to get an accurate reading means that much less 'lag' between a change in the system and your response to that change. Where is the sweet spot, based on shooter momentum and motor update rate? Heck if I know...

JamesTerm
21-05-2012, 12:06
we have our shooter logic in a 10 ms timed loop (not a waited loop).

We calculate the rate based on 3 loops worth of data. Each loop we drop the oldest data point and add a new one.


10ms timed loop? is that because of vision tracking? Why does it need to be timed?



For our 'enable to shoot' logic, we use an 8 sample average of rate fed into an IIR filter with a .5 constant.

Please, feel free to tell us what we can improve =). I'm an mechanical engineer masquerading as a controls engineer, and systems engineering was always one of my most hated classes :lol:


First thing I would like to mention from a previous time is that we had a problem with our encoder where the vibrations of the shooter caused the encoder itself to rupture the casing and then caused it to lose counts at higher speeds as the centripital force of the encoder wheel lost contact during certain phase intervals. We had to tape the outer casing down tight to keep this problem to a minimum, and then needed to use a priority averager to erase this symtom. The priority averager looks like this:

class Priority_Averager
{
private:
std::priority_queue<double> m_queue;
const size_t m_SampleSize;
const double m_PurgePercent;

double m_CurrentBadApple_Percentage;
size_t m_Iteration_Counter;
void flush()
{
while (!m_queue.empty())
m_queue.pop();
}
public:
Priority_Averager(size_t SampleSize, double PurgePercent) : m_SampleSize(SampleSize),m_PurgePercent(PurgePerce nt),
m_CurrentBadApple_Percentage(0.0),m_Iteration_Coun ter(0)
{
}
double operator()(double newItem)
{
m_queue.push(newItem);
double ret=m_queue.top();
if (m_queue.size()>m_SampleSize)
m_queue.pop();
//Now to manage when to purge the bad apples
m_Iteration_Counter++;
if ((m_Iteration_Counter % m_SampleSize)==0)
{
m_CurrentBadApple_Percentage+=m_PurgePercent;
//printf(" p=%.2f ",m_CurrentBadApple_Percentage);
if (m_CurrentBadApple_Percentage >= 1.0)
{
//Time to purge all the bad apples
flush();
m_queue.push(ret); //put one good apple back in to start the cycle over
m_CurrentBadApple_Percentage-=1.0;
//printf(" p=%.2f ",m_CurrentBadApple_Percentage);
}
}
return ret;
}
};



This got rid of that symptom but we still had the typical noise so we used a kalman filter:

void KalmanFilter::Reset()
{
m_FirstRun=true;
//initial values for the kalman filter
m_x_est_last = 0.0;
m_last = 0.0;
}

KalmanFilter::KalmanFilter(): m_Q(0.022),m_R(0.617) //setup Q and R as the noise in the system
{
}

double KalmanFilter::operator()(double input)
{
//For first run set the last value to the measured value
if (m_FirstRun)
{
m_x_est_last=input;
m_FirstRun=false;
}
//do a prediction
double x_temp_est = m_x_est_last;
double P_temp = m_last + m_Q;
//calculate the Kalman gain
double K = P_temp * (1.0/(P_temp + m_R));
//the 'noisy' value we measured
double z_measured = input;
//correct
double x_est = x_temp_est + K * (z_measured - x_temp_est);
double P = (1- K) * P_temp;

//update our last's
m_last = P;
m_x_est_last = x_est;

//Test for NAN
if ((!(m_x_est_last>0.0)) && (!(m_x_est_last<0.0)))
m_x_est_last=0;

return x_est;
}




And the typical averager

// A templated averager, make sure the type being averaged can handle the +, -, and / functions
template<class T, unsigned NUMELEMENTS>
class Averager
{
public:
Averager() : m_array(NULL), m_currIndex((unsigned)-1)
{
if (NUMELEMENTS > 1)
m_array = new T[NUMELEMENTS];
}
virtual ~Averager() {if (m_array) delete[] m_array;}
T GetAverage(T newItem)
{
if (!m_array) // We are not really using the Averager
return newItem;

// If the first time called, set up the array and use this value
if (m_currIndex == -1)
{
m_array[0] = newItem;
m_currIndex = -2;
m_sum = newItem;
return newItem;
}
else if (m_currIndex < -1)
{
// We have not populated the array for the first time yet, still populating
m_sum += newItem;
int arrayIndex = (m_currIndex*-1)-1;
m_array[arrayIndex] = newItem;

// Still counting backwards unless we have filled all of the array
if (arrayIndex == (NUMELEMENTS-1)) // This means we have filled the array
m_currIndex = 0; // Start taking from the array next time
else
--m_currIndex;

// Return the average based on what we have counted so far
return (m_sum / (arrayIndex+1));
}
else // 0 or greater, we have filled the array
{
m_sum += newItem;
m_sum -= m_array[m_currIndex];
m_array[m_currIndex] = newItem;
++m_currIndex;
if (m_currIndex == NUMELEMENTS)
m_currIndex = 0;
return (m_sum / NUMELEMENTS);
}
}

void Reset(){m_currIndex=-1;}

private:
T* m_array;
int m_currIndex;
T m_sum;
};



With the three averagers working together our encoder readings look like this:

http://www.termstech.com/files/test400_700.bmp

Where the cyan line is the encoder reading trying to match the magenta ramp velocity. Hope these ideas may help out.

Ether
21-05-2012, 12:51
the cyan line is the encoder reading trying to match the magenta ramp velocity.

What's the time scale on the X axis? Looks like a lot of phase lag.

JamesTerm
21-05-2012, 13:32
What's the time scale on the X axis? Looks like a lot of phase lag.



Each pixel represents a 10ms iteration... for completion, the green is the voltage applied while the yellow is the voltage scaler from PID (or actually PD). Notice how quickly the voltage peaks to full voltage... then drops when the encoder reading got too high... I cannot explain why it is trying to calibrate at a fixed offset below the desired velocity, but fortunately any issue of that would not be contributed to the averagers. I suspect that issue is due to the tolerance size (I'll need to review the code).

Ether
21-05-2012, 14:08
It doesn't look like it's holding the final speed very well (see portion circled in red). Do you have a screenshot with a longer time axis showing if it settles out?

JamesTerm
21-05-2012, 14:19
It doesn't look like it's holding the final speed very well (see portion circled in red). Do you have a screenshot with a longer time axis showing if it settles out?



Here same exact run... I fixed a bug in the graph program that allows it to process multiple bitmaps when the dump was too long so we can see most of it this time.

http://www.termstech.com/files/Test700_400.bmp

Ether
21-05-2012, 14:55
http://www.termstech.com/files/Test700_400.bmp

Hmm. Looks like 10 pixels (p-p) out of 229 pixels (full scale) = 4.4% p-p.

Tom is looking for 2% p-p (2674 to 2619).

JamesTerm
21-05-2012, 15:42
Hmm. Looks like 10 pixels (p-p) out of 229 pixels (full scale) = 4.4% p-p.
Tom is looking for 2% p-p (2674 to 2619).



The 4.4 is due to poor PID tuning and other mechanical issues with the shooter and should have nothing to do with the averagers giving the encoders a better reading... I could probably get 2.2 if I spent more time tuning the PID... which I did not have during the competition. I really need to find and post what the encoder readings looked like before these averagers were applied... unfortunately I do not have them on this machine.

Ether
21-05-2012, 16:46
I really need to find and post what the encoder readings looked like before these averagers were applied... unfortunately I do not have them on this machine.

The comparison you really want is your present filtered solution versus the true speed (as measured with a high quality calibrated speed sensor).

JamesTerm
21-05-2012, 17:06
The comparison you really want is your present filtered solution versus the true speed (as measured with a high quality calibrated speed sensor).




Ok I found the other machine that has a sample of the encoder in its raw state... this is the best I have to show at this time... that should illustrate what the averagers fixed.

http://www.termstech.com/files/BadEncoder.bmp

Ether
21-05-2012, 17:56
http://www.termstech.com/files/BadEncoder.bmp

Red circle: what is your controller trying to do here?

Blue circle: how are you reading the encoder? Are you using WPILib's GetRate, or are you getting raw counts and dividing by the elapsed time since the last sample was taken? If the latter, are you using actual elapsed time (as measured with a microsecond timer) or are you just using the scheduled period of your sampler? If the latter, did you take any measures (like giving it a higher priority) to reduce jitter?

JamesTerm
21-05-2012, 18:23
Red circle: what is your controller trying to do here?
Blue circle: how are you reading the encoder? Are you using WPILib's GetRate, or are you getting raw counts and dividing by the elapsed time since the last sample was taken? If the latter, are you using actual elapsed time (as measured with a microsecond timer) or are you just using the scheduled period of your sampler? If the latter, did you take any measures (like giving it a higher priority) to reduce jitter?




I tried both GetRate() preferred, and then the raw counts against a microsecond timer... I believe this one was the GetRate() and both tests look identical (as I did some side-by-side of both method dumps)... where GetRate() did just slightly improve... but not really significant. At slower speeds this noise was not apparent.

A part of me thinks this may be a unique problem that this could be a defected encoder... but I can only speculate, and therefore I think it is good to reveal this to the group in case others may have seen something similar.

AdamHeard
21-05-2012, 18:49
Tom,

It sounds like now you're doing a 9 cycle moving average filter now, we found that going to a low pass filter with a gusstimated gain was superior. We had several other performance issues at that time so I can't say for sure that a moving average is poor for flywheel control, but my controls classes would make me think a low pass is far better.

We also found that either running in constant time, or compensating for time in all controls calcs increased stability.

"Linearizing" the victors helped us a lot too.

These three rather simple changes gave us the biggest bang for our buck, and could be implemented/tested with little time/investment.

A complete revamp of the control to any number of things would also work, but I aim for the lower hanging fruit.

AdamHeard
21-05-2012, 18:53
Could you also give us more info on your pid loop?

I see you're commanding velocity with pid, are you doing any other math or tricks? Feed forward? Are using p, i and d?

There is a lot of potential for poor velocity measurement/filtering, especially when acceleration is calculated for the d term, to cause jitter along the lines of what ether already said.

apalrd
21-05-2012, 22:03
All,

We managed to get quite good speed control (1% from the key, slightly more variance at the lower speed fender) using a 4-line code wheel and Banner sensor. We setup the encoder to average 12 samples and used the rate functions of the WPI library (in LabVIEW). We ran the controller to a certain speed and verified it to within a few rpm with a tachometer, the tachometer readings corresponded quite well to the rpm speed measurements in software.

We used a feed-forward term as the primary element in the control, with PI (or, it could be thought of as PD with the output integrated) handling the variance and spin up. We had a cap on the max integral value which was limited linearly by the error up to ~300rpm (from memory), at which point the integral would be cut off completely. The controller ran in a 10ms Timed Task at high priority.

The feed forward term was calculated by measuring the speed at 25 throttle points, graphing it, and calculating a best fit curve in Excel, although the precision required on some of the terms is high - We didn't get anywhere close to accurate results until we took some of the constants to 10 or more significant digits. If I were to do it again I might come up with a solution that also considered battery voltage, but the I term can deal with battery voltage issues well enough down to around 11v or so, and we don't shoot while moving, so the battery is never that low when shooting.

We attempted a controller without a feed-forward term, using PID only, and the results were less than ideal. Since the I term has to wind up to keep a constant velocity, the gain has to be high enough for it to constantly oscillate slightly and never really settle.

Tom Line
21-05-2012, 22:20
Could you also give us more info on your pid loop?

I see you're commanding velocity with pid, are you doing any other math or tricks? Feed forward? Are using p, i and d?

There is a lot of potential for poor velocity measurement/filtering, especially when acceleration is calculated for the d term, to cause jitter along the lines of what ether already said.

We're using a PID velocity .vi, in stable time (a timed loop at 10ms). This is not labview's positional PID, it's a true velocity PID that the kids wrote. You can see it at our website - www.fightingpi.org under the resources->controls section in labview.

We use P ( essentially I in a positional PID) and D to damp the overshoot. We have a limiter on the P windup (I believe it's 1 and -1).

You can also see the code on page 22 in this paper:
http://www.chiefdelphi.com/media/papers/2695

Ether
21-05-2012, 22:21
We didn't get anywhere close to accurate results until we took some of the constants to 10 or more significant digits.

Hmm. Red flags here. If your model is so sensitive that it requires 10 digits of precision for the constants, then the model probably needs to be changed. I'm guessing you used a high-degree polynomial?

Would you mind posting the 25 data points? I'd like to play around with it.

JamesTerm
21-05-2012, 22:53
All,
The feed forward term was calculated by measuring the speed at 25 throttle points, graphing it, and calculating a best fit curve in Excel, although the precision required on some of the terms is high - We didn't get anywhere close to accurate results until we took some of the constants to 10 or more significant digits. If I were to do it again I might come up with a solution that also considered battery voltage, but the I term can deal with battery voltage issues well enough down to around 11v or so, and we don't shoot while moving, so the battery is never that low when shooting.


This is very interesting... it would be cool to see the equations used here.... Thanks.

JamesTerm
21-05-2012, 23:01
Red circle: what is your controller trying to do here?



Looks like I missed that question... as you see the voltage is pretty constant when that dip occurs... I believe this is when the encoder started to bust out of its casing and loose its counts. This symptom is hard to put into words, but we could see the tiny encoder wheel rolling (very slightly and rapidly) around inside the casing, before we fixed it with the tape.

apalrd
22-05-2012, 01:19
Hmm. Red flags here. If your model is so sensitive that it requires 10 digits of precision for the constants, then the model probably needs to be changed. I'm guessing you used a high-degree polynomial?

Would you mind posting the 25 data points? I'd like to play around with it.




The equation is 4th order, we tried 2nd also. I don't have the data, for some reason it's on the desktop of one of the team laptops and not with the code, I probably can't get it soon.

The resulting equation is in the LabVIEW code I posted to CD-Media. The constants are:
-4th order = 3.038921E-10
-3rd order = -1.8893034681E-6
-2nd order = 0.003990997414888
-1st order = -2.710486371347
There is no intercept, the equation runs through the origin. The input is the desired speed in RPM, the output is a motor power which is divided by 1000 to produce a pct (0-1 range) number. This equation is obviously only valid with this particular mechanical system. The second order function was similar, but the R/R^2 values weren't quite as good as the 4th order function, so we used the 4th order function. The 3rd order function did not follow the curve very well, and was overall not as good as either the 2nd or 4th order functions.

Initially, we scaled the input to 0-1 range, and used the Excel default of two decimal places. The accuracy of this was nowhere near enough, so we scaled the input by 1000 and increased the precision to include many more decimal places (as Excel rounds by decimal places, not significant digits). Looking back, we probably would be OK with as few as 4 digits, but 2 is definitely not enough.

Ether
22-05-2012, 09:11
The constants are:
-4th order = 3.038921E-10
-3rd order = -1.8893034681E-6
-2nd order = 0.003990997414888
-1st order = -2.710486371347

I plotted:

3.038921E-10*X4 - 1.8893034681E-6*X3 + 0.003990997414888*X2 - 2.710486371347*X

... and got this graph (http://www.chiefdelphi.com/forums/attachment.php?attachmentid=12771&stc=1&d=1337691923). Are you using just the rightmost part of the graph?


Try the following model instead of a polynomial:

Y = p1 + p2/(X-p3)

You can do that in Excel as explained here (http://www.chiefdelphi.com/forums/showpost.php?p=1169523&postcount=10).

JamesTerm
22-05-2012, 10:05
All,
The feed forward term was calculated by measuring the speed at 25 throttle points, graphing it, and calculating a best fit curve in Excel

Can you elaborate on this procedure? The thing I'm really interested in here is when you pick a throttle point... how long do you hold it in that position to get a reading. For us the shooter wheel took some time to gain momentum given a constant voltage, where the more voltage... the more time it took for the momentum to take effect.

Ether
22-05-2012, 10:29
Can you elaborate on this procedure? The thing I'm really interested in here is when you pick a throttle point... how long do you hold it in that position to get a reading. For us the shooter wheel took some time to gain momentum given a constant voltage, where the more voltage... the more time it took for the momentum to take effect.

To get the data for a feedforward term, you want the steady-state open-loop value. So you hold the voltage until the speed stops changing, then record your data point. You do this open-loop.

The purpose of the feedforward is to anticipate the required steady-state value required. Then you add your closed-loop terms to make the dynamic response acceptable, and to compensate for load and supply voltage variations that were not present when you recorded your feedforward data.

JamesTerm
22-05-2012, 10:56
To get the data for a feedforward term, you want the steady-state open-loop value. So you hold the voltage until the speed stops changing, then record your data point. You do this open-loop.



Thanks Ether that really explains it...


The purpose of the feedforward is to anticipate the required steady-state value required. Then you add your closed-loop terms to make the dynamic response acceptable, and to compensate for load and supply voltage variations that were not present when you recorded your feedforward data.



Ok if I read this correctly... for fixed point type of rotary systems like a turret or arm (logomotion). You would linearize the victor of the motors while they are still free moving, before you restrict its movement to its fixed point function. Does that sound right?

(Once I understand this I'm going to finally get the arm working) :)

Ether
22-05-2012, 11:20
You would linearize the victor of the motors while they are still free moving, before you restrict its movement to its fixed point function. Does that sound right?

I don't know. I'm having trouble understanding your sentence.

JamesTerm
22-05-2012, 11:23
I don't know. I'm having trouble understanding your sentence.



Ok how about this... how would obtain throttle points from say an arm or turret?

Ether
22-05-2012, 11:40
Ok how about this... how would obtain throttle points from say an arm or turret?

Position control (like for a turret) and speed control (like for a shooter wheel, what we have been discussing here) are different problems.

If you are trying to control the position of a turret, just use the position PID provided in the WPILib. Feedforward is not appropriate for a position controller where the load is essentially zero at the target position.

Jared Russell
22-05-2012, 12:57
Tom,

It sounds like now you're doing a 9 cycle moving average filter now, we found that going to a low pass filter with a gusstimated gain was superior.


A moving average filter is a low-pass filter. I think what you are saying is that you found an IIR (infinite impulse response) low-pass filter performed better than an FIR (finite impulse response) low-pass filter in your application.

Add our team to the list of those that found using a low count per revolution solution (in our case, two pieces of reflective tape and a 2011 KOP line sensor) and using the built-in getRate() function based on timing consecutive edges performed well (less than 1% p-p variation with a simple low-pass filter). Our design methodology was that if we are only running PID at 200Hz (using a Jaguar), then we want a maximally stable speed measurement every 5 milliseconds. At the speeds our shooter was running at, taking two speed measurements per wheel revolution is very close to this ballpark.

Tom Line
22-05-2012, 19:02
A moving average filter is a low-pass filter. I think what you are saying is that you found an IIR (infinite impulse response) low-pass filter performed better than an FIR (finite impulse response) low-pass filter in your application.

Add our team to the list of those that found using a low count per revolution solution (in our case, two pieces of reflective tape and a 2011 KOP line sensor) and using the built-in getRate() function based on timing consecutive edges performed well (less than 1% p-p variation with a simple low-pass filter). Our design methodology was that if we are only running PID at 200Hz (using a Jaguar), then we want a maximally stable speed measurement every 5 milliseconds. At the speeds our shooter was running at, taking two speed measurements per wheel revolution is very close to this ballpark.

If you're running your PID at 200 HZ, or once every 5 milliseconds, then I understand why you'd want a maximally stable speed measurement every 5 milliseconds.

2 counts per revolution, at a speed of 4000 rpm, nets you 66.67 rps. That's 66.67*2= 133.34 counts per second. That's .6667 counts every 5 ms. So how many loops were accumulating? It seems like 75 - 100 ms would be reasonable to reduce the error of being part-way between counts (+/- 1.5% at 100 ms). So 15-20 loops worth of counts?

Something I didn't mention is that we are using a low-pass filter of sorts. We use the labview coerce function and set a max and minimum that limits the amount of change from reading to reading.

JamesTerm
22-05-2012, 19:54
If you're running your PID at 200 HZ, or once every 5 milliseconds, then I understand why you'd want a maximally stable speed measurement every 5 milliseconds.

2 counts per revolution, at a speed of 4000 rpm, nets you 66.67 rps. That's 66.67*2= 133.34 counts per second. That's .6667 counts every 5 ms. So how many loops were accumulating? It seems like 75 - 100 ms would be reasonable to reduce the error of being part-way between counts (+/- 1.5% at 100 ms). So 15-20 loops worth of counts?

Something I didn't mention is that we are using a low-pass filter of sorts. We use the labview coerce function and set a max and minimum that limits the amount of change from reading to reading.

The coerce function sounds similar to kalman filter in behavior... we run PID once every 10ms but our PID works different where it multiplies error times the delta time. I found this to get better results... which roughly tuned yields 4% from a PD solution with no victor linearization. Looking forward to getting better results with that change. Oh and it's a coicedence that our target speed was 341 radians per second. :)

Ether
22-05-2012, 21:21
It seems like 75 - 100 ms would be reasonable to reduce the error of being part-way between counts (+/- 1.5% at 100 ms).

When you use GetRate, there is no "error of being part-way between counts".

The FPGA has a timer which measures the elapsed time between counts. When you call the GetRate method, the FPGA reports the speed based on that elapsed time, not elapsed time between calls to the function.

If the speed is 6000 rpm or higher, you are guaranteed to get a new reading every 5ms, when using a wheel with two counts per rev.

Ether
22-05-2012, 21:28
The coerce function sounds similar to kalman filter in behavior.

In what way do you think it is similar?

Tom Line
22-05-2012, 21:50
When you use GetRate, there is no "error of being part-way between counts".

The FPGA has a timer which measures the elapsed time between counts. When you call the GetRate method, the FPGA reports the speed based on that elapsed time, not elapsed time between calls to the function.

If the speed is 6000 rpm or higher, you are guaranteed to get a new reading every 5ms, when using a wheel with two counts per rev.




I see now. So the getrate will return an accurate rate in that situation. So, then, my next question:

Does that mean that if I were to use getrate with an extremely high-count encoder, the time returned will only be the last two counts it sees? Or does it average between calls? How does getrate calculate the rate?

Answer: Correct me if I'm wrong, but a couple posts I just read suggest that the getrate calculation does report only the rate between the last two ticks. In which case, having the longest period between ticks that still gives you new data for each loop would give you the most accurate rate calculation. Am I interpretting this correctly?

Ether
22-05-2012, 21:58
if I were to use getrate with an extremely high-count encoder, the time returned will only be the last two counts it sees?

Yes. That's why GetRate is so noisy for high-count encoders. The tolerance of the locations of the edges relative to the spacing between edges is high, so you get jitter.

In the C++ implementation of WPILib (but not the LabVIEW or Java implementations), GetRate does do some averaging in an attempt to reduce the noise, at least according to this post (http://www.chiefdelphi.com/forums/showpost.php?p=1122730&postcount=35).

Ether
22-05-2012, 22:12
How does getrate calculate the rate?

I would assume it takes the elapsed time (between the last two counted edges) and divides that into whatever value you have set for DistancePerPulse.

having the longest period between ticks that still gives you new data for each loop would give you the most accurate rate calculation. Am I interpretting this correctly?

That should give you a very accurate and noise-free rate calculation, assuming the edge location tolerance compared to the edge spacing is small (in other words, if you are making your own sensor with more than one piece of reflective tape, make sure they are evenly spaced).

Tom Line
22-05-2012, 22:13
Yes. That's why GetRate is so noisy for high-count encoders. The tolerance of the locations of the edges relative to the spacing between edges is high, so you get jitter.

In the C++ implementation of WPILib (but not the LabVIEW or Java implementations), GetRate does do some averaging in an attempt to reduce the noise, at least according to this post (http://www.chiefdelphi.com/forums/showpost.php?p=1122730&postcount=35).




Well isn't that entertaining :eek: . As a result of the noise we saw with getrate with our 128 count counter, we switched over to calculating rate ourselves, and have been moving to progressively higher-count encoders to reduce noise. It's a bit of a kick in the butt to understand we should have been going the opposite direction for the most accurate reading, and should go back to using getrate.

Guess I'll figure out our lowest speed tonight and our loop time and determine the lowest count encoder we can use.

AdamHeard
22-05-2012, 22:16
In terms of precision and how wpilib implements them, are getRate and getPeriod comparable?

apalrd
22-05-2012, 22:17
You can configure getRate to tell the FPGA to average a certain number of samples - This is NOT done in the Get Encoder VI, but in a different VI (Counter Set Averaging). You can also set the maximum time between pulses before the counter declares the encoder to be stopped. I believe it sets the time to 0 in that case, which the VI that reads the FPGA register then uses to determine the "Stopped" boolean output, but I don't have the VI in front of me.

We set it to 12 (using a hand made 4-line encoder) and there was very little if any noise. We don't even filter the output and it looks very clean.

My current laptop doesn't have WPIlib on it, so I can't read the exact documentation.

On a semi-related note, make sure to use Up/Down mode for the counter, and not Semi-Period mode. Semi-Period mode measures the time of either the logical high or logical low pulse (not the time between rising or falling edges), as to read a sensor which outputs a PWM signal, so differences between black and white line size will cause measurement errors in Semi-Period mode (I know the documentation conflicts on this, I have observed this behavior and know it to be true).

Edit: All of this applies to LabVIEW. I have not read through any of the other WPIlib's.

Ether
22-05-2012, 22:19
In terms of precision and how wpilib implements them, are getRate and getPeriod comparable?

From the WPILib C++ Encoder class:

/**
* Get the current rate of the encoder.
* Units are distance per second as scaled by the value from SetDistancePerPulse().
*
* @return The current rate of the encoder.
*/
double Encoder::GetRate()
{
if (StatusIsFatal()) return 0.0;
return (m_distancePerPulse / GetPeriod());
}

AdamHeard
22-05-2012, 22:25
If using getPeriod or getRate, would the optimal precision be the smallest amount that still guarantees a new data point per cycle of the control loop? Or is this difference likely negligible when compared to other errors in the system?

Ether
22-05-2012, 22:30
You can configure getRate to tell the FPGA to average a certain number of samples - This is NOT done in the Get Encoder VI, but in a different VI (Counter Set Averaging). ...All of this applies to LabVIEW.

I don't have LabVIEW installed here; could I ask you to please post a screenshot of the LabVIEW help where it says that the FPGA is being configured to average a certain number of samples?

Ether
22-05-2012, 22:52
Regardless what method you use the read the encoder, it should be mounted so that free play between the encoder and the flywheel is minimized.

apalrd
22-05-2012, 23:10
I don't have LabVIEW installed here; could I ask you to please post a screenshot of the LabVIEW help where it says that the FPGA is being configured to average a certain number of samples?




Attached are a few screenshots of the LabVIEW help illustrating several of the things I mentioned in my post above. I also attached the contents of the VI's (CounterConfigureTimer and CounterGet) and all relevant SubVI calls.

Tom Line
22-05-2012, 23:39
Attached are a few screenshots of the LabVIEW help illustrating several of the things I mentioned in my post above. I also attached the contents of the VI's (CounterConfigureTimer and CounterGet) and all relevant SubVI calls.

I see. I'm assuming the encoder getrate has no similar ability to set the sample number to average?

Ether - the method you described of calculating our own rate is the own we switched to when we threw out the getrate because of noisy returns with our high-count counters.

Now that I'm comparing the two I wonder which would be more accurate: a 512 count encoder using our own rate calculation, which will potentially lose accuracy based on when the last count happened, or a low encoder count method using the FPGA... how precise is the FPGA's internal timer?

apalrd
22-05-2012, 23:48
I see. I'm assuming the encoder getrate has no similar ability to set the sample number to average?

I see the confusion.

There are two (Actually three, only two are exposed) sets of Encoder VI's in LabVIEW:

-Encoder (what most people use for Encoders, can be set to 1x,2x,4x decoding, set number of distance per count, etc.)
-Counter (what Encoder internally uses for 1x or 2x encoding, can be set in many more ways than Encoder, can set averaging)
-There is another module which is not exposed which handles Encoder 4x decoding, it is like Counter but uses a different set of FPGA modules.

The FPGA has 8 Counter and 4 Encoder4x modules which can be used. Counter can do 1x or 2x quadrature or single wire, as well as Gear Tooth and PWM input handling. Encoder4x can do 4x quadrature decoding.

I have been talking about Counter as the Encoder VI's, since we use Counters directly for our shooter controller.

JamesTerm
23-05-2012, 00:04
In what way do you think it is similar?




I don't think it is similar... I think it sounds similar to this behavior:
"
and set a max and minimum that limits the amount of change from reading to reading
"

The kalman limits the amount of change from reading to reading when noise is introduced. I looked at the labview's definition of coerce and it looks like it is more like the priority averager I showed earlier... from what I can tell. (I do not use labview, so these terms are unfamiliar to me).

JamesTerm
23-05-2012, 00:12
I see. I'm assuming the encoder getrate has no similar ability to set the sample number to average?

Ether - the method you described of calculating our own rate is the own we switched to when we threw out the getrate because of noisy returns with our high-count counters.

Now that I'm comparing the two I wonder which would be more accurate: a 512 count encoder using our own rate calculation, which will potentially lose accuracy based on when the last count happened, or a low encoder count method using the FPGA... how precise is the FPGA's internal timer?

The thing I find interesting here is that you threw out the GetRate()... In our tests the GetRate() worked every bit as well as using...

well that's the question... what exactly did you use?

We used GetDistance() which is virtually GetRaw(). I did not see the need for critical sections, so I'd be curious to see how you did this.

Tom Line
23-05-2012, 00:36
The thing I find interesting here is that you threw out the GetRate()... In our tests the GetRate() worked every bit as well as using...

well that's the question... what exactly did you use?

We used GetDistance() which is virtually GetRaw(). I did not see the need for critical sections, so I'd be curious to see how you did this.

Initially we used getrate with the equivalent of a 64 count per rotation encoder. We would easily see swings of +/- 200 counts / second.

JamesTerm
23-05-2012, 01:15
Initially we used getrate with the equivalent of a 64 count per rotation encoder. We would easily see swings of +/- 200 counts / second.

That sounds just like what I showed in the noisy graph that was posted in pages 2-3. Notice how the reading jumps above and below a more predominant line (cyan).

And we use WindRiver... so now I'm wondering if we really did get some averaging features in the WindRiver builds.

Ether
23-05-2012, 09:08
the equivalent of a 64 count per rotation encoder.

what do you mean by "the equivalent of"? what part number were you using?

at 4000rpm, a variation of +/-0.13 degree in the absolute location of the edges1 could produce the +/-200 counts/sec jitter you saw when using GetRate.


1 equivalent to +/- 0.26 degree with respect to adjacent edges

Tom Line
23-05-2012, 11:34
what do you mean by "the equivalent of"? what part number were you using?

at 4000rpm, a variation of +/-0.13 degree in the absolute location of the edges1 could produce the +/-200 counts/sec jitter you saw when using GetRate.


1 equivalent to +/- 0.26 degree with respect to adjacent edges



We were using a 128 count bourns encoder, however it was geared down: we were taking readings from the chain with a sprocket on the encoder.

Ether
23-05-2012, 11:41
We were using a 128 count bourns encoder, however it was geared down: we were taking readings from the chain with a sprocket on the encoder.

Using GetRate to read a chain-driven high-count encoder has the potential for producing a lot of jitter, even with a high-quality encoder. Any angular free play at the encoder can show up as jitter, especially in the high-vibration environment of FRC robotics.

Not trying to be a pest, but what do you mean by "we were taking readings from the chain with a sprocket on the encoder"? Could you post a picture?

Ether
23-05-2012, 12:39
I don't think it is similar... I think it sounds similar...

Not sure I understand the distinction, but OK.

The kalman limits the amount of change from reading to reading when noise is introduced.

So does a simple low-pass filter of any type.

JamesTerm
23-05-2012, 13:58
Using GetRate to read a chain-driven high-count encoder has the potential for producing a lot of jitter


Thanks for sharing this, as we have a chain driven encoder as well and it is not geared down at all. Could a chain-driven encoder explain the extremly noisy graph that I showed on page 2? I can say that our shooter had a lot of vibrations at higher speeds, probably more than what it should have had.

All:
What are teams doing as a better alternative to a chain-driven encoder for the shooter?

Ether
23-05-2012, 14:58
we have a chain driven encoder

Is the encoder separately chain-driven? Or is the encoder mounted directly to the wheel (or wheel axle) which is chain-driven?

Makes a difference.

Brandon Holley
23-05-2012, 15:00
All:
What are teams doing as a better alternative to a chain-driven encoder for the shooter?

Encoder's require very little torque to spin (usually). In the past I've used rubber bands with no issue to transfer one shafts rotation to an encoders.

Our particular shooter wheel has the end of the shooter wheel shaft drilled out. We then just wrapped the encoder shaft in a layer or two of Teflon tape and inserted the encoder into the shooter shaft. The encoder was mounted to a simple rectangular plate and some standoffs space the encoder off the shooter wheel's mounting plate.

-Brando

apalrd
23-05-2012, 15:02
All:
What are teams doing as a better alternative to a chain-driven encoder for the shooter?

Direct-driving the encoder off the shooter, via several methods:

-Using a live axle shooter and connecting the encoder directly to it
-Using a live or dead axle shooter with a code wheel and optical sensor (Banner and Rockwell sensors seem to be the most popular)

In addition to the vibrations of a chain, I assume the constant modification of output power by the control loop would cause additional jitter if the chain was slightly (or more than slightly) slack, probably more than any other single source of jitter.

In our shooter system, I can hear the difference between running the control loop and applying fixed power, especially at low speeds where we're approaching the Victor deadband. The motor is pulsing at a low frequency, and I can hear the gears in the gearbox make nasty noises as they're being driven by the motors, then the motors back off and the inertia of the system drives the output gear faster than the motors, then the motors pick up. It's much less noticeable at high speeds.

ParkerF
23-05-2012, 15:16
Is the encoder separately chain-driven? Or is the encoder mounted directly to the wheel (or wheel axle) which is chain-driven?

Makes a difference.



Separately driven. It's a short 1:1 #25 chain off of the main shooter shaft.

-Parker

Ether
23-05-2012, 15:30
Separately driven. It's a short 1:1 #25 chain off of the main shooter shaft.

If you're using GetRate() with a separately-chain-driven 128 CPR encoder, that could be the source of a lot of your noise.

JamesTerm
23-05-2012, 15:57
If you're using GetRate() with a separately-chain-driven 128 CPR encoder, that could be the source of a lot of your noise.




I've managed to find a rate compare dump that may be of interest... this shows GetRate() (magenta) vs. The GetDistance()/time (cyan)... where the yellow shows a minor change in time of 10-11 ms. I believe GetRate() has better results in this test. Setting that aside, notice how the faster speeds have more linear patterns at certain points. This is measuments in RPS from 0 to 35... where x is 10-11 ms iterations.

http://www.termstech.com/files/RateTest.gif

Ether
23-05-2012, 17:22
I've managed to find a rate compare dump that may be of interest... this shows GetRate() (magenta) vs. The GetDistance()/time (cyan)... where the yellow shows a minor change in time of 10-11 ms. I believe GetRate() has better results in this test. Setting that aside, notice how the faster speeds have more linear patterns at certain points. This is measuments in RPS from 0 to 35... where x is 10-11 ms iterations.

http://www.termstech.com/files/RateTest.bmp

Two things:

1) You might want to consider converting your large (1+ megabytes) BMP file attachments to a compressed format (PNG would be nice) before uploading. Attached PNG file is 380 times smaller (only 3 kilobytes) than the BMP you posted, with no noticeable decrease in quality

2) 35 revs/sec @ 64 counts/rev = 2240 counts/sec = 22.4 counts/(10 ms), so +/- 1 count should give jitter of approx +/-1/22.4 = +/-4.5%. So I can't draw any conclusion from your graph except that something is not right.

Tom Line
23-05-2012, 18:01
With regards to the questions about how we were running our encoder in the past: we had a sprocket on the encoder shaft riding against the chain that drove the shooter. The shooter sprocket was smaller than the encoder sprocket by a ratio of 2:1, resulting in 2 turns of the shooter for 1 turn of the encoder.

Since then, in our quest to lower noise, we now have the encoder coupled directly to the output shaft with a very short piece of surgical tubing. That has worked very well (it puts less stress on the encoder shaft as well).

Ether
23-05-2012, 18:13
With regards to the questions about how we were running our encoder in the past: we had a sprocket on the encoder shaft riding against the chain that drove the shooter.

Like this (http://www.chiefdelphi.com/forums/attachment.php?attachmentid=12786&stc=1&d=1337811338)?

That explains a lot of the noise:)

Since then, in our quest to lower noise, we now have the encoder coupled directly to the output shaft with a very short piece of surgical tubing. That has worked very well (it puts less stress on the encoder shaft as well).

Makes sense.

Tom Line
23-05-2012, 20:01
Like this (http://www.chiefdelphi.com/forums/attachment.php?attachmentid=12786&stc=1&d=1337811338)?

That explains a lot of the noise:)

Makes sense.




No... our encoder and sprocket was on top of the chain, not under it..... ;)

Yes, exactly like that.

JamesTerm
23-05-2012, 21:00
Two things:

1) You might want to consider converting your large (1+ megabytes) BMP file attachments to a compressed format (PNG would be nice) before uploading. Attached PNG file is 380 times smaller (only 3 kilobytes) than the BMP you posted, with no noticeable decrease in quality

2) 35 revs/sec @ 64 counts/rev = 2240 counts/sec = 22.4 counts/(10 ms), so +/- 1 count should give jitter of approx +/-1/22.4 = +/-4.5%. So I can't draw any conclusion from your graph except that something is not right.




Ok I've changed this one to gif... I tried to get the others but I can't edit them... oh well. I'll use gif from now on for these. (They compress about the same and PNG).

For now... I'll conclude that the encoder was defected with the problems I mentioned before. At some point, since we have both bots... I'll get an up to date reading from both and present them here.

One thing I do find interesting though is how much better GetRate() worked for this stress, but for now I want to dismiss this as evidence until I do further testing.

While I'm here I want to thank Brandon and Palardy for their suggestions... we are very grateful for your input as we learn new stuff everyday. :)