Enkoder rotacyjny to kluczowy element w projektach robotycznych i elektronicznych, umożliwiający precyzyjne wykrywanie obrotów, pozycji oraz kierunku ruchu. W tym poradniku pokazujemy, czym jest enkoder, jak poprawnie podłączyć go do Arduino, jak programować odczyt impulsów z użyciem przerwań oraz jak wykorzystać go w praktycznych aplikacjach – od sterowania serwem po efekty LED.

Czym jest enkoder i jak działa?

Enkoder inkrementalny (rotacyjny) to cyfrowy sensor, który generuje impulsy elektryczne przy obrocie pokrętła. Posiada dwa kanały wyjściowe A i B przesunięte w fazie (kwadratura), co umożliwia określenie kierunku: obrót w prawo daje sekwencję A przed B, a w lewo – odwrotnie.

Wiele popularnych modułów (np. KY-040, Iduino SE055) ma pięć pinów: A (CLK), B (DT), SW (przycisk), VCC (+) oraz GND (−). Na potrzeby odczytu należy osobno obsłużyć kanały A i B oraz przycisk SW.

Impulsy z enkodera są zliczane przez mikrokontroler Arduino. Przy szybkich obrotach odczyt metodą polling (ciągłe sprawdzanie pinów) bywa zawodny, ponieważ program może „nie zdążyć” zarejestrować zmian. Dlatego w praktyce stosuje się przerwania sprzętowe na pinach z obsługą interruptów (np. 2 i 3 w Arduino Uno) i tryb CHANGE.

Podłączenie enkodera do Arduino – krok po kroku

Podłączając enkoder do Arduino Uno, wybierz piny z obsługą przerwań (2 i 3), aby zminimalizować ryzyko utraty impulsów. Poniżej proponowany schemat:

Schemat podłączenia:

  • pin A (CLK) – do pinu 2 Arduino (INT0) – najlepiej z INPUT_PULLUP;
  • pin B (DT) – do pinu 3 Arduino (INT1) – najlepiej z INPUT_PULLUP;
  • pin SW (przycisk) – do pinu 4 Arduino (z INPUT_PULLUP);
  • VCC (+) – do 5 V Arduino;
  • GND (−) – do GND Arduino.

Włącz wewnętrzne rezystory podciągające (INPUT_PULLUP), aby ustabilizować sygnały i ograniczyć zakłócenia. W środowiskach „zaszumionych” (silniki, przekaźniki) stosuj przewody ekranowane oraz kondensator 100 nF pomiędzy VCC i GND przy module.

Programowanie enkodera – od podstaw do zaawansowanych technik

Do pracy wystarczy Arduino IDE. Możesz użyć prostego polling, przerwań lub biblioteki. Dla szybkiego porównania podejść warto zerknąć na zestawienie:

Metoda Złożoność Odczyt szybkich obrotów Zalety Kiedy użyć
Polling (pętla) Niska Niska Prosta implementacja Testy, wolne obroty
Przerwania Średnia Wysoka Niska latencja, mniejsza utrata impulsów Realne projekty, precyzja
Biblioteka Niska–średnia Wysoka Gotowe algorytmy, debounce Szybki start, złożone projekty

1. Podstawowy kod z pollingiem (dla wolnych obrotów)

Prosty szkic, który odczytuje stany pinów A i B w pętli loop() – dobry na start i wolne obroty:

#define pinA 5
#define pinB 4

int lastA;

void setup() {
pinMode(pinA, INPUT_PULLUP);
pinMode(pinB, INPUT_PULLUP);
Serial.begin(115200);
lastA = digitalRead(pinA);
}

void loop() {
int a = digitalRead(pinA);
if (a != lastA) {
if (digitalRead(pinB) != a) {
Serial.println("+1"); // prawo
} else {
Serial.println("-1"); // lewo
}
}
lastA = a;
}

Kod bazuje na kodzie Graya (zmienia się tylko jeden bit na krok). Otwórz Monitor portu szeregowego (115200) i obserwuj kierunek obrotu.

2. Zaawansowany kod z przerwaniami (zalecany)

Najbardziej niezawodny sposób to przerwania – reagujemy na każdą zmianę stanu kanału i natychmiast aktualizujemy licznik. Użyj trybu CHANGE i funkcji digitalPinToInterrupt():

const uint8_t PinA = 2;
const uint8_t PinB = 3;
const uint8_t PinSW = 4;

volatile long licznik = 0;
unsigned long t0 = 0;

void isrA() {
// Kierunek zależny od relacji A do B
if (digitalRead(PinA) == digitalRead(PinB)) licznik++; else licznik--;
}

void isrB() {
if (digitalRead(PinA) != digitalRead(PinB)) licznik++; else licznik--;
}

void setup() {
Serial.begin(115200);
pinMode(PinA, INPUT_PULLUP);
pinMode(PinB, INPUT_PULLUP);
pinMode(PinSW, INPUT_PULLUP);

attachInterrupt(digitalPinToInterrupt(PinA), isrA, CHANGE);
attachInterrupt(digitalPinToInterrupt(PinB), isrB, CHANGE);

t0 = millis();
}

