6. Прерывания
Как уже неоднократно говорилось, специфика микроконтроллеров заключается в их скорости реакции на внешние события и большом разнообразии подключаемой периферии при малой сложности вычислений. Чтобы повысить скорость реакции, можно чаще проверять биты статуса, но это существенно усложнит написание программ и замедлит выполнение. А начиная с некоторого количества периферии, вообще наступит физический предел: на опрос всех битов уйдет больше времени, чем допустимо в устройстве. Чтобы обойти этот предел, для проверки битов придумали использовать не программный код, а аппаратный модуль — контроллер прерываний. Его задача заключается в том, чтобы отловить факт возникновения события, удостовериться, что данное событие разработчику интересно и что контроллер в данный момент готов его обрабатывать. После этого выполнение основного кода приостанавливается (прерывается), а управление передается на специальную подпрограмму — обработчик прерывания.
Для перехода на обработчик прерывания должны быть выполнены все связанные с ним условия:
- Прерывание от данного события должно быть разрешено. Если в устройстве используется только UART, возникновение прерываний от всяких таймеров нам не интересно. Более того, некоторые устройства (в основном, ножки ввода-вывода) генерируют прерывания не импульсно (один раз на событие), а непрерывно. Например, все время пока на ножке высокий уровень. Без возможности запрета таких прерываний контроллер будет постоянно висеть в обработчике.
- Прерывание должно быть разрешено глобально. Дело в том, что некоторые группы команд прерывать нельзя — собьются тайминги, возникнут нежелаельные импульсы или что-то в этом роде. Такие операции называются атомарными (неделимыми) и в простейшем случае реализуются именно сбросом глобального разрешения прерываний и его последующим восстановлением. Также глобальное разрешение сбрасывается при заходе в прерывание, чтобы не было циклической обработки.
- Контроллер прерываний вообще-то должен быть настроен. Выставлен адрес обработчика, разрешения и т.п.
- При наличии системы приоритетов прерываний (в некоторых контроллерах, например AVR, ее нет, но в нашем gd32vf103 — есть, хотя работать с ней мы пока не будем) приоритет пытающегося запуститься прерывания должен быть выше того, что обрабатывается сейчас. “Приоритет” основного кода, естественно, ниже, чем у любого прерывания. Таким способом можно обойти предыдущий пункт и все-таки реализовать обработку прерывания в обработчике прерывания.
Поскольку прерывания генерируются внешними устройствами и не привязаны к выполняющейся в данный конкретный момент инструкции, обработчик обязан после завершения работы вернуть контроллер ровно в то же состояние, что было до его вызова. Это значит восстановить все регистры, включая временные, регистр возврата ra, стек. В случае архитектур с регистрами флагов (к RISC-V это не относится) — и их тоже. При этом возникает два очевидных вопроса:
Как писать код, если все регистры заняты? В общем-то, примерно так же, как и для обычных подпрограмм: нужные регистры сохраняются на стеке, а перед выходом из обработчика восстанавливаются. В простейшем случае (на котором мы остановимся) стек будет общий как для пользовательского кода, так и для прерываний. Но вообще-то RISC-V поддерживает переключение стеков при смене привилений. Скажем, основной код выполняется в U-mode (пользовательский с низкими привилегиями), а прерывания — в M-mode (машинный уровень с максимальными привилегиями). Для этого служит интересный CSR регистр mscratchcsw, но его мы проверить не сможем, пока не доберемся до режимов работы контроллера.
Откуда брать адрес возврата из прерывания, если ra использовать нельзя? Для этого существует специальный CSR-регистр mepc. Возврат по его значению осуществляется специальной инструкцией mret. Помимо прочего она умеет переключать уровни привилегий, если контроллер настроен правильно.
6.1. ECLIC и его настройка
Контроллер прерываний в нашем микроконтроллере называется ECLIC (Enhanced Core Local Interrupt Controller). Управляется он частично через CSR-регистры, частично через MMIO. Регистры у него следующие:
| регистр | размер | смещение | описание |
|---|---|---|---|
| cliccfg | 4(8) | 0x0 | Глобальные настройки приоритетов |
| clicinfo | 25(32) | 0x4 | Разнообразная информация о прерываниях конкретного контроллера |
| mth | 8(8) | 0xB | Порог срабатывания прерываний |
| clicintip[i] | 1(8) | 0x1000+4*i | Флаг ожидающего прерывания |
| clicintie[i] | 1(8) | 0x1001+4*i | Флаг разрешения прерывания |
| clicintattr[i] | 3(8) | 0x1002+4*i | Настрока фронта прерывания и режим |
| clicintctl[i] | 8(8) | 0x1003+4*i | Приоритет |
Тут же встает вопрос относительно чего расчитывается смещение и где про это написано. И тут у меня ответа к сожалению нет: я не нашел упоминаний этого адреса ни в одной документации. Только изучая примеры кода от производителя, был обнаружен базовый адрес 0xD200’0000.
Регистры clicintip, clicintie, clicintattr и clicintctl привязаны каждый к своему прерыванию, поэтому и объединены в массив. В нашем случае используется прерывание от USART0, за которым производителем закреплен номер 56, соответственно использоваться будут clicintip[56], clicintie[56], clicintattr[56] и clicintctl[56] с адресами (0x1000 + 4*56 =) 0x10E0, 0x10E1, 0x10E2 и 0x10E3. Посмотреть номера и список всех доступных в данном конкретном контроллере прерываний можно в его User Manual’е в разделе, посвященном прерываниям. В нашем случае это здоровенная табличка из 86 элементов.
Немного расшифрую что написано в таблице. Регистр mth: я пока точно не знаю за что он отвечает, поэтому подробностей не будет, изучайте документацию. Регистр clicintip содержит всего один значащий бит. Если прерывание уже готово выполниться, он выставляется в 1, что, при выполнении остальных условий, приводит к собственно переходу на прерывание. Насколько я понял, он оставлен только для совместимости со старыми версиями контроллеров векторов прерываний и в ECLIC практически не используется. Регистр clicattr: некоторая периферия (особенно ножки ввода-вывода GPIO) умеют генерировать прерывание по высокому уровню (все время пока на ножке лог.1 будет вызываться прерывание), по нарастающему фронту (лог.0 -> лог.1) и по спадающему фронту (лог.1 -> лог.0). Для большей же части периферии эта настройка бесполезна. Что такое режим (векторный / не-векторный) рассмотрим чуть позже.
Собственно настройка ECLIC для простейшего случая работы с прерываниями сводится всего лишь к разрешению прерываний от интересующей нас периферии, то есть выставлении clicintie[56] в 1:
Обратите внимание, что для записи использована инструкция sb: размер регистров 8 бит, и трогать соседние мы не хотим.
6.2. Настрока периферии
Периферии мы пока изучили немного, поэтому работать будем с UART. Как мы уже выяснили, за все события, происходящие с UART’ом отвечает одно прерывание, с номером 56. Самих же событий может быть несколько, и прописаны они в регистре USART_CTL0:
PERRIE, Parity error interrupt enable — прерывание по ошибке приема. У UART есть простенькая система защиты от сбоев при обмене, и это прерывание возникает при ее срабатывании.
TBEIE, Transmitter buffer empty interrupt enable — прерывание по опустошению буфера передачи
TCIE, Transmission complete interrupt enable — прерывание по фактическому окончанию передачи
RBNEIE, Read data buffer not empty interrupt and overrun error interrupt enable — прерывание по приему байта
IDLEIE, IDLE line detected interrupt enable — прерывание по таймауту. Если данные не приходили слишком долго.
Продемонстрировать работу прерываний будет проще всего на передаче. Контроллер передаст байт, после чего должно произойти событие и мы окажемся в обработчике. Но в регистре USART_CTL0 этих прерываний два. Дело в том, что передача байта происходит в два этапа: сначала байт записывается в USART_DATA, потом автоматически копируется во внутренний буфер, из которого бит за битом передается в линию TX. И пока он передается, в регистр USART_DATA можно положить еще один байт, он там будет лежать, пока предыдущий не освоболит место во внутреннем регистре. Так вот, прерывание TBEIE возникает когда байт покинул регистр USART_DATA и начал передаваться. А TCIE — когда покинул внутренний регистр, и передача полностью завершилась. Соответственно, TBEIE надо использовать когда передаются байт за байтом, чтобы не было задержки между фактическим окончанием передачи, пока отработает прерывание, пока положат следующий байт и т.д. А TCIE — когда надо отключить модуль UART, то есть дождаться фактического окончания передачи. Поскольку отключать UART мы не будем, воспользуемся TBEIEб его нужно добавить к прочим флагам USART_CTL0.
6.3. Настройка контроллера
Итак, модуль ECLIC мы настроили, периферию настроили. Осталось написать собственно обработчик прерывания, положить его адрес в какой-нибудь регистр и разрешить прерывания глобально. Начнем, как ни странно, с регистра хранения адреса обработчика, mtvec:

Как видно из таблицы, младшие 6 битов отвечают за режим работы. Нас интересует режим ECLIC, которому соответствует комбинация 0b000011. Но из-за аж шести занятых битов, данный регистр не может хранить шесть младших битов адреса. Поэтому придется обработчик прерывания располагать с выравниванием на 64:
Из кода довольно очевидно, что прерывание всего лишь мигает зеленым светодиодом и возвращается по адресу mepc при помощи команды mret. Вот именно адрес этой подпрограммы надо записать в mtvec:
И разрешить прерывания глобально. Очевидно, что делать это надо когда периферия и прерывания уже настроены, то есть обычно перед бесконечным рабочим циклом. За глобальное разрешение прерываний отвечает регистр mstatus, а точнее его бит MIE:
Вот теперь прерывание работает: после выполнения кода передачи строки начинает мигать зеленый светодиод, а управление в основную программу не возвращается. В чем дело?
В том, что с прерыванием мы ничего не сделали: оно как ждало обработки, так и продолжает ждать. Вот и тыкается в обработчик в надежде, что хоть теперь его обработают. Но полноценно мы его обрабатывать пока не будем, просто скажем “хорошо, мы поняли, что данные переданы, можешь больше не следить за UART’ом”. То есть просто запретим данное прерывание:
…ну и плюсик выведем, почему бы и нет. Вот теперь прерывание работает правильно: срабатывает, отключает само себя и возвращает управление основному коду.
6.4. Исключения
В микроконтроллерах источником неожиданных событий почти всегда оказывается именно периферия. Но в компьютерах, где программы куда больше и сложнее, чаще бывают и чисто программные ошибки. Плюс при наличии операционной системы появляется и необходимость к ней обращаться из пространства пользователя. Эти задачи также решаются контроллером прерываний. Тут я сразу вынужден уточнить терминологию: прерывания это события от внешних устройств; исключения — от выполнения определенных инструкций кода; исключительные ситуации — от обоих. Разница в том, что прерывание возникает когда ему хочется, и к коду не привязано. Поэтому после обработки надо вернуться в то же самое место, на котором оно случилось. Исключение же возникает в строго отведенных местах: код попытался выполнить несуществующую инструкцию; попытался обратиться к недоступному адресу; попытался работать с невыровненными данными (кстати, наш контроллер к невыровненным данным равнодушен, и ошибок не выдает); попытался сделать системный вызов. Следовательно, при обработке исключений надо сначала определиться что же собственно произошло — штатное событие или ошибка. Если ошибка, то можно ли ее обработать или лучше прибить процесс, пока хуже не стало.
Для примера напишем три инструкции, приводящие к исключениям:
При выполнении такого кода контроллер начинает яростно мигать зеленым светодиодом и спамить плюсики в UART. Логично, ведь при выполнении инструкции 0xFFFFFFFF возникает исключительная ситуация, обрабатывается нашим trap_entry, после чего управление передается опять на 0xFFFFFFFF. Что снова приводит к исключению.
В первую очередь надо отделить исключения от прерываний. Для этого служит регистр mcause, а точнее, его 31-й бит. Если он сброшен в 0, то перед нами исключение, а если выставлен в 1 — прерывание. Соответственно обработчик прерываний остается неизменным, но при обнаружении нуля в 31-м бите mcause надо перейти на обработчик исключения. Отличаться он будет тем, что мигать в нем будем красным диодом, а возвращаться не на ту же инструкцию, которая привела к исключению, а на следующую. Просто-напросто считаем mepc, увеличим на 4 (размер инструкции) и запишем обратно:
Но тут из-под воды возникают интересные грабли: контроллер наш поддерживает расширение C (Compressed) — сжатые инструкции. То есть часть инструкций у него 32-битная, а часть — 16-битная. А перепрыгивать 16-битную инструкцию через 4 байта это плохая идея. К счастью, разработчики RISC-V предусмотрели замечательный способ определить длину инструкции. У 32-битных два младших бита всегда равны 0b11, а в 16-битных — любому другому числу. То есть нам надо всего лишь проверить эти два бита и в зависимости от этого решить, прибавлять 4 или все же 2:
Как я говорил в самом начале, исключения в контроллерах используются достаточно редко, поэтому пока на этом и остановимся.
6.5. Разделение прерываний и исключений
Но если исключения штука редкая, но возможная, можно ли убрать проверку mcause из обработчика прерываний? Оказывается, можно. Для этого используется еще один CSR-регистр mtvt2, который в стандарт RISC-V не входит, и является специфичным для нашего контроллера. Его младший бит отвечает за то, использоать ли его вообще, а оставшиеся биты хранят адрес обработчика прерываний. То есть в mtvec будет адрес обработчика исключений, а в mtvt2 — прерываний:
Естественно, раз уж обработчиков теперь стало два, нужно каждый из них оформить как обработчик — персональная точка входа, работа со стеком, mret.
6.6. Векторный режим
Специфика контроллера вынуждает пойти еще дальше и вместо одного обработчика на все прерывания, в котором нужно было анализировать младшие биты mcause чтобы выяснить какое именно устройство вызвало прерывание, был придуман еще более хитрый механизм. Он заключается в том, что для каждого устройства пишут свой, персональный обработчик прерывания, а их адреса (иногда — прямо команды перехода по адресам) сводят в специальную таблицу — таблицу векторов прерываний. Например, если нас интересует 56-е прерывание, то в 56-ю ячейку надо записать адрес обработчика. Как и с отдельными обработчиками, для хранения адреса таблицы выделен отдельный CSR-регистр mtvt. Причем работа с таблицей реализована очень разумно: в регистре хранится старшая часть адреса таблицы, а вместо младшей подставляется номер прерывания. То есть если адрес самой таблицы равен 0x2000’1000 (где-то в оперативной памяти), и произошло прерывание 56 (поскольку инструкция 4-байтная, то смещение будет 224, оно же 0x0000’00E0), то адрес будет взят из ячейки (0x2000’1000 OR 0x0000’00E0) = 0x2000’10E0. Из этой реализации следует ограничение на выравнивание таблицы. В нашем случае, когда прерываний 86, фактический размер таблицы составляет 344 байта, что помещается в 512-байтную область. Это соответствует выравниванию .align 9.
Здесь надо не забыть, что векторный / не-векторный режим настраивается в регистре clicattr[i], причем для каждого прерывания независимо.
Прописывать простыню из 86 адресов прерываний я здесь не буду, кому интересно может посмотреть в примерах кода.
Вот теперь мы познакомились со всеми основными способами обработки исключительных ситуаций, и можем выбирать тот или иной в зависимости от задач. Не стоит думать будто невекторный режим является устаревшим, просто для разных задач оптимальными будут разные подходы.
6.7. Расположение таблицы векторов прерываний
Как мы увидели раньше, расположить ее можно где угодно, лишь бы выравнивание соблюдалось. Можно даже хранить несколько таблиц по разным адресам и переключать их по желанию левой пятки. Но проще всего все же выделить для таблицы постоянное место — в начале прошивки. Мы ведь точно знаем, что адрес 0x0000’0000 (и даже реальный адрес 0x0800’0000) совершенно точно выровнены по 512-байтной границе. Разработчики даже сделали нам подарок, не став использовать 0-й адрес вектора прерываний, на который попадает управление при старте контроллера. В него можно записать безусловный переход на начало основного кода. Ну а чтобы таблица располагалась именно там, где надо, для нее можно выделить специальную секцию памяти, а в *.ld файле указать, что размещается она в самом начале.
6.8. Системные вызовы
Теперь, когда с самой сложной частью закончили, можно вернуься к исключениям. Например, реализовать системные вызовы из стандарта RARS вроде ввода-вывода чисел, строк, символов и прочего.
Как говорит нам документация, причина исключения хранится в младших 12 битах регистра mcause. За выполнением ecall зарезервировано два кода: 11 (если вызов произошел на M-mode) и 4 (если на U-mode). Пока что будем обрабатывать только ecall, а остальные исключения игнорировать.
Номер ecall‘а хранится в регистра a7, и из списка вызовов RARS’а (напоминаю, в других средах системные вызовы другие!) нас интересуют 1 и 4 — вывод числа и вывод строки. Вот так может выглядеть обработчик исключений, поддерживающий эти два системных вызова:
В результате наконец-то начали корректно работать участки кода
Заключение
Вот так в микроконтроллер gd32vf103 настраиваются прерывания и исключения. Возможно, на таком простом примере, как UART, их польза не очевидна, но желающие могут изучить и другую периферию, поработать через polling и прерывания и оценить, что выгоднее для их задачи. Это, кстати, не шутка: не надо пихать прерывания куда попало, в ряде случаев именно опрос регистра является оптимальным решением.
8. Прерывания, вектор прерываний.
Прерывания – это аппаратные события, которые прекращают нормальный ход программы для выполнения какой-либо приоритетной задачи. При этом событие может быть, как внутренним (от встроенной периферии самого микроконтроллера – таймеров, портов ввода-вывода, АЦП и других), так и внешним – например, появление на входе импульса от нажатой кнопки.
[Можно ли обрабатывать эти события без использования прерываний?
В наиболее простых случаях так и происходит: основная программа представляет собой бесконечный цикл, внутри которого тем или иным способом отслеживаются возникновения событий, при наступлении которых устанавливается определённое значение переменной, называемой обычно флагом (бит в специализированном регистре или ячейке памяти). Основная программа проверяет в цикле значения используемых флагов, и при их изменении переходит к обработке соответствующего события.
Чем неудобен подобный подход?
1) Подобный цикл ожидания полностью загружает микроконтроллер, который в это время мог бы делать что-нибудь полезное.
2) Некоторые события в принципе не могут долго ждать, например, обработка приёма данных по USART – если пропустить хоть один байт, логика работы программы будет нарушена, а повторная пересылка данных может быть не предусмотрена.
Именно поэтому для организации более эффективного использования ресурсов микроконтроллера и используют прерывания.]
Как работают прерывания?
При возникновении прерывания микроконтроллер завершает текущую команду, сохраняет в стеке содержимое счетчика команд и совершает переход на адрес соответствующего вектора прерывания. По этому адресу, как правило, находится команда безусловного (JMP или RJMP) перехода к подпрограмме обработки прерывания.
За каждым аппаратным прерыванием микроконтроллера закреплен свой адрес, и все вместе они образуют таблицу векторов прерываний, которая расположена в самом начале памяти программ.
Контроллер стартует с адреса 0x00 (нулевого адреса памяти программ). Далее мы делаем безусловный переход на метку RESET. Если этого не сделать, то контроллер начнёт выполнять команды из таблицы векторов прерываний, что нам совершенно не нужно.
После перехода по метке первым делом необходимо проинициализировать стек. Потом, используя команду SEI, разрешаем глобальные прерывания. За разрешение конкретных прерываний для каждого вида периферии отвечают соответствующие регистры управления локальными прерываниями.
Когда возникает прерывание, выполнение текущих операций приостанавливается и микроконтроллер переходит по соответствующей метке на подпрограмму обработки прерывания. Последней командой подпрограммы обработки прерывания должна быть команда RETI (выход из прерывания), которая обеспечивает возврат в основную программу и восстановление предварительно сохраненного счетчика команд и данных, заблаговременно перенесённых в стек.
// инициализация стека и инициализация периферии
Что происходит в том случае, если одновременно возникает не одно прерывание, а несколько?
Тогда в регистре состояния устанавливаются соответствующие флаги, и прерывания обрабатываются одно за другим в порядке очерёдности. Положение вектора в таблице определяет приоритет соответствующего прерывания, при этом чем меньше адрес, тем выше приоритет прерывания.
Обработка прерываний
[ВОПРОС ИЗ КОНТРОЛЬНОЙ – краткое описание того, что ниже.
В билете можно писать это, а потом читать остальное для пояснения]
Выберите правильную последовательность действий при обработке прерывания:
1) Для включения прерывания программа должна установить флаг I регистра SREG в единицу и записать в регистры маски такой код, который разрешит лишь нужные в данный момент прерывания
2) При поступлении запроса на прерывание устанавливается флаг соответствующего прерывания
3) Флаг I автоматически сбрасывается, запрещая обработку других прерываний. Флаг, соответствующий вызванному прерыванию, также сбрасывается, сигнализируя о том, что МК уже приступил к его обработке.
4) После окончания обработки очередного прерывания происходит проверка остальных флагов, и если имеется хоть одно не обработанное прерывание, МК переходит к его обработке.
Для глобального разрешения или запрещения прерываний предназначен флаг I регистра состояния SREG. Для разрешения работы прерываний он должен быть установлен в единицу (это делается с помощью ассемблерной команды SEI), а для запрещения сброшен (командой CLI). По умолчанию (после сброса микроконтроллера) этот флаг сброшен, и все прерывания микроконтроллера запрещены.
Для каждого блока периферии существует собственный регистр для разрешения локальных прерываний (E/TIMSK). При возникновении прерывания флаг I регистра SREG сбрасывается на аппаратном уровне, запрещая тем самым обработку следующих прерываний. При возврате из подпрограммы обработки прерывания (при выполнении команды RETI) флаг I устанавливается обратно.
Все доступные для работы прерывания можно разделить на два типа.
I тип) возникают при наступлении некоторого события, в результате которого устанавливается флаг прерывания.
Если прерывание разрешено, в счетчик команд загружается адрес вектора соответствующего прерывания. При этом флаг прерывания сбрасывается на аппаратном уровне. Он также может быть сброшен программным образом, путем записи единицы в разряд регистра, соответствующий флагу.
II тип) Не имеют флагов прерываний и возникают в течение всего времени, пока присутствуют условия, необходимые для их возникновения.
Если условия, вызывающие прерывание, исчезнут до разрешения прерывания, генерации прерывания не произойдет.
Поскольку прерывание нарушает стандартный ход выполнения программы и может возникнуть в любой момент времени, необходимо сохранить текущие данные. Для этого необходимо использовать стек, туда нужно переместить все регистры, используемые в обработчике прерывания, чтобы не потерять хранящиеся там данные. Также необходимо сохранять регистр флагов SREG, в котором хранится результат логических операций:
PUSH R16 // Сохраняем регистр R16
IN R16, SREG // Перемещаем содержимое SREG в R16
PUSH R16 // Сохраняем всё в стек
PUSH R17 // R17 сохраняем туда же
…………… // Выполнение кода обработчика прерывания
POP R17 // Перед выходом из прерывания извлекаем
POP R16 // сохранённые данные
OUT SREG, R16 // Действуем при этом в обратном порядке
RETI // Выходим из прерывания
Также существует ряд операций, которые должны выполняться неразрывно, например, чтение 16-разрядного счетного регистра таймера.
16-разрядный регистр считывается в два приема: сначала считывается младший байт, а потом старший. (ЗАПИСЬ ПРОИСХОДИТ В ОБРАТНОМ ПОРЯДКЕ)
Между двумя этими операциями не должно возникать никаких прерываний, поэтому перед чтением такого регистра необходимо их запретить, а после окончания операции чтения разрешить обратно.
Прерывания
Прерывание (interrupt) – событие, требующие немедленной реакции со стороны процессора. Реакция состоит в том, что процессор прерывает обработку текущей программы ( прерываемой программы ) и переходит к выполнению некоторой другой программы ( прерывающей программы ), специально предназначенной для данного события. По завершении этой программы процессор возвращается к выполнению прерванной программы.

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

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

