ARM. Учебный Курс. Прерывания и NVIC — приоритетный контроллер прерываний

Стандартной плюхой ядра Cortex M3 является NVIC — контроллер приоритетных векторных прерываний. Сейчас я разжую что это такое и с чем это едят.
 

Прерывания и события
Вообще, если пошерстить мануал, то везде при разборе периферии рассматриваются interrupt/event. Может сложиться ощущение, что это одно и то же. Но это не так.
 

Interrupt — это прерывание. При прерывании обычно программа пакует регистры в стек и бросается по вектору, а оттуда через JMP сигает уже в обработчик прерывания. В кортексах все немного не так. Тут вектора прерывания это просто вектор-адреса лежащие в нужном месте. В виде констант. А при прерывании прога не прыгает на вектор, а берет этот вектор-адрес и сразу же пихает его в програмный счетчик, тем самым переходит сразу на обработчик. Так быстрей, исчезает лишняя операция по переходу на вектор.
 

Event — это аппаратное событие. Опустел буфер UART — держи event, натикал таймер — еще один event. Событие может вызвать прерывание, может запустить какую-либо периферию, например пнуть DMA, чтобы оно выгрузило данные. Но событие далеко не всегда может вызвать прерывание. Каждое прерывание вызывается событием, но не каждое событие вызывает прерывание. Вот. Так что надо отличать.
 

Как и в AVR в STM32 существуют вектора прерываний. Это особые адреса, куда контроллер бросается если происходит прерывание. Они записаны в таблицу и располагаются вначале памяти. Впрочем, система гибкая и переконфигурировав NVIC можно засунуть ее куда угодно. Если вы пишите на Си, то по дефолту, в стартовом файле, вектора прерываний забиты затычками которые ведут в бесконечный цикл. Так что если вызывать прерывание не указав ему обработчик контроллер тупо повиснет. Что является максимизацией ошибки и это хорошо.
 


Таблицу векторов можно подглядеть для конкретного контроллера в STM32F10x.s файле. Идем туда и глядим на вектора, точнее на их имена. Выглядят они примерно так:
 

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
; Vector Table Mapped to Address 0 at Reset
 
                AREA    RESET, DATA, READONLY
                EXPORT  __Vectors
 
__Vectors       DCD     __initial_sp              ; Top of Stack
                DCD     Reset_Handler             ; Reset Handler
                DCD     NMI_Handler               ; NMI Handler
                DCD     HardFault_Handler         ; Hard Fault Handler
                DCD     MemManage_Handler         ; MPU Fault Handler
                DCD     BusFault_Handler          ; Bus Fault Handler
                DCD     UsageFault_Handler        ; Usage Fault Handler
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     SVC_Handler               ; SVCall Handler
                DCD     DebugMon_Handler          ; Debug Monitor Handler
                DCD     0                         ; Reserved
                DCD     PendSV_Handler            ; PendSV Handler
                DCD     SysTick_Handler           ; SysTick Handler
 
                ; External Interrupts
                DCD     WWDG_IRQHandler          ; Window Watchdog
                DCD     PVD_IRQHandler           ; PVD through EXTI Line detect
                DCD     TAMPER_IRQHandler        ; Tamper
                DCD     RTC_IRQHandler           ; RTC
                DCD     FLASH_IRQHandler         ; Flash
                DCD     RCC_IRQHandler            ; RCC
                DCD     EXTI0_IRQHandler          ; EXTI Line 0
                DCD     EXTI1_IRQHandler          ; EXTI Line 1
                DCD     EXTI2_IRQHandler          ; EXTI Line 2
                DCD     EXTI3_IRQHandler          ; EXTI Line 3
                DCD     EXTI4_IRQHandler          ; EXTI Line 4
                DCD     DMAChannel1_IRQHandler    ; DMA Channel 1
                DCD     DMAChannel2_IRQHandler    ; DMA Channel 2
                DCD     DMAChannel3_IRQHandler    ; DMA Channel 3
                DCD     DMAChannel4_IRQHandler    ; DMA Channel 4
                DCD     DMAChannel5_IRQHandler    ; DMA Channel 5
                DCD     DMAChannel6_IRQHandler    ; DMA Channel 6
                DCD     DMAChannel7_IRQHandler    ; DMA Channel 7
                DCD     ADC_IRQHandler            ; ADC
                DCD     USB_HP_CAN_TX_IRQHandler  ; USB High Priority or CAN TX
                DCD     USB_LP_CAN_RX0_IRQHandler ; USB Low  Priority or CAN RX0
                DCD     CAN_RX1_IRQHandler        ; CAN RX1
                DCD     CAN_SCE_IRQHandler        ; CAN SCE
                DCD     EXTI9_5_IRQHandler        ; EXTI Line 9..5
                DCD     TIM1_BRK_IRQHandler       ; TIM1 Break
                DCD     TIM1_UP_IRQHandler        ; TIM1 Update
                DCD     TIM1_TRG_COM_IRQHandler   ; TIM1 Trigger and Commutation
                DCD     TIM1_CC_IRQHandler        ; TIM1 Capture Compare
                DCD     TIM2_IRQHandler           ; TIM2
                DCD     TIM3_IRQHandler           ; TIM3
                DCD     TIM4_IRQHandler           ; TIM4
                DCD     I2C1_EV_IRQHandler        ; I2C1 Event
                DCD     I2C1_ER_IRQHandler        ; I2C1 Error
                DCD     I2C2_EV_IRQHandler        ; I2C2 Event
                DCD     I2C2_ER_IRQHandler        ; I2C2 Error
                DCD     SPI1_IRQHandler           ; SPI1
                DCD     SPI2_IRQHandler           ; SPI2
                DCD     USART1_IRQHandler         ; USART1
                DCD     USART2_IRQHandler         ; USART2
                DCD     USART3_IRQHandler         ; USART3
                DCD     EXTI15_10_IRQHandler      ; EXTI Line 15..10
                DCD     RTCAlarm_IRQHandler       ; RTC Alarm through EXTI Line
                DCD     USBWakeUp_IRQHandler      ; USB Wakeup from suspend
 
 
                AREA    |.text|, CODE, READONLY

 

