Работа с внешними прерываниями

AVR Урок 42. EXINT или внешние прерывания

Урок 42

EXINT или внешние прерывания

Вот наконец-то и настало время нам попробовать поработать с внешними прерываниям. Данный урок, во-первых, был очень востребован, хотя он и кажется на первый взгляд несложным. Очень много было просьб и я не мог не откликнуться. Во-вторых, мы сейчас работаем с модулем LAN ENC28J60, у которго имеется выход, по которому мы можем получить прерывания по окончании определённых операций, которые я и хотел обработать. Также, если у меня получится, я хотел этим поделиться и с вами, но без первоначального представления о внешних прерываниях в контроллере AVR это понять, я считаю, будет, мягко говоря, нелегко. Вот поэтому и созрел данный урок.

Так как я случайно залочил свой контроллер ATMega328, расположенный на недорогой плате, то работать мы будем с платой Arduino UNO.

Нет, не пугайтесь, мы не будем работать со средой программирования Arduino IDE, мы также будем работать с тем же самым Atmel Studio, только Arduino мы будем использовать в качестве отладочной платы и будем его программировать через разъём ISP.

Так что вы можете использовать по-прежнему любые отладочные платы. Но если вдруг кто-то решит использовать так же, как и я, Arduino UNO или любую другую Arduino, то предупреждаю заранее – после этого уже работать с ней Вы не сможете через стандартную среду программирования – Arduino IDE, так как загрузчик мы в случае прошивания через ISP затираем. Но это не смертельно, так как его можно будет впоследствии восстановить. Правда это занятие не из лёгких, но если есть интерес к восстановлению загрузчиков Arduino после их затирания программированием через ISP, то я могу по этому поводу написать статью и дать видеоурок.

Вообщем, пока не приедут платы более мелкие, я буду свои уроки писать с применением Arduino UNO, также и с модулем LAN я также буду работать с той же платой.

Также чем оказался хорош Arduino, что к нему не требуется переходник TTL-USB, так как он там уже прекрасно аппаратно реализован. Причём, мало того, мы через штатный USB-разъём Arduino не только будем пользоваться USART-ом, но и питать наш Arduino будем через него же.

В качестве программатора мы также будем использоваь дешёвый USBASP. Так как он у меня с переключателем пиания 3,3В/5В, то убрав с него совсем перемычку, мы вообще отключим питание с программатора на плату и будем через ISP исключительно только программировать нашу плату.

Так как на плате Arduino находится 6-контактный разъём ISP, то будем использовать стандартный ISP-переходник

После подключения программатора к плате мы получим вот такую картину

Подключим раъём USB к компьютеру – теперь у нас есть готовый выход USART на ПК, а также и питание

Вернёмся к нашим прерываниям. Внешние прерывания – это такие прерывания, которые обрабатываются вследствие возникновения некоторых событий на определённой ножке порта микроконтроллера. Таких событий существует несколько. Прерывания у контроллера AVR могут срабатывать как по уровню, так и по фронту. По уровню они срабатывают, когда будет замечен определённый логический уровень. То есть, если будет настроено срабатывание по уровню логической единицы, то если данный уровень будет оставаться на ножке внешнего прерывания, то прерывание будет срабатыать циклично, пока не установится другой логический уровень. А по фронту – когда будет замечен переход из одного логического состояния в другое. Фронт, как известно бывает восходящий или нисходящий. Говорят ещё вместо восходящего просто фронт, а вместо нисходящего – спад. Также ещё называют передний и задний фронт. Вообщем – это кому как удобно.

Ножек для отслеживания внешних прерываний у контроллера существует две – INT0 и INT1. Первая ножка совпадает с ножкой порта PD2, а вторая – с PD3

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

Первый регистр – это регистр управления типом обрабатываемого события – EICRA

Пара битов ICS01 и ICS00 управляет типом событий на ножке INT0, а вторая – ICS11 и ICS10INT1

Вот зависимость типов событий от включения битов ICS01 и ICS00

А это для ICS11 и ICS10

Соответственно получим мы следющую зависимость:

00 – Низкий уровень на ножке,

01 – Любое изменение на ножке,

10 – нисходящий фронт на ножке,

11 – восходящий фронт на ножке.

Второй регистр – это регистр включения ножки прерываний EIMSK

В данном регистре существует всего два бита, разрешающих прерывания на соответствующих ножках – INT1 и INT0. Биты так и называются, поэтому не перепутаем.

