[아두이노] 아두이노 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하는 소스 코딩은 그냥 따라만 하면 제 코딩 스타일에 여러분들은 갇히게 됩니다. 자신의 코딩 스타일을 찾지 못하게 되는 것이죠. 이방법, 저방법을 시도하면서 자신만의 코딩 스타일을 만들어 내는 것이 무척 중요하니깐 꼭 여러가지 방법으로 접근해 보시면서 자신만의 스타일을 만드셨으면 합니다.


댓글()