Прерывания используются для обработки событий, которые не происходят во время "нормального" функционирования программы, эти события происходят при срабатывании каких-нибудь триггеров. К примеру, в программе мигания светодиодом микроконтроллер исполняет команды последовательно, одну за другой. Но если мы хотим проконтролировать состояние светодиода в произвольный момент времени, это можно сделать только после того как все операции перед командой проверки будут выполнены, то есть это уже будет не в "реальном времени". Для решения подобных проблем и приходят на помощь прерывания. При использовании прерываний у нас нет необходимости непрерывно проверять состояние цифрового контакта микроконтроллера. При срабатывании прерывания микроконтроллер останавливает выполнение основной программы и вызывается функция/процедура обработки прерывания (Interrupt Service Routine, ISR). После выполнения всех команд внутри этой функции микроконтроллер возвращается к выполнению основной программы.
Модуль ESP32 имеет 32 прерывания на каждое ядро. Каждое прерывание имеет определенный уровень приоритета, большинство (но не все) прерывания подключены к мультиплексору прерываний. В связи с тем, что источников прерываний больше чем самих прерываний, некоторые прерывания делят общие источники прерываний.
Ранее на нашем сайте мы уже рассматривали практическое использование прерываний в модуле ESP32 в статье про использование спящих режимов в нем, в этой же статье мы рассмотрим общие принципы использования прерываний в данном модуле.
Типы прерываний в модуле ESP32
Основная классификация прерываний в модуле ESP32 разделяет их по источникам прерываний. Таким образом, можно выделить аппаратные и программные прерывания.
Внешние или аппаратные прерывания
Внешние прерывания срабатывают как отклик на какое внешнее аппаратное событие. Например, существует прерывание прикосновения (Touch Interrupt), которое срабатывает при обнаружении прикосновения. Также в эту категорию попадают и прерывания, срабатывающие при изменении состояния контактов ввода/вывода (GPIO) микроконтроллера.
Программные прерывания
Эти прерывания формируются при работе программы. Их типовым примером являются прерывания от таймеров. Более подробно эти прерывания рассмотрены в статье про таймеры в модуле ESP32.
Часто задаваемые вопросы про прерывания в модуле ESP32
Сколько прерываний может обрабатывать модуль ESP32?
Модуль ESP32 может обрабатывать до 32 прерываний на каждое ядро.
Как использовать внешние прерывания в модуле ESP32?
Для этого вам необходимо прикрепить (attach) один из доступных цифровых контактов к этому прерыванию и назначить для этого прерывания функцию его обработки (ISR) с помощью функции attachInterrupt.
Какие контакты модуля ESP32 поддерживают обработку внешних прерываний?
Это могут делать все контакты модуля ESP32, а 10 из этих контактов поддерживают обработку прерываний прикосновения (touch interrupt) – их называют сенсорными входами.
Какие режимы прерываний поддерживает модуль ESP32?
Модуль ESP32 поддерживает 5 типов следующих прерываний: LOW, HIGH, CHANGE, FALLING и RISING.
Аппаратные прерывания в модуле ESP32
Все контакты ввода/вывода (GPIO pins) модуля ESP32 мы можем использовать в качестве источников внешних прерываний. Для этого нужно прикрепить контакт в обработке прерывания и назначить ему функцию обработки прерывания. Сделать это можно с помощью функции attachInterrupt(), которая имеет следующий формат:
1 |
attachInterrupt(GPIOpin, ISR, Event); |
Функция attachInterrupt() имеет три аргумента:
GPIOpin – это номер контакта, к которому мы прикрепляем прерывание.
ISR – это имя функции, которая будет вызываться при срабатывании прерывания.
Event – событие, в результате которого прерывание наступает (срабатывает).
В модуле ESP32 возможно пять типов таких событий:
- LOW: прерывание срабатывает когда контакт находится в состоянии LOW;
- HIGH: прерывание срабатывает когда контакт находится в состоянии HIGH;
- CHANGE: прерывание срабатывает когда состояние контакта изменяется, с HIGH на LOW или с LOW на HIGH;
- FALLING: прерывание срабатывает когда состояние контакта изменяется с HIGH на LOW;
- RISING: прерывание срабатывает когда состояние контакта изменяется с LOW на HIGH.
К примеру, чтобы использовать прерывание, которое будет срабатывать при изменении состояния 4-го контакта (GPIO4), мы можем использовать команду вида:
1 |
attachInterrupt(4, ISR, CHANGE); |
Как отключить прерывание
В некоторых случаях бывает необходимо временно отключить обработку прерываний. Для этого можно использовать функцию detachInterrupt. Аргументом данной функции является номер контакта, на котором нужно отключить обработку прерывания. Функция detachInterrupt отключает обработку прерывания на указанном контакте до тех пор пока на нем не будет снова вызвана функция attachInterrupt, либо система перезагрузится. Синтаксис этой функции следующий:
1 |
detachInterrupt(GPIOPin); |
Функция (процедура) обработки прерывания (ISR)
Эта функция будет вызываться при срабатывании определенного прерывания. Ее синтаксис выглядит следующим образом:
1 2 3 |
void IRAM_ATTR ISR() { Statements; } |
Здесь ISR() – имя функции, а statements – это операции, которые выполняются при вызове данной функции. Поскольку функция (процедура) обработки прерывания блокирует выполнение основной программы рекомендуется чтобы ее тело было как можно более коротким, то есть чтобы функция выполнялась максимально быстро. Параметр IRAM_ATTR перед именем функции (ISR) гарантирует что она будет помещена в память IRAM, а не во флэш-память модуля ESP32. Благодаря этому повысится скорость загрузки данной функции.
Пример работы с аппаратным прерыванием – включение/выключение светодиода
В данном примере мы будем включать и выключать светодиод при нажатии кнопки. Для этого мы будем использовать обработку прерывания на контакте, к которому подключена кнопка. Для этого соберите схему проекта, показанную на следующем рисунке.
Внешний вид собранной на макетной плате конструкции проекта показан на следующем рисунке.
Код программы для данного примера обработки прерывания будет следующим:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#define pushButton_pin 33 #define LED_pin 32 void IRAM_ATTR toggleLED() { digitalWrite(LED_pin, !digitalRead(LED_pin)); } void setup() { pinMode(LED_pin, OUTPUT); pinMode(pushButton_pin, INPUT_PULLUP); attachInterrupt(pushButton_pin, toggleLED, RISING); } void loop() { } |
При нажатии кнопки в схеме нашего проекта уровень напряжения на контакте GPIO33 модуля ESP32 будет падать до 0V. При отпускании кнопки уровень напряжения на нем будет снова возрастать до VCC. При повышении уровня напряжения (rising) будет срабатывать прерывание на контакте GPIO33, в результате чего будет вызываться функция обработки прерывания toggleLED(). Поскольку данная функция будет вызываться у нас только при повышении уровня напряжения на контакте, то проблемы дребезга контактов кнопки не возникает.
Прерывания прикосновения в ESP32
Аналогично обычным прерываниям на контактах ввода/вывода (GPIO) модуль ESP32 может обрабатывать прерывания прикосновения (touch Interrupt) на своих 10 сенсорных входах (touch inputs). Для их использования также необходимо применять функцию touchAttachInterrupt, ее синтаксис в данном случае выглядит следующим образом:
1 |
touchAttachInterrupt(GPIOPin, ISR, Threshold) |
Здесь GPIOPin – это номер контакта, который поддерживает обработку прерываний прикосновения, ISR – имя функции для обработки прерывания, Threshold – значение прикосновения (touch value), при котором прерывание будет срабатывать. Во всем остальном работа с данными прерываниями аналогична работе с обычными аппаратными прерываниями в модуле ESP32.
Необходимые файлы для рассмотренного проекта можно скачать по следующей ссылке.
Пытаюсь сделать прерывание по приему байта в UART1 но не получается.
В UART0 все работает.
Вот как сделал:
/**************************************************************************
// Прерывание от UART
***************************************************************************/
void IRAM_ATTR serialEventRun(){
while(Serial.available())
Serial.println((char)Serial.read());
}
Все работает, но если сменить serial на serial1 компилится, не ругается, но не работает.
Инициализация serial1 конечно сделана.
Serial1.begin(115200,SERIAL_8N1, 0, 1);
Сообщения в serial1 выводятся, а прерывания не работают.
Плата ESP32-C3 Super Mini
К сожалению не знаю чем вам помочь. Сейчас пока доступа к железу нет, а чисто теоретически по вашей проблеме сложно подсказать.
Возможно ли прикрепить два прерывания на один пин, например falling и rising, например чтобы максимально быстро дублировать состояние одного пина (вход) на другом (выход)?
При этом детач убивает оба аттача?
Даже не знаю, не пробовал никогда так. А просто change вам не подойдет разве чтобы заменить falling + rising?
Приветствую. Трое суток уже ищу причину проблемы, может кто поможет чем. Нексия, выходы тахометра и спидометра идущие из ЭБУ на приборку ловлю прерываниями на пинах 34,35 ESP32devkit V1. адекватной подтяжки в еспхе не увидел, поэтому стянул пины 10к резисторами на 0 и продублировал стабилитронами 3,3в. сами пины дергаются через резистор 2к. Когда подобрал эти параметры, вышли четкие показатели, расколбаса нет. Формулы простые, прерывание запускает счетчик и запоминает micros(). на десятом тике считаем затраченное время, записываем в переменную, обнуляем счетчик итд до бесконечности. По необходимости считаем по переменной с временем нужную цифру в основном коде. Вечером отрепетировал коэффициенты, подогнал все, обрадовался результау, отметил... Утром все тоже самое запускаю...скачут обороты и скорость. Все тоже самое, скетч тот же, плата, элементы. Коэффициенты уже не подходят и параметры скачут от 50% до 150%. Раскопал старый скетч под ардуину, подключаю мегу, потом нану, потом уно, все идеально. Переписал на СТМ32С8Т6 - тоже самое как на есп32.... Я не особо силен во внутрянке этих контроллеров. У них что таймеры бегут по разному день ото дня? Или я что то не учел при назначении пинов? перекидывал на 25/26 - тож самое. Может есть какой прикол с еспхами особенный?
Если юзать прерывания то каналы ШИМ на ноги ADC лучше не назначать. ХЗ почему но тогда прерывания начинают жить своей жизнью. Да и считывание analogRead() шумит сильно. Думаю если надо много всего на еспху навешать то лучше разделить ответственность. Например ЕСП дает ШИМ и считает большую логику, а данные прерываний и аналоговых датчиков запрашивает у нано. по I2C или еще как. Так что куча способностей ЕСП32 дает ей универсальность конечно, но задействовать все и сразу не получится.
Да, тоже слышал про то, что ШИМ на пины с ADC лучше не назначать
Таймеры могут быть разной разрядности. У ESP32 таймеры являются 64-битными с 16-битным предделителем, у Ардуино точно не такие. А если разрядность таймеров разная, то считать до заданной точки они будут разное время
А у вас точно правильный порядок команды void IRAM_ATTR toggleLED() ?
У меня при этом ругается что "was not declared in this scope"
в строке attachInterrupt(pushButton_pin, toggleLED, RISING);
т.е. компилятор думает что нет подпрограммы прерывания с именем "IRAM_ATTR toggleLED()".
А если сделать так IRAM_ATTR void toggleLED(), то не ругается, но и непонятно есть ли толк.
Ну если следовать официальному документу от производителя данных модулей (https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/memory-types.html), то правильнее все же писать void IRAM_ATTR toggleLED(). Возможно, у вас ошибка выдается по какой то другой причине
Получается что в официальной документации опечатка. А авторы статет переписывают друг у друга не проверяя.
Не знаю, подробно не изучал данный вопрос. Но на форумах по этому вопросу синтаксис данных конструкций как в официальной документации
Оказывается внутри прерывания нельзя чтобы чтото было, что вызывает другое прерывание.
У меня в прерывании был вывод на индикатор, а для него использовна библиотека с прерываниями.
Удалил вывод на индикатор. Переменная начала выводиться из прерывания, но проявился другой глюк: с пина, через который происходит по кнопке вход в прерывание, после одного входа в прерывание, отлетает подтяжка INPUT_PULLUP.
Да, видимо модуль ESP32 не поддерживает многоуровневые прерывания
Добрый день! Подскажите, у меня прерывание включается по фронту RISING. Но как я понимаю, если сигнал остается и далее высоким на ноге прерывания, после обработки прерывания, программа должна продолжиться, у меня не хочет. Выполняется если вновь сигнал уходит в ноль. В чем может быть проблема?
Добрый вечер. Ну может быть вы в функции обработки прерывания меняете значение какой-нибудь переменной, а в основной программе у вас стоит условие с этой переменной, в результате условие не выполняется и программа дальше не работает. После обработки прерывания управление должно передаваться в основную программу независимо от того по какому уровню происходит срабатывание прерывания. Или у вас в функции обработки прерывания происходит какое нибудь зацикливание, в результате чего выход из этой функции у вас просто не происходит.
Я бы советовал вам добавить в программу вывод в монитор последовательного порта значений переменных, которые каким либо образом связаны с обработкой прерывания. Можно даже выводить состояние контакта, на котором вы обрабатываете прерывание. Может быть, вы как то неправильно судите об истинном состоянии этого контакта.
Спасибо за ответ. Попробуйте на Вашем примере с кнопкой, нажать кнопку, включите диод по прерыванию на 3 секунды(например) и удерживая кнопку выйти из обработки прерывания тем самым отклюить диод. У меня примерно такая ситуация с меткой на упаковочной бумаге.
Успехов вам. Будем надеяться что у вас все получится
Привет! А если нужно знать, rising или falling и дублировать состояние на другой пин, потребуется два прерывания на пин? В change нет варианта узнать, в какую он сторону, этот change?
Задача - в некоторых ситуациях повторять все, что приходит на пин Х, в виде похожего на uart/tx протокола, на пине У.
Добрый день. Да, чтобы обрабатывать rising или falling потребуется два прерывания на пин. При использовании change если вы знаете начальное состояние контакта, то с помощью дополнительно введенной переменной вы сможете легко следить за тем каким становится состояние контакта при срабатывании change.
Здравствуйте. Не подскажете, в чем может быть проблема. ESP32.
описываем прерывание на 12 ноге attachInterrupt(digitalPinToInterrupt(12), prerivanie, RISING);
само прерывание void prerivanie() { код исправно включает/гасит по кнопке на 12 ноге диод }, но вот вывод на индикатор 1602 в прерывании никак не происходит и измененные переменные из прерывания в основную программу не попадают . Подозреваю что вывод на индикатор не идет, потому что использую библиотеку LiquidCrystal_I2C переменные которой не заявлены как volatile, но другие переменные-то заявлены.
При попытке записать void IRAM_ATTR prerivanie() или же void ICACHE_RAM_ATTR prerivanie() выскакивает ошибка "void ICACHE_RAM_ATTR prerivanie()". Ошибка исчезает, если void prerivanie() разместить перед void setup(), хотя разницы быть не должно.
Чем не нравятся IRAM_ATTR / ICACHE_RAM_ATTR и почему внутри прерывания не работает вывод на индикатор??
Добрый день.
IRAM_ATTR prerivanie() и ICACHE_RAM_ATTR prerivanie() - для простых функций разницы нет стоят ли они перед void setup() или после нее, но здесь же вы хотите закэшировать функцию в оперативной памяти модуля чтобы она быстрее исполнялась, поэтому, наверное, для таких функций есть разница где их размещать.
Не работает вывод на индикатор, скорее всего, из-за переменных, как вы сами указали. Вы каким образом это делаете? Можете скопировать сюда кусок кода (но не весь, код может обрезаться в комментариях), я попробую посмотреть. И можете ли вы задать эти переменные через define? Иногда это помогает
При нажатии кнопки вход в прерывание получается, светодиод включается-гснет, но индикатор не реагирует и переменная Knopka не меняется. Хотя Knopka обявлена volatile.
#include
#include
LiquidCrystal_I2C lcd(0x27, 16, 2); // Set the LCD address to 0x27 for a 16 chars and 2 line display
volatile uint8_t Knopka;
volatile uint8_t a=0;
// ========================== ПРЕРЫВАНИЕ ===============================================
void ICACHE_RAM_ATTR prerivanie() // мигать светодиодом при каждом нажатии
//void IRAM_ATTR prerivanie()
{
if(a == HIGH) a = LOW;
else a = HIGH;
digitalWrite(13, a); // зажигать-гасить диод
lcd.setCursor(9,0); lcd.print("99999"); // вывести просто так
lcd.setCursor(9,0); lcd.print(a); // вывести меняющуюся от кнопки переменную
Knopka = 5;
} // КОНЕЦ ПРЕРЫВАНИЯ
// ========================== SETUP ===============================================
void setup()
{
lcd.begin();// initialize the LCD
lcd.backlight(); // Turn on the blacklight and print a message.
pinMode(12, INPUT_PULLDOWN); // кнопка
pinMode(13, OUTPUT); // светодиод
attachInterrupt(digitalPinToInterrupt(12), prerivanie, RISING); // инициализация прерывания
//digitalPinToInterrupt
}
// ====================== LOOP ===========================================================
void loop()
{
lcd.setCursor(3,0); lcd.print(a);
lcd.setCursor(3,1); lcd.print(Knopka);
delay(500);
}
У вас начальное состояние переменной Knopka не задано и вы ее как то немного странно используете в прерывании, на мой взгляд. А попробуйте ее значение в прерывании и в основном цикле выводить в окно монитора последовательной связи. Посмотрите меняется она или нет
Спасибо. Вывел в монитор. Обе переменные внутри прерывания меняются, но из прерывания выходят нулевыми. И выводятся в монитор попарно, а когда заканчивается прерывание выскакивает в мон итор странная таблица с еще более странным "Гуру медитации":
Guru Meditation Error: Core 0 panic'ed (LoadProhibited). Exception was unhandled.
Core 0 register dump:
PC : 0x4008cf7f PS : 0x00060d33 A0 : 0x8008c453 A1 : 0x3ffbd0b0
A2 : 0x3ffbc7cc A3 : 0x3ffbd238 A4 : 0x00000001 A5 : 0x00000001
A6 : 0x00060d23 A7 : 0x00000000 A8 : 0x00000000 A9 : 0x3ffbc7cc
A10 : 0x3ffb8074 A11 : 0x00060023 A12 : 0x00060021 A13 : 0x00000020
A14 : 0x00000020 A15 : 0x3ffbca40 SAR : 0x00000000 EXCCAUSE: 0x0000001c
EXCVADDR: 0x00000004 LBEG : 0x40088700 LEND : 0x4008871c LCOUNT : 0x00000000
ELF file SHA256: 0000000000000000
Backtrace: 0x4008cf7f:0x3ffbd0b0 0x4008c450:0x3ffbd0d0 0x4008ad9f:0x3ffbd0f0 0x4008ae2d:0x3ffbd110 0x4008cac2:0x3ffbd130 0x4008cba3:0x3ffbd170 0x4008a661:0x3ffbd1a0
Rebooting...
0Q⸮0q⸮q1)f!G⸮⸮⸮=
Y
⸮n⸮0: 0
0: 0
Exception was unhandled - это сообщение свидетельствует о том, что прерывание было не обработано. Я бы посоветовал вам начать с более простой схемы и добиться того чтобы такое сообщение у вас не появлялось. А потом постепенно усложнять схему и программу.
Может быть, вам стоит попробовать использовать функцию обработки прерывания без ICACHE_RAM_ATTR. Также я бы сократил код внутри функции обработки прерывания - золотым стандартом является то, чтобы делать эти функции максимально короткими. У вас сейчас внутри этой функции стоит вывод значений на экран монитора, а это достаточно медленная операция и не очень хорошо для функции обработки прерывания. Я бы просто внутри этой функции менял бы переменную, которая отвечает за вывод той или иной информации на экран дисплея, а сам вывод на экран дисплея осуществлял бы в основном цикле программы
Первая статья, где все разжевали и положили в рот!
Браво!!!
Огромное спасибо!
Может быт теперь разберусь почему я не могу получить хоть какие то данные с модуля Rx008B.
Пишите, пишите пожалуйста побольше таких статей и столь же подробно.
Спасибо !
Спасибо что оценили мой труд. Постараюсь. И вам успехов в освоении микроконтроллеров