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
9 лет назад

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

Leo
Leo
9 лет назад

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

Leo
Leo
9 лет назад

Огромное спасибо за пример. У меня почему то ругается на вот эту строчку 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
9 лет назад

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

Leo
Leo
9 лет назад

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Здравствуйте. Уже долгое время я бьюсь с установкой приоритета прирываний при помощи 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
8 лет назад

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

Argento
Argento
8 лет назад

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

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

Argento
Argento
8 лет назад

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

Argento
Argento
8 лет назад

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

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

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

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

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

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

Спасибо!

Sergey
6 лет назад

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

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

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

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

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

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

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

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

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

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

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