AVR. Учебный Курс. Управляемый вектор прерывания. Вложенные прерывания

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

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

Или, например, USART. Нам запросто может потребоваться, чтобы в зависимости от режима на прерывание по приходу байта выполнялся разный код. В одном режиме — выдача приветствия, в другом посыл матом в баню. В третьем удар в голову. А вектор один.

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

То есть в свитче вида:

1 2 3 4 5 6 7 switch (x) { 1 : Действие 1 2 : Действие 2 3 : Действие 3 4 : Действие 4 }

switch(x) { 1: Действие 1 2: Действие 2 3: Действие 3 4: Действие 4 }

Будет последовательное сравнение х вначале с 1, потом с 2, потом с 3 и так до перебора всех вариантов. А в таком случае реакция на Действие 1 будет быстрей чем реакция на Действие 4. Особо важно это при расчете точных временных интервалов на таймере.

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

В памяти создаем переменные под плавающий вектор:

Timer0_Vect_L: .byte 1 ; Два байта адреса, старший и младший Timer0_Vect_H: .byte 1

Подготовка к ожиданию прерывания проста, мы берем и загружаем в нашу переменную нужным адресом

CLI ; Критическая часть. Прерывания OFF LDI R16,low(Timer_01) ; Берем адрес и сохраняем STS Timer0_Vect_L,R16 ; его в ячейку памяти. LDI R16,High(Timer_01) ; Аналогично, но уже со старшим вектором STS Timer0_Vect_H,R16 SEI ; Прерывания ON

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

А обработчик получается вида:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 ;============================= ; Вход в прерывание по переполнению от Timer0 ;============================= TIMER_0: PUSH ZL ; сохраняем индексный регистр в стек PUSH ZH ; т.к. мы его используем PUSH R2 ; сохраняем R2, т.к. мы его тоже портим IN R2,SREG ; Извлекем и сохраняем флаговый регистр PUSH R2 ; Если не сделать это, то 100% получим глюки LDS ZL,Timer0_Vect_L ; загружаем адрес нового вектора LDS ZH,Timer0_Vect_H ; оба байта. CLR R2 ; Очищаем R2 OR R2,ZL ; Проверяем вектор на ноль. Иначе схватим аналог OR R2,ZH ; reset"a. Проверка идет через операцию OR BREQ Exit_Tm0 ; с накоплением результата в R2 ; так мы не портим содержимое Z и нам не придется; загружать его снова IJMP ; Уходим по новому вектору; Выход из прерывания. Exit_Tm0: POP R2 ; Достаем и восстанавливаем регистр флагов OUT SREG,R2 POP R2 ; восстанавливаем R2 POP ZH ; Восстанавливаем Z POP ZL RETI ; Дополнительный вектор 1 Timer_01: NOP ; Это наши новые вектора NOP ; тут мы можем творить что угодно NOP ; желательно недолго - в прерывании же NOP ; как никак. Если используем какие другие NOP ; регистры, то их тоже в стеке сохраняем RJMP Exit_Tm0 ; Это переход на выход из прерывания; специально сделал через RJMP чтобы; Дополнительный вектор 2 ; сэкономить десяток байт на коде возврата:))) Timer_02: NOP NOP NOP NOP NOP RJMP Exit_Tm0 ; Дополнительный вектор 3 Timer_03: NOP NOP NOP NOP NOP RJMP Exit_Tm0

;============================= ; Вход в прерывание по переполнению от Timer0 ;============================= TIMER_0: PUSH ZL ; сохраняем индексный регистр в стек PUSH ZH ; т.к. мы его используем PUSH R2 ; сохраняем R2, т.к. мы его тоже портим IN R2,SREG ; Извлекем и сохраняем флаговый регистр PUSH R2 ; Если не сделать это, то 100% получим глюки LDS ZL,Timer0_Vect_L ; загружаем адрес нового вектора LDS ZH,Timer0_Vect_H ; оба байта. CLR R2 ; Очищаем R2 OR R2,ZL ; Проверяем вектор на ноль. Иначе схватим аналог OR R2,ZH ; reset"a. Проверка идет через операцию OR BREQ Exit_Tm0 ; с накоплением результата в R2 ; так мы не портим содержимое Z и нам не придется; загружать его снова IJMP ; Уходим по новому вектору; Выход из прерывания. Exit_Tm0: POP R2 ; Достаем и восстанавливаем регистр флагов OUT SREG,R2 POP R2 ; восстанавливаем R2 POP ZH ; Восстанавливаем Z POP ZL RETI ; Дополнительный вектор 1 Timer_01: NOP ; Это наши новые вектора NOP ; тут мы можем творить что угодно NOP ; желательно недолго - в прерывании же NOP ; как никак. Если используем какие другие NOP ; регистры, то их тоже в стеке сохраняем RJMP Exit_Tm0 ; Это переход на выход из прерывания; специально сделал через RJMP чтобы; Дополнительный вектор 2 ; сэкономить десяток байт на коде возврата:))) Timer_02: NOP NOP NOP NOP NOP RJMP Exit_Tm0 ; Дополнительный вектор 3 Timer_03: NOP NOP NOP NOP NOP RJMP Exit_Tm0

Реализация для RTOS
Но что делать если у нас программа построена так, что весь код вращается по цепочкам задач через диспетчер RTOS? Просчитать в уме как эти цепочки выполняются относительно друг друга очень сложно. И каждая из них может попытаться завладеть таймером (конечно не самовольно, с нашей подачи, мы же программу пишем, но отследить по времени как все будет сложно).
В современных больших осях на этот случай есть механизм Mutual exclusion — mutex. Т.е. это своего рода флаг занятости. Если какой нибудь процесс общается, например, с UART то другой процесс туда байт сунуть не смеет и покорно ждет пока первый процесс освободит UART, о чем просемафорит флажок.

В моей механизмов взаимоисключений нет, но их можно реализовать. По крайней мере сделать некоторое минимальное подобие. Полноценную реализацию всего этого барахла я делать не хочу, т.к. моей целью является удержания размера ядра на уровне 500-800 байт.
Проще всего зарезервировать в памяти еще один байт — переменную занятости. И когда один процесс захватывает ресурс, то в эту переменную он записывает время когда ориентировочно он его освободит. Время идет в тиках системного таймера которое у меня 1ms.
Если какой либо другой процесс попытается обратиться к этому же аппаратному ресурсу, то он вначале посмотрит на состояние его занятости, считает время в течении которого будет занято и уйдет покурить на этот период — загрузит сам себя в очередь по таймеру. Там снова проверит и так далее. Это простейший вариант.

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

Решение проблемы — добавление еще одной очередной цепочки, на этот раз уже на доступ к ресурсу. Чтобы он не простаивал вообще. Т.е. один выскочил, тут же второй, третий и так далее пока все процессы не справят свою нужду в какой нибудь там USART.
Недостаток очевиден — еще одна очередь это дополнительная память, дополнительный код, дополнительное время. Можно, конечно, извратиться и на очередь к вектору натравить код диспетчера основной цепи. Но тут надо все внимательно отлаживать, ведь вызываться он будет по прерыванию! Да и громоздко, требуется лишь тогда, когда у нас много желающих.

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

Разумеется таймер тут приведен для примера, большую часть задач можно решить системным таймером RTOS, но если нужна вдруг меньшая дискретность или высокая скорость реакции на событие (а не пока главный конвеер дотащит задачу до исполнения), то механим управляемых прерываний, ИМХО, то что доктор прописал.

Механизм приоритетов (МП) показывает какие устройства нужно обслужить первыми. МП решает следующие задачи:

    Фиксирует приоритет любой выполняемой процессором программы.

    Идентифицирует ЗП от ВУ с максимальным приоритетом.

    Разрешает прерывание программы при возникновении запроса с большим приоритетом.

Прерывание программы обслуживания прерываний называется вложенным прерыванием.

Рис. 6.4 Пример работы ЦП в режиме вложенных прерываний.

На рис 6.4 показан пример вложенного прерывания:

    До t 1 нет ЗП

    t 1 → ЗП от ВУ4

    t 2 → ЗП от ВУ3

    t 3 → ЗП от ВУ2

    t 4 → закончено обслуживание ВУ2

    t 5 → ЗП от ВУ1

    t 6 → закончено обслуживание ВУ1

    t 7 → закончено обслуживание ВУ3

    t 8 → закончено обслуживание ВУ4

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

Можно уменьшить частоту ЗП путем включения буферных ЗУ.

