mirror of
https://github.com/hybridgroup/gobot.git
synced 2025-05-01 13:48:57 +08:00
i2c PCF8583 clock and counter driver introduced
This commit is contained in:
parent
d827c913c5
commit
9d1fd13a2f
@ -288,6 +288,7 @@ drivers provided using the `gobot/drivers/i2c` package:
|
|||||||
- MPU6050 Accelerometer/Gyroscope
|
- MPU6050 Accelerometer/Gyroscope
|
||||||
- PCA9501 8-bit I/O port with interrupt, 2-kbit EEPROM
|
- PCA9501 8-bit I/O port with interrupt, 2-kbit EEPROM
|
||||||
- PCA9685 16-channel 12-bit PWM/Servo Driver
|
- 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
|
- PCF8591 8-bit 4xA/D & 1xD/A converter
|
||||||
- SHT2x Temperature/Humidity
|
- SHT2x Temperature/Humidity
|
||||||
- SHT3x-D Temperature/Humidity
|
- SHT3x-D Temperature/Humidity
|
||||||
|
@ -40,6 +40,7 @@ Gobot has a extensible system for connecting to hardware devices. The following
|
|||||||
- MPU6050 Accelerometer/Gyroscope
|
- MPU6050 Accelerometer/Gyroscope
|
||||||
- PCA9501 8-bit I/O port with interrupt, 2-kbit EEPROM
|
- PCA9501 8-bit I/O port with interrupt, 2-kbit EEPROM
|
||||||
- PCA9685 16-channel 12-bit PWM/Servo Driver
|
- 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
|
- PCF8591 8-bit 4xA/D & 1xD/A converter
|
||||||
- SHT2x Temperature/Humidity
|
- SHT2x Temperature/Humidity
|
||||||
- SHT3x-D Temperature/Humidity
|
- SHT3x-D Temperature/Humidity
|
||||||
|
360
drivers/i2c/pcf8583_driver.go
Normal file
360
drivers/i2c/pcf8583_driver.go
Normal 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
|
||||||
|
}
|
612
drivers/i2c/pcf8583_driver_test.go
Normal file
612
drivers/i2c/pcf8583_driver_test.go
Normal 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))
|
||||||
|
}
|
61
examples/tinkerboard_pcf8583_clock.go
Normal file
61
examples/tinkerboard_pcf8583_clock.go
Normal 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()
|
||||||
|
}
|
61
examples/tinkerboard_pcf8583_counter.go
Normal file
61
examples/tinkerboard_pcf8583_counter.go
Normal 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()
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user