cancel
Showing results for 
Search instead for 
Did you mean: 

STM32L4 PWM Phase Delay

Rogers.Gary
Senior II
Posted on February 19, 2018 at 19:02

Hello:

Using the Nucleo STM32L476RG demo board.

On an STM32F4xx, using CMSIS, I am able to create 2 PWM pulses that have the same period. However, the 2nd pulse is delayed by: TIM4->CNT = TIM3->CNT + (timerPeriod /2 ); // =180 degrees

I want to do the same thing using HAL, but it looks like a few things have changed from CMSIS!

I have my code where I have 2 pulses being generated at the correct duty cycle and frequency, but spinning the wheels on how to get the second pulse delayed as shown above.

Can someone please provide some insight on how I could accomplish this? Has HAL made it easier than CMSIS? Or just different?

Thanks!

UPDATE.

Found: DM00405316

Note: this post was migrated and contained many threaded conversations, some content may be missing.
22 REPLIES 22
Posted on February 20, 2018 at 19:05

I hope this is what you wanted!

After Master (TIM2) INIT;

0690X00000609l0QAA.png

After Slave (TIM1) INIT;

0690X00000609gaQAA.png

After Master (TIM2) PWM Starts;

0690X00000609YMQAY.png

Slave (TIM1) after Master PWM Starts only:

0690X00000609eCQAQ.png

finally after SLAVE (TIM1) PWM Startup

0690X00000609eBQAQ.png
Posted on February 21, 2018 at 05:46

hereCubeMX ioc file

NoteI tested this on Nucleo-F303RE.

I don't have a 476, but i threw a cubemx file together for you.

________________

Attachments :

N64-303-TIM1-TIM2-CHAINING.ioc.zip : https://st--c.eu10.content.force.com/sfc/dist/version/download/?oid=00Db0000000YtG6&ids=0680X000006Hxfn&d=%2Fa%2F0X0000000b2i%2FHUr3ovNWgDZwzHfzywkKZX2QCT_tVnU2jTOyIQcTZwo&asPdf=false

N64-476-TIM2-TIM1-Chaining.ioc.zip : https://st--c.eu10.content.force.com/sfc/dist/version/download/?oid=00Db0000000YtG6&ids=0680X000006Hxx2&d=%2Fa%2F0X0000000b2f%2FndAEgVzaixgVdm7hrr2qXGzMWoPgdn7OSNB0HZoqaqs&asPdf=false
Posted on February 21, 2018 at 05:47

here is the INIT code for the 'cleaner' alternative setup.

/* TIM1 init function */static void MX_TIM1_Init(void){ TIM_ClockConfigTypeDef sClockSourceConfig; TIM_SlaveConfigTypeDef sSlaveConfig; TIM_MasterConfigTypeDef sMasterConfig; TIM_OC_InitTypeDef sConfigOC; TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig; htim1.Instance = TIM1; htim1.Init.Prescaler = 71; htim1.Init.CounterMode = TIM_COUNTERMODE_UP; htim1.Init.Period = 1000; htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim1.Init.RepetitionCounter = 0; htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; if (HAL_TIM_Base_Init(&htim1) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } if (HAL_TIM_PWM_Init(&htim1) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } if (HAL_TIM_OnePulse_Init(&htim1, TIM_OPMODE_SINGLE) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } sSlaveConfig.SlaveMode = TIM_SLAVEMODE_TRIGGER; sSlaveConfig.InputTrigger = TIM_TS_ITR1; if (HAL_TIM_SlaveConfigSynchronization(&htim1, &sSlaveConfig) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } sConfigOC.OCMode = TIM_OCMODE_PWM1; sConfigOC.Pulse = 1; sConfigOC.OCPolarity = TIM_OCPOLARITY_LOW; sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH; sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET; sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET; if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE; sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE; sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF; sBreakDeadTimeConfig.DeadTime = 0; sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE; sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH; sBreakDeadTimeConfig.BreakFilter = 0; sBreakDeadTimeConfig.Break2State = TIM_BREAK2_DISABLE; sBreakDeadTimeConfig.Break2Polarity = TIM_BREAK2POLARITY_HIGH; sBreakDeadTimeConfig.Break2Filter = 0; sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE; if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } HAL_TIM_MspPostInit(&htim1);}/* TIM2 init function */static void MX_TIM2_Init(void){ TIM_ClockConfigTypeDef sClockSourceConfig; TIM_MasterConfigTypeDef sMasterConfig; TIM_OC_InitTypeDef sConfigOC; htim2.Instance = TIM2; htim2.Init.Prescaler = 71; htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 9999; htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; if (HAL_TIM_Base_Init(&htim2) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } if (HAL_TIM_PWM_Init(&htim2) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_OC1; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } sConfigOC.OCMode = TIM_OCMODE_PWM1; sConfigOC.Pulse = 5000; sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } sConfigOC.Pulse = 1000; if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_2) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } HAL_TIM_MspPostInit(&htim2);}

