본문 바로가기

프로젝트/블루투스 무드등

[비전공자도 만들 수 있는 블루투스 무드등] 3. 아두이노 코딩 part 2-블루투스 코딩

안녕하세요? 닉네임간편입니다.
저번 시간에 코딩을 위한 준비를 마쳤으니, 이번 시간에는 본격적으로 코딩을 통해 블루투스 기기와 연동되어서 동작하도록 만들겠습니다.

 

1. 블루투스 세팅

블루투스 기능을 사용하기에 앞서 블루투스의 기본 세팅을 하겠습니다.

 

<블루투스 세팅 전체 코드>

#include <SoftwareSerial.h>
SoftwareSerial blueTooth(2, 3);

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

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

 

자세한 설명은 본격적인 코딩에서 다룰 것이므로 현재는 해당 코드를 이용해 블루투스 설정을 해보겠습니다.

 

 

1. 연결 여부

우측 상단에 있는 돋보기 버튼을 클릭하면 시리얼 모니터가 나옵니다.

시리얼 모니터란 쉽게 말해 컴퓨터와 아두이노가 서로 대화를 하는 창이며, 이를 통해 컴퓨터에서 아두이노로 명령을 할 수 있고, 아두이노에서 변숫값 등을 입력받아 모니터에 출력할 수도 있습니다.

이때 하단 부분을 line ending 없음, 9600 보드레이트로 설정해주어야 합니다.

AT를 입력하고 전송을 누르면 OK라고 나옵니다. 통신이 잘 되고 있다는 것입니다.

 

잘 연결되었는지 확인

 

2. 블루투스 이름 설정

다음으로 블루투스의 이름을 변경할 수 있습니다.
시리얼 모니터에 AT+NAME + "바꾸고자 하는 이름"을 입력하면 해당 이름이 설정됩니다.
저는 AT+NAMEKMIGHT 라고 입력하였고, OKsetname 이라고 떴다면 이름이 잘 설정된 것입니다.
블루투스 이름이 KMIGHT 가 된 것을 확인할 수 있습니다.

 

3. 비밀번호 설정

AT+PIN0000 키워드를 통해 비밀번호를 설정할 수 있습니다.

저는 올해 연도인 2021을 비밀번호로 설정해보겠습니다.

'AT+PIN2021'을 시리얼 모니터에 입력하면 OKsetPIN 이라는 내용이 표시되며, 이렇게 표시될 경우 비밀번호가 잘 설정된 것입니다.

 

블루투스 이름 및 비밀번호 설정 후 등록

 

이렇게 이름이 KMIGHT로 설정된 것이 보이고, 비밀번호를 입력하면 스마트폰에 등록이 잘 되는 걸 확인할 수 있습니다.

이것으로 블루투스 기본 세팅을 마쳤습니다.

 

 

2. 본격적인 코딩

본격적으로 블루투스 연결을 위한 코드를 알아보겠습니다.

 

1. 라이브러리 포함

#include <SoftwareSerial.h>
#include <Adafruit_NeoPixel.h>

 

먼저 외부 라이브러리를 포함합니다. 여기선 네오픽셀 제어를 위한 Adafruit_NeoPixel.h 라이브러리와, 소프트웨어 시리얼 포트를 추가하기 위한 SoftwareSerial.h 라이브러리를 추가합니다.

 

이때 소프트웨어 시리얼 라이브러리를 포함하는 이유는, 추가적인 시리얼 포트를 확보하기 위함입니다.

아두이노는 하나의 하드웨어 시리얼 포트를 갖고 있으며, 시리얼 포트를 통해 시리얼 통신을 할 수 있습니다. 간단히 말해, 시리얼 포트를 사용해 데이터를 송수신할 수 있습니다.

 

이때 아두이노는 0번 핀과 1번 핀을 각각 데이터 수신, 데이터 송신에 사용하며, 아두이노와 컴퓨터를 연결할 때 이 0번 핀과 1번 핀을 사용하여 스케치 파일에 있는 코드를 업로드합니다. 즉, 아두이노의 하드웨어 시리얼 포트가 바로 이것입니다.

 

그런데 아두이노는 하나의 하드웨어 시리얼 포트를 갖고 있으므로, 추가로 블루투스 통신을 하거나 시리얼 모니터를 보기 위해선 추가적인 시리얼 포트가 필요합니다. 이때 소프트웨어 시리얼을 라이브러리로 포함함으로써 시리얼 포트를 추가할 수 있습니다. 이렇게 시리얼 포트를 추가해야 시리얼 모니터를 볼 수 있고 블루투스 통신을 할 수 있으므로 매우 중요하다고 볼 수 있습니다.

 

 

 

