Внешние прерывания

Внешние прерывания

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

 
Все прерывания можно разделить на две большие группы – внутренние и внешние.

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

Внутренние прерывания, называемые также синхронными, происходят внутри микроконтроллера и являются результатом предсказуемых событий, происходящих в ядре или периферийных модулях. Момент возникновения внутренних прерываний можно предсказать. Примером внутреннего прерывания является прерывание при переполнении таймера, рассмотренное на прошлом уроке.

Поскольку встроенный контроллер прерываний может обрабатывать до 66 источников прерываний, возникает вопрос, в каком порядке осуществлять обработку прерываний происшедших одновременно? И что делать если во время обработки одного прерывания происходит прерывание от другого источника?

Для разрешения этой ситуации встроенный контроллер прерываний имеет возможность задать до 16 уровней приоритетов. Если во время обработки прерывания происходит прерывание с более высоким приоритетом, то текущее выполнение обработчика прерывания приостанавливается, и управление передается обработчику прерывания с более высоким приоритетом.

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

Встроенный контроллер прерываний отводит 4 бита для задания уровня приоритета, причем эти 4 бита могут быть разделены на группу и подгруппу.
В первую очередь анализируется приоритет группы, и именно он определяет, может ли одно прерывание приостанавливать обработчик другого. Затем приоритет подгруппы анализируется внутри каждой группы. Если одновременно придут на исполнение два прерывания из одной группы, то приоритет подгруппы определит очередность их обработки. Но прерывать друг друга прерывания из одной подгруппы уже не могут. Если одновременно придут два прерывания из одной группы и одной подгруппы, то вызов пойдет по порядку их описания в таблице векторов.
Чем меньше значение группы или подгруппы прерывания, тем выше его приоритет.

Давайте перейдем к практической задаче и к проекту прошлого урока добавим еще обработчик внешнего прерывания по нажатию кнопки.

Откроем конфигурацию проекта прошлого урока в STM32CubeMX и для вывода PA0 во вкладке Pinout выберем назначение GPIO_EXTI0. Перейдем во вкладку Configuration и займемся настройкой внешнего прерывания.
Конфигурация внешнего прерывания на линии PA0

Выбираем настройку модуля GPIO. Для линии PA0 изменилась возможность выбора режима. Можно выбрать Interrupt – прерывание или Event – событие, не вызывающее прерывания, а также фронт, по которому будет возникать прерывание.
Rising edge – это нарастающий фронт, то есть переход сигнала на выводе из логического нуля в единицу.
Falling edge – это спадающий фронт, то есть переход сигнала на выводе из единицы в ноль.

Можно, например, настроить прерывание, чтобы оно возникало по каждому фронту. Оставим все по умолчанию – будем обрабатывать внешнее прерывание по нарастающему фронту.
Конфигурация типа прерывания

Теперь перейдем к настройке контроллера прерываний. Выбираем NVIC, и напротив EXTI line0 interrupt ставим галочку.
Разрешение внешнего прерывания

Генерируем код проекта и открываем проект в IAR. В файле обработчиков прерываний у нас появилась дополнительная функция, обрабатывающая внешнее прерывание по нажатию кнопки.
Пусть нажатие кнопки запускает или останавливает бегущий огонь.

Добавим в файл stm32f3xx_it.c в обработчик внешнего прерывания вот такую строчку.

1
2
3
4
5
6
7
8
9
void EXTI0_IRQHandler(void)
{
  /* USER CODE BEGIN EXTI0_IRQn 0 */
  f ^= 2;
  /* USER CODE END EXTI0_IRQn 0 */
  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
  /* USER CODE BEGIN EXTI0_IRQn 1 */
  /* USER CODE END EXTI0_IRQn 1 */
}

Добавлена строка 4.

Это – поразрядная операция «исключающее ИЛИ».
Давайте сделаем небольшое лирическое отступление и рассмотрим таблицы истинности основных логических функций.
Для двух битов А и В функция логического И единична только в том случае когда оба бита единичны. Функция логического ИЛИ единична когда хотя бы один из рассматриваемых битов единичен. А функция исключающего ИЛИ единична в случае если из двух рассматриваемых битов имеется только одна единица.

a b a & b a | b a ^ b
0 0 0 0 0
0 1 0 1 1
1 0 0 1 1
1 1 1 1 0

Что же можно сказать о нашем случае? Рассматриваемая переменная до сих пор принимала одно из двух значений, 0 или 1. А мы производим операцию исключающего или со значением, равным 2. Вот так это будет выглядеть в двоичном коде.

00000001 ^ 00000010 = 00000011

00000011 ^ 00000010 = 00000001

00000000 ^ 00000010 = 00000010

00000010 ^ 00000010 = 00000000

Младший, нулевой, разряд мы не трогаем, а производим операцию со следующим, первым, разрядом. Если в первом разряде был ноль, то после исключающего или с единицей в этом разряде, в нем получится 1. В итоге значение переменной может получиться равным 2 или 3 в зависимости от состояния младшего бита.
Если в первом разряде была единица, то операция исключающего ИЛИ с единицей даст в этом разряде ноль. В итоге значение переменной будет 0 или 1 в зависимости от младшего разряда. Напомню, что в основной функции у нас анализируется значение переменной на значение, равное 1. Поэтому в случае равенства этой переменной 2 или 3 условие не будет выполнено, и бегущий огонь остановится.

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

А теперь давайте поиграем с приоритетами прерываний. Для начала поставим точки останова в обработчиках прерывания таймера и кнопки и снова запустим программу на выполнение. Выполнение остановилось на обработке прерывания таймера. А теперь зажмем кнопку на плате и выполним следующий шаг программы, нажав F10. Выполнение обработчика прерывания продолжилось, и мы перешли к следующей команде. Теперь выполним программу до следующей точки останова. Кнопку на плате можно отпустить. И мы видим, что после обработки прерывания таймера программа перешла к обработке прерывания кнопки. Несмотря на то, что кнопка в этот момент уже не нажата, произошедшее прерывание дождалось своей очереди на обработку.

Теперь поменяем приоритет прерывания таймера. Остановим отладку и перейдем к конфигуратору STM32CubeMX. В настройке контроллера прерываний NVIC понизим приоритет прерывания таймера, выбрав для него, например 1 вместо 0. Напомню, что приоритет, равный 0, является наивысшим.
Установка приоритета прерывания

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

Прокрутить вверх