Build a Basic uC 3 Level Led Driver - A Tutorial

mpf

Enlightened
Joined
Oct 2, 2005
Messages
228
For an Android controlled version of this project Andriod Controlled Led Driver

As I was describing my current 42W Torch/Lamp build I realised how simple the basic controller was. So here is the first part of a tutorial on building your own uC Led driver. Enjoy. The tutorial is also on my website at http://www.forward.com.au/uCLedDriver/build_basic_uC_led_driver.html

As this tutorial is too long for a single post I will split it over multiple ones
This first post covers Circuit, Parts, Construction and Software Flow chart.

Note: the heat sink in the photos is gets quite hot running at 500mA. Use a larger one if you want to run continuously at 500mA.

This tutorial describes the construction and programming of a basic pulse width modulator controlled linear current regulator for driving a LED at 500mA. No SMD devices are used. You can use this same basic design for supplying upto 5A or more by changing the current sense resistor.

Construction time 2hrs.

The tutorial covers
  • Circuit
  • Parts
  • Construction
  • Software Design (Flow chart and outline of code)
  • Complete Code (in later posts)
  • Programming the uC (in later posts)
  • Debugging the code in circuit (in later post)
  • Extensions (in later posts)
Circuit

The circuit for this basic uC controlled LED driver is
BasicLedController_circuit.png


The circuit shown above can be powered from 3 x NiMH batteries or 1 Lithium-Ion battery (or even 3 Alkalines). The uC will work all the way down to 1.8V which is below the fully discharged level of the batteries.
The basic operation of the controller is as follows:-
R1 (0.1ohm) is the current sense resistor. The uC measures the voltage across this resistor to determine the current flowing in the led. If the current is below the required level the uC controller sets the uC output to +5V which charges C3 through R2 raising the gate voltage on the FET U2 this lowers the Fet's resistance and increase the current through the led. IRF3202 is an N-Channel fet which turns on as its gate voltage is increased compared to is source pin. When the current exceeds the required level the uC controller set the uC output to 0V and the discharges C3 so lowering the voltage on the Fet's gate and so increasing the Fet's resistance and reducing the current though the Led.
R3 is there to make sure the Fet gate has zero Volts (i.e. Fet is OFF) when the control uC circuit is powered down.
R2 and R3 form a voltage divider from the uC output to ground which limits the maximum voltage that can be applied to the gate of the Fet. With the values shown the maximum fet gate voltage is about 4.55V.
This is called BangBang control and is simple and effective. The capacitor, C3 smooths out the square wave output from the uC so the Fet gate sees only a small ripple.
R4, R5 and C4 form an RC filter for the current sense voltage applied to the uC differential Analog to Digital 10 bit converter. The uC has an internal gain of x20 applied to this signal and then converts the differential voltage to a count between 0 and 1023, where 1023 equals 1.1V (approximately). Dividing 1.1V by the internal gain of 20 implies that full range on the input to the uC is about 55mV. With a sense resistor of 0.1ohm this means full range current is 55/0.1 = 550mA. Of course you can change the full range current by changing the sense resistor.
C2 and C5 are supply filtering for the uC and R6 is the Reset pull-up resistor.

Parts

Parts.jpg

The parts list is:-
Part Value
C2 0.1uF ceramic
C3 10uF tag
C4 0.47uF
C5 1uF tag
IC1 ATTINY84V 10PU
LED1 K2 LUMILED + heatsink
R1 0.1 1/2W
R2 4.7K
R3 47K
R4 3.3K
R5 3.3K
R6 12K
S1 Momentary On
SV2 6PIN HEADER
U2 IRF3202
Veroboard with copper tracks on the back
500mA power pack 3 to 12V selectable

For programming the uC you will need
AVRStudio4 (free download)
AVR Dragon programming board (~$50)
6pin header cable
USB cable + Window 2000/XP/Visa computer

As you can see from the photo my sense resistor (0.1 ohm) is a 5W resistor. Only a 1/2W resistor is needed but this was what I had to hand. The uC is an Atmel Attiny84V 10PU. Note the V suffix this indicates the low voltage part which will run down to 1.8V. The Led heat sink I used was CPU self adhesive heat sink. If you use a normal heat sink you will also need some thermal epoxy to attach the led to the heatsink.