2. 변수 정의

#define PIN 6
#define NUMS 4
#define TX 2 
#define RX 3

 

6번 핀은 PIN으로 정의하고, 현재 사용할 LED 스트립의 픽셀 개수가 4개이므로 NUMS 를 입력하면 4가 입력되도록 정의하겠습니다.

앞서 데이터 송신은 2번 핀에서 수행하도록 하였으므로 2번 핀을 TX로 정의하고, 수신을 위한 3번 핀은 RX로 정의하겠습니다. 

 

이렇게 변수를 정의하는 이유는 다양한 이유가 있지만, 어떤 값을 반복적으로 사용할 경우 이름을 붙여서 사용하는 것이 편리하기 때문입니다. 또한 단지 사용하기에 편리한 것뿐만이 아니라 추후에 코드를 다시 볼 때 단순히 숫자만 적혀있다면 그 의미를 파악하기 어렵지만, 변수 이름이 정의되어있다면 어떤 의미로 사용되었는지 파악하기 수월합니다. 따라서 자주 사용되는 특정 값에 대해선 이름을 정의하는 것이 좋겠습니다.

 

SoftwareSerial blueTooth(TX, RX);
SoftwareSerial blueTooth(2, 3);

 

이 코드를 예시로 들자면, 숫자가 적혀있는 함수는 지금은 알아볼 수 있더라도, 차후에 코드를 다시 보아야 할 때 이 값이 어떤 값을 의미하는지 알기 어렵습니다. 그러나 TX, RX 이렇게 이름을 붙인다면 어떤 의미인지 파악하기 쉽고, 나중에 해당하는 값을 변경할 때에도 정의했던 곳에서 숫자만 바꿔주면 되므로 유지보수가 편리합니다.

 

 

3. 네오픽셀 함수

Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUMS, PIN, NEO_GRB + NEO_KHZ800);
SoftwareSerial blueTooth(TX, RX);
byte colors[5]; // 색상을 받아오는 배열

 

이전 시간에서 보았든 스트립 객체를 생성해줍니다.

그리고 앞에서 추가한 소프트웨어 시리얼 라이브러리를 통해 블루투스 객체를 생성해줍니다. 이때 파라미터로는 데이터를 송신하는 핀과 수신하는 핀을 순서대로 전달해줍니다.

 

마지막으로는 앱에서 색상과 그 이외의 정보들을 전달받을 배열을 생성해줍니다.

색상 정보는 빨간색, 파란색, 초록색 이렇게 세 가지와 삼원색의 정보가 주어지며, 이 정보와 함께 밝기 정보도 전달되어야 하므로 배열 길이는 4는 되어야 합니다. 그러나 전 한 가지 정보를 더 추가할 것이므로 일단 5로 설정하겠습니다. 그 정보에 대해선 차후에 설명하겠습니다.

또한 데이터는 byte 단위로 전달되기 때문에, 정보를 전달받을 배열도 byte 단위의 정보를 받도록 합니다.

 

 

 

4. setup 함수

void setup() {
  blueTooth.begin(9600); // 블루투스 통신 준비, 통신속도는 9600 보드레이트로 설정
  Serial.begin(9600); // 시리얼 모니터 준비
  strip.begin(); // LED 제어 준비
  delay(1000); // 1초 기다렸다가 켜지게 하기
  rainbow(20); // rainbow 효과가 나오면서 켜지도록 하기
  strip.show(); // LED 전원이 꺼진 상태로 초기화하기
}

 

변수를 초기화하고 라이브러리 사용을 시작하는 setup 함수입니다.

 

1) 블루투스 통신을 사용할 준비를 합니다.

2) 시리얼 모니터를 사용할 준비를 합니다.

이때 앞서 시리얼 모니터에서도 하단의 값을 9600 보드레이트로 설정했던 것과 같이, 위의 두 객체의 보드레이트도 9600으로 설정합니다.

보드레이트는 통신 속도로, 일반적으로 9600 값을 사용합니다. 통신 속도는 송신하는 쪽과 수신하는 쪽이 같아야 하기 때문에 모두 같은 값으로 통일해줍니다.

 

3) 스트립 객체를 사용할 준비를 합니다.

4) 1초 기다렸다가 동작이 시작하도록 합니다.

5) 무드등을 켰을 때 레인보우 기능을 시작해서 아름답게? 시작하도록 합니다.

