본문 바로가기
DSP, MCU/STM32 (ARM Cortex-M)

STM32 , UART 통신 ( 수신 ) 을 이용한 PWM 서보모터 ( SG90 ) 제어 + 펄스폭 찾아내는 팁

by eteo 2022. 5. 26.

 

 

 

 

 

UART 설정

 

USART3을 비동기식으로 켜준다.

 

Parameter는 디폴트로 놔두고 폴링방식을 사용할거면 NVIC 세팅을 켜주지 않아도 된다.

 

만약 MCU/MPU Selector로 시작했다면 보드매뉴얼을 보고 핀도 제대로 잡아주어야 한다.

 

 

 

 

Timer 설정

 

클락설정은 아래와 같다.

 

 

 

Timer 의 클락이 어떤 버스에서 제공되는지는 datasheet 20페이지의 블록 다이어그램을 보면 알 수 있다.

 

내가 사용할 TIM2가 쓰는 클락은 APB1이다.

 

 

 

 

 

Internal Clock 소스로 설정해주고 PWM Generation CH1을 선택해준다.

 

TIM2, PWM CH1에 어떤핀이 연결되어 있는지는 보드매뉴얼을 보고 알 수 있다. Board Selector 를 선택했다면 MX툴에서 그냥 채널 활성화 시키면 해당핀이 깜빡깜빡 거리는데 MCU/MPU Selector에선 매뉴얼 확인이 필수이다.

 

 

 

 

 

 

다음 중요한 TIM2의 Configuration은 다음과 같이 했다.

 

 

 

 

먼저 Prescaler는 타이머에 공급되는 클락을 내가 원하는 값으로 분주하는 것이다. 카운터 클락 주파수와 동일하다.

 

예를 들어 공급되는 클락이 90MHz인데 Prescaler 값을 4500-1으로 분주하면 20KHz(0.05ms)이고 이 0.05ms마다 Counter(CNT) 레지스터의 값이 1씩 증가한다.

 

그러다가 ARR(Auto-Reload Register)로 설정된 값과 동일해지면 Overflow 가 나면서 업데이트 인터럽트가 트리거 되고 CNT의 값은 다시 0으로 돌아간다.

그래서 ARR을 10000-1로 설정해주면 최종적으로 2Hz(0.5s)가 얻어지는 것이다.

 

-1해주는 이유는 0부터 카운트하기 때문이다.

 

 

 

만약 단순히 타이머 인터럽트만 사용한다고 할 때는

 

공급 클락 / (Prescaler * ARR) = 이 원하는 Hz가 되도록 잘 나눠서 설정해주기만 하면 된다.

예를 들어 위의 케이스 라면 각각 4500, 10000 으로 하거나 9000, 5000 으로 하거나 2250, 20000 으로 하거나 결국 같은 결과가 된다.

 

 

하지만 PWM을 사용할땐 ARR의 값이 중요한데, 바로 이 값을 활용해 듀티비(Duty Ratio/Cycle)를 설정하기 때문이다.

 

예를들어 ARR이 1000이고 듀티사이클을 50%로 설정해주려면 그냥 PWM의 Pulse를 500으로 설정해주면 된다. 25%는 250, 75%는 750. 그래서 보통은 듀티사이클을 계산하기 쉽게 ARR을 100 또는 1000 또는 255 같이 딱 떨어지는 값을 사용하거나, 좀 더 세밀한 조정이 필요한 경우에는 더 큰 값을 사용하는 것 같다.

 

 

 

그리고 위 설정에서 PWM mode 1로하면 한 주기 내의 펄스폭 동안 HIGH이고 주기 내의 나머지 시간은 LOW상태가 유지되고, PWM mode 2로 하면 HIGH / LOW 상태가 반대이다.

 

PWM 채널의 Pulse값은 나중에 소스코드에서 CCR (Counter Capture Register) 에 접근해 수정할 수 있기 때문에 위에서는 0으로 해두었다.

 

 

https://deepbluembedded.com/stm32-pwm-example-timer-pwm-mode-tutorial/

 

 

 

 

 

 

회로연결 및 서보모터 제어 설정값 결정

 

사용한 모델은 SG90, 이 서보모터는 0도부터 180도까지 동작할 수 있다.

빨간선은 전원, 갈색선은 GND.

주황색 선을 TIM2의 PWM CH1 포트인 PA0에 연결한다.

 

 

PWM 주기는 서보모터의 datasheet 상 20ms로 설정 해야하는데 주파수는 주기의 역수이니까 50Hz이다.

TIM2에 공급되는 클럭은 90MHz이니까 50Hz를 뽑아내기 위해 Prescaler 값을 1800-1, ARR을 1000-1로 설정해 주었다.

