MCP23008-RK
Public Member Functions | Protected Member Functions | Static Protected Member Functions | Protected Attributes | Static Protected Attributes
MCP23008 Class Reference

Class for the MCP23008 I2C GPIO chip. More...

#include <MCP23008-RK.h>

Public Member Functions

 MCP23008 (TwoWire &wire, int addr=0)
 Construct the object. Normally you create these as global variables in the main app. More...
 
virtual ~MCP23008 ()
 Object destructor. More...
 
void begin (bool callWireBegin=true)
 Initialize the library - required from setup()! More...
 
void pinMode (uint16_t pin, PinMode mode)
 Sets the MCP23008 GPIO pin mode. More...
 
PinMode getPinMode (uint16_t pin)
 Gets the currently set pin mode. More...
 
bool pinAvailable (uint16_t pin) const
 Returns true if the pin exists on the device. More...
 
void digitalWrite (uint16_t pin, uint8_t value)
 Sets the output value of the pin. More...
 
int32_t digitalRead (uint16_t pin)
 Reads the input value of a pin. More...
 
uint8_t readAllPins ()
 Reads all of the pins at once. More...
 
void enableInterrupts (pin_t mcuInterruptPin, MCP23008InterruptOutputType outputType)
 Enables MCP23008 interrupt mode. More...
 
void attachInterrupt (uint16_t pin, InterruptMode mode, std::function< void(bool)> handler)
 Attaches an interrupt handler for a pin. More...
 
template<typename T >
void attachInterrupt (uint16_t pin, InterruptMode mode, void(T::*callback)(bool), T *instance)
 Attaches an interupt handler in a class member function to a pin. More...
 
void detachInterrupt (uint16_t pin)
 Detaches an interrupt handler for a pin. More...
 
MCP23008withStackSize (size_t value)
 Sets the stack size of the interrupt handler worker thread. More...
 

Protected Member Functions

 MCP23008 (const MCP23008 &)=delete
 This class is not copyable.
 
MCP23008operator= (const MCP23008 &)=delete
 This class is not copyable.
 
bool readRegisterPin (uint8_t reg, uint16_t pin)
 Reads an MCP23008 register and masks off a specific bit. More...
 
bool writeRegisterPin (uint8_t reg, uint16_t pin, bool value)
 Reads an MCP23008 register, masks off a specific bit, and writes the register back. More...
 
uint8_t readRegister (uint8_t reg)
 Reads an MCP23008 register. More...
 
bool writeRegister (uint8_t reg, uint8_t value)
 Writes an MCP23008 register. More...
 
void handleInterrupts ()
 Handles interrupts, used internally. More...
 

Static Protected Member Functions

static os_thread_return_t threadFunctionStatic (void *param)
 Thread function, used internally. More...
 

Protected Attributes

pin_t mcuInterruptPin = PIN_INVALID
 The MCU GPIO the MCP23008 INT pin is connected to. More...
 
std::vector< MCP23008InterruptHandler * > interruptHandlers
 Vector of interrupt handlers. More...
 
os_mutex_t handlersMutex = 0
 Mutex used to prevent simultaneous use of interruptHandlers. More...
 
TwoWire & wire
 The I2C interface to use, typically Wire
 
int addr
 Which MCP23008 to use (0-7) based on A0, A1, and A2 on the MCP23008. More...
 
size_t stackSize = 1024
 Default stack size for the worker thread. More...
 

Static Protected Attributes

static const uint8_t PIN_0 = 0b00000001
 bit mask for GP0
 
static const uint8_t PIN_1 = 0b00000010
 bit mask for GP1
 
static const uint8_t PIN_2 = 0b00000100
 bit mask for GP2
 
static const uint8_t PIN_3 = 0b00001000
 bit mask for GP3
 
static const uint8_t PIN_4 = 0b00010000
 bit mask for GP4
 
static const uint8_t PIN_5 = 0b00100000
 bit mask for GP5
 
static const uint8_t PIN_6 = 0b01000000
 bit mask for GP6
 
static const uint8_t PIN_7 = 0b10000000
 bit mask for GP7
 
static const uint16_t NUM_PINS = 8
 Number of GP pins on the MCP23008 (8)
 
static const uint8_t REG_IODIR = 0x0
 MCP23008 register number for IODIR.
 
static const uint8_t REG_IPOL = 0x1
 MCP23008 register number for IPOL.
 
static const uint8_t REG_GPINTEN = 0x2
 MCP23008 register number for GPINTEN.
 
static const uint8_t REG_DEFVAL = 0x3
 MCP23008 register number for DEFVAL.
 
static const uint8_t REG_INTCON = 0x4
 MCP23008 register number for INTCON.
 
