Skip to content

File DS3231M.cpp

File List > firmware > DS3231M.cpp

Go to the documentation of this file

/*
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
 * and associated documentation files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge, publish, distribute,
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include "Wire.h"
#include "DS3231M.h"
#include "Debug.h"

#undef DEBUG_LEVEL
#define DEBUG_LEVEL DEBUG_DS3231M  
#define DEBUG_TAG "DS3231M"        

bool DS3231M::begin(void) {
  _pWire->begin();
  delay(100);
  _pWire->beginTransmission(_deviceAddr);
  if (_pWire->endTransmission() == 0)
    return true;
  else
    return false;
}

void DS3231M::setRTCTime(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second) {
  // Stores values in the rtc array and converts them to BCD (Binary-Coded Decimal) format
  // which is the format used by the DS3231M.
  rtc[0] = bin2bcd(second);
  rtc[1] = bin2bcd(minute);
  rtc[2] = bin2bcd(hour);
  rtc[3] = bin2bcd(dayOfTheWeek(year, month, day));  // Calculates the day of the week.
  rtc[4] = bin2bcd(day);
  rtc[5] = bin2bcd(month);
  rtc[6] = bin2bcd(year - 2000);  // The DS3231M chip stores the year as an offset from 2000.

  // Writes the data to the RTC registers via I2C.
  writeReg(DS3231M_REG_RTC_SEC, rtc, 7);
}

DateTime DS3231M::getRTCTime() {
  // Reads the 7 time and date registers from the DS3231M.
  readReg(DS3231M_REG_RTC_SEC, rtc, 7);

  // Converts BCD values to BIN and creates a DateTime object.
  return DateTime(bcd2bin(rtc[6]) + 2000, bcd2bin(rtc[5]), bcd2bin(rtc[4]), bcd2bin(rtc[2]), bcd2bin(rtc[1]), bcd2bin(rtc[0]));
}

void DS3231M::setAlarm1(const DateTime& dt, eAlarmType_t type) {
  uint8_t sec = bin2bcd(dt.second());
  uint8_t min = bin2bcd(dt.minute());
  uint8_t hour = bin2bcd(dt.hour());
  uint8_t day = bin2bcd(dt.day());
  uint8_t dy;

  // Configures the control bit for the alarm type.
  switch (type) {
    case eEverySecond:  // Every second
      sec |= 0x80;
      min |= 0x80;
      hour |= 0x80;
      day |= 0x80;
      break;
    case eEveryMinute:  // Every minute (at the specified second)
      min |= 0x80;
      hour |= 0x80;
      day |= 0x80;
      break;
    case eEveryHour:  // Every hour (at the specified minute and second)
      hour |= 0x80;
      day |= 0x80;
      break;
    case eEveryDay:  // Every day (at the specified hour, minute and second)
      day |= 0x80;
      break;
    case eEveryWeek:  // Every week (at the specified day of the week, hour, minute and second)
      dy = bin2bcd(dt.dayOfTheWeek());
      day = dy | 0x40;  // Bit 6 to indicate day of the week instead of day of the month.
      break;
    case eEveryMonth:  // Every month (at the specified day of the month, hour, minute and second)
      break;
    default:
      return;  // Invalid alarm type.
  }

  // Writes the alarm values to the corresponding registers.
  writeReg(DS3231M_REG_ALARM1_SEC, &sec, 1);
  writeReg(DS3231M_REG_ALARM1_MIN, &min, 1);
  writeReg(DS3231M_REG_ALARM1_HOUR, &hour, 1);
  writeReg(DS3231M_REG_ALARM1_DAY, &day, 1);

  // Enables alarm 1 interrupt (A1IE) in the control register.
  uint8_t ctrl[1];
  readReg(DS3231M_REG_CTRL, ctrl, 1);
  ctrl[0] |= 0x01;  // Set A1IE bit
  writeReg(DS3231M_REG_CTRL, ctrl, 1);
}

void DS3231M::setAlarm2(const DateTime& dt, eAlarmType_t type) {
  uint8_t min = bin2bcd(dt.minute());
  uint8_t hour = bin2bcd(dt.hour());
  uint8_t day = bin2bcd(dt.day());
  uint8_t dy;

  // Configures the control bit for the alarm type.
  switch (type) {
    case eEveryMinute:  // Every minute (at the specified minute)
      min |= 0x80;
      hour |= 0x80;
      day |= 0x80;
      break;
    case eEveryHour:  // Every hour (at the specified minute)
      hour |= 0x80;
      day |= 0x80;
      break;
    case eEveryDay:  // Every day (at the specified hour and minute)
      day |= 0x80;
      break;
    case eEveryWeek:  // Every week (at the specified day of the week, hour and minute)
      dy = bin2bcd(dt.dayOfTheWeek());
      day = dy | 0x40;  // Bit 6 to indicate day of the week instead of day of the month.
      break;
    case eEveryMonth:  // Every month (at the specified day of the month, hour and minute)
      break;
    default:
      return;  // Invalid alarm type.
  }

  // Writes the alarm values to the corresponding registers.
  writeReg(DS3231M_REG_ALARM2_MIN, &min, 1);
  writeReg(DS3231M_REG_ALARM2_HOUR, &hour, 1);
  writeReg(DS3231M_REG_ALARM2_DAY, &day, 1);

  // Enables alarm 2 interrupt (A2IE) in the control register.
  uint8_t ctrl[1];
  readReg(DS3231M_REG_CTRL, ctrl, 1);
  ctrl[0] |= 0x02;  // Set A2IE bit
  writeReg(DS3231M_REG_CTRL, ctrl, 1);
}

float DS3231M::getTemp() {
  uint8_t temp[2];
  // Reads the two bytes from the temperature register.
  readReg(DS3231M_REG_TEMP, temp, 2);
  // Combines the bytes to obtain the temperature and converts it to float.
  return (float)temp[0] + ((temp[1] >> 6) * 0.25);
}

bool DS3231M::isAlarm() {
  uint8_t status[1];
  readReg(DS3231M_REG_STATUS, status, 1);  // Reads the status register.
  return status[0] & 3;                    // Returns true if bits 0 (A1F) or 1 (A2F) are set.
}

void DS3231M::clearAlarm() {
  uint8_t status[1];
  readReg(DS3231M_REG_STATUS, status, 1);   // Reads the status register.
  status[0] &= 0xFC;                        // Clears bits A1F (bit 0) and A2F (bit 1).
  writeReg(DS3231M_REG_STATUS, status, 1);  // Writes the new status value.
}

void DS3231M::enable32k() {
  uint8_t status[1];
  readReg(DS3231M_REG_STATUS, status, 1);   // Reads the status register.
  status[0] |= 0x08;                        // Sets the EN32kHz bit (bit 3).
  writeReg(DS3231M_REG_STATUS, status, 1);  // Writes the new status value.
}

void DS3231M::disable32k() {
  uint8_t status[1];
  readReg(DS3231M_REG_STATUS, status, 1);   // Reads the status register.
  status[0] &= 0xF7;                        // Clears the EN32kHz bit (bit 3).
  writeReg(DS3231M_REG_STATUS, status, 1);  // Writes the new status value.
}

bool DS3231M::lostPower(void) {
  uint8_t status[1];
  readReg(DS3231M_REG_STATUS, status, 1);
  return status[0] >> 7;
}

void DS3231M::writeReg(uint8_t reg, const void* pBuf, size_t size) {
  if (pBuf == NULL) {
    // Prints a debug message if the pointer is null.
    DEBUG_ERROR("pBuf null pointer");
  }
  uint8_t* _pBuf = (uint8_t*)pBuf;  // Casts the pointer to uint8_t for iteration.

  _pWire->beginTransmission(DS3231M_IIC_ADDRESS);  // Starts I2C transmission to the DS3231M.
  _pWire->write(reg);                              // Sends the register address.
  for (size_t i = 0; i < size; i++) {
    _pWire->write(_pBuf[i]);  // Sends each byte from the buffer.
  }
  _pWire->endTransmission();  // Ends I2C transmission.
}

uint8_t DS3231M::readReg(uint8_t reg, const void* pBuf, size_t size) {
  if (pBuf == NULL) {
    // Prints a debug message if the pointer is null.
    DEBUG_ERROR("pBuf null pointer");
  }
  uint8_t* _pBuf = (uint8_t*)pBuf;  // Casts the pointer to uint8_t for iteration.

  _pWire->beginTransmission(DS3231M_IIC_ADDRESS);  // Starts I2C transmission to the DS3231M.
  _pWire->write(reg);                              // Sends the register address to read.
  _pWire->endTransmission(false);                  // Ends transmission but keeps the connection (repeated start).

  // Requests 'size' bytes from the DS3231M
  uint8_t i = 0;
  _pWire->requestFrom(DS3231M_IIC_ADDRESS, size);
  while (_pWire->available()) {
    if (i < size) {
      _pBuf[i++] = _pWire->read();  // Reads bytes and stores them in the buffer.
    } else {
      _pWire->read();  // Discards extra bytes if more than requested are read.
    }
  }
  return i;  // Returns the number of bytes read.
}

uint8_t DS3231M::bcd2bin(uint8_t val) {
  return val - 6 * (val >> 4);  // BCD to BIN conversion.
}

uint8_t DS3231M::bin2bcd(uint8_t val) {
  return val + 6 * (val / 10);  // BIN to BCD conversion.
}

uint8_t DS3231M::dayOfTheWeek(uint16_t y, uint8_t m, uint8_t d) const {
  // Implementation of Zeller's algorithm to calculate the day of the week.
  // k = day of the month
  // m = month (3=March, 4=April, ..., 12=December, 1=January, 2=February)
  // D = year of the century (e.g. for 2024, D=24)
  // C = century (e.g. for 2024, C=20)
  uint8_t k = d;
  uint8_t month = m;
  uint16_t year = y;

  if (month <= 2) {
    month += 12;
    year -= 1;
  }

  uint8_t D = year % 100;
  uint8_t C = year / 100;

  // Zeller's formula: h = (k + floor(2.6 * m - 0.2) + D + floor(D/4) + floor(C/4) - 2 * C) mod 7
  // The result is 0=Saturday, 1=Sunday, ..., 6=Friday.
  // Adjusted so that 1=Sunday, ..., 7=Saturday.
  uint8_t dayOfWeek = (k + (26 * (month + 1)) / 10 + D + D / 4 + C / 4 + 5 * C) % 7;
  return (dayOfWeek == 0) ? 7 : dayOfWeek;  // Adjusts so that Sunday is 7 or 1 depending on the convention.
}