산업 제조
산업용 사물 인터넷 | 산업자재 | 장비 유지 보수 및 수리 | 산업 프로그래밍 |
home  MfgRobots >> 산업 제조 >  >> Manufacturing Technology >> 제조공정

미니 LED 매트릭스 시계 - 아두이노 나노 구동 타임피스

부품 및 소모품

아두이노 나노 R3 1개
Maxim Integrated DS3231M - ±5ppm, I2C RTC 1개
푸시버튼 스위치, 모멘터리 2개
LED 매트릭스 모듈 32x8 (MAX7219 4-in-1) 1개

필요한 도구 및 장비

앱 및 온라인 서비스

프로젝트 소개

"Nick's LED Projects" 페이지에서 8x8 LED 매트릭스 4개로 시간을 표시하는 시계 프로젝트를 발견했습니다. 그는 ICStation 스토어의 매트릭스 모듈 패널 DIY 키트를 사용했습니다.

코드를 최소한만 수정해 MAX7219 도트 매트릭스 모듈 4-in-1 디스플레이(완전 일체형, 훨씬 저렴)로 시계를 만들었습니다. 알리익스프레스에서 구매했습니다.

이 시계는 다양한 기능을 제공합니다:

미니 LED 매트릭스 시계 - 아두이노 나노 구동 타임피스

회로도에서 볼 수 있듯 매트릭스 외에 아두이노 보드, RTC 모듈, 설정용 푸시 버튼 2개만 필요합니다. 라이브러리와 수정된 코드는 아래 링크에서 다운로드 가능합니다.

소스 코드

/*********************************************************************** 
Mini Clock v1.0, Jul 2014 by Nick Hall
Distributed under the terms of the GPL.
For help on how to build the clock see my blog: https://123led.wordpress.com/
Tested on IDE v1.6.5
***********************************************************************/

// 라이브러리 포함:
#include "LedControl.h"
#include  // 폰트 라이브러리
#include  // DS1307 클럭
#include "RTClib.h" // DS1307 클럭
#include  // Alexander Brevig의 버튼 라이브러리

// LED 매트릭스 설정
// 핀 12: 디스플레이 DataIn
// 핀 11: 디스플레이 CLK
// 핀 10: 디스플레이 LOAD
LedControl lc = LedControl(12, 11, 10, 4); // 3핀 설정 후 4개 디스플레이 지정 (최대 8개)

// 전역 변수
byte intensity = 7;        // 기본 밝기 (0-15)
byte clock_mode = 0;       // 기본 시계 모드. 0 = basic_mode
bool random_mode = 0;      // 랜덤 모드 - 몇 시간마다 표시 타입 변경. 0 = 끔
byte old_mode = clock_mode; // 이전 시계 모드 저장 (날짜 표시 후 복귀용)
bool ampm = 0;             // 12/24시간. 0 = 24시간, 1 = 12시간
byte change_mode_time = 0; // 랜덤 모드 시 다음 모드 변경 시각 저장
unsigned long delaytime = 500; // 디스플레이 업데이트 간 대기
int rtc[7];                // RTC 출력값 저장
char days[7][4] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; // 요일 약어 (DS1307은 1-7 출력, 1=일요일)
char daysfull[7][9] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
char suffix[4][3] = { "st", "nd", "rd", "th" }; // 날짜 접미사 (1st, 2nd...)

// 상수 정의
#define NUM_DISPLAY_MODES 3       // 디스플레이 모드 수 (0부터 카운트)
#define NUM_SETTINGS_MODES 4      // 설정 모드 수 (0부터 카운트)
#define SLIDE_DELAY 20            // 슬라이드 모드에서 문자당 애니메이션 지연 (ms). 높일수록 느림
#define cls clear_display         // 화면 지우기 매크로

RTC_DS1307 ds1307; // RTC 객체 생성
Button buttonA = Button(2, BUTTON_PULLUP); // 버튼 A 설정 (핀 2, 풀업)
Button buttonB = Button(3, BUTTON_PULLUP); // 버튼 B 설정 (핀 3, 풀업)

