2024-01-21 12:34 AM
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:
Neither of the channels (1 or 2) output an signal.
Clock configuration just in case:
Most probably its a beginners mistake somewhere, but I can not track it down. Any ideas?
Solved! Go to Solution.
2024-03-08 07:39 AM
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
2024-03-08 09:51 AM
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){
}
}
2024-03-08 10:36 AM
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
2024-03-09 02:43 AM
Yes, that was the problem. Now I got my PWM working purely on registers... Thank you so much for your help
2024-03-10 01:25 AM
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