Третий регистр – регистр флагов EIFR, устанавливаемых и сбрасываемых при определённых событиях

Напрямую мы к данному регистру не обращаемся, так как работать будем с функцией-обработчиком.

Теперь вроде что-то прояснилось по вопросу внешних прерываний.

Осталось дело за малым. Создать и прошить проект, который будет как-то наглядно демонстрировать работу внешних прерываний.

Запустим Atmel Studio 7, создадим проект с именем EXTI01, выберем контроллер ATmega328P, добавим в main.c функцию инициализации портов port_ini, в которой включим ножку светодиода на выход. Светодиод на плате располагается на ножке B6

void port_ini ( void )

//Включим ножку светодиода на выход

DDRB |= 0b00100000;

Вызовем данную функцию в main()

int main ( void )

port_ini ();

while (1)

Подключим кнопку между ножкой PD2 (D2) и общим проводом

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

void int_ini ( void )

//включим прерывания INT0 по нисходящему фронту

EICRA |= (1 ISC01 );

//разрешим внешние прерывания INT0

EIMSK |= (1 INT0 );

Вызовем данную функцию в main(), а также не забываем включить глобальные прерывания

int_ini ();

sei ();

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

//Включим ножку INT0 (PD2) на вход

//Подтянем резистор на ножке INT0 (PD2) к питанию

PORTD |= 0b00000100;

Также ещё нам необходимо добавить сам обработчик прерывания, в котором мы по возникновению настроенного события на ножке порта будем зажигать наш светодиод

ISR ( INT0_vect )

PORTB |= 0b00100000;

Соберём код, прошьём контроллер и попробуем нажать кнопку

Как мы видим, светодиод у нас загорелся.

Теперь давайте попробуем также испытать и другую ножку – INT1.

Для этого сначала настроим данную ножку в port_ini()

//Включим ножки INT0 и INT1 (PD2 и PD3) на вход

(0b00001100);

//Подтянем резисторы на ножках INT0 и INT1 (PD2 и PD3) к питанию

PORTD |= 0b00001100;

Затем перенастроим прерывания с учётом данной ножки и в функции int_ini()

//включим прерывания INT0 и INT1 по нисходящему фронту

EICRA |= (1 ISC11 )|(1 ISC01 );

//разрешим внешнее прерывание INT0 и INT1

EIMSK |= (1 INT1 )|(1 INT0 );

А вот обработчик у данной ножки свой отдельный, поэтому добавим его и светодиод в нём будем гасить

ISR ( INT1_vect )

Теперь мы можем подключить ещё кнопку на ножку PD3 (D3), воторым проводм также подключив её к общему проводу, собрать код, прошить контроллер и понажимать кнопки и отследить результаты изменения уровней по светодиоду

Теперь, если мы соберём код и прошьём контроллер, то первая кнопка будет у нас светодиод зажигать, а вторая тушить.

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

Читайте также  Антенна на подоконнике

Приобрести плату Arduino UNO R3 можно здесь.

Смотреть ВИДЕОУРОК (нажмите на картинку)

Аппаратные прерывания

Забавную картинку к этому уроку я найти не смог, нашёл только какую-то лекцию по программированию, и вот самое начало этой лекции отлично объясняет нам, что такое прерывание. Прерывание в Ардуино можно описать абсолютно точно так же: микроконтроллер “всё бросает”, переключается на выполнение блока функций в обработчике прерывания, выполняет их, а затем возвращается ровно к тому месту основного кода, в котором остановился.

Прерывания бывают разные, то есть не сами прерывания, а их причины: прерывание может вызвать аналогово-цифровой преобразователь, таймер-счётчик или буквально пин микроконтроллера. Такие прерывания называются внешними аппаратными, и именно о них мы сегодня поговорим. External hardware interrupt – это прерывание, вызванное изменением напряжения на пине микроконтроллера. Основная суть состоит в том, что микроконтроллер (системное ядро) не занимается опросом пина и не тратит на это время. Как только напряжение на пине изменяется (имеется в виду цифровой сигнал, +5 подали/+5 убрали) – микроконтроллер получает сигнал, бросает все дела, обрабатывает прерывание, и возвращается к работе. Зачем это нужно? Чаще всего прерывания используются для детектирования коротких событий – импульсов, или даже для подсчёта их количества, не нагружая основной код. Аппаратное прерывание может поймать короткое нажатие кнопки или срабатывание датчика во время сложных долгих вычислений или задержек в коде, т.е. грубо говоря – пин опрашивается параллельно основному коду. Также прерывания могут будить микроконтроллер из режимов энергосбережения, когда вообще практически вся периферия отключена. Посмотрим, как работать с аппаратными прерываниями в среде Arduino IDE.

