Photon SPI line sometimes not working with MCP23S17

Hello, I am experiencing a weird issue with using a GPIO extender through SPI on the Photon. Basically, I have dozens of Photons that don’t work with a binary, and dozens of Photons that do work with the same binary. I am running the latest system firmware on the Photon, and have noticed the problem consistently since v0.4.7

I have also noticed a similar problem that I think is related. When I connect to the Photon through a serial port immediately after plugging the Photon in, the GPIO extender refuses to switch. When I power cycle and wait a little longer to access the Photon via serial, the SPI works fine and the GPIO extender switches as it should. Is there anything I should know about the system firmware in regards to Photon start-up time and serial/SPI timing?

Here is my user code:

#include "MFRC522.h"
#include "MCP23S17.h"
#define SS_PIN SS
#define RST_PIN A7

MFRC522 mfrc522(SS_PIN, RST_PIN);	// Create MFRC522 instance.
MCP GPIO(0, A1);    // create an object at address 0, SPI slave select on pin A1

String firmwareVersion = "0.0.3.4";
String rfid = "";

int serialMode;

const int SERIAL_MODE_READ = 0;
const int SERIAL_MODE_SCAN_MANUAL = 1;
const int SERIAL_MODE_THEATRICAL = 2;
const int SERIAL_MODE_SCAN_AUTO = 3;
const int SERIAL_MODE_REPORT_DEVICE = 4;
const int SERIAL_MODE_CONNECT = 5;

char endMarker = '\n';
static byte ndx = 0;
const byte numChars = 64;
char receivedChars[numChars];
boolean newData = false;
boolean debugMode;

int antennaAddress[16] = {
  0B0000000000000000,
  0B0000000100000000,
  0B0000001100000000,
  0B0000001000000000,
  0B0000011000000000,
  0B0000111000000000,
  0B0000101000000000,
  0B0001101000000000,
  0B0011101000000000,
  0B0010101000000000,
  0B0110101000000000,
  0B1110101000000000,
  0B1010101000000000,
  0B1010101000000001,
  0B1010101000000011,
  0B1111111111111111
};

char COMMAND_THEATRICAL = 'T';
char COMMAND_LISTEN = 'X';
char COMMAND_CONNECT = 'C';
char COMMAND_SCAN_MANUAL = 'R';
char COMMAND_SCAN_AUTOMATIC = 'A';
char COMMAND_INFO = 'I';
char COMMAND_HEARTBEAT = 'H';

char ACTION_ADD_PERFORMER = 'A';
char ACTION_REMOVE_PERFORMER = 'R';

// MODE: SCAN MANUAL 
int antennasRequestedForScan[15] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1};
int antennasRequestedForScanCount = 0;

// MODE: THEATRICAL
boolean performingAntennas[15] = {
 false,false,false,false,false,
 false,false,false,false,false,
 false,false,false,false,false 
};

int performanceOrder[15]= {
  -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
};

int performingAntenna = -1;
int performingAntennaCount = 0;

int performanceDuration;
int performanceFrames[56];
int performanceFramesTotal;


// REPORTING SETUP 
const int antennaTotal = 15;  // 15 antennas per antenna board
const int scanInterval = 5;
int antennaScanTarget = 0;

int boardPosition;  // will be unique to each board

int currentTag = 0;

struct tagReport {
    char uuid[13] = "null";
    int rssiValue = 0;
};

int tagsPerAntennaReport = 4;
struct tagReport tagReportCache[4];  // each antenna can detect up to 4 RFID tags
// END REPORTING SETUP

SYSTEM_MODE(SEMI_AUTOMATIC);
//SYSTEM_MODE(MANUAL);

