[아두이노] 지그재그 주행 패턴 아두이노 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를 나중에 두개 구매해서 한번 도전해 봐야겠네요. 아직은 구매할 마음이 없지만요.

댓글()

[아두이노] 도망가는 아두이노 RC카 응용

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

[아두이노] 도망가는 아두이노 RC카 응용



오늘은 지난시간에 마무리로 이야기 했던 내용 중 하나를 선택하여 한번 실제로 동작을 테스트 해보면 좋을 것 같아서 이렇게 도전해 봤네요. 상황은 초음파센서 아두이노 RC카에 정면에 다가가면 초음파 아두이노 RC카가 다가온 방향의 반대로 일정거리 동안 도망가도록 주행패턴을 만들어서 실험을 하겠습니다.


1. 초음파센서 아두이노 RC카 회로도


  • 준비물 : L293D Motor Shield, DC Motor 2개, Servo Motor 1개, 초음파센서 1개, 외부전원 2개, 아두이노우노
  • 내용 : A4(Trig), A5(Echo)로 초음파센서에 연결하고 Servo Motor는 왼쪽 상당애 Servo Pin(-,+,Sig)에 연결한다. DC Motor 2개는 M3, M4에 연결합니다.



지난시간의 회로도 입니다.

2. 코딩



도망다니는 아두이노 RC카를 상상하여 그 상상을 코딩화 하는 과정을 이야기 할까 합니다. 상상의 내용은 다음과 같습니다.

[도망가는 아두이노 RC카 상상하기]

  1. 전방에 다가오는 사람이 있는지 확인한다.
  2. 초음파센서로 인지거리 내 감지되면 감지된 반대방향으로 도망간다.
  3. 아두이노 RC카가 후면(엉덩이) 흔들기
  4. 다시 180도 회전하여 전방에 다가오는 사람이 있는지 확인한다.
  5. 이 과정을 계속 반복한다.

1) 전방에 다가오는 사람이 있는지 확인한다.


void loop() {
  //초음파센서 회전
  servo.write(angle);
  delay(50);
  
  int distance = sonar.ping_cm();

  //다가오는 사람 확인
  if(distance>0 && distance<10){
    도망가기;   
  }
  //회전 각도
  if (angle == 140) state = -10;    
  else if (angle == 40) state = 10; 
  angle += state;
}

2) 초음파센서로 인지거리 내 감지되면 감지된 반대방향으로 도망간다.


  //다가오는 사람 확인
  if(distance>0 && distance<10){
    movePattern(); //도망가기
  }

여기서, distance의 거리가 10cm 미만이면 다가 온 걸로 간주 하고 movePattern()함수로 반대방향으로 도망을 가 볼까요.

void movePattern(){
     int timeVal=0;
     motor1.run(BACKWARD);
     motor2.run(BACKWARD);
     delay(500);   
    
    motor1.run(FORWARD);
    motor2.run(BACKWARD);
    timeVal=(180-(angle-90))*5.56;
    delay(timeVal);   
    
    motor1.run(FORWARD);
    motor2.run(FORWARD);
    delay(2000);                
}

정면에 다가온 각도 방향의 정 반대로 도망을 갑니다. timeVal은 1초당 1000일때 1도 당 5의 값으로 가정하여 계산하면 해당 방향을 기준으로 180도 회전을 할려면 다음과 같은 식으로 표현할 수 있습니다.


timeVal=(180-(angle-90))*5;

Servo Motor의 회전(angle)각 방향이 100도면 RC카의 전방 진행 방향이 90도 임으로 왼쪽 10도 각이면 위 식으로 계산을 하면

각도 = 180-(100-90) => 170

만약에, angle이 70도이면

각도 = 180-(70-90) => 200

그리고, 이 각도의 회전각 시간을 구하면 timeVal 변수에 5값을 곱하시면 해당 각도에 대한 아두이노 RC카의 회전각이 나오게 됩니다.

뒤로 회전이 된 후 2초동안 도망가게 됩니다.

motor1.run(FORWARD);
motor2.run(FORWARD);
delay(2000);  

이렇게 하면 계속 후진하는 동작이라서 뭔가 전진하거나 자신의 자리로 되돌아오는 그런 명령들이 필요 하다고 생각되실 꺼에요. 오늘 실험은 하나의 패턴 도망자 아두이노 RC카 이기 때문에 하나의 패턴에만 집중하기 위해서 코딩하고 싶지만 생략했습니다.

다른방식으로 표현하면

전방 기준으로 왼쪽은 오른쪽으로 회전하고 오른쪽은 왼쪽으로 회전하고 싶다면 코딩이 좀 길어집니다.

void movePattern(){
     int timeVal=0;
     motor1.run(BACKWARD);
     motor2.run(BACKWARD);
     delay(500);   
    if(angle>=90){
      motor1.run(FORWARD);
      motor2.run(BACKWARD);
      timeVal=(180-(angle-90))*5;
      delay(timeVal);   
    }
    else{
      motor1.run(BACKWARD);
      motor2.run(FORWARD);
      timeVal=(180+(angle-90))*5;
      delay(timeVal);         
    }
    motor1.run(FORWARD);
    motor2.run(FORWARD);
    delay(2000);                

if문으로 해서 90도 기준으로 왼쪽인지 오른쪽인지 나누게 됩니다. angle>=90 은 90도 이상이면 우회전하게 되고 90도 미만이면 좌회전을 하게됩니다. 이렇게 하면 timeVal 값을 구하는 식은 한개가 달라집니다.

90도 이상 => timeVal=(180-(angle-90))*5;
90도 미만 => timeVal=(180+(angle-90))*5;

보시면 90도 이상이면 180도에서 빼주지만 90도 미만은 180도에서 더해주면 됩니다. 식이 잘 이해가 안되면 angle(각) 값을 숫자로 대입해 보세요. 그러면 이해가 쉬울 듯 합니다.

3) 아두이노 RC카가 후면(엉덩이) 흔들기


도망 간 다음 아두이노 RC카가 후면을 엉덩이 흔드는 것처럼 흔드는 동작을 추가 해 볼까요.

void movePattern(){
  생략...
    
    for(int i=0;i<3;i++){
     motor1.run(FORWARD);
     motor2.run(BACKWARD);
     delay(300);
     motor1.run(BACKWARD);
     motor2.run(FORWARD);  
     delay(300);
    }
}

우로 0.3초 회전 한 뒤에 좌로 0.3초 회전한다. 이 과정을 3번 반복하면 엉덩이를 흔드는 동작처럼 표현 할 수 있겠죠. 만약에 꼬리 같은 걸 후면에 붙여 놓으면 좀 더 그럴싸 해 지겠죠.

4) 다시 180도 회전하여 전방에 다가오는 사람이 있는지 확인한다.


void movePattern(){
  생략...
    
  motor1.run(BACKWARD);
  motor2.run(FORWARD);
  timeVal=180*5;
  delay(timeVal); 
  motor1.run(RELEASE);
  motor2.run(RELEASE);
  angle=90;
  servo.write(angle);
  delay(100);   
}

좌회전으로 180도 회전하여 다시 아까 다가 온 방향으로 아두이노 RC카는 향하게 회전 시킨 후 아두이노 RC카는 정지 상태가 됩니다. 초음파센서는 다시 정면 90도 방향으로 향하게 합니다. 이렇게 해서 도망갔다가 다시 주변을 탐색을 시작하기 전까지의 동작이 끝나게 됩니다. loop()함수는 끝나고 다시 재호출 되어서 loop()함수는 주변에 사람이 있는지 확인하게 됩니다.

5) 이 과정을 계속 반복한다.


위 과정을 loop()함수에서 계속 반복하게 됩니다.

위 과정을 종합하여 코딩하면,

[기본소스]

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

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

Servo servo;
const int servoPin = 10;
const int TRIG = A4;
const int ECHO = A5;
const int MAX_DISTANCE = 100;
NewPing sonar(TRIG, ECHO, MAX_DISTANCE);

int speed=200;
int state = 10;
int angle = 90;

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

  servo.attach(servoPin);
  servo.write(angle);
  delay(1000);
}

void loop() {
  //초음파센서 회전
  servo.write(angle);
  delay(50);
  
  int distance = sonar.ping_cm();
  
  //장애물 감지
  if(distance>0 && distance<10){  
     movePattern(); 
  }   
  //회전 각도
  if (angle == 140) state = -10;    
  else if (angle == 40) state = 10; 
  angle += state;
}

void movePattern(){
    int timeVal=0;
    motor1.run(BACKWARD);
    motor2.run(BACKWARD);
    delay(500);   
    
    //도망
    if(angle>=90){
      motor1.run(FORWARD);
      motor2.run(BACKWARD);
      timeVal=(180-(angle-90))*5;
      delay(timeVal);   
    }
    else{
      motor1.run(BACKWARD);
      motor2.run(FORWARD);
      timeVal=(180+(angle-90))*5;
      delay(timeVal);         
    }
    motor1.run(FORWARD);
    motor2.run(FORWARD);
    delay(2000);  
    
    //후면 흔들기
    for(int i=0;i<3;i++){
      motor1.run(FORWARD);
      motor2.run(BACKWARD);
      delay(300);
      motor1.run(BACKWARD);
      motor2.run(FORWARD);  
      delay(300);
  }
  
    //원위치 전방 보기
    motor1.run(BACKWARD);
    motor2.run(FORWARD);
    timeVal=180*5;
    delay(timeVal); 
    motor1.run(RELEASE);
    motor2.run(RELEASE);
    
    angle=90;
    servo.write(angle);
    delay(100);     
}

동작 하나하나 패턴을 추가해서 표현하니깐 movePattern()함수 내 동작 패턴이 꽤 길어 졌네요. 아두이노 RC카가 어떻게 움직이길 바라면 그 움직임을 하나씩 원하는 행동을 하도록 상상하고 그 상상을 이렇게 코딩화 하면 됩니다.

위 소스는 그냥 즉흥적으로 간단히 만든 거라서 사실 지져분한 코딩입니다. 맘에 들지 않는 코딩이라는 뜻이죠. 뭔가 축약된 코딩을 좋아하는데 이렇게 길게 늘려서 일일히 코딩하는 것을 싫어하기 때문에 맘에 들지는 않지만 대충 이런 느낌으로 코딩을 한다는 의미만 전달하기 위해서 그냥 이 소스로 마무리 합니다.

여러분들은 한번 다른 방식으로 도망자 아두이노 RC카를 만들어 보세요.

추가사항


위의 코딩만으로는 사실 문제점이 많습니다. 무조건 전방에 다가오는 물체에 대해서 도망만 가기 때문에 계속 후진하는 느낌의 코딩입니다. 또한 뒤로 도망을 갈때 보면 뒤에 장애물에 대한 감지가 빠져 있습니다. 이 부분을 추가할려면 loop()함수의 물체 감지 부분을 사용자 정의함수로 빼서 뒤로 도망가는 로직에 물체 감지 함수부분을 호출하는 식으로 코딩을 변경해야 합니다. 그리고 도망가는 2초간의 시간은 delay(2000) 하면 절대 안되고 delay()함수를 사용하지 않는 post [아두이노] delay()함수 안쓰고 delay 제어하기의 원리를 이용해서 2초간 도망가는 주행을 하는 동안 장애물을 감지하는 코딩으로 수정해야 합니다. 그리고, 그냥 도망으로만 끝내지 않고 다시 도망을 갔다가 물체 감지된 방향으로 조금직 다시 직립주행을 하면서 물체와의 거리를 측정하면서 자신이 있던 자리로 되돌아가게 코딩을 하면 좀 더 재밌는 코딩으로 변경 됩니다.

지금 열거한 내용만 코딩을 하더라도 수정해야 할 부분과 추가해야할 코딩이 늘어납니다. 그러면 오늘 전달하고자 하는 도망자 아두이노 RC카의 원리가 제대로 전달되지 않기 때문에 코딩에서는 실제 추가하지 않겠습니다. 이부분은 여러분들의 상상코딩에 맡기겠습니다.

3. 결과


코딩은 그럭저럭 되었는데 결과는 썩 마음에 들지 않네요. 원하는 결과는 얻지 못했습니다. 1초를 1000으로 해서 180도를 1초 회전으로 첨에 잡았는데 회전하니깐 180도 이상의 각도로 회전이 일어나서 댜략 눈 짐작으로 900 정도로 180도로 간주하여 회전을 시켰지만 그래도 회전각은 원한 각도로 정교하게 회전을 되지 않았네요. 2~3번의 주행 테스트를 하고 post를 한 거라 정교한 결과를 얻지 못해서 아쉽네요.


위 영상을 보시면 회전각 오차가 크고 도망가는 주행이 직립주행이 되지 않았네요. 건전지가 추가되고 무게중심이 보조 바뀌쪽으로 좀 더 가서 그런지 보조바퀴의 방향각에 의해서 회전이나 직립주행의 오차가 심하게 발생했네요. 아두이노 RC카의 몸체에 대해서 교정을해야하고 수학적으로 DC기어모터의 속도와 시간값을 통해 회전각을 정교하게 해야하는데 단순하게 눈짐작으로 실험을 한계 이런 오차결과를 만들어 내고 말았네요.

결과는 마음에 들지 않지만 코딩은 대충 어떤 느낌으로 설계하는지만 이해하는 시간이 되었으면 합니다. 기본적으로 이렇게 출발하고 조금씩 문제점에 대해서 해결 코딩을 해가면서 좀 더 정교한 프로그램을 만들어가는 것이죠.

마무리


원래 상상의 의도는 도망자 아두이노 RC카를 만들고자 했는데 느낌이 꼭 애완동물이 주인을 피해 도망갔다가 꼬리를 흔들고 다시 주인이 있는 방향으로 다시 바라보는 느낌의 코딩이 되어버렸네요.

이 느낌으로 좀 더 추가해 볼까 하는 생각도 드는데 아직 결정은 안했습니다. 따라다니는 아두이노 RC카를 다음편에 이야기 할까 그냥 여기서 끝낼까 고민을 좀 해봐야 겠네요.

코딩은 아직 안했는데 상상을 잠깐 해보고 결정해야 겟네요.


댓글()

[아두이노] 아두이노 RC카 자율주행(장애물피하기)

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

[아두이노] 아두이노 RC카 자율주행(장애물피하기)



오늘은 몇일동안 포스트한 장애물을 피하는 자율주행 테스트 해보는 시간으로 내용을 채우고자 합니다. 좀 더 정교한 자율주행 코딩을 할까도 생각 했지만 처음은 단순한 장애물을 피하는 자율주행을 보여드리는 것이 좋을 것 같아서 종합 코딩은 간단하게 표현하여 자율주행의 의미를 전달하고자 합니다.


우선 초음파 아두이노 RC카를 사진으로만 보면 정확히 구조를 이해할 수 없으니 간단히 fritzing으로 회로도를 보여드리고 간단히 장애물피하기 자율주행 코딩을 보여드리겠습니다.

1. 초음파센서 아두이노 RC카 회로도


  • 준비물 : L293D Motor Shield, DC Motor 2개, Servo Motor 1개, 초음파센서 1개, 외부전원 2개, 아두이노우노
  • 내용 : A4(Trig), A5(Echo)로 초음파센서에 연결하고 Servo Motor는 왼쪽 상당애 Servo Pin(-,+,Sig)에 연결한다. DC Motor 2개는 M3, M4에 연결합니다.



위 사진의 아두이노 RC카의 회로도는 아래 그림과 같이 구성되어 있습니다.


위 그림에서 외부 전원을 두개로 분리해서 공급합니다. 참고포 점퍼핀 덮개를 빼주면 아두이노와 Motor Shield의 전원을 나눌 수 있습니다. 그림이 좀 복잡해 보일 수 있지만 내용에 자세히 핀 연결 설명이 되어 있으니깐 해당 위치에 부품의 선을 연결해주시면 됩니다.

2. 코딩



이전 post에서 장애물을 감지하는 방법과 장애물을 피하는 패턴을 살펴 보았습니다. 여러개를 설명했는데 그중에 각각 한개씩 간단한 표현으로 코딩을 하겠습니다.

[기본소스]

#include <AFMotor.h>

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

void setup() {
  motor1.setSpeed(200);
  motor2.setSpeed(200);
  motor1.run(RELEASE);
  motor2.run(RELEASE);
}

void loop() {  

  초음파센서 장애물 감지;

  motor1.run(FORWARD); //전진
  motor2.run(FORWARD);
  delay(1000);
}

1) 서보모터+초음파센서 장애물 감지


[코딩 순서]

  • 40~140도 사시를 10도씩 회전하면서 초음파센서로 장애물를 측정한다.
  • 장애물 감지 거리를 15cm 미만일 때 감지로 간주한다.
void loop() {
  //초음파센서 회전
  servo.write(angle);
  delay(50);
  
  int distance = sonar.ping_cm();

  //장애물 감지
  if(distance>0 && distance<15){
     movePattern(); 
     //Serial.println(distance);
     motor1.run(FORWARD);
     motor2.run(FORWARD);  
  }   
  //회전 각도
  if (angle == 140) state = -10;    
  else if (angle == 40) state = 10; 
  angle += state;
}

2) 장애물 피하기 패턴


[코딩 순서]

  • 장애물 감지시 0.5초 동안 후진한다.
  • 전방 90도를 기준으로 90이상이면 우회전 90미만 좌회전 시킨다.
  • 장애물 피하기 회전이 끝나면 다시 전진 주행한다.
  • 초음파센서는 방향은 90도로 위치시킨다. (90도 기준으로 다시 10도씩 변화를 시키기 위해서)
void movePattern(){
     motor1.run(BACKWARD);
     motor2.run(BACKWARD);
     delay(500);   
    if(angle>=90){
      motor1.run(FORWARD);
      motor2.run(BACKWARD);
      delay(500);   
    }
    else{
      motor1.run(BACKWARD);
      motor2.run(FORWARD);
      delay(500);   
    }
    angle=90;
    servo.write(angle);
    delay(100);
}

3) 기본소스+장애물감지소스+장애물피하기 패턴소스


위 세가지 소스를 합치면 다음과 같습니다. 참고로 몇가지 다듬어서 코딩을 했습니다.

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

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

Servo servo;
const int servoPin = 10;
const int TRIG = A4;
const int ECHO = A5;
const int MAX_DISTANCE = 100;
NewPing sonar(TRIG, ECHO, MAX_DISTANCE);

int speed=200;
int state = 10;
int angle = 90;

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

  servo.attach(servoPin);
  servo.write(angle);
  delay(1000);

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

void loop() {
  //초음파센서 회전
  servo.write(angle);
  delay(50);
  
  int distance = sonar.ping_cm();

  //장애물 감지
  if(distance>0 && distance<15){
     movePattern(); 
     //Serial.println(distance);
     motor1.run(FORWARD);
     motor2.run(FORWARD);  
  }   
  //회전 각도
  if (angle == 140) state = -10;    
  else if (angle == 40) state = 10; 
  angle += state;
}
void movePattern(){
     motor1.run(BACKWARD);
     motor2.run(BACKWARD);
     delay(500);   
    if(angle>=90){
      motor1.run(FORWARD);
      motor2.run(BACKWARD);
      delay(500);   
    }
    else{
      motor1.run(BACKWARD);
      motor2.run(FORWARD);
      delay(500);   
    }
    angle=90;
    servo.write(angle);
    delay(100);
}

setup()함수에서 초기화 작업을 하는데 젤 먼저 DC기어모터의 속도를 200으로 세팅하고 각 Motor를 RELEASE(해제 or 정지)상태로 둔다. 그리고 나서 주행전 Servo Motor의 초기 위치는 90도 회전 시켜 초음파센서가 전방90도를 바라보게 한다. 그 다음 1초동안 준비 상태로 있다가 FORWARD(전진)명령으로 아두이노 RC카가 출발하게 됩니다.

loop()함수는 angle(각도)에 위치로 회전을 0.05초 간격으로 합니다. 회전이 될 때 초음파센서(sonar)의 거리를 측정하여 distance에 저장합니다. 이 값이 0보다 크거나 15cm보다 작은 값일 때 장애물을 감지한 걸로 감주합니다. 0의 값은 MAX_DISTANCE의 제한최대거리 값을 넘게 되면 0으로 반환되기 때문에 0의 값이 주행 중에 일정 간격으로 발생하게 됩니다. 그렇기 때문에 0보다 크거나 15cm보다 작은 값으로 if문으로 체크하는 조건식을 만든 것이죠. 이렇게 15cm 미만일 때 장애물 감지 판정을 내리게 되면 if문 이하 문장을 수행 하게 됩니다. 이때 장애물 피하기 패턴을 코딩하면 됩니다 movePattern()함수로 장애물을 피하는 동작을 코딩해 놨는데 이 방식으로 피하고 나면 다시 motor1, motor2은 FORWARD(전진)으로 게속 자율주행을 하게 됩니다.

여기서, movePattern()함수를 살펴보면 먼저 0.5초 동안 후진을 하고 후진 한 후에 Servo Motor가 회전한 현재 각(angle)의 값이 90보다 큰거 아니면 작은 가로 나누게 되는데 이때 90도보다 크면 좌측에 장애물을 감지했기에 우회전 명령문을 0.5초 동안 90도 우회전을 하게 되고 90도보다 작으면 우측에 장애물을 감지했기에 좌회전 명령문으로 0.5초 동안 90도 좌회전을 하게 됩니다. 그리고 다시 전진 주행을 하기 전에 초음파센서의 방향 위치를 초기화 상태로 전방 90도 위치로 회전시켜놓습니다.

대충 전체의 소스에 대해 설명을 했는데 이해하셨는지 모르겠네요.

만약, 다른 방식으로 접근하고 싶다면 장애물 감지 코딩 부분을 다른 감지 주행패턴을 선택해서 붙여 넣으시면 되고요. 장애물 피하기 패턴도 movePattern()함수 내부의 코딩을 원하는 패턴으로 만들어서 넣으시면 됩니다.

3. 결과


그냥 전방의 장애물을 손으로 표현해서 손으로 막을 때 이 손을 아두이노 RC카가 장애물로 감지하고 초음파센서가 감지한 각도에 따라 좌/우회전을 하는 짧게 촬영한 영상입니다.


이렇게 간단하게 장애물 피하기로 자율주행을 출발합니다.