Прерывания в Arduino

В этом уроке речь пойдёт об обычных прерываниях, которые называются INT, потому что стандартный фреймворк Ардуино умеет работать только с ними. Таких прерываний и соответствующих им пинов очень мало (таблица ниже), но у самого микроконтроллера есть возможность получать прерывания с любого пина, такие прерывания называются PCINT и работать с ними можно только при помощи сторонних библиотек (вот отличная), либо вручную (читай у меня вот тут). В этом уроке мы рассмотрим базовые возможности и INT прерывания:

МК / номер прерывания INT 0 INT 1 INT 2 INT 3 INT 4 INT 5
ATmega 328/168 (Nano, UNO, Mini) D2 D3
ATmega 32U4 (Leonardo, Micro) D3 D2 D0 D1 D7
ATmega 2560 (Mega) D2 D3 D21 D20 D19 D18

Как вы поняли из таблицы, прерывания имеют свой номер, который отличается от номера пина. Есть кстати удобная функция digitalPinToInterrupt(pin) , которая принимает номер пина и возвращает номер прерывания. Скормив этой функции цифру 3 на Ардуино нано, мы получим 1. Всё по таблице выше, функция для ленивых. Подключается прерывание при помощи функции attachInterrupt(pin, handler, mode) :

  • pin – номер прерывания
  • handler – имя функции-обработчика прерывания (её нужно создать самому)
  • mode – “режим” работы прерывания:
    • LOW (низкий) – срабатывает при сигнале LOW на пине
    • RISING (рост) – срабатывает при изменении сигнала на пине с LOW на HIGH
    • FALLING (падение) – срабатывает при изменении сигнала на пине с HIGH на LOW
    • CHANGE (изменение) – срабатывает при изменении сигнала (с LOW на HIGH и наоборот)

Также прерывание можно отключить при помощи функции detachInterrupt(pin) , где pin – опять же номер прерывания. А ещё можно глобально запретить прерывания функцией noInterrupts() и снова разрешить их при помощи interrupts() . Аккуратнее с ними! noInterrupts() остановит также прерывания таймеров, и у вас “сломаются” все функции времени и генерация ШИМ. Давайте рассмотрим пример, в котором в прерывании считаются нажатия кнопки, а в основном цикле они выводятся с задержкой в 1 секунду. Работая с кнопкой в обычном режиме, совместить такой грубый вывод с задержкой – невозможно:

Итак, наш код считает нажатия даже во время задержки! Здорово. Но что такое volatile ? Мы объявили глобальную переменную counter , которая будет хранить количество нажатий на кнопку. Если значение переменной будет изменяться в прерывании, нужно сообщить об этом микроконтроллеру при помощи спецификатора volatile , который пишется перед указанием типа данных переменной, иначе работа будет некорректной. Это просто нужно запомнить: если переменная меняется в прерывании – делайте её volatile . Ещё несколько важных моментов:

  • Переменные, изменяемые в прерывании, должны быть объявлены как volatile
  • В прерывании не работают задержки типа delay()
  • В прерывании не меняет своё значение millis() и micros()
  • В прерывании некорректно работает вывод в порт ( Serial.print() ), также не стоит там его использовать – это нагружает ядро
  • В прерывании нужно стараться делать как можно меньше вычислений и вообще “долгих” действий – это будет тормозить работу МК при частых прерываниях! Что же делать? Читайте ниже.

Ловим событие

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

  • В обработчике прерывания просто поднимаем флаг
  • В основном цикле программы проверяем флаг, если поднят – сбрасываем его и выполняем нужные действия

Следующий возможный сценарий: нам надо поймать сигнал с “датчика” и сразу на него отреагировать однократно до появления следующего сигнала. Если датчик – кнопка, нас поджидает дребезг контактов. С дребезгом лучше бороться аппаратно, но можно решить проблему программно: запомнить время нажатия и игнорировать последующие срабатывания. Рассмотрим пример, в котором прерывание будет настроено на изменение ( CHANGE ).