При присвоении приоритетов ВУ учитываются следующие условия:

    Чем больше быстродействие устройства, тем выше приоритет ему присваивается.

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

    В ЭВМ семейства Macintosh приоритет программы указывается во втором слове ВП.

    В ЭВМ семейства IBM PC приоритет программы устанавливается с помощью специальной БИС (большая интегральная схема) – программируемый контроллер прерываний.

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

Реализация многоуровневых ВП в ЭВМ семейства IBM .

Для Реализация многоуровневых ВП в ЭВМ семейства IBM применена БИСIntel 8259A .

Технические характеристики бис Intel 8259a.

    Число уровней ЗП = 8.

    Количество уровней можно расширить до 64 за счет каскадного включения микросхем

    Режим обслуживания ЗП, уровни приоритетов, АВП устанавливаются программным путем.

    Техническая реализация приоритетных векторных прерываний в ЭВМ с изолированными магистральными каналами обмена данными (семейство IBM AT ): режимы работы программируемого контроллера прерываний (ПКП),

схема подключения ПКП к системной магистрали,

Схема включения ПКП к системной шине ВУ.

Рис. 6.7 Схема включения ПКП к системной шине и ВУ.

Назначение выводов БИС:

    D 7- D 0 – выводы ШД, служат для приема управляющее информации от ЦП и передачи статусной информации в ЦП.

    A 0 – адресный вход, адресация внутренних регистров контроллера (2 адреса).

    ~ CS (chip select ) - выбор кристалла, разрешает или запрещает связь контроллера с системной шиной.

    • ~CS = 0 – есть связь, ~CS = 1 – нет связи.

Первый ПКП использует адреса – 20 h , 21 h .

Второй ПКП использует адреса – A 0 h , A 1 h .

    ~ RD , ~ WR – ввод, вывод (сигналы ШУ), соединяются с линиями магистрали~ IOR и ~ IOW .

    INT (выход) – сигнал ЗП в ЦП.

    ~INTA (interrupt acknowledge) – сигнал РП от ЦП.

    CAS 2, CAS 1, CAS 0 – шина каскадирования. Для ведущего контроллера прерывания эти линии являются выходами, а для ведомого – входами.

    ~ SP /~ EN – указывает ведущий (1) или ведомый (0) ПКП.

    IR 0... IR 7 – входы запросов прерывания от ВУ.

функциональный состав и программная модель ПКП.

Здесь мы разберем такие важные темы, как: обработка прерываний, векторы прерываний, программные прерывания, IRQ , в общем поговорим на темы прерывания.

Идея прерывания была предложена в середине 50-х годов и основная цель введения прерываний – реализация синхронного режима работы и реализация параллельной работы отдельных устройств ЭВМ.

Прерывания и обработка прерываний зависят от типа ЭВМ, поэтому их реализацию относят к машинно-зависимым свойствам операционных систем.

Прерывание (interrupt) – это сигнал, заставляющий ЭВМ менять обычный порядок выполнения команд процессором.

Возникновение подобных сигналов обусловлено такими событиями , как:

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

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

Например прерывание с номером 9 — прерывание от клавиатуры, которое генерируется при нажатии и при отжатии клавиши. Используется для чтения данных с клавиатуры. Обозначается в ОС как IRQ 1, где IRQ – обозначение прерывания, а 1 – приоритет прерывания. Данные о запросах на прерывание можно проанализировать в диспетчере устройств:

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

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

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

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

Для микропроцессора требуется простой способ определения местоположения программы обработки прерывания и это осуществляется путем использования таблицы векторов прерываний .

Таблица векторов прерываний занимает первый килобайт оперативной памяти — адреса от 0000:0000 до 0000:03FF. Таблица состоит из 256 элементов — FAR-адресов обработчиков прерываний. Эти элементы называются векторами прерываний . В первом слове элемента таблицы записано смещение, а во втором — адрес сегмента обработчика прерывания. Векторами являются просто полные адреса памяти программы (в сегментированной форме), которая должна быть активизирована в случае возникновения прерывания.

Прерыванию с номером 0 соответствует адрес 0000:0000, прерыванию с номером 1 — 0000:0004 и т.д. Адрес такой состоит из пары 2-байтовых слов, поэтому каждый из векторов занимает четыре байта.

