Electrical – generating different duty cycle on two different pins of same pwm timer STM32 Nucleo

cembeddedpwmstm32

I used STCube to generate my code in order to get a different duty cycle on two different pins of the same timer on my Nucleo F446ZE board. It works for the most part, both pins have the same frequency but they both have the same duty cycle as well, the duty cycle I set up as PULSE1_VALUE. I've tried several things including trying to replicate the example code for PWM outputs that came with the F446Ze board but I can't seem to get my TIM_CHANNEL_2 pin to toggle at a different duty cycle. Here's the relevant code:

/* Private variables      ---------------------------------------------------------*/
TIM_HandleTypeDef htim2;

/* Private typedef -----------------------------------------------------------*/
#define  PERIOD_VALUE       (uint32_t)(661-1)  /* Period Value  */ counter clock of 15MHz and period of 660 gives a frequency of 22.7kHz
#define  PULSE1_VALUE       (uint32_t)(PERIOD_VALUE*12.5/100)        /*    Capture Compare 1 Value  12.5% duty */
#define  PULSE2_VALUE       (uint32_t)(PERIOD_VALUE*1/100) /* Capture  Compare 2 Value  1%duty*/

TIM2 init function

static void MX_TIM2_Init(void)
{
  uhPrescalerValue = (uint32_t)((SystemCoreClock/2)/15000000)-1; //this gives a counter frequency of 15MHz. SystemCoreClock is 180MHz
  TIM_ClockConfigTypeDef sClockSourceConfig;
  TIM_SlaveConfigTypeDef sSlaveConfig;
  TIM_MasterConfigTypeDef sMasterConfig;
  TIM_OC_InitTypeDef sConfigOC;
  memset(&sConfigOC, 0, sizeof(sConfigOC));
  htim2.Instance = TIM2;
  htim2.Init.Prescaler = uhPrescalerValue;
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim2.Init.Period = PERIOD_VALUE; //660 in this case
  htim2.Init.ClockDivision = 0;

  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_DISABLE;
  sSlaveConfig.InputTrigger = TIM_TS_ITR0;
  if (HAL_TIM_SlaveConfigSynchronization(&htim2, &sSlaveConfig) != HAL_OK)
  {
    Error_Handler();
  }

  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }

  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_ENABLE;//TIM_OCFAST_DISABLE;
  sConfigOC.OCNPolarity  = TIM_OCNPOLARITY_HIGH;
  //sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
  sConfigOC.OCIdleState  = TIM_OCNIDLESTATE_SET;//TIM_OCIDLESTATE_RESET;

  sConfigOC.Pulse = PULSE1_VALUE;
  if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }

  sConfigOC.Pulse = PULSE2_VALUE;
  if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
  {
    Error_Handler();
  }
  /* Peripheral clock enable */

  HAL_TIM_MspPostInit(&htim2);
  //HAL_TIM_Base_Start(&htim2);

  /*##-3- Start PWM signals generation #######################################*/
   /* Start channel 1 */
   if (HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1) != HAL_OK)
   {
     /* PWM Generation Error */
     Error_Handler();
   }


   /* Start channel 2 */
   if (HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2) != HAL_OK)
   {
     /* PWM Generation Error */
     Error_Handler();
   }


}

I'm thinking it could be something with the way I'm setting up the GPIO for the two pins I want to control in the stm32f4xx_hal_msp.c file but I've seen other people set it up the exact same way on other sites: https://visualgdb.com/tutorials/arm/stm32/pwm/

void HAL_TIM_MspPostInit(TIM_HandleTypeDef* htim)
{

  GPIO_InitTypeDef GPIO_InitStruct;
  if(htim->Instance==TIM2)
  {
  /* USER CODE BEGIN TIM2_MspPostInit 0 */

      /* TIMx Peripheral clock enable */
      //TIMx_CLK_ENABLE();
  /* USER CODE END TIM2_MspPostInit 0 */

    /**TIM2 GPIO Configuration    
    PA0-WKUP     ------> TIM2_CH1
    PA1     ------> TIM2_CH2 
    */
    GPIO_InitStruct.Pin = ext_LED_1_Pin|ext_LED_2_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF1_TIM2;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /* USER CODE BEGIN TIM2_MspPostInit 1 */

  /* USER CODE END TIM2_MspPostInit 1 */
  }

}

