MCP23S17

Hello, this is a pre-version of a working module I worked on some months ago, and unfortunately, for various reasons, I never got time to work on it again.

In this article, I'm publishing two modules dealing with the Microchip MCP23S17 (SPI) port expander. first module handles only one MCP23S17 module. It's usefull when you need only one module in your design. the second module handles multiple MCP23S17 modules in the same circuit.

-Why two modules? simply because in the case of multiple modules, you have to pass an additional parameter to all routines: the additional param is the chip physical address. The single MCP23S17 module does not have this param in its routines, and thus, let you save some precious ram bytes that you may need for other stuff.

The single chip module has been also modded (some io direction registers buffered in local variables to speedup operations - mainly done for GLCD through SPI stuff).

Main Features:

1- Handle one or multiple MCP23S17 modules.

2- Use Hardware or Software SPI to manage the modules. Software SPI is usefull for little PICs that does not have SPI hardware module (like the little 18 pins 18F1320 mcu).

3- Most of functions/subs make the MCP chip act as additional PIC ports.

4- MCP chips are handled as two 8 bit ports.

5- All routines to read/write registers are available.

What's not handled:

- This is really a pre-version. Next version of the driver (under devlopment) handles also Interrupts.

Actually, interrupts can be handled by reading/writing concerned registers, it will be used transparently in upcomming drivers.

- Full support for SPI and I2C version of this chips. Switching from SPI to I2C version will be done by selecting only one OPTION !

This work has been done very quickly, when I was working on GLCD through SPI expander support. Instead of keeping all that on my PC, I prefer to share it in case anyone has urgent need to it. I'll also post all my GLCD stuff with all comments on results in the article session in next couple of days.

I hope you can find this helpfull.

For any comment, or feedback (or bug report), please post to Swordfish Forum.

VERY IMPORTANT NOTICE concerning Swordfish SPI module:

PS. I never got this module to work with official Swordfish hatdware SPI driver. I used instead a modified version of (hardware) SPI driver using some comments published by Steven on the forum (and sent to me by mail - Great thank Steven). The modified version of the SPI module is also published at the END of this article.

IMPORTANT Options

For Both drivers (Single Chip and Multi-Chip)

  1. option MCPSPI_SOFTWARE=true // use software SPI...
  2. option MCPSPI_SOFTWARE = false // Use Hardware SPI
  3. Option MCP_CS = PORTC.1 // Chip Select Pin
  4. Option MCP_RST = PORTC.2 // may be commented If RST is Not used

For the SINGLE MCP Chip driver, there is an additional option to set its physical address. Default is 0.

  1. option MCPCHIP_ADDRESS = 0

Single MCP23S17 chip module

connection to hardware SPI is shown in following diagram (for software SPI you can use Swordfish Options to modify pins affectation).

Reset pin can be ommited (saving one pic pin), and pulled up.

Module Code

{
*************************************************************************
*  Name    : MCP23S17.BAS                                               *
*  Author  : Ahmed Lazreg  (octal@pocketmt.com)                             *
*            http://www.pocketmt.com                                        *
*  Notice  : Copyright (c) 2006 Pocket MicroTechnics                    *
*          : All Rights Reserved                                        *
*  Date    : 01/03/2008                                                 *
*  Version : 1.0                                                        *
*  Modified:                                                            *
*  Notes   : This module handles a SINGLE MCP23S17 - 16 pins I/O port   *
*            expander from Microchip as 2x8 bits PortA/PortB.           *
*            If you want to use multiple chips simultaniously, use      *
*            MCP23S17_MULT.BAS instead. It handles multi_chip selection *
*            using chip address pins                                    *    
*************************************************************************
}


Module MCP23S17

// hardware or software SPI...
#if IsOption(MCPSPI_SOFTWARE) And Not (MCPSPI_SOFTWARE in (true, false))
   #error MCPSPI_SOFTWARE, "Invalid option, must be TRUE or FALSE."
#endif
#option MCPSPI_SOFTWARE = false


#if MCPSPI_SOFTWARE=true // use software SPI...
Include "sspi.bas"
#else // use hardware SPI...
Include "SPI.BAS"
#EndIf

#Option MCP_CS  = PORTC.1
#Option MCP_RST = PORTC.2   // may be commented If RST is Not used

// validate SCK pin...
#If IsOption(MCP_CS) And Not IsValidPortPin(MCP_CS) 
   #Error MCP_CS, "Invalid option. MCP_CS must be a valid port pin."
#EndIf

// validate RST pin...
#If IsOption(MCP_RST) And Not IsValidPortPin(MCP_RST) 
   #Error MCP_RST, "Invalid option. MCP_RST must be a valid port pin."
#EndIf

#if IsOption(MCPCHIP_ADDRESS) And Not (MCPCHIP_ADDRESS in (0 to 7))
   #error MCPCHIP_ADDRESS, "Invalid option, MCPCHIP_ADDRESS must be between 0 and 7."
#endif

#if Not IsOption(MCPCHIP_ADDRESS)
  #option MCPCHIP_ADDRESS = 0 // Default chip address to [A2 A1 A0] = [000] 
  // Disable the warning if you do not want it!
  #warning "MCPCHIP_ADDRESS option is set default to [0]."
#endif

Public Dim 
   CS As MCP_CS.MCP_CS@   // Chip select

#if IsOption(MCP_RST)
Public Dim
   RST As MCP_RST.MCP_RST@    // RST pin
#endif

//----------------------------------------------------------------------------
//Please ignore these options ... only here for the GLCD through SPI stuff...
#option MCPPortA0 = 0
#option MCPPortA1 = 1
#option MCPPortA2 = 2
#option MCPPortA3 = 3
#option MCPPortA4 = 4
#option MCPPortA5 = 5
#option MCPPortA6 = 6
#option MCPPortA7 = 7

#option MCPPortB0 = 8
#option MCPPortB1 = 9
#option MCPPortB2 = 10
#option MCPPortB3 = 11
#option MCPPortB4 = 12
#option MCPPortB5 = 13
#option MCPPortB6 = 14
#option MCPPortB7 = 15
//----------------------------------------------------------------------------

