1
0
mirror of https://github.com/hybridgroup/gobot.git synced 2025-04-24 13:48:49 +08:00

i2c PCF8583 clock and counter driver introduced

This commit is contained in:
Thomas Kohler 2022-06-12 19:00:09 +02:00
parent d827c913c5
commit 9d1fd13a2f
6 changed files with 1096 additions and 0 deletions

View File

@ -288,6 +288,7 @@ drivers provided using the `gobot/drivers/i2c` package:
- MPU6050 Accelerometer/Gyroscope
- PCA9501 8-bit I/O port with interrupt, 2-kbit EEPROM
- PCA9685 16-channel 12-bit PWM/Servo Driver
- PCF8583 clock and calendar or event counter, 240 x 8-bit RAM
- PCF8591 8-bit 4xA/D & 1xD/A converter
- SHT2x Temperature/Humidity
- SHT3x-D Temperature/Humidity

View File

@ -40,6 +40,7 @@ Gobot has a extensible system for connecting to hardware devices. The following
- MPU6050 Accelerometer/Gyroscope
- PCA9501 8-bit I/O port with interrupt, 2-kbit EEPROM
- PCA9685 16-channel 12-bit PWM/Servo Driver
- PCF8583 clock and calendar or event counter, 240 x 8-bit RAM
- PCF8591 8-bit 4xA/D & 1xD/A converter
- SHT2x Temperature/Humidity
- SHT3x-D Temperature/Humidity

View File

