ESP8266 Arduino – zliczanie impulsów zewnętrznych TIMER0 i przerwaniami GPIO

Pytanie

ESP8266 jak zliczać impulsy zewnętrzne timerem program w arduino

Odpowiedź Sztucznej Inteligencji


Bezpośrednia odpowiedź na pytanie

Na ESP8266 w środowisku Arduino nie zlicza się impulsów zewnętrznych „timerem” w sensie sprzętowego licznika taktowanego z pinu, jak np. w niektórych AVR. Oficjalnie ESP8266 nie ma sprzętowego modułu licznika impulsów (PCNT), więc poprawne rozwiązanie to: przerwanie GPIO do inkrementacji licznika oraz millis() / timer programowy do wyznaczania okna pomiarowego, np. 1 s. (docs.espressif.com)

Najprostszy i praktycznie poprawny program w Arduino wygląda tak:

#include <Arduino.h>
const uint8_t PULSE_PIN = 14;          // GPIO14, dostosuj do swojej płytki
const uint32_t OKNO_POMIARU_MS = 1000; // 1 s
volatile uint32_t licznikImpulsow = 0;
volatile uint32_t ostatniImpulsUs = 0;
// Ustaw 0 dla czystego sygnału cyfrowego.
// Dla kontaktronu / przycisku możesz dać np. 3000...10000 us.
const uint32_t MIN_ODSTEP_US = 0;
void IRAM_ATTR onPulse()
{
  uint32_t teraz = micros();
  if (MIN_ODSTEP_US == 0 || (uint32_t)(teraz - ostatniImpulsUs) >= MIN_ODSTEP_US) {
    licznikImpulsow++;
    ostatniImpulsUs = teraz;
  }
}
void setup()
{
  Serial.begin(115200);
  // Jeśli źródło jest typu otwarty kolektor / kontaktron, INPUT_PULLUP ma sens.
  // Dla aktywnego drivera push-pull można użyć INPUT.
  pinMode(PULSE_PIN, INPUT_PULLUP);
  // Wybierz odpowiednie zbocze: RISING albo FALLING.
  attachInterrupt(digitalPinToInterrupt(PULSE_PIN), onPulse, FALLING);
  Serial.println();
  Serial.println("Start licznika impulsow ESP8266");
}
void loop()
{
  static uint32_t lastMs = 0;
  uint32_t nowMs = millis();
  if ((uint32_t)(nowMs - lastMs) >= OKNO_POMIARU_MS) {
    lastMs += OKNO_POMIARU_MS;
    noInterrupts();
    uint32_t impulsy = licznikImpulsow;
    licznikImpulsow = 0;
    interrupts();
    Serial.print("Impulsy w 1 s: ");
    Serial.print(impulsy);
    Serial.print("  |  f = ");
    Serial.print(impulsy);
    Serial.println(" Hz");
  }
  delay(1); // daje czas stosowi Wi-Fi / systemowi
}

To rozwiązanie jest zgodne z tym, jak ESP8266 jest przewidziany do takich zadań: ISR na GPIO ma być bardzo krótki, umieszczony w IRAM, a cięższe operacje robi się poza przerwaniem. (arduino-esp8266.readthedocs.io)

Szczegółowa analiza problemu

Najważniejszy punkt techniczny jest taki, że w ESP8266 nie ma sprzętowego PCNT, więc jedna z przykładowych odpowiedzi sugerująca „sprzętowe zliczanie TIMER0 z GPIO0” nie odpowiada standardowemu, oficjalnie opisanemu modelowi pracy tego układu w Arduino. Dokumentacja Espressif podaje wprost, że ESP8266 nie zawiera hardware pulse counter i obsługuje zliczanie impulsów przez przerwania od zbocza GPIO. Z kolei oficjalny opis hardware timera pokazuje timer jako peryferium do odmierzania czasu, ładowania wartości, odczytu licznika i generacji przerwania, a nie jako licznik impulsów zewnętrznych z pinu. To prowadzi do jednoznacznego wniosku inżynierskiego: w Arduino na ESP8266 należy liczyć impulsy w ISR od GPIO, a „timer” używać wyłącznie do bramkowania czasowego pomiaru. (docs.espressif.com)