Public Const
  IODIRA   = $00, 
  IODIRB   = $01,
  IPOLA    = $02,
  IPOLB    = $03,
  GPINTENA = $04,
  GPINTENB = $05,
  DEFVALA  = $06,
  DEFVALB  = $07,
  INTCONA  = $08,
  INTCONB  = $09,
  IOCONA   = $0A,
  IOCONB   = $0B,
  GPPUA    = $0C,
  GPPUB    = $0D,
  INTFA    = $0E,
  INTFB    = $0F,
  INTCAPA  = $10,
  INTCAPB  = $11,
  GPIOA    = $12,
  GPIOB    = $13,
  OLATA    = $14,
  OLATB    = $15 


//Control Byte For SPI operations [0100][A2][A1][A0][R=1/W=0]
Private Const CONTROL_BYTE_MASK_R As Byte = %01000001 Or (MCPCHIP_ADDRESS<<1)
Private Const CONTROL_BYTE_MASK_W As Byte = %01000000 Or (MCPCHIP_ADDRESS<<1)

Private Const CONTROL_IOCON = %00011000 // Init value for IOCON - bits details in following comment

{
 Reg IOCON = [BANK][MIRROR][SEQOP][DISSLW][HAEN][ODR][INTPOL][-Unused]

 Bit 7 BANK: Controls how the registers are addressed
             1 = The registers associated With each Port are separated into different banks
             0 = The registers are in the same bank (addresses are sequential)
 Bit 6 MIRROR: INT Pins Mirror Bit
             1 = The INT pins are internally connected
             0 = The INT pins are Not connected. INTA is associated With PortA And INTB is associated With PortB
 Bit 5 SEQOP: Sequential Operation mode Bit.
             1 = Sequential operation disabled, address pointer does Not increment.
             0 = Sequential operation Enabled, address pointer increments.
 Bit 4 DISSLW: Slew Rate control Bit For SDA Output.
             1 = Slew rate disabled.
             0 = Slew rate Enabled.
 Bit 3 HAEN: Hardware Address Enable Bit (MCP23S17 only).
             Address pins are always Enabled on MCP23017.
             1 = Enables the MCP23S17 address pins.
             0 = Disables the MCP23S17 address pins.
 Bit 2 ODR: This Bit configures the INT pin As an open-drain Output.
             1 = Open-drain Output (overrides the INTPOL Bit).
             0 = Active driver Output (INTPOL Bit sets the polarity).
 Bit 1 INTPOL: This Bit sets the polarity of the INT Output pin.
             1 = Active-High.
             0 = Active-Low.
 Bit 0 Unimplemented: Read As 0.

}


//Local Variables used to cache the state of Direction registers
Dim DirRegA, DirRegB, DataRegA, DataRegB As Byte


{
****************************************************************************
* Name    : WriteReg                                                       *
* Purpose : Writes pData byte to register address given by pRegAddress     *
****************************************************************************
} 
Public Sub WriteReg(pRegAddress As Byte, pData As Byte)
    CS = 0
    'DelayUS(5)  'delays can/should be added if 
                   'you have problems at High Clock values
    WriteByte(CONTROL_BYTE_MASK_W)
    WriteByte(pRegAddress)
    WriteByte(pData)
    CS = 1
    'DelayUS(5) 
End Sub

{
****************************************************************************
* Name    : ReadReg                                                        *
* Purpose : Read register address given by pRegAddress                     *
****************************************************************************
} 
Public Function ReadReg(ByVal pRegAddress As Byte) As Byte
    CS = 0
    'DelayUS(5)  'delays can/should be added if 
                   'you have problems at High Clock values   
    WriteByte(CONTROL_BYTE_MASK_R)
    WriteByte(pRegAddress)
    Result = ReadByte()
    CS = 1  
    'DelayUS(5) 
End Function

{
****************************************************************************
* Name    : ReadPortA                                                      *
* Purpose : Returns PortA reg value                                        *
****************************************************************************
} 
Public Inline Function ReadPortA() As Byte
    Result = ReadReg(GPIOA)
End Function

{
****************************************************************************
* Name    : ReadPortB                                                      *
* Purpose : Returns PortB reg value                                        *
****************************************************************************
} 
Public Inline Function ReadPortB() As Byte
    Result = ReadReg(GPIOB)
End Function

{
****************************************************************************
* Name    : ReadPinPortA                                                   *
* Purpose : Returns Value of a specified PortA Pin                         *
****************************************************************************
} 
Public Inline Function ReadPinPortA(pPin As Byte) As Bit
    Dim tmp As Byte
    tmp = ReadReg(GPIOA)
    Result = tmp.Bits(pPin)
End Function

{
****************************************************************************
* Name    : ReadPinPortB                                                   *
* Purpose : Returns Value of a specified PortB Pin                         *
****************************************************************************
} 
Public Inline Function ReadPinPortB(pPin As Byte) As Bit
    Dim tmp As Byte
    tmp = ReadReg(GPIOB)
    Result = tmp.Bits(pPin)
End Function

{
****************************************************************************
* Name    : WritePortA                                                     *
* Purpose : Writes PortA Latch register with pData value                   *
****************************************************************************
} 
Public Inline Sub WritePortA(pData As Byte)
    WriteReg(OLATA, pData) // I prefer to write to latch instead of direct port 
                           // This is safer in case if some pins were inputs    
End Sub

{
****************************************************************************
* Name    : WritePortB                                                     *
* Purpose : Writes PortB Latch register with pData value                   *
****************************************************************************
} 
Public Inline Sub WritePortB(pData As Byte)
    WriteReg(OLATB, pData)                            
End Sub

{
****************************************************************************
* Name    : SetPinPortA                                                    *
* Purpose : Sets PortA pin given by pPin to High state                     *
****************************************************************************
} 
Public Sub SetPinPortA(pPin As Byte)
    Dim tmp As Byte
    tmp = ReadPortA() Or (1<<pPin)
    WriteReg(OLATA, tmp)
End Sub

