cancel
Showing results for 
Search instead for 
Did you mean: 

Fighting faulty PWM output with a servo

TSche.8
Associate

Ive ben meaning to create a PWM output on an STM32 Nucleof303re, however: its very buggy and i dont know why. Im trying to drive a servo with it but this half-works. When i tap the pwm contact of the servo it sometimes springs to life and sometimes doesnt (dont worry, the contacts are fine i checked them). So it has to be in the code, however after hours i cannot find it. Please let me know if you can find something that could help me :)

Code:

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2021 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under Ultimate Liberty license
  * SLA0044, the "License"; You may not use this file except in compliance with
  * the License. You may obtain a copy of the License at:
  *                             www.st.com/SLA0044
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "cmsis_os.h"
#include "usart.h"
#include "gpio.h"
#include <string.h>
#include <stdio.h>
 
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
 
/* USER CODE END Includes */
 
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
 
/* USER CODE END PTD */
 
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
 
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
 
/* USER CODE END PM */
 
/* Private variables ---------------------------------------------------------*/
 
/* USER CODE BEGIN PV */
 
/* USER CODE END PV */
 
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void MX_FREERTOS_Init(void);
/* USER CODE BEGIN PFP */
 
/* USER CODE END PFP */
 
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
extern "C" void TIM2_IRQHandler(void)
{
  TIM2->SR |= ~TIM_SR_CC1IF;
}
 
void SetupServo(TIM_TypeDef* tim, IRQn_Type interruptPort, GPIO_TypeDef* port, uint8_t pin)
{
  RCC->APB1ENR |= 0x1 << RCC_APB1ENR_TIM2EN_Pos;
 
  tim->PSC = (72-1);
  tim->ARR = 20000;
 
  tim->CCMR1 |= ((0x3) << 5);
  tim->CCR1 = 2000; 
  
  tim->CCER |= TIM_CCER_CC1E; //0x1 << 0;
  tim->CR1 |= TIM_CR1_CEN; //0x1 << 0;
 
  NVIC_EnableIRQ(interruptPort);
  tim->DIER |= TIM_DIER_CC1IE;
 
  port->MODER = (port->MODER & ~(pin * 2)) | (0b10 << (pin * 2)); //init alternate function
  port->AFR[0] = (port->AFR[0] & ~(pin)) | (0b0001 << (pin * 4)); //set alternate function af1 to pa0
}
/* USER CODE END 0 */
 
/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
 
  /* USER CODE END 1 */
 
  /* MCU Configuration--------------------------------------------------------*/
 
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
 
  /* USER CODE BEGIN Init */
 
  /* USER CODE END Init */
 
  /* Configure the system clock */
  SystemClock_Config();
 
  /* USER CODE BEGIN SysInit */
 
  /* USER CODE END SysInit */
 
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART2_UART_Init();
  /* USER CODE BEGIN 2 */
  /* USER CODE END 2 */
 
  /* Init scheduler */
  // ES Course Comments: Uncomment the three lines below to enable FreeRTOS.
  //osKernelInitialize(); /* Call init function for freertos objects (in freertos.c) */
  //MX_FREERTOS_Init();
  //osKernelStart(); /* Start scheduler */
 
  /* We should never get here as control is now taken by the scheduler */
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  SetupServo(TIM2, TIM2_IRQn, GPIOA, 0);
  const int MSGBUFSIZE = 80;
  char msgBuf[MSGBUFSIZE];
  while (1)
  {
    /* USER CODE END WHILE */
 
    
    snprintf(msgBuf, MSGBUFSIZE, "%s", "In loop!\r\n");
    HAL_UART_Transmit(&huart2, (uint8_t *)msgBuf, strlen(msgBuf), HAL_MAX_DELAY);
    
    TIM2 -> CCR1 = 500;
 
    for (int i = 500; i < 2600; i++)
    {
      TIM2 -> CCR1 = i;
      
      HAL_Delay(10);
    }
    
 
    HAL_Delay(100);
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}
 
/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
 
  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  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.PLLMUL = RCC_PLL_MUL9;
  RCC_OscInitStruct.PLL.PREDIV = RCC_PREDIV_DIV1;
  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_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
 
  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
  PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART2;
  PeriphClkInit.Usart2ClockSelection = RCC_USART2CLKSOURCE_PCLK1;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
  {
    Error_Handler();
  }
}
 
/* USER CODE BEGIN 4 */
 
/* USER CODE END 4 */
 
/**
  * @brief  Period elapsed callback in non blocking mode
  * @note   This function is called  when TIM17 interrupt took place, inside
  * HAL_TIM_IRQHandler(). It makes a direct call to HAL_IncTick() to increment
  * a global variable "uwTick" used as application time base.
  * @param  htim : TIM handle
  * @retval None
  */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  /* USER CODE BEGIN Callback 0 */
 
  /* USER CODE END Callback 0 */
  if (htim->Instance == TIM17)
  {
    HAL_IncTick();
  }
  /* USER CODE BEGIN Callback 1 */
 
  /* USER CODE END Callback 1 */
}
 
/**
  * @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 */
 
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

2 REPLIES 2
TDK
Guru

If you debug the program, is the counter enabled and counting correctly? Is execution where you expect it to be?

> TIM2->SR |= ~TIM_SR_CC1IF;

This doesn't clear CC1IF. This does:

TIM2->SR = ~TIM_SR_CC1IF;

Your assignments to MODER and AFR registers work, but only because the default values are 0. You are not clearing with the correct mask before you OR with the desired value.

If you feel a post has answered your question, please click "Accept as Solution".

ARR = 20000-1 for 50 Hz from a 1 MHz source

Notional range of CCR1 for this type of application would be 1000 to 2000, with 1500 being 1.5ms, top-dead-center, and the overall range being determined by the mechanical limits of the servo

500 is NOT a viable pulse width. Look at signal with a scope to determine if a) the frequency is correct, b) the pulse widths are present/correct.

You don't need interrupts for PWM to work, and if you wanted to reconfigure 4 channels you'd do so at the UPDATE event.

Consider using libraries before going to register level programming, few are interested in unpack others broken code, simpler just to write stuff that works. There are issues with touching the peripheral immediately after enabling the clock, as the writes are buffered and can occur back-to-back from the peripherals perspective. Calls to/from library slow this, and why with the original SPL you enabled the all the clocks first, and then the peripherals, as it masked the hazard. Peripheral registers have combinational logic behind them, they are not memory. Don't make assumptions about initial content.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..