Unexplainable capture inconsistencies

Go To Last Post
7 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

First, a link to the code:

 

https://github.com/nsayer/GPS_cl...

 

It's a little difficult to explain this. I'm not asking for the forum to just debug my code for me (well, I guess maybe I am), but I've had this issue for a few days now and cannot explain it.

 

TCC4+C5 are set up to be a 32 bit cascaded counter with capture The C4 clock is DIV1 and the C5 clock is event 4 (the overflow from C4). Event 0 is a PPS rising edge from a GPS module connected to pin C0. On the scope the edges are crisp and sharp. I would not expect any noise issue there. CCA for C5 results in a medium priority interrupt where the capture is read.

 

Outside the ISR, there is a need to read the current timer value. I have an inline function defined to do this which strobes event channel 1, waits for CCBIF to be set on both C4 and C5 and then reads the value.

 

Note that both in the capture ISR and in the place where the timer value is read are in ATOMIC_BLOCK(ATOMIC_RESTORESTATE) blocks. I've looked at the resulting assembly code and it's doing the right thing with that regard.

 

Here's the part I can't figure out. In the ATOMIC_BLOCK in MAIN, I obtain the value of "last_pps_tick" (that variable is declared volatile). That's where the capture ISR stores the captured timer value. That same ATOMIC BLOCK also fetches the current timer value. In some cases, the current timer value ("now") is LESS than the captured value, and the subtraction stored in current_tick winds up being a negative number.

 

None of that makes the least bit of sense. In the initialization code I set EV_DELAY on timer C5 to account for the overflow propagation delay - just like the Atmel application notes talk about. I've tried everything I can think of. The deltas between adjacent captures in the ISR are sensible - they are showing that the 32 MHz clock is running about 0x1000 ticks slow, which is about 128 ppm - inside of the FLL spec given the 32 kHz internal reference. The delta between adjacent calls to timer_value() show differences of around 0x50, which seems reasonable to me. Swapping capture channels A and B around makes no difference.

 

The mystery is complete.

Last Edited: Fri. Jun 30, 2017 - 05:51 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

In playing more with it, it appears that clearing CCxIF immediately after reading CCx makes this problem go away. It would make sense that if, for instance, reading the current timer value didn't clear the capture buffer that the next time you read the timer value, you'd get its value as it was *last* time.

 

But this still leaves me with an inconsistency: the manual states that reading CCx will clear CCxIF. This appears to not be universally true. Is there an errata for the XMega32E5 concerning this? Or is it still something wrong in the code?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You say

In some cases, the current timer value ("now") is LESS than the captured value, and the subtraction stored in current_tick winds up being a negative number.

but current_tick is an unsigned long which can't be negative.  When this value is incorrect is it consistent with the previous value of CCB?

How often is this happening?  Your timer should overflow every 134 seconds (if my math is correct) and it is okay to just take the difference when this overflow occurs, so you should still get the correct difference value.  

If you have a debugger, just put some break points in before you read CCx and see if the corresponding flags clear.  It should be pretty straight forward to verify this behavior.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

When I say current_tick is a negative number, I mean that if you treat it as signed it would be negative. In actual fact, of course, it winds up being a very large positive number simply because it can't help but be. In general, you do not expect that subtraction to result in the 31 bit being a 1, certainly.

I don't have a debugger set up, but I was able to observe that the values of CCAIF and CCBIF on TC4 remained set after the read took place.

The current code has the workaround forcing CCxIF off immediately after the read. Along with the "WTF" comment, of course.

Last Edited: Sun. Jul 2, 2017 - 03:29 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Wasn't trying to be brash, but don't have a lot of time to write carefully.  Could you write some test code to verify this?  It appears that you have your timer configured correctly.  I am suspicious since the comment on how CCxIF is cleared in the datasheet appears to be copied across all manuals and the timers on this micro are different.  If the flag is still set and you read the value again is it the same value?  There is a buffer on the input capture and I was wondering if it is being double triggered somehow.  It would be good to keep reading CCx value and see if the flag is eventually cleared and if the value is changing at all.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I've written test code that validates that CCxIF isn't clearing on timer 4 after reading CCx. You've got a good point about figuring out whether the value changes. I can probably write some test code to do that.

 

In the case of the timer_value() version of it, I'd really have to wonder how that could be being double-triggered. After all, I'm just hitting EVSYS.STROBE for channel 1 and then waiting for both CCBIF flags to turn true and then doing the read.

 

I did try at one point setting up event noise filtering on event channel zero (rising edge of pin C0 - the PPS input), but nothing changed (about the behavior).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 

Well, this just makes no sense at all.

 

ISR(TCC5_CCA_vect) {
        unsigned long this_tick;
        ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
                delta = 0;
                while(!((TCC4.INTFLAGS & TC4_CCAIF_bm) && (TCC5.INTFLAGS & TC5_CCAIF_bm))) ; // wait for both words
                this_tick = (((unsigned long)TCC5.CCA) << 16) | TCC4.CCA;
                delta += (TCC4.INTFLAGS & TC4_CCAIF_bm)?1:0;
                delta += (TCC5.INTFLAGS & TC5_CCAIF_bm)?10:0;
#if 1
                unsigned long read2 = (((unsigned long)TCC5.CCA) << 16) | TCC4.CCA;
#endif
                delta += (TCC4.INTFLAGS & TC4_CCAIF_bm)?1:0;
                delta += (TCC5.INTFLAGS & TC5_CCAIF_bm)?10:0;
                //TCC4.INTFLAGS = TC4_CCAIF_bm; // XXX Why is this necessary?
                //TCC5.INTFLAGS = TC5_CCAIF_bm;
        }

 

with the "#if 1" changed to "#if 0" the value of delta is 2. With the #if 1 as-is, the value is ZERO!

 

In other words, the presence of the second read causes the first one to be effective at clearing CCAIF.

 

What horrible witchcraft is this?!

 

P.s. this is consistent with -Os -g or with just -g.

 

P.p.s. Both reads have the same value.

Last Edited: Mon. Jul 3, 2017 - 02:27 AM