Мы уже познакомились с понятием прерывания (и даже использовали таблицу векторов прерываний) для реализации задержки с SysTick. Теперь же нам предстоит работать с кнопкой, и если с точки зрения программной реализации нам всё понятно — действия аналогичны тому, как мы работали со светодиодом (только в этом случае мы не пишем в ODR
, а читаем из IDR
— если вы делаете это в основном цикле), — то с прерываниями не всё так просто. Как отмечалось раньше, они бывают двух видов: внутренние и внешние по отношению к ядру. Так как SysTick — составная часть ядра, то и прерывание от него реализовано по-другому (вы включаете его непосредственно из регистров SysTick). В случае с GPIO (или любой другой периферией) это происходит через модуль внешних прерываний EXTI (англ. external interrupt). Но перед тем, как мы начнем разбираться с ним, давайте настроим ножку, используя библиотеку StdPeriph.
Исходя из принципиальной схемы устройства, ножка, к которой подсоединена кнопка — PA5. Добавим функцию инициализации в файл button.c
xxxxxxxxxx
void button_init(void) {
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
// PA3 - floating input
GPIOA->CRL &= ~GPIO_CRL_MODE5;
GPIOA->CRL |= GPIO_CRL_CNF5_1;
GPIOA->CRL &= ~GPIO_CRL_CNF5_0;
// ...
}
Настройка завершена. Наша цель — по нажатию кнопки менять состояние светодиода (выключать, если он был включен, и наоборот), который мы уже настраивали ранее. Теперь осталось написать код, который будет реагировать на нажатие. Конечно, мы можем проверять регистр IDR
в главном цикле, но это не самое лучшее решение.
Замечание
Посмотрите внимательно на принципиальную схему устройства, в частности, на то, как подключена кнопка. В случае подключения её без «обвеса» (RC-фильтра) будет происходить так называемый «дребезг» механического контакта.
Это приведет к непредсказуемому результату, т. е. получится так, что кнопка нажимается несколько раз, и в каком состоянии окажется светодиод – неизвестно. Чтобы «отфильтровать» шум, можно поставить задержку на 30-40 мс (принято считать, что дребезг составляет именно столько времени). Далее мы еще раз проверим, нажата ли кнопка: если да, то, очевидно, это был не «шум», и нам действительно нужно переключить состояние светодиода. Для того чтобы избежать такого же дребезга, но уже на этапе отпускания кнопки, необходимо добавить цикл
while
, аргументом которого будет состояние регистра нужной ножки. Вот часть программы:xxxxxxxxxx
while(1) {
if ((GPIOA->IDR & GPIO_IDR_IDR5) == GPIO_IDR_IDR5) {
delay(40);
if ((GPIOA->IDR & GPIO_IDR_IDR5) == GPIO_IDR_IDR5) {
GPIOA->ODR ^= GPIO_ODR_ODR3;
while(GPIOA->IDR & GPIO_IDR_IDR5);
}
}
Так как RC-цепочка впаяна, то дребезг будет фильтроваться и никакой проблемы не возникнет.
Гораздо лучше использовать для таких целей прерывания. Обратимся к документации (это делается для более глубокого понимания — эту информацию можно получить и из наименования макросов в библиотеке), к разделу «8 Interrupts and events» ⇒ «External interrupt/event controller (EXTI)». На странице 133 можно найти функциональную схему работы этого модуля.
Изображение из Reference Manual, Figure 20. External interrupt/event controller block diagram
Блок «Edge detect circuit» может реагировать как на передний фронт (импульса), так и на задний или на оба сразу. Это задается соответствующими битами в соответствующих регистрах. Всё в той же документации можно найти схему подключения ножек к модулю EXTI:
Изображение из Reference Manual, Figure 21. External interrupt/event GPIO mapping
Как видно, 3-я ножка порта A подключена к EXTI5
. Чтобы глобально разрешить прерывания с EXTI5
, необходимо произвести работу с регистром ISER
(контроллера NVIC). К счастью, в файле core_cm3.h
уже имеется готовая функция:
xxxxxxxxxx
__STATIC_INLINE void NVIC_EnableIRQ(IRQn_Type IRQn)
{
NVIC->ISER[((uint32_t)(IRQn) >> 5)] =
(1 << ((uint32_t)(IRQn) & 0x1F)); /* enable interrupt */
}
IRQn_Type объявлен в файле stm32f10x.h
, следовательно:
xxxxxxxxxx
NVIC_EnableIRQ(EXTI9_5_IRQn);
Перейдем к регистрам.
Регистр IMR
из Reference Manual
Регистр IMR
отвечает за включение/отключение прерывания. Чтобы разрешить прерывание EXTI5
, нужно записать «1» в MR3
:
xxxxxxxxxx
EXTI->IMR |= EXTI_IMR_MR5;
Последующие два регистра отвечают за прерывание по переднему фронту и заднему соответственно. Мы будем реагировать на передний, поэтому следует настроить RTSR
:
Регистр RTSR
из Reference Manual
Так как мы работаем с 5-й ножкой, нас интересует бит TR5
:
xxxxxxxxxx
EXTI->RTSR |= EXTI_RTSR_TR5;
Допишем код.
xxxxxxxxxx
void button_init() {
// ...
NVIC_EnableIRQ(EXTI9_5_IRQn);
EXTI->IMR |= EXTI_IMR_MR5;
EXTI->RTSR |= EXTI_RTSR_TR5;
EXTI->FTSR &= ~EXTI_FTSR_TR5;
}
Настройка прерывания закончена, осталось написать обработчик, название которого мы также возьмем в startup_<mcu>.s
файле.
Регистр PR
из Reference Manual
Последний значимый для нас регистр именуется PR
(англ. pending register). Дело в том, что прерывание может прийти от разных портов (что было видно на схеме выше), поэтому вам самостоятельно нужно проверять, с какого именно порта оно пришло, и сбрасывать «флаг», который записывается в соответствующий бит регистра PR
.
Таким образом, код для обработчика примет следующий вид:
xxxxxxxxxx
void EXTI9_5_IRQHandler(void) {
static volatile uint8_t flag = 0;
if (flag) {
GPIOA->ODR |= GPIO_ODR_ODR3; // led on
flag = 0;
} else {
GPIOA->ODR &= ~GPIO_ODR_ODR3; // led off
flag = 1;
}
EXTI->PR |= EXTI_PR_PR5;
}
В стандартную библиотеку входят два файла для работы с модулем EXTI
. Опять же, для повышения уровня абстракции мы предлагаем вам переписать работу с EXTI
на функции стандартной библиотеки. В дальнейшем мы не будем рассматривать периферию так же подробно (на уровне регистров). Нашей целью было показать принцип — мы этой цели достигли.
Код урока можно найти на github: CMSIS.
Назад | Оглавление | Дальше