Reading MCP23S17

Hello everyone,

I currently try to use a MCP23S17 using the library from Rob Tillaart. I successfully can "write" to the ports and control mosfets and status leds. The SPI-connection and basic functionality should work. My problem is the reading of input. I connected the following elements

The following code should poll all 16 ports

#include <Arduino.h>
// #include<DigitalExtender.h>
#include<MCP23S17.h>

// #include<Logger.h>
// bool DEBUGLOG = true;

uint8_t CS_PIN = 9;

MCP23S17 mcp23s17 = MCP23S17(CS_PIN);

void setup() {
  
  Serial.begin(9600);

  uint8_t  rv = mcp23s17.begin();
  rv = mcp23s17.pinMode8(0, 1);  // 0 = output , 1 = input
  Serial.println(rv);
  rv = mcp23s17.pinMode8(1, 1);
  Serial.println(rv);

  mcp23s17.usesHWSPI();
  



  Serial.println("--- Digital Extender ---");

}

  

void loop() {
  

  uint8_t status = 0;
      
  for(uint8_t i=0;i<16;i++) {
    status = mcp23s17.read1(i);
    Serial.print(status);
  }

  Serial.println("");

  delay(500);

}

The output reads

true
1
1
--- Digital Extender ---
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111111

I tried adjusting the internal pullup using the example values of the library and changed it to 0xFF

