1.脉冲宽度调制PWM(Pulse Width Modulation)是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。PWM可以理解为高低电平的占空比,即输出高电平时间与低电平时间的比值。PWM的应用是否广泛,比如在步进电机的控制中,可以通过PWM来控制电机的速度。PWM的原理图如下所示。
如图中所示,当CNT的值小于ARR时,I/O输出低电平;当CNT的值大于等于ARR时,I/O输出高电平。当CNT的值等于ARR时,CNT的值归零,然后重新向上计数(向上或向下计数可以自行设置)。CNT(TIMx_CNT寄存器是定时器的计数器,该寄存器存储了当前定时器的计数值。)。因此,通过修改自动装载寄存器ARR的值,就可以改变PWM的输出频率。
简单来说,就是设置了自动装载寄存器ARR的值,然后定时器计数CNT,根据CNT与CCRx的大小关系,使I/O输出不同的电平。
PWM周期:T = (ARR+1) / (CLK / (PSC+1)) = (ARR+1) * (PSC+1) / CLK。
PWM占空比:P = (VAL+1)/ TIM_p eriod。
其中,ARR和TIM_p eriod是定时器的自动重装值,PSC是预分频值,CLK是时钟频率值,VAL是自己设定的占空比数值。
2.相关寄存器介绍:(寄存器TIMx_CR1、TIMx_PSC、TIMx_ARR、TIMx_SR的介绍请查看:CSDN)
(1)捕获比较模式寄存器(TIMx_CCR1/2):TIMx_CCR1、TIMx_CCR2。
如图所示,第一行为输出时的设置,第二行是输入时的配置。
OCxM是模式设置位,由3位组成,可以控制7种模式。使用PWM模式时,必须设置为110/111。这两种模式的区别是输出的电平极性相反。
CCxS是通道方向的设置位(输入/输出),默认为0,即作为输出使用。
(2)捕获比较使能寄存器(TIMx_CCER):该寄存器控制着各个输入输出通道的开关。
本次实验中只使用到了CC1E位(输入/输出使能位),需要设置为1。
(3)捕获/比较寄存器(TIMx_CCR1~4):TIMx_CCR1、TIMx_CCR2、TIMx_CCR3、TIMx_CCR4。
在输出模式下,该寄存器的值与定时器的计数值CNT比较,并根据结果使I/O输出高低电平。
(4)刹车和死区寄存器(TIMx_BDTR):该寄存器只有高级定时器需要配置,普通定时器不需要配置此寄存器。本次实验中,实验的普通定时器TIM1,不需要配置此定时器。
如果想要高级定时器正常输出PWM,MOR为必须设置为1。
3.设计思路:首先,使能相应的时钟。然后,配置PA8为复用输出(TIM1_CH1的是和PA8复用的。同时,PA8外接了LED)。其次,设置TIM1的ARR和PSC。最后设置TIM1_CH1的PWM模式和通道方向,使能TIM1的CH1输出。
4.代码:
(1)delay:
#ifndef __DELAY_H
#define __DELAY_H
#include "stm32f10x.h"
void delay_us(uint32_t us); //ÑÓʱ΢Ãë
void delay_ms(uint32_t ms); //ÑÓʱºÁÃë
#endif
#include "delay.h"
void delay_us(uint32_t us)
{
uint32_t i;
//1.Ñ¡ÔñHCLKʱÖÓ£¬²¢ÉèÖõδðʱÖÓ¼ÆÊýÖµ
SysTick_Config(72);
for(i = 0;i < us;i++)
{
while(!((SysTick->CTRL) & (1 << 16))); //µÈ´ý¼ÆÊýÍê³É
}
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; //Ñ¡ÔñSTCLKʱÖÓÔ´£¬²¢Ê§Äܶ¨Ê±Æ÷
}
void delay_ms(uint32_t ms)
{
uint32_t i;
//1.Ñ¡ÔñHCLKʱÖÓÔ´£¬²¢ÉèÖõδðʱÖÓ¼ÆÊýÖµ
SysTick_Config(72000);
for(i = 0;i < ms;i++)
{
while(!((SysTick->CTRL) & (1 << 16))); //µÈ´ý¼ÆÊýÍê³É
}
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; //Ñ¡ÔñSTCLKʱÖÓÔ´£¬²¢Ê§Äܶ¨Ê±Æ÷
}
(2)led:
#ifndef __LED_H
#define __LED_H
#include "stm32f10x.h"
void LED_Init(void);
#endif
#include "led.h"
void LED_Init(void)
{
//¶¨Òå¶Ë¿ÚµÄ½á¹¹Ìå:
GPIO_InitTypeDef GPIO_InitStruct;
//1.ʹÄÜʱÖÓ£º
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOA,ENABLE);
//2.ÅäÖö˿ڽṹÌåµÄÏà¹ØÐÅÏ¢£º£¨LED1£©
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOD,&GPIO_InitStruct);
//ÅäÖÃLED0£º
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8;
GPIO_Init(GPIOA,&GPIO_InitStruct);
//³õʼʱ½«LEDµÄ¶Ë¿Ú¶¼ÖÃΪ1£¬¼´Ï¨Ãð
GPIO_SetBits(GPIOA,GPIO_Pin_8);
GPIO_SetBits(GPIOD,GPIO_Pin_2);
}
(3)pwm:
#ifndef __PWM_H
#define __PWM_H
#include "stm32f10x.h"
void PWM_Init(u16 arr,u16 psc);
#endif
#include "pwm.h"
/*±¾´ÎʵÑéÖÐÊÇʹÓÃTIM1Êä³öPWM£¬TIM1ʹÓÃÐèÒª¸´ÓÃPA8ΪÊä³ö*/
void PWM_Init(u16 arr,u16 psc)
{
//¶¨ÒåÏà¹ØµÄ½á¹¹Ì壺
GPIO_InitTypeDef GPIO_InitStrcture;
TIM_TimeBaseInitTypeDef TIM_InitStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
//1.ʹÄÜʱÖÓ£º
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1 | RCC_APB2Periph_GPIOA,ENABLE);
//2.ÅäÖÃGPIOA.8½á¹¹ÌåÐÅÏ¢£º
GPIO_InitStrcture.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStrcture.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStrcture.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStrcture);
//3.ÅäÖÃTIM1½á¹¹ÌåÐÅÏ¢£º
TIM_InitStructure.TIM_Period = arr;
TIM_InitStructure.TIM_Prescaler = psc;
TIM_InitStructure.TIM_ClockDivision = 0;
TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Down;
TIM_TimeBaseInit(TIM1,&TIM_InitStructure);
//4.ÅäÖÃTIM1_CH1µÄPWMģʽºÍͨµÀ·½Ïò£º
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM1,&TIM_OCInitStructure);
//5.ʹÄÜÏà¹Ø¼Ä´æÆ÷£º
TIM_CtrlPWMOutputs(TIM1,ENABLE); //ʹÄÜÖ÷Êä³ö
TIM_OC1PreloadConfig(TIM1,TIM_OCPreload_Enable); //CH1ԤװÔØʹÄÜ
TIM_ARRPreloadConfig(TIM1,ENABLE); //ʹÄÜTIMxµÄARRµÄԤװÔؼĴæÆ÷
TIM_Cmd(TIM1,ENABLE); //ʹÄÜTIM1
}
(4)main:
#include "stm32f10x.h"
#include "delay.h"
#include "led.h"
#include "pwm.h"
int main(void)
{
u8 flag = 1;
u16 pwm_value = 0;
LED_Init();
PWM_Init(899,0);
while(1)
{
delay_ms(20);
if(flag)
{
pwm_value ++;
}
else
{
pwm_value --;
}
if(pwm_value > 300)
{
//pwm_value = 0;
flag = !flag;
}
if(pwm_value == 0)
{
flag = !flag;
}
//ÉèÖÃTIM1±È½Ï/²¶»ñͨµÀ¼Ä´æÆ÷µÄÖµ£¬Í¨¹ýÐ޸ĴËÖµ¸Ä±äPWMµÄÕ¼¿Õ±È
TIM_SetCompare1(TIM1,pwm_value);
}
}
5.运行结果:可以看到LED的亮度在自动变化。
6.总结:PWM是通过微控制器的数字输出,控制模拟输出。通常可以利用定时器实现PWM。PWM可以控制I/O输出高低电平的时间,通过I/O输出高低电平的时间,就可以控制外部器件工作/不工作的时间,从而控制其速度或者亮度等。
配置PWM的基本步骤:
(1)使能对应I/O的时钟
(2)配置定时器,根据配置的定时器的自动重装载值ARR和PSC分频值,计算PWM产生的周期和频率
(3)设置定时器的PWM模式、通道方向,使能定时器输出。
(4)通过修改捕获/比较寄存器TIMx_CCR1的值来控制PWM占空比。当TIMx_CCR1的值小于定时器的CNT值时,端口输出低电平,但TIMx_CCR1的值大于等于定时器的CNT值时,输出高电平。