아두이노 RC카 똑바로 보내기 첫 번째 - 비례 제어기

2 minute read

안녕하세요! 이번에 새로운 프로젝트를 시작해 보려고 합니다. 바로 똑바로 가기 프로젝트입니다! 그동안 다양한 프로젝트에서 차가 앞으로 갈 때 경로가 한 쪽으로 치우쳐서 움직이는 문제가 발생했었는데요, 이러한 문제점을 해결하기 위해 이번엔 가장 기본적이면서도 중요한 비례 제어기를 사용해 보고자 합니다.

차체 및 회로 구성

이번 프로젝트에서 사용한 차체와 회로의 구성은 이전 포스트에서와 완전히 동일합니다. 자세한 정보를 위해서는 이전 포스트인 ‘아두이노 인코더를 이용해 일정한 거리 보내기’를 참고해주세요! 아래 링크를 누르면 바로 이동합니다.

똑바로 가기

왜 한쪽으로 휘어지는가?

아래 그림에서 보는 것처럼 같은 PWM을 주더라도 두 모터의 회전속도가 달라서 한쪽으로 휘어집니다. 아래 그림에서는 오른쪽 바퀴에 붙인 모터 A가 항상 모터 B보다 빠르게 회전하여 차체가 왼쪽으로 휘어졌습니다.

두 모터의 회전 속도 차이 측정

똑바로 보내려면?

두 모터의 속도가 같아지도록 PWM을 조정하여야 합니다. 두 모터를 동시에 조정하는 것은 어렵기 때문에, 한쪽 모터의 회전 속도를 기준으로 다른 모터의 속도를 조절하는 것이 편리합니다.

예를 들어 왼쪽 바퀴에 연결된 모터 B를 기준으로 두고 생각해 봅시다. 모터 A가 모터 B보다 빠르게 돈다면, 모터 A에 주는 PWM을 줄여서 모터 B와 같은 속도로 만들어야 합니다. 반대로, 모터 A가 모터 B보다 느리게 돈다면, 모터 A에 주는 PWM을 높여서 모터 B와 같은 속도로 만들어야 합니다.

그런데 두 모터의 회전 속도 차이가 난다고 어느 한 쪽 모터의 PWM 값을 갑자기 높여버리면 차체가 진전하면서도 좌우로 심하게 흔들리게 됩니다.

이러한 사항을 고려하여 속도 차이가 적게 날 경우에는 PWM 값을 조금 조정하고 속도 차이가 많이 날 경우엔 PWM 값을 많이 하는 방식을 사용하여 차체의 흔들림을 줄일 수 있습니다. 이와 같은 방법을 오류에 비례하는 값으로 출력 값력 조정한다 하여 비례 제어기(proportional controller)라 부릅니다.

비례 제어기

이 프로젝트에서는 모터 B를 기준으로 모터 A의 속도를 조절하여 차량을 똑바로 보내고자 합니다. 우선, 모터 B와 모터 A의 속력 차이를 오차, 즉 error라고 정의합니다. 그러면 error 값이 음수면 모터 A가 빠르다는 것을, 양수면 모터 B가 빠르다는 것을 나타내게 됩니다.

int error = encoderB - encoderA;

에러에 비례하여 모터 A의 PWM을 조정하는데, 어느 정도의 값으로 조정할지를 정하는 값이 비례 상수입니다. 보통 비례 제어기에 사용하는 비례 상수를 Kp라고 부릅니다.

int pwmA = (int)(pwmB + Kp * error);

예를 들어 차체가 왼쪽으로 휘어진다고 가정해 보겠습니다. 이 상황에서는 error값이 음수를 나타게 됩니다. 그러면 pwmA는 pwmB 보다 작게 되므로, 모터 A의 속도가 느려지게 됩니다. 왼쪽에 연결된 모터 B보다 오른쪽에 연결된 모터 A의 속도가 느리게 되어 차량이 똑바로 가게 됩니다.

그런데, pwmA를 조절할 때 사용하는 Kp 값은 반복된 실험을 통해 설정하여야 합니다. 먼저 Kp를 1로 설정하여 차량이 움직이는 모습을 살펴봅니다. 충분히 똑바로 가지 않는다면 Kp 값을 증가시키고, 좌우로 심하게 흔들린다면 Kp 값을 줄입니다. 이와 같은 과정을 반복하여 차량이 흔들림없이 앞으로 가는 적절한 Kp 값을 선택합니다.

코드

Kp를 여러 값으로 바꾸어 실험해야 하기 때문에, 이동 명령에 추가하여 블루 투스를 이용해 Kp값을 설정하는 명령을 추가하였습니다. 시리얼 모니터를 통해 s <Kp 값> 명령을 주어 값을 설정할 수 있습니다.

void loop() {
  if (mySerial.available()) {
    char command = mySerial.read();
    if (command == 'G' || command == 'g') {
      // 움직이기 명령 처리 ...
    } else if (command = 'S' || command == 's') {
      // Kp 설정 명령 처리
      Kp = mySerial.parseFloat();
      mySerial.print("set Kp = ");
      mySerial.println(Kp);
    }
  }
}

비례 제어기를 이용해서 move() 함수를 다음과 같이 수정하였습니다.

// 비례 상수 선언
float Kp = 0.0;

// 비례 제어기로 차량 움직이기
void move(int distance, int pwm) {
  // ...
  while (encoderB <= ticksToMove) {
    int error = encoderB - encoderA;
    int pwmB = pwm;
    int pwmA = (int)(pwmB + Kp * error);
    // ....
    drive(pwmA, pwmB);
    delay(200);
  }
  // ....
}

전체 코드는 깃허브 레포지토리에서 보실 수 있습니다.

실험 동영상

위 동영상과 같이 Kp 값이 7일 때 가장 똑바로 차가 움직인다는 것을 알 수 있었습니다. 하지만 중간에 오차가 생겨 좌우로 조금씩 흔들리는 것을 발견할 수 있었는데, 다음에는 이를 개선하는 방안을 찾아보려고 합니다.