void setup() {
        //setWiFiCredentials();
        //connectToSparkCloud();
 	//WiFi.off();
        debugMode = false;
        //debugMode = true;
    
        // hardcoded for each microcontroller/board pair
        boardPosition = 0;
  
        GPIO.pinMode(antennaAddress[0]);	
        
        Serial.begin(9600);	// Initialize serial communications with the PC
	SPI.begin();			// Init SPI bus
	SPI.setClockDivider(SPI_CLOCK_DIV8);
	
        mfrc522.PCD_Init();	// Init MFRC522 card
        
        serialMode = SERIAL_MODE_READ;
        
        //mfrc522.PCD_AntennaOn();
        
        //mfrc522.PCD_SetAntennaGain(mfrc522.RxGain_max);
        //mfrc522.PCD_SetAntennaGain(mfrc522.RxGain_48dB);
        // RC522 Max antenna Gai
	//Added code to void setup() to set antenna gain to 
        // max value of 48dB at initialization. (default antenna gain value is 33dB) 
        // Can also be set to max by mfrc522.RxGain_max, described in lines 179-189 of MFRC522.h file.
        
        
        //mfrc522.PCD_AntennaOff();
        changeAntennaScanTarget(16);        
}

void setWiFiCredentials() {
    WiFi.on();
    
    WiFi.clearCredentials();
    
    WiFi.setCredentials("ShadysWorld", "WaterMelon!@34");
    //WiFi.setCredentials("olaunch", "charface123",WPA);
    WiFi.setCredentials("Apple Network Base", "palpalwelcome");
    WiFi.setCredentials("Verizon-SM-G900V-FC42", "hcky808@");
    //WiFi.setCredentials("Roger","ultraMonkey123");
    WiFi.setCredentials("Lemnos Labs","hardwarerevolution");
    WiFi.setCredentials("Mom", "Raymond6");

    WiFi.connect();
}

void debug(String message) {
   if (debugMode) {
     synchronousSerialPrintln(message);
   }
}

void connectToSparkCloud() {
  if (Spark.connected() == false) {
    debug(String("Connecting to cloud"));
    Spark.connect();
  }
}

void synchronousSerialPrintln(String message) {
  Serial.println(message);
  clearSerialBuffers();
}

void clearSerialBuffers() {
  Serial.flush();
//  while (Serial.available()) {
//    Serial.read();
//  }  
}

void loop() {
    //Spark.process(); // necessary for MANUAL MODE
    
    if (serialMode == SERIAL_MODE_READ) {
      //debug(String("MODE: READ"));
      recvWithEndMarker();
      parseNewData();
    
    } else if (serialMode == SERIAL_MODE_CONNECT) {
      debug(String("MODE: CONNECT TO CLOUD"));
      //connectToSparkCloud();
      switchSerialMode(SERIAL_MODE_READ);
    
    } else if (serialMode == SERIAL_MODE_REPORT_DEVICE) {
      debug(String("MODE: SCAN REPORT DEVICE"));
      reportDeviceInfo();
      switchSerialMode(SERIAL_MODE_READ);
      
    } else if (serialMode == SERIAL_MODE_SCAN_AUTO) {
      debug(String("MODE: SCAN AUTO"));
      
      //mfrc522.PCD_AntennaOn();
      clearCaches();
  
      for (int antenna=0; antenna<antennaTotal; antenna++) {
          
          scanAntenna(antenna);
          reportAntenna(antenna,boardPosition);
          clearCaches();
          
          recvWithEndMarker();
          parseNewData();
      }
      
      //changeAntennaScanTarget(16);
      //mfrc522.PCD_AntennaOff();
      //switchSerialMode(SERIAL_MODE_READ);
      
      recvWithEndMarker();
      parseNewData();
      
    } else if (serialMode == SERIAL_MODE_SCAN_MANUAL) {
      debug(String("MODE: SCAN MANUAL"));
      
      scanAntennaGroup(antennasRequestedForScanCount);
      clearAntennaGroupScanCaches();
      
      // do not leave an antenna energized after it was scanned
      changeAntennaScanTarget(16);
      
      switchSerialMode(SERIAL_MODE_READ);
    }
//    
//    } else if (serialMode == SERIAL_MODE_THEATRICAL) {
//      debug(String("MODE: THEATRICAL"));
//      
//      playLightingPattern(performingAntenna, performanceDuration, performanceFramesTotal);
//      
//    }  
    
    // illumination sequences are co-routines that energize an entenna frame by frame
    // if there are any antennas to illuminate
    // spend time on the next frame of an illumation sequence
    // then rotate through illumation sequences
    illuminate();
      
    //delay(1000); // FOR DEBUGGING
}


