> For the complete documentation index, see [llms.txt](https://voltbro.gitbook.io/vbcores/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://voltbro.gitbook.io/vbcores/tutorials/stm32-cube-ide/taimery-preryvaniya.md).

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

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

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

## Таймеры

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

<figure><img src="/files/ZMcCGvcddZE8F4QD9vR4" alt=""><figcaption></figcaption></figure>

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

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

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

<figure><img src="/files/zNo5elIcwBm35RUWqhOX" 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 */
}
```

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


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://voltbro.gitbook.io/vbcores/tutorials/stm32-cube-ide/taimery-preryvaniya.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
