[아두이노] 지그재그 주행 패턴 아두이노 RC카 응용

IOT/아두이노|2019. 5. 31. 09:00

[아두이노] 지그재그 주행 패턴 아두이노 RC카 응용 



지난 시간까지 해서 아두이노 기본 키보드/마우스 동작 명령을 실험을 해보았습니다. 오늘은 아두이노 IDE의 USB 예제에서 "KeyboardReprogram" 소스가 있는데 매크로 키보드 제어 예제로 딱 좋을 것 같아서 소개를 하도록 하겠습니다. 아두이노를 이용하여 PC에서 수행 할 명령을 기록해 놓았다가 기록 된 순서대로 명령을 내릴 수 있습니다. 이 원리를 이용하면 상상력에 따라 다양한 오토 매크로 프로그램을 만들 수 있습니다.

1. 지그재그 주행 패턴 동작 원리


아두이노 RC카가 위 그림처럼 주행 패턴을 만든다면 일종의 로봇청소기와 같은 주행을 만들 수 있겠죠. 이 주행은 과연 어떻게 이뤄질까요.

첫줄은 과연 어떻게 주행 할까요. 위 그림에서 한 칸당 1초 주행이라면 총 6초(전체이동시간)동안 주행하면 첫줄 주행이 끝나겠죠.

motor1.run(FORWARD);
motor2.run(FORWARD);
delay(전체이동시간);

첫줄에서 두번째 줄로 넘어갈려면 90도 회전 후 0.2초 이동 후 다시 같은 방향으로 90도 회전합니다.

위 그림과 같은 회전 모습을 보여야 겠죠.


motor1.run(FORWARD);
motor2.run(BACKWARD);
delay(90도우회전시간);
motor1.run(FORWARD);
motor2.run(FORWARD);
delay(다음라인이동시간);
motor1.run(FORWARD);
motor2.run(BACKWARD);
delay(90도우회전시간);


두번째 줄에서 세번째 줄로 넘어갈려면 위 코딩에서 반대 방향으로 90도 회전 후 0.2초 이동 후 다시 같은 방향으로 90도 회전합니다.

motor1.run(BACKWARD);
motor2.run(FORWARD);
delay(90도좌회전시간);
motor1.run(FORWARD);
motor2.run(FORWARD);
delay(다음라인이동시간);
motor1.run(BACKWARD);
motor2.run(FORWARD);
delay(90도좌회전시간);

종합해 보면,

//홀수라인 정주행
motor1.run(FORWARD);
motor2.run(FORWARD);
delay(전체이동시간);

//짝수다음라인으로 이동
motor1.run(FORWARD);
motor2.run(BACKWARD);
delay(90도우회전시간);
motor1.run(FORWARD);
motor2.run(FORWARD);
delay(다음라인이동시간);
motor1.run(FORWARD);
motor2.run(BACKWARD);
delay(90도우회전시간);

//짝수라인 역주행
motor1.run(FORWARD);
motor2.run(FORWARD);
delay(전체이동시간);

//홀수라인으로 이동
motor1.run(BACKWARD);
motor2.run(FORWARD);
delay(90도좌회전시간);
motor1.run(FORWARD);
motor2.run(FORWARD);
delay(다음라인이동시간);
motor1.run(BACKWARD);
motor2.run(FORWARD);
delay(90도좌회전시간);

이렇게 해서, 주행 하면 되겠죠.

2. 지그재그 주행 패턴 만들기

id movePattern(){
     state=!state; //방향전환상태값

     if(state==true){
       motor1.run(FORWARD);
       motor2.run(BACKWARD);
       delay(90도우회전시간);
       motor1.run(FORWARD);
       motor2.run(FORWARD);
       delay(다음라인이동시간);
       motor1.run(FORWARD);
       motor2.run(BACKWARD);
       delay(90도우회전시간);
     }
     else{
       motor1.run(BACKWARD);
       motor2.run(FORWARD);
       delay(90도좌회전시간);
       motor1.run(FORWARD);
       motor2.run(FORWARD);
       delay(다음라인이동시간);
       motor1.run(BACKWARD);
       motor2.run(FORWARD);
       delay(90도좌회전시간);
     }            
     motor1.run(FORWARD);
     motor2.run(FORWARD);
}

위 코딩을 보면 if문으로

state=!state; //방향전환상태값
if(state==true){
  짝수라인으로 이동;
}
else{
  홀수라인으로 이동;
}
전진: 

state 값을 다음 라인으로 이동할 때 마다 반전시켜서 홀수/짝수 라인을 교대로 방향이 바뀌게 if문으로 제어할 수 있겠죠.

2. 지그재그 주행


위 그림처럼 주행을 하게 하려면 어떻게 코딩해야 할까요.