// SERIAL TX / RX
//===============

void recvWithEndMarker() {
 
  char myChar;
  
  while (Serial.available() > 0 && newData == false) 
  {
    debug(String("received data:"));
    
    myChar = Serial.read();
    debug(String(myChar));
    
    if (myChar != endMarker) {
      receivedChars[ndx] = myChar;
      ndx++;
      debug(String(ndx));
      
      if (ndx >= numChars) { // the buffer is full, so clear it out
        debug(String("receive buffer full."));
        clearRXBuffer(numChars-1);
      }
      
    } else { // received an end marker before the buffer was full
      debug(String("end of command string."));
      clearRXBuffer(ndx);
    }
  }
}

void clearRXBuffer(byte index) {
  debug(String("clearRXBuffer"));
  debug(String(index));
  
  receivedChars[index] = '\0'; // terminate the string
  newData = true;
  ndx = 0;
}

void parseNewData() {
  if (newData) {
    debug(String("parseNewData:"));
    debug(String(receivedChars));
    parseServerCommand(String(receivedChars));
    newData = false;
  }
}

void clearCaches() {
    
    for (int i=0;i<4;i++) {
        // clear uuid cache
        strcpy(tagReportCache[i].uuid,"null");
        // clear RSSI cache
        tagReportCache[i].rssiValue = 0;
    }
    
    currentTag = 0;
}


