Low-Power LED Lighting Applications: A Power-Management Technique

By Ezana Haile, Principal Applications Engineer, Analog & Interface Products Division

Microchip Technology Inc.

 

The balance of LED luminous versus electrical power is an essential specification when determining the quality of visible light when a set of LEDs is controlled in a lighting application. Illuminating an entire set of LEDs simultaneously to achieve the maximum luminous level may not be possible in low-power applications, due to the limited current source. To produce optimum luminosity the power dissipation per LED must be managed efficiently, from the low-power source. This requires a power-management technique where, within the set, only one bank of LEDs is powered for a given time. The alternating banks of LEDs will remain visually undetected as the required luminous intensity is achieved.

 

The available power and the luminous intensity required for the application must be established in order to determine the number of LEDs for a given time interval. A comparison between luminous intensity and forward-current characteristics must be made to select LEDs with the required intensity level. Once the number of LEDs required has been established, that number can be powered at a given time interval is determined by taking the ratio of the total current required for the LEDs and available current, as follows:

 

            # of LEDs in a bank = Total Required Current for LEDs / Total Available Source Current

 

The frequency at which the bank of LEDs is turned ON/OFF must be tuned so that the ON time is long enough for full illumination of a bank, and the OFF time is limited by the time it takes before the bank starts to visibly dim. The OFF time limits the number of additional LED banks that can be controlled via time-interval management.

 

This technique can be implemented at low cost with a clock source, digital flip-flops to control banks of LEDs, and an OR gate to detect a start condition with a simple On/Off switch. Figure 1 shows a block diagram of a D flip-flop configuration to control four banks of LEDs.

 

 

Figure 1: LED lighting time-interval using a flip-flop circuit

 

To start, the flip-flop is in a no-change state and requires a start pulse, which must be at least once one clock cycle, so that it can be detected by the first flip-flop at the rising edge of the clock. The duration of the start signal must be momentary and cannot be longer than one clock cycle, otherwise the first two flip-flop outputs will be set at the same time and since the source current is limited, the lighting application will not function properly. A bank of LEDs is fully illuminated at the rising edge of every clock with this configuration and to the human eye it appears as though all LEDs are fully turned on, simultaneously.

 

This implementation is monotonic, however, and does not provide design flexibility. It only has an on or off state. This circuit may be adequate for applications such as LCD backlights, but if dimming or pattern generation is needed, a microcontroller (MCU)-based circuit provides the greatest flexibility with minimum impact to the total cost of the solution. The circuit is simpler to build, with fewer components as the MCU controls each bank of LEDs, and can also detect user inputs for dim control and pattern selection.

 

A low-cost and low pin count 8-bit microcontroller, such as Microchip’s PIC10F or PIC12F family, with an I/O port expander such as Microchip’s MCP23018 would deliver a cost-effective implementation. I/O expanders can also be useful for driving LEDs, when the lighting circuit is remotely located with respect to the MCU.

 

I/O port expanders expand the I/O ports of a microcontroller. In this application, the MCU controls the I/O expander ports via the I2C™ protocol to drive the LEDs on or off, and the MCU’s I/O pins can be used to detect user inputs via a push-button switch, or with the built-in analog to digital converter to detect a potentiometer level for dim control.

I/O expanders are available with open-drain or push-pull output configurations. Microcontrollers can operate at 3.3V or lower, so an open-drain-output I/O expander lends itself well to this application. The advantage is that it permits the LEDs to operate at 5V or higher while the microcontroller and the I/O expander are powered at a lower voltage. The MCP23018 is a 16-bit I/O port expander with an open-drain output and an I2C interface. Figure 2 shows a circuit diagram for an open-drain-output I/O expander pulled up to a higher voltage than the MCU’s supply voltage.

 

 

Figure 2: Low-cost, microcontroller-based lighting solution using I/O port expander

 

When the I/O port is set as low, then the voltage at the I/O expander port is 0V and current flows, which forward biases and turns on the LED. The LED-biasing resistor, limits the current to the LED for the required luminous intensity and also functions as a pull-up resistor for the open-drain output.

 

