# Build a Basic uC 3 Level Led Driver - A Tutorial



## mpf (Jun 27, 2008)

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






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*





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







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







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*


----------



## mpf (Jun 27, 2008)

*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 -------------------------------------------------


----------



## Mr Happy (Jun 27, 2008)

*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.)


----------



## VanIsleDSM (Jun 28, 2008)

*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.


----------



## Mr Happy (Jun 28, 2008)

*Re: Build a Basic uC Led Driver - A Tutorial*



VanIsleDSM said:


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


Actually no, I am not looking for that term as I know it already (I am a professional engineer and know a fair bit about control theory)


----------



## VanIsleDSM (Jun 28, 2008)

*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.



Mr Happy said:


> (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 (Jun 28, 2008)

*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







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







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. 







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 (Jun 28, 2008)

*Re: Build a Basic uC Led Driver - A Tutorial*



VanIsleDSM said:


> ...
> 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


----------



## VanIsleDSM (Jun 28, 2008)

*Re: Build a Basic uC Led Driver - A Tutorial*



mpf said:


> 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.
> ...




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.


----------



## Mr Happy (Jun 28, 2008)

*Re: Build a Basic uC Led Driver - A Tutorial*



VanIsleDSM said:


> 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 (Jun 28, 2008)

*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 (Jul 2, 2008)

*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







*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.







Click Next and on the next panel select AVR Dragon and Attiny84







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. 







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.




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.







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.







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.







*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 (Jul 2, 2008)

*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 (Jul 7, 2008)

*Re: Build a Basic uC Led Driver - A Tutorial (programming and testing the circuit)*



ifor powell said:


> ... 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 (Jul 7, 2008)

*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 (Jul 7, 2008)

*Re: Build a Basic uC Led Driver - A Tutorial (programming and testing the circuit)*



MrAl said:


> .....
> 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 (Jul 7, 2008)

*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 (Jul 8, 2008)

*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.


----------



## MrAl (Jul 8, 2008)

*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 (Jul 8, 2008)

*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.


----------



## MrAl (Jul 8, 2008)

*Re: Build a Basic uC Led Driver - A Tutorial (programming and testing the circuit)*

Hi again mpf,


Well, it's a linear but it's PWM controlled, so maybe call it a 
Pulse Width Modulation Controlled Linear Regulator?

The only reason why i suggest this is because the output stage is biased into
the linear region, but it's not analog controlled but rather PWM controlled.
If we say "linear" many people are going to think LM317 an the like, which
would detract from the cooler idea of PWM control.

Just as a side note, i once designed a PWM controlled switching current regulator,
which is sort of a buck, but the buck part was actually switching but it was 
actually controlled by a PWM signal out of the uC like this design.
The current regulator was used for Li-ion charging.


----------



## mpf (Jul 8, 2008)

*Re: Build a Basic uC Led Driver - A Tutorial (programming and testing the circuit)*

OK, PWM Linear Regulator it is.


----------



## mpf (Jul 8, 2008)

*Re: Build a Basic uC Led Driver - A Tutorial (debugging the code)*

*Debugging the Code in the uC*

The AVR Studio4 allows you simulate the program and single step through it. However using the AVR Dragon you to debug the code in the uC while it is running in your test circuit. It does this by using three wires, VCC, GND and Reset (debugWIRE).
*
Make sure the Supply Voltage is set to 3V. *When debugging, the program will be paused and so will not be controlling the current to the led. A 3V supply prevents the led from burning out.

1) To Start debugging, first compile and load the program into the uC (see above).
2) Choose the menu item Debug -> Select Platform and Device and select AVR Dragon and Attiny84. You need to do this step even after you selected these when setting up the project.





You are now ready to start debugging the code in the uC in your test circuit.

3) Then select the menu item, Debug -> Start Debugging. You will see the following dialog. 




Choose “Use SPI to enable debugWIRE interface” and click OK. This brings up the following dialog




Clicking OK brings up the following dialog




Click Retry to enter Debug mode. All this is the same as setting the DWEN flag via the ISP Flags tab.

4) The program will stop at the first instruction. The *rjmp* to the *RESET* label.






The I/O view on the right hand side allows you to inspect and directly change any of the uC control registers.

Use Debug -> Single Step Over, (F10) to step through the code.
Stop at the *sei* instruction just before the *E_LOOP:* label.
Expand the PORTB I/O




You can see that the DDRB 0 bit is set, making this pin an output. Click on the PORTB 0 bit to set it. The led comes on. Click it again to set it to zero, the led goes off.

Leave PORTB 0 set, i.e. led on.
Scroll the code window to the *ADC_INT:* label and set a breakpoint on the first instruction after the *ADC_INT* label using the menu item, Debug -> Toggle Breakpoint. 
Then select Debug->Run. The code stops at the breakpoint. You can now inspect the AD_CONVERTER ADC value 0x0103. Right click on the window and un-tick the Hexadecimal Display. 





This then shows the ADC count (and every other value in the window) in decimal, i.e. 259 counts out of 1023. That is about 140mA 




The reference voltage in the uC is 1.1V (+/-0.1V). Dividing by the 20x gain of the differential input gives 55mV full scale across the 0.1ohm resistor. That is a full scale current of 0.55A = 0.055V/0.1ohm. So 259 counts out of a full scale of 1023 is 0.55*259/1023 = 139mA

As you single step through the ADC_INT: code you can hover your mouse over the register variables to view their values and the names. On the left hand side you can expand the Registers node and view and directly change the value of the any register.





You can also change/correct the code on the fly and continue running or restart the program. The AVR Studio4 will automatically compile and reload the uC through the AVR Dragon debugWire interface.
So you can see the debug modes available are very powerful. Explore the other features for yourself. 


* Getting out of debugWIRE and back into ISP Mode*

When you are finished debugging you may want to get back into ISP mode. You cannot un-tick the ISP DWEN flag because the ISP programming does not work while debugWIRE is enabled. 

To get out of debugWIRE. 
Select the menu item Debug -> Start Debugging. 
Then when debugging has started a new menu item is added to the bottom of the Debug menu, AVR Dragon Options. 
Select the new menu item Debug -> AVR Dragon Options. This brings up the following dialog 






Click on the Disable debugWIRE button, which bring up the following dialog




Clicking YES, exits debugWIRE and re-enables ISP and bring up the confirmation dialog




Finally click OK and close the AVR Dragon dialog.

You can now access Tools -> Program AVR -> Auto Connect to open the AVR programming dialog and change the fuse settings, lock bits, program the uC etc. using the ISP programming interface.


----------



## ifor powell (Jul 20, 2008)

*Re: Build a Basic uC Led Driver - A Tutorial (plus debugging in circuit)*

A big thanks for this. I just managed to get everything in the tutorial going. Must say I was a bit worried on the inital programing when the led failed to light but it helps if you conect it up the right way around!! Once sorted everything was working as expected. Even managed to walk though the debug section without any trouble.

Time for me to start learning how to use some more features. Add another button or two and work on some user setting code. Then look at remembering the level in the eeprom and the input changed interrupt for a none spining type off... Lots more to learn.

Thanks

Ifor


----------



## mpf (Jul 25, 2008)

*Re: Build a Basic uC Led Driver - A Tutorial (plus debugging in circuit)*

Great to hear it all worked for you.

If you are going to add more features, I suggest you use a state based model. When I first started programming, I started adding features adhoc and soon found it impossible to debug. Using a state based model the torch is in a given state until something triggers a change to another state. Lots of states but the changes all happen in one place.

I will post some example code of this approach to implement a 3 level torch control off -> low -> med -> high - off.

matthew


----------



## mpf (Jul 25, 2008)

*Three Level Led Driver*

Now that you have the push button working, you can use it to switch the led between multiple levels, Off, Low, Med, High. To provide an extensible bases for further development I will use Torch States as opposed to flags to implement these levels. The full code is in ThreeLevelDriver.asm 

.def Torch_State = r22 // initially 0 == off
.equ Torch_State_OFF = 0 // the torch state constants
.equ Torch_State_LOW = Torch_State_OFF+1 // 1
.equ Torch_State_MED = Torch_State_LOW+1 // 2
.equ Torch_State_HIGH = Torch_State_MED+1 // 3
.equ Torch_State_EXCEEDED = Torch_State_HIGH+1 // this and higher states invalid

While you can easily write a program to handle three levels by using flags, you will find that as you controller becomes more complex States are the way to go. The torch is switched between states by triggers. In this case the trigger is the push button changing from up to down. Once the new state of the torch is set then the new state can be use to set the appropriate current setpoint for the ADC_INT: procedure.

In order to vary the setpoint, it is now stored in two registers, SP_High and SP_Low, and the ADC_INT code changes to

sub ADCLow, SP_low 
sbc ADCHigh, SP_High 

The PROCESS_STATE_TRIGGERS first sets the New_TORCH_State to the current TORCH_State and then determines the New_TORCH_State depending on the triggers, if any. It there are no triggers then we just return from PROCESS_STATE_TRIGGERS, otherwise the New_TORCH_State is set and then at the end it is transferred to become the current TORCH_State and the new current setpoint loaded depending of the TORCH_State. See the code file ThreeLevelDriver.asm for details.

//-------------------------------------
//PROCESS_STATE_TRIGGERS
//-------------------------------------
PROCESS_STATE_TRIGGERS:
mov New_TORCH_State, TORCH_State // set initial state

cpi TRIGGER_Flags, 0 // are any trigger flags set
breq END_PROCESS_STATE_TRIGGERS // all zero so just return

sbrs TRIGGER_Flags, TRIGGER_SwitchChanged
rjmp Finished_SwitchChanged_TRIGGER_PROCESSING
// else switch changed state, check switch state
sbrc SW_Flags, SW_SWDown // skip rcall if switch is up
rcall PROCESS_SWDown_TRIGGER // set level accordingly
Finished_SwitchChanged_TRIGGER_PROCESSING:
// check other triggers here

END_PROCESS_STATE_TRIGGERS:
rcall UPDATE_TORCH_STATE // transfer new state to TORCH_State and update setpoint etc.
clr TRIGGER_Flags // clear all triggers as should have been processed.
ret
//------------------------------------- 

A couple of points to note. The triggers work on the New_TORCH_State, leaving the current TORCH_State untouched until the end when it updated to the New_TORCH_State. This approach has two advantages i) the current TORCH_State is always available while processing the triggers. ii) from the value in the New_TORCH_State you can determine which triggers that have been processed.

To emphasise the triggers, they are now stored in their own register, TRIGGER_Flags. In this code there is only one trigger, SwitchChanged. 

.def TRIGGER_Flags = r21 // initially 0 no triggers set
.equ TRIGGER_SwitchChanged = 0 // bit0, 1 if switch just changed else 0

At the end of the PROCESS_STATE_TRIGGERS all triggers are always cleared. This prevents double triggering.

Also in some cases, some triggers may have precedence over others. The higher precedence triggers should be processed last so that the New_TORCH_State can be overwritten.

Another point to note is that the processing of each trigger, updating the torch state and is that the setting of the current setpoint are done in seperate procedures, PROCESS_SWDown_TRIGGER, UPDATE_TORCH_STATE and LOAD_CURRENT_SETPOINT. This simplifies the code and makes it easier to maintain and debug.

You now have a solid and expendable code base on which you can build you own ideas.

matthew


----------



## wquiles (Jul 25, 2008)

Mathew - good job there, and thanks MUCH for sharing this with all of us here in the forum :thumbsup:

Will


----------



## mpf (May 29, 2009)

Also see a complete general purpose Mobile-Phone Controller (with J2ME jars proved) based on POD (Protocol for Operations Discovery). The tutorial covers complete implementation of a Mobile Phone controlled Led driver.
Mobile Phone control of pfodDevices 
http://www.candlepowerforums.com/vb...e-control-of-PODdevices-A-Tutorial-Led-Driver

which is an extension of Mobile Phone Controlled Led Driver - A Tutorial
http://www.candlepowerforums.com/vb...Mobile-Phone-Controlled-Led-Driver-A-Tutorial

*Alternative FETs*

I received an email recently asking me for alternative FETs that could be used for this tutorial. 
Almost any N-Channel FET in a TO-220 case can be used. The TO-220 case is used for high power, high current, low on resistance FETs. Because the FET is being used in a linear mode, the gate charge current specifications are not important and, for a supply of 5V or less, the various breakdown voltage specifications are not important either. The only thing you need to check is, that for the circuit's available gate drive voltage, that the voltage drop across the FET when it is passing 0.5A is low enough so that there is still enough voltage left to drive the LED. The circuit below shows the gate drive and the current path throught the led 
The voltage supplied by the uC to the FET gate will be lower than the supply voltage due to the 4.7K + 47K resistor divider. This divider limits the maximum voltage at the FET gate to 0.91 times the uC Vcc voltage. That is about 3.3V for a 3.6V supply (i.e. 3 x NiMH batteries). 







The voltage available to the drive the Led is the supply voltage less the voltage drop across the FET (Vds) and the voltage drop across the current sense resistor 0.1 ohm. At 0.5A, the voltage across the sense resistor is 0.05V so the Led voltage is 
*Led V = Vcc - 0.05V - Vds (FET)* 
The K2 Led I am using here typically needs about 3.5V to pass 0.5A (see graph below). However you should note that this Led current to voltage curve varies from Led to Led and varies with temperature. 




*Fig 1. K2 typical I versus V curve* 
So for a typical K2 Led, with a 3.6V supply, we want the FET (Vds) to be less than 0.1V 
Led V = 3.6 - 0.05 - 0.1 = 3.45V.
and the available gate drive voltage (Vgs) is 3.3V 
To check that the Vds for the FET at a given gate voltage, in the FET datasheet look for the graph of Drain to Source Current (Id) versus Drain to Source Voltage (Vds) for various gate voltages (Vgs). 
Below is the graph for the FET listed in the tutorial, IRF3202. With a 2.5V gate drive this FET will pass 5A with less than 0.1V drop. At 0.5A the voltage drop will be about 10 times less or 0.01V. This is a very good FET for this application 




*Fig 2. IRF3202* 
For comparison here is an example of a FET that not as good but still very usable, IRLZ24N. With a 3V gate drive this FET will pass 0.7A with less than 0.1V drop. 




*Fig 3. IRLZ24N* 
Finally here is an example of a FET you would *NOT* want to use, IRFB4620 




*Fig 4. IRFB4620* – Do not use
Even with a gate drive of 5V (Vgs), the voltage drop across the IRFB4620 is about 3V (Vds) at 300mA (Id). If you used this FET the led may glow but not very brightly and you would not be able to drive it at 500mA even if you used a 5V supply. 
*Don't use more than a 5V supply or you will destroy the Attiny84 uC.*


----------



## Microa (Jun 16, 2011)

Hi everybody,
I am a uC dummy, but as I followed the steps in this thread, I successfully built my driver. I would like to share my experience with the DIY folks here.

1) The Pandora’s box was opened
I read this tutorial sometime ago. It was interesting, but the size of the Attiny84 is not favorable for a 20mm driver board. I just book marked it for my reference. On 1/20/2011, Georges80 announced his LFlex on the thread LFlex for XML and Li-ion in MTBR which used an Attiny85V. I thought that may be possible to swap the 84 by the 85. I checked and found out the corresponding I/O ports of 84 and 85 in their data sheet.

The wiring diagram I ported for 85 was like this 
 



The 20mm driver will be look like this.




When I simulated my 85 program in AVR Studio 4.18 SP3, it showed error MUX5. So I contacted Matthew to ask for his comments. Kindly enough he gave me a lot of valuable advice and helped me out.

2) It is not a piece of cake
After I revised the code as per Matthew’s instructions, the program would build and run without error.(Actually, I had made some errors in the program.)
I immediately ordered the chips, the AVR ISP MK2 programmer, made the testing PCB and was sitting in front of the mail box waiting for the delivery man to come.
Finally, all the parts came and I set up for testing.
Oh, what’s wrong?
I could not enter the program mode and read the chip’s signature. I checked the wiring many times and made sure that it was all correct.
I changed the chip to another chip. The symptoms persisted. I tried any method I could think of, but I had no luck.
Might be something is wrong with the programmer? The appearance of the chips was not good, however they were suppose to be brand new.
I contacted the supplier to ask for help. This guy is nice and responsible. He went to the chip supplier’s shop to get some chips for testing.
He came back to me later and told me that the chip was bad. They were not brand new or might be fake chips. He swore their programmer was 100% compatible to Atmel’s AVR ISP MK2. My program size is less than 1K. He strongly recommended that I use the more common chip Attiny13A. 
OK. That’s fine. I would love to have a try. I bought some Attiny13A to do some testing. Later I found that the Attiny13A does not support differential input and 20X gain. It is not suitable for this circuit. Oddly enough, the Attiny13A would enter program mode and and I could read signature of the chip. My programmer could also write the program to the flash and the EEPROM and the chip worked fine in another PWM circuit. That lead me to the conclusion that the 85 chips I bought were bad. The 25/45/85 are the same family, if they were to do the job then I would have to find another source of supply. 

3) Going into the dead end.
I confirmed this point with Matthew. I purchased some Attiny25V and carried on my testing. Dam it, Same symptom. Desperately, nothing I can do would fix it
I packed up my stuff and sent it to Matthew to see what he could do. After attempting many variations Matthew could not solve the problem.
Matthew was going to send them back to me.

4) Turning point
Matthew mentioned he had ordered some Attiny25 for his project and that they should arrive in a few weeks. I begged him to keep my stuff until
his chips arrive. I wanted him to program his new chip with my programmer in order to confirm the chips I bought were bad chips. Fortunately, he had some
spare DIP chips and found out the cause of problem.
The problem is the 25/45/85 chips’ RESET fuse was set disabled by default. In this mode the ISP programming can not work properly. The fuse requires HVSP (High Voltage Serial Programming) to enable the RESET.

The enabled RESET fuse




3) Modifying the original 84 program
As soon as I received the spare DIP 25 chip from Matthew with my program in the flash, I fire it up immediately with my new testing board 
Standing by. Oh. That is strange, The LED is always on. I check my program again and find that I have accidentally made mistake because I modify the codes
by notepad and omitted to save.

The original Attiny84 program should be modified in these lines 








After some adjustments, the circuit will be transplanted on the 20mm board.


6) Photos of my test driver

Writing program to the flash




Close-up of the board




Low_110mA




Medium_330mA




High_1A




7) Recommendations
The AVR Dragon is a better investment than the AVR ISP MK2. It supports HVSP and Debug wire which ISP MK2 has omitted.
This circuit requires low Vgs on MOSFET. I use AON7202 for my driver.
The Maximum Vcc is 5V. Low Vf LEDs like XML are the best choice.
If the Vcc is higher than 5V, a LDO regulator is required to power the MCU instead of directly powered from the Vcc.
The FET on my board has metal filled vias connecting the bottom layer as heat sink which is not electrical neutral. Don’t short
circuit it with the ground.

Here, I would like to thank Matthew again for what he has done for me.
Enjoy DIY.


----------



## uk_caver (Jun 23, 2012)

An excellent thread - not sure how I managed to miss it earlier.

I built a driver on the same basic principles for my first proper caving light, nudging a FET gate up or down to keep the current constant, though the FET doesn't have a pulldown and much of the time the drive output is tristated .
At least when driven by a PIC, without having any explicit pulldown the circuit only seems to turn on when the chip is active, and that's with a fairly sensitive FET.

Given that without drive, the gate voltage only very slowly drops, it means I can have the power control as part of my main code loop but go off and do other things (checking switch states, dealing with interrupts, etc) without having to worry whether brief inattention will cause the light to get brighter or darker.

Ripple seems very low as it is, but if I wanted it even lower, once the current had been set by explicit driving up or down, I could probably rely on the very slow drift (seemingly always a drop in my circuit) of the gate voltage to do slower movements than I could do via code, and make a point of just doing a series of very brief nudges to push the current _just_ across the threshold value, which would require hardly any modification to the code - have the 'nudge down' threshold one step higher than the 'nudge up', and if the current reading was close to the threshold, to make sure I did very short adjustment steps so as not to push it too far the other side of the threshold.

At least with the early code, it was tricky to get good operation at low currents, where the current corresponded to an A/D reading of about 2 or 3, so I switched to using PWM for the lowest levels, which was fairly easy since I already had a second transistor in the circuit for redundancy - the design was such that with its switch input high, the driver was on full power, and when the switch input went low, the driver dropped to a lower level or turned off depending on its history.
To add redundancy (since it was my caving light) I had already built in an extra FET+resistor in parallel with the main FET, both running from the current sense resistor, with gate drive coming directly from the switch input.
That meant when the switch was on, the backup FET turned on independent of the PIC to supply a decent fraction of the power (unregulated) via its resistor, with the main FET effectively topping the power up to the full power level.
One useful spinoff from that was that, choosing the resistor appropriately, the power dissipation in the main FET could be made to be roughly constant across the battery voltage range rather than proportional to (battery voltage - LED Vf), since the higher the voltage, the more current ran through the backup circuit.
That made me happier using tiny SMD FETs in the driver, where I had some significant space issues.

