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-01-21 07:44 AM
Initialize those pins as GPIO output and toggle them to verify they are connected as you expect. If that works, run your program and print out the values in the TIMx registers at the time of the error.
> sConfigOC.Pulse = 0;
Note that this will produce a DC signal, not a PWM.
2024-01-21 12:54 AM
Hello @Vilius
As a start, i suggest you check this example to understand how you have to do to make it work.
Best Regards.
STTwo-32
To give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.
2024-01-21 02:20 AM
What hardware is this, a "known good" as Nucleo, or your own?
Set given pins to GPIO output and toggle, to check hardware (bad solder joints, shorts).
Read out and check/post content of TIM and relevant GPIO registers.
JW
2024-01-21 02:21 AM
Hi,
Try : switch on the internal clock...
2024-01-21 03:20 AM
This is my custom learning board. But I have already verified the hardware is working. My programmer reads the chip`s ID, I am getting the clock, I already tested a led blinky program. Despite its custom BGA board, hardware is not a consideration after so many tests and verifications.
2024-01-21 07:44 AM
Initialize those pins as GPIO output and toggle them to verify they are connected as you expect. If that works, run your program and print out the values in the TIMx registers at the time of the error.
> sConfigOC.Pulse = 0;
Note that this will produce a DC signal, not a PWM.
2024-01-21 08:55 AM
Yes, this seemed to be the issue. Thank you.
2024-01-26 12:44 AM
Also I was wondering, is there any way to somehow automatically reverse engineer the Cube generated code to register level? I prefer working with registers but at the same time it is a bit too difficult for me to write a driver on registers only. I would really love to learn an analyze how to convert the Cube generated code back to registers level commands. Is it possible to do something like that?
2024-01-26 02:13 AM
Cube is open source, its sources are readable and can be single-stepped.
JW
2024-03-08 07:13 AM
Ok, late update,
I have been trying to reverse engineer this comparatively simple PWM setup down to register level code on my own. I spent hours and hours reading the datasheet and came up with some code, however it still does not produce PWM (only the autogenerated version does).
So, here is autogenerated code setup for PWM at pin PA11:
#include "main.h"
TIM_HandleTypeDef htim1;
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_TIM1_Init(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
//MX_GPIO_Init();
MX_TIM1_Init();
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_4); //PA11
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_SCALE1);
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
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();
}
if (HAL_PWREx_EnableOverDrive() != 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_6) != HAL_OK)
{
Error_Handler();
}
}
static void MX_TIM1_Init(void)
{
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
htim1.Instance = TIM1;
htim1.Init.Prescaler = 0;
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
htim1.Init.Period = 65000;
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim1.Init.RepetitionCounter = 0;
htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_PWM_Init(&htim1) != HAL_OK)
{
Error_Handler();
}
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();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 43000;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_ENABLE;
sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_4) != 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.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();
}
HAL_TIM_MspPostInit(&htim1);
}
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOH_CLK_ENABLE();
__HAL_RCC_GPIOE_CLK_ENABLE();
__HAL_RCC_GPIOA_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
looking beyond those functions from main.c did not get me far. Reading the datasheet on my own I came up with:
#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();
Timer1_Setup();
PWM_Setup();
while(1){
}
}
All this was written based on the instructions in the picture (from datasheet). I feel like I am getting somewhere but I still can not understand how do I set the PA11 pin to channel 4 to timer 1 (based on the autogenerated code). What is the exact bit that disables and enables the PWM (in case I need to do setup adjustments)? I need just the simplest PWM signal without any fancy stuff datasheet is full of (deadtime compensation, different modes, complementary output etc. non of this is needed for me.) Any hints or provided code adjustments would be much appreciated. Core clock setup and Timer 1 functions are already tested in the previous programs. Problem lies somewhere in the PWM setup function.
P.S. I am not intentionally making my life harder already having working code example. Having a 200 MHz MCU with unclear autogenerated functions slows it down (not just in PWM but in general) to a point arduino uno may become its rival in terms of performance. So granted register based programming is way harder, I still find this way more appropriate to me, (sadly I have reached the step I can not continue on my own anymore...)