Chief Delphi

Chief Delphi (http://www.chiefdelphi.com/forums/index.php)
-   C/C++ (http://www.chiefdelphi.com/forums/forumdisplay.php?f=183)
-   -   Unexpected results from Encoder::GetRate() (http://www.chiefdelphi.com/forums/showthread.php?t=82171)

LyraS 08-02-2010 14:07

Unexpected results from Encoder::GetRate()
 
I'm trying to set up a speed controller using the US Digital E4P-250-250-D-D-D-B encoder and the WPI Encoder class. Everything basically works fine but I'm seeing an odd discrepency between the values out of GetRate() and GetDistance(). Spinning the encoder at a constant rate, I would expect that the rate returned by GetRate() (units/sec) would more or less match the one-second delta returned by GetDistance(). But what I see is this: the distance returned for one second is almost exactly half of GetRate(). Because GetRate() is fairly noisy, I'm testing using a 20 sample moving average (with a sampling rate of 40hz). I'm sure I'm missing something obvious... Can one of the WPIlib experts here clue me in on what it is? Thanks!

Update, possible clue:

Looking through the WPI source I noticed this comment in Counter.cpp (used by 1x and 2x Encoder):

/*
* Get the Period of the most recent count.
* Returns the time interval of the most recent count. This can be used for velocity calculations
* to determine shaft speed.
* @returns The period of the last two pulses in units of seconds.
*/
double Counter::GetPeriod()

vamfun 23-03-2010 04:05

Re: Unexpected results from Encoder::GetRate()
 
Just now seeing the same problem when running x1 with the kit encoders.

GetDistance() gives the correct value but the GetRate() gives double rate.

Anybody using this function with success?

We have all the latest updates in SW.

Update:

I decided to check the GetPeriod() in Encoder.cpp. I don't see any problem with the LyraS question but something else seems strange to me here and maybe someone can tell me why its correct.

double Encoder::GetPeriod()
{
if (m_counter)
{
return m_counter->GetPeriod() * DecodingScaleFactor(); //****THIS IS WHERE I SEE A PROBLEM ***
}
else
{
tEncoder::tTimerOutput output = m_encoder->readTimerOutput(&status);
double value;
if (output.Stalled)
{
// Return infinity
double zero = 0.0;
value = 1.0 / zero;
}
else
{
value = (double)output.Period / (double)output.Count;
}
wpi_assertCleanStatus(status);
return value * 1.0e-6 / (DecodingScaleFactor() * 4.0);
}
}


In the 1x and 2x the counters are used. The period that the counters return should be for the actual pulses not the spec pulse frame. Hence, the periods should be shorter when running 2x. So when

if (m_counter)
{
return m_counter->GetPeriod() * DecodingScaleFactor(); //****THIS IS WHERE I SEE A PROBLEM ***
}

occurs, it should correct the counter period to the spec period which would be longer. This statement makes the period the same or shorter since DecodingScaleFactor() returns 1 for 1x , .5 for 2x.

It seems that this should read

if (m_counter)
{
return m_counter->GetPeriod() / DecodingScaleFactor(); //****USE DIVISION TO CORRECT PERIOD ***
}

The division by DecodingScaleFactor() would then be similar to that used in the 4x return value.
If my reasoning is correct, then the 2x should be off by a factor of 4 but the 1x should be ok. So unfortunately this doesn't explain why we still are double the rate when running at 1x and maybe when I get some time, I'll check this hypothesis. Where is Joe Hershberger when we need him?

Joe Hershberger 25-03-2010 00:52

Re: Unexpected results from Encoder::GetRate()
 
Have you guys tried 2x and 4x modes? Do you get what you expect in those modes?

At a glance, it looks like the period should be divided by DecodingScaleFactor(), as suggested. I don't think the 4x version should have the "4.0" in the denominator either. I need to try it out when I'm at a system that has an encoder. I'm at the regional in Cleveland this weekend.

-Joe

vamfun 25-03-2010 01:16

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by Joe Hershberger (Post 942972)
Have you guys tried 2x and 4x modes? Do you get what you expect in those modes?

-Joe

Not yet..competing in LA regional this weekend also.

Quote:

I don't think the 4x version should have the "4.0" in the denominator either.
Yes, I could see no reason for that either. Thought this was a hold over from the 4x error days. Hard to believe no one has reported any problems with this function. Guess everyone is doing their own rate derivation which we were going to do , but I wanted to give the wpi lib a chance since I didn't remember these being a problem last year. We are using the GetRate() with a .5 multiplier. Seems to work ok then.

Bigcheese 26-03-2010 20:06

Re: Unexpected results from Encoder::GetRate()
 
Encoder::GetRate() in 4x mode returns crap. And by crap I mean the value returned is proportional to the speed of the robot, but I know our bot is not going 80 feet per second (55 mph)... I tried changing (4x mode) to return:
Code:

value * 1.0e-6  / DecodingScaleFactor()
Instead of:
Code:

value * 1.0e-6  / (DecodingScaleFactor() * 4)
Which lowered the error, however it is still way too high.

I know I can just multiply the returned value by some gain, but I'd rather have the code actually be right. I'm about to go chuck the dang thing up in the lathe so I know what the value returned should be, and work from that.

vamfun 27-03-2010 02:42

Re: Unexpected results from Encoder::GetRate()
 
Good to have another data point. Couple of questions.

Is the GetDistance() correct?

If so, can you use it to make a quantitative estimate on what the ratio of encoder rate to actual rate in x4 after you eliminated the 4 factor in the denominator? I'm guessing it might be off by a factor of two like the 1x. If it is, that could provide a clue to whats going on.

In my experience, using 4x to get rate always introduces extra noise due to inherent phase errors built into the code wheels.
http://www.chiefdelphi.com/forums/sh...=encoder+noise

jhersh 29-03-2010 02:40

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by Bigcheese (Post 943518)
Encoder::GetRate() in 4x mode returns crap. And by crap I mean the value returned is proportional to the speed of the robot, but I know our bot is not going 80 feet per second (55 mph)...

Did you actually call SetDistancePerPulse() with the correct scale? The API can't magically know your gear ratios or wheel circumferances.

Bigcheese 03-04-2010 21:47

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by jhersh (Post 944762)
Did you actually call SetDistancePerPulse() with the correct scale? The API can't magically know your gear ratios or wheel circumferances.

Yes, GetDistance returns the correct distance when the bot is pushed.

1x and 2x are not as noisy. And are normally distributed. 4x is distributed towards the center and +- some value. It's either a code problem or an encoder problem. I'm going to hook it up to a function generator and figure out which it is.

vamfun 04-04-2010 19:05

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by Bigcheese (Post 947582)
Yes, GetDistance returns the correct distance when the bot is pushed......

I'm going to hook it up to a function generator and figure out which it is.

The function gen is a good idea, but you can use your GetDistance() to make an accurate estimate of the average rate. Could save you a little time.

Bigcheese 04-04-2010 22:53

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by vamfun (Post 947941)
The function gen is a good idea, but you can use your GetDistance() to make an accurate estimate of the average rate. Could save you a little time.

If you used a large enough dt, true. However, 1x or 2x using GetRate returns perfectly fine data and takes less time to implement. The point of using a function generator is simply to verify that it is indeed the encoder, not the FPGA code, that is the problem.

I could also just fix my stupid 2nd probe and look at the encoder output on the scope...

BTW, the FPGA code could be changed to account for the phase lag. It's actually a very easy fix, although it would require you to experimentally determine the phase lag and pass it to the FPGA. Determining this would simply require spinning the encoder at a constant speed and look at the channel B phase offset.

vamfun 05-04-2010 17:09

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by Bigcheese (Post 948133)
If you used a large enough dt, true. However, 1x or 2x using GetRate returns perfectly fine data and takes less time to implement.

Ok, then you are not seeing the factor of 2 on 1x rate that we are and determining avg rate would be unnecessary. How did you independently verified the 1x rate was ok?

It is hard to envision that the encoder could be in error if GetDistance() is working, so I vote for the code.

Bigcheese 05-04-2010 21:10

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by vamfun (Post 948638)
Ok, then you are not seeing the factor of 2 on 1x rate that we are and determining avg rate would be unnecessary. How did you independently verified the 1x rate was ok?

It is hard to envision that the encoder could be in error if GetDistance() is working, so I vote for the code.

The speed is still off by a constant factor, but that's not what I'm focusing on at the moment. I agree that this is a code issue because GetDistance is correct.

The encoders are the reason for the noisy data returned by 4x decoding. GetDistance isn't greatly effected by this because every rising/falling edge is offset an equal but opposite amount, so it averages out to be correct. The problem comes in when you try to take the derivative (GetSpeed), because now these offsets are no longer averaged out.

mikets 05-04-2010 21:46

Re: Unexpected results from Encoder::GetRate()
 
I did not follow the entire thread of discussion so if I misunderstood the issue, I apologize. But I do want to mention my experience on dealing with the accelerometer by integrating it twice to get distance. How did you differentiate the distance value to get speed? In particular, how did you get dt? The reason I ask is because I discovered that if you have code that is supposed to be run periodically at a fixed interval, don't count on the interval being accurate. For example, my integrator code is supposed to run every 10 msec, eventually I found out that it got called every 20+ msec instead. So don't use the period as your dt because it is not accurate. Your speed data will be all over the place. I scratched my head on the accelerometer for a while until I finally timed the interval of my code being called and found that I can't depend on it being constant. So I now wrote the code to measure the interval and use it as my dt instead. Then my integrator is now quite accurate.

vamfun 06-04-2010 01:42

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by Bigcheese (Post 948819)
The speed is still off by a constant factor..

So I was hoping that you would use GetDistance() with a large dt (say 10 revolutions) to determine this constant to verify that it matches our estimated factor of 2 constant.

Quote:

The encoders are the reason for the noisy data returned by 4x decoding. GetDistance isn't greatly effected by this because every rising/falling edge is offset an equal but opposite amount, so it averages out to be correct. The problem comes in when you try to take the derivative (GetSpeed), because now these offsets are no longer averaged out.
We are in full agreement here and as I stated before, our team never uses x4 because it really is unnecessary with 1 deg resolution encoders and why introduce the asymmetric 4x phase errors.

So the noise is expected, but the mean values being off by a constant is what I'm focused on. This smacks of a FPGA counter average period problem.

vamfun 06-04-2010 02:06

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by mikets (Post 948843)
For example, my integrator code is supposed to run every 10 msec, eventually I found out that it got called every 20+ msec instead. So don't use the period as your dt because it is not accurate. 't depend on it beingYour speed data will be all over the place. I scratched my head on the accelerometer for a while until I finally timed the interval of my code being called and found that I can constant. So I now wrote the code to measure the interval and use it as my dt instead. Then my integrator is now quite accurate.

This is a little off topic in that the encoders use pulse interval timers to derive speed and do not depend on periodic loops. But I suspect that the FPGA interval timer that measures an average pulse period may have a bug in it.

In support of your comments, we use the iterative robot class and a navigation function that integrates encoder rate resolved to field coordinates. Distance tests were off by a factor of 2. Either the speeds were double or the loop iteration rate was double. We integrated dt = 1/(Update_Rate) and checked that the computed time matched real time so this led us to the double encoder GetRate() error which we verified independently using motor rpm measurements.

mikets 06-04-2010 06:07

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by vamfun (Post 949026)
We integrated dt = 1/(Update_Rate) and checked that the computed time matched real time so this led us to the double encoder GetRate() error which we verified independently using motor rpm measurements.

But what is Update_Rate? If it's derived from the period of some periodic loop, you need to verify the real interval of the periodic loop. How did you compute real time? I use the following code to measure my periodic intervals:
Code:

UINT32 timeCurr = GetFPGATime(); // in usec
float period = (float)(timeCurr - m_timestamp)/1000000.0;
m_timestamp = timeCurr;


vamfun 06-04-2010 16:17

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by mikets (Post 949047)
But what is Update_Rate? If it's derived from the period of some periodic loop, you need to verify the real interval of the periodic loop. How did you compute real time?

Update_Rate is a define... say 100. hz.
dt = 1/Update_rate;

We use iterative class SetPeriod(dt);

Then we measure cumulative time rather than loop period:
We put this in the loop : time = time + dt :
and simply compare time print out with a stop watch. Since we were looking for a factor of 2 this was accurate enough. We easily checked within a few %.

mikets 06-04-2010 17:31

Re: Unexpected results from Encoder::GetRate()
 
I am sorry. I am probably not helping with your particular problem. I am merely trying to point out that in the iterative robot class, don't count on the period being accurate and use it as dt for differentiation or integration. You said your Update_Rate is 100 Hz. That makes the period 10 ms. If you put "time = time + 1/Update_Rate" in your loop, your time is really summing a constant 10 ms on every loop period, not real time. If you do this instead:
Code:

class MyRobot: public IterativeRobot
{
private:
    UINT32 m_timestamp;
    ....

public:
    void AutonomousInit()
    {
        m_timestamp = GetFPGATime();
        ....
    }
    void AutonomousPeriodic()
    {
        UINT32 timeCurr = GetFPGATime(); // in usec
        float period = (float)(timeCurr - m_timestamp)/1000000.0;
        m_timestamp = timeCurr;
        printf("loop period is %f\n", period);
        ...
    }
};

"period" will be the accurate loop interval that can be used as dt in your differentiation or integration. In theory period should print out 0.01s (10 ms), but in our case, it is way off. It could be because our loop execution took longer than the 10ms to execute that may cause a period to be skipped in some cases. Therefore, as a good practice, never use the period you set in any time critical calculations.

vamfun 06-04-2010 18:40

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by mikets (Post 949348)
In theory period should print out 0.01s (10 ms), but in our case, it is way off. It could be because our loop execution took longer than the 10ms to execute that may cause a period to be skipped in some cases.

I've done a lot of real time control systems and never had a problem with interrupt driven loops because we always verify that execution cannot exceed the loop period. So I suspect you have a problem delay in your loop and indeed if you are using a debug console printf(...) in a 10 ms loop then this can easily cause you a problem since it requires about 1ms per char and once you exceed the buffer, this becomes a realized delay.



Quote:

Therefore, as a good practice, never use the period you set in any time critical calculations
... unless you have verified your computations can't exceed the loop time:)

Edit: Actually this is a design issue rather than good practice. Either you design it as a periodic loop or not. Often programmers don't understand the hardware fully and run into these types of problems and wind up adding the type of compensation programming that you are suggesting. In fact, IMHO, when things are time critical, the periodic loop is often preferred since it is more predictable than non periodic solutions.

vamfun 09-04-2010 00:53

Re: Unexpected results from Encoder::GetRate()
 
Ok, back to Encoder Problem:
I got hold of a CRIO finally and ran some encoder outputs while we bench drove the robot. I set the iterative loop rate to 10 hz and printed to the console :
printf( ..... ", GetDistance(), GetRate(), Derived rate) where derived rate was the avg rate over the 100ms period.

Ran full forward, full reverse, partial forward, partial reverse cases for 1x,2x,4x.

Data can be downloaded from
http://www.scribd.com/doc/29635300/5...er-4-7-09-Test
I recommend downloading and viewing the plots since they are cutoff on scrid site.

For some reason the encoder counts were about 1/5.7 of what they should have been... ie the avg derived rate was 1.2 fps rather than 6.8 fps. So the encoder might be broken but it did register linear GetDistance().
Aside from this, the GetRate() was consistent for 1x,2x,4x and showed plots with basically two interwoven levels. One close to the derived rate of 1.2 fps and the other around 12 fps when running max speed. So there is definitely something odd for all modes.

So, today, I tried some more direct debug of encoder.cpp. I disconnected the encoder inputs and replaced them with a periodic pulse on the A channel and open (pull up value) on the B channel. We figured we could manually cause the counters to increment by alternating between open and ground on the A input. Well, something really weird happened. The 0 input caused a 1 count and the 1 input caused the count to decrement to 0 count again. Huh?? So the GetRaw() just alternated between 1 and 0 rather than accumulate.

So must be some good explaination... right?

Maybe more on Monday.

Joe Ross 09-04-2010 01:02

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by vamfun (Post 950605)
So, today, I tried some more direct debug of encoder.cpp. I disconnected the encoder inputs and replaced them with a periodic pulse on the A channel and open (pull up value) on the B channel. We figured we could manually cause the counters to increment by alternating between open and ground on the A input. Well, something really weird happened. The 0 input cause a 1 count and the 1 input caused the count to decrement rather than accumulate. Huh?? So the GetRaw() just alternated between 1 and 0.

That would be expected for quadrature decoding. With the B channel stuck at a value, it looks like it's constantly changing directions.

vamfun 09-04-2010 01:26

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by Joe Ross (Post 950606)
That would be expected for quadrature decoding. With the B channel stuck at a value, it looks like it's constantly changing directions.

Joe, I would expect that for 2x and 4x but not 1x, since with 1x only A channel leading edge determines the count and the B channel is always the same value on the leading edge. Unless I'm missing something.

Jared Russell 09-04-2010 07:52

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by vamfun (Post 950613)
Joe, I would expect that for 2x and 4x but not 1x, since with 1x only A channel leading edge determines the count and the B channel is always the same value on the leading edge. Unless I'm missing something.

There are many ways that one could implement a 1x quadrature decoder.

For a few reasons, a 1x decoder that requires a transition on both phases prior to incrementing the count is advantageous (namely, it can discriminate between the constantly changing direction vs. constant direction case).

vamfun 09-04-2010 17:12

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by Jared341 (Post 950645)
There are many ways that one could implement a 1x quadrature decoder.

For a few reasons, a 1x decoder that requires a transition on both phases prior to incrementing the count is advantageous (namely, it can discriminate between the constantly changing direction vs. constant direction case).

Ok, lets get specific. When we write our own interrupt encoder routines, the B channel is just looked at when a TEU pulse occurs on the A chan to get the direction. However, the tcounters in CRIO are a bit of a mystery to me still and I need some more tutoring.

Here, the upSource is A chan and the downSource is B chan. This is called by encoder.cpp.

Counter::Counter(EncodingType encodingType, DigitalSource *upSource, DigitalSource *downSource, bool inverted)
{
wpi_assert(encodingType == k1X || encodingType == k2X);
InitCounter(kExternalDirection);
SetUpSource(upSource);
SetDownSource(downSource);

if (encodingType == k1X)
SetUpSourceEdge(true, false); // THis is clear to me
else
SetUpSourceEdge(true, true); // This is clear to me

SetDownSourceEdge(inverted, true); // This needs explanation
}


As I understand it, the tcounters have independent sources than can be set to count up or down. How does setting the B chan to always count down on a TED and down on TEU if inverted=true set up the counter to give the proper direction?

Bigcheese 10-04-2010 01:56

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by vamfun (Post 950605)
For some reason the encoder counts were about 1/5.7 of what they should have been... ie the avg derived rate was 1.2 fps rather than 6.8 fps. So the encoder might be broken but it did register linear GetDistance().
Aside from this, the GetRate() was consistent for 1x,2x,4x and showed plots with basically two interwoven levels. One close to the derived rate of 1.2 fps and the other around 12 fps when running max speed. So there is definitely something odd for all modes.

That's definitely not what I get. How do you get a negative value when going full forward?

Here's the data I got for a bump test I did about a week ago. The setup is really weird because we were rushing to get it before we had to leave for a week (spring break). There are two different encoders on the left and right drive trains (I don't remember which is which at the moment). We set the robot on its butt and set the jags to output 0.6, waited for 2 seconds, and then set them back to 0.

The code is set to 2x decoding and a 0.005 period (200 HZ) between every sample.

https://docs.google.com/leaf?id=0B3I...NTFjM2Iz&hl=en

Again, I don't know exactly what value should be returned, but obviously there is a difference in what our GetRate()s are returning.

I should have access to the bot tomorrow, so I'll try adding a plot of the derived rate.

vamfun 10-04-2010 03:16

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by Bigcheese (Post 951452)
That's definitely not what I get. How do you get a negative value when going full forward?

Here's the data I got for a bump test I did about a week ago.
Again, I don't know exactly what value should be returned, but obviously there is a difference in what our GetRate()s are returning.

I should have access to the bot tomorrow, so I'll try adding a plot of the derived rate.

Well, this data looks better... I suspect we had a bad encoder or maybe hardware problem.

Only thing clear is a factor of 2 difference in your data so far.

What are the units?
How did you acquire data?
Can you also print GetDistance or GetRaw?
Was SetDistancePerPulse checked for each encoder?
Can you run a 1x case if you have time?

Bigcheese 10-04-2010 11:07

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by vamfun (Post 951461)
Only thing clear is a factor of 2 difference in your data so far.

The factor of two difference between the encoders is because one is about twice the pulses per rev as the other.

Quote:

Originally Posted by vamfun (Post 951461)
What are the units?
How did you acquire data?
Can you also print GetDistance or GetRaw?
Was SetDistancePerPulse checked for each encoder?
Can you run a 1x case if you have time?

  • The units are in feet per second.
  • Using a notifier that printed GetFPGATime(),GetJagOutput(),GetLeftRate(),GetRigh tRate() at 200Hz
  • Hopefully today.
  • The same value was used for both and was correct for one of them.
  • I think I have one, but I'm not sure which it was (we were rushing to see the data, didn't mark any of it). The data was almost identical except for a little less noise. I'll run it again though.

I will do this today if I can get into the school.

Bigcheese 11-04-2010 21:16

Re: Unexpected results from Encoder::GetRate()
 
Ok, I finally have good data. First, I edited Encoder.cpp, here's the diff.

Code:

Index: Encoder.cpp
===================================================================
--- Encoder.cpp        (revision 2130)
+++ Encoder.cpp        (working copy)
@@ -258,7 +258,7 @@
 {
        if (m_counter)
        {
-                return m_counter->GetPeriod() * DecodingScaleFactor();
+                return m_counter->GetPeriod() / DecodingScaleFactor();
        }
        else
        {
@@ -275,7 +275,7 @@
                        value = (double)output.Period / (double)output.Count;
                }
                wpi_assertCleanStatus(status);
-                return value * 1.0e-6  / (DecodingScaleFactor() * 4.0);
+                return value * 1.0e-6 / DecodingScaleFactor();
        }
 }
 
@@ -380,7 +380,7 @@
  */
 double Encoder::GetRate()
 {
-        return m_distancePerPulse / GetPeriod() * (GetDirection() ? 1.0 : -1.0);
+        return (m_distancePerPulse / GetPeriod() * (GetDirection() ? 1.0 : -1.0)) / 2;
 }
 
 /**

The following data is in 1x and 2x mode. I didn't do a 4x test.

https://spreadsheets.google.com/ccc?...0JZe UE&hl=en

Looks perfect to me :)

vamfun 12-04-2010 04:15

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by Bigcheese (Post 952416)
The following data is in 1x and 2x mode. I didn't do a 4x test.

https://spreadsheets.google.com/ccc?...0JZe UE&hl=en

Looks perfect to me :)

Thanks for running a nice data set. So this is what I get from it:
1) Confirmed that the 1x is off by factor of 2.
2) Confirmed that 2x is off by same factor of 2 if corrected for inverted Decoding scale factor error. 2x rate exhibits more noise as expected.
3) We have not explained why the counter GetPeriod() is off by 2.
4)TBD for 4x. If the problem is in the counter GetPeriod() we might expect that the factor of 2 used in GetRate() will not be correct for 4x since it uses the FPGA. So this needs confirmation.

The next step is to find out where the counter period problem is. This is where the factor of 2 should be fixed rather than in the GetRate() function. In the counter.cpp
period = (double)output.Period / (double)output.Count;
So either output.Period is wrong or output.Count is defaulting to 2 instead of 1. I suspect the latter and will try to check this today.

Also still waiting for Joe or other expert to explain how B channel works to set count direction in WPI libs .
post http://www.chiefdelphi.com/forums/sh...3&postcount=24
Edit: Looking at the counter constructor for the quad encoder...seems that if
InitCounter(kExternalDirection);
is present then
SetDownSourceEdge(inverted, true);
has a special meaning. It sets direction externally with chan B rather than actually change the down count with chan B edges as the name implies. If this is the case, I really have the urge to yell at an overworked WPI programmer for this style of coding.

Mike: can you post a snippit of your notifier code and tell us how the printf data was sent to the console? We might try that tomorrow.

vamfun 12-04-2010 21:14

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by vamfun (Post 952580)
Thanks for running a nice data set. So this is what
The next step is to find out where the counter period problem is. This is where the factor of 2 should be fixed rather than in the GetRate() function. In the counter.cpp
period = (double)output.Period / (double)output.Count;
So either output.Period is wrong or output.Count is defaulting to 2 instead of 1. I suspect the latter and will try to check this today.

Todays runs showed that output.Count = 1 as it should.... so output.Period is the remaining suspect.

Bigcheese 13-04-2010 00:42

Re: Unexpected results from Encoder::GetRate()
 
Here's 4x decoding data with the same changes to Encoder.cpp as listed above.

http://spreadsheets.google.com/ccc?k...E1zX 2c&hl=en

And here's the relevant code for getting the data.

Code:

void  updateCSV(void* stream)
{
        CompetitionRobot1771* r = CompetitionRobot1771::GetInstance();
        std::ofstream& s = *reinterpret_cast<std::ofstream*>(stream);

        static double left_previous_distance = r->m_PMU.m_leftEncoder.GetDistance();
        static double right_previous_distance = r->m_PMU.m_rightEncoder.GetDistance();
        static double previous_time = static_cast<double>(GetFPGATime() - r->m_BumpTestStartTime) * 1e-6;

        double current_time = static_cast<double>(GetFPGATime() - r->m_BumpTestStartTime) * 1e-6;

        double left_distance = r->m_PMU.m_leftEncoder.GetDistance();
        double right_distance = r->m_PMU.m_rightEncoder.GetDistance();

        double left_derived_rate = (left_distance - left_previous_distance) / (current_time - previous_time);

        double right_derived_rate = (right_distance - right_previous_distance) / (current_time - previous_time);

        double left_rate = r->m_PMU.m_leftEncoder.GetRate();
        double right_rate = r->m_PMU.m_rightEncoder.GetRate();

        s << current_time << "," << r->m_CO << ","
          << left_distance << "," << left_rate << "," << left_derived_rate << ","
          << right_distance << "," << right_rate << "," << right_derived_rate << std::endl;

        left_previous_distance = left_distance;
        right_previous_distance = right_distance;
        previous_time = current_time;

        r->GetWatchdog().Feed();
}

void CompetitionRobot1771::Autonomous(void)
{
        GetWatchdog().SetEnabled(true);
        while(IsAutonomous() && IsEnabled())
        {
                GetWatchdog().Feed();
                std::ofstream csv("bump_test.csv", std::ios::trunc);
                Notifier csv_update(updateCSV, &csv);

                csv << "Time,CO,Left Distance,Left PV,Left Derived PV,Right Distance,Right PV,Right Derived PV\n";
                m_CO = 0;
                m_BumpTestStartTime = GetFPGATime();

                m_PMU.m_leftEncoder.Reset();
                m_PMU.m_rightEncoder.Reset();
               
                csv_update.StartPeriodic(0.005);

                foreach(CANJaguar* c, m_LeftJags)
                {
                        c->Set(-m_CO);
                }
                foreach(CANJaguar* c, m_RightJags)
                {
                        c->Set(m_CO);
                }
                Wait(0.1);
               
                m_CO = m_BumpTestCO;
                foreach(CANJaguar* c, m_LeftJags)
                {
                        c->Set(-m_CO);
                }
                foreach(CANJaguar* c, m_RightJags)
                {
                        c->Set(m_CO);
                }
                Wait(m_BumpTestTime);
               
                m_CO  = 0;
                foreach(CANJaguar* c, m_LeftJags)
                {
                        c->Set(-m_CO);
                }
                foreach(CANJaguar* c, m_RightJags)
                {
                        c->Set(m_CO);
                }
                Wait(m_BumpTestTime);

                csv_update.Stop();
        }
}

I agree that the problem is with the period returned by the FPGA.

jhersh 13-04-2010 03:43

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by vamfun (Post 952580)
Edit: Looking at the counter constructor for the quad encoder...seems that if
InitCounter(kExternalDirection);
is present then
SetDownSourceEdge(inverted, true);
has a special meaning. It sets direction externally with chan B rather than actually change the down count with chan B edges as the name implies. If this is the case, I really have the urge to yell at an overworked WPI programmer for this style of coding.

This is directly related to the register interface for the counter IP in the FPGA where bits that are not used in a given mode are overloaded to control other parts of the logic. The real failure here is that I didn't clearly document what wicked things I was doing with those bits in the library code.

Allow me to do that here...

If you select "ExternalDirection" mode, then the following overloads apply:
"DownRisingEdge" specifies invert the direction or not.
"DownFallingEdge => true" enables quadrature decoding mode.
In quadrature decoding mode, "UpFallingEdge" selects 1x or 2x decoding and "UpRisingEdge" must be true... more literally, what these two mean in quadrature mode is:
"UpRisingEdge => count A if B is high"
"UpFallingEdge => count A if B is low"

Obvious, right? Again sorry for the confusion (I realize you probably aren't any less confused at this point).

Quote:

Originally Posted by vamfun (Post 952580)
Also still waiting for Joe or other expert to explain how B channel works to set count direction in WPI libs .
post http://www.chiefdelphi.com/forums/sh...3&postcount=24

Here are the much anticipated logic equations used in the 1x and 2x quadrature decoding mode of the counters.

Count = (ARising | AFalling) & ((BHigh & UpRisingEdge) | (BLow & UpFallingEdge))
CountUp = Count & !(BHigh ^ DownRisingEdge ^ ARising)
CountDown = Count & (BHigh ^ DownRisingEdge ^ ARising)

With this, there will hopefully be no more mystery. :)

See you in Atlanta!
-Joe

vamfun 13-04-2010 17:00

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by Bigcheese (Post 953143)
Here's 4x decoding data with the same changes to Encoder.cpp as listed above.

http://spreadsheets.google.com/ccc?k...E1zX 2c&hl=en

And here's the relevant code for getting the data.



I agree that the problem is with the period returned by the FPGA.

Thanks for the code... both our encoders are broken so may not get to it for a while.

Question: What encoders did you use on left and right and what are the corresponding distances per pulse? The right encoder has a little less noise than the left.

I did a statistical analysis of your data and will post it soon.

If you still have the same set up and a little extra time, I'd love to see the 2x and 4x cases with the get rate averaged over 2 and 4 pulses respectively rather than the default single pulse. Please use the corresponding JAG speeds as in the original data sets. In case you aren't familiar with this:

The 2x is set in the counter.cpp InitCounter() line :
m_counter->writeTimerConfig_AverageSize(1, &status);
by changing to
m_counter->writeTimerConfig_AverageSize(2, &status);

Similarly, the 4x is set in the encoder.cpp InitEncoder() line:
m_encoder->writeTimerConfig_AverageSize(1, &status);
by changing to
m_encoder->writeTimerConfig_AverageSize(4, &status);

This would help mitigate the rate noise if people insist on using 2x and 4x encoder modes. I would like this to be the default configurations. If you run the cases Ill add it to the statistical analysis and post it also. It would be a nice reference for future First teams making this decision.

If you can't do it, I certainly understand. Thanks

By the way, last year Joe H and I had a thread on moving average : http://forums.usfirst.org/showthread...8143#post28143. Our team added a function to the Encoder.cpp that allows you to do this in your robot init code similar to SetDistancePerPulse(). Eventually, I hope the WPI guys incorporate this capability.
Code:

void SetMovingAveragePulseCount(int max_count)
{ //check if 1=<max_count<=127 TODO: should throw an error flag here with message if max_count invalid
if (max_count<1) max_count = 1;
else if (max_count>127) max_count = 127;
if(m_counter)
m_counter->m_counter->writeTimerConfig_AverageSize(max_count, &status); // Counter object within Encoder -> tCounter within Counter
else
m_encoder->writeTimerConfig_AverageSize(max_count, &status);
}


Bigcheese 13-04-2010 18:27

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by vamfun (Post 953400)
Question: What encoders did you use on left and right and what are the corresponding distances per pulse? The right encoder has a little less noise than the left.

They are both the standard ones from andymark. 0.0054105234681583 feet per pulse. I have no idea why one is a lot noisier than the other.

Quote:

Originally Posted by vamfun (Post 953400)
If you still have the same set up and a little extra time, I'd love to see the 2x and 4x cases with the get rate averaged over 2 and 4 pulses respectively rather than the default single pulse. Please use the corresponding JAG speeds as in the original data sets. In case you aren't familiar with this:

The 2x is set in the counter.cpp InitCounter() line :
m_counter->writeTimerConfig_AverageSize(1, &status);
by changing to
m_counter->writeTimerConfig_AverageSize(2, &status);

Similarly, the 4x is set in the encoder.cpp InitEncoder() line:
m_encoder->writeTimerConfig_AverageSize(1, &status);
by changing to
m_encoder->writeTimerConfig_AverageSize(4, &status);

This would help mitigate the rate noise if people insist on using 2x and 4x encoder modes. I would like this to be the default configurations. If you run the cases Ill add it to the statistical analysis and post it also. It would be a nice reference for future First teams making this decision.

Thanks for the info I didn't know about that. We just packed up everything for Atlanta, so I won't be able to test it until after.

Quote:

Originally Posted by vamfun (Post 953400)
By the way, last year Joe H and I had a thread on moving average : http://forums.usfirst.org/showthread...8143#post28143. Our team added a function to the Encoder.cpp that allows you to do this in your robot init code similar to SetDistancePerPulse(). Eventually, I hope the WPI guys incorporate this capability.
Code:

void SetMovingAveragePulseCount(int max_count)
{ //check if 1=<max_count<=127 TODO: should throw an error flag here with message if max_count invalid
if (max_count<1) max_count = 1;
else if (max_count>127) max_count = 127;
if(m_counter)
m_counter->m_counter->writeTimerConfig_AverageSize(max_count, &status); // Counter object within Encoder -> tCounter within Counter
else
m_encoder->writeTimerConfig_AverageSize(max_count, &status);
}


Do the WPI people know about it? I would hope they add it. Also they need to apply that patch I posted. I need to figure out who to send it to...

jhersh 13-04-2010 21:06

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by Bigcheese (Post 953437)
Do the WPI people know about it? I would hope they add it. Also they need to apply that patch I posted. I need to figure out who to send it to...

http://firstforge.wpi.edu/sf/tracker...wpilib/tracker

If you log in with a generic FIRST Forge user account, you can post bug reports.

-Joe

vamfun 13-04-2010 21:22

Re: Unexpected results from Encoder::GetRate()
 
Michael, I posted the rate statistics for segments of your data here.

http://www.chiefdelphi.com/forums/sh...519#post953519

Joe: Thanks for the info and good luck in Atlanta. Our factor of 2 GetPeriod() problem awaits your attention.

jhersh 13-04-2010 21:45

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by vamfun (Post 953520)
Joe: Thanks for the info and good luck in Atlanta.

Seeing the logic for the decoder do you now see why you can't leave the B line static?

Quote:

Originally Posted by vamfun (Post 953520)
Our factor of 2 GetPeriod() problem awaits your attention.

I'll be looking into it after Atlanta. Please report each issue you are aware of on FIRST Forge. It will help us and all of you keep track of each issue and the current status of each of them.

-Joe

vamfun 13-04-2010 23:36

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by jhersh (Post 953173)
"UpRisingEdge => count A if B is high"
"UpFallingEdge => count A if B is low"

Here you lost me. I would have been ok if you'd said
"DownRisingEdge => countup A if B is high"
"DownFallingEdge => countdown A if B is low"


Quote:

Here are the much anticipated logic equations used in the 1x and 2x quadrature decoding mode of the counters.


Count = (ARising | AFalling) & ((BHigh & UpRisingEdge) | (BLow & UpFallingEdge))
CountUp = Count & !(BHigh ^ DownRisingEdge ^ ARising)
CountDown = Count & (BHigh ^ DownRisingEdge ^ ARising)

With this, there will hopefully be no more mystery. :)
Ok, I'm going to think outloud and see if you agree I see the light.
Lets try an example for 1x: UpRisingEdge = true , DownRisingEdge=false
Assume B is static high so Bhigh=true
Now A channel gets a rising pulse so ARising = true , AFalling= false
Evaluate 1)
Count = true;
(BHigh ^ DownRisingEdge ^ ARising)=(true^false^true) = false;
so
CountUp = true & !(false) = true
CountDown = true&false = false
Hence we count up ....
Now A channel gets a falling pulse so Arising = false, AFalling = true
Evaluate 2)
Count = true:
(BHigh ^ DownRisingEdge ^ ARising)=(true^false^false) = true;
so
CountUp= true &!(true)=false
CountDown = true&true = true
Hence we count down ....

This repeats so with a stuck B channel, it oscillates.

In the case where B is not static , it inhibits the count when AFalling = true because Bhigh= false during that transition.

So I guess I'm with you.

Since this logic requires a transition to count 1x properly, it seems the code should throw a flag when the B is static since this is an invalid input. Otherwise why require it.

jhersh 14-04-2010 01:01

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by vamfun (Post 953591)
...So I guess I'm with you.

Excellent!

Quote:

Originally Posted by vamfun (Post 953591)
Since this logic requires a transition to count 1x properly, it seems the code should throw a flag when the B is static since this is an invalid input. Otherwise why require it.

What it is doing is by definition quadrature decoding. If it counted A without B transitioning, it would just be a simple counter, not be quadrature (which you can do if you select that mode on the counter open).

We shouldn't single out B being static... it's perfectly valid for the encoder to measure a change in direction as well as many changes in direction one after another.

-Joe

jhersh 14-04-2010 01:24

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by vamfun (Post 950613)
Joe, I would expect that for 2x and 4x but not 1x, since with 1x only A channel leading edge determines the count and the B channel is always the same value on the leading edge. Unless I'm missing something.

Quote:

Originally Posted by vamfun (Post 951043)
Ok, lets get specific. When we write our own interrupt encoder routines, the B channel is just looked at when a TEU pulse occurs on the A chan to get the direction. However, the tcounters in CRIO are a bit of a mystery to me still and I need some more tutoring.

I think the fundamental misunderstanding here is that what you are doing in your microcontroller-based 1x decoder is take a shortcut that is not a valid quadrature decoder.

To create a valid 1x decoder, you must be sensitive to a different edge of A based on the value of B, not always be sensitive to one edge of A and choose direction based on B. Naturally the correct way to implement this is not simple on a microcontroller because interrupt configuration cannot be tied to a digital input, so the mistake is often made because it is seen as the only option. The fact that it almost works only reinforces to the misunderstanding.

In some cases is it a concious decision to use this optimization (which has half the interrupt traffic that would be required to implement it correctly) with the acceptance and acknowledgement that it will generate invalid counts any time the direction changes.

-Joe

vamfun 14-04-2010 16:30

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by jhersh (Post 953616)
I think the fundamental misunderstanding here is that what you are doing in your microcontroller-based 1x decoder is take a shortcut that is not a valid quadrature decoder.

To create a valid 1x decoder, you must be sensitive to a different edge of A based on the value of B, not always be sensitive to one edge of A and choose direction based on B.

The scheme that I'm describing is a valid quadrature decoder in that it uses the B channel to determine direction. When going CW , the uprising A edge occurs when say B is High and when going CCW, the uprising A edge occurs when B is low. So when going a given direction, B is essentially static when looked at during the uprising edge.

Quote:

Naturally the correct way to implement this is not simple on a microcontroller because interrupt configuration cannot be tied to a digital input, so the mistake is often made because it is seen as the only option. The fact that it almost works only reinforces to the misunderstanding.
Seems simple to me... in the A rising edge interrupt handler, we just read B digital port immediately and either countup or countdown pending value. The only requirement is that B not be changing for those few useconds. It does give half the interrupt traffic so we have been using this with MPLAB Vex for years primarily to conserve the interrupt ports.

]

Alan Anderson 15-04-2010 23:09

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by vamfun (Post 953739)
The scheme that I'm describing is a valid quadrature decoder in that it uses the B channel to determine direction.

No, it's just a counter with a direction input. It happens to work with a quadrature encoder if you don't have "noisy" rotation. If the encoder happens to oscillate a bit back and forth at the A channel transition, you'll get false counts.

That exact situation happened on our "Triple Play" robot. We had quadrature encoders on miniature omniwheel-ish rollers to track our motion on the carpet. We could watch the wheels not spin appreciably while the code told us they were making several revolutions. I fixed it by implementing a true quadrature decoder. Something almost exactly like my code was independently developed by Kevin Watson and incorporated into his Encoder.c library for the IFI controller.

vamfun 16-04-2010 06:10

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by Alan Anderson (Post 954117)
No, it's just a counter with a direction input. It happens to work with a quadrature encoder if you don't have "noisy" rotation. If the encoder happens to oscillate a bit back and forth at the A channel transition, you'll get false counts.

That exact situation happened on our "Triple Play" robot. We had quadrature encoders on miniature omniwheel-ish rollers to track our motion on the carpet. We could watch the wheels not spin appreciably while the code told us they were making several revolutions. I fixed it by implementing a true quadrature decoder. Something almost exactly like my code was independently developed by Kevin Watson and incorporated into his Encoder.c library for the IFI controller.

I call it quadrature only since it is uses the quadrature information, even though it might be a weaker algorithm. Perhaps it takes some liberty with industry conventions.

You make a good point Alan, regarding the sensitive nature of my scheme to vibration that can cause oscillation about an edge. It will certainly do what you said... but it is the price that must be paid if you want to use fewer interrupts. If this is not a constraint, then clearly the use of both rising edge and falling edges will help as you discovered.

These are the mappings as I see them:
my 1x scheme
1x , 1 interrupt per cycle, sensitive to edge oscillation,
rate sensitive to rising edge phase errors of A channel
A (rising) B (low) increment
A (rising) B (high) decrement

2x , 2 interrupts per cycle, not sensitive to oscillations
rate sensitive to rising and falling edge phase errors of A channel
A (rising) B (low) increment
A (falling) B (high) increment
A (rising) B (high) decrement
A (falling) B (low) decrement

4x , 4 interrupts per cycle, not sensitive to oscillations
rate sensitive to rising and falling edge phase errors of both A and B channels

A (rising) B (low) increment
B (rising) A (high) increment
A (falling) B (high) increment
B (falling) A (low) increment
A (rising) B (high) decrement
B (rising) A (low) decrement
A (falling) B (low) decrement
B (falling) A (high) decrement

Making a 1x insensitive to oscillations could be done a number of ways:
One simple mapping is to enable count when B (low) and count when B(high)
This requires B transitions for counting.
So..
1x, 2 interrupts per cycle, not sensitive to oscillations
rate sensitive to rising edge phase errors of A channel
A (rising) B (high) increment , reset enable
A (falling) B (high) decrement, reset enable
A (rising) B (low) enable count
A (falling) B (low) enable count

Ill have to go back and review how Kevin did his. Its been a while.

vamfun 17-04-2010 19:13

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by vamfun (Post 954166)
Making a 1x insensitive to oscillations could be done a number of ways:
One simple mapping is to enable count when B (low) and count when B(high)
This requires B transitions for counting.
So..
1x, 2 interrupts per cycle, not sensitive to oscillations
rate sensitive to rising edge phase errors of A channel
A (rising) B (high) increment , reset enable
A (falling) B (high) decrement, reset enable
A (rising) B (low) enable count
A (falling) B (low) enable count

Ill have to go back and review how Kevin did his. Its been a while.

I took a look at Kevins code while watching the Einstein Final...
Congrats 67/177/294

In the same mapping style, it translates to:
A(falling) B(high)...................set Backward state
A(falling) B(low) ...................set Forward state
A(rising) B(low) Backward........decrement
A(rising) B(high) Forward.........increment

This works well to prevent edge oscillation error counts since it requires a B transition to count also.
Kevin used the single interrupt scheme on the first two encoder channels and this two interrupt scheme on the three and four channels. He had the foresight to know that sometimes you need fewer interrupts with high velocity encoders and more interrupts with high precision distance encoders.

Note to Joe H: For your 1x, I don't like your algorithm because the count oscillates when stuck with an oscillating A edge over a BHigh or Blow state. I believe this can really aggravate the GetRate() noise.

Code:

Count = (ARising | AFalling) & ((BHigh & UpRisingEdge) | (BLow & UpFallingEdge))
CountUp = Count & !(BHigh ^ DownRisingEdge ^ ARising)
CountDown = Count & (BHigh ^ DownRisingEdge ^ ARising)

Revisiting the 1x example with DownRisingEdge=false,UpRisingEdge= true this simplifies to:

CountUp = BHigh&ARising
CountDown = Bhigh&!Arising --> Bhigh&Afalling if counting.

You count up or down on the A edge transition if Bhigh so you oscillate between +1 and -1 increments. My 1x non-oscillating scheme and Kevin's will not do this. Mine will allow a single proper count in the normal direction and no change on the oscillatory reversal. Kevin's will not allow any count until B changes sign.

jhersh 20-04-2010 14:38

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by vamfun (Post 954610)
Note to Joe H: For your 1x, I don't like your algorithm because the count oscillates when stuck with an oscillating A edge over a BHigh or Blow state. I believe this can really aggravate the GetRate() noise.

...

You count up or down on the A edge transition if Bhigh so you oscillate between +1 and -1 increments. My 1x non-oscillating scheme and Kevin's will not do this. Mine will allow a single proper count in the normal direction and no change on the oscillatory reversal. Kevin's will not allow any count until B changes sign.

What's a proper count? If the lines did it, it gets decoded. It seems unreasonable for the decoder to make assumptions about what the sensor meant to encode. The way I've implemented it allows you to read the true position at any point independent of the rate calculation. I then address the concern you have for the rate measurement independently by ignoring any count that has changed direction for the purposes of calculating rate.

Because I'm using an FPGA, I can afford to implement the decoder correctly without regard for shortcuts in interrupt handling. The simple fact that your interrupt handler can't check the state of the B line at the exact instance that the A line edge is detected means that you have a race condition to count the wrong way anyway... and it's a pretty big hole with most interrupt handlers, especially on the PIC18F where you don't get hardware context saving and it's just generally a very slow processor. Without hardware support, you can't really trust the decoding in the face of noise.

-Joe

vamfun 20-04-2010 15:30

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by jhersh (Post 955993)
What's a proper count? If the lines did it, it gets decoded. It seems unreasonable for the decoder to make assumptions about what the sensor meant to encode. The way I've implemented it allows you to read the true position at any point independent of the rate calculation.

I suspect the accuracy of both approaches would be comparable, just have max errors occurring at different locations in the resolution cycle. On a change of direction, yours would be off by a count initially but be more accurate just prior to the next edge count. Mine would be more accurate on the initial reversal and off by a count just prior to the next count. Probably needs a simulation to verify.


Quote:

I then address the concern you have for the rate measurement independently by ignoring any count that has changed direction for the purposes of calculating rate

Here you have disconnected the rate from the position signal temporarily. This is why I like Kevin's or my approach .. since it doesn't require this and yet maintains the same accuracy if what I said above is true.



Quote:

Because I'm using an FPGA, I can afford to implement the decoder correctly without regard for shortcuts in interrupt handling
. No argument there.... just so we know what you are doing.
Please, please, please put these algorithms in the code documentation at the nearest opportunity. I don't want to spend the rest of my retirement slowly extracting this information on our CD threads:)

vamfun 23-04-2010 03:09

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Note to Joe H: For your 1x, I don't like your algorithm because the count oscillates when stuck with an oscillating A edge over a BHigh or Blow state. I believe this can really aggravate the GetRate() noise.

You count up or down on the A edge transition if Bhigh so you oscillate between +1 and -1 increments. My 1x non-oscillating scheme and Kevin's will not do this. Mine will allow a single proper count in the normal direction and no change on the oscillatory reversal. Kevin's will not allow any count until B changes sign.
Quote:

Originally Posted by jhersh View Post
What's a proper count? If the lines did it, it gets decoded. It seems unreasonable for the decoder to make assumptions about what the sensor meant to encode. The way I've implemented it allows you to read the true position at any point independent of the rate calculation.
Quote:

Originally Posted by vamfun (Post 956019)
I suspect the accuracy of both approaches would be comparable, just have max errors occurring at different locations in the resolution cycle. On a change of direction, yours would be off by a count initially but be more accurate just prior to the next edge count. Mine would be more accurate on the initial reversal and off by a count just prior to the next count. Probably needs a simulation to verify.

The more I think about this the stronger I believe that you are doing the wrong thing by decrementing on the reversal. The encoder knows it is the same edge so it is sure that it has not moved since the last edge. Before the reversal, the encoder knew that it was on a new edge so the increment was valid and warranted. Any other situation there is a 1 count uncertainty.

By allowing the oscillation in position, you are penalizing those who derive rate from position with unwarranted errors that you corrected for in your GetRate().


Quote:

Here you have disconnected the rate from the position signal temporarily. This is why I like Kevin's or my approach ..
As a user , I want the rate to be the time derivative of the position. Here the position is moving and the rate is not. In this case, the rate is correct because it recognizes that the encoder has not changed position and should have a zero rate.

It seems reasonable therefore to simply apply the same logic to the position as you apply to GetRate() and force the position to match the rate.



Aside: I was only partially correct a while back when I said that the x2 and x4 were not edge oscillation sensitive.... they will not increment continuously but they will oscillate between increment and decrement as the x1 case because of the way your algorithm works. Right?




Users who are listening... is this a bug in your minds?

jhersh 23-04-2010 17:10

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by vamfun (Post 957061)
The more I think about this the stronger I believe that you are doing the wrong thing by decrementing on the reversal. The encoder knows it is the same edge so it is sure that it has not moved since the last edge. Before the reversal, the encoder knew that it was on a new edge so the increment was valid and warranted. Any other situation there is a 1 count uncertainty.

I don't understand why introducing phase error between forward and reverse is preferable. I have an encoder on a shaft because I want a specific angular range on that shaft to be represented by a number in my software. Why would I want the range that a number represents to be different based on the direction that the shaft was last rotating? I believe the answer is that I would not.

Quote:

Originally Posted by vamfun (Post 957061)
By allowing the oscillation in position, you are penalizing those who derive rate from position with unwarranted errors that you corrected for in your GetRate().

I'm not clear on what algorithm you are using to compute the rate in software. Depending on what you are using, this may or may not be a moot point or have other issues. Please describe what you are using.

Quote:

Originally Posted by vamfun (Post 957061)
As a user , I want the rate to be the time derivative of the position. Here the position is moving and the rate is not. In this case, the rate is correct because it recognizes that the encoder has not changed position and should have a zero rate.

This is not an accurate statement. A rate is a difference in timing between two events. As such the first event can not have a "rate" associated with it because there is no reference as to when that motion began. That is why the first edge in a given direction can not calculate a rate.

-Joe

vamfun 23-04-2010 19:07

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by jhersh (Post 957277)
Why would I want the range that a number represents to be different based on the direction that the shaft was last rotating?

Simply to be more accurate for the edge oscillation case.
Your scheme and mine will both have 1 count ambiguity...it just occurs at different times. I am interested in the best estimate of angle not just the count. I am showing a preference to have the best estimate at edge reversal rather than a count later. I can do this because of B channel information.
Lets take a simple 1 deg resolution encoder. We start with 0 angle, rotate to 1.1 deg and reverse to .99 and continue back to .1 and eventually back to 0. Error = true - encoder
Angle..joe count/chris count... joe error/chris error

0.0..0/0....0/0
1.1..1/1... 0.1/0.1*****tie
0.9..0/1...0.9/-0.1*****chris wins
0.5..0/1...0.5/-0.5*****tie.
0.1..0/1....0.1/-0.9*****joe wins
0.0..0/0.....0/0******tie

So if we are oscillating in a band about the 1 deg edge [.9 to 1.1], my algorithm will give a better estimate of angle position on reversal and we are the same going forward past 1. My rate will be zero without special compensation when oscillating in band, yours needs compensation to avoid huge rates with high frequency small amplitude oscillations (see Edit). Likewise, if we oscillate about the 0 deg edge or any edge.

So the reason for introducing the hysteresis is to avoid the rate spikes associated with the edge oscillation without giving up anything on accuracy. I.e. both have the same maximum error , just shifted in the cycle.

Edit:
Refer back to the above example and assume a small oscillation about the 1 deg edge with .1 deg amplitude at 10 rad/s. The truth rate amplitude should be 1 deg/s. Mine will be 0 yours will be (1*10) deg/s. The error in rate estimate for mine will in general be A*w , yours will be (1-A)*w where w is the frequency (rps) and A is the angle displacement amplitude. The ratio between our errors (joe/chris) is roughly 1/A for small A. Therefore , very small amplitude vibrations can cause very large differences in accuracy. E.g. A =.01 ... your error can be 100 times mine. In absolute terms, my error will be equal to the truth amplitude (100%) , while yours will be 100%/A. This you know and that's why you compensated for this effect in GetRate(). Using the hysteresis scheme, this is automatically compensated for in the GetDistance() count hence no compensation is required in GetRate() if you just divide the count by the count period.


Quote:

I'm not clear on what algorithm you are using to compute the rate in software. Depending on what you are using, this may or may not be a moot point or have other issues. Please describe what you are using.
Lets assume its just (new-old)/dt without special filtering. If my dt spans multiple counts, then it is moot...but if I'm rocking on an edge like Alan mentioned...it can lead to trouble unless we do some compensation somewhere with your algorithm.


Quote:

This is not an accurate statement. A rate is a difference in timing between two events. As such the first event can not have a "rate" associated with it because there is no reference as to when that motion began. That is why the first edge in a given direction can not calculate a rate.
We have two events.... crossing same edge in forward and reverse directions.
-Joe

Alan Anderson 24-04-2010 12:33

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by vamfun (Post 957313)
Your scheme and mine will both have 1 count ambiguity...it just occurs at different times.

I don't believe that is true. Yours has the ambiguity; a true quadrature decoder does not.

Quote:

Lets take a simple 1 deg resolution encoder. We start with 0 angle, rotate to 1.1 deg and reverse to .99 and continue back to .1 and eventually back to 0. Error = true - encoder
Angle..joe count/chris count... joe error/chris error

0.0..0/0....0/0
1.1..1/1... 0.1/0.1*****tie
0.9..0/1...0.9/-0.1*****chris wins
0.5..0/1...0.5/-0.5*****tie.
0.1..0/1....0.1/-0.9*****joe wins
0.0..0/0.....0/0******tie
That range seems chosen especially to favor your scheme, and you ignored the fractional information that a quadrature encoder can provide. Depending on what "1 deg resolution" means, a quadrature decoder will give at least twice the resolution of an edge-triggered up/down counter, and can give four times as much.

You also left out all of the samples that would reveal that yours gives different values for the same position depending on where it came from. For measuring position, I do not believe hysteresis is a positive attribute.

vamfun 24-04-2010 14:35

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by Alan Anderson (Post 957496)
I don't believe that is true. Yours has the ambiguity; a true quadrature decoder does not.

Don't follow you here. All encoders have a resolution ambiguity. The 4x quadrature decoder is 1/4 of 1x but there still exists the ambiguity within the resolution deadband.



Good to hear from you Alan. I am primarily addressing the 1x decoding of a quadrature encoder. So we have two channels to make the best 1x estimate. We are using 1x to minimize the rate errors associated with extra edge phase noise found with 2x and 4x decoding. So please evaluate my comments in this regard. The 1 deg resolution is identical to the kit 360 count encoders.

The comparison is between Joe's 1x and my 1x. The example doesn't favor either but is designed to illustrate the differences in the two approaches, so it focuses on the edge reversal. If there isn't an edge reversal, then the two schemes are identical. You can start anywhere on the cycle... at the exact point where the edge reverses, Joe's error will have a max 1 count error (ambiguity) and mine will be zero. As the reversal continues toward the next edge, Joe's estimate gets better and mine gets worse. If you look at the example carefully, you will notice that the max distance error is the same for both and will not exceed the resolution.

If the Kevin W. algorithm was implemented as I discussed earlier in the thread , I believe it would produce the same output as my 1x case. So I thought I was arguing in your favor.

Quote:

You also left out all of the samples that would reveal that yours gives different values for the same position depending on where it came from. For measuring position, I do not believe hysteresis is a positive attribute.
In my example, I am 0 going up to 1.1 and 1 returning so this is exactly what is illustrated. But , I claim this is the best estimate you can make when a reversal on the same edge is detected. If at the reversal, I ask you where you are and you tell me that you are 1 count further away than just before the reversal... then I know you can do better than that. My algorithm lets you say that you are in the same position that created your last count, hence it gives a more accurate guess at that point. Continuing in the reverse direction , if you happen to move close to the next edge... I will be reporting a 1 count error while Joe would be spot on. Users of either scheme cannot claim any better accuracy than 1 count after an event has been registered. But, in the neighborhood of the reversal event I can claim better accuracy hence I report better rate performance in the presence of your oscillating wheel phenomenon which triggers that event. This is a very subtle point and that's why I'm belaboring it.

Let's nix that "hysteresis" word, since it only applies to same edge reversals not all reversals and may be confusing.

Alan Anderson 24-04-2010 19:59

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by vamfun (Post 957532)
Don't follow you here. All encoders have a resolution ambiguity. The 4x quadrature decoder is 1/4 of 1x but there still exists the ambiguity within the resolution deadband.

We obviously have a different understanding of the word "ambiguity". I'm using it to mean that a specific measured value can indicate different things. You seem to be using it as a synonym of "precision" instead.

Quote:

Let's nix that "hysteresis" word, since it only applies to same edge reversals not all reversals and may be confusing.
I thought you were only looking at transitions on one channel to determine when to change counts. With that assumption, it looks to me like "same edge reversals" are the only kind you would see.

vamfun 24-04-2010 21:04

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by Alan Anderson (Post 957624)
We obviously have a different understanding of the word "ambiguity". I'm using it to mean that a specific measured value can indicate different things. You seem to be using it as a synonym of "precision" instead.
I thought you were only looking at transitions on one channel to determine when to change counts. With that assumption, it looks to me like "same edge reversals" are the only kind you would see.

I have the same definition of ambiguity... I just see it as caused by the precision round off.

You are right.. they are the only kind... But the hysteresis is cleared up as soon as the next count comes in without another reversal. In a normal hysteresis, this doesn't happen. This is what I meant by possible confusion.


Do you still feel that the value should be the same going up as down for the 1x case as discussed in post 51? If so .. can you think of a counter example that illustrates your concerns with my logic? This goes for Joe too.

Alan Anderson 24-04-2010 22:38

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by vamfun (Post 957636)
But the hysteresis is cleared up as soon as the next count comes in without another reversal.

I don't see how. The last line in your table of states looks wrong -- in order for your scheme to return to 0, there has to have been a transition, right? The true quadrature decoding would see that transition and go to -1. Your values will continue to be off by one compared to a quadrature decoder when going in reverse. The term hysteresis seems perfectly appropriate to me.

Quote:

Do you still feel that the value should be the same going up as down for the 1x case as discussed in post 51? If so .. can you think of a counter example that illustrates your concerns with my logic? This goes for Joe too.
Yes, of course the values should be the same for a given position, no matter which direction one gets there from. I don't know what example you want to see countered.

vamfun 25-04-2010 01:04

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by Alan Anderson (Post 957659)
I don't see how. The last line in your table of states looks wrong -- in order for your scheme to return to 0, there has to have been a transition, right? The true quadrature decoding would see that transition and go to -1. Your values will continue to be off by one compared to a quadrature decoder when going in reverse.

Yes, thank you.. I forgot to decrement Joe. Here is a revised and expanded table:
1x Decoding example of a quadrature encoder
Lets take a simple 1 deg resolution encoder. We start with 0 angle, rotate to 1.1 deg and reverse to -.9 and then reverse again and go forward back to 0.
Error = true - encoder
Angle..joe count/chris count... joe error/chris error
fwd
0.0..0/0....0/0********tie
0.5..0/0....0/0********tie
1.0..1/1... 0/0 New edge event (joe and chris increment)****tie note:joe and chris in sync
1.1..1/1... 0.1/0.1*****tie
bkwd
1.0..0/1... 1/0 Same edge event (joe decrement, chris no) ****chris wins note:joe and chris out of sync by 1
0.9..0/1...0.9/-0.1*****chris wins
0.5..0/1...0.5/-0.5*****tie.
0.1..0/1....0.1/-0.9*****joe wins
0.0..-1/0....1.0/0 New edge event(joe and chris decrement) ******chris wins
-0.5..-1/0...0.5/-.5*****tie
-.9...-1/0....0.1/-.9*****joe wins
fwd
-.5... -1/0...0.5/-.5*****tie
0.0...0/0....0/0 Same edge event(joe increment,chris no) ***tie note: back in sync here
0.5...0/0.... 0/0********tie

Quote:

The term hysteresis seems perfectly appropriate to me.
Sort of... If joe and I are in sync going up, I will lag going down. But at each transition, I will have zero error. This is not typical of a hysteresis band which has either plus or minus error equal to half the band.

Quote:

Yes, of course the values should be the same for a given position, no matter which direction one gets there from. I don't know what example you want to see countered.
This type of comment doesn't help.. please refer to a flawed statement in my logic as you did above. I have corrected the table...and I see no reason to adopt joes algorithm since it is a less accurate predictor in position when an event occurs. At the time an event occurs, the best you can do is have zero error at that time... which is what my algorithm does. Anything better than this must rely on some future assumed behavior, e.g. moving to an angle where joe's error is zero and staying there. If all angles are equally likely, then mine would be the optimal estimator at the time the estimate is updated.

Here is another analogy: A climber must report his position to base camp as either (ground or top). The climber reaches the top of a mountain and reports (top) to base camp. He then turns around and takes one step down and then updates his position to base camp. What is his best estimate? Alan/joe might say..of course... he must report a ground position. I say no... he has knowledge that he is at the top on his last report and he that he is still there... so his best estimate must be his last reported position which is top.

vamfun 26-04-2010 03:22

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by vamfun (Post 957679)

This type of comment doesn't help.. please refer to a flawed statement in my logic as you did above. I have corrected the table...and I see no reason to adopt joes algorithm since it is a less accurate predictor in position when an event occurs. At the time an event occurs, the best you can do is have zero error at that time... which is what my algorithm does. Anything better than this must rely on some future assumed behavior, e.g. moving to an angle where joe's error is zero and staying there. If all angles are equally likely, then mine would be the optimal estimator at the time the estimate is updated.

Ok, allow me Alan. The flaw in the logic is what happens between events. I finally put my controls hat on instead of my optimal estimation hat. The added one unit of hysteresis with my algorithm does cause major headaches when trying to do closed loop feedback control of position. This is what was bothering Alan and Joe. If I had to control position, I would certainly choose Joe's algorithm since one can control to a sharp edge without phase lag. Although it is accurate, my algorithm creates a control dead zone with a width of + or -1 precision unit centered on the last event. That is a big price to pay for the improved rate noise from an oscillating edge.

So Joe has reached a compromise position by providing a GetDistance() that is control friendly but sensitive to oscillating edges and a GetRate() that is insensitive to oscillating edges.

I still need to study Kevin's solution since it appears to have the hysteresis in it.... yet Alan found it acceptable for his control problem. Perhaps Alan can describe what type of control they were doing at that time.

jhersh 26-04-2010 14:14

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by vamfun (Post 957313)
Your scheme and mine will both have 1 count ambiguity...it just occurs at different times. I am interested in the best estimate of angle not just the count.
...
Error = true - encoder

So if we are oscillating in a band about the 1 deg edge [.9 to 1.1], my algorithm will give a better estimate of angle position on reversal and we are the same going forward past 1.

Aside from the fact that your algorithm has a different value at the same position depending on the direction of approach, I must also point out that the mapping of "true to encoder" is arbitrary and is chosen to optimize for accuracy. What I mean by this is you have chosen that the code "1" represents true [1 .. 2) where as I would argue that the code "1" represents true [0.5 .. 1.5). Hence my algorithm is more accurate than yours given that definition.

Quote:

Originally Posted by vamfun (Post 957313)
We have two events.... crossing same edge in forward and reverse directions.

Again, I contend that the definition of speed is distance per time. Distance is the difference between two positions. If the encoder reverses directions, then the only thing I know about the distance is that it is somewhere between (0 .. 2) counts of the encoder. I effectively have no (known) distance. How, then, can I claim to compute a speed with no distance?

Therefore, I say again that I do not have two events... I have one. As such, I do not report an invalid rate. It is not a patch to the algorithm, it is fundamentally accurate and required.

-Joe

jhersh 26-04-2010 14:25

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by vamfun (Post 957679)
Here is another analogy: A climber must report his position to base camp as either (ground or top). The climber reaches the top of a mountain and reports (top) to base camp. He then turns around and takes one step down and then updates his position to base camp. What is his best estimate? Alan/joe might say..of course... he must report a ground position. I say no... he has knowledge that he is at the top on his last report and he that he is still there... so his best estimate must be his last reported position which is top.

Then I would say that my encoder ticks halfway up the mountain... anywhere above half way, and I am at the top (best estimate given 2 choices); anywhere below, and I'm at the ground.

Your algorithm would say ground when starting, then after crossing half way, you are at the top... then you come back down all the way to the ground, and you still report that you are at the TOP!

-Joe

jhersh 26-04-2010 14:30

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by vamfun (Post 957974)
Although it is accurate, my algorithm creates a control dead zone with a width of + or -1 precision unit centered on the last event. That is a big price to pay for the improved rate noise from an oscillating edge.

How can you call it accurate and then describe how it is inaccurate?

See post 58.

Quote:

Originally Posted by vamfun (Post 957974)
So Joe has reached a compromise position by providing a GetDistance() that is control friendly but sensitive to oscillating edges and a GetRate() that is insensitive to oscillating edges.

What did I compromise? It simply doesn't report invalid measurements.

-Joe

Alan Anderson 26-04-2010 15:12

Re: Unexpected results from Encoder::GetRate()
 
Joe figured out what the disconnect was.

A proper quadrature decoder has a sawtooth-shaped error. This is typical of any digital measurement of a continuous value. Usually, one chooses the interpretation of the actual value to be between the transitions rather than right at them. This places the "zero error" level midway up the sawtooth, yielding a quantization error of +/- half the precision. The hysterical scheme :) instead puts the "zero error" level at the bottom of the sawtooth when traveling in one direction, and at the top when traveling in the other. The quantization error is either between 0 and 1, or between 0 and -1. Such a scheme strikes me as less desireable in every way but one -- that one way being ease of implementation when the only tool is an inflexible edge detector.

Any scheme with a one-count lag when changing direction can not give the same answer at both the beginning and the end of a round trip. If it says you're at the bottom of the mountain when you start, and you go far enough for it to say you're at the top, it will still say you're at the top after you return to the bottom. Trying to measure the position at the transition is always problematic. Once you choose which side of the discontinuity you want to compare things on, the advantages of each scheme become much more clear...and I see no obvious advantages to the scheme with the lag on direction reversal.

vamfun 26-04-2010 16:39

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by jhersh (Post 958086)
Aside from the fact that your algorithm has a different value at the same position depending on the direction of approach, I must also point out that the mapping of "true to encoder" is arbitrary and is chosen to optimize for accuracy. What I mean by this is you have chosen that the code "1" represents true [1 .. 2) where as I would argue that the code "1" represents true [0.5 .. 1.5). Hence my algorithm is more accurate than yours given that definition.

I beg to differ again.... what is important is the relative accuracy and that is referenced to the new edge ticks. When a new edge is detected, a mark is made in the sand. You travel one tick to another new edge. You make a second mark in the sand. You know that you have traveled exactly one tick from your first mark. If you now reverse slightly and get a same edge indication... you will always say that you are at the 1st mark when indeed you know that you are not. Hence no matter what the relative scaling, when a reverse edge occurs... my algorithm will always be better than yours since its says I'm at the second mark which is indeed where I am.

So there is no uncertainty here... you are purposely causing an error of 1 tick but it is warranted because it signals to whomever is watching that you have reversed direction which allows tighter control. My algorithm lacks that signal so the control must be delayed until a new edge is detected. I believe that if a "direction" sense was outputted from a decoder with my algorithm, one could design a controller that would not suffer the hysteresis problem while not needing to cause an error on a same edge event.

I think we all agree that both algorithms will have a max error of 1 count and this will occur at different times. If you demand exact repeatability with angle, then Joe's is the way to go. However, it introduces large rate spikes when oscillating over an edge. Mine will always be 1 count different on the return trip, but it tolerates an oscillating edge and its control can never be tighter than +_1 one count.... unless modified to output a direction sense (TBD). With today's high resolution encoders, a 2 count error band might be very acceptable.

Quote:

Again, I contend that the definition of speed is distance per time. Distance is the difference between two positions. If the encoder reverses directions, then the only thing I know about the distance is that it is somewhere between (0 .. 2) counts of the encoder. I effectively have no (known) distance. How, then, can I claim to compute a speed with no distance?
With my algorithm, the "average" speed at a same edge is zero because the "known" distance between the last two edge events is zero no matter how long it took to get them.

jhersh 26-04-2010 16:55

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by jhersh (Post 957277)
I'm not clear on what algorithm you are using to compute the rate in software. Depending on what you are using, this may or may not be a moot point or have other issues. Please describe what you are using.

Quote:

Originally Posted by vamfun (Post 958133)
With my algorithm, the "average" speed at a same edge is zero because the "known" distance between the last two edge events is zero no matter how long it took to get them.

This is why I was interested in the algorithm you are using. If you are counting events between fixed times, then you can compute a rate with no events. If instead you are measuring the time between events (as the FPGA does), you must have monotonic events.

It also seems to me that your algorithm will give a false speed...

Lets say the true shaft is 0.4 when you make your first speed sample. You then move to 1.4 and then back to 0.4 to take your second speed measurement. Your encoder will have changed from 0 to 1, so your speed will report forward 1 tick / period. My algorithm will report 0 speed.

Am I missing something?

-Joe

Alan Anderson 27-04-2010 09:53

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by vamfun (Post 958133)
I beg to differ again.... what is important is the relative accuracy and that is referenced to the new edge ticks. When a new edge is detected, a mark is made in the sand. You travel one tick to another new edge. You make a second mark in the sand. You know that you have traveled exactly one tick from your first mark.

You don't know that. You know you have traveled at least one tick. You might have traveled almost two ticks. That's what the [1 .. 2) notation Joe used means.

Quote:

If you now reverse slightly and get a same edge indication... you will always say that you are at the 1st mark when indeed you know that you are not.
What you know is that you are between the first and second marks.

Quote:

Hence no matter what the relative scaling, when a reverse edge occurs... my algorithm will always be better than yours since its says I'm at the second mark which is indeed where I am.
You were at the second mark for a moment. Until another edge occurs, you don't know precisely where between the first and second marks you are.

Quote:

I think we all agree that both algorithms will have a max error of 1 count and this will occur at different times.
It is reasonable to consider the true quadrature algorithm to have a maximum error of a half count either side of zero. Again, this is typical of an optimum quantization process.

Quote:

If you demand exact repeatability with angle, then Joe's is the way to go. However, it introduces large rate spikes when oscillating over an edge.
What rate spikes? I think you missed the part where the FPGA doesn't compute a rate until two events have occurred in the same direction.





The number that a quadrature decoder yields does not represent an edge. It represents a region between edges. Does that help you understand the explanations here?

vamfun 27-04-2010 15:22

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by Alan Anderson (Post 958338)
You don't know that. You know you have traveled at least one tick. You might have traveled almost two ticks. That's what the [1 .. 2) notation Joe used means.

What you know is that you are between the first and second marks.

You were at the second mark for a moment. Until another edge occurs, you don't know precisely where between the first and second marks you
are.
The number that a quadrature decoder yields does not represent an edge. It represents a region between edges. Does that help you understand the explanations here?

Alan, all my comments are referenced to when an interrupt occurs. There is no uncertainty at that time except for what happens during the interrupt processing time. Again, I am focused on the relative accuracy and if you believe the new edge events and use them as a reference, the same edge event will cause a 1 cnt error with joes scheme and not mine.



Quote:

It is reasonable to consider the true quadrature algorithm to have a maximum error of a half count either side of zero. Again, this is typical of an optimum quantization process.
Yes, it is reasonable... then both algorithms would be better on an absolute scale. But when considering relative distances which you encounter with dead reckoning, won't there will always be the 1 count max error?

Quote:

Alan: What rate spikes? I think you missed the part where the FPGA doesn't compute a rate until two events have occurred in the same direction.
Quote:

Joe:This is why I was interested in the algorithm you are using. If you are counting events between fixed times, then you can compute a rate with no events. If instead you are measuring the time between events (as the FPGA does), you must have monotonic events.

It also seems to me that your algorithm will give a false speed...

Lets say the true shaft is 0.4 when you make your first speed sample. You then move to 1.4 and then back to 0.4 to take your second speed measurement. Your encoder will have changed from 0 to 1, so your speed will report forward 1 tick / period. My algorithm will report 0 speed.

Am I missing something?

In my mind , I was picturing a user derived rate on the GetDistance() output which rookie PID users might encounter using the WPI PIDController.cpp routine. I will post some discussion on Joe's example and propose a GetRate() for my algorithm a little later.

Alan Anderson 27-04-2010 22:21

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by vamfun (Post 958439)
Alan, all my comments are referenced to when an interrupt occurs. There is no uncertainty at that time except for what happens during the interrupt processing time.

When the interrupt occurs is not related to when the program wants to know what the decoded position value is. Even though there might be perfect certainty at that instant, at the time the value is actually used the certainty is not guaranteed.

Quote:

In my mind , I was picturing a user derived rate on the GetDistance() output which rookie PID users might encounter using the WPI PIDController.cpp routine.
Are you basing your preference for the "hysterical" scheme on naive rate calculations? You can put that concern aside, as the FPGA does not produce the rate spikes you were worried about.

vamfun 28-04-2010 04:09

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by Alan Anderson (Post 958575)
When the interrupt occurs is not related to when the program wants to know what the decoded position value is. Even though there might be perfect certainty at that instant, at the time the value is actually used the certainty is not guaranteed.

I agree, the error can be any value [0 to 1] when the program wants the data, and this is exactly why I qualify my statements to the interrupt time.

I think we all understand the problems associated with quantization and my characterization of the error is perhaps clouding the discussion a bit too much.

So here is a thumbnail of the problem:
The two algorithms simply take the high road or the low road to rectify the count on a return trip. Taking the low road (joe) rectifies the count immediately on start of return, where the high road(chris) delays rectification until the return trip is completed. Joe's leads, mine lags. Joe's conveys directional sense by creating a high but erroneous initial rate, mine does not. Joe's lead hurts if the return never occurs because of a reversal. Mine gets in trouble if the return occurs quickly since I delay the rectification count ,however, if there is a reversal, mine then creates a smoother transition since I never changed direction.
Mine creates delay which makes control more difficult. It creates a control dead zone of +_ 1 count.

The problems occur in both algorithms because we are trying to describe position and speed states with one variable. At the initial reversal, joe's is good at showing direction at the expense of accurate position. Mine is good at position, but lousy at direction.
The quadrature encoder gives us access to the B channel which allows us to know both position and direction on a same edge reversal. I'm thinking about creating a GetState() which outputs the position ,direction and speed states at the edge event. I suspect this would be close to using joes GetDistance(), GetRate() output pair , but slightly different algorithms.
I'll post more when I've done a little more thinking.








Quote:

Are you basing your preference for the "hysterical" scheme on naive rate calculations? You can put that concern aside, as the FPGA does not produce the rate spikes you were worried about.
Hmmm, I thought Joe's actual algorithm showed that differentiated GetDistance() outputs would be a problem with an oscillating edge and that the GetRate() would not. The naive rate calculations are used in the WPI PID routines so teams would have to be careful to source the rate from the GetRate() rather than the error function fed to the PID routine.

How did you guys use Kevin's type encoder algorithm that solved the oscillating edge problem? Was the encoder information used in a closed loop controller? Doesn't that algorithm use a "hysterical" scheme?

Alan Anderson 28-04-2010 10:24

Re: Unexpected results from Encoder::GetRate()
 
I'm confused. I'm getting the impression you don't understand how quadrature encoding works, but I can't believe that's the case.

Quote:

Originally Posted by vamfun (Post 958622)
So here is a thumbnail of the problem:
The two algorithms simply take the high road or the low road to rectify the count on a return trip. Taking the low road (joe) rectifies the count immediately on start of return, where the high road(chris) delays rectification until the return trip is completed. Joe's leads, mine lags.

The "Joe" algorithm doesn't lead; it tracks. The "Chris" algorithm is still off by one at the completion of the trip; it can't get back in synch until after the return trip is completed.

Quote:

Joe's conveys directional sense by creating a high but erroneous initial rate, mine does not.
Why do you keep saying this? Maybe Joe needs to recap the FPGA rate computation for you. It explicitly does not compute a rate on the change of direction.

Quote:

The problems occur in both algorithms because we are trying to describe position and speed states with one variable. At the initial reversal, joe's is good at showing direction at the expense of accurate position. Mine is good at position, but lousy at direction.
You are trying to describe position and speed with one variable for some reason I do not understand. Don't do that. The provided distance and rate values are two different things.

(And I think it's Joe's algorithm that accurately reflects the position, and it's your algorithm that has the inaccurate position issue, but we've been through that already.)

Quote:

How did you guys use Kevin's type encoder algorithm that solved the oscillating edge problem? Was the encoder information used in a closed loop controller? Doesn't that algorithm use a "hysterical" scheme?
Kevin Watson implemented two different schemes for reading quadrature encoders, based on the different features of hardware interrupts on the PIC input pins. I used a routine that would count up and down as the phase A signal changed state; the other could be fooled into continually incrementing or continually decrementing at the noisy edge. It was indeed used for closed loop control, with two encoders being read to determine robot heading based on the difference in their values. (A third encoder provided odometer information. It was read by the simpler routine because it did not suffer from noisy rotational motion.)

vamfun 28-04-2010 13:00

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by Alan Anderson (Post 958656)
I'm confused. I'm getting the impression you don't understand how quadrature encoding works, but I can't believe that's the case

Sorry for the confusion...Its funny, I know we have the exact same picture in mind, but choose to describe it so differently. So I will break the limit cycle in interest of your sanity and perhaps any readers and not try to re clarify.






Quote:

Kevin Watson implemented two different schemes for reading quadrature encoders, based on the different features of hardware interrupts on the PIC input pins. I used a routine that would count up and down as the phase A signal changed state; the other could be fooled into continually incrementing or continually decrementing at the noisy edge. It was indeed used for closed loop control, with two encoders being read to determine robot heading based on the difference in their values. (A third encoder provided odometer information. It was read by the simpler routine because it did not suffer from noisy rotational motion.)

Did your decoder also use B phase to halt the continuous counting similar to Kevin? If so wouldn't it have incorporated a type of hysteresis?

Probably at that time you did not have a need for special rate processing of your decoded heading unless heading rate was involved in your loop...right?

EricVanWyk 28-04-2010 14:07

Re: Unexpected results from Encoder::GetRate()
 
I really hope that I can end this thread with this post, but I'm not holding my breath.

Lets examine the Rate and Position functions separately - assume that selecting one implementation of Position does not necessitate selecting the matching implementation of Rate.

Position
JoeMethod -
. Error is bounded to [-.5, .5]
. Uncertainty is bounded to [-.5, .5]
. Position is repeatable (no hysteresis).
ChrisMethod -
. Error is bounded to [-.5, .5]
. Uncertainty is bounded to [-1.5, 1.5]
. Position is dependent on direction (hysteresis).

View the position as a circle divided into quadrants. A true quadrature decoder tells you which quadrant you are currently in. You then make the optimal assumption that you are right in the middle of that quadrant. Thinking in terms of assuming that you are on the border is non-optimal. Joe will always report the correct quadrant. You will be off by one half of the time, depending on which direction you are traveling.

As far as I'm concerned, that is a clear win towards using an actual quadrature encoder, such as the one currently implemented.

Arguments as to 'free hysteresis' are bunk as far as I'm concerned. I don't want features that belong in my controller sneaking into my sensor. Tell me what is happening, and I'll work with it from there.

Rate
JoeMethod GetRate
. Given an oscillating edge, no rate is reported
JoeMethod GetPosition+hand derivative
. Given an oscillating edge, the correct rate is reported for the time period.
ChrisMethod
. Given an oscillating edge, zero rate is reported.

These all stink in their own way. But, none of them report rate "spikes". I'd rather get the GetPosition delta at fixed time intervals as rate, if I could choose.


I'm not saying that every aspect of the FPGA is perfect, but I will assert that the encoder implementation is what I hoped it would be. If it was implemented per your suggestion, I'd be disappointed.

vamfun 29-04-2010 19:40

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by EricVanWyk (Post 958701)
I really hope that I can end this thread with this post, but I'm not holding my breath.

You can't get off that easy:)
Personally, I would like to see it end with Joe's report on the GetPeriod() problem. Your summary is a nice place to break on the algorithm discussion subject but I would feel better about the table with the following amendments:


Quote:

Position
JoeMethod -
ChrisMethod -
. Uncertainty is bounded to [-1.5, 1.5]
Uncertainty is bounded to [-1., 1.]


Quote:

Rate
. JoeMethod GetPosition+hand derivative
. Given an oscillating edge, the correct rate is reported for the time period.
JoeMethod GetPosition+hand derivative
. Given an oscillating edge, an amplified rate is reported for the time period.


I rationalize this as follows: If the true oscillating edge amplitude is A , it will always cause an average GetPosition() oscillation amplitude of 0.5. Since this is a single edge oscillation, A < .5 , so any rate derived will be amplified by a factor of .5/A. E.g. A = .05 will amplify by 10 the true rate. This is the main reason that the Joe rate method and mine are in the ball game is to prevent these amplifications from tainting the rate output.



Quote:

These all stink in their own way. But, none of them report rate "spikes". I'd rather get the GetPosition delta at fixed time intervals as rate, if I could choose.
One might characterize the above amplified rates as spikey behavior if viewed on a plot but its not important.

Quote:

I'm not saying that every aspect of the FPGA is perfect, but I will assert that the encoder implementation is what I hoped it would be. If it was implemented per your suggestion, I'd be disappointed.
I too would be disappointed if the current algorithms were not available since most of my control work would be happy with them.

What I would like, though is an option to modify the algorithms which I could do if the A/B interrupt count and timing was transparent. But Joe /Ni have elected to not make this code open source. We can write our own routines from scratch, but I would be satisfied if a GetEdgeState() function was implemented that provided edge count, direction and time_since_last_edge for the 1x case. This state captures all the information available from the A/B channels and could be easily modified to create whatever distance/rate algorithms you wanted. One could argue that these are available with GetRaw(), GetDirection() and GetPeriod() or their visible subfunctions.. but if called in sequence, I don't think they can guarantee to be synchronized to the same edge event. Maybe Joe can comment on this.

Alan Anderson 29-04-2010 22:30

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by vamfun (Post 959040)
JoeMethod GetPosition+hand derivative
. Given an oscillating edge, an amplified rate is reported for the time period.

I rationalize this as follows: If the true oscillating edge amplitude is A , it will always cause an average GetPosition() oscillation amplitude of 0.5. Since this is a single edge oscillation, A < .5 , so any rate derived will be amplified by a factor of .5/A. E.g. A = .05 will amplify by 10 the true rate. This is the main reason that the Joe rate method and mine are in the ball game is to prevent these amplifications from tainting the rate output.

I have no idea what you're trying to say here.

vamfun 30-04-2010 02:59

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by Alan Anderson (Post 959082)
I have no idea what you're trying to say here.

Here is pseudo simulation code that I would write to illustrate the error amplification
Code:

Param:
T = edge oscillation period
A  = edge oscillation amplitude
dt = T/20,  sample rate small enough to measure oscillation
variables:
t = time,
x = encoder input
y = encoder output
x_dot = encoder input rate
y_dot= encoder output rate

Run Simulation:
while(t < runtime){

Encoder input movement:

x = A*sin(2*pi*t/T); Small oscillation movement , A < 1 to be on same edge.

x_dot = A*2*pi/T*cos(2*pi*t/T)

Encoder output change when x is centered on a threshold:

If(x > 0 ) {y = .5 }  //sample
else {y = -.5 }

y_dot = (y-y_last)/dt  //Derive simple rate
y_last = y;
t= t + dt;
}

Expected Results:
The output amplitude will oscillate between -.5 and .5

The amplitude ratio y/x = .5/A.

peak |y_dot| = 1/dt,

average |y_dot| = 2/dt/(20)= 2/T Average over oscillation period



Then (avg |y_dot|)/(x_dot amplitude) = 1/(A*pi)
(this is slightly less than my .5/A approximation.)

jhersh 30-04-2010 11:51

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by vamfun (Post 959040)
What I would like, though is an option to modify the algorithms which I could do if the A/B interrupt count and timing was transparent. But Joe /Ni have elected to not make this code open source. We can write our own routines from scratch, but I would be satisfied if a GetEdgeState() function was implemented that provided edge count, direction and time_since_last_edge for the 1x case. This state captures all the information available from the A/B channels and could be easily modified to create whatever distance/rate algorithms you wanted. One could argue that these are available with GetRaw(), GetDirection() and GetPeriod() or their visible subfunctions.. but if called in sequence, I don't think they can guarantee to be synchronized to the same edge event. Maybe Joe can comment on this.

This is yet another demonstration that there has not been enough time for code or documentation. The whole point of the DMA engine is to provide you with information in a synchronous packet. I believe that with the Interrupt and DMA together you can accomplish everything you described or intended.

Here's how:

The DMA packet contains the position, direction, period, and "stalled" indicator (the configurable counter-inactivity timer). Each packet also includes a timestamp. You can configure DMA packets to be sent periodically or you can configure a digital source as the packet clock. If you use one of the encoder phases as the packet clock, then you can just use the timestamp in the packet for your timing. If you hook the packet clock to some unused digital output, you can request DMA packets on demand. You can also use periodic timing and use the distance and direction only. You can also hook one or both of the encoder phases to interrupts. This will not only notify your code, but also timestamp the event (using the same timestamp source as the DMA packets) so you can compare the timestamps of interrupts and DMA packets as needed.

That being said, the one that is built in for you is WAY easier to use so at least for me I would have to have a pretty big problem with that implementation to want to reimplement it on the RTOS using DMA and Interrupts. The reason for including DMA and Interrupts was to make the system be super flexible to support interesting sensor interface requirements.

BTW, you can get the same results that you get with your algorithm by simply adding 1 to the distance if direction is false with the built-in counter and you don't need any of that fancy stuff. But it will be worse! ;)

-Joe

jhersh 30-04-2010 11:54

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by vamfun (Post 959040)
You can't get off that easy:)
Personally, I would like to see it end with Joe's report on the GetPeriod() problem.

I haven't gotten around to evaluating what the problem is with that just yet. Perhaps if I'm bored this weekend I'll find some test hardware and dive into it.

-Joe

Alan Anderson 30-04-2010 14:28

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by vamfun (Post 959131)
Expected Results:
The output amplitude will oscillate between -.5 and .5

Ignoring for a moment the oddness of deciding that the measurement will always end in 0.5, that is the optimum result. It matches the resolution of the sensor. It's as good as it gets. You have no way of knowing whether the true amplitude is less than that, or greater than that (but less than +/-1).

Anyway, so far as I can tell, your proposal is worse than what you're pointing out here. Ignoring the first edge after a change in direction gives an unchanging output for any input oscillation amplitude less than 1, right?

vamfun 01-05-2010 14:54

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by jhersh (Post 959193)
BTW, you can get the same results that you get with your algorithm by simply adding 1 to the distance if direction is false with the built-in counter and you don't need any of that fancy stuff.

-Joe

This is along the lines I was thinking. But it seems that we have to capture the complete register array before we start using the results or at least temporarily halt the interrupts while we do multiple register reads.
I.E Wouldn't this be a possible problem?
Code:

*/
INT32 Counter::Chris_Get()

{      INT32 value ;

        INT32 cnt = m_counter->readOutput_Value(&status);
       
        bool dir = m_counter->readOutput_Direction(&status);
//Couldn't the registers be different when we read dir vs the cnt read ??


        if(dir )  // Assume dir = 0 decrements count
   
        { return value  ; } // if we haven't reversed then update count
        else
      {  return value +1; } // else keep the count at last value
     
}

Quote:

But it will be worse! ;
Beauty is in the eye of the beholder. I'm pretty happy with the money I'm saving with my hysteresis thermostat even thought the temperature varies a little. All I have to do is figure out a way to connect an encoder to a bi-metalic coil and then hook it to my heater control:)

