Xmega Timer Overflow Accuracy

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

I am new here, so let me know if this is in the wrong place.

I am developing on a custom xmega board with an ATXMEGA256A3BU and I have an A3BU Xplained that duplicates this issue.  Two timers are configured for overflow interrupt and a global counter is incremented in the interrupt.  One increments at 1 kHz and another runs at 100 kHz (system clock is 32 MHz).  I have verified that I am not missing interrupts and I am seeing an issue where the 100 kHz timer is running significantly slower than the 1 kHz timer.  Code says a thousand words, so this.  Oh, and the result is this:

    _ms_timer    60534       unsigned long{data}@0x2192

ten_us_timer    6043996    unsigned long{data}@0x218e

 

 

 

// Timer setup

    TCC1.CTRLB=(0<<TC1_CCBEN_bp) | (0<<TC1_CCAEN_bp) | TC_WGMODE_NORMAL_gc;
        // Set Timer/Counter in Normal mode
    TCC1.CTRLE=TC_BYTEM_NORMAL_gc;
        // Overflow interrupt: High Level
        // Error interrupt: Disabled
    TCC1.INTCTRLA=TC_ERRINTLVL_OFF_gc | TC_OVFINTLVL_MED_gc;
        // Clear the interrupt flags
    TCC1.INTFLAGS=TCC1.INTFLAGS;
        // Set Counter register
    TCC1.CNT=0x0000;
        // Set Period register
    TCC1.PER=0x7D00;
        // Clock source: ClkPer/1
    TCC1.CTRLA=TC_CLKSEL_DIV1_gc;

 

other timer setup is the same, but prescaler is 320 at 32 MHz clock

// Timer/Counter TCC1 Overflow/Underflow interrupt service routine:

// This runs at 1kHz
ISR(TCC1_OVF_vect)
{
        // Ensure the interrupt flags are cleared:
        // Increment global _ms_timer.
    _ms_timer++;

        // DEBUG:
    if (TCC1.INTFLAGS & 0x01) {
            // Interrupt error because we are taking too long:
        interrupt_debug = 1;
    }

}

 

// Timer/Counter TCD1 Overflow/Underflow interrupt service routine:

// This runs at 100 kHz
ISR(TCD1_OVF_vect)
{

      
        // Ensure the interrupt flags are cleared:
    ten_us_timer++;  // global unsigned long

    if (TCD1.INTFLAGS & 0x01) {
            // Interrupt error because we are taking too long:
        interrupt_debug = 1;
    }

}

 

This is very frustrating and is not helping my faith in the xmega for accurate timing.  Any help is appreciated, and if this is really an issue then anyone trying to do accurate timing with these things will be having problems as well.  As a quick hack I have subtracted 1 from the 100 kHz timer overflow and it runs slightly faster than the 1 kHz timer, but this solution is not ideal.  I could also compare in software and teak the overflow to keep them in sync, but again this should occur in time with the CPU and software tweaks shouldn't be needed.

This topic has a solution.
Last Edited: Fri. Mar 31, 2017 - 01:18 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Don't blame me the xmega! It's doing what you told it to do.
First up, variables shared with isr code must be declared volatile. Also you need to access the atomically.
At 32MHz, you have 320 clocks in 10us. I'd guess each isr would take at least 1us-likely more. This means you're chewing up over 10% of cpu. Thus having a 100kHz interrupt is not common.

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

And there's nothing else in the entire test program?

 

Or is there something else that may be blocking the fast interrupts from being serviced every time? Presumably you expected:

 

    _ms_timer    60534       unsigned long{data}@0x2192

ten_us_timer    6043996    unsigned long{data}@0x218e

 

to be:

 

    _ms_timer    60534       unsigned long{data}@0x2192

ten_us_timer    6053400    unsigned long{data}@0x218e

 

which to me suggests that not all the 10us interrupts got a chance to be serviced.

 

Sure you can block interrupts and handle the 10us "late" but you cannot be so late that another "tick" has occurred before you finally get to service the event. The AVRs only latch the fact that "some" interrupts have occurred not how many have occurred before you got a chance to service them. So if more than one occurred it will act as just one.

 

Even at 32MHz 10us is not a "lot" of time. 320 cycles I guess? You may well be tied up for more than that elsewhere?

 

Also bear in mind that there is a cost to get in/out of an ISR. The whole process could "cost" the best part of 30 cycles before you really get started doing anything.

Last Edited: Fri. Mar 31, 2017 - 09:26 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks for the quick replies,

The test program has nothing else running, and the results are pulled from the debugger.  I have had this run for a fixed period of time then read out the values they still do not agree.  All access to the variables is done as per:

{
        // Disable interrupts and make a copy of _ms_timer:
    cli();
    unsigned long value = _ms_timer;
    sei(); // Re-enable interrupts
    return value;

}

