diff --git a/ds3231/ds3231.go b/ds3231/ds3231.go index 4c6f11958..ba515f7cb 100644 --- a/ds3231/ds3231.go +++ b/ds3231/ds3231.go @@ -5,10 +5,12 @@ package ds3231 // import "tinygo.org/x/drivers/ds3231" import ( + "encoding/binary" + "errors" "time" "tinygo.org/x/drivers" - "tinygo.org/x/drivers/internal/legacy" + "tinygo.org/x/drivers/internal/regmap" ) type Mode uint8 @@ -17,6 +19,7 @@ type Mode uint8 type Device struct { bus drivers.I2C Address uint16 + d regmap.Device8I2C } // New creates a new DS3231 connection. The I2C bus must already be @@ -24,54 +27,50 @@ type Device struct { // // This function only creates the Device object, it does not touch the device. func New(bus drivers.I2C) Device { - return Device{ + d := Device{ bus: bus, Address: Address, } + d.Configure() + return d } // Configure sets up the device for communication func (d *Device) Configure() bool { + d.d.SetBus(d.bus, d.Address, binary.BigEndian) return true } // IsTimeValid return true/false is the time in the device is valid func (d *Device) IsTimeValid() bool { - data := []byte{0} - err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_STATUS, data) + status, err := d.d.Read8(REG_STATUS) if err != nil { return false } - return (data[0] & (1 << OSF)) == 0x00 + return (status & (1 << OSF)) == 0x00 } // IsRunning returns if the oscillator is running func (d *Device) IsRunning() bool { - data := []uint8{0} - err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CONTROL, data) + control, err := d.d.Read8(REG_CONTROL) if err != nil { return false } - return (data[0] & (1 << EOSC)) == 0x00 + return (control & (1 << EOSC)) == 0x00 } // SetRunning starts the internal oscillator func (d *Device) SetRunning(isRunning bool) error { - data := []uint8{0} - err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CONTROL, data) + control, err := d.d.Read8(REG_CONTROL) if err != nil { return err } if isRunning { - data[0] &^= uint8(1 << EOSC) + control &^= uint8(1 << EOSC) } else { - data[0] |= 1 << EOSC + control |= 1 << EOSC } - err = legacy.WriteRegister(d.bus, uint8(d.Address), REG_CONTROL, data) - if err != nil { - return err - } - return nil + return d.d.Write8(REG_CONTROL, control) } // SetTime sets the date and time in the DS3231. The DS3231 hardware supports @@ -86,18 +85,16 @@ func (d *Device) SetRunning(isRunning bool) error { // 2100 as a leap year, causing it to increment from 2100-02-28 to 2100-02-29 // instead of 2100-03-01. func (d *Device) SetTime(dt time.Time) error { - data := []byte{0} - err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_STATUS, data) + status, err := d.d.Read8(REG_STATUS) if err != nil { return err } - data[0] &^= 1 << OSF - err = legacy.WriteRegister(d.bus, uint8(d.Address), REG_STATUS, data) - if err != nil { + status &^= 1 << OSF + if err = d.d.Write8(REG_STATUS, status); err != nil { return err } - data = make([]uint8, 7) + data := make([]uint8, 7) data[0] = uint8ToBCD(uint8(dt.Second())) data[1] = uint8ToBCD(uint8(dt.Minute())) data[2] = uint8ToBCD(uint8(dt.Hour())) @@ -118,21 +115,16 @@ func (d *Device) SetTime(dt time.Time) error { data[5] = uint8ToBCD(uint8(dt.Month()) | centuryFlag) data[6] = uint8ToBCD(year) - err = legacy.WriteRegister(d.bus, uint8(d.Address), REG_TIMEDATE, data) - if err != nil { - return err - } - - return nil + return d.bus.Tx(d.Address, append([]byte{REG_TIMEDATE}, data...), nil) } // ReadTime returns the date and time func (d *Device) ReadTime() (dt time.Time, err error) { data := make([]uint8, 7) - err = legacy.ReadRegister(d.bus, uint8(d.Address), REG_TIMEDATE, data) - if err != nil { + if err = d.d.ReadData(REG_TIMEDATE, data); err != nil { return } + second := bcdToInt(data[0] & 0x7F) minute := bcdToInt(data[1]) hour := hoursBCDToInt(data[2]) @@ -151,13 +143,265 @@ func (d *Device) ReadTime() (dt time.Time, err error) { // ReadTemperature returns the temperature in millicelsius (mC) func (d *Device) ReadTemperature() (int32, error) { data := make([]uint8, 2) - err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_TEMP, data) - if err != nil { + if err := d.d.ReadData(REG_TEMP, data); err != nil { return 0, err } return milliCelsius(data[0], data[1]), nil } +// GetSqwPinMode returns the current square wave output frequency +func (d *Device) GetSqwPinMode() SqwPinMode { + control, err := d.d.Read8(REG_CONTROL) + if err != nil { + return SQW_OFF + } + + control &= 0x1C // turn off INTCON + if control&0x04 != 0 { + return SQW_OFF + } + + return SqwPinMode(control) +} + +// SetSqwPinMode sets the square wave output mode to the given frequency +func (d *Device) SetSqwPinMode(mode SqwPinMode) error { + control, err := d.d.Read8(REG_CONTROL) + if err != nil { + return err + } + + control &^= 0x04 // turn off INTCON + control &^= 0x18 // set freq bits to 0 + + control |= uint8(mode) + + return d.d.Write8(REG_CONTROL, control) +} + +// SetAlarm1 sets alarm1 to the given time and mode +func (d *Device) SetAlarm1(dt time.Time, mode Alarm1Mode) error { + control, err := d.d.Read8(REG_CONTROL) + if err != nil { + return err + } + if control&(1< 0 { + day = dowToDS3231(int(dt.Weekday())) + } + + alarm1 := uint32(uint8ToBCD(uint8(dt.Second()))|A1M1) << 24 + alarm1 |= uint32(uint8ToBCD(uint8(dt.Minute()))|A1M2) << 16 + alarm1 |= uint32(uint8ToBCD(uint8(dt.Hour()))|A1M3) << 8 + alarm1 |= uint32(uint8ToBCD(uint8(day)) | A1M4 | DY_DT) + + if err := d.d.Write32(REG_ALARMONE, alarm1); err != nil { + return err + } + + control |= AlarmFlag_Alarm1 + return d.d.Write8(REG_CONTROL, control) +} + +// ReadAlarm1 returns the alarm1 time +func (d *Device) ReadAlarm1() (dt time.Time, err error) { + data := make([]uint8, 4) + if err = d.d.ReadData(REG_ALARMONE, data); 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 { + control, err := d.d.Read8(REG_CONTROL) + if err != nil { + return err + } + if control&(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 + if err = d.bus.Tx(d.Address, append([]byte{REG_ALARMTWO}, data...), nil); err != nil { + return err + } + + control |= AlarmFlag_Alarm2 + return d.d.Write8(REG_CONTROL, control) +} + +// ReadAlarm2 returns the alarm2 time +func (d *Device) ReadAlarm2() (dt time.Time, err error) { + data := make([]uint8, 3) + if err = d.d.ReadData(REG_ALARMTWO, data); 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) +} + +// SetEnabledAlarm1 sets the enabled status of alarm1 +func (d *Device) SetEnabledAlarm1(enable bool) error { + if enable { + return d.enableAlarm(1) + } + return d.disableAlarm(1) +} + +// IsEnabledAlarm2 returns true when alarm2 is enabled +func (d *Device) IsEnabledAlarm2() bool { + return d.isEnabledAlarm(2) +} + +// SetEnabledAlarm2 sets the enabled status of alarm2 +func (d *Device) SetEnabledAlarm2(enable bool) error { + if enable { + return d.enableAlarm(2) + } + return d.disableAlarm(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 when alarm1 is firing +func (d *Device) IsAlarm1Fired() bool { + return d.isAlarmFired(1) +} + +// IsAlarm2Fired returns true when alarm2 is firing +func (d *Device) IsAlarm2Fired() bool { + return d.isAlarmFired(2) +} + +// SetEnabled32K sets the enabled status of the 32KHz output +func (d *Device) SetEnabled32K(enable bool) error { + status, err := d.d.Read8(REG_STATUS) + if err != nil { + return err + } + + if enable { + status |= 1 << EN32KHZ + } else { + status &^= 1 << EN32KHZ + } + + return d.d.Write8(REG_STATUS, status) +} + +// IsEnabled32K returns true when the 32KHz output is enabled +func (d *Device) IsEnabled32K() bool { + status, err := d.d.Read8(REG_STATUS) + if err != nil { + return false + } + return (status & (1 << EN32KHZ)) != 0x00 +} + +func (d *Device) disableAlarm(alarm_num uint8) error { + control, err := d.d.Read8(REG_CONTROL) + if err != nil { + return err + } + control &^= (1 << (alarm_num - 1)) + return d.d.Write8(REG_CONTROL, control) +} + +func (d *Device) enableAlarm(alarm_num uint8) error { + control, err := d.d.Read8(REG_CONTROL) + if err != nil { + return err + } + control |= (1 << (alarm_num - 1)) + return d.d.Write8(REG_CONTROL, control) +} + +func (d *Device) isEnabledAlarm(alarm_num uint8) bool { + control, err := d.d.Read8(REG_CONTROL) + if err != nil { + return false + } + return (control & (1 << (alarm_num - 1))) != 0x00 +} + +func (d *Device) clearAlarm(alarm_num uint8) error { + status, err := d.d.Read8(REG_STATUS) + if err != nil { + return err + } + status &^= (1 << (alarm_num - 1)) + return d.d.Write8(REG_STATUS, status) +} + +func (d *Device) isAlarmFired(alarm_num uint8) bool { + status, err := d.d.Read8(REG_STATUS) + if err != nil { + return false + } + return (status & (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 +444,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..f38b7017e 100644 --- a/ds3231/registers.go +++ b/ds3231/registers.go @@ -46,3 +46,52 @@ 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..7ef9e878b --- /dev/null +++ b/examples/ds3231/alarms/main.go @@ -0,0 +1,74 @@ +// 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" + + "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 { + println("Error while setting Alarm1") + } + if err := rtc.SetEnabledAlarm1(true); err != nil { + 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 { + println("Error while setting Alarm2") + } + if err := rtc.SetEnabledAlarm2(true); err != nil { + println("Error while enabling Alarm2") + } + + running := rtc.IsRunning() + if !running { + err := rtc.SetRunning(true) + if err != nil { + println("Error configuring RTC") + } + } + + for { + dt, err := rtc.ReadTime() + if err != nil { + println("Error reading date:", err) + continue + } + + a1 := rtc.IsAlarm1Fired() + a2 := rtc.IsAlarm2Fired() + + println(dt.Format(time.DateTime), "A1:", a1, "A2:", a2) + + if a1 { + if err := rtc.ClearAlarm1(); err != nil { + println("Error while clearing alarm1") + } + } + if a2 { + if err := rtc.ClearAlarm2(); err != nil { + 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 68% rename from examples/ds3231/main.go rename to examples/ds3231/basic/main.go index 08ec468e6..e406176f5 100644 --- a/examples/ds3231/main.go +++ b/examples/ds3231/basic/main.go @@ -3,10 +3,9 @@ package main import ( "machine" + "strconv" "time" - "fmt" - "tinygo.org/x/drivers/ds3231" ) @@ -26,19 +25,19 @@ func main() { if !running { err := rtc.SetRunning(true) if err != nil { - fmt.Println("Error configuring RTC") + println("Error configuring RTC") } } for { dt, err := rtc.ReadTime() if err != nil { - fmt.Println("Error reading date:", err) + 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()) + println(dt.Format(time.DateTime)) } temp, _ := rtc.ReadTemperature() - fmt.Printf("Temperature: %.2f °C \r\n", float32(temp)/1000) + println("Temperature:", strconv.FormatFloat(float64(temp)/1000, 'f', -1, 32), "°C") time.Sleep(time.Second * 1) } 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