Music Maker Featherwing stops other program execution?

Please tell us which board you are using.
For CircuitPython issues, ask in the Adafruit CircuitPython forum.

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
Locked
User avatar
ZevEisenberg
 
Posts: 36
Joined: Thu Feb 23, 2023 12:59 am

Music Maker Featherwing stops other program execution?

Post by ZevEisenberg »

TL;DR when I play music on my Music Maker FeatherWing, the rest of my Arduino sketch becomes unresponsive, and I'm not sure if that's expected. I thought the interrupt-based playback of the FeatherWing meant that my sketch could do other stuff while it's playing, like handling other interrupts or interacting with SPI peripherals?

I've got a Feather RP2040, a Music Maker FeatherWing, an RC522 RFID reader (via SPI), and a button (via an interrupt pin). In my current setup, I want to read an RFID card, play a song, and keep scanning the card reader in case another card is scanned before it finishes. While a song is playing, pushing the button should act as a play/pause toggle. The Feather's NeoPixel acts as a playback status indicator: red for paused, blue for playing.

Here's what happens in practice:
  1. The sketch starts running and loads all the songs.
  2. I can push the button to test my NeoPixel toggling and interrupt code, which works fine.
  3. I see output that the card scanning is working.
  4. I scan a card and start playing a song.
  5. A couple more lines of serial output appear, but then all serial output stops (see the "H", "I", and "** End Reading **" at the bottom of the attached log).
  6. Button presses now do nothing (no log, no play/pause functionality, and no NeoPixel change).
  7. Scanning for cards stops working - no serial output, and nothing happens when I scan.
  8. The song plays through to the end, but the sketch does not resume serial output or functionality - it effectively remains frozen until I reboot.
Here is the code I'm using:

Code: Select all

#include <algorithm>
#include <Adafruit_VS1053.h>
#include <MFRC522.h>
#include <SD.h>
#include <SPI.h>
#include <Adafruit_NeoPixel.h>

Adafruit_NeoPixel pixels(1, PIN_NEOPIXEL);

const uint32_t red = pixels.Color(0xFF, 0, 0);
const uint32_t blue = pixels.Color(0, 0, 0xFF);

// Correct pins via
// https://flashgamer.com/blog/comments/using-feather-rp2040-with-adafruits-music-maker-featherwing

// These are the pins used
#define VS1053_RESET -1  // VS1053 reset pin (not used!)

// Feather RP2040 - MusicMaker FeatherWing
#define VS1053_CS 8    // VS1053 chip select pin (output)
#define VS1053_DCS 10  // VS1053 Data/command select pin (output)
#define CARDCS 7       // Card chip select pin
#define VS1053_DREQ 9  // VS1053 Data request, ideally an Interrupt pin

// Feather RP2040 - RC522 RFID Reader
#define RFID_CS_PIN 12   // chipSelectPin, aka slave select, aka SDA on RC522
#define RFID_RST_PIN 11  // resetPowerDownPin

const int PLAY_PAUSE_BUTTON_PIN = 3;  // SCL, GPIO3

volatile boolean isPlaying = false;
volatile boolean pendingPlayPauseButtonPress = true;

volatile int currentTrackIndex = -1;  // -1 means no track is playing
std::vector<String> tracks{};

Adafruit_VS1053_FilePlayer musicPlayer =
  Adafruit_VS1053_FilePlayer(VS1053_RESET, VS1053_CS, VS1053_DCS, VS1053_DREQ, CARDCS);

MFRC522 rfid = MFRC522(RFID_CS_PIN, RFID_RST_PIN);

void setup() {
  // Some boards work best if we also make a serial connection
  Serial.begin(115200);

  while (!Serial) {
    delay(1);
  }
  delay(500);
  Serial.println(F("\n\nAdafruit VS1053 Feather Test"));

  // delay(500);
  SPI.begin();  // Init SPI bus
  // delay(500);
  rfid.PCD_Init();  // Start reading RFID cards

  // We want INPUT_PULLUP so the value doesn't randomly fluctuate when not HIGH
  pinMode(PLAY_PAUSE_BUTTON_PIN, INPUT_PULLUP);

  pixels.begin();

  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"));

  // Set volume for left, right channels. lower numbers == louder volume!
  musicPlayer.setVolume(40, 40);

  // musicPlayer.sineTest(0x44, 500);  // Make a tone to indicate VS1053 is working

  if (!SD.begin(CARDCS)) {
    Serial.println(F("SD failed, or not present"));
    while (1)
      ;  // don't do anything more
  }
  Serial.println(F("SD OK!"));

  // DEBUGGING: list files
  // printDirectory(SD.open("/Music"), 0);

  loadFilesFromSDCard(SD.open("/Music"));

  // If DREQ is on an interrupt pin we can do background
  // audio playing. In this case, #if defined(__AVR_ATmega32U4__) will be false.
  if (musicPlayer.useInterrupt(VS1053_FILEPLAYER_PIN_INT)) {  // DREQ int
    Serial.println(F("Able to use interrupt"));
  } else {
    Serial.println(F("Can't use interrupt"));
  }

  attachInterrupt(digitalPinToInterrupt(PLAY_PAUSE_BUTTON_PIN), readButton, FALLING);

  // Example Code
  // Play a file synchronously
  // Serial.println(F("Playing 0013 The_Car_Bunnies.mp3"));
  // musicPlayer.playFullFile("0013 The_Car_Bunnies.mp3");

  // Play a file in the background, REQUIRES interrupts!
  // Serial.println(F("0013 The_Car_Bunnies.mp3p3"));
  // musicPlayer.startPlayingFile("0013 The_Car_Bunnies.mp3");
}

