Time to complete: 10–15min; Level of difficulty: Beginner
In this activity we’ll generate different tones with a microcontroller and control their volume. We’ll also learn about some Arduino IDE functions, particularly those used to handle analog values.
List of Materials
- 1 x Arduino Uno R3
- 1 x USB-A to USB-B Male / Male Cable
- 1 x 1KOhm Potentiometer
- 1 x 10KOhm Potentiometer
- 1 x Resistor 1/4W Through Hole (1KOhm)
- 1 x Jumper Wires Male/Male - 10-pack
- 1 x Mounting Plate for Arduino Uno
- 1 x Half-Sized Solderless Breadboard
Obtaining the Code
For this activity, we'll be loading a program to our Arduino Uno board, it's available in our Github repository:
We can open a new window in the Arduino environment and copy-paste the code.
Digital Signals: Frequency vs. Duty Cycle (PWM)
In the context of embedded electronics, digital signals are electrical signals comprised by changes in voltage between two or more discrete values. These changes can occur either as a function of time, most commonly as a train of pulses. They are often used to transmit data either by representing a bitstream (e.g., serial communication), encoding analog values (e.g., Pulse-Code Modulation), and can even be used for digital modulation of an analog signal.
When digital signals behave as a pulse train, they're represented as a particular function of time called rectangular waves. Thus, they are characterized by a duty cycle and a frequency. Within a single period of the signal, the duty cycle is defined as the ratio between the time the signal has a high value over the total time duration of the period. Similarly, the signal's frequency is defined as one over the total time duration of the period.
By changing these two parameters, we will modify both the pitch (controlled by frequency) and volume (controlled by duty cycle) of the tones played through the speaker.
Generating Sound: Transducers vs. Buzzers
In order to turn electrical signals into sound we'll make use of a transducer. In a general sense, a transducer is defined as any device that converts variations of any physical quantity into an electrical signal or viceversa. The particular transducer we've chosen is a little Piezo Transducer (sometimes called Piezo Diaphragm), which contains a disk made out of piezoceramic, a material that exhibits an inverse piezo electric effect—turning electrical signals into mechanical pressure.
When we apply a voltage to a Piezo Transducer, the piezoceramic disk moves and changes the pressure of air molecules around it, effectively generating a sound. This sound is audible (to our ears) and resembles a "click". By generating a digital signal with a microcontroller connected to the Piezeo Transducer in the audible frequencies, 20Hz to 20KHz, we can make it generate tones with different pitches.
Piezo Transducers are often confused with Piezo Sounders or Piezo Buzzers (the terms are used interchangeably!). Although the inverse piezo electric effect is similar in the two components, Piezo Sounders and Buzzers contain built-in oscillator circuitry to produce a single-frequency sound when a voltage is applied (e.g., the beeping sound when a truck is backing up).
Variable Resitors and Potentiometers
A potentiometer is simply a variable resistor that, unlike a regular resistor, has three terminals (pins). The resistance between the two pins at each end is fixed, whereas the resistance between the middle pin and either of the other two varies. As shown by the digram below, the change in resistance affects the path through which the current travels when a voltage is applied to 2 of the pins.
Similar to a regular resistor, potentiometers have a fixed resistance value (in this case 1KOhm). If we measure the resistance of this device by connecting a multimeter to the two pins furthest apart, we can read this reasistance value maximum value every time.
So what's the use of the middle pin of a potentiometer?
The middle pin of a potentiometer is internally connected to a little wiper. The wiper is also connected to the knob (or sometimes a screw depending on the potentiometer model) on top of the potentiometer. As we turn the knob, the wiper changes the point of contact with the resistive material inside. This change in the wiper's position physically alters the amount of resistive material between the middle pin and each of the other two pins, one becomes smaller while the other becomes larger. If we continue moving the wiper all the way to the end, the middle pin will be internally connected to one of the two other pins, and the amount of resistive material between those two pins and the third one will effectively be 1KOhm.
We will be using the varying resistance of the potentiometer to adjust the Voltage read by the microcontroller using a Voltage Divider circuit. We'll look at Voltage Dividers in more detail in Arduino Activity 8. Photoresistors.
Wiring: Analog Input and Volume
After learning a about transducers, buzzers, and potentiometers we proceed to connect the first circuit. As always, it's important to disconnect the Arduino board from any power source including the USB port from our computer. This allows us to catch any wiring mistakes before turning it on, preventing components from being damaged.
For this circuit we will need a Piezoelectric Transducer, a 1KOhm Trimpot (potentiometer), and 1 resistor (330Ohm). We follow the diagram below to wire up the circuit:
Please note that the transducer is sensitive to polarity, so we must connect the terminal marked with the positive (+) symbol to pin 11 (either directly or using the breadboard as shown).
Code Walkthrough: Analog Input and Volume
After ensuring that our connections are correct, we can plug in the USB cable to both the Arduino and the computer. After uploading the Analog Input and Volume program, we'll notice that by moving the knob we can increase or decrease the volume of a fixed-frequency tone. Let's have a look over the code used to do this:
setup() function we need only initialize the communication over serial, by calling the method
begin() from the available
Serial object. If you find the terminology confusing, have a look at our Classes and Object-Oriented Programming tutorial. We'll be using the
Serial object later in the code to communicate data read by the microcontroller on the Arduino board.
// initialize serial communications at 9600 bps: Serial.begin(9600);
Moving to our
loop() function, we start by using the
analogRead() function to read the voltage at the pin (A0), that is, where the potentiometer's middle pin is connected.
// read the analog in value: sensor_value = analogRead(analog_in_pin);
sensor_value stores the value returned by the
analogRead() function, which is in the range 0 to 1023. When the voltage at the pin is 0V, the value read is 0. When the voltage at the pin is 5V, which is the maximum voltage that a pin on an Arduino Uno board can read, then the value is 1023. This is because the reading takes place through a 10-bit Analog-To-Digital Converter (ADC). We want to use this input to control the volume of the speaker by adjusting the duty cycle of the PWM output signal.
As seen in the video below, under a constant frequency, increasing the duration of Tmax results in a higher average voltage for the entire signal. A higher voltage means that the piezo is driven with more (electric) power, which is why it is able to generate a louder sound.
As we've seen in previous tutorials, we can change the PWM signal's duty cycle with the
analogWrite() function. Using a value from 0 to 255 we can adjust it from 0 (always OFF or 0% duty cycle) to 255 (always ON or 100% duty cycle). Because
sensor_value ranges between 0 to 1023, we need to scale down first. Moreover, we wouldn't want 100% duty cycle because an always ON signal would not generate any vibration of the piezo tranducer. In fact, we don't gain anything by having the duty cycle above 50% as (in theory) both the OFF and ON periods of the signal are important for vibrating the piezo transducer effectively. This is a good rule of thumb, but in practice there are different factors involved (see the Code Tinkering section) that'll affect the vibration.
Instead of doing the calculations by hand, we make use of the available
map() function. It requires five arguments to specify the scaling we want done:
map(variable, current_min, current_max, new_min, new_max). Using those values, it performs the scaling of
variable by way of interpolation from the range
current_max to the range
new_max. Looking at our code we see the following:
// map it to the range of the analog out: volume = map(sensor_value, 0, 1023, 0, 127);
This means that when
sensor_value is 0,
volume should be 0, and when
sensor_value is 1023,
volume should be 127. We then use
volume as the value that we want to write with the
// change the analog out value: analogWrite(analog_out_pin, volume);
This is why the volume of the speaker gets louder and softer as we turn the knob. The Arduino reads the voltage coming from the potentiometer, converts it to a number from 0 to 1023. Then it maps this value to a value from 0 to 127, and assigns the value as the duty cycle of the PWM signal on the output pin.
If we open the Serial Monitor by clicking on the square button with a magnifying glass (top-right of the window) see the values being sent from the microcontroller to the computer over USB. We use the
println() methods of the
Serial object for this purpose:
// print the results to the serial monitor: Serial.print("sensor = " ); Serial.print(sensor_value); Serial.print("\t output = "); Serial.println(frequency);
The output of the Serial Monitor should look like this:
Code Walkthrough: Analog Input and Frequency
Without changing the circuitry, we can now upload the Analog Input and Frequency program. After doing so, we can see how now turning the knob will change the pitch of the tone while the volume remains constant. Since we're reusing the circuit to run with this program, and we know that our previous code didn't damage our electronics, we leave the connections where they are without any trouble.
The code is almost identical to our previous program. The main difference is inside the
loop() function. We rename the variable that holds the output of the
map() function for
frequency (instead of
volume), and we also change the range of the output to be between 120 to 1500. This is because instead of our usual function call to
analogWrite(), we'll make use of the function
tone() function changes the frequency of the same rectangular wave signal we were using earlier while keeping the frequency constant. That is, it changes the total Period (Tmin+Tmax) of the signal rather than only the ON (OFF) duration Tmax (Tmin). At it's minimum of 120, the output signal is switching on and off 120 times in one second, and at its maximum value of 1500, the output signal is switching 1500 times per second. These frequencies may seem arbitrary, but we're choosing them based on the range of human hearing which is between 20 and 20,000Hz!
frequency = map(sensor_value, 0, 1023, 120, 1500); // change the analog out value: tone(analog_out_pin, frequency);
Just as before, when we turn the knob we change the value read by the microcontroller at pin A0, this varies the value passed to the
map() function, which in turn changes the frequency passed to the
tone() function. We hear the change in the frequency at which the piezo transducer is being driven ON and OFF!
Can't hear sound!
A few seconds after uploading the program Arduino board, you will begin to hear an annoying tone coming from the speaker. If you do not hear anything, try turning the knob all the way in both directions until you hear a sound. If after this you still can not hear a tone, verify that your speaker is connected with the correct polarity. If the speaker is connected in the correct polarity, verify all other connections.
Serial Monitor display woes.
If your Serial Monitor is displaying gibberish, check the number labeled "baud" on the bottom right corner of the window, and make sure this is the same number as the
Serial.begin(####) in our code.
If your Serial Monitor doesn't seem to be keeping up with the chaning values of the potentiometer, make sure the Autoscroll option is checked at the bottom left corner of the window.
We didn't go into too much detail about analog-to-digital conversion. As a relevant exercise, experiment with substituting the 1KOhm Potentiometer for the 10K one.
We learned that by adjusting the duty cycle of the PWM signal with a fixed frequency affects the amplitude (or 'strength') with which the piezo transducer vibrates. However, the vibration amplitude as a function of ON/OFF-time is extremely nonlinear. See for yourself by changing the range to the full 0 to 255. The volume goes louder even when we think it should be softer due to resonance, and other electrical and mechanical phenomena.
If we change both the duty cycle and the frequency of the rectangular wave signal, we should be able to change both the volume and pitch of the sound generated. However, neither the
tone() library nor the
analogWrite() function have a way of changing both parameters simultaneously. Have a look at the wonderful codebase developed for the Teensy development board, and have a crack at porting it over to your Arduino!