Go Down

Topic: Low power LED driver with patterns (fade in/out, breathe, blink) (Read 377 times) previous topic - next topic

iadev

Hello everyone ! This is my first post so apologies if I did anything wrong.

I'm looking for:
  • an "easy to use" LED driver with simple programmable patterns such as fade in and out, breathing, blinking.
  • The objective is to be able to drive for example an RGB LED at low power, low voltage (less than 50mA, less 3V).


If it is a complete integrated package (LED + Driver), that's fine with me (best, TBO) !

  • So far I've stumbled upon the IS31FL3194 from ISSI or the AS3668 from AMS.
  • About these references: these are incredibly small. Soldering it with my poor equipment would be a massacre. That's why having it on a breakout board or as an integrated package with some LEDs would be nice for my usage.



I'd like to know if you could share your knowledge with me ! (no particular background in electronics, just a noob hobbyist).

Here's what I want to do with it: to have a low-power LED indicator (just like what you can find on some Android phones for notifications) able to be run on batteries (so effects are run automatically without the need to wake any power-hungry processing unit).

Thanks !


PaulRB

Maybe you could use an attiny45/85. Program it to produce the various effects you want in response to a command sent via serial. The tiny can run at 1MHz and spend most of its time in a light sleep mode. It will probably need less than 0.5mA for itself.

It will be a problem to run the circuit at less than 3V, because the green and blue LEDs in the RGB led will have forward voltages of perhaps 3.2V, plus you need to use series resistors, which will need 0.5~1V on top of that, so supply voltage will need to be 4V+.

iadev

Maybe you could use an attiny45/85. Program it to produce the various effects you want in response to a command sent via serial. The tiny can run at 1MHz and spend most of its time in a light sleep mode. It will probably need less than 0.5mA for itself.
Thanks for the hint. Actually that is my initial setup (T85), naively thinking "he who can do more can do less".
I'm investigating in parallel the IDLE sleep mode, but I have troubles with the TIMER0 waking up the ATTINY.
Since I have trouble estimating the power gains I could get (and troubles so far with power management), I was thinking lately that maybe a dedicated driver could be more power-efficient (and less trouble-prone).
But your indication that I could reach less than 0.5mA gives me hope.


I'll follow your advice and continue investigating this way then !

It will be a problem to run the circuit at less than 3V, because the green and blue LEDs in the RGB led will have forward voltages of perhaps 3.2V, plus you need to use series resistors, which will need 0.5~1V on top of that, so supply voltage will need to be 4V+.
Noted, thanks! That's another problem to handle  :smiley-lol:

PaulRB

Why do you want to use TIMER0? Can't you just use millis()? That still works ok with IDLE sleep mode, I seem to remember. Your code puts the tiny to sleep and it wakes next time millis() gets updated.

iadev

Why do you want to use TIMER0? Can't you just use millis()? That still works ok with IDLE sleep mode, I seem to remember. Your code puts the tiny to sleep and it wakes next time millis() gets updated.
Thanks for the reply.

You're right, millis() still runs when IDLE. That's good for me to be able to keep track of animations.

However I'm not "voluntarily" using TIMER0 at all, and I actually don't want to use it.
It's just that from what I understood the TIMER0 overflow interrupts are waking the ATTINY up from IDLE, although I do not want that. Indeed, for smooth fading effects I just need to update the PWM duty cycle every 20 to 30ms or so, so an order of magnitude higher than what I automatically get from the TIMER0 interrupts.

Do you confirm that I get a TIMER0 interrupt everytime millis() gets updated, so exactly every millisecond ?

My solution for now to counter this is to use the watchdog at around 30ms period, and set a flag in the watchdog ISR.
Then at the beginning of the loop() I go back immediately to IDLE sleep if the flag has not been set (indeed meaning that I have been woken up by TIMER0 and not by the watchdog). Hopefully saving up some cycles.
There might be a better way, but I'm not proficient enough to mess with TIMER0 yet.

Right now I'm using the ATTINY on a Digispark clone board (easy to learn from there), so I don't have a way to measure the ATTINY current draw without measuring the draw from the USB + regulator stuff.
I ordered some parts to make a "bare" board and make some tests, and report back here the results !


PaulRB

Here is my code for you to have a look at. It's part of a weather station, and monitors the anemometer, windvane and rain sensor by polling digital inputs every 1ms.

