Cornell University ECE4760
mcp23s17 Port Expander

Pi Pico rp2040/2350

The mcp23s17 description and wiring
The Microchip mcp23s17 port expander adds 16 lines of dgital i/o, driven by an SPI connection to the Pico. The 16 lines are grouped into two 8-bit ports.  Each i/o line may be set to input or output. Each i/o line may turn on an internal pullup resistor (there are no pulldowns), if it is an input. The grouping of lines into two 8-bit ports means that one spi transaction can affect the eight i/o lines of PortA or of PortB. For example, you can write a uint8_t number to a port with one spi transaction. You can also set the data direction of each of the eight bits of a port by writing an 8-bit value with desired direction encoded as high for output and low for input. For example writing 0xf0 to the data direction register of PortA sets bits 0 to 3 as input and bits 4 to 7 as output. Similarly, writing eight bits to the pullup enable register turns on pullups on lines with a high bit. For example writing 0x03 to the pullup enable register of PortB turns on pullups on bits zero and one of PortB.

The mcp23s17 we use is in 28-pin PDIP package as shown below.
For the code below the INTA and INTB pins are not connected, and the RESET pin is wired high.

The wiring for the PortA to PortB loop-back test. The convienient wiring of the 470 ohm resistors directly
across the package from PortA to PortB results in bit reversal when reading the loop-back results in the code.
The wiring details as a text table.


The Port Expander command interface.
Adding
#include "mcp23s17_rp2040.h"
to your program adds the port expander code.
In the following functions:

Functions:

The loop-back test code.
This program merely exercises the inteface routines to the port expander to that you can test connections and see the syntax.
There are two threads running on one core. The usual blinky thread, of course, blinks. The serial thread:

  1. Sets up the port expander
    PEinit() ;
    makes all 8 port A pins outputs by writing 0xff to PortA direction register
    PEgpioSetDir(PORTA, 0xff) ;
    makes port B all inputs by writing zeros to PortB direction register
    PEgpioSetDir(PORTB, 0x00) ;

  2. prompts for a 2-digit hexadecimal number and sends the number to output of PortA.
    sscanf(pt_serial_in_buffer,"%2x", &port_A_out) ;
    PEwritePort(PORTA, port_A_out) ;

    then reads PortB, reverses the bits and prints
    port_B_input = PEreadPort(PORTB) ;
    port_B_input_rev = __rev((uint32_t) port_B_input) >> 24; // shift 24 because __rev is 32 bit
    printf ("A_out=%02x B_in=%02x B_in_rev=%02x\n\r", PEreadPort(PORTA), port_B_input, port_B_input_rev)
    ;

  3. prompts for a bit number (0 to 7) and bit value (1/0) inserts the bit into PortA output
    sscanf(pt_serial_in_buffer,"%d %d", &bit_position, &bit_value) ;
    PEwriteBit(PORTA, bit_position, bit_value) ;

    then reads PortB as above.

  4. prompts for a bit position (0 to 7) then reads that bit from PortA.
    sscanf(pt_serial_in_buffer,"%d", &bit_position) ;
    printf ("A bit=%1d\n\r\n\r",PEreadBit(PORTA, bit_position));


  5. repeats from 2 above

These commands exercise most of what the port expander can do.
A serial screen dump shows two examples.

Test Codemcp23s17_rp2040.h,   Project ZIP


Copyright Cornell University July 4, 2025