{
****************************************************************************
* Name    : SetPinPortB                                                    *
* Purpose : Sets PortB pin given by pPin to High state                     *
****************************************************************************
} 
Public Sub SetPinPortB(pPin As Byte)
    Dim tmp As Byte
    tmp = ReadPortB() Or (1<<pPin)
    WriteReg(OLATB, tmp)
End Sub

{
****************************************************************************
* Name    : SetPinPortAB                                                   *
* Purpose : Sets PortA or PortB pin given by pPin to High state            *
*           Pins are numbered from 0 to 15.                                * 
****************************************************************************
} 
Public Sub SetPinPortAB(pPin As Byte)
    If pPin<8 Then
      SetPinPortA(pPin)
    Else
      SetPinPortB(pPin)
    EndIf
End Sub

{
****************************************************************************
* Name    : ClearPinPortA                                                    *
* Purpose : Sets PortA pin given by pPin to Low state                     *
****************************************************************************
} 
Public Sub ClearPinPortA(pPin As Byte)
    Dim tmp As Byte
    tmp = ReadPortA() And Not (1<<pPin)
    WriteReg(OLATA, tmp)
End Sub

{
****************************************************************************
* Name    : ClearPinPortB                                                 *
* Purpose : Sets PortB pin given by pPin to Low state                     *
****************************************************************************
} 
Public Sub ClearPinPortB(pPin As Byte)
    Dim tmp As Byte
    tmp = ReadPortB() And Not (1<<pPin)
    WriteReg(OLATB, tmp)
End Sub

{
****************************************************************************
* Name    : ClearPinPortAB                                                   *
* Purpose : Clears PortA or PortB pin given by pPin to low state           *
*           Pins are numbered from 0 to 15.                                * 
****************************************************************************
} 
Public Sub ClearPinPortAB(pPin As Byte)
    If pPin<8 Then
      ClearPinPortA(pPin)
    Else
      ClearPinPortB(pPin)
    EndIf
End Sub

{
****************************************************************************
* Name    : SetTrisPortA                                                   *
* Purpose : Sets PortA pins direction 0=Output 1=Input                     *
****************************************************************************
} 
Public Inline Sub SetTrisPortA(pDirection As Byte)
    WriteReg(IODIRA, pDirection) 
    DirRegA = pDirection
End Sub

{
****************************************************************************
* Name    : SetTrisPortB                                                   *
* Purpose : Sets PortB pins direction 0=Output 1=Input                     *
****************************************************************************
} 
Public Inline Sub SetTrisPortB(pDirection As Byte)
    WriteReg(IODIRB, pDirection)
    DirRegB = pDirection
End Sub

{
****************************************************************************
* Name    : GetTrisPortA                                                   *
* Purpose : Gets PortA pins direction 0=Output 1=Input                     *
****************************************************************************
} 
Public Inline Function GetTrisPortA() As Byte
    result = DirRegA   
End Function

{
****************************************************************************
* Name    : GetTrisPortB                                                   *
* Purpose : Gets PortB pins direction 0=Output 1=Input                     *
****************************************************************************
} 
Public Inline Function GetTrisPortB() As Byte
    result = DirRegB
End Function

{
****************************************************************************
* Name    : SetPinTrisPortA                                                *
* Purpose : Sets the Tris of a Single Pin from PortA pins  0=Output 1=Input*
****************************************************************************
} 
Public Sub SetPinTrisPortA(pPin As Byte, pDirection As Bit)
    DirRegA.bits(pPin) = pDirection
    WriteReg(IODIRA, DirRegA)
End Sub

{
****************************************************************************
* Name    : SetPinTrisPortB                                                *
* Purpose : Sets the Tris of a Single Pin from PortB pins  0=Output 1=Input*
****************************************************************************
} 
Public Sub SetPinTrisPortB(pPin As Byte, pDirection As Bit)
    DirRegB.bits(pPin) = pDirection
    WriteReg(IODIRB, DirRegB)
End Sub

{
****************************************************************************
* Name    : SetPinTrisPortAB                                               *
* Purpose : Sets the Tris of a Single Pin from PortA or PortB              *
*          0=Output 1=Input   Pins are numbered from 0 to 15               *
****************************************************************************
} 
Public Sub SetPinTrisPortAB(pPin As Byte, pDirection As Bit)
    If pPin<8 Then
      SetPinTrisPortA(pPin, pDirection)
    Else
      SetPinTrisPortB(pPin, pDirection)
    End If
End Sub

{
****************************************************************************
* Name    : SetPullUpsPortA                                                *
* Purpose : Sets the Pullups resistors  for any Pin of PortA               *
*           Pullups can be enabled/disabled independently for any pin of   *
*           the port 0=Disabled  1=Enabled                                 *
****************************************************************************
} 
Public Inline Sub SetPullUpsPortA(pPullUps As Byte)
    WriteReg(GPPUA, pPullUps)
End Sub

{
****************************************************************************
* Name    : SetPullUpsPortB                                                *
* Purpose : Sets the Pullups resistors  for any Pin of PortB               *
*           Pullups can be enabled/disabled independently for any pin of   *
*           the port 0=Disabled  1=Enabled                                 *
****************************************************************************
} 
Public Inline Sub SetPullUpsPortB(pPullUps As Byte)
    WriteReg(GPPUB, pPullUps)
End Sub


{
****************************************************************************
* Name    : InitChip                                                       *
* Purpose : Initialize the MCP23S17 CHIP                                   *
****************************************************************************
} 
Public Sub InitChip()
#if IsOption(MCP_RST)    
    // Reset the Chip
    RST = 0 
    DelayMS(10)
    RST =   1
    DelayMS(10)
#EndIf       
    // At initialization, we must force CHIP_ADDRESS to "000" regardless of 
    // actual MCPCHIP_ADDRESS value. After initialization, once IOCON.HAEN 
    // enabled, MCPCHIP_ADDRESS is taken into account by the chip.
    CS = 0
    //DelayUS(10)
    WriteByte(CONTROL_BYTE_MASK_W And %11110001)   // Force ChipAdd to 000
    WriteByte(IOCONA) // writing to IOCONA writes also to IOCONB     
    WriteByte(CONTROL_IOCON)
    CS = 1
    DelayMS(10)
