268 lines
8.7 KiB
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
|
|
}
|