Можно просмотреть таблицу векторов прерываний в компьютере, если воспользоваться программой DEBUG. Используйте команду D для вывода содержимого начала памяти: D 0:0. Программа DEBUG покажет вам первые 128 байтов или 32 вектора, которые могут иметь вид наподобие следующего:

0000:0000 E8 4E 9A 01 00 00 00 00-C3 E2 00 F0 00 00 00 00
0000:0010 F0 01 70 00 54 FF 00 F0-05 18 00 F0 05 18 00 F0
0000:0020 2C 08 51 17 D0 0A 51 17-AD 08 54 08 E8 05 01 2F
0000:0030 FA 05 01 2F 05 18 00 F0-57 EF 00 F0 F0 01 70 00
0000:0040 90 13 C7 13 4D F8 00 F0-41 F8 00 F0 3E 0A 51 17
0000:0050 5C 00 B7 25 59 F8 00 F0-E2 0A 51 17 9C 00 B7 25
0000:0060 00 00 00 F6 8E 00 DE 09-6E FE 00 F0 F2 00 7B 09
0000:0070 27 08 51 17 A4 F0 00 F0-22 05 00 00 00 00 00 F0

Векторы хранятся как «слова наоборот»: сначала смещение, а потом сегмент. Например, первые четыре байта, которые программа DEBUG показала выше (E8 4E 9A 01) можно преобразовать в сегментированный адрес 019A:4EE8.

Можно встретить три вида адресов в таблице векторов . Это могут быть адреса, указывающие на ROM-BIOS, которые можно идентифицировать шестнадцатеричной цифрой F, которая предшествует номеру сегмента. Это могут быть адреса, которые указывают на главную память (как в примере: 019A:4EE8). Эти адреса могут указывать на подпрограммы ДОС или на резидентную программу (например, SideKick или Prokey), либо они могут указывать на саму программу DEBUG (поскольку DEBUG должна временно управлять прерыванием). Также векторы могут состоять из одних нулей, когда прерывание с данным номером не обрабатывается в текущий момент.

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

Ниже приведено назначение некоторых векторов:

Описание
0 Ошибка деления. Вызывается автоматически после выполнения команд DIV или IDIV, если в результате деления происходит переполнение (например, при делении на 0).
2 Аппаратное немаскируемое прерывание. Это прерывание может использоваться по-разному в разных машинах. Обычно вырабатывается при ошибке четности в оперативной памяти и при запросе прерывания от сопроцессора.
5 Печать копии экрана. Генерируется при нажатии на клавиатуре клавиши PrtScr. Обычно используется для печати образа экрана.
8 IRQ0 — прерывание интервального таймера, возникает 18,2 раза в секунду.
9 IRQ1 — прерывание от клавиатуры. Генерируется при нажатии и при отжатии клавиши. Используется для чтения данных от клавиатуры.
A IRQ2 — используется для каскадирования аппаратных прерываний в машинах класса AT
B IRQ3 — прерывание асинхронного порта COM2.
C IRQ4 — прерывание асинхронного порта COM1.
D IRQ5 — прерывание от контроллера жесткого диска для XT.
E IRQ6 — прерывание генерируется контроллером флоппи-диска после завершения операции.
F IRQ7 — прерывание принтера. Генерируется принтером, когда он готов к выполнению очередной операции. Многие адаптеры принтера не используют это прерывание.
10 Обслуживание видеоадаптера.
11 Определение конфигурации устройств в системе.
12 Определение размера оперативной памяти в системе.
13 Обслуживание дисковой системы.
14 Последовательный ввод/вывод.
1A Обслуживание часов.
1B Обработчик прерывания Ctrl-Break.
70 IRQ8 — прерывание от часов реального времени.
71 IRQ9 — прерывание от контроллера EGA.
75 IRQ13 — прерывание от математического сопроцессора.
76 IRQ14 — прерывание от контроллера жесткого диска.
77 IRQ15 — зарезервировано.

IRQ0 — IRQ15 — это аппаратные прерывания.

Механизм обработки прерываний

При обработке каждого прерывания должна выполняться следующая последовательность действий:

  • Восприятие запроса на прерывание: прием сигнала и идентификация прерывания.
  • Запоминание состояния прерванного процесса: определяется значением счетчика команд (адресом следующей команды) и содержимым регистров процессора.
  • Передача управления прерывающей программе (в счетчик команд заносится начальный адрес подпрограммы обработки прерываний, а в соответствующие регистры – информация из слова состояния процессора).
  • Обработка прерывания.
  • Восстановление прерванного процесса и возврат в прерванную программу.