void setup() {
  digitalWrite(2, HIGH); // 핀 2 풀업 활성화
  digitalWrite(3, HIGH); // 핀 3 풀업 활성화
  digitalWrite(4, HIGH); // 핀 4 풀업 활성화
  Serial.begin(9600);
  
  // 4개 매트릭스 패널 초기화
  int devices = lc.getDeviceCount();
  for (int address = 0; address < devices; address++) {
    lc.shutdown(address, false);        // 절전 모드 해제
    lc.setIntensity(address, intensity); // 밝기 설정
    lc.clearDisplay(address);           // 화면 지우기
  }
  
  // DS1307 RTC 설정
  #ifdef AVR
    Wire.begin();
  #else
    Wire1.begin(); // 실드 I2C 핀이 아두이노 대체 I2C 버스에 연결된 경우
  #endif
  ds1307.begin();
  
  if (!ds1307.isrunning()) {
    Serial.println("RTC is NOT running!");
    ds1307.adjust(DateTime(__DATE__, __TIME__)); // 컴파일 시점 시간으로 RTC 설정
  }
  
  // 소프트웨어 버전 & 인사 메시지 표시
  printver();
  
  // 빨간 LED 켜기
  digitalWrite(13, HIGH);
}

void loop() {
  // clock_mode에 따라 시계 실행 (기본값은 코드 상단에서 설정)
  switch (clock_mode) {
    case 0: basic_mode(); break;
    case 1: small_mode(); break;
    case 2: slide(); break;
    case 3: word_clock(); break;
    case 4: setup_menu(); break;
  }
}

// 디스플레이에 점 그리기
void plot(byte x, byte y, byte val) {
  byte address;
  if (x >= 0 && x <= 7) { address = 3; }
  if (x >= 8 && x <= 15) { address = 2; x = x - 8; }
  if (x >= 16 && x <= 23) { address = 1; x = x - 16; }
  if (x >= 24 && x <= 31) { address = 0; x = x - 24; }
  if (val == 1) { lc.setLed(address, y, x, true); }
  else { lc.setLed(address, y, x, false); }
}

// 화면 지우기
void clear_display() {
  for (byte address = 0; address < 4; address++) {
    lc.clearDisplay(address);
  }
}

// 페이드 아웃 효과
void fade_down() {
  for (byte i = intensity; i > 0; i--) {
    for (byte address = 0; address < 4; address++) {
      lc.setIntensity(address, i);
    }
    delay(30); // 페이드 속도 조절
  }
  clear_display();
  for (byte address = 0; address < 4; address++) {
    lc.setIntensity(address, intensity);
  }
}

// 전원 온 LED 테스트 & 소프트웨어 버전 표시
void printver() {
  byte i = 0;
  char ver_a[9] = "Vers 1.0";
  char ver_b[9] = " Hello! ";
  
  // 전체 LED 테스트
  for (byte x = 0; x <= 31; x++) {
    for (byte y = 0; y <= 7; y++) {
      plot(x, y, 1);
    }
  }
  delay(500);
  fade_down();
  
  while (ver_a[i]) { puttinychar((i * 4), 1, ver_a[i]); delay(35); i++; }
  delay(700);
  fade_down();
  i = 0;
  while (ver_b[i]) { puttinychar((i * 4), 1, ver_b[i]); delay(35); i++; }
  delay(700);
  fade_down();
}

// 3x5 작은 문자 출력
void puttinychar(byte x, byte y, char c) {
  byte dots;
  if (c >= 'A' && c <= 'Z' || (c >= 'a' && c <= 'z')) { c &= 0x1F; }
  else if (c >= '0' && c <= '9') { c = (c - '0') + 32; }
  else if (c == ' ') { c = 0; }
  else if (c == '.') { c = 27; }
  else if (c == ':') { c = 28; }
  else if (c == '\'') { c = 29; }
  else if (c == '!') { c = 30; }
  else if (c == '?') { c = 31; }
  
  for (byte col = 0; col < 3; col++) {
    dots = pgm_read_byte_near(&mytinyfont[c][col]);
    for (char row = 0; row < 5; row++) {
      if (dots & (16 >> row)) plot(x + col, y + row, 1);
      else plot(x + col, y + row, 0);
    }
  }
}