static const uint8_t REG_IOCON = 0x5
 MCP23008 register number for IOCON.
 
static const uint8_t REG_GPPU = 0x6
 MCP23008 register number for GPPU.
 
static const uint8_t REG_INTF = 0x7
 MCP23008 register number for INTF.
 
static const uint8_t REG_INTCAP = 0x8
 MCP23008 register number for INTCAP.
 
static const uint8_t REG_GPIO = 0x9
 MCP23008 register number for GPIO.
 
static const uint8_t REG_OLAT = 0xa
 MCP23008 register number for OLAT.
 
static const uint8_t DEVICE_ADDR = 0b0100000
 The base I2C address for the MCP23008 (0x20)
 
static Thread * thread
 The worker thread object. More...
 
static std::vector< MCP23008 * > * instances
 Array of MCP23008 object instances. More...
 

Detailed Description

Class for the MCP23008 I2C GPIO chip.

You can connect up to 8 of these to a single I2C interface. You normally create one of these objects for each chip (at a different I2C address) as a global variable in the main application.

You must call begin() from global setup() before using other library methods!

Constructor & Destructor Documentation

◆ MCP23008()

MCP23008::MCP23008 ( TwoWire &  wire,
int  addr = 0 
)

Construct the object. Normally you create these as global variables in the main app.

Parameters
wireThe I2C interface to connect to. This is typically Wire (pins D0 and D1) but could be Wire1 on the Electron and E Series, or Wire3 on the Tracker SoM.
addrthe I2C address (0-7) the MCP23008 is configured for using the AD0, AD1, and AD2 pins. Note that this is just the 0-7 part, the actual I2C address will be 0x20 - 0x27.

◆ ~MCP23008()

MCP23008::~MCP23008 ( )
virtual

Object destructor.

As this object is normally allocated as a global variable, it will typically never be destructed.

Member Function Documentation

◆ attachInterrupt() [1/2]

void MCP23008::attachInterrupt ( uint16_t  pin,
InterruptMode  mode,
std::function< void(bool)>  handler 
)

Attaches an interrupt handler for a pin.

Parameters
pinThe GPIO pin 0 - 7.
modeThe interrupt handler mode, one of: RISING, FALLING, or CHANGE.
handlerA function or C++11 lambda with the following prototype:
void handlerFunction(bool newState);

You must call enableInterrupts() before making this call!

Note that the handler will be called from a thread, not an ISR, so it's not a true interrupt handler. The reason is that in order to handle the MCP23008 interrupt, more than one I2C transaction is required. This cannot be easily done at ISR time because an I2C lock needs to be acquired. It can, however, be easily done from a thread, which is what is done here. enableInterrupts() starts this thread.

Even though handler is called from a thread and not an ISR you should still avoid any lengthy operations during it, as the thread handles all interrupts on all MCP23008 and blocking it will prevent all other interrupts from being handled.

There is also a version of this method that takes a class member function below.

If you need to pass additional data ("context") you should instead use a C++11 lambda.

You must not call attachInterrupt() from an interrupt handler! If you do so, this function will deadlock the thread and all interrupt-related functions will stop working.

◆ attachInterrupt() [2/2]

template<typename T >
void MCP23008::attachInterrupt ( uint16_t  pin,
InterruptMode  mode,
void(T::*)(bool)  callback,
T *  instance 
)
inline

Attaches an interupt handler in a class member function to a pin.

Parameters
pinThe GPIO pin 0 - 7.
modeThe interrupt handler mode, one of: RISING, FALLING, or CHANGE.
callbackA C++ class member function with following prototype:
void MyClass::handler(bool newState);
Parameters
instanceThe C++ object instance ("this") pointer.

You typically use it like this:

attachInterrupt(2, FALLING, &MyClass::handler, this);

You must call enableInterrupts() before making this call!

Note that the handler will be called from a thread, not an ISR, so it's not a true interrupt handler. The reason is that in order to handle the MCP23008 interrupt, more than one I2C transaction is required. This cannot be easily done at ISR time because an I2C lock needs to be acquired. It can, however, be easily done from a thread, which is what is done here. enableInterrupts() starts this thread.

Even though handler is called from a thread and not an ISR you should still avoid any lengthy operations during it, as the thread handles all interrupts on all MCP23008 and blocking it will prevent all other interrupts from being handled.

There is also a version of this method that takes a class member function below.

If you need to pass additional data ("context") you should instead use a C++11 lambda.

You must not call attachInterrupt() from an interrupt handler! If you do so, this function will deadlock the thread and all interrupt-related functions will stop working.

◆ begin()

void MCP23008::begin ( bool  callWireBegin = true)

Initialize the library - required from setup()!