그리고 펄스폭으로 각도를 제어하는데 찾아보니 제품마다 편차가 심하고 데이터시트와 일치하지 않는 경우가 많다.

 

나는 아래 사진의 출처 사이트에서 직접 실험해본 사람의 자료를 참고해 최소각은 0.5ms 최대각은 2.5ms 라고 가정하고 계산하니 잘 맞았다.

 

위에서 말했지만 원하는 펄스폭은 ARR의 비율로 계산해서 쉽게 설정할 수 있다.

 

듀티비(Duty Cycle, %) = 펄스폭(Pulse Width) / 주기(T)

 

0.5ms/20ms*1000(ARR) = 25

2.5ms/20ms*1000(ARR) = 125

 

중간값 75는 90도이다.

 

https://deepbluembedded.com/stm32-servo-motor-control-with-pwm-servo-library-examples-code/

 

 

 

 

 

 

소스코드

  /* USER CODE BEGIN 2 */
  HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
  uint8_t value=25;
//  HAL_UART_Receive_IT(&huart3, &data, 1);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {

	  HAL_UART_Receive(&huart3, &data, sizeof(data), 10);
	  if(data == '1'){
		  HAL_UART_Transmit(&huart3, "button pressed\r\n", strlen("button pressed\r\n"), 10);
		  if(value < 125){
			  value+=50;
			  TIM2->CCR1 = value;
			  HAL_Delay(1000);
		  }else {
			  value=25;
			  TIM2->CCR1 = value;
			  HAL_Delay(1000);
		  }
		  data=0;
	  }



    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }

 

uart 수신은 인터럽트를 쓰려다가 그냥 폴링방식을 썼다. 주석처리로도 그 흔적이 있다.

폴링방식은 마치 전화가 왔는지 계속 반복해서 체크하는 거라면 인터럽트 방식은 그냥 다른일 하다가 전화벨이 울리면 받는 것과 마찬가지라서 인터럽트 방식이 훨씬 효율적이다. 다음에 다른 모듈을 사용해 볼때는 인터럽트 방식을 사용해 보도록 하겠다.

 

USER CODE BEGIN 2 부분에

HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);

를 써줘야한다. 매개변수 값을 직접쓰는게 귀찮다면 이 위의 MX_TIM2_Init(); 함수를 컨트롤 누르고 따라들어가서 복붙해와도 된다.

 

그 다음에 while문 내 UART 수신부분.

HAL_UART_Receive(&huart3, &data, sizeof(data), 10);

타임아웃값은 10ms으로 했고 버퍼는 폴링방식이라 지역변수로 해도 되는데 전역변수구간에 uint8_t data;로 설정해줬었다.

 

서보모터의 듀티비 초기값은

uint8_t value=25;

으로하고 만약 UART로 수신된 데이터가 '1' 이라면 "button pressed\n\r" 문장을 송신한 뒤, 듀티비를 50비율 씩 늘리고 최대각에 도달했다면 다시 최소각으로 간다. 그리고 버퍼에 0을 대입해 한번만 실행되도록 했다. 

 

 

 

그리고 실제 TIM2 의 PWM CH1의 펄스폭을 실시간으로 수정하는데에는

TIM2->CCR1 = value;

를 사용했는데 이 문장 대신 매크로함수인

__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, value);

를 쓰거나

htim2.Instance->CCR1 = value;

를 써도 똑같이 작동한다.

 

만약 한 TIM에 PWM 채널을 여러개 쓴다면 CCR1, CCR2, CCR3, CCR4 에 접근해 각각 제어하면 된다.

 

 

 

외부전원을 사용한 회로연결

 

 

 

 

 

 

 

혹은 CW, CCW 최대각만 찾아내려면 더 간단한 방식도 있다.

 

  /* USER CODE BEGIN 2 */
  HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
	  HAL_UART_Receive(&huart3, &rx, 1, 10);
	  TIM2->CCR1=rx;

	  HAL_Delay(2000);
    /* USER CODE END WHILE */

 

주파수를 50Hz로 맞추고 ARR을 내가 설정한 값과 같이 1000-1로 했다고 했을때, 서보모터의 펄스폭조정은 대략 25~125 사이에서 이루어진다.

 

그럼 UART로 아스키코드를 전송하고 바로 CCR1에 대입해 어떤 값에 어떤 각도로 움직이는지 확인하는 것이다.

아마 내가 가지고 있는 서보 spec과 같다면ESC와 DEL을 눌렀을 때  0도와 180도로 움직이고 , 대문자 M을 눌렀을 때  90도로 움직일 것이다.