void parseServerCommand(String message) {
  debug(String("parseServerCommand:"));
  debug(String(message));
  
  String commandType = message.substring(0,1);
  
  if (commandType.equals(String(COMMAND_LISTEN)) || commandType.equals(toLowerCaseX(String(COMMAND_LISTEN)))) {
    debug(String("COMMAND: LISTEN"));
    //clearAntennaTarget(); // in case, current scan mode AUTO, clear last antenna target
    switchSerialMode(SERIAL_MODE_READ);
  
  } else if (commandType.equals(String(COMMAND_HEARTBEAT)) || commandType.equals(toLowerCaseX(String(COMMAND_HEARTBEAT)))) {
    debug(String("COMMAND: HEARTBEAT"));
    sendHeartbeat();
  
  } else if (commandType.equals(String(COMMAND_CONNECT))) {
    debug(String("COMMAND: CONNECT TO CLOUD"));
    switchSerialMode(SERIAL_MODE_CONNECT);
  
  } else if (commandType.equals(String(COMMAND_INFO)) || commandType.equals(toLowerCaseX(String(COMMAND_INFO)))) {
    debug(String("COMMAND:REPORT DEVICE INFO"));
    
    switchSerialMode(SERIAL_MODE_REPORT_DEVICE);
    
  } else if (commandType.equals(String(COMMAND_SCAN_AUTOMATIC)) || commandType.equals(toLowerCaseX(String(COMMAND_SCAN_AUTOMATIC)))) {  //if (commandType == "A") {
    debug(String("COMMAND: SCAN AUTO"));
    
    switchSerialMode(SERIAL_MODE_SCAN_AUTO);
  
  
  } else if (commandType.equals(String(COMMAND_SCAN_MANUAL)) || commandType.equals(toLowerCaseX(String(COMMAND_SCAN_MANUAL)))) { //} else if (commandType == "R") {
    debug(String("COMMAND: SCAN MANUAL"));
    
    // request scan
    // "R3237"
    // "R" - request scan
    // 3 - number of antennas to scan
    // 237 - scan antennas at indices 2,3,7
    
    clearAntennaGroupScanCaches();
    
    int antennasToScan = message.charAt(1) - '0';
    
    debug(String("TOTAL ANTENNAS TO SCAN:"));
    debug(String(antennasToScan));
    
    for (int i=0;i<antennasToScan;i++) {
       // index is hexidecimal char
      
       //int antennaIndex = message.charAt(2+i) - '0';
       char hexChar = message.charAt(2+i);
       int antennaIndex = (hexChar > '9') ? (hexChar &~ 0x20) - 'A' + 10 : (hexChar - '0');
       //int antennaIndex = intFromHexChar(index);
       
       antennasRequestedForScan[i] = antennaIndex;
       debug(String("ANTENNA TO SCAN:"));
       debug(String(antennaIndex));
    }
    
    antennasRequestedForScanCount = antennasToScan;
    
    switchSerialMode(SERIAL_MODE_SCAN_MANUAL);
  

  } else if (commandType.equals(String(COMMAND_THEATRICAL))) {     //} else if (commandType == "T") { 
    debug(String("COMMAND: THEATRICAL"));
    
    // add antenna for illumination, light ON
    // TA0
    
    // remove antenna from illiumincation, light OFF
    // TR0
    
    
    String action = message.substring(1,2);
    
    
    debug(String("Action:"));
    debug(String(action));
    
    char hexChar = message.charAt(2);
    //int antennaIndex = intFromHexChar(index);
    int antennaIndex = (hexChar > '9') ? (hexChar &~ 0x20) - 'A' + 10 : (hexChar - '0');
    debug(String("Index:"));
    debug(String(antennaIndex));
    
    if (action.equals(String(ACTION_ADD_PERFORMER))) {
      performingAntennas[antennaIndex] = true;
      debug(String("adding performer"));
      
    } else if (action.equals(String(ACTION_REMOVE_PERFORMER))) {
      performingAntennas[antennaIndex] = false;
      debug(String("removing performer"));
    }
    
    updatePerformanceOrder();
//    // "T01999900123456789..."
//    // "T" - 0 Theatrical mode
//    // "01" - 1,2 antenna index
//    // "9999" - 3,6 duration of playback in miliseconds
//    // "00" - 7,8 totalFrames
//    // "123456789..." - 9,N lighting pattern frames (up to 56 char (64-8))
//    // pattern frame value 0-9
  
//    performanceFramesTotal = message.substring(7,9).toInt();
//    performingAntenna = message.substring(1,3).toInt();
//    performanceDuration = message.substring(3,7).toInt();
//  
//    // cache frames
//    for (int i=0;i++;i<performanceFramesTotal) {
//       int frameValue = message.charAt(9+i) - '0';
//       performanceFrames[i] = frameValue;
//    }
//    
//    switchSerialMode(SERIAL_MODE_THEATRICAL);
  }
    
}

String toLowerCaseX(String message) {
    return message.toLowerCase();
}

int intFromHexChar(char hexChar) {
  return (hexChar > '9') ? (hexChar &~ 0x20) - 'A' + 10 : (hexChar - '0');
}


// THEATRICAL MODE
//================


void illuminate() {
  
  if (performingAntennaCount==0) return;
   
   performingAntenna = getPerformingAntenna();
   playNextIlluminationFrame(performingAntenna);
   
}

void updatePerformanceOrder() {
  
  changeAntennaScanTarget(16); // clear current target
  performingAntenna = -1;
  
  for (int i=0;i<15;i++) {
    performanceOrder[i] = -1;
  }
  int performerIndex = 0;
  
  for (int i=0;i<15;i++) {
    if (performingAntennas[i]) {
      debug(String("performer found:"));
      debug(String(i));
      
      performanceOrder[performerIndex] = i; 
      performerIndex++; 
    }
  }
  
  performingAntennaCount = performerIndex;
  debug(String("performingAntennaCount:"));
  debug(String(performingAntennaCount));
}



int getPerformingAntenna() {
  
  if (performingAntenna==-1 || performingAntennaCount==1 || performingAntenna==performanceOrder[performingAntennaCount-1]) {
   return performanceOrder[0]; 
  }
  
  int antenna;
  
  for (int i=0;i<performingAntennaCount;i++) {
    if (performingAntenna==performanceOrder[i]) {
     antenna = performanceOrder[i+1]; 
    }
  }
  
  return antenna;
    
}

