Широтно-импульсная модуляция

Широтно-импульсная модуляция

На этом уроке мы рассмотрим формирование широтно-импульсно модулированного сигнала с использованием периферийного модуля таймера и научимся с его помощью управлять яркостью светодиода.
 

 
В нескольких прошлых уроках мы научились работать с базовым таймером TIM6, изменять его период и предварительный делитель, а также генерировать с его помощью временные задержки и использовать прерывания.
Но микроконтроллер STM32F303VCT6 содержит также несколько таймеров, допускающих расширенную настройку и некоторые дополнительные функции.

Каждый таймер, обладающий расширенной настройкой, позволяет сформировать до 6 каналов, работающих в одном из 4 режимов:

  • захват входного сигнала,
  • выход сравнения,
  • выход широтно-импульсной модуляции
  • режим одиночного импульса.

В документации подробно описано об использовании таймеров во всех режимах, но мы остановимся на режиме широтно-импульсной модуляции и попробуем использовать этот режим для управления яркостью светодиода.

Широтно-импульсная модуляция (сокращенно ШИМ) представляет собой импульсный сигнал постоянной частоты и переменной скважности.
Сигнал ШИМ
Скважность представляет собой отношение периода следования импульса к длительности импульса.
Скважность
Обратная величина, то есть отношение длительности импульса к периоду, называется коэффициентом заполнения, который часто измеряется в процентах. С помощью задания длительности импульса можно менять среднее напряжение на выходе ШИМ.
Коэффициент заполнения
Для того чтобы показать, как меняется среднее значение сигнала на выходе ШИМ я произвела моделирование схемы, состоящей из генератора импульсов и сглаживающей RC-цепочки, с помощью бесплатной моделирующей программы LTspice.
Схема моделирования сглаживающей RC-цепочки

Среднее значение меняется в зависимости от коэффициента заполнения и определяется как коэффициент заполнения, умноженный на напряжение питания, соответствующее логической единице.
Среднее значения напряжения ШИМ
График среднего напряжения на выходе ШИМ

Сигнал ШИМ формируется на канале связанном с выходом таймера с помощью двух регистров. Один из этих регистров - TIMx_ARR используется для задания периода таймера. Мы его уже использовали для формирования задержки.
Другой регистр -TIMx_CCR предназначен для задания длительности импульса. Количество таких регистров для каждого таймера определяется количеством каналов, на которых данный таймер может сформировать ШИМ-сигнал.

Счетный регистр таймера увеличивает свое значение на 1 с приходом каждого тактового импульса с выхода предварительного делителя таймера. В момент, когда значение счетного регистра превышает значение в соответствующем регистре TIMx_CCR, сигнал на выходе ШИМ сбрасывается в 0. В момент переполнения таймера, то есть превышения им значения, записанного в регистре периода TIMx_ARR, сигнал на выходе ШИМ устанавливается в 1.

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

 

Практическая реализация сигнала ШИМ

Рассмотрим задачу формирования ШИМ-сигнала с частотой 200 кГц. Будем в качестве тактового сигнала использовать сигнал с выхода внутреннего тактового генератора 8 МГц. При этом в регистр периода таймера необходимо поместить значение, равное 39.
Расчет регистра периода ШИМ
Давайте попробуем реализовать такой ШИМ-сигнал на одном из светодиодов.

Создадим новый проект в STM32CubeMX для микроконтроллера STM32F303VCT6. Установим режим отладки Serial Wire, и для вывода PE9 выберем конфигурацию TIM1_Channel1. Этот вывод подсвечивается оранжевым. Если вы обратили внимание, то правильно настроенные выводы маркируются зеленым.
Для того чтобы закончить конфигурацию этого вывода необходимо настроить использование таймера. Выберем в левой части окна периферийный модуль TIM1 и сконфигурируем для него канал 1 как выход ШИМ. Теперь конфигурация вывода закончена.
Осталось задать тактирование для этого таймера, Clock Source, от внутреннего генератора.
Конфигурирование вывода как выход ШИМ

Переходим во вкладку Configuration.
Здесь в настройках таймера укажем величину периода, равной 39 и длительность импульса для канала 1. Пусть она будет равна четверти периода. Укажем в этом регистре 10.
Настройка модуля таймера
Сгенерируем проект и откроем его в IAR.
Чтобы запустить таймер в режиме ШИМ используется функция

 
HAL_StatusTypeDef HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel)