@ -0,0 +1,360 @@
package i2c
import (
"fmt"
"log"
"time"
)
const (
pcf8583Debug = true
// PCF8583 supports addresses 0x50 and 0x51
// The default address applies when the address pin is grounded.
pcf8583DefaultAddress = 0x50
// default is 0x10, when set to 0 also some free or unused RAM can be accessed
pcf8583RamOffset = 0x10
)
// PCF8583Control is used to specify control and status register content
type PCF8583Control uint8
const (
// registers are named according to the datasheet
pcf8583Reg_CTRL = iota // 0x00
pcf8583Reg_SUBSEC_D0D1 // 0x01
pcf8583Reg_SEC_D2D3 // 0x02
pcf8583Reg_MIN_D4D5 // 0x03
pcf8583Reg_HOUR // 0x04
pcf8583Reg_YEARDATE // 0x05
pcf8583Reg_WEEKDAYMONTH // 0x06
pcf8583Reg_TIMER // 0x07
pcf8583Reg_ALARMCTRL // 0x08, offset for all alarm registers 0x09 ... 0xF
pcf8583CtrlTimerFlag PCF8583Control = 0x01 // 50% duty factor, seconds flag if alarm enable bit is 0
pcf8583CtrlAlarmFlag PCF8583Control = 0x02 // 50% duty factor, minutes flag if alarm enable bit is 0
pcf8583CtrlAlarmEnable PCF8583Control = 0x04 // if enabled, memory 08h is alarm control register
pcf8583CtrlMask PCF8583Control = 0x08 // 0: read 05h, 06h unmasked, 1: read date and month count directly
PCF8583CtrlModeClock50 PCF8583Control = 0x10 // clock mode with 50 Hz
PCF8583CtrlModeCounter PCF8583Control = 0x20 // event counter mode
PCF8583CtrlModeTest PCF8583Control = 0x30 // test mode
pcf8583CtrlHoldLastCount PCF8583Control = 0x40 // 0: count, 1: store and hold count in capture latches
pcf8583CtrlStopCounting PCF8583Control = 0x80 // 0: count, 1: stop counting, reset divider
)
// PCF8583Driver is a Gobot Driver for the PCF8583 clock and calendar chip & 240 x 8-bit bit RAM with 1 address program pin.
// please refer to data sheet: https://www.nxp.com/docs/en/data-sheet/PCF8583.pdf
//
// 0 1 0 1 0 0 0 A0|rd
// Lowest bit (rd) is mapped to switch between write(0)/read(1), it is not part of the "real" address.
//
// PCF8583 is mainly compatible to PCF8593, so this driver should also work for PCF8593 except RAM calls
//
// This driver was tested with Tinkerboard.
type PCF8583Driver struct {
*Driver
mode PCF8583Control // clock 32.768kHz (default), clock 50Hz, event counter
yearOffset int
ramOffset byte
}
// NewPCF8583Driver creates a new driver with specified i2c interface
// Params:
// c Connector - the Adaptor to use with this Driver
//
// Optional params:
// i2c.WithBus(int): bus to use with this driver
// i2c.WithAddress(int): address to use with this driver
// i2c.WithPCF8583Mode(PCF8583Control): mode of this driver
//
func NewPCF8583Driver(c Connector, options ...func(Config)) *PCF8583Driver {
d := &PCF8583Driver{
Driver: NewDriver(c, "PCF8583", pcf8583DefaultAddress),
ramOffset: pcf8583RamOffset,
}
d.afterStart = d.initialize
for _, option := range options {
option(d)
}
// API commands
d.AddCommand("WriteTime", func(params map[string]interface{}) interface{} {
val := params["val"].(time.Time)
err := d.WriteTime(val)
return map[string]interface{}{"err": err}
})
d.AddCommand("ReadTime", func(params map[string]interface{}) interface{} {
val, err := d.ReadTime()
return map[string]interface{}{"val": val, "err": err}
})
d.AddCommand("WriteCounter", func(params map[string]interface{}) interface{} {
val := params["val"].(int32)
err := d.WriteCounter(val)
return map[string]interface{}{"err": err}
})
d.AddCommand("ReadCounter", func(params map[string]interface{}) interface{} {
val, err := d.ReadCounter()
return map[string]interface{}{"val": val, "err": err}
})
d.AddCommand("WriteRAM", func(params map[string]interface{}) interface{} {
address := params["address"].(uint8)
val := params["val"].(uint8)
err := d.WriteRAM(address, val)
return map[string]interface{}{"err": err}
})
d.AddCommand("ReadRAM", func(params map[string]interface{}) interface{} {
address := params["address"].(uint8)
val, err := d.ReadRAM(address)
return map[string]interface{}{"val": val, "err": err}
})
return d
}
// WithPCF8583Mode is used to change the mode between 32.678kHz clock, 50Hz clock, event counter
// Valid settings are of type "PCF8583Control"
func WithPCF8583Mode(mode PCF8583Control) func(Config) {
return func(c Config) {
d, ok := c.(*PCF8583Driver)
if ok {
if !mode.isClockMode() && !mode.isCounterMode() {
panic(fmt.Sprintf("%s: mode 0x%02x is not supported", d.name, mode))
}
d.mode = mode
} else if pcf8583Debug {
log.Printf("trying to set mode for non-PCF8583Driver %v", c)
}
}
}
// WriteTime setup the clock registers with the given time
func (d *PCF8583Driver) WriteTime(val time.Time) error {
d.mutex.Lock()
defer d.mutex.Unlock()
// according to chapter 7.11 of the product data sheet, the stop counting flag of the control/status register
// must be set before, so we read the control byte before and only set/reset the stop
ctrlRegVal, err := d.connection.ReadByteData(uint8(pcf8583Reg_CTRL))
if err != nil {
return err
}
if !PCF8583Control(ctrlRegVal).isClockMode() {
return fmt.Errorf("%s: can't write time because the device is in wrong mode 0x%02x", d.name, ctrlRegVal)
}
// auto increment feature is used
year, month, day := val.Date()
written, err := d.connection.Write([]byte{
uint8(pcf8583Reg_CTRL), ctrlRegVal | uint8(pcf8583CtrlStopCounting),
pcf8583encodeBcd(uint8(val.Nanosecond() / 1000000 / 10)), // sub seconds in 1/10th seconds
pcf8583encodeBcd(uint8(val.Second())),
pcf8583encodeBcd(uint8(val.Minute())),
pcf8583encodeBcd(uint8(val.Hour())),
pcf8583encodeBcd(uint8(day)), // year, date (we keep the year counter zero and set the offset)
uint8(val.Weekday())<<5 | pcf8583encodeBcd(uint8(month)), // month, weekday (not BCD): Sunday = 0, Monday = 1 ...
})
if err != nil {
return err
}
if written != 8 {
return fmt.Errorf("%s: %d bytes written, but %d expected", d.name, written, 8)
}
d.yearOffset = year
return d.run(ctrlRegVal)
}
// ReadTime reads the clock and returns the value
func (d *PCF8583Driver) ReadTime() (val time.Time, err error) {
d.mutex.Lock()
defer d.mutex.Unlock()
// according to chapter 7.1 of the product data sheet, the setting of "hold last count" flag
// is not needed when reading with auto increment
ctrlRegVal, err := d.connection.ReadByteData(uint8(pcf8583Reg_CTRL))
if err != nil {
return
}
if !PCF8583Control(ctrlRegVal).isClockMode() {
return val, fmt.Errorf("%s: can't read time because the device is in wrong mode 0x%02x", d.name, ctrlRegVal)
}
// auto increment feature is used
clockDataSize := 6
data := make([]byte, clockDataSize)
read, err := d.connection.Read(data)
if err != nil {
return
}
if read != clockDataSize {
return val, fmt.Errorf("%s: %d bytes read, but %d expected", d.name, read, clockDataSize)
}
nanos := int(pcf8583decodeBcd(data[0])) * 1000000 * 10 // sub seconds in 1/10th seconds
seconds := int(pcf8583decodeBcd(data[1]))
minutes := int(pcf8583decodeBcd(data[2]))
hours := int(pcf8583decodeBcd(data[3]))
// year, date (the device can only count 4 years)
year := int(data[4]>>6) + d.yearOffset // use the first two bits, no BCD
date := int(pcf8583decodeBcd(data[4] & 0x3F)) // remove the year-bits for date
// weekday (not used here), month
month := time.Month(pcf8583decodeBcd(data[5] & 0x1F)) // remove the weekday-bits
return time.Date(year, month, date, hours, minutes, seconds, nanos, time.UTC), nil
}
// WriteCounter writes the counter registers
func (d *PCF8583Driver) WriteCounter(val int32) error {
d.mutex.Lock()
defer d.mutex.Unlock()
// we don't care of negative values here
// according to chapter 7.11 of the product data sheet, the stop counting flag of the control/status register
// must be set before, so we read the control byte before and only set/reset the stop
ctrlRegVal, err := d.connection.ReadByteData(uint8(pcf8583Reg_CTRL))
if err != nil {
return err
}
if !PCF8583Control(ctrlRegVal).isCounterMode() {
return fmt.Errorf("%s: can't write counter because the device is in wrong mode 0x%02x", d.name, ctrlRegVal)
}
// auto increment feature is used, PCF8583 not working with WriteBlockData
written, err := d.connection.Write([]byte{
uint8(pcf8583Reg_CTRL), ctrlRegVal | uint8(pcf8583CtrlStopCounting), // stop
pcf8583encodeBcd(uint8(val % 100)), // 2 lowest digits
pcf8583encodeBcd(uint8((val / 100) % 100)), // 2 middle digits
pcf8583encodeBcd(uint8((val / 10000) % 100)), // 2 highest digits
})
if err != nil {
return err
}
if written != 5 {
return fmt.Errorf("%s: %d bytes written, but %d expected", d.name, written, 5)
}
return d.run(ctrlRegVal)
}
// ReadCounter reads the counter registers
func (d *PCF8583Driver) ReadCounter() (val int32, err error) {
d.mutex.Lock()
defer d.mutex.Unlock()
// according to chapter 7.1 of the product data sheet, the setting of "hold last count" flag
// is not needed when reading with auto increment
ctrlRegVal, err := d.connection.ReadByteData(uint8(pcf8583Reg_CTRL))
if err != nil {
return
}
if !PCF8583Control(ctrlRegVal).isCounterMode() {
return val, fmt.Errorf("%s: can't read counter because the device is in wrong mode 0x%02x", d.name, ctrlRegVal)
}
// auto increment feature is used
counterDataSize := 3
data := make([]byte, counterDataSize)
read, err := d.connection.Read(data)
if err != nil {
return
}
if read != counterDataSize {
return val, fmt.Errorf("%s: %d bytes read, but %d expected", d.name, read, counterDataSize)
}
return int32(pcf8583decodeBcd(data[0])) +
int32(pcf8583decodeBcd(data[1]))*100 +
int32(pcf8583decodeBcd(data[2]))*10000, nil
}
// WriteRAM writes a value to a given address in memory (0x00-0xFF)
func (d *PCF8583Driver) WriteRAM(address uint8, val uint8) error {
d.mutex.Lock()
defer d.mutex.Unlock()
realAddress := uint16(address) + uint16(d.ramOffset)
if realAddress > 0xFF {
return fmt.Errorf("%s: RAM address overflow %d", d.name, realAddress)
}
return d.connection.WriteByteData(uint8(realAddress), val)
}
// ReadRAM reads a value from a given address (0x00-0xFF)
func (d *PCF8583Driver) ReadRAM(address uint8) (val uint8, err error) {
d.mutex.Lock()
defer d.mutex.Unlock()
realAddress := uint16(address) + uint16(d.ramOffset)
if realAddress > 0xFF {
return val, fmt.Errorf("%s: RAM address overflow %d", d.name, realAddress)
}
return d.connection.ReadByteData(uint8(realAddress))
}
func (d *PCF8583Driver) run(ctrlRegVal uint8) error {
ctrlRegVal = ctrlRegVal & ^uint8(pcf8583CtrlStopCounting) // reset stop bit
return d.connection.WriteByteData(uint8(pcf8583Reg_CTRL), ctrlRegVal)
}
func (d *PCF8583Driver) initialize() error {
// switch to configured mode
ctrlRegVal, err := d.connection.ReadByteData(uint8(pcf8583Reg_CTRL))
if err != nil {
return err
}
if d.mode.isModeDiffer(PCF8583Control(ctrlRegVal)) {
ctrlRegVal = ctrlRegVal&^uint8(PCF8583CtrlModeTest) | uint8(d.mode)
if err = d.connection.WriteByteData(uint8(pcf8583Reg_CTRL), ctrlRegVal); err != nil {
return err
}
if pcf8583Debug {
if PCF8583Control(ctrlRegVal).isCounterMode() {
log.Printf("%s switched to counter mode 0x%02x", d.name, ctrlRegVal)
} else {
log.Printf("%s switched to clock mode 0x%02x", d.name, ctrlRegVal)
}
}
}
return nil
}
func (c PCF8583Control) isClockMode() bool {
return uint8(c)&uint8(PCF8583CtrlModeCounter) == 0
}
func (c PCF8583Control) isCounterMode() bool {
counterModeSet := (uint8(c) & uint8(PCF8583CtrlModeCounter)) != 0
clockMode50Set := (uint8(c) & uint8(PCF8583CtrlModeClock50)) != 0
return counterModeSet && !clockMode50Set
}
func (c PCF8583Control) isModeDiffer(mode PCF8583Control) bool {
return uint8(c)&uint8(PCF8583CtrlModeTest) != uint8(mode)&uint8(PCF8583CtrlModeTest)
}
func pcf8583encodeBcd(val byte) byte {
// decimal 12 => 0x12
if val > 99 {
val = 99
if pcf8583Debug {
log.Printf("PCF8583 BCD value (%d) exceeds limit of 99, now limited.", val)
}
}
hi, lo := byte(val/10), byte(val%10)
return hi<<4 | lo
}
func pcf8583decodeBcd(bcd byte) byte {
// 0x12 => decimal 12
hi, lo := byte(bcd>>4), byte(bcd&0x0f)
if hi > 9 {
hi = 9
if pcf8583Debug {
log.Printf("PCF8583 BCD value (%02x) exceeds limit 0x99 on most significant digit, now limited", bcd)
}
}
if lo > 9 {
lo = 9
if pcf8583Debug {
log.Printf("PCF8583 BCD value (%02x) exceeds limit 0x99 on least significant digit, now limited", bcd)
}
}
return 10*hi + lo
}

