Переривання таймера ESP32: hw_timer_t, налаштування обробника та обмеження ISR

ESP32 має апаратні таймери загального призначення (General Purpose Timers), які можуть викликати ваш код за точним розкладом — без блокувань і незалежно від loop(). У цій статті розглянемо, як працює hw_timer_t, як налаштувати переривання та обробник, а також які обмеження діють усередині ISR.

Як працює hw_timer_t

  • У ESP32 є дві групи таймерів (Timer Group0 та Group1), у кожній — по 2 таймери (усього 4).
  • Кожен таймер — це лічильник, який тактується від APB (зазвичай 80 МГц) через переддільник (prescaler). Наприклад: переддільник 80 дає частоту тіків 1 МГц, тобто 1 тик = 1 мкс.
  • Таймер може працювати в режимі одноразового спрацювання (one-shot) або періодичному (auto-reload) і генерувати переривання після досягнення порогового значення (alarm).

Швидка схема налаштування

  1. timerBegin(timerNum, prescaler, countUp) — створити таймер і задати переддільник.
  2. timerAttachInterrupt(timer, isr, edge) — прив’язати обробник переривання (IRAM_ATTR).
  3. timerAlarmWrite(timer, alarmTicks, autoReload) — задати поріг (у тіках) та авто-повтор.
  4. timerAlarmEnable(timer) — запустити таймер.

Приклад 1. Періодичне переривання 1 кГц із безпечним прапором

ISR робить мінімум роботи: виставляє прапор. Основна логіка — у loop().


#include <Arduino.h>

#define LED_PIN 2

hw_timer_t* timer = nullptr;
volatile bool tick = false;        // прапор від ISR

void IRAM_ATTR onTimer() {         // ISR має бути в IRAM
  tick = true;                     // тільки виставляємо прапор
}

void setup() {
  pinMode(LED_PIN, OUTPUT);
  Serial.begin(115200);

  // 80 МГц / 80 = 1 МГц → 1 тик = 1 мкс
  timer = timerBegin(0, 80, true);
  timerAttachInterrupt(timer, &onTimer, true);
  // 1000 тіків при 1 МГц = 1000 мкс (1 кГц)
  timerAlarmWrite(timer, 1000, true);
  timerAlarmEnable(timer);
}

void loop() {
  if (tick) {                      // обробляємо подію таймера поза ISR
    tick = false;
    static bool led = false;
    led = !led;
    digitalWrite(LED_PIN, led);
    // Додаткові дії: зчитування сенсорів, вивід у Serial тощо.
  }
}
  

Приклад 2. Високоточний «тайм-слот» для підрахунку подій

ISR рахує «тіки», а у loop() ми атомарно читаємо лічильник і виводимо статистику. Для безпеки використовуємо критичну секцію.


#include <Arduino.h>

hw_timer_t* timer = nullptr;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;

volatile uint32_t isrTicks = 0;    // зростає в ISR кожен період

void IRAM_ATTR onTimer() {
  portENTER_CRITICAL_ISR(&timerMux);
  isrTicks++;                      // дуже коротка операція
  portEXIT_CRITICAL_ISR(&timerMux);
}

void setup() {
  Serial.begin(115200);
  // 1 МГц тик → ставимо період 10 000 мкс (100 Гц)
  timer = timerBegin(1, 80, true);
  timerAttachInterrupt(timer, &onTimer, true);
  timerAlarmWrite(timer, 10000, true);
  timerAlarmEnable(timer);
}

void loop() {
  static uint32_t lastMs = 0;
  if (millis() - lastMs >= 1000) {          // раз на секунду
    lastMs = millis();
    uint32_t ticksCopy;

    portENTER_CRITICAL(&timerMux);          // атомарне читання і обнулення
    ticksCopy = isrTicks;
    isrTicks = 0;
    portEXIT_CRITICAL(&timerMux);

    Serial.printf("ISR спрацював: %lu раз/сек\n", (unsigned long)ticksCopy);
  }
}
  

Приклад 3. Передача подій із ISR у задачу (черга FreeRTOS)

Правильний спосіб «відкласти» важку роботу з ISR — надіслати повідомлення у чергу.


#include <Arduino.h>

hw_timer_t* timer = nullptr;
QueueHandle_t q;

