Attachinterrupt()

Step 4: Example

Here’s one I whipped up. It isn’t perfect, there are certainly better ways, but it should be enough to get you started on using buttons.

The basic idea is to log the last time the button was pressed using the millis( ) function. (The millis( ) function tells you how many milliseconds have elapsed since the sketch started running). If the previous interrupt trigger happened less than 200 milliseconds before the current one, it’s likely that the button is bouncing, so no action is taken. Otherwise, everything happens normally. Any variables you intend to modify inside an Interrupt function should be declared as volatile so that the interrupt and main loop never disagree on the value.

Step 5: What’s Next?

That’s it! Every time you click the button the LED should change state. You now have a working interrupt. It’s up to you to imagine ways to incorporate interrupts into your project. As you can see, interrupts are essential when timing and computation power are important. In the next guide we will cover how to put the ATMega328p into ultra-low power sleep mode, relying on interrupts to wake us up again. Until then, subscribe to us on Instructables and check out our product page at www.doteverything.co. We’re building Dot, an Internet of Things device that we hope you’ll find cool and useful, and through this series of guides we’re going to show you exactly how to build your own version of the hardware!

Зачем отключать прерывания?

Могут быть временные критические фрагменты кода, которые вы не хотите прервать, например, прерыванием таймера.

Кроме того, если многобайтовые поля обновляются с помощью ISR, вам может потребоваться отключить прерывания, чтобы вы получили данные «атомарно». В противном случае один байт может быть обновлен ISR во время чтения другого.

Например:

Временное отключение прерываний гарантирует, что isrCounter (счетчик, установленный внутри ISR) не изменяется, пока мы получаем егозначение.

Предупреждение: , если вы не уверены, что прерывания уже включены или нет, вам необходимо сохранить текущее состояние и восстановить его позже. Например, код из функции millis () выполняет следующее:

Обратите внимание, что указанные строки сохраняют текущий SREG (регистр состояния), который включает флаг прерывания. После того, как мы получили значение таймера (длиной 4 байта), мы вернем регистр состояния, как это было

Volatile

Еще один момент, на который стоит указать: наш обработчик прерывания использует переменную для хранения состояния вывода. Проверьте определение : вместо типа , мы определили его, как тип . В чем же здесь дело? является ключевым словом языка C, которое применяется к переменным. Оно означает, что значение переменной находится не под полным контролем программы. То есть значение может измениться и измениться на что-то, что сама программа не может предсказать – в этом случае, пользовательский ввод.

Еще одна полезная вещь в ключевом слове заключается в защите от любой случайной оптимизации. Компиляторы, как выясняется, выполняют еще несколько дополнительных задач при преобразовании исходного кода программы в машинный исполняемый код. Одной из этих задач является удаление неиспользуемых в исходном коде переменных из машинного кода. Так как переменная не используется или не вызывается напрямую в функциях или , существует риск того, что компилятор может удалить её, как неиспользуемую переменную. Очевидно, что это неправильно – нам необходима эта переменная! Ключевое слово обладает побочным эффектом, сообщая компилятору, что эту переменную необходимо оставить в покое.

Удаление неиспользуемых переменных из кода – это функциональная особенность, а не баг компиляторов. Люди иногда оставляют в коде неиспользуемые переменные, которые занимают память. Это не такая большая проблема, если вы пишете программу на C для компьютера с гигабайтами оперативной памяти. Однако, на Arduino оперативная память ограничена, и вы не хотите тратить её впустую! Даже C компиляторы для компьютеров будут поступать точно так же, несмотря на массу доступной системной памяти. Зачем? По той же причине, по которой люди убирают за собой после пикника – это хорошая практика, не оставлять после себя мусор.

Using Interrupts

Interrupts are useful for making things happen automatically in microcontroller programs, and can help solve timing problems. Good tasks for using an interrupt may include reading a rotary encoder, or monitoring user input.

If you wanted to insure that a program always caught the pulses from a rotary encoder, so that it never misses a pulse, it would make it very tricky to write a program to do anything else, because the program would need to constantly poll the sensor lines for the encoder, in order to catch pulses when they occurred. Other sensors have a similar interface dynamic too, such as trying to read a sound sensor that is trying to catch a click, or an infrared slot sensor (photo-interrupter) trying to catch a coin drop. In all of these situations, using an interrupt can free the microcontroller to get some other work done while not missing the input.