Если запрос прерывания окажется не обслуженным к моменту прихода нового запроса от того же источника (того же приоритета), то возникает насыщение системы прерываний . При этом часть запросов прерывания будет утрачена, что для нормальной работы микроконтроллера недопустимо.
Характеристиками системы прерывания являются:
- общее количество запросов прерывания – количество источников запросов прерывания;
- тип представления прерывания – как правило, запрос прерывания представлен логическим уровнем сигнала;
- приоритет прерывания – определяет очередность обработки каждого запроса прерывания, чем выше приоритет, тем меньше задержка в исполнении прерывающей программы для него;
- время реакции – временной интервал между появлением запроса прерывания и началом выполнения прерывающей программы;
- задержка прерывания – определяется суммарным временем на запоминание и восстановление программы;
- глубина, обычно совпадает с числом уровней приоритетов в системе прерывания;
- насыщение системы прерывания;
- допустимые моменты прерывания программ (как правило, окончание выполнения следующей команды).
Маскирование прерываний используется для сообщения микроконтроллеру о необходимости реагировать на каждый тип прерывания или игнорировать его. Маска прерывания представляет двоичный код, разряды которого поставлены в соответствие источникам запроса прерываний. Единичный бит в двоичном коде сообщает микроконтроллеру о необходимости обработки прерываний такого типа. Нулевой бит напротив не позволяет микроконтроллеру переходить к обработке прерываний указанного типа.
Как правило, кроме маскирования прерываний, существует также бит глобального разрешения прерываний, нулевое значение которого отключает все обработчики прерываний (кроме аппаратного сброса и перехода к началу исполняемой программы).
Кроме двоичного кода маски прерываний существует также двоичный код флагов прерываний , который позволяет обработчику прерываний установить источник возникновения прерывания в случае если источников с указанным запросом в микроконтроллере несколько.
Назад
Использование прерываний в микроконтроллере PIC
В данной статье мы рассмотрим работу с внешними прерываниями в микроконтроллере PIC и почему в этом возникает необходимость. Перед началом изучения данной статьи рекомендуем ознакомиться с общими принципами программирования микроконтроллеров PIC с помощью программы MPLABX и подключения к микроконтроллеру PIC ЖК дисплея.

Также на нашем сайте вы можете посмотреть статьи про использование прерываний в плате Arduino и плате STM32 Blue Pill.
Необходимые компоненты
- Микроконтроллер PIC16F877A (купить на AliExpress).
- Держатель микросхем на 40 контактов (купить на AliExpress).
- Программатор PICkit 3 (купить на AliExpress).
- Кварцевый генератор 20 МГц (купить на AliExpress).
- Конденсаторы 22 пФ (2шт.) и 0,1 мкФ (купить на AliExpress).
- Конденсатор 10 мкФ (купить на AliExpress).
- Регулятор напряжения 7805 (купить на AliExpress).
- ЖК дисплей 16х2 (купить на AliExpress).
- Макетная плата.
- Соединительные провода.
Что такое прерывания и зачем они нужны
В микроконтроллере PIC16F877A можно использовать 15 типов прерываний. Так зачем они нужны?
Как мы знаем, все микроконтроллеры программируются на выполнение определенной последовательности действий, задаваемой программой. Но достаточно часто в реальных проектах возникают ситуации, в которых требуется немедленное (первоочередное) выполнение каких либо действий. В этом случае на помощь и приходят прерывания.
Прерывания постоянно анализируют ситуацию на предмет появления определенных событий и когда эти события наступают, они исполняют небольшой фрагмент кода, после чего основная программа продолжает свое нормальное функционирование. Этот небольшой фрагмент кода называется процедурой обработки (обслуживания) прерывания (Interrupt Service Routine, ISR). На нашем сайте мы уже рассматривали один пример использования прерываний в микроконтроллере PIC – в проекте цифрового спидометра и одометра.
В микроконтроллерах существует два основных типа прерываний: внешние и внутренние. Внутренние прерывания происходят непосредственно внутри микроконтроллера, к из примерам можно отнести прерывания от таймеров, модулей АЦП и т.д. Эти прерывания срабатывают программным способом, то есть непосредственно в программе.
Срабатывание внешних прерываний могут вызывать какие-либо внешние устройства или действия пользователя. В данном проекте для срабатывания внешнего прерывания мы будем использовать обычную кнопку. Мы будем производить на экране ЖК дисплея счет от 0 до 1000 и когда будет происходить внешнее прерывание, мы будем выводить сообщение об этом на ЖК дисплей и затем продолжать счет.
Схема проекта
Схема проекта для демонстрации возможностей использования прерываний в микроконтроллере PIC представлена на следующем рисунке.

