Tone() not producing correct frequency

Hello, I am using the Tone() function to create a frequency at 50% duty cycle so it can be read by a campbell scientific datalogger. If I output a tone at 1500Hz, the serial monitor says 1500Hz, but the datalogger reads a frequency of 1485. Similar, I tell it to output at 831, and it outputs as 826Hz. I have swapped arduino Uno's and reloaded the same program with the same results.

I am transferring data from a zigbee to this arduino, but it reads the data fine, it just outputs using tone incorrectly. Below is my code if it might help? The section I output tone at is a little more than halfway down at the
//////////////////////
// Voltage Output //
//////////////////////
part.

Thank you

const int VSensors = 0; // # of voltage 10:1 sensors you will be sending wirelessly for the project. 
const int ISensors = 2; // # of ACS711 current sensors you will be sending wirelessly for the project. 
const int outputType = 2; // (0,1) 0 = analog voltage output, 1 = PWM output



///////////////
// Libraries //
///////////////
#include <SoftwareSerial.h>     // Includes the library softwareSerial which should automatically be included in Arduino 1.6.9 and up
//#include "pitches.h"            // Used to test for the command tone() which outputs 50% duty cycle PWM to be detected by CS dataloggers
#define VERSION "1.02"          // No idea what this does, but it works



const byte XB_RX = 2;           // XBee's RX (Din) pin
const byte XB_TX = 3;           // XBee's TX (Dout) pin
const int XBEE_BAUD = 9600;     // Your XBee's baud (9600 is default)
const int SERIAL_BAUD = 4800;   // Your XBee's baud (9600 is default)
SoftwareSerial xB(XB_RX, XB_TX);// Designates pins on arduino board to be used for communication

///////////////////////////
// Variable Declarations //
///////////////////////////
const int tot_sensors = VSensors + ISensors;  // The total number of sensors being measured by board
float V[tot_sensors];               // Input voltage for sensor [i]
float V_out[tot_sensors];           // Used to output voltage at arduino board for sensor[i]
int MSB[tot_sensors];               // Voltage sensors MSB
int LSB[tot_sensors];               // Voltage sensors LSB
int MSB_I;                      // Current sensor MSB
int LSB_I;                      // Current sensor LSB
float current[tot_sensors];                  // Calculated to print current to monitor
float V_in[tot_sensors];             // The combination of the MSB and LSB for current
//float current_out;              // Calculated to output voltage at arduino board
int debugLED = 13;              // Used to detect if information is being sent to Arduino board (blinks when received)
//int outPin[tot_sensors];            // Creates an array of output pins the size of the # of sensors being used
int freq;                       // Frequency to output a 50% duty cycle to mimic a 0-5V output
int outPin[] = {3, 5, 6, 9, 10};  // Pins 3,5,6,9, and 10 can output PWM to be detected by logger




///////////
// Setup //
///////////
void setup() {

  Serial.begin(SERIAL_BAUD);    // Sets the baud rate for communication via terminal monitor on computer
  xB.begin(XBEE_BAUD);          // Sets the baud rate for communication with the Transmitting Xbee radio
  Serial.print(VSensors); Serial.println(" # of Vsensors");
  Serial.print(ISensors); Serial.println(" # of Isensors");
  Serial.print(tot_sensors); Serial.println(" # of sensors");
}





//////////
// Loop //
//////////
void loop() {


  // Diagnostics testing, uncomment below to test communications and see all packets being delivered
  // Comment out everything below this diagnostics though using a /* at the beginning and */ at the end
  while (!xB.available()) {

  }



  /**/
  // make sure everything we need is in the buffer packet
  if (xB.available() >= 21) {                 // If available # of bytes is greater than 21 proceed
    //while (xB.available()) {
    if (xB.read() == 0x7E) {                  // Start byte always = 126 or 7E in Hex
      if (xB.read() == 0x00) {                // First bit of packet length, typically 0
        //if (xB.read() == (14 + (tot_sensors * 2))) {// Second bit of packet length, in this case it is # of inputs * 2 + 14 preceeding bits
        if (xB.read() == (15)) {
          if (xB.read() == 0x90) {            // 144(90 hex) seems to be the golden value here while 146 is an escaping character and should be avoided

            // blink debug LED to indicate when data is received
            digitalWrite(debugLED, HIGH);
            delay(10);
            digitalWrite(debugLED, LOW);

            // read the variables that we're not using out of the buffer and discard
            for (int i = 0; i < 11; i++) {    // 11 means there are 11 variables between the "144" above and our "Data" bytes
              byte discard = xB.read();       // Discards bytes
            }

            byte i = xB.read();


            if (i < VSensors) {
              readVoltage(i);
            }
            else {
              readCurrent(i);
            }


            ////////////////////
            // Voltage Output //
            ////////////////////
            switch (outputType) {

              case 1: // Analog voltage output on outPin{i}
                Serial.println("Case 1 has been entered");
                analogWrite(outPin[i], V_out[i]); // Ouputs sensor voltage(i) to pin (i+5)
                break;

              case 2: // PWM output on outPin{i}
                Serial.println("Case 2 has been entered");
                // Tone can only be between 31 and 4978 hz
                freq = (i * 500 + 500) + 500 * (V[i] / 5);  // 1st output starts at 500hz, 2nd @ 1000hz etc.
                tone(3, freq);
                Serial.print("V["); Serial.print(i); Serial.print("] is "); Serial.println(V[i]);
                Serial.print("Frequency is "); Serial.println(freq);
                Serial.print("i =  "); Serial.println(i);
                Serial.print("Output pin is "); Serial.println(outPin[i]);
                break;

              default: // outputType has not been declared
                Serial.print("outputType has not been declared");
                break;
            }
            Serial.println("");
          }
        }
      }
    }
  }
}


//////////////////
// Read Voltage //
//////////////////
void readVoltage(int i) {
  // Begin to read the 2 part bytes sent from the other radio
  //for (int i = 0; i < VSensors; i++) { // Begins loop the size of # of input sensors 1-5

  MSB[i] = xB.read();               // Reads the MSB of input[i]
  LSB[i] = xB.read();               // Reads the LSB of input[i]
  V[i] = LSB[i] + (MSB[i] * 256);   // Converts multiple 0-256 values of MSB&LSB to a single 0-1024 value
  V_out[i] = V[i] / 4;              // Converts single 0-1024 value (V[i]) to single 0-256 value for voltage output
  V[i] = map(V[i], 0, 1023, 0, 500);// Maps the 0-1024 to 0-500 for serial printing in terms of voltage display
  V[i] = V[i] / 100;                // Arduino does not like changing from an int to a float in the same line??? 500V now equals 5V
  Serial.print("Input_"); Serial.print(i); Serial.print(" = "); Serial.print(V[i]); Serial.println(" Volts ");
}


//////////////////
// Read Current //
//////////////////
void readCurrent(int i) {
  MSB[i] = xB.read();    // Reads the MSB of the current input
  LSB[i] = xB.read();    // Reads the LSB of the current input
  V[i] = LSB[i] + (MSB[i] * 256);
  V_out[i] = V[i] / 4;              // Converts single 0-1024 value (V[i]) to single 0-256 value for voltage output
  V[i] = map(V[i], 0, 1023, 0, 500);
  V[i] /= 100;
  current[i] = 73.3 * (V[i] + 2.5) / 5 - 36.65;
  Serial.print("V at input "); Serial.print(i); Serial.print(" = "); Serial.println(V[i]);
  Serial.print("Current at input "); Serial.print(i); Serial.print(" = "); Serial.println(current[i]);
  Serial.print("Bits out at pin "); Serial.print(outPin[i]); Serial.print(" = "); Serial.println(V_out[i]);


  //current_out = current * 100; // Multiply by 100 to get rid of decimal places
  //current_out = map(current_out, 0 , 500, 0 , 255);   // Map the 0-5 V to 0-255 for voltage output on arduino pins
  //analogWrite(current_pin, current_out);    // Outputs voltage corresponding to input current on other board (0.45A = 0.45V)

  Serial.println(" ");  // Creates new line for serial debugging purposes


}

//////////
// END! //
//////////

The 16MHz resonator isn't exactly 16MHz. Change the temperature on the board and see if the observed frequency value doesn't change a little.

Yes, I did notice a little change, but not enough to make this project viable for my purposes. I also noticed this in the fine print.

• Uno: pins 5 and 6 always support PWM. Pins 3 and 11 support PWM if you do not use the tone library. Pins 9 and 10 support PWM if you do not use the servo library.

Thank you

I also noticed this in the fine print. .....

Which as nothing to do with your problem.

Even if the oscillator driving the Uno was spot on, it can only produce tones by integer division of 16MHz, so tones will never be spot on from a musical perspective. Unless you pick a precise frequency, one that lends itself to producing the frequencies on a chromatic scale this is always going to happen.

@GM

If I'm making 1500hz from 16MHz I expect to be able to get within around 100ppm or so at least right? Or am I doing my calculation wrong.

