Прерывания, события и NVIC

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

button schematic

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

Допустим, вы попросили объяснить своего друга, что же такое прерывание. В следующий момент звонит ваша возлюбленная, и вы со словами: «Прости, мне надо ответить...», – берете трубку. Закончив беседу, вы возвращаетесь к понятию о прерывании и ждете, когда ваш друг даст определение. Всё, что ему нужно сказать — «Собственно, это оно и было». Другими словами, основная программа останавливается (при этом текущее состояние сохраняется в стек), и начинает работать другой участок кода, который называется обработчиком (англ. handler) прерывания. По завершении выполнения обработчика программа возвращается на то место, где была прервана, и продолжает свою работу.

handler

В действительности всё немножко сложнее. Помимо понятия прерываний, существует понятие события (англ. event). При изучении документации может сложиться ложное предположение, что они не отличаются друг от друга. Используя теорию множеств, ситуацию можно разъяснить следующим образом:

interrupt event

Любое прерывание вызывается событием, но не любое событие вызывает прерывание.

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

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

Все имена существующих векторов прерываний описаны в файле startup_<mcu>.s1. Мы их уже видели, когда рассматривали библиотеку CMSIS. По-умолчанию все обработчики — это заглушки, бесконечный цикл.

Если прерывание разрешено, а обработчик не описан программистом, то микроконтроллер зависнет.

Правило работы с прерываниями

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

Модуль NVIC имеет несколько регистров.

ИмяЗначениеАдресТип
ISER0 ... ISER70x000000000xE000E100 ... 0xE000E11CRW2
ICER0 ... ICER70x000000000xE000E180 ... 0xE000E19CRW
ISPR0 ... ISPR70x000000000xE000E200 ... 0xE000E21CRW
ICPR0 ... ICPR70x000000000xE000E280 ... 0xE000E31CRW
IABR0 ... IABR70x000000000xE000E300 ... 0xE000E31C
IPR0 ... IPR70x000000000xE000E400 ... 0xE000E41CRW
STIR0x000000000xE000EF00WO

Таблица из документа ARM® Cortex®‑M3 Processor Technical Reference Manual

Рассмотрим их по порядку:

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

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

  1. разрешить глобальные прерывания в NVIC;
  2. настроить и разрешить конкретные прерывания (по конкретному событию);
  3. описать обработчик прерывания.

Для глобального разрешения/запрета на выполнение прерываний библиотека CMSIS предлагает две интрисик-функции (core_cmFunc.h).

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

Что бы включить прерывание по определённому вектору CMSIS так же предлагает специальные функции, которые мы уже упомянали, когда говорили про данную библиотеку.

IRQn_Type – это перечисление объявленное в файле stm32f10x.h, которое ставит в соответсвие вектор и его номер (позицию).

Для работы с ISPR и ISPR регистрами предназначены следующие функции:

А получить номер (позицию) активного прерывания можно функцией:

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

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

Системные прерывания по-умолчанию имеют наивысший уровень, а все остальные более низкий и одинаковый. Т.е. одно внешнее прерывание не может вытеснить другое внешнее прерывание. Если во время обработки сообщения по UART, произойдёт прерывание от АЦП, то оно будет ждать завершения прерывания от UART. (Выполняться они будут от меньшего номера к старшему)

По стандарту ядро позволяет иметь до 240 уровней приоритетов.

По факту в STM32 имеют силу только первые четыре бита, что даёт всего 16 (24) уровней!

Чем меньше число, тем выше приоритет!

Приоритеты можно устанавливать напрямую, через соответствующую функцию.

Либо группировать их.

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

ГруппыПриоритетПодприоритет
SCB_AIRCR_PRIGROUP_000-15
SCB_AIRCR_PRIGROUP_10-10-7
SCB_AIRCR_PRIGROUP_20-30-3
SCB_AIRCR_PRIGROUP_30-70-1
SCB_AIRCR_PRIGROUP_40-150

Группы задаются через отдельный регистр Application Interrupt and Reset Control Register

В стандартной библиотеке периферии работа с модулем NVIC осуществляется через структуру (определена в файле misc.c).

Далее её следует передать в функцию NVIC_Init() для инициализации.

Время перейти к работе с кнопкой.


Назад | Оглавление | Дальше


1 По-умолчанию вся таблица копируется в начало прошивки, но её можно смещать, например при организации загрузчика, пока что мы данную возможность рассматривать не будем.
2 RW сокращение от Read/Write, т.е. регистр можно как считывать, так и записывать в него. WO сокращение от Write Only, т.е. только для чтения.