[아두이노] Serial 통신 때 String 사용

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

[아두이노] Serial 통신 때 String 사용



오늘은 간단히 Serial 통신을 할 때 String으로 접근하는 방법을 알아보고자 합니다. 지금까지는 간단히 Sensor의 값을 Serial 통신을 통해 값을 전송할 때는 하나의 값만 주고 받아 왔습니다. 조이스틱에서는 두개의 x,y값을 만들어 내지만 이것 역시 간단히 하나씩 키 값으로 해서 전송해 왔는데 이제는 여러개의 값을 한번에 전송하고 그 값을 수신하는 쪽에서 분리해 내는 과정을 설명하면 좋을 것 같아서 오늘 Post 주제로 결정했습니다. 대부분 실험이 한개의 데이터만 Serial 통신에 주고 받기 때문에 필요 없을 수 도 있지만 한번은 접해 놓으셔야 나중에 필요하실 때 기억해 내서 활용 할 수 있으니깐 한번은 접해 보셨으면 합니다. 이제부터서 String에 대해 이야기를 하겠습니다.

1. Serial 통신 함수


[시리얼 통신]

  • Serial.begin(9600) : 시리얼 통신 시작(9600 통신속도)
  • Serial.println(값) : 시리얼모니터 출력
  • Serial.available() : 시리얼통신으로 데이터가 들어놨는지 상태 확인
  • Serial.read() : Int형으로 데이터를 읽음

[Bluetooth(SoftwareSerial 통신)]

#include <SoftwareSerial.h>
  • SoftwareSerial mySerial (rx, tx) : 소프트시리얼 객체선언(rx(수신), tx(전송))
  • mySerial.begin(9600) : 시리얼 통신 시작(예로 9600 통식속도를 사용해 봤네요.)
  • mySerial.println(값) : 데이터 전송
  • mySerial.read() : Int형으로 데이터를 읽음

[기본소스]

Serial 통신

void setup() {
  Serial.begin(9600);    //시리얼 통신 9600 통신속도로 시작
}
void loop() {
  
  if (Serial.available() > 0) { //데이터가 수신되는지 확인
    char ch = Serial.read(); //1byte 읽음
    Serial.println(ch); //1byte 읽은거 출력
  }
}

SoftwareSerial 통신

#include <SoftwareSerial.h>

const int rx = 2; //Bluetooth TX 핀
const int rx = 3; //Bluetooth RX 핀
SoftwareSerial mySerial (rx, tx) : 소프트시리얼 객체선언(rx(수신), tx(전송))

void setup() {
  mySerial.begin(9600);    //시리얼 통신 9600 통신속도로 시작
}
void loop() {
  
  if (mySerial.available() > 0) { //데이터가 수신되는지 확인
    char ch =mySerial.read(); //1byte 읽음
    mySerial.println(ch); //1byte 읽은거 출력
  }
}

기본 동작은 Serial.read() 함수로 1byte씩 읽어와 다시 시리얼모니터로 1byte을 출력하는 방법은 예전에 post로 설명을 했었습니다. 복습차원으로 다시 한번 살펴 봐 주세요. 그리고 Bluetooth의 SoftwareSerial 통신도 Serial에 mySerial로 작명한 Serial 객체변수명으로 접근하는데 기본 함수명과 동작은 동일합니다. Serial 통신을 할 수 있으면 SoftwareSerial로 Bluetooth 통신도 할 수 있겠죠.

복습이 끝났다면 본격적으로 사용할 String 대해서 살펴보도록 하죠.

2. String 함수



아두이노 공식 홈페이지에 가면 String 함수들이 나열 되어 있습니다. 링크된 곳에 가셔서 한번씩 테스트 해보셨으면 합니다.
오늘 post에 사용 할 몇개 String 함수에 대해서 살펴보도록 하겠습니다.

위 링크쪽을 가시면 Serial 통신 시 관련 함수들이 있습니다. 문자열을 Serial 통신을 통해 읽을때 readString(), readStringUntil() 두개의 함수가 있습니다. 둘 다 사용해도 되지만 readStringUntil()함수를 사용합니다.

 if(Serial.available()){
    String inString = Serial.readStringUntil('\n');
 }

문자열 변수 선언

  • String inString : 문자열 객체변수 선언

문자열 Serial 통신 읽기

  • Serial.readStringUntil('\n') : '\n' 문자를 만날때까지 문자열을 읽는 함수

문자열 공백 제거

  • String.trim() : 문자열변수안에 공백을 제거

문자열 분리에 사용할 함수

  • String.indexOf('찾을문자') : 문자열에 '찾을문자'가 있는 위치(index) 값을 반환
  • String.indexOf('찾을문자',시작위치) : 문자열의 시작위치에서 시작하고 '찾을문자'가 있는 위치(index) 값을 반환
  • String.substring(시작위치, 종료위치) : 시작~종료위치까지의 문자열을 반환한다. 전체 문자열에서 부분 문자열 추출.

3. 문자열 분리


String inString = "111,222";
int index1 = inString.indexOf(','); 
Serial.println(index1);   

결과 : 3


이렇게 하면 inString에 저장된 "111,222" 문자열에서 ','가 있는 위치값 '3'을 반환합니다. 위치는 0부터 시작하니깐 네번째인 3이 반환됩니다. 혼동하지 마세요. 여기서 ','를 기준으로 "111"과 "222"의 문자열을 분리해 낼려면 substring()함수를 사용하게 됩니다.

String inString = "111,222";
int index1 = inString.indexOf(','); 
int index2 = inString.length();
String inString1 = inString.substring(0, index1);
String inString2 = inString.substring(index1+1,index2);
Serial.println(inString1);   
Serial.println(inString2);   

결과 :
111
222

여기서, 문자열이 숫자면 숫자형으로 변환 시켜야 합니다. 그냥 사용 한다면 문자열이지 숫자형이 아닙니다. 문자열을 숫자형으로 변환 하기 위해서는 다음과 같은 함수를 사용해야 합니다.

  • String.toInt() : 문자열을 정수형으로 변환
String inString = "111,222";
int index1 = inString.indexOf(','); 
int index2 = inString.length();
int inString1 = inString.substring(0, index1).toInt();
int inString2 = inString.substring(index1+1,index2).toInt();
Serial.print(inString1);   
Serial.print('+');  
Serial.print(inString2);  
Serial.print('=');  
Serial.println(inString2+inString2);  

결과 : 111+222=333

int inString1 = inString.substring(0, index1).toInt();
자료형 변수 = 전체문자열.부분문자열추출.정수형변환;

inString의 문자열 "111,222" 값에서 substing(0,3)함수로 "111"문자열이 추출되고 이 값을 toInt()로 정수형으로 변환 합니다.

substring(from, to)함수의 혼동 주의를 하세요. substring(인자1, 인자2)에서 두 인자는 위치를 가리키지만 정확히 말하면 인자1과 인자2의 의미는 좀 다른 것 같습니다. 문자열이 0부터 시작한다고 했죠. 그러면, 시작 위치가 0부터 하셔야 첫번째 문자 '1'의 값을 가리키게 됩니다. 그런데 뒤에 콤마(,) 문자의 위치를 가리키면 위치값이 3이 됩니다. substring(0,3)의 문자열을 추출하게 됩니다. 이럴 때 3이니깐 4번째 콤마(,)까지 추출되어 나오는 거 아냐 하실 수 있는데 그 전 문자열 index(2)까지의 부분 문자열을 추출합니다. 간혹, 콤마(,)의 위치가 3이니깐 필요한 문자열이 "111"로 index(0~2)의 문자 3개가 필요하니깐 콤마(,) index 값에서 -1을 해서 부분문자열을 추출하려고 잘못된 코딩을 하실 수 있습니다. 처음에는 이런 문제로 시행착오를 거치실 수 있지만 금방 문제의 원인을 찾아서 수정하실 수 있을 꺼에요. 하지만, 실제 실험을 안하고 글을 읽고 상상코딩을 하신다면 이부분을 고려하시고 상상코딩을 하셔야 합니다.

substring(인자1, 인자2)함수가 좀 그렇더군요. 명확하게 인자2를 문자열의 정확한 추출위치 인자 index로 해 놓았다면 좋았을 것을 왜! 이런식으로 표현했는지 아쉬움이 남는 함수입니다. 마지막 널문자를 나타내기 위한 의도인지 아니면 뒤에 인자2는 몇번째 인지를 나타내는 숫자인지 참 모호한 인자인 것 같아요. 나중에 문자열을 좀 더 많은 데이터를 쪼갤때 혼동하실 수 있으니깐 잘 기억해 두세요.

예) String inString = "1,2,3,4,5";

콤마(,)인덱스는

int index1 = inString.indexOf(',');
int index2 = inString.indexOf(',',index1+1);
int index3 = inString.indexOf(',',index2+1);
...

이렇게 각 콤마(,) 문자의 위치를 얻고 이것을 분리해 낼때는
int inString1 = inString.substring(0, index1).toInt();
int inString2 = inString.substring(index1+1, index2).toInt();
int inString3 = inString.substring(index2+1, index3).toInt();
...
이렇게 해야하는데 인덱스 위치를 혼동해서 잘못 위치를 지정하게 될 수 있으니 주의하세요. 혼동을 피하기 위해서는 인자1은 배열 index 위치라고 생각하고 인자2은 문자열의 몇번째 위치라고 생각하시면 될 듯 싶네요. 이게 더 혼동이 될려나 모르겠군요.

indexOf(0,3) 이면 배열[0]에서 문자열 3번째 배열[2]까지의 부분 문자열을 추출한다. 이렇게 전 정리했네요. 아무튼 요상한 함수입니다. 이게 원래는 배열[0]~배열[3]까지 부분 문자열을 추출하는데 마지막 배열[3]은 문자열 끝을 나타내는 널문자가 들어가기 때문에 실제로 문자열 배열[0]~배열[2]까지의 글자가 출력되는지 모르겠지만요. 좀 혼동되는 함수이니깐 주의해서 코딩해 주세요.

이야기가 삼천포로 빠졌지만 계속 이야기를 이여 가겠습니다.

자이로센서와 같은 센서를 사용할 경우 값이 실수형으로 표현될 경우 문자열을 실수형 값으로 전송하게 된다면 다음과 같이 변경하시면 됩니다.

  • String.toFloat() : 문자열을 실수형으로 변환

예) String inString = "111.11,-222.22";

이와 같이 문자열이 주어졌다면

float inString1 = inString.substring(0, index1).toFloat();

결과 : 111.11

이렇게 문자열을 실수형으로 변환하고 그 값을 실수자료형 변수에 저장하게 됩니다.

4. Serial 통신으로 테스트



간단히, 시리얼 모니터로 두개의 실수형 데이터 x,y 값을 Serial 통신을 통해 문자열로 한번에 전송한다고 가정하고 이 두값을 구분하는 문자 콤마(,)로 구분자를 만들어 보낸다고 설정 했습니다. Serial 통신을 통해 문자열을 읽고 그 문자열을 다시 x,y값으로 분리해 내서 각 데이터를 실수형 변수에 저장하고 정상적으로 분리가 되었는지 시리얼모니터로 출력하는 소스입니다.

분리된 문자열이 실수형으로 정확히 변환이 되었는지 확인하기 위해서 간단히 두 실수값을 더한 값을 시리얼모니터로 출력하여 확인합니다.

[소스]

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

void loop()
{  
  if(Serial.available()){
    String inString = Serial.readStringUntil('\n');    
        
    int index_x = inString.indexOf(',');     
    int index_y = inString.length(); 
    float x = inString.substring(0, index_x).toFloat();     
    float y = inString.substring(index_x+1,index_y).toFloat(); 
 
    Serial.print(x);
    Serial.print('+');
    Serial.print(y);
    Serial.print('=');
    Serial.println(x+y);
  } 
}

[결과]


마무리


오늘은 간단히 Serial 통신으로 여러개의 데이터를 하나의 문자열로 보내지면 그 문자열을 다시 여러개의 데이터로 분리해내는 과정을 실험 하였네요.

이 원리는 다양한 데이터를 측정할 때 그 값을 한번에 전송하는데 사용하면 좋습니다. 예를 들어, MPU6050 자이로센서의 경우는 가속도 x,y,z 온도, 각속도 x,y,z 값으로 총 7개의 데이터를 측정하게 됩니다. 이것을 하나씩 개별적으로 보낸다면 불편하겠죠. 이 데이터 7개를 한번에 보내고 형식에 맞춰 분리하여 원하는 동작 제어를 한다면 편하게 제어 할 수 있게 됩니다.

하나씩 전송하게 되면은 데이터를 읽을 때 x값인지 y값인지 구분해서 읽는 코딩은 좀 복잡해집니다. 하지만 이렇게 문자열로 보내고 문자열로 읽고 해당 x, y값의 위치 문자열에서 분리해 내서 읽게 되면 좀 더 편하게 코딩을 할 수 있습니다. 사실 문자열 Serial 통신을 사용할 경우는 극히 드물지만 참고로 이런게 있다는 것만 알아만 두세요


댓글()

[아두이노] 코딩의 잘못된 습관

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

[아두이노] 코딩의 잘못된 습관



초보분들이 대개 코딩을 할 때 잘못된 습관이 있는데 간단히 살펴보도록 하겠습니다.

1. 코딩의 잘못된 습관


초보분들은 대체적으로 코딩을 할 때 한번에 전체 코딩을 하고 전체 코딩한 것을 한번에 컴파일하여 실행 시킵니다. 그러다 에러가 발생하면 어디서 에러가 발생하는지 찾지 못하는 경우가 많습니다.


위 그림처럼 초보분들은 한번에 A코딩하고 B코딩하고 C코딩하고 이렇게 자신이 표현하고 싶은 코딩을 쭉 써내려 갑니다. 그러다가 실제 컴파일을 하게 되면 에러가 발생할 때 A에서 에러가 발생하는지 B에서 에러가 발생하는지 C에서 에러가 발생하는지 잘 모르는 경우가 종종 발생합니다. 본인이 코딩한 로직에 대해 본인이 해독하지 못하는 경우는 대부분 이런식으로 코딩하기 때문에 발생합니다.

코딩을 한번에 전체 코딩을 하려고 하는 습관 버려야 합니다. 코딩을 수집줄 이상 코딩을 하고 그 코딩을 컴파일해서 결과를 확인하는 것은 가장 마지막 단계에서 수행하는 작업입니다. 그런데 초보분들은 코딩을 시작하는 단계에서 부터 현재 코딩한 전체 단위로 한번에 컴파일하여 실행 시키려고 하고 새로운 로직을 만들어 삽입 할 때도 바로 전체 코딩한 곳에다 넣고 무조건 컴파일을 시킵니다. 그러다 보면 코딩량은 늘어나고 실행 시 에러가 발생할 가능성이 높아집니다. 에러가 발생할 때 초보분들은 쉽게 에러의 위치를 찾지 못하는 이유가 바로 이런 이유 때문입니다. 전체 코딩을 했는데 어디서 잘못되었는지 몰라서 대부분 인터넷 프로그램언어 관련 카페에 가서 소스를 전체 올려놓고 에러를 찾아주세요 라고 질문을 던지게 됩니다. 문제가 발생한 에러의 상당 부분들은 본인이 직접 코딩한 로직을 부분 단위로 나눠서 컴파일하여 체크하면 쉽게 해답을 찾을 수 있는 경우가 많은데 전체를 한번에 컴파일하고 발생한 에러를 찾을려고 하니 사소한 에러도 어렵게 느껴지게 됩니다. 이렇게 잘못된 코딩습관 때문에 프로그램언어를 처음 배우는분들이 언어를 어렵게 생각하는 주된 이유입니다.

해결책은 다음과 같습니다.

부분컴파일 하기

코딩하는 방법은 전체 코딩 할 곳에다 코딩을 하기전에 먼저 할 일은 전체 코딩 할 곳에 코딩할 바로 삽입해서 테스트 하지 말고 따로 여러분들이 해당 코딩만 컴파일을 해보고 나서 전체 소스에 삽입해야 합니다. 그래야 자신이 코딩한 소스에 대해 어디서 에러가 발생하는지 쉽게 알 수 있게 됩니다. 적상적으로 해당 명령문 코딩이 문제가 없을 때 전체 소스에 삽입하시면 전체소스는 에러가 발생을 하지 않습니다.


위 그림처럼 A, B 코딩이 전체 소스에 삽입되어 정상적으로 A, B까지 실행이 될 때 C코딩을 바로 전체 소스에 코딩해서 컴파일 하지 말고 C코딩을 먼저 컴파일 해보고 나서 정상적으로 아무 문제가 없으면 그 때 전체 소스에 삽입하셔야 합니다.

위에 A, B, C 코딩이 큰틀에서 각 코딩 부분을 컴파일도 해야 하지만 좀 더 세부적으로 명령문 단위로 컴파일을 하셔야 합니다. 구지 컴파일 까지 필요 없는 명령문들은 넘어갈 수 있지만 명령문이 정상적인 명령문인지는 새로운 코딩창에서 가상 데이터을 명령문에 대입하고 print문에 그 결과를 출력해 보면서 정상적으로 명령문이 수행되는지를 체크하시고 나서 실제 코딩하는 곳에 삽입하셔야 합니다.


이런식으로 컴파일을 해보고 나서 정상적으로 작동 했을 때 C 코딩 소스에 삽입하고 다시 C코딩을 컴파일 한 뒤에 실행 하셔서 문제가 없다면 전체 소스에 삽입하시면 됩니다. 그렇게 코딩을 하셔야 소스에 에러가 발생해도 어떤 에러인지 쉽게 찾을 수 있고 그 문제에 대한 해답을 인터넷에서 충분히 본인 스스로 답을 찾을 수 있습니다. 아니면 인터넷 프로그램 언어 관련 카페에 가셔서 질문을 하더라도 정확한 질문을 할 수 있고 필요한 답변을 얻을 수 있게 됩니다.

2. 잘못된 질문 습관


자신이 코딩한 소스에 에러가 발생하여 해결책을 찾지 못하면 대개 프로그램언어 관련 카페 게시판에 질문을 하게 됩니다. 대학 레포트 시즌이 되면 집중적으로 과제 관련 코딩 질문들이 쏟아져 나옵니다. 대부분 소스를 짜집기해서 코딩한 내용으로 코딩 로직 자체를 이해 못하거나 또는 본인이 코딩을 했어도 한번에 전체 코딩을 할 때 어디에서 에러가 발생하는지조차 알지 못하는 경우가 발생합니다. 그리고, 질문게시판에 전체소스를 가져와서 "해독해주세요. 에러를 찾아주세요."라는 질문들이 많습니다. 이런 질문은 진짜 잘못된 질문 입니다.

재밌는 것은 과거에도 그렇고 현재에도 그렇고 내년에도 그렇고 내후년에도 전체 소스를 올려놓고 에러를 찾아주세요라는 질문은 반복됩니다.

이런 잘못된 질문을 하는 경우는 둘 중 하나입니다. 짜집기 소스이거나 또는 부분 컴파일로 테스트 해보지 않고 한번에 전체 로직을 코딩한 경우입니다. 그래서, 에러가 발생해도 에러의 위치를 찾지도 못하고 그냥 막연하게 전체 소스를 올려놓고 에러를 찾아주세요라고 질문을 던지게 됩니다. 그런 질문의 대부분 에러가 발생한 곳을 살펴보면 충분히 본인 스스로 답을 찾을 수 있는 에러의 문제들인 경우가 많습니다. 한번에 전체 코딩을 하다 보면 오타가 났거나 또는 잘못된 변수 표현이나 또는 A로직에서 B로직으로 넘어가는 과정에서 잘못된 값이 넘어거나 아니면 연결이 잘못된 경우가 많습니다. 아니면 A, B, C 코딩중에 특정 코딩에 명령라인 자체가 잘못 처리 되는 경우도 있습니다. 충분히 처음 코딩할 때 부분 컴파일을 해서 테스트 해보고 전체 소스에 삽입 시켰다면 이런 문제는 아예 생기지도 않았겠지요. 그냥 한번에 코딩을 하다보니깐 자신이 짠 코딩에 임에도 소스 로직 자체를 이해 못하는 경우가 초보분들에게 발생합니다.

올바른 질문은 다음과 같습니다.

올바른 질문을 하기 위해서는

코딩을 전체 소스를 본인이 짜거나 짜집기를 했거나 긁어왔을 때 에러에 대한 질문을 하기 전에 먼저 다음과 같은 작업을 수행해 주세요.


전체 소스에서 해당 코딩만 남기고 나머지는 다 주석 처리를 해주세요. 그리고 A라는 코딩을 컴파일 합니다. 그리고 컴파일 한 결과를 print문으로 출력해주시면 됩니다. 그래서 A라는 코딩의 동작을 파악하시면 됩니다. A라는 코딩이 외부로 부터 입력값이 있으면 변수로 임의의 가상값을 선언해주고 A코딩에 넘겨줘서 컴파일을 하셔서 결과를 확인하시면 됩니다. 결과가 원하는 방향으로 안나왔다면 해당 A코딩은 문제가 있는 코딩이겠죠. 그럼 다시 A코딩에서 의심가는 명령문 라인을 제외한 나머지 명령문 라인을 주석처리 해주세요. 그리고 나서 해당 명령문 라인만 방금 했던 방식으로 컴파일을 하시면 됩니다. 아니면 전체 소스에 대해서 주석 처리가 귀찮을 때는 새로운 코딩창에다가 해당 A코딩 부분만 복사해와서 해당 코딩을 컴파일 하시면 됩니다. 이렇게 부분 컴파일로 테스트 하시면 대부분 본인 스스로가 에러의 원인을 찾을 수 있으며 에러의 해결책도 인터넷에서 조금만 검색하시면 찾을 수 있게 됩니다. 그리고, 계산과 같은 처리가 이뤄지는 곳에는 무조건 print명령을 통해서 어떤 값이 찍히는지 정상적인 값인지 꼭 체크하셔야 합니다.

그래도 못찾을 때는 해당 명령문이나 해당 부분 코딩부분을 보여주고 뭘 표현하고 싶었는지 명확하게 질문을 던지시면 됩니다. 그러면 그 질문을 본 사람들이 해당 코딩보다 더 괜찮은 알고리즘을 소개해 줄 수 있고 또는 해당 질문의 해답을 쉽게 알려 줍니다.. 질문자가 질문을 제대로 했을 때 원하는 답을 빠르게 얻을 수 있게 됩니다.

마무리


오늘 post의 목적은 "기승전-부분컴파일" 입니다. 여러분들이 뭔가를 표현하고 로직을 짤 때 한번에 그 로직 전체를 컴파일해서 결과를 보려하지 마시고 어떤 알고리즘을 짜면 그 알고리즘에 코딩되는 명령문 라인을 새로운 창에서 개별적으로 컴파일하는 습관을 가져주세요. 임의의 가상값을 해당 명령문에 대입하고 그결과를 print문으로 출력해서 체크를 하고 정상적인 원하는 결과가 나왔을 때에 메인코딩창에 명령문을 삽입해주는 습관을 가져줬으면 하네요. 그리고 코딩을 해놓고 나서 새로운 명령문들이 떠오를 때 중간에 삽입할 경우에는 바로 삽입하지 말고 해당 구간의 일부 코드를 복사 해와서 새로운 코딩과 합쳐도 에러가 발생하지 않는지 체크한 뒤에 합쳐주시기 바랍니다.

이렇게 하셔야 에러가 발생해도 대부분 본인 스스로가 그 문제에 대한 해답을 쉽게 찾을 수 있게 됩니다.


댓글()

[아두이노] 아두이노 코딩을 쉽게하는법

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

[아두이노] 아두이노 코딩을 쉽게하는법



오늘은 아두이노를 코딩를 하는법을 배워보도록 하겠습니다. 아두이노를 코딩할 때 너무 복잡하게 생각하지 마시고 쉽게 접근하는게 가장 중요합니다.

1. 관찰


대상(부품)에 대한 관찰이 필요합니다. LED의 경우는 전류가 공급되면 불이 들어오고 전류가 차단되면 불이 꺼집니다. LED의 경우는 두가지 패턴만 존재합니다. 일상에서의 LED와 유사한 대상을 찾아보면 뭐가 있을까요. 전자시계, 전자렌지, 냉장고, 스마트폰, TV, 신호등, 전광판 등 모든 전자기기에 부착 되어 있습니다. 어떤 전자기기든 상관없으며 아무거나 하나를 선택해서 LED의 변화를 관찰 해보세요. 예로, 신호등에 대해 관찰해 봅시다. 초록->황색->적색순으로 일정 시간 단위로 3색 신호등이 깜박이게 됩니다. 관찰이 끝났으니깐 이제 그 동작에 대해서 기록해 볼까요.

2. 기록


3색 신호등이 깜박이는 순서와 시간을 기록합니다. 너무 길기 때문에 짧게 초록 3초, 황색 2초, 적색 5초라고 가정해 봅시다.

순서를 기록해볼까요.

(1) 초록 켜진다.
(2) 초록 3초동안 켜져있다.
(3) 초록 3초 후 꺼진다.
(4) 황색 켜진다.
(5) 황색 2초동안 켜져 있다.
(6) 황색 2초 후 꺼진다.
(7) 적색 켜진다.
(8) 적색 5초동안 켜진다.
(9) 적색 5초 후 꺼진다.

자신이 원하는 동작을 기록을 하든 아니면 이처럼 관찰을 통해서 얻은 동작을 그대로 기록하시면 됩니다. 어떻게 생각하지 마시고 신호등이 어떻게 동작하는지 관찰하고 그 동작을 글로써 한줄씩 간단하게 써 내려가면 됩니다. 동작을 한줄에 많이 쓰지 마시고 간단히 위처럼 하나의 동작을 한줄씩 써내려가면 됩니다.
어느정도 감각이 붙으면 여러개의 동작을 한줄로 표현해도 되지만 초보분들은 위처럼 한동작에 한줄씩 기록하는 습관을 가져보세요. 그러면 한동작에 대한 아두이노 함수로 간단히 코딩으로 변환시키면 되기 때문에 코딩이 어렵지 않을 꺼에요.

3. 실험에 사용할 부품 이해


LED 부품을 실험에 사용 할 경우 LED은 아두이노에서 어떻게 코딩하는지 살펴볼까요. LED는 포함시킬 라이브러리가 없습니다. 아두이노 자체 함수이기 때문입니다. 아두이노에서 제공되는 기본 함수의 경우는 변수 선언과 시작 or 초기화 부분과 사용에서 실제로 LED핀에 명령을 내리게 됩니다.

(1) 포함 :

#include<라이브러리.h> //LED 필요없음 생략

(2) 변수선언:
const int RedPin = 13;

(3) 시작 or 초기화 :
pinMode(RedPin, OUTPUT) : RedPin을 OUTPUT 모드로 선언한다.

(4) 사용:
digitalWrite(RedPin, 핀상태) : 핀상태는 HIGH(5V) or LOW(0V)

