Як працюють переривання на платах Arduino Uno та подібних?


11

Поясніть, будь ласка, як працюють переривання на Arduino Uno та пов'язаних платах за допомогою процесора ATmega328P. Дошки, такі як:

  • Уно
  • Міні
  • Нано
  • Pro Mini
  • Лілії

Зокрема, будь ласка, обговоріть:

  • Для чого використовувати переривання
  • Як написати звичайну службу переривання (ISR)
  • Проблеми з термінами
  • Критичні розділи
  • Атомний доступ до даних

Примітка. Це запитання .

Відповіді:


25

TL; DR:

При написанні звичайної служби переривання (ISR):

  • Тримайте це коротко
  • Не використовуйте delay ()
  • Не робіть серійних відбитків
  • Make змінні спільно з основним кодом летючого
  • Змінні, що діляться з основним кодом, можливо, повинні бути захищені "критичними розділами" (див. Нижче)
  • Не намагайтеся вимикати або вмикати переривання

Що таке переривання?

Більшість процесорів мають переривання. Перерви дозволяють вам реагувати на "зовнішні" події, роблячи щось інше. Наприклад, якщо ви готуєте вечерю, можете поставити картоплю, щоб вона варилася протягом 20 хвилин. Замість того, щоб дивитися на годинник протягом 20 хвилин, ви можете встановити таймер, а потім перейти дивитися телевізор. Коли таймер дзвонить, ви "перебиваєте" перегляд телевізора, щоб щось зробити з картоплею.


Приклад переривань

const byte LED = 13;
const byte SWITCH = 2;

// Interrupt Service Routine (ISR)
void switchPressed ()
{
  if (digitalRead (SWITCH) == HIGH)
    digitalWrite (LED, HIGH);
  else
    digitalWrite (LED, LOW);
}  // end of switchPressed

void setup ()
{
  pinMode (LED, OUTPUT);  // so we can update the LED
  pinMode (SWITCH, INPUT_PULLUP);
  attachInterrupt (digitalPinToInterrupt (SWITCH), switchPressed, CHANGE);  // attach interrupt handler
}  // end of setup

void loop ()
{
  // loop doing nothing
}

Цей приклад показує, що, хоча основний контур нічого не робить, ви можете увімкнути або вимкнути світлодіод на штифті 13, якщо натиснутий перемикач на штифт D2.

Щоб перевірити це, просто підключіть провід (або комутатор) між D2 та Ground. Внутрішня підтяжка (увімкнена в налаштуваннях) нормально змушує штифт ВИСОКИЙ. При заземленні він стає НИЗКОМ. Зміна штифта виявляється перервою CHANGE, яка викликає виклик звичайної служби переривання (ISR).

У більш складному прикладі основний цикл може робити щось корисне, як, наприклад, проводити показник температури, і дозволити оброблювачу переривання виявити натиснуту кнопку.


Перетворення контактних номерів на переривання чисел

Для спрощення перетворення номерів переривань векторів у штифтові номери ви можете зателефонувати у функцію digitalPinToInterrupt(), передаючи PIN-код. Він повертає відповідний номер переривання, або NOT_AN_INTERRUPT(-1).

Наприклад, на Uno штифт D2 на дошці переривається 0 (INT0_vect з таблиці нижче).

Таким чином, ці два рядки мають однаковий ефект:

  attachInterrupt (0, switchPressed, CHANGE);    // that is, for pin D2
  attachInterrupt (digitalPinToInterrupt (2), switchPressed, CHANGE);

Однак другий простіший для читання і переносний для різних типів Arduino.


Доступні переривання

Нижче наводиться перелік переривань у пріоритетному порядку для Atmega328:

 1  Reset
 2  External Interrupt Request 0  (pin D2)          (INT0_vect)
 3  External Interrupt Request 1  (pin D3)          (INT1_vect)
 4  Pin Change Interrupt Request 0 (pins D8 to D13) (PCINT0_vect)
 5  Pin Change Interrupt Request 1 (pins A0 to A5)  (PCINT1_vect)
 6  Pin Change Interrupt Request 2 (pins D0 to D7)  (PCINT2_vect)
 7  Watchdog Time-out Interrupt                     (WDT_vect)
 8  Timer/Counter2 Compare Match A                  (TIMER2_COMPA_vect)
 9  Timer/Counter2 Compare Match B                  (TIMER2_COMPB_vect)