void loop() {
  pixels.setPixelColor(0, isPlaying ? red : blue);
  pixels.show();

  if (pendingPlayPauseButtonPress) {
    if (currentTrackIndex != -1) {
      // Don't want to do serial things inside interrupt handler, so do it here instead.
      musicPlayer.pausePlaying(!isPlaying);
    }
    pendingPlayPauseButtonPress = false;
  }

  if (!musicPlayer.playingMusic) {
    // toggle isPlaying in case song finished
    isPlaying = false;
  }
  readFromCard();
}

void loadFilesFromSDCard(File dir) {
  if (!dir.isDirectory()) {
    Serial.println(F("Error: attempt to load files from path that is not a folder"));
    return;
  }
  while (true) {
    File entry = dir.openNextFile();
    if (!entry) {
      // no more files
      entry.close();
      break;
    }

    auto name = entry.name();
    Serial.print(F("Loading track "));
    Serial.print(tracks.size() + 1);
    Serial.print(F(" "));
    Serial.println(name);
    tracks.push_back(name);
  }
  sort(tracks.begin(), tracks.end());
}

void readButton() {
  static unsigned long lastInterruptTime = 0;
  unsigned long interruptTime = millis();
  // debounce button press
  if (interruptTime - lastInterruptTime > 500) {
    isPlaying = !isPlaying;
  }
  lastInterruptTime = interruptTime;
  pendingPlayPauseButtonPress = true;
}

void readFromCard() {
  rfid.PCD_Init();  // Need to keep doing this in case it shuts down or something

  Serial.println(F("A"));

  // Reset the loop if no new card present on the sensor/reader. This saves the entire process when idle.
  if (rfid.PICC_IsNewCardPresent()) {
    Serial.println(F("new card present"));
    // Select one of the cards
    if (!rfid.PICC_ReadCardSerial()) {
      Serial.println(F("Failed to read card."));
      return;
    }

    Serial.println(F("** Card Detected: **"));

    rfid.PICC_DumpDetailsToSerial(&(rfid.uid));  // Dump some details about the card

    // rfid.PICC_DumpToSerial(&(rfid.uid)); // uncomment this to see all blocks in hex

    /**** Get number and play song ****/

    // Prepare key - all keys are set to FFFFFFFFFFFFh at chip delivery from the factory.
    MFRC522::MIFARE_Key key;
    for (byte i = 0; i < 6; i++) {
      key.keyByte[i] = 0xFF;
    }

    Serial.println(F("C"));

    byte block = 1;
    byte len = 18;
    byte buffer2[18];
    MFRC522::StatusCode status = rfid.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, 1, &key, &(rfid.uid));

    Serial.println(F("D"));

    if (status != MFRC522::STATUS_OK) {
      Serial.print(F("Authentication failed: "));
      Serial.println(rfid.GetStatusCodeName(status));
      return;
    }

    status = rfid.MIFARE_Read(block, buffer2, &len);
    if (status != MFRC522::STATUS_OK) {
      Serial.print(F("Reading failed: "));
      Serial.println(rfid.GetStatusCodeName(status));
      return;
    }

    Serial.println(F("E"));

    // Print Number
    String numberString = "";
    for (uint8_t i = 0; i < 16; i++) {
      numberString += (char)buffer2[i];
    }
    Serial.println(F("F"));
    numberString.trim();
    Serial.print(F("Number: "));
    Serial.println(numberString);

    // // Play Song

    Serial.println(F("F"));

    currentTrackIndex = numberString.toInt();
    // Example so I can think about this, because array indexing is the hardest problem in computer science
    // current track index: 14
    // tracks.size(): 14
    // highest array index: 13
    // currentTrackIndex must be <= tracks.size

    // RFID cards and audio files are 1-indexed, but array is 0-indexed
    auto currentTrackArrayIndex = currentTrackIndex - 1;
    if (currentTrackArrayIndex < tracks.size()) {
      Serial.println(F("G"));
      String trackName = tracks[currentTrackArrayIndex];
      Serial.print(F("Playing song: "));
      Serial.println(trackName);
      Serial.println(F("H"));
      isPlaying = true;
      musicPlayer.startPlayingFile(trackName.c_str());
      Serial.println(F("I"));
    } else {
      Serial.print(F("Attempt to play out-of-bounds track "));
      Serial.print(currentTrackIndex);
      Serial.print(F(", but highest number is "));
      Serial.println(tracks.size() - 1);
    }

    // musicPlayer.startPlayingFile("0013 The_Car_Bunnies.mp3");

    Serial.println(F("\n** End Reading **\n"));

    rfid.PICC_HaltA();
    Serial.println(F("J"));
    rfid.PCD_BANNED();

    Serial.println(F("K"));
  } else {
    Serial.println(F("No new card present"));
  }

  delay(1000);  // change value if you want to read cards faster
  Serial.println(F("M"));
}

/// File listing helper
void printDirectory(File dir, int numTabs) {
  while (true) {
    File entry = dir.openNextFile();
    if (!entry) {
      // no more files
      // Serial.println(F("**nomorefiles**"));
      break;
    }
    for (uint8_t i = 0; i < numTabs; i++) {
      Serial.print('\t');
    }
    Serial.print(entry.name());
    if (entry.isDirectory()) {
      Serial.println(F("/"));
      printDirectory(entry, numTabs + 1);
    } else {
      // files have sizes, directories do not
      Serial.print("\t\t");
      Serial.println(entry.size(), DEC);
    }
    entry.close();
  }
}
Or as a gist if you prefer syntax highlighting.

And here's the serial output during a test run where I started the sketch and then scanned a card:

Code: Select all

23:19:58.875 -> 
23:19:58.875 -> 
23:19:58.875 -> Adafruit VS1053 Feather Test
23:19:59.302 -> VS1053 found
23:19:59.334 -> SD OK!
23:19:59.334 -> Loading track 1 0014 Let_It_Go.mp3
23:19:59.334 -> Loading track 2 0002 Into_the_Unknown.mp3
23:19:59.334 -> Loading track 3 0007 Where_You_Are.mp3
23:19:59.334 -> Loading track 4 0011 Squishy_Touch.mp3
23:19:59.367 -> Loading track 5 0001 First_Time_in_Forever.mp3
23:19:59.367 -> Loading track 6 0013 The_Car_Bunnies.mp3
23:19:59.367 -> Loading track 7 0003 Lost_in_the_Woods.mp3
23:19:59.399 -> Loading track 8 0010 You_re_Welcome.mp3
23:19:59.399 -> Loading track 9 0009 I_Am_Moana.mp3
23:19:59.399 -> Loading track 10 0008 How_Far_I_ll_Go.mp3
23:19:59.399 -> Loading track 11 0012 Hush_Little_Baby.mp3
23:19:59.432 -> Loading track 12 0005 In_Summer.mp3
23:19:59.432 -> Loading track 13 0006 Love_Is_an_Open_Door.mp3
23:19:59.432 -> Loading track 14 0004 Show_Yourself.mp3
23:19:59.432 -> Able to use interrupt
23:19:59.498 -> A
23:19:59.531 -> No new card present
23:20:00.518 -> M
23:20:00.584 -> A
23:20:00.584 -> No new card present
23:20:01.608 -> M
23:20:01.641 -> A
23:20:01.673 -> No new card present
23:20:02.664 -> M
23:20:02.729 -> A
23:20:02.729 -> No new card present
23:20:03.741 -> M
23:20:03.807 -> A
23:20:03.807 -> new card present
23:20:03.807 -> ** Card Detected: **
23:20:03.807 -> Card UID: 03 3B 2B 94
23:20:03.807 -> Card SAK: 08
23:20:03.807 -> PICC type: MIFARE 1KB
23:20:03.807 -> C
23:20:03.807 -> D
23:20:03.807 -> E
23:20:03.807 -> F
23:20:03.807 -> Number: 11
23:20:03.807 -> F
23:20:03.807 -> G
23:20:03.807 -> Playing song: 0011 Squishy_Touch.mp3
23:20:03.807 -> H
23:20:03.840 -> I
23:20:03.840 -> 
23:20:03.840 -> ** End Reading **
23:20:03.840 -> 
Now you know my 2-year-old's favorite songs (and one Shel Silverstein poem).

User avatar
mikeysklar
 
Posts: 13936
Joined: Mon Aug 01, 2016 8:10 pm

Re: Music Maker Featherwing stops other program execution?

Post by mikeysklar »

Nice program.

You have defined an interrupt pin (DREQ), but you need to tell the code to use it. I normally do this at the end of setup when you are setting the volume level.

Code: Select all