예) Bluetooth 경우는
(1) 포함 :

#include <SoftwareSerial.h>

(2) 변수선언:
const int rxPin = 2;
const int txPin = 3;
SoftwareSerial mySerial(rxPin, txPin); // RX, TX

(3) 시작 or 초기화 :
mySerial.begin(9600);

(4) 사용:
mySerial.available();
mySerial.read();
mySerial.println("Hellow");
... 등등

예) Servo Motor 경우는
(1) 포함 :

<
#include <Servo.h>

(2) 변수선언:
Servo servo;
const int SERVO_PIN = 7;

(3) 시작 or 초기화 :
servo.attach(SERVO_PIN);

(4) 사용:
servo.write(180);

대충 부품을 이해하실 때 이런식으로 이해하시면 됩니다. 머리 속에서 어떤 특정 부품을 사용하고자 하면 위 순서를 머리속에서 라이브러리를 추가되는지 변수를 외부에 선언되는지 시작 or 초기화 작업이 있는지 사용은 어떤 함수로 하는지에 대해서 해당 부품에 대해 생각하시면 됩니다. 이렇게 하면 둘 이상의 부품을 사용하더라도 어렵지 않게 선언하고 사용하실 수 있습니다.

4. 코딩


3색 신호등을 코딩을 해볼까요. LED 부품를 사용하여 위에 기록된 내용을 코딩해 볼까요.

digitalWrite(초록핀, HIGH);
delay(3초);
digitalWrite(초록핀, LOW);
digitalWrite(황색핀, HIGH);
delay(2초);
digitalWrite(황색핀, LOW);
digitalWrite(적색핀, HIGH);
delay(5초);
digitalWrite(적색핀, LOW);

기록된 글을 순서대로 프로그램 언어 명령으로 표현을 했습니다. 이렇게 먼저 표현하고자 하는 것에 대해서 관찰하고 그 관찰한 동작을 기록합니다. 그리고 기록한 것을 표현한 부품을 선택하고 선택된 부품에 대한 기본 사용법을 위와 대한 4가지로 분류해서 머리속에 정리해놓으신 후에 기록된 순서대로 글을 명령코딩으로 변경만 해주시면 됩니다.

4. 부품 추가 코딩


황색 LED가 불이 들어올 때 경고 음을 울리게 하고 싶다면 어떻게 해야 할까요. 똑 같이 위 과정을 반복합니다.

(1) 관찰 : 황색 LED가 불이 들어올 때 경고음이 삐! 삐! 하고 1초 단위로 소리가 울린다고 상상 관찰을 해봅시다.
(2) 기록 :
초록 켜진다.
초록 3초동안 켜져있다.
초록 3초 후 꺼진다.
황색 켜진다.
황색 2초동안 켜져 있다.
경보음 1초
경보음 1초
황색 2초 후 꺼진다.
적색 켜진다.
적색 5초동안 켜진다.
적색 5초 후 꺼진다.

(3) 실험에 사용 할 부품의 이해 : 피에조부저를 사용한다.

  • 포함 : 라이브러리 필요 없음
  • 변수선언:
    const int TONEPIN = 6;
  • 시작 or 초기화 :
  • 사용:
    tone(TONEPIN ,523,1000/8); // 도음으로 8분음표(음길이)
    delay(1000/4*1.30);
    noTone(TONEPIN);

(4) 코딩 :

digitalWrite(초록핀, HIGH);
delay(3초);
digitalWrite(초록핀, LOW);
digitalWrite(황색핀, HIGH);
delay(2초);
digitalWrite(황색핀, LOW);
digitalWrite(적색핀, HIGH);
delay(5초);
digitalWrite(적색핀, LOW);

위의 기존의 코딩에서 기록한 해당 위치에 경보음을 표현한다.

digitalWrite(초록핀, HIGH);
delay(3초);
digitalWrite(초록핀, LOW);
digitalWrite(황색핀, HIGH);
tone(TONEPIN ,523,1000/8);     // 도음으로 8분음표(음길이)
delay(1000/4*1.30);             
noTone(TONEPIN); 
one(TONEPIN ,523,1000/8);     // 도음으로 8분음표(음길이)
delay(1000/4*1.30);             
noTone(TONEPIN); 
digitalWrite(황색핀, LOW);
digitalWrite(적색핀, HIGH);
delay(5초);
digitalWrite(적색핀, LOW);

여기서, 주의 할 점은 코딩과 코딩이 결합시 한쪽 코딩이 다른쪽 코딩에 미치는 영향을 파악하시고 결합하시면 문제가 생기더라도 쉽게 해결할 수 있습니다. 위 코딩에서 피에조부저를 결합할 때 그냥 황색 LED 코딩 사이에 삽입하면 안됩니다. 피에조부저 딜레이가 황색LED 딜레이에 영향을 주기 때문에 총 4초의 딜레이가 발생합니다. 고로, 피에조부저 delay()함수를 사용하고 황색 LED delay()함수를 제거하면 2초의 딜레이로 해결 되는 코딩입니다. 이처럼 두 부품이 결합할 때 각 부품의 동작에 이해하고 계시면 여러개의 부품을 연결하더라도 쉽게 결합시킬 수 있습니다.

마무리


간단히 신호등 예제를 통해 코딩하는 법을 살펴 보았습니다. 뭔가를 표현하고 싶을 때 그 표현에 대해서 일상에 비슷한 대상을 찾거나 상상을 통해서 동작에 대한 관찰이 필요합니다. 그리고나서, 여러분들이 그것을 표현하기 위한 동작을 글로써 기록합니다. 그 다음 사용할 부품을 선택하고 그 부품에 대한 기본 4가지로 분류한 형태로 해당 부품을 이해하십시오. 그다음에 기록 된 명령의 글을 사용함수들을 사용하여 그대로 글을 명령 코딩으로 대입하시면 됩니다. 하나의 부품을 정상적으로 사용할 수 있게 되면 다른 추가 부품을 사용할 때에도 방금 했던 과정을 반복하셔서 코딩하면 쉽게 코딩할 수 있게 됩니다.

어렵게 생각하지 마시고 개별 부품으로 생각을 하면서 정리 해 놓고 나서 여러분이 표현하고자 하는 것에 대해서 관찰과 기록을 하시고 나서 기록에 대해 부품의 함수로 명령코딩을 하시면 됩니다. 아두이노 코딩은 기존에 만들어 놓은 함수나 제공되는 함수 함수를 가져다가 사용하시기만 하면 됩니다. 아두이노는 어렵게 생각하지 마시고 쉽게 생각하고 접근하시면 됩니다.


댓글()

[아두이노] RFID-RC522+Bluetooth+초음파 센서 실험

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

[아두이노] RFID-RC522+Bluetooth+초음파 센서 실험



지난 시간에 RFID-RC522 + Servo Motor를 제어하는 실험을 하였습니다. 오늘은 몇가지 상황을 정하고 그 상황에 맞게 아두이노를 제어하는 실험을 하고자 합니다.

첫번째, 실험 내용은 RFID-RC522는 카드를 읽는 동작만 수행하는 아두이노와 Servo Motor를 회전 시키는 아두이노로 나누고 싶은 때가 있을 수 있습니다. 역할 분담으로 설계를 하고 싶을 때 그 문제를 해결 하기 위해서 두 아두이노 사이의 통신을 통해서 메세지를 주고 받으면 됩니다. 통신 중의 무선 통신 모듈인 Bluetooth 통신을 접목하여 실험을 하고 싶었습니다. 실제 실험의 내용은 Card가 저장된 UID랑 일치하면 Bluetooth를 통해 스마트폰에 메세지를 전송하는 과정을 실험 하였습니다. 이렇게 메세지를 전송할 수 있다면 다른 아두이노가 있으면 그 아두이노가 블루투스로 메세지를 수신하고 그에 따른 회전 명령을 수행하면 간단히 역할을 나눠서 동작할 수 있게 됩니다. 실제로 역할 분담 실험을 하기에는 부품이 부족하여 아쉽게 실험을 할 수 없고 해서 무선 통신으로 스마트폰에 메세지를 전송하는 데 까지만 실험을 해도 충분히 실험의 목적을 달성 할 수 있기 때문에 이정선에서 실험을 하기로 결정 했습니다.

두번째, 실험 내용은 RFID-RC522 리더기로 Card를 읽고 Servo Motor가 회전 되었을 때 다시 원위치로 되돌아 가게 하는 방법을 실험하고자 합니다. 실험에 쓰인 부품은 초음파센서 입니다. Servo Motor를 출입문이라고 가정하고 Card가 인식되면 180(Open)도 회전 후 사람이 출입문을 지나가고 나면 출입문을 통과 된 상황을 설정하면 자동으로 Servo Motor가 다시 0(Close)도 회전 되어 문이 닫히게 하면 좋을 것 같아서 상상해 보았습니다. 그 역할을 초음파센서를 이용할 예정입니다. 상상을 더하면 초음파센서가 인간이 지나기 전에 가지고 있는 거리측정값에서 인간이 지나가면 초음파센서의 거리측정값이 작아집니다. 인간를 감지한 값이 됩니다. 이때 초음파 센서값을 작아졌을 때 Servo Motor를 0(Close)도 회전 시켜서 문이 닫힌다는 설정입니다. 초음파센서의 위치는 안전위치에 문이 닫혀도 되는 위치에 설치 해야 겠지요.

1. RFID-RC522의 Bluetooth 통신



실험은 RFID-RC522 리더기로 Card를 인식하면 그 인식한 값을 Bluetooth를 이용하여 스마트폰에 전송하는 실험을 하겠습니다. 본 실험의 목적은 RFID-RC522 모듈을 사용하면서 이 모듈을 통해서 얻은 데이터를 외부 다른 기기로 전송하기 위해서 입니다. 지금 하고자 하는 실험은 Bluetooth 통신으로 외부로 기기(스마트폰)으로 RFID-RC522 리더기의 결과를 보낼 수 있다면 나중에 아두이노 두대로 나눠서 Bluetooth 통신으로 하나는 RFID-RC522 리더기 제어하고 하나는 Servo Motor제어를 하게 할 수 있게 됩니다.


1) RFID-RC522 + ServoMotor+피에조부저+Bluetooth 회로도



  • 준비물 : RFID-RC522, Servo Motor, 피에조부저, Bluetooth, 아두이노우노
  • 내용 : SPI 통신을 할 수 있게 핀은 연결하고 Servo Pin 7번, 피에조부저 6번으로 연결해 보자. 2,3번 핀을 Bluetooth핀으로 사용.
  • 지난시간 회로도 참고 : [아두이노] RFID-RC522 제어

  • BlueTX - arduinoRx - 2번
  • BlueRX - arduinoTx - 3번

출처 : Fritzing

지난 시간의 회로도에서 Bluetooth 모듈이 추가로 더 연결된 회로도 입니다. 2,3번 핀만 Bluetooth로 추가로 연결하시면 됩니다.

2) 코딩



MFRC522

#include <SPI.h>
#include <MFRC522.h>
  • MFRC522 mfrc522(SS_PIN, RST_PIN) : MFRC522 인스턴스화
  • SPI.begin() :SPI bus 초기화
  • mfrc522.PCD_Init() : MFRC522 card 초기화
  • mfrc522.PICC_IsNewCardPresent() : 새카드 확인
  • frc522.PICC_ReadCardSerial() : 하나의 카드읽기 확인
  • rfid.uid.uidByte[] : 읽은 카드 키값이 들어 있음

피에조 부저

  • tone(tonePin ,음,음길이) : 음 시작
  • noTone(tonePin ) : 음 중지

Servo Motor

#include <Servo.h>
  • Servo servo : 서보모터 객체 선언
  • servo.attach(서보핀) : 서보 모터에 출력을 담당할 핀 선언
  • servo.write(회전각) : 회전각 만큰 서보모터를 회전 시킴

시리얼 통신

  • Serial.begin(9600) : 시리얼 통신 시작(9600 통신속도)
  • Serial.println(값) : 시리얼모니터 출력
  • Serial.available() : 시리얼통신으로 데이터가 들어놨는지 상태 확인
  • Serial.parseInt() : Int형으로 데이터를 읽음

Bluetooth(SoftwareSerial 통신)

#include <SoftwareSerial.h>

SoftwareSerial mySerial (rx, tx) : 소프트시리얼 객체선언(rx(수신), tx(전송))
mySerial.begin(9600) : 시리얼 통신 시작(예로 9600 통식속도를 사용해 봤네요.)
mySerial.println(값) : 데이터 전송


지난 시간 소스에서 Bluetooth를 다음과 SfotwareSerial 명령들을 추가하면 됩니다. 간단히 Serial 부분을 SoftwareSerial 형태로 변경만 해주시면 됩니다.

SoftwareSerial mySerial (rx, tx) : 소프트시리얼 객체선언(rx(수신), tx(전송))

이렇게 통신객체변수 mySerial로 선언을 해주면 다음과 같이 표현만 해주시면 됩니다.

Serial.begint(9600) => mySerial.begin(9600)
Serial.println("Open") => mySerial.println("Open")
Serial.println("Close") => mySerial.println("Close")

이렇게 SoftwareSerial 통신을 하면 됩니다.

Bluetooth 부분을 추가하여 수정 하면 다음과 같습니다.

[소스]

#include <SPI.h>
#include <MFRC522.h>
#include <Servo.h>
#include <SoftwareSerial.h>

const int RST_PIN = 9;
const int SS_PIN = 10;

MFRC522 mfrc522(SS_PIN, RST_PIN);   // MFRC522 인스턴스

byte cardkeyByte[4] = {0x55, 0xAF, 0x07, 0x88}; //card UID 
boolean state = false; //Servo Motor 상태값

Servo servo;
const int SERVO_PIN = 7;
const int TONEPIN = 6;

const int rxPin = 2;
const int txPin = 3;
SoftwareSerial mySerial(rxPin, txPin); // RX, TX

void setup() {
  Serial.begin(9600);  
  while (!Serial);  
  mySerial.begin(9600);     
  SPI.begin();         //SPI 시작
  mfrc522.PCD_Init();  //MFRC522 card 초기화
  Serial.println(F("Warning: this example clears your mifare UID, use with care!"));

  servo.attach(SERVO_PIN);
  servo.write(0);
  delay(50);
}

void loop() {
  // 새 카드 확인
  if ( ! mfrc522.PICC_IsNewCardPresent()) return; 

  // 카드 읽기
  if ( ! mfrc522.PICC_ReadCardSerial()) return;

  //카드 확인 메세지 음
  tone(TONEPIN ,523,1000/8);     // 도음으로 8분음표(음길이)
  delay(1000/4*1.30);             
  noTone(TONEPIN); 

  //읽은 Card UID와 등록된 Card UID가 일치하는 확인
  if (mfrc522.uid.uidByte[0] == cardkeyByte[0] && mfrc522.uid.uidByte[1] == cardkeyByte[1] &&
        mfrc522.uid.uidByte[2] == cardkeyByte[2] && mfrc522.uid.uidByte[3] == cardkeyByte[3] ){
   
         state=!state;    
         if(state == true){
          Serial.println("Open");
          mySerial.println("Open");
          servo.write(180);
          delay(1000);
   }
   else{          
          Serial.println("Close");
          mySerial.println("Close");
          servo.write(0);
          delay(1000);
         }
         delay(2000);
   }  
}

2) 결과


아두이노 IDE 시리얼모니터로 "Open" or "Close" 메세지 출력


실제 블루투스로 스마트폰에 깔리 블루투스 앱에 "Open" or "Close" 메세지 출력


작동 동영상 결과는 다음과 같습니다.


2. RFID-RC522 + 초음파 센서



RFID-RC522 모듈로 출입문이 열리고 닫히는 동작을 한다고 상상을 해보세요. 이때 출입문에 RFID-RC522 Card를 대면 Servo Motor를 180도 회전 시켜서 "Open" 상태가 되고 출입문를 지나가면 자동으로 Servo Motor가 0도로 회전 시켜서 "Close" 상태를 만든 다면 좀 더 그럴싸 해지겠죠. 이 표현을 하기 위해서 여러 Sensor 중 초음파 Sensor를 사용하여 그 느낌을 표현 하고자 합니다. 즉, 출입문을 지나고 출입문이 닫혀도 되는 안전지점에 초음파 Sensor를 배치하고 인간 감지를 통해 출입문을 자동으로 닫히게 한다는 설정으로 상상을 해 보았습니다. 물론 이 경우는 상황 조건 변수들을 다 고려한다면 좀 복잡해지지만 최대한 단순하게 의미만 전달하는 실험을 하는게 목적임으로 단순하게 접근하고 단순한 동작만 수행되도록 실험 하겠습니다.
실제로 모형을 만들어서 실험을 하면 좋은데 손재주가 없어서 못 만들었네요. 대충 그 의미만 전달하는 실험으로 Card를 RFID-RC522모듈에 대면 Servo Motor가 180도 회전을 하고 초음파 Sensor가 측정한 거리값을 일정 수치 이하면 인간을 감지한 걸로 판정하고 Servo Motor를 0도로 회전시키는 실험으로 그 느낌을 대신 전달하고자 합니다.


1) RFID-RC522 + 초음파 센서 회로도



부품이 하나씩 늘어날 때마다 회로도가 지져분 해지네요. 처음에는 초음파센서 두개로 연결해서 상황을 하나 더 만들려고 했는데 엄청 지져분 해져서 한개의 초음파 센서로 단순하게 초음파센서로 읽은 거리값으로 동작하게 표현하여 최대한 코딩량을 줄였네요.


2) 코딩


  • 내용 : RFID-RC522 리더기로 읽은 Card가 인식되면 Servo Motor를 180도 회전 시키고 초음파 센서로 일정거리값이하면 Servo Motor를 0도로 회전 시킨다.(거리값=10cm 실험)

위 소스를 약간만 변경하겠습니다.

  //읽은 Card UID와 등록된 Card UID가 일치하는 확인
  if (mfrc522.uid.uidByte[0] == cardkeyByte[0] && mfrc522.uid.uidByte[1] == cardkeyByte[1] &&
        mfrc522.uid.uidByte[2] == cardkeyByte[2] && mfrc522.uid.uidByte[3] == cardkeyByte[3] ){
   
         state=!state;    
         if(state == true){
          Serial.println("Open");
          mySerial.println("Open");
          servo.write(180);
          delay(1000);
   }
   else{          
          Serial.println("Close");
          mySerial.println("Close");
          servo.write(0);
          delay(1000);
         }
         delay(2000);
   }  

위 소스에서 필요한 동작은 Card가 일치하면 문만 열기게 하면 되니깐 else 이하문은 필요 없습니다. 이 부분은 초음파센서 코딩으로 넘기게 됩니다.

<
  if (mfrc522.uid.uidByte[0] == cardkeyByte[0] && mfrc522.uid.uidByte[1] == cardkeyByte[1] &&
        mfrc522.uid.uidByte[2] == cardkeyByte[2] && mfrc522.uid.uidByte[3] == cardkeyByte[3] ){
   
         state=true;
                 
         Serial.println("Open");
         mySerial.println("Open");
         servo.write(180);
         delay(1000);
    }

이렇게 해서 Card가 읽치하면 무조건 Servo Motor는 180도로 향하게 됩니다. 여기서 state 상태변수를 그냥 남겨둿는데 이것은 초음파센서 진입 변수로 재활용 할 예정입니다. 즉, Card를 대고 문이 열렸을 때만 초음파센서를 작동시킨다고 보시면 됩니다.

초음파 센서핀은 Trig, Echo핀이 있는데 Trig은 초음파를 쏘는 핀이고 Echo은 초음파를 읽는 핀입니다.

const int TRIG_PIN = 4;
const int ECHO_PIN = 5;

남는 핀중에 4,5번핀을 그냥 선택했습니다.

void setup(){
  pinMode(TRIG_PIN,OUTPUT); //초음파센서핀(TRIG)
  pinMode(ECHO_PIN,INPUT); //초음파센서핀(ECHO)
}

그리고 Trig(OUTPUT), Echo(INPUT)핀을 어떤 모드로 사용할 것인지 선언해야 합니다.

if(state == true){   
     float distance = UltrasonicDistance();
     if(distance<10){
          state=false;
          Serial.print("Close : "); 
          Serial.println(distance);          
          mySerial.println("Close");
          servo.write(0);
          delay(500);
      }
}   

state로 Card가 일치하면 문이 열리잖아요 그때 state가 true가 되니깐 초음파센서는 그 때부터서 초음파 센서가 동작해야 하기 때문에 이렇게 if문으로 "state==true"일 때 UltrasonicDistance()함수로 초음파 센서를 측정하겠다는 로직입니다. UltrasonicDistance()함수는 사용자정의함수로 직접 만든함수명입니다.

float UltrasonicDistance(){  
  digitalWrite(TRIG_PIN, LOW); 
  delayMicroseconds(2); 
  digitalWrite(TRIG_PIN,HIGH); 
  delayMicroseconds(10); 
  digitalWrite(TRIG_PIN,LOW); 

  float duration = pulseIn(ECHO_PIN, HIGH);  
  return duration / 57.5;  
}

초음파 측정 로직인데 이 로직은 기본 아두이노홈페이지에 가시면 측정하는 코딩 로직을 예제로 소개하고 있습니다. 그 부분을 묶어서 캡슐화 해서 사용자정의 함수로 분리해 냈습니다. 이 코딩을 loop()안에다가 전부 코딩하면 오히려 가독성이 떨어지기 때문에 사용자정의함수로 분리를 한 것이죠.

     if(distance<10){
           거리가 10cm이하면 참;
             state = false;
             servo.write(0);
             delay(500);
     }

거리가 10cm이하면 문이 닫힌다는 설정문입니다. 그러면 문이 닫히면 더이상 초음파센서 측정이 필요 없으니깐 state=false로 바꾸어 주면 다시 문이 열리기 전까지는 초음파 측정을 할 필요가 없겠죠. state가 초음파센서 작동 진입 락이라고 생각하시면 됩니다.

초음파센서 로직의 대한 코딩 배치를 하면 종합 코딩은 다음과 같습니다. 새카드확인과 카드읽기 전에 초음파센서를 동작시킬건지 말건지를 먼저 수행되도록 배치 했습니다. 그 이유는 새카드확인과 카드읽기가 만족하지 않으면 거기서 return 되기 때문에 새카드확인과 카드읽기 문장 아래에 배치하면 카드를 대기 전까지 초음파센서는 측정하지 못하게 됩니다. 그래서 먼저 배치 한 것이죠. 그리고, state 상태변수로 초음파센서를 작동 상태값으로 초음파센서를 제어하게 됩니다.

[소스]

#include <SPI.h>
#include <MFRC522.h>
#include <Servo.h>
#include <SoftwareSerial.h>

const int RST_PIN = 9;
const int SS_PIN = 10;

MFRC522 mfrc522(SS_PIN, RST_PIN);   // MFRC522 인스턴스

byte cardkeyByte[4] = {0x55, 0xAF, 0x07, 0x88}; //card UID 
boolean state = false; //초음파 센서 작동 상태값

Servo servo;
const int SERVO_PIN = 7;
const int TONEPIN = 6;

const int rxPin = 2;
const int txPin = 3;
SoftwareSerial mySerial(rxPin, txPin); // RX, TX

const int TRIG_PIN = 4;
const int ECHO_PIN = 5;


void setup() {
  Serial.begin(9600);  
  while (!Serial);  
  mySerial.begin(9600);     
  SPI.begin();         //SPI 시작
  mfrc522.PCD_Init();  //MFRC522 card 초기화
  Serial.println(F("Warning: this example clears your mifare UID, use with care!"));

  pinMode(TRIG_PIN,OUTPUT); //초음파센서핀(TRIG)
  pinMode(ECHO_PIN,INPUT); //초음파센서핀(ECHO)

  servo.attach(SERVO_PIN);
  servo.write(0);
  delay(50);
}

void loop() {

  if(state == true){   
     float distance = UltrasonicDistance();
     if(distance<10){
          state=false;
          Serial.print("Close : "); 
          Serial.println(distance);          
          mySerial.println("Close");
          servo.write(0);
          delay(500);
      }
   }    
  // 새 카드 확인
  if ( ! mfrc522.PICC_IsNewCardPresent()) return; 

  // 카드 읽기
  if ( ! mfrc522.PICC_ReadCardSerial()) return;

  //카드 확인 메세지 음
  tone(TONEPIN ,523,1000/8);     // 도음으로 8분음표(음길이)
  delay(1000/4*1.30);             
  noTone(TONEPIN); 

  //읽은 Card UID와 등록된 Card UID가 일치하는 확인
  if (mfrc522.uid.uidByte[0] == cardkeyByte[0] && mfrc522.uid.uidByte[1] == cardkeyByte[1] &&
        mfrc522.uid.uidByte[2] == cardkeyByte[2] && mfrc522.uid.uidByte[3] == cardkeyByte[3] ){
   
          state = true;    
          Serial.println("Open");
          mySerial.println("Open");
          servo.write(180);
          delay(500);
   }  
}
float UltrasonicDistance(){  
  digitalWrite(TRIG_PIN, LOW); 
  delayMicroseconds(2); 
  digitalWrite(TRIG_PIN,HIGH); 
  delayMicroseconds(10); 
  digitalWrite(TRIG_PIN,LOW); 
    
  float duration = pulseIn(ECHO_PIN, HIGH);  
  return duration / 57.5;  
}

3) 결과


아두이노 IDE 시리얼모니터로 "Open" or "Close" 메세지 출력과 함께 초음파센서가 측정된 거리(Cm)을 동시에 출력 합니다.



스마트폰에는 이전 소스와 동일하게 "Open" or "Close" 메세지 출력 합니다.


동작 동영상은 다음과 같습니다.


마무리


RFID-RC522 모듈에 부품이 하나씩 늘어나면서 코딩량이 좀 많이 늘어 났습니다. 만약, 처음부터 이렇게 회도도를 만들고 코딩량이 길었다면 아마도 RFID-RC522 모듈에 대해 이해하기가 힘들었을 꺼에요. 그리고, 이전 post를 읽지 않는다면 오늘 post가 어려울 수도 있으니깐 꼭 읽고 이 post를 보셨으면 합니다.

최근의 post를 계속 중복된 실험이 이루어지고 있는데 그 중복되 실험 의미를 잘 이해하시고 따라오셔야 합니다. 대개 개별적인 부품 실험은 쉽게 이해하시는데 2개 이상의 부품이 결합하면 어떻게 코딩해야 할지 이해하지 못하는 경우가 많습니다. 어디에 어느 명령 코딩을 삽입해야 하는지를요. RFID-RC522 모듈을 이렇게 작동합니다. 하고 끝내면 사실 이 모듈로 상상을 하더라도 다른 부품과 연결 하기는 초보분들은 쉽지 않습니다. 여러개의 부품을 연결하면 코딩에 멘붕이 발생하기도 합니다. 제가 실험한 과정을 잘 보시고 어떤 과정으로 회로도를 만들고 코딩을 했는지 이해하셨으면 합니다.