Вы скажете: но ведь millis() Не меняет значение в прерывании! Да, не меняет, но он меняется между прерываниями! Это в принципе всё, что нужно знать о прерываниях, более конкретные случаи мы разберём в продвинутых уроках.

Видео

Программирование STM32. Часть 15: Внешние прерывания EXTI

Внешние прерывания нужны для реакции прошивки МК на какие-либо быстро протекающие внешние события, которые проблематично регистрировать методом опроса состояния вывода GPIO. В stm32f103c8 для этих целей есть специальный блок EXTI, который мы рассмотрим в этой статье. Предыдущая статья здесь, все статьи цикла можно посмотреть тут: http://dimoon.ru/category/obuchalka/stm32f1.

Возможности контроллера внешних событий/прерываний EXTI

  • Установка края импульса на входе канала EXTI, по которому будет генерироваться прерывание. Можно установить триггер по нарастающему краю импульса, по спадающему, или установить оба триггера сразу.
  • Каждый канал имеет свой бит разрешения прерывания
  • Каждый канал имеет свой бит ожидания запроса прерывания, который необходимо очистить в обработчике соответствующего прерывания
  • Контроллер EXTI гарантированно обнаруживает импульсы, длительность которых больше длительность периода тактового сигнала шины APB2. Т.е. если частота шина APB2 равна 72 МГц, то EXTI будет корректно обнаруживать фронты сигналов с частотами ниже 72 МГц.

Каналы EXTI

Каналы EXTI имеют следующие названия: EXTI0, EXTI1, EXTI2 .. EXTI19. Всего в нашем распоряжении 20 каналов. Причем EXTI0 — EXTI15 могут быть подключены к одному из портов GPIO. EXTI16 подключен внутри МК к выходу программируемого детектора напряжения PVD, EXTI17 к событию RTC Alarm, EXTI18 к USB микроконтроллера, и EXTI19 к контроллеру Ethernet, если он конечно есть.

В данный момент нас интересуют те каналы EXTI, которые могут быть подключены к портам GPIO. И здесь есть один нюанс. На входе каждого канала EXTI стоит мультиплексор, который позволяет выбрать пин GPIO следующим образом:

Т.е. к EXTI0 можно подключить один из 0-ых выводов портов, к EXTI1 один из 1-ых выводов, и так далее. Для каждой линии значение мультиплексора можно выбрать независимо, т.е. EXTI0 можно подключить к PA0, EXTI1 к PB1, и так далее. Однако такая организация подключений имеет некоторое ограничение, которое необходимо учитывать: мы не можем одновременно регистрировать события от, например, линий PA0 и PB0, так как они подключены к одному и тому же мультиплексору.

Внутреннее устройство

Блок-схема контроллера EXTI представлена ниже:

Входной сигнал на канал EXIT подается с Input Line. Далее, он идет на детекторы нарастающего и спадающего краев импульса. Далее, сигнал с детекторов идет на логический элемент ИЛИ, который будет генерировать сигнал на запрос прерывания либо при срабатывании детекторов края импульса, либо при установки бита Software interrupt event. Далее, сигнал запроса прерывания идет на 2 логических элемента И, которые пропускают этот сигнал на генерацию события (нижний элемент) и генерацию прерывания (верхний элемент), если установлены соответствующие биты. Далее, сигнал на генерацию прерывания устанавливает бит в регистре Pending request register, что приводит к запросу к контроллеру прерываний NVIC. После того, как будет изложено описание регистров EXTI, данная блок-схема станет понятней.

Читайте также  Ремонт пистолета стражник мр-461

Настройка GPIO

Согласно Reference manual-у для работы вывода порта совместно с EXTI данный вывод должен быть настроен на вход. Дополнительных настроек GPIO не требуется.

Регистры конфигурации

Для настройки прерываний, фронтов срабатывания и так далее существуют регистры, расположенные в адресном пространстве контроллера EXTI. Однако, настройки мультиплексоров выбора вывода GPIO, который подключен к соответствующему каналу EXTI выполняется в регистрах AFIO (Alternate function I/O). Мы рассмотрим все регистры EXTI и регистры AFIO, которые относятся к настройки мультиплексоров EXTI.

Interrupt mask register (EXTI_IMR) — Регистр маски прерываний

MRx: Разрешения прерывания канала x

  • 0 — прерывание отключено
  • 1 — прерывание включено