When the I/O-expander output port is set as high, the open-drain output is off or high impedance and the voltage at the I/O expander port is pulled up to 5V by the pull-up resistor. The LED is in an off state because current will not flow. An open-drain-output configuration also offers the advantage that when the port is configured as high impedance, it takes longer for the LED to turn off due parasitic capacitance. This enables the next bank of LEDs to turn on for slightly longer duration, compared to similar applications with push-pull output.

The MCP23018’s 16 I/O ports can drive up to 16 LEDs. The amount of current that can be sunk into the I/O port when the LED is fully turned on can be limited by the I/O expander’s output drive. The I/O port’s low-level voltage is specified for 0.6V maximum at 8.5 mA of current. If the current is higher than 8.5 mA, the low-level voltage will exponentially increase. The absolute maximum current is 25 mA.

If the source current is limited to 5V/50 mA, for example, and 2 mA is budgeted for the microcontroller, the I/O expander and the resistors for user-input detection, the rest of the available current can be dedicated for LED lighting. Four LEDs can be controlled per bank if the luminous intensity of the LED at approximately 10 mA is adequate. In this example the current-limiting resistor value will be approximately 440W.

The timing shown in Figure 1 can be replicated using a short MCU instruction code. For example, referring to the Pseudo Code 1, the main subroutine can be an infinite while-loop. At the MCU timer intervals (Timer0) an interrupt service routine sends an I2C command to the I/O expander to turn on only one bank of LEDs. The interrupt service routine counts or keeps track of the LED bank status, such as the bank that is currently turned on, and the corresponding I/O expander port configuration as high or low. Initially, or after the MCU resets, on the first Timer0 interrupt, bank 1 is turned on. And, on the second interrupt, bank 1 is off and bank 2 is on. Then, on the third interrupt, bank 2 is off and bank 3 is on. Finally, on the fourth interrupt, bank 3 is off and bank 4 is on, and the bank counter variable is cleared. On the next interrupt, the cycle repeats by turning on bank 1 and turning off bank 4. Other variables, such as Command_byte, Address_pointer_bank1n2 and Address_pointer_bank3n4 are constants defined in the header file. With such an implementation, the circuit outputs the required luminous intensity, and it appears that all LEDs are turned on simultaneously from the available power source.

 

void main (Void){

initialize();    //initialize the PICmicro peripherals including Timer0, and I/O expander peripherals

bank_counter = 0;    //Clear the bank counter variable

while(1){}    //infinite while loop

}

Void Interrupt int_service(void){

            TurnOffAll_LEDs();        //subroutine to turn off all previously lit LEDs

            I2C_start();    //I2C protocol – start signal subroutine

            I2C_send(Comand_byte);  //I2C protocol – send byte subroutine

 

If(bank_counter == bank1){          // bank1 is a constant defined as 0

            I2C_send(Address_pionter_bank1n2);  //Send peripheral register address pointer

            I2C_send(‘0000 1111’);  //I2C protocol – send bank status (bank 1 on and bank 2 off)

            bank_counter = bank_counter + 1;

}

If(bank_counter == bank2){      // bank1 is a constant defined as 1

            I2C_send(Address_pionter_bank1n2);  //Send peripheral register address pointer

            I2C_send(‘1111 0000’);  //I2C protocol – send bank status (bank 2 on and bank 1 off)

            bank_counter = bank_counter + 1;

}

If(bank_counter == bank3){     // bank1 is a constant defined as 2

            I2C_send(Address_pionter_bank3n4);  //Send peripheral register address pointer

            I2C_send(‘0000 1111’);  //I2C protocol – send bank status (bank 3 on and bank 4 off)

            bank_counter = bank_counter + 1;

}

If(bank_counter == bank4){     // bank1 is a constant defined as 3

            I2C_send(Address_pionter_bank3n4);  //Send peripheral register address pointer

            I2C_send(‘1111 0000’);  //I2C protocol – send bank status (bank 4 on and bank 3 off)

            bank_counter = 0;

}

I2C_stop();    //I2C protocol – stop signal subroutine

}

Pseudo Code 1: Microcontroller interrupt service routine code flow

 

Dim Control and LED Pattern Generation

