Using PWM Outputs

programming

How can you change the brightness of an LED? Let's look into Pulse Width Modulation.

Pulse Width Modulation (PWM)

Last time we learned to use analog inputs and create a twilight switch. This time we will have a look into analog outputs - at least sort of. There are real digital to analog converters (DACs) to do analog output, but the Arduino Uno does not have one. Instead, we will look into Pulse Width Modulation or short PWM.

When using PWM the voltage is not changed, instead the on-time of the LED is changed. By quickly turning a digital output on and off, we can fade an LED. If it is done fast enough we will not notice the flickering, but instead observe the LED as less bright. This technique is perfectly good for LEDs, as their brightness cannot be easily adjusted by changing the voltage. Doing so might cause the LED to slightly change the color, and below a certain voltage the LED will just turn off. In theory the PWM signal can be converted into an analog signal using a low pass filter, but this is clearly not a task for beginners and will not help us with our LED anyway.

The Arduino can produce a fast PWM signal at the pins marked with ~. Usually a frequency of 490 Hz is used. This means, that the LED is turned on and off 490 times per second. The frequency of 980 Hz used at pins 5 and 6 is even higher. In normal Arduino code we can't change this frequency, but we can change the fraction of time for which the LED is turned on in each period. This is also called the duty cycle. I added the waveform produced for different duty cycles below. As you can see, the LED is turned on or off for the same amount time at 50 % duty cycle. 0 % duty cycle means that the LED is constantly off. With a duty cycle of 100 % the LED is always on. Instead of a value in percentage the Arduino uses values between 0 and 255 where 255 corresponds to a duty cycle of 100 %. PWM signal with different duty cycle

Building the Circuit

Circuit with potentiometer and LED on the breadboard

As usual, we connect an LED with an in-series resistor of 220 Ω. This time it is connected to a PWM capable pin. I chose pin 9 for this purpose. To control the brightness of our LED we use a potentiometer. The two sides of the potentiometer are connected to GND and 5V, whereas the wiper in the middle is connected to the analog pin A0. I used a linear potentiometer with a total resistance of 10 kΩ. The wiper slides over the resistive material. Right in the middle we have half of the resistive material right and half of it left to our wiper. This corresponds to a voltage divider with two 5 kΩ resistors. In the center position we would thus measure 2.5V. By turning the potentiometer we get voltages from 0 V up to 5 V. We measure this voltage at analog pin A0, where the analog-to-digital converter (ADC) will convert it into a value between 0 and 1023. We can use this measurement value to control the duty cycle of our LED and thus its brightness.

Writing the Code

The required code is quite simple, once again. We already know the analogRead function and only need one new function: analogWrite.

  • analogWrite(pin, value)
    • pin: Number of a PWM capable pin
    • value: Duty cycle as number between 0 and 255

In our code we just need to scale the value returned by analogRead into the range between0 and 255 and pass it to the analogWrite function. For this we just divide the value by four and are ready to go.
Here is the code:

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

void loop() {
  int dutyCycle = analogRead(A0)/4;
  analogWrite(9, dutyCycle);
}

Looking at the Result

When you turn the potentiometer you can see the LED changing its brightness. You might notice a surprising detail: The LED is fading quickly at low brightness and almost no change is noticeable at a high brightness. Is it the code? Or is it the way we connected the potentiometer? No, everything is just fine, but still seems to work not quite correct though. I included a video for you and as you can see my solution suffers from the same problem. What is happening?

The problem is not in the code or the circuit, it lays buried in the way humans sense brightness. We don't observe brightness changes in a linear fashion, but instead notice a brightness change at lower brightness a lot better than at a higher brightness. This is a problem all lights and also monitors have to deal with. The solution is called gamma correction.

Gamma Correction

In gamma correction, we compensate the non-linearity in human vision, by using math. We need smaller steps at lower brightness and bigger ones for high brightness to evenly fade the LED. For this purpose the following formula can be used:

\[ I_{compensated} = I_{in}^\gamma \]

The value for \(\gamma\) is usually somewhere between 1.5 and 3. A common values used for monitors is 2.2, which is the value I'm going to use. For the power we will use the function pow(base, exponent). I transform the value for the potentiometer position to the range between 0 and 1and use the data type float to avoid any trouble with scaling the value afterwards. If we use the value between 0 and 1023 as base we would need to divide by some strange value afterwards to get the result back into the range between 0 and 255. Because of the exponent 2.2, we have to use floats anyway. To make it short, here is the code:

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

void loop() {
  float input = analogRead(A0)/1023.0;
  float corrected = pow(input, 2.2);
  int dutyCycle = corrected*255; 
  analogWrite(9, dutyCycle);
}

In the video below you can see that we now get a smooth fading for our LED. Math is great, isn't it?

Previous Post Next Post