Pixel Bracelet [제작중]

Pixel Bracelet 착용모습

아두이노와 블루투스 실드, 네오픽셀 LED 를 활용하여 만든 블루투스 연동 LED 팔찌.

부품

1. BLE Shield

BLE 는 Bluetooth Low Energy 의 약자로 일반적으로 4.0 버젼을 일컫는다. iPhone 에서는 3.0 이하 버젼은 애플에 인증받은 회사만 개발이 가능하기 때문에 (자격 조건 및 절차가 매우 복잡함) 4.0 이상 버젼을 사용하기로 하였다.

RFDuino

Bluetooth 4.0 이상 버젼은 구하기 어렵고, 기존 Arduino 라이브러리와 충돌하는 경우가 종종 있다. RFDuino 는 좋은 옵션이긴 하나, Adafruit NeoPixels Library 와 충돌하기 때문에 한동안 시도했지만 계속 LED 가 깜빡 거리는 문제가 발생하여 이번에는 사용하지 않았다.

RedBearLab BLE Shield 장착 모습

RedBearLab 에서 BLE 관련 부품은 두가지로 출시되어있는데, 위 사진의 빨간색 보드가 BLE Shield 로 Arduino Uno 등에 장착하여 프로토타입핑 하기에 편리하다.

RedBearLab BLE Mini

또한 BLE Mini 라고 하는 소형제품도 나와있으나, 최근 BLEDuino 나 Punchthrough 에서 제작한 LightBlue Bean 도 있으니 참고하자. 다만, BLEDuino 는 아직 시중에 나와있지 않다.

2. NeoPixels Strip

Arduino MEGA with Multiple LEDs

NeoPixels Strip 은 Adafruit 에서 개발한 Strip형 LED 로 기존에는 여러 LED 를 켜기 위해서 위 사진과 같이 각각 핀에 연결해야 했었지만 NeoPixels 의 경우 Adafruit 에서 개발한 WS2811(2) 를 사용, 신호를 다음 LED 에 전달할 수 있도록 만들어, 매우 편리하게 LED 를 켜고 꺼는 것이 가능하도록 하였다.

Arduino Uno & NeoPixels LED

NeoPixels 의 연결은 상대적으로 매우 간단하여 예제와 같이 Arduino Uno 6번 PWM Pin 에 연결하여 사용하였다. Strip 은 손목 둘레에 맞춰 12개 정도를 잘랐음.

3. 3D Printed Bracelet

팔찌는 SketchUp 으로 모델링하여 3D 프린터 Almond 로 출력하였다. 모델링 과정은 매우 복잡하고 긴 스토리이므로 생략하기로 한다.

3D Model

10 종류의 팔찌 디자인을 수정하였고, 최종적으로 Thingiverse의 LED Bracelet V2.0 을 참고하여 실드가 있는 형태로 잠정 확정지었다.

3D Printed Bracelets

상당히 여러 형태를 테스트해보았으나 아래 사진의 형태로 확정.

최종 형태

레일과 같은 형태로 NeoPixels 를 넣고, 손목에 상처가 가지 않도록 플라스틱 실드를 만들어주었다.

실제 NeoPixels Strip 을 장착한 모습이다. 스케치업 3D Model 은 이곳에서 다운받을 수 있다.

4. Arduino

Arduino 측에는 RBL_nRF8001 이라는 라이브러리를 사용했다. RedBearLab 에서 제공하는 BLE Shiled 및 Mini 를 위한 소프트웨어 라이브러러리로 Bluetooth 로 보내는 것은 매우 간편하나 Arduino 에서 신호를 받을때에는 Memory 가 부족하여 약간의 편법(?)을 사용해보았다. 좀 더 효율적인 방법이 있을것 같지만, 프로로타이핑 시간 절약을 위해서 할 수 있는 부분만을 적용했다. loop(){} 부분을 참고.

#include <SPI.h>
#include <boards.h>
#include <RBL_nRF8001.h>
#include <services.h> 
#include <Adafruit_NeoPixel.h>

#define PIN 6

#define COLOR_WIPE 1
#define RAINBOW 2
#define ROTATE_SINGLE_PIXEL 3
#define ROTATE_SINGLE_PIXEL_BOUNCE 4
#define THEATER_CHASE 5
#define THEATER_CHASE_RAINBOW 6

