Play WAV file and animate LED strip

Hello, I've been working on this problem for a while. The required functionality of my project is to play a song, while also animating an LED strip based on values from another file (in this case it's currently a CSV).

I am using an Arduino Nano (because of price, size) with a Micro SD Module and a WS2812B LED strip.

I am able to animate the LEDs and play a song with separate programs, but combining them into a single one that plays them both in sync is proving to be the hardest challenge.

The current state is that I am using FastLED to send data to the lights and TMRpcm to play the audio. I've found these two libraries don't play well together because FastLED (or the LED strip) requires the data to be sent in strict timing, while TMRpcm, which plays the audio requires the use of timer interrupts. Timer interrupts break the LED data timing, causing the LEDs to not light up (as I understand it).

The LED part of the program has to read at least 150 bytes of data every 50ms to load the data for the LEDs without interrupting the audio. This actually requires opening two files at once from the SD card (audio + LED data), which does seem to work, but it's just sending that data to the outputs (a speaker and the LED strip) that is more complicated.

Is there a way to play audio and animate LEDs at the same time from the Arduino nano?

More specifically: can I rework the LED animation stuff to work with interrupts (a different library, or different type of LED)? is there a library that will work with this? Or is there a way to play WAV files without interrupts?

I've looked for other solutions, it seems playing audio without a DAC (digital analog converter?) pin is preferred, but it's not available on the nano. It's possible to use the DF Player Mini, but because I am reading another file for the LED animations, I'd need a second micro SD card and module, which is not a good solution for my project's use case.

I am very new to the world of microcontrollers, but have lots of software development experience working on desktop/web applications. I find this stuff so interesting, but at times very frustrating.

I have scoured the forums for the solution to this problem but have had a hard time finding anything that will work in this case. The closest I've found is this: DFPlayer MP3 module - get data from SD Card | Hackaday.io but I am not sure it will work for my use case.

Since you did not post any code, only you know the answer. There are many ways to do multiple things at a time. You can tell if you song is playing or not and you can change your animation to do small things at a time several things at the same time

DFPlayer Mini has an SD slot.

The real thing to do is get 2 MSGEQ7 chips (for stereo, 1 for mono) and use readings to set your colors.
I bought 10 for about $30 years ago, for one was maybe $7.

This guy is or was a member here, he has a whole how-to on these:

Other thing, won't there be enough room in Nano flash to hold your prepared lights data?

Ok, here's the code I have. I had to alter it a bit just to get rid of the unimportant stuff.

The issue I am having now is that the LEDs are lighting up the wrong colors (not all red, all green, all blue) but instead is showing random colors. This makes me believe that the LED pin is constantly sending the light color data (not just once when I call FastLED.show()) and it's getting interrupted by the arduino playing music, causing the random colors (and colors changing between FastLED.show() calls).

@blh64
With this code I am able to do both at once, but the issue is that the sound is interfering with the LEDs data. Is there any way to fix this? Is the problem with the type of LEDs? Is there another LED or Sound library or chip that would be able to handle this use case?

#include <FastLED.h>
#include <TMRpcm.h>

#define LED_PIN                   7
#define NUM_LEDS                  50
CRGB leds[NUM_LEDS];

TMRpcm tmrpcm;

void setup() {
 // Open serial communications and wait for port to open:
 Serial.begin(9600);
 while (!Serial);

 // Initialize LEDs
 FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, NUM_LEDS);

 // Start the song
 tmrpcm.speakerPin = 9; //5,6,11 or 46 on Mega, 9 on Uno, Nano, etc
 tmrpcm.setVolume(3);
 tmrpcm.play("TEST.WAV");
}

long lastMillis = 0;
int colorIdx = 0;

void loop() {
 int colorList[3][3] = {{100, 0, 0}, {0, 100, 0}, {0, 0, 100}};
 if (millis() - lastMillis > 1000) {
   noInterrupts();
   // print a random number from 0 to 299
   for (int i = 0; i < NUM_LEDS; i++) {
     leds[i] = CRGB(colorList[colorIdx][0], colorList[colorIdx][1], colorList[colorIdx][2]);
   }
   colorIdx = colorIdx % 3;
   FastLED.show();
   lastMillis = millis();
   interrupts();
 }
}

@GoForSmoke
That's interesting, but I want a different stream of data read from a CSV or some type of file, not the audio data stream.

