Thursday, December 8, 2016

Arduino and MCP23S17 Port Expander (Part 1)

MCP23S17 Port Expander — 
The Microchip MCP23x17 Port Expander can be used to add 16 digital pins to an Arduino. The MCP23S17 uses SPI while the MCP23017 uses I2C to communicate with the Arduino. In this series of blogs, I will examine the MCP23S17 SPI chip, with the goals of: 1) gaining experience with the device; 2) better understanding how the device works on an Arduino with SPI; and, 3) using the device in several prototypic applications.
Devices and pins can get very confusing when adding hardware chips and their associated software to an Arduino. In this blog, I will use  some codes to help identify pins and devices. MCP will refer to an MCP23S17 chip. A "device" will equate to either the Arduino board (device = 0) or any one MCP chip (device 1, 2, ..., n). A pin identified as D"x" will be the common term for an Arduino digital pin. The Uno has 14 digital pins and 6 analog pins. The Mega 2560 has 54 digital and 16 analog pins. Microchip identifies MCP pins as GPA"x" or GPB"x" which refers to the Port (A or B) and pin (0-7). The MCP is an 8 bit device, and each device has a Port A (pins GPA0...GPA7, which are chip pins 21-28) and Port B (pins GPB0...GPB7, which are chip pins 1-8). A port value of "0" refers to Port A. Port values of >="1" refers to Port B. The Arduino sketch and MCP23S17 library that is used will also dictate how a chip device, a port, and a pin are identified.
Majenko MCP23S17 library -- I have adopted the Majenko MCP23S17 library (on github) which has worked well with the Arduino IDE 1.6.5 SPI library. I tested other libraries, but none measured up to the Majenko library. All code presented in this series of blogs will use the Majenko MCP23S17 library. Download the library (revised 2017-Jan-23), add it to the Arduino libraries, and restart the Arduino IDE.
The Majenko library provides both 16-bit (Port A and Port B) and 8-bit (Port A or Port B) functions. This library uses the MCP "pin" term to mean one of 16 digital pins ranging from 0 to 15, where [0...7] are the GPA"x" pins and [8-15] are the GPB"x" pins. The Microchip GPA"x" and GPB"x" terms are not used to identify pins by the Majenko library, just as pins 0-15.
The MCP23S17 chip used for this work was the 28-pin narrow dual inline package which can be easily used with 0.1" breadboards for prototype testing. Eight (8) MCP23S17 chips (SPI variant) can be used together on one SPI CS pin address. SPI on the Arduino uses 3 SPI pins (Uno pins D11, D12, D13, or on Mega 2560 pins D50, D51, D52). SPI also requires a CS pin which is used to select which SPI device is active at any given time. The default Arduino CS pin is D10 on Uno and D53 on Mega 2560. Any dedicated digital pin can be used as the CS pin. Since 8 MCP23S17 chips can be multiplexed together, three other pins (MCP23S17 pins A0, A1, A2), must be either connected to ground or +V to give each chip a unique address of [0,1,2...,7]. The software can then identify which chip to access by this address. Eight chips could provide 8x16=128 digital pins while only using the 3 SPI pins plus 1 CS pin of the Arduino for each group of 8 chips.

Wiring the MCP23S17 for testing is a bit of a challenge, just due to the large number of pins. Connect MCP chip pin 10 (VSS) to 0V (ground); pin 9 (VDD) to +V; pin 18 (Reset) through a 10K resistor to +V; pins 15 (A0), 16 (A1), and 17 (A2) to either 0V or +V (unique for each chip); pin 11 (CS) to D10/D53; pin 12(SCK) to D13/D52; pin 13 (SI) to D11/D51; and pin 14 (SO) to D12/D50. Each of the MCP digital pins 0-15 are wired as inputs to some digital sensor (button or switch), or can be used as output pins to drive other devices. The MCP interrupt pins, if used, are connected to Arduino interrupt pins, to trigger the Arduino to take instant action.
  

Pictures: Arduino Mega 2560 with MCP23S17 Port Expander on breadboard. The sketch MCP23S17_SingleTest_01.ino will display an LED ON (left picture) while MCP digital pin 8 (IC pin 1) is not grounded. Connecting MCP digital pin 8 to ground (right picture, purple wire) will switch the LED OFF. As a sketch option, a wire from MCP pin 7 to Arduino pin D6 (yellow wire) can demonstrate a hard-wired connection from the MCP to another device. In this case, "another device" is the Arduino input pin D6.

This fritzing drawing might help show the wiring for this example.