Adafruit_NeoPixel strip = Adafruit_NeoPixel(12, PIN, NEO_GRB + NEO_KHZ800);

void setup()
{  
  ble_set_name("PixelBracelet");
  ble_begin();
  strip.begin();
  strip.show();

  // Enable serial debug
  Serial.begin(57600);
}

unsigned char buf[16] = {0};
unsigned char len = 0;

unsigned char lightMode;

int rgb[] = {};
int mode;

void loop()  
{
  digitalWrite(13, LOW);

  if ( ble_available() )
  {
    while ( ble_available() ) {

      mode = (char)ble_read() - '0';

      if ( mode == COLOR_WIPE ) {
        lightMode = 'C';
      } else if ( mode == ROTATE_SINGLE_PIXEL ) {
        lightMode = 'S';
      } else if ( mode == ROTATE_SINGLE_PIXEL_BOUNCE ) {
        lightMode = 'B';
      } else if ( mode == THEATER_CHASE ) {
        lightMode = 'T';
      } else if ( mode == RAINBOW ) {
        lightMode = 'R';
      } else if ( mode == THEATER_CHASE_RAINBOW ) {
        lightMode = 'E';
      }

      switch ( mode ) {
      case COLOR_WIPE:
      case ROTATE_SINGLE_PIXEL:
      case ROTATE_SINGLE_PIXEL_BOUNCE:
      case THEATER_CHASE:
        Serial.println("SETTING COLOR");

        for ( int i = 0; i < 3; i++ ) {
          String code = "";
          boolean isContinue = true;

          while (isContinue) {
            char ch = (char)ble_read();

            if ( ch != ',' && code.length() < 3 ) {
              code += ch;
            } 
            else {
              isContinue = false;
            }
          }

          int intCode = code.toInt();
          rgb[i] = intCode;      
        }

        break;

      default: 
        break;

      }      

    }

  }

   switch ( lightMode ) {

    case 'C':
      Serial.println("COLORWIPE");
      colorWipe(strip.Color(rgb[0], rgb[1], rgb[2]), 50); 
      break;
    case 'R':
      Serial.println("RAINBOW");
      rainbow(20);
      break;
    case 'S':
      Serial.println("ROTATE");
      rotateSinglePixel(strip.Color(rgb[0], rgb[1], rgb[2]), 50, true);
      break;
    case 'B':
      Serial.println("ROTATE_SINGLE_PIXEL_BOUNCE");
      rotateSinglePixel(strip.Color(rgb[0], rgb[1], rgb[2]), 50, false);
      break;
    case 'T':
      Serial.println("THEATER_CHASE");
      theaterChase(strip.Color(rgb[0], rgb[1], rgb[2]), 50);
      break;
    case 'E':
      Serial.println("THEATER_CHASE_RAINBOW");
      theaterChaseRainbow(50);
      break;
    default:
      break;
    }   

  if ( Serial.available() )
  {
    delay(5);

    while ( Serial.available() )
      ble_write( Serial.read() );
  }

  ble_do_events();
}

void rotateSinglePixel(uint32_t c, uint8_t wait, bool isSingle) {
  int totalPixels = strip.numPixels();

  for (int i = 0; i < totalPixels; i++) {
    strip.setPixelColor(i, c);    //turn every third pixel on

    for ( int j = 0; j < totalPixels; j++ ) {
      if ( j != i ) {
        strip.setPixelColor(j, 0);
      }
    }

    strip.show();
    delay(wait);
  }

  if ( isSingle ) {
    return;
  } 

  for (int i = totalPixels-1; i >= 0; i--) {
    strip.setPixelColor(i, c);    //turn every third pixel on

    for ( int j = totalPixels-1; j >= 0; j-- ) {
      if ( j != i ) {
        strip.setPixelColor(j, 0);
      }
    }

    strip.show();
    delay(wait);
  }
}

void theaterChaseRainbow(uint8_t wait) {
  for (int j=0; j < 256; j++) {     // cycle all 256 colors in the wheel
    for (int q=0; q < 3; q++) {
        for (int i=0; i < strip.numPixels(); i=i+3) {
          strip.setPixelColor(i+q, Wheel( (i+j) % 255));    //turn every third pixel on
        }
        strip.show();

        delay(wait);

        for (int i=0; i < strip.numPixels(); i=i+3) {
          strip.setPixelColor(i+q, 0);        //turn every third pixel off
        }
    }
  }
}