Architektura rozwiązania jest więc następująca:

  • sygnał zewnętrzny trafia na GPIO,
  • attachInterrupt() wywołuje ISR na zboczu RISING albo FALLING,
  • w ISR zwiększasz tylko licznik,
  • co ustalony czas, np. co 1000 ms, kopiujesz licznik do zmiennej lokalnej, zerujesz go i liczysz częstotliwość. (arduino-esp8266.readthedocs.io)

To jest najlepszy kompromis dla ESP8266, ponieważ:

  • ISR może być bardzo szybki,
  • nie blokujesz głównej pętli ciągłym pollingiem,
  • możesz jednocześnie robić transmisję po Wi‑Fi, MQTT, HTTP itp.,
  • częstotliwość w Hz dostajesz od razu jako liczbę impulsów w oknie 1 s. (arduino-esp8266.readthedocs.io)

Dlaczego nie polling?

Polling, czyli ciągłe sprawdzanie stanu wejścia w loop(), na ESP8266 jest słabszym rozwiązaniem, bo rdzeń i stos Wi‑Fi potrzebują czasu procesora. Dokumentacja Arduino core podkreśla, że delay()/yield() są potrzebne, aby systemowe zadania mogły się wykonywać. W praktyce oznacza to, że przy polling’u możesz łatwo zgubić krótkie impulsy. Dlatego do zliczania impulsów lepsze są przerwania GPIO. (arduino-esp8266.readthedocs.io)

Dlaczego ISR musi być bardzo krótkie?

W ESP8266 procedura przerwania:

  • musi być w IRAM,
  • nie może wywoływać delay() ani yield(),
  • nie powinna wykonywać długich operacji,
  • nie powinna robić dynamicznej alokacji pamięci ani operacji typu String. (arduino-esp8266.readthedocs.io)

Dlatego poprawny ISR wygląda praktycznie tak:

void IRAM_ATTR onPulse() {
  licznikImpulsow++;
}

albo, jeśli potrzebujesz prostego antydrgania:

void IRAM_ATTR onPulse() {
  uint32_t teraz = micros();
  if (teraz - ostatniImpulsUs > 5000) {
    licznikImpulsow++;
    ostatniImpulsUs = teraz;
  }
}

To drugie podejście ma sens dla kontaktronu, przycisku, przekaźnika, gdzie jedno fizyczne zdarzenie może generować kilka szybkich przejść logicznych. Sam debounce programowy działa, ale z punktu widzenia elektroniki lepsza jest filtracja sprzętowa: rezystor podciągający, filtr RC i ewentualnie bramka Schmitta. To daje czystsze zbocze i zmniejsza ryzyko fałszywych zliczeń. Wniosek o potrzebie filtracji jest klasyczną praktyką projektową dla wejść impulsowych. (arduino-esp8266.readthedocs.io)

Dobór zbocza

Najczęściej wybierasz jedno z dwóch:

  • RISING — liczysz zbocza narastające,
  • FALLING — liczysz zbocza opadające.

Tryb CHANGE liczy każdą zmianę stanu, więc dla prostokąta dostaniesz zwykle 2 zliczenia na okres. Jeśli użytkownik oczekuje częstotliwości sygnału, a nie liczby wszystkich przejść, to CHANGE zwykle nie jest właściwym wyborem. Obsługiwane typy przerwań na ESP8266 to CHANGE, RISING, FALLING. (arduino-esp8266.readthedocs.io)

Bezpieczny odczyt licznika

Mimo że ESP8266 jest układem 32-bitowym, praktyka poprawnego programu wymaga krótkiej sekcji krytycznej przy kopiowaniu i zerowaniu wspólnej zmiennej volatile. Typowy wzorzec:

noInterrupts();
uint32_t impulsy = licznikImpulsow;
licznikImpulsow = 0;
interrupts();

