cancel
Showing results for 
Search instead for 
Did you mean: 

STM32F756VGH6 not generating PWM.

Vilius
Associate II

Hi, I am a beginner STM32 user. I am exploring the STMCube ecosystem and some simple functions my MCU can do. However, I stuck on generating PWM signal (I simply dont get any signal on the pin, tried few different pins), which based on some youtube STMCube tutorials should be really easy. My hardware and clock setups are verified earlier, so the only thing wrong or incomplete is STMCube PWM configuration:

The code:

 

#include "main.h"


TIM_HandleTypeDef htim9;


void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_TIM9_Init(void);

int main(void)
{

  HAL_Init();

  SystemClock_Config();


  MX_GPIO_Init();
  MX_TIM9_Init();

  HAL_TIM_PWM_Start(&htim9, TIM_CHANNEL_2);

  while (1)
  {

  }
}


void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE3);

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
  RCC_OscInitStruct.PLL.PLLM = 16;
  RCC_OscInitStruct.PLL.PLLN = 256;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 2;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
  {
    Error_Handler();
  }
}

static void MX_TIM9_Init(void)
{

  TIM_OC_InitTypeDef sConfigOC = {0};

  htim9.Instance = TIM9;
  htim9.Init.Prescaler = 0;
  htim9.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim9.Init.Period = 60000;
  htim9.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim9.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  if (HAL_TIM_PWM_Init(&htim9) != 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(&htim9, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.Pulse = 50000;
  if (HAL_TIM_PWM_ConfigChannel(&htim9, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
  {
    Error_Handler();
  }

  HAL_TIM_MspPostInit(&htim9);

}


static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  __HAL_RCC_GPIOH_CLK_ENABLE();
  __HAL_RCC_GPIOE_CLK_ENABLE();

  HAL_GPIO_WritePin(GPIOE, GPIO_PIN_4, GPIO_PIN_RESET);

  GPIO_InitStruct.Pin = GPIO_PIN_4;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

}

void Error_Handler(void)
{
  __disable_irq();
  while (1)
  {
  }
}

#ifdef  USE_FULL_ASSERT

void assert_failed(uint8_t *file, uint32_t line)
{

}
#endif

 

 And the actual PWM configuration this code is generated on:

Vilius_0-1705825909751.png

Neither of the channels (1 or 2) output an signal.
Clock configuration just in case:

Vilius_1-1705825993797.png

Most probably its a beginners mistake somewhere, but I can not track it down. Any ideas?

14 REPLIES 14

Work up step by step.

First, write a loopdelay blinky. Don't try to change the clocks in RCC at first, go with the defaults. Try to follow this writeup.

If that works, try the faster clock setup.

> how do I set the PA11 pin to channel 4 to timer 1

First, read GPIO chapter in RM.

Set GPIO_MODER for given pin to 0b10 for Alternate function.

Then in Datasheet (not Reference Manual), look at the pins Alternate Function table, find PA11, and the column where you see TIM1_CH4; that gives you the AF number. Set GPIO_AFR[] for given pin to this number.

You may want to visit also GPIO_OSPEEDR.

JW

Thank you for your observations. That part was really missing in my code, thanks to you I have read about it and added the PA11 GPIO setup for alternate function of Timer1 CH4. However, I still do not get the PWM on the pin (tried to play with different GPIO pull type and speed settings, it did not change anything). Is there anything obviously wrong with my PWM setup function? I am 100% confident that my core clock ant timer1 itself setups are correct.

I am not sure about: GPIOA->AFR[1] syntax, but the compiler did not accept the GPIOA->AFRH or GPIOA->AFRL syntax as the registers were named in the RM. HAL header file was holding AFR[2] variable type, so thats the only way syntax I could get to compile...

#include "main.h"

void Core_Clock_Setup (void){

	RCC->CR |= RCC_CR_HSEON;               //Set the clock source to external crystal/resonator (HSE)
	while (!(RCC->CR & RCC_CR_HSEON));	   //Wait until clock gets stable

	RCC->APB1ENR |= RCC_APB1ENR_PWREN;     //Enable power interface clock
	PWR->CR1  &= ~(1U << 14);
	PWR->CR1  &= ~(1U << 15);              //Set internal voltage regulator to is reset value (scale 1)

	FLASH->ACR &= ~FLASH_ACR_ARTEN;        //Disable ART accelerator
	FLASH->ACR &= ~FLASH_ACR_ARTRST;       //Reset ART accelerator
	FLASH->ACR |= FLASH_ACR_PRFTEN;        //Enable prefetch
	FLASH->ACR |= FLASH_ACR_LATENCY_6WS;   //Set 7 CPU clock cycle flash memory access time (in order to get 200 MHz core clock)

	//@ 25 MHz crystal, 200 MHz core clock configuration down below

	RCC->CFGR &= ~(((1 << (7 - 4 + 1)) - 1) << 4);
	RCC->CFGR |= (0 << 4);                 //Core clock division by 1 (core clock is not devided)

	RCC->PLLCFGR |= RCC_PLLCFGR_PLLSRC_HSE;//HSE is set to be PLL entry

	RCC->PLLCFGR &= ~(1 << 16);              //PLLP Setting corresponding PLL prescalers (division by 2)
	RCC->PLLCFGR &= ~(1 << 17);

	RCC->PLLCFGR &= ~((1 << 6) - 1);
	RCC->PLLCFGR |= (16 & ((1 << 6) - 1));  //PLLM Setting corresponding PLL prescalers (division by 16)

	RCC->PLLCFGR &= ~(((1 << (14 - 6 + 1)) - 1) << 6);
	RCC->PLLCFGR |= (256 << 6);          	//PLLN Setting corresponding PLL prescalers ( multiplication by 256)

	RCC->CFGR |= RCC_CFGR_PPRE1_DIV4;      //APB1 Low speed prescaler of 4 (50 MHz, max is 54 Mhz)
	RCC->CFGR |= RCC_CFGR_PPRE2_DIV2;      //APB2 High speed prescaler of 2 (100 MHz, max is 108 Mhz)

	RCC->CR |= RCC_CR_PLLON;               //Enable PLL
	while (!(RCC->CR & RCC_CR_PLLRDY));	   //Wait until PLL gets stable

	RCC->CFGR |= RCC_CFGR_SW_PLL;          //PLL is set to be core clock


	//RCC->CFGR |= RCC_CFGR_SW_HSE;        //HSE is set to be core clock

	while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL); // Wait until PLL indeed becomes core clock source
}

//----------------------------------------------------------------------------------------------------------------------------------------------------------------------
void Timer1_Setup(void){ //16 bit advanced timer

	RCC->DCKCFGR1 &= ~(1 << 24);          //TIMxCLK = 2xPCLKx

	//When TIMPRE bit of the RCC_DCKCFGR1 register is reset, if APBx prescaler is 1, then TIMxCLK = PCLKx, otherwise
	//TIMxCLK = 2x PCLKx.

	// When TIMPRE bit in the RCC_DCKCFGR1 register is set, if APBx prescaler is 1,2 or 4, then TIMxCLK = HCLK, otherwise
	//TIMxCLK = 4x PCLKx.

	RCC->APB2ENR |= (1 << 0);               //Enable Timer 1 clock
	TIM1->PSC = 99;                         //APB1 is 50 Mhz and 100 MHZ for timer (The number is set: Clock in MHz - 1)
	TIM1->ARR = 0x2710;                     //Auto reload at 10000 -> around 100 micro seconds at 100 MHz timer clock

}
//----------------------------------------------------------------------------------------------------------------------------------------------------------------------

void PWM_Setup(void){

	TIM1->CCR1 = 0x2134;                    //Duty cycle of around 85 percent, considering period 0x2710

	//START unlock sequence

	TIM1->CCER &= ~ (1 << 0);               //CC1E disabled

	TIM1->CCMR1 &= ~ (1 << 0);               //CC1 channel is configured as input, IC1 is mapped on TI1
	TIM1->CCMR1 |=   (1 << 1);               //CC1 channel is configured as input, IC1 is mapped on TI1

	TIM1->BDTR &= ~ (1 << 8);               //LOCK OFF - No bit is write protected.
	TIM1->BDTR &= ~ (1 << 9);               //LOCK OFF - No bit is write protected.
	//END unlock sequence

	TIM1->CCMR1 &= ~(((1 << (6 - 4 + 1)) - 1) << 4);
	TIM1->CCMR1 |= (0b110 << 4);            //PWM mode 1 - In upcounting, channel 1 is active as long as TIMx_CNT<TIMx_CCR1 else inactive
	TIM1->CCMR1 |= (1 << 3);                //Output compare 1 preload enable

	TIM1->CCMR1 &= ~ (1 << 0);               //CC1 channel is configured as output
	TIM1->CCMR1 &= ~ (1 << 1);               //CC1 channel is configured as output

	TIM1->CR1 |= (1 << 7);                  //TIMx_ARR register is buffered

	TIM1->EGR |= (1 << 0);                  //Re-initialize the counter and generates an update of the registers. Note that the prescaler counter is cleared too (anyway the prescaler ratio is not affected).

	TIM1->CCER &= ~(1 << 1);                //OC1 active high
	TIM1->CCER |= (1 << 0);                 //On - OC1 signal is output on the corresponding output pin depending on MOE, OSSI, OSSR, OIS1, OIS1N and CC1NE bits.

	TIM1->CR2 |= (1 << 8);                  //OIS1 OC1=1 (after a dead-time if OC1N is implemented) when MOE=0


	TIM1->BDTR |= (1 << 15);                //MOE   OC and OCN outputs are enabled if their respective enable bits are set (CCxE, CCxNE in TIMx_CCER register)
	TIM1->BDTR |= (1 << 11);                //OSSR  When inactive, OC/OCN outputs are enabled with their inactive level as soon as CCxE=1 or CCxNE=1 (the output is still controlled by the timer)
	TIM1->BDTR |= (1 << 10);                //OSSI  When inactive, OC/OCN outputs are first forced with their inactive level then forced to their idle level after the deadtime. The timer maintains its control over the output.

	//-------------
	while(!(TIM1->SR & (1<<0)));            //Wait until timer update bit is set
	TIM1->CR1 |= (1 << 0);                  //Enable Timer 1 counter
}
//----------------------------------------------------------------------------------------------------------------------------------------------------------------------
int main (void){


	Core_Clock_Setup();

	//GPIO PA11 config for PWM CH4 TIM 1--------------------------------------

	RCC->AHB1ENR |= (1 << 0);                //Enable clock for GPIO bank A

	GPIOA->MODER |= (1 << 23);               //Alternate function setup
	GPIOA->MODER &= ~ (1 << 22);

	GPIOA->OTYPER &= ~ (1 << 11);            //PA11 configured as push-pull type (not open drain)

	GPIOA->OSPEEDR |=   (1 << 22);           //Medium GPIO speed
	GPIOA->OSPEEDR &= ~ (1 << 23);

	GPIOA->PUPDR &= ~  (1 << 22);            //No pull up, no pull down resistors
	GPIOA->PUPDR &= ~  (1 << 23);

	GPIOA->AFR[1] |=   (1 << 12);            //AF1 config
	GPIOA->AFR[1] &= ~ (1 << 13);
	GPIOA->AFR[1] &= ~ (1 << 14);
	GPIOA->AFR[1] &= ~ (1 << 15);

	//---------------------------
	Timer1_Setup();
	PWM_Setup();

	while(1){

	}

}

You want to use TIM1_CH4, but you are configuring TIM1_CH1 in TIM1_CCMR1, TIM1_CCER and TIM1_CCR1.

It's always helpful to read out and check the registers you've written. Often, an "aha!" moment comes from such check.

JW

Yes, that was the problem. Now I got my PWM working purely on registers... Thank you so much for your help

HI @Vilius ,

glad you got it working.

A couple of stylistical remarks:

- instead of using "magic" values, try to use the symbols defined in CMSIS-mandated device header, e.g.

TIM1->CR1 |= TIM_CR1_CEN

for enabling timer's counter. You already use them in some instances; they make reading code somewhat easier. Those symbols are designed to match the registers' and bitfields' names in the Reference Manual, so they are easy to look up and relatively easy to remember too. 

- TIMx_EGR is a write-only register, so don't use |= but only = when writing into it. As it is now it does what you intend, as it reads as 0, but it's better to do things correctly.

- you don't have writes to TIMx_SR so after setting TIMx_EGR.UG, don't be surprised if the Update interrupt will fire immediately after enabling timer interrupts. Also, when when you'll clear individual bits in TIMx_SR, don't use &= (i.e. a read-modify-write, RMW) operation, but, again, a direct write.

- it's relatively dangerous to enable a peripheral's clock in RCC and then immediately after that write to that peripheral's registers (see related erratum). We've discussed this here extensively.

JW