void playNextIlluminationFrame(int antennaIndex) {
    changeAntennaScanTarget(antennaIndex);
    
    // TODO track frame across predetermined sequence of brightness
    
    int frameBrightness = 100;
    
    // TODO set brightness by antenna gain?
    
    energizeAntenna();
}


// RFID SCANNING FUNCTIONS
//========================

void scanAntennaGroup(int antennaGroupCount) {
  debug(String("scanAntennaGroup..."));
  debug(String(antennaGroupCount));
  debug(String("..."));
  
  for (int i=0;i<antennaGroupCount;i++) {
    
    int antenna = antennasRequestedForScan[i];
    debug(String("antenna:"));
    debug(String(antenna));
    
    scanAntenna(antenna);
    reportAntenna(antenna,boardPosition);

    clearCaches();
  }
}

void clearAntennaGroupScanCaches() {
  
  antennasRequestedForScanCount = 0;
  
  for (int i=0;i++;i<antennaTotal) {
    antennasRequestedForScan[i] = -1;
  }  
}

void changeAntennaScanTarget(int index) {
    GPIO.digitalWrite(antennaAddress[index]);    //Activate / energize antenna
}


void scanAntenna(int index) {
  
    changeAntennaScanTarget(index);
  
    int tagsFound = 0;
  
    delay(scanInterval);    // Antenna will not read unless this delay is present
    // TODO investigating timing issue
    // 5-10ms seems to be the "sweet spot" where scans are not missed/dropped
    
    getTagUUID();
   
    if (rfid != ""){
      
      tagsFound++;
      
      if (tagsFound<=tagsPerAntennaReport) {
        
        // tags with 4 byte-pairs are 11 characters long with padding
        // tags with 7 byte-pairs are 20 characters long with padding
        int tagIdLength = 13;
        char uuid[tagIdLength];
        rfid.toCharArray(uuid,tagIdLength);
        
        int rssiValue = 1; // placeholder, default value
        // TODO get actual RSSI value
        
        pushTag(uuid,rssiValue); 
      }
    }
      
    rfid = "";
}

boolean getTagUUID(){
  
  if (!energizeAntenna()) return false;
  
  for (byte i = 0; i < mfrc522.uid.size; i++) {
    // each uidByte has a value of 0-32 represented as 2 hex cahracters
    // so pad a value of 0-16 with a "0"
    rfid += mfrc522.uid.uidByte[i] < 0x10 ? " 0" : " ";
    rfid += String(mfrc522.uid.uidByte[i], HEX);
    rfid.trim();
    rfid.toUpperCase();
  }
  
  return true;
}

void pushTag(char uuid[20], int rssiValue) {

    if (currentTag>=tagsPerAntennaReport) return;

    strcpy(tagReportCache[currentTag].uuid,uuid);
    tagReportCache[currentTag].rssiValue = rssiValue;
    
    currentTag++;
}

boolean energizeAntenna() {
  // MUST check if card is present before reading
  if ( ! mfrc522.PICC_IsNewCardPresent()) {
                return false;
  }
  if ( ! mfrc522.PICC_ReadCardSerial()) {
		return false;
  }
  
  return true;
}


// SERiAL REPORTING
//=================

void sendHeartbeat() {
  String frame = String("{"); // open frame
  frame+="\"typ\":\"hbt\"";
  frame+=",";
  frame+="\"brd\":";
  frame.concat( String(boardPosition) );
  frame+="}"; // close frame
  synchronousSerialPrintln(frame);
}

void reportDeviceInfo() {
  String frame = String("{"); // open frame
  frame+="\"typ\":\"dev\"";
  frame+=",";
  frame+="\"ver\":\"";
  frame.concat( firmwareVersion );
  frame+="\"";
  frame+=",";
  frame+="\"brd\":";
  frame.concat( String(boardPosition) );
  frame+="}"; // close frame
  synchronousSerialPrintln(frame);
}