Главные функции механизма прерывания:

  1. распознавание или классификация прерываний.
  2. передача управления соответственно обработчику прерываний.
  3. корректное возвращение к прерванной программе (перед передачей управления обработчику прерываний содержимое регистров процессора запоминается либо в памяти с прямым доступом либо в системном стеке).

Типы прерываний

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

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

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

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

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

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

Маскируемые и немаскируемые внешние прерывания

Существуют два специальных внешних сигнала среди входных сигналов процессора, при помощи которых можно прервать выполнение текущей программы и тем самым переключить работу центрального процессора. Это сигналы NMI (Non Mascable Interrupt, немаскируемое прерывани ) INTR (interrupt request, запрос на прерывание ).

Соответственно внешние прерывания подразделяются на два вида: немаскируемые и маскируемые.

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

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

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

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

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

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

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

Механизм программных прерываний был специально введен для того, чтобы:

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

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

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

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

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

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

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

В случае о прерывании самой программы обработки прерывания говорят о вложенном прерывании . Уровни приоритетов обозначаются сокращенно IRQ0 — IRQ15 или IRQ0 – IRQ23 (в зависимости от микросхемой реализации).

Пpepывaнию вpeмeни cутoк дан мaкcимaльный пpиopитeт, пocкoльку ecли oнo будет пocтoяннo тepятьcя, то будут нeвepными пoкaзaния cиcтeмныx чacoв. Пpepывaниe от клaвиaтуpы вызывaeтcя при нaжaтии или oтпуcкaнии клавиши; oнo вызывaeт цепь coбытий, кoтopaя oбычнo зaкaнчивaeтcя тем, что код клавиши пoмeщaeтcя в буфep клaвиaтуpы (oткудa он зaтeм мoжeт быть пoлучeн пpoгpaммными пpepывaниями).

Ну и наконец реализация механизма обработки прерываний

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

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

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

Сохранение и восстановление состояния процессора и содержимого регистров называют операцией контекстного переключения .

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

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

  • при программном прерывании отражается тип вызвавшего его условия, например деление на нуль.
  • при прерывании по вводу-выводу заносится номер канала, вызвавший прерывание.

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

Это поле используется, чтобы не допустить наступления прерываний определенного типа, пока первое из них не будет обработано. В MASK каждый бит соответствует некоторому классу прерываний. Если какой-то бит установлен в 1, то прерывания соответствующего класса разрешены, если в 0, то запрещены. В последнем случае говорят, что они маскированы (их также называют запрещенными или закрытыми ). Однако маскированные прерывания не теряются, потому что сигнал, вызвавший прерывание, сохраняется аппаратурой. Временно задержанное таким способом прерывание называется отложенным . Когда (вследствие того, что значение MASK сброшено) прерывания соответствующего класса вновь разрешаются, сигнал опознается и происходит прерывание.

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

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

Механизм прерывания обеспечивается соответствующими аппаратно-программными средствами компьютера.

Классификация прерываний представлена на рис. 7.1 .


Рис. 7.1.

Запросы аппаратных прерываний возникают асинхронно по отношению к работе микропроцессора и связаны с работой внешних устройств.

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

Для запросов маскируемых прерываний используется вход INT микропроцессора. Обработка запроса прерывания по данному входу может быть заблокирована сбросом бита IF в регистре флагов микропроцессора.

Программные прерывания , строго говоря, называются исключениями или особыми случаями. Они связаны с особыми ситуациями, возникающими при выполнении программы (отсутствие страницы в оперативной памяти, нарушение защиты, переполнение ), то есть с теми ситуациями, которые программист предвидеть не может, либо с наличием в программе специальной команды INT n, которая используется программистом для вызова функций операционной системы либо BIOS , поддерживающих работу с внешними устройствами. В дальнейшем при обсуждении работы системы прерываний мы будем употреблять единый термин " прерывание " для аппаратных прерываний и исключений, если это не оговорено особо.

Программные прерывания делятся на следующие типы.

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

Ловушка - особый случай, который обнаруживается после окончания выполнения команды (например, наличие в программе команды INT n или установленный флаг TF в регистре флагов ). После обработки этого прерывания выполнение программы продолжается со следующей команды.

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