// 5x7 일반 문자 출력
void putnormalchar(byte x, byte y, char c) {
  byte dots;
  if (c >= 'A' && c <= 'Z') { c &= 0x1F; }
  else if (c >= 'a' && c <= 'z') { c = (c - 'a') + 41; }
  else if (c >= '0' && c <= '9') { c = (c - '0') + 31; }
  else if (c == ' ') { c = 0; }
  else if (c == '.') { c = 27; }
  else if (c == '\'') { c = 28; }
  else if (c == ':') { c = 29; }
  else if (c == '>') { c = 30; }
  else if (c >= -80 && c <= -67) { c *= -1; }
  
  for (char col = 0; col < 5; col++) {
    dots = pgm_read_byte_near(&myfont[c][col]);
    for (char row = 0; row < 7; row++) {
      if (dots & (64 >> row)) plot(x + col, y + row, 1);
      else plot(x + col, y + row, 0);
    }
  }
}

// small_mode: 작은 3x5 문자로 초 단위 표시
void small_mode() {
  char textchar[8];
  byte mins = 100;
  byte secs = rtc[0];
  byte old_secs = secs;
  cls();
  
  while (run_mode()) {
    get_time();
    if (buttonA.uniquePress()) { switch_mode(); return; }
    if (buttonB.uniquePress()) { display_date(); return; }
    
    secs = rtc[0];
    if (secs != old_secs) {
      char buffer[3];
      itoa(secs, buffer, 10);
      if (secs < 10) { buffer[1] = buffer[0]; buffer[0] = '0'; }
      puttinychar(20, 1, ':'); // 초 콜론
      puttinychar(24, 1, buffer[0]);
      puttinychar(28, 1, buffer[1]);
      old_secs = secs;
    }
    
    if (mins != rtc[1]) {
      mins = rtc[1];
      byte hours = rtc[2];
      if (hours > 12) { hours = hours - ampm * 12; }
      if (hours < 1) { hours = hours + ampm * 12; }
      
      char buffer[3];
      itoa(hours, buffer, 10);
      if (hours < 10) {
        buffer[1] = buffer[0];
        if (ampm) { buffer[0] = ' '; } else { buffer[0] = '0'; }
      }
      textchar[0] = buffer[0]; textchar[1] = buffer[1]; textchar[2] = ':';
      itoa(mins, buffer, 10);
      if (mins < 10) { buffer[1] = buffer[0]; buffer[0] = '0'; }
      textchar[3] = buffer[0]; textchar[4] = buffer[1];
      textchar[5] = ':';
      buffer[3];
      secs = rtc[0];
      itoa(secs, buffer, 10);
      if (secs < 10) { buffer[1] = buffer[0]; buffer[0] = '0'; }
      textchar[6] = buffer[0]; textchar[7] = buffer[1];
      
      byte x = 0, y = 0;
      for (byte x = 0; x < 6; x++) { puttinychar(x * 4, 1, textchar[x]); }
    }
    delay(50);
  }
  fade_down();
}

