diff --git a/ds3231/ds3231.go b/ds3231/ds3231.go index 4c6f11958..86c74e04d 100644 --- a/ds3231/ds3231.go +++ b/ds3231/ds3231.go @@ -5,6 +5,7 @@ package ds3231 // import "tinygo.org/x/drivers/ds3231" import ( + "errors" "time" "tinygo.org/x/drivers" @@ -158,6 +159,327 @@ func (d *Device) ReadTemperature() (int32, error) { return milliCelsius(data[0], data[1]), nil } +// GetSqwPinMode returns the current square wave output frequency +func (d *Device) GetSqwPinMode() SqwPinMode { + data := []uint8{0} + err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CONTROL, data) + if err != nil { + return SQW_OFF + } + + data[0] &= 0x1C // turn off INTCON + if data[0]&0x04 != 0 { + return SQW_OFF + } + + return SqwPinMode(data[0]) +} + +// SetSqwPinMode sets the square wave output mode to the given frequency +func (d *Device) SetSqwPinMode(mode SqwPinMode) error { + data := []uint8{0} + err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CONTROL, data) + if err != nil { + return err + } + + data[0] &^= 0x04 // turn off INTCON + data[0] &^= 0x18 // set freq bits to 0 + + data[0] |= uint8(mode) + + err = legacy.WriteRegister(d.bus, uint8(d.Address), REG_CONTROL, data) + if err != nil { + return err + } + + return nil +} + +// SetAlarm1 sets alarm1 to the given time and mode +func (d *Device) SetAlarm1(dt time.Time, mode Alarm1Mode) error { + dataCtrl := []uint8{0} + err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CONTROL, dataCtrl) + if err != nil { + return err + } + if dataCtrl[0]&(1< 0 { + day = dowToDS3231(int(dt.Weekday())) + } + + data := make([]uint8, 4) + data[0] = uint8ToBCD(uint8(dt.Second())) | A1M1 + data[1] = uint8ToBCD(uint8(dt.Minute())) | A1M2 + data[2] = uint8ToBCD(uint8(dt.Hour())) | A1M3 + data[3] = uint8ToBCD(uint8(day)) | A1M4 | DY_DT + + err = legacy.WriteRegister(d.bus, uint8(d.Address), REG_ALARMONE, data) + if err != nil { + return err + } + dataCtrl[0] |= AlarmFlag_Alarm1 + err = legacy.WriteRegister(d.bus, uint8(d.Address), REG_CONTROL, dataCtrl) + if err != nil { + return err + } + + return nil +} + +// ReadAlarm1 returns the alarm1 time +func (d *Device) ReadAlarm1() (dt time.Time, err error) { + data := make([]uint8, 5) + err = legacy.ReadRegister(d.bus, uint8(d.Address), REG_ALARMONE, data) + if err != nil { + return + } + second := bcdToInt(data[0] & 0x7F) + minute := bcdToInt(data[1] & 0x7F) + hour := hoursBCDToInt(data[2] & 0x3F) + + isDayOfWeek := (data[3] & 0x40) >> 6 + var day int + if isDayOfWeek > 0 { + day = bcdToInt(data[3] & 0x0F) + } else { + day = bcdToInt(data[3] & 0x3F) + } + + dt = time.Date(2000, 5, day, hour, minute, second, 0, time.UTC) + return +} + +// SetAlarm2 sets alarm2 to the given time and mode +func (d *Device) SetAlarm2(dt time.Time, mode Alarm2Mode) error { + dataCtrl := []uint8{0} + err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CONTROL, dataCtrl) + if err != nil { + return err + } + if dataCtrl[0]&(1< 0 { + day = dowToDS3231(int(dt.Weekday())) + } + + data := make([]uint8, 4) + data[0] = uint8ToBCD(uint8(dt.Minute())) | A2M2 + data[1] = uint8ToBCD(uint8(dt.Hour())) | A2M3 + data[2] = uint8ToBCD(uint8(day)) | A2M4 | DY_DT + + err = legacy.WriteRegister(d.bus, uint8(d.Address), REG_ALARMTWO, data) + if err != nil { + return err + } + dataCtrl[0] |= AlarmFlag_Alarm2 + err = legacy.WriteRegister(d.bus, uint8(d.Address), REG_CONTROL, dataCtrl) + if err != nil { + return err + } + + return nil +} + +// ReadAlarm2 returns the alarm2 time +func (d *Device) ReadAlarm2() (dt time.Time, err error) { + data := make([]uint8, 5) + err = legacy.ReadRegister(d.bus, uint8(d.Address), REG_ALARMTWO, data) + if err != nil { + return + } + minute := bcdToInt(data[0] & 0x7F) + hour := hoursBCDToInt(data[1] & 0x3F) + + isDayOfWeek := (data[2] & 0x40) >> 6 + var day int + if isDayOfWeek > 0 { + day = bcdToInt(data[2] & 0x0F) + } else { + day = bcdToInt(data[2] & 0x3F) + } + + dt = time.Date(2000, 5, day, hour, minute, 0, 0, time.UTC) + return +} + +// IsEnabledAlarm1 returns true when alarm1 is enabled +func (d *Device) IsEnabledAlarm1() bool { + return d.isEnabledAlarm(1) +} + +// DisableAlarm1 disables alarm1 +func (d *Device) DisableAlarm1() error { + return d.disableAlarm(1) +} + +// EnableAlarm1 enables alarm1 +func (d *Device) EnableAlarm1() error { + return d.enableAlarm(1) +} + +// IsEnabledAlarm2 returns true when alarm2 is enabled +func (d *Device) IsEnabledAlarm2() bool { + return d.isEnabledAlarm(2) +} + +// DisableAlarm2 disables alarm2 +func (d *Device) DisableAlarm2() error { + return d.disableAlarm(2) +} + +// EnableAlarm2 enables alarm2 +func (d *Device) EnableAlarm2() error { + return d.enableAlarm(2) +} + +// ClearAlarm1 clears status of alarm1 +func (d *Device) ClearAlarm1() error { + return d.clearAlarm(1) +} + +// ClearAlarm2 clears status of alarm2 +func (d *Device) ClearAlarm2() error { + return d.clearAlarm(2) +} + +// IsAlarm1Fired returns true if alarm1 is firing +func (d *Device) IsAlarm1Fired() bool { + return d.isAlarmFired(1) +} + +// IsAlarm2Fired returns true if alarm2 is firing +func (d *Device) IsAlarm2Fired() bool { + return d.isAlarmFired(2) +} + +// Enable32K enables the 32KHz output +func (d *Device) Enable32K() error { + data := []byte{0} + err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_STATUS, data) + if err != nil { + return err + } + data[0] |= 1 << EN32KHZ + err = legacy.WriteRegister(d.bus, uint8(d.Address), REG_STATUS, data) + if err != nil { + return err + } + return nil +} + +// Disable32K disables the 32KHz output +func (d *Device) Disable32K() error { + data := []byte{0} + err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_STATUS, data) + if err != nil { + return err + } + data[0] &^= 1 << EN32KHZ + err = legacy.WriteRegister(d.bus, uint8(d.Address), REG_STATUS, data) + if err != nil { + return err + } + return nil +} + +// IsEnabled32K get status of 32KHz output +func (d *Device) IsEnabled32K() bool { + data := []byte{0} + err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_STATUS, data) + if err != nil { + return false + } + return (data[0] & (1 << EN32KHZ)) != 0x00 +} + +// disableAlarm disable alarm +func (d *Device) disableAlarm(alarm_num uint8) error { + data := []byte{0} + err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CONTROL, data) + if err != nil { + return err + } + data[0] &^= (1 << (alarm_num - 1)) + err = legacy.WriteRegister(d.bus, uint8(d.Address), REG_CONTROL, data) + if err != nil { + return err + } + return nil +} + +// enableAlarm enable alarm +func (d *Device) enableAlarm(alarm_num uint8) error { + data := []byte{0} + err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CONTROL, data) + if err != nil { + return err + } + data[0] |= (1 << (alarm_num - 1)) + err = legacy.WriteRegister(d.bus, uint8(d.Address), REG_CONTROL, data) + if err != nil { + return err + } + return nil +} + +// isEnabledAlarm check if alarm is enabled for interrupt +func (d *Device) isEnabledAlarm(alarm_num uint8) bool { + data := []byte{0} + err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CONTROL, data) + if err != nil { + return false + } + return (data[0] & (1 << (alarm_num - 1))) != 0x00 +} + +// clearAlarm clear status of alarm +func (d *Device) clearAlarm(alarm_num uint8) error { + data := []byte{0} + err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_STATUS, data) + if err != nil { + return err + } + data[0] &^= (1 << (alarm_num - 1)) + err = legacy.WriteRegister(d.bus, uint8(d.Address), REG_STATUS, data) + if err != nil { + return err + } + return nil +} + +// IsAlarmFired get status of alarm +func (d *Device) isAlarmFired(alarm_num uint8) bool { + dataCtrl := []byte{0} + if err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CONTROL, dataCtrl); err != nil { + return false + } + dataCtrl[0] &^= (1 << (alarm_num - 1)) + data := []byte{0} + if err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_STATUS, data); err != nil { + return false + } + return (data[0] & (1 << (alarm_num - 1))) != 0x00 +} + // milliCelsius converts the raw temperature bytes (msb and lsb) from the DS3231 // into a 32-bit signed integer in units of milli Celsius (1/1000 deg C). // @@ -200,3 +522,11 @@ func hoursBCDToInt(value uint8) (hour int) { } return } + +// dowToDS3231 converts the day of the week to internal DS3231 format +func dowToDS3231(d int) int { + if d == 0 { + return 7 + } + return d +} diff --git a/ds3231/registers.go b/ds3231/registers.go index 05f17608f..98e8fb2cd 100644 --- a/ds3231/registers.go +++ b/ds3231/registers.go @@ -46,3 +46,50 @@ const ( AlarmTwo Mode = 4 ModeAlarmBoth Mode = 5 ) + +// SQW Pin Modes +type SqwPinMode uint8 + +const ( + SQW_OFF SqwPinMode = 0x1C + SQW_1HZ SqwPinMode = 0x00 + SQW_1KHZ SqwPinMode = 0x08 + SQW_4KHZ SqwPinMode = 0x10 + SQW_8KHZ SqwPinMode = 0x18 +) + +// Alarm1 Modes define which parts of the set alarm time has to match the current timestamp of the clock device for alarm1 to fire +type Alarm1Mode uint8 + +const ( + // Alarm1 fires every second + A1_PER_SECOND Alarm1Mode = 0x0F + // Alarm1 fires when the seconds match + A1_SECOND Alarm1Mode = 0x0E + // Alarm1 fires when both seconds and minutes match + A1_MINUTE Alarm1Mode = 0x0C + // Alarm1 fires when seconds, minutes and hours match + A1_HOUR Alarm1Mode = 0x08 + // Alarm1 fires when seconds, minutes, hours and the day of the month match + A1_DATE Alarm1Mode = 0x00 + // Alarm1 fires when seconds, minutes, hours and the day of the week match + A1_DAY Alarm1Mode = 0x10 +) + +// Alarm2 Modes define which parts of the set alarm time has to match the current timestamp of the clock device for alarm2 to fire. +// +// Alarm2 only supports matching down to the minute unlike alarm1 which supports matching down to the second. +type Alarm2Mode uint8 + +const ( + // Alarm2 fires every minute + A2_PER_MINUTE Alarm2Mode = 0x07 + // Alarm2 fires when the minutes match + A2_MINUTE Alarm2Mode = 0x06 + // Alarm2 fires when both minutes and hours match + A2_HOUR Alarm2Mode = 0x04 + // Alarm2 fires when minutes, hours and the day of the month match + A2_DATE Alarm2Mode = 0x00 + // Alarm2 fires when minutes, hours and the day of the week match + A2_DAY Alarm2Mode = 0x08 +) diff --git a/examples/ds3231/alarms/main.go b/examples/ds3231/alarms/main.go new file mode 100644 index 000000000..0fa83b79b --- /dev/null +++ b/examples/ds3231/alarms/main.go @@ -0,0 +1,86 @@ +// Connects to an DS3231 I2C Real Time Clock (RTC) and sets both alarms. It then repeatedly checks +// if the alarms are firing and prints out a message if that is the case. +package main + +import ( + "machine" + "time" + + "fmt" + + "tinygo.org/x/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) + } + + // Set alarm1 so it triggers when the seconds match 59 => repeats every minute at dd:hh:mm:59 + if err := rtc.SetAlarm1(time.Date(0, 0, 0, 0, 0, 59, 0, time.UTC), ds3231.A1_SECOND); err != nil { + fmt.Println("Error while setting Alarm1") + } + if err := rtc.EnableAlarm1(); err != nil { + fmt.Println("Error while enabling Alarm1") + } + + // Set alarm2 so it triggers when the minutes match 35 => repeats every hour at dd:hh:35:ss + if err := rtc.SetAlarm2(time.Date(0, 0, 0, 0, 35, 0, 0, time.UTC), ds3231.A2_MINUTE); err != nil { + fmt.Println("Error while setting Alarm2") + } + if err := rtc.EnableAlarm2(); err != nil { + fmt.Println("Error while enabling Alarm2") + } + + 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) + continue + } + + a1 := rtc.IsAlarm1Fired() + a2 := rtc.IsAlarm2Fired() + + fmt.Printf( + "%d/%s/%02d %02d:%02d:%02d A1: %t A2: %t\r\n", + dt.Year(), + dt.Month(), + dt.Day(), + dt.Hour(), + dt.Minute(), + dt.Second(), + a1, + a2, + ) + + if a1 { + if err := rtc.ClearAlarm1(); err != nil { + fmt.Println("Error while clearing alarm1") + } + } + if a2 { + if err := rtc.ClearAlarm2(); err != nil { + fmt.Println("Error while clearing alarm2") + } + + } + + time.Sleep(time.Second * 1) + } +} diff --git a/examples/ds3231/main.go b/examples/ds3231/basic/main.go similarity index 100% rename from examples/ds3231/main.go rename to examples/ds3231/basic/main.go diff --git a/smoketest.sh b/smoketest.sh index b0035988f..b7dccb428 100755 --- a/smoketest.sh +++ b/smoketest.sh @@ -22,7 +22,8 @@ tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/bmp tinygo build -size short -o ./build/test.hex -target=trinket-m0 ./examples/bmp388/main.go tinygo build -size short -o ./build/test.hex -target=bluepill ./examples/ds1307/sram/main.go tinygo build -size short -o ./build/test.hex -target=bluepill ./examples/ds1307/time/main.go -tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/ds3231/main.go +tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/ds3231/alarms/main.go +tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/ds3231/basic/main.go tinygo build -size short -o ./build/test.hex -target=microbit ./examples/easystepper/main.go tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/flash/console/spi tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/flash/console/qspi