Внешние прерывания

Внешние прерывания

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

 
Все прерывания можно разделить на две большие группы – внутренние и внешние.

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

Внутренние прерывания, называемые также синхронными, происходят внутри микроконтроллера и являются результатом предсказуемых событий, происходящих в ядре или периферийных модулях. Момент возникновения внутренних прерываний можно предсказать. Примером внутреннего прерывания является прерывание при переполнении таймера, рассмотренное на прошлом уроке.

Поскольку встроенный контроллер прерываний может обрабатывать до 66 источников прерываний, возникает вопрос, в каком порядке осуществлять обработку прерываний происшедших одновременно? И что делать если во время обработки одного прерывания происходит прерывание от другого источника?

Для разрешения этой ситуации встроенный контроллер прерываний имеет возможность задать до 16 уровней приоритетов. Если во время обработки прерывания происходит прерывание с более высоким приоритетом, то текущее выполнение обработчика прерывания приостанавливается, и управление передается обработчику прерывания с более высоким приоритетом.

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

Встроенный контроллер прерываний отводит 4 бита для задания уровня приоритета, причем эти 4 бита могут быть разделены на группу и подгруппу.
В первую очередь анализируется приоритет группы, и именно он определяет, может ли одно прерывание приостанавливать обработчик другого. Затем приоритет подгруппы анализируется внутри каждой группы. Если одновременно придут на исполнение два прерывания из одной группы, то приоритет подгруппы определит очередность их обработки. Но прерывать друг друга прерывания из одной подгруппы уже не могут. Если одновременно придут два прерывания из одной группы и одной подгруппы, то вызов пойдет по порядку их описания в таблице векторов.
Чем меньше значение группы или подгруппы прерывания, тем выше его приоритет.

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

Откроем конфигурацию проекта прошлого урока в STM32CubeMX и для вывода PA0 во вкладке Pinout выберем назначение GPIO_EXTI0. Перейдем во вкладку Configuration и займемся настройкой внешнего прерывания.
Конфигурация внешнего прерывания на линии PA0

Выбираем настройку модуля GPIO. Для линии PA0 изменилась возможность выбора режима. Можно выбрать Interrupt – прерывание или Event – событие, не вызывающее прерывания, а также фронт, по которому будет возникать прерывание.
Rising edge – это нарастающий фронт, то есть переход сигнала на выводе из логического нуля в единицу.
Falling edge – это спадающий фронт, то есть переход сигнала на выводе из единицы в ноль.

Можно, например, настроить прерывание, чтобы оно возникало по каждому фронту. Оставим все по умолчанию – будем обрабатывать внешнее прерывание по нарастающему фронту.
Конфигурация типа прерывания

Теперь перейдем к настройке контроллера прерываний. Выбираем NVIC, и напротив EXTI line0 interrupt ставим галочку.
Разрешение внешнего прерывания

Генерируем код проекта и открываем проект в IAR. В файле обработчиков прерываний у нас появилась дополнительная функция, обрабатывающая внешнее прерывание по нажатию кнопки.
Пусть нажатие кнопки запускает или останавливает бегущий огонь.

Добавим в файл stm32f3xx_it.c в обработчик внешнего прерывания вот такую строчку.

1
2
3
4
5
6
7
8
9
void EXTI0_IRQHandler(void)
{
  /* USER CODE BEGIN EXTI0_IRQn 0 */
  f ^= 2;
  /* USER CODE END EXTI0_IRQn 0 */
  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
  /* USER CODE BEGIN EXTI0_IRQn 1 */
  /* USER CODE END EXTI0_IRQn 1 */
}

Добавлена строка 4.

Это – поразрядная операция «исключающее ИЛИ».
Давайте сделаем небольшое лирическое отступление и рассмотрим таблицы истинности основных логических функций.
Для двух битов А и В функция логического И единична только в том случае когда оба бита единичны. Функция логического ИЛИ единична когда хотя бы один из рассматриваемых битов единичен. А функция исключающего ИЛИ единична в случае если из двух рассматриваемых битов имеется только одна единица.