Interesting lighting patterns can be easily generated with a microcontroller-based application, The I/O port expander’s output states are loaded from 2 bytes of RAM variables, labeled as Bank1n2_pattern and Bank3n4_pattern. The nibble of each byte corresponds to each bank. A predefined look-up table contains various patterns of 1s and 0s for each bank.

To continuously monitor the push-button switch on/off state, an IF THEN statement is added to the MCU code–the main subroutine’s infinite while-loop. When the push-button is momentarily pressed, a pattern from the look-up table is loaded in the two bytes of RAM, labeled as Bank1n2_pattern and Bank3n4_pattern. When the Timer0 interrupt occurs, the new pattern is sent to the I/O expander and the LEDs are lit according to the pattern. The main subroutine cycles through the look-up table, as the push-button switch continues to be momentarily pressed by the user. To display an alternating light, simply send the complement of the previous pattern. For example, if the bank1 pattern is ‘0101’ then the complement is ‘1010,’ as shown in Pseudo Code 2, which is a snippet from Pseudo Code 1.

 

*

*

*

If(bank_counter == bank1){        // bank1 is a constant defined as 0

            I2C_send(Address_pionter_bank1n2);  //Send peripheral register address pointer

            Bank1n2_pattern = ~Bank1n2_pattern; //complement the variable using ‘~’

            I2C_send((Bank1n2_pattern | ‘0000 1111’));  //I2C protocol – send bank status

                                                                        //(use OR ‘|’ to set bank 1 on and bank 2 off)

            bank_counter = bank_counter + 1;

}

*

*

*

Pseudo Code 2: Pattern generation

However, a delay must be added in the interrupt subroutine, before the bank on/off IF THEN statements to visually inspect the alternating patterns. This is created with a Delay_ON flag, so that on the following interrupt, only a delay counter is decremented to count down the number of interrupts for delay. The delay value can also be user-selected using a potentiometer[JRC1] , where the centre tab is connected to the MCU’s on-chip analog to digital converter (ADC) input. The ADC’s digital data is scaled from minimum to maximum delay by detecting the top four bits, which provide 16 levels. A finer ratio can be set by detecting the top five bits, or 32 levels. The maximum delay is the slowest the LEDs can blink and, with minimum delay, it appears that all LEDs are fully turned on. The timing diagram in Figure 3 shows the delay position, tDELAY.

*

*

*

