/* Hotwire Regulator 0.95 (c) 10/2008 Alan K Biocca */
/* Voltage regulator for high power incandescent flashlights
* using PWM driving a FET
*
* website http://www.akbeng.com/w/HotWireReg
*
*-------------------------------------------------------------------------*
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*-------------------------------------------------------------------------*
*/
/* Hardware Config (ATTiny25/45/85)
*
* see schematic
*
* The SPI programming plug takes either the programming cable or
* dongle with pushbutton.
*
* BP0/MOSI enable ref out to capacitor for filtering and SPI
* BP1/MISO drive opto input low to enable battery v sampler and SPI (optional)
* BP2/SCK control pushbutton switch to ground and SPI
* BP3/OC1Bnot drive high with PWM to enable bulb current
* PB4/ADC2 battery/bulb voltage sample input divider
* PB5/!RST SPI programming
*/
/* Voltage Divider Resistor Values
*
* R1, R2 and R3 determine the voltage range of the regulator. R3
* should be 10K to maintain the impedance of the circuit to Atmel
* guidlines. R2, the pot, is chosen to provide a reasonable span of
* calibration adjustment. It should be shorted, 1K or 2K, generally.
* The pot may be omitted and the calibration done in software. R1 is
* the resistor that determines the high voltage end of things.
* R1 = 100K is adequate for about 30V. R1 = 47K is good for up to
* 12V battery packs. The 100K will work for the lower voltages, but
* the lower R value will give slightly more resolution, however it
* also increases the standby drain of the circuit.
*/
/* Formulae
*
* VbattDC^2 * Duty_cycle = VbulbRMS^2
*
* VbulbRMS = VbattDC * sqrt(Duty_cycle)
*
* Duty_cycle = VbulbRMS^2 / VbattDC^2
*
* VbulbMEAN = VbattDC * Duty_cycle
*
* VbulbRMS = sqrt(VbattDC * VbulbMEAN)
*
* VbulbMEAN = VbulbRMS^2 / VbattDC
*/
/* REGULATOR MAXIMUM RATINGS
*
* The maximum ratings depend on the hardware. The prototype hardware
* maximum ratings are (proposed to be) 30V 15A. Maximum current will
* depend on heatsinking if run for long periods of time. The minimum
* voltage is 6 volts. Below 6 volts the system will shut down. Above
* 30 volts the electronics is subject to damage.
*/
/* WARNING
*
* Unlike most software this program controls hardware that
* is not completely protected against damage. Errors in the software
* can cause hardware damage including blowing light bulbs,
* damaging the FET and overcurrent or overdischarging the
* batteries of the flashlight, possibly leading to fire or
* explosion of the batteries. Use at your own risk. Test
* with a resistor/LED combination, or bulb that can safely
* take the full battery voltage, and protected or safe
* chemistry batteries or a power supply.
*
* YOU HAVE BEEN WARNED.
*/
/* TESTING
*
* Safe testing can be performed using a low power load that
* can handle full battery voltage. An LED with a resistor
* that will limit the current is a good example. A 1K resistor
* will limit the current to less than 30 mA at 30V. Watch the
* polarity or use two LEDs in parallel, one with each polarity
* to make a polarity independent test load.
*
* Measuring bulb voltage on this regulator will require a DC RMS
* reading meter. Note that many RMS capable meters only do RMS on
* the AC scale. This will not produce a correct reading on this PWM
* signal. Using an averaging DC meter will produce the mean
* voltage, which is different from RMS. The bulb will react to
* RMS not average voltage. A common averaging DC meter can be
* used by doing the calculations shown below.
*
*
* RMS Calculations
*
* VbulbRMS = sqrt(VbattDC * VbulbMEAN) # to see what you have
*
* VbulbMEAN = VbulbRMS^2 / VbattDC # to set a desired value
*/
/* Tools and Settings
*
* use WinAVR and AVR Studio (free software tools)
*
* set to ATTiny85 or ATTiny25 CPU type (depending on which you have)
* Compiler Optimization: Os
* Additional Library: libm.a
*/
/* Flashlight Program Calibration
*
* The chip's ADC needs to be calibrated for voltage. Use one of the
* first two methods, and optionally use the RMS method after.
*
* 1) Rough Calibration Method
*
* The accuracy of this method depends on the actual values of the
* resistors and the on chip voltage reference. These can vary a bit
* so this method is not recommended for a final solution. If the RMS
* method is going to be used for fine tuning this is adequate for
* initial settings.
*
* set the desired values for battery and bulb voltage parameters
* set the known values for R1, R2, R3, VREF and VCAL
* compile and load the software into the chip
*
* 2) DC Calibration Method (not implemented yet)
*
* Measure the DC voltage of the power source
* Set the measured voltage into the DC Cal Voltage parameter
* Set the DC Calibration flag to one
* Apply the CAL Voltage until the light comes on (approx 10 seconds)
* The calbration flag is automatically reset and the calibration
* parameter automatically determined and set
*
* 3) RMS Meter Fine Tune Calibration Method
*
* This method REQUIRES an RMS+DC meter. Most standard meters measure
* average voltage and WILL NOT WORK for this method. Even many RMS meters
* won't handle the square wave PWM properly or only measure RMS on AC scales.
*
* Use either the Rough or DC Calibration methods first
*
* Then turn the flashlight on to the lowest level
* adjust the potentiometer for the specified lowest voltage
* set the control for the highest voltage (if appropriate)
* fine tune the potentiometer for the highest voltage
*
* IF the adjustment range is insufficient (or there is no adjustment pot
* in the hardware) then adjust the VCAL calibration
* parameter value by the error percentage
*
*
* 4) DC Meter and Calculator Fine Tune Calibration Method
*
* Using a standard averaging meter, measure the DC battery voltage
* (VbattDC) and the average bulb voltage (VbulbMEAN). Calculate the
* VbulbRMS from the equation shown above. Otherwise follow method (3)
* above.
*/
/* Program Selection
*
* This project will have several different programs for testing
* and flashlight operation. The software switches here select
* which program and options will be used in creating the
* program. Define ONE program and any options that relate
* to that program, and then compile and load into the chip.
* TEST CAREFULLY at low power using an LED with resistor or
* low power bulb that can withstand full battery voltage.
*
* Programs
*
* FP1: Toggle the FET Gate Output (very slow or 100 hz, 50% duty cycle)
* FP2: Electronic Pushbutton Switch (on/off)
* FP3: Always On, Fixed PWM, Soft Start
* FP4: Pushbutton, Fixed PWM, Soft Start
* FP5: Pushbutton, VariLevel PWM, Soft Start
* FP6: Always On, Regulated, Calibrated, Soft Start
* FP7: Pushbutton, Regulated, Calibrated, Soft Start
* FP8: Test Suite for ADC, PWM, Regulation Algorithms - WARNING - BULB DANGER
*/
#define FP9 // select ONE of the programs in the suite
/***********************************************************/
#define F_CPU 8e6 // 8 mhz cpu frequency for delay timing
#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <avr/power.h>
#include <util/delay.h>
#include <avr/eeprom.h>
#include <math.h>
/***********************************************************/
#ifdef FP9 // Flashlight Program Number Nine
// This is ALPHA, partially debugged
// Feature Options in this software include:
//
// VariLevel - variable level control between two values, or on/off option
// Level Memory option
// Synchronous end of PWM pulse read ADC option
// Soft Start with fast ramp option
// Pre-calculated and Dynamic calibration options
// Blast to VMAX from double-click
// Low Battery protection backoff option
// Low standby power option
// Minimum Voltage auto-off
//
// This Flashlight Regulation and Control Program has many options
// VariLevel - This monitors the pushbutton, soft-starting to the low
// voltage setting when clicked. Clicking at low will turn off. Pressing
// will ramp toward the high voltage setting, stopping when released.
// Clicking when above the minimum value will ramp down to minimum, where
// the next click will turn off. Pressing anytime will ramp toward high.
//
// If the low and high voltage values are set to the same number the
// interface becomes click-on and click-off. Press will always turn on.
//
// If the battery voltage is too high, or too low the voltage protection
// code will turn the output. There may
// be a "moon" mode as the voltage goes low if the batteries will hold voltage
// as the load is reduced. If they recover after being off a short time
// the light will come on when clicked again, temporarily.
//
// The optional BLAST mode allows a double click from off to quickly ramp to
// the highest voltage output.
//
// Initial Calibration is provided by entering values for the three
// resistors and the chip voltage reference that determine this. The
// pot is assumed to be in the center of rotation, it can be used to
// fine tune the output, or omitted by setting R2 to zero.
//
// The PWM is
// computed by integrating the sign of the error calculation
// in a feedback loop. The fast start option adds additional pulsewidth
// when the error exceeds a threshold.
// Power Supply Testing - testing with this regulator with a power supply
// can lead to some problems. The peak current required may be more than
// the supply can do in a short timeframe. Using a large capacitor acros
// the supply can help this <move this above the programs>*******************
// Calibration
//
// Calibration precalculations: The C preprocessor resolves these calculations
// to integers so the runtime does not have to do floating point math.
// Most values in the chip are in ADC units.
//
// Using VCAL factor: this additional factor is multiplied by the
// VOLTStoADC coefficient. Increasing VCAL above the nominal 1.0 value
// will reduce the output RMS voltage. This factor is used to compensate
// for ADC calibration errors to get the pot in range, or calibrate units
// that do not have the pot installed. Replace this coefficient with the
// ratio of the actual to the desired RMS voltage: VCAL = measured / desired.
//=========================== User Settings ===================================
// bulb and battery settings
#define VLO 3.0 // bulb low setting, in RMS volts
#define VHI 9.0 // bulb high setting, in RMS volts
#define VBATT 12.0 // nominal battery voltage, in volts
//#define VBATLO 9.5 // battery low voltage protection, in volts, comment out to skip
// note - instead of having an onoff flag, just set VLO = VHI
// this looks like it will work, test it!
// options for VariLevel mode
#define BLAST // initial double click speedramp to VHI
#define LEVELMEM // remember last and return to it
// ADC calibration factors
#define R1 47e3 // resistor, +battery to pot, ohms (47K/100K)
#define R2 0 // adjustable cal pot, adc on wiper (0 if none)
#define R3 10e3 // resistor, pot to ground, 10K recommended
#define VREF 2.56 // chip ADC reference, in volts, 2.56 typical
#define VCAL 1.0 // tuning calibration tweak factor, 1.0 nominal
// program controls and timing
#define LOOPD 4.0 // main loop delay (floating point milliseconds)
#define SHORT 50 // duration of short vs long click (in main loop cycles)
#define DEBOUNCE 3 // pushbutton debounce, (in main loop cycles)
// misc program options, comment out to disable option
//#define LOPWR // enable low power battery saver mode
//#define VARICAL // variable ADC calibration in eeprom, uses up/down buttons
#define FASTSTART 25 // fast soft-start (in main loop cycles)
#define SYNCADC // read ADC at end of PWM pulse
// test options, comment out to disable option - WARNING - BULB DANGER
//#define MAXPWM 100 // maximum PWM value for bulb protection
//#define INVERT // invert the output (LO == GATE ON) (or STK500 LED)
//#define ADCOVERRIDE 240 // override ADC on STK500 when not hooked up
//#define RAWADC // output raw ADC readings to PWM
//========================= End of User Settings =============================
// Calibration Calculations and Convenience Macros
#define VOLTStoADC ( (R2*0.5+R3) / (R1+R2+R3) / VREF*255.0*VCAL )
#define VLOc ((uint8_t)(VLO * VOLTStoADC))
#define VHIc ((uint8_t)(VHI * VOLTStoADC))
#define VBATLOc ((uint8_t)(VBATLO * VOLTStoADC))
#define VMINc ((uint8_t)(4.0 * VOLTStoADC)) // min operating voltage
#define BUTTON (PINB & _BV(PINB2)) // button on PB2 to ground
#define ADC_START ADCSRA |= _BV(ADSC) /* trigger an ADC conversion */
#define ADC_WAIT while (ADCSRA & _BV(ADSC)) /* await conversion complete */
// allocate nonvolatile memory consistently, grow at end only
uint16_t EEMEM EEVersion; // version number of EE memory (unused.)
uint16_t EEMEM adcCalE; // ADC cal factor (dynamic calibration)
// global variables
uint8_t fault = 0; // fault period counter
int16_t pwm = 0; // PWM control normal value 0..255
int16_t vbulb = 0; // bulb voltage in ADC units
int16_t err; // feedback control error
uint16_t adc = 0; // ADC reading
uint16_t adcsq; // ADC squared
uint8_t rawadc; // raw ADC reading
uint8_t press = 0; // control button pressed counter
uint8_t release = 255; // control button released counter
uint8_t ontime = 0; // time since turning on
//uint8_t onclick = 0; // turning-on click flag RETIRE THIS
uint8_t uistate = 0; // user interface state FUTURE
uint8_t count = 0; // main loop counter
#ifdef LEVELMEM // level memory
uint8_t levelMem = VLOc;
#endif
#ifdef VARICAL // variable calibration in eeprom
int16_t adcCal = 0; // ADC cal factor
uint8_t cpress = 0; // Cal button press
#endif
// interrupt handlers
ISR(INT0_vect) /**/; // null handler for INT0 interrupt, nothing to do
// initialization and misc functions
void initADC() // initialize and prepare ADC for battery V reading
{
ADMUX = _BV(REFS2)+_BV(REFS1)+
_BV(ADLAR)+_BV(MUX1); // ADC ref 2.56V, left adjust, inp ADC2/PB4
DIDR0 = _BV(ADC2D); // disa binary input ADC2/PB4 power save
ADCSRA = _BV(ADEN)+_BV(ADSC)+ // ADC ena, start convert
_BV(ADPS2); // clk/16 496khz 160 rdgs
ADC_WAIT; // wait till done, discard first result
}
void readADC() // read ADC result and scale
{
ADC_WAIT; // wait for conversion complete
adc = ADCH; // get left justified 8 bit result
#ifdef ADCOVERRIDE // override ADC for testing
adc = ADCOVERRIDE;
#endif
rawadc = adc; // provide raw ADC reading
#ifdef VARICAL // variable ADC calibration in eeprom
if (adcCal < 0) // adcCal in [-255..255]
{
adc -= (adc * -adcCal) >> 10; // 25% plus or minus cal range
}
else
{
adc += (adc * adcCal) >> 10;
}
#endif
}
void init()
{
CLKPR = 128; // set clock divider for 8mhz clock
CLKPR = 0;
DDRB = _BV(DDB3); // init PB3/!OC1B as output for gate
PORTB = _BV(PORTB2)+_BV(PORTB1)+
_BV(PORTB0); // ena PB0.1.2 pullups
TCCR1 = _BV(CS13); // pwm use 8 mhz ck/128 for 244 hz
OCR1B = 255; // clean up first pulse
GTCCR = _BV(PWM1B)+_BV(COM1B0); // enable B pwm
initADC(); // setup ADC
}
int main() // Program Starts Here
{
init();
#ifdef VARICAL // variable calibration in eeprom
#define CALMAX (255)
#define CALMIN (-255)
#define CALRATE (10) // main loop cycles per cal bit change
cpress = 0;
adcCal = eeprom_read_word(&adcCalE); // get cal from EEMEM
if (adcCal > CALMAX || adcCal < CALMIN) // clamp cal to good range
adcCal = 0;
#endif
for (;;) // Main Loop never exits.
// it does two primary things -
// process pushbuttons and regulation.
{
// read and process the pushbutton(s)
if (BUTTON) // button released
{
if (release < 255) ++release;
if (release == DEBOUNCE)
{
if (press < SHORT) // short press
{
if (ontime > SHORT) // ignore the on-click
{
#ifdef BLAST
if (ontime < 3*SHORT && vbulb > 0) // if double-click-on
vbulb = VHIc; // blast to max
else
#endif
{
if (vbulb > VLOc) // if above minimum
vbulb = VLOc; // step down
else if (vbulb == VLOc) // if at minimum
vbulb = 0; // go off
}
}
}
}
if (release >= DEBOUNCE || press < DEBOUNCE) press = 0;
#ifdef LEVELMEM
if (release == 250 && vbulb > 0) levelMem = vbulb;
#endif
}
else // button pressed
{
if (press < 255) ++press; // measure press duration
if (press == DEBOUNCE && vbulb == 0) // turn on
{
#ifdef LEVELMEM
vbulb = levelMem;
#else
vbulb = VLOc; // was off, coming on
#endif
}
release = 0;
}
if (vbulb > 0 && (count & 3) == 0) // light is on, ramp every fourth cycle
// pwm can take two cycles to catch up
// need better control of ramp rate here?
{
if (vbulb < VHIc && press > SHORT) ++vbulb; // press-ramping
}
#ifdef VARICAL // variable calibration in eeprom
if (vbulb == VLOc || vbulb == VHIc) // only at known points
{
if (!(PINB & _BV(PINB1))) // if up pressed
{
if (++cpress > CALRATE)
{
cpress = 0;
if (adcCal > CALMIN) --adcCal;
}
}
else if (!(PINB & _BV(PINB0))) // if down pressed
{
if (++cpress > CALRATE)
{
cpress = 0;
if (adcCal < CALMAX) ++adcCal;
}
}
else cpress = 0;
}
#endif
if (vbulb != 0) // do RMS Voltage Regulation
{
if (ontime < 255) ++ontime;
if (vbulb > VHIc) vbulb = VHIc; // protect bulb voltage clamp
#ifdef SYNCADC // read ADC synchronized at end of PWM pulse
if (pwm > 0 && pwm < 255) // sync to PWM cycle
{
while (PINB & _BV(PB3)) /**/; // wait for PWM off
while (!(PINB & _BV(PB3))) /**/; // wait for PWM on
}
ADC_START; // start conversion Vbatt
readADC(); // read result
// get last ADC reading at end of PWM cycle
while (pwm > 0 && pwm < 255 && PINB & _BV(PB3)) // while PWM cycle is on
{
readADC(); // store previous ADC result
ADC_START; // start conversion Vbatt
ADC_WAIT; // wait till done
}
#endif
ADC_START; // start conversion Vbatt
readADC();
adcsq = (adc * adc) >> 8; // square and scale
// use this loaded conversion for FET drive voltage protection
if (adc < VMINc) ++fault; // lo volt protection
if (rawadc > 252 && vbulb > VLOc) vbulb = VLOc; // hi volt protection
// use a new unloaded conversion for battery condition
ADC_START; // start conversion Vbatt
readADC();
err = adcsq * pwm - vbulb * vbulb; // mean-square error
if (err > 0) --pwm; // integrate error
else ++pwm;
#ifdef FASTSTART // fast soft-start ramp
#define NOMPWM (VHI*VHI/(VBATT*VBATT) * 255.0) // nominal pwm
#define FASTSTEP (NOMPWM / FASTSTART) // pwm step for fast ramp
#define FASTTHRESH ((VBATT*VBATT * FASTSTEP/255.0)*VOLTStoADC*VOLTStoADC) // threshold above which to kick into fast ramp
if (err < -(int16_t)(FASTTHRESH)) // if error is large
pwm += (uint8_t)(FASTSTEP-1); // apply PWM kick
#endif
// note that fast start is going to fight with battery protection
#ifdef VBATLO
if (adc < VBATLOc) pwm -= 5; // low battery protect
#endif
if (pwm < 0) // check for PWM fault
{
++fault;
pwm = 0;
}
else if (pwm > 255) pwm = 255; // clamp PWM to valid range
}
else pwm = ontime = 0;
//pwm = vbulb; // test vbulb, bypassing regulation - WARNING - BULB AT RISK
if (fault > 240) pwm = vbulb = ontime = fault = 0; // fault trip
else if (fault > 0) --fault;
#ifdef ADCTEST // output raw adc values - WARNING - BULB AT RISK
pwm = rawadc;
#endif
#ifdef MAXPWM
if (pwm > MAXPWM) pwm = MAXPWM; // safe test clamp
#endif
#ifdef INVERT
OCR1B = pwm; // invert output (LOW = GATE ON or for LED on STK500)
#else
OCR1B = 255 - pwm; // normal output PWM to FET gate (HI = ON)
#endif
if (vbulb == 0 && release > 250) // off and no button activity
{
#ifdef VARICAL // variable calibration in eeprom
if (adcCal != eeprom_read_word(&adcCalE)) // if cal changed
eeprom_write_word(&adcCalE,adcCal); // write to eeprom
#endif
#ifdef LOPWR
// set to low power, prepare wakeup interrupt, go to sleep
// button is on INT0 pin, use low level int, no clocks req'd
// insure output is low. only run when PWM is off
// disable brownout detection? (BOD not enabled)
ADCSRA = 0; // disable ADC to save power
sleep_enable(); // prepare for sleep
sei(); // enable interrupts
MCUCR = 0b00100000; // int on INT0 low level, conserve power
GIMSK = 0b01000000; // INTO mask enable
sleep_cpu(); // sleep, interrupt on button push
sleep_disable(); // waking up
cli(); // disable interrupts
init(); // wake up, re-init, ADC on, etc
#endif
}
// estab loop rate
#ifdef SYNCADC
if (pwm == 0 || pwm == 255)
_delay_ms(LOOPD);
#else
_delay_ms(LOOPD);
#endif
++count; // loop counter
}
}
#endif // FP9
//=============================================================================
#ifdef FP8 // Test Suite, Regulation, ADC, PWM Testing
// WARNING - Several of these tests are unregulated and present a risk to
// overdriving a bulb that cannot handle the full supply voltage. Keep the
// supply voltage lower than the bulb's rated voltage for bulb protection.
// Type of Control
#define PWMS // step by PWM, choose NONE of the control algorithms (1,2,4,8,16,32,64,128,254)
//#define VOLTAGES // step by Voltage, choose ONE of the control algorithms (0,1,2,3,4,5,6,7,8,9,10,11,12)
// Choose ONE of the following Control Algorithms. Comment the rest out.
// For the special PWMS mode, comment ALL of the control algorithms out
//#define INTEGRAL // feedback with integrator, standard version
//#define INTEGER // direct PWM calc using 32 bit integers
//#define FLOATINGPOINT // direct PWM calc using floating point
//#define ADCTEST // directly output the ADC reading as PWM - WARNING - BULB AT RISK
// The scope trigger mode limits the range of the PWM to [1,254]
#define SCOPETRIG // leave an edge to trigger scope, comment out to skip
//#define SAFE 180 // max PWM value allowed to protect bulb, comment out to skip
// calibration factors
#define R1 47e3 // resistor, +battery to pot, ohms
#define R2 0 // adjustable cal pot, adc on wiper (0 if none)
#define R3 10e3 // resistor, pot to ground, 10K recommended
#define VREF 2.56 // chip ADC reference, in volts, 2.56 typical
#define VCAL 1.0 // additional calibration factor for tuning
// program controls and timing
#define LOOPD 4.0 // main loop delay (floating point milliseconds)
//#define INVERT // invert the output (LO == GATE ON) (or STK500 LED)
//#define ADCOVERRIDE 200 // override ADC on STK500 when not hooked up
// This program monitors the pushbutton, cycling through integral voltage
// regulated outputs 0.0, 1.0, 2.0 through 12.0 volts and back to 0.0.
// Each press of the button advances the regulated output 1.0 volt. Note that
// if Vbatt is not greater than 12.0 volts the PWM will go to 100% duty cycle
// at the voltage settings exceeding Vbatt.
//.
// The ADC is read at the end of the PWM cycle.
//
// The output is not soft-started except in integral feedback mode.
//
// A compile time option allows outputting the ADC reading directly as PWM.
// This allows tabulation of VbattDC versus PWM ontime in uS.
//
// Simple Calibration is provided by entering values for the three
// resistors and the chip voltage reference that determine this. The
// pot is assumed to be in the center of rotation, it can be used to
// fine tune the output with an RMS reading at the VLO or VHI levels.
//
// The Voltage is regulated by adjusting the PWM which is
// computed directly from the equations using either integer or floating
// point math, as configured. There are several algorithms to choose from,
// review the source code for more details.
//
// Using VCAL factor: this additional factor is multiplied by the
// VOLTStoADC coefficient. Increasing VCAL above the nominal 1.0 value
// will reduce the output RMS voltage. This factor is used to compensate
// for ADC calibration errors to get the pot in range, or calibrate units
// that do not have the pot installed. Replace this coefficient with the
// ratio of the actual to the desired RMS voltage: VCAL = measured / desired.
#define VOLTStoADC ( (R2*0.5+R3) / (R1+R2+R3) / VREF*255.0*VCAL )
void initADC() // initialize and prepare ADC for battery V reading
{
ADMUX = _BV(REFS2)+_BV(REFS1)+_BV(ADLAR)+_BV(MUX1); // adc ref 2.56V, left adjust, inp ADC2/PB4
DIDR0 = _BV(ADC2D); // disa binary input on ADC2/PB4
ADCSRA = _BV(ADEN)+_BV(ADSC)+_BV(ADPS2)+_BV(ADPS1)+_BV(ADPS0); // adc ena, start convert, clk/128 62khz
while (ADCSRA & _BV(ADSC)) /**/; // wait till done, discard result
}
void init()
{
CLKPR = 128; // set clock divider for 8mhz clock
CLKPR = 0;
DDRB = _BV(DDB3); // init PB3/OC1Bnot as output for gate
PORTB = _BV(PORTB2)+_BV(PORTB1)+
_BV(PORTB0); // ena PB2 pullup to observe pushbutton
TCCR1 = _BV(CS13); // pwm use 8 mhz ck/128 for 244 hz
OCR1B = 255; // clean up first pulse
GTCCR = _BV(PWM1B)+_BV(COM1B0); // enable B pwm
initADC();
}
#define BUTTON (PINB & _BV(PINB2)) // button PB2 to ground, use internal pullup
#define DEBOUNCE 3 // pushbutton debounce, in main loop intervals
int16_t pwm = 0;
uint16_t vbulb = 0;
uint8_t adc;
uint8_t adcsq;
uint8_t press = 0;
float level = 0.0;
int main()
{
init();
for (;;)
{
// read and debounce the pushbutton
if (BUTTON) // button released
{
if (press > 0) --press;
if (press == DEBOUNCE) press = 0;
}
else // button pressed
{
if (press < DEBOUNCE * 2) ++press; // measure press duration
if (press == DEBOUNCE)
{
#ifdef VOLTAGES // switch cycles by one volt from 0 to 12
if (level < 11.1) level += 1.0;
else level = 0.0;
vbulb = level * VOLTStoADC;
#endif
#ifdef PWMS // switch cycles by PWM in 1,2,4,8,16,32,64,128,254
pwm += pwm;
if (pwm == 0) pwm = 1;
if (pwm > 256) pwm = 1;
if (pwm > 254) pwm = 254;
#endif
}
}
// do RMS Voltage Regulation
if (pwm > 0 && pwm < 255) // sync to PWM cycle
{
while (PINB & _BV(PB3)) /**/; // wait for PWM off
while (!(PINB & _BV(PB3))) /**/; // wait for PWM on
}
ADCSRA |= _BV(ADSC); // start conversion Vbatt
while (ADCSRA & _BV(ADSC)) /**/; // wait till done
adc = ADCH; // read result
// get last ADC reading at end of PWM cycle
while (pwm > 0 && pwm < 255 && PINB & _BV(PB3)) // while PWM cycle is on
{
adc = ADCH; // store previous ADC result
ADCSRA |= _BV(ADSC); // start conversion Vbatt
while (ADCSRA & _BV(ADSC)) /**/; // wait till done
}
#ifdef ADCOVERRIDE
adc = ADCOVERRIDE; // test only, force ADC result
#endif
#ifdef INTEGRAL
adcsq = (adc * adc) >> 8; // square and scale
if (adcsq * pwm >= vbulb * vbulb) --pwm; // integrate error
else ++pwm;
if (vbulb == 0) pwm = 0; // insure off is off
#endif
#ifdef INTEGER
pwm = (int32_t)(255)*(int32_t)(vbulb)*(int32_t)(vbulb)/
((int32_t)(adc)*(int32_t)(adc)); // direct pwm calculation
if (vbulb == 0) pwm = 0; // insure off is off
#endif
#ifdef FLOATINGPOINT
pwm = 255.0*(float)(vbulb)*(float)(vbulb)/
((float)(adc)*(float)(adc)); // direct pwm calculation
if (vbulb == 0) pwm = 0; // insure off is off
#endif
if (pwm < 0) pwm = 0; // clamp pwm to valid range
if (pwm > 255) pwm = 255;
#ifdef ADCTEST // ADC to PWM - WARNING - BULB AT RISK
pwm = adc;
#endif
#ifdef SCOPETRIG
if (pwm < 1) pwm = 1; // always trigger the scope
if (pwm > 254) pwm = 254;
#endif
#ifdef SAFE
// 14V to 10V is 180 for testing to limit output voltage and protect bulb
if (pwm > SAFE) pwm = SAFE; // protection clamp for testing
#endif
#ifdef INVERT
OCR1B = pwm; // invert output (LOW = GATE ON or for LED on STK500)
#else
OCR1B = 255 - pwm; // output pwm to FET gate (HI = ON)
#endif
if (pwm == 0 || pwm == 255) // sync with PWM unless there is none
_delay_ms(LOOPD); // estab loop rate
}
}
#endif // FP8
/***********************************************************/
#ifdef FP7 // Regulated, Pushbutton control with Soft Start Program
// This program has voltage regulation and soft start.
// bulb settings
#define VBULB 4.0 // bulb voltage setting, in RMS volts
// calibration factors
#define R1 47e3 // resistor, +battery to pot, ohms
#define R2 0 // adjustable cal pot, adc on wiper (0 if none)
#define R3 10e3 // resistor, pot to ground, 10K recommended
#define VREF 2.56 // chip ADC reference, in volts, 2.56 typical
#define VCAL 1.0 // additional calibration factor for tuning
// program controls and timing
#define LOOPD 4.0 // main loop delay (floating point milliseconds)
//#define INVERT // invert the output (LO == GATE ON) (or STK500 LED)
//#define ADCOVERRIDE 200 // override ADC on STK500 when not hooked up
// This program monitors the pushbutton, turning on and off, soft starting and
// regulating the bulb voltage. ADC is unsynchronized.
//
// Simple Calibration is provided by entering values for the three
// resistors and the chip voltage reference that determine this. The
// pot is assumed to be in the center of rotation, it can be used to
// fine tune the output with an RMS reading at the VLO or VHI levels.
//
// The Voltage regulated by adjusting the PWM which is
// computed by integrating the sign of the error calculation
// in a feedback loop.
//
// Calibration precalculations: The C preprocessor resolves these
// to integers so the runtime does not have floating point.
// Everything in the chip is in ADC units.
//
// Using VCAL factor: this additional factor is multiplied by the
// VOLTStoADC coefficient. Increasing VCAL above the nominal 1.0 value
// will reduce the output RMS voltage. This factor is used to compensate
// for ADC calibration errors to get the pot in range, or calibrate units
// that do not have the pot installed. Replace this coefficient with the
// ratio of the actual to the desired RMS voltage: VCAL = measured / desired.
#define VOLTStoADC ( (R2*0.5+R3) / (R1+R2+R3) / VREF*255.0*VCAL )
#define VBc ((uint8_t)(VBULB * VOLTStoADC))
void initADC() // initialize and prepare ADC for battery V reading
{
ADMUX = _BV(REFS2)+_BV(REFS1)+_BV(ADLAR)+_BV(MUX1); // adc ref 2.56V, left adjust, inp ADC2/PB4
DIDR0 = _BV(ADC2D); // disa binary input on ADC2/PB4
ADCSRA = _BV(ADEN)+_BV(ADSC)+_BV(ADPS2)+_BV(ADPS1)+_BV(ADPS0); // adc ena, start convert, clk/128 62khz
while (ADCSRA & _BV(ADSC)) /**/; // wait till done, discard result
}
void init()
{
CLKPR = 128; // set clock divider for 8mhz clock
CLKPR = 0;
DDRB = _BV(DDB3); // init PB3/OC1Bnot as output for gate
PORTB = _BV(PORTB2)+_BV(PORTB1)+
_BV(PORTB0); // ena PB2 pullup to observe pushbutton
TCCR1 = _BV(CS13); // pwm use 8 mhz ck/128 for 244 hz
GTCCR = _BV(PWM1B)+_BV(COM1B0); // enable B pwm
initADC();
}
#define BUTTON (PINB & _BV(PINB2)) // button PB2 to ground, use internal pullup
#define DEBOUNCE 3 // pushbutton debounce, in main loop intervals
int16_t pwm,vbulb;
uint8_t adc;
uint8_t adcsq;
//int32_t ad,vb,pw;
//uint16_t ad,vb,pw,adsq;
uint8_t press;
int main()
{
vbulb = pwm = press = 0;
init();
#ifdef CKMULT
ad = 255; // math check hangs on bad multiply
if ((ad * ad) != 65025L)
{
for (;;) /**/;
}
#endif
for (;;)
{
// read and debounce the pushbutton
if (BUTTON) // button released
{
if (press > 0) --press;
if (press == DEBOUNCE) press = 0;
}
else // button pressed
{
if (press < DEBOUNCE * 2) ++press; // measure press duration
if (press == DEBOUNCE)
{
if (vbulb == 0) vbulb = VBc; // was off, coming on
else vbulb = pwm = 0; // on going off
}
}
// do RMS Voltage Regulation
ADCSRA |= _BV(ADSC); // start conversion battery V
while (ADCSRA & _BV(ADSC)) /**/; // wait till done
adc = ADCH; // get ADC result
#ifdef ADCOVERRIDE
adc = ADCOVERRIDE; // test only, force adc result
#endif
//ad = adc; // 16/32 bit test
//vb = vbulb;
//pw = pwm;
//adsq = (ad * ad)>>8;
//if (adsq * pw >= vb * vb) --pwm; // integrate error
//else ++pwm;
//if (ad * ad * pw >= vb * vb * 255) --pwm; // integrate error
//else ++pwm;
adcsq = (adc * adc) >> 8; // square and scale
if (adcsq * pwm >= vbulb * vbulb) --pwm;
else ++pwm;
if (adc > 250) pwm -= 2; // overvoltage protect
if (pwm < 0) // shut off
{
pwm = 0;
//vbulb = 0;
}
if (pwm > 255) pwm = 255; // clamp pwm to valid range
//pwm = adc; // adc testing
//if (pwm > 64) pwm = 64; // protection clamp for testing
#ifdef INVERT
OCR1B = pwm; // invert output (LOW = GATE ON or for LED on STK500)
#else
OCR1B = 255 - pwm; // output pwm to FET gate (HI = ON)
#endif
_delay_ms(LOOPD); // estab loop rate
}
}
#endif // FP7
/***********************************************************/
#ifdef FP6 // Regulated always-on with Soft Start Program
// This program has voltage regulation and soft start. It is suitable
// for a light with a mechanical switch, it will regulate whenever it
// has power.
// bulb settings
#define VBULB 4.0 // bulb voltage setting, in RMS volts
// calibration factors
#define R1 47e3 // resistor, +battery to pot, ohms
#define R2 0 // adjustable cal pot, adc on wiper (0 if none)
#define R3 10e3 // resistor, pot to ground, 10K recommended
#define VREF 2.56 // chip ADC reference, in volts, 2.56 typical
#define VCAL 1.0 // additional calibration factor for tuning
// program controls and timing
#define LOOPD 4.0 // main loop delay (floating point milliseconds)
#define INVERT // invert the output (LO == GATE ON) (or STK500 LED)
// This program monitors the battery voltage, soft starting and
// regulating the bulb voltage. ADC is unsynchronized.
//
// Simple Calibration is provided by entering values for the three
// resistors and the chip voltage reference that determine this. The
// pot is assumed to be in the center of rotation, it can be used to
// fine tune the output with an RMS meter at the VLO or VHI levels.
//
// The Voltage regulated by adjusting the PWM which is
// computed by integrating the sign of the error calculation
// in a feedback loop.
//
// Calibration precalculations: The C preprocessor resolves these
// to integers so the runtime does not have floating point.
// Everything in the chip is in ADC units.
//
// Using VCAL factor: this additional factor is multiplied by the
// VOLTStoADC coefficient. Increasing VCAL above the nominal 1.0 value
// will reduce the output RMS voltage. This factor is used to compensate
// for ADC calibration errors to get the pot in range, or calibrate units
// that do not have the pot installed. Replace this coefficient with the
// ratio of the actual to the desired RMS voltage: VCAL = measured / desired.
#define VOLTStoADC ( (R2*0.5+R3) / (R1+R2+R3) / VREF*255.0*VCAL )
#define VBc ((uint8_t)(VBULB * VOLTStoADC))
int16_t pwm;
uint16_t adc,adcsq,vbulb;
void initADC() // initialize and prepare ADC for battery V reading
{
ADMUX = _BV(REFS2)+_BV(REFS1)+_BV(ADLAR)+_BV(MUX1); // adc ref 2.56V, left adjust, inp ADC2/PB4
DIDR0 = _BV(ADC2D); // disa binary input on ADC2/PB4
ADCSRA = _BV(ADEN)+_BV(ADSC)+_BV(ADPS2)+_BV(ADPS1)+_BV(ADPS0); // adc ena, start convert, clk/128 62khz
while (ADCSRA & _BV(ADSC)) /**/; // wait till done, discard result
}
void init()
{
CLKPR = 128; // set clock divider for 8mhz clock
CLKPR = 0;
DDRB = _BV(DDB3); // init PB3/OC1Bnot as output for gate
PORTB = _BV(PORTB2)+_BV(PORTB1)+_BV(PORTB0); // ena PB2 pullup to observe pushbutton action
TCCR1 = _BV(CS13); // pwm use 8 mhz ck/128 for 244 hz
GTCCR = _BV(PWM1B)+_BV(COM1B0); // enable B pwm
initADC();
}
int main()
{
pwm = 0;
vbulb = VBc;
init();
for (;;) // do RMS Voltage Regulation
{
ADCSRA |= _BV(ADSC); // start conversion battery V
while (ADCSRA & _BV(ADSC)) /**/; // wait till done
adc = ADCH; // get ADC result
#ifdef ADCOVERRIDE
adc = 240; // test only, force adc result ***********
#endif
adcsq = (adc * adc) >> 8; // square and scale
if (adcsq * pwm >= vbulb * vbulb) --pwm; // integrate error
else ++pwm;
if (adc > 250) pwm -= 10; // overvoltage protect
if (pwm < 0) // shut off
{
pwm = vbulb = 0;
}
if (pwm > 255) pwm = 255; // clamp pwm to valid range
#ifdef INVERT
OCR1B = pwm; // invert output (LOW = GATE ON or for LED on STK500)
#else
OCR1B = 255 - pwm; // output pwm to FET gate (HI = ON)
#endif
_delay_ms(LOOPD); // estab loop rate
}
}
#endif // FP6
/***********************************************************/
#ifdef FP5 // Ramped, Pushbutton VariLevel PWM with Soft Start Test Program
#define PWM1 64 // low setting
#define PWM2 255 // high setting
// This program monitors the pushbutton, ramping the PWM up to PWM1
// when pressed. If pressed briefly it will then go off. If pressed
// and held it will ramp up towards PWM2.
//
// The PWM value is ramped in a linear fashion.
// This program does not have regulation, low
// battery protection or low power consumption. It is intended
// as a simple test program rather than an operational flashlight
// program.
void init()
{
CLKPR = 128; // set clock divider for 8mhz clock
CLKPR = 0;
DDRB = 8; // init PB3 as output for gate
PORTB = 7; // ena pullup to observe pushbutton action
TCCR1 = 0b00001000; // use 8 mhz ck/128 for 244 hz pwm
GTCCR = 0b01010000;
}
#define BUTTON (PINB & 4) // Pushbutton is on PB2 to ground
int main()
{
uint16_t pwm;
uint8_t press;
pwm = press = 0;
init();
for (;;)
{
// read and debounce the pushbutton
if (BUTTON) // button released
{
if (press > 0) --press;
if (press > 3)
{
if (press < 50 && pwm >= 50) // short press going off
{
pwm = 0;
}
press = 3;
}
}
else // button pressed
{
if (press < 255) ++press; // measure press duration
if (press < 3) press = 3;
if (pwm == 0) pwm = 1; // was off, coming on
}
if (pwm > 0) // light is on
{
if (pwm < PWM1) ++pwm; // soft starting
else
{
if (pwm < PWM2 && press > 50) ++pwm; // ramping
}
}
OCR1B = 255 - pwm; // output pwm to FET gate
//OCR1B = pwm; // invert output for LED on STK500
_delay_ms(10.0);
}
}
#endif // FP5
/***********************************************************/
#ifdef FP4 // Pushbutton operated fixed PWM with Soft Start Test Program
#define PWM 16 // on PWM value [1..255]
// This program monitors the pushbutton, turning the PWM output to
// the FET on when the button is pressed, and off when pressed again.
// The PWM value is ramped up from zero to a fixed value in a linear
// fashion.
// This program does not have regulation, low
// battery protection or low power consumption. It is intended
// as a simple test program rather than an operational flashlight
// program.
void delay() // delay 5ms, (should conserve power)
{
_delay_ms(5.0);
}
void pwm_init()
{
TCCR1 = 0b00001000; // use 8 mhz ck/128 for 244 hz pwm
GTCCR = 0b01010000;
}
void pwm_on() // ramp pwm output up to value
{
uint16_t i;
for (i = 1; i <= PWM; ++i)
{
OCR1B = 255 - i; // gate drive inverted output, 0 is fully on
_delay_ms(4.0); // reduce this to speed up ramp
}
}
void pwm_off()
{
OCR1B = 255; // gate driven from inverting output, 255 is off
}
#define BUTTON (PINB & 4) // Pushbutton is on PB2 to ground
void waitForButton() // Wait for push on button PB2
{
uint8_t i; i = 3;
for (;;) // wait for pushbutton release
{
if (BUTTON)
{
if (--i == 0)
break;
}
else
i = 3;
delay();
}
for (;;)
{
if (!BUTTON) return;
delay();
}
}
int main()
{
CLKPR = 128; // set clock divider for 8mhz clock
CLKPR = 0;
DDRB = 8; // init PB3 as output for gate
PORTB = 7; // ena pullup to observe pushbutton action
pwm_init();
for (;;)
{
pwm_off(); // set gate output low (light off)
waitForButton();
pwm_on(); // set gate output high (light on)
waitForButton();
}
}
#endif // FP4
/***********************************************************/
#ifdef FP3 // Fixed always-on PWM with Soft Start Test Program
#define PWM 16 // on PWM value
//#define INVERT // for gate low = on, else comment out
// The PWM value is ramped up from zero to a fixed value in a linear
// fashion. This would be good for a light with a mechanical power switch
// that needs soft start and voltage reduction via PWM.
int main()
{
uint16_t pwm;
CLKPR = 128; // set clock divider for 8mhz clock
CLKPR = 0;
DDRB = 8; // init PB3 as output for gate
PORTB = 7; // ena pullups
TCCR1 = 0b00001000; // use 8 mhz ck/128 for 244 hz pwm
GTCCR = 0b01010000;
for (pwm = 0; pwm <= PWM; ++pwm)
{
#ifdef INVERT
OCR1B = pwm; // gate low = on for STK500 LED
#else
OCR1B = 255 - pwm; // gate high = on
#endif
_delay_ms(4);
}
for (;;) /**/ ; // done, wait
}
#endif // FP3
/***********************************************************/
#ifdef FP2 // Electronic Pushbutton Switch Program
// This program monitors the pushbutton, turning the FET fully on
// when the button is pressed, and back off when pressed again.
// This program does not have soft start, voltage regulation, low
// battery protection or low power consumption. It is intended
// as a simple test program rather than an operational flashlight
// program.
// WARNING - Insure the bulb can take continuous battery voltage
void delay() // delay 5ms, (should conserve power)
{
_delay_ms(5.0);
}
#define BUTTON (PINB & 4) // Pushbutton on PB2
void waitForButton() // Wait for push on button PB2
{
uint8_t i; i = 3;
for (;;) // wait for pushbutton release
{
if (BUTTON)
{
if (--i == 0)
break;
}
else
i = 3;
delay();
}
for (;;)
{
if (BUTTON == 0) return;
delay();
}
}
int main()
{
CLKPR = 128; // set clock divider for 8mhz clock
CLKPR = 0;
DDRB = 8; // init PB3 as output for gate
PORTB = 7; // ena pullup to observe pushbutton action
for (;;)
{
PORTB = 0+7; // set gate output low (light off) and maintain pullups
waitForButton();
PORTB = 8+7; // set gate output high (light on) and maintain pullups
waitForButton();
}
}
#endif // FP2
/***********************************************************/
#ifdef FP1 // Test Program #1 toggles the FET Gate Output
#define SLOW // toggle rate for FP1
// WARNING - Insure the bulb can take continuous battery voltage
// If this seems to run slow, the clock/8 fuse is probably set and
// everything will take 8 times longer. This is controlled by the
// programmer under the fuse bit window.
void delay() // if SLOW defined delay about 1 S
{
#ifdef SLOW
_delay_ms(1000.0); // 1 second on, 1 second off
#else
_delay_ms(5.0); // 100 hz
#endif
}
int main()
{
CLKPR = 128; // set clock divider for 8mhz clock
CLKPR = 0;
DDRB = 9; // init PB3 as output for gate
for (;;)
{
PORTB = 9; // set gate output high
delay();
PORTB = 0; // set gate output low
delay();
}
}
#endif // FP1
/***********************************************************/
#ifdef EEMEMTEST // eeprom test
// this program initializes the second location in the eeprom to 3,
// and then blinks the led as many times as that location in the eeprom,
// and then writes the value plus one back to that location and stops
//
// when the chip is power cycled it reads the location, blinks the led
// and again writes the next value.
//
// so the first time this runs after loading it blinks 3 times, then after
// each power cycle it adds one to the blink count.
uint8_t i,j;
uint8_t EEMEM first; // allocate byte in eeprom uninitialized
uint8_t EEMEM test1 = 3; // allocate byte in eeprom initialized at prog load
int main()
{
CLKPR = 128; // set clock divider for 8mhz clock
CLKPR = 0;
DDRB = 8; // led on the pin, lit when low
j = eeprom_read_byte(&test1);
for (i = 0; i < j; ++i)
{
PORTB = 0; // stk500 led on
_delay_ms(500.0);
PORTB = 8; // led off
_delay_ms(500.0);
}
eeprom_write_byte(&test1,++i);
for (;;) /**/ ;
}
#endif
/***********************************************************/
// test delay calibration. seems fine in sim. much slower in stk500.
// must be clock rate in chip. clock divide by 8 was fused on! fixed.
#ifdef DLY
int main()
{
_delay_ms(10.0);
}
#endif
/***********************************************************/
// eof