Самый наглядный способ продемонстрировать работу ШИМ — плавное изменение яркости светодиода. Начнём с настройки выводов микроконтроллера в конфигураторе CubeIDE.
Для тех, кто ещё не знаком с этим понятием есть специальный урок про ШИМ.
ШАГ 1. Настраиваем контакты программатора и отладчика.
Для этого в разделе System Core / SYS в поле Debug выбираем Serial Wire.

Этот шаг позволяет нам избежать проблем с загрузкой программы на микроконтроллер. Мы делаем его начиная с первого урока про stm32 и CubeIDE.
ШАГ 2. Выбираем режим контакта PA10
Нажимаем правую кнопку мыши на конакте PA10 и выбираем странный пункт TIM1_CH3. Выбор этого пункта означает, что мы подключаем данный контакт к каналу №3 аппаратного таймера №1.

Аппаратный таймер — это устройство, которое может генерировать и обрабатывать прямоугольные импульсы не нагружая центральный процессор. У STM32 имеется несколько таймеров с разным набором функций. Разные каналы разных таймеров могут быть подключены только к определенным контактам, что следует учитывать при проектировании принципиальной схемы устройства.
ШАГ 3. Настройка таймера
Теперь нам необходимо настроить канал №3 таймера №1 на генерацию именно ШИМ сигнала. Для этого раскроем раздел timers в левой колонке конфигуратора. В появившемся блоке настроек находим пункт Channel 3 и выбираем в нём:
PWM Generation CH3
Далее, в том же блоке настроек, но чуть ниже указываем величину предделителя (Prescaler) и счётчика (Counter period).

Для установки этих параметров будем использовать следующие рассуждения.
Мы управляем светодиодом, и чтобы не было видно мерцания частота ШИМ должна быть не менее 100 Гц. Пусть будет 250Гц, что соответствует периоду T = 4мс. Чтобы градаций яркости совсем не было заметно, выберем разрешение ШИМ равным 500.
Для обеспечения таких параметров, на вход генератора ШИМ необходимо подать сигнал с частотой 250Гц*500 = 125кГц. Это нужно для того, чтобы в течение каждого периода счётчик таймера смог отсчитывать импульс нужной длины: 0,1,2,…,499 частей периода.
С другой стороны, таймер №1 в STM32F103 подключён к шине APB2, а значит он тактируется её частотой. Шина — это такой аппаратный канал передачи данных между разными устройствами внутри микроконтроллера: процессором, памятью, интерфейсами I2C, SPI и пр.
Мы ничего не меняли в проекте, поэтому изначально APB2 работает на частоте 8МГц, значит и на входе счётчика таймера будет 8МГц. А нам надо 125кГц! Что делать? Для этого нам и понадобится предделитель! Мы просто поделим частоту шины на 64 и получим, что хотим.
И самое последнее. В поле Counter period мы запишем разрешение ШИМ сигнала — 500. Этот параметр говорит счётчику таймера на сколько частей необходимо делить период сигнала.
Важный нюанс. При выставлении предделителя и периода счётчика следует вычитать единицу!
Не забываем сохранить конфигурацию. Только после сохранения CubeIDE автоматически обновит исходный код согласно внесённым в конфигураторе изменениям. Готово! Переходим к программе.
Программа
Чтобы изменить коэффициент заполнения ШИМ сигнала нам необходимо каждый раз перенастраивать таймер, но уже не в конфигураторе, а в самой программе. Для этого мы опишем новую функцию, которую назовём setPWM. Сначала объявим её в самом начале файла main.c.
/* USER CODE BEGIN PFP */
void setPWM(uint16_t pwm_value);
/* USER CODE END PFP */
А затем реализуем её в блоке USER CODE BEGIN 4.
/* USER CODE BEGIN 4 */
void setPWM(uint16_t value)
{
TIM_OC_InitTypeDef sConfigOC;
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = value;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_3); // таймер №1, канал №3
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3);
}
/* USER CODE END 4 */
Также необходимо запустить ШИМ на 3-м канале первого таймера. Делаем это так:
/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3);
/* USER CODE END 2 */
Это была подготовка. Теперь напишем программу, которая будет плавно менять яркость светодиода от 0 до 100%, а затем от 100% до нуля. Сделаем это следующим образом. Пусть изначально яркость равна 0. На каждом шаге суперцикла будем прибавлять единицу к яркости, пока не достигнем максимального значения. В момент, когда яркость достигла 500, меняем направление отсчёта и начинаем вычитать по единице, пока не достигнем 0. И так далее.
Объявим пару переменных, в которых будем хранить текущее значение коэффициента заполнения (pwm_value) и направление отсчёта (step).
/* USER CODE BEGIN PV */
uint16_t pwm_value = 0;
int8_t step = 0;
/* USER CODE END PV */
Основной код добавим в суперцикл while:
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if(pwm_value == 0) step = 1;
if(pwm_value == 500) step = -1;
pwm_value += step;
setPWM(pwm_value);
HAL_Delay(5);
}
/* USER CODE END 3 */
Последнее, что нам нужно сделать — подключить светодиод к ножке PA10. Воспользуемся простейшей схемой с резистором на 200 Ом, такой же, как в уроке про светодиод на Ардуино.
Наконец, загружаем программу на микроконтроллер и наблюдаем плавно вспыхивающий и также плавно затухающий светодиод. Ура!
На следующем уроке будем управлять куда более серъёзной нагрузкой — двигателем постоянного тока!
Очень жду! «На следующем уроке будем управлять куда более серъёзной нагрузкой — двигателем постоянного тока!»
Жду уроков на CMSIS
Частота ЦП и периферии какая?
Читатель по прескейлерам должен угадывать?
Там как бы указано что шина APB работает на частоте 8МГц
Не работает этот код. Всё в точности скопировал отсюда и ничего нет на выходе. Светодиод не загорается. Не подскажете в чём может быть проблема?
функция setPWM работает только один раз, поэтому
используй HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_3);
int8_t step = 0;
step = -1;
Это косяк, int8_t не может быть отрицательным, однако этот пример работает.
TIM1->CCR3 = pwm_value;
без функции
Здравствуйте! В настройках канала таймера есть опция «PWM Generation No Output»
Очень прошу рассказать о ней, для чего она предназначена и как ею правильно пользоваться?
Как и в случае остальных функций таймеров, будет срабатывать определённое прерывание, но при этом ни на какие ноги сигнал выходить не будет «No Output».
А можно узнать, получится ли данным способом управлять светодиодом только уже который есть на плате?
Просто сделал подобным образом, только для пина относящемуся к светодиоду установленному на плате и к сожалению не работает, хотя программа выполняется в суперцикле без ошибок
На плате stm32f407g-disk1 штатный светодиод задействовал, конечно на таймере4. А вот почему не работает HAL_TIM_PWM_ConfigChannel так и не понял, пока заменил изменением регистра сравнения с помощью CMSIS.
А можно какие-то комментарии насчёт структуры функции? Мне вот совсем не понятно что за параметры в ней.
Посмотрел в отладчике, регистр занят. Нужно вначале прописать HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_3);
Данный пример отказывался работать пока в pwmSet не добавил HAL_TIM_PWM_Stop
А можно пример двухтактного ШИМ для полумоста?