How can I slow the ISR down just a little?

I'm using an Atmega1284p to record audio at 19,500 bytes per second. When I play the music back, I hear a brief skip-ahead every 50 seconds or so.

I think the cause must be that reading the audio buffer into the SD Card is just a tad slower than the ISR is writing to that buffer, causing the ISR's writing to catch up with and pass the SD Card's reading about once every 50 seconds. This, of course, causes one full buffer of data to never get saved to the SD Card.

However, the ISR frequency settings I'm aware of (shown in the code below) are basically Times Two, or Divide by Two, while I only need a slight slow-down of the ISR frequency.

How can I manage that?

char buffA[2049];
char buffB[2049];
int buffCnt = 0;

void setupISR()
{
//   ADC ISR frequencies available
//   *** ISR FREQUENCIES ***
//   0-1-2  9,700 // (1 << ADPS0) | (1 << ADPS1) | (1 << ADPS2)
//   x-1-2  19,500
//   0-x-2  37,800
//   x-x-2  76,900
//   0-1-x  154,000
//   0-x-x  233,000
//   x-1-x  233,000

  cli();//disable interrupts
  ADCSRA = 0; // clear before adding bits
  ADCSRB = 0; // clear before adding bits
  ADMUX |= (1 << MUX0); //setting input pin A7
  ADMUX |= (1 << MUX1); //setting input pin A7
  ADMUX |= (1 << MUX2); //set input pin A7
  ADMUX |= (1 << REFS0); //set reference voltage 0 0 external, 0 1
  //ADMUX |= (1 << REFS1); //
  ADMUX |= (1 << ADLAR); //left align the ADC value- so we can read highest 8 bits from ADCH register only
  bitSet(ADMUX, 6); // set bits 6, 7 for AREF of 2.56 Volts
  bitClear(ADMUX, 7); //
  ADCSRA |= (1 << ADPS1) | (1 << ADPS2); //set ADC clock
  ADCSRA |= (1 << ADATE); //enabble auto trigger
  ADCSRA |= (1 << ADIE); //enable interrupts when measurement complete
  ADCSRA |= (1 << ADEN); //enable ADC
  ADCSRA |= (1 << ADSC); //start ADC measurements
  isrB = true;
  sei();//enable interrupts
}

unsigned int bCount = 0;
ISR(ADC_vect)
{
  if(aReady)
    PORTC = buffA[buffCnt];
  else
    PORTC = buffB[buffCnt];
  if (buffCnt == 2047)
  {
    buffCnt = 0;
    aReady = ! aReady;
    myFileDo = true;
  }
  else
    buffCnt++;
}

// ....

void loop()
{
  while (!myFileDo) {};
  myFileDo = false;
  if (aReady)
    myFile.read(buffB,2048);
  else
    myFile.read(buffA,2048);
}

I don't think the problem is with your ISR. You should always want an ISR to be as fast as possible.

I think you are using the free-running mode of the ADC. If your surmise is correct it is due to the speed at which free-running mode operates and AFAIK there are limited opportunities to affect that.

What ADC clock speed have you chosen? and how many samples per second does that give (at 13 clocks per sample).

...R

You're right, Robin, I am "using the free-running mode of the ADC". I read that doing so is much more efficient and faster than repeatedly calling analogRead().

To answer your question, I beleive the line:
ADCSRA |= (1 << ADPS1) | (1 << ADPS2);is setting the ADC clock speed, which my scope measures as giving me 19,500 samples per second.

Sorry to hear there's no way to fine-tune the frequency in free-running mode.

As an alternative, I just now tried leaving it in "free-running mode", but doing all the work in the main loop, like this:

void loop()
{
  if (recordMicro < micros())
  {
    recordMicro += 25;
    if(aReady)
      buffA[buffCnt]=ADCH;
    else
      buffB[buffCnt]=ADCH;
    if (buffCnt == 2047)
    {
      buffCnt = 0;
      aReady = ! aReady;
      if (aReady)
        myFile.write(buffB,2048);
      else
        myFile.write(buffA,2048);
      recordClock();
      if (!digitalRead(select))
        endRecord();
    }
    else
      buffCnt++;
  }

In playback, the resulting music had three problems:
(1) The pitch was "shaky", meaning the recording speed wasn't totally constant.
(2) There was a "snap-snap-snap-snap" sound.
(3) I wasn't able to get the recording speed quite fast enough.

I think all three problems were due to time-sharing with other processes, such as writing to the SD-Card. With the interrupt, I believe the SPI clock can stop a moment and then continue after the interrupt. But that doesn't happen with the main loop.

Clearly, I still need to do the recording from within an interrupt. So, would it work to have two interrupts running without them interfering with each other? If so, I could leave the ADC free-running at a higher frequency, and read the analog port from the slower interrupt adjusted to be the frequency I need.

If that's feasible, how would I go about it?

Can you please post your whole sketch? We often find the actual problem is somewhere else in the sketch other than what is originally posted.

RE: your idea that the SD card write is too slow. Unlikely to be the cause. At 19500 bytes/sec and a buffer size of 2048 for each of your buffers, that gives us 2048/19500 = 105ms (0.105 seconds) to write out the 2048 bytes before our buffer is needed again. That seems like plenty of time. You could try increasing your buffer sizes and seeing what effect that has.

If your sample speed is slightly out, it shouldn't cause a problem. It just means the audio will play back at a very slightly different speed. You won't hear it.

But, there is nothing obviously causing a problem in the code you posted, so please post the whole sketch.

With the interrupt, I believe the SPI clock can stop a moment and then continue after the interrupt

The SPI data buffer and transfer clock go out independently of the ISR happening, it's a separate set of hardware within the chip.

You should look into using a fast external ADC with SPI interface. fat16lib worked with me last summer to record mono 16-bit sound at 44,100 (CD quality speed) and save to SD, and as long a sample as one wanted to record.
External ADC can be read in 3 SPI.transfers, for example see Figure 6

Run at SPI clock of 4 MHz (default speed for SPI.begin( ) ; )
3 SPI.transfers need ~ 10-12uS. Way faster than internal ADC, and 16 bits vs 10 bits for better sound quality.
Total code looking something like this for a read:

PORTB = PORTB & 0b11111011; // clear D10 on Uno
byte0 = SPI.transfer(0);
byteHIGH = SPI.transfer(0);
byteLOW = SPI.transfer(0);
PORTB = PORTB | 0b00000100; // set D10
dataInt = (byteHIGH <<8) | byteLOW; // is there a faster way to move a byte into the upper half?

I think you can find single channel ADCs, probably much pricier, that can do the transfer in 2 SPI.transfer() time periods; look for units with much faster conversion rates.

How about using the fast A/D mode, include this in the setup:-

 // set up fast ADC mode
   ADCSRA = (ADCSRA & 0xf8) | 0x04; // set 16 times division

Alternatively you can have the A/D generate an interrupt and then let the ISR read the A/D, place it in the buffer and set the next conversion going.

It would help to see the sketch that's likely causing the problem, the one that writes to the SD card.

Instead of using the ADC conversion time to determine your sample rate you could use a timer interrupt instead. Within the ISR you could read the ADC result and initiate a new conversion.

It's possible that the problem with your invisible sketch is insufficient buffer size. SD cards can have significant write latency on occasion. I've seen 70ms delays with mine and have read that 100ms isn't uncommon. Search the forum for "SD card write latency".

Also search for "fast SD logger" and focus on posts by fat16lib. He has authored code to do what you're trying to do.

While I'm going to give deference to @CrossRoads and his reply - here's another potential idea...

Right now, it appears you are using double-buffering for the recording - have you considered (or tried) a triple-buffering system?

Of course - if the buffers and access during flips isn't the issue - then the above likely won't help...

CosmickGold:
Clearly, I still need to do the recording from within an interrupt.

Correct.

CosmickGold:
So, would it work to have two interrupts running without them interfering with each other?

No. One interrupt would block the other while being active.

The problen is: Every sector write to SD may take up to 70 milliseconds ("worst case"), or even more. After writing a sector, it may also be necessary to write another sector to SD (update FAT sector that the written sector is belonging to the file) and to read from SD (read from FAT next free sector for writing). So one command like "myFile.write(buffA,2048);" may lead to three actions that have to be done on SD card.

The problem seems to me, that you are just using 2 buffers of 4 SD sectors in size each. You better adapt your buffer size to the sector size on SD (512 bytes) and use more sectors for buffering data.

You could win a lot time for buffering and writing, if you would use 8 buffers of 512 bytes each.

So your ISR routine would fill 8 buffers of 512 bytes in a round robin scheduling. You are just managing a read index and a write index. While readIndex==writeIndex, nothing has to be written (no buffer is full).

Your loop function would "chase" after the buffers also in a round robin scheduling and write buffers marked as "full buffer" to SD, then unmark them to be free then.

In that case, if the worst case scenario comes true, you can fill up to 7 buffers from the ISR, while 1 buffer is written to SD card. 7 buffers would represent a buffering time of 7*512/19500= 0,183 = 183 milliseconds. This should be enough for "worst case buffering" with multiple access to SD (write sector to file, mark sector in FAT as belonging to file, read next free FAT entry).

So instead of

char buffA[2049];
char buffB[2049];
int buffCnt = 0;

You'd perhaps better have data buffers like that:

char buff[8][512];
byte activeSampleBuffer=0; // the buffer active for filling from ISR
byte activeWriteBuffer=0;  // the next buffer writing from loop function
int buffCnt = 0;

And the loop function would be something like that:

void loop()
{
  if (activeSampleBuffer!=activeWriteBuffer)
  {
    myFile.write(buff[activeWriteBuffer],512);
    activeWriteBuffer++;
    if (activeWriteBuffer>=8) activeWriteBuffer=0;
  }
}

Here's the topic where I worked on the 44.1KHz sample & record system with fat16lib.
http://forum.arduino.cc/index.php?topic=180769.0
Has his working software too. I still need to get the ADC & DAC circuits I want cleaned up & layed out on PCB. I had that started, and then BobuinoII morphed out of that and I haven't moved ahead from protoboard playing (still).

There's a LOT of good advice and information from the comments above. I never fail to be surprised and impressed.

For now, I just learned how to set interrupt timer1 to virtually any frequency I could want. I found really good information at: Arduino Timer Interrupts.

It gives the formula for getting the interrupt frequency you want as:

interrupt frequency (Hz) = (Arduino clock speed 16,000,000Hz) / (prescaler * (compare match register + 1))

And how to find the number to put in the match register as:

compare match register = [ 16,000,000Hz/ (prescaler * desired interrupt frequency) ] - 1

But my math is so lousy. I look at formulas and just think "Duhh!"

So I modified a sketch into a way of finding the frequency, like playing a game of "hotter...colder".

// This sketch is based on:
//timer interrupts
//by Amanda Ghassaei
//June 2012
//http://www.instructables.com/id/Arduino-Timer-Interrupts/

/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
*/
volatile unsigned long count;

void setup()
{
  Serial.begin(9600);
  cli();//stop interrupts

  // *** Timer1 Interrupt Settings ***
  TCCR1A = 0;// set entire TCCR1A register to 0
  TCCR1B = 0;// same for TCCR1B
  TCNT1  = 0;//initialize counter value to 0
  // SETTING ONE
  OCR1A = 221;// how high to count before intterrupt occurs (must be < 65536)
  TCCR1B |= (1 << WGM12); // turn on CTC (Clear Timer on Compare) mode
  
  // SETTING TWO
  // Choose a pre-scale from the following 5 choices:
  TCCR1B |= (1 << CS10);                  // divide by 1
  //TCCR1B |= (1 << CS11);                // divide by 8
  //TCCR1B |= (1 << CS11) | (1 << CS10);  // divide by 64
  //TCCR1B |= (1 << CS12);                // divide by 256
  //TCCR1B |= (1 << CS12) | (1 << CS10);  // divide by 1024

  TIMSK1 |= (1 << OCIE1A);// enable timer compare interrupt

  sei();//allow interrupts
}

ISR(TIMER1_COMPA_vect)
{
  count++;
}


void loop()
{
  // SETTING THREE
  delay(250); 
  Serial.println(count);
  count = 0;
}

To use it, go to
// SETTING TWO
and uncomment your choice of the five prescaler settings you'd like to try.

Go to
// SETTING ONE
and enter the number you guess you'll need to count to. (I started with 200, because I wanted to be pretty accurate, and smaller numbers make bigger changes when you go up or down even one digit.

At
// SETTING THREE
you'd normally have it set to delay(1000), to see how many interrupts occur each second. I have it set to a quarter second, delay(250) because I want to average four interrupts together, 18,000 times each second. (And of course, 1/4th the number of actual interrupts occurs in 1/4 second.)

And of course, you've got to open your "Serial Monitor" window to see the resulting interrupt frequency.

I hope this proves helpful to someone. It worked for me. Now I need to move on to the bigger problem described above, using the 18,000 cps I've here learned how to create.

flyrontech:
there is MP3 audio recording module FN-RM01 that can meet your needs.

This morning, the above abbreviated message was left on a topic I started over four months ago.

I thought I was long ago done with trying to use separate audio boards, as the ones I tried ALL had serious flaws, from missing traces on their pcb board, to operating instructions that were just plain wrong. But this board looks so promising, I've ordered it to give it a try. If the board actually works (this time), that will have a lot of advantages over what I'm trying to do here:

(1) Using .mp3 instead of .wav will make far smaller files.
(2) Using a standard audio format will make it possible for files recorded elsewhere, to be played on my project/product.
(3) Will no longer need the microphone pre-amp.
(4) Will no longer need the microphone signal-conditioning board.
(5) Will no longer need the output digital pot (for volume control).
(6) Will no longer need the speaker power-amplifier board.
(7) Will no longer need the 17 resistors that convert 8-pin digital audio to analog audio (and the 34 holes drilled for their leads).

All of the above saving money and making customers happier.

The web page for the FN-RM0 audio board shows a price of $31.48. That's too much to pay per item, but the small gray (hard to even see) print above the price explains:

"(2 pieces / lot , US $ 15.74 / piece )"

I left them a note saying they must be loosing sales by not making the real price more clear; it's just $15.74 per audio board. And for me, when you subtract the parts I won't need and the holes that won't be drilled, it almost completely pays for itself!

While waiting for it to be delivered, I'm going to continue solving the problem this top is about (considering that I have never found an audio board that actually WORKS), but I'm really keeping my fingers tightly crossed for this one.