Void Interrupt int_service(void){

            TurnOffAll_LEDs();        //subroutine to turn off all previously lit LEDs

            If (Delay_ON == ON){    // ON is a constant defined as 1

                        Update_Timer0_Counter(Timer0delay_interval);        //subroutine to update Timer0

                                                            // Timer0delay_interval is a constant to set the minimum delay

                        Delay_Counter = Delay_Counter – 1;            //count down the number of interrupts for delay

                        If(Delay_Counter == 0){

                                    Delay_ON = OFF;    // Clear the delay flag for the next interrupt

                                    Delay_Counter  = Get_Delay_Counter();        //subroutine to detect user input

                                                            //and set the delay counter variable

                        }else{

                                    Delay_ON = ON; // leave delay flag ON

                                    Return;            //exit the interrupt service routine

                        }

            }

 

            I2C_start();    //I2C protocol start signal subroutine

            I2C_send(Comand_byte);  //I2C protocol – send byte subroutine

If(bank_counter == bank1){        // bank1 is a constant defined as 0

            I2C_send(Address_pionter_bank1n2);  //Send peripheral register address pointer

            Bank1n2_pattern = ~Bank1n2_pattern; //complement the variable using ‘~’

            I2C_send((Bank1n2_pattern | ‘0000 1111’));  //I2C protocol – send bank status

                                                                        //(use OR ‘|’ to set bank 1 on and bank 2 off)

            bank_counter = bank_counter + 1;

                        Delay_ON = ON; // turn on the delay flag

}

*

*

*

Pseudo Code 3:  delay code implementation to view the lighting pattern

To control the duration for the time-interval of each bank, the dim control uses pulse width modulation (PWM). The Timer0 interrupt duration has two values, one for high duration and another one for low duration, proportional to the PWM percent ratio, set using a thumb-wheel potentiometer; the centre tab is connected to the ADC input. The resolution level can be adjusted by selecting the top 4 or 5 bits from the ADC. The Timer0 counter position is adjusted ratiometrically, with the scaled ADC data, where the 100% PWM is equal to the maximum counter position, fully lit, and 0% minimum or lowest dim level. Pseudo Code 4 shows the PWM scaling equation for 16 levels, and Figure 3 shows the timing diagram for the PWM duration, tPWM_LOW and tPWM_HIGH.

 

Void get_PWM_ratio(void) {

            Double PotScale;        //local variable to store ADC output scale

            Double PWM_Percentage;     //local variable to store PWM change percentage

 

PotScale = (ADRESH)/16 + 1;           //Scale ADC output high byte

PWM_Percentage = 1/16 * PotScale; //Scale the output from from 0 to 1, equivalent to 100%

PWM_High = Frequency_counter * PWM_Percentage;        //set PWM high Timer0 value

PWM_Low = Frequency_counter * (1 – PWM_Percentage);            //set PWM low Timer0 value

                        //PWM_High and PWM_Low are global variables,

                        //and Frequency_counter is a constant Timer0 value to set frequency.

}

 

Pseudo Code 4:  Subroutine to calculate PWM ratio

 

 

Figure 3: Timing diagram for PWM output and timing delay

 

The interrupt service routine must update the Timer0 counter positions for the next interrupt. It also has to detect whether the duration is for PWM high or low time. Therefore, a few instructions must be added to detect the potentiometer level for dim control and ratiometrically scale the Timer0 counter position with the PWM_High and PWM_low values shown in Pseudo code 4. Pseudo Code 5 shows the code for adjusting the PWM; an IF THEN statement is used to detect the PWM state.

 

*

*

*

If(bank_counter == bank1){   // bank1 is a constant defined as 0

            If (PWM_High_Low_flag == OFF){   //check PWM status flag

                        get_PWM_ratio();       //Detect user input for PWM ratio

                                    Timer0_counter = 65535 – PWM_High;  //set Timer0 counter variable                                                         Update_Timer0_Counter(Timer0_counter);        //subroutine to update Timer0

                                                         

            I2C_send(Address_pionter_bank1n2);  //Send peripheral register address pointer

            Bank1n2_pattern = ~Bank1n2_pattern; //complement the variable using ‘~’

            I2C_send((Bank1n2_pattern | ‘0000 1111’));  //I2C protocol – send bank status

                                                            //(use OR ‘|’ to set bank 1 on and bank 2 off)

                        PWM_High_Low_flag = ON;   //Set flag

                        Delay_ON = OFF;

 

}else{

                        Timer0_counter = 65535 – PWM_Low;  //set Timer0 counter variable

                                    Update_Timer0_Counter(Timer0_counter);        //subroutine to update Timer0

                                                                                  

            I2C_send(Address_pionter_bank1n2);  //Send peripheral register address pointer

            I2C_send(‘1111 1111’));  //I2C protocol – turnoff all LEDs

                        bank_counter = bank_counter + 1;

                        PWM_High_Low_flag = OFF;  //Clear flag

                        Delay_ON = ON;

}

}

*

*

*

Pseudo Code 5:  Code flow for PWM ratio setting

 

This methodology can also be implemented on a mid-range 8-bit microcontroller with additional program memory, such as Microchip’s PIC16F family. An upgrade would enable the main subroutine to handle sophisticated lighting patterns, such as chasing lights. The MCU’s Timer1 module can be employed to vary the duration where the two RAM bytes are updated for the chasing pattern.

Designers are always looking for novel ways to cut costs without compromising performance. There are many methods to efficiently drive the banks of LEDs used in LCD backlights or lighting-pattern applications for efficient illumination. In low-power applications, LEDs can be controlled by managing the time interval for each bank of LEDs. In addition, low pin count microcontrollers and I/O port expanders provide a low-cost alternative for lighting solutions with additional design flexibility.


 [JRC1]Internal link to: /web/c/passive-components/variable-resistors/potentiometers/