10  Timer/Counter2 Overflow                         (TIMER2_OVF_vect)
11  Timer/Counter1 Capture Event                    (TIMER1_CAPT_vect)
12  Timer/Counter1 Compare Match A                  (TIMER1_COMPA_vect)
13  Timer/Counter1 Compare Match B                  (TIMER1_COMPB_vect)
14  Timer/Counter1 Overflow                         (TIMER1_OVF_vect)
15  Timer/Counter0 Compare Match A                  (TIMER0_COMPA_vect)
16  Timer/Counter0 Compare Match B                  (TIMER0_COMPB_vect)
17  Timer/Counter0 Overflow                         (TIMER0_OVF_vect)
18  SPI Serial Transfer Complete                    (SPI_STC_vect)
19  USART Rx Complete                               (USART_RX_vect)
20  USART, Data Register Empty                      (USART_UDRE_vect)
21  USART, Tx Complete                              (USART_TX_vect)
22  ADC Conversion Complete                         (ADC_vect)
23  EEPROM Ready                                    (EE_READY_vect)
24  Analog Comparator                               (ANALOG_COMP_vect)
25  2-wire Serial Interface  (I2C)                  (TWI_vect)
26  Store Program Memory Ready                      (SPM_READY_vect)

Внутрішні імена (які можна використовувати для встановлення зворотних дзвінків ISR) містяться в дужках.

Попередження: Якщо ви неправильно написали ім'я вектора переривання, навіть просто помилившись з великої літери (це легко зробити), процедура переривання не буде викликана , і ви не отримаєте помилку компілятора.


Причини використовувати переривання

Основні причини, по яких ви можете використовувати переривання:

  • Для виявлення змін штифтів (наприклад, обертові датчики, натискання кнопок)
  • Таймер сторожової собаки (наприклад, якщо нічого не відбувається через 8 секунд, перервіть мене)
  • Переривання таймера - використовується для порівняння / переповнення таймерів
  • Передача даних SPI
  • Передача даних I2C
  • Передача даних USART
  • Перетворення АЦП (аналогові до цифрових)
  • EEPROM готовий до використання
  • Флеш-пам’ять готова

"Передача даних" може використовуватися для того, щоб програма могла робити щось інше під час надсилання або отримання даних на послідовний порт, порт SPI або порт I2C.

Прокиньте процесор

Зовнішні переривання, перемикання контактів і переривання таймеру сторожового собаки також можуть бути використані для пробудження процесора. Це може бути дуже зручно, так як у сплячому режимі процесор може бути налаштований на використання набагато менше енергії (наприклад, близько 10 мікроампер). Переривання підйому, падіння або низького рівня може використовуватися для пробудження гаджета (наприклад, якщо ви натиснете на нього кнопку), або переривання «таймера сторожового часу» може періодично його будити (наприклад, перевірити час або температура).

Переривання змінної штифти може бути використане для пробудження процесора, якщо натиснути клавішу на клавіатурі чи подібне.

Процесор також може бути розбуджений тимчасовим перериванням (наприклад, таймером, що досягає певного значення або переповненням) та деякими іншими подіями, такими як вхідне повідомлення I2C.


Увімкнення / відключення переривань

Переривання "скидання" неможливо відключити. Однак інші переривання можна тимчасово відключити, очистивши глобальний прапор переривання.

Увімкнути переривання

Ви можете ввімкнути переривання за допомогою виклику функції "переривання" або "sei" таким чином:

interrupts ();  // or ...
sei ();         // set interrupts flag

Вимкнути переривання

Якщо вам потрібно відключити переривання, ви можете "очистити" глобальний прапор переривання таким чином:

noInterrupts ();  // or ...
cli ();           // clear interrupts flag

Будь-який метод має однаковий ефект, використовуючи interrupts/ noInterruptsтрохи легше запам’ятати, який шлях вони мають.

За замовчуванням в Arduino вмикається переривання. Не відключайте їх протягом тривалого періоду, або такі речі, як таймери, не працюватимуть належним чином.

Чому відключити переривання?

Можливо, є критично важливі за часом фрагменти коду, які ви не хочете переривати, наприклад, тимчасовим перериванням.

Крім того, якщо багатобайтові поля оновлюються ISR, вам може знадобитися відключити переривання, щоб отримати дані "атомно". В іншому випадку один байт може бути оновлений ISR, поки ви читаєте інший.

Наприклад:

noInterrupts ();
long myCounter = isrCounter;  // get value set by ISR
interrupts ();

Тимчасове відключення переривань гарантує, що isrCounter (лічильник, встановлений всередині ISR), не змінюється, поки ми отримуємо його значення.

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

unsigned long millis()
{
  unsigned long m;
  uint8_t oldSREG = SREG;    // <--------- save status register

  // disable interrupts while we read timer0_millis or we might get an
  // inconsistent value (e.g. in the middle of a write to timer0_millis)
  cli();
  m = timer0_millis;
  SREG = oldSREG;            // <---------- restore status register including interrupt flag

  return m;
}

Зверніть увагу, що зазначені рядки зберігають поточний SREG (реєстр статусу), який включає прапор переривання. Після того, як ми отримали значення таймера (яке становить 4 байти), ми повертаємо реєстр статусу назад, як це було.


Поради

Назви функцій

Функції cli/ seiта регістр SREG є специфічними для процесорів AVR. Якщо ви використовуєте інші процесори, такі як ARM, функції можуть дещо відрізнятися.

Відключення глобально проти відключення одного переривання

Якщо ви користуєтесь, cli()ви відключаєте всі переривання (включаючи таймери, серійні переривання тощо).

Однак якщо ви просто хочете відключити певний переривання, тоді вам слід очистити прапор включення переривання для цього конкретного джерела переривання. Наприклад, для зовнішніх переривань дзвоніть detachInterrupt().


Що таке пріоритет перерви?

Оскільки є 25 переривань (крім скидання), можливо, що більше однієї події переривання може статися одночасно, або принаймні, статися до того, як попередня буде оброблена. Також подія переривання може статися під час відключення переривань.

Порядок пріоритетності - це послідовність, в якій процесор перевіряє наявність подій переривання. Чим вище список, тим вище пріоритет. Так, наприклад, зовнішній запит на переривання 0 (контакт D2) буде обслуговуватися перед зовнішнім запитом на переривання 1 (контакт D3).


Чи можуть переривання статися під час відключення перерв?

Переривання подій (тобто помічання події) може статися в будь-який час, і більшість запам'ятовується, встановивши прапор "події переривання" всередині процесора. Якщо переривання вимкнено, то це переривання буде оброблено, коли вони знову ввімкнуті, у порядку пріоритетності.


Як ви використовуєте переривання?

  • Ви пишете ISR (режим переривання обслуговування). Це називається, коли відбувається переривання.
  • Ви повідомляєте процесору, коли хочете, щоб перерва спрацювала.

Написання ISR

Процедури обслуговування переривань - це функції без аргументів. Деякі бібліотеки Arduino призначені для виклику ваших власних функцій, тому ви просто надаєте звичайну функцію (як у наведених вище прикладах), наприклад.

// Interrupt Service Routine (ISR)
void switchPressed ()
{
 flag = true;
}  // end of switchPressed

Однак якщо бібліотека ще не надала "гачок" для ISR, ви можете зробити свій власний, наприклад:

volatile char buf [100];
volatile byte pos;

// SPI interrupt routine
ISR (SPI_STC_vect)
{
byte c = SPDR;  // grab byte from SPI Data Register

  // add to buffer if room
  if (pos < sizeof buf)
    {
    buf [pos++] = c;
    }  // end of room available
}  // end of interrupt routine SPI_STC_vect

У цьому випадку ви використовуєте макрос "ISR" і вводите ім'я відповідного вектора переривання (з таблиці раніше). У цьому випадку ISR обробляє завершення передачі SPI. (Зауважте, деякий старий код використовує SIGNAL замість ISR, однак SIGNAL застарілий).

Підключення ISR до переривання

Для перерв, які вже обробляються бібліотеками, ви просто використовуєте документований інтерфейс. Наприклад:

void receiveEvent (int howMany)
 {
  while (Wire.available () > 0)
    {
    char c = Wire.receive ();
    // do something with the incoming byte
    }
}  // end of receiveEvent

void setup ()
  {
  Wire.onReceive(receiveEvent);
  }

У цьому випадку бібліотека I2C призначена для внутрішньої обробки вхідних байтів I2C, а потім виклику наданої функції в кінці потоку вхідних даних. У цьому випадку primaEvent не є строго ISR (він має аргумент), але він викликається вбудованим ISR.

Інший приклад - переривання "зовнішнього штифта".

// Interrupt Service Routine (ISR)
void switchPressed ()
{
  // handle pin change here
}  // end of switchPressed

void setup ()
{
  attachInterrupt (digitalPinToInterrupt (2), switchPressed, CHANGE);  // attach interrupt handler for D2
}  // end of setup

У цьому випадку функція attachInterrupt додає функцію switchPress до внутрішньої таблиці та додатково налаштовує відповідні прапорці переривання в процесорі.

Налаштування процесора для обробки переривання

Наступний крок, коли у вас буде ISR, - сказати процесору, що ви хочете, щоб ця конкретна умова викликала переривання.

Наприклад, для зовнішнього переривання 0 (переривання D2) ви можете зробити щось подібне:

EICRA &= ~3;  // clear existing flags
EICRA |= 2;   // set wanted flags (falling level interrupt)
EIMSK |= 1;   // enable it

Більш зрозумілим було б використання визначених імен, як це:

EICRA &= ~(bit(ISC00) | bit (ISC01));  // clear existing flags
EICRA |= bit (ISC01);    // set wanted flags (falling level interrupt)
EIMSK |= bit (INT0);     // enable it

EICRA (зовнішній реєстр контролю переривань A) буде встановлений відповідно до цієї таблиці з таблиці даних Atmega328. Це визначає точний тип переривання, який потрібно:

  • 0: Низький рівень INT0 генерує запит на переривання (LOW переривання).
  • 1: Будь-яке логічне зміна INT0 породжує запит на переривання (CHANGE prerupt).
  • 2: Падіння краю INT0 генерує запит переривання (FALLING переривання).
  • 3: Піднімається край INT0 створює запит на переривання (RISING переривання).

EIMSK (Зовнішній реєстр масок переривання) фактично дозволяє переривання.

На щастя, вам не потрібно запам'ятовувати ці цифри, тому що attachInterrupt робить це для вас. Однак це насправді відбувається, і для інших перебоїв вам, можливо, доведеться "встановити" вручну прапори переривань.


ІРС низького рівня порівняно з бібліотечними ISR

Для спрощення вашого життя деякі поширені обробники переривань фактично знаходяться всередині бібліотечного коду (наприклад, INT0_vect та INT1_vect), а потім надається більш зручний інтерфейс (наприклад, attachInterrupt). Насправді attachInterrupt - це зберегти адресу потрібного обробника переривання в змінну, а потім зателефонувати за допомогою INT0_vect / INT1_vect. Він також встановлює відповідні прапори реєстрації для виклику обробника, коли це вимагається.


Чи можна перервати ISR?

Якщо коротко, ні, хіба що ви хочете, щоб вони були.

Коли вводиться ISR, переривання вимикаються . Звичайно, вони повинні бути включені в першу чергу, інакше ISR не буде введено. Однак, щоб сам ISR не був перерваний, процесор вимикає переривання.

Коли ISR закінчується, переривання вмикаються знову . Компілятор також генерує код всередині ISR, щоб зберегти регістри та прапори стану, так що все, що ви робили, коли відбулося переривання, не впливатиме.

Однак ви можете увімкнути переривання всередині ISR, якщо це абсолютно необхідно, наприклад.

// Interrupt Service Routine (ISR)
void switchPressed ()
{
  // handle pin change here
  interrupts ();  // allow more interrupts

}  // end of switchPressed

Зазвичай для цього вам знадобиться досить вагомий привід, оскільки чергове переривання зараз може призвести до рекурсивного виклику pinChange з цілком можливими небажаними результатами.


Скільки часу потрібно для виконання ISR?

