Внешние прерывания активизируются по изменению логического уровня на ноге контроллера. Удобно для обработки срочных событий от внешней периферии, иногда на них делают обработку кнопок. Т.к. они могут будить контроллер из глубокой спячки.
У STM32 за внешние прерывания отвечает EXTI контроллер. Его основные возможности:
До 20 линий прерываний (в реальности несколько меньше, зависит от контроллера)
Независимая работа со всеми линиями. Каждой линии присвоен собственный статусный бит в спец регистре
Улавливает импульсы длительность которых ниже меньше периода частоты APB2
EXTI Может генерировать:
- Прерывания — это когда происходит переход на обработчик
- События — когда обработчик не вызывается, просто поднимается флажок. Может разбудить проц или пнуть какую периферию, АЦП, например.
- Софтверные прерывания — то же самое, что и обычные прерывания, но мы их вызываем вручную, записью бита в регистр.
Сама структура связи EXTI следующая:
Т.е. у нас есть 16 EXTI линий к которым мы можем подключать выводы порта. Причем группируются они по номерам пинов. Т.е. мы не можем на разные прерывания навесить, например, PA0 и PB0 либо одно, либо другое. Придется выбирать. Мультиплексоры управляются из группы регистров AFIO_EXTICR*.
Кстати, интересный факт. Даташит утверждает следующее:
8.4 Note: To read/write the AFIO_EVCR, AFIO_MAPR and AFIO_EXTICRx registers, the AFIO clock should first be enabled. Refer to Section 6.3.7: APB2 peripheral clock enable register (RCC_APB2ENR).
Т.е. для записи в регистры AFIO_EXTICRx должно быть включено тактирование AFIO. Проверил без него — все работает, значения записываются, прерывания работают. Может я где то ошибся? Прогнал под JTAG в Keil — нет, AFIO CLOCK Disabled, а все работает. Странно.
Но после выяснилось, что без включения AFIO мультиплексоры не работают. Т.е. сигнал будет передаваться ТОЛЬКО от порта А, т.к. это дефолтное значение мультиплексоров. Причем вне зависимости от того, что вы там себе настроили в программе. Прикиньте какие грабли! Забыл включить AFIO и привет! Ладно бы прерывания тупо не работали, а они будут работать, но совсем не от того вывода который вы выставили. А если там что-то дрыгается, то прерывание у вас срабатывать будет, но совсем не тогда, когда вы ждете. Попробуйте выловить такую фигню!
Так что AFIO надо включать до выбора канала мультиплексора! У меня же все работало потому, что выбранные линии порта совпали с портом А, стоящим по умолчанию. ВСЕГДА затактирывайте AFIO если используете альтернативные функции портов, избежите кучи глюков.
Есть еще четыре EXTI линии которые подключены не к GPIO, а к разной периферии.
● EXTI 16 — PVD выход
● EXTI 17 — событие от RTC Alarm
● EXTI 18 — событие от USB Wakeup
● EXTI 19 — событие от Ethernet Wakeup (там где есть эзернет на борту)
▌Конфигурация и регистры
AFIO_EXTICR* — выбор вывода.
Сначало надо выбрать в AFIO какой канал у нас к чему подключен. За это отвечает группа регистров AFIO_EXTICR* их там 4 штуки. Вот структура первого:
Т.е. у нас тут типичный мультиплексор. В первом регистре первые 4 канала EXTI, во втором регистре (AFIO_EXTICR2) следующая четверка и так до 16го. Мы просто выбираем на какой канал какой порт будет подцеплен. Т.е. чтобы, например, повесить прерывание на PD2 надо в EXTI2 (биты с 8 по 11) записать 0011. Ногу выбрали, дальше приступаем к настройке непосредственно прерываний.
EXTI_IMR — Регистр масок прерываний. Единичка в этом регистре в соответствующем бите разрешает прерывание на соответствующий канал EXTI. После сброса там нули. Т.е. все прерывания внешние локально запрещены.
EXTI_EMR — Регистр масок событий. Единичка тут разрешает событие на канал. События отличается от прерывания тем, что контроллер только сигнализирует о нем, но никуда, ни по какому вектору, не бежит выполнять код. Еще события умеют чуять некоторые периферийные устройства.
EXTI_RTSR и EXTI_FTSR — Регистры определяющие когда срабатывать. По какому фронту? Единичка в EXTI_RTSR даст событие по восходящему фронту, а единичка в EXTI_FTSR по спадающему. Можно поставить оба бита и ловить прерывание на подъеме и спаде.
EXTI_SWIER — Регистр софтверного запуска прерывания.
Т.е. если нам надо вручную, принудительно, его запустить, то надо сюда записать в конкретный бит. Записываем в EXTI_SWIER 1 и у нас вскакивает бит в EXTI_PR, а контроллер генерирует событие и, если разрешено, уходит на прерывание.
EXTI_PR — Ну и бит собственно флажка события, по которому происходит вызов прерывания. Обратите внимание на обозначение обращения с ним — rc_w1. Это следует читать как read/clear = write 1. Т.е. мы этот бит можем читать, а чтобы его сбросить надо в него записать единичку. Как на AVR прям :) При переходе в прерывание его надо сбрасывать вручную! Иначе на выходе с прерывания будет «и снова здравствуйте!».
Теперь покажу пример кода работы с прерыванием. Отдельный пример создавать мне лень, возьму кусок кода из прошлого примера про NVIC там как раз были внешние прерывания:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | #include "stm32f10x.h" #define F_CPU 72000000UL void Delay( uint16_t Val); // Обработчик EXTI 1 void EXTI1_IRQHandler(void) { // Делаем что-то в прерывании uint32_t i; for(i=0;i!`;i++) { GPIOB->BSRR = GPIO_BSRR_BS0; // Сбросили бит. Delay(55560); GPIOB->BSRR = GPIO_BSRR_BR0; // Установили бит. Delay(55560); } // Сбрасываем флаг прерывания // Обратите внимание как сбрасывается флаг прерывания! Единичкой! // Т.е. тут даже OR не надо, можно просто в это место загнать 1. Нуль роли не играет. // Очень удобно :) EXTI->PR |= EXTI_PR_PR1; } // Обработчик EXTI 2 void EXTI2_IRQHandler(void) { // Делаем что-то в прерывании uint32_t i; for(i=0;i!`;i++) { GPIOB->BSRR = GPIO_BSRR_BS1; // Сбросили бит. Delay(55560); GPIOB->BSRR = GPIO_BSRR_BR1; // Установили бит. Delay(55560); } // Сбрасываем флаг прерывания EXTI->PR |= EXTI_PR_PR2; } int main(void) { // Запускаем конфигуратор тактовой частоты из CMSIS SystemInit(); // Тут стандартная настройка портов. Нам же кнопочки почуять, диодиками поморгать. // Подаем тактирование на порт А, В. Надо AFIO затактировать. Иначе будет трудно уловимый глюк! // Прерывания работать будут, но все будут вести на Порт А вне зависимости от настроек мультиплексоров. RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_AFIOEN; // Настройка выходных портов. Те, что на светодиоды. // Конфигурируем CRL регистры. // Сбрасываем биты CNF для битов 3,4,5. Режим 00 - Push-Pull GPIOB->CRL &= ~(GPIO_CRL_CNF5 | GPIO_CRL_CNF1 | GPIO_CRL_CNF0); // Выставляем режим для 0,1,5 пина. Режим MODE 01 = Max Speed 10MHz Ставим нулевой бит MODE GPIOB->CRL &= ~(GPIO_CRL_MODE5 | GPIO_CRL_MODE1 | GPIO_CRL_MODE0); // Занулим заранее GPIOB->CRL |= GPIO_CRL_MODE5_0 | GPIO_CRL_MODE1_0 | GPIO_CRL_MODE0_0; // Выставим бит 0 // Настройка входных портов. Те что на кнопки. // Конфигурируем CRL регистры. // Выставляем режим порта в CNF для битов 1,2 Режим 10 = PullUp(Down) Ставим первый бит CRL GPIOA->CRL &= ~(GPIO_CRL_CNF1 | GPIO_CRL_CNF2); // Занулим заранее GPIOA->CRL |= GPIO_CRL_CNF1_1 | GPIO_CRL_CNF2_1; // Выставим бит 1 // Выставляем режим для 1,2 пина. Режим MODE_00 = Input GPIOA->CRL &= ~(GPIO_CRL_MODE1 | GPIO_CRL_MODE2); // Установили бит 1,2 в ODR включив PULL UP. GPIOA->BSRR = GPIO_BSRR_BS2 | GPIO_BSRR_BS1; // Вот тут самая мякотка. Настройка, собственно, прерываний :) // Настраиваем EXTI1 и EXTI2 на выводы порта А. Они в первом регистре оба. А регистры в виде массива описаны в CMSIS // Тут еще одна засада. Регистры EXTICR сгруппированы в массив, а массив считается с нуля. Т.е. первый EXTICR регистр в 0 ячейке // второй регистр в первой, третий во 2. Со сдвигом короче. Вот чего бы их, как принято у программистов, не обзывать с нуля? AFIO->EXTICR[0] |= AFIO_EXTICR1_EXTI1_PA | AFIO_EXTICR1_EXTI2_PA ; // Прерывание по падению уровня на ноге 1 и 2 порта привязанного к EXTI EXTI->FTSR |=(EXTI_FTSR_TR1 | EXTI_FTSR_TR2); // Функции CMSIS разрешающие прерывания в NVIC NVIC_EnableIRQ (EXTI1_IRQn); NVIC_EnableIRQ (EXTI2_IRQn); //Расставляем приоритеты (опционально, можно закомментить и тогда приоритеты будут равны) NVIC_SetPriority (EXTI2_IRQn, 2); // Разрешаем прерывания в периферии EXTI->IMR |=(EXTI_IMR_MR1 | EXTI_IMR_MR2); // Разрешаем глобальные прерывания __enable_irq (); //Фоновая программа while(1) { GPIOB->BSRR = GPIO_BSRR_BR5; // Сбросили бит. Delay(55560); GPIOB->BSRR = GPIO_BSRR_BS5; // Установили бит. Delay(55560); } } /* Тупая задержка */ void Delay(uint16_t Val) { for( ; Val != 0; Val--) { look = Val; __nop(); } } #ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t* file, uint32_t line) { /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %drn", file, line) */ /* Infinite loop */ while (1) { } } #endif |
Вот, собственно, и все. Ничего сложного.
Здравствуйте.
А будет ли урок по отладке с помощью CoLinkEX?
А есть какие то непонятки? Пример подключения и настройки у меня где то был. А дальше обычная работа. Точки останова, ватчи в память и прочее.
http://softkey.biz/postcard/?utm_medium=email&utm_source=UniSender&utm_campaign=utm_campaign&referer1=rass&referer2=ny14
Спасибо за статью. Мне как новичку очень помогло для более быстрого понимания сабжа. Только вот наступил на грабли (а как без них). При выключенном тактировании AFIO мультиплексоры не будут переключаться, поэтому обрабатывать мы будем всегда только порт PА по умолчанию, не зависимо от того, что записали в AFIO_EXTICR*
Во! А я сидел и думал, каким боком тут AFIO — вроде альтернативные функции задействованы, но включай я AFIO — не включай — все работает. Поэтому я решил, что оно там почему-то нафиг не нужно. А так совпало, что все инты у меня на порту А висели. А проверить переключаются ли мульты я не догадался. Спасибо! Дополнил статью.
Причем, на F4 RCC_APB2ENR_AFIOEN открутили.
В чем различие между Interrupts и events? В коментах это обсуждалось, но внятного ответа никто не сказал и пример не привели.
В принципе разница понятна. Не могу понять как работать с событиями. какой флаг анализировать?
Все разобрался. Достаточно посмотреть на схему и все становится ясно. Флагов здесь никаких нет. При принятии фронта или использованием регистра SWIER и установленном бите в регистре EMR генератор импульсов генерирует наше событие.
Опечатка в примере — EXTICR1 соответствует нулевому элементу массива. Должно быть:
AFIO->EXTICR[0] |= AFIO_EXTICR1_EXTI1_PA | AFIO_EXTICR1_EXTI2_PA ;
Тогда бы оно не работало, а оно работает.
Это не показатель, пользователь dev совершенно прав.
AFIO->EXTICR[0] |= AFIO_EXTICR1_EXTI1_PA | AFIO_EXTICR1_EXTI2_PA ;
или AFIO->EXTICR[2>>0x02] |= AFIO_EXTICR1_EXTI1_PA | AFIO_EXTICR1_EXTI2_PA ;
Оно работает потому что там по умолчанию выбраны для всех каналов порты А.
Здравствуйте!
У меня такой вопрос: в таблице прерываний на 5-9 прерывания у нас один общий вектор. При этом в регистре разрешения внешних прерываний IMR, можно разрешить и 5 и 6 и 9 прерывания по отдельности.
Как решить вопрос с различными обработчиками для одного вектора? может он там флаги смотрит? Или на один вектор один обработчик, а внутри него уже ручками через switch-case?
Прошу прощения, уточню: в таблице прерываний один вектор на внешние прерывания номер 5-9.
Ручками внутри флаги щупать.
Для того чтобы сделать usb нужно искать мк с поддержкой usb или это функция отдельного чипа с которым будет общаться основной мк? (Что искать то, куда смотреть)
Нашел на сайте atmel информацию, в общем можно и так и так, либо чип с поддержкой либо uart usb чип юзать
Похоже, в свежую CMSIS внесли изменения, и теперь надо писать EXTICR[0] … EXTICR[3] вместо EXTICR1 … EXTICR3
Возможно стоит упомянуть в статье об этом, а то у новичков будут плавиться мозги))
Теперь окончательно разобрался. Ди, у тебя реально ошибка в коде.
AFIO->EXTICR[1] |= AFIO_EXTICR1_EXTI1_PA | AFIO_EXTICR1_EXTI2_PA — это неправильно.
Элементы массива EXTICR структуры AFIO нумеруются начиная с нуля, а вот в CMSIS-овских дефайнах типа AFIO_EXTICR1_EXTI1_PA цифра после EXTICR нумеруется начиная с 1.
Спасибо, поправил.
Почему сброс флага прерывания Вы делаете в конце обработчика? Я сделал как Вы и у меня обработчик на одно событие вызывается дважды.
void EXTI0_IRQHandler(void) {
GPIOC->ODR ^= GPIO_ODR_ODR13; //делаем что-то полезное…
EXTI->PR |= EXTI_PR_PR0; //сброс флага события
}
А когда сначала сброс флага, а потом полезная работа, то фсьо норм. Обработчик вызывается как и положено один раз на событие.
void EXTI1_IRQHandler(void) {
EXTI->PR |= EXTI_PR_PR1; //сброс флага события
GPIOC->ODR ^= GPIO_ODR_ODR13; //делаем что-то полезное…
}
И ещо. При отладке.
Когда сброс флага стоит в конце, и на него поставить точку останова, а потом сделать щаг (F11) с выходом, то второй раз обработчик уже не вызывается. Магия!
А не может у вас второе прерывание прийти когда вы еще первое не обработали? Изза дребезга или еще чего подобного.
Пожалуй нет. При програмном запуске (используя SWIER, или через смену подтяжки через ODR) — те же яйца.
Я то же долго мучился пока не стал в начале обработчика флаг сбрасывать. Хоть я точно знаю там всего приходит один импульс. Так как его посылает другое устройство, и я еще посмотрел осциллографом
Почему у меня не работает? В обработчик не заходит.
Флаг в EXTI_PR поднимается, а в обработчик ни ногой. Через SWIER тоже ничего.
void EXTI0_IRQHandler(void)
{
GPIOC->ODR ^= (1<PR |= EXTI_PR_PR6;
}
int main(void)
{
// Настройка LED на PC13
RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
GPIOC->CRH &=~ GPIO_CRH_CNF13;
GPIOC->CRH |= GPIO_CRH_MODE13_0;
GPIOC->ODR ^= (1<APB2ENR |= (RCC_APB2ENR_IOPAEN | RCC_APB2ENR_AFIOEN);
GPIOA->CRL &= ~GPIO_CRL_CNF6;
GPIOA->CRL |= GPIO_CRL_CNF6_1;
GPIOA->CRL &= ~GPIO_CRL_MODE6;
GPIOA->BSRR = GPIO_BSRR_BS6;
AFIO->EXTICR [0] |= AFIO_EXTICR1_EXTI0_PA;
EXTI->FTSR |= EXTI_FTSR_TR6;
NVIC_EnableIRQ (EXTI0_IRQn);
EXTI->IMR |= EXTI_IMR_MR6;
__enable_irq ();
while (1)
{
}
}
А как вы проверяете? В симуляторе может не работать, там есть глюки. Оно и в железе не заходит?
EXTI->IMR |=(EXTI_IMR_MR1 | EXTI_IMR_MR2);
и где у вас настройка прерываний в периферии.
Проверяю в железе, через китайский ST-link. Настройка в периферии между строчками NVIC_EnableIRQ (EXTI0_IRQn); и __enable_irq (); Настраиваю пин 6 порта А.
Скажите пожалуйста если я настроил внешнее прерывание для срабатывания по восходящему и спадающему фронтам , можно ли индицировать уже в обработчике по какому фронту произошло прерывание ?
Нет, но можно быстро чекнуть состояние порта и по нему понять что вызывало прерывание. Если там высокий уровень, то было восходящее, если низкий — нисходящее.