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 이죠.


   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

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


/*
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/


#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 에 표시해주는 소스로 살짝 바꾸었습니다.

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



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


#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