Відповідно до даних, мінімальна кількість часу для обслуговування переривання - це 4 тактових циклу (для натискання поточного лічильника програми на стек) з подальшим кодом, який виконується в місці переривання вектора. Це, як правило, містить стрибок туди, де насправді є програма переривання, а це ще 3 цикли. Експертиза коду, виданого компілятором, показує, що ISR, зроблений з декларацією "ISR", може зайняти близько 2,625 мкс, плюс все, що робить сам код. Точна сума залежить від того, скільки реєстрів потрібно зберегти та відновити. Мінімальна кількість складе 1,1875 мкс.

Зовнішні переривання (де ви використовуєте attachInterrupt) роблять трохи більше і займають близько 5,125 мкс в цілому (працює з тактовою частотою 16 МГц).


За скільки часу процесор починає вводити ISR?

Це дещо змінюється. Наведені вище цифри є ідеальними, коли переривання негайно обробляється. Кілька факторів можуть затримати, що:

  • Якщо процесор спить, передбачено час "пробудження", який може бути досить декількох мілісекунд, а годинник повертається до швидкості. Цей час залежатиме від налаштувань запобіжника та від глибокого сну.

  • Якщо функція служби переривання вже виконується, то подальше переривання не може бути введено, поки воно не закінчиться, або не включить переривання. Ось чому ви повинні тримати кожну процедуру обслуговування переривань короткими, оскільки кожна мікросекунда, яку ви проводите в одній, ви потенційно затримуєте виконання іншої.

  • Деякий код вимикання переривається. Наприклад, виклик millis () ненадовго вимикає переривання. Тому час для обслуговування переривання буде подовжено на тривалість часу, коли переривання було вимкнено.

  • Перерви можна обслуговувати лише наприкінці інструкції, тому якщо конкретна інструкція займає три тактових тактики, і щойно розпочалася, то переривання затримається щонайменше на пару циклів годин.

  • Подія, яка знову вмикає переривання (наприклад, повернення з програми переривання служби), гарантовано виконає щонайменше ще одну інструкцію. Тож навіть якщо ISR закінчиться, а переривання очікує, перед його обслуговуванням доведеться почекати ще однієї інструкції.

  • Оскільки переривання мають пріоритет, переривання вищого пріоритету може бути обслужено до переривання, яке вас цікавить.


Міркування щодо продуктивності

Перерви можуть підвищити продуктивність у багатьох ситуаціях, тому що ви можете продовжувати "основну роботу" вашої програми без постійного тестування, щоб побачити, чи були натиснуті комутатори. Сказавши це, накладні витрати на переривання, як обговорювалося вище, насправді були б більше, ніж робити «щільну петлю», опитуючи один вхідний порт. Ви навряд чи зможете відповісти на подію, скажімо, у мікросекунді. У такому випадку ви можете відключити переривання (наприклад, таймери) і просто петлю шукати шпильку для зміни.


Як чергуються переривання?

Існує два види переривань:

  • Деякі встановлюють прапор, і вони обробляються в порядку пріоритетності, навіть якщо подія, яка спричинила їх, припинилася. Наприклад, перерив підйому, падіння або зміна рівня на штифті D2.

  • Інші тестуються лише у тому випадку, якщо вони відбуваються "прямо зараз". Наприклад, переривання низького рівня на штифті D2.

Ті, що встановлюють прапор, можна вважати чергою, оскільки прапор переривання залишається встановленим до тих пір, поки не буде введено процедуру переривання, і тоді процесор очистить прапор. Звичайно, оскільки є лише один прапор, якщо однакова умова переривання повториться до того, як буде оброблений перший, він не буде обслуговуватися двічі.

Потрібно пам’ятати, що ці прапори можна встановити перед тим, як приєднати обробник переривання. Наприклад, можливо, щоб переривання рівня зростаючого або падіння на штифті D2 було "позначено", а потім, як тільки ви зробите вкладення. Перервіть перерва негайно, навіть якщо подія сталася годину тому. Щоб цього уникнути, ви можете вручну очистити прапор. Наприклад:

EIFR = bit (INTF0);  // clear flag for interrupt 0
EIFR = bit (INTF1);  // clear flag for interrupt 1