rv = mcp23s17.pinMode8(0, 0xFF);
rv = mcp23s17.pinMode8(1, 0xFF);`

The results are the same. Since the chip can not distinguish between an open connection, unconnected and connected I may have overlooked some additional settings.

Am I missing a register setting or are the connection faulty?

I don't think you read the library documentation carefully enough. Unlike the standard arduino pinMode() function, the pinMode() function in the library does not enable the internal pull-up resistors. There is a different function for that.

Changing the parameter value for pinMode() only sets which pins are input and which are output. 0xFF sets them all to output, I suspect. If you set the pin connected to the reed switch to output and then to high, and the reed switch closes, there could be a damaging short-circuit.

I referenced the wrong function. In the library ( ) you can set a whole port to either input or output. Basically the whole bit pattern has to be set for port A and B.

From the library

bool MCP23S17::pinMode8(uint8_t port, uint8_t value)
{
  if (port > 1)
  {
    _error = MCP23S17_PORT_ERROR;
    return false;
  }
  if (port == 0) writeReg(MCP23S17_DDR_A, value);
  if (port == 1) writeReg(MCP23S17_DDR_B, value);
  _error = MCP23S17_OK;
  return true;
}

according to the datasheet the register for the direction is set per port (8 pins per port).

therefore setting all pins to input (now as bit value to improve readability)

rv = mcp23s17.pinMode8(0, 0b11111111);  // 0 = output , 1 = input
 Serial.println(rv);
 rv = mcp23s17.pinMode8(1, 0b11111111);
 Serial.println(rv);

should make pins input.

I am unable to follow the procedure to set port B. With

MCP23S17_DDR_A 0x00 -> direction register
MCP23S17_DDR_B 0x01 -> input polarity port register

where is the selector for Port A and B?

Alos in writeReg

bool MCP23S17::writeReg(uint8_t reg, uint8_t value)
{
  _error = MCP23S17_OK;

  if (reg > MCP23S17_OLAT_B)
  {
    _error = MCP23S17_REGISTER_ERROR;
    return false;
  }
  ::digitalWrite(_select, LOW);
  if (_hwSPI)
  {
    _mySPI->beginTransaction(_spi_settings);
    //  _address already shifted
    _mySPI->transfer(MCP23S17_WRITE_REG | _address );
    _mySPI->transfer(reg);
    _mySPI->transfer(value);
    _mySPI->endTransaction();
  }
  else
  {
    //  _address already shifted
    swSPI_transfer(MCP23S17_WRITE_REG | _address );
    swSPI_transfer(reg);
    swSPI_transfer(value);
  }
  ::digitalWrite(_select, HIGH);
  return true;
}

the value MCP23S17_WRITE_REG = 0x40 is set as control byte before the command and values. There is no selector for port A and B?

Agreed. Have you read the library documentation and figured out the right function yet?

I agree. I was wrong about 1 = output. But that's not the reason why you can't read your reed switch. You need to enable the internal pull-up for that pin. You need to figure out which library function does that. I can see it...

I think you already did that.

The first parameter of .pinMode8() is 0=port A, 1=port B

Following the library function to the writing of the registers leads to

if (port == 0) writeReg(MCP23S17_DDR_A, value);
if (port == 1) writeReg(MCP23S17_DDR_B, value);

where is defined as

MCP23S17_DDR_A = 0x00
MCP23S17_DDR_B = 0x01

This is valid when IOCON.BANK=0, which should be the default state. Or at least I have not figured out how to change it.

adding the registers for the internal pullup GPPUA, GPPUB

I changed the setup part of the code to

  uint8_t  rv = mcp23s17.begin();
  Serial.println(rv ? "true" : "false");
  rv = mcp23s17.pinMode8(0x00, 0b11111111);  // 0 = output , 1 = input
  Serial.println(rv);
  rv = mcp23s17.pinMode8(0x0C, 0b11111111);  // internal pullup
  Serial.println(rv);
  rv = mcp23s17.pinMode8(0x01, 0b11111111);
  Serial.println(rv);
  rv = mcp23s17.pinMode8(0x0D, 0b11111111);  // internal pullup
  Serial.println(rv);

The output seems to change but is equally untrustworthy

�␀�true
1      
0      
1
0
--- Digital Extender ---
1111111111111111        1111111111111111
1111111111111111        1111111111111111
1111111111111111        1111111111111111
1111111111111111        1111111111111111
1111111111111111        1111111111111111
1111111111111111        1111111111111111
1111111111111111        1111111111111111
1111111111111111        1111111111111111
1111111111111111        1111111111111111
1111111111111111        1111111111111111
1111111111111111        1111111111111111
1111111111111111        1111111111111111
1111111111111111        1111111111111111
1111111111111111        1111111111111111
1111111111111111        1111111111111111
1111111111111111        1111111111111111
1111111111111111        1111111111111111
1111111111111111        1111111111111111
1111111111111111        1111111111111111
1111111111111111        1111111111111111
1111111111111111        1111111111111111
1001000000000000        0
0000000000111111        1111111111111111
1111111111111111        1111111111111111

Do all unused ports need to be grounded or can they left unconnected?

You seem to be doing something strange here. That first parameter of .pinMode() is intended to be 0 or 1. Not 0x0C or 0x0D. Why do you think that will affect the internal pullups?

Find the right function in the library docs for enabling the internal pullups. Its very easy to find. I found it in less than a minute, using your link from the first post. Use it.

They can be left disconnected, just like Arduino pins. But if you try to read them, you will get random results. Unless you enable the pullup resistors. just like Arduino pins.

You are right. The functions are preconfigured using only '0' or '1'. This is just a coincident that the values of IODIRA\B are also 0x00, 0x01.

Looking at "setPullup(port,pattern)"

if (port == 0) writeReg(MCP23S17_PUR_A, mask);
if (port == 1) writeReg(MCP23S17_PUR_B, mask);

where

MCP23S17_PUR_A = 0x0C
MCP23S17_PUR_B = 0x0D

This would also explain the "false" return of the "rv" value (since the port value is >1).

It looks like Rob did the heavy lifting again and I misinterpreted the first value as register value not as port A\B.

I already changed my code, but will probably not be able to test it today. I will report back tomorrow morning. Thanks for the help so far.

I found the pullup function, where 0x0C,0x0D was called and changed my setup() to

void setup() {
  
  Serial.begin(9600);

  uint8_t  rv = mcp23s17.begin();
  Serial.println(rv ? "true" : "false");

  //set config  
  rv = mcp23s17.pinMode8(0x00, 0b11111111);  // 0 = output , 1 = input
  Serial.println(rv);
  delay(10);
  rv = mcp23s17.setPullup8(0x00,0b11111111); // pullup 1=enabled
  Serial.println(rv);
  delay(10);

  rv = mcp23s17.pinMode8(0x01, 0b11111111);
  Serial.println(rv);
  delay(10);
  rv = mcp23s17.setPullup8(0x01, 0b11111111); // pullup 1=enabled
  Serial.println(rv);
  delay(10);

  mcp23s17.usesHWSPI();
  
  Serial.println("--- Digital Extender ---");

}

void loop() {
  

  uint8_t status = 0;
  uint16_t status16 = 0;
      
  for(uint8_t i=0;i<16;i++) {
    status = mcp23s17.read1(i);

    Serial.print(status);
  }

  Serial.print("\t");

  status16 = mcp23s17.read16();
  Serial.println(status16,BIN);

  delay(500);

}

Sadly the results are unchanged. There is no distinction between open, closed and unconnected pins.

true
1   
1   
1   
1
--- Digital Extender ---
1111111111111111        1111111111111111
1111111111111111        1111111111111111
1111111111111111        1111111111111111

Update and solution
I copied the executable example here in the forum from my original project and forgot to include the initialization from the SPI bus (all buses are configured elsewhere). The solution is to start the SPI transaction

SPI.begin();

The whole example now looks like

#include <Arduino.h>
// #include<DigitalExtender.h>
#include<MCP23S17.h>

// #include<Logger.h>
// bool DEBUGLOG = true;

uint8_t CS_PIN = 9;

MCP23S17 mcp23s17 = MCP23S17(CS_PIN);

void setup() {
  
  Serial.begin(9600);

  SPI.begin();

  uint8_t  rv = mcp23s17.begin();
  Serial.println(rv ? "true" : "false");

  //set config  
  rv = mcp23s17.pinMode8(0x00, 0b11111111);  // 0 = output , 1 = input
  Serial.println(rv);
  delay(10);
  rv = mcp23s17.setPullup8(0x01,0x00); // pullup 1=enabled 0b11111111
  Serial.println(rv);
  delay(10);

  rv = mcp23s17.pinMode8(0x01, 0b11111111);
  Serial.println(rv);
  delay(10);
  rv = mcp23s17.setPullup8(0x01, 0x00); // pullup 1=enabled
  Serial.println(rv);
  delay(10);

  mcp23s17.usesHWSPI();

  pinMode(LED_BUILTIN, OUTPUT);
  
  Serial.println("--- Digital Extender ---");

}

void blink() {
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);                       // wait for a second
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);                       // wait for a second
}
  

void loop() {  

  uint8_t status = 0;
  uint16_t status16 = 0;
      
  for(uint8_t i=0;i<16;i++) {
    status = mcp23s17.read1(i);

    Serial.print(status);
  }

  Serial.print("\t");

  status16 = mcp23s17.read16();
  Serial.print(status16,BIN); Serial.print("\t");

  // delay(500);

  long val = 0;
  long t0 = millis();

  for(int i=0;i<1024;i++) {
    if(digitalRead(6)) {
      val++;
    }
  }

  Serial.print("D6["); Serial.print(millis()-t0); Serial.print("]\t");
  Serial.println(val);

  delay(500);

  blink();

}

The chip now works as intended.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.