아두이노2016. 5. 9. 13:47

예전에 DS1302 라는 칩을 사용한 RTC (Real Time Clock) 모듈에 대해서 알아본적이 있습니다. 이번에는 DS1302 모듈보다 한층 업그레이드된 DS3231 이라는 모듈에 대해서 알아보았습니다. 아두이노에는 자체적인 RTC가 없으므로 Real Time 을 다루기 위해서는 이런 RTC 모듈을 붙여서 현재의 시간을 알아내야 합니다. DS3231은 다음과 같은 장점이 있습니다. 1년에 2분 이내의 오차, 5V/3.3V 겸용, 2개의 알람기능 제공, 주기적 인터럽트(Interrupt) 발생가능, 칩 내부에 발진기능을 가지고 있어 외부에 크리스탈 부품이 필요 없음, 자체에 온도센서를 가지고 있어서 온도측정도 가능...  이런 이유로 조금 더 정밀한 시간이 필요한 프로젝트에는 DS3231 모듈을 사용하는 것이 더 적합하다고 할 수 있습니다.


DS3231 모듈 스펙

  • Size: 38mm (length) x 22mm (Width) x 14mm (height)
  • Weight: 8g
  • Operating voltage :3.3 - 5.5V
  • Clock chip: high-precision clock chip DS3231
  • Clock Accuracy :0-40 °C range, the accuracy 2ppm, the error was about 1 minute
  • Calendar alarm clock with two Programmable square-wave output
  • Real time clock generator seconds, minutes, hours, day, date, month and year timing and provide valid until the year 2100 leap year compensation
  • Chip temperature sensor comes with an accuracy of ± 3 °C
  • Memory chips: AT24C32 (storage capacity 32K)
  • IIC bus interface, the maximum transmission speed of 400KHz (working voltage of 5V)
  • Can be cascaded with other IIC device, 24C32 addresses can be shorted A0/A1/A2 modify default address is 0x57
  • Rechargeable battery type: LIR2032

DS3231 데이터시트



DS3231 모듈의 모습입니다. 전면에 현재의 시간을 유지할 수 있도록 LR-2032 배터리를 넣을 수 있습니다. 이걸 빼면 모든 시간설정값이 초기화 됩니다. 컴퓨터 메인보드에 끼워져 있는 전지와 같은 용도 입니다. 메인보드에 전지를 빼면 컴퓨터의 시간설정값도 초기화 되죠.


모듈의 구조는 위와 같습니다. 크리스털 등 모든 RTC 기능을 탑재한 DS3231 칩이 상부에 자리잡고 있으며 아랫쪽에 32KB 용량의 데이터를 저장할 수 있는 EEPROM 칩인 AT24C32N 이 달려 있습니다.


아두이노와의 연결

연결은 별다른 것 없이 전원과 SDA, SCL 단자만 연결해주면 됩니다.


아두이노 우노

DS3231 모듈

5V

VCC 

GND

GND

A4

SDA

A5

SCL











소스

소스의 주석에도 써있지만 현재시간의 설정을 위하여 시간설정을 입력하는 방법은 다음과 같습니다.


▶ 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초 월요일)


위와 예와 같이 T 로 시작하는 시간 설정 문자열을 만들어서 시리얼모니터에 입력해서 아두이노로 전송하면 됩니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
#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;
}
cs


위의 소스는 http://www.goodliffe.org.uk/arduino/rtc_ds3231.php 의 소스를 한국의 시간표시 방식에 맞도록 시간설정의 입력과 출력부분을 살짝 수정한 소스 입니다.


위의 소스를 실행한 결과 아래와 같이 1초 간격으로 년, 월, 일, 시, 분, 초, 요일 및 내장된 온도센서로 현재 온도까지 출력이 되었습니다. 설정한 시간은 모듈의 배터리에 의해서 백업되므로 외부전원을 완전히 끊었다가 연결해도 현재시간 데이터가 계속 살아 있습니다.





