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();
}
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.