Как работают прерывания выводов GPIO (ввода/вывода общего назначения)
В данной статье описываются прерывания GPIO, включая примеры прерываний и их различных функций. Это продолжение предыдущей статьи, в которой объясняются концепции параллелизма и прерываний для микроконтроллеров.
Что делает периферийное устройство GPIO?
Периферийное устройство GPIO способно обнаруживать (или «определять») четыре вещи: является ли состояние на выводе 1 или 0, и изменилось ли состояние с 0 на 1 или с 1 на 0.
Обнаружение разных событий бывает полезно. Например, если я подключу геркон к двери, то моя программа на микроконтроллере на основании изменения состояния на выводе, к которому подключен геркон, может определить, открылась ли только что дверь или закрылась (рисунок 1).
Сначала я объясню, как работают прерывания, предполагая, что всё настроено правильно, а затем мы рассмотрим различные составляющие, которые необходимо правильно настроить для работы прерывания.
Флаг периферийного прерывания
Давайте предположим, что аналогично предыдущему примеру, событие, которое мы пытаемся обнаружить, – это изменение состояния на выводе с 1 на 0. Внутри периферийного устройства GPIO будет находиться аппаратное обеспечение, которое обнаруживает это изменение и показывает, что это изменение произошло, установив так называемый флаг прерывания для этого вывода в 1.
Это показано на рисунке 2.
Контроллер прерываний и флаги контроллера прерываний
В микроконтроллере много периферийных устройств, и каждое из них может иметь свой собственный набор прерываний. Большинство микроконтроллеров имеют аппаратную часть, которая обычно называется контроллер прерываний, который управляет всеми прерываниями, поступающими от периферийных устройств, решает, какое прерывание запускается и прерывает работу процессора, чтобы заставить его выполнить правильный обработчик прерывания.
Как правило, контроллер прерываний имеет список возможных прерываний, а также их соответствующий приоритет. Наше периферийное устройство GPIO может иметь в списке, хранящемся в контроллере прерываний, одно или несколько прерываний.
Например, у CC2544 есть группа из восьми выводов, которые являются частью GPIO, которая называется PORT0. Каждый вывод обозначен как P0_0, P0_1 и т. д. до P0_7. Хотя каждый вывод имеет свой собственный флаг прерывания, у контроллера прерываний есть только одно прерывание для всего порта, P0INT. Всякий раз, когда в периферийном устройстве GPIO устанавливаются какие-либо флаги выводов, в контроллере прерываний также устанавливается флаг для всего порта.
Обратите внимание, что здесь есть два флага, один – флаг для конкретного вывода, который является частью периферийного устройства GPIO, а другой – флаг прерывания для всего порта, который является частью контроллера прерываний. Это показано на рисунке 3.
Таблица векторов
Многие микроконтроллеры используют для прерываний так называемый векторный подход. При таком подходе в памяти имеется таблица векторов, в которой для каждого прерывания указывается адрес, по которому находится обработчик прерывания (ISR), который должен выполнить центральный процессор (CPU) для этого конкретного прерывания. Этот адрес обычно называется вектором прерывания.
Например, у CC2544, который использует архитектуру микроконтроллера 8501, вектор прерывания для PORT0 является адресом памяти 0x6B. Когда контроллер прерываний сообщает CPU, что есть прерывание от определенного вектора, CPU выполняет некоторое сохранение и затем начинает выполнять обработчик прерывания из этого вектора прерывания. Это показано на рисунке 4.
Настройка поведения прерываний
Периферийные устройства, такие как GPIO, обычно дают вам возможность настроить, какие события периферийного устройства будут генерировать прерывание. Для GPIO типовыми являются варианты, когда состояние изменяется с 0 на 1, когда состояние изменяется с 1 на 0, любое изменение состояния (т. е. с 0 на 1 или с 1 на 0, не имеет значения какое), или когда состояние остается в 1 или 0.
В зависимости от микроконтроллера это можно сделать для каждого вывода или для всех выводов порта. ATmega328P имеет два вывода, которые вы можете настроить индивидуально. Другие выводы GPIO по умолчанию обнаруживают любые изменения на выводе (с 0 на 1 или с 1 на 0). В предыдущей статье, где мы иллюстрировали работу прерываний, мы предполагали, что вывод был настроен на обнаружение изменений только с 1 на 0.
Кроме того, некоторые микроконтроллеры требуют, чтобы для установки флага прерывания при возникновении события интересующий нас вывод был сконфигурирован в качестве входа (например, CC2544). Другие (например, ATmega328P) установят флаг независимо от того, сконфигурирован ли вывод в качестве выхода или входа.
Маскирование прерываний
Общим термином, используемым для описания включения и отключения прерываний, является «маскирование». Как правило, существуют различные уровни, на которых прерывания могут быть отключены. Процессор может включать или отключать все прерывания, хотя обычно существуют критические прерывания, называемые немаскируемыми прерываниями, которые никогда не отключаются.
Отключение всех прерываний в CPU по существу останавливает связь между контроллером прерываний и CPU. Это означает, что при обнаружении события будут установлены и флаг вывода в периферийном устройстве GPIO, и флаг соответствующего прерывания в контроллере; однако центральный процессор не получит запрос прерывания. Это показано на рисунке 5.
Другой уровень, на котором можно маскировать прерывания, – это уровень контроллера прерываний. Здесь мы можем включить или отключить определенное прерывание внутри контроллера.
Пример прерывания CC2544
Будет полезен конкретный пример. Допустим, мы используем CC2544, и мы отключили PORT0. Допустим, вывод P0_3 изменил свое состояние, и поэтому в периферийном устройстве GPIO был установлен его флаг. В контроллере прерываний также будет установлен флаг прерывания PORT0, но контроллер прерываний проигнорирует его.
Это отличается от того, как центральный процессор отключает все прерывания, потому что контроллер прерываний все еще находится на связи с CPU. Так, если, например, для PORT1 разрешено прерывание, и на выводе P1_2 изменилось состояние так, чтобы его флаг был установлен на периферийном устройстве GPIO, а также был установлен флаг прерывания PORT1, контроллер прерываний прервет CPU для обработки этого прерывания.
Случай, когда контроллер прерываний игнорирует флаг вектора прерывания, показан на рисунке 6.
Большинство микроконтроллеров также позволяют маскировать прерывания на уровне периферийного устройства. Здесь мы можем включить или отключить прерывание для определенного вывода периферийного устройства GPIO.
Во всех микроконтроллерах, с которыми я сталкивался, флаг прерывания устанавливается всегда, когда на периферийном устройстве GPIO происходит искомое событие, независимо от того, включено или отключено прерывание для этого вывода. Например, если вывод P0_3 изменил свое значение так, как мы ищем, его флаг будет установлен в периферийном устройстве GPIO. Однако периферийное устройство GPIO не будет предупреждать контроллер прерываний об этом, поэтому флаг прерывания PORT0 в контроллере прерываний не будет установлен, и, поскольку нам необходимо установить этот флаг для прерывания CPU, прерывание не произойдет. Это показано на рисунке 7.
Когда прерывание маскируется, событие всё равно обнаруживается. Процессор просто не реагирует на него. Если флаг прерывания не сброшен, и прерывание полностью не маскируется, тогда CPU будет реагировать на него, если оно удовлетворяет всем другим условиям (кроме маскирования) для его выполнения. Прерывание, которое было обнаружено и ожидает, пока CPU выполнит его обработку, обычно называется ожидающим прерыванием.
На рисунке 8 показан случай, когда прерывание находится в состоянии ожидания, а затем не маскируется.
Напомним, чтобы размаскировать прерывание, чтобы процессор мог на него реагировать, когда все другие условия для прерывания выполнены, необходимо, чтобы:
- прерывание должно быть разрешено в периферийном устройстве (если применимо);
- также должно быть разрешено соответствующее ему прерывание в контроллере прерываний;
- все прерывания должны быть разрешены центральным процессором (т. е. должна быть разрешена связь между центральным процессором и контроллером прерываний для маскируемых прерываний).
Приоритеты прерываний
Иногда два или более события, которые приводят к прерываниям, происходят одновременно. Когда это происходит, поскольку CPU может обрабатывать только одно прерывание за раз, контроллеру прерываний нужен механизм, чтобы знать, какое из них должно идти первым, вторым и т. д. Контроллер прерываний обычно предоставляет конфигурацию, называемую приоритетом, которая позволяет пользователю через свой код указывать, какие прерывания имеют более высокий приоритет, а какие – более низкий. Большинство микроконтроллеров также предоставляет настройки по умолчанию для каждого прерывания.
Всякий раз, когда несколько событий происходят одновременно и приводят к нескольким прерываниям, ожидающим на контроллере прерываний, контроллер прерываний для обработки процессором выбирает прерывания с наивысшим приоритетом. Прерывания могут прерывать (или вытеснять) уже запущенные прерывания, поэтому, если CPU обрабатывает прерывание с более низким приоритетом, и происходит событие, связанное с прерыванием с более высоким приоритетом, контроллер прерывает CPU для обработки прерывания с более высоким приоритетом, и CPU после его выполнения возобновит обработку прерывания с более низким приоритетом, которое было ранее прервано. На рисунке 9 показано, как работают приоритеты и прерывания выполнения обработчиков прерываний.
- Флаги прерываний с более высоким и более низким приоритетами устанавливаются одновременно. Процессор выполняет прерывание с более высоким приоритетом, а затем прерывание с более низким приоритетом (поскольку его флаг все еще ожидает обработки, пока выполняется прерывание с более высоким приоритетом).
- Флаг прерывания с более высоким приоритетом устанавливается после того, как CPU начал обрабатывать прерывание с более низким приоритетом. Прерывание с более высоким приоритетом вытесняет прерывание с более низким приоритетом, и CPU выполняет обработчик для прерывания с более высоким приоритетом до полного завершения, прежде чем возобновить выполнение обработчика для прерывания с более низким приоритетом. Обратите внимание, что CPU возобновляет выполнение обработчика для прерывания с более низким приоритетом, даже если его флаг всё еще не установлен к моменту, когда он выполнит обработчик для высокоприоритетного прерывания. Это связано с тем, что после выполнения обработчика, если код не мешает нормальной обработке прерывания, CPU всегда возвращается к тому состоянию, в котором он находился до того, как начал выполнять обработчик. Это состояние, к которому он возвращается, может быть другим обработчиком прерывания.
- Флаг прерывания с более низким приоритетом устанавливается после флага с более высоким приоритетом. Поскольку прерывания с более низким приоритетом не могут вытеснять прерывания с более высоким приоритетом, CPU выполняет обработку прерывание с более высоким приоритетом до полного завершения, прежде чем реагировать на прерывание с более низким приоритетом.
Проверка и очистка флагов прерываний
Ранее мы видели, что для некоторых микроконтроллеров, таких как CC2544, когда код обработчика прерывания начинает выполняться, мы знаем только, какой порт вызвал прерывание, но не конкретный вывод. Например, если P0_3 изменяет свое значение, его флаг будет установлен внутри периферийного устройства GPIO, но центральный процессор выполняет обработчик прерывания только в ответ на флаг прерывания PORT0 от контроллера прерываний. Проверка флагов прерываний периферийного устройства GPIO внутри обработчика прерывания говорит нам, какой конкретный вывод вызвал прерывание, чтобы мы могли реагировать соответствующим образом.
Поскольку флаг прерывания указывает, что искомое событие произошло, пока установлен флаг прерывания, CPU будет реагировать на прерывание каждый раз, когда у него есть такая возможность. Например, допустим, что P0_3 изменил состояние только один раз и заставил установить его флаг прерывания. Если мы оставим флаг установленным, то после того, как CPU запустит обработчик прерывания, связанный с PORT0, он всё равно будет думать, что есть новое прерывание, поэтому он снова запустит обработчик.
Чтобы избежать этого, нам нужно очистить флаг прерывания. Иногда флаг прерывания автоматически очищается процессором, когда он запускает обработчик прерывания; в остальных случаях вы должны очистить флаг самостоятельно. Техническая документация на микроконтроллер позволит вам узнать, что делать в этом случае. Например, CC2544 автоматически не сбрасывает флаг для прерываний выводов, а ATmega328P – сбрасывает его автоматически. Если вам нужно очистить прерывание самостоятельно, это обычно первое, что вы делаете в коде обработчика, обычно сразу после выяснения того, прерывание по какому выводу вызвало выполнение обработчика.
Резюме. Получение прерывания GPIO для работы
Обобщим всё описанное выше. Чтобы прерывание GPIO работало с вашим кодом, вы должны:
- написать обработчик прерывания (ISR), внутри которого вы:
- обязательно очищаете все флаги, которые необходимо очистить;
- реагируете на прерывание необходимыми действиями;
- связать обработчик прерывания с правильным вектором прерывания;
- настроить, какое событие на выводе GPIO должно инициировать прерывание. Возможные параметры, которые могут быть недоступны для вашего конкретного микроконтроллера, – это только изменение с 0 на 1, только изменение с 1 на 0, любое изменение (с 0 на 1 или с 1 на 0), постоянное значение 1 или постоянное значение 0;
- включить прерывание для вывода внутри GPIO. Обычно перед включением прерывания вывода рекомендуется сбросить флаг для вывода;
- разрешить прерывание внутри контроллера прерываний;
- убедиться, что в CPU включены все прерывания.