■ 추가내용 (2017.11.14) : 댓글에 SDA 와 SCL핀을 변경할 수 있느냐는 질문이 있어서 내용을 추가해 본다. 이 모듈은 I2C 방식으로 아두이노와 통신을 하는데 아두이노의 I2C 통신을 위한 핀은 정해져 있다. 아두이노 우노는 A4가 SDA, A5가 SCL 이다. 아두이노 메가는 보드에 쓰여 있다. 아래의 그림을 보면 이해가 편할 것이다.


[아두이노 우노의 구조]

반응형
Posted by 대네브 (deneb)

댓글을 달아 주세요

  1. njg7194

    안녕하세요 좋은정보 잘얻어갑니다.
    그런데 소스코드에서 수정해야할 부분이 있네요.

    함수생성시 선언을 하지 않고 정의만 되어있어 오류가 뜹니다.

    코드 중

    #define DS3231_I2C_ADDRESS 104



    // 데이터핀 연결

    사이에

    byte decToBcd(byte val);
    void watchConsole();
    void set3231Date();
    void get3231Date();
    float get3231Temp();

    이런식으로 함수선언을 해주어야 오류가 뜨지않고 정상적으로 컴파일 및 업로드가 되네요.
    해매시는분 계실까봐 댓글달고갑니다.
    (사용 보드는 Mega2560 입니다.)

    2017.05.02 22:43 [ ADDR : EDIT/ DEL : REPLY ]
    • 그런가요?
      저는 함수 선언을 안해도 오류가 안뜨는데
      1.05 버전과 1.8.2 버전 모두에서
      오류가 안뜨는걸 확인 했습니다.

      아무튼 FM대로 하는게 제일 좋은거죠.
      좋은 정보 감사합니다.

      2017.05.03 01:17 신고 [ ADDR : EDIT/ DEL ]
  2. 안녕하세요 도움 많이 얻고 갑니다. 궁금한게 있는데요 아날로그 4,5번 핀 말고 2,3번핀으로 변경하려면 코드를 어떻게 손봐야 하는지 아시나요?

    2017.11.14 00:29 신고 [ ADDR : EDIT/ DEL : REPLY ]
    • 위 소스는 아두이노에서 기본으로 제공되는 Wire.h 를 이용해서 I2C 통신을 합니다. 아두이노 우노의 I2C 통신을 위한 SDA 및 SCL 단자는 각각 A4 와 A5로 정해져 있습니다. 우노가 아닌 다른 아두이노의 경우는 또 틀립니다. 본문에 추가내용 적어 놓았습니다. 참고하시길

      2017.11.14 13:17 신고 [ ADDR : EDIT/ DEL ]
    • 답변 감사합니다. 제가 이를 물어봤던 이유는 I2C통신으로 realtimeclock 모듈도 연결해서 쓰고 LCD도 같이 작동시키고 싶은데 핀을 나눠야 가능한건지 궁금해서 여쭤봤습니다.

      2017.11.19 13:29 신고 [ ADDR : EDIT/ DEL ]
    • 포스팅해주신 글 덕분에 시리얼모니터에 시간 출력은 성공했습니다. 그런데 이를 LCD에 표시하려고 생각해보니, LCD도 I2C 통신을 써야하더라고요. 혹시 I2C를 병렬로 사용해야 할까요?

      2017.11.19 13:35 신고 [ ADDR : EDIT/ DEL ]
    • i2c 는 bus 개념 입니다. 하나의 master에 여러개의 slave 연결이 가능 합니다.

      2017.11.19 14:17 신고 [ ADDR : EDIT/ DEL ]
    • 질문 관련해서 좋은 유튜브 강의 영상이 있어서 링크해 드립니다. https://youtu.be/QQLfzlPGjjE

      2017.11.19 14:36 신고 [ ADDR : EDIT/ DEL ]
  3. 궁금이

    안녕하세요
    RTC 모듈 설명해주시는거 보고 시간은 시리얼모니터에 잘 나타나는데요
    이 시간정보를 이용해서 지정된 시간에 모터를 돌아가게 하고 싶은데
    시간 정보를 저장하는 코드는 어떻게 삽입하면될까요..

    2020.06.08 14:56 [ ADDR : EDIT/ DEL : REPLY ]