// basic_mode: 5x7 큰 문자로 시간 표시
void basic_mode() {
  cls();
  char buffer[3];
  byte offset = 0;
  byte x, y;
  byte hours = rtc[2];
  if (hours > 12) { hours = hours - ampm * 12; }
  if (hours < 1) { hours = hours + ampm * 12; }
  if (ampm && hours < 10) { offset = 2; }
  
  byte secs = 100;
  byte mins = 100;
  int count = 0;
  
  while (run_mode()) {
    get_time();
    if (buttonA.uniquePress()) { switch_mode(); return; }
    if (buttonB.uniquePress()) { display_date(); return; }
    
    if (secs != rtc[0]) {
      secs = rtc[0];
      plot(15 - offset, 2, 1); // 콜론 위쪽 점
      plot(15 - offset, 5, 1); // 콜론 아래쪽 점
      count = 400;
    }
    if (count == 0) {
      plot(15 - offset, 2, 0);
      plot(15 - offset, 5, 0);
    } else { count--; }
    
    if (mins != rtc[1]) {
      mins = rtc[1];
      hours = rtc[2];
      if (hours > 12) { hours = hours - ampm * 12; }
      if (hours < 1) { hours = hours + ampm * 12; }
      itoa(hours, buffer, 10);
      if (hours < 10) {
        buffer[1] = buffer[0];
        buffer[0] = '0';
      }
      if (ampm && hours < 10) {
        offset = 2;
        if (hours == 1 && mins == 0) { cls(); }
      } else {
        offset = 0;
        if (hours == 10 && mins == 0) { cls(); }
        putnormalchar(1, 0, buffer[0]);
      }
      putnormalchar(7 - offset, 0, buffer[1]);
      
      itoa(mins, buffer, 10);
      if (mins < 10) { buffer[1] = buffer[0]; buffer[0] = '0'; }
      putnormalchar(19 - offset, 0, buffer[0]);
      putnormalchar(25 - offset, 0, buffer[1]);
    }
  }
  fade_down();
}

// slide 모드: 슬라이드 효과로 시간 표시
void slide() {
  byte digits_old[4] = {99, 99, 99, 99};
  byte digits_new[4];
  byte digits_x_pos[4] = {25, 19, 7, 1};
  char old_char[2], new_char[2];
  
  cls();
  putnormalchar(13, 0, ':');
  byte old_secs = rtc[0];
  
  while (run_mode()) {
    get_time();
    if (buttonA.uniquePress()) { switch_mode(); return; }
    if (buttonB.uniquePress()) { display_date(); return; }
    
    if (rtc[0] != old_secs) {
      old_secs = rtc[0];
      byte hours = rtc[2];
      if (hours > 12) { hours = hours - ampm * 12; }
      if (hours < 1) { hours = hours + ampm * 12; }
      
      digits_new[0] = (rtc[1] % 10);       // 분 1의 자리
      digits_new[1] = ((rtc[1] / 10) % 10); // 분 10의 자리
      digits_new[2] = (hours % 10);         // 시 1의 자리
      digits_new[3] = ((hours / 10) % 10);  // 시 10의 자리
      
      for (byte i = 0; i <= 3; i++) {
        if (digits_old[i] != digits_new[i]) {
          for (byte seq = 0; seq <= 8; seq++) {
            itoa(digits_old[i], old_char, 10);
            itoa(digits_new[i], new_char, 10);
            if (ampm && i == 3) {
              if (digits_new[3] == 0) new_char[0] = ' ';
              if (digits_old[3] == 0) old_char[0] = ' ';
            }
            slideanim(digits_x_pos[i], 0, seq, old_char[0], new_char[0]);
            delay(SLIDE_DELAY);
          }
        }
      }
      
      for (byte i = 0; i <= 3; i++) { digits_old[i] = digits_new[i]; }
    }
  }
  fade_down();
}