Как видно, тут все написано почти человеческим языком и особо пояснять то ничего не требуется. Все итак понятно. Обращу лишь внимание на то, что вначале идут так называемые Fault Interrupt
это

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
__Vectors       DCD     __initial_sp              ; Top of Stack
                DCD     Reset_Handler             ; Reset Handler
                DCD     NMI_Handler               ; NMI Handler
                DCD     HardFault_Handler         ; Hard Fault Handler
                DCD     MemManage_Handler         ; MPU Fault Handler
                DCD     BusFault_Handler          ; Bus Fault Handler
                DCD     UsageFault_Handler        ; Usage Fault Handler
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     SVC_Handler               ; SVCall Handler
                DCD     DebugMon_Handler          ; Debug Monitor Handler
                DCD     0                         ; Reserved
                DCD     PendSV_Handler            ; PendSV Handler
                DCD     SysTick_Handler          ; SysTick Handler

 

Прерывания ядра. Которые имеют наивысший приоритет и не подчиняются команде общего запрета/разрешения прерываний:
Для них свой флажок и своя команда:

1
2
__enable_fiq ();
__disable_fiq();

 

Обработчик прерывания
Теперь, если нам потребуется создать обработчик прерывания. Допустим для внешнего прерывания по изменению состояния вывода EXTI1 мы должны будем нарисовать в коде вот такую конструкцию:

1
2
3
4
5
// Int Vectors
void EXTI1_IRQHandler(void)
{
 
}

 

Собственно обработчик готов, можем писать туда всякую каку.
 

Разрешение прерываний
Для разрешения прерываний надо сделать три вещи:
 

  • Разрешить глобальные прерывания
  • Разрешить нужное прерывание в NVIC
  • Настроить и разрешить конкретные прерывания непосредственно в периферии. Т.е. настроить нужные events на прерывания.

 

А теперь по порядку.
 

Разрешение глобальных прерываний
Это просто, достаточно всего лишь поставить флажок состояния процессора. В CMSIS за это отвечает крошечная функция

1
 __enable_irq ();

 

Разрешить прерывания в NVIC
Настройка прерываний в NVIC чуть более сложное дело, но не намного. Описание на NVIC можно найти в файле PM0056 STM32F10xxx Cortex-M3 programming manual. Там описывается только то, что есть в ядре Cortex M3. Можно конечно открыть более общее описание, взятое с сайта ARM. Но там будут небольшие отличия. Например, у STM32 урезана система группировки приоритетов. И другие мелочи. Но в целом все похоже.
 

За работу с прерываниями в NVIC отвечают несколько регистров. Вот они все указаны в таблице 41
 


 

  • ISER — Interrupt Set Enable Register. Запись бита в нужную позицию включает прерывание.
  • ICER — Interrupt Clr Enable Register. Запись сюда наоборот выключает прерывание.

Запись 1 в биты этих регистров запрещает/разрешает прерывания. Запись 0 не делает ничего, а чтение возвращает текущее состояние разрешено/запрещено
 

  • ISPR — Interrupt Set Pending Register. Поставить прерывание в ожидание.
  • IСPR — Interrupt Clr Pending Register. Сбросить прерывание с ожидания.

