Переривання таймера 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 размітка не підтримується! Використовуйте звичайтий текст.
    Погано           Добре
Модуль стабілізатора напруги 3,3 В

Модуль стабілізатора напруги 3,3 В

Модуль призначений для живлення різних електронних пристроїв, модулів, що працюють від напруги 3,3 В..

18.61грн.

Датчик вологості та температури DHT21

Датчик вологості та температури DHT21

DHT21 (AM2301) – Датчик вологості та температури для Arduino, ESP32, Raspberry Pi DHT21 (AM2301) ..

261.32грн.

3-осьовий акселерометр ADXL346Z

3-осьовий акселерометр ADXL346Z

Акселерометр - це датчик прискорення. Даний датчик вимірює статичне (гравітацію - нахил до земної пл..

167.51грн.

Arduino уроки масиви

Arduino уроки масиви

Привіт друзі! Радий вас усіх бачити! Пропоную ознайомитись з наступною темою навчального циклу – Ard..

Модуль MP3-плеєра GW

Модуль MP3-плеєра GW

Повністю автономний mp3-плеєр з живленням 5 В. Мініатюрний, має на борту кнопки керування. Чита..

89.24грн.

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

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

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

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

0.00грн.

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

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

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

0.00грн.