10-bit Component-less Volume Control for Arduino!

Using PWM at an ultrasonic frequency, I've given Arduino 10-bit volume control for Tone() generation using nothing but the speaker.

Similar projects worth following

I've devised a trick to give the standard Arduino Tone() function 1024 smooth volume levels with ZERO extra components by using PWM at an ultrasonic frequency.

This allows for programmatic control of a square wave's volume with nothing but a speaker connected directly to the Arduino!

Normally to simulate an analog voltage with a digital-only pin of a microcontroller you'd use Pulse Width Modulation. This works great for LEDs because your eyes can't the 490 / 976Hz flicker of the standard analogWrite() function. But for audio things are a bit more difficult. Because your ears can easily detect frequencies between 20 - 20,000Hz, any PWM with a frequency in this range is out.

Luckily, the ATmega328p allows you to change the clock prescalers for ultrasonic PWM! We need to use Timer0, because it can drive PWM at a max frequency of 62,500Hz, which even if you cut that in half would still be above your hearing range. (NOTE - this is probably far above the frequency response range of your project speaker anyways.) Now that we have ultrasonic PWM on the Supported Pins, we configure Timer1 to fire an Interrupt Service Routine at a rate of "desired frequency" * 2. (Twice speed for toggling on and off every other round) There's an awesome website where you can generate the code for Timer Interrupts, but my library does this for you in the background.

Finally, inside the Timer1 ISR routine, we incorporate our volume trick. Instead of digitalWrite()'ing the pin HIGH and LOW like the normal Tone() function does, we analogWrite() "HIGH" with our volume value (0 - 255) and analogWrite(0) for "LOW". Because of how fast the PWM is running, the user doesn't hear the 62.5KHz PWM frequency, and instead perceives a 50% percent duty cycle as a speaker driven with only 2.5 volts! While a few volume levels do produce subtle artifacts to the sound, it mostly delivers quality 8-bit volume control to replace the standard Tone() function.

The library makes this trick as easy to write as a normal Tone() function, and the GitHub repo has documentation / ready to use example sketches.

The Volume library for Arduino, with example sketches!

