Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Кармин Новиелло - Освоение STM32.pdf
Скачиваний:
2741
Добавлен:
23.09.2021
Размер:
47.68 Mб
Скачать

Обработка прерываний

189

Напротив, чтобы программно сбросить бит отложенного состояния IRQ, мы можем использовать функцию:

void HAL_NVIC_ClearPendingIRQ(IRQn_Type IRQn);

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

Чтобы проверить, активна ли ISR (обслуживается ли IRQ), мы можем использовать функцию:

uint32_t HAL_NVIC_GetActive(IRQn_Type IRQn);

которая возвращает 1, если IRQ активен, 0 в противном случае.

7.4. Уровни приоритета прерываний

Отличительной особенностью архитектуры ARM Cortex-M является возможность расставлять приоритеты прерываний (за исключением первых трех программных исключений, имеющих фиксированный приоритет, как показано в таблице 1). Приоритет прерывания позволяет определить две вещи:

ISR, которые будут выполняться первыми в случае одновременных прерываний;

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

Механизм приоритетов контроллера NVIC существенно отличается между ядрами Cortex-M0/0+ и Cortex-M3/4/7. По этой причине мы собираемся объяснить их в двух отдельных подпунктах.

7.4.1. Cortex-M0/0+

Микроконтроллеры на базе Cortex-M0/0+ имеют более простой механизм приоритетов прерываний. Это означает, что микроконтроллеры STM32F0 и STM32L0 ведут себя иначе, чем остальные микроконтроллеры STM32. И вы должны обратить особое внимание, если переносите свой код между сериями STM32.

В ядрах Cortex-M0/0+ приоритет каждого прерывания определяется 8-разрядным регистром, называемым IPR. В архитектуре ядра ARMv6-M используются только 4 бита этого регистра, что позволяет использовать до 16 различных уровней приоритета. Однако на практике микроконтроллеры STM32, реализующие эти ядра, используют только два старших бита этого регистра, а все остальные биты равны нулю.

Рисунок 13: Содержимое регистра IPR в микроконтроллере STM32 на базе Cortex-M0

На рисунке 13 показано, как интерпретируется содержимое регистра IPR. Это означает, что у нас есть только четыре максимальных уровня приоритета: 0x00, 0x40, 0x80, 0xC0. Чем ниже это число, тем выше приоритет. То есть IRQ, имеющий приоритет, равный 0x40, имеет более высокий приоритет, чем IRQ с уровнем приоритета, равным 0xC0. Если

Обработка прерываний

190

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

вается вытеснением прерывания (interrupt preemption).

Рисунок 14: Вытеснение прерывания в случае одновременного выполнения

На рисунке 14 показан пример вытеснения прерывания. A – это IRQ с более низким приоритетом, который срабатывает в момент времени t0. ISR начинает выполнение, но IRQ B, который имеет более высокий приоритет (более низкий уровень приоритета), срабатывает в момент времени t1, и выполнение ISR прерывания A останавливается. Когда процедура обслуживания прерывания B завершает свою работу, выполнение ISR прерывания A возобновляется до ее завершения. Этот «вложенный» механизм, обусловленный приоритетами прерываний, сводит к названию контроллера NVIC, который назы-

вается Контроллером вложенных векторных прерываний (Nested Vectored Interrupt Controller).

Cortex-M0/0+ имеет важное отличие в сравнении с ядрами Cortex-3/4/7. Их приоритет прерывания является статическим. Это означает, что как только прерывание разрешено, его приоритет больше не может быть изменен, пока мы снова не запретим IRQ.

CubeHAL предоставляет следующую функцию для назначения приоритета IRQ:

void HAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority, uint32_t SubPriority);

Функция HAL_NVIC_SetPriority() принимает IRQ, который мы собираемся сконфигурировать, и PreemptPriority, являющийся приоритетом вытеснения, который мы собираемся назначить IRQ. API-интерфейс CMSIS, а, следовательно, и библиотека CubeHAL спроектированы таким образом, что PreemptPriority указывается с номером уровня приоритета в диапазоне от 0 до 4. Значение автоматически сдвигается на старшие значащие биты. Это упрощает перенос кода на другой микроконтроллер с другим количеством битов приоритета (по этой причине производители интегральных схем используют только левую часть регистра IPR).

Как видите, функция также принимает дополнительный параметр SubPriority, который просто игнорируется в HAL CubeF0 и CubeL0, так как данные процессоры на базе Cortex-M не поддерживают субприоритет прерываний. В данных HAL инженеры ST решили использовать тот же API, что и в других HAL для процессоров на базе Cortex-M3/4/7. Вероятно, они решили сделать это, чтобы упростить перенос кода между различными микроконтроллерами STM32.

Обработка прерываний

191

Любопытно, что они решили определить соответствующую функцию для получения приоритета IRQ следующим образом:

uint32_t HAL_NVIC_GetPriority(IRQn_Type IRQn);

которая полностью отличается от той, которая определена в HAL для процессо-

ров на базе Cortex-M3/4/710.

В следующем примере11 показано, как работает механизм приоритета прерываний.

Имя файла: src/main-ex3.c

39 uint8_t blink = 0;

40

41int main(void) {

42GPIO_InitTypeDef GPIO_InitStruct;

44

HAL_Init();

45

 

46/* Разрешение тактирования портов GPIO */

47__HAL_RCC_GPIOC_CLK_ENABLE();

48__HAL_RCC_GPIOB_CLK_ENABLE();

49__HAL_RCC_GPIOA_CLK_ENABLE();

50

51/* Конфигурирование вывода GPIO : PC13 – кнопка USER BUTTON */

52GPIO_InitStruct.Pin = GPIO_PIN_13 ;

53GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;

54GPIO_InitStruct.Pull = GPIO_PULLDOWN;

55HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

56

57/* Конфигурирование вывода GPIO : PB2 */

58GPIO_InitStruct.Pin = GPIO_PIN_2 ;

59GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;

60GPIO_InitStruct.Pull = GPIO_PULLUP;

61HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

62

63/* Конфигурирование вывода GPIO : PA5 - светодиод LD2 */

64GPIO_InitStruct.Pin = GPIO_PIN_5;

65GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;

66GPIO_InitStruct.Pull = GPIO_NOPULL;

67GPIO_InitStruct.Speed = GPIO_SPEED_LOW;

68HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

69

70HAL_NVIC_SetPriority(EXTI4_15_IRQn, 0x1, 0);

71HAL_NVIC_EnableIRQ(EXTI4_15_IRQn);

72

73HAL_NVIC_SetPriority(EXTI2_3_IRQn, 0x0, 0);

74HAL_NVIC_EnableIRQ(EXTI2_3_IRQn);

10Я открыл посвященный этому тред на официальном форуме ST, но на момент написания данной главы ответа от ST до сих пор нет.

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

Обработка прерываний

192

75

76while(1);

77}

78

79void EXTI4_15_IRQHandler(void) {

80HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);

81}

82

83void EXTI2_3_IRQHandler(void) {

84HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_2);

85}

86

87void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {

88if(GPIO_Pin == GPIO_PIN_13) {

89blink = 1;

90while(blink) {

91HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);

92for(volatile int i = 0; i < 100000; i++) {

93/* Цикл активного ожидания */

94}

95}

96}

97else {

98blink = 0;

Код должен быть достаточно простым для понимания, если предыдущее объяснение для вас понятно. Здесь у нас есть два IRQ, связанных с линиями 2 и 13 контроллера EXTI. Соответствующие им ISR вызывают обработчик HAL_GPIO_EXTI_IRQHandler(), который, в свою очередь, вызывает обратный вызов HAL_GPIO_EXTI_Callback(), передав ему GPIO, участвующий в прерывании. Когда нажата пользовательская кнопка, подключенная к сигналу PC13, ISR запускает бесконечный цикл, пока глобальная переменная blink не станет >0. Этот цикл заставляет светодиод LD2 быстро мигать. Когда на выводе PB2 установлен низкий уровень (используйте схему выводов для вашей Nucleo из Приложения C, чтобы определить положение вывода PB2), срабатывает EXTI2_3_IRQHandler()12, и это приводит к тому, что HAL_GPIO_EXTI_IRQHandler() устанавливает переменную blink в значение 0. Обработчик EXTI4_15_IRQHandler() теперь можно закончить. Приоритет каждого прерывания конфигурируется в строках 70 и 73: как видите, поскольку приоритет прерывания является статическим в микроконтроллерах на базе Cortex-M0/0+, мы должны установить его, прежде чем разрешим соответствующее прерывание.

Пожалуйста, обратите внимание, что это довольно плохой способ работы с прерываниями. Блокировка микроконтроллера внутри прерывания – плохой стиль программирования, и он является корнем всего зла во встроенном программировании. К сожалению, это единственный пример, который пришел в голову автору, учитывая, что на данный момент книга все еще охватывает только несколько тем. Каждая ISR должна быть спроектирована так, чтобы

12 Обратите внимание, что для микроконтроллеров STM32F302 именем IRQ, связанным с линией 2 контроллера EXTI, по умолчанию является EXTI2_TSC_IRQHandler. Обратитесь к примерам книги, если вы работаете с данным микроконтроллером.