Giới thiệu
PWM – Pulse Width Modulation (điều chế độ rộng xung) – là phương pháp điều khiển giá trị điện áp trung bình trên tải bằng cách thay đổi chu kỳ của xung. Về cơ bản các vi điều khiển đều cho phép khởi tạo tín hiệu số PWM với các tần số khác nhau.
Đơn giản nhất để hiểu PWM có thể xem ví dụ sau: cấu hình output cho chân GPIO điều khiển bóng LED, nếu ta muốn làm sáng bóng LED theo hiệu ứng mờ hay tỏ, giải pháp đó là cho bóng LED sáng tắt ở tần số cao sao cho mắt mất khả năng phân tích sự sáng tắt của một bóng LED. Muốn bóng LED sáng tỏ thì thời gian sáng sẽ nhiều hơn thời gian tắt, và ngược lại. Hình dưới đây biểu diễn các tín hiệu dạng xung PWM.

– Duty cycle là tỷ lệ phần trăm mức cao.
– Period là chu kỳ xung.
– Pulse width là giá trị mức cao so với period.
Dựa trên nguyên lý bên trên mà STM32 hay các loại vi điều khiển khác đều hỗ trợ bộ tạo độ rộng xung tự động mà ta không phải tốn thời gian bật tắt chân IO, có thể thiết lập chu kỳ, tần số, độ rộng xung và một số chức năng đặc biệt. PWM thuộc khối timer.
Vì việc xuất tín hiệu PWM không phải là chức năng chính của các cổng, do vậy để làm được việc này trước hết cần thiết lập cấu hình cho cổng làm việc theo chức năng lựa chọn (Aternative Function – AFIO). Khởi tạo xung liên quan đến việc sử dụng chế độ phụ của timer.
Trước khi kết nối thiết bị, cần xác định rõ các tham số của tín hiệu PWM như tần số và hệ số prescalar.

Trong đó:
PSC – hệ số Prescalar,
TIMxCLK – tần số dao động đầu vào của timer (clock timer),
TIMxCNT – tần số bộ đếm (counter timer)
Để nhận được tần số đầu ra mong muốn cần ghi giá trị vào thanh ghi ARR theo công thức:

Trong đó:
ARR_VAL – giá trị ghi vào thanh ghi ARR,
TIMxCNT – tần số bộ đếm,
TIMx_out_freq – tần số đầu ra PWM mong muốn.
Bước cuối cùng là xác định hệ số để cung cấp giá trị điện áp trung bình đầu ra. Bước thiết lập này được thực hiện nhờ thanh ghi capture/compare (CCRx) theo công thức:

Trong đó:
D – hệ số cần điền, đơn vị %
CCRx _VAL – giá trị của thanh ghi CCRx (х – số hiệu thanh ghi, tương ứng với đường ngắt cụ thể),
ARR_VAL – giá trị để ghi vào thanh ghi ARR.


