About Interrupt Service Routines
ISRs are special kinds of functions that have some unique limitations most other functions do not have. An ISR cannot have any parameters, and they shouldn’t return anything.
Generally, an ISR should be as short and fast as possible. If your sketch uses multiple ISRs, only one can run at a time, other interrupts will be executed after the current one finishes in an order that depends on the priority they have. millis() relies on interrupts to count, so it will never increment inside an ISR. Since delay() requires interrupts to work, it will not work if called inside an ISR. micros() works initially, but will start behaving erratically after 1-2 ms. delayMicroseconds() does not use any counter, so it will work as normal.
Typically global variables are used to pass data between an ISR and the main program. To make sure variables shared between an ISR and the main program are updated correctly, declare them as .
For more information on interrupts, see Nick Gammon’s notes.
Parameters
: the number of the interrupt (): the pin number (Arduino Due, Zero, MKR1000 only): the ISR to call when the interrupt occurs; this function must take no parameters and return nothing. This function is sometimes referred to as an interrupt service routine.: defines when the interrupt should be triggered. Four constants are predefined as valid values:
-
LOW to trigger the interrupt whenever the pin is low,
-
CHANGE to trigger the interrupt whenever the pin changes value
-
RISING to trigger when the pin goes from low to high,
-
FALLING for when the pin goes from high to low.
The Due, Zero and MKR1000 boards allows also: -
HIGH to trigger the interrupt whenever the pin is high.
Общие рекомендации по написанию обработчиков прерываний
В заключение хочу привести несколько рекомендаций по написанию обработчиков прерываний.
- Во-первых, делайте обработчики предельно короткими. Ведь они прерывают выполнение основной программы, а также блокируют обработку других прерываний. По возможности обработчик должен фиксировать только факт возникновения события, изменяя значение переменной. А сама реакция на событие должна выполняться в основной программе при анализе этой переменной.
- Как уже было сказано, при входе в обработчик устанавливается глобальный запрет на обработку других прерываний. А это в свою очередь влияет на работу функций, использующих прерывания. Будьте с ними осторожнее. Если не уверены в безопасности их вызова, то лучше откажитесь от их использования в обработчике.
- Возьмите за правило объявлять разделяемые между основной программой и обработчиком переменные как volatile. И не забывайте, что этого квалификатора недостаточно в случае многобайтных переменных — используйте при работе с ними атомарно исполняемые блоки или interrupts/noInterrupts
следующей части
Тестирование работы проекта
В нашем проекте мы подключили датчик расхода воды к водопроводной трубе. Если по трубе не течет воды, то на выход датчика расхода воды не поступает никаких импульсов, следовательно, не регистрируется прерываний на контакте 2 платы Arduino и значение переменной flow_frequency будет равно нулю. В этом случае выполняется код основной программы, идущий после оператора else.
Если по трубе протекает вода, то турбина (колесо, крыльчатка) внутри датчика расхода воды начинает вращаться, поэтому на выходе датчика расхода воды появляются электрические импульсы, генерируемые датчиком Холла. Каждый из этих импульсов вызывает срабатывание прерывания на контакте 2 платы Arduino. С каждым поступившим сигналом прерывания (rising edge – передний фронт импульса) значение переменной flow_frequency увеличивается на 1. Затем переменные current time и cloopTIme гарантируют, что значение переменной flow_frequency будет учитываться в расчетах каждую секунду. После проведения вычислений значение переменной flow_frequency устанавливается равным 0 и процесс начинается сначала.
Более подробно работу проекта вы можете посмотреть в видео, приведенном в конце статьи.
Мы прерываем нашу передачу…
Как выясняется, существует отличный (но недостаточно часто используемый) механизм, встроенный во все Arduino, который идеально подходит для отслеживания событий в режиме реального времени. Данный механизм называется прерыванием. Работа прерывания заключается в том, чтобы убедиться, что процессор быстро отреагирует на важные события. При обнаружении определенного сигнала прерывание (как и следует из названия) прерывает всё, что делал процессор, и выполняет некоторый код, предназначенный для реагирования на вызвавшую его внешнюю причину, воздействующую на Arduino. После того, как этот код будет выполнен, процессор возвращается к тому, что он изначально делал, как будто ничего не случилось!
Что в этом удивительного, так это то, что прерывания позволяют организовать вашу программу так, чтобы быстро и эффективно реагировать на важные события, которые не так легко предусмотреть в цикле программы. И лучше всего это то, что прерывания позволяют процессору заниматься другими делами, а тратить время на ожидание события.
Объяснение программы для Arduino
Полный код программы приведен в конце статьи, здесь же мы кратко рассмотрим его основные фрагменты.
Arduino
#include <dht.h>
#include <LowPower.h>
#define dataPin 2
dht DHT;
1 |
#include <dht.h> dhtDHT; |
В функции void setup мы инициализируем последовательную связь с помощью функции serial.begin(9600). Мы будем использовать встроенный в плату Arduino светодиод для индикации спящего режима. Также зададим режимы работы используемых контактов.
Arduino
void setup() {
Serial.begin(9600);
pinMode(LED_BUILTIN,OUTPUT);
digitalWrite(LED_BUILTIN,LOW);
}
1 |
voidsetup(){ Serial.begin(9600); pinMode(LED_BUILTIN,OUTPUT); digitalWrite(LED_BUILTIN,LOW); } |
В функции void loop мы зажигаем встроенный в плату светодиод (подаем на него HIGH) и считываем значения температуры и влажности с датчика DHT11, которые мы затем сохраняем в переменных ‘t’ и ‘h’. Затем мы данные температуры и влажности выводим в окно монитора последовательной связи (serial monitor).
Arduino
void loop() {
Serial.println(«Get Data From DHT11»);
delay(1000);
digitalWrite(LED_BUILTIN,HIGH);
int readData = DHT.read11(dataPin); // DHT11
float t = DHT.temperature;
float h = DHT.humidity;
Serial.print(«Temperature = «);
Serial.print(t);
Serial.print(» C | «);
Serial.print(«Humidity = «);
Serial.print(h);
Serial.println(» % «);
delay(2000);
1 |
voidloop(){ Serial.println(«Get Data From DHT11»); delay(1000); digitalWrite(LED_BUILTIN,HIGH); intreadData=DHT.read11(dataPin);// DHT11 floatt=DHT.temperature; floath=DHT.humidity; Serial.print(«Temperature = «); Serial.print(t); Serial.print(» C | «); Serial.print(«Humidity = «); Serial.print(h); Serial.println(» % «); delay(2000); |
Перед переходом в спящий режим мы выводим в окно монитора последовательной связи «Arduino: — I am going for a Nap» и выключаем встроенный в плату светодиод (подаем на него Low). После этого с помощью специальной команды (рассмотрена ранее в статье) плата Arduino переводится в спящий режим.
В нашем примере мы переводим плату Arduino в периодический режим Idle на 8 секунд. В этом режиме АЦП, таймеры, интерфейсы SPI, USART, TWI переводятся в выключенное состояние (OFF). После истечения 8 секунд плата Arduino автоматически выводится из спящего режима и в окно монитора последовательной связи печатается сообщение “Arduino:- Hey I just Woke up”.
Arduino
Serial.println(«Arduino:- I am going for a Nap»);
delay(1000);
digitalWrite(LED_BUILTIN,LOW);
LowPower.idle(SLEEP_8S, ADC_OFF, TIMER2_OFF, TIMER1_OFF, TIMER0_OFF,
SPI_OFF, USART0_OFF, TWI_OFF);
Serial.println(«Arduino:- Hey I just Woke up»);
Serial.println(«»);
delay(2000);
}
1 |
Serial.println(«Arduino:- I am going for a Nap»); delay(1000); digitalWrite(LED_BUILTIN,LOW); LowPower.idle(SLEEP_8S,ADC_OFF,TIMER2_OFF,TIMER1_OFF,TIMER0_OFF, SPI_OFF,USART0_OFF,TWI_OFF); Serial.println(«Arduino:- Hey I just Woke up»); Serial.println(«»); delay(2000); } |
Таким образом, в нашем примере в течение минутного интервала плата Arduino будет находиться в спящем режиме 36 секунд и в обычном режиме 24 секунды, что позволяет значительно уменьшить энергопотребление нашей «станции погоды» на основе платы Arduino.
Энергопотребление нашего проекта в спящем режиме
Энергопотребление нашего проекта в обычном режиме
Таким образом, если мы будем использовать подобный спящий режим в плате Arduino, мы сможем приблизительно увеличить время ее функционирования в 2 раза при ее работе от батарейки.
Почему стоит воздержаться от использования функции delay() в Arduino
В документации на Arduino указано, что в этой платформе существует две функции для организации задержек — delay() и delayMicroseconds(). По принципу действия обе эти функции абсолютно идентичны, только в функции delay() значение задержки указывается в миллисекундах, а в функции delayMicroseconds() – в микросекундах.
Обе эти функции останавливают работу платы Arduino на заданное время. К примеру, если мы сделаем задержку на 10 секунд, то плата Arduino не сможет выполнить никакую другую команду до тех пор, пока эти 10 секунд не истекут. Естественно, подобный подход сильно мешает рациональному использованию вычислительных ресурсов микроконтроллера.
Рассмотрим, к примеру, две кнопки. Допустим, нам нужно переключать состояние двух светодиодов с помощью этих двух кнопок. Например, при нажатии первой кнопки первый светодиод должен включаться на 2 секунды, а при нажатии второй кнопки второй светодиод должен включаться на 4 секунды. Если в данном случае мы будем использовать функцию delay(), то при нажатии первой кнопки программа остановится на 2 секунды и даже если в это время нажать вторую кнопку, то программа просто не сможет обработать нажатие этой кнопки.
В официальной документации на Arduino вы можете прочитать замечания и предупреждения по использованию функции delay(). Если вы найдете в себе силы их прочитать, то вы окончательно убедитесь в том, что использование функции delay() – далеко не лучший способ организации задержек в программах для Arduino.
Подключение ISR к прерыванию
Для прерываний, которые уже обрабатываются библиотеками, вы просто используете документированный интерфейс. Например:
В этом случае библиотека I2C предназначена для обработки входящих байтов I2C внутри, а затем вызывает вашу предоставленную функцию в конце входящего потока данных. В этом случае receiveEvent не является строго ISR (он имеет аргумент), но он вызывается встроенным ISR.
Другим примером является прерывание «внешнего вывода».
В этом случае функция attachInterrupt добавляет функцию switchPressed во внутреннюю таблицу и дополнительно настраивает соответствующие флаги прерываний в процессоре.
Датчик расхода воды YF-S201
Как показано на рисунке, датчик YF-S201 имеет три провода: красный, желтый и черный. Красный провод используется для подачи питающего напряжения, которое может составлять от 5V до 18V, а черный провод подключается к земле (GND). Через желтый провод осуществляется передача выходных импульсов датчика, которые могут быть считаны микроконтроллером. Измеряющим элементом датчика является вихревое колесо (pinwheel), которое измеряет количество жидкости, прошедшее через него.
Принцип работы датчика расхода воды YF-S201 основан на эффекте Холла, который заключается в появлении разности потенциалов в электрическом проводнике под действием магнитного поля, приложенного перпендикулярно протекающему через проводник току. Датчик расхода воды YF-S201 включает датчик Холла, который генерирует электрический импульс с каждым вращением (оборотом) колеса, измеряющего поток воды. При этом датчик Холла надежно запаян и непосредственно не контактирует с водой, что позволяет ему всегда оставаться сухим и готовым к работе. Внешний вид датчика расхода воды YF-S201 показан на следующем рисунке.
Для соединения датчика YF-S201 с водопроводной трубой мы использовали два коннектора с внутренней резьбой, показанные на следующем рисунке.
В соответствии со спецификацией на датчик YFS201 максимальный потребляемый ток при питающем напряжении 5V составляет 15mA. При этом измеряемая им скорость потока воды составляет от 1 до 30 литров в минуту. Когда через датчик протекает поток воды, он контактирует с лопатками турбины (колеса), расположенного на пути потока воды. Ось турбины соединена с датчиком Холла, поэтому всегда, когда через датчик протекает поток воды, датчик Холла генерирует электрические импульсы. Все что нам нужно сделать для измерения скорости потока воды – это измерять время между этими импульсами или подсчитывать количество этих импульсов за 1 секунду. С помощью этих данных затем мы можем рассчитать скорость потока воды в литрах в минуту (L/Hr — liter per hour) и далее с помощью простой формулы найти объем воды, который прошел (протек) через трубу. Для подсчета количества импульсов от датчика расходы воды мы будем использовать плату Arduino Uno.
Цикл WHILE и бесконечный цикл в Ардуино
Если вы пока еще начинающий программист и хотите понять, что вообще такое цикл и зачем он нужен – посмотрите следующий раздел этой статьи с подробным описанием.
Оператор WHILE используется в C++ и Ардуино для организации повтора одних и тех команд произвольное количества раз. По сравнению с FOR цикл WHILE выглядит проще, он обычно используется там, где нам не нужен подсчет числа итераций в переменной, а просто требуется повторять код, пока что-то не изменится, не наступит какие-то событие.
Синтаксис WHILE
while(<условие или список условий>)
{
<программный блок, который будет повторяться>
}
В качестве условий может использоваться любая конструкция языка, возвращающая логическое значение. Условиями могут быть операции сравнения, функции, константы, переменные. Как и при любых других логических операциях в Ардуино любое значение, кроме нуля будет восприниматься как истина (true), ноль – ложь (false).
Пример:
// Бесконечный цикл while(true){ Serial.println("Waiting…"); } // Цикл, выполняющийся до изменения значения функции checkSignal() while( !checkSignal() ){ Serial.println("Waiting…"); }
Обратите внимание, что оператор while может использоваться без выделения блока фигурными скобками, в этом случае повторяться будет первая команда, встреченная после цикла. Крайне не рекомендуется использовать while без фигурных скобок, т.к
в этом случае можно очень легко допустить ошибку. Пример:
while(true) Serial.print("Waiting for interruption"); delay(1000);
В данном случае надпись будет выводиться в бесконечном цикле без пауз, потому что команда delay(1000) повторяться не будет. Вы можете потратить много времени, выявляя такие ошибки – гораздо проще использовать фигурную скобку.
Пример использования цикла while
Чаще всего while используется для ожидания какого-либо события. Например, готовности объекта Serial к работе.
Serial.begin(9600); while (!Serial) { ; // Для некоторых плат ардуино требуется ждать, пока не освободится последовательный порт }
Пример ожидания прихода символа от внешних устройств по UART:
while(Serial.available()){ int byteInput = Seria.read(); // Какие-то другие действия }
В данном случае мы будем считывать значения до тех пор, пока Serial.available() будет возвращать не нулевое значение. Как только все данные в буфере закончатся, цикл остановится.
Использование прерываний
Прерывания применяются, к примеру, в микроконтроллерных программах — когда вам нужно сделать так, чтобы те или иные процессы выполнялись автоматически. Кроме того, они могут помочь в решении проблем с синхронизацией. Что касается конкретных областей применения, то прерывания могут пригодиться, например, при считывании данных с поворотного регулятора или отслеживания пользовательских данных.
Написать программу, которая постоянно, не упуская ни единого импульса, следила бы за линией, ведущей от поворотного регулятора, и в то же время занималась чем-нибудь еще, не так-то просто. Похожая ситуация обстоит и с другими датчиками — к примеру, со звуковым сенсором, который следит за кликами мышью, или с инфракрасным сенсором (фотопрерывателем), который пытается уловить какое-либо движение (например, падение монеты). Тут-то на помощь и приходит прерывание — оно позволяет сделать так, чтобы микроконтроллер, с одной стороны, продолжал следить за какими-либо сигналами (движениями, звуками и т.д.), а с другой, мог выполнять какую-то другую работу.
Final Example
#define LED1 9 #define LED2 10 #define SW1 2 #define SW2 3 volatile byte flag0 = LOW; // declare IRQ flag volatile byte flag1 = LOW; // declare IRQ flag // change state of an output pin void toggle(byte pinNum) { byte pinState = !digitalRead(pinNum); digitalWrite(pinNum, pinState); } void setup() { pinMode(LED1, OUTPUT); pinMode(LED2, OUTPUT); digitalWrite(LED1, 0); // LED off digitalWrite(LED2, 0); // LED off pinMode(SW1, INPUT); pinMode(SW2, INPUT); attachInterrupt(0, ISR0, FALLING); attachInterrupt(1, ISR1, RISING); } void loop() { noInterrupts(); toggle(LED2); myDelay(2000); toggle(LED2); myDelay(2000); interrupts(); // no other interrupt work except from here to begin loop. } // end loop void myDelay(int x) { for(unsigned int i=0; i<=x; i++) { delayMicroseconds(1000); } } void ISR0() { toggle(LED1); } void ISR1(){ toggle(LED2); myDelay(5000); // 5 sec. toggle(LED2); flag1 = 0; // clear flag }
Your turn to test this sketch.
That completes this introduction to Arduino interrupts.
- Web Master
- Hobby Electronics
- Arduino IF Statement Code Examples
- Arduino Solid State Relay Motor Enable Control
- Arduino XOR Blinks LED
- Using Zero-Crossing Detectors with Arduino
- Hardware Interrupts Demo and Tutorial for Arduino
- In Depth Look at AC Power Control with Arduino
- Micro-controller AC Power Control Using Interrupts
- Light Activated SCR Based Optocouplers Circuit Examples
- Solid State AC Relays with Triacs
- YouTube
- Zero-Crossing Detectors Circuits and Applications
- Zero-Crossing Circuits for AC Power Control
- In Depth Look at AC Power Control with Arduino
- Micro-controller AC Power Control Using Interrupts
- YouTube Video for Arduino AC Power Control
- Arduino
- Arduino PWM to Analog Conversion
- Arduino Analog Digital Conversion Voltmeter
- Better Arduino Rotary Encoder Sensor
- Simple 3-Wire MAX6675 Thermocouple ADC Arduino Interface
- YouTube:
- 3-Wire MAX6675 Thermocouple ADC Arduino Interface
- Arduino
- Arduino PWM to Analog Conversion
- Arduino Analog Digital Conversion Voltmeter
- Better Arduino Rotary Encoder Sensor
- Simple 3-Wire MAX6675 Thermocouple ADC Arduino Interface
- YouTube:
- 3-Wire MAX6675 Thermocouple ADC Arduino Interface
- Arduino ADC Voltmeter YouTube video
- Arduino PWM to ADC YouTube video
- Connect Arduino to LCD Display with 74164 Shift Register
- Arduino with LCD Display and DS18B20 Temperature Sensor
- Below has differing code from the above. Works the same.
- Arduino with LCD Display and DHT11 Temperature-Humidity Sensor
- In Depth Look at AC Power Control with Arduino
- YouTube Video for Arduino AC Power Control
- Four part series:
- Experimenting with the PCA9555 32-Bit GPIO Expander with Arduino
- PCA9555 32-Bit GPIO Expander with Arduino and a 4X4 Keypad
- PCA9555 32-Bit GPIO Expander with Arduino Using Interrupts
- PCA9555 32-Bit GPIO Expander with Arduino and LCD Display
- YouTube Video for Series
- Arduino Projects Revisited Revised
- Programming ADS1115 4-Channel I2C ADC with Arduino
- Arduino uses ADS1115 with TMP37 to Measure Temperature
- Connect Arduino to I2C Liquid Crystal Display
- Arduino Reads Temperature Sensor Displays Temperature on LCD Display
- Arduino with MCP4725 12-bit Digital-to-Analog Converter Demo
- Videos
- Arduino with ADS1115 4-Channel 16-bit Analog-to-Digital Converter
- Arduino with MCP4725 12-Bit DAC
Справочник языка Ардуино
Операторы
Синтаксис
Битовые операторы
|
ДанныеТипы данных
sizeof() Библиотеки
|
ФункцииЦифровой ввод/вывод
Аналоговый ввод/вывод
Только для Due
Расширенный ввод/вывод
Время
Математические вычисления
Тригонометрия
Случайные числа
Биты и байты
Внешние прерывания
Прерывания
|
Исходный код программы (скетча)
Программа с использованием спящего режима
Arduino
#include <dht.h>
#include <LowPower.h>
#define dataPin 2
dht DHT;
void setup() {
Serial.begin(9600);
pinMode(LED_BUILTIN,OUTPUT);
digitalWrite(LED_BUILTIN,LOW);
}
void loop() {
Serial.println(«Get Data From DHT11»);
delay(1000);
digitalWrite(LED_BUILTIN,HIGH);
int readData = DHT.read11(dataPin); // DHT11
float t = DHT.temperature;
float h = DHT.humidity;
Serial.print(«Temperature = «);
Serial.print(t);
Serial.print(» C | «);
Serial.print(«Humidity = «);
Serial.print(h);
Serial.println(» % «);
delay(2000);
Serial.println(«Arduino:- I am going for a Nap»);
delay(200);
digitalWrite(LED_BUILTIN,LOW);
LowPower.idle(SLEEP_8S, ADC_OFF, TIMER2_OFF, TIMER1_OFF, TIMER0_OFF,
SPI_OFF, USART0_OFF, TWI_OFF);
Serial.println(«Arduino:- Hey I just Woke up»);
Serial.println(«»);
delay(2000);
}
1 |
#include <dht.h> dhtDHT; voidsetup(){ Serial.begin(9600); pinMode(LED_BUILTIN,OUTPUT); digitalWrite(LED_BUILTIN,LOW); } voidloop(){ Serial.println(«Get Data From DHT11»); delay(1000); digitalWrite(LED_BUILTIN,HIGH); intreadData=DHT.read11(dataPin);// DHT11 floatt=DHT.temperature; floath=DHT.humidity; Serial.print(«Temperature = «); Serial.print(t); Serial.print(» C | «); Serial.print(«Humidity = «); Serial.print(h); Serial.println(» % «); delay(2000); Serial.println(«Arduino:- I am going for a Nap»); delay(200); digitalWrite(LED_BUILTIN,LOW); LowPower.idle(SLEEP_8S,ADC_OFF,TIMER2_OFF,TIMER1_OFF,TIMER0_OFF, SPI_OFF,USART0_OFF,TWI_OFF); Serial.println(«Arduino:- Hey I just Woke up»); Serial.println(«»); delay(2000); } |
Программа без использования спящего режима
Arduino
#include <dht.h>
#define dataPin 2
dht DHT;
void setup() {
Serial.begin(9600);
pinMode(LED_BUILTIN,OUTPUT);
digitalWrite(LED_BUILTIN,LOW);
}
void loop() {
digitalWrite(LED_BUILTIN,HIGH);
int readData = DHT.read11(dataPin); // DHT11
float t = DHT.temperature;
float h = DHT.humidity;
Serial.print(«Temperature = «);
Serial.print(t);
Serial.print(» C | «);
Serial.print(«Humidity = «);
Serial.print(h);
Serial.println(» % «);
delay(2000);
}
1 |
#include <dht.h> dhtDHT; voidsetup(){ Serial.begin(9600); pinMode(LED_BUILTIN,OUTPUT); digitalWrite(LED_BUILTIN,LOW); } voidloop(){ digitalWrite(LED_BUILTIN,HIGH); intreadData=DHT.read11(dataPin);// DHT11 floatt=DHT.temperature; floath=DHT.humidity; Serial.print(«Temperature = «); Serial.print(t); Serial.print(» C | «); Serial.print(«Humidity = «); Serial.print(h); Serial.println(» % «); delay(2000); } |
Подводя итоги
Прерывания – это простой способ заставить вашу систему быстрее реагировать на чувствительные к времени задачи. Они также обладают дополнительным преимуществом – освобождением главного цикла , что позволяет сосредоточить в нем выполнение основной задачи системы (я считаю, что использование прерываний, как правило, позволяет сделать мой код немного более организованным: проще увидеть, для чего разработан основной кусок кода, и какие периодические события обрабатываются прерываниями). Пример, показанный здесь, – это самый базовый случай использования прерываний; вы можете использовать для чтения данных с I2C устройства, беспроводных передачи и приема данных, или даже для запуска или остановки двигателя.
Есть какие-нибудь крутые проекты с прерываниями? Оставляйте комментарии ниже!