모든 프로젝트에는 멀티미터가 필요합니다. 제대로 된 제품을 구입하면 최소 4년, 수리 방법을 안다면 훨씬 더 오래 쓸 수 있습니다. 납땜 연결 상태 확인과 회로 전류 측정에 필요합니다.
앱 및 온라인 서비스
아두이노 IDE
아두이노 프리칭 (Fritzing)
프로젝트 소개
RTC DS1307 IC를 사용해 만든 매우 간단하고 조립하기 쉬운 디지털 시계입니다. LCD 디스플레이 대신 4자리 7세그먼트 디스플레이에 시간을 표시합니다. 코드를 약간 수정하면 알람 기능 등 추가 기능을 쉽게 구현할 수 있습니다. 상상력과 독창성만 있으면 됩니다. 이 프로젝트는 더 복잡하고 나은 작품으로 나아가기 위한 디딤돌이자, 방에 전시할 멋진 시계를 만들고 싶어서 시작했습니다.
납땜 과정에서 겪은 문제들과 해결 방법까지 모든 세부 사항을 이 글에 담았습니다.
1단계: 부품 소개
RTC 모듈 (DS1307)
DS1307 칩은 전원이 꺼진 상태에서도 시간을 유지하는 탁월한 기능을 가집니다. 아두이노와 인터페이스가 쉽고, 이 모듈용 라이브러리도 많이 공개되어 있습니다. RTC는 I2C 프로토콜로 아두이노와 통신하며, 아두이노 나노의 A4(SDA)와 A5(SCL) 핀을 사용합니다. DS 핀은 이 프로젝트에서 사용하지 않습니다.
단점은 원하는 만큼 정확하지 않다는 점입니다. 온도에 따라 시간이 쉽게 드리프트(시간 오차 발생)됩니다.
주의: GND와 Vcc 핀을 올바르게 연결하세요. 모듈에서 Vcc 핀이 GND 핀 앞에 있습니다. 극성을 반대로 연결하면 순식간에 매우 뜨거워집니다. 극성 연결이 걱정된다면 전원 켤 때 코인셀 배터리를 만져보고 뜨거워지면 즉시 끄세요.
시프트 레지스터 (74HC595)
시프트 레지스터 74HC595는 멀티플렉싱(multiplexing) 기법 덕분에 이 프로젝트를 가능하게 했습니다. 초보자도 겁먹지 마세요. 재미있고 배워두면 유용합니다.
74HC595는 16핀이며, 4자리 7세그먼트 디스플레이 제어를 위해 두 개의 시프트 레지스터를 사용합니다.
첫 번째 시프트 레지스터: 세그먼트 점등 제어
두 번째 시프트 레지스터: 어느 자릿수를 켤지 선택 (디지트 선택)
멀티플렉싱 덕분에 자릿수 간 전환이 매우 빨라 모든 숫자가 동시에 표시되는 것처럼 보입니다.
참고: 이 칩들은 꽤 신뢰성이 있지만, 몇 개는 불량이었습니다. 어떤 칩은 Q0, Q1이 작동 안 하고, 어떤 건 Q3가 내부적으로 접지되어 있었습니다 (제조 결함). 지금 쓰는 칩들도 완벽하진 않습니다. 하나는 Q7이 고장 나서 연결을 정확히 하고, 그래도 안 되면 멀티미터 연속성 테스트로 핀을 확인해야 했습니다. 그래도 불평 안 합니다. 프로젝트하며 작은 장애물을 극복하는 법을 배우는 과정이니까요.
4자리 7세그먼트 디스플레이
범용 공통 애노드(Common Anode) 타입 4자리 세그먼트를 사용했습니다. 12핀이며, 왼쪽 아래부터 번호가 시작해 왼쪽 위로 끝납니다. 각 세그먼트는 숫자와 소수점을 표시할 수 있습니다. 디지털 시계의 전형적인 콜론(:)이 없어서 두 번째 자릿수의 소수점으로 대체했습니다. 숫자 표시가 주 목적이라면 훌륭한 디스플레이입니다.
주의: 초보자에게는 좀 까다로울 수 있습니다. 세그먼트 a-g가 일렬로 정렬되어 있지 않으니까요. 전류 제한 저항 없이 5V에 직접 연결하지 마세요.
회로도를 포함시켰으니 참고하세요. 회로도의 디스플레이 타입과 제가 쓴 게 달라서, 시프트 레지스터에서 세그먼트로의 핀 연결을 표로 정리했습니다:
세그먼트
디스플레이 핀 번호
시프트 레지스터 핀
A
11
15 (첫 번째 595)
B
7
1 (첫 번째 595)
C
4
2 (첫 번째 595)
D
2
3 (첫 번째 595)
E
1
4 (첫 번째 595)
F
10
5 (첫 번째 595)
G
5
6 (첫 번째 595)
소수점
3
7 (첫 번째 595)
D1 (첫째 자리)
12
15 (두 번째 595)
D2 (둘째 자리)
9
1 (두 번째 595)
D3 (셋째 자리)
8
2 (두 번째 595)
D4 (넷째 자리)
6
3 (두 번째 595)
이 프로젝트는 저렴하고 만들기 쉽지만, PCB에 납땜해 제대로 만드려면 약간의 인내와 끈기가 필요합니다 (그저 재미로 브레드보드에 해보려면 2시간도 안 걸립니다).
피드백 주시면 개선에 참고하겠습니다. 빠트린 내용이 있다면 알려주세요.
코드
메인 시계 코드 (아두이노)
RTC 라이브러리와 I2C 라이브러리를 사용합니다. 프로그램 실행을 위해 이 라이브러리들이 필요합니다. 이 프로그램은 공통 애노드(Common Anode) 타입 디스플레이용입니다.
#include <Wire.h>
#include <EEPROM.h>
#include <RTClib.h>
RTC_DS1307 RTC;
int temp, inc, hours1, minut, add = 11;
int HOUR, MINUT, SECOND;
int latchPin = 3; // 595의 12번 핀
int dataPin = 4; // 595의 14번 핀
int clockPin = 2; // 595의 11번 핀
int shift = 256;
int units, tens, hundreds, thousands;
int x, y;
const int alarmHour = 17;
const int alarmMinute = 26;
void setup() {
Serial.begin(9600);
pinMode(latchPin, OUTPUT);
pinMode(dataPin, OUTPUT);
pinMode(clockPin, OUTPUT);
pinMode(13, OUTPUT);
Wire.begin();
RTC.begin();
if (!RTC.isrunning()) {
RTC.adjust(DateTime(__DATE__, __TIME__));
}
}
void loop() {
int temp = 0, val = 1, temp4;
DateTime now = RTC.now();
HOUR = now.hour();
MINUT = now.minute();
// 시간 분리 (십의 자리, 일의 자리)
if (HOUR < 10) {
hundreds = HOUR;
thousands = HOUR / 10;
} else if (HOUR >= 10 && HOUR < 24) {
hundreds = HOUR % 10;
thousands = HOUR / 10;
}
if (MINUT <= 9) {
units = MINUT;
tens = MINUT / 10;
} else if (MINUT > 9 && MINUT <= 60) {
units = MINUT % 10;
tens = MINUT / 10;
}
// 각 자릿수별 세그먼트 패턴 출력 (멀티플렉싱)
// units (분의 1의 자리)
switch (units) {
case 0: writeDigit(shift * 8, 192); break;
case 1: writeDigit(shift * 8, 249); break;
case 2: writeDigit(shift * 8, 164); break;
case 3: writeDigit(shift * 8, 176); break;
case 4: writeDigit(shift * 8, 153); break;
case 5: writeDigit(shift * 8, 146); break;
case 6: writeDigit(shift * 8, 130); break;
case 7: writeDigit(shift * 8, 248); break;
case 8: writeDigit(shift * 8, 128); break;
case 9: writeDigit(shift * 8, 144); break;
}
delay(1);
// tens (분의 10의 자리)
switch (tens) {
case 0: writeDigit(shift * 4, 192); break;
case 1: writeDigit(shift * 4, 249); break;
case 2: writeDigit(shift * 4, 164); break;
case 3: writeDigit(shift * 4, 176); break;
case 4: writeDigit(shift * 4, 153); break;
case 5: writeDigit(shift * 4, 146); break;
}
delay(1);
// hundreds (시의 1의 자리)
switch (hundreds) {
case 0: writeDigit(shift * 2, 64); break;
case 1: writeDigit(shift * 2, 121); break;
case 2: writeDigit(shift * 2, 36); break;
case 3: writeDigit(shift * 2, 48); break;
case 4: writeDigit(shift * 2, 25); break;
case 5: writeDigit(shift * 2, 18); break;
case 6: writeDigit(shift * 2, 2); break;
case 7: writeDigit(shift * 2, 120); break;
case 8: writeDigit(shift * 2, 0); break;
case 9: writeDigit(shift * 2, 16); break;
}
delay(1);
// thousands (시의 10의 자리)
switch (thousands) {
case 0: writeDigit(shift, 192); break;
case 1: writeDigit(shift, 249); break;
case 2: writeDigit(shift, 164); break;
case 3: writeDigit(shift, 176); break;
case 4: writeDigit(shift, 153); break;
case 5: writeDigit(shift, 146); break;
case 6: writeDigit(shift, 130); break;
case 7: writeDigit(shift, 248); break;
case 8: writeDigit(shift, 128); break;
case 9: writeDigit(shift, 152); break;
}
delay(1);
// 알람 섹션
if (HOUR == alarmHour && MINUT == alarmMinute) {
digitalWrite(13, HIGH);
} else {
digitalWrite(13, LOW);
}
}
// 세그먼트 출력을 위한 헬퍼 함수 (코드 중복 제거)
void writeDigit(int shiftVal, int pattern) {
digitalWrite(latchPin, LOW);
shiftOut(dataPin, clockPin, MSBFIRST, shiftVal >> 8);
shiftOut(dataPin, clockPin, MSBFIRST, shiftVal + pattern);
digitalWrite(latchPin, HIGH);
}
시간 설정 코드 (아두이노)
DS1307은 시간 드리프트가 발생하기 쉽습니다. 이 프로그램으로 시리얼 모니터를 통해 정확한 시간/날짜를 설정할 수 있습니다. 시간이 맞지 않을 때 RTC 모듈을 아두이노에 연결하고 이 코드를 업로드한 뒤, 시리얼 모니터에서 날짜, 월, 년, 시간을 입력하세요. 그런 다음 메인 시계 코드를 업로드하면 정확한 시간이 7세그먼트 디스플레이에 표시됩니다.
/* RealTimeClockDS1307 라이브러리 - DS1307 RTC 모듈 제어
Copyright (c) 2011 David H. Brown. All rights reserved.
... (라이선스 생략) ... */
#include <Wire.h>
#include <RealTimeClockDS1307.h>
#define Display_Clock_Every_N_Seconds 1
#define Display_ShortHelp_Every_N_Seconds 25
int count = 0;
char formatted[] = "00-00-00 00:00:00x";
void setup() {
Serial.begin(9600);
}
void loop() {
if (Serial.available()) processCommand();
delay(1000);
RTC.readClock();
count++;
if (count % Display_Clock_Every_N_Seconds == 0) {
Serial.print(count);
Serial.print(": ");
RTC.getFormatted(formatted);
Serial.print(formatted);
Serial.println();
}
if (count % Display_ShortHelp_Every_N_Seconds == 0) {
Serial.println("Send ? for a list of commands.");
}
// ... (테스트용 코드들 생략) ...
}
void processCommand() {
if (!Serial.available()) return;
char command = Serial.read();
int in, in2;
switch (command) {
case 'H': case 'h': in = SerialReadPosInt(); RTC.setHours(in); RTC.setClock(); break;
case 'I': case 'i': in = SerialReadPosInt(); RTC.setMinutes(in); RTC.setClock(); break;
case 'S': case 's': in = SerialReadPosInt(); RTC.setSeconds(in); RTC.setClock(); break;
case 'Y': case 'y': in = SerialReadPosInt(); RTC.setYear(in); RTC.setClock(); break;
case 'M': case 'm': in = SerialReadPosInt(); RTC.setMonth(in); RTC.setClock(); break;
case 'D': case 'd': in = SerialReadPosInt(); RTC.setDate(in); RTC.setClock(); break;
case 'W': Serial.print("Day of week is "); Serial.println((int)RTC.getDayOfWeek()); break;
case 'w': in = SerialReadPosInt(); RTC.setDayOfWeek(in); RTC.setClock(); break;
case 't': case 'T': /* 12/24시간 전환 */ break;
case 'A': case 'a': /* AM 설정 */ break;
case 'P': case 'p': /* PM 설정 */ break;
case 'q': RTC.sqwEnable(RTC.SQW_1Hz); break;
case 'Q': RTC.sqwDisable(0); break;
case 'z': RTC.start(); break;
case 'Z': RTC.stop(); break;
case '>': /* 레지스터 쓰기 */ break;
case '<': /* 레지스터 읽기 */ break;
default: /* 도움말 출력 */ break;
}
}
int SerialReadPosInt() {
int i = 0; boolean done = false;
while (Serial.available() && !done) {
char c = Serial.read();
if (c >= '0' && c <= '9') i = i * 10 + (c - '0');
else done = true;
}
return i;
}
라이브러리 파일들
다음 라이브러리 파일들을 아두이노 IDE에 설치해야 합니다:
RealTimeClockDS1307 라이브러리: RealTimeClockDS1307.h, RealTimeClockDS1307.cpp, keywords.txt, Readme를 RealTimeClockDS1307 폴더에 넣고 ZIP으로 압축해 아두이노 IDE의 "라이브러리 추가"로 설치
RTClib 라이브러리: Adafruit/JeeLabs 포크 버전. RTClib.h, RTClib.cpp, README.md 등을 RTClib 폴더에 넣고 동일하게 설치