touch.c


// touch.c

/************************************************************************

         <<< ATtiny15L Controlled "Touch Lamp" >>>

ATtiny15L AVR microcontroller controls a "touch lamp" which is equipped
with a 6V incandescent light bulb and 4 AA batteries (in series).  Power
from the AA battery pack is sent through a diode for reverse-voltage
protection, and to ensure that the 6.0V maximum input voltage of the
ATtiny15L is not exceeded.  (Note that 4 fresh AA batteries in series
will generate ~6.5 VDC.)

The "touch lamp" switch is modified to defeat its normal on/off "toggle"
behavior.  This is done by extracting a spring-like wire from inside the
switch using needle-nose pliers.  The switch becomes a "momentary-on"
push-button switch.  Wire one end of the switch to ground.  Wire the
other end to an I/O pin on the microcontroller.  This firmware programs
this I/O pin as an input and enables the pin's ~20K on-chip pull-up.

The incandescent lamp is controlled by N-channel MOSFET.  The gate of the
MOSFET is driven by a microcontroller output pin.  One side of the lamp
is wired to 6 VDC and other side of lamp is wired to MOSFET.  When on,
the MOSFET conducts lamp current to ground.  Typical current through
lamp is 0.3-0.5 amps.  By wiring the MOSFET gate to the microcontroller's
PWM output, the lamp can be dimmed with a total of 256 brightness levels
using the PWM function of the ATtiny15L.

Behavior:

When lamp is off, pressing the switch once lights the lamp for 1 minute.
Pressing switch multiple times immediately after the first press increases
"on time" by factors of two as follows:

   1 time     1 minute
   2 times    2 minutes
   3 times    4 minutes
   4 times    8 minutes
   5 times   16 minutes
   6 times   32 minutes (maximum)

When the lamp is on, pressing the switch turns the lamp off, except
during "warning period".  The "warning period" begins 10 seconds before
the lamp is due to turn off.  The beginning of the "warning period" is
signaled by the lamp flashing twice.  At the end of the "warning period"
the lamp slowly dims until the lamp is completely off.

Pressing the switch during the "warming period", or while the lamp is
dimming, will reset the lamp "on time" to the original period.

This firmware puts the ATtimy15L in "deep sleep" mode until the
push-button switch is pressed.  Total current consumption during while
in this mode a a few microamps.  The "pin change" interrupt is used to
wake-up the microcontroller from "sleep mode".  The default interrupt
handler for "pin change interrupt" is used, which is simply a RETI
instruction.  Note that interrupts are disabled while the lamp is on in
order to inhibit "pin change interrupts" from the PWM output.

B.D. Lightner        Thu Jan 23 21:40:06 PST 2003

************************************************************************/

#include "iodefs.h"
#include "timer.h"

//#define TESTING

#ifdef TESTING
#define LAMP_PULSE_SECS 4
#define LAMP_WARN_SECS 3
#else /* !TESTING */
#define LAMP_PULSE_SECS 60
#define LAMP_WARN_SECS 10
#endif /* !TESTING */

#define MULT_MAX 5      /* max. on time = (LAMP_PULSE_SECS << MULT_MAX) */

//
// lamp with N-chan FET driver
//
#define lamp_on()  dac_set(0)
#define lamp_off() dac_set(255)
#define lamp_set(x)  dac_set(255-(x))

#define wdr()  asm volatile ("wdr" ::)
#define sei()  asm volatile ("sei" ::)
#define cli()  asm volatile ("cli" ::)

#define lamp_init() \
    timer1_stop(); \
    sbi(DDR(LAMP_PORT),LAMP_BIT);  /* make LAMP driver output */ \
    /* setup DAC (PWM mode, active low, fast clock) */ \
    timer1_init(PWM_ON, COM1A_SET, T1_CK, 0, 0); \
    lamp_off()

#define pbsw_init() \
    cbi(DDR(PBSW_PORT), PBSW_BIT);  /* make input */ \
    sbi(PBSW_PORT, PBSW_BIT)        /* enable pull-up */

