Proton BASIC Compiler - Port expansion using the MCP23008

  • Pic® Basic

  • Port expansion using the MCP23008

    The MCP23008 device provides 8-bit, general purpose, parallel I/O expansion for I2C bus applications.
    The MCP23008 consists of multiple 8-bit configuration registers for input, output and polarity selection.
    The system master can enable the I/Os as either inputs or outputs by writing the I/O configuration bits.
    The polarity of the Input Port register can be inverted with the Polarity inversion register. All registers can be read by the system master.

    Interrupt mode:
    Three registers configure the interrupt system for accurate and complete operation fulfilling all possibilities. And 2 more registers show the results.
    • The GPINTEN register controls the interrupt-onchange feature for each pin. It works likes the INT1E, INT2E, INT3E interrupts enable of the PIC(r)
    • The INTCON register controls how the associated pin value is compared for the interrupt-on-change feature.
    • If a bit is set, the corresponding I/O pin is compared against the associated bit in the DEFVAL register. If a bit value is clear, the corresponding I/O pin is compared against the previous value.
    • The default comparison value is configured in the DEFVAL register. If enabled (via GPINTEN and INTCON) to compare against the DEFVAL register, an opposite value on the associated pin will cause an interrupt to occur.
    • To understand more easily the status of the corresponding entry an idle status bit is set. In the DEMO program, the number 1 is connected to VDD at standby, then bit 1 will be put in the corresponding position in the DEFVAL register.
    • The INTF register reflects the interrupt condition on the port pins of any pin that is enabled for interrupts via the GPINTEN register. A ‘set’ bit indicates that the associated pin caused the interrupt.
    • The INTCAP register captures the GPIO port value at the time the interrupt occurred. The register is ‘readonly’ and is updated only when an interrupt occurs. This register will remain unchanged until the interrupt is cleared via a read of INTCAP or GPIO (very important).
    The GPIO register reflects the value on the port. The expander reads the port making an exclusive OR with IPOL register to inverse the input bits for the INTCAP and GPIO registers.

    I present two libraries to use with the MCP23008 expander.
    The first library uses hardware MSSP module of the PIC(r). (immovable pins) (
    The second library uses software to create a dialogue on the I2C bus. (configurable pins) (
    In both cases the ACK response of the expander is used to verify the proper operation of the I2C bus.
    The code has been written for the Amicus board and has been tested with real piece.

    But to reduce the lines of code, I modified the files clearing all controls of the Acknowledge except for routine Initialization. See the smaller code files: and

    Define the parameters of the library work.
    Configuration of the Interrogation mode:
    • Define the MCP23008 static address generated at compiler time.
    • Define and insert the Reset code of the MCP23008 expander, if needed.
    • Include the file. (,, or
    ' Define the Address of the MCP23008 chip (See ISIS schematic)
    $define InpMCP23008Address 1
    ' Define the reset of the MCP23008 expander
    $define _MCP_RST_
    Include "MCP23008-H.Inc"
    Configuration of the Interrupt mode:
    • Define the MCP23008 static address generated at compiler time.
    • Define and insert the Reset code of the MCP23008 expander, if needed.
    • Define if interrupt is used for the expander. (uncomment the line to enable the interrupt mode).
    • Define the reading code that it's inside the Interrupt Handler or in main program. (uncomment the line to enable the option).
    • Define the interrupt PIC pin connected (INT0, INT1 or INT2). (uncomment the line to enable the interrupt pin, only one).
    • Include the file.
    $define InpMCP23008Address 1
    $define _MCP_RST_
    $define _PCF_Interrupt_
    $ifdef _PCF_Interrupt_
      ' $define _Read_Inside_INT_
           $define _INT0_
      '    $define _INT1_    
      '    $define _INT2_    
    Include "MCP23008-H.Inc"
    The code uses shadow variables to copy the value of the expander registers. Therefore commands are specific for each register. Each command writes to the expander and updates the shadow variable.

    Mode READ the MCP23008:
    This command reads a Byte from the PORT expander and updates the shadow variable.
    This is the recommended command to read the PORT.
    ReadMCP = MCP23008_Read_PORT(CheckAck)
    ReadMCP = MCP23008_Read_PORT()

    This command reads a Byte from any register of the expander and does not update the shadow variable.
    ReadMCP = MCP23008_Read(Address, CheckAck)
    ReadMCP = MCP23008_Read(Address)

    These commands read only the expander registers. The Acknowledge is not checked.
    ReadMCP = MCP23008_Read_IODIR()
    ReadMCP = MCP23008_Read_IPOL()
    ReadMCP = MCP23008_Read_GPINTEN()
    ReadMCP = MCP23008_Read_DEFVAL()
    ReadMCP = MCP23008_Read_INTCON()
    ReadMCP = MCP23008_Read_IOCON()
    ReadMCP = MCP23008_Read_GPPU()
    ReadMCP = MCP23008_Read_INTF()
    ReadMCP = MCP23008_Read_GPIO()
    ReadMCP = MCP23008_Read_OLAT()

    Theses commands read only the shadow variables, used for testing or debug.
    ReadMCP = MCP23008_Read_IODIR_Shadow()
    ReadMCP = MCP23008_Read_GPPU_Shadow()

    Mode WRITE the MCP23008:
    These commands write a Byte to the PORT expander and update the shadow variable. (WriteMCP and CheckAck could be omitted)
    These are the recommended commands to write the PORT.
    WriteMCP = MCP23008_Write_PORT(DataByte, CheckAck)
    WriteMCP = MCP23008_Write_PinPort(Pin, HiLo, CheckAck)
    WriteMCP = MCP23008_Toggle_PinPort(Pin, CheckAck)

    These commands write a Byte to the expander registers and update the shadow variables.
    Thus any expander register can be written on the fly. (WriteMCP and CheckAck could be omitted)
    WriteMCP = MCP23008_Write_IODIR(Direction, CheckAck)
    WriteMCP = MCP23008_Write_IPOL(DataByte, CheckAck)
    WriteMCP = MCP23008_Write_GPINTEN(DataByte, CheckAck)
    WriteMCP = MCP23008_Write_DEFVAL(DataByte, CheckAck)
    WriteMCP = MCP23008_Write_INTCON(DataByte, CheckAck)
    WriteMCP = MCP23008_Write_IOCON(DataByte, CheckAck)
    WriteMCP = MCP23008_Write_GPPU(PullUps, CheckAck)

    Special features:
    To configure and write bits to the expander every register has a shadow variable.
    The shadow variables OLAT, GPIO, INTCAP are filtered to avoid seeing the output bits.

    The test files seem complex but it is needed to test all possibilities of the expander.
    Some functionality of the Proteus model does not work properly especially the interrupt function with the DEFVAL register.
    Interrupt On Change with DEFVAL: INTCON Bit = 1
    The operation of this option is very special. If an input is active, the INT interrupt output goes to low level and disables all other inputs. If a sensor stays active for a failure the port inputs are void. I recommend using this option only with fast signals such as pushbuttons.
    Please read carefully the datasheet and application AN1043a. I test the MCP23008 up to 1200 Khz and works well. Take care with mechanical sensors for the bounces. Some malfunction occurs. I recommend some RC filters in the inputs.

    Using the MCP23008 expander in interrupt mode is very easy if the registers are set well.
    Using the I2C bus with highest speed as possible can help working with the interrupt mode.
    The software library uses the file 'Software_I2C.Inc' written by Les Johnson because it has the same structure of the HBUSOUT/HBUSIN commands. This file has been modified then please save your original file.
    Alberto Freixanet
    Updated 07/04/2015