Работа с внешними прерываниями

Учебный курс AVR. Использование внешних прерываний в AVR

Введение

  Микроконтроллер общается с внешним миром посредством портов ввода/вывода. Порт представляет собой совокупность выводов микроконтроллера объединенных в группу.

Каждый вывод порта можно независимо от других выводов конфигурировать на вход или на выход. Также многие выводы микроконтроллеров AVR имеют альтернативные функции.

Одна из таких функций – это прерывание по изменению состояния вывода, так называемое внешнее прерывание. Об этом прерывании мы сегодня и поговорим. 

   Для примера будем рассматривать микроконтроллер ATmega16. 

   У него три внешних прерывания – INT0, INT1 и INT2. Эти прерывания жестко «привязаны» к выводам PD2, PD3 и PB2 и переназначить их на другие выводы нельзя. 

   Когда используются внешние прерывания, выводы PD2, PD3 и PB2  конфигурируются на вход. Однако если они настроены на  выход, внешние прерывания тоже будут генерироваться при изменении их состояния, что позволяет реализовать программные прерывания.     Для разрешения или запрещения внешних прерываний предназначен управляющий  регистр GICR (General Interrupt Control Register).

   Установка битов INT1, INT0 или INT2 разрешает прерывания при возникновении события на соответствующем выводе микроконтроллера AVR, а сброс — запрещает.

   Естественно нужно установить еще и флаг глобального разрешения прерываний –  I, который расположен в регистре SREG. Без него вообще ни одно прерывание вызываться не будет.

Внешнее прерывание может происходить по одному из условий:

– по низкому уровню на выводах INT0, INT1, 

– по любому изменению логического уровня на выводах INT0, INT1

– по спадающему фронту сигнала на выводах INT0, INT1, INT2,

– по нарастающему фронту на выводах INT0, INT1, INT2.

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

   Условия генерации прерываний устанавливаются с помощью конфигурационных регистров. Для INT0, INT1 – это регистр MCUCR (MCU Control Register). Для INT2 – MCUCSR (MCU Control and Status Register)   В таблице ниже приведены возможные значения разрядов ISC01, ISC00 и соответствующие им условия генерации внешнего прерывания. 

   Для прерывания INT1 таблица выглядит аналогично, только управляющие разряды другие — это ISC11 и ISC10. 

   Прерывание INT2 может происходить только по фронтам сигнала, поэтому для установки условий используется всего один бит – это бит ISC2 регистра MCUCSR.

  Кстати, при смене значения бита ISC2 может быть сгенерировано прерывание INT2. Чтобы этого не происходило, нужно производить модификацию бита ISC2 так: запретить внешнее прерывание, поменять бит ISC2, сбросить флаг прерывания — INTF2 (смотри ниже) и опять разрешить прерывание INT2.

    Обнаружение фронтов сигналов на выводах INT0/INT1 осуществляется синхронно, то есть по сигналу тактового генератора. Минимальная длительность входного импульса, гарантирующая генерацию прерывания, составляет один период тактового сигнала микроконтроллера AVR.

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

    Обнаружение перепадов сигнала на выводе INT2 тоже осуществляется асинхронно. Минимальная длительность импульса, гарантирующая генерацию прерывания, составляет 50 нс.

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

   Последний регистр, имеющий отношение к внешним прерываниям, – это статусный регистр GIFR (General Interrupt Flag Register). В нем содержатся флаги, устанавливаемые в случае формирования запроса на внешнее прерывание. 

   Флаги сбрасываются аппаратно, когда вызывается обработчики прерываний. Также их можно сбросить программно, записав в регистр единицы. Причем сброс нужно производить перезаписью регистра GIFR, на не операцией побитового ИЛИ.

