Need best choice when dealing with multiple serial things

Hi gang.

I am building a jack o lantern which will flicker as if there is a candle inside normally. When a PIR sensor sees someone walk up, it will switch to a mode where it plays a spooky voice and synchronizes a flashing red light with the voice. Then it will go back to the flicker mode.

For the lighting I am using a five element strip LED and the FastLED library.
For the sound I am using a DFPlayer min with the DFPlayerMini_Fast library referenced here.

I have been constructing this one piece at a time.
I added the lighting and got the flicker mode working.
I got the red voice light working using a temporary solution that just randomly flashes the light for now.
Then I added the PIR sensor and was able to change modes based on it.
I am now adding the voice player.
Later I will try to synchronize the red voice light to the voice audio.

I am having some trouble with playing the voice while the LEDs are flashing (randomly flashing for now).
When the voice starts to play, the red LEDs begin flashing in a wonky, stuttering sort of way.

I am currently trying to use the isPlaying() method of the DFPlayerMini_Fast library to determine if the sound clip is still playing. Since this function reads serial data (I think) coming from the DFPlayer mini TX port back to the Arduino, I am wondering if this extra serial data may be interfering with the serial data used to control the LED strip. I have read that those strips need precise timing and I thought this might be the case though I'm not sure.

I believe I have found a different way to tell if the player is still playing the audio or not. I am wondering if it would make more sense to use this type of approach and was hoping that y'all could give me some guidance.

In this schematic you will see that the author connected the busy pin (16) on the player to the Arduino and then monitored that pin to know if it was actively playing audio.

I was thinking this might help my problem, though I truthfully don't even know if the isPlaying() method even uses serial data in the first place.

I am including my code as it sits right now as a reference. I tried adding it in code tags but was told that it exceeds the 9000 character limit, so I have included it as an attachment. Bear in mind that it is a work in progress and I am only up to the point of trying get the audio to play while some random LEDs flash at this point.

Thank you for your time and effort.

WackOJackO.ino (6.83 KB)

This is a pretty kooky way to use millis(). 'timerStart' should be set to the current value of millis(), not zero:

	// If the timer has expired, then return value true.
	if (currentMillis - timerStart >= delayTime) {
		returnValue = true;

		// Restart the flicker timer.
		timerStart = 0;

You should have a look at millis() examples. You would see the difference right away.

The demo Several Things at a Time illustrates the use of millis() to manage timing without blocking. It may help with understanding the technique.

Have a look at Using millis() for timing. A beginners guide if you need more explanation.

...R

Thank for your input guys.

@Robin2 The timer is not the issue in this case though. It is non-blocking.

@aarg I get what you mean about setting it to zero, however doing so allows it to be automatically be set to millis() on the next loop as well as to set a random delay at the same time. Waiting one loop cycle to determine a flicker value didn't seem like a big deal.

/*
PumpkinLED

Project to make a jack o'lantern which will light up and flicker in it's normal mode. This should look like a candle inside
the jack o'lantern.  Eventually it will hopefully use a PIR sensor to detect a person and then switch to a mode where it will 
flash red in tune with speaking some manner of Halloweenish voice or soemthing. Then it will switch back to flicker mode.

Currently:
There is a set of individually addressable LEDs which produce a candle flicker effect. 
There is one red LED which is always on and additional LEDs that light up in oragne based on the intensity of the flicker.

There is a red flashing mode to simulate the talking mode. A PIR sensor is used to switch into the red flashing/talking mode.

Todo:
* Add a two minute delay prior to looking at the PIR sensor to allow it to settle.
* Figure out the best PIR delay and sensitivity settings.
* Use smaller types where possible.
* Make it possible to disable the talking and/or red flashing mode.

Eventually:
* I would like to make the red blink syncronize with the MP3 voice track.

Author: Mitch Moss
Date: April 7, 2020
*/

#include <FastLED.h>                                 // Used to control the individually addressable LED(s).
#include <SoftwareSerial.h>                          // Need to send serial commands to the DFPlayer Mini.
#include <DFPlayerMini_Fast.h>                       // Used to control the DFPlayer mini mp3 player.

// #define DEBUG_PIR                                 // Uncomment to turn on debugging.



// LED parameters.
const byte LED_PIN = 3;                              // Pin for LED data transmission.
const byte NUM_LEDS = 5;                             // Number of LEDs being used.
CRGB leds[NUM_LEDS];                                 // Define the leds array.

// PIR parameters.
const byte PIR_PIN = 7;                              // Pin for the PIR sensor.

// MP3 player parameters.
const byte MP3_TX_PIN = 11;                          // Pin to transmit serial data to the DFPlayer Mini.
const byte MP3_RX_PIN = 10;                          // Pin to receive serial data from the DFPlayer Mini.
const int MP3_VOLUME = 15;                           // Set the volume of the mp3 player. Range 0 - 30.
SoftwareSerial mySerial(MP3_RX_PIN, MP3_TX_PIN);     // RX, TX.
DFPlayerMini_Fast myMP3;                             // Instantial an mp3 object.


// Define variables.
int minFlicker = 20;                                // Shortest period of time to flicker.
int maxFlicker = 100;                               // Longest period of time to flicker.
bool redState = false;                              // State of the Red LED(s) in RED_BLINK mode.

// Define types.
enum Modes { ORANGE_FLICKER, RED_BLINK };
Modes mode = ORANGE_FLICKER;

// Debug stuff.
#ifdef DEBUG_PIR   
	#define DPRINT(...)    Serial.print(F(__VA_ARGS__)     // DPRINT is a macro, debug print.
  	#define DPRINTLN(...)  Serial.println(F(__VA_ARGS__)   // DPRINTLN is a macro, debug print with new line.
	#define DELAY(...)    delay(__VA_ARGS__)               // DELAY is a macro.
#else
  	#define DPRINT(...)                                  // Now defines a blank line.
  	#define DPRINTLN(...)                                // Now defines a blank line.
	#define DELAY(...)                                   // Now defines a blank line.
#endif




// Setup stuff.
void setup() {
	// Connect the LED strip.
	FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, NUM_LEDS);

	// Default it off.
	FastLED.clear();
	FastLED.show();

	// Fire up the DFPlayer Mini.
	mySerial.begin(9600);
  	myMP3.begin(mySerial);
  	myMP3.volume(MP3_VOLUME);

	// Pin setup.
	pinMode(PIR_PIN, INPUT);

	#ifdef DEBUG_PIR
   	 Serial.begin(9600);
  	#endif

}