a b a & b a | b a ^ b
0 0 0 0 0
0 1 0 1 1
1 0 0 1 1
1 1 1 1 0

Что же можно сказать о нашем случае? Рассматриваемая переменная до сих пор принимала одно из двух значений, 0 или 1. А мы производим операцию исключающего или со значением, равным 2. Вот так это будет выглядеть в двоичном коде.

00000001 ^ 00000010 = 00000011

00000011 ^ 00000010 = 00000001

00000000 ^ 00000010 = 00000010

00000010 ^ 00000010 = 00000000

Младший, нулевой, разряд мы не трогаем, а производим операцию со следующим, первым, разрядом. Если в первом разряде был ноль, то после исключающего или с единицей в этом разряде, в нем получится 1. В итоге значение переменной может получиться равным 2 или 3 в зависимости от состояния младшего бита.
Если в первом разряде была единица, то операция исключающего ИЛИ с единицей даст в этом разряде ноль. В итоге значение переменной будет 0 или 1 в зависимости от младшего разряда. Напомню, что в основной функции у нас анализируется значение переменной на значение, равное 1. Поэтому в случае равенства этой переменной 2 или 3 условие не будет выполнено, и бегущий огонь остановится.

Откомпилируем проект и загрузим в отладочную плату. Запустим на выполнение, и мы видим, что переключение светодиодов останавливается при нажатии на кнопку. Повторное нажатие на кнопку снова включает бегущий огонь.

А теперь давайте поиграем с приоритетами прерываний. Для начала поставим точки останова в обработчиках прерывания таймера и кнопки и снова запустим программу на выполнение. Выполнение остановилось на обработке прерывания таймера. А теперь зажмем кнопку на плате и выполним следующий шаг программы, нажав F10. Выполнение обработчика прерывания продолжилось, и мы перешли к следующей команде. Теперь выполним программу до следующей точки останова. Кнопку на плате можно отпустить. И мы видим, что после обработки прерывания таймера программа перешла к обработке прерывания кнопки. Несмотря на то, что кнопка в этот момент уже не нажата, произошедшее прерывание дождалось своей очереди на обработку.

Теперь поменяем приоритет прерывания таймера. Остановим отладку и перейдем к конфигуратору STM32CubeMX. В настройке контроллера прерываний NVIC понизим приоритет прерывания таймера, выбрав для него, например 1 вместо 0. Напомню, что приоритет, равный 0, является наивысшим.
Установка приоритета прерывания

Снова сгенерируем и откроем проект. Загрузим программу в отладочную плату и запустим на выполнение, ничего не меняя в коде. Программа выполнилась до точки останова в обработчике прерывания таймера. Проделаем те же действия. Зажмем кнопку на плате и нажмем F10, чтобы выполнить шаг программы. И мы видим, что выполнение программы перешло к обработчику прерывания кнопки, которое имеет более высокий приоритет. После выполнения обработчика прерывания кнопки заканчивает свое выполнение обработчик прерывания таймера, а после этого продолжает свое выполнение основной цикл программы.