When I decided to use PWM, For the lowest currents, I just turned the main FET off, turned the backup one on by driving the switch input pin high, measured the current to see what duty cycle I'd need (which due to the resistor in series with the FET, depended on the supply voltage), and then drove the pin at the appropriate duty cycle.
It seemed quite neat to be able to add the PWM functionality just by writing more code, while keeping the same circuit I had originally made when I wasn't thinking of doing PWM.

Another advantage of PWM was that I could get finer control of current than when doing linear regulation, where my output level choices had been pretty much fixed by the A/D step size (with a 2v5 reference and a 0R22 sense, only giving choices to the nearest 10mA). With PWM, I wasn't really limited in the same way.


----------



## mpf (Jun 25, 2012)

> "though the FET doesn't have a pulldown and much of the time the drive output is tristated."




The driver I use in my torches also does not have an explicit pulldown and the drive is also in tri state most of the time.

When the current is within a small range from setpoint I just do a small loop to put a bit more on the fet gate capacitor and then go tri state for the rest of the main loop. For large errors I just leave the uC pin high(or low) until the next A/D processing.

In addition to this I add a second larger FET gate drive resistor which is used near set point to give much smaller/slower changes to the FET gate. (see "A Faster Smoother Drive" near the top of http://www.forward.com.au/torches.html)

However even with this I cannot control down to 6 to 10 A/D counts with out some flicker.

Your PWM approach is a good idea.

I toyed with the same approach of a switched fixed resitor, see the circuit on http://www.forward.com.au/20WTorch/20WTorch.html R8 and R14, when buidling a PWM driver. 

But not going caving did not need such a low level. At my lowest level (6 to 10 counts) the torches still runs for >24hrs.

I am currently working on 

a) An Andriod version of pfodApp to control the led driver from my phone (and check the battery level and charging state.)

I sum the current measurements to see how much of the battery has been discharged. I don't seem to have written that up, think I posted it on a thread somewhere. There is a process to find the real battery capacity accurately. Measuring the discharge also lets me automatically reduce the output as the battery goes flat.




b) Turning my Android controlled led driver into a garage door opener (update:Nov 2012 - did this one, see www.pfod.com.au for another way)

c) Building a built-in NiMh battery charger for the led driver (on a 31mm dia board)

d) Building a buck supply to run the torch as a lamp (on a 31mm dia board) (update:Nov 2012 - currently working on c and d on the same board)

e) Adding "Adventure" to my Led driver (and perhaps support for sending Morse Code messages if I run out of projects to do) (updated:Nov 2012 - added a small Adventure game to my Led Driver, still need to write it up)


----------



## uk_caver (Jun 25, 2012)

Ah - I'd been looking at the original circuit with an explicit pulldown.

The battery measurement sounds like a neat idea, though not something I could do since the units have swappable battery packs, typically 3x~4Ah NiMH, but also 4.5V alkaline packs or 3xAA.

I did originally put in low-battery detection - when the unit dropped out of regulation on max power, it flashed briefly (with a mainly-on duty cycle to avoid catching people out at an awkward moment) and dropped to the next level, but no-one seemed particularly interested in that, and personally I found even the subtlest thing I could do a bit annoying, so I ditched it in later versions (I was getting very tight for code space as well).

Also, some people used the unit with a pre-existing commercial battery back with notoriously shaky connections. I'd set the flash-and-drop so it only did it for a couple of times and after that, if the light was set on full it would do a graceful decline, only resetting to re-enable flashes when the battery was changed. While the unit output power levels were persistent across battery changes, making it generally resistant to bad connections, if there was a bad connection it would re-enable the low power flash-and-drop, which could be annoying if someone had a poor connection, since not only would it re-enable, but the bad connection could simulate a bad battery, meaning they couldn't engage high power without the possibility of the light keeping overriding them.

