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;
}
}
}