tone() through PWM pin, voltage control

I am trying to adjust the volume of a piezo speaker, via a potentiometer. I have researched a fair bit, and I have read that the volume can be changed by changing the voltage going through it. And to do this, I have read, you can alter the duty cycle of a PWM pin.
I am making noises through the piezo via the tone() function, and have encountered a few problems...

I have all of the materials provided in the Arduino Starter Kit: Arduino Starter Kit Multi-language — Arduino Official Store

Here is the code relating to the noise making:

int piezoPin = 9;
int volumePin = A0;
float volume = 0;
float voltage = 0;

void setup()

  {
    Serial.begin(9600);
    
    pinMode(songChangePin,INPUT_PULLUP);
    pinMode(piezoPin,OUTPUT);
  }

void loop()

  {
    for (i = 0; i < notes[song]; i ++)
    
      {
        buttonCheck();
        volumeCheck();
        
        tone(piezoPin,note[song][i]*2,240000*fracNote[song][i]/tempo[song]*duration[song][i]);
        delay(240000*fracNote[song][i]/tempo[song]);
      }
  }

void volumeCheck()

  {
    volume = analogRead(volumePin);
    volume = map(volume,484,1023,0,5);
    Serial.print("Volume - ");
    Serial.print(volume);
    Serial.print(" , ");
    digitalWrite(piezoPin,volume);
  }

Sorry I don't have a photo of the circuit, but I've lost the USB cable for my camera...

The piezo is connected to the ~9 pin (~ = PWM), the potentiometer 3rd leg is connected to A0, both have resistors in between.

The voltage readings in the serial window range from 0-5, however, the actual volume of the poezo doesn't seem to change.

I think the problem is that the tone function ignores anything to do with the set duty cycle of the pin, and chucks out whatever voltage it needs. Could be completely wrong, though. If that is the case, how can I combat this.

I have looked for alternatives to the tone() function, and have found this: http://michael.thegrebs.com/2009/03/23/playing-a-tone-through-an-arduino-connected-piezo/
Would this have the same problems, though?

Thanks for any help. If I've left any required information out, please let me know.

You need extra external hardware to convert the pwm duty cycle to a voltage. PWM only gives 5v or 0v and never anything else.

Mark

Oh, okay, so what could I use to alter the volume then? Previously, I had a pushbutton in series with the piezo, which had a resistor running in parallel with it, which I think acted as a voltage divider, but when you pressed the button, it had an unhindered path to the piezo, effectively working as a volume controller. However, that didn't allow for toggling, or for more than two volume states.

Also, I would prefer to keep the circuit relatively simple, without lots of connections going everywhere, so preferably not a resistor ladder, as I saw in some other methods, but the programming can be more complex - I need to get familiar with this language anyway.

Have you tried Tim's toneAC library? It allows you to change the volume from 1-10.
http://forum.arduino.cc/index.php?topic=142097.0

I will have a look at that library, I havent used a library before. I am also looking at a low-pass filter with a resistor and a capacitor - is that what I might be looking for?

A low pass filter will only block the high frequencies, it will slightly reduce the volume but won't give you control over it.
With the toneAC library and additional programing, you could use a pot to control the volume.

I've started to look at different digital to analog chips after seeing one in a Youtube Arduino SPI tutorial by Jeremy Blum. He uses the AD5402 which runs $4 and change but has 4 channels. There's another at Futurlec that's 16 bit 2 channel for about $3. But the ones I see for sale are all surface mount though the AD5402 is supposed to come in through hole DIP.
I've seen true sine wave oscillator circuits where one of those would let an MCU control the sound. One uses a dual op-amp that I happen to have and a few other bits. But that's as far as I've gotten.

I have had good luck with Tim's ToneAC libraries as well. The volume control does work by varying the PWM duty cycle, and it is a square wave that's always either at full voltage or 0. But, the speaker or piezo tends to average it out somewhat. If you want to add the capacitor, it would average it out even more. The duty cycle volume control does work, but it's not linear. Tim posted about trying to do his own linear mapping, but I don't know if it made it into the code or not.

