tinygo.org/x/drivers@v0.27.1-0.20240509133757-7dbca2a54349/ds3231/ds3231.go (about)

     1  // Package ds3231 provides a driver for the DS3231 RTC
     2  //
     3  // Datasheet:
     4  // https://datasheets.maximintegrated.com/en/ds/DS3231.pdf
     5  package ds3231 // import "tinygo.org/x/drivers/ds3231"
     6  
     7  import (
     8  	"time"
     9  
    10  	"tinygo.org/x/drivers"
    11  	"tinygo.org/x/drivers/internal/legacy"
    12  )
    13  
    14  type Mode uint8
    15  
    16  // Device wraps an I2C connection to a DS3231 device.
    17  type Device struct {
    18  	bus     drivers.I2C
    19  	Address uint16
    20  }
    21  
    22  // New creates a new DS3231 connection. The I2C bus must already be
    23  // configured.
    24  //
    25  // This function only creates the Device object, it does not touch the device.
    26  func New(bus drivers.I2C) Device {
    27  	return Device{
    28  		bus:     bus,
    29  		Address: Address,
    30  	}
    31  }
    32  
    33  // Configure sets up the device for communication
    34  func (d *Device) Configure() bool {
    35  	return true
    36  }
    37  
    38  // IsTimeValid return true/false is the time in the device is valid
    39  func (d *Device) IsTimeValid() bool {
    40  	data := []byte{0}
    41  	err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_STATUS, data)
    42  	if err != nil {
    43  		return false
    44  	}
    45  	return (data[0] & (1 << OSF)) == 0x00
    46  }
    47  
    48  // IsRunning returns if the oscillator is running
    49  func (d *Device) IsRunning() bool {
    50  	data := []uint8{0}
    51  	err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CONTROL, data)
    52  	if err != nil {
    53  		return false
    54  	}
    55  	return (data[0] & (1 << EOSC)) == 0x00
    56  }
    57  
    58  // SetRunning starts the internal oscillator
    59  func (d *Device) SetRunning(isRunning bool) error {
    60  	data := []uint8{0}
    61  	err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CONTROL, data)
    62  	if err != nil {
    63  		return err
    64  	}
    65  	if isRunning {
    66  		data[0] &^= uint8(1 << EOSC)
    67  	} else {
    68  		data[0] |= 1 << EOSC
    69  	}
    70  	err = legacy.WriteRegister(d.bus, uint8(d.Address), REG_CONTROL, data)
    71  	if err != nil {
    72  		return err
    73  	}
    74  	return nil
    75  }
    76  
    77  // SetTime sets the date and time in the DS3231. The DS3231 hardware supports
    78  // only a 2-digit year field, so the current year will be stored as an offset
    79  // from the year 2000, which supports the year 2000 until 2100.
    80  //
    81  // The DS3231 also supports a one-bit 'century' flag which is set by the chip
    82  // when the year field rolls over from 99 to 00. The current code interprets
    83  // this flag to be the year 2100, which appears to extend the range of years
    84  // until the year 2200. However the DS3231 does not incorporate the 'century'
    85  // flag in its leap year calculation, so it will incorrectly identify the year
    86  // 2100 as a leap year, causing it to increment from 2100-02-28 to 2100-02-29
    87  // instead of 2100-03-01.
    88  func (d *Device) SetTime(dt time.Time) error {
    89  	data := []byte{0}
    90  	err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_STATUS, data)
    91  	if err != nil {
    92  		return err
    93  	}
    94  	data[0] &^= 1 << OSF
    95  	err = legacy.WriteRegister(d.bus, uint8(d.Address), REG_STATUS, data)
    96  	if err != nil {
    97  		return err
    98  	}
    99  
   100  	data = make([]uint8, 7)
   101  	data[0] = uint8ToBCD(uint8(dt.Second()))
   102  	data[1] = uint8ToBCD(uint8(dt.Minute()))
   103  	data[2] = uint8ToBCD(uint8(dt.Hour()))
   104  
   105  	year := uint8(dt.Year() - 2000)
   106  	// This code interprets the centuryFlag to be the year 2100. Warning: The
   107  	// DS3231 does not incorporate the centuryFlag in its leap year calculation.
   108  	// It will increment from 2100-02-28 to 2100-02-29, which is incorrect because
   109  	// the year 2100 is not a leap year in the Gregorian calendar.
   110  	centuryFlag := uint8(0)
   111  	if year >= 100 {
   112  		year -= 100
   113  		centuryFlag = 1 << 7
   114  	}
   115  
   116  	data[3] = uint8ToBCD(uint8(dt.Weekday()))
   117  	data[4] = uint8ToBCD(uint8(dt.Day()))
   118  	data[5] = uint8ToBCD(uint8(dt.Month()) | centuryFlag)
   119  	data[6] = uint8ToBCD(year)
   120  
   121  	err = legacy.WriteRegister(d.bus, uint8(d.Address), REG_TIMEDATE, data)
   122  	if err != nil {
   123  		return err
   124  	}
   125  
   126  	return nil
   127  }
   128  
   129  // ReadTime returns the date and time
   130  func (d *Device) ReadTime() (dt time.Time, err error) {
   131  	data := make([]uint8, 7)
   132  	err = legacy.ReadRegister(d.bus, uint8(d.Address), REG_TIMEDATE, data)
   133  	if err != nil {
   134  		return
   135  	}
   136  	second := bcdToInt(data[0] & 0x7F)
   137  	minute := bcdToInt(data[1])
   138  	hour := hoursBCDToInt(data[2])
   139  	day := bcdToInt(data[4])
   140  	monthRaw := data[5]
   141  	year := bcdToInt(data[6]) + 2000
   142  	if monthRaw&(1<<7) != 0x00 {
   143  		year += 100
   144  	}
   145  	month := time.Month(bcdToInt(monthRaw & 0x7F))
   146  
   147  	dt = time.Date(year, month, day, hour, minute, second, 0, time.UTC)
   148  	return
   149  }
   150  
   151  // ReadTemperature returns the temperature in millicelsius (mC)
   152  func (d *Device) ReadTemperature() (int32, error) {
   153  	data := make([]uint8, 2)
   154  	err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_TEMP, data)
   155  	if err != nil {
   156  		return 0, err
   157  	}
   158  	return milliCelsius(data[0], data[1]), nil
   159  }
   160  
   161  // milliCelsius converts the raw temperature bytes (msb and lsb) from the DS3231
   162  // into a 32-bit signed integer in units of milli Celsius (1/1000 deg C).
   163  //
   164  // According to the DS3231 datasheet: "Temperature is represented as a 10-bit
   165  // code with a resolution of 0.25 deg C and is accessible at location 11h and
   166  // 12h. The temperature is encoded in two's complement format. The upper 8 bits,
   167  // the integer portion, are at location 11h and the lower 2 bits, the fractional
   168  // portion, are in the upper nibble at location 12h."
   169  //
   170  // In other words, the msb and lsb bytes should be treated as a signed 16-bit
   171  // integer in units of (1/256 deg C). It is possible to convert this into a
   172  // 16-bit signed integer in units of centi Celsius (1/100 deg C) with no loss of
   173  // precision or dynamic range. But for backwards compatibility, let's instead
   174  // convert this into a 32-bit signed integer in units of milli Celsius.
   175  func milliCelsius(msb uint8, lsb uint8) int32 {
   176  	t256 := int16(uint16(msb)<<8 | uint16(lsb))
   177  	t1000 := int32(t256) / 64 * 250
   178  	return t1000
   179  }
   180  
   181  // uint8ToBCD converts a byte to BCD for the DS3231
   182  func uint8ToBCD(value uint8) uint8 {
   183  	return value + 6*(value/10)
   184  }
   185  
   186  // bcdToInt converts BCD from the DS3231 to int
   187  func bcdToInt(value uint8) int {
   188  	return int(value - 6*(value>>4))
   189  }
   190  
   191  // hoursBCDToInt converts the BCD hours to int
   192  func hoursBCDToInt(value uint8) (hour int) {
   193  	if value&0x40 != 0x00 {
   194  		hour = bcdToInt(value & 0x1F)
   195  		if (value & 0x20) != 0x00 {
   196  			hour += 12
   197  		}
   198  	} else {
   199  		hour = bcdToInt(value)
   200  	}
   201  	return
   202  }