The AVR Studio 4 provides the code compiler and runs the AVR Dragon programmer. You can download AVR Studio 4 from www.atmel.com (as at July 2008 the direct link is http://www.atmel.com/dyn/Products/tools_card.asp?tool_id=2725 scroll down to the Software section and choose AVR Studio 4.14). AVR Dragon is available from Digiky (and others) in the US and Avnet in Australia. There are other software compilers available that run under Linux (google avr programmer linux).

Other items you need are a fine tipped soldering iron (20W is OK), some solder and a multimeter and tools. I also suggest solder wick and a flux pen.
The power pack's output voltage is selectable from 3V to 12V.

Do not select an output voltage above 4.5V as the normal maximum running voltage for the uC is 5V.


Construction

After 2hrs work I had this
Completed.JPG




The only messy part is the 6 pin header which did not fit neatly into the layout of the copper tracks on the back of the veroboard. I had to cut away some track connections to wire the header up.
Before inserting the uC, plug in the power pack and check the voltage at pin 1 and 14 of the uC. Pin 1 should be +Volts. Also check Pin 4 for +Volts and pin 2 of the 6 pin programming header. Double check the connections and orientation of the 6 pin programming header.

Software Design (Flow chart and outline of code)

The uC's I use are from the Attiny series by Atmel. This design uses the Attiny84V which has 8K of programmable memory, 512 bytes of EEPROM and
512 byte of RAM.

I use a very basic program flow, shown below
BasicFlowChart.jpg




This uses only one interrupt, the ADC conversion complete interrupt. It has the advantage of simplicity and low noise since there is no processing or output pin changes while the ADC conversion is running. It has the disadvantage of reducing the sampling rate of the ADC since all the input and output processing is done each ADC cycle. However my present code still has a sampling rate of about 5300 samples/sec which is more than enough for controlling a torch.
The Attiny84 has a number of built in timers. I don't use any of these. Instead I run my own counters from the ADC loop to generate timers of 1/100 sec and 1/10sec and 1/2sec. Because the ADC loop times vary depending on the processing being done these timers are only approximate but appear to be with in +/- 5%.
An outline of the code is shown below

//---- MAIN PROGRAM --------------------------------------------
.org 0x0000
rjmp RESET
......
.org ADCCaddr// = 0x000b ; ADC Conversion Complete
rjmp ADC_INT
....
RESET:
// initialize uC here
...
sei // enable interrupts
E_LOOP:
// set up for ADC conversion and then sleep
ldi Temp, (1<<SM0) | (1<<SE)
out MCUCR, Temp // ADC noise reduction rest zero, enable sleep
sleep // goto sleep this starts the ADC
// wake up on ADC interrupt
// on completion of interrupt returns here
rjmp E_LOOP // loop for next ADC
//----END MAIN PROGRAM --------------------------------------------


//----HANDLE ADC INTERRUPT ----------------------------------------------
ADC_INT:
// save ADC results
in ADCLow,ADCL
in ADCHigh,ADCH
.....
// process the ADC measurement of current, voltage, etc
.....
// all paths jmp to here
END_ADC_INT:
rcall SWITCH_DEBOUNCE // every ADC cycle about 5kHz
rcall INCREMENT_100HZ_TIMER // may set volts or temp flag
rcall PROCESS_STATE_TRIGGERS
reti // return from interrupt, interrupts enabled here
//----END ADC_INT -------------------------------------------------

See later post for full code of the basic led driver
 
Last edited:

mpf

Enlightened
Joined
Oct 2, 2005
Messages
228
Re: Build a Basic uC Led Driver - A Tutorial

Here is the complete code for the simple Led driver BasicLedDriver.asm

I will dicuss programming the uC next week when my AVR Dragon arrives.
At present I am using an old STK500 programmer to test this code
To vary the led current just change the CURRENT_SP
1000 == 500mA (approx)
100 == 50mA (approx)

Basic Led Driver

As a first attempt, the code in BasicLedDriver.asm just controls the current in the led to a fixed setpoint. It does not used the pushbutton. The led is always on when the power is applied. You could use this in a simple on/off torch by just switch the battery on and off.
To compile this code, download the AVR Studio 4 from www.atmel.com (as at July 2008 the direct link is http://www.atmel.com/dyn/Products/tools_card.asp?tool_id=2725 scroll down to the Software section and choose AVR Studio 4.14). Start a new project
(todo fill in details here)
To follow the code you need to refer to know what the instructions mean. The on-line help in AVR Studio provide explanations of the instructions. You will also need to refer to the attiny24/44/84 data sheet the details of meaning of various control register bits.
There are 4 main sections in the code

Definitions. These dot commands include a file of "standard" definitions for this uC and define (.equ) some useful names for constants and registers (r16,r18,r19)
.include "tn84def.inc"
.equ CURRENT_SP = 1000 // the current setpoint, 1000 = full scale current, approx 500mA
.equ SW_B = PB1 // define name for switch input pin
.equ uC_OUTPUT_B = PB0 // define name for uC output pin
.def Temp = r16// Temporary register
.def ADCLow = r18 // low byte of adc
.def ADCHigh = r19 // high byte of adc


Interrupt Vectors. These dot org commands place relative jump (rjmp) instructions at particular locations in the program memory. On Reset. when the power comes up or when the Reset input pin is allowed to go high, the uC automatically goes to location 0x0000 and executes the instruction found there. Here we jump to our RESET label. When the ADC conversion completes its interrupt jumps to the location 0x000d and from there jumps to our ADC_INT label
.org 0x0000
rjmp RESET
.org ADCCaddr // = 0x000d ; ADC Conversion Complete
rjmp ADC_INT


Main Program. The main program initializes the uC, set up the ADC and then sleeps waiting for the ADC to complete it conversion
The first part prevents this code being interrupted by any other interrupt and then sets the uC internal clock to 4Mhz. The default is 1Mhz set by the programming fuses. 4Mhz is the fasted you can run this uC when the supply is less then 2.7V. Next pin PB0 is set as an output, to drive the Fet, and its state is set to 0V, by default all pins are inputs. The same code applies a pullup resistor to the PB1 pin, the Switch input. This pullup resistor insures the input is held high when the switch is open.
Then the ADC is setup. You need to set its clock frequency to <200KHz to get full accuracy, by dividing the main uC clock. It is also enabled and its interrupt is enabled by this code. The ADMUX register controls which input pins the ADC is connected to and what the reference voltage is and the input gain. Here we are using the internal 1.1V reference and applying a gain of 20 to the differential measurement between PA2 and PA1 inputs.
Finally the interrupts are enabled again before setting the uC into low noise ADC conversion mode. The sleep command starts the conversion. When it completes the ADC interrupt jumps to our ADC_INT label. When it returns here, the command after the sleep just loops and starts another conversion.
RESET:
cli // disable interrupts
// set clock to 4Mhz so can run down to 1.8V
ldi Temp, (1<<CLKPCE)
out CLKPR, Temp // enable clock change for next 4 cycles
ldi Temp, (1<<CLKPS0) // divide by 2 for 4Mhz clock
out CLKPR,Temp // set 4Mhz clock

// uC_OUTPUT_B (PB0) is an output for driving the led DDB0=1, PB0=0 low OFF to start
// SW_B (PB1) is the switch input DDB1=0, PB1=1 with pullup
ldi Temp,(1<<SW_B)
ldi r17, (1<<uC_OUTPUT_B)
out PORTB,Temp
out DDRB,r17

// Enable ADC
ldi Temp, (1<<ADEN) | (1<<ADIE) | (1<<ADPS2) | (1<<ADPS0)
// (1<<ADEN) enable ADC
// ADSC zero do not start conversion yet
// ADATE zero do not trigger conversion
// (1<<ADIE) enable ADC interrupt need I-bit in SREG set also
// (1<<ADPS2) (1<<ADPS0) pre-scale for 4Mhz clock and <200Khz ==> >20 pre-scale say 32
// i.e. 1,0,1 for 4Mhz/32 = 125Khz
out ADCSRA, Temp // set the ADCSR

// set Vref 1.1V (1,0) and the ADC mux inputs and gain (101101) PA2+ve, PA1-ve, x20
ldi Temp, (1<<REFS1) | (1<<MUX5) | (1<<MUX3) | (1<<MUX2) | (1<<MUX0)
out ADMUX, Temp

sei // enable interrupts
E_LOOP:
// set up for ADC conversion and then sleep
ldi Temp, (1<<SM0) | (1<<SE)
out MCUCR, Temp // ADC noise reduction rest zero, enable sleep
sleep // goto sleep
// wake up on ADC interrupt
// on completion of interrupt returns here
rjmp E_LOOP // loop for next ADC


ADC Interrupt Handler This is where the program jumps to each time the ADC finishes a conversion. First collect the results of the 10 bit conversion into the two registers ADCLow, ADCHigh. Then do a double word subtraction of the CURRENT_SP. First subtract the low bytes. The Carry flag is set if the absolute value of the low byte of the CURRENT_SP is larger then the absolute value of ADCLow. Then the high bytes are subtracted including this carry flag also. Finally if the result is >=0 then branch to the CURRENT_HIGH label else drop through to the CURRENT_LOW branch. As described in the circuit description above if the current is high then make the output low (0 volts). If the current is low then make the output high (+V). The reti command returns from the interrupt handler.
//-------------------------------------
// ADC interrupt, result available
//-------------------------------------
ADC_INT:
// save ADC results
in ADCLow,ADCL
in ADCHigh,ADCH

subi ADCLow, low(CURRENT_SP)
sbci ADCHigh, high(CURRENT_SP)
brge CURRENT_HIGH // branch if ADC >= setpoint (signed comparison)
// else ADC < setpoint

CURRENT_LOW:
// ADC < setpoint ==> make output high
sbi PORTB, uC_OUTPUT_B // set output high
rjmp END_ADC_INT

CURRENT_HIGH:
// ADC > setpoint ==> make output low
cbi PORTB, uC_OUTPUT_B // set output low
rjmp END_ADC_INT

// all paths jmp to here
END_ADC_INT:
reti // interrupts enabled here
//----END ADC_INT -------------------------------------------------
 
Last edited:

Mr Happy

Flashlight Enthusiast
Joined
Nov 21, 2007
Messages
5,390
Location
Southern California
Re: Build a Basic uC Led Driver - A Tutorial

Excellent. That's a nice, detailed and very content-filled post.

As a discussion point regarding the on-off or "bang bang" control, I believe there would often be a deadband around the set point, which would be a tunable parameter. Do you have any observations to make about whether you found a deadband useful or necessary, and what value to use -- e.g. 1%, 5%? From a quick glance at your code, it doesn't seem that you have one.

I can imagine that hardware dedicated to this sole purpose and no limitations on switching frequency then a deadband may not be needed. On the other hand, I think (but am not certain) that a small deadband may lead to slightly more accurate current regulation.

(For anyone wondering about this aspect of control, think household thermostat. When the room temperature decreases below set temperature minus X the heating turns on. When the temperature increases above set temperature plus X the heating turns off again. X is the deadband and is inserted to stop the furnace or boiler cycling on and off too quickly. It also provides for a more definite switching signal and avoids extraneous noise on the input signal upsetting the controller.)
 
Last edited:

VanIsleDSM

Enlightened
Joined
Oct 16, 2007
Messages
649
Location
Victoria BC, Canada.
Re: Build a Basic uC Led Driver - A Tutorial

Mr.Happy I believe the term you're looking for is hysteresis.

Nice post, I'll read further into it later.
 

VanIsleDSM

Enlightened
Joined
Oct 16, 2007
Messages
649
Location
Victoria BC, Canada.
Re: Build a Basic uC Led Driver - A Tutorial

Well I hope I didn't insult you, but I'm confused. It seems to me that what you are explaining is hysteresis.

(For anyone wondering about this aspect of control, think household thermostat. When the room temperature decreases below set temperature minus X the heating turns on. When the temperature increases above set temperature plus X the heating turns off again. X is the deadband and is inserted to stop the furnace or boiler cycling on and off too quickly. It also provides for a more definite switching signal and avoids extraneous noise on the input signal upsetting the controller.)

Here's a quote from wiki about hysteresis:

"Human-designed systems will sometimes intentionally exhibit hysteresis. For example, consider a thermostat that controls a furnace. The furnace is either off or on, with nothing in between. The thermostat is a system; the input is the temperature, and the output is the furnace state. If we wish to maintain a temperature of 20 degrees, then we might set the thermostat to turn the furnace on when the temperature drops below 18 degrees, and turn it off when the temperature exceeds 22 degrees. This thermostat has hysteresis."
 

mpf

Enlightened
Joined
Oct 2, 2005
Messages
228
Re: Build a Basic uC Led Driver - A Tutorial

Well I was planning to cover some of this later but since the quality of the control has been raised here are some comments and possiable improvements. (Not that any are needed for a fix relative high current as you will see below)

Extensions

While the above circuit and code is perfectly adequate for controlling the Led current at 500mA or so. At low currents any small changes in the Fet gate voltage are easily visible so you might like to look at making improvements in the control.

Improving the Control

As noted above in the description of the circuits operation, this is bang bang control. Bang Bang control is often associated with limit cycles. Limit cycles are stable oscillations in the output around the setpoint.
There is the driver running at 500mA
DriverLightAt500mA.JPG




The voltage on the Fet's Gate oscillates as shown below
BangBangRipple.JPG




The mean gate voltage is 1.83V to control the led's current to 500mA with a 4.5V supply. The ripple (oscillation) is 0.136V peak to peak (pk-pk) but the frequency is 200Hz which is not visible to the eye. So the control is fine as it is.


However lets consider what we could do to improve the control and reduce the amplitude of this. In this discussion I am taking a practical approach to the problem rather then diving into the maths of sample data systems.
Lets consider:- i) the uC control, ii) input filtering, iv) output filtering, v) deadband, vi) proportional control, vii) variable gain/deadband