View File

@ -0,0 +1,612 @@
package i2c
import (
"strings"
"testing"
"time"
"gobot.io/x/gobot"
"gobot.io/x/gobot/gobottest"
)
// this ensures that the implementation is based on i2c.Driver, which implements the gobot.Driver
// and tests all implementations, so no further tests needed here for gobot.Driver interface
var _ gobot.Driver = (*PCF8583Driver)(nil)
func initTestPCF8583WithStubbedAdaptor() (*PCF8583Driver, *i2cTestAdaptor) {
a := newI2cTestAdaptor()
d := NewPCF8583Driver(a)
d.Start()
return d, a
}
func TestNewPCF8583Driver(t *testing.T) {
var di interface{} = NewPCF8583Driver(newI2cTestAdaptor())
d, ok := di.(*PCF8583Driver)
if !ok {
t.Errorf("NewPCF8583Driver() should have returned a *PCF8583Driver")
}
gobottest.Refute(t, d.Driver, nil)
gobottest.Assert(t, strings.HasPrefix(d.name, "PCF8583"), true)
gobottest.Assert(t, d.mode, PCF8583Control(0x00))
gobottest.Assert(t, d.yearOffset, 0)
gobottest.Assert(t, d.ramOffset, uint8(0x10))
}
func TestPCF8583Options(t *testing.T) {
// This is a general test, that options are applied in constructor by using the common WithBus() option and
// least one of this driver. Further tests for options can also be done by call of "WithOption(val)(d)".
d := NewPCF8583Driver(newI2cTestAdaptor(), WithBus(2), WithPCF8583Mode(PCF8583CtrlModeClock50))
gobottest.Assert(t, d.GetBusOrDefault(1), 2)
gobottest.Assert(t, d.mode, PCF8583CtrlModeClock50)
}
func TestPCF8583CommandsWriteTime(t *testing.T) {
// arrange
d, a := initTestPCF8583WithStubbedAdaptor()
readCtrlState := uint8(0x10) // clock 50Hz
// arrange writes
a.i2cWriteImpl = func(b []byte) (int, error) {
return len(b), nil
}
// arrange reads
numCallsRead := 0
a.i2cReadImpl = func(b []byte) (int, error) {
numCallsRead++
b[len(b)-1] = readCtrlState
return len(b), nil
}
// act
result := d.Command("WriteTime")(map[string]interface{}{"val": time.Now()})
// assert
gobottest.Assert(t, result.(map[string]interface{})["err"], nil)
}
func TestPCF8583CommandsReadTime(t *testing.T) {
// arrange
d, a := initTestPCF8583WithStubbedAdaptor()
d.yearOffset = 2019
milliSec := 550 * time.Millisecond // 0.55 sec = 550 ms
want := time.Date(2021, time.December, 24, 18, 00, 00, int(milliSec), time.UTC)
reg0Val := uint8(0x00) // clock mode 32.768 kHz
reg1Val := uint8(0x55) // BCD: 1/10 and 1/100 sec (55)
reg2Val := uint8(0x00) // BCD: 10 and 1 sec (00)
reg3Val := uint8(0x00) // BCD: 10 and 1 min (00)
reg4Val := uint8(0x18) // BCD: 10 and 1 hour (18)
reg5Val := uint8(0xA4) // year (2) and BCD: date (24)
reg6Val := uint8(0xB2) // weekday 5, bit 5 and bit 7 (0xA0) and BCD: month (0x12)
returnRead := [2][]uint8{
{reg0Val},
{reg1Val, reg2Val, reg3Val, reg4Val, reg5Val, reg6Val},
}
// arrange reads
// arrange reads
numCallsRead := 0
a.i2cReadImpl = func(b []byte) (int, error) {
numCallsRead++
rr := returnRead[numCallsRead-1]
for i := 0; i < len(b); i++ {
b[i] = rr[i]
}
return len(b), nil
}
// act
result := d.Command("ReadTime")(map[string]interface{}{})
// assert
gobottest.Assert(t, result.(map[string]interface{})["err"], nil)
gobottest.Assert(t, result.(map[string]interface{})["val"], want)
}
func TestPCF8583CommandsWriteCounter(t *testing.T) {
// arrange
d, a := initTestPCF8583WithStubbedAdaptor()
readCtrlState := uint8(0x20) // counter
// arrange writes
a.i2cWriteImpl = func(b []byte) (int, error) {
return len(b), nil
}
// arrange reads
numCallsRead := 0
a.i2cReadImpl = func(b []byte) (int, error) {
numCallsRead++
b[len(b)-1] = readCtrlState
return len(b), nil
}
// act
result := d.Command("WriteCounter")(map[string]interface{}{"val": int32(123456)})
// assert
gobottest.Assert(t, result.(map[string]interface{})["err"], nil)
}
func TestPCF8583CommandsReadCounter(t *testing.T) {
// arrange
d, a := initTestPCF8583WithStubbedAdaptor()
want := int32(123456)
reg0Val := uint8(0x20) // counter mode
reg1Val := uint8(0x56) // BCD: 56
reg2Val := uint8(0x34) // BCD: 34
reg3Val := uint8(0x12) // BCD: 12
returnRead := [2][]uint8{
{reg0Val},
{reg1Val, reg2Val, reg3Val},
}
// arrange reads
// arrange reads
numCallsRead := 0
a.i2cReadImpl = func(b []byte) (int, error) {
numCallsRead++
rr := returnRead[numCallsRead-1]
for i := 0; i < len(b); i++ {
b[i] = rr[i]
}
return len(b), nil
}
// act
result := d.Command("ReadCounter")(map[string]interface{}{})
// assert
gobottest.Assert(t, result.(map[string]interface{})["err"], nil)
gobottest.Assert(t, result.(map[string]interface{})["val"], want)
}
func TestPCF8583CommandsWriteRAM(t *testing.T) {
// arrange
d, _ := initTestPCF8583WithStubbedAdaptor()
var addressValue = map[string]interface{}{
"address": uint8(0x12),
"val": uint8(0x45),
}
// act
result := d.Command("WriteRAM")(addressValue)
// assert
gobottest.Assert(t, result.(map[string]interface{})["err"], nil)
}
func TestPCF8583CommandsReadRAM(t *testing.T) {
// arrange
d, _ := initTestPCF8583WithStubbedAdaptor()
var address = map[string]interface{}{
"address": uint8(0x34),
}
// act
result := d.Command("ReadRAM")(address)
// assert
gobottest.Assert(t, result.(map[string]interface{})["err"], nil)
gobottest.Assert(t, result.(map[string]interface{})["val"], uint8(0))
}
func TestPCF8583WriteTime(t *testing.T) {
// sequence to write the time:
// * read control register for get current state and ensure an clock mode is set
// * write the control register (stop counting)
// * create the values for date registers (default is 24h mode)
// * write the clock and calendar registers with auto increment
// * write the control register (start counting)
// arrange
d, a := initTestPCF8583WithStubbedAdaptor()
a.written = []byte{} // reset writes of Start() and former test
readCtrlState := uint8(0x07) // 32.768kHz clock mode
milliSec := 210 * time.Millisecond // 0.21 sec = 210 ms
initDate := time.Date(2022, time.December, 16, 15, 14, 13, int(milliSec), time.UTC)
wantCtrlStop := uint8(0x87) // stop counting bit is set
wantReg1Val := uint8(0x21) // BCD: 1/10 and 1/100 sec (21)
wantReg2Val := uint8(0x13) // BCD: 10 and 1 sec (13)
wantReg3Val := uint8(0x14) // BCD: 10 and 1 min (14)
wantReg4Val := uint8(0x15) // BCD: 10 and 1 hour (15)
wantReg5Val := uint8(0x16) // year (0) and BCD: date (16)
wantReg6Val := uint8(0xB2) // weekday 5, bit 5 and bit 7 (0xA0) and BCD: month (0x12)
wantCrtlStart := uint8(0x07) // stop counting bit is reset
// arrange writes
a.i2cWriteImpl = func(b []byte) (int, error) {
return len(b), nil
}
// arrange reads
numCallsRead := 0
a.i2cReadImpl = func(b []byte) (int, error) {
numCallsRead++
b[len(b)-1] = readCtrlState
return len(b), nil
}
// act
err := d.WriteTime(initDate)
// assert
gobottest.Assert(t, err, nil)
gobottest.Assert(t, d.yearOffset, initDate.Year())
gobottest.Assert(t, numCallsRead, 1)
gobottest.Assert(t, len(a.written), 11)
gobottest.Assert(t, a.written[0], uint8(pcf8583Reg_CTRL))
gobottest.Assert(t, a.written[1], uint8(pcf8583Reg_CTRL))
gobottest.Assert(t, a.written[2], wantCtrlStop)
gobottest.Assert(t, a.written[3], wantReg1Val)
gobottest.Assert(t, a.written[4], wantReg2Val)
gobottest.Assert(t, a.written[5], wantReg3Val)
gobottest.Assert(t, a.written[6], wantReg4Val)
gobottest.Assert(t, a.written[7], wantReg5Val)
gobottest.Assert(t, a.written[8], wantReg6Val)
gobottest.Assert(t, a.written[9], uint8(pcf8583Reg_CTRL))
gobottest.Assert(t, a.written[10], wantCrtlStart)
}
func TestPCF8583WriteTimeNoTimeModeFails(t *testing.T) {
// arrange
d, a := initTestPCF8583WithStubbedAdaptor()
a.written = []byte{} // reset writes of Start() and former test
readCtrlState := uint8(0x30) // test mode
// arrange writes
a.i2cWriteImpl = func(b []byte) (int, error) {
return len(b), nil
}
// arrange reads
numCallsRead := 0
a.i2cReadImpl = func(b []byte) (int, error) {
numCallsRead++
b[len(b)-1] = readCtrlState
return len(b), nil
}
// act
err := d.WriteTime(time.Now())
// assert
gobottest.Refute(t, err, nil)
gobottest.Assert(t, strings.Contains(err.Error(), "wrong mode 0x30"), true)
gobottest.Assert(t, len(a.written), 1)
gobottest.Assert(t, a.written[0], uint8(pcf8583Reg_CTRL))
gobottest.Assert(t, numCallsRead, 1)
}
func TestPCF8583ReadTime(t *testing.T) {
// sequence to read the time:
// * read the control register to determine mask flag and ensure an clock mode is set
// * read the clock and calendar registers with auto increment
// * create the value out of registers content
// arrange
d, a := initTestPCF8583WithStubbedAdaptor()
a.written = []byte{} // reset writes of Start() and former test
d.yearOffset = 2020
milliSec := 210 * time.Millisecond // 0.21 sec = 210 ms
want := time.Date(2022, time.December, 16, 15, 14, 13, int(milliSec), time.UTC)
reg0Val := uint8(0x10) // clock mode 50Hz
reg1Val := uint8(0x21) // BCD: 1/10 and 1/100 sec (21)
reg2Val := uint8(0x13) // BCD: 10 and 1 sec (13)
reg3Val := uint8(0x14) // BCD: 10 and 1 min (14)
reg4Val := uint8(0x15) // BCD: 10 and 1 hour (15)
reg5Val := uint8(0x96) // year (2) and BCD: date (16)
reg6Val := uint8(0xB2) // weekday 5, bit 5 and bit 7 (0xA0) and BCD: month (0x12)
returnRead := [2][]uint8{
{reg0Val},
{reg1Val, reg2Val, reg3Val, reg4Val, reg5Val, reg6Val},
}
// arrange writes
a.i2cWriteImpl = func(b []byte) (int, error) {
return len(b), nil
}
// arrange reads
numCallsRead := 0
a.i2cReadImpl = func(b []byte) (int, error) {
numCallsRead++
rr := returnRead[numCallsRead-1]
for i := 0; i < len(b); i++ {
b[i] = rr[i]
}
return len(b), nil
}
// act
got, err := d.ReadTime()
// assert
gobottest.Assert(t, err, nil)
gobottest.Assert(t, len(a.written), 1)
gobottest.Assert(t, a.written[0], uint8(pcf8583Reg_CTRL))
gobottest.Assert(t, numCallsRead, 2)
gobottest.Assert(t, got, want)
}
func TestPCF8583ReadTimeNoTimeModeFails(t *testing.T) {
// arrange
d, a := initTestPCF8583WithStubbedAdaptor()
a.written = []byte{} // reset writes of Start() and former test
readCtrlState := uint8(0x20) // counter mode
// arrange writes
a.i2cWriteImpl = func(b []byte) (int, error) {
return len(b), nil
}
// arrange reads
numCallsRead := 0
a.i2cReadImpl = func(b []byte) (int, error) {
numCallsRead++
b[len(b)-1] = readCtrlState
return len(b), nil
}
// act
got, err := d.ReadTime()
// assert
gobottest.Refute(t, err, nil)
gobottest.Assert(t, strings.Contains(err.Error(), "wrong mode 0x20"), true)
gobottest.Assert(t, got, time.Time{})
gobottest.Assert(t, len(a.written), 1)
gobottest.Assert(t, a.written[0], uint8(pcf8583Reg_CTRL))
gobottest.Assert(t, numCallsRead, 1)
}
func TestPCF8583WriteCounter(t *testing.T) {
// sequence to write the counter:
// * read control register for get current state and ensure the event counter mode is set
// * write the control register (stop counting)
// * create the values for counter registers
// * write the counter registers
// * write the control register (start counting)
// arrange
d, a := initTestPCF8583WithStubbedAdaptor()
a.written = []byte{} // reset writes of Start() and former test
readCtrlState := uint8(0x27) // counter mode
initCount := int32(654321) // 6 digits used of 10 possible with int32
wantCtrlStop := uint8(0xA7) // stop counting bit is set
wantReg1Val := uint8(0x21) // BCD: 21
wantReg2Val := uint8(0x43) // BCD: 43
wantReg3Val := uint8(0x65) // BCD: 65
wantCtrlStart := uint8(0x27) // counter mode
// arrange writes
a.i2cWriteImpl = func(b []byte) (int, error) {
return len(b), nil
}
// arrange reads
numCallsRead := 0
a.i2cReadImpl = func(b []byte) (int, error) {
numCallsRead++
b[len(b)-1] = readCtrlState
return len(b), nil
}
// act
err := d.WriteCounter(initCount)
// assert
gobottest.Assert(t, err, nil)
gobottest.Assert(t, numCallsRead, 1)
gobottest.Assert(t, len(a.written), 8)
gobottest.Assert(t, a.written[0], uint8(pcf8583Reg_CTRL))
gobottest.Assert(t, a.written[1], uint8(pcf8583Reg_CTRL))
gobottest.Assert(t, a.written[2], wantCtrlStop)
gobottest.Assert(t, a.written[3], wantReg1Val)
gobottest.Assert(t, a.written[4], wantReg2Val)
gobottest.Assert(t, a.written[5], wantReg3Val)
gobottest.Assert(t, a.written[6], uint8(pcf8583Reg_CTRL))
gobottest.Assert(t, a.written[7], wantCtrlStart)
}
func TestPCF8583WriteCounterNoCounterModeFails(t *testing.T) {
// arrange
d, a := initTestPCF8583WithStubbedAdaptor()
a.written = []byte{} // reset writes of Start() and former test
readCtrlState := uint8(0x10) // 50Hz mode
// arrange writes
a.i2cWriteImpl = func(b []byte) (int, error) {
return len(b), nil
}
// arrange reads
numCallsRead := 0
a.i2cReadImpl = func(b []byte) (int, error) {
numCallsRead++
b[len(b)-1] = readCtrlState
return len(b), nil
}
// act
err := d.WriteCounter(123)
// assert
gobottest.Refute(t, err, nil)
gobottest.Assert(t, strings.Contains(err.Error(), "wrong mode 0x10"), true)
gobottest.Assert(t, len(a.written), 1)
gobottest.Assert(t, a.written[0], uint8(pcf8583Reg_CTRL))
gobottest.Assert(t, numCallsRead, 1)
}
func TestPCF8583ReadCounter(t *testing.T) {
// sequence to read the counter:
// * read the control register to ensure the event counter mode is set
// * read the counter registers
// * create the value out of registers content
// arrange
d, a := initTestPCF8583WithStubbedAdaptor()
a.written = []byte{} // reset writes of Start() and former test
want := int32(654321)
reg0Val := uint8(0x20) // counter mode
reg1Val := uint8(0x21) // BCD: 21
reg2Val := uint8(0x43) // BCD: 43
reg3Val := uint8(0x65) // BCD: 65
returnRead := [2][]uint8{
{reg0Val},
{reg1Val, reg2Val, reg3Val},
}
// arrange writes
a.i2cWriteImpl = func(b []byte) (int, error) {
return len(b), nil
}
// arrange reads
numCallsRead := 0
a.i2cReadImpl = func(b []byte) (int, error) {
numCallsRead++
rr := returnRead[numCallsRead-1]
for i := 0; i < len(b); i++ {
b[i] = rr[i]
}
return len(b), nil
}
// act
got, err := d.ReadCounter()
// assert
gobottest.Assert(t, err, nil)
gobottest.Assert(t, len(a.written), 1)
gobottest.Assert(t, a.written[0], uint8(pcf8583Reg_CTRL))
gobottest.Assert(t, numCallsRead, 2)
gobottest.Assert(t, got, want)
}
func TestPCF8583ReadCounterNoCounterModeFails(t *testing.T) {
// arrange
d, a := initTestPCF8583WithStubbedAdaptor()
a.written = []byte{} // reset writes of Start() and former test
readCtrlState := uint8(0x30) // test mode
// arrange writes
a.i2cWriteImpl = func(b []byte) (int, error) {
return len(b), nil
}
// arrange reads
numCallsRead := 0
a.i2cReadImpl = func(b []byte) (int, error) {
numCallsRead++
b[len(b)-1] = readCtrlState
return len(b), nil
}
// act
got, err := d.ReadCounter()
// assert
gobottest.Refute(t, err, nil)
gobottest.Assert(t, strings.Contains(err.Error(), "wrong mode 0x30"), true)
gobottest.Assert(t, got, int32(0))
gobottest.Assert(t, len(a.written), 1)
gobottest.Assert(t, a.written[0], uint8(pcf8583Reg_CTRL))
gobottest.Assert(t, numCallsRead, 1)
}
func TestPCF8583WriteRam(t *testing.T) {
// sequence to write the RAM:
// * calculate the RAM address and check for valid range
// * write the given value to the given RAM address
// arrange
d, a := initTestPCF8583WithStubbedAdaptor()
a.written = []byte{} // reset writes of Start() and former test
wantRamAddress := uint8(0xFF)
wantRamValue := uint8(0xEF)
// arrange writes
a.i2cWriteImpl = func(b []byte) (int, error) {
return len(b), nil
}
// act
err := d.WriteRAM(wantRamAddress-pcf8583RamOffset, wantRamValue)
// assert
gobottest.Assert(t, err, nil)
gobottest.Assert(t, len(a.written), 2)
gobottest.Assert(t, a.written[0], wantRamAddress)
gobottest.Assert(t, a.written[1], wantRamValue)
}
func TestPCF8583WriteRamAddressOverflowFails(t *testing.T) {
// arrange
d, a := initTestPCF8583WithStubbedAdaptor()
a.written = []byte{} // reset writes of Start() and former test
// act
err := d.WriteRAM(uint8(0xF0), 15)
// assert
gobottest.Refute(t, err, nil)
gobottest.Assert(t, strings.Contains(err.Error(), "overflow 256"), true)
gobottest.Assert(t, len(a.written), 0)
}
func TestPCF8583ReadRam(t *testing.T) {
// sequence to read the RAM:
// * calculate the RAM address and check for valid range
// * read the value from the given RAM address
// arrange
d, a := initTestPCF8583WithStubbedAdaptor()
a.written = []byte{} // reset writes of Start() and former test
wantRamAddress := uint8(pcf8583RamOffset)
want := uint8(0xAB)
// arrange writes
a.i2cWriteImpl = func(b []byte) (int, error) {
return len(b), nil
}
// arrange reads
numCallsRead := 0
a.i2cReadImpl = func(b []byte) (int, error) {
numCallsRead++
b[len(b)-1] = want
return len(b), nil
}
// act
got, err := d.ReadRAM(wantRamAddress - pcf8583RamOffset)
// assert
gobottest.Assert(t, err, nil)
gobottest.Assert(t, got, want)
gobottest.Assert(t, len(a.written), 1)
gobottest.Assert(t, a.written[0], wantRamAddress)
gobottest.Assert(t, numCallsRead, 1)
}
func TestPCF8583ReadRamAddressOverflowFails(t *testing.T) {
// arrange
d, a := initTestPCF8583WithStubbedAdaptor()
a.written = []byte{} // reset writes of Start() and former test
// arrange writes
a.i2cWriteImpl = func(b []byte) (int, error) {
return len(b), nil
}
// arrange reads
numCallsRead := 0
a.i2cReadImpl = func(b []byte) (int, error) {
numCallsRead++
return len(b), nil
}
// act
got, err := d.ReadRAM(uint8(0xF0))
// assert
gobottest.Refute(t, err, nil)
gobottest.Assert(t, strings.Contains(err.Error(), "overflow 256"), true)
gobottest.Assert(t, got, uint8(0))
gobottest.Assert(t, len(a.written), 0)
gobottest.Assert(t, numCallsRead, 0)
}
func TestPCF8583_initializeNoModeSwitch(t *testing.T) {
// arrange
a := newI2cTestAdaptor()
d := NewPCF8583Driver(a)
a.written = []byte{} // reset writes of former tests
readCtrlState := uint8(0x01) // 32.768kHz clock mode
// arrange writes
a.i2cWriteImpl = func(b []byte) (int, error) {
return len(b), nil
}
// arrange reads
numCallsRead := 0
a.i2cReadImpl = func(b []byte) (int, error) {
numCallsRead++
b[len(b)-1] = readCtrlState
return len(b), nil
}
// act, assert - initialize() must be called on Start()
err := d.Start()
// assert
gobottest.Assert(t, err, nil)
gobottest.Assert(t, numCallsRead, 1)
gobottest.Assert(t, len(a.written), 1)
gobottest.Assert(t, a.written[0], uint8(pcf8583Reg_CTRL))
}
func TestPCF8583_initializeWithModeSwitch(t *testing.T) {
// sequence to change mode:
// * read control register for get current state
// * reset old mode bits and set new mode bit
// * write the control register
// arrange
a := newI2cTestAdaptor()
d := NewPCF8583Driver(a)
d.mode = PCF8583CtrlModeCounter
a.written = []byte{} // reset writes of former tests
readCtrlState := uint8(0x02) // 32.768kHz clock mode
wantReg0Val := uint8(0x22) // event counter mode
// arrange writes
a.i2cWriteImpl = func(b []byte) (int, error) {
return len(b), nil
}
// arrange reads
numCallsRead := 0
a.i2cReadImpl = func(b []byte) (int, error) {
numCallsRead++
b[len(b)-1] = readCtrlState
return len(b), nil
}
// act, assert - initialize() must be called on Start()
err := d.Start()
// assert
gobottest.Assert(t, err, nil)
gobottest.Assert(t, numCallsRead, 1)
gobottest.Assert(t, len(a.written), 3)
gobottest.Assert(t, a.written[0], uint8(pcf8583Reg_CTRL))
gobottest.Assert(t, a.written[1], uint8(pcf8583Reg_CTRL))
gobottest.Assert(t, a.written[2], uint8(wantReg0Val))
}

View File

@ -0,0 +1,61 @@
// +build example
//
// Do not build by default.
package main
import (
"fmt"
"log"
"time"
"gobot.io/x/gobot"
"gobot.io/x/gobot/drivers/i2c"
"gobot.io/x/gobot/platforms/tinkerboard"
)
// Wiring
// PWR Tinkerboard: 1 (+3.3V, VCC), 6, 9, 14, 20 (GND)
// I2C1 Tinkerboard: 3 (SDA), 5 (SCL)
// PCF8583 DIP package: 1 (OSCI, 50Hz), 2 (OSCO, nc), 3 (A0 - GND), 4 (VSS, +3.3V), 5 (SDA), 6 (SCL), 7 (/INT, nc), 8 (VDD, GND)
func main() {
board := tinkerboard.NewAdaptor()
pcf := i2c.NewPCF8583Driver(board, i2c.WithBus(1), i2c.WithPCF8583Mode(i2c.PCF8583CtrlModeClock50))
work := func() {
currentTime := time.Now()
log.Println(currentTime)
if err := pcf.WriteTime(currentTime); err != nil {
fmt.Println(err)
}
gobot.Every(10*time.Second, func() {
if val, err := pcf.ReadTime(); err != nil {
fmt.Println(err)
} else {
log.Printf("read Time: %v", val)
}
ramVal, err := pcf.ReadRAM(uint8(0))
if err != nil {
fmt.Println(err)
} else {
log.Printf("read RAM: %v", ramVal)
ramVal++
}
if err := pcf.WriteRAM(uint8(0), ramVal); err != nil {
fmt.Println(err)
}
})
}
robot := gobot.NewRobot("pcfBot",
[]gobot.Connection{board},
[]gobot.Device{pcf},
work,
)
robot.Start()
}

