Янв 132013
 

Наш постоянный читатель и автор — Metcenger прислал статью, в которой он описывает работу с источниками внешних прерываний для микроконтроллеров серии STM32L.

Давайте попробуем настроить внешнее прерывание на нашу кнопочку, чтобы при нажатии на нее загорался светодиод, например, синий.

Настраиваем ножку диода на выход. Кнопку на вход. Самый обычный вход. Никаких альтернативных функций. Открываем Reference manual в разделе Interrupts and events и конфигурируем EXTI регистры. Это такие регистры, которые отвечают за прерывания или события. Мы рассмотрим их конфигурацию на примере внешнего прерывания от кнопки.

EXTI interrupt mask register (EXTI_IMR). Здесь мы задаем какая линия из 23-х у нас будет отвечать за прерывания. Дело в том, что, в отличие от AVR, здесь ножки МК не жестко привязаны к внешним прерываниям, здесь почти любую ножку можно назначить на прерывание. А какая нога будет висеть на прерывании (сразу для всех портов) — этим регистр и занимается. Например, записав в него число 0x1,

EXTI->IMR |= EXTI_IMR_MR0; // #define EXTI_IMR_MR0 ((uint32_t)0x00000001)

мы говорим, что хотим использовать для прерывания линию 0.

EXTI_IMR

Т.е. получаем настроенными на прерывания ноги 0 со всех портов. Допустим, хотим использовать ногу PC0 — то тоже значение надо записать сюда. Если мы захотим использовать ногу PB6, то пишем

EXTI->IMR |= EXTI_IMR_MR6;

External interrupt_event GPIO mapping

Ноги соответствуют линиям, а какой конкретно порт — выберем дальше.

Настраиваем по фронту или срезу, по какому перепаду уровня хотим получать срабатывание.

EXTI->RTSR |= EXTI_RTSR_TR0; //настройка фронта-среза

В файле проекта попробуйте закомментировать одно или другое — получится, что в одном случае загорится при нажатии, а во втором случае, только тогда, когда уже отпустили кнопку (как на мину встал -  убрал ногу и…)

Вот теперь мы и выберем, на какой порт нам настроить нашу линию 0. За это отвечает SYSCFG registers, а именно его SYSCFG_EXTICR1…..SYSCFG_EXTICR4.

SYSCFG_EXTICR1

4 регистра по 4 линии — 16 линий для внешних прерываний. 23-16=7 Остальные 7- под внутренние дела.

Итак, хотим порт А

SYSCFG->EXTICR[0] |= SYSCFG_EXTICR1_EXTI0_PA; // Connect EXTI line 0 to PA.0

Обращу внимание, что в библиотеках эти регистры объявлены как массив, т.е. 0-й элемент соответствует регистру EXTICR1

Т.е. захотим линию 4 конфигурировать, надо будет писать

SYSCFG->EXTICR[1] |= SYSCFG_EXTICR2_EXTI4_PA;

Ну и, конечно, разрешить тактирование SYSCONFIG

RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;

Надо сказать, что в серии F, не во всей правда, регистры те же (EXTICR), только они не в SYSCFG находятся, а в AFIO вроде. И также надо разрешать тактирование AFIO. Смысл тот же.

Ну и на последок — разрешаем прерывания и устанавливаем их приоритет

NVIC_EnableIRQ(EXTI0_IRQn); //разрешение прерывания EXTI0
NVIC_SetPriority(EXTI0_IRQn, 1); //задаем приоритет прерывания

Загрузить архив с проектом.

