프로젝트

나도 메이커! 메이커스 여러분들의 작품/프로젝트를 공유하는 공간입니다.

Fashionable한 GPS LED 시계 목걸이 만들기

2014-09-19 12:54:49

개요

스마트폰이 활성화 된 요즘 시계를 악세사리로 가지고 다니시는 분이 많습니다.
그래서 더욱 특이한 모형의 시계나 화려한 보석을 부착한 시계들이 많이 있는데요,

이러한 시계를 LED를 이용하여 빛이나고 화려하면 어떨까요?  다른 사람들의 시선을 끌고, 자기 자신을 나타낼 수 있겟죠?
 
사진 출처 : KATIE STORY

사진 출처 : Aliexpress


이 시계를 아두이노 Flora 보드와 NeoPixel ring / GPS 모듈을 이용하여 목걸이형 아날로그 시계를 만들어 보겠습니다.

여기에 추가 기능으로 기본상태에 LED가 무지개 색으로 돌면서 나오게 하고, RGB 센서를 이용하여 센서에서 측정된 값을 NeoPixel Ring 에서 나오도록 하겠습니다.

여기에 다른 센서를 이용하여 시계에 추가로 기능을 추가 할 수도 있고, 자신이 마음에 드는 LED를 표현할 수도 있습니다.


사진 출처 : KMUG





미리보기 동영상

시작 전 개념 이해하기

 - 컬러픽업 장갑 만들기
 - NeoPixel
 - GPS
 - Push Button 으로 LED켜기
 

부품 목록

NO 부품명 수량 상세설명
1 Arduino Flora 1 아두이노
2 NeoPixel Ring 1 NeoPixel
3 Flora Wearable Ultimate GPS Module 1 GPS
4 Color Sensor(TCS34725) 1 Color Sensor
5 버튼 1 버튼
6 10kΩ 저항 1 저항
7 점퍼케이블 14 점퍼케이블


부품명 Arduino Flora NeoPixel Ring GPS Module Color Sensor Button 10kΩ 저항 점퍼케이블
파트

하드웨어 Making

회로도 


브레드보드 레이아웃






소프트웨어 Coding

#include <Wire.h>
#include "Adafruit_TCS34725.h"
#include <Adafruit_GPS.h>
#include <SoftwareSerial.h>
#include <Adafruit_NeoPixel.h>
#include "Flora_Pianoglove.h"

#define PIN 6
#define buttonPIN 10
#define CLEARTHRESHHOLD 2000

Adafruit_GPS GPS(&Serial1);

#define GPSECHO false

boolean usingInterrupt = false;

int temp = 0;
int buttonState = 0;
byte gammatable[256];

Adafruit_NeoPixel strip = Adafruit_NeoPixel(16, PIN, NEO_GRB + NEO_KHZ800);
Adafruit_TCS34725 tcs = Adafruit_TCS34725(TCS34725_INTEGRATIONTIME_50MS, TCS34725_GAIN_4X);

