使用STM32定时器实现呼吸灯效果并输出PWM波(BJTU)

一、原理

1、PWM(Pulse Width Modulation)脉冲宽度调制

一种利用脉冲宽度,即占空比实现对模拟信号进行仿真的技术,即是对模拟信号电平进行数字表示的方法

2、占空比(Duty Cycle)

指在一个周期内,高电平时间占整个信号周期的百分比,即高电平时间与周期的比值

占空比=Tp/T

其中Tp指一个周期内高电平时间,T表示的是周期

3、​​​​​定时器输出比较功能与实现(PWM)

        (1)PWM的工作原理

        *STM32中每个定时器有4个输入/输出通道:TIMx_CH1-TIMx_CH4

        *每个通道对应1个捕获/比较寄存器TIMx_CRRx,将寄存器值和计数器值相比较,通过比较结果输出高低电平,从而得到PWM信号

        *脉冲宽度调制模式可以产生一个由TIMx_ARR寄存器确定频率、由TIM_CRRx寄存器确定占空比的信号

        (2)原理流程及示意图

        *在PWM的一个周期内,定时器从0开始 向上计数,在0-t1时间段,定时器计数器 TIMx_CNT值小于TIMx_CCRx值,输出 低电平;

        *在t1-t2时间段,定时器计数器 TIMx_CNT值大于TIMx_CCRx值,输出 高电平;

        *当定时器计数器的值TIMx_CNT达到 ARR时,定时器溢出,重新从0开始向上 计数,如此循环。

        (3)​​​​​​​实际输出电平

实际输出的电平是由输出极性和电平是否有效共同决定的。

举例:如果此时电平为有效,且配置为输出高级性,那么实际输出的电平是高;如果此时电平为有效,且配置为输出低级性,那么实际输出的电平是低。

STM32中PWM共有两种输出模式:PWM1和PWM2

假设此时配置为PWM模式2,输出高级性,如图所示,当TIMx_CNT<TIMx_CCRx的时候,是无效电平,那么输出为0,即低电平;当TIMx_CNT>TIMx_CCRx的时候,是有效电平,那么输出为1,即高电平。

初始状态下:

PWM1 Low  ——高

PWM2 High ——高

二、实验案例1

内容及要求

(1)利用TIM2的通道2输出PWM信号,控制LED发光管H40亮灭;

(2)无按键操作时,将PWM周期、高电平时间交替显示在4位LED数码管上(单位

ms),交替显示频率1Hz左右;

(3)按键操作更改PWM周期或占空比,并将更改结果实时显示在4位LED数码管上:

利用KEY1(减少)和KEY2(增加)调整PWM周期; KEY3(减少)和KEY4(增加)

调整PWM占空比(0-100%);PWM最小周期为100ms,最大周期为1000ms。

程序流程图如下:

部分代码演示(仅供参考):

1、定时器配置

#include "timer.h"
#include "stm32f10x.h"

void TIM2_PWM_Init(u16 arr, u32 psc)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_OCInitTypeDef  TIM_OCInitStructure;

	// 配置PWM输出通道,开启TIM2时钟-----------------------------------------------------------------------------
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);  // 使能定时器TIM2的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);  // 使能GPIOA和AFIO时钟
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;  // GPIO引脚设置PA1
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  // 复用推挽输出模式
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);  // 初始化GPIOA

  // TIM2初始化------------------------------------------------------------------------------------------------
	TIM_TimeBaseStructure.TIM_Period = arr;   // 设置自动重载值
	TIM_TimeBaseStructure.TIM_Prescaler = psc;  // 设置用来作为TIM2时钟频率的预分频值
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;  // 设置时钟分割
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  // TIM向上计数模式
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);  // 初始化TIM2;

	// 设置TIM2_CH2的PWM模式,使能TIM2的CH2输出--------------------------------------------------------------------
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;  // 设置定时器模式:PWM1模式
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;  // 比较输出使能
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;  // 设置输出极性
	TIM_OCInitStructure.TIM_Pulse=0;  // 设置占空比,由于main函数里会重新初始化,这里取0
	TIM_OC2Init(TIM2, &TIM_OCInitStructure);  // 初始化TIM2 OC2
	TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable);  // 使能TIM2在CCR2上的预装载寄存器
	TIM_ARRPreloadConfig(TIM2,ENABLE);  
	
	TIM_Cmd(TIM2, ENABLE);  // 使能TIM2
}