Параметры

: номер прерывания ().

: номер вывода (только для Arduino Due, Zero, MKR1000).

: функция обработчика прерывания, которая вызывается, когда происходит прерывание; данная функция должна не принимать никаких параметров и ничего не возвращать.

: определяет, когда должно быть запущено прерывание. В качестве допустимых значений предопределены четыре константы:

  • , чтобы вызывать прерывание, когда на выводе низкий логический уровень;
  • , чтобы вызывать прерывание всякий раз, когда меняется состояние на выводе;
  • для запуска, когда состояние на выводе переходит от низкого к высокому;
  • , когда состояние на выводе переходит от высокого к низкому.

Платы Due, Zero и MKR1000 также поддерживают:

HIGH, чтобы вызывать прерывание всякий раз, когда на выводе высокий логический уровень.

Проблемы с контекстом прерываний

Для обмена данными между кодом обработчика прерывания и остальной частью вашей программы необходимо принять дополнительные меры.

Переменные обычно должны быть типа . говорит компилятору о необходимости избегать оптимизаций, которые предполагают, что переменная не может спонтанно измениться. Поскольку ваша функция может изменять переменные, пока ваша программа использует их, компилятор нуждается в этой подсказке. Но одного часто не хватает.

При доступе к общим переменным прерывания, как правило, должны быть отключены. Даже с , если обработчик прерывания изменит многобайтную переменную между последовательностью команд, эта переменная может быть прочитана неправильно. Если данные состоят из нескольких переменных, например, массив и счетчик, прерывания, как правило, должны быть отключены на протяжении всего кода, который получает доступ к данным.

Использование прерываний

Прерывания полезны для того, чтобы всё в программах микроконтроллеров происходило автоматически, и могут помочь решить проблемы с синхронизацией. Подходящие задачи для использования прерываний включают в себя считывание поворотного энкодера или мониторинг пользовательского ввода данных.

Если вы хотите обеспечить, чтобы программа всегда будет захватывать импульсы от поворотного энкодера, так чтобы она никогда не пропускала их, было бы сложно написать последовательно выполняемую программу, которая делала бы что-либо еще, потому что программе необходимо было бы постоянно выполнять опрос линий датчика, идущих от энкодера, чтобы отлавливать импульсы при их появлении. Другие датчики также имеют аналогичную динамику интерфейса, например, попытка прочитать звуковой датчик, который пытается отловить щелчок, или инфракрасный датчик (фотопрерыватель), который пытается отследить падение монеты. Во всех этих ситуациях использование прерывания может освободить микроконтроллер для выполнения какой-либо другой работы, не пропуская ввода данных.

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. relies on interrupts to count, so it will never increment inside an ISR. Since requires interrupts to work, it will not work if called inside an ISR. works initially but will start behaving erratically after 1-2 ms. 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.

Syntax

(recommended) (not recommended) (Not recommended. Additionally, this syntax only works on Arduino SAMD Boards, Uno WiFi Rev2, Due, and 101.)

Parameters

: the number of the interrupt. Allowed data types: .: the Arduino pin number.: 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 allow also:

Using Interrupts

Interrupts are useful for making things happen automatically in microcontroller programs and can help solve timing problems. Good tasks for using an interrupt may include reading a rotary encoder, or monitoring user input.

If you wanted to ensure that a program always caught the pulses from a rotary encoder, so that it never misses a pulse, it would make it very tricky to write a program to do anything else, because the program would need to constantly poll the sensor lines for the encoder, in order to catch pulses when they occurred. Other sensors have a similar interface dynamic too, such as trying to read a sound sensor that is trying to catch a click, or an infrared slot sensor (photo-interrupter) trying to catch a coin drop. In all of these situations, using an interrupt can free the microcontroller to get some other work done while not missing the input.

volatile

volatilevolatile

byte A = ;
byte B;

void loop() {
  A++;
  B = A + 1;
}
  1. Загрузить из памяти значение A в регистр Р1
  2. Загрузить в регистр Р2 константу 1
  3. Сложить значение Р2 с Р1 (результат в Р2)
  4. Сохранить значение регистра Р2 в памяти по адресу A
  5. Сложить содержимое регистра Р1 с константой 2
  6. Сохранить значение регистра Р1 в памяти по адресу B

loopvolatilevolatilevolatilevolatile

#define interruptPin 2
volatile byte f = ;

void setup() {
  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), buttonPressed, FALLING);
}

void loop() {
  while (f == ) {
    
  }
  
}

void buttonPressed() {
  f = 1;
}

loopvolatilevolatileinterruptsnoInterruptsатомарно исполняемый блок кода

Описание

Цифровые выводы с прерываниями

Первый параметр – это номер прерывания. Как правило, вы должны использовать для перевода реального цифрового вывода в конкретный номер прерывания. Например, если вы подключаетесь к выводу 3, используйте в качестве первого параметра .

Плата Цифровые выводы, используемые для прерываний
Uno, Nano, Mini, other 328-based 2, 3
Uno WiFi Rev.2 все цифровые выводы
Mega, Mega2560, MegaADK 2, 3, 18, 19, 20, 21
Micro, Leonardo, other 32u4-based 0, 1, 2, 3, 7
Zero все цифровые выводы, кроме 4
MKR Family boards 0, 1, 4, 5, 6, 7, 8, 9, A1, A2
Due все цифровые выводы
101 все цифровые выводы (Выводы 2, 5, 7, 8, 10, 11, 12, 13 работают только с CHANGE)

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. relies on interrupts to count, so it will never increment inside an ISR. Since requires interrupts to work, it will not work if called inside an ISR. works initially, but will start behaving erratically after 1-2 ms. 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.

Syntax

(recommended) (not recommended) (Not recommended. Additionally, this syntax only works on Arduino SAMD Boards, Uno WiFi Rev2, Due, and 101.)

Parameters

: the number of the interrupt (): the pin number: 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:

Дребезг контактов

Очевидно, что при подаче на вход внешнего прерывания сигнала, искаженного дребезгом контактов, обработчик прерывания будет выполнен несколько раз. В некоторых случаях дребезг не является проблемой, например, если мы ожидаем нажатия на кнопку для выполнения в программе каких-либо действий: достаточно отключить отслеживание данного прерывания при входе в обработчик, а после выполнения нашего кода снова включить (подразумевается, что время выполнения наших действий превышает длительность дребезга контактов). Таким образом мы пропустим дребезг и обработчик будет выполнен только один раз. Другое дело, когда прерывания используются для регистрации событий. Хороший пример — механический энкодер вращения. В этом случае мы не можем отключить отслеживание прерываний, поскольку рискуем пропустить событие (импульс от энкодера). Принять решение о том, ложный это импульс или нет, обработчик не может, да и просто это не та задача, которой он должен заниматься. Поэтому единственным рациональным решением является применение аппаратных подавителей дребезга. Данной теме посвящены несколько моих публикаций, ссылки на них ниже:

Устранение дребезга контактов. Часть 1 — триггер ШмиттаУстранение дребезга контактов. Часть 2 — микросхема MC14490Устранение дребезга контактов. Часть 3 — микросхемы MAX6816/MAX6817/MAX6818

Introduction: Arduino Timer Interrupts

By amandaghassaeiuh-man-duh-guss-eye-dot-comFollow

More by the author:

About: I’m a Research Engineer at Adobe. Previously, I was a grad student at the Center for Bits and Atoms at MIT Media Lab. Before that, I worked at Instructables, writing code for the website and iOS apps and m…

More About amandaghassaei »

Timer interrupts allow you to perform a task at very specifically timed intervals regardless of what else is going on in your code. In this instructable I’ll explain how to setup and execute an interrupt in Clear Timer on Compare Match or CTC Mode. Jump straight to step 2 if you are looking for sample code.

Normally when you write an Arduino sketch the Arduino performs all the commands encapsulated in the loop() {} function in the order that they are written, however, it’s difficult to time events in the loop(). Some commands take longer than others to execute, some depend on conditional statements (if, while…) and some Arduino library functions (like digitalWrite or analogRead) are made up of many commands. Arduino timer interrupts allow you to momentarily pause the normal sequence of events taking place in the loop() function at precisely timed intervals, while you execute a separate set of commands. Once these commands are done the Arduino picks up again where it was in the loop().

Interrupts are useful for:

Measuring an incoming signal at equally spaced intervals (constant sampling frequency)
Calculating the time between two events
Sending out a signal of a specific frequency
Periodically checking for incoming serial data
much more…

There are a few ways to do interrupts, for now I’ll focus on the type that I find the most useful/flexible, called Clear Timer on Compare Match or CTC Mode. Additionally, in this instructable I’ll be writing specifically about the timers to the Arduino Uno (and any other Arduino with ATMEL 328/168… Lilypad, Duemilanove, Diecimila, Nano…). The main ideas presented here apply to the Mega and older boards as well, but the setup is a little different and the table below is specific to ATMEL 328/168.

Step 3: Example 1: Bike Speedometer

In this example I made an arduino powered bike speedometer.  It works by attaching a magnet to the wheel and measuring the amount of time it takes to pass by a magnetic switch mounted on the frame- the time for one complete rotation of the wheel.
I set timer 1 to interrupt every ms (frequency of 1kHz) to measure the magnetic switch.  If the magnet is passing by the switch, the signal from the switch is high and the variable «time» gets set to zero.  If the magnet is not near the switch «time» gets incremented by 1.  This way «time» is actually just a measurement of the amount of time in milliseconds that has passed since the magnet last passed by the magnetic switch.  This info is used later in the code to calculate rpm and mph of the bike.
Here’s the bit of code that sets up timer1 for 1kHz interrupts
cli();//stop interrupts
//set timer1 interrupt at 1kHz
TCCR1A = 0;// set entire TCCR1A register to 0
TCCR1B = 0;// same for TCCR1B
TCNT1  = 0;//initialize counter value to 0
// set timer count for 1khz increments
OCR1A = 1999;// = (16*10^6) / (1000*8) — 1
//had to use 16 bit timer1 for this bc 1999>255, but could switch to timers 0 or 2 with larger prescaler
// turn on CTC mode
TCCR1B |= (1 << WGM12);
// Set CS11 bit for 8 prescaler
TCCR1B |= (1 << CS11);  
// enable timer compare interrupt
TIMSK1 |= (1 << OCIE1A);
sei();//allow interrupts
Here’s the complete code if you want to take a look:

//bike speedometer
//by Amanda Ghassaei 2012
//https://www.instructables.com/id/Arduino-Timer-Interrupts/
//https://www.instructables.com/id/Arduino-Timer-Interrupts/

/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
*/

//sample calculations
//tire radius ~ 13.5 inches
//circumference = pi*2*r =~85 inches
//max speed of 35mph =~ 616inches/second
//max rps =~7.25

#define reed A0//pin connected to read switch

//storage variables
float radius = 13.5;// tire radius (in inches)- CHANGE THIS FOR YOUR OWN BIKE

int reedVal;
long time = 0;// time between one full rotation (in ms)
float mph = 0.00;
float circumference;
boolean backlight;

int maxReedCounter = 100;//min time (in ms) of one rotation (for debouncing)
int reedCounter;


void setup(){
  
  reedCounter = maxReedCounter;
  circumference = 2*3.14*radius;
  pinMode(1,OUTPUT);//tx
  pinMode(2,OUTPUT);//backlight switch
  pinMode(reed,INPUT);//redd switch
  
  checkBacklight();
  
  Serial.write(12);//clear
  
  // TIMER SETUP- the timer interrupt allows preceise timed measurements of the reed switch
  //for mor info about configuration of arduino timers see http://arduino.cc/playground/Code/Timer1
  cli();//stop interrupts

  //set timer1 interrupt at 1kHz
  TCCR1A = 0;// set entire TCCR1A register to 0
  TCCR1B = 0;// same for TCCR1B
  TCNT1  = 0;//initialize counter value to 0;
  // set timer count for 1khz increments
  OCR1A = 1999;// = (16*10^6) / (1000*8) - 1
  // turn on CTC mode
  TCCR1B |= (1 << WGM12);
  // Set CS11 bit for 8 prescaler
  TCCR1B |= (1 << CS11);   
  // enable timer compare interrupt
  TIMSK1 |= (1 << OCIE1A);
  
  sei();//allow interrupts
  //END TIMER SETUP
  
  Serial.begin(9600);
}

