Tone() Produces Unstable Output at Certain Frequencies

Hi All,

I am using a Pro Mini 5V/16MHz with a GPS receiver to generate a frequency output that is proportional to speed to drive a motorbike speedometer.

While developing the code I found the standard tone() function in 1.6.7 to be unstable at certain frequencies. As a very simple example -

unsigned int frequency = 20000; int outPin = 9;

void setup() { tone(outPin, frequency); }

void loop() { }

This works as expected with 20KHz on pin 9 with a stable output frequency measured on both a DVM and oscilloscope, but change the frequency to 22000 and the output frequency becomes unstable with the output drifting +/- 500Hz over time.

Having looked at the core tone.cpp source I cannot see any reason why this should be, or is there some dithering function that averages the frequency output to that required due to division errors for the pre scaler and timer values ?

I have written directly to the timer registers and got very close to 22KHz with a very stable output frequency so this is possible but this was not using a ISR, unlike tone().

Has anyone else observed this and can explain why this should be the case at certain frequencies but not other ?

Many thanks in advance ...

This also uses a timer : https://bitbucket.org/teckel12/arduino-toneac/wiki/Home

This is tone.cpp: https://github.com/arduino/Arduino/blob/master/hardware/arduino/avr/cores/arduino/Tone.cpp

The Arduino uses Timer0 for time, for example for millis(). That interrupt might delay other interrupts now and then.

NiLL:
Has anyone else observed this and can explain why this should be the case at certain frequencies but not other ?

Many people using interrupts and interrupts for each and everything in their sketches already found strange behaviour sooner or later.

So look up and watch your code for using interrupt handling routines (also in included libraries).

Just a guess: Did you possibly include and use “SoftwareSerial”?

Hi Koepel,

Thanks for your suggestions - I guess it can only be the interaction of the millis() ISR and that of tone() but I did not expect this to have such a significant effect, especially as millis() should be relatively fast in terms of what it has to do but will take a look ... The only way I guess to verify this would be to disable millis().

Thanks Jurs,

I did have hardware serial for the initial code development but when I spotted the issue I used the simple sketch shown in my original post and this exhibits the same behaviour ...

If you're using pin 9 and not using timer 1 then you can configure that timer to generate a 22000Hz square wave without any interrupts.

Try this:

// Formula for AVR for timer 1 square wave (includes rounding)
// OCR = (((F_CPU/prescaler)/frequency)+1)/2 - 1;

#define COMPARE_VALUE_22000_HZ      363       // OCR1A value for 22000Hz square wave


// ---------------------------------------------------------------------------------------
// startBeep
// ---------------------------------------------------------------------------------------
void startBeep()
{
  TCCR1A = bit(COM1A0);                      // toggle OC1A on compare match
  TCCR1B = bit(WGM12) | bit(CS10);           // CTC mode, prescaler = 1
  OCR1A = COMPARE_VALUE_22000_HZ;            // OCR1A value for 22000 Hz tone
}

// ---------------------------------------------------------------------------------------
// stopBeep
// ---------------------------------------------------------------------------------------
void stopBeep()
{
  TCCR1B = 0;                                // stop timer
  TCCR1A = bit(COM1A1);                      // clear OC1A on compare match
  TCCR1C = bit(FOC1A);                       // force compare to set OC1A low
}

Don't disable millis(), use a hardware timer. The timers are made for such things. The toneAC uses two pins, but it works up to a few MHz and does all the prescaling and so on for you. Maybe you want to add more and more to your sketch, with perhaps more use of interrupts. Or perhaps a DS18B20 which disables the interrupts temporarily or NeoPixels which also disables the interrupts. Using a hardware timer is the only good option.

Thanks jboyton and Koepel,

I did use something similar myself to verify that I should be able to generate a stable frequency at 22KHz on the same pin but I guess I was a bit naive to assume I could simply use tone(). I need to generate a frequency between 1000Hz and 60000Hz based on the current speed. Timer 1 and a lookup table in FLASH looks to be the most simple solution that is also deterministic.

but change the frequency to 22000 and the output frequency becomes unstable with the output drifting +/- 500Hz over time.

I can not confirm your observation with this sketch.

volatile unsigned long  count = 0;
unsigned long copyCount = 0;

unsigned long lastRead = 0;
unsigned long interval = 1000;

unsigned int maximumFreq = 0;
unsigned int minimumFreq = 25000;

void setup()
{
  Serial.begin(115200);
  Serial.println("start...");

  pinMode(2, INPUT_PULLUP); //external interrupt on pin2
  attachInterrupt(0, isrCount, RISING);
  
  tone(2, 22000); //will generate interrupt on pin2
}

