gobot.io/x/gobot@v1.16.0/drivers/i2c/mcp23017_driver.go (about)

     1  package i2c
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"strings"
     7  	"sync"
     8  
     9  	"gobot.io/x/gobot"
    10  )
    11  
    12  // default address for device when a2/a1/a0 pins are all tied to ground
    13  // please consider special handling for MCP23S17
    14  const mcp23017Address = 0x20
    15  
    16  const mcp23017Debug = false // toggle debugging information
    17  
    18  // port contains all the registers for the device.
    19  type port struct {
    20  	IODIR   uint8 // I/O direction register: 0=output / 1=input
    21  	IPOL    uint8 // input polarity register: 0=normal polarity / 1=inversed
    22  	GPINTEN uint8 // interrupt on change control register: 0=disabled / 1=enabled
    23  	DEFVAL  uint8 // default compare register for interrupt on change
    24  	INTCON  uint8 // interrupt control register: bit set to 0= use defval bit value to compare pin value/ bit set to 1= pin value compared to previous pin value
    25  	IOCON   uint8 // configuration register
    26  	GPPU    uint8 // pull-up resistor configuration register: 0=enabled / 1=disabled
    27  	INTF    uint8 // interrupt flag register: 0=no interrupt / 1=pin caused interrupt
    28  	INTCAP  uint8 // interrupt capture register, captures pin values during interrupt: 0=logic low / 1=logic high
    29  	GPIO    uint8 // port register, reading from this register reads the port
    30  	OLAT    uint8 // output latch register, write modifies the pins: 0=logic low / 1=logic high
    31  }
    32  
    33  // A bank is made up of PortA and PortB pins.
    34  // Port B pins are on the left side of the chip (starting with pin 1), while port A pins are on the right side.
    35  type bank struct {
    36  	portA port
    37  	portB port
    38  }
    39  
    40  // MCP23017Config contains the device configuration for the IOCON register.
    41  // These fields should only be set with values 0 or 1.
    42  type MCP23017Config struct {
    43  	bank   uint8
    44  	mirror uint8
    45  	seqop  uint8
    46  	disslw uint8
    47  	haen   uint8
    48  	odr    uint8
    49  	intpol uint8
    50  }
    51  
    52  type MCP23017Behavior struct {
    53  	forceRefresh bool
    54  	autoIODirOff bool
    55  }
    56  
    57  // MCP23017Driver contains the driver configuration parameters.
    58  type MCP23017Driver struct {
    59  	name       string
    60  	connector  Connector
    61  	connection Connection
    62  	Config
    63  	mcpConf  MCP23017Config
    64  	mcpBehav MCP23017Behavior
    65  	gobot.Commander
    66  	gobot.Eventer
    67  	// mutex needed because following sequences must not be interrupted:
    68  	// read-write-read-write in WriteGPIO()
    69  	// read-write-read in ReadGPIO()
    70  	// read-write in all other public methods using write()
    71  	mutex *sync.Mutex
    72  }
    73  
    74  // WithMCP23017Bank option sets the MCP23017Driver bank option
    75  func WithMCP23017Bank(val uint8) func(Config) {
    76  	return func(c Config) {
    77  		d, ok := c.(*MCP23017Driver)
    78  		if ok {
    79  			d.mcpConf.bank = val
    80  		} else if mcp23017Debug {
    81  			log.Printf("trying to set bank for non-MCP23017Driver %v", c)
    82  		}
    83  	}
    84  }
    85  
    86  // WithMCP23017Mirror option sets the MCP23017Driver mirror option
    87  func WithMCP23017Mirror(val uint8) func(Config) {
    88  	return func(c Config) {
    89  		d, ok := c.(*MCP23017Driver)
    90  		if ok {
    91  			d.mcpConf.mirror = val
    92  		} else if mcp23017Debug {
    93  			log.Printf("Trying to set mirror for non-MCP23017Driver %v", c)
    94  		}
    95  	}
    96  }
    97  
    98  // WithMCP23017Seqop option sets the MCP23017Driver seqop option
    99  func WithMCP23017Seqop(val uint8) func(Config) {
   100  	return func(c Config) {
   101  		d, ok := c.(*MCP23017Driver)
   102  		if ok {
   103  			d.mcpConf.seqop = val
   104  		} else if mcp23017Debug {
   105  			log.Printf("Trying to set seqop for non-MCP23017Driver %v", c)
   106  		}
   107  	}
   108  }
   109  
   110  // WithMCP23017Disslw option sets the MCP23017Driver disslw option
   111  func WithMCP23017Disslw(val uint8) func(Config) {
   112  	return func(c Config) {
   113  		d, ok := c.(*MCP23017Driver)
   114  		if ok {
   115  			d.mcpConf.disslw = val
   116  		} else if mcp23017Debug {
   117  			log.Printf("Trying to set disslw for non-MCP23017Driver %v", c)
   118  		}
   119  	}
   120  }
   121  
   122  // WithMCP23017Haen option sets the MCP23017Driver haen option
   123  // This feature is only available for MCP23S17, because address pins are always enabled on the MCP23017.
   124  func WithMCP23017Haen(val uint8) func(Config) {
   125  	return func(c Config) {
   126  		d, ok := c.(*MCP23017Driver)
   127  		if ok {
   128  			d.mcpConf.haen = val
   129  		} else if mcp23017Debug {
   130  			log.Printf("Trying to set haen for non-MCP23017Driver %v", c)
   131  		}
   132  	}
   133  }
   134  
   135  // WithMCP23017Odr option sets the MCP23017Driver odr option
   136  func WithMCP23017Odr(val uint8) func(Config) {
   137  	return func(c Config) {
   138  		d, ok := c.(*MCP23017Driver)
   139  		if ok {
   140  			d.mcpConf.odr = val
   141  		} else if mcp23017Debug {
   142  			log.Printf("Trying to set odr for non-MCP23017Driver %v", c)
   143  		}
   144  	}
   145  }
   146  
   147  // WithMCP23017Intpol option sets the MCP23017Driver intpol option
   148  func WithMCP23017Intpol(val uint8) func(Config) {
   149  	return func(c Config) {
   150  		d, ok := c.(*MCP23017Driver)
   151  		if ok {
   152  			d.mcpConf.intpol = val
   153  		} else if mcp23017Debug {
   154  			log.Printf("Trying to set intpol for non-MCP23017Driver %v", c)
   155  		}
   156  	}
   157  }
   158  
   159  // WithMCP23017ForceWrite option modifies the MCP23017Driver forceRefresh option
   160  // Setting to true (1) will force refresh operation to register, although there is no change.
   161  // Normally this is not needed, so default is off (0).
   162  // When there is something flaky, there is a small chance to stabilize by setting this flag to true.
   163  // However, setting this flag to true slows down each IO operation up to 100%.
   164  func WithMCP23017ForceRefresh(val uint8) func(Config) {
   165  	return func(c Config) {
   166  		d, ok := c.(*MCP23017Driver)
   167  		if ok {
   168  			d.mcpBehav.forceRefresh = val > 0
   169  		} else if mcp23017Debug {
   170  			log.Printf("Trying to set forceRefresh for non-MCP23017Driver %v", c)
   171  		}
   172  	}
   173  }
   174  
   175  // WithMCP23017AutoIODirOff option modifies the MCP23017Driver autoIODirOff option
   176  // Set IO direction at each read or write operation ensures the correct direction, which is the the default setting.
   177  // Most hardware is configured statically, so this can avoided by setting the direction using PinMode(),
   178  // e.g. in the start up sequence. If this way is taken, the automatic set of direction at each call can
   179  // be safely deactivated with this flag (set to true, 1).
   180  // This will speedup each WriteGPIO by 50% and each ReadGPIO by 60%.
   181  func WithMCP23017AutoIODirOff(val uint8) func(Config) {
   182  	return func(c Config) {
   183  		d, ok := c.(*MCP23017Driver)
   184  		if ok {
   185  			d.mcpBehav.autoIODirOff = val > 0
   186  		} else if mcp23017Debug {
   187  			log.Printf("Trying to set autoIODirOff for non-MCP23017Driver %v", c)
   188  		}
   189  	}
   190  }
   191  
   192  // NewMCP23017Driver creates a new Gobot Driver to the MCP23017 i2c port expander.
   193  // Params:
   194  //		conn Connector - the Adaptor to use with this Driver
   195  //
   196  // Optional params:
   197  //		i2c.WithBus(int):	bus to use with this driver
   198  //		i2c.WithAddress(int):	address to use with this driver
   199  //		i2c.WithMCP23017Bank(int):	MCP23017 bank to use with this driver
   200  //		i2c.WithMCP23017Mirror(int):	MCP23017 mirror to use with this driver
   201  //		i2c.WithMCP23017Seqop(int):	MCP23017 seqop to use with this driver
   202  //		i2c.WithMCP23017Disslw(int):	MCP23017 disslw to use with this driver
   203  //		i2c.WithMCP23017Haen(int):	MCP23017 haen to use with this driver
   204  //		i2c.WithMCP23017Odr(int):	MCP23017 odr to use with this driver
   205  //		i2c.WithMCP23017Intpol(int):	MCP23017 intpol to use with this driver
   206  //
   207  func NewMCP23017Driver(a Connector, options ...func(Config)) *MCP23017Driver {
   208  	m := &MCP23017Driver{
   209  		name:      gobot.DefaultName("MCP23017"),
   210  		connector: a,
   211  		Config:    NewConfig(),
   212  		mcpConf:   MCP23017Config{},
   213  		Commander: gobot.NewCommander(),
   214  		Eventer:   gobot.NewEventer(),
   215  		mutex:     &sync.Mutex{},
   216  	}
   217  
   218  	for _, option := range options {
   219  		option(m)
   220  	}
   221  
   222  	m.AddCommand("WriteGPIO", func(params map[string]interface{}) interface{} {
   223  		pin := params["pin"].(uint8)
   224  		val := params["val"].(uint8)
   225  		port := params["port"].(string)
   226  		err := m.WriteGPIO(pin, val, port)
   227  		return map[string]interface{}{"err": err}
   228  	})
   229  
   230  	m.AddCommand("ReadGPIO", func(params map[string]interface{}) interface{} {
   231  		pin := params["pin"].(uint8)
   232  		port := params["port"].(string)
   233  		val, err := m.ReadGPIO(pin, port)
   234  		return map[string]interface{}{"val": val, "err": err}
   235  	})
   236  
   237  	return m
   238  }
   239  
   240  // Name return the driver name.
   241  func (m *MCP23017Driver) Name() string { return m.name }
   242  
   243  // SetName set the driver name.
   244  func (m *MCP23017Driver) SetName(n string) { m.name = n }
   245  
   246  // Connection returns the i2c connection.
   247  func (m *MCP23017Driver) Connection() gobot.Connection { return m.connector.(gobot.Connection) }
   248  
   249  // Halt stops the driver.
   250  func (m *MCP23017Driver) Halt() (err error) { return }
   251  
   252  // Start writes the device configuration.
   253  func (m *MCP23017Driver) Start() (err error) {
   254  	bus := m.GetBusOrDefault(m.connector.GetDefaultBus())
   255  	address := m.GetAddressOrDefault(mcp23017Address)
   256  
   257  	m.connection, err = m.connector.GetConnection(address, bus)
   258  	if err != nil {
   259  		return err
   260  	}
   261  	// Set IOCON register with MCP23017 configuration.
   262  	ioconReg := m.getPort("A").IOCON // IOCON address is the same for Port A or B.
   263  	ioconVal := m.mcpConf.getUint8Value()
   264  	if _, err := m.connection.Write([]uint8{ioconReg, ioconVal}); err != nil {
   265  		return err
   266  	}
   267  	return
   268  }
   269  
   270  // PinMode set pin mode of a given pin based on the value:
   271  // val = 0 output
   272  // val = 1 input
   273  func (m *MCP23017Driver) PinMode(pin, val uint8, portStr string) (err error) {
   274  	m.mutex.Lock()
   275  	defer m.mutex.Unlock()
   276  
   277  	selectedPort := m.getPort(portStr)
   278  	// Set IODIR register bit for given pin to an output/input.
   279  	if err = m.write(selectedPort.IODIR, uint8(pin), bitState(val)); err != nil {
   280  		return
   281  	}
   282  	return
   283  }
   284  
   285  // WriteGPIO writes a value to a gpio pin (0-7) and a port (A or B).
   286  func (m *MCP23017Driver) WriteGPIO(pin uint8, val uint8, portStr string) (err error) {
   287  	m.mutex.Lock()
   288  	defer m.mutex.Unlock()
   289  
   290  	selectedPort := m.getPort(portStr)
   291  	if !m.mcpBehav.autoIODirOff {
   292  		// Set IODIR register bit for given pin to an output by clearing bit.
   293  		// can't call PinMode() because mutex will cause deadlock
   294  		if err = m.write(selectedPort.IODIR, uint8(pin), clear); err != nil {
   295  			return err
   296  		}
   297  	}
   298  	// write value to OLAT register bit
   299  	err = m.write(selectedPort.OLAT, pin, bitState(val))
   300  	if err != nil {
   301  		return err
   302  	}
   303  	return nil
   304  }
   305  
   306  // ReadGPIO reads a value from a given gpio pin (0-7) and a port (A or B).
   307  func (m *MCP23017Driver) ReadGPIO(pin uint8, portStr string) (val uint8, err error) {
   308  	m.mutex.Lock()
   309  	defer m.mutex.Unlock()
   310  
   311  	selectedPort := m.getPort(portStr)
   312  	if !m.mcpBehav.autoIODirOff {
   313  		// Set IODIR register bit for given pin to an input by set bit.
   314  		// can't call PinMode() because mutex will cause deadlock
   315  		if err = m.write(selectedPort.IODIR, uint8(pin), set); err != nil {
   316  			return 0, err
   317  		}
   318  	}
   319  	val, err = m.read(selectedPort.GPIO)
   320  	if err != nil {
   321  		return val, err
   322  	}
   323  	val = 1 << uint8(pin) & val
   324  	if val > 1 {
   325  		val = 1
   326  	}
   327  	return val, nil
   328  }
   329  
   330  // SetPullUp sets the pull up state of a given pin based on the value:
   331  // val = 1 pull up enabled.
   332  // val = 0 pull up disabled.
   333  func (m *MCP23017Driver) SetPullUp(pin uint8, val uint8, portStr string) error {
   334  	m.mutex.Lock()
   335  	defer m.mutex.Unlock()
   336  
   337  	selectedPort := m.getPort(portStr)
   338  	return m.write(selectedPort.GPPU, pin, bitState(val))
   339  }
   340  
   341  // SetGPIOPolarity will change a given pin's polarity based on the value:
   342  // val = 1 opposite logic state of the input pin.
   343  // val = 0 same logic state of the input pin.
   344  func (m *MCP23017Driver) SetGPIOPolarity(pin uint8, val uint8, portStr string) (err error) {
   345  	m.mutex.Lock()
   346  	defer m.mutex.Unlock()
   347  
   348  	selectedPort := m.getPort(portStr)
   349  	return m.write(selectedPort.IPOL, pin, bitState(val))
   350  }
   351  
   352  // write gets the value of the passed in register, and then sets the bit specified
   353  // by the pin to the given state.
   354  func (m *MCP23017Driver) write(reg uint8, pin uint8, state bitState) (err error) {
   355  	valOrg, err := m.read(reg)
   356  	if err != nil {
   357  		return err
   358  	}
   359  
   360  	var val uint8
   361  	if state == clear {
   362  		val = clearBit(valOrg, pin)
   363  	} else {
   364  		val = setBit(valOrg, pin)
   365  	}
   366  
   367  	if val != valOrg || m.mcpBehav.forceRefresh {
   368  		if mcp23017Debug {
   369  			log.Printf("write done: MCP forceRefresh: %t, address: 0x%X, register: 0x%X, name: %s, value: 0x%X\n",
   370  				m.mcpBehav.forceRefresh, m.GetAddressOrDefault(mcp23017Address), reg, m.getRegName(reg), val)
   371  		}
   372  		if err = m.connection.WriteByteData(reg, val); err != nil {
   373  			return err
   374  		}
   375  	} else {
   376  		if mcp23017Debug {
   377  			log.Printf("write skipped: MCP forceRefresh: %t, address: 0x%X, register: 0x%X, name: %s, value: 0x%X\n",
   378  				m.mcpBehav.forceRefresh, m.GetAddressOrDefault(mcp23017Address), reg, m.getRegName(reg), val)
   379  		}
   380  	}
   381  	return nil
   382  }
   383  
   384  // read get the data from a given register
   385  // it is mainly a wrapper to create additional debug messages, when activated
   386  func (m *MCP23017Driver) read(reg uint8) (val uint8, err error) {
   387  	val, err = m.connection.ReadByteData(reg)
   388  	if err != nil {
   389  		return val, err
   390  	}
   391  	if mcp23017Debug {
   392  		log.Printf("reading done: MCP autoIODirOff: %t, address: 0x%X, register:0x%X, name: %s, value: 0x%X\n",
   393  			m.mcpBehav.autoIODirOff, m.GetAddressOrDefault(mcp23017Address), reg, m.getRegName(reg), val)
   394  	}
   395  	return val, nil
   396  }
   397  
   398  // getPort return the port (A or B) given a string and the bank.
   399  // Port A is the default if an incorrect or no port is specified.
   400  func (m *MCP23017Driver) getPort(portStr string) (selectedPort port) {
   401  	portStr = strings.ToUpper(portStr)
   402  	switch {
   403  	case portStr == "A":
   404  		return getBank(m.mcpConf.bank).portA
   405  	case portStr == "B":
   406  		return getBank(m.mcpConf.bank).portB
   407  	default:
   408  		return getBank(m.mcpConf.bank).portA
   409  	}
   410  }
   411  
   412  // getUint8Value returns the configuration data as a packed value.
   413  func (mc *MCP23017Config) getUint8Value() uint8 {
   414  	return mc.bank<<7 | mc.mirror<<6 | mc.seqop<<5 | mc.disslw<<4 | mc.haen<<3 | mc.odr<<2 | mc.intpol<<1
   415  }
   416  
   417  // getBank returns a bank's PortA and PortB registers given a bank number (0/1).
   418  func getBank(bnk uint8) bank {
   419  	if bnk == 0 {
   420  		return bank{portA: port{0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x10, 0x12, 0x14}, portB: port{0x01, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0D, 0x0F, 0x11, 0x13, 0x15}}
   421  	}
   422  	return bank{portA: port{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A}, portB: port{0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A}}
   423  }
   424  
   425  // getRegName returns the name of the given register related to the configured bank
   426  // and can be used to write nice debug messages
   427  func (m *MCP23017Driver) getRegName(reg uint8) string {
   428  	b := getBank(m.mcpConf.bank)
   429  	portStr := "A"
   430  	regStr := "unknown"
   431  
   432  	for i := 1; i <= 2; i++ {
   433  		if regStr == "unknown" {
   434  			p := b.portA
   435  			if i == 2 {
   436  				p = b.portB
   437  				portStr = "B"
   438  			}
   439  			switch reg {
   440  			case p.IODIR:
   441  				regStr = "IODIR"
   442  			case p.IPOL:
   443  				regStr = "IPOL"
   444  			case p.GPINTEN:
   445  				regStr = "GPINTEN"
   446  			case p.DEFVAL:
   447  				regStr = "DEFVAL"
   448  			case p.INTCON:
   449  				regStr = "INTCON"
   450  			case p.IOCON:
   451  				regStr = "IOCON"
   452  			case p.GPPU:
   453  				regStr = "GPPU"
   454  			case p.INTF:
   455  				regStr = "INTF"
   456  			case p.INTCAP:
   457  				regStr = "INTCAP"
   458  			case p.GPIO:
   459  				regStr = "GPIO"
   460  			case p.OLAT:
   461  				regStr = "OLAT"
   462  			}
   463  		}
   464  	}
   465  
   466  	return fmt.Sprintf("%s_%s", regStr, portStr)
   467  }