Другие статьи:

  16 Responses to “STM32L1xx. Внешние прерывания”

  1. Можете описать обработку событий типа UsageFault, у меня что-то не получается.

  2. не совсем понял, что имеется ввиду?

  3. Да все сам уже разобрался с исключениями.

  4. Доброго времени суток! я начал изучать мк stm32l152 и наткнулся на вашу замечательную статью. Я использовал ваш код и библиотеки, и у меня все заработало! Я даже почти все понял, из того, что вы объяснили. Однако затем я решил пойти другой дорогой и у меня получился код в несколько строчек, выполняющий те же функции что и ваш. В общем, вы не могли бы объяснить нубу, зачем было так усложнять или в этом есть смысл?

    вот пример моего рабочего кода:

    #include «stm32l1xx.h»

    int main(void)
    {
    RCC->AHBENR |= RCC_AHBENR_GPIOAEN | RCC_AHBENR_GPIOBEN;
    GPIOB->MODER |= GPIO_MODER_MODER6_0 | GPIO_MODER_MODER7_0;
    GPIOB->OTYPER &= ~(GPIO_OTYPER_OT_6 | GPIO_OTYPER_OT_7);
    while(1)
    {
    if(GPIOA->IDR & GPIO_OTYPER_IDR_0)
    GPIOB->ODR |= GPIO_OTYPER_ODR_6 | GPIO_OTYPER_ODR_7;
    }
    }

    Спасибо.

    • Тут меня одна мысль посетила…
      функции-то этот код и выполняет те же, но не совсем…
      У меня они на прерываниях сделаны, в моей статье и инициализируются они, а здесь просто цикл, где и опрашивается флаг регистра прерывания. И в этом коде нет нигде инициализации прерывания. Так что- это разные вещи. Хотя, лампочку оба зажигают.

  5. Я отвечу за автора, надеюсь не обидится :). Тут как бы не учебник со сплошными аксиомами, а всего лишь делимся опытом. Вот почитал я мануал, сел и набросал код, поискал баги, почертыхался. Отладил, заработало, почитал снова мануал, создал свое мнение о порядке настройки данного периферийного модуля, написал код исходя из своих знаний и умений. Вот и выложил, поделился рабочим вариантом.
    Всегда можно сделать лучше!
    А избыточность кода, возможно, всего лишь для наглядности, чтобы понятней было для начинающего.

    • Согласен. Лично мне эта статья помогла лучше разобраться с внешними прерываниями, за это автору низкий поклон. Хотя все равно остается впечатление, что существует скрытый смысл в этом коде)

  6. уже год прошел… мало что помню. Для того эти записи и делаются, чтобы потом если что, так вспомнить сразу.
    Особо накрученного кода я не вижу. Просто пошагово включил тактирование, выбрал фронты и прочее. Так код читаемый. Все в кучу валить не стал.
    И, да, while (1)- это некорректно.
    Корректно for (;;)
    Так писали Керниган и Ричи.

  7. Я пересмотрел код свой- там ничего не накручено.
    ANSI- старо. Есть Си- 99
    for (;;)
    это корректно, т.к. говорит компилятору, что это вечный цикл.
    А while (1)- тут он каждый раз должен проверить условие. Умный компилятор догадается, что это вечный цикл, но может и проверить.

    цитата с
    http://chipenable.ru/index.php/programming-avr/item/68-si-test-0x10-luchshih-voprosov-dlya-teh-kto-hotel-by-stat-programmistom-vstraivaemyh-sistem#comment-1955

    Другая общепринятая конструкция выглядит так:

    for( ; ; )
    {

    }

    Лично я не люблю эту конструкцию, потому что такой синтаксис не объясняет, что происходит. Если кандидат предлагает именно это решение, я пытаюсь выяснить, чем он обосновывает свои действия. Если ответ сводится к тому, что — «Меня научили так делать, и я никогда об этом с тех пор не думал» — это говорит не в пользу кандидата. С другой стороны, если он заявляет, что Керниган и Ритчи предпочитали этот метод, и это единственный способ для бесконечного цикла пройти контроль на соответствие стандартам, то он получает дополнительные очки.

  8. Добрый день! Пытаюсь настроить ножку PA9 на прерывание по заднему фронту — но после события на входе программа не уходит на вектор прерывания.
    void EXTI0_IRQHandler(void)
    {
    //on (LED_Blue);
    EXTI->PR |= EXTI_PR_PR9;//сбросили бит прерывания
    ON(PERIPH_2, SW1); // Вывести сигнал для диагностики
    Delay_ms(2);
    OFF(PERIPH_2, SW1); // Вывести сигнал для диагностики
    }

    Хотя в отладчике видно что когда я пинцетом корочу ножку PA9 на землю ( ножка подтянута внешним резистором на питание) то в Watch я вижу изменение уровня т.е. ножка правильно сконфигурирован на вход, и вижу что флаг устанавливается EXTI_PR9 — но прерывание программа не уходит. Подскажите что я неправильно сделал

    Вот мой код:
    RCC->AHBENR |= RCC_AHBENR_GPIOAEN; //Включить тактирование порта А
    GPIOA->MODER &= ~GPIO_MODER_MODER9; // Уст. 00 — цифровой вход для PA0
    GPIOA->OTYPER &= ~GPIO_OTYPER_OT_9; //Output push-pull
    GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR9; // Уст. 00 — без подтяжки для PA0
    GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR9; //40 MHz

    RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;// вкл тактирование SYSCONFIG
    SYSCFG->EXTICR[0] |= SYSCFG_EXTICR1_EXTI0_PA; //подключить линия EXTI 0 к порту PA.0
    EXTI->IMR |= EXTI_IMR_MR9; // использовать для прерывания линию 9
    EXTI->FTSR |= EXTI_FTSR_TR9; //настройка фронта
    //EXTI->RTSR |= EXTI_RTSR_TR9;

    NVIC_EnableIRQ(EXTI0_IRQn); //разрешение прерывания EXTI0
    NVIC_SetPriority(EXTI0_IRQn, 1); //задаем приоритет прерывания

  9. Доброго Вам дня!
    У меня возник такой вопрос: у МК STM32L для линий с 10 по 15 существует общий обработчик EXTI15-10_IRQn. Необходимо обработать два прерывания: одно от кнопки по линии 10 и второе от переключателя по линии 11. Настройки для линии одинаковы. Одновременно может возникнуть только одно событие.
    Однако как только я пытаюсь запустить обработчик с разрешенными двумя прерываниями, у меня не работает ни одно из них. При разрешении только одного из них, все отрабатывает как положено.

    Инициализация линий прерываний:
    /*** Interrupt for switch ***/
    SYSCFG->EXTICR[2]= SYSCFG_EXTICR3_EXTI10_PB;
    EXTI->RTSR |= EXTI_RTSR_TR10; // Rising edge
    EXTI->FTSR |= EXTI_FTSR_TR10; // Falling edge
    EXTI->IMR |= EXTI_IMR_MR10;
    /*** Interrupt for power button ***/
    SYSCFG->EXTICR[2]= SYSCFG_EXTICR3_EXTI11_PB;
    EXTI->RTSR |= EXTI_RTSR_TR11; // Rising edge
    EXTI->FTSR |= EXTI_FTSR_TR11; // Falling edge
    EXTI->IMR |= EXTI_IMR_MR11;

    NVIC_SetPriorityGrouping(5);
    NVIC_EnableIRQ(EXTI15_10_IRQn); // EXTI10 enable
    NVIC_SetPriority(EXTI15_10_IRQn, 3); // Set priority

    в обработчике: анализ что сработало (по флагу), по результату сброс флага и инверсия светодиода (для наглядности).
    В чем может быть ошибка ???

  10. Здравствуйте.
    Я в коде включаю NVIC_EnableIRQ(EXTI0_IRQn) и NVIC_DisableIRQ(EXTI0_IRQn) отключаю прерывание, как узнать какое состояние сейчас?