Прерывания

Прерывания

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

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

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

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

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

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

Прерывание (от английского interrupt) – это сигнал, сообщающий процессору о наступлении какого-либо события. При этом выполнение текущей последовательности команд приостанавливается, и управление передается специальной функции - обработчику прерывания, который выполняет работу по обработке события и возвращает управление в прерванный код.
Таким образом, прерывание прекращает нормальный ход программы для выполнения приоритетной задачи, определяемой внутренним или внешним событием микроконтроллера.


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

Все возможные источники прерываний представлены в виде таблицы векторов прерываний, представленной в файле startup_stm32f303xc.s.

Контролем за возникновением и обработкой прерываний в микроконтроллерах STM32 занимается встроенный контроллер векторов прерываний NVIC, являющийся частью ядра ARM Cortex. Этот контроллер поддерживает обработку до 66 типов прерываний с программируемым уровнем приоритетов.

Размещение обработчиков прерываний автоматически осуществляется в файле stm32f3xx_it.c, и сейчас в этом файле представлен только обработчик прерывания системного таймера.

Давайте воспользуемся прерыванием таймера 6. Откроем в конфигураторе STM32CubeMX проект с прошлого урока и зайдем в настройку таймера. На вкладке NVIC Settings установим галочку, разрешающую прерывания таймера. И сгенерируем код проекта.
Разрешение прерываний таймера

Открываем проект в IAR.
Перейдем к файлу обработчиков прерываний, и мы увидим, что у нас появилась дополнительная функция – обработчик прерывания таймера TIM6.
Уберем из бесконечного цикла цикл ожидания и введем дополнительную переменную, значение которой будем изменять в обработчике прерывания. Важно, чтобы эта переменная имела глобальную область видимости, то есть была описана за пределами функции main() в файле main.c.

1
2
3
4
/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
uint8_t f=0;
/* USER CODE END PV */


Для того чтобы при включении таймера одновременно разрешить прерывания используется функция
 
HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim)

с той же ссылкой на дескриптор таймера в качестве аргумента.

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

В основном цикле проверим значение введенной переменной, и если оно равно 1, выполним все указанные действия и обнулим эту переменную.
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
int main(void)
{
  /* USER CODE BEGIN 1 */
  /* USER CODE END 1 */
  /* MCU Configuration----------------------------------------------------------*/
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
  /* Configure the system clock */
  SystemClock_Config();
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM6_Init();
  /* USER CODE BEGIN 2 */
  HAL_TIM_Base_Start_IT(&htim6);
  uint8_t i = 0;
  /* USER CODE END 2 */
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    if (f == 1)
    {
      f = 0;
      HAL_GPIO_WritePin(GPIOE, GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11
        | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15, GPIO_PIN_RESET);
      switch (i)
      {
        case 0: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_8, GPIO_PIN_SET); break;
        case 1: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_9, GPIO_PIN_SET); break;
        case 2: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_10, GPIO_PIN_SET); break;
        case 3: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_11, GPIO_PIN_SET); break;
        case 4: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_12, GPIO_PIN_SET); break;
        case 5: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_13, GPIO_PIN_SET); break;
        case 6: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_14, GPIO_PIN_SET); break;
        case 7: HAL_GPIO_WritePin(GPIOE, GPIO_PIN_15, GPIO_PIN_SET); break;
      }
      i++;
      i %= 8;
    }
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}


Теперь наша задача – изменить значение указанной переменной в обработчике прерывания. Здесь сложность заключается в том, что есть необходимость использования переменной, описание которой содержится в другом файле. С этой целью используется модификатор extern, который позволяет объявить внешнюю переменную, то есть переменную, описанную вне указанного файла. Важно то, что имя и тип этой переменной обязательно должны совпадать с теми, которые указаны для этой переменной в файле main.c.

Перейдем к файлу, содержащему обработчик прерываний - stm32f3xx_it.c - и опишем внешнюю переменную.
1
2
3
/* USER CODE BEGIN 0 */
extern uint8_t f;
/* USER CODE END 0 */

В обработчике прерывания TIM6 присвоим этой переменной значение, равное 1, в случае равенства нулю.
1
2
3
4
5
6
7
8
9
10
void TIM6_DAC_IRQHandler(void)
{
  /* USER CODE BEGIN TIM6_DAC_IRQn 0 */
  if (f == 0)
    f = 1;
  /* USER CODE END TIM6_DAC_IRQn 0 */
  HAL_TIM_IRQHandler(&htim6);
  /* USER CODE BEGIN TIM6_DAC_IRQn 1 */
  /* USER CODE END TIM6_DAC_IRQn 1 */
}


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

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

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

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

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