View File

@ -0,0 +1,61 @@
// +build example
//
// Do not build by default.
package main
import (
"fmt"
"log"
"time"
"gobot.io/x/gobot"
"gobot.io/x/gobot/drivers/i2c"
"gobot.io/x/gobot/platforms/tinkerboard"
)
// Wiring
// PWR Tinkerboard: 1 (+3.3V, VCC), 6, 9, 14, 20 (GND)
// I2C1 Tinkerboard: 3 (SDA), 5 (SCL)
// PCF8583 DIP package: 1 (OSCI, event), 2 (OSCO, nc), 3 (A0 - GND), 4 (VSS, +3.3V), 5 (SDA), 6 (SCL), 7 (/INT, nc), 8 (VDD, GND)
// Note: event can be created by e.g. an debounced button
func main() {
board := tinkerboard.NewAdaptor()
pcf := i2c.NewPCF8583Driver(board, i2c.WithBus(1), i2c.WithPCF8583Mode(i2c.PCF8583CtrlModeCounter))
work := func() {
lastCnt := int32(1234)
if err := pcf.WriteCounter(lastCnt); err != nil {
fmt.Println(err)
}
gobot.Every(1000*time.Millisecond, func() {
if val, err := pcf.ReadCounter(); err != nil {
fmt.Println(err)
} else {
log.Printf("read Counter: %d, diff: %d", val, val-lastCnt)
lastCnt = val
}
ramVal, err := pcf.ReadRAM(uint8(0))
if err != nil {
fmt.Println(err)
} else {
log.Printf("read RAM: %d", ramVal)
ramVal++
}
if err := pcf.WriteRAM(uint8(0), ramVal); err != nil {
fmt.Println(err)
}
})
}
robot := gobot.NewRobot("pcfBot",
[]gobot.Connection{board},
[]gobot.Device{pcf},
work,
)
robot.Start()
}