End Sub

{
****************************************************************************
* Name    : Initialize                                                     *
* Purpose : Initializes module and included modules                        *
****************************************************************************
} 
Public Sub Initialize()  

#If MCPSPI_SOFTWARE    
    SSPI.Initialize
    SSPI.SetClock(spiIdleHigh)
#Else
    SPI.SetAsMaster(spiOscDiv4)        
    SPI.SetClock(spiIdleHigh, spiRisingEdge)
    SPI.SetSample(spiSampleMiddle)
    EnableSPI()            
#EndIf    
    Output(CS)     
#If IsOption(MCP_RST)        
    Output(RST)
#EndIf    
    High(CS)
    High(RST)      
    InitChip()
    SetTrisPortA($ff) 'intializes PortA direction register into RegDirA local variable
    SetTrisPortB($ff) 'caches PortB direction register into RegDirB local variable
    //SetPullUpsPortA($FF)
    //SetPullUpsPortB($00)
End Sub

Initialize()

Example Code

{
*****************************************************************************
*  Name    : test for Single chip MCP23S17 driver                           *
*  Author  : Ahmed Lazreg  (octal@pocketmt.com)                             *
*            http://www.pocketmt.com                                        *
*  Notice  : Copyright (c) 2008 Pocket MicroTechnics                        *
*          : All Rights Reserved                                            *
*  Date    : 10/08/2008                                                     *
*  Version : 1.0                                                            *
*  Notes   :                                                                *
*          :                                                                *
*****************************************************************************
}

Device = 18F452
Clock  = 20

#option MCPSPI_SOFTWARE = false // set to true to use Software SPI
#option MCPCHIP_ADDRESS = 0     //Physical address of the MCP23S17 chip

Include "mcp23s17.bas"


Dim dataIn As Byte

MCP23S17.SetTrisPortA($00)    // You can set the tris of anyPin separately
MCP23S17.SetTrisPortB($FF)
MCP23S17.SetPullUpsPortB($FF)  // depending on your config, you can activate pullups
MCP23S17.SetPullUpsPortA($00)  // or disable them for "any PIN" independently

While true
    dataIn = MCP23S17.ReadPortB()
    MCP23S17.WritePortA(dataIn)
Wend

Multiple MCP23S17 chips module

Connection to hardware SPI is shown in following diagram (for software SPI you can use Swordfish Options to modify pins affectation).

Basically it's the same as the Single Chip module, you just have to pass as first param to all functions the chip address.

Module Code

{
*************************************************************************
*  Name    : MCP23S17_MULT.BAS                                          *
*  Author  : Ahmed Lazreg  (octal@pocketmt.com)                         *
*            http://www.pocketmt.com                                    *
*  Notice  : Copyright (c) 2006 Pocket MicroTechnics                    *
*          : All Rights Reserved                                        *
*  Date    : 01/03/2008                                                 *
*  Version : 1.0                                                        *
*  Modified:                                                            *
*  Notes   : This module handles multiple MCP23S17 - 16 pins I/O port   *
*            expander from Microchip as 2x8 bits PortA/PortB.           *
*            The module shares the same Clk/SDI/SDO/CS/RST pins,        *
*            selection is done using chip address pins.                 *    
*************************************************************************
}


Module MCP23S17_MULT

// hardware or software SPI...
#if IsOption(MCPSPI_SOFTWARE) And Not (MCPSPI_SOFTWARE in (true, false))
   #error MCPSPI_SOFTWARE, "Invalid option, must be TRUE or FALSE."
#endif
#option MCPSPI_SOFTWARE = false


#if MCPSPI_SOFTWARE=true // use software SPI...
Include "sspi.bas"
#else // use hardware SPI...
Include "SPI.BAS"
#EndIf

#Option MCP_CS  = PORTC.1
#Option MCP_RST = PORTC.2   // may be commented If RST is Not used

// validate SCK pin...
#If IsOption(MCP_CS) And Not IsValidPortPin(MCP_CS) 
   #Error MCP_CS, "Invalid option. MCP_CS must be a valid port pin."
#EndIf

// validate RST pin...
#If IsOption(MCP_RST) And Not IsValidPortPin(MCP_RST) 
   #Error MCP_RST, "Invalid option. MCP_RST must be a valid port pin."
#EndIf

Public Dim 
   CS As MCP_CS.MCP_CS@   // Chip select

#if IsOption(MCP_RST)
Public Dim
   RST As MCP_RST.MCP_RST@    // RST pin
#endif

//----------------------------------------------------------------------------
//Please ignore these options ... only here for the GLCD through SPI stuff...
#option MCPPortA0 = 0
#option MCPPortA1 = 1
#option MCPPortA2 = 2
#option MCPPortA3 = 3
#option MCPPortA4 = 4
#option MCPPortA5 = 5
#option MCPPortA6 = 6
#option MCPPortA7 = 7

#option MCPPortB0 = 8
#option MCPPortB1 = 9
#option MCPPortB2 = 10
#option MCPPortB3 = 11
#option MCPPortB4 = 12
#option MCPPortB5 = 13
#option MCPPortB6 = 14
#option MCPPortB7 = 15
//----------------------------------------------------------------------------


Public Const
  IODIRA   = $00, 
  IODIRB   = $01,
  IPOLA    = $02,
  IPOLB    = $03,
  GPINTENA = $04,
  GPINTENB = $05,
  DEFVALA  = $06,
  DEFVALB  = $07,
  INTCONA  = $08,
  INTCONB  = $09,
  IOCONA   = $0A,
  IOCONB   = $0B,
  GPPUA    = $0C,
  GPPUB    = $0D,
  INTFA    = $0E,
  INTFB    = $0F,
  INTCAPA  = $10,
  INTCAPB  = $11,
  GPIOA    = $12,
  GPIOB    = $13,
  OLATA    = $14,
  OLATB    = $15 