The FastLED library turns off interrupts when it is clocking out the data. That is probably what is messing up the audio. You can get other LED controllers that are faster like APA102 which uses SPI

Both of those tasks are very CPU intensive and I doubt you can run them both at once. :frowning:

An audio module/shield will do most of the "audio work" with the processor just selecting the track and starting/stopping, etc. That way, the Arduino can focus on running the lighting.

And, you should get MUCH better audio quality. It might be slightly-tricky to keep the audio & LEDs exactly in-sync because the Arduino can't directly-read SD card (and the SD card can't hold the lighting program) but you should be able everything synchronized for the length of song.

An MSGEQ7 probably won't help the way you described your project. That chip helps with analyzing the frequency content so your lights can react to sound without FFT (software) which is also CPU intensive. If you want to program you lighting you don't need either.

i.e. DJ lighting reacts to sound so you don't have to make a special lighting program for every song. Lighting for stage shows is planned/programmed so it's predictable and the same every time, and there's often one or more human lighting operators.

@blh64
The audio does seem to work properly, it's the leds that aren't working correctly. I think DVDdoug is right I just wanted to investigate my options before I purchased another chip that will take 1-2 months to arrive

I'll try LEDs with the SPI interface, though the strip that I am using has the LEDs in a nice form for my use case so I'd like to use them (https://www.alitove.net/product/500x-2811-12v-r/)

I will try the DF Mini (https://www.aliexpress.com/item/32641284367.html?spm=a2g0o.productlist.0.0.6e902eb1ZCcAlz). I was hoping that I would only need 1 SD card, but it seems I will need 2 in this case (1 for the audio in the df player, 1 for the light data to be loaded from by the arduino).

I will also try other microchips with a faster CPU clock time like 32 or 48 Mhz

I would recommend keeping your LEDs and getting a DF Mini player. This offloads the burden of the audio. It is a pretty typical setup.

I've been racking my brain trying to figure out how to take my project design down from 2 SD cards to 1.

My project requires playing a sound file (WAV) and animating lights at the same time. The animations for the lights are stored in CSV files on an SD card, the WAV files on another.

The current design is to have an arduino nano which controls the lights and sends instructions to a DF player mini, which plays the sound file. This design requires 2 SD cards because the arduino must load data off of an SD card for the light animations and the DF mini has it's own SD slot for the music files (which is not accessible, plus it'd be difficult to read from it while the DF mini is reading from it)

I've attempted to run the lights and the audio off of a single microcontroller but it seems the time it takes to update the LEDs causes issues for the audio.

I've found this tutorial for an audio player (https://www.arduino.cc/en/Tutorial/SimpleAudioPlayer) which requires the Arduino Due board, but one thing that's interesting about it is that it loads the data in batches to output. Is there another form of memory I could utilize with a board similar to the DF player mini where I could load the data into the memory for the controller to load and play as audio?

Is there another solution that I am missing?

For reference I am using the FastLED and TMRpcm libraries.

I am a beginner when it comes to microcontrollers and hardware in general.

Your link doesn't work. It could be that Due is required because it is faster and has more memory thereby handling data from a buffer rather than using the SD card. There may be other modern boards in the Arduino arena that are even more suitable. A Nano isn't one of them.

I'm fairly certain it's because of the DAC pins available on the due

Not getting the answers you want is no reason to post your question again.
Previous thread: Play WAV file and animate LED strip - Project Guidance - Arduino Forum

@kschieck

TOPIC MERGED.

Could you take a few moments to Learn How To Use The Forum.

Other general help and troubleshooting advice can be found here.
It will help you get the best out of the forum.

I was under the impression it was different enough that it merited a new thread.

Ok I will read more about the forum

What immediately hit me about the code

   noInterrupts();
   // print a random number from 0 to 299
   for (int i = 0; i < NUM_LEDS; i++) {
     leds[i] = CRGB(colorList[colorIdx][0], colorList[colorIdx][1], colorList[colorIdx][2]);
   }
   colorIdx = colorIdx % 3;
   FastLED.show();
   lastMillis = millis();
   interrupts();

Try this instead. If it works, figure out why.

   noInterrupts();
   for (int i = 0; i < NUM_LEDS; i++) {
     leds[i] = CRGB(colorList[ i % 3 ][0], colorList[ i % 3 ][1], colorList[ i % 3 ][2]);
   }
   lastMillis = millis();
   interrupts();
   FastLED.show();

Or..

#include <FastLED.h>
#include <TMRpcm.h>

#define LED_PIN                   7
#define NUM_LEDS                  50
CRGB leds[NUM_LEDS];

TMRpcm tmrpcm;

unsigned long lastMillis;
const unsigned long changeInterval = 1000UL;
byte ledNum;


void setup() {
 // Open serial communications and wait for port to open:
 Serial.begin(115200);    //  Note this change, it clears the print output buffer quicker.
 while (!Serial)

 FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, NUM_LEDS);

 const byte colorList[3][3] = {{100, 0, 0}, {0, 100, 0}, {0, 0, 100}};

 // Start the song
 tmrpcm.speakerPin = 9; //5,6,11 or 46 on Mega, 9 on Uno, Nano, etc
 tmrpcm.setVolume(3);
 tmrpcm.play("TEST.WAV");
}