i) The ADC is sampling at about 5Ksamples/sec so it takes about 25 samples in each rise and fall of the trace. This means there are plenty of opportunities for the software to control the output so this is not the problem

ii) The input anti-alias filter (R4, R5 and C4) slows down the response of our control. A step change in the sensed current has to charge up C4 before the ADC see the full effect. The time constant for the input filter is about 3.1mS i.e. 1/(2*pi*C*R)Hz = 51Hz This is fine as a filter for the sampling frequency of 5Ksamples/sec as this gives a nyquist rate of 2.5Ksamples/sec, much higher then the input filters cutoff. However the input filters' cutoff is well below the 200Hz ripple frequency so the input filter is limiting the controller's response to the changes in sense current (due to the change in the Fet's gate voltage). This can be confirmed by reducing C4 from 0.47uF to 0.1uF. (241Hz cutoff) This gives about 0.08V pk-pk at about 430Hz. Going further and changing R3,R4 to 1.5K C4 0.1uF (530Hz cutoff). The ripple is about 0.06V pk-pk and about 590Hz. Actually the pk-pk and hz of all the measurements vary considerable.


ReduceLimitCycle.JPG


The disadvantage of increasing the frequency response of the input filter is that it makes the system more sensitive to impulsive noise (noise spikes).


iv) The output of the controller is filtered by R2, C3. This RC filter turns the square wave (bangbang) output into a roughly smooth level for to drive the Fet gate. The RC time constant for this filter is about 47mS i.e. 3.3Hz. Increasing this time constant also reduces the ripple and increases the frequency. The disadvantage of increasing this time constant is that the response of the led becomes slower. The Led comes on slower and goes off slower. The advantage of a longer time constant is that the led is less responsive to noise spikes (flicker). These flickers become more noticeable at low light levels.