// 슬라이드 애니메이션 프레임 그리기
void slideanim(byte x, byte y, byte sequence, char current_c, char new_c) {
  if (sequence < 7) {
    byte dots;
    if (current_c >= 'A' && current_c <= 'Z') { current_c &= 0x1F; }
    else if (current_c >= 'a' && current_c <= 'z') { current_c = (current_c - 'a') + 41; }
    else if (current_c >= '0' && current_c <= '9') { current_c = (current_c - '0') + 31; }
    else if (current_c == ' ') { current_c = 0; }
    else if (current_c == '.') { current_c = 27; }
    else if (current_c == '\'') { current_c = 28; }
    else if (current_c == ':') { current_c = 29; }
    else if (current_c == '>') { current_c = 30; }
    
    byte curr_char_row_max = 7 - sequence;
    byte start_y = sequence;
    for (byte curr_char_row = 0; curr_char_row <= curr_char_row_max; curr_char_row++) {
      for (byte col = 0; col < 5; col++) {
        dots = pgm_read_byte_near(&myfont[current_c][col]);
        if (dots & (64 >> curr_char_row)) plot(x + col, y + start_y, 1);
        else plot(x + col, y + start_y, 0);
      }
      start_y++;
    }
  }
  
  if (sequence >= 1 && sequence <= 8) {
    for (byte col = 0; col < 5; col++) {
      plot(x + col, y + (sequence - 1), 0);
    }
  }
  
  if (sequence >= 2) {
    byte dots;
    if (new_c >= 'A' && new_c <= 'Z') { new_c &= 0x1F; }
    else if (new_c >= 'a' && new_c <= 'z') { new_c = (new_c - 'a') + 41; }
    else if (new_c >= '0' && new_c <= '9') { new_c = (new_c - '0') + 31; }
    else if (new_c == ' ') { new_c = 0; }
    else if (new_c == '.') { new_c = 27; }
    else if (new_c == '\'') { new_c = 28; }
    else if (new_c == ':') { new_c = 29; }
    else if (new_c == '>') { new_c = 30; }
    
    byte newcharrowmin = 6 - (sequence - 2);
    byte start_y = 0;
    for (byte newcharrow = newcharrowmin; newcharrow <= 6; newcharrow++) {
      for (byte col = 0; col < 5; col++) {
        dots = pgm_read_byte_near(&myfont[new_c][col]);
        if (dots & (64 >> newcharrow)) plot(x + col, y + start_y, 1);
        else plot(x + col, y + start_y, 0);
      }
      start_y++;
    }
  }
}

// word_clock: 단어로 시간 표시 (예: "ten past twelve")
void word_clock() {
  cls();
  char numbers[19][10] = { "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen" };
  char numberstens[5][7] = { "ten", "twenty", "thirty", "forty", "fifty" };
  
  char str_a[8], str_b[8], str_c[8];
  byte hours = rtc[2];
  if (hours > 12) { hours = hours - ampm * 12; }
  if (hours < 1) { hours = hours + ampm * 12; }
  
  get_time();
  byte old_mins = 100;
  byte mins;
  
  while (run_mode()) {
    if (buttonA.uniquePress()) { switch_mode(); return; }
    if (buttonB.uniquePress()) { display_date(); }
    get_time();
    mins = rtc[1];
    
    if (mins != old_mins) {
      old_mins = mins;
      mins = rtc[1];
      hours = rtc[2];
      if (hours > 12) { hours = hours - 12; }
      if (hours == 0) { hours = 12; }
      
      int minsdigit = rtc[1] % 10;
      byte minsdigitten = (rtc[1] / 10) % 10;
      
      if (mins < 10) {
        strcpy(str_a, numbers[minsdigit - 1]);
        strcpy(str_b, "PAST");
        strcpy(str_c, numbers[hours - 1]);
      } else if (mins == 10) {
        strcpy(str_a, numbers[9]);
        strcpy(str_b, " PAST");
        strcpy(str_c, numbers[hours - 1]);
      } else if (minsdigitten != 0 && minsdigit != 0) {
        strcpy(str_a, numbers[hours - 1]);
        if (mins <= 19) { strcpy(str_b, numbers[mins - 1]); }
        else { strcpy(str_b, numberstens[minsdigitten - 1]); strcpy(str_c, numbers[minsdigit - 1]); }
      } else if (minsdigitten != 0 && minsdigit == 0) {
        strcpy(str_a, numbers[hours - 1]);
        strcpy(str_b, numberstens[minsdigitten - 1]);
        strcpy(str_c, "");
      } else if (minsdigitten == 0 && minsdigit == 0) {
        strcpy(str_a, numbers[hours - 1]);
        strcpy(str_b, "O'CLOCK");
        strcpy(str_c, "");
      }
      
      // 첫 번째 줄 출력
      byte len = 0; while (str_a[len]) len++;
      byte offset_top = (31 - ((len - 1) * 4)) / 2;
      byte i = 0; while (str_a[i]) { puttinychar((i * 4) + offset_top, 1, str_a[i]); i++; }
      
      int counter = 1000;
      while (counter > 0) {
        if (buttonA.uniquePress()) { switch_mode(); return; }
        if (buttonB.uniquePress()) { display_date(); }
        delay(1); counter--;
      }
      fade_down();
      
      // 두 번째 줄 출력
      len = 0; while (str_b[len]) len++;
      offset_top = (31 - ((len - 1) * 4)) / 2;
      i = 0; while (str_b[i]) { puttinychar((i * 4) + offset_top, 1, str_b[i]); i++; }
      
      counter = 1000;
      while (counter > 0) {
        if (buttonA.uniquePress()) { switch_mode(); return; }
        if (buttonB.uniquePress()) { display_date(); }
        delay(1); counter--;
      }
      fade_down();
      
      // 세 번째 줄 출력 (있는 경우)
      len = 0; while (str_c[len]) len++;
      if (len > 0) {
        offset_top = (31 - ((len - 1) * 4)) / 2;
        i = 0; while (str_c[i]) { puttinychar((i * 4) + offset_top, 1, str_c[i]); i++; }
        
        counter = 1000;
        while (counter > 0) {
          if (buttonA.uniquePress()) { switch_mode(); return; }
          if (buttonB.uniquePress()) { display_date(); }
          delay(1); counter--;
        }
        fade_down();
      }
      
      // 공백 화면 유지하며 버튼 체크
      counter = 1000;
      while (counter > 0) {
        if (buttonA.uniquePress()) { switch_mode(); return; }
        if (buttonB.uniquePress()) { display_date(); }
        delay(1); counter--;
      }
    }
  }
  fade_down();
}

