diff --git a/ds3231/ds3231.go b/ds3231/ds3231.go new file mode 100644 index 0000000..38d8cd8 --- /dev/null +++ b/ds3231/ds3231.go @@ -0,0 +1,166 @@ +// Package ds3231 provides a driver for the DS3231 RTC +// +// Datasheet: +// https://datasheets.maximintegrated.com/en/ds/DS3231.pdf +package ds3231 + +import ( + "machine" + "time" +) + +type Mode uint8 + +// Device wraps an I2C connection to a DS3231 device. +type Device struct { + bus machine.I2C + Address uint16 +} + +// New creates a new DS3231 connection. The I2C bus must already be +// configured. +// +// This function only creates the Device object, it does not touch the device. +func New(bus machine.I2C) Device { + return Device{ + bus: bus, + Address: Address, + } +} + +// Configure sets up the device for communication +func (d *Device) Configure() bool { + return true +} + +// IsTimeValid return true/false is the time in the device is valid +func (d *Device) IsTimeValid() bool { + data := []byte{0} + err := d.bus.ReadRegister(uint8(d.Address), REG_STATUS, data) + if err != nil { + return false + } + return (data[0] & (1 << OSF)) == 0x00 +} + +// IsRunning returns if the oscillator is running +func (d *Device) IsRunning() bool { + data := []uint8{0} + err := d.bus.ReadRegister(uint8(d.Address), REG_CONTROL, data) + if err != nil { + return false + } + return (data[0] & (1 << EOSC)) == 0x00 +} + +// SetRunning starts the internal oscillator +func (d *Device) SetRunning(isRunning bool) error { + data := []uint8{0} + err := d.bus.ReadRegister(uint8(d.Address), REG_CONTROL, data) + if err != nil { + return err + } + if isRunning { + data[0] &^= uint8(1 << EOSC) + } else { + data[0] |= 1 << EOSC + } + err = d.bus.WriteRegister(uint8(d.Address), REG_CONTROL, data) + if err != nil { + return err + } + return nil +} + +// SetTime sets the date and time in the DS3231 +func (d *Device) SetTime(dt time.Time) error { + data := []byte{0} + err := d.bus.ReadRegister(uint8(d.Address), REG_STATUS, data) + if err != nil { + return err + } + data[0] &^= 1 << OSF + err = d.bus.WriteRegister(uint8(d.Address), REG_STATUS, data) + if err != nil { + return err + } + + data = make([]uint8, 7) + data[0] = uint8ToBCD(uint8(dt.Second())) + data[1] = uint8ToBCD(uint8(dt.Minute())) + data[2] = uint8ToBCD(uint8(dt.Hour())) + + year := uint8(dt.Year() - 2000) + centuryFlag := uint8(0) + if year >= 100 { + year -= 100 + centuryFlag = 1 << 7 + } + + data[3] = uint8ToBCD(uint8(dt.Weekday())) + data[4] = uint8ToBCD(uint8(dt.Day())) + data[5] = uint8ToBCD(uint8(dt.Month()) | centuryFlag) + data[6] = uint8ToBCD(year) + + err = d.bus.WriteRegister(uint8(d.Address), REG_TIMEDATE, data) + if err != nil { + return err + } + + return nil +} + +// ReadTime returns the date and time +func (d *Device) ReadTime() (dt time.Time, err error) { + data := make([]uint8, 7) + err = d.bus.ReadRegister(uint8(d.Address), REG_TIMEDATE, data) + if err != nil { + return + } + second := bcdToInt(data[0] & 0x7F) + minute := bcdToInt(data[1]) + hour := hoursBCDToInt(data[2]) + day := bcdToInt(data[4]) + monthRaw := data[5] + year := bcdToInt(data[6]) + 2000 + if monthRaw&(1<<7) != 0x00 { + year += 100 + } + month := time.Month(bcdToInt(monthRaw & 0x7F)) + + dt = time.Date(year, month, day, hour, minute, second, 0, time.UTC) + return +} + +// ReadTemperature returns the temperature in millicelsius (mC) +func (d *Device) ReadTemperature() (int32, error) { + data := make([]uint8, 2) + err := d.bus.ReadRegister(uint8(d.Address), REG_TEMP, data) + if err != nil { + return 0, err + } + return int32(data[0])*1000 + int32((data[1]>>6)*25)*10, nil +} + +// uint8ToBCD converts a byte to BCD for the DS3231 +func uint8ToBCD(value uint8) uint8 { + return value + 6*(value/10) +} + +// bcdToInt converts BCD from the DS3231 to int +func bcdToInt(value uint8) int { + return int(value - 6*(value>>4)) +} + +// hoursBCDToInt converts the BCD hours to int +func hoursBCDToInt(value uint8) (hour int) { + if value&0x40 != 0x00 { + hour = bcdToInt(value & 0x1F) + if (value & 0x20) != 0x00 { + hour += 12 + } + } else { + hour = bcdToInt(value) + } + return +} diff --git a/ds3231/registers.go b/ds3231/registers.go new file mode 100644 index 0000000..05f1760 --- /dev/null +++ b/ds3231/registers.go @@ -0,0 +1,48 @@ +package ds3231 + +// The I2C address which this device listens to. +const Address = 0x68 + +// Registers +const ( + REG_TIMEDATE = 0x00 + REG_ALARMONE = 0x07 + REG_ALARMTWO = 0x0B + + REG_CONTROL = 0x0E + REG_STATUS = 0x0F + REG_AGING = 0x10 + + REG_TEMP = 0x11 + + REG_ALARMONE_SIZE = 4 + REG_ALARMTWO_SIZE = 3 + + // DS3231 Control Register Bits + A1IE = 0 + A2IE = 1 + INTCN = 2 + RS1 = 3 + RS2 = 4 + CONV = 5 + BBSQW = 6 + EOSC = 7 + + // DS3231 Status Register Bits + A1F = 0 + A2F = 1 + BSY = 2 + EN32KHZ = 3 + OSF = 7 + + AlarmFlag_Alarm1 = 0x01 + AlarmFlag_Alarm2 = 0x02 + AlarmFlag_AlarmBoth = 0x03 + + None Mode = 0 + BatteryBackup Mode = 1 + Clock Mode = 2 + AlarmOne Mode = 3 + AlarmTwo Mode = 4 + ModeAlarmBoth Mode = 5 +) diff --git a/examples/ds3231/main.go b/examples/ds3231/main.go new file mode 100644 index 0000000..edb92ce --- /dev/null +++ b/examples/ds3231/main.go @@ -0,0 +1,45 @@ +// Connects to an MAG3110 I2C magnetometer. +package main + +import ( + "machine" + "time" + + "fmt" + + "github.com/tinygo-org/drivers/ds3231" +) + +func main() { + machine.I2C0.Configure(machine.I2CConfig{}) + + rtc := ds3231.New(machine.I2C0) + rtc.Configure() + + valid := rtc.IsTimeValid() + if !valid { + date := time.Date(2019, 12, 05, 20, 34, 12, 0, time.UTC) + rtc.SetTime(date) + } + + running := rtc.IsRunning() + if !running { + err := rtc.SetRunning(true) + if err != nil { + fmt.Println("Error configuring RTC") + } + } + + for { + dt, err := rtc.ReadTime() + if err != nil { + fmt.Println("Error reading date:", err) + } else { + fmt.Printf("Date: %d/%s/%02d %02d:%02d:%02d \r\n", dt.Year(), dt.Month(), dt.Day(), dt.Hour(), dt.Minute(), dt.Second()) + } + temp, _ := rtc.ReadTemperature() + fmt.Printf("Temperature: %.2f ÂșC \r\n", float32(temp)/1000) + + time.Sleep(time.Second * 1) + } +}