2、按键配置

#include "stm32f10x.h"
#include "key.h"
#include "sys.h" 
#include "delay.h" 
								    
//按键初始化函数
void KEY_Init(void) //IO初始化
{ 
 	GPIO_InitTypeDef GPIO_InitStructure;
 
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOC,ENABLE);//使能PORTA,PORTC时钟

	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3; //KEY2 KEY1 KEY4对应引脚
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置成上拉输入
 	GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化GPIOC1,2,3

	//初始化 WK_UP-->GPIOA.0	  下拉输入
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_0;//KEY3对应引脚
	//GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA0设置成输入,默认下拉	  
	GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.0

}

//按键处理函数
//返回按键值
//mode:0,不支持连续按;1,支持连续按;
//0,没有任何按键按下
//1,KEY0按下
//2,KEY1按下
//3,KEY2按下 
//4,WKUP按下 WK_UP
//注意此函数有响应优先级,KEY0>KEY1>KEY2>WK_UP!!
u8 KEY_Scan(u8 mode)
{	 
	static u8 key_up=1;//按键按松开标志
	if(mode)key_up=1;  //支持连按		  
	if(key_up&&(KEY1==0||KEY2==0||KEY3==0||KEY4==0))
	{
		delay_ms(10);//去抖动 
		key_up=0;
		if(KEY1==0)return 1;
		else if(KEY2==0)return 2;
		else if(KEY3==0)return 3;
		else if(KEY4==0)return 4;
	}else if(KEY1==1&&KEY2==1&&KEY3==1&&KEY4==1)key_up=1; 	    
 	return 0;// 无按键按下
}

3、主程序设计

#include "stm32f10x.h"
#include "timer.h" 
#include "delay.h"
#include "key.h"
#include "exti.h"
#include "74HC595_LED.h"

u16 ccr=5000;  // 设置高电平时间为5000(预分频后为500ms)
u16 arr=10000;  // 设置定时器设置自动重载值(即周期)为10000(预分频后为1s)
float pulse=0.5;  // 设置占空比
uint8_t u8DispBuf[5]={0,0x3f,0x06,0x4f,0x66};


//------------------------------------------------------------------------------------------


// 主函数设计-------------------------------------------------------------------------------
int main(void)
{
  Init74HC595();  // 数码管初始化
	delay_init();  // delay函数初始化
	KEY_Init();  // 按键初始化
	EXTIX_Init();  // 外部中断初始化
	TIM2_PWM_Init(pulse*arr,7200-1);  // 定时器输出PWM波初始化
	TIM_SetAutoreload(TIM2,arr);	//更新定时器初始周期为1000ms
  while(1)
  {
		int i=0;
		
		TIM_SetCompare2(TIM2,ccr);  // 配置高电平时间				
    ccr=pulse*arr;									//更新ccr的值		
		TIM_SetCompare2(TIM2,ccr);  // 配置高电平时间		
		
    for(i = 0;i <100;i++)  // 数码管显示arr/10的值,即显示周期,显示0.5s
    {
      u8DispBuf[4]=(arr/10)%10;
      u8DispBuf[3]=(arr/100)%10;
      u8DispBuf[2]=(arr/1000)%10;
      u8DispBuf[1]=(arr/10000)%10;
      DispUpdate();
    }		
    for(i = 0;i <100;i++)  // 数码管显示ccr/10的值,即显示高电平时长,显示0.5s
    {
      u8DispBuf[4]=(ccr/10)%10;
      u8DispBuf[3]=(ccr/100)%10;
      u8DispBuf[2]=(ccr/1000)%10;
      u8DispBuf[1]=(ccr/10000)%10;
      DispUpdate();
    }
	}
}