Ví dụ
Ví dụ 1
#include <stm32f4xx_gpio.h>
#include <stm32f4xx_rcc.h>
#include <stm32f4xx_tim.h>
void InitializeGPIO(GPIO_TypeDef* GPIOx, uint32_t Pin);
void InitializeTimer(TIM_TypeDef* TIMx, uint16_t PrescaleDiv, uint16_t Period);
void InitializePWMChannel4(TIM_TypeDef* TIMx, uint32_t ValueToOn);
void main( )
{
// Initialize SystemFrequency
SystemInit();
// Initialize GPIO
RCC_AHB1PeriphClockCmd( RCC_AHB1Periph_GPIOD, ENABLE );
InitializeGPIO( GPIOD, GPIO_Pin_15);
// Initialize TIM4
// prescaler*period = 60000*2800 (for SysClk = 168 MHz clock of PWM will be 1 Hz)
RCC_APB1PeriphClockCmd( RCC_APB1Periph_TIM4, ENABLE );
InitializeTimer( TIM4, 60000-1, 2800-1 );
// Initialize PWM Chanel4
// 50% duty factor = 1400
InitializePWMChannel4( TIM4, 1400 );
GPIO_PinAFConfig( GPIOD, GPIO_PinSource15, GPIO_AF_TIM4 );
}
/**
* @brief Setup the GPIOx port as out of PWM on selected Pin.
* @param GPIOx: where x can be (A..I) to select the GPIO peripheral.
* @param Pin: specifies the port bit to be initialized for out of PWM signal.
* Refer STM32F407xx Datasheet for Alternate function mapping (Table 9)
* @retval None
*/
void InitializeGPIO(GPIO_TypeDef* GPIOx, uint32_t Pin)
{
GPIO_InitTypeDef sg;
sg.GPIO_Pin = Pin;
sg.GPIO_Mode = GPIO_Mode_AF; // PWM is Alternate function Mode
sg.GPIO_Speed = GPIO_Speed_100MHz;
sg.GPIO_OType = GPIO_OType_PP;
sg.GPIO_PuPd = GPIO_PuPd_DOWN;
GPIO_Init( GPIOx, &sg );
}
/**
* @brief Initialize timer TIMx as counter with the specified Period and Prescaler.
* @param TIMx: where x can be 1 to 14 to select the TIM peripheral.
* @param Period: the length of period will be n+1 because the counting begin from 0
* @param PrescaleDiv: Divides the clock at PrescaleDiv+1.
* If PrescaleDiv=0 then SrcClock not changed.
* @retval None
*/
void InitializeTimer(TIM_TypeDef* TIMx, uint16_t PrescaleDiv, uint16_t Period)
{
TIM_TimeBaseInitTypeDef sT;
sT.TIM_Prescaler = PrescaleDiv;
sT.TIM_CounterMode = TIM_CounterMode_Up;// Count to up
sT.TIM_Period = Period;
sT.TIM_ClockDivision = TIM_CKD_DIV1; // No pre-division
sT.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit( TIMx, &sT );
TIM_Cmd( TIMx, ENABLE );
}
/**
* @brief Initialize PWM channel 4 of TIMx
* @param TIMx: where x can be 1 to 14 to select the TIM peripheral.
* @param ValueToOn: the value of TIMx starting from which PWM out will be 1
* @retval None
*/
void InitializePWMChannel4 ( TIM_TypeDef* TIMx, uint32_t ValueToOn )
{
TIM_OCInitTypeDef sOC;
sOC.TIM_OCMode = TIM_OCMode_PWM1;
sOC.TIM_Pulse = ValueToOn;
sOC.TIM_OutputState = TIM_OutputState_Enable;
sOC.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC4Init( TIMx, &sOC);
TIM_OC4PreloadConfig( TIMx, TIM_OCPreload_Enable);
}
Ví dụ 2
Tạo xung trên các chân PD12, PD13, PD14, PD15 với các độ rộng xung khác nhau
#include "stm32f4xx.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_tim.h"
void TM_LEDS_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct;
/* Clock for GPIOD */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
/* Alternating functions for pins */
GPIO_PinAFConfig(GPIOD, GPIO_PinSource12, GPIO_AF_TIM4);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource13, GPIO_AF_TIM4);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource14, GPIO_AF_TIM4);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource15, GPIO_AF_TIM4);
/* Set pins */
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(GPIOD, &GPIO_InitStruct);
}
void TM_TIMER_Init(void) {
TIM_TimeBaseInitTypeDef TIM_BaseStruct;
/* Enable clock for TIM4 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
/*
TIM4 is connected to APB1 bus, which has on F407 device 42MHz clock
But, timer has internal PLL, which double this frequency for timer, up to 84MHz
Remember: Not each timer is connected to APB1, there are also timers connected
on APB2, which works at 84MHz by default, and internal PLL increase
this to up to 168MHz
Set timer prescaller
Timer count frequency is set with
timer_tick_frequency = Timer_default_frequency / (prescaller_set + 1)
In our case, we want a max frequency for timer, so we set prescaller to 0
And our timer will have tick frequency
timer_tick_frequency = 84000000 / (0 + 1) = 84000000
*/
TIM_BaseStruct.TIM_Prescaler = 0;
/* Count up */
TIM_BaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
/*
Set timer period when it have reset
First you have to know max value for timer
In our case it is 16bit = 65535
To get your frequency for PWM, equation is simple
PWM_frequency = timer_tick_frequency / (TIM_Period + 1)
If you know your PWM frequency you want to have timer period set correct
TIM_Period = timer_tick_frequency / PWM_frequency - 1
In our case, for 10Khz PWM_frequency, set Period to
TIM_Period = 84000000 / 10000 - 1 = 8399
If you get TIM_Period larger than max timer value (in our case 65535),
you have to choose larger prescaler and slow down timer tick frequency
*/
TIM_BaseStruct.TIM_Period = 8399; /* 10kHz PWM */
TIM_BaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_BaseStruct.TIM_RepetitionCounter = 0;
/* Initialize TIM4 */
TIM_TimeBaseInit(TIM4, &TIM_BaseStruct);
/* Start count on TIM4 */
TIM_Cmd(TIM4, ENABLE);
}
void TM_PWM_Init(void) {
TIM_OCInitTypeDef TIM_OCStruct;
/* Common settings */
/* PWM mode 2 = Clear on compare match */
/* PWM mode 1 = Set on compare match */
TIM_OCStruct.TIM_OCMode = TIM_OCMode_PWM2;
TIM_OCStruct.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCStruct.TIM_OCPolarity = TIM_OCPolarity_Low;
/*
To get proper duty cycle, you have simple equation
pulse_length = ((TIM_Period + 1) * DutyCycle) / 100 - 1
where DutyCycle is in percent, between 0 and 100%
25% duty cycle: pulse_length = ((8399 + 1) * 25) / 100 - 1 = 2099
50% duty cycle: pulse_length = ((8399 + 1) * 50) / 100 - 1 = 4199
75% duty cycle: pulse_length = ((8399 + 1) * 75) / 100 - 1 = 6299
100% duty cycle: pulse_length = ((8399 + 1) * 100) / 100 - 1 = 8399
Remember: if pulse_length is larger than TIM_Period, you will have output HIGH all the time
*/
TIM_OCStruct.TIM_Pulse = 2099; /* 25% duty cycle */
TIM_OC1Init(TIM4, &TIM_OCStruct);
TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable);
TIM_OCStruct.TIM_Pulse = 4199; /* 50% duty cycle */
TIM_OC2Init(TIM4, &TIM_OCStruct);
TIM_OC2PreloadConfig(TIM4, TIM_OCPreload_Enable);
TIM_OCStruct.TIM_Pulse = 6299; /* 75% duty cycle */
TIM_OC3Init(TIM4, &TIM_OCStruct);
TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Enable);
TIM_OCStruct.TIM_Pulse = 8399; /* 100% duty cycle */
TIM_OC4Init(TIM4, &TIM_OCStruct);
TIM_OC4PreloadConfig(TIM4, TIM_OCPreload_Enable);
}
int main(void) {
/* Initialize system */
SystemInit();
/* Init leds */
TM_LEDS_Init();
/* Init timer */
TM_TIMER_Init();
/* Init PWM */
TM_PWM_Init();
while (1) {
}
}
Ví dụ 3
Ví dụ dưới đây cho phép thiết lập độ sáng của 4 đèn LED giảm dần trên các chân PD12, PD13, PD14, PD15.

