Audio Library causing delays in interrupt based timing

Status
Not open for further replies.

zachtos

Well-known member
The problem: Audio library while playing files will not return reliable timings even when using interrupt based timers.
Question: Can someone replicate this problem? If so, any solutions available would be amazing to aid in my company product progress.

Test setup requirements:
-teensy 3.1
-teensy audio shield and Audio library from Teensy
-microSD card with a RAW file to playback
-teensyduino 1.2rc3 and Arduino 1.06 installed
-oscilloscope with probes on ground to pin 25 output

059AP.RAW - audio file to use for test, can use any RAW type file though w/ same results

The Code
Code:
#include <Audio.h>
#include <Wire.h>
#include <SD.h>
#include <SPI.h>

// Create the Audio components.  These should be created in the
// order data flows, inputs/sources -> processing -> outputs
//
AudioPlaySdRaw     wav;
AudioOutputI2S     dac;

// Create Audio connections between the components
//
AudioConnection c1(wav, 0, dac, 0);
AudioConnection c2(wav, 1, dac, 1);

// Create an object to control the audio shield.
// 
AudioControlSGTL5000 audioShield;

volatile unsigned long timer = 0; //timer to increment inside interrupt
unsigned long start_time = 0; //set up a time variable to see how long it's been since timer fired
IntervalTimer myTimer; //setup a timer to fire

void setup() 
{

  myTimer.begin(waitIR, 5);//set a timer to fire and increment every 5 microseconds
  myTimer.priority(0);// set timer to have highest priority of 0 (255 lowest), should return reliable timings while using audio but it does not
  Serial.begin(38400);
  analogWriteFrequency(25, 38000); // set pin 25 to output PWM at 38khz
  analogWriteResolution(10);//10 = 1023 value, more bits of resolution for higher frequency
  pinMode(25,OUTPUT); //pin to oscillate

  AudioMemory(20);

  audioShield.enable();
  audioShield.volume(0.25); //set volume 0 to 1

  SPI.setMOSI(7);
  SPI.setSCK(14);
  SD.begin(10);
}

void loop() 
{
  ///////////turn audio off briefly while crating a waveform that pulses on/off on pin 25 at 38khz at 1200uS then 600uS with 600uS spacer ///////////
  AudioNoInterrupts(); //enable this line to fix waveform
  for (byte x = 12; x >0; x--)
  {
    oscillationWrite_new(25, 1200); //send start bit, actually first bit
    IR_spacer(25,600);
    oscillationWrite_new(25, 600); //send start bit, actually first bit
    IR_spacer(25,600);
  }
  AudioInterrupts();  //enable this line to fix waveform
  wav.play("059AP.RAW"); //play the sound effect, can use any sound here w/ same results
  delay(500);
  // AudioNoInterrupts(); //enable this line to fix waveform

  /////////// next waveform will have timer delays about every 6ms that last about 0.7ms ////////////
  for (byte x = 12; x >0; x--)
  {
    oscillationWrite_new(25, 1200); //send start bit, actually first bit
    IR_spacer(25,600);
    oscillationWrite_new(25, 600); //send start bit, actually first bit
    IR_spacer(25,600);
  }
  // AudioInterrupts();  //enable this line to fix waveform
  wav.play("059AP.RAW"); //play the sound effect, can use any sound here w/ same results
  delay(500);
}

void oscillationWrite_new(byte pin, int time) //only 20% duty cycle allows you to push the LED harder then normal
{
  start_time = timer;
  analogWrite(pin, 300);
  while(timer - start_time < time/5)
  {
    //wait
  }
  analogWrite(pin, 0);
}

void IR_spacer(byte pin, int time)
{
  start_time = timer;
  analogWrite(pin, 0);
  while(timer - start_time < time/5)
  {
    //wait
  }
}

void waitIR(void) 
{
  timer++;  // increment timer via interrupts
}

expected waveform
DS1Z_QucikPrint22.jpg

second waveform will have unexpected delays causing the overall time to be wrong, roughly a delay every 6ms
DS1Z_QucikPrint23.jpg


//////////////////////////////////////////////
SD Card Test - CLASS 4 SANDISK FROM OEMPCWORLD USING FIRST 4 MUSIC FILES (tested w/ a class 10 card Sandisk Ultra, same problem persisted, I have more cards in the mail to verify for sure that it's not hardware)
------------
SD card is connected :)
Card type is SDHC
File system space is 3956.80 Mbytes.
SD library is able to access the filesystem

Reading SDTEST1.WAV:
Overall speed = 0.57 Mbyte/sec
Worst block time = 4.08 ms
140.80% of audio frame time

Reading SDTEST1.WAV & SDTEST2.WAV:
Overall speed = 0.54 Mbyte/sec
Worst block time = 3.67 ms
126.42% of audio frame time

Reading SDTEST1.WAV & SDTEST2.WAV staggered:
Overall speed = 0.54 Mbyte/sec
Worst block time = 2.78 ms
95.71% of audio frame time

Reading SDTEST1.WAV, SDTEST2.WAV, SDTEST3.WAV:
Overall speed = 0.53 Mbyte/sec
Worst block time = 5.53 ms
190.71% of audio frame time

Reading SDTEST1.WAV, SDTEST2.WAV, SDTEST3.WAV staggered:
Overall speed = 0.53 Mbyte/sec
Worst block time = 3.77 ms
129.91% of audio frame time

Reading SDTEST1.WAV, SDTEST2.WAV, SDTEST3.WAV, SDTEST4.WAV:
Overall speed = 0.53 Mbyte/sec
Worst block time = 7.40 ms
254.92% of audio frame time

Reading SDTEST1.WAV, SDTEST2.WAV, SDTEST3.WAV, SDTEST4.WAV staggered:
Overall speed = 0.53 Mbyte/sec
Worst block time = 4.75 ms
163.55% of audio frame time

/////////////////////////////////////
 
Just an idle thought, could you use the audio library to generate the timing for the IR control signal?
 
First, I need to point out the incorrectness of your summary, specifically this:

The problem: Audio library while playing files will not return reliable timings even when using interrupt based timers.

Understanding this problem is important, for you and perhaps anyone else who later finds this thread and needs to do a similar thing. Before talking about specific solutions, I want to make sure you and anyone else finding this later understand this issue.

The ARM interrupt system uses priority levels, where lower numbers mean higher priority. 0 is the highest priority. Most interrupt default to priority level 128. The lowest interrupt priority is 255. Your Arduino sketch effectively runs at priority level 256.

The ARM interrupt controller allows nested interrupts, up to 16 levels deep, so higher priority interrupts can run even when a lower priority interrupt has control. This is fundamentally different than 8 bit AVR, which most people familiar with AVR understand, where there is no interrupt priority scheme. On AVR, you have to enable all interrupts if you want any to be able to work. On ARM, when an interrupt at priority level 128 is running, all code at levels 128 to 256 is unable to run, but all interrupts assigned a higher priority can still interrupt it.

The audio library uses 2 types of interrupts. There are short duration interrupts for the DMA channels, which today run at priority level 128. Future versions of the audio library might assign these higher priority, probably 96. The actual computation of audio data is done with a low priority interrupt. Today it's assigned level 255. Future versions will likely raise it to 224.

The timing accuracy of any code is impacted by all interrupts that run at the same or higher priority.

In the code above, you're using an IntervalTimer, but all it's doing inside its interrupt is incrementing a number. You've raised the priority to the highest level, so the the increment of that number will happen with very good timing accuracy.

However, your code that actually reads the number is still in the main program, running at the very lowest priority level. While the audio library's low priority interrupt does lots of calculations and I/O if using the SD card, that number keeps incrementing with very accurate timing, but your code that responds to the number can't run, so the net result it poor timing.

To make this work properly, you're going to have to move more of the code into that high priority interrupt. Since you're trying to create a waveform to drive a IR LED, you're going to need to turn the PWM signal on/off within that interrupt.

