I'm new to Arduino, AVR, microcontrollers, and not proficient with C.
Here is my attempt at a library for the DS1307 real-time clock, which connect via I2C.
It covers the basics of stopping and starting the clock, setting (some validation) and read time components.
extern "C" {
#include <../Wire/Wire.h>
}
#include "DS1307.h"
DS1307::DS1307()
{
Wire.begin();
}
DS1307 RTC=DS1307();
// PRIVATE FUNCTIONS
// Aquire data from the RTC chip in BCD format
// refresh the buffer
void DS1307::read(void)
{
// use the Wire lib to connect to tho rtc
// reset the resgiter pointer to zero
Wire.beginTransmission(DS1307_CTRL_ID);
Wire.send(0x00);
Wire.endTransmission();
// request the 7 bytes of data (secs, min, hr, dow, date. mth, yr)
Wire.requestFrom(DS1307_CTRL_ID, 7);
for(int i=0; i<7; i++)
{
// store data in raw bcd format
rtc_bcd[i]=Wire.receive();
}
}
// update the data on the IC from the bcd formatted data in the buffer
void DS1307::save(void)
{
Wire.beginTransmission(DS1307_CTRL_ID);
Wire.send(0x00); // reset register pointer
for(int i=0; i<7; i++)
{
Wire.send(rtc_bcd[i]);
}
Wire.endTransmission();
}
// PUBLIC FUNCTIONS
void DS1307::get(int *rtc, boolean refresh) // Aquire data from buffer and convert to int, refresh buffer if required
{
if(refresh) read();
for(int i=0;i<7;i++) // cycle through each component, create array of data
{
rtc[i]=get(i, 0);
}
}
int DS1307::get(int c, boolean refresh) // aquire individual RTC item from buffer, return as int, refresh buffer if required
{
if(refresh) read();
int v=-1;
switch(c)
{
case DS1307_SEC:
v=(10*((rtc_bcd[DS1307_SEC] & DS1307_HI_SEC)>>4))+(rtc_bcd[DS1307_SEC] & DS1307_LO_BCD);
break;
case DS1307_MIN:
v=(10*((rtc_bcd[DS1307_MIN] & DS1307_HI_MIN)>>4))+(rtc_bcd[DS1307_MIN] & DS1307_LO_BCD);
break;
case DS1307_HR:
v=(10*((rtc_bcd[DS1307_HR] & DS1307_HI_HR)>>4))+(rtc_bcd[DS1307_HR] & DS1307_LO_BCD);
break;
case DS1307_DOW:
v=rtc_bcd[DS1307_DOW] & DS1307_LO_DOW;
break;
case DS1307_DATE:
v=(10*((rtc_bcd[DS1307_DATE] & DS1307_HI_DATE)>>4))+(rtc_bcd[DS1307_DATE] & DS1307_LO_BCD);
break;
case DS1307_MTH:
v=(10*((rtc_bcd[DS1307_MTH] & DS1307_HI_MTH)>>4))+(rtc_bcd[DS1307_MTH] & DS1307_LO_BCD);
break;
case DS1307_YR:
v=(10*((rtc_bcd[DS1307_YR] & DS1307_HI_YR)>>4))+(rtc_bcd[DS1307_YR] & DS1307_LO_BCD)+DS1307_BASE_YR;
break;
} // end switch
return v;
}
void DS1307::set(int c, int v) // Update buffer, then update the chip
{
switch(c)
{
case DS1307_SEC:
if(v<60 && v>-1)
{
//preserve existing clock state (running/stopped)
int state=rtc_bcd[DS1307_SEC] & DS1307_CLOCKHALT;
rtc_bcd[DS1307_SEC]=state | ((v / 10)<<4) + (v % 10);
}
break;
case DS1307_MIN:
if(v<60 && v>-1)
{
rtc_bcd[DS1307_MIN]=((v / 10)<<4) + (v % 10);
}
break;
case DS1307_HR:
// TODO : AM/PM 12HR/24HR
if(v<24 && v>-1)
{
rtc_bcd[DS1307_HR]=((v / 10)<<4) + (v % 10);
}
break;
case DS1307_DOW:
if(v<8 && v>-1)
{
rtc_bcd[DS1307_DOW]=v;
}
break;
case DS1307_DATE:
if(v<31 && v>-1)
{
rtc_bcd[DS1307_DATE]=((v / 10)<<4) + (v % 10);
}
break;
case DS1307_MTH:
if(v<13 && v>-1)
{
rtc_bcd[DS1307_MTH]=((v / 10)<<4) + (v % 10);
}
break;
case DS1307_YR:
if(v<13 && v>-1)
{
rtc_bcd[DS1307_YR]=((v / 10)<<4) + (v % 10);
}
break;
} // end switch
save();
}
void DS1307::stop(void)
{
// set the ClockHalt bit high to stop the rtc
// this bit is part of the seconds byte
rtc_bcd[DS1307_SEC]=rtc_bcd[DS1307_SEC] | DS1307_CLOCKHALT;
save();
}
void DS1307::start(void)
{
// unset the ClockHalt bit to start the rtc
// TODO : preserve existing seconds
rtc_bcd[DS1307_SEC]=0;
save();
}
I like it I've adapted the library for use with the DS1337 RTC. One issue I have with time setting is that it's hard to set the time wrong a source of epoch (serial, ntp).
What the differenc ebetween the DS1307 and DS1337 (leet!), is it just the alarms ?
I have some code for setting the time, but really it's no more complex than checking the serial buffer for a command byte and then assuming the following 7 bytes are decimal Sec, Min, Hr, DOW, Date, MTh, Yr.
All it has more is the geek factore It also has a slightly different register layout. This mostly affected the stop/start functions. Also the ds1337 come in a (C) version which has a built in xtal, which can be practical.
As for setting it, sure that's what I though to, but you can't really send bytes through screen. As for epoch, I have some plan on adding NTP... which will deliver the time using an epoch stamp. What ever, that's all just playing with numbers
Here's what the port looks like.
extern "C" {
#include <Wire/Wire.h>
#include <avr/pgmspace.h>
}
#include "DS1337.h"
#include "programStrings.h"
DS1337::DS1337()
{
Wire.begin();
}
DS1337 RTC=DS1337();
// PRIVATE FUNCTIONS
// Aquire data from the RTC chip in BCD format
// refresh the buffer
void DS1337::read(void)
{
// use the Wire lib to connect to tho rtc
// reset the resgiter pointer to zero
Wire.beginTransmission(DS1337_CTRL_ID);
Wire.send(0x00);
Wire.endTransmission();
// request the 7 bytes of data (secs, min, hr, dow, date. mth, yr)
Wire.requestFrom(DS1337_CTRL_ID, 7);
for(int i=0; i<7; i++)
{
// store data in raw bcd format
if (Wire.available())
rtc_bcd[i]=Wire.receive();
}
}
// update the data on the IC from the bcd formatted data in the buffer
void DS1337::save(void)
{
Wire.beginTransmission(DS1337_CTRL_ID);
Wire.send(0x00); // reset register pointer
for(int i=0; i<7; i++)
{
Wire.send(rtc_bcd[i]);
}
Wire.endTransmission();
}
unsigned char DS1337::getRegister(unsigned char registerNumber)
{
Wire.beginTransmission(DS1337_CTRL_ID);
Wire.send(registerNumber);
Wire.endTransmission();
Wire.requestFrom(DS1337_CTRL_ID, 1);
return Wire.receive();
}
void DS1337::setRegister(unsigned char registerNumber, unsigned char registerMask)
{
Wire.beginTransmission(DS1337_CTRL_ID);
Wire.send(registerNumber); // reset register pointer
Wire.send(registerMask);
Wire.endTransmission();
}
void DS1337::unsetRegister(unsigned char registerNumber, unsigned char registerMask)
{
setRegister(registerNumber, (getRegister(registerNumber) & ~registerNumber));
}
// PUBLIC FUNCTIONS
void DS1337::get(int *rtc, boolean refresh) // Aquire data from buffer and convert to int, refresh buffer if required
{
if(refresh) read();
for(int i=0;i<7;i++) // cycle through each component, create array of data
{
rtc[i]=get(i, 0);
}
}
int DS1337::get(int c, boolean refresh) // aquire individual RTC item from buffer, return as int, refresh buffer if required
{
if(refresh) read();
int v=-1;
switch(c)
{
case DS1337_SEC:
v=(10*((rtc_bcd[DS1337_SEC] & DS1337_HI_SEC)>>4))+(rtc_bcd[DS1337_SEC] & DS1337_LO_BCD);
break;
case DS1337_MIN:
v=(10*((rtc_bcd[DS1337_MIN] & DS1337_HI_MIN)>>4))+(rtc_bcd[DS1337_MIN] & DS1337_LO_BCD);
break;
case DS1337_HR:
v=(10*((rtc_bcd[DS1337_HR] & DS1337_HI_HR)>>4))+(rtc_bcd[DS1337_HR] & DS1337_LO_BCD);
break;
case DS1337_DOW:
v=rtc_bcd[DS1337_DOW] & DS1337_LO_DOW;
break;
case DS1337_DATE:
v=(10*((rtc_bcd[DS1337_DATE] & DS1337_HI_DATE)>>4))+(rtc_bcd[DS1337_DATE] & DS1337_LO_BCD);
break;
case DS1337_MTH:
v=(10*((rtc_bcd[DS1337_MTH] & DS1337_HI_MTH)>>4))+(rtc_bcd[DS1337_MTH] & DS1337_LO_BCD);
break;
case DS1337_YR:
v=(10*((rtc_bcd[DS1337_YR] & DS1337_HI_YR)>>4))+(rtc_bcd[DS1337_YR] & DS1337_LO_BCD)+DS1337_BASE_YR;
break;
} // end switch
return v;
}
void DS1337::set(int c, int v) // Update buffer, then update the chip
{
switch(c)
{
case DS1337_SEC:
if(v<60 && v>-1)
{
//preserve existing clock state (running/stopped)
int state=rtc_bcd[DS1337_SEC] & DS1337_CLOCKHALT;
rtc_bcd[DS1337_SEC]=state | ((v / 10)<<4) + (v % 10);
}
break;
case DS1337_MIN:
if(v<60 && v>-1)
{
rtc_bcd[DS1337_MIN]=((v / 10)<<4) + (v % 10);
}
break;
case DS1337_HR:
// TODO : AM/PM 12HR/24HR
if(v<24 && v>-1)
{
rtc_bcd[DS1337_HR]=((v / 10)<<4) + (v % 10);
}
break;
case DS1337_DOW:
if(v<8 && v>-1)
{
rtc_bcd[DS1337_DOW]=v;
}
break;
case DS1337_DATE:
if(v<31 && v>-1)
{
rtc_bcd[DS1337_DATE]=((v / 10)<<4) + (v % 10);
}
break;
case DS1337_MTH:
if(v<13 && v>-1)
{
rtc_bcd[DS1337_MTH]=((v / 10)<<4) + (v % 10);
}
break;
case DS1337_YR:
if(v<13 && v>-1)
{
rtc_bcd[DS1337_YR]=((v / 10)<<4) + (v % 10);
}
break;
} // end switch
save();
}
void DS1337::stop(void)
{
setRegister(DS1337_SP, DS1337_SP_EOSC);
}
void DS1337::start(void)
{
unsetRegister(DS1337_SP, DS1337_SP_EOSC);
}
Yeah the clock definitely doesn't want to start. I don't see anything in the datasheet about the high bit of the seconds register. I believe the stop/start is tied to EOSC, which I handle in the start/stop functions.
Apparently the internal xtal's capacitance already matched the DS's internal caps. But I had two 22pF caps there... removed them and bam
I'll post some more code and stuff a bit later
EDIT: this may still not be perfect, the xtal might want some caps, cause the OSF flag is always risen and the possible reasons are
The first time power is applied.
The voltage present on VCC is insufficient to support oscillation.
The [not]EOSC bit is turned off.
External influences on the crystal (e.g., noise, leakage, etc.).
It's not the first time power is applied, vcc is fine, EOSC is turned off (note reason 2 has [not]!). So that leaves only reason 4. But is seems to work just fine. I'm fooling around with some Epoch code as we speak.
Here it is.. the whole library. It includes a test in the init to detect the RTC (if you have the I2C scanner mod I posted here). Setting the time using the old method or the new epoch functions which also calculates US and EU (even pre 2006 if needed!) DST and calculates the GMT offset (configurable at compile). And yes that function is almost as big as the rest of the driver! The old way of getting the time is still the current one as it's a lot more practical for printing in a strftime-less environment. But for good measure I added a way to get the current Epoch (doesn't care about dst!, but if you need that it should be easy to fix). My only issue right now is that OSCF (oscillator flag), which would be nice for the original time setting condition. But the time seems to stay accurate (at least after a few hours).
The RTC config "rtcConfig.h":
#ifndef RTC_CONFIG_H
#defin#ifndef RTC_CONFIG_H
#define RTC_CONFIG_H
/**
* Set this to your local GMT offset
* Comment to skip GMT offset calculations
**/
#define RTC_GMT_OFFSET -5
/**
* Select your DST type
* Comment all to skip DST calculation
**/
#define RTC_DST_TYPE 0 /* US */
//#define RTC_DST_TYPE 1 /* EU */
// Uncomment this only if you need to check DST prior to 2006
//#define RTC_CHECK_OLD_DST
#define RTC_DOW_1 PSTR("Sun") /* First day of the week */
#define RTC_DOW_2 PSTR("Mon")
#define RTC_DOW_3 PSTR("Tue")
#define RTC_DOW_4 PSTR("Wed")
#define RTC_DOW_5 PSTR("Thu")
#define RTC_DOW_6 PSTR("Fri")
#define RTC_DOW_7 PSTR("Sat")
#define RTC_DOW_0 RTC_DOW_4 /* Wednesday is the first day of Epoch: This probably shouldn't change! */
#endif
Rest to follow....
EDIT: Pre 2006 check is now optional to save space.
void DS1337::clockSetWithUTS(uint32_t unixTimeStamp, boolean correctedTime)
{
uint16_t leapCorrection = 0;
uint32_t tt;
uint8_t thisDate;
int ii;
#if defined(RTC_DST_TYPE)
uint16_t thisYear;
#endif
uint16_t year;
const uint16_t monthcount[] = {0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};
/**
* Calculate GMT and DST
**/
#if defined(RTC_GMT_OFFSET)
if (!correctedTime) unixTimeStamp = unixTimeStamp + (RTC_GMT_OFFSET * 3600);
#endif
// Years
tt = unixTimeStamp / 3600 / 24 / 365;
year = tt + 1970;
#if defined(RTC_DST_TYPE)
if (!correctedTime) {
thisYear = year;
}
#endif
// Set the century bit
if (tt > 30) {
rtc_bcd[DS1337_MTH] = (rtc_bcd[DS1337_MTH] | DS1337_LO_CNTY);
tt-= 30;
} else {
rtc_bcd[DS1337_MTH] = (rtc_bcd[DS1337_MTH] & ~DS1337_LO_CNTY);
}
// Set the year
rtc_bcd[DS1337_YR] = binToBcd(tt);
// Number of days left in the year
tt = (unixTimeStamp%31536000 / 3600 / 24) + 1;
// leap year correction
for (year--; year > 1970; year--) {
if (isleap(year))
{
leapCorrection++;
tt--;
}
}
// Set the month
for (ii = 1; ii < 12; ii++)
{
if (monthcount[ii+1] > (tt + ((ii == 2 && isleap(thisYear)) * 1)))
{
rtc_bcd[DS1337_MTH] = (binToBcd(ii) & ~DS1337_LO_CNTY) | (rtc_bcd[DS1337_MTH] & DS1337_LO_CNTY);
break;
}
}
// Date
#if defined(RTC_DST_TYPE)
if (!correctedTime) {
thisDate = tt - monthcount[ii];
}
#endif
rtc_bcd[DS1337_DATE] = binToBcd(tt - monthcount[ii]);
// Day of the week
rtc_bcd[DS1337_DOW] = ((tt)%7 + 1) & DS1337_LO_DOW;
// Hour
tt = unixTimeStamp%86400 / 3600;
rtc_bcd[DS1337_HR] = binToBcd(tt);
#if defined(RTC_DST_TYPE)
if (!correctedTime) {
uint8_t dstStartMo, dstStopMo, dstStart, dstStop;
#ifndef RTC_CHECK_OLD_DST
dstStart = (31-((thisYear * 5 / 4) + 1) % 7);
#if RTC_DST_TYPE == 1
dstStop = (31-((thisYear * 5 / 4) + 1) % 7); // EU DST
#else
dstStop = 7 - ((1 + thisYear * 5 / 4) % 7); // US DST
#endif
dstStartMo = 3;
dstStopMo = 11;
#else
if (thisYear < 2006) {
dstStart = (2+6 * thisYear - (thisYear / 4) ) % 7 + 1;
dstStop = 14 - ((1 + thisYear * 5 / 4) % 7);
dstStartMo = 4;
dstStopMo = 10;
} else {
dstStart = (31-((thisYear * 5 / 4) + 1) % 7);
#if RTC_DST_TYPE == 1
dstStop = (31-((thisYear * 5 / 4) + 1) % 7); // EU DST
#else
dstStop = 7 - ((1 + thisYear * 5 / 4) % 7); // US DST
#endif
dstStartMo = 3;
dstStopMo = 11;
}
#endif
if (ii >= dstStartMo && ii <= dstStopMo)
{
if (ii < dstStopMo)
{
if (ii > dstStartMo || thisDate > dstStart || thisDate == dstStart && tt >= 2)
{
clockSetWithUTS(unixTimeStamp + 3600, true);
return;
}
} else {
if (thisDate < dstStop || thisDate == dstStop && tt < 2)
{
clockSetWithUTS(unixTimeStamp + 3600, true);
return;
}
}
}
}
#endif
// Minutes
tt = unixTimeStamp%3600 / 60;
rtc_bcd[DS1337_MIN] = binToBcd(tt);
// Seconds
tt = (unixTimeStamp%3600)%60;
rtc_bcd[DS1337_SEC] = binToBcd(tt);
// Stop the clock
//clockStop();
// Save buffer to the RTC
clockSave();
// Restart the oscillator
//clockStart();
}
void DS1337::printRegisters(void)
{
for(int ii=0;ii<0x10;ii++)
{
SPrint("0x");
Serial.print(ii, HEX);
SPrint(" ");
Serial.println(getRegister(ii), BIN);
}
delay(200);
}
EDIT: No need to stop/start the oscillator when reading or writing to the ds1337 as the IC as in internal buffer and it will read/write only when all of the memory is read for the operation. Also, if the scan mod is used the clock is always started when the address is found and the osc. is stopped.
It's pretty well-commented, although the code does go the long way round to do I2C comms,
since it's written for the stamps that don't have any built-in.
Is using a macro in this way, better than defining a function, it looks easier (quicker).
What impact does it have on the resulting compiled code size ?
Code size is obviously bigger than using a function. On the other hand it's not only obviously faster (no call) it also reduces the function stack and parameter passing. So you really have to see what you need. I actually ended up switch that back to a function cause it was called many times and saved up something like 20~30 bytes (for both bintobcd and bcdtobin). Hope this helps
I now added both alarms support as well as their interrupts and external square wave output. The alarms can also trigger a user selected callback function. Let me know what you think!