jhersh 01-05-2010 16:42

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by vamfun (Post 959423)
This is along the lines I was thinking. But it seems that we have to capture the complete register array before we start using the results or at least temporarily halt the interrupts while we do multiple register reads.
I.E Wouldn't this be a possible problem?
Code:

*/
INT32 Counter::Chris_Get()

{   
        INT32 cnt = m_counter->readOutput_Value(&status);
       
        bool dir = m_counter->readOutput_Direction(&status);
//Couldn't the registers be different when we read dir vs the cnt read ??


        if(dir )  // Assume dir = 0 decrements count
   
        { return value  ; } // if we haven't reversed then update count
        else
      {  return value +1; } // else keep the count at last value
     
}


Yes... if you did it that way, you would have a small window for the register to change. However, that is why I put both in the same register. Same thing goes for the period, the count, and the stall information. All related; all in the same register.

If instead you did:

Code:

INT32 Counter::Chris_Get()
{
        //Now all the information is read in one register access!
        tCounter::tOutput output = m_counter->readOutput(&status);

        // dir = 0 decrements count
        if (output.Direction)
        {
                // if we haven't reversed then update count
                return output.Value;
        }
        else
        {
                // else keep the count at last value
                return output.Value + 1;
        }
     
}

Cheers!
-Joe