Неправильно: GIFR |= (1

Источник: http://chipenable.ru/index.php/item/105

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

Оптимизируйте ваши программы для Arduino с помощью прерываний – простого способа для реагирования на события в режиме реального времени!

Мы прерываем нашу передачу..

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

При обнаружении определенного сигнала прерывание (как и следует из названия) прерывает всё, что делал процессор, и выполняет некоторый код, предназначенный для реагирования на вызвавшую его внешнюю причину, воздействующую на Arduino.

После того, как этот код будет выполнен, процессор возвращается к тому, что он изначально делал, как будто ничего не случилось!

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

Прерывания по кнопке

Начнем с простого примера: использования прерывания для отслеживания нажатия кнопки. Для начала, мы возьмем скетч, который вы, вероятно, уже видели: пример «Button», включенный в Arduino IDE (вы можете найти его в каталоге «Примеры», проверьте меню Файл → Примеры → 02. Digital → Button).

const int buttonPin = 2; // номер вывода с кнопкой const int ledPin = 13; // номер вывода со светодиодом int buttonState = 0; // переменная для чтения состояния кнопки void setup() { // настроить вывод светодиода на выход: pinMode(ledPin, OUTPUT); // настроить вывод кнопки на вход: pinMode(buttonPin, INPUT); } void loop() { // считать состояние кнопки: buttonState = digitalRead(buttonPin); // проверить нажата ли кнопка. // если нажата, то buttonState равно HIGH: if (buttonState == HIGH) { // включить светодиод: digitalWrite(ledPin, HIGH); } else { // погасить светодиод: digitalWrite(ledPin, LOW); } }

В том, что вы видите здесь, нет ничего шокирующего и удивительного: всё, что программа делает снова и снова, это прохождение через цикл loop() и чтение значения buttonPin.

Предположим на секунду, что вы хотели бы сделать в loop() что-то еще, что-то большее, чем просто чтение состояния вывода. Вот здесь и пригодится прерывание.

Вместо того, чтобы постоянно наблюдать за состоянием вывода, мы можем поручить эту работу прерыванию и освободить loop() для выполнения в это время того, что нам необходимо! Новый код будет выглядеть следующим образом:

const int buttonPin = 2; // номер вывода с кнопкой const int ledPin = 13; // номер вывода со светодиодом volatile int buttonState = 0; // переменная для чтения состояния кнопки void setup() { // настроить вывод светодиода на выход: pinMode(ledPin, OUTPUT); // настроить вывод кнопки на вход: pinMode(buttonPin, INPUT); // прикрепить прерывание к вектору ISR attachInterrupt(0, pin_ISR, CHANGE); } void loop() { // Здесь ничего нет! } void pin_ISR() { buttonState = digitalRead(buttonPin); digitalWrite(ledPin, buttonState); }

Циклы и режимы прерываний

Здесь вы заметите несколько изменений. Первым и самым очевидным из них является то, чтоloop() теперь не содержит никаких инструкций! Мы можем обойтись без них, так как вся работа, которая ранее выполнялась в операторе if/else, теперь выполняется в новой функцииpin_ISR().

Этот тип функций называется обработчиком прерывания: его работа состоит в том, чтобы быстро запуститься, обработать прерывание и позволить процессору вернуться обратно к основной программе (то есть к содержимому loop()).

При написании обработчика прерывания следует учитывать несколько важных моментов, отражение которых вы можете увидеть в приведенном выше коде:

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

Вам, наверное, интересно: откуда мы знаем, когда запустится прерывание? Что его вызывает? Третья функция, вызываемая в функции setup(), устанавливает прерывание для всей системы. Данная функция, attachInterrupt(), принимает три аргумента:

  1. вектор прерывания, который определяет, какой вывод может генерировать прерывание. Это не сам номер вывода, а ссылка на место в памяти, за которым процессор Arduino должен наблюдать, чтобы увидеть, не произошло ли прерывание. Данное пространство в этом векторе соответствует конкретному внешнему выводу, и не все выводы могут генерировать прерывание! На Arduino Uno генерировать прерывания могут выводы 2 и 3 с векторами прерываний 0 и 1, соответственно. Для получения списка выводов, которые могут генерировать прерывания, смотрите документацию на функцию attachInterrupt для Arduino;
  2. имя функции обработчика прерывания: определяет код, который будет запущен при совпадении условия срабатывания прерывания;
  3. режим прерывания, который определяет, какое действие на выводе вызывает прерывание. Arduino Uno поддерживает четыре режима прерывания:
    • RISING– активирует прерывание по переднему фронту на выводе прерывания;
    • FALLING– активирует прерывание по спаду;
    • CHANGE– реагирует на любое изменение значения вывода прерывания;
    • LOW– вызывает всякий раз, когда на выводе низкий уровень.

И резюмируя, наша настройка attachInterrupt()соответствует отслеживанию вектора прерывания 0 (вывод 2), чтобы отреагировать на прерывание с помощью pin_ISR(), и вызвать pin_ISR()всякий раз, когда произойдет изменение состояния на выводе 2.

Volatile

Еще один момент, на который стоит указать: наш обработчик прерывания использует переменную buttonStateдля хранения состояния вывода. Проверьте определение buttonState: вместо типа int, мы определили его, как тип volatile int.

В чем же здесь дело? volatileявляется ключевым словом языка C, которое применяется к переменным. Оно означает, что значение переменной находится не под полным контролем программы.

То есть значение buttonStateможет измениться и измениться на что-то, что сама программа не может предсказать – в этом случае, пользовательский ввод.

https://www.youtube.com/watch?v=EptTr2nUCJk

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

Так как переменная buttonStateне используется или не вызывается напрямую в функциях loop()или setup(), существует риск того, что компилятор может удалить её, как неиспользуемую переменную.

Очевидно, что это неправильно – нам необходима эта переменная! Ключевое слово volatile обладает побочным эффектом, сообщая компилятору, что эту переменную необходимо оставить в покое.

Удаление неиспользуемых переменных из кода – это функциональная особенность, а не баг компиляторов. Люди иногда оставляют в коде неиспользуемые переменные, которые занимают память. Это не такая большая проблема, если вы пишете программу на C для компьютера с гигабайтами оперативной памяти.

Однако, на Arduino оперативная память ограничена, и вы не хотите тратить её впустую! Даже C компиляторы для компьютеров будут поступать точно так же, несмотря на массу доступной системной памяти.

Зачем? По той же причине, по которой люди убирают за собой после пикника – это хорошая практика, не оставлять после себя мусор.

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

Прерывания – это простой способ заставить вашу систему быстрее реагировать на чувствительные к времени задачи.

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

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

Оригинал статьи:

  • Nash Reilly. Using Interrupts on Arduino

Источник: https://radioprog.ru/post/114

Программирование Arduino – прерывания

10. Прерывания

уважаемый ДиХальт на доступных примерах объясняет, что такое Подпрограммы и прерывания 🙂

Arduino так же предоставляет свои функции для работы с прерываниями. Эти функции объявлены в файле hardwarecoresarduinowiring.h и реализованы в файле hardwarecoresarduinoWInterrupts.c Их всего две: attachInterrupt и DetachInterrupt.

void attachInterrupt(uint8_t, void (*)(void), int mode);

Описание: Определяет, какую функцию вызывать, когда происходит внешнее прерывание. Замещает предыдущую функцию, если таковая была привязана к данному прерыванию. Большинство плат Arduino/Freeduino имеют два внешних прерывания с номерами 0 (на digital pin 2) и 1 (на digital pin 3). Arduino Mega имеет дополнительно ещё четыре: с номерами 2 (pin 21), 3 (pin 20), 4 (pin 19) и 5 (pin 18).

Вызов:attachInterrupt(interrupt, function, mode);Параметры: interrupt: номер прерывания (int) function: функция, которая должны вызываться при прерывании. Функция не должна принимать параметров и не должна ничего возвращать. mode: определяет, когда должно сработать прерывание.

Определены следующие константы:

LOW — вызов прерывания всякий раз, когда на порту низкий уровень напряжения;

CHANGE – прерывание вызывается при изменении значения на входе;
RISING – вызов прерывания при изменении уровня напряжения с низкого (LOW) на высокое(HIGH)
FALLING – вызов прерывания при изменении уровня напряжения с высокого (HIGH) на низкое (LOW) Возвращаемое значение: ничего Пример:// // светодиод, подключённый к digital pin 13 будет изменять своё // состояние при изменении напряжения на digital pin 2 // int pin = 13; volatile int state = LOW; void setup() { pinMode(pin, OUTPUT); // порт как выход attachInterrupt(0, blink, CHANGE); // привязываем 0-е прерывание к функции blink(). } void loop() { digitalWrite(pin, state); // выводим state } void blink() { state = !state; // меняем значение на противоположное }

Примечание относительно использования volatile:

Т.о. переменная получается как бы «расшарена». Т.е. значение переменной могут изменять разные части программы — обработчики прерываний, подпрограммы, функции.

void detachInterrupt(uint8_t);

Описание: Отключает указанное прерывание. Вызов:detachInterrupt(interrupt);Параметры: interrupt: номер прерывания для отключения (0 или 1). Возвращаемое значение: ничего

читать далее:

Ссылки:

По теме:

Источник: http://robocraft.ru/blog/arduino/45.html

Внешние прерывания STM32

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

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

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

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

Теперь от общей теории перейдём к частным вариантам реализации. У AVR под внешние прерывания отведены конкретные выводы, у Atmega16 их 4. У STM32 дело с внешними прерываниями обстоит интереснее, управляет внешними прерываниями контроллер EXTI.

Любой из выводов порта может быть настроен на работу с внешними прерываниями, но количество выводов GPIO одновременно настроенных на работу с внешними прерываниями не может превышать шестнадцать. Эти выводы соединяются с EXTI c помощью мультиплексоров.

Если посмотреть на картинку с мультиплексорами видно, что номер мультиплексора совпадает с номером вывода и мы не можем настроить на прерывание два вывода с одинаковыми номерами, то есть нельзя одновременно настроить на внешние прерывания PB0 и PA0.

Есть ещё 4 линии, которые подключаются не к GPIO, а к периферии:

  • EXTI 17 – выход PVD
  • EXTI 18 – событие от RTC_Alarm
  • EXTI 19 – событие от USB_Wakeup
  • EXTI 20 – событие от Ethernet_Wakeup

Кроме привычных прерываний, контроллер EXTI может генерировать события и программные прерывания.

Событие — выставляется флаг, без перехода в обработчик прерывания.

Программное прерывание — то же прерывание, но вызывается вручную, записью '1' в соответствующий регистр.

Существует всего семь обработчиков внешних прерываний: EXTI0, EXTI1, EXTI2, EXTI3, EXTI4, EXTI9_5, EXTI15_10. Видно, что не каждое внешнее прерывание от GPIO имеет отдельный обработчик, некоторые из них совмещены.

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

RCC->APB2ENR |= RCC_APB2ENR_AFIOEN | RCC_APB2ENR_IOPEEN;
Вывод настроенный на работу с внешними прерываниями может быть сконфигурирован как: плавающий вход, вход с подтяжкой к питанию/земле.

Для настройки мультиплексоров выделены 4 регистра AFIO_EXTICRx разделённые на 4 секции, по 4 бита в каждой, мы рассмотрим один из них.

Для настройки нулевого вывода на работу с внешними прерываниями, надо в секцию EXTI0 записать код порта.
//выбор порта и пина для внешнего прерывания AFIO->EXTICR [0] |= AFIO_EXTICR1_EXTI0_PE; //если хотим выбрать первый пин порта B AFIO->EXTICR [0] |= AFIO_EXTICR1_EXTI1_PB;

EXTI_IMR – регистр маскировки прерываний, запись '1' в соответствующий бит разрешает прерывания, по умолчанию там нули.

EXTI_EMR – регистр маскировки событий, запись '1' в соответствующий бит разрешает событие, по умолчанию там нули.

EXTI_FTSR и EXTI_RTSR – определяют по какому фронту будет возникать прерывание. При записи '1' в соответствующий бит EXTI_FTSR по спадающему фронту, в EXTI_RTSR – по возрастающему.

EXTI_SWIER – программный вызов события/прерывания, запись '1' в соответствующий бит вызывает генерацию события/прерывания.

EXTI_PR – флаг возникновения события/прерывания, сбрасывается вручную при записи '1'.

Теперь давайте рассмотрим простой пример, в котором по нажатию кнопки, которая подключена к нулевому выводу порта E, будет генерироваться прерывание. #include “stm32f10x.h” void EXTI0_IRQHandler(void) { //сбрасываем флаг прерывания EXTI->PR |= EXTI_PR_PR0; } int main(void) { //включаем тактирование порта и альтернативных функций RCC->APB2ENR |= RCC_APB2ENR_AFIOEN | RCC_APB2ENR_IOPEEN; //настраиваем вывод на вход с подтяжкой GPIOE->CRL &= ~GPIO_CRL_CNF0_0; GPIOE->CRL |= GPIO_CRL_CNF0_1; GPIOE->CRL &= ~GPIO_CRL_MODE0 ; //подтягиваем к питанию GPIOE->BSRR |= GPIO_BSRR_BR0; //выбор порта и пина для внешнего прерывания AFIO->EXTICR [0] |= AFIO_EXTICR1_EXTI0_PE; //по спадающему фронту EXTI->FTSR |= EXTI_FTSR_TR0; //устанавливаем маску EXTI->IMR |= EXTI_IMR_MR0; //разрешаем прерывания EXTI0 NVIC->ISER[0] = NVIC_ISER_SETENA_6; //разрешаем прерывания глобально __enable_irq (); while(1) { } }

Источник: https://hubstub.ru/stm32/80-vneshnie-preryvaniya-stm32.html

Механизм работы прерываний. Понятие вектора прерываний. Системные и пользовательские прерывания. Их назначения. Способы обработки прерываний

Источник: https://infopedia.su/17xc8d2.html

Работа системы прерываний в реальном режиме работы процессора

⇐ ПредыдущаяСтр 17 из 50Следующая ⇒

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

Итак, каждый вектор прерываний состоит из 4 байтов или 2 слов; первые два содержат новое значение для регистра IP, а следующие два – новое значение регистра CS. Таблица векторов прерываний занимает 1024 байта. Таким образом, в ней может быть задано 256 векторов прерываний.

В процессоре i8086 эта таб­лица располагается на адресах 00000-003FFH. Расположение этой таблицы в про­цессорах i80286 и старше определяется значением регистра IDTR – Interrupt Descriptor Table Register. При включении или сбросе процессора i80x86 этот ре­гистр обнуляется.

Однако при необходимости можно в регистре IDTR указать смещение и, таким образом, перейти на новую таблицу векторов прерываний.

Таблица векторов прерываний заполняется (инициализируется) при запуске сис­темы, но в принципе может быть изменена или перемещена.

Каждый вектор прерывания имеет свой номер, называемый номером прерывания, который указывает его место в таблице. Этот номер, помноженный на четы­ре (сдвиг на два разряда влево и заполнение освободившихся битов нулями), и сложенный с содержимым регистра IDTR, дает абсолютный адрес первого бай­та вектора в оперативной памяти.

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

В IBM PC, как и в других вычислительных системах, прерывания бывают двух видов: внутренние и внешние.

Внутренние прерывания, как мы уже знаем, возникают в результате работы про­цессора. Они возникают в ситуациях, которые нуждаются в специальном обслу­живании, или при выполнении специальных инструкций – INT или INT0. Это следующие прерывания:

¨ прерывание при делении на ноль; номер прерывания – 0;

¨ прерывание по флагу TF (trap flag1). В этом случае прерывание обычно ис­пользуется специальными программами отладки типа DEBUG. Номер прерыва­ния – 1;

¨ инструкции INT (interrupt – выполнить прерывание с соответствующим но­мером) и INT0 (interrupt if overflow – прерывание по переполнению). Эти пре­рывания называются программными.

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

Внешние прерывания возникают по сигналу какого-нибудь внешнего устройства.

Существуют два специальных внешних сигнала среди входных сигналов процес­сора, при помощи которых можно прервать выполнение текущей программы и тем самым переключить работу центрального процессора.

Это сигналы NMI (no mask interrupt, немаскируемое прерывание) и INTR (interrupt request, запрос на прерывание). Соответственно, внешние прерывания подразделяются на немаскируемые и маскируемые.

Маскируемые прерывания генерируются контроллером прерываний по заявке определенных периферийных устройств2. Контроллер прерываний (его обозна­чение – i8259A) поддерживает восемь уровней (линий) приоритета; к каждому уровню «привязано» одно периферийное устройство1.

Маскируемые прерывания часто называют ещё аппаратными прерываниями. В ПК, начиная с IBM PC AT, построенных на базе микропроцессора i80286, используются два контроллера прерываний i8259A; они соединяются каскадным образом.

Схема последователь­ного соединения этих контроллеров изображена на рис. 3.12.

Таким образом, на IBM PC AT предусмотрено 15 линий IRQ2, часть которых ис­пользуется внутренними контроллерами системной платы, а остальные заняты стандартными адаптерами либо не используются. Ниже перечислены линии запроса на прерывание, которые мы приводим потому, что каждый специалист по вычислительной технике должен знать основные стандарты ПК. Итак, линии IRQ:

0 – системный таймер;

1 – контроллер клавиатуры;

2 – сигнал возврата по кадру (EGA/VGA), на AT соединен с IRQ 9;

3 – обычно СОМ2/СОМ4;

4 – обычно СОМ1/СОМ3;

5 – контроллер HDD (на первых компьютерах IBM PC XT), обычно свободен на IBM PC AT и используется звуковой картой;

6 – контроллер FDD;

7 – LPT1, многими LPT-контроллерами не используется;

8 – часы реального времени с автономным питанием (RTC – real time clock);

9 – параллельна IRQ 2;

10 – не используется, то есть свободно;

11 – свободно;

12 – обычно контроллер мыши типа PS/2;

13 – математический сопроцессор;

14 – обычно контроллер IDE0 (первый канал);

Рис. 3.12. Каскадирование контроллеров прерывания

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

1 В стек помещается регистр флагов PSW.

2 Флаг включения/выключения прерываний IF и флаг трассировки TF, находящиеся в регистре PSW, обнуляются для блокировки других маскируемых прерываний и исключения пошагового режима исполнения команд.

3 Значения регистров CS и IP сохраняются в стеке вслед за PSW.

4 Вычисляется адрес вектора прерывания, и из вектора, соответствующего номеру прерывания, загружаются новые значения IP и CS.

Когда системная подпрограмма принимает управление, она может снова разрешить маскируемые прерывания командой STI (set interrupt flag, установить флаг прерываний), которая переводит флаг IF в состояние 1, что разрешает микропроцессору вновь реагировать на прерывания, инициируемые внешними устрой­ствами, поскольку стековая организация позволяет вложение прерываний друг в друга.

Закончив работу, подпрограмма обработки прерывания должна выполнить инст­рукцию IRET (interrupt return), которая извлекает из стека три 16-битовых значения и загружает их в указатель команд IP, регистр сегмента команд CS и регистр PSW соответственно. Таким образом, процессор сможет продолжить работу с того места, где он был прерван.

В случае внешних прерываний процедура перехода на подпрограмму обработки прерывания дополняется следующими шагами:

1 Контроллер прерываний получает заявку от определенного периферийного устройства и, соблюдая схему приоритетов, генерирует сигнал INTR (inter­rupt request), который является входным для микропроцессора.

2 Микропроцессор проверяет флаг IF в регистре PSW. Если он установлен в 1, то переходим к шагу 3. В противном случае работа процессора не прерывает­ся. Часто говорят, что прерывания замаскированы, хотя правильнее говорить, что они отключены. Маскируются (запрещаются) отдельные линии запроса на прерывания посредством программирования контроллера прерываний.

3 Микропроцессор генерирует сигнал INTA (подтверждение прерывания). В от­вет на этот сигнал контроллер прерывания посылает по шине данных номер прерывания. После этого выполняется описанная нами ранее процедура пере­дачи управления соответствующей программе обработки прерывания.

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

Источник: https://lektsia.com/3×7316.html

Прерывания, события и NVIC

Допустим, в вашей программе имеется задержка на 5 секунд, и вам необходимо при этом отслеживать состояние какого-нибудь порта на появление там сигнала (предположим, это будет кнопка).

Пока вы находитесь в функции delay(5000), никакие действия, кроме бесполезных вычислений, не будут происходить, и сменить состояние светодиода у вас не получится.

Другими словами, пока программа не завершит выполнение кода из функции delay и не перейдет к строчке, где считывается значение из Input Data Register, – определить, что кнопка была нажата, вы не сможете, а значит, и обработать это нажатие не получится. Более того, вы можете просто пропустить этот момент.

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

Допустим, вы попросили объяснить своего друга, что же такое прерывание. В следующий момент звонит ваша возлюбленная, и вы со словами: «Прости, мне надо ответить…», – берете трубку. Закончив беседу, вы возвращаетесь к понятию о прерывании и ждете, когда ваш друг даст определение. Всё, что ему нужно сказать – «Собственно, это оно и было».

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

Возможны разнообразные прерывания по самым различным причинам. Поэтому каждому прерыванию ставят в соответствие число – так называемый номер прерывания (англ. position). Этот номер однозначно соответствует определенному событию.

Для того чтобы связать адрес обработчика прерывания с номером прерывания, используется таблица векторов прерываний, которая описана в startup-файле.

В STM32 имеется контроллер векторных прерываний NVIC (англ. nvic). Каждое прерывание имеет приоритет. Другими словами, если во время работы обработчика произойдет другое прерывание, приоритет которого больше, чем тот, что обрабатывается сейчас, то произойдет то же самое, что и с основной программой – обработчик будет остановлен.

В действительности всё немножко сложнее. Помимо прерываний существует понятие события (англ. event). При изучении документации может сложиться ложное предположение, что они не отличаются друг от друга. Используя теорию множеств, ситуацию можно разъяснить следующим образом:

Каждое прерывание вызывается событием, но не каждое событие вызывает прерывание. Event – аппаратное событие. Например, таймер достиг какого-то значение – это событие. Это событие может вызвать прерывание, а может запустить какую-либо периферию, например, запустит UART для отправки сообщения.

В зависимости от источника, прерывания можно разделить на три типа.

  • Асинхронные (или внешние) – это такие события, которые исходят от внешних источников, таких как периферийные устройства, а значит, могут произойти в произвольный момент времени. Факт возникновения в системе такого прерывания трактуется как запрос на прерывание (англ. Interrupt ReQuest, IRQ).
  • Синхронные (или внутренние) – это события непосредственно в ядре, и возникают они как следствие нарушения каких-то условий при исполнении кода: деление на ноль, переполнение стека, обращение к недопустимым адресам памяти и т. д.

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

Все возможные прерывания (а точнее, все имена существующих векторов прерываний) можно найти в файле startup_.s. Мы их уже видели, когда рассматривали библиотеку CMSIS. По умолчанию все обработчики – это затычки, делающие «ничего».

Если что-то вызовет прерывание, а обработчик не описан, то произойдет – ничего, камень просто застрянет в прерывании (т. к. затычка – это просто бесконечный цикл).

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

Для управления прерываниями существует специальный модуль – NVIC, который является частью ядра.

Если прерывание внутреннее, т. е. исходит от частей ядра, то оно разрешено по умолчанию. Если прерывание внешнее (кнопка, таймер, АЦП и т. д.) то его необходимо настроить отдельно. В общем случае алгоритм будет следующим:

  1. разрешить глобальные прерывания в NVIC;
  2. настроить и разрешить конкретные прерывания непосредственно в периферии – т. е. настроить нужные events на прерывания;
  3. описать обработчик прерывания.

Разрешение или запрет прерывания по вектору выполняется функциями (core_.h):

__STATIC_INLINE void NVIC_EnableIRQ(IRQn_Type IRQn)

__STATIC_INLINE void NVIC_DisableIRQ(IRQn_Type IRQn)

Все возможные прерывания перечислены в IRQn_Type.

typedef enum IRQn
{  /******  Cortex-M0 Processor Exceptions Numbers ******/  /*!

За работу с прерываниями в NVIC отвечают несколько регистров.

Рассмотрим их по порядку:

  • ISER (Interrupt Set Enable Register) – запись бита в нужную позицию включает прерывание;
  • ICER (Interrupt Clear Enable Register) – запись сюда, наоборот, выключает прерывание;
  • ISPR (Interrupt Set Pending Register) – поставить прерывание в ожидание;
  • IСPR (Interrupt Clear Pending Register) – сбросить прерывание с ожидания;
  • IPR (Interrupt priority register) – сбрасывает прерывание.

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

// Сброс флага события
EXTI->PR |= EXTI_PR_PR2;

Подробнее о том, как работать с внешним прерыванием, мы посмотрим в занятии, когда будем реализовывать работу кнопки, а сейчас перейдем к обработчику. Все обработчики имеют стандартный вид – _Handler. В следующем занятии мы научимся создавать задержку правильным образом, используя таймер, находящийся в ядре Cortex-M под названием SysTick.

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

Допустим, что вы уже настроили SysTick – что же делать дальше? Заглянув в таблицу векторов прерываний, мы можем найти следующую строчку:

DCD     SysTick_Handler                ; SysTick Handler

Это то, что нам нужно. Описав функцию обработчика (в соответствующем файле – stm32f10x_it.c), мы начинаем делать что-то полезное!

void SysTick_Handler (void) { // do something
}

Последнее, о чём стоит упомянуть, – это приоритет прерываний. NVIC может отслеживать приоритет (число от 0 до 15). Чем меньше номер уровня, тем он главней.

Если прерывания имеют одинаковые приоритеты, то они будут идти друг за другом. Если приоритеты разные, то NVIC остановит менее приоритетное прерывание и запустит более приоритетное.

По умолчанию все прерывания имеют одинаковый ранг, однако вы можете задать свой (приоритет «0» лучше не указывать):

void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority);
uint32_t NVIC_GetPriority(IRQn_Type IRQn);

– и функции, отвечающие за установку в очередь или удаление из очереди прерывания:

void NVIC_SetPendingIRQ(IRQn_Type IRQn);
void NVIC_ClearPendingIRQ(IRQn_Type IRQn);
uint32_t NVIC_GetPendingIRQ(IRQn_Type IRQn);

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

Источник: http://stm32.chrns.com/post/149089562384/nvic

Ссылка на основную публикацию
Adblock
detector
",css:{backgroundColor:"#000",opacity:.6}},container:{block:void 0,tpl:"
"},wrap:void 0,body:void 0,errors:{tpl:"
",autoclose_delay:2e3,ajax_unsuccessful_load:"Error"},openEffect:{type:"fade",speed:400},closeEffect:{type:"fade",speed:400},beforeOpen:n.noop,afterOpen:n.noop,beforeClose:n.noop,afterClose:n.noop,afterLoading:n.noop,afterLoadingOnShow:n.noop,errorLoading:n.noop},o=0,p=n([]),h={isEventOut:function(a,b){var c=!0;return n(a).each(function(){n(b.target).get(0)==n(this).get(0)&&(c=!1),0==n(b.target).closest("HTML",n(this).get(0)).length&&(c=!1)}),c}},q={getParentEl:function(a){var b=n(a);return b.data("arcticmodal")?b:(b=n(a).closest(".arcticmodal-container").data("arcticmodalParentEl"),!!b&&b)},transition:function(a,b,c,d){switch(d=null==d?n.noop:d,c.type){case"fade":"show"==b?a.fadeIn(c.speed,d):a.fadeOut(c.speed,d);break;case"none":"show"==b?a.show():a.hide(),d();}},prepare_body:function(a,b){n(".arcticmodal-close",a.body).unbind("click.arcticmodal").bind("click.arcticmodal",function(){return b.arcticmodal("close"),!1})},init_el:function(d,a){var b=d.data("arcticmodal");if(!b){if(b=a,o++,b.modalID=o,b.overlay.block=n(b.overlay.tpl),b.overlay.block.css(b.overlay.css),b.container.block=n(b.container.tpl),b.body=n(".arcticmodal-container_i2",b.container.block),a.clone?b.body.html(d.clone(!0)):(d.before("
"),b.body.html(d)),q.prepare_body(b,d),b.closeOnOverlayClick&&b.overlay.block.add(b.container.block).click(function(a){h.isEventOut(n(">*",b.body),a)&&d.arcticmodal("close")}),b.container.block.data("arcticmodalParentEl",d),d.data("arcticmodal",b),p=n.merge(p,d),n.proxy(e.show,d)(),"html"==b.type)return d;if(null!=b.ajax.beforeSend){var c=b.ajax.beforeSend;delete b.ajax.beforeSend}if(null!=b.ajax.success){var f=b.ajax.success;delete b.ajax.success}if(null!=b.ajax.error){var g=b.ajax.error;delete b.ajax.error}var j=n.extend(!0,{url:b.url,beforeSend:function(){null==c?b.body.html("
"):c(b,d)},success:function(c){d.trigger("afterLoading"),b.afterLoading(b,d,c),null==f?b.body.html(c):f(b,d,c),q.prepare_body(b,d),d.trigger("afterLoadingOnShow"),b.afterLoadingOnShow(b,d,c)},error:function(){d.trigger("errorLoading"),b.errorLoading(b,d),null==g?(b.body.html(b.errors.tpl),n(".arcticmodal-error",b.body).html(b.errors.ajax_unsuccessful_load),n(".arcticmodal-close",b.body).click(function(){return d.arcticmodal("close"),!1}),b.errors.autoclose_delay&&setTimeout(function(){d.arcticmodal("close")},b.errors.autoclose_delay)):g(b,d)}},b.ajax);b.ajax_request=n.ajax(j),d.data("arcticmodal",b)}},init:function(b){if(b=n.extend(!0,{},a,b),!n.isFunction(this))return this.each(function(){q.init_el(n(this),n.extend(!0,{},b))});if(null==b)return void n.error("jquery.arcticmodal: Uncorrect parameters");if(""==b.type)return void n.error("jquery.arcticmodal: Don't set parameter \"type\"");switch(b.type){case"html":if(""==b.content)return void n.error("jquery.arcticmodal: Don't set parameter \"content\"");var e=b.content;return b.content="",q.init_el(n(e),b);case"ajax":return""==b.url?void n.error("jquery.arcticmodal: Don't set parameter \"url\""):q.init_el(n("
"),b);}}},e={show:function(){var a=q.getParentEl(this);if(!1===a)return void n.error("jquery.arcticmodal: Uncorrect call");var b=a.data("arcticmodal");if(b.overlay.block.hide(),b.container.block.hide(),n("BODY").append(b.overlay.block),n("BODY").append(b.container.block),b.beforeOpen(b,a),a.trigger("beforeOpen"),"hidden"!=b.wrap.css("overflow")){b.wrap.data("arcticmodalOverflow",b.wrap.css("overflow"));var c=b.wrap.outerWidth(!0);b.wrap.css("overflow","hidden");var d=b.wrap.outerWidth(!0);d!=c&&b.wrap.css("marginRight",d-c+"px")}return p.not(a).each(function(){var a=n(this).data("arcticmodal");a.overlay.block.hide()}),q.transition(b.overlay.block,"show",1*")),b.overlay.block.remove(),b.container.block.remove(),a.data("arcticmodal",null),n(".arcticmodal-container").length||(b.wrap.data("arcticmodalOverflow")&&b.wrap.css("overflow",b.wrap.data("arcticmodalOverflow")),b.wrap.css("marginRight",0))}),"ajax"==b.type&&b.ajax_request.abort(),p=p.not(a))})},setDefault:function(b){n.extend(!0,a,b)}};n(function(){a.wrap=n(document.all&&!document.querySelector?"html":"body")}),n(document).bind("keyup.arcticmodal",function(d){var a=p.last();if(a.length){var b=a.data("arcticmodal");b.closeOnEsc&&27===d.keyCode&&a.arcticmodal("close")}}),n.arcticmodal=n.fn.arcticmodal=function(a){return e[a]?e[a].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof a&&a?void n.error("jquery.arcticmodal: Method "+a+" does not exist"):q.init.apply(this,arguments)}}(jQuery)}var debugMode="undefined"!=typeof debugFlatPM&&debugFlatPM,duplicateMode="undefined"!=typeof duplicateFlatPM&&duplicateFlatPM,countMode="undefined"!=typeof countFlatPM&&countFlatPM;document["wri"+"te"]=function(a){let b=document.createElement("div");jQuery(document.currentScript).after(b),flatPM_setHTML(b,a),jQuery(b).contents().unwrap()};function flatPM_sticky(c,d,e=0){function f(){if(null==a){let b=getComputedStyle(g,""),c="";for(let a=0;a=b.top-h?b.top-h{const d=c.split("=");return d[0]===a?decodeURIComponent(d[1]):b},""),c=""==b?void 0:b;return c}function flatPM_testCookie(){let a="test_56445";try{return localStorage.setItem(a,a),localStorage.removeItem(a),!0}catch(a){return!1}}function flatPM_grep(a,b,c){return jQuery.grep(a,(a,d)=>c?d==b:0==(d+1)%b)}function flatPM_random(a,b){return Math.floor(Math.random()*(b-a+1))+a}
");let k=document.querySelector(".flat_pm_modal[data-id-modal=\""+a.ID+"\"]");if(-1===d.indexOf("go"+"oglesyndication")?flatPM_setHTML(k,d):jQuery(k).html(b+d),"px"==a.how.popup.px_s)e.bind(h,()=>{e.scrollTop()>a.how.popup.after&&(e.unbind(h),f.unbind(i),j())}),void 0!==a.how.popup.close_window&&"true"==a.how.popup.close_window&&f.bind(i,()=>{e.unbind(h),f.unbind(i),j()});else{let b=setTimeout(()=>{f.unbind(i),j()},1e3*a.how.popup.after);void 0!==a.how.popup.close_window&&"true"==a.how.popup.close_window&&f.bind(i,()=>{clearTimeout(b),f.unbind(i),j()})}f.on("click",".flat_pm_modal .flat_pm_crs",()=>{jQuery.arcticmodal("close")})}if(void 0!==a.how.outgoing){let b,c="0"==a.how.outgoing.indent?"":" style=\"bottom:"+a.how.outgoing.indent+"px\"",e="true"==a.how.outgoing.cross?"":"",f=jQuery(window),g="scroll.out"+a.ID,h=void 0===flatPM_getCookie("flat_out_"+a.ID+"_mb")||"false"!=flatPM_getCookie("flat_out_"+a.ID+"_mb"),i=document.createElement("div"),j=jQuery("body"),k=()=>{void 0!==a.how.outgoing.cookie&&"false"==a.how.outgoing.cookie&&h&&(jQuery(".flat_pm_out[data-id-out=\""+a.ID+"\"]").addClass("show"),j.on("click",".flat_pm_out[data-id-out=\""+a.ID+"\"] .flat_pm_crs",function(){flatPM_setCookie("flat_out_"+a.ID+"_mb",!1)})),(void 0===a.how.outgoing.cookie||"false"!=a.how.outgoing.cookie)&&jQuery(".flat_pm_out[data-id-out=\""+a.ID+"\"]").addClass("show")};switch(a.how.outgoing.whence){case"1":b="top";break;case"2":b="bottom";break;case"3":b="left";break;case"4":b="right";}jQuery("body > *").eq(0).before("
"+e+"
");let m=document.querySelector(".flat_pm_out[data-id-out=\""+a.ID+"\"]");-1===d.indexOf("go"+"oglesyndication")?flatPM_setHTML(m,d):jQuery(m).html(e+d),"px"==a.how.outgoing.px_s?f.bind(g,()=>{f.scrollTop()>a.how.outgoing.after&&(f.unbind(g),k())}):setTimeout(()=>{k()},1e3*a.how.outgoing.after),j.on("click",".flat_pm_out .flat_pm_crs",function(){jQuery(this).parent().removeClass("show").addClass("closed")})}countMode&&(flat_count["block_"+a.ID]={},flat_count["block_"+a.ID].count=1,flat_count["block_"+a.ID].click=0,flat_count["block_"+a.ID].id=a.ID)}catch(a){console.warn(a)}}function flatPM_start(){let a=flat_pm_arr.length;if(0==a)return flat_pm_arr=[],void jQuery(".flat_pm_start, .flat_pm_end").remove();flat_body=flat_body||jQuery("body"),!flat_counter&&countMode&&(flat_counter=!0,flat_body.on("click","[data-flat-id]",function(){let a=jQuery(this),b=a.attr("data-flat-id");flat_count["block_"+b].click++}),flat_body.on("mouseenter","[data-flat-id] iframe",function(){let a=jQuery(this),b=a.closest("[data-flat-id]").attr("data-flat-id");flat_iframe=b}).on("mouseleave","[data-flat-id] iframe",function(){flat_iframe=-1}),jQuery(window).on("beforeunload",()=>{jQuery.isEmptyObject(flat_count)||jQuery.ajax({async:!1,type:"POST",url:ajaxUrlFlatPM,dataType:"json",data:{action:"flat_pm_ajax",data_me:{method:"flat_pm_block_counter",arr:flat_count}}})}).on("blur",()=>{-1!=flat_iframe&&flat_count["block_"+flat_iframe].click++})),flat_userVars.init();for(let b=0;bflat_userVars.textlen||void 0!==a.chapter_sub&&a.chapter_subflat_userVars.titlelen||void 0!==a.title_sub&&a.title_subc&&cc&&c>d&&(b=flatPM_addDays(b,-1)),b>e||cd||c-1!=flat_userVars.referer.indexOf(a))||void 0!==a.referer.referer_disabled&&-1!=a.referer.referer_disabled.findIndex(a=>-1!=flat_userVars.referer.indexOf(a)))&&(c=!0),c||void 0===a.browser||(void 0===a.browser.browser_enabled||-1!=a.browser.browser_enabled.indexOf(flat_userVars.browser))&&(void 0===a.browser.browser_disabled||-1==a.browser.browser_disabled.indexOf(flat_userVars.browser)))){if(c&&void 0!==a.browser&&void 0!==a.browser.browser_enabled&&-1!=a.browser.browser_enabled.indexOf(flat_userVars.browser)&&(c=!1),!c&&(void 0!==a.geo||void 0!==a.role)&&(""==flat_userVars.ccode||""==flat_userVars.country||""==flat_userVars.city||""==flat_userVars.role)){flat_pm_then.push(a),flatPM_setWrap(a),flat_body.hasClass("flat_pm_block_geo_role")||(flat_body.addClass("flat_pm_block_geo_role"),flatPM_ajax("flat_pm_block_geo_role")),c=!0}c||(flatPM_setWrap(a),flatPM_next(a))}}}let b=jQuery(".flatPM_sticky");b.each(function(){let a=jQuery(this),b=a.data("height")||350,c=a.data("top");a.wrap("
");let d=a.parent()[0];flatPM_sticky(this,d,c)}),debugMode||countMode||jQuery("[data-flat-id]:not([data-id-out]):not([data-id-modal])").contents().unwrap(),flat_pm_arr=[],jQuery(".flat_pm_start, .flat_pm_end").remove()}

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

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

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

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

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

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

Вектор прерываний

Еще одна важная составная часть микропроцессора 8088 – механизм прерываний. Эта компонента системы встроена в микропроцессор, и обеспечивает эффективные методы обработки прерываний.

Когда микропроцессор 8088 получает сигнал о необходимости прерывания, он определяет, какое из усройств требует обслуживания посредством аппаратной процедуры, известной как цикл подтверждения прерывания.

В IBM PC для обслуживания внешних прерываний используется контроллер прерываний 8259 фирмы Intel. Контроллер прерываний программируется так, чтобы выдавать однобайтовое число в ответ на цикл подтверждения прерывания микропроцессора 8088.

Это число, находящееся в диапазоне от 0 до 255, – номер прерывания внешнего усройства, вызвавшего прерывание. В персональной ЭВМ контроллер прерываний обслуживает восемь внешних прерываний, которым соответствуют номера от 8 до 15.

Как только микропроцессор 8088 получает номер прерывания, он должен передать управление соответствующей программе обработки прерывания. Первые 1024 байт памяти микропроцессора 8088

зарезервированы для векторов прерываний. Каждому из 256 возможных прерываний отводится четырехбайтовая область. Прерывание 0 имеет четыре байта по адресам от 0 до 3, прерывание 1 – от 4 до 7, и т.д.

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

Когда возникает прерывание, микропроцессор помещает в стек региср флагов, за которым следуют регистры CS и IP. 8088 использует номер прерывания, чтобы считать указатель на программу обработки

прерывания, и передать ей управление. Теперь уже эта программа отвечает за сохранение регистров, которые она использует, и восстановление их перед возвратом управления в прерванную процедуру. Для возврата из прерывания используется специальная команда IRET. Она извлекает верхние три слова из стека и помещает их в регистры IP, CS и регистр флагов.

Аппаратные прерывания

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

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

Микропроцессор использует записанный в начале памяти вектор

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

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

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

Обработчики прерываний

Когда в реальном режиме выполняется команда INT, управление передается по адресу, который считывается из специального массива, таблицы векторов прерываний, начинающегося в памяти по адресу 0000h:0000h.

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

Команда INT помещает в стек регистр флагов и дальний адрес возврата, поэтому, чтобы завершить обработчик, надо выполнить команды popf и retf или одну команду iret, которая в реальном режиме полностью им аналогична.

; Пример обработчика программного прерыванияint_handler proc far mov ax,0 iretint_handler endp

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

push 0 ; сегментный адрес таблицы ; векторов прерываний pop es ; в ES pushf ; поместить регистр флагов в стек cli ; запретить прерывания; (чтобы не произошло аппаратного прерывания между следующими; командами, обработчик которого теоретически может вызвать INT 87h; в тот момент, когда смещение уже будет записано, а сегментный; адрес еще нет, что приведет к передаче управления; в неопределенную область памяти); поместить дальний адрес обработчика int_handler в таблицу; векторов прерываний, в элемент номер 87h (одно из неиспользуемых прерываний) mov word ptr es:[87h*4], offset int_handler mov word ptr es:[87h*4+2], seg int_handler popf ; восстановить исходное значение флага IF

Теперь команда INT 87h будет вызывать наш обработчик, то есть приводить к записи 0 в регистр АХ.

Перед завершением работы программа должна восстанавливать все старые обработчики прерываний, даже если это были неиспользуемые прерывания типа 87h — автор какой-нибудь другой программы мог подумать точно так же.

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

Для обычных программ DOS предоставляет две системные функции: 25h и 35h — установить и считать адрес обработчика прерывания, которые и рекомендуются к использованию в обычных условиях:

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

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

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

#DE (деление на ноль) — INT 0 — ошибка, возникающая при переполнении и делении на ноль. Как для любой ошибки, адрес возврата указывает на ошибочную команду.

#DB (прерывание трассировки) — INT 1 — ловушка, возникающая после выполнения каждой команды, если флаг TF установлен в 1. Используется отладчиками, действующими в реальном режиме.

#OF (переполнение) — INT 4 — ловушка, возникающая после выполнения команды INTO, если флаг OF установлен.

#ВС (переполнение при BOUND) — INT 5 — уже рассмотренная нами ошибка, возникающая при выполнении команды BOUND.

#UD (недопустимая команда) — INT 6 — ошибка, возникающая при попытке выполнить команду, отсутствующую на данном процессоре.

#NM (сопроцессор отсутствует) — INT 7 — ошибка, возникающая при попытке выполнить команду FPU, если FPU отсутствует.

https://www.youtube.com/watch?v=fQkphiPFv2k

Прерывания от внешних устройств, или аппаратные прерывания — это то, что понимается под термином «прерывание». Внешние устройства (клавиатура, дисковод, таймер, звуковая карта и т.д.

) подают сигнал, по которому процессор прерывает выполнение программы и передает управление на обработчик прерывания.

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

Рассмотрим их кратко в порядке убывания приоритетов (прерывание имеет более высокий приоритет, и это означает, что, пока не завершился его обработчик, прерывания с низкими приоритетами будут ждать своей очереди).

IRQ0 (INT 8) — прерывание системного таймера. Это прерывание вызывается 18,2 раза в секунду. Стандартный обработчик этого прерывания вызывает INT 1Ch при каждом вызове, так что, если программе необходимо только регулярно получать управление, а не перепрограммировать таймер, рекомендуется использовать прерывание 1Ch.

IRQ1 (INT 9) — прерывание клавиатуры. Это прерывание вызывается при каждом нажатии и отпускании клавиши на клавиатуре. Стандартный обработчик этого прерывания выполняет довольно много функций, начиная с перезагрузки по Ctrl-Alt-Del и заканчивая помещением кода клавиши в буфер клавиатуры BIOS.

IRQ2 — к этому входу на первом контроллере прерываний подключены аппаратные прерывания IRQ8 – IRQ15, но многие BIOS перенаправляют IRQ9 на INT 0Ah.

IRQ8 (INT 70h) — прерывание часов реального времени. Это прерывание вызывается часами реального времени при срабатывании будильника и если они установлены на генерацию периодического прерывания (в последнем случае IRQ8 вызывается 1024 раза в секунду).

IRQ9 (INT 0Ah или INT 71h) — прерывание обратного хода луча. Вызывается некоторыми видеоадаптерами при обратном ходе луча. Часто используется дополнительными устройствами (например, звуковыми картами, SCSI-адаптерами и т.д.).

IRQ10 (INT 72h) — используется дополнительными устройствами.

IRQ11 (INT 73h) — используется дополнительными устройствами.

IRQ12 (INT 74h) — мышь на системах PS используется дополнительными устройствами.

IRQ13 (INT 02h или INT 75h) — ошибка математического сопроцессора. По умолчанию это прерывание отключено как на FPU, так и на контроллере прерываний.

IRQ14 (INT 76h) — прерывание первого IDE-контроллера «операция завершена».

IRQ15 (INT 77h) — прерывание второго IDE-контроллера «операция завершена».

IRQ3 (INT 0Bh) — прерывание последовательного порта COM2 вызывается, если порт COM2 получил данные.

IRQ4 (INT 0Ch) — прерывание последовательного порта СОМ1 вызывается, если порт СОМ1 получил данные.

IRQ5 (INT 0Dh) — прерывание LPT2 используется дополнительными устройствами.

IRQ6 (INT 0Eh) — прерывание дисковода «операция завершена».

IRQ7 (INT 0Fh) — прерывание LPT1 используется дополнительными устройствами.

Самые полезные для программ аппаратные прерывания — прерывания системного таймера и клавиатуры.

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

Следует обязательно вызвать предыдущий обработчик, передав ему управление следующим образом (если его адрес сохранен в переменной old_handler, как в предыдущих примерах):

pushf call old_handler

Эти две команды выполняют действие, аналогичное команде INT (сохранить флаги в стеке и передать управление подобно команде call), так что, когда обработчик завершится командой IRET, управление вернется в нашу программу. Так удобно вызывать предыдущий обработчик в начале собственного. Другой способ — простая команда jmp:

jmp cs:old_handler

приводит к тому, что, когда старый обработчик выполнит команду IRET, управление сразу же перейдет к прерванной программе. Этот способ применяют, если нужно, чтобы сначала отработал новый обработчик, а потом он передал бы управление старому.