Close

Using a Counter for 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/25/2017 at 15:270 Comments

Using a counter for the ISR

I received a suggestion to use a counter and trip method for the Interrupt Service Routine (ISR). While is was not keen as the method (Direct Digital Synthesis) is pretty standard and gives the best long term accuracy. But that is not the whole story as this accuracy comes at the cost of sub-harmonics on the higher frequencies. Anyway I decided to have a look at it. First the speadsheet calculations:

As you can see the DDS (magic) method is more accurate especially at high frequencies and around the frequencies of most interest (middle C and A), than the count and trip method. The greatest fault of the count and trip is it cannot play some keys above 1.1 kHz.

The ISR is running at 31275.5kHz which leaves 31 us for ISR code execution time. Not a lot! You can go faster which would push these problems into higher frequencies but really for most music that is going to be played on an Arduino, it does not matter.

The second test was to listen to the tones. In this case the Count and Trip method has a much cleaner sound. At high frequencies the sub-harmonics of the DDS method are quite obvious. Again, does not matter for most of the music that is going to be played on an Arduino.

Here is the code for the Count and Trip version:

/*
   A Concurrent Key/Tone Sketch
   Six channels: A0-A5
   Procedure is SetKey(Pin,Key);
   Setq Key to 0 to turn off sound
   Middle C is Key 60   
   Author: agp.cooper@gmail.com
*/
#define TickDuration 1 // 1 ms 
const unsigned int Key2Trip[128] = {
 1913, 1805, 1704, 1608, 1518, 1433, 1352, 1277, 
 1205, 1137, 1073, 1013,  956,  903,  852,  804, 
  759,  716,  676,  638,  602,  569,  537,  507, 
  478,  451,  426,  402,  380,  358,  338,  319, 
  301,  284,  268,  253,  239,  226,  213,  201, 
  190,  179,  169,  160,  151,  142,  134,  127, 
  120,  113,  107,  101,   95,   90,   85,   80, 
   75,   71,   67,   63,   60,   56,   53,   50, 
   47,   45,   42,   40,   38,   36,   34,   32, 
   30,   28,   27,   25,   24,   22,   21,   20, 
   19,   18,   17,   16,   15,   14,   13,   13, 
   12,   11,   11,   10,    9,    9,    8,    8, 
    7,    7,    7,    6,    6,    6,    5,    5, 
    5,    4,    4,    4,    4,    4,    3,    3, 
    3,    3,    3,    2,    2,    2,    2,    2, 
    2,    2,    2,    2,    1,    1,    1,    1 
};
// 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 count=0;
volatile unsigned int count0=0;
volatile unsigned int count1=0;
volatile unsigned int count2=0;
volatile unsigned int count3=0;
volatile unsigned int count4=0;
volatile unsigned int count5=0;
volatile unsigned int trip=31;
volatile unsigned int trip0=0;
volatile unsigned int trip1=0;
volatile unsigned int trip2=0;
volatile unsigned int trip3=0;
volatile unsigned int trip4=0;
volatile unsigned int trip5=0;
volatile unsigned long master=0;
ISR(TIMER2_OVF_vect)
{
  count++;
  count0++;
  count1++;
  count2++;
  count3++;
  count4++;
  count5++;
  if (count>=trip) {
    master++;
    count=0;
  }
  if (count0>=trip0) {
    if (trip0>0) PINC|=B00000001;
    count0=0;
  }
  if (count1>=trip1) {
    if (trip1>0) PINC|=B00000010;
    count1=0;
  }
  if (count2>=trip2) {
    if (trip2>0) PINC|=B00000100;
    count2=0;
  }
  if (count3>=trip3) {
    if (trip3>0) PINC|=B00001000;
    count3=0;
  }
  if (count4>=trip4) {
    if (trip4>0) PINC|=B00010000;
    count4=0;
  }
  if (count5>=trip5) {
    if (trip5>0) PINC|=B00100000;
    count5=0;
  }
}
void SetKey(unsigned int Channel,unsigned int Key)
{
  if (Channel==0) trip0=Key2Trip[Key];
  if (Channel==1) trip1=Key2Trip[Key];
  if (Channel==2) trip2=Key2Trip[Key];
  if (Channel==3) trip3=Key2Trip[Key];
  if (Channel==4) trip4=Key2Trip[Key];
  if (Channel==5) trip5=Key2Trip[Key];
  // Turn it off
  if (Key==0) {
    if (Channel==0) trip0=0;
    if (Channel==1) trip1=0;
    if (Channel==2) trip2=0;
    if (Channel==3) trip3=0;
    if (Channel==4) trip4=0;
    if (Channel==5) trip5=0;
  }
}
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
  
  /* Start ISR */
  // 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 (31275.5 Hz), 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();   
  for (int i=0;i<128;i++) {
    SetKey(0,i);
    delay(100);
  }
  SetKey(0,0);
}
void loop()
{
  delay(1000);
  digitalWrite(LED_BUILTIN,!digitalRead(LED_BUILTIN));
}

And here is the code for the DDS version:

/*
   A Concurrent Key/Tone Sketch
   Six channels: A0-A5
   Procedure is SetTone(Pin,Key);
   Set Freq to zero to turn off tone
   
   Author: agp.cooper@gmail.com
*/
#define TickDuration 1 // 1 ms 
const unsigned int Key2Freq[128] = {
    0,    9,    9,   10,   10,   11,   12,   12,
   13,   14,   15,   15,   16,   17,   18,   19,
   21,   22,   23,   24,   26,   28,   29,   31,
   33,   35,   37,   39,   41,   44,   46,   49,
   52,   55,   58,   62,   65,   69,   73,   78,
   82,   87,   92,   98,  104,  110,  117,  123,
  131,  139,  147,  156,  165,  175,  185,  196,
  208,  220,  233,  247,  262,  277,  294,  311,
  330,  349,  370,  392,  415,  440,  466,  494,
  523,  554,  587,  622,  659,  698,  740,  784,
  831,  880,  932,  988, 1047, 1109, 1175, 1245,
 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976,
 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136,
 3322, 3520, 3729, 3951, 4186, 4435, 4699, 4978,
 5274, 5588, 5920, 6272, 6645, 7040, 7459, 7902,
 8372, 8870, 9397, 9956,10548,11175,11840,12544
};
// 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 magic=0;
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 phase=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;
volatile unsigned long master=0;
ISR(TIMER2_OVF_vect)
{
  if (phase<0x8000) {
    phase+=magic;
    if (phase>=0x8000) master++;
  } else {
    phase+=magic;    
  }
  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 SetKey(unsigned int Pin,unsigned int Freq)
{
  if (Pin==0) magic0=Key2Freq[Freq];
  if (Pin==1) magic1=Key2Freq[Freq];
  if (Pin==2) magic2=Key2Freq[Freq];
  if (Pin==3) magic3=Key2Freq[Freq];
  if (Pin==4) magic4=Key2Freq[Freq];
  if (Pin==5) magic5=Key2Freq[Freq];
  // 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 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 (31275.5 Hz), 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();   
  // Set tick duration (ms)
  magic=2095*TickDuration;
  
  for (int i=0;i<128;i++) {
    SetKey(0,i);
    delay(100);
  }
  SetKey(0,0);
}
void loop()
{
  delay(1000);
  digitalWrite(LED_BUILTIN,!digitalRead(LED_BUILTIN));
}


 Anyway, investigating the differences was interesting and worth while.

Use the version of the code you prefer.

Regards AlanX

Discussions