버튼으로 LED 제어하기) 디지털 입력과 풀업 저항의 모든 것

안녕하세요! 지난 편에서는 LED를 깜빡이며 출력 제어의 기초를 배웠습니다. 이번에는 입력을 다뤄보겠습니다. 버튼을 눌러서 LED를 제어하는 인터랙티브한 프로그램을 만들어보죠!

이번 편의 목표

  • 디지털 입력을 읽는 방법 이해
  • 풀업 저항의 개념과 필요성 파악
  • 디바운싱 기법으로 안정적인 입력 처리
  • 버튼과 LED를 조합한 다양한 제어 패턴 구현

회로 구성하기

SimulIDE에서 회로 만들기

지난 편의 LED 회로에 버튼을 추가해보겠습니다:

추가할 부품들:

  • Push Button (택트 스위치) 1개
  • Resistor 10kΩ 1개 (풀업 저항용)

연결 방법:

  1. LED 회로 (기존 유지):
    • ATmega328P PB5 → 저항 330Ω → LED → GND
  2. 버튼 회로 (새로 추가):
    • ATmega328P PB0을 풀업 저항 10kΩ을 통해 +5V에 연결
    • ATmega328P PB0을 버튼의 한쪽 다리에 연결
    • 버튼의 다른쪽 다리를 GND에 연결

왜 이렇게 연결할까?

    +5V
     |
    10kΩ (풀업 저항)
     |
PB0 ─┴─ 버튼 ─ GND
  • 버튼이 안 눌린 상태: PB0은 풀업 저항을 통해 +5V에 연결 → HIGH
  • 버튼이 눌린 상태: PB0이 직접 GND에 연결 → LOW

디지털 입력의 기초

PINx 레지스터 사용법

출력에 PORTB를 사용했다면, 입력에는 PINB를 사용합니다:

c

// PB0 핀을 입력으로 설정 (기본값이므로 생략 가능)
DDRB &= ~(1 << PB0);

// PB0 핀의 상태 읽기
if (PINB & (1 << PB0)) {
    // 버튼이 안 눌린 상태 (HIGH)
} else {
    // 버튼이 눌린 상태 (LOW)
}

비트 마스킹으로 특정 핀 읽기

c

uint8_t button_state = PINB & (1 << PB0);

동작 과정:

  1. (1 << PB0)0b00000001 (마스크)
  2. PINB의 값과 AND 연산
  3. PB0 비트만 추출, 나머지는 0
PINB 값:   0b10110001
마스크:    0b00000001
결과:      0b00000001  // PB0이 HIGH면 0이 아닌 값

첫 번째 버튼 프로그램

기본 버튼-LED 제어

c

#include <avr/io.h>
#include <util/delay.h>

int main(void) {
    // PB5를 출력으로 설정 (LED)
    DDRB |= (1 << PB5);
    
    // PB0을 입력으로 설정 (기본값이므로 생략 가능)
    DDRB &= ~(1 << PB0);
    
    // PB0에 내부 풀업 저항 활성화
    PORTB |= (1 << PB0);
    
    while(1) {
        // 버튼이 눌렸는지 확인
        if (!(PINB & (1 << PB0))) {
            // 버튼이 눌림 (LOW) → LED 켜기
            PORTB |= (1 << PB5);
        } else {
            // 버튼이 안 눌림 (HIGH) → LED 끄기
            PORTB &= ~(1 << PB5);
        }
        
        _delay_ms(50);  // 작은 지연으로 안정성 확보
    }
    
    return 0;
}

풀업 저항의 비밀

왜 풀업 저항이 필요할까?

풀업 저항 없이 버튼만 연결하면:

PB0 ─ 버튼 ─ GND

문제점:

  • 버튼이 안 눌린 상태에서 PB0이 플로팅 상태
  • 주변 노이즈에 의해 HIGH/LOW가 불규칙하게 변함
  • 안정적인 입력 읽기 불가능

내부 풀업 저항 vs 외부 풀업 저항

내부 풀업 저항 사용

c

// 입력 모드로 설정 후
DDRB &= ~(1 << PB0);
// 내부 풀업 저항 활성화
PORTB |= (1 << PB0);

장점:

  • 외부 부품 불필요
  • 회로 간소화

단점:

  • 저항값이 크다 (20~50kΩ)
  • 노이즈에 상대적으로 민감

외부 풀업 저항 사용

c

// 별도 설정 불필요 (하드웨어에서 해결)
DDRB &= ~(1 << PB0);

장점:

  • 저항값 선택 가능 (보통 10kΩ)
  • 더 안정적인 신호

단점:

  • 추가 부품 필요

디바운싱: 깨끗한 신호 만들기

바운싱 현상이란?

기계적 버튼을 누를 때 접점이 물리적으로 여러 번 접촉하면서 짧은 시간 동안 HIGH/LOW가 반복됩니다:

버튼 누름: HIGH → LOW → HIGH → LOW → LOW (안정)
시간:      0ms     1ms    2ms     3ms    5ms

소프트웨어 디바운싱

c

#include <avr/io.h>
#include <util/delay.h>

// 디바운싱 함수
uint8_t button_debounce(void) {
    if (!(PINB & (1 << PB0))) {
        // 버튼이 눌린 것 같음, 확인을 위해 잠시 대기
        _delay_ms(20);
        
        // 다시 확인
        if (!(PINB & (1 << PB0))) {
            // 여전히 눌려있으면 진짜 눌림
            return 1;
        }
    }
    return 0;
}

int main(void) {
    DDRB |= (1 << PB5);     // LED 출력 설정
    DDRB &= ~(1 << PB0);    // 버튼 입력 설정
    PORTB |= (1 << PB0);    // 내부 풀업 활성화
    
    while(1) {
        if (button_debounce()) {
            PORTB |= (1 << PB5);    // LED 켜기
        } else {
            PORTB &= ~(1 << PB5);   // LED 끄기
        }
    }
    
    return 0;
}

다양한 버튼 제어 패턴

1. 토글 스위치 (누를 때마다 ON/OFF)

c

#include <avr/io.h>
#include <util/delay.h>

uint8_t led_state = 0;  // LED 상태 저장

int main(void) {
    DDRB |= (1 << PB5);     // LED 출력
    DDRB &= ~(1 << PB0);    // 버튼 입력
    PORTB |= (1 << PB0);    // 풀업 활성화
    
    while(1) {
        // 버튼이 눌렸는지 확인
        if (!(PINB & (1 << PB0))) {
            _delay_ms(20);  // 디바운싱
            
            // 여전히 눌려있으면
            if (!(PINB & (1 << PB0))) {
                // LED 상태 토글
                led_state = !led_state;
                
                if (led_state) {
                    PORTB |= (1 << PB5);
                } else {
                    PORTB &= ~(1 << PB5);
                }
                
                // 버튼이 떼질 때까지 대기
                while (!(PINB & (1 << PB0)));
                _delay_ms(20);  // 릴리즈 디바운싱
            }
        }
    }
    
    return 0;
}

2. 길게 누르기 감지

c

#include <avr/io.h>
#include <util/delay.h>

int main(void) {
    DDRB |= (1 << PB5) | (1 << PB4);  // LED 2개 출력
    DDRB &= ~(1 << PB0);              // 버튼 입력
    PORTB |= (1 << PB0);              // 풀업 활성화
    
    while(1) {
        if (!(PINB & (1 << PB0))) {
            _delay_ms(20);  // 디바운싱
            
            if (!(PINB & (1 << PB0))) {
                uint16_t press_time = 0;
                
                // 버튼이 눌려있는 동안 시간 측정
                while (!(PINB & (1 << PB0))) {
                    _delay_ms(10);
                    press_time += 10;
                    
                    if (press_time >= 1000) {
                        // 1초 이상 누름 - 긴 누르기
                        PORTB |= (1 << PB4);   // PB4 LED 켜기
                        break;
                    }
                }
                
                if (press_time < 1000) {
                    // 짧은 누르기
                    PORTB ^= (1 << PB5);   // PB5 LED 토글
                    PORTB &= ~(1 << PB4);  // PB4 LED 끄기
                }
                
                // 버튼 릴리즈 대기
                while (!(PINB & (1 << PB0)));
                _delay_ms(20);
            }
        }
    }
    
    return 0;
}

3. 더블 클릭 감지

c

#include <avr/io.h>
#include <util/delay.h>