Запись 1 в биты этих регистров ставит/снимает прерывания в очередь на исполнение. Запись 0 не делает ничего, а чтение возвращает текущее состояние прерывания. Ждет он обработки или уже нет. Т.е. если в этом регистре где то стоит бит 1, значит это прерывание еще не вызывалось.

  • IABR — Interrupt active bit registers. Регистр показывающий активно ли в данный момент прерывание. Автоматически ставится когда мы попадаем в обработчик и автоматом же снимается когда мы уходим из него. Этот регистр можно только читать.

 

Да, но тут возникает вопрос. Прерывания то у нас все именные. Таймер там, UART еще что то. А тут какие то номера? Где узнать у какого прерывания какой номер? А все в той же таблице векторов, что я привел в самом начале. Пропускаем FAULT INTERRUPT и начиная с
 

1
2
3
4
5
6
 ; External Interrupts
                DCD     WWDG_IRQHandler         ; Window Watchdog    		Номер 0
                DCD     PVD_IRQHandler          ; PVD through EXTI Line detect  Номер 1
                DCD     TAMPER_IRQHandler       ; Tamper  			Номер 2
                DCD     RTC_IRQHandler          ; RTC		        	Номер 3
...

И так далее до конца. Ну, а номер регистра выбираем исходя из того, что в один регистр влазит всего 32 бита. А еще, в составе CMSIS есть удобные функции для руления NVIC контроллером. Они стандартные для всех МК на Cortex M3.
 

1
2
void NVIC_EnableIRQ(IRQn_t IRQn) // Enable IRQn
void NVIC_DisableIRQ(IRQn_t IRQn) // Disable IRQn

Одна запрещает, другая разрешает. Все просто :)
 

Пример:

1
NVIC_EnableIRQ (EXTI1_IRQn);	// Разрешить прерывание EXTI1 в NVIC

Там же есть функции для остальных ковыряний с NVIC. Вот весь список:
 

1
2
3
4
5
6
7
8
9
10
void NVIC_SetPriorityGrouping(uint32_t priority_grouping)	// Задать группы/подгруппы приоритетов
void NVIC_EnableIRQ(IRQn_t IRQn) 				// Включить IRQn
void NVIC_DisableIRQ(IRQn_t IRQn) 				// Выключить IRQn 
uint32_t NVIC_GetPendingIRQ (IRQn_t IRQn) 			// Вернуть true (точнее IRQ-Number) если  IRQn в ожидании
void NVIC_SetPendingIRQ (IRQn_t IRQn) 			// Поставить IRQn в ожидание 
void NVIC_ClearPendingIRQ (IRQn_tIRQn) 			// Выкинуть из очереди на ожидание IRQn 
uint32_t NVIC_GetActive (IRQn_t IRQn) 			// Функция "Бля, где это я?" Возвращает номер текущего активного прерывания если такое имеется 
void NVIC_SetPriority (IRQn_t IRQn, uint32_t priority) 		// Задать приоритет IRQn
uint32_t NVIC_GetPriority (IRQn_t IRQn) 			// Считать приоритет IRQn
void NVIC_SystemReset (void) 				// Reset the system

 

Настройка прерывания в периферии
Тут надо смотреть конкретную периферию и какие биты за что отвечают. Обычно есть бит события, например, есть у UART бит события TXE, а для включения прерывания на это событие есть бит конфигурации TXEIE. Вообще это характерно, что есть какой то event, а бит разрешения прерывания для него зовется как [чето там]IE.
 

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

1
2
// Разрешаем прерывания в периферии для выводов 1 и 2. 
EXTI->IMR |=(EXTI_IMR_MR1 | EXTI_IMR_MR2);

 

Все, теперь прерывание активно и работает.
 

Особенности обработки прерываний
При написании обработчика есть ряд тонких моментов. Дело в том, что тут, в отличии от AVR далеко не все флаги событий снимаются аппаратно при переходе по вектору. ОЧЕНЬ часто флаг события надо сбрасывать вручную. Иначе при выходе из прерывания оно тутже сгенерится вновь и мы снова окажемся в обработчике. Причем это НЕ ОТНОСИТСЯ к Pendind флагам NVIC!!! Сие сбрасывается на периферии и только там.
 

Для EXTI, после того как я обслужил это прерывание, я делаю сброс флага события в блоке прерываний EXTI:
 

1
2
// Сброс флага события
EXTI->PR |= EXTI_PR_PR2;

 

Конкретно по флагам событий и тому как они сбрасываются надо внимательно читать описания битов в User Manual. Там обычно описано чем бит ставится и чем сбрасывается.
 

Приоритеты прерываний
NVIC же приоритетный контроллер. Но что то никто на это, как я погляжу, не заморачивается особо. Как лепили одноранговые прерывания на AVR так и тут лепят.
 

По дефолту более высокий уровень приоритета имеют только группа FIQ. У остальных же он одинаковый. Как на AVR. Т.е. кто раньше встал — того и тапки. Одноранговое прерывание не может перебить другое такое же. А если случилось так, что несколько прерываний встали на ожидание, но не смогли пойти на выполнение (т.к., например, был глобальный запрет, либо выполнялось прерывание), то как только появится возможность первым пойдет то прерывание, чей номер в таблице векторов меньше. Ну как на AVR.
 

Но все можно изменить! :)
 

За приоритет отвечает группа регистров IPRx всего их там двадцать (но вообще может быть до 60). В каждом регистре по четыре 8 битных поля, каждое поле отвечает за один вектор. От 0 (помним, что номера считаются от External Interrupt, исключая FIQ группу) до 80. Конечно у STM32 нет 80 векторов, это число взято с запасом. В IRP0 лежат приоритеты с 0 по 3 вектор, в IRP1 с 4 по 7 и так далее до 80го.
 


 

Поле приоритета 8ми разрядное. Значит теоретически приоритетов может быть 256 штук. Но это только в теории, это возможности ядра в принципе. А конкретно в STM32F103 реально рулят только старшие 4ре бита. Образуя таким образом 16 уровней приоритетов. Чем меньше номер уровня, тем он главней. После RESET у всех 0000 т.е. полная анархия.
 

Байт приоритета можно вкатить вручную, засунув его в старшую тетраду нужного поля, но куда наглядней вызвать функцию CMSIS, например так:
 

1
2
//Понижаем приоритет прерыванию EXTI2
NVIC_SetPriority (EXTI2_IRQn, 2);

 

Прерывания группы FIQ (не все, но многие) тоже позволяют менять приоритет. Но это в другом месте. Ищите в документации на ядро описание System handler priority registers (SHPRx)
 

Прерывание с большим приоритетом невозбранно может прерывать более стремные прерывания. И таким образом мы имеем 16 цветов штанов. А потом матрешка разворачивается в обратном направлении. Чтобы это наглядно показать я накатал прогу с кнопочками и реагирующими на них прерываниями. В обработчиках яростно дрыгало ногами. Это все я сгрузил на логический анализатор
 


 

Вот, парочка примеров:
Два равных прерывания пришли с перехлестом:

 

А вот более важное перебивает низкоприоритетное:

 

Группы приоритетов
Приоритеты можно бить на группы и подгруппы. И тут получается такая ситуация:
 

Приоритет группы определяет то может ли одно прерывание перебивать другое. Скажем прерывания группы 0 легко забирают управление у прерываний группы 1. А группа 1 так сделать не может ибо лох.
 
А в каждой группе возникают подгруппы приоритетов. Они уже решают кто пойдет первым если одновременно придут на исполнение два прерывания из одной группы. Но перебивать друг друга прерывания из одной подгруппы уже не могут. А если одновременно придут два прерывания из одной группы и одной подгруппы, то вызов пойдет по порядку их описания в таблице векторов.
 

Группа определяется специальном поле PRIGROUP регистра AIRCR, записав туда число от 0 до 7 (для STM32F103 только от 3 до 7). При этом поле задания приоритета в IRP делится на группу и подгруппу.
 


 

Т.е. число в PRIGROUP определят как бы положение десятичной точки, которое разделяет поле IRP на биты разделяющие группу и подгруппу. 0 — точка перед нулевым битом, 128 групп и 2 подгруппы. 1 — точка стоит перед первым битом, имеем 6 бит на группы и два на подгруппы. Т.е. 64 группы и 4 подгруппы. И так далее.
 

Но У STM32 поле IPR всего 4 разрядное, только старшие биты. Потому PRIGROUP имеет смысл только с 3 до 7.
 

Выбрав, например, PRIGROUP = 5 мы получим, что у поля IPR старшая тетрада, что отвечает за приоритет, разобьется еще на два поля. Старшие два бита будут определять группу, а младшие подгруппу. Чем меньше номер группы тем она главней.
 

Слайды давай! Слайды!
А теперь пример. Простенький такой.
Создадим такую программу:
 

  • Фоновая задача моргает диодом LED3
  • Прерывание EXTI2 имеет приоритет 2 и моргает диодом LED1
  • Прерывание EXTI1 имеет приоритет 0 моргает диодом LED0

 

Двумя кнопками мы замыкаем выводы B00 и B01 на землю, вызывая срабатывания прерываний по изменению уровня и пыримся на светодиоды, наблюдая как оно работает. В обработчики прерываний я запихал здоровенные циклы с задержками. Это термоядерный быдлокод. Так делать нельзя, прерывания должны отрабатываться как можно быстрей, но иначе по простому не увидеть как это все работает.
 

Вот весь код:

Показать »

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
#include "stm32f10x.h"
#define F_CPU 72000000UL
 
void Delay( uint16_t Val);
 
// Обработчик EXTI 2
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);
	}
// Сбрасываем флаг прерывания
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();
 
RCC->APB2ENR 	|= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN;	// Подаем тактирование на порт А и В
 
// Настройка выходных портов. Те, что на светодиоды.
// Конфигурируем 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  на выводы порта А
AFIO->EXTICR[1] 	|= 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

 

Схема включения простая. На порты B03, B00 и B01 вешаем диоды. На порты A01 и A02 кнопки. На демоплате Pinboard все видно наглядно. Главное правильно сконфигурировать кнопочную матрицу.
 


 

Ну и киношка того как это работает в реале.

 

Файлы к статье

41 thoughts on “ARM. Учебный Курс. Прерывания и NVIC — приоритетный контроллер прерываний”

  1. Разбиение приоритетов на группы и подгруппы имеет (если верить документации) такой смысл:
    1. Внутри группы прерывания не могут перебивать друг друга.
    2. Прерывания из группы с меньшим номером могут перебивать прерывания из группы с большим номером.
    3. Приоритет в подгруппе определяет порядок вызова обработчиков при одновременном возникновении прерываний из одной группы.

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

      Смотри сам.
      Прерывание 2.1 и прерывание 1.2. 1.2 главней
      Прерывание 21 и прерывание 12. 12 главней.

      От перемены места точки ничего не меняется.

      3. То же самое. Например, 2.1 и 2.0 и 21 и 20

      И зачем нужны группы?

      1. В случае если есть 2 прерывания (без точки, а значить без подгрупп)
        11 и 12 прерывание 11 будет главнее и сможет прервать процесс выполнения прерывания 12.
        В случае если прерывания имеют подгруппы то 1.1 и 1.2 относятся к одной группе но имеют разный приоритет внутри подгруппы (в разных подгруппах) при этом прерывание 1.1 уже не сможет прервать прерывание 1.2. Приоритет внутри группы (то что после точки) влияет на последовательность выполнения если 1.1 и 1.2 наступили одновременно, то сначала будет обработано 1.1 а затем 1.2
        Вывод прерывания внутри одной группы не могут прерывать друг друга. Вот дословно из документации:
        Only the group priority determines preemption of interrupt exceptions. When the processor is executing an interrupt exception handler, another interrupt with the same group priority as the interrupt being handled does not preempt the handler,

  2. Следует немного дополнить по поводу арбитража одновременно пришедших прерываний по номеру IRQ — для возникновения такой ситуации не нужен экстрим вроде прихода запросов «такт-в-такт», достаточно установки двух pending битов во время, когда эти прерывания не могут быть немедленно обработаны, что, вообще говоря, происходит сплошь и рядом — например, прерывания пришли во время работы обработчика с большим или таким же приоритетом, прерывание маскируется в момент прихода регистром BASEPRI, или прерывания вообще запрещены через CLSIE в момент поступления. В таких случаях неважно, в каком порядке выставлялись pending биты — обработчики все равно будут запущены по возрастанию IRQ. И одно из применений подгрупп приоритета состоит как раз в том, чтобы этот порядок переопределить — мы можем IRQ 12 заставить срабатывать позже IRQ 13, назначив ему меньшую подгруппу приоритета, но при этом, расположив их в одной группе, добъемся того, что вытеснять друг друга они не будут.

  3. «Как и в AVR в STM32 существуют вектора прерываний. Это особые адреса, куда контроллер бросается если происходит прерывание». Немного не так… Поясню на примере AVR, В AVR — да,контроллер «бросается», т.е.в PC загружается ВЕКТОР из области векторов прерывания — и совершается бросок на вектор. А оттуда уже JMP HandlerХ (или там может располагаться просто пользовательский код). А в ARM никакого броска в область векторов — там в области векторов не находятся инструкции типа АВРоских «0004 JMP HandlerХ». Там находятся сами константные Адреса обработчиков — они сразу засовываются в PC (безо всяких инстукций «JMP»), благо что в ARMе PC это один из РОН. Таким образом, ARM не прыгает в область векторов.

      1. Директива DCD это алиас директивы DC32, что есть «Generates 32-bit constants».
        Не прыгая на область векторов экономится время, т.к. в области векторов тесно, и все равно приходится где-то в другом месте размещать обработчик.

      2. Дополнение. Вот этот пассаж не вполне корректен:

        При прерывании обычно программа пакует регистры в стек и бросается
        по вектору, а оттуда через JMP сигает уже в обработчик прерывания. В
        кортексах все немного не так. Тут вектора прерывания это просто вектор-адреса лежащие в нужном месте. В виде констант. А при прерывании прога не прыгает на вектор, а берет этот вектор-адрес и сразу же пихает его в програмный счетчик

        «Программа» никуда не бросается и ничего не пакует. Ни в кортексах, ни в каком другом процессоре. По большому счету, есть всего два сценария обработки прерываний, но оба из них выполняет процессор аппаратно:

        1. Сохранение контекста (переключение набора регистров, сохранение регистров на стеке, и т.п.) и переход на адрес вектора прерывания

        2. Просто переход на адрес вектора прерывания (а там уже должен лежать программный код, выполняющий сохранение контекста, если нужно). Единственное, что в таком варианте сохраняется — это регистр адреса возврата из подпрограммы и регистр состояния процессора, если таковые имеются в данной архитектуре (как, например, регистры LR и MSR в PowerPC)

        В обоих случаях исполняемая программа условно «даже не узнает», что её прервали.

  4. Ди, привет.
    Смотри:
    // Прерывание по падению уровня на ноге 1 и 2 порта привязанного к EXTI
    EXTI->FTSR |=(EXTI_FTSR_TR1 | EXTI_FTSR_TR2);
    Судя по тому, что в регистре есть от TR0 до TR17 это всё же не нога порта, а линия внешнего прерывания. Т.е ты, как я понимаю, включаешь для первого и второго внешнего прерывания реакцию по спадающему фронту (Falling trigger). А вот только я не нашёл как тогда отдельно на реакцию на конкретной ноге настраивать. Неужели только по целым портам???

    1. Это EXTI не нога порта, это уже каналы мультиплексоров.

      Т.е. смотри, вначале ты привязываешь нужную тебе ногу к конкретному мультиплексору (см. картинку 21 стр. 192 RM0008). Видишь там у тебя 16 мультиплексоров по номерам выводов портов и в каждый входит от А до G порта? А потом уже на эти выходы EXTI программируешь на экшн по спаду, фронту или еще как.

      Т.е. ты не можешь одновременно на разные прерывания использовать скажем PA0 и PB0 т.к. они относятся к одному мультиплексору и либо то либо другое. Но зато на разные прерывания можно повесить PA1 и PA2 т.к. у них мультиплексоры разные.

  5. Добрый день.
    Такой вот вопрос. У меня проц — STM32F103RBT6. Играюсь с ним в IAR 6.30
    Пытаюсь настроить прерывания. Подключил CMSIS. Указал стратап — startup_stm32f10x_md.s
    Написал свой обработчик — void EXTI0_IRQHandler(void). Что в режиме симуляции, что при работе с jtag’ом происходит такая вещь. Из стартапа берется таблица векторов. После инициализации сэка, происходит вход в reset обработчик и там происходит инициализация порта(это все startup_stm32f10x_md.s), но указатели на обработчики не изменяются. Т.е. не прописывается адрес моего обработчика.
    Сколько уже над этим мучаюсь. Уже подумываю программно переносить табл.векторов и там указывать новый обработчик, но это далеко не выход. Не понятно, почему он при инициализации не заполняет табл. вектр. адресами моих обработчиков.

  6. Подскажите пожалуйста, а как лучше организовать подсчет временных интервалов? Не в стиле delay, а скажем перед входом в функцию засек время старта, в нужный момент вычел из текущего времени и получил прошедший интервал.
    Как организовать такой учет времени? что-то вроде millis или micros в Arduino. Подойдет любой источник, хоть количество тактов процессора. Вот только откуда их взять.

    1. Таймером. Запускаешь таймер на какой то интревал и он тикает параллельно. Генерируя прерывания. Я обычно делаю одно прерывание в 1мс, в результате у меня каждую мс тикает счетная переменная относительно которой можно следить за временем.

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

        1. интересно, а можно посмотреть текущее значение счетчика в таймере?
          Например, таймер тикает и вызывает прерываение раз в мс, а если мы попали между прерываниями более точное значение интервала можно узнать прочитав текущее значение счетчика таймера. Такое возможно?
          Вообще вечно удивляюсь, почему не сделать такой тупой счетчик тактов, ведь дешево было бы, а время всегда учитывать нужно.

          1. Просто время учитывают обычно событиями, тиками диспетчера. А просто счетчик тактов нафиг никому не уперся.

  7. О! то, что нужно, мне как раз именно нужно физическое время, а точнее чем тактовая частота источников в МК нету. Счетный регистр — это то, что нужно. О нем и спрашивал. Только что узнал, что в AVR он называется TCNT. Наверное что-то похожее должно быть в STM32

    1. Именно. Название регистра я не помню, но даташит (точне юзермануал) вам в помощь. Раздел таймеров. Только он вроде бы всего 16ти разрядный, так что при естественном такте переполнится и обнулится за какие то доли секунды. Но есть прерывание где можно тикать более глобально, а уточнять по таймерному счетчику. А еще предделители, чтобы тикало не так быстро.

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

    2. Вот наиболее подходящий таймер — http://easyelectronics.ru/arm-uchebnyj-kurs-systick-sistemnyj-tajmer.html

      SysTick->LOAD = SystemCoreClock / 1000 — 1; // 1 ms
      SysTick->VAL = 0;
      SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk
      | SysTick_CTRL_TICKINT_Msk
      | SysTick_CTRL_ENABLE_Msk;

      При этом прерывание SysTick вызывается ровно раз в миллисекунду, тут можно просто увеличивать дополнительный счетчик.

      Получить текущее значение счетчика в наносекундах:
      ((SysTick->LOAD — SysTick->VAL) * 1000) / (SystemCoreClock / 1000000uL);

      Сложив его с нашим счетчиком миллисекунд, получаем точное время с момента старта МК.

  8. Всем привет! Вопрос не по прерываниям, поэтому извиняюсь если испорчу тему. Не судите строго. Пишу сюда так как есть обсуждения за 2013 год. Нужна помощь в поиске ошибок в следующем коде.
    #include «stm32f10x.h»

    void HSI_Init(void)
    {
    RCC->CR |= RCC_CR_HSION;
    while((RCC->CR & RCC_CR_HSIRDY)==0){}
    RCC->CFGR &= ~RCC_CFGR_SW;
    RCC->CFGR |= RCC_CFGR_SWS_HSI;
    }

    void GPIO_Init(void)
    {
    RCC->APB2ENR |= RCC_APB2ENR_IOPEEN;

    GPIOE->CRL &= ~GPIO_CRL_CNF6;//RS:COMMAND — 0; DATA — 1
    GPIOE->CRL |= GPIO_CRL_MODE6;
    GPIOE->CRL &= ~GPIO_CRL_CNF7;//E — enable read/write
    GPIOE->CRL |= GPIO_CRL_MODE7;

    GPIOE->CRH &= ~GPIO_CRH_CNF8;//
    GPIOE->CRH|= GPIO_CRH_MODE8;//
    GPIOE->CRH &= ~GPIO_CRH_CNF9;//
    GPIOE->CRH |= GPIO_CRH_MODE9;//
    GPIOE->CRH &= ~GPIO_CRH_CNF10;//LOW TETRADE
    GPIOE->CRH |= GPIO_CRH_MODE10;//
    GPIOE->CRH &= ~GPIO_CRH_CNF11;//
    GPIOE->CRH |= GPIO_CRH_MODE11;//

    GPIOE->CRH &= ~GPIO_CRH_CNF12;//
    GPIOE->CRH |= GPIO_CRH_MODE12;//
    GPIOE->CRH &= ~GPIO_CRH_CNF13;//
    GPIOE->CRH |= GPIO_CRH_MODE13;//
    GPIOE->CRH &= ~GPIO_CRH_CNF14;//HIGH TETRADE
    GPIOE->CRH |= GPIO_CRH_MODE14;//
    GPIOE->CRH &= ~GPIO_CRH_CNF15;//
    GPIOE->CRH |= GPIO_CRH_MODE15;//
    }

    void Delay(unsigned long delay)
    {
    unsigned long wait;
    for(wait=delay; wait > 0; wait—);
    }
    void lcd_command(unsigned char lcd)
    {
    GPIOE->ODR &= ~GPIO_ODR_ODR6;
    Delay(4);
    GPIOE->ODR |= GPIO_ODR_ODR7;
    Delay(4);
    GPIOE->ODR = ((lcd<ODR &= ~GPIO_ODR_ODR7;
    }

    void LCD_Init(void)
    {
    lcd_command(0x30);
    Delay(320);
    lcd_command(0x0E);
    Delay(320);
    lcd_command(0x06);
    Delay(320);
    }

    int main (void)
    {
    HSI_Init();
    GPIO_Init();
    LCD_Init();

    }
    Программа отсылки команд на LCD 16*2 с HD44780. Проверяю JTAGом все биты выставляет правильно. Дисплей питается от 5 вольт (мультиметр показывает 4,4 вольта). Но все порты контроллера PE6-PE15 толерантны к 5 вольтам. Контроллер STM32F107. Подскажите, что может быть не так в коде. Заранее благодарю.

  9. А зачем делаете так?

    // Сброс флага события
    EXTI->PR |= EXTI_PR_PR2;

    Зачем первоначально читать? Ведь ноль никак не влияет! Или есть какой то момент? регистр вроде как реагирует только на ноль, да и даташит (f4) на станице 262 так и гласит (rc_w1). И в библиотеке ни пишут EXTI->PR = ….

    1. Нет, возможно просто привычка. Вообще я не помню что там с реакцией на ноль и просто подстраховался.

  10. что если нужно обработать несколько внешних прерываний,более 4-х,как тут быть?
    например разрешил прерывание от четырех портов:
    // Функции CMSIS разрешающие прерывания в NVIC
    NVIC_EnableIRQ (EXTI1_IRQn);
    NVIC_EnableIRQ (EXTI2_IRQn);
    NVIC_EnableIRQ (EXTI3_IRQn);
    NVIC_EnableIRQ (EXTI4_IRQn);

    а как дальше? параметра EXTI5_IRQn уже нет,а необходимо 12 линий

    1. Смотреть таблицу векторов дальше :) Они там есть, но уже не пораздельно, а группами. Т.е. в прерывании надо будет сначала вычислить кто его вызвал.

        1. Что не ясно? Таблицу векторов смотри до конца. Там до кучи будут вектора и остальных линий EXTI

          DCD EXTI9_5_IRQHandler ; EXTI Line 9..5

  11. Здравствуйте, а не могли бы вы пояснить, какие векторы в самой первой приведенной у вас таблице соответствуют «группе FIQ» ?

    Я вообще никак не могу разобраться… с одной стороны, в описаниях ядер ARM фигурирует короткая таблица исключений (в которой есть место для одного FIQ и для одного IRQ) и эта таблица вроде как тоже начинается с нулевого адреса (и в которой прерывания — это не адреса обработчиков, а именно команды, из-за чего вектор FIQ находится последним, чтоб обработчик FIQ можно было просто сразу писать в конце вектора, чтоб не тратить время на переход).

    С другой стороны, есть NVIC, у которого своя таблица (содержащая именно адреса) и который (по идее) должен дергать входы IRQ (и FIQ?) ядра ARM. И эта таблица тоже начинается типа с нулевого адреса. Может кто-нибудь это разъяснить?

    1. Сам нашел ответ на свой вопрос. Если коротко, то Cortex-M3 и M4 не имеют FIQ режима вовсе. И не имеют FIQ-прерываний. Поэтому и в векторе нет никакой FIQ-группы.

      Более подробно: ARM развивается. Предком кортексов было ядро ARM-7TDMI. В нем не было NVIC, а был какой-то другой контроллер прерываний, не связанный с процессорным ядром так тесно, как NVIC связан в кортексах. Количество элементов в таблице прерываний было порядка 10 (например, см. https://web.eecs.umich.edu/~prabal/teaching/eecs373-f10/readings/ARM_Architecture_Overview.pdf, стр 5). Вектор прерываний, насколько я понимаю, был устроен так же, как и в AVR, т.е. это были команды перехода на обработчик (вместе с адресами либо смещениями). Это позволяло поставить переход для FIQ-прерывания в самый конец таблицы и, таким образом, сэкономить на переходе. FIQ обработчик кроме того, что не должен был сохранять регистры в стек (т.к. пользовался теневым комплектом регистров (хотя и не полным)), ещё и можно было разместить прямо в конце таблицы прерываний и сэкономить на времени перехода.

      В кортексе-М (это важно, потому что в R все не так как в М (http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0363g/CHDDHIIJ.html)) все немного по-другому: в векторе хранятся не команды, а адреса. Режимов всего 2: User и Handle (http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0439d/CHDBHCFH.html).
      NVIC тесно связан с ядром и работает как описано в этой статье, таблица прерываний теперь может содержать несколько десятков обработчиков, но в ней нет места для FIQ.

      Грубо говоря, ARM7TDMI развился в 3 ветви Cortex-M, Cortex-R и Cortex-A. Каждая из которых взяла от ARM7TDMI то, что лучше подходило ей и немного улучшила эти качества. В результате с одной стороны ARM стал лучше, специализированнее, но с другой стороны появилась путаница.

  12. Правильно ли я понимаю, что за разрешение/запрет всех IRQ прерываний отвечает младший бит PRIMASK? Т. е. чтобы в пользовательской программе проверить запрещены или разрешены в данный момент все IRQ прерывания, надо проверить этот бит? Можно ли это сделать в лоб или не выйдет, и вообще тот ли это способ?

  13. Каким образом сбрасывать Pendind флаги NVIC? Написано, что это делается только на периферии, если можно, то с этого места пожалуйста поподробнее.

  14. Здравствуйте, пытаюсь баловаться с отечественным представителем Cortex-M3 в виде микроконтроллера 1986ВЕ94Т. Так вот, стандартную библиотеку входит схожий .s файл. Не могу понять один момент, это инструкция «B .». Что это обозначает я не нашел, единственные мысли что ветвление по регистру связи LR/R14. Может кто подсказать?

    1. Ну ну. Сколько там тактов на команду? А какое энергопотребление? А с периферией как общаться? А с отладчиком?

Добавить комментарий

Ваш e-mail не будет опубликован.

Перед отправкой формы:
Human test by Not Captcha