Lab 07a: PWM

England Expects Every 6.08 Student to Do Their Duty Cycle

The questions below are due on Tuesday March 19, 2019; 10:00:00 PM.

Partners: You have not yet been assigned a partner for this lab.
You are not logged in.

If you are a current student, please Log In for full access to the web site.
Note that this link will take you to an external site ( to authenticate, and then you will be redirected back to this page.

Bright Kishi Bashi


Goals:In this Lab we're going to study pulse-width modulation and apply it to the backlight of our LCD in order to implement an auto-brightness control

Code for lab today found here

1) Intensity Control

Our LCD works by having a white light source (a bright LED which is called a backlight) shine through a high-density array of liquid crystals controlled by Thin Film Transistors (TFTs) to selectively allow certain colors to come through. Assuming the power needed to control the pixels themselves is negligible, the majority of power consumed comes from the backlit LED. It would be good to control the brightness of this power-hungry element in an energy efficient way. In order to do that, let's think about how it operates:

In schematic form, our backlight LED is set up in the following way...basically a white LED hooked up to a power source such as 3.3V and Ground.

Our backlit LED. The Blue text indicates the pin lables on the ST7735 that correspond to the LED terminals

The amount of light the LED emits is roughly proportional to the current through it, and the current through the LED is exponentially based on the voltage applied to it (don't worry about that here). Often a small series resistor will be placed in series with the LED to further constrain/limit the current through the LED like so:

The small series resistor limits the maximum current through the LED

In the case of the backlight LED for our ST7735 TFT Displays, the series resistor has a value of 7.5 Ohms and is hardwired to the back of the board.

1.1) Controlling Brightness

Our backlight LED is quite bright. If we wanted to provide the ability to change the brightness of the screen how could we do it? The solution is to obviously change the current through the LED, which in turn changes the amount of light it emits.

One way to go about doing this is to change the circuit to where the series resistor can be varied like shown below. While the relationship of the resistance to the current of this resulting circuit is non-linear and beyond the scope of this class (take 6.002 or 6.012), it does follow a general trend such that the higher the series resistance, the lower the resulting current, and therefore the lower the amount of light emitted. Take for example the following case where a 3 \Omega series resistor results in the following node voltage conditions of a similar circuit:

The LED with a 3 Ohm series resistor

Answer the following questions to within 0.01% of actual numbers:

What is the power consumed by the LED (in Watts) in this situation?

What is the power consumed by the resistor (in Watts) in this situation?

Efficiency is the ratio of power going towards useful work over the total power consumed. In this case, the LED's power is useful (it gives us light) whereas the power consumed by the resistor is a waste.

What is the efficiency of the above condition?

Now consider a case where we've increased the series resistance, thus lowering the total current, and therefore light emitted by the LED. Answer the following questions in this "dimmed" setting.

The LED with a 10 Ohm series resistor

What is the power consumed by the LED (in Watts) in this situation?

What is the power consumed by the resistor (in Watts) in this situation?

What is the efficiency of the above condition?

Finally consider the situation where an open circuit (an infinite ohm resistor) is in place with the LED, blocking all current through it.

The LED with an Open in series with it

What is the power consumed by the LED (in Watts) in this situation?

What is the power consumed by the resistor (in Watts) in this situation?

Check Yourself:

What are some conclusions we can draw out here? When on, how does the efficiency of the LED circuit change as we dim it using the above-described control? What is the efficiency of the system when the system is "off", and/or does a metric like efficiecy really matter in that particular case?

1.2) PWM To the Rescue

The approach above will work in order to vary brightness. You can get variable resistors that could be placed in series with the LED (a potentiometer or a switchable resistor array), or you can get devices that can modulate their effective resistance based on other control voltages (cough transistors, cough, the most important things in the world, upon which all good things are built, cough). You could also build a variable voltage supply, but that's also a lot of stuff to have to incorporate (and actually would just be made of transistors as well). Regardless of how you do it, a couple problems arise with controlling our LED's brightness by changing the current through them:

  • The relationship between resistance/current and brightness is highly-nonlinear. Doubling the series resistance does not halve the current in an LED, nor does halving the voltage. While it can be predicted how one input affects the output, it is complicated.

  • The efficiency of the system varies significantly in different states. To dim the LED, you need to run it at a lower efficiency than when it is fully on.

There exists an alternative. To understand the alternative, we first need to figure out what we care about. In our case we want to change the brightness of an LED. What is the purpose of a light? Usually to illuminate things. And for what purpose do we illuminate things? For people to see them generally. The act of a person seeing something generally utilizes their eyes. Human eyes, while marvels of engineering, are actually pretty slow-responding in time, at least in comparison to the speeds at which electronics and LEDs can operate. This is in fact why we have movies and why the media below looks like actual moving kittens and not just a series of sequentially drawn kitten pictures.

The kittens aren't actually moving in this video. Our mediocre eyes let us think that they are. Ces ne sont pas des chatons en mouvement

We can take advantage of this same phenomenon for our LED brightness control. If instead of constantly providing a dim continuous illumination from the LED, we can instead provide quick bright bursts interspersed with periods of time when the LED is not on, we can obtain something very similar. We can, in effect, trick the eye into seeing a various degreees of dim illumination—, but we end up achieving this using only high-efficiency states from the LED (high-brightness with a high efficiency and no-brightness/no power at all when off)! In order to do this, we will turn it on and off at various duty cycles. Because our eye cannot respond fast enough to the turning-on-and-off events of the LED, it will allow us the freedom to change the perceived brightness of the system. This technique is known as Pulse-Width Modulation, PWM, and is widely used. With light, in effect, our eye time-averages for us. Graphically this looks like the following:

Duty Cycle.

We define the term Duty Cycle to be the percentage of "ON" time with respect to the total amount of "ON" and "OFF" time. 50% duty cycle, for example, means the LED would be on for half the time and off for half the time. 25% means it is on for 25% of the time and off for 75%. The frequency at which we do this on-off switching is extremely important. If our frequency is 1Hz, our eyes can very easily see that and the illusion will not hold...we won't get varyied levels of dimming, just weird flashing patterns. But if we move up a couple orders of magnitude beyond the response rate (the time constants of the eye's light-detecting ion channels), we can get our achieved effect. If we run through our flashing period in 10 ms (100 Hz), for example, as shown below, a 50% duty cycle signal will look brightish (but not as bright as a 100% duty cycle), where as something much smaller (5 or 10% duty cycle) will look much dimmer.

The effect of varying duty cycles.

PWM control of systems can also have the added benefit of more linear control! We can quite readily set our system to be 25% of full brightness by setting the duty cycle to be 25%. This is actually tough to do the other way with an LED since its voltage-current-power relationship is highly non-linear.

Checkoff 1:
Discuss with staff the reasons for PWM

2) Design a PWM Class

What we'd like to now do is to implement a C++ data structure that will implement PWM of an arbitrary frequency, duty cycle, and apply it to an assigned digital pin. We can think of the PWM operation as a state machine with a simple two state existence that is very coarsely demonstrated below:

A PWM module can be thought of as a two-state FSM which given a duty cycle and frequency, uses time to determine how often it should turn on or off. In our implementation, you won't need an explicit "state" variable since system state can be derived solely from time

Our PWM_608 class should have a digital output pin associated with it and turn that pin on and off at the correct duty-cycle and at the correct frequency using digitalWrite(pin,value) calls. Pin number and frequency (in Hz) are specified upon creation of a PWM_608 object, and the duty cycle should default to 0 upon instantiation. The period should be rounded down to the nearest whole millisecond.

class PWM_608
    int pin; //digital pin class controls
    int period; //period of PWM signal (in milliseconds)
    int on_amt; //duration of "on" state of PWM (in milliseconds)
    PWM_608(int op, float frequency); //constructor op = output pin, frequency=freq of pwm signal in Hz
    void set_duty_cycle(float duty_cycle); //sets duty cycle to be value between 0 and 1.0
    void update(); //updates state of system based on millis() and duty cycle

Upon instantiation, an object of the PWM_608 class should start with a duty cycle of 0. When PWM_608::set_duty_cycle is called, the duty cycle is updated based on the value provided (that value will range from 0.0 to 1.0 for 0 to full duty-cycle respectively. The PWM_608::update should be called as often as possible to update the state of the PWM! We should use the millis() function for timing, and because this limits the time resolution of our operation to one millisecond, the member variable, on_amt should be floored when it is calculated, in the case a sub-millisecond timing resolution is required (similar with period on instantiation). Specifics of a single period's timing are shown below graphically, showing particularly how we'd like the ON-OFF cycle to be synchronized with the millis() counter's return value:

A timing diagram of our desired PWM functionality

Your class implementation should protect against invalid duty cycle specifications such as less than 0 or greater than 1.0, by clipping the values set at their nearest limit (a value of -1.45 should just be 0, for example).

3) Implement PWM

PWM_608::PWM_608(int op, float frequency){ //your code here } void PWM_608::update(){ //and here } void PWM_608::set_duty_cycle(float duty_cycle){ //oh. and also here }

When this code is working, place the working class definitions into the lab07a.ino file. This file is set up to use an analog reading collected on Pin A3 to instruct the brightness of our screen. In order to make this feature useful, the analog reading being collected will come from a voltage divider utilizing a photoresistor like shown below. For our LED driving pin, we'll be using pin 12 for this first part. Go to the front and grab your parts including:

  • Photoresistor
  • 10 KOhm resistor

Modify your wiring to implement the following schematic to your existing system, modifying where the ST7735's LED pin is connected (IT SHOULD NO LONGER TO 3.3V!!! READ THIS!!), and complete the needed code to get things working. Try to position the photoresistor nearby to your screen.

The ESP32 pin diagram is here.

Wiring up a screen autobrightness feature

Once wired up you should see that the system is running and the intensity of the screen's brightness is a function of how much illumination is on the photoresistor. When the environment gets darker, your screen should get dimmer, and when it gets brighter, your screen should get brighter. Just like your phone does! Congratulations, you have just built some auto-brightness functionality and are now saving power.

Demonstrate to the staff your autobrightness setup and how it operates. Discuss with them any problems and limitations you see.

4) A Hardware Solution

As should have been discussed in the previous checkoff, you'll notice "glitches occurring in the illumination experienced by the user. This is coming from slight deviations in the timing loop which cause "jitter" in our PWM as well as some background processes including limitations with the ESP32's RTOS (Real Time Operating System). Even if our ESP32's loop varies by a little bit, this can inadvertantly change our PWM's duty cycle (since it might be delayed by a millisecond on updating its value, thus changing the on-off ratio of the LED) and that will change the perceived brightness. While our eyes are pretty slow, they still have some sensitivity, and you'll see evidene of that in the periodic flickering coming from the glitches.

Another cool thing to do with how our PWM is implemented is to stare slightly off-axis at our display when it is illuminated. You should notice a more consistent flickering which dissipates when you look dead-on at the display. This is because the rod photodetectors around the edges of our eyes have a higher frequency cutoff than the cones in the center of our eye, so it takes a higher PWM frequency to fully "fool" them. This same sensitivity difference is why you can sometimes detect stuff easily out of the corner of your eye at night, but then when you look at it head-on, it disappears...the cones just aren't as sensitive. If you bump your PWM rate up to 60 to 70 Hz, you'll see this rod/cone differential dissapear as it is now beyond the frequency sensitivity of both types of receptors.

In order to fix the aperiodic glitch problem from loop variations and background processes, we're going to use built-in PWM functionality on the ESP32. The ESP32 actually has 16 separately controllable PWM modules within it1 that are implemented in small dedicated hardware modules(not software). These can be individually driven and associated with different GPIO pins as needed. What's nice about using these peripheral modules as opposed to writing code is that they can be programmed to run independently of our main program loop, so they don't take up valuable clock cycles. As a result they operate in a set-it-and-forget-it manner, giving us truly in-parallel operation, not faked-parallel that we get with our non-blocking code practice.

Our ESP32's Hardware PWM capabilities!

The code and syntax needed to interface to the Hardware PWM modules is discussed briefly below:

In order to initialize one of the hardware PWM modules, you call the following function (preferablly in setup):

  ledcSetup(pwmChannel, freq, resolution); 
  • pwmChannel is a PWM channel (value 0 through 15) you want to initialize
  • freq is the PWM frequency in Hz you want the PWM module to run at
  • resolution The bits of precision with which you can specify the duty cycle

The next thing to do is to link the PWM channel to a particular output pin. You can do that with the following call:

  ledcAttachPin(ledPin, pwmChannel);
  • ledPin is the actual pin we want to attach to (it'll be 14 for us here)
  • pwmChannel is the id (0 through 15) of the PWM channel we'd like to link to this pin.

If we'd then like to change/set the PWM's duty cycle, we need to do so using the following call:

ledcWrite(pwmChannel, value);
  • pwmChannel is the id (0 through 15) of the PWM channel we'd like to change.
  • value is the desired duty cycle as an integer based on the number of bits of precision it was created with. The range of value will be from 0 to 2^n-1 where n is the number of bits.

Some of this is in your downloaded code already. Uncomment those parts as needed. Then update your code so that a hardware PWM connected to pin 14 and running at 50Hz or 60Hz is set up to control your LED backlight. Make sure to slightly modify your wiring as shown below Your system should still use the output of the photoresistor's voltage divider to get instruction on what brightness it should be, changing its screen brightness in repsponse to ambient brightness.

Use Pin 14 now instead of Pin 12

Implement it, and then test it. Do you notice as many glitches? The answer should be no.

Discuss with staff why a hardware-implemented PWM is superior to a software version.

The jerks who sold me these photoresistors, shorted me 100, so you need to return your photoresistor. One Star, would not purchase from them again. Return your photoresistor to the front when completed.

Also for more information on PWM, see staff member Kenneth Collins' carefully constructed high school talk about PWM for 6.UAT. Also make sure to connect with Kenneth on LinkedIn.

One more lab on Thursday and then Spring Break! Whoooooooo. Whoo. (then more labs after).


1many microcontrollers have these (click to return to text)

This page was last updated on Tuesday March 19, 2019 at 03:50:40 PM (revision 39c13d1).
Course Site powered by CAT-SOOP 14.0.4.dev5.
CAT-SOOP is free/libre software, available under the terms
of the GNU Affero General Public License, version 3.
(Download Source Code)
CSS/stryling from the Outboxcraft library Beauter, licensed under MIT
Copyright 2017