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

아두이노 8×8 LED 매트릭스 플립 시계 – DIY 실시간 시계 프로젝트

부품 및 소모품

부품 수량
아두이노 나노 R3 (Arduino Nano R3) 1
Maxim Integrated DS3231M - ±5ppm, I2C 실시간 시계 (RTC) 1
MAX7219 드라이버 내장 8×8 LED 매트릭스 1
RGB 확산형 공통 캐소드 LED 1
220옴 저항 2
푸시버튼 내장 로터리 인코더 1

필요 도구 및 장비

앱 및 온라인 서비스

프로젝트 소개

이 8×8 LED 매트릭스 모듈 시계는 Adrian Jones가 개발한 것을 기반으로 제작했습니다. 시간은 좌우로 스크롤하며 표시되거나, 소프트웨어 제어 하에 상하로 플립되며 표시됩니다. 각 시간 숫자가 오른쪽에서부터 차례로 슬라이드되어 중앙에 도달하면 잠시 멈추고 약간 밝아졌다가 왼쪽으로 스크롤하면서 다음 숫자가 나타나는 방식입니다. 이 주기가 짧은 지연 후 반복됩니다.

상하 플립(vertical flipping) 모드로 변경하려면 코드에서 다음 부분을 수정하세요:

static boolean top2bottom = false;

를

static boolean top2bottom = true;

로 변경하면 됩니다.

코드 주석에 나와 있듯이 스크롤 속도, 문자 간 공백 줄 수, 12/24시간 표시, 문자 간 지연 시간, 밝기 등 다양한 파라미터를 조정할 수 있습니다.

로터리 인코더를 사용해 시간을 쉽게 설정할 수 있습니다. 투컬러 다이오드가 설정 모드(분, 시, 일반)를 표시합니다. 시계를 두 개의 기본 부품으로 구성된 상자에 넣어 내부 접근이 매우 편리하고 실용적입니다.

아두이노 8×8 LED 매트릭스 플립 시계 – DIY 실시간 시계 프로젝트

코드

code (아두이노)

//*****************************************************************************************//
// MATRIX CLOCK
// Adrian Jones, March 2014
//
// - 좌우 스크롤 및 상하 스크롤 지원
//
//*****************************************************************************************//
#include <Wire.h> // I2C-WIRE 라이브러리
#include <RTClib.h> // RTC 라이브러리

// MAX7219 레지스터 및 제어 핀 정의
#define max7219_reg_noop 0x00
#define max7219_reg_digit0 0x01
#define max7219_reg_digit1 0x02
#define max7219_reg_digit2 0x03
#define max7219_reg_digit3 0x04
#define max7219_reg_digit4 0x05
#define max7219_reg_digit5 0x06
#define max7219_reg_digit6 0x07
#define max7219_reg_digit7 0x08
#define max7219_reg_decodeMode 0x09
#define max7219_reg_intensity 0x0a
#define max7219_reg_scanLimit 0x0b
#define max7219_reg_shutdown 0x0c
#define max7219_reg_displayTest 0x0f

#define dataIn 12 // DIN
#define load 10   // CS
#define clock 11  // CLK

