/*
ATtiny85 Pin Change Interrupts, all on PCINT0 vector 3.
🧠 Register Overview
Register Purpose
GIMSK Enable pin change interrupt (PCIE)
PCMSK Choose which pin(s) can trigger interrupt
GIFR Flag register
sei() Enable global interrupts
For the ISR, note:
1. You do not pass any parameters to the ISR and do not let it return anything.
2. The ISR should always be kept as short as possible.
3. For some boards, the ISR must be placed before setup(). That’s why I’m doing it this way throughout this post.
4. Global variables that you change in the ISR must be defined with the keyword volatile.
5. Within the ISR, all further interrupts are suspended.
*/
#define F_CPU 8000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#define LED_PIN PB0
#define BUTTON_PIN PB1
#define DEBOUNCE_TIME 50 // Debounce time in milliseconds
volatile uint8_t button_state = 0;
// ==== START FUNCTIONS =====
void setup_pin_change_interrupt() {
// GIMSK – General Interrupt Mask Register
GIMSK |= (1 << PCIE); // Enable pin change interrupt
// PCMSK – Pin Change Mask Register
PCMSK |= (1 << PCINT1); // Enable interrupt on PB1 (PCINT1)
sei(); // Enable global interrupts
}
bool is_button_pressed(void) {
// Check if button is pressed (logic low)
if (!(PINB & (1 << BUTTON_PIN))) {
_delay_ms(DEBOUNCE_TIME); // Debounce delay
// Check again to confirm stable press
if (!(PINB & (1 << BUTTON_PIN))) {
// Wait for button release to avoid multiple toggles
while (!(PINB & (1 << BUTTON_PIN))) {
_delay_ms(1); // Simple hold loop
}
return true;
}
}
return false;
}
// ==== END FUNCTIONS =====
ISR(PCINT0_vect) {
if (is_button_pressed()) { // debounce function
PORTB ^= (1 << LED_PIN); // Toggle LED
}
}
int main(void) {
DDRB |= (1 << LED_PIN); // LED output
PORTB |= (1 << BUTTON_PIN); // Enable pull-up on button pin
setup_pin_change_interrupt();
while (1) {
// Main loop — could sleep or do something else
}
return 0;
}