2015-11-03 18:08:39 +00:00
/ *
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 (
2016-03-07 23:51:45 -07:00
"fmt"
2015-11-03 18:08:39 +00:00
"log"
"strings"
"time"
"github.com/hybridgroup/gobot"
)
var (
2016-03-07 23:51:45 -07:00
debug = false // Set this to true to see debugging information
2015-11-03 18:08:39 +00:00
// Register this Driver
_ gobot . Driver = ( * MCP23017Driver ) ( nil )
)
// Port contains all the registers for the device.
type port struct {
2016-03-07 23:51:45 -07:00
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
2015-11-03 18:08:39 +00:00
}
// 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 } }
}
2016-03-07 23:51:45 -07:00
// MCP23017Config contains the device configuration for the IOCON register.
// These fields should only be set with values 0 or 1.
2015-11-03 18:08:39 +00:00
type MCP23017Config struct {
Bank uint8
Mirror uint8
Seqop uint8
Disslw uint8
Haen uint8
Odr uint8
Intpol uint8
}
2016-03-07 23:51:45 -07:00
// 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
2015-11-03 18:08:39 +00:00
}
2016-03-07 23:51:45 -07:00
// MCP23017Driver contains the driver configuration parameters.
2015-11-03 18:08:39 +00:00
type MCP23017Driver struct {
name string
connection I2c
conf MCP23017Config
mcp23017Address int
interval time . Duration
gobot . Commander
gobot . Eventer
}
2016-09-25 14:08:18 +02:00
// NewMCP23017Driver creates a new driver with specified i2c interface.
func NewMCP23017Driver ( a I2c , conf MCP23017Config , deviceAddress int , v ... time . Duration ) * MCP23017Driver {
2015-11-03 18:08:39 +00:00
m := & MCP23017Driver {
2016-10-03 19:06:37 +02:00
name : "MCP23017" ,
2015-11-03 18:08:39 +00:00
connection : a ,
conf : conf ,
mcp23017Address : deviceAddress ,
Commander : gobot . NewCommander ( ) ,
Eventer : gobot . NewEventer ( ) ,
}
m . AddCommand ( "WriteGPIO" , func ( params map [ string ] interface { } ) interface { } {
2016-03-07 23:51:45 -07:00
pin := params [ "pin" ] . ( uint8 )
val := params [ "val" ] . ( uint8 )
2015-11-03 18:08:39 +00:00
port := params [ "port" ] . ( string )
2016-03-07 23:51:45 -07:00
err := m . WriteGPIO ( pin , val , port )
return map [ string ] interface { } { "err" : err }
2015-11-03 18:08:39 +00:00
} )
m . AddCommand ( "ReadGPIO" , func ( params map [ string ] interface { } ) interface { } {
2016-03-07 23:51:45 -07:00
pin := params [ "pin" ] . ( uint8 )
2015-11-03 18:08:39 +00:00
port := params [ "port" ] . ( string )
val , err := m . ReadGPIO ( pin , port )
return map [ string ] interface { } { "val" : val , "err" : err }
} )
return m
}
2016-03-07 23:51:45 -07:00
// Name return the driver name.
2015-11-03 18:08:39 +00:00
func ( m * MCP23017Driver ) Name ( ) string { return m . name }
2016-09-25 14:08:18 +02:00
// SetName set the driver name.
func ( m * MCP23017Driver ) SetName ( n string ) { m . name = n }
2016-03-07 23:51:45 -07:00
// Connection returns the I2c connection.
2015-11-03 18:08:39 +00:00
func ( m * MCP23017Driver ) Connection ( ) gobot . Connection { return m . connection . ( gobot . Connection ) }
2016-03-07 23:51:45 -07:00
// Halt stops the driver.
2016-11-07 14:55:21 +01:00
func ( m * MCP23017Driver ) Halt ( ) ( err error ) { return }
2015-11-03 18:08:39 +00:00
2016-03-07 23:51:45 -07:00
// Start writes the device configuration.
2016-11-07 14:55:21 +01:00
func ( m * MCP23017Driver ) Start ( ) ( errs error ) {
2015-11-03 18:08:39 +00:00
if err := m . connection . I2cStart ( m . mcp23017Address ) ; err != nil {
2016-11-07 14:55:21 +01:00
return err
2015-11-03 18:08:39 +00:00
}
2016-03-07 23:51:45 -07:00
// 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 {
2016-11-07 14:55:21 +01:00
return err
2015-11-03 18:08:39 +00:00
}
return
}
2016-03-07 23:51:45 -07:00
// 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 ) {
2015-11-03 18:08:39 +00:00
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
}
2016-09-07 17:13:42 +08:00
// 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
}
2015-11-03 18:08:39 +00:00
// ReadGPIO reads a value from a given gpio pin (0-7) and a
// port (A or B).
2016-03-07 23:51:45 -07:00
func ( m * MCP23017Driver ) ReadGPIO ( pin uint8 , portStr string ) ( val uint8 , err error ) {
2015-11-03 18:08:39 +00:00
selectedPort := m . getPort ( portStr )
2016-03-07 23:51:45 -07:00
val , err = m . read ( selectedPort . GPIO )
2015-11-03 18:08:39 +00:00
if err != nil {
2016-03-07 23:51:45 -07:00
return val , err
2015-11-03 18:08:39 +00:00
}
2016-03-07 23:51:45 -07:00
return ( 1 << uint8 ( pin ) & val ) , nil
2015-11-03 18:08:39 +00:00
}
// SetPullUp sets the pull up state of a given pin based on the value:
// val = 1 pull up enabled.
// val = 0 pull up disabled.
2016-03-07 23:51:45 -07:00
func ( m * MCP23017Driver ) SetPullUp ( pin uint8 , val uint8 , portStr string ) error {
2015-11-03 18:08:39 +00:00
selectedPort := m . getPort ( portStr )
2016-07-13 09:32:22 -06:00
return m . write ( selectedPort . GPPU , pin , val )
2015-11-03 18:08:39 +00:00
}
// 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.
2016-03-07 23:51:45 -07:00
func ( m * MCP23017Driver ) SetGPIOPolarity ( pin uint8 , val uint8 , portStr string ) ( err error ) {
2015-11-03 18:08:39 +00:00
selectedPort := m . getPort ( portStr )
2016-07-13 09:32:22 -06:00
return m . write ( selectedPort . IPOL , pin , val )
2015-11-03 18:08:39 +00:00
}
// write gets the value of the passed in register, and then overwrites
// the bit specified by the pin, with the given value.
2016-03-07 23:51:45 -07:00
func ( m * MCP23017Driver ) write ( reg uint8 , pin uint8 , val uint8 ) ( err error ) {
2015-11-03 18:08:39 +00:00
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 ) )
}
2016-03-07 23:51:45 -07:00
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 {
2015-11-03 18:08:39 +00:00
return err
}
return nil
}
2016-03-07 23:51:45 -07:00
// 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 )
2015-11-03 18:08:39 +00:00
if err != nil {
return val , err
}
2016-03-07 23:51:45 -07:00
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 ] )
2015-11-03 18:08:39 +00:00
}
2016-03-07 23:51:45 -07:00
return v [ register ] , nil
2015-11-03 18:08:39 +00:00
}
// 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
}