Однак переривання "низького рівня" постійно перевіряються, тому, якщо ви не будете обережні, вони продовжують стріляти, навіть після виклику переривання. Тобто ISR вийде, і тоді переривання негайно знову вистрілить. Щоб цього уникнути, вам слід зробити detachInterrupt негайно після того, як ви дізнаєтесь, що перерва запущена.


Підказки для написання реєстрів

Коротше кажучи, тримайте їх короткими! Поки ISR виконує інші переривання, не можна обробити. Таким чином, ви можете легко пропустити натискання кнопок або вхідні послідовні комунікації, якщо спробуєте зробити занадто багато. Зокрема, не слід намагатися робити налагодження "відбитків" всередині ISR. Час, який потрібно зробити на це, може викликати більше проблем, ніж вони вирішують.

Доцільна річ - це встановити однобайтовий прапор, а потім протестувати цей прапор у головній функції циклу. Або збережіть вхідний байт із послідовного порту у буфер. Вбудований таймер переривання відстежує минулий час, стріляючи щоразу, коли внутрішній таймер переповнюється, і, таким чином, ви можете відпрацювати минулий час, знаючи, скільки разів таймер переповнюється.

Пам'ятайте, всередині ISR переривання вимкнено. Таким чином, сподівання, що час, повернутий викликами функції millis (), зміниться, призведе до розчарування. Дійсно отримувати час таким чином, просто слід пам’ятати, що таймер не збільшується. І якщо ви витратите занадто довго в ISR, то таймер може пропустити подію переповнення, що призведе до того, що час, повернутий milis (), стане неправильним.

