Hardware | RTC DS3231 부품 사용기 - 1

|

이 포스트는 DS3231 에 대한 이야기 이며, 후속편에 이어집니다.


* Hardware | RTC DS3231 부품 사용기 - 2

http://chocoball.tistory.com/entry/Hardware-RTC-usning-DS3231-2





1. RTC


보통 internet 이 달린 기기라면 NTP Server 와 연동하여 시간을 맞추고,

특정 시간에 정확하게 일을 시킬 수가 있습니다.


Internet 에 연결되지 않은 기기의 경우는 다음과 같은 과정이 필요하겠죠.


A) 시간을 설정한다.

B) 시간을 기억한다.

C) 특정 시간에 일을 시킨다.


Internet 이 연결되지 않은 기기와 비교해 보면 B) 항목이 필요합니다.

이 "시간을 기억" 하고, 언제든지 현재 시간 정보를 가져올 수 있는 부품이 DS3231 입니다.


그래서 이번에는 DS3231 을 구입하고 사용해 보겠습니다.

자세한 내용은 아래 Arduino 사이트를 참조해 보세요.


* RTC Library

https://www.arduino.cc/en/Reference/RTC





2. 구입


말할것도 없이 AliExpress 입니다.

배송까지 40일 걸렸습니다. 이정도 되면 배송에 대해서는 해탈해야 합니다.


* 1PCS DS3231 AT24C32 IIC Precision RTC Real Time Clock Memory Module For Arduino new original

https://www.aliexpress.com/item/1PCS-DS3231-AT24C32-IIC-Precision-RTC-Real-Time-Clock-Memory-Module-For-Arduino-new-original/32830730519.html



건전지 미포함 1.06 USD 무료배송이면 고민거리는 아닙니다.

(배송기간 빼고)


인터넷을 뒤지니, 위의 부품으로 거의 통일되어 있는것 같았습니다.





3. 도착


요로코롬 도착했습니다.




부품 확대 사진입니다.

메인 chip 에 DS321 이라고 적혀 있네요.

발진기인 오실레이터도 보이고, 밑에는 AT24C32 EEPROM (32Kb) 도 있습니다.

참고로 DS3231 칩 안에는 추가로 온도센서도 존재합니다.


- Data sheet : DS3232.pdf




특이하게 "왜 온도센서?" 냐 하면,

전자 발진기 - 오실레이터 - 는 온도에 따라 그 값이 변합니다.

그래서 온도에 따른 변화를 보정하기 위해 온도센서가 자리잡고 있는 것이지요.



뒷면에는 CR2032 버튼 전지를 끼울 수 있는 플라스틱이 존재합니다.

이는 전원이 차단되더라도 "시간을 기억" 하기고 있기 위한 것이지요.


이 건전지 한개로 몇년은 쓴다고 하네요.





4. Layout


배선은 일반 IIC 배선과 동일합니다.

SLA 은 A4에, SCL은 A5 이죠.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
   DS3231 | Arduino Nano
-------------------------
    VCC   |     3.3V
    GND   |     GND
    SDC   |     A5
    SDA   |     A4
-------------------------
 
 
  SSD1306 | Arduino Nano
-------------------------
    VCC   |     3.3V
    GND   |     GND
    SDC   |     A5
    SDA   |     A4
-------------------------


실재 배선 모양입니다.



I2C의 특성상, 다른 센서 / 부품들 중, I2C 방식이면 arduino 의 동일한 pin 에 꼽아도 따로 인식 됩니다.

이는 각 device 가 가지는 address 가 다르기 때문이지요.


이는 I2C Scanner 를 이용해서 살펴보면, 각각 따로 인식하는 것을 알 수 있습니다.

아래에서 0x3c 는 OLED 이고, 0x68 이 DS3231 입니다.


추가로 나오는 0x57 은 AT24C32 EEPROM 입니다.



여기서 이상한 점은 0x5f 라는 address 입니다.

무얼까... 답을 찾지는 못했지만, 찾는 와중에 한가지 새로운 사실을 알게 됩니다.





5. EEPROM


EEPROM 으로는 ATmega 사의 AT24C32 이 쓰입니다.

이것의 실제 chip 번호는 ATML332 라고 적혀 있습니다.


24C32-Datasheet.pdf



그런데 제가 가지고 있는 DS3231 의 EEPROM 부분은 다음과 같습니다.



https://www.kynix.com/Detail/447536/ATMLH745.html


그렇습니다. EEPROM 이 original 이 아니고 fake 제품인 것이죠.


그러나 제품 구동은 정상적으로 돌아갑니다.

100% original chip 과 동일하지 않기 때문에 보다 복잡한 작업을 시키면 정상적으로 동작하지 않을 지도 모릅니다.


여튼, 앞으로 싼 부품은 좀 걸러야 할지도 모르겠네요.





6. Library 등록


