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

Доброго всем дня! Продолжаем работать с отладочной платой 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 😉 Пусть будет такая задачка:

  1. Опрашиваем состояние кнопки (ножка PA0).
  2. Если кнопка нажата, то выставляем вывод PE2 в 1 (высокий уровень сигнала).
  3. Замыкаем на плате ногу PE2 на вывод PE1.
  4. Настраиваем внешнее прерывание так, чтобы оно возникало при изменении сигнала на PE1 с низкого уровня на высокий.
  5. И в обработчике прерывания меняем состояние какого-нибудь светодиода из установленных на плате — пусть будет синий (PE8).

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

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

/*******************************************************************/
#include "stm32f30x_gpio.h"
#include "stm32f30x_rcc.h"
#include "stm32f30x_exti.h"
#include "stm32f30x_syscfg.h"
#include "stm32f30x_misc.h"
#include "stm32f30x.h"
 
 
 
/*******************************************************************/
GPIO_InitTypeDef gpio;
EXTI_InitTypeDef exti;
NVIC_InitTypeDef nvic;
 
 
 
/*******************************************************************/

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

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

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

/*******************************************************************/
void initAll()
{
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOE, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
 
    gpio.GPIO_Mode = GPIO_Mode_OUT;
    gpio.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_2;
    gpio.GPIO_OType = GPIO_OType_PP;
    gpio.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOE, &gpio);
 
    gpio.GPIO_Mode = GPIO_Mode_IN;
    gpio.GPIO_Pin = GPIO_Pin_0;
    gpio.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &gpio);
 
    gpio.GPIO_Mode = GPIO_Mode_IN;
    gpio.GPIO_Pin = GPIO_Pin_1;
    gpio.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOE, &gpio);
 
    SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE, EXTI_PinSource1);
 
    exti.EXTI_Line = EXTI_Line1;
    exti.EXTI_Mode = EXTI_Mode_Interrupt;
    exti.EXTI_Trigger = EXTI_Trigger_Rising;
    exti.EXTI_LineCmd = ENABLE;
    EXTI_Init(&exti);
 
    nvic.NVIC_IRQChannel = EXTI1_IRQn;
    nvic.NVIC_IRQChannelPreemptionPriority = 0;
    nvic.NVIC_IRQChannelSubPriority = 0;
    nvic.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&nvic);
}
 
 
 
/*******************************************************************/

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

/*******************************************************************/
int main()
{
    __enable_irq();
    initAll();
 
    while(1)
    {	
	if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == 1)
	{
    	    GPIO_SetBits(GPIOE, GPIO_Pin_2);			
	}
	else
	{
	    GPIO_ResetBits(GPIOE, GPIO_Pin_2);			
	}
    }
}
 
void EXTI1_IRQHandler()
{
    EXTI_ClearFlag(EXTI_Line1);
 
    if (GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_8) == 1)
    {		
	GPIO_ResetBits(GPIOE, GPIO_Pin_8);
    }
    else
    {
	GPIO_SetBits(GPIOE, GPIO_Pin_8);
    }
}
 
 
 
/*******************************************************************/

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

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

Понравилась статья? Поделись с друзьями!

STM32F3. Использование внешних прерываний.: 36 комментариев
  1. Хорошая статья, но к сожалению не могу понять как инициализировать внешние прерывания на STM32F10x. Если можно расскажите пожалуйста как инициализируются прерывания на STM32F10x

  2. Да по сути то же самое, но в библиотеке отсутствуют подобные файлы «stm32f30x_syscfg.h» «stm32f30x_misc.h». Я сам перехожу с PIC и асемблера на STM32 поэтому очень сложно дается язык С. Если вам не трудно расскажите хотя бы в крации как подключить прерывания на STM32F10x

    • Ну вот, что-то вроде такого должно быть:

      EXTI_InitTypeDef EXTI_InitStructure; 
      GPIO_InitTypeDef GPIO_InitStructure; 
      NVIC_InitTypeDef NVIC_InitStructure; 
       
      RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); 
       
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
       
      GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
      GPIO_Init(GPIOA, &GPIO_InitStructure);  
       
      GPIO_EXTILineConfig(GPIOA,GPIO_Pin_0); 
       
      EXTI_InitStructure.EXTI_Line = EXTI_Line0; 
      EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; 
      EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; 
      EXTI_InitStructure.EXTI_LineCmd = ENABLE; 
      EXTI_Init(&EXTI_InitStructure); 
       
      NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; 
      NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
      NVIC_Init(&NVIC_InitStructure);

      И обработчик прерывания:

       void EXTI0_IRQHandler(void)
      {
      {
  3. Огромное спасибо за пример. У меня почему то ругается на вот эту строчку timer.c(73): error: #167: argument of type «GPIO_TypeDef *» is incompatible with parameter of type «uint8_t» GPIO_EXTILineConfig(GPIOA, GPIO_Pin_0); аргумент несовместим с типом данных. Подскажите пожалуйста где надо глянуть чтобы исправить эту ошибку?

  4. Еще вопрос а как правильно сбросит флаг прерывания? Попробовал вот так не вышло EXTI_ClearITPendingBit( EXTI0, EXTI_Line)

  5. Что-то залил этот пример и на нажатие на кнопку не реагирует вообще…ошибок при компиляции не было.

  6. Да, она самая…
    хотел пока просто залить пример дабы проверить, но…
    http://www.st.com/web/catalog/tools/FM116/SC959/SS1532/PF254044

    Если бы была ошибка в проге из примера, то хотя бы компилятор тогда ругался бы…

    P.S. Прошу прощения, что ниже написал комментарий…

  7. Заработало 🙂
    Только повешал прерывание на прямую на кнопку.

    А как можно сделать, чтобы например прерывание срабатывало, например, если загорелся светодиод LD12?

    exti.EXTI_Line = EXTI_Line12;
    exti.EXTI_Mode = EXTI_Mode_Interrupt;
    exti.EXTI_Trigger = EXTI_Trigger_Rising;
    exti.EXTI_LineCmd = ENABLE;
    EXTI_Init(&exti);

    nvic.NVIC_IRQChannel = EXTI15_10_IRQn;
    nvic.NVIC_IRQChannelPreemptionPriority = 0x0F;
    nvic.NVIC_IRQChannelSubPriority = 0x0F;
    nvic.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&nvic);

    И функция обработчик EXTI15_10_IRQHandler?
    Почему-то так не работает…

  8. Охохох…и снова я сильно поспешил…спутал мягкое с теплым…
    Прерывание же и не будет работать на «выход» от ПИНа контроллера…

  9. Здравствуйте. Уже долгое время я бьюсь с установкой приоритета прирываний при помощи SPL, но все четно. Вот программа:
    #include «STM32F30x.h» // Device header
    #include «stm32f30x_rcc.h»
    #include «stm32f30x_gpio.h»
    #include «stm32f30x_exti.h»
    #include «stm32f30x_misc.h»
    #include «stm32f30x_syscfg.h»

    EXTI_InitTypeDef exti;
    GPIO_InitTypeDef port;
    NVIC_InitTypeDef nvic;

    int i, j;
    uint16_t SostKnopki;

    void Inicial(void)
    {
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOE, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE);

    GPIO_StructInit(&port);

    port.GPIO_Mode = GPIO_Mode_IN;
    port.GPIO_Pin = GPIO_Pin_3;
    port.GPIO_PuPd = GPIO_PuPd_DOWN;
    GPIO_Init(GPIOC, &port);

    port.GPIO_Mode = GPIO_Mode_OUT;
    port.GPIO_Pin = GPIO_Pin_8;
    port.GPIO_PuPd = GPIO_PuPd_DOWN;
    GPIO_Init(GPIOE, &port);

    port.GPIO_Mode = GPIO_Mode_OUT;
    port.GPIO_Pin = GPIO_Pin_11;
    port.GPIO_PuPd = GPIO_PuPd_DOWN;
    GPIO_Init(GPIOE, &port);

    port.GPIO_Mode = GPIO_Mode_IN;
    port.GPIO_Pin = GPIO_Pin_1;
    port.GPIO_PuPd = GPIO_PuPd_DOWN;
    GPIO_Init(GPIOA, &port);

    port.GPIO_Mode = GPIO_Mode_OUT;
    port.GPIO_Pin = GPIO_Pin_10;
    port.GPIO_PuPd = GPIO_PuPd_DOWN;
    GPIO_Init(GPIOE, &port);

    SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOC, EXTI_PinSource3);
    SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource1);

    exti.EXTI_Line = EXTI_Line3;
    exti.EXTI_LineCmd = ENABLE;
    exti.EXTI_Mode = EXTI_Mode_Interrupt;
    exti.EXTI_Trigger = EXTI_Trigger_Rising;
    EXTI_Init(&exti);

    exti.EXTI_Line = EXTI_Line1;
    exti.EXTI_LineCmd = ENABLE;
    exti.EXTI_Mode = EXTI_Mode_Interrupt;
    exti.EXTI_Trigger = EXTI_Trigger_Rising;
    EXTI_Init(&exti);

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);

    nvic.NVIC_IRQChannel = EXTI3_IRQn;
    nvic.NVIC_IRQChannelCmd = ENABLE;
    nvic.NVIC_IRQChannelSubPriority = 0xF;
    NVIC_Init(&nvic);

    nvic.NVIC_IRQChannel = EXTI1_IRQn;
    nvic.NVIC_IRQChannelCmd = ENABLE;
    nvic.NVIC_IRQChannelSubPriority = 0x2;
    NVIC_Init(&nvic);
    }

    void EXTI3_IRQHandler(void)
    {
    EXTI_ClearFlag(EXTI_Line3);
    GPIO_ResetBits(GPIOE, GPIO_Pin_8);
    GPIO_ResetBits(GPIOE, GPIO_Pin_10);
    for(j=0; j<10; j++)
    {
    GPIO_SetBits(GPIOE, GPIO_Pin_11);
    for(i=0; i<600000; i++);
    GPIO_ResetBits(GPIOE, GPIO_Pin_11);
    for(i=0; i<600000; i++);
    }
    }

    void EXTI1_IRQHandler(void)
    {
    EXTI_ClearFlag(EXTI_Line1);
    GPIO_ResetBits(GPIOE, GPIO_Pin_8);
    GPIO_ResetBits(GPIOE, GPIO_Pin_11);
    for(j=0; j<10; j++)
    {
    GPIO_SetBits(GPIOE, GPIO_Pin_10);
    for(i=0; i<600000; i++);
    GPIO_ResetBits(GPIOE, GPIO_Pin_10);
    for(i=0; i<600000; i++);
    }
    }

    int main(void)
    {
    __enable_irq();
    Inicial();
    while(1)
    {
    GPIO_SetBits(GPIOE, GPIO_Pin_8);
    for(i=0; i<600000; i++);
    GPIO_ResetBits(GPIOE, GPIO_Pin_8);
    for(i=0; i<600000; i++);
    }
    }

    Пожалуйста помогите.

  10. Добрый день!
    Ваш пример, аккуратно и без ошибок перенесенный,
    у меня не работает. Похоже, не вызывается обработчик прерывания. В чем может быть проблема?
    Пины соединил. Пробовал переносить прерывание на другие пины, не помогает.
    Пробую именно на STM32F3Discovery, среда разработки CodeBlocks 13.12.
    Спасибо!

  11. При беглом просмотре Вашей программы я не увидел, где собственно меняется переменная SostKnopki, и где считывается ее состояние.

    Я не претендую на истину, сам начинающий, и проблемы с прерываниями, не вызываются! У Вас получилось в итоге?

    • У меня нет там такой переменной вроде бы. Плата у меня — discovery, ide — Keil 4. Стартап там Кейл сам добавляет в проект при создании проекта.

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

  12. У Вас STM32F3Discovery плата? Какую версию startup файла Вы использовали и в какой среде разработки делали проект? Мучаюсь уже месяц, прерывания не вызываются…

  13. Спасибо! Про переменную я не Вам, видимо, мои комменты в кучу смешались.

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

    • Да, фронт настраивается. Можно настроить прерывание так, чтобы реагировало на любое изменение фронта, а в самом обработчике просто анализировать сигнал, чтобы понять какой именно фронт пришел.

  15. Здравствуйте. Подскажите пожалуйста, возникла проблема при обработке внешнего прерывания
    программа зависает в обработчике прерывания:
    void EXTI0_IRQHandler(void)
    {
    /* USER CODE BEGIN EXTI0_IRQn 0 */

    /* USER CODE END EXTI0_IRQn 0 */
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
    /* USER CODE BEGIN EXTI0_IRQn 1 */
    HAL_GPIO_WritePin( GPIOD,GPIO_PIN_12, GPIO_PIN_SET);
    HAL_Delay (100);
    /* USER CODE END EXTI0_IRQn 1 */
    }
    если закоментировать HAL_Delay (100); то всё начинает работать.

    • Да, я только хотел написать, что причина в задержке, как увидел, что в конце сообщения это уже написано )

      Связано это с тем, что функция задержки сама построена на прерывании SysTick’а, поэтому если ее вызвать из другого прерывания, возникает такая вот проблема.

  16. Добрый день ! Хочу повесить прерывания напрямую на кнопку , делаю следующим образом :
    void Button_init_Handler(){

    GPIO_InitTypeDef GPIO_Button_Ini;
    EXTI_InitTypeDef EXTI_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA,ENABLE);

    GPIO_Button_Ini.GPIO_Pin = GPIO_Pin_0;
    GPIO_Button_Ini.GPIO_Mode = GPIO_Mode_IN ;
    GPIO_Button_Ini.GPIO_Speed = GPIO_Speed_40MHz;
    GPIO_Button_Ini.GPIO_PuPd = GPIO_PuPd_UP;
    GPIO_Button_Ini.GPIO_OType = GPIO_OType_PP;
    GPIO_Init(GPIOA,&GPIO_Button_Ini);

    //SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource1);
    // GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
    //*
    SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0);
    EXTI_InitStructure.EXTI_Line = EXTI_Line0;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);
    //NVIC_EnableIRQ(EXTI0_IRQn);//enable IRQ
    //*/
    //*
    NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;

    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    //так программа вылетает в сore_cm3.h , если поставить //DISABLE , все нормально , идет по программе , но не заходит //в EXTIO_IRQHandler
    NVIC_Init(&NVIC_InitStructure);
    // */

    }

    void EXTIO_IRQHandler()
    {
    EXTI_ClearFlag(EXTI_Line0);
    if( BUTTON_READ_USER == 1){
    Led_Green;
    }else {
    Led_Green_OFF;
    }
    }
    //*/

    void led_init(){
    //RCC_AHBPeriph_GPIOB
    RCC_APB1PeriphClockCmd(RCC_AHBPeriph_GPIOB,ENABLE);
    GPIO_InitTypeDef GPIO_Init_Led_Green;

    GPIO_Init_Led_Green.GPIO_Pin = GPIO_Pin_7;
    GPIO_Init_Led_Green.GPIO_Mode = GPIO_Mode_OUT;//|GPIO_Mode_AF;
    GPIO_Init_Led_Green.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_Init_Led_Green.GPIO_PuPd = GPIO_PuPd_UP;
    GPIO_Init_Led_Green.GPIO_OType = GPIO_OType_PP;

    GPIO_Init(GPIOB,&GPIO_Init_Led_Green);
    //NVIC_EnableIRQ(EXTI0_IRQn);

    }

    int main(void)
    {

    init_mcu_fu();

    __enable_irq();

    NVIC_EnableIRQ(SPI2_IRQn);

    SPI_I2S_ITConfig(SPI2, SPI_I2S_IT_RXNE, ENABLE);

    // EXTI15_10_IRQnHandler();

    /* task_add(*Butt_poll);
    task_add(*butt_handler);

    task_add(*on_off_handler);
    // task_add(*current_check);

    task_add(*IWDG_ReloadCounter);
    task_add(*tmg_dm);*/
    //LED_ON;
    // encoder_ini();
    led_init();
    Button_init_Handler();

    // Systick_Enc();//proba
    // EXTI15_10_IRQnHandler();
    while(1)
    {

    //*
    if ( BUTTON_READ_USER == 1){
    Led_Green;
    }else {
    Led_Green_OFF;
    }
    //*/
    // UpdateSats();
    delay(50);

    }

    //taskmgr();
    }
    //}
    //************************************

    Что неправильно делаю ?

      • поменял на всякий случай , я думаю там в инцилизации должно быть оба ENABLE , но у меня почему то неправильно работает , если и NVIC и EXTI Enable .Проверьте мою иницилизацию пожалуйста

        • После изменений ничего не поменялось? Если там было неверное название функции обработчика, то при включении прерывания программа улетала не туда куда надо из-за того, что не могла найти обработчик.

          • а ок , я тогда проверю с ENABLE )

          • спасибо как проверю . отпишу.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *