This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

32-bit time capture not clearing counter on TM4C123G LaunchPad

Other Parts Discussed in Thread: TM4C123GH6PM

Elapsed time capture is working well on the TM4C123G Launchpad, except for that the counter is not resetting after a capture event. I understand that this counter register reset is not automatic. From the datasheet page 715:

"After an event has been captured, the timer does not stop counting. It continues to count until the

TnEN bit is cleared."

So I inserted a command to clear the counter by using the TimerLoadSet command in the ISR, but the counter continues on where it left off after the capture event. Here is my complete code:

#include <stdint.h>
#include <stdbool.h>
#include "inc/tm4c123gh6pm.h"
#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "driverlib/sysctl.h"
#include "driverlib/interrupt.h"
#include "driverlib/gpio.h"
#include "driverlib/timer.h"
#include "driverlib/pin_map.h"

extern void WTimer0IntHandler(void);
uint32_t period_value;

int main(void)
{
	SysCtlClockSet(SYSCTL_SYSDIV_5|SYSCTL_USE_PLL|SYSCTL_XTAL_16MHZ|SYSCTL_OSC_MAIN); //40 MHz

	SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
	GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3);
	SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOC);
	GPIOPinTypeTimer(GPIO_PORTC_BASE, GPIO_PIN_4);
	GPIOPinConfigure(GPIO_PC4_WT0CCP0);

	SysCtlPeripheralEnable(SYSCTL_PERIPH_WTIMER0);
	TimerConfigure(WTIMER0_BASE, TIMER_CFG_SPLIT_PAIR | TIMER_CFG_A_CAP_TIME_UP);
	TimerControlEvent(WTIMER0_BASE, TIMER_A, TIMER_EVENT_POS_EDGE);
	TimerIntEnable(WTIMER0_BASE, TIMER_CAPA_EVENT);
	TimerIntClear(WTIMER0_BASE, TIMER_CAPA_EVENT);
	IntEnable(INT_WTIMER0A);
	IntMasterEnable();
	TimerEnable(WTIMER0_BASE, TIMER_A);

	while(1)
	{
	}
}

void WTimer0IntHandler(void)
{
	TimerIntClear(WTIMER0_BASE, TIMER_CAPA_EVENT);
	period_value = TimerValueGet(WTIMER0_BASE, TIMER_A);
	TimerLoadSet(WTIMER0_BASE, TIMER_A,	0);
}

In a watch window I could see period_value increase after each capture, but the input at pin PC4 is a constant pulse width so period_value should likewise be constant. Likewise WTIMER0_TIMER_TAV also constantly increased after each capture event in the Memory Browser.

The TimerLoadSet description in the API page 549 says:

"This function configures the timer load value; if the timer is running then the value is immediately loaded into the timer."

So I should not have to stop the timer to reset it, yet it will not reset. Please advise.

  • Hello Otto,

    The TimerLoadSet having an effect on the timer value is only applicable for Periodic Mode of the timer and not for the Timer Edge Time Mode. So you would have to clear the timer enable, change the value and then restart the same.

    Regards

    Amit

  • Is there an API to change the timer value? The closest I could find is TimerLoadSet, which is not appropriate as you pointed out. Do I need to do it in assembly?

  • Hi,

    You do not need to clear/reset the timer value - there are two situations when measuring:

    a) the period is within the time range

    b) the period is bigger than the timer range

    For both cases, the period can be simply computed as the actual value minus the old value, so you must play a little bit with a static variable and keep always the previous captured value. Take care about the arithmetic of operation. Special care when the period is bigger or the captured value is over the timer range.

    Also, another case is when measured pulses vanishes from various reasons - what you measure in those cases?

    Petrei

  • Well, OK, pulse duration measurement could be done that way, but, jeez, is there no simple way to clear a register?

  • Hi,

    Well, no, not here, not also on other implementations - even brands, or architectures - the same on 16-bits or 32-bits (e.g. Freescale or NXP).

    Petrei 

  • Otto Hunt said:
    is there no simple way to clear a register?

    May well depend upon how you wish to define, "simple."  (always a delight...)

    Might a (simple) way to glean such insight reside w/in the source code - for TimerValueGet()?  Revealed are the pertinent registers - are they not?  Your job then - check & see if these registers accept your write - and if so you may clear them via assembler (as you mentioned) or via Direct Register (easier).

    Here's the essence of TimerValueGet():

        // Return the appropriate timer value.
        //
        return((ulTimer == TIMER_A) ? HWREG(ulBase + TIMER_O_TAR) :
               HWREG(ulBase + TIMER_O_TBR));

    We note that these registers are not impacted by, "TimerLoadSet()" which is as you earlier reported & Amit confirmed.  And - perhaps now - you know the reason why...

  • Hello Otto

    I may have found a way out for your consideration. In the Interrupt Handler replace the TimerLoadSet with a write to the TAV register,

    void WTimer0IntHandler(void)
    {
        TimerIntClear(WTIMER0_BASE, TIMER_CAPA_EVENT);
        period_value = TimerValueGet(WTIMER0_BASE, TIMER_A);
        HWREG(WTIMER0_BASE+TIMER_O_TAV) = 0x0;
    }
    Regards

    Amit

  • Hi Amit,

    First of all, I do appreciate your continued attention to this problem. I was exploring assembly language techniques and also this approach.

    When I insert 

    HWREG(WTIMER0_BASE+TIMER_O_TAV) = 0x0;

    verbatim, I get a compile error "TIMER_0_TAV is undefined". I checked tm4c123gh6pm.h then changed it to 0x36050 (the offset address of WTIMER0_TAV_R). The timer quit completely, even when disabling then enabling the timer on either side of that statement. I do believe we are on the right track. This is exciting!

  • Here is an update: With this in the ISR:

    	HWREG(0x40036050) = 0x0;

    I get large (8-digit) values in period_value, and though they fluctuate way too much, they do not continually increase as before. When I insert

    	TimerDisable(WTIMER0_BASE, TIMER_A);
    	HWREG(0x40036050) = 0x0;
    	TimerEnable(WTIMER0_BASE, TIMER_A);

    I get 4-5-6 digit values in period_value that still fluctuate more than I would like and, again, do not continually increase.

  • Hello Otto,

    Question-1: When you state "continually increase" do you mean they start from 0

    Question-2: Is the input signal a periodic signal of fixed frequency, and that causes 4-6 digits worth of change?

    Regards

    Amit

  • When, due to my mistake, the timer was not being reset at all, period_value would start at a non-zero (nor anywhere near zero) value and increase from that point with each iteration (I set a breakpoint inside the ISR). i did not notice how linear the increase was.

    The capture interrupt is being driven by a classic opamp multivibrator as described here. It does not have the stability of a crystal oscillator, but it looks solid on my 'scope at 217.6 Hz. It should not cause the degree of fluctuation i observe in the Expressions Watch Window.

  • Hello Otto

    I would prefer a stable input system to measure when there are errors in measurement. That way we can eliminate one cause of discrepancies. Please note that the ramp time of the signal may create multiple sampling edges. So ensure that on the scope the edge has a high Slew Rate.

    In the meantime I took some other routes to investigate.

    One of them is to put the timer in down count mode instead of up count mode. That way I can still call the TimerLoadSet API and reset the counter back to 0xFFFFFFFF instead of using the GPTMTAV register. The only difference in the ISR would be instead of getting the value directly, I would need to subtract the value from 0xFFFFFFFF.

    Regards

    Amit

  • I am now feeding 1.95316 kHz into the Launchpad pin PC4. Signal source is an ECS2100 oscillator feeding a CD4020 CMOS divider. 4020 output is pin 10. So without changing anything else, period_value has the following successive readings when a breakpoint is active in the ISR:

    9391

    12254

    14677

    11610

    12869

    9761

    7622

  • Hi,

    Suggest to collect a number of 20...50 computed periods into an array, and then examine them, not by one-interrupt basis since that may give you false measurements. Take into account you have also a timer stall API, but the best method is with the array.

    Try this first...

    Petrei

  • That was a great suggestion, Petrei. I added a for loop to fill an array of 100 elements with the current period_value, naming the array period_values[100]. I got a series of 100 rock-solid values (00004FB4) filled into this array while the program free ran. So it appears this is a good program for capture AND I now know that setting any kind of breakpoint messes up the timing. Woo-hoo! Here is the new code listing:

    #include <stdint.h>
    #include <stdbool.h>
    #include "inc/tm4c123gh6pm.h"
    #include "inc/hw_memmap.h"
    #include "inc/hw_types.h"
    #include "driverlib/sysctl.h"
    #include "driverlib/interrupt.h"
    #include "driverlib/gpio.h"
    #include "driverlib/timer.h"
    #include "driverlib/pin_map.h"
    
    extern void WTimer0IntHandler(void);
    uint32_t period_value;
    uint8_t i;
    uint32_t period_values[100];
    
    int main(void)
    {
    	SysCtlClockSet(SYSCTL_SYSDIV_5|SYSCTL_USE_PLL|SYSCTL_XTAL_16MHZ|SYSCTL_OSC_MAIN); //40 MHz
    
    	SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
    	GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3);
    	SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOC);
    	GPIOPinTypeTimer(GPIO_PORTC_BASE, GPIO_PIN_4);
    	GPIOPinConfigure(GPIO_PC4_WT0CCP0);
    
    	SysCtlPeripheralEnable(SYSCTL_PERIPH_WTIMER0);
    	TimerConfigure(WTIMER0_BASE, TIMER_CFG_SPLIT_PAIR | TIMER_CFG_A_CAP_TIME_UP);
    	TimerControlEvent(WTIMER0_BASE, TIMER_A, TIMER_EVENT_POS_EDGE);
    	TimerIntEnable(WTIMER0_BASE, TIMER_CAPA_EVENT);
    	TimerIntClear(WTIMER0_BASE, TIMER_CAPA_EVENT);
    	IntEnable(INT_WTIMER0A);
    	IntMasterEnable();
    	TimerEnable(WTIMER0_BASE, TIMER_A);
    
    	while(1)
    	{
    	}
    }
    
    void WTimer0IntHandler(void)
    {
    	TimerIntClear(WTIMER0_BASE, TIMER_CAPA_EVENT);
    	period_value = TimerValueGet(WTIMER0_BASE, TIMER_A);
    	TimerDisable(WTIMER0_BASE, TIMER_A);
    	HWREG(0x40036050) = 0x0;
    	TimerEnable(WTIMER0_BASE, TIMER_A);
    	for (i = 0; i < 100; i++) period_values[i] = period_value;
    }
    

  • Otto Hunt said:
    I am now feeding 1.95316 kHz into the Launchpad pin PC4.

    Good this was resolved - and we thank you for providing a clear, written tech trail - for the benefit of (many) others...  Your example here probably should be promoted to, "permanent, most instructive example status" alas that will be duly noted but glacier moves slowly.  (if at all)

    For clarity your input signal (f=1.953 KHz) has a period of 512uS and your report of 20404 Timer counts - of a 40MHz (earlier) specified System Clock - reveals (20404 * 25nS) that same 512uS.  Q.E.D.

    The "GPTMTAV" register is indeed R/W capable - thus allows your periodic reloading - freeing you from the necessity to, "subtract/add current from past register content."  (depending upon whether you employ up or down counter)

  • Hi Otto,

    Your last line in interrupt routine is not a good one - that's why you got 'rock solid' values, because in fact was a single value, the first, repeated hundred times in your loop:
    for (i = 0; i < 100; i++) period_values[i] = period_value;
    Instead, the last line sholud be: (i should be declared as static)
    if (i++ < 100) period_values[i] = period_value;
    to allow the interrupt routine to get out and collect the next period.
    Please re-test your program. (The first value was a good one, I expect the others one to be correct also, but the program should be also without bugs.Hope you understand that- is for you and also for others)

    regards,

    Petrei

  • Petrei,

    Great point - "if (i++ < 100)" - that "rock solid" notation bothered me as well.  (and I was not as smart nor as persistent to note the code error)  I seized upon simple "confirmation" of poster's result - and failed to dig down & uncover the source of the surprising measurement consistency.  Not you - Bravo Petrei!

    May I ask if you - indeed - responded to that, "rock solid" comment - knowing that it was unlikely?

    Systems such as these are not capable of such "rock solid" precision - beware!   Such report should have signaled some difficulty.  And you - alone - caught & reported.

    Expect that 2-10 count deviation would be more, normal/customary (deviation of 20 is 1%) - based upon temperature & voltage/signal stability of user's system - even when employing Petrei's suggestion of automatic, array load...

  • Thanks again, Petrei, for your insight. Yes, I see now how the for loop executes all at once, versus the if statement that executes once per pass (interrupt). You are also right about the variable i being static. I did make those changes and did retest. The hundred values centered around 00004FB3 with %30 B2 and  %3 B4. So, in the interest of a bug-free tech trail (as cb1 likes to call it) here is the corrected program:

    #include <stdint.h>
    #include <stdbool.h>
    #include "inc/tm4c123gh6pm.h"
    #include "inc/hw_memmap.h"
    #include "inc/hw_types.h"
    #include "driverlib/sysctl.h"
    #include "driverlib/interrupt.h"
    #include "driverlib/gpio.h"
    #include "driverlib/timer.h"
    #include "driverlib/pin_map.h"
    
    extern void WTimer0IntHandler(void);
    uint32_t period_value, period_values[100];
    static uint8_t i;
    
    int main(void)
    {
    	SysCtlClockSet(SYSCTL_SYSDIV_5|SYSCTL_USE_PLL|SYSCTL_XTAL_16MHZ|SYSCTL_OSC_MAIN); //40 MHz
    
    	SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
    	GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3);
    	SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOC);
    	GPIOPinTypeTimer(GPIO_PORTC_BASE, GPIO_PIN_4);
    	GPIOPinConfigure(GPIO_PC4_WT0CCP0);
    
    	SysCtlPeripheralEnable(SYSCTL_PERIPH_WTIMER0);
    	TimerConfigure(WTIMER0_BASE, TIMER_CFG_SPLIT_PAIR | TIMER_CFG_A_CAP_TIME_UP);
    	TimerControlEvent(WTIMER0_BASE, TIMER_A, TIMER_EVENT_POS_EDGE);
    	TimerIntEnable(WTIMER0_BASE, TIMER_CAPA_EVENT);
    	TimerIntClear(WTIMER0_BASE, TIMER_CAPA_EVENT);
    	IntEnable(INT_WTIMER0A);
    	IntMasterEnable();
    	TimerEnable(WTIMER0_BASE, TIMER_A);
    
    	while(1)
    	{
    	}
    }
    
    void WTimer0IntHandler(void)
    {
    	TimerIntClear(WTIMER0_BASE, TIMER_CAPA_EVENT);
    	period_value = TimerValueGet(WTIMER0_BASE, TIMER_A);
    	TimerDisable(WTIMER0_BASE, TIMER_A);
    	HWREG(0x40036050) = 0x0;
    	TimerEnable(WTIMER0_BASE, TIMER_A);
    	if (i++ < 100) period_values[i] = period_value;
    }
    

  • @Otto,

    That's nice - thank you.  Might I suggest the award of a 2nd Verify as Petrei made a major contribution to resolving your issue?...

    [edit 18:01] Let the record show poster Petrei's received (deserved) verification.

  • Hi,

    @cb1, yes, 'rock solid' is something unusual and unexpected in such cases, and second, is also reflex on code - for loop in interrupts doesn' t sound always good... Thanks.

    @Otto, OK, this is what you should expect, since the events (your external pulse and the internal clock) are not correlated. 

    Regards,

    Petrei