Best Answer

It is not HAL as I do not use this bloatware. It is for F3 series but the timers are almost identical. You can change frequency & PWM duty ön the fly - just assign the variables with the correct values and enable overflow interrupt flag (ie tim -> DIER |= TIM_DIER_UIE;) :

To change the values you may use the timer DMA burst mode but the logic is a bit more complicated (I usually do it this way in my projects)

void InitTimerPWM(TIM_TypeDef *tim int channel)
{
    if(!(tim -> CR1 & TIM_CR1_CEN)
    {
        tim -> CR1 |= TIM_CR1_ARPE;
        if(tim != TIM2 && tim != TIM3 && tim != TIM4) __SetVaueIfDifferent((void *)&tim -> BDTR , TIM_BDTR_MOE, TIM_BDTR_MOE);
    }

    switch(channel)
    {
    case CH1:
        __SetVaueIfDifferent((void *)&tim -> CCMR1, TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1PE, TIM_CCMR1_OC1M_Msk | TIM_CCMR1_OC1PE);
        __SetVaueIfDifferent((void *)&tim -> CCER, TIM_CCER_CC1E, TIM_CCER_CC1E);
        break;
    case CH2:
        __SetVaueIfDifferent((void *)&tim -> CCMR1, TIM_CCMR1_OC2M_1 | TIM_CCMR1_OC2M_2 | TIM_CCMR1_OC2PE, TIM_CCMR1_OC2M_Msk |  TIM_CCMR1_OC2PE);
        __SetVaueIfDifferent((void *)&tim -> CCER, TIM_CCER_CC2E, TIM_CCER_CC2E);
        break;
    case CH3:
        __SetVaueIfDifferent((void *)&tim -> CCMR2, TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC3PE, TIM_CCMR2_OC3M_Msk |  TIM_CCMR2_OC3PE);
        __SetVaueIfDifferent((void *)&tim -> CCER, TIM_CCER_CC3E, TIM_CCER_CC3E);
        break;
    case CH4:
        __SetVaueIfDifferent((void *)&tim -> CCMR2, TIM_CCMR2_OC4M_1 | TIM_CCMR2_OC4M_2 | TIM_CCMR2_OC4PE, TIM_CCMR2_OC4M_Msk |  TIM_CCMR2_OC4PE);
        __SetVaueIfDifferent((void *)&tim -> CCER, TIM_CCER_CC4E, TIM_CCER_CC4E);
        break;
    default:
        break;

    }
}

void StartTimer(TIM_TypeDef *tim)
{
    if(!(tim -> CR1 & TIM_CR1_CEN))
    {
        tim -> CNT = 0;
        tim -> ARR = ARR;  // table with the reload values if you need t
        tim -> PSC = PSC;
        tim -> DIER |= TIM_DIER_UIE;
        tim -> EGR |= TIM_EGR_UG;
        tim -> CR1 |= TIM_CR1_CEN;
    }
}


void  __SetVaueIfDifferent(void *ptrx, uint32_t value, uint32_t mask) {
    uint32_t *ptr = ptrx;
    uint32_t target = *ptr;
    if(mask)
    {
        if((target & mask) == value)
            return;
        target &= ~mask;
        target |= value;
    }
    *ptr = target;
}

#define INLINE inline  __attribute__((always_inline))

static INLINE void PWMHandler(volatile TIM_TypeDef *tim)
{
    if(tim -> SR & TIM_SR_UIF & tim -> DIER)
    {
        tim -> SR &= ~(TIM_SR_UIF);
        tim -> DIER &= ~TIM_DIER_UIE;
        tim -> PSC = PSC;
        tim -> ARR = ARR;
        tim -> CCR1 = CCR1; //id timer has more or less channels just add couple of the lines and the if
        tim -> CCR2 = CCR2;
        tim -> CCR3 = CCR3;
        tim -> CCR4 = CCR4; 
    }
}

/* and example handler*/

void TIM3_IRQHandler(void)
{
    PWMHandler(TIM3);
}