To zapobiega sytuacji, w której ISR zwiększy licznik dokładnie podczas odczytu lub zerowania. Krótkie zablokowanie przerwań na kilka instrukcji jest akceptowalne; problemem byłyby dopiero długie sekcje krytyczne, bo dokumentacja ostrzega, że długie blokowanie obsługi przerwań destabilizuje system. (arduino-esp8266.readthedocs.io)

Dobór pinu

Oficjalna dokumentacja Arduino core podaje, że przerwania pinowe działają na dowolnym GPIO poza GPIO16. Jeśli więc wybierasz pin do licznika, nie używaj GPIO16 do attachInterrupt(). (arduino-esp8266.readthedocs.io)

Dodatkowo dokumentacja przypomina, że niektóre piny pełnią inne funkcje, np. UART. Jeżeli używasz jednocześnie Serial, to warto nie mieszać wejścia impulsowego z pinami intensywnie wykorzystywanymi przez interfejs szeregowy. UART0 używa GPIO1/GPIO3, a UART1 TX jest na GPIO2. (arduino-esp8266.readthedocs.io)

Aktualne informacje i trendy

Aktualna dokumentacja Espressif mówi wprost:

  • ESP8266 nie ma modułu PCNT, więc zliczanie impulsów odbywa się przez przerwania GPIO,
  • przy włączonym Wi‑Fi mogą wystąpić „dziury” w próbkowaniu GPIO i utrata części zliczeń,
  • dla zastosowań z wysokimi wymaganiami zliczania producent zaleca raczej ESP32 i nowsze układy. (docs.espressif.com)

To bardzo ważne praktycznie. Jeżeli mierzysz:

  • wodomierz,
  • licznik energii z wyjściem impulsowym,
  • przepływomierz Hall,
  • wolny enkoder,
  • kontaktron z maszyny,

to ESP8266 zwykle wystarczy. Jeśli jednak mówimy o szybkich impulsach, krytycznej dokładności albo pracy równoległej z aktywnym Wi‑Fi, to nowoczesnym kierunkiem jest ESP32 z PCNT. Oficjalna dokumentacja rodziny ESP32 zawiera już osobny blok peryferyjny Pulse Counter (PCNT). (docs.espressif.com)

Wspierające wyjaśnienia i detale

Czym tu naprawdę jest „timer”?

W pytaniu pojawia się słowo „timer”, ale na ESP8266 należy rozdzielić dwa pojęcia:

  1. Licznik impulsów zewnętrznych — tego sprzętowo brak.
  2. Timer do odmierzania czasu — ten istnieje i służy do generacji przerwań czasowych lub opóźnień pomiarowych. (docs.espressif.com)

Zatem poprawny model mentalny jest taki:

  • GPIO interrupt = co liczyć,
  • timer / millis() = jak długo liczyć. (docs.espressif.com)

IRAM_ATTR a ICACHE_RAM_ATTR

W nowszych wersjach Arduino core dla ESP8266 stosuje się IRAM_ATTR. W starszych wersjach dokumentacja pokazywała ICACHE_RAM_ATTR. Jeżeli ktoś kompiluje stary projekt lub używa starej wersji core, może spotkać oba zapisy. (arduino-esp8266.readthedocs.io)

volatile

Zmienne współdzielone między ISR a loop() muszą być oznaczone jako volatile, inaczej kompilator może zoptymalizować dostęp w sposób niezgodny z oczekiwanym działaniem programu. To jest standardowa zasada dla programowania współbieżnego na mikrokontrolerach; w tym przypadku wynika bezpośrednio z użycia ISR. (arduino-esp8266.readthedocs.io)

Aspekty etyczne i prawne

W samym zliczaniu impulsów nie ma szczególnych problemów etycznych, ale są istotne kwestie bezpieczeństwa technicznego:

  • jeżeli impulsy pochodzą z urządzeń energetycznych, falowników, liczników sieciowych lub automatyki przemysłowej, należy zapewnić izolację galwaniczną,
  • nie wolno podawać bezpośrednio na GPIO sygnałów o poziomach napięć innych niż zgodne z logiką 3,3 V,
  • trzeba uwzględnić kompatybilność EMC, szczególnie przy długich przewodach i środowisku przemysłowym.