Как видите, в этом проекте нам необходимо всего лишь подключить ЖК дисплей к микроконтроллеру PIC.
Для определения контакта, на котором микроконтроллером PIC16F877A возможна обработка внешнего прерывания, необходимо обратиться к даташиту на данный микроконтроллер. Из данного даташита можно узнать, что 33-й контакт микроконтроллера с названием RBO/INT может использоваться для обработки сигнала внешнего прерывания. Другой контакт нельзя будет использовать для этой цели.
Схема соединений микроконтроллера PIC приведена в следующей таблице.
| № п/п | № контакта микроконтроллера | Наименование контакта микроконтроллера | Куда подключен |
| 1 | 21 | RD2 | RS of LCD |
| 2 | 22 | RD3 | E of LCD |
| 3 | 27 | RD4 | D4 of LCD |
| 4 | 28 | RD5 | D5 of LCD |
| 5 | 29 | RD6 | D6 of LCD |
| 6 | 30 | RD7 | D7 of LCD |
| 7 | 33 | RBO/INT | кнопка |
На порту PORT B мы будем использовать внутренние подтягивающие резисторы, поэтому мы непосредственным образом можем замкнуть контакт RB0 на землю (общий провод) через кнопку. Таким образом, всегда, когда на этом контакте будет появляться уровень LOW, будет срабатывать прерывание.
Внешний вид собранной на макетной плате конструкции проекта показан на следующем рисунке.

Моделирование работы проекта
Перед проверкой работы схемы на «реальном железе» мы протестировали ее работу в симуляторе Proteus. Схема проекта в данном симуляторе показана на следующем рисунке.

Во время тестирования работы схемы в симуляторе Proteus вы должны увидеть как на экране ЖК дисплея будет последовательно инкрементироваться счетчик. А когда пользователь будет нажимать кнопку, будет происходить выполнение процедуры обработки прерывания (ISR).
Объяснение программы для микроконтроллера PIC
Полный код программы приведен в конце статьи, здесь же мы кратко рассмотрим его основные фрагменты.
Первым делом в программе настроим биты конфигурации. После этого необходимо указать, что на контакте RB0/INT мы будем производить обработку сигнала внешнего прерывания, то есть он не будет обычным цифровым входом или выходом. Следующая строка кода подключает внутренние подтягивающие резисторы к контактам порта portB при помощи установки 7-го бита регистра OPTION_REG в 0.