위 코딩을 좀 더 정교하게 하려면 다음과 같이 해야 합니다.

void loop(){
  //장애물 감지
  if(distance>0 && distance<15){
     movePattern(); 
  }  
}   
void movePattern(){
     motor1.run(BACKWARD);
     motor2.run(BACKWARD);
     delay(500);   
    if(angle>=90){
      motor1.run(FORWARD);
      motor2.run(BACKWARD);
      delay(회전각도시간값);   
    }
    else{
      motor1.run(BACKWARD);
      motor2.run(FORWARD);
      delay(회전각도시간값);   
    }
    motor1.run(FORWARD);
    motor2.run(FORWARD);    
        
    angle=90;
    servo.write(angle);
    delay(100);
}

여기서, 회전각도시간값은 여러분들이 가지고 있는 아두이노 RC카의 바퀴힐과 속도를 기준으로 1초(1000)에서 delay값 100을 기준으로 몇도 회전되는지 체크했다가 정확하게 회전각도시간값을 넣어주세요. 그리고 회전을 시킨 후 바로 전진 주행으로 바꿔 주시면 좀 더 정확하게 회전 후 전진 주행이 됩니다. 사실 촬영할 때는 loop()함수에다가 넣어서 간단히 movePattern()함수에서는 회전 주행만 표현했는데 마지막 초음파센서의 90 회전 초기화에서 0.1초 동안의 회전이 더 일어나는 단점을 가지게 되더군요. 그러면, 회전각도시간값으로 정확히 회전을 시키더라도 초기화 0.1초동안 회전이 더 발생하는 문제가 생길 수 있습니다. 단순하게 장애물 피하기 코딩에서는 0.1초 차이는 티도 안나기 때문에 무시하고 촬영을 했지만 post을 작성하면서 이 코딩부분을 수정해서 올릴까도 했지만 촬영은 이전 소스를 기반으로 주행 시켜서 그냥 약간 부족한 상태로 수정 없이 올리게 되었네요. 하지만 이렇게 추가로 이야기 하고 넘어가야 할 것 같아서 좀 더 내용을 채우게 되었네요.

motor1.run(FORWARD);
motor2.run(FORWARD);   

아무튼 이 명령이 어느 위치에 있느냐에 따라 결과는 달라지게 됩니다. 단순한 코딩은 무시 할 정도로 표현해도 되지만 정교한 코딩에서는 명령문의 위치에 따라 딜레이 0.1초 차이도 회전 오류각을 발생할 수 있기 때문에 코딩을 완료되더라도 완료된 코딩의 흐름을 머리속에서 상상하면서 문제가 없는지 체크를 꼭 하셔야 합니다.

마무리


위 코딩을 보면 그렇게 어려운 코딩이 아닙니다. 재밌는 것은 이 원리를 이해하시면 또다른 다양한 표현을 할 수 있습니다. 장애물을 피한다면 아두이노 RC카가 도망다니는 RC카로 표현 할 수 있습니다. 가령, 전방의 특정 각도의 장애물이 감지되면 그 장애물의 각도을 기준으로 180도 회전해서 뒤로 도망가는 RC카를 만들 수 있습니다. 인간과 RC카가 이 원리로 접근하게되면 인간이 아두이노 RC카를 추적하고 아두이노 RC카는 도망자가 됩니다.

아니면, 주행을 규칙적으로 지그재그로 표현을 한다면 지그재그 주행을 할 때 전방의 초음파센서가 장애물을 감지하면 진행 방향에 있는 장애물을 피하기 위해서 진행 라인의 주행을 포기하고 다음 라인의 주행을 이여간다면 로봇청소기와 같은 주행을 할 수 있게 됩니다.

또 다른 상상을 하면 아두이노 RC카를 랜덤 주행을 일정 범위안에서 수행되게 해놓고 있다가 초음파센서의 장애물 감지 범위에 인간이 다가갔을 경우 일정 거리까지 아두이노 RC카가 따라오도록 주행패턴을 만들게 되면 어떤 느낌의 아두니오 RC카가 될까요. 바로 애완동물 아두이노 RC카로 표현이 가능해 집니다.

간단히 장애물 감지와 장애물 피하는 동작의 원리를 곰곰히 생각해보니깐 방금 이야기 했던 상상들이 떠오르더군요. 위에서 설명한 주제들의 원리는 전부 동일한 원리 입니다. 장애물 감지와 장애물 피하기에서 장애물 피하기를 역발상으로 장애물 따라오기로 생각의 관점을 바꾸면 이렇게 또 다른 결과가 나올 수 있습니다. 재밌는 아두이노 RC카를 만들 수 있겠죠.

여러분들도 한번 이 원리를 기반으로 나는 어떤 상상을 할 수 있을지 상상의 나래를 펼쳐보셨으면 합니다.


댓글()

[아두이노] 아두이노 RC카 초음파센서로 장애물 피하기 패턴

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

[아두이노] 아두이노 RC카 초음파센서로 장애물 피하기 패턴



지난 시간에는 초음파센서로 장애물을 감지하는 방법들에 대해서 간단히 살펴 보았습니다. 오늘은 감지 했을 때 피하는 방법에 대해 한번 이야기 하고자 합니다. 아두이노 RC카는 주행 중 전방에 장애물이 감지하면 후진을 할 것인지 좌회전을 할 것인지 아니면 우회전을 할 것인지를 결정해야 합니다. 여러분들은 어떤 선택을 하실 건지 한번 머리속에서 상상을 해보세요. 현재 여러분들이 서 있는 위치에서 앞으로 걷다가 앞에 벽이 나타난다면 나는 어느 방향으로 벽을 피해 이동할지를 상상하며 한번 걸어보세요. 벽이 나타나고 벽을 피해서 방향전환하고 나서 다시 걷는 상상을 하거나 아니면 실제 걸어보시면서 걷는 과정을 기록해 주세요. 이 행동의 기록이 아두이노 RC카의 주행 패턴이 됩니다. 아두이노 RC카는 이 주행 패턴을 통해서 자율주행을 하게 됩니다. 어떤 느낌인지 아시겠지요.


지금부터 장애물 감지 센서인 초음파센서를 지난 시간에 배운 방식 중에 하나를 선택해서 실험해야 하는데 위 사진을 보면 서보모터로 회전되는 초음파센서로 전방에 하나 배치했기 때문에 이 방식을 통해 장애물을 감지 하고 장애물을 피하는 패턴들을 만들겠습니다.

1. 초음파센서로 장애물 감지 시 피하기 패턴



위의 선행 학습을 꼭 하시고 오셔야 합니다. 그래야 아래 내용을 쉽게 이해 하실 수 있습니다.

1) 전방 장애물 피하는 기본 방향 패턴


지난 시간에 몇가지 초음파센서로 장애물 감지에 대해서 살펴 보았습니다. 장애물 감지 중 첫번째 회전 초음파센서로 장애물 감지를 기준으로 한번 패턴을 만들어 보도록 할까요.

주행 중 회전 초음파센서로 장애물을 감지 상황은 다음과 같습니다.


주행 중에 전방에 장애물이 감지하는 한다고 상상해 봅시다. 이 상황에서 여러분들은 어떤 주행 패턴을 만드시겠습니까?


좌회전 아니면 우회전 그것도 아니면 후진 중 어떤 주행패턴을 하실지 머리속에서 그려 보세요.

#include <AFMotor.h>

AF_DCMotor motor1(3); //왼쪽 모터
AF_DCMotor motor2(4); //오른쪽 모터

두개의 DC기어모터를 제어할 Motor가 있을 때

  • motor.run(FORWARD) : 전진
  • motor.run(BACKWARD) : 후진
  • motor.run(RELEASE) : 해제

모터의 기본 동작함수인데 이 함수를 통해서 장애물을 피하는 패턴을 만듭니다.

if(장애물 감지){
  장애물 피하기 패턴;
}

장애물 감지 시 제자리에서 좌/우 회전을 시킨 후 회전 된 방향으로 주행을 계속 진행하도록 주행패턴을 만들어 보도록 합시다. 단, 90도 회전은 0.5초동안 회전하고 180도 회전은 1초동안 회전이라고 가정하고 상상 코딩을 합니다. Motor의 속도에 따라 시간에 따른 회전각은 달라집니다. 개념을 잡기 위해서 0.5초은 90도 이고 1초가 180도라고 상상한 것이기 때문에 이대로 실제 코딩하시면 안됩니다. Motor 속도와 시간에 대한 회전각도를 수학적으로 계산하시거나 단순하게 속도에 따른 시간별 각도를 일일히 체크해서 각도를 가늠하시거나 둘 중 하나를 선택하셔서 나중에 실제 만들어 보세요.


[90도 우회전 후 전진]

motor1.run(FORWARD);
motor2.run(BACKWARD);
delay(500);   
motor1.run(FORWARD);
motor2.run(FORWARD);

[90도 좌회전 후 전진]

motor1.run(BACKWARD);
motor2.run((FORWARD););
delay(500);   
motor1.run(FORWARD);
motor2.run(FORWARD);

이렇게 하면, if문으로 장애물 감지되면 좌/우로 90도 회전 시킨 후 그 방향으로 전진하게 됩니다. 장애물 피하기 주행 패턴이 단순하죠.

다음으로, 뒤로 회전 한다면 좌/우로 회전 방향은 상관 없이 180도 회전을 시키면 됩니다.


[180도 뒤로 회전 후 전진] 우방향을 기본으로 잡았을 경우

motor1.run(FORWARD);
motor2.run(BACKWARD);
delay(1000);   
motor1.run(FORWARD);
motor2.run(FORWARD);

대충 이런 느낌이 됩니다. 1초가 정확히 180도 회전이 아닙니다. 가정한 것이고 DC기어모터 회전 속도에 따라서 회전각은 달라집니다. 가정하에서 코딩한 것이기 때문에 감안하시고 보세요.

이렇게 해서 간단히 장애물을 피해서 주행을 할 수 있게 되었습니다.

2) 전방 장애물을 피할 때 문제점


방금 세가지 피하는 방식에서 문제가 발생할 수 있습니다. 제자리에서 아래 그림처럼 DC기어모터가 회전하면 바퀴를 기준으로 몸체가 돌아가게 됩니다. 몸체가 돌아갈 때 장애물과의 충돌이 발생 할 수 있습니다.


제자리 좌/우회전을 시키거나 뒤로 180도 회전 시킬때 장애물 거리 감지가 짧을 경우 아래와 같은 상황이 발생 할 수 있습니다.

[우회전]


[180도 회전]



위 그림같은 상황 때문에 주행 중 진행 방향에 장애물이 감지 되었을 때 다음 장애물 피하기 동작 패턴을 만들려면 감지한 거리과 아두이노 RC카의 몸체의 크기를 고려해야 합니다. 그리고 피하는 행동을 할 때에도 주변 환경의 장애물과의 충돌 상황도 고려해야 합니다.

[해결 방법]

  • 첫번째, 장애물 감지 거리를 좀 더 길게 잡으면 해결 됩니다. 아두이노 RC카가 충분히 회전 할 수 있는 거리만큼을 장애물 감지 거리로 설정해 놓으면 장애물 감지 후 바로 회전을 하더라도 정상적으로 회전 할 수 있습니다.
  • 두번째, BACKWARD(후진)을 일정 거리만큼 시킨 뒤 회전을 시키면 됩니다. 하지만, 이 경우에는 또 다른 문제점으로 후진 후 회전시 아래 그림처럼 측면에 장애물이 있을 경우 충돌 가능성이 있습니다.

위 그림처럼 문제가 생기면 후면에 초음파센서를 부착하여 후진 시 뒷면에 장애물을 감지하면 해결 할 수 있겠죠. 그런데 이부분은 코딩화 하지 않겠습니다 계속 상황을 만들어 가면 이야기가 끝도 없기 때문에 의미만 전달하기 위해서 이정도로 마무리 합니다.

장애물을 피하는 주행 패턴을 만들 때 처음에 제가 설명한 세가지 주행 패턴을 기반으로 상상코딩을 한 뒤에 주행 패턴을 변화시키십시오. 만약에, 자신이 만든 주행패턴에 문제가 생기면 그 문제 해결을 위한 주행 패턴을 만들고 기존 소스에 덧붙여가며 코딩을 늘려가시면 문제를 충분히 해결 할 수 있게 됩니다. 처음은 단순한 주행패턴으로 머리속에서 정리하고 그 주행 패턴에서 하나씩 새로운 주행 패턴을 만들어가면 좀 더 정교한 장애물 피하기 패턴을 만들 어 가면 자율주행하는 재밌는 아두이노 RC카를 만들 수 있습니다.

2. 회전 초음파센서로 장애물 감지 후 피하기 패턴 - I



위 그림처럼 초음파센서는 전방에 일정 각도로 좌/우 회전을 하면서 각도마다 거리를 측정하게 됩니다. A, B, C로 세부분으로 나누었지만 만약, 40~140도 사이의 각도를 10도 간격으로 하면 11개의 각도의 장애물 감지 거리를 측정하게 됩니다. 11개의 각도가 나올 때 전방 중앙을 90도 각도로 하면 좌/우 5개의 각도로 장애물 감지 거리가 측정 할 수 있습니다.

이렇게 각도별로 장애물이 감지 되었을 때 어떻게 상상코딩을 할까요. 정중앙을 90도기준으로 장애물이 감지된 초음파센서 각도에서 반대 방향으로 회전시켜 장애물을 피하는 간단한 방법이 있습니다.

위 그림에서 B가 90도 기준으로 A은 140도 방향이고 C은 40도방향이라고 한다면 A방향은 좌측이고, C방향은 우측으로 나눌 수 있습니다. 이때, A방향의 장애물이 감지 되었을 때 우측으로 진로 C방향으로 진행하도록 합니다. C방향 우측에서 장애물이 감지되면 좌측으로 진로 C방향으로 진행하게 하면 간단한 피하기 패턴을 만들 수 있습니다.

[A방향 장애물 발견 시 90도 우회전 후 전진]

motor1.run(FORWARD);
motor2.run(BACKWARD);
delay(500);   
motor1.run(FORWARD);
motor2.run(FORWARD);

[B방향 장애물 발견 시 90도 좌회전 후 전진]

motor1.run(BACKWARD);
motor2.run((FORWARD););
delay(500);   
motor1.run(FORWARD);
motor2.run(FORWARD);

이렇게 진로 방향을 바꾼다면 장애물 감지는 아래와 같이 코딩을 할 수 있겠죠.

if(장애물거리<15){
  if(초음파각도>90){
      [A방향 장애물 발견 시 90도 우회전 후 전진];
  }
  else{
      [B방향 장애물 발견 시 90도 좌회전 후 전진];
  }
}

이렇게 단순하게 표현 할 수 있습니다.

추가로

전방, 좌/우측 방향의 장애물이 나타 났을 때 각도별 상황 구간을 잡아 놓고 아두이노 RC카를 좀 더 세부적으로 아래와 같이 if문으로 나누어서 각도별로 회전 주행 패턴을 만들 수 있습니다. 아래는 3구간으로 나눴지만 더 나눌 수 있고 또는 각 나눈 구간에서 다시 if조건문으로 해당 구간의 각도 범위에서 좀 더 각도를 나눠서 제어 방식을 변경할 수 도 있습니다. 이것은 여러분들의 상상력에 달려 있으니깐 한번 상상해 보세요.

if(A상황일때) 우회전;
else if(B상황일때) 좌/우회전 결정;
else if(C상황일때) 좌회전;

3. 회전 초음파센서로 장애물 감지 후 피하기 패턴 - II


위에서는 단순하게 장애물이 발견 된 시점에서 제자리에서 방향전환을 시키고 그다음 주행을 진행 했습니다. 뭔가 주행 중에 멈춰서 회전을 하기 때문에 끊기는 느낌으로 주행이 됩니다.


위 그림처럼 부드럽게 반원을 그리 듯 곡선 주행을 시키는 주행 패턴을 만들고 싶지 않으신가요. 이 방식은 어떤 주행패턴 일까요.

지난 [아두이노] 아두이노 2륜 RC카 주행 패턴 실험에서 주행패턴을 여러가지를 설명했는데 다음 주행패턴 코딩으로 표현을 할 수 있습니다.

motor1.setSpeed(130);
motor2.setSpeed(200);
motor1.run(FORWARD);
motor2.run(FORWARD);

바로, 이 주행패턴 코딩으로 곡선 주행을 하게 됩니다. 좌회전을 하고 싶다면 좌측 DC기어모터의 속도를 낮추게 되면 좌회전을 위 그림처럼 하게 되고 우회전을 하겠다면 우측 DC기어모터의 속도를 낮추면 그 방향으로 회전이 이뤄집니다. 실험에 사용하는 2륜 아두이노 RC카의 방향전환은 해당 DC기어모터의 속도에 따라서 방향을 전환하면서 주행할 수 있습니다. 도로주행과 같은 느낌의 주행이 되겠죠.

하지만, 아두이노 RC카는 이 방식만 있는게 아닙니다. 앞바퀴에 핸들식으로 서보모터를 부착해서 좌/우회전을 시킬 수 있는 방식이 있습니다. RC카 완제품을 사시면 앞바퀴 핸들식으로 무선조정하는 제품들을 많이 보셨을 꺼에요. 이방식에서는 서보모터가 45도가 수평일 때 90도는 좌회전이고 0도는 우회전으로 방향으로 조정하고 뒷바퀴는 그냥 RC카의 속도와 전진/후진 기능을 담당하면 됩니다.


servo.write(90);
motor1.run(FORWARD);
motor2.run(FORWARD);

이렇게 코딩을 바꾸면 됩니다.

마무리


대충 장애물 감지 했을 때 몇가지 패턴들을 만들어 보았습니다. Post [아두이노] 아두이노 2륜 RC카 주행 패턴 실험 의 내용에서 별로 바뀐 부분은 없습니다. 그냥 장애물 감지했을 때 상황을 두고 몇가지 패턴만 만들었을 뿐입니다. 상황을 그림으로 그리고 나서 그 그림을 기반으로 DC기어모터의 바퀴 회전을 어떤 형태로 회전 시킬 건지만 여러분들이 결정해서 패턴을 만들어 내면 됩니다.

  • motor.run(FORWARD) : 전진
  • motor.run(BACKWARD) : 후진
  • motor.run(RELEASE) : 해제

이 함수 3개를 이용하여 초음파센서로 장애물이 감지되면 각 DC기어모터를 FORWARD, BACKWARD, RELEASE 중 하나를 선택하여 장애물를 피하는 주행 패턴을 만들면 됩니다.

처음에는 이렇게 단순하게 장애물에 대해서 난 이렇게 피할꺼야! 아니면 저렇게 피할꺼야! 하면서 쉽게 만들면 됩니다. 처음부터서 정교하게 실제 차처럼 주행시켜야하지 하면 단순하 RC카 주행도 못시킬 가능성이 큽니다. 처음은 단순하게 무조건 쉽게 아두이노 RC카 주행을 시키면서 거기서 문제점이 발견되면 그 문제에 대해 다시 또 다른 주행 패턴을 만들고 하면서 조금씩 살을 붙여가면서 하셔야 정교한 주행이 되는 아두이노 RC카를 만들어 낼 수 있습니다.

오늘 Post도 처음에는 기조척으로 90도 기준으로 해서 좌/우 방향 회전하여 주행 패턴을 만들었는데 만들고 나서 Post를 쓰다보니깐 쓰는 도중에 상상코딩을 하게 되면서 주행 패턴에 대해 내용이 추가되었네요. 몇가지 더 내용이 있었는데 너무 길게 쓰는 것은 좀 그래서 중간에 멈췄네요. 다른 상황과 주행 패턴이 있었는데 대충 이런식으로 장애물을 피하는 주행패턴을 만들 수 있다는 의미를 전달하고 여러분들의 상상력에 맞기는게 나을 것 같아서 이정도로 마무리 했네요.

다음 Post에서 장애물 감지와 장애물 감지 했을 때 어떤 주행 패턴을 선택할지는 결정을 못했네요. 90도 기준으로 좌/우 방향 회전이 가장 유력하지만 너무 복잡한 주행 코딩은 안하고 단순하게 의미전달하는 주행 코딩으로 소개할 것 같습니다. 아무튼 여러분들은 오늘 제가 상상한 주행 패턴을 보시고 여러분들도 직접 장애물이 이렇게 배치 되어 있다면 난 이런 주행을 시켜봐야지 하고 상상을 하는 시간을 가졌으면 합니다.

댓글()

[아두이노] 초음파센서 아두이노 RC카 장애물 감지 방법

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

[아두이노] 초음파센서 아두이노 RC카 장애물 감지 방법



오늘은 초음파센서로 전방에 장애물을 감지하는 방법에 대해서 이야기를 할까 합니다. 우선, 주행하는 전방에 장애물이 등장 했을 때 여러분들이 제작한 RC카가 어떤 주행을 시킬 것인지 상상을 한번 해봅시다. 주행과 장애물 감지에 대해 잘 상상이 안될 수 있습니다. 그런데 주행과 장애물 감지 원리를 알면 그렇게 어렵지 않습니다. 주행과 장애물 감지에서 주행은 그냥 전진주행을 하게 하고 전진 주행 중 장애물에 대해 일정시간 단위로 초음파센서가 측정하게 하면 됩니다. 이 두가지 과정을 loop()함수에서 반복하게 하면 아두이노가 전진 주행중에 실시간으로 계속 장애물을 감지하게 되겠죠. 그리고 장애물이 감지 되었을 때 주행 방향을 바꿔주는 코딩을 하면 아두이노 RC카 자율주행이 됩니다. 복잡하게 출발하지 말고 쉽게 전진 중행을 하면서 초음파센서로 거리를 측정하여 장애물을 감지한다는 개념만 잡고 출발하시면 됩니다.


그러면, 간단히 상황을 설정하고 장애물 감지에 대해 살펴보도록 합시다.

1. 아두이노 RC카의 기본 주행



참고 자료를 보시면 주행 패턴에 대한 내용입니다. RC카가 주행을 하기 위한 기본 소스 코딩이 있는데 주행 패턴에 대해 사전학습을 하셨으면 합니다. 우선 아두이노 RC카의 움직임을 먼저 머리속에서 개념을 잡아놓고 출발하시면 쉽게 코딩 할 수 있습니다.

자율 주행은 전진 주행을 기본 베이스로 합니다.

