Доброго всем дня! Продолжаем работать с отладочной платой 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 ) Пусть будет такая задача:
- опрашиваем состояние кнопки (ножка 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, на котором у нас висит светодиод.
Компилируем, прошиваем микроконтроллер, жмем на кнопку и видим результат - на каждое нажатие кнопки синий светодиод меняет свое состояние. Соответственно, программа работает именно так, как и было задумано 👍
Хорошая статья, но к сожалению не могу понять как инициализировать внешние прерывания на STM32F10x. Если можно расскажите пожалуйста как инициализируются прерывания на STM32F10x
Ну там в принципе все то же самое по сути
Да по сути то же самое, но в библиотеке отсутствуют подобные файлы "stm32f30x_syscfg.h" "stm32f30x_misc.h". Я сам перехожу с PIC и асемблера на STM32 поэтому очень сложно дается язык С. Если вам не трудно расскажите хотя бы в крации как подключить прерывания на STM32F10x
Ну вот, что-то вроде такого должно быть:
И обработчик прерывания:
Огромное спасибо за пример. У меня почему то ругается на вот эту строчку timer.c(73): error: #167: argument of type "GPIO_TypeDef *" is incompatible with parameter of type "uint8_t" GPIO_EXTILineConfig(GPIOA, GPIO_Pin_0); аргумент несовместим с типом данных. Подскажите пожалуйста где надо глянуть чтобы исправить эту ошибку?
Это я поспешил ) Вот так надо:
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0);
Спасибо большое за ответ буду дальше пробовать.
Еще вопрос а как правильно сбросит флаг прерывания? Попробовал вот так не вышло EXTI_ClearITPendingBit( EXTI0, EXTI_Line)
Что-то залил этот пример и на нажатие на кнопку не реагирует вообще...ошибок при компиляции не было.
Прошил пример, на нажатие на кнопку не реагирует...ошибок при компиляции не было...
STM32F3Discovery?
Да, она самая...
хотел пока просто залить пример дабы проверить, но...
http://www.st.com/web/catalog/tools/FM116/SC959/SS1532/PF254044
Если бы была ошибка в проге из примера, то хотя бы компилятор тогда ругался бы...
Да, она самая…
хотел пока просто залить пример дабы проверить, но…
http://www.st.com/web/catalog/tools/FM116/SC959/SS1532/PF254044
Если бы была ошибка в проге из примера, то хотя бы компилятор тогда ругался бы…
P.S. Прошу прощения, что ниже написал комментарий...
Снова прошу прощения, ошибка была с моей стороны.
В проект не добавил startup файл...
Главное, чтобы заработало в итоге)
Заработало 🙂
Только повешал прерывание на прямую на кнопку.
А как можно сделать, чтобы например прерывание срабатывало, например, если загорелся светодиод 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?
Почему-то так не работает...
Охохох...и снова я сильно поспешил...спутал мягкое с теплым...
Прерывание же и не будет работать на "выход" от ПИНа контроллера...
Здравствуйте. Уже долгое время я бьюсь с установкой приоритета прирываний при помощи 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++);
}
}
Пожалуйста помогите.
Добрый день!
Ваш пример, аккуратно и без ошибок перенесенный,
у меня не работает. Похоже, не вызывается обработчик прерывания. В чем может быть проблема?
Пины соединил. Пробовал переносить прерывание на другие пины, не помогает.
Пробую именно на STM32F3Discovery, среда разработки CodeBlocks 13.12.
Спасибо!
При беглом просмотре Вашей программы я не увидел, где собственно меняется переменная SostKnopki, и где считывается ее состояние.
Я не претендую на истину, сам начинающий, и проблемы с прерываниями, не вызываются! У Вас получилось в итоге?
У меня нет там такой переменной вроде бы. Плата у меня - discovery, ide - Keil 4. Стартап там Кейл сам добавляет в проект при создании проекта.
Да, я перед публикацией все программы проверяю в железе, эта статья была уже давно написана, но совершенно точно проверял код.
У Вас STM32F3Discovery плата? Какую версию startup файла Вы использовали и в какой среде разработки делали проект? Мучаюсь уже месяц, прерывания не вызываются...
Спасибо! Про переменную я не Вам, видимо, мои комменты в кучу смешались.
=)
А можно сделать прерывание по любой смене фронта?
Или, ещё лучше, два обработчика прерывания, для низкого и высокого фронта?
Да, фронт настраивается. Можно настроить прерывание так, чтобы реагировало на любое изменение фронта, а в самом обработчике просто анализировать сигнал, чтобы понять какой именно фронт пришел.
Здравствуйте. Подскажите пожалуйста, возникла проблема при обработке внешнего прерывания
программа зависает в обработчике прерывания:
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'а, поэтому если ее вызвать из другого прерывания, возникает такая вот проблема.
Спасибо!
Добрый день ! Хочу повесить прерывания напрямую на кнопку , делаю следующим образом :
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();
}
//}
//************************************
Что неправильно делаю ?
По тексту создается впечатление, что в "EXTIO_....." это не ноль, а буква "О"... В коде тоже?
поменял на всякий случай , я думаю там в инцилизации должно быть оба ENABLE , но у меня почему то неправильно работает , если и NVIC и EXTI Enable .Проверьте мою иницилизацию пожалуйста
После изменений ничего не поменялось? Если там было неверное название функции обработчика, то при включении прерывания программа улетала не туда куда надо из-за того, что не могла найти обработчик.
а ок , я тогда проверю с ENABLE )
Инициализация вроде бы в порядке.
спасибо как проверю . отпишу.
Добрый день! подскажите, пожалуйста, как организовать двойное нажатие (например зажечь диод). Всю голову сломал. Заранее спасибо!
Завести переменную-счетчик и инкрементировать при нажатии, когда получили два нажатия, то выполнять действие. И кроме того, после первого нажатия начинать отсчитывать время и по прошествию, к примеру, секунды обнулять счетчик (если нужно именно двойное нажатие, а не два нажатия подряд с любым интервалом).
Спасибо, буду пробовать!