오늘은 3D 프린터를 이용하여 출력한 부품들과 앞서 소개한 부품들을 함께 조립하여 시계를 완성하겠습니다. 이 글을 통해 시계 자체는 모두 완성되고 다음 글에서 무선 인터넷을 이용한 사용자 인터페이스를 구성하면 이번 프로젝트는 모두 마무리 됩니다. 출력 과정은 생략하고 출력물부터 소개할 것이고, 별다른 설명은 필요 없어서 사진 위주의 소개글이 될 것입니다.
3D 프린터로 출력한 부품
출력물은 다섯 개입니다. 케이스 좌, 우 그리고 엔코더용 노브(knob)와 네오픽셀 LED용 디퓨저(diffuser), 마지막으로 시계 뒤 커버(cover)입니다. 제가 가진 3D 프린터의 출력 범위가 125mm 밖에 안돼서 케이스를 둘로 나눌 수 밖에 없었습니다. 세그먼트 LED의 폭이 120mm나 되는 바람에 고민을 너무 많이 했네요!
시계 뒤 커버입니다. NodeMCU 보드, 아날로그 광센서, 그리고 배터리 쉴드를 장착합니다.
케이스 좌, 우 부품입니다. 처음에는 한 덩이로 설계했지만, 출력하다보니 어쩔 수 없이 나누게 되었네요. 그래서 디자인이 많이 지저분해졌습니다.
네오픽셀 RGBW LED를 위한 디퓨저입니다. 반투명 재질로 출력했는데, 생각보다 투명한 느낌은 없네요. 세그먼트 LED 아래쪽에 장착됩니다.
로터리 엔코더를 위한 노브입니다. 돌리기 쉽게 약간 크게 디자인했습니다.
부품 조립 및 완성
디퓨저에 네오픽셀 LED를 장착했습니다. 돌출된 LED를 위해 안쪽으로 홈을 내었고, 볼트 홀이 작아서 M2 사이즈의 볼트를 이용해 고정하였습니다.
#include <ESP8266WiFi.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <Adafruit_LEDBackpack.h>
#include <Adafruit_NeoPixel.h>
//
// 엔코더 관련 핀 번호
#define outA D5
#define outB D6
#define sw D7
//
// WiFi 접속 정보
const char *ssid = "coffee";
const char *password = "coffee11";
//
int previousMin = 0; // 1분마다 NTP 정보 업데이트
//
// LED Segment 관련 변수들
int segBright = 15; // 숫자판 밝기
int segSleepBright = 4; // 슬립모드일 때 밝기
// 1초마다 Colon blink, LED effects 효과 내기 위한 변수들
int previousSec = 0; // 1초 지났는지 체크위해 이전 초값 저장
bool hours12 = true; // 12, 24 시간제 선택, true - 12시간제
unsigned long colonOnTime; // 콜론이 켜져있는 시간 체크
bool colonBlink = true; // 콜론이 깜박일지 여부
bool colonSleepBlink = true; // 슬립 모드일때 콜론 깜박임 여부
//
// NEOPIXEL LED 관련
bool ledSlide = true; // LED 슬라이드 효과 여부
bool ledSlideFirst = true; // 슬라이드 시작할 때 먼저 배경색 채워준다
byte ledCount; // 몇 번째 LED인지 체크
int bgRed = 100;
int bgGreen = 30;
int bgBlue = 30;
int fgRed = 0;
int fgGreen = 0;
int fgBlue = 255;
int rgbBrightness = 125;
//
// 슬립 모드 관련 변수
bool sleepMode = true; // 슬립 모드 사용 여부
int startSleepMode = 50; // 슬립 모드로 들어가는 밝기 값
bool sleepModeOn = false; // 슬립 모드로 들어간 상태인지 체크
// 이미 슬립 모드라면 처리하지 않기 위해...
// 엔코더 관련 변수
int previousA = 1;
int previousB = 1;
int previousPush = 1;
int cwCount = 0;
int ccwCount = 0;
int whiteBrightness = 125;
bool whiteOn = false;
unsigned long debounce = 0;
//
WiFiUDP udp;
NTPClient timeClient(udp, "kr.pool.ntp.org", 32400, 3600000);
Adafruit_7segment ledSegment = Adafruit_7segment();
Adafruit_NeoPixel ledBar = Adafruit_NeoPixel(8, D3, NEO_GRBW + NEO_KHZ800);
//
void setup(){
//
pinMode(outA, INPUT_PULLUP);
pinMode(outB, INPUT_PULLUP);
pinMode(sw, INPUT_PULLUP);
//
Wire.begin(D1, D2);
ledSegment.begin(0x70);
ledSegment.setBrightness(segBright);
//
ledBar.begin();
ledBar.show();
ledBar.setBrightness(rgbBrightness);
ledBarClear();
//
wifiConnect();
//
timeClient.begin();
}
//
void loop() {
if ( ( millis() - debounce ) > 1 ) {
encoderInput();
}
//
ntpUpdate();
//
effectSec();
//
if ( sleepMode ) { // 슬립모드 사용일때만 밝기 체크 들어감
toSleepMode();
}
}
//
void wifiConnect() {
WiFi.begin(ssid, password);
int count = 0;
while ( WiFi.status() != WL_CONNECTED ) {
ledSegment.displaybuffer[0] = 0x01 << count;
ledSegment.displaybuffer[1] = 0x01 << count;
ledSegment.displaybuffer[3] = 0x01 << count;
ledSegment.displaybuffer[4] = 0x01 << count;
ledSegment.writeDisplay();
//
count = count + 1;
if ( count > 5 ) count = 0;
delay ( 200 );
}
}
//
void ntpUpdate() {
timeClient.update(); // NTP 정보 업데이트
int currentMin = timeClient.getMinutes();
if (previousMin != currentMin) { // 1분이 지났는지 체크
int currentTime = timeClient.getHours() * 100 + currentMin;
//
if (hours12) { // 12시간제로 표시할 경우
if (currentTime > 1259) {
ledSegment.print(currentTime - 1200, DEC);
} else {
ledSegment.print(currentTime, DEC);
}
//
if (currentTime > 1159) {
ledSegment.displaybuffer[2] = 8; // PM
} else {
ledSegment.displaybuffer[2] = 4; // AM
}
} else { // 24시간제로 표시
ledSegment.print(currentTime, DEC);
}
ledSegment.writeDisplay();
previousMin = currentMin;
}
}
//
void effectSec() {
int currentSec = timeClient.getSeconds();
if (previousSec != currentSec) { // 1초가 지났다면...
// 1초마다 Colon ON
// Blink 여부와 상관없이 항상 ON
// AM, PM을 위해 켜져있는 앞쪽 도트랑 OR 연산을 해준다
ledSegment.displaybuffer[2] = ledSegment.displaybuffer[2] | 0x02;
ledSegment.writeDisplay();
colonOnTime = millis(); // 500밀리초가 지나면 콜론 OFF위해 ON 시간 저장
//
// 1초마다 네오픽셀 LED 슬라이드 효과 내기
if ( ledSlide && !sleepModeOn && !whiteOn ) {
if ( ledSlideFirst ) { // 처음 출력할 땐, 8개 LED에 먼저 배경색 채운다
for ( int i = 0; i < 8; i++ ) {
ledBar.setPixelColor(i, bgRed, bgGreen, bgBlue);
}
ledCount = 7;
ledSlideFirst = false;
}
ledBar.setPixelColor(ledCount, bgRed, bgGreen, bgBlue);
if ( ++ledCount > 7 ) ledCount = 0;
ledBar.setPixelColor(ledCount, fgRed, fgGreen, fgBlue);
ledBar.show();
}
previousSec = currentSec;
}
//
// 가운데 콜론 깜박임 처리
if ( colonBlink ) {
if ( sleepModeOn && !colonSleepBlink ) {
// 슬립 모드일때 깜박임 안한다면 아무 일도 안함
} else if (millis() - colonOnTime > 500) { // 콜론이 켜지고 500밀리 초가 지났다면...
ledSegment.displaybuffer[2] = ledSegment.displaybuffer[2] & 0xFD;
ledSegment.writeDisplay();
colonOnTime = millis();
}
}
}
//
void ledBarClear() {
for ( int i = 0; i < 8; i++ ) {
ledBar.setPixelColor(i, 0, 0, 0, 0);
}
ledBar.show();
}
//
void toSleepMode() {
int lightVal = analogRead(A0);
if ( ( lightVal <= startSleepMode ) && !sleepModeOn ) { // 정해진 밝기 이하로 떨어지고
// 아직 슬립 모드가 아닐 때...
ledSegment.setBrightness(segSleepBright); // 슬립 모드일때의 세그먼트 밝기
ledBarClear(); // 슬립 모드에서 네오픽셀은 OFF
sleepModeOn = true;
} else if ( ( lightVal > startSleepMode + 100 ) && sleepModeOn ) {
ledSegment.setBrightness(segBright);
ledSlideFirst = true;
sleepModeOn = false;
}
}
//
void encoderInput() {
int currentA = digitalRead(outA);
int currentB = digitalRead(outB);
//
if ( ( currentA != previousA ) || ( currentB != previousB ) ) {
if ( currentA == 0 ) { // A 입력이 "0"일때
if ( currentB == 0 ) { // B 입력이 "0"일때
// 입력이 0,0 일때의 처리
if ( ( cwCount == 1 ) ) {
cwCount = 2;
} else if ( ( ccwCount == 1 ) ) {
ccwCount = 2;
}
} else { // B 입력이 "1"일때
// 입력이 0,1 일때의 처리
if ( ( cwCount == 0 ) && ( ccwCount == 0 ) ) {
cwCount = 1;
} else if ( ( ccwCount == 2 ) ) {
ccwCount = 3;
}
}
} else { // A 입력이 "1"일때
if ( currentB == 0 ) { // B 입력이 "0"일때
// 입력이 1,0 일때의 처리
if ( ( cwCount == 2 ) ) {
cwCount = 3;
} else if ( ( cwCount == 0 ) && ( ccwCount == 0 ) ) {
ccwCount = 1;
}
} else { // B 입력이 "1"일때
// 입력이 1,1 일때의 처리
if ( ( cwCount == 3 ) ) {
whiteBrightness += 5;
if ( whiteBrightness > 255 ) whiteBrightness = 255;
whiteOn = true;
whiteLedOn();
cwCount = 0;
} else if ( ( ccwCount == 3 ) ) {
whiteBrightness -= 10;
if ( whiteBrightness < 1 ) whiteBrightness = 1;
whiteOn = true;
whiteLedOn();
ccwCount = 0;
}
}
}
previousA = currentA;
previousB = currentB;
}
// 엔코더의 push 스위치 처리
int currentPush = digitalRead(sw);
//
if ( currentPush != previousPush ) {
if ( currentPush == 0 ) {
if ( whiteOn ) {
whiteOn = false;
ledBarClear();
ledSlideFirst = true;
} else {
whiteOn = true;
whiteLedOn();
}
}
previousPush = currentPush;
}
debounce = millis();
}
//
void whiteLedOn() {
ledBar.setBrightness(255);
for ( int i = 0; i < 8; i++ ) {
ledBar.setPixelColor(i, 0, 0, 0, whiteBrightness);
}
ledBar.show();
}
프로그램을 업로드 하고 실행된 모습입니다.
마지막으로 White LED를 이용한 램프를 켜보았습니다.
완성된 모습과 간단한 조작 영상입니다.영상 속에서는 처음 실행시의 구동 영상과 LED 램프를 켜는 모습이 나옵니다. LED 램프는 상단의 노브를 눌러 on/off 하는 모습과 살짝 돌리는 동작으로 램프를 켜는 모습이 이어서 나옵니다. 여기까지 해서 시계는 우선 완성하였고, 다음 글에서는 마지막으로 WiFi를 이용하여 시계를 제어할 수 있도록 서버 및 웹 페이지를 구성하겠습니다. 이상입니다.