x-zip-compressed - 19.45 kB - 05/29/2016 at 09:21


  • 1 × ATmege328p / based Arduino
  • 1 × Speaker / Piezo

  • Updates - over 768 of them! ;)

    Lixie Labs11/14/2016 at 01:36 0 comments

    Woo! Update time again!

    Last time we talked, I had branched development of Volume into a second library allowing for custom waveforms like sine or sawtooth.

    I've decided to branch again, to provide a third, higher-quality, lower-consumption library. Let's take a look at the specs!


    10-bit Control - 768 more volume levels!

    After adopting the lovely TimerOne library to drive this technique, Volume3 now offers 10-bit volume control, 0 - 1,023.

    Only using Timer1!

    Because of the lovely TimerOne lib, we can now produce our PWM AND the interrupts for driving waves on a single timer.

    No More Awful Prescaler!

    We're now producing the ultrasonic PWM on Timer 1, leaving that nasty Timer 0 to manage delay and millis() like normal with it's default 64x prescaler. This means you can now use the native delay/millis functions with Volume3!

    No More God-Awful Distortion

    As proud as I was of the first version, it had a characteristic "fizzle" when you lowered the volume of certain frequencies. This is virtually gone now, due to raising the PWM carrier frequency from 62,500 Hz to 100,000 Hz. Nice and clean now!


    I'm still going to actively maintain these three as SEPARATE libraries. I'll explain why after this table:

    Volume Library Comparison:

    Library Volume1 Volume2 Volume3
    Accuracy 8-bit 8-bit 10-bit
    Frequency Range (Hz) 120 - 5,000 1 - 3,400 1 - 4,186
    PWM Frequency (Hz) 62,500 62,500 100,000
    Polyphony 1 1 1
    Needs vol.begin() YES YES NO
    Compiled Library Size (Bytes) 2,501 2,542 1,054
    RAM Usage (Bytes) 39 457 24
    Frequency Slide Quality GREAT BAD GOOD
    Timer Usage 0, 1 0, 1 1
    Delay Issue (Timer0 Prescaler) YES YES NO
    Square Wave YES YES YES
    Sawtooth Wave NO YES NO
    Triangle Wave NO YES NO
    Sine Wave NO YES NO

    As you can see, each library has it's strengths and weaknesses. Volume1 is best for range and slides, Volume2 is best for custom waveforms, and Volume3 is best for accuracy, size, and ease of use. Speaking of ease of use, here's a comparison of Volume1 and 3:


    #include "Volume.h"
    Volume vol;
    uint16_t frequency = 440;
    void setup() {
    void loop() {
      for(byte volume = 0; volume < 255; volume++){
      for(byte volume = 255; volume > 0; volume--){


    #include "Volume3.h"
    uint16_t frequency = 440;
    void setup() {}
    void loop() {
      for(int volume = 0; volume < 1023; volume++){
      for(int volume = 1023; volume > 0; volume--){

    Several improvements have been made here:

    • No more Volume class definition needed
    • No more vol.begin() init needed
    • No more special delay/millis functions
    • Ability to use either Timer1 pin at will, no more vol.alternatePin()
    • 10-bit accuracy, adding 768 new volume levels!

    I'm still working on catching up Volume3 to include original functions like a master volume control and one-liner fades, but for now I'm sharing in excitement to see how people use the new library!


    I promise I'm not making a Volume4. ;)

    - Connor

  • More waveforms? Sine me up!

    Lixie Labs09/02/2016 at 18:31 0 comments

    I've branched development of the Volume library to a second version, which now supports multiple waveform types such as sine, sawtooth, and more!


    This new Volume2 Library is not a successor, but a separate project I've decided to library-ize to see if people like it. Just like the Volume1 lib, it provides 8-bit volume control with no extra components. The kicker here is that now you can choose from the following waveform shapes:

    SQUARE | Your friendly neighborhood square wave

    PWM_12 | PWM "square" at 12.5% duty cycle

    PWM_25 | PWM "square" at 25%. The NES used these PWM voices!

    SAWTOOTH / SAWTOOTH_HIGH | Sawtooth wave, low and high quality

    TRIANGLE / TRIANGLE_HIGH | Triangle wave, low and high quality

    SINE / SINE_HIGH | Sine wave, low and high quality

    CUSTOM | Custom 32-byte array defined by the user for custom voices

    NOISE | Ignores frequency, returns fast volume-controlled RNG

    The CUSTOM waveform is an exciting option. You can use this to define you own complex waveshapes to produce sound similar to things like pianos, guitars, cellos, and more. The array given must be 32 8-bit signed values. For example, this is an array for generating several square wave octaves:

    byte wave[32] = {

    With this array, setting the frequency to 220 Hz will generate 220, 440, 880, and 1760 Hz simultaneously!


    While the new library is great at generating waves, it's max frequencies are lower and it doesn't handle fades well yet. For now, I'm keeping the two separate to allow people to choose what's best for their own needs until I can optimize each further for a smooth merge!


    As always, I've provided a ton of documentation on the GitHub, and examples can be found as well. But the easiest way to try the Volume and Volume2 libraries is to grab them through the Arduino Library Manager!

    - Connor

  • People were ready for this!

    Lixie Labs06/14/2016 at 02:41 0 comments

    I developed this library for my own use in a fake "cricketuino" project.

    Now thousands of people have tried it, broke it, forked it, and it even caught the eye of Atmel themselves after being posted on the main HaD blog. (Go follow / like Atmel on here by the way, we owe this awesome hardware platform to them!)

    In return for all the love the lib has gotten this week, I've picked development back up, and expanded much-needed compatibility to the ATmega168 / 328 / 1280 / 2560 / 16u2 / 32u4! Because each of these boards ties Timer0 to different pins, the most recent release of the Volume lib no longer allows direct assignment of speaker pins.

    Volume vol(speakerPin);
    is now just:
    Volume vol;

    With this change, a DEFAULT_PIN for each board is automatically defined, and you have the option to change it to the ALTERNATE_PIN Timer0 is also tied to for that board using:


    Supported Pins:

    (Uno) ATmega168/328(pb) 5 6 YES
    (Mega) ATmega1280/2560 4 13 YES
    (Leo/Micro) ATmega16u2/32u4 9 10 YES*

    *I recently killed my only ATmega32u4 board while stripping it for low-power usage and don't have one to test current releases of the library. If anyone who has a working one wants to report compatibility back to me, please do so as I've only tested the initial release!

    Thank you all so much for the awesome support, let's work together to bring beautiful tones to more Arduinos! I'm currently working on the highly-demanded ATtiny support, more on that soon!

    - Connor Nishijima

View all 3 project logs

  • 1
    Step 1

    To use the Volume Control library, there are a few prerequisites:

  • 2
    Step 2

    With all of these things ready, follow this tutorial to install the .zip library where the Arduino IDE can see it.

  • 3
    Step 3

    The most basic example to get Volume control working is as follows:

    #include "Volume.h" // Include the Volume library
    Volume vol(6); // Plug your speaker into Pin 5 or 6
    void setup() {
    void loop() {
      // vol.delay() replaces the standard delay function (Timer0's prescaler has been altered, throwing the default delay() 64 x faster!)
      vol.tone(1000, 255); // 100% volume
      vol.tone(1000, 192); // 75% volume
      vol.tone(1000, 127); // 50% volume
      vol.tone(1000, 51); // 20% volume
      vol.tone(1000, 12); // 5% volume

View all 5 instructions

Enjoy this project?



Ben wrote 09/15/2020 at 16:00 point

Can and how would this be used to dim LEDs?

  Are you sure? yes | no

wrote 04/23/2018 at 23:08 point

This (volume_test_sounds.ino) works straight off, on a Pro Micro 5V, ATmega32U4 (Leonardo). Pin set to 3 (physical 6) with a piezo speaker. With some distortion. Thank You!

  Are you sure? yes | no

Simon Merrett wrote 07/24/2017 at 19:13 point

@Connor Nishijima, by Polyphony = 1, does that mean we can't have stereo/two pins producing tones? 

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates