LED fade malfunction (random flash)

Hi,

When I try to fade an LED using the following code, a random flash sometimes happens at the beginning of the cycle, as if the LED has been set to 255 instead of 1:

Original Code:

int led = 11;
int brightness = 0;

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


void loop()  
{ 
  if(brightness >= 256) //checks if brightness has passed 255, resets to 0
  {
    analogWrite(led, 255);
    brightness = 0; 
  }
  
  analogWrite(led, brightness);
  if(brightness == 0)
    {
      delay(1000); //LED off for 1 second
    }
    
  brightness+=1; //increment brightness
  delay(20);
}

This only happens occasionally - (watch a video of it on YouTube)

However when the brightness variable is continually incrementing and is not reset to zero, this does not happen - analogWrite() "overflows" back to zero when it reaches 256 or when it is given any multiple of 256. ie. analogWrite(led, 1024) sets led to 0.

Modified Code:

int brightness = 0;
int led = 11;

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

void loop()
{
  analogWrite(led,brightness);
  if((brightness ==  0) || ((brightness%256) == 0)) //if brightness is at zero or is 
  {                                                 // a multiple of 256, delay of 1 second
    delay(1000);
  }
  else
  {
    delay(20); // otherwise, short delay to slow down fade rate
  }
  brightness++; //increment brightness
}

I have also tried:

  • Changing board (to another UNO)
  • Changing LED pin from 11 to 3
  • Using a 12V power supply instead of USB (sometimes USB supply causes malfunctions)

So I am assuming that this is not a hardware problem, but a software problem. Any ideas why resetting brightness to zero would cause a flash to sometimes occur?

I think that the glitch is in the code for analogWrite(). You can find that code in ../hardware/arduino/cores/avr/arduino/wiring_analog.c.

When the value is 0 or 255, the code simply digitalWrite()s a HIGH or LOW to the pin, as appropriate, to get steady high or low voltage at the pin. digitalWrite() turns off PWM by default - you can see its code in wiring_digital.c. But, it doesn't write anything to the output compare registers - it leaves them as it found them. After the delay elapses and the sketch analogWrite()s a value of 1, analogWrite() sets the output compare function bits to allow the pin to be controlled by the compare match unit. But, the output compare register is double-buffered, since the timer is initialized to mode 1, a PWM mode. The value of the output compare register doesn't change until the next time the timer rolls over. The value of the output compare register will still be at the last value written - 255 - and it will stay there until the timer rolls over. Depending on where in the timer cycle the output compare unit is reenabled, the output might be high for a larger or smaller part of the timer cycle. You can read about double-buffered registers and Timer2 operating modes in the ATMega328P datasheet.

That would explain why there would be a flash, of varying apparent intensity, with the first sketch.

In the second sketch, the value of brightness is sent to analogWrite() without adjustment. When it gets to 256, that value is sent to the function. The second argument to analogWrite() is type int, so analogWrite() tests it, determines that it's not zero, and writes it to the output compare register. Because the output compare register is a byte, the value written is a zero, but that's not detected by analogWrite(). analogWrite() doesn't see a zero, so it does a bona fide analogWrite() with a value that's effectively zero, and there's no flash. When brightness rolls over from 0xFFFF to 0, I think that the next analogWrite(), with a value of 1, might generate a flash. But, that will only happen for one fade cycle out of every 256 - about every 25 minutes or so - and it's unlikely that you'd notice it, or even still be paying attention to the LED, when it happens.

The datasheet mentions that writing a 0 to the output compare register will result in short spikes on the output as the timer rolls over, due, I believe, to a race condition between the circuitry that sets the output on rollover and the compare match detection circuitry. So, there may be good reasons to use digitalWrite(), instead of a genuine analogWrite(), to set the pin voltage at the extremes. [The second argument to analogWrite() is type int in order to allow for serving Timer1, on the Uno at least, which has 16-bit output compare registers, and a 16-bit counter.] Edit: add this: That may be wrong. analogWrite() digitalWrite()s a HIGH when the value is 255, without regard to which timer is being served. So, it looks like analogWrite() can be relied on only for eight-bit timer modes.

A possible fix for analogWrite() might be to have it write the appropriate value, 0 or 255, to the output compare register, while still calling digitalWrite() to set the voltage on the pin. Then, when the output compare unit is reenabled, the value that's already in the register will be consistent with the last call to analogWrite().

Man! Thank you so much for the detailed and comprehensive response! I have been pondering over this for hours and I had actually started looking through the various header and source files. It is such a simple piece of code and I couldn't figure out why it would happen.

Still getting my head around it but I can basically understand the root of the problem. I shall do some more research on double-buffered registers etc. in the ATMega328 data sheet and the wiring_analog.c file.

Thanks again, :smiley: