На цю особливість поведінки зовнішнього переривання в AVR мою увагу звернув колега по форуму Радіокота Rtmip. Питання мене зацікавив, і я провів невелике дослідження, результати якого пропонуються увазі читачів нижче.
Зрозуміло, перед тим, як взятися за дослідження, нам знадобиться правильна музика - я в тиші рідко працюю.
Отже, суть питання. Як відомо, переривання (все, і не тільки в AVR) влаштовані таким чином: є керуючі регістри, значення в них локально включають / вимикають / налаштовують цікавлять переривання; є регістри прапорів - коли відбувається подія, якій судилося викликати переривання, прапор цікавить переривання зводиться апаратно. після чого, якщо переривання глобально дозволені, перехід до обробника проводиться негайно, а якщо глобально заборонені - перехід до обробника проводиться за фактом активного стану цікавить прапора як тільки переривання будуть знову дозволені.
Зовнішнє переривання INT0, що служить темою цієї статті, може бути створене в декількох випадках, зокрема, по фронту імпульсу на ніжці, по спаду імпульсу, по зміні стану і за низьким рівнем на ніжці. І ось тут починається цікаве - якщо вчитатися в даташит, можна прочитати, що буде переривання налаштоване по низькому рівню, його прапор завжди скинутий.
Як так? А як же тоді виклик обробника? А що буде, якщо переривання сталося в момент, коли переривання глобально заборонені? Як контролер дізнається, що воно було? І чи впізнає?
Щоб відповісти на ці питання, я розчохлив макетке, написав невеличкий тестик, код якого наведено лістингом нижче, і провів кілька експериментів.
1. Так, при конфігуруванні переривання за рівнем прапор переривання завжди скинутий.
2. При цьому, якщо переривання глобально дозволені, переривання викликається без установки прапора! Чудеса!
3. Якщо рівень на ніжці трапився в момент, коли переривання були заборонені (і пропав до їх вирішення), подія буде втрачено. Контролер ніяк про нього не дізнається, на відміну від режимів реакції на фронт / спад / зміна рівня.
Зрозуміло, висновок вірний не тільки для INT0, але і для INT1 - вони ідентичні.
У чому глибинний сенс такого режиму? Якщо дивитися на речі філософськи, то можна припустити, що режим чутливості до рівня був доданий виключно заради того, щоб виводити контролер зі стану зниженого енергоспоживання, оскільки переривання по фронту / спаду / зміни не працюють, коли тактовий генератор вимкнений, а ось переривання по рівню працює і успішно пробуджує МК.
Але ж перевести МК в активний режим можна і будь-яким перериванням з набору PCINT. Навіщо потрібна така дивна обробка INT0? Відповідь можна знайти, якщо почитати даташит на що-небудь викопне з серії AVR; наприклад, на AT90S1200. У ті роки ще не було ніяких новомодних PCINT, зате цілком були режими зниженого енергоспоживання і необхідність виходити з них ще якимось чином крім скидання системи - ось і придумали хитрий фінт з INT0. Нині це, очевидно, не більше ніж данина сумісності, оскільки переривання по рівню в цілому незручно - спрацьовує постійно, поки присутній рівень (є потенційний шанс підвісити контролер), та й зазвичай реакції вимагає саме зміна стану ніжки - натискання кнопки, спрацьовування датчика, що -то ще в цьому роді. Тому, власне, мало хто звертає увагу на описувані особливості.
Нижче наведено код тесту. Він написаний в стилі «кошмарний сон Vga» - на Сі, насичений директивами умовної компіляції, макрозаменамі і навіть містить трохи магічних чисел, в загальному, я відтягнули. 😀 Програма дозволяє тестувати переривання INT0 в різних режимах і різними методами, в залежності від певних директив препроцесора. Для режимів 1 і 2 (тести переривання і прапорів) потрібне зовнішнє підтягаючий резистор і кнопка, режим 3 (тест переривання в період глобального заборони переривань) працює автономно.
Коректна очистка тільки одного прапора виглядає як EIFR = _BV (INTF0) ;. Проте, в даному прикладі це несуттєво.
О, а дивіться, яка тут дівчина в окулярах в хорі співає ...
Ок, давай так зробимо:
Перед блокуванням переривань додай:
PORTC = EIFR;
_delay_us (50);
PORTC = 0;
а після блокування прибери sei ();
Природно потрібно налаштувати порт С на висновок.
Повір, дуже здивуєшся!
Те що ти назвав забороною переривання int0, ні що інше як - скасування обробника, саме ж переривання, на апаратному рівні - відбувається.
mask; / * Wrong way! * /
The bitwise «not» operator (
) Will also promote the value in mask to an int. To keep it an 8-bit value, typecast before the «not» operator:
var = (Unsigned char)
і ще, ось так:
EICRA = _BV (ISC01) | _BV (ISC00); // TESTED_MODE_MASK;
робити не можна, потрібно спочатку заборонити переривання.
Не помітив оновлення статті відразу 🙂 Тепер все вірно. (Про EIFR | = _BV (INTF0)).
Я якось завжди пов'язував переривання з подіями. Подія - це зміна в системі. Наростаючий фронт, спадаючий фронт - це все зміни в системі. Значить, це є події. А значить, вони вписуються в парадигму переривань. Все логічно. А якщо в системі нічого не змінюється - де ж тут подія-то? Все життя на лапці одиничний (нульовий) рівень - це не подія, це постійний стан. Реагувати на постійний стан - це якось безглуздо і скидається на некомпетентність. Тому я теж не розумію сенсу переривань-по-рівня. З моєї точки зору це якийсь «анахренізм». Я теж так думаю, що розробникам АВР-контролерів було складно в перших мікроконтролерах розмістити на входах портів тригери, які б «Взвод» за подією.
Так, судячи з усього це і правда зроблено не від хорошого життя. Але, до речі, як мені знову ж підказав Rtmip. в mega8, наприклад, немає PCINT. Тому єдиний спосіб ззовні розбудити МК, що знаходиться в стані сну, - ось таке ось дивне переривання, або system reset.
EIFR | = _BV (INTF0); // Clear flag