K씨의 별별제작소

아두이노로 시계만들기 (DS3231 RTC, 7세그먼트 TM1637) 본문

아두이노 프로젝트

아두이노로 시계만들기 (DS3231 RTC, 7세그먼트 TM1637)

K씨 2018. 11. 7. 22:40

집안 곳곳마다 시계가 있어서 언제든 시간을 확인할 수 있으면 좋겠다는 생각을 하곤하는건

필요한 장소에 비해 시계가 부족하기 때문이리라.

부엌에도 시계가 없어 들여놓아야겠다는 생각을 하다가

아두이노로 여러가지 필요한 기능을 겸하는 시계를 만들어보자는 생각에 이르렀다.


우선, 시계부터 만들고 기능들을 추가하기로 결정, 시계만들기 시작함.


1. 준비물



4-digit 7세그먼트 디스플레이모듈, DS3231 RTC 모듈, 아두이노 우노 호환보드,

그리고 브레드보드와 점퍼선을 이용하여 만들었다.


1> 4-digit 7세그먼트 디스플레이 모듈



시계로 사용할 수 있도록 4자리 숫자가 표시되는 7세그먼트 LED 디스플레이를 사용했다.

구입은 알리익스프레스에서 했고, 가격은 $1.18

헤더핀이 납땜되지 않고 같이 동봉되어 왔기에 납땜해 주었다.



2> DS3231 RTC 모듈



DS3231 모듈은 저렴하면서도 꽤 정확한(1년에 2분 정도의 오차가 발생한다고 한다.)

Real Time Clock (RTC)로

현재 날짜, 시간 등을 실시간으로 알 수 있는 모듈이다.

이모듈은 CR2032 (듀라셀은 DL2032로 표시되기도 함) 배터리를 사용하는데,

배터리 덕분에 시계 전원을 껐다 켜도 시간이 초기화되지 않고 정확하게 표시된다.

역시 알리익스프레스에서 구입했고, 가격은 $0.99 (배터리 별도 구입)


3> 아두이노 우노 호환보드



보드는 집에 있던 아두이노 우노 호환보드를 사용했다.

이 보드는 스마트폰 연결 선(마이크로USB)을 사용하여 컴퓨터와 연결할 수 있고,

제작 후 시계로 사용할 때 역시 스마트폰 연결 선으로 전원을 공급할 수 있다는 장점이 있다.

또한 아날로그 입력핀이 2개 더 추가로 있다고 적혀있다.

아두이노 우노 정품 보드와 비교를 위해서 함께 찍어보았다.

보드 구입은 알리익스프레스에서 했고, 가격은 $3.49



2. 보드 Test

 

새로 뜯은 보드라 테스트부터 진행했다.



▲ 보드를 컴퓨터에 연결하고



 장치 연결 포트를 확인 후

아두이노 IDE(내가 사용한 버전은 1.8.7)에서 포트를 선택한다.

별다른 추가 설치 없이도 곧바로 인식이 된다.



▲ 보드는 아두이노 우노를 선택.



▲ Blink 예제를 실행해보았다.

LED 깜박이는 시간을 조정하여 확인했는데 정상적으로 작동한다.



3. DS3231 RTC 모듈 연결 & 시간 설정



▲ DS3231 모듈의 4개 핀을 보드와 연결한다.

이 모듈은 보드와 연결하는 핀이 정해져있다.

점퍼선을 이용하여 맞게 연결한다.


아두이노IDE를 열고 RTC 시간 설정을 위해 아래 스케치 코드를 업로드한다.

출처는 http://deneb21.tistory.com/327


#include <Wire.h>

 

#define DS3231_I2C_ADDRESS 104

 

// 데이터핀 연결

// SCL - pin A5

// SDA - pin A4

 

byte seconds, minutes, hours, day, date, month, year;

char weekDay[4];

 

byte tMSB, tLSB;

float temp3231;

 

void setup()

{

  Wire.begin();

  Serial.begin(9600);

}

 

