아두이노 라인트레이서 속도 - adu-ino lainteuleiseo sogdo

2 minute read

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

차체 및 회로 구성

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

  • 아두이노 인코더를 이용해 일정한 거리 보내기

똑바로 가기

왜 한쪽으로 휘어지는가?

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

아두이노 라인트레이서 속도 - adu-ino lainteuleiseo sogdo

똑바로 보내려면?

두 모터의 속도가 같아지도록 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일 때 가장 똑바로 차가 움직인다는 것을 알 수 있었습니다. 하지만 중간에 오차가 생겨 좌우로 조금씩 흔들리는 것을 발견할 수 있었는데, 다음에는 이를 개선하는 방안을 찾아보려고 합니다.

아두이노 라인 트레이서 만들기 - 두 개의 디지털 센서를 이용한 실험

3 minute read

안녕하세요! 줄 따라가는 로봇 만들기 두 번째 시간입니다. 오늘은 지난 포스트에서 완성했던 차체에 전선을 연결하고, 두개의 센서의 디지털 신호를 이용해서 줄을 따라가는 실험을 실행해보고자 합니다.

전선 배선

차체 조립에 대한 정보는 아래 링크를 참고해주시기 바랍니다.

아두이노 줄 따라가는 로봇 조립하기

전선 배선은 아래 표에 나와있는 내용대로 진행하였습니다.

아두이노 모터 L298N TCRT5000 (왼쪽) TCRT5000 (오른쪽) HC-06
VCC   VCC VCC VCC  
GND   GND GND GND  
4   IN2      
5   ENA      
6   ENB      
7   IN4      
8   IN3      
9   IN1      
10     D0    
11       D0  
12         RX
13         TX
  motor A OUT1      
  motor A OUT2      
  motor B OUT3      
  motor B OUT4      

트랙 만들기

트랙은 밑의 링크에 나와있는 pdf 파일을 인쇄하여 만들었습니다. 차가 다녀도 손상되지 않도록 두꺼운 종이를 덧붙여 완성하였습니다.

아두이노 줄 따라가는 로봇 트랙

아두이노 라인트레이서 속도 - adu-ino lainteuleiseo sogdo
아두이노 라인트레이서 속도 - adu-ino lainteuleiseo sogdo
아두이노 라인트레이서 속도 - adu-ino lainteuleiseo sogdo

실험

원리

이번 실험은 센서 두개의 길 인식 여부에 따라 자동차를 움직입니다. 우선, 길폭보다 더 넓게 두개의 센서를 배치하여 처음에는 두 센서 모두 길을 인식하지 않도록 설정합니다. 이 때 자동차는 두 센서 중 적어도 하나에 길이 인식될 때까지 전진합니다. 전진 하던 중 어느 쪽 센서에 길이 인식되었다는 것은 곧 길이 휘어졌다는 것을 의미합니다. 즉, 오른쪽 센서가 길을 인식했다면 오른쪽으로 길이 휘어졌다는 것을 의미하여 왼쪽 바퀴를 돌려 자동차를 오른쪽으로 돌려야 하고 반대로 왼쪽 센서가 길을 인식했다면 왼쪽으로 길이 휘어졌다는 것을 의미하기에 오른쪽 바퀴를 돌려 자동차를 왼쪽으로 돌려야 합니다.

코드

#include <SoftwareSerial.h>

// Motor pins
#define ENA 5 
#define ENB 6
#define IN1 4
#define IN2 9
#define IN3 7
#define IN4 8

// Drives two motors according to the given pwm values.
void drive(int pwmA, int pwmB);

// TCRT5000 sensors
#define IR_R 11
#define IR_L 10

// HC06 pins
#define TX 12
#define RX 13

SoftwareSerial mySerial(RX, TX);

void setup() {
  Serial.begin(115200);
  mySerial.begin(57600);

  // set motor pins to ouput
  pinMode(ENA, OUTPUT);
  pinMode(ENB, OUTPUT);
  pinMode(IN1, OUTPUT); 
  pinMode(IN2, OUTPUT); 
  pinMode(IN3, OUTPUT); 
  pinMode(IN4, OUTPUT); 

  // set TCRT500 pins to input
  pinMode(IR_R, INPUT);
  pinMode(IR_L, INPUT);
}

// speed of motors in pwm value
int speed = 0;

void loop() {
  // check command over BLE
  if (mySerial.available()) {
    char command = mySerial.read();
    if (command == 'g') {
      speed = mySerial.parseInt();
    }
    else if (command == 's') {
      speed = 0;
    }
  }

  // drive line following robot
  if (speed > 0) {
    bool leftDetectLine = digitalRead(IR_L) == HIGH;
    bool rightDetectLine = digitalRead(IR_R) == HIGH;
    
    if (leftDetectLine) {
      drive(speed, 0);
    } else if (rightDetectLine) {
      drive(0, speed);
    } else {
      drive (speed, speed);
    }
  }  else {
    drive(0, 0);
  }
}

/*
 * Drives two motors according to the given pwm values.
 * 
 * To avoid write several functions for each motor direction, I simply
 * drive motor forward if the given value is positive and backward if
 * negative. To stop a motor, just give 0.
 * 
 * Since every DC motors have differenct characteristics, I stop a 
 * motor if the absolute value of the given pwm value is less than 
 * MOTOR_MIN_PWM to drive the vehicle as strait as possible.
 * 
 * @param pwmA PWM value of motor A. An integer value between 
 *             -255 and 255.
 * @param pwmB PWM value of motor B. An integer value between 
 *             -255 and 255.
 */

void drive(int pwmA, int pwmB) {
  // MOTOR A direction
  if (pwmA > 0) {
    digitalWrite(IN1, LOW);
    digitalWrite(IN2, HIGH);
  } else if (pwmA < 0) {
    digitalWrite(IN1, HIGH);
    digitalWrite(IN2, LOW);
  } else {
    digitalWrite(IN1, LOW);
    digitalWrite(IN2, LOW);
  }

  // MOTOR B direction
  if (pwmB > 0) {
    digitalWrite(IN3, LOW);
    digitalWrite(IN4, HIGH);
  } else if (pwmB < 0) {
    digitalWrite(IN3, HIGH);
    digitalWrite(IN4, LOW);
  } else {
    digitalWrite(IN3, LOW);
    digitalWrite(IN4, LOW);
  }

  // speed of motors
  analogWrite(ENA, abs(pwmA));
  analogWrite(ENB, abs(pwmB));
}

다양한 속도로 시험을 하기 위해 블루투스를 통해 명령을 받아 움직이도록 만들었습니다. 시리얼 모니터에 g pwm값을 주면 pwm 값으로 줄을 따라 이동합니다. 시리얼 모니터에 s를 입력하면 차가 멈춥니다.

원리에서 설명한 것과 같이 한 센서가 줄을 발견하면 그 방향의 바퀴를 멈추고 반대쪽 바퀴를 움직여 차의 방향을 조정하였습니다.

결과

느낀 점

이번 실험에서 가장 아쉬웠던 점은 길이 차에 비해 작고 경사가 심해 차가 움직이는데 제약이 생긴다는 것이었습니다. 길이 좀만 더 크고 완만했다면 차를 돌리기 위해 한 쪽 바퀴를 멈추기 보다는 다른 쪽 바퀴에 비해 속도를 줄여 더욱 부드럽게 움직일 수 있을 것입니다. 반대로 이보다 더 길의 경사가 가파르면 한 바퀴를 아예 뒤로 돌려 더 급격하게 방향을 조정할 수 있습니다. 트랙에 따라 조정 방법이 달라진다는 것을 확인 하였습니다.