인터넷에 돌아다니는 source 를 등록해서 사용할 수 있지만,

IDE 에서 지원해주는 Library 등록 기능을 이용하여 Example source 를 등록해 봅니다.


우선 IDE 메뉴에서 "Sketch > Add File... > Manage Libraries..." 를 선택합니다.

이건 이제 매번 써먹는 방법이지요?



Libarry Manager 의 검색창에서 "ds3231" 을 쳐서 검색합니다.

그러면 여러가지 source 들이 나오는데, 왠만하면 제일 위에 나오는 것을 선택하면 됩니다.

아래 그림처럼 adafruit 에서 만든 library 이니, 쓸만 할껍니다.



이렇게 하면 IDE 메뉴의 "File > Examples > DS3231" 항목이 생기고 sample source 를 이용할 수 있습니다.






6. sketch - 시간 설정


인터넷에 돌아다니는 source 를 등록해서 사용할 수 있지만,

위에서처럼 Arduino IDE 의 Library Manager 를 통해서 얻은 소스를 활용해 봅니다.


일단 시간을 입력합니다.

소스에 보이듯이 Serial Monitor 에 "YYMMDDwHHMMSS" 를 넣고, 마지막에 "x" 를 붙이면 설정됩니다.


------------------------------------------------

YYMMDDwHHMMSS, with an 'x' at the end

------------------------------------------------


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
/*
Sets the time and prints back time stamps for 5 seconds
 
Based on DS3231_set.pde
by Eric Ayars
4/11
 
Added printing back of time stamps and increased baud rate
(to better synchronize computer and RTC)
Andy Wickert
5/15/2011
*/
 
#include "DS3231.h"
#include "Wire.h"
 
DS3231 Clock;
 
byte Year;
byte Month;
byte Date;
byte DoW;
byte Hour;
byte Minute;
byte Second;
 
bool Century=false;
bool h12;
bool PM;
 
void GetDateStuff(byte& Year, byte& Month, byte& Day, byte& DoW,
        byte& Hour, byte& Minute, byte& Second) {
    // Call this if you notice something coming in on
    // the serial port. The stuff coming in should be in
    // the order YYMMDDwHHMMSS, with an 'x' at the end.
    boolean GotString = false;
    char InChar;
    byte Temp1, Temp2;
    char InString[20];
 
    byte j=0;
    while (!GotString) {
        if (Serial.available()) {
            InChar = Serial.read();
            InString[j] = InChar;
            j += 1;
            if (InChar == 'x') {
                GotString = true;
            }
        }
    }
    Serial.println(InString);
    // Read Year first
    Temp1 = (byte)InString[0] -48;
    Temp2 = (byte)InString[1] -48;
    Year = Temp1*10 + Temp2;
    // now month
    Temp1 = (byte)InString[2] -48;
    Temp2 = (byte)InString[3] -48;
    Month = Temp1*10 + Temp2;
    // now date
    Temp1 = (byte)InString[4] -48;
    Temp2 = (byte)InString[5] -48;
    Day = Temp1*10 + Temp2;
    // now Day of Week
    DoW = (byte)InString[6] - 48;      
    // now Hour
    Temp1 = (byte)InString[7] -48;
    Temp2 = (byte)InString[8] -48;
    Hour = Temp1*10 + Temp2;
    // now Minute
    Temp1 = (byte)InString[9] -48;
    Temp2 = (byte)InString[10] -48;
    Minute = Temp1*10 + Temp2;
    // now Second
    Temp1 = (byte)InString[11] -48;
    Temp2 = (byte)InString[12] -48;
    Second = Temp1*10 + Temp2;
}
 
void setup() {
    // Start the serial port
    Serial.begin(57600);
 
    // Start the I2C interface
    Wire.begin();
}
 
void loop() {
 
    // If something is coming in on the serial line, it's
    // a time correction so set the clock accordingly.
    if (Serial.available()) {
        GetDateStuff(Year, Month, Date, DoW, Hour, Minute, Second);
 
        Clock.setClockMode(false);  // set to 24h
        //setClockMode(true);   // set to 12h
 
        Clock.setYear(Year);
        Clock.setMonth(Month);
        Clock.setDate(Date);
        Clock.setDoW(DoW);
        Clock.setHour(Hour);
        Clock.setMinute(Minute);
        Clock.setSecond(Second);
         
        // Give time at next five seconds
        for (int i=0; i<5; i++){
            delay(1000);
            Serial.print(Clock.getYear(), DEC);
            Serial.print("-");
            Serial.print(Clock.getMonth(Century), DEC);
            Serial.print("-");
            Serial.print(Clock.getDate(), DEC);
            Serial.print(" ");
            Serial.print(Clock.getHour(h12, PM), DEC); //24-hr
            Serial.print(":");
            Serial.print(Clock.getMinute(), DEC);
            Serial.print(":");
            Serial.println(Clock.getSecond(), DEC);
        }
 
    }
    delay(1000);
}