Event mask register (EXTI_EMR) — Регистр макси событий

С событиями мы еще не разбирались, этот регистр можно просто игнорировать

Rising trigger selection register (EXTI_RTSR) — Регистр включения детектора нарастающего края импульса

TRx: Разрешить срабатывание триггера нарастающего края импульса канала x

  • 0 — триггер выключен
  • 1 — триггер включен

Falling trigger selection register (EXTI_FTSR) — Регистр включения детектора спадающего края импульса

TRx: Разрешить срабатывание триггера спадающего края импульса канала x

  • 0 — триггер выключен
  • 1 — триггер включен

Software interrupt event register (EXTI_SWIER) — Регистр программного прерывания

SWIERx: Программное прерывание линии x

Если прерывание данного канала EXTI включено в регистре EXTI_IMR, то запись ‘1’ в соответствующий бит, если до этого там было ‘0’, приводит к установке соответствующего бита в регистре EXTI_PR и генерируется запрос на прерывание данного канала EXTI.

Этот бит очищается при очистке соответствующего бита в регистре EXTI_PR.

Pending register (EXTI_PR) — Регистр ожидания

PRx: Бит ожидания обработки прерывания канала x

  • 0 — события триггеров запроса на прерывания не произошли
  • 1 — произошли события триггеров запроса на прерывание

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

Сброс бита в регистре EXTI_PR выполняется путем записи в соответствующий бит значения ‘1’. Запись значения ‘0’ не оказывает ни какого эффекта.

Регистры AFIO

Теперь разберем несколько регистров в AFIO, которые так же участвуют в настройке контроллера внешних прерываний EXTI.

External interrupt configuration register 1 (AFIO_EXTICR1) — Конфигурационный регистр 1 внешних прерываний

EXTIx[3:0]: Конфигурация мультиплексора канала x EXTI

  • 0000: Выбор пина PA[x]
  • 0001: Выбор пина PB[x]
  • 0010: Выбор пина PC[x]
  • 0011: Выбор пина PD[x]
  • 0100: Выбор пина PE[x]
  • 0101: Выбор пина PF[x]
  • 0110: Выбор пина PG[x]

External interrupt configuration register 2 (AFIO_EXTICR2) — Конфигурационный регистр 2 внешних прерываний

Тут все то же самое, что и для AFIO_EXTICR1, только для каналов EXTI4..7

External interrupt configuration register 3 (AFIO_EXTICR3) — Конфигурационный регистр 3 внешних прерываний

Конфигурация каналов EXTI8..11

External interrupt configuration register 4 (AFIO_EXTICR4) — Конфигурационный регистр 4 внешних прерываний

Конфигурация каналов EXTI12..15

Обработчики прерываний

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

  • EXTI0_IRQHandler
  • EXTI1_IRQHandler
  • EXTI2_IRQHandler
  • EXTI3_IRQHandler
  • EXTI4_IRQHandler
  • EXTI9_5_IRQHandler
  • EXTI15_10_IRQHandler
  • PVD_IRQHandler
  • RTC_Alarm_IRQHandler
  • USBWakeUp_IRQHandler

EXTI0..4 ни с кем ни чего не делят и имеют по собственному обработчику прерываний. EXTI5..9 имеют один и тот же обработчик прерываний EXTI9_5_IRQHandler, каналы EXTI10..15 так же имеют общий обработчик EXTI15_10_IRQHandler. Если одному и тому же обработчику соответствует несколько каналов EXTI, то отличить запросы от разных каналов внутри обработчика можно с помощью регистра EXTI_PR.

Последние 3 обработчика относятся к линиям EXTI16, EXTI17, EXTI18 и нас сейчас особо не интересуют.

Пример программы

В качестве демонстрации настроим внешнее прерывание по нарастающему и спадающему краю импульса на пине PA0 микроконтроллера. Создадим функцию void EXTI_Init(void), в которой будем производить все необходимые настройки:

Приступаем к настройке. Первым делом включаем тактирование GPIOA и AFIO. Тактирование EXTI специальным образом включать не надо:

Далее, настроим PA0 на вход с подтяжкой вверх:

Про настройку GPIO можно почитать вот в этой статье.

Теперь настройка EXTI. Так как мы выбрали пин PA0, то прерывание будет висеть на канале EXTI0. Нам нужно в регистре AFIO_EXTICR1 с помощью группы бит EXTI0[3:0] выбрать PA0 в качестве источника сигнала. Делается это вот так:

Далее, выбираем по каким краям у нас будет возникать прерывание. В нашем случае по обоим:

Ну и несколько последних штрихов: на всякий случай сбрасываем флаг запроса на прерывание, включаем соответствующее прерывание в регистре EXTI_IMR и разрешаем прерывание EXTI0_IRQn в NVIC:

Вот полный код функции:

Не забываем про обработчик прерывания:

И вот такой простой main()

При изменении логического уровня на PA0 будет возникать прерывание EXTI0_IRQHandler().

Тут необходимо обратить внимание на кучу asm(«nop»)-ов в обработчике прерывания. Дело в том, что сразу после вызова EXTI->PR = EXTI_PR_PR0; необходимо хотя бы 2 такта процессора перед выходом из функции обработки прерывания, для того, чтобы флаг прерывания успел сброситься. Иначе обработчик прерывания будет вызван повторно. Поэтому сброс флага прерывания в регистре EXTI_PR желательно выполнять в начале обработчика.

Ну и напоследок небольшой эксперимент. В Reference manual-е сказано, что при работе пина микроконтроллера в качестве источника сигнала для EXTI он должен быть настроен как вход. А что будет, если этот пин настроить на выход? Как будет себя вести система?

В этом случае при программном изменении состояния PA0 система ведет себя точно так же, как и в предыдущем случае, возникает прерывание по изменению логического уровня на PA0. Возможно, если настроить вывод микроконтроллера на выход Open-drain (открытый коллектор), то этим можно как-то пользоваться в некоторых экзотических случаях.

На этом предновогодняя 15-я статья по микроконтроллерам STM32 окончена, всех с наступающим 0x7E3 годом! ?

STM32F3 и EXTI. Использование внешних прерываний.

Доброго всем дня! Продолжаем работать с отладочной платой STM32F3Discovery, и сегодня мы разберемся как настроить и использовать внешние прерывания в микроконтроллерах серии STM32F3.

Что такое вообще внешнее прерывание? Ну тут особо нечего рассказывать – это просто такое прерывание, которое возникает при изменении состояния определенного входа микроконтроллера.

То есть хотим мы, например, оперативно реагировать на изменение входного сигнала на выводе PA0. Тут то нам и придут на помощь внешние прерывания. Настраиваем их соответствующим образом, и при изменении сигнала с 0 на 1 (или наоборот, в зависимости от настроек) на интересующей нас ножке контроллера программа ускачет на обработку прерывания. Как видите, все действительно довольно-таки просто, так что перейдем к практической реализации всего этого безобразия 🙂

У STM32F3 имеется в наличии целых 36(!) внешних прерываний. Прерывания EXTI0…EXTI15 висят на обработке изменения уровня сигнала на обычных ножках нашего контроллера. То есть прерывание EXTI0 мы можем настроить на работу с ножками PA0, PB0, PC0, PD0, PE0 или PF0, аналогично для EXTI3 – выводы PA3, PB3, PC3, PD3, PE3 или PF3. Вот как это все выглядит:

Возникает вопрос – а остальные 20 прерываний? А вот они:

Вот, например, в статье про будильник (вот она) мы использовали EXTI line 17, для пробуждения по будильнику.

Итак, с теорией мы разобрались немножко, давайте напишем тестовую программку для работы с внешними прерываниями. А заодно, пожалуй, поэкспериментируем-ка с пользовательской кнопкой (синего цвета), установленной на плате STM32F3Discovery 🙂 Пусть будет такая задачка:

  • опрашиваем состояние кнопки (ножка PA0)
  • если кнопка нажата, то выставляем вывод PE2 в 1 (высокий уровень сигнала)
  • замыкаем на плате ногу PE2 на вывод PE1
  • настраиваем внешнее прерывание так, чтобы оно возникало при изменении сигнала на PE1 с низкого уровня на высокий
  • и в обработчике прерывания меняем состояние какого-нибудь светодиода из установленных на плате – пусть будет синий (PE8)

Что же мы ожидаем увидеть в результате всего этого? Сейчас разберемся…

При нажатии на кнопку сигнал на выводе PE2 меняется с 0 на 1, а, соответственно, аналогичным образом меняется сигнал и на выводе PE1, что приводит к возникновению внешнего прерывания. А в обработчике, как вы помните, мы должны менять состояние светодиода. Таким образом, при нажатиях на кнопку синий светодиод должен то зажигаться, то гаснуть. Немного запутанно, конечно, но для примера подойдет 🙂

