arduino / ArduinoCore-samd

Arduino Core for SAMD21 CPU
GNU Lesser General Public License v2.1
467 stars 714 forks source link

Incompatibility between MKRZERO RTC & I2S libraries #451

Open white-ar opened 5 years ago

white-ar commented 5 years ago

There appears to be an incompatibility between the ArduinoSound.h and RTCZero.h libraries.

When you try to play an audio file after the internal RTC has been initialised, the WAV files won't play. See the rtc.begin() function call in the code below. Configuration: MKRZERO and Adafruit MAX98357A I2S audio board.

#include <SD.h>               // SD card access library
#include <ArduinoSound.h>     // I2S sound interface library 
#include <RTCZero.h>          // Real Time Clock library

// SETUP Audio playback
// filename of wave file to play
const char filename[] = "MUSIC.WAV";
// variable representing the Wave File
SDWaveFile waveFile;

// SETUP Real Time Clock
/* Create a RTC object */
RTCZero rtc;

/* Change these values to set the current initial time */
const byte seconds = 0;
const byte minutes = 0;
const byte hours = 16;

/* Change these values to set the current initial date */
const byte day = 15;
const byte month = 6;
const byte year = 15;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);     // Start Arduino serial port at 9,600 baud
  //SD.begin();             // Open the SD card
  delay(500);

  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }

  // setup the SD card, depending on your shield of breakout board
  // you may need to pass a pin number in begin for SS
  Serial.print("Initializing SD card..z.");

  if (!SD.begin()) {
    Serial.println("initialization failed!");
    return;
  }
  Serial.println("initialization done.");

  // create a SDWaveFile
  waveFile = SDWaveFile(filename);

  // check if the WaveFile is valid
  if (!waveFile) {
    Serial.println("wave file is invalid!");
    while (1); // do nothing
  }

  // print out some info. about the wave file
  Serial.print("Bits per sample = ");
  Serial.println(waveFile.bitsPerSample());

  long channels = waveFile.channels();
  Serial.print("Channels = ");
  Serial.println(channels);

  long sampleRate = waveFile.sampleRate();
  Serial.print("Sample rate = ");
  Serial.print(sampleRate);
  Serial.println(" Hz");

  long duration = waveFile.duration();
  Serial.print("Duration = ");
  Serial.print(duration);
  Serial.println(" seconds");

  // adjust the playback volume
  AudioOutI2S.volume(100);

  // check if the I2S output can play the wave file
  if (!AudioOutI2S.canPlay(waveFile)) {
    Serial.println("unable to play wave file using I2S!");
    while (1); // do nothing
  }

  // IF START PLAYBACK LOCATED HERE IT WORKS
  // start playback
  Serial.println("starting playback");
  AudioOutI2S.play(waveFile);

  // Start the RTC
  rtc.begin(); // Initialize RTC

  // IF START PLAYBACK LOCATED HERE, after rtc.begin();, IT DOESN'T
  // start playback
  //Serial.println("starting playback");
  //AudioOutI2S.play(waveFile);

  // Set the time
  rtc.setHours(hours);
  rtc.setMinutes(minutes);
  rtc.setSeconds(seconds);

  // Set the date
  rtc.setDay(day);
  rtc.setMonth(month);
  rtc.setYear(year);

  rtc.setAlarmTime(16, 0, 20);
  rtc.enableAlarm(rtc.MATCH_HHMMSS);
  rtc.attachInterrupt(alarmMatch);
}

void loop() {
}

void alarmMatch() {
  Serial.println("Alarm Match!");
}
white-ar commented 5 years ago

Having investigated this, I wonder if it is a clock conflict?

sslupsky commented 4 years ago

@white-ar I think you are likely correct regarding the clock conflict. I have not used ArduinoSound but had a quick look. Unfortunately, I cannot locate where the I2C object is actually instantiated so I cannot determine which clock it is using. I2S.h has an external reference but not the actual instantiation with the parameters that indicate which clock is used. Maybe someone more familiar with this library could chime in and tell us where it is instantiated.