vamfun 02-05-2010 16:34

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by jhersh (Post 959442)
Yes... if you did it that way, you would have a small window for the register to change. However, that is why I put both in the same register. Same thing goes for the period, the count, and the stall information. All related; all in the same register.

If instead you did:

Code:

INT32 Counter::Chris_Get()
{
        //Now all the information is read in one register access!
        tCounter::tOutput output = m_counter->readOutput(&status);

        // dir = 0 decrements count
        if (output.Direction)
        {
                // if we haven't reversed then update count
                return output.Value;
        }
        else
        {
                // else keep the count at last value
                return output.Value + 1;
        }
     
}

Cheers!
-Joe

Thanks, that's what I was looking for. Duh, I finally found the tcounter.h files in Windriver Chipobject directory that describes the output data structure. Had I found those earlier, I could have figured a few more things out for myself.

I still would like to know the details of your GetRate() algorithm that keeps the rate from reporting on a same edge. Its not obvious where you do this.

Also, could you describe the 4x algorithm equations (like your description of the 1x and 2x earlier).

jhersh 02-05-2010 21:19

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by vamfun (Post 959637)
I still would like to know the details of your GetRate() algorithm that keeps the rate from reporting on a same edge. Its not obvious where you do this.

The rate calculations and the quadrature decoder are decoupled bits of logic. The "Event Timer" logic takes input from a generic event source... one of which is a quadrature decoder. The quadrature decoder will only indicate an event to the event timer if the direction that is decoded is the same as the previous direction. That's really all there is to it.

