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.

HET Duty Cycle to Period relationship

Other Parts Discussed in Thread: HALCOGEN

I could be missing something obvious here but I have been testing the NHET on the RM48L950PGE. I have set up PWM0 on NHET01[04] and loaded a simple routine that allows me to modify the duty cycle and period.


As you can see below the waveform for duty cycle 50% and period of 1 second (1000000usecs) looks correct.


If I try to change this to a period of 2 seconds (0.5Hz) the duty cycle becomes 25% as opposed to longer 50% pulse:

Here is the code, I vary pwm0_signal elements in the debug window to change the waveform

void main(void)
{
/* USER CODE BEGIN (3) */

	hetInit();

	pwm0_enb = false;
	pwm0_signal.duty = 50;
	pwm0_signal.period = 1000; /*in usecs*/
	while(1)
	{
		if(pwm0_enb)
		{
			/*pwmSetDuty(hetRAM1,pwm0,pwm0_duty);*/
			pwmSetSignal(hetRAM1,pwm0,pwm0_signal);
			pwmStart(hetRAM1,pwm0);
		}
		else
		{
			pwmStop(hetRAM1,pwm0);
		}
	/* Forever */
	}
/* USER CODE END */
}

  • Jamie,

    What does your HET code look like.  Depending on the instructions used to generate the PWM this could be normal.

     

  • Hello Anthony,


    I have only configured PWM0 to the HET1[4] pin in Halcogen, I have not used the HET IDE to generate anything complex. Perhaps I am missing something in HET config of HALcogen. The het.c & het.h are attached.

    Thanks

    Jamie8484.het.c7651.het.h

  • Jamie,

    This may just be an issue w. the driver code in HalCoGen not updating the duty cycle when the period is updated.  You can check this easily.

    The PWM generated by the HalCoGen driver code is created with loop resolution instructions PWCNT (pulse width count) for the pulse duration and DJZ for the period:

    /* PWCNT: PWM 0 -> Duty Cycle
        *         - Instruction                  = 1
        *         - Next instruction             = 2
        *         - Conditional next instruction = 2
        *         - Interrupt                    = 1
        *         - Pin                          = 4
        */
        {
            /* Program */
            0x000055C0U,
            /* Control */
            (0x00004006U | (uint32)((uint32)4U << 8U) | (uint32)((uint32)3U << 3U)),
            /* Data */
            0x00000000U,                                                                                         <--------- PULSE WIDTH
            /* Reserved */
            0x00000000U
        },

    /* DJZ: PWM 0 -> Period
        *         - Instruction                  = 2
        *         - Next instruction             = 3
        *         - Conditional next instruction = 41
        *         - Interrupt                    = 2
        *         - Pin                          = na
        */
        {
            /* Program */
            0x00007480U,
            /* Control */
            0x00052006U,
            /* Data */
            0x00000000U,
            /* Reserved */
            0x00000000U
        },

    However, these are the equivalent of the hardware compare registers that are 'active', if this were a hardware timer.  The driver shouldn't write to these directly but to the 'double buffered' compare registers which you can see are at instructions 41 and 42 ... executed whenever the DJZ reaches 0 (end of period compare matches).

    /* MOV64: PWM 0 -> Duty Cycle Update
        *         - Instruction                  = 41
        *         - Next instruction             = 42
        *         - Conditional next instruction = 2
        *         - Interrupt                    = 1
        *         - Pin                          = 4
        */
        {
            /* Program */
            0x00054201U,
            /* Control */
            (0x00004007U | (uint32)((uint32)1U << 22U) | (uint32)((uint32)4U << 8U) | (uint32)((uint32)3U << 3U)),
            /* Data */
            120000128U,
            /* Reserved */
            0x00000000U
        },
        /* MOV64: PWM 0 -> Period Update
        *         - Instruction                  = 42
        *         - Next instruction             = 3
        *         - Conditional next instruction = 41
        *         - Interrupt                    = 2
        *         - Pin                          = na
        */
        {
            /* Program */
            0x00006202U,
            /* Control */
            (0x00052007U),
            /* Data */
            159999872U,
            /* Reserved */
            0x00000000U
        },

    You can check whether the duty cycle (data field of instruction 41) is updated when you update the period (data field of instruction 42).  They need to be both updated together if you want to keep the duty cycle at 50%. 
    The representation is loop resolution counts, not a % so if you change the period from say 100 to 400 then the pulse width needs to change proportionally from 50 to 200 in order to keep the duty cycle at 50%.
    It may be that the HalCoGen driver isn't updating these together.   You can either do it manually, or change the HET code to automatically create a 50% duty cycle.  The latter is probably 'trivial';  but you would need to use a custom HET code to do this.  You could simply change instruciton 41 to an ADD which adds the period to 0, shifts it right by 1 bit (/2) and stores it in the pulse width location.  

     

  • Hello Anthony,


    Thanks for all this detail but my goal is not to always have a 50% duty cycle. I want my PWM drive to be configurable both by duty cycle and period. I can successfully change the duty cycle using the pwmSetDuty function. For period there appears to be no comparable function so I use pwmSetSignal instead. Looking into het.c @ line 1534 the period is first calculated, then polarity is checked for the case that the pwm is high or low by default and then the hetRAM instructions are called:

        hetRAM->Instruction[(pwm << 1U) + 41U].Control = ((hetRAM->Instruction[(pwm << 1U) + 41U].Control) & (~(uint32)(0x00000018U))) | (action << 3U);
        hetRAM->Instruction[(pwm << 1U) + 41U].Data = (uint32)((pwmPeriod * signal.duty) / (uint32)100U) + 128U;
        hetRAM->Instruction[(pwm << 1U) + 42U].Data = pwmPeriod - 128U;

    The first instruction loaded with data is 41, then 42 so I would expect that the pwmDuty and period are both updated. Are they updated correctly...not so sure. I am still getting up to speed on how the HET module executes, but I assume that all these instructions sit in the hetRAM and are continually executed, change the instruction data set and you get different PWM outputs.

    If my understanding is correct I can only suggest that the wrong data is loaded to the instructions - would you agree with this statement?

  • Hi Jamie,

    It's a bug in HALCogen. The Math ie., Shift by 7 is done at wrong place, hence the data bits got truncated. So for certain range of data the API's would fail. It's been already fixed, it will roll out in upcoming HALCoGen release. 4.00.01.
    pwmSetSignal , pwmGetSignal and capGetSignal Api's are affected becasue of it.

    Work around : Replace the HALCoGen generated API with below API's in het.c..

    Note: %%HET1_LR_ACTUALTIME%% and %%HET2_LR_ACTUALTIME%% values must be replaced with the one generated through GUI.. It depends on the user configuration.

    --------------------------------------------

    void pwmSetSignal(hetRAMBASE_t * hetRAM, uint32 pwm, hetSIGNAL_t signal)
    {
        uint32 action;
        uint32 pwmPeriod = 0U;
        uint32 pwmPolarity = 0U;
        float32 temp = 0.0F;

        if(hetRAM == hetRAM1)
        {
            temp = ((float32)signal.period * 1000.0F) / %%HET1_LR_ACTUALTIME%%F;
            pwmPolarity = s_het1pwmPolarity[pwm];

        }
        else
        {
            temp = ((float32)signal.period * 1000.0F) / %%HET2_LR_ACTUALTIME%%F;
            pwmPolarity = s_het2pwmPolarity[pwm];
        }
        if (signal.duty == 0U)
        {
            action = (pwmPolarity == 3U) ? 0U : 2U;
        }
        else if (signal.duty >= 100U)
        {
            action = (pwmPolarity == 3U) ? 2U : 0U;
        }
        else
        {
            action = pwmPolarity;
        }

        hetRAM->Instruction[(pwm << 1U) + 41U].Control = ((hetRAM->Instruction[(pwm << 1U) + 41U].Control) & (~(uint32)(0x00000018U))) | (action << 3U);
        hetRAM->Instruction[(pwm << 1U) + 41U].Data = ((uint32)((pwmPeriod * signal.duty) / (uint32)100U) << 7) + 128U;
        hetRAM->Instruction[(pwm << 1U) + 42U].Data = (pwmPeriod << 7U) - 128U;
    }

    --------------------------------------------

    void pwmGetSignal(hetRAMBASE_t * hetRAM, uint32 pwm, hetSIGNAL_t* signal)
    {
        uint32    pwmDuty   = (hetRAM->Instruction[(pwm << 1U) + 41U].Data - 128U) >> 7;
        uint32    pwmPeriod = (hetRAM->Instruction[(pwm << 1U) + 42U].Data + 128U) >> 7;

        signal->duty   = (uint32)(((uint32)100.0 * pwmDuty) / pwmPeriod);

        if(hetRAM == hetRAM1)
        {
            signal->period = (float64)(((float64)pwmPeriod * (float64)%%HET1_LR_ACTUALTIME%%) / (float64)1000U);
        }
        else
        {
            signal->period = (float64)(((float64)pwmPeriod * (float64)%%HET2_LR_ACTUALTIME%%) / (float64)1000U);
        }
    }

    --------------------------------------------

    void capGetSignal(hetRAMBASE_t * hetRAM, uint32 cap, hetSIGNAL_t *signal)
    {
        uint32    pwmDuty   = hetRAM->Instruction[(cap << 1U) + 25U].Data >> 7;
        uint32    pwmPeriod = hetRAM->Instruction[(cap << 1U) + 26U].Data >> 7;

        signal->duty   = (uint32)(((uint32)100U * pwmDuty) / pwmPeriod);

        if( hetRAM == hetRAM1)
        {
            signal->period = (float64)(((float64)pwmPeriod * (float64)%%HET1_LR_ACTUALTIME%%) / (float64)1000U);
        }
        else
        {
            signal->period = (float64)(((float64)pwmPeriod * (float64)%%HET2_LR_ACTUALTIME%%) / (float64)1000U);
        }
    }

    ----------------------------------------------------------------------------------------

  • Thanks Prathap for getting to the bottom of this!

    Jamie, you should use Prathap's answer - but to clarify one point you made

    Jamie said:
    The first instruction loaded with data is 41, then 42 so I would expect that the pwmDuty and period are both updated. Are they updated correctly...not so sure. I am still getting up to speed on how the HET module executes, but I assume that all these instructions sit in the hetRAM and are continually executed, change the instruction data set and you get different PWM outputs.

    In this case instructions 41 and 42 are not executed continually (in every loop resolution period).

    They are skipped unless the "DJZ" instruction 2 reaches zero:

    /* DJZ: PWM 0 -> Period
        *         - Instruction                  = 2
        *         - Next instruction             = 3                  << Most of the time
        *         - Conditional next instruction = 41            << Only at the end of the period...
        *         - Interrupt                    = 2
        *         - Pin                          = na
        */
      

    This is how we get the same behavior as a hardware buffer that updates a compare register only at period boundaries (from instructions 41 and 42).

    Hope that helps w. the HET understanding.

     

  • Anthony & Prathap thank you,

    I just got back to trying this fix. I notice the the hetRAM else clause is specific to het2 and I do not have this enabled so I will leave the else clause empty (this appears to be what my HALcogen version did).

    This leaves just one problem, in the code provided by Prathap the pwmPeriod required by the het instructions in pwmSetSignal is never calculated. I think the het instructions should use the 'temp' variable as opposed to pwmPeriod. Having implemented this slight change I can get a waveform that correctly executes variable duty cycle and period:

    Here is the code I am using for pwmSetSignal:

    void pwmSetSignal(hetRAMBASE_t * hetRAM, uint32 pwm, hetSIGNAL_t signal)
    {
        uint32 action;
        uint32 pwmPeriod = 0U;
        uint32 pwmPolarity = 0U;
        float32 temp = 0.0F;
    
        if(hetRAM == hetRAM1)
        {
            temp = ((float32)signal.period * 1000.0F) / 800.00F;
            pwmPolarity = s_het1pwmPolarity[pwm];
    
        }
        else
        {
           /* het2 not enabled */
        }
        if (signal.duty == 0U)
        {
            action = (pwmPolarity == 3U) ? 0U : 2U;
        }
        else if (signal.duty >= 100U)
        {
            action = (pwmPolarity == 3U) ? 2U : 0U;
        }
        else
        {
            action = pwmPolarity;
        }
    
        hetRAM->Instruction[(pwm << 1U) + 41U].Control = ((hetRAM->Instruction[(pwm << 1U) + 41U].Control) & (~(uint32)(0x00000018U))) | (action << 3U);
        hetRAM->Instruction[(pwm << 1U) + 41U].Data = ((uint32)((temp * signal.duty) / (uint32)100U) << 7) + 128U;
        hetRAM->Instruction[(pwm << 1U) + 42U].Data = ((uint32)temp << 7U) - 128U;
    }

    Thanks

    Jamie

  • I couldnt understand the calculation. Why are we

    a. multiplying the period by 1000.

    b. Adding 128U for the duty cycle data field and subtracting the same for Period data.

    c. wat would be the value of HET1_LR_ACTUALTIME if VCLK2 = 96Mhz , Lr = 64 and hr = 1.

    Can someone explain me.

    Thanks in advance.

  • Hi Harsha,

    Answers to your Questions

    a. multiplying the period by 1000.

    This is done to convert the microseconds value to nanoseconds. Then we divide the value by the LR time to get the period time in terms of number of LRPs. That is, we get the number of LRPs that should execute for one time period.

    b. Adding 128U for the duty cycle data field and subtracting the same for Period data.

    After finding the value of Time period in terms of No of LRPs, we shift it by 7 bits before writing into the instruction. This is because there is a 7 bit offset in the data field of DJZ and PWCNT instructions. The +and- 128U is +and- 1U shifted by 7. The 1U is added and subtracted from duty and period for adjusting the values based on the instruction characteristics. An easy way to understand this is to check what happens when the duty is 0% or 100%.

    c. wat would be the value of HET1_LR_ACTUALTIME(LR period) if VCLK2 = 96Mhz , Lr = 64 and hr = 1.

    HET1_LR_ACTUALTIME = LR period = LR * HR period. (make sure it is converted to nanoseconds, if planning to use the above pwmSetSignal API)
    HR period= HR/VCLK2

    More information on this can be found in the manual under N2HET Functional Description in High-End Timer Module.

  • Thanks Prathap.

    I understood.

  • Think the above fix is wrong.

    Sure the math is wrong for the shift. In the above fix in pwmSetSignal() temp is nolonger used in the fix to set the pwmPeriod...,
    I think the real fix is....

    temp = ((float32)signal.period * 1000.0F) / 1280.000F;
    temp = temp * 10000000;
    pwmPeriod = (uint32)temp;
    //pwmPeriod = (uint32)temp << 7U;
    pwmPolarity = s_het1pwmPolarity[pwm];

    Even though I have PWM's running with halcogen; and the above patch I am not seeing the correctly generated frequencies.

    See my post under...

    http://e2e.ti.com/support/microcontrollers/hercules/f/312/p/357705/1265037.aspx#1265037

    I thought this Halcogen stuff was SIL-2 qualified? Seems to have lots of bugs.

  • Hi Owain,

    The above fix was given by me and I made a mistake and was corrected by  in his post above. I am not sure if that's the fix HALCoGen developer is taking for upcoming 4.01.00, I will double check with them. 

    Also could you please share you HALCoGen project ( pjt and dil) and what's the PWM you are trying to generate and facing issue. I can pull the details of your Microcontroller and the clock settings form HALCoGen Pjt.

    HET is a complex module which has lot of capabilities and configurations. With HALCoGen "Black box" we address only basic uses of HET like simple PWM generation, capture and event, mostly they are for the light weight users of HET ( Ofcourse if their use case is simple they can use it as is in application) . In fact we do not use the High resolution block in the "Blackbox mode". You can use HET IDE or your own assemble file and use the "Advance Config mode" in HALCoGen.  This is what we recommend for heavy users and customer who need higher resolutions. From HALCoGen only hetInit() function will be generated.