Когда нам необходимо в программе отмерить какой-нибудь промежуток времени нам на помощь приходят таймеры и прерывания от них. С помощью таймеров можно обеспечить точное значение временной задержки. Встроенные таймеры есть в большинстве современных микроконтроллеров. Таймеры могут использоваться не только для формирования временных задержек, но и в качестве счетчиков. Работой таймеров управляют регистры специальных функций микроконтроллера.
В данной статье мы рассмотрим использование таймеров и прерываний от них в модуле ESP32. Данный модуль содержит две аппаратных группы таймеров, каждая из которых содержит два таймера общего назначения. Данные таймеры являются 64-битными с 16-битным предделителем, с возможностью автоматической перезагрузки.
Также на нашем сайте мы рассматривали использование таймеров в других микроконтроллерах (платах):
Прерывания от таймеров
Прерывания от таймеров являются эффективным средством обеспечения точных временных задержек, что востребовано, к примеру, в операциях формирования сигналов ШИМ (широтно-импульсной модуляции). Эти прерывания являются программными и позволяют отмерять заданные временные интервалы вне зависимости от того, какими другими задачами в это время занят микроконтроллер. Они во многом похожи на внешние аппаратные прерывания, но они срабатывают не от какого то внешнего события, а от события переполнения таймера. При срабатывании они ждут пока завершится текущая выполняемая инструкция, после этого прерывают выполнение основной программы при помощи вызова функции обработки прерывания (ISR) и после ее выполнения возвращают исполнение основной программы к следующей инструкции. Принцип формирования прерываний от таймеров показан на следующем рисунке.
Таймеры являются аппаратно-зависимыми, а скорость их счета (скорость таймера) можно определить по следующей формуле:
1 |
timer speed (Hz) = Timer clock speed (Mhz) / prescaler |
К примеру, если мы в модуле ESP32, работающим на тактовой частоте 80MHz, установим коэффициент деления предделителя (prescaler value) равный 1, то мы получим скорость счета таймера равную 80MHz, а если мы установим коэффициент деления предделителя равный 80, то мы получим скорость счета таймера равную 1MHz или 1000000Hz.
Процесс обработки прерывания от таймера по отношению к исполнению основной программы показан на следующем рисунке.
Также, как и в случае с аппаратными прерываниями, прерывания от таймеров являются самым лучшим способом запуска не блокируемых ничем функций через заданные интервалы времени. Для этого в функции setup программы мы настраиваем и прикрепляем прерывание от конкретного таймера к определенной функции обработки прерывания (ISR). Далее микроконтроллер будет последовательно исполнять команды в основной функции программы main. И при срабатывании прерывания от таймера микроконтроллер будет останавливать выполнение основной программы и начинать выполнение функции обработки прерывания (Interrupt Service Routine, ISR). После того как микроконтроллер завершит выполнение функции ISR, он продолжит выполнение основной программы с того места, на котором ее выполнение прервало прерывание. Данные процессы будут происходить при каждом срабатывании прерывания от таймера.
Более подробно об использовании прерываний в модуле ESP32 вы можете прочитать в этой статье.
Наиболее часто задаваемые вопросы про прерывания от таймеров в ESP32
Как много таймеров содержит модуль ESP32?
Он содержит четыре 64-битных таймера.
Как использовать прерывания от таймеров в модуле ESP32?
Необходимо прикрепить конкретный таймер к прерыванию и назначить ему функцию обработки (ISR).
Как работают таймеры в модуле ESP32?
Таймеры используют счетчик, который считает со скоростью, зависящей от тактовой частоты микроконтроллера и коэффициента деления предделителя. Таймер сбрасывается когда его счетчик достигает заданного значения и запускает срабатывание прерывания. Мы можем изменить периодичность срабатывания интервала от таймера изменяя это значение, до которого считает таймер.
Схема проекта
В нашей статье мы рассмотрим пример использования прерывания от таймера в модуле ESP32 на примере со светодиодом. Для этого мы будем использовать схему проекта, приведенную на следующем рисунке.
В этом примере мы будем мигать светодиодом с заданной периодичностью, но эта периодичность будет задаваться не с помощью функции delay, а с помощью прерывания от таймера.
Внешний вид собранной на макетной плате конструкции проекта показан на следующем рисунке.
Объяснение кода программы для модуля ESP32
Полный код программы приведен в конце статьи, здесь же мы кратко рассмотрим его основные фрагменты. В нашем примере светодиод будет мигать с частотой 1 Гц.
Вначале программы дадим осмысленное наименование контакту, к которому подключен светодиод (GPIO21). Затем создадим указатель на переменную с именем My_timer и типом type hw_timer_t для того чтобы в последующем производить настройку таймера.
1 2 |
#define LED 21 hw_timer_t *My_timer = NULL; |
Далее запрограммируем функцию обработки прерывания от таймера (ISR). В ней мы будем инвертировать состояние контакта GPIO21, к которому подключен светодиод. Данная функция ISR будет вызываться каждый раз при срабатывании прерывания от таймера.
1 2 3 |
void IRAM_ATTR onTimer(){ digitalWrite(LED, !digitalRead(LED)); } |
Затем в функции void setup() мы зададим режим работы контакта, к которому подключен светодиод, на вывод данных.
1 2 3 4 5 6 7 8 9 |
void setup() { pinMode(LED, OUTPUT) My_timer = timerBegin(0, 80, true); timerAttachInterrupt(My_timer, &onTimer, true); timerAlarmWrite(My_timer, 1000000, true); timerAlarmEnable(My_timer); } void loop() { } |
Для инициализации таймера мы будем использовать функцию timerbegin с необходимыми параметрами. Первым из них будет номер таймера, который мы будем использовать (их номера от 0 до 3 поскольку у нас 4 таймера в модуле ESP32). Второй – это коэффициент деления предделителя и последний – это флаг, показывающий должен ли счетчик таймера считать вверх (true) или вниз (false). В нашем проекте мы используем timer 0 с коэффициентом деления предделителя 80 и счетчиком, считающим вверх.
1 |
My_timer = timerBegin(0, 80, true); |
Перед тем как задействовать таймер, мы должны прикрепить (attach) к нему функцию обработки прерывания (ISR), которая будет исполняться при срабатывании прерывания. Делать мы это будем с помощью функции timerAttachInterrupt. В нашем примере мы прикрепим функцию обработки прерывания onTimer к нашему таймеру.
1 |
timerAttachInterrupt(My_timer, &onTimer, true); |
Далее мы будем использовать функцию timerAlarmWrite чтобы загрузить в таймер значение, при достижении которого счетчиком таймера должно срабатывать прерывание от таймера. В нашем примере нам необходимо формировать прерывание каждую секунду, поэтому мы загрузим в таймер значение 1000000 микросекунд, что равно 1 секунде. В качестве третьего параметра функции timerAlarmWrite мы используем значение true, что будет означать что счетчик таймера будет перезагружаться и, таким образом, прерывание будет срабатывать периодически.
1 |
timerAlarmWrite(My_timer, 1000000, true); |
И, наконец, нам необходимо включить использование прерывания от таймера с помощью функции timerAlarmEnable.
1 |
timerAlarmEnable(My_timer); |
Исходный код программы
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#define LED 21 hw_timer_t *My_timer = NULL; void IRAM_ATTR onTimer(){ digitalWrite(LED, !digitalRead(LED)); } void setup() { pinMode(LED, OUTPUT); My_timer = timerBegin(0, 80, true); timerAttachInterrupt(My_timer, &onTimer, true); timerAlarmWrite(My_timer, 1000000, true); timerAlarmEnable(My_timer); //Just Enable } void loop() { } |
Все необходимые файлы для этого проекта можно скачать по следующей ссылке.
3 580 просмотров
Спасибо за полезный пример. Пжт, подскажите как выйти из цикла, например, по прошествии определенного времени или по какому-то событию (например, команды из порта).
В функции обработчике прерывания от таймера инкрементировать переменную счетчик и при достижении ею определенного значения делать нужное вам действие, а значение переменной сбрасывать в 0. Например, если таймер срабатывает у вас каждую секунду, то чтобы отмерить 100 секунд, необходимо будет сравнивать значение этой переменной со 100.
А чтобы выйти из цикла когда на необходимом контакте порта появится 0 или 1, то это легко сделать используя цикл while, можно даже проверку этого условия поставить в условие этого цикла