cancel
Showing results for 
Search instead for 
Did you mean: 

Trying to use PWM to toggle LED, won't output to LED

SG.121
Associate II

Hi folks

New to STM32 and getting into it by starting with clocks, timers and ADC. All I want to do is dim a few of the onboard LED's with a pot on a breadboard. Stepping through debug I've found the following

  • Analog input is reading values from the potentiometer fine and converting to correct duty cycle values
  • Onboard LED's are set to TIM4 with respective channels PD12 (CH1), PD13 (CH2), PD14 (CH3)
  • Using the output compare with TIM4 to = duty cycle
TIM4->CCR1 = duty_cycle;
TIM4->CCR2 = duty_cycle;

And I'm getting nothing. Wiring must be fine if it's reading values, all parameters seem to be set up correctly and I think clocks are too (?).

Full code follows below. Any help appreciated!

Edit: Using F411VE Discovery board

#include "main.h"
 
ADC_HandleTypeDef hadc1;
 
RTC_HandleTypeDef hrtc;
 
TIM_HandleTypeDef htim4;
 
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_ADC1_Init(void);
static void MX_RTC_Init(void);
static void MX_TIM4_Init(void);
 
int main(void)
{
 
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
 
  /* Configure the system clock */
  SystemClock_Config();
 
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_ADC1_Init();
  MX_RTC_Init();
  MX_TIM4_Init();
 
  while (1)
  {
  
	 HAL_ADC_Start(&hadc1); 
 
	 uint16_t adc_value = HAL_ADC_GetValue(&hadc1);
 
	 /* Convert ADC value to voltage */
	 float voltage = (float) adc_value * 5.0f / 4095.0f;
 
	 TIM4->ARR; /*is this needed?*/
 
	 //printf("Potentiometer value: %.2f V\r\n", voltage);
 
	 //sprintf(msg, "%hu\r\n", raw);
	 //HAL_UART_Transmit(&huart2, (uint8_t*)msg, strlen(msg), HAL_MAX_DELAY);
	 uint16_t duty_cycle = (adc_value / 4095.0) * 1000;
	 TIM4->CR1 |= TIM_CR1_CEN;
 
	 TIM4->CCR1 = duty_cycle;
	 TIM4->CCR2 = duty_cycle;
	 
	 GPIOD->ODR |= (1 << 14); /*test
  }
  /* USER CODE END 3 */
}
 
/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
 
  /** Configure the main internal regulator output voltage
  */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
 
  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_LSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.LSIState = RCC_LSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
 
  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
 
  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
  {
    Error_Handler();
  }
}
 
static void MX_ADC1_Init(void)
{
 
  ADC_ChannelConfTypeDef sConfig = {0};
 
 
  /** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)
  */
  hadc1.Instance = ADC1;
  hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV2;
  hadc1.Init.Resolution = ADC_RESOLUTION_12B;
  hadc1.Init.ScanConvMode = DISABLE;
  hadc1.Init.ContinuousConvMode = DISABLE;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
  hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.NbrOfConversion = 1;
  hadc1.Init.DMAContinuousRequests = DISABLE;
  hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  if (HAL_ADC_Init(&hadc1) != HAL_OK)
  {
    Error_Handler();
  }
 
  /** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
  */
  sConfig.Channel = ADC_CHANNEL_6;
  sConfig.Rank = 1;
  sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN ADC1_Init 2 */
 
  /* USER CODE END ADC1_Init 2 */
 
}
 
/**
  * @brief RTC Initialization Function
  * @param None
  * @retval None
  */
static void MX_RTC_Init(void)
{
 
  hrtc.Instance = RTC;
  hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
  hrtc.Init.AsynchPrediv = 127;
  hrtc.Init.SynchPrediv = 255;
  hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
  hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
  hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
  if (HAL_RTC_Init(&hrtc) != HAL_OK)
  {
    Error_Handler();
  }
 
}
 
/**
  * @brief TIM4 Initialization Function
  * @param None
  * @retval None
  */