//Control Byte For SPI operations [0100][A2][A1][A0][R=1/W=0]
Private Const CONTROL_BYTE_MASK_R As Byte = %01000001
Private Const CONTROL_BYTE_MASK_W As Byte = %01000000

Private Const CONTROL_IOCON = %00011000 // Init value for IOCON - bits details in following comment

{
 Reg IOCON = [BANK][MIRROR][SEQOP][DISSLW][HAEN][ODR][INTPOL][-Unused]

 bit 7 BANK: Controls how the registers are addressed
             1 = The registers associated with each port are separated into different banks
             0 = The registers are in the same bank (addresses are sequential)
 bit 6 MIRROR: INT Pins Mirror bit
             1 = The INT pins are internally connected
             0 = The INT pins are not connected. INTA is associated with PortA and INTB is associated with PortB
 bit 5 SEQOP: Sequential Operation mode bit.
             1 = Sequential operation disabled, address pointer does not increment.
             0 = Sequential operation enabled, address pointer increments.
 bit 4 DISSLW: Slew Rate control bit for SDA output.
             1 = Slew rate disabled.
             0 = Slew rate enabled.
 bit 3 HAEN: Hardware Address Enable bit (MCP23S17 only).
             Address pins are always enabled on MCP23017.
             1 = Enables the MCP23S17 address pins.
             0 = Disables the MCP23S17 address pins.
 bit 2 ODR: This bit configures the INT pin as an open-drain output.
             1 = Open-drain output (overrides the INTPOL bit).
             0 = Active driver output (INTPOL bit sets the polarity).
 bit 1 INTPOL: This bit sets the polarity of the INT output pin.
             1 = Active-high.
             0 = Active-low.
 bit 0 Unimplemented: Read as 0.

}

{
****************************************************************************
* Name    : WriteReg                                                       *
* Purpose : Writes pData byte to register address given by pRegAddress     *
*           pChipAddress is the address of concerned MCP23S17 chip         *
****************************************************************************
} 
Public Sub WriteReg(ByVal pChipAddress, pRegAddress As Byte, pData As Byte)
    Dim CntByte As Byte
    CntByte = CONTROL_BYTE_MASK_W Or (pChipAddress << 1) 
    CS = 0
    //DelayUS(10)  'delays can/should be added if 
                   'you have problems at High Clock values
    WriteByte(CntByte)
    WriteByte(pRegAddress)
    WriteByte(pData)
    CS = 1
    //DelayUS(10) 
End Sub
{
****************************************************************************
* Name    : ReadReg                                                        *
* Purpose : Read register address given by pRegAddress                     *
****************************************************************************
} 
Public Function ReadReg(ByVal pChipAddress As Byte, ByVal pRegAddress As Byte) As Byte
    Dim CntByte As Byte
    CntByte = CONTROL_BYTE_MASK_R Or (pChipAddress << 1) 
    CS = 0
    //DelayUS(10)  'delays can/should be added if 
                   'you have problems at High Clock values   
    WriteByte(CntByte)
    WriteByte(pRegAddress)
    Result = ReadByte()
    CS = 1  
    //DelayUS(10) 
End Function
{
****************************************************************************
* Name    : ReadPortA                                                      *
* Purpose : Returns PortA reg value                                        *
****************************************************************************
} 
Public Inline Function ReadPortA(ByVal pChipAddress As Byte) As Byte
    Result = ReadReg(pChipAddress, GPIOA)
End Function
{
****************************************************************************
* Name    : ReadPortB                                                      *
* Purpose : Returns PortB reg value                                        *
****************************************************************************
} 
Public Inline Function ReadPortB(ByVal pChipAddress As Byte) As Byte
    Result = ReadReg(pChipAddress, GPIOB)
End Function
{
****************************************************************************
* Name    : ReadPinPortA                                                   *
* Purpose : Returns Value of a specified PortA Pin                         *
****************************************************************************
} 
Public Inline Function ReadPinPortA(ByVal pChipAddress As Byte, pPin As Byte) As Bit
    Dim tmp As Byte
    tmp = ReadReg(pChipAddress, GPIOA)
    Result = tmp.Bits(pPin)
End Function
{
****************************************************************************
* Name    : ReadPinPortB                                                   *
* Purpose : Returns Value of a specified PortB Pin                         *
****************************************************************************
} 
Public Inline Function ReadPinPortB(ByVal pChipAddress As Byte, pPin As Byte) As Bit
    Dim tmp As Byte
    tmp = ReadReg(pChipAddress, GPIOB)
    Result = tmp.Bits(pPin)
End Function
{
****************************************************************************
* Name    : WritePortA                                                     *
* Purpose : Writes PortA Latch register with pData value                   *
****************************************************************************
} 
Public Inline Sub WritePortA(ByVal pChipAddress, pData As Byte)
    WriteReg(pChipAddress, OLATA, pData) // I prefer to write to latch instead of direct port 
                                         // This is safer in case if some pins were inputs    
End Sub
{
****************************************************************************
* Name    : WritePortB                                                     *
* Purpose : Writes PortB Latch register with pData value                   *
****************************************************************************
} 
Public Inline Sub WritePortB(ByVal pChipAddress, pData As Byte)
    WriteReg(pChipAddress, OLATB, pData) // I prefer to write to latch instead of direct port 
                                         // This is safer in case if some pins were inputs   
End Sub
{
****************************************************************************
* Name    : SetPinPortA                                                    *
* Purpose : Sets PortA pin given by pPin to High state                     *
****************************************************************************
} 
Public Sub SetPinPortA(ByVal pChipAddress, pPin As Byte)
    Dim tmp As Byte
    tmp = ReadPortA(pChipAddress) Or (1<<pPin)
    WriteReg(pChipAddress, OLATA, tmp) // I prefer to write to latch instead of direct port 
                                         // This is safer in case if some pins were inputs    
