目录
1. PWM简介
2. PWM原理
3. 定时器PWM输出比较
4. 定时器PWM捕获/比较通道
5. PWM输出相关寄存器
5.1 捕获/比较模式寄存器 TIMx_CCMR1
5.2 捕获/比较使能寄存器 TIMx_CCER
5.3 捕获/比较寄存器 TIMx_CCR1~4
5.4 刹车(断路)和死区寄存器 TIMx_BDTR
6. 库函数配置PWM输出
7. 实验程序
7.1 main.c
7.2 pwm.c
7.3 pwm.h
1. PWM简介
脉冲宽度调制:PWM,是英文Pulse Width Modulation的缩写,简称脉宽调制,利用微处理器的数字输出(DAC)来对模拟电路进行控制的一种非常有效的技术。实质就是对脉冲宽度的控制。
2. PWM原理
如图是PWM的工作原理图:
我将通过以往中学数学的一个简单题型来引入PWM的具体工作原理该如何理解:每当看到PWM,总是想起中学数学的一种题型,题目大概意思是这样的:首先我拿到两个复杂的函数,需要先画出函数的图像,当画出两个函数图像时,会明显发现这两个函数交织在一起,通常这个时候出题人总会让我们判断交点所对应的值是多少,进而进行赋值写出分段函数;比如说 1<x<2时y=0; x=2时y=1;x=3时y=2; 3<x<4时y=3;
其实PWM原理就形似于一个分段函数:
如图,定时器工作在向上计数模式,也就是计数器这个时候是递增计数的;当计数器CNT的值小于CCRx时,输出低电平0(CNT<CCRx等同于上述的1<x<2,低电平0等同于y=0;输出低电平再次阐述了定时器的输出比较功能);当CNT大于CCRx时,输出高电平1;当CNT=ARR时,重新归零(等同于函数的结点值时赋值);然后重新向上计数,依次循环;这时IO口会跟据PWM输出的比较值产生一个高低电平的时序图;
当我们改变CCRx和ARR的值会有什么影响呢?
改变CCRx的值:会导致我们IO输出的低电平时间变小,因为总的定时器时间是不变的,理所当然的高电平1的时间会变长。相对专业的讲是改变了PWM输出的占空比;(占空比是一个脉冲周期内,高电平的时间与整个周期时间的比例)
改变ARR的值:改变了PWM输出的频率;(PWM频率的意思是指1秒钟内信号从高电平到低电平再回到高电平的次数)
3. 定时器PWM输出比较
我们将定时器分为四部分:选择时钟、时基电路、输入捕获、输出比较。
4. 定时器PWM捕获/比较通道
每个定时器有四个通道,每一个通道都有一个捕获比较寄存器;将计数器TIMx_CNT的值TIMx_CCRx寄存器的值进行比较,通过比较的结果输出高低电平,实现PWM输出信号。
每个捕获/比较通道均围绕一个捕获/比较寄存器(包括一个影子寄存器)、一个输入捕获阶段(包括数字滤波、多路复用和预分频器)、一个输出比较阶段(包括比较器和输出控制)组成。本章PWM输出就是利用定时器PWM输出比较功能实现的。这里我们将输入捕获和输出比较阶段都进行讲解;
极性的作用就是判断有效电平是高电平还是有效电平是低电平。
5. PWM输出相关寄存器
STM32 除了 TIM6 和 TIM7 ,其余的定时器都可以用来产生 PWM 输出。通用定时器可以同时产生 4 路的 PWM 输出。以下介绍的寄存器均是TIM14的相关寄存器。
5.1 捕获/比较模式寄存器 TIMx_CCMR1
捕获/比较模式寄存器 TIMx_CCMR1 (capture/compare mode register1)16位寄存器
因为该寄存器既具有输入捕获功能,又具有输出比较功能;所以配置寄存器的相关位会导致在输入模式和输出模式下的功能均不同。
位[1:0]:配置通道方向;也就是该寄存器为输出还是输入;
00:输出比较
01:输入捕获
注意:OCXX表示输出位配置(Output);ICXX表示输入位配置(Intput);
因为本实验程序中只使用了TIM14的1通道,所以只使用了该寄存器的低8位;但不意味着高8位就无效;多通道时,就会启动高8位;
5.2 捕获/比较使能寄存器 TIMx_CCER
捕获/比较使能寄存器 TIMx_CCER (capture/compare enable register)16位寄存器
对于该寄存器的使用,在该实验中需要使能该寄存器;
位0[CC1E]:捕获/比较 1 输出使能
0:关闭
1:开启
5.3 捕获/比较寄存器 TIMx_CCR1~4
捕获/比较寄存器 TIMx_CCR1~4 (capture/compare regiater 1)该寄存器一共有四个;分别对应1~4;本实验所使用的是TIM14定时器,该定时器只有1个该寄存器TIM14_CCR1;
5.4 刹车(断路)和死区寄存器 TIMx_BDTR
刹车(断路)和死区寄存器 TIMx_BDTR(break and dead-time register)16位寄存器 该寄存器是高级定时器下PWM主输出使能的,该实验并不会使用。
6. 库函数配置PWM输出
1. 开启TIM14和GPIO时钟,配置PF9选择复用功能AF9(TIM14)输出。
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM14,ENABLE); //TIM14时钟使能,才能使用定时器TIM14的功能
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE); //开启GPIOF时钟
GPIO_PinAFConfig(GPIOF,GPIO_PinSource9,GPIO_AF_TIM14); //因为STM32的所有引脚都可以当GPIO口,所以需要将GPIOF9复用为定时器TIM14
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //GPIO初始化设置模式为复用功能
GPIO_Init(); //GPIO初始化,GPIO口初始化并不只是上述一个复用功能;
2. 初始化TIM14,设置TIM14的ARR和PSC等参数。TIMx_ARR:自动重载寄存器 TIMx_PSC:预分频寄存器
TIM_TimeBaseInit(); //初始化TIM14
TIM_TimeBaseInitStructure.TIM_Period=arr; //设置自动重装载值arr;
……………………TIM_TimeBaseInitStructure初始化的其他参数配置
TIM_TimeBaseInit(TIM14,&TIM_TimeBaseInitStructure);//初始化TIMx
3. 设置TIM14_CH1的PWM模式,使能TIM14的CH1输出
根据对PWM输出原理图的学习,如果我们希望LED灯逐渐亮,又逐渐暗,呈现一个变化趋势。那么我们必须调控CCR1相对于一个频率内所占的比例,这样PWM输出高低电平时的时间也就不同(也可以理解为在一个10s周期内,我让LED亮7s,灭3s,那么循环呈现给视觉的效果就是灯相对比较亮;而如果我让LED亮3s,灭7s,结果呈现给视觉的效果就是LED比较暗)所以我们需要通过配置TIM14_CCMR1的相关位来控制TIM14_CH1。
PWM通道通过函数TIM_OC1Init()~TIM_OC4Init();如果使用1通道,那么所使用的函数就是TIM_OC1Init();
void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct); // PWM通道初始化函数
typedef struct
{
uint16_t TIM_OCMode; //设置模式是PWM还是输出比较
uint16_t TIM_OutputState; //设置输出比较使能
uint16_t TIM_OutputNState;
uint16_t TIM_Pulse;
uint16_t TIM_OCPolarity; //设置极性高低
uint16_t TIM_OCNPolarity;
uint16_t TIM_OCIdleState;
uint16_t TIM_OCNIdleState;
} TIM_OCInitTypeDef;
//其余未标记的均是使用高级定时器时才会用到的参数
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;//选择模式为PWM
TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;//比较输出使能
TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_Low;//输出极性低
TIM_OCInit(TIM14,&TIM_OCInitStructure)
4. 使能TIM14
TIM_Cmd(TIM14, ENABLE); //使能TIM14
5. 修改TIM14_CCR1来控制占空比
void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare2); //修改TIM14_CCR1占空比 第二个参数就是设置的占空比
对于其他通道分别是:
void TIM_SetComparex( x取值1 2 3 4 )
注:以上设置对大多数寄存器都是通用的;但是高级定时器想要输出PWM,必须还要设置一个MOE位(刹车(断路)和死区寄存器 TIMx_BDTR(break and dead-time register)16位寄存器 的第15位)使能主输出,否则不会输出PWM。
void TIM_CtrlPWMOutputs(TIM_TypeDef* TIMx, FunctionalState NewState) //高级定时器使能主输出函数
注意:
PWM模式下必须设置TIMx_CCMRx寄存器OCxPE位以使能相应的预装载寄存器:
TIM_ARRPreloadConfig(TIM14,ENABLE);//使能自动重装载寄存器
最后还有设置TIMx_CR1寄存器的ARPE位,(向上计数或中心对称模式)使能自动重装载的预装载寄存器:
TIM_OC1PreloadConfig(TIM14,ENABLE);//使能预装载寄存器
7. 实验程序
通过该实验程序可以控制LED灯从亮变暗,再从暗变亮;依次循环。
首先解释为什么实验程序内没有LED0相关的程序,最后却能使得LED0的亮度从亮变暗在变亮?
7.1 main.c
#include "stm32f4xx.h"
#include "delay.h"
#include "LED.h"
#include "BEEP.h"
#include "Key.h"
#include "usart.h"
#include "exti.h"
#include "iwdg.h"
#include "wwdg.h"
#include "Timer.h"
#include "pwm.h"
int main(void)
{
//占空比的意思就是在一个脉冲周期内,高电平的占整个周期的比例;调整设置的占空比就可以设置在一个脉冲周期内
//高低电平所占的比例,进而使得LED0亮暗交替。
unsigned int LED0PulseWidthModulation;//设置LED0占空比
unsigned int ARR=1;//用ARR的状态来判断一个周期是否结束,起始值为1
//如果ARR=1,表示一个周期回到了起点;如果ARR=0,表示一个周期达到了终点。
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组为2
delay_init(168);
TIM14_PWM_Init(500-1,84-1);
//设置重装载值为500,也就是计数器的峰值为500
//预分频值为84,定时器时钟为84M,所以计数频率为84M/84=1Mhz;PWM脉冲周期为频率分之一,同中学的T=1/f
//所以PWM脉冲周期等于1M/500=2Khz;
while(1)
{
delay_ms(10);
//之所以初始化ARR就是1,也就是初始化为脉冲周期的起点,是因为模式为向上,也就是递增;
//所以只能从起点开始递增,达到终点就开始递减。
if(ARR==1)//ARR==1表示脉冲周期回到了起点
LED0PulseWidthModulation++;//占空比++,高电平占脉冲周期的比例逐渐在增大,LED0越来越亮
else
LED0PulseWidthModulation--;//否则表示ARR==0,脉冲周期来到了终点,占空比越来越小,高电平比例越来越小,LED越来越暗
if(LED0PulseWidthModulation>300)//如果占空比大于300,默认达到了脉冲周期的终点,占空比要开始--,变暗
ARR=0;//ARR=0为上述程序中的else语句,占空比--;
if(LED0PulseWidthModulation==0)//当占空比减到0时,达到最暗,默认达到了脉冲周期的起点,占空口开始++;变量
ARR=1;//ARR=1是上述程序if判断下的语句,占空比++;
TIM_SetCompare1(TIM14,LED0PulseWidthModulation); //修改比较值,修改占空比
//注意该函数的第二个参数就是设置的占空比值
}
}
//此处之所以将占空比的值与300进行比较,是因为PWM的输出占空比达到这个值时,LED的亮度变化就不大了。
//PWM最大值可以设置为499
//此程序达到的效果是:从起点出发,LED灯越来越亮,当占空比达到300时,默认达到了峰值,LED灯越来越暗;按照这个流程依次进行循环。
7.2 pwm.c
#include "stm32f4xx.h"
#include "pwm.h"
//AutomaticReload:自动重装载值 PrioritySendCount:时钟预分频数
void TIM14_PWM_Init(u32 AutomaticReload,u32 PrioritySendCount) //初始化TIM14
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM14,ENABLE);//使能TIM14_CH1 1通道时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE);// 使能GPIOF引脚
GPIO_PinAFConfig(GPIOF,GPIO_PinSource9,GPIO_AF_TIM14);//引脚复用PF9引脚复用为TIM14的通道1
//GPIO的初始化函数,设置初始化的模式为复用
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF; //设置GPIO模式为复用 对应上述引脚复用PF9引脚复用为TIM14的通道1
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;
GPIO_Init(GPIOF,&GPIO_InitStructure);
//初始化TIM14定时器,设置预分频值和自动重装载值
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;//向上计数,也就是递增计数
TIM_TimeBaseInitStructure.TIM_Period=AutomaticReload;//自动重装载值
TIM_TimeBaseInitStructure.TIM_Prescaler=PrioritySendCount;//时钟预分频值
TIM_TimeBaseInit(TIM14,&TIM_TimeBaseInitStructure);
//设置TIM14的PWM模式
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_Low;//输出极性低
TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;//PWM调质模式1
TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;//输出比较使能
TIM_OC1Init(TIM14,&TIM_OCInitStructure);//初始化TIM14通道1
TIM_ARRPreloadConfig(TIM14,ENABLE);//使能自动重装载寄存器
TIM_OC1PreloadConfig(TIM14,ENABLE);//使能预装载寄存器
TIM_Cmd(TIM14,ENABLE);//使能TIM14
}
7.3 pwm.h
#ifndef _PWM__H_
#define _PWM__H_
void TIM14_PWM_Init(u32 AutomaticReload,u32 PrioritySendCount);
#endif