void checkBacklight(){
  backlight = digitalRead(2);
  if (backlight){
    Serial.write(17);//turn backlight on
  }
  else{
    Serial.write(18);//turn backlight off
  }
}

ISR(TIMER1_COMPA_vect) {//Interrupt at freq of 1kHz to measure reed switch
  reedVal = digitalRead(reed);//get val of A0
  if (reedVal){//if reed switch is closed
    if (reedCounter == 0){//min time between pulses has passed
      mph = (56.8*float(circumference))/float(time);//calculate miles per hour
      time = 0;//reset timer
      reedCounter = maxReedCounter;//reset reedCounter
    }
    else{
      if (reedCounter > 0){//don't let reedCounter go negative
        reedCounter -= 1;//decrement reedCounter
      }
    }
  }
  else{//if reed switch is open
    if (reedCounter > 0){//don't let reedCounter go negative
      reedCounter -= 1;//decrement reedCounter
    }
  }
  if (time > 2000){
    mph = 0;//if no new pulses from reed switch- tire is still, set mph to 0
  }
  else{
    time += 1;//increment timer
  } 
}

void displayMPH(){
  Serial.write(12);//clear
  Serial.write("Speed =");
  Serial.write(13);//start a new line
  Serial.print(mph);
  Serial.write(" MPH ");
  //Serial.write("0.00 MPH ");
}

void loop(){
  //print mph once a second
  displayMPH();
  delay(1000);
  checkBacklight();
}


Общие рекомендации по написанию обработчиков прерываний

В заключение хочу привести несколько рекомендаций по написанию обработчиков прерываний.

  • Во-первых, делайте обработчики предельно короткими. Ведь они прерывают выполнение основной программы, а также блокируют обработку других прерываний. По возможности обработчик должен фиксировать только факт возникновения события, изменяя значение переменной. А сама реакция на событие должна выполняться в основной программе при анализе этой переменной.
  • Как уже было сказано, при входе в обработчик устанавливается глобальный запрет на обработку других прерываний. А это в свою очередь влияет на работу функций, использующих прерывания. Будьте с ними осторожнее. Если не уверены в безопасности их вызова, то лучше откажитесь от их использования в обработчике.
  • Возьмите за правило объявлять разделяемые между основной программой и обработчиком переменные как volatile. И не забывайте, что этого квалификатора недостаточно в случае многобайтных переменных — используйте при работе с ними атомарно исполняемые блоки или interrupts/noInterrupts

следующей части

Сначала … когда вы используете «изменчивые» переменные?

Переменная должна быть отмечена только изменчивой, если она используется как внутри ISR, так и снаружи.

  • Переменные только , используемые вне ISR, должны быть не неустойчивыми.
  • Переменные только , используемые внутри ISR, должны не быть нестабильными.
  • Переменные, используемые как внутри, так и снаружи ISR , должны быть неустойчивыми.

например.

Маркировка переменной как volatile говорит компилятору не «кэшировать» содержимое переменных в регистр процессора, а всегда читать его из памяти, когда это необходимо. Это может замедлить обработку, поэтому вы не просто делаете каждую переменную изменчивой, когда это не нужно.

Подводя итоги

Прерывания – это простой способ заставить вашу систему быстрее реагировать на чувствительные к времени задачи. Они также обладают дополнительным преимуществом – освобождением главного цикла , что позволяет сосредоточить в нем выполнение основной задачи системы (я считаю, что использование прерываний, как правило, позволяет сделать мой код немного более организованным: проще увидеть, для чего разработан основной кусок кода, и какие периодические события обрабатываются прерываниями). Пример, показанный здесь, – это самый базовый случай использования прерываний; вы можете использовать для чтения данных с I2C устройства, беспроводных передачи и приема данных, или даже для запуска или остановки двигателя.

Есть какие-нибудь крутые проекты с прерываниями? Оставляйте комментарии ниже!

Оцените статью:
Оставить комментарий