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.

Timer PWM mode - 100% and 0% duty cycle

Other Parts Discussed in Thread: TM4C1292NCPDT

Hi there,


I'm using TM4C1292NCPDT's Timerst to generate a pwm signal with a inverted CCP output signal.
The datasheet says that the CCP output should be high if the timer match register has a greater value than the timer interval load register. In case the match and interval load registers have the same value the output should be low (in my case it's the other way around because CCP is inverted!).

Now the problem is, in both cases my CCP output is low. I already checkt this by scoping and reading the registers (including the precaler and the match prescaler).

Is this a known problem? Is there any solution apart from changing the GPIO configuration?

Regards

Daniel

  • Hello Daniel,

    So if I read the post correctly, the value of the CCP output is low even if the Match and Load Registers are the same or not?

    If that is the case can you check that the CCP pin corresponds to the correct sub-timer A or B

    Regards

    Amit

  • Hi Amit,


    thx for your reply! Sry, I forgot to mention that. I use the subtimer B and therefore the CCP1 pin.

    Datasheet says:
    GPTMnILR = GPTMnMATCH => CCP = LOW
    GPTMnILR < GPTMnMATCH => CCP = HIGH

    My observation is:
    GPTMnILR = GPTMnMATCH => CCP = LOW
    GPTMnILR < GPTMnMATCH => CCP = LOW

    Do I need to configure the subtimer A match and interval load registers as well to match the conditions described in the datasheet? Though I use the subtimer B, only?

    Regards
    Daniel

  • Hello Daniel,

    In 16-bit mode each sub-timer is independent of the other. Can you please attach the code.

    Regards

    Amit

  • Yes, of cause!

    The PWM initialisation:

    /**
    ****************************************************************************
    *
    \b Name:    Bi_PWMInit
    *
    * \brief    Initialize PWM module.
    *
    * This function initializes one of the four possible PWM modules. Each of
    * them requires a GPIO output and a timer. GPIO ant timer configuration
    * depend on the \e PWMName parameter. This module uses the following
    * recources:
    *   - ::PWM_0: Timer0, PL5 (IO_PAR0)
    *   - ::PWM_1: Timer3, PD5 (IO_PAR1)
    *   - ::PWM_2: Timer4, PM5 (IO_PAR2)
    *   - ::PWM_3: Timer5, PM7 (IO_PAR3)
    *
    * All PWM modules will be set to 1ms period (1kHz) and 50% duty cycle by
    * default. These default values are defined by PWM_DEFAULT_PERIOD and
    * PWM_DEFAULT_DUTY in Bi_PWM.h.
    *      
    * \param   	PWMName determines the PWM module to initialize. Possible values
    *                   are defined in enumerated type ::pwmID.
    *
    * \return   void
    *
    * \note     The TM4C1292 processor has also got a dedicated PWM module, but
    *           its possible outputs are used by other modules. Therefore timers
    *           are used to generate PWM signals.
    *
    ****************************************************************************/
    void Bi_PWMInit(const pwmID PWMName)
    {
        U32 TimerBase = PWMGetBase(PWMName);
        
    	if (TimerBase != 0)
    	{
    		// Power timer and GPIO port on and activate configure GPIO pin.
    		PWMPowerOnPinConfig(PWMName);
            
            // Disable all timers => therefore PWM is stopped
    		TimerDisable(TimerBase, TIMER_BOTH);
    
    		// Set timer clock source to system clock (120Mhz)
    		TimerClockSourceSet(TimerBase, TIMER_CLOCK_SYSTEM);
    		
    		// Configure TimerB to PWM timer (counts down)
    		TimerConfigure(TimerBase, TIMER_CFG_SPLIT_PAIR|TIMER_CFG_B_PWM);
    		
            // Invert PWM output => active-low
            TimerControlLevel(TimerBase, TIMER_B, true);
            
            // Update match register after time-out and therefore not during PWM cycle
            TimerUpdateMode(TimerBase, TIMER_B, TIMER_UP_MATCH_TIMEOUT);
    		
            // Configure default PWM but do not start PWM
    		Bi_PWMSetPeriod(PWMName, PWM_DEFAULT_PERIOD);   // Period = 1ms => 1kHz
    		Bi_PWMSetDutyCyc(PWMName, PWM_DEFAULT_DUTY);	// Duty = 50% => 500µs
    	}
    }

    The local PWMPowerOnPinConfig() function:

    /**
    ****************************************************************************
    *
    \b Name:    PWMPowerOnPinConfig
    *
    * \brief    Turn peripheral's power on.
    *
    * This function turns on all required peripherals, timer and GPIO port, for
    * PWM operation. It also configures the corresponding GPIO pin to be a
    * capture and compare (CCP) output.
    *
    * \param   	PWMName determines the PWM module. Possible values are defined
    *                   in enumerated type ::pwmID.
    *
    * \return   void 
    *
    * \warning  This function waits until peripherals are ready!
    *
    ****************************************************************************/
    void PWMPowerOnPinConfig(const pwmID PWMName)
    {
        U32 SysCtlTimer;
        U32 SysCtlPort;
        U32 PortBase;
        U32 PinNo;
        U32 PinConfig;
        
        // Choose API-function parameter for each PWM module (it's corresponding GPIO port and timer)
        switch (PWMName)
        {
            case PWM_0:
            SysCtlTimer = SYSCTL_PERIPH_TIMER0;
            SysCtlPort  = SYSCTL_PERIPH_GPIOL;
            PortBase    = GPIO_PORTL_BASE;
            PinNo       = GPIO_PIN_5;
            PinConfig   = GPIO_PL5_T0CCP1;
            break;
            
            case PWM_1:
            SysCtlTimer = SYSCTL_PERIPH_TIMER3;
            SysCtlPort  = SYSCTL_PERIPH_GPIOD;
            PortBase    = GPIO_PORTD_BASE;
            PinNo       = GPIO_PIN_5;
            PinConfig   = GPIO_PD5_T3CCP1;
            break;
            
            case PWM_2:
            SysCtlTimer = SYSCTL_PERIPH_TIMER4;
            SysCtlPort  = SYSCTL_PERIPH_GPIOM;
            PortBase    = GPIO_PORTM_BASE;
            PinNo       = GPIO_PIN_5;
            PinConfig   = GPIO_PM5_T4CCP1;
            break;
            
            case PWM_3:
            SysCtlTimer = SYSCTL_PERIPH_TIMER5;
            SysCtlPort  = SYSCTL_PERIPH_GPIOM;
            PortBase    = GPIO_PORTM_BASE;
            PinNo       = GPIO_PIN_7;
            PinConfig   = GPIO_PM7_T5CCP1;
            break;
        }
        
        // Turn peripheral's power on
        SysCtlPeripheralPowerOn(SysCtlTimer);
        SysCtlPeripheralEnable(SysCtlTimer);
        SysCtlPeripheralPowerOn(SysCtlPort);
        SysCtlPeripheralEnable(SysCtlPort);
        
        // Wait for peripherals to be ready
        while ( !(SysCtlPeripheralReady(SysCtlTimer) && SysCtlPeripheralReady(SysCtlPort)) );
        
        // Make GPIO pin configuration
        GPIOPinTypeTimer(PortBase, PinNo);
        GPIOPinConfigure(PinConfig);
    }

    And the Bi_PWMSetDutyCyc() function:

    /**
    ****************************************************************************
    *
    \b Name:    Bi_PWMSetDutyCyc
    *
    * \brief	Set duty cycle [%] of PWM module.
    *
    * This function calculates the duty cycle from timer match and reload value.
    * Therefore not the exact value may be set to the PWM module. If the \e Duty
    * parameter exceeds valid range the duty cycle may be set to 0% or 100%.
    *
    * \param   	PWMName determines the PWM module. Possible values are defined
    *                   in enumerated type ::pwmID.
    * \param	Duty	is the desired duty cycle [0...100%].
    *
    * \return   Returns the duty cycle set to the PWM module [0...100%] or -1.0
    *           in case of falilure.
    *
    * \note     In case of maximium period: T = (2^24)-1/::SysCtl_ClockFreq the
    *           result of 100% duty cyle will be 0% in fact. In this case the
    *           timer match register cannot have a higher value than the
    *           interval load register.
    *
    * \warning  Timer reload prescaler and match prescaler must be set with the
    *           same value to have the timer operate correctly. These prescalers
    *           contain the upper 8 bit of the 24 bit match/reload value.
    *
    * \bug      In case of <e> Duty >= 100 </e> the PWM output should be \e high.
    *           Timer configuration is implemented as suggested in the
    *           processor's datasheet, but the output is still \e low.
    *
    ****************************************************************************/
    float Bi_PWMSetDutyCyc(const pwmID PWMName, const float Duty)
    {
    	float RetDuty = -1.0;
        U32 TimerBase = PWMGetBase(PWMName);
    	U32 MatchVal, LoadVal;
    	
    	if ( TimerBase != 0 )
    	{
            // Disabling the timer may not be neccessary
    		// TimerDisable(TimerBase, TIMER_B);
    		
    		if (Duty >= 100.0 )
    		{
                /*
                Set match register to the same value as the interval load register
                to make the output high (CCP output is low but it's inverted (see
                timer configuration in Bi_PWMInit()).
                */
                            
    			// Get timer reload value from hw
    			LoadVal = TimerLoadGet(TimerBase, TIMER_B);
    			LoadVal |= TimerPrescaleGet(TimerBase, TIMER_B)<<16;
    			// note that the prescale register contains the upper 8 bit
    			
    			// Set calculated match value to hw
    			TimerMatchSet(TimerBase, TIMER_B, LoadVal);
    			TimerPrescaleMatchSet(TimerBase, TIMER_B, LoadVal>>16);
    			// note that the prescale register contains the upper 8 bit
    		}
    		
    		else if (Duty <= 0.0)
    		{
                /*
                Set the match register to it's maximum value. If match >
                interval load register CCP is low (but PWM output is
                inverted => see timer configuration in Bi_PWMInit()).
                */
                
    			// Set maximum to the match register
    			TimerMatchSet(TimerBase, TIMER_B, 0xFFFF);
    			TimerPrescaleMatchSet(TimerBase, TIMER_B, 0xFF);
    			// note that the prescale register contains the upper 8 bit
    		}
    		
    		else
    		{
    			// Get timer reload value from hw
    			LoadVal = TimerLoadGet(TimerBase, TIMER_B);
    			LoadVal |= TimerPrescaleGet(TimerBase, TIMER_B)<<16;
    			// note that the prescale register contains the upper 8 bit
    			
    			// Claculate match value for desired duty cycle
    			MatchVal = (U32)( (float)(LoadVal) * Duty * 0.01 );
    			
    			// Mask 32 bit value to 24 bit value
    			MatchVal &= 0x00FFFFFF;
    			
    			// Set calculated match value to hw
    			TimerMatchSet(TimerBase, TIMER_B, MatchVal);
    			TimerPrescaleMatchSet(TimerBase, TIMER_B, MatchVal>>16);
    			// note that the prescale register contains the upper 8 bit
    			
    			// Calculate exact duty set to hw
    			RetDuty = 100.0 * (float)(MatchVal) / (float)(LoadVal);
    		}
    	}
    	
    	return RetDuty;
    }

  • Hello Daniel

    I do not see a TimerEnable Function call?

    Regards

    Amit

  • Hi Amit,

    sry, I forgot the the Bi_PWMStart() function witch is also called.

    /**
    ****************************************************************************
    *
    \b Name:    Bi_PWMStart
    *
    * \brief	Starts the PWM signal on corresponding output.
    *
    * In fact the correspondung timer will be enabled to start the PWM.
    *   - ::PWM_0: Timer0, PL5 (IO_PAR0)
    *   - ::PWM_1: Timer3, PD5 (IO_PAR1)
    *   - ::PWM_2: Timer4, PM5 (IO_PAR2)
    *   - ::PWM_3: Timer5, PM7 (IO_PAR3)
    *
    * \param   	PWMName determines the PWM module. Possible values are defined
    *                   in enumerated type ::pwmID.
    *
    * \return   void
    *
    ****************************************************************************/
    void Bi_PWMStart(const pwmID PWMName)
    {	
        U32 TimerBase = PWMGetBase(PWMName);
        
    	if ( TimerBase != 0)
    	{
    		TimerEnable(TimerBase, TIMER_BOTH);
    	}
    }

  • Hello Daniel,

    I tried a simple configuration of using the Load and Match on the 16-bit counter only. I am able to see the issue that you mentioned. I would break the configuration into something as follows

    1. Getting 0% and 100% is not possible: You can get a DC of 100-((1/Load)*100) or (1/Load)*100

    2. The value needs to be one less than Load value as the count is always decremented from Load to 0 in PWM Mode. So a load of 0xFFFF is not a valid load.

    Regards

    Amit

  • Ok, it's a pity 100% duty won't work perfectly. But thanks for your help!

    I changed my configuration to the following (0% duty works perfectly!):

    if (Duty >= 100.0 )
    		{
                /*
                Set match register to the same value as the interval load register
                minus one to make the output high (perfect 100% duty is not
                possible!).
                */
                
                // Get timer reload value from hw
    			LoadVal = TimerLoadGet(TimerBase, TIMER_B);
    			LoadVal |= TimerPrescaleGet(TimerBase, TIMER_B)<<16;
    			// note that the prescale register contains the upper 8 bit
                
    			// Set maximum to the match register
    			TimerMatchSet(TimerBase, TIMER_B, LoadVal-1);
    			TimerPrescaleMatchSet(TimerBase, TIMER_B, (LoadVal-1)>>16);
    			// note that the prescale register contains the upper 8 bit
    		}
    		
    		else if (Duty <= 0.0)
    		{
                /*
                Set match register to the same value as the interval load register
                to make the output low.
                */
                
                // Get timer reload value from hw
    			LoadVal = TimerLoadGet(TimerBase, TIMER_B);
    			LoadVal |= TimerPrescaleGet(TimerBase, TIMER_B)<<16;
    			// note that the prescale register contains the upper 8 bit
                
    			// Set the timer reload value to the timer match registers
    			TimerMatchSet(TimerBase, TIMER_B, LoadVal);
    			TimerPrescaleMatchSet(TimerBase, TIMER_B, LoadVal>>16);
    			// note that the prescale register contains the upper 8 bit
    		}

    Regards
    Daniel

  • Hello Daniel,

    For 100% duty cycle you can use the PWML bit to invert the output which is 0% and then in the other functions use it to revert to the actual DC. My only concern will be a non-0% or non-correct DC for one full cycle

    Regards

    Amit

  • Note that this PWM limitation inflicts many (most) ARM Timers from many (most/all) ARM vendors.  And - has long been known.  (I, "held my fire" thinking (perhaps) newer device here had rectified)

    Method normally used requires the test for PWM @ either limit - and the call to a special function to correct.  (I believe that call can be automated so that the, "PWM @ extreme" test function is always called - and "corrects" only upon the 0 or 100% PWM recognition...)

  • I understand that, it's not even important in my usecase but the processor's datasheet says, the CCP output is able to make both DC values under following contitions:

    GPTMTnMATCHR > GPTMTnILR => CCP = HIGH
    GPTMTnMATCHR = GPTMTnILR => CCP = LOW

    So I thought it is possible to have a 0%-100% duty cycle.

    Changing the PLO-Bit during operation leads to a fault PWM cycle. I scoped that ;)
    I think the best way is to accept some small glitches. This shouldn't be a problem as long as the PWM frequency is not too high.


    Regards
    Daniel

  • Hello Daniel,

    Yes, I agree. But at the same time we would need to correct the data sheet for such information as well.

    Regards

    Amit

  • Hello Amit/TI,

    Are such datasheet/errata corrections in process?  I too have burned some time reaching the same conclusions that I later discovered here...

    Regards,

    Dave

  • Hello Dave

    Datasheets are not spinned too often so the document issues may persist longer. Erratas are on the other hand done regularly, but in this case the issue is not classified as an errata.
  • Amit,

    Thank you!

    Regards,

    Dave