char alphanum[][8] = {
  {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 공백
  {0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x10}, // !
  {0x00,0x28,0x28,0x28,0x00,0x00,0x00,0x00}, // "
  {0x00,0x28,0x7C,0x28,0x7C,0x28,0x00,0x00}, // #
  {0x10,0x38,0x50,0x38,0x14,0x54,0x38,0x10}, // $
  {0x41,0xA2,0x44,0x08,0x10,0x22,0x45,0x82}, // %
  {0x38,0x44,0x44,0x38,0x50,0x4A,0x44,0x3A}, // &
  {0x08,0x08,0x00,0x00,0x00,0x00,0x00,0x00}, // '
  {0x30,0x40,0x80,0x80,0x80,0x80,0x40,0x30}, // (
  {0xC0,0x20,0x10,0x10,0x10,0x10,0x20,0xC0}, // )
  {0x28,0x10,0xAA,0x54,0xAA,0x10,0x28,0x00}, // *
  {0x00,0x10,0x10,0x10,0xFE,0x10,0x10,0x10}, // +
  {0x00,0x00,0x00,0x00,0x00,0x08,0x08,0x10}, // ,
  {0x00,0x00,0x00,0x00,0x7E,0x00,0x00,0x00}, // -
  {0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18}, // .
  {0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80}, // /
  {0x7E,0xC1,0xA1,0x91,0x89,0x85,0x83,0x7E}, // 0
  {0x10,0x30,0x10,0x10,0x10,0x10,0x10,0x7C}, // 1
  {0x38,0x44,0x82,0x04,0x18,0x20,0x40,0xFE}, // 2
  {0x7C,0x82,0x02,0x3C,0x02,0x02,0x82,0x7C}, // 3
  {0x08,0x18,0x28,0x48,0xFE,0x08,0x08,0x08}, // 4
  {0xFE,0x80,0xF8,0x04,0x02,0x82,0x44,0x38}, // 5
  {0x38,0x44,0x80,0xB8,0xC4,0x82,0x44,0x38}, // 6
  {0xFE,0x02,0x04,0x08,0x10,0x20,0x20,0x20}, // 7
  {0x7C,0x82,0x82,0x7C,0x82,0x82,0x82,0x7C}, // 8
  {0x7C,0x82,0x82,0x7E,0x02,0x82,0x44,0x38}, // 9
  {0x00,0x00,0x18,0x18,0x00,0x18,0x18,0x00}, // :
  {0x00,0x00,0x18,0x18,0x00,0x18,0x18,0x30}, // ;
  {0x00,0x10,0x20,0x40,0x80,0x40,0x20,0x10}, // <
  {0x00,0x00,0x00,0x7E,0x00,0x7E,0x00,0x00}, // =
  {0x00,0x80,0x40,0x20,0x10,0x20,0x40,0x80}, // >
  {0x70,0x88,0x88,0x10,0x20,0x20,0x00,0x20}, // ?
  {0x7E,0x81,0x99,0xA1,0xA1,0x9E,0x80,0x7E}, // @
  {0x3C,0x42,0x81,0x81,0xFF,0x81,0x81,0x81}, // A
  {0xFC,0x82,0x81,0xFE,0x81,0x81,0x82,0xFC}, // B
  {0x3C,0x42,0x81,0x80,0x80,0x81,0x42,0x3C}, // C
  {0xFC,0x82,0x81,0x81,0x81,0x81,0x82,0xFC}, // D
  {0xFE,0x80,0x80,0xFC,0x80,0x80,0x80,0xFE}, // E
  {0xFE,0x80,0x80,0xFC,0x80,0x80,0x80,0x80}, // F
  {0x3C,0x42,0x81,0x80,0x87,0x81,0x42,0x3C}, // G
  {0x81,0x81,0x81,0xFF,0x81,0x81,0x81,0x81}, // H
  {0xFE,0x10,0x10,0x10,0x10,0x10,0x10,0xFE}, // I
  {0xFF,0x08,0x08,0x08,0x08,0x88,0x88,0x70}, // J
  {0x88,0x90,0xA0,0xC0,0xA0,0x90,0x88,0x84}, // K
  {0x80,0x80,0x80,0x80,0x80,0x80,0x80,0xFE}, // L
  {0x81,0xC3,0xA5,0x99,0x81,0x81,0x81,0x81}, // M
  {0x81,0xC1,0xA1,0x91,0x89,0x85,0x83,0x81}, // N
  {0x3C,0x42,0x81,0x81,0x81,0x81,0x42,0x3C}, // O
  {0xFC,0x82,0x81,0x82,0xFC,0x80,0x80,0x80}, // P
  {0x3C,0x42,0x81,0x81,0x81,0x85,0x42,0x3D}, // Q
  {0xFC,0x82,0x81,0x82,0xFC,0x84,0x82,0x81}, // R
  {0x3C,0x42,0x81,0x40,0x3E,0x81,0x42,0x3C}, // S
  {0xFE,0x10,0x10,0x10,0x10,0x10,0x10,0x10}, // T
  {0x82,0x82,0x82,0x82,0x82,0x82,0x44,0x38}, // U
  {0x82,0x82,0x82,0x82,0x82,0x44,0x28,0x10}, // V
  {0x81,0x81,0x81,0x81,0x99,0xA5,0xC3,0x81}, // W
  {0x81,0x42,0x24,0x18,0x18,0x24,0x42,0x81}, // X
  {0x82,0x44,0x28,0x10,0x10,0x10,0x10,0x10}, // Y
  {0xFF,0x02,0x04,0x08,0x10,0x20,0x40,0xFF}, // Z
  {0xE0,0x80,0x80,0x80,0x80,0x80,0x80,0xE0}, // [
  {0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01}, // 백슬래시
  {0x07,0x01,0x01,0x01,0x01,0x01,0x01,0x07}, // ]
  {0xE0,0xA0,0xE0,0xA0,0xAA,0x15,0x15,0x11}, // am ('^'로 코딩)
  {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7E}, // _
  {0x10,0x08,0x00,0x00,0x00,0x00,0x00,0x00}, // '
  {0x00,0x00,0x38,0x04,0x3C,0x44,0x48,0x34}, // a
  {0x00,0x40,0x40,0x40,0x78,0x44,0x44,0x38}, // b
  {0x00,0x00,0x18,0x24,0x40,0x40,0x24,0x18}, // c
  {0x00,0x04,0x04,0x04,0x3C,0x44,0x44,0x38}, // d
  {0x00,0x00,0x38,0x44,0x7C,0x40,0x44,0x38}, // e
  {0x00,0x18,0x20,0x20,0x78,0x20,0x20,0x20}, // f
  {0x00,0x38,0x44,0x44,0x38,0x04,0x44,0x38}, // g
  {0x00,0x40,0x40,0x40,0x78,0x44,0x44,0x44}, // h
  {0x00,0x00,0x40,0x00,0x40,0x40,0x40,0x40}, // i
  {0x00,0x08,0x00,0x08,0x08,0x08,0x48,0x30}, // j
  {0x00,0x40,0x40,0x48,0x50,0x60,0x50,0x48}, // k
  {0x00,0x40,0x40,0x40,0x40,0x40,0x40,0x20}, // l
  {0x00,0x00,0x00,0x28,0x54,0x44,0x44,0x44}, // m
  {0x00,0x00,0x00,0x38,0x44,0x44,0x44,0x44}, // n
  {0x00,0x00,0x00,0x38,0x44,0x44,0x44,0x38}, // o
  {0x00,0x00,0x70,0x48,0x48,0x70,0x40,0x40}, // p
  {0x00,0x00,0x30,0x48,0x48,0x38,0x08,0x08}, // q
  {0x00,0x00,0x00,0x30,0x48,0x40,0x40,0x40}, // r
  {0x00,0x30,0x48,0x40,0x30,0x08,0x48,0x30}, // s
  {0x00,0x20,0x70,0x20,0x20,0x20,0x28,0x10}, // t
  {0x00,0x00,0x44,0x44,0x44,0x44,0x44,0x38}, // u
  {0x00,0x00,0x44,0x44,0x44,0x44,0x28,0x10}, // v
  {0x00,0x00,0x82,0x82,0x82,0x92,0x54,0x28}, // w
  {0x00,0x00,0x84,0x48,0x30,0x30,0x48,0x84}, // x
  {0x00,0x48,0x48,0x48,0x38,0x08,0x48,0x30}, // y
  {0x00,0x00,0x00,0x7C,0x08,0x10,0x20,0x7C}, // z
  {0x00,0x30,0x40,0x40,0x80,0x40,0x40,0x30}, // {
  {0x00,0x20,0x20,0x20,0x20,0x20,0x20,0x20}, // |
  {0x00,0x60,0x10,0x10,0x08,0x10,0x10,0x60}, // }
  {0xE0,0xA0,0xE0,0x80,0x8A,0x15,0x15,0x11}  // pm ('~'로 코딩)
};

// RTC 동작 정의
RTC_DS1307 RTC; // Tiny RTC (DS1307) 모듈 (SDA - A4, SCL - A5)

// 로터리 인코더, 스위치, LED 제어
#define enc_PinA 2     // 인코더 A를 핀 2에 연결 (인터럽트 0)
#define enc_PinB 4     // 인코더 B를 핀 4에 연결
#define enc_Switch 3   // 인코더 스위치를 핀 3에 연결 (인터럽트 1)
#define mode_Pin 8     // 모드 LED 핀
#define min_Pin 9      // 분 LED 핀

unsigned char enc_A, enc_B, enc_A_prev=0;
static boolean rotating = false;
static boolean clockwise = false;
static boolean updateFlag= false;
static int mode = 0; // 0 - 없음, 1 - 시 설정, 2 - 분 설정

// 디스플레이 문자열 정의
#define max_array_size 100
char ac[max_array_size] = {};
byte rc[8] = {};
String display_message = "";
int arraylen;

// 동작 파라미터
#define delay_line 75  // 줄 전환 간 ms
#define delay_char 400 // 문자 간 ms
#define delay_mess 500 // 메시지 간 ms
#define cblanks 1      // 문자 간 공백 줄 수
#define eblanks 0      // 메시지 끝 추가 공백 줄 수 (8줄 초과분)

// 디스플레이 기능
static boolean top2bottom = false; // 표시 방향 (상하 또는 좌우)
static boolean hour24 = false;     // 24시간 표시?
static boolean charHI = true;      // 전체 문자 하이라이트?
static boolean doSerial = true;    // 시리얼 출력?

//*****************************************************************************************//
// 초기 설정
//*****************************************************************************************//
void setup () {
  Wire.begin();
  Serial.begin(57600);
  if(doSerial) Serial.print("MATRIX Clock - Adrian Jones, Mar. 2014");

  // 8×8 LED 매트릭스 제어 핀
  pinMode(dataIn, OUTPUT);
  pinMode(clock, OUTPUT);
  pinMode(load, OUTPUT);
  initMatrix(); // LED 매트릭스 초기화

  // LED 핀
  pinMode(mode_Pin, OUTPUT);
  digitalWrite(mode_Pin, 1);
  pinMode(min_Pin, OUTPUT);
  digitalWrite(min_Pin, 1);

  // 인코더 제어
  pinMode(enc_PinA, INPUT_PULLUP);
  digitalWrite(enc_PinA, HIGH);
  pinMode(enc_PinB, INPUT_PULLUP);
  digitalWrite(enc_PinB, HIGH);
  pinMode(enc_Switch, INPUT_PULLUP);
  digitalWrite(enc_Switch, HIGH);

  attachInterrupt(0, rotEncoder, CHANGE); // 시간 설정
  attachInterrupt(1, swEncoder, CHANGE);  // 분/시 설정

  // RTC
  RTC.begin();
  if (! RTC.isrunning()) {
    RTC.adjust(DateTime(__DATE__, __TIME__));
    if(doSerial) Serial.println(" (RTC reset)");
  } else {
    if(doSerial) Serial.println(" (RTC running)");
  }
}

//*****************************************************************************************//
// 메인 루프
//*****************************************************************************************//
void loop () {
  DateTime now = RTC.now();
  display_message = createMessage(now);
  arraylen = initDisplayString(display_message);
  if(updateFlag) {
    show_time_and_date(now);
    updateFlag = false;
  }
  while(rotating) {
    delay(1); // 디바운스
    adjTime(now, clockwise);
    show_time_and_date( RTC.now() );
    display_message = createMessage(now);
    arraylen = initDisplayString(display_message);
    delay(1);
    rotating = false;
  }
  delay(5);
  for (int i = 0; i < (arraylen-7); i++) {
    for (int j = 1; j < 9; j++) {
      maxSingle(j,ac[i+8-j]);
    }
    if(i%(8+cblanks) == 0) {
      if(charHI) maxSingle(max7219_reg_intensity, 0x01);
      newDelay(delay_char);
    } else {
      maxSingle(max7219_reg_intensity, 0x00);
      newDelay(delay_line);
    }
  }
  if(mode == 0) newDelay(delay_mess);
}

//*****************************************************************************************//
// 인터럽트 루틴
//*****************************************************************************************//
// rotEncoder(): 인코더 회전 시 호출되는 ISR
void rotEncoder(){
  delay(1);
  enc_A = digitalRead(enc_PinA);
  enc_B = digitalRead(enc_PinB);
  if(!enc_A && enc_A_prev){
    clockwise = (!enc_A && enc_B)? true : false;
    if(mode != 0) rotating = true;
  }
  enc_A_prev = enc_A;
}

// swEncoder(): 인코더 버튼 눌림 시 호출되는 ISR
void swEncoder(){
  delay(1);
  if(digitalRead (enc_Switch) != LOW) return;
  delay(1);
  if(digitalRead (enc_Switch) != LOW) return;
  mode++;
  mode = mode % 3;
  digitalWrite(mode_Pin, !(mode == 1));
  digitalWrite(min_Pin, !(mode == 2));
  updateFlag = true;
}

//*****************************************************************************************//
// 동작 루틴
//*****************************************************************************************//
// newDelay 함수
void newDelay (int dly) {
  for (int z=1; z< dly; z++) {
    delay(1);
    if(rotating || updateFlag) break;
  }
}

// initMatrix(): MAX7219 레지스터 초기화
void initMatrix() {
  maxSingle(max7219_reg_scanLimit, 0x07);
  maxSingle(max7219_reg_decodeMode, 0x00);
  maxSingle(max7219_reg_shutdown, 0x01);
  maxSingle(max7219_reg_displayTest, 0x00);
  for (int e=1; e<=8; e++) {maxSingle(e,0); }
  maxSingle(max7219_reg_intensity, 0x00);
}

// adjTime(): 인코더 방향에 따라 시/분 증감
void adjTime(DateTime now, boolean dir) {
  if(mode == 1) {
    int adj_hrs = now.hour();
    if(dir) {
      if(++adj_hrs >= 25) adj_hrs = 1;
    } else {
      if(adj_hrs == 0) adj_hrs = 24;
      if(--adj_hrs <= 0) adj_hrs = 24;
    }
    RTC.adjust(DateTime(now.year(), now.month(), now.day(), adj_hrs, now.minute(), now.second() ));
  }
  if(mode == 2) {
    int adj_mins = now.minute();
    if(dir) {
      if(++adj_mins >= 60) adj_mins = 0;
    } else {
      if(--adj_mins < 0) adj_mins = 59;
    }
    RTC.adjust(DateTime(now.year(), now.month(), now.day(), now.hour(), adj_mins, now.second() ));
  }
}

// rotChar(): 문자를 90도 회전 (상하 → 좌우)
byte rotChar(char inLetter) {
  int ind = int(inLetter) - 0x20;
  for (int col = 0; col < 8; col++) {
    byte mask = 0x01 << (7-col);
    for (int row = 0; row < 8 ; row++) {
      bitWrite(rc[col], 7-row,(alphanum[ind][row] & mask));
    }
  }
}

// show_time_and_date: 시간 문자열 및 바이트 출력
void show_time_and_date(DateTime datetime){
  if(doSerial) {
    int minutes = datetime.minute();
    int hours = datetime.hour();
    if(hours==0) hours=24;
    int seconds = datetime.second();
    char delim = '/';
    char dend = ' ';
    String te = "Current date/time: ";
    te = te + datetime.year() + delim + datetime.month() + delim + datetime.day() + dend;
    Serial.print(te);
    if(hours<10) Serial.print(0);
    Serial.print(hours,DEC);
    Serial.print(":");
    if(minutes<10) Serial.print(0);
    Serial.print(minutes,DEC);
    Serial.print(":");
    if(seconds < 10) Serial.print(0);
    Serial.print(seconds,DEC);
    Serial.println("");
  }
}

String createMessage(DateTime datetime) {
  String new_mess = " ";
  int hr = datetime.hour()%24;
  if(hr == 0) hr = 24;
  int mn = datetime.minute();
  if(mode == 0) {
    if(hour24) {
      new_mess += hr;
    } else {
      new_mess += (hr > 12)? hr - 12 : hr;
    }
    new_mess += ':';
    if(mn < 10) new_mess += '0';
    new_mess += mn;
    if(!hour24) new_mess += (hr > 12)? "~" : "^";
  }
  if(mode == 1) {
    new_mess += hr;
  }
  if(mode == 2) {
    if(mn < 10) new_mess += '0';
    new_mess += mn;
  }
  return new_mess;
}

// initDisplayString(): 메시지 문자열을 블랭크 포함 배열로 변환
int initDisplayString(String message) {
  int x = 0;
  for (int y = 0; y < message.length(); y++ ){
    char thisCh = message.charAt(y);
    int ind = int(thisCh) - 0x20;
    if(!top2bottom) rotChar(thisCh);
    for (int row = 0; row < 8 + cblanks; row++) {
      if (row <= 7) {
        ac[x] = (top2bottom)? alphanum[ind][7-row] : rc[row];
      } else {
        ac[x] = 0;
      }
      x++;
    }
  }
  for(int y = 0; y < 7+eblanks; y++) {ac[x] = 0; x++; }
  return x;
}

// maxSingle(): 레지스터에 데이터 로드
void maxSingle(byte reg, byte col) {
  digitalWrite(load, LOW);
  putByte(reg);
  putByte(col);
  digitalWrite(load,HIGH);
}

// putByte(): 매트릭스에 데이터 전송 (MSB to LSB)
void putByte(byte data) {
  byte i = 8;
  byte mask;
  while(i > 0) {
    mask = 0x01 << (i - 1);
    digitalWrite(clock, LOW);
    if (data & mask){
      digitalWrite(dataIn, HIGH);
    } else {
      digitalWrite(dataIn, LOW);
    }
    digitalWrite(clock, HIGH);
    --i;
  }
}

회로도

아두이노 8×8 LED 매트릭스 플립 시계 – DIY 실시간 시계 프로젝트

제조공정

  1. 아두이노 POV 클락: 퍼시스턴스 오브 비전 LED 디스플레이 제작
  2. 아두이노로 만드는 레트로 누미트론 클락: 심플하고 신뢰성 높으며 에너지 효율적
  3. 아두이노로 만드는 대형 LED 마스터 시계 (알람 기능 포함, 12m 거리에서도 식별 가능)
  4. 아두이노 나노로 테트리스 게임 만들기: DIY 16×8 LED 매트릭스
  5. MotionSense: 아두이노와 ESP8266으로 만드는 스마트 침입 감지 시스템
  6. PIR 모션 센서 연동 인터랙티브 LED 매트릭스 도어 디스플레이 - 아두이노 홀리데이 인사
  7. 스마트 아두이노 4족 보행 로봇 제작: 단계별 완벽 가이드
  8. 볼트 IoT와 아두이노 우노로 구현하는 무선 LED 밝기 제어 (PWM 6채널)
  9. 아두이노, Yaler, IFTTT로 웹 연동 IoT 게이지 만들기
  10. 아두이노 기반 척력 자기 부상: 간단 회로 & DIY 가이드