5 комментариев к “Внешние прерывания”

  1. Добрый день, может здесь подскажет кто. У меня такая же плата что и в уроках STM32F3Discovery, имеется вот такой код:

    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
    146
    147
    148
    149
    150
    151
    152
    #define F_CPU 72000000 //Тактируется от STLINK — 8MHz, PLL:ON, PLLMUL:0111, input clock x9
    //—————————————————————————————————————————
    #define RCC_CR          (*(volatile unsigned long *) 0x40021000) //Clock control register(RCC_CR)
    #define RCC_CFGR        (*(volatile unsigned long *) 0x40021004) //Clock configuration register(RCC_CFGR)
    #define RCC_AHBENR      (*(volatile unsigned long *) 0x40021014) //AHB peripheral clock enable register (RCC_AHBENR)
    #define RCC_APB2ENR     (*(volatile unsigned long *) 0x40021018) //APB2 peripheral clock enable register
    #define FLASH_ACR       (*(volatile unsigned long *) 0x40022000) // Flash access control register
    //—————————————————————————————————————————
    #define GPIOE_MODER (*(volatile unsigned long *) 0x48001000) //GPIO port mode register (GPIOx_MODER)
    #define GPIOE_ODR   (*(volatile unsigned long *) 0x48001014) //GPIO port output data register (GPIOx_ODR) (x = A..H)

    //—————————————————————————————————————————
    #define GPIOA_MODER     (*(volatile unsigned long *) 0x48000000) //GPIO port mode register (GPIOx_MODER)
    #define GPIOA_AFRL      (*(volatile unsigned long *) 0x48000024) //GPIO alternate function high register
    #define GPIOA_OSPEEDR   (*(volatile unsigned long *) 0x48000008)
    #define GPIOA_IDR       (*(volatile unsigned long *) 0x48000010)

    //—————————————————————————————————————————
    #define USART_BRR  (*(volatile unsigned long *) 0x4001380C) // Baud rate register
    #define USART_CR1  (*(volatile unsigned long *) 0x40013800) // Control register 1
    #define USART_ISR  (*(volatile unsigned long *) 0x4001381C) // Interrupt and status register
    #define USART1_TDR (*(volatile unsigned long *) 0x40013828) // Transmit data register
    #define USART1_RDR (*(volatile unsigned long *) 0x40013824) // Receive data register
    //—————————————————————————————————————————
    #define SYSCFG_CFGR1  (*(volatile unsigned long *) 0x40010000) //GPIO port output data register (GPIOx_ODR) (x = A..H)

    #define NVIC_ISER1 (*(volatile unsigned long *) 0xE000E104) // Interrupt set-enable registers

    void _delay(unsigned long int index);
    void sendByteUSART1(unsigned char txData); // Передаем байт по USART
    void sendStringUSART1(unsigned char *txData); // Передаем строку по USART

    void RCC_init()
    {
       RCC_CFGR |= (0<<1 | 1<<20 | 1<<19 | 1<<18); // PLL multiplication factor
                                                  //0111: PLL input clock x 9
       RCC_CFGR |= (1<<16|0 << 15); //PLL entry clock source
                                    //10: HSE used as PREDIV1 entry
       RCC_CR |= (1<<16); //HSE clock enable
        while(!(RCC_CR & (1<<17)));
      // sendStringUSART1("HSE oscillator is stable!…\n");
        FLASH_ACR |= (0<<0 | 1<<1 | 0<<2);
         RCC_CR |= (1<<24); //PLL enable
         while(!(RCC_CR & (1<<17))); //PLL clock ready flag
    //USART1_Send_String((u8*)"PLL is locked!…\n");
         RCC_CFGR |= (1<<1 | 0<<0); //System clock switch
       // RCC_CFGR = 0x001D040A;
         while(!(RCC_CFGR & (1<<3))); //System clock switch
        

         
     // USART_BRR = 72000000/9600; //Baudrate 9600, 72MHz
    //sendStringUSART1("PLL selected as system clock!…\n");
    //USART1_Send_String((u8*)"PLL selected as system clock!…\n");

    //10: PLL selected as system clock
    }
         

    int main(void)
    {

      RCC_AHBENR |= 1<<21; //port E clock enable
      RCC_AHBENR |= 1<<17; //port A clock enable
      RCC_init();
      RCC_AHBENR = 0x00620014;

      GPIOE_MODER |= (0<<31)|(1<<30);  //General purpose output mode
        GPIOE_MODER |= (0<<29)|(1<<28);  //General purpose output mode

      //USART1_TX.PA9.AF7
      GPIOA_MODER |= (1<<19)|(0<<18);  //PA9 AF mode
      //PA9 AF mode
      GPIOA_AFRL |= (0<<7)|(1<<6|1<<5)|(1<<4);  //PA9 in AF7 mode

      //USART1_RX.PA10.AF7
      GPIOA_MODER |= (1<<21)|(0<<20);
      GPIOA_AFRL |= (0<<11)|(1<<10|1<<9)|(1<<8);  //PA10 in AF7 mode
          
      
      //USART1 init
      RCC_APB2ENR |= 1<<14; //USART1 clock enable
          
            
        // USART_BRR = 8000000/9600;

      USART_BRR = 0x1D4C; //Budrate 9600

      USART_CR1  |= 1<<0; //USART Enabled
      USART_CR1  |= 1<<3;//Transmitter enable
        USART_CR1  |= 1<<2;//Reciver enable

        USART_CR1  |= 1<<5; //RXNE interupt enabled
        STK_CTRL = 0x00000007;
      // STK_LOAD = 0x0001193f;
        NVIC_ISER1 |= 1<<5; //Разрешить прерывание 37, USART1
        asm volatile ("CPSIE I"); //Глобальное разрешение прерываний

    // unsigned char *txData = "Start Programm\n";
        sendStringUSART1("Start Programm\n");

      //Конец программы
        while(1)
        {
          //if (USART_ISR & (1<<7)) //Передаем байт
          //USART1_TDR = 0x41;
          //sendByteUSART1(0x41);

          GPIOE_ODR |= (1<<15); //PE15 is HIGH
            sendStringUSART1("An!\n");
          _delay(2000000);
          GPIOE_ODR &= ~(1<<15); //PE15 is LOW
          _delay(2000000);
        }

    }

    void _delay(unsigned long int index) //Задержка
    {
      while(index—);
    }
    //Возращает длину строки

    // Передаем байт по USART
    void sendByteUSART1(unsigned char txData)
    {
      while(!(USART_ISR & (1<<7))); // Ждем пока осовободиться TDR
          USART1_TDR = txData; //Передаем байт
    }
    // Передаем строку по USART
    void sendStringUSART1(unsigned char *txData)
    {
      while(*txData)
        {
          USART1_TDR = *txData++;
          while(!(USART_ISR & (1<<7))); //Ждём пока освободится TDR
        }
    }

    void USART1_IRQHandler(void)
    {
      GPIOE_ODR |= (1<<14); //PE15 is HIGH
     unsigned char data = USART1_RDR;
      sendStringUSART1("Answer!\n");
    }

    Как видите я не пользуюсь готовыми библиотеками(чисто академический интерес). Вот вопрос, этот код по USART отправляет данные и принимает по прерыванию NVIC, и самое главное, он работает в СooCox IDE, AC6 SystemWorkbrean, а в IAR с hal библиотеками работает, но если не пользоваться библиотеками, а чисто вот такой код, то данные отправляются, но на прерывании виснет контроллер. Не могу понять в чем проблема. Не скажу, что прям кровь из носа надо узнать, повторюсь чисто академический интерес, но все же интересно))))

    1. Елена Вставская

      Думаю, что обработчик прерывания не видится как обработчик прерывания. А вот как его настроить — уже не помню. Там есть какой-то файл ассемблерных *.s, который отвечает в том числе за настройку прерываний.
      С HAL проще 🙂

      1. Спасибо, правда есть ассемблерный файл с настройками, т.е. надо было просто пустой проект сгенирировать в кубе и этот в код вставить и все работает.

  2. Андрей

    Здравствуйте, спасибо за курс!
    Подскажите, пожалуйста, как получается, что наша переменная f, которая принимает значение только 0 либо 1 при выполнении исключающего ИЛИ с двойкой получается 4 варианта на выходе?

    1. Андрей

      вроде разобрался.. 0^2=2, 1^2=3, а после этого обрабатываем уже сохраненные в f результаты и получаем начальные результаты 2^2=0, 3^2=1 . Интересное решение, может есть где-то список подобных оптимальных "паттернов" применительно к микроконтроллерам? спасибо

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Прокрутить вверх