#include <AFMotor.h>

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

void setup() {
  motor1.setSpeed(200);
  motor2.setSpeed(200);
  motor1.run(RELEASE);
  motor2.run(RELEASE);
}

void loop() {  

  초음파센서 장애물 감지;

  motor1.run(FORWARD); //전진
  motor2.run(FORWARD);
  delay(1000);
}

이렇게 기본 베이스 코딩이 끝났습니다. 실행을 하면 무조건 전진 주행을 하게 됩니다. 지난 RC카 post에서 위 소스에 대한 설명을 했기 때문에 간단히만 재정리 차원으로 설명하면 motor1(3), morot2(4)로 모터쉴드에서 M3, M4의 위치의 핀으로 DC기어모터 두개를 제어하는데 속도는 setSpeed(200)으로 스피드가 200이고 FORWARD(전진), BACKWARD(후진), RELEASE(해제) 중에서 loop()함수에서 무한 반복 FORWARD(전진)을 한다는 로직입니다.

딱 한줄로 정리하자면 전진만 하는 RC카라고 생각하시면 됩니다.

자율주행 기본 베이스 코딩은 끝났습니다. 이 소스를 기반으로 초음파센서로 장애물을 감지하는 방법들을 살펴 볼까요.

2. 초음파센서로 장애물 감지


초음파센서로 장애물을 감지하는 방식은 엄청 많습니다. 지금 소개하는 방식 외에도 더 많지만 생각나는 것만 몇개 간단히 설명을 할까 합니다.

1) 회전 초음파 센서로 장애물 감지



위 그림처럼 직진으로 주행하다가 장애물이 나타났을 때 초음파센서가 장애물을 어떤 식으로 감지 해야 할까요. 초보적 접근 방법으로는 두가지 방식으로 나누어 살펴 볼 수 있습니다. 주행 중 전방, 좌/우측 방향의 장애물 감지하는 방식과 주행 중에는 전방 장애물만 감지하다가 장애물 발견시 주행 중단 후 좌/우측 방향 장애물 감지하는 방식으로 나누어 살펴 볼 수 있습니다.

주행 중 전방, 좌/우측 방향의 장애물 감지



위 그림처럼 아두이노 RC카는 전방으로 진행하면서 서보모터가 부착된 초음파센서가 전방 일정 각도 범위로 왔다 갔다 회전하면서 장애물을 감지합니다. 이 방식은 전방, 좌/우측 방향으로 3가지 방향을 실시간으로 측정하여 장애물이 감지했을 때 이 3가지 방향의 값을 기준으로 장애물을 피하기 위한 RC카 주행 패턴을 만들 수 있습니다. 주행 중에 초음파센서가 장애물 감지하기 위해 거리 측정을 하고 장애물이 감지 된 측정 각도에 따라 진행 방향을 바꿀 수 있기 때문에 장애물이 감지된 각도에 따라 적절하게 방향을 바꿀 수 있게 할 수 있습니다.

void loop() {
  //회전
  servo.write(angle);
  delay(50);  

  //거리측정
  int distance = sonar.ping_cm();
  
  //장애물 감지
  if(distance<15){
      각도에 따른 장애물 피하기 동작;  
  }
  
  //회전 각도
  if (angle == 140) state = -10;    
  else if (angle == 40) state = 10; 
  angle += state;

  전방주행;
}

위 소스는 지난시간에 정상적으로 초음파센서가 0~180도 회전되는지 테스트한 코딩을 약간 수정한 소스입니다. 이 코딩을 기반으로 0.05초 간격으로 회전하면서 거리를 측정하면 됩니다. 회전 각도는 10도씩하여 40~140도 사이를 11번을 측정하게 됩니다. 1도씩 안하고 10도씩 한 이유는 사실 장애믈을 측정하기 위해서 1도씩 하면 110번을 측정해야하고 왕복 220번을 0.05초 간격으로 측정을 한다면 꽤 긴시간이 걸리기 때문에 실시간 대응이 어렵습니다. 짧게 돌면서 원하는 동작을 수행하기에는 10도정도가 가장 이상적이네요. 더 큰 각도로 제어해도 됩니다, 그냥 90도, 135도, 45도 이렇게 세각도만 측정해도 됩니다. 선택은 여러분의 마음입니다.

주행 중에는 전방 장애물만 감지하다가 장애물 발견시 주행 중단 후 좌/우측 방향 장애물 감지



아두이노 RC카가 직진 주행하는 중 전방 방향의 장애물만 측정합니다. 그러다가 장애물이 발견 되면 위 그림에서 오른쪽 그림처럼 A, B 각도의 장애물을 있는지 측정해서 둘 중 안전한 방향을 찾아 진행 방향을 선택하는 방식입니다.

void loop() {
  delay(50);  

  //거리측정
  int distance = sonar.ping_cm();
  
  //장애물 감지
  if(distance>0 && distance<15){
    주행정지
      angle=135;
        servo.write(angle);
        delay(500);
        int distanceA = sonar.ping_cm();
        angle=45;
        servo.write(angle);
        delay(500);
        int distanceB = sonar.ping_cm();
        
    if(distanceA<distanceB){
         B방향으로 피하기 주행;  
    }
    else{
         A방향으로 피하기 주행;
    }
  }
  전방주행; 
}

위 소스는 0.05초 간격으로 전방 장애물을 감지하다가 장애물이 발견되면 if문으로 들어가서 먼저 주행이 정지되고 그다음 135도와 45도의 방향으로 장애물을 감지합니다. 그리고 둘 중 거리값이 긴 쪽으로 장애물을 피하는 동작을 만들 수 있습니다.

2) 고정 초음파센서 장애물 감지



고정 형태로 한개의 초음파센서로 전방 장애물만 감지합니다. 전방 장애물을 감지 할 때 장애물을 피하기 동작 패턴을 만들기가 애매합니다. 그냥 랜덤 방향으로 할지 아니면 무조건 우방향으로 피할지는 여러분의 마음에 달렸습니다.

void loop() {
  delay(50);  

  //거리측정
  int distance = sonar.ping_cm();
  
  //장애물 감지
  if(distance<15){
    장애물 감지 방향선택;
  }
  전방주행; 
}

코딩은 가장 단순합니다. if문으로 전방 장애물이 발견 되면 그냥 후진, 좌/우 방향으로 진로 변경 할지는 여러분들의 선택으로 하나의 방향을 만들어 주면 됩니다.

3) 양쪽 두개의 고정 초음파 센서 장애물 감지



두개의 고정된 초음파 센서로 장애물을 감지할 경우 A, B 두지점으로 좌/우 방향 위치의 장애물을 감지하게 됩니다. 이 경우는 위에서 전방 장애물을 감지 되었을 때 주행을 정지 한 상태에서 좌/우 방향 위치의 장애물을 감지 했던 코딩과 유사 합니다. 차이점은 주행 중에 실시간으로 좌/우 방향의 장애물을 감지 한다는 점만 차이가 있습니다.

void loop() {
  //거리측정
  delay(50);  
  int distanceA = sonar[0].ping_cm();  
  delay(50);  
  int distanceB = sonar[1].ping_cm();
    
  //장애물 감지
  if(distanceA<15 || distanceB<15){
    if(distanceA<distanceB){
         B방향으로 피하기 주행;  
    }
    else{
         A방향으로 피하기 주행;
    }
  }
  전방주행; 
}

이렇게 두 지점의 거리를 측정 한 뒤에 if문에서 조건식을 체크하는데 '||'로 두 조건식 중 하나라도 만족하면 참인 조건문으로 체크하게 됩니다. 둘중 하나라도 만족하면 진로 방향에 장애물이 있다고 간주하는 것이죠. 즉, A or B의 거리가 15cm 이하이면 참이되어 장애물을 감지된 걸로 간주 합니다. 여기서, 두 지점을 비교하여 A지점이 거리가 더 짧으면 B방향으로 주행하게 만들고 A지점의 거리가 더 길면 A방향으로 주행하도록 만들 수 있습니다.

이외에도 장애물 감지 방식은 여러분들이 상상하는 방식에 따라서 다양하게 표현이 가능합니다. 순간 떠오른 생각들을 몇가지 정리한 것이 위의 장애물 감지 표현입니다. 여러분들도 이 방식 말고 다른 방식으로 장애물을 감지할 수 있는 방법을 찾아보셨으면 합니다. 그래야 상상코딩 능력을 키울 수 있습니다.

마무리


오늘은 초음파센서를 이용하여 장애물을 잠지하는 방법들을 살펴 보았습니다. 초음파센서를 어떤 형태로 배치하느냐에 따라서 감지하는 방식이 달라집니다. 어떤 방식이 정도라도 말 할 수 없으며 여러분들이 원하는 방식으로 표현하시는게 바로 정도입니다. 그리고, 위에서 열거한 방식으로 구지 안하셔도 됩니다. 여러분들의 상상력으로 감지 방식을 만들면 됩니다.

장애물 감지를 위해 실험에 사용한 초음파센서를 이용하셔도 되고 다른 거리측정 센서를 이용하셔도 됩니다. 아니면 전혀 다른 방식으로 주변을 감지할 수 있는 부품을 사용하여 표현하셔도 됩니다. 저는 가장 쉽게 접근할 수 있는 방식이 초음파센서라서 초음파센서를 사용한 것 뿐입니다. 여러분들이 다른 방식으로 하기 어려우시다면 1200원 짜리 초음파센서를 이용하여 실험하셔도 됩니다. 초음파센서를 어떻게 배치하고 사용할지는 여러분의 상상력에 달려 있음으로 한번 상상의 나래를 펼쳐보세요.

댓글()

[아두이노] 초음파센서 아두이노 RC카 자율주행 준비 입문자용

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

[아두이노] 초음파센서 아두이노 RC카 자율주행 준비 입문자용



아두이노 2륜 RC카를 가지고 초음파센서와 결합하여 장애물을 감지하면서 자율주행을 할 수 있도록 하는 간단한 실험을 할 예정입니다. 오늘 Post는 초음파센서를 이용한 자율주행 RC카를 만들기 위한 몸체 조립과 조립에 대한 테스트 동작을 통해 아무 문제가 없는지 확인하는 단계의 내용으로 구성되어 있는데 천천히 배워보도록 하죠.

진짜로, 정교한 자율주행 RC카를 만든다면 영상처리 부분까지 하셔야 합니다. 입문자분들이 바로 영상처리까지 하기는 무리가 따릅니다. 저도 영상처리 부분은 어렵고 실시간 영상을 가지고 주변환경 영상의 에지를 추출하여 그 값을 기반으로 주행 처리를 해야하는데 대충 코딩 방향은 아는데 실시간 영상에서 에지 검출이 좀 힘들어서 설명드리기가 어렵네요. 에지 검출이 영상처리책 보면 대표적으로 canny, sobel 에지 검출 알고리즘이 있습니다. 이 알고리즘으로 정지 영상에서 에지를 추출하는 것은 쉽지만 실시간 영상에서는 저도 알고리즘 로직이 잘 감이 안와서 설명을 못 드리겠네요. opencv로 얻은 영상에서 에지를 검출하고 그 영상을 기반으로 주행을 시키는 것 같은데 고급 자율주행은 사실 깊게 이쪽 분야로 공부를 안해서 아쉽게 실험을 못해 봤습니다. 나중에 라즈베리파이로 공부할 때 한번 도전은 해보고 싶긴 하네요.

사설은 그만하고 오늘 post의 주제는 지난 시간에 만든 아두이노 RC카 정면에 초음파센서를 부착하는 조립하는 단계입니다. 초음파센서에 서보모터를 통해 0~180도 사이각의 회전을 시킬 수 있는 형태로 조립을 하게 되는데 사실 뼈대가 부품가격과 거의 비슷하기 때문에 그냥 테이프로 부착시켰습니다. 테이프로 부착하다보니 정교한 측정이 되지 않지만 그래도 근사값 측정을 통해서 원하는 동작을 충분히 실험할 수 있기 때문에 아무런 문제는 없습니다. 여러분들은 뼈대를 구매하거나 3D 프린트로 직접 만들시면 좋겠죠. 저처럼 테이프로 고정시키는 실험은 보기 흉하니깐 뼈대를 사셔서 이쁘게 만들어 보세요.


이제 본격적으로 아두이노 RC카 자율주행을 도전 할까요.

1. 아두이노 RC카 준비



사전학습 post로 가셔서 아두이노 RC카에 대한 학습을 먼저 하셔야 합니다. 대충 사전학습에서 RC카 조립을 끝낸 상태면 이렇게 완성되어 있겠죠.


조립이 완성된 상태에서 다음 준비 부품이 필요합니다.

2. 아두이노 RC카에 추가 부품 조립


  • 준비물 : 아두이노 RC카 1대, 초음파센서 1개, ServoMotor 1개


위 사진에서는 뼈대가 있으면 좋은데 간단히 실험을 하기 위해서 그냥 테이프로 붙였습니다. 아두이노 RC카 정면에 Servo Motor와 초음파 센서를 정면을 바라보도록 배치하시면 됩니다. 혹시 뼈대를 개인적으로 구매하신다면 안정적으로 배치 할 수 있겠죠. 저는 테이프로 붙여서 각도도 안맞고 약간 아래로 쳐져 있어서 좀 불편하게 실험 했네요.


위에서 바라보는 모습입니다. 보시면 A은 초음파센서핀으로 연결합니다. 초음파센서 핀은 A4(Trig), A5(Echo)핀입니다. 그리고 B는 Servo Motor 핀을 연결합니다. 바깥쪽부터 (-,+,sig) 이렇게 순서대로 있는데 3핀을 순서대로 연결하시면 됩니다. 제가 실험한 모터쉴드는 2개의 Serovo Motor 핀을 제공하는데 상단 첫번째 줄은 아두이노 디지털핀 10번이고 두번째 줄은 9번핀이네요. 10번핀을 사용하였습니다.

좀 더 자세히 아래 사진의 L293D Motor Shield의 모습을 보시고 연결하세요.


해당 위치에 핀을 꼽아주고 아두이노 RC카 정면에 Servo Motor와 초음파 센서를 부착하시면 조립은 완성입니다.

2. 간단히 장애물 감지센서 시범 회전 코딩



Servo Motor

#include <Servo.h> 
  • Servo servo : 서보모터 객체변수 선언
  • servo.attach(servoPin) : 서보모터 시작하는데 사용되는 핀은 servoPin(10) 임
  • servo.write(angle) : angle 값으로 서보모터를 회전 시킨다.

NewPing

#include <NewPing.h>
  • NewPing sonar(TrigPin, EchoPin, MaxDistance) : TrigPin과 EchoPin과 최대제한거리(MaxDistance)의 값을 선언합니다.
  • sonar.ping_cm() : 센서 거리를 'cm'로 계산된 값을 출력한다.

1) Servo Motor 회전

Servo Motor가 정상적으로 회전이 되는지 테스트 코딩을 해야 겠지요.

Servo servo;
const int servoPin = 10;
int state = 1;
int angle = 90;

이렇게 우선 서보모터 객체변수을 하나 만들고 서보모터를 제어할 아두이노 디지털 핀 10을 선언하고 초기 각도(angle) 값을 90도로 표현 했습니다. 그리고 state은 초기값이 1인데 이 값은 1이면 1씩 증가 -1이면 1씩 감소하는 변수로 사용하기 위해 만든 변수입니다.

void setup() {
  servo.attach(servoPin);
  servo.write(angle);
  delay(1000);
}

setup()함수에서 초기화 작업을 하는데 서보모터를 시작하고 바로 servo.write()함수를 사용하여 angle(90)각도로 회전합니다. 딜레이 시간은 1초를 줬네요. 초기값이라서 원하는 시간값을 여러분들이 마음대로 결정하세요. 서보모터를 회전시킬 만큼의 딜레이시간을 줘야하는데 1초은 꽤 긴시간이고 더 짧게 딜레이시간을 주고 90도까지 회전 시켜도 됩니다. loop()함수로 진입하기 전에 충분히 대기시간을 주기 위해서 1초정도 딜레이를 줬네요.

void loop() {
  //회전
  servo.write(angle);
  delay(50);  

  //회전 각도
  if (angle == 180) state = -1;    
  else if (angle == 0) state = 1; 
  angle += state;
}

loop()함수는 0.05초 간격으로 서보모터를 angle(각)으로 회전시키게 됩니다. 어떻게 회전될 까요 회전 각도 로직을 살펴보시기 바랍니다.

  if (angle == 180) state = -1;    
  else if (angle == 0) state = 1; 
  angle += state;

보시면 angle이 180도면 state은 -1이고 angle이 0도이면 state 값은 1이다. 이 의미를 이해하기 위해서는 아래 문장을 보시면 쉽게 이해가 되실 꺼에요.

angle = angle + state;

이 문장은 0.05초 간격으로 loop()함수가 무한 반복하는데 기존에 angle값에다가 state을 계속 더해주는 문장입니다. 여기서, state가 1이면 1씩 증가하고 state가 -1이면 1씩 감소하게 됩니다. 의미를 잘 이해해 주세요. 이렇게 되면 초기값이 state가 1이기 때문에 초기각도 angle이 90도니깐 1도씩 증가하게 됩니다.

서보모터는 0~180도까지 제어가 가능합니다 그러면 1씩 증가하더라도 180도를 넘을 수 없게 됩니다 180도가 되면 state값을 -1로 변경하면 1도씩 감소하게 되고 역방향으로 회전하게 됩니다. 그러면, 다시 역방향으로 1도씩 감소하는데 angle은 0이하로 떨어질 수 없습니다. 이때 state 값을 1로 변경하고 다시 정방향으로 1도씩 증가하게 됩니다. 이렇게 표현하면 0~180도 사이를 1도씩 왔다 갔다 회전하게 됩니다.

loop()함수 로직의 동작은 0.05초 간격으로 0~180도 사이를 무한 반복 회전을 하게 됩니다. 이 움직임을 보시면 초음파레이더가 생각 나시는 분이 있을 지 모르겠네요. 초음파레이더의 기본 동작 코딩입니다. 이 코딩으로 초음파레이더를 만들 수 있습니다. 현재 우리가 초음파레이더를 만드는 것이 목적이 아니기 때문에 서보모터 회전에 대한 테스트 동작으로만 이해해 주셨으면 합니다.

2) 초음파센서 거리 측정


이제는 서보모터가 회전할 때마다 초음파센서로 거리를 측정해야 겠죠.

NewPing sonar(A4, A5, 100);

trig(A4), Echo(A5), 제한거리1미터(100)으로 초음파센서 객체변수를 만듭니다.

  int distance = sonar.ping_cm();

distance 변수에 초음파센서에서 측정한 거리(cm)값이 저장됩니다. 이렇게 해서 거리 측정이 끝났네요.

그러면 이 코딩은 전체소스 안에서 어느 위치에 삽입하는 것이 좋을까요. 회전 시마다 초음파센서로 측정한다고 했죠. 서보모터는 어떻게 회전한다고 했죠. 0.05초 간격으로 회전이 이뤄집니다. 그러면, 0.05초 간격으로 회전할 때 초음파센서로 측정하면 되겠죠. 즉, 서보모터를 angle(각)으로 회전시킨 후 초음파센서로 거리를 측정하게 코딩하면 간단히 해결 됩니다.

void loop() {
  //회전
  servo.write(angle);
  delay(50);  
    
  int distance = sonar.ping_cm();
    
  //회전 각도
  if (angle == 180) state = -1;    
  else if (angle == 0) state = 1; 
  angle += state;
}

이렇게 하면, 0.05초 간격으로 서보모터를 회전시키고 distance 변수에 회전 시킨 각도에서 거리측정한 값이 저장됩니다.

그러면, 이 값을 시리얼모니터로 정상적으로 출력되는지 Serial 함수로 표현하면 되겠죠.


  • Serial.begin(9600) : 시리얼통신 시작
  • Serial.print(distance) : 시리얼모니터로 출력

3) 종합 코딩


좀 더 깔끔하게 각도와 거리값을 동시에 시리얼모니터로 출력시켜서 확인할 수 있도록 소스를 수정하였습니다. Serial함수는 너무 많이 설명을 드렸기 때문에 구지 설명을 안드려도 아실꺼라 믿고 아래 변경된 부분만 잘 확인해 주세요.

참고로, 완성된 소스는 초음파레이더 소스와 동일합니다. 이 소스로 processing으로 시각화 하면 초음파레이더가 됩니다. 초음파레이더로 보시지 마시고 그냥 앞에 장애물을 감지하는 센서로 보셨으면 합니다. 서보모터의 회전이 정상적으로 이뤄지고 거리측정이 정상적으로 이루어지는지 테스트하는 용도로 코딩을 바라 보시기 바랍니다.

[소스]

#include <Servo.h> 
#include <NewPing.h>

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

Servo servo;
const int servoPin = 10;
int state = 1;
int angle = 90;

void setup() {
  Serial.begin(9600);
  servo.attach(servoPin);
  servo.write(angle);
  delay(1000);
}

void loop() {
  //회전
  servo.write(angle);
  delay(50);  

  //거리측정
  int distance = sonar.ping_cm();
  Serial.print(angle);        
  Serial.print(" : ");        
  Serial.print(distance);    
  Serial.println(" cm");         

  //회전 각도
  if (angle == 180) state = -1;    
  else if (angle == 0) state = 1; 
  angle += state;
}

newPing 라이브러리를 혹시 안깔고 코딩하신분들이 있을 것 같아서

라이브러리 매너저 창에서 "newPing"이라고 검색하시면 자동으로 검색되고 해당 라이브러리를 설치하시면 됩니다.


3. 결과


촬영이 좀 깔끔하게 되지 못했네요. 폰으로 찍어서 하다 보니깐 공간적 제약도 따르고 해서 좀 보기 불편하시더라도 대충 어떤식으로 Servo Motor가 회전 되고 초음파센서를 통해 거리가 측정되는지 영상으로 살펴보시기 바랍니다. 참고로 위쪽 상단는 모니터가 있어서 거리값이 10~11정도로 측정됩니다. 오류값이 아니라 모니터와의 거리 때문에 나온 값이니깐 감안하시고 보세요.


마무리