/*
 * MCP23S17_SingleTest_01.ino
 * Library Ref: github.com/MajenkoLibraries/MCP23S17
 *
 * Lowell Bahner
 * 2016-12-11
 *
 * Hardware: Arduino UNO/Mega2560
 * IDE: Arduino 1.6.5
 *
 * Useful SPI Ref: gammon.com.au/spi
 *
 * Wire the SPI Interface common lines (Mega2560 SPI pins shown in [ ]):
 * Arduino SPI_MOSI pin 11 [51] <->   SI  MCP23S17 pin 13
 * Arduino SPI_MISO pin 12 [50] <->   SO  MCP23S17 pin 14
 * Arduino SPI_CLOCK pin 13 [52]<->   SCK MCP23S17 pin 12
 * Arduino SPI_CS pin 10 [53]   <->   SS  MCP23S17 pin 11
 * MCP23S17 VDD pin 9 to +5V
 * MCP23S17 VSS pin 10 to Gnd
 * MCP23S17 Reset pin 18 to +5V
 * MCP23S17 Interrupt pins 19-20
 * MCP23S17 A0,A1,A2 pins connect to Gnd or +5V (000 = Address 0, 100 = Address 1, 010 = Address 2, etc)
 * MCP23S17 Port A pins are chip pins 21-28
 * MCP23S17 Port B pins are chip pins 1-8
 *
 * This sketch runs a simple example to demonstrate library capabilities.
 * This code will run if the MCP23S17 is properly wired to Arduino.
 * The sketch reads a Port Expander input pin,
 *    writes that value to a Port Expander output pin.
 *    That output pin value is written to an Arduino pin
 *    which can turn an LED on and off as the Port Expander
 *    input pin is grounded or not grounded.
 *    
 * This sketch does not demonstrate interrupt functionality.
 *
 * This sketch instantiates 1 MCP23S17 chip.
 * Bank0 (MCP23S17 address "000") Port B pins are set as INPUT_PULLUP.
 *    Normally HIGH, use switch to set a pin LOW.
 * Bank0 (MCP23S17 address "000") Port A pins are set as OUTPUT.
 *    MCP23S17 outputs will power LEDs up to 25ma through 220-1000ohm resistor to ground.
 *
 * Majenko MCP23S17 library functions (see .h,.cpp files for details):
 *
 *  bank.pinMode(uint8_t pin, uint8_t mode)
 *    where modes are:
 *      OUTPUT - sets pin HIGH
 *      INPUT  - sets pin LOW for sensing change to HIGH +V
 *      INPUT_PULLUP - sets pin HIGH for sensing change to LOW 0V
 *
 *  bank.digitalWrite(uint8_t pin, uint8_t value)
 *    if mode=OUTPUT, pin value = LOW (0) or HIGH (1)
 *    if mode=INPUT, value LOW disables pullup on pin, value HIGH enables pullup on pin
 *
 *  uint8_t value = bank.digitalRead(uint8_t pin)
 *    read the state of the pin
 *
 *  uint8_t value = bank.readPort(uint8_t port)
 *    read entire 8-bit port of all INPUT pins on Port A (port=0) or Port B (port>=1)
 *
 *  uint16_t longValue = bank.readPort()
 *    read both ports as 16-bit combined
 *
 *  bank.writePort(uint8_t port, uint8_t val)
 *    write val (0 or 1, hex example 0x55) to all OUTPUT pins on Port A (port=0) or Port B (port>=1)
 *
 *  bank.writePort(uint16_t val)
 *    write val (hex example 0x55AA) to all OUTPUT pins on Port A and Port B
 *
 *  Also, interrupt functions are defined:
 *      enableInterrupt(uint8_t pin, uint8_t type);
 *      void disableInterrupt(uint8_t pin);
 *      void setMirror(boolean m);
 *      uint16_t getInterruptPins();
 *      uint16_t getInterruptValue();
 *      void setInterruptLevel(uint8_t level);
 *      void setInterruptOD(boolean openDrain);
 *
 *
 *
 *
Run Sketch: Serial Monitor Output

 -- RWpins() --

Bank0: Input  pin 8: connect MCP pin 8 to 0V (ground)
Bank0: Output pin 7: MCP pin 7 will switch to LOW.
                     LED on pin D7 will switch OFF.
                     Let MCP pin 8 go HIGH, LED will switch ON.

(ground MCP pin 8)
 -- RWpins01() --

Bank0: Input pin 8: 0
Bank0: Output pin 7: 0
Pin D7 0

(un-ground MCP pin 8)
 -- RWpins01() --

Bank0: Input pin 8: 1
Bank0: Output pin 7: 1
Pin D7 1

 *
 *
 */

// Majenko MCP23S17 Library
#include <MCP23S17.h>

// Arduino Library SPI.h
#include <SPI.h>

// SPI CS/SS chipselect pin can be changed by user as desired
const uint8_t chipSelect = 53; // Mega2560
//const uint8_t chipSelect = 10; // Uno

// example Arduino board pins
uint8_t pin5 = 5;
uint8_t pin6 = 6;
uint8_t pin7 = 7;

// Create an object for each chip
// Bank0 is address 0. Pins A0,A1,A2 grounded.

MCP23S17 Bank0(&SPI, chipSelect, 0);