At the power levels I run at, the long natural tail-off without level dropping still means that people have a lot of time to change battery packs if/when they notice the light is dimming.
In practice, if someone wants to get a rough idea of battery state, they just need to see how much difference there is between high and medium levels - if the difference is low or nonexistent, that's a sign that it's worth thinking about changing packs at some point if they expect to want bright light.

You certainly seem to have a lot of interesting projects to do.


----------



## mpf (Jun 26, 2012)

Yes the flashes can be either annoying or to subtle or both. 

I use NiMh batteries and their voltage drops off a cliff when they discharge. BUT you would get a long tail-off from normal batteries. 

Bad battery connections are a pain. My first torches had problems with the batteries bouncing on the spring and disconnecting. Spent a while trying to program around the light going out. In the end I moved on to purpose build NiMh battery packs (welded) with plug connectors and have not had connection problems since. (Using welded battery packs and soldered connectors also increased the available drive to the leds by reducing the contact resistance)


----------



## uk_caver (Jun 26, 2012)

For me, even NiMH discharge is slow enough to be safe, since the battery voltage/capacity and LED voltage/current curves mean that when a pack is virtually empty, the current draw is likely to be pretty low, and even a handful of lumens is sufficient for movement, if not necessarily desired. Especially if there's no-one else around (which is really when it's most important), the extent to which the eye can adapt and make do is constantly surprising. For someone who knows where they're going, a few lumens is actually sufficient.

However, I suppose on the battery-monitoring front, typical caving usage does make it less necessary, since most people using NiMH packs with a clean slate (full battery packs) so would have a fair idea of what the worst-case runtime should be, as well as a reasonable idea how long the trip will be.

I guess for a more general-use light, which may well not be routinely charged up before each use, it's a somewhat different situation - someone picking up the light might have little idea of the state of the battery pack, so feedback on the battery state could be much more useful.
Also, for a high power/short-worst-case-runtime device it may be more useful to have monitoring/feedback/power dropping than for a light where the worst case full-power runtime was sufficient for most uses.

As to the flickering at low powers, I wonder how much is down to the FET control and how much to noise affecting the A/D readings - might multiple reading and some kind of summing actually help make things more stable.
I didn't really experiment much when writing my original code, since it was a bit laborious - I didn't even have flash chips, so started off writing the assembler and burning it into an EPROM-based chip to try out, without the capacity to do debugging and needing UV to erase.
I could probably do more these days, with a debugger and a debug version of the current chip, but the PWM seems to work OK, and I'm making relatively few of those units any more. Sticking with code that definitely works is rather more attractive.

I did make a smart charger for 3-cell NiMH packs since there seemed to be virtually nothing around for realistic money which could do the job (it's annoying how many cheap chargers there were that would do 4-8 or 4-10 cell packs, or even the bizarre '2,4,5,6.7,8,10').
With readings taken off-charge every second or so, and averaged over a minute or half-minute to limit noise issues, it seemed fairly easy to get consistently reliable termination even at low charge rates (0.15-0.2C) but I did stick in multiple termination conditions including a relatively fast/sensitive negative delta V, and a 'slower' less-sensitive zero delta V.


----------



## mpf (Jun 28, 2012)

uk_caver said:


> As to the flickering at low powers, I wonder how much is down to the FET control and how much to noise affecting the A/D readings - might multiple reading and some kind of summing actually help make things more stable.



I think your PWM approach is the way to go for really low light output.



uk_caver said:


> I did make a smart charger for 3-cell NiMH packs .....
> With readings taken off-charge every second or so, and averaged over a minute or half-minute to limit noise issues, it seemed fairly easy to get consistently reliable termination even at low charge rates (0.15-0.2C) but I did stick in multiple termination conditions including a relatively fast/sensitive negative delta V, and a 'slower' less-sensitive zero delta V.



I use deltaT termination in my chargers (8 averaged A/D readings every 10sec with the deltas averaged over 60 secs). I had a bad experience with my first re-chargeable torch and a negative delta V "intelligent" charger. The Charging did not stop when recharging nearly fully charged batteries and they melted. Your zero delta V check may have saved them. The delta T check , combined with over temp and max charge time is very reliable for charging from any state of charge at charge rates of 2C. I also measure voltage off-charge to detect bad/missing batteries.


----------