void reportAntenna(int antennaId, int boardPosition) {
    
    String frame = String("{"); // open frame
    // construct JSON frame
    frame+="\"typ\":\"ant\",";
    
    frame+="\"brd\":";
    frame.concat( String(boardPosition) );
    frame+=",";
    frame+="\"ant\":";
    frame.concat( String(antennaId) );
    frame+=",";
    
    frame+="\"tgs\":[";
    // each tag found

    for (int tag=0; tag<currentTag;tag++) {
        
        frame+="[\"";
        frame.concat( String(tagReportCache[tag].uuid) );
        frame+="\",";
        frame.concat( String(tagReportCache[tag].rssiValue) );
        frame+="]";
        
        if (tag<currentTag-1) {
            frame+=",";
        }
        
    }
    frame+="]";

    frame+="}"; // close frame
    synchronousSerialPrintln(frame);
}    

void switchSerialMode(int mode) {
    serialMode = mode;
}

void playLightingPattern(int antenna, int totalDuration, int totalFrames) {
    
  int currentTime = 0;
  int frameDuration = totalDuration / totalFrames;
  
  changeAntennaScanTarget(antenna);
  
  for (int f=0;f<totalFrames;f++) {
    
    int frameTime = 0;
    int frameValue = performanceFrames[f];
    
    while (frameTime<frameDuration) {

      // higher intensity = shorter delay between energizing
      int intensityDelay;
      
      switch (frameValue) {
        case 1: 
          energizeAntenna();
          intensityDelay = scanInterval;
        break;
        case 0:
          intensityDelay = frameDuration;
        break;
      }
      
      delay(intensityDelay);
      
      frameTime+=intensityDelay;
    }
    
  }   
  
}

@John1

I have experienced something similar with an SPI TFT display and another SPI peripheral - the solution was to put some time critical setups like pinMode() in a function called from STARTUP(). I realise that these may be inside the 2 libraries you call here. There did not seem to be any logic I just had to find out by trial and error. Have you tried swapping the object create instance calls?

As an aside does the MCP23S17 work well otherwise?

@armor
By STARTUP() do you mean setup()? Because currently all of the setup processes necessary, time critical or not, are in the setup() function. I’ll investigate the libraries that I’m using to see if they also are pinMode() from setup(). I can try putting the object creation inside the setup() function as well, but the weird thing is that the same exact compiled binaries work on some photons and not on others. Meaning I get consistently bad switching with a MCP23S17 with one photons, I’ll swap it out with another photon flashed with the same binary, and I’ll get consistently good switching. Other times I have to wait 10 - 15 seconds before connecting to the Photon with a serial connection on my computer and it will start working with the MCP23S17.

The MCP23S17 works well otherwise, meaning I’ve tested dozens of different chips and they will all work with a Photon that works. The same MCP23S17 will not behave with a “faulty” Photon.

@armor
I found the STARTUP() macro from the Particle References. Thanks for bringing it up, I didn’t know such a macro existed and I’ll test out if I get better results using the time-sensitive STARTUP()

Better example

SYSTEM_MODE(SEMI_AUTOMATIC);
SYSTEM_THREAD(ENABLED);
// Since Firmware 0.4.5 time and value critical I/O pin setting a.k.a display is done here
STARTUP( setup_io() );

of what I have at the start of sketches using TFT on SPI. This is specifically because the spec for the TFT signals demands very short time to pull one of the lines low to reset after the power is applied.

Another possibility with your “faulty” Photon - have you tested each GPIO pin is working? I have one Photon that has a unreliable D0 pin - I spent a long time wondering why I2C didn’t work. I think it was faulty from receipt and Particle claim I must have fried it! :frowning:

Another thought, do you know what the photon is doing during the 10-15 seconds wait? I suspect the photon is trying to connect to wifi and cloud on the system thread - I didn’t see any form of timeout near your Spark.connect().