void loop()

{

 

  watchConsole();

  get3231Date();

 

  Serial.print(weekDay);

  Serial.print(", 20");

  Serial.print(year, DEC);

  Serial.print("/");

  Serial.print(month, DEC);

  Serial.print("/");

  Serial.print(date, DEC);

  Serial.print(" - ");

  Serial.print(hours, DEC); 

  Serial.print(":"); 

  Serial.print(minutes, DEC); 

  Serial.print(":"); 

  Serial.print(seconds, DEC);

  Serial.print(" - Temp: "); 

  Serial.println(get3231Temp());

 

  delay(1000);

}

 

// 10진수를 2진화 10진수인 BCD 로 변환 (Binary Coded Decimal)

byte decToBcd(byte val)

{

  return ( (val/10*16) + (val%10) );

}

 

void watchConsole()

{

  if (Serial.available()) {      // Look for char in serial queue and process if found

    if (Serial.read() == 84) {   //If command = "T" Set Date

      set3231Date();

      get3231Date();

      Serial.println(" ");

    }

  }

}

 

//시간설정

// T(설정명령) + 년(00~99) + 월(01~12) + 일(01~31) + 시(00~23) + 분(00~59) + 초(00~59) + 요일(1~7, 일1 월2 화3 수4 목5 금6 토7)

// 예: T1605091300002 (2016년 5월 9일 13시 00분 00초 월요일)

void set3231Date()

{

  year    = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));

  month   = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));

  date    = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));

  hours   = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));

  minutes = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));

  seconds = (byte) ((Serial.read() - 48) * 10 + (Serial.read() - 48));

  day     = (byte) (Serial.read() - 48);

 

  Wire.beginTransmission(DS3231_I2C_ADDRESS);

  Wire.write(0x00);

  Wire.write(decToBcd(seconds));

  Wire.write(decToBcd(minutes));

  Wire.write(decToBcd(hours));

  Wire.write(decToBcd(day));

  Wire.write(decToBcd(date));

  Wire.write(decToBcd(month));

  Wire.write(decToBcd(year));

  Wire.endTransmission();

}

 

 

void get3231Date()

{

  // send request to receive data starting at register 0

  Wire.beginTransmission(DS3231_I2C_ADDRESS); // 104 is DS3231 device address

  Wire.write(0x00); // start at register 0

  Wire.endTransmission();

  Wire.requestFrom(DS3231_I2C_ADDRESS, 7); // request seven bytes

 

  if(Wire.available()) {

    seconds = Wire.read(); // get seconds

    minutes = Wire.read(); // get minutes

    hours   = Wire.read();   // get hours

    day     = Wire.read();

    date    = Wire.read();

    month   = Wire.read(); //temp month

    year    = Wire.read();

       

    seconds = (((seconds & B11110000)>>4)*10 + (seconds & B00001111)); // convert BCD to decimal

    minutes = (((minutes & B11110000)>>4)*10 + (minutes & B00001111)); // convert BCD to decimal

    hours   = (((hours & B00110000)>>4)*10 + (hours & B00001111)); // convert BCD to decimal (assume 24 hour mode)

    day     = (day & B00000111); // 1-7

    date    = (((date & B00110000)>>4)*10 + (date & B00001111)); // 1-31

    month   = (((month & B00010000)>>4)*10 + (month & B00001111)); //msb7 is century overflow

    year    = (((year & B11110000)>>4)*10 + (year & B00001111));

  }

  else {

    //oh noes, no data!

  }

 

  switch (day) {

    case 1:

      strcpy(weekDay, "Sun");

      break;

    case 2:

      strcpy(weekDay, "Mon");

      break;

    case 3:

      strcpy(weekDay, "Tue");

      break;

    case 4:

      strcpy(weekDay, "Wed");

      break;

    case 5:

      strcpy(weekDay, "Thu");

      break;

    case 6:

      strcpy(weekDay, "Fri");

      break;

    case 7:

      strcpy(weekDay, "Sat");

      break;

  }

}

 