RFID-RC522 부품을 하나에서 출발해서 여기까지 왔습니다. 부족한 부분이 많고 추가해야 할 부품은 많지만 상상의 상상을 더하면 위 회로도가 아닌 여러분만의 회로도를 만들 수 있으니깐 RFID-RC522 모듈 하나에서 다시 처음부터 여러분들이 회로도를 만들고 상황을 설정하고 그 상황의 회로도를 만들고 그 상황의 코딩을 한번 해보셨으면 합니다.


댓글()

[아두이노] RFID-RC522 + Servo Motor 제어

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

[아두이노] RFID-RC522 + Servo Motor 제어



지난 시간에 RFID-RC522를 아두이노에 연결하여 제대로 인식하는지에 대해 예제를 통해 간단히 테스트를 하였습니다. MFRC522 라이브러리에서 초기 선언하는 세팅 부분만 간단히 살펴 보았기 때문에 뭔가 제어하는 실험이 부족한 것 같아서 오늘은 Servo Motor를 같이 연결하여 RFID-RC522를 제어하는 실험을 하고자 합니다. 어제 배웠던 내용에서 크게 변화 된 것은 없습니다. 단지 카드 UID 값을 읽고 그 값에 일치 하면 Servo Motor가 특정 각도로 회전이 일어나도록 하는 실험을 하고자 합니다. 일상에서의 도어락이나 전철을 탈때 출입 통로, 회사 출입 통로 등의 카드를 대면 인식하고 출입을 할 수 있도록 문을 열어주는 동작과 느낌을 실험하고자 Servo Motor의 Open 회전값과 Close 회전값을 주어 비슷한 느낌을 표현 하였습니다.


1. RFID-RC522 + Servo Motor 회로도



  • 준비물 : RFID-RC522, Servo Motor, 피에조부저, 아두이노우노
  • 내용 : SPI 통신을 할 수 있게 핀은 연결하고 Servo Pin 7번, 피에조부저 6번으로 연결해 보자.
  • 지난시간 회로도 참고 : RFID-RC522 제어(아두이노)


출처 : Fritzing

RFID-RC522 회로도가 좀 복잡해 보일 꺼에요. 지난 시간에 RFID-RC522 회로도에서 Servo Motor와 피에조부저 핀을 추가 하시면 됩니다. 오늘 회로도를 보고 선을 연결하지 마시고 링크 걸린 지난 시간의 회로도를 보고 회로도 선을 연결한 뒤에 오늘사용하는 7번 Servo 핀과 6번 피에조 핀만 연결하면 되니깐 그렇게 어렵지 않을 꺼에요.

2. 코딩



MFRC522

#include <SPI.h>
#include <MFRC522.h>
  • MFRC522 mfrc522(SS_PIN, RST_PIN) : MFRC522 인스턴스화
  • SPI.begin() :SPI bus 초기화
  • mfrc522.PCD_Init() : MFRC522 card 초기화
  • mfrc522.PICC_IsNewCardPresent() : 새카드 확인
  • frc522.PICC_ReadCardSerial() : 하나의 카드읽기 확인
  • rfid.uid.uidByte[] : 읽은 카드 키값이 들어 있음

피에조 부저

  • tone(tonePin ,음,음길이) : 음 시작
  • noTone(tonePin ) : 음 중지

Servo Motor

#include <Servo.h>
  • Servo servo : 서보모터 객체 선언
  • servo.attach(서보핀) : 서보 모터에 출력을 담당할 핀 선언
  • servo.write(회전각) : 회전각 만큰 서보모터를 회전 시킴

시리얼 통신

  • Serial.begin(9600) : 시리얼 통신 시작(9600 통신속도)
  • Serial.println(값) : 시리얼모니터 출력
  • Serial.available() : 시리얼통신으로 데이터가 들어놨는지 상태 확인
  • Serial.parseInt() : Int형으로 데이터를 읽음

지난 시간에 이여서 오늘도 위 함수 부분은 그대로 Post에 남겨 뒀습니다. 지난 시간의 Post를 왔다 갔다 하실 필요 없이 오늘 Post만 보시고 코딩을 생각할 수 있게 그대로 가져 왔네요.

[소스] MFRC522의 예제 중 하나( 출처 : miguelbalboa의 라이브러리)

#include <SPI.h>
#include <MFRC522.h>

#define RST_PIN         9       
#define SS_PIN          10      

MFRC522 mfrc522(SS_PIN, RST_PIN);  // Create MFRC522 instance

void setup() {
    Serial.begin(9600);     // Initialize serial communications with the PC
    while (!Serial);        // Do nothing if no serial port is opened (added for Arduinos based on ATMEGA32U4)
    SPI.begin();            // Init SPI bus
    mfrc522.PCD_Init();     // Init MFRC522
    mfrc522.PCD_DumpVersionToSerial();  // Show details of PCD - MFRC522 Card Reader details
    Serial.println(F("Scan PICC to see UID, SAK, type, and data blocks..."));
}

void loop() {
    // Look for new cards
    if ( ! mfrc522.PICC_IsNewCardPresent()) {
        return;
    }

    // Select one of the cards
    if ( ! mfrc522.PICC_ReadCardSerial()) {
        return;
    }

    // Dump debug info about the card; PICC_HaltA() is automatically called
    mfrc522.PICC_DumpToSerial(&(mfrc522.uid));
}

지난 시간에 라이브러리에 있던 예제 소스입니다. 이 소스를 기반으로 Servo Motor를 제어 하겠습니다. 우선 위 소스를 DumpInfo 예제를 한번 실행 시켜 주세요.

Card UID: 55 AF 07 88

Card UID의 값을 확인 하세요. 참고로 제가 실험하는 Card UID 값하고 여러분이 실험하는 Card UID는 다릅니다. 이 Card UID로 실험하기 때문에 한번 실행해서 자신의 Card UID를 알아내야 합니다. 확인이 되면 이 Card UID 값을 배열 변수로 저장 하세요.

byte CardUidByte[4] = {0x55, 0xAF, 0x07, 0x88};

참고로, 숫자가 알파벳이 나오면 16진수라고 여기세요. 여기서 그냥 숫자를 기입하시면 안되고 앞에 '0x'을 꼭 붙여주셔야 이 수가 16진수이구나 하고 프로그램이 인식합니다. 이렇게 해서 비교 할 Card UID값을 저장해 놓았습니다.

카드를 읽게 되면 그 값은 어디에 들어 있을까요.

mfrc522.uid.uidByte[]

이곳에 담겨져 있습니다. 4개의 UID 숫자값이 Card UID 값을 가지고 있으니 mfrc522.uid.uidByte[0] ~ mfrc522.uid.uidByte[3] 까지해서 그 값을 가지고 있습니다. 그러면, IF문으로 해서 저장된 값과 비교하면 됩니다.

  if (mfrc522.uid.uidByte[0] == CardUidByte[0] && mfrc522.uid.uidByte[1] == CardUidByte[1] &&
        mfrc522.uid.uidByte[2] == CardUidByte[2] && mfrc522.uid.uidByte[3] == CardUidByte[3] ){
                읽어온 Card UID 값과 저장된 Card UID값이 일치하면 참;
                명령문;
  }

이렇게 해서 카드를 RFID-RC522에 대면 두 Card UID 값을 서로 비교하게 됩니다. 그리고 일치하면 if문 안의 명령문을 수행하게 됩니다.

코딩은 거의 다 했습니다. 이제는 Servo Motor를 회전 시켜야 겠죠. 카드의 UID가 일치하니깐 Servo Motor가 Open 시키는 의미로 일정 각도로 회전 시키는 동작과 다시 카드를 RFID-RC522에 대면 Servo Motor가 Close 시키는 의미로 원래 각도로 회전 시켜는 동작을 표현 할 에정입니다. Card를 대면 Open or Close가 교차로 반복되는 실험입니다.

이 동작을 수행하기 위해서는 어떻게 코딩해야 할까요.

boolean state = false;

이렇게 하나의 ServoMotor의 상태값 변수를 만들어 놓습니다. 그리고 나서 아래와 같은 코딩을 하면 됩니다.

  if (mfrc522.uid.uidByte[0] == CardUidByte[0] && mfrc522.uid.uidByte[1] == CardUidByte[1] &&
        mfrc522.uid.uidByte[2] == CardUidByte[2] && mfrc522.uid.uidByte[3] == CardUidByte[3] ){
          
          state=!state;  //Servo Motor(Open or Close)
                
          if(state == true){
           Serial.println("Open");
           servo.write(180);
           delay(1000);
          }
          else{           
           Serial.println("Close");
           servo.write(0);
           delay(1000);
          }
          delay(2000);              
  }

초기 state은 false 상태이고 Cade UID 값이 일치하면 state은 반전 true가 되어 다음 if문에서 "state == ture"이면 참이니깐 "Open"으로 Servo Motor를 180도 회전 시키고 1초 대기 했다가 다시 연속으로 카드값이 인식하지 못하도록 2초동안 딜레이 시간을 추가로 더 연장 했습니다. 총 3초 동안은 Card가 인식되지 못하게 했습니다. 3초가 지난 후 Card가 다시 RFID-RC522에서 인식하면 state은 !state로 반전으로 state은 false가 되어 다음 if문 "state == ture"가 거짓으로 else 이하 문장을 수행 합니다. 그러면 "Close"로 Servo Motor은 0도로 원래 각도로 회전 되어 돌아오게 됩니다. Card가 RFID-RC522에 대면 교차로 "Open" 과 "Close"가 반복 됩니다.

이렇게 해서 Card로 Servo Motor를 회전 시킬 수 있게 되었습니다.

여기서, 추가로 Card가 읽혔는지 확인 할 방법이 없습니다. Servo Motor가 회전 되었을 때 확인이 됩니다. 즉, 카드를 Servo Motor가 회전 될 때 까지 RFID-RC522에 대고 있어야 한다는 소리가 됩니다. 좀 불편하죠.

그래서, 카드를 읽은 순간 그때 뭔가 메세지를 외부로 보여준다면 이 문제가 해결 할 수 있습니다. 그 역할을 소리로 표현하기 위해서 피에조부저를 이용하고자 합니다.

  // 새 카드 확인
  if ( ! mfrc522.PICC_IsNewCardPresent()) return; 

  // 카드 읽기
  if ( ! mfrc522.PICC_ReadCardSerial()) return;
    
  tone(tonePin ,523,1000/8);     // 도음으로 8분음표(음길이)
  delay(1000/4*1.30);             
  noTone(tonePin ); 

이렇게 카드가 읽기와 카드를 비교하기 전 사이에 사이에 피에조부저의 음이 울리게 하면 쉽게 확인이 되겠죠.

종합해 보면,

[소스]

#include <SPI.h>
#include <MFRC522.h>
#include <Servo.h>

const int RST_PIN = 9;
const int SS_PIN = 10;

MFRC522 mfrc522(SS_PIN, RST_PIN);   // MFRC522 인스턴스

byte CardUidByte[4] = {0x55, 0xAF, 0x07, 0x88}; //card UID 
boolean state = false; //Servo Motor 상태값

Servo servo;
const int SERVO_PIN = 7;
const int TONEPIN = 6;

void setup() {
  Serial.begin(9600);  
  while (!Serial);     
  SPI.begin();         //SPI 시작
  mfrc522.PCD_Init();  //MFRC522 card 초기화
  Serial.println(F("Warning: this example clears your mifare UID, use with care!"));

  servo.attach(SERVO_PIN);
  servo.write(0);
  delay(50);
}

void loop() {
  // 새 카드 확인
  if ( ! mfrc522.PICC_IsNewCardPresent()) return; 

  // 카드 읽기
  if ( ! mfrc522.PICC_ReadCardSerial()) return;

  //카드 확인 메세지 음
  tone(tonePin ,523,1000/8);     // 도음으로 8분음표(음길이)
  delay(1000/4*1.30);             
  noTone(tonePin ); 

  //읽은 Card UID와 등록된 Card UID가 일치하는 확인
  if (mfrc522.uid.uidByte[0] == CardUidByte[0] && mfrc522.uid.uidByte[1] == CardUidByte[1] &&
        mfrc522.uid.uidByte[2] == CardUidByte[2] && mfrc522.uid.uidByte[3] == CardUidByte[3] ){
   
         state=!state;    
                 
         if(state == true){
           Serial.println("Open");
           servo.write(180);
           delay(1000);
          }
          else{
           Serial.println("Close");
           servo.write(0);
           delay(1000);
         }
         delay(2000);
   }  
}

3. 결과



4. 추가 코딩


위 코딩은 약간 도어락 같은 느낌 이였다면 출입문일 경우는 카드로 열고 닫고를 안하고 한번 대면은 열리고 나서 일정시간이 지나면 닫히게 됩니다. 출입문 경우는 어떻게 코딩할까요.

위 소스에서 한 부분만 수정하면 됩니다.

  //읽은 Card UID와 등록된 Card UID가 일치하는 확인
  if (mfrc522.uid.uidByte[0] == CardUidByte[0] && mfrc522.uid.uidByte[1] == CardUidByte[1] &&
        mfrc522.uid.uidByte[2] == CardUidByte[2] && mfrc522.uid.uidByte[3] == CardUidByte[3] ){
   
           Serial.println("Open");
           servo.write(180);
           delay(5000);
           Serial.println("Close");
           servo.write(0);
           delay(1000);                 
   }  

이렇게 수정하시면 끝납니다. 실험에서는 5초 동안 열렸다가 다시 닫히는 걸로 했는데요. 실제 출입문이면 이러지는 않겠죠. 적어도 수십초는 열려 있다가 닫혀야 겠죠. 아니면 초음파 센서같은 인간 감지 센서를 이용하여 지나 가면 자동으로 닫히게 하면 되겠죠. 그렇게 하자면 부품이 추가로 늘어나고 오히려 오늘 전달하고자 하는 의미가 제대로 전달되지 않기 때문에 이정도 까지만 하겠습니다.

[결과]


마무리


RFID-RC522 라이브러리는 쉽지 않지만 간단한 부분만 가져다가 응용하면 그래도 재밌는 표현들을 할 수 있습니다. 여러분들도 한번 이 RFID-RC522 모듈을 구하셔서 실험을 해보셨으면 합니다. 가격도 비싸지 않고 싼편이라서 괜찮은 실험 도구라고 생각 되네요.

아두이노우노에서 실험하면 SPI 통신을 하기 때문에 사실 다른 부품을 추가 할 자리가 많지 않습니다. 그럴 때는 두개 이상의 아두이노로 통신을 통해 제어하면 좋은데 그 실험을 하려면 2대정도 있어야 하는데 한대 뿐이라 아쉽네요.

아무튼 오늘 배운 내용을 토대로 다른 부품 특징들을 머리속에서 떠올려서 RFID-RC522와 연결하는 상상을 한번 해보셨으면 합니다.


댓글()

[아두이노] RFID-RC522 제어

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

아두이노] RFID-RC522 제어



오늘은 RFID-RC522 모듈을 살펴보고자 합니다. 이 모듈은 일상에서 교통카드, 출입문 카드, 도어락 카드와 같은 것들을 아두이노상에서 표현할 수 있는 모듈입니다. 카드에 등록된 정보를 RFID-RC522 리더기가 읽고 그 읽은 값을 통해서 우리는 여러가지를 표현할 수 있습니다. RFID 모듈은 통신 방식이 SPI여서 좀 까다롭습니다. I2C 모듈이면 좀 더 사용하기가 편할 텐데 말이죠. 실험에서는 카드 인식에 문제가 생겨서 아래 사진에서 보는 것처럼 열쇠 고리 모양 같은걸로 간단히 실험을 하였네요. 이제부터서 RFID-RC522 모듈을 아두이노 핀에 어떻게 연결하고 코딩할 때 필요한 MFRC522 라이브러리를 설치에 대해서 알아본 뒤에 라이브러리드 안에 있는 DumpInfo 예제를 통해서 카드 정보를 읽는 실험을 하겠습니다.


1. RFID-RC522





출처 : Fritzing

RFID-RC522 모습은 위 출처가 링크 된 곳에서 다운로드 하신 후 Fritzing에 등록하셔서 디자인 하시면 됩니다. 핀 번호는 위에서 부터 아래로 순서대로 아래 표를 참조하시면 되겠습니다.


SPI 통신을 하기 때문에 아두이노의 핀과 RFID-RC522 핀을 위 표처럼 연결하시면 됩니다. 어떤 RFID 모듈은 I2C통신을 하는 모듈이 있는데 그럴 경우는 아날로그 핀 A4, A5으로 연결하셔서 실험하시면 됩니다. 자신이 사용하는 모듈은 어떤 방식인지를 우선 구별하시고 핀을 연결하시면 됩니다.

2. RFID-RC522 회로도


  • 준비물 : RFID-RC522, 아두이노우노
  • 내용 : SPI 통신을 할 수 있게 핀은 연결한다.


선 연결이 좀 복잡해 보이지만 위 표를 보시고 선을 연결하시면 어렵지 않을 거라 생각됩니다. 그래도 이해가 안되시면 아래 스케메틱 회로도를 보고 선을 연결하시면 되겠습니다.



출처 : Fritzing

3. RFID-RC522 라이브러리 추가


rfid로 검색하시면 되는데 직접 MFRC522로 검색어로 검색하셔도 됩니다. 아래처럼 검색이 되면 이 라이브러리를 설치하시면 됩니다.


4. 코딩



MFRC522

#include <SPI.h>
#include <MFRC522.h>
  • MFRC522 mfrc522(SS_PIN, RST_PIN) : MFRC522 인스턴스화
  • SPI.begin() :SPI bus 초기화
  • mfrc522.PCD_Init() : MFRC522 초기화
  • mfrc522.PICC_IsNewCardPresent() : 새카드 확인
  • frc522.PICC_ReadCardSerial() : 하나의 카드읽기 확인
  • rfid.uid.uidByte[] : 읽은 카드 키값이 들어 있음

miguelbalboa의 라이브러리 안에 위 함수들이 있는데 여러가지 함수들이 있는데 가장 기본적인 것만 설명하기 때문에 꼭 기억해 주세요. 저도 이 라이브러리를 사용하여 간단히 테스트를 했지만 따로 만들고 싶은 것이 없어서 그냥 간단히 테스트만 했네요. 제대로 RFIC-RC522를 사용하기 위해서는 링크된 라이브러리에 가셔서 코딩을 제대로 이해하시고 사용하실 수 있을 꺼에요.

MFRC522 라이브러리를 설치하면 여러개의 예제가 있는 데 한번씩 다 사용해서 어떤 결과가 나오는지 확인 해보시기 바랍니다.

여러 예제들 중에서 소스 코딩이 짧아보이는 DumpInfo라는 예제가 있습니다. 카드 정보를 읽어와서 출력하는 예제인데 이걸로 정상 작동하는지 살펴 볼께요.

[소스] MFRC522의 예제 중 DumpInfo( 출처 : miguelbalboa의 라이브러리)

#include <SPI.h>
#include <MFRC522.h>

#define RST_PIN         9          // Configurable, see typical pin layout above
#define SS_PIN          10         // Configurable, see typical pin layout above

MFRC522 mfrc522(SS_PIN, RST_PIN);  // Create MFRC522 instance

void setup() {
    Serial.begin(9600);     // Initialize serial communications with the PC
    while (!Serial);        // Do nothing if no serial port is opened (added for Arduinos based on ATMEGA32U4)
    SPI.begin();            // Init SPI bus
    mfrc522.PCD_Init();     // Init MFRC522
    mfrc522.PCD_DumpVersionToSerial();  // Show details of PCD - MFRC522 Card Reader details
    Serial.println(F("Scan PICC to see UID, SAK, type, and data blocks..."));
}

void loop() {
    // Look for new cards
    if ( ! mfrc522.PICC_IsNewCardPresent()) {
        return;
    }

    // Select one of the cards
    if ( ! mfrc522.PICC_ReadCardSerial()) {
        return;
    }

    // Dump debug info about the card; PICC_HaltA() is automatically called
    mfrc522.PICC_DumpToSerial(&(mfrc522.uid));
}

[실행]


리더기가 정상적으로 인식했네요.

소스 코딩을 보면 MFRC522의 객체변수를 mfrc522를 선언할 때 RST, SS Pin을 두개를 인자로 인스턴스화 하네요.

SPI.begin();            // Init SPI bus
mfrc522.PCD_Init();     // Init MFRC522

이렇게 해서 초기화 작업을 끝냈고 장상적으로 인식하는지 테스트가 진행 됩니다.

mfrc522.PCD_DumpVersionToSerial();

위 실행 결과에서 정상적으로 "Firmware Version: 0x88= (clone)" 라고 떴지만 인식을 안하면 실패한 에러 메세지가 출력 됩니다.

정상적으로 인식 했으니깐 카드를 읽을 준비를 합니다.

두개의 if문이 loop()함수에서 나옵니다.

mfrc522.PICC_IsNewCardPresent() 새카드 확인
mfrc522.PICC_ReadCardSerial() 카드 읽기

새로운 카드를 확인하면 다음 카드 읽기가 진행됩니다. 이 두 과정을 IF문으로 이렇게 표현하면 어떻게 동작 할까요.

if(!조건식) return ;

이 명령라인의 의미는 조건식이 거짓일 때 참이 됩니다. 족건식이 거짓이면 if문이 참이되어 return 명령을 만나는데 이 명령은 현재 명령 범위에서 아래 명령을 수행 할 필요 없이 다시 그 명령 범위를 빠져 나오라는 의미가 됩니다.

void loop(){
  if(!조건식) return ;
    명령문1;
    명령문2;
}

이렇게 되어 있으면 조건식이 거짓이면 return 명령으로 명령문1, 명령문 2를 수행하지 않고 빠져나와 종료된다고 생각하시면 돼요. loop()은 문한 반복이니깐 빠져나왔지만 loop()함수가 처음부터 다시 수행됨으로 if문의 조건식이 참인지 거짓인지 계속 무한 판정을 하게 됩니다. 이때 if문이 거짓이 되면 return 명령을 수행하지 않고 다음 명령문1, 명령문2가 수행되게 됩니다. 어떤 의미인지 아시겠죠.

위 소스에서 PICC_IsNewCardPresent()함수로 새카드인지 확인하고 카드가 확인되면 if문에서 새카드 확인되지 못할 때만 return 명령을 수행하기 때문에 확인되면 다음으로 넘어 갑니다. PICC_ReadCardSerial() 확인된 새카드를 읽게 됩니다. 읽게 되면은 if문에서 읽지 못할때 return 명령을 수행하기 때문에 읽었으니깐 다음 명령으로 넘어 가게 됩니다.

이렇게 두단계로 새카드 확인과 카드읽기로 락을 걸어놓은 것이죠.

mfrc522.PICC_DumpToSerial(&(mfrc522.uid));

Dump 정보를 시리얼모니터로 출력시키는 명령입니다.

5. 결과


아래와 같이 카드를 리더기에 올려놓으면 카드의 정보를 읽어오게 됩니다.


5. RFID-RC522 인식이 에러 해결책


아래와 같은 메세지가 인식하지 못할 때 뜨게 됩니다.


첫번째, 인식 실패의 원인은 접촉 불량입니다. 처음에 잘 작동되더라도 나중에 쓰다보면 잘 인식하지 못합니다. 그래서 이 모듈을 사용하는 분들은 대부분 납땜을 많이 합니다.

두번째, 기존 아두이노에 이식 된 프로그램에 새로 업로드 한 프로그램과 그 사이에 리더기의 동작 에러가 발생 할 수 있습니다. 전원을 끄고 다시 접속해서 한번 문제가 생기면 사실 재 인식이 되지 않는 경우가 발생 합니다. 그럴 때 접촉 불량인가 하고 다시 연결선들을 점검하는데 혹시 이런 문제로 인해 인식을 못할 수 있으니깐요. 다른 프로그램을 아두이노에 업로해서 돌려 본 뒤에 다시 RFID-RC522 소스를 돌려보세요. 저도 방금 전 잘 인식되던게 인식 에러 상황을 만들려고 Gnd 선을 빼고 프로그램을 업로드 한뒤에 정상적으로 선 연결하고 업로드 했더니 RFID-RC522가 인식을 안하더군요. 선 접촉 불량인가 하고 삽질을 하다가 그냥 기본 예제인 Blink를 아두이노에 업로드 하고 나서 다시 RFID-RC522 소스를 업로드 하니깐 그때서야 정상적으로 인식이 되었습니다.

마무리


RFID-RC522 라이브러리가 참 쉽지 않는 라이브러리 입니다. 사용하는 함수들이 많은데 사실 라이브러리 파일을 찾아가서 안에 함수 내용이 정확히 어떻게 코딩되어 있는지 확인해야 그 의미를 이해할 수 있습니다. 저도 이 RFID 리더기를 사용할 때 좀 버겁습니다. 단지 카드를 대면 리더기가 읽으면 카드 UID값이 rfid.uid.uidByte[] 변수에 저장되는데 카드 UID 값을 기준으로 간단한 제어만 할 수 있습니다.

이 부품은 응용 범위가 많습니다. 일상에서 하루에 한번씩은 접해 보셨을 거라 생각됩니다. 교통카드 충전할 때 그 상황을 생각해보세요. 또는 매장에 물건을 살 때 택을 찍을 때를 상황을 떠올려 보세요. 도서관이나 회사 입구를 지나갈때 카드를 대고 지나가는 상황을 떠올려 보세요. 교통카드로 전철이나 버스를 타기 위해서 교통카드를 찍을 때를 떠올려 보세요. 집 출입문에 도어락을 떠올려보세요. 이외에도 많은 곳에서 이와 비슷한 것들이 많습니다.

RFID-RC522를 이용하시는 분들은 한번 이 모듈을 이용하여 어떤 것을 표현하고 싶은지 상상을 해보시고 재밌어 보이면 해당 라이브러리를 한번 열어보시고 그 함수의 로직이 어떻게 코딩되어 있는지 제대로 공부해보세요. 응용 분야가 많아서 아이디어만 있으면 꽤 괜찮은 작품들을 만들어 낼 수 있을 거라 생각됩니다.

댓글()

[아두이노] Stepper Motor에 대해 상상하다.

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

[아두이노] Stepper Motor에 대해 상상하다.



Steem.js에 재미를 붙여서 실험하고 있어서 아두이노 post가 늦어지고 있네요. 지난주의 Stepper Motor 제어를 집중적으로 다뤘는데 오늘 post는 실제 구현은 안해 봤지만 상상한 내용을 post에 담아 이야기를 하고자 합니다. Stepper Motor는 각도를 제어하는 Motor 입니다. 각도를 제어 한다는 것은 회전을 프로그래머가 설계한 방향으로 회전을 시킬 수 있다는 의미입니다. 원하는 위치로 회전을 시킬 수 있고 회전 된 각도의 값을 알 수 있다면 이것을 이용하여 많은 것들을 표현 할 수 있습니다.

