cancel
Showing results for 
Search instead for 
Did you mean: 

STM32F446 synced timers with phase shift

Matt Blessinger
Associate III

0693W000001pLB1QAM.png

Hello,

I'm using a Nucleo F446ZE to test timer capabilities to make a peak-and-hold PWM driver. I already have the core functionality where TIM1 uses DMA to run through a buffer of variable duty cycles at a higher frequency (e.g. 5 kHz) for the high-side mosfet. TIM2 is triggered by TIM1 to produce a synchronized signal that is low frequency (e.g. 1 Hz) for the low-side mosfet.

The problem I have now is making a third timer output that is synchronized to the first two but with a programmable phase shift. In reality I do have code that will make a synchronized, phase-shifted signal as shown in the image but there is an issue.

Image legend: TIM1 high-frequency (yellow), TIM2 low-frequency (cyan), TIM2 phase-shift falling-edge trigger (magenta), and TIM3 phase-shifted signal (blue).

What’s supposed to happen is that the blue line goes high on the falling edge of the magenta line, as seen in the 2nd half of the scope image. And this method does work except for the very first output cycle as seen in the 1st half of the scope image; the blue line goes high with all other timer outputs instead of being delayed.

My method to produce this is (code is below):

  • Set TIM1 trigger output as enable (CNT_EN)
  • Set TIM2 slave mode as trigger with source ITR0
  • TIM2 CH1 is phase-shift falling-edge trigger. Output is PWM generation, where CCR1 sets the phase shift
  • Set TIM2 trigger output as Compare Pulse (OC1). Corresponds to CH1 falling-edge.
  • TIM2 CH2 is low-frequency PWM output
  • Set TIM3 slave mode as trigger with source ITR1
  • TIM3 CH1 is phase-shift signal

I just can’t figure out why the blue output is starting early for the first cycle. I tried changing the order of enabling the timers and capture compare channels, but that didn’t work. I also tried clearing the TIM2 SR_CC1IF, SR_TIF and EGR_TG bits just in case the TIM2 trigger output was being fired, all to no success.

I know there are other threads here that show using one pulse mode to do this. I tried that, but the problem is that there is a minimum non-zero delay with one pulse. I’m using this with a high-speed camera, and the majority of my time I’ll need no delay so that won’t work. This solution has no delay when programmed to do so, except for the first pulse.

Code to start timers

HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1); // arm TIM2 CH1: phase-shift falling-edge trigger
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2); // arm TIM2 CH2: low-side mosfet
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1); // arm TIM3 CH1: phase-shift sync signal
HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_1, (uint32_t *)pData1, sizeof(pData1) / sizeof(pData1[0])); // start TIM1, triggers TIM2

TIM1 init

static void MX_TIM1_Init(void)
{
 
  /* USER CODE BEGIN TIM1_Init 0 */
 
  /* USER CODE END TIM1_Init 0 */
 
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};
  TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
 
  /* USER CODE BEGIN TIM1_Init 1 */
 
  /* USER CODE END TIM1_Init 1 */
  htim1.Instance = TIM1;
  htim1.Init.Prescaler = 319;
  htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim1.Init.Period = 99;
  htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim1.Init.RepetitionCounter = 0;
  htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim1) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_Init(&htim1) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_ENABLE;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_ENABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = 0;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  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();
  }
  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.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
  if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM1_Init 2 */
 
  /* USER CODE END TIM1_Init 2 */
  HAL_TIM_MspPostInit(&htim1);
 
}

TIM2 init

