지그재그 주행 패턴 아두이노 RC카 응용 (아두이노)

in #kr-arduino7 years ago

지그재그 주행 패턴 아두이노 RC카 응용 (아두이노)



어제 "너도인간이니" 드라마에서 로봇청소기와 대화하는 주인공의 모습을 보면서 로봇청소기에 관심이 가서 다른 주행 패턴을 만들다거 드라마 보면서 급하게 변경했네요. 로봇청소기의 주행 패턴의 핵심은 지그재그 주행인데 그 지그재그 주행 패턴에 대해서 코딩적 관점으로 접근해 보았습니다. 사실 코딩적으로는 지그재그 주행을 만들어 냈지만 실제로는 DC기어모터의 회전 시간값을 정교하게 만들지 못해서 일직선주행의 오차각이 크고 90도 방향전환 회전도 delay()함수의 시간값으로만 제어 하다 보니간 방향전환 회전각도의 오차가 생기면 일직선주행의 방향도 크게 벌어져서 실제로 원하는 결과를 얻지 못했네요. stepper Motor로 제어 했다면 아마 정확한 각도와 일직선 주행을 만들어 냈을 텐데 아쉽게 실제 주행에서는 실패했지만 그래도, 코딩상에서의 지그재그 주행 원리를 없던 걸로 하기에는 아까워서 실패한 실험이지만 그래도 오늘의 주제로 결정했습니다. 이제부터서 지그재그 주행 패턴 동작에 대해 살펴볼까요.

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. 지그재그 주행 패턴 만들기


void 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. 지그재그 주행


주행 6칸이 6초이고 6000의 시간값이라고 가정하면 다음 과 같습니다.
timeVal 변수는 이전 시간값을 저장합니다. 여기서, 시작 timeVal=0일때 millis()값이 6000이상 되면 주행 방향을 전환하고 timeVal=6000이 되겠죠. 이렇게 6초단위로 방향전환이 됩니다.

if(millis() - timeVal >= maxTime){   
    주행방향전환패턴;
    timeVal = millis();
}

이렇게 하면 6초 단위로 주행방향을 전환을 수행한다.

3. 지그재그 주행시 장애물 발견 시 처리


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

아래 그림과 같이 상황이 주어질 경우를 살펴보겠습니다. 정해진 구간이 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를 나중에 두개 구매해서 한번 도전해 봐야겠네요. 아직은 구매할 마음이 없지만요.

Sort:  

호호. 이번에는 앞으로 가는 길이 생겼네요. 와 점점 신기해집니다. ㅎㅎ

코딩으로 움직임을 제어하는게 재밌어요. 그런데 정교한 제어가 힘드니 코딩과 하드웨어의 벽 이 생겨서 좀 답답한 부분이 있긴 하지만요.

음. 하긴 저도. 아까 댓글에 뭔가 좀 답답하시다고 쓰신 것 같은데. 저도 한 번 알아보겠습니다. ㅎㅎㅎ 뭔가 또 길이 있을지도 모르죠.

문제 해결책은 아는데 사실 그 값을 만들기 위해서는 계속 시뮬레이터를 반복해야 해서 좀 그게 답답하네요. DC기어모터로 시간값으로 각도를 정교하게 제어하기는 힘들어요. 간단한 주행은 근사시간값으로 만들어 낼 수 있는데 지그재그로 정확하게 제어하는데에는 약간 한계가 있는 것 같아요.
DC기어모터중에 비싼거 한바퀴 돌때마다 바퀴숫자를 카운터 할 수 있는 모터를 사용하면 바퀴 회전수로 제어를 할 수 있어요. 아니면 아예 저부분을 stepper Motor로 바꾸면 정교한 회전각도 제어가 가능해서 주행코딩이 쉽게 해결되긴 해요.
그외 여러방법이 있긴 하는데 해답은 알고 있는데 생각난것을 바로 결과로 뚝딱 안나오니깐 그 부분이 답답하네요.

아. 맞아요. 저도 맨날 그런걸 느껴요. ㅎㅎ. 와 진짜 공감되네요. ㅎㅎㅎ.

아 안타갑네요. 계획대로 움직이는 동영상을 기대했었는데... DC MOTOR로는 속도 제어가 어려워 coding 대로 안움직이는 것 같습니다.

지그 재그로 움직이긴 하는데 시간으로 각도제어하다 보니깐 각도가 약간씩 오차각이 나와서 나중에 지그재그 방향이 계속 바뀌어버리네요. 싼 모터의 한계인 것 같아요.

Loading...

팔로우 및 보팅도 하고갑니다!!
앞으로 자주 찾아뵙겠습니다.
정말 갑작스럽게 질문으로 찾아뵙게되어서 죄송합니다.

만나서 반갑습니다.

예 반갑습니다!! 앞으로 종종 찾아뵐게요!!

pairplay 가 kr-dev 컨텐츠를 응원합니다! :)

컨텐츠 응원에 감사합니다.