6) 스트립 객체를 시작합니다. 이때 앞서 설명한 것과 같이 현재 색상과 밝기 정보가 생성되지 않았으므로, 이때 show 함수는 스트립 객체를 전원이 꺼진 상태로 초기화합니다.

 

 

5. loop 함수

void loop() { 
  if(blueTooth.available() > 0) { // 데이터가 들어온 게 있다면
    blueTooth.readBytes(colors, 5); // colors 배열에 해당 데이터를 입력한다.
    if(colors[4] == (byte) 0) { // 일반적인 경우라면
      for(byte i = 0; i<4; i++) { // 각각의 데이터를
        Serial.println(colors[i]); // 시리얼 모니터에 출력한다.
      }
      for(byte i = 0;i<NUMS; i++) { // 각각의 픽셀에 
        strip.setPixelColor(i, colors[0], colors[1], colors[2]); // RGB 색상을 지정한다.
      }
      strip.setBrightness(colors[3]); // 밝기를 설정한다.
      // setPixelColor 함수가 스트립 객체에 보낼 메모리 이미지를 형성하고,
      // show 함수가 해당 이미지를 스트립 객체로 보낸다.
      strip.show(); // 밝기, 색상 정보를 스트립 객체로 보내고 LED를 켠다.
      delay(20); // 20 밀리세컨드 기다린다.
    }
    else if(colors[4] == (byte) 1) { // 레인보우 기능을 켠다면
      rainbow(20); // 레인보우 함수를 호출한다.
      delay(20); // 20 밀리세컨드 기다린다.
    }
  }
}

 

동작을 반복하는 loop 함수입니다.

 

1) blueTooth.available() 은 시리얼 포트에서 읽을 수 있는 바이트의 수를 반환합니다.

시리얼 안에는 데이터를 받은 후 이를 저장하는 버퍼(일종의 저장소)가 있으며, 여기에 저장되어있는 데이터를 읽은 후 데이터의 바이트 수를 알 수 있습니다. 만약 0보다 크다면 데이터가 존재한다는 것이며 받은 데이터가 존재해야 필요한 동작을 진행할 수 있으므로, 이 조건이 만족되면 다음 동작을 수행되도록 합니다.

만약 받아온 데이터가 없다면 -1을 반환합니다.

 

2)  blueTooth.readBytes() 함수는 데이터를 읽어서 저장소에 저장하는 기능을 합니다. 이때 colors는 데이터를 저장할 배열이고, 5는 전달받은 데이터 중 읽을 데이터의 수입니다. 즉, 전달받은 데이터 중 앞에서부터 5개를 읽어서 colors 배열에 저장하는 것입니다.

이 함수는 배열, 즉 저장소에 저장된 데이터의 수를 반환하며, 이 경우 5를 반환합니다.

 

3) 앞서 색상 정보는 3개이고 밝기 정보가 1개이므로 4개의 데이터만 전달받아도 되는데, colors의 배열 길이를 5로 설정하였죠? 이는 단지 추가적인 기능을 위해서 제가 추가한 것입니다. 이 정보를 레인보우 정보라고 하겠습니다. 그리고 이 값이 0인 것을 '레인보우가 아니다'라고 하겠습니다.

colors [4]에 들어있는 바이트가 0이라는 조건을 만족하면, 즉 레인보우가 아니라면, 중괄호 {} 안에 있는 내용을 수행합니다.

Serial.println() 함수는 시리얼 모니터에 내용을 출력하는 함수이며, 전달받은 색상 정보와 밝기 정보를 순서대로 출력합니다. 컴퓨터는 0부터 시작하므로, colors 배열의 0번째, 1번째, 2번째, 3번째 총 4개의 정보를 순서대로 출력합니다. 0~2번째 정보는 색상 정보(RGB)이고, 3번째 정보는 밝기 정보입니다.

 

4) strip.setPixelColor() 함수는 말 그대로 LED 픽셀의 색을 설정합니다. 전달되는 파라미터는 순서대로 픽셀의 번호, 빨간색, 파란색, 초록색 순서입니다. 픽셀의 번호는 LED 스트립에서 몇 번째 LED 픽셀인지의 여부를 알려주며, 그 이후 파라미터는 RGB 색상을 설정하도록 합니다.

이때 배열은 픽셀의 개수만큼 반복하여, 모든 픽셀의 색을 설정하도록 합니다.

(NUMS는 4이며, <4는 4보다 작은 3까지 포함하는 것입니다. 따라서 이때 배열은 0≤픽셀 번호≤3으로, 총 4개의 픽셀을 지정합니다)

 