float get3231Temp()

{

  //temp registers (11h-12h) get updated automatically every 64s

  Wire.beginTransmission(DS3231_I2C_ADDRESS);

  Wire.write(0x11);

  Wire.endTransmission();

  Wire.requestFrom(DS3231_I2C_ADDRESS, 2);

 

  if(Wire.available()) {

    tMSB = Wire.read(); //2's complement int portion

    tLSB = Wire.read(); //fraction portion

   

    temp3231 = (tMSB & B01111111); //do 2's math on Tmsb

    temp3231 += ( (tLSB >> 6) * 0.25 ); //only care about bits 7 & 8

  }

  else {

    //error! no data!

  }

  return temp3231;

}



RTC 모듈은 I2C 통신을 하는데,

아두이노에는 I2C통신을 쉽게할 수 있는 Wire 라이브러리가 포함되어 있어서

여기에서는 별도의 라이브러리 설치가 필요없다.

위 소스를 업로드 한 후 시리얼모니터를 연다.



▲ 아직 시간이 설정되지 않아서 시간이 제대로 나타나지 않는다.

이제 시리얼모니터에 현재 시간을 입력하고 전송한다.



소스 코드에 시간 설정 방법이 나와있다.

 T(설정명령) + 년(00~99) + 월(01~12) + 일(01~31) + 시(00~23) + 분(00~59) + 초(00~59) + 요일(1~7, 일1 월2 화3 수4 목5 금6 토7)


이렇게 T 다음 13자리 입력하고 전송하면 된다.

참고로, 좀더 정확한 시간 입력을 위해 네이버시계를 켜놓고

초를 딱 맞춰서 전송했더니 거의 정확한 시간이 뜬다.


여기서 잠깐!

위의 소스코드는 24시간을 표시해 주므로

저녁 8시는 20시로 입력해야 하고, 화면에도 20시로 표시된다.


나는 12시간으로 표시되는 시계를 원했기에..

hours   = (((hours & B00110000)>>4)*10 + (hours & B00001111)); // convert BCD to decimal (assume 24 hour mode)

아래에 다음의 코드를 추가했다.


    if (hours > 12 ) {

      hours = hours - 12;

      }



12시간 표기를 위해 라이브러리도 바꿔보고 검색을 통해 다양한 시도를 해봤지만 모두 실패하고

저 방법이 가장 간단하고 편했다.


아두이노IDE에 코드 추가 후 다시 보드에 업로드하고

시리얼모니터를 확인했다.



▲ 20시를 입력하니 8시로 표시된다.

 

여기까지하면 RTC에 시간설정이 완료되었다.

보드에 전원연결을 끊어도 시간이 정확히 흘러가도록 하려면

RTC 모듈 배터리는 항상 끼워두어야한다.



4. 4-digit 7세그먼트 TM1637 디스플레이에 시간 표시


검색을 통해 여러가지 방법을 시도해보았는데 잘 안되어서 좀 해매다가

모 카페에서 소스코드를 찾았다.

그분은 유튜브에서 소스를 얻었다고 한다.

유튜브 영상 https://www.youtube.com/watch?v=e8trpe0A4Ls&list=PLDwXD7wX7lRtod-w96QYW8EAOmRwm-3wF&index=2



1> 스케치 코드 입력하기


나는 12시간 표기를 위해

위에서처럼 중간에 if문을 삽입했다.

(이 부분 없이 업로딩하면 RTC 시간표시 코딩을 바꾸어주었음에도 불구하고

디스플레이에 24시간으로 표기된다.)



 #include "TM1637.h" 

   

//{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20}; 

//0~9,A,b,C,d,E,F,"-"," ",degree,r,h 


 

#define CLK 9//Pins for TM1637        

#define DIO 8 

TM1637 tm1637(CLK,DIO); 


 

// Date and time functions using a DS3231 RTC connected via I2C and Wire lib 

#include <Wire.h> 