v) A deadband is often employed in bangbang controls to prevent cycling. As shown above the cycling is not significant at 500mA and can be further reduced if necessary without using deadband. But by all means experiment with putting some deadband into the code and investigate its effect.

vi) Proportional control is another solution. Instead of switching the output on for full length of the ADC sample period, you could set a counter proportional to the size of the error and tri-state the output when the counter counts out. This would require taking the sleep out of the main loop and counting down the counter, or you could load one of the uC's counter/timers and handle the interrupt when it timed out. In either of these of these cases your ADC measurement will be noisier as the uC is running and switching outputs while the ADC is doing its conversion.

vii) Because of the logarithmic response of the eye, low levels of light correspond to very low currents and hence very low ADC conversion counts (try a setpoint of 10 for example). At these levels you may need a different size of deadband or proportional gain, i.e. ones that vary with the current setpoint.

So there are a few ideas for you to try out.
 

mpf

Enlightened
Joined
Oct 2, 2005
Messages
228
Re: Build a Basic uC Led Driver - A Tutorial

...
Here's a quote from wiki about hysteresis:
..."If we wish to maintain a temperature of 20 degrees, then we might set the thermostat to turn the furnace on when the temperature drops below 18 degrees, and turn it off when the temperature exceeds 22 degrees. This thermostat has hysteresis."

Hi Van,
I had a look at the wiki and I think it is wrong. Your quote about hysteresis is correct but the wiki incorrectly says hysteresis is the same as deadband. I have edited the deadband wiki page.

It is not in my book. The difference is that when you are in the deadband nothing happens. While with hysteresis you are alway either on or off. What Mr Happy was thinking of was making the output of the controller tri-state in the deadband. That is neither driving the current up nor down. Think of gears with sloppy teeth. Inside the sloppy range the input shaft is not driving the output shaft in any direction.

hope this helps
 
Last edited:

VanIsleDSM

Enlightened
Joined
Oct 16, 2007
Messages
649
Location
Victoria BC, Canada.
Re: Build a Basic uC Led Driver - A Tutorial

Hi Van,
I had a look at the wiki and I think it is wrong. Your quote about hysteresis is correct but the wiki incorrectly says hysteresis is the same as deadband.

It is not in my book. The difference is that when you are in the deadband nothing happens. While with hysteresis you are alway either on or off. What Mr Happy was thinking of was making the output of the controller tri-state in the deadband. That is neither driving the current up nor down. Think of gears with sloppy teeth. Inside the sloppy range the input shaft is not driving the output shaft in any direction.

hope this helps


Thank you, that does help. I do know the term you speak of with the gears.. that would be backlash, and is a great analogy to understand the difference between hysteresis and deadband.
 