void loop() {
if (millis() - t0 > 1000) {
noInterrupts();
long kopia = licznik; // bezpieczne odczytanie zmiennej
licznik = 0;
interrupts();

Serial.print("Impulsy/s: ");
Serial.println(kopia);

t0 = millis();
}

// Przycisk: aktywny stan LOW
if (digitalRead(PinSW) == LOW) {
// akcja po kliknięciu
}
}

Uwaga: Unikaj trybu LOW w przerwaniach – generuje on lawinę wywołań, gdy pin pozostaje w stanie niskim. W praktyce stosuj CHANGE, RISING lub FALLING.

3. Biblioteki i niestandardowe obiekty

Do szybkiego startu sprawdza się biblioteka Encoder (Paul Stoffregen). Inicjalizacja i odczyt wyglądają następująco:

#include <Encoder.h>

Encoder mojEnkoder(2, 3); // piny A, B
long staryStan = 0;

void setup() {
Serial.begin(115200);
}

void loop() {
long nowyStan = mojEnkoder.read();
if (nowyStan != staryStan) {
Serial.println(nowyStan);
staryStan = nowyStan;
}
}

Biblioteka obsługuje szybkie obroty i ułatwia dokładny odczyt bez ręcznej obsługi przerwań.

Obsługa przycisku enkodera (debounce)

Przycisk SW wymaga eliminacji drgań styków (debounce). Prosty algorytm programowy może wyglądać tak:

const uint8_t PinSW = 4;

int lastReading = HIGH;
int stableState = HIGH;
unsigned long lastDebounceTime = 0;
const unsigned long debounceDelay = 50;

void setup() {
pinMode(PinSW, INPUT_PULLUP);
Serial.begin(115200);
}

void loop() {
int reading = digitalRead(PinSW);
if (reading != lastReading) {
lastDebounceTime = millis();
lastReading = reading;
}

if ((millis() - lastDebounceTime) > debounceDelay) {
if (reading != stableState) {
stableState = reading;
if (stableState == LOW) {
Serial.println("Przycisk: klik");
}
}
}
}

Praktyczne projekty z enkoderem w Arduino

Projekt 1 – sterowanie serwomechanizmem

Enkoder zmienia kąt serwa w zakresie 0–180°. Użyj przerwań, aby ruch był płynny i precyzyjny:

#include <Servo.h>

const uint8_t PinA = 2;
const uint8_t PinB = 3;
volatile long licznik = 0;

Servo servo;

void isrA() { if (digitalRead(PinA) == digitalRead(PinB)) licznik++; else licznik--; }
void isrB() { if (digitalRead(PinA) != digitalRead(PinB)) licznik++; else licznik--; }

void setup() {
servo.attach(9);
pinMode(PinA, INPUT_PULLUP);
pinMode(PinB, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(PinA), isrA, CHANGE);
attachInterrupt(digitalPinToInterrupt(PinB), isrB, CHANGE);
}

void loop() {
noInterrupts();
long k = licznik;
interrupts();

int pozycja = constrain(map(k, -50, 50, 0, 180), 0, 180);
servo.write(pozycja);
}

Projekt 2 – sterowanie taśmą WS2812B (NeoPixel)

Enkoder zmienia liczbę zapalonych diod (np. „słupek” postępu). Zadbaj o ograniczenie zakresu:

#include <Adafruit_NeoPixel.h>

const uint8_t LED_PIN = 6;
const uint16_t LEDS = 16;
Adafruit_NeoPixel strip(LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);

volatile long licznik = 0;

void setup() {
strip.begin();
strip.setBrightness(50);
strip.show();
}

void loop() {
long k;
noInterrupts();
k = licznik;
interrupts();

int n = constrain((int)k, 0, LEDS);
for (int i = 0; i < LEDS; i++) {
if (i < n) strip.setPixelColor(i, strip.Color(255, 0, 0)); // czerwony
else strip.setPixelColor(i, 0);
}
strip.show();
}

Projekt 3 – menu na LCD

Wyświetlaj wartość licznika na LCD 16×2 i używaj przycisku do zatwierdzania wyborów. Enkoder świetnie nadaje się do nawigacji po menu i regulacji parametrów.

Rozwiązywanie problemów i wskazówki zaawansowane

Poniżej znajdziesz zestaw praktycznych zasad, które ułatwiają niezawodny odczyt i integrację enkodera:

  • utrata impulsów – stosuj przerwania na kanałach A/B i tryb CHANGE;
  • zakłócenia – używaj INPUT_PULLUP, kondensatorów 100 nF blisko modułu oraz przewodów ekranowanych;
  • szybkie obroty – rozważ bibliotekę Encoder lub RotaryEncoder oraz debounce sprzętowy (RC/Schmitt);
  • Arduino Mega – dostępne przerwania na pinach 2, 3, 18, 19, 20, 21;
  • kod Graya – analizuj sekwencje zmian A/B, aby wykrywać kierunek i minimalizować błędy.

W robotyce enkoder służy m.in. do pomiaru prędkości kół: RPM ≈ (impulsy / okres) × (60 / impulsy_na_obrót). Testuj rozwiązania na Arduino Uno, a następnie skaluj do Arduino Mega lub ESP32 w bardziej złożonych projektach.