Skip to content

Labsson 7: Understanding Pulse Width Modulation PWM, Controlling LED brightness and Modulating Speaker Volume and Pitch using PWM, and Reading Writing Analog Signals in Arduino

Madi Babaiasl edited this page Feb 27, 2024 · 13 revisions

Introduction

In this labsson, we will become familiar with Pulse Width Modulation (PWM) with a focus on controlling LED brightness, modulating speaker volume and pitch, and reading analog signals in Arduino. This lesson provides an understanding of how PWM enables precise control over digital outputs to simulate analog behavior.

PWM Warmup Activity: analogWrite() in Arduino

analogWrite() is used to output a simulated analog value using Pulse Width Modulation (PWM) (we'll see what it is in a bit) on a specified digital pin. Unlike digitalWrite(), which can only output a HIGH or LOW state, analogWrite() can simulate varying levels of voltage by rapidly switching the output between HIGH and LOW at a duty cycle corresponding to the desired analog value. This is particularly useful for controlling the brightness of LEDs or the speed of motors.

It’s important to note that not all digital pins support PWM. Only specific pins are PWM capable (3, 5, 6, 9, 10, and 11 on the Uno). Key Differences Between digitalWrite() and analogWrite() are:

  • Binary vs. Range: digitalWrite() can only set a pin to HIGH or LOW, while analogWrite() can vary the output in a range from 0 to 255.
  • Pin Capability: Not all pins support analogWrite() since it requires PWM capability, but all digital pins support digitalWrite().
  • Applications: digitalWrite() is used for binary state devices like turning an LED on or off, while analogWrite() is suitable for applications requiring variable output like adjusting LED brightness or motor speed.

Syntax:

analogWrite(pin, value);

Where pin is the number of the PWM capable pin you want to write to, and value is the duty cycle between 0 (always off) and 255 (always on).


Explain why 0 volts corresponds to 0 and 5 V corresponds to 255 (think about the number of bits used to code the voltages between 0 volts and 5 volts). What about the voltages in between?


Now to understand how analogWrite() works, do the following simple activity.

Class Activity: LED Brightness Control using PWM in Arduino

In this activity, you will understand how Pulse Width Modulation (PWM) works and how it can be used to control the brightness of an LED using the analogWrite() function in Arduino. You will manipulate the brightness levels of an LED by changing the duty cycle of a PWM signal.

Hardware: Connect one LED through a current limiting resistor to one of the PWM pins of the Arduino.

Software: Open your Arduino IDE and write a code to control the brightness of the LED using a PWM signal:

  • Define a variable for LED pin, a variable for delay, and several variables for the LED brightness
  • In setup() function, set the LED pin as an output
  • In the loop() function, write the code to control LED brightness using analogWrite(). Include delays between changes in brightness to observe the changes clearly. Sample code below can give you an idea how to do it:
void loop() {
  analogWrite(LED_pin, brightness_level_1); // Set brightness_level_1 to 0 corresponding to off case
  delay(dt); // wait for about 1 second 
  .
  .
  .
}
  • Verify and upload the code to your board and explain what happens.

In the above warmup activity, you created analog-like signals from digital pins of Arduino. In other words, the Arduino's digital pins that were capable of PWM were used to generate signals that mimic analog output.

In other words, we used a digital pin but created an analog-like signal. analogWrite() function works with PWM. When we write analogWrite(pin, 0), this is like digitalWrite(pin, LOW) and the signal that is provided as a voltage out of the pin capable of PWM will look like this:

zero percent duty LED brightness

And when we write analogWrite(pin, 255), this is like digitalWrite(pin, HIGH) and the signal that is provided as a voltage out of the pin capable of PWM will look like this:

100 percent duty LED brightness

What if we write analogWrite(pin, 127) (127 is half of 255)? We may expect the voltage out of the pin to be constant 2.5 v but instead it looks something like this:

50 percent pwm led brightness control

Because as we discussed, it is a simulated analog signal out of the digital pin. If you look at the signal above, you see that it is simulating 2.5 volts by averaging the time spent in the HIGH state (5 volts) and the LOW state (0 volts) across the pulse width modulation (PWM) cycle. This is achieved by setting the duty cycle to 50%, meaning the signal is HIGH for half the time and LOW for the other half within each PWM cycle. By rapidly switching between these two states and leveraging the inertia of electrical components and human perception, the PWM signal effectively mimics a constant voltage of 2.5 volts. This principle allows digital systems, which can only output full-on or full-off states, to control devices in a way that appears analog, enabling the fine adjustment of LED brightness, motor speeds, and speaker volumes, among other applications.


Given our discussion on PWM above, if we write the analogWrite(pin, 20) command, what would be the waveform of the PWM signal produced by the digital pin, and what analog voltage level is it effectively simulating? How about analogWrite(pin, 200)?


After this warmup activity, let's officially see what PWM signals are.

Introduction to PWM signals

As we saw earlier, Pulse Width Modulation (PWM) is a technique used in electronics to generate analog-like signals from digital devices. It's commonly used for controlling the intensity of LEDs, the speed of motors, and other applications where variable power levels are required. Let's dive into a detailed tutorial on PWM.

Definition: PWM is a method for encoding analog information in a digital signal by varying the duty cycle of a square wave.

Duty Cycle: It's the percentage of time the signal is high (ON) compared to the total time of one cycle, as shown in the figure below:

duty cycling

A PWM signal is a series of ON and OFF states with varying durations. The average voltage over one cycle determines the effective voltage. Higher duty cycles result in higher average voltage.

Arduino PWM

As we saw in the warmup activity, Arduino boards have built-in PWM capability on certain pins (e.g., Arduino Uno pins 3, 5, 6, 9, 10, and 11). analogWrite(pin, value) is used to generate PWM signals on the specified pin.

PWM Frequency and Resolution

Frequency: PWM signals have a frequency, which is the number of cycles per second. Arduino default PWM frequency is around 490Hz.

Resolution: It refers to the number of steps in the duty cycle. Arduino Uno has 8-bit resolution (0 to 255) as we used it in the warm-up activity. For an Arduino Uno, which has an 8-bit resolution, this means the duty cycle can be set to one of 256 different levels, ranging from 0 to 255. A value of 0 would mean the signal is always off, and a value of 255 means the signal is always on, with values in between controlling how bright an LED appears or how fast a motor runs by adjusting how much time the signal spends in the on versus the off state within each cycle. For example, 127 can be approximately 50% duty cycle.

PWM Applications

  • Motor Speed Control: PWM is widely used for controlling the speed of motors that we will in the next labsson.

  • Audio Generation: PWM can be used for simple audio generation. For example, by rapidly turning a digital output pin on and off (using PWM), we can create vibrations in a speaker connected to that pin. The duty cycle of the PWM signal (the proportion of time the signal is high vs. low in each cycle) can be varied to modulate the volume, while the frequency of the PWM signal (how fast the signal cycles between on and off) can determine the pitch of the sound produced.


Class Activity: Modulating Speaker Volume and Pitch with Arduino

Here, you will learn how to control the volume and pitch of a sound produced by a speaker connected to an Arduino.

Hardware setup: Connect one terminal of the 8 Ohm Mini Speaker to a PWM-capable digital pin (e.g., pin 9) on the Arduino and connect the other pin through a current limiting resistor to ground:

connecting a speaker to an arduino

Software:

  1. Controlling the pitch of the sound: In the Arduino, you can use the tone() function to generate a square wave of the specified frequency (and thus a specific tone) on a pin. Internally, this function utilizes PWM to create the sound frequencies. Here is an example code (try to change the frequency to have sounds with different pitches):
int speakerPin = 9; // Connect a speaker to digital pin 9 or any PWM capable pin

void setup() {
  // No setup needed for this example
}

void loop() {
  tone(speakerPin, 440); // Generate a 440 Hz tone
  delay(1000); // Play the tone for 1 second
  noTone(speakerPin); // Stop playing the tone
  delay(1000); // Pause for 1 second
}

OPTIONAL: If you know music notes, see if you can use this tone() function to generate music. Here is an idea of how to do it:

  • Define the pin connected to the speaker
  • Define a melody and its note durations. Notes are in Hertz (Hz) corresponding to musical notes.
  • Define note durations: 4 = quarter note, 8 = eighth note, etc.

Here is a sample code (see if you can produce a better music):

// Define the pin connected to the speaker
int speakerPin = 9;

// Define a melody and its note durations.
// Notes are in Hertz (Hz) corresponding to musical notes.
int melody[] = {
  262,  // C4
  294,  // D4
  330,  // E4
  349,  // F4
  392,  // G4
  440,  // A4
  494,  // B4
  523   // C5
};

// Note durations: 4 = quarter note, 8 = eighth note, etc.
int noteDurations[] = {
  4, 4, 4, 4, 4, 4, 4, 4
};

void setup() {
}

void loop() {
  // Iterate over the notes of the melody:
  for (int thisNote = 0; thisNote < 8; thisNote++) {
    // To calculate the note duration, take one second divided by the note type.
    // e.g., quarter note = 1000 / 4, eighth note = 1000/8, etc.
    int noteDuration = 1000 / noteDurations[thisNote];
    tone(speakerPin, melody[thisNote], noteDuration);

    // To distinguish the notes, set a minimum time between them.
    // The note's duration + 30% seems to work well:
    int pauseBetweenNotes = noteDuration * 1.30;
    delay(pauseBetweenNotes);
    // Stop the tone playing:
    noTone(speakerPin);
  }
}
  1. Controlling the perceived volume of the sound: Use the analogWrite() function that we learned in this lesson to control the perceived volume of the sound by adjusting the duty cycle of the PWM signal sent to the speaker. By varying the duty cycle, we can alter the amount of time the signal is in the HIGH state versus the LOW state within each cycle, which changes the average power delivered to the speaker. Note that this technique doesn't adjust the sound wave's amplitude directly but modifies the power output to the speaker, thereby affecting the loudness of the sound produced.

  • Light Dimming: Used in lighting systems to control brightness. We also saw an example at the beginning of this lesson.

PWM Limitations

  • Resolution Limitation: Limited resolution can result in less precise control.
  • Filtering Requirement: Some applications may require additional low-pass filtering. By using a capacitor at the output to smooth out the signal, we can get a real analog signal.

Reading Analog Voltages Using analogRead() Command

In the previous labsson, we could read a digital voltage (0 or 1) from a pushbutton using digitalRead() command. Here, we want to know how to read analog voltages because suppose that you have a sensor that changes its resistance based on environmental factors like temperature, light, or moisture. This change in resistance affects the voltage across the sensor. By reading these voltage variations with an Arduino's analog pin, we can interpret the sensor's data to quantify the changes in the environment.


Class Activity: Reading the voltage (basically a voltmeter) from a voltage divider output using Arduino

Go back to Labsson 2 and implement the voltage divider circuit. Then do the following:

  • Connect the junction between the two resistors to one of the Arduino's analog input pins (A0 through A5).
  • Open the Arduino IDE and create a new sketch.
  • Initialize the analog pin you've connected to the voltage divider as an input.
  • In the setup() function, begin serial communication using Serial.begin(9600); to send data back to the computer over USB.
  • In the void loop():
    • Read the Analog Input: int sensorValue = analogRead(A0); Note: analogRead(A0); reads the voltage at the analog input pin A0, where your voltage divider is connected. The Arduino converts this analog voltage into a digital value (ranging from 0 to 1023) which is stored in the sensorValue variable. This conversion is based on the reference voltage of 5V (for most Arduino boards), where 0 corresponds to 0V and 1023 corresponds to 5V.
    • Convert the Digital Value to Voltage: float voltage = sensorValue * (5.0 / 1023.0); Note: This line converts the digital value (0-1023) back into the corresponding voltage value in volts. It multiplies the sensorValue by the value of each step (5V divided by 1023 steps), resulting in the actual voltage at pin A0.
    • Print the Voltage to the Serial Monitor (calculate the expected voltage and show that it is equal to the voltage that the serial monitor shows):
Serial.print("Voltage: ");
Serial.println(voltage);
  • Do not forget to implement delay between readings.

Question: The Arduino's Analog to Digital Converter (ADC) represent analog voltage levels with digital values ranging from 0 to 1023. This means that the ADC divides the voltage range it can read (0V to 5V for many Arduino boards) into 1024 discrete steps. How many bits does the Arduino's analog to digital converter (ADC) use to represent analog voltage levels?


Class Activity: Measuring the voltage output of a potentiometer

Again go to Labsson 2 and review how a "pot" works (include a short review in your report as well). Now do these things:

  • Based on what you have learned in Labsson 2 and this lesson about analog inputs, explain how you can hook up a pot to Arduino. Implement this using a pot, breadboard and Arduino.
  • After wiring your potentiometer, you can use the same method above about reading the analog inputs to display the output voltage of the potentiometer. Rotate the knob to get different voltage readings on the serial monitor.

Questions:

  1. Print out the value that Arduino reads from the analog input. What is the minimum value and what is the maximum values.
  2. Now, convert this digital value that serial monitor shows back to the analog voltage. What is the minimum and maximum values.
  3. What is the potentiometer's role here?
  • Now use this measured voltage from the potentiometer to control an LED's state. Implement a code segment that lights up the LED if the voltage surpasses a specific threshold (say 4 volts). Don't forget to use a current limiting resistor for the LED, initialize the LED pin, and also turn off the LED if the voltage is below 4 volts. Feel free to play around with different conditions for the if statement.
  • Now, see if you can use the read values from the analog input to create a Dimmable LED where the LED's brightness is gradually changing. Note that Arduino changes the voltage read from the analog input (using analogRead() command) to a number between 0 and 1023. However, when you want to analogWrite() to an LED, the range will be between 0 and 255. So, the value read from the analog input needs to be mapped from a range of 0 to 1023 to a range of 0 to 255 (find the equation of the line):

mapping analogread to analogwrite


Guidelines for the lab report

  • The guidelines and report format is pretty much like the previous lab reports but you should also submit videos showing that the implementation of the projects work. Creative twists have extra credits as always.
  • Submit your code as well. You can send a link to a GitHub repository or directly submit your Arduino code.

Good luck!

Clone this wiki locally