Таймеры - прерывания

Базовая работа с таймерами, использование прерываний

Часто, нам надо выполнять какие-то действия с фиксированной частотой или, наоборот, немедленно реагировать на внешние события. Обе этих задачи решаются прерываниями.

Разберем самый простой пример, и попробуем менять частоту мигания диода по синусоиде с помощью прерываний и таймеров. Будем использовать настройку выходов микроконтроллера из предыдущего раздела.

Таймеры

Настройка внутреннего тактирования

Откроем Clock Configuration. Первое изменение, которое надо сделать, это переключить с HSI на HSE (слева) и Input frequency усттановить 12. Далее, HCLK установить в 160 МГц. Это даст всей переферии (справа), включая таймеры, базовую частоту в 160 МГц. Можно указать и другое значение, в зависимости от задачи, но здесь мы будем работать с этим.

Настройка частоты таймера

Настроим третий таймер (TIM3).

Clock Source - Internal Clock. Это "включает" таймер в базовом варианте. Prescaler - "делитель" для частоты таймера. Поделив частоту таймера на 16000, мы получаем таймер с частотой 10 КГц. Counter Period - значение, по достижению которого счетчик таймера сбрасывается.

Также в NVIC Settings поставим галочку на TIM3 global interrupt. Теперь, по достижению значения counter period, таймер не только сбрасывается, но и вызывается прерывание (код для которого будет сгенерирован в stm32g4xx_it.c). Учитывая, что частота таймера 10 КГц, а период 10000, мы получили прерывание, вызывающееся 1 раз в секунду.

Обратите внимание, что из всех числовых параметров в редакторе вычитается единица (16000-1, 10000-1). Это связано с тем, что на самом деле prescaler при значении 0, конечно, делит входящую частоту не на 0, а на 1, соответственно его реальное значение на единицу больше. А counter ведет отсчет от 0.

Код прерывания и управление периодом

Заведем в Inc/main.h переменную extern double x;, отвечающую за "время" - ось X для нашего синуса. Этот хедер включается и в main.c, и в stm32g4xx_it.c, что позволит нам использовать эту переменную в обеих файлах.

Вообще говоря, использовать глобальные переменные как разделяемое (shared) состояние в разных файлах - антипаттерн и крайне подверженная багам конструкция (как со стороны программиста, так и со стороны компилятора - например "initialization order fiasco"). Но в этом туториале для наглядности мы воспользуемся одной глобальной переменной.

Инициализируем x в main.c:

/* USER CODE BEGIN PV */
double x = 0;
/* USER CODE END PV */

Запустим прерывания внутри блока USER CODE в main, после всех инициализаций но до бесконечного цикла:

HAL_TIM_Base_Start_IT(&htim3);

И в главном цикле будем увеличивать его значение со временем в диапазоне от 0 до 2:

/* USER CODE BEGIN WHILE */
while (1) {
    HAL_Delay(1);
    if (x < 2) x += 0.003;
    else x = 0.0;
/* USER CODE END WHILE */

В файле ы CubeIDE для нас сгенерировала функцию-обработчик прерывания таймера. В ней, мы будем делать две вещи:

  1. Переключать состояние диода. HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);

  2. Менять значение counter period таймера по синусоиде, чтобы частота переключений плавно менялась. __HAL_TIM_SET_AUTORELOAD(&htim3, (sin(3.14*x)+1.2)*500);. Мы пользуемся третьим таймером, x меняется от 0 до 2, значение синуса нам надо немного "поднять" над 0, поэтому +1.2, и *500, так как получившееся до этого значение лежит только в пределе [0.2, 2.2]

Приведем код функции прерывания целиком:

void TIM3_IRQHandler(void)
{
  /* USER CODE BEGIN TIM3_IRQn 0 */
  HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
  __HAL_TIM_SET_AUTORELOAD(&htim3, (sin(3.14*x)+1.2)*500);
  /* USER CODE END TIM3_IRQn 0 */
  HAL_TIM_IRQHandler(&htim3);
  /* USER CODE BEGIN TIM3_IRQn 1 */

  /* USER CODE END TIM3_IRQn 1 */
}

Все! Залив этот код на контроллер, мы получим мерцающий по синусоиде светодиод.

Last updated