• Picģ Basic


  • Port expansion using the MCP23S17

    A standard 74HC595 shift register is an excellent choice if only output port expansion is required, however itís sometimes desirable to have inputs as well, which is where a dedicated port expander comes into its own. There are a whole range of port expanders available from virtually every semiconductor manufacturer, however, the MCP23S17 device is particularly popular at the time of writing this article.

    The MCP23S17 provides 16 I/O pins, all with interrupt generation capability and a host of configuration options. The MCP range also includes smaller devices with only 8 I/O pins, and either SPI or I2C communication interfaces. Using such a device does require more complex software to communicate over an SPI interface, However, the software is quite straightforward and doesnít take up much code space, which is important within the close confines of a microcontroller environment.

    The pinout of an MCP23S17 is shown below:


    Click on image for larger graphic

    The interface consists of the standard SCK, SI and SO communication pins (which are referred to as the SPI bus). The CS pin (Chip Select) must be driven low to place the device onto the SPI bus, and start a communication exchange.

    The SPI bus is normally used with multiple devices connected to the SCK, SI and SO pins, each having its own CS signal so that the devices can be placed onto the bus one at a time, and therefore not conflict with each other. Microchip have added an extremely useful feature to the SPI interface, which reduces the number of processor pins required to address up to eight devices.

    The MCP23S17 has three address pins, A0, A1 and A2, which must connect to either VDD (power) to select a 1, or to VSS (Gnd) to select a 0. These three inputs then define the 3-bit device address, which is specified in the initial SPI message sent to the device. If there are multiple devices connected in parallel on the SPI bus, each one for MCP23017 and MCP23S17 with a different setting on the address pins, only the device that matches the address specified in the SPI message will accept the message and respond. This way, with 16 I/O pins per device, and could conceivably have 128 additional I/O pins on the Amicus18 and only require four I/O pins on the microcontroller, more than enough for even the most challenging application!

    MCP23S17 connections
    The typical connections for an MCP23S17 are shown below, with the address set to 000. The four SPI bus signals connect to the microcontrollerís SPI peripheral pins. The CS signal is always driven from a standard I/O pin; because the SPI peripheral does not offer a special pin for this purpose when used as an SPI master.



    Click on image for larger graphic

    The MCP23S17 in the above circuit is configured to use address 000 (address pins A0,A1 and A2 are connected to the ground) and the LEDís are connected to GPB0 through to GPB7 (PortB). Weíll use the above circuit later when we eventually g
    et to the code examples. Also note the use of a simple resistor-capacitor to provide a reset to the device. The MCP23S17 is quite a complicated device and does have some logic circuits that require a clear reset prior to use. However, a simple circuit, such as 100nF and 10K Ohm will suffice.

    Itís also important to note that the LED implementation is not strictly by-the-book. This is in order to simplify the solderless breadboard layout, but is quite sufficient for a demonstration. In reality, each LED must have itís own, current limiting, 47 Ohm resistor.

    An Amicus18 Companion shield layout of the previous circuit is shown below:



    Click on image for larger graphic

    A top down view of the same layout is shown below:



    Click on image for larger graphic

    Internal workings of the MCP23S17
    There are 10 registers per port, and the datasheet for the MCP23S17 is surprisingly involved, amounting to 48 pages, and Microchip seem to have packed in more features than you can shake a stick at. Of course, we donít need to use all the features, and probably wonít. Itís nice to have the options, however.

    Configuration options
    The configuration registers break down into two groups of ten, duplicated between the two 8-bit ports, plus an overall control register. The common registers are suffixed with the letter A or B to indicate which port it is referring to. We will discuss the registers first, and then how to actually access them, which is more complicated than you might expect.

    IODIR: Determines whether a pin is an input or an output, just as the TRIS register does on a PIC microcontroller.

    IPOL: Input polarity. When a bit is set it causes the corresponding input port bit to be inverted in the input port register GPIO. Itís not easy to imagine of a use for this, but the default setting of the register is all zeros, having no effect, so you can simply ignore this register.

    GPINTEN: Interrupt-On-Change enable. This register enables one or more pins to act as an interrupt source. When an interrupt occurs it causes one of the interrupt pins to change state. This pin can be connected to an interrupt pin on the Amicus18ís microcontroller in order to receive immediate notification of a change of state on an input pin on the MCP23S17. This is very useful for keyboard or switch interfaces.

    DEFVAL: This register works in conjunction with the GPINTEN register. When the interrupt on change feature is enabled, this specifies the "default" expected value on the corresponding input pin. When the input pin takes on a value different to the value in DEFVAL, an interrupt is generated.

    INTCON: This register also works in conjunction with the GPINTEN register. It specifies whether an interrupt is generated when an input pin changes state (toggles) or when it changes to a value that is different to the default value specified in DEFVAL.

    GPPU: On a per pin basis, this allows an internal pull-up resistor to be enabled for the specified pins. This is an excellent feature for reducing external components, but bear in mind that the pull-up value is quite weak (approx 100k) and is not accurately defined, so it could vary considerably between different ICs. By default, the pull-ups are disabled, so make sure you enable them for any unused I/O pin.

    INTF: Interrupt status flag register; this register will indicate which bit, or bits, caused an interrupt. The bit will stay set in the register until the GPIO or INTCAP register has been read.

    INTCAP: Interrupt capture register; this records the status on the input pins at the instant at which the interrupt occurred. This can be very useful for recording data at the precise instant that the interrupt occurred. After all, it could take several milliseconds from detecting an interrupt to actually reading the status of the input ports.

    GPIO: Port register; reading this register will return the status of the port pins. A write will cause any output pins to take on the value specified in the write.

    OLAT: Output latch register; this register holds the value of data written to a port, either through this register or to the GPIO register. Itís equivalent to the LAT register on the Amicus18ís microcontroller.

    Overall Control Register
    There is an overall control register named IOCON, and there are two copies of this register in the memory map of the device. The extra one is there for convenience. Seven bits within this register control overall operation of the chip. These are:

    BANK: Selects whether the port A and port B registers are grouped separately, or accessed one after the other. This can help reduce the time it takes to read data from the two ports (if BANK = 0). There is little in the difference, so it is probably best leaving this at its default value of 0.

    MIRROR: Determines whether the chip provides a single interrupt output pin(MIRROR = 1) or one pin per 8-bit port.

    SEQOP: Determines whether the internal address pointer of the device increments by one when an access is made to the device. Turning off sequential operation (SEQOP = 1) increases the speed at which a single port can be read (or polled) since itís not required to constantly resend the device address and command.

    DISSLW: Slew rate control; Enabling this feature improves the reliability of SPI communication at high speeds or when there are many devices on the SPI bus.

    HAEN: Hardware address pins enable; Can disable the use of the three address selection pins. If this option is disabled, the three address pins must still be wired to one of the supply rails, and a value of 000 must still be specified for the address bits in the SPI message. Itís recommended to leave this feature enabled.

    ODR: Determines whether the interrupt output pins are push-pull or open drain. Open drain means that the pin can only drive the signal low, which means that several pins can be connected in parallel. If open drain is used, it must provide a pull-up resistor, since the device can only pull a signal low.

    INTPOL: When the ODR register is set to push-pull, this register determines whether the an interrupt has occurred state is a high or low level.

    Each I/O pin of the MCP23S17 can source up to 25mA, which is more than enough to drive an LED. However, keep in mind that the maximum current the device can manage overall is 150mA, so donít drive 16 LEDs at 25mA each! Most LEDs work quite adequately at 6mA to 8mA, so with care; 16 LEDs can be driven without buffering.

    Actually using the MCP23S17
    Unlike a 74HC595 8-bit shift register, writing and reading to and from the MCP23S17 must be done with a three byte operation cycle. First the SPI master has to send the MCP23S17 SPI slave ID with its physical address (set by A2, A1 and A0 pins) and the read or write instruction set or cleared accordingly. Secondly the SPI master has to inform the MCP23S17 which one of the control register addressís we want to use, and the lastly, we send or read the actual data.

    For clarity, weíre only interfacing to a single MCP23S17 device, with an address of 000.



    Click on image for larger graphic

    From the MCP23S17 SPI addressing diagram above we can see that we need to perform three SPI writes to instruct the device. To start communication, the CS line is pulled to ground (low), and the Opcode byte is sent, then the address of the register that is required is sent, and finally, the data is read or written, by sending a further byte. Remember that the SPI interface is capable of writing and reading at the same time. The CS line is then brought high to disable the SPI interface.

    The SPI_Write() and RSPI_Read() macros are used to do the write and read to and from the MCP23S17 device. Because an SPI slave cannot initiate a data transfer, it is still required to write to the device in order to read from it.

    It may seem strange that a write is performed in order to read from an SPI slave, however, this is because the master outputs the clock pulses, and everything is performed on the rising or falling edge of the clock pulses. The SPI slave clock line is a input, and it can never generate a clock pulse itself.

    The MCP23S17 has two general I/O ports named GPIOA and GPIOB, the default power-up condition of these is both I/O ports configured as an input. By changing each of the I/O direction registers (IODIRA and IODIRB) we can change each portís behaviour. This is the same methodology as changing the TRIS state on the Amicus18ís microcontroller, where a 1 is an input, and a 0 is an output.

    The program below shows a very simple interface to the MCP23S17. It will set PORTB to all outputs, then set them all high. If the program is used in conjunction with the previous Companion Shield layout, all the LEDs will illuminate.

    Code:
    '
    ' Simple interface to a Microchip MCP23S17 16-bit SPI I/O expander
    '
      Include "Amicus18.inc"         ' Configure the compiler to use the Amicus18 board (18F25K20 at 64MHz)
      Include "Amicus18_SPI.inc"     ' Load the Amicus18 SPI macros into the program
    
      Symbol MCP_CS = PORTC.2        ' MCP23S17 CS line connects here
    
      High MCP_CS                    ' Disable the SPI interface for now
    '  
    ' Open the SPI  peripheral:
    '                          Fosc/16 (4MHz operation)
    '                          SPI mode 0:0 
    '                          Sample at the end of the clock 
      OpenSPI(SPI_FOSC_16, MODE_00, SMPEND)   
    '  
    ' Initialise the MCP23S17  
    ' At initialisation, Chip_Address must be forced to "000"
    ' regardless of actual Mcpchip_Address value. 
    ' After initialisation, once IOCON.HAEN is enabled, 
    ' Mcpchip_Address is taken into account by the chip.
    '
      Clear MCP_CS           ' Enable the SPI interface
      DelayCS 1              ' A one cycle delay before starting
      SPI_Write(%11110001)   ' Force ChipAdd to 000
      SPI_Write($0A)         ' Writing to IOCONA writes also to IOCONB
      SPI_Write(%00011000)   ' Init value for IOCON
      Set MCP_CS             ' Disable the SPI interface
    '  
    ' Write to the TRIS of PORTB and set it to all outputs
    '
      Clear MCP_CS           ' Enable the SPI interface
      DelayCS 1              ' A one cycle  delay before starting
      SPI_Write(%01000000)   ' Target address 000 and set the R/W bit to Write(0)
      SPI_Write($01)         ' We want to access the IODIRB register
      SPI_Write(%00000000)   ' Set to all outputs
      Set MCP_CS             ' Disable the SPI interface  
     ' 
    ' Write to the Latch of PORTB
    '
      Clear MCP_CS           ' Enable the SPI interface
      DelayCS 1              ' A one cycle  delay before starting
      SPI_Write(%01000000)   ' Target address 000 and set the R/W bit to Write(0)
      SPI_Write($15)         ' We want to access the OLATB register
      WriteSPI(%11111111)    ' Set all outputs high
      Set MCP_CS             ' Disable the SPI interface
      Stop
    The above program only demonstrates output operations on the MCP23S17. In order to demonstrate inputs, we can use the same companion shield layout, but with the addition of a fly lead.

    An appended circuit diagram for input demonstration is shown below:



    Click on image for larger graphic

    The layout of the above circuit is shown below:



    Click on image for larger graphic

    And a top down view of the same layout looks like this:


    Click on image for larger graphic

    The program below can be used with the above circuit and layouts. It demonstrates both inputs and outputs:

    Code:
    ' 
    ' Demonstrate input operation on an MCP23S17 16-bit SPI I/O expander 
    '
      Include "Amicus18.inc"    ' Configure the compiler to use the Amicus18 board (18F25K20 at 64MHz)
      Include "Amicus18_SPI.inc"   ' Load the Amicus18 SPI macros into the program
      Symbol MCP_CS = PORTC.2      ' MCP23S17 CS line connects here
      Dim bTemp As Byte            ' Variable used to hold the input value
    ' 
    ' Main program starts here
    ' 
      High MCP_CS                  ' Disable the SPI interface for now
    '
    ' Open the SPI peripheral:
    '                          Fosc/16 (4MHz operation)
    '                          SPI mode 0:0
    '                          Sample at the end of the clock
      OpenSPI(SPI_FOSC_16,  MODE_00, SMPEND)  
    ' 
    ' Initialise the MCP23S17   
    ' At initialisation, Chip_Address must be forced to "000" 
    ' regardless of actual Mcpchip_Address value.  
    ' After initialisation, once IOCON.HAEN is enabled,  
    ' Mcpchip_Address is taken into account by the chip. 
    '
      Clear MCP_CS              ' Enable the SPI interface
      DelayCS 1                 ' A one cycle  delay before starting
      SPI_Write(%11110001)     ' Force ChipAdd to 000
      SPI_Write($0A)           ' Writing to IOCONA writes also to IOCONB
      SPI_Write(%00011000)     ' Init value for IOCON
      Set MCP_CS                ' Disable the SPI interface
    '
    ' Write to the TRIS of PORTB and set it to all outputs
    '
      Clear MCP_CS              ' Enable the SPI interface
      DelayCS 1                 ' A one cycle delay before starting
      SPI_Write(%01000000)     ' Target address 000 and set the R/W Line to Write(0)
      SPI_Write($01)           ' We want to access the IODIRB register
      SPI_Write(%00000000)     ' Set PORTB to all outputs
      Set MCP_CS                ' Disable the SPI interface  
    ' 
    ' Write to the TRIS of PORTA and set it to all inputs 
    '
      Clear  MCP_CS             ' Enable the SPI interface
      DelayCS 1                 ' A one cycle delay before starting
      SPI_Write(%01000000)     ' Target address 000 and set the R/W Line to Write(0)
      SPI_Write($00)           ' We want to access the IODIRB register
      SPI_Write(%11111111)     ' Set to all inputs
      Set MCP_CS                ' Disable the SPI interface 
    ' 
    ' Enable pull-up resistors on PORTA 
    '
      Clear  MCP_CS             ' Enable the SPI interface
      DelayCS 1                 ' A one cycle delay before starting
      WriteSPI(%01000000)      ' Target address 000 and set the R/W Line to Write(0)
      WriteSPI($0C)            ' We want to access the GPPUA register
      WriteSPI(% 11111111)     ' Set all outputs high
      Set MCP_CS                ' Disable the SPI interface 
    ' 
    ' Display the contents of PORTA inputs on PORTB outputs 
    '
      While 1 = 1               ' Create an infinite loop
        '
        ' Read the  condition of PORTA into variable bTemp
        '
        Clear MCP_CS            ' Enable the SPI interface
        DelayCS 1               ' A one cycle  delay before starting
        SPI_Write(%01000001)   ' Target address 000 and set the R/W line to Read(1)
        SPI_Write($12)         ' We want to access the GPIOA register
        bTemp = ReadSPI()      ' Read from the SPI interface
        Set MCP_CS              ' Disable the SPI interface
        '
        ' Write the  contents of bTemp to the Latch of PORTB
        '
        Clear MCP_CS            ' Enable the SPI interface
        DelayCS 1               ' A one cycle delay before starting
        SPI_Write(%01000000)   ' Target address 000 and set the R/W Line to Write(0)
        SPI_Write($15)         ' We want to access the OLATB register
        SPI_Write(~bTemp)      ' Write complement of bTemp to the latches of PORTB
        Set MCP_CS              ' Disable the SPI interface
        DelayMs 20              ' A small delay within the loop
      Wend
    
    Moving the flying lead from one pin to another of the MCP23S17 deviceís PORTA (pins 21 to 28), will illuminate the appropriate LED on the light bar.

    The video below shows the program in operation using the Isis simulator:


    Using the compiler's macro's and pre-processor, we can condense the above code to a few simple pseudo commands, and add some more into the bargain. The include file "MCP23S17.inc" contains several macros that make interfacing to the MCP23S17 chip very straightforward. For example, the same program as above can now be written as:

    Code:
    
    '
    ' Interface to a Microchip MCP23S17 16-bit SPI I/O peripheral
    ' Uses the MCP23S17 macro library routines
    '
      Include "Amicus18.inc"                  ' Configure the compiler to use the 18F25K20 at 64MHz for the Amicus18 board
        
    $define MCP_CS PORTC.2                    ' Use PORTC.2 as the CS pin
      Include "MCP23S17.inc"                  ' Load the MCP23S17 macros into the program
    
      Dim bTemp As Byte                       ' Create a variable to work with
    '---------------------------------------------------------------------
    ' Main demonstration loop
    ' Mirror the input of PORTA on the outputs of PORTB
    '
    Main:
      MCP23S17_SetTrisPortA($FF)              ' Make all PORTA inputs
      MCP23S17_SetTrisPortB($00)              ' Make all PORTB outputs
      MCP23S17_WritePortB($00)                ' Pull all PORTB outputs low
      MCP23S17_SetPullUpsPortA($FF)           ' Enable pull-up resistors on PORTA
    
      While 1 = 1                             ' Create and infinite loop
        bTemp = MCP23S17_ReadPortA()          ' Read the condition of PORTA
        MCP23S17_WritePortB(~bTemp)           ' Place the complement of PORTA on PORTB
      Wend                                    ' Do it forever


    MCP23S17 macros
    The MCP23S17.inc file contains the following macros:

    Macro Name Purpose
    MCP23S17_Init() Initialise the MCP23S17 (performed automatically upon inclusion of the MCP23S17.inc file)
    MCP23S17_ReadPortA() Read PORTA
    MCP23S17_ReadPortB() Read PORTB
    MCP23S17_WritePortA(pDataByte) Write PORTA Latch register
    MCP23S17_WritePortB(pDataByte) Write PORTB Latch register
    MCP23S17_SetTrisPortA(pDirection) Set PORTA pin's direction
    MCP23S17_SetTrisPortB(pDirection) Set PORTB pin's direction
    MCP23S17_WritePinPortA(pPin,pHiLo) Write to a bit of PORTA
    MCP23S17_WritePinPortB(pPin,pHiLo) Write to a bit of PORTB
    MCP23S17_WritePinTrisA(pPin,pDirection) Set or clear the TRIS of a Single Pin from PORTA pins
    MCP23S17_WritePinTrisB(pPin,pDirection) Set or clear the TRIS of a Single Pin from PORTB pins
    MCP23S17_ReadTrisPortA() Get PORTA pin's direction 0=Output 1=Input
    MCP23S17_ReadTrisPortB() Get PORTB pin's direction 0=Output 1=Input
    MCP23S17_SetPullUpsPortA(pPullUps) Set the Pullups resistors for any Pin of PORTA
    MCP23S17_SetPullUpsPortB(pPullUps) Set the Pullups resistors for any Pin of PORTB
    MCP23S17_Write(pRegAddress, pDataByte) Write to one of the MCP23S17 registers
    MCP23S17_Read(pRegAddress, pDataByte) Read from one of the MCP23S17 registers

    An explanation of each macro can be found alongside its declaration within the MCP23S17.inc file.

    The above circuit, source code, Isis DSN file and Google SketchUp 7 layouts can be downloaded from here:
    Interfacing MCP23S17 Firmware Source
    MCP23S17 Circuits and Sketchup Drawings


    Les Johnson
  • Recent Activity

    joesaliba-353

    Pulse counter memory problem

    Thread Starter: amod

    Please tell me my faults.My pulse counter memory does not store.When i switch on device after 5 second ,memory exist by after 30 seconds it is washed...

    joesaliba Yesterday, 18:33 Go to last post