// Main loop.
void loop() {

	// Handle each mode.
	switch (mode) {
		case ORANGE_FLICKER:
			// Set the flicker durations.
			minFlicker = 20;
			maxFlicker = 80;

			// If the PIR sensor is activated then switch to RED_BLINK mode.
			if (isPirSensorActivated()) {
				mode = RED_BLINK;
				break;
			}

			// If the timer has expired then change the LED values to a random value.
			if (hasTimerExpired()) {
				// Set the LEDs to a random intensity value. 
				doFlickerEffect();
			}

			break;


		case RED_BLINK:
			// Set the flicker durations.
			minFlicker = 80;
			maxFlicker = 170;

			// If the PIR sensor is not activated and we are not playing an mp3 then switch to ORANGE_FLICKER mode.
			if (!isPirSensorActivated() && !myMP3.isPlaying()) {
				mode = ORANGE_FLICKER;
				break;
			}

			// Play the maniacal laugh track if it is not already playing.
			if (!myMP3.isPlaying()) {
				myMP3.play(1);
			}


			// If the imer has expired then toggle the red LED on and off in a random manner.
			if (hasTimerExpired()) {
				redState = !redState;
				FastLED.clear();

				if (redState) {
					leds[0] = CRGB::Red;
					leds[1] = CRGB::Red;
				}

				FastLED.show();
			}

			break;
	}
	
}



// Function tor determine if the PIR sensor is activated.
bool isPirSensorActivated() {
	int pirValue = digitalRead(PIR_PIN);

	// Debug pirValue.
	#ifdef DEBUG_PIR
		DPRINT("pirValue: ");
		DPRINTLN(pirValue);
	#endif

	return pirValue;
}




// Fucntion to determine if we need to set a new LED setting due to the timer expiring.
bool hasTimerExpired() {
	static unsigned long timerStart = 0UL;   // Value of when the timer started running.
	static int delayTime = 0;                // The length of time that we will delay for.
	unsigned long currentMillis = millis();  // Current millis() as of this loop.
	bool returnValue = false;                // Value to return from this function.


	// If no flicker timer is set, then set it.
	if (timerStart == 0) {
		// Start the timer and record a random delay time.
		timerStart = currentMillis;
		delayTime = random(minFlicker, maxFlicker);
	}

	// If the timer has expired, then return value true.
	if (currentMillis - timerStart >= delayTime) {
		returnValue = true;

		// Restart the flicker timer.
		timerStart = 0;

	// Else return value false.
	} else {
		returnValue = false;
	}

	return returnValue;

}





