Рассмотрим быстро едущую машину. Если она в процессе своего движения сталкивается с другой машиной, акселерометр, присутствующий в машине, фиксирует значительное отрицательное ускорение (то есть замедление) и подает сигнал внешнего прерывания на микроконтроллер, присутствующий в машине. В результате этого микроконтроллер подает управляющий сигнал на немедленное срабатывание подушек безопасности. Микроконтроллер в машине в процессе ее движения выполняет множество функций – определение скорости, опрос различных датчиков, управление температурой кондиционера и т.д. Так почему же он на фоне всех этих действий может немедленно подать команду на срабатывание подушек безопасности. Все дело в использовании сигнала прерывания, который имеет наивысший приоритет среди всех выполняемых микроконтроллером действий.
Другим примером прерываний является экран современных смартфонов, реагирующий на прикосновения. Обработка прикосновения к экрану имеет наивысший приоритет среди всех действий, выполняемых микропроцессором смартфона. Почти все современные электронные устройства имеют определенные виды прерываний (в англ. – interrupts), которые «прерывают» регулярную последовательность действий чтобы выполнить какое-нибудь первоочередное действие при наступлении определенного события. Процесс выполнения регулярной последовательности действий возобновляется после обслуживания прерывания.
Фактически, прерывания представляют собой механизм, с помощью которого можно временно приостановить нормальную последовательность действий (нормальное функционирование) и вынудить его обслуживать прерывание, которое в данном случае имеет наивысший приоритет среди всех выполняемых им действий. К примеру, нормальное функционирование микропроцессора может прервать какой-нибудь датчик и запустить последовательность действий, присутствующую в программе (функции, процедуре) обслуживания прерываний (ISR — Interrupt Service Routine). После выполнения программы обслуживания прерывания микропроцессор может вернуться к нормальному функционированию.
Типы прерываний
Существует два типа прерываний:
- Аппаратные прерывания: возникают при наступлении какого-нибудь внешнего события, например, состояние контакта изменится с LOW на HIGH или с HIGH на LOW.
- Программные прерывания: возникают при выполнении каких-нибудь инструкций в теле программы. К примеру, прерывания от таймеров в Arduino являются программными прерываниями.
Прерывания в Arduino
Платы Arduino поддерживают два типа прерываний:
- внешние прерывания;
- прерывания, возникающие при изменении состояния контакта.
Внешние прерывания
Эти прерывания обрабатываются на аппаратном уровне, поэтому их обработка происходит очень быстро. Эти прерывания можно настроить на события увеличения или падения уровня, или просто на низкий уровень напряжения.
В следующей таблице представлены контакты для различных типов плат Arduino, на которых возможна обработка внешних прерываний.
Тип платы Arduino | Номера контактов для использования внешних прерываний |
UNO , NANO | 2,3 |
Mega | 2,3,18,19,20,21 |
Как видите из представленной таблицы, в наиболее распространенных платах Arduino (Uno и Nano) всего два контакта для обработки внешних прерываний, но в большинстве реальных задач этого хватает.
Прерывания, возникающие при изменении состояния контакта
В платах Arduino можно значительно увеличить количество задействованных контактов для обработки прерываний. В платах Arduino, построенных на основе микроконтроллеров ATmega168/328, любой из 20 сигнальных контактов можно сконфигурировать на обработку прерывания. Их также можно настроить на события увеличения или падения уровня.
Использование прерываний в Arduino
Для использования прерываний в платах Arduino необходимо понять несколько основополагающих вещей.
Программа обслуживания прерывания (ISR — Interrupt Service Routine)
Данная программа представляет собой обработчик прерывания и обычно состоит из небольшого числа инструкций. Когда поступает сигнал внешнего прерывания, микроконтроллер сначала исполняет инструкции, записанные в данном обработчике прерывания, а после этого возвращается к тому месту программы, во время исполнения которого поступил запрос на исполнение прерывания, то есть возвращается в режим «нормального функционирования».
В Arduino используется следующий формат команды для программы обслуживания прерывания (ISR):
attachInterrupt(digitalPinToInterrupt(pin), ISR, mode);
digitalPinToInterrupt(pin): в платах Arduino Uno, NANO для обработки прерываний используются контакты 2,3, а в Arduino Mega – контакты 2,3,18,19,20,21. В этом параметре приведенной команды необходимо указать конкретный контакт (один из доступных для этого), который будет использоваться для обработки сигнала внешнего прерывания.
ISR: название функции, которая будет вызываться при поступлении внешнего прерывания.
Mode: тип перехода (изменения состояния), который будет обрабатываться, например, понижение уровня, повышение уровня и т.д. Возможны следующие значения данного параметра:
- RISING (повышение уровня): будет срабатывать прерывание когда контакт будет переходить из состояния LOW в состояние HIGH;
- FALLING (понижение уровня): будет срабатывать прерывание когда контакт будет переходить из состояния HIGH в состояние LOW;
- CHANGE (изменение уровня): будет срабатывать прерывание когда контакт будет переходить из состояния LOW в состояние HIGH или из состояния HIGH в состояние LOW, то есть изменять свое состояние.
Особенности использования прерываний
- Функция программы обслуживания прерывания (ISR) должна быть по возможности максимально короткой.
- Внутри программы обслуживания прерывания не работает функция задержки Delay(), поэтому ее использование в данной программе необходимо исключить.
В данном руководстве (обучающей статье) по использованию прерываний в платах Arduino мы будем использовать два прерывания. Для срабатывания данных прерываний мы будем использовать две кнопки, подключенные к контактам D2 и D3 платы Arduino. Для индикации поступившего прерывания будет использоваться светодиод. При нажатии первой кнопки светодиод будет загораться и на ЖК дисплее будет высвечиваться надпись interrupt2 (прерывание 2). При нажатии второй кнопки светодиод будет выключаться и на ЖК дисплее будет высвечиваться надпись interrupt1 (прерывание 1).
Необходимые компоненты
- Плата Arduino Nano (купить на AliExpress). Можно использовать и другие типы плат Arduino.
- ЖК дисплей 16х2 (купить на AliExpress).
- Кнопки – 2 шт.
- Светодиод (купить на AliExpress).
- Резистор 10 кОм – 2 шт. (купить на AliExpress).
- Макетная плата.
- Соединительные провода.
Схема проекта
Схема проекта для демонстрации возможностей использования прерываний в плате Arduino представлена на следующем рисунке.
ЖК дисплей | Плата Arduino Nano |
VSS | GND |
VDD | +5V |
V0 | к средней точке потенциометра для управления контрастностью ЖК дисплея |
RS | D7 |
RW | GND |
E | D8 |
D4 | D9 |
D5 | D10 |
D6 | D11 |
D7 | D12 |
A | +5V |
K | GND |
Две кнопки подключены к контактам платы Arduino Nano D2 и D3. Кнопки используются для формирования двух сигналов внешних прерываний: одно будет включать светодиод, а другое – выключать. Каждая кнопка подключена с помощью внешнего подтягивающего резистора на 10 кОм – использование подтягивающих резисторов в данном случае является обязательным чтобы исключить ложные (плавающие) изменения уровней на контактах D2 и D3. То есть когда кнопка будет нажата, на контакте, к которому она подключена, будет состояние логической 1 (HIGH), а если кнопка будет отжата, на контакте, к которому она подключена, будет состояние логического 0 (LOW).
Светодиод в схеме будет также информировать нас о том, что произошло (сработало) какое-нибудь из двух прерываний.
Объяснение программы для Arduino
Полный текст программы приведен в конце статьи, здесь же мы рассмотрим его основные фрагменты.
1. Сначала в программе нам необходимо подключить библиотеку для работы с ЖК дисплеем и инициализировать используемые контакты.
1 2 |
#include<LiquidCrystal.h> LiquidCrystal lcd (7,8,9,10,11,12); // Define LCD display pins RS, E, D4, D5, D6, D7 |
2. Внутри функции void setup () мы на экране ЖК дисплея покажем пару приветственных сообщений.
1 2 3 4 5 6 7 |
lcd.begin(16,2); lcd.setCursor(0,0); lcd.print("CIRCUIT DIGEST"); lcd.setCursor(0,1); lcd.print("ArduinoInterrupt"); delay(3000); lcd.clear(); |
3. Также в функции void setup () необходимо задать режим работы для контакта D13 (к нему подключен светодиод) – на вывод данных.
1 |
pinMode(13,OUTPUT); |
4. Далее следует такая важная часть для нашей программы, как использование функции attachInterrupt() – это тоже происходит в функции void setup().
1 2 |
attachInterrupt(digitalPinToInterrupt(2),buttonPressed1,RISING); attachInterrupt(digitalPinToInterrupt(3),buttonPressed2,RISING); |
В первой из этих команд контакт 2 назначается для обработки внешнего прерывания, а функция buttonPressed1 будет вызываться всегда, когда будет происходить повышение уровня (с LOW до HIGH) на контакте D2. Контакт 3 назначается для обработки внешнего прерывания, а функция buttonPressed2 будет вызываться всегда, когда будет происходить повышение уровня (с LOW до HIGH) на контакте D3.
5. Внутри функции void loop() число i инкрементируется начиная с 0 и выводится на экран ЖК дисплея.
1 2 3 4 5 |
lcd.clear(); lcd.print("COUNTER:"); lcd.print(i); ++i; delay(1000); |
Также в функции void loop() функция digitalWrite() включает или выключает светодиод (в зависимости от состояния переменной output), подключенный к контакту D13.
6. Наиболее важная часть нашей программы – это создание функции для обработки прерывания, имя этой функции было определено при вызове функции attachInterrupt().
Поскольку в нашей программе мы используем 2 контакта для обработки прерываний, то, следовательно, нам понадобится и две функции для обработки прерываний.
1 2 3 4 5 6 |
void buttonPressed1() { output = LOW; lcd.setCursor(0,1); lcd.print("Interrupt 1"); } |
Эта функция будет выполняться когда будет происходить повышение уровня на контакте D2. Эта функция будет изменять состояние переменной output на LOW, что будет приводить к выключению светодиода, а также будет выводить сообщение “interrupt1” на экран ЖК дисплея.
1 2 3 4 5 6 |
void buttonPressed2() { output = HIGH; lcd.setCursor(0,1); lcd.print("Interrupt2"); } |
Эта функция будет выполняться при нажатии кнопки подключенной к контакту D3. Эта функция будет изменять состояние переменной output на HIGH, что будет приводить к включению светодиода, а также будет выводить сообщение “interrupt2” на экран ЖК дисплея.
Демонстрация использования прерываний в плате Arduino
1. При нажатии кнопки на левой части схемы будет зажигаться светодиод и на экране ЖК дисплея будет показываться сообщение Interrupt2.
2. При нажатии кнопки на правой части схемы будет выключаться светодиод и на экране ЖК дисплея будет показываться сообщение Interrupt1.
Аналогичным образом можно использовать прерывания в реальных задачах. На нашем сайте пример использования прерываний в Arduino можно посмотреть в следующих проектах:
- спидометр для велосипеда на основе платы Arduino и смартфона на Android;
- измерение скорости, пройденного пути и угла поворота с помощью Arduino и датчика LM393.
Исходный код программы (скетча)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
#include<LiquidCrystal.h> // подключение библиотеки для работы с ЖК дисплеем LiquidCrystal lcd (7,8,9,10,11,12); // контакты Arduino , к которым подключены контакты ЖК дисплея (RS,E,D4,D5,D6,D7) volatile int output = LOW; int i = 0; void setup() { lcd.begin(16,2); // устанавливаем режим работы ЖК дисплея 16x2 lcd.setCursor(0,0); lcd.print("CIRCUIT DIGEST"); lcd.setCursor(0,1); lcd.print("ArduinoInterrupt"); delay(3000); lcd.clear(); pinMode(13,OUTPUT); attachInterrupt(digitalPinToInterrupt(2),buttonPressed1,RISING); // функция для создания внешнего прерывания на контакте 2 для события повышения уровня (с LOW на HIGH) attachInterrupt(digitalPinToInterrupt(3),buttonPressed2,RISING); // функция для создания внешнего прерывания на контакте 3 для события повышения уровня (с LOW на HIGH) } void loop() { lcd.clear(); lcd.print("COUNTER:"); lcd.print(i); ++i; delay(1000); digitalWrite(13,output); //светодиод включается или выключается в зависимости от состояния переменной output } void buttonPressed1() // эта функция для обработки прерывания выполняется при нажатии кнопки, подключенной к контакту D2 { output = LOW; //изменяем состояние переменной Output на LOW lcd.setCursor(0,1); lcd.print("Interrupt 1"); } void buttonPressed2() // эта функция для обработки прерывания выполняется при нажатии кнопки, подключенной к контакту D3 { output = HIGH; // изменяем состояние переменной Output на HIGH lcd.setCursor(0,1); lcd.print("Interrupt2"); } |
16 ответов к “Руководство по прерываниям в Arduino для начинающих”
На плате Ардуино Мега 2560 имеется 6 входов аппаратных прерываний.
А нужно хотя бы 12.
Можно ли это организовать?
Ставить второй процессор с ещё 6-входами — это не вариант.
Буду рад любым идеям
Можно использовать плату Arduino Due, она позволяет использовать функции прерывания для всех доступных контактов
Здравствуйте. Вас устроил ответ автора статьи?
Я на ардуино уно написал ради развлечения библиотеку, когда у меня есть прерывания для любого пина.
Думаю, что и в мега так можно сделать.
Посмотрите информацию по прерываниям PCINT. Именно ими я пользовался, и прямой работой с регистрами PCICR и PCMSK.
Добрый день, а не поделитесь ссылкой на вашу библиотеку. Думаю, она могла бы быть полезна тем, кто заинтересовался темой прерываний в Ардуино
Здравствуйте !
Можно ли создать прерывания от кнопок в Ардуино МЕГА 2560 не используя контакты 2,3,18,19,20,21, а используя другие цифровые или аналоговые контакты ?
Если ДА, то как ?
Добрый вечер. Да, можно, но их обработка будет происходить ощутимо медленнее (поскольку они будут обрабатываться программно, а не аппаратно) чем на специально выделенных для этого контактах. Как сделать, сейчас не могу конкретно подсказать поскольку нахожусь сейчас в отпуске и нет доступа к ряду ресурсов
Здравствуйте. А вот я про AVR как раз и хотел спросить)) Как с помощью внутреннего генератора сделать, чтобы таймер у ATmega8A считал больше, чем до 1 минуты? В новых МК тактовая частота 8000000/8=1000000Гц. Если применить предделитель 1024, то получим 1000000/1024=976,56Гц или 0,001 сек. В регистр сравнения записываем максимальное число 65535. И получаем, что таймер может досчитать только до 65 сек. А мне надо от трёх минут и выше.
Может быть, в прерывании по совпадению запустить ещё один таймер, потом по его прерыванию запустить ещё один, и там уже будут мои 3 секунды?))
Минуты, а не секунды.
Запустите отсчет этого таймера в цикле от одного до трех. Если поставите время счета 60 секунд, то при выполнении цикла три раза у вас как раз отмерится время равное 3 минутам
А, ну правильно: часы ведь так и работают))) Спасибо!
Успехов ))
Добрый день, могу ли я обратиться к Вам за советом с вопросом, и тогда у Вас может появится новая тема в отношении прерываний и радиочастот на ардуино. Такого решения, по моему вопросу за 2-е недели, я так и не нашёл. Даже к сожалению у Вас, хотя некоторые Ваши темы взял на вооружение, в том числе и эту. Спасибо, буду признателен за любой ответ.
Добрый вечер. Спросить вы конечно можете, но если вы нигде в сети не нашли ответа на свой вопрос не думаю что я смогу вам полноценно помочь. Сильно глубоко внутрь платы Arduino я не погружался. Хотя обладаю некоторыми знаниями по микроконтроллерам AVR — может быть, именно это может помочь мне в ответе на ваш вопрос. В общем, можете писать свой вопрос, но гарантий, что я на него смогу ответить, я дать не могу
А вопрос то где? И что это за отношения радиочастот с прерываниями, да ещё на Ардуине? Даже прям ох как интересно стало! А я уже и совет заготовил… Но-о… Видимо мы так никогда и не узнаем, какую же интересную тему в их «отношениях» нашёл Андрей!
Да, что то такой интригующий вопрос так и не был задан, к сожалению