아래 그림과 같이 상황이 주어질 경우를 살펴보겠습니다. 정해진 구간이 1~6칸까지로 주행을 하게 된다고 가정해 봅시다.

1에서 5까지 갔다가 다음라인에서 3까지 갔다가 다시 다음라인에서 6까지 이동해야 합니다. 이 주행을 하기 위해서 각 진행방향에 대한 남은 주행 거리를 계산해 내야 합니다. 주행을 계산하는 방법이 떠오른 것이 대충 3가지 종류가 되는데 일일히 다 설명하자니 너무 post가 길어질 것 같아서 제일 맘에 드는 방법 중 하나를 소개할까 합니다.

장애물 발견시 다음 라인 주행을 위한 시간을 구하는 식 :

[남은 주행거리 시간]
timetemp = 전체주행구간 - (현재시간-이전시간);
timetemp = maxTime - (millis() - timeVal);

[주행 시작위치시간 가정]
timeVal = 현재시간-주행거리 시간;

timeVal = millis()-timetemp;


주행 시작위치시간은 millis()함수로 현재 시간값을 기준으로 이전 남은주행거리 시간값을 빼주게 되면 이 남은주행거리 시간이 주행을 한걸로 간주하는 시간값으로 만들 수 있습니다. 설명이 좀 그런데 쉽게 숫자로 설명드리면 다음과 같습니다.

6칸을 6초로 6000이라고 하면 장애물 위치 millis()값이 5000이 될때 방향전환을 통해 짝수라인으로 이동하겠죠. 남은 주행거리 시간은 1000입니다. 이 1000 값을 millis()함수로 현재시간에서 1000을 빼주면 1000 동안 주행 한걸로 간주할 수 있게 됩니다. 그러면 1000만큼 주행한거니 남은 주행거리 시간은 5000이 됩니다. 어떤 의미인지 아시겠지요.

if(장애물감지){
  int timetemp = maxTime - (millis() - timeVal);
    주행방향전환패턴;
  timeVal = millis()-timetemp;
}

이렇게 해서 timeVal값은 이전시간변수이지만 위 식에 의해서 이전 남은 주행거리 시간값을 빼줌으로써 현재라인에서는 그 빼준 시간값만큼 주행한 걸로 간주하게 됩니다.

4. 종합 코딩


maxTime =>주행 구간거리
timeVal  => 이전시간

if(장애물감지){
  int timetemp = maxTime - (millis() - timeVal);
    주행방향전환패턴;
  timeVal = millis()-timetemp;
}
if(millis() - timeVal >= maxTime){   
    주행방햔전환패턴;
    timeVal = millis();
}

대충 위와 같은 과정을 거치게 됩니다. 전방에 장애물을 감지해야 하기 때문에 초음파센서로 장애물 감지하게 되면 다음과 같이 코딩을 완성할 수 있게 됩니다.

void loop() {
  delay(50);
  int distance = sonar.ping_cm();

  //장애물 감지
  if(distance>0 && distance<10){
     int timetemp = maxTime - (millis() - timeVal);
     movePattern();
     timeVal = millis()-timetemp;
  }
  else if(millis() - timeVal > maxTime){
    movePattern();
    timeVal = millis();
  }
}

전체소스를 종합해보면,

#include <AFMotor.h>
#include <NewPing.h>

AF_DCMotor motor1(3);
AF_DCMotor motor2(4);

const int TRIG = A4;
const int ECHO = A5;
const int MAX_DISTANCE = 100;
NewPing sonar(TRIG, ECHO, MAX_DISTANCE);

int speed = 200;
int maxTime = 5000; //주행거리
boolean state = false; //주행방향전환
unsigned long timeVal = 0; //이전시간값

void setup() {
//  Serial.begin(9600);
  motor1.setSpeed(speed);
  motor2.setSpeed(speed);
  motor1.run(RELEASE);
  motor2.run(RELEASE);
  delay(maxTime);

  //Start
  motor1.run(FORWARD);
  motor2.run(FORWARD);
}

void loop() {
  delay(50);
  int distance = sonar.ping_cm();

  //장애물 감지
  if(distance>0 && distance<10){
     int timetemp = maxTime - (millis() - timeVal);
     movePattern();
     timeVal = millis()-timetemp;
  }
  else if(millis() - timeVal > maxTime){
    movePattern();
    timeVal = millis();
  }
}