#include "stm32f4xx.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_tim.h"
#include "misc.h"
void GPIOinit();
void TimerInit();
int main(void)
{
SystemInit();
GPIOinit();
TimerInit();
int brightness = 0;
int i;
while(1)
{
brightness++;
TIM4->CCR3 = 333 - (brightness + 0) % 333;
TIM4->CCR4 = 333 - (brightness + 166 / 2) % 333;
TIM4->CCR1 = 333 - (brightness + 333 / 2) % 333;
TIM4->CCR2 = 333 - (brightness + 499 / 2) % 333;
for(i=0;i < 50000; ++i);
}
}
void GPIOinit()
{
GPIO_InitTypeDef gpio_init;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
gpio_init.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
gpio_init.GPIO_Mode = GPIO_Mode_AF;
gpio_init.GPIO_Speed = GPIO_Speed_100MHz;
gpio_init.GPIO_OType = GPIO_OType_PP;
gpio_init.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOD, &gpio_init);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource12, GPIO_AF_TIM4);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource13, GPIO_AF_TIM4);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource14, GPIO_AF_TIM4);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource15, GPIO_AF_TIM4);
}
void TimerInit()
{
TIM_TimeBaseInitTypeDef time_init;
TIM_OCInitTypeDef oc_init;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
uint16_t PrescalerValue = (uint16_t)((SystemCoreClock / 2) / 21000000);
time_init.TIM_Period = 665;
time_init.TIM_Prescaler = PrescalerValue;
time_init.TIM_ClockDivision = 0;
time_init.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM4, &time_init);
oc_init.TIM_OCMode = TIM_OCMode_PWM1;
oc_init.TIM_OutputState = TIM_OutputState_Enable;
oc_init.TIM_Pulse = 0;
oc_init.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM4, &oc_init);
TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable);
oc_init.TIM_OutputState = TIM_OutputState_Enable;
oc_init.TIM_Pulse = 0;
TIM_OC2Init(TIM4, &oc_init);
TIM_OC2PreloadConfig(TIM4, TIM_OCPreload_Enable);
oc_init.TIM_OutputState = TIM_OutputState_Enable;
oc_init.TIM_Pulse = 0;
TIM_OC3Init(TIM4, &oc_init);
TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Enable);
oc_init.TIM_OutputState = TIM_OutputState_Enable;
oc_init.TIM_Pulse = 0;
TIM_OC4Init(TIM4, &oc_init);
TIM_OC4PreloadConfig(TIM4, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM4, ENABLE);
TIM_Cmd(TIM4, ENABLE);
}