// 按键实现外部中断来调整周期和占空比--------------------------------------------------------------------

 
// 外部中断1服务程序
void EXTI1_IRQHandler(void)
{
	delay_ms(10);  // 消抖
	if(KEY2==0 && arr<10000)	
	{
    arr+=1000;
		TIM_SetAutoreload(TIM2,arr);  // 重装arr
	}		 
	EXTI_ClearITPendingBit(EXTI_Line1);  //清除LINE1上的中断标志位  
}

//外部中断2服务程序
void EXTI2_IRQHandler(void)
{
	delay_ms(10);  // 消抖
	if(KEY1==0 && arr>1000)	
	{				 
    arr-=1000;  // 周期减少500(即减少50ms)
		TIM_SetAutoreload(TIM2,arr);  // 重装arr
	}
	EXTI_ClearITPendingBit(EXTI_Line2);  //清除LINE2上的中断标志位  
}

// 外部中断0服务程序 
void EXTI0_IRQHandler(void)
{
	delay_ms(10);  // 消抖
	if(KEY3==0 && pulse>0)  
	{				 
		pulse-=0.2;  //占空比-10%
//  TIM_SetCompare2(TIM2,ccr);  此处不用重设高电平时间,在main函数里会进行重设
	}
	EXTI_ClearITPendingBit(EXTI_Line0); //清除LINE0上的中断标志位  
}


//外部中断3服务程序
void EXTI3_IRQHandler(void)
{
	delay_ms(10);  // 消抖
	if(KEY4==0 && pulse<1)
	{
    pulse+=0.2;  // 占空比+10%
//  TIM_SetCompare2(TIM2,ccr);  此处不用重设高电平时间,在main函数里会进行重设
		
	}		 
	EXTI_ClearITPendingBit(EXTI_Line3);  //清除LINE3上的中断标志位  
}

三、呼吸灯

实现原理

        1. 视觉暂留

人眼在观察景物时,光信号传入大脑神经,需经过一段短暂的时间,光的作用结束后,视觉形象并不立即消失,这种残留的视觉称“后像”,视觉的这一现象则被称为“视觉暂留”。

        2.呼吸灯原理

利用人眼的视觉暂留效应,将定时器的定时周期设定为20ms,通过PWM调制,改变占空比控制一个周期内高低电平的持续时间比例,并将PWM波输出给LED灯。

在这个定时周期下可以体现人眼的视觉暂留效果,此时人眼无法分辨高低电平的变换,因此人眼看见的LED灯始终处于亮/灭的两种状态。但可以通过改变高低电平持续时间的比例,即改变占空比,调节LED灯的亮暗程度。

当占空比低时,高电平持续时间短,在视觉暂留下体现出灯的亮度较低,反之则较高。通过持续循环改变占空比,即可以实现呼吸灯的效果。  

代码演示(仅供参考):

1、定时器配置(同上)

2、呼吸灯主函数代码

#include "stm32f10x.h"
#include "timer.h" 
#include "delay.h"


// 主函数设计-------------------------------------------------------------------------------
int main(void)
{
	u8 pwd=1;  //用于切换渐变状态
	int CCR=0;  //比较寄存器值CRR
	delay_init();  // delay函数初始化
	TIM2_PWM_Init(200-1,7200-1);  // 定时器输出PWM波初始化,一个定时周期为20ms
  while(1)
  {
		if(pwd)  //状态1:递减
		{
			CCR++;
			TIM_SetCompare2(TIM2,CCR);  // 配置高电平时间					
			delay_ms(10);  //使LED灯亮一段时间,让实验结果更加明显
			if(CCR>200)pwd=0;
		}
		else  //状态2:递增
		{
			CCR--;
			TIM_SetCompare2(TIM2,CCR);  // 配置高电平时间		
			delay_ms(10);			
			if(CCR==0)pwd=1;
		}
	}
}

作者:LINOTGY0411

物联沃分享整理
物联沃-IOTWORD物联网 » 使用STM32定时器实现呼吸灯效果并输出PWM波(BJTU)

发表评论