0

I use a single MCP23S17 connected to an Arduino Uno. I use its port B for output and port A for input. When I first started to use this device I noticed, I could read pins, but using them as output failed. After a lot of debugging and trial and error I found out, that the IODIRx Register was never programmed. I then copied the respective code from the setup() routine — where it belongs IMHO — to the loop() routine.

And miraculously the IODIRx Register was written correctly. The code below contains the IODIRx programming snippet in the setup() as well as in loop(). But only in loop() it yields an effect. This means: The output will work only after the first traversal of loop() in my example.

The rest of the setup() routine obviously works, which means it is called at least. SPI is initialised correctly there.

This drives me nuts. What the hell is a setup routine for, if you can't setup things there. And why on earth doesn't it work then? I don't do strange things there.

#include <Arduino.h>
#include <Wire.h>   
#include <inttypes.h>
#include <SPI.h>
#include "flanschcontrol.h"
#define DEBUGOUTPUT
#define PROTOV2
typedef int elem_type;
#define ADDRESS (0)   
#define    OPCODEW       (0b01000000)  // Opcode for MCP23S17 with LSB (bit0) set to write (0), address OR'd in later, bits 1-3
#define    OPCODER       (0b01000001)  // Opcode for MCP23S17 with LSB (bit0) set to read (1), address OR'd in later, bits 1-3
#define    ADDR_ENABLE   (0b00001000)  // Configuration register for MCP23S17, the only thing we change is enabling hardware addressing
uint8_t PexSSPin=7;

void setup() {
   Serial.begin(9600);
    pinMode(PexSSPin, OUTPUT);
    SPI.begin();                          
    SPI.setClockDivider(2);   
    SPI.setBitOrder(MSBFIRST);            
    SPI.setDataMode(SPI_MODE0);           

    delay(1000);    
    PORTD &= 0b01111111;    // Slaveselect low

    SPI.transfer(OPCODEW | (ADDRESS << 1)); 
    SPI.transfer(IODIRB);
    SPI.transfer(0b10101010);
    PORTD |= 0b10000000;    // Slaveselect high
    delay(1000);
}

void loop() 
{
    PORTD &= 0b01111111;    // Slaveselect low
    SPI.transfer(OPCODEW | (ADDRESS << 1));             // Send the MCP23S17 opcode, chip address, and write bit
    SPI.transfer(GPIOB);
    SPI.transfer(0b00000000);
    PORTD |= 0b10000000;    // Slaveselect high
    Serial.println("0");
    delay(1000);

    PORTD &= 0b01111111;    // Slaveselect low
    SPI.transfer(OPCODEW | (ADDRESS << 1));             // Send the MCP23S17 opcode, chip address, and write bit
    SPI.transfer(GPIOB);
    SPI.transfer(0b11111111);
    PORTD |= 0b10000000;    // Slaveselect high
    Serial.println("ff");
    delay(1000);

    PORTD &= 0b01111111;    // Slaveselect low
    SPI.transfer(OPCODEW | (ADDRESS << 1));             // Send the MCP23S17 opcode, chip address, and write bit
    SPI.transfer(GPIOB);
    SPI.transfer(0b00001111);
    PORTD |= 0b10000000;    // Slaveselect high
    Serial.println("0f");
    delay(1000);

    PORTD &= 0b01111111;    // Slaveselect low
    SPI.transfer(OPCODEW | (ADDRESS << 1));             // Send the MCP23S17 opcode, chip address, and write bit
    SPI.transfer(GPIOB);
    SPI.transfer(0b11110000);
    PORTD |= 0b10000000;    // Slaveselect high
    Serial.println("f0");
    delay(1000);

    int value;

    PORTD &= 0b01111111;    // Slaveselect low
    SPI.transfer(OPCODER | (ADDRESS << 1));  // Send the MCP23S17 opcode, chip address, and read bit
    SPI.transfer(GPIOA);                      // Send the register we want to read
    value = SPI.transfer(0x00);               // Send any byte, the function will return the read value (register address pointer will auto-increment after write)
    value |= (SPI.transfer(0x00) << 8);       // Read in the "high byte" (portB) and shift it up to the high location and merge with the "low byte"
    PORTD |= 0b10000000;    // Slaveselect high
    Serial.print("pindata: ");
    Serial.println(value);
    delay(1000);    

    PORTD &= 0b01111111;    // Slaveselect low
    SPI.transfer(OPCODEW | (ADDRESS << 1)); 
    SPI.transfer(IODIRB);
    SPI.transfer(0b10101010);
    PORTD |= 0b10000000;    // Slaveselect high     
}