오늘은 아두이노 RC카가 준비된 상태에서 두개의 부품 Servo Motor와 초음파센서를 조립에 대해 간단히 살펴보았습니다. 그리고 정상적으로 동작하는지 실험 코딩으로 테스트까지 해보았습니다. 이렇게 해서 초음파센서를 통해 아두이노 RC카는 정면의 장애물 감지까지 할 수 있게 되었습니다. 다음 단계는주행 도중에 장애물이 감지된 정보에 대해 어떻게 반응 할 것인지가 남아 있습니다.

사실 위에서 정상 작동하는지 테스트한 코딩을 이해하셨다면 조금만 상상을 더하면 장애물을 피해 자율주행을 시키는 코딩은 어렵지 않게 하실 수 있을 겁니다. 이미 이 post로 아두이노 RC카 자율주행은 끝난 거나 다름 없습니다. 그런데, 이걸로 어떻게 자율주행이 되냐고 생각하시는 분들도 많을 꺼에요. 잘 연상이 안되실 꺼에요. 상상을 많이하고 자기 자신이 RC카라고 상상하면서 내 이마에 서보모터가 부착되어 회전하고 있고 회전하면서 초음파센서로 거리를 측정하고 있다고 상상하면서 한번 제자리에서 걸어 다녀보세요. 전방에 벽이 나타나면 여러분들은 걷다가 멈추게 되겠죠. 그리고, 그 벽을 피해서 어떤 행동을 하겠죠. 벽 앞에서 다시 뒤로 되돌아 간다거나 오른쪽 길이 있으면 오른쪽으로 걸음을 옮기게 되겠죠. 이렇게 어떤 행동을 취하게 됩니다. 그 행동을 코딩화 하면 그 코딩이 아두이노 RC카의 자율주행 코딩이 됩니다. 즉, 어떤 상황이 되면 그 상황을 피하기 위해서 어떤 행동을 취하고 그 행동이 자율 주행 명령 코드가 되어 실제로 아두이노 RC카가 자율주행을 하게 됩니다. 코딩은 모르더라도 대충 어떤 느낌이신지 아시겠지요.

한번 자기자신이 아두이노 RC카라고 상상하고 제자리에서 주변을 걸어다녀보시면서 상황을 만들고 그 상황을 기록했다가 상상 코딩을 해보셨으면 합니다.


댓글()

[아두이노] newPing 라이브러리로 초음파센서 제어

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

[아두이노] newPing 라이브러리로 초음파센서 제어



이제까지 초음파센서를 이용하여 거리를 구하기 위해서 직접 공식에 대입하여 코딩을 했었습니다. 직접 코딩하는 것이 불편하신 분들을 위해 newPing이라는 라이브러리를 소개할까 합니다. newPing이라는 라이브러리를 통해서 초음파센서를 쉽게 제어하여 원하는 거리측정 값을 얻을 수 있습니다. newPing 라이브러리를 이야기하기전에 우선 초음파센서 거리측정에 대해서 복습해야겠죠.

초음파센서 거리를 구하는 공식은 다음과 같습니다.

duration = 초음파 센서를 통해 읽은 거리 시간값
distance = ((float)(340 * duration) / 10000) / 2;

위 공식은 복습차원으로 봐주세요. duration을 구하는 코딩 로직은 참고 자료 post를 한번 읽고 와주세요.


여기까지 복습이 완료 된 상태라면 newPing 라이브러리로 실제 실험을 해봅시다.

1. 초음파센서 회로도



공개회로도는 3핀 초음파센서로 직접 측정 로직을 코딩하고 직접 측정한 시간값을 거리를 구하는 공식에 대입하여 구한 예제입니다. 실제 실험에서는 4pin으로 구성된 초음파 센서를 실험하기 때문에 회로도를 표현하면 아래 회로도와 같습니다.


2. newPing 라이브러리 설치


라이브러리 매너저 창에서 "newPing"이라고 검색하시면 자동으로 검색되고 해당 라이브러리를 설치하시면 됩니다.


3. 코딩



NewPing

#include <NewPing.h>
  • NewPing sonar(TrigPin, EchoPin, MaxDistance) : TrigPin과 EchoPin과 최대제한거리(MaxDistance)의 값을 선언합니다.
  • sonar.ping_cm() : 센서 거리를 'cm'로 계산된 값을 출력한다.
  • 다수의 초음파센서 사용 :
NewPing sonar[센서수] = { 
  NewPing(trigPin1, echoPin1, 최대제한거리1), 
  NewPing(trigPin2, echoPin2, 최대제한거리2), 
    ...
};
  • 다수의 초음파센서 접근 :
  • sonar[0].ping_cm() : 0번째 sonar에서 측정된 거리를 'cm'로 계산된 값을 출력한다.

그외 함수는 newPing 라이브러리가 링크된 아두이노 공식 사이트에 가시면 함수들에 대해 자세히 설명되어 있으니깐 가셔서 한번 읽어 주시기 바랍니다.

[소스] : NewPingExample 소스(출처: newPing 라이브러리 예제)

#include <NewPing.h>

//sonar(TrigPin, EchoPin, MaxDistance);
NewPing sonar(2, 3, 200);

void setup() {
  Serial.begin(9600);
}

void loop() {
  delay(50);          
  Serial.print("Ping : ");
  Serial.print(sonar.ping_cm());
  Serial.println("cm");
}

sonar.ping_cm() 함수를 통해서 초음파 센서의 거리를 'cm'로 출력된 값을 시리얼모니터로 출력하는 코딩입니다. 딱 한줄입니다. sonar.ping_cm() 함수 이 한줄의 명령을 실험하는 내용입니다. 그걸 시리얼모니터에 출력하기 위해서 3줄의 시리얼 출력문을 코딩했을 뿐 실제 초음파센서를 제어하는 명령은 딱 한줄입니다.

void loop() {
 delay(50);          
 sonar.ping_cm();
}

이렇게 0.05초 간격으로 초음파센서는 거리측정 값을 'cm'을 반환합니다. 어렵지 않죠.

라이브러리를 이용하면 이 함수 한개로 복잡하게 코딩을 생각하지 않고 거리를 'cm'로 만들 수 있지만 직접 거리를 계산하는 공식 코딩은 단순하기 때문에 구지 라이브러리를 사용 안해도 됩니다.

그런데, 왜! 라이브러리를 사용하는지 궁금하실 꺼에요. 거리 측정하는 거리 공식도 별로 어렵지 않은데 그냥 코딩하면 되지 라이브러리를 사용할 필요가 있냐고 생각하실 꺼에요. 그 이유는 초음파 센서를 한개 사용할 때는 상관 없는데 다중 초음파센서를 사용할 때 newPing 라이브러리 사용하면 효율적으로 코딩을 할 수 있습니다. 표현도 배열로 간단히 하나의 묶음으로 표현 할 수 있고, 여러개를 측정하려면 시간 관련한 문제도 고려해서 코딩해야 하는데 라이브러리를 사용하면 좀 더 정교하게 코딩이 가능하기 때문에 사용합니다. 위에 링크 된 아두이노 공식 홈페이지에서 소개하는 newPing 라이브러리 관련 내용을 보시면 15개 초음파센서를 한번에 제어하는 예제가 있는데 가셔서 살펴 봐주세요. 물론 라이브러리를 설치하면 아두이노 IDE에 예제로 나와 있어 설치하고 나서 예제를 열어보시고 어떻게 코딩되어 있는지 살펴보셔도 됩니다.

3. 결과


4. 2개 초음파 센서 사용


이제 2개의 초음파를 한번에 측정하여 그 결과를 출력해 보는 실험을 해보겠습니다.

1) 2개 초음파 센서 회로도


  • 준비물 : 초음파센서 2개, 아두이노우노
  • 내용 : 왼쪽 초음파센서는 tragPin 2, echoPin 3 에 연결하고, 오른쪽 초음파센서는 tragPin 4, echoPin 5 에 연결하시오.


2) 코딩


배열로 초음파센서 객체를 선언합니다.

NewPing sonar[2] = { 
  NewPing(2, 3, 200), 
  NewPing(4, 5, 200), 
};

이렇게 선언하시면 초음파센서 측정은 sonar[0].ping_cm(), sonar[1].ping_cm() 함수로 해서 2개의 초음파 센서의 거리를 측정하여 'cm'로 출력하게 됩니다. 초음파 센서들 간의 시간 딜레이는 0.05 초로 간격을 두어 측정하게 됩니다. 너무 빠르게 두개의 초음파센서의 값이 시리얼모니터로 출력되기 때문에 딜레이를 loop()함수 안에 마지막 라인에 0.5초의 딜레이를 줌으로서 좀 천천히 2개의 초음파센서 값이 시리얼모니터로 출력되게 코딩했네요.

코딩은 따로 설명할 필요 없이 방금 앞에서 코딩한 소스에서 한번 더 초음파센서 sonar[위치].ping_cm()함수로 출력하는 명령만 추가한 것 뿐이니 구지 설명하지 않겠습니다.

[소스]

#include <NewPing.h>

//sonar(TrigPin, EchoPin, MaxDistance);
NewPing sonar[2] = { 
  NewPing(2, 3, 200), 
  NewPing(4, 5, 200), 
};

void setup() {
  Serial.begin(9600);
}

void loop() {
  delay(50);          
  Serial.print("A Ping : ");
  Serial.print(sonar[0].ping_cm());
  Serial.println("cm");
  
  delay(50);          
  Serial.print("B Ping : ");
  Serial.print(sonar[1].ping_cm());
  Serial.println("cm");

  delay(500);
}

3) 결과



위 사진을 보시면 A초음파센서와 B초음파센서로 나뉩니다. 여기서, B초음파센서는 Servo Motor에 테이프로 감겨 있고 Servo Motor 같이 아두이노 RC카에 테이프로 부착되어 있어서 어쩔 수 없이 RC카에 부착된 B초음파센서랑 A초음파센서를 실험하다 보니깐 보시는 것처럼 좀 불편하게 실험 되었습니다. 감안하시고 동영상을 보시기 바랍니다.


마무리


오늘 post는 초음파센서를 직접 코딩하는 방법도 있지만 이렇게 라이브러리를 이용하여 쉽게 코딩할 수 있는 것을 보여드리기 위해서 다뤘습니다. 그리고, 왜! 갑자기 초음파센서를 이 시점에 다시 post를 했냐면 아두이노 RC카에서 거리측정을 통해 장애물을 감지하는데 사용하기 위해서 사전 학습으로 거론하게 되었네요. 직접적으로, 측정 로직을 코딩하는 것도 좋지만 새로운 것을 소개하는 것이 좋을 것 같아서 초음파센서에 대한 post를 하면서 newPing에 대해 이야기 하면 좋을 것 같아 이렇게 이야기를 하게 되었네요.

오늘 이야기한 post를 보기전 사전학습으로 직접 거리를 측정하는 방법에 대한 링크된 post 글을 찾아가셔서 복습을 한 뒤에 newPing을 사용해 보셨으면 합니다.


댓글()

[아두이노] 2륜 RC카 원리를 가상시뮬레이터로 실험

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

[아두이노] 2륜 RC카 원리를 가상시뮬레이터로 실험



지난 시간에 기본 아두이노 2륜 RC카 조정에 대해서 마무리 했습니다. post로만 아두이노 RC카 설명을 끝내기가 아쉬워서 가상시뮬레이터에서 동일하게 표현하고 코딩도 유사하게 코딩해서 간접적으로나마 체험 할 수 있게 하면 좋을 것 같아서 간단히 가상시뮬레이터에서 회로도를 만들어 보았습니다.

구체적으로 어떻게 표현했는지 설명 하겠습니다.

1. 아두이노 2륜 RC카 회도로


  • 준비물 : L293D 칩 1개, DC Motor 2개, 외부전원, 아두이노우노
  • 내용 : 아두이노 위에 L293D 칩 pin 들을 아두이노우노에 원하는 위치에 연결한다.
  • 참고 : [아두이노] L293D + DC MOTOR 제어

회로도가 복잡해 보일 꺼에요. 회로도가 이해가 안가신다면 위에 링크 걸린 참고 자료에 가셔서 복습하시면 됩니다.

참고로, [아두이노] 2륜 RC카 Bluetooth를 통해 스마트폰(무선) 조정하기 post의 동작과 동일합니다. 실제로 만들 경우에 동일한 결과가 나오겠지요.

그리고, 이 회로도에서 Bluetooth은 0,1 핀을 사용한다는 가정하에서 진행됩니다. 저번에 설명했듯이 아두이노 내부 시리얼통신 핀을 사용할 경우는 SoftwareSerial 라이브러리가 필요 없습니다. 그냥 상상으로 0,1번핀에 Bluetooth가 연결되었다고 상상하신 후 보시기 바랍니다.

Bluetooth Tx = arduino Pin0
Bluetooth Rx = arduino Pin1

3. Motor 라이브러리 만들기



원래는 Adafruit-Motor-Shield-library 를 인용해서 코딩해야 하는데 해당 부분만 편집하면 좀 문제가 생길 수 있고 해서 지난시간에 이 라이브러리로 실험했던 느낌을 계속 유지하기 위해서 라이브러리에 사용한 함수명만 그대로 인용해서 새롭게 Motor 라이브러리를 만들어 보았습니다.


  • AF_DCMotor motor(3) : M3핀을 DC Motor 제어용으로 사용.
  • motor.setSpeed(200) : 모터 속도
  • motor.run(FORWARD) : FORWARD, BACKWARD, RELEASE 회전 명령

기본 구조가 이렇게 위처럼 되어 있는데 혼동을 피하고 같은 느낌을 계속 유지하기 위해서 위 표현을 그대로 인용 합니다.

#define FORWARD  1
#define BACKWARD 2
#define RELEASE  3

class DCMotor{
 public:
   DCMotor(uint8_t pinA, uint8_t pinB, uint8_t pinC);
   void run(uint8_t);
   void setSpeed(uint8_t);
   private:
   uint8_t motorA, motorB,speedPin;
};

위 처럼 우선 생성자 DCMotor() 함수를 표현 했습니다. 어떤 핀을 Motor로 제어할지 Motor 객체변수를 선언과 동시에 지정해주기 위해서 입니다. 그리고, run(), setSpeed() 함수명을 그대로 인용했네요.

함수들의 로직 코딩을 볼까요.

DCMotor::DCMotor(uint8_t pinA, uint8_t pinB, uint8_t pinC) {
  motorA = pinA;
  motorB = pinB;
  speedPin = pinC;
  pinMode(motorA,OUTPUT);
  pinMode(motorB,OUTPUT);
  pinMode(speedPin,OUTPUT);
}

DCMotor()함수는 private 형으로 선언한 클래스 내부 변수들에 대한 초기화 작업을 수행합니다. 각 pin의 사용 모드도 여기서 초기화 합니다. 사실 클래스 형태로 표현할려면 이런 코딩이 아니라 아두이노 주소번지로 직접 접근하여 좀 더 하드웨어적 코딩을 해야하는데 복잡해 보일 것 같아서 아두이노 함수로 간단히 클래스를 표현했네요. 느낌만 표현한 클래스 입니다. 진짜 만든다면 이렇게 하면 안되고요. 좀 더 하드웨어적 코딩을 해야 합니다. 우리는 즐기기 위해서 하는 것이기 때문에 쉽게 코딩하는게 좋겠죠.

void DCMotor::run(uint8_t key) {
  switch (key) {
  case 1:
    digitalWrite(motorA,HIGH); //전진
    digitalWrite(motorB,LOW);
    break;
  case 2:
    digitalWrite(motorA,LOW); //후진
    digitalWrite(motorB,HIGH);
    break;
  case 3:
    digitalWrite(motorA,LOW); //정지
    digitalWrite(motorB,LOW);
    break;
  default:
    return;
  }
}

run()함수는 key 값은 FORWARD, BACKWARD, RELEASE 값을 말합니다. define으로 정의한 1, 2, 3값에 해당되는 switch()함수를 통해서 해당된 pin을 digitalWrite()함수로 High or Low로 결정하게 됩니다. 이 코딩은 이미 지난 Post L293D + DC MOTOR 제어 (아두이노) 에서 소개 했습니다. 혹시 잘 모르시겠다면 해당 post에 가셔서 한번 읽고 오시기 바랍니다.

void DCMotor::setSpeed(uint8_t speed) {
   analogWrite(speedPin, speed);
}

setSpeed()함수는 Enable Pin으로 값을 출력하는데 PWM Pin을 사용하여 0~255 사이의 아날로그 신호를 출력합니다. speed의 값을 analogWrite()함수로 아날로그 신호를 출력하면 되기 때문에 딱 한줄 명령으로 표현 됩니다.

이렇게 해서 DCMotor 클래스를 간단히 표현 했습니다.

3. 코딩


만든 DC Motor 클래스

  • DCMotor motor1(7,6,5);) : In1, In2, EnablePin
  • motor.setSpeed(200) : 모터 속도
  • motor.run(FORWARD) : FORWARD, BACKWARD, RELEASE 회전 명령

[소스]

#define FORWARD  1
#define BACKWARD 2
#define RELEASE  3

class DCMotor{
 public:
   DCMotor(uint8_t pinA, uint8_t pinB, uint8_t pinC);
   void run(uint8_t);
   void setSpeed(uint8_t);
   private:
   uint8_t motorA, motorB,speedPin;
};

DCMotor::DCMotor(uint8_t pinA, uint8_t pinB, uint8_t pinC) {
  motorA = pinA;
  motorB = pinB;
  speedPin = pinC;
  pinMode(motorA,OUTPUT);
  pinMode(motorB,OUTPUT);
  pinMode(speedPin,OUTPUT);
}
void DCMotor::run(uint8_t key) {
  switch (key) {
  case 1:
    digitalWrite(motorA,HIGH);
    digitalWrite(motorB,LOW);
    break;
  case 2:
    digitalWrite(motorA,LOW);
    digitalWrite(motorB,HIGH);
    break;
  case 3:
    digitalWrite(motorA,LOW);
    digitalWrite(motorB,LOW);
    break;
  default:
    return;
  }
}

void DCMotor::setSpeed(uint8_t speed) {
   analogWrite(speedPin, speed);
}
                 
DCMotor motor1(7,6,5); //L293D Motor Pin과 Enable Pin
DCMotor motor2(9,8,3);

int speed = 200;
                 
void setup() {
  Serial.begin(9600);   
  motor1.setSpeed(speed);
  motor2.setSpeed(speed);
  motor1.run(RELEASE);
  motor2.run(RELEASE); 
}
 
void loop() {
  if (Serial.available()){
    char ch = Serial.read();

    switch(ch){
      case 'w':
            motor1.run(FORWARD);
            motor2.run(FORWARD);
            break;
      case 's':
            motor1.run(BACKWARD);
            motor2.run(BACKWARD);
            break;
      case 'a':
            motor1.run(BACKWARD);
            motor2.run(FORWARD);
            break;
      case 'd':
            motor1.run(FORWARD);
            motor2.run(BACKWARD);
            break;
      case 'z':
            motor1.run(RELEASE);
            motor2.run(RELEASE);
            break;
      case 'm':
            speed+=10;
            if(speed>=250) speed=250;
            motor1.setSpeed(speed);
            motor2.setSpeed(speed);
            break;
      case 'n':
            speed-=10;
            if(speed<=0) speed=0;
            motor1.setSpeed(speed);
            motor2.setSpeed(speed);
            break;
    }         
  }
}

만든 Motor 클래스만 추가되고 setup(), loop()함수는 실제 제작했던 Post [아두이노] 2륜 RC카 Bluetooth를 통해 스마트폰(무선) 조정하기 와 동일한 소스 입니다.

이렇게 해서 가상시뮬레이터로 회로도를 만들고 코딩도 동일하게 코딩해서 실험할 수 있도록 세팅이 끝났네요.

위 소스에서 실제로 L293D Motor Shield로 제어한다면 해당 라이브러리를 다운 받은 후 객체 선언부분만 형식에 맞춰서 하신 후 setup(), loop()함수는 수정 없이 그대로 사용하면 실제로 스마트폰으로 조정이 가능 합니다.

5. 결과


가상시뮬레이터를 싱행 시킨 후 위 그림처럼 시리얼모니터에서 회전 명령 키값을 전송하면 됩니다. 스마트폰의 역활을 대신하는 것이죠. 0,1번 핀에 Bluetooth Pin을 연결하면 바로 스마트폰으로 제어가 가능합니다.

전진(w)/후진(s), 좌(a)/우회전(d), 정지(z), 속도 증가(m)/감소(n) 의 명령으로 세팅 되어 있고 가상시뮬레이터가 스마트폰으로 상상하고 키값을 입력하여 전송하면 거기에 맞게 가상시뮬레이터에 DC기어모터가 회전을 하게 됩니다.

아래는 테스트 결과입니다.


위 영상이 제대로 식별이 안되신다면 위에 공개회로도를 링크 걸어 놓았어요. 가셔서 실제로 실행 시켜보셔서 확인하시면 됩니다. 시뮬레이더 실행 키 누르고 코딩창 열고 코딩창 하단에 시리얼모니터 아이콘을 눌러서 창을 개방시키고 조정 키값을 입력하시면 회로도의 DC기어모터가 조정 키값에 따른 회전명령을 수행합니다.

마무리


실제로 아두이노 RC카 부품을 구매해서 직접 제작하지 않더라도 간접적으로 이렇게 가상시뮬레이터에서 실험을 할 수 있습니다. 실제로 주행하는 모습은 볼 수 없지만 DC Motor 회전만으로도 간접적으로 회전의 원리를 체험할 수 있습니다.

실험을 하실 때 Post [아두이노] 아두이노 2륜 RC카 주행 패턴 실험 에서 그림으로 주행 패턴을 설명했는데 그 그림을 보시고 가상시뮬레이터의 DC기어모터 회전을 합쳐진 이미지를 머리속에서 상상하시면서 오늘 post의 내용을 보시면 대충 상상속에서 주행하는 모습이 이미지로 그려질 거라 생각합니다.

이렇게 해서 아두이노 RC카를 만들고 Bluetooth로 무선 조정하고 마무리로 가상시뮬레이터로 표현까지 해서 꽤 긴 Post를 마무리 합니다. 사실 간단하게 이렇게조립하세요. 소스코딩은 이렇게하면 Bluetooth로 조정이 돼요. 하면 1~2일 post 주제로 끝낼 수 있었지만 아두이노 RC카의 만드는 것보다 만드는 과정을 보여드리고 싶었기 때문에 post가 길어졌네요.

