간단히 말해, 4행의 램프로 구성됩니다. 최상단 행은 5시간 단위, 그 다음 행은 1~4시간(단일 시간)을 표시합니다. 세 번째 행은 5분 단위, 최하단 행은 1분 단위를 표시합니다.
사진(실제로는 램프 하나가 고장 난 상태)에서 시간은: 2×5시간 + 3시간 + 6×5분 + 2분 = 13:32입니다. 유럽인들은 24시간제에 익숙하니 이 시계도 24시간제를 따릅니다.
맨 위 가장 큰 램프는 초마다 깜빡입니다.
이 시계를 아두이노로 만들어보면 재미있을 것 같았습니다.
1단계: 전자 회로 구성
이 프로젝트에 필요한 것들:
당연히 아두이노. 책상 근처에 굴러다니는 우노 보드 중 하나를 사용했습니다.
시계 기능이 필요하므로 RTC 사용. DS1307 칩이 탑재된 저렴한 모듈 사용.
'디스플레이'용 LED 필요. 5mm 황색 LED 13개, 적색 LED 11개 사용. (적색 LED가 부족해 RGB LED로 대체했음)
브레드보드, 구멍만 뚫린 기판(솔더 아일랜드 타입). 소위 '유로 카드 포맷'의 일부를 잘라 사용.
저항 2개 사용. 220옴(초 표시 LED용), 68k옴(MAX7219 전류 제한용).
추가 IC 1개: MAX7219.
MAX7219는 8자리 7세그먼트 디스플레이 드라이버입니다. LED 매트릭스에도 널리 쓰이는 칩으로 잘 알려져 있습니다.
편의를 위해 'LedControl' 아두이노 라이브러리를 사용했습니다. 아직 없다면 여기서 다운로드하세요.
2단계: 회로 연결
MAX7219 데이터시트는 여기에서 볼 수 있습니다. 데이터시트를 자세히 다루진 않겠지만, 꽤 흥미로운 칩이니 참고하세요.
아두이노 연결로 넘어가겠습니다.
회로도에서 볼 수 있듯 매우 단순한 구성입니다. RTC(DS1307 브레이크아웃)와 통신용으로 SDA, SCL 사용. MAX7219용으로 4개 출력 추가: DataIn, Clock, ChipSelect, 그리고 초 표시 LED용 OUT 13.
주요 작업은 아두이노에서 수행됩니다: RTC에서 시간을 읽고, 베를린 시계 형식으로 변환한 뒤, 데이터를 7219로 전송합니다.
코드를 보면 네덜란드어(제 모국어)를 변수명으로 사용했습니다. 예: 'vijfuren'은 '5시간'을 세는 변수, 'vijfminuten'과 'vijfminuten1'은 '5분' 출력을 위한 변수입니다.
스케치에서 연도, 월, 일, 요일도 읽지만, 이 스케치에서는 사용하지 않습니다. 나중에 달력 기능을 추가할 수 있습니다.
프리칭(Fritzing) 레이아웃에서는 IC 배선을 생략했습니다. 출력이 많아서 복잡해지기 때문입니다.
핀 배열 정리:
핀 4, 9 → GND
핀 19 → VCC
핀 18 (ISet) → 저항을 거쳐 VCC 연결: 출력 전류 제한용. 처음엔 20k옴을 썼는데 10분쯤 지나니 모든 게 꺼짐. 68k옴으로 바꾸니 24시간 넘게 작동 중. 하지만 적색과 황색 LED 밝기 차이가 나는데, 적색 LED가 실제로는 RGB LED라 그런 것 같습니다. 이건 나중에 해결해야 할 과제입니다.
핀 1 (DataIn) → 아두이노 핀 12
핀 12 (Load) → 아두이노 핀 10
핀 13 (Clk) → 아두이노 핀 11
7219의 입력 핀은 여기까지입니다.
출력 핀들을 보면:
7219는 8개의 7세그 디스플레이를 구동할 수 있습니다.
이 출력들 중 DIG 0~4를 LED '행' 구동용으로 사용합니다. '5분' 행에 LED 11개가 있으므로 DIG 1과 DIG 2로 분담 구동.
나머지 출력: 일반 7세그 디스플레이의 세그먼트 A~G와 DP.
이 경우(LedControl 라이브러리) 이진 워드(바이트 형태, 예: B01010100)를 전송합니다.
여기서 실수를 하나 했습니다. 논리적으로 SEG A가 MSB(최상위 비트), B가 그다음, … DP가 LSB(최하위 비트)일 거라 생각했습니다. 틀렸습니다. 어떤 이유에서인지 IC 설계자는 DP를 MSB로 만들었습니다. 그 논리는 이해하기 어렵네요…
이미 LED를 납땜한 상태라 코드를 수정해야 했습니다.
3단계: LED 배선
아래 회로도에서 LED들이 어떻게 연결되는지 볼 수 있습니다. 처음 3개만 그렸고, 나머지도 동일합니다.
초 표시 LED는 핀 13에 직결.
아래 사진은 '작동 모습'입니다. 17:47을 표시 중.
최하단 행은 브레드보드에 있어 잘 안 보이지만, 실제로 잘 작동합니다 (진짜로요!).
마지막 단계가 남았습니다…
4단계: 마지막 단계 – 코드
#include
#include
const int DS1307 = 0x68; // A5 = SCL, A4 = SDA
const char* days[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
const char* months[] = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"};
byte second = 0;
byte minute = 0;
byte hour = 0;
byte weekday = 0;
byte monthday = 0;
byte month = 0;
byte year = 0;
byte lastMinute = 0;
byte lastSecond = 0;
int Led = 13;
byte nul = B0000000;
byte een = B01000000;
byte twee = B01100000;
byte drie = B01110000;
byte vier = B01111000;
byte vijf = B01111100;
byte zes = B01111110;
byte zeven = B01111111;
byte acht = B11111111;
byte negen = B01000000;
byte tien = B01100000;
byte elf = B01110000;
int a = 0;
int b = 0;
int c = 0;
int vijfMinTwee = 0;
int uitgang = LOW;
/* 핀 12 = DataIn
핀 11 = CLK
핀 10 = LOAD */
LedControl lc = LedControl(12, 11, 10, 1);
unsigned long delaytime = 100;
void setup() {
Wire.begin();
Serial.begin(9600);
pinMode(Led, OUTPUT);
lc.shutdown(0, false);
/* 밝기를 중간 값으로 설정 */
lc.setIntensity(0, 8);
/* 디스플레이 클리어 */
lc.clearDisplay(0);
}
void loop() {
readTime();
digitalWrite(Led, uitgang);
if (second != lastSecond) {
if (uitgang == LOW) uitgang = HIGH;
else uitgang = LOW;
digitalWrite(Led, uitgang);
lastSecond = second;
}
if (minute != lastMinute) {
printTime();
lastMinute = minute;
}
}
byte bcdToDec(byte val) {
return ((val / 16 * 10) + (val % 16));
}
void printTime() {
char buffer[3];
long minuten = minute;
long uren = hour;
int vijfmin = minuten / 5;
int eenminuut = minuten - (vijfmin * 5);
int vijfuren = uren / 5;
int eenuren = uren - (vijfuren * 5);
a = vijfmin;
digitaal();
vijfmin = b;
vijfMinTwee = c;
a = eenminuut;
digitaal();
eenminuut = b;
a = vijfuren;
digitaal();
vijfuren = b;
a = eenuren;
digitaal();
eenuren = b;
lc.setRow(0, 4, vijfuren);
lc.setRow(0, 3, eenuren);
lc.setRow(0, 1, vijfmin);
lc.setRow(0, 2, vijfMinTwee);
lc.setRow(0, 0, eenminuut);
}
void readTime() {
Wire.beginTransmission(DS1307);
Wire.write(byte(0));
Wire.endTransmission();
Wire.requestFrom(DS1307, 7);
second = bcdToDec(Wire.read());
minute = bcdToDec(Wire.read());
hour = bcdToDec(Wire.read());
weekday = bcdToDec(Wire.read());
monthday = bcdToDec(Wire.read());
month = bcdToDec(Wire.read());
year = bcdToDec(Wire.read());
}
void digitaal() {
switch (a) {
case 0: b = nul; c = nul; break;
case 1: b = een; c = nul; break;
case 2: b = twee; c = nul; break;
case 3: b = drie; c = nul; break;
case 4: b = vier; c = nul; break;
case 5: b = vijf; c = nul; break;
case 6: b = zes; c = nul; break;
case 7: b = zeven; c = nul; break;
case 8: b = acht; c = nul; break;
case 9: b = acht; c = negen; break;
case 10: b = acht; c = tien; break;
case 11: b = acht; c = elf; break;
}
}
꽤 직관적이라고 생각합니다.
이 스케치의 또 다른 장점: 네덜란드어로 0부터 11까지 세는 법을 배울 수 있습니다… ;-)