There may be some other limitations/errors/rounding related to the math or the hardware.

Welcome to the real world where "nothing is perfect". :wink:

Is your "datalogger" calibrated?

If it's stable you can do a calibration (correction). A straight-line calibration usually involves an offset adjustment (a value added or subtracted) and a gain/slope adjustment (a value multiplied).

Typically the offset would be applied at zero, the gain is applied to the maximum (but there is no zero Hz.) But, that doesn't necessarily give you the best-overall calibration. And, you can also make finer adjustments. Where I work we calibrate a DAC at 10 points along it's range.

The [u]map() function[/u] will also give you a straight-line calibration. You need to add 5Hz at 831 and 15Hz at 1500. If that's your full range...

map(X, 831, 1500, 836, 1515);

Hi there,

Ya the dataloggers are calibrated. At least they better be as they are about $5,000 each. I have double checked with my multimeter to corroborate the datalogger output.

I have since performed a linear calibration. I output hundreds of frequencies, then recorded the inputs and created a linear regression. I then inserted that regression curve into the program to fix it.

Hopefully this will be stable for the future. We will see when the temperatures rise above 105 here in New Mexico if the frequencies change dramatically.

Delta_G:
@GM

If I'm making 1500hz from 16MHz I expect to be able to get within around 100ppm or so at least right? Or am I doing my calculation wrong.

The thing is you have to do it at the divider ratios of the timers, so that is a prescaller that rises in powers of two and an 8 bit divider after that.

It is the same sort of thing that makes a baud rate generator inaccurate if you are not using a special frequency crystal.

Grumpy_Mike:
The thing is you have to do it at the divider ratios of the timers, so that is a prescaller that rises in powers of two and an 8 bit divider after that.

It is the same sort of thing that makes a baud rate generator inaccurate if you are not using a special frequency crystal.

Gotcha. Wasn't thinking about the prescalers.

Delta_G:
If I'm making 1500hz from 16MHz I expect to be able to get within around 100ppm or so at least right?

If you were to use a 16 bit timer, you'd ideally want it to interrupt every 5333.3333 clock cycles (which is 3000 times per seconds, since tone needs to create both edges). That is indeed ~62 ppm error from round off.

However, tone() uses an 8 bit timer on Arduino Uno. So the 16 MHz clock gets prescaled to 500 kHz to accomodate the limited 8 bit resolution. You'd ideally need 166.667 of those 2 us clock periods. If you round to 167 of those clocks, it's ~2000 ppm error. If you round down to 166, then ~4000 ppm error.

But none of this explains the ~10000 ppm error you're actually seeing....

As a quick sanity check, I ran this just now on a genuine Arduino Uno R3

void setup() {
  tone(2, 1500);
}

void loop() {
}

My Fluke 87 handheld multimeter measures 1504.3 Hz.

My (very old) BK Precision 1823A frequency counter measures 1504.375 Hz.

My Agilent 34410A bench multimeter measures 1504.399 Hz.

4.38 Hz off from 1500 Hz is 2920 ppm error. My guess is the tone() function is rounding off as expected, plus the ceramic resonator is contributing ~1000 ppm error. But this quick test with measurements from 3 different instruments is about how much investigation as I'm going to do today. Not going to dig into how much the resonator really is contributing, or whether tone() is rounding off as well as it could/should.

I have no idea why you're seeing 15 Hz error. Maybe try running this simple program, rather than a big complex one? If you still get 15 Hz error, maybe it's time to replace that Uno. The one I used for testing here is absolutely a genuine Uno R3 from Arduino, not a cheap clone.

I tried running on some of the other boards sitting on desk. Used only the Fluke 87.

Arduino Zero: 1500.1 Hz
Arduino Due (no tone function)
Arduino 101: 1501.3 Hz
Arduino Leonardo: 1500.0 Hz
Teensy 2.0: 1499.2 Hz
Teensy 3.2: 1499.9 Hz
ChipKit Uno32: 1488.0 Hz
Wemos D1 ESP8266: 1499.9 Hz (signal actually output at different pin)
HiFive1 RISC-V: (no tone function)

stevenette:
Yes, I did notice a little change, but not enough to make this project viable for my purposes. I also noticed this in the fine print.

• Uno: pins 5 and 6 always support PWM. Pins 3 and 11 support PWM if you do not use the tone library. Pins 9 and 10 support PWM if you do not use the servo library.

Thank you

I think you need an Arduino board with a quartz 16MHz crystal, not a ceramic resonator (they used
to be like this, then someone made a very poor decision about saving 20cents)