Контроллер прерываний в STM8 самый сложный из всех рассматриваемых нами микроконтроллеров. В нем добавлен изменяемый программно уровень приоритета, причем программный приоритет более значим, чем аппаратный в большинстве случаев. Кроме того, контроллеры прерываний в STM8S/STM8A и STM8L различаются, хоть и имеют много общего.
Есть в STM8 и программные прерывания (не путать с программными приоритетами прерываний), которые вызываются командой TRAP. Причем это немаскируемые прерывания. Так что сегодня скучно точно не будет.
В документации на STM8 тоже не приводятся даже функциональные схемы обработки прерываний, как и для AVR. Поэтому в этой статье многие иллюстрации будут нарисованы мной, на основе личного опыта. Учитывайте это и не считайте все иллюстрации частью официальной документации.
Типы прерываний STM8
В STM8 есть несколько различных типов прерываний:
- Внешние прерывания. В данном случае практически любой вывод, любого порта ввода-вывода, может использоваться как вход внешних прерываний. В STM8 нет выделенных входов внешних прерываний, как это было в PIC или AVR. При этом нет и понятия "прерывание по изменению состояния вывода порта", как это было в других рассматриваемых нами микроконтроллерах. В STM8 все управляется именно контроллером прерываний.
- Внутренние прерывания. Это прерывания от внутренних периферийных модулей микроконтроллера. Причем, в большинстве случаев, модуль выставляет один запрос прерывания не смотря на множество внутренних флагов запросов и состояний. И с этой точки зрения ситуация очень похожа на "много источников, один обработчик", которую мы уже подробно рассматривали.
- Программные прерывания. Сюда относится не только команда TRAP, но и сброс. Причем сброс может быть и аппаратным. Эти прерывания нельзя замаскировать!
- Немаскируемое высокоуровневое внешнее прерывание. Это прерывание, TLI (Top Level hardware Interrupt), является внешним прерыванием, которое, в отличии от обычных внешних прерываний, нельзя замаскировать.
Можно рассматривать и деление прерываний на маскируемые и немаскируемые.
Флаги запросов прерываний и сигналы запросов прерываний
Как я уже сказал, несколько флагов запросов могут объединяться (и зачастую объединяются) в единый запрос прерывания, который и поступает на вход контроллера прерываний. Поэтому в STM8 нельзя считать тождественными понятия флага запроса и сигнала запроса на входе контроллера прерываний, как мы это делали для других микроконтроллеров. Вот так это можно представить, на примере таймера TIM1.
Таймер TIM1 может формировать сразу два различных сигнала запроса. Какой именно флаг запроса привел к возникновению прерывания (или несколько флагов) должен выяснять обработчик прерывания обращаясь к регистрам таймера.
Сигналы запроса автоматически не сбрасываются при вызове обработчика, это нужно делать вручную. Как именно выполнить сброс запроса соответствующего флага следует смотреть в документации на периферийный модуль. Иногда требуется явный сброс бита, иногда достаточно простого чтения какого либо регистра.
Обратите внимание, что все показанные на иллюстрации входные сигналы принадлежат именно модулю таймера и соответствуют битам его управляющих регистров. Как именно работают эти биты и каким процессам в таймере соответствуют, в рамках данной статьи мы не рассматриваем. К контроллеру прерываний имеют отношение только два выходных сигнала (для случая показанного на иллюстрации).
Векторы прерываний
Каждый сигнал запроса аппаратно и однозначно связан одним из векторов прерываний. В STM8 один вектор занимает 4 байта. При этом, в отличии от PIC и AVR, вектор содержит не машинные команды, а адрес обработчика прерывания! Помните, я говорил о том, что векторы бывают разные?
Микроконтроллеры для начинающих. Часть 57. Прерывания
Правда тут есть одна тонкость. Адрес обработчика все таки хранится в специальном формате, в виде команды INT, которая может использоваться только в таблице векторов прерываний, и нигде более. По сути, эта команда просто заносит свой операнд в регистр PC и ее можно рассматривать как префикс, признак того, что далее следует 24-битный абсолютный адрес обработчика. Таблица векторов прерываний содержит именно, и только, адреса обработчиков в формате этой команды.
То, что команда INT по сути является лишь префиксом и важен лишь адрес обработчика свидетельствует и иллюстрация общей процедуры обработки прерывания, которая выполняется аппаратно. Эта иллюстрация неудачна, на мой взгляд, но я все таки ее приведу
Обратите внимание на фразу "LOAD PC FROM INTERRUPT VECTOR", которая говорит, что процессор загружает в регистр PC адрес обработчика из вектора, а не выполняет команду INT в явном виде.
Таблица векторов, как это почти всегда бывает, располагается в начале памяти программ. Напомню, что память программ располагается в адресном пространстве STM8 начиная с адреса 0x008000. Всего определено место для 32 векторов прерываний.
Обратите внимание, что два первых элемента таблицы прерываний, векторы RESET и TRAP, которые размещаются по адресам 0x008000 и 0x008004 номеров не имеют! Поэтому вектору 0 (TLI) соответствует адрес 0x008008 в таблице прерываний.
Приоритеты
Итак, каждый сигнал запроса связан с определенным вектором. Каждый вектор имеет определенный адрес в таблице векторов. Но каждый вектор прерывания имеет еще один атрибут, который нам ранее не встречался - приоритет, который определяется программистом. Если быть более точным, то приоритет имеет не вектор, а сигнал запроса. Но поскольку они однозначно связаны между собой, то в документации говорится именно о векторе. И я буду следовать этому правилу.
Всего имеется 4 уровня приоритета. Самый низкий уровень 0, который называется основным (main). Он условно соответствует состоянию "прерывания разрешены" других микроконтроллеров. Самый высокий уровень 3. Он условно соответствует состоянию "прерывания запрещены".
Для процессора определяется понятие текущего уровня приоритета. Этот уровень определен не только при обработке прерываний, но и при выполнении основной программы. То есть, всегда. Текущий уровень приоритета находится под контролем программиста и может быть изменен в любой момент.
Текущий уровень приоритета процессора определяется двумя битами регистра CCR (Condition Code register)
Обратите внимание, что эти биты не соответствуют двоичному коду уровня приоритета!
Когда в контроллер прерываний поступает сигнал запроса, он сравнивает приоритет сигнала запроса (вектора, соответствующего сигналу) и текущий приоритет процессора. Если приоритет вектора выше, то выполняется процедура перехода к обработчику прерывания. При этом текущий приоритет процессора автоматически устанавливается равным приоритету вектора, по которому выполняется переход к обработчику прерывания.
Если приоритет сигнала запроса не превышает текущего приоритета процессора, то запрос переводится в состояние "отложен" до того момента, пока приоритет процессора не понизится и не станет меньше приоритета запроса.
Если на входе контроллера прерываний присутствуют несколько запросов (не важно, поступивших одновременно, или с учетом ожидающих), то контроллер выбирает запрос самого высокого программного приоритета, который и обрабатывается по описанным выше правилам.
Если несколько запросов имеют одинаковый программный приоритет, то контроллер прерываний дополнительно учитывает и аппаратный уровень приоритета, который определяется номером вектора. Чем меньше номер, тем выше приоритет.
При этом векторы сброса (RESET), TLI, TRAP считаются имеющими высший приоритет. Обработчики этих векторов вызываются вне зависимости от текущего уровня приоритета процессора.
При этом система приоритетов является не только гибкой, но и обеспечивает совместимость с классической обработкой прерываний, где приоритеты не используются, а есть только разрешение и запрет прерываний. Для этого необходимо установить текущий приоритет процессора равным уровню 0, а приоритеты всех векторов прерываний равными уровню 3.
После сброса текущий уровень процессора устанавливается равным уровню 3 (прерывания запрещены). Приоритеты всех векторов тоже устанавливаются равными уровню 3.
Приоритеты векторов прерываний задаются с помощью регистров ITC_SPRx. Всего таких регистров 8. Каждому вектору прерываний соответствует 2 бита
Уровень приоритета определяется этими двумя битами точно так же, так и текущий уровень приоритета процессора. Однако, здесь есть несколько особенностей.
Во первых, приоритет вектора TLI автоматически установлен на уровень 3, то есть, обработчик TLI прерван быть не может. Однако, программист может изменить текущий уровень приоритета процессора, что позволит даже обработчику TLI быть прерванным. Это будет действовать до явного ручного изменения текущего приоритета или до выполнения команды IRET
Во вторых, приоритет вектора прерывания не может быть установлен на уровень 0. Если же вы все таки попытаетесь это сделать, то ранее заданный уровень приоритета вектора не изменится, сохранится старое значение.
Таким образом, в STM8 можно гибко управлять приоритетами прерываний (режим вложенности прерываний), или отказаться от системы приоритетов (конкурентный режим), сведя все к классической схеме разрешения/запрета прерываний. Причем по умолчанию, после сброса, контроллер прерываний фактически работает в конкурентном режиме.
Аппаратный уровень приоритетов вмешивается лишь при совпадении программных уровней в режиме с вложенностью прерываний. Но однозначно определяет очередь обработки прерываний в конкурентном режиме.
Обработка запросов внешних прерываний
До этого момента мы рассматривали обработку прерываний, как одинаковую во всех моделях микроконтроллеров. Однако, теперь мы должны начать учитывать и существенные отличия. И это касается отнюдь не количества используемых (а не зарезервированных) векторов прерываний, и не соответствия сигналов запросов номерам и адресам векторов.
Основные различия касаются обработки внешних запросов прерываний. Для начала давайте рассмотрим, как это работает в STM8S
Обработка внешних прерываний в STM8S
Как я говорил ранее, практически каждый вывод порта ввода-вывода может служить входом внешних запросов прерываний. Всего в STM8S может быть до 5 портов, что дает нам до 5 сигналов внешних запросов прерываний.
Дело в том, что все выводы порта сводятся в один сигнал запроса прерывания. При этом, для каждого вывода можно определить, участвует ли он в формировании запроса прерывания. Но вот какому входному сигналу будет соответствовать формирование запроса определяется лишь для каждого порта в целом, а не для отдельного вывода.
Тип входного сигнала для каждого порта задается в регистрах EXTI_CR1 и EXTI_CR2. Режим работы определяется двумя битами, принцип размещения бит такой же, как и для регистров ITC_SPR. Регистр EXTI_CR1 отвечает за порты PA-PD, а регистр EXTI_CR2 за порт PE и вывод TLI. Для TLI используется только один бит, который определяет нарастающий или спадающий фронт сигнала.
А вот для каждого порта возможны варианты:
- 00 - спадающий фронт и низкий уровень
- 01 - только нарастающий фронт
- 10 - только спадающий фронт
- 11 - нарастающий фронт и спадающий фронты
При этом выполнить запись в регистры EXTI_CR1 и EXTI_CR2 можно только при работе процессор на уровне приоритета 3, то есть, при запрещенных прерываниях.
Как видно, здесь нет варианта "прерывания не используются". Все верно, за это отвечают регистры порта. Вот упрощенная схема формирования запросов внешних прерываний, без учета типа сигнала
Таким образом, при использовании внешних запросов прерываний требуется совместная настройки и контроллера прерываний, и портов. При этом определить, какой именно вывод порта (или несколько выводов) вызвали прерывание можно только программно, прочитав регистр Px_IDR. Если конечно входной сигнал еще присутствует на выводе.
Обработка внешних прерываний в STM8L
В микроконтроллерах STM8L обработка внешних запросов прерываний значительно более гибкая, но и значительно более сложная. И дело даже не в том, что здесь может быть до 9 портов.
Дело в том,что здесь кроме единого запроса прерывания для порта в целом, возможно и формирование запроса для определенного бита сразу нескольких портов.
Таким образом, у нас появляются такие сигналы внешних запросов прерываний:
- EXTIB/G - запрос прерываний от любого из выводов порта PB или PG
- EXTID/H - запрос прерываний от любого вывода порта PD или PH
- EXTIE/F - запрос прерываний от любого вывода порта PE или PF
- EXTI0 - EXTI7 - запросы прерываний от соответствующего бита портов PA/PB/PC/PD/PE. А для EXTI0 еще и PF.
Регистры EXTI_CR1 и EXTI_CR2 теперь определяют не тип сигнала прерывания для портов, а тип сигнала для отдельных выводов портов. За каждый вывод отвечают два бита. Причем выбор типа сигнала определяется комбинацией этих бит точно так же, как это было в ST8S для портов в целом.
А настройка типа сигнала для портов в целом (PB, PD, PE, PF, PG, PH) переехала в регистры EXTI_CR3 и EXTI_CR4. Как и всегда, за каждый порт отвечают два бита.
Регистры EXTI_CRx можно записывать, как и в случае STM8S только при работе процессора на 3 уровне приоритета.
В STM8L добавлены и регистры флагов, которые позволяют определить, что именно вызвало внешнее прерывание. Таких регистров два.
Регистр EXTI_SR1 содержит флаги для отдельных выводов все портов - PnF. А регистр EXTI_SR2 содержит флаги для всех выводов отдельных портов - PxF. Это позволяет программе определить конкретный источник внешнего прерывания.
А вот какое именно прерывание будет сформировано для разных комбинаций флагов определяется регистрами EXTI_CONF1 и EXTI_CONF2. Описание этих регистров довольно громоздкое, и я не буду полностью его приводить. Ограничусь лишь примерами для комбинаций портов PE и PF.
Так бит PFES (EXTI_CONF1) определяет какой именно порт будет участвовать в формировании запроса прерывания, PF или PE. Бит PELIS (EXTI_CONF1) определяет, какие прерывания будут вызывать младшие 4 бита порта PE. Возможно или формирование прерывания EXTIE (для порта в целом), или одного из прерываний EXTI0-EXTI3. Бит PEHIS (EXTI_CONF1) определяет прерывания формируемые старшими 4 битами порта PE. Это либо прерывание EXTIE, или одного из прерываний EXTI4-EXTI7. Точно так же, существуют биты PFLIS (EXTI_CONF1) и PFHIS (EXTI_CONF2), которые определяют поведение младшей и старшей тетрад порта PF.
При этом вывод порта, как и в STM8S должен быть настроен как вход с прерываниями. Для этого используются регистры портов, а не контроллера прерываний.
Я позволю себе не рисовать иллюстрацию для обработки внешних прерываний в STM8L, ограничусь лишь этим словесным описанием. В наших учебных проектах мы будем в основном использовать STM8S, как более простые для начинающих. Если же я все таки решу использовать в учебном проекте STM8L, причем с внешними прерываниями, я уделю описанию гораздо больше внимания.
Задержки обработки прерываний
Как и в других микроконтроллерах, выполнение команды прервано быть не может. Это дает возможную задержку до начала процесса обработки прерывания от 1 до 6 циклов. Еще 9 циклов занимает сохранение регистров процессора в стеке и переход по адресу обработчика прерывания. Таким образом, обработчик получает управление через 10-15 циклов.
Выход из обработки прерывания, восстановление регистров и передача управления занимаю 9 циклов.
Кроме того, нужно обязательно учитывать, что и переход к обработчику, и выход из него, приводят к очистке конвейера. Это может привести к вставке дополнительных тактов ожидания выборки/декодирования. Причем при выполнении программы из ОЗУ моет оказаться более медленным, чем при выполнении из памяти программ. Не смотря на более высокое быстродействие ОЗУ имеет лишь 8-битную шину данных, вместо 32-битной у памяти программ.
Программный аспект
В STM8 перед переходом к обработчику прерывания автоматически сохраняются регистры CCR, A, X, Y, PC. Кроме того, текущий уровень приоритета процессора устанавливается равным приоритету вектора. При выходе из обработчика регистры восстанавливаются из стека.
После сброса микроконтроллера уровень приоритета процессора равен 3, что соответствует запрету прерываний. Разрешить прерывания можно командой RIM, которая устанавливает 0 уровень (полная прерываемость) приоритета процессора. Запретить прерывания можно командой SIM, которая устанавливает 3 уровень приоритета процессора.
Уровни приоритета 1 и 2 можно установить только прямым сбросом/установкой бит I1 и I0 в регистре CCR. Уровни 0 и 3 тоже можно установить напрямую работая с битами, просто команды RIM и SIM удобнее.
Обработчик прерывания должен завершаться выполнением команды IRET.
Напомню, что автоматический сброс запросов прерываний в STM8 не производится. Программист должен сам сбросить запрос сбросом соответствующего флага, или флагов, запросов в регистрах периферийных модулей. Как и что нужно делать, следует искать в описании соответствующего модуля.
Поскольку в цикле статей предполагается, что большинство читателей будет использовать язык С при разработке программ, посмотрим, как реализуются процедуры обработки прерываний при использовании компилятора SDCC. Предполагается, что версия компилятора не ниже 4.0.
Сначала важное замечание! Обработчики прерываний могут находиться в любом исходном файле, при компоновке все разместится как нужно. Однако, в исходном файле с функцией main должен быть указан, как минимум, прототип (описание, не определение) всех обработчиков.
Сама функция обработки прерывания определяется как обычная функция, но с дополнительным модификатором __interrupt, который содержит номер (не адрес!) вектора. Вот небольшой пример
void PortA_ISR() __interrupt(3) {
. . .
}
Номер вектора, в данном случае 3, используется указанный в документации на микроконтроллер. Для микроконтроллера STM8S103F3K3 вектору EXTI0 (внешние прерывания порта A) соответствует именно вектор 3.
Компилятор автоматически использует для такой функции пролог и эпилог обработчика прерывания и разместит адрес ее адрес в нужном месте таблицы прерываний в формате команды INT.
Однако, приоритет вектора прерывания не хранится в таблице, он указывается в одном из регистров EXTI_SPRx в виде битового поля длиной 2 бита. Компилятор не выполняет автоматическую установку приоритета вектора, это должен сделать программист работая с регистрами EXTI_SPRx напрямую. Причем до разрешения прерываний!
На плечи программиста ложится не только настройка приоритетов, но вся дополнительная настройка контроллера прерываний и периферийных модулей микроконтроллера. Обычно, это делается в самом начале программы. В дальнейшем, микроконтроллер будет автоматически управлять вызовами обработчиков и отслеживанием их приоритетов. Дополнительных усилий (как разрешение прерываний внутри обработчика в AVR, например) от программиста здесь не потребуется.
Для обработчика прерывания от команды TRAP используется немного иной формат определения. Вместо модификатора __interrupt необходимо использовать модификатор __trap
void TRAP_ISR() __trap {
. . .
}
Заключение
Мы рассмотрели реализацию обработки прерываний в микроконтроллерах STM8. Это самая сложная реализация из всех рассматриваемых нами микроконтроллеров. Поэтому я и стал рассказывать о ней в последнюю очередь, после знакомства с PIC и AVR. Ранее изученное значительно облегчает понимание того, как все это работает в STM8.
Множество регистров, параметров настроек, программные и аппаратные приоритеты, может показаться слишком сложным для новичков. Однако, все организовано очень логично. Даже обработка внешних прерываний в STM8L. Затрудняет понимание необходимость учитывать еще и множество настроек периферийных модулей, которые связаны с прерываниями. Одни таймеры чего стоят. Но это следствие большой гибкости и функциональности.
Я постарался в статье выделить работу именно контроллера прерываний. Максимально абстрагируясь от деталей формирования запросов периферийными модулями. Надеюсь, это немного облегчит задачу новичкам.
В следующей статье мы начнем заниматься вторым учебным проектом. В нем будут использоваться и таймеры, и прерывания, и порты ввода-вывода. Кроме того, мы будем использовать внешний кварцевый резонатор для стабилизации тактовой частоты. Надеюсь, будет интересно