![]() |
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() |
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? |
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 |
Re: Unexpected results from Encoder::GetRate()
Quote:
Quote:
|
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()Code:
value * 1.0e-6 / (DecodingScaleFactor() * 4)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. |
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 |
Re: Unexpected results from Encoder::GetRate()
Quote:
|
Re: Unexpected results from Encoder::GetRate()
Quote:
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. |
Re: Unexpected results from Encoder::GetRate()
Quote:
|
Re: Unexpected results from Encoder::GetRate()
Quote:
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. |
Re: Unexpected results from Encoder::GetRate()
Quote:
It is hard to envision that the encoder could be in error if GetDistance() is working, so I vote for the code. |
Re: Unexpected results from Encoder::GetRate()
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. |
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.
|
Re: Unexpected results from Encoder::GetRate()
Quote:
Quote:
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. |
Re: Unexpected results from Encoder::GetRate()
Quote:
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. |
Re: Unexpected results from Encoder::GetRate()
Quote:
Code:
UINT32 timeCurr = GetFPGATime(); // in usec |
Re: Unexpected results from Encoder::GetRate()
Quote:
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 %. |
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 |
Re: Unexpected results from Encoder::GetRate()
Quote:
Quote:
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. |
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. |
Re: Unexpected results from Encoder::GetRate()
Quote:
|
Re: Unexpected results from Encoder::GetRate()
Quote:
|
Re: Unexpected results from Encoder::GetRate()
Quote:
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). |
Re: Unexpected results from Encoder::GetRate()
Quote:
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? |
Re: Unexpected results from Encoder::GetRate()
Quote:
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. |
Re: Unexpected results from Encoder::GetRate()
Quote:
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? |
Re: Unexpected results from Encoder::GetRate()
Quote:
Quote:
I will do this today if I can get into the school. |
Re: Unexpected results from Encoder::GetRate()
Ok, I finally have good data. First, I edited Encoder.cpp, here's the diff.
Code:
Index: Encoder.cpphttps://spreadsheets.google.com/ccc?...0JZe UE&hl=en Looks perfect to me :) |
Re: Unexpected results from Encoder::GetRate()
Quote:
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. |
Re: Unexpected results from Encoder::GetRate()
Quote:
|
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) |
Re: Unexpected results from Encoder::GetRate()
Quote:
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:
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 |
Re: Unexpected results from Encoder::GetRate()
Quote:
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) |
Re: Unexpected results from Encoder::GetRate()
Quote:
Quote:
Quote:
|
Re: Unexpected results from Encoder::GetRate()
Quote:
If you log in with a generic FIRST Forge user account, you can post bug reports. -Joe |
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. |
Re: Unexpected results from Encoder::GetRate()
Quote:
Quote:
-Joe |
Re: Unexpected results from Encoder::GetRate()
Quote:
"DownRisingEdge => countup A if B is high" "DownFallingEdge => countdown A if B is low" Quote:
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. |
Re: Unexpected results from Encoder::GetRate()
Quote:
Quote:
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 |
Re: Unexpected results from Encoder::GetRate()
Quote:
Quote:
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 |
Re: Unexpected results from Encoder::GetRate()
Quote:
Quote:
] |
Re: Unexpected results from Encoder::GetRate()
Quote:
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. |
Re: Unexpected results from Encoder::GetRate()
Quote:
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. |
Re: Unexpected results from Encoder::GetRate()
Quote:
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 = 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. |
Re: Unexpected results from Encoder::GetRate()
Quote:
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 |
Re: Unexpected results from Encoder::GetRate()
Quote:
Quote:
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:
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:) |
Re: Unexpected results from Encoder::GetRate()
Quote:
Quote:
Quote:
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:
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? |
Re: Unexpected results from Encoder::GetRate()
Quote:
Quote:
Quote:
-Joe |
Re: Unexpected results from Encoder::GetRate()
Quote:
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:
Quote:
-Joe |
Re: Unexpected results from Encoder::GetRate()
Quote:
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. |
Re: Unexpected results from Encoder::GetRate()
Quote:
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:
Let's nix that "hysteresis" word, since it only applies to same edge reversals not all reversals and may be confusing. |
Re: Unexpected results from Encoder::GetRate()
Quote:
Quote:
|
Re: Unexpected results from Encoder::GetRate()
Quote:
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. |
Re: Unexpected results from Encoder::GetRate()
Quote:
Quote:
|
Re: Unexpected results from Encoder::GetRate()
Quote:
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:
Quote:
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. |
Re: Unexpected results from Encoder::GetRate()
Quote:
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. |
Re: Unexpected results from Encoder::GetRate()
Quote:
Quote:
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 |
Re: Unexpected results from Encoder::GetRate()
Quote:
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 |
Re: Unexpected results from Encoder::GetRate()
Quote:
See post 58. Quote:
-Joe |
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. |
Re: Unexpected results from Encoder::GetRate()
Quote:
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:
|
Re: Unexpected results from Encoder::GetRate()
Quote:
Quote:
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 |
Re: Unexpected results from Encoder::GetRate()
Quote:
Quote:
Quote:
Quote:
Quote:
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? |
Re: Unexpected results from Encoder::GetRate()
Quote:
Quote:
Quote:
Quote:
|
Re: Unexpected results from Encoder::GetRate()
Quote:
Quote:
|
Re: Unexpected results from Encoder::GetRate()
Quote:
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:
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? |
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:
Quote:
Quote:
(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:
|
Re: Unexpected results from Encoder::GetRate()
Quote:
Quote:
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? |
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. |
Re: Unexpected results from Encoder::GetRate()
Quote:
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:
Quote:
. 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:
Quote:
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. |
Re: Unexpected results from Encoder::GetRate()
Quote:
|
Re: Unexpected results from Encoder::GetRate()
Quote:
Code:
Param: 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.) |
Re: Unexpected results from Encoder::GetRate()
Quote:
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 |
Re: Unexpected results from Encoder::GetRate()
Quote:
-Joe |
Re: Unexpected results from Encoder::GetRate()
Quote:
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? |
Re: Unexpected results from Encoder::GetRate()
Quote:
I.E Wouldn't this be a possible problem? Code:
*/Quote:
|
Re: Unexpected results from Encoder::GetRate()
Quote:
If instead you did: Code:
INT32 Counter::Chris_Get()-Joe |
Re: Unexpected results from Encoder::GetRate()
Quote:
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). |
Re: Unexpected results from Encoder::GetRate()
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? -Joe |
Re: Unexpected results from Encoder::GetRate()
Quote:
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:
/*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:
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 |
Re: Unexpected results from Encoder::GetRate()
Quote:
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 |
Re: Unexpected results from Encoder::GetRate()
Quote:
Quote:
Quote:
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. Thanks, -Joe |
Re: Unexpected results from Encoder::GetRate()
Quote:
|
Re: Unexpected results from Encoder::GetRate()
Quote:
|
Re: Unexpected results from Encoder::GetRate()
Quote:
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:
Quote:
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. |
Re: Unexpected results from Encoder::GetRate()
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). Quote:
Quote:
-Joe |
Re: Unexpected results from Encoder::GetRate()
[quote]
Quote:
Quote:
Quote:
Quote:
Quote:
|
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 |
Re: Unexpected results from Encoder::GetRate()
Quote:
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 ? |
Re: Unexpected results from Encoder::GetRate()
Quote:
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:
Quote:
Quote:
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 |
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