Внешние прерывания нужны для реакции прошивки МК на какие-либо быстро протекающие внешние события, которые проблематично регистрировать методом опроса состояния вывода 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, данная блок-схема станет понятней.
Настройка 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), в которой будем производить все необходимые настройки:
void EXTI_Init(void) { }
Приступаем к настройке. Первым делом включаем тактирование GPIOA и AFIO. Тактирование EXTI специальным образом включать не надо:
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; //Тактирование GPIOA RCC->APB2ENR |= RCC_APB2ENR_AFIOEN; //Тактирование AFIO
Далее, настроим PA0 на вход с подтяжкой вверх:
/* Настройка GPIO Пин: PA0 Режим: Input Pull Up */ GPIOA->CRL &= ~(GPIO_CRL_MODE0 | GPIO_CRL_CNF0); GPIOA->CRL |= (0x02 << GPIO_CRL_CNF0_Pos); //Вход Pull Up/Pull Down GPIOA->ODR |= (1 << 0); //Подтяжка вверх
Про настройку GPIO можно почитать вот в этой статье.
Теперь настройка EXTI. Так как мы выбрали пин PA0, то прерывание будет висеть на канале EXTI0. Нам нужно в регистре AFIO_EXTICR1 с помощью группы бит EXTI0[3:0] выбрать PA0 в качестве источника сигнала. Делается это вот так:
AFIO->EXTICR[0] &= ~(AFIO_EXTICR1_EXTI0); //Нулевой канал EXTI подключен к порту PA0
Далее, выбираем по каким краям у нас будет возникать прерывание. В нашем случае по обоим:
EXTI->RTSR |= EXTI_RTSR_TR0; //Прерывание по нарастанию импульса EXTI->FTSR |= EXTI_FTSR_TR0; //Прерывание по спаду импульса
Ну и несколько последних штрихов: на всякий случай сбрасываем флаг запроса на прерывание, включаем соответствующее прерывание в регистре EXTI_IMR и разрешаем прерывание EXTI0_IRQn в NVIC:
EXTI->PR = EXTI_PR_PR0; //Сбрасываем флаг прерывания //перед включением самого прерывания EXTI->IMR |= EXTI_IMR_MR0; //Включаем прерывание 0-го канала EXTI NVIC_EnableIRQ(EXTI0_IRQn); //Разрешаем прерывание в контроллере прерываний
Все, готово 🙂
Вот полный код функции:
void EXTI_Init(void) { RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; //Тактирование GPIOA RCC->APB2ENR |= RCC_APB2ENR_AFIOEN; //Тактирование AFIO /* Настройка GPIO Пин: PA0 Режим: Input Pull Up */ GPIOA->CRL &= ~(GPIO_CRL_MODE0 | GPIO_CRL_CNF0); GPIOA->CRL |= (0x02 << GPIO_CRL_CNF0_Pos); //Вход Pull Up/Pull Down GPIOA->ODR |= (1 << 0); //Подтяжка вверх /* Настройка EXTI */ AFIO->EXTICR[0] &= ~(AFIO_EXTICR1_EXTI0); //Нулевой канал EXTI подключен к порту PA0 EXTI->RTSR |= EXTI_RTSR_TR0; //Прерывание по нарастанию импульса EXTI->FTSR |= EXTI_FTSR_TR0; //Прерывание по спаду импульса EXTI->PR = EXTI_PR_PR0; //Сбрасываем флаг прерывания //перед включением самого прерывания EXTI->IMR |= EXTI_IMR_MR0; //Включаем прерывание 0-го канала EXTI NVIC_EnableIRQ(EXTI0_IRQn); //Разрешаем прерывание в контроллере прерываний }
Не забываем про обработчик прерывания:
void EXTI0_IRQHandler(void) { EXTI->PR = EXTI_PR_PR0; //Сбрасываем флаг прерывания asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); }
И вот такой простой main()
void main(void) { EXTI_Init(); for(;;) { } }
При изменении логического уровня на PA0 будет возникать прерывание EXTI0_IRQHandler().
Тут необходимо обратить внимание на кучу asm(«nop»)-ов в обработчике прерывания. Дело в том, что сразу после вызова EXTI->PR = EXTI_PR_PR0; необходимо хотя бы 2 такта процессора перед выходом из функции обработки прерывания, для того, чтобы флаг прерывания успел сброситься. Иначе обработчик прерывания будет вызван повторно. Поэтому сброс флага прерывания в регистре EXTI_PR желательно выполнять в начале обработчика.
Ну и напоследок небольшой эксперимент. В Reference manual-е сказано, что при работе пина микроконтроллера в качестве источника сигнала для EXTI он должен быть настроен как вход. А что будет, если этот пин настроить на выход? Как будет себя вести система?
void EXTI_Init(void) { RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; //Тактирование GPIOA RCC->APB2ENR |= RCC_APB2ENR_AFIOEN; //Тактирование AFIO /* Настройка GPIO Пин: PA0 Режим: Output */ GPIOA->CRL &= ~(GPIO_CRL_MODE0 | GPIO_CRL_CNF0); GPIOA->CRL |= (0x00 << GPIO_CRL_CNF0_Pos) | (0x01 << GPIO_CRL_MODE0_Pos); /* Настройка EXTI */ AFIO->EXTICR[0] &= ~(AFIO_EXTICR1_EXTI0); //Нулевой канал EXTI подключен к порту PA0 EXTI->RTSR |= EXTI_RTSR_TR0; //Прерывание по нарастанию импульса EXTI->FTSR |= EXTI_FTSR_TR0; //Прерывание по спаду импульса EXTI->PR = EXTI_PR_PR0; //Сбрасываем флаг прерывания //перед включением самого прерывания EXTI->IMR |= EXTI_IMR_MR0; //Включаем прерывание 0-го канала EXTI NVIC_EnableIRQ(EXTI0_IRQn); //Разрешаем прерывание в контроллере прерываний }
В этом случае при программном изменении состояния PA0 система ведет себя точно так же, как и в предыдущем случае, возникает прерывание по изменению логического уровня на PA0. Возможно, если настроить вывод микроконтроллера на выход Open-drain (открытый коллектор), то этим можно как-то пользоваться в некоторых экзотических случаях.
На этом предновогодняя 15-я статья по микроконтроллерам STM32 окончена, всех с наступающим 0x7E3 годом! ?
Спасибо за уроки!) Планируете еще уроки по cmsis? очень здорово у вас получается.
Да, планирую, хочу написать статью про uart в ближайшее время
Добрый день!
Спасибо за хорошую серию. Очень толково.
Ждем продолжения.
По работе с USB планируете что то?
Добрый день! Спасибо за комментарий)) По USB пока что нет, так как в нем куча нагромождений уровней, с ходу в него трудно въехать. Ethernet и то понятней 🙂 Сейчас я предпочитаю брать готовые мосты с USB на что то попроще
Доброго времени суток. Приссоединяюсь ко всем сказаным словам благодарности за данные уроки по stm32! Тему ADC не нашел в ваших публикациях.
Спасибо, стараюсь 🙂 По ADC пока ни чего нет, но планирую
Вот бы ещё статьи по таймерам и прерываниям, а статей с использованием CMSIS действительно очень мало в интернете.
Здравствуйте! У меня на эту строчку GPIOA->CRL |= (0x02 <CRL |= (0x02 << GPIO_CRL_CNF0_0);" , крестик пропадает. Компилится без ошибок и врайтингов. В железе не проверял. Буду пробовать в начале в Proteusе. Почему на "Pos" показывает ошибку? Заранее благодарен, Юрий.
Возможно, причина в старой версии SMSIS. Попробуйте скачать последнюю версию на сайте ST
Отличный курс.
Вопрос — как узнать, по восходящему фронту сработало прерывание или по нисходящему? Или только читать состояние пина в прерывании? Накладок не получится, что вошел в прерывание, а пин уже сменил состояние?
Спасибо, стараюсь ?
Насколько понял, в вашем случае генерация прерывания настроена и по фронту, и по спаду? В этом случае только читать состояние ножки внутри прерывания.
Если вошёл в прерывание, а ножка в это время изменила своё состояние, то тут 2 варианта. Если в прерывании сначала сбросили флаг этого прерывания в регистре EXTI_PR, а затем ножка изменила своё значение, то после выхода из прерывания будет сгенерировано ещё одно. Если сначала изменилось состояние ножки, а потом сбросили с помощью EXTI_PR, то повторный вызов прерывания вроде как произойти не должен.
Короче, вложенного в само в себя прерывания не будет
DiMoon, поклон тебе за просвещение. Изучаю STM32 на примере stm32f4discovery несколько лет в свободное время. Не смотря на это нахожу в твоих статьях и комментариях ответы на своивопросы. Поэтому что описание более чем подробное. По сравнению с другими источниками с данной тематикой. Вообще уважаю статьи в которых авторы описывают микроконтроллеры простыми словами, картинками с пояснениями и уточнениями. Если автор видит своего читателя не специалистом и пытается рассказать на простом языке сложные процессы происходящие в микроконтроллере, то вы можете рассчитывать на популярность ваших статей. Основная масса читателей данных сайтов далеко не профи. И очень приятно, когда находишь ответы на свои вопросы. Спасибо за труд. Для меня это просто хобби.
А ещё хорошо не быть скупым. Обращение ко всем читателям. Если вы нашли что то полезное для себя, отправляйте немного денег Автору. Для их творческого развития. Что бы таких статей было больше.
Интересно, а если используется сразу прерывание по таймеру и внешнее прерывание, что будет ?
Например при работе программы обработки прерывания по таймеру сработает внешнее прерывание по входу, что будет? внешнее прерывание будет проигнорировано ? или программа внешнего прерывания начет выполняться сразу после окончание обработки прерывания по таймеру ?
Сейчас попробовал смоделировать на микроконтроллере, внешнее прерывание (если происходит во время выполнения обработки прерывания по таймеру) стало обрабатываться сразу после выхода из прерывания по таймеру, ну неплохо…
А ещё вопрос, как сделать что бы для прерывания по нарастанию импульса выполнялась одна функция а для прерывания по спаду — другая? такое возможно ?
В прерывании смотреть состояние пина только…
Спасибо, да, это нормальный выход.
Хм, сейчас попробовал, включил только прерывание по фронту, иногда срабатывает и по спаду, почему так ? дребезг при переключении ?
Да, скорее всего дребезг. Если подключил кнопку, то 100% будет дребезг. Если сигнал идет от другой микросхемы, надо смотреть осциллом, что происходит там
Там еще прикол может быть, если фронты слишком затянуты и сигнал зашумлен, то могут быть подобные явления. Хотя на входах GPIO вроде как триггеры Шмитта стоят, и не должно сильно проявляться это… В общем, осциллом надо смотреть
в общем, я сам косякнул, питание подал от дискавери платы 3 вольта, выходит маловато (вместо 3,3 в) подал 5 вольт на 5 вольтовый вход — теперь эти баги стали значительно реже…
сигнал беру с другого микроконтроллера, не с кнопки.
Надеюсь 5 вольт подал на толерантный к 5 вольтам вывод 🙂 Посмотри, как у тебя там земли подключены. Сигнальный провод должен идти вместе с землей, чтоб рамка между сигнальным проводом и землей была минимальной площади, иначе у тебя получится антенна, которая сама излучает в пространство нехило, и ловит все помехи на себя. Плюс на другом конце будет сигнал искажаться
Да, запитал правильно 🙂
СПАСИБО!Очень помогает в изучении STM32….( asm, уже разобрался с настройкой ADC,DMA,GPIO,TIM1..4.)
Привет!
Классное изложение.
Но вот в работе регистров EXTI_RTSR и EXTI_FTSR (на фронт и спад) видимо, логика такая: при записи в них «1»- разрешение срабатывания, а при чтении «1»- был фронт (спад), «0» — его не было.
Это вряд ли… в мануале об этом нет ни слова…
Привет.А можешь подсказать.Контроллер настроен на прерывание от RTC и EXTI.Вопрос как определить в коде после пробуджения отчего проснулись от RTC или по кнопке?Может флаг какой нибудь есть.Я чтобы это выяснить горожу такую ахинею — работает но хотелось бы узнать может есть попроще решение