2020-21 Sem 2 Lab 8: Pulse Width Modulation

Pulse width modulation (PWM) is a way of encoding a continuously variable signal into a binary signal. A PWM signal consists of a series of regularly timed pulses. The continuously variable signal, which we call the modulating signal, controls the width of the pulses in the PWM signal. The frequency of a PWM signal (i.e. the number of pulses per second) is normally fixed, as are the high and low signal levels – all that varies is the width of the pulses.

The duty cycle of a PWM signal is the fraction of each period during which the signal level is high. The duty cycle of a PWM signal is typically expressed as a percentage. The following figure from yesterday’s lecture illustrated how a PWM waveform changes as its duty cycle varies.

A PWM signal can be used to transfer information from one device to another. In that case, the receiving device determines the magnitude of the original modulating signal by measuring the duration of each incoming pulse. One advantage of transmitting a signal this way is that binary signals are relatively resistant to interference (compared to analog signals).

PWM signals can also be used to control the amount of power delivered to an electrical load, such as a light, heating element, or motor. This is how we will use them in today’s experiment.

One way to generate a PWM signal using a microcontroller is to write a program that repeatedly sets a digital output pin high and low at the right times to create the series of pulses. Using software to generate a PWM signal (or some other kind of binary data signal) is often referred to as bit banging. One drawback of this approach is that it uses up processor time, which may be a limited resource.

Because PWM signals are so widely used, many microcontrollers include dedicated hardware modules to generate them. Such a hardware module typically uses a digital counter to generate the PWM pulses in the background, leaving the processor free to execute other code (unlike bit banging). The Arduino Nano is based on the ATmega328P microcontroller, which includes several hardware modules that can generate PWM signals.

Part 1: PWM signal generation by bit banging

Typically, PWM signals contain hundreds or thousands of pulses per second. However, we’ll begin with a very low frequency example (1 pulse per second) so that you can clearly see the signal changes occurring.

The program below outputs a 1 Hz PWM signal with a 10% duty cycle.

//
// Low frequency bit banging PWM example
// Written by Ted Burke - 20-Apr-2021
//

void setup()
{
  pinMode(3, OUTPUT);
}

void loop()
{
  // PWM counter, period and duty cycle
  int counter, top = 100, dutycycle = 10;
  
  // Use a while loop to count from 0 up to 100
  counter = 0;
  while(counter < top)
  {
    digitalWrite(3, counter < dutycycle); // Set D3 high or low
    counter = counter + 1;
    delayMicroseconds(10000); // 10 ms delay controls counting rate
  }
}

Run the program as it is. Observe the 1 Hz PWM signal making the LED flash once per second with a duty cycle of 10%. Modify the program to configure the PWM signal as follows:

  • Set the PWM period, Tpwm = 500 ms (i.e. fpwm = 2 Hz).
  • Set the PWM duty cycle to 80%.

SUBMISSION ITEM 1: Record a 10-second video of your LED flashing at 2 Hz with 80% duty cycle. Name the file “bitBangPWM.mp4” (or .avi or .mkv or whatever). Submit your video file to Brightspace.

The following program is functionally identical to the example above, but it uses a for loop instead of a while loop to generate the waveform. As you can see, a for loops can often be more compact than the equivalent while loops. Please read this program carefully to see how it relates to the example above.

void loop()
{
  // PWM counter, period and duty cycle
  int counter, top = 100, dutycycle = 10;
  
  // Use a for loop to count from 0 up to 100
  for(counter = 0 ; counter < top ; ++counter)
  {
    digitalWrite(3, counter < dutycycle); // Set D3 high or low    
    delayMicroseconds(10000); // 10 ms delay controls counting rate
  }
}

Part 2: Higher frequency PWM with sinusoidal modulation

The following program uses bit banging again to generate a PWM signal, but this time the duty cycle is modulated sinusoidally. The PWM frequency is also considerably higher (approximately 100 Hz), so although the LED really is switching on and off, the human eye can’t see it happening. Instead, the LED appears to be continuously on, but with a brightness that varies with the duty cycle.

Run the program to verify that the LED pulsates sinusoidally, then read over the code carefully to confirm that you understand what each line is doing.

//
// Bit banging PWM with sinusoidal modulation
// Written by Ted Burke - 20-Apr-2021
//

void setup()
{
  pinMode(3, OUTPUT);
}

void loop()
{
  // PWM counter, period and duty cycle
  int counter, top = 100, dutycycle;

  // Sinusoidal modulation of duty cycle
  dutycycle = 50 + 45 * sin(millis()/1000.0);
  
  // Use a for loop to count from 0 up to 100
  for(counter = 0 ; counter < top ; ++counter)
  {
    digitalWrite(3, counter < dutycycle); // Set D3 high or low    
    delayMicroseconds(100); // Sets PWM frequency to approximately 100 Hz
  }
}

Once you’re sure you understand how the above program works, save a new copy of it called “sineLEDs” then try modifying it so that it modulates two LEDs out of phase with each other, as shown the following short video.

There are several ways you could approach this problem, but here are some suggestions:

  • Add a second LED and 220Ω resistor to the circuit.
  • In the program, create an additional digital output pin.
  • Look at the lines in the original program that switch the first LED on and off. You’ll need to add something similar to control the second LED in the opposite way.

SUBMISSION ITEM 2: Record a 20-second video of your two LEDs pulsating out of phase with each other. Name the file “sineLEDs.mp4” (or .avi or .mkv or whatever). Submit your video file to Brightspace.

Part 3: PWM output using the Arduino library’s analogWrite function

The primary disadvantage of bit banging is that it uses up processor time and makes it difficult to get anything else done because the microprocessor is so busy switching the pin high and low all the time. Fortunately, like many other microcontrollers, the ATmega328P (the brain of the Arduino Nano) has several hardware modules that can generate PWM signals in the background while the microprocessor gets on with other tasks.

The easiest way to output PWM signals from an Arduino Nano is using the “analogWrite()” function which is provided by the Arduino libraries, but it imposes certain limitations: Only some of the pins can be used and for each pin, the PWM frequency is fixed. Specifically,

  • Pins D3, D9, D10 and D11 can output 490 Hz PWM.
  • Pins D5 and D6 can output 980 Hz PWM.

Note that the specific pins and PWM frequencies vary from one model of Arduino to another. Also, it’s worth noting that by programming the low level registers that configure the ATmega328P’s hardware PWM modules, the frequency can be set to different values. However, this is beyond the scope of today’s lab. More details can be found in chapters 14-17 of the ATmega328P datasheet.

To understand how the analogWrite function works, read the official documentation page on the Arduino website. Using the same circuit as in the previous part, try running the example program below. Having read the analogWrite documentation, hopefully the program will be self-explanatory.

void setup()
{
  pinMode(3, OUTPUT);
}

void loop()
{
  // Increase LED brightness in steps
  analogWrite(3, 0);   // 0% duty cycle
  delay(1000);
  analogWrite(3, 16);  // 6.25% duty cycle
  delay(1000);
  analogWrite(3, 32);  // 12.5% duty cycle
  delay(1000);
  analogWrite(3, 64);  // 25% duty cycle
  delay(1000);
  analogWrite(3, 128); // 50% duty cycle
  delay(1000);
  analogWrite(3, 255); // 100% duty cycle
  delay(1000);
}

Using the Arduino’s analogRead and analogWrite functions, write a program that allows a TCRT5000 sensor to control the brightness of an LED, as shown in the following short video. As explained in the Arduino documentation (and as you have seen before), the analogRead function returns a value between 0 and 1023, whereas the analogWrite function accepts duty cycle values between 0 and 255. Hence, you will probably need to divide down each value you read from the analog input before using it to set the PWM duty cycle.

SUBMISSION ITEM 3: Record a 10-second video of your LED brightness responding to the TCRT5000 sensor. Name the file “TCRT_LED.mp4” (or .avi or .mkv or whatever). Submit your video file to Brightspace.

Part 4: Motor speed control using PWM

In this part, we will use PWM to control the speed of a motor. Construct the following circuit:

As explained in the SN754410NE datasheet, pins 1 and 9 on that chip are “enable” pins. When the voltage on pin 1 is high, the two outputs on that side of the chip (pins 3 and 6) are enabled; when the voltage on pin 1 is low, pins 3 and 6 are disabled. By injecting a PWM signal into pin 1, the power supplied to the motor becomes intermittent rather than constant. In essence, the duty cycle of the PWM signal controls the amount of power delivered to the motor and hence its speed.

Try running the following (hopefully self-explanatory) program and verify that the motor moves as expected.

void setup()
{
  pinMode(3, OUTPUT); // PWM output pin
  
  pinMode(4, OUTPUT); // motor forward
  pinMode(5, OUTPUT); // motor reverse
}

void loop()
{
  // Motor forward at 50% for 2 seconds
  digitalWrite(4, HIGH);
  digitalWrite(5, LOW);
  analogWrite(3, 128); // 50% duty cycle
  delay(2000);
  
  // Motor forward at 100% for 2 seconds
  digitalWrite(4, HIGH);
  digitalWrite(5, LOW);
  analogWrite(3, 255); // 100% duty cycle
  delay(2000);
  
  // Motor reverse at 50% for 4 seconds
  digitalWrite(4, LOW);
  digitalWrite(5, HIGH);
  analogWrite(3, 128); // 50% duty cycle
  delay(4000);
}

Modify the program to perform the following sequence repeatedly:

  1. Forward for 2 seconds at 50% duty cycle,
  2. Stop for 2 seconds,
  3. Reverse for 1 second at 100% duty cycle,
  4. Stop for 2 seconds.

SUBMISSION ITEM 4: Record a 20-second video of your motor performing the above sequence. Name the file “motor.mp4” (or .avi or .mkv or whatever). Submit your video file to Brightspace.

Part 5: Remote control of motor speed

Previously, we used the Serial Monitor to display text printed from the Arduino on the screen of the PC. In this final part of today’s lab, we will use the Serial Monitor tool in a new way – to send commands from the PC to the Arduino. This will allow the motor speed and direction to be controlled remotely.

Using the same circuit as in the previous part, run the following program.

//
// Motor speed remote control example
// Written by Ted Burke - 20-Apr-2021
//

void setup()
{
  Serial.begin(115200);

  pinMode(3, OUTPUT); // PWM output for motor speed control
  
  pinMode(4, OUTPUT); // motor forward
  pinMode(5, OUTPUT); // motor reverse
}

void loop()
{
  int command_char; // incoming command character
  int motor_speed;

  // Check if any characters have been received
  if (Serial.available() > 0)
  {
    // Read the command character
    command_char = Serial.read();

    // Process the forward command if necessary
    if (command_char == 'f')
    {
      motor_speed = Serial.parseInt();
      
      Serial.print("forward ");
      Serial.println(motor_speed);
      
      analogWrite(3, motor_speed);
      digitalWrite(4, HIGH); // forward
      digitalWrite(5, LOW);      
    }

    // Process the stop command if necessary
    if (command_char == 's')
    {
      Serial.println("stop");
      
      digitalWrite(4, LOW); // stop
      digitalWrite(5, LOW);      
    }
  }
}

At the top of the Serial Monitor window, there is a text box for sending messages to the Arduino. The motor can be controlled by typing commands into this text box and pressing enter (or clicking the “Send” button). The program above responds to two commands: “f” for forward and “s” for stop. The forward command should include an integer value between 0 and 255 for the motor speed. For example, to set the motor driving forward at 50% duty cycle, type “f128”. The stop command is just the single character “s” and does not require a numerical value. Try the program out for yourself and verify that you can control the motor. Read the program carefully to figure out how it works. It includes two new functions from the Arduino library that we have not used before: Serial.available() and Serial.parseInt(). Read the documentation pages to learn about what each one does.

Your final task is to modify the previous program to add a third command: “r” for reverse. It should function the same as the forward command, except that the motor turns in the opposite direction.

SUBMISSION ITEM 5: Save your Arduino program as “motorCommands” and submit the file “motorCommands.ino” to Brightspace. Please ensure that your code is neatly indented and fully commented (including your name and date at the beginning).

Leave a comment