Skip to content

Commit cfa50fd

Browse files
thomasrichner-ovivadeadprogram
authored andcommitted
pcf8523: RTC driver
1 parent 5888bb2 commit cfa50fd

File tree

5 files changed

+254
-0
lines changed

5 files changed

+254
-0
lines changed

examples/pcf8523/main.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package main
2+
3+
import (
4+
"machine"
5+
"time"
6+
"tinygo.org/x/drivers/pcf8523"
7+
)
8+
9+
func main() {
10+
machine.I2C0.Configure(machine.I2CConfig{})
11+
dev := pcf8523.New(machine.I2C0)
12+
13+
// make sure the battery takes over if power is lost
14+
err := dev.SetPowerManagement(pcf8523.PowerManagement_SwitchOver_ModeStandard)
15+
if err != nil {
16+
panic(err)
17+
}
18+
19+
// set RTC once, i.e. from `date -u +"%Y-%m-%dT%H:%M:%SZ"`
20+
now, _ := time.Parse(time.RFC3339, "2023-09-18T20:31:38Z")
21+
err = dev.SetTime(now)
22+
if err != nil {
23+
panic(err)
24+
}
25+
26+
for {
27+
ts, err := dev.ReadTime()
28+
if err != nil {
29+
panic(err)
30+
}
31+
println("tick-tock, it's: " + ts.String())
32+
time.Sleep(2 * time.Second)
33+
}
34+
}

pcf8523/pcf8523.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Package pcf8523 implements a driver for the PCF8523 CMOS Real-Time Clock (RTC)
2+
//
3+
// Datasheet: https://www.nxp.com/docs/en/data-sheet/PCF8523.pdf
4+
package pcf8523
5+
6+
import (
7+
"time"
8+
"tinygo.org/x/drivers"
9+
)
10+
11+
type Device struct {
12+
bus drivers.I2C
13+
Address uint8
14+
}
15+
16+
func New(i2c drivers.I2C) Device {
17+
return Device{
18+
bus: i2c,
19+
Address: DefaultAddress,
20+
}
21+
}
22+
23+
// Reset resets the device according to the datasheet section 8.3
24+
// This does not wipe the time registers, but resets control registers.
25+
func (d *Device) Reset() (err error) {
26+
return d.bus.Tx(uint16(d.Address), []byte{rControl1, 0x58}, nil)
27+
}
28+
29+
// SetPowerManagement configures how the device makes use of the backup battery, see
30+
// datasheet section 8.5
31+
func (d *Device) SetPowerManagement(b PowerManagement) error {
32+
return d.setRegister(rControl3, byte(b)<<5, 0xE0)
33+
}
34+
35+
func (d *Device) setRegister(reg uint8, value, mask uint8) error {
36+
var buf [1]byte
37+
err := d.bus.Tx(uint16(d.Address), []byte{reg}, buf[:])
38+
if err != nil {
39+
return err
40+
}
41+
buf[0] = (value & mask) | (buf[0] & (^mask))
42+
return d.bus.Tx(uint16(d.Address), []byte{reg, buf[0]}, nil)
43+
}
44+
45+
// SetTime sets the time and date
46+
func (d *Device) SetTime(t time.Time) error {
47+
buf := []byte{
48+
rSeconds,
49+
bin2bcd(t.Second()),
50+
bin2bcd(t.Minute()),
51+
bin2bcd(t.Hour()),
52+
bin2bcd(t.Day()),
53+
bin2bcd(int(t.Weekday())),
54+
bin2bcd(int(t.Month())),
55+
bin2bcd(t.Year() - 2000),
56+
}
57+
58+
return d.bus.Tx(uint16(d.Address), buf, nil)
59+
}
60+
61+
// ReadTime returns the date and time
62+
func (d *Device) ReadTime() (time.Time, error) {
63+
buf := make([]byte, 9)
64+
err := d.bus.Tx(uint16(d.Address), []byte{rSeconds}, buf)
65+
if err != nil {
66+
return time.Time{}, err
67+
}
68+
69+
seconds := bcd2bin(buf[0] & 0x7F)
70+
minute := bcd2bin(buf[1] & 0x7F)
71+
hour := bcd2bin(buf[2] & 0x3F)
72+
day := bcd2bin(buf[3] & 0x3F)
73+
//skipping weekday buf[4]
74+
month := time.Month(bcd2bin(buf[5] & 0x1F))
75+
year := int(bcd2bin(buf[6])) + 2000
76+
77+
t := time.Date(year, month, day, hour, minute, seconds, 0, time.UTC)
78+
return t, nil
79+
}
80+
81+
// bin2bcd converts binary to BCD
82+
func bin2bcd(dec int) uint8 {
83+
return uint8(dec + 6*(dec/10))
84+
}
85+
86+
// bcd2bin converts BCD to binary
87+
func bcd2bin(bcd uint8) int {
88+
return int(bcd - 6*(bcd>>4))
89+
}

