一、PWM介绍
PWM(Pulse Width Modulation——脉冲宽度调制)是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用在测量、通信、功率控制与变换的许多领域中。
PWM技术中有个重要的概念,称为占空比(Duty Cycle),即在一个周期内,高电平占整个信号周期的百分比。PWM信号也就是占空比可变的周期性方波信号。
PWM是定时器输出比较典型的应用。除了基本定时器外,其他定时器都可以产生PWM信号输出。通用定时器可以同时产生4路PWM,高级定时器可以同时产生多达7路PWM。
以通用定时器为例,每个定时器有4路PWM输出通道:TIMx_CH1、TIMx_CH2、TIMx_CH3、TIMx_CH4。每个通道都对应一个捕获/比较寄存器:TIMx_CCR1、TIMx_CCR2、TIMx_CCR3、TIMx_CCR4。
二、PWM实现原理
计数器CNT的值与捕获/比较寄存器TIMx_CCRx的值相比较,根据比较结果决定输出电平高低,从而实现PWM信号输出。
现在假设定时器计数模式为向上计数,定时器自动重装载寄存器TIMx_ARR的值为ARR,捕获/比较值寄存器TIMx_CCRx的值为CCRx。
计数器CNT的值从0计数到自动重载值ARR后又从0开始计数,故自动重装载寄存器TIMx_ARR的值决定了PWM信号的周期。
假设计数器值小于CCRx值时,输出低电平;计数器值大于CCRx值时,输出高电平。PWM信号输出效果见下图。
根据上图对比可以看出CCRx值不同,PWM信号的占空比也不同,即CCRx值决定了PWM信号的占空比。
当然也可以设置为当计数值小于CCRx值时,输出高电平,计数值大于CCRx值时,输出低电平。效果见下图。
三、PWM输出通道结构
计数器CNT的值与捕获/比较寄存器CCRx的值相比较,根据比较结果通过引脚输出PWM信号。根据比较结果是输出高电平还是低电平则需通过相关寄存器进行设置。具体输出通道设置见下图。
(1)TIMx_CCMR1寄存器的OC1M[2:0]位,设置输出模式
110:PWM模式1
在向上计数时,一旦TIMx_CNT < TIMx_CCR1时通道1为有效电平,否则为无效电平;在向下计数时,一旦TIMx_CNT > TIMx_CCR1时通道1为无效电平,否则为有效电平。
111:PWM模式2
在向上计数时,一旦TIMx_CNT < TIMx_CCR1时通道1为无效电平,否则为有效电平;在向下计数时,一旦TIMx_CNT > TIMx_CCR1时通道1为有效电平,否则为无效电平。
(2)设置通道输出极性
0:高电平有效
1:低电平有效
(3)输出信号使能,信号能否输出到对应引脚
0:关闭
1:开启
【举例说明】(1)向上计数,PWM模式1,低电平有效;
(2)向上计数,PWM模式2,高电平有效;
两种方式输出信号效果见下图。
三、PWM相关库函数
1、PWM输出初始化库函数
函数原型:
void TIM_OCxInit(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);(x=1,2,3,4)
函数参数:
TIMx:TIM1~TIM5,TIM8~TIM14
TIM_OCInitStruct:结构体指针
typedef struct
{
uint16_t TIM_OCMode; // PWM模式,PWM模式1或PWM模式2
uint16_t TIM_OutputState; // 设置比较输出使能/失能
uint16_t TIM_OutputNState; // 该参数仅用于高级定时器
uint16_t TIM_Pulse; // 写入捕获比较寄存器的值,0x00~0xFF
uint16_t TIM_OCPolarity; // 比较输出极性
uint16_t TIM_OCNPolarity; //该参数仅用于高级定时器
uint16_t TIM_OCIdleState; //该参数仅用于高级定时器
uint16_t TIM_OCNIdleState; //该参数仅用于高级定时器
} TIM_OCInitTypeDef;
1)TIM_OCMode:
TIM_OCMode_PWM1:PWM模式1
TIM_OCMode_PWM2:PWM模式2
2)TIM_OutputState:
TIM_OutputState_Enable:比较输出使能
TIM_OutputState_Disable:比较输出禁止
3)TIM_OCPolarity:
TIM_OCPolarity_High:高电平有效
TIM_OCPolarity_Low:低电平有效
使用方法实例:
TIM_OCInitTypeDef TIM_OCInitStructure;//定义结构体
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //PWM模式1
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能TIM_OCInitStructure. TIM_Pulse=0;//比较值
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性高电平
TIM_OC2Init(TIM3, &TIM_OCInitStructure); //初始化TIM3比较输出通道2
2、设置比较值库函数,外部改变TIM_Pulse值,即改变CCR的值
函数原型:
void TIM_SetComparex(TIM_TypeDef* TIMx, uint16_t Compare); (x=1,2,3,4)
函数参数:
TIMx:TTIM1~TIM5,TIM8~TIM14
Compare:值范围0x00~0xFF
使用方法示例:TIM_SetCompare1(TIM2,1000);//定时器2通道1,设定比较值100
3、使能预装载寄存器库函数
函数原型:
void TIM_OCxPreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);(x=1,2,3,4)
使用方法示例:TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable);//使能定时器2通道1预装载寄存器
4、使能自动重装载寄存器库函数
函数原型:
void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);
使用方法示例:TIM_ARRPreloadConfig(TIM2,ENABLE);
【说明】在PWM应用中,我们经常需要在运行时改变占空比(CCRx寄存器)和周期(ARR寄存器),如果直接写入这些寄存器,新值会立即生效,这可能会导致当前输出信号异常。为了避免这种情况,我们需要使用预装载寄存器和影子寄存器的机制。写入的新值会下一个周期生效。
五、PWM使用步骤
1、使能定时器时钟和GPIO口时钟
2、配置PWM引脚,选择引脚、模式等
3、配置时基单元,设置重装载值、预分频值、计数模式等
4、配置比较输出结构体,调用TIM_OCxInit(TIMx,&TIM_OCInitStruct)函数完成初始化
5、使能预装载寄存器
TIM_OCxPreloadConfig(TIMx, TIM_OCPreload_Enable);
6、使能自动重装载寄存器
7、TIM_ARRPreloadConfig(TIMx, ENABLE);
8、使能定时器TIM_Cmd(TIMx,ENABLE);
9、不断改变比较值CCRx,达到不同的占空比效果
TIM_SetComparex(TIMx,Compare);
示例代码:以定时器14、输出通道1为例,对应引脚PF9
void TIM14_PWM_Init(){GPIO_InitTypeDef GPIO_InitStruct; //GPIO口结构体TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;//时基结构体TIM_OCInitTypeDef TIM_OCInitStruct;//比较输出结构体/*1-开定时器和GPIO口时钟*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM14, ENABLE);RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE);/*2-配置定时器输入引脚*/GPIO_PinAFConfig(GPIOF,GPIO_PinSource9,GPIO_AF_TIM14);//复用功能配置GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9; //选择A2引脚GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF; //复用功能模式GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz; //输出速度GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; //推挽输出GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; //GPIO_Init(GPIOF, &GPIO_InitStruct);/*3-时基配置*/TIM_TimeBaseStruct.TIM_Period = 1000-1;//自动重装载ARR值TIM_TimeBaseStruct.TIM_Prescaler = 84-1;//预分频PSC值 10KHzTIM_TimeBaseStruct.TIM_ClockDivision=TIM_CKD_DIV1;//1分频TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;//向上计数模式TIM_TimeBaseInit(TIM14, &TIM_TimeBaseStruct);//完成初始化/*4-比较输出配置*/TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM2; //PWM模式1TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性高电平TIM_OC1Init(TIM14, &TIM_OCInitStruct); //初始化TIM5比较输出通道3/*5-使能预装载寄存器*/TIM_OC1PreloadConfig(TIM14, TIM_OCPreload_Enable);/*6-使能自动重装载寄存器*/TIM_ARRPreloadConfig(TIM14, ENABLE);/*7-使能定时器*/TIM_Cmd(TIM14,ENABLE);}
六、PWM应用之呼吸灯(开发板上的LED灯连接PF9,PWM通道1)
主函数
void delay_ms(unsigned int n){SysTick->CTRL = 0; // Disable SysTickSysTick->LOAD = n * 21 * 1000 - 1; // Count from 255 to 0 (256 cycles),0算一次,所以要减1SysTick->VAL = 0; // Clear current value as well as count fla// 0位控制使能,2位控制选择FCLK(1)还是STCLK(1),这里选择stclkSysTick->CTRL = 1; // Enable SysTick timer with processor clockwhile ((SysTick->CTRL & 0x00010000)==0);// Wait until count flag is set,控制寄存器的第16位用于检测是否计数结束SysTick->CTRL = 0; // Disable SysTick}uint16_t cnt = 0;int main(void){TIM14_PWM_Init();while (1) {for(cnt = 0; cnt < 999; cnt++){TIM_SetCompare1(TIM14, cnt);delay_ms(5);}}}
效果

