Close

The ISR

A project log for Simple Concurrent Tones for the Arduino

The Arduino tone library does not support concurrent multiple tones. Now I am sure someone has done this before but here is my take.

agpcooperagp.cooper 09/24/2017 at 02:390 Comments

The Interrupt Service Routine

This is the most important bit. Its code I originally wrote for a modem project.

The ISR is triggered 31372.5 times a second which is about as fast as the Arduino can sensibly go. At this speed you only have 31 us to do your stuff (e.i. the quick or the dead!). I may have to slow it down but lets try fast first.

Here is the basic code:

/*
   A Concurrent Tone Sketch
   Six channels: A0-A5
   Maximum frequency is 5957 Hz
   Procedure is SetTone(Pin,Freq);
   Set Freq to zero to turn off tone
   
   Author: agp.cooper@gmail.com
*/
// Define various ADC prescaler
const byte PS_16=(1<<ADPS2);
const byte PS_32=(1<<ADPS2)|(1<<ADPS0);
const byte PS_64=(1<<ADPS2)|(1<<ADPS1);
const byte PS_128=(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);
/* ISR: Tone on A0-5 */
volatile unsigned int magic0=0;
volatile unsigned int magic1=0;
volatile unsigned int magic2=0;
volatile unsigned int magic3=0;
volatile unsigned int magic4=0;
volatile unsigned int magic5=0;
volatile unsigned int phase0=0;
volatile unsigned int phase1=0;
volatile unsigned int phase2=0;
volatile unsigned int phase3=0;
volatile unsigned int phase4=0;
volatile unsigned int phase5=0;
ISR(TIMER2_OVF_vect) {
  phase0+=magic0;
  phase1+=magic1;
  phase2+=magic2;
  phase3+=magic3;
  phase4+=magic4;
  phase5+=magic5;
  if (phase0<0x8000) PORTC&=B11111110; else PORTC|=B00000001;
  if (phase1<0x8000) PORTC&=B11111101; else PORTC|=B00000010;
  if (phase2<0x8000) PORTC&=B11111011; else PORTC|=B00000100;
  if (phase3<0x8000) PORTC&=B11110111; else PORTC|=B00001000;
  if (phase4<0x8000) PORTC&=B11101111; else PORTC|=B00010000;
  if (phase5<0x8000) PORTC&=B11011111; else PORTC|=B00100000;
}
void setup() {
  pinMode(LED_BUILTIN,OUTPUT);
  pinMode(A0,OUTPUT);
  pinMode(A1,OUTPUT);
  pinMode(A2,OUTPUT);
  pinMode(A3,OUTPUT);
  pinMode(A4,OUTPUT);
  pinMode(A5,OUTPUT);
  pinMode(11,OUTPUT); // Used as a frequency check
  // Set ADC prescaler (assume a 16 MHz clock)
  ADCSRA&=~PS_128;                                 // Remove bits set by Arduino library
  ADCSRA|=PS_16;                                   // 16 prescaler (1 MHz)
  // Disable interrupts
  cli();
  
  // Use Timer 2 for ISR (Output on D11 for frequency check)
  // Good for ATmega48A/PA/88A/PA/168A/PA/328/P
  TIMSK2 = 0;                                      // Timer interrupts off
  TCCR2A = (2 << COM2A0)|(1 << WGM20);             // Phase correct PWM (31.25 kHz), toggle output on OC2A (PB3/D11)
  TCCR2B = (0 << WGM22)|(1 << CS20);               // 16 MHz clock (no pre-scaler)
  OCR2A = 128;                                     // Set 50% duty
  TIMSK2 = (1<<TOIE2);                             // Set interrupt on overflow (=BOTTOM)
  // Enable interrupts 
  sei();   
  // Test tones
  SetTone(0,13);
  SetTone(1,51);
  SetTone(2,103);
  SetTone(3,171);
  SetTone(4,200);
  SetTone(5,300);
  delay(10000);   
  // Turn off test
  SetTone(0,0);
  SetTone(1,0);
  SetTone(2,0);
  SetTone(3,0);
  SetTone(4,0);
  SetTone(5,0);                                     
}
void SetTone(unsigned int Pin,unsigned int Freq) {
  // Max Frequency is 5957 Hz
  if (Pin==0) magic0=4*(Freq*11/21);
  if (Pin==1) magic1=4*(Freq*11/21);
  if (Pin==2) magic2=4*(Freq*11/21);
  if (Pin==3) magic3=4*(Freq*11/21);
  if (Pin==4) magic4=4*(Freq*11/21);
  if (Pin==5) magic5=4*(Freq*11/21);
  // Turn it off
  if (Freq==0) {
    if (Pin==0) phase0=0;
    if (Pin==1) phase1=0;
    if (Pin==2) phase2=0;
    if (Pin==3) phase3=0;
    if (Pin==4) phase4=0;
    if (Pin==5) phase5=0;
  }
}
void loop() {
  delay(1000);
  digitalWrite(LED_BUILTIN,!digitalRead(LED_BUILTIN));
}

Well, it works so a good start (it helps if you have existing code to hack).

The code sets different frequencies on A0-5 for 10 seconds and then turns off.

The LED flashes when done.

My multimeter show the frequency to be +/- 1 Hz for the frequencies tested.

The code has a limit of 5957 Hz because of a quick and dirty magic number calculations.

This can be fixed but it is pretty unlikely any music midi would go this high.

Next?

I think I have to add the note duration to the sketch.

AlanX

Discussions