End Sub
{
****************************************************************************
* Name    : SetPinPortB                                                    *
* Purpose : Sets PortB pin given by pPin to High state                     *
****************************************************************************
} 
Public Sub SetPinPortB(ByVal pChipAddress, pPin As Byte)
    Dim tmp As Byte
    tmp = ReadPortB(pChipAddress) Or (1<<pPin)
    WriteReg(pChipAddress, OLATB, tmp) // I prefer to write to latch instead of direct port 
                                         // This is safer in case if some pins were inputs    
End Sub
{
****************************************************************************
* Name    : SetPinPortAB                                                   *
* Purpose : Sets PortA or PortB pin given by pPin to High state            *
*           Pins are numbered from 0 to 15.                                * 
****************************************************************************
} 
Public Sub SetPinPortAB(ByVal pChipAddress, pPin As Byte)
    If pPin<8 Then
      SetPinPortA(pChipAddress, pPin)
    Else
      SetPinPortB(pChipAddress, pPin)
    EndIf
End Sub
{
****************************************************************************
* Name    : ClearPinPortA                                                    *
* Purpose : Sets PortA pin given by pPin to Low state                     *
****************************************************************************
} 
Public Sub ClearPinPortA(ByVal pChipAddress, pPin As Byte)
    Dim tmp As Byte
    tmp = ReadPortA(pChipAddress) And Not (1<<pPin)
    WriteReg(pChipAddress, OLATA, tmp) // I prefer to write to latch instead of direct port 
                                         // This is safer in case if some pins were inputs    
End Sub
{
****************************************************************************
* Name    : ClearPinPortB                                                 *
* Purpose : Sets PortB pin given by pPin to Low state                     *
****************************************************************************
} 
Public Sub ClearPinPortB(ByVal pChipAddress, pPin As Byte)
    Dim tmp As Byte
    tmp = ReadPortB(pChipAddress) And Not (1<<pPin)
    WriteReg(pChipAddress, OLATB, tmp) // I prefer to write to latch instead of direct port 
                                         // This is safer in case if some pins were inputs    
End Sub
{
****************************************************************************
* Name    : ClearPinPortAB                                                   *
* Purpose : Clears PortA or PortB pin given by pPin to low state           *
*           Pins are numbered from 0 to 15.                                * 
****************************************************************************
} 
Public Sub ClearPinPortAB(ByVal pChipAddress, pPin As Byte)
    If pPin<8 Then
      ClearPinPortA(pChipAddress, pPin)
    Else
      ClearPinPortB(pChipAddress, pPin)
    EndIf
End Sub
{
****************************************************************************
* Name    : SetTrisPortA                                                   *
* Purpose : Sets PortA pins direction 0=Output 1=Input                     *
****************************************************************************
} 
Public Inline Sub SetTrisPortA(ByVal pChipAddress, pDirection As Byte)
    WriteReg(pChipAddress, IODIRA, pDirection) 
End Sub
{
****************************************************************************
* Name    : SetTrisPortB                                                   *
* Purpose : Sets PortB pins direction 0=Output 1=Input                     *
****************************************************************************
} 
Public Inline Sub SetTrisPortB(ByVal pChipAddress, pDirection As Byte)
    WriteReg(pChipAddress, IODIRB, pDirection)
End Sub
{
****************************************************************************
* Name    : GetTrisPortA                                                   *
* Purpose : Gets PortA pins direction 0=Output 1=Input                     *
****************************************************************************
} 
Public Inline Function GetTrisPortA(ByVal pChipAddress As Byte) As Byte
    result = ReadReg(pChipAddress, IODIRA) 
End Function
{
****************************************************************************
* Name    : GetTrisPortB                                                   *
* Purpose : Gets PortB pins direction 0=Output 1=Input                     *
****************************************************************************
} 
Public Inline Function GetTrisPortB(ByVal pChipAddress As Byte) As Byte
    result = ReadReg(pChipAddress, IODIRB) 
End Function
{
****************************************************************************
* Name    : SetPinTrisPortA                                                *
* Purpose : Sets the Tris of a Single Pin from PortA pins  0=Output 1=Input*
****************************************************************************
} 
Public Sub SetPinTrisPortA(ByVal pChipAddress As Byte, pPin As Byte, pDirection As Bit)
    Dim PortTris As Byte
    PortTris = ReadReg(pChipAddress, IODIRA)
    PortTris.Bits(pPin) = pDirection
    WriteReg(pChipAddress, IODIRA, PortTris)
End Sub
{
****************************************************************************
* Name    : SetPinTrisPortB                                                *
* Purpose : Sets the Tris of a Single Pin from PortB pins  0=Output 1=Input*
****************************************************************************
} 
Public Sub SetPinTrisPortB(ByVal pChipAddress As Byte, pPin As Byte, pDirection As Bit)
    Dim PortTris As Byte
    PortTris = ReadReg(pChipAddress, IODIRB)
    PortTris.Bits(pPin) = pDirection
    WriteReg(pChipAddress, IODIRB, PortTris)
End Sub
{
****************************************************************************
* Name    : SetPinTrisPortAB                                               *
* Purpose : Sets the Tris of a Single Pin from PortA or PortB              *
*          0=Output 1=Input   Pins are numbered from 0 to 15               *
****************************************************************************
} 
Public Sub SetPinTrisPortAB(ByVal pChipAddress As Byte, pPin As Byte, pDirection As Bit)
    If pPin<8 Then
      SetPinTrisPortA(pChipAddress, pPin, pDirection)
    Else
      SetPinTrisPortB(pChipAddress, pPin, pDirection)
    End If
End Sub
{
****************************************************************************
* Name    : SetPullUpsPortA                                                *
* Purpose : Sets the Pullups resistors  for any Pin of PortA               *
*           Pullups can be enabled/disabled independently for any pin of   *
*           the port 0=Disabled  1=Enabled                                 *
****************************************************************************
} 
Public Inline Sub SetPullUpsPortA(ByVal pChipAddress, pPullUps As Byte)
    WriteReg(pChipAddress, GPPUA, pPullUps)