// Function to set the jack o'lantern's flicker intensity.
void doFlickerEffect() {
	int intensity = random(1, 1000);         // A random number to determine which LEDs to turn on.

	// Start with a blank canvas and turn on one base Red LED all of the time.
	FastLED.clear();
	leds[0] = CRGB::Red;

	// If over 750 turn on all LEDs.
	if (intensity > 750) {
		leds[1] = CRGB::Orange;
		leds[2] = CRGB::Orange;
		leds[3] = CRGB::Orange;
		leds[4] = CRGB::Orange;

	// Between 501 and 750 turn on 3 LEDs.
	} else if (intensity > 500) {
		leds[1] = CRGB::Orange;
		leds[2] = CRGB::Orange;
		leds[3] = CRGB::Orange;

	// Between 251 nd 500 turn on 2 LEDs.
	} else if (intensity > 250) {
		//leds[1] = CRGB::Red;
		leds[1] = CRGB::Orange;
		leds[2] = CRGB::Orange;

	// 200 or under turn on one LEDs.
	} else {
		leds[1] = CRGB::Red;
	}

	// Display it.
	FastLED.show();
}

mmitchellmoss:
I was thinking this might help my problem, though I truthfully don't even know if the isPlaying() method even uses serial data in the first place.

Yes, ".isPlaying()" requires serial data to be sent to and received from the DFPlayer.

While I haven't tested it myself, I do believe you can use pin 16 for the same purpose as ".isPlaying()".

Power_Broker:
Yes, ".isPlaying()" requires serial data to be sent to and received from the DFPlayer.

While I haven't tested it myself, I do believe you can use pin 16 for the same purpose as ".isPlaying()".

Thank you for verifying the serial situation for .isPlaying(). I think I will just give the pin 16 thing a shot then and see if it helps the situation.

Thanks again.

mmitchellmoss:
@anon57585045 I get what you mean about setting it to zero, however doing so allows it to be automatically be set to millis() on the next loop

Why would setting, or not setting it to some value have any bearing on the ability to set it to something else later? This makes no sense. If you're using it as some kind of semaphore, back away and redesign it right now! That just means there is some other mechanism that you forgot to do, didn't realize you had to do, or didn't know how to do.

It probably won't work. It's possible that you could make it work, but it would be really confusing and likely break later on when you want to add new features.

The Arduino world often follows "design patterns" for common functions. It's always better to follow them unless you have some very specialized need that they need modification for.

mmitchellmoss:
@Robin2 The timer is not the issue in this case though. It is non-blocking.

I was responding to the comment at the top of Reply #1 by providing an example of the usual way that millis() is used.

...R

UPDATE:

Okay, so I got this part of the project working. It turns out that whether you are using isPlaying() or reading the delay pin, there is a delay between the time that you tell it to play and the Arduino knowing that it is playing. Since I was checking isPlaying() (and later tried using delay) prior to calling play() I thought that it would only call play() once. Due to the delay in communications it would actually call play() over and over again. This would cause the whole system to freak out.

Instead of relying on isPlaying() or delay prior to calling play, I set up a variable called mp3Played where I keep track manually of whether or not I have called play(). Seems to have fixed all of those problems. Plus the response time between play() and actual audio being played is significantly shorter now.

I also have the voice / light synchronization working fairly well. I am still trying to figure out my audio level thresholds to make it look its best.

There are some other little quirks that I have to work out, but I think some of those are going to be hardware related around driving a powered speaker.

I still can't post the code inline. I'll try to post it on a subsequent post.