void IRAM_ATTR onTimer() {
  static uint32_t seq = 0;
  BaseType_t hpTaskWoken = pdFALSE;
  uint32_t stamp = ++seq;                    
  xQueueSendFromISR(q, &stamp, &hpTaskWoken);
  if (hpTaskWoken) portYIELD_FROM_ISR();    
}

void setup() {
  Serial.begin(115200);
  q = xQueueCreate(8, sizeof(uint32_t));

  timer = timerBegin(2, 80, true);          
  timerAttachInterrupt(timer, &onTimer, true);
  timerAlarmWrite(timer, 500000, true);     // кожні 500 мс
  timerAlarmEnable(timer);
}

void loop() {
  uint32_t stamp;
  if (xQueueReceive(q, &stamp, 50 / portTICK_PERIOD_MS) == pdPASS) {
    Serial.printf("Подія таймера: #%lu\n", (unsigned long)stamp);
  }
}
  

Обмеження та правила для ISR (обробника переривання)

  • Коротко і швидко. ISR має виконуватися якнайшвидше (десятки мкс).
  • IRAM_ATTR. Обов’язково позначайте обробник, щоб він був у швидкій пам’яті.
  • Без блокувань. Не можна використовувати delay(), vTaskDelay(), while-очікування.
  • Без важкого I/O. Заборонено Serial.print(), Wi-Fi, файлову систему, malloc/new, String, I2C/SPI у ISR.
  • Спільні дані. Використовуйте volatile, для складних операцій — критичні секції.
  • Передавайте роботу далі. В ISR тільки ставте прапор, інкрементуйте лічильник або надсилайте у чергу — основні дії виконуйте у loop() чи у задачі FreeRTOS.
  • Точна частота. Формула: період(мкс) = alarmTicks / (APB/переддільник). При APB=80 МГц і переддільнику 80 отримаємо 1 МГц тіків.

Корисні функції для роботи з таймером

  • timerAlarmDisable(timer) / timerAlarmEnable(timer) — зупинити/запустити alarm.
  • timerWrite(timer, value) — записати значення лічильника.
  • timerRestart(timer) — перезапустити лічильник із нуля.
  • timerEnd(timer) — звільнити таймер.

Висновок

Переривання таймерів ESP32 дозволяють викликати код строго за часом, не блокуючи основний цикл. Дотримуйтеся правил ISR — тримайте обробник коротким, виконуйте важку логіку поза перериваннями, захищайте спільні дані — і ви отримаєте точну й надійну роботу навіть у складних проєктах реального часу.

<< Проекти << Усі товари >> Статті, уроки >>

Написати відгук

Примітка: HTML размітка не підтримується! Використовуйте звичайтий текст.
    Погано           Добре
Керування машинкою через WiFi

Керування машинкою через WiFi

Вирішив я недавно зробити апгрейд радіокерованого іграшкового джипу свого сина. Радіокерування м..

Автоматичне реверсивне управління двигуном з ATtiny85

Автоматичне реверсивне управління двигуном з ATtiny85

Постановка задачі проекту Ставимо задачу автоматично тягати двигуном постійного струму корисну ..

Стерео підсилювач потужності 15Втх2 на TDA7297

Стерео підсилювач потужності 15Втх2 на TDA7297

Двохканальний підсилювач звукової частоти на основі мікросхеми TDA7297Містить захист від перевантаже..

125.21грн.

Використання внутрішнього Watchdog таймера в ESP32

Використання внутрішнього Watchdog таймера в ESP32

Використання внутрішнього Watchdog таймера в ESP32 Watchdog (WDT) — це вбудований таймер мікрокон..

STM32F103CBT6 мікроконтролер QFP-48

STM32F103CBT6 мікроконтролер QFP-48

Мікроконтролер STM32F103CBT6 в корпусі QFP-48..

259.68грн.

Рекомендовані товари

Створення простого таймера на ESP32: приклад періодичного виклику функції

Створення простого таймера на ESP32: приклад періодичного виклику функції

Створення простого таймера на ESP32: приклад періодичного виклику функції Під час розробки проєкт..

0.00грн.

ESP32 таймери: Багатозадачність з апаратними таймерами

ESP32 таймери: Багатозадачність з апаратними таймерами

ESP32 таймери: Багатозадачність з апаратними таймерами ESP32 — це потужний мікроконтролер з двояд..

0.00грн.