End Sub
{
****************************************************************************
* Name    : SetPullUpsPortB                                                *
* Purpose : Sets the Pullups resistors  for any Pin of PortB               *
*           Pullups can be enabled/disabled independently for any pin of   *
*           the port 0=Disabled  1=Enabled                                 *
****************************************************************************
} 
Public Inline Sub SetPullUpsPortB(ByVal pChipAddress, pPullUps As Byte)
    WriteReg(pChipAddress, GPPUB, pPullUps)
End Sub
{
****************************************************************************
* Name    : InitChip                                                       *
* Purpose : Initialize ALL the MCP23S17 CHIP MODULES on the bus            *
****************************************************************************
} 
Public Sub InitChip()
#if IsOption(MCP_RST)    
    // Reset the Chips
    RST = 0 
    DelayMS(10)
    RST = 1
    DelayMS(10)
#EndIf       
    // At initialization, IOCON.HAEN is not set, so chip Addresses are not
    // active. All chips are considered with CHIP_ADDRESS set to "000" 
    // regardless of the state of their Adress [A2 A1 A0] pins.
    // After initialization, once IOCON.HAEN enabled, MCPCHIP_ADDRESS is 
    // taken into account by the chip.
    WriteReg(%000, IOCONA, CONTROL_IOCON) // writing to IOCONA writes also to IOCONB     
    DelayMS(10)
End Sub
{
****************************************************************************
* Name    : Initialize                                                     *
* Purpose : Initializes module and included modules                        *
****************************************************************************
} 
Public Sub Initialize()  

#If MCPSPI_SOFTWARE    
    SSPI.Initialize
    SSPI.SetClock(spiIdleHigh)
#Else
    SPI.SetAsMaster(spiOscDiv4)
    SPI.SetClock(spiIdleHigh, spiRisingEdge)
    SPI.SetSample(spiSampleMiddle)
    EnableSPI()    
#EndIf    
    Output(CS)     
#If IsOption(MCP_RST)        
    Output(RST)
#EndIf       
    High(CS)
    High(RST)      
    InitChip()      
End Sub

Initialize()

Example Code

{
*****************************************************************************
*  Name    : test for Multichip  MCP23S17 driver                            *
*  Author  : Ahmed Lazreg  (octal@pocketmt.com)                             *
*            http://www.pocketmt.com                                        *
*  Notice  : Copyright (c) 2008 Pocket MicroTechnics                        *
*          : All Rights Reserved                                            *
*  Date    : 10/08/2008                                                     *
*  Version : 1.0                                                            *
*  Notes   :                                                                *
*          :                                                                *
*****************************************************************************
}

Device = 18F452
Clock  = 20

#option MCPSPI_SOFTWARE = false  

Include "mcp23s17_mult.bas"


Dim i1, i2 As Word

Const mcp1 = %000,  // First chip address
      mcp2 = %111   // Second chip address

MCP23S17_MULT.SetTrisPortA(mcp1,0)
MCP23S17_MULT.SetTrisPortB(mcp1,$FF)
MCP23S17_MULT.SetPullUpsPortB(mcp1,$FF)

MCP23S17_MULT.SetTrisPortA(mcp2,0)
MCP23S17_MULT.SetTrisPortB(mcp2,$FF)
MCP23S17_MULT.SetPullUpsPortB(mcp2,$FF)

While true
    i1 = MCP23S17_MULT.ReadPortB(mcp1)
    MCP23S17_MULT.WritePortA(mcp1,i1)

    i2 = MCP23S17_MULT.ReadPortB(mcp2)
    MCP23S17_MULT.WritePortA(mcp2,i2)
Wend

Modified Hardware SPI module

{
****************************************************************************
*  Name    : SPI.BAS                                                       *
*  Author  : John Barrat                                                   *
*          : David John Barker                                             *
*  Notice  : Copyright (c) 2006 Mecanique                                  *
*          : All Rights Reserved                                           *
*  Date    : 26/05/2006                                                    *
*  Version : 1.1 Changed WriteByte() param to WREG for silicon workaround  *                                            
*          : 1.0 Release                                                   *
*          : ============================================================= *
*  Notes   : This post was made on the Swordfish forum by Steve B on       *
*          : April 8th 2007...                                             *
*          : I was having some trouble with the SPI module as well. I am   *
*          : using an 18F2620, Silicon A4, and was getting some            *
*          : inconsistent reads/writes, especially when doing a lot of     *
*          : TX/RX one after another. After looking at the errata, I       *
*          : changed the ReadByte and WriteByte routines as follows...     *
*          :                                                               *
*          : Public Function ReadByte() As SSPBuffer                       *
*          :    SSPIF = 0                                                  *
*          :    SSPBuffer = 0                                              *
*          :    Repeat                                                     *
*          :       ClrWDT                                                  *
*          :    Until SSPIF = 1                                            *
*          : End Function                                                  *
*          :                                                               *
*          : Public Sub WriteByte(pData As WREG)                           *
*          :    SSPIF = 0                                                  *
*          :    SSPBuffer = pData                                          *
*          :    Repeat                                                     *
*          :       ClrWDT                                                  *
*          :    Until SSPIF = 1                                            *
*          : End Sub                                                       *
*          :                                                               *
*          : Now they use the SSP Interrupt flag instead of the Buffer     *
*          : Full flag (SSPSTAT.0). I also added a subroutine to enable    *
*          : the SPI module                                                *
*          :                                                               *
*          : Public Sub EnableSPI()                                        *
*          :    Dim pDummy As Byte                                         *
*          :    Enabled = true                                             *
*          :    pDummy = SSPBuffer                                         *
*          :    SSPIF = 0                                                  *
*          : End Sub                                                       *
****************************************************************************
}
Module SPI

Include "system.bas"

// map registers to SSP(x)
#if _mssp = 0
   #error _device + " does not support MSSP"

// single SSP...
#elseif _mssp = 1
Dim
   SSPControl1 As SSPCON1,
   SSPStatus   As SSPSTAT,
   SSPBuffer   As SSPBUF,
   SCK As PORTC.3,
   SDI As PORTC.4,
   SDO As PORTC.5,  
   _SS As PORTA.5