If you want to get fancy, you can do Direct Digital Synthesis with a Chebyshev filter but I've found that to be a bit of overkill for the quality I do with my Arduino projects. I've tried audio amp ICs and they work pretty well, but just a simple transistor seemed to do almost as much for me, and let me drive a decent sized speaker.

I say wire it up and see how it sounds for you.

I have now implemented the toneAC library, and the volume control works pretty well. Unfortunately, the sounds only sound clear at volume 10, below that, they become distorted, more fuzzy... Also, for some reason I have had to double the tempo, and the note durations are now off, but that's not really an issue, I can play around with that. So here's the relevant code now:

volume = analogRead(volumePin);
volume = map(volume,484,1023,0,10);

toneAC(note[song][i]*2,volume,240000*fracNote[song][i]/tempo[song]*duration[song][i]);
delay(240000*fracNote[song][i]/tempo[song]);

What is the reason for the reduction in the clarity of the sounds, and how can I fix this?

Thanks for your help.

Could you post your whole code?

Pharrahnox:
I have now implemented the toneAC library, and the volume control works pretty well. Unfortunately, the sounds only sound clear at volume 10, below that, they become distorted, more fuzzy... Also, for some reason I have had to double the tempo, and the note durations are now off, but that's not really an issue, I can play around with that. So here's the relevant code now:

volume = analogRead(volumePin);

volume = map(volume,484,1023,0,10);

toneAC(note[song][i]2,volume,240000fracNote[song][i]/tempo[song]duration[song][i]);
delay(240000
fracNote[song][i]/tempo[song]);




What is the reason for the reduction in the clarity of the sounds, and how can I fix this?

Thanks for your help.

I have to wonder at the word size of the variables involved and how long some of those calculations must take.

And then there's that ugly delay() holding up processing when pre-calculation for the next note could be done instead.

Integer multiply and divide are faster than floating point but are by no means just a couple microseconds even for int, but calculations involving 240000 don't get performed on ints!

My whole code is:

#include <toneAC.h>

float note[3][28] =  {
                   {123.5,185.0,174.6,123.5,196.0,185.0,123.5,185.0,174.6,123.5,164.8,146.8,138.6,110.0},
                   {82.4,82.4,164.8,82.4,82.4,146.8,82.4,82.4,130.8,82.4,82.4,116.54,82.4,82.4,123.47,130.8,82.4,82.4,164.8,82.4,82.4,146.8,82.4,82.4,130.8,82.4,82.4,116.54},
                   {349.2,415.3,349.2,349.2,466.16,349.2,311.1,349.2,523.3,349.2,349.2,554.4,523.3,415.3,349.2,523.3,698.5,349.2,311.1,311.1,261.6,392,349.2,311.1,261.6,233.1,207.65}
                  };

float fracNote[3][28] =  {
                      {0.125,0.125,0.125,0.125,0.25,0.25,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125},
                      {0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.625},
                      {0.25,0.1875,0.125,0.0625,0.125,0.125,0.125,0.25,0.1875,0.125,0.0625,0.125,0.125,0.125,0.125,0.125,0.125,0.0625,0.125,0.0625,0.125,0.125,0.6875,0.0625,0.125,0.125,0.125}
                    };

float duration[3][28] =  {
                         {1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0},
                         {0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,1.0},
                         {0.75,0.75,0.75,0.75,0.75,0.75,0.75,0.75,0.75,0.75,0.75,0.75,0.75,0.75,0.75,0.75,0.75,0.75,0.75,0.75,0.75,0.75,0.75,0.75,0.75,0.75,0.75}
                       };

int notes[] = {14,28,27};
int tempo[] = {84,220,120};
char* name[] = {"Refuelling Base","The Hangar","Axel F."};
int song = 0;
int songMax = 2;
int songChangePin = 4;
int songChangeTimer = 1000;
int buttonCurrent = 0;
int volumePin = A0;
float volume = 0;
int i = 0;