The sliding-window average engine that follows the event timer will reset its pipeline of samples on direction changes as well. This is because the rates are unsigned and averaging rates that refer to different directions without sign has no meaning. It could potentially be modified to be signed, but then we are duplicating direction information. Maybe that's a good thing. Thoughts?

-Joe

vamfun 04-05-2010 13:18

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by jhersh (Post 959732)
The rate calculations and the quadrature decoder are decoupled bits of logic. The "Event Timer" logic takes input from a generic event source... one of which is a quadrature decoder. The quadrature decoder will only indicate an event to the event timer if the direction that is decoded is the same as the previous direction.

.
Ok...I just have to remind myself that the m_counter object register really represents two counter results... output.value and output.count. They are related only when the output.dir is the same as previous dir. The GetPeriod() intro
Code:

/*
 * Get the Period of the most recent count.
 * Returns the time interval of the most recent count. This can be used for velocity calculations
 * to determine shaft speed.
 * @returns The period of the last two pulses in units of seconds.
 */
double Counter::GetPeriod()

really only truly applies to the event timer count not the value count.

So I think I'm clear on that now.

If I wanted to implement my own rate algorithm, I don't think I can determine if a same edge event has occurred by looking at your register information. I believe I would need a same edge flag added to your output register.

The only change I would make is to set the rate = zero at this event rather than no report at all. I think you are throwing out some of the A/B information available to you. When a reversal occurs, your rate will be 180 deg out of phase, rather than just 90. This is slightly more destabilizing and if rate is needed by a PID loop to stabilize an otherwise near neutral or unstable plant it will lead to a several count limit cycle which would probably approach the one generated by a hysteresis controller. Plus, your rate estimate will be biased in the presence of oscillations.

