Познакомившись с общими моментами организации прерываний и работы с ними мы можем перейти к немного более подробному знакомству я реализацией прерываний в реальных микроконтроллерах. И начну я с PIC. Причем рассмотрю и PIC18, которые имеют некоторые особенности.
Как я уже говорил, не все микроконтроллеры PIC умеют работать с прерываниями. В BaseLine прерываний нет вообще. Поэтому это семейство сегодня останется "за кадром".
Аппаратная реализация
Аппаратная реализация отличается даже в пределах одного семейства. Тем не менее, есть очень много общих моментов. Поэтому давайте сначала посмотрим на функциональную схему
Как видно, в PIC довольно простой контроллер прерываний. Здесь нет приоритетов. И есть всего один вектор прерывания.
У внимательных читателей может возникнуть вопрос "а где триггеры, которые есть почти на всех иллюстрациях в предыдущей статье?". Они есть, но в данном случае схемы на иллюстрациях являются сигнальными, а не принципиальными. Так сигнал TMR0IF на самом деле является сигналом с выхода триггера.
На глобальном уровне за разрешение обработки прерываний отвечает бит GIE, который находится в регистре INTCON. Обратите внимание, что этот бит управляет прохождение сигнала запроса прерывания в процессор микроконтроллера. Он не оказывает никакого влияния на формирование собственно запросов от различных источников.
Источником сигнала запроса прерывания может быть почти любой модуль микроконтроллера. Разумеется, есть и вход внешнего запроса прерывания. Причина формирования сигнала запроса прерывания, который представлен в виде флага запроса, может быть разной. Таких причин может быть даже несколько, это определяется лишь внутренней логикой модуля.
Но с точки зрения контроллера прерываний все определяется именно сигналами (флагами) запроса. Именуются сигналы по единому принципу
имя_модуля/запроса_IF
Например, сигнал запроса прерывания от TIMER0 (TMR0), который соответствует флагу запроса, называется TMR0IF. Исключением является сигнал внешнего прерывания INTF.
Если модуль может формировать несколько сигналов, то их имена включают основную причину запроса. Например, для модуля EUSART (расширенный последовательный интерфейс) существуют флаги запроса RCIF (прерывание получения данных) и TXIF (прерывание отправки данных).
Формируемые модулями запросы прерываний мы будем рассматривать при изучении этих модулей. А для таймеров мы с флагами запросов уже сталкивались.
Для всех сигналов запроса существует возможность управления их прохождением. За это отвечают биты
имя_модуля/запроса_IE
Их можно увидеть на приведенной иллюстрации. Установленное состояние бита разрешает дальнейшее прохождение сигнала запроса.
Исторический путь развития микроконтроллеров PIC хорошо виден в структуре регистров управления прерываниями. На иллюстрации видно, что часть запросов обрабатывается не напрямую, а через дополнительную сущность "Периферийное прерывание". Понятие "периферийное прерывание" объединяет в себе модули, которых не было в "ядре" исходных микроконтроллеров. Вот и появилось такое расширение. Тут можно провести аналогию с AVR, где часть регистров модулей оказалась вынесенной из адресного пространства ввода-вывода в адресное пространство данных.
Таким образом, для попадания запроса прерывания в процессор необходимо установить и бит GIE, и соответствующий бит xxxxIE. А для прерываний "периферийных" еще и бит PEIE.
Кроме того, прерывания могут использоваться и для пробуждения микроконтроллера, выхода из режима сна. При этом не требуется глобальное разрешение прерываний. Но вот локальное разрешение xxxxIE обязательно.
Аппаратная реализация PIC18
В общем и целом, PIC18 очень похож на младших собратьев. Но у него появилось разделение на два уровня приоритета. Причем по умолчанию деление на приоритеты отключено, для совместимости с младшими моделями.
Видно, что по сути просто добавлен второй контроллер прерываний. Включением/выключением приоритетов управляет бит IPEN, который располагается в регистре RCON.
При включенных приоритетах, разумеется, есть возможность разрешать/запрещать обработку каждого приоритете в отдельности. Кроме того, у каждого приоритета есть свой вектор прерывания.
Существует еще семейство enhanced PIC18 (например,PIC18F27Q43), в котором контроллер прерываний гораздо сложнее. Кроме того, там есть не только приоритеты, но векторы прерываний. Однако, в рамках данного цикла статей (для начинающих!) я его не буду рассматривать. Возможно, ему будут посвящены отдельные статьи.
Задержки при возникновении прерываний
Переход к обработке возникшего прерывания требует некоторой подготовки со стороны процессора. Эта подготовка требует определенного времени, что иногда бывает важным.
Не смотря на то, что на иллюстрации показана обработка внешнего прерывания, это распространяется на все прерывания. Анализ входа запроса прерывания выполняется процессором в такте Q1. Таким образом, в большинстве случаев аппаратная процедура обработки прерывания начнется при выполнении машинной команды следующей за той, во время которой флаг запроса был установлен.
При этом выполнение машинной команды не может быть прервано. Равно как не может быть прервана и выборка следующей машинной команды из памяти. Поэтому очередная команда успевает выполниться, а обработка прерывания откладывается еще на один машинный цикл.
И только теперь процессор приступает к отработке аппаратной процедуры. Выборка новой команды не выполняется, а процессор выполняет пустой цикл. Но зато в такте Q2 запрещаются (аппаратно) прерывания. Одновременно, в регистр PC заносится адрес единственного вектора прерывания 0004h.
Теперь у нас прерывания запрещены, а очередь команд очищена. И процессор выполняет выборку команды по адресу вектора прерывания. Но у нас нет ранее выбранной команды, поэтому процессор выполняет еще один пустой цикл.
Таким образом, между моментом появления запроса прерывания и началом выполнения первой команды обработчика проходит три машинных цикла. Или 4, если считать и цикл, в котором запрос появился.
Крайне важно отметить, что задержка в 4 цикла будет лишь для активного режима работы процессора. Если микроконтроллер находился в режиме сна, то сначала будет выполнен переход в режим работы. Что может потребовать значительного времени на запуск и стабилизацию кварцевого тактового генератора.
На самом деле, такая задержка существует абсолютно у всех микроконтроллеров, а не только у PIC. А длительное время пробуждения может привести к невозможности использования режимов сна, если критически важна максимальная быстрота реакции на прерывание.
Программный аспект
С точки зрения программиста, контроллер прерываний представлен набором регистров:
- Регистры запросов прерываний - PIR
Все сигналы запросов прерываний сводятся в эту группу регистров. Это и есть те самые триггеры, которые я показывал в предыдущей статье. Исключением является несколько запросов в младших моделях. Например, T0IF, запрос прерывания от TIMER0 в PIC16F675.
С точки зрения программы, сигналы представлены отдельными битами регистров PIR. Количество регистров может быть разным, это зависит от количества возможных источников запроса прерываний.
Так запрос прерывания по окончании преобразования АЦП в PIC16F675 называется ADIF и доступен через бит 6 регистра PIR1.
Еще раз отмечу, что нет автоматического сброса запросов прерываний! Это должна делать процедура обработки прерываний вручную. Если бит не будет сброшен вручную, то при выходе из процедуры обработки прерывание тут же будет сгенерированно вновь, что является ошибкой.
- Регистры разрешения (маскирования) прерываний - PIE
Каждому регистру PIR соответствует свой регистр PIE. Устанавливая/сбрасывая биты регистров PIE мы управляем сигналами разрешения прохождения запросов прерываний. Так для разрешения прохождения запроса прерывания от АЦП нужно установить бит 6 регистра PIE1 (для PIC16F675).
- Регистры приоритета - IPR
Эти регистры есть только в PIC18, так как работами с приоритетами реализована только в этой линейке. Если приоритеты отключены, то регистры IPR не участвуют в обработке прерываний.
Если приоритеты включены, что установленное состояние соответствующего бита переводит запрос прерывания в группу с высоким приоритетом, а сброшенное в группу с низким приоритетом.
Например, бит 6 регистра IPR1 (сигнал ADIP) микроконтроллера PIC18F46K22 определяет запрос прерывания от АЦП (ADIF) как запрос высокого приоритета.
Количество регистров IPR может быть разным, все определяется количеством источников запроса прерываний.
- Управляющие регистры - INTCON
В младших микроконтроллерах эти регистры используются не только для управления контроллером прерываний, но и для работы с сигналами запросов и разрешения. В частности, именно здесь расположены биты T0IF и T0IE в микроконтроллере PIC16F675. Но самое интересное, что в PIC18 в регистрах INTCON тоже располагаются некоторые биты запросов/разрешений.
Для нас же, на данном этапе, самым важным битом в регистре INTCOM будет бит GIE. Как вы помните, этот бит отвечает за глобальное разрешение обработки прерываний.
Сохранение контекста процессора
В младших моделях, в простых MidRange, микроконтроллерах в аппаратном стеке возвратов сохраняется только адрес возврата. Не сохраняется даже регистр STATUS. Вся работа возлагается на собственно процедуру обработки прерывания в которой программист должен выполнить все необходимы действия вручную.
Ситуация усложняется тем, что регистр STATUS хранит флаги состояния процессора, которые непрерывно изменяются. При этом и WREG нуждается в сохранении. В документации приводится специальная последовательность команд, которая позволяет корректно сохранить и восстановить регистры WREG и STATUS в программах на ассемблере.
Для программ на С необходимая процедура сохранения/восстановления входит в состав стандартного пролога/эпилога обработчика прерываний, или реализована как набор макросов. И мы со всем этим сегодня тоже познакомимся.
В улучшенных моделях, Enhanced Mid-range, микроконтроллерах в стеке возвратов по прежнему сохраняется адрес возврата. Но дополнительно, аппаратно, в специальные теневые регистры сохраняется и состояние регистров WREG, STATUS, BSR, FSR, PCLATH. То есть, Core Registers, сохраняются аппаратно. Но не в стеке, а в специальной области памяти, теневых регистрах. При этом никакой вложенности нет.
Для PIC18 возможны оба варианта сохранения контекста, их можно переключать. Однако, состав сохраняемых регистров сократился, сохраняются только WREG, STATUS и BSR. При этом область сохранения регистров носит гордое название "fast return stack", но на самом деле никакого стека нет и в помине! Это те же самые теневые регистры, без какой либо вложенности. И если используются приоритеты, то fast return stack так просто использовать не получится. Так при прерывании высокого приоритета, которое возникнет при обработке прерывания низкого приоритета, содержимое теневых регистров окажется перезаписанным.
Структура процедур обработки прерываний на языке С
Как я уже говорил, обработка прерываний не только аппаратно-зависима, но и компиляторо-зависима. В данной статье, как и во всем цикле статей, я использую компилятор CC5X. Для PIC18 используется CC8E. Для других компиляторов оформление процедуры ISR будет отличаться, но сам подход к обработке останется тем же самым. Если у вас возникнут вопросы, задавайте их комментариях.
Что бы компилятор обрабатывал функцию как процедуру обработки прерываний, необходимо описывать ее специальным образом
interrupt name(void)
Это включает специальные проверки использования регистров (сохранять/не сохранять) и использование машинной команды RETFIE вместо RETURN. Имя (name) процедуры может быть любым, а void в списке параметров можно просто опустить.
Компилятор CC5X несколько специфический, я уже об этом не раз говорил. Поэтому необходимо еще и разместить обработчик прерывания в правильном месте, вручную. Для этого используется директива
#pragma origin 4
которая и задает адрес размещения (0x04). Для PIC18 вектор прерываний низкого приоритета располагается по адресу 0x18, а высокого приоритета по адресу 0x08. Естественно, эти адреса и надо будет использовать в директиве pragma.
Кроме того, из-за особенности распределения адресов компилятором, процедура обработки прерываний должна располагаться в том же исходом файле, что и функция main. Причем до функции main.
Какого либо стандартного пролога/эпилога компилятор для процедур обработки прерываний не добавляет. Вы можете все сохранять/восстанавливать вручную. Однако, есть набор макросов, которые дают некоторую, довольно удобную, автоматизацию этих процессов.
Для PIC эти макросы располагаются в файле int16CXX.h, а для PIC18 в файле int18XXX.h
За сохранение контекста (регистров) отвечают макросы
int_save_registers
а за восстановление макросы
int_restore_registers
Эти макросы выполняют различные действия в зависимости от используемых микроконтроллеров. Дополнительно, для PIC18, существует директива
#pragma fastMode
Однако, я не буду ее подробно рассматривать, как и собственно использование fast return stack, в статьях для начинающих.
Таким образом, общая структура обработчика прерываний для PIC будет примерно такой
Обратите внимание, что сброс флага запроса прерывания T0IF приведен лишь в качестве примера. В реальном обработчике должен сбрасываться тот флаг запроса, прерывание которого обрабатывается.
Для PIC18 все выглядит очень похоже. Только обработчика будет два
Однако, остается открытым вопрос определения источника прерывания. Ведь у нас нет отдельных векторов. Здесь все просто, мы должны проанализировать состояние флагов запросов прерываний. Очередность их анализа будет одновременно и некоторым программным аналогом приоритетов.
Однако, мы не можем анализировать только флаги запросов. Нужно анализировать и флаг запроса, и бит разрешения прохождения запроса. Иначе мы можем допустить ошибку начав обрабатывать запрос прерывания у которого флаг установлен, но вот прохождение запроса прерывания запрещено.
Здесь я показал обработку прерываний от Timer0 и ADC (для PIC16F675). Обратите внимание на то, что для прерывания АЦП проверяется установка двух бит разрешения прохождения запроса. Та как это прерывание относится к группе "периферийных".
Заключение
Реализация прерываний в PIC, включая PIC18, довольно проста. При этом использование языка С, даже с учетом специфичности компилятора, позволяет не обращать особого внимания на различие вариантов сохранения контекста, а сосредоточиться на прикладной задаче.
В дальнейшем мы будем достаточно активно использовать прерывания. Тогда и рассмотрим, детально, практические процедуры их обработки. Уже на реальных примерах.