iadev

Thanks for your code ! That's nice to see that you can reach 0.5mA simply sleeping at the end of every loop, without tinkering with anything.

Here's your (pseudo)code in essence:
Code: [Select]

//<?php hack for syntax coloring

void setup
() {
  
//... various power-savings
  
set_sleep_mode(SLEEP_MODE_IDLE);
}

void loop() {
  
//... do something
  
sleep_mode(); // go to sleep until next 1ms timer interrupt
}


I cannot find the exact documentation of when exactly we are waking up from IDLE (due to TIMER0 / millis()), and I don't have the equipment to measure it. Is that exactly at 1ms ?

Anyways I want to save as much power as possible, and since I cannot find any reference to know exactly when we are waking up from IDLE, I'm using the watchdog timer that I can configure easily (if I knew exactly when TIMER0 triggers an interrupt, I could simply use a counter).
Here's my pseudo-code
Code: [Select]

//<?php hack for syntax coloring

volatile bool is_waking_from_wdt 
false// whether we are waking up from the watchdog.

/** The Watchdog ISR  */
ISR(WDT_vect) {
  
is_waking_from_wdt  true;
}

void setup() {
  
//... various power-savings
  
set_sleep_mode(SLEEP_MODE_IDLE);
  
SETUP_WATCHDOG(); // custom routine to start the watchdog at the desired period
}

void loop() {
  if (!
is_waking_from_wdt) { // we are here because of an interrupt other than the watchdog 
    
sleep_mode(); // go back to sleep immediately
    
return; // exit the loop when waking up to check again the source
  
}
  
is_waking_from_wdt  true// clear the flag
  //... do something
  
sleep_mode(); // go to sleep until next interrupt
}


Any reference somewhere to have a confirmation of when exactly we are waking up from IDLE due to TIMER0 / millis() interrupts ?

iadev

Maybe you could use an attiny45/85. Program it to produce the various effects you want in response to a command sent via serial. The tiny can run at 1MHz and spend most of its time in a light sleep mode. It will probably need less than 0.5mA for itself.
So I tested the current draw @3.3V when putting the device to IDLE at the end of each loop() (see code at the end of the post).
I'm getting 0.4mA (vs 0.8mA when not sleeping) @1MHz, so similar to what you stated.
(Note: you can go down to 0.3mA using power_timer1_disable())

However, regarding how often we get interrupts (TIMER0) to put us out of IDLE:
I measured (using a blinking led every X cycles) that I get around 16ms between each interrupts @1MHz.
This seems to be consistent with a prescaler of 64 and 8-bit timer (10000/1.e6*64*256 = 16.4ms).
So much longer period than the expected 1ms.

Did you change the prescaler manually to get 1ms ? (I don't know much about that)

I'm using this board manager.



Code: [Select]

#include <avr/sleep.h>
#include <avr/power.h>

void setup() {
  // power-savings
  ADCSRA = 0;            // FIRST disable ADC
  power_adc_disable();
  //power_timer1_disable(); // can spare an additional 0.1mA
  set_sleep_mode(SLEEP_MODE_IDLE);
}

void loop() {
  // [...] do something
  sleep_mode(); // go to sleep until next interrupt
}

PaulRB

No, I'm not setting any pre-scalers, my code is as you see it in that link (and the tiny core I am using is noted in the comments).

I have to say, I have not measured the period between interrupts as you have. I have to admit I have been making the assumption that the interrupts are every 1ms, because millis() appears to work normally, and that relies on a 1ms interrupt, as I understand.

Perhaps the differences can be due to the different cores we are using. DrAzzy created the core I used, and he is a regular poster on the forum, perhaps he can help explain.

iadev

Hello PaulRB,

Ok thanks I'll try to learn a bit more about prescalers, fuses and so on.

I have to say, I have not measured the period between interrupts as you have. I have to admit I have been making the assumption that the interrupts are every 1ms, because millis() appears to work normally, and that relies on a 1ms interrupt, as I understand.
Regarding millis() it is working fine in my case too (although I get an overflow interrupt every 16ms or so).
I guess that while millis() probably relies on this interrupt, it probably accounts for prescaler when counting.
Probably though (if my understanding is correct) I get a 16ms resolution for millis() in my case.

If I find the time I will try the core you used.

Go Up