Skip to content
Rob Stave edited this page Feb 19, 2018 · 5 revisions

Overflowing your interrupts

The ATTINY85 can do some pretty neat stuff, but when its running at 8Mhz, you have to do some tricks to squeeze out the most of it for audio applications. Signal processing at 44.1Khz is not going to be easy to do. Considering:

8MHz/44.1KHZ = 181 clock cycles per interrupt.

You will spend a minimum of 10% of that just setting up and getting out of the interrupt. See Arduino interrupts.

So the key to using Arduino for audio applications is sometimes going to a balance between extreme engineering and scaled down expectations.

Bit banging VCO

One of the strategies I have used in many of the sketches here is to just flip bits based on counter values. Just check a counter on a timed interrupt and flip bits accordingly. This is not a particularly horrible approach, if you understand and accept the limitations.

ACS-0002 is a pretty good example. It is not a complicated sketch. Just two analog reads and an interrupt timer that flips bits based on their respective counters. In addition, there is a third bit that I flip based on a mix of the first two.

Setup code is

  TCCR1 = 0;                  //stop the timer
  TCNT1 = 0;                  //zero the timer
  OCR1A = 50;                //set the compare value
  OCR1C = 50;                //set the compare value  ??? needed
  TIMSK = _BV(OCIE1A);        //interrupt on Compare Match A
  TCCR1 = _BV(CTC1) | _BV(CS11); // Start timer, ctc mode, prescaler clk/2

So we are setting up timer one to invoke the interrupt with the OCR1A at 50.

8 MHZ / ( clock Prescaler * (OCR+1)) = 8,000,000/2/51 = 78,431 Hz

The basic snippet used in the interrupt is:

  if (oscCounter1 > oscFreq1) {
    oscCounter1 = 0;
    PORTB ^= (_BV(PB0));
  }
  oscCounter1++;

We read the value of the Analog pin and map it between 5 and 108 to oscFreq1 (somewhat arbitrary numbers)

Every time we enter the loop, if we have counted up to our number, we flip the pin and reset the counter.

For a counter value of 108, we find our theoretical frequency to be: Interrupt/(oscFreq1 * 2)

so

oscFreqCounter Freq
5 7,834 hz
6 6535 hz
7 5596 hz
8 4896 hz
9 4352 hz
.. ..
106 369 hz
107 366 hz
108 362 hz

As you can see, there is a LOT of resolution for the lower frequencies and not so much for the higher ones. It does not have a smooth ramp in frequency at all.

The upper resolution could increasing the frequency of the interrupt. However there comes a point where the interrupt will be interrupted by the interrupt and you will get no further gains.

At 78khz we are at close to 100 cycles an interrupt. Reducing it more will start to cause issues.

To see this, I uploaded the same sketch over and over across multiple OCR1A values. I did not really get the theoretical number to start with. Not sure why, but its close enough to illustrate the issue.

interrupt

The first line is the frequency of the highest note for the sketch in an ideal world. As if the interrupt had no limitations.

The next line is the code pretty much as is in ACS-0002. I hardcoded the values, but the sketch is basically the same. Note with an OCR1 of around 35-40, it just stops. The size of the interrupt is taking all the time allowed.

The next line shows that it gets worse by switching out the BYTE with a SHORT. Yes, just using a two byte variable makes the interrupt longer and thus reduces the highest available frequency.

Whats the take away here?

  1. make your interrupts as small and fast as possible. Use the smallest variables you can.
  2. Suit the design to the requirements. In this case, I just needed a chip I could plop on a breadboard to make a square with the bonus of making it a bit tunable to suit my taste. Anything more complicated is over engineered. I you want a nice frequency resolution at higher frequencies, this is NOT the way to do it.

What marginal improvements could be made? For one, we are actually using one timer for two Oscillators. Instead, we should consider one per pin, and ditch that third pin mixing code. Its just there for fun anyways. That would reduce the over all interrupt code to just a flip. Since the OCR1A value is larger than a byte, you can get better resolution as well.

What about DDS? Absolutely. In this case, making an 8Mhz chip work with 2-3 hz resolution at 6KHz frequencies is a lot to ask with just timer interrupts. Lets say your sample rate was 20Hz.

  • If you flipped a bit every interrupt, that would give you a 10hz square wave.
  • If you flipped a bit every other interrupt, that would give you a 5 hz square wave.
  • If you flipped a bit every third interrupt, that would give you a 3.33 hz square wave.

With DDS, you would instead use tables to approximate where the wave should be and output your value based on a pointer to a wave table. It would not be perfect, but you would be able to get 8 Hz and 9 Hz much better.

These strategies use PWM and a low pas filter to generate the signal. It is no longer one bit. Tradeoffs right :)

Some good example are Mozzi and Illutron

Clone this wiki locally