void loop()
{
  if (millis() - lastRead >= interval) //read interrupt count every second
  {
    lastRead  += interval; //millis();
    // disable interrupts,make copy of count,reenable interrupts
    noInterrupts();
    copyCount = count;
    count = 0;
    interrupts();

    if(copyCount >= maximumFreq)
      maximumFreq = copyCount;

    if(copyCount <= minimumFreq)
      minimumFreq = copyCount; 

    Serial.println(copyCount); //raw counts in time period
    Serial.println(maximumFreq);
    Serial.println(minimumFreq);
    Serial.println();
  }
}

void isrCount()
{
  count++;
}

Good job, cattledog. None of us thought to check the initial assumption. Why would it / how would it drift by 500Hz anyway?

I tried the code from NiLL's first post and used a second Arduino to measure the number of pulses per second. It worked just fine up to 60000Hz.

edit: There was a little bit of error that increased with increasing frequency. At 20000Hz the error was about 0.1%. At 60000Hz it was about 0.2%. The crystal in the processor is within 10ppm and the measuring processor was using a GPS to count seconds. I think this error is because of the resolution of the processor cycle (62.5ns). You can't get 60000Hz exactly.

Anyway, it seems something else is amiss... although to be fair, cattledog and I aren't measuring the stability, only the average. The timer 0 interrupt would cause an occasional jitter.

Thanks All ...

I will investigate further and try on an UNO for comparison ...

The drift / jitter was measured on an oscilloscope with persistence mode enabled and while this is over a relatively long period compared to the signal period there are significant deviations from the centre frequency, although a DVM on frequency measurement does visually show something amiss - certainly compared to say 20000Hz.

Will post my findings together with the original scope trace as soon as I can ...

How long does it take before the frequency is off by 500Hz ?
Could your measurement devices be wrong ?
I tried to capture 20kHz and 22kHz from an Arduino Uno with my computer, but the sample rate of 384kHz is not enough (see attached picture). The spectrum analysis didn’t show a difference.

Koepel:
How long does it take before the frequency is off by 500Hz ?

I think you need around 50 pulses so that you get a collision with the timer 0 interrupt, which occurs every 1024us.

I was able to see the jitter with a different sketch on a second processor (I don’t have an oscilloscope yet). I used the input capture register to time the number of cycles between successive pulses and looked for min/max. For short sample periods (like 10 pulses) I didn’t see it but when I sampled long enough (hundreds or even tens of thousands of pulses) I caught all kinds of bad values, many far worse than the 500Hz error reported, even at 20000Hz.

It kind of makes sense. At 20kHz a pulse is 50us long. The timer 0 interrupt routine takes about 5us to run. Worst case collision you’ll get a 55us pulse width followed by a 45us pulse. Those intervals translate into 18.2 and 22.2kHz. I was seeing 18.1 and 22.5kHz glitches.

If I turned off timer 0 interrupts in the processor running the simple tone() sketch by adding the line “TIMSK0 = 0” then it worked perfectly even at 60kHz. And of course using the timer to generate the pulse in hardware was also flawless.

Here’s the measuring sketch, in case anyone wants to play with it.

#define PULSE_SAMPLE_COUNT  10000    // number of samples in each report run

#define TONE_INPUT_PIN    8    // must be ICP1 (pin 8)

void setup()
{
  Serial.begin(115200);
  Serial.println("\nHello.\n");
  
  pinMode(TONE_INPUT_PIN, INPUT);
  
  // set up timer 1 to run at full speed
  TCCR1A = 0;
  TCCR1B = 1;
}

void loop()
{
  uint16_t count;
  uint16_t lastCount;
  uint16_t diff;
  uint16_t maxDiff = 0;
  uint16_t minDiff = 0xFFFF;

  noInterrupts();
  
  // synch the input capture register with the square wave input
  count = ICR1;     
  while (ICR1 == count) {}
  lastCount = ICR1;
  
  for (uint16_t i=0; i<PULSE_SAMPLE_COUNT; i++) {
    while (ICR1 == lastCount) {}    // wait for the next (falling) edge
    count = ICR1;
    diff = count - lastCount;
    lastCount = count;
    if (diff > maxDiff) maxDiff = diff;
    if (diff < minDiff) minDiff = diff;
  }
  
  interrupts();
 
  Serial.print(16000.0/float(minDiff));
  Serial.print(", ");
  Serial.print(16000.0/float(maxDiff));
  Serial.print(" kHz");
  Serial.println();
  Serial.flush();
}

Awesome work jboyton !

Sorry for my delay in responding but verified your own observations that disabling Timer0 removed the frequency jitter !

Please find attached two oscilloscope traces at 22KHz with persistence mode enabled for Timer0 enabled and disabled …

Thanks again all - first post for me and very impressed with the response.