pcf8523/pcf8523_test.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package pcf8523
2+
3+
import (
4+
"encoding/hex"
5+
"testing"
6+
"time"
7+
"tinygo.org/x/drivers/tester"
8+
)
9+
10+
func TestDecToBcd_RoundTrip(t *testing.T) {
11+
12+
for i := 0; i < 60; i++ {
13+
a := bcd2bin(bin2bcd(i))
14+
if a != i {
15+
t.Logf("not equal: %d != %d", a, i)
16+
t.FailNow()
17+
}
18+
}
19+
}
20+
21+
func TestDevice_Reset(t *testing.T) {
22+
bus := tester.NewI2CBus(t)
23+
fake := bus.NewDevice(DefaultAddress)
24+
25+
dev := New(bus)
26+
27+
err := dev.Reset()
28+
assertNoError(t, err)
29+
30+
assertEquals(t, fake.Registers[rControl1], 0x58)
31+
}
32+
33+
func TestDevice_SetPowerManagement(t *testing.T) {
34+
bus := tester.NewI2CBus(t)
35+
fake := bus.NewDevice(DefaultAddress)
36+
37+
dev := New(bus)
38+
39+
err := dev.SetPowerManagement(PowerManagement_SwitchOver_ModeStandard)
40+
assertNoError(t, err)
41+
42+
assertEquals(t, fake.Registers[rControl3], 0b100<<5)
43+
}
44+
45+
func TestDevice_SetTime(t *testing.T) {
46+
bus := tester.NewI2CBus(t)
47+
fake := bus.NewDevice(DefaultAddress)
48+
49+
dev := New(bus)
50+
51+
pointInTime, _ := time.Parse(time.RFC3339, "2023-09-12T22:35:50Z")
52+
err := dev.SetTime(pointInTime)
53+
assertNoError(t, err)
54+
55+
actual := hex.EncodeToString(fake.Registers[rSeconds : rSeconds+7])
56+
expected := "50352212020923"
57+
assertEquals(t, actual, expected)
58+
}
59+
60+
func TestDevice_ReadTime(t *testing.T) {
61+
bus := tester.NewI2CBus(t)
62+
fake := bus.NewDevice(DefaultAddress)
63+
64+
expectedPointInTime := time.Date(2023, 9, 12, 17, 55, 42, 0, time.UTC)
65+
fake.Registers[rSeconds] = 0x42
66+
fake.Registers[rMinutes] = 0x55
67+
fake.Registers[rHours] = 0x17
68+
fake.Registers[rDays] = 0x12
69+
fake.Registers[rMonths] = 0x9
70+
fake.Registers[rYears] = 0x23
71+
72+
dev := New(bus)
73+
74+
//when
75+
actualPointInTime, err := dev.ReadTime()
76+
77+
//then
78+
assertNoError(t, err)
79+
assertEquals(t, actualPointInTime, expectedPointInTime)
80+
}
81+
82+
func assertNoError(t testing.TB, e error) {
83+
if e != nil {
84+
t.Fatalf("unexpected error: %v", e)
85+
}
86+
}
87+
func assertEquals[T comparable](t testing.TB, a, b T) {
88+
if a != b {
89+
t.Fatalf("%v != %v", a, b)
90+
}
91+
}

pcf8523/registers.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package pcf8523
2+
3+
const DefaultAddress = 0x68
4+
5+
// datasheet 8.5 Power management functions, table 11
6+
type PowerManagement byte
7+
8+
const (
9+
PowerManagement_SwitchOver_ModeStandard_LowDetection PowerManagement = 0b000
10+
PowerManagement_SwitchOver_ModeDirect_LowDetection PowerManagement = 0b001
11+
PowerManagement_VddOnly_LowDetection PowerManagement = 0b010
12+
PowerManagement_SwitchOver_ModeStandard PowerManagement = 0b100
13+
PowerManagement_SwitchOver_ModeDirect PowerManagement = 0b101
14+
PowerManagement_VddOnly PowerManagement = 0b101
15+
)
16+
17+
// constants for all internal registers
18+
const (
19+
rControl1 = 0x00 // Control_1
20+
rControl2 = 0x01 // Control_2
21+
rControl3 = 0x02 // Control_3
22+
rSeconds = 0x03 // Seconds
23+
rMinutes = 0x04 // Minutes
24+
rHours = 0x05 // Hours
25+
rDays = 0x06 // Days
26+
rWeekdays = 0x07 // Weekdays
27+
rMonths = 0x08 // Months
28+
rYears = 0x09 // Years
29+
rMinuteAlarm = 0x0A // Minute_alarm
30+
rHourAlarm = 0x0B // Hour_alarm
31+
rDayAlarm = 0x0C // Day_alarm
32+
rWeekdayAlarm = 0x0D // Weekday_alarm
33+
rOffset = 0x0E // Offset
34+
rTimerClkoutControl = 0x0F // Tmr_CLKOUT_ctrl
35+
rTimerAFrequencyControl = 0x10 // Tmr_A_freq_ctrl
36+
rTimerARegister = 0x11 // Tmr_A_reg
37+
rTimerBFrequencyControl = 0x12 // Tmr_B_freq_ctrl
38+
rTimerBRegister = 0x13 // Tmr_B_reg
39+
)

smoketest.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examp
103103
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/max72xx/main.go
104104
tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/dht/main.go
105105
# tinygo build -size short -o ./build/test.hex -target=arduino ./examples/keypad4x4/main.go
106+
tinygo build -size short -o ./build/test.hex -target=feather-rp2040 ./examples/pcf8523/
106107
tinygo build -size short -o ./build/test.hex -target=xiao ./examples/pcf8563/alarm/
107108
tinygo build -size short -o ./build/test.hex -target=xiao ./examples/pcf8563/clkout/
108109
tinygo build -size short -o ./build/test.hex -target=xiao ./examples/pcf8563/time/

0 commit comments

Comments
 (0)