#include "Wire.h"
#include "time.h"

// 7-bit RTC address on the I2C bus, specified in the datasheet
#define RTC_ADDR          0x68
#define RTC_DATA_SIZE     7

// Specify 1 to set a new time at initialization
#define RTC_SET_NEW_TIME  0

uint8_t rxBytes[15];
struct tm newTime;
char str[32];

void setup() 
{
  Serial.begin(115200);
  Serial.println("Start I2C rtc");

  // Initializing the device as a master on the I2C bus
  Wire.begin();

  // Set new time if necessary
  if (RTC_SET_NEW_TIME) {
    initDS1307(); // Initialization of ds1307
    writeTimeToRtc(); // setting time is written inside the function
  }
}

void loop() 
{
  // Read and display the current time
  readTimeFromRtc();
  delay(2000);
}

// ------------------- rtc ------------------- //
// Function for initializing the DS1307 chip
void initDS1307(void)
{
  /* According to the datasheet, the bits in three registers 
  are checked: 0x00, 0x02 and 0x07
  */
  uint8_t oneByte;
  Wire.requestFrom(RTC_ADDR, 1, 0x00, 1, 1);
  // Get data
  if(Wire.available())
    oneByte = Wire.read();
  if (oneByte & 0x80) {
    Wire.beginTransmission(RTC_ADDR); // Start of transaction
    Wire.write(0x00);                 // Writing command for a transaction
    Wire.write(oneByte & ~0x80);      // Writing data for a transaction
    Wire.endTransmission();           // Completing a transaction
  }

  Wire.requestFrom(RTC_ADDR, 1, 0x02, 1, 1);
  // Get data
  if(Wire.available())
    oneByte = Wire.read();
  if (oneByte & 0x40) {
    Wire.beginTransmission(RTC_ADDR); // Start of transaction
    Wire.write(0x02);                 // Writing command for a transaction
    Wire.write(oneByte & ~0x40);      // Writing data for a transaction
    Wire.endTransmission();           // Completing a transaction
  }

  Wire.requestFrom(RTC_ADDR, 1, 0x07, 1, 1);
  // Get data
  if(Wire.available())
    oneByte = Wire.read();
  if ((oneByte & 0x03) || !(oneByte & 0x10)) {
    Wire.beginTransmission(RTC_ADDR);   // Start of transaction
    Wire.write(0x07);                   // Writing command for a transaction
    Wire.write(oneByte & ~0x03 | 0x10); // Writing data for a transaction
    Wire.endTransmission();             // Completing a transaction
  }
}

// Function to write new time to rtc
void writeTimeToRtc(void)
{
  /* The data to be written is located in the common buffer.
  First, the register address is specified - writing starts from register 0x00.
  Next, the values ​​of seconds, minutes, etc. are listed in accordance with the 
  order of the corresponding registers, specified in the datasheet for ds1307. 
  The values ​​are pre-translated into binary-decimal format
  */
  //                       sec           min         hour        wday        mday        month       year
  uint8_t buf[] = {0x00, dec2bcd(00), dec2bcd(01), dec2bcd(12), dec2bcd(2), dec2bcd(1), dec2bcd(1), dec2bcd(24)};

  Wire.beginTransmission(RTC_ADDR);         // Start of transaction
  Wire.write(buf, sizeof(buf));             // Writing data for a transaction
  uint8_t result = Wire.endTransmission();  // Completing a transaction
  
  // Show results
  if (result == 0)
    Serial.println("New time successfully set");
  else
    Serial.println("New time set failed");
}

// Function to read time from rtc
void readTimeFromRtc(void)
{
  /* Send a request to receive data from rtc. When reading from rtc, you must
  first specify from which register to start reading. To do this, use the extended 
  version of the requestFrom() function. In the arguments specify:
  - device address,
  - the number of bytes we want to read,
  - the address of the starting register,
  - the size of the address of the starting register in bytes,
  - 1 - generate a stop condition after the request, 0 - do not generate
  */
  Wire.requestFrom(RTC_ADDR, RTC_DATA_SIZE, 0x00, 1, 1);
  // Get data
  uint8_t i = 0;
  while(Wire.available())
    rxBytes[i++] = Wire.read();

  // Show new time in the port upon successful reading
  if (i == RTC_DATA_SIZE)
    parseAndShowTime();
  else
    Serial.println("New time read failed");
}

// Functions for converting binary-decimal code to decimal and back
uint8_t bcd2dec(uint8_t val)
{
  return (val >> 4) * 10 + (val & 0x0f);
}

uint8_t dec2bcd(uint8_t val)
{
  return ((val / 10) << 4) + (val % 10);
}

// Function for format and output the current time to the port
void parseAndShowTime(void)
{
  /* take the values ​​for time output from the received data buffer.
  Convert the values ​​into decimal format and adjust them in accordance with
  the datasheet for ds1307
  */
  newTime.tm_sec = bcd2dec(rxBytes[0] & 0x7f);
  newTime.tm_min = bcd2dec(rxBytes[1]);
  if (rxBytes[2] & (1 << 6))
  {
    // RTC in 12 hour mode
    newTime.tm_hour = bcd2dec(rxBytes[2] & 0x1f) - 1;
    if (rxBytes[2] & (1 << 5))
      newTime.tm_hour += 12;
  }
  else
      newTime.tm_hour = bcd2dec(rxBytes[2] & 0x3f);
  newTime.tm_wday = bcd2dec(rxBytes[3])-1;
  newTime.tm_mday = bcd2dec(rxBytes[4]);
  newTime.tm_mon  = bcd2dec(rxBytes[5])-1;
  // Subtract 1900 so that the year is correctly displayed in the struct tm type structure
  newTime.tm_year = bcd2dec(rxBytes[6])+2000-1900;
  
  // The received time is output to the port in the format Sun Aug 19 02:56:02 2012
  strftime(str, sizeof(str), "%c", &newTime);
  Serial.println(str);
}