#include "RTClib.h" 

RTC_DS3231 rtc; 

int hh, mm;  


 

void setup() 

  { 

    tm1637.init(); 

    tm1637.set(5);  

    //BRIGHT_TYPICAL = 2,BRIGHT_DARKEST = 0,BRIGHTEST = 7; 

  

 

    rtc.begin(); 

  // manual adjust 

    // January 21, 2014 at 3am you would call: 

    // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0)); 

  // automatic adjust 

    //rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); 

  }//end "setup()" 

    

  void loop(){ 

  DateTime now = rtc.now(); 

  hh = now.hour(), DEC; 

  

/*12시간표기를 위해 삽입*/

  if (now.hour() > 12 )

{

hh = now.hour() - 12;

}

else

{

hh = now.hour();

}

/*여기까지*/


  mm = now.minute(), DEC; 

  

 

  tm1637.point(POINT_ON); 

  if ((hh/10) == 0) tm1637.display(0,17); 

  else 

      tm1637.display(0,hh/10);     // hour 

      tm1637.display(1,hh%10); 

      tm1637.display(2,mm/10);    // minutes 

      tm1637.display(3,mm%10);    //  

  delay(500); 

      tm1637.point(POINT_OFF); 

  if ((hh/10) == 0) tm1637.display(0,17); 

  else 

      tm1637.display(0,hh/10);     // hour 

      tm1637.display(1,hh%10); 

      tm1637.display(2,mm/10);    // minutes 

      tm1637.display(3,mm%10);    //  

  delay(500); 

  }// end loop()


소스를 보면 알겠지만

기본 라이브러리인 <Wire.h>외에

"TM1637.h"와  "RTClib.h" 라이브러리를 추가해주어야 한다.



2> TM1637.h 라이브러리 설치


다운로드 받은 곳  http://wiki.seeedstudio.com/Grove-4-Digit_Display/

 


▲ 사이트를 들어가서 쭉 아래로 내리면 라이브러리 다운로드 메뉴가 있다.



▲ 아두이노IDE를 열고 ZIP라이브러리 추가에서 다운로드 받은 ZIP파일을 선택한다.


▲ 라이브러리가 추가된 걸 확인할 수 있다.

이 라이브러리를 클릭하면  #include "TM1637.h" 코드가 삽입되는 걸 확인할 수 있다.



3> RTClib.h 라이브러리 설치


다운로드 받은 곳 https://github.com/adafruit/RTClib



 마찬가지로 ZIP파일을 다운로드 하고

아두이노IDE에서 라이브러리 추가해준다.



▲ RTClib 라이브러리가 추가된 걸 확인할 수 있다.


4> 4-digit 7세그먼트 TM1637 디스플레이 연결하기


보드에 RTC모듈과 7세그먼트 디스플레이모듈을 같이 연결하기 위해 브레드보드를 사용했다.

(여기서 브레드보드는 VCC, GND 연결에만 사용하면 됨.)



소스코드를 보면 CLK를 D9로, DIO를 D8로 설정해두어서 거기에 맞게 연결했다.

(이 핀은 연결을 바꾸고 코드에서 번호를 수정해도 괜찮다.)

 

 

▲ 전체 연결 그림


 

5> 컴파일 & 보드에 업로드


라이브러리도 추가하고 소스코드도 입력하고 보드도 연결이 되면

코드를 컴파일하고 업로드한다.

 

시간이 정확히 표시된다.

원했던대로 오후 1시가 13시가 아닌 1시로 표시된다.

 

컴파일에서는 문제가 없었는데, 업로딩하면서 에러가 떴는데

보드 연결 선을 뺐다가 다시 끼우고,

스케치프로그램에서 COM포트를 다시 클릭해준 후 다시 업로딩하니

정상적으로 작동했다.

 

몇 가지 기능들을 더 추가할 예정이라

그 후 케이스를 만들어 끼우기로하고

지금은 일단 이렇게 사용하기로~

 

Comments