Читайте также  Что такое радиоволны?

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

Пора переходить к инициализации! Для того, чтобы настроить правильную работу внешних прерываний нужно сделать следующее:

  • настраиваем нужный вывод на работу в режиме входа
  • используя функцию SYSCFG_EXTILineConfig() выбираем, какой вывод и какого порта будем использовать в качестве источника внешнего прерывания
  • настраиваем поля структуры EXTI_InitTypeDef и, как и при работе с любой другой периферией, вызываем функцию инициализации – в данном случае это EXTI_Init()
  • аналогично производим настройки прерывания при помощи структуры NVIC_InitTypeDef

Теперь осталось реализовать все эти шаги в коде:

Почти все готово, осталось написать функцию main() и, собственно, обработчик прерывания:

В функции main() опрашиваем нашу кнопку и в зависимости от ее состояния меняем уровень сигнала на выводе PE2, который, как вы помните, у нас замкнут на ножку PE1. При нажатии кнопки должно возникать прерывание EXTI1_IRQ, в обработчике которого меняем состояние вывода PE8, на котором у нас висит светодиод.

Компилируем, прошиваем микроконтроллер, жмем на кнопку и видим результат – на каждое нажатие кнопки синий светодиод меняет свое состояние. Соответственно, программа работает именно так, как и было задумано 🙂

Arduino

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

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

Для работы с прерываниями в Arduino есть 2 функции:

  • attachInterrupt()
  • detachInterrupt()

Функция attachInterrupt()

Описание

Задает функцию, которая будет вызвана по внешнему прерыванию. Если функция уже была задана, то она будет заменена на новую. Рекомендуется использовать функцию digitalPinToInterrupt(pin) для перевода номера пина в номер прерывания. Например, если вы подключены к пину 3, используйте digitalPinToInterrupt(3) в качестве первого параметра attachInterrupt() .

Плата Arduino Пины, используемые для прерываний
Arduino Uno, Arduino Nano, Arduino Mini и остальные на ATmega328 2, 3
Arduino Mega, Arduino Mega2560 2, 3, 18, 19, 20, 21
Arduino Micro, Arduino Leonardo и остальные на ATmega32U4 0, 1, 2, 3, 7
Arduino Zero все цифровые пины кроме 4
Arduino Due все цифровые пины

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

ISR (Interrupt Service Routine) — это особые функции, которые имеют некоторые уникальные ограничения, которых нет у большинства других функций. ISR не может иметь никаких параметров, и они не должны ничего возвращать.

Как правило, функция ISR должна быть максимально короткой и быстрой. Если в одной программе задано несколько ISR-функций, то одновременно выполняться может только одна, остальные будут выполняться после завершения текущей в порядке, заданным приоритетом. Функция millis() использует прерывания для подсчета, поэтому возвращаемое ей значение не будет увеличиваться внутри ISR. Поскольку функции delay() для работы требуются прерывания, он тоже не будет работать. micros() сначала будет работает, но через 1-2 мс начнет возвращать неверный беспорядочный результат. delayMicroseconds() не использует счетчик, поэтому она будет работать как обычно.

Обычно для передачи данных между функциями ISR и основной программой используются глобальные переменные. Чтобы быть уверенными, что переменные, общие для ISR и основной программы, будут работать правильно, следует объявить их как volatile .

Синтаксис

attachInterrupt(digitalPinToInterrupt(pin), ISR, mode) — правильное использование

attachInterrupt(interrupt, ISR, mode) — так использовать не рекомендуется

attachInterrupt(pin, ISR, mode) — так использовать не рекомендуется (только для плат Arduino SAMD, Arduino Uno WiFi Rev2, Arduino Due, Arduino 101)

Параметры

interrupt — номер прерывания

pin — номер пина

ISR — функция для вызова при возникновении прерывания; эта функция не должна принимать никаких параметров и ничего не должна возвращать; эту функцию иногда называют процедурой обработки прерываний

mode — определяет, когда прерывание должно быть запущено

Допустимые значения параметра mode :

  • LOW — вызов прерывания когда на пине значение LOW
  • CHANGE — вызов прерывания когда на пине меняется значение
  • RISING — вызов когда значение переходит от LOW к HIGH
  • FALLING — вызов когда значение переходит от HIGH к LOW
  • HIGH (доступно только для Arduino Due, Arduino Zero) — вызов прерывания когда на пине значение HIGH
