Projects‎ > ‎Programming‎ > ‎ATMEL AVR‎ > ‎


This code was used in a touch light controller for the Illuminate Project. In reads in a single touch sensor and pulses the gate of a MOSFET to control the brightness of an LED bank. There are two brightness levels, and the unit goes into sleep mode when it not controlling any LEDs. Because this circuit is going into a camper which is often run from batteries, low power consumption is a priority. The MCU shouldn't be drawing (much) power when it is not in use.

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>


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_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;


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.


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


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

LOOP_UNTIL_BIT_LO(TOUCH_PIN, TOUCH); // Delay until not touching


  Click here to download all of the necessary source code files!