If you modifiy your GetRate() in the future, I would favor adding the zero rate and same edge flag features.

Quote:

The sliding-window average engine that follows the event timer will reset its pipeline of samples on direction changes as well. This is because the rates are unsigned and averaging rates that refer to different directions without sign has no meaning. It could potentially be modified to be signed, but then we are duplicating direction information. Maybe that's a good thing. Thoughts?
This question probably needs simulation testing but here is my gut reaction: I don't think this is a good thing. It probably won't matter if the default single pulse is maintained. But if a larger averaging window is truly needed to filter the noise, then your implementation has all the drawbacks of a low pass but few of the benefits. If there are no high frequencies in the signal, the pipeline will just cause phase lag. If there are high frequencies, the filter will be reset often giving a shorter effective averaging pipeline (raising filter band width)and lowering the effective noise reduction.

Perhaps this phenomenon was present last year when I did a couple of runs with different pulse averaging lengths and did not see much benefit. http://forums.usfirst.org/showpost.p...43&postcount=5

jhersh 07-05-2010 00:26

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by vamfun (Post 959637)
Also, could you describe the 4x algorithm equations (like your description of the 1x and 2x earlier).

Notation:
A => value of A channel currently
A' => value of A channel last sample
A + B => A XOR B
A | B => A OR B
A & B => A AND B
!A => NOT A

