arduino / ArduinoCore-samd

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

Incompatibility between LowPowerArduino & ArduinoSound #445

Open white-ar opened 5 years ago

white-ar commented 5 years ago

Calling the LowPower.sleep(..) function from the ArduinoLowPower library causes I2S audio playback using the ArduinoSound library to crash.

The sketch below is based on the WavePlayback demo sketch, with a small delay between playbacks. If delay() is used, the sketch works as expected. However, if LowPower.sleep(..) audio playback crashes.

Any ideas or suggestions would be most welcome.

Thank you

/*
  This reads a wave file from an SD card and plays it using the I2S interface to
  a MAX08357 I2S Amp Breakout board.

  Circuit:
   Arduino/Genuino Zero, MKRZero or MKR1000 board
   SD breakout or shield connected
   MAX08357:
     GND connected GND
     VIN connected 5V
     LRC connected to pin 0 (Zero) or pin 3 (MKR1000, MKRZero)
     BCLK connected to pin 1 (Zero) or pin 2 (MKR1000, MKRZero)
     DIN connected to pin 9 (Zero) or pin A6 (MKR1000, MKRZero)

  created 15 November 2016
  by Sandeep Mistry
*/

#include <SD.h>
#include <ArduinoSound.h>
#include <ArduinoLowPower.h>

// filename of wave file to play
const char filename[] = "SONG0.WAV";

// variable representing the Wave File
SDWaveFile waveFile;

void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  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...");
  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( 50 );

  // 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
  }
}

void loop() {
  // start playback
  Serial.println("starting playback");
  AudioOutI2S.play(waveFile);

  do {
    // wait
  }
  while ( AudioOutI2S.isPlaying() );

  /*
     Using delay(..) works as expected
     Using LowPower.sleep(..) causes audio playback to stop working
  */

  //delay(1000);

  LowPower.sleep(1000);

  Serial.println("Playing file again ");
  AudioOutI2S.play(waveFile);

  do {
    // wait
  }
  while ( AudioOutI2S.isPlaying() );

  delay(1000);
}
facchinm commented 5 years ago

Hi @white-ar , the issue might be related with I2S using DMA for data transfer, and may not be stopped properly. @sandeepmistry any thought on this?

sandeepmistry commented 5 years ago

Does the low power lib disable DMA or I2S?

facchinm commented 5 years ago

None of the two, it simply calls __wfi() but there may be faults if there's an ongoing DMA transaction

white-ar commented 5 years ago

Perhaps this is linked to the other issue I have encountered? https://github.com/arduino/Arduino/issues/9124

Thank you for looking at it ... much appreciated.

white-ar commented 5 years ago

Using I2S.h seems to work.

The following works as expected

/*
 This example generates a square wave based tone at a specified frequency
 and sample rate. Then outputs the data using the I2S interface to a
 MAX08357 I2S Amp Breakout board.

 Circuit:
 * Arduino/Genuino Zero, MKRZero or MKR1000 board
 * MAX08357:
   * GND connected GND
   * VIN connected 5V
   * LRC connected to pin 0 (Zero) or pin 3 (MKR1000, MKRZero)
   * BCLK connected to pin 1 (Zero) or pin 2 (MKR1000, MKRZero)
   * DIN connected to pin 9 (Zero) or pin A6 (MKR1000, MKRZero)

 created 17 November 2016
 by Sandeep Mistry
 */

#include <I2S.h>
#include <ArduinoLowPower.h>

const int frequency = 440; // frequency of square wave in Hz
const int amplitude = 500; // amplitude of square wave
const int sampleRate = 8000; // sample rate in Hz

const int halfWavelength = (sampleRate / frequency); // half wavelength of square wave

short sample = amplitude; // current sample value
int count = 0;

unsigned long startTime;

void setup() {
  Serial.begin(9600);
  Serial.println("I2S simple tone");

  // start I2S at the sample rate with 16-bits per sample
  if (!I2S.begin(I2S_PHILIPS_MODE, sampleRate, 16)) {
    Serial.println("Failed to initialize I2S!");
    while (1); // do nothing
  }

startTime = millis();

}

void loop() {

do {  

  if (count % halfWavelength == 0) {
    // invert the sample every half wavelength count multiple to generate square wave
    sample = -1 * sample;
  }

  // write the same sample twice, once for left and once for the right channel
  I2S.write(sample);
  I2S.write(sample);

  // increment the counter for the next sample
  count++;

} while ( millis() - startTime < 3000 );

// delay(1000);

LowPower.sleep(1000);

startTime = millis(); 

do {  

  if (count % halfWavelength == 0) {
    // invert the sample every half wavelength count multiple to generate square wave
    sample = -1 * sample;
  }

  // write the same sample twice, once for left and once for the right channel
  I2S.write(sample);
  I2S.write(sample);

  // increment the counter for the next sample
  count++;

} while ( millis() - startTime < 3000 );

delay(1000);
startTime = millis(); 
}
white-ar commented 5 years ago

Hi, @sandeepmistry and @facchinm have you had any thoughts on this issue?

I am trying to help my son with a school project to make an alarm clock that plays WAV files.

The initial issue with compatibility between the RTC & I2S (or SD card library) led me to use an external RTC. This all works (Arduino MKR Zero, RTC, I2S MAX98357A, SD card, 4x4 matrix keypad & ST7735R display). However, combined it draws around 40mA, which eats through batteries.

Hence, it would be ideal to be able to put the Arduino to sleep, ideally using the internal RTC, which can then easily be woken with an alarm interrupt, or user input.

Any thoughts on ideas for a work around would also be appreciated.

Many thanks