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

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

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

Что это, в принципе понятно, но вот что за программное или аппаратное обеспечение генерирует сигнал, почему и, главное для чего все это придумали, разберем подробнее.

МК снабжены множеством различной периферии и практически у всех имеются прерывания. У модулей передачи данных, это прерывания связанные с такими событиями как начало и окончание передачи/приема данных или такими ошибками как переполнение буфера и др.

Для чего это нужно рассмотрим на примере модуля UART (интерфейс последовательной передачи данных). Допустим у нас имеется МК, который общается по UART с другим устройством. Когда к нам приходят данные, в модуле устанавливается флаг приема данных, который нам необходимо отследить и в случае когда флаг поднят, произвести действия по обработке данных и принятия последующего решения. Самое простое решение, это отслеживание флага в основном программе с помощью цикла.

while(1)
{
  flagRX = false;

  while (!flagRX)
  {
  }
  //обрабатываем принятые данные

  //другие задачи
  ...
  ...
}

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

flagRX = false;

while (1)
{
  if (flagRX == true) 
  {
    //обрабатываем принятую информацию
    ...
    //сбрасываем флаг
    flagRX = false;
  }

  //другие задачи
  ....
  ....
}

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

int main(void)
{
  //инициализация периферии и прерываний
  ...
  ...
  while(1)
  {
    //выполнение основных задач
    ...
    ...
  }
}

void USART1_IRQHandler(void)
{
  //обработка событий от UART1
  ...
  ...
}

Разобравшись с назначением прерываний, перейдем к непосредственной реализации их в STM32.

В STM32 имеется таблица всех доступных прерываний (таблица векторов прерываний). Найти ее вы сможете в файле startup_stm32fxxx.*.

Там же описаны все обработчики прерываний с ключевым словом WEAK. Все обработчики, за исключением системных прерываний, ссылаются на функцию Default_Handler с бесконечным циклом. Обработчики системных прерываний также зациклины, но их названия соответствуют имени вектора прерывания. WEAK означает, что функции обработчиков являются "слабыми" и их можно переопределить в основной программе. Default_Handler необходим для информирования разработчика в режиме отладки, о том, что он включил прерывание, но не написал соответствующего обработчика. Если вы не знаете имя обработчика прерывания, самый быстрый способ найти его, это заглянуть в данную таблицу.

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

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

Как бы это не выглядело страшно по началу, появление библиотеки HAL, упростило написание кода. Рассмотрим пример с использованием библиотеки HAL для UART1 на STM32F107VC.

/* Includes ------------------------------------------------------------------*/
#include "stm32f1xx_hal.h"
/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart1;
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void Error_Handler(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);
//============================================================================//
int main(void)
{
  /* Сброс всей периферии, инициализация флеш и системного таймера */
  HAL_Init();

  /* Конфигурация тактирования */
  SystemClock_Config();

  /* Инициализация периферии */
  MX_GPIO_Init();
  MX_USART1_UART_Init();

  /* Задаем приоритет и включаем прерывание от UART */
  HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(USART1_IRQn);

  while (1)
  {
        HAL_Delay(500);
        HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET);
        HAL_UART_Transmit_IT(&huart1, (uint8_t*)"stm32dev.ru", 11);
  }
}
//============================================================================//
static void MX_GPIO_Init(void)
{

  GPIO_InitTypeDef GPIO_InitStruct;

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOB_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0 | GPIO_PIN_1, GPIO_PIN_RESET);

  /*Configure GPIO pin : PB0 */
  GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

}
//============================================================================//
void USART1_IRQHandler(void)
{
  HAL_UART_IRQHandler(&huart1);
}
//============================================================================//
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);
}
//============================================================================//

Код сгенерирован с помощью CubeMX для макетной платы Open107V. Здесь предоставлен код на который стоит обратить внимание, полную версию вы можете скачать ниже. Работа с UART будет подробно рассмотрена в следующей статье.

У нас имеются два светодиода Led1 и Led2 на портах PB0 и PB1 соответственно. В главном цикле раз в секунду меняем состояние Led1 на противоположное, отключаем светодиод Led2  и в Uart1 передаем сообщение "stm32dev.ru". После окончания передачи, срабатывает прерывание USART1_IRQHandler по событию UART_IT_TC (окончание передачи). Обработчик вызывает соответствующую данному событию функцию HAL_UART_TxCpltCallback, в которой отключаем светодиод Led2. После компиляции и прошивки макетной платы, вы увидите кратковременное включение светодиода Led2.


Архив с проектом ссылка на скачивание.

Информация
Посетители, находящиеся в группе Гости, не могут оставлять комментарии к данной публикации.


© Copyright 2017. Все права защищены.
Яндекс.Метрика