Обработка прерываний
Прерывание — это функция микроконтроллера, которая позволяет прерывать выполнение основной программы при совершении некоторого события и ненадолго передавать управление специальной подпрограмме. Таким событием может быть сигнал на контакте от внешнего устройства, импульс встроенного таймера или даже ошибка при работе с памятью. Обработав событие в подпрограмме, система возвращается к выполнению основной программы ровно в том месте, где она прервалась.
У STM32F103 есть 16 линий внешних прерываний (кстати, у atmega328p их всего 2), которые подключены ко всем ножкам через мультиплексор. То есть, ножки PA0, PB0 и PC0 соединены с одной линией EXTI0, а ножки PA1, PB1, PC2 — c линией EXT1. Мы можем связать только одну из трёх ножек с выделенной линией прерывания.
Настройка прерывания в конфигураторе
У нас будет две кнопки, нажатие которых мы должны детектировать. Начнем с установки двух ножек в режим прерываний. Выберем всё те же контакты PA0 и PA1. Жмем левую кнопку мыши и выбираем режим GPIO_EXTI0 и GPIO_EXTI1.
Затем открываем новую для нас вкладку System Core/GPIO/NVIC и ставим там галочку в колонке Enabled напротив обеих строчек:
- EXTI line0 interrupt
- EXTI line1 interrupt
Должно получиться вот так:
Как и ранее, устанавливаем подтяжку к земле и назначаем псевдонимы BTN1 и BTN2.
На этой же вкладке можно заметить еще одно поле: GPIO mode. Оно может принимать несколько значений, включая:
- External interrupt mode with Rising edge trigget — прерывание срабатывает по нарастающему фронту (в случае кнопки — когда мы нажимаем кнопку);
- External interrupt mode with Falling edge trigger — по спадающему (когда отпускаем кнопку);
- External interrupt mode with Rising/Falling trigger — по смене уровня (сначала нарастающий, а следом спадающий — нажали и сразу отпустили кнопку).
Эти режимы указывают микроконтроллеру по какой части импульса должно срабатывать прерывание. Пока нам это не особо важно, поэтому оставим как есть — режим Rising. То есть прерывание будет происходить в момент когда мы нажимаем кнопку.
Настраиваем ножку для светодиода.
Наконец, контакты для программатора.
После всех настроек в конфигураторе не забываем нажать «Сохранить». Только после сохранения, конфигуратор внесет все изменения в код проекта.
Программа
Итак, для двух кнопок у нас задействованы две линии прерываний: EXTI0 и EXTI1. Опишем функцию-обработчик, которая будет вызываться при срабатывании этих прерываний.
Делаем это в файле main.c, в соответствующем фрагменте, выделенном специально для описания функций разработчика.
/* USER CODE BEGIN 4 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){
if(GPIO_Pin==BTN1_Pin) {
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);
} else if(GPIO_Pin==BTN2_Pin) {
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
} else{
__NOP(); //ничего не делаем
}
}
/* USER CODE END 4 */
В коде всё просто. У функции есть аргумент GPIO_Pin — это номер контакта с которого пришло прерывание. Проверяем, если это первый контакт, то гасим светодиод, если второй контакт — зажигаем.
Следующий шаг. Откроем для редактирования другой файл: stm32f1xx_it.c
В этом файле конфигуратор уже создал две функции: EXTI0_IRQHandler и EXTI1_IRQHandler. Наша задача — добавить в тело этих функций вызов нашего обработчика из main.c
void EXTI0_IRQHandler(void)
{
/* USER CODE BEGIN EXTI0_IRQn 0 */
/* USER CODE END EXTI0_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(BTN1_Pin);
/* USER CODE BEGIN EXTI0_IRQn 1 */
/* USER CODE END EXTI0_IRQn 1 */
}
/**
* @brief This function handles EXTI line1 interrupt.
*/
void EXTI1_IRQHandler(void)
{
/* USER CODE BEGIN EXTI1_IRQn 0 */
/* USER CODE END EXTI1_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(BTN2_Pin);
/* USER CODE BEGIN EXTI1_IRQn 1 */
/* USER CODE END EXTI1_IRQn 1 */
}
Функция EXTI0_IRQHandler вызывается при срабатывании прерывания на линии 0. В этом случае мы образаемся к нашему обработчику с аргументом BTN1_Pin (наш псевдоним для первой кнопки). То же самое делаем для EXTI1_IRQHandler, только кнопка BTN2_Pin.
Загружаем код, проверяем. Работает — круто! Не работает? — проверяем всё на 7 раз.
Почему обработчик прерываний в main.c — HAL_GPIO_EXTI_Callback, а вызывается он из stm32f1xx_it.c как HAL_GPIO_EXTI_IRQHandler ?
вызов Вашегообработчика прерываний нужно добавить в сгенерированную функцию EXTIX_IRQHandler
точнее говоря, если вы генерируете код через куб, то функция EXTX_Callback вызывается через HAL_GPIO_EXTI_IRQHandler
она и так вызывается в качестве week после сброса. в кубе надо поменять приоритет с 0 допустим на 2.
Здравствуйте. Находится ли МК внутри прерывания когда срабатывает одна из функций ***handler? если да то получается что и callback обрабатывается внутри прерывания. Просьба уточнить последовательность смены режимов МК для данного примера
А зачем такие танцы с бубнами? Сначала код для двух разных прерываний разный, потом мы его объединяем, вызывая одну функцию, с разными аргументами, чтобы потом опять ветвлениями выяснять, а какая же кнопка была нажата. А не проще всё сделать непосредственно в самих функциях-обработчиках, не объединяя код без необходимости?
мне показалось, что у вас ошибка в фразе : «а ножки PA1, PB1, PC2 — c линией EXT1» надо написать EXTI0