To są wymagania wynikające z dobrej praktyki inżynierskiej i bezpieczeństwa układów niskonapięciowych.

Praktyczne wskazówki

  • Dla czystego sygnału cyfrowego użyj RISING albo FALLING, bez debounce.
  • Dla kontaktronu / przycisku / mechaniki dodaj debounce programowy albo lepiej filtrację RC.
  • Jeśli źródło ma wyjście otwarty kolektor, użyj INPUT_PULLUP i licz zbocze FALLING.
  • Jeśli źródło ma własny driver push-pull 3,3 V, zwykle lepsze będzie INPUT.
  • Nie wykonuj Serial.print() w ISR.
  • Nie używaj String w ISR.
  • Nie blokuj przerwań na dłużej niż to konieczne. (arduino-esp8266.readthedocs.io)

Jeżeli chcesz dodatkowo mierzyć okres między impulsami, możesz w ISR zapamiętywać micros() i różnicę czasów, ale tylko wtedy, gdy sygnał nie jest zbyt szybki. Dla szybkich przebiegów lepiej zostać przy zliczaniu w stałym oknie czasowym.

Ewentualne zastrzeżenia lub uwagi dodatkowe

Najważniejsze zastrzeżenie: przy włączonym Wi‑Fi producent wprost ostrzega, że na ESP8266 mogą wystąpić straty zliczeń z powodu priorytetów systemowych. Jeżeli więc licznik ma mieć charakter rozliczeniowy albo ma nie gubić impulsów w żadnych warunkach, ESP8266 nie jest najlepszym wyborem. (docs.espressif.com)

Druga uwaga: sugestia o wykorzystaniu „sprzętowego licznika TIMER0 na GPIO0” nie wynika z oficjalnej, aktualnej ścieżki dokumentacyjnej Arduino-ESP8266/Espressif. W praktyce, jeśli zależy Ci na rozwiązaniu stabilnym i przenośnym, trzymaj się GPIO interrupt + okno czasowe. (docs.espressif.com)

Sugestie dalszych badań

Jeżeli chcesz rozwinąć projekt, kolejne sensowne kroki to:

  • dodać uśrednianie z kilku okien pomiarowych,
  • dodać wykrywanie zaniku sygnału,
  • dodać sumator całkowitej liczby impulsów zapisywany okresowo do flash/EEPROM,
  • dla wysokich wymagań przejść na ESP32 z PCNT,
  • przy bardzo szybkich sygnałach użyć zewnętrznego licznika sprzętowego i odczytywać go z ESP8266.

To ostatnie jest już rekomendacją inżynierską: skoro ESP8266 nie ma PCNT i może gubić zliczenia przy Wi‑Fi, zewnętrzny licznik lub migracja na ESP32 są naturalnym kierunkiem projektu. (docs.espressif.com)

Krótkie podsumowanie

Najkrócej: na ESP8266 nie liczysz impulsów zewnętrznych timerem sprzętowym z pinu, tylko przerwaniem GPIO. Timer lub millis() służy tylko do wyznaczenia czasu pomiaru. ISR ma być krótki, z IRAM_ATTR, bez Serial, bez delay(), a wynik odczytujesz i zerujesz w loop(). Przy większych wymaganiach dokładności lub przy aktywnym Wi‑Fi rozważ ESP32 z PCNT. (docs.espressif.com)

Jeżeli chcesz, mogę w następnym kroku przygotować Ci:

  1. wersję do wodomierza / kontaktronu,
  2. wersję do czujnika Hall,
  3. wersję z MQTT / wysyłką wyniku przez Wi‑Fi,
  4. wersję z pomiarem częstotliwości i okresu jednocześnie.

Oceń odpowiedź:
Zastrzeżenie: Odpowiedzi udzielone przez sztuczną inteligencję (model językowy) mogą być niedokładne i wprowadzające w błąd. Elektroda nie ponosi odpowiedzialności za dokładność, rzetelność ani kompletność prezentowanych informacji. Wszystkie odpowiedzi powinny być zweryfikowane przez użytkownika.

Zadaj dodatkowe pytanie Sztucznej Inteligencji

Czekaj (2min)...