Parameters
callWireBeginDefault is true, to call wire.begin() in this method.

You must call this from setup() to initialize the hardware. You must not call this from a global object constructor.

◆ detachInterrupt()

void MCP23008::detachInterrupt ( uint16_t  pin)

Detaches an interrupt handler for a pin.

Parameters
pinThe GPIO pin 0 - 7.

You must not call detachInterrupt() from your interrupt handler! If you do so, this function will deadlock the thread and all interrupt-related functions will stop working.

You should avoid attaching and detaching an interrupt excessively as it's a relatively expensive operation. You should only attach and detach from loop().

◆ digitalRead()

int32_t MCP23008::digitalRead ( uint16_t  pin)

Reads the input value of a pin.

Returns
The value, 0 or 1:
  • 0, false, or LOW
  • 1, true, or HIGH

This performs and I2C transaction, so it will be slower that MCU native digitalRead.

◆ digitalWrite()

void MCP23008::digitalWrite ( uint16_t  pin,
uint8_t  value 
)

Sets the output value of the pin.

Parameters
pinThe GPIO pin 0 - 7.
valueThe value to set the pin to. Typically you use one of:
  • 0, false, or LOW
  • 1, true, or HIGH

You must have previously set the pin to OUTPUT mode before using this method.

This performs and I2C transaction, so it will be slower that MCU native digitalWrite.

◆ enableInterrupts()

void MCP23008::enableInterrupts ( pin_t  mcuInterruptPin,
MCP23008InterruptOutputType  outputType 
)

Enables MCP23008 interrupt mode.

Parameters
mcuInterruptPinThe MCU pin (D2, A2, etc.) the MCP23008 INT pin is connected to. This can also be PIN_INVALID if you want to use the latching change mode but do not have the INT pin connected or do not have a spare MCU GPIO.
outputTypeThe way the INT pin is connected. See MCP23008InterruptOutputType.

Interrupt mode uses the INT output of the MCP23008 to connect to a MCU GPIO pin. This allows a change (RISING, FALLING, or CHANGE) on one or more GPIO connected to the expander to trigger the INT line. This is advantageous because the MCU can poll the INT line much more efficiently than making an I2C transaction to poll the interrupt register on the MCP23008.

Note that this is done from a thread, not using an MCU hardware interrupt. This is because the I2C transaction requires getting a lock on the Wire object, which cannot be done from an ISR. Also, because the MCP23008 INT output is latching, there is no danger of missing it when polling.

It's possible for multiple MCP23008 to share a single MCU interrupt line by using open-drain mode to logically OR them together with no external gate required. You can also use separate MCU interrupt lines, if you prefer.

If you pass PIN_INVALID for mcuInterruptPin, outputType is ignored. This mode is used when you still want to be able to handle latching RISING, FALLING, or CHANGE handlers but do not have the INT pin connected or do not have spare MCU GPIOs. This requires an I2C transaction on every thread timeslice (once per millisecond) so it's not as efficient as using a MCU interrupt pin, but this mode is supported.

◆ getPinMode()

PinMode MCP23008::getPinMode ( uint16_t  pin)

Gets the currently set pin mode.

Parameters
pinThe GPIO pin 0 - 7.
Returns
One of these constants:
  • INPUT
  • INPUT_PULLUP
  • OUTPUT

◆ handleInterrupts()

void MCP23008::handleInterrupts ( )
protected

Handles interrupts, used internally.

This method is called from the worker thread when the MCU interrupt GPIO line signals an interrupt. It then queries the MCP23008 by I2C to read the interrupt status and call any handlers for pins that caused interrupt(s).

◆ pinAvailable()

bool MCP23008::pinAvailable ( uint16_t  pin) const

Returns true if the pin exists on the device.

Parameters
pinThe GPIO pin 0 - 7.
Returns
true if pin is 0 <= pin < 8, otherwise false

◆ pinMode()

void MCP23008::pinMode ( uint16_t  pin,
PinMode  mode 
)

Sets the MCP23008 GPIO pin mode.

Parameters
pinThe GPIO pin 0 - 7.
modeThe mode, one of the following:
  • INPUT
  • INPUT_PULLUP
  • OUTPUT

Note that the MCP23008 does not support input with pull-down or open-drain outputs for its GPIO.

◆ readAllPins()

uint8_t MCP23008::readAllPins ( )

Reads all of the pins at once.

Returns
A bit mask of the pin values

The bit mask is as follows:

Pin Mask
0 0b00000001 = 0x01
1 0b00000010 = 0x02
2 0b00000100 = 0x04
3 0b00001000 = 0x08
4 0b00010000 = 0x10
5 0b00100000 = 0x20
6 0b01000000 = 0x40
7 0b10000000 = 0x80