마지막으로 아두이노 RC카의 주행 패턴도 알았고 조정하는 방법까지 알았습니다. RC카의 움직임을 원하는 형태로 여러분들은 할 수 있게 되었습니다. 즉, 어떤 상황이 되었을 때 여러분들이 아두이노 RC카를 특정한 패턴의 움직임을 보이도로 설계가 가능하다는 이야기가 됩니다. 쉽게 말해서, 이 의미는 아두이노 RC카를 상황에 맞게 주행할 수 있도록 자율주행 설계가 가능하다는 의미가 됩니다. 예를들면, 아두이노 RC카 앞에 장애물이 발견되면 그 장애물을 피해서 주행을 자동으로 할 수 있게 할 수 있다는 것이죠.

여러분들이 상상력을 어디까지 끌어 올리느냐에 따라서, 아두이노 RC카 자율주행을 단순하게 또는 정교하게 표현을 가능해 집니다. 한번 아두이노 RC카 자율주행에 대해서 상상의 나래를 펼쳐 보세요.

댓글()

[아두이노] 아두이노 RC카를 조정하는 다양한 코딩 접근법

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

[아두이노] 아두이노 RC카를 조정하는 다양한 코딩 접근법


지난시간까지 해서 간단히 아두이노 2륜 RC카를 조립하고 주행패턴을 분석한 뒤에 Bluetooth를 연결하여 스마트폰으로 조정하는 것 까지 살펴 보았습니다. 오늘은 지난시간에 Bluetooth로 RC카를 조정하는 소스를 가지고 다양한 접근을 시도하고자 합니다. 코딩은 딱 하나의 정해진 로직에 의한 코딩으로 이루어지지 않습니다. 비슷하면서도 다양한 코딩이 존재 합니다. 바로 프로그래머의 주관적 상상에 의해 코딩의 로직이 표현 됩니다. 제가 코딩하는 방식은 제 나름대로의 상상의 의한 코딩입니다. 그렇기 때문에 RC카를 조정하는 코딩은 여러분들이 직접 상상하여 코딩하시면 제가 했던 방식과 다르게 표현 될 수 있습니다. 어떤 회전을 하고 어떤식으로 명령어를 처리해서 DC Motor를 회전 시킬지는 여러분들의 상상력에 달린 것이죠.

오늘은 제가 어떤 상상을 하고 코딩을 했을 때 그 하나의 코딩에 머물지 않고 다양한 방식으로 접근하는 코딩을 설명할까 합니다. 제가 처음 코딩을 입문했을 때 부터 고수해 온 코딩법 입니다. 하나의 결과물을 만들어 내면 거기서 멈추지 않고 유사한 표현이나 다른 접근법을 통해 둘 이상의 결과물이 만들어지도록 코딩 합니다. 그래서인지 대학시절에 조별 프로젝트를 만들면 꼭 2개이상의 결과물을 만들어 제출 했던 기억이 나네요. 저는 할수 있는 코딩만 하고 그 코딩을 통해 표현할 수 있는 최대의 방향으로 코딩을 표현 합니다.

아무튼, 이야기가 삼천포로 좀 빠졌지만 RC카를 조정하는 코딩을 다양하게 접근해 보겠습니다.


1. 2륜 RC카 + Bluetooth 조정 기본 코딩(Switch문)


void loop() {
  if (mySerial.available()){
    char ch = mySerial.read();
        처리문;
  }
}

우선 Bluetooth로 통신을 받는 기본 틀 소스입니다. 처리문은 통신을 통해서 읽은 값을 통해 RC카를 처리하는 위치입니다. 그 위치에서 Switch문을 통해서 명령을 수행하는 소스가 지난시간에 만든 아래 소스입니다.

#include <AFMotor.h>
#include <SoftwareSerial.h>

const int rxPin = A0;
const int txPin = 2;

SoftwareSerial mySerial(rxPin, txPin); // RX, TX

AF_DCMotor motor1(3);
AF_DCMotor motor2(4);
int speed =200;

void setup() {
  mySerial.begin(9600);   
  motor1.setSpeed(speed);
  motor2.setSpeed(speed);
  motor1.run(RELEASE);
  motor2.run(RELEASE);
}

void loop() {
  if (mySerial.available()){
    char ch = mySerial.read();

    switch(ch){
      case 'w': //전진
            motor1.run(FORWARD);
            motor2.run(FORWARD);
            break;
      case 's': //후진
            motor1.run(BACKWARD);
            motor2.run(BACKWARD);
            break;
      case 'a': //제자리 좌회전
            motor1.run(BACKWARD);
            motor2.run(FORWARD);
            break;
      case 'd': //제자리 우회전
            motor1.run(FORWARD);
            motor2.run(BACKWARD);
            break;
      case 'z': //정지
            motor1.run(RELEASE);
            motor2.run(RELEASE);
            break;
      case 'o': //좌회전
            motor1.run(RELEASE);
            motor2.run(FORWARD);
            break;
      case 'p': //우회전
            motor1.run(FORWARD);
            motor2.run(RELEASE);
            break;            
      case 'm': //속도 증가
            speed+=10;
            if(speed>=250) speed=250;
            motor1.setSpeed(speed);
            motor2.setSpeed(speed);
            break;
      case 'n': //속도 감소
            speed-=10;
            if(speed<=0) speed=0;
            motor1.setSpeed(speed);
            motor2.setSpeed(speed);
            break;
    }      
  }
}

지난시간의 코딩한 이 소스에서 간단히 전진/후진, 좌/우회전, 정지 이렇게 5가지 동작만을 코딩으로 간단히 표현 한뒤에 그 소스를 기반으로 연습하셔도 됩니다. 저는 그냥 지난 소스를 기반으로 다른 방식으로 접근하는 코딩을 하겠습니다.

2. IF~Else IF 문


IF~Else IF문은 기본적으로 Switch문과 유사한 동작을 수행합니다.

IF (조건식1) 명령1;
Else IF(조건식2) 명령2;
Else IF(조건식3) 명령3;
...

이런 구조로 되어 있습니다. Bluetooth 통신으로 읽은 값이 조건식1에 만족하면 명령1의 RC카 주행을 하고 만약 조건식1에 만족하지 않으면 다음 라인 조건식2에 만족하는지 체크하고 만족하면 명령2의 RC카 주행을 합니다. 이런식으로 하나씩 비교해서 만족하는 명령의 RC카 주행을 하게 됩니다.

Switch문을 IF~Else IF으로 수정만 하시면 됩니다.

#include <AFMotor.h>
#include <SoftwareSerial.h>

const int rxPin = A0;
const int txPin = 2;

SoftwareSerial mySerial(rxPin, txPin); // RX, TX

AF_DCMotor motor1(3);
AF_DCMotor motor2(4);
int speed =200;

void setup() {
  mySerial.begin(9600);   
  motor1.setSpeed(speed);
  motor2.setSpeed(speed);
  motor1.run(RELEASE);
  motor2.run(RELEASE);
}

void loop() {
  if (mySerial.available()){
    char ch = mySerial.read();

    if (ch == 'w') {
      motor1.run(FORWARD);
      motor2.run(FORWARD);
    } 
    else if (ch == 's') {
      motor1.run(BACKWARD);
      motor2.run(BACKWARD);
    } 
    else if (ch == 'a') {
      motor1.run(BACKWARD);
      motor2.run(FORWARD);
    } 
    else if (ch == 'd') {
      motor1.run(FORWARD);
      motor2.run(BACKWARD);
    }
    else if (ch == 'z') {
      motor1.run(RELEASE);
      motor2.run(RELEASE);
    }
    else if(ch =='o'){
      motor1.run(RELEASE);
      motor2.run(FORWARD);
    }
        else if(ch == 'p'){
      motor1.run(FORWARD);
      motor2.run(RELEASE);
    }
    else if (ch == 'm') {
      speed+=10;
      if(speed>=250) speed=250;
      motor1.setSpeed(speed);
      motor2.setSpeed(speed);
    }
    else if (ch == 'n') {
      speed-=10;
      if(speed<=0) speed=0;
      motor1.setSpeed(speed);
      motor2.setSpeed(speed);
    }
  }
}

이렇게 IF~Eles IF문으로 표현을 합니다.

3. 배열로 동작 명령 제어


다음으로는 배열로 동작 명령을 등록해 놓고 Bluetooth로 수신한 명령 키값을 등록된 배열 명령 키값과 비교하여 일치한 키값에 대한 명령을 수행하는 코딩입니다. 중복된 코딩을 줄이고자 표현한 방식입니다.

이전 시간에 주행 패턴을 만들 때 배열로 했던 기억이 나실지 모르겠네요. 유사합니다. Bluetooth 앱에 조정버턴의 등록된 키 값을 문자열로 등록 합니다.

char stringVal[]="wsadzop"

전진/후진(w/s), 제자리 좌/우회전(a/d), 정지(z), 좌회전/우회전(o/p), 속도 증가/감소(m/n) 입니다. 참고로 속도는 배열에서 제외했습니다.

다음으로는 Motor 패턴 배열입니다. 즉, 두개의 Motor 회전 명령 FORWARD(1), BACKWARD(2), RELEASE(4)의 값을 위의 키값의 순서대로 배열에 위치에 Motor 회전 명령값을 저장합니다.

byte movePattern1[7]={1,2,1,2,4,1,4}; //motor1 Pattern
byte movePattern2[7]={1,2,2,1,4,4,1}; //motor2 Pattern

이렇게 하면, 배열 키값의 index랑 Motor 회전의 index은 같은 위치가 됩니다. 즉, Bluetooth로 수신된 키값이 StringVal[]의 값과 일치한 index(위치)값을 알게 되면 해당 index(위치)값의 Motor 패턴으로 회전 시키면 간단히 키값에 따른 회전 명령을 내릴 수 있게 됩니다.

  if (mySerial.available()){
    char ch = mySerial.read();
        처리문;
  }

에서,

      indexVal = 4;
      for(int i=0;i<7;i++){         
         if(ch==stringVal[i]) indexVal=i;
      }

이렇게 ch값이 순차적으로 StringVal[i]와 비교해서 일치하면 indexVal에 해당 일치한 i값을 저장하게 됩니다. 일치한 키값에 Motor 회전 명령을 수행하기 위해서 index(위치)를 찾는 문장이라고 생각하시면 됩니다. 참고로, for문에 들어가기 전 초기값이 "index=4"인 이유는 4는 정지 명령입니다. 일치한 값이 없을 경우 초기값으로 0을 두면 무조건 전진을 하겠죠. 일치한 동작만 수행하기 위해서는 일치하지 않았을 때 정지해야 겠죠. 그렇게 때문에 정지 위치인 4가 초기값으로 세팅됩니다. 만약 스마트폰에서 다른 알파펫이 들어왔을 때는 일치하지 않기 때문에 정지상태로 머물게 됩니다. 왜! 이렇게 선언했는지 아시겠지요.
만약, index(위치)의 0의 위치에 정지 명령을 넣어두시면 "index=0"으로 초기값을 선언해도 됩니다.

      indexVal = 4;
      for(int i=0;i<7;i++){      
         if(ch==stringVal[i]) indexVal=i;
      }
       motor1.run(movePattern1[indexVal]);
       motor2.run(movePattern2[indexVal]);

이렇게 표현하면, Bluetooth로 수신된 ch값과 일치한 StringVal[i]값의 index를 찾고 그 index의 위치인 movePattern1[index]와 movePattern2[index]의 Motor 회전 명령을 수행하면 키값에 회전 명령을 수행하게 됩니다.

여기까지, 내용은 Bluetooth로 수신된 키값을 회전 비교문이였다면 이제는 속도부분을 처리할 코딩을 작성해야 합니다. 속도는 키값등록 배열에서 제외 했습니다. 그렇기 때문에 Bluetooth에서 수신한 할때 개별적으로 체크해야 합니다. 다음 아래와 같은 형식으로 코딩을 하면 됩니다. Bluetooth 통신에 의해 수신된 ch값이 'm'과 'n' 키와 일치하냐고 묻고 일치하지 않으면 else 이하문으로 해서 방금 위에서 코딩한 방향키 indexVal를 찾는 로직을 수행하면 됩니다.

    if (ch == 'm') {
      speed+=10;
      if(speed>=250) speed=250;
      motor1.setSpeed(speed);
      motor2.setSpeed(speed);
    }
    else if (ch == 'n') {
      speed-=10;
      if(speed<=0) speed=0;
      motor1.run(BACKWARD);
      motor2.run(FORWARD);
    }
    else{
      indexVal = 4;
      for(int i=0;i<7;i++){      
         if(ch==stringVal[i]) indexVal=i;
      }
       motor1.run(movePattern1[indexVal]);
       motor2.run(movePattern2[indexVal]);
     }

속도 내부 로직은 지난시간에 설명을 했기 때문에 생략합니다.

종합해보면,

#include <AFMotor.h>
#include <SoftwareSerial.h>

const int rxPin = A0;
const int txPin = 2;

SoftwareSerial mySerial(rxPin, txPin); // RX, TX

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

int speed = 200;
int indexVal = 4;
char stringVal[]="wsadzop"
byte movePattern1[7]={1,2,1,2,4,1,4}; //motor1 Pattern
byte movePattern2[7]={1,2,2,1,4,4,1}; //motor2 Pattern

void setup() {
  mySerial.begin(9600);   
  motor1.setSpeed(speed);
  motor2.setSpeed(speed);
  motor1.run(RELEASE);
  motor2.run(RELEASE);
}

void loop() {
  if (mySerial.available()){
    char ch = mySerial.read();
    if (ch == 'm') {
      speed+=10;
      if(speed>=250) speed=250;
      motor1.setSpeed(speed);
      motor2.setSpeed(speed);
    }
    else if (ch == 'n') {
      speed-=10;
      if(speed<=0) speed=0;
      motor1.run(BACKWARD);
      motor2.run(FORWARD);
    }
    else{
      indexVal = 4;
      for(int i=0;i<7;i++){      
         if(ch==stringVal[i]) indexVal=i;
      }
       motor1.run(movePattern1[indexVal]);
       motor2.run(movePattern2[indexVal]);
     }
  }
}

코딩이 엄청 간소화 되었죠. 위에 배열부분은 원하는 형태로 수정하면은 loop()함수를 건들 필요가 없어 수정이 가능합니다.

4. 스마트폰으로 방향키를 누른 상태에서만 동작


스마트폰 앱에서 누르고 있는 동안에만 그 방향으로 회전하고 손을 때면 RC카가 멈추는 것으로 표현하고 싶다면 어떻게 해야 할까요. 키가 누르고 있는 동안에는 그 키 방향으로 RC카가 회전해야 하고 키를 누르지 않으면 정지해 있어야 한다면 어떤식으로 코딩을 하면 좋을까요.

지난시간에 Bluetooth 통신을 통한 조정을 하면서 문득 이런 경우에는 어떻게 제어를 하지 하고 떠오른 상상을 통해서 코딩을 수정해 보았습니다.

예전에 RFID 리더기 코딩을 하면서 카드를 RFID에 대기 전까지 계속 if문으로 무한 체크를 하는 문장이 떠오르더군요.

 if (mySerial.available()){
   처리문;
   return ;
 }
  motor1.run(RELEASE);
  motor2.run(RELEASE);

그냥 이런표현을 하면 되지 않을까 해서 코딩을 해 보았습니다. 즉, 키가 입력되면 처리문을 수행하고 loop()함수문을 빠져나오고 다시 if문으로 Bluetooth의 수신 명령이 있는지 체크하게 하면 되지 않을까 하고 상상을 했습니다. 이렇게 Bluetooth 통신을 통해 수신된 데이터가 없으면 DC 기어모터는 RELEASE로 정지 상태가 되고 수신데이터가 있으면 처리문으로 해당 회전 명령을 수행하게 하면 된다는 상상을 하게 되었네요.

하지만 이렇게 하면 너무 짧은 시간의 찰라에 전진/후진, 좌/우회전을 하고 바로 정지해버리기 때문에 사실 아무런 동작을 수행하지 않게 됩니다. 그래서, 약간의 DC 기어모터가 회전할 딜레이 시간을 주었습니다. 즉, Bluetooth를 통해 전송되는 키값의 딜레이 시간이라 아두이노에서 수신해서 DC 기어모터를 회전 시키는 딜레이시간을 적절이 조절하여 계속 회전 명령을 키를 누를때마다 수행되게 하고 누르지 않으면 정지상태로 되도록 변경했습니다.

참고로, 제가 깐 Bluetooth 앱은 누르고 있으면 연속해서 해당 키값을 전송하지 않기 때문에 일부러 딜레이 시간을 100으로 주고 테스트 했습니다.

#include <AFMotor.h>
#include <SoftwareSerial.h>

const int rxPin = A0;
const int txPin = 2;

SoftwareSerial mySerial(rxPin, txPin); // RX, TX

AF_DCMotor motor1(3);
AF_DCMotor motor2(4);
int speed =200;

void setup() {
  mySerial.begin(9600);   
  motor1.setSpeed(speed);
  motor2.setSpeed(speed);
  motor1.run(RELEASE);
  motor2.run(RELEASE);
}

void loop() {
  if (mySerial.available()){
    char ch = mySerial.read();

    switch(ch){
      case 'w':
            motor1.run(FORWARD);
            motor2.run(FORWARD);
            break;
      case 's':
            motor1.run(BACKWARD);
            motor2.run(BACKWARD);
            break;
      case 'a':
            motor1.run(BACKWARD);
            motor2.run(FORWARD);
            break;
      case 'd':
            motor1.run(FORWARD);
            motor2.run(BACKWARD);
            break;
    }      
    delay(100);
    return ;
  }
  motor1.run(RELEASE);
  motor2.run(RELEASE);
}

이렇게 키를 한번 누를때마다 0.1초동안 회전하게 되는 것이죠. 이것은 Bluetooth 앱이 연속 키를 전달하지 못하기 때문에 회전의 시간을 좀 길게 잡기 위해서 0.1초동안으로 회전을 강제적으로 했지만 연속으로 보낼 수 있는 앱이나 조이스틱이면 delay 시간값을 조정해 주세요. 0.1초도 긴 시간입니다. 1초동안에 300번의 'w'키값이 들어왔다면 1초동안 회전하는게 아니라 3초동안 회전하게 됩니다. 그러면 조정에 문제가 있겠죠. 보내는 시간과 읽는 시간에 대한 회전 딜레이를 조정하시면 원하는 RC카 조정이 이뤄집니다.

[ 결과 ]


키를 누를때 0.1동안 회전시키게 한 RC카 조정하는 영상입니다.


마무리


오늘은 코딩 이야기만 했네요. 하나의 코딩을 했다면 거기서 멈추지 말고 이렇게 상상을 계속 하시면 여러 방법으로 동일한 표현을 할 수 있습니다. 구지 동일한 표현을 다른 방식으로 코딩 할 필요가 있냐고 생각하실 수 있지만 이것은 상상코딩에서 무척 중요한 부분입니다.
코딩하는 사람들은 하나의 상상으로 결과를 얻게 되면 더이상 상상하려 하지 않습니다. 그렇게 되면 하나의 틀에 갇히게 됩니다. 코딩에서는 상상은 무척 중요합니다. 하나의 상상의 틀에 갇히면 발전은 없고 단지 엔지니어가 될 뿐이죠. 계속 이방식으로 코딩해보고 저방식으로 코딩하면서 코딩의 영역을 넓혀가시고 자신만의 색체을 지닌 코딩을 만들어 내셔야 코딩의 성장을 이룹니다.
제가 post하는 소스 코딩은 그냥 따라만 하면 제 코딩 스타일에 여러분들은 갇히게 됩니다. 자신의 코딩 스타일을 찾지 못하게 되는 것이죠. 이방법, 저방법을 시도하면서 자신만의 코딩 스타일을 만들어 내는 것이 무척 중요하니깐 꼭 여러가지 방법으로 접근해 보시면서 자신만의 스타일을 만드셨으면 합니다.


댓글()

[아두이노] 2륜 RC카 Bluetooth를 통해 스마트폰(무선) 조정하기

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

[아두이노] 2륜 RC카 Bluetooth를 통해 스마트폰(무선) 조정하기



지난시간에는 아두이노 2륜 RC카의 주행 패턴을 실험을 해 보았습니다. 오늘은 실험한 주행 패턴을 가지고 Bluetooth로 한번 조정하는 실험을 하겠습니다. 스마트폰에서 Bluetooth 앱을 미리 깔아놓고 그 앱을 통해서 아두이노 2륜 RC카에 부착된Bluetooth로 명령을 내리고 RC카를 조정하게 됩니다. 아래 사진은 아두이노 RC카에 Bluetooth를 연결한 모습입니다.


1. 2륜 RC카 + Bluetooth 회로도


  • 준비물 : Bluetooth, DC 기어모터 2개, L293D Motor Shield, AAx4개 배터리 케이스 배터리 홀더(6V), 아두이노우노

지난시간의 2륜 RC카를 회로도에서 Bluetooth 부품만 추가 연결은 아래와 같습니다.


제가 사용한 L293D Motor Shield에는 따로 Bluetooth를 연결할 수 있는 핀이 없습니다. 대부분 0,1번핀을 이용하여 Serial 통신을 합니다. 전선으로 0, 1번핀을 아두이노와 쉴드 사이에 선을 중간에 묶거나 납탬하는 방법뿐이 없습니다. 대부분 이런식으로 실험을 하지만 자세히 L293D Motor Shield의 기판을 보시면 2번 핀에 구멍이 있고 A0~A5라는 위치에 구명이 뚫려 있습니다. 즉, 핀을 꼽을 수 있는 위치에 핀구멍이 있는데 이 핀구멍을 이용하면 Bluetooth를 쉽게 이용할 수 있습니다.

BlueTooth은 Rx, Tx 핀으로 구성되는데 읽는 핀과 출력 핀으로 나뉩니다. 즉, 2번핀은 디지털핀으로 출력이 가능하고 A0~A5핀은 아날로그핀으로 입력이 가능합니다.

BlueTooth Tx -> Motor Shield A0
BlueTooth Rx -> Motor Shield 2

이렇게 핀 구멍을 활용하면 Bluetooth 통신을 따로 선을 납땜을 할 필요 없이 연결 할 수 있습니다.


