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

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

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

## Таймеры

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

<figure><img src="https://3551773033-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FBjLyfPc4FcQUfXFi5fQr%2Fuploads%2FLbESvv0585CXNBq8ui9t%2F%D0%A1%D0%BD%D0%B8%D0%BC%D0%BE%D0%BA%20%D1%8D%D0%BA%D1%80%D0%B0%D0%BD%D0%B0%202023-09-27%20%D0%B2%2019.44.17.png?alt=media&#x26;token=3d67c448-0ca1-4c26-a95f-546b8ea21c1c" alt=""><figcaption></figcaption></figure>

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

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

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

<figure><img src="https://3551773033-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FBjLyfPc4FcQUfXFi5fQr%2Fuploads%2FpDzoNqTP7b9Vmi3CeaZw%2F%D0%A1%D0%BD%D0%B8%D0%BC%D0%BE%D0%BA%20%D1%8D%D0%BA%D1%80%D0%B0%D0%BD%D0%B0%202023-09-27%20%D0%B2%2019.57.25.png?alt=media&#x26;token=25f0fa43-d7fd-42c1-a3ae-71dcfc9f209f" alt="" width="453"><figcaption></figcaption></figure>

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

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

{% hint style="info" %}
Обратите внимание, что из всех числовых параметров в редакторе вычитается единица (`16000-1`, `10000-1`). Это связано с тем, что на самом деле **prescaler** при значении 0, конечно, делит входящую частоту не на 0, а на 1, соответственно его реальное значение на единицу больше. А **counter** ведет отсчет от 0.
{% endhint %}

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

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

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

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

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

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

```c
HAL_TIM_Base_Start_IT(&htim3);
```

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

```c
/* 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]

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

```c
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 */
}
```

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