int main(void) {
    DDRB |= (1 << PB5) | (1 << PB4);  // LED 2개
    DDRB &= ~(1 << PB0);              // 버튼 입력
    PORTB |= (1 << PB0);              // 풀업 활성화
    
    while(1) {
        // 첫 번째 클릭 감지
        if (!(PINB & (1 << PB0))) {
            _delay_ms(20);
            
            if (!(PINB & (1 << PB0))) {
                // 버튼 릴리즈 대기
                while (!(PINB & (1 << PB0)));
                _delay_ms(20);
                
                // 300ms 안에 두 번째 클릭이 있는지 확인
                uint16_t wait_time = 0;
                uint8_t double_click = 0;
                
                while (wait_time < 300) {
                    if (!(PINB & (1 << PB0))) {
                        _delay_ms(20);
                        if (!(PINB & (1 << PB0))) {
                            double_click = 1;
                            break;
                        }
                    }
                    _delay_ms(10);
                    wait_time += 10;
                }
                
                if (double_click) {
                    // 더블 클릭 - 두 LED 모두 토글
                    PORTB ^= (1 << PB5) | (1 << PB4);
                } else {
                    // 싱글 클릭 - PB5만 토글
                    PORTB ^= (1 << PB5);
                }
                
                // 버튼 릴리즈 대기 (더블클릭인 경우)
                if (double_click) {
                    while (!(PINB & (1 << PB0)));
                    _delay_ms(20);
                }
            }
        }
    }
    
    return 0;
}

디버깅과 최적화 팁

1. 시리얼 모니터 활용

c

// 디버깅용 코드 (SimulIDE에서 확인)
printf("Button state: %d\n", (PINB & (1 << PB0)) ? 1 : 0);

2. 매크로로 코드 정리

c

#define BUTTON_PIN    PB0
#define LED_PIN       PB5

#define BUTTON_INIT() do { \
    DDRB &= ~(1 << BUTTON_PIN); \
    PORTB |= (1 << BUTTON_PIN); \
} while(0)

#define BUTTON_PRESSED() (!(PINB & (1 << BUTTON_PIN)))

#define LED_INIT()    (DDRB |= (1 << LED_PIN))
#define LED_ON()      (PORTB |= (1 << LED_PIN))
#define LED_OFF()     (PORTB &= ~(1 << LED_PIN))
#define LED_TOGGLE()  (PORTB ^= (1 << LED_PIN))

3. 상태 머신 패턴

c

typedef enum {
    STATE_IDLE,
    STATE_PRESSED,
    STATE_RELEASED
} button_state_t;

button_state_t button_state = STATE_IDLE;

void update_button_state(void) {
    switch(button_state) {
        case STATE_IDLE:
            if (BUTTON_PRESSED()) {
                button_state = STATE_PRESSED;
            }
            break;
            
        case STATE_PRESSED:
            if (!BUTTON_PRESSED()) {
                button_state = STATE_RELEASED;
                // 버튼 릴리즈 이벤트 처리
                LED_TOGGLE();
            }
            break;
            
        case STATE_RELEASED:
            button_state = STATE_IDLE;
            break;
    }
}

응용 과제

과제 1: 3단계 밝기 조절

버튼을 누를 때마다 LED가 꺼짐 → 어둡게 → 밝게 → 꺼짐 순으로 변하게 만들어보세요. (힌트: PWM을 사용하거나 빠른 ON/OFF로 밝기 시뮬레이션)

과제 2: 버튼 조합

2개의 버튼을 사용해서:

  • 버튼1만: LED1 토글
  • 버튼2만: LED2 토글
  • 버튼1+2 동시: 모든 LED 토글

과제 3: 게임 만들기

“반응속도 테스트” 게임을 만들어보세요:

  1. 랜덤한 시간 후 LED 켜짐
  2. 사용자가 버튼을 최대한 빨리 누름
  3. 반응시간에 따라 다른 패턴의 LED 표시

다음 편 예고

다음 편에서는 여러 LED로 패턴 만들기를 배워보겠습니다:

  • 8개 LED로 만드는 나이트 라이더 효과
  • 이진 카운터와 패턴 생성
  • 배열과 반복문을 활용한 효율적인 코드
  • 더 복잡한 시각적 효과들

버튼 입력을 마스터했다면, 이제 더 화려한 출력 패턴을 만들 차례입니다!


직접 버튼을 눌러가며 코드를 테스트해보세요. 디바운싱의 중요성을 몸소 체험할 수 있을 거예요!