pcf8523: RTC driver

This commit is contained in:
Thomas Richner
2023-09-18 22:38:00 +02:00
committed by deadprogram
parent 5888bb2ded
commit cfa50fd3c2
5 changed files with 254 additions and 0 deletions
+34
View File
@@ -0,0 +1,34 @@
package main
import (
"machine"
"time"
"tinygo.org/x/drivers/pcf8523"
)
func main() {
machine.I2C0.Configure(machine.I2CConfig{})
dev := pcf8523.New(machine.I2C0)
// make sure the battery takes over if power is lost
err := dev.SetPowerManagement(pcf8523.PowerManagement_SwitchOver_ModeStandard)
if err != nil {
panic(err)
}
// set RTC once, i.e. from `date -u +"%Y-%m-%dT%H:%M:%SZ"`
now, _ := time.Parse(time.RFC3339, "2023-09-18T20:31:38Z")
err = dev.SetTime(now)
if err != nil {
panic(err)
}
for {
ts, err := dev.ReadTime()
if err != nil {
panic(err)
}
println("tick-tock, it's: " + ts.String())
time.Sleep(2 * time.Second)
}
}
+89
View File
@@ -0,0 +1,89 @@
// Package pcf8523 implements a driver for the PCF8523 CMOS Real-Time Clock (RTC)
//
// Datasheet: https://www.nxp.com/docs/en/data-sheet/PCF8523.pdf
package pcf8523
import (
"time"
"tinygo.org/x/drivers"
)
type Device struct {
bus drivers.I2C
Address uint8
}
func New(i2c drivers.I2C) Device {
return Device{
bus: i2c,
Address: DefaultAddress,
}
}
// Reset resets the device according to the datasheet section 8.3
// This does not wipe the time registers, but resets control registers.
func (d *Device) Reset() (err error) {
return d.bus.Tx(uint16(d.Address), []byte{rControl1, 0x58}, nil)
}
// SetPowerManagement configures how the device makes use of the backup battery, see
// datasheet section 8.5
func (d *Device) SetPowerManagement(b PowerManagement) error {
return d.setRegister(rControl3, byte(b)<<5, 0xE0)
}
func (d *Device) setRegister(reg uint8, value, mask uint8) error {
var buf [1]byte
err := d.bus.Tx(uint16(d.Address), []byte{reg}, buf[:])
if err != nil {
return err
}
buf[0] = (value & mask) | (buf[0] & (^mask))
return d.bus.Tx(uint16(d.Address), []byte{reg, buf[0]}, nil)
}
// SetTime sets the time and date
func (d *Device) SetTime(t time.Time) error {
buf := []byte{
rSeconds,
bin2bcd(t.Second()),
bin2bcd(t.Minute()),
bin2bcd(t.Hour()),
bin2bcd(t.Day()),
bin2bcd(int(t.Weekday())),
bin2bcd(int(t.Month())),
bin2bcd(t.Year() - 2000),
}
return d.bus.Tx(uint16(d.Address), buf, nil)
}
// ReadTime returns the date and time
func (d *Device) ReadTime() (time.Time, error) {
buf := make([]byte, 9)
err := d.bus.Tx(uint16(d.Address), []byte{rSeconds}, buf)
if err != nil {
return time.Time{}, err
}
seconds := bcd2bin(buf[0] & 0x7F)
minute := bcd2bin(buf[1] & 0x7F)
hour := bcd2bin(buf[2] & 0x3F)
day := bcd2bin(buf[3] & 0x3F)
//skipping weekday buf[4]
month := time.Month(bcd2bin(buf[5] & 0x1F))
year := int(bcd2bin(buf[6])) + 2000
t := time.Date(year, month, day, hour, minute, seconds, 0, time.UTC)
return t, nil
}
// bin2bcd converts binary to BCD
func bin2bcd(dec int) uint8 {
return uint8(dec + 6*(dec/10))
}
// bcd2bin converts BCD to binary
func bcd2bin(bcd uint8) int {
return int(bcd - 6*(bcd>>4))
}
+91
View File
@@ -0,0 +1,91 @@
package pcf8523
import (
"encoding/hex"
"testing"
"time"
"tinygo.org/x/drivers/tester"
)
func TestDecToBcd_RoundTrip(t *testing.T) {
for i := 0; i < 60; i++ {
a := bcd2bin(bin2bcd(i))
if a != i {
t.Logf("not equal: %d != %d", a, i)
t.FailNow()
}
}
}
func TestDevice_Reset(t *testing.T) {
bus := tester.NewI2CBus(t)
fake := bus.NewDevice(DefaultAddress)
dev := New(bus)
err := dev.Reset()
assertNoError(t, err)
assertEquals(t, fake.Registers[rControl1], 0x58)
}
func TestDevice_SetPowerManagement(t *testing.T) {
bus := tester.NewI2CBus(t)
fake := bus.NewDevice(DefaultAddress)
dev := New(bus)
err := dev.SetPowerManagement(PowerManagement_SwitchOver_ModeStandard)
assertNoError(t, err)
assertEquals(t, fake.Registers[rControl3], 0b100<<5)
}
func TestDevice_SetTime(t *testing.T) {
bus := tester.NewI2CBus(t)
fake := bus.NewDevice(DefaultAddress)
dev := New(bus)
pointInTime, _ := time.Parse(time.RFC3339, "2023-09-12T22:35:50Z")
err := dev.SetTime(pointInTime)
assertNoError(t, err)
actual := hex.EncodeToString(fake.Registers[rSeconds : rSeconds+7])
expected := "50352212020923"
assertEquals(t, actual, expected)
}
func TestDevice_ReadTime(t *testing.T) {
bus := tester.NewI2CBus(t)
fake := bus.NewDevice(DefaultAddress)
expectedPointInTime := time.Date(2023, 9, 12, 17, 55, 42, 0, time.UTC)
fake.Registers[rSeconds] = 0x42
fake.Registers[rMinutes] = 0x55
fake.Registers[rHours] = 0x17
fake.Registers[rDays] = 0x12
fake.Registers[rMonths] = 0x9
fake.Registers[rYears] = 0x23
dev := New(bus)
//when
actualPointInTime, err := dev.ReadTime()
//then
assertNoError(t, err)
assertEquals(t, actualPointInTime, expectedPointInTime)
}
func assertNoError(t testing.TB, e error) {
if e != nil {
t.Fatalf("unexpected error: %v", e)
}
}
func assertEquals[T comparable](t testing.TB, a, b T) {
if a != b {
t.Fatalf("%v != %v", a, b)
}
}
+39
View File
@@ -0,0 +1,39 @@
package pcf8523
const DefaultAddress = 0x68
// datasheet 8.5 Power management functions, table 11
type PowerManagement byte
const (
PowerManagement_SwitchOver_ModeStandard_LowDetection PowerManagement = 0b000
PowerManagement_SwitchOver_ModeDirect_LowDetection PowerManagement = 0b001
PowerManagement_VddOnly_LowDetection PowerManagement = 0b010
PowerManagement_SwitchOver_ModeStandard PowerManagement = 0b100
PowerManagement_SwitchOver_ModeDirect PowerManagement = 0b101
PowerManagement_VddOnly PowerManagement = 0b101
)
// constants for all internal registers
const (
rControl1 = 0x00 // Control_1
rControl2 = 0x01 // Control_2
rControl3 = 0x02 // Control_3
rSeconds = 0x03 // Seconds
rMinutes = 0x04 // Minutes
rHours = 0x05 // Hours
rDays = 0x06 // Days
rWeekdays = 0x07 // Weekdays
rMonths = 0x08 // Months
rYears = 0x09 // Years
rMinuteAlarm = 0x0A // Minute_alarm
rHourAlarm = 0x0B // Hour_alarm
rDayAlarm = 0x0C // Day_alarm
rWeekdayAlarm = 0x0D // Weekday_alarm
rOffset = 0x0E // Offset
rTimerClkoutControl = 0x0F // Tmr_CLKOUT_ctrl
rTimerAFrequencyControl = 0x10 // Tmr_A_freq_ctrl
rTimerARegister = 0x11 // Tmr_A_reg
rTimerBFrequencyControl = 0x12 // Tmr_B_freq_ctrl
rTimerBRegister = 0x13 // Tmr_B_reg
)
+1
View File
@@ -103,6 +103,7 @@ tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examp
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/max72xx/main.go
tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/dht/main.go
# tinygo build -size short -o ./build/test.hex -target=arduino ./examples/keypad4x4/main.go
tinygo build -size short -o ./build/test.hex -target=feather-rp2040 ./examples/pcf8523/
tinygo build -size short -o ./build/test.hex -target=xiao ./examples/pcf8563/alarm/
tinygo build -size short -o ./build/test.hex -target=xiao ./examples/pcf8563/clkout/
tinygo build -size short -o ./build/test.hex -target=xiao ./examples/pcf8563/time/