static void MX_TIM2_Init(void)
{
 
  /* USER CODE BEGIN TIM2_Init 0 */
 
  /* USER CODE END TIM2_Init 0 */
 
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_SlaveConfigTypeDef sSlaveConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};
 
  /* USER CODE BEGIN TIM2_Init 1 */
 
  /* USER CODE END TIM2_Init 1 */
  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 7999;
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim2.Init.Period = 9999;
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_Init(&htim2) != HAL_OK)
  {
    Error_Handler();
  }
  sSlaveConfig.SlaveMode = TIM_SLAVEMODE_TRIGGER;
  sSlaveConfig.InputTrigger = TIM_TS_ITR0;
  if (HAL_TIM_SlaveConfigSynchro(&htim2, &sSlaveConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_OC1;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = 0;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM2_Init 2 */
 
  /* USER CODE END TIM2_Init 2 */
  HAL_TIM_MspPostInit(&htim2);
 
}

TIM3 init

static void MX_TIM3_Init(void)
{
 
  /* USER CODE BEGIN TIM3_Init 0 */
 
  /* USER CODE END TIM3_Init 0 */
 
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_SlaveConfigTypeDef sSlaveConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};
 
  /* USER CODE BEGIN TIM3_Init 1 */
 
  /* USER CODE END TIM3_Init 1 */
  htim3.Instance = TIM3;
  htim3.Init.Prescaler = 7999;
  htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim3.Init.Period = 9999;
  htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_Init(&htim3) != HAL_OK)
  {
    Error_Handler();
  }
  sSlaveConfig.SlaveMode = TIM_SLAVEMODE_TRIGGER;
  sSlaveConfig.InputTrigger = TIM_TS_ITR1;
  if (HAL_TIM_SlaveConfigSynchro(&htim3, &sSlaveConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = 2499;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM3_Init 2 */
 
  /* USER CODE END TIM3_Init 2 */
  HAL_TIM_MspPostInit(&htim3);
 
}

1 ACCEPTED SOLUTION

Accepted Solutions

I don't understand the Cube/HAL gobbledygook and you maybe should also resort to register programming if you want to do something other than usual, having full control over what you are doing.

It's not the one-pulse mode which makes the difference, but that with it, the other polarity PWM is used (i.e. which results in high output in the second part of the period). The issue here is, that as soon as you enable the PWM output, it starts to compare CNT - which stands at 0, waiting for the trigger - with CCRx, outputting the respective level, which in you case is high for the first part of period.

On 'F446 it may be tricky to achieve what you want (on newer STM32, the combined Reset+Trigger mode may probably be employed, with suitably preloaded CCRx). You may need to set TIMx_CR2.CCPC to enable CCxE preloading, only after that set TIMx_CCER.CCxE so that the enable goes into the preload register; and by setting TIMx_CR2.CCUS the TRGI trigger will not only start the timer but also load the CCxE bit from preload to "active".

JW

View solution in original post

6 REPLIES 6

I don't understand the Cube/HAL gobbledygook and you maybe should also resort to register programming if you want to do something other than usual, having full control over what you are doing.

It's not the one-pulse mode which makes the difference, but that with it, the other polarity PWM is used (i.e. which results in high output in the second part of the period). The issue here is, that as soon as you enable the PWM output, it starts to compare CNT - which stands at 0, waiting for the trigger - with CCRx, outputting the respective level, which in you case is high for the first part of period.

On 'F446 it may be tricky to achieve what you want (on newer STM32, the combined Reset+Trigger mode may probably be employed, with suitably preloaded CCRx). You may need to set TIMx_CR2.CCPC to enable CCxE preloading, only after that set TIMx_CCER.CCxE so that the enable goes into the preload register; and by setting TIMx_CR2.CCUS the TRGI trigger will not only start the timer but also load the CCxE bit from preload to "active".

JW

0693W000001pLKXQA2.png

Thank you for the reply. I start with CUBE and HAL and then use the registers as needed, so I can modify them here as needed.

Are you saying the timer outputs at its default polarity (high in this case) until it receives the trigger, hence it starts once CCxE is enabled? I think I understand that can be fixed by preloading CCxE, so output won't be enabled until the timer is triggered. Only the advanced timers have CCPC and CCUS bits, so I'll have change TIM3 to 1 or 8.

I don't know if this changes the situation, but when I set TIM2 CH1 (magenta line) CCR to 0 to have zero delay on the phase-shift signal (blue line), the signal stays high the whole 1st cycle so there is no rising edge on the 2nd cycle. The 3rd cycle on everything is good.

I don't know how you sequenced the start, but probably you have not TIM3 cocked yet when TIM2 outputs the first trigger, or there's simply no trigger unless TIM2 goes from CNT=ARR into CNT=0=CCR1.

With TIM3, you can try to begin setting CNT=ARR which should result its output having the "upper part of PWM" at the beginning, and then a one cycle delay when the trigger arrives.

JW

I made a little head way with this. I believe you are right that when I set CCR1=0 (0 phase shift) there's no trigger on the first cycle. If I set a delay of CCR1=1 then the phase-shift signal stops in the first cycle stops when it's supposed to instead of continuing into the second cycle.

I also figured out that for non-zero phase shifts the early high pulse (image in original post) is due to enabling the TIM3 capture compare channel. I tested this by setting the phase-shift trigger to have a delay of 500ms and started TIM1 and TIM2. I then did a HAL delay of 400ms and enabled TIM3 capture compare channel. At that point the signal went high, which is 100ms before the TIM3 trigger. This further suggests I need to look into your earlier suggestion of using CCxE preloading.

I tested setting TIM3 CNT=ARR. It worked as you thought, but TIM3 was delayed by 1 CNT value for all cycles. In my timing setup that's 100us, which is too large for the application.

> I tested setting TIM3 CNT=ARR. It worked as you thought, but TIM3 was delayed by 1 CNT value for all cycles.

> In my timing setup that's 100us, which is too large for the application.

You could possibly improve this method by setting up TIM3 using PSC=0, then - after any Cube call, i.e. to avoid PSC getting "active" by setting EGR.UG what Cube dose - set PSC to your value (8000-1). This should result in the first prescaler period after trigger to be the shortest possible 1-cycle, but as immediately CNT rollover i.e. Update occurs, the "real" PSC value gets loaded and subsequent counting should already use that one.

JW

Matt Blessinger
Associate III

Thanks for all the help, Jan!

I have a working solution with which I will move forward. The key was to set the CR2.CCPC and CR2.CCUS bits to preload the capture compare enable. (I moved the phase-shift signal to an advanced timer that has this functionality.) This prevented the output from going high early by not enabling CCxE until the timer was triggered. Note in the code below I reset the bits and then set them again. This was required for when I stopped and restarted the timers. Without resetting the bits, the output would go high early again.

htim8->Instance->CR2 &= ~TIM_CR2_CCPC;
htim8->Instance->CR2 &= ~TIM_CR2_CCUS;
htim8->Instance->CCR2 = 2499;
htim8->Instance->EGR |= TIM_EGR_UG;
htim8->Instance->CR2 |= TIM_CR2_CCPC;
htim8->Instance->CR2 |= TIM_CR2_CCUS;
TIM_CCxChannelCmd(htim8->Instance, TIM_CHANNEL_2, TIM_CCx_ENABLE);
__HAL_TIM_MOE_ENABLE(htim8);

In the case of 0 phase shift, I simply change the timer trigger to TIM1 (ITR0). With this it outputs on the first cycle and is synced to the other timers.

I did find a Nucleo F767 lying around, so I'll try the Reset+Trigger mode on its timers to see if that works.