#define is_pbsw_inactive() bit_is_set(PINS(PBSW_PORT), PBSW_BIT)

#define is_pbsw_active() bit_is_clear(PINS(PBSW_PORT), PBSW_BIT)

void wait_csecs(
    unsigned short csecs      // number of centiseconds to wait (0.01 secs)
){
    while (csecs -= BYTE(1)) {
        timer0_start(T0_CK64);
        while (timer0_get() < US2T0CNT64(10000)) {
            wdr();
        }
    }
}

int main(void)
{
    unsigned short secs;
    unsigned char mcusr;
    unsigned char mult;
    unsigned char n;

    osccal();   // calibrate internal processor clock

    mcusr = read_mcusr();

    outb((SM_PWROFF << SM0), MCUCR);      // select power down sleep mode
    sbi(MCUCR, SE);                       // enable sleep mode

    outb((1 << PCIE), GIMSK);             // enable pin-change interrupt

    pbsw_init();
    lamp_init();

#if 1
    if ((mcusr & (1 << WDRF)) == 0) {
        // pulse LED after reset
#define XXX 20
        lamp_on(); wait_csecs(XXX); lamp_off();
        wait_csecs(XXX);
        lamp_on(); wait_csecs(XXX); lamp_off();
        wait_csecs(XXX);

        // pulse lamp on startup if power-on reset
        if ((mcusr & (1 << PORF)) != 0) {
            lamp_on(); wait_csecs(XXX); lamp_off();
            wait_csecs(XXX);
        }
        wait_csecs(20);
    }
#endif

    wdr();
    ///outb((1 << WDE) | WD_1024K, WDTCR);   // enable watchdog timer

    sei();
    while (1) {
        ///lamp_on(); wait_csecs(10); lamp_off(); wait_csecs(10);
        wait_csecs(2);
        if (is_pbsw_active()) {
            cli();   // interrupts off (no pin-change INTs)
            mult = 0;
            lamp_on();
            wait_csecs(2);  // debounce
            while(is_pbsw_active()) wdr();  // wait for button release
            wait_csecs(2);  // debounce

            for (secs = 0; secs < (100 >> 2); secs += BYTE(1)) {
                wait_csecs(1 << 2);
                if (is_pbsw_active()) {
                    lamp_off();
                    wait_csecs(2);  // debounce
                    ++mult;
                    if (mult > MULT_MAX) mult = MULT_MAX;
                    while(is_pbsw_active()) wdr();  // wait for button release
                    wait_csecs(2);  // debounce
                    secs = 0;
                    lamp_on();
                }
            }

more:
            for (secs = (LAMP_PULSE_SECS << mult); secs > BYTE(0);  secs -= BYTE(1)) {
                lamp_on();
                for (n = 10; n; --n) {
                    wait_csecs(10);
                    if (is_pbsw_active()) {
                        lamp_off();
                        wait_csecs(2);  // debounce
                        while(is_pbsw_active()) wdr();  // wait for button release
                        wait_csecs(2);  // debounce
                        if (secs <= LAMP_WARN_SECS) goto more;
                        goto off;
                    }
                }
                if (secs == LAMP_WARN_SECS) {
                    lamp_set(64);
                    wait_csecs(25);
                    lamp_on();
                    wait_csecs(25);
                    lamp_set(64);
                    wait_csecs(25);
                    lamp_on();
                }
            }
            for (n = 255; n >= 32; n -= n >> 4) {
                lamp_set(n);
                wait_csecs(20);
                if (is_pbsw_active()) {
                    lamp_on();
                    wait_csecs(2);  // debounce
                    while(is_pbsw_active()) wdr();  // wait for button release
                    wait_csecs(2);  // debounce
                    goto more;
                }
            }
            lamp_off();
            wait_csecs(10);
        }
off:
        lamp_off();
        sei();
        sleep();
    }
}

Back