Порядок обработки прерываний

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

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

Обработка запросов прерываний состоит из:

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

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

Тип прерывания для программных прерываний вводится изнутри микропроцессора; например, прерывание по отсутствию страницы в памяти имеет тип 14. Для прерываний, вызываемых командой INT n, тип содержится в самой команде. Для маскируемых аппаратных прерываний тип вводится из контроллера приоритетных прерываний по шине данных . Немаскируемому прерыванию назначен тип 2.

Всего микропроцессор различает 256 типов прерываний . Таким образом, все они могут быть закодированы в 1 байте.

"Рефлекторные" действия микропроцессора по обработке запроса прерывания выполняются аппаратными средствами МП и включают в себя:

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

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

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


Рис. 7.3.

Содержимое регистра IDTr не сохраняется в сегментах TSS и не изменяется при переключении задачи. Программы не могут обратиться к IDT , так как единственный бит TI индикатора таблицы в селекторе сегмента обеспечивает выбор только между таблицами GDT и LDT .

Максимальный предел таблицы дескрипторов прерываний составляет 256*8 - 1 = 2047.

Можно определить предел меньшим, но это не рекомендуется. Если происходит обращение к дескриптору вне пределов IDT , процессор переходит в режим отключения до получения сигнала по входу NMI или сброса.

В IDT могут храниться только дескрипторы следующих типов:

  • шлюз ловушки ,
  • шлюз прерывания, шлюз задачи.

3. Примеры прерываний

3.1. Прерывание по изменению сигнала на портах ввода/вывода (пример в PROTEUS)

3.2. Внешнее прерывание INT (пример в PROTEUS)

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

Итак, какие же существуют прерывания, в микроконтроллере dsPIC33FJ32GP204?

А прерываний в микроконтроллере достаточно много. И все перечислять в данной лекции не имеет смысла. Ведь первое на что мы обращаем внимание, выбирая микроконтроллер – это какие модули в нём есть, а уж потом смотрим, какие прерывания он имеет. Так же и мы будем изучать всё в такой же последовательности. По мере изучения микроконтроллера будем разбирать прерывания различных модулей.

В общем, нужно знать, что для микроконтроллера существует таблица векторов прерываний, которая располагается в памяти программ. Каждое прерывание имеет свой вектор, т. е. адрес начала подпрограммы обработки конкретного прерывания (ISR). Также имеется альтернативная таблица векторов прерывания. При помощи одного бита ALTINV можно указать, из какой таблицы нужно брать адрес подпрограммы прерывания. Т. е. есть альтернатива основной таблице. Но нужно помнить, что бит ALTINV используется сразу для всей таблицы. И если установлена альтернативная таблица, то абсолютно все адреса подпрограммы обработки прерываний, основная программа будет брать из альтернативной таблицы. Альтернативная таблица очень полезна для режима отладки, так как позволяет один раз записать в контроллер программу и при помощи, к примеру, кнопки выбирать между двумя алгоритмами работы программы.

В dsPIC расширили диапазон приоритетов прерываний. Если в PIC18 их было только 2, то в dsPIC уже 8 уровней. И когда я разрабатывал модуль для связи по каналу GPRS, мне это очень пригодилось, так как в микроконтроллере использовалась практически вся имеющаяся на борту периферия, а количество обрабатываемых прерывания было около двадцати.

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

Итак, что же нужно от программиста для работы с прерыванием:

1. Ему нужно разрешить какое-нибудь конкретное прерывание.

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

3. Главное не забыть в подпрограмме прерывания сбросить флаг этого самого прерывания.

Для микроконтроллера dsPIC компилятор С30 предусматривает следующее правило описание подпрограмм обработки прерывания, например для прерывания INT1:

void __ attribute __((__ interrupt __)) _ INT 1 Interrupt ()

Главное нужно обратить внимание на концовку текста заголовка подпрограммы: _ INT 1 Interrupt () – это единственный параметр который обязательно нужно изменять для различных видов прерываний. Для каждого прерывания из таблицы векторов прерывания имеется своё обозначение данного параметра. И тут уж придётся открыть файл помощи к компилятору С30, в частности раздел (у меня это…) «dsPIC30F DSCs (SMPS) Interrupt Vectors» и посмотреть таблицу какое прерывание каким образом должно вызываться. В данной статье приводить эту таблицу не считаю целесообразным, так как у того, кого есть компилятор С30, у того должен быть вложен файл помощи hlpMPLABC30.chm (лично у меня именно так называется). А тем, у кого нет данного компилятора, то и таблица им незачем J. Ладно, отвлеклись…

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

