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.

Measuring Duty Cycle using Timer 3 Edge-Time Mode

Other Parts Discussed in Thread: EK-TM4C1294XL, TM4C1294NCPDT

Hi all,

Below is the snippet of code causing me grief:

void Timer3IntHandler(void)
{
	// If timer rollover occurs when sampling is true, increment rollover counter.
	if (MAP_TimerIntStatus(TIMER3_BASE, TIMER_TIMA_TIMEOUT) == TIMER_TIMA_TIMEOUT)
	{	
		// Ensures that the program is currently sampling before counting rollovers.
		if (captureFlag == 1)
		{
			rollOverCount ++;			
		}
	}

	if (MAP_TimerIntStatus(TIMER3_BASE, TIMER_CAPA_EVENT) == TIMER_CAPA_EVENT)
	{
		currentSampleCount = MAP_TimerValueGet(TIMER3_BASE, TIMER_A);
    	        samplePeriod = (currentSampleCount + (65535 * rollOverCount)) - lastSampleCount;
    	        lastSampleCount = currentSampleCount;
    	        captureFlag ^= 1;
    	        rollOverCount = 0;
	}	

    MAP_TimerIntClear(TIMER3_BASE, (TIMER_CAPA_EVENT | TIMER_TIMA_TIMEOUT));
}

What I believe the ISR I have written does is take the period of every other square wave signal period. What I mean is, it counts the time from one rising edge to another, then ignores a full wave, then proceeds with the cycle of sampling one period, ignoring one and so on. However, the program is doing something completely different which I just can't figure out.

My test is a 100 Hz square with a duty cycle of 50% into PM4. I can't say for sure what period the program is calculating due to the fact that the values vary greatly from one another. I was curious to know if anyone here could lend me a hand with my 

Below is the configuration code:

void edge_timer_init(void)
{
    // Enable port peripherals (pins used for timer for my purpose)
    MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOM);

    // Assign Port M Pin 2 as timer pin for Timer 3.
    MAP_GPIOPinTypeTimer(GPIO_PORTM_BASE, GPIO_PIN_2);
    MAP_GPIOPinConfigure(GPIO_PM2_T3CCP0);

    // Enable pullup on port pins for additional proteciton and accuracy in input.
    MAP_GPIOPadConfigSet(GPIO_PORTM_BASE, GPIO_PIN_2, GPIO_STRENGTH_2MA, GPIO_PIN_TYPE_STD_WPU);

    // Enable Timer 3 peripheral - used for the edge timer mode.
    MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER3);
    
    // Enable processor to call interrupts when interrupts specified in vector file are called.
    MAP_IntMasterEnable();

    // Configure timer 3 for edge-time capture on rising edges
    MAP_TimerConfigure(TIMER3_BASE, (TIMER_CFG_SPLIT_PAIR | TIMER_CFG_A_CAP_TIME_UP));
    MAP_TimerControlEvent(TIMER3_BASE, TIMER_A, TIMER_EVENT_POS_EDGE);

    MAP_IntEnable(INT_TIMER3A);
    MAP_TimerIntEnable(TIMER3_BASE, (TIMER_CAPA_EVENT | TIMER_TIMA_TIMEOUT)); 

    MAP_TimerEnable(TIMER3_BASE, TIMER_A);
}

I believe my problem lies in the logic I'm using. Is there a better way of going about this problem? 
I have considered using two timers - one for the edge-time mode and one for counting roll overs, but isn't this possible with two half-width timers?

I am trying to achieve this all on an EK-TM4C1294XL development board if that helps.

  • Hello Stephen,

    First of the ISR has functional flaw.

    Note that you check for the Timer Status and then clear both status. So if one of the status is not true the if statement is skipped, but if it does become true after that it gets cleared. I would have that corrected first.

    Secondly since you want to measure duty cycle you need to capture both edges and use the timeout as a reference when the counter rolls over.

    Thirdly you can read the IO status for high or low to see if it is the rising or falling edge.

    Regards
    Amit
  • 1) I did not notice that, I will report back with an updated code. Thanks!
    2) My intention was to use two timers to acquire the duty cycle of the square wave input. One timer for measuring the period and one timer for acquiring the high time/low time. Then calculating the duty cycle by (High_time/Period) * 100.
    3) I'm not entirely sure what you mean by this, I thought I had already done this by configuring the timer input pin by reading edges.

    Thank you very much for the quick reply!

    Cheers,
    Stephen
  • Hello Stephen

    When using 2 timers the input signal would be connected to 2 GPIO for 2 Timers. The other way is to connect it to a single Timer and then use the GPIO Module GPIOPinRead to check if the edge is falling or rising by checking the current pin level. That way you have the time for each edge and the math is simpler.

    Regards
    Amit
  • Here's a very slight update to code where I moved the status clears for the interrupts:

    void Timer3IntHandler(void)
    {
    	// Conditional statement to ensure that this part of the ISR is only triggered in the case
    	// that a timeout/rollover occurs during the "sampling period".
    	if (MAP_TimerIntStatus(TIMER3_BASE, TIMER_TIMA_TIMEOUT) == TIMER_TIMA_TIMEOUT)
    	{
    		// Ensures that the program is only counting rollovers during a sampling period.
    		// Ignore rollovers during non-sampling periods.
    		if (captureFlag == 1)
    		{
    			// Updates a global variable when a rollover** occurs during a sampling period.
    			// ** Rollovers - When timer reaches max value (2**16 in this case) then restarts at 0x00.
    			// ** This is only in the case that the timer is configured in timer count up mode.
    			rollOverCount ++;			
    		}
    		// Clear timeout event interrupt status once serviced.
    		MAP_TimerIntClear(TIMER3_BASE, TIMER_TIMA_TIMEOUT);
    	}
    
    	// Conditional statement to ensure that this part of the ISR is only triggered in the case
    	// that edge event occurs.
    	if (MAP_TimerIntStatus(TIMER3_BASE, TIMER_CAPA_EVENT) == TIMER_CAPA_EVENT)
    	{
    		currentSampleCount = MAP_TimerValueGet(TIMER3_BASE, TIMER_A);
    		// Period is calculated from the sum of the current timer value and overflow
    		// All subtracted by the previous sample value.
        	        samplePeriod = (currentSampleCount + (65535 * rollOverCount)) - lastSampleCount;
        	        // Store current value for use in next period calculation.
        	        lastSampleCount = currentSampleCount;
        	        // Toggle sampling period.
        	        // Allows for sampling every other wave/period.
        	        captureFlag ^= 1;
        	        // Rollovers during the sampling period have been used, reset for next sampling period.
        	        rollOverCount = 0;
        	        // Clear edge event interrupt status once serviced.
        	        MAP_TimerIntClear(TIMER3_BASE, TIMER_CAPA_EVENT);
    	}	
    }
    

    I also included as much comments (I should have done so in the beginning) as I could to further clarify my intentions with the code.

    After having run the updated code, I am now noticing the rollover part of the ISR is now being skipped. I know this is the case since I am throwing a 1 Hz signal to the timer, thus the value of the rollover count should be fairly large, but CSS shows it to be zero. A break-point within the conditional statement for the timeout event is also not triggering which indicates that the program does not see a rollover for some reason.

    Coming back to your suggestion of using the GPIOPinRead for rising and falling edges, is the purpose to avoid using the edge event interrupts so the program avoids the confusion between two events (edge event and timeout) in the same timer ISR? As I understand it, the timer could also be configured to look for both edges, would this not be more appropriate than using the GPIO?

    If I were to attempt using the GPIOPinRead function to look for edges, would the code be along the lines of:

    int main(void)
    {
    	while(1)
    	{
    		// Rising edge and currently sampling
    		if previousLevel = 0 and currentLevel = 1 and captureFlag = 1
    		{
    			if firstEdgeCapture != 1
    			{
    				firstRiseEdge = TimerValueGet(TIMER3_BASE, TIMER_A);
    				firstEdgeCapture = 1;
    			}
    			if thirdEdgeCapture != 1
    			{
    				secondRiseEdge = TimerValueGet(TIMER3_BASE, TIMER_A);
    				thirdEdgeCapture = 1;
    			}		
    			previousLevel = currentLevel;
    		}
    		// Falling edge and currently sampling
    		else if previousLevel = 1 and currentLevel = 0 and captureFlag = 1
    		{
    			fallEdge = TimerValueGet(TIMER3_BASE, TIMER_A);
    			previousLevel = currentLevel;
    		}
    		// Sampling period done, calculate duty cycle
    		else if captureFlag = 0
    		{
    			wavePeriod = (secondRiseEdge + (65535 * rollOver)) - firstRiseEdge;
    			waveHiTime = (fallEdge + (65535 * rollOver)) - firstRiseEdge;
    			waveDutyCycle = (waveHiTime/wavePeriod) * 100;
    		}
    	}
    }
    

    I sincerely apologize if I am completely misunderstanding what you are trying to convey.

    Stephen

    EDIT: I finally understood what you meant! I got it working with one timer! Thank you so very much for your help!

  • Hello Stephen,

    Just when I completed the debug of your code, did I see the "Edit". Congrats... And if you may post a version of the code here for future reference?

    Regards
    Amit
  • W/out seeking to "dilute" the joy of victory - achieved via the, "Use of only a single Timer" - we note that (many) timers exist w/in ARM MCUs.   And - KISS dictates that the proper, coordinated sharing of roles among ARM resources - most always results in a faster, easier & more robust implementation.

    Did your testing which enabled, "Got it working w/just one..." include tests at near 0 & 100% duty cycles?   And what about frequency range - would not a, "more expansive" solution prove more usable - to many?

    In specific - it would appear that use of 2 "wide" timers would substantially expand the input frequency range.   (due to the (likely) elimination of timer roll-over)

    And - with two (or more) timers - the dedication of one timer to a "PWM Timebase" - may serve to enable the measure of multiple, PWM channels - not just one!   (three phase motor control - a huge growth area - benefits greatly from such)

    Our firm finds that, "Designing "up front" for proper "Test/Troubleshoot" pays huge dividends.   Forcing the entire function into a single timer complicates such testing - while reducing functional range & utility.

    Trade-offs occur very often in engineering and programming.    When vast resources are not fully & best utilized - that "joy of victory" may not ring as loud - nor last as long - as it should/could...

  • Amit Ashara said:

    Hello Stephen,

    Just when I completed the debug of your code, did I see the "Edit". Congrats... And if you may post a version of the code here for future reference?

    Regards

    Amit

    As per cb1-mobile's reply, I will attempt to use two timers first for a more robust solution and avoid placing code here that isn't "the best". Despite what cb1 has mentioned, people who still want to use the code will have to message me as again, I don't want to post any code that isn't entirely robust.

    Note that while the code does work, it may not be the best way of going about measuring the duty cycle, thus it could be used as a template or an "inspiration". From a very quick test of 20% to 80%  duty cycle (limited by the signal generator) and 1 Hz to 15 Mhz (incrementing by ten-fold), it is abundantly clear that the code has a very limited frequency range at which it can provide an accurate duty cycle measurement. That, or it's most likely the fact that as I reach 10 MHz, the square wave no longer resembles that of a square wave, anyway, the results may vary for everyone.

    EDIT1: I did a slightly more in-depth test of the frequency limit of the code at 20% duty cycle today and found that the program can barely get the duty cycle at 2 Hz and again at 2.1 KHz. So the program has a frequency range of 2 Hz to 2.1 Khz... this puts a damper on things. I guess it could be said that the problem is half solved.

    cb1-mobile said:

    W/out seeking to "dilute" the joy of victory - achieved via the, "Use of only a single Timer" - we note that (many) timers exist w/in ARM MCUs.   And - KISS dictates that the proper, coordinated sharing of roles among ARM resources - most always results in a faster, easier & more robust implementation.

    Did your testing which enabled, "Got it working w/just one..." include tests at near 0 & 100% duty cycles?   And what about frequency range - would not a, "more expansive" solution prove more usable - to many?

    In specific - it would appear that use of 2 "wide" timers would substantially expand the input frequency range.   (due to the (likely) elimination of timer roll-over)

    And - with two (or more) timers - the dedication of one timer to a "PWM Timebase" - may serve to enable the measure of multiple, PWM channels - not just one!   (three phase motor control - a huge growth area - benefits greatly from such)

    Our firm finds that, "Designing "up front" for proper "Test/Troubleshoot" pays huge dividends.   Forcing the entire function into a single timer complicates such testing - while reducing functional range & utility.

    Trade-offs occur very often in engineering and programming.    When vast resources are not fully & best utilized - that "joy of victory" may not ring as loud - nor last as long - as it should/could...

    And there goes the victory dance... until next time then hahaha!

    Being fresh out school and also being inexperienced, small victories such as this causes a bit of an overreaction of joy, but thank you for snapping me back to reality.

    1) Unfortunately no, I did not test near 0 or 100% duty cycles. I am using a signal generator to simulate the square wave input which ultimately will come out of another board capable of 5% to 95% duty cycles (My workplace's old HP 33120A signal generator is only capable of 20% to 80%).

    The objective of the program is to be able to measure the duty cycle of a 200 Hz to 1 MHz signal with duty cycles ranging from 5% to 95%, then eventually display that on a website (I will get to that eventually).

    2) I'm not certain how I would go about utilizing two timers. My solution uses Timer 3A in edge-time mode and a Timer 3B in periodic mode. Timer 3A ISR would obtain the time (timer value) of each edge and Timer 3B ISR would count the rollovers that occur during the "high" time and the period of the sample, from there it's a few calculations to obtain the duty cycle. However to implement two timers, I can foresee one being in edge-time mode and the other in...? Because as you said "... the (likely) elimination of timer roll-over"  there's an unlikely chance that I would see rollovers occurring during sampling.

    Reading from the datasheet, are you suggesting that the the second timer be configured in period-timer mode to ensure missed edge detection?

    3) When you say "wide" timers, what are you referring to? According to the datasheet, the timer could only be configured to edge-time mode when it is in 2 x 16-bit halves. Do you mean using TimerPrescaleSet()? If so, (excuse my complete lack of knowledge) how would you go about using it? I have read the peripheral driver library and know the arguments used in the function, but am unsure what the 0 -255 in the third argument means.

    Thank you,

    Stephen B.

  • Other than the full "copy/paste" of my post - "KISS" did not receive its "due" w/in your writing.    And to me/my small firm - that's a shame.

    Repeatedly - at small firms & even large - we encounter problems which "spring" from the attempt to, "Prove one's smart" by doing too much - too quickly - and neglecting most all areas of, "Test/Troubleshooting."    And - by contrast - the "tried/true" KISS process most always yields better results (certainly more predictable ones) with greater ease & in a shorter time-frame.

    You are unaware of this vendor's "Wide Timers."   Did you perform a "keyword" search?  

    You're uncertain as to how you'd employ 2 timers - but I (pretty much) described that 2nd (wide one) as PWM time-base - did I not?   The key/critical advantage of avoiding (needless) timer roll-over may not impact your relatively simple application (now) - but the requirement to (someday) monitor multiple PWM channels - in strict phase sequence - will welcome the code simplification & enhancement to timer accuracy which wide timers provide.   (they must exist for (some) reason...)

    Most of the questions you now ask are w/in your ability - Amit's guided you thru many - I believe you must spend more time in timing diagrams & thought/analysis.

    This trend to "overly complicate" (one & only one) timer - blends well w/schools' enforcing "DRM only" code style - neither works well in small, tech biz.  

    KISS advises that we use "every resource at hand" to, "Accomplish the mission."   (which must include some focus upon test/troubleshoot) [even if they're just pcb test points, although signal "loopbacks" & monitors ease & speed]    In almost every aspect of business: Finance, Design, Production, Marketing - first breaking the task into smaller, far more manageable chunks - is acknowledged as "best practice."   One & only one (timer) solution - which you could not make fully work - not so much...

    KISS best enables you to get something (almost) working - in the shortest time-frame.   As an ex military officer we learned to "limit our field of battle" - and that's KISS - is it not?  

    Then - as/if needed - you may "proceed by refinement" to improve/expand/reduce component count/cost.   (perhaps even use a single timer...)

  • One of the basic techniques is to use one timer to measure rising edges and a second to measure falling edges. Your measuring limit then becomes period rather than duty cycle.

    Duty cycle is calculated from the difference between the captured timers.

    Robert
  • cb1_mobile said:

    You are unaware of this vendor's "Wide Timers."   Did you perform a "keyword" search?  

    I am developing on a EK-TM4C1294XL Development Board which utilizes a TM4C1294NCPDT MCU.

    According to Amit: "The TM4C129 does not have Wide Timers."

    Source: http://e2e.ti.com/support/microcontrollers/tiva_arm/f/908/p/335138/1171008

    As much as I would like to avoid rollovers, I would need to monitor them as they will mess with duty cycle calculations.

    Robert Adsett said:
    One of the basic techniques is to use one timer to measure rising edges and a second to measure falling edges. Your measuring limit then becomes period rather than duty cycle.

    Duty cycle is calculated from the difference between the captured timers. 

    Robert

    This was my intention from the beginning, I had deviated slightly and unfortunately, over-complicated the logic by limiting myself to one timer as Amit had suggested.

    Anyway, I had finally gotten it to work using "two" timers, the reason I say that is one is merely monitoring for rollovers and all the work is being done by the other timer. As far as I know, this code works quite well for a 10 KHz square wave signal ranging from 5% to 95% duty cycle. However, note that I tested this using the expression watch window on CCS and running/pausing the program at different times. I am still working on understanding the use of TeraTerm and UART to monitor in real time but, that is for another thread.

    I will be posting have posted the .zip file containing the configuration, vector and main code shortly as I am commenting it to make it clearer for other users to utilize. Please feel free to post here of any improvements I could make or you have made to the program for the benefit of other users.

    Duty Cycle Code.zip

  • If you have a capable JTAG Adapter you might consider using SWO rather than a UART. Saves you a few pins if you are not intending the serial as a user interface.

    Robert