// Set volume for left, right channels. lower numbers == louder volume!
musicPlayer.setVolume(10,10);

// play song in background using interrupts
musicPlayer.useInterrupt(VS1053_FILEPLAYER_PIN_INT);  // DREQ int

User avatar
ZevEisenberg
 
Posts: 36
Joined: Thu Feb 23, 2023 12:59 am

Re: Music Maker Featherwing stops other program execution?

Post by ZevEisenberg »

I am enabling the interrupt pin here:

Code: Select all

if (musicPlayer.useInterrupt(VS1053_FILEPLAYER_PIN_INT)) {  // DREQ int
  Serial.println(F("Able to use interrupt"));
} else {
  Serial.println(F("Can't use interrupt"));
}
(And I get "Able to use interrupt" in the logs.)

A friend suggested something I had not considered, which is that the code is crashing shortly after playback stops. It seems obvious in hindsight, but I had not considered it for 2 reasons:
  1. I'm used to programming things that have debuggers and show error messages when they crash, so when I didn't see any output, my "crash" spidey sense wasn't triggered. I haven't had much experience with crashes in Arduino code because I haven't been doing much crashy stuff before. (Not that my programs are bug-free; just that I haven't been doing much with array bounds, pointer dereferences, and other commonly crashy activities. My arduino bugs have tended to be logic bugs.)
  2. The music keeps playing! I had assumed that, if my program crashed or hung, the music would stop. It was my understanding that the FeatherWing would periodically use an interrupt, behind the scenes, to use some CPU time in between other things to feed its buffer from the SD card. Since it now seems that my program is crashing, I assume it's getting the whole song in one go or something? Or maybe it's playing directly from the SD card without needing to pass through the RP2040 chip at all? That's how my previous audio player, the DFPlayer mini, worked, but I thought this FeatherWing was different.
Anyway, I did a test where I scan for cards as normal, but as soon as I start playing a song, I set a boolean that short-circuits the card scanning logic. Sure enough, once I did that, the serial log output keeps working and my interrupt-driven play/pause button works as well. So it seems that there is some crash or hang related to the card reader, and nothing at all to do with the music player per se.

I noticed an interesting quirk while debugging this: I would run the program and everything would work. Then I would run it again. This time, no scanning. It seems that the card reader was getting left in some invalid or paused state, so even though it was not reporting errors, it was never reporting that it had scanned a card. I think I need to more carefully reset the card reader between runs, and hopefully not doing whatever naughty thing was causing it to crash. Perhaps I'll pull in a local copy of the RC522 source code so I can add logging statements there to try to catch it.

I'm not set up to use a debugger with this board - it's my understanding that I'd need about USD $50-$100 to do that? I'd love to avoid that if possible, but maybe I'll get hooked up as my programs get more complicated. As a software developer, I definitely feel it when I don't have a debugger!

User avatar
mikeysklar
 
Posts: 13936
Joined: Mon Aug 01, 2016 8:10 pm

Re: Music Maker Featherwing stops other program execution?

Post by mikeysklar »

Nice work narrowing down the symptoms and culprit.

Does your RFID reader need to be on the SPI bus? I would expect CS pin management to be a likely cause of contention when the Audio and RFID reader are sharing the same SPI bus. I assume you can use I2C or UART as well.

You definitely want a JTAG debugger. They are invaluable and the skill set is important when you have any level of complexity in your code. It also allows you to do fearless things knowing you can have the magical ability to unbrick a stuck board. They pay for themselves.

JTAG devices have recently jumped in place with a $60 price for hardware like the J-LINK EDU.

Since you have an RP2040 there is a $12 debug Probe Kit if you already have a Pi.

User avatar
ZevEisenberg
 
Posts: 36
Joined: Thu Feb 23, 2023 12:59 am

Re: Music Maker Featherwing stops other program execution?

Post by ZevEisenberg »

The RFID reader I’m using supports I2C and UART as well. The library I’m using (https://github.com/miguelbalboa/rfid) is SPI-only. However, I just noticed that it has an actively maintained fork (https://github.com/OSSLibraries/Arduino_MFRC522v2) that supports I2C. If you think that is likely to be more reliable, I think it’s worth a try. I might tinker with it while I wait for my SWD stuff to arrive. I ended up getting the J-LINK EDU direct from Segger (because it’s in stock) and a SWD connector from Amazon.

I do have an old Raspberry Pi, but it’s pretty slow and I’d rather have something purpose-built than something I have to worry about updating the OS on, you know?

User avatar
mikeysklar
 
Posts: 13936
Joined: Mon Aug 01, 2016 8:10 pm

Re: Music Maker Featherwing stops other program execution?

Post by mikeysklar »

Nice. Good luck with the JTAG HW and possible I2C transition.

Locked
Please be positive and constructive with your questions and comments.

Return to “Feather - Adafruit's lightweight platform”