в качестве аргументов которой передаются ссылка на дескриптор таймера и константа, определяющая номер канала ШИМ.
Скопируем ее и вставим перед бесконечным циклом. Дескриптор таймера уже описан для инициализации. Используем ссылку на него в качестве первого аргумента. В качестве второго аргумента скопируем константу TIM_CHANNEL_1 из описания этой функции в файле библиотеки.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int main(void)
{
  /* USER CODE BEGIN 1 */
  /* USER CODE END 1 */
  /* MCU Configuration----------------------------------------------------------*/
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
  /* Configure the system clock */
  SystemClock_Config();
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM1_Init();
  /* USER CODE BEGIN 2 */
  HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
  /* USER CODE END 2 */
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

Добавлена строчка 14.

Скомпилируем проект, загрузим в отладочную плату и запустим на выполнение. На плате засветился светодиод на линии PE9. А вот так выглядит сигнал PE9 на осциллографе.
Осциллограмма канала ШИМ

 
Однако поскольку период ШИМ составляет 40 периодов тактовой частоты, в регистр длительности импульса можно поместить только одно из 39 значений, то есть можно сформировать всего 40 различных коэффициентов заполнения. Во многих случаях этого может оказаться недостаточно. Чтобы увеличить это значение, при той же частоте генерируемого ШИМ-сигнала, необходимо увеличить тактовую частоту, используя для этого выход умножителя частоты.
Высокая частота ШИМ сигнала хороша тем, что к параметрам фильтра, выявляющего среднее значение, предъявляются меньшие требования и, соответственно, меньше задержка на выходе этого фильтра. Однако, с другой стороны, чем выше частота ШИМ, тем большая тактовая частота требуется для ее реализации, что увеличивает общее энергопотребление микроконтроллера. Кроме того, при слишком высокой частоте начинает существенно влиять время коммутации сигнала, то есть перехода его из нулевого состояния в единичное и наоборот.

Теперь рассмотрим генерацию ШИМ с низкой частотой. Пусть теперь нам нужно сгенерировать сигнал с частотой 200 Гц. При этом период таймера составит 39999:
Расчет регистра периода ШИМ для частоты 200 Гц
Давайте введем это значение. Скорректируем также длительность импульса, сгенерируем код проекта и загрузим в отладочную плату.
Расчет регистра импульса ШИМ для частоты 200 Гц
Запустим программу на выполнение. Я проанализирую сигнал на выводе PE9 с помощью осциллографа. Частота и длительность импульса соответствуют расчетным.
Осциллограмма выхода ШИМ 200 Гц

 

Использование нескольких каналов ШИМ

На таймере TIM1 можно реализовать до 6 каналов ШИМ с одинаковой частотой. Давайте добавим второй канал ШИМ в конфигураторе. Вывод PE11 автоматически сконфигурировался как выход ШИМ.
Конфигурация двух каналов ШИМ

Во вкладке Configuration в настройках модуля таймера появилась возможность задать длительность импульса для канала 2.
Давайте зададим ее и сгенерируем проект.
Задание длительности импульсов каналов ШИМ
Поскольку каждый канал ШИМ запускается отдельно, продублируем строчку и включим канал 2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
int main(void)
{
  /* USER CODE BEGIN 1 */
  /* USER CODE END 1 */
  /* MCU Configuration----------------------------------------------------------*/
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
  /* Configure the system clock */
  SystemClock_Config();
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM1_Init();
  /* USER CODE BEGIN 2 */
  HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
  HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2);
  /* USER CODE END 2 */
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

Добавлена строчка 15.

Теперь загрузим программу в отладочную плату и запустим на выполнение. Теперь светятся два светодиода. А сигналы на выводах PE9 и PE11 выглядят вот так.
Осциллограммы двух каналов ШИМ

 
Ну и еще один момент. Наверняка в практических задачах придется менять коэффициент заполнения ШИМ в процессе выполнения программы, а не только в момент инициализации. Давайте реализуем простую задачу. Будем управлять яркостью светодиода при нажатии кнопки. Пусть при нажатой кнопке коэффициент заполнения соответствует 90%, а при отжатой – 10%.

Выберем частоту ШИМ 100 кГц и соответственно значение регистра периода будет равно 79.

  • 90% будет соответствовать длительности импульса 72 периода тактовой частоты,
  • 10% будет соответствовать длительности импульса 8 периодов тактовой частоты.

Сконфигурируем таймер, задав для него значения периода и длительности импульса. Второй канал можно отключить. И сконфигурируем линию PA0 как вход. Сгенерируем код проекта и откроем его в IAR.
Меняем яркость светодиода


Уберем функцию включения второго канала ШИМ и добавим обработку нажатия кнопки. Поскольку обрабатывается уровень сигнала при нажатии кнопки, а не его фронт, никаких дополнительных переменных можно не вводить.
Считываем состояние вывода PA0. Напомню, что для считывания состояния вывода используется функция

 
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)

И сравниваем это значение с константой GPIO_PIN_SET, отображающей состояние «Кнопка нажата».
Если кнопка нажата, задаем длительность импульса равной 72 периодам тактовой частоты. Длительность импульса задается в регистре CCR. Для канала 1 это регистр CCR1, для канала 2 – регистр CCR2 и так далее.
Для обращения к этому регистру используем поле Instance дескриптора таймера htim1.

В случае если кнопка не нажата, задаем длительность импульса равной 8 периодам тактовой частоты.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
int main(void)
{
  /* USER CODE BEGIN 1 */
  /* USER CODE END 1 */
  /* MCU Configuration----------------------------------------------------------*/
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
  /* Configure the system clock */
  SystemClock_Config();
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM1_Init();
  /* USER CODE BEGIN 2 */
  HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
  /* USER CODE END 2 */
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET) {
      htim1.Instance->CCR1 = 72;
    }
    else
      htim1.Instance->CCR1 = 8;
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}



Компилируем проект, загружаем в отладочную плату и запускаем на выполнение. Мы видим заметное на глаз изменение яркости светодиода на линии PE9 при нажатии кнопки.

Прокрутить вверх