/*
WackOJackO

Project to make a jack o'lantern which will light up and flicker in it's normal mode. This should look like a candle inside
the jack o'lantern.  Eventually it will hopefully use a PIR sensor to detect a person and then switch to a mode where it will 
flash red in tune with speaking some manner of Halloweenish voice or soemthing. Then it will switch back to flicker mode.

Currently:
There is a set of individually addressable LEDs which produce a candle flicker effect. 
There is one red LED which is always on and additional LEDs that light up in oragne based on the intensity of the flicker.

Todo:
* Add a two minute delay prior to looking at the PIR sensor to allow it to settle.
* Figure out the best PIR delay and sensitivity settings.
* Use smaller types where possible.
* Make it possible to disable the talking and/or red flashing mode.
* Get my output level correct to be able to drive a powered speaker.
* Get the thresholds set so that the lights look more like an actual voice speaking.
* Add more voice tracks and randomize which one is said.
* Figure out why there is a red flash when the audio track ends.

Author: Mitch Moss
Date: April 9, 2020
*/

#include <FastLED.h>                                 // Used to control the individually addressable LED(s).
#include <SoftwareSerial.h>                          // Need to send serial commands to the DFPlayer Mini.
#include <DFPlayerMini_Fast.h>                       // Used to control the DFPlayer mini mp3 player.

// #define DEBUG_PIR                                 // Uncomment to turn on debugging for PIR sensor.
#define DEBUG_MP3                                 // Uncomment to turn on debugging for MP3 player.



// LED parameters.
const byte LED_PIN = 3;                              // Pin for LED data transmission.
const byte NUM_LEDS = 5;                             // Number of LEDs being used.
CRGB leds[NUM_LEDS];                                 // Define the leds array.

// PIR parameters.
const byte PIR_PIN = 7;                              // Pin for the PIR sensor.

// MP3 player parameters.
const byte MP3_TX_PIN = 11;                          // Pin to transmit serial data to the DFPlayer Mini.
const byte MP3_RX_PIN = 10;                          // Pin to receive serial data from the DFPlayer Mini.
const byte MP3_BUSY_PIN = 9;                         // Pin to receive the busy status form the DFPlayer Mini.
const byte MP3_SAMPLE_PIN = A1;                      // Analog pin to sample the current sound volume of the playing audio.
const int MP3_VOLUME = 25;                           // Set the volume of the mp3 player. Range 0 - 30.
SoftwareSerial mySerial(MP3_RX_PIN, MP3_TX_PIN);     // RX, TX.
DFPlayerMini_Fast myMP3;                             // Instantiate an mp3 object.


// Define variables.
int minFlicker = 20;                                // Shortest period of time to flicker.
int maxFlicker = 100;                               // Longest period of time to flicker.
bool redState = false;                              // State of the Red LED(s) in RED_BLINK mode.
bool mp3Played = false;                             // Keep track of if the MP3 track has been played or not.
int mp3SoundVolume = 0;                             // Holds the current sampled volume from the playing audio.

// Define types.
enum Modes { ORANGE_FLICKER, RED_BLINK };
Modes mode = ORANGE_FLICKER;

// Debug stuff.
#if defined DEBUG_PIR || defined DEBUG_MP3  
	#define DPRINT(...)    Serial.print(__VA_ARGS__)      // DPRINT is a macro, debug print.
  	#define DPRINTLN(...)  Serial.println(__VA_ARGS__)    // DPRINTLN is a macro, debug print with new line.
	#define DELAY(...)    delay(__VA_ARGS__)              // DELAY is a macro.
#else
  	#define DPRINT(...)                                  // Now defines a blank line.
  	#define DPRINTLN(...)                                // Now defines a blank line.
	#define DELAY(...)                                   // Now defines a blank line.
#endif




// Setup stuff.
void setup() {
	// Connect the LED strip.
	FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, NUM_LEDS);

	// Default it off.
	FastLED.clear();
	FastLED.show();

	// Fire up the DFPlayer Mini.
	mySerial.begin(9600);
  	myMP3.begin(mySerial);
  	myMP3.volume(MP3_VOLUME);

	// Pin setup.
	pinMode(PIR_PIN, INPUT);
	pinMode(MP3_BUSY_PIN, INPUT);

	#if defined DEBUG_PIR || defined DEBUG_MP3 
   	 Serial.begin(115200);
  	#endif

}