Posted on February 21, 2018 at 06:33

Ok, thank you John. Will give it a try.

Posted on February 25, 2018 at 16:06

John,

thanks for the details.

I ''decomposed'' it into register accesses, and then modified it for a simple demo program (in attachment).

It may seem that there's quite a lot of typing; but in fact, once you already set up a timer in this manner, it's mostly copy/paste/modify, and the possibility to add comments as appropriate is IMO a bonus.

0690X00000609k4QAA.png

0690X00000609hdQAA.png

0690X00000609oTQAQ.png

0690X00000609coQAA.png

JW

________________

Attachments :

tim master slave.zip : https://st--c.eu10.content.force.com/sfc/dist/version/download/?oid=00Db0000000YtG6&ids=0680X000006HxqD&d=%2Fa%2F0X0000000b2Z%2FcWdA2s9Ylx1iY.bsBn_U8u5UF5Rb94kZ_lhsE3RxFnU&asPdf=false
henry.dick
Senior II
Posted on February 25, 2018 at 17:27

if the numbers used here are real, you have 720k ticks in between. a fully software solution is possible here, with its own caveats as just as hardware -based solution does.

Posted on February 25, 2018 at 17:19

'

It may seem that there's quite a lot of typing; but in fact, once you already set up a timer in this manner'

you have done a lot here. so don't let it go waste:

1) write two routines, tim2_init(ps, pr) and tim2_setdc1(dc), to initialize a timer and then set duty cycle.

2) use them to perform the desired tasks here:

  tim2_init(72, 10000); tim2_setdc1(10000*1/4); //set tim2 to 72:1 prescaler, 10000 period, 1/4 duty cycle

 

  tim3_init(72, 10000); tim3_setdc1(10000*1/4); //set tim3 to 72:1 prescaler, 10000 period, 1/4 duty cycle

  TIM2->CNT = 0; TIM3->CNT = 10000*1/2; //TIM2/3 counters 180 degrees apart

  //enable the timers.....

this approach allows the code to be reused again, and allows for flexible implementation: tim2_init() and tim2_setdc() can be implemented via registers, LL/spl/hal/...., without any changes to the user code.

Posted on February 25, 2018 at 18:01

here is a screen capture of a debug session for the code above.

as you can see, the difference between TIM2->CNT and TIM3->CNT is 4999 (it actually oscillates between 4999 and -5001: the 1 count off is due to execution of the two TIM2/3->CNT assignment statements, and the negative sign is due to counter roll-over from ARR).

0690X00000609oeQAA.png
Posted on February 25, 2018 at 18:36

regarding the setting / resetting of the timer counters, either directly as i used, or increments on top of TIM2->CNT:

1) with prescaler to 1:1, the direct approach yields a phase difference of 5015 (or -4985);

2) under the same condition, the incrementing approach yields a phase difference of 5021 (or -4979).

both are fairly small but if you want to be picky, the direct approach is slightly faster.

You obviously can add an error term to correct for that. Just remember that the error term varies with your set-up.

Posted on February 25, 2018 at 21:28

While the hardware method using has its limitations indeed (e.g. not every couple of timers have a link), it is there for a reason - not only is software 'slower', it's also more 'jittery'; and using high-level language adds another level of uncertainty.

Would I want to do this software approach, I'd set both timers completely except enabling them, set their CNT for the phase difference, and then - having disabled interrupts - enable the timers counters (write into the two CR1 so that CR1.CEN is set) in inline asm.

I understand that for a given application this might not be necessary.

JW