Мы подошли к моменту, когда без изучения прерываний дальнейшее изучение микроконтроллеров будет делом сложным. Но и сами прерывания являются одной из самых сложных для изучения тем.
Поэтому соберитесь и запаситесь терпением. Прерываниям будет посвящено несколько статей, в которых мы рассмотрим и аппаратные, и программные вопросы. И, разумеется, особенности реализации прерываний в различных семействах микроконтроллеров.
Очень кратко темы прерываний я касался в статье "Микроконтроллеры для начинающих. Часть 26. Команды вызова подпрограмм и прерываний и возврата из них". Рекомендую ее прочитать.
Кроме того, будет полезно вспомнить статью "Микроконтроллеры для начинающих. Часть 2. Процессор микроконтроллера". В ней показано, в частности, как формируется адрес команды, которую будет исполнять процессор. А так же статью "Микроконтроллеры для начинающих. Часть 3. Процессор микроконтроллера. Тактирование и синхронизация.". Здесь показано, как организована совместная работа узлов процессора.
Что такое прерывания
Прежде всего, нам надо разобраться, что же такое прерывания. Собственно говоря ответ на этот вопрос кроется в самом термине. Прерывание действительно прерывает нормальное выполнение программы. А управление получает процедура обработки (обслуживания) прерывания, которую кратко называют ISR - Interrupt service routine.
После завершения обработки прерывания выполнение программы продолжается как обычно, с того самого места, в котором прерывание возникло.
Очень похоже на вызов подпрограмм? Да, похоже. Но есть одно важнейшее отличие - программа не должна знать, что прерывание вообще возникало. Программа не должна зависеть от того, возникло прерывание, или нет. За исключением случаев, когда программист такую зависимость предусматривает в явном виде.
А это означает, что переход к обработке прерывания требует дополнительных действий, которые могут выполняться или аппаратно, или программно.
Программные прерывания, синхронные
В микроконтроллерах, тех, что мы рассматриваем, программные прерывания есть только в STM8. Это команда TRAP. Но в микроконтроллерах вообще, тем более, в микропроцессорах, может быть несколько различных программных прерываний.
Программные прерывания всегда синхронные. То есть, они возникают синхронно с ходом выполнения программы, всегда в одном и том же месте - при выполнении команды прерывания.
Программные прерывания можно рассматривать как простой вызов подпрограммы, но адрес этой подпрограммы всегда предопределен и изменить его нельзя. И нельзя передать такой подпрограмме параметры, иначе чем заранее предусмотренные программистом переменные, обычно, глобальные.
Поскольку программные прерывания вызываем мы сами, не требуется сохранение состояния (контекста) процессора. Но поведение конкретного процессора при обработке программных прерываний нужно искать в документации.
Мы не будем рассматривать программные прерывания в рамках данного цикла статей.
Аппаратные прерывания, асинхронные
Сегодня аппаратные прерывания есть в подавляющем большинстве микроконтроллеров. Но исключения все таки есть, например, линейка BaseLine микроконтроллеров PIC.
Аппаратные прерывания всегда асинхронные. То есть, момент их возникновения никак не связан с выполнением программы. Может возникнуть вопрос, как так не связан, а, например, АЦП? Мы же сами запрашиваем преобразование, а значит, возникающее при завершении преобразования прерывание вызвано именно нами, синхронно.
Нет, совсем не синхронно и не нами. Да, мы сами запросили преобразование. Но сколько для преобразования потребуется времени мы не знаем, количество тактов преобразования может зависеть от многих параметров. Поэтому и прерывание все равно возникнет асинхронно.
Аппаратные прерывания могут быть вызваны и внутренними модулями микроконтроллера, и внешними цепями (через специальный вход INT). Кроме того, аппаратные прерывания могут возникать и при выполнении программы, команды. Например, деление на 0. И это тоже асинхронное прерывание.
Приоритеты прерываний
Некоторые аппаратные прерывания могут быть более важными, чем другие. Например, готовность байта полученного из канала связи более важна, чем нажатие кнопки. При этом говорят о приоритете прерываний.
Уровней приоритета может быть несколько. Но возможно и полное отсутствие деления на приоритеты. Если одновременно возникает несколько запросов прерывания, то обрабатываться они будут в порядке убывания приоритета. Реализуется приоретизация аппаратно.
Прерывание может возникнуть во время обработки другого прерывания. В этом случае говорят о вложенности прерываний.
Вложенные прерывания
Процедура обработки прерывания тоже может быть прервана новым прерыванием, но только если его приоритет выше, чем у обрабатываемого. То есть, мы получаем обработку прерывания внутри обработки другого прерывания. Это и есть вложенность.
В большинстве случаев с вложенными прерываниями проблем не возникает. После завершения обработчика более высокого приоритета управление вернется в прерванный обработчик.
Контроллер прерываний
Теперь становится понятным, что обработка прерываний не такой простой процесс. Запуск процедуры обработки прерывания, безусловно, будет процессор. Но вот разбираться с приоритетами, запретами (маскированием) прерываний, другой вспомогательной (но важной!) работой будет специальный модуль - контроллер прерываний. Этот контроллер может быть и очень простым, и очень сложным. И может быть выполнен как в виде модуля внутри микроконтроллера, так и быть внешней микросхемой.
И сегодня основное внимание я буду уделять именно контроллеру прерываний.
Контроллер прерываний
И начнем мы с самого простого случая. Сегодня он скорее гипотетический, но зато позволит начать разбираться с тем, что лежит в самой глубине процессов
Единственный источник прерываний
Сейчас мы будем рассматривать собственно процессор. Причем процессор имеющий единственный вход запроса прерывания.
Я не стал показывать всю функциональную схему процессора. Она есть в статьях, ссылки на которые я дал ранее. Здесь же я показал только регистр адреса команды PC и дополнительные узлы, которые используются для обработки прерывания.
Но сначала о регистре PC. Как Вы уже знаете, этот регистр автоматически инкрементируется для перехода к следующей команде. Так же, в него можно загрузить адрес из команд перехода и вызова. Разумеется, этот регистр можно и прочитать.
Я показал, что запись и чтение регистра выполняется через ALU, подписав соответствующие цепи. Все остальные сигналы просто идут к/от других узлов процессора, которые нам сегодня не важны.
Триггер IE_TRIG отвечает за разрешение/запрет прохождения запроса прерывания в процессор. Он может быть частью регистра состояния процессора или частью иного регистра. В данном случае, если он установлен, прерывания разрешены, а если сброшен запрещены.
Триггер INT_TRIG отвечает за хранение запроса прерывания, так как я показал, что вход INT, собственно вывод на который поступает запрос из внешнего мира, является не потенциальным, а импульсным.
Как все это работает... Когда на вход INT поступает импульс запроса прерывания в INT_TRIG записывается 1 (триггер устанавливается). И это выполняется независимо от того, разрешены прерывания или нет. Если прерывания запрещены, то обработка просто будет отложена до момента разрешения. Триггер будет хранить запрос.
На вход процессора сигнал запроса прерывания IF попадет лишь в том случае, если триггер IE_TRIG будет установлен. То есть, если прерывания будут разрешены.
Теперь в игру вступает процессор. И первым делом он запрещает прерывания сбросив IE_TRIG. Теперь нам не важно состояние INT_TRIG поэтому сбрасывается и он. Заодно это позволяет зафиксировать запрос прерывания, который может придти во время обработки текущего прерывания. Далее, обязательно, сохраняет регистр PC.
Выполнив всю дополнительную работу, например, сохранив состояние процессора, процессор записывает в PC адрес процедуры обработки прерывания. Когда процедура обработки завершится она выполнит не команду RET (возврат), а специальную команду IRET (возврат из прерывания).
Теперь процессор снова выполняет всю дополнительную работу, например, восстанавливает состояние процессора. После чего можно восстановить регистр PC. Остается разрешить прерывания.
Теперь программа продолжит работать с того места, где ее выполнение было прервано.
В документации и книгах часто пишут, что восстанавливается состояние разрешения обработки прерываний. Это верно, но не совсем точно. Дело в том, что если уж мы попали в обработчик прерывания, значит прерывания точно были разрешены. И мы можем просто установить IE_TRIG без дополнительных раздумий.
А теперь начнем усложнять нашу задачу. Для начала увеличим количество источников прерывания, но оставим единственный обработчик.
Несколько источников прерываний, единственный обработчик
Ярким представителем такого подхода являются микроконтроллеры PIC, за исключением BaseLine и PIC18.
Основное отличие здесь в замене INT_TRIG на INT_REG и появлении дополнительного регистра IE_REG. Кроме того, теперь у нас не IE_TRIG, а GIE_TRIG. Дело в том, что теперь он управляет глобальным разрешением/запретом прерываний. А вот запретить/разрешить конкретный источник мы можем через IE_REG.
В целом, обработка прерываний не изменилась. Автоматически сбрасывается GIE_TRIG, что запрещает все прерывания, глобально. IE_REG автоматически не изменяется, только программно, если это программист это предусмотрел.
А вот со сбросом INT_REG все сложнее и интереснее. Сбросить регистр целиком нельзя, можно только триггер соответствующий обрабатываемому запросу. Но как этот триггер найти? Ответ простой - никак.
Поэтому в подобных контроллерах запросы прерываний от источников не сбрасываются автоматически. Их обязательно нужно сбрасывать вручную.
А как мы можем найти источник запроса, вызвавшего прерывание? У нас ведь обработчик один на всех. Довольно просто - прочитав содержимое INT_REG и проанализировав его биты. Это позволит определить источник и вызвать соответствующую подпроцедуру обработки. Именно она потом и сбросит флаг запроса.
Кстати последовательность анализа битов INT_REG определяет и приоритетность прерывания. Такой вот дополнительный программный бонус. Считаете невозможным, так как прерывания запрещаются автоматически?
Возможно! Достаточно держать прерывания запрещенными глобально. При этом триггеры INT_REG будут фиксировать поступающие запросы. Теперь, разрешив в нужный момент прерывания (глобально) мы можем обработать их все сразу. В нужном нам порядке.
Безусловно, это далеко не универсальный метод. Но это и чистая теория, как может показаться. Дело в том, что несколько запросов (они же асинхронные!) могут придти одновременно. Кроме того, мы можем запрещать прерывания на время выполнения критических участков программы по необходимости. А после их разрешения получить как раз такую вот ситуацию.
Несколько источников, несколько обработчиков
Продолжаем усложнять нашу задачу. Давайте добавим отдельные обработчики для каждого источника прерывания. Это не так просто, как может показаться.
Но сначала нам надо вспомнить, что такое вектор прерывания. В своем первоначальном варианте вектором называлась ячейка памяти хранившая адрес обработчика. Содержимое этой ячейки загружалось в PC, что и приводило к переходу на обработчик. И мы могли спокойно изменять адрес обработчика, просто занеся его в вектор.
Однако, в микроконтроллерах (по крайней мере тех, что мы изучаем) вектор это просто определенный набор ячеек памяти команд на начало которого и передается управление. А программист размещает в векторе команду перехода на нужный обработчик.
Преобразовать номер прерывания (номер источника) в адрес перехода (вектора) достаточно легко. Например, можно сдвинуть номер вектора влево на два разряда и сбросить два младших разряда. И мы получим вектор позволяющий разместить 4 команды.
Я не стал показывать всю функциональную схему контроллера прерываний, для наглядности. Что у нас здесь добавилось? Во первых, шифратор, причем шифратор приоритетный, что бы корректно обрабатывать ситуацию с несколькими конкурирующими запросами прерываний. Во вторых, мультиплексор, который определяет источник адреса для занесения в регистр PC.
Источниками адресам могут быть адрес команды из ALU, для команд перехода, или адрес вектора из нашего контроллера прерываний.
Запрос прерывания из источника с помощью приоритетного шифратора превращается в номер прерывания. У нас 4 источника, поэтому достаточно 2 бит адреса. Но вектор должен вмещать более одной команды. Поэтому номер вектора мы помещаем в биты 3 и 2 адреса, а биты 1 и 0 сбрасываем, что и показано на иллюстрации.
Это позволяет нам разместить в каждом векторе до 4 команд, а адрес вектора (адрес первой команды) будет равен номеру прерывания умноженному на 4.
В остальном обработка прерываний не изменилась. Но теперь у нас появилась возможность автоматически сбрасывать соответствующий триггер в INT_REG, ведь мы теперь знаем его номер. Я не буду показывать, как это выполняется на функциональной схеме.
Контроллер прерываний с приоритетами
Приоритетный шифратор в предыдущем случае не означает, что у прерываний есть приоритет. Да, он позволяет выбрать из нескольких прерываний одно, с наименьшим номером (наибольшим приоритетом). Но это не делает возможным прервать текущий обработчик прерывания с меньшим приоритетом.
Причина проста, при возникновении прерывания у нас прерывания запрещаются глобально. Я не буду приводить иллюстрацию, она сложна для начинающих, я просто расскажу, как это работает.
Вводится дополнительный регистр, который хранит приоритет текущего обрабатываемого прерывания. Причем, в общем случае, приоритет не равен номеру прерывания. Например, прерывания могут быть сгруппированы по несколько штук в пределах одного приоритета.
Прерывания не запрещаются глобально. Прерывания запрещаются для приоритета текущего прерывания и меньших приоритетов. При поступлении нового запроса специальный компаратор сравнивает его приоритет с текущим (хранится в специальном регистре, как я говорил). Если приоритет нового запроса выше, он поступает в процессор, что приводит к прерыванию текущего обработчика.
Причем это простейший случай. Могут быть предусмотрены дополнительные режимы работы. Например, циклическая смена приоритетов. Причем и приоритетов в группе, и групп приоритетов.
Когда мы будет изучать реализацию контроллеров прерываний в конкретных семействам микроконтроллеров, мы вернемся к приоретизации.
Заключение
На сегодня все. Мы только начали изучать прерывания. Впереди нас ждет много интересного, и трудного. Но наши возможности использования микроконтроллеров значительно возрастут. Так что результат стоит затраченного времени и усилий.