using the Timer1 library for PCM audio output

Hi All, I'm trying to generate audio from the Arduino, using Pulse Code Modulation.

If you're not familiar with this technique, I take a waveform (let's say a sine wave), divide it up into 48 samples, and encode it's level at that point into a char array. Then I set up an interrupt on timer1 so that every time it fires, it sets one of the timer2 pwm pins the current sample point, then increments the array index. I've changed the prescaler on timer2 so that the pwm frequency is 62,500 Hz, so once I put the output through a low-pass filter it sounds...surprisingly decent, actually.

So this all works as expected with a static frequency. On my oscilloscope, I can see the waveform that I've encoded, and I can hear it on my speaker.

Now, I should be able to vary the frequency by changing how quickly the timer1 interrupt is called, i.e. how quickly it steps through the array of samples. I'm currently using the Timer1 library in the Arduino playground, but when I try to vary the interrupt period, I get squat-- no output at all.

Has anyone used this library with varying interrupt frequencies? Or had success generating sound with this method? I'd like to use the library because manually configuring timers makes me :'(

Sorry, example code here:

#include "TimerOne.h"

char sineovertones[] = {0,84,106,99,113,127,107,79,71,51,1,-26,7,43,23,-17,-26,-23,-43,-60,-49,-44,-66,-63,0,63,66,44,49,60,43,23,26,17,-23,-43,-8,26,-1,-51,-71,-79,-107,-127,-113,-99,-106,-84};


char sine[] = {0, 22, 44, 64, 82, 98, 111, 120, 126, 128, 126, 120, 111, 98, 82, 64, 44, 22, 0, -22, -44, -64, -82, -98, -111, -120, -126, -128, -126, -120, -111, -98, -82, -64, -44, -22};
//a simple sine wave with 36 samples

long analog0 = 160; //variable to store input from analog input pin 0

byte speakerpin = 3;  //audio playback on pin 3.  This can also be set to pin 11.

volatile byte waveindex = 0; //index variable for position in waveform array Sine[]
volatile byte currentvalue = 0;

void setup() {
  
Serial.begin(57600);
  
  
/************************** PWM audio configuration ****************************/
// Configures PWM on pins 3 and 11 to run at maximum speed, rather than the default
// 500Hz, which is useless for audio output

pinMode(speakerpin,OUTPUT); //Speaker on pin 3

cli(); //disable interrupts while registers are configured

bitSet(TCCR2A, WGM20);
bitSet(TCCR2A, WGM21); //set Timer2 to fast PWM mode (doubles PWM frequency)

bitSet(TCCR2B, CS20);
bitClear(TCCR2B, CS21);
bitClear(TCCR2B, CS22);
/* set prescaler to /1 (no prescaling).  The timer will overflow every 
*  62.5nS * 256ticks = 16uS, giving a frequency of 62,500Hz, I think.   */

sei(); //enable interrupts now that registers have been set
  
  
/************************* Timer 1 interrupt configuration *************************/

Timer1.initialize(160);         // initialize timer1, and set a 1/2 second period
Timer1.attachInterrupt(playtone);  // attaches playtone() as a timer overflow interrupt
}



void playtone() {
/* This function is called by the timer1 interrupt.  Every time it is called it sets
*  speakerpin to the next value in Sine[].  frequency modulation is done by changing
*  the timing between successive calls of this function, e.g. for a 1KHz tone,
*  set the timing so that it runs through Sine[] 1000 times a second. */
if (waveindex > 47) {
  waveindex = 0;
  }

analogWrite(speakerpin, sineovertones[waveindex] + 128);
waveindex++;
}

void loop()
{

analog0 = analogRead(0) / 2;
Serial.println(analog0);
delay(100);
Timer1.setPeriod(analog0);

}

Okay, I did get it working by manually configuring timer1. Changes in capacitance are reflected as a change in frequency in the speaker output.

It’s definitely quick and dirty, but fwiw here’s the code (I’m using Paul Badger’s capacitance sensing code here: http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1171076259):

int  i;
unsigned int x, y;
float accum, fval = .20;    // these are variables for a simple low-pass (smoothing) filter - fval of 1 = no filter - .001 = max filter
long fout;

int period = 160;


char sineovertones[] = {0,84,106,99,113,127,107,79,71,51,1,-26,7,43,23,-17,-26,-23,-43,-60,-49,-44,-66,-63,0,63,66,44,49,60,43,23,26,17,-23,-43,-8,26,-1,-51,-71,-79,-107,-127,-113,-99,-106,-84};


char sine[] = {0, 22, 44, 64, 82, 98, 111, 120, 126, 128, 126, 120, 111, 98, 82, 64, 44, 22, 0, -22, -44, -64, -82, -98, -111, -120, -126, -128, -126, -120, -111, -98, -82, -64, -44, -22};
//a simple sine wave with 36 samples

int analog0 = 160; //variable used to store analog input pin 0

byte speakerpin = 3;  //audio playback on pin 3.  This can also be set to pin 11.
 
volatile byte waveindex = 0; //index variable for position in waveform array Sine[]
volatile byte currentvalue = 0;

void setup() {
  
Serial.begin(57600);
  
/************************** PWM audio configuration ****************************/
// Configures PWM on pins 3 and 11 to run at maximum speed, rather than the default
// 500Hz, which is useless for audio output

pinMode(3,OUTPUT); //Speaker on pin 3

cli(); //disable interrupts while registers are configured

bitSet(TCCR2A, WGM20);
bitSet(TCCR2A, WGM21); //set Timer2 to fast PWM mode (doubles PWM frequency)

bitSet(TCCR2B, CS20);
bitClear(TCCR2B, CS21);
bitClear(TCCR2B, CS22);
/* set prescaler to /1 (no prescaling).  The timer will overflow every 
*  62.5nS * 256ticks = 16uS, giving a PWM frequency of 62,500Hz, I think.   */

sei(); //enable interrupts now that registers have been set
  
  
/************************* Timer 1 interrupt configuration *************************/

cli(); //disable interrupts while registers are configured

bitClear(TCCR1A, COM1A1);
bitClear(TCCR1A, COM1A1);
bitClear(TCCR1A, COM1A1);
bitClear(TCCR1A, COM1A1);
/* Normal port operation, pins disconnected from timer operation (breaking pwm).  
*  Should be set this way by default, anyway. */

bitClear(TCCR1A, WGM10);
bitClear(TCCR1A, WGM11);
bitSet(TCCR1B, WGM12);
bitClear(TCCR1B, WGM13);
/* Mode 4, CTC with TOP set by register OCR1A.  Allows us to set variable timing for
*  the interrupt by writing new values to OCR1A. */

bitClear(TCCR1B, CS10);
bitSet(TCCR1B, CS11);
bitClear(TCCR1B, CS12);
/* set the clock prescaler to /8.  Since the processor ticks every 62.5ns, the timer
*  will increment every .5uS.  Timer 1 is a 16-bit timer, so the maximum value is 65536,
*  Giving us a theoretical range of .5us-32.7mS.  There are 48 samples, so the 
*  theoretical frequency range is 41.7KHz - .635Hz, which neatly covers the audio 
*  spectrum of 20KHz-20Hz.  Theoretical, because I wouldn't recommend actually calling
*  the Timer1 interrupt every .5uS :)  */

bitClear(TCCR1C, FOC1A);
bitClear(TCCR1C, FOC1B);
/* Disable Force Output Compare for Channels A and B, whatever that is.
*  Should be set this way by default anyway. */

OCR1A = 160;
/* Initializes Output Compare Register A at 160, so a match will be generated every 
*  62.5nS * 8 * 160 = 80uS, for a 1/(80uS*48) = 260Hz tone. */

bitClear(TIMSK1, ICIE1); //disable input capture interrupt
bitClear(TIMSK1, OCIE1B); //disable Output Compare B Match Interrupt
bitSet(TIMSK1, OCIE1A); //enable Output Compare A Match Interrupt
bitClear(TIMSK1, TOIE1); //disable Overflow Interrupt Enable

sei(); //enable interrupts now that registers have been set

delay(1000);

}//end setup()
 
 
 
ISR(TIMER1_COMPA_vect) {
/* timer1 ISR.  Every time it is called it sets
*  speakerpin to the next value in Sine[].  frequency modulation is done by changing
*  the timing between successive calls of this function, e.g. for a 1KHz tone,
*  set the timing so that it runs through Sine[] 1000 times a second. */

if (waveindex > 47) { //reset waveindex if it has reached the end of the array
  waveindex = 0;
  }

analogWrite(speakerpin, sineovertones[waveindex] + 128);
waveindex++;

OCR1A = period;

} //end Timer1 ISR
 
void loop()
{
  
  
//analog0 = analogRead(0) + 4;
//Serial.println(analog0);
  
 y = 0;        // clear out variables
 x = 0;

 for (i=0; i < 4 ; i++ ){       // do it four times to build up an average - not really neccessary but takes out some jitter

   // LOW-to-HIGH transition
   digitalWrite(8, HIGH);    
   // output pin is PortB0 (Arduino 8), sensor pin is PortB1 (Arduinio 9)                                   

   while ((PINB & B100) != B100 ) {        // while the sense pin is not high
     //  while (digitalRead(9) != 1)     // same as above port manipulation above - only 20 times slower!                
     x++;
   }
   delay(1);

   //  HIGH-to-LOW transition
   digitalWrite(8, LOW);              
   while((PINB & B100) != 0 ){            // while pin is not low  -- same as below only 20 times faster
     // while(digitalRead(9) != 0 )      // same as above port manipulation - only 20 times slower!
     y++;  
   }

   delay(1);
 }

 fout =  (fval * (float)x) + ((1-fval) * accum);  // Easy smoothing filter "fval" determines amount of new data in fout
 accum = fout;   
period = constrain(fout / 3, 50, 500);
Serial.println(period);


}//end loop()

Perhaps whoever maintains the timer1 library should take a gander at why you can’t change timer frequency dynamically?