void loop() 
{
  if ( ledNum < NUM_LEDS )
  {
     leds[ledNum] = CRGB(colorList[ ledNum % 3 ][0], colorList[ ledNum % 3 ][1], colorList[ ledNum % 3 ][2]);
     ledNum++;
  }

 if (( millis() - lastMillis > changeInterval ) && ( ledNum == NUM_LEDS ))
 {
   FastLED.show();
   lastMillis += changeInterval;
   ledNum = 0;
 }

}

I've determined the cause of my issue, it wasn't due to how I was doing things, but due to my not connecting the grounds of 2 separate power sources together. From this I have learned to always post the wiring diagram when asking questions, because the issue might not be in the code.

Anyways for people who want to accomplish changing lights while also playing audio, here's some code that accomplishes that.

I don't have time to make a diagram at the moment, but here's how everything is wired:

for playing audio:

(except I used d9 for the output)

for the lights:
5v -> +5v
d7 -> resistor (220 Ω) -> data
gnd -> gnd

for SD card:
https://create.arduino.cc/projecthub/electropeak/sd-card-module-with-arduino-how-to-read-write-data-37f390

#include <SD.h>
#include <FastLED.h>
#include <TMRpcm.h>

#define SD_CS_PIN                 10

#define LED_PIN                   7
#define NUM_LEDS                  50
CRGB leds[NUM_LEDS];

TMRpcm tmrpcm;

void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  while (!Serial);

  Serial.print("Initializing SD card...");
  if (!SD.begin(SD_CS_PIN)) {
    Serial.println("initialization failed. Things to check:");
    Serial.println("1. is a card inserted?");
    Serial.println("2. is your wiring correct?");
    Serial.println("3. did you change the chipSelect pin to match your shield or module?");
    Serial.println("Note: press reset or reopen this serial monitor after fixing your issue!");
    while (true) {
      delay(10000);
    }
  }
  Serial.println("initialization done.");

  // Initialize LEDs
  FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, NUM_LEDS);

  // Start the song
  tmrpcm.speakerPin = 9; //5,6,11 or 46 on Mega, 9 on Uno, Nano, etc
  tmrpcm.setVolume(5);
  tmrpcm.play("SONG.WAV");
}

long lastMillis = 0;
void loop() {
  // cycle through red, green, blue on all lights, 200 ms delay
  int colorList[3][3] = {{3, 0, 0}, {0, 3, 0}, {0, 0, 3}};
  if (millis() - lastMillis > 200) {
    for (int i = 0; i < NUM_LEDS; i++) {
      leds[i] = CRGB(colorList[colorIdx][0], colorList[colorIdx][1], colorList[colorIdx][2]);
    }
    FastLED.show();
    lastMillis = millis();
    colorIdx = (colorIdx + 1) % 3;
  }
}

@GoForSmoke

Thanks for the code, I am sure those will work as the issue seemed to be what I described above (I hadn't seen your answer before I posted because it was on a new page). I was getting weird behaviour from my LED strip because the 2 grounds weren't connected (I believe). Once I figured that out they started working and the interrupts were not an issue.

They are however an issue when reading light data from an SD card as well as playing a song off that SD card, so I am investigating other options.

Thanks for taking the time to look into my issue, it was driving me insane for a while and I've already ordered some more components and microchips to test on different (faster, more memory) hardware to see if I can get it working still with my original design.

Well this time you're iterating colorIdx though you don't need it at all, the index i in the for-loop presents the same sequence as i % 3. With code, you can add extra bits but that can make debugging harder. Look to trim out redundancies.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.