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
IODIRB
,GPIOA
,GPIOB
.