Equations:
Count = (A + A') | (B + B')
Count_Dir = A + B' + !Reverse
Count_Up = Count & Count_Dir
Count_Down = Count & !Count_Dir

(Reverse is a configuration register)

-Joe

jhersh 07-05-2010 03:01

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by vamfun (Post 960102)
Ok...I just have to remind myself that the m_counter object register really represents two counter results... output.value and output.count. They are related only when the output.dir is the same as previous dir. The GetPeriod() intro really only truly applies to the event timer count not the value count.

The only meaning that "Count" in the tTimerOutput register has is the number of samples that are included in the sum presented in "Period". It is simply for the purpose of computing the average while avoiding doing the division in the FPGA.

Quote:

Originally Posted by vamfun (Post 960102)
If I wanted to implement my own rate algorithm, I don't think I can determine if a same edge event has occurred by looking at your register information. I believe I would need a same edge flag added to your output register.

If you can actually read the registers fast enough to keep up with each edge (which you would need to do to take advantage of a "same edge" flag), then you can just look for the dir to change.

Quote:

Originally Posted by vamfun (Post 960102)
The only change I would make is to set the rate = zero at this event rather than no report at all.

If you modifiy your GetRate() in the future, I would favor adding the zero rate and same edge flag features.

I tend to agree that I should set the rate to 0 on a same edge. I'll look for a way to work that in for next year. As for the same edge flag, that info is not very useful and already available as stated above.

