Lekcja 10: Obsługa przycisku — wejścia GPIO

Pull-up, stan spoczynkowy, odczyt PINx, podstawy debouncigu. To jest pierwsza lekcja, w której mikrokontroler czyta sygnał ze świata zewnętrznego.

1) Cel lekcji

W poprzednich lekcjach mikrokontroler tylko wystawiał stany na wyjściach (LED). Teraz robimy krok dalej: uczysz się, jak poprawnie skonfigurować pin jako wejście i jak bezpiecznie odczytywać stan przycisku.

  • różnica: DDRx, PORTx, PINx,
  • pull-up (wewnętrzny rezystor podciągający),
  • stan spoczynkowy i logika aktywnego zera,
  • pierwszy kontakt z drganiami styków (debouncing).
Po tej lekcji zrobisz: LED reaguje na przycisk (on/off), a w wersji rozszerzonej: przełącza stan po kliknięciu (toggle).

2) Najważniejsza teoria: DDR, PORT, PIN

Każdy port ma 3 podstawowe rejestry:

  • DDRx — kierunek pinu (0 = wejście, 1 = wyjście),
  • PORTx — zapis stanu na wyjściu, a dla wejścia: włącza pull-up,
  • PINx — odczyt aktualnego stanu pinu (tu czytasz przycisk).
Typowy błąd początkujących: Odczytywanie PORTx zamiast PINx.
Przycisk czyta się z PINx.

3) Podłączenie przycisku — najprostszy, poprawny wariant

Użyjemy wewnętrznego pull-up (nie potrzebujesz zewnętrznego rezystora).

  • Przycisk podłącz pomiędzy pin PD2 a GND
  • LED zostaje na PB0 (jak w lekcjach 8–9)
Logika aktywnego zera (ważne!)
Gdy pull-up jest włączony:
  • przycisk NIE wciśnięty → pin ma stan 1 (HIGH)
  • przycisk wciśnięty → pin jest zwarty do masy → stan 0 (LOW)

4) Konfiguracja: LED jako wyjście, przycisk jako wejście

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

#define LED_DDR     DDRB
#define LED_PORT    PORTB
#define LED_PIN     PB0

#define BTN_DDR     DDRD
#define BTN_PORT    PORTD
#define BTN_PINREG  PIND
#define BTN_PIN     PD2

int main(void)
{
    // LED: wyjście
    LED_DDR |= (1 << LED_PIN);

    // Przycisk: wejście
    BTN_DDR &= ~(1 << BTN_PIN);

    // Włącz wewnętrzny pull-up dla PD2
    BTN_PORT |= (1 << BTN_PIN);

    while (1)
    {
        // tu za chwilę logika
    }
}
Klucz: DDR = 0 dla wejścia, a PORT = 1 włącza pull-up.

5) Wariant 1: LED świeci tylko, gdy trzymasz przycisk

Najprostsze sterowanie typu „przycisk = reakcja natychmiastowa”.

while (1)
{
    // aktywne zero: wciśnięty = 0
    if ( (BTN_PINREG & (1 << BTN_PIN)) == 0 )
    {
        LED_PORT |= (1 << LED_PIN);   // ON
    }
    else
    {
        LED_PORT &= ~(1 << LED_PIN); // OFF
    }
}
Co tu się dzieje?
  • Maskujesz interesujący bit: BTN_PINREG & (1<<BTN_PIN)
  • Jeśli wynik = 0 → przycisk wciśnięty (bo zwarcie do GND)
  • Nie ma opóźnień: reaguje natychmiast

6) Dlaczego przycisk „wariuje”? Drgania styków (debouncing)

Mechaniczny przycisk NIE przełącza idealnie 0/1. Podczas wciskania i puszczania pojawiają się krótkie, wielokrotne przełączenia (kilka–kilkanaście ms). To powoduje:

  • „podwójne kliknięcia”
  • losowe przełączanie stanu
  • niestabilne odczyty