In other words: bitMask = (1 << pin).

This performs and I2C transaction, but all pins are read with a single I2C transaction so it is faster than calling digitalRead() multiple times.

◆ readRegister()

uint8_t MCP23008::readRegister ( uint8_t  reg)
protected

Reads an MCP23008 register.

Parameters
regThe register number, typically one of the constants like REG_IODIR (0).
Returns
the value. All MCP23008 register values are 8-bit (uint8_t).

There is no way to know if this actually succeeded.

◆ readRegisterPin()

bool MCP23008::readRegisterPin ( uint8_t  reg,
uint16_t  pin 
)
protected

Reads an MCP23008 register and masks off a specific bit.

Parameters
regThe register number, typically one of the constants like REG_GPIO (9).
pinThe pin 0 - 7
Returns
the value boolean value

For registers that contain a bitmask of all 8 values (like REG_GPIO), reads all registers and then performs the necessary bit manipulation.

There is no way to know if this actually succeeded.

◆ threadFunctionStatic()

os_thread_return_t MCP23008::threadFunctionStatic ( void *  param)
staticprotected

Thread function, used internally.

There is only one thread for all instances of this object

◆ withStackSize()

MCP23008& MCP23008::withStackSize ( size_t  value)
inline

Sets the stack size of the interrupt handler worker thread.

Parameters
valueSize in bytes. Default is 1024.
Returns
Returns *this so you can chain options together, fluent-style.

You must call this before the first call to enableInterrupts(). Making the call after will have no effect. If multiple MCP23008 objects are used (multiple chips) you must make this call before the first call to enableInterrupts() as all instances share a single thread.

◆ writeRegister()

bool MCP23008::writeRegister ( uint8_t  reg,
uint8_t  value 
)
protected

Writes an MCP23008 register.

Parameters
regThe register number, typically one of the constants like REG_IODIR (0).
valueThe value. All MCP23008 register values are 8-bit (uint8_t).
Returns
true if the I2C operation completed successfully or false if not.

◆ writeRegisterPin()

bool MCP23008::writeRegisterPin ( uint8_t  reg,
uint16_t  pin,
bool  value 
)
protected

Reads an MCP23008 register, masks off a specific bit, and writes the register back.

Parameters
regThe register number, typically one of the constants like REG_GPIO (9).
pinThe pin 0 - 7
valueThe bit value to set (true or false)
Returns
true if the I2C operation completed successfully or false if not.

For registers that contain a bitmask of all 8 values (like REG_GPIO), reads all registers, performs the necessary bit manipulation, and writes the register back.

The read/modify/write cycle is done within a single I2C lock()/unlock() pair to minimize the chance of simultaneous modification.

Field Documentation

◆ addr

int MCP23008::addr
protected

Which MCP23008 to use (0-7) based on A0, A1, and A2 on the MCP23008.

This is just 0-7, the (0b0100000 of the 7-bit address is ORed in later)

◆ handlersMutex

os_mutex_t MCP23008::handlersMutex = 0
protected

Mutex used to prevent simultaneous use of interruptHandlers.

While technically you can use the vector from two threads at the same time, you can't modify it from another threads at the same time as it will break the iterator. Since we only read from one thread (the worker thread), this isn't a case worth optimizing for.

◆ instances

std::vector< MCP23008 * > * MCP23008::instances
staticprotected

Array of MCP23008 object instances.

This is modified on the contructor and destructor for MCP23008. It is used from the worker thread to handle all interrupts across all MCP23008 objects from a single thread.

◆ interruptHandlers

std::vector<MCP23008InterruptHandler*> MCP23008::interruptHandlers
protected

Vector of interrupt handlers.

This is used by handleInterrupts() and modified by attachInterrupt() and detachInterrupt().

Simultaneous use is prevented by using handlersMutex and std::vector is not inherently thread-safe.

◆ mcuInterruptPin

pin_t MCP23008::mcuInterruptPin = PIN_INVALID
protected

The MCU GPIO the MCP23008 INT pin is connected to.

Is PIN_INVALID if interrupts are not used. This is set by enableInterrupts().

◆ stackSize

size_t MCP23008::stackSize = 1024
protected

Default stack size for the worker thread.

You can change this using withStackSize() before the first call to enableInterrupts()

◆ thread

Thread * MCP23008::thread
staticprotected

The worker thread object.

This is NULL before the first call to enableInterrupts(). A single thread is shared by all instances of this object.


The documentation for this class was generated from the following files:
MCP23008::attachInterrupt
void attachInterrupt(uint16_t pin, InterruptMode mode, std::function< void(bool)> handler)
Attaches an interrupt handler for a pin.
Definition: MCP23008-RK.cpp:181