void movePattern(){
     state=!state; //방향전환상태값

     if(state==true){
       motor1.run(FORWARD);
       motor2.run(BACKWARD);
       delay(90도우회전시간);
       motor1.run(RELEASE);
       motor2.run(RELEASE);
       delay(100);
       motor1.run(FORWARD);
       motor2.run(FORWARD);
       delay(다음라인이동시간);
       motor1.run(RELEASE);
       motor2.run(RELEASE);
       delay(100);
       motor1.run(FORWARD);
       motor2.run(BACKWARD);
       delay(90도우회전시간);
     }
     else{
       motor1.run(BACKWARD);
       motor2.run(FORWARD);
       delay(90도좌회전시간);
       motor1.run(RELEASE);
       motor2.run(RELEASE);
       delay(100);
       motor1.run(FORWARD);
       motor2.run(FORWARD);
       delay(다음라인이동시간);
       motor1.run(RELEASE);
       motor2.run(RELEASE);
       delay(100);
       motor1.run(BACKWARD);
       motor2.run(FORWARD);
       delay(90도좌회전시간);
     }            
     motor1.run(RELEASE);
     motor2.run(RELEASE);
     delay(100);
     motor1.run(FORWARD);
     motor2.run(FORWARD);
} 

위 소스는 최근 아두이노 RC카 소스를 기반으로 오늘 코딩만 삽입하여 수정한 소스입니다. 코딩상으로는 이렇게 설계할 수 있습니다. 하지만 안타깝게는 정상적인 주행 결과를 얻을 수 없습니다. post에서 실패한 사례입니다. 즉흥적으로 상상하고 그걸 바로 코딩화하고 바로 post에 옮기니깐 실패할때는 난감해지네요. 계속 회전각과 일직선주행에 대하 반복 실험을 해서 정확한 각도 시간과 일직선문제를 해결해야 하는데 시간적 여건이 안되어서 이건 그냥 포기할 까 합니다. 여러분들이 시간이 남는 분이 있으시면 이 소스를 기반으로 한번 시간값을 반복 실험을 통해서 각도와 일직선주행 문제를 해결해 보세요. 

우선 단순하게 생각해서 좌/우회전각도시간값을 500으로 하고 다음라인이동시간을 500정도로 잡고 주행을 시도했는데 회전각도와 양쪽 DC기어모터의 순간 속도 차이로 인해 일직선 주행과 90도 회전이 되지 않았습니다. 근사각도로 변경은 되었지만 사실상 오차각도만큼의 주행라인이 크게 변화되는 현상이 발생했더군요. 시간으로 DC기어모터의 각도를 제어해야 한다는게 만만치 않는 부분이네요. 처음부터 stepper Motor 두개를 달았다면 이문제는 쉽게 해결되었을텐데 말이죠

마무리


코딩상으로는 괜찮았는데 실제 주행에서는 DC기어모터의 각도제어랑 두 DC기어모터 직선주행 속도를 일치하니 않는 한계에서 정확한 지그재그 주행을 이루지 못했네요. 안타깝게 실패한 실험입니다.
하지만, 실패한 주행이 되었지만 지그재그 주행패턴 코딩 과정은 생각하는 만큼 표현은 되었네요. 오늘 post은 코딩과정만 보시기 바랍니다.

오늘의 핵심은 이 식입니다.

  //장애물 감지
  if(distance>0 && distance<10){
     int timetemp = maxTime - (millis() - timeVal);
     movePattern(); //다음라인방향전화패턴(실패함)
     timeVal = millis()-timetemp;
  }
  else if(millis() - timeVal > maxTime){
     movePattern(); //다음라인방향전환패턴(실패함)
     timeVal = millis();
  } 

movePattern() 주행패턴이 정교하지 못해서 주행은 실패했지만 위 식의 동작은 문제가 없습니다. 위 식의 내용은 주행중 초음파센서의 장애물이 감지되면 현재 주행라인에서 다음 주행라인으로 넘어가게 되고 장애물이 감지되지 않았다가 정상 주행하다가 maxTime(전체이동시간)만큼 주행하다가 다음 주행라인으로 넘어가게 된다는 코딩입니다.

실제 아두이노 RC카 주행이 실패했지만 그래도 제가 만든 이 식이 너무 아까워서 post를 하게 되었네요.

아두이노 RC카를 애완동물의 행동을 보고 그 행동의 패턴을 코딩으로 만들기 했는데 각도와 직선 주행에서 사실 결함이 많이 발생하네요. 나중에 기회가 되면 stepper Motor 두개로 좀 더 정교하게 제어를 해 봐야겠네요. 다양한 몇가지 패턴을 계속 post를 할까 했는데 코딩으로는 몇개 만들었는데 오늘 실제 주행에서 회전각과 일직선 주행에서 오차가 계속 영향을 줄 것 같아서 RC카 post는 이정도로 우선 마무리 할까 합니다. stepper Motor를 나중에 두개 구매해서 한번 도전해 봐야겠네요. 아직은 구매할 마음이 없지만요.

댓글()