hybridgroup.gobot/drivers/i2c/pca9501_driver.go

166 lines
4.9 KiB
Go

package i2c
import "gobot.io/x/gobot/v2/drivers/common/bit"
// PCA9501 supports addresses from 0x00 to 0x7F
// 0x00 - 0x3F: GPIO, 0x40 - 0x7F: EEPROM
//
// 0 EE A5 A4 A3 A2 A1 A0|rd
// Lowest bit (rd) is mapped to switch between write(0)/read(1), it is not part of the "real" address.
// Highest bit (EE) is mapped to switch between GPIO(0)/EEPROM(1).
//
// The EEPROM address will be generated from GPIO address in this driver.
const pca9501DefaultAddress = 0x3F // this applies, if all 6 address pins left open (have pull up resistors)
// PCA9501Driver is a Gobot Driver for the PCA9501 8-bit GPIO & 2-kbit EEPROM with 6 address program pins.
// 2-kbit EEPROM has 256 byte, means addresses between 0x00-0xFF
//
// please refer to data sheet: https://www.nxp.com/docs/en/data-sheet/PCA9501.pdf
//
// PCA9501 is the replacement for PCF8574, so this driver should also work for PCF8574 except EEPROM calls
type PCA9501Driver struct {
connectionMem Connection
*Driver
}
// NewPCA9501Driver creates a new driver with specified i2c interface
// Params:
//
// a 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
func NewPCA9501Driver(a Connector, options ...func(Config)) *PCA9501Driver {
p := &PCA9501Driver{
Driver: NewDriver(a, "PCA9501", pca9501DefaultAddress, options...),
}
p.afterStart = p.initialize
// API commands
//nolint:forcetypeassert // ok here
p.AddCommand("WriteGPIO", func(params map[string]interface{}) interface{} {
pin := params["pin"].(uint8)
val := params["val"].(uint8)
err := p.WriteGPIO(pin, val)
return map[string]interface{}{"err": err}
})
//nolint:forcetypeassert // ok here
p.AddCommand("ReadGPIO", func(params map[string]interface{}) interface{} {
pin := params["pin"].(uint8)
val, err := p.ReadGPIO(pin)
return map[string]interface{}{"val": val, "err": err}
})
//nolint:forcetypeassert // ok here
p.AddCommand("WriteEEPROM", func(params map[string]interface{}) interface{} {
address := params["address"].(uint8)
val := params["val"].(uint8)
err := p.WriteEEPROM(address, val)
return map[string]interface{}{"err": err}
})
//nolint:forcetypeassert // ok here
p.AddCommand("ReadEEPROM", func(params map[string]interface{}) interface{} {
address := params["address"].(uint8)
val, err := p.ReadEEPROM(address)
return map[string]interface{}{"val": val, "err": err}
})
return p
}
// WriteGPIO writes a value to a gpio pin (0-7)
func (p *PCA9501Driver) WriteGPIO(pin uint8, val uint8) error {
p.mutex.Lock()
defer p.mutex.Unlock()
// read current value of CTRL register, 0 is output, 1 is no output
iodir, err := p.connection.ReadByte()
if err != nil {
return err
}
// set pin as output by clearing bit
iodirVal := bit.Clear(int(iodir), pin)
// write CTRL register
err = p.connection.WriteByte(uint8(iodirVal)) //nolint:gosec // TODO: fix later
if err != nil {
return err
}
// read current value of port
cVal, err := p.connection.ReadByte()
if err != nil {
return err
}
// set or reset the bit in value
var nVal int
if val == 0 {
nVal = bit.Clear(int(cVal), pin)
} else {
nVal = bit.Set(int(cVal), pin)
}
// write new value to port
err = p.connection.WriteByte(uint8(nVal)) //nolint:gosec // TODO: fix later
if err != nil {
return err
}
return nil
}
// ReadGPIO reads a value from a given gpio pin (0-7)
func (p *PCA9501Driver) ReadGPIO(pin uint8) (uint8, error) {
p.mutex.Lock()
defer p.mutex.Unlock()
// read current value of CTRL register, 0 is no input, 1 is an input
iodir, err := p.connection.ReadByte()
if err != nil {
return 0, err
}
// set pin as input by setting bit
iodirVal := bit.Set(int(iodir), pin)
// write CTRL register
err = p.connection.WriteByte(uint8(iodirVal)) //nolint:gosec // TODO: fix later
if err != nil {
return 0, err
}
// read port and create return bit
val, err := p.connection.ReadByte()
if err != nil {
return val, err
}
val = 1 << pin & val
if val > 1 {
val = 1
}
return val, nil
}
// ReadEEPROM reads a value from a given address (0x00-0xFF)
// Note: only this sequence for memory read is supported: "STARTW-DATA1-STARTR-DATA2-STOP"
// DATA1: EEPROM address, DATA2: read value
func (p *PCA9501Driver) ReadEEPROM(address uint8) (uint8, error) {
p.mutex.Lock()
defer p.mutex.Unlock()
return p.connectionMem.ReadByteData(address)
}
// WriteEEPROM writes a value to a given address in memory (0x00-0xFF)
func (p *PCA9501Driver) WriteEEPROM(address uint8, val uint8) error {
p.mutex.Lock()
defer p.mutex.Unlock()
return p.connectionMem.WriteByteData(address, val)
}
func (p *PCA9501Driver) initialize() error {
// initialize the EEPROM connection
bus := p.GetBusOrDefault(p.connector.DefaultI2cBus())
addressMem := p.GetAddressOrDefault(pca9501DefaultAddress) | 0x40
var err error
p.connectionMem, err = p.connector.GetI2cConnection(addressMem, bus)
return err
}