// display_date: 요일, 날짜, 월 표시 (깜빡이는 커서 효과)
void display_date() {
  cls();
  byte dow = rtc[3]; // 요일 0=일요일
  byte date = rtc[4];
  byte month = rtc[5] - 1;
  
  char monthnames[12][9] = { "January", "February", "March", "April", "May", "June", "July", "August", "Sept", "October", "November", "December" };
  
  // 요일 이름 출력 (가운데 정렬)
  byte len = 0; while (daysfull[dow][len]) len++;
  byte offset = (31 - ((len - 1) * 4)) / 2;
  int i = 0; while (daysfull[dow][i]) { puttinychar((i * 4) + offset, 1, daysfull[dow][i]); i++; }
  delay(1000);
  fade_down();
  cls();
  
  // 날짜 숫자 출력
  char buffer[3];
  itoa(date, buffer, 10);
  offset = 10;
  byte s = 3;
  if (date == 1 || date == 21 || date == 31) { s = 0; }
  else if (date == 2 || date == 22) { s = 1; }
  else if (date == 3 || date == 23) { s = 2; }
  
  puttinychar(0 + offset, 1, buffer[0]);
  byte suffixposx = 4;
  if (date > 9) { suffixposx = 8; puttinychar(4 + offset, 1, buffer[1]); offset = 8; }
  puttinychar(suffixposx + offset, 1, suffix[s][0]);
  puttinychar(suffixposx + 4 + offset, 1, suffix[s][1]);
  delay(1000);
  fade_down();
  
  // 월 이름 출력
  len = 0; while (monthnames[month][len]) len++;
  offset = (31 - ((len - 1) * 4)) / 2;
  i = 0; while (monthnames[month][i]) { puttinychar((i * 4) + offset, 1, monthnames[month][i]); i++; }
  delay(1000);
  fade_down();
}