Last edited:

Mr Happy

Flashlight Enthusiast
Joined
Nov 21, 2007
Messages
5,390
Location
Southern California
Re: Build a Basic uC Led Driver - A Tutorial

Well I hope I didn't insult you, but I'm confused. It seems to me that what you are explaining is hysteresis.
I see a lot more explanation has been posted since my comment, so there is not much more to add. But simply speaking, I didn't feel there was a need to use an uncommon word like hysteresis when not all readers might be familiar with it.

What I would say I think is that a deadband is a feature inserted into a control system, and hysteresis is a property seen in the output of a control system. We could say that a control system has a deadband and exhibits hysteresis.
 

mpf

Enlightened
Joined
Oct 2, 2005
Messages
228
Re: Build a Basic uC Led Driver - A Tutorial

Press On / Press Off Led Driver

So now you have a constant current led driver that is switched on and off by turning the power on and off. But there is a push button on the board. What about controlling the led using the push button. Press to turn on, press to turn off.
A naive approach to this is to just read the input the switch is connected to using the instructions

sbic PINB, SW_B // read SW if SW high jump to released
rjmp SW_HIGH_RELEASED
// else drop through to SW_LOW_PRESSED

However it turns out this is not reliable. When switches open and close the contacts tend to bounce and give a number of fast open and closes before the switch settles out to its final state. See Guide to Debouncing for more details.
That article contains some code for debouncing switches. The code presented here is not as compact but is easier to follow and includes a switch changed trigger. The full code is in SwitchedLedDriver.asm . Below is the debounce code.

The SWITCH_DEBOUNCE is called after each ADC conversion, i.e. About 5000 times pre sec. First it clears the SW_SwitchChanged flag. This flag is only set for the single cycle during with the switch changed state, after allowing for debounce. Next it increments the DEBOUNCE_counter. This counter counts from 0 to 253. It is limited at 253 to prevent it wrapping round and triggering extra SwitchChanged triggers. Next the switch input is checked to see what the current state of the switch is. The code then jumps to the appropriate label. The actions are similar so lets just look at SW_LOW_PRESSED.

SW_LOW_PRESSED first checks the last input state. If the last input was also low then the code jumps to check for debounce. If the last input was high then the switch input has just changed and the code drops through to update the last input and clear the debounce counter.
SW_low_check_debounced checks the current DEBOUNCE_counter against the Debounce_Count_Low value (about 10mS) if they are not equal then the code just returns. If the count equals the debounce value then the SW_SWDown flag is set to indicate the switch is down (debounced) and the SW_SwitchChanged flag is set to indicate the switch state has just changed.

SW_HIGH_RELEASE preforms similar checks for when the switch is released.

// ------------- CODE ---------------------------
// Debounce Counter Counts allows up to 50mS debounce = 250 count
.def DEBOUNCE_Counter = r17 // count debounce timeout
.equ Debounce_Count_Low = 50 // about 0.01 sec = 50x5kHz
.equ Debounce_Count_High = 50 // about 0.01 sec = 50x5kHz
// increase the count if you switch needs a longer debouce in one direction

//------------------------------------------------
//SW_Flags, Holds the current debounced switch state and last state and change trigger
//------------------------------------------------
.def SW_Flags = r20 // 0b00000000 initially i.e. Switch up and last switch up and not trigger and led off
.equ SW_SWDown = 0 // bit0, 1 if button pressed, 0 if button released.
.equ SW_SWLastInput = 1 // bit1, 1 if last read SW pressed (low) not debounced
.equ SW_SwitchChanged = 2 // bit2, 1 if switch just changed else 0
.equ SW_LedOn = 3 // bit3, 1 if led is on, 0 if off
//------------------------------------------------
.......
SWITCH_DEBOUNCE:
andi SW_Flags, ~(1<<SW_SwitchChanged) // clear switch changed flag

cpi DEBOUNCE_Counter, 0xfe // inc counter but limit to 253
brsh DEBOUNCE_Counter_LIMITED // skip increment if at or above limit
inc DEBOUNCE_Counter ; count up 5KHz counter for debouce

DEBOUNCE_Counter_LIMITED:
sbic PINB, SW_B // read SW if SW high jump to released
rjmp SW_HIGH_RELEASED
// else drop through to SW_LOW_PRESSED

SW_LOW_PRESSED: // read SW low/pressed
sbrc SW_Flags, SW_SWLastInput //check if last flag high/on, rjmp if was high
rjmp SW_low_check_debounced // if last input low and this one low check for debounce low

// else this sw low and last high, set last input to high and reset debounce counter
ori SW_Flags,(1<<SW_SWLastInput) // 1 for last pressed
clr DEBOUNCE_Counter; load counter restart counter for next debounce
rjmp SWITCH_DEBOUNCE_RETURN // no change yet

SW_low_check_debounced:
cpi DEBOUNCE_Counter, Debounce_Count_Low
brne SWITCH_DEBOUNCE_RETURN // only do switch change on DEBOUNCE count
// else update output and return
ori SW_Flags,(1<<SW_SWDown) // debounced
ori SW_Flags, (1<<SW_SwitchChanged) // debouced switch changed
rjmp SWITCH_DEBOUNCE_RETURN

SW_HIGH_RELEASED: // read SW high/released
sbrs SW_Flags, SW_SWLastInput // check if last flag low/off, rjmp if was low
rjmp SW_high_check_debounced // if last input high and this one high check for debounce high

// else this sw high and last low, clear last input and reset debounce counter
andi SW_Flags,~(1<<SW_SWLastInput)
clr DEBOUNCE_Counter; load counter restart counter for next debounce
rjmp SWITCH_DEBOUNCE_RETURN // no change yet