Тест показує, що на процесорі Atmega328 потужністю 16 МГц виклик мікро (() займає 3,55625 мкс. Виклик millis () займає 1,9375 мкс. Запис (збереження) поточного значення таймера є розумною справою в ISR. Пошук минулих мілісекунд швидше, ніж минулі мікросекунди (кількість мілісекунд просто витягується зі змінної). Однак підрахунок мікросекунд отримується шляхом додавання поточного значення таймера 0 Timer 0 (який буде продовжувати збільшуватися) до збереженого "Переліку таймера 0".

Попередження: Оскільки переривання вимкнено всередині ISR, а оскільки в останній версії ID Arduino IDE використовується переривання для послідовного читання та запису, а також для збільшення лічильника, що використовується "milis" та "затримка", ви не повинні намагатися використовувати ці функції всередині ISR. Іншим чином:

  • Не намагайтеся затягувати, наприклад: delay (100);
  • Ви можете отримати час від дзвінка до мільйонів, однак він не збільшуватиметься, тому не намагайтеся затягувати, чекаючи його збільшення.
  • Не робіть серійних відбитків (напр. Serial.println ("ISR entered");)
  • Не намагайтеся робити серійне читання.

Переривання змін перемикання

Є два способи виявлення зовнішніх подій на штифтах. Перший - це спеціальні «зовнішні переривальні» штифти, D2 і D3. Ці загальні дискретні події переривання, по одному на контакт. Ви можете дістатися до них, використовуючи attachInterrupt для кожного штифта. Ви можете вказати стан підйому, падіння, зміни або низький рівень переривання.

Однак існують також переривання «pin pin» для всіх штифтів (у Atmega328, не обов'язково, всі штирі на інших процесорах). Вони діють на групи штифтів (D0 - D7, D8 - D13 і A0 - A5). Вони також мають нижчий пріоритет, ніж переривання зовнішньої події. Однак їх використовувати трохи складніше, ніж зовнішні переривання, оскільки вони згруповані в партії. Тож якщо виникла перерва, вам доведеться відпрацювати у власному коді, який саме штифт спричинив переривання.

Приклад коду:

ISR (PCINT0_vect)
 {
 // handle pin change interrupt for D8 to D13 here
 }  // end of PCINT0_vect

ISR (PCINT1_vect)
 {
 // handle pin change interrupt for A0 to A5 here
 }  // end of PCINT1_vect

ISR (PCINT2_vect)
 {
 // handle pin change interrupt for D0 to D7 here
 }  // end of PCINT2_vect


void setup ()
  {
  // pin change interrupt (example for D9)
  PCMSK0 |= bit (PCINT1);  // want pin 9
  PCIFR  |= bit (PCIF0);   // clear any outstanding interrupts
  PCICR  |= bit (PCIE0);   // enable pin change interrupts for D8 to D13
  }

Для обробки переривання зміни штифта вам потрібно:

  • Вкажіть, який штифт у групі. Це змінна PCMSKn (де n дорівнює 0, 1 або 2 з таблиці нижче). Ви можете мати перерви на декількох штирях.
  • Увімкніть відповідну групу переривань (0, 1 або 2)
  • Поставити обробник переривання, як показано вище

Таблиця шпильок -> зміна назв / масок шпильки

D0    PCINT16 (PCMSK2 / PCIF2 / PCIE2)
D1    PCINT17 (PCMSK2 / PCIF2 / PCIE2)
D2    PCINT18 (PCMSK2 / PCIF2 / PCIE2)
D3    PCINT19 (PCMSK2 / PCIF2 / PCIE2)
D4    PCINT20 (PCMSK2 / PCIF2 / PCIE2)
D5    PCINT21 (PCMSK2 / PCIF2 / PCIE2)
D6    PCINT22 (PCMSK2 / PCIF2 / PCIE2)
D7    PCINT23 (PCMSK2 / PCIF2 / PCIE2)
D8    PCINT0  (PCMSK0 / PCIF0 / PCIE0)
D9    PCINT1  (PCMSK0 / PCIF0 / PCIE0)
D10   PCINT2  (PCMSK0 / PCIF0 / PCIE0)
D11   PCINT3  (PCMSK0 / PCIF0 / PCIE0)
D12   PCINT4  (PCMSK0 / PCIF0 / PCIE0)
D13   PCINT5  (PCMSK0 / PCIF0 / PCIE0)
A0    PCINT8  (PCMSK1 / PCIF1 / PCIE1)
A1    PCINT9  (PCMSK1 / PCIF1 / PCIE1)
A2    PCINT10 (PCMSK1 / PCIF1 / PCIE1)
A3    PCINT11 (PCMSK1 / PCIF1 / PCIE1)
A4    PCINT12 (PCMSK1 / PCIF1 / PCIE1)
A5    PCINT13 (PCMSK1 / PCIF1 / PCIE1)

Обробка обробки переривань

Обробник переривання повинен був розібратися, який штифт спричинив переривання, якщо маска вказує більше одного (наприклад, якщо ви хочете переривання на D8 / D9 / D10). Для цього вам потрібно буде зберегти попередній стан цього штифта та відпрацювати (виконавши цифрове читання чи подібне), якщо цей конкретний штифт змінився.


Напевно, ви все одно використовуєте переривання ...

"Нормальне" середовище Arduino вже використовує переривання, навіть якщо ви особисто цього не робите. Виклики функції millis () та micros () використовують функцію "переповнення таймера". Один із внутрішніх таймерів (таймер 0) налаштований для переривання приблизно 1000 разів на секунду та збільшення внутрішнього лічильника, який фактично стає лічильником milis (). У цьому є трохи більше, ніж це, оскільки регулюється точна тактова частота.

Також апаратна послідовна бібліотека використовує переривання для обробки вхідних та вихідних послідовних даних. Це дуже корисно, оскільки ваша програма може робити інші речі, коли переривання спрацьовує та заповнює внутрішній буфер. Потім, перевіривши Serial.available (), ви зможете дізнатися, що, якщо що, було розміщено в цьому буфері.


Виконання наступної інструкції після вмикання перерв

Після невеликої дискусії та досліджень на форумі Arduino, ми з’ясували, що саме відбувається після того, як ви включите переривання. Є три основні способи, на які я можу подумати, що ви можете вмикати переривання, які раніше не були включені:

  sei ();  // set interrupt enable flag
  SREG |= 0x80;  // set the high-order bit in the status register
  reti  ;   // assembler instruction "return from interrupt"

У всіх випадках процесор гарантує, що наступна інструкція після вмикання перешкод (якщо вони були раніше відключені) завжди буде виконуватися, навіть якщо очікується подія переривання. (Під "наступним" я маю на увазі наступну в послідовності програми, не обов'язково ту, яка фізично слідує. Наприклад, інструкція RETI відскакує туди, де відбулося переривання, а потім виконує ще одну інструкцію).

Це дозволяє писати код так:

sei ();
sleep_cpu ();

Якщо б не ця гарантія, переривання може статися до того, як процесор спав, і тоді він ніколи не може прокинутися.


Порожній перериває

Якщо ви хочете просто перервати, щоб розбудити процесор, але нічого не робити, зокрема, ви можете використовувати визначення EMPTY_INTERRUPT, наприклад.

EMPTY_INTERRUPT (PCINT1_vect);

Це просто генерує інструкцію "reti" (повернення від переривання). Оскільки він не намагається зберегти або відновити регістри, це був би найшвидший спосіб отримати переривання, щоб його розбудити.


Критичні розділи (атомний змінний доступ)

Існують деякі тонкі питання щодо змінних, які поділяються між процедурами переривання обслуговування (ISR) та основним кодом (тобто кодом не в ISR).

Оскільки ISR може запускатись у будь-який час, коли вмикаються переривання, вам потрібно бути обережними щодо доступу до таких загальних змінних, оскільки вони можуть оновлюватися в той самий момент, коли ви отримуєте доступ до них.

По-перше ... коли ви використовуєте "мінливі" змінні?

Змінна повинна бути позначена мінливою лише тоді, коли вона використовується як всередині ISR, так і зовні.

  • Змінні, що використовуються лише за межами ISR, не повинні бути мінливими.
  • Змінні, що використовуються лише в ISR, не повинні бути мінливими.
  • Змінні, що використовуються як внутрішньо, так і зовні ISR, повинні бути мінливими.

напр.

volatile int counter;

Позначення змінної як мінливої ​​говорить компілятору не "кешувати" вміст змінних в регістрі процесора, а завжди читати його з пам'яті, коли це потрібно. Це може уповільнити обробку, і тому ви не просто робите кожну змінну мінливою, коли це не потрібно.

Вимкніть переривання під час доступу до змінної змінної

Наприклад, для порівняння countз деякою кількістю вимкніть переривання під час порівняння, якщо один байт countоновлюється ISR, а не другий байт.

volatile unsigned int count;

ISR (TIMER1_OVF_vect)
  {
  count++;
  } // end of TIMER1_OVF_vect

void setup ()
  {
  pinMode (13, OUTPUT);
  }  // end of setup

void loop ()
  {
  noInterrupts ();    // <------ critical section
  if (count > 20)
     digitalWrite (13, HIGH);
  interrupts ();      // <------ end critical section
  } // end of loop

Прочитайте аркуш даних!

Більше інформації про перебої, таймери тощо можна отримати з інформаційного аркуша для процесора.

http://www.atmel.com/images/Atmel-8271-8-bit-AVR-Microcontroller-ATmega48A-48PA-88A-88PA-168A-168PA-328-328P_datasheet_Complete.pdf


Подальші приклади

Місця щодо місця (обмеження розміру публікації) заважають моєму списку більше прикладного коду. Для додаткового коду див. Мою сторінку про переривання .


Дуже корисна довідка - це була вражаюче швидка відповідь.
Дат Хан Сумка

Це було довідкове питання. Я підготував відповідь, і це було б ще швидше, якби відповідь не була занадто довгою, тому мені довелося її обрізати. Докладнішу інформацію див. На пов'язаному веб-сайті.
Нік Гаммон

Щодо "режиму сну", чи ефективно змусити сон Arduino за, скажімо, 500 мс?
Дат Ха

@ Nick Gammon Я думаю, що включення або вимкнення живлення (з автоматизацією чи ні) для процесора можна визначити як нетрадиційне переривання - якщо ви цього хотіли. "Я мав відповідь підготовлений" - ви просто вийняли всю магію того моменту, який я подумав, що це було.
Дат Хан Сумка

1
Я боюся, що це неправда. У мене є приклад, який використовує переривання зміни штифтів, щоб вийти з режиму відключення живлення. Крім того, як я згадував на своїй сторінці про перебої, Atmel підтвердив, що будь-який зовнішній перерив розбудить процесор (тобто підвищення / падіння / зміна і низький рівень).
Нік Гаммон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.