MCP23008-RK
|
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... | |
MCP23008 & | withStackSize (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. | |
MCP23008 & | operator= (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... | |
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!
MCP23008::MCP23008 | ( | TwoWire & | wire, |
int | addr = 0 |
||
) |
Construct the object. Normally you create these as global variables in the main app.
wire | The 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. |
addr | the 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. |
|
virtual |
Object destructor.
As this object is normally allocated as a global variable, it will typically never be destructed.
void MCP23008::attachInterrupt | ( | uint16_t | pin, |
InterruptMode | mode, | ||
std::function< void(bool)> | handler | ||
) |
Attaches an interrupt handler for a pin.
pin | The GPIO pin 0 - 7. |
mode | The interrupt handler mode, one of: RISING , FALLING , or CHANGE . |
handler | A function or C++11 lambda with the following prototype: |
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.
|
inline |
Attaches an interupt handler in a class member function to a pin.
pin | The GPIO pin 0 - 7. |
mode | The interrupt handler mode, one of: RISING , FALLING , or CHANGE . |
callback | A C++ class member function with following prototype: |
instance | The C++ object instance ("this") pointer. |
You typically use it like 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.
void MCP23008::begin | ( | bool | callWireBegin = true | ) |
Initialize the library - required from setup()!
callWireBegin | Default 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.
void MCP23008::detachInterrupt | ( | uint16_t | pin | ) |
Detaches an interrupt handler for a pin.
pin | The 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().
int32_t MCP23008::digitalRead | ( | uint16_t | pin | ) |
Reads the input value of a pin.
This performs and I2C transaction, so it will be slower that MCU native digitalRead.
void MCP23008::digitalWrite | ( | uint16_t | pin, |
uint8_t | value | ||
) |
Sets the output value of the pin.
pin | The GPIO pin 0 - 7. |
value | The value to set the pin to. Typically you use one of: |
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.
void MCP23008::enableInterrupts | ( | pin_t | mcuInterruptPin, |
MCP23008InterruptOutputType | outputType | ||
) |
Enables MCP23008 interrupt mode.
mcuInterruptPin | The 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. |
outputType | The 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.
PinMode MCP23008::getPinMode | ( | uint16_t | pin | ) |
Gets the currently set pin mode.
pin | The GPIO pin 0 - 7. |
INPUT
INPUT_PULLUP
OUTPUT
|
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).
bool MCP23008::pinAvailable | ( | uint16_t | pin | ) | const |
Returns true if the pin exists on the device.
pin | The GPIO pin 0 - 7. |
void MCP23008::pinMode | ( | uint16_t | pin, |
PinMode | mode | ||
) |
uint8_t MCP23008::readAllPins | ( | ) |
Reads all of the pins at once.
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.
|
protected |
|
protected |
Reads an MCP23008 register and masks off a specific bit.
reg | The register number, typically one of the constants like REG_GPIO (9). |
pin | The pin 0 - 7 |
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.
|
staticprotected |
Thread function, used internally.
There is only one thread for all instances of this object
|
inline |
Sets the stack size of the interrupt handler worker thread.
value | Size in bytes. Default is 1024. |
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.
|
protected |
|
protected |
Reads an MCP23008 register, masks off a specific bit, and writes the register back.
reg | The register number, typically one of the constants like REG_GPIO (9). |
pin | The pin 0 - 7 |
value | The bit value to set (true or false) |
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.
|
protected |
|
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.
|
staticprotected |
|
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.
|
protected |
The MCU GPIO the MCP23008 INT pin is connected to.
Is PIN_INVALID
if interrupts are not used. This is set by enableInterrupts()
.
|
protected |
Default stack size for the worker thread.
You can change this using withStackSize() before the first call to enableInterrupts()
|
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.