본문 바로가기
Embedded/STM32

[STM32] PWM를 통한 SG-90 서보 모터 제어

by Gordon_ 2025. 4. 10.

- 개발 환경

개발 보드 : NUCLEO-F103RB

개발 프로그램

- STM32 CubeMX : 6.12.0

- STM32 CubeIDE : 1.16.0


1. 서론

  많은 액추에이터는 전압을 통해 특정한 각도 및 세기를 제어할 수 있습니다. MCU에서는 DAC을 통해 전압을 제어할 수 있지만, 더 쉽고 간단하게 전압을 직접 조절하지 않고 엑추에이터의 각도 및 세기를 조절할 수 있는 방법이 있습니다. 그 방법이 바로 PWM입니다. PWM을 간략하게 소개하자면 특정 시간내에 전압이 HIGH인 비율을 조절하는 것입니다. 

PWM에 대한 간략한 설명

 

  만약 PWM에서 High가 5V이고 25%의 Duty cycle라면 5V X 0.25 = 1.25V와 비슷한 세기를 엑추에이터에 전달하는 것입니다. 그리고 PWM을 사용하면서 주의 사항이 있습니다. PWM을 사용하는 제품이라는 주파수를 꼭 확인하여 정확한 주파수를 설정하고, 만약 주파수가 설정되어 있지 않다면 60Hz 이상의 주파수를 설정하여야 합니다. 너무 낮은 주파수는 단순히 전압을 On Off한느 것과 같기 때문입니다.

 

2. 타이머 사용 방법

타이머의 자세한 사용방법은 아래 포스트에 설명하였습니다. 시작하시전 해당 포스트를 한번 보고 오시는 것을 추천드립니다.

https://littlebitodd-developer.tistory.com/15

 

[STM32] 간략한 Timer 사용 방법(1ms 주기 설정)

첨부파일개발 환경 개발 보드 : nucleo-f429zi board개발 프로그램- STM32 CubeMX : 6.12.0- STM32 CubeIDE : 1.16.0개요  임베디드 시스템에서 타이머는 매우 기본적인 기능중 하나로 실습시 반드시 해보는 기능

littlebitodd-developer.tistory.com

 

2. SG-90 서보(Servo) 모터

SG-90 서보 모터 Datasheet

http://www.ee.ic.ac.uk/pcheung/teaching/DE1_EE/stores/sg90_datasheet.pdf

SG-90 서보 모터 동작 방법

 

  여기서 확인할 수 있는 사항은 주파수가 50Hz 라는 점과 5V의 지속시간이 1~2ms으로 0~180도를 제어할 수 있다는 점입니다.

 

3. IOC 파일 설정

 

Channel1을 "PWM Generation CH1"으로 설정하면 PA6으로 자동적으로 설정됩니다.

 

 

각 설정에 대해 자세한 설명을 드리도록하겠습니다.

3.1. Prescaler 값을 640 - 1로 설정

  저는 주파수 계산을 하기 편하도록 타이머의 클럭을 되도록이면 10으로 나누어 떨어지도록 하고 있습니다. 또한 Prescaler값을 너무 크게 하지 않을 것입니다. 왜냐하면 Prescaler값이 너무 높다면 Duty cycle를 자세히 설정할 수 없기 때문입니다. 그래서 타이머의 클럭을 100kHz으로 설정할 것입니다.

(여기서 제가 말하는 타이머의 클럭은 타이머의 Count가 1 증가하는 주파수입니다.)

타이머의 클럭은 입력 클럭(64MHz : ABP1 Timer clocks)에서 Prescaler(640)를 나눈 값입니다.

그런데 -1을 한 이유는 Prescaler의 시작값이 0이기에 -1을 하였습니다.

3.2. Counter Period 값을 2000 - 1로 설정

   Counter Period를 2000으로 설정한 이유는 주파수를 50Hz로 맞추기 위해서 입니다. 우리는 타이머의 클럭을 100kHz로 설정하였습니다. 그 의미는 0.00001초 즉, 10us 마다  타이머의 Counter가 1씩 증가한다는 것입니다. 목표 주파수인 50Hz의 주기는 0.02초 즉, 20ms 입니다. 그렇다면 20ms동안 10us마다 Counter가 1씩 증가한다면 Counter의 값은 2000이 되어 있을 것입니다. 그러므로 Counter Period 값을 2000으로 설정하였습니다.

  하지만 편하게 다음과 같은 공식으로도 주파수를 구할 수 있습니다.

 

또한 Prescaler와 같은 이유로 -1을 한 이유는 Counter Period의 시작값이 0이기에 -1을 하였습니다.

3.3. Pulse 값을 200 - 1로 설정

  Pulse값은 Compare의 초기 값을 정하는 설정입니다. Compare는 말 그대로 비교라는 의미입니다. Conter 값과 비교하여 Counter 값이 Compare값 보다 작으면 출력이 High, Counter 값이 Compare 값 보다 크면 출력이 Low가 되는 것입니다.

 

  위 그래프를 보게 되면 Compare의 값에 따라 출력이 결정되는 것을 확인 할 수 있습니다. 그 의미는 Compare이 작아질수록 Duty cycle도 작아지고, Compare이 커질수록 Duty cycle도 커집니다. 

 

그럼 Pulse 값을 200으로 한 이유는 출력을 2ms 만큼 High로 하기 위해서입니다. 타이머의 클럭이 100kHz이므로 10us마다 Counter 값이 1씩 증가합니다. 그렇다면 Counter의 값이 0에서 200에 도달하는 시간이 2ms(10us x 200) 입니다. 그러므로 Pluse 값을 200으로 설정하였습니다.

 

또한 Prescaler와 같은 이유로 -1을 한 이유는 Pulse의 시작값이 0이기에 -1을 하였습니다.

4. 관련 함수 소개

HAL_StatusTypeDef HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel)

- 매개변수

  • TIM_HandleTypeDef *htim
      PWM을 사용할 Timer의 구조체의 주소를 전달
  • uint32_t Channel
      PWM을 사용할 Channel을 입력

- 리턴 값

  • HAL_StatusTypeDef
    HAL_OK : 정상적으로 PWM 출력
    HAL_ERROR : 준비 중 에러 발생
#define __HAL_TIM_SET_COMPARE(__HANDLE__, __CHANNEL__, __COMPARE__)

- 매개변수

  • __HANDLE__
      PWM을 사용할 Timer의 구조체의 주소를 전달
  • __CHANNEL__
      PWM을 사용할 Channel을 입력
  • __COMPARE__
      설정하고 싶은 Compare 값 입력

5. 실습 코드 작성

0도에서 180도로 1초마다 반복하는 코드를 작성하도록 하겠습니다.

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  // Timer3의 Channel1의 PWM 출력 시작작
  HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
  while (1)
  {
    // 서보 모터 각도 0도
    __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 100-1 );
    HAL_Delay(1000);
    // 서보 모터 각도 180도
    __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 200-1 );
    HAL_Delay(1000); 
    
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */

하지만 하드웨어마다 차이가 있어 정확히 180도로 회전하지 않을 수 있습니다. 그런 경우 Compare의 값을 변경하여 적절한 값을 찾아야합니다.