2, A0 Pin 위치는 위 사진에서 확인하시고 어느 위치인지 잘 기억해 두세요. Vcc, Gnd pin 위치도 기판에 써있으니깐 보시면 쉽게 찾을 수 있을 꺼에요.

2. 코딩


DC Motor

#include <AFMotor.h>
  • AF_DCMotor motor(3) : M3핀을 DC Motor 제어용으로 사용.
  • motor.setSpeed(200) : 모터 속도 200으로 설정
  • motor.run(FORWARD) : FORWARD, BACKWARD, RELEASE 회전 명령 중 하나를 선택해서 실행 시킴.

SoftwareSerial 통신

#include <SoftwareSerial.h>
  • SoftwareSerial mySerial (rx, tx) : 소프트시리얼 객체선언(rx(수신), tx(전송))
  • mySerial.begin(9600) : 시리얼 통신 시작(예로 9600 통식속도를 사용해 봤네요.)
  • mySerial.write(값) : 데이터 전송
  • mySerial.available() : 데이터 들어왔는 확인
  • mySerial.read() : 전송된 데이터 1byte 읽기

복습으로, Adafruit Industries에서 제공해주는 Motor 라이브러리 함수를 다시 post에 담았습니다. 중요한 부분이니깐 다시 살펴봐 주세요. 그리고 아두이노 내부 시리얼통신 0,1번 핀으로 실험하는게 아니라 다른 핀을 활용하기 때문에 SoftwareSerial 통신 부분도 다시 복습해 주세요.

1) Bluetooth 연결

#include <SoftwareSerial.h>

const int rxPin = A0;
const int txPin = 2;

SoftwareSerial mySerial(rxPin, txPin); // RX, TX

이렇게 해서 SoftwareSerial 통신 객체를 만들었습니다.

void setup() {
  mySerial.begin(9600);   
}

setup()함수안에다 begin()함수로 9600 통신속도로 통신을 시작합니다.

void loop() {
  if (mySerial.available()){
    char ch = mySerial.read();
  }
}

이렇게 loop()함수에서 bluetooth을 통해 들어온 데이터을 읽게 됩니다. available() 함수를 통해 수신 데이터가 있는지 체크합니다. 수신 데이터가 있게 되면 read()함수로 1byte을 읽게 되는데 char 자료형으로 한 문자를 읽어서 저장하게 됩니다.

이미 예전에 다뤘던 내용인데 혹시 잊으신 분들이 있을 수 있기 때문에 다시 설명을 드립니다.

[0,1번 시리얼통신을 할 경우]
따로, SoftwareSerial 라이브러리를 이용하실 필요는 없습니다.

void setup() {
  Serial.begin(9600);   
}
void loop() {
  if (Serial.available()){
    char ch = Serial.read();
  }
}

이렇게 기본 Serial 통신을 이용하시면 됩니다. 0,1번 Pin을 이용할 경우에 이렇게 하고 다른 Pin을 이용할 경우는 SoftwareSerial 라이브러리를 이용하셔야 합니다.

2) 스마트폰에 Bluetooth 앱 설정


안드로이드폰이면 구글스토어에서 Bluetooth 앱을 치시면 아무거나 적당한 것을 다운로드 받아서 설치하시면 됩니다.

앱에서 Controller mode를 선택합니다.



위 그림처럼 버턴에 대한 키값을 지정합니다. 오른쪽 상단에 환경설정 아이콘을 누르시면 아래 창이 뜹니다.


빈칸에 키값을 위에 표시한 알파벳을 등록하시면 됩니다. 그러면 스마트폰에서의 모든 설정은 끝납니다.

3) Bluetooth를 통해 읽은 데이터 값을 통한 동작 제어

Bluetooth를 통해 수신된 데이터 값을 통해서 RC카를 조정하도록 하겠습니다.

전진/후진, 좌/우 명령을 수행한다면 스마트폰 등록된 키값 알파벳 문자들이 수신되면 그 값에 따라서 동작명령을 내려야 합니다.

void loop() {
  if (mySerial.available()){
    char ch = mySerial.read();
  }
}

위 로직에서 if문 안에 수신된 키값에 대한 Motor를 제어하는 명령을 표현 해야 겠죠.

지난 시간 post에서 주행 패턴을 전부 삽입했습니다. 전진/후진과 좌/우만 넣어도 되지만 그냥 실험한 김에 전부 넣었습니다. 여러분들은 전진/후진과 좌/우와 정지 명령만 표현하셔서 실험하세요. 구지 아래처럼 전부 다 넣어서 실험하실 필요는 없습니다.

    switch(ch){
      case 'w':
            motor1.run(FORWARD); //전진
            motor2.run(FORWARD);
            break;
      case 's':
            motor1.run(BACKWARD); //후진
            motor2.run(BACKWARD);
            break;
      case 'a':
            motor1.run(BACKWARD); //제자리 좌회전
            motor2.run(FORWARD);
            break;
      case 'd':
            motor1.run(FORWARD); //제자리 우회전
            motor2.run(BACKWARD);
            break;
      case 'z':
            motor1.run(RELEASE); //정지
            motor2.run(RELEASE);
            break;
      case 'o':
            motor1.run(RELEASE); //좌회전
            motor2.run(FORWARD);
            break;
      case 'p':
            motor1.run(FORWARD); //우회전
            motor2.run(RELEASE);
            break;            
      case 'm':                  //속도증가
            speed+=10;
            if(speed>=250) speed=250;
            motor1.setSpeed(speed);
            motor2.setSpeed(speed);
            break;
      case 'n':                  //속도감소
            speed-=10;
            if(speed<=0) speed=0;
            motor1.setSpeed(speed);
            motor2.setSpeed(speed);
            break;
    }      

위 코딩을 보시면 속도 증가와 감소가 있는데 이 부분은 실제 변화하는 수치는 실험에 사용하는 앱에서는 확인할 수 없어서 불편합니다. 따로 앱인벤터로 전용 Bluetooth를 만들면 되는데 그냥 생략 합니다.

코딩 명령부분만 살펴보면은

speed+=10;
if(speed>=250) speed=250;

예전 post에 표현했던 방법인데 speed를 무조건 10씩 증가시키고 그다음 if문에서 max값으로 해서 그 이상 값이 나오면 max로 무조건 speed 값으로 고정시키는 표현입니다. 그러면 반대로 10씩 감소하면 if문으로 min값 이하 값이 되면 무조건 speed값을 min값으로 고정시켠 되겠죠.

speed-=10;
if(speed<=0) speed=0;

이렇게 어떤 값의 범위을 벗어날려고 하면 min or max 값으로 고정화 시키는 표현은 자주 사용하는 표현이니깐 잘 기억해 두세요. 다른 곳에서 이 표현을 사용할 수 있으니깐요.

4) 종합 소스

지난 시간의 주행 패턴을 Bluetooth 통신을 한다면 다음과 같습니다.

#include <AFMotor.h>
#include <SoftwareSerial.h>

const int rxPin = A0;
const int txPin = 2;

SoftwareSerial mySerial(rxPin, txPin); // RX, TX

AF_DCMotor motor1(3);
AF_DCMotor motor2(4);
int speed =200;

void setup() {
  mySerial.begin(9600);   
  motor1.setSpeed(speed);
  motor2.setSpeed(speed);
  motor1.run(RELEASE);
  motor2.run(RELEASE);
}

void loop() {
  if (mySerial.available()){
    char ch = mySerial.read();

    switch(ch){
      case 'w':
            motor1.run(FORWARD);
            motor2.run(FORWARD);
            break;
      case 's':
            motor1.run(BACKWARD);
            motor2.run(BACKWARD);
            break;
      case 'a':
            motor1.run(BACKWARD);
            motor2.run(FORWARD);
            break;
      case 'd':
            motor1.run(FORWARD);
            motor2.run(BACKWARD);
            break;
      case 'z':
            motor1.run(RELEASE);
            motor2.run(RELEASE);
            break;
      case 'o':
            motor1.run(RELEASE);
            motor2.run(FORWARD);
            break;
      case 'p':
            motor1.run(FORWARD);
            motor2.run(RELEASE);
            break;            
      case 'm':
            speed+=10;
            if(speed>=250) speed=250;
            motor1.setSpeed(speed);
            motor2.setSpeed(speed);
            break;
      case 'n':
            speed-=10;
            if(speed<=0) speed=0;
            motor1.setSpeed(speed);
            motor2.setSpeed(speed);
            break;
    }      
  }
}

지난시간의 패턴을 전부 키에 등록시켜서 실험하기 때문에 switch()문이 꽤! 길게 코딩이 되었네요.

간단히, 동작 FORWARD, BACKWARD, RELEASE 기준으로 Bluetooth 통신으로 읽은 ch(키값)에 해당된 모터 동작 명령을 내리면 됩니다. 주행 패턴이 지난시간에 실험한 것들과 속도까지 컨트롤 하다보니깐 코딩이 길어졌을 뿐 원리는 아래 코딩이 전부입니다.

switch(ch){
 case '키값1':  
            motor1.run(동작1);
            motor2.run(동작1);
            break;
 case '키값2':  
            motor1.run(동작2);
            motor2.run(동작2);
            break;  
}

이 코딩만 이해하시면 됩니다. switch문에서 인수 ch값과 같은 case을 찾고 그 케이스의 라인아래로 명령문들이 수행되는데 break 문을 만나면 switch문을 빠져나오는 로직입니다. 즉, ch값이 키값1이면 motor1.run(동작1)과 motor2.run(동작1) 함수를 수행한뒤에 다음 라인 break문을 만나 switch문을 빠져나오게 됩니다. 만약 동작1을 수행한 뒤에 break문이 없으면 두번째 케이스의 동작2을 수행하게 됩니다. break문이 switch문에서 꼭 필요하니깐 switch문에서 실수하지 말아주세요.

3. 결과


스마트폰으로 조정하면 이렇게 주행을 할 수 있게 됩니다. 이 주행이 정석은 아닙니다. 여러가지 표현 중 하나이고 그 하나를 그냥 소개하는 것일 뿐 다른식으로 조정을 하고 싶다면 한번 코딩에 도전 해보는 것도 괜찮습니다.


마무리


지난 시간의 주행패턴을 전부 사용하다 보니깐 코딩이 길어졌을 뿐 전진/후진, 좌/우 키와 Stop 키로 구성된 5개 키값 만으로 테스트 하셔도 됩니다. 제가 코딩한 것처럼 전부 하실 필요는 없습니다. 좌/우회전 패턴이 2종류인데 원하는 한 종류만 조정값으로 선택하셔도 됩니다.
참고로, speed 코딩이 추가되었는데 어떤식으로 코딩했는지 기억하셨다가 이 원리는 여기뿐 아니라 다른곳에서도 활용이 자주 되는 코딩이라서 원리를 꼭 기억해 주세요.

그렇게까지 어렵지 않습니다. Bluetooth 앱도 구글 검색에서 "앱인벤터 Bluetooth" 키워드로 찾으시면 유튜브 동영상 강좌나 블로그 같은 곳에서 튜토리얼로 잘 나와 있으니깐 따라서 만드시면서 자신만의 Bluetooth 앱으로 개조하셔도 됩니다.

http://ai2.appinventor.mit.edu

이곳에 가셔서 구글계정으로 로그인하면 됩니다. 그리고, 검색하셔서 튜토리얼을 따라서 하시면 됩니다.

이제 여기까지 해서 Bluetooth로 아두이노 RC카를 무선 조정까지 하였습니다. 이제 아두이노 RC카에 어떤 부품을 연결하여 재밌는 것을 만들지 한번 상상의 나래를 펼쳐보세요.


댓글()

[아두이노] 아두이노 2륜 RC카 주행 패턴 실험

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

[아두이노] 아두이노 2륜 RC카 주행 패턴 실험 


지난시간에는 아두이노 2륜 RC카 조립과 시험 주행을 해보았습니다. 이제 본격적으로 주행 패턴을 만들어서 직접 만든 RC카의 주행 동작이 구체적으로 어떤식으로 움직이는지 살펴 볼 차례입니다. 그냥 Bluetooth로 DC Motor 회전의 방향만 조장하는 스마트폰 조정을 바로 할 수 있지만 그전에 자신이 만든 RC카가 어떤식으로 움직이는지 먼저 알아야 합니다. 나중에 직접 조정을 하거나 자율주행을 할 때 꼭 필요한 사전 지식입니다. RC카의 움직임을 미리 알아 두시면 어떤 상황이 될 때 전진 후진, 좌회전, 우회전, 정지 등의 패턴을 쉽게 만들어 낼 수 있게 됩니다. 꼭 필요한 실험이니깐 RC카가 이런식으로 주행 패턴을 만들어 내는구나 하고 배웠으면 합니다.

이제 본격적으로 주행 패턴을 만들어 보도록 하겠습니다.


1. 2륜 RC카 회로도


  • 준비물 : DC 기어모터 2개, L293D Motor Shield, AAx4개 배터리 케이스 배터리 홀더(6V), 아두이노우노

실제 2륜 RC카를 회로도로 살펴보면 아래 그림과 같습니다. 참고용으로 살펴보시기 바랍니다.


2. 아두이노 2륜 RC카 주행 패턴 만들기


DC Motor 2개를 제어하기 하기 위한 함수 motor1.run(), motor2.run() 로 FORWARD, BACKWARD. RELEASE 의 명령을 통해서 주행 패턴이 만들어 낼 수 있습니다.

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

위 motor 기준으로 왼쪽 모터는 3번 오른쪽 모터는 4번으로 했을 때 다음과 같습니다.

1) 전진과 후진


전진

  motor1.run(FORWARD);
  motor2.run(FORWARD);

후진

  motor1.run(BACKWARD);
  motor2.run(BACKWARD);

2) 좌회전과 우회전


좌회전

  motor1.run(RELEASE);
  motor2.run(FORWARD);

우회전

  motor1.run(FORWARD);
  motor2.run(RELEASE);

3) 제자리 좌회전과 우회전


좌회전

  motor1.run(BACKWARD);
  motor2.run(FORWARD);

우회전

  motor1.run(FORWARD);
  motor2.run(BACKWARD);

4) 정지

  motor1.run(RELEASE);
  motor2.run(RELEASE);

3. 코딩


DC Motor

#include <AFMotor.h>
  • AF_DCMotor motor(3) : M3핀을 DC Motor 제어용으로 사용.
  • motor.setSpeed(200) : 모터 속도 200으로 설정
  • motor.run(FORWARD) : FORWARD, BACKWARD, RELEASE 회전 명령 중 하나를 선택해서 실행 시킴.

Adafruit Industries에서 제공해주는 Motor 라이브러리를 이용하면 setSpeed(), run() 함수로 쉽게 DC Motor를 움직이게 할 수 있습니다.

위에서 주행 패턴에 대해서 간단히 살펴보았습니다. 그 동작을 실제로 코딩해서 실험을 해보도록 할까요.

[ 소스 ]

#include <AFMotor.h>

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

void setup() {
  motor1.setSpeed(200);
  motor2.setSpeed(200);
  motor1.run(RELEASE);
  motor2.run(RELEASE);
}

void loop() {  
  motor1.run(FORWARD); //전진
  motor2.run(FORWARD);
  delay(2000);
  
  motor1.run(BACKWARD); //후진
  motor2.run(BACKWARD);
  delay(2000);
 
  motor1.run(RELEASE); //정지
  motor2.run(RELEASE);
  delay(2000);

  motor1.run(RELEASE); //좌회전
  motor2.run(FORWARD);
  delay(2000);

  motor1.run(FORWARD); //우회전
  motor2.run(RELEASE);
  delay(2000);

  motor1.run(BACKWARD); //제자리 좌회전
  motor2.run(FORWARD);
  delay(2000);
    
  motor1.run(FORWARD); //제자리 우회전
  motor2.run(BACKWARD);
  delay(2000);
}

주의사항

위 사진을 보면 빨간색으로 표시한 부분이 있는데 덮개처럼 되어 있습니다. 아두이노우노에 프로그램을 업로드 할 때 이 덮개를 빼주세요. 덮개가 그대로 되어 있으면 아두이노우노에서 전원이 공급되는데 Motor Shield에 그대로 전원이 공급되어 프로그램 명령에 따라서 바로 DC Motor가 회전을 하게 됩니다. 덮개를 분리해 놓으면 Motor Shield에 전원이 공급되지 않기 때문에 RC카는 움직이지 않게 됩니다. 매번 실험에 우두이노우노에 프로그램을 업로드 할 때마다 Motor Shield를 아두이노우노에서 분리하지 말고 이 덮개만 빼주면 됩니다.

위에 표시된 덮개는 꼽아져 있으면 아두이노우노와 Motor Shield가 서로 전류를 공유하게 되고 분리하면 전류를 공유하지 않고 개별적으로 전류를 공급 받는 다고 생각하시면 됩니다. 쉽게 말해서 Motor Shield에 전원을 공급하는데 덮개가 되어 있으면 이 전류가 아두이노우노에도 전류가 공급됩니다, 반대로 아두이노우노에 전원이 공급되면 Motor Shield에도 전류가 공급된다고 생각하시면 됩니다. 하지만 덮개가 빠지면 아두이노우노에 전류가 공급되더라도 Motor Shield에 전류를 공급되지 않습니다. 반대로 Motor Shield에 전류가 공급되더라도 아두이노우노에는 전류가 공급되지 않습니다.

위 사진에서 표시한 위치의 덮개를 어떻게 하느냐에 따라서 아두이노우노와 Motor Shield의 전류 공급에 형태를 결정하게 됩니다.

4. 결과




5. 주행 패턴을 배열로 만들기


위 코딩을 보면 순차적으로 길게 나열된 소스입니다. 중복되는 코딩을 보면 뭔가 줄이고 싶은 마음이 생기지 않나요. 저같은 경우는 중복된 코딩을 보면 바로 거것을 배열로 만들어서 코딩량을 줄이고 싶어집니다. 즉, 중복코딩을 보면 바로 배열을 떠오릅니다. 그러면 한번 배열로 전부 만들어 볼까요.

1) 코딩

[패턴] : 2륜 RC카의 motor 패턴만들기

byte movePattern1[7]={1,2,4,1,4,1,2}; //motor1 Pattern
byte movePattern2[7]={1,2,4,4,1,2,1}; //motor2 Pattern
byte delayTime[7]={2,2,2,2,2,2,2}; //motor Rotation time
byte speedPattern1[7]={200,200,200,200,200,200,200}; //motor1 speed Pattern
byte speedPattern2[7]={200,200,200,200,200,200,200}; //motor2 speed Pattern

FORWARD, BACKWARD, RELEASE의 값은 라이브러리 헤더파일에 가면 해당 값을 확인 할 수 있습니다.



위 사이트에 가시면 이렇게 확인이 가능합니다. define으로 정의한 변수명으로 표현할 필요 없이 직접 해당 값으로 표현 해도 됩니다. 보면 BRAKE라고 하나의 변수명이 더 있는데 사실 제가 쓰는 모터쉴드에서는 반응하지 않습니다. 정확히 소스를 살펴봐야 하는데 어떠한 반응도 보이지 않더군요. 그래서, BRAKE만 뺀 나머지 3개의 변수네임만 사용합니다.

실험한 위 소스에서는 Motor1, Motor2의 패턴을 개별 배열 변수로 해서 표현했습니다.

byte movePattern1[7]={1,2,4,1,4,1,2}; //motor1 Pattern
byte movePattern2[7]={1,2,4,4,1,2,1}; //motor2 Pattern

그리고, 회전 시간은 다음과 같이 1초가 1000입니다. 즉, 2초면 2000이 되기 때문에 나중에 delay()함수에서 "2*1000"으로 하면 되기 때문에 간단히 아래와 같이 표현 했습니다. 2초로 고정이니깐 구지 배열변수로 만들 필요가 없지 않냐고 생각 할 수 있지만 나중에 추가로 시간을 제어하기 위해서 우선 만들어 놓았습니다.

byte delayTime[7]={2,2,2,2,2,2,2}; //motor Rotation time

초 단위로 제어를 했지만 만약 좀 더 짧은 시간으로 제어한다면 다음과 같이 표혀을 해야 겠지요.

int delayTime[7]={2000,2000,2000,2000,2000,2000,2000}; //motor Rotation time

이렇게 하고

delay(delayTime[i]*1000);

이 표현에서

delay(delayTime[i]);

이렇게 곱하기 1000 부분을 지워 주면은 되겠죠. 실험은 초 단위로 하기 때문에 byte형으로 간단히 표현 했습니다.

마지막으로 speed 부분입니다. 위 실험에서는 200 speed로 고정이였습니다. 좀 더 다양한 패턴을 실험하기 위해서는 speed에 따른 주행거리를 계산을 해야 합니다. 나중에 speed에 따른 주행거리과 회전각도를 구하기 위해서는 속도와 시간의 값을 통해 수학적으로 계산해야 하기 때문에 측정하기 위해 별로로 배열변수로 다시 선언했습니다.

byte speedPattern1[7]={200,200,200,200,200,200,200}; //motor1 speed Pattern
byte speedPattern2[7]={200,200,200,200,200,200,200}; //motor2 speed Pattern

byte 자료형은 아두이노에서는 8bit 부호없는 숫자 0~255까지 표현이 가능합니다.

[소스]

#include <AFMotor.h>

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

byte movePattern1[7]={1,2,4,1,4,1,2}; //motor1 Pattern
byte movePattern2[7]={1,2,4,4,1,2,1}; //motor2 Pattern
byte delayTime[7]={2,2,2,2,2,2,2}; //motor Rotation time
byte speedPattern1[7]={200,200,200,200,200,200,200}; //motor1 speed Pattern
byte speedPattern2[7]={200,200,200,200,200,200,200}; //motor2 speed Pattern
int lenghtVal = 7; //pattern lenght

void setup() {
  motor1.setSpeed(0);
  motor2.setSpeed(0);
  motor1.run(RELEASE);
  motor2.run(RELEASE);
}

void loop() {  
    for(int i=0;i<lenghtVal;i++){
        motor1.setSpeed(speedPattern1[i]);
        motor2.setSpeed(speedPattern2[i]);
        motor1.run(movePattern1[i]);
        motor2.run(movePattern2[i]);
        delay(delayTime[i]*1000);
    }
}   