시각을 입력하니 잘 등록되고 읽어집니다.






7. sketch - 시간 설정 + 시간 가져오기 + 온도


기본 sample 을 사용해도 되지만, 찾아다니면서 하나로 된 소스는 아래인것 같습니다.

Serial Monitor 의 입력창에 "T1124154091014" 등으로 입력하면 시각이 입력되면서,

그냥 와두면, "시간 + 온도" 를 표시해 준다.


* Tutorial – Using DS1307 and DS3231 Real-time Clock Modules with Arduino

https://tronixstuff.com/2014/12/01/tutorial-using-ds1307-and-ds3231-real-time-clock-modules-with-arduino/


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
#include "Wire.h"
 
#define DS3231_I2C_ADDRESS 104
 
// SCL - pin A5
// SDA - pin A4
// To set the clock, run the sketch and use the serial monitor.
// Enter T1124154091014; the code will read this and set the clock. See the code for full details.
 
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(", "); Serial.print(date, DEC); Serial.print("/"); Serial.print(month, DEC); Serial.print("/"); Serial.print(year, 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);
}
 
// Convert normal decimal numbers to 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(" ");
        }
    }
}
  
void set3231Date() {
//T(sec)(min)(hour)(dayOfWeek)(dayOfMonth)(month)(year)
//T(00-59)(00-59)(00-23)(1-7)(01-31)(01-12)(00-99)
//Example: 02-Feb-09 @ 19:57:11 for the 3rd day of the week -> T1157193020209
// T1124154091014
    seconds = (byte) ((Serial.read() - 48) * 10 + (Serial.read() - 48)); // Use of (byte) type casting and ascii math to achieve result.
    minutes = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
    hours   = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
    day     = (byte) (Serial.read() - 48);
    date    = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
    month   = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
    year    = (byte) ((Serial.read() - 48) *10 +  (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 {
            //oh noes, no data!
        }
    return temp3231;
}


Serial Monitor 의 결과 입니다.






8. sketch - 시간 설정 + 시간 가져오기 + 온도 + OLED


위의 소스를 조금 바꾸어 OLED 에 표시해주는 소스로 살짝 바꾸었습니다.

일주일 지난 뒤, 측정하니 여전히 잘 동작하고 있네요.



살짝 바꾼 소스 올려 봅니다.


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
#include "SPI.h"
#include "Wire.h"
#include "Adafruit_GFX.h"
#include "Adafruit_SSD1306.h"
 
Adafruit_SSD1306 display = Adafruit_SSD1306();
  
#define DS3231_I2C_ADDRESS 104
 
// SCL - pin A5
// SDA - pin A4
// To set the clock, run the sketch and use the serial monitor.
// Enter T1124154091014; the code will read this and set the clock. See the code for full details.
 
byte seconds, minutes, hours, day, date, month, year;
char weekDay[4];
 
byte tMSB, tLSB;
float temp3231;
 
void setup() {
  Serial.begin(9600);
   
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3C (for the 128x32)
  display.clearDisplay();
  display.display();
  delay(1000);
 
  Wire.begin();
}
 
void loop() {
  watchConsole();
   
  get3231Date();
   
  display.clearDisplay();
  display.setTextColor(WHITE);
  display.setCursor(0,0);
  display.setTextSize(1);
  display.print("DATE : "); display.print(weekDay); display.print(", "); display.print(date, DEC); display.print("/"); display.print(month, DEC); display.print("/"); display.println(year, DEC);
  display.print("TIME : "); display.print(hours, DEC); display.print(":"); display.print(minutes, DEC); display.print(":"); display.println(seconds, DEC);
  display.print("TEMP : "); display.println(get3231Temp());
  display.display();
   
  delay(1000);
}
 
// Convert normal decimal numbers to 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(" ");
    }
  }
}
  
void set3231Date() {
  //T(sec)(min)(hour)(dayOfWeek)(dayOfMonth)(month)(year)
  //T(00-59)(00-59)(00-23)(1-7)(01-31)(01-12)(00-99)
  //Example: 02-Feb-09 @ 19:57:11 for the 3rd day of the week -> T1157193020209
  // T1124154091014
  seconds = (byte) ((Serial.read() - 48) * 10 + (Serial.read() - 48)); // Use of (byte) type casting and ascii math to achieve result.
  minutes = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
  hours   = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
  day     = (byte) (Serial.read() - 48);
  date    = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
  month   = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
  year    = (byte) ((Serial.read() - 48) *10 +  (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 {
    //oh noes, no data!
  }
  return temp3231;
}


동영상도 올려 봅니다.






FIN


DS3231 에 대해서는 이야기 할 내용이 더 있어서 2편에서 더 다루어 보겠습니다.


And