这个时钟系列目前五篇分别是:
《8266+DS3231时钟之开发个时钟遇到的N个坑【一】》
《8266+ds3231时钟之arduino官网发布的DS3231库的分析【二】》
《8266+DS3231时钟之DS3231具体实现及代码【三】》
《8266+DS3231时钟之显示TM1638的使用【四】上》
《8266+DS3231时钟之显示TM1638的使用【四】下》
《8266+DS3231时钟之语音MP3-TF-16P模块使用【五】》
有兴趣的可以去看看,如果觉得对你有帮助,请点个赞。
一、前言
昨天写了《8266+DS3231时钟之开发个时钟遇到的N个坑【一】》
有兴趣的可以去看看,如果觉得对你有帮助,请点个赞。希望对使用8266开发DS3231的人有所帮助。毕竟技术说明网上一大把,但开发中遇到的坑却能让人快速成长,经验是技术文档里不会说的内容。
今天趁热打铁,把驱动程序中的函数分析一遍,对几个难以理解的地方重点说明一下,也算是开发中的一些经验介绍。下面直入主题 :
二、DateTime对象和RTClib对象
首先,函数库分为两个文件,一个自然是头文件DS3231.h,里面是所有的对象、函数等的声明内容。这里直接贴出来。注释和说明都直接写在代码里面:
*
* DS3231.h
*
* Arduino Library for the DS3231 Real-Time Clock chip
*
* (c) Eric Ayars
* 4/1/11
* released into the public domain. If you use this, please let me know
* (just out of pure curiosity!) by sending me an email:
* eric@ayars.org
*
*/
// Modified by Andy Wickert 5/15/11: Spliced in stuff from RTClib
#ifndef DS3231_h
#define DS3231_h
// Changed the following to work on 1.0
//#include "WProgram.h"
#include <Arduino.h>
#include <Wire.h>
// DateTime (get everything at once) from JeeLabs / Adafruit
// Simple general-purpose date/time class (no TZ / DST / leap second handling!)
/*
注释:DataTime对象主要是定义了一个简单的易于读取的数据结构对象。作为一个格式接口使用。方便用户将以秒为单位的系统使用的时间数字串转换成人们日常阅读习惯的日期格式。(同时这里没有时区处理、夏令时和闰年 等的处理,因为这些处理在DS3231对象里完成了,DataTime专心只负责数据结构的定义)
*/
class DateTime {
public:
DateTime (uint32_t t =0);
DateTime (uint16_t year, uint8_t month, uint8_t day,
uint8_t hour =0, uint8_t min =0, uint8_t sec =0);
DateTime (const char* date, const char* time);
uint16_t year() const { return 2000 + yOff; }
uint8_t month() const { return m; }
uint8_t day() const { return d; }
uint8_t hour() const { return hh; }
uint8_t minute() const { return mm; }
uint8_t second() const { return ss; }
uint8_t dayOfTheWeek() const;
// 32-bit times as seconds since 1/1/2000
long secondstime() const;
// 32-bit times as seconds since 1/1/1970
// THE ABOVE COMMENT IS CORRECT FOR LOCAL TIME; TO USE THIS COMMAND TO
// OBTAIN TRUE UNIX TIME SINCE EPOCH, YOU MUST CALL THIS COMMAND AFTER
// SETTING YOUR CLOCK TO UTC
uint32_t unixtime(void) const;
protected:
uint8_t yOff, m, d, hh, mm, ss;
};
/*
RTClib类,只有一个静态类函数,就是now().负责取DS3231中当前时间取出来,转换成刚才说的DateTime对象定义的数据结构进行显示。由于是个静态类,因此使用时你可别定义对象使用如:
DateTime now = RTClib.now();
uint8_t year = now.year();
也可以直接使用
unit8_t year =RTClib.now().year();
*/
class RTClib {
public:
// Get date and time snapshot
static DateTime now();
};
// Eric's original code is everything below this line
class DS3231 {
public:
//Constructor
DS3231();
// Time-retrieval functions
// the get*() functions retrieve current values of the registers.
byte getSecond();
byte getMinute();
byte getHour(bool& h12, bool& PM);
// In addition to returning the hour register, this function
// returns the values of the 12/24-hour flag and the AM/PM flag.
byte getDoW();
byte getDate();
byte getMonth(bool& Century);
// Also sets the flag indicating century roll-over.
byte getYear();
// Last 2 digits only
// Time-setting functions
// Note that none of these check for sensibility: You can set the
// date to July 42nd and strange things will probably result.
void setSecond(byte Second);
// In addition to setting the seconds, this clears the
// "Oscillator Stop Flag".
void setMinute(byte Minute);
// Sets the minute
void setHour(byte Hour);
// Sets the hour
void setDoW(byte DoW);
// Sets the Day of the Week (1-7);
void setDate(byte Date);
// Sets the Date of the Month
void setMonth(byte Month);
// Sets the Month of the year
void setYear(byte Year);
// Last two digits of the year
void setClockMode(bool h12);
// Set 12/24h mode. True is 12-h, false is 24-hour.
// Temperature function
float getTemperature();
// Alarm functions
void getA1Time(byte& A1Day, byte& A1Hour, byte& A1Minute, byte& A1Second, byte& AlarmBits, bool& A1Dy, bool& A1h12, bool& A1PM);
void getA2Time(byte& A2Day, byte& A2Hour, byte& A2Minute, byte& AlarmBits, bool& A2Dy, bool& A2h12, bool& A2PM);
// Same as getA1Time();, but A2 only goes on seconds == 00.
void setA1Time(byte A1Day, byte A1Hour, byte A1Minute, byte A1Second, byte AlarmBits, bool A1Dy, bool A1h12, bool A1PM);
// Set the details for Alarm 1
void setA2Time(byte A2Day, byte A2Hour, byte A2Minute, byte AlarmBits, bool A2Dy, bool A2h12, bool A2PM);
// Set the details for Alarm 2
void turnOnAlarm(byte Alarm);
// Enables alarm 1 or 2 and the external interrupt pin.
// If Alarm != 1, it assumes Alarm == 2.
void turnOffAlarm(byte Alarm);
// Disables alarm 1 or 2 (default is 2 if Alarm != 1);
// and leaves the interrupt pin alone.
bool checkAlarmEnabled(byte Alarm);
// Returns T/F to indicate whether the requested alarm is
// enabled. Defaults to 2 if Alarm != 1.
bool checkIfAlarm(byte Alarm);
// Checks whether the indicated alarm (1 or 2, 2 default);
// has been activated.
// Oscillator functions
void enableOscillator(bool TF, bool battery, byte frequency);
// turns oscillator on or off. True is on, false is off.
// if battery is true, turns on even for battery-only operation,
// otherwise turns off if Vcc is off.
// frequency must be 0, 1, 2, or 3.
// 0 = 1 Hz
// 1 = 1.024 kHz
// 2 = 4.096 kHz
// 3 = 8.192 kHz (Default if frequency byte is out of range);
void enable32kHz(bool TF);
// Turns the 32kHz output pin on (true); or off (false).
bool oscillatorCheck();;
// Checks the status of the OSF (Oscillator Stop Flag);.
// If this returns false, then the clock is probably not
// giving you the correct time.
// The OSF is cleared by function setSecond();.
private:
byte decToBcd(byte val);
// Convert normal decimal numbers to binary coded decimal
byte bcdToDec(byte val);
// Convert binary coded decimal to normal decimal numbers
byte readControlByte(bool which);
// Read selected control byte: (0); reads 0x0e, (1) reads 0x0f
void writeControlByte(byte control, bool which);
// Write the selected control byte.
// which == false -> 0x0e, true->0x0f.
};
#endif
三、几个概念解释
上面就注释了两部份,针对主体DS3231类的注释,放在DS3231.CPP里详细说明。再注释之前先在这里重点讲几个重点概念:
1、关于控制寄存器的关键说明
资料里都详细说明,这里只提几个重点以及资料里没有说到的。
Bit7 EOSC
:Bit7 EOSC设为0时,使能振荡,这个很重要,涉及到时钟的准确性。在外接电源时,振荡器始终是打开状态,也就是这里Bit7要置0。这里说明一点是由于库函数里的关联命令,使Bit7与Bit2 INTCN联动了。当初始化时,把Bit7置0时,会同步把INTCN也置0了。具体详见下面关于enableOscillator()
函数的解释。
Bit3&Bit4
这两位文档没说明,但实际这两位是存储芯片引脚INT/SQW引输出波的频率的,涉及的关联命令也是与enableOscillator()
函数相关。
Bit0&Bit1
这两位好理解,直接是闹钟一和闹钟二的使能控制位。当打开闹钟1后,Bit0会被设置。打开闹钟2后,Bit1会被设置。涉及的关联函数为turnOnAlarm()、turnOffAlarm(1)
2、关于状态寄存器的说明
状态寄存器的中的Bit0(A1F)和Bit1(A2F)是状态位,与控制寄存器中A1IE ,A2IE最大的区别就是一个是使能标志,允许你闹铃,一个是时间到了进入闹铃的状态标志。这里面有个坑就是,A1F和A2F由时钟自动设定,但是取消或叫置0必须是人工干预。必须由使用者在程序里人为去置0。否则芯片的INT/SQW引脚会一直处于低电平状态。另外,两路闹钟都共用这个中断输出引脚 INT/SQW,A1F与A2F必须都 置零后INT/SQW才会恢复高电平。涉及的库函数为checkIfAlarm()
3、关于闹钟设置中控制位的说明
我们用库函数setA1Time()、setA2Time()、getA1Time()、getA2Time()
时在参数表里会有一个AlarmBits参数。该参数是一个字节长度,里面的每一个位实际就是填入到上表中的A1M1–A1M4 ,A2M2–A2M4这几个位置里。具体的含义见下面这些程序的说明文字。
看到这里是不是就晕了,好像都明白了,但是怎么用还是不知道。我当时也是这样,看了上面几个函数的源码后才明白过来,说破了上面一堆内容,而实际就是汇积到下面这个字节表:
Dy不用去管,因为在函数的形参里还有一个bool型的A1Dy参数会针对到Dy 。而两组闹钟的设置控制字是合到同一个字节里的。举个例子,我闹钟一和闹钟二都选了时和分匹配就响铃的模式,那么上面那些参数如何选呢,看下图:
这时候就一一对应填入AlarmBits控制字就可以了。如下:
控制字的值 为AlarmBits=48H。这里要搞清楚的是,不论你用的是setA1Time(),还是setA2Time()这两个函数中的哪一个,虽然函数不同,但AlarmBits参数都是同一个,必须把两个时钟的闹钟模式控制位放在同一个字节里填写和使用。
四、DT3231对象的注释
上面说了那么长的铺垫,现在终于可以开始对最主要的DT3231对象进行解释了。由于源码比较长,具体代码我放到文章最后。这里对重点函数进行注释:
1、振荡器使能函数
该函数叫振荡器使用,但实际设置了上面提到的控制寄存器中的Bit7( EOSC) Bit6 (BBSQW) Bit3&Bit4(输出频率),Bit2(INTCN) 中断使用,这几个寄存器。因此这个函数可以放在时钟的初始化部分。需要注意的是由于振荡器使用EOSC是低电平有效,所以使能实际上是把该 位置0,但函数的参数TF必须是TRUE。这里有个习惯上的相反说法,使用时要注意。
void DS3231::enableOscillator(bool TF, bool battery, byte frequency) {
// turns oscillator on or off. True is on, false is off.
// if battery is true, turns on even for battery-only operation,
// otherwise turns off if Vcc is off.
// frequency must be 0, 1, 2, or 3.
// 0 = 1 Hz
// 1 = 1.024 kHz
// 2 = 4.096 kHz
// 3 = 8.192 kHz (Default if frequency byte is out of range)
if (frequency > 3) frequency = 3;
// read control byte in, but zero out current state of RS2 and RS1.
byte temp_buffer = readControlByte(0) & 0b11100111; //读出控制寄存器字节
if (battery) {
// turn on BBSQW flag
temp_buffer = temp_buffer | 0b01000000; //把Bit位置1
} else {
// turn off BBSQW flag
temp_buffer = temp_buffer & 0b10111111; //把Bit位置0
}
if (TF) {
// set ~EOSC to 0 and INTCN to zero.
temp_buffer = temp_buffer & 0b01111011; //注意当参数TF=true时使能了振荡器同时禁了中断输出
} else {
// set ~EOSC to 1, leave INTCN as is.
temp_buffer = temp_buffer | 0b10000000; //当参数TF=false时关闭了振荡器同时禁了中断输出
}
// shift frequency into bits 3 and 4 and set.
frequency = frequency << 3; //把频率设置参数移位到了bit3和bit4去存储
temp_buffer = temp_buffer | frequency;
// And write the control bits
writeControlByte(temp_buffer, 0);
}
2、振荡器是否曾停止工作检测
函数返回值如果是false表示振荡器停止工作或曾停止工作。以此来说明目前计时数据的可信与否。
bool DS3231::oscillatorCheck() {
// Returns false if the oscillator has been off for some reason.
// If this is the case, the time is probably not correct.
byte temp_buffer = readControlByte(1); //读出状态寄存器字节
bool result = true;
if (temp_buffer & 0b10000000) {
// Oscillator Stop Flag (OSF) is set, so return false.
result = false;
}
return result;
}
3、设置闹钟
由于设置闹钟有两个函数,分别是设置闹钟一的setA1Time()和设置闹钟2的setA2time()。两个函数类似,这城就只拿 setA1Time()来剖析。
函数的参数,byte A1Day, byte A1Hour, byte A1Minute, byte A1Second,都发理解,就是你想设置闹钟的天,时,分,秒的具体值 ,这里用的数据类型是byte数据类型。A1Day和A1Dy是关联参数。A1Dy 如果是true,则A1Day参数表示的日期是星期几。A1Dy如果是false,则表示的日期是月份里的几号。
A1h12如果是True代表的是12小时制,相应的A1PM也就为True。如果A1h12=false,那么就说明小时是24小时制,那么A1PM相应的也要填false。
最后就是AlarmBits了,这个部分说破了也很简单 ,具体在上面第三大点里已详细解释了,这里不再展开。
void DS3231::setA1Time(byte A1Day, byte A1Hour, byte A1Minute, byte A1Second, byte AlarmBits, bool A1Dy, bool A1h12, bool A1PM) {
// Sets the alarm-1 date and time on the DS3231, using A1* information
byte temp_buffer;
Wire.beginTransmission(CLOCK_ADDRESS);
Wire.write(0x07); // A1 starts at 07h
// Send A1 second and A1M1 //设置控制位和秒
Wire.write(decToBcd(A1Second) | ((AlarmBits & 0b00000001) << 7));
// Send A1 Minute and A1M2 //设置控制位和分钟
Wire.write(decToBcd(A1Minute) | ((AlarmBits & 0b00000010) << 6));
// Figure out A1 hour
if (A1h12) {
// Start by converting existing time to h12 if it was given in 24h.
if (A1Hour > 12) {
// well, then, this obviously isn't a h12 time, is it?
A1Hour = A1Hour - 12;
A1PM = true;
}
if (A1PM) {
// Afternoon
// Convert the hour to BCD and add appropriate flags.
temp_buffer = decToBcd(A1Hour) | 0b01100000;
} else {
// Morning
// Convert the hour to BCD and add appropriate flags.
temp_buffer = decToBcd(A1Hour) | 0b01000000;
}
} else {
// Now for 24h
temp_buffer = decToBcd(A1Hour);
}
temp_buffer = temp_buffer | ((AlarmBits & 0b00000100)<<5); //设置控制位和
// A1 hour is figured out, send it
Wire.write(temp_buffer);
// Figure out A1 day/date and A1M4
temp_buffer = ((AlarmBits & 0b00001000)<<4) | decToBcd(A1Day); //设置控制位和日期
if (A1Dy) {
// Set A1 Day/Date flag (Otherwise it's zero)
temp_buffer = temp_buffer | 0b01000000;
}
Wire.write(temp_buffer);
// All done!
Wire.endTransmission();
}
4、开启或关闭闹钟使能
在通过setA1Time()
函数或setA2Time()
函数设置好闹钟后,启动闹钟并在时间到后输出中断的函数就是靠turnOnAlarm()
。该函数执行好,会同步设置INTCN位和A1IE位为1。也就是闹钟中断输出的三个要素:INTCN 和 A1IE 以及A1F中的两个控制位是由turnOnAlarm()
函数设置 的。
相反要取消闹钟中断输出,需要执行turnOffAlarm()函数,这个函数中介把A1IE或是A2IE设置为0,对INTCN不影响。
void DS3231::turnOnAlarm(byte Alarm) {
// turns on alarm number "Alarm". Defaults to 2 if Alarm is not 1.
byte temp_buffer = readControlByte(0); //读取控制寄存字节内容
// modify control byte
if (Alarm == 1) {
temp_buffer = temp_buffer | 0b00000101; //设置INTCN位和A1IE位为1
} else {
temp_buffer = temp_buffer | 0b00000110; 设置INTCN位和A2IE位为1
}
writeControlByte(temp_buffer, 0);
}
void DS3231::turnOffAlarm(byte Alarm) {
// turns off alarm number "Alarm". Defaults to 2 if Alarm is not 1.
// Leaves interrupt pin alone.
byte temp_buffer = readControlByte(0); //读取控制寄存字节内容
// modify control byte
if (Alarm == 1) {
temp_buffer = temp_buffer & 0b11111110; //设置A1IE位为0
} else {
temp_buffer = temp_buffer & 0b11111101; //设置A1IE位为0
}
writeControlByte(temp_buffer, 0);
}
5、检测闹钟使能位是否设置
该函数就是负责检测A1IE或A2IE是否置1。
bool DS3231::checkAlarmEnabled(byte Alarm) {
// Checks whether the given alarm is enabled.
byte result = 0x0;
byte temp_buffer = readControlByte(0); //读取控制寄存器字节
if (Alarm == 1) {
result = temp_buffer & 0b00000001; //A1IE是否置1
} else {
result = temp_buffer & 0b00000010; //A2IE是否置1
}
return result;
}
6、检测闹钟中断输出与否并置0
该函数直接检测状态寄存器字节的A1F或A2F的状态,并同时把这这个位置0.。该 函数一执行,原本如果有正在输出的闹钟中断立即中止,INT/SQW引脚输出高电平。
bool DS3231::checkIfAlarm(byte Alarm) {
// Checks whether alarm 1 or alarm 2 flag is on, returns T/F accordingly.
// Turns flag off, also.
// defaults to checking alarm 2, unless Alarm == 1.
byte result;
byte temp_buffer = readControlByte(1); //读出状态寄存器字节
if (Alarm == 1) {
// Did alarm 1 go off?
result = temp_buffer & 0b00000001; //读取A1F的状态
// clear flag
temp_buffer = temp_buffer & 0b11111110; //把A1F状态置0
} else {
// Did alarm 2 go off?
result = temp_buffer & 0b00000010; //读取A2F的状态
// clear flag
temp_buffer = temp_buffer & 0b11111101; //把A2F状态置0
}
writeControlByte(temp_buffer, 1); //写回状态寄存器
return result;
}
以上把主要的函数的功能,以及使用上的注意事项已全部讲完。自已摸索总是很艰辛的,说破了其实也就这么些内容。希望上面的内容能对初次接触DS3231的人有所帮助,在使用中少走弯路。
附录:DS3231.cpp源码
/*
DS3231.cpp: DS3231 Real-Time Clock library
Eric Ayars
4/1/11
Spliced in DateTime all-at-once reading (to avoid rollover) and unix time
from Jean-Claude Wippler and Limor Fried
Andy Wickert
5/15/11
Released into the public domain.
*/
#include <DS3231.h>
// These included for the DateTime class inclusion; will try to find a way to
// not need them in the future...
#if defined(__AVR__)
#include <avr/pgmspace.h>
#elif defined(ESP8266)
#include <pgmspace.h>
#endif
// Changed the following to work on 1.0
//#include "WProgram.h"
#include <Arduino.h>
#define CLOCK_ADDRESS 0x68
#define SECONDS_FROM_1970_TO_2000 946684800
// Constructor
DS3231::DS3231() {
// nothing to do for this constructor.
}
// Utilities from JeeLabs/Ladyada
// utility code, some of this could be exposed in the DateTime API if needed
// DS3231 is smart enough to know this, but keeping it for now so I don't have
// to rewrite their code. -ADW
static const uint8_t daysInMonth [] PROGMEM = { 31,28,31,30,31,30,31,31,30,31,30,31 };
// number of days since 2000/01/01, valid for 2001..2099
static uint16_t date2days(uint16_t y, uint8_t m, uint8_t d) {
if (y >= 2000)
y -= 2000;
uint16_t days = d;
for (uint8_t i = 1; i < m; ++i)
days += pgm_read_byte(daysInMonth + i - 1);
if (m > 2 && y % 4 == 0)
++days;
return days + 365 * y + (y + 3) / 4 - 1;
}
static long time2long(uint16_t days, uint8_t h, uint8_t m, uint8_t s) {
return ((days * 24L + h) * 60 + m) * 60 + s;
}
/*****************************************
Public Functions
*****************************************/
/*******************************************************************************
* TO GET ALL DATE/TIME INFORMATION AT ONCE AND AVOID THE CHANCE OF ROLLOVER
* DateTime implementation spliced in here from Jean-Claude Wippler's (JeeLabs)
* RTClib, as modified by Limor Fried (Ladyada); source code at:
* https://github.com/adafruit/RTClib
******************************************************************************/
// DateTime implementation - ignores time zones and DST changes
// NOTE: also ignores leap seconds, see http://en.wikipedia.org/wiki/Leap_second
DateTime::DateTime (uint32_t t) {
t -= SECONDS_FROM_1970_TO_2000; // bring to 2000 timestamp from 1970
ss = t % 60;
t /= 60;
mm = t % 60;
t /= 60;
hh = t % 24;
uint16_t days = t / 24;
uint8_t leap;
for (yOff = 0; ; ++yOff) {
leap = yOff % 4 == 0;
if (days < 365 + leap)
break;
days -= 365 + leap;
}
for (m = 1; ; ++m) {
uint8_t daysPerMonth = pgm_read_byte(daysInMonth + m - 1);
if (leap && m == 2)
++daysPerMonth;
if (days < daysPerMonth)
break;
days -= daysPerMonth;
}
d = days + 1;
}
DateTime::DateTime (uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t min, uint8_t sec) {
if (year >= 2000)
year -= 2000;
yOff = year;
m = month;
d = day;
hh = hour;
mm = min;
ss = sec;
}
static uint8_t conv2d(const char* p) {
uint8_t v = 0;
if ('0' <= *p && *p <= '9')
v = *p - '0';
return 10 * v + *++p - '0';
}
// UNIX time: IS CORRECT ONLY WHEN SET TO UTC!!!
uint32_t DateTime::unixtime(void) const {
uint32_t t;
uint16_t days = date2days(yOff, m, d);
t = time2long(days, hh, mm, ss);
t += SECONDS_FROM_1970_TO_2000; // seconds from 1970 to 2000
return t;
}
// Slightly modified from JeeLabs / Ladyada
// Get all date/time at once to avoid rollover (e.g., minute/second don't match)
static uint8_t bcd2bin (uint8_t val) { return val - 6 * (val >> 4); }
static uint8_t bin2bcd (uint8_t val) { return val + 6 * (val / 10); }
DateTime RTClib::now() {
Wire.beginTransmission(CLOCK_ADDRESS);
Wire.write(0); // This is the first register address (Seconds)
// We'll read from here on for 7 bytes: secs reg, minutes reg, hours, days, months and years.
Wire.endTransmission();
Wire.requestFrom(CLOCK_ADDRESS, 7);
uint8_t ss = bcd2bin(Wire.read() & 0x7F);
uint8_t mm = bcd2bin(Wire.read());
uint8_t hh = bcd2bin(Wire.read());
Wire.read();
uint8_t d = bcd2bin(Wire.read());
uint8_t m = bcd2bin(Wire.read());
uint16_t y = bcd2bin(Wire.read()) + 2000;
return DateTime (y, m, d, hh, mm, ss);
}
/ ERIC'S ORIGINAL CODE FOLLOWS /
byte DS3231::getSecond() {
Wire.beginTransmission(CLOCK_ADDRESS);
Wire.write(0x00);
Wire.endTransmission();
Wire.requestFrom(CLOCK_ADDRESS, 1);
return bcdToDec(Wire.read());
}
byte DS3231::getMinute() {
Wire.beginTransmission(CLOCK_ADDRESS);
Wire.write(0x01);
Wire.endTransmission();
Wire.requestFrom(CLOCK_ADDRESS, 1);
return bcdToDec(Wire.read());
}
byte DS3231::getHour(bool& h12, bool& PM) {
byte temp_buffer;
byte hour;
Wire.beginTransmission(CLOCK_ADDRESS);
Wire.write(0x02);
Wire.endTransmission();
Wire.requestFrom(CLOCK_ADDRESS, 1);
temp_buffer = Wire.read();
h12 = temp_buffer & 0b01000000;
if (h12) {
PM = temp_buffer & 0b00100000;
hour = bcdToDec(temp_buffer & 0b00011111);
} else {
hour = bcdToDec(temp_buffer & 0b00111111);
}
return hour;
}
byte DS3231::getDoW() {
Wire.beginTransmission(CLOCK_ADDRESS);
Wire.write(0x03);
Wire.endTransmission();
Wire.requestFrom(CLOCK_ADDRESS, 1);
return bcdToDec(Wire.read());
}
byte DS3231::getDate() {
Wire.beginTransmission(CLOCK_ADDRESS);
Wire.write(0x04);
Wire.endTransmission();
Wire.requestFrom(CLOCK_ADDRESS, 1);
return bcdToDec(Wire.read());
}
byte DS3231::getMonth(bool& Century) {
byte temp_buffer;
byte hour;
Wire.beginTransmission(CLOCK_ADDRESS);
Wire.write(0x05);
Wire.endTransmission();
Wire.requestFrom(CLOCK_ADDRESS, 1);
temp_buffer = Wire.read();
Century = temp_buffer & 0b10000000;
return (bcdToDec(temp_buffer & 0b01111111)) ;
}
byte DS3231::getYear() {
Wire.beginTransmission(CLOCK_ADDRESS);
Wire.write(0x06);
Wire.endTransmission();
Wire.requestFrom(CLOCK_ADDRESS, 1);
return bcdToDec(Wire.read());
}
void DS3231::setSecond(byte Second) {
// Sets the seconds
// This function also resets the Oscillator Stop Flag, which is set
// whenever power is interrupted.
Wire.beginTransmission(CLOCK_ADDRESS);
Wire.write(0x00);
Wire.write(decToBcd(Second));
Wire.endTransmission();
// Clear OSF flag
byte temp_buffer = readControlByte(1);
writeControlByte((temp_buffer & 0b01111111), 1);
}
void DS3231::setMinute(byte Minute) {
// Sets the minutes
Wire.beginTransmission(CLOCK_ADDRESS);
Wire.write(0x01);
Wire.write(decToBcd(Minute));
Wire.endTransmission();
}
void DS3231::setHour(byte Hour) {
// Sets the hour, without changing 12/24h mode.
// The hour must be in 24h format.
bool h12;
// Start by figuring out what the 12/24 mode is
Wire.beginTransmission(CLOCK_ADDRESS);
Wire.write(0x02);
Wire.endTransmission();
Wire.requestFrom(CLOCK_ADDRESS, 1);
h12 = (Wire.read() & 0b01000000);
// if h12 is true, it's 12h mode; false is 24h.
if (h12) {
// 12 hour
if (Hour > 12) {
Hour = decToBcd(Hour-12) | 0b01100000;
} else {
Hour = decToBcd(Hour) & 0b11011111;
}
} else {
// 24 hour
Hour = decToBcd(Hour) & 0b10111111;
}
Wire.beginTransmission(CLOCK_ADDRESS);
Wire.write(0x02);
Wire.write(Hour);
Wire.endTransmission();
}
void DS3231::setDoW(byte DoW) {
// Sets the Day of Week
Wire.beginTransmission(CLOCK_ADDRESS);
Wire.write(0x03);
Wire.write(decToBcd(DoW));
Wire.endTransmission();
}
void DS3231::setDate(byte Date) {
// Sets the Date
Wire.beginTransmission(CLOCK_ADDRESS);
Wire.write(0x04);
Wire.write(decToBcd(Date));
Wire.endTransmission();
}
void DS3231::setMonth(byte Month) {
// Sets the month
Wire.beginTransmission(CLOCK_ADDRESS);
Wire.write(0x05);
Wire.write(decToBcd(Month));
Wire.endTransmission();
}
void DS3231::setYear(byte Year) {
// Sets the year
Wire.beginTransmission(CLOCK_ADDRESS);
Wire.write(0x06);
Wire.write(decToBcd(Year));
Wire.endTransmission();
}
void DS3231::setClockMode(bool h12) {
// sets the mode to 12-hour (true) or 24-hour (false).
// One thing that bothers me about how I've written this is that
// if the read and right happen at the right hourly millisecnd,
// the clock will be set back an hour. Not sure how to do it better,
// though, and as long as one doesn't set the mode frequently it's
// a very minimal risk.
// It's zero risk if you call this BEFORE setting the hour, since
// the setHour() function doesn't change this mode.
byte temp_buffer;
// Start by reading byte 0x02.
Wire.beginTransmission(CLOCK_ADDRESS);
Wire.write(0x02);
Wire.endTransmission();
Wire.requestFrom(CLOCK_ADDRESS, 1);
temp_buffer = Wire.read();
// Set the flag to the requested value:
if (h12) {
temp_buffer = temp_buffer | 0b01000000;
} else {
temp_buffer = temp_buffer & 0b10111111;
}
// Write the byte
Wire.beginTransmission(CLOCK_ADDRESS);
Wire.write(0x02);
Wire.write(temp_buffer);
Wire.endTransmission();
}
float DS3231::getTemperature() {
// Checks the internal thermometer on the DS3231 and returns the
// temperature as a floating-point value.
// Updated / modified a tiny bit from "Coding Badly" and "Tri-Again"
// http://forum.arduino.cc/index.php/topic,22301.0.html
byte tMSB, tLSB;
float temp3231;
// temp registers (11h-12h) get updated automatically every 64s
Wire.beginTransmission(CLOCK_ADDRESS);
Wire.write(0x11);
Wire.endTransmission();
Wire.requestFrom(CLOCK_ADDRESS, 2);
// Should I do more "if available" checks here?
if(Wire.available()) {
tMSB = Wire.read(); //2's complement int portion
tLSB = Wire.read(); //fraction portion
temp3231 = ((((short)tMSB << 8) | (short)tLSB) >> 6) / 4.0;
}
else {
temp3231 = -9999; // Some obvious error value
}
return temp3231;
}
void DS3231::getA1Time(byte& A1Day, byte& A1Hour, byte& A1Minute, byte& A1Second, byte& AlarmBits, bool& A1Dy, bool& A1h12, bool& A1PM) {
byte temp_buffer;
Wire.beginTransmission(CLOCK_ADDRESS);
Wire.write(0x07);
Wire.endTransmission();
Wire.requestFrom(CLOCK_ADDRESS, 4);
temp_buffer = Wire.read(); // Get A1M1 and A1 Seconds
A1Second = bcdToDec(temp_buffer & 0b01111111);
// put A1M1 bit in position 0 of DS3231_AlarmBits.
AlarmBits = AlarmBits | (temp_buffer & 0b10000000)>>7;
temp_buffer = Wire.read(); // Get A1M2 and A1 minutes
A1Minute = bcdToDec(temp_buffer & 0b01111111);
// put A1M2 bit in position 1 of DS3231_AlarmBits.
AlarmBits = AlarmBits | (temp_buffer & 0b10000000)>>6;
temp_buffer = Wire.read(); // Get A1M3 and A1 Hour
// put A1M3 bit in position 2 of DS3231_AlarmBits.
AlarmBits = AlarmBits | (temp_buffer & 0b10000000)>>5;
// determine A1 12/24 mode
A1h12 = temp_buffer & 0b01000000;
if (A1h12) {
A1PM = temp_buffer & 0b00100000; // determine am/pm
A1Hour = bcdToDec(temp_buffer & 0b00011111); // 12-hour
} else {
A1Hour = bcdToDec(temp_buffer & 0b00111111); // 24-hour
}
temp_buffer = Wire.read(); // Get A1M4 and A1 Day/Date
// put A1M3 bit in position 3 of DS3231_AlarmBits.
AlarmBits = AlarmBits | (temp_buffer & 0b10000000)>>4;
// determine A1 day or date flag
A1Dy = (temp_buffer & 0b01000000)>>6;
if (A1Dy) {
// alarm is by day of week, not date.
A1Day = bcdToDec(temp_buffer & 0b00001111);
} else {
// alarm is by date, not day of week.
A1Day = bcdToDec(temp_buffer & 0b00111111);
}
}
void DS3231::getA2Time(byte& A2Day, byte& A2Hour, byte& A2Minute, byte& AlarmBits, bool& A2Dy, bool& A2h12, bool& A2PM) {
byte temp_buffer;
Wire.beginTransmission(CLOCK_ADDRESS);
Wire.write(0x0b);
Wire.endTransmission();
Wire.requestFrom(CLOCK_ADDRESS, 3);
temp_buffer = Wire.read(); // Get A2M2 and A2 Minutes
A2Minute = bcdToDec(temp_buffer & 0b01111111);
// put A2M2 bit in position 4 of DS3231_AlarmBits.
AlarmBits = AlarmBits | (temp_buffer & 0b10000000)>>3;
temp_buffer = Wire.read(); // Get A2M3 and A2 Hour
// put A2M3 bit in position 5 of DS3231_AlarmBits.
AlarmBits = AlarmBits | (temp_buffer & 0b10000000)>>2;
// determine A2 12/24 mode
A2h12 = temp_buffer & 0b01000000;
if (A2h12) {
A2PM = temp_buffer & 0b00100000; // determine am/pm
A2Hour = bcdToDec(temp_buffer & 0b00011111); // 12-hour
} else {
A2Hour = bcdToDec(temp_buffer & 0b00111111); // 24-hour
}
temp_buffer = Wire.read(); // Get A2M4 and A1 Day/Date
// put A2M4 bit in position 6 of DS3231_AlarmBits.
AlarmBits = AlarmBits | (temp_buffer & 0b10000000)>>1;
// determine A2 day or date flag
A2Dy = (temp_buffer & 0b01000000)>>6;
if (A2Dy) {
// alarm is by day of week, not date.
A2Day = bcdToDec(temp_buffer & 0b00001111);
} else {
// alarm is by date, not day of week.
A2Day = bcdToDec(temp_buffer & 0b00111111);
}
}
void DS3231::setA1Time(byte A1Day, byte A1Hour, byte A1Minute, byte A1Second, byte AlarmBits, bool A1Dy, bool A1h12, bool A1PM) {
// Sets the alarm-1 date and time on the DS3231, using A1* information
byte temp_buffer;
Wire.beginTransmission(CLOCK_ADDRESS);
Wire.write(0x07); // A1 starts at 07h
// Send A1 second and A1M1
Wire.write(decToBcd(A1Second) | ((AlarmBits & 0b00000001) << 7));
// Send A1 Minute and A1M2
Wire.write(decToBcd(A1Minute) | ((AlarmBits & 0b00000010) << 6));
// Figure out A1 hour
if (A1h12) {
// Start by converting existing time to h12 if it was given in 24h.
if (A1Hour > 12) {
// well, then, this obviously isn't a h12 time, is it?
A1Hour = A1Hour - 12;
A1PM = true;
}
if (A1PM) {
// Afternoon
// Convert the hour to BCD and add appropriate flags.
temp_buffer = decToBcd(A1Hour) | 0b01100000;
} else {
// Morning
// Convert the hour to BCD and add appropriate flags.
temp_buffer = decToBcd(A1Hour) | 0b01000000;
}
} else {
// Now for 24h
temp_buffer = decToBcd(A1Hour);
}
temp_buffer = temp_buffer | ((AlarmBits & 0b00000100)<<5);
// A1 hour is figured out, send it
Wire.write(temp_buffer);
// Figure out A1 day/date and A1M4
temp_buffer = ((AlarmBits & 0b00001000)<<4) | decToBcd(A1Day);
if (A1Dy) {
// Set A1 Day/Date flag (Otherwise it's zero)
temp_buffer = temp_buffer | 0b01000000;
}
Wire.write(temp_buffer);
// All done!
Wire.endTransmission();
}
void DS3231::setA2Time(byte A2Day, byte A2Hour, byte A2Minute, byte AlarmBits, bool A2Dy, bool A2h12, bool A2PM) {
// Sets the alarm-2 date and time on the DS3231, using A2* information
byte temp_buffer;
Wire.beginTransmission(CLOCK_ADDRESS);
Wire.write(0x0b); // A1 starts at 0bh
// Send A2 Minute and A2M2
Wire.write(decToBcd(A2Minute) | ((AlarmBits & 0b00010000) << 3));
// Figure out A2 hour
if (A2h12) {
// Start by converting existing time to h12 if it was given in 24h.
if (A2Hour > 12) {
// well, then, this obviously isn't a h12 time, is it?
A2Hour = A2Hour - 12;
A2PM = true;
}
if (A2PM) {
// Afternoon
// Convert the hour to BCD and add appropriate flags.
temp_buffer = decToBcd(A2Hour) | 0b01100000;
} else {
// Morning
// Convert the hour to BCD and add appropriate flags.
temp_buffer = decToBcd(A2Hour) | 0b01000000;
}
} else {
// Now for 24h
temp_buffer = decToBcd(A2Hour);
}
// add in A2M3 bit
temp_buffer = temp_buffer | ((AlarmBits & 0b00100000)<<2);
// A2 hour is figured out, send it
Wire.write(temp_buffer);
// Figure out A2 day/date and A2M4
temp_buffer = ((AlarmBits & 0b01000000)<<1) | decToBcd(A2Day);
if (A2Dy) {
// Set A2 Day/Date flag (Otherwise it's zero)
temp_buffer = temp_buffer | 0b01000000;
}
Wire.write(temp_buffer);
// All done!
Wire.endTransmission();
}
void DS3231::turnOnAlarm(byte Alarm) {
// turns on alarm number "Alarm". Defaults to 2 if Alarm is not 1.
byte temp_buffer = readControlByte(0);
// modify control byte
if (Alarm == 1) {
temp_buffer = temp_buffer | 0b00000101;
} else {
temp_buffer = temp_buffer | 0b00000110;
}
writeControlByte(temp_buffer, 0);
}
void DS3231::turnOffAlarm(byte Alarm) {
// turns off alarm number "Alarm". Defaults to 2 if Alarm is not 1.
// Leaves interrupt pin alone.
byte temp_buffer = readControlByte(0);
// modify control byte
if (Alarm == 1) {
temp_buffer = temp_buffer & 0b11111110;
} else {
temp_buffer = temp_buffer & 0b11111101;
}
writeControlByte(temp_buffer, 0);
}
bool DS3231::checkAlarmEnabled(byte Alarm) {
// Checks whether the given alarm is enabled.
byte result = 0x0;
byte temp_buffer = readControlByte(0);
if (Alarm == 1) {
result = temp_buffer & 0b00000001;
} else {
result = temp_buffer & 0b00000010;
}
return result;
}
bool DS3231::checkIfAlarm(byte Alarm) {
// Checks whether alarm 1 or alarm 2 flag is on, returns T/F accordingly.
// Turns flag off, also.
// defaults to checking alarm 2, unless Alarm == 1.
byte result;
byte temp_buffer = readControlByte(1);
if (Alarm == 1) {
// Did alarm 1 go off?
result = temp_buffer & 0b00000001;
// clear flag
temp_buffer = temp_buffer & 0b11111110;
} else {
// Did alarm 2 go off?
result = temp_buffer & 0b00000010;
// clear flag
temp_buffer = temp_buffer & 0b11111101;
}
writeControlByte(temp_buffer, 1);
return result;
}
void DS3231::enableOscillator(bool TF, bool battery, byte frequency) {
// turns oscillator on or off. True is on, false is off.
// if battery is true, turns on even for battery-only operation,
// otherwise turns off if Vcc is off.
// frequency must be 0, 1, 2, or 3.
// 0 = 1 Hz
// 1 = 1.024 kHz
// 2 = 4.096 kHz
// 3 = 8.192 kHz (Default if frequency byte is out of range)
if (frequency > 3) frequency = 3;
// read control byte in, but zero out current state of RS2 and RS1.
byte temp_buffer = readControlByte(0) & 0b11100111;
if (battery) {
// turn on BBSQW flag
temp_buffer = temp_buffer | 0b01000000;
} else {
// turn off BBSQW flag
temp_buffer = temp_buffer & 0b10111111;
}
if (TF) {
// set ~EOSC to 0 and INTCN to zero.
temp_buffer = temp_buffer & 0b01111011;
} else {
// set ~EOSC to 1, leave INTCN as is.
temp_buffer = temp_buffer | 0b10000000;
}
// shift frequency into bits 3 and 4 and set.
frequency = frequency << 3;
temp_buffer = temp_buffer | frequency;
// And write the control bits
writeControlByte(temp_buffer, 0);
}
void DS3231::enable32kHz(bool TF) {
// turn 32kHz pin on or off
byte temp_buffer = readControlByte(1);
if (TF) {
// turn on 32kHz pin
temp_buffer = temp_buffer | 0b00001000;
} else {
// turn off 32kHz pin
temp_buffer = temp_buffer & 0b11110111;
}
writeControlByte(temp_buffer, 1);
}
bool DS3231::oscillatorCheck() {
// Returns false if the oscillator has been off for some reason.
// If this is the case, the time is probably not correct.
byte temp_buffer = readControlByte(1);
bool result = true;
if (temp_buffer & 0b10000000) {
// Oscillator Stop Flag (OSF) is set, so return false.
result = false;
}
return result;
}
/*****************************************
Private Functions
*****************************************/
byte DS3231::decToBcd(byte val) {
// Convert normal decimal numbers to binary coded decimal
return ( (val/10*16) + (val%10) );
}
byte DS3231::bcdToDec(byte val) {
// Convert binary coded decimal to normal decimal numbers
return ( (val/16*10) + (val%16) );
}
byte DS3231::readControlByte(bool which) {
// Read selected control byte
// first byte (0) is 0x0e, second (1) is 0x0f
Wire.beginTransmission(CLOCK_ADDRESS);
if (which) {
// second control byte
Wire.write(0x0f);
} else {
// first control byte
Wire.write(0x0e);
}
Wire.endTransmission();
Wire.requestFrom(CLOCK_ADDRESS, 1);
return Wire.read();
}
void DS3231::writeControlByte(byte control, bool which) {
// Write the selected control byte.
// which=false -> 0x0e, true->0x0f.
Wire.beginTransmission(CLOCK_ADDRESS);
if (which) {
Wire.write(0x0f);
} else {
Wire.write(0x0e);
}
Wire.write(control);
Wire.endTransmission();
}