위 결과는 위에서 직접 명령을 내린 코딩과 동일함으로 결과도 동일하게 나옵니다. 위에서 패턴 결과를 동영상으로 찍어서 보여드렸는데 구지 똑같은 영상을 찍을 필요가 없기 때문에 배열 변수로 패턴을 저장하여 실험한 결과물 영상은 생략합니다.

6. 추가 주행


같은 방향으로 회전을 진행하더라도 DC Motor 돌의 speed 값에 따라 진행방향이 바뀌게 됩니다.


대충 이런 느낌이겠죠.

motor1.setSpeed(130);
motor2.setSpeed(200);
motor1.run(FORWARD);
motor2.run(FORWARD);

오른쪽 motor2은 원을 크게 그리게 되고 왼쪽 motor1은 원을 작게 그리며 우회전을 하게 됩니다. 만약 반대면 왼쪽은 큰원을 그리고 오른쪽은 작은원을 그리는 좌회전이 되겠죠.

[소스]

#include <AFMotor.h>

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

void setup() {
    motor1.setSpeed(0);
    motor2.setSpeed(0);
    motor1.run(RELEASE);
    motor2.run(RELEASE);
}

void loop() {   
    motor1.setSpeed(130);
    motor2.setSpeed(200);
    motor1.run(FORWARD);
    motor2.run(FORWARD);
    delay(1000);    
}   

[결과]


7. 주의 사항


모터 쉴드에 전원을 6V으로 공급하기 때문에 실제 DC 기어모터에 제대로 된 힘을 발휘하지 못합니다. 아두이노에 연결하는 9V 전원 공급쪽으로 하면 DC 기어모터의 회전에 힘을 발휘하기는 합니다. 전원 공급이 약하면 회전력이 떨어집니다. DC 기어모터를 회전시킬 수 있는 힘이 약하기 때문에 speed 값이 낮아지면 정상적으로 해당 speed만큼 회전을 시키지 못합니다.

방금 한쪽은 200을 주고 한쪽을 130정도로 speed 값을 주고 실험한 이유는 100이하로 speed값을 주면 제가 쓰는 DC 기어모터를 정상적으로 회전을 시키지 못합니다. 130의 정도로 해야 겨우 반응을 보이는데 이것도 반응이 없는 경두도 발생했습니다. 싼 DC기어모터의 한계인지 모르겠네요. DC기어모터가 회전을 하려고 하는데 회전 기어를 움직이게 할 힘이 너무 약해서 돌지를 못하는 현상이 발생하더군요. 참고로 아두이노에서 5V usb로 연결하면 speed 일정값이하는 아예 반응하지 않고 DC기어모터에서 이상한 신호음만 크게 납니다. 문제가 있다는 신호인거죠. 싼 DC기어모터여서 그런지 약간 퍽퍽하고 또 전류 공급이 약하면 반응을 잘 못해서 실험에 약간 불편했네요.

그냥 RC카를 조정할 때 6V 전류를 한다면 주행 speed를 200으로 고정해놓고 주행 실험을 하시는게 편할 듯 싶네요. 좋은 모터를 쓰면 그렇게 까지 신경을 안쓰셔도 되겠지만요. 결론은 DC 기어모터의 퍽퍽함과 전류공급에 따라 DC Motor의 회전에 영향을 준다는 것이죠.

마무리


오늘 주행 패턴을 만들면서 사실 6V 전원 공급으로는 DC Motor를 정교하게 제어를 하기 어렵습니다. DC Motor를 오랫동안 사용하지 않아서 처음에는 speed를 200으로 해놓고 실험을 해도 회전을 제대로 시키지 못하더군요. 그냥 건전지를 DC Motor에 다이렉트로 연결하여 회전을 계속 시켰습니다. 그리고 나니 그제서야 DC Motor가 회전하더군요. 기어 부분에게 퍽퍽하고 싼 부품을 구매해서 그런지 제어하기가 좀 불편 했네요.

그래도 어느정도 패턴을 만들어 주행 실험을 하였습니다. 이 주행 결과를 다음 시간에 Bluetooth를 이용하여 스마트폰으로 무선 조정 실험을 할 예정입니다.

참고로, 위에서 주행 배열 변수를 구지 만든 이유는 따로 여러분들이 주행에 대한 실험을 할 수 있는 틀을 마련하기 위해서 입니다. 만약, 주행 정보를 이렇게 나눠 놓고 특정 움직임에 대한 RC카 주행을 기록해 놓는다고 상상해 보세요. 그 기록으로 주행을 시킨다면 어떻게 될까요. 예를들어, 같은 기록 데이터를 가지고 2대 이상의 RC카가 움직인다면 어떻게 될 까요. 아이돌 군무를 상상하면 쉬울 듯 싶네요. 같은 동작을 여러 RC카가 군무를 추듯이 주행을 하게 됩니다. 이런 개념을 갖게 되면은 나중에 군집 주행의 첫발을 내딛을 수 있겠죠.

그리고, 배열에 특정 동작에 대해서 순차적으로 증가 시키거나 감소시키는 값들로 채워 넣는다면 어떻게 될까요. 어떤 패턴 실험의 측정 도구로도 활용이 가능합니다. 시간에 따른 RC카의 주행 거리와 회전 각도들을 시간값의 변화를 배열로 미리 만들어 넣고 실험하여 측정도 가능 합니니다.

실험은 안했지만 여러분들이 한번 다음 실험을 해보셨으면 합니다. 시간과 speed 값에 의한 이동거리와 회전각도를 수학적으로 접근해 보세요. 아니면 그냥 무식하게 speed값을 고정 시키고 시간값에 의한 RC카의 바퀴휠 원둘레 길이를 가지고 시간대 별 이동거리와 회전각도를 눈으로 대충 근사거리와 근사각도를 가늠해 놓고 그걸 기준으로 RC카를 제어를 해보세요. 그러면 좀 더 정교한 제어가 가능해 집니다.

아무튼 오늘은 생각할 부분이 많습니다. 한번 상상의 나래를 펼쳐 보세요.


댓글()

[아두이노] L293D Motor Shield+DC MOTOR 제어

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

[아두이노] L293D Motor Shield+DC MOTOR 제어 



지난 시간에 74HC595 칩을 이용하여 2개의 L293D 칩을 제어함으로써 4개의 DC Motor를 제어를 가상시뮬레이터에서 실험을 하였습니다. 오늘은 L293D Motor Shield를 이용하여 실제로 DC Motor를 제어하는 실험을 하겠습니다.


1. L293D Motor Shield



  • A : 전원핀
  • B : Servo핀
  • C :외부전원사용선택

M1, M2, M3, M4은 DC Motor 제어핀으로 총 4개의 DC Motor를 제어 할 수 있습니다. 참고로, M1, M2핀을 두개를 합치면 Stepper Motor를 제어할 수 있다. L293D Motor Shield은 비슷하지만 약간씩 다른 형태를 취하고 있는데 제가 사용하는 Motor Shield은 디지털핀을 꼽을 수 있는 곳이 없습니다. 아날로그핀은 오른쪽 하단에 A0~A5까지 꼽을 수 있게 되어 있지만 위에 0~13번까지의 디지털핀은 납땜하거나 선을 아두이노와 쉴드 사이에 묶어야 연결할 수 있기 때문에 좀 불편합니다.
핀을 많이사용할 경우 제가 쓰고 있는 L293D Motor Shield은 좀 문제가 있겠죠. 제가하는 실험은 A0~A5번핀 정도만 있어도 충분히 실험할 수 있기 때문에 사용하는데에는 문제는 없습니다.

2. L293D Motor Shield+DC MOTOR 회로도




동일한 L293D Motor Shield 이미지를 찾았는데 못 찾고 가장 유사한 이미지로 회로도를 표현했는데 거의 동일한 L293D Motor Shield 이미지라고 생각하셔도 무방합니다. L293D Motor Shield를 아두이노우노 위에 꼽으면 됩니다 하나의 몸체로 만드시고 외부전원은 보시는 것과 같이 핀에 연결하고 DC Motor은 실험에서는 M3에 연결했습니다. 참고로 실험하실 때 원하는 위치 M1~M4 중에서 선택하셔서 실험하시면 됩니다.

3. L293D Motor Shield 라이브러리 설치



라이브러리 관리자에서 "Motor shield" 라고 치시면 여러개가 검색됩니다. 비슷한 이름의 두개의 버전이 있는데 빨간색으로 표시한 라이브러리를 설치하시면 됩니다.


그리고 라이브러리 혹시 검색이 안된다면 위 라이브러리 출처에 가셔서 다운받아서 설치하시면 됩니다.

주의사항 : 실제 실험에 사용하시는 Motor Shield에 맞는 라이브러리를 설치해야 합니다. 다른 Motor Shield라면 다른 라이브러리를 찾아서 설치해야 합니다.

4. 코딩


DC Motor

#include <AFMotor.h>
  • AF_DCMotor motor(3) : M3핀을 DC Motor 제어용으로 사용.
  • motor.setSpeed(200) : 모터 속도
  • motor.run(FORWARD) : FORWARD, BACKWARD, RELEASE 회전 명령

2초 단위로 전진, 후진, 정지를 테스트 명령을 내려 볼까요.

  motor.run(FORWARD);
  delay(2000);
  
  motor.run(BACKWARD);
  delay(2000);
 
  motor.run(RELEASE);
  delay(2000);

라이브러리를 이용하니깐 따로 코딩할 부분이 없습니다. 무지 간단하게 DC Motor를 제어할 수 있습니다. 이미 가상시뮬레이터에서 동작 원리와 제어 코딩에 대해 배웠으니깐 따로 설명은 안드리겠습니다. 라이브러리를 이용하면 motor.run() 함수를 이용하여 DC Motor 회전제어에 대한 명령을 한줄로 내릴 수 있게 되었고 위와 같은 코딩은 구지 설명을 안하더라도 전진, 후진, 해제라는 의미만으로 무슨 명령인지 쉽게 알 수 있을거라 생각합니다.

테스트 할 전체 코딩

#include <AFMotor.h>

AF_DCMotor motor(3);

void setup() {
  motor.setSpeed(200);
  motor.run(RELEASE);
}

void loop() {
  motor.run(FORWARD);
  delay(2000);
  
  motor.run(BACKWARD);
  delay(2000);
 
  motor.run(RELEASE);
  delay(2000);
}

5. 결과




마무리


오늘 실험은 지난 시간의 가상시뮬레이터로 실험한 내용과 동일합니다. 단지 차이점을 실제로 실험하는 것과 Motor Shield 라이브러리를 사용한다는 점만 다를 뿐이죠. 실제로 라이브러리를 이용하니깐 DC Motor를 제어하는 데 그렇게 어렵지 않죠.

 motor.run(FORWARD) => 전진
 motor.run(BACKWARD) => 후진
 motor.run(RELEASE) => 해제(정지)

이 세가지 명령을 통해 DC Motor를 마음대로 회전 시킬 수 있게 되었습니다. 그리고, motor.setSpeed(속도) 함수로 원하는 속도로 회전을 시킬 수 있게 되었습니다.

DC Motor 1개를 오늘 배운 것처럼 어렵지 않게 제어 했으니깐 DC Motor 2개를 제어하더라도 어렵지 않게 제어가 가능하겠죠.

DC Motor 2개가 있으면 2륜 RC카를 만들 수 있습니다. 오늘 배운 회전 관련 3개의 함수와 속도함수 1개만 알면 RC카 구현도 어렵지 않을 거라 생각됩니다.

한번 여러분들은 오늘 배운 4개의 함수를 가지고 RC카 구현에 대해 상상의 나래를 펼처 보세요.


댓글()

[아두이노] 74HC595+L293D+DC MOTOR 제어

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

[아두이노] 74HC595+L293D+DC MOTOR 제어 



지난 시간에 L293D를 이용하여 DC Motor의 회전 방향 제어와 속도 제어를 가단히 실험 하였습니다. 오늘은 좀 더 Motor 쉴드에 가깝게 74HC595 칩을 이용하여 L293D 칩을 두개를 제어하여 DC Motor 4개를 제어하는 실험을 하겠습니다.

1. 74HC595 복습




  • 데이터입력핀 DS핀 : 아두이노에서 데이터를 이칩에 보내게 됩니다.(총8개 출력값)
  • STCP 핀 : HIGH, LOW값으로 결쇠 역할을 하는 핀입니다. 칩에 입력할때 LOW 칩을 개방하고 입력을 끝내면 HIGH 칩닫음
  • SHCP 핀 : 클럭핀

세개의 핀 DS, STCP, SHCP를 통해서 74HC595핀을 제어하게 됩니다. 아두이노와 핀 연결은 위 사전학습에 가셔서 한번 읽어 주세요.

  • Q0~Q7 : 각 핀으로 입력된 8개의 값을 출력합니다.

Q0~Q7까지 8개의 값을 출력하는데 여기서 각 4개씩 L293D 칩에 입력신호로 나누면 2개의 L293D 칩을 제어할 수 있게 됩니다.

2. 74HC595+L293D


두 칩을 연결한 모습을 살펴 볼까요.


위 74HC595 칩의 핀넘버를 잘 보시고 L293D 칩의 In1~In4까지 연결된 모습입니다. 좀 햇갈릴 수 있으니 아래 이미지를 보시고 정확한 위치를 파악해 두세요.


그래도, 어렵다면 위에 공개 회로도에 가셔서 해당 칩이 어떻게 연결 되어 있는지 살펴보도록 하세요.

3. 74HC595+L293D+DC MOTOR 회로도


  • 준비물 : 74HC595 1개, L293D 2개, DC Motor 4개, Power Supply, 아두이노우노
  • 내용 : 74HC595핀은 9, 10, 11핀을 연결하고 74HC595 출력 Q0~Q7핀으로 L293D In1~In4핀에 입력 핀에 연결한다. 마지막으로 DC Motor는 L293D 출력핀에 연결한다.

외부 5V 전압일 경우의 실험입니다.


74HC595핀의 전류 아두이노가 아니 이부분도 외부에서 공급되도록 하였습니다. 참고로 74HC595의 허용 전압이 제한되어 있기 때문에 실제로 전압은 DC Motor에 따라 더 높은 전압을 연결할 수 있습니다. 그러면 74HC595의 허용 전압을 초과하게 됩니다 그러면 칩에 문제가 생기기 때문에 5V이상의 전압을 사용할 경우 5V 레귤러를 연결해야 합니다. 그리고 안정적 전류 공급을 위해서 충전기도 달아야 하는데 사실 의미만 전달하기 위한 회로도로 실험하기 때문에 가상시뮬레이터에서만 사용하시고 실제로는 만들지 말아주세요.

외부 5V 전압 이상일 경우 실험은 아래와 같이 5V 레귤러(LM7805)를 달아야 겠죠.



위 그림을 자세히 보시면 5V 레귤러(LM7805)은 I, G, O핀으로 구성되어 있는데 I은 입력 전압이고, G은 Gnd이고, O는 출력전압입니다. 5V 이상의 전압을 출력 5V로 만들어주는 부품입니다.

참고

5V 레귤러(LM7805)를 사용하요 5V 공급되면 강압이 일어나 가상시뮬레이터에서 약3V 전류가 흐르게 됩니다. 5V 레귤러(LM7805)로 5V의 전류가 공급이 될려면 7V이상이 공급되어야 겠죠.


아래 그림처럼 10V가 공급되었을 때 7V 이상이니깐 정상적으로 5V의 전류가 흐르게 됩니다.


오늘 실험 회로도는 간단히 원리만 이해하기 위한 회로도 입니다. 실제 모터쉴드를 구현한다면 추가로 몇가지 부품이 더 있어야 합니다. 여러개의 부품을 연결하면 사실 회로도가 너무 복잡해지기 때문에 간단히 의미만 전달할 수 있는 실험을 위해서 칩들만 연결하고 5V를 제한을 둔 회로도로 DC Motor를 제어하는 실험으로 구성했습니다.

3. 코딩


지난 시간의 코딩과 마찬가지로 L293D 칩의 동작을 기준으로 FORWARD, BACKWARD, RELEASE의 세가지 상태를 코딩으로 만들겠습니다. 참고로 속도 Enable은 Vcc에 연결하여 최대속도로 무조건 회전하도록 회도로를 구성했기 때문에 따로 이부분은 생략합니다.

74HC595 칩의 장점은 1byte 신호값을 8개의 출력 신호로 만들어 낼 수 있다는 점입니다. 그래서 4개의 출력신호로 1개의 DC Motor를 제어할 수 있게 됩니다. 2개의 DC Motor를 제어하니깐 1byte로 구성된 회전명을 미리 만들어 놓으면 쉽게 회전 제어가 가능하겠죠.

#define FORWARD  0B10011001
#define BACKWARD 0B01100110
#define RELEASE  0B00000000

위 표현은 각 비트 자리가 L293D의 입력 핀 값이 되기 때문에 정방향으로 갈려면 다음과 같습니다.
예)
1001 1001
1234 5678
=> FORWARD

나머지 BACKWARD, RELEASE는 해당 Motor 핀위치의 값을 1 or 0으로 맞춰서 값을 표현하면 되겠죠. 그래서 위와 같이 FORWARD, BACKWARD, RELEASE의 세가지 상태값을 정의해 놓습니다.

74HC595 핀을 사용하기 위해서

//Pin connected to ST_CP of 74HC595
const int ST_CP = 10;
//Pin connected to SH_CP of 74HC595
const int SH_CP = 11;
////Pin connected to DS of 74HC595
const int DS = 9;

3개의 핀을 선언합니다.

   pinMode(ST_CP, OUTPUT);
   pinMode(SH_CP, OUTPUT);
   pinMode(DS, OUTPUT);

3개의 핀은 출력모드이니깐 출력모드로 사용한다고 선언하게 됩니다.

74HC595 칩에 1byte의 신호를 전송하기 위해서는 다음과 같은 명령을 내리면 됩니다.

74HC595 사용함수

     digitalWrite(ST_CP, LOW);
     shiftOut(DS, SH_CP, LSBFIRST, FORWARD);
     digitalWrite(ST_CP, HIGH);

이렇게 1byte씩 데이터를 보낼 수 있습니다. 'OB' 기호가 있는 값은 byte값을 나타내는데 8bit이니깐 1byte 값이 되고 1byte을 shiftOut()함수를 통해서 74HC595 칩에 전송하게 됩니다. 전송된 값에 의해서 74HC595 칩의 Q0~Q7핀으로 FORWARD의 출력이 발생합니다.

그러면, FORWARD, BACKWARD, RELEASE 명령을 내려 볼까요.

digitalWrite(ST_CP, LOW);
shiftOut(DS, SH_CP, LSBFIRST, FORWARD);
digitalWrite(ST_CP, HIGH);
delay(2000);

digitalWrite(ST_CP, LOW);
shiftOut(DS, SH_CP, LSBFIRST, BACKWARD);
digitalWrite(ST_CP, HIGH);
delay(2000);

digitalWrite(ST_CP, LOW);
shiftOut(DS, SH_CP, LSBFIRST, RELEASE);
digitalWrite(ST_CP, HIGH);
delay(2000);

2초 단위로 FORWARD, BACKWARD, RELEASE 명령이 내려지게 됩니다. 2초단위로 전진, 후진, 정지 이렇게 DC Motor 4개가 동시에 회전하게 됩니다. 지난 시간에 비해 코딩이 더 간소화 되었습니다.

종합해보면, 2초 단위로 전진, 정지, 후진, 정지 반복수행

#define FORWARD  0B10011001
#define BACKWARD 0B01100110
#define RELEASE  0B00000000

//Pin connected to ST_CP of 74HC595
const int ST_CP = 10;
//Pin connected to SH_CP of 74HC595
const int SH_CP = 11;
////Pin connected to DS of 74HC595
const int DS = 9;

void setup(){
   pinMode(ST_CP, OUTPUT);
   pinMode(SH_CP, OUTPUT);
   pinMode(DS, OUTPUT);  
}

void loop(){
     digitalWrite(ST_CP, LOW);
     shiftOut(DS, SH_CP, LSBFIRST, FORWARD);
     digitalWrite(ST_CP, HIGH);
     delay(2000);

     digitalWrite(ST_CP, LOW);
     shiftOut(DS, SH_CP, LSBFIRST, RELEASE);
     digitalWrite(ST_CP, HIGH);
     delay(2000);
      
     digitalWrite(ST_CP, LOW);
     shiftOut(DS, SH_CP, LSBFIRST, BACKWARD);
     digitalWrite(ST_CP, HIGH);
     delay(2000);
  
     digitalWrite(ST_CP, LOW);
     shiftOut(DS, SH_CP, LSBFIRST, RELEASE);
     digitalWrite(ST_CP, HIGH);
     delay(2000);
}

4. 결과


결과는 2초 전진, 2초 정지, 2초 후진, 2초 정지 결과 입니다.


마무리


지난시간에 비해 회로도는 더 복잡해졌지만 코딩은 더 간소화 되었습니다. 칩에 데이터를 한번에 전송하고 그 데이터를 기준으로 74HC595에서 2개의 L293D 칩에 입력신호를 보내 DC Motor를 제어 했습니다. 74HC595칩을 사용할 줄 알면은 쉽게 L293D 칩을 제어를 할 수 있습니다. '0B10011001'라는 이 데이터 값을 통해 FORWARD(전진) 쉽게 표현할 수 있게 되었습니다.

     digitalWrite(ST_CP, LOW);
     shiftOut(DS, SH_CP, LSBFIRST, 모터신호값);
     digitalWrite(ST_CP, HIGH);

이 세줄로 DC Motor를 마음대로 제어할 수 있게 되었습니다. 하지만, 실제로 이것을 적용하려면 위 회로도를 그대로 적용하시면 안됩니다. 그 이유는 위에서 설명했듯이 불안전한 회로도입니다. 단지, DC Motor를 제어하는 원리를 이해하기 위해 표현한 회로도입니다. 4개의 입력값만 있으면 L293D 칩을 제어할 수 있으니깐 L293D 칩을 두개 제어하려면 8개의 입력값을 제어할 수 있으면 되니 떠오른 것이 바로 74HC595칩이였고 이 칩을 기반으로 간단히 표현한 것 뿐입니다.