원하는 각도로 회전 시킨다는 것은 정방향 100도 회전 시킬 수 있고, 역방향 -500도로 회전 시킬 수 있으며 회전하면서 회전 각도를 저장해 놓으면 현재 Stepper Motor가 어느 방향으로 몇도 회전 되어 있는지 알 수 있습니다. 이 정보로 우리들은 뭘 할 수 있을까요. 산업 현장에서는 산업 로봇이 자동으로 나사를 조이고 용접하고 물건을 나르는 등의 움직임을 제어하는 데 사용 됩니다. 조립을 하는 과정에서 나사를 몇 바뀌 회전 시켜야 하는지 각도값으로 제어할 수 있습니다. 그리고 산업 로봇팔이 용접을 할 때 용접 할 위치로 산업 로봇팔의 관절이 회전하면서 해당 위치로 이동하겠죠. 즉, 움직임을 표현하는데 각도제어 Motor가 사용 되고 각도를 제어를 할 수 있는 Stepper Motor는 많은 것들을 할 수 있게 됩니다.

오늘 다룰 내용은 움직임을 각도로 나눠서 초음파 센서로 거리를 측정하고자 합니다. 이렇게 얻은 거리 정보를 토대로 초음파 센서에서 감지된 대상의 좌표 (x,y,z) 꼭지점을 구하고자 합니다. 그리고 구한 꼭지점을 processing으로 시각화 하는 상상을 해 보았습니다. 실제 구현은 못해 봤습니다. 사실 딴거에 빠져서 구현은 안했습니다. 그냥 상상한 내용을 담아 이야기 하고자 합니다.

1. 거리 측정 회로도


  • 준비물 : 스템모터, 모터드라이버, 초음파센서, 아두이노우노


대충 위와 같은 형태로 구성한다고 상상을 해 봤습니다. 이렇게 하면 Stepper Motor가 1도씩 회전을 한다면 어떻게 될까요. 각도에 따른 거리를 측정 할 수 있게 됩니다.


위 그림처럼 원통에 거리 측정기를 넣고 측정을 시작한다고 가정을 해 봅시다. 그러면 원통의 벽면까지의 거리값을 구할 수 있겠죠. 여기서, 360도를 1도씩 회전하면서 거리를 측정한다고 상상을 해보세요. 측정되어 나온 원통까지의 거리값을 이용하여 꼭지점(x,y,z)를 구할 수 있습니다. 그 꼭지점을 연결하면 단층 이미지를 만들어 낼 수 있습니다. 이 단층 이미지가 여러장이 쌓이면 3D Rendering 을 통해서 실제 원통 모양으로 이미지를 만들어 낼 수 있겠죠.

오늘 실험은 얻어진 좌표 (x,y,z) 꼭지점을 연결하여 processing에 배치시켜 3D 단층 이미지를 만드는 과정을 상상을 해보고자 합니다. 그 과정을 이제부터서 진행 하겠습니다.

2. 거리 측정에 따른 좌표 꼭지점 구하기



위 x,y,z 좌표계가 있다면 원점은 Stepper Motor의 회전하는 중심 지점이 되겠죠. (x,z)축의 평면 방향으로 초음파 센서가 1도씩 회전을 한다면 위 그림처럼 360도의 360개의 좌표 지점을 만들어 낼 수 있게 됩니다. y축은 아두이노의 초음파가 붙어있는 위치가 되며 초음파가 측정하는 각도에 방향에 따라 (x,z)축의 거리 꼭지점을 아래의 공식에 의해서 구할 수 있습니다.


그러면 (x,z)축으로 펼쳐서 보면 위 그림 처럼 나타나고 Stepper Motor의 회전 angle이 되고 초음파 측정 거리는 r이 되어 이 r과 angle의 값을 기준으로 꼭지점 (x,z)를 구할 수 있습니다. 오랫만에 접하는 수학이라서 기억도 안나고 손 놓은지가 정말 오래만이라 공식도 기억 안나서 인터넷에서 찾았네요. 이제 이런것들을 수학적으로 계산하기에는 너무 벅차네요. 중학교 수준이지만요. 요즘 초등학생들이 배우는 수학인 것 같은데 잘 모르겠네요. 이정도 수학도 이제 벅차네요.

이렇게 해서 y축은 초음파의 고정 위치이니깐 고정 y꼭지점에서 Setpper Motor가 회전하는 angle에 따른 초음파의 거리(r)을 통해 x,z 꼭지점을 구하면 원점에서 1도씩 회전 할때마다 좌표 (x,y,z)의 꼭지점을 구할 수 있게 됩니다.


좌표 (x,y,z)의 꼭지점 선으로 연결하면 이런 평면 이미지가 그려지겠죠.

3. processing으로 측정된 좌표를 연결


위에서 구한 좌표(x,y,z)의 꼭지점을 구하게 되면 processing으로 표현 한다면 어떤 느낌일까요.

다음과 예제를 보시기 바랍니다.

void setup() {  
    
    size(600,600,P3D); //창사이즈
    noStroke();  //테두리없음
} 

void draw() {  
    background(0); //배경색
    lights();  //조명

    pushMatrix();  //Start
    translate(300,270,300); //이동
    shape();
    popMatrix(); //End
}
void shape(){
  fill(0,255,0); //채우기
  beginShape();
  for(int i=0;i<=360;i++){
      vertex(cos(i)*100, 0, sin(i)*100);   
  }
  endShape(CLOSE);
}

위 예제는 for문을 유심히 보시기 바랍니다. 위 소스는 processing 3D 도형 제어 (아두이노) Post의 소스 수정을 최소화하여 표현 했습니다.

아두이노에서 processing과 시리얼통신을 할 수 있다고 했죠. 그러면 Stepper Motor가 1도씩 회전하면서 초음파 센서가 측정한 좌표 지점을 구한 (x,y,z) 꼭지점을 시리얼통신으로 전송한다면 그 값으로 beginShape()~endShape(CLOSE) 사이의 vertex(x,yz) 값을 시리얼통신으로 얻은 (x,y,z) 꼭지점으로 대체한다면 360도 회전할 때 360개의 꼭지점을 그리게 되면 평면 이미지를 만들 수 있게 됩니다.

위 소소는 100이라는 초음파 거리를 고정으로 초음파 거리 (r)에 대한 Stepper Motor 회전(angle)을 가상으로 주어진 상태에서 가상으로 이미지를 그리는 예제입니다.

4. processing으로 측정된 좌표 이미지 결과



위 결과는 가상 데이터 (x,y,z)의 값을 기준으로 그렸기 때문에 원형의 이미지를 얻었습니다. 실제로 구현을 한다면 불규칙적인 평면의 이미지를 얻을 수 있겠죠.

5. 이것로 뭘 만들까?


순간 떠오르는 생각을 상상력으로 구현은 안했지만 글로써 표현을 했습니다. 이걸로 뭘 만들 수 있을까요. 일상에서 이런 표현들이 뭐가 있을까요. 이 글을 쓰는 순간에 떠오르는 생각은 자율주행에서 360도 회전하는 센서에 주변 사물을 감지하는데 적용하게 좋겠죠.

그럼 또 뭘 할 수 있을까요. 거리측정센서로 3D 랜더링을 할 수 있을까? post에서 이야기한 3D 랜더링에 적용할 수 있습니다. 위 실험에 대한 상상한 내용은 하나의 평면 단층 이미지를 만들 수 있습니다. 그러면, 초음파 센서가 일렬로 여러개가 연결되어 있으면 여러장의 단층 이미지를 얻을 수 있겠죠. 아니면 Stepper Motor를 하나 더 제어해서 y축 회전 시켜서 y축 위치를 변경해 가면서 x,z 축의 단층 이미지 데이터를 얻을 수 있겠죠. 이렇게 여러개의 단층 (x,y,z)의 값을 얻을 수 있으면 이 데이터들을 서로 연결하면 3D 모형의 이미지 형태로 랜더링을 할 수 있습니다. 위 실험 자체만으로도 3D 좌표 평면 이미지로 표현 되었지만 좀더 Volume Rendering을 하려면 여러장의 이미지 데이터가 필요 합니다.

그외도 이야기를 하자면 끝도 없습니다. 오늘 제가 post한 내용들이 바로 상상 코딩입니다. 왜! 제가 post를 끝날 때마다 상상의 나래를 펼쳐 보라는 이유가 오늘 post의 담겨져 있습니다. 실제 구현을 안하더라도 우리는 일상의 사물을 보면서 또는 영화 속 SF장면을 떠올리면서 많은 상상을 할 수 있습니다. 그 상상이 아두이노의 소재가 되는 것이고 그 아두이노로 상상한 것들을 비슷하게 구현해 낼 수 있습니다.

실제로 구현을 안하더라도 가상으로 이렇게 상상 코딩을 할 수 있습니다.

마무리


여러분들도 상상 코딩을 생활화 해보세요. 뭔가 프로그램 언어를 배우고 정석으로 깊게 파고드는 것도 중요하지만 무엇보다 중요한 것은 상상 코딩입니다. 정석으로 전문 코딩을 하는 것은 엔지니어이지 개발자나 창작자가 아닙니다. 다소 코딩이 부족하더라도 뭔가에 대해서 상상하는 사람이 진정한 개발자이나 창작자입니다.

엔지니어는 반복학습하면 누구나 다 될 수 있지만 개발자나 창작자는 상상을 하지 않으면 되지 못합니다. 여러분들도 처음에 코딩을 전문적으로 정석 코스로 깊게 배우는 싶은 분들도 많을 꺼에요. 하지만 상상하지 않으면 의미가 없습니다.

마지막으로 여러분들도 위 내용이 아니더라도 아두이노 관련 부품이나 누가 표현한 원리 중 하나를 찾아서 그 원리를 다른 쪽으로 응용하여 상상해보는 시간을 가졌으면 합니다.

댓글()

[아두이노] Stepper 대신 Servo Motor로 가상시뮬레이터 실험

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

[아두이노] Stepper 대신 Servo Motor로 가상시뮬레이터 실험



지난시간에 Stepper Motor 원하는 각도 회전과 블루투스 원격 제어 실험을 하였습니다. 이 부분을 아두이노가 없는 분들을 위해서 어떤 느낌인지 의미를 전달하고자 가상시뮬레이터로 표현을 해보았네요. 표현한 방식은 Servo Motor를 이용하여 아두이노 두대를 연결하여 시리얼통신을 통해서 Servo Motor를 제어하는 실험입니다. 가상시뮬레이터에서 Stepper Motor가 없기 때문에 직접 실험을 할 수 없습니다. 물론 DC 모터를 가지고 Stepper 라이브러리를 적용하여 억지로 표현하는 경우는 있지만 그렇게 표현하면 오히려 혼동을 야기 할 수 있으니 정석으로 지난 시간의 실험한 Stepper Motor 코딩 로직을 기반으로 Servo Motor를 대신 사용하여 실험을 진행했으며 위에 공개회로도 링킁 가셔서 가상시뮬레이터를 실행 시켜보세요. 온라인 상에서 체험 할 수 있을 거라 생각 됩니다.

이제 본격적으로 실험 내용을 다뤄 보겠습니다.

1. Servo Motor 회로도


  • 준비물 : Servo Motor 1개, 아두이노우노
  • 내용 : 10핀을 Servo Motor 출력핀으로 연결하시오.


간단하게 선 연결을 할 수 있겠죠.

1) 코딩


내용 : servo 라이브러리를 이용하여 아두이노 IDE의 시리얼통신 입력값에 의해 회전시키자.
참조 : [아두이노] Stepper Motor 원하는 각도 회전(1)


Servo Motor

#include <Servo.h>
Servo servo : 서보모터 객체 선언
servo.attach(서보핀) : 서보 모터에 출력을 담당할 핀 선언
servo.write(회전각) : 회전각 만큰 서보모터를 회전 시킴

시리얼 통신

  • Serial.begin(9600) : 시리얼 통신 시작(9600 통신속도)
  • Serial.println(값) : 시리얼모니터 출력
  • Serial.available() : 시리얼통신으로 데이터가 들어놨는지 상태 확인
  • Serial.parseInt() : Int형으로 데이터를 읽음

지난 시간의 Stepper Motor의 소스에서 그대로 가져왔습니다.

#include <Servo.h>

Servo servo;
int angle = 0;

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

void loop()
{
    if(Serial.available()) {
      int val=0;
      char ch=Serial.read();
      if(ch=='a')val=1;      
      else if(ch=='d')val =-1;
      else val =0;
      
      angle+=val;
      
      if(angle>180) angle=180;
      else if(angle<0) angle=0;
        
      servo.write(angle);
      Serial.println(angle);      
      delay(50);
    }
}

Stepper Motor랑 코딩은 동일합니다. 단 if문이 하나 더 추가 되었는데 0~180도 사이 각으로 회전이 제한이 됩니다. 180도 이상과 0도 이하의 각도로 회전을 할 수 없습니다. 그래서 angle각이 180도를 넘으로 180도에 수렴하게 만들고 0도 이하가 나오면 0도에 수렴하게 만들어 놓았습니다. 그외는 코딩이 동일하기 때문에 따로 변경할 부분은 없습니다.

      if(angle>180) angle=180;
      else if(angle<0) angle=0;

위 문장만 잘 이해하시면 따로 설명은 필요 없겠죠. 지난 시간에 설명 했으니깐요. 아무튼 시리얼 모니터에서 'a'와 'd'를 입력하면 알파벳 키 값에 따라서 회전을 시키게 됩니다.

2) 결과



2. 2대의 아두이노 + Servo Motor 회로도


  • 준비물 : Servo Motor 1개, 아두이노우노 2개
  • 내용 : 10핀을 Servo Motor 출력핀으로 연결하고 Serial 통신을 하기 위해서 두대의 아두이노는 2,3빈으로 교차로 연결하라.

  • Rx - 데이터 읽기 (B arduino Pin3 =>A arduino Pin2)
  • Tx - 데이터 보내기(B arduino Pin2 =>A arduino Pin3)

한대는 스마트폰이라고 생각하고 다른 한대에 Servo Motor를 제어하는 실험입니다. 소스 코딩도 동일합니다. B 아두이노가 아래쪽 Servo Motor가 연결된 A 아두이노에 명령을 내리면 그 명령에 따라서 A 아두이노가 Servo Motor를 회전 하게 됩니다. Bluetooth 통신 소스와 동일합니다. 2대의 아두이노로 Bluetooth 실험을 대신한다고 생각하시면 됩니다.


1) 코딩



시리얼 통신

  • Serial.begin(9600) : 시리얼 통신 시작(9600 통신속도)
  • Serial.println(값) : 시리얼모니터 출력
  • Serial.available() : 시리얼통신으로 데이터가 들어놨는지 상태 확인
  • Serial.parseInt() : Int형으로 데이터를 읽음

SoftwareSerial 통신

#include <SoftwareSerial.h>

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


[A 아두이노 코딩]

#include <Servo.h>
#include <SoftwareSerial.h>

const int rxPin = 2;
const int txPin = 3;
SoftwareSerial mySerial(rxPin, txPin); // RX, TX

Servo servo;
int angle = 0;

void setup()
{
  Serial.begin(9600);
  mySerial.begin(2400);
  servo.attach(10);  
  
  servo.write(angle);
  delay(50);
}

void loop()
{
    if(mySerial.available()) {
      int val=0;
      char ch=mySerial.read();
      
      if(ch=='a')val=1;      
      else if(ch=='d')val =-1;
      else val =0;
      
      angle+=val;
      
      if(angle>180) angle=180;
      else if(angle<0) angle=0;
        
      servo.write(angle);
      Serial.println(angle);      
      delay(50);
    }
}

[B 아두이노 코딩]

#include <SoftwareSerial.h>

const int rxPin = 2;
const int txPin = 3;
SoftwareSerial mySerial(rxPin, txPin); // RX, TX

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

void loop()     
{
  if(Serial.available()) {
    mySerial.write(Serial.read());      
  } 
}

2) 결과



마무리


Stepper Motor 회전을 Bluetooth를 이용하여 스마트폰에서 제어를 하는 실험을 지난 시간에 했었습니다. 그것을 가상시뮬레이터에서 최대한 같은 표현을 하기 위해서 두 대의 아두이노를 연결하여 한쪽에서 다른쪽으로 Motor 제어 명령을 보내고 Stepper Motor가 지원이 안됨으로 그 역활을 Servo Motor로 대신하여 회전시키는 실험을 하였습니다.

가상시뮬레이터에서 최대한 같은 느낌의 표현과 코딩을 그대로 적용함으로써 지난 시간에 배운 Stepper Motor 제어를 느낄 수 있게 표현 되어 있으니 한번 공개회로도 링크로 가셔서 체험을 해보세요.

이상으로 Stepper Motor Post를 마무리 합니다.

댓글()

[아두이노] Stepper Motor 원하는 각도 회전(2)

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

[아두이노] Stepper Motor 원하는 각도 회전(2)



지난 시간에 Stepper Motor를 원하는 각도로 회전 시켰는데 오늘은 Bluetooth로 스마트폰에서 아두이노 연결 된 Stepper Motor를 회전 시키는 실험을 하도록 하겠습니다.


1. Stepper Motor + Bluetooth 통신 준비


스마트폰과 아두이노는 Bluetooth를 통해 통신합니다.


스마트폰 Bluetooth 어플은 아무거나 사용해도 됩니다. 실험에서는 Controller mode와 Terminal mode를 사용하여 실험 했네요. Controller mode은 버턴을 지정하고 버턴을 클릭하면 해당 버턴값을 아두이노로 보내는 형식이고요, Terminal mode은 채팅하는 식으로 아두이노 IDE 시리얼모니터에서 값을 입력하는 것과 유사합니다. 직접 전송할 값을 타이핑해서 보내는 방식입니다. 버턴을 눌러서 그 값을 전송하는 방식과 직접 타이핑해서 전송하는 방식이 들어 있는 어플이면 아무거나 상관 없습니다. 직접 앱인벤터로 만드셔도 되고요.


그냥 시리얼 통신으로 값을 전달할 수 있는 Bluetooth 어플이면 아무거나 상관 없습니다. 안드로이드면 구글스토어에서 Arduino Bluetooth만 치셔도 많은 어플들이 검색되니깐 실험하고 싶은 어플을 아무거나 선택하시면 됩니다.

2. Stepper Motor + Bluetooth 회로도



  • 준비물 : 5V 스템모터, ULN2003 모터드라이버, Bluetooth(HC-06) 1개, 아두이노우노
  • 내용 : 8,9,10,11핀을 순서대로 IN1, IN2, IN3, IN4에 연결하고 스템모터와 UNL2003 모터쉴드에 연결하고 Bluetooth를 2, 3번에 연결한다.

  • Rx - 데이터 읽기 (BlueRX =>arduino Pin3)
  • Tx - 데이터 보내기(BlueTX =>arduino Pin2)


지난 시간의 Stepper Motor 회로도에서 Bluetooth만 하나 더 연결한 회로도 입니다.

2. 코딩 - I



Stepper

#include <Stepper.h>
  • Stepper stepper(STEPS, IN1, IN2, IN3, IN4) : 사용하는 Stepper Motor 스템수, 각 4핀을 세팅.
  • stepper.setSpeed(값) : Stepper Motor의 속도 설정.
  • stepper.step(스텝수) : 스템수로 회전 각을 표현 한다.

시리얼 통신

  • Serial.begin(9600) : 시리얼 통신 시작(9600 통신속도)
  • Serial.println(값) : 시리얼모니터 출력
  • Serial.available() : 시리얼통신으로 데이터가 들어놨는지 상태 확인
  • Serial.parseInt() : Int형으로 데이터를 읽음

SoftwareSerial 통신

#include <SoftwareSerial.h>

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


[지난 시간 소스]

#include <Stepper.h>

const int STEPS = 2048;
Stepper stepper(STEPS, 8,10,9,11);

void setup()
{
  stepper.setSpeed(12);
  Serial.begin(9600);
}

void loop()
{
   if(Serial.available()) {
    int val=Serial.parseInt(); //회전각 int형으로 읽기
    
    val=map(val,0,360,0,2048); //회전각 스템 수
    stepper.step(val);
    Serial.println(val);
    delay(10);
  }
}

위 소스를 가지고 수정을 합시다. Bluetooth 통신을 하기 위해서 SoftwareSerial 라이브러리를 이용하여 통신을 하게 됩니다 그래서, 다음과 같은 Bluetooth 통신 명령 과정이 필요합니다.

#include <SoftwareSerial.h>

const int rxPin = 2;
const int txPin = 3;
SoftwareSerial mySerial(rxPin, txPin); // RX, TX

이렇게 초기 선언을 합니다. 2번핀은 Bluetooth의 TX핀이고 3번핀은 Bluetooth의 RX핀입니다. 혼동하시면 안됩니다. 나머지 코딩은 Serial 단어를 mySerial로만 변경하시면 코딩 수정이 끝납니다. 변경되는 부분은 아래와 같습니다.

void setup()
{  
  mySerial.begin(9600);
}
void loop()
{
   if(mySerial.available()) {
    int val=mySerial.parseInt(); //회전각 int형으로 읽기
    mySerial.println(val);
    delay(10);
  }
}

합쳐진 코딩은,

#include <SoftwareSerial.h>
#include <Stepper.h>

const int rxPin = 2;
const int txPin = 3;
SoftwareSerial mySerial(rxPin, txPin); // RX, TX

const int STEPS = 2048;
Stepper stepper(STEPS, 8,10,9,11);

void setup()
{
  stepper.setSpeed(12);  
  mySerial.begin(9600);  
}

void loop()
{
 
  if(mySerial.available()) {
    int val=mySerial.parseInt();
    val=map(val,0,360,0,2048);
    stepper.step(val);
        mySerial.println(val);
    delay(10);   
  }
}

[결과]
Terminal Mode로 값을 직접 타이핑 한 값으로 회전시키면 다음과 같은 결과가 나오네요. 360도를 보내면 아두이노가 Bluetooth로 받은 후 Stepper Motor를 회전시킨후 회전 시킵니다. 그리고 나서 스템수를 다시 스마트폰으로 보내고 스마트폰는 받은 스템수를 아래 이미지 처럼 "HC-06: 2048"이라고 출력 됩니다.


간단히 실험 영상을 보시기 바랍니다.


3. 코딩 - II


코딩-I은 스마트폰으로 각도를 채팅방식으로 입력을 해서 Stepper Motor를 회전 시켰다면 이제는 방향버턴키로 회전시키는 코딩으로 수정하도록 하겠습니다. 참고로 방향키버턴으로 눌러서 회전 시키면 위 코딩-I 소스대로 하면 문제가 좀 발생합니다. 그 이유는 mySerial.parseInt() 함수 때문에 그렇습니다. 만약에 각도 데이터를 전송하고 읽을때 읽는 데이터가 밀리면 통신 버퍼에 쌓이게 되고 그러면 합쳐진 상태로 각도를 읽는 문제가 발생합니다. 가령, 10도 회전을 연달아 이어서 10, 10 이렇게 전송이 되었을 때 받는 아두이노에서 데이터가 쌓여 1010이렇게 되면 parseInt()함수는 한번에 1010각도로 읽어버리게 됩니다. 즉, 10, 10 이렇게 회전 시켜서 20도를 회전 시켜야 하는데 1010 이것을 한번에 읽어서 1010각도로 회전 시켜버리는 문제가 생기게 됩니다. 그래서 방향키를 방향 알파벳 한글자로 해서 mySerial.read()로 1byte씩 읽고 그 값에 따라 회전시키는 방법으로 쉽게 제어할 수 있도록 코딩을 변경 하겠습니다.

코딩-1 소스에서,

  if(mySerial.available()) {
    int val=mySerial.parseInt();
    val=map(val,0,360,0,2048);
    stepper.step(val);
        Serial.println(val);
    delay(10);   
  }

이 부분만 변경하면 됩니다.

int angle = 0; //외부변수 현재 각도값
if(mySerial.available()) {
      int val=0;
      char ch=mySerial.read();

      if(ch=='a')val=10;      //시계방향
      else if(ch=='d')val=-10; //반시계방향
      else val=0;
      
      angle+=val;
      val=map(val,0,360,0,2048);  
      stepper.step(val);
      //Serial.println(angle);      
      delay(10);
}

if문으로 1byte 읽은 알파벳이 ch변수에 저장되고 이 저장된 변수가 'a'면 정방향 10도 회전, 'd'면 역방향 10도 회전시키고 그외 키 값은 무시 0도 회전으로 if문에서 체크하여 val 변수에 회전시킬 각도값을 저장하게 했습니다.

여기서 외부 변수로 angle에 대한 동작 명령은 코딩하지 않았습니다. 이것은 여러분들이 나중에 활용하라는 의미로 남겨 둔 변수입니다. 이 변수는 현재 코딩에서는 없어도 되는 변수이지만 구지 표현한 이유는 나중에 어려분들이 Stepper Motor를 회전을 제어할 때 꼭 필요한 변수이기 때문입니다. 예를 들면, 우리가 Stepper Motor 회전을 시키면 700도 정방향으로 회전 시켰다가 역방향 200도 회전 시키고 또 다시 정방향, 역방향 이렇게 계속 회전을 시킬 경우 Stepper Motor라 마지막 회전된 위치를 단순히 회전만 시키면 알 수 없습니다. 이때 angle이라는 변수를 하나 선언해 놓고 처음 회전이 시작한 위치를 0도에 지정해놓고 계속 회전 값들을 angle변수에 더하게 되면 Stepper Motor의 시작 위치 0도에서 어느 방향으로 회전을 하더라도 현재 회전한 위치를 알 수 있게 됩니다.

여기서, 한가지 예로 외부 입력값 E(End)라고 생각하고 위 if문에 연장 선상으로 if문으로 아래와 같이 코딩하면

else if(ch=='e') val = -angle;

이렇게만 표현하면 e키가 입력된 순간 angle의 변수 안에는 지금 까지 회전되어 진 현재의 위치각도를 가지고 있기 때문에 그 값에 -(마이너스)를 붙이면 원상태 0도로 되돌아 가게 됩니다. 즉, 90도가 현재의 위치면 -angle로 -90도 만큼 회전 하면 0이 되겠죠. 다시 -90도가 현재의 위치면 -(-90)이니깐 +90도 만큼 회전 하면 0이 됩니다. 결론은 e키가 입력되면 이 한줄로 처음 자신의 위치로 되돌아 가는 명령을 코딩할 수 있습니다.

angle의 변수를 남겨 둔 이유는 여러분들이 나중에 회전을 제어할 때 언제든지 자신의 위치로 되돌아 갈 수 있고 현재의 회전 된 위치 값을 가질 수 있으면 그 기준으로 특별한 회전을 제어할 수 있게 되기 때문에 이런 angle 변수가 중요하기 때문에 선언만 해놓았습니다. 나머지는 여러분들이 이 변수를 가지고 어떻게 활용할지는 여러분의 몫입니다.

종합해보면,

#include <Stepper.h>
#include <SoftwareSerial.h>

const int rxPin = 2;
const int txPin = 3;
SoftwareSerial mySerial(rxPin, txPin); // RX, TX

const int STEPS = 2048;
Stepper stepper(STEPS, 8,10,9,11);

int angle = 0;

void setup()
{
  Serial.begin(9600);
  mySerial.begin(9600);
  stepper.setSpeed(12); 
}

void loop()
{
  if(mySerial.available()) {
      int val=0;
    char ch=mySerial.read();
      if(ch=='a')val=10;      
      else if(ch=='d')val=-10;
      else val=0;
      
      angle+=val;
      val=map(val,0,360,0,2048);  
      stepper.step(val);
      Serial.println(angle);      
      delay(10);
    }
}

[결과]



Controller Mode를 누르면 위 그림처럼 버턴 이미지로 구성된 창이 뜹니다. 그리고 톱니모양 설정을 누르면 버턴 키 값을 지정할 수 있습니다. 게임 방향키값인 a,s,w,s키로 우선 설정했는데 실제 실험에서는 a,d키만 사용합니다. 이 키값을 통해서 Setpper Motor를 회전 시키게 됩니다.

간단히 스마트 폰으로 방향키를 누렀을 때 Stepper Motor가 회전되는 실험 영상입니다.

마무리


이렇게 해서 스마트폰으로 간단히 회전을 시키는 법을 배웠습니다. Bluetooth를 한번 소개한 뒤에 계속 중복된 실험이 이뤄지고 있는데 이런 실험 과정을 여러분들도 실험하면서 계속 반복 학습을 해주세요. 그래야 나중에 다른 부품을 사용하더라고 이 부품을 이전에 배웠던 부품들과 쉽게 연결할 수 있습니다. 어떻게 두 부품을 결합하고 두 부품의 소스 코딩을 어떻게 합쳐지고 코딩이 되는지 그 감각을 익히셨으면 합니다. 그리고 하나의 표현으로 끝내지 말고 다른 표현을 계속 머리속에서 상상하셔야 합니다. 처음에는 이게 힘들지만 나중에는 재밌는 놀이처럼 여러가지의 상상을 하게 됩니다.

그런데 처음 배우시는 분들은 Bluetooth로 통신하는 실험만 하고 끝나고 Stepper Motor 회전 실험만 하고 넘어 갑니다. 나주엥 어떤 주제로 작품을 만들려고 할 때 그 때부터 문제가 발생합니다. 두 개 이상의 부품을 합치는 실험을 할 때 회로도 표현은 그럭저럭 되는데 코딩부분은 둘을 합치는 방법 자체를 이해를 못하는 경우가 많기 때문에 여러분들은 처음 부품을 배울 때 그 부품을 다른 부품과 계속 연결해서 실험하셔야 나중에 어떤 부품이든지 쉽게 표현을 할 수 있게 됩니다.

오늘 실험은 Stepper Motor를 하나를 조정했지만 두개 이상 연결하면 RC카 또는 로봇팔 등의 구현이 가능하고 Stepper Motor은 다양한 관절 제어에 사용하니깐 한번 SF영화나 애니메이션에서 봤던 장면들을 떠올리시고 거기서 Stepper Motor가 적용된다면 어떤 느낌으로 회로도를 구성하고 코딩 하면 좋을 지 상상의 나래를 펼쳐 보세요.


댓글()

[아두이노] Stepper Motor 원하는 각도 회전(1)

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

[아두이노] Stepper Motor 원하는 각도 회전(1)



지난 시간에 Stepper Motor를 간단히 360도 회전 실험을 했습니다. 이제는 Stepper Motor로 자유자재로 회전을 시켜 봐야 겠지요. 그럼 원하는 각도로 회전 시키는 방법을 알아보도록 합시다.


1. Stepper Motor 회전의 원리


회전을 제어하는 Motor는 대표적으로 두 종류가 있습니다. Servo Motor와 Stepper Motor 입니다. 둘은 비슷하게 회전하지만 실제 회전 명령을 내릴 때는 약간의 차이가 있습니다. Servo Motor의 경우 0~180도 사이 각으로 회전을 하게 됩니다. 여기서 90도 회전 할 경우 servo.write(90도) 이라고 표현하면 90도 만큼 회전 됩니다. 그리고 90도 위치에서 다시 120도 위치로 갈려면 servo.write(120도) 이라고 표현하면 됩니다. 즉, 원하는 각도로 가고 싶으면 해당 각도값을 넣으면 해당된 위치로 Servo Motor가 회전하게 됩니다.

하지만 Stepper Motor의 경우 좀 다릅니다. 360도 회전 시킬 수 있는 Motor 입니다. 여기서, 90도 회전시 stepper.step(90도스텝수) 표현하면 90도 만큼 회전 됩니다. 그런데 90도 위치에서 120도 위치로 갈려면 stepper.step(120도스텝수) 이라고 하면 안됩니다. stepper.step(120도스템수)이라고 하면 현재 90도 위치이지만 그 시점에서 새롭게 120도 만큼 회전을 하여 210도를 위치로 회전 됩니다. 즉, 회전이 끝난 지점이 새로운 회전이 시작되는 지점이니깐 90도 회전 후 120도 위치로 회전 될려면 Stepper Motor는 30도 회전을 시켜야 합니다. 이점을 생각하시고 코딩을 하셔야 합니다. 즉, 매번 회전 명령을 내리는 곳이 시작점이 되는 거라고 생각하시면 됩니다.

Servo Motor와 Stepper Motor의 회전는 이점을 생각하고 코딩하면 됩니다.

2. Stepper Motor 회로도



  • 준비물 : 5V 스템모터, ULN2003 모터드라이버, 아두이노우노
  • 내용 : 8,9,10,11핀을 순서대로 IN1, IN2, IN3, IN4에 연결하고 스템모터와 UNL2003 모터쉴드에 연결하시오.


지난 시간과 동일한 회로도를 사용하였습니다.


3. 코딩



함수

#include <Stepper.h>
  • Stepper stepper(STEPS, IN1, IN2, IN3, IN4) : 사용하는 Stepper Motor 스템수, 각 4핀을 세팅.
  • stepper.setSpeed(값) : Stepper Motor의 속도 설정.
  • stepper.step(스텝수) : 스템수로 회전 각을 표현 한다.

시리얼 통신

  • Serial.begin(9600) : 시리얼 통신 시작(9600 통신속도)
  • Serial.println(값) : 시리얼모니터 출력
  • Serial.available() : 시리얼통신으로 데이터가 들어놨는지 상태 확인
  • Serial.parseInt() : Int형으로 데이터를 읽음

[지난 시간 소스]

#include <Stepper.h>

#define STEPS 2048

Stepper stepper(STEPS, 8, 10, 9, 11);

void setup() {
  stepper.setSpeed(12);
}

void loop() {
  stepper.step(STEPS);
  delay(1000);
  stepper.step(-STEPS);
  delay(1000);
}

위 소스를 가지고 수정을 합시다.

우선, 아두이노 IDE 시리얼모니터에서 입력으로 회전각을 입력한다고 가정하면 아래와 같이 회전 각을 int형으로 읽어서 val변수에 회전각을 저장하게 표현 했습니다. 참고로 정수로 입력하는 방식으로 정수로 읽게 parseInt()함수로 표현 했고요.

if(Serial.available()) {
    int val=Serial.parseInt();
}

이제 val이라는 변수는 회전각도 값이 들어 있으니깐 이 각도를 실제 Stepper Motor의 회전 스템수로 변환 시켜야 겠죠. 스템수가 회전각이니깐요. 가독성으로 각도로 입력을 했습니다. 각도에 대한 스템수를 구하는 식이 필요 하겠죠. 예로 180도라고 입력을 하면 180도에 대한 스템수 1024값을 구해야 합니다.

그러기 위해서는 다음과 같이 map()함수를 이용합니다.

val=map(val,0,360,0,2048);

map() 함수에 입력각 val에 대한 입력 범위 0~360에서 출력 0~2048의 스템수를 세팅하면 회적 각도에 따라 스템수를 만들어 낼 수 있습니다. val이 180이면 스템수는 1023이 나오고 만약 -180도면 -1023 스템수가 나오게 됩니다. 이렇게 해서 각도에 대한 스템수를 만들어 냈습니다.

회전은,

stepper.step(val);

마무리로 step() 명령으로 실제 Stepper Motor를 회전을 시켜 원하는 각도에 제어할 수 있게 됩니다.

종합해서 코딩하면,

#include <Stepper.h>

const int STEPS = 2048;
Stepper stepper(STEPS, 8,10,9,11);

void setup()
{
  stepper.setSpeed(14);
  Serial.begin(9600);
}

void loop()
{
   if(Serial.available()) {
    int val=Serial.parseInt(); //회전각 int형으로 읽기
    
    val=map(val,0,360,0,2048); //회전각 스템 수
    stepper.step(val);
    Serial.println(val);
    delay(10);
  }
}

아두이노 IDE 시리얼모니터에서 입력한 각도에 따라 원하는 각도로 Stepper Motor를 회전을 시킬 수 있게 되었습니다.

4. 결과


아두이노 IDE 시리얼모니터에서 각도를 입력하면 Stepper Motor가 그 각도에 맞에 회전을 하는 영상입니다.


마무리


아두이노 IED 시리얼 모니터에서 입력한 회적각을 map()함수로 회전 스템수로 구하고 step()함수로 실제 Stepper Motor를 회전 키는 로직을 완성 했습니다. 시리얼 통신으로 회전을 제어 할 수 있게 되었으니깐 이걸로 블루투스 통신으로 하면 스마트폰으로 쉽게 Stepper Motor를 회전 시킬 수 있겠죠. 아니면 processing 으로 이미지로 버턴을 만들어서 Stepper Motor를 회전 시킬 수 도 있습니다. 그것도 아니면, 입력을 조이스틱이나 가변저항 같은 것들로 외부의 다른 Sensor를 이용해서 회전을 시킬 수 도 있습니다. 선택은 여러분의 상상력에 따라 달라집니다.

추가로, Bluetooth를 이용해서 스마트폰으로 회전을 시켰는데 오늘 전부 다 올리려고 하니 좀 내용이 길어질 것 같아서 나눠서 내일 post에 올리도록 하겠습니다.

여러분들은 Stepper Motor를 가지고 어떻게 회전을 할지 상상력을 펼쳐 보세요.


댓글()

[아두이노] Stepper Motor 라이브러리로 제어

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

[아두이노] Stepper Motor 라이브러리로 제어



오늘은 복잡하게 Stepper Motor의 4핀을 직접 제어할 필요 없이 Stepper Motor 라이브러리를 사용하여 쉽게 제어하는 실험을 하겠습니다.


1. Stepper Motor 회로도



  • 준비물 : 5V 스템모터, ULN2003 모터드라이버, 아두이노우노
  • 내용 : 8,9,10,11핀을 순서대로 IN1, IN2, IN3, IN4에 연결하고 스템모터와 UNL2003 모터쉴드에 연결하시오.


Stepper Motor 라이브러리를 실험하기 위해 따로 새롭게 만들 필요가 없기에 그냥 지난 시간에 만든 회로도를 그대로 사용하겠습니다.


2. 코딩



함수

#include <Stepper.h>
  • Stepper stepper(STEPS, IN1, IN2, IN3, IN4) : 사용하는 Stepper Motor 스템수, 각 4핀을 세팅.
  • stepper.setSpeed(값) : Stepper Motor의 속도 설정.
  • stepper.step(스텝수) : 스템수로 회전 각을 표현 한다.

아두이노 IDE Stepper 예제에 보시면 MotorKnob 예제가 있습니다. 원래 동작은 A0에 입력되는 아날로그 신호에 의한 회전인데 아마도 조이스틱과 가변저항으로 조정한 값을 Stepper Motor의 회전각을 만드는 것 같더군요. 오늘 실험은 이걸 하기 위한게 아니라 간단히 회전을 시킬 수 있는지에 대해 테스트 실험이기 때문에 수정을 하겠습니다.

[소스] : MotorKnob 예제입니다.

#include <Stepper.h>

#define STEPS 100
Stepper stepper(STEPS, 8, 9, 10, 11);
int previous = 0;

void setup() {
  stepper.setSpeed(30);
}

void loop() {
  int val = analogRead(0);
  stepper.step(val - previous);
  previous = val;
}

위 소스를 가지고 수정을 합시다.

우선,

#define STEPS 100

Stepper Motor의 Step의 총수입니다. 즉, 360도 회전에 Step 수라고 생각하시면 됩니다. 이부분은 여러분들이 변경해야 합니다. 자신이 사용하는 Stepper Motor의 종류에 따라서 계산해야 합니다.



STEPS = (360/step angle)* Gear ratio


예) [ 28BYJ-48 5V DC Stepper Motor]

  • Number of Phase : 4

  • Speed Variation Ratio : 1/64

  • Stride Angle : 5.625° /64

  • STEPS = (360/5.625)*64 = 4096


실제로 4096으로 입력하면 제 모터에서는 2바퀴를 회전하더군요. 2048로 STEPS을 잡으니깐 360도 회전을 할 수 있었습니다.

다음으로 핀 연결인데 위 회로도 대로 연결해서 돌리면 사실 돌아가지 않아서 처음 공부 할 당시 애를 먹었던 부분입니다. 처음에 삽질 했는데 핀 위치가 문제였더군요. 위 회로도로 연결했을 때 핀위치는 바뀌어야 합니다.

Stepper stepper(STEPS, 8, 9, 10, 11);

변경,

Stepper stepper(STEPS, IN1, IN3, IN2, IN4);
Stepper stepper(STEPS, 8, 10, 9, 11);

이렇게 설정하여 STEPS로 회전 시키니깐 시계방향으로 360도 회전 하였네요. -SEPS로 하니깐 반시계방향으로 360도 회전이 되었고요.

그 다음, stepper Motor의 회전 속도를 steup()함수에서 설정합니다. 하지만 이 speed값은 STEPS의 수와 관계가 있습니다. 만약 4096으로 잡고 돌릴 경우에는 5~7정도의 speed를 잡아야 정상적으로 회전이 일어나고 10정도 입력하면 회전을 하지 않습니다. 2048의 경우는 10~14사이가 정상적으로 회전을 하더군요.

stepper.setSpeed(12);

실제 회전 명령은 step()함수로 step수는 회전각입니다.

stepper.step(step수);

종합해서 수정하면, 정방향으로 360도 회전 후 역방향으로 360도 회전

#include <Stepper.h>

#define STEPS 2048

Stepper stepper(STEPS, 8, 10, 9, 11);

void setup() {
  stepper.setSpeed(12);
}

void loop() {
  stepper.step(STEPS);
  delay(1000);
  stepper.step(-STEPS);
  delay(1000);
}

4. 결과


회전은 깔끔하게 되긴 했는데 오차각이 좀 생기네요. Stepper Motor 싼거라 분해능력이 떨어져서 각이 약간 오차가 발생해서 정교한 각도 제어는 제가 실험한 Stepper Motor는 다소 무리가 있네요.


마무리


복잡하게 생각을 하지 않아도 Stepper Motor라이브러리로 쉽게 회전을 시킬 수 있겠죠. 코딩량도 대폭 줄어 들고 간단히 세개의 함수의 의미만 이해하면 Stepper Motor를 회전 시킬 수 있습니다.

Stepper stepper(STEPS, 8, 10, 9, 11);
stepper.setSpeed(12);
stepper.step(STEPS);

Stepper 객체변수 선언하고 Stepper 객체변수의 setSpeed()함수의 회전속도와 step()회전 스템수로 각도f를 회전 하게 됩니다. 이 세개의 함수만 이해하시면 쉽게 제어 할 수 있게 됩니다. 그런데 사실 쉽지는 않습니다. STEPS의 수를 계산하기가 약간 어려울 수 있습니다. Stepper Motor의 부품 사양을 보고 계산해서 구해야 하기 때문에 이부분이 어려울 수 있지만 이부분만 제대로 지정해 주시면 그 다음 부분들은 어렵지 않을 꺼에요.

참고로, 다른 Stepper Motor를 사용하실 때에는 해당 Stepper Motor 라이브러리를 검색하시셔 라이블러리에 추가해서 사용하시면 됩니다. 혹시 라이브러리가 없는데 그냥 따라서 include만 시킨다고 되는게 아니니깐요. 자신이 사용하는 부품명으로 우선 검색하시고 있으면 그걸 사용하세요. 없다면 공용 Stepper Motor 라이브러리를 사용하시고요.

마지막으로, 여러분들은 360도 회전을 할 수 있다면 어떤 것을 해보고 싶은지 상상의 나래를 펼쳐 보세요.


댓글()

[아두이노] Stepper Motor 제어

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

[아두이노] Stepper Motor 제어



오늘은 Stepper Motor를 다뤄보려고 합니다. Motor편은 Servo Motor 이후 오랫만이죠. Servo Motor은 아두이노에서 바로 제어가 가능하기 때문에 따로 준비사항이 없었지만 Stepper Motor나 DC Motor는 모터 쉴드가 필요합니다. 아두이노에서 직접 Motor에 연결해서는 안됩니다. 직접 연결한다고 해서 작동을 안하는 것은 아니지만 아두이노에 문제가 생길 수 있기 때문에 직접 제어를 피해야 합니다. 그 이유는 Motor는 많은 전류를 필요하기 때문에 아두이노에 전류를 공급할 때 아두이노에서 일시적으로 전류가 불안정한 상태에 빠져 숏다운도 될 수 있습니다. 그리고 Motor의 역전류 현상에서 아두이노에 역전류가 흐르게 되면 아두이노는 치명적 손상을 입히게 됩니다. 그래서, Motor의 경우는 아두이노에 직접 Motor와 연결하지 않습니다. 그 사이에 모터 쉴드가 필요하고 모터 쉴드를 통해서 Motor를 제어하게 됩니다.

오늘 배우게 될 Stepper Motor는 참 재있는 부품입니다. Servo Motor와 유사하게 각도를 제어한 부품입니다. Servo Motor는 일반적으로 360도 회전이 안됩니다. 변형시키거나 특수 Motor의 경우는 회전이 가능하지만 일반적으로 일정 사이각 180도로 회전이 제한이 됩니다. 하지만 Stepper Motor는 360도 회전 뿐 아니라 각도를 정교하게 회전 시킬 수 있어 큰 장점을 지닌 Motor입니다.

이제 본격적으로 Stepper Motor에 대해서 살펴보도록 하겠습니다.


1. 5V 스템모터 + ULN2003 모터 드라이버



[ Fritzing 부품 그림 ]


Stepper Motor는 모터 내부의 고정자의 극 수에 따라서 1상, 2상, 3상, 4상, 5상, 6상 등 여러 종류로 나뉩니다.

그리고, 회로방식에서 Unipolar Steppers, Bipolar Steppers으로 나뉩니다. 아래 아두이노 공식 홈페이지의 튜토리얼에 자세히 나와 있으니깐 읽어주세요.



제 실험에서 Stepper Motor를 ULN 2003 모터 드라이브로 제어를 하게 됩니다.

Stepper Motor의 자세한 설명은 사실 포스트에 전체적인 내용을 쓰기에는 그림작업이 너무 힘들고 글로써 설명하자니 그림 없이 의미 전달하기에는 가독성이 너무 떨어지고 해서 괜찮은 글들을 소개하는 수준으로 Stepper Motor의 기본 개념 설명을 대신하고자 합니다. 사실 이태훈님의 블로그보다 더 쉽게 설명을 할 자신이 없어서 아래 참고할만한 사이트들을 링크 걸어놓습니다.


[출처]


참고로, pdf 파일로 만들어 놓은 세번째 링크 주소에서 유니플러 "1 상 여자 동작" 이 부분을 특히 잘 봐주시기 바랍니다. 이부분을 사용하여 Stepper Motor를 제어를 실험할 예정입니다. 1 step에 하나의 상만 전류가 공급되는 방식인데 이걸로 스템모터를 라이브러리 없이 모터를 제어할 수 있습니다.

1 step => A상(5V), B상(0V), C상(0V), D상(0)
2 step => A상(0V), B상(5V), C상(0V), D상(0)
3 step => A상(0V), B상(0V), C상(5V), D상(0)
4 step => A상(0V), B상(0V), C상(0V), D상(5V)

예) 1 step 일 경우 :

  digitalWrite(stepPin[0],HIGH);
  digitalWrite(stepPin[1],LOW);
  digitalWrite(stepPin[2],LOW);
  digitalWrite(stepPin[3],LOW);
  delay(10);

이렇게 계속 반복을 하고 각도를 회전하게 됩니다 위 step 순서로 전류가 공급되었을 때 이 방향을 정방향이라고 하면 역방향은 위 과정을 역순으로 진행하면 역방향이 되겠죠. 링크된 pdf 파일에 있는 곳에서 그림으로 보시면 대충 어떤식으로 진행되는지만 파악해 두세요. 이걸 코딩으로 표현한다면 어떻게 할지도 여러분들이 공곰히 생각해보세요.

힘든 분들은 이부분을 그냥 잊어주세요. 편하게 라이브러리를 처음에 사용하시는 것이 좋겠죠.

스템 모터 쉴드


위 그림을 보시면 대충 위치는 파악 하실 수 있겠죠. 참고로 Stepper Motor선 연결은 연결부분이 구별되어 있어서 개별핀의 위치는 신경 안쓰셔도 됩니다. 회로도를 그릴 때 만 연결 핀 위치를 명확하게 표현 해야하지만요. 참고로 아두이노에서 IN1에 전류가 공급되면 입력 핀에 신호가 온 것을 해당 LED에 불이 들어오게 하여 입력 신호 상태를 알 수 있는 모터 드라이브네요. 실제로 구매하시면 전원 담당 부분은 +, -가 표기가 제대로 안되어 있으니깐 +, -위치는 암기해 주세요. 나머지 선은 순서대로 연결만 하면 됩니다.

자세히 세부적인 핀 연결은 아래 그림을 잘 확인하시면 됩니다. 연결 선은 Color Name으로 표시 되어 있기 때문에 맞춰서 연결하시면 됩니다. 이 동작은 위에 링크 걸린 곳에 가시면 다른분이 자세한 설명이 있으니깐 깊게 공부하실 분들은 가셔서 필독하시면 됩니다.


제가 생각하기에는 처음에는 우선 스템 모터를 회전시키는 동작에만 초점을 두었으면 합니다. 뭔가 실제로 돌아가는 걸 보고 나서 스템 모터의 내부 원리에 대해서 이해하시는게 더 좋을 듯 싶네요. 나중에 이걸로 다양한 표현을 하다가 좀 더 깊게 공부하고 싶은 욕구가 생길 때 정석으로 공부하시고 지금은 그냥 모터를 회전시킬 수 있는 것에만 관심을 가지시면 됩니다. 스템 모터 라이브러리를 직접 여러분들이 만들 필요가 없으니깐요. 우리는 단지 이 부품을 가지고 회전만 시킬 수 있는 능력을 갖추시면 됩니다. 그리고 회전을 시켰을때 이것을 어디에 써먹을지에 좀 더 초점을 두었으면 합니다.

2. Stepper Motor 회로도


  • 준비물 : 5V 스템모터, ULN2003 모터드라이버, 아두이노우노
  • 내용 : 8,9,10,11핀을 순서대로 IN1, IN2, IN3, IN4에 연결하고 스템모터와 UNL2003 모터쉴드에 연결하시오.

여기서 전원부분은 아두이노에서 빼냈습니다. 참고로 전원은 아두이노보다는 따로 외부 전원을 사용하시는 것이 좋습니다. 귀찮아서 실제로도 아두이노에 연결해서 사용했지만 Stepper Motor를 여러개 연결 할 경우 아두이노에 문제가 생길 수도 있습니다.


3. 코딩


  • 내용 : stepper 라이브러리 없이 1상 여자 방식으로 회전 시켜보자.

함수

pinMode(사용핀,사용모드) : 사용핀을 INPUT/OUTPUT 모드를 설정
digitalWrite(사용핀,출력상태) : 사용핀을 HIGH(5V) or LOW(0V) 설정
delay(시간값) : 시간값만큼 대기




1상 여자 방식은 전류의 흐름을 아래 그래프와 같습니다.


이걸 표로 살펴보면,


위 표처럼 4개의 핀에 전류를 순서대로 공급하면 됩니다.

회로도에서 핀 8(A), 9(B), 10(C), 11(D)의 핀을 전류 공급 순서를 step 순서대로 순차적으로 공급하면 Stepper Motor가 회전을 하게 됩니다.

1 step => A상(5V), B상(0V), C상(0V), D상(0)
2 step => A상(0V), B상(5V), C상(0V), D상(0)
3 step => A상(0V), B상(0V), C상(5V), D상(0)
4 step => A상(0V), B상(0V), C상(0V), D상(5V)

1 Step : 1,0,0,0 으로 사용자 정의 함수를 하나 phasesate()함수를 하나 만들어 봅시다.

void phasesate(int a, int b, int c, int d, int delayval){
  digitalWrite(stepPin[0],a);
  digitalWrite(stepPin[1],b);
  digitalWrite(stepPin[2],c);
  digitalWrite(stepPin[3],d);
  delay(delayval);
}

이렇게 함수를 하나 정의해 놓으면 이 함수를 호출함으로서 아래와 같이 간단히 표현할 수 있습니다.

phasesate(1,0,0,0,10);

phasesate(1번핀,2번핀,3번핀,4번핀,속도)로 이렇게 표현이 됩니다.

패턴을 하나의 함수로 묶으면,

void stepmovea(){
  phasesate(1,0,0,0,10);
  phasesate(0,1,0,0,10);
  phasesate(0,0,1,0,10);
  phasesate(0,0,0,1,10);
}

이제 회전을 시켜볼까요.

void loop() {
  for(int i=0;i<500;i++){
    stepmovea();
  }
}

약 500정도에 360도 근처정도의 회전을 시키게 됩니다. 정확히 360도 회전을 하지 않습니다.

역회전을 한다면, stepmovea() 패턴 함수를 역순으로 진행하면 됩니다.

void stepmoveb(){
  phasesate(0,0,0,1,10);
  phasesate(0,0,1,0,10);
  phasesate(0,1,0,0,10);
  phasesate(1,0,0,0,10);
}

이렇게 하면 역순으로 회전이 되겠죠.

코드를 종합해 보면.

const int stepPin[4] = {8,9,10,11};

void setup() {

  for(int i=0;i<4;i++){
    pinMode(stepPin[i],OUTPUT);
  }
}

void loop() {
 
  for(int i=0;i<500;i++){
    stepmovea();
  }
  for(int i=0;i<500;i++){
    stepmoveb();
  }
}
void phasesate(int a, int b, int c, int d, int delayval){
  digitalWrite(stepPin[0],a);
  digitalWrite(stepPin[1],b);
  digitalWrite(stepPin[2],c);
  digitalWrite(stepPin[3],d);
  delay(delayval);
}

void stepmovea(){
  phasesate(1,0,0,0,10);
  phasesate(0,1,0,0,10);
  phasesate(0,0,1,0,10);
  phasesate(0,0,0,1,10);
}