Even when the 100 kHz timer is reduced to 10 kHz this issue still occurs, however at a much lower rate which seems to rule out a missed interrupt...

It also accumulates over time and appears that it is sometimes taking an extra clock cycle before resetting the counter or something else strange

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

Edit:  This also seems unlikely to be missed interrupts as it is a very predictable error.  I would expect missed interrupts to cause sudden changes between the counter values, but I another program which is doing lots of things interrupts etc. and has the exact same percentage error as this test and on the xplained board the error is also very similar?  What is going on here?

 

I am coming back to this as I have been using a work around up until now.  I have re-created the test code and here is the full program and the results.

I have slowed the second timer down to 10 kHz which should be easy for the micro at 32 MHz (3200 clock cycles to service an interrupt should be ample).  I am still seeing a discrepancy.

As expected the slower timer interrupt is being triggered when we are servicing the 10 kHz interrupt and occasionally the faster one is set while servicing the slower timer, but there is no reason that this should be missing interrupts on a 10 kHz timer with this configuration that I can see.  If anyone can replicate this or provide further advice it would be greatly appreciated.  The result below is with all the debug code stripped out and main is simply while(1){}

clawson you are correct I am expecting the timers to have the same value (whithin the precision of the lower frequency)

 

 _ms_timer    212754    unsigned long{data}@0x2001

hundred_us_timer    2126949    unsigned long{data}@0x2005

 

I would expect values of 212754

and                               21275xx

since it may be just about to increment to 212754 from 212753 depending on when the values are read etc. etc. 

 

This is showing an error of 59 ms! over 212 seconds which is massive.  This means it is missing nearly 3 interrupts every second!

 

 

 

 

/*

 * GccApplication1.c
 *
 * Created: 31/03/2017 10:35:04 p.m.
 *  Author: Carl
 */ 

#include <avr/xmega.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <util/delay.h>
#include <stddef.h>

volatile unsigned long hundred_us_timer = 0;
volatile unsigned long _ms_timer = 0;
volatile unsigned char interrupt_debug = 0;

int main(void)
{
    unsigned char n;
    
    // Enable the internal 32 MHz RC oscillator
    OSC.CTRL|=OSC_RC32MEN_bm;
    // Wait for the internal 32 MHz RC oscillator to stabilize
    while ((OSC.STATUS & OSC_RC32MRDY_bm)==0);

        // PLL initialization
        // Ensure that the PLL is disabled before configuring it
    OSC.CTRL &= ~OSC_PLLEN_bm;
        // PLL clock source: 32 MHz Internal Osc./4
        // PLL multiplication factor: 4
        // PLL output/2: Off
        // PLL frequency: 32.000000 MHz
        // Set the PLL clock source and multiplication factor
    n = OSC_PLLSRC_RC32M_gc | (0<<OSC_PLLDIV_bp) | (4<<OSC_PLLFAC_gp);
        // Enable the PLL
    CCP = CCP_IOREG_gc;
    OSC.PLLCTRL = n;
    OSC.CTRL |= OSC_PLLEN_bm;

        // Wait for the PLL to stabilize
    while ((OSC.STATUS & OSC_PLLRDY_bm) == 0);

        // System Clock prescaler A division factor: 1
        // System Clock prescalers B & C division factors: B:1, C:1
        // ClkPer4: 32000.000 kHz
        // ClkPer2: 32000.000 kHz
        // ClkPer:  32000.000 kHz
        // ClkCPU:  32000.000 kHz
    n = (CLK.PSCTRL & (~(CLK_PSADIV_gm | CLK_PSBCDIV1_bm | CLK_PSBCDIV0_bm))) | CLK_PSADIV_1_gc | CLK_PSBCDIV_1_1_gc;
    CCP = CCP_IOREG_gc;
    CLK.PSCTRL = n;

        // Select the system clock source: Phase Locked Loop
    n = (CLK.CTRL & (~CLK_SCLKSEL_gm)) | CLK_SCLKSEL_PLL_gc;
    CCP = CCP_IOREG_gc;
    CLK.CTRL = n;

        // Disable the unused oscillators: 32 kHz, 2 MHz
    OSC.CTRL &= ~(OSC_RC32KEN_bm | OSC_RC2MEN_bm);
    
    n = (PMIC.CTRL & (~(PMIC_RREN_bm | PMIC_IVSEL_bm | PMIC_HILVLEN_bm | PMIC_MEDLVLEN_bm | PMIC_LOLVLEN_bm))) | PMIC_LOLVLEN_bm | PMIC_MEDLVLEN_bm | PMIC_HILVLEN_bm;
        // Update the PMIC control register
    PROTECTED_WRITE(PMIC.CTRL, n);
        // Set the default priority for round-robin scheduling
    PMIC.INTPRI=0x00;
    
    
    TCC1.CTRLB=(0<<TC1_CCBEN_bp) | (0<<TC1_CCAEN_bp) | TC_WGMODE_NORMAL_gc;
        // Set Timer/Counter in Normal mode
    TCC1.CTRLE=TC_BYTEM_NORMAL_gc;
        // Overflow interrupt: High Level
        // Error interrupt: Disabled
    TCC1.INTCTRLA=TC_ERRINTLVL_OFF_gc | TC_OVFINTLVL_MED_gc;
        // Clear the interrupt flags
    TCC1.INTFLAGS=TCC1.INTFLAGS;
        // Set Counter register
    TCC1.CNT=0x0000;
        // Set Period register
    TCC1.PER=0x7D00;
        // Clock source: ClkPer/1
    TCC1.CTRLA=TC_CLKSEL_DIV1_gc;
    
// Configure ADC Trigger Timer: Interrupts at SAMPLE_CLOCK frequency.
        // Mode: Normal Operation, Overflow Int./Event on TOP
        // Compare/Capture on channel A: Off
        // Compare/Capture on channel B: Off
    TCD1.CTRLB = (0<<TC1_CCBEN_bp) | (0<<TC1_CCAEN_bp) | TC_WGMODE_NORMAL_gc;
        // Set Timer/Counter in Normal mode
    TCD1.CTRLE = TC_BYTEM_NORMAL_gc;
        // Overflow interrupt: High Level
        // Error interrupt: Disabled
    TCD1.INTCTRLA = TC_ERRINTLVL_OFF_gc | TC_OVFINTLVL_HI_gc;
        // Clear the interrupt flags
    TCD1.INTFLAGS = TCD1.INTFLAGS;
        // Set Counter register
    TCD1.CNT = 0x0000;
        // Set Period register
    TCD1.PER = 0xC80;//0x140;
        // Clock source: ClkPer/1
    TCD1.CTRLA = TC_CLKSEL_DIV1_gc;
    
    sei();
    
    while(1)
    {
      /*  //TODO:: Please write your application code 
        if (interrupt_debug == 1) {
            interrupt_debug = 0;
        }*/
    }
}
    
// This runs at 1kHz
ISR(TCC1_OVF_vect)
{
    // Ensure the interrupt flags are cleared:
    // Increment global _ms_timer.
    _ms_timer++;
    // DEBUG:
    /*if (TCC1.INTFLAGS & 0x01) {
        // Interrupt error because we are taking too long:
        interrupt_debug = 1;
    }
    if (TCD1.INTFLAGS & 0x01) {
        // Interrupt error because we are taking too long:
        interrupt_debug = 1;
    }*/
}
    
// Timer/Counter TCD1 Overflow/Underflow interrupt service routine:
// This runs at 10 kHz
ISR(TCD1_OVF_vect)
{
    // Ensure the interrupt flags are cleared:
    hundred_us_timer++;  // global unsigned long
   /* if (TCD1.INTFLAGS & 0x01) {
        // Interrupt error because we are taking too long:
        interrupt_debug = 1;
    }
    if (TCC1.INTFLAGS & 0x01) {
        // Interrupt error because we are taking too long:
        interrupt_debug = 1;
    }*/
}

Last Edited: Fri. Mar 31, 2017 - 10:19 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have been furiously testing things out and it appears that when both timer pre-scalers are equal the values always agree even up to 100 kHz, so there is nothing weird going on with interrupts.  It appears to genuinely be a bug on the chips.  I will verify these frequencies on the scope tomorrow and try and confirm whether the timer has some increasing error with frequency.

This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

No hardware bug on the chip. It does exactly what you ask it to do. One timer counts to 32000, and with 0 that is 32001 ticks. The other 3201 ticks. And that is your problem.
Decrement your two PER registers by one, and the two timer variables should match.

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

Ahhh! The old 'off by one' bug - second time this week!

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

Thank you, thank you!  I did have that thought go through my mind and i discounted it for what ever reason.  It is working perfectly with the period decremented by 1.  Edit: The world makes sense again...

Last Edited: Fri. Mar 31, 2017 - 09:10 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

There is no bug - it is a design feature. If you wanted the timer to divide by 2. You would compare with 1. Eg
0,1,0,1,0,1
If you used 2, the sequence would be:
0,1,2,0,1,2
Which would divide by 3.
Nothing to do with the compiler.

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

xMega is so lousy, it keeps doing what I tell it to do, not what I want it to do.

The largest known prime number: 282589933-1

It's easy to stop breaking the 10th commandment! Break the 8th instead. 

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

Torby wrote:
xMega is so lousy ...

Not just xMega, but most every computer, uP, or uC I work with...wink

David (aka frog_jr)