// 시계 모드 변경 메뉴
void switch_mode() {
  old_mode = clock_mode;
  char* modes[] = { "Basic", "Small", "Slide", "Words", "Setup" };
  byte next_clock_mode;
  byte firstrun = 1;
  
  for (int count = 0; count < 35; count++) {
    if (buttonA.uniquePress() || firstrun == 1) {
      count = 0;
      cls();
      if (firstrun == 0) { clock_mode++; }
      if (clock_mode > NUM_DISPLAY_MODES + 1) { clock_mode = 0; }
      
      char str_top[9];
      strcpy(str_top, modes[clock_mode]);
      next_clock_mode = clock_mode + 1;
      if (next_clock_mode > NUM_DISPLAY_MODES + 1) { next_clock_mode = 0; }
      
      byte i = 0; while (str_top[i]) { putnormalchar(i * 6, 0, str_top[i]); i++; }
      firstrun = 0;
    }
    delay(50);
  }
}

// 현재 모드 실행 여부 확인 (랜덤 모드 지원)
byte run_mode() {
  if (random_mode) {
    if (change_mode_time == rtc[2]) {
      set_next_random();
      return 0;
    }
  }
  return 1;
}

// 랜덤 모드 시 다음 변경 시각 설정
void set_next_random() {
  get_time();
  change_mode_time = rtc[2] + random(1, 5);
  if (change_mode_time > 23) { change_mode_time = random(1, 4); }
  clock_mode = random(0, NUM_DISPLAY_MODES + 1);
}

// 설정 메뉴
void setup_menu() {
  char* set_modes[] = { "Rndom", "24 Hr", "Set", "Brght", "Exit" };
  if (ampm == 0) { set_modes[1] = "12 Hr"; }
  
  byte setting_mode = 0, next_setting_mode, firstrun = 1;
  
  for (int count = 0; count < 35; count++) {
    if (buttonA.uniquePress() || firstrun == 1) {
      count = 0;
      cls();
      if (firstrun == 0) { setting_mode++; }
      if (setting_mode > NUM_SETTINGS_MODES) { setting_mode = 0; }
      
      char str_top[9];
      strcpy(str_top, set_modes[setting_mode]);
      next_setting_mode = setting_mode + 1;
      if (next_setting_mode > NUM_SETTINGS_MODES) { next_setting_mode = 0; }
      
      byte i = 0; while (str_top[i]) { putnormalchar(i * 6, 0, str_top[i]); i++; }
      firstrun = 0;
    }
    delay(50);
  }
  
  switch (setting_mode) {
    case 0: set_random(); break;
    case 1: set_ampm(); break;
    case 2: set_time(); break;
    case 3: set_intensity(); break;
    case 4: break; // 종료
  }
  clock_mode = old_mode;
}

// 랜덤 모드 토글
void set_random() {
  cls();
  char text_a[9] = "Off";
  char text_b[9] = "On";
  byte i = 0;
  if (random_mode) {
    random_mode = 0;
    while (text_a[i]) { putnormalchar((i * 6), 0, text_a[i]); i++; }
  } else {
    random_mode = 1;
    set_next_random();
    while (text_b[i]) { putnormalchar((i * 6), 0, text_b[i]); i++; }
  }
  delay(1500);
}

// 12/24시간 토글
void set_ampm() {
  ampm = (ampm ^ 1);
  cls();
}

// 밝기 설정
void set_intensity() {
  cls();
  byte i = 0;
  char text[7] = "Bright";
  while (text[i]) { puttinychar((i * 4) + 4, 0, text[i]); i++; }
  
  while (!buttonA.uniquePress()) {
    levelbar(0, 6, (intensity * 2) + 2, 2);
    while (buttonB.isPressed()) {
      if (intensity == 15) { intensity = 0; cls(); }
      else { intensity++; }
      i = 0; while (text[i]) { puttinychar((i * 4) + 4, 0, text[i]); i++; }
      levelbar(0, 6, (intensity * 2) + 2, 2);
      for (byte address = 0; address < 4; address++) { lc.setIntensity(address, intensity); }
      delay(150);
    }
  }
}

// 수평 바 표시 (밝기 레벨 시각화)
void levelbar(byte xpos, byte ypos, byte xbar, byte ybar) {
  for (byte x = 0; x < xbar; x++) {
    for (byte y = 0; y <= ybar; y++) { plot(x + xpos, y + ypos, 1); }
  }
}

