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.

TM4C1294NCPDT: Multiple Edge-Time Capture Timer Interrupts Issues

Part Number: TM4C1294NCPDT
Other Parts Discussed in Thread: EK-TM4C1294XL

Hi all,

I'm developing on a custom board that utilizes a TM4C1294NCPDT microcontroller.

My debugging environment is as follows:

- I use the debugging portion of EK-TM4C1294XL Launchpad for programming, debugging and flashing code onto my board

- Code Composer Studio v6

- LM Flash Programming

On my board, I have 6 circuits that convert optical PWM signals into a 10 KHz PWM electrical signals which go into both Timer A and B of all 6 timers (timer0 to timer5).

I noticed recently that using more than 3 inputs at a time (3 timers) decreases the range at which the timers can sense. Using less than 3 inputs at a time is shown to work perfectly fine at 1% to 99% within a reasonable tolerance. When I use 3 or more, then pushing any of the 3 inputs above 93% duty cycle will cause one of the 3 timers (slightly random) to display garbage. I am assuming at this point that the this is mainly due to the timers now essentially interrupting each other causing one the edge time capture registers to capture garbage. Unfortunately, I couldn't find any relevant information in the manual, and I was not able to find a similar regarding my issue.

Any help in solving this problem is greatly appreciated

Cheers,

Stephen B

Relevant files are attached (I attached the wrong configuration file, I will update when I get access to my work laptop. The handler file should be the correct one).

//*****************************************************************************
//
// timerconfiguration.c - Functions used to initialize the timers and components.
//
// This .c file contains the retrieval functions for the counter values of 
// the high time and period of the square wave input. Functions that convert
// the counter values to frequency and duty cycle are also contained within the
// file.
//
//*****************************************************************************
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>

#include "inc/hw_ints.h"
#include "inc/hw_types.h"
#include "inc/hw_memmap.h"
#include "inc/hw_nvic.h"

#include "driverlib/gpio.h"
#include "driverlib/sysctl.h"
#include "driverlib/interrupt.h"
#include "driverlib/rom.h"
#include "driverlib/rom_map.h"
#include "driverlib/pin_map.h"
#include "driverlib/timer.h"

#include "timerhandlers.h"
#include "output.h"

static volatile float pwmDutyCycle[6];
int32_t period[6];
int32_t highTime[6];

//*****************************************************************************
//
// Interrupt handler for events that occur for both halves of the timer
// (Timer A and Timer B). This handler continuously samples the square wave
// input and calculates their high time and period for future calculations.
//
//*****************************************************************************
void Timer0IntHandler(void) {

	static int32_t firstEdge = 0;
	int32_t tempTimerValOne, tempTimerValTwo;

	// Checks if a rising edge event occured in Timer A
	if (MAP_TimerIntStatus(TIMER0_BASE, 0) & TIMER_CAPA_EVENT) {

		MAP_TimerIntClear(TIMER0_BASE, TIMER_CAPA_EVENT);

    	// Obtain the timer value when rising edge occured and calculate period.
		tempTimerValOne = MAP_TimerValueGet(TIMER0_BASE, TIMER_A);
		period[0] = tempTimerValOne - firstEdge;
		firstEdge = tempTimerValOne;

    	// If rollover occurs (causing a negative period value) add 2^24.
		if (period[0] < 0) {
			period[0] += (1<<24);
		} 
	}

	// Checks if a falling edge event occured in Timer B
	if (MAP_TimerIntStatus(TIMER0_BASE, 0) & TIMER_CAPB_EVENT) {

		MAP_TimerIntClear(TIMER0_BASE, TIMER_CAPB_EVENT);

    	// Obtain the timer value when falling edge occured and calculate high time.
		tempTimerValTwo = MAP_TimerValueGet(TIMER0_BASE, TIMER_B);
		highTime[0] = tempTimerValTwo - firstEdge;      

    	// If rollover occurs (causing a negative period value) add 2^24.
		if (highTime[0] < 0) {
			highTime[0] += (1<<24);
		}
	}
}

//*****************************************************************************
//
// Interrupt handler for events that occur for both halves of the timer
// (Timer A and Timer B). This handler continuously samples the square wave
// input and calculates their high time and period for future calculations.
//
//*****************************************************************************
void Timer1IntHandler(void) {

	static int32_t firstEdge = 0;
	int32_t tempTimerValOne, tempTimerValTwo;

	// Checks if a rising edge event occured in Timer A
	if (MAP_TimerIntStatus(TIMER1_BASE, 0) & TIMER_CAPA_EVENT) {

		MAP_TimerIntClear(TIMER1_BASE, TIMER_CAPA_EVENT);

    	// Obtain the timer value when rising edge occured and calculate period.
		tempTimerValOne = MAP_TimerValueGet(TIMER1_BASE, TIMER_A);
		period[1] = tempTimerValOne - firstEdge;
		firstEdge = tempTimerValOne;

    	// If rollover occurs (causing a negative period value) add 2^24.
		if (period[1] < 0) {
			period[1] += (1<<24);
		} 
	}

	// Checks if a falling edge event occured in Timer B
	if (MAP_TimerIntStatus(TIMER1_BASE, 0) & TIMER_CAPB_EVENT) {

		MAP_TimerIntClear(TIMER1_BASE, TIMER_CAPB_EVENT);

    	// Obtain the timer value when falling edge occured and calculate high time.
		tempTimerValTwo = MAP_TimerValueGet(TIMER1_BASE, TIMER_B);
		highTime[1] = tempTimerValTwo - firstEdge;      

    	// If rollover occurs (causing a negative period value) add 2^24.
		if (highTime[1] < 0) {
			highTime[1] += (1<<24);
		}
	}
}