void setup() {

  Serial.begin(9600);

  Bank0.begin();

  // LED connected to Arduino pin 7 to test inter-device communication
  pinMode(pin5, INPUT); // input pin normally LOW
  pinMode(pin6, INPUT_PULLUP); // input pin normally HIGH
  pinMode(pin7, OUTPUT);

  //
  // Set MCP23S17 pin modes:
  // example set one pin: chip.pinMode(0, OUTPUT); // sets chip pin 0 (Port A 0) to OUTPUT mode
  //
  setPortPins(); // example: set pins on each Port on each chip to different modes.
  //

}

// Set Port pins to desired mode
void setPortPins() {
  // Example usage

  // Set all Bank0 Port A pins to OUTPUT
  for (uint8_t i = 0; i <= 7; i++) {
    Bank0.pinMode(i, OUTPUT);
    Bank0.digitalWrite(i, 0); // set value LOW
  }

  // Set all Bank0 Port B pins to INPUT_PULLUP
  for (uint8_t i = 8; i <= 15; i++)
    Bank0.pinMode(i, INPUT_PULLUP);
}

//
// Change LED On/Off with button push
// 1) Switch an INPUT_PULLUP pin to ground
// 2) Read the pin value
// 3) Write the value to an OUTPUT pin
// 4) Read the OUTPUT pin value
// 5) Write the value to Arduino OUTPUT pin D7 with LED=>r1000=>Ground
// 6) Turn LED On/Off
// Use functions digitalRead(pin) and digitalWrite(pin,value)
//
void RWpins01() {

  Serial.print("\n -- RWpins01() --"); Serial.println("");

  uint8_t bValue = 0;   // Bank0 Port B input pin value (0 push LOW, 1 normally open HIGH) with push button switch to gnd
  uint8_t bPin  = 8;    // Bank0 Port B pin number
  uint8_t aValue = 0;   // Bank0 Port A output pin value
  uint8_t aPin  = 7;    // Bank0 Port A pin number
  uint8_t d6Value = 0;  // pin d6
  uint8_t d7Value = 0;  // pin d7


  // read the switch value into b15
  bValue = Bank0.digitalRead(bPin);
  Serial.print("\nBank0: Input pin "); Serial.print(bPin); Serial.print(": ");  Serial.println(bValue);

  // write the switch bPin, bValue, to the output aPin
  Bank0.digitalWrite(aPin, bValue);

  aValue = Bank0.digitalRead(aPin);
  Serial.print("Bank0: Output pin "); Serial.print(aPin); Serial.print(": ");  Serial.println(aValue);

  // // -----------------------------------
  // do one of the following
  // comment out the code lines after 1) or 2)
  //
  // // example 1: write the aValue from port expander output pin
  // // to Arduino output pin to switch LED.
  // // 1) Turn on/off LED connected to Arduino pin 7 through resistor to ground
  digitalWrite(pin7, aValue);

  // // example 2: connect port expander output pin to
  // // Arduino input pin using wire. Read that pin value and write
  // // it to Arduino output pin to switch LED.
  // // or, 2) wire pin D6 to Bank0 pin 7; retain LED on pin D7
  //          d6Value = digitalRead(pin6); // pin6 normally HIGH
  //          digitalWrite(pin7, d6Value);
  // // end do one of the following
  // // -----------------------------------

  d7Value = digitalRead(pin7); // pin7 follows pin6
  Serial.print("Pin D7 "); Serial.println(d7Value);

}


void loop() {

  // Change LED On/Off with button push
  // Switch or Short pin Bank0, Port B, pin 8 to ground
  // LED=>r1000=>ground on Arduino pin D7 will turn on/off
  RWpins01();

  delay(100);

}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++
// print binary8 binary16 and hex functions
// ++++++++++++++++++++++++++++++++++++++++++++++++++++

//---------------------------------------------------------------------------------
// print 8-bit byte as 8 bit binary string
//---------------------------------------------------------------------------------

void print8Bits(uint8_t myByte) {
  for (uint8_t mask = 0x80; mask; mask >>= 1) {
    if (mask  & myByte)
      Serial.print('1');
    else
      Serial.print('0');
  }
}

//---------------------------------------------------------------------------------
// print 16-bit word as 16 bit binary string
//---------------------------------------------------------------------------------

void print16Bits(uint16_t myWord) {
  for (uint16_t mask = 0x8000; mask; mask >>= 1) {
    if (mask  & myWord)
      Serial.print('1');
    else
      Serial.print('0');
  }
}


//---------------------------------------------------------------------------------
// crPrintHEX print value as hex with specified number of digits
//---------------------------------------------------------------------------------

void crPrintHEX(unsigned long DATA, unsigned char numChars) {
  unsigned long mask  = 0x0000000F;
  mask = mask << 4 * (numChars - 1);
  Serial.print("0x");
  for (unsigned int eID = numChars; eID > 0;  --eID) {
    Serial.print(((DATA & mask) >> (eID - 1) * 4), HEX);
    mask = mask >> 4;
  }
  Serial.print("  ");
}

No comments:

Post a Comment