Stm32 第三十三讲:PWM输出
PWM简介
脉冲宽度调制((Pulse Width Modulation),简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。
STM32的定时器除了TIM6和7,其他的定时器都可以用来产生PWM输出。其中高级定时器TIM1和TIM8可以同时产生多达7路的PWM输出。而通用定时器也能同时产生多达4路的PWM输出,这样,STM32最多可以产生30路PWM输出。
PWM输出相关寄存器
捕获/比较模式寄存器TIMx_CCMR1/2
该寄存器总共有两个,TIMx_CCMR1控制CH1和CH2,TIMx_CCMR2控制CH3和CH4。该寄存器各位描述如图:
该寄存器的有些位在不同模式下,功能不一样,所以在上图把寄存器分成了2层,上面一层对应输出而下面的对应输入。在这里只说明模式设置为OCxM,此部分由3位组成,总共可以配置为7中模式,在本篇中使用PWM模式,所以这3位必须设置为110/111,这两种PWM模式的区别就是输出电平的极性相反。
PWM模式
脉冲宽度调制模式可以产生一个由TIMx_ARR寄存器确定频率, 由TIMx_CCRx寄存器确定占空比的信号。
在TIMx_CCMRx寄存器中的OCxM位写入’110’(PWM模式1)或’111’(PWM模式2),能够独立地设置每个OCx输出通道产生一路PWM。必须设置TIMx_CCMRx寄存器OCxPE位以使能相应的预装载寄存器,最后还要设置TIMx_CR1寄存器的ARPE位,(在向上计数或中心对称模式中)使能自动重装载的预装载寄存器。
相关库函数:
void TIM_OC1PreloadConfig(TIM_TypeDef TIMx, uint16_t TIM_OCPreload);
void TIM_ARRPreloadConfig(TIM_TypeDef TIMx, FunctionalState NewState);
PWM模式1和模式2的区别
以寄存器TIMx_CCMR1的OC1M[2:0]位来分析:
PWM模式1的一个例子
捕获/比较使能寄存器TIMx_CCER
该寄存器控制着各个输入输出通道的开关。
在这里只介绍两位,第一个是CCxE位,该位是输入/捕获x输出使能位,要想PWM从IO口输出,这个位必须设置为1。第二个是CCxP位,该位控制着输入/捕获x输出极性。0:高电平有效,1:低电平有效。
捕获/比较寄存器(TIMx_CCR1~4)
该寄存器总共有4个,对应4个输出通道CH1~4。下面以TIMx_CCR1为例介绍,该寄存器的各位描述如下图:
在输出模式下,该寄存器的值与CNT的值比较,根据比较结果产生相应动作。可以利用这点来修改这个寄存器的值,就可以控制PWM的输出脉宽了。
重映射寄存器AFIO_MAPR
该寄存器各位描述如下图:
STM32定时器3输出通道引脚
STM32 PWM工作过程
以通道1为例:
CCR1:捕获比较(值)寄存器(x=1,2,3,4):设置比较值。
CCMR1->OC1M[2:0]位:对于PWM方式下,用于设置PWM模式1[110]或者PWM模式2[111]。
CCER->CC1P位:输入/捕获1输出极性。0:高电平有效,1:低电平有效。
CCER->CC1E位:输入/捕获1输出使能。0:关闭,1:打开。
自动重载的预装载寄存器
简单来说,ARPE=1时,ARR立即生效;ARPE=0,ARR下个比较周期生效。
相关库函数:
void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);
PWM相关库函数
PWM输出库函数
void TIM_OCxInit(TIM_TypeDef TIMx, TIM_OCInitTypeDef TIM_OCInitStruct)
typedef struct
{
uint16_t TIM_OCMode;//PWM模式1或者模式2
uint16_t TIM_OutputState;//输出使能or使能
uint16_t TIM_OutputNState;
uint16_t TIM_Pulse;//比较值,写CCRx
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_PWM2;//PWM模式2
TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;//比较输出使能
TIM_OCInitStructure.TIM_Pulse=100;
TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;//输出极性:TIM输出比较极性为高
void TIM_OC1Init(TIM3,&TIM_OCInitStructure);//根据T指定的参数初始化外设TIM3 OC2
设置比较值函数
void TIM_SetComparex(TIM_TypeDef* TIMx, uint16_t Compare1);
使能输出比较预装载寄存器
void TIM_OCxPreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
使能自动重装载的预装载寄存器允许位
void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);
PWM输出配置步骤
①. 使能定时器3和相关IO口时钟。
- 使能定时器3时钟:RCC_APB1PeriphClockCmd();
- 使能GPIOB时钟:RCC_APB2PeriphClockCmd();
②. 初始化IO口位复用功能输出。函数:GPIO_Init();
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
③. 这里把PB5用作定时器的PWM输出引脚,所以要重映射配置,所以需要开启AFIO时钟。同时配置重映射。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3,ENABLE);
④. 初始化定时器:ARR,PSC等。
TIM_TimeBaseInit();
⑤. 初始化输出比较参数。
TIM_OC2Init();
⑥. 使能预装载寄存器。
TIM_OC2PreloadConfig(TIM3,TIM_OCPreload_Enable);
⑦. 使能定时器。
TIM_Cmd();
⑧. 不断改变比较值CCRx,达到不同的占空比效果。
TIM_SetCompare2();
PWM输出实验
要求
使用定时器3的PWM功能,输出占空比可变的PWM波,用来驱动LED灯,从而达到LED[PB5]亮度由暗变亮,又从亮变暗,如此循环。
下面程序都是在定时器中断实验的基础上编写的,有些函数在此实验中并没有用到。
by库函数
timer.h
#ifndef __TIMER_H
#define __TIMER_H
#include "sys.h"
void TIM3_Int_Init(u16 arr, u16 psc);
void TIM3_PWM_Init(u16 arr, u16 psc);
#endif
timer.c
#include "timer.h"
#include "sys.h"
#include "led.h"
void TIM3_Int_Init(u16 arr, u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitSturc;
NVIC_InitTypeDef NVIC_InitStucture;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
TIM_TimeBaseInitSturc.TIM_Period=arr;//重装载值
TIM_TimeBaseInitSturc.TIM_Prescaler=psc;//预分频系数
TIM_TimeBaseInitSturc.TIM_CounterMode=TIM_CounterMode_Up;//向上计数模式
TIM_TimeBaseInitSturc.TIM_ClockDivision=TIM_CKD_DIV1;//时钟分频因子为1
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitSturc);
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);//使能TIM3的更新中断
//中断优先级NVIC设置
NVIC_InitStucture.NVIC_IRQChannel=TIM3_IRQn;//TIM3中断通道
NVIC_InitStucture.NVIC_IRQChannelPreemptionPriority=2;//抢占优先级2
NVIC_InitStucture.NVIC_IRQChannelSubPriority=2;//响应优先级2
NVIC_InitStucture.NVIC_IRQChannelCmd=ENABLE;//中断使能
NVIC_Init(&NVIC_InitStucture);
TIM_Cmd(TIM3,ENABLE);//TIM3定时器初始化
}
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET)//通过中断标志位判断是否发生了定时器中断
{
LED1=!LED1;
TIM_ClearITPendingBit(TIM3,TIM_IT_Update);//清除中断标志位
}
}
void TIM3_PWM_Init(u16 arr, u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitSturc;
TIM_OCInitTypeDef TIM_OCInitSturc;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);//使能定时器3时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO,ENABLE);//使能PB和AFIO复用功能时钟
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_5;//PB.5
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//复用推挽输出
GPIO_Init(GPIOB,&GPIO_InitStructure);//初始化GPIOB.5
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3,ENABLE);//定时器3部分重映射
TIM_TimeBaseInitSturc.TIM_Period=arr;//重装载值
TIM_TimeBaseInitSturc.TIM_Prescaler=psc;//预分频系数
TIM_TimeBaseInitSturc.TIM_CounterMode=TIM_CounterMode_Up;//向上计数模式
TIM_TimeBaseInitSturc.TIM_ClockDivision=TIM_CKD_DIV1;//时钟分频因子为1
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitSturc);
TIM_OCInitSturc.TIM_OCMode=TIM_OCMode_PWM2;//模式为PWM2
TIM_OCInitSturc.TIM_OutputState=TIM_OutputState_Enable;//比较输出使能
TIM_OCInitSturc.TIM_OCPolarity=TIM_OCPolarity_High;//有效极性为高
TIM_OC2Init(TIM3,&TIM_OCInitSturc);
TIM_OC2PreloadConfig(TIM3,TIM_OCPreload_Enable);//使能预装载寄存器
TIM_Cmd(TIM3,ENABLE);//使能定时器3(TIM3)
}
main.c
#include "stm32f10x.h"
#include "timer.h"
#include "sys.h"
#include "delay.h"
#include "led.h"
int main(void)
{
u16 led0pwmval=0;
u8 dir=1;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//NVIC中断优先级分组
delay_init();//延时函数初始化
LED_Init();//LED初始化函数
TIM3_PWM_Init(899,0);//不分频,PWM频率=72000000/900=80KHz
while(1)
{
delay_ms(10);
if(dir)led0pwmval++;
else led0pwmval--;
if(led0pwmval>300)dir=0;
if(led0pwmval==0)dir=1;
TIM_SetCompare2(TIM3,led0pwmval);
}
}
其实led0pwmval的值最大是可以设置到899的,但是其实到300之后led的亮度就不会再明显变化了,所以设置了值为300。
by寄存器
timer.h
#ifndef __TIMER_H
#define __TIMER_H
#include "sys.h"
void TIM3_Int_Init(u16 arr, u16 psc);
void TIM3_PWM_Init(u16 arr, u16 psc);
#endif
```
#### timer.c
```c
//
#include "timer.h"
#include "led.h"
#include "sys.h"
void TIM3_Int_Init(u16 arr, u16 psc)
{
RCC->APB1ENR|=1<<1;//TIM3定时器时钟使能
TIM3->ARR=arr;//TIM3自动重装载值
TIM3->PSC=psc;//设置预分频值
TIM3->DIER|=1<<0;//允许TIM3更新中断
MY_NVIC_Init(2,2,TIM3_IRQn,2);//TIM3 NVIC中断设置
TIM3->CR1|=0x01;//使能TIM3计数器、向上计数
}
void TIM3_IRQHandler(void)
{
if(TIM3->SR&0X0001)//检测是否发生溢出中断
{
LED1=!LED1;
}
TIM3->SR&=~(1<<0);//清除中断标志位
}
void TIM3_PWM_Init(u16 arr, u16 psc)
{
RCC->APB2ENR|=1<<3;//PB口时钟使能
RCC->APB2ENR|=1<<0;//AFIO口时钟使能
RCC->APB1ENR|=1<<1;//TIM3时钟使能
GPIOB->CRL&=0XFF0FFFFF;
GPIOB->CRL|=0X00B00000;//PB.5复用功能推挽输出
AFIO->MAPR&=0XFFFFF0FF;
AFIO->MAPR|=0X00000800;//PB.5设置为重映射
TIM3->ARR=arr;//TIM3自动重装载值
TIM3->PSC=psc;//设置预分频值
TIM3->CCMR1&=0X00FF;
TIM3->CCMR1|=0X7000;//PWM模式2
TIM3->CCMR1|=1<<11;//预装载寄存器使能
TIM3->CCER|=1<<4;//输出/捕获2使能,高电平有效。
TIM3->CR1|=0x01;//TIM3向上计数,并使能定时器
}
main.c
#include "sys.h"
#include "delay.h"
#include "timer.h"
#include "led.h"
int main(void)
{
u16 led0pwmval=0;
u8 dir=1;
Stm32_Clock_Init(9); //系统时钟设置
delay_init(72); //延时初始化
LED_Init(); //初始化与LED连接的硬件接口
TIM3_PWM_Init(899,0); //不分频。PWM频率=72000/(899+1)=80Khz
while(1)
{
delay_ms(10);
if(dir)led0pwmval++;
else led0pwmval--;
if(led0pwmval>300)dir=0;
if(led0pwmval==0)dir=1;
TIM3->CCR2=led0pwmval;
}
}