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

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

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

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

Каналы EXTI

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

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

Т.е. к EXTI0 можно подключить один из 0-ых выводов портов, к EXTI1 один из 1-ых выводов, и так далее. Для каждой линии значение мультиплексора можно выбрать независимо, т.е. EXTI0 можно подключить к PA0EXTI1 к 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 обработчика относятся к линиям EXTI16EXTI17EXTI18 и нас сейчас особо не интересуют.

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

В качестве демонстрации настроим внешнее прерывание по нарастающему и спадающему краю импульса на пине 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 годом!  ?

Метки: , . Закладка Постоянная ссылка.

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

  1. Дмитрий пишет:

    Спасибо за уроки!) Планируете еще уроки по cmsis? очень здорово у вас получается.

  2. AlexID пишет:

    Добрый день!
    Спасибо за хорошую серию. Очень толково.
    Ждем продолжения.

    По работе с USB планируете что то?

    • DiMoon пишет:

      Добрый день! Спасибо за комментарий)) По USB пока что нет, так как в нем куча нагромождений уровней, с ходу в него трудно въехать. Ethernet и то понятней 🙂 Сейчас я предпочитаю брать готовые мосты с USB на что то попроще

  3. Дмитрий пишет:

    Доброго времени суток. Приссоединяюсь ко всем сказаным словам благодарности за данные уроки по stm32! Тему ADC не нашел в ваших публикациях.

  4. Юрий пишет:

    Здравствуйте! У меня на эту строчку GPIOA->CRL |= (0x02 <CRL |= (0x02 << GPIO_CRL_CNF0_0);" , крестик пропадает. Компилится без ошибок и врайтингов. В железе не проверял. Буду пробовать в начале в Proteusе. Почему на "Pos" показывает ошибку? Заранее благодарен, Юрий.

  5. Денис пишет:

    Отличный курс.
    Вопрос — как узнать, по восходящему фронту сработало прерывание или по нисходящему? Или только читать состояние пина в прерывании? Накладок не получится, что вошел в прерывание, а пин уже сменил состояние?

    • DiMoon пишет:

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

  6. Дмитрий пишет:

    DiMoon, поклон тебе за просвещение. Изучаю STM32 на примере stm32f4discovery несколько лет в свободное время. Не смотря на это нахожу в твоих статьях и комментариях ответы на своивопросы. Поэтому что описание более чем подробное. По сравнению с другими источниками с данной тематикой. Вообще уважаю статьи в которых авторы описывают микроконтроллеры простыми словами, картинками с пояснениями и уточнениями. Если автор видит своего читателя не специалистом и пытается рассказать на простом языке сложные процессы происходящие в микроконтроллере, то вы можете рассчитывать на популярность ваших статей. Основная масса читателей данных сайтов далеко не профи. И очень приятно, когда находишь ответы на свои вопросы. Спасибо за труд. Для меня это просто хобби.

  7. Дмитрий пишет:

    А ещё хорошо не быть скупым. Обращение ко всем читателям. Если вы нашли что то полезное для себя, отправляйте немного денег Автору. Для их творческого развития. Что бы таких статей было больше.

  8. Роман пишет:

    Интересно, а если используется сразу прерывание по таймеру и внешнее прерывание, что будет ?

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

  9. Роман пишет:

    Сейчас попробовал смоделировать на микроконтроллере, внешнее прерывание (если происходит во время выполнения обработки прерывания по таймеру) стало обрабатываться сразу после выхода из прерывания по таймеру, ну неплохо…
    А ещё вопрос, как сделать что бы для прерывания по нарастанию импульса выполнялась одна функция а для прерывания по спаду — другая? такое возможно ?

    • DiMoon пишет:

      В прерывании смотреть состояние пина только…

      • Роман пишет:

        Спасибо, да, это нормальный выход.

        Хм, сейчас попробовал, включил только прерывание по фронту, иногда срабатывает и по спаду, почему так ? дребезг при переключении ?

        • DiMoon пишет:

          Да, скорее всего дребезг. Если подключил кнопку, то 100% будет дребезг. Если сигнал идет от другой микросхемы, надо смотреть осциллом, что происходит там

        • DiMoon пишет:

          Там еще прикол может быть, если фронты слишком затянуты и сигнал зашумлен, то могут быть подобные явления. Хотя на входах GPIO вроде как триггеры Шмитта стоят, и не должно сильно проявляться это… В общем, осциллом надо смотреть

          • Роман пишет:

            в общем, я сам косякнул, питание подал от дискавери платы 3 вольта, выходит маловато (вместо 3,3 в) подал 5 вольт на 5 вольтовый вход — теперь эти баги стали значительно реже…
            сигнал беру с другого микроконтроллера, не с кнопки.

          • DiMoon пишет:

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

  10. Роман пишет:

    Да, запитал правильно 🙂

  11. Александр пишет:

    Привет!
    Классное изложение.
    Но вот в работе регистров EXTI_RTSR и EXTI_FTSR (на фронт и спад) видимо, логика такая: при записи в них «1»- разрешение срабатывания, а при чтении «1»- был фронт (спад), «0» — его не было.

  12. shads пишет:

    Это вряд ли… в мануале об этом нет ни слова…

  13. Dimon пишет:

    Привет.А можешь подсказать.Контроллер настроен на прерывание от RTC и EXTI.Вопрос как определить в коде после пробуджения отчего проснулись от RTC или по кнопке?Может флаг какой нибудь есть.Я чтобы это выяснить горожу такую ахинею — работает но хотелось бы узнать может есть попроще решение

Добавить комментарий для Роман Отменить ответ