// has more than one SSP module...   
#else
Dim                         // -> MSSP2
   SSPControl1 As SSP1CON1, //    as SSP2CON1
   SSPStatus   As SSP1STAT, //    as SSP2STAT
   SSPBuffer   As SSP1BUF,  //    as SSP2BUF
   SCK As PORTC.3,
   SDI As PORTC.4,
   SDO As PORTC.5,  
   _SS As PORTA.5
#endif

// SSPSTAT bitnames
Public Dim
   BF  As SSPStatus.0,            // buffer full (receive and transmit)    
   SMP As SSPStatus.7,            // read sample mode
   CKE As SSPStatus.6             // clock edge control

// SSPCON1 bitnames, master mode only...
Public Dim
   WCOL  As SSPControl1.7,        // write collision Detect
   SSPOV As SSPControl1.6,        // receive overflow
   SSPEN As SSPControl1.5,        // synchronous receive enable
   CKP   As SSPControl1.4,        // clock polarity

   // synchronous mode select bits, %00XX for master mode
   SSPM3 As SSPControl1.3,        // always zero
   SSPM2 As SSPControl1.2,        // slave Mode
   SSPM1 As SSPControl1.1,        // clock Mode (MSB)
   SSPM0 As SSPControl1.0         // clock Mode (LSB)

// interrupt flag
Public Dim
   SSPIF As PIR1.3



Public Const
   spiOscDiv4              = 0,   // master mode FOSC/4
   spiOscDiv16             = 1,   // master mode FOSC/16
   spiOscDiv64             = 2,   // master mode FOSC/64
   spiOscTimer2            = 3,   // master mode TMR2 provides clock
   spiSlaveSSEnabled       = 4,   // slave mode slave synch enabled
   spiSlaveSSDisabled      = 5,   // slave mode slave synch disabled

   // clock idle and edge settings  (SPI Mode compatible)
   spiRisingEdge           = $40, // data transmitted on rising edge of clock
   spiFallingEdge          = $00, // data transmitted on falling edge of clock
   spiIdleHigh             = $10, // idle state for clock is high
   spiIdleLow              = $00, // idle state for clock is low

   // RX data sampling settings  (SSPStatus.6 - SMP)
   spiSampleEnd            = $80, // input data sampled at end of data output time
   spiSampleMiddle         = $00  // input data sampled at middle of data output time

// local helper aliases...   
Dim
   FBufferIsFull As SSPStatus.Booleans(0)      // BF as boolean

// public aliases...
Public Dim
   Enabled As SSPControl1.Booleans(5),         // SSPEN as boolean
   Overflow As SSPControl1.Booleans(6),        // SSPOV as boolean
   WriteCollision As SSPControl1.Booleans(7)   // WCOL as boolean
{
****************************************************************************
* Name    : SetAsMaster                                                    *
* Purpose : Set SPI to master mode                                         *
*         : spiOscDiv4 (DEFAULT), spiOscDiv16, spiOscDiv64, spiOscTmr2     *
****************************************************************************
} 
Public Sub SetAsMaster(pMode As Byte = spiOscDiv4)
   SSPControl1 = SSPControl1 And $F0
   SSPControl1 = SSPControl1 Or pMode
   Output(SDO)
   Output(SCK)  
End Sub
{
****************************************************************************
* Name    : SetAsSlave                                                     *
* Purpose : Set SPI to slave mode                                          *
*         : spiSlaveSSEnabled, spiSlaveSSDisabled (DEFAULT)                *
****************************************************************************
} 
Public Sub SetAsSlave(pMode As Byte = spiSlaveSSDisabled)
   SSPControl1 = SSPControl1 And $F0
   SSPControl1 = SSPControl1 Or pMode
   Output(SDO)
   Input(SCK)
   Input(_SS)  
End Sub
{
****************************************************************************
* Name    : SetSample                                                      *
* Purpose : Sets when the SPI input data in sampled                        *
*         : spiSampleEnd, spiSampleMiddle. For slave mode, sample should   *
*         : always be spiSampleMiddle                                      *
****************************************************************************
} 
Public Sub SetSample(pSample As Byte)
   SSPStatus = SSPStatus And $7F
   SSPStatus = SSPStatus Or pSample
End Sub
{
****************************************************************************
* Name    : SetClock                                                       *
* Purpose : Set SPI idle state and data transmission clock edge            *
*         : pIdle -> spiIdleHigh, spiIdleLow                               *
*         : pEdge -> spiRisingEdge, spiFallingEdge                         *
****************************************************************************
} 
Public Sub SetClock(pIdle As Byte, pEdge As Byte)
   SSPControl1 = SSPControl1 And $EF
   SSPControl1 = SSPControl1 Or pIdle
   If pIdle = spiIdleHigh Then
      pEdge = Not pEdge And $BF
   EndIf 
   SSPStatus = SSPStatus And $BF  
   SSPStatus = SSPStatus Or pEdge
End Sub
{
****************************************************************************
* Name    : ReadByte                                                       *
* Purpose : Read a single byte from the SPI bus                            *
****************************************************************************
}         
'Public Function ReadByte() As SSPBuffer
'   SSPBuffer = 0
'   Repeat
'      ClrWDT
'   Until FBufferIsFull
'End Function


Public Function ReadByte() As  SSPBuffer
      SSPIF = 0 
      SSPBuffer = $FF 
      Repeat 
         ClrWDT 
      Until SSPIF = 1 
      ReadByte = SSPBuffer
End Function

Public Sub WriteByte(pData As Byte)
      SSPIF = 0 
      SSPBuffer = pData 
      Repeat 
         ClrWDT 
      Until SSPIF = 1 
End Sub

{
****************************************************************************
* Name    : WriteByte                                                      *
* Purpose : Write a single byte to the SPI bus                             *
****************************************************************************
}         
'Public Sub WriteByte(pData As WREG)
'   SSPBuffer = pData
'   Repeat
'      ClrWDT    
'   Until FBufferIsFull
'End Sub

Public Sub EnableSPI()                                     
    Dim pDummy As Byte                                         
    Enabled = true                                             
    pDummy = SSPBuffer                                         
    SSPIF = 0                                                  
End Sub