Quote:

Originally Posted by vamfun (Post 960102)
This question probably needs simulation testing but here is my gut reaction: I don't think this is a good thing. It probably won't matter if the default single pulse is maintained. But if a larger averaging window is truly needed to filter the noise, then your implementation has all the drawbacks of a low pass but few of the benefits. If there are no high frequencies in the signal, the pipeline will just cause phase lag. If there are high frequencies, the filter will be reset often giving a shorter effective averaging pipeline (raising filter band width)and lowering the effective noise reduction.

The average engine does have trouble at around 0 rate, which is why I suggested integrating the direction and not resetting the pipeline on direction change. When not at zero, it works great. Presumably in a lot of cases if you want zero rate, you can just turn off the motor, so it doesn't seem like a showstopper case.

Does anyone have a better idea for filtering noise from this kind of system than a sliding window average that doesn't reset at 0? Remember that we are HIGHLY space constrained so keep in mind that we need small and simple algorithms that are effective.

Thanks,
-Joe

Alan Anderson 07-05-2010 08:37

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by jhersh (Post 960727)
Presumably in a lot of cases if you want zero rate, you can just turn off the motor, so it doesn't seem like a showstopper case.

If you're trying to maintain zero rate, a simple way to do it is to maintain constant position.

jhersh 07-05-2010 14:00

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by Alan Anderson (Post 960747)
If you're trying to maintain zero rate, a simple way to do it is to maintain constant position.