white-ar commented 4 years ago

@sslupsky thank you, I am glad I am not the only one who has 'reached a dead end' whilst trying to fault find this.
There is a similar problem when trying to call the LowPowerArduino function. I assume it is possible that the issue could also lie in the SD card library

It seems from the Atmel Software Framework (ASF) that it is possible to use different clocks. So, I assume if one of the libraries re configures a clock used by one of the other libraries, this could cause a conflict.

As you say, this problem appears to require the help of someone with greater holistic knowledge of the architecture, and I suspect ASF.

All help greatly appreciated.

sslupsky commented 4 years ago

@white-ar I have found issues with the RTC library and the LowPowerLibrary. I recently added support for using the periodic interval interrupts with the RTC and the event system. This allows you to wake up more often than is possible using the RTC alarm interrupts.

There are some defects with the Wire and SERCOM libraries with regard the low power compatibility as well. I proposed some solutions with a PR but they have not been accepted (yet?).

aoriani commented 2 years ago

I wonder if someone got some ideas after two years. I am working on a clock that would play sound alarms using the MKR WIFI 1010. The board would freeze when trying to play a file. I took hours to realize that removing any line that touched the RTC, would allow the board to play the sound.

white-ar commented 2 years ago

@aoriani Hi Andre, I never managed to get it to work, and gave up on making a battery powered version.

@sandeepmistry : was there a resolution to this issue?

I'm sorry, I can't be of further assistance.

aoriani commented 2 years ago

@white-ar thanks a lot for your response. It looks like you were the pioneer on this issue because every Google search hits a post of yours. If I correctly followed your saga, you ended using an external RTC and you could not use the LowerPower lib because it faced a similar problem. Is that a precise account?

Someone mentioned on https://forums.adafruit.com/viewtopic.php?f=25&t=182648 that the possible root cause was the RTC lib enabling protection on a register needed by I2S. So I wonder if the incompatibility is at the hardware level, or is there still hope that it could be solved thru software?

white-ar commented 2 years ago

@aoriani My project was an alarm clock that played music. The issue being that unless one puts the Arduino into low power mode (sleep) it draws too much power to be viably from battery power.

The RTC was used to keep time when the Arduino is switched off, or between battery changes, etc.

You may note from the code snippet above, this is standard Arduino example code, with the simple addition of the standard RTCzero library. Put another way - the issue is within the standard libraries. I started investigating the underlying libraries and register architecture, however it soon became clear that I didn't have enough background knowledge to make any meaningful progress in this regards.

My project succeeded in its wider goal, so all was not lost.

If you 'need' to get this working, you could try a pair of Arduinos, using an interrupt to signal between them. i.e. one handles the RTC, then wakes the Zero to play audio. (Note: Also, you'd need to message time and alarm time, etc, between them as well.) Alternatively you could look at a STM32 dev card or even a RPI ... Each of these other routes will undoubtedly have its issues.

Hope this helps.

Khosos commented 1 year ago

Hey everyone. I ran into this issue recently and found a fix that seems to work so far. The issue has to do with clock conflicts as many of you have suspected. When the RTCZero library configures the clock it sets the bit value of DIVSEL in the GLCK GENCTRL register to 1. I2S expects this value to be 0 but doesn't explicitly set its value.

To remedy this all you have to do is add this line in the I2S library within the ( void I2SClass::enableClock(int divider) ) function. GCLK->GENCTRL.bit.DIVSEL = 0;

Below is where that line of code goes. // use the DFLL as the source while (GCLK->STATUS.bit.SYNCBUSY); GCLK->GENCTRL.bit.ID = _clockGenerator; GCLK->GENCTRL.bit.SRC = src; GCLK->GENCTRL.bit.IDC = 1; GCLK->GENCTRL.bit.DIVSEL = 0; GCLK->GENCTRL.bit.GENEN = 1;