From 761bcfc4db2849340f2bb984c37b0de8eabd447a Mon Sep 17 00:00:00 2001 From: sago35 Date: Sun, 9 May 2021 13:44:05 +0900 Subject: [PATCH] pcf8563: add support for pcf8563 real time clock --- Makefile | 11 +- README.md | 3 +- examples/pcf8563/alarm/main.go | 45 ++++++ examples/pcf8563/clkout/main.go | 31 ++++ examples/pcf8563/time/main.go | 36 +++++ examples/pcf8563/timer/main.go | 46 ++++++ pcf8563/pcf8563.go | 252 ++++++++++++++++++++++++++++++++ pcf8563/registers.go | 31 ++++ 8 files changed, 453 insertions(+), 2 deletions(-) create mode 100644 examples/pcf8563/alarm/main.go create mode 100644 examples/pcf8563/clkout/main.go create mode 100644 examples/pcf8563/time/main.go create mode 100644 examples/pcf8563/timer/main.go create mode 100644 pcf8563/pcf8563.go create mode 100644 pcf8563/registers.go diff --git a/Makefile b/Makefile index d529b35..2a37f2e 100644 --- a/Makefile +++ b/Makefile @@ -177,11 +177,20 @@ endif @md5sum ./build/test.hex tinygo build -size short -o ./build/test.hex -target=arduino ./examples/keypad4x4/main.go @md5sum ./build/test.hex + tinygo build -size short -o ./build/test.hex -target=xiao ./examples/pcf8563/alarm/ + @md5sum ./build/test.hex + tinygo build -size short -o ./build/test.hex -target=xiao ./examples/pcf8563/clkout/ + @md5sum ./build/test.hex + tinygo build -size short -o ./build/test.hex -target=xiao ./examples/pcf8563/time/ + @md5sum ./build/test.hex + tinygo build -size short -o ./build/test.hex -target=xiao ./examples/pcf8563/timer/ + @md5sum ./build/test.hex DRIVERS = $(wildcard */) NOTESTS = build examples flash semihosting pcd8544 shiftregister st7789 microphone mcp3008 gps microbitmatrix \ hcsr04 ssd1331 ws2812 thermistor apa102 easystepper ssd1351 ili9341 wifinina shifter hub75 \ - hd44780 buzzer ssd1306 espat l9110x st7735 bmi160 l293x dht keypad4x4 max72xx p1am tone tm1637 + hd44780 buzzer ssd1306 espat l9110x st7735 bmi160 l293x dht keypad4x4 max72xx p1am tone tm1637 \ + pcf8563 TESTS = $(filter-out $(addsuffix /%,$(NOTESTS)),$(DRIVERS)) unit-test: diff --git a/README.md b/README.md index a2da2a9..cef7706 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ func main() { ## Currently supported devices -The following 58 devices are supported. +The following 60 devices are supported. | Device Name | Interface Type | |----------|-------------| @@ -95,6 +95,7 @@ The following 58 devices are supported. | [MPU6050 accelerometer/gyroscope](https://store.invensense.com/datasheets/invensense/MPU-6050_DataSheet_V3%204.pdf) | I2C | | [P1AM-100 Base Controller](https://facts-engineering.github.io/modules/P1AM-100/P1AM-100.html) | SPI | | [PCD8544 display](http://eia.udg.edu/~forest/PCD8544_1.pdf) | SPI | +| [PCF8563 real time clock](https://www.nxp.com/docs/en/data-sheet/PCF8563.pdf) | I2C | | [Resistive Touchscreen (4-wire)](http://ww1.microchip.com/downloads/en/Appnotes/doc8091.pdf) | GPIO | | [Semihosting](https://wiki.segger.com/Semihosting) | Debug | | [Shift register (PISO)](https://en.wikipedia.org/wiki/Shift_register#Parallel-in_serial-out_\(PISO\)) | GPIO | diff --git a/examples/pcf8563/alarm/main.go b/examples/pcf8563/alarm/main.go new file mode 100644 index 0000000..c932bef --- /dev/null +++ b/examples/pcf8563/alarm/main.go @@ -0,0 +1,45 @@ +package main + +import ( + "fmt" + "machine" + "time" + + "tinygo.org/x/drivers/pcf8563" +) + +var ( + i2c = machine.I2C0 + rtc = pcf8563.New(i2c) +) + +func main() { + i2c.Configure(machine.I2CConfig{Frequency: machine.TWI_FREQ_400KHZ}) + rtc.Reset() + + time.Sleep(3 * time.Second) + rtc.SetTime(time.Date(2006, 1, 2, 15, 4, 50, 0, time.UTC)) + + rtc.SetAlarm(time.Date(2006, 1, 2, 15, 5, 0, 0, time.UTC)) + rtc.EnableAlarmInterrupt() + + prev := -1 + + for { + for { + t, _ := rtc.ReadTime() + if prev != t.Second() { + fmt.Printf("%s\r\n", t.String()) + prev = t.Second() + + if rtc.AlarmTriggered() { + fmt.Printf("alarm triggered\r\n") + rtc.ClearAlarm() + } + + break + } + time.Sleep(time.Millisecond * 100) + } + } +} diff --git a/examples/pcf8563/clkout/main.go b/examples/pcf8563/clkout/main.go new file mode 100644 index 0000000..62037b7 --- /dev/null +++ b/examples/pcf8563/clkout/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "machine" + "time" + + "tinygo.org/x/drivers/pcf8563" +) + +var ( + i2c = machine.I2C0 + rtc = pcf8563.New(i2c) +) + +func main() { + i2c.Configure(machine.I2CConfig{Frequency: machine.TWI_FREQ_400KHZ}) + rtc.Reset() + + for { + rtc.SetOscillatorFrequency(pcf8563.RTC_COT_1HZ) + time.Sleep(3 * time.Second) + rtc.SetOscillatorFrequency(pcf8563.RTC_COT_32HZ) + time.Sleep(3 * time.Second) + rtc.SetOscillatorFrequency(pcf8563.RTC_COT_1KHZ) + time.Sleep(3 * time.Second) + rtc.SetOscillatorFrequency(pcf8563.RTC_COT_32KHZ) + time.Sleep(3 * time.Second) + rtc.SetOscillatorFrequency(pcf8563.RTC_COT_DISABLE) + time.Sleep(3 * time.Second) + } +} diff --git a/examples/pcf8563/time/main.go b/examples/pcf8563/time/main.go new file mode 100644 index 0000000..9f1e1b3 --- /dev/null +++ b/examples/pcf8563/time/main.go @@ -0,0 +1,36 @@ +package main + +import ( + "fmt" + "machine" + "time" + + "tinygo.org/x/drivers/pcf8563" +) + +var ( + i2c = machine.I2C0 + rtc = pcf8563.New(i2c) +) + +func main() { + i2c.Configure(machine.I2CConfig{Frequency: machine.TWI_FREQ_400KHZ}) + rtc.Reset() + + rtc.SetTime(time.Date(2006, 1, 2, 15, 4, 5, 0, time.UTC)) + + prev := -1 + + for { + for { + t, _ := rtc.ReadTime() + if prev != t.Second() { + fmt.Printf("%s\r\n", t.String()) + prev = t.Second() + break + } + time.Sleep(time.Millisecond * 100) + } + } + +} diff --git a/examples/pcf8563/timer/main.go b/examples/pcf8563/timer/main.go new file mode 100644 index 0000000..0f9c27f --- /dev/null +++ b/examples/pcf8563/timer/main.go @@ -0,0 +1,46 @@ +package main + +import ( + "fmt" + "machine" + "time" + + "tinygo.org/x/drivers/pcf8563" +) + +var ( + i2c = machine.I2C0 + rtc = pcf8563.New(i2c) +) + +func main() { + i2c.Configure(machine.I2CConfig{Frequency: machine.TWI_FREQ_400KHZ}) + rtc.Reset() + + time.Sleep(3 * time.Second) + rtc.SetTime(time.Date(2006, 1, 2, 15, 4, 5, 0, time.UTC)) + + rtc.SetTimer(15 * time.Second) + rtc.EnableTimerInterrupt() + + prev := -1 + + for { + for { + t, _ := rtc.ReadTime() + if prev != t.Second() { + fmt.Printf("%s\r\n", t.String()) + prev = t.Second() + + if rtc.TimerTriggered() { + fmt.Printf("timer triggered\r\n") + rtc.ClearTimer() + rtc.SetTimer(10 * time.Second) + } + + break + } + time.Sleep(time.Millisecond * 100) + } + } +} diff --git a/pcf8563/pcf8563.go b/pcf8563/pcf8563.go new file mode 100644 index 0000000..875835d --- /dev/null +++ b/pcf8563/pcf8563.go @@ -0,0 +1,252 @@ +// Package pcf8563 implements a driver for the PCF8563 CMOS Real-Time Clock (RTC) +// +// Datasheet: https://www.nxp.com/docs/en/data-sheet/PCF8563.pdf +// + +package pcf8563 + +import ( + "time" + + "tinygo.org/x/drivers" +) + +// Device wraps an I2C connection to a PCF8563 device. +type Device struct { + bus drivers.I2C + Address uint16 +} + +// New creates a new PCF8563 connection. I2C bus must be already configured. +func New(i2c drivers.I2C) Device { + return Device{ + bus: i2c, + Address: PCF8563_ADDR, + } +} + +// Reset resets the `control and status registers`. When this method is +// called, it writes `0x00` to the `control and status registers`. This will +// cause `Alarm` and `Timer` to become Inactive. Please refer to the datasheet +// for details. +func (d *Device) Reset() (err error) { + return d.bus.Tx(d.Address, []byte{0x00, 0x00, 0x00}, nil) +} + +// SetTime sets the time and date +func (d *Device) SetTime(t time.Time) error { + var buf [9]byte + buf[0] = 0x02 + buf[1] = decToBcd(t.Second()) + buf[2] = decToBcd(t.Minute()) + buf[3] = decToBcd(t.Hour()) + buf[4] = decToBcd(t.Day()) + buf[5] = decToBcd(int(t.Weekday() + 1)) + buf[6] = decToBcd(int(t.Month())) + buf[7] = decToBcd(t.Year() - 2000) + err := d.bus.Tx(d.Address, buf[:], nil) + return err +} + +// ReadTime returns the date and time +func (d *Device) ReadTime() (time.Time, error) { + var buf [9]byte + err := d.bus.Tx(d.Address, []byte{0x00}, buf[:]) + if err != nil { + return time.Time{}, err + } + + seconds := bcdToDec(buf[2] & 0x7F) + minute := bcdToDec(buf[3] % 0x7F) + hour := bcdToDec(buf[4] & 0x3F) + day := bcdToDec(buf[5] & 0x3F) + month := time.Month(bcdToDec(buf[7] & 0x0F)) + year := int(bcdToDec(buf[8])) + 2000 + + t := time.Date(year, month, day, hour, minute, seconds, 0, time.UTC) + return t, nil +} + +// SetAlarm sets the alarm +func (d *Device) SetAlarm(t time.Time) error { + var buf [5]byte + buf[0] = 0x09 + buf[1] = RTC_ALARM_ENABLE | decToBcd(t.Minute()) + buf[2] = RTC_ALARM_ENABLE | decToBcd(t.Hour()) + buf[3] = RTC_ALARM_ENABLE | decToBcd(t.Day()) + buf[4] = RTC_ALARM_DISABLE + err := d.bus.Tx(d.Address, buf[:], nil) + if err != nil { + return err + } + + // enable alarm + buf[0] = 0x01 + err = d.bus.Tx(d.Address, buf[:1], buf[1:]) + if err != nil { + return err + } + + buf[1] |= RTC_CTRL_AF + err = d.bus.Tx(d.Address, buf[:2], nil) + return err +} + +// ClearAlarm disables alarm. +func (d *Device) ClearAlarm() error { + var buf [2]byte + buf[0] = 0x01 + err := d.bus.Tx(d.Address, buf[:1], buf[1:]) + if err != nil { + return err + } + + buf[1] &= ^uint8(RTC_CTRL_AF) + err = d.bus.Tx(d.Address, buf[:], nil) + return err +} + +// EnableAlarmInterrupt enables alarm interrupt. When triggered, INT pin (3) +// goes low. +func (d *Device) EnableAlarmInterrupt() error { + var buf [2]byte + buf[0] = 0x01 + err := d.bus.Tx(d.Address, buf[:1], buf[1:]) + if err != nil { + return err + } + + buf[1] |= RTC_CTRL_AIE + err = d.bus.Tx(d.Address, buf[:], nil) + return err +} + +// DisableAlarmInterrupt disable alarm interrupt. +func (d *Device) DisableAlarmInterrupt() error { + var buf [2]byte + buf[0] = 0x01 + err := d.bus.Tx(d.Address, buf[:1], buf[1:]) + if err != nil { + return err + } + + buf[1] &= ^uint8(RTC_CTRL_AIE) + err = d.bus.Tx(d.Address, buf[:], nil) + return err +} + +// AlarmTriggered returns whether or not an Alarm has been triggered. +func (d *Device) AlarmTriggered() bool { + var buf [1]byte + buf[0] = 0x01 + err := d.bus.Tx(d.Address, buf[:], buf[:]) + if err != nil { + return false + } + return (buf[0] & RTC_CTRL_AF) != 0 +} + +// SetTimer sets timer. The available durations are 1 to 127 seconds. If any +// other value is specified, it will be truncated. +func (d *Device) SetTimer(dur time.Duration) error { + var buf [3]byte + + sec := dur / time.Second + if sec > 127 { + sec = 127 + } + + // Treat as sec timer. + buf[0] = 0x0E + buf[1] = RTC_TIMER_1S + buf[2] = byte(sec) + err := d.bus.Tx(d.Address, buf[:], nil) + if err != nil { + return err + } + + // enable alarm + buf[0] = 0x01 + err = d.bus.Tx(d.Address, buf[:1], buf[1:]) + if err != nil { + return err + } + + buf[1] |= RTC_CTRL_TF + err = d.bus.Tx(d.Address, buf[:2], nil) + return err +} + +// ClearTimer disables timer. +func (d *Device) ClearTimer() error { + var buf [2]byte + buf[0] = 0x01 + err := d.bus.Tx(d.Address, buf[:1], buf[1:]) + if err != nil { + return err + } + + buf[1] &= ^uint8(RTC_CTRL_TF) + err = d.bus.Tx(d.Address, buf[:], nil) + return err +} + +// EnableTimerInterrupt enables timer interrupt. When triggered, INT pin (3) +// goes low. +func (d *Device) EnableTimerInterrupt() error { + var buf [2]byte + buf[0] = 0x01 + err := d.bus.Tx(d.Address, buf[:1], buf[1:]) + if err != nil { + return err + } + + buf[1] |= RTC_CTRL_TIE + err = d.bus.Tx(d.Address, buf[:], nil) + return err +} + +// DisableTimerInterrupt disable timer interrupt. +func (d *Device) DisableTimerInterrupt() error { + var buf [2]byte + buf[0] = 0x01 + err := d.bus.Tx(d.Address, buf[:1], buf[1:]) + if err != nil { + return err + } + + buf[1] &= ^uint8(RTC_CTRL_TIE) + err = d.bus.Tx(d.Address, buf[:], nil) + return err +} + +// TimerTriggered returns whether or not an Alarm has been triggered. +func (d *Device) TimerTriggered() bool { + var buf [1]byte + buf[0] = 0x01 + err := d.bus.Tx(d.Address, buf[:], buf[:]) + if err != nil { + return false + } + return (buf[0] & RTC_CTRL_TF) != 0 +} + +// SetOscillatorFrequency sets output oscillator frequency +// Available modes: RTC_COT_DISABLE, RTC_COT_32KHZ, RTC_COT_1KHZ, +// RTC_COT_32Hz, RTC_COT_1HZ. +func (d *Device) SetOscillatorFrequency(sqw uint8) error { + var buf [2]byte + buf[0] = 0x0D + buf[1] = sqw + return d.bus.Tx(d.Address, buf[:], nil) +} + +// decToBcd converts int to BCD +func decToBcd(dec int) uint8 { + return uint8(dec + 6*(dec/10)) +} + +// bcdToDec converts BCD to int +func bcdToDec(bcd uint8) int { + return int(bcd - 6*(bcd>>4)) +} diff --git a/pcf8563/registers.go b/pcf8563/registers.go new file mode 100644 index 0000000..1598848 --- /dev/null +++ b/pcf8563/registers.go @@ -0,0 +1,31 @@ +package pcf8563 + +// Registers +const ( + PCF8563_ADDR = 0x51 // R:A3 W:A2 + + I2C_SPEED_STANDARD = 100000 + I2C_SPEED_DOUBLE = 200000 + + RTC_CTRL_STOP = 0x20 + RTC_CTRL_TITP = 0x10 + RTC_CTRL_AF = 0x08 + RTC_CTRL_TF = 0x04 + RTC_CTRL_AIE = 0x02 + RTC_CTRL_TIE = 0x01 + + RTC_COT_DISABLE = 0x00 + RTC_COT_32KHZ = 0x80 + RTC_COT_1KHZ = 0x81 + RTC_COT_32HZ = 0x82 + RTC_COT_1HZ = 0x83 + + RTC_TIMER_DISABLE = 0x00 + RTC_TIMER_4KHZ = 0x80 + RTC_TIMER_64HZ = 0x81 + RTC_TIMER_1S = 0x82 + RTC_TIMER_60S = 0x83 + + RTC_ALARM_DISABLE = 0x80 + RTC_ALARM_ENABLE = 0x00 +)