5) strip.setBrightness() 함수는 스트립의 밝기를 설정합니다. colors [3]에 밝기에 대한 정보가 있으므로 이를 설정해주시면 되겠습니다.

 

6) stip.show() 함수로 밝기, 색상에 대한 정보를 업데이트해서 스트립 객체가 시작하도록 합니다. 만일 이 함수가 없다면 밝기와 색상을 설정한 이후 즉각적으로 변경되지 않아 제대로 동작하지 않는 것처럼 보일 수 있습니다.

이후 20 밀리세컨드를 기다립니다. 이때 delay() 함수는 단순히 기다리는 것이 아니라 프로그램을 그동안 멈추는 것이므로, 이때는 동작하지 않습니다.

 

7) else if는 앞선 조건이 부합하지 않다면~ 이란 뜻입니다. 만일 colors [4]의 값이 0이 아니라면, 즉 '레인보우 정보'가 1이라면, 바꿔 말해서 레인보우 정보라면 rainbow() 함수를 호출하도록 합니다.

즉, 이 정보는 레인보우 기능을 시작하도록 하는 정보입니다.

마찬가지로 이후에 20 밀리세컨드를 기다려줍니다.

 

 

rainbow 함수와 Wheel 함수는 앞선 시간에 설명했으므로, 여기선 해당 내용에 대한 설명은 배제하겠습니다.

 

3. 전체 코드

#include <SoftwareSerial.h>
#include <Adafruit_NeoPixel.h>

#define PIN 6
#define NUMS 4
#define TX 2 
#define RX 3

Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUMS, PIN, NEO_GRB + NEO_KHZ800);
SoftwareSerial blueTooth(TX, RX);
byte colors[5]; // 색상을 받아오는 배열


void setup() {
  blueTooth.begin(9600); // 블루투스 통신 준비, 통신속도는 9600 보드레이트로 설정
  Serial.begin(9600); // 시리얼 모니터 준비
  strip.begin(); // LED 제어 준비
  delay(1000); // 1초 기다렸다가 켜지게 하기
  rainbow(20); // rainbow 효과가 나오면서 켜지도록 하기
  strip.show(); // LED 전원이 꺼진 상태로 초기화하기
}

void loop() { 
  if(blueTooth.available() > 0) { // 데이터가 들어온 게 있다면
    blueTooth.readBytes(colors, 5); // colors 배열에 해당 데이터를 입력한다.
    if(colors[4] == (byte) 0) { // 일반적인 경우라면
      for(byte i = 0; i<4; i++) { // 각각의 데이터를
        Serial.println(colors[i]); // 시리얼 모니터에 출력한다.
      }
      for(byte i = 0;i<NUMS; i++) { // 각각의 픽셀에 
        strip.setPixelColor(i, colors[0], colors[1], colors[2]); // RGB 색상을 지정한다.
      }
      strip.setBrightness(colors[3]); // 밝기를 설정한다.
      // setPixelColor 함수가 스트립 객체에 보낼 메모리 이미지를 형성하고,
      // show 함수가 해당 이미지를 스트립 객체로 보낸다.
      strip.show(); // 밝기, 색상 정보를 스트립 객체로 보내고 LED를 켠다.
      delay(20); // 20 밀리세컨드 기다린다.
    }
    else if(colors[4] == (byte) 1) { // 레인보우 기능을 켠다면
      rainbow(20); // 레인보우 함수를 호출한다.
      delay(20); // 20 밀리세컨드 기다린다.
    }
  }
}

// 표현할 수 있는 색을 순서대로 표현하는 함수
void rainbow(uint8_t wait) {
  uint16_t i, j;

  for(j=0; j<256; j++) {
    for(i=0; i<strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel((i+j) & 255));
    }
    strip.show();
    delay(wait);
  }
}

// 색을 지정해주는 함수
uint32_t Wheel(byte WheelPos) {
  WheelPos = 255 - WheelPos;
  if(WheelPos < 85) {
   return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  } else if(WheelPos < 170) {
    WheelPos -= 85;
   return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  } else {
   WheelPos -= 170;
   return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  }
}

코드량이 많지 않으므로 충분히 해보실 수 있다고 생각합니다. 물론 여기서 다른 기능을 추가하셔도 무방합니다.

 

 

 

4. 마무리

이것으로 아두이노에 대한 준비는 모두 마쳤습니다. 다음 시간에는 스마트폰에서 구동할 안드로이드 앱을 본격적으로 만들어보겠습니다.
이 정보가 많은 도움이 되면 좋겠습니다. 궁금한 점이 있으시다면 언제든 댓글로 남겨주시기 바랍니다. ^^

728x90
반응형