SW_high_check_debounced:
cpi DEBOUNCE_Counter, Debounce_Count_High
brne SWITCH_DEBOUNCE_RETURN // only do switch change on DEBOUNCE count
// update output and return
andi SW_Flags,~(1<<SW_SWDown) // debounced
ori SW_Flags, (1<<SW_SwitchChanged) // debouced switch changed
rjmp SWITCH_DEBOUNCE_RETURN

SWITCH_DEBOUNCE_RETURN:
ret

So now we have our debounced input. We need a few more changes to get the get the led to turn on and off. First in the ADC_INT code we add at the top a test to see if the Led should be on or off. If it should be off we always set the uC output to low to drive the Fet off. Otherwise we let the current control do its work.

At the bottom of the code we add calls to our SWITCH_DEBOUNCE code to update the switch input flags and then to a PROCESS_STATE_TRIGGERS procedure to handle changes in the switch state.

The new ADC_INT code is

ADC_INT:
// save ADC results
in ADCLow,ADCL
in ADCHigh,ADCH

sbrs SW_Flags, SW_LedOn // skip rjmp if led should be on
rjmp CURRENT_HIGH // if led off set output low
// else led should be on
subi ADCLow, low(CURRENT_SP)
sbci ADCHigh, high(CURRENT_SP)
brge CURRENT_HIGH // branch if ADC >= setpoint (signed comparison)
// else ADC < setpoint

CURRENT_LOW:
// ADC < setpoint ==> make output high
sbi PORTB, uC_OUTPUT_B // set output high
rjmp END_ADC_INT

CURRENT_HIGH:
// ADC > setpoint ==> make output low
cbi PORTB, uC_OUTPUT_B // set output low
rjmp END_ADC_INT

// all paths jmp to here
END_ADC_INT:
rcall SWITCH_DEBOUNCE // every ADC cycle about 5kHz
rcall PROCESS_STATE_TRIGGERS
reti // interrupts enabled here


The PROCESS_STATE_TRIGGERS code is responsible for updating the SW_LedOn flag in response to switch presses.

The code is straight forward PROCESS_STATE_TRIGGERS:

sbrs SW_Flags, SW_SwitchChanged // skip rjmp is there was a switch change
rjmp END_PROCESS_STATE_TRIGGERS // no switch change so just finish

// else switch changed state, set SW_LedOn accordingly
sbrs SW_Flags, SW_SWDown // skip rjmp if switch is down/on
rjmp END_PROCESS_STATE_TRIGGERS // do nothing on switch relase
// else toggle led on state
sbrc SW_Flags, SW_LedOn // skip rjmp if led already off
rjmp TURN_LED_OFF // led on, so turn off
// else turn led on
ori SW_Flags,(1<<SW_LedOn) // turn on
rjmp END_PROCESS_STATE_TRIGGERS // finished

TURN_LED_OFF:
andi SW_Flags,~(1<<SW_LedOn) // turn off
// drop through to end

END_PROCESS_STATE_TRIGGERS:
ret


If there has been no switch change then just return. Otherwise if the new switch state is On/down then toggle the LedOn flag.

Finally a note on setting the Debounce_count_high and low. You can check this will an oscilloscope by outputing the SW_SWLastInput flag to a spare uC pin and measure the bounce in your particular switch. However another way that does not require an oscilloscope is to just try it. If the led always reliablely turns on and off then the debounce counts are long. If not make them a bit longer and try again. If you get to 50mS (about 250 counts) without success then buy a different switch.
 

mpf

Enlightened
Joined
Oct 2, 2005
Messages
228
Re: Build a Basic uC Led Driver - A Tutorial (programming and testing the circuit)

Writing the Program to the uC

AVRStudio4 is used to write, compile and debug the uC code. The AVR Dragon board is used to write the compiled code to the Attiny84 and to debug the code in circuit. AVRStudio4 requires a Window2000/XP/Vista PC to run it. A USB connection is required to connect to the AVR Dragon programming board.

Installing the AVRStudio4 and USB Driver

First download and install AVRStudio4 (V4.14 or later from www.atmel.com). When the installation is complete, re-run the install program and select Modify, and then tick to install the USB driver
installUSBDriver.jpg




Installing the AVRDragon Board

Once the drive has finished installing, connect the AVR Dragon to a USB port. It will be found by the operating system. After a while the "Please Wait .." dialog will dissappear. The AVR Dragon board has now been installed.

Creating a AVR Project.

Start AVRStudio4 and click on the New Project button. Select Project type as Atmel AVR Assembler and type in the project name.
newProject_1.png




Click Next and on the next panel select AVR Dragon and Attiny84
newProject_2.png




Click Finished. When the Project opens, choose from the menu item Tools -> AVR Dragon Upgrade and upgrade the AVR Dragon's firmware. This will close the current project. When the upgrade is complete, open your BasicLedDriver project and copy the code for BasicLedDriver into the BasicLedDriver.asm file.

Choose the menu item Build -> Build to compile the code. There should be no errors or warnings.

Writing the Compiled Code to the uC

Connect the AVR Dragon ISP 6pin header to the 6pin header on the circuit board.
AVR_Dragon.jpg




If you cannot found a ready made 6pin header cable, you can make your own. Construction time 5mins.

Buy some ribbon cable, 6 strands or more, and two insulation displacement headers 6pin (or 10pin). Strip back the ribbon cable to 6 strands and place in the header, aligning the red stripe on the pin 1 side of the header plug.
10pinHeader_circled.jpg

Here I am using a 10pin header. The circled mark is the pin 1 marker.

Then squeeze the header together in a vice and add the locking clip (if there is one). Do the same for the other end of the cable. Finished. Plug in your cable, aligning the red stripe with the pin 1 marking on the board headers.