void stepmoveb(){
  phasesate(0,0,0,1,10);
  phasesate(0,0,1,0,10);
  phasesate(0,1,0,0,10);
  phasesate(1,0,0,0,10);
}

4. 결과


회전 속도를 10으로 해서 그런지 좀 느리게 회전을 하네요. 그리고 500번의 루프를 돌았는데 약 360도 정방향으로 회전 했다가 역방향으로 회전하고 이렇게 반복적으로 회전을 수행한 결과입니다.

마무리


오늘은 Stepper 라이브러리 없이 회전을 직접 시도 했습니다. 회전시 문제점은 정교한 회전을 하기 위해서는 Stepper Motor의 사양과 delay(시간)값을 수학적으로 계산해야 합니다. 이런 복잡한 부분을 직접 코딩하는 것은 귀찮은 일이죠. Stepper Motor의 종류에 따라서 접근 방식과 계산이 달라지기 때문에 시간적으로 낭비입니다. 그렇기 때문에 우리는 편하게 Stepper 라이브러리를 사용해야 합니다. Stepper Motor로 뭘 만들지 상상해야지 Stepper Motor를 직접 라이브러리를 만드는데 시간을 낭비하면 안되겠죠.

Stepper Motor가 어떻게 회전을 했는지만 의미만 이해하시고 이론적인 부분은 위에 링크로 소개한 게시물들로 가셔서 이론을 배우세요.


댓글()

[아두이노] Calibration의 의미

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

[아두이노] Calibration의 의미



오늘은 Calibration의 대해서 알아보고자 합니다. 우리가 Sensor를 사용하면 처음에 Sensor의 입력값에 대한 초기 보정이 필요하다고 이야기 한 적이 있을 꺼에요. 대표적으로 마이크사운드감지센서가 떠오르는군요. 이 Sensor의 초기 값은 처음 찍어보기 전까지는 알 수 없습니다. 그러면 코딩을 할 때 초기 Sensor의 값을 찍어봐야 알 수 있고 그 찍힌 값을 기준으로 코딩을 해야 합니다. 참 번거로운 코딩이 되겠죠. 그리고 같은 Sensor라도 초기값이 제각각 입니다. 대개 아날로그 신호를 출력하는 Sensor은 가변저항이 붙어 있어서 초기 값이 약간씩 차이를 보이고 이 가변저항을 돌리면 초기값이 변경 되기 때문에 미리 코딩을 해놓으면 초기 Sensor값이 다르기 때문에 원치 않는 동작을 수행 할 수 도 있겠죠. 이런 문제를 해결하기 위해서 Calibration라는 의미의 교정 작업을 코딩으로 표현할 수 있습니다. 이제부터서 Calibration에 대해서 자세히 살펴보도록 하겠습니다.

1. Calibration이란,


교정의 의미로 아두이노가 처음 전원이 공급되면 사용 할 부품에 대한 값에 대한 교정을 수행하기 위한 코딩입니다. 우리가 그래픽카드를 사면 젤 먼저 그래픽드라이버를 설치하고 모니터의 해상도를 설정 하잖아요. 여기서, 모니터의 해상도를 설정하는 작업을 교정작업이라고 생각하면 될 듯 싶네요. 실제 모니터의 해상도 작업이 아니라 의미적으로 그런 느낌의 작업을 처음에 수행하는 것을 Calibration이라고 의미적으로 이해하시면 될 듯 싶네요.

아두이노에서 Calibration의 작업은 Sensor의 초기값이나 초기 측정된 값에 대한 범위 지정을 설정하는데 주로 이용합니다. 즉, 초기 Sensor의 값을 기준점을 잡거나 기준 범위를 설정할 때 수행 합니다.

예를들어,


조도센서가 있다면 현실 환경에서는 측정되는 환경의 조건에 따라 그 값이 일정범위로 유지 되잖아요. 그러면 구지 전체 범위의 0~1023의 값의 기준으로 동작제어를 할 필요가 없습니다. 현재 환경에서 조도센서가 필요한 영역에 맞게 동작제어를 하면 효율적인 제어를 할 수 있겠죠. 그러면, 측정되는 조도센서값에서 동작 제어 값의 범위를 교정작업을 통해 최소~최대값을 잡아놓으면 그 값의 범위에 맞게 수행되기 때문에 정교하게 제어를 할 수 있게 됩니다.


또한, 마이크사운드감지센어의 경우에서도 가변저항기의 초기 값이 제각각이니깐 초기 값을 일정시간 동안 측정하고 측정된 값으로 교정하면 교정된 값이 기준이 되고 그 기준을 통해 마이크사운드감지센서의 소리가 입력에 대해 맞춰서 동작을 제어할 수 있게 됩니다.

예)

 int soundSensor =analogRead(A0);
    if(soundSensor>100) 작업명령1;
      else if(SoundSensor>50) 작업명령2;
        else 작업명령3;

이런 코드가 있다면 soundSensor 초기값이 만약 51이면 위 코딩에서는 else문을 절대 수행 할 수 없습니다. soundSensor 초기 값이 39면 else문을 수행 할 수 있지만 뭔가 너무 낮은 값에서 시작하기 때문에 조건식 100, 50의 비교가 옳바른지를 판단하기 애매합니다. 정확한 의미 전달의 작업명령을 하기 위해서는 어떻게 해야해야 할가요. 즉 초기의 soundSensor의 값을 기준으로 비교하는 값을 교정해야면 soundSensor의 값에 맞게 동작하겠죠.

SoundSensor의 초기 측정을 5초동안 수행해서 얻어진 평균값을 기준으로 해서

s = 기준값- 5초동안 측정한 평균값;

에서,

   if(soundSensor>100-s) 작업명령1;
      else if(soundSensor>50-s) 작업명령2;
        else 작업명령3;

이렇게 하면 대충 SoundSensor의 초기값에 맞게 작업명령을 수행하겠죠. 이렇게 교정하는 과정을 Calibration이라고 생각하시면 됩니다. 약간 의미의 느낌이 보정에 가까워져 버렸네요. 아두이노가 어떤 명령을 수행하기 위한 범위를 미리 잡아놓고 작업을 교정으로 생각하시면 될 듯 싶네요.

이제 본격적으로 실험을 해보도록 하겠습니다.

2. 회로도 구성


  • 준비물 : - 조도 센서 1개, 저항 10K옴 1개, 아두이노우노, 뻥판
  • 내용 : 아날로그핀에 조도센서의 입력 받을 수 있게 연결 한다.


저는 A0핀에 조도센서의 입력을 받도록 연결 했습니다.

3. 코딩


  • 내용 : 조도센서에서 일정시간동안 측정하고자 하는 최소~최대값을 범위를 측정한다.

함수

  • analogRead(아날로그핀) : 0~1023의 아날로그 신호를 읽음
  • millis() : 현재 타이머 시간값 반환
  • map(입력값, 입력최소값, 입력최대값, 출력최소값, 출력최대값) : 입력값이 입력 최소~최대값의 어느 위치이고 그 위치에 매칭되는 출력 최소~최대값 사이의 값을 반환한다.
  • constrain(입력값, 최소값, 최대값) : 입력값이 최소값보다 적을 경우 최소값에 수렴하고 입력값이 최대값보다 클 경우 최대값에 수렴한다.
    예로 constrain(3, 0, 10)이면 3이 반환, constrain(-3, 0, 10)이면 0이 반환, constrain(15, 0, 10)이면 10이 반환

Serial 통신

- Serial.begin(9600) : 시리얼통신 시작
- mySerial.print(값) : 값을 시리얼모니터로 출력하지만 커서는 현재 라인에 계속 유지.
- mySerial.println값) : 값을 시리얼 모니터로 출력하지만 커서는 새로운 라인으로 이동. 


const int sensorPin = A0;
int sensorValue = 0;      
int sensorMin = 1023;     
int sensorMax = 0;       

void setup() {
  Serial.begin(9600);
  
  while (millis() < 5000) {
    sensorValue = analogRead(sensorPin);

    if (sensorValue > sensorMax) {
      sensorMax = sensorValue;
    }
 
    if (sensorValue < sensorMin) {
      sensorMin = sensorValue;
    }
  }  
}

void loop() {
  
  sensorValue = analogRead(sensorPin);   
  sensorValue = map(sensorValue, sensorMin, sensorMax, 0, 255);  
  sensorValue = constrain(sensorValue, 0, 255);

  Serial.print(sensorMin);
  Serial.print(" ");
  Serial.print(sensorMax);
  Serial.print(" ");
  Serial.println(sensorValue);  
}

제 블로그에도 소개한 적이 있는데 다시 소개하게 되었네요. loop문에서 사용 될 sensorMin과 sensorMax의 값을 교정작업을 setup() 함수에서 수행이 됩니다. Calibration의 동작은 setup()함수의 while()문이니깐 어떻게 교정되는지 잘 살펴 볼까요.

int sensorMin = 1023;     
int sensorMax = 0;      
  while (millis() < 5000) {
    sensorValue = analogRead(sensorPin);

    if (sensorValue > sensorMax) {
      sensorMax = sensorValue;
    }
 
    if (sensorValue < sensorMin) {
      sensorMin = sensorValue;
    }
  }  

위 코딩은 최소값을 1023으로 하고 최대값 0으로 초기값으로 while()문에 들어갑니다. 이때 while()함수의 조건식은 millis() 함수의 값이 5초보다 작을 때까지 참이 되는 문장입니다. 여기서, millis()은 시간함수로 아두이노에 전원이 공급되는 순간부터 0부터 시작하여 타이머가 돕니다. 시간값을 1000당 1초라고 생각하시면 됩니다. 처음 전류가 공급되니 0부터 시작해서 5000이 될 때 5초가 되게 됩니다. 즉, 5초동안 무한 루프를 돌게 됩니다.

if(조도센서값 >최대값){
  최대값=초도센서값;
}
if(조도센서값 <최소값){
  최소값=조도센서값;
}

조도센서값이 최대값보다 크면 그 값을 최대값으로 바꾸고 조도센서값이 최소값보다 작으면 그 값을 최소값으로 5초동안 반복 명령을 수행한다고 생각하시면 됩니다. 5초동안 측정된 최소~최대값을 실제 측정하는 입력범위 최소~최대값을 교정하게 됩니다.

void loop() {  
  sensorValue = analogRead(sensorPin);   
  sensorValue = map(sensorValue, sensorMin, sensorMax, 0, 255);  
  sensorValue = constrain(sensorValue, 0, 255);
}

조도센서값을 읽고

map(조도센서값, 교정최소값, 교정최대값, 0, 255)

이렇게 교정된 값을 기준으로 조도센서값이 처리되어 나오게 되는 것이죠. 다음 라인은 만약에 교정된 최소~최대값의 범위를 벗어나는 값이 입력되었다면 예외처리가 필요합니다. constrain()함수로 값을 벗어나지 못하게 합니다.
map()함수에서는 교정값에 대해서 0~255가 매칭되어 출력되지만 벗어나게 되면 마이너스값이나 255이상의 값으로 나오게 됩니다. 그럴 경우에 대해서 다음과 같은 명령라인을 코딩하게 됩니다.

constrain(sensorValue, 0, 255);

마이너스 값을 가지게 되면 0에 수렴되게 하고 255이상의 값을 가지게 되면 255에 수렴되게 하는 일종의 값의 범위에 락을 걸어놓은 느낌이라고 생각하시면 될 듯 싶네요.

이제 코딩에 대해서 설명했으니깐 정확하게 교정된 값이 출력되는지 살펴 볼까요.

4. 결과



조도센서의 측정된 값이 교정최소값 54와 교정 최대값 951사이에서 map()수행 후 241로 반환되어 나왔네요.

가령, 아래와 같은 예외 상황이 발생한다면


조도센서값이 951을 넘어갈 경우 최대값을 넘어가기 때문에 map()함수는 255 이상의 값을 반환됩니다. 하지만 constrain()함수로 255에 수렴 되게 해서 결과가 255로 나왔네요.

마무리


아두이노공식 홈페이지에 튜토리얼에 나온 예제입니다. 너무 괜찮은 소스라 그대로 인용하게 되었네요. Calibration의 의미를 잘 이해하시고 아두이노에서 사용하셨으면 합니다. 사용하는 이유는 사용하는 Sensor의 초기값을 교정함으로써 보다 정교하게 컨트롤 하기 위함이니깐 좀 더 정교하게 동작을 제어하고 싶다면 꼭 Calibration의 의미를 꼭 이해를 하셨으면 합니다. 그리고 위 코딩과 같은 것이 교정의 하나의 방법이지 절대적 교정이 아닌 점을 기억 하세요. 이런 느낌의 교정하는게 Calibration 라고 생각하시면 됩니다.

여러분들도 한번 조이스틱의 초기 중심값에 대해 교정작업을 이 원리를 곰곰히 생각한 뒤 에 적용해 보셨으면 합니다. 그러면 따로 초기에 코딩으로 측정하고 측정된 값을 다시 수작업으로 코딩하지 말고 교정작업으로 한번에 처리해서 어느 조이스틱과 연결해도 해당 조이스틱에 맞게 컨트롤 될 수 있게 만들어 보세요.

이 외에도 생각하는 Sensor가 있다면 그 Sensor에 Calibration를 적용한다면 어떻게 코딩할까 하고 상상의 나래를 펼쳐 보세요.

댓글()

[아두이노] 조이스틱+processing 3D 도형 회전(2)

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

[아두이노] 조이스틱+processing 3D 도형 회전(2)



지난 시간에는 아두이노는 아두이노 제어 코딩을 하고 processing은 아두이노에서 전송된 방향키 data를 시리얼통신 받아와서 3D 도형 회전을 하였다면 오늘은 prcoessing에서 3D 도형 뿐 아니라 아두이노 코딩까지 전부 담당하는 코딩을 살펴보도록 하겠습니다.


1. 아두이노 소스



지난 시간에 아두이노 방향키를 만든 소스입니다. 시리얼모니터로 해당 방향 알파벳을 출력한 소스인데 processing 코딩에 그대로 복사해서 이식할 예정입니다.

const int AXIS_X = A0;
const int AXIS_Y = A1;
const int SW_P = 3; 
 
void setup() {
  Serial.begin(9600);
  pinMode(SW_P,INPUT_PULLUP);
}
 void loop() {
  //X축 방향값
  if(analogRead(AXIS_X)<=300){
    Serial.println('a');
  }
  else if(analogRead(AXIS_X)>=700){
    Serial.println('d');   
  }
  //Y축 방향값
  if(analogRead(AXIS_Y)<=300){
    Serial.println('w');      
  }
  else if(analogRead(AXIS_Y)>=700){
    Serial.println('s');
  }
  delay(20);
}

2. processing으로 옮기기



위 아두이노 기본 방향키 값을 구하는 로직을 processing으로 전부 가져 오겠습니다.
지난 시간의 processing 코딩한 소스에다가 옮겨야 하니깐 우선 원래 소스는 다음과 같습니다.

[processing 소스]

import processing.serial.*;

Serial myPort;

int x=0;
int y=0;

void setup() {

    println(Serial.list());
    myPort = new Serial(this, Serial.list()[0], 9600);
    myPort.bufferUntil('\n');
    noStroke();
    size(600,600,P3D);
    
} 

void draw() {  
    background(0); //배경색
    lights();  //조명

    pushMatrix();  //Start
    fill(0,255,0); //채우기
    translate(width/2,height/2,-100); //이동
    rotateX(radians(y)); //x축 회전
    rotateY(radians(x)); //y축 회전
    
    box(200,100,200); //상자
    popMatrix(); //End
}
 
void serialEvent(Serial p) { 
  String inString = myPort.readStringUntil('\n');
  char ch=inString.charAt(0);
    
  println(ch);
  
  if (ch == 'w') {
      y+=1;
  } 
  else if (ch == 's') {
     y-=1;
  } 
  else if (ch == 'a') {
     x-=1;
  } 
  else if (ch == 'd') {
     x+=1;
  }
}

아두이노 함수들을 사용하기 위해서는 다음과 같은 과정이 필요합니다.

아두이노 라이브러리를 import 해서 객체 변수로 하나 만들어야 합니다.

[ processing에서 아두이노 코딩 ]

import cc.arduino.*;
Arduino arduino;

Arduino arduino;

arduino = new Arduino(this, Arduino.list()[0], 57600); //인스턴스화
    

아두이노 소스코딩 부분에 앞에 arduino 객체변수를 접근자로 연결해서 바꿔주시면 됩니다.

[아두이노 소스랑 processing 소스를 결합]을 하면 다음과 같습니다. const 상수형 표현은 processing에서 에러가 발생하더군요. 그래서 int형으로 만들었습니다.스위치 SW_P 변수로 사용준비만 해놓기만 했고 실제로 사용하지 않습니다. 나중에 이 키값은 3D box 의 색상을 변경하는 코딩을 여러분들이 한번 IF문을 써서 해보세요.

import processing.serial.*;
import cc.arduino.*;


Arduino arduino;

int AXIS_X = 0;
int AXIS_Y = 1;
int SW_P = 3; 

int x=0;
int y=0;

void setup() {  // this is run once.   

    println(Serial.list());
    arduino = new Arduino(this, Arduino.list()[0], 57600);
    arduino.pinMode(SW_P,Arduino.INPUT_PULLUP);

    size(600,600,P3D);
    noStroke();    
} 

void draw() {  
    
    move();
    
    background(0); //배경색
    lights();  //조명

    pushMatrix();  //Start
    fill(0,255,0); //채우기
    translate(width/2,height/2,-100); //이동
    rotateX(radians(y)); //x축 회전
    rotateY(radians(x)); //y축 회전
    
    box(200,100,200); //상자
    popMatrix(); //End
}
 
void move() { 
  int axisX = arduino.analogRead(AXIS_X);
  int axisY = arduino.analogRead(AXIS_Y);  
  
  //X축 방향값  
  if(axisX <= 300){
    x-=1;
  }
  else if(axisX >= 700){
    x+=1;
  }
  //Y축 방향값
  if(axisY <= 300){
    y-=1;
  }
  else if(axisY >= 700){
    y+=1;
  }
  delay(20);
}

따로 방향키 알파벳을 만들 필요 없고 시리얼 호출 함수명을 move()함수명으로 사용자 정의 함수로 변경 했습니다. 아날로그 핀이니깐 0,1번 핀으로 아날로그로 읽으면 A0, A1핀의 값을 읽습니다. 방향키 알파벳 자리에 x, y 변수값을 해당 조이스틱의 방향으로 증감시키면 됩니다.

아마 이 코딩이 지난 시간에 비해 훨씬 편한 코딩인 것 처럼 보일 꺼에요. 아두이노에서 명령을 내린 코딩을 processing으로 전부 가져와야 하니깐 좀 번거롭고 코딩이 될 수 있스니다. 나중에 processing이나 아두이노 코딩이 복잡해지고 길어지면 코딩이 산만해 질 가능성이 있기 때문에 아두이노는 아두이노 코딩을 하고 processing은 processing 코딩을 하는게 좋습니다.

3. 실행 과정


[회로도]


회로도는 동일합니다.

[1단계] : 아두이노는 processing이 사용할 수 있는 조건으로 펌웨어 해야합니다. "Firmata->StandardFirmata" 업로드 시키면 됩니다. 그러면 processing에서 아두이노를 제어할 수 있습니다.




[2단계] : processing에 아두이노+processing 가 합쳐진 소스를 복사해서 붙여 넣으시면 됩니다. 아래 이미지는 기존 소스에서 지워야 했던 시리얼객체변수가 이미지에 남아 있네요. 지웠어야 했는데 오의티네요.


[3단계] : processing 실행하시면 윈도우창이 뜨면서 3D box가 나타납니다. console창에는 com넘버 출력되고 3D box가 정상적으로 출력되면 제대로 실행 된거라 생각히시면 됩니다.


4. 결과


폰으로 촬영하려다 화질이 구려서 포기 했네요. 아래 움직이는 이미지는 어제 사용한 아미지가 아니고 다시 pc 녹화한 영상입니다. 옆에 코딩 소스를 보시면 Arduino 객체가 선언되어 있는 걸 보실 꺼에요. pc 실행 영상만 녹화했습니다. 정상적으로 조이스틱에 따라 3D box가 회전하네요.


마무리


원래는 다른 포스트를 할 예정이였는데 processing은 두가지 방식으로 아두이노를 접근하기 때문에 어쩔 수 없이 두개의 포스트로 나눠서 오늘까지 쓰게 되었네요. 추가적으로, 한번 Bluetooth를 스마트폰에 연결해서 스마트폰으로 조정한 값을 토대로 processing 3D box를 회전를 해보셨으면 합니다. 이건 포스팅 하지 않겠습니다. 금요일 소스에서 스마트폰에서 방향키 값을 Bluetooth로 통해서 아두이노에 시리얼통신 한 값을 다시 pc에 시리얼모니터로 출력하는 문장으로 보내면 그 값을 processing이 읽어서 회전 시키면 되기 때문입니다. 금요일 소스에서 조이스틱 부분 빼고 Bluetooth통신으로 값을 읽고 Serial 통신으로 값을 보내면 끝납니다.

어렵지 않으니깐 한번 해보셨으면 하네요.

댓글()

[아두이노] 조이스틱+processing 3D 도형 회전(1)

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

[아두이노] 조이스틱+processing 3D 도형 회전(1)



오늘은 조이스틱의 키 값을 가지고 지난 시간에 포스트한 3D 도형을 회전시키는 실험을 해보겠습니다. 참고로 오늘 접근 방식은 아두이노는 기존 조이스틱 제어 코딩을 그대로 동작하고 processing에서 조이스틱을 제어한 값을 시리얼통신을 통해서 값을 받아 3D 도형을 회전 시킵니다. 즉, 아두이노는 아두이노 역할에 충실하고 processing은 processing의 역할을 충실한 코딩으로 생각하시면 됩니다.


1. 아두이노 소스


지난 시간에 아두이노 방향키를 만든 소스입니다. 시리얼모니터로 해당 방향 알파벳을 출력한 소스인데 그대로 적용할 예정입니다.

const int AXIS_X = A0;
const int AXIS_Y = A1;
const int SW_P = 3; 
 
void setup() {
  Serial.begin(9600);
  pinMode(SW_P,INPUT_PULLUP);
}
 void loop() {
  //X축 방향값
  if(analogRead(AXIS_X)<=300){
    Serial.println('a');
  }
  else if(analogRead(AXIS_X)>=700){
    Serial.println('d');   
  }
  //Y축 방향값
  if(analogRead(AXIS_Y)<=300){
    Serial.println('w');      
  }
  else if(analogRead(AXIS_Y)>=700){
    Serial.println('s');
  }
  delay(20);
}

2. processing 소스


지난 시간에 3D box를 제어한 포스트에서 다룬 소스를 기반으로 아두이노의 시리얼 통신 값을 가지고 3D box를 제어 해보기 위해서 몇가지 지난 소스에서 수정을 해야 합니다.

복습 : 시리얼 통신



시리얼통신을 사용하기 위해서는 Serial를 프로세싱에 import해야 합니다. 그리고 사용 할 객체 변수를 하나 선언해야겠죠.

import processing.serial.*;

Serial myPort;  

다음으로 myPort를 인스턴스화 해야 합니다.

 println(Serial.list()); //연결된 포트정보
 myPort = new Serial(this, Serial.list()[0], 9600); //시리얼통신 세팅

연결된 포트정보를 list()함수로 찾을 수 있고 그 정보를 시리얼모니터로 출력합니다. 그리고 myPort는 객체변수를 선언했다고 해서 바로 사용되는게 아닙니다. 객체변수 선언은 비유로 들자면 껍때기만 만들어 놓은 거고 인스턴스를 해야 합니다. 할당한다는 의미로 생각하면 될 듯 싶네요. 비유로 알맹이를 채운다고 생각하면 좋을 듯 싶네요.

이렇게 해서 시리얼통신의 준비 작업은 끝났습니다.

다음으로 시리얼통신에서 들어온 데이터를 읽는 방법은 두가지 방법이 있습니다. 아두이노에서 시리얼통신으로 읽는 방식과 동일하게 코딩하는 방법과 processing 자체에서 시리얼통신에서 데이터가 들어오면 자동으로 호출되는 함수가 있습니다. 두번째 호출함수로 코딩을 해보겠습니다.

void serialEvent(Serial p) { 
    명령;
}

SerialEvent()함수가 바로 시리얼통신으로 데이터가 들어오면 바로 호출되는 함수입니다.

void serialEvent(Serial p) { 
  String inString = myPort.readStringUntil('\n');
  char ch=inString.charAt(0);
}

시리얼 통신에서 일부러 문자열로 받았습니다. myPort.readString()함수는 문자열로 읽는 함수입니다. 여기서 myPort.readStringUntile('\n') 함수는 찾고자 하는 인자가 없을 때 null을 반환합니다. 문자열 라인 단위로 읽는다고 생각하면 될 듯 싶네요.

참고로, 아두이노에서는 시리얼통신으로 println('a') 함수로 보내면 "a\n"이렇게 전송되게 됩니다. 받는 쪽에서 만약 문자니깐 함수를 myPort.readChar() 함수를 쓰게 되면 한 글짜씩 개별로 읽게 됩니다. 'a'라는 문자를 보냈지만 추가로 옆에 기호까지 같이 보내지기 때문에 Char으로 읽으면 안됩니다. 나중에 여러개 값을 한번에 보낼 때를 사용하시라고 String 문자열로 읽겠습니다.
'a'라는 한글자지만 문자열이기 때문에 해당 글자를 한글자 문자로 읽어서 변수에 저장하겠습니다. charAt(0)으로 0번째 위치의 문자를 읽는 함수인데 'a'라는 문자를 읽게 됩니다. 그걸 char 문자 변수에 저장하면 되겠죠.

진짜 번거로운 과정을 거쳐서 코딩을 했네요. 그 이유는 나중에 여러분들이 자이로센서같은 것을 다루게 되면 한번에 x,y,z 값을 받아서 읽게 되는데 이때 문자열로 읽고 해당 위치의 값만 빼내서 제어하는데 활용하시라고 미리 이렇게 번거로운 코딩을 하게 되었네요.

시리얼 통신에서 데이터를 읽는 함수의 종류는 다양합니다. 그 다양한 함수들은 위에 링크한 라퍼런스을 보고 한번 여러분들이 따로 코딩해 보셨으면 합니다.

추가로,

inString = trim(inString);

문자열의 시작과 끝에서 공백 문자를 제거하는 함수입니다.("nbsp" 제거) 이 명령라인을 추가해 주시면 더 안정적으로 데이터를 읽겠지만 실험에서는 그냥 사용하지 않았네요. 이런 함수가 있다는 정도만 이해하시고 나중에 활용해 보세요.

ch변수에 겨우 방향키 값을 저장할 수 있게 되었습니다. 지난 3D box 제어에서 키보드로 제어했던 것을 기억하실 지 모르겠네요.

[ 키보드 제어 원 소스]

void keyPressed() {
  if (key == CODED) {
    if (keyCode == UP) {
      y+=1;
    } 
    else if (keyCode == DOWN) {
      y-=1;
    } 
    else if (keyCode == LEFT) {
      x-=1;
    } 
    else if (keyCode == RIGHT) {
      x+=1;
    }
  }
  else if (key == 'z' || key == 'Z') {
      z+=10;
  } 
  else if (key == 'x' || key == 'X') {
      z-=10;
  } 
}

여기서, 수정을 하면은 다음과 같습니다.

void serialEvent(Serial p) { 
    String inString = myPort.readStringUntil('\n');
    char ch=inString.charAt(0);
    println(ch); //시리얼모니터로 방향키 값 출력
    
    if (ch == 'w') {
        y+=1;
    } 
    else if (ch == 's') {
        y-=1;
     } 
     else if (ch == 'a') {
        x-=1;
     } 
     else if (ch == 'd') {
        x+=1;
     }  
}

여기서는 X, Y축을 한꺼번에 if~ else if문으로 표현했습니다. 지난 시간에 방향키 값을 만들 때는 X축과 Y축을 따로 if문을 했는데 여기서 묶여서 처리한 이유가 뭘까요. 방향키 ch변수에는 하나의 값만 존재합니다. 하나의 값만 순서대로 읽어오기 때문에 X,Y축을 구별 안하고 읽어온 하나에 값에 대한 하나의 동작만이 필요하기 때문에 선택문으로 전부 연결한 것이죠.

if문은 상황에 따라 맞게 의미를 이해하시고 사용하셔야 합니다.

전체적으로, 코딩을 하면 아래와 같습니다.

import processing.serial.*;

Serial myPort;

int x=0;
int y=0;

void setup() {

    println(Serial.list());
    myPort = new Serial(this, Serial.list()[0], 9600);
    myPort.bufferUntil('\n');
    noStroke();
    size(600,600,P3D);
    
} 

void draw() {  
    background(0); //배경색
    lights();  //조명

    pushMatrix();  //Start
    fill(0,255,0); //채우기
    translate(width/2,height/2,-100); //이동
    rotateX(radians(y)); //x축 회전
    rotateY(radians(x)); //y축 회전
    
    box(200,100,200); //상자
    popMatrix(); //End
}
 
void serialEvent(Serial p) { 
  String inString = myPort.readStringUntil('\n');
  char ch=inString.charAt(0);
    
  println(ch);
  
  if (ch == 'w') {
      y+=1;
  } 
  else if (ch == 's') {
     y-=1;
  } 
  else if (ch == 'a') {
     x-=1;
  } 
  else if (ch == 'd') {
     x+=1;
  }
}

3. 실행 과정


[회로도]


[1단계] : 아두이노 IDE에 방향키 소스를 복사해서 붙여 넣으시고 화살표에 업로드 버턴을 누르면 아래 정상적으로 처리되면 업로드 완료라고 뜹니다.


[2단계] : 시리얼모니터로 결과 검사



정상적으로 방향키값이 시리얼모니터로 출력되는 것을 확인 하실 수 있습니다.

[3단계] : processing에 오늘 수정한 코드를 복사해서 붙여 넣으시고 화살표 실행 버턴을 누르시면 아래 그림처럼 console창에 방향키값이 정상적으로 출력되는 것을 확인 하실 수 있을 겁니다.



[4단계] : processing에서 만든 윈도우창이 뜨면서 3D box가 보여지는데 조이스틱으로 조정을 하면 이 3D box가 움직이는 걸 확인 하실 수 있을 꺼에요.


4. 결과


processing에서 실제로 조이스틱을 조정하면 움직이는 이미지 입니다.


실제 폰으로 촬영한 영상으로 살펴 봅시다.


만약, 여러분들이 processing를 좀 더 깊게 공부하시면 아두이노와 processing를 연동한 재밌는 작품들을 만드실 수 있을 거라 생각됩니다.

마무리


이렇게 시리얼통신을 통해서 읽은 데이터 값으로 processing 제어하는 실험을 하였습니다. 예전에는 processing에서 아두이노를 제어 했다면 반대로 아두이노에서 processing를 제어한 실험인데 그렇게 어렵지 않죠. 여기서 부터 출발하여 좀 더 다양한 시각화 표현을 processing 에서 코딩하면 좀 더 멋진 작품으로 만들어 질 수 있습니다.

여러분들도 한번 다른 모형을 만들어서 제어를 꼭 해보셨으면 합니다. 최대한 의미 전달을 목적으로 코딩을 최소화 하기 위해서 box()함수로 상자도형을 간단히 제어를 했짐나 좀 더 멋진 모형을 만들어서 멋진 작품을 만드셨으면 하네요.

댓글()

[아두이노] 조이스틱 방향키 값 출력

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

[아두이노] 조이스틱 방향키 값 출력



지난 조이스틱 포스트에서 현재 상태값을 출력하는 실험을 했었습니다. 그리고 조이스틱 두번째 포스트에서는 조이스틱 X축 0~1023 아날로그 신호 값을 기준으로 Servo Motor를 제어도 해보았습니다. 마무리에 방향 조절에 대한 로직을 간단히 글로 소개 한 적이 있는데 이번에 제대로 이 주제에 대해 포스트를 할까 합니다.


날씨가 우중충해서 그런가 사진이 이상하게 나왔네요.

1. 조이스틱 방향키 값 만들기




위그림은 지난 조이스틱 회로도 입니다.


조이스틱의 X(A0)핀, Y(A1)핀 값을 위 그림과 같은 방향 키값으로 만들어 내려고 합니다.

[복습] : 조이스틱 현재 상태값 출력

const int AXIS_X = A0;
const int AXIS_Y = A1;
const int SW_P = 2; 
 
void setup() {
  Serial.begin(9600);
  pinMode(SW_P,INPUT_PULLUP);
}
 
void loop() {

  Serial.print("Axis_X: ");
  Serial.println(analogRead(AXIS_X));

  Serial.print("Axis_Y: ");
  Serial.println(analogRead(AXIS_Y));

  Serial.print("SW_P:  ");
  Serial.println(digitalRead(SW_P));

  delay(500);
}

위 소스는 조이스틱의 상태값을 시리얼모니터로 출력하는 예제입니다. 방향키 값을 만들기 위해서 Axis_X, Axis_Y 값에서 방향값으로 분리해 낼 능력이 필요합니다.

Axis_X, Axis_Y 값은 0~1023의 아날로그 값을 가집니다. 이론상으로 조이스틱의 중심값은 (X, Y)은 (512, 512)가 됩니다.

여기서, X축으로 아날로그 값 0~1023에서 A방향의 아날로그 신호값의 범위는 0~512 사이가 되고 D방향의 아날로그 신호값의 범위는 512~1023 사이가 됩니다. 그런데 실제로 조이스틱은 512가 중심값이 아니라고 했죠. 하드웨어 자체 조립과정의 문제와 환경적 요인에 의해 512의 근사값이라고 저번 시간에 이야기 했는데 오늘 실험에서는 정밀 제어가 아닌 기초적인 간단한 제어임으로 512이라고 가정하겠습니다.

위 그림을 보면 동그라미 모양이 있죠. 이 동그라미 영역은 방향 불인정 영역으로 잡아 놓고 나머지 영역을 방향으로 하면 방향을 명확하게 나눌 수 있습니다. 즉, 조이스틱의 방향을 움직일 때 동그라미 영역을 무시하고 그 나머지 신호값을 해당 방향의 키값으로 설정하면 명확하게 나누어지게 됩니다. 그 이유는 중심 근처는 방향 신호값의 변화가 너무 심하기 때문입니다. A와 D값의 교차 변화가 중심 근처에서 빈번하게 발생하면 키 값이 교차로 빈번하게 출력되고 그 값을 통해서 움직이는 물체는 떠는 것처럼 "덜덜덜" 하면서 움직이게 됩니다. 움직임의 값이 크게 세팅하면 왔다 갔다 반복하는 현상이 그 중심점에서 빈번하게 일어난다고 생각하시면 될 듯 싶네요. 그래서 중심지점 근방은 방향 신호값으로 인정 안함으로써 좀 도 명확하게 방향키 값을 잡는 것이죠.

제 경우는 그냥 500을 기준으로 X축 좌/우 방향을 200씩 무시구간을 잡았습니다. 정확히 200이 아니라 그냥 500기준 200으로 정교함보다는 의미 전달 방향키 값임을 참고하고 보시기 바랍니다.

[X축 키값 구하기]

  //X축 방향값
  if(analogRead(AXIS_X)<=300){
    Serial.println('a');
  }
  else if(analogRead(AXIS_X)>=700){
    Serial.println('d');   
  }

위 코딩은 X축을 기준으로 방향키 a, d를 출력하는 로직입니다. 500을 기준으로 300이하일때 왼쪽(a)키로 인정하고 700이상일때 오른쪽(d)키로 인정한다는 문장입니다.
만약에, 정확하게 나눌려면 조이스틱 X중심이 521로 설정되어 있으면 200차이로 나눈다면 왼쪽 기준은 321이고, 오른쪽 기준 721이겠죠. 간단한 실험이라 수치를 보기 편하게 단순화 시킨 수치입니다. 여러분들은 조이스틱로 실제 제어한다면 300, 700으로 하지 말고 좀 더 조정된 값으로 제어를 하셨으면 합니다.

여기서, if문을 개별적으로 해도 되지만 else if문을 쓴 이유가 뭘까요. AXIS_X 기준 신호로 보면 기준 신호 값에서 +, - 값인 둘중 하나상태만 존재하기 때문입니다. 예를 들면은 X축의 조정신호 값이 오른쪽이면서 왼쪽일 수 없잖아요. 오른쪽이면 오른쪽 신호값만 왼쪽이면 왼쪽 신호값만 존재합니다. 동시 오른쪽, 왼쪽이 나올 수 없습니다. 그래서 if~else if 문으로 왼쪽이냐 묻고 아니면 오른쪽이냐 묻는 문게 했습니다. 이 표현이 가장 괜찮은 코딩으로 이렇게 if~else if문을 사용하게 되었습니다.

반대로, Y축을 살펴볼까요 동일합니다.

[Y축 키값 구하기]

//Y축 방향값
  if(analogRead(AXIS_Y)<=300){
    Serial.println('w');      
  }
  else if(analogRead(AXIS_Y)>=700){
    Serial.println('s');
  }

그러면, 여기서 왜 X축과 Y축을 다 묶어서 if~else if문을 쓰지 않았을 까요.

  if(analogRead(AXIS_X)<=300){
    Serial.println('a');
  }
  else if(analogRead(AXIS_X)>=700){
    Serial.println('d');   
  }
  else if(analogRead(AXIS_Y)<=300){
    Serial.println('w');      
  }
  else if(analogRead(AXIS_Y)>=700){
    Serial.println('s');
  }

이렇게 안한 이유는 조이스틱에서 X, Y축을 동시에 읽어오게 됩니다. 그러면 X축을 읽고 X축의 값에 만족하는 왼쪽/오른쪽이 결정되면은 Y축은 무시하고 넘어가게 됩니다. 그래서 X축의 방향키를 구하고 Y축의 방향키를 구하는 식을 따로 if문을 분리해서 코딩한 것이죠.

if문을 어떻게 처리하냐에 따라서 그 의미는 완전 달라지기 때문에 if문도 신중하게 써야 합니다. 그리고 위 코딩도 썩 좋은 코딩이 아닙니다. 해당 축을 읽는 함수는 한번으로 하면 좋지만 두번 연속으로는 읽는다면 비효율적이겠죠.

int x = analogRead(AXIS_X);
int y = analogRead(AXIS_Y);

x, y값을 if문으로 비교하시면 구지 두번 반복해서 읽을 필요는 없게 됩니다. 전 그냥 2번 반복읽기로 뒀네요.

2. 코딩


const int AXIS_X = A0;
const int AXIS_Y = A1;
const int SW_P = 3; 
 
void setup() {
  Serial.begin(9600);
  pinMode(SW_P,INPUT_PULLUP);
}
 void loop() {
  //X축 방향값
  if(analogRead(AXIS_X)<=300){
    Serial.println('a');
  }
  else if(analogRead(AXIS_X)>=700){
    Serial.println('d');   
  }
  //Y축 방향값
  if(analogRead(AXIS_Y)<=300){
    Serial.println('w');      
  }
  else if(analogRead(AXIS_Y)>=700){
    Serial.println('s');
  }
  delay(20);
}

스위치 버턴도 선언은 했지만 그냥 해당 버턴키는 만들지 않았습니다. 다음에 processing과 연동 할 때 줌인/줌아웃키로 하려고 햇는데 스위치버턴이 한개라 따로 뻥판에 스위치 버턴을 만들어 표현 할 수 있었지만 그냥 방향제어만으로만 포스팅을 하고 위 틀은 확장 코딩을 할 수 있는 상태로 표현했다고 생각하시면 될 듯 싶네요. 나머지 코딩은 여러분들이 추가로 스위치 버턴을 가지고 제어해보시라고 남겨 둡니다.

미완성 코딩으로 위에 아날로그 신호값을 읽어 변수에 저장하는 것과 스위치 버턴을 추가로 로직을 짜셔 완성 시켜보세요.

3. 결과


시리얼 모니터로 출력되는 영상 입니다.


깔끔하게 잘 나오네요. 다음은 폰으로 조이스틱을 움직일 때 키 값이 나오는 영상입니다.


마무리


오늘은 간단히 조이스틱의 방향 키 값을 시리얼모니터로 출력 했습니다. 이 출력이 무슨 키 값이야 하고 생각하실 수 있지만 내일 포스트는 processing과 연동합니다. 시리얼통신으로 보내지는 이 알파벳 값을 processing에서 읽고 그 값을 기준으로 지난 processing 예제인 box 3D 도형을 도형을 움직이게 할 예정 입니다. 키보드로 제어 했던 부분을 오늘 배운 키 값으로 제어하겠다는 의미인 셈이죠. 왜! 키 값인지 아시겠지요.

오늘 포스트 한 내용을 잘 따라오셔야 내일 재밌는 것을 할 수 있습니다.

이상 마무리 하고 오늘도 배운 내용을 어디서 써먹을지 상상의 나래를 펼쳐 보세요.

댓글()

[아두이노] processing 3D 도형 제어

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

[아두이노] processing 3D 도형 제어 


예전에 잠깐 아두이노와 연동하는 processing을 소개 한 적이 있습니다. 오늘은 아두이노 부분을 제외하고 간단히 processing에서 키보드와 마우스로 도형을 제어하는 원리를 소개하려고 합니다. 이부분을 어느정도 이해하시면 나중에 아두이노에서 제어값을 입력된 값으로 해서 processing 이미지를 제어 할 수 있기 때문입니다. 오늘 다루게 될 키보드와 마우스가 나중에 아두이노로 대체하면 재밌는 표현들을 할 수 있기 때문에 관심을 가져줬으면 하는 마음으로 포스팅을 합니다.

1. processing 3D box() 도형 그리기


[복습]

void setup() {  
  한번만 수행;
}
void draw() { 
  무한 반복 수행;
}

아두이노랑 구조가 비슷하다고 했죠. setup()은 한번만 수행되는 초기화 작업을 코딩하고, draw()은 계속 그리는 무한 작업 코딩을 담당합니다.

void setup() {  
    
    size(600,600,P3D); //창사이즈
    noStroke();  //테두리없음

//  frameRate(30); //초당 프레임
} 
void draw() {  
     background(0); //배경색
}

추가적으로 초기 설정하는 함수들이 있는데 다 생략하고 딱 두개만 사용합니다. 윈도우 창이 하나 생성되는데 size() 함수로 윈도우 창 만듭니다. 여기서, "P3D" 3D Rendering 모드입니다. "OPENGL"로 선언해도 됩니다. 참고로 opengl은 import 선언이 필요하지만요. 그리고 테두리를 그리지 않는 noStroke()함수를 사용합니다. box 그렸는데 테두리가 보기 흉해서 테두리를 지웠네요.

배경색은 background(0)로 검정색으로 했네요. 아래 그림은 위 코딩의 결과입니다.


다음으로,

void draw() {  
    background(0); //배경색
    lights();  //조명

    pushMatrix();  //Start
    fill(0,255,0); //채우기
    translate(width/2,height/2,-100); //이동
    box(200,100,200); //상자
    popMatrix(); //End
}

옆에 주석을 다 달아 놓았습니다. 배경색은 검정색으로 하고 lights() 조명효과를 나타냅니다. 이미지 도형을 만들면 그 이미지에 light를 비추는 효과라고 생각하시면 됩니다.

fill()은 그리는 도형에 어떤 색을 채울것인지 지정하는 함수입니다. 즉, fill() 함수에 의해서 다음에 그려진 도형의 표면의 색이 결정됩니다. 사각형을 그리면 사각형 면의 색이 fill()함수의 색으로 채워진다고 생각하시면 됩니다.

pushMatrix(), popMatrix()함수가 있는데 스텍, 행렬의 의미를 이해하고 있어야 하지만 설명을 해도 쉽게 의미를 전달하기 어렵습니다. 다음으로 약식 꼼수 의미로 이해해 주셨으면 합니다. 저 함수가 나오면 push랑 pop은 한쌍이라고 생각하세요. 그리고 그리는 영역으로 push 시작위치에서 pop은 그리기 끝나는 위치라고 생각하세요. push~pop사이의 그리기 명령함수는 그 영역안에서만 독립적으로 적용된다고 생각하시면 될 듯요.


위 그림으로 다시 설명하면은 draw()에서 한장에 종이에 다가 그림을 그린다고 생각하시면 push~pop함수가 있으면 그 부분은 새로운 종이로 독립적인 영역의 그림을 그린다고 생각하시면 됩니다. 여기서, draw라는 메인 종이 위헤 새로 그린 종이를 붙이는 느낌으로 이해하시면 될 듯요. push~pop 안에 또다른 push~pop이 나오면 밖에 push~pop은 부모라고 생각하시고 부모 종이 위에 자식 종이가 두장 있다고 생각하시면 됩니다. 이때 자식 종이들은 각각 독립된 영역으로 A종이에 그리는 명령은 B종이에 영향을 줄 수 없고, B종이에 그리는 명령은 A종이에 영향을 줄 수 없는 독립된 영역으로 보시면 됩니다. 하지만 부모 종이에서는 자식 A, B가 둘 다 포함되어 있기 때문에 부모 종이에서 그리는 명령은 A, B 종이에 영향을 주게 됩니다.

왜! 이렇게 번거롭게 push~pop를 사용하느냐면 한두개의 도형을 그릴 때는 상관 없지만 다수의 도형을 그리고 배치하고 개별적인 도형들이 움직이거나 변화가 일어나게 코딩할려면 push~pop이 없이 그냥 한다면 어렵습니다. 그리는 명령을 매번 일일히 변화를 줘야 하기 때문이지요. 한장의 종이에 자동차 그림을 그린다면 한번에 전체를 다 그려야 합니다. 엔진, 바퀴, 핸들 등등의 각각의 부품들을 한번에 한장에 다 그려야 하기 때문에 그리고 나면 다시 엔진이 맘에 안든다고 엔진을 바꿀려고 하면 수정하기도 힘들고 하나를 변경하면 그 영향이 전체에 가기 때문에 무척 힘든 작업이 됩니다.

하지만 push~pop은 각각의 엔진이면 엔진 바퀴면 바퀴를 독립적으로 그리고 한장에 종이에 독립적으로 그림 그림을 가져와서 해당 위치에 붙이기만 하면 조립을 쉽게 완성할 수 있습니다. 나중에 엔진을 바꾸고 싶다면 엔진만 다시 그려서 해당 위치에 붙이면 쉽게 변경할 수 있게 됩니다.

한마디로 말하면 각 그림을 캡슐화 한다고 생각하시면 됩니다.

다시 설명드리면,


위 그림을 그리기 위해서 push~pop이 없다면 한개의 좌표계에서 몸체와 2개의 바퀴를 3D공간 좌표계에서 위치 좌표를 일일히 이동시켜서 그 위치에서 그림을 그려야 합니다. 한개라도 변경되는 일이 발생하면 전체에 영향을 주게 됩니다. 그리고 바퀴만 회전시키고 싶을때 제어하기가 무자 까다롭습니다.

하지만 push~pop을 사용하면 독립된 3D좌표계에서 독립적으로 그리게 됩니다.


위 그림처럼 계별적 3D 좌표계에서 도형을 그린 다음에 메인 draw() 이 그림을 push~pop으로 배치만 하면 됩니다. 나중에 원의 색을 바꾸고 싶으면 push~pop안에 fill()값만 바꾸면 색을 쉽게 변경이 됩니다. 바퀴가 두개인데 복제도 가능해지죠. 바퀴를 회전 시키고 싶으면 push~pop에 rotate()함수만 삽입하면 됩니다. draw()함수 내에서 하나의 좌표에게서 push~pop의 개별좌표계로 독립된 표현이 된다고 생각하시면 되겠습니다.

3D 포스트가 아닌데 삼천포로 빠지고 말았네요. 나중에 아두이노와 연동해서 뭔가 화려한 3D 관련 표현을 하실 수 있으니깐 가장 중요한 push~pop을 이야기 하다보니 좀 설명이 길어 졌네요.

    pushMatrix();  //Start
    fill(0,255,0); //채우기
    translate(width/2,height/2,-100); //이동
    box(200,100,200); //상자
    popMatrix(); //End

fill(0,255,0)함수로 다음에 그리게 될 도형의 표면 색을 녹색으로 정했습니다. translate(x,y,z)로 그리게 될 도형이 윈도우창에서 어디쯤 위치할 것인지 이동 시킵니다. 즉, 도형의 색은 녹색이고 특정위치로 이동시킨 곳에 상자가 그려진다고 생각하시면 될 듯 싶네요.

[결과]


위 그림에서는 상자가 녹색인 것은 알 수 있지만 3D 상자인지는 알 수 없네요. 이제 회전을 시켜 볼까요.

2. Mouse 회전


마우스 회전은 간단합니다. 상자가 그려진 윈도우창에 마우스의 좌표를 가져 올 수 있다면 쉽게 해결 됩니다. processing에서는 마우스 좌표를 변수로 실시간 좌표를 가져올 수 있습니다. mouseX, mouseY가 그 변수명입니다. 이 변수를 사용하면 현재 마우스가 가리키는 좌표값을 얻을 수 있습니다. 회전을 시켜볼까요.

회전 rotate()함수는 그냥 회전인데 마우스 좌표로 X, Y 축으로 회전를 해봅시다. processing 에서는 각 축을 기준으로 회전 시키는 함수를 제공합니다.

해당 함수를 살펴보면, ratateX()은 X축 기준으로 회전함수이고, ratateY()은 Y축 기준으로 회전하는 함수입니다. mouseX, mouseY 변수는 size()함수로 만든 윈도우 창의 영역 안에 마우스의 좌표(x,y)값을 실시간으로 가져 올 수 있어 이 값을 회전각으로 하면 쉽게 회전 할 수 있습니다.

처음, 회전각은 mouseX로 했더니 너무 고속으로 회전해서 약간 회전을 느리게 하기 위해서 0.1을 곱해도 너무 빠르고 0.01정도 하니깐 제 컴퓨터에서 이상적으로 회전 하네요.

회전각(mouseX * 0.01)로 회전시켜 보았습니다.

void setup() {  
    
    size(600,600,P3D); //창사이즈
    noStroke();  //테두리없음
//  frameRate(30); //초당 프레임
} 

void draw() {  
    background(0); //배경색
    lights();  //조명

    pushMatrix();  //Start
    fill(0,255,0); //채우기
    translate(width/2,height/2,-100); //이동
    rotateX(mouseY*0.01); //x축 회전
    rotateY(mouseX*0.01); //y축 회전
    box(200,100,200); //상자
    popMatrix(); //End
}

[결과]


나중에 조이스틱 같은걸로 조정한 값을 이 부분을 대신하면 회전이 되겠죠.

2. 키보드 회전



이제는 키보드로 회전 시키는 방식을 살펴보도록 하겠습니다.

위 링크 소스에서 키보드 관련 부분 살펴보면,

void keyPressed() {
    if (key == CODED) {
            if (keyCode == UP) {
                 회전각;
            }   
    }
    else if (key == 'z'){
       명령문;
    }   
}

대충 구조는 이렇습니다. keyPressed()함수는 키보드의 키를 누르면 호출 되는 함수입니다. key는 눌렀을때의 키 값을 가지고 있으면 그 키 값이 CODED인지 채크하게 됩니다. CODED면 keyCode 변수의 키 값이 들어 있고 그 키가 UP, DOWN, LEFT, RIGHT 인지를 체크하게 됩니다. 참고로 화살표 키 값이라고 생각하시면 될 듯요.
일반 키값은 key에 들어 있으면 'a'~'z', 'A'~'Z' 등 등으로 키값을 가질 수 있고 key에 저장 되어 있어서 key로 비교하면 됩니다.

키보드로 회전한다면 방향키로 X, Y축 회전을 시키고 키값 Z, X키로 Z축으로 이동 제어 키로 쭘인/줌아웃을 시킬 예정입니다.

int x=0;
int y=0;
int z=0;

void setup() {  
    
    size(600,600,P3D); //창사이즈
    noStroke();  //테두리없음
        
//  frameRate(30); //초당 프레임
} 

void draw() {  
    background(0); //배경색
    lights();  //조명

    pushMatrix();  //Start
    fill(0,255,0); //채우기
    translate(width/2,height/2,-100+z); //이동
    rotateX(radians(y)); //x축 회전
    rotateY(radians(x)); //y축 회전
    
    box(200,100,200); //상자
    popMatrix(); //End
}

void keyPressed() {
  if (key == CODED) {
    if (keyCode == UP) {
      y+=1;
    } 
    else if (keyCode == DOWN) {
      y-=1;
    } 
    else if (keyCode == LEFT) {
      x-=1;
    } 
    else if (keyCode == RIGHT) {
      x+=1;
    }
  }
  else if (key == 'z' || key == 'Z') {
      z+=10;
  } 
  else if (key == 'x' || key == 'X') {
      z-=10;
  } 
}

위에서 if문이 조건식이 두개인데 '||'은 둘중 하나만 참이면 참으로 인정하는 기호입니다. 둘개 다 참이여야 참으로 인정하는 기호는 '&&'인데 참고로 알아 두세요. '||'로 'z' or 'Z' 중 Z키가 대소문자 중 아무거나 입력해도 Z로 인정한다는 의미입니다. 나머지는 구지 성명 드릴 필요는 없겠죠.

    rotateX(radians(y)); //x축 회전
    rotateY(radians(x)); //y축 회전

여기서 radians(y)은 라디안 단위로 각도로 변환시키는 함수입니다. 그냥 각도 구하기라고 생각하시면 될 듯 싶네요.

[결과]



