I am having a problem getting my functions to run properly from the loop.
I am building a model railroad crossing gate and need to run the 4 processes once the sensor signals the train entering the crossing zone. For right now I need to learn how to "function" in C++ running in loop, as I'm a C# ASP.Net guy.
My thought process was in loop to
- Check entry sensor, this is all the loop should ever do UNTIL its tripped
- Once tripped, run the 4 processes functions (close the gates, turn on the sound, turn on the flashers, power down the road that crosses the tracks)
- After the entry sensor is tripped and the processes run (the sound and flashers should operate continuously until the exit sensor is cleared) the exit sensor must be monitored, and once its tripped, its monitored until it goes static again indicating the train has cleared. Then the 4 process are run to shut down the flashers, shut the sound, power up the road and raise the gates.
Right now I'm just trying to get the sound functioning properly. Right now, as the code stands, it plays for a split second then stops. I need that file playing until I call the function to stop it.
Here is the code so far. I've removed the includes and defines for brevity.
// These are the pins used for the music maker shield
#define SHIELD_RESET -1 // VS1053 reset pin (unused!)
#define SHIELD_CS 7 // VS1053 chip select pin (output)
#define SHIELD_DCS 6 // VS1053 Data/command select pin (output)
// These are common pins between breakout and shield
#define CARDCS 4 // Card chip select pin
// DREQ should be an Int pin, see http://arduino.cc/en/Reference/attachInterrupt
#define DREQ 3 // VS1053 Data request, ideally an Interrupt pin
// create shield-example object
Adafruit_VS1053_FilePlayer musicPlayer = Adafruit_VS1053_FilePlayer(SHIELD_RESET, SHIELD_CS, SHIELD_DCS, DREQ, CARDCS);
//global variables required
boolean crossingSignalInOperation = false;
boolean signalEntrySensor = false;
boolean signalExitSensor = false;
boolean debugMode = false;
boolean checkEntrySensor() {
//check entry sensor, if tripped return true otherwise false
/*if () return true;
return false;*/
return true; //hardcoded for testing
}
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
debugMode = true; //set this to false once code is fully operational
//initialise music maker shield
if (!musicPlayer.begin()) {
if (debugMode)
{
Serial.println(F("Couldn't find VS1053, do you have the right pins defined?"));
}
while (1);
}
// 1st parameter is left, second parameter is right
musicPlayer.setVolume(20, 20);
if (debugMode)
{
Serial.println(F("VS1053 found"));
}
// initialise the SD card
SD.begin(CARDCS);
//ensure our global booleans all start false
crossingSignalInOperation = false;
}
void loop() {
// put your main code here, to run repeatedly
signalEntrySensor = checkEntrySensor();
if (signalEntrySensor == true) { //only want to catch the FIRST time it senses
if (crossingSignalInOperation == false)
{
//crossing signal is inactive, activate it
// turn the sound on and return the music player object
Adafruit_VS1053_FilePlayer mp = onSignalSound(musicPlayer,"trainxng.mp3");
while (mp.playingMusic) {
// file is now playing in the 'background' so now's a good time
// to do something else
if (!crossingSignalInOperation)
{
crossingSignalInOperation = true;
//run the other three processes
//offRoadPower();
//lowerSignalArms();
//startSignalFlashers();
}
}
}
}
}
Here are the functions;
Adafruit_VS1053_FilePlayer onSignalSound(Adafruit_VS1053_FilePlayer mp, const char* fileName) {
// Play the train crossing sound file
if (debugMode)
{
Serial.println(F("Playing track - "));
Serial.print(fileName);
}
//all sound files are on the micro sd card on the music player shield
if (!mp.startPlayingFile(fileName)) {
Serial.println(F("Could not open file "));
Serial.print(fileName);
while (1);
}
return mp;
}
boolean offSignalSound(Adafruit_VS1053_FilePlayer mp) {
if (mp.playingMusic)
{
mp.stopPlaying();
return true;
}
return true;
}
Any assistance would be appreciated.
Update:
New version using the state machine methodology suggested by JRobert and Edgar Any comments on structure?
// include SPI, MP3 and SD libraries
#include <SPI.h>
#include <Adafruit_VS1053.h>
#include <SD.h>
// include AccelStepper libraries
#include <MultiStepper.h>
#include <AccelStepper.h>
using namespace std;
/*-----( Declare Enums & Constants )-----*/
// Arduino Digital I/O pin numbers for MEGA
//note: digital pins 30-45
//MEGA is a MUST for this project
enum {
Relay1 = 30, Relay2 = 31, Relay3 = 32, Relay4 = 33, Relay5 = 34, Relay6 = 35,
Relay7 = 36, Relay8 = 37, Relay9 = 38, Relay10 = 39, Relay11 = 40,
Relay12 = 41, Relay13 = 42, Relay14 = 43, Relay15 = 44, Relay16 = 45
};
enum { RELAY_OFF = HIGH, RELAY_ON = LOW };
enum {
CROSSINGZONE_CLEAR, // no train in crossing area, also initialized state
CROSSINGZONE_OCCUPIED, // train detected by the entry sensor
CROSSINGZONE_EXITING // train detected by the exit sensor, when sensor clears state = CLEAR
};
enum {
EXITSENSOR_CLEAR, // no train has passed the crossing gate exit sensor
EXITSENSOR_ENTERED, // train detected by the exit sensor
EXITSENSOR_WAITINGTOCLEAR // train passed by the exit sensor, when sensor clears state = CLEAR
};
byte state = CROSSINGZONE_CLEAR;
//*-----( Declare Variables )-----*/
// hardware is an optocoupler 16 channel 12V relay
const byte relays[] = { Relay1,Relay2,Relay3,Relay4,
Relay5,Relay6,Relay7,Relay8,
Relay9,Relay10,Relay11,Relay12,
Relay13,Relay14,Relay15,Relay16 };
enum { maxRelayCount = sizeof relays / sizeof relays[0] };
byte entrySensorPin = 26;
byte exitSensorPin = 28;
const char* soundFile = "trainxng.mp3";
boolean debugMode = true;
boolean exitSensorTripped = false;
// These are the pins used for the music maker shield
#define SHIELD_RESET -1 // VS1053 reset pin (unused!)
#define SHIELD_CS 7 // VS1053 chip select pin (output)
#define SHIELD_DCS 6 // VS1053 Data/command select pin (output)
// These are common pins between breakout and shield
#define CARDCS 4 // Card chip select pin
// DREQ should be an Int pin, see http://arduino.cc/en/Reference/attachInterrupt
#define DREQ 3 // VS1053 Data request, ideally an Interrupt pin
AccelStepper gateMotor1Left(AccelStepper::DRIVER, 4, 3); // 1=Stepper driver option, 4=Step pulse pin, 3=Direction
AccelStepper gateMotor1Right(AccelStepper::DRIVER, 6, 5); // 1=Stepper driver option, 6=Step pulse pin, 5=Direction
AccelStepper gateMotor2Left(AccelStepper::DRIVER, 8, 7); // 1=Stepper driver option, 8=Step pulse pin, 7=Direction
AccelStepper gateMotor2Right(AccelStepper::DRIVER, 10, 9); // 1=Stepper driver option, 10=Step pulse pin, 9=Direction
Adafruit_VS1053_FilePlayer musicPlayer =
Adafruit_VS1053_FilePlayer(SHIELD_RESET, SHIELD_CS, SHIELD_DCS, DREQ, CARDCS);
class CrossingGate {
public:
// hmm voids or booleans?
boolean GateFlashersOn(byte relayNumber) {
digitalWrite(relayNumber, RELAY_ON);
//need a check here to ensure its set LOW which is inactive for these relays
return true;
}
boolean GateFlashersOff(byte relayNumber) {
digitalWrite(relayNumber, RELAY_OFF);
//need a check here to ensure its set HIGH which is inactive for these relays
return true;
}
boolean GateArmFlashersOn(byte relayNumber) {
digitalWrite(relayNumber, RELAY_ON);
//need a check here to ensure its set LOW which is inactive for these relays
return true;
}
boolean GateArmFlashersOff(byte relayNumber) {
digitalWrite(relayNumber, RELAY_OFF);
//need a check here to ensure its set HIGH which is inactive for these relays
return true;
}
boolean GatesUp(byte relayNumber) {
digitalWrite(relayNumber, RELAY_ON);
//need a check here to ensure its set LOW which is inactive for these relays
return true;
}
boolean GatesDown(byte relayNumber) {
digitalWrite(relayNumber, RELAY_OFF);
//need a check here to ensure its set HIGH which is inactive for these relays
return true;
}
};
class CrossingGateRoad {
public:
void PowerOn(byte relayNumber) {
digitalWrite(relayNumber, RELAY_ON);
}
void PowerOff(byte relayNumber) {
digitalWrite(relayNumber, RELAY_OFF);
}
};
// Sensor methods
int CheckEntrySensor(byte esp) {
if (digitalRead(esp) == HIGH)
{
state = CROSSINGZONE_CLEAR;
return CROSSINGZONE_CLEAR;
}
else {
state = CROSSINGZONE_OCCUPIED;
return CROSSINGZONE_OCCUPIED;
}
return -1;
}
int CheckExitSensor(byte xsp) {
if (digitalRead(xsp) == HIGH && state == CROSSINGZONE_OCCUPIED)
{
state = CROSSINGZONE_OCCUPIED;
return CROSSINGZONE_OCCUPIED;
}
else if(digitalRead(xsp) == LOW && state == CROSSINGZONE_OCCUPIED)
{
state = CROSSINGZONE_CLEAR;
return CROSSINGZONE_OCCUPIED;
}
return -1;
}
boolean CrossingBellsOn(Adafruit_VS1053_FilePlayer & mp, const char* soundFile) {
if (!mp.startPlayingFile(soundFile)) {
Serial.println("Could not open file trainxng.mp3 using the passed in object");
while (1);
}
return true;
}
boolean CrossingBellsOff(Adafruit_VS1053_FilePlayer & mp) {
if (mp.playingMusic) {
mp.stopPlaying();
return true;
}
if (mp.stopped())
{
return true;
}
return false;
}
CrossingGate xGate1Left;
CrossingGate xGate1Right;
CrossingGate xGate2Left;
CrossingGate xGate2Right;
CrossingGateRoad xGateRoad;
void setup() {
Serial.begin(9600);
// Set max speed. Steps per second
gateMotor1Left.setMaxSpeed(500);
gateMotor2Left.setMaxSpeed(500);
gateMotor1Right.setMaxSpeed(500);
gateMotor2Right.setMaxSpeed(500);
// set motor acceleration
gateMotor1Left.setAcceleration(35);
gateMotor2Left.setAcceleration(35);
gateMotor1Right.setAcceleration(35);
gateMotor2Right.setAcceleration(35);
// set motor speeds
gateMotor1Left.setSpeed(20);
gateMotor2Left.setSpeed(20);
gateMotor1Right.setSpeed(20);
gateMotor2Right.setSpeed(20);
Serial.println("Adafruit VS1053 Library Test");
// initialise the music player
if (!musicPlayer.begin()) { // initialise the music player
Serial.println(F("Couldn't find VS1053, do you have the right pins defined?"));
while (1);
}
Serial.println(F("VS1053 found"));
//musicPlayer.sineTest(0x44, 500); // Make a tone to indicate VS1053 is working
// check the card reader
if (!SD.begin(CARDCS)) {
Serial.println(F("SD failed, or not present"));
while (1);
}
Serial.println("SD OK!");
// Set volume for left, right channels. lower numbers == louder volume!
musicPlayer.setVolume(20, 20);
// This option uses a pin interrupt. But DREQ must be on an
// interrupt pin. For Uno/Duemilanove/Diecimilla that's Digital #2 or #3
// See http://arduino.cc/en/Reference/attachInterrupt for other pins
// * This is the preferred method
if (!musicPlayer.useInterrupt(VS1053_FILEPLAYER_PIN_INT))
Serial.println(F("DREQ pin is not an interrupt pin"));
}
void loop() {
switch (state) {
case CROSSINGZONE_CLEAR:
// Check entry sensor. If tripped, turn on the sound.
if (CheckEntrySensor(entrySensorPin) == 1) {
// first go around i'll try to tun all these here otherwise
// I will break out to more states
//these happen instantly
xGateRoad.PowerOff(relays[0]);
xGate1Left.GateArmFlashersOn(relays[1]);
xGate1Right.GateArmFlashersOn(relays[2]);
xGate2Left.GateArmFlashersOn(relays[3]);
xGate2Right.GateArmFlashersOn(relays[4]);
xGate1Left.GateFlashersOn(relays[5]);
xGate1Right.GateFlashersOn(relays[6]);
xGate2Left.GateFlashersOn(relays[7]);
xGate2Right.GateFlashersOn(relays[8]);
//these take some time
xGate1Left.GatesDown(relays[9]);
xGate1Right.GatesDown(relays[10]);
xGate2Left.GatesDown(relays[11]);
xGate2Right.GatesDown(relays[12]);
//this is the last process, if it completes change state
if (CrossingBellsOn(musicPlayer, soundFile)) {
state = CROSSINGZONE_OCCUPIED;
}
}
else {
state = CROSSINGZONE_CLEAR;
}
break;
case CROSSINGZONE_OCCUPIED:
// Once zone is occupied, monitor the exit sensor
if (CheckExitSensor(exitSensorPin) == 0) { // Exit sensor not tripped yet
state = CROSSINGZONE_OCCUPIED;
}
else {
exitSensorTripped = true;
}
if (exitSensorTripped)
{
// need to code here to wait until Exit Sensor has 1500 milliseconds where its clear
// to indicate that the last car of train has passed it
// if above set exitSensorTripped back to false and state to EXITING
state = CROSSINGZONE_EXITING;
}
break;
case CROSSINGZONE_EXITING:
// Check if the train has cleared, then shut the sound.
if (CheckExitSensor(exitSensorPin) == 1) {
// first go around i'll try to tun all these here otherwise
// I will break out to more states
//these happen instantly
xGateRoad.PowerOn(relays[0]);
xGate1Left.GateArmFlashersOff(relays[1]);
xGate1Right.GateArmFlashersOff(relays[2]);
xGate2Left.GateArmFlashersOff(relays[3]);
xGate2Right.GateArmFlashersOff(relays[4]);
xGate1Left.GateArmFlashersOff(relays[5]);
xGate1Right.GateArmFlashersOff(relays[6]);
xGate2Left.GateArmFlashersOff(relays[7]);
xGate2Right.GateArmFlashersOff(relays[8]);
//these take some time
xGate1Left.GatesUp(relays[9]);
xGate1Right.GatesUp(relays[10]);
xGate2Left.GatesUp(relays[11]);
xGate2Right.GatesUp(relays[12]);
//this is the last process, if it completes change state
if (CrossingBellsOff(musicPlayer)) {
state = CROSSINGZONE_CLEAR;
}
}
break;
}
}
EXITSENSOR_*
,maxRelayCount
,GateFlashersOff()
, theAccelStepper
s). The state should have the proper enum as its type instead ofbyte
. Do not change the state in the checkSensor() methods. Failure of playing the sound should not prevent the state to become OCCUPIED...exitSensorTripped
is useless:case CROSSINGZONE_OCCUPIED: if (CheckExitSensor()) state = CROSSINGZONE_EXITING; break;
is enough. The variouselse { state = the_current_state; }
are useless. The return values of the CrossingGate methods are never checked. The pin numbers should be private data of CrossingGate rather than method parameters. Only two methods are needed in CrossingGate: open() and close(), the details can be taken care of by the class.