[아두이노] 아두이노 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카에 어떤 부품을 연결하여 재밌는 것을 만들지 한번 상상의 나래를 펼쳐보세요.


댓글()