void theaterChase(uint32_t c, uint8_t wait) {
  for (int j=0; j<10; j++) {  //do 10 cycles of chasing
    for (int q=0; q < 3; q++) {
      for (int i=0; i < strip.numPixels(); i=i+3) {
        strip.setPixelColor(i+q, c);    //turn every third pixel on
      }
      strip.show();

      delay(wait);

      for (int i=0; i < strip.numPixels(); i=i+3) {
        strip.setPixelColor(i+q, 0);        //turn every third pixel off
      }
    }
  }
}

void colorWipe(uint32_t c, uint8_t wait) {
  for(uint16_t i=0; i<strip.numPixels(); i++) {
    strip.setPixelColor(i, c);
    strip.show();
    delay(wait);
  }
}

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));

      if ( ble_available() ) {
        break; 
      }
    }
    strip.show();
    delay(wait);
  }
}

void rainbowCycle(uint8_t wait) {
  uint16_t i, j;

  for(j=0; j<256*5; j++) { // 5 cycles of all colors on wheel
    for(i=0; i< strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));
    }
    strip.show();
    delay(wait);
  }
}

uint32_t Wheel(byte WheelPos) {
  if(WheelPos < 85) {
    return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  } 
  else if(WheelPos < 170) {
    WheelPos -= 85;
    return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  } 
  else {
    WheelPos -= 170;
    return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
}

소스는 Github 에서 얻을 수 있다.

5. App

디자인은 고려하지 않고 기능에 집중하였다. 역시 RedBearLab 제공 iOS 샘플 을 기초로 제작하였고, Adafruit 에서 제공하는 NeoPixels Library strandtest 에 나오는 형태를 재현하는데 최선을 다하였다.

PixeBracelet Controller App

상단의 회색 부분은 색상 변경을 편리하게 할수 있도록, 아래의 RGB 값과 연동되도록 구성하였다. 소스는 역시 Github 에서 얻을 수 있다.

6. Testing

크기 소 크기 중 크기 대

크기를 달리하며 테스트해보았다. 중간 사이즈의 팔지크기가 가장 예쁘고 산란형태가 좋았다. 다만 필라멘트가 전부 ABS 라 딱딱한 것이 단점. 나일론이나 플렉서블 필라멘트로 제작하면 탄성 및 형태 잡기에도 더 좋을 것으로 예상.

7. 3D Printing Material

어차피 실제 제품의 경우 3D 프린터로 제작하지 않겠지만, 다양한 소재로 테스트해보는 것의 의미는 최적의 형태를 찾기 위함이다. 특히 팔찌의 경우 손목에 밀착하기 위해서 물렁물렁한 소재를 사용해야 하기 때문에, 소재를 바꿔가며 테스트해야할 필요가 있다.

1) ABS Filament

단단하고, 탄성이 거의 없다. 팔찌로 사용하려면 사이즈를 크게 만들어야 하고, 그러다보니 팔에 얹어놓는 느낌이 든다.

LED 테스트 (R255, G128, B128)

2) Nylon Filament

Almond with Nylon Filament

나일론 소재를 사용하여 탄성이 좋고, 표면이 빛을 반사해 반짝거린다. 탄성이 좋아서 물렁거리고 따라서 ABS 와 같은 크기로 만들었더니 헐렁하다는 느낌이 든다. 디자인을 변경해야할 것 같음.

팔찌로 만들기 적합한 소재로 보이나, Flexbile Filament 를 사용해보면 좋을듯. 반짝거려서 LED 빛이 상당히 예쁘게 보인다.

LED 테스트 (R255, G128, B128)

3. Flexible Filament

아직 판매 하고 있지 않다. Plabs 를 주시하는 중

개선할 사항

  • Coin BLE Development KitLightBlue Bean 벤치마크
  • BLE Board 와 3D Printed Bracelet 결합하여 프로토타입 완성
  • 앱에서 색상이나 모드를 변경하면 바로 반응하지 않음
  • 앱에서 색상을 ColorPicker 로 찍을 수 있도록 UI 변경
  • 자전거용 앱과의 연동 API
  • 소리 센서(마이크)를 추가하여 이퀄라이저 (Music Visualizer) 모드 추가
comments powered by Disqus