Top.Mail.Ru

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. Вот как это все выглядит:

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

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

Список прерываний модуля EXTI в STM32.

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

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

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

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

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

Время традиционной вставки: поскольку компания STMicroelectronics прекратила поддержку библиотеки SPL, которая использовалась в этом курсе, я создал новый, посвященный работе уже с новыми инструментами, так что буду рад видеть вас там - STM32CubeMx. Кроме того, вот глобальная рубрика по STM32.

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

/***************************************************************************************/
#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, на котором у нас висит светодиод.

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

Подписаться
Уведомить о
guest

39 комментариев
Старые
Новые
Межтекстовые Отзывы
Посмотреть все комментарии
Leo
Leo
10 лет назад

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

Leo
Leo
10 лет назад

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

Leo
Leo
10 лет назад

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

Leo
Leo
10 лет назад

Спасибо большое за ответ буду дальше пробовать.

Leo
Leo
10 лет назад

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

Василий
Василий
10 лет назад

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

Василий Hound
10 лет назад

Прошил пример, на нажатие на кнопку не реагирует...ошибок при компиляции не было...

Василий Hound
10 лет назад

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

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

Василий Hound
10 лет назад

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

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

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

Василий Hound
10 лет назад

Снова прошу прощения, ошибка была с моей стороны.
В проект не добавил startup файл...

Василий Hound
10 лет назад

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

А как можно сделать, чтобы например прерывание срабатывало, например, если загорелся светодиод 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?
Почему-то так не работает...

Василий Hound
10 лет назад

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

Александр
Александр
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++);
}
}

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

Argento
Argento
9 лет назад

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

Argento
Argento
9 лет назад

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

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

Argento
Argento
9 лет назад

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

Argento
Argento
9 лет назад

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

Вадя
Вадя
9 лет назад

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

Александр
Александр
8 лет назад

Здравствуйте. Подскажите пожалуйста, возникла проблема при обработке внешнего прерывания
программа зависает в обработчике прерывания:
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); то всё начинает работать.

Александр
Александр
8 лет назад

Спасибо!

Sergey
7 лет назад

Добрый день ! Хочу повесить прерывания напрямую на кнопку , делаю следующим образом :
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();
}
//}
//************************************

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

Sergey
Ответ на комментарий  Aveal
7 лет назад

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

Sergey
Ответ на комментарий  Aveal
7 лет назад

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

Sergey
Ответ на комментарий  Aveal
7 лет назад

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

Александр
Александр
6 лет назад

Добрый день! подскажите, пожалуйста, как организовать двойное нажатие (например зажечь диод). Всю голову сломал. Заранее спасибо!

Александр
Александр
Ответ на комментарий  Aveal
6 лет назад

Спасибо, буду пробовать!

39
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x