SAME70 Timer Capture Issues

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

Hi,

 

I'm working with a SAME70 and need to calculate the frequency of an input square wave on a GPIO pin (PIO_PA5 in this example). I've read through the various Timer and Interrupt ASF guides, and have confirmed the functionality by inspecting the example projects. I've also read numerous similar threads on here regarding interrupts, latency, TC capture, etc.

 

The input square itself has a frequency range from 0Hz to ~5kHz. The input square wave is from a function generator, and I've verified the signal with my oscilloscope (just to make sure the signal itself was stable and didn't have any jitter).

 

Below is the relevant code:

 

/*Using TC1 Channel 0*/
#define ID_TC1_0      ID_TC3
#define TC1_0_Handler TC3_Handler

volatile uint32_t tc_Count = 0;
volatile uint32_t tc_Capture = 0;
volatile uint32_t tc_Temp = 0;
volatile float freq;

/*TC 1 Channel 0 handler*/
void TC1_0_Handler(void)
{
    tc_Count++;
    
    volatile uint32_t dummy;
    dummy = tc_get_status(TC1, 0);
    UNUSED(dummy);
}

/*Configure and initialise TC1 Channel 0*/
static void configure_tc1_0()
{
    uint32_t ul_div;
    uint32_t ul_tcclks;
    uint32_t ul_sysclk = sysclk_get_cpu_hz();
    
    pmc_enable_periph_clk(ID_TC1_0);
    
    tc_find_mck_divisor(1000, ul_sysclk, &ul_div, &ul_tcclks, ul_sysclk);
    tc_init(TC1, 0, ul_tcclks | TC_CMR_CPCTRG);
    tc_write_rc(TC1, 0, (ul_sysclk / ul_div) / 1000);
    
    NVIC_EnableIRQ((IRQn_Type) ID_TC1_0);
    tc_enable_interrupt(TC1, 0, TC_IER_CPCS);
    
    tc_start(TC1, 0);
}

/*PIO_PA5 interrupt handler*/
static void PIOA_Interrupt_Handler(uint32_t id, uint32_t mask)
{
    //Capture current tc_Count value
    tc_Temp = tc_Count;
    
    if (id == ID_PIOA && mask == PIO_PA5) {
        
        //Check if this is the first rising edge
        if (tc_Capture == 0) {
            
            tc_Capture = tc_Count;
        } else {

            freq = 1 / (float)(tc_Temp - tc_Capture);
        }
        
        tc_Capture = tc_Temp;
        tc_Temp = 0;
    }
}

/*Initialisation function*/
void init(void)
{
    //General initialisation etc.
    
    //Configure TC1_0
    configure_tc1_0();
    
    //Setup PIO_PA5 rising edge interrupt and callback
    pmc_enable_periph_clk(ID_PIOA);
    pio_disable_interrupt(ID_PIOA, PIO_PA5);
    NVIC_DisableIRQ((IRQn_Type) ID_PIOA);
    NVIC_ClearPendingIRQ((IRQn_Type) ID_PIOA);
    NVIC_EnableIRQ((IRQn_Type) ID_PIOA);
    pio_handler_set(PIOA, ID_PIOA, PIO_PA5, PIO_IT_RISE_EDGE, PIOA_Interrupt_Handler);
    pio_handler_set_priority(PIOA, (IRQn_Type) ID_PIOA, 0);
    pio_enable_interrupt(PIOA, PIO_PA5);
}

As per the above code, I'm currently using a 1000Hz interrupt frequency, with ul_sysclk verified as being 150MHz. I have two issues with the above code:

  • The calculated frequency is reported as twice that of the actual signal. E.g. an input frequency of 10Hz results in freq = 20Hz.
  • The calculated frequency isn't stable for frequencies where the period isn't an integer (i.e. it has fractional parts). E.g. for a frequency of 10, the underlying period (100ms) results in freq being stable. However, a frequency of 9Hz (a period of 111.111ms) causes freq to bounce between two values due to rounding. I don't need to capture fractional frequencies, so can use a uint32_t for freq, but that doesn't address the underlying issue of not having enough resolution.

 

My questions:

  1. Is the above approach sound for low frequency (<5kHz) frequency capture?
  2. What would be a reasonable TC counter frequency to use, to allow for accurate (nearest 1Hz) frequency calculation between 1Hz and ~5kHz.
  3. Why does the above code result in a calculated frequency twice that of the actual input frequency?
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Giving this some further thought, I'm wondering if rather than using the RC compare approach I'd be better off using an interrupt on overflow, then read the value of RC on PIO interrupt. That way I can take the current RC count and add the number of overflow events to get the current (total) count, and reduce the overhead of generating and responding to an interrupt X thousand times a second.

 

I'd still very much appreciate any insight, but will play with that idea in the meantime.

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

I can't see where the capture hardware is being used. You really don't need to do floating point calcs in your isr - especially on a 32bit micro.

 

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

Thanks Kartman, your input is appreciated as always. I confused the issue by using the term 'capture', as I'm not using the inbuilt capture hardware, simply trying to measure the frequency of an input on a standard GPIO. As for the floating point operation, you're right. All captured data is sent to a more powerful processor, so I think I'd be best to send the raw data (i.e. period) and let any subsequent floating point calculations be done off the 32-bit MCU.

 

Per my second post, I've adapted the approach so that the TC1_0_Handler() function is called on TC1_0 overflow. On overflow I increment tc_Count, so to keep track of the number of overflow events for use in calculating the period. In the GPIO ISR, I read the current count number of TC1_0, and then add the overflow-based number of ticks (tc_Count x number of counts per overflow).

 

Example code below:

 

/*Using TC1 Channel 0*/
#define ID_TC1_0      ID_TC3
#define TC1_0_Handler TC3_Handler

volatile uint32_t tc_Count = 0;
volatile uint32_t tc_Capture = 0;
volatile uint32_t tc_Temp = 0;

/*TC 1 Channel 0 handler*/
void TC1_0_Handler(void)
{
    tc_Count++;
    
    volatile uint32_t dummy;
    dummy = tc_get_status(TC1, 0);
    UNUSED(dummy);
}

/*Configure and initialise TC1 Channel 0*/
static void configure_tc1_0()
{
    
    pmc_enable_periph_clk(ID_TC1_0);

    tc_init(TC1, 0, 3); //Use MCK/128
    NVIC_DisableIRQ((IRQn_Type) ID_TC1_0);
    NVIC_ClearPendingIRQ((IRQn_Type) ID_TC1_0);
    NVIC_EnableIRQ((IRQn_Type) ID_TC1_0);
    tc_enable_interrupt(TC1, 0, TC_IER_COVFS); //Interrupt on overflow
    
    tc_start(TC1, 0);
}

/*PIO_PA5 interrupt handler*/
static void PIOA_Interrupt_Handler(uint32_t id, uint32_t mask)
{
    //Calculate total current value
    //REG_TC1_CV0 reads the current count value for TC1_0
    //16-bit counter so 65536 counts per overflow event
    tc_Temp = REG_TC1_CV0 + (65536 * tc_Count);
}

/*Initialisation function*/
void init(void)
{
    //General initialisation etc.
    
    //Configure TC1_0
    configure_tc1_0();
    
    //Setup PIO_PA5 rising edge interrupt and callback
    pmc_enable_periph_clk(ID_PIOA);
    pio_disable_interrupt(ID_PIOA, PIO_PA5);
    NVIC_DisableIRQ((IRQn_Type) ID_PIOA);
    NVIC_ClearPendingIRQ((IRQn_Type) ID_PIOA);
    NVIC_EnableIRQ((IRQn_Type) ID_PIOA);
    pio_handler_set(PIOA, ID_PIOA, PIO_PA5, PIO_IT_RISE_EDGE, PIOA_Interrupt_Handler);
    pio_handler_set_priority(PIOA, (IRQn_Type) ID_PIOA, 0);
    pio_enable_interrupt(PIOA, PIO_PA5);
}

As you can see I've greatly simplified the ISR portion; all I'm looking for is the current count since the timer was started. The issue I have now, is when I use an input signal of frequency greater than about 10Hz, the tc_Count value (when monitored via a tracepoint) no longer increments? The REG_TC1_CV0 is read correctly, but tc_Count stays the same. Any ideas as to why that might be the case?

 

Assuming this is a reasonable approach, the intent would be to send the elapsed count value (current count minus previous count) to the main processor.

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

I've managed to get tc_Count to continue incrementing with higher input frequencies, but I don't entirely understand why. The interrupt priority for PIOA was originally set to 0; I've now changed this to 1 and used NVIC_SetPriority() in configure_tc1_0() to set the overflow priority as 0. I can now see tc_Count incrementing as expected. I would have thought that with an input signal of only ~20Hz, both interrupts would be serviced with plenty of room...

 

Edit: While the above priority re-ordering addressed the tc_Count incrementing issue, I'm still at a loss as to whether this is the correct approach. I've now calculated the period (measured in elapsed ticks) for the incoming signal, and even at ~5Hz the values (monitored via tracepoint) are entirely unstable. Given such a low frequency, and given that I've verified it's accuracy with my oscilloscope, I must be doing something wrong because the measured period is all over the place.

Last Edited: Wed. Mar 25, 2020 - 11:22 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I appear to be going backwards. These timing numbers are making no sense. I've deleted both the RC compare and overflow interrupt approaches, and have gone back to absolute basics. I've now setup a simple 1ms SysTick handler which simply increments tc_Count. In my PIOA_Interrupt_Handler() I simply read tc_Count via a tracepoint. I'm applying a 1Hz input signal, so would expect the tracepoint to show 1000, 2000, 3000, 4000, etc., however I'm getting increments in the 600-800 range each time. Having used SysTick previously, including in example projects, I would expect the SysTick handler to be completely accurate for a 1Hz signal...

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

Have the pio isr toggle an output port bit. You can observe this on an oscilloscope or logic analyser. This will confirm that your input signal is being detected correctly. You could also do the same for the 1kHz tick. Work forward from there.

 

Note that the debugger may not be telling the whole story - depending on where you trigger, the ram copy of the variable may not have been updated.

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

Thanks again Kartman.

 

I've done exactly as you've suggested, and set up a spare GPIO as an output, and matched the input signal (i.e. read PA5 in the ISR and set the output as either high or low). I've measured the output signal with my oscilloscope, and have verified that it is indeed absolutely stable (tested from 1Hz through to 5kHz). Similarly, I set up a 1000Hz SysTick handler, which was also absolutely stable when verified with the oscilloscope.

 

At this point I'm going to try sending the captured SysTick values to the remote processor and not rely on the debugger at all, as it may be what's causing the confusion (as you've suggested). I'll give that a go this morning and report back.