// 시간/날짜 설정 루틴
void set_time() {
  cls();
  get_time();
  byte set_min = rtc[1];
  byte set_hr = rtc[2];
  byte set_date = rtc[4];
  byte set_mnth = rtc[5];
  int set_yr = rtc[6];
  
  set_date = set_value(2, set_date, 1, 31);
  set_mnth = set_value(3, set_mnth, 1, 12);
  set_yr = set_value(4, set_yr, 2013, 2099);
  set_hr = set_value(1, set_hr, 0, 23);
  set_min = set_value(0, set_min, 0, 59);
  
  ds1307.adjust(DateTime(set_yr, set_mnth, set_date, set_hr, set_min));
  cls();
}

// 값 설정 헬퍼 (분, 시, 일, 월, 년)
int set_value(byte message, int current_value, int reset_value, int rollover_limit) {
  cls();
  char messages[6][17] = { "Set Mins", "Set Hour", "Set Day", "Set Mnth", "Set Year" };
  
  byte i = 0; while (messages[message][i]) { puttinychar(i * 4, 1, messages[message][i]); i++; }
  delay(2000);
  cls();
  
  char buffer[5] = " ";
  itoa(current_value, buffer, 10);
  puttinychar(0, 1, buffer[0]);
  puttinychar(4, 1, buffer[1]);
  puttinychar(8, 1, buffer[2]);
  puttinychar(12, 1, buffer[3]);
  delay(300);
  
  while (!buttonA.uniquePress()) {
    while (buttonB.isPressed()) {
      if (current_value < rollover_limit) { current_value++; }
      else { current_value = reset_value; }
      itoa(current_value, buffer, 10);
      puttinychar(0, 1, buffer[0]);
      puttinychar(4, 1, buffer[1]);
      puttinychar(8, 1, buffer[2]);
      puttinychar(12, 1, buffer[3]);
      delay(150);
    }
  }
  return current_value;
}

// RTC에서 시간 읽기
void get_time() {
  DateTime now = ds1307.now();
  rtc[6] = now.year();
  rtc[5] = now.month();
  rtc[4] = now.day();
  rtc[3] = now.dayOfWeek(); // 0-6, 0=일요일
  rtc[2] = now.hour();
  rtc[1] = now.minute();
  rtc[0] = now.second();
  
  // 디버깅용 시리얼 출력 (주석 해제 시)
  /* Serial.print(rtc[2]); Serial.print(":"); Serial.print(rtc[1]); Serial.print(":"); Serial.println(rtc[0]); */
}

필요 라이브러리

라이브러리는 코드 섹션에서 다운로드 가능합니다. (미리보기 불가, 다운로드만 가능)

회로도

미니 LED 매트릭스 시계 - 아두이노 나노 구동 타임피스

제조공정

  1. 555 타이머 & 4017 카운터로 LED 시퀀서 제작: 주파수 분할 & 디바운스
  2. 쿠쿠 시계: 역사, 장인 정신, 현대적 진화
  3. MATLAB 행렬 기초 마스터: 생성, 참조, 데이터 조작
  4. 아두이노 미니 보스 배틀 게임: 레트로풍 LCD 게임 프로젝트
  5. 아두이노로 만드는 대형 LED 마스터 시계 (알람 기능 포함, 12m 거리에서도 식별 가능)
  6. 아두이노 우노로 베를린 시계 만들기 – DIY LED 매트릭스 시계
  7. 아날로그 스타일 LED POV 시계 - 아두이노 나노 DIY 프로젝트
  8. PIR 모션 센서 연동 인터랙티브 LED 매트릭스 도어 디스플레이 - 아두이노 홀리데이 인사
  9. 소리 감지 8-LED 라이트 디스플레이 - 아두이노 초보자 프로젝트
  10. 스마트 아두이노 4족 보행 로봇 제작: 단계별 완벽 가이드