Apply 3V to your Led Driver board.

NOTE: When tesing and debugging your code do not exceed 3V on the Led Driver board. This will protect your led from being burnt out if the uC output gets stuck on High, turning the FET hard on.


Setting the ISP Frequency


Now choose Tools -> Program AVR -> Auto Connect to open the AVR programming dialog. When this first runs it will probably not work. This is due to the ISP Frequency being to high for your circuit. Click Settings button and set the frequency to 125Khz and Write it.

The ISP mode should be automatically set. Check that the Device is Attiny84. You should now be able to Read the Signature of your uC.
DragonFrequencyOK.png




If you still have problems such as an ISP Mode Error dialog box, the press F1 while the error dialog is open to get help on what else could be wrong. (Is the power to the board off??)


Setting the Fuses


On the Fuses tab, set the following Fuse settings and click the Program button. You can check them by clicking the Verify or Read buttons.
FuseSettings.png




Leave the other tabs with their default values.


Writing the compiled .hex file to the uC


You are now ready to write your code the uC. You have already build the code so there should be .hex file in the same directory as the .asm file. You need to specify this file as the Input HEX file on the Progam tab.
flashProgramming.png




Make sure the Supply Voltage is set to 3V, then click on the Flash Program button to write the compiled code to your in circuit uC. The led should come on when the programming is finished.

