Included Header Files
I used my standard custom AVR.h file in addition to the sleep.h file which comes with WinAVR. The sleep.h file contains all of the macros necessary to enable and enter into sleep mode.
#include <custom/avr.h>
#include <avr/sleep.h>
Definitions
I only had a few constants in this program: the HIGH and LOW values for the output compare register. Otherwise, I gave each MCU pin a custom name and included a couple of status flag bits.
// Constants
#define LOW_LIGHT
51
// Low Intensity LED Output - 51/255 = 20%
#define HIGH_LIGHT
178 // High Intesity LED Output - 178/255 = 70%
// Hardware Definitions
#define LED
_BV(PB0)
// Pin 5 - OC0A
#define NA1
_BV(PB1)
// Pin 6 - OC0B
#define TOUCH
_BV(PB2)
// Pin 7 - INT0
#define NA3
_BV(PB3)
// Pin 2 - XTAL1
#define NA4
_BV(PB4)
// Pin 3 - XTAL2
#define RESET
_BV(PB5)
// Pin 1 - !RESET
#define LED_PORT
PORTB
#define LED_DDR
DDRB
#define TOUCH_PORT
PORTB
#define TOUCH_DDR
DDRB
#define TOUCH_PIN
PINB
// Status Flag Bits
#define LED_MODE
0x10
// 0 = LOW, 1 = HIGH
#define LED_STATE
0x20
// 0 = OFF, 1 = ON
Global Variables
There was only one global variable - the eight bit status flag. This has to be global because it is used in the interrupt handlers. The two defined status flag bits are a part of this variable.
static volatile uint8_t stat_flag = 0x00;
Main
The main part of the program is divided into two sections. First, the AVR chip is initialized. It then enters a forever loop. In the initialization, the power reduction register is set to shut off all features. A timer is then set up for the pulse to control the LED brightness. The reading of the sensor is handled entirely within a "pin change" interrupt, so that had to be enabled on the pin connected to the touch sensor. Finally, the program puts the MCU to sleep while it waits on input from the sensor. The sleep mode is set to "power down" for maximum energy savings.
/**************************************************************************
Main
***************************************************************************/
int main (void)
{
/****************************
Initialize ATtiny25 Device
****************************/
cli();
// Turn off interrupts
// Power Reduction Register
PRR =
_BV(PRTIM0) |
// Timer0 - 118.2uA
_BV(PRTIM1) |
// Timer1 - 153.0uA
_BV(PRUSI) |
// USI
- 92.2 uA
_BV(PRADC);
// ADC
- 333.3uA
// Set I/O Ports
DDRB = LED;
// Set as Output
PORTB = 0x00;
// Outputs - Low; Inputs - Pulls Ups Disabled
// Set up Timer 0 for 60 Hz LED Pulse -> 1MHz / (64 * 256)
PRR &= ~_BV(PRTIM0);
// Disable Timer Power Save
TCCR0A = _BV(WGM00) | _BV(WGM01) | _BV(COM0A1);
// PWM, clear OC0A on Match
TCCR0B = _BV(CS01) | _BV(CS01);
// Prescaler = 64
OCR0A = LOW_LIGHT;
// Duty Cycle = 20% = (51/255)
// Enable Pin Change Interrupt (INT0 only wakes Power Down Sleep Mode on Low Level)
PCMSK = _BV(PCINT2);
// Pin Change Interrupt on PB2
GIMSK = _BV(PCIE);
// Enable Pin Change Interrupt
// Enable Sleep Mode for Power Down
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // Set Sleep Mode: Power Down
sleep_enable(); // Enable Sleep Mode
// Disable Timer0 and Output Until Sensor is touched
// This helps ensure LEDs are off on Power Up
PRR &= ~_BV(PRTIM0); // Enable Timer0 Power Save
TCCR0A &= ~_BV(COM0A1); // Disable OC0A Output
PORTB &= ~LED; // Set LED output LO
sei(); // Enable Interrupts
In the next section, the program loops indefinitely. The only function of this part is to check if sleep has been enabled. If so, the MCU is put to sleep while it waits for input from the sensor.
/****************************
Enter Main Program Loop
****************************/
for(;;)
{
if (MCUCR & _BV(SE)){
// If Sleep is Enabled
cli();
// Disable Interrupts
sleep_bod_disable();
// Disable BOD
sei();
// Enable Interrupts
sleep_cpu();
// Go to Sleep
sleep_disable();
// Disable Sleep on Wakeup
}
}
}
Interrupt Handlers
The only interrupt in use is for the pin change. This happens asynchronously, so it can be used to wake the MCU from sleep mode. Because I only care when the sensor is touched, I filter out the negative edges of the pin change with an 'if' statement. A 'switch' statement is then used to determine the correct action based on the state and mode of the LEDs. If they are off, the lights turn on in LOW mode. The program would then either enter HIGH mode and turn the lights off on subsequent interrupts. A final loop is used to hold the program until the user stops touching the sensor. Without this loop, this interrupt would be continuously triggered while the sensor is being touched.
/**************************************************************************
INTERRUPT HANDLERS
***************************************************************************/
ISR(PCINT0_vect)
// Pin Change Interrupt Request 0 - Touch Sensor
{
sleep_disable();
// Disable Sleep on Wakeup
if(TOUCH_PIN & TOUCH){
// if touched...
switch (stat_flag & 0x30){
// Check LED State and Mode
case 0x20:
// State = ON, Mode = LOW
SET_BIT(stat_flag, LED_MODE);
// Set mode to HIGH
OCR0A = HIGH_LIGHT;
// Set Output to HIGH
break;
case 0x30:
// State = ON, Mode = HIGH
CLEAR_BIT(stat_flag, LED_STATE);
// Set state to OFF
PRR &= ~_BV(PRTIM0);
// Enable Timer0 Power Save
TCCR0A &= ~_BV(COM0A1);
// Disable OC0A Output
sleep_enable();
// Enable Sleep Mode
break;
default:
// State = OFF, Mode = don't care
SET_BIT(stat_flag, LED_STATE);
// Set state to ON
CLEAR_BIT(stat_flag, LED_MODE);
// Set mode to LOW
OCR0A = LOW_LIGHT;
// Set Output to LOW
PRR &= ~_BV(PRTIM0);
// Disable Timer0 Power Save
TCCR0A |= _BV(COM0A1);
// Enable OC0A Output
break;
}
LOOP_UNTIL_BIT_LO(TOUCH_PIN, TOUCH);
// Delay until not touching
}
}
Downloads
Click here to download all of the necessary source code files!