// Main loop.
void loop() {

	// Handle each mode.
	switch (mode) {
		case ORANGE_FLICKER:
			// Set the flicker durations.
			minFlicker = 20;
			maxFlicker = 80;


			// If the PIR sensor is activated then switch to RED_BLINK mode.
			if (isPirSensorActivated()) {
				mode = RED_BLINK;
				break;
			}

			// If the timer has expired then change the LED values to a random value.
			if (hasTimerExpired()) {
				// Set the LEDs to a random intensity value. 
				doFlickerEffect();
			}

			break;


		case RED_BLINK:

			// If the PIR sensor is not activated and we are not playing an mp3 then switch to ORANGE_FLICKER mode.
			if (!isPirSensorActivated() && !isMP3Playing()) {
				mode = ORANGE_FLICKER;
				mp3Played = false;
				mp3SoundVolume = 0;
				break;
			}


			// Play the maniacal laugh track if it has not already been played.
			if (!mp3Played) {
				mp3Played = true;
				myMP3.play(1);
			}


			// If the audio is playing and the timer has expired then toggle the red LED on and off in a random manner.
			if (isMP3Playing() && hasTimerExpired()) {
				// redState = !redState;
				FastLED.clear();

				// Get the current sound volume.
				mp3SoundVolume = analogRead(MP3_SAMPLE_PIN);

				Serial.println(mp3SoundVolume);

				// Trying different thresholds to make the lights sync with the voice.
				if (mp3SoundVolume < 480 || mp3SoundVolume > 520) {
					leds[0] = CRGB::Red;
					leds[1] = CRGB::Red;
				}
				
				if (mp3SoundVolume < 400 || mp3SoundVolume > 620) {
					leds[2] = CRGB::Red;
				}


				FastLED.show();
			}

			break;
	}
	
}



// Function to determine if the PIR sensor is activated.
bool isPirSensorActivated() {
	int pirValue = digitalRead(PIR_PIN);

	// Debug pirValue.
	#ifdef DEBUG_PIR
		DPRINT("pirValue: ");
		DPRINTLN(pirValue)	;
	#endif

	return pirValue;
}




// Function used to see if the MP3 player is playing something or not.
bool isMP3Playing() {
	int busyValue = digitalRead(MP3_BUSY_PIN);

	// The mp3 player uses negative logic, so return the opposite of the pin value.
	return !busyValue;
}




// Fucntion to determine if we need to set a new LED setting due to the timer expiring.
bool hasTimerExpired() {
	static unsigned long timerStart = 0UL;   // Value of when the timer started running.
	static int delayTime = 0;                // The length of time that we will delay for.
	unsigned long currentMillis = millis();  // Current millis() as of this loop.
	bool returnValue = false;                // Value to return from this function.


	// If no flicker timer is set, then set it.
	if (timerStart == 0) {
		// Start the timer and record a random delay time.
		timerStart = currentMillis;
		delayTime = random(minFlicker, maxFlicker);
	}

	// If the timer has expired, then return value true.
	if (currentMillis - timerStart >= delayTime) {
		returnValue = true;

		// Restart the flicker timer.
		timerStart = 0;

	// Else return value false.
	} else {
		returnValue = false;
	}

	return returnValue;

}





// Function to set the jack o'lantern's flicker intensity.
void doFlickerEffect() {
	int intensity = random(1, 1000);         // A random number to determine which LEDs to turn on.

	// Start with a blank canvas and turn on one base Red LED all of the time.
	FastLED.clear();
	leds[0] = CRGB::Red;

	// If over 750 turn on all LEDs.
	if (intensity > 750) {
		leds[1] = CRGB::Orange;
		leds[2] = CRGB::Orange;
		leds[3] = CRGB::Orange;
		leds[4] = CRGB::Orange;

	// Between 501 and 750 turn on 3 LEDs.
	} else if (intensity > 500) {
		leds[1] = CRGB::Orange;
		leds[2] = CRGB::Orange;
		leds[3] = CRGB::Orange;

	// Between 251 and 500 turn on 2 LEDs.
	} else if (intensity > 250) {
		//leds[1] = CRGB::Red;
		leds[1] = CRGB::Orange;
		leds[2] = CRGB::Orange;

	// 200 or under turn on one LEDs.
	} else {
		leds[1] = CRGB::Red;
	}

	// Display it.
	FastLED.show();
}

So, I figured I would let y'all know that I have "finished" this project. My jack o lantern is working pretty much the way that I wanted it to. I may have to tweak the audio tracks that I am using, seal up a light leak, and a few minor things like that, but overall it is done.

Below is a youtube link to it working if you are interested. Please forgive the trash in my basement. Also, the project does look better in real life than on this poorly made video.

Thanks for all of the responses while this was going on.

Thanks for the update. Good to hear you have a solution.

...R