hybridgroup.gobot/drivers/i2c/mcp23017_driver.go

268 lines
8.7 KiB
Go

/*
Copyright (c) 2015 Ulises Flynn
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package i2c
import (
"fmt"
"log"
"strings"
"time"
"gobot.io/x/gobot"
)
var (
debug = false // Set this to true to see debugging information
// Register this Driver
_ gobot.Driver = (*MCP23017Driver)(nil)
)
// Port contains all the registers for the device.
type port struct {
IODIR uint8 // I/O direction register: 0=output / 1=input
IPOL uint8 // Input polarity register: 0=normal polarity / 1=inversed
GPINTEN uint8 // Interrupt on change control register: 0=disabled / 1=enabled
DEFVAL uint8 // Default compare register for interrupt on change
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
IOCON uint8 // Configuration register
GPPU uint8 // Pull-up resistor configuration register: 0=enabled / 1=disabled
INTF uint8 // Interrupt flag register: 0=no interrupt / 1=pin caused interrupt
INTCAP uint8 // Interrupt capture register, captures pin values during interrupt: 0=logic low / 1=logic high
GPIO uint8 // Port register, reading from this register reads the port
OLAT uint8 // Output latch register, write modifies the pins: 0=logic low / 1=logic high
}
// Registers in the MCP23017 have different address based on which bank is used.
// Each bank is made up of PortA and PortB registers.
type bank struct {
PortA port
PortB port
}
// getBank returns a bank's PortA and PortB registers given a bank number (0/1).
func getBank(bnk uint8) bank {
if bnk == 0 {
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}}
}
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}}
}
// MCP23017Config contains the device configuration for the IOCON register.
// These fields should only be set with values 0 or 1.
type MCP23017Config struct {
Bank uint8
Mirror uint8
Seqop uint8
Disslw uint8
Haen uint8
Odr uint8
Intpol uint8
}
// GetUint8Value returns the configuration data as a packed value.
func (mc *MCP23017Config) GetUint8Value() uint8 {
return mc.Bank<<7 | mc.Mirror<<6 | mc.Seqop<<5 | mc.Disslw<<4 | mc.Haen<<3 | mc.Odr<<2 | mc.Intpol<<1
}
// MCP23017Driver contains the driver configuration parameters.
type MCP23017Driver struct {
name string
connection I2c
conf MCP23017Config
mcp23017Address int
interval time.Duration
gobot.Commander
gobot.Eventer
}
// NewMCP23017Driver creates a new driver with specified i2c interface.
func NewMCP23017Driver(a I2c, conf MCP23017Config, deviceAddress int, v ...time.Duration) *MCP23017Driver {
m := &MCP23017Driver{
name: "MCP23017",
connection: a,
conf: conf,
mcp23017Address: deviceAddress,
Commander: gobot.NewCommander(),
Eventer: gobot.NewEventer(),
}
m.AddCommand("WriteGPIO", func(params map[string]interface{}) interface{} {
pin := params["pin"].(uint8)
val := params["val"].(uint8)
port := params["port"].(string)
err := m.WriteGPIO(pin, val, port)
return map[string]interface{}{"err": err}
})
m.AddCommand("ReadGPIO", func(params map[string]interface{}) interface{} {
pin := params["pin"].(uint8)
port := params["port"].(string)
val, err := m.ReadGPIO(pin, port)
return map[string]interface{}{"val": val, "err": err}
})
return m
}
// Name return the driver name.
func (m *MCP23017Driver) Name() string { return m.name }
// SetName set the driver name.
func (m *MCP23017Driver) SetName(n string) { m.name = n }
// Connection returns the I2c connection.
func (m *MCP23017Driver) Connection() gobot.Connection { return m.connection.(gobot.Connection) }
// Halt stops the driver.
func (m *MCP23017Driver) Halt() (err error) { return }
// Start writes the device configuration.
func (m *MCP23017Driver) Start() (errs error) {
if err := m.connection.I2cStart(m.mcp23017Address); err != nil {
return err
}
// Set IOCON register with MCP23017 configuration.
ioconReg := m.getPort("A").IOCON // IOCON address is the same for Port A or B.
ioconVal := m.conf.GetUint8Value()
if err := m.connection.I2cWrite(m.mcp23017Address, []uint8{ioconReg, ioconVal}); err != nil {
return err
}
return
}
// WriteGPIO writes a value to a gpio pin (0-7) and a port (A or B).
func (m *MCP23017Driver) WriteGPIO(pin uint8, val uint8, portStr string) (err error) {
selectedPort := m.getPort(portStr)
// Set IODIR register bit for given pin to an output.
if err := m.write(selectedPort.IODIR, uint8(pin), 0); err != nil {
return err
}
// Set OLAT register to write a value to the given pin.
if err := m.write(selectedPort.OLAT, uint8(pin), uint8(val)); err != nil {
return err
}
return nil
}
// PinMode set pin mode
// val (0 output 1 input)
// port (A or B).
func (m *MCP23017Driver) PinMode(pin, val uint8, portStr string) (err error) {
selectedPort := m.getPort(portStr)
// Set IODIR register bit for given pin to an output/input.
if err = m.write(selectedPort.IODIR, uint8(pin), val); err != nil {
return
}
return
}
// ReadGPIO reads a value from a given gpio pin (0-7) and a
// port (A or B).
func (m *MCP23017Driver) ReadGPIO(pin uint8, portStr string) (val uint8, err error) {
selectedPort := m.getPort(portStr)
val, err = m.read(selectedPort.GPIO)
if err != nil {
return val, err
}
return (1 << uint8(pin) & val), nil
}
// SetPullUp sets the pull up state of a given pin based on the value:
// val = 1 pull up enabled.
// val = 0 pull up disabled.
func (m *MCP23017Driver) SetPullUp(pin uint8, val uint8, portStr string) error {
selectedPort := m.getPort(portStr)
return m.write(selectedPort.GPPU, pin, val)
}
// SetGPIOPolarity will change a given pin's polarity based on the value:
// val = 1 opposite logic state of the input pin.
// val = 0 same logic state of the input pin.
func (m *MCP23017Driver) SetGPIOPolarity(pin uint8, val uint8, portStr string) (err error) {
selectedPort := m.getPort(portStr)
return m.write(selectedPort.IPOL, pin, val)
}
// write gets the value of the passed in register, and then overwrites
// the bit specified by the pin, with the given value.
func (m *MCP23017Driver) write(reg uint8, pin uint8, val uint8) (err error) {
var ioval uint8
iodir, err := m.read(reg)
if err != nil {
return err
}
if val == 0 {
ioval = clearBit(iodir, uint8(pin))
} else if val == 1 {
ioval = setBit(iodir, uint8(pin))
}
if debug {
log.Printf("Writing: MCP address: 0x%X, register: 0x%X\t, value: 0x%X\n", m.mcp23017Address, reg, ioval)
}
if err = m.connection.I2cWrite(m.mcp23017Address, []uint8{reg, ioval}); err != nil {
return err
}
return nil
}
// read get the data from a given register. The I2cRead does not read a specific
// register from the device, rather it will read n bytes starting at the base
// device address. To read a specific register, read register + 1 bytes, and then index
// the result with the given register to get the value.
func (m *MCP23017Driver) read(reg uint8) (val uint8, err error) {
register := int(reg)
bytesToRead := register + 1
v, err := m.connection.I2cRead(m.mcp23017Address, bytesToRead)
if err != nil {
return val, err
}
if len(v) != bytesToRead {
return val, fmt.Errorf("Read was unable to get %d bytes for register: 0x%X\n", bytesToRead, reg)
}
if debug {
log.Printf("Reading: MCP address: 0x%X, register:0x%X\t,value: 0x%X\n", m.mcp23017Address, reg, v[register])
}
return v[register], nil
}
// getPort return the port (A or B) given a string and the bank.
// Port A is the default if an incorrect or no port is specified.
func (m *MCP23017Driver) getPort(portStr string) (selectedPort port) {
portStr = strings.ToUpper(portStr)
switch {
case portStr == "A":
return getBank(m.conf.Bank).PortA
case portStr == "B":
return getBank(m.conf.Bank).PortB
default:
return getBank(m.conf.Bank).PortA
}
}
// setBit is used to set a bit at a given position to 1.
func setBit(n uint8, pos uint8) uint8 {
n |= (1 << pos)
return n
}
// clearBit is used to set a bit at a given position to 0.
func clearBit(n uint8, pos uint8) uint8 {
mask := ^uint8(1 << pos)
n &= mask
return n
}