Definitions of some preprocessor thingies:

#define    IODIRA    (0x00)      // MCP23x17 I/O Direction Register
#define    IODIRB    (0x01)      // 1 = Input (default), 0 = Output
#define    GPIOA     (0x12)      // MCP23x17 GPIO Port Register
#define    GPIOB     (0x13)      // Value on the Port - Writing Sets Bits in the Output Latch
4
  • Please show the declarations for IODIRB, GPIOA, GPIOB.
    – Nick Gammon
    Aug 15, 2015 at 0:18
  • @NickGammon: I added the defines.
    – Ariser
    Aug 15, 2015 at 0:25
  • I have one question. Once you set the PinMode of an expander then can you change the mode without giving a reset? I mean, I have to use 3 expanders for controlling an SRAM: on one MCP I have the data lines, on the second one I have the control signals and on the last one I have the address lines. Firstly, I do a SRAM_WRITE, so I need the pins of the first expander to be OUTPUTs,then I do a READ_SRAM so a need the pins of the first expander to be INPUTs. Can I change the mode from output to input? Then I am trying to check the signals with an oscilloscope. When a do a pinMode OUTPUT of the expa
    – Gabriele
    Feb 2, 2018 at 10:27
  • @Gabriele: Why should you have to do a reset to change pin behaviour? But please don't use the comment section to ask new questions.
    – Ariser
    Feb 2, 2018 at 20:00

1 Answer 1

1

You've fallen for the old "slave select pin is already low" trick!

Let me show you. First we add a line to bring pin 2 high before starting the sequence (for logic analyzer triggering):

pinMode (2, OUTPUT);     // <--- pin 2 output
delay(1000);    
PORTD |= bit (2);        // <--- pin 2 HIGH
PORTD &= 0b01111111;     // Slaveselect low
SPI.transfer(OPCODEW | (ADDRESS << 1)); 
SPI.transfer(IODIRB);
SPI.transfer(0b10101010);
PORTD |= 0b10000000;    // Slaveselect high
delay(1000);

This is the logic analyzer output of running your sketch:

Slave select low

Notice how SS is low all the time, and thus the chip does not respond to the command?


Now we add a line before the SPI.begin

PORTD |= 0b10000000;    // Slaveselect high
SPI.begin();                          
SPI.setClockDivider(2);   
SPI.setBitOrder(MSBFIRST);            
SPI.setDataMode(SPI_MODE0);           

Now look at the results:

Slave select high

This time you get the transition of SS from high to low, the chip notices it, and we have a valid SPI transaction.

So without bringing SS high, the first thing you sent was always going to fail.


It was nothing to do with it being in setup - it couldn't be. After all the logic is basically this:

init ();
setup ();
while (true) 
  loop ();

Personally I wouldn't hard code all those bit patterns throughout the code. What if you change the slave select pin one day?

How about:

digitalWrite (PexSSPin, LOW);
/// write to chip
digitalWrite (PexSSPin, HIGH);

Isn't that much more readable? And easier to change later?

1
  • Hahahaaa, that's good. Works flawlessly now. My code looks so awful due to all the debugging steps I took. In the beginning it was a nice class with everything in it's place. Finally I unrolled all crucial parts into the main loop and used constants everywhere to rule out all other programming failures. Thanks for riddling this for me.
    – Ariser
    Aug 15, 2015 at 10:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.