Например, описание прерывания будет выглядеть следующим образом:

void __attribute__((interrupt, auto_psv)) _INT1Interrupt()

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

_ INT 1 Interrupt () -> _ AltINT 1 Interrupt ()

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

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

3.1. Прерывание по изменению сигнала на портах ввода/вывода (CN )

Одно из самых простых для понимания прерывание – это прерывание по изменению состояние на входе CN. В микроконтроллере dsPIC33FJ32GP204 полно таких входов, так что, думаю, это количество удовлетворит любые запросы. Не важно, с какого на какое изменяется состояние на этих каналах («1» -> «0» или «0» -> «1»), это изменение, если оно разрешено, вызовет установление флага «CNIF». Для того, чтобы активизировать прерывание по изменению сигнала, нужно проделать следующие действия:

1. Настроить необходимые каналы CN на вход (с помощью регистра TRISх).

2. Включить контроль изменения сигнала на соответствующем входе CN. Для этого есть аж 2 регистра CNEN1 и CNEN2. Можно как целиком обращаться к каждому регистру для настройки, либо обращаться к соответствующим битам (например _CN15IE=1; _CN6IE=1;)

3. Если необходимо, то включить подтягивающие резисторы. Для этого есть также два регистра CNPU1 и CNPU2. Можно и по отдельности, к примеру _CN15PUE=1; _CN6PUE=1;

4. Разрешить прерывание по изменению сигнала на CN (_ CNIE =1 )

5. Теперь как только изменится сигнал на контролируемых выводах CN, установится флаг прерывания _CNIF. И программа заходит в функцию обработки прерывания. Компилятор С30 для прерывания по изменению сигнала на CN предусмотрел следующее описание функции:

void __ attribute __((__ interrupt __)) _ CNInterrupt ()

Именно здесь происходит обработка данного прерывания (смотреть пример)

6. В подпрограмме обработки прерывания не забываем сбросить флаг прерывания.

Для ознакомления с данным видом прерывания рассмотрим следующий пример. Имеется двигатель и 4 датчика. Когда всё в порядке, то двигатель должен вращаться против часовой стрелки. Но как только состояние датчика изменится (аварийный режим) – двигатель должен немедленно начать вращаться в обратную сторону (по часовой стрелки). А через заданный промежуток времени вновь начать вращаться против часовой стрелки.

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

Собираем схему в PROTEUS

А теперь составляем программу для выполнения поставленной задачи

# include " p 33 Fxxxx . h "

_ FOSCSEL (0 x 02);

_ FOSC (0 xE 2);

char state ; // переменная хранит направление вращения двигателя

// "1" - аварийный режим, "0" - нормальный режим

void init (void );

void init (void )

{ _ CN 8 PUE =1; //включаем подтягивающий резистор на вход CN8 (RС0)

_ CN 10 PUE =1; //включаем подтягивающий резистор на вход CN10 (RС2)

_ CN 17 PUE =1; //включаем подтягивающий резистор на вход CN17 (RС7)

_ CN 19 PUE =1; //включаем подтягивающий резистор на вход CN19 (RС9)

AD 1 PCFGL =0 xffff ;

PORTC =0; // инициализируем порт С

LATC =0;

TRISC =0 xFFFF ; // настраиваем порт C как вход

PORTC =0 xFFFF ;

PORTA =0; // инициализируем порт A

LATA =0;

TRISA =0; // Все вывод настраиваем на выход,

PORTA =0;

_ CN 8 IE =1; // включаем контроль изменения сигнала на

_ CN 10 IE =1; // соответствующих выводах

_ CN 17 IE =1; // если на них изменится сигнал, то

_ CN 19 IE =1; // установится флаг прерывания _CNIF

_ CNIE =1; // разрешаем прерывание INT1

void __ attribute __((__ interrupt __)) _ CNInterrupt ()

state =1; // это аварийный режим

_ CNIF =0; // сбрасываем флаг прерывания по изменению сигнала

void main (void )

{ static long int i ;

init ();

state =0; // считаем, что режим работы при старте нормальный

while (1) // запускаем бесконечный цикл

{ if (state ) // если режим аварийный, то

{ _ CNIE =0; // запрещаем прерывание по изменению сигнала

_ RA 8=0;

_ RA 0=1; // в аварийном режиме

for (i =0; i <210000; i ++) //обеспечиваем задержку

state =0; // активизируем нормальный режим работы двигателя

_ CNIE =1; // разрешаем прерывания CN

{ _ RA 8=1; // в нормальном режиме

_ RA 0=0; // обеспечиваем направления вращения двигателя

} // while(1)

3.2. Внешнее прерывание INT

Если вам необходимо, чтобы ваш микроконтроллер незамедлительно реагировал на действия внешних устройств, то прерывание INT – это то, что вам нужно. В микроконтроллере dsPIC33FJ32GP204 есть три канала INT (INT0, INT1, INT2). Т. е. вы можете для каждого из трёх внешних устройств предусмотреть своё прерывание. Данное прерывание хорошо тем, что оно может вывести контроллер из режима SLEEP. К тому же вам не нужно непрерывно сканировать данные входы, что отнимало бы ресурсы микроконтроллера.

Прерывание INT возникает при изменении сигнала на входе либо «0» -> «1» либо «1» -> «0». Это зависит от состояния бита _INT1EP . Если _INT1EP =1, то по заднему фронту; если _INT1EP =0, то по переднему фронту (например _INT1EP =1 – прерывание INT0 происходит по заднему фронту).

Что касается приоритета, то прерывание INT0 имеет наивысший приоритет, причём в таблице прерываний он является самым первым. Ниже располагаются прерывания INT1 и INT2.

В принципе это и все особенности данного прерывания. Всё остальное как и у других прерываний, т. е. нужно разрешить соответствующее внешнее прерывание. И если произойдёт прерывание, то оно установит соответствующий флаг _INTхIF (например _INT1IF). Не забываем его сбросить. В общем, всё проще простого.

Переходим к примеру

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

Составляем схему в PROTEUS, нам понадобится дополнительно к микроконтроллеру ещё кнопка, пищалка и светодиод.

Думаю на рисунке всё предельно ясно. Перейдём к программе.

#include "p33Fxxxx. h"

// Устанавливаем биты настройки генератора (HS)

_ FOSCSEL (0 x 02);

_ FOSC (0 xE 2);

char state; // "1" - включить звонок, "0" - звонок отключён

void init (void); //объявляем подпрограмму инициализации

// ** подпрограмма инициализация **

void init (void)

{ _CN4PUE=1; //включаем подтягивающий резистор на вход CN4 (RB0)

AD1PCFGL=0xffff; // все выводы как цифровые I/O

PORTB=0x00; // инициализируем порт В

LATB=0;

TRISB=0x0001; // вывод RB0 настраиваем на вход

PORTB=0x00;

// инициализации порта C.

PORTC=0;

LATC=0;

TRISC=0; //устанавливаем все выводы как выход

PORTC=0;

RPINR0=0x0000; // сигнал INT1 настраиваем, чтобы снимать с вывода RP0 (RB0) (данная

// команда не для всех dsPIC

_INT1EP=1; // прерывание INT1 происходит по заднему фронту

_INT1IE=1; // разрешаем прерывание INT1

// ******* подпрограмма обработки прерывания *

void __ attribute __((__ interrupt __)) _ INT 1 Interrupt ()

state =1; // необходимо включить звонок

_ INT 1 IF =0; // сбрасываем флаг прерывания INT1

// **** Точка входа в программу *

void main (void )

{ static long int i ; //объявляем переменную, чтобы организовать задержку

init(); // вызываем подпрограмму инициализации

state=0; // вначале звонок должен быть выключен

while(1) // запускаем бесконечный цикл

{ if (state) // если state равен "1", то

{ _RC0=1; // зажигаем светодиод

_RC2=1; // включаем пищалку

for(i=0;i<160000;i++) // задержка, обеспечивает определённое время звучания

state=0; // указываем, что пищалку нужно отключить

else // иначе, если state равна "0", то

{ _RC0=0; // гасим светодиод

_RC2=0; // прекращаем звуковой сигнал

} // while(1)

Аналогично работать и с прерываниями INT0 и INT2. В дальнейших примерах мы ещё не раз будем пользоваться этими прерываниями.