static void MX_TIM4_Init(void)
{
 
  /* USER CODE BEGIN TIM4_Init 0 */
 
  /* USER CODE END TIM4_Init 0 */
 
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};
 
  /* USER CODE BEGIN TIM4_Init 1 */
 
  /* USER CODE END TIM4_Init 1 */
  htim4.Instance = TIM4;
  htim4.Init.Prescaler = 0;
  htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim4.Init.Period = 65535;
  htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim4) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim4, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_OC_Init(&htim4) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_TIMING;
  sConfigOC.Pulse = 0;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  if (HAL_TIM_OC_ConfigChannel(&htim4, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_OC_ConfigChannel(&htim4, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM4_Init 2 */
 
  /* USER CODE END TIM4_Init 2 */
  HAL_TIM_MspPostInit(&htim4);
 
}
 
/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
 
  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOH_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOD_CLK_ENABLE();
 
  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOD, GPIO_PIN_14, GPIO_PIN_RESET);
 
  /*Configure GPIO pin : PD14 */
  GPIO_InitStruct.Pin = GPIO_PIN_14;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
 
}
 
/* USER CODE BEGIN 4 */
 
/* USER CODE END 4 */
 
/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}
 
#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

8 REPLIES 8

I don't use Cube, but

> sConfigOC.OCMode = TIM_OCMODE_TIMING;

Probably not, you want some of the PWM modes.

Also, if you set timer period to 65536 and then set CCRx between 0 and 1000, the duty cycle is relatively low and you may see nothing on the LEDs. You may want to observe the pin(s) using oscilloscope.

Read out and check/post content of TIM and relevant GPIO registers. For experiment, you may want to stop the program execution and in debugger directly change the TIM registers. It's fun and very illustrative.

At the end of the day, you may want to program timers through its registers, it's not that difficult.

JW

gbm
Lead III
  1. PA13 and PA14 are the debug interface pins - not a good choice for timer outputs.
  2. CubeIDE-generated code soes not enable PWM outputs by default. Use TIM_PWM_Start() explicitly after timer config.
  3. In the code above, I cannot see the PWM pins being configured as AF outputs - fix it.

> PA13 and PA14 are the debug interface pins - not a good choice for timer outputs.

The LEDs indeed are not on PA12/13/14, but PD12/13/14/15.

0693W00000aJKL1QAO.pngJW

Oh, thats a mistake in my post, I do have it set to PD12, PD13 and PD14. Edited it to correct that

SG.121
Associate II

1 Actually the pins have always been on PD12,13,14 - that was just a typo in my post!

2 Will try the TIM_PWM_Start()

3 AF outputs? I didn't know the PWM had to be configured as an output (if it's already an analog read?)

Read out and check/post content of TIM and relevant GPIO registers. For experiment, you may want to stop the program execution and in debugger directly change the TIM registers. It's fun and very illustrative.

That does sound fun, but I didn't know I could do that. I've just been stepping through, and then if I see something I don't like I changed code, then stop and restart debugger. Can I change values live while it's executing?

Again, very new to STM (and embedded in general). All i've done is simple arduino projects, but am wanting to go deeper.

I think the Timer and HAL_TIM_PWM_Start() is the main thing thats missing. You need it for the timer and for every PWM channel e.g.

 if (HAL_TIM_Base_Start(&htim4) != HAL_OK)
 {
    /* Starting Error */
    Error_Handler();
 }
 if (HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_1) != HAL_OK)
 {
    /* PWM Generation Error */
    Error_Handler();
  }
  if (HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_1) != HAL_OK)
  {
     /* PWM Generation Error */
     Error_Handler();
   }
   if (HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_1) != HAL_OK)
   {
      /* PWM Generation Error */
      Error_Handler();
   }

I think its quite confusing that the init call is generated but the starting itself is not.

Yes, it MUST BE an AF output. "analog" in STM32 means "disconnected from digital circuitry".