5 microseconds is certainly much faster than you need. Even a single cycle of 38 kHz is 26.3 us. You should redesign your code to run the IntervalTimer interrupt at the moments when you need to turn the PWM waveform on/off. You can call analogWrite() from within the interrupt. It will give you extremely reliable timing. Of course, the 2 challenges in any interrupt-based programming are limiting the time your interrupt runs (especially if it's at a very high priority) and properly sharing data between the main program and the interrupt.

Hopefully this lengthy explanation helps you to revise this code. I want to be absolutely clear, this timing resolution issue you're seeing is fundamentally how the hardware and interrupt system works. It's not a bug in the audio library. It's simply how things are. To make your project work as you want, you have to design your code to use the system's higher priority interrupts to do your timing critical work (not merely increment a variable), while hopefully keeping your high priority interrupt usage short enough to not mess up the audio library and other stuff.
 
Last edited:
Just an idle thought, could you use the audio library to generate the timing for the IR control signal?

You could if it needed a signal within the 22 kHz audio bandwidth. It's actually a great idea, for under 22 kHz signals.

But 38 kHz is much too high for the 44.1 kHz sample rate to synthesize.
 
In the interest of chaos perhaps PDB_PERIOD could be adjusted? I have sampled at 8Khz for the FFT example.
 
I shall give this a shot tomorrow. Excellent explanation. I will try to create interrupt driven IR output routine which will probably be 30ms, a bit long, but it that's the only way to keep the timings accurate, i'll give it a shot, probably at 130 priority first.
 
SWEET. I got it working. I will fine tune the real code tomorrow, but this demo worked great. SUPER HAPPY, thanks for pointing out the obvious hardware interrupt issues to me. I think this was the last major hurdle I had with production plans. The rest should be gravy. Time to start making PCBs and make an order for some bootloaders in the next 2 months.

Code:
#include <Audio.h>
#include <Wire.h>
#include <SD.h>
#include <SPI.h>

// Create the Audio components.  These should be created in the
AudioMixer4        mix1;    // two 4-channel mixers are needed in
AudioMixer4        mix2;    // tandem to combine 6 audio sources

// Create Audio connections between the components
AudioPlaySdRaw     wav;
AudioPlaySdRaw     wav2;
AudioPlaySdRaw     wav3;

AudioOutputI2S     dac;

// Create Audio connections between the components
// order data flows, inputs/sources -> processing -> outputs
AudioConnection c1(wav, 0, mix1, 0);
AudioConnection c2(wav2, 0, mix1, 1);
AudioConnection c3(wav3, 0, mix1, 2);
AudioConnection c5(mix1, 0, mix2, 0);   // output of mix1 into 1st input on mix2
AudioConnection c10(mix1, 0, dac, 0);

// Create an object to control the audio shield.
AudioControlSGTL5000 audioShield;

volatile unsigned long timer = 0; //timer to increment inside interrupt
volatile boolean fired = false;
volatile boolean wait = false;
volatile byte array = 0;
boolean flip = false; //toggle sound

IntervalTimer myTimer; //setup a timer to fire

void setup() 
{

  Serial.begin(38400);
  analogWriteFrequency(25, 38000); // set pin 25 to output PWM at 38khz
  analogWriteResolution(10);//10 = 1023 value, more bits of resolution for higher frequency
  pinMode(25,OUTPUT); //pin to oscillate

  AudioMemory(20);

  audioShield.enable();
  audioShield.volume(.5); //set volume 0 to 1

  SPI.setMOSI(7);
  SPI.setSCK(14);
  SD.begin(10);
}

void loop() 
{

  for (byte x = 5; x > 0; x--)
  {
    myTimer.begin(waitIR, 50);//set a timer to fire and increment every 5 microseconds
    myTimer.priority(0);// set timer to have highest priority of 0 (255 lowest), should return reliable timings while using audio but it does not
    fired = true;
    while(fired)
    {  //interrupt of firing IR is actually happening here, but idles here for other less priority interrupts to fire
      //sit and wait or watch IR maybe
    }
    if (flip == false)
    {
      flip = true;
    }
    else
    {
      flip = false;
    }
    if (!flip)
    {
      wav.play("048M4.RAW");
    }
    else
    {
      wav2.play("048M4.RAW");
    }
    delay(75);
  }
  delay(2500);
}

void waitIR(void) 
{
  timer++;  // increment timer via interrupts every 50ms no matter what
  /////////// if sending IR data do this too //////////////////////
  if (array >= 12)
  {
    array = 0;
    fired = false; //triggers an exit from loops
    analogWrite(25, 0);
    myTimer.end();
  }
  else if (fired && !wait)
  {
    analogWrite(25, 300);
    if (timer >= 1200/50)
    {
      timer = 0;
      wait = true;
      analogWrite(25, 0);
    }
  }
  else if (fired && wait)
  {
    if (timer >= 600/50)
    {
      timer = 0;
      wait = false;
      array++;
      analogWrite(25, 0);
    }
  }
  //////////////// then do not do this if not firing IR ///////////////
}
 
Status
Not open for further replies.
Back
Top