Yes of course. :yikes:

vamfun 08-05-2010 01:59

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by jhersh (Post 960727)
I tend to agree that I should set the rate to 0 on a same edge. I'll look for a way to work that in for next year. As for the same edge flag, that info is not very useful and already available as stated above

.
Well, Ill have to work on you a little more. This flag would make the register a complete representation of the A/B state information which doesn't seem like a bad thing. I don't think a fast read would be practical solution.


Quote:

The average engine does have trouble at around 0 rate, which is why I suggested integrating the direction and not resetting the pipeline on direction change. When not at zero, it works great. Presumably in a lot of cases if you want zero rate, you can just turn off the motor, so it doesn't seem like a showstopper case.
I worry about a position servo case which drives to a reference and then holds zero rate. You may not be able to turn off a motor if there isn't sufficient braking torque to hold the controlled object.. ie a heavy manipulator arm.

Quote:

Does anyone have a better idea for filtering noise from this kind of system than a sliding window average that doesn't reset at 0? Remember that we are HIGHLY space constrained so keep in mind that we need small and simple algorithms that are effective.
Can you pipe the DIR flag into the pipeline so there would be 0's and 1's in the pipe line ? If so, couldn't you construct a recursive pipeline sum by summing the 1's in the pipeline and then computing a signed_sum = 2*(sum of 1's) - max_count. Then in GetRate() compute rate = (signed_sum )/period rather than 1/(period/max count). The summation would take a little extra time... not sure what the FPGA could do to optimize this.

The other alternative would be to use a IIR exponential filter. This would weight the latest pulse heavier than prior pulses. I would prefer the FIR pipeline filter over this but it might work fine.

jhersh 08-05-2010 15:33

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by vamfun (Post 960905)
.
Well, Ill have to work on you a little more. This flag would make the register a complete representation of the A/B state information which doesn't seem like a bad thing. I don't think a fast read would be practical solution.

My general feeling about indicators that only stick around for one tick of something is that they are basically useless in a register interface. I designed the interfaces to isolate the timing of the measurement from the code that needs it, meaning the code can stop by any time it wants and ask for a value. What you are proposing would only be set for one tick of the encoder, and the information is already available as the change in value of dir. If you look for a change in value of dir, you can get that information any time you read. If you are looking at your proposed same edge flag, then you have to read before the next edge or you've lost the information. Hence my statement that you would have to read fast enough to catch every edge in order to make use of such a bit.

Perhaps I could make the same edge event set the stalled bit immediately, since a same edge represents a stop (except in the ridiculous noise case).

Quote:

Originally Posted by vamfun (Post 960905)
I worry about a position servo case which drives to a reference and then holds zero rate. You may not be able to turn off a motor if there isn't sufficient braking torque to hold the controlled object.. ie a heavy manipulator arm.

As Alan stated, use a position controller, which doesn't use the rate output, it uses the position output.

Quote:

Originally Posted by vamfun (Post 960905)
Can you pipe the DIR flag into the pipeline so there would be 0's and 1's in the pipe line ? If so, couldn't you construct a recursive pipeline sum by summing the 1's in the pipeline and then computing a signed_sum = 2*(sum of 1's) - max_count. Then in GetRate() compute rate = (signed_sum )/period rather than 1/(period/max count). The summation would take a little extra time... not sure what the FPGA could do to optimize this.

What is the benefit to this? Just cancelling out one set of rates for each dir change?

-Joe

vamfun 08-05-2010 16:59

Re: Unexpected results from Encoder::GetRate()
 
[quote]
Quote:

Originally Posted by jhersh (Post 960992)
If you are looking at your proposed same edge flag, then you have to read before the next edge or you've lost the information. Hence my statement that you would have to read fast enough to catch every edge in order to make use of such a bit.

This is like trying to decode the encoder without interrupts using fast reads...just seems you couldn't do it reliably.

Quote:

Perhaps I could make the same edge event set the stalled bit immediately, since a same edge represents a stop (except in the ridiculous noise case).
Thats an idea.. or maybe make DIR flag a two bit output... the first bit is DIR, the second is LAST_DIR



Quote:

As Alan stated, use a position controller, which doesn't use the rate output, it uses the position output.
This of course depends on the dynamics of the open loop plant. If there is sufficient loop lag, then usually a rate term is required to place the closed loop poles at the optimum values.

Quote:

Chris...Can you pipe the DIR flag into the pipeline so there would be 0's and 1's in the pipe line ? If so, couldn't you construct a recursive pipeline sum by summing the 1's in the pipeline and then computing a signed_sum = 2*(sum of 1's) - max_count. Then in GetRate() compute rate = (signed_sum )/period rather than 1/(period/max count). The summation would take a little extra time... not sure what the FPGA could do to optimize this.
Quote:

Joe...What is the benefit to this? Just cancelling out one set of rates for each dir change?
The signed_sum is just the net count (net position change) over the period. Isn't this what you want for a moving average that isn't zero reset?

jhersh 12-07-2010 05:35

Re: Unexpected results from Encoder::GetRate()
 
1 Attachment(s)
I finally took the time to look into this issue. I found the source of the mysterious factor of 2 that was introduced for the patch. It's caused by the fact that the "Period" variable for TimerOutput is a fixed point number that is not decoded when TimerOutput is read as a complete structure... only when read from the direct accessor. That means the decoding must be done in the driver. Since the fixed point format is 24 bits with 25 integer bits, the data must be shifted by 1 bit position... the factor of two!

I've attached the final patch that went in for those who are curious.

-Joe

vamfun 12-07-2010 18:09

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by jhersh (Post 968780)
I finally took the time to look into this issue. I found the source of the mysterious factor of 2 that was introduced ....

I've attached the final patch that went in for those who are curious.

-Joe

Thanks Joe, you done well.

A few questions/comments:
1) I see you took my suggestion for the x1,x2 and x4 averaging pulse count. Did you take a look to see if this really helped the rate noise increase with the x2 and x4 configs? I sort of have mixed feelings on this now. It should sure help if all the pulses are within a single reference frame, but would be less effective if averaging pulses which are part of different frames.
I still would like you to add a SetMovingAveragePulseCount(int max_count) procedure to allow user control of this.

2)Did you set the rate = zero at a same edge event rather than no report at all in your subcode? If not is this in your plans?

3)Did you try to implement the signed pipeline as I suggested so you don't have to reset the pipeline at zero crossings http://www.chiefdelphi.com/forums/sh...5&postcount=85 ?

jhersh 14-07-2010 11:33

Re: Unexpected results from Encoder::GetRate()
 
Quote:

Originally Posted by vamfun (Post 968841)
1) I see you took my suggestion for the x1,x2 and x4 averaging pulse count. Did you take a look to see if this really helped the rate noise increase with the x2 and x4 configs? I sort of have mixed feelings on this now. It should sure help if all the pulses are within a single reference frame, but would be less effective if averaging pulses which are part of different frames.

Absolutely. At least for the encoder I was using, it made an incredible difference. Both phase noise and high vs. low time noise (slit width). It's essentially mandatory unless you know you have a high precision encoder. Without it, the measurements were very noisy... around 30% variation for my encoder.

As far as your concern about frames, an encoder doesn't have "frames". The pulses are from one slit to the next and are typically pretty uniform (more so than the A-B phase or the slit-spoke ratio). I think this works just great.

Quote:

Originally Posted by vamfun (Post 968841)
I still would like you to add a SetMovingAveragePulseCount(int max_count) procedure to allow user control of this.

I agree. It's always been my intention to have this configurable by the user, I just never got around to adding the function to expose it. Fortunately the rest of the code can handle when the value is changed, so last year the worst a team would have to do is hack in their own little accessor. It's on my list of things to do this year.

Quote:

Originally Posted by vamfun (Post 968841)
2)Did you set the rate = zero at a same edge event rather than no report at all in your subcode? If not is this in your plans?

This would be an FPGA change... I'm looking into it. Setting the rate to 0 would involve setting the count to 0. This is directly related to #3.

Quote:

Originally Posted by vamfun (Post 968841)
3)Did you try to implement the signed pipeline as I suggested so you don't have to reset the pipeline at zero crossings http://www.chiefdelphi.com/forums/sh...5&postcount=85 ?

I have not tried it yet, but I have looked at the code and have a good idea of how it would fit in. Essentially, I would add a sign bit to the count output, and instead of resetting the count to 1 on a direction change, I would just set the direction as the sign bit. This way if you change direction, the counts cancel out and gives you 0 (from #2 above). I believe this is the signed sum behavior you were looking for.

It also means that the direction is available in this TimerOutput register as well as the position output register. We would no longer need to access the position register to dermine the direction as we do today. I still need to decide whether to reduce the range of the timer or the resolution to get the bit I need for the sign bit.

-Joe

Ether 16-04-2012 20:27

Re: Unexpected results from Encoder::GetRate()
 


Joe, Vamfun: could you guys please update the status of the items bolded below? If there's an update somewhere else, my apologies, I could not find it. Thanks.


Posted by vamfun:
I still would like you to add a SetMovingAveragePulseCount(int max_count) procedure to allow user control of this.


Response by jhersh:
I agree. It's always been my intention to have this configurable by the user, I just never got around to adding the function to expose it. Fortunately the rest of the code can handle when the value is changed, so last year the worst a team would have to do is hack in their own little accessor. It's on my list of things to do this year.


Posted by vamfun:
2)Did you set the rate = zero at a same edge event rather than no report at all in your subcode? If not is this in your plans?


Response by jhersh:
This would be an FPGA change... I'm looking into it. Setting the rate to 0 would involve setting the count to 0. This is directly related to #3.


Posted by vamfun:
3)Did you try to implement the signed pipeline as I suggested so you don't have to reset the pipeline at zero crossings http://www.chiefdelphi.com/forums/sh...5&postcount=85 ?


Response by jhersh:
I have not tried it yet, but I have looked at the code and have a good idea of how it would fit in. Essentially, I would add a sign bit to the count output, and instead of resetting the count to 1 on a direction change, I would just set the direction as the sign bit. This way if you change direction, the counts cancel out and gives you 0 (from #2 above). I believe this is the signed sum behavior you were looking for.

It also means that the direction is available in this TimerOutput register as well as the position output register. We would no longer need to access the position register to determine the direction as we do today. I still need to decide whether to reduce the range of the timer or the resolution to get the bit I need for the sign bit.





All times are GMT -5. The time now is 12:29.

Powered by vBulletin® Version 3.6.4
Copyright ©2000 - 2017, Jelsoft Enterprises Ltd.
Copyright © Chief Delphi