Debouncing = filtracja drgań styków Najprostsza metoda na start: krótki delay + ponowny odczyt.

7) Wariant 2: przełączanie LED po kliknięciu (toggle) + prosty debouncing

To jest najczęściej oczekiwane zachowanie: klik → zmiana stanu. Warunek: musisz wykryć zbocze (moment wciśnięcia), a nie stan ciągły.

uint8_t led_state = 0;

while (1)
{
    // 1) wykrycie wciśnięcia (aktywnie 0)
    if ( (BTN_PINREG & (1 << BTN_PIN)) == 0 )
    {
        _delay_ms(20); // debounce

        // 2) sprawdzenie, czy nadal wciśnięty po 20 ms
        if ( (BTN_PINREG & (1 << BTN_PIN)) == 0 )
        {
            led_state ^= 1; // toggle stanu

            if (led_state)
                LED_PORT |= (1 << LED_PIN);
            else
                LED_PORT &= ~(1 << LED_PIN);

            // 3) czekamy aż puścisz (blokada wielokrotnego zliczania)
            while ( (BTN_PINREG & (1 << BTN_PIN)) == 0 ) { }
            _delay_ms(20); // debounce puszczenia
        }
    }
}
To jest kompletna, praktyczna baza pod przyciski.
Masz debounce i blokadę „trzymania”.

8) Wariant 3: „krótkie naciśnięcie” vs „długie przytrzymanie” (mini-projekt)

Ten wzorzec przyda Ci się później w projektach (menu, reset, tryby pracy).

  • krótkie naciśnięcie: toggle LED
  • długie przytrzymanie (np. > 700 ms): LED OFF i reset stanu
uint8_t led_state = 0;

while (1)
{
    if ( (BTN_PINREG & (1 << BTN_PIN)) == 0 )
    {
        _delay_ms(20);
        if ( (BTN_PINREG & (1 << BTN_PIN)) == 0 )
        {
            // pomiar czasu trzymania (prosto, na delayach)
            uint16_t t = 0;
            while ( (BTN_PINREG & (1 << BTN_PIN)) == 0 )
            {
                _delay_ms(10);
                t += 10;
                if (t >= 700) break;
            }

            if (t >= 700)
            {
                // długie przytrzymanie: reset
                led_state = 0;
                LED_PORT &= ~(1 << LED_PIN);
            }
            else
            {
                // krótkie kliknięcie: toggle
                led_state ^= 1;
                if (led_state) LED_PORT |= (1 << LED_PIN);
                else           LED_PORT &= ~(1 << LED_PIN);
            }

            // debounce puszczenia
            while ( (BTN_PINREG & (1 << BTN_PIN)) == 0 ) { }
            _delay_ms(20);
        }
    }
}
Ważne: tu mierzymy czas „na oko” delayami.
W przyszłości zrobimy to poprawnie timerem (bez blokowania programu).

9) Zadania obowiązkowe (musisz zrobić)

  1. Podłącz przycisk na PD2 i uruchom wariant 1 (LED świeci tylko podczas trzymania).
  2. Zrób wariant 2: klik przełącza LED (toggle).
  3. Dodaj debounce 20 ms i blokadę „czekaj aż puścisz”.

10) Zadania dodatkowe (dla lepszych)

  1. Dodaj drugi przycisk (np. PD3) i steruj dwoma LED.
  2. Zrób: przycisk1 = ON, przycisk2 = OFF.
  3. Zrób tryby: każde kliknięcie zmienia tryb świecenia LED (np. 3 tryby).

11) Zadania PRO (dla ambitnych / konkursowych)

  1. Zrób wykrywanie „zbocza” bez pętli while(przycisk) (pamiętaj poprzedni stan).
  2. Zrób filtrację debouncing bez _delay_ms (tylko koncepcja – kod uproszczony).
  3. Połącz z lekcją 9: przycisk zmienia wzór świecenia 4 LED.
Check: Jeśli Twój przycisk działa stabilnie i nie „dubluje klików”, to masz poprawne wejście GPIO.