초보분들은 대충 원리만 이해하고 넘어가시기 바랍니다. 사실 편하게 DC Motor 쉴드를 사용하는게 편하니깐요. 처음부터서 너무 완벽하게 전부 다 하실 필요는 없습니다. 모르는 것은 이미 구현된 것을 가져다가 사용하시면 됩니다. Motor를 안정적으로 제어하기 위해서는 DC Motor 쉴드를 구매하셔서 쉽게 제어하시기 바랍니다.

이렇게 해서 DC Motor 제어를 간단히 살펴보았습니다.


댓글()

[아두이노] L293D + DC MOTOR 제어

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

[아두이노] L293D + DC MOTOR 제어



지난 시간에 DC Motor의 회전 방향 제어와 속도 제어를 가단히 실험 하였습니다. 오늘은 L293D 칩을 이용하여 방향과 속도 제어를 실험을 하겠습니다. DC MOTOR를 제어하는 모터쉴드 중 가장 싸고 가장 많이 사용되는 모터쉴드의 사용되는 칩입니다. 이제 L293D 칩으로 어떻게 DC MOTOR를 제어하는지 알아보도록 하죠.

1. L293D



L293D 칩은 16Pin으로 구성되어 있습니다. 아래 핀 정보를 잘 살펴 봐주세요.


[핀 정보]
Vcc - 8, 16 Pin
Gnd - 4, 5, 12, 13 Pin
Enable - 1, 9 Pin (속도)
Input - In1(2), In2(7), In3(10), In4(15)
Output - Out1(3), Out2(6), Out3(11), Out(14)

총 4개의 Input 값에 의해 4개의 Output 신호를 만들어 냅니다. L293D 칩을 이용하면 D2 Motor 2개를 제어할 수 있게 됩니다.

In1+In2 => Out1+Out2 = DC Motor1 (Enable1 - 1번핀)
In3+In4 => Out3+Out4 = DC Motor2 (Enable2 - 9번핀)

위 관계를 잘 기억해 두세요. 이 관계를 통해서 DC Motor의 회전과 속도가 결정됩니다.

예로)
In1(HIGH) + In2(LOW) 이면 Out1(DC Motor + 연결), Out2(DC Motor - 연결) 되어 있다면 정방향 회전입니다.
Enalbe1(255) 이면 정방향으로 255속도로 회전됩니다.

In1(LOW) + In2(HIGH) 이면 Out1(DC Motor + 연결), Out2(DC Motor - 연결) 되어 있다면 역방향 회전입니다.
Enalbe1(255) 이면 역방향으로 255속도로 회전됩니다.

In1(HIGH) + In2(HIGH) 이면 Out1(DC Motor + 연결), Out2(DC Motor - 연결) 되어 있다면 정지입니다.
In1(LOW) + In2(LOW) 이면 Out1(DC Motor + 연결), Out2(DC Motor - 연결) 되어 있다면 정지입니다.

기본 동작을 이해 했으니깐 이제 실험을 할까요.

2. L293D + DC Motor 회로도


  • 준비물 : L293D, 가변저항 1개, DC Motor 2개, Power Supply, 아두이노우노
  • 내용 : 핀정보를 통해서 DC Motor 제어할 4개의 Input핀과, 1개 Enable 핀을 아두이노에서 적당한 핀을 선택하여 연결한다.


L293D 핀을 연결할 때 In1(7번), In2(6번), In3(8), In4(9번)을 연결 했습니다. Enable(5번) 핀은 2개의 Enable핀을 하나의 핀으로 연결 했습니다.

Motor1 => In1(+핀), In2(-핀)
Motor2 => In4(+핀), In3(-핀)

3. 코딩


L293D 칩의 동작을 기준으로 FORWARD, BACKWARD, RELEASE의 세가지 상태를 코딩으로 만들어야 합니다.

가상시뮬레이터에서 실험을 해야 하기 때문에 const 형을 가상시뮬레이터에서 실험하니깐 사이트 문제인지 error가 발생했다 안했다 해서 그냥 아래와 같이 선언 했습니다. 한번 error가 발생하면 지금까지 실험한 정상적인 모든 회로로들이 전부 error가 발생해서 이 표현을 원래 싫어하는데 그냥 사용 했네요.

#define FORWARD  1
#define BACKWARD 2
#define RELEASE  3

byte enablePin = 5;
byte motorPin1 = 7;
byte motorPin2 = 6;
byte motorPin3 = 9;
byte motorPin4 = 8;

FORWARD 상태를 우선 하나를 기준으로 만들어 볼까요.

digitalWrite(motorPin1,HIGH);
digitalWrite(motorPin2,LOW);
digitalWrite(motorPin3,HIGH);
digitalWrite(motorPin4,LOW);

이렇게 L293D 칩에 입력을 주어졌을 때 이 상태를 FORWARD라고 기준을 정합니다. 그러면, BACKWARD은 반대로 전류가 공급되면 되겠죠.

digitalWrite(motorPin1,LOW);
digitalWrite(motorPin2,HIGH);
digitalWrite(motorPin3,LOW);
digitalWrite(motorPin4,HIGH);

어렵지 않죠. 그러면 RELEASE은 어떻게 전류 공급 상태를 만들어야 할까요. 정지는 두가지로 나뉘는데 어떤 것을 선택해도 상관 없습니다.

digitalWrite(motorPin1,HIGH);
digitalWrite(motorPin2,HIGH);
digitalWrite(motorPin3,HIGH);
digitalWrite(motorPin4,HIGH);

or

digitalWrite(motorPin1,LOW);
digitalWrite(motorPin2,LOW);
digitalWrite(motorPin3,LOW);
digitalWrite(motorPin4,LOW);

전부 HIGH or LOW가 되면 전류가 흐르지 않기 때문에 정지 상태가 됩니다.

이대로 코딩하기 보단 가독성을 위해 사용자 정의함수로 표현

void motorMove(int key){
  switch(key){
    case 1:
            digitalWrite(motorPin1,HIGH);
            digitalWrite(motorPin2,LOW);
            digitalWrite(motorPin3,HIGH);
            digitalWrite(motorPin4,LOW);
            break;
    case 2:
            digitalWrite(motorPin1,LOW);
            digitalWrite(motorPin2,HIGH);
            digitalWrite(motorPin3,LOW);
            digitalWrite(motorPin4,HIGH);
            break;
    case 3:
            digitalWrite(motorPin1,HIGH);
            digitalWrite(motorPin2,HIGH);
            digitalWrite(motorPin3,HIGH);
            digitalWrite(motorPin4,HIGH);
            break;
  }  
} 

3가지 상태를 선택제어문 switch() 함수를 통해서 3가지 상태중 하나를 선택하게 사용자정의 함수로 만들어 놓습니다.

이제 명령은 아래와 같이 가독성이 있는 코딩을 할 수 있습니다.

motorMove(FORWARD);
delay(1000); 
  
motorMove(BACKWARD);
delay(1000); 
  
motorMove(RELEASE);
delay(1000);  

코드만 봐도 FORWARD 상태로 1초동안 회전하고, BACKWARD 상태로 1초동안 회전하고 RELEASE상태로 1초동안 회전한다고 쉽게 위 코딩을 해석할 수 있고 쉽게 motorMove()함수로 Motor 동작을 제어할 수 있게 되었습니다.

DC Motor 속도 제어

지난 시간의 코딩과 동일합니다. 가변저항으로 속도를 결정하게 하였습니다.

  int speed = map(analogRead(A0), 0, 1023, 0, 255); //가변저항기의 입력값
  analogWrite(enablePin, speed);

속도핀 enablePin으로 가변저항으로 읽은 speed 값으로 DC Motor의 속도를 결정하게 됩니다.

종합해보면,

#define FORWARD  1
#define BACKWARD 2
#define RELEASE  3

byte enablePin = 5;
byte motorPin1 = 7;
byte motorPin2 = 6;
byte motorPin3 = 9;
byte motorPin4 = 8;

void setup() {
  pinMode(enablePin,OUTPUT); 
  pinMode(motorPin1,OUTPUT);
  pinMode(motorPin2,OUTPUT);
  pinMode(motorPin3,OUTPUT);
  pinMode(motorPin4,OUTPUT);  
  motorMove(RELEASE);
}
 
void loop() {
    
  int speed = map(analogRead(A0), 0, 1023, 0, 255); //가변저항기의 입력값
  analogWrite(enablePin, speed);

  motorMove(FORWARD);
  delay(1000); 
  
  motorMove(BACKWARD);
  delay(1000); 
  
  motorMove(RELEASE);
  delay(1000);  
} 

void motorMove(int key){
  switch(key){
    case 1:
            digitalWrite(motorPin1,HIGH);
            digitalWrite(motorPin2,LOW);
            digitalWrite(motorPin3,HIGH);
            digitalWrite(motorPin4,LOW);
            break;
    case 2:
            digitalWrite(motorPin1,LOW);
            digitalWrite(motorPin2,HIGH);
            digitalWrite(motorPin3,LOW);
            digitalWrite(motorPin4,HIGH);
            break;
    case 3:
            digitalWrite(motorPin1,HIGH);
            digitalWrite(motorPin2,HIGH);
            digitalWrite(motorPin3,HIGH);
            digitalWrite(motorPin4,HIGH);
            break;
  }  
}

4. 결과


위 회로도대로 연결해서 시뮬레이터를 하면 FORWARD가 +RPM이고 BACKWARD은 -RPM으로 돌아갑니다. 그리고, FORWARD, BACKWARD, RELEASE 상태를 각각 1초씩 실행하기 때문에 속도가 즉각적으로 반응하지 않습니다. 즉각적으로 반응하게 하려면 delay함수 없이 delay 효과를 부여하여 코딩해야 합니다. 하지만 오늘 실험은 회전 방향과 속도만 실험하기 때문에 생략 했습니다.


마무리


오늘 배운 L293D 칩을 통해서 DC Motor 2개를 제어를 해보았습니다. L293D 칩의 경우는 16개핀으로 좀 복잡해 보일 수 있지만 사실 Input 핀 4개와 Enable 핀 2개에 대해서만 알고 있으면 쉽게 제어가 가능하기 때문에 어렵게 생각 안하셔도 됩니다. 시중에서 판매하는 L293D 모터쉴드는 L293D 칩이 2개가 결합 된 형태로 DC Motor 4개를 제어할 수 있습니다.

나중에 L293D 모터쉴드를 통해 DC Motor를 제어할 때 사전 지식으로 L293D 칩에 대한 동작 제어를 알아 두시면 좋습니다.


댓글()

[아두이노] DC MOTOR 제어

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

[아두이노] DC MOTOR 제어 



오늘은 DC Motor의 회전 방향 제어와 속도 제어를 가단히 실험 하겠습니다. 회전 방향은 전류 공급의 +, - 의 위치를 바꿈으로써 DC Motor의 회전 방향을 바뀌게 되고 npn 트랜지스터를 이용하여 전류의 세기를 조절하여 DC Motor의 속도를 조절을 할 수 있습니다. 이 두가지에 대해서 간단히 살펴 봅시다.

1. DC MOTOR


DC Motor에서 모터의 회전수를 RPM이라고 합니다. 5V의 전류에 대한 회전수가 가상시뮬레이터에서는 170RPM이 나왔네요. 이건 그냥 가상시뮬레이터에서의 RPM이니깐 그렇게 까지 크게 의미를 두지 마세요. 그냥, RPM이란 단어가 나오면 모터의 회전수라고 생각하시면 됩니다.

DC Motor의 전류 +, - 방향에 따라서 정방향과 역방향으로 바뀝니다. 아래 이미지를 잘 살펴 보세요.

첫번째, 이미지는 전류 공급이 정방향 회전입니다.


두번째, 이미지는 왼쪽은 DC Motor +, - 연결은 정방향이라고 했을 때 오른쪽 DC Motor +, - 방향을 바꾸면 역방향으로 회전을 하게 됩니다.


전류 공급에 따라서 어떤 느낌으로 회전 되시는지 아실 수 있겠죠.

2. npn 트랜지스터 DC Motor 속도 제어


1) 회로도


  • 준비물 : npn 트랜지스터, 가변저항 1개, 저항 1k옴, 다이오드 1개, DC Motor, Power Supply, 아두이노우노
  • 내용 : npn 트랜지스터를 이용해서 DC Motor의 회전 속도를 제어를 해보자.

아두이노에서 전류를 공급 받아도 되지만 될 수 있으면 DC Motor를 제어할 때는 외부로 부터 전류를 공급하는 게 좋습니다. DC Motor를 아두이노와 전류를 공유하게 되면 아두이노에 정상적으로 전류 공급이 이루어지지 않을 수 있으니깐 될 수 있으면 전류를 따로 쓰는게 좋습니다.


대충 회로도는 위의 모습처럼 표현 했습니다. 위 이미지로는 좀 복잡해 보일 수 있기 때문에 스케메틱 회로도로 그린 아래 이미지로 살펴 보세요.


트랜지스터는 증폭 부품입니다. 따로 해당 부분만 빼내면 아래 그림으로 살펴 볼 수 있습니다.


위 그림처럼 전류의 방향이 화살표 처럼 흐를때 아두이노의 9번핀에 입력 전류에 의해서 증폭이 일어나게 됩니다. 실험의 아두이노 9번핀에 전류(0~255)값에 의해서 0일때 전류가 흐르지 않고 255일때 전류가 최대치로 흐르게 됩니다. 쉽게, 수도꼭지 역활을 수행한다고 생각하면 될 듯 싶네요.

여기서,


Collector, Base, Emitter 로 나눌 수 있습니다. Base 아두이노의 9번 핀이였었죠. Collector는 Vcc, Emitter는 End로 연결했을 때 Collector에 들어오는 전류는 Base에 입력에 전류에 의해서 증폭이 일어나는 원리인데요.

실험에서는 그냥 전류의 양을 조절한다고만 간단히 초보분들은 넘어가셔도 됩니다. 전자회로를 전문적으로 공부하실 분이라면 따로 npn, pnp 트랜지스터의 동작원리를 구글검색으로 통해서 다 찾아보고 깊게 이론 공부를 하셔야 겠지만 초보분들은 우선 DC Motor의 움직이는 것만 이해해 주셔도 충분합니다.

위 DC Motor에 보면 다이오드가 Motor에 연결되어 있는 그 이유는 역전류를 막기 위해서 입니다. 전류의 방향을 한방향으로만 진행되도록 하기 위한 안전장치라고 보시면 됩니다.

2) 코딩


int motorPin = 9;

void setup() 
{ 
  Serial.begin(9600);
  pinMode(motorPin, OUTPUT);  
}

void loop() 
{ 
  int speed = map(analogRead(A0), 0, 1023, 0, 255); 
  analogWrite(motorPin, speed);
  Serial.println(speed);
}

코딩은 간단합니다. 위 코딩을 보시면 우선 Dc Motor를 제어할 핀은 PWM 디지털핀으로 아날로그 0~255값을 출력하는 핀으로 제어하게 됩니다.

map(analogRead(A0), 0, 1023, 0, 255);

가변저항기를 이용하기 때문에 아날로그 0~1023값을 읽습니다. 읽은 값을 map()함수로 0~255값으로 변환 시킵니다. 이 값을 다시 npn 트랜지스터의 base 입력값으로 해서 전류의 세기를 조절하게 됩니다.

analogWrite(motorPin, speed);

어려운 코딩은 없죠. 기존에 다 쓰던 pinMode(), analogRead(), map(), analogWrite() 함수들은 지금까지 계속 반복되어 사용하는 함수임으로 이제는 해당 함수의 설명은 생략하겠습니다.

Serial.println(speed);

Motor의 속도를 speed의 값이 가변저항기로 입력받는데 그 값은 아래와 같습니다.


이 값으로 전류의 세기를 조절하게 됩니다. 그래서 실제 Motor의 회전 속도가 speed의 값에 따라서 달라지게 됩니다

3) 결과


가상시뮬레이터에서 가변저항을 조절하면 Motor의 RPM 수의 변합니다.


마무리


DC Motor 회전 방향과 속도에 대해서 살펴보았습니다. 오늘 살펴본 내용에서 +, - 방향을 어떻게 DC Motor에 연결되느냐에 따라서 회전의 방향을 바뀌는 것을 알게 되었고, 전류를 조절함으로 DC Motor의 속도를 조절 할 수 있게 되었습니다. 이 의미는 무척 중요합니다. RC카와 같은 것을 조정할 때 정면으로 달리고 싶으면 전류 방향은 정방향으로 맞춰서 정면으로 달리게 하였다면 후진을 할려고 한다면 전류 방향을 반대로 하여 역방향 회전을 시키게 하면 됩니다. 그리고 진행 방향에 속도를 조절을 할 수 있습니다. 초보분들은 위의 회로도의 npn 회로도를 그렇게 까지 깊게 이해하실 필요는 없습니다. 단지 DC Motor의 속도를 간단히 가상시뮬레이터에서 테스트 하기 위함이였으니깐요. 나중에 모터쉴드를 이용하면 쉽게 회전 방향과 속도를 제어할 수 있기 때문에 DC Motor의 회전과 속도에 대한 의미만 이해하시면 됩니다.


댓글()

[아두이노] 릴레이 모듈+온도센서+DC모터 응용

IOT/아두이노|2019. 3. 28. 09:00

[아두이노] 릴레이 모듈+온도센서+DC모터 응용 



다른 주제로 포스팅을 끝냈는데 @ellenalee 님이 릴레이 모듈로 선풍기를 제어 해보셨다고 하셔서 이걸 한번 비슷하게 구현해보면 릴레이 모듈의 응용으로 괜찮을 것 같아서 가상시뮬레이터에서 느낌만 비슷하게 한번 구현해 보도록 하겠습니다.

1. 회로도 구성


  • 준비물 : relay DPDT 1개, DC Motor 1개, Temperature Sensor[TmP36] 1개, Power Supply, 아두이노우노
  • 내용 : 온도센서의 값에 의해서 모터가 회전할 수 있게 회로도를 구성해 보자.


대충 DC Motor가 선풍기라고 생각해 봅시다. 느낌은 그런데로 비슷해가 표현 했네요.

3. 코딩


  • 사용함수 : analogRead(), map()
  • 내용 : LED 깜박이는 소스를 이용해서 그 값을 제어값으로 해서 Light bulb 깜박이게 해보자.
  • 참고소스 : [아두이노] 온도센서(TMP36) 제어

복습

  • analogRead(아날로그핀) : 아날로그신호값을 읽음
  • map(입력값,입력최소값,입력최대값,출력최소값,출력최대값) : 입력값이 입력범위에 기준에 맞게 출력범위의 해당값을 출력.

변형함수(만든함수)

  • fmap(입력값,입력최소값,입력최대값,출력최소값,출력최대값) : map()함수와 동일하지만 자료형 타입을 float형으로만 변경했습니다. 계산값이 실수형 값이라서 map()함수를 사용하기가 애매해서 인자들을 전부 float형으로 변경했네요.

[ 온도센서 측정 소스 ]

[아두이노] 릴레이 모듈+온도센서+DC모터 응용
void loop(){
  float V =fmap(analogRead(A0),0,1023,0,5); //map함수 원리를 이용한 다이렉트 Voltage계산
   //공식
  //float V = analogRead(A0)*5.0/1023;
 
  float C = (V-0.5)*100;  //섭씨 C = (F-32)*1.8;
  float F = C*9.0/5.0+32; //화씨 F = C*1.8+32;
 
}

float fmap(long x, long in_min, long in_max, float out_min, float out_max)
{
  return (x - in_min) * (out_max - out_min) /(float) (in_max - in_min) + out_min;
}

여기서 대충 온도 C변수 값에 따라서 릴레이모듈를 제어하면 되겠죠

[아두이노] 릴레이 모듈+온도센서+DC모터 응용
   if(C>35){    //35도 이상이면 참
     digitalWrite(2, HIGH); //스위치 켜라
  }
  else{
     digitalWrite(2, LOW);  //스위치 꺼라
  }  

끝! 간단하게 느낌만 살렸습니다.

전체적인 코드를 살펴보면,

[아두이노] 릴레이 모듈+온도센서+DC모터 응용
void setup()
{
  Serial.begin(9600);
  pinMode(7, INPUT_PULLUP);
  pinMode(2, OUTPUT);
}
void loop()
{
  
  float V =fmap(analogRead(A0),0,1023,0,5); //map함수 원리를 이용한 다이렉트 Voltage계산
  float C = (V-0.5)*100;  //섭씨 C = (F-32)*1.8;
  
  if(C>35){    
     digitalWrite(2, HIGH); 
  }
  else{
     digitalWrite(2, LOW);   
  }   
}

float fmap(long x, long in_min, long in_max, float out_min, float out_max)
{
  return (x - in_min) * (out_max - out_min) /(float) (in_max - in_min) + out_min;
}

4. 결과


[ 35도 이하일때 ] : 그림에서는 25도 위치



[ 35도 이상일때 ] : 그림에서는 57도 위치


동영상을 찍어야 했지만 그냥 이미지로 간단히 차이점을 화살표로 표시 해 놨습니다. 25도일때는 0.00A로 전류가 흐르지 않는 상태이고 57도 위치에서는 79.9mA로 전류가 흐르는 상태입니다. 그 차이점으로 확인해주시기 바랍니다.

코딩에서는 35도 이상이면 모터가 회전되게 되어 있습니다. 실제 시뮬레이터로 돌아가는 장면을 보고 싶으면 공개되 회로도에서 바로 시뮬레이터를 실행 시켜서 그 결과를 확인 할 수 있습니다.


링크된 곳으로 가면 위 그림과 같은 창이 뜹니다. 거기서 1번 눌러주셔서 2번의 시뮬레이터 실행버턴의 창이 뜹니다. 그리고 2번을 누르시면 실행을 시킬 수 있습니다. 한번 온도조절을 해보시고 결과를 확인해 보세요.

마무리


상상이 어렵지만 표현은 그렇게 어렵지 않습니다. 매번 상상의 나래를 펼치라는 멘트를 날리는 이유가 바로 그 이유입니다. 온도에 따라서 선풍기를 자동을 켜볼까라는 상상력이 뭔가를 만들어 냅니다.

여러분들도 어떤 원리를 배우게 되면 그 원리에서만 멈추지 말고 상상력을 더해서 새로운 뭔가를 창조할 수 있는 능력을 키우시면 엄청난 결과를 얻을 수 있을 거에요.

언제나 그럴듯이 상상의 나래를 펼쳐 보세요.

댓글()