화살표 방향키하고 춤인/줌아웃 키인 Z, X 키가 잘 작동하네요.

마무리


processing에 대해서 한번 관심을 가져보시라고 간단히 3D Rendering를 해보았습니다. 참고로 이미 만들어진 box()함수의 도형이지만 좌표계 기준으로 각 좌표로 모형을 그리고 싶다면 공식홈페이지에 도형 그리는 함수들을 찾아보시고 따라서 그려보세요, 충분히 그릴 수 있을 거라 생각됩니다.

참고로 processing 함수들이 다양한 기능들을 제공해주기 기 때문에 위 포스트한 코딩에서 추가 할 코딩 부분이 있지만 다 생략하고 기본만 되는 부분만 코딩한거라 좀 단순한 표현이 되었네요. 그래도 핵심적인 표현은 다 했습니다. 이제는 여러분들이 도형을 그리고 그 도형에 대해서 움직임을 push~pop를 이용해서 제어하시면 됩니다.

갑자기 processing를 포스팅을 한 이유는 최근에 스위치 버턴과 조이스틱으로 조정하는 것을 배웠습니다. 한번 여러분들이 이 코딩을 기반으로 아두이노와 접목하면 좋을 것 같아서 소개하게 되었습니다.

한번 스위치 버턴 or 조이스틱으로 오늘 배운 도형을 컨트롤 해보고 싶지 않으신지요. 제가 답을 바로 드리는 것보다 여러분들이 한번 지난 시간에 processing에서 아두이노와 어떻게 연동을 했는지 상단에 참고 링크 포스트를 읽어보시고 연구를 해보셨으면 합니다.

댓글()

[아두이노] 아두이노의 업로드 에러

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

[아두이노] 아두이노의 업로드 에러 



아두이노를 실제로 다루다 보면 몇가지 문제가 생길 때 알아두면 좋은 팁들을 간단히 정리 했습니다.

1. 아두이노 업로드 환경 설정 에러


처음 아두이노를 업로드 할 때 발생하는 문제는 보드와 포트 설정입니다.


위 그림에서 보면 보드는 어떤 보드이고 포트는 정상적으로 잡혔는지를 확인 하셔야 합니다. 아두이노에 연결이 안되어 있는데 무조건 업로드 시키면 정상적으로 작동을 안하겠지요. 그리고 보드 부분도 기본은 우노이지만 여러분들이 사용하는 보드가 어떤 보드인지 처음 아두이노 IDE를 할 때 환경 설정을 해 놓으셔야 합니다.

2. 아두이노 업로드 포트 문제


평소 정상적으로 사용하던 아두이노가 갑자기 업로드가 안되는 에러가 발생하는 경우가 종종 있습니다. 대부분 이런 경우는 포트가 아두이노 IED에서 안잡히는 경우입니다. 내컴퓨터->장치관리자로 가시면 아래 그림창이 뜹니다.


포트는 장치관리자에 연결하는 순간 COM숫자로 표시가 됩니다. 위 그림은 잡힌 상태 그림이지만 안 잡혔을 경우는 제가 쓰는 운영체제에서는 기타 장치에서 느낌표로 포트가 잡혀 있더군요. 기타 장치나 포트쪽에 느낌표 COM숫자로 된 아두이노 포트를 찾으시면 됩니다.


그리고 해당 포트의 속성창을 누르면 드라이버 쪽에 드라이버 업데이트가 있는게 눌러 줍니다.


컴퓨터에서 드라이버 소프트웨어를 찾아보기를 눌러 줍니다.


그리고 아두이노 IED가 위치한 경로로 가셔서 drivers 폴더를 클릭한 뒤에서 확인을 누르시면 됩니다. 업데이트가 끝나면 장치관리자에 위 첫 그림처럼 Arduino Uno(COM숫자) 이렇게 잡히게 됩니다. 그리고 나서 아두이노 환경 설정에서 해당 포트를 다시 클릭해주면 정상적으로 업로드가 됩니다.

3. 아두이노 업로드 보드 자체 문제


연결된 상태에서 실험을 하면서 자주 업로드 할 때 업로드가 되지 않는 문제가 생기는 경우가 종종 있습니다. 보드 자체 문제라 이건 보드가 망가진게 아니라 잠시 몇분 정도 연결 된 포트 선을 제거 한 뒤 그냥 두세요. 보드에 있는 잔재 전류가 소멸할 때까지 방치해야 하는 것 같아요. 암튼 일정 시간 그냥 뒀다가 다시 연결하면 아무 문제 없이 작동합니다.

4. 아두이노 코딩 문제


어느정도 재미를 붙이시면 여러 블로그의 게시물들을 찾아 실험 소스들을 보시게 될 꺼에요. 초보분들의 경우 코딩을 그대로 했는데 "왜! 에러가 발생하지" 하는 경우도 있습니다. 대개 라이브러리 문제입니다. 해당 블로그의 라이브러리가 아두이노 IDE에 설치되어 있지 않으면 정상적으로 동작하지 않습니다.


위 그림처럼 라이브러리 포함하기를 하셔야 합니다. 기본 라이브러리는 라이브러리 관리로 가셔서 추가하시면 됩니다.


위 검색 필터하기에 해당 라이브러리 이름을 치시면 검색이 되고 설치하시면 되고요.


간단히 MPU6050 자이로센서를 설치해 봤습니다.

블로그 같은곳에서 라이브러리가 링크된 github나 아예 압축파일로 다운로드 받을 수 있게 해주는데 그런 압축파일로 된 라이브러리는 zip 라이브러리 추가로 설치하시면 됩니다.

MPU6050의 다른 라이브러리 파일을 이용할 경우는 직접 해당 파일을 다운 받아야 합니다.


zip 라이브러리 추가를 누르면 위 그림처럼 창이 뜨고 다운 받은 MPU6050를 열기를 누르시면 됩니다. 정상적으로 아두이노 IDE에 라이브러리가 추가 되어 있는지 확인하기 위해서 아두이노 IDE의 예제를 누르면 해당 MPU6050의 예제들이 추가되어 있는 것을 확인 하실 수 있을 꺼에요.


위 그림에서 보시는 것처럼 라이브러리 관리에서 MPU6050를 추가한 것과 github에서 직접 다운받은 MPU6050이 추가 되어 있는 것을 확인 하실 수 있죠.

대부분 외부 코딩을 참고하실 때 초보분들이 똑같이 코딩했는데 "왜! 실행이 안되지!" 하면 라이브러리 문제일 가능성이 큽니다. 이 부분은 이제 실수는 없겠죠.

마무리


위 설명한 몇가지는 사소한 부분인데 초보분들이 왜 업로드 안되지 하면서 어려움을 겪는 부분입니다. 잘 작동되는데 안되면 포트를 체크하고 그래도 작동이 안된다면 전원 케이블 제거 한뒤에 그냥 아두이노를 식힌다는 기분으로 놔뒀다가 다시 연결하면 정상적으로 작동합니다. 저도 처음에 아두이노를 접할 때 왜 갑자기 업로드 안되지 하면서 코딩에 문제가 있나 하면서 코딩 분석하면서 삽질 했던 기억이 나네요. 그리고, 포트도 이상없는 데 왜 작동을 안하지 보드가 망가졌나 하고 삽질 했던 추억이 있네요. 잠깐 전원 연결부분을 제거 한뒤에 한참 그 상태로 뒀다가 다시 연결해서 실험하니 정상적으로 동작하더군요.

의외로 이런 문제들이 종종 일어나니깐 참고해 주세요.

실험 포스트를 하려다가 오늘은 휴식하는 마음으로 간단한 주제로 포스팅 했네요.

댓글()

[아두이노] 조도센서 + Bluetooth 통신 제어

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

[아두이노] 조도센서 + Bluetooth 통신 제어



오늘은 지난시간에 배운 Bluetooth를 이용하여 Servo Motor를 제어 해볼려고 했지만 스마트폰의 데이터를 아두이노로 읽었으면 그 반대로 보내는 실험을 하는 것이 더 좋을 것 같아서 반대로 보내는 실험을 해볼까 합니다. 조도센서에서히 조도 값을 아두이노에서 측정하여 Bluetooth을 이용하여 스마트폰으로 보내는 실험을 하겠습니다.


1. 조도센서



링크 된 곳은 예전에 포스트 한 내용입니다. 회로도를 공개 모드로 변경해서 올려 놨습니다. 포스트에서 두가지 타입이 있는데 아래 타입으로 조도 센서를 배치 했습니다.


2. Bluetooth 연결


링크 : [아두이노] Bluetooth 통신 제어



  • Bluetooth Rx -> Arduino txPin - pin3
  • Bluetooth TX -> Arduino rxPin - pin2

지난 시간에 설명했기 때문에 설명은 생략하겠습니다. 혹시, 기억이 안나면 위에 걸어 놓은 링크 주소로 가셔서 다시 살펴보시기 바랍니다.

3. 회로도 구성


  • 준비물 : - Bluetoooth HC-06 1개, 조도센서 1개, 저항 10k옴, 아두이노우노
  • 내용 : A0핀은 조도센서 입력핀으로 사용하고 2,3번 Bluetooth 통신핀으로 사용한다.


여기서, 제어를 담당하는 세개의 핀이 있는데 조도센서 입력 받을 핀과 Bluetooth rx, tx 핀만 정확히 연결하시면 됩니다.

4. 코딩


  • 내용 : 조도센서를 아두이노에서 측정한 데이터를 Bluetooth로 스마트폰에 보내고 그 결과를 출력하도록 하자.

SoftwareSerial 통신

#include <SoftwareSerial.h>
  • SoftwareSerial mySerial (rx, tx) : 소프트시리얼 객체선언(rx(수신), tx(전송))
  • mySerial.begin(9600) : 시리얼 통신 시작(예로 9600 통식속도를 사용해 봤네요.)
  • mySerial.println(값) : 데이터 전송

따로 코딩을 새로 만들지 않았습니다. 기존에 조도센서를 시리얼모니터로 출력 했던 소스를 그대로 bluetooth통신에 적용하겠습니다.

[조도센서+시리얼출력] : 조도센서 제어(아두이노)

int greenpin = 13;
int cdspin = A0;

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

void loop()
{
  int m_cds = map(analogRead(cdspin),0,1023,0,255);
  
  Serial.print("CDS =  ");
  Serial.println(m_cds);
  
  if(m_cds<100) { 
     digitalWrite(greenpin, HIGH);  
  }
  else {
      digitalWrite(greenpin, LOW);  
  }
  
}

위 소스에서 필요한 부분만 뽑아내면은

int cdspin = A0;
void setup()
{
    Serial.begin(9600);
}

void loop()
{
    int m_cds = map(analogRead(cdspin),0,1023,0,255);
 
    Serial.print("CDS =  ");
    Serial.println(m_cds);
}

이렇게 이 소스만 필요합니다. 여기서 Serial 개체변수명만 여러분이 선언한 블루투스객체명으로만 바꾸면 끝납니다.

수정을 하면.

#include <SoftwareSerial.h>

const int rxPin = 2;
const int txPin = 3;

SoftwareSerial mySerial(rxPin, txPin);

int cdspin = A0;

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

void loop()
{
  int m_cds = map(analogRead(cdspin),0,1023,0,255);
  
  mySerial.print("CDS =  ");
  mySerial.println(m_cds);  
  delay(1000);
}

이렇게 수정이 완료 되었네요. Serial 이름을 블루투스 통신에 사용할 객체명 mySerial로 "my"라는 단어가 앞에 추가되고 변경된 부분은 없네요.

간단하게 코딩을 완료 했습니다. 어렵게 생각하시지 마시고 평소 시리얼 모니터로 출력하는 식으로 코딩을 표현하시면 됩니다.

5. Bluetooth 통신을 하기 위한 세팅


지난 시간에는 "Controller mode"에서 실험 했다면 오늘은 "Terminal mode"에서 실험하시면 됩니다. 실행하시면 자동으로 아두이노에서 조도값을 측정하면 그 값을 바로 스마트폰으로 출력이 되어 나옵니다. "Terminal mode"에서는 스마트폰에서 데이터를 아두이노로 보낼 수 있고 아두이노에서 읽어온 데이터를 바로 출력해주는 모드입니다. 채팅모드라고 생각하시면 됩니다.

6. 결과


아래 동영상은 실행 과정을 PC에서 녹화한 장면입니다.


마무리


원래는 앱인벤터로 직접 만들어서 자신이 원하는 스타일로 앱을 만드는게 가장 좋습니다. 이미 만들어진 앱들이 구글스토어에 많지만 여러분들이 원하는 기능으로 딱 맞게 세팅 되어 있지 않습니다. 오늘 실험은 단순히 출력 데이터를 수치 상으로 보여 주기만 하면 되기 때문에 구글스토어에 등록된 앱을 사용했지만 그래프와 같은 시각적 표현을 하시고 싶다면 직접 블루투스 앱을 만드셔야 합니다.

혹시 스마트폰과 특정 센서를 연동한 뭔가를 제작 싶다면 앱인벤터를 구글에서 검색하시면 강좌가 많기 때문에 한번 제대로 공부해 보셨으면 해요.

블루투스는 사용범위가 넓기 때문에 상상를 어떻게 하냐에 따라 재밌는 작품들을 많이 만드실 수 있을거에요. 실제로 어떤 분은 자외선 센서를 이용했는데 소형으로 만들어서 스마트폰과 연동하여 실시간으로 자외선 관련 데이터 모니터링하면서 서버와 연동해서 자외선 정보를 알려주는 어플을 만드신 분도 있으시더군요. 이처럼 스마트폰과 연동하면 재밌는 것들을 만들 수 잇습니다.

여러분들도 상상력을 발휘해 보세요.


댓글()

[아두이노] Bluetooth 통신 제어

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

[아두이노] Bluetooth 통신 제어



오늘은 Bluetooth 통신을 주제로 이야기를 할가 합니다. 원래는 나중에 할 예정이였는데 Servo Motor를 조이스틱으로 제어하고 스위치 버턴으로 제어하는 포스프를 쓰다 보니깐 나중에 Bluetooth를 포스트하고 응용 예제로 LED나 Servo Motor를 복습차원을 포스팅을 할 것 같아서 이참에 한번에 해버리는게 좋을 것 같아서 오늘의 포스트 주제로 선정했네요. 이제 본격적으로 Bluetooth 통신으로 들어 가겠습니다.


1. Bluetooth HC-06 모듈



  • Rx - 데이터 읽기
  • Tx - 데이터 보내기

두 개의 Rx, Tx만 무슨 핀인지만 아시면 됩니다. 두 핀만 잘 아두이노에 연결하면 무선 통신을 할 수 있게 됩니다.

하지만 이 Bluetooth HC-06 모듈을 아두이노에 연결할 때 잘 생각하고 연결하셔야 합니다. 물론 아무 핀에다 연결해도 상관은 없습니다만 아두이노에 연결 했을 때 해당 핀이 어떤 핀인지에 대한 정의를 머리속에서 내리셔야 합니다.

다음 회로도에서 설명하겠지만 Bluetooth Rx핀은 아두이노에서 사용 할 Tx핀에 연결해야 하고 Bluetooth Tx핀은 아두이노에서 사용 할 Rx핀에 연결해야 합니다. 이 부분만 주의하시면 됩니다. 즉, 아두이노가 보내는 데이터 핀은 아두이노 자체니깐 Tx핀이겠죠. 그게 Bluetooth가 읽으니간 Rx에 연결하는 것이고 Bluetooth가 보내는 데이터는 Bluetooth의 Tx핀에서 나와서 아두이노가 그 데이터를 읽기 위해서는 아두이노의 Rx핀에서 읽어들이겠죠. 이렇게 생각하시면 될 듯 싶네요.

2. 회로도 구성


  • 준비물 : - Bluetoooth HC-06 1개, 아두이노우노
  • 내용 : 0,1핀을 제외한 아무핀에다 연결하시오.

0,1 은 아두이노 자체 시리얼통신 핀입니다. 이곳에 Bluetooth를 연결해도 되긴 하는데 처음에 따로 연결해 주세요.


Bluetooth Tx 핀은 아두이노의 Rx 2번핀에 연결하고 Bluetooth Rx 은 아두이노의 Tx 3번핀에 연결합니다. 왜 이렇게 복잡하게 말하냐면 나중에 SoftwareSerial 라이브러리를 통해서 통신을 할 때 통신 함수의 인자 변수명을 일치시키기 위해서 입니다.

SoftwareSerial::SoftwareSerial(uint8_t rxPin, uint8_t txPin, bool inverse_logic)
  • SoftwareSerial(rxPin, txPin) : 시리얼통신 핀을 세팅합니다.

여기서 인자값은 bluetooth의 Tx=rxPin, Rx=txPin으로 넘겨지기 때문입니다.

const int rxPin = 2;
const int txPin = 3;

SoftwareSerial mySerial(rxPin, txPin);
  • Bluetooth Rx -> Arduino txPin
  • Bluetooth TX -> Arduino rxPin

대충 변수명은 SoftwareSerial 생성자 함수의 인자 명하고 일치 시켜 유사한 이름을 써주세요.

간혹 Bluetooth 이름으로 짓는 경우가 많은데 그러면 혼동이 생길 수 있습니다.

int BT_TX = 2;
int BT_RX = 3;

이럴때

SoftwareSerial mySerial(BT_TX, BT_RX);

이렇게 이름을 짓는 경우가 많습니다. Bluetooth 관점으로 핀이름을 만들다 보니 이렇게 되죠. 엄밀히 말하면 생성자 함수의 인자 네임음 (rx, tx)입니다. 그런데 들어가는 값이 블루투스의 (tx, rx)다고 해서 이렇게 이름 만들면 혼동할 수 있습니다. 될 수 있으면 함수 인자의 네임을 일치시켜 주세요.

3. 코딩


  • 내용 : 스마트폰 연결 Bluetooth를 연결하여 간단히 스마트폰에서 입력한 값을 아두이노에서 읽고 아두이노 IDE 시리얼모니터로 읽은 값을 출력을 해보자.

함수

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

SoftwareSerial 통신

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

SoftwareSerial 라이브러리에서 제공되는 기본 예제로 테스트




해당 소스에서 2,3핀을 시리얼통신 핀으로 사용 했습니다. 그리고 전송속도는 9600으로 둘 다 해버렸네요.

  • 전송 속도 : 300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 31250, 38400, 57600, 115200
    다른 속도로 하셔도 됩니다.

[기본 소스] : 이 코딩은 위 아두이노공식홈페이지에 있는 기본 예제를 기반으로 약간만 수정한 소스입니다. 제가 만든게 아니라 위 아두이노 공식홈페이지에서 제공되는 오픈 예제 소스입니다.

#include <SoftwareSerial.h>

const int rxPin = 2;
const int txPin = 3;

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

void setup()  
{
  Serial.begin(9600);
  while (!Serial) {
    ; 
  }
  Serial.println("Bluetooth Start!");
  mySerial.begin(9600);  
}

void loop() 
{
  if (mySerial.available()) Serial.write(mySerial.read()); 
  if (Serial.available()) mySerial.write(Serial.read()); 
}

딱히, 어려운 건 없습니다. 링크 예제를 안하더라도 대부분 Bluetooth 샘풀 테스트를 하면 위 소스와 비슷한 방식으로 다들 실험을 합니다.

(1) SoftwareSerial 헤더파일 연결
(2) SoftwareSerial 객체변수 선언
(3) 객체변수.begin(전송속도)
(4) if(객체변수.available()) { byte ch = 객체변수.read(); }
(5) Serial.write(ch);

누가 실험을 하든 이게 젤 처음 다들 하는 Bluetooth 실험 소스입니다. 그냥 자연스럽게 이렇게 코딩하게 됩니다.

여기서 loop()문은 통신을 수행하는 문장입니다.

예전에 시리얼 통신을 할 때 시리얼모니터에서 데이터를 입력하면
if (Serial.available()) {
byte ch = Serial.read();
}

이렇게 읽었습니다. 시리얼 모티터로 출력 한다면
Serial.write(출력값); 
이렇게 사용했습니다. 블루투스 통신에서도 이와 같습니다. 

if (mySerial.available()){
    Serial.write(mySerial.read()); 
}

블루투스를 통해 들어온 데이터를 시리얼모니터로 출력하는 문장입니다. 이것과 반대로 시리얼모니터로 입력한 데이터를 블루투스로 반대로 보내는 동작은 다음과 같습니다.

  if (Serial.available()) {
        mySerial.write(Serial.read()); 
    }

이전에 시리얼 통신을 해왔던 방식 그대로 표현 하시면 됩니다. 단지 블루투스를 SoftwareSerial 라이브러리를 이용하기 때문에 따로 SoftwareSerial 의 객체 변수를 선언해주고 그 객체명으로 시리얼통신함수를 사용하시면 됩니다.

간단히 정리하자면 평소 Serial 통신에서 시리얼모니터에 아두이노의 결과물을 출력했던 방식으로 동일하게 Bluetooth도 동일한 방식으로 푠현한다고 생각하시면 됩니다.

4. Bluetooth 통신을 하기 위한 세팅


1) 스마트폰에서 Bluetooth 등록


위 그림 처럼 HC-06의 이름을 가진 Blutooth가 잡힙니다. 등록하기 위해서는 비번을 쳐야 하는데 초기 비번은 "0000", "1234" 둘 중 하나로 초기 비번으로 설정 되어 있습니다. 둘 중 하나니깐 두 개 다 입력해서 잡히면 그걸로 등록하시면 됩니다. 참고로 AT 명령어로 비번을 변경할 수 있습니다.

2) 스마트 폰 Bluetooth 통신 앱 설치

전 느낌 오는 걸로 구글스토어에서 블루투스 컨트롤을 쳐서 아래 그림의 어플을 설치했네요. 결과만 보기 위해서 대충 선택했네요. 다른 것들도 많으니간 편한 걸로 설치 하시면 됩니다.


혹시, 직접 만드시고자 하시는 분들은 웹인벤터에서 앱을 만들 수 있습니다. 스크래치 방식으로 블록으로 배치하여 직접 만들 수 있는데 구글 검색 키워드로 "bluetooth 앱인벤터"로 치시면 동영상하고 블로그 게시물이 엄청 많습니다. 유튜브에서 검색하셔도 됩니다. 동영상을 보고 따라 만드시면 나중에 자신이 원하는 스타일로 개조 할 수 있습니다.

3)실행



Bluetooth를 잡아놓은 상태어서 HC-06으로 제가 현재 쓰는 Bluetooth 모듈 명이 검색 되어 있네요. 초기 이름울 AT명령으로 Bluetooth 이름을 변경 할 수 있습니다. 그냥 초기 이름을 그대로 사용합니다. 클릭을 하면 옆 그림처럼 선택 목록이 있는데 젤 위의 Controller mode를 누르면 다음과 같이 조종기가 나옵니다.



오른쪽 상단에 톱니모양을 누르면 환경 설정을 할 수 있습니다.


간단히 방향버턴을 1,2,3,4 로 세팅했네요.

이제 아두이노 IDE를 열고 결과를 테스트 하면 됩니다.

5. 결과



스마트폰에서 방향 스위치를 누르면 아두이노에 연결된 Bluetooth가 방향 스위치 값을 읽고 그 값을 아두이노에서 PC에 연결된 아두이노 IDE 시리얼모니터로 결과가 출력된다.

아래 실험 영상 녹화는 데스크탑 PC에서 녹화한 영상입니다. PC에서 스마트폰을 원격접속하여 스마트폰을 PC에서 볼 수 있게 하였고, 아두이노는 라즈베리파이에 연결되어 작동하고 라즈베리파이에 설치된 아두이노 IDE 의 시리얼모니터로 Bluetooth에서 들어온 데이터를 출력하도록 해놓았습니다. 참고로 라즈베리파이도 데스크탑 PC에서 원격 접속을 하였습니다. 결론은 녹화를 하기 위해서 PC가 스마트폰과 라즈베리파이를 동시에 원격접속하여 PC 모니터에 띄워서 둘을 동시에 조정하여 그 결과를 녹화한 영상물입니다.


위 그림처럼 스마트폰과 라즈베리파이를 원격 접속하여 창을 띄운 화면입니다. 참 번거롭게 녹화을 했네요. 스마트폰으로 찍자니 그러면 Bluetooth 어플 조정을 못하니깐 어쩔 수 없이 원격 접속하여 PC로 녹화를 하게 되었습니다.


아 'Start' 단어에 오타가 다시 녹화 하기 귀찮아서 그냥 올립니다. 참고로 영상에서 처음 실행이 되면 "AT"라는 단어를 쳐보세요 그리고 보내면 "OK"라는 단어로 리턴된 값이 시리얼모니터로 출력됩니다. 이 말은 Bluetooth가 정상적으로 동작한다는 의미가 되겠습니다.

6. AT 명령어



구글 검색하시면 레퍼런스가 많습니다. 제가 봤던 사이트 레퍼런스인데 명령어들을 잘 살펴보시면 됩니다.

Bluetooth 설정된 값을 변경할 수 있는 명령어입니다. 현재 Bluetooth 버전, 이름, 비번, 전속속도, Master와 Slave 설정 등을 할 수 있습니다. 참고로 버전이 낮을 경우 Master와 Slave은 고정되어 있는데 구매하실 때 자신이 쓰는 모델이 고정인지 겸용인지를 꼭 확인하세요.

  • AT : "OK" 메세지가 나오면 Bluetooth 정상
  • AT+VERSION : 현재 Bluetooth 사양 정보
  • AT+NAMEaaa 이름(aaa)
  • AT+PIN1234 비번(1234)
  • AT+BAUE1 전송속도(1200) 1~9,A,B,C(레퍼런스참조)

이름과 비번만 바꿔주시고 사용하시면 돼요. 추가적으로 레퍼런스를 읽어보시기 바랍니다.

마무리


if (mySerial.available()) Serial.write(mySerial.read()); 

이 한줄의 명령을 수행하기 위해서 엄청 글을 썼네요. 이 한줄이 오늘 동작하는 명령의 전부입니다. 이걸 하기 위해서 세팅하는 과정이 꽤 길었네요.

지금까지 Serial 통신으로 시리얼모니터로 결과를 출력했던 방식으로 Serial 대신에 여러분들이 작명한 이름으로 변경만 하면 통신을 할 수 있게 됩니다.

마지막으로 방향 버턴을 1,2,3,4 로 값을 세팅했잖아요. 그 값을 기준으로 Servo 머턴을 제어를 코딩해 보세요. 어제 스위치 버턴으로 Servo Motor를 제어 했는데 그 소스랑 오늘 통신 소스를 합쳐서 코딩해보시면 아마도 쉽게 코딩을 할 수 있을 거라 생각됩니다.

이걸 또 내일 포스팅하기 그러니깐요 이 부분은 여러분들이 Servo Motor를 제어하는 하는 걸로 마무리 할까 합니다.

나중에 다른 주제에 오늘 사용한 Bluetooth를 활용하는 것을 포스트 하겠습니다.


댓글()