void setup()

  {
    Serial.begin(9600);
    
    pinMode(songChangePin,INPUT_PULLUP);
    
    for (i = 0; i <= songMax; i ++)
    
      {
          tempo[i] *= 2; //here's the part where I've had to double the speed
      }
  }

void loop()

  {
    for (i = 0; i < notes[song]; i ++)
    
      {
        buttonCheck();
        volumeCheck();
        
        toneAC(note[song][i]*2,volume,240000*fracNote[song][i]/tempo[song]*duration[song][i]);
        delay(240000*fracNote[song][i]/tempo[song]);
      }
  }

void volumeCheck()

  {
    volume = analogRead(volumePin);
    volume = map(volume,484,1023,0,10);
    Serial.print("Volume - ");
    Serial.print(volume);
  }

void buttonCheck()

  {
    buttonCurrent = digitalRead(songChangePin);
    Serial.print(buttonCurrent);
    Serial.print(" - ");
    Serial.println(name[song]);
    if (buttonCurrent == LOW)
        
      {
        song += 1;
        i = 0;
            
        if (song > songMax)
            
          {
            song = 0;
          }
            
        delay(songChangeTimer);
      }
  }

I could have it so that it precalculates 240000/tempo[] in the buttonCheck() function. What should I do about the delay then?

About getting rid of delay, start here:

It will free up many of those wasted cycles, 16,000 cycles per millisecond! But beware, you will have to approach coding differently to the top-down method. When time is a factor, you have to keep loop() running fast by spreading the tasks over many runs through loop().

Also you probably want to keep your tables in flash memory.

Okay, I had a quick look at the link, and I will attempt to implement it if I have time tomorrow. I will also research about the unsigned long data type, because that might be what I need for the calculations. Also, I will research about flash memory, so that I, so I can see how to do that, and why it is a good idea to keep the tables (arrays?) in it.

Pharrahnox:
Okay, I had a quick look at the link, and I will attempt to implement it if I have time tomorrow.

The subject is deserving of a full study. It is more than just checking a difference in times ( now - start >= interval ). For code to be highly responsive (always "on time"), loop() needs to be always running as fast or faster than response needs to be.

I will also research about the unsigned long data type, because that might be what I need for the calculations.

Type long, signed or unsigned is good for 9 decimal digits (that all can be 0 to 9) accuracy. 240000 is 6 places, if you multiply by 1000 it will be too much. They are slower than ints but that is the price for decimal places.
If long is not enough, Arduino can do 64 bit type long long to give 19 decimal digits. But again there is a bigger price in speed.

Also, I will research about flash memory, so that I, so I can see how to do that, and why it is a good idea to keep the tables (arrays?) in it.

Because a PROGMEM table takes no space in RAM. UNO has only 2k RAM, MEGA2560 only 8k but the Due with such different hardware has 96k.

Possibly you could pre-calculate all the values for each song then fetch them from flash memory (PROGMEM) if they are small, or a storage device like an SD card if there is too much to fit in flash, and plug those values straight into the toneAC() function. The calculations would be done on a PC for speed and accuracy then the Arduino would only need to play them back.

Whether you would need SD or not depends on how much data in all your songs and what Arduino or AVR or chip you will be using. With luck and choices you won't need SD at all!

You can make your own SD adapter pretty cheap. There is at least one way shown on the Instructables site and other ways shown elsewhere. The first I saw had wires soldered to a micro-SD adapter to read micro-SD cards. They all run on about 3.3V but the parts to handle the 5V to 3.3V change are not so many and all cheap.
Or you can buy an SD module for a few Euros on eBay and wire it direct, at least my LC modules work that way.

Somewhere along though, be aware that there are music chips (DSP = digital signal processor) that can play MP3 or MIDI and do come on breakout boards you can hook up to a controller or some even run by themselves. But a BIG caveat... check out threads here and blogs before buying, some are a real pain to get working either at all or with different SD cards for those that have built-in SD readers.