void setup()  
{
  Serial.begin(115200);
  pinMode(buttonPIN, INPUT);
  Serial.println("Adafruit GPS library basic test!");
  
  strip.begin();
  strip.show();

   if (tcs.begin()) {
    Serial.println("Found sensor");
  } 
  else {
    Serial.println("No TCS34725 found ... check your connections");
    while (1); 
  }
   
  GPS.begin(9600);
  
  GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);
  GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ);  
  delay(1000);
  Serial1.println(PMTK_Q_RELEASE);
  
    for (int i=0; i<256; i++) {
    float x = i;
    x /= 255;
    x = pow(x, 2.5);
    x *= 255;

    gammatable[i] = x;      
  }
  
    tcs.setInterrupt(true);  
}
uint32_t timer = millis();
void loop(){ // 버튼을 눌러 현재 버튼의 상태값에 따라 모드를 전환시켜줍니다. if(!digitalRead(buttonPIN)){ buttonState++; if(buttonState > 2){ buttonState = 0; } delay(500); } if(buttonState == 0){ rainbowCycle(20); // 대기모드(무지개 색으로 LED가 켜집니다.) } else if(buttonState == 1){ gpsCheck();
// GPS 시계 모드(현재 시간을 표시합니다.) } else if(buttonState == 2){ colorPick();
// 칼라 픽업 모드(시계 가운데 있는 센서에서 색을 측정하여 LED로 표현합니다.) } delay(10); } void gpsCheck(){ // 시계 모드 char c = GPS.read(); if (GPSECHO) if (c) Serial.print(c); if (GPS.newNMEAreceived()) { Serial.println(GPS.lastNMEA()); if (!GPS.parse(GPS.lastNMEA())) return; } if (timer > millis()) timer = millis(); if (millis() - timer > 2000) { timer = millis(); int hour; if(GPS.hour < 3){ hour = GPS.hour + 9; }else{ hour = GPS.hour - 3; } Serial.print("\nTime: "); Serial.print(hour, DEC); Serial.print(':'); Serial.print(GPS.minute, DEC); Serial.print(':'); Serial.println(GPS.seconds, DEC); Serial.print('.'); int h = map(hour, 0, 12, 15, 0); int m = map(GPS.minute, 0, 59, 15, 0); int s = map(GPS.seconds, 0, 59, 15, 0); for(int i = 0; i < strip.numPixels(); i++){ strip.setPixelColor(i, strip.Color(0, 0, 0)); strip.show(); } strip.setPixelColor(s, strip.Color(0, 0, 255)); strip.setPixelColor(m, strip.Color(0, 255, 0)); strip.setPixelColor(h, strip.Color(255, 0, 0)); strip.show(); // Serial.println(GPS.milliseconds); Serial.print("Date: "); Serial.print(GPS.day, DEC); Serial.print('/'); Serial.print(GPS.month, DEC); Serial.print("/20"); Serial.println(GPS.year, DEC); Serial.print("Fix: "); Serial.print((int)GPS.fix); Serial.print(" quality: "); Serial.println((int)GPS.fixquality); if (GPS.fix) { Serial.print("Location: "); Serial.print(GPS.latitude, 4); Serial.print(GPS.lat); Serial.print(", "); Serial.print(GPS.longitude, 4); Serial.println(GPS.lon); Serial.print("Speed (knots): "); Serial.println(GPS.speed); Serial.print("Angle: "); Serial.println(GPS.angle); Serial.print("Altitude: "); Serial.println(GPS.altitude); Serial.print("Satellites: "); Serial.println((int)GPS.satellites); } } } 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); } } // Slightly different, this makes the rainbow equally distributed throughout void rainbowCycle(uint8_t wait) { uint16_t i, j; for(j=0; j<256; 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(); } } // Input a value 0 to 255 to get a color value. // The colours are a transition r - g - b - back to r. 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); } } void colorPick() { // 칼라 픽업 모드 uint16_t clear, red, green, blue; tcs.setInterrupt(false); // LED를 켭니다. delay(60); //50밀리초동안 읽습니다. tcs.getRawData(&red, &green, &blue, &clear); tcs.setInterrupt(true); // LED를 끕니다. //충분한 색정보를 불러올 수 있는 대상이 없을 경우 if (clear < CLEARTHRESHHOLD) { for(int i = 0; i < 16; i++){ strip.setPixelColor(i, strip.Color(0, 0, 0)); // LED를 끕니다. strip.show(); } return; } // Serial.print("C:\t"); Serial.print(clear); // Serial.print("\tR:\t"); Serial.print(red); // Serial.print("\tG:\t"); Serial.print(green); // Serial.print("\tB:\t"); Serial.print(blue); // 시각화를 위해 hex code를 계산합니다. uint32_t sum = red; sum += green; sum += blue; sum = clear; float r, g, b; r = red; r /= sum; g = green; g /= sum; b = blue; b /= sum; r *= 256; g *= 256; b *= 256; if (r > 255) r = 255; if (g > 255) g = 255; if (b > 255) b = 255; // Serial.print("\t"); // Serial.print((int)r, HEX); Serial.print((int)g, HEX); Serial.print((int)b, HEX); // Serial.println(); float remove, normalize; if ((b < g) && (b < r)) { remove = b; normalize = max(r-b, g-b); } else if ((g < b) && (g < r)) { remove = g; normalize = max(r-g, b-g); } else { remove = r; normalize = max(b-r, g-r); } //작은 소수를 제거합니다. float rednorm = r - remove; float greennorm = g - remove; float bluenorm = b - remove; // 가장 높은수를 정상화 합니다. rednorm /= normalize; greennorm /= normalize; bluenorm /= normalize; // Serial.println(); // for(int j = 0; j < 16; j++){ strip.setPixelColor(temp, strip.Color(gammatable[(int)r], gammatable[(int)g], gammatable[(int)b])); for(temp; temp < 16; temp++){ strip.setPixelColor(temp, strip.Color(gammatable[(int)r], gammatable[(int)g], gammatable[(int)b])); } strip.show(); if(temp < 16) temp++; else temp = 0; // } // Serial.print(rednorm); Serial.print(", "); // Serial.print(greennorm); Serial.print(", "); // Serial.print(bluenorm); Serial.print(" "); // Serial.println(); } RgbColor HsvToRgb(HsvColor hsv){ RgbColor rgb; unsigned char region, remainder, p, q, t; if (hsv.s == 0) { rgb.r = hsv.v; rgb.g = hsv.v; rgb.b = hsv.v; return rgb; } region = hsv.h / 43; remainder = (hsv.h - (region * 43)) * 6; p = (hsv.v * (255 - hsv.s)) >> 8; q = (hsv.v * (255 - ((hsv.s * remainder) >> 8))) >> 8; t = (hsv.v * (255 - ((hsv.s * (255 - remainder)) >> 8))) >> 8; switch (region) { case 0: rgb.r = hsv.v; rgb.g = t; rgb.b = p; break; case 1: rgb.r = q; rgb.g = hsv.v; rgb.b = p; break; case 2: rgb.r = p; rgb.g = hsv.v; rgb.b = t; break; case 3: rgb.r = p; rgb.g = q; rgb.b = hsv.v; break; case 4: rgb.r = t; rgb.g = p; rgb.b = hsv.v; break; default: rgb.r = hsv.v; rgb.g = p; rgb.b = q; break; } return rgb; } HsvColor RgbToHsv(RgbColor rgb){ HsvColor hsv; unsigned char rgbMin, rgbMax; rgbMin = rgb.r < rgb.g ? (rgb.r < rgb.b ? rgb.r : rgb.b) : (rgb.g < rgb.b ? rgb.g : rgb.b); rgbMax = rgb.r > rgb.g ? (rgb.r > rgb.b ? rgb.r : rgb.b) : (rgb.g > rgb.b ? rgb.g : rgb.b); hsv.v = rgbMax; if (hsv.v == 0) { hsv.h = 0; hsv.s = 0; return hsv; } hsv.s = 255 * long(rgbMax - rgbMin) / hsv.v; if (hsv.s == 0) { hsv.h = 0; return hsv; } if (rgbMax == rgb.r) hsv.h = 0 + 43 * (rgb.g - rgb.b) / (rgbMax - rgbMin); else if (rgbMax == rgb.g) hsv.h = 85 + 43 * (rgb.b - rgb.r) / (rgbMax - rgbMin); else hsv.h = 171 + 43 * (rgb.r - rgb.g) / (rgbMax - rgbMin); return hsv; }

* 이 소스는 Flora_Pianoglove.hAdafruit_NeoPixel.hAdafruit_TCS34725.hAdafruit_GPS.h 추가 라이브러리를 사용합니다. 라이브러리 사용법은 링크를 참고하시기 바랍니다.

void loop(){

  // 버튼을 눌러 현재 버튼의 상태값에 따라 모드를 전환시켜줍니다. 
  if(!digitalRead(buttonPIN)){
    buttonState++;
    
    if(buttonState > 2){
      buttonState = 0;
    }
    
    delay(500);
  }
  if(buttonState == 0){
    rainbowCycle(20);
   // 대기모드(무지개 색으로 LED가 켜집니다.)
  }
  else if(buttonState == 1){
    gpsCheck();
// GPS 시계 모드(현재 시간을 표시합니다.) } else if(buttonState == 2){ colorPick();
// 칼라 픽업 모드(시계 가운데 있는 센서에서 색을 측정하여 LED로 표현합니다.) } delay(10); }


버튼의 입력에 따라서 모드를 전환 시킵니다.(기본 0부터 시작하여 버튼을 눌렀을시 1씩 증가, 버튼이 3이 되었을 경우 다시 0으로 바꿔줍니다.)

처음 0번은 NeoPixel Ring 이 무지개 색으로 LED를 회전 시키고, 1번은 GPS 센서에서 측정된 시간을 가지고 NeoPixel Ring으로 표현해 줍니다.

2번은 Color PickUP모드로 가운데 있는 칼라 센서에서 측정된 값을 NeoPixel Ring 에서 한칸씩 차례대로 출력합니다.

    for(int i = 0; i < 16; i++){
      strip.setPixelColor(i, strip.Color(gammatable[(int)r], gammatable[(int)g], gammatable[(int)b]));      
strip.show(); }

NeoPixel Ring 에 출력하는 부분입니다. pixel 0번 부터 pixel 마다 값을 세팅하고 show()로 띄워줍니다.

원하는 위치에 pixel에 불을 제어하고 싶으면 setPixelColor()에서 i 부분만 원하시는 pixel번호로 바꿔주시면 됩니다. 
(꼭 세팅 후 show()를 해주셔야 NeoPixel 에서 켜집니다.)

    int h = map(GPS.hour, 0, 12, 15, 0);
    int m = map(GPS.minute, 0, 59, 15, 0);
    int s = map(GPS.seconds, 0, 59, 15, 0);

GPS 센서에서 시간값을 받아서 NeoPixel Ring 에 Pixel을 정해주는 부분입니다.

시간은 0~12시, 분,초는 0~60초 값이 나오는데 그것을 0~15번안에 들어가게 세팅을 해준 후 해당 pixel에 불을 켜게 됩니다.
(시각을 정확하게 표현하기 위해서는 12Pixel 짜리 NeoPixel Ring 을 쓰시면 됩니다. 이 컨텐츠에서는 16Pixel NeoPixel Ring을 사용했습니다.)
(위의 소스를 그대로 사용하셧을 경우 GPS가 잡히지 않으면 9:0:0초부터 시작하게 됩니다.)

    for(int i = 0; i < strip.numPixels(); i++){
      strip.setPixelColor(i, strip.Color(0, 0, 0));
      strip.show();
    }

    strip.setPixelColor(s, strip.Color(0, 0, 255));
    strip.setPixelColor(m, strip.Color(0, 255, 0));   
    strip.setPixelColor(h, strip.Color(255, 0, 0));
NeoPixel Ring에 세팅된 값들을 리셋하고, 현재 시간에 맞는 값을 세팅합니다. 시분초 순서대로 빨간색, 녹색, 파란색 순서입니다.
(시 = 빨강, 분 = 초록, 초 = 파랑)
(Pixel이 겹쳣을 경우 시 > 분 > 초의 우선 순위를 두고 색을 출력합니다. 예: 6시 30분 30초일 경우 6시에 맞는 Pixel에는 빨간색만 나오게 됩니다.)

판다마니아

아두이노, Flora, GPS, Color Sensor, 중급, Button, NeoPixel, LED