Measure the voltage across the 0.1 ohm sense resistor. I measured about 20mV. That is I = V/R = 20mV/0.1ohm = 200mA. With a 3V supply there is not enough supply voltage to drive the led at 500mA. If you measure the voltage across the FET Drain to Source you should find only a few millivolts as the uC output should be turning the FET hard on trying get the led current up to 500mV. Measure the voltage from the Gate of the FET to ground. You should get about 0.9 x the supply volts (i.e. around 2.7V for a 3V supply. The 0.9 factor is due the voltage divider R2, R3. The voltage at the Gate is equal to the supply voltage x R3/(R2+R3) = supply voltage x 0.909

If everything looks OK so far, you can try setting the voltage to 4.5V. First attach the multimeter across the 0.1 ohm sense resistor. Then while watching the multimeter, turn the supply on. The multimeter should read about 50mV (45mV to 55mV), if the multimeter reads >60mV then the uC is not controlling the current in the led. Turn the supply off and check your circuit for shorts. When I shorted the Drain to the Source of my FET, I read 90mV across the sense resistor, i.e. 900mA with a 4.5V supply voltage.

With a 4.5V supply, the FET Gate voltage in my circuit was about 1.9V. This indicates that the uC is switching its output on and off to control Gate voltage and hence the FET's resistance and hence the current in the led. Measure the voltage from the FET's Drain to Source. My measurement was about 0.95V. This indicates the FET's resistance is being controlled to R = V/I = 0.95V/0.5A = 1.9ohms. You can watch this Drain Source voltage vary as the led heats up. As the led heats up, the uC continues to adjust the FET's resistance to keep the current through the led constant.

The possible variation in sense resistor voltage of between 45mV and 55mV is primarily due to range of uC's ADC 1.1V reference voltage. The 1.1V reference voltage can be anywhere between 1.0V and 1.2V If the ADC reference voltage is higher then the current will be higher for the same ADC conversion count. The voltage across the sense resistor will also vary due to the accuracy of the 0.1 ohm resistor. Plus or minus 5% is typical. All these variations are not critical. However if the current is too high you can always just reduce the setpoint in the code. If the current is too low, you will need to reduce the sense resistor. Putting a 1ohm resistor in parallel with it will reduce it by about 10% and hence increase the full range current level by 10%.

Note: You will not get an accurate measurement of the current by using your multimeter as a current meter unless there is still enough voltage for the uC to regulate the led current to the setpoint. This is because your multimeter meter has its own sense resistor which drops some of the supply voltage. To see if the uC is still regulating the current in the led, you need another multimeter to measure the FET Gate voltage at the same time and check that it is less then 0.9 times the supply voltage.


Now that your circuit is running, you can try some of the other programs in this tutorial or code your own, however remember to set the Flash Input HEX File to the same name as the .asm file you have just compiled. Opening a new project does not automatically update the Input HEX file setting, you need to do this yourself.

 

ifor powell

Enlightened
Joined
Nov 18, 2007
Messages
230
Location
Bristol UK
Re: Build a Basic uC Led Driver - A Tutorial (programming and testing the circuit)

Thanks for the tutorial mpf. It just might be enough to get me to go and get some bits so I can have a go at following it, you looks to of covered everything you need to have a go.

Ifor
 

mpf

Enlightened
Joined
Oct 2, 2005
Messages
228
Re: Build a Basic uC Led Driver - A Tutorial (programming and testing the circuit)

... you looks to of covered everything you need to have a go.
Ifor

Ifor, there are one or two more installments to go.
One about debugging using AVR Dragon and perhaps another on some more suggestions for improvements.

Let me know how you get on.

matthew
 

MrAl

Flashlight Enthusiast
Joined
Sep 9, 2001
Messages
3,144
Location
New Jersey
Re: Build a Basic uC Led Driver - A Tutorial (programming and testing the circuit)

Hello there,


I couldnt read the whole thread but i noticed some talk about 'dead band' and
'hysteresis' and thought i would comment a little.

'Dead band' is only possible in systems that have three state outputs. A good
example of this is a transistor "half H bridge", where you can drive the output
to a low or you can drive the output to a high or you can present a relatively
high output impedance to the load instead. This kind of action is not possible
with a two state only driver.

'Hysteresis', on the other hand, is an overlap of a two state driver where
the output stays turned 'on' for a given feedback range that would normally
turn it 'off', and stays turned 'off' for a given feedback range that would
normally turn it 'on'. All two state systems have at least some hysteresis
or else they burn out (or are low power). Without hysteresis the switch
output can go into a linear mode where the series pass device (usually
a transistor) will consume excess power and burn out.

I'm not sure yet however if this is a switching project or a linear one, i'll
have to read more i guess.

In any case, it looks like a very interesting project.
 

mpf

Enlightened
Joined
Oct 2, 2005
Messages
228
Re: Build a Basic uC Led Driver - A Tutorial (programming and testing the circuit)

.....
I'm not sure yet however if this is a switching project or a linear one, i'll
have to read more i guess.

Good description of the difference between hysteresis and deadband, MrAl. You might want to add it to the Wiki page on deadband.

I think of this project as a linear one. The led current is regulated by a linear buck regulator.

However the control of the FET regulator is via a switching "bangbang" controller. So it has switching in it also.

Deadband was suggested as a means of reducing or eliminating the limit cycle oscillation in the uC switching output. However since the limit cycle is around 200Hz (see earlier post), it is not visible and can be ignored at this current level.
 

MrAl

Flashlight Enthusiast
Joined
Sep 9, 2001
Messages
3,144
Location
New Jersey
Re: Build a Basic uC Led Driver - A Tutorial (programming and testing the circuit)

Hi there mpf,


Im not sure i understand you yet. You are saying that it might be linear
but then you say that it's a buck regulator. It can be both in practice,
but if it was then your program would be designed with specific points
where it switches from linear to buck and vice versa, and you would
be well aware of this. Also, most regulators are either linear or
switching and not both. Buck means step down switching. Switching
is usually preferred because of the lower losses associated with
those regulators, whereas linears are energy eaters.

So the question is, within your program do you turn the output
transistor on and then later turn it off, and maybe later
turn it back on (depending on feedback) or do you sometimes
drive it with a filtered PWM signal out of the uC or some
other form of analog signal ?
Knowing this would help define the mode of operation. It
would also help greatly to have a schematic to look at.
If you could draw one up we could take a look and perhaps
provide some more insight into your circuit.

Im looking forward to seeing more of this circuit and how
well it is working out. The schematic will help much too.
 

mpf

Enlightened
Joined
Oct 2, 2005
Messages
228
Re: Build a Basic uC Led Driver - A Tutorial (programming and testing the circuit)

Well there is a circuit, photos etc. But just at this present time it seems my hosting service is not responding. Try again later. (Edit: Opps the circuit image was changed to a png and did not update post here. Corrected now, see http://www.forward.com.au/uCLedDriver/build_basic_uC_led_driver.html for the everything on one page, with pictures)

I am using the word "buck", as opposed to "boost", to imply the input voltage is greater than the output voltage. I say this is a linear buck regulator to indicate that it does not use a switching inductor to buck but rather a linear resistance (FET). The output of the uC controlling the FET is switching variable pulse width which is then filtered to give the required gate voltage.

Careful selection of the battery/led combination gives very high >90% efficiencies.
 
Last edited:

MrAl

Flashlight Enthusiast
Joined
Sep 9, 2001
Messages
3,144
Location
New Jersey
Re: Build a Basic uC Led Driver - A Tutorial (programming and testing the circuit)

Hi again Matthew,


Ok, the MOSFET is operated in the *linear* mode as you thought. Note however
that this would not be called a 'buck' circuit at all. The interesting
thing is that the drive method is in fact three state, which would allow for
some dead band to be used (assuming at run time PB0 can programmically go to
high Z as well as L and H logic states, which is typical of uC's).

The three *output* states would then be:
1. Output H (high)
2. Output L (low)
3. Output High Z (high output impedance)

This would provide for three corresponding *drive* states:
1. Drive +V through 4.7k
2. Drive 0v through 4.7k
3. Drive 0v through 47k

The first two (1 and 2) would provide for the normal drive, while the
third state here (3) would provide for the dead band drive.

Since the MOSFET characteristics would probably change slowly with time
and temperature, the dead band drive state would help smooth the output
current as someone suggested. In fact, since there is almost total control
over the gate even without the 47k resistor, this resistor could probably
be increased to 470k which would allow for more time between pulses
during normal operation. This might even go higher or eliminate it completely.
Also, since the gate of the MOSFET is capacitive
in nature, it may even be possible to operate this without a gate cap
but with a higher value for the 4.7k resistor.

The bit bang drive scheme would then go something like this for say a 300ma LED:

; dead band is 40ma here
UpperLimit=0.320
LowerLimit=0.280

If I>UpperLimit then
SetOutput(logic low)
elsif I<LowerLimit then
SetOutput(logic high)
else
SetOutput(high Z)
end if

Of course because the time constant of the drive is so slow there also needs
to be some slow start turn on built in, and the circuit will have to be evaluated for
the condition where the battery cell connectors dont make good contact and
therefore break open now and then during normal use (drop the light from
a small height perhaps). If the chip resets the slow start will take over but
if not the current may change abruptly. I think this can be tested by
wiggling the cells around a bit. The slow start can simply ramp the current
up gradually at reset.

I dont know how much of this you have already considered so i thought i
would mention all this stuff.
 

mpf

Enlightened
Joined
Oct 2, 2005
Messages
228
Re: Build a Basic uC Led Driver - A Tutorial (programming and testing the circuit)

Thanks for the comments. If it is confusing people, I will cease referring to this as a linear buck regulator and just call it a linear regulator.

All your suggestions are worth consideration/investigation. I will leave them as an exercise to the reader, since my tutorial is only on a Basic uC Led Driver.

However I would encourage people to post the results of their enhancements here for the rest of us.
 
Top