gobot.io/x/gobot/v2@v2.1.0/drivers/i2c/pcf8583_driver.go (about)

     1  package i2c
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"time"
     7  )
     8  
     9  const (
    10  	pcf8583Debug = false
    11  
    12  	// PCF8583 supports addresses 0x50 and 0x51
    13  	// The default address applies when the address pin is grounded.
    14  	pcf8583DefaultAddress = 0x50
    15  
    16  	// default is 0x10, when set to 0 also some free or unused RAM can be accessed
    17  	pcf8583RamOffset = 0x10
    18  )
    19  
    20  // PCF8583Control is used to specify control and status register content
    21  type PCF8583Control uint8
    22  
    23  const (
    24  	// registers are named according to the datasheet
    25  	pcf8583Reg_CTRL         = iota // 0x00
    26  	pcf8583Reg_SUBSEC_D0D1         // 0x01
    27  	pcf8583Reg_SEC_D2D3            // 0x02
    28  	pcf8583Reg_MIN_D4D5            // 0x03
    29  	pcf8583Reg_HOUR                // 0x04
    30  	pcf8583Reg_YEARDATE            // 0x05
    31  	pcf8583Reg_WEEKDAYMONTH        // 0x06
    32  	pcf8583Reg_TIMER               // 0x07
    33  	pcf8583Reg_ALARMCTRL           // 0x08, offset for all alarm registers 0x09 ... 0xF
    34  
    35  	pcf8583CtrlTimerFlag     PCF8583Control = 0x01 // 50% duty factor, seconds flag if alarm enable bit is 0
    36  	pcf8583CtrlAlarmFlag     PCF8583Control = 0x02 // 50% duty factor, minutes flag if alarm enable bit is 0
    37  	pcf8583CtrlAlarmEnable   PCF8583Control = 0x04 // if enabled, memory 08h is alarm control register
    38  	pcf8583CtrlMask          PCF8583Control = 0x08 // 0: read 05h, 06h unmasked, 1: read date and month count directly
    39  	PCF8583CtrlModeClock50   PCF8583Control = 0x10 // clock mode with 50 Hz
    40  	PCF8583CtrlModeCounter   PCF8583Control = 0x20 // event counter mode
    41  	PCF8583CtrlModeTest      PCF8583Control = 0x30 // test mode
    42  	pcf8583CtrlHoldLastCount PCF8583Control = 0x40 // 0: count, 1: store and hold count in capture latches
    43  	pcf8583CtrlStopCounting  PCF8583Control = 0x80 // 0: count, 1: stop counting, reset divider
    44  )
    45  
    46  // PCF8583Driver is a Gobot Driver for the PCF8583 clock and calendar chip & 240 x 8-bit bit RAM with 1 address program pin.
    47  // please refer to data sheet: https://www.nxp.com/docs/en/data-sheet/PCF8583.pdf
    48  //
    49  // 0 1 0 1 0 0 0 A0|rd
    50  // Lowest bit (rd) is mapped to switch between write(0)/read(1), it is not part of the "real" address.
    51  //
    52  // PCF8583 is mainly compatible to PCF8593, so this driver should also work for PCF8593 except RAM calls
    53  //
    54  // This driver was tested with Tinkerboard.
    55  type PCF8583Driver struct {
    56  	*Driver
    57  	mode       PCF8583Control // clock 32.768kHz (default), clock 50Hz, event counter
    58  	yearOffset int
    59  	ramOffset  byte
    60  }
    61  
    62  // NewPCF8583Driver creates a new driver with specified i2c interface
    63  // Params:
    64  //    c Connector - the Adaptor to use with this Driver
    65  //
    66  // Optional params:
    67  //    i2c.WithBus(int):	bus to use with this driver
    68  //    i2c.WithAddress(int):	address to use with this driver
    69  //    i2c.WithPCF8583Mode(PCF8583Control): mode of this driver
    70  //
    71  func NewPCF8583Driver(c Connector, options ...func(Config)) *PCF8583Driver {
    72  	d := &PCF8583Driver{
    73  		Driver:    NewDriver(c, "PCF8583", pcf8583DefaultAddress),
    74  		ramOffset: pcf8583RamOffset,
    75  	}
    76  	d.afterStart = d.initialize
    77  
    78  	for _, option := range options {
    79  		option(d)
    80  	}
    81  
    82  	// API commands
    83  	d.AddCommand("WriteTime", func(params map[string]interface{}) interface{} {
    84  		val := params["val"].(time.Time)
    85  		err := d.WriteTime(val)
    86  		return map[string]interface{}{"err": err}
    87  	})
    88  
    89  	d.AddCommand("ReadTime", func(params map[string]interface{}) interface{} {
    90  		val, err := d.ReadTime()
    91  		return map[string]interface{}{"val": val, "err": err}
    92  	})
    93  
    94  	d.AddCommand("WriteCounter", func(params map[string]interface{}) interface{} {
    95  		val := params["val"].(int32)
    96  		err := d.WriteCounter(val)
    97  		return map[string]interface{}{"err": err}
    98  	})
    99  
   100  	d.AddCommand("ReadCounter", func(params map[string]interface{}) interface{} {
   101  		val, err := d.ReadCounter()
   102  		return map[string]interface{}{"val": val, "err": err}
   103  	})
   104  
   105  	d.AddCommand("WriteRAM", func(params map[string]interface{}) interface{} {
   106  		address := params["address"].(uint8)
   107  		val := params["val"].(uint8)
   108  		err := d.WriteRAM(address, val)
   109  		return map[string]interface{}{"err": err}
   110  	})
   111  
   112  	d.AddCommand("ReadRAM", func(params map[string]interface{}) interface{} {
   113  		address := params["address"].(uint8)
   114  		val, err := d.ReadRAM(address)
   115  		return map[string]interface{}{"val": val, "err": err}
   116  	})
   117  	return d
   118  }
   119  
   120  // WithPCF8583Mode is used to change the mode between 32.678kHz clock, 50Hz clock, event counter
   121  // Valid settings are of type "PCF8583Control"
   122  func WithPCF8583Mode(mode PCF8583Control) func(Config) {
   123  	return func(c Config) {
   124  		d, ok := c.(*PCF8583Driver)
   125  		if ok {
   126  			if !mode.isClockMode() && !mode.isCounterMode() {
   127  				panic(fmt.Sprintf("%s: mode 0x%02x is not supported", d.name, mode))
   128  			}
   129  			d.mode = mode
   130  		} else if pcf8583Debug {
   131  			log.Printf("trying to set mode for non-PCF8583Driver %v", c)
   132  		}
   133  	}
   134  }
   135  
   136  // WriteTime setup the clock registers with the given time
   137  func (d *PCF8583Driver) WriteTime(val time.Time) error {
   138  	d.mutex.Lock()
   139  	defer d.mutex.Unlock()
   140  
   141  	// according to chapter 7.11 of the product data sheet, the stop counting flag of the control/status register
   142  	// must be set before, so we read the control byte before and only set/reset the stop
   143  	ctrlRegVal, err := d.connection.ReadByteData(uint8(pcf8583Reg_CTRL))
   144  	if err != nil {
   145  		return err
   146  	}
   147  	if !PCF8583Control(ctrlRegVal).isClockMode() {
   148  		return fmt.Errorf("%s: can't write time because the device is in wrong mode 0x%02x", d.name, ctrlRegVal)
   149  	}
   150  	year, month, day := val.Date()
   151  	err = d.connection.WriteBlockData(uint8(pcf8583Reg_CTRL),
   152  		[]byte{ctrlRegVal | uint8(pcf8583CtrlStopCounting),
   153  			pcf8583encodeBcd(uint8(val.Nanosecond() / 1000000 / 10)), // sub seconds in 1/10th seconds
   154  			pcf8583encodeBcd(uint8(val.Second())),
   155  			pcf8583encodeBcd(uint8(val.Minute())),
   156  			pcf8583encodeBcd(uint8(val.Hour())),
   157  			pcf8583encodeBcd(uint8(day)),                             // year, date (we keep the year counter zero and set the offset)
   158  			uint8(val.Weekday())<<5 | pcf8583encodeBcd(uint8(month)), // month, weekday (not BCD): Sunday = 0, Monday = 1 ...
   159  		})
   160  	if err != nil {
   161  		return err
   162  	}
   163  	d.yearOffset = year
   164  	return d.run(ctrlRegVal)
   165  }
   166  
   167  // ReadTime reads the clock and returns the value
   168  func (d *PCF8583Driver) ReadTime() (val time.Time, err error) {
   169  	d.mutex.Lock()
   170  	defer d.mutex.Unlock()
   171  
   172  	// according to chapter 7.1 of the product data sheet, the setting of "hold last count" flag
   173  	// is not needed when reading with auto increment
   174  	ctrlRegVal, err := d.connection.ReadByteData(uint8(pcf8583Reg_CTRL))
   175  	if err != nil {
   176  		return
   177  	}
   178  	if !PCF8583Control(ctrlRegVal).isClockMode() {
   179  		return val, fmt.Errorf("%s: can't read time because the device is in wrong mode 0x%02x", d.name, ctrlRegVal)
   180  	}
   181  	// auto increment feature is used
   182  	clockDataSize := 6
   183  	data := make([]byte, clockDataSize)
   184  	read, err := d.connection.Read(data)
   185  	if err != nil {
   186  		return
   187  	}
   188  	if read != clockDataSize {
   189  		return val, fmt.Errorf("%s: %d bytes read, but %d expected", d.name, read, clockDataSize)
   190  	}
   191  	nanos := int(pcf8583decodeBcd(data[0])) * 1000000 * 10 // sub seconds in 1/10th seconds
   192  	seconds := int(pcf8583decodeBcd(data[1]))
   193  	minutes := int(pcf8583decodeBcd(data[2]))
   194  	hours := int(pcf8583decodeBcd(data[3]))
   195  	// year, date (the device can only count 4 years)
   196  	year := int(data[4]>>6) + d.yearOffset        // use the first two bits, no BCD
   197  	date := int(pcf8583decodeBcd(data[4] & 0x3F)) // remove the year-bits for date
   198  	// weekday (not used here), month
   199  	month := time.Month(pcf8583decodeBcd(data[5] & 0x1F)) // remove the weekday-bits
   200  	return time.Date(year, month, date, hours, minutes, seconds, nanos, time.UTC), nil
   201  }
   202  
   203  // WriteCounter writes the counter registers
   204  func (d *PCF8583Driver) WriteCounter(val int32) error {
   205  	d.mutex.Lock()
   206  	defer d.mutex.Unlock()
   207  
   208  	// we don't care of negative values here
   209  	// according to chapter 7.11 of the product data sheet, the stop counting flag of the control/status register
   210  	// must be set before, so we read the control byte before and only set/reset the stop
   211  	ctrlRegVal, err := d.connection.ReadByteData(uint8(pcf8583Reg_CTRL))
   212  	if err != nil {
   213  		return err
   214  	}
   215  	if !PCF8583Control(ctrlRegVal).isCounterMode() {
   216  		return fmt.Errorf("%s: can't write counter because the device is in wrong mode 0x%02x", d.name, ctrlRegVal)
   217  	}
   218  	err = d.connection.WriteBlockData(uint8(pcf8583Reg_CTRL),
   219  		[]byte{ctrlRegVal | uint8(pcf8583CtrlStopCounting), // stop
   220  			pcf8583encodeBcd(uint8(val % 100)),           // 2 lowest digits
   221  			pcf8583encodeBcd(uint8((val / 100) % 100)),   // 2 middle digits
   222  			pcf8583encodeBcd(uint8((val / 10000) % 100)), // 2 highest digits
   223  		})
   224  	if err != nil {
   225  		return err
   226  	}
   227  	return d.run(ctrlRegVal)
   228  }
   229  
   230  // ReadCounter reads the counter registers
   231  func (d *PCF8583Driver) ReadCounter() (val int32, err error) {
   232  	d.mutex.Lock()
   233  	defer d.mutex.Unlock()
   234  
   235  	// according to chapter 7.1 of the product data sheet, the setting of "hold last count" flag
   236  	// is not needed when reading with auto increment
   237  	ctrlRegVal, err := d.connection.ReadByteData(uint8(pcf8583Reg_CTRL))
   238  	if err != nil {
   239  		return
   240  	}
   241  	if !PCF8583Control(ctrlRegVal).isCounterMode() {
   242  		return val, fmt.Errorf("%s: can't read counter because the device is in wrong mode 0x%02x", d.name, ctrlRegVal)
   243  	}
   244  	// auto increment feature is used
   245  	counterDataSize := 3
   246  	data := make([]byte, counterDataSize)
   247  	read, err := d.connection.Read(data)
   248  	if err != nil {
   249  		return
   250  	}
   251  	if read != counterDataSize {
   252  		return val, fmt.Errorf("%s: %d bytes read, but %d expected", d.name, read, counterDataSize)
   253  	}
   254  	return int32(pcf8583decodeBcd(data[0])) +
   255  		int32(pcf8583decodeBcd(data[1]))*100 +
   256  		int32(pcf8583decodeBcd(data[2]))*10000, nil
   257  }
   258  
   259  // WriteRAM writes a value to a given address in memory (0x00-0xFF)
   260  func (d *PCF8583Driver) WriteRAM(address uint8, val uint8) error {
   261  	d.mutex.Lock()
   262  	defer d.mutex.Unlock()
   263  
   264  	realAddress := uint16(address) + uint16(d.ramOffset)
   265  	if realAddress > 0xFF {
   266  		return fmt.Errorf("%s: RAM address overflow %d", d.name, realAddress)
   267  	}
   268  	return d.connection.WriteByteData(uint8(realAddress), val)
   269  }
   270  
   271  // ReadRAM reads a value from a given address (0x00-0xFF)
   272  func (d *PCF8583Driver) ReadRAM(address uint8) (val uint8, err error) {
   273  	d.mutex.Lock()
   274  	defer d.mutex.Unlock()
   275  
   276  	realAddress := uint16(address) + uint16(d.ramOffset)
   277  	if realAddress > 0xFF {
   278  		return val, fmt.Errorf("%s: RAM address overflow %d", d.name, realAddress)
   279  	}
   280  	return d.connection.ReadByteData(uint8(realAddress))
   281  }
   282  
   283  func (d *PCF8583Driver) run(ctrlRegVal uint8) error {
   284  	ctrlRegVal = ctrlRegVal & ^uint8(pcf8583CtrlStopCounting) // reset stop bit
   285  	return d.connection.WriteByteData(uint8(pcf8583Reg_CTRL), ctrlRegVal)
   286  }
   287  
   288  func (d *PCF8583Driver) initialize() error {
   289  	// switch to configured mode
   290  	ctrlRegVal, err := d.connection.ReadByteData(uint8(pcf8583Reg_CTRL))
   291  	if err != nil {
   292  		return err
   293  	}
   294  	if d.mode.isModeDiffer(PCF8583Control(ctrlRegVal)) {
   295  		ctrlRegVal = ctrlRegVal&^uint8(PCF8583CtrlModeTest) | uint8(d.mode)
   296  		if err = d.connection.WriteByteData(uint8(pcf8583Reg_CTRL), ctrlRegVal); err != nil {
   297  			return err
   298  		}
   299  		if pcf8583Debug {
   300  			if PCF8583Control(ctrlRegVal).isCounterMode() {
   301  				log.Printf("%s switched to counter mode 0x%02x", d.name, ctrlRegVal)
   302  			} else {
   303  				log.Printf("%s switched to clock mode 0x%02x", d.name, ctrlRegVal)
   304  			}
   305  		}
   306  	}
   307  	return nil
   308  }
   309  
   310  func (c PCF8583Control) isClockMode() bool {
   311  	return uint8(c)&uint8(PCF8583CtrlModeCounter) == 0
   312  }
   313  
   314  func (c PCF8583Control) isCounterMode() bool {
   315  	counterModeSet := (uint8(c) & uint8(PCF8583CtrlModeCounter)) != 0
   316  	clockMode50Set := (uint8(c) & uint8(PCF8583CtrlModeClock50)) != 0
   317  	return counterModeSet && !clockMode50Set
   318  }
   319  
   320  func (c PCF8583Control) isModeDiffer(mode PCF8583Control) bool {
   321  	return uint8(c)&uint8(PCF8583CtrlModeTest) != uint8(mode)&uint8(PCF8583CtrlModeTest)
   322  }
   323  
   324  func pcf8583encodeBcd(val byte) byte {
   325  	// decimal 12 => 0x12
   326  	if val > 99 {
   327  		val = 99
   328  		if pcf8583Debug {
   329  			log.Printf("PCF8583 BCD value (%d) exceeds limit of 99, now limited.", val)
   330  		}
   331  	}
   332  	hi, lo := byte(val/10), byte(val%10)
   333  	return hi<<4 | lo
   334  }
   335  
   336  func pcf8583decodeBcd(bcd byte) byte {
   337  	// 0x12 => decimal 12
   338  	hi, lo := byte(bcd>>4), byte(bcd&0x0f)
   339  	if hi > 9 {
   340  		hi = 9
   341  		if pcf8583Debug {
   342  			log.Printf("PCF8583 BCD value (%02x) exceeds limit 0x99 on most significant digit, now limited", bcd)
   343  		}
   344  	}
   345  	if lo > 9 {
   346  		lo = 9
   347  		if pcf8583Debug {
   348  			log.Printf("PCF8583 BCD value (%02x) exceeds limit 0x99 on least significant digit, now limited", bcd)
   349  		}
   350  	}
   351  	return 10*hi + lo
   352  }