На этом уроке мы попробуем запускать и останавливать бегущий огонь по кнопке, причем обработку нажатия кнопки осуществим с использованием прерываний.
Все прерывания можно разделить на две большие группы – внутренние и внешние.
Внешние прерывания, называемые также асинхронными, исходят от внешних источников и могут произойти в любой произвольный момент. Примером внешнего прерывания может служить изменение сигнала на линии ввода-вывода.
Внутренние прерывания, называемые также синхронными, происходят внутри микроконтроллера и являются результатом предсказуемых событий, происходящих в ядре или периферийных модулях. Момент возникновения внутренних прерываний можно предсказать. Примером внутреннего прерывания является прерывание при переполнении таймера, рассмотренное на прошлом уроке.
Поскольку встроенный контроллер прерываний может обрабатывать до 66 источников прерываний, возникает вопрос, в каком порядке осуществлять обработку прерываний происшедших одновременно? И что делать если во время обработки одного прерывания происходит прерывание от другого источника?
Для разрешения этой ситуации встроенный контроллер прерываний имеет возможность задать до 16 уровней приоритетов. Если во время обработки прерывания происходит прерывание с более высоким приоритетом, то текущее выполнение обработчика прерывания приостанавливается, и управление передается обработчику прерывания с более высоким приоритетом.
Если во время обработки прерывания происходит прерывание, приоритет которого не выше текущего, то текущий обработчик заканчивает свое выполнение, после чего управление передается обработчику прерывания, ожидающего обработки.
Встроенный контроллер прерываний отводит 4 бита для задания уровня приоритета, причем эти 4 бита могут быть разделены на группу и подгруппу.
В первую очередь анализируется приоритет группы, и именно он определяет, может ли одно прерывание приостанавливать обработчик другого. Затем приоритет подгруппы анализируется внутри каждой группы. Если одновременно придут на исполнение два прерывания из одной группы, то приоритет подгруппы определит очередность их обработки. Но прерывать друг друга прерывания из одной подгруппы уже не могут. Если одновременно придут два прерывания из одной группы и одной подгруппы, то вызов пойдет по порядку их описания в таблице векторов.
Чем меньше значение группы или подгруппы прерывания, тем выше его приоритет.
Давайте перейдем к практической задаче и к проекту прошлого урока добавим еще обработчик внешнего прерывания по нажатию кнопки.
Откроем конфигурацию проекта прошлого урока в STM32CubeMX и для вывода PA0 во вкладке Pinout выберем назначение GPIO_EXTI0. Перейдем во вкладку Configuration и займемся настройкой внешнего прерывания.
Выбираем настройку модуля GPIO. Для линии PA0 изменилась возможность выбора режима. Можно выбрать Interrupt – прерывание или Event – событие, не вызывающее прерывания, а также фронт, по которому будет возникать прерывание.
Rising edge – это нарастающий фронт, то есть переход сигнала на выводе из логического нуля в единицу.
Falling edge – это спадающий фронт, то есть переход сигнала на выводе из единицы в ноль.
Можно, например, настроить прерывание, чтобы оно возникало по каждому фронту. Оставим все по умолчанию – будем обрабатывать внешнее прерывание по нарастающему фронту.
Теперь перейдем к настройке контроллера прерываний. Выбираем NVIC, и напротив EXTI line0 interrupt ставим галочку.
Генерируем код проекта и открываем проект в IAR. В файле обработчиков прерываний у нас появилась дополнительная функция, обрабатывающая внешнее прерывание по нажатию кнопки.
Пусть нажатие кнопки запускает или останавливает бегущий огонь.
Добавим в файл stm32f3xx_it.c в обработчик внешнего прерывания вот такую строчку.
2
3
4
5
6
7
8
9
{
/* 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, чтобы выполнить шаг программы. И мы видим, что выполнение программы перешло к обработчику прерывания кнопки, которое имеет более высокий приоритет. После выполнения обработчика прерывания кнопки заканчивает свое выполнение обработчик прерывания таймера, а после этого продолжает свое выполнение основной цикл программы.
Добрый день, может здесь подскажет кто. У меня такая же плата что и в уроках STM32F3Discovery, имеется вот такой код:
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 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 библиотеками работает, но если не пользоваться библиотеками, а чисто вот такой код, то данные отправляются, но на прерывании виснет контроллер. Не могу понять в чем проблема. Не скажу, что прям кровь из носа надо узнать, повторюсь чисто академический интерес, но все же интересно))))
Думаю, что обработчик прерывания не видится как обработчик прерывания. А вот как его настроить — уже не помню. Там есть какой-то файл ассемблерных *.s, который отвечает в том числе за настройку прерываний.
С HAL проще 🙂
Спасибо, правда есть ассемблерный файл с настройками, т.е. надо было просто пустой проект сгенирировать в кубе и этот в код вставить и все работает.
Здравствуйте, спасибо за курс!
Подскажите, пожалуйста, как получается, что наша переменная f, которая принимает значение только 0 либо 1 при выполнении исключающего ИЛИ с двойкой получается 4 варианта на выходе?
вроде разобрался.. 0^2=2, 1^2=3, а после этого обрабатываем уже сохраненные в f результаты и получаем начальные результаты 2^2=0, 3^2=1 . Интересное решение, может есть где-то список подобных оптимальных "паттернов" применительно к микроконтроллерам? спасибо