TLC5940 sometimes goes full instead of blank

(Not sure if this should go in Programming Questions. Feel free to move it.)

I have a binary clock. I’m using a TLC to control the LEDs, and Alex Leone’s Tlc5940 library. The µC is a standalone 328p (internal clock) reading from a DS1337, which is itself clocked by a TCXO. (Gotta love free samples.)

There are two ways to affect the LEDs. One is a pot:

brightLevel = analogRead(potPin) / 4; //convert for gamma correct (included in library)
Tlc.setGC(9, brightLevel);} else {Tlc.setGC(9,0); //for example

The other is a pin-change interrupt that actually sleeps the chip:

 ISR(PCINT2_vect) {
   if (justSlept == 1) {} //don't mess things up if we're in the middle of/just woke up from sleeping
   else {lightsOn = 0;}  //otherwise, set the sleep-now flag
 }
 
 void sleepNow() {
   justSlept = 1;
   Tlc.clear();
   Tlc.update();
   digitalWrite(10, HIGH); //this is redundant (there is a pull-up from BLANK to Vcc)
   ADCSRA |= (0<<ADEN); //turn of ADC to reduce power consumption
   sleep_enable();
   delay(150);       //debounce; otherwise we wake immediately after sleep
   sei();
   MCUCR = _BV (BODS) | _BV (BODSE);  // turn on brown-out enable select
   MCUCR = _BV (BODS);        // this must be done within 4 clock cycles of above
   sleep_cpu ();              // sleep within 3 clock cycles of above
   sleep_disable();          //now we've just woken up (button push triggers PCint)
   lightsOn = 1;              //set sleep-now flag OFF
   lastToggle = millis();
   ADCSRA |= (1<<ADEN); //turn ADC back on
 }

void loop() {
  int timeSinceToggle = millis() - lastToggle;
  if (timeSinceToggle >= 200) justSlept = 0; //debounce
  
  if (lightsOn == 0 || timeSinceToggle >= 1800000) sleepNow(); //if the sleep-now flag is set, or if it's been half an hour, sleep
  else {
  //here the code reads the pot, gets time from the RTC, sets the LEDs, sees if the time is being set externally...
  }

The code works beautifully, for the most part. Current into the 7805 drops from ~120mA (pot all the way down) to ~40mA asleep. However, whenever the pot is at full-on, the LEDs go full-bright instead of full-off when the 328p sleeps. (Not all of them, just the ones lit at the time.) Current drops, but certainly not all the way (as the lights are still on). The pot doesn’t affect the brightness, and setting the time doesn’t do anything—again, because the 328p is asleep. But it must still be driving BLANK low, or something. The problem doesn’t go away until I turn the pot down, at which point the lights turn off as expected.
This sometimes happens when the pot isn’t at full, too. Instead of going off, the LEDs go full-bright. But it doesn’t happen as often.

Any ideas what might be causing this?

The TLC5940 gets it's PWM clock signal from the Arduino (or the ATmega in your case). If you put the processor to sleep the clock signal stops which probably leave the TLC in the state it had at that time.

If you want the TLC output to turn off while the ATmega is asleep, I'd try to use a pullup for the BLANK pin. This way as soon as the ATmega turns it's outputs off, the line goes HIGH and the TLC output turn off.

randomizer: the 328p is asleep. But it must still be driving BLANK low, or something.

The TLC5940 receives its clock signal from the 328p.

If you stop sending clock pulses the PWM from the TLC5940 will freeze in whatever state it was in when you stopped sending the signal.

randomizer: This sometimes happens when the pot isn't at full, too. Instead of going off, the LEDs go full-bright. But it doesn't happen as often.

At low brightness there's more chance of the LEDs being off (they spend more time 'off' in the PWM cycle).

fungus: The TLC5940 receives its clock signal from the 328p. [...] At low brightness there's more chance of the LEDs being off (they spend more time 'off' in the PWM cycle).

Makes perfect sense! Except... 1) I'm clearing and updating the TLC before sleep, 2) I'm writing BLANK (pin 10) HIGH before sleep, and 3) there's a pull-up from BLANK to Vcc.

On a whim, I tried writing D11 HIGH on sleep instead of D10. Instead of never working, it now works every fourth try. Writing D9 high instead makes it sporadically work for a few pushes in a row. (I have it wired up exactly as shown in the "Basic Use" example from the Tlc5940 library.)

Writing D3 (GSCLK) HIGH before sleep (and the re-calling Tlc.init() after wake) makes it work great... but if the pot's not at full, it occasionally blinks instead of being steady when it wakes.

randomizer:

fungus: The TLC5940 receives its clock signal from the 328p. [...] At low brightness there's more chance of the LEDs being off (they spend more time 'off' in the PWM cycle).

Makes perfect sense! Except... 1) I'm clearing and updating the TLC before sleep, 2) I'm writing BLANK (pin 10) HIGH before sleep, and 3) there's a pull-up from BLANK to Vcc.

The BLANK pin is being controlled by a hardware timer so all that may be irrelevant. Try setting the blank pin as an INPUT.

fungus: Try setting the blank pin as an INPUT.

But I'm still calling Tlc.clear() and Tlc.update(), right? So it shouldn't matter. Whatever. I did that, and it was the same as before: issue fixed when the pot is at full, but at anything else the lights flickered every fourth wake. Sometimes it flickered even when the pot was full. It stopped flickering when I moved my hand near the GSCLK pin, so I figured it wasn't initializing correctly (though why every fourth time, I don't know). So I cleared TCCR1A and TCCR1B before calling Tlc.init() after wake, and everything is fine now! It looks like several of my issues along the way might have been caused by a corrupted TIMER1. Maybe.

It also works if I keep D10 as an output and write it HIGH, but just to be safe I'll make it an input as you suggested.

Thanks for helping me.

randomizer: I cleared TCCR1A and TCCR1B before calling Tlc.init() after wake, and everything is fine now!

Weird. Tlc.init() sets both those registers. Setting those registers before going to sleep might be a good idea (also timer2 which is used for the GSCLK signal).

randomizer: It also works if I keep D10 as an output and write it HIGH, but just to be safe I'll make it an input as you suggested.

I suggested that because you said you added an external pullup. Maybe your pullup isn't strong enough.