# Таймеры - ШИМ

Хоть нам и удалось в прошлом разделе сделать "мигание по синусоиде", оно было прерывистым и не очень красивым. Хотелось бы добиться плавного изменения яркости, без заметного для глаза мерцания. В этом нам поможет ШИМ - широтно-импульсная модуляция (PWM - pulse-width modulation).

## Что такое ШИМ

В прошлом упражнении мы уже видели, что у таймера есть период - значения, достигая которого он сбрасывается. Таким образом, можно говорить о частоте таймера, с этой частотой у нас вызывались прерывания и эта же частота будет использоваться для ШИМ.

ШИМ нужна для генерации сигналов, переключающихся между двумя значениями (условно, 0 и 1) с постоянной частотой:

<figure><img src="https://3551773033-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FBjLyfPc4FcQUfXFi5fQr%2Fuploads%2FnHxSdPF2HkQdysS1SKU2%2F%D0%B8%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5.png?alt=media&#x26;token=efd3542b-545e-4edb-8e2a-f39da81f03e1" alt=""><figcaption><p>Примеры сигналов</p></figcaption></figure>

Видно, что частота ("время" от начала одного сигнала до следующего) постоянно - эта та же частота, что была и у прерывания и задается с помощью **Counter Period**. Duty cycly, или коэффициент заполнения, отмеченный на графике, управляется с помощью второго счетчика - **pulse**, по достижении которого сигнал переходит в состояние 0.

Представим, что counter period у нас 20. Тогда для получения таких сигналов, как на рисунке, нам нужно выставить pulse:

1. 10
2. 15
3. 5

Если частота ШИМ достаточно велика (тысячи КГц), то держа таким образом светодиод во включенном состоянии 50/75/25 процентов времени, мы получим стабильную яркость в 50/75/25 или любой другой процент от максимальной. Переключения с такой частотой будут не различимы для глаза.

{% hint style="info" %}
Аналогичным образом можно управлять, например, скоростью DC-мотора
{% endhint %}

## Управление яркостью светодиода с помощью ШИМ

### Настройка микроконтроллера

Выберем ножку диода `PA5`, и назначим ей режим `TIM2_CH1` - это означает, что для нее мы будем использовать первый канал генерации PWM из второго таймера (дальше станет понятнее, что имеется ввиду).

<figure><img src="https://3551773033-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FBjLyfPc4FcQUfXFi5fQr%2Fuploads%2F5kE7xufkvWvKCklQtnL7%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-28%20%D0%B2%2013.13.12.png?alt=media&#x26;token=bcbb9736-9b5b-4d03-9126-6d2ca95fae22" alt=""><figcaption></figcaption></figure>

Теперь необходимо настроить `TIM2`:

<figure><img src="https://3551773033-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FBjLyfPc4FcQUfXFi5fQr%2Fuploads%2FHhR5aa8qYE4VcCELwC3D%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-28%20%D0%B2%2013.15.15.png?alt=media&#x26;token=ad3a38c6-143b-4116-84f0-0146334a7c8d" alt=""><figcaption></figcaption></figure>

С **Clock Source**, **Prescaler** и **Counter Period** мы уже разбирались. Тут важны следующие новые настройки:&#x20;

* `Channel1 - PWM Generation CH1` - указывает, что на первом канале таймера (который мы как раз привязали к светодиоду) нужно генерировать ШИМ
* `PWM Generation Channel 1 - Pulse - 10` - значение, контролирующее коэффициент заполнения сигнала. При `pulse == 10` и `period == 500`, заполнение равно 2%

Очевидно, что чтобы менять яркость светодиода, необходимо просто менять значение pulse по синусоиде - при **prescaler** 16, частота таймера получилась равной 10 МГц, и с периодом 500 - частота ШИМ 20 КГц. С такой частотой, при управлении яркостью через **pulse** никакого мерцания заметно не будет, и можно рассматривать коэффициент заполнения просто как процент от максимальной яркости.

В таком случае, все что нам осталось сделать, это запустить ШИМ и в цикле устанавливать pulse как синус от какого-то X. Тут удобно привести большой кусок кода из `main`, в котором содержится вся эта логика:

```c
/* USER CODE BEGIN 2 */
double x = 0;
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
    HAL_Delay(0.05);
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 125*(sin(x*3.14)+1));
    if (x < 2) x += 0.001;
    else x = 0;
/* USER CODE END WHILE */
```

Залив этот код на контроллер, мы увидим, как светодиод плавно меняет яркость, без заметного мерцания.