Возвращаемое значение
Пример

Зажигаем и гасим встроенный светодиод при изменении сигнала на 2 пине:

Примечания

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

Функция detachInterrupt()

Описание
Синтаксис

detachInterrupt(digitalPinToInterrupt(pin)) — правильное использование

detachInterrupt(interrupt) — так использовать не рекомендуется

detachInterrupt(pin) — так использовать не рекомендуется (только для плат Arduino SAMD, Arduino Uno WiFi Rev2, Arduino Due, Arduino 101)

Параметры

interrupt — номер прерывания, которое нужно отключить

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

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

Предположим нам надо зафиксировать когда изменится напряжение на какой-то ножке и экстренно выполнить определённый набор инструкций. Самый простой алгоритм выглядит следующим образом, через определённые интервалы времени опрашиваем вывод и анализируем изменилось ли его состояние. Если время между опросами будет большим, то мы можем пропустить переход, а если мало, то большая часть процессорного времени будет тратиться на опрос вывода и ожидание события. Понятно, что рассмотренный алгоритм нам не подходит. Что же делать? Решение этой задачи уже давно придумано за нас инженерами, разрабатывающими микроконтроллеры, название его — внешнее прерывание.

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

Теперь от общей теории перейдём к частным вариантам реализации. У AVR под внешние прерывания отведены конкретные выводы, у Atmega16 их 4. У STM32 дело с внешними прерываниями обстоит интереснее, управляет внешними прерываниями контроллер EXTI. Любой из выводов порта может быть настроен на работу с внешними прерываниями, но количество выводов GPIO одновременно настроенных на работу с внешними прерываниями не может превышать шестнадцать. Эти выводы соединяются с EXTI c помощью мультиплексоров.

Если посмотреть на картинку с мультиплексорами видно, что номер мультиплексора совпадает с номером вывода и мы не можем настроить на прерывание два вывода с одинаковыми номерами, то есть нельзя одновременно настроить на внешние прерывания PB0 и PA0.

Есть ещё 4 линии, которые подключаются не к GPIO, а к периферии:

  • EXTI 17 – выход PVD
  • EXTI 18 – событие от RTC_Alarm
  • EXTI 19 – событие от USB_Wakeup
  • EXTI 20 – событие от Ethernet_Wakeup

Кроме привычных прерываний, контроллер EXTI может генерировать события и программные прерывания.

Событие — выставляется флаг, без перехода в обработчик прерывания.

Программное прерывание — то же прерывание, но вызывается вручную, записью ‘1’ в соответствующий регистр.

Существует всего семь обработчиков внешних прерываний: EXTI0, EXTI1, EXTI2, EXTI3, EXTI4, EXTI9_5, EXTI15_10. Видно, что не каждое внешнее прерывание от GPIO имеет отдельный обработчик, некоторые из них совмещены.

Давайте рассмотрим регистры необходимые для настройки внешних прерываний.
Первым делом надо включить тактирование порта и альтернативных функций так, как прерывание — это альтернативная функция порта.

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

Для настройки мультиплексоров выделены 4 регистра AFIO_EXTICRx разделённые на 4 секции, по 4 бита в каждой, мы рассмотрим один из них.

Для настройки нулевого вывода на работу с внешними прерываниями, надо в секцию EXTI0 записать код порта.

EXTI_IMR — регистр маскировки прерываний, запись ‘1’ в соответствующий бит разрешает прерывания, по умолчанию там нули.

EXTI_EMR – регистр маскировки событий, запись ‘1’ в соответствующий бит разрешает событие, по умолчанию там нули.

EXTI_FTSR и EXTI_RTSR – определяют по какому фронту будет возникать прерывание. При записи ‘1’ в соответствующий бит EXTI_FTSR по спадающему фронту, в EXTI_RTSR – по возрастающему.

EXTI_SWIER – программный вызов события/прерывания, запись ‘1’ в соответствующий бит вызывает генерацию события/прерывания.

EXTI_PR – флаг возникновения события/прерывания, сбрасывается вручную при записи ‘1’.

Теперь давайте рассмотрим простой пример, в котором по нажатию кнопки, которая подключена к нулевому выводу порта E, будет генерироваться прерывание.