dual bright LEDs/Driving it/07. Dimming with PWM
Driving it · 07 of 9

Dimming with PWM

analogWrite does not lower the voltage. It switches the LED fully on and off hundreds of times a second, and the duty, 0 to 255, is the share of each cycle it is on. Every pulse carries the full current; only the average falls. The eye is kinder to dim light than the arithmetic, so a fade in equal steps looks wrong, and a 2.2 power fixes it.

Full on, for part of the time

The transistor is either off or fully on. There is no half-on in this circuit, and analogWrite does not ask for one: it switches the pin HIGH for part of each cycle and LOW for the rest. Move the slider.

Dimming with PWM
analogWrite(pin, 170)
VCC wired to
Duty170 of 255
Each pulse
168 mA
Average
112 mA
Looks
83 %
170 of 255: the LED is on for 67 % of each cycle, at the full 168 mA, so it averages 112 mA. It looks about 83 % as bright as full on, not 67 %: your eye is kinder to dim light than the arithmetic.

Every pulse is the full current VCC gives, about 168 mA from 5 V. What the duty changes is how long each pulse lasts, and so the average: at 170 of 255 the LED is on two thirds of the time and averages about 112 mA. The heat in the LED and its resistor follows the average, which is why capping the duty keeps them inside their ratings.

The cycle is short: 490 times a second on an Uno's D9 and D10, 1000 times on an ESP32 or a Pico. Your eye sees the average.

A fade that looks even

The eye answers roughly to the logarithm of light, not the light itself. So the step from 0 to 17 of 255 looks like a big jump, and the step from 153 to 170 looks like nothing.

A fade that looks even
Duty steps
First step looks
35 %
Last step adds
5 %
Power
2.2
Equal steps of duty: the first step, 17 of 255, already looks 35 % bright, and the last one adds only 5 %. The fade seems to jump on and then stall.

The fix is to choose the duty for each step through a power of about 2.2: tiny duties for the first steps, big ones at the end. The fade sketch does exactly that, and the MicroPython version too. 2.2 is a rule of thumb from LED fading code, not a measurement of these LEDs or of your eye; try 2 or 2.5 if the fade looks uneven to you.

The same correction applies to the mix: dim both colours by the same corrected step and the colour holds while the brightness falls.

The code

Fades the warm LED up and down in 100 steps, then the cool one, each step sent through a 2.2 power so the fade looks even. Change WARM_PIN and COOL_PIN to the pins you wired.

dual_leds_fade.ino
/*
  Dual Bright LEDs - a fade that looks even              TK95 / /p/tk95

  Wiring. Count from the square pad on the TinkerBlock board, parts
  up, header at the bottom:

    GND    -> GND
    VCC    -> 5V (VBUS on a Pico). It only feeds the LEDs, so 5V is
              safe on a 3.3 V board. Never the Uno's 3.3V pin.
    3000K  -> D9, GPIO 25, GPIO 4 or GP14   (a PWM pin)
    6500K  -> D10, GPIO 26, GPIO 5 or GP15  (a PWM pin)

  Uno, ESP32, ESP32-S3, Pico, in that order.

  Arduino IDE
    Tools > Board                 your board, e.g. ESP32S3 Dev Module
    Tools > Port                  the one that appears when you plug in
    Tools > USB CDC On Boot       Enabled   (ESP32-S3 only)
    No library needed.
    Serial Monitor                115200
*/

// The pins 3000K and 6500K are wired to.
// Uno: 9. ESP32: 25. ESP32-S3: 4. Pico: 14.
const int WARM_PIN = 4;
// Uno: 10. ESP32: 26. ESP32-S3: 5. Pico: 15.
const int COOL_PIN = 5;

// Two thirds of full: the cap for VCC on 5V.
const int MAX_DUTY = 170;
const int STEPS = 100;

// Step 0..STEPS to a duty, through a 2.2 power: the eye answers
// roughly to the logarithm of light, so equal steps of duty do not
// look equal. 2.2 is the usual rule of thumb.
int dutyFor(int step) {
  return lround(pow((float)step / STEPS, 2.2) * MAX_DUTY);
}

void fade(int pin, const char *name) {
  Serial.print(name);
  Serial.println(": up");
  for (int s = 0; s <= STEPS; s++) {
    analogWrite(pin, dutyFor(s));
    delay(20);
  }
  Serial.print(name);
  Serial.println(": down");
  for (int s = STEPS; s >= 0; s--) {
    analogWrite(pin, dutyFor(s));
    delay(20);
  }
}

void setup() {
  // LOW first: nothing on the board holds the transistors off.
  pinMode(WARM_PIN, OUTPUT);
  digitalWrite(WARM_PIN, LOW);
  pinMode(COOL_PIN, OUTPUT);
  digitalWrite(COOL_PIN, LOW);
  Serial.begin(115200);
}

void loop() {
  fade(WARM_PIN, "3000K");
  fade(COOL_PIN, "6500K");
}

The top of the fade is MAX_DUTY, 170 of 255, the cap for VCC on 5V. The sketch compiles for an ESP32-S3, an ESP32 and an Uno; pow() and lround() are in every Arduino core.

View on GitHub · blocks/tk95-dual-bright-leds/arduino/dual_leds_fade/dual_leds_fade.ino @ v1.8

When it does not work

The fade jumps on and then barely changes.

That is a straight fade: equal steps of duty. The first few steps already look bright and the last hundred look the same. Send each step through a power of about 2.2, as the fade sketch does, and it climbs evenly.

The LEDs flicker on my phone's camera.

PWM switches them 490 times a second on an Uno's D9 and D10 and 1000 times on an ESP32 or a Pico. Your eye blends that; a camera's rolling shutter shows it as bands. Full on removes it; on an ESP32 a higher PWM frequency, set with analogWriteFrequency, can too.

The lowest steps look like off.

At 1 or 2 of 255 the LED is on for under one percent of each cycle. It is still lit, very faintly, in a dark room. With the 2.2 correction the fade spends several steps there.

Alongside this page
Where this goes next

The block's current against each board's 5V and 3.3 V pins.

What your supply must give →

Edit this page — content/books/dual-bright-leds/dimming-with-pwm.mdx

Community

Questions about this product

See what other owners have asked, and read their solutions.

Ask a question ↗

Dual Bright LEDs

Loading discussions…

Discuss this article

Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.

Browse Modules and blocks on the forum →