//*****************************************************************************
//
// Interrupt handler for events that occur for both halves of the timer
// (Timer A and Timer B). This handler continuously samples the square wave
// input and calculates their high time and period for future calculations.
//
//*****************************************************************************
void Timer2IntHandler(void) {

	static int32_t firstEdge = 0;
	int32_t tempTimerValOne, tempTimerValTwo;

	// Checks if a rising edge event occured in Timer A
	if (MAP_TimerIntStatus(TIMER2_BASE, 0) & TIMER_CAPA_EVENT) {

		MAP_TimerIntClear(TIMER2_BASE, TIMER_CAPA_EVENT);

    	// Obtain the timer value when rising edge occured and calculate period.
		tempTimerValOne = MAP_TimerValueGet(TIMER2_BASE, TIMER_A);
		period[2]= tempTimerValOne - firstEdge;
		firstEdge = tempTimerValOne;

    	// If rollover occurs (causing a negative period value) add 2^24.
		if (period[2] < 0) {
			period[2] += (1<<24);
		} 
	}

	// Checks if a falling edge event occured in Timer B
	if (MAP_TimerIntStatus(TIMER2_BASE, 0) & TIMER_CAPB_EVENT) {

		MAP_TimerIntClear(TIMER2_BASE, TIMER_CAPB_EVENT);

    	// Obtain the timer value when falling edge occured and calculate high time.
		tempTimerValTwo = MAP_TimerValueGet(TIMER2_BASE, TIMER_B);
		highTime[2]= tempTimerValTwo - firstEdge;      

    	// If rollover occurs (causing a negative period value) add 2^24.
		if (highTime[2] < 0) {
			highTime[2] += (1<<24);
		}
	}
}

//*****************************************************************************
//
// Interrupt handler for events that occur for both halves of the timer
// (Timer A and Timer B). This handler continuously samples the square wave
// input and calculates their high time and period for future calculations.
//
//*****************************************************************************
void Timer3IntHandler(void) {

	static int32_t firstEdge = 0;
	int32_t tempTimerValOne, tempTimerValTwo;

	// Checks if a rising edge event occured in Timer A
	if (MAP_TimerIntStatus(TIMER3_BASE, 0) & TIMER_CAPA_EVENT) {

		MAP_TimerIntClear(TIMER3_BASE, TIMER_CAPA_EVENT);

    	// Obtain the timer value when rising edge occured and calculate period.
		tempTimerValOne = MAP_TimerValueGet(TIMER3_BASE, TIMER_A);
		period[3] = tempTimerValOne - firstEdge;
		firstEdge = tempTimerValOne;

    	// If rollover occurs (causing a negative period value) add 2^24.
		if (period[3] < 0) {
			period[3] += (1<<24);
		} 
	}

	// Checks if a falling edge event occured in Timer B
	if (MAP_TimerIntStatus(TIMER3_BASE, 0) & TIMER_CAPB_EVENT) {

		MAP_TimerIntClear(TIMER3_BASE, TIMER_CAPB_EVENT);

    	// Obtain the timer value when falling edge occured and calculate high time.
		tempTimerValTwo = MAP_TimerValueGet(TIMER3_BASE, TIMER_B);
		highTime[3] = tempTimerValTwo - firstEdge;      

    	// If rollover occurs (causing a negative period value) add 2^24.
		if (highTime[3] < 0) {
			highTime[3] += (1<<24);
		}
	}
}

//*****************************************************************************
//
// Interrupt handler for events that occur for both halves of the timer
// (Timer A and Timer B). This handler continuously samples the square wave
// input and calculates their high time and period for future calculations.
//
//*****************************************************************************
void Timer4IntHandler(void) {

	static int32_t firstEdge = 0;
	int32_t tempTimerValOne, tempTimerValTwo;

	// Checks if a rising edge event occured in Timer A
	if (MAP_TimerIntStatus(TIMER4_BASE, 0) & TIMER_CAPA_EVENT) {

		MAP_TimerIntClear(TIMER4_BASE, TIMER_CAPA_EVENT);

    	// Obtain the timer value when rising edge occured and calculate period.
		tempTimerValOne = MAP_TimerValueGet(TIMER4_BASE, TIMER_A);
		period[4] = tempTimerValOne - firstEdge;
		firstEdge = tempTimerValOne;

    	// If rollover occurs (causing a negative period value) add 2^24.
		if (period[4] < 0) {
			period[4] += (1<<24);
		} 
	}

	// Checks if a falling edge event occured in Timer B
	if (MAP_TimerIntStatus(TIMER4_BASE, 0) & TIMER_CAPB_EVENT) {

		MAP_TimerIntClear(TIMER4_BASE, TIMER_CAPB_EVENT);

    	// Obtain the timer value when falling edge occured and calculate high time.
		tempTimerValTwo = MAP_TimerValueGet(TIMER4_BASE, TIMER_B);
		highTime[4] = tempTimerValTwo - firstEdge;      

    	// If rollover occurs (causing a negative period value) add 2^24.
		if (highTime[4] < 0) {
			highTime[4] += (1<<24);
		}
	}
}

//*****************************************************************************
//
// Interrupt handler for events that occur for both halves of the timer
// (Timer A and Timer B). This handler continuously samples the square wave
// input and calculates their high time and period for future calculations.
//
//*****************************************************************************
void Timer5IntHandler(void) {

	static int32_t firstEdge = 0;
	int32_t tempTimerValOne, tempTimerValTwo;

	// Checks if a rising edge event occured in Timer A
	if (MAP_TimerIntStatus(TIMER5_BASE, 0) & TIMER_CAPA_EVENT) {

		MAP_TimerIntClear(TIMER5_BASE, TIMER_CAPA_EVENT);

    	// Obtain the timer value when rising edge occured and calculate period.
		tempTimerValOne = MAP_TimerValueGet(TIMER5_BASE, TIMER_A);
		period[5] = tempTimerValOne - firstEdge;
		firstEdge = tempTimerValOne;

    	// If rollover occurs (causing a negative period value) add 2^24.
		if (period[5] < 0) {
			period[5] += (1<<24);
		} 
	}

	// Checks if a falling edge event occured in Timer B
	if (MAP_TimerIntStatus(TIMER5_BASE, 0) & TIMER_CAPB_EVENT) {

		MAP_TimerIntClear(TIMER5_BASE, TIMER_CAPB_EVENT);

    	// Obtain the timer value when falling edge occured and calculate high time.
		tempTimerValTwo = MAP_TimerValueGet(TIMER5_BASE, TIMER_B);
		highTime[5] = tempTimerValTwo - firstEdge;      

    	// If rollover occurs (causing a negative period value) add 2^24.
		if (highTime[5] < 0) {
			highTime[5] += (1<<24);
		}
	}
}

//*****************************************************************************
//
// 
//
//*****************************************************************************
float retrieveOptiPWMIn(uint8_t output) {
	pwmDutyCycle[output] = ((float) highTime[output]/ (float) period[output]) * 100.00;
	
	if (output < OPTICAL_PWMOUT_COUNT) {
		return pwmDutyCycle[output];
	}
	return 0;
}

  • Hello Stephen

    What is the system clock being configured as?
  • Hi Amit,

    Apologies for the late response.

    My system clock is set via:

        // Run from the PLL at 120 MHz.
        g_ui32SysClock = MAP_SysCtlClockFreqSet((SYSCTL_XTAL_25MHZ | SYSCTL_OSC_MAIN | SYSCTL_USE_PLL | SYSCTL_CFG_VCO_480), 120000000);

    The board clock uses the following schematic:

    Oscillator part number: XLH735025.000000X

    Cheers,

    Stephen

  • Stephen,
    We do similar PWM measurement using a pin configured as TCCP, and configuring a DMA to transfer both a timer count and the pin level whenever there is a transition. Hence, the timing is logged with no CPU consumption, and you can "later" figure out the PWM duty.
    BUT, do you have a special reason to convert an analog signal to PWM for measurement? Do you use it somewhere else? Why not use an ADC solution for the purpose?
    Regards,
  • Bruno Saraiva said:
    timing is logged with no CPU consumption, and you can "later" figure out the PWM duty.

    Greetings Bruno and (my turn) to lobby for "multiple Likes!"

    Crue & I love your recognition & promotion of, "Performing needed processing LATER" - so as not to overwhelm (or reduce) MCU's logging capability.   Such does involve/require (some) indirection & fore-thought - and while NOT always appropriate - surely warrants consideration.

    May I ask if you can (truly) achieve, "(both) timer count & pin level @ transition captures" - FREE from ANY program instruction?   (Crue/I have not employed this vendor's µDMA - thus our question.)

    To your question: "...special reason to convert an analog signal to PWM for measurement?"   Might that conversion exist NOT for measurement - but to best address (another) stage or chip - which welcomes PWM?

  • Hello Stephen

    A quick question. Why is there a single interrupt handler per timer when the two sub-timers are working in 16-bit mode. There must be two interrupt handlers, one each for sub-timer A and B.
  • Greetings cb1, always good to be liked, ain't? At least this is certainly more useful than Zuckerberg's platform... (no?)

     

    cb1 said:
    May I ask if you can (truly) achieve, "(both) timer count & pin level @ transition captures" - FREE from ANY program instruction?

    I shall honestly reply: "I am pretty sure we can". We implemented the solution, made some tests and found nothing that suggests a problem for the past two years of product being used. Honestly, I'd prefer to have created some sort of stress test scenario, but we never had the time.

    Basically, DMA target address is an array with "a few" (at least 4 needed for one PWM duty) measurements. There are two sources: a continuous timer counter and the full GPIO port (two 32-bit DMA transfers per transition event). The transfer does happen as expected, and sets the interrupt flag for dealing with it later. With a bit more of engineering, it is probably possible to define the target array as some kind of ping-pong with a controlling pointer, and have "continuous duty measurement". Instead, we still reconfigure a new 4-transition event at the rate required by our application, not an issue for us.

    cb1 said:
    Might that conversion exist NOT for measurement - but to best address (another) stage or chip

    I would assume so - that's why I asked OP as for any "special reason for the PWM?". Each design is a new adventure, but from a blind eye I would still prefer to obtain the analog measurements in the MCU via it's internal ADC or via a dedicated external one... Probably more precise and possibly cheaper than this pwm conversion... (nope, too raw info to assume anything).

  • Greetings as well. May I ask you to confirm that the "pin-level transition alone" (w/out program code to test & detect said transition) proves sufficient to "automatically then capture the Timer's value?"

    It appears that I'm questioning the µDMAs ability to detect a single GPIO bit's transition - and launch a Timer Capture - as the direct result.

    Is this what your design has sought - and achieved?    And - if such proves true - there must be (some) time penalty between, "Pin level transition and (automatic) Timer Value capture/store."   Have you measured the extent of such an (expected) delay?

    Thanks Bruno for your well thought post & time/attention...

  • cb1 said:
    May I ask you to confirm that the "pin-level transition alone"

    ... opening the project - please standby, not that easy...

    ... reconfiguring global variables after PC was recently reinstalled - please standby...

    ... allowed CCS620 to install updates and restart - please standby...

    ... confirming which product version uses that specific sensor (it was later replaced from PWM to SPI and the old fun is no longer needed) - please standby...

    Found it.

    Yes, I can confirm. The DMA is actually configured to be triggered by the TCCP event, enabled both for rising and lowering edges. Still, the GPIO status is copied as well, so that later software can evaluate if that transition was up or down, and properly figure out the duty.

    	uDMAChannelTransferSet((UDMA_CH18_TIMER1A|UDMA_PRI_SELECT), UDMA_MODE_BASIC,(void *)(TIMER1_BASE + 0x048),encoderInternal1.encoderPulses,4);
    	uDMAChannelTransferSet((UDMA_CH20_TIMER1A|UDMA_PRI_SELECT), UDMA_MODE_BASIC,(void *)(GPIO_PORTF_BASE + 0x010),encoderInternal1.encoderLevel,4);   //0x010 Masks pin 2 only
    

    Given that the DMA is transferring the timer event register, and not the timer counter register, there should be no delay penalty... BUT as I mentioned, it makes total sense in theory and our tests back then have shown enough reliability, but we never produced some error inducing situations or deeper tests... Maybe I'll tell the next internship candidate to propose/implement a test to evaluate if the fellow deserves the position...

  • Hello Bruno

    In this case the OP is making some computation as well for every rising and falling edge. The DMA for every single transfer would be way too overhead intensive. Don't you think so?
  • Amit,
    The computation is done "only" once, after 4 transitions happen - the DMA transfer is what indeed runs on each transition, but without CPU involvement.
    Myself I couldn't ever figure a solution to evaluate PWM duty without timing all the transitions, because the direction of the pulse is also needed. It is different than a simpler frequency measurement where you can just read the time elapsed between two consecutive rising edges... But I'm always looking to learn/discuss new strategies.
    Cheers
  • Taking some time to catch up as I am on and off the computer...

    Bruno Saraiva said:
    ... Do you have a special reason to convert an analog signal to PWM for measurement? Do you use it somewhere else? Why not use an ADC solution for the purpose?

    Hi Bruno,

    It was only to achieve as high accuracy as we possibly could. Additionally, I noticed that such functionality exists to do an edge time capture and figured, "Why not use it?"

    My apologies, I made a huge mix-up, the signal coming in is actually a 10 KHz optical PWM signal. This mix-up may have caused quite a bit of confusion.

    I have edited the original post to reflect this, again, my apologies.

    Bruno Saraiva said:
    Basically, DMA target address is an array with "a few" (at least 4 needed for one PWM duty) measurements. There are two sources: a continuous timer counter and the full GPIO port (two 32-bit DMA transfers per transition event). 

    Surprisingly I had talked to a friend of mine about this same issue and he had mentioned something similar to this. The only difference being that I would only need to have one GPIO that sensed an edge which then captured the time from the continuous running clock. Granted I don't fully understand how to implement this as I have not looked into it aside from skimming the manual.

    cb1 said:
    "Performing needed processing LATER"

    Hi cb1,

    Having come back to review the code that may have had a part in this issue I realize this as well and I have yet to code in a fix for processing at a later time.

    I can understand this being a greater issue when more instructions are involved, but my code appears to do little to warrant such issue. That being said, this is from my untrained mind.

    Forgive me, still a bit of an amateur when it comes down to programming properly and professionally.

    .Amit Ashara said:
     A quick question. Why is there a single interrupt handler per timer when the two sub-timers are working in 16-bit mode? There must be two interrupt handlers, one each for sub-timer A and B.

    Hi Amit,

    I should certainly be doing that. It passed my mind while I was coding as it was easier for me (a bit weird, I'm aware) to see it this way when testing. It's been a while since I last looked at the code for this board.

    All your help is greatly appreciated!

    Cheers all,

    Stephen

  • Bruno Saraiva said:
    configuring a DMA to transfer both a timer count

    Not surprisingly - this reporter is (again) confused.   Quote above notes that "timer count" was captured - yet (NOW) it is revealed that "timer event" (instead) was captured!

    If the "timer event" fails to include the earlier, specified, "timer count" - I'm at a loss to understand the merit of this method...

    Might Bruno or Amit kindly, "Remove some mystery" from this "automatic capture of timer count via µDMA" application?

  • cb1,
    I'm also tired and should call it a day, hence my clarification might serve worse... but let me try!
    The earlier "timer count" was a generic way to refer to, well, a GP Timer counting.... :) Don't consider that one part of the contract.
    If I recall and understood correctly, there are two important registers on each timer related to counting: one always contains the current counting, and the other freezes with the value that the counter had when a predetermined event happened (GPTMTAV and GPTMTAR, right?).
    The proposed/implemented configuration copies the "frozen value" to the memory array, via DMA. It also copies the GPIO status at that moment. After 4 transitions, you have enough information to precisely read the elapsed time between each of the 4 events, and based on status (high/low) of the GPIO on those instants, to figure out which "percentage" reflected the mountain and which reflected the valley!
    Do I make any sense?
  • Hello Stephen,

    Could you please check the same?
  • Thank you Bruno, appreciated. (We'd be (closer) in time zone if you were (still) in Brazil - yet (then) you'd be further from Luis...)

    We've used the Timer features of these (and other, ARM MCUs) and it would prove of interest to learn, "If, how, and by what extent" the µDMA "aids, automates, or possibly adds error" to this calculation process. Our interest is the comparison to your µDMA results vs. a short code routine - operating w/out the benefit (or burden) of µDMA. The fact that you're capturing just four events - at least to me - points to the µDMA as not enjoying its full "value added."
  • cb1 said:
    The fact that you're capturing just four events - at least to me - points to the µDMA as not enjoying its full "value added."

    Not really!

    If the PWM is at an "extremely" high or low duty cycle, the up and down transitions are too close to each other. If the CPU is busy during the transition serving something else, it might not attend the ISR fast enough before the opposite coming transition - and the measurement would be lost, for the "frozen" register would then have the content replaced before being read.

    Letting DMA do the job makes this a deterministic operation, and you can actually document the minimum or maximum duty cycle that a PWM signal can be measured (which I haven't formally calculated/researched, but it is something related to the number of CPU clocks required to actually perform the two 32bit DMA transfers right after the triggering event).

    Then again, you can go deeper into the DMA settings and create a rotating buffer or a ping-pong structure, and let it continuously capture the PWM transitions, being able to evaluate the cycle "at any desired moment" - for valid, recent and sufficient data will exist on the arrays.

  • Bruno Saraiva said:
    If the CPU is busy during the transition serving something else, it might not attend the ISR fast enough before the opposite coming transition

    We note that you've "biased" your response by choosing "worst case" for the (ISR driven) MCU processing alternative.   I'd expect that there is (some) time (or other) penalty extracted by µDMA - it is rare to find any engineering system/sub-system - executing w/out "trade-offs."

    From my/group's (admittedly) disjointed & sparse readings of this vendor's µDMA implementation - we expect it to be at maximum effectiveness when, "far larger amounts of data are collected."

    You note the (very) special case of "Duty Cycle @ extremes."   And - firm/I have dealt w/that via impressing the input PWM Signal upon TWO Different MCU Timer pins - each dedicated to a (different) pin transition.   (we HAVE measured and confirmed "improved" results achieved under this method)

    I'll take this opportunity to note that "on many occasions" the requirement for (near) 100% PWM Duty - results from inadequate Power Supply and/or Signal Design.   In our fields (Industrial, Medical, Scientific) the need for 100% Duty Cycle - more often than not - results from, "inadequate power and/or signal provisioning" - up front. 

    Both "ISR & µDMA" impose (some) overhead upon the measurement process - it would be of interest to learn the (real) extent of each.    (i.e. what error results under each process?)   

    You write that you've not (yet) "calculated/researched" - I must remind you that neither you/me or 95%+ here can qualify as, "Researchers!"   We are "Investigators" at best - visit the taxing authority documents/web-site to learn what (really) qualifies as "Research."   Venture Capital firms will "see you quickly to the door" should you state "Research" - which is "gross (and unearned) over-statement" - 99% of the time...   Schools monumentally MISUSE the term - which causes great resentment among those who have EARNED such achievement & title!

    I do not believe the case for µDMA - in this specific PWM Measurement task - (especially when compared to our "Dual Timer Usage") has been made!   (i.e. no measurement "facts" support such, "Fully Deterministic" claim - and for serious purpose - such should be presented!)

  • cb1,

    I'll set up a testing environment in a few weeks, just for the engineering of it...

    I did like your concept of splitting the PWM signal into two timer pins (and enabling opposite transition interrupts for each of them).

    For now, I just did some quick theoretical math to further consider the challenges given by OP, noting that he uses a 10KHz pwm signal, and that he "would like to improve the measurement as much as possible":

    - The MCU is a TM4C1294. He can/shall use the maximum 120MHz clock.
    - A 10KHz PWM will require his attention once every 12000 clock cycles - otherwise, he will skip measurements.
    - In theory, the two-pin configuration can't measure more than 13 bits of resolution - which of course does not mean much, as there are lots of other things to go wrong in the analog signal before becoming a PWM and reaching the MCU.
    - With the DMA alone into a single pin, such resolution is impossible - because we need to log one transition moment before the next happens. Even if the DMA was done in 2 cycles, we could achieve at most 12 bit resolution with one pin. Or 11 bit, if it transfers in 5 cycles... I don't know how many are actually needed, nor will investigate now. The one-pin advantage here is that hardware can remain unchanged if board is already built... (in my particular case, I would not have one single extra pin available on the MCU, so no hope...)

    With two pins TOGETHER with DMA, there is one final advantage: the improved resolution of your method, and the fact that we don't need to compute the measurement before those 12000 cycles. That might be interesting in his case, as he mentions several signals being measured.

    There is one detail to consider, however: does poster actually need to use 10KHz DMA? If he can get away with 1KHz DMA, a single pin strategy can achieve 14 bits resolution depending on the DMA transfer to happen within 7 cycles, which is probably the case (or as far as 15 bits if the transfer happens in 3 cycles).

    These numbers will probably make no sense to me next time I see them...

    Cheers,
  • Bruno,

    Thank you - yours is a quick yet impressive analysis. One thing though - poster did note that his input signal WAS PWM - his (earlier) use of analog signal - he (later) admitted - was mistaken.

    While that MCU appears as chosen - other MCUs (even lower cost) perform @ 180MHz - which reduces error. (adds 1 bit to your 13)    World is global - casting a "wider tech net" often proves advantageous!   (and is the nature of my firm's tech business)

    Here's another point in favor of the, "Two pin, common PWM signal input, ISR trigger method": as it is likely that the "PWM Measurement" is central to user's application he can apply the highest priority to the Timer-based interrupts AND "block" (other) tasks during such PWM acquisitions.   Under such conditions - the totality of ISR (and other) delays may be calculated - and then "subtracted out" - rendering the PWM measurement (almost) Deterministic!

    Firm/I are not against use of µDMA - we do believe a chart comparing/contrasting µDMA's measured (not theoretical/calculated) performance against "known" alternatives (i.e. far simpler, better known, "ISR") would better justify the "extra" time/attention/effort which µDMA forces upon a user...

  • Apologies for slow responses, the nature of my job requires me to handle several tasks at once and this is at the bottom of it (not my choice). I am putting every bit of effort to answer your questions and will update my status when I can.

    Amit Ashara said:
    Could you please check the same.

    Hi Amit,

    I was in the middle of rewriting the code with separate handlers, I will get to posting that as soon as I am done.

    Bruno, cb1,

    I am learning a surprising amount from your conversations, cheers!

    Bruno Saraiva said:
    ... does poster actually need to use 10KHz DMA? If he can get away with 1KHz DMA

    Unfortunately, I do, the board is made to be as general as possible so as to be used by multiple systems in the company. 10 KHz is the most frequent PWM signal used in the diagnostics and thus was decided to be what I coded around. I do not know the specifics as to why that is the case and so I have not argued the merits of going with a lower, 1 KHz signal.

  • Hi all,

    It's been 2 and half months, and I have been put back into to this project and I have asked to work on it until the issues have been fixed so as to avoid these long periods of absolute silence.

    Since the last reply to this thread, I have done the following to the program:

    1. I have updated the timerhandler.c file by separating the TimerxIntHandlers() to two halves (TimerxAIntHandler() and TimerxBIntHandler()). This improved the range at which I can input a 10 KHz PWM from 1% - 93% up to 1% - 95% before the duty cycles start calculating garbage. (Less important, but I have also updated the timerhandlers.h file to reflect the TimerxIntHandler() changes.)
    2. I have attempted to remove the calculations from the interrupts (placing a calculation handler in the while(1){} main loop) but ended up with complete garbage values. I assume this has something to do with needed to service the data immediately before it is written over by another interrupt call.
    3. I have lowered the input 10 KHz PWM input down to 1 KHz for experimental purposes - the program was able to handle the full range of 1% to 99% without any hiccups.
    4. Another attempt to this issue was to disable timer interrupts which are currently not being serviced. Once the program has entered a timer interrupt, the program will not service another interrupt, unless it has finished with its current interrupt (avoiding layers upon layers of interrupts occurring). I thought I may have done this using IntMasterDisable(), but I'm not 100% if it is doing its job.

    To reiterate the issue:

    On a custom PCB, utilising a TM4C1294NCPDT microcontroller, a 10 KHz PWM input is causing garbage values to display when 3 or more timer inputs hit 96% duty cycle.

    Any and all help is greatly appreciated.

    Below you can find the files mentioned as well as a picture of the values I attain at "buggy range". I have also included the configuration files which are relevant to the timers.

    Cheers, 

    Stephen B.

    Note(s):

    • 02 to 07 are the timer inputs
    • When the program hits the "buggy range" the value is always in the 139800 (decimal float) area.

    //*****************************************************************************
    //
    // timerconfiguration.c - Functions used to initialize the timers and components.
    //
    // This .c file contains the retrieval functions for the counter values of 
    // the high time and period of the square wave input. Functions that convert
    // the counter values to frequency and duty cycle are also contained within the
    // file.
    //
    //*****************************************************************************
    #include <stdbool.h>
    #include <stdint.h>
    #include <stdio.h>
    
    #include "inc/hw_ints.h"
    #include "inc/hw_types.h"
    #include "inc/hw_memmap.h"
    #include "inc/hw_nvic.h"
    
    #include "driverlib/interrupt.h"
    #include "driverlib/rom_map.h"
    #include "driverlib/timer.h"
    
    #include "timerhandlers.h"
    #include "output.h"
    
    volatile struct timerStruct{
    	int32_t period;
    	int32_t highTime;
    } timerData[6];
    
    static volatile int32_t timerVal[6];
    static volatile float pwmDutyCycle[6];
    static volatile int32_t prevEdge[6];
    
    const int32_t TIMER_TABLE[6] = {TIMER0_BASE, TIMER1_BASE, TIMER2_BASE, TIMER3_BASE, TIMER4_BASE, TIMER5_BASE};
    
    //*****************************************************************************
    //
    // Handles calculation of High Time and Period of each square wave.
    //
    //******************************************************************************
    void calculatePeriod(uint8_t timer) {
    	MAP_IntMasterDisable();
    	int32_t tempPrev = prevEdge[timer];
    
    	MAP_TimerIntClear(TIMER_TABLE[timer], TIMER_CAPA_EVENT);
    
    	int32_t tempCurrent = MAP_TimerValueGet(TIMER_TABLE[timer], TIMER_A);
    	int32_t tempPeriod = tempCurrent - tempPrev;
    	prevEdge[timer] = tempCurrent;
    
    	// If rollover occurs (causing a negative period value) add MSB of 24 bits.
    	if (tempPeriod < 0) {
    		tempPeriod += (1<<24);
    	}
    
    	timerData[timer].period = tempPeriod;
    	MAP_IntMasterEnable();
    }
    
    //*****************************************************************************
    //
    // Handles calculation of high time of the input square wave.
    //
    //******************************************************************************
    void calculateHighTime(uint8_t timer) {
    	MAP_IntMasterDisable();
    	int32_t tempPrev = prevEdge[timer];
    
    	MAP_TimerIntClear(TIMER_TABLE[timer], TIMER_CAPB_EVENT);
    	int32_t tempCurrent = MAP_TimerValueGet(TIMER_TABLE[timer], TIMER_B);
    	int32_t tempHigh = tempCurrent - tempPrev;
    
    	// If rollover occurs (causing a negative high time value) add MSB of 24 bits.
    	if (tempHigh < 0) {
    		tempHigh += (1<<24);
    	}
    
    	timerData[timer].highTime = tempHigh;
    	MAP_IntMasterEnable();
    }
    
    //*****************************************************************************
    //
    // Calculates duty cycle.
    //
    //******************************************************************************
    void calculateDutyCycle(uint8_t timer) {
    
    	int32_t tempHigh = timerData[timer].highTime;
    	int32_t tempPeriod = timerData[timer].period;
    	pwmDutyCycle[timer] = ((float) tempHigh/ (float) tempPeriod) * 100.00;
    }
    
    //*****************************************************************************
    //
    // Retrieves the calculated duty cycle when an HTTP POST request occurs.
    //
    //*****************************************************************************
    float retrieveOptiPWMIn(uint8_t output) {
    	calculateDutyCycle(output);
    	
    	if (output < OPTICAL_PWMOUT_COUNT) {
    		return pwmDutyCycle[output];
    	}
    	return 0;
    }
    
    //*****************************************************************************
    //
    // Interrupt handler for events that occur for  Timer 0A and Timer 0B.
    //
    //******************************************************************************
    void Timer0AIntHandler(void) {
    	calculatePeriod(0);
    }
    
    void Timer0BIntHandler(void) {
    	calculateHighTime(0);
    }
    
    //*****************************************************************************
    //
    // Interrupt handlers for events that occur for Timer 1A and 1B
    //
    //******************************************************************************
    void Timer1AIntHandler(void) {
    	calculatePeriod(1);
    }
    
    void Timer1BIntHandler(void) {
    	calculateHighTime(1);
    }
    
    //*****************************************************************************
    //
    // Interrupt handlers for events that occur for Timer 2A and 2B
    //
    //******************************************************************************
    void Timer2AIntHandler(void) {
    	calculatePeriod(2);
    }
    
    void Timer2BIntHandler(void) {
    	calculateHighTime(2);
    }
    
    
    //*****************************************************************************
    //
    // Interrupt handlers for events that occur for Timer 3A and 3B
    //
    //******************************************************************************
    void Timer3AIntHandler(void) {
    	calculatePeriod(3);
    }
    
    void Timer3BIntHandler (void) {
    	calculateHighTime(3);
    }
    
    //*****************************************************************************
    //
    // Interrupt handlers for events that occur for Timer 4A and 4B
    //
    //******************************************************************************
    void Timer4AIntHandler(void) {
    	calculatePeriod(4);
    }
    
    void Timer4BIntHandler(void) {
    	calculateHighTime(4);
    }
    
    //*****************************************************************************
    //
    // Interrupt Handlers for events that occur for Timer 5A and 5B
    //
    //******************************************************************************
    void Timer5AIntHandler(void) {
    	calculatePeriod(5);
    }
    
    void Timer5BIntHandler(void) {
    	calculateHighTime(5);
    }
    

    timerhandlers.h

    //*****************************************************************************
    //
    // timerconfiguration.c - Functions used to initialize the timers and components.
    //
    // This .c file contains the retrieval functions for the counter values of 
    // the high time and period of the square wave input. Functions that convert
    // the counter values to frequency and duty cycle are also contained within the
    // file.
    //
    //*****************************************************************************
    #include <stdbool.h>
    #include <stdint.h>
    #include <stdio.h>
    
    #include "inc/hw_ints.h"
    #include "inc/hw_types.h"
    #include "inc/hw_memmap.h"
    #include "inc/hw_nvic.h"
    
    #include "driverlib/gpio.h"
    #include "driverlib/sysctl.h"
    #include "driverlib/interrupt.h"
    #include "driverlib/rom.h"
    #include "driverlib/rom_map.h"
    #include "driverlib/pin_map.h"
    #include "driverlib/timer.h"
    
    #include "timerconfiguration.h"
    
    //*****************************************************************************
    //
    // Initialize the two timers used for measuring the duty cycle
    //
    //*****************************************************************************
    void initTimer(void) {
    
    	MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);	// Timer 5 and Timer 4 inputs
    	MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);	// Timer 3, Timer 2 and Timer 1 inputs
    	MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOD);	// Timer 0 input
    
    	// Configures the each half of the timer to its corresponding pins
    	MAP_GPIOPinTypeTimer(GPIO_PORTB_BASE, (GPIO_PIN_3 | GPIO_PIN_2 | GPIO_PIN_1 | GPIO_PIN_0));
    	MAP_GPIOPinTypeTimer(GPIO_PORTA_BASE, (GPIO_PIN_7 | GPIO_PIN_6 | GPIO_PIN_5 | GPIO_PIN_4 | GPIO_PIN_3 | GPIO_PIN_2));
    	MAP_GPIOPinTypeTimer(GPIO_PORTD_BASE, (GPIO_PIN_1 | GPIO_PIN_0));
    
    	MAP_GPIOPinConfigure(GPIO_PB3_T5CCP1);		// PWMFIB1
    	MAP_GPIOPinConfigure(GPIO_PB2_T5CCP0);
    
    	MAP_GPIOPinConfigure(GPIO_PB1_T4CCP1);		// PWMFIB2
    	MAP_GPIOPinConfigure(GPIO_PB0_T4CCP0);
    
    	MAP_GPIOPinConfigure(GPIO_PA7_T3CCP1);		// PWMFIB3
    	MAP_GPIOPinConfigure(GPIO_PA6_T3CCP0);
    
    	MAP_GPIOPinConfigure(GPIO_PA5_T2CCP1);		// PWMFIB4
    	MAP_GPIOPinConfigure(GPIO_PA4_T2CCP0);
    
    	MAP_GPIOPinConfigure(GPIO_PA3_T1CCP1);		// PWMFIB5
    	MAP_GPIOPinConfigure(GPIO_PA2_T1CCP0);
    
    	MAP_GPIOPinConfigure(GPIO_PD1_T0CCP1);		// PWMFIB6
    	MAP_GPIOPinConfigure(GPIO_PD0_T0CCP0);
    
    	// Enable all timer module peripherals
    	MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0);
    	MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER1);
    	MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER2);
    	MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER3);
    	MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER4);
    	MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER5);
    
    	// Enable processor interrupts to occur
    	MAP_IntMasterEnable();
    
    	// Configure Timers into two 16-bit timers in edge time count up mode
    	// Configure each half to trigger an interrupt to occur at each edge of a the square wave input
    	MAP_TimerConfigure(TIMER5_BASE, (TIMER_CFG_SPLIT_PAIR | TIMER_CFG_A_CAP_TIME_UP | TIMER_CFG_B_CAP_TIME_UP));
    	MAP_TimerControlEvent(TIMER5_BASE, TIMER_A, TIMER_EVENT_POS_EDGE);
    	MAP_TimerControlEvent(TIMER5_BASE, TIMER_B, TIMER_EVENT_NEG_EDGE);
    
    	MAP_TimerConfigure(TIMER4_BASE, (TIMER_CFG_SPLIT_PAIR | TIMER_CFG_A_CAP_TIME_UP | TIMER_CFG_B_CAP_TIME_UP));
    	MAP_TimerControlEvent(TIMER4_BASE, TIMER_A, TIMER_EVENT_POS_EDGE);
    	MAP_TimerControlEvent(TIMER4_BASE, TIMER_B, TIMER_EVENT_NEG_EDGE);
    
    	MAP_TimerConfigure(TIMER3_BASE, (TIMER_CFG_SPLIT_PAIR | TIMER_CFG_A_CAP_TIME_UP | TIMER_CFG_B_CAP_TIME_UP));
    	MAP_TimerControlEvent(TIMER3_BASE, TIMER_A, TIMER_EVENT_POS_EDGE);
    	MAP_TimerControlEvent(TIMER3_BASE, TIMER_B, TIMER_EVENT_NEG_EDGE);
    
    	MAP_TimerConfigure(TIMER2_BASE, (TIMER_CFG_SPLIT_PAIR | TIMER_CFG_A_CAP_TIME_UP | TIMER_CFG_B_CAP_TIME_UP));
    	MAP_TimerControlEvent(TIMER2_BASE, TIMER_A, TIMER_EVENT_POS_EDGE);
    	MAP_TimerControlEvent(TIMER2_BASE, TIMER_B, TIMER_EVENT_NEG_EDGE);
    
    	MAP_TimerConfigure(TIMER1_BASE, (TIMER_CFG_SPLIT_PAIR | TIMER_CFG_A_CAP_TIME_UP | TIMER_CFG_B_CAP_TIME_UP));
    	MAP_TimerControlEvent(TIMER1_BASE, TIMER_A, TIMER_EVENT_POS_EDGE);
    	MAP_TimerControlEvent(TIMER1_BASE, TIMER_B, TIMER_EVENT_NEG_EDGE);
    
    	MAP_TimerConfigure(TIMER0_BASE, (TIMER_CFG_SPLIT_PAIR | TIMER_CFG_A_CAP_TIME_UP | TIMER_CFG_B_CAP_TIME_UP));
    	MAP_TimerControlEvent(TIMER0_BASE, TIMER_A, TIMER_EVENT_POS_EDGE);
    	MAP_TimerControlEvent(TIMER0_BASE, TIMER_B, TIMER_EVENT_NEG_EDGE);
    
    	// Effectively extends the range of the timer to allow for signal acquisition as low as 15 Hz
    	MAP_TimerPrescaleSet(TIMER5_BASE, TIMER_A, 255);
    	MAP_TimerPrescaleSet(TIMER5_BASE, TIMER_B, 255);
    
    	MAP_TimerPrescaleSet(TIMER4_BASE, TIMER_A, 255);
    	MAP_TimerPrescaleSet(TIMER4_BASE, TIMER_B, 255);
    
    	MAP_TimerPrescaleSet(TIMER3_BASE, TIMER_A, 255);
    	MAP_TimerPrescaleSet(TIMER3_BASE, TIMER_B, 255);
    
    	MAP_TimerPrescaleSet(TIMER2_BASE, TIMER_A, 255);
    	MAP_TimerPrescaleSet(TIMER2_BASE, TIMER_B, 255);
    
    	MAP_TimerPrescaleSet(TIMER1_BASE, TIMER_A, 255);
    	MAP_TimerPrescaleSet(TIMER1_BASE, TIMER_B, 255);
    
    	MAP_TimerPrescaleSet(TIMER0_BASE, TIMER_A, 255);
    	MAP_TimerPrescaleSet(TIMER0_BASE, TIMER_B, 255);
    
    	// Enables the interrupts to occur
    	MAP_IntEnable(INT_TIMER5A);
    	MAP_IntEnable(INT_TIMER5B);
    
    	MAP_IntEnable(INT_TIMER4A);
    	MAP_IntEnable(INT_TIMER4B);
    
    	MAP_IntEnable(INT_TIMER3A);
    	MAP_IntEnable(INT_TIMER3B);
    
    	MAP_IntEnable(INT_TIMER2A);
    	MAP_IntEnable(INT_TIMER2B);
    
    	MAP_IntEnable(INT_TIMER1A);
    	MAP_IntEnable(INT_TIMER1B);
    
    	MAP_IntEnable(INT_TIMER0A);
    	MAP_IntEnable(INT_TIMER0B);	
    
    	// Enable timer interrupt for the events set earlier
    	MAP_TimerIntEnable(TIMER5_BASE, TIMER_CAPA_EVENT);
    	MAP_TimerIntEnable(TIMER5_BASE, TIMER_CAPB_EVENT);
    
    	MAP_TimerIntEnable(TIMER4_BASE, TIMER_CAPA_EVENT);
    	MAP_TimerIntEnable(TIMER4_BASE, TIMER_CAPB_EVENT);
    
    	MAP_TimerIntEnable(TIMER3_BASE, TIMER_CAPA_EVENT);
    	MAP_TimerIntEnable(TIMER3_BASE, TIMER_CAPB_EVENT);
    
    	MAP_TimerIntEnable(TIMER2_BASE, TIMER_CAPA_EVENT);
    	MAP_TimerIntEnable(TIMER2_BASE, TIMER_CAPB_EVENT);
    
    	MAP_TimerIntEnable(TIMER1_BASE, TIMER_CAPA_EVENT);
    	MAP_TimerIntEnable(TIMER1_BASE, TIMER_CAPB_EVENT);
    
    	MAP_TimerIntEnable(TIMER0_BASE, TIMER_CAPA_EVENT);
    	MAP_TimerIntEnable(TIMER0_BASE, TIMER_CAPB_EVENT);
    
    	// Enable timers in the program
    	MAP_TimerEnable(TIMER5_BASE, (TIMER_A | TIMER_B));
    	MAP_TimerEnable(TIMER4_BASE, (TIMER_A | TIMER_B));
    	MAP_TimerEnable(TIMER3_BASE, (TIMER_A | TIMER_B));
    	MAP_TimerEnable(TIMER2_BASE, (TIMER_A | TIMER_B));
    	MAP_TimerEnable(TIMER1_BASE, (TIMER_A | TIMER_B));
    	MAP_TimerEnable(TIMER0_BASE, (TIMER_A | TIMER_B));
    
    /*
    	MAP_TimerSynchronize(TIMER0_BASE, 
    		(TIMER_0A_SYNC | TIMER_0B_SYNC
    		| TIMER_1A_SYNC | TIMER_1B_SYNC
    		| TIMER_2A_SYNC | TIMER_2B_SYNC
    		| TIMER_3A_SYNC | TIMER_3B_SYNC
    		| TIMER